From e6b4efdb1b311808d067ded3343c54db17f45d81 Mon Sep 17 00:00:00 2001 From: noot Date: Tue, 6 Apr 2021 18:20:25 -0400 Subject: [PATCH 01/40] don't start justification requesting if syncing --- dot/network/sync_justification.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/dot/network/sync_justification.go b/dot/network/sync_justification.go index c5310cc144..dd08917ee4 100644 --- a/dot/network/sync_justification.go +++ b/dot/network/sync_justification.go @@ -36,6 +36,15 @@ func (q *syncQueue) finalizeAtHead() { return } + head, err := q.s.blockState.BestBlockHeader() + if err != nil { + continue + } + + if head.Int64() < q.goal { + continue + } + curr, err := q.s.blockState.GetFinalizedHeader(0, 0) if err != nil { continue From 60246a2e53d9cd18e9eedc0b3cddb53613fa3761 Mon Sep 17 00:00:00 2001 From: noot Date: Tue, 6 Apr 2021 18:23:32 -0400 Subject: [PATCH 02/40] fix --- dot/network/sync_justification.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/dot/network/sync_justification.go b/dot/network/sync_justification.go index dd08917ee4..e0897d0d10 100644 --- a/dot/network/sync_justification.go +++ b/dot/network/sync_justification.go @@ -36,7 +36,7 @@ func (q *syncQueue) finalizeAtHead() { return } - head, err := q.s.blockState.BestBlockHeader() + head, err := q.s.blockState.BestBlockNumber() if err != nil { continue } @@ -59,12 +59,6 @@ func (q *syncQueue) finalizeAtHead() { prev = curr - // no new blocks have been finalized, request block justifications from peers - head, err := q.s.blockState.BestBlockNumber() - if err != nil { - continue - } - start := head.Uint64() - uint64(blockRequestSize) if curr.Number.Uint64() > start { start = curr.Number.Uint64() + 1 From f976f1e42c66c3b1e01c1b2f26c00ac84b65c4a0 Mon Sep 17 00:00:00 2001 From: noot Date: Mon, 12 Apr 2021 14:22:56 -0400 Subject: [PATCH 03/40] write handshake directly to stream --- dot/network/connmgr.go | 16 +++++++++++----- dot/network/notifications.go | 18 +++++++++++------- dot/network/service.go | 26 +++++++++++++++----------- dot/network/utils.go | 2 +- 4 files changed, 38 insertions(+), 24 deletions(-) diff --git a/dot/network/connmgr.go b/dot/network/connmgr.go index f10b2fc585..58f414fb25 100644 --- a/dot/network/connmgr.go +++ b/dot/network/connmgr.go @@ -20,6 +20,7 @@ import ( "context" "math/rand" "sync" + "time" "github.com/libp2p/go-libp2p-core/connmgr" "github.com/libp2p/go-libp2p-core/network" @@ -191,9 +192,16 @@ func (cm *ConnManager) Disconnected(n network.Network, c network.Conn) { Addrs: addrs, } - err := cm.host.connect(info) - if err != nil { - logger.Warn("failed to reconnect to persistent peer", "peer", c.RemotePeer(), "error", err) + var maxRetries = 12 + for i := 0; i < maxRetries; i++ { + err := cm.host.connect(info) + if err != nil { + logger.Warn("failed to reconnect to persistent peer", "peer", c.RemotePeer(), "error", err) + } else { + return + } + + time.Sleep(time.Second * 10) } // TODO: if number of peers falls below the min desired peer count, we should try to connect to previously discovered peers @@ -207,7 +215,6 @@ func (cm *ConnManager) registerDisconnectHandler(cb func(peer.ID)) { func (cm *ConnManager) OpenedStream(n network.Network, s network.Stream) { logger.Trace( "Opened stream", - "host", s.Conn().LocalPeer(), "peer", s.Conn().RemotePeer(), "protocol", s.Protocol(), ) @@ -221,7 +228,6 @@ func (cm *ConnManager) registerCloseHandler(protocolID protocol.ID, cb func(id p func (cm *ConnManager) ClosedStream(n network.Network, s network.Stream) { logger.Trace( "Closed stream", - "host", s.Conn().LocalPeer(), "peer", s.Conn().RemotePeer(), "protocol", s.Protocol(), ) diff --git a/dot/network/notifications.go b/dot/network/notifications.go index 98760d6e18..5580f33774 100644 --- a/dot/network/notifications.go +++ b/dot/network/notifications.go @@ -18,6 +18,7 @@ package network import ( "errors" + "fmt" "math/rand" "sync" @@ -131,7 +132,7 @@ func (s *Service) createNotificationsMessageHandler(info *notificationsProtocol, err := handshakeValidator(peer, hs) if err != nil { logger.Trace("failed to validate handshake", "protocol", info.protocolID, "peer", peer, "error", err) - _ = stream.Conn().Close() + //_ = stream.Conn().Close() return errCannotValidateHandshake } @@ -145,13 +146,16 @@ func (s *Service) createNotificationsMessageHandler(info *notificationsProtocol, return err } - err = s.host.send(peer, info.protocolID, resp) + enc, _ := resp.Encode() + + err = s.host.writeToStream(stream, resp) if err != nil { - logger.Trace("failed to send handshake", "protocol", info.protocolID, "peer", peer, "error", err) + logger.Debug("failed to send handshake", "data", fmt.Sprintf("0x%x", enc), "protocol", info.protocolID, "peer", peer, "error", err) _ = stream.Conn().Close() return err } - logger.Trace("receiver: sent handshake", "protocol", info.protocolID, "peer", peer) + logger.Debug("receiver: sent handshake", "data", fmt.Sprintf("0x%x", enc), "protocol", info.protocolID, "peer", peer) + return nil } // if we are the initiator and haven't received the handshake already, validate it @@ -225,6 +229,9 @@ func (s *Service) broadcastExcluding(info *notificationsProtocol, excluding peer peers := s.host.peers() rand.Shuffle(len(peers), func(i, j int) { peers[i], peers[j] = peers[j], peers[i] }) + info.mapMu.RLock() + defer info.mapMu.RUnlock() + for i, peer := range peers { // TODO: check if stream is open, if not, open and send handshake // TODO: configure this and determine ideal ratio, as well as when to use broadcast vs gossip if i > len(peers)/3 { @@ -235,9 +242,6 @@ func (s *Service) broadcastExcluding(info *notificationsProtocol, excluding peer continue } - info.mapMu.RLock() - defer info.mapMu.RUnlock() - if hsData, has := info.getHandshakeData(peer); !has || !hsData.received { info.handshakeData.Store(peer, &handshakeData{ validated: false, diff --git a/dot/network/service.go b/dot/network/service.go index 6c454d611a..34b6ae8129 100644 --- a/dot/network/service.go +++ b/dot/network/service.go @@ -212,7 +212,7 @@ func (s *Service) Start() error { } // since this opens block announce streams, it should happen after the protocol is registered - s.host.h.Network().SetConnHandler(s.handleConn) + //s.host.h.Network().SetConnHandler(s.handleConn) // log listening addresses to console for _, addr := range s.host.multiaddrs() { @@ -526,13 +526,18 @@ func (s *Service) readStream(stream libp2pnetwork.Stream, peer peer.ID, decoder for { tot, err := readStream(stream, msgBytes) if err == io.EOF { + if tot != 0 { + logger.Debug("stream EOF", "len", tot, "data", fmt.Sprintf("0x%x", msgBytes[:tot]), "peer", stream.Conn().RemotePeer(), "protocol", stream.Protocol()) + } continue } else if err != nil { - logger.Trace("failed to read from stream", "protocol", stream.Protocol(), "error", err) + logger.Debug("failed to read from stream", "peer", stream.Conn().RemotePeer(), "protocol", stream.Protocol(), "error", err) _ = stream.Close() return } + logger.Debug("got stream data!", "len", tot, "data", fmt.Sprintf("0x%x", msgBytes[:tot]), "peer", stream.Conn().RemotePeer(), "protocol", stream.Protocol()) + // decode message based on message type msg, err := decoder(msgBytes[:tot], peer) if err != nil { @@ -547,15 +552,14 @@ func (s *Service) readStream(stream libp2pnetwork.Stream, peer peer.ID, decoder "msg", msg.String(), ) - go func() { - // handle message based on peer status and message type - err = handler(stream, msg) - if err != nil { - logger.Trace("Failed to handle message from stream", "message", msg, "error", err) - _ = stream.Close() - return - } - }() + //go func() { + err = handler(stream, msg) + if err != nil { + logger.Debug("Failed to handle message from stream", "message", msg, "error", err) + _ = stream.Close() + return + } + //}() } } diff --git a/dot/network/utils.go b/dot/network/utils.go index 62e9c53c00..f3bf385e7c 100644 --- a/dot/network/utils.go +++ b/dot/network/utils.go @@ -189,7 +189,7 @@ func readStream(stream libp2pnetwork.Stream, buf []byte) (int, error) { } if length == 0 { - return 0, err // TODO: return bytes read from readLEB128ToUint64 + return 0, nil // msg length of 0 is allowed, for example transactions handshake } // TODO: check if length > len(buf), if so probably log.Crit From 87b1f17dbb13c1d3782d310a41fef5ce992dadd4 Mon Sep 17 00:00:00 2001 From: noot Date: Mon, 12 Apr 2021 14:24:03 -0400 Subject: [PATCH 04/40] lint --- dot/network/notifications.go | 3 ++- dot/network/service.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/dot/network/notifications.go b/dot/network/notifications.go index 5580f33774..0d5b17e247 100644 --- a/dot/network/notifications.go +++ b/dot/network/notifications.go @@ -179,7 +179,7 @@ func (s *Service) createNotificationsMessageHandler(info *notificationsProtocol, // if we are the initiator, send the message if hsData, has := info.getHandshakeData(peer); has && hsData.validated && hsData.received && hsData.outboundMsg != nil { logger.Trace("sender: sending message", "protocol", info.protocolID) - err := s.host.send(peer, info.protocolID, hsData.outboundMsg) + err := s.host.writeToStream(stream, hsData.outboundMsg) if err != nil { logger.Debug("failed to send message", "protocol", info.protocolID, "peer", peer, "error", err) return err @@ -204,6 +204,7 @@ func (s *Service) createNotificationsMessageHandler(info *notificationsProtocol, if !s.noGossip { seen := s.gossip.hasSeen(msg) if !seen { + // TODO: update this to write to stream s.broadcastExcluding(info, peer, msg) } } diff --git a/dot/network/service.go b/dot/network/service.go index 34b6ae8129..15972e333f 100644 --- a/dot/network/service.go +++ b/dot/network/service.go @@ -279,7 +279,7 @@ func (s *Service) logPeerCount() { } } -func (s *Service) handleConn(conn libp2pnetwork.Conn) { +func (s *Service) handleConn(conn libp2pnetwork.Conn) { //nolint // give new peers a slight weight s.syncQueue.updatePeerScore(conn.RemotePeer(), 1) From 3915d033a011c7c7590816ee0962415da96b5a43 Mon Sep 17 00:00:00 2001 From: noot Date: Mon, 12 Apr 2021 14:31:58 -0400 Subject: [PATCH 05/40] cleanup --- dot/network/notifications.go | 11 +++----- dot/network/service.go | 51 +++++------------------------------- 2 files changed, 9 insertions(+), 53 deletions(-) diff --git a/dot/network/notifications.go b/dot/network/notifications.go index 0d5b17e247..fe5f940080 100644 --- a/dot/network/notifications.go +++ b/dot/network/notifications.go @@ -132,7 +132,6 @@ func (s *Service) createNotificationsMessageHandler(info *notificationsProtocol, err := handshakeValidator(peer, hs) if err != nil { logger.Trace("failed to validate handshake", "protocol", info.protocolID, "peer", peer, "error", err) - //_ = stream.Conn().Close() return errCannotValidateHandshake } @@ -142,19 +141,16 @@ func (s *Service) createNotificationsMessageHandler(info *notificationsProtocol, // once validated, send back a handshake resp, err := info.getHandshake() if err != nil { - logger.Debug("failed to get handshake", "protocol", info.protocolID, "error", err) + logger.Warn("failed to get handshake", "protocol", info.protocolID, "error", err) return err } - enc, _ := resp.Encode() - err = s.host.writeToStream(stream, resp) if err != nil { - logger.Debug("failed to send handshake", "data", fmt.Sprintf("0x%x", enc), "protocol", info.protocolID, "peer", peer, "error", err) - _ = stream.Conn().Close() + logger.Trace("failed to send handshake", "protocol", info.protocolID, "peer", peer, "error", err) return err } - logger.Debug("receiver: sent handshake", "data", fmt.Sprintf("0x%x", enc), "protocol", info.protocolID, "peer", peer) + logger.Trace("receiver: sent handshake", "protocol", info.protocolID, "peer", peer) return nil } @@ -165,7 +161,6 @@ func (s *Service) createNotificationsMessageHandler(info *notificationsProtocol, if err != nil { logger.Trace("failed to validate handshake", "protocol", info.protocolID, "peer", peer, "error", err) hsData.validated = false - _ = stream.Conn().Close() return errCannotValidateHandshake } diff --git a/dot/network/service.go b/dot/network/service.go index 15972e333f..778a206908 100644 --- a/dot/network/service.go +++ b/dot/network/service.go @@ -212,7 +212,7 @@ func (s *Service) Start() error { } // since this opens block announce streams, it should happen after the protocol is registered - //s.host.h.Network().SetConnHandler(s.handleConn) + s.host.h.Network().SetConnHandler(s.handleConn) // log listening addresses to console for _, addr := range s.host.multiaddrs() { @@ -279,42 +279,10 @@ func (s *Service) logPeerCount() { } } -func (s *Service) handleConn(conn libp2pnetwork.Conn) { //nolint +func (s *Service) handleConn(conn libp2pnetwork.Conn) { // give new peers a slight weight + // TODO: do this once handshake is received s.syncQueue.updatePeerScore(conn.RemotePeer(), 1) - - s.notificationsMu.Lock() - defer s.notificationsMu.Unlock() - - info, has := s.notificationsProtocols[BlockAnnounceMsgType] - if !has { - // this shouldn't happen - logger.Warn("block announce protocol is not yet registered!") - return - } - - // open block announce substream - hs, err := info.getHandshake() - if err != nil { - logger.Warn("failed to get handshake", "protocol", blockAnnounceID, "error", err) - return - } - - info.mapMu.RLock() - defer info.mapMu.RUnlock() - - peer := conn.RemotePeer() - if hsData, has := info.getHandshakeData(peer); !has || !hsData.received { //nolint - info.handshakeData.Store(peer, &handshakeData{ - validated: false, - }) - - logger.Trace("sending handshake", "protocol", info.protocolID, "peer", peer, "message", hs) - err = s.host.send(peer, info.protocolID, hs) - if err != nil { - logger.Trace("failed to send block announce handshake to peer", "peer", peer, "error", err) - } - } } func (s *Service) beginDiscovery() error { @@ -526,18 +494,13 @@ func (s *Service) readStream(stream libp2pnetwork.Stream, peer peer.ID, decoder for { tot, err := readStream(stream, msgBytes) if err == io.EOF { - if tot != 0 { - logger.Debug("stream EOF", "len", tot, "data", fmt.Sprintf("0x%x", msgBytes[:tot]), "peer", stream.Conn().RemotePeer(), "protocol", stream.Protocol()) - } continue } else if err != nil { - logger.Debug("failed to read from stream", "peer", stream.Conn().RemotePeer(), "protocol", stream.Protocol(), "error", err) + logger.Trace("failed to read from stream", "peer", stream.Conn().RemotePeer(), "protocol", stream.Protocol(), "error", err) _ = stream.Close() return } - logger.Debug("got stream data!", "len", tot, "data", fmt.Sprintf("0x%x", msgBytes[:tot]), "peer", stream.Conn().RemotePeer(), "protocol", stream.Protocol()) - // decode message based on message type msg, err := decoder(msgBytes[:tot], peer) if err != nil { @@ -546,20 +509,18 @@ func (s *Service) readStream(stream libp2pnetwork.Stream, peer peer.ID, decoder } logger.Trace( - "Received message from peer", + "received message from peer", "host", s.host.id(), "peer", peer, "msg", msg.String(), ) - //go func() { err = handler(stream, msg) if err != nil { - logger.Debug("Failed to handle message from stream", "message", msg, "error", err) + logger.Debug("failed to handle message from stream", "message", msg, "error", err) _ = stream.Close() return } - //}() } } From dce28d6eaf9089702f47f47bac0d1efa97bc8dee Mon Sep 17 00:00:00 2001 From: noot Date: Mon, 12 Apr 2021 14:34:56 -0400 Subject: [PATCH 06/40] lint --- dot/network/notifications.go | 1 - 1 file changed, 1 deletion(-) diff --git a/dot/network/notifications.go b/dot/network/notifications.go index fe5f940080..e8bb67112e 100644 --- a/dot/network/notifications.go +++ b/dot/network/notifications.go @@ -18,7 +18,6 @@ package network import ( "errors" - "fmt" "math/rand" "sync" From 223f1aa4b73e4ae87ccb28dc44773a7d424bcf81 Mon Sep 17 00:00:00 2001 From: noot Date: Mon, 12 Apr 2021 15:12:15 -0400 Subject: [PATCH 07/40] cleanup --- dot/network/connmgr.go | 11 +++++++---- dot/network/notifications.go | 14 ++++++++------ dot/network/service.go | 2 +- dot/network/service_test.go | 12 ------------ dot/network/sync.go | 6 +++--- 5 files changed, 19 insertions(+), 26 deletions(-) diff --git a/dot/network/connmgr.go b/dot/network/connmgr.go index 58f414fb25..f6e6eb4871 100644 --- a/dot/network/connmgr.go +++ b/dot/network/connmgr.go @@ -30,6 +30,10 @@ import ( ma "github.com/multiformats/go-multiaddr" ) +var ( + maxRetries = 12 +) + // ConnManager implements connmgr.ConnManager type ConnManager struct { sync.Mutex @@ -192,16 +196,15 @@ func (cm *ConnManager) Disconnected(n network.Network, c network.Conn) { Addrs: addrs, } - var maxRetries = 12 for i := 0; i < maxRetries; i++ { err := cm.host.connect(info) if err != nil { logger.Warn("failed to reconnect to persistent peer", "peer", c.RemotePeer(), "error", err) - } else { - return + time.Sleep(time.Minute) + continue } - time.Sleep(time.Second * 10) + return } // TODO: if number of peers falls below the min desired peer count, we should try to connect to previously discovered peers diff --git a/dot/network/notifications.go b/dot/network/notifications.go index e8bb67112e..840bc4ef9f 100644 --- a/dot/network/notifications.go +++ b/dot/network/notifications.go @@ -195,12 +195,14 @@ func (s *Service) createNotificationsMessageHandler(info *notificationsProtocol, } // TODO: improve this by keeping track of who you've received/sent messages from - if !s.noGossip { - seen := s.gossip.hasSeen(msg) - if !seen { - // TODO: update this to write to stream - s.broadcastExcluding(info, peer, msg) - } + if s.noGossip { + return nil + } + + seen := s.gossip.hasSeen(msg) + if !seen { + // TODO: update this to write to stream + s.broadcastExcluding(info, peer, msg) } return nil diff --git a/dot/network/service.go b/dot/network/service.go index 778a206908..cbcb4623de 100644 --- a/dot/network/service.go +++ b/dot/network/service.go @@ -145,7 +145,7 @@ func NewService(cfg *Config) (*Service, error) { } network.syncQueue = newSyncQueue(network) - + network.noGossip = true // TODO: remove once duplicate message sending is merged return network, err } diff --git a/dot/network/service_test.go b/dot/network/service_test.go index 4f9743fc1f..642dfd8da0 100644 --- a/dot/network/service_test.go +++ b/dot/network/service_test.go @@ -369,16 +369,4 @@ func TestHandleConn(t *testing.T) { aScore, ok := nodeB.syncQueue.peerScore.Load(nodeA.host.id()) require.True(t, ok) require.Equal(t, 1, aScore) - - infoA := nodeA.notificationsProtocols[BlockAnnounceMsgType] - hsDataB, has := infoA.getHandshakeData(nodeB.host.id()) - require.True(t, has) - require.True(t, hsDataB.received) - require.True(t, hsDataB.validated) - - infoB := nodeB.notificationsProtocols[BlockAnnounceMsgType] - hsDataA, has := infoB.getHandshakeData(nodeA.host.id()) - require.True(t, has) - require.True(t, hsDataA.received) - require.True(t, hsDataA.validated) } diff --git a/dot/network/sync.go b/dot/network/sync.go index 9cbc5d21e0..06d50b5d20 100644 --- a/dot/network/sync.go +++ b/dot/network/sync.go @@ -182,7 +182,7 @@ func (q *syncQueue) syncAtHead() { for { select { // sleep for average block time TODO: make this configurable from slot duration - case <-time.After(q.slotDuration): + case <-time.After(q.slotDuration * 2): case <-q.ctx.Done(): return } @@ -793,7 +793,6 @@ func (q *syncQueue) handleBlockAnnounce(msg *BlockAnnounceMessage, from peer.ID) return } - logger.Debug("received BlockAnnounce!", "number", msg.Number, "hash", header.Hash(), "from", from) has, _ := q.s.blockState.HasBlockBody(header.Hash()) if has { return @@ -803,13 +802,14 @@ func (q *syncQueue) handleBlockAnnounce(msg *BlockAnnounceMessage, from peer.ID) return } + q.goal = header.Number.Int64() + bestNum, err := q.s.blockState.BestBlockNumber() if err != nil { logger.Error("failed to get best block number", "error", err) return } - q.goal = header.Number.Int64() q.pushRequest(uint64(bestNum.Int64()+1), blockRequestBufferSize, from) } From b006e57851ebac2c8532c0e373003d6c5b97dcd4 Mon Sep 17 00:00:00 2001 From: noot Date: Mon, 12 Apr 2021 15:27:12 -0400 Subject: [PATCH 08/40] fix log --- dot/network/block_announce.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dot/network/block_announce.go b/dot/network/block_announce.go index 05e1c9b71f..6d22b42b59 100644 --- a/dot/network/block_announce.go +++ b/dot/network/block_announce.go @@ -56,7 +56,7 @@ func (bm *BlockAnnounceMessage) Type() byte { // string formats a BlockAnnounceMessage as a string func (bm *BlockAnnounceMessage) String() string { - return fmt.Sprintf("BlockAnnounceMessage ParentHash=%s Number=%d StateRoot=%sx ExtrinsicsRoot=%s Digest=%v", + return fmt.Sprintf("BlockAnnounceMessage ParentHash=%s Number=%d StateRoot=%s ExtrinsicsRoot=%s Digest=%v", bm.ParentHash, bm.Number, bm.StateRoot, From 9b805abaf5efa16020354fcd941fb79159a08b8e Mon Sep 17 00:00:00 2001 From: noot Date: Mon, 12 Apr 2021 17:07:43 -0400 Subject: [PATCH 09/40] fix test --- dot/network/sync_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dot/network/sync_test.go b/dot/network/sync_test.go index c65d700952..656c14bb81 100644 --- a/dot/network/sync_test.go +++ b/dot/network/sync_test.go @@ -425,9 +425,10 @@ func TestSyncQueue_SyncAtHead(t *testing.T) { q.stop() time.Sleep(time.Second) q.ctx = context.Background() + q.slotDuration = time.Millisecond * 100 go q.syncAtHead() - time.Sleep(time.Millisecond * 6100) + time.Sleep(q.slotDuration * 3) select { case req := <-q.requestCh: require.Equal(t, uint64(2), req.req.StartingBlock.Uint64()) From b4243ef2ae8a8b5454972675a381980f4418311d Mon Sep 17 00:00:00 2001 From: noot Date: Mon, 12 Apr 2021 17:12:05 -0400 Subject: [PATCH 10/40] keep all unfinalized tries in memory --- dot/network/sync.go | 3 +++ dot/state/storage.go | 12 ++++++------ dot/sync/syncer.go | 23 ++++++++++++----------- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/dot/network/sync.go b/dot/network/sync.go index 06d50b5d20..b1b11c77f5 100644 --- a/dot/network/sync.go +++ b/dot/network/sync.go @@ -194,6 +194,9 @@ func (q *syncQueue) syncAtHead() { // we aren't at the head yet, sleep if curr.Number.Int64() < q.goal && curr.Number.Cmp(prev.Number) > 0 { + if q.goal-curr.Number.Int64() < int64(blockRequestSize) { + q.s.syncer.SetSyncing(false) + } prev = curr continue } diff --git a/dot/state/storage.go b/dot/state/storage.go index 73bea66666..439081e641 100644 --- a/dot/state/storage.go +++ b/dot/state/storage.go @@ -100,12 +100,12 @@ func (s *StorageState) pruneKey(keyHeader *types.Header) { func (s *StorageState) StoreTrie(ts *rtstorage.TrieState) error { s.lock.Lock() root := ts.MustRoot() - if s.syncing { - // keep only the trie at the head of the chain when syncing - for key := range s.tries { - delete(s.tries, key) - } - } + // if s.syncing { + // // keep only the trie at the head of the chain when syncing + // for key := range s.tries { + // delete(s.tries, key) + // } + // } s.tries[root] = ts.Trie() s.lock.Unlock() diff --git a/dot/sync/syncer.go b/dot/sync/syncer.go index dcae7dc8ae..bdcf97d5ae 100644 --- a/dot/sync/syncer.go +++ b/dot/sync/syncer.go @@ -139,18 +139,19 @@ func (s *Service) HandleBlockAnnounce(msg *network.BlockAnnounceMessage) error { } // save block header if we don't have it already - if !has { - err = s.blockState.SetHeader(header) - if err != nil { - return err - } - logger.Debug( - "saved block header to block state", - "number", header.Number, - "hash", header.Hash(), - ) + if has { + return nil } + err = s.blockState.SetHeader(header) + if err != nil { + return err + } + logger.Debug( + "saved block header to block state", + "number", header.Number, + "hash", header.Hash(), + ) return nil } @@ -325,7 +326,7 @@ func (s *Service) handleBlock(block *types.Block) error { if err != nil { return err } - logger.Trace("stored resulting state", "state root", ts.MustRoot()) + logger.Info("executed block and stored resulting state", "state root", ts.MustRoot()) // TODO: batch writes in AddBlock err = s.blockState.AddBlock(block) From 5388e0e072bfc92a85acb99f025b9bd1535e1ecf Mon Sep 17 00:00:00 2001 From: noot Date: Mon, 12 Apr 2021 17:30:29 -0400 Subject: [PATCH 11/40] check BlockRequestMessage.From length --- dot/network/message.go | 4 ++++ dot/network/sync.go | 12 ++++++++---- dot/state/storage.go | 12 ++++++------ dot/sync/syncer.go | 2 +- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/dot/network/message.go b/dot/network/message.go index 87a60a976f..801d3a1296 100644 --- a/dot/network/message.go +++ b/dot/network/message.go @@ -148,6 +148,10 @@ func (bm *BlockRequestMessage) Decode(in []byte) error { case *pb.BlockRequest_Hash: startingBlock, err = variadic.NewUint64OrHash(common.BytesToHash(from.Hash)) case *pb.BlockRequest_Number: + // TODO: we are receiving block requests w/ 4-byte From field; did the format change? + if len(from.Number) != 8 { + return errors.New("invalid BlockResponseMessage.From; uint64 is not 8 bytes") + } startingBlock, err = variadic.NewUint64OrHash(binary.LittleEndian.Uint64(from.Number)) default: err = errors.New("invalid StartingBlock") diff --git a/dot/network/sync.go b/dot/network/sync.go index b1b11c77f5..f0b5836ab5 100644 --- a/dot/network/sync.go +++ b/dot/network/sync.go @@ -18,6 +18,7 @@ package network import ( "context" + "errors" "fmt" "reflect" "sort" @@ -29,6 +30,7 @@ import ( "github.com/ChainSafe/gossamer/lib/common/optional" "github.com/ChainSafe/gossamer/lib/common/variadic" + "github.com/ChainSafe/chaindb" libp2pnetwork "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" ) @@ -692,14 +694,14 @@ func (q *syncQueue) handleBlockJustification(data []*types.BlockData) { } func (q *syncQueue) handleBlockData(data []*types.BlockData) { - bestNum, err := q.s.blockState.BestBlockNumber() + finalized, err := q.s.blockState.GetFinalizedHeader(0, 0) if err != nil { panic(err) // TODO: don't panic but try again. seems blockState needs better concurrency handling } end := data[len(data)-1].Number().Int64() - if end <= bestNum.Int64() { - logger.Debug("ignoring block data that is below our head", "got", end, "head", bestNum.Int64()) + if end <= finalized.Number.Int64() { + logger.Debug("ignoring block data that is below our head", "got", end, "head", finalized.Number.Int64()) q.pushRequest(uint64(end+1), blockRequestBufferSize, "") return } @@ -739,7 +741,7 @@ func (q *syncQueue) handleBlockData(data []*types.BlockData) { func (q *syncQueue) handleBlockDataFailure(idx int, err error, data []*types.BlockData) { logger.Warn("failed to handle block data", "failed on block", q.currStart+int64(idx), "error", err) - if err.Error() == "failed to get parent hash: Key not found" { // TODO: unwrap err + if /*err.Error() == "failed to get parent hash: Key not found" ||*/ errors.Is(err, chaindb.ErrKeyNotFound) { // TODO: unwrap err header, err := types.NewHeaderFromOptional(data[idx].Header) if err != nil { logger.Debug("failed to get header from BlockData", "idx", idx, "error", err) @@ -813,6 +815,8 @@ func (q *syncQueue) handleBlockAnnounce(msg *BlockAnnounceMessage, from peer.ID) return } + // TODO: if we're at the head, this should request by hash instead of number, since there will + // certainly be blocks with the same number. q.pushRequest(uint64(bestNum.Int64()+1), blockRequestBufferSize, from) } diff --git a/dot/state/storage.go b/dot/state/storage.go index 439081e641..73bea66666 100644 --- a/dot/state/storage.go +++ b/dot/state/storage.go @@ -100,12 +100,12 @@ func (s *StorageState) pruneKey(keyHeader *types.Header) { func (s *StorageState) StoreTrie(ts *rtstorage.TrieState) error { s.lock.Lock() root := ts.MustRoot() - // if s.syncing { - // // keep only the trie at the head of the chain when syncing - // for key := range s.tries { - // delete(s.tries, key) - // } - // } + if s.syncing { + // keep only the trie at the head of the chain when syncing + for key := range s.tries { + delete(s.tries, key) + } + } s.tries[root] = ts.Trie() s.lock.Unlock() diff --git a/dot/sync/syncer.go b/dot/sync/syncer.go index bdcf97d5ae..9f6a7861a1 100644 --- a/dot/sync/syncer.go +++ b/dot/sync/syncer.go @@ -326,7 +326,7 @@ func (s *Service) handleBlock(block *types.Block) error { if err != nil { return err } - logger.Info("executed block and stored resulting state", "state root", ts.MustRoot()) + logger.Trace("executed block and stored resulting state", "state root", ts.MustRoot()) // TODO: batch writes in AddBlock err = s.blockState.AddBlock(block) From 14e6c4a8939cb61469250c76955350d3559dd752 Mon Sep 17 00:00:00 2001 From: noot Date: Mon, 12 Apr 2021 17:37:01 -0400 Subject: [PATCH 12/40] cleanup --- dot/network/sync.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/dot/network/sync.go b/dot/network/sync.go index f0b5836ab5..3fddeeccc4 100644 --- a/dot/network/sync.go +++ b/dot/network/sync.go @@ -196,9 +196,6 @@ func (q *syncQueue) syncAtHead() { // we aren't at the head yet, sleep if curr.Number.Int64() < q.goal && curr.Number.Cmp(prev.Number) > 0 { - if q.goal-curr.Number.Int64() < int64(blockRequestSize) { - q.s.syncer.SetSyncing(false) - } prev = curr continue } @@ -741,7 +738,7 @@ func (q *syncQueue) handleBlockData(data []*types.BlockData) { func (q *syncQueue) handleBlockDataFailure(idx int, err error, data []*types.BlockData) { logger.Warn("failed to handle block data", "failed on block", q.currStart+int64(idx), "error", err) - if /*err.Error() == "failed to get parent hash: Key not found" ||*/ errors.Is(err, chaindb.ErrKeyNotFound) { // TODO: unwrap err + if errors.Is(err, chaindb.ErrKeyNotFound) { header, err := types.NewHeaderFromOptional(data[idx].Header) if err != nil { logger.Debug("failed to get header from BlockData", "idx", idx, "error", err) From 8256bcbdd3941f4d7f0c7b80ca9bf3f01676d3bc Mon Sep 17 00:00:00 2001 From: noot Date: Mon, 12 Apr 2021 17:38:44 -0400 Subject: [PATCH 13/40] fix error --- dot/network/sync_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dot/network/sync_test.go b/dot/network/sync_test.go index 656c14bb81..fba9b11e05 100644 --- a/dot/network/sync_test.go +++ b/dot/network/sync_test.go @@ -28,6 +28,7 @@ import ( "github.com/ChainSafe/gossamer/lib/common/optional" "github.com/ChainSafe/gossamer/lib/utils" + "github.com/ChainSafe/chaindb" "github.com/libp2p/go-libp2p-core/peer" "github.com/stretchr/testify/require" ) @@ -501,7 +502,7 @@ func TestSyncQueue_handleBlockDataFailure_MissingParent(t *testing.T) { q.ctx = context.Background() data := testBlockResponseMessage().BlockData - q.handleBlockDataFailure(0, fmt.Errorf("failed to get parent hash: Key not found"), data) + q.handleBlockDataFailure(0, fmt.Errorf("some error: %w", chaindb.ErrKeyNotFound), data) select { case req := <-q.requestCh: require.True(t, req.req.StartingBlock.IsHash()) From d6b192c0af60993705c73e0957887948d257a200 Mon Sep 17 00:00:00 2001 From: noot Date: Mon, 12 Apr 2021 21:26:42 -0400 Subject: [PATCH 14/40] add grandpa NeighbourMessage type and handle accordingly --- dot/network/message.go | 15 +-- dot/network/sync.go | 19 ++- dot/network/sync_justification.go | 92 -------------- dot/network/sync_justification_test.go | 168 ------------------------- lib/grandpa/grandpa.go | 36 +++--- lib/grandpa/message.go | 42 +++++-- lib/grandpa/message_handler.go | 67 ++++++++-- lib/grandpa/state.go | 2 + lib/grandpa/vote_message.go | 12 +- 9 files changed, 128 insertions(+), 325 deletions(-) delete mode 100644 dot/network/sync_justification.go delete mode 100644 dot/network/sync_justification_test.go diff --git a/dot/network/message.go b/dot/network/message.go index 801d3a1296..c50e8b971f 100644 --- a/dot/network/message.go +++ b/dot/network/message.go @@ -359,9 +359,6 @@ var _ NotificationsMessage = &ConsensusMessage{} // ConsensusMessage is mostly opaque to us type ConsensusMessage struct { - // Identifies consensus engine. - ConsensusEngineID types.ConsensusEngineID - // Message payload. Data []byte } @@ -377,23 +374,17 @@ func (cm *ConsensusMessage) Type() byte { // String is the string func (cm *ConsensusMessage) String() string { - return fmt.Sprintf("ConsensusMessage ConsensusEngineID=%d, DATA=%x", cm.ConsensusEngineID, cm.Data) + return fmt.Sprintf("ConsensusMessage Data=%x", cm.Data) } // Encode encodes a block response message using SCALE func (cm *ConsensusMessage) Encode() ([]byte, error) { - encMsg := cm.ConsensusEngineID.ToBytes() - return append(encMsg, cm.Data...), nil + return cm.Data, nil } // Decode the message into a ConsensusMessage func (cm *ConsensusMessage) Decode(in []byte) error { - if len(in) < 5 { - return errors.New("cannot decode ConsensusMessage: encoding is too short") - } - - cm.ConsensusEngineID = types.NewConsensusEngineID(in[:4]) - cm.Data = in[4:] + cm.Data = in return nil } diff --git a/dot/network/sync.go b/dot/network/sync.go index 3fddeeccc4..b38c56ce73 100644 --- a/dot/network/sync.go +++ b/dot/network/sync.go @@ -163,7 +163,6 @@ func newSyncQueue(s *Service) *syncQueue { func (q *syncQueue) start() { go q.handleResponseQueue() go q.syncAtHead() - go q.finalizeAtHead() go q.processBlockRequests() go q.processBlockResponses() @@ -691,17 +690,17 @@ func (q *syncQueue) handleBlockJustification(data []*types.BlockData) { } func (q *syncQueue) handleBlockData(data []*types.BlockData) { - finalized, err := q.s.blockState.GetFinalizedHeader(0, 0) - if err != nil { - panic(err) // TODO: don't panic but try again. seems blockState needs better concurrency handling - } + // finalized, err := q.s.blockState.GetFinalizedHeader(0, 0) + // if err != nil { + // panic(err) + // } end := data[len(data)-1].Number().Int64() - if end <= finalized.Number.Int64() { - logger.Debug("ignoring block data that is below our head", "got", end, "head", finalized.Number.Int64()) - q.pushRequest(uint64(end+1), blockRequestBufferSize, "") - return - } + // if end <= finalized.Number.Int64() { + // logger.Debug("ignoring block data that is below our head", "got", end, "head", finalized.Number.Int64()) + // q.pushRequest(uint64(end+1), blockRequestBufferSize, "") + // return + // } defer func() { q.currStart = 0 diff --git a/dot/network/sync_justification.go b/dot/network/sync_justification.go deleted file mode 100644 index e0897d0d10..0000000000 --- a/dot/network/sync_justification.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2019 ChainSafe Systems (ON) Corp. -// This file is part of gossamer. -// -// The gossamer library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The gossamer library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the gossamer library. If not, see . - -package network - -import ( - "math/big" - "time" -) - -func (q *syncQueue) finalizeAtHead() { - prev, err := q.s.blockState.GetFinalizedHeader(0, 0) - if err != nil { - logger.Error("failed to get latest finalized block header", "error", err) - return - } - - for { - select { - // sleep for average block time TODO: make this configurable from slot duration - case <-time.After(q.slotDuration * 2): - case <-q.ctx.Done(): - return - } - - head, err := q.s.blockState.BestBlockNumber() - if err != nil { - continue - } - - if head.Int64() < q.goal { - continue - } - - curr, err := q.s.blockState.GetFinalizedHeader(0, 0) - if err != nil { - continue - } - - logger.Debug("checking finalized blocks", "curr", curr.Number, "prev", prev.Number) - - if curr.Number.Cmp(prev.Number) > 0 { - prev = curr - continue - } - - prev = curr - - start := head.Uint64() - uint64(blockRequestSize) - if curr.Number.Uint64() > start { - start = curr.Number.Uint64() + 1 - } else if int(start) < int(blockRequestSize) { - start = 1 - } - - q.pushJustificationRequest(start) - } -} - -func (q *syncQueue) pushJustificationRequest(start uint64) { - startHash, err := q.s.blockState.GetHashByNumber(big.NewInt(int64(start))) - if err != nil { - logger.Error("failed to get hash for block w/ number", "number", start, "error", err) - return - } - - req := createBlockRequestWithHash(startHash, blockRequestSize) - req.RequestedData = RequestedDataJustification - - logger.Debug("pushing justification request to queue", "start", start, "hash", startHash) - q.justificationRequestData.Store(startHash, requestData{ - received: false, - }) - - q.requestCh <- &syncRequest{ - req: req, - to: "", - } -} diff --git a/dot/network/sync_justification_test.go b/dot/network/sync_justification_test.go deleted file mode 100644 index 00d854471a..0000000000 --- a/dot/network/sync_justification_test.go +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright 2019 ChainSafe Systems (ON) Corp. -// This file is part of gossamer. -// -// The gossamer library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The gossamer library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the gossamer library. If not, see . - -package network - -import ( - "context" - "math/big" - "testing" - "time" - - "github.com/ChainSafe/gossamer/dot/types" - "github.com/ChainSafe/gossamer/lib/common" - "github.com/ChainSafe/gossamer/lib/common/optional" - "github.com/ChainSafe/gossamer/lib/utils" - - "github.com/libp2p/go-libp2p-core/peer" - "github.com/stretchr/testify/require" -) - -func TestSyncQueue_PushResponse_Justification(t *testing.T) { - basePath := utils.NewTestBasePath(t, "nodeA") - config := &Config{ - BasePath: basePath, - Port: 7001, - RandSeed: 1, - NoBootstrap: true, - NoMDNS: true, - } - - s := createTestService(t, config) - s.syncQueue.stop() - time.Sleep(time.Second) - - peerID := peer.ID("noot") - msg := &BlockResponseMessage{ - BlockData: []*types.BlockData{}, - } - - for i := 0; i < int(blockRequestSize); i++ { - msg.BlockData = append(msg.BlockData, &types.BlockData{ - Hash: common.Hash{byte(i)}, - Justification: optional.NewBytes(true, []byte{1}), - }) - } - - s.syncQueue.justificationRequestData.Store(common.Hash{byte(0)}, requestData{}) - err := s.syncQueue.pushResponse(msg, peerID) - require.NoError(t, err) - require.Equal(t, 1, len(s.syncQueue.responseCh)) - data, ok := s.syncQueue.justificationRequestData.Load(common.Hash{byte(0)}) - require.True(t, ok) - require.Equal(t, requestData{ - sent: true, - received: true, - from: peerID, - }, data) -} - -func TestSyncQueue_PushResponse_EmptyJustification(t *testing.T) { - basePath := utils.NewTestBasePath(t, "nodeA") - config := &Config{ - BasePath: basePath, - Port: 7001, - RandSeed: 1, - NoBootstrap: true, - NoMDNS: true, - } - - s := createTestService(t, config) - s.syncQueue.stop() - time.Sleep(time.Second) - - peerID := peer.ID("noot") - msg := &BlockResponseMessage{ - BlockData: []*types.BlockData{}, - } - - for i := 0; i < int(blockRequestSize); i++ { - msg.BlockData = append(msg.BlockData, &types.BlockData{ - Hash: common.Hash{byte(i)}, - Justification: optional.NewBytes(false, nil), - }) - } - - s.syncQueue.justificationRequestData.Store(common.Hash{byte(0)}, &requestData{}) - err := s.syncQueue.pushResponse(msg, peerID) - require.Equal(t, errEmptyJustificationData, err) -} - -func TestSyncQueue_processBlockResponses_Justification(t *testing.T) { - q := newTestSyncQueue(t) - q.stop() - time.Sleep(time.Second) - q.ctx = context.Background() - - go func() { - q.responseCh <- []*types.BlockData{ - { - Hash: common.Hash{byte(0)}, - Header: optional.NewHeader(false, nil), - Body: optional.NewBody(false, nil), - Receipt: optional.NewBytes(false, nil), - MessageQueue: optional.NewBytes(false, nil), - Justification: optional.NewBytes(true, []byte{1}), - }, - } - }() - - peerID := peer.ID("noot") - q.justificationRequestData.Store(common.Hash{byte(0)}, requestData{ - from: peerID, - }) - - go q.processBlockResponses() - time.Sleep(time.Second) - - _, has := q.justificationRequestData.Load(common.Hash{byte(0)}) - require.False(t, has) - - score, ok := q.peerScore.Load(peerID) - require.True(t, ok) - require.Equal(t, 2, score) -} - -func TestSyncQueue_finalizeAtHead(t *testing.T) { - q := newTestSyncQueue(t) - q.stop() - time.Sleep(time.Second) - q.ctx = context.Background() - q.slotDuration = time.Millisecond * 200 - - hash, err := q.s.blockState.GetHashByNumber(big.NewInt(1)) - require.NoError(t, err) - - go q.finalizeAtHead() - time.Sleep(time.Second) - - data, has := q.justificationRequestData.Load(hash) - require.True(t, has) - require.Equal(t, requestData{}, data) - - expected := createBlockRequestWithHash(hash, blockRequestSize) - expected.RequestedData = RequestedDataJustification - - select { - case req := <-q.requestCh: - require.Equal(t, &syncRequest{ - req: expected, - to: "", - }, req) - case <-time.After(time.Second): - t.Fatal("did not receive request") - } -} diff --git a/lib/grandpa/grandpa.go b/lib/grandpa/grandpa.go index 345b33ec74..f44c003bc5 100644 --- a/lib/grandpa/grandpa.go +++ b/lib/grandpa/grandpa.go @@ -34,12 +34,14 @@ import ( log "github.com/ChainSafe/log15" ) -var interval = time.Second +var ( + interval = time.Second + logger = log.New("pkg", "grandpa") +) // Service represents the current state of the grandpa protocol type Service struct { // preliminaries - logger log.Logger ctx context.Context cancel context.CancelFunc blockState BlockState @@ -105,7 +107,6 @@ func NewService(cfg *Config) (*Service, error) { return nil, ErrNilNetwork } - logger := log.New("pkg", "grandpa") h := log.StreamHandler(os.Stdout, log.TerminalFormat()) h = log.CallerFileHandler(h) logger.SetHandler(log.LvlFilterHandler(cfg.LogLvl, h)) @@ -126,7 +127,6 @@ func NewService(cfg *Config) (*Service, error) { ctx, cancel := context.WithCancel(context.Background()) s := &Service{ - logger: logger, ctx: ctx, cancel: cancel, state: NewState(cfg.Voters, cfg.SetID, 0), // TODO: determine current round @@ -166,7 +166,7 @@ func (s *Service) Start() error { go func() { err := s.initiate() if err != nil { - s.logger.Error("failed to initiate", "error", err) + logger.Error("failed to initiate", "error", err) } }() @@ -247,7 +247,7 @@ func (s *Service) initiate() error { // make sure no votes can be validated while we are incrementing rounds s.roundLock.Lock() s.state.round++ - s.logger.Trace("incrementing grandpa round", "next round", s.state.round) + logger.Trace("incrementing grandpa round", "next round", s.state.round) if s.tracker != nil { s.tracker.stop() } @@ -266,7 +266,7 @@ func (s *Service) initiate() error { return err } s.tracker.start() - s.logger.Trace("started message tracker") + logger.Trace("started message tracker") } s.roundLock.Unlock() @@ -377,7 +377,7 @@ func (s *Service) waitForFirstBlock() error { // playGrandpaRound executes a round of GRANDPA // at the end of this round, a block will be finalized. func (s *Service) playGrandpaRound() error { - s.logger.Debug("starting round", "round", s.state.round, "setID", s.state.setID) + logger.Debug("starting round", "round", s.state.round, "setID", s.state.setID) // save start time start := time.Now() @@ -389,13 +389,13 @@ func (s *Service) playGrandpaRound() error { if bytes.Equal(primary.key.Encode(), s.keypair.Public().Encode()) { msg, err := s.newFinalizationMessage(s.head, s.state.round-1).ToConsensusMessage() if err != nil { - s.logger.Error("failed to encode finalization message", "error", err) + logger.Error("failed to encode finalization message", "error", err) } else { s.network.SendMessage(msg) } } - s.logger.Debug("receiving pre-vote messages...") + logger.Debug("receiving pre-vote messages...") go s.receiveMessages(func() bool { if s.paused.Load().(bool) { @@ -430,7 +430,7 @@ func (s *Service) playGrandpaRound() error { s.mapLock.Lock() s.prevotes[s.publicKeyBytes()] = pv - s.logger.Debug("sending pre-vote message...", "vote", pv, "prevotes", s.prevotes) + logger.Debug("sending pre-vote message...", "vote", pv, "prevotes", s.prevotes) s.mapLock.Unlock() finalized := false @@ -448,15 +448,15 @@ func (s *Service) playGrandpaRound() error { err = s.sendMessage(pv, prevote) if err != nil { - s.logger.Error("could not send prevote message", "error", err) + logger.Error("could not send prevote message", "error", err) } time.Sleep(time.Second * 5) - s.logger.Trace("sent pre-vote message...", "vote", pv, "prevotes", s.prevotes) + logger.Trace("sent pre-vote message...", "vote", pv, "prevotes", s.prevotes) } }(&finalized) - s.logger.Debug("receiving pre-commit messages...") + logger.Debug("receiving pre-commit messages...") go s.receiveMessages(func() bool { end := start.Add(interval * 4) @@ -487,7 +487,7 @@ func (s *Service) playGrandpaRound() error { s.mapLock.Lock() s.precommits[s.publicKeyBytes()] = pc - s.logger.Debug("sending pre-commit message...", "vote", pc, "precommits", s.precommits) + logger.Debug("sending pre-commit message...", "vote", pc, "precommits", s.precommits) s.mapLock.Unlock() // continue to send precommit messages until round is done @@ -503,11 +503,11 @@ func (s *Service) playGrandpaRound() error { err = s.sendMessage(pc, precommit) if err != nil { - s.logger.Error("could not send precommit message", "error", err) + logger.Error("could not send precommit message", "error", err) } time.Sleep(time.Second * 5) - s.logger.Trace("sent pre-commit message...", "vote", pc, "precommits", s.precommits) + logger.Trace("sent pre-commit message...", "vote", pc, "precommits", s.precommits) } }(&finalized) @@ -586,7 +586,7 @@ func (s *Service) attemptToFinalize() error { // if we haven't received a finalization message for this block yet, broadcast a finalization message votes := s.getDirectVotes(precommit) - s.logger.Debug("finalized block!!!", "setID", s.state.setID, "round", s.state.round, "hash", s.head.Hash(), + logger.Debug("finalized block!!!", "setID", s.state.setID, "round", s.state.round, "hash", s.head.Hash(), "precommits #", pc, "votes for bfc #", votes[*bfc], "total votes for bfc", pc, "precommits", s.precommits) msg, err := s.newFinalizationMessage(s.head, s.state.round).ToConsensusMessage() if err != nil { diff --git a/lib/grandpa/message.go b/lib/grandpa/message.go index 55ceb9c255..94bd37d9ed 100644 --- a/lib/grandpa/message.go +++ b/lib/grandpa/message.go @@ -28,6 +28,7 @@ import ( ) // GrandpaMessage is implemented by all GRANDPA network messages +// TODO: the fields can be un-exported, as can all the message implementations type GrandpaMessage interface { //nolint ToConsensusMessage() (*network.ConsensusMessage, error) Type() byte @@ -35,10 +36,11 @@ type GrandpaMessage interface { //nolint var ( voteType byte = 0 - precommitType byte = 1 - finalizationType byte = 2 + precommitType byte = 1 // TODO: precommitType is now part of voteType + neighbourType byte = 2 catchUpRequestType byte = 3 catchUpResponseType byte = 4 + finalizationType byte = 5 // TODO: this is actually 1 ) // FullVote represents a vote with additional information about the state @@ -86,11 +88,34 @@ func (v *VoteMessage) ToConsensusMessage() (*ConsensusMessage, error) { typ := byte(v.Stage) return &ConsensusMessage{ - ConsensusEngineID: types.GrandpaEngineID, - Data: append([]byte{typ}, enc...), + Data: append([]byte{typ}, enc...), }, nil } +type NeighbourMessage struct { + Version byte + Round uint64 + SetID uint64 + Number uint32 +} + +// ToConsensusMessage converts the NeighbourMessage into a network-level consensus message +func (m *NeighbourMessage) ToConsensusMessage() (*network.ConsensusMessage, error) { + enc, err := scale.Encode(m) + if err != nil { + return nil, err + } + + return &ConsensusMessage{ + Data: append([]byte{neighbourType}, enc...), + }, nil +} + +// Type returns neighbourType +func (m *NeighbourMessage) Type() byte { + return neighbourType +} + // FinalizationMessage represents a network finalization message type FinalizationMessage struct { Round uint64 @@ -111,8 +136,7 @@ func (f *FinalizationMessage) ToConsensusMessage() (*ConsensusMessage, error) { } return &ConsensusMessage{ - ConsensusEngineID: types.GrandpaEngineID, - Data: append([]byte{finalizationType}, enc...), + Data: append([]byte{finalizationType}, enc...), }, nil } @@ -149,8 +173,7 @@ func (r *catchUpRequest) ToConsensusMessage() (*ConsensusMessage, error) { } return &ConsensusMessage{ - ConsensusEngineID: types.GrandpaEngineID, - Data: append([]byte{catchUpRequestType}, enc...), + Data: append([]byte{catchUpRequestType}, enc...), }, nil } @@ -222,7 +245,6 @@ func (r *catchUpResponse) ToConsensusMessage() (*ConsensusMessage, error) { } return &ConsensusMessage{ - ConsensusEngineID: types.GrandpaEngineID, - Data: append([]byte{catchUpResponseType}, enc...), + Data: append([]byte{catchUpResponseType}, enc...), }, nil } diff --git a/lib/grandpa/message_handler.go b/lib/grandpa/message_handler.go index 8d4db4eed9..34b0c62106 100644 --- a/lib/grandpa/message_handler.go +++ b/lib/grandpa/message_handler.go @@ -17,6 +17,7 @@ package grandpa import ( + "math/big" "reflect" "github.com/ChainSafe/gossamer/lib/common" @@ -43,7 +44,7 @@ func NewMessageHandler(grandpa *Service, blockState BlockState) *MessageHandler // if it is a VoteMessage, it sends it to the GRANDPA service func (h *MessageHandler) handleMessage(msg *ConsensusMessage) (*ConsensusMessage, error) { if msg == nil || len(msg.Data) == 0 { - h.grandpa.logger.Trace("received nil message or message with nil data") + logger.Trace("received nil message or message with nil data") return nil, nil } @@ -52,6 +53,8 @@ func (h *MessageHandler) handleMessage(msg *ConsensusMessage) (*ConsensusMessage return nil, err } + logger.Info("handling grandpa message", "msg", m) + switch m.Type() { case voteType, precommitType: vm, ok := m.(*VoteMessage) @@ -63,6 +66,47 @@ func (h *MessageHandler) handleMessage(msg *ConsensusMessage) (*ConsensusMessage if fm, ok := m.(*FinalizationMessage); ok { return h.handleFinalizationMessage(fm) } + case neighbourType: + nm, ok := m.(*NeighbourMessage) + if !ok { + return nil, nil + } + + currFinalized, err := h.grandpa.blockState.GetFinalizedHeader(0, 0) + if err != nil { + return nil, err + } + + if uint32(currFinalized.Number.Int64()) >= nm.Number { + return nil, nil + } + + head, err := h.grandpa.blockState.BestBlockNumber() + if err != nil { + return nil, err + } + + if uint32(head.Int64()) < nm.Number { + return nil, nil + } + + // TODO: is there another way to confirm the hash? + hash, err := h.grandpa.blockState.GetHashByNumber(big.NewInt(int64(nm.Number))) + if err != nil { + return nil, err + } + + err = h.grandpa.blockState.SetFinalizedHash(hash, nm.Round, nm.SetID) + if err != nil { + return nil, err + } + + err = h.grandpa.blockState.SetFinalizedHash(hash, 0, 0) + if err != nil { + return nil, err + } + + logger.Info("🔨 finalized block", "number", nm.Number, "hash", hash) case catchUpRequestType: if r, ok := m.(*catchUpRequest); ok { return h.handleCatchUpRequest(r) @@ -79,7 +123,7 @@ func (h *MessageHandler) handleMessage(msg *ConsensusMessage) (*ConsensusMessage } func (h *MessageHandler) handleFinalizationMessage(msg *FinalizationMessage) (*ConsensusMessage, error) { - h.grandpa.logger.Debug("received finalization message", "round", msg.Round, "hash", msg.Vote.hash) + logger.Debug("received finalization message", "round", msg.Round, "hash", msg.Vote.hash) if has, _ := h.blockState.HasFinalizedBlock(msg.Round, h.grandpa.state.setID); has { return nil, nil @@ -108,7 +152,7 @@ func (h *MessageHandler) handleFinalizationMessage(msg *FinalizationMessage) (*C h.grandpa.paused.Store(true) h.grandpa.state.round = msg.Round + 1 req := newCatchUpRequest(msg.Round, h.grandpa.state.setID) - h.grandpa.logger.Debug("sending catch-up request; paused service", "round", msg.Round) + logger.Debug("sending catch-up request; paused service", "round", msg.Round) return req.ToConsensusMessage() } @@ -116,7 +160,7 @@ func (h *MessageHandler) handleFinalizationMessage(msg *FinalizationMessage) (*C } func (h *MessageHandler) handleCatchUpRequest(msg *catchUpRequest) (*ConsensusMessage, error) { - h.grandpa.logger.Debug("received catch up request", "round", msg.Round, "setID", msg.SetID) + logger.Debug("received catch up request", "round", msg.Round, "setID", msg.SetID) if msg.SetID != h.grandpa.state.setID { return nil, ErrSetIDMismatch } @@ -130,16 +174,16 @@ func (h *MessageHandler) handleCatchUpRequest(msg *catchUpRequest) (*ConsensusMe return nil, err } - h.grandpa.logger.Debug("sending catch up response", "round", msg.Round, "setID", msg.SetID, "hash", resp.Hash) + logger.Debug("sending catch up response", "round", msg.Round, "setID", msg.SetID, "hash", resp.Hash) return resp.ToConsensusMessage() } func (h *MessageHandler) handleCatchUpResponse(msg *catchUpResponse) error { - h.grandpa.logger.Debug("received catch up response", "round", msg.Round, "setID", msg.SetID, "hash", msg.Hash) + logger.Debug("received catch up response", "round", msg.Round, "setID", msg.SetID, "hash", msg.Hash) // if we aren't currently expecting a catch up response, return if !h.grandpa.paused.Load().(bool) { - h.grandpa.logger.Debug("not currently paused, ignoring catch up response") + logger.Debug("not currently paused, ignoring catch up response") return nil } @@ -179,7 +223,7 @@ func (h *MessageHandler) handleCatchUpResponse(msg *catchUpResponse) error { close(h.grandpa.resumed) h.grandpa.resumed = make(chan struct{}) h.grandpa.paused.Store(false) - h.grandpa.logger.Debug("caught up to round; unpaused service", "round", h.grandpa.state.round) + logger.Debug("caught up to round; unpaused service", "round", h.grandpa.state.round) return nil } @@ -220,6 +264,11 @@ func decodeMessage(msg *ConsensusMessage) (m GrandpaMessage, err error) { if m, ok = mi.(*FinalizationMessage); !ok { return nil, ErrInvalidMessageType } + case neighbourType: + mi, err = scale.Decode(msg.Data[1:], &NeighbourMessage{}) + if m, ok = mi.(*NeighbourMessage); !ok { + return nil, ErrInvalidMessageType + } case catchUpRequestType: mi, err = scale.Decode(msg.Data[1:], &catchUpRequest{}) if m, ok = mi.(*catchUpRequest); !ok { @@ -257,7 +306,7 @@ func (h *MessageHandler) verifyFinalizationMessageJustification(fm *Finalization // confirm total # signatures >= grandpa threshold if uint64(count) < h.grandpa.state.threshold() { - h.grandpa.logger.Error("minimum votes not met for finalization message", "votes needed", h.grandpa.state.threshold(), + logger.Error("minimum votes not met for finalization message", "votes needed", h.grandpa.state.threshold(), "votes", fm.Justification) return ErrMinVotesNotMet } diff --git a/lib/grandpa/state.go b/lib/grandpa/state.go index fc610f35aa..e4d830d5a6 100644 --- a/lib/grandpa/state.go +++ b/lib/grandpa/state.go @@ -48,6 +48,8 @@ type BlockState interface { SetJustification(hash common.Hash, data []byte) error HasJustification(hash common.Hash) (bool, error) GetJustification(hash common.Hash) ([]byte, error) + GetHashByNumber(num *big.Int) (common.Hash, error) + BestBlockNumber() (*big.Int, error) } // DigestHandler is the interface required by GRANDPA for the digest handler diff --git a/lib/grandpa/vote_message.go b/lib/grandpa/vote_message.go index 9315c35879..5767426289 100644 --- a/lib/grandpa/vote_message.go +++ b/lib/grandpa/vote_message.go @@ -39,22 +39,22 @@ func (s *Service) receiveMessages(cond func() bool) { continue } - s.logger.Trace("received vote message", "msg", msg) + logger.Trace("received vote message", "msg", msg) vm, ok := msg.(*VoteMessage) if !ok { - s.logger.Trace("failed to cast message to VoteMessage") + logger.Trace("failed to cast message to VoteMessage") continue } v, err := s.validateMessage(vm) if err != nil { - s.logger.Trace("failed to validate vote message", "message", vm, "error", err) + logger.Trace("failed to validate vote message", "message", vm, "error", err) continue } - s.logger.Debug("validated vote message", "vote", v, "round", vm.Round, "subround", vm.Stage, "precommits", s.precommits) + logger.Debug("validated vote message", "vote", v, "round", vm.Round, "subround", vm.Stage, "precommits", s.precommits) case <-ctx.Done(): - s.logger.Trace("returning from receiveMessages") + logger.Trace("returning from receiveMessages") return } } @@ -90,7 +90,7 @@ func (s *Service) sendMessage(vote *Vote, stage subround) error { } s.network.SendMessage(cm) - s.logger.Trace("sent VoteMessage", "msg", msg) + logger.Trace("sent VoteMessage", "msg", msg) return nil } From bf132656a5d86d45a6e02391df50d54630a87ca3 Mon Sep 17 00:00:00 2001 From: noot Date: Mon, 12 Apr 2021 21:27:36 -0400 Subject: [PATCH 15/40] fix test --- dot/network/message_test.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/dot/network/message_test.go b/dot/network/message_test.go index 3fa96e5eb2..48bb7879d4 100644 --- a/dot/network/message_test.go +++ b/dot/network/message_test.go @@ -340,8 +340,6 @@ func TestDecodeTransactionMessageTwoExtrinsics(t *testing.T) { } func TestDecodeConsensusMessage(t *testing.T) { - ConsensusEngineID := types.BabeEngineID - testID := hex.EncodeToString(types.BabeEngineID.ToBytes()) testData := "03100405" @@ -358,8 +356,7 @@ func TestDecodeConsensusMessage(t *testing.T) { require.Nil(t, err) expected := &ConsensusMessage{ - ConsensusEngineID: ConsensusEngineID, - Data: out, + Data: out, } require.Equal(t, expected, m) From d3e30e1e4615a401558a56c9bca06339228f7a2a Mon Sep 17 00:00:00 2001 From: noot Date: Mon, 12 Apr 2021 21:32:46 -0400 Subject: [PATCH 16/40] fix grandpa tests --- lib/grandpa/message_handler_test.go | 9 +++------ lib/grandpa/message_test.go | 9 +++------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/lib/grandpa/message_handler_test.go b/lib/grandpa/message_handler_test.go index 090920aa09..6e091e0db1 100644 --- a/lib/grandpa/message_handler_test.go +++ b/lib/grandpa/message_handler_test.go @@ -71,8 +71,7 @@ func createSignedVoteMsg(t *testing.T, number, round, setID uint64, pk *ed25519. func TestDecodeMessage_VoteMessage(t *testing.T) { cm := &ConsensusMessage{ - ConsensusEngineID: types.GrandpaEngineID, - Data: common.MustHexToBytes("0x004d000000000000006300000000000000017db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a777700000000000036e6eca85489bebbb0f687ca5404748d5aa2ffabee34e3ed272cc7b2f6d0a82c65b99bc7cd90dbc21bb528289ebf96705dbd7d96918d34d815509b4e0e2a030f34602b88f60513f1c805d87ef52896934baf6a662bc37414dbdbf69356b1a691"), + Data: common.MustHexToBytes("0x004d000000000000006300000000000000017db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a777700000000000036e6eca85489bebbb0f687ca5404748d5aa2ffabee34e3ed272cc7b2f6d0a82c65b99bc7cd90dbc21bb528289ebf96705dbd7d96918d34d815509b4e0e2a030f34602b88f60513f1c805d87ef52896934baf6a662bc37414dbdbf69356b1a691"), } msg, err := decodeMessage(cm) @@ -99,8 +98,7 @@ func TestDecodeMessage_VoteMessage(t *testing.T) { func TestDecodeMessage_FinalizationMessage(t *testing.T) { cm := &ConsensusMessage{ - ConsensusEngineID: types.GrandpaEngineID, - Data: common.MustHexToBytes("0x024d000000000000007db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a0000000000000000040a0b0c0d00000000000000000000000000000000000000000000000000000000e7030000000000000102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034602b88f60513f1c805d87ef52896934baf6a662bc37414dbdbf69356b1a691"), + Data: common.MustHexToBytes("0x054d000000000000007db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a0000000000000000040a0b0c0d00000000000000000000000000000000000000000000000000000000e7030000000000000102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034602b88f60513f1c805d87ef52896934baf6a662bc37414dbdbf69356b1a691"), } msg, err := decodeMessage(cm) @@ -126,8 +124,7 @@ func TestDecodeMessage_FinalizationMessage(t *testing.T) { func TestDecodeMessage_CatchUpRequest(t *testing.T) { cm := &ConsensusMessage{ - ConsensusEngineID: types.GrandpaEngineID, - Data: common.MustHexToBytes("0x0311000000000000002200000000000000"), + Data: common.MustHexToBytes("0x0311000000000000002200000000000000"), } msg, err := decodeMessage(cm) diff --git a/lib/grandpa/message_test.go b/lib/grandpa/message_test.go index 1cd458667d..74e758a941 100644 --- a/lib/grandpa/message_test.go +++ b/lib/grandpa/message_test.go @@ -43,8 +43,7 @@ func TestVoteMessageToConsensusMessage(t *testing.T) { require.NoError(t, err) expected := &ConsensusMessage{ - ConsensusEngineID: types.GrandpaEngineID, - Data: common.MustHexToBytes("0x014d000000000000006300000000000000017db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a7777000000000000a28633c3a1046351931209fe9182fd530dc659d54ece48e9f88f4277e47f39eb78a84d50e3d37e1b50786d88abafceb5137044b6122fb6b7b5ae8ff62787cc0e34602b88f60513f1c805d87ef52896934baf6a662bc37414dbdbf69356b1a691"), + Data: common.MustHexToBytes("0x014d000000000000006300000000000000017db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a7777000000000000a28633c3a1046351931209fe9182fd530dc659d54ece48e9f88f4277e47f39eb78a84d50e3d37e1b50786d88abafceb5137044b6122fb6b7b5ae8ff62787cc0e34602b88f60513f1c805d87ef52896934baf6a662bc37414dbdbf69356b1a691"), } require.Equal(t, expected, cm) @@ -57,8 +56,7 @@ func TestVoteMessageToConsensusMessage(t *testing.T) { require.NoError(t, err) expected = &ConsensusMessage{ - ConsensusEngineID: types.GrandpaEngineID, - Data: common.MustHexToBytes("0x004d000000000000006300000000000000007db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a7777000000000000215cea37b45853e63d4cc2f0a04c7a33aec9fc5683ac46b03a01e6c41ce46e4339bb7456667f14d109b49e8af26090f7087991f3b22494df997551ae44a0ef0034602b88f60513f1c805d87ef52896934baf6a662bc37414dbdbf69356b1a691"), + Data: common.MustHexToBytes("0x004d000000000000006300000000000000007db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a7777000000000000215cea37b45853e63d4cc2f0a04c7a33aec9fc5683ac46b03a01e6c41ce46e4339bb7456667f14d109b49e8af26090f7087991f3b22494df997551ae44a0ef0034602b88f60513f1c805d87ef52896934baf6a662bc37414dbdbf69356b1a691"), } require.Equal(t, expected, cm) @@ -79,8 +77,7 @@ func TestFinalizationMessageToConsensusMessage(t *testing.T) { require.NoError(t, err) expected := &ConsensusMessage{ - ConsensusEngineID: types.GrandpaEngineID, - Data: common.MustHexToBytes("0x024d000000000000007db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a0000000000000000040a0b0c0d00000000000000000000000000000000000000000000000000000000e7030000000000000102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034602b88f60513f1c805d87ef52896934baf6a662bc37414dbdbf69356b1a691"), + Data: common.MustHexToBytes("0x054d000000000000007db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a0000000000000000040a0b0c0d00000000000000000000000000000000000000000000000000000000e7030000000000000102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034602b88f60513f1c805d87ef52896934baf6a662bc37414dbdbf69356b1a691"), } require.Equal(t, expected, cm) From a3620c0fa1dbae240b54631b250b0e0c768cbd5e Mon Sep 17 00:00:00 2001 From: noot Date: Mon, 12 Apr 2021 21:34:43 -0400 Subject: [PATCH 17/40] lint --- lib/grandpa/message.go | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/grandpa/message.go b/lib/grandpa/message.go index 94bd37d9ed..4cc72da9d0 100644 --- a/lib/grandpa/message.go +++ b/lib/grandpa/message.go @@ -92,6 +92,7 @@ func (v *VoteMessage) ToConsensusMessage() (*ConsensusMessage, error) { }, nil } +// NeighbourMessage represents a network-level neighbour message type NeighbourMessage struct { Version byte Round uint64 From c267ccb5ab97959ae8f2a5131d881994d8aade4f Mon Sep 17 00:00:00 2001 From: noot Date: Mon, 12 Apr 2021 21:35:40 -0400 Subject: [PATCH 18/40] lint --- lib/grandpa/message.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/grandpa/message.go b/lib/grandpa/message.go index 4cc72da9d0..9e68c30538 100644 --- a/lib/grandpa/message.go +++ b/lib/grandpa/message.go @@ -92,7 +92,7 @@ func (v *VoteMessage) ToConsensusMessage() (*ConsensusMessage, error) { }, nil } -// NeighbourMessage represents a network-level neighbour message +// NeighbourMessage represents a network-level neighbor message type NeighbourMessage struct { Version byte Round uint64 From 9e8189fe234bbc8c503bfe3eef4aa15e4c140b1d Mon Sep 17 00:00:00 2001 From: noot Date: Tue, 13 Apr 2021 10:29:29 -0400 Subject: [PATCH 19/40] fix test --- dot/network/message_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/dot/network/message_test.go b/dot/network/message_test.go index 48bb7879d4..ba02958cce 100644 --- a/dot/network/message_test.go +++ b/dot/network/message_test.go @@ -340,10 +340,8 @@ func TestDecodeTransactionMessageTwoExtrinsics(t *testing.T) { } func TestDecodeConsensusMessage(t *testing.T) { - testID := hex.EncodeToString(types.BabeEngineID.ToBytes()) testData := "03100405" - - msg := "0x" + testID + testData // 0x4241424503100405 + msg := "0x" + testData encMsg, err := common.HexToBytes(msg) require.Nil(t, err) From 4b5d23b1524a373b8fc778ef01ccfeb1dcd0abed Mon Sep 17 00:00:00 2001 From: noot Date: Tue, 13 Apr 2021 10:41:17 -0400 Subject: [PATCH 20/40] move persistent peer reconnect to goroutine --- dot/network/connmgr.go | 20 +++++++++++--------- dot/network/connmgr_test.go | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/dot/network/connmgr.go b/dot/network/connmgr.go index f6e6eb4871..276ad0e6e6 100644 --- a/dot/network/connmgr.go +++ b/dot/network/connmgr.go @@ -196,16 +196,18 @@ func (cm *ConnManager) Disconnected(n network.Network, c network.Conn) { Addrs: addrs, } - for i := 0; i < maxRetries; i++ { - err := cm.host.connect(info) - if err != nil { - logger.Warn("failed to reconnect to persistent peer", "peer", c.RemotePeer(), "error", err) - time.Sleep(time.Minute) - continue - } + go func() { + for i := 0; i < maxRetries; i++ { + err := cm.host.connect(info) + if err != nil { + logger.Warn("failed to reconnect to persistent peer", "peer", c.RemotePeer(), "error", err) + time.Sleep(time.Minute) + continue + } - return - } + return + } + }() // TODO: if number of peers falls below the min desired peer count, we should try to connect to previously discovered peers } diff --git a/dot/network/connmgr_test.go b/dot/network/connmgr_test.go index e760704013..5f6b22bbcb 100644 --- a/dot/network/connmgr_test.go +++ b/dot/network/connmgr_test.go @@ -112,7 +112,7 @@ func TestPersistentPeers(t *testing.T) { require.NotEqual(t, 0, len(conns)) // if A disconnects from B, B should reconnect - nodeA.host.h.Network().ClosePeer(nodeA.host.id()) + nodeA.host.h.Network().ClosePeer(nodeB.host.id()) time.Sleep(time.Millisecond * 500) conns = nodeB.host.h.Network().ConnsToPeer(nodeA.host.id()) require.NotEqual(t, 0, len(conns)) From d02b1748fb5afde25e27fa56d4bac96baf5683fb Mon Sep 17 00:00:00 2001 From: noot Date: Tue, 13 Apr 2021 11:30:40 -0400 Subject: [PATCH 21/40] cleanup --- dot/network/sync.go | 154 +++++++++++++++------------------ lib/grandpa/message_handler.go | 77 +++++++++-------- 2 files changed, 113 insertions(+), 118 deletions(-) diff --git a/dot/network/sync.go b/dot/network/sync.go index b38c56ce73..ee823aa537 100644 --- a/dot/network/sync.go +++ b/dot/network/sync.go @@ -99,8 +99,8 @@ var ( ) var ( - errEmptyResponseData = fmt.Errorf("response data is empty") - errEmptyJustificationData = fmt.Errorf("no justifications in response data") + errEmptyResponseData = fmt.Errorf("response data is empty") + //errEmptyJustificationData = fmt.Errorf("no justifications in response data") ) type syncPeer struct { @@ -126,9 +126,9 @@ type syncQueue struct { cancel context.CancelFunc peerScore *sync.Map // map[peer.ID]int; peers we have successfully synced from before -> their score; score increases on successful response - requestData *sync.Map // map[uint64]requestData; map of start # of request -> requestData - justificationRequestData *sync.Map // map[common.Hash]requestData; map of requests of justifications -> requestData - requestCh chan *syncRequest + requestData *sync.Map // map[uint64]requestData; map of start # of request -> requestData + //justificationRequestData *sync.Map // map[common.Hash]requestData; map of requests of justifications -> requestData + requestCh chan *syncRequest responses []*types.BlockData responseCh chan []*types.BlockData @@ -145,18 +145,18 @@ func newSyncQueue(s *Service) *syncQueue { ctx, cancel := context.WithCancel(s.ctx) return &syncQueue{ - s: s, - slotDuration: defaultSlotDuration, - ctx: ctx, - cancel: cancel, - peerScore: new(sync.Map), - requestData: new(sync.Map), - justificationRequestData: new(sync.Map), - requestCh: make(chan *syncRequest, blockRequestBufferSize), - responses: []*types.BlockData{}, - responseCh: make(chan []*types.BlockData, blockResponseBufferSize), - benchmarker: newSyncBenchmarker(), - buf: make([]byte, maxBlockResponseSize), + s: s, + slotDuration: defaultSlotDuration, + ctx: ctx, + cancel: cancel, + peerScore: new(sync.Map), + requestData: new(sync.Map), + //justificationRequestData: new(sync.Map), + requestCh: make(chan *syncRequest, blockRequestBufferSize), + responses: []*types.BlockData{}, + responseCh: make(chan []*types.BlockData, blockResponseBufferSize), + benchmarker: newSyncBenchmarker(), + buf: make([]byte, maxBlockResponseSize), } } @@ -460,33 +460,33 @@ func (q *syncQueue) pushResponse(resp *BlockResponseMessage, pid peer.ID) error return errEmptyResponseData } - startHash := resp.BlockData[0].Hash - if _, has := q.justificationRequestData.Load(startHash); has && !resp.BlockData[0].Header.Exists() { - numJustifications := 0 - justificationResponses := []*types.BlockData{} - - for _, bd := range resp.BlockData { - if bd.Justification.Exists() { - justificationResponses = append(justificationResponses, bd) - numJustifications++ - } - } - - if numJustifications == 0 { - return errEmptyJustificationData - } - - q.updatePeerScore(pid, 1) - q.justificationRequestData.Store(startHash, requestData{ - sent: true, - received: true, - from: pid, - }) - - logger.Info("pushed justification data to queue", "hash", startHash) - q.responseCh <- justificationResponses - return nil - } + //startHash := resp.BlockData[0].Hash + // if _, has := q.justificationRequestData.Load(startHash); has && !resp.BlockData[0].Header.Exists() { + // numJustifications := 0 + // justificationResponses := []*types.BlockData{} + + // for _, bd := range resp.BlockData { + // if bd.Justification.Exists() { + // justificationResponses = append(justificationResponses, bd) + // numJustifications++ + // } + // } + + // if numJustifications == 0 { + // return errEmptyJustificationData + // } + + // q.updatePeerScore(pid, 1) + // q.justificationRequestData.Store(startHash, requestData{ + // sent: true, + // received: true, + // from: pid, + // }) + + // logger.Info("pushed justification data to queue", "hash", startHash) + // q.responseCh <- justificationResponses + // return nil + // } start, end, err := resp.getStartAndEnd() if err != nil { @@ -588,7 +588,7 @@ func (q *syncQueue) trySync(req *syncRequest) { } err = q.pushResponse(resp, peer.pid) - if err != nil && err != errEmptyResponseData && err != errEmptyJustificationData { + if err != nil && err != errEmptyResponseData /*&& err != errEmptyJustificationData*/ { logger.Debug("failed to push block response", "error", err) } else { return @@ -601,12 +601,12 @@ func (q *syncQueue) trySync(req *syncRequest) { sent: true, received: false, }) - } else if req.req.StartingBlock.IsHash() && (req.req.RequestedData&RequestedDataHeader) == 0 { + } /*else if req.req.StartingBlock.IsHash() && (req.req.RequestedData&RequestedDataHeader) == 0 { q.justificationRequestData.Store(req.req.StartingBlock.Hash(), requestData{ sent: true, received: false, }) - } + }*/ req.to = "" q.requestCh <- req @@ -651,10 +651,10 @@ func (q *syncQueue) processBlockResponses() { select { case data := <-q.responseCh: // if the response doesn't contain a header, then it's a justification-only response - if !data[0].Header.Exists() { - q.handleBlockJustification(data) - continue - } + // if !data[0].Header.Exists() { + // q.handleBlockJustification(data) + // continue + // } q.handleBlockData(data) case <-q.ctx.Done(): @@ -663,44 +663,34 @@ func (q *syncQueue) processBlockResponses() { } } -func (q *syncQueue) handleBlockJustification(data []*types.BlockData) { - startHash, endHash := data[0].Hash, data[len(data)-1].Hash - logger.Debug("sending justification data to syncer", "start", startHash, "end", endHash) +// func (q *syncQueue) handleBlockJustification(data []*types.BlockData) { +// startHash, endHash := data[0].Hash, data[len(data)-1].Hash +// logger.Debug("sending justification data to syncer", "start", startHash, "end", endHash) - _, err := q.s.syncer.ProcessBlockData(data) - if err != nil { - logger.Warn("failed to handle block justifications", "error", err) - return - } +// _, err := q.s.syncer.ProcessBlockData(data) +// if err != nil { +// logger.Warn("failed to handle block justifications", "error", err) +// return +// } - logger.Debug("finished processing justification data", "start", startHash, "end", endHash) +// logger.Debug("finished processing justification data", "start", startHash, "end", endHash) - // update peer's score - var from peer.ID +// // update peer's score +// var from peer.ID - d, ok := q.justificationRequestData.Load(startHash) - if !ok { - // this shouldn't happen - logger.Debug("can't find request data for response!", "start", startHash) - } else { - from = d.(requestData).from - q.updatePeerScore(from, 2) - q.justificationRequestData.Delete(startHash) - } -} +// d, ok := q.justificationRequestData.Load(startHash) +// if !ok { +// // this shouldn't happen +// logger.Debug("can't find request data for response!", "start", startHash) +// } else { +// from = d.(requestData).from +// q.updatePeerScore(from, 2) +// q.justificationRequestData.Delete(startHash) +// } +// } func (q *syncQueue) handleBlockData(data []*types.BlockData) { - // finalized, err := q.s.blockState.GetFinalizedHeader(0, 0) - // if err != nil { - // panic(err) - // } - end := data[len(data)-1].Number().Int64() - // if end <= finalized.Number.Int64() { - // logger.Debug("ignoring block data that is below our head", "got", end, "head", finalized.Number.Int64()) - // q.pushRequest(uint64(end+1), blockRequestBufferSize, "") - // return - // } defer func() { q.currStart = 0 diff --git a/lib/grandpa/message_handler.go b/lib/grandpa/message_handler.go index 34b0c62106..8ef9c7773c 100644 --- a/lib/grandpa/message_handler.go +++ b/lib/grandpa/message_handler.go @@ -53,7 +53,7 @@ func (h *MessageHandler) handleMessage(msg *ConsensusMessage) (*ConsensusMessage return nil, err } - logger.Info("handling grandpa message", "msg", m) + logger.Debug("handling grandpa message", "msg", m) switch m.Type() { case voteType, precommitType: @@ -72,41 +72,7 @@ func (h *MessageHandler) handleMessage(msg *ConsensusMessage) (*ConsensusMessage return nil, nil } - currFinalized, err := h.grandpa.blockState.GetFinalizedHeader(0, 0) - if err != nil { - return nil, err - } - - if uint32(currFinalized.Number.Int64()) >= nm.Number { - return nil, nil - } - - head, err := h.grandpa.blockState.BestBlockNumber() - if err != nil { - return nil, err - } - - if uint32(head.Int64()) < nm.Number { - return nil, nil - } - - // TODO: is there another way to confirm the hash? - hash, err := h.grandpa.blockState.GetHashByNumber(big.NewInt(int64(nm.Number))) - if err != nil { - return nil, err - } - - err = h.grandpa.blockState.SetFinalizedHash(hash, nm.Round, nm.SetID) - if err != nil { - return nil, err - } - - err = h.grandpa.blockState.SetFinalizedHash(hash, 0, 0) - if err != nil { - return nil, err - } - - logger.Info("🔨 finalized block", "number", nm.Number, "hash", hash) + return nil, h.handleNeighbourMessage(nm) case catchUpRequestType: if r, ok := m.(*catchUpRequest); ok { return h.handleCatchUpRequest(r) @@ -122,6 +88,45 @@ func (h *MessageHandler) handleMessage(msg *ConsensusMessage) (*ConsensusMessage return nil, nil } +func (h *MessageHandler) handleNeighbourMessage(msg *NeighbourMessage) error { + currFinalized, err := h.grandpa.blockState.GetFinalizedHeader(0, 0) + if err != nil { + return err + } + + if uint32(currFinalized.Number.Int64()) >= msg.Number { + return nil + } + + head, err := h.grandpa.blockState.BestBlockNumber() + if err != nil { + return err + } + + if uint32(head.Int64()) < msg.Number { + return nil + } + + // TODO: is there another way to confirm the hash? + hash, err := h.grandpa.blockState.GetHashByNumber(big.NewInt(int64(msg.Number))) + if err != nil { + return err + } + + err = h.grandpa.blockState.SetFinalizedHash(hash, msg.Round, msg.SetID) + if err != nil { + return err + } + + err = h.grandpa.blockState.SetFinalizedHash(hash, 0, 0) + if err != nil { + return err + } + + logger.Info("🔨 finalized block", "number", msg.Number, "hash", hash) + return nil +} + func (h *MessageHandler) handleFinalizationMessage(msg *FinalizationMessage) (*ConsensusMessage, error) { logger.Debug("received finalization message", "round", msg.Round, "hash", msg.Vote.hash) From 2d50d14a53683d09a1625f732e5fc0ddb294bf39 Mon Sep 17 00:00:00 2001 From: noot Date: Tue, 13 Apr 2021 12:40:36 -0400 Subject: [PATCH 22/40] update blocktree err checking in syncer --- dot/network/sync.go | 3 ++- dot/sync/syncer.go | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/dot/network/sync.go b/dot/network/sync.go index ee823aa537..abc356e74b 100644 --- a/dot/network/sync.go +++ b/dot/network/sync.go @@ -26,6 +26,7 @@ import ( "time" "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/blocktree" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/common/optional" "github.com/ChainSafe/gossamer/lib/common/variadic" @@ -727,7 +728,7 @@ func (q *syncQueue) handleBlockData(data []*types.BlockData) { func (q *syncQueue) handleBlockDataFailure(idx int, err error, data []*types.BlockData) { logger.Warn("failed to handle block data", "failed on block", q.currStart+int64(idx), "error", err) - if errors.Is(err, chaindb.ErrKeyNotFound) { + if errors.Is(err, chaindb.ErrKeyNotFound) || errors.Is(err, blocktree.ErrParentNotFound) { header, err := types.NewHeaderFromOptional(data[idx].Header) if err != nil { logger.Debug("failed to get header from BlockData", "idx", idx, "error", err) diff --git a/dot/sync/syncer.go b/dot/sync/syncer.go index 9f6a7861a1..516566b750 100644 --- a/dot/sync/syncer.go +++ b/dot/sync/syncer.go @@ -184,8 +184,9 @@ func (s *Service) ProcessBlockData(data []*types.BlockData) (int, error) { } err = s.blockState.AddBlockToBlockTree(header) - if err != nil { - logger.Debug("failed to add block to blocktree", "hash", bd.Hash, "error", err) + if err != nil && !errors.Is(err, blocktree.ErrBlockExists) { + logger.Warn("failed to add block to blocktree", "hash", bd.Hash, "error", err) + return i, err } if bd.Justification != nil && bd.Justification.Exists() { From 28cab3ea62b83f0507c93963fa400b4965a7a91d Mon Sep 17 00:00:00 2001 From: noot Date: Tue, 13 Apr 2021 14:21:01 -0400 Subject: [PATCH 23/40] re-add justification request logic --- dot/network/sync.go | 152 ++++++++++++---------- dot/network/sync_justification.go | 92 ++++++++++++++ dot/network/sync_justification_test.go | 168 +++++++++++++++++++++++++ 3 files changed, 345 insertions(+), 67 deletions(-) create mode 100644 dot/network/sync_justification.go create mode 100644 dot/network/sync_justification_test.go diff --git a/dot/network/sync.go b/dot/network/sync.go index abc356e74b..9aa86cd841 100644 --- a/dot/network/sync.go +++ b/dot/network/sync.go @@ -100,8 +100,8 @@ var ( ) var ( - errEmptyResponseData = fmt.Errorf("response data is empty") - //errEmptyJustificationData = fmt.Errorf("no justifications in response data") + errEmptyResponseData = fmt.Errorf("response data is empty") + errEmptyJustificationData = fmt.Errorf("no justifications in response data") ) type syncPeer struct { @@ -127,9 +127,9 @@ type syncQueue struct { cancel context.CancelFunc peerScore *sync.Map // map[peer.ID]int; peers we have successfully synced from before -> their score; score increases on successful response - requestData *sync.Map // map[uint64]requestData; map of start # of request -> requestData - //justificationRequestData *sync.Map // map[common.Hash]requestData; map of requests of justifications -> requestData - requestCh chan *syncRequest + requestData *sync.Map // map[uint64]requestData; map of start # of request -> requestData + justificationRequestData *sync.Map // map[common.Hash]requestData; map of requests of justifications -> requestData + requestCh chan *syncRequest responses []*types.BlockData responseCh chan []*types.BlockData @@ -146,18 +146,18 @@ func newSyncQueue(s *Service) *syncQueue { ctx, cancel := context.WithCancel(s.ctx) return &syncQueue{ - s: s, - slotDuration: defaultSlotDuration, - ctx: ctx, - cancel: cancel, - peerScore: new(sync.Map), - requestData: new(sync.Map), - //justificationRequestData: new(sync.Map), - requestCh: make(chan *syncRequest, blockRequestBufferSize), - responses: []*types.BlockData{}, - responseCh: make(chan []*types.BlockData, blockResponseBufferSize), - benchmarker: newSyncBenchmarker(), - buf: make([]byte, maxBlockResponseSize), + s: s, + slotDuration: defaultSlotDuration, + ctx: ctx, + cancel: cancel, + peerScore: new(sync.Map), + requestData: new(sync.Map), + justificationRequestData: new(sync.Map), + requestCh: make(chan *syncRequest, blockRequestBufferSize), + responses: []*types.BlockData{}, + responseCh: make(chan []*types.BlockData, blockResponseBufferSize), + benchmarker: newSyncBenchmarker(), + buf: make([]byte, maxBlockResponseSize), } } @@ -461,33 +461,33 @@ func (q *syncQueue) pushResponse(resp *BlockResponseMessage, pid peer.ID) error return errEmptyResponseData } - //startHash := resp.BlockData[0].Hash - // if _, has := q.justificationRequestData.Load(startHash); has && !resp.BlockData[0].Header.Exists() { - // numJustifications := 0 - // justificationResponses := []*types.BlockData{} + startHash := resp.BlockData[0].Hash + if _, has := q.justificationRequestData.Load(startHash); has && !resp.BlockData[0].Header.Exists() { + numJustifications := 0 + justificationResponses := []*types.BlockData{} - // for _, bd := range resp.BlockData { - // if bd.Justification.Exists() { - // justificationResponses = append(justificationResponses, bd) - // numJustifications++ - // } - // } + for _, bd := range resp.BlockData { + if bd.Justification.Exists() { + justificationResponses = append(justificationResponses, bd) + numJustifications++ + } + } - // if numJustifications == 0 { - // return errEmptyJustificationData - // } + if numJustifications == 0 { + return errEmptyJustificationData + } - // q.updatePeerScore(pid, 1) - // q.justificationRequestData.Store(startHash, requestData{ - // sent: true, - // received: true, - // from: pid, - // }) + q.updatePeerScore(pid, 1) + q.justificationRequestData.Store(startHash, requestData{ + sent: true, + received: true, + from: pid, + }) - // logger.Info("pushed justification data to queue", "hash", startHash) - // q.responseCh <- justificationResponses - // return nil - // } + logger.Info("pushed justification data to queue", "hash", startHash) + q.responseCh <- justificationResponses + return nil + } start, end, err := resp.getStartAndEnd() if err != nil { @@ -602,12 +602,12 @@ func (q *syncQueue) trySync(req *syncRequest) { sent: true, received: false, }) - } /*else if req.req.StartingBlock.IsHash() && (req.req.RequestedData&RequestedDataHeader) == 0 { + } else if req.req.StartingBlock.IsHash() && (req.req.RequestedData&RequestedDataHeader) == 0 { q.justificationRequestData.Store(req.req.StartingBlock.Hash(), requestData{ sent: true, received: false, }) - }*/ + } req.to = "" q.requestCh <- req @@ -652,10 +652,10 @@ func (q *syncQueue) processBlockResponses() { select { case data := <-q.responseCh: // if the response doesn't contain a header, then it's a justification-only response - // if !data[0].Header.Exists() { - // q.handleBlockJustification(data) - // continue - // } + if !data[0].Header.Exists() { + q.handleBlockJustification(data) + continue + } q.handleBlockData(data) case <-q.ctx.Done(): @@ -664,35 +664,53 @@ func (q *syncQueue) processBlockResponses() { } } -// func (q *syncQueue) handleBlockJustification(data []*types.BlockData) { -// startHash, endHash := data[0].Hash, data[len(data)-1].Hash -// logger.Debug("sending justification data to syncer", "start", startHash, "end", endHash) +func (q *syncQueue) handleBlockJustification(data []*types.BlockData) { + startHash, endHash := data[0].Hash, data[len(data)-1].Hash + logger.Debug("sending justification data to syncer", "start", startHash, "end", endHash) -// _, err := q.s.syncer.ProcessBlockData(data) -// if err != nil { -// logger.Warn("failed to handle block justifications", "error", err) -// return -// } + _, err := q.s.syncer.ProcessBlockData(data) + if err != nil { + logger.Warn("failed to handle block justifications", "error", err) + return + } -// logger.Debug("finished processing justification data", "start", startHash, "end", endHash) + logger.Debug("finished processing justification data", "start", startHash, "end", endHash) -// // update peer's score -// var from peer.ID + // update peer's score + var from peer.ID -// d, ok := q.justificationRequestData.Load(startHash) -// if !ok { -// // this shouldn't happen -// logger.Debug("can't find request data for response!", "start", startHash) -// } else { -// from = d.(requestData).from -// q.updatePeerScore(from, 2) -// q.justificationRequestData.Delete(startHash) -// } -// } + d, ok := q.justificationRequestData.Load(startHash) + if !ok { + // this shouldn't happen + logger.Debug("can't find request data for response!", "start", startHash) + } else { + from = d.(requestData).from + q.updatePeerScore(from, 2) + q.justificationRequestData.Delete(startHash) + } +} func (q *syncQueue) handleBlockData(data []*types.BlockData) { end := data[len(data)-1].Number().Int64() + var ( + finalized *types.Header + err error + ) + + for { + finalized, err = q.s.blockState.GetFinalizedHeader(0, 0) + if err == nil { + break // TODO: seems blockState needs better concurrency handling + } + } + + if end <= finalized.Number.Int64() { + logger.Debug("ignoring block data that is below our head", "got", end, "head", finalized.Number.Int64()) + q.pushRequest(uint64(end+1), blockRequestBufferSize, "") + return + } + defer func() { q.currStart = 0 q.currEnd = 0 diff --git a/dot/network/sync_justification.go b/dot/network/sync_justification.go new file mode 100644 index 0000000000..e0897d0d10 --- /dev/null +++ b/dot/network/sync_justification.go @@ -0,0 +1,92 @@ +// Copyright 2019 ChainSafe Systems (ON) Corp. +// This file is part of gossamer. +// +// The gossamer library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The gossamer library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the gossamer library. If not, see . + +package network + +import ( + "math/big" + "time" +) + +func (q *syncQueue) finalizeAtHead() { + prev, err := q.s.blockState.GetFinalizedHeader(0, 0) + if err != nil { + logger.Error("failed to get latest finalized block header", "error", err) + return + } + + for { + select { + // sleep for average block time TODO: make this configurable from slot duration + case <-time.After(q.slotDuration * 2): + case <-q.ctx.Done(): + return + } + + head, err := q.s.blockState.BestBlockNumber() + if err != nil { + continue + } + + if head.Int64() < q.goal { + continue + } + + curr, err := q.s.blockState.GetFinalizedHeader(0, 0) + if err != nil { + continue + } + + logger.Debug("checking finalized blocks", "curr", curr.Number, "prev", prev.Number) + + if curr.Number.Cmp(prev.Number) > 0 { + prev = curr + continue + } + + prev = curr + + start := head.Uint64() - uint64(blockRequestSize) + if curr.Number.Uint64() > start { + start = curr.Number.Uint64() + 1 + } else if int(start) < int(blockRequestSize) { + start = 1 + } + + q.pushJustificationRequest(start) + } +} + +func (q *syncQueue) pushJustificationRequest(start uint64) { + startHash, err := q.s.blockState.GetHashByNumber(big.NewInt(int64(start))) + if err != nil { + logger.Error("failed to get hash for block w/ number", "number", start, "error", err) + return + } + + req := createBlockRequestWithHash(startHash, blockRequestSize) + req.RequestedData = RequestedDataJustification + + logger.Debug("pushing justification request to queue", "start", start, "hash", startHash) + q.justificationRequestData.Store(startHash, requestData{ + received: false, + }) + + q.requestCh <- &syncRequest{ + req: req, + to: "", + } +} diff --git a/dot/network/sync_justification_test.go b/dot/network/sync_justification_test.go new file mode 100644 index 0000000000..00d854471a --- /dev/null +++ b/dot/network/sync_justification_test.go @@ -0,0 +1,168 @@ +// Copyright 2019 ChainSafe Systems (ON) Corp. +// This file is part of gossamer. +// +// The gossamer library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The gossamer library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the gossamer library. If not, see . + +package network + +import ( + "context" + "math/big" + "testing" + "time" + + "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/common/optional" + "github.com/ChainSafe/gossamer/lib/utils" + + "github.com/libp2p/go-libp2p-core/peer" + "github.com/stretchr/testify/require" +) + +func TestSyncQueue_PushResponse_Justification(t *testing.T) { + basePath := utils.NewTestBasePath(t, "nodeA") + config := &Config{ + BasePath: basePath, + Port: 7001, + RandSeed: 1, + NoBootstrap: true, + NoMDNS: true, + } + + s := createTestService(t, config) + s.syncQueue.stop() + time.Sleep(time.Second) + + peerID := peer.ID("noot") + msg := &BlockResponseMessage{ + BlockData: []*types.BlockData{}, + } + + for i := 0; i < int(blockRequestSize); i++ { + msg.BlockData = append(msg.BlockData, &types.BlockData{ + Hash: common.Hash{byte(i)}, + Justification: optional.NewBytes(true, []byte{1}), + }) + } + + s.syncQueue.justificationRequestData.Store(common.Hash{byte(0)}, requestData{}) + err := s.syncQueue.pushResponse(msg, peerID) + require.NoError(t, err) + require.Equal(t, 1, len(s.syncQueue.responseCh)) + data, ok := s.syncQueue.justificationRequestData.Load(common.Hash{byte(0)}) + require.True(t, ok) + require.Equal(t, requestData{ + sent: true, + received: true, + from: peerID, + }, data) +} + +func TestSyncQueue_PushResponse_EmptyJustification(t *testing.T) { + basePath := utils.NewTestBasePath(t, "nodeA") + config := &Config{ + BasePath: basePath, + Port: 7001, + RandSeed: 1, + NoBootstrap: true, + NoMDNS: true, + } + + s := createTestService(t, config) + s.syncQueue.stop() + time.Sleep(time.Second) + + peerID := peer.ID("noot") + msg := &BlockResponseMessage{ + BlockData: []*types.BlockData{}, + } + + for i := 0; i < int(blockRequestSize); i++ { + msg.BlockData = append(msg.BlockData, &types.BlockData{ + Hash: common.Hash{byte(i)}, + Justification: optional.NewBytes(false, nil), + }) + } + + s.syncQueue.justificationRequestData.Store(common.Hash{byte(0)}, &requestData{}) + err := s.syncQueue.pushResponse(msg, peerID) + require.Equal(t, errEmptyJustificationData, err) +} + +func TestSyncQueue_processBlockResponses_Justification(t *testing.T) { + q := newTestSyncQueue(t) + q.stop() + time.Sleep(time.Second) + q.ctx = context.Background() + + go func() { + q.responseCh <- []*types.BlockData{ + { + Hash: common.Hash{byte(0)}, + Header: optional.NewHeader(false, nil), + Body: optional.NewBody(false, nil), + Receipt: optional.NewBytes(false, nil), + MessageQueue: optional.NewBytes(false, nil), + Justification: optional.NewBytes(true, []byte{1}), + }, + } + }() + + peerID := peer.ID("noot") + q.justificationRequestData.Store(common.Hash{byte(0)}, requestData{ + from: peerID, + }) + + go q.processBlockResponses() + time.Sleep(time.Second) + + _, has := q.justificationRequestData.Load(common.Hash{byte(0)}) + require.False(t, has) + + score, ok := q.peerScore.Load(peerID) + require.True(t, ok) + require.Equal(t, 2, score) +} + +func TestSyncQueue_finalizeAtHead(t *testing.T) { + q := newTestSyncQueue(t) + q.stop() + time.Sleep(time.Second) + q.ctx = context.Background() + q.slotDuration = time.Millisecond * 200 + + hash, err := q.s.blockState.GetHashByNumber(big.NewInt(1)) + require.NoError(t, err) + + go q.finalizeAtHead() + time.Sleep(time.Second) + + data, has := q.justificationRequestData.Load(hash) + require.True(t, has) + require.Equal(t, requestData{}, data) + + expected := createBlockRequestWithHash(hash, blockRequestSize) + expected.RequestedData = RequestedDataJustification + + select { + case req := <-q.requestCh: + require.Equal(t, &syncRequest{ + req: expected, + to: "", + }, req) + case <-time.After(time.Second): + t.Fatal("did not receive request") + } +} From 004b5f6eb21af22acff2b148f209172eff652b12 Mon Sep 17 00:00:00 2001 From: noot Date: Tue, 13 Apr 2021 15:06:08 -0400 Subject: [PATCH 24/40] add test for re-org check --- dot/network/sync.go | 2 +- dot/state/block.go | 76 +++++++++++++++++++---- dot/state/block_data.go | 12 ++-- dot/state/block_test.go | 108 +++++++++++++++++++++++++++++++++ lib/grandpa/message_handler.go | 7 ++- 5 files changed, 183 insertions(+), 22 deletions(-) diff --git a/dot/network/sync.go b/dot/network/sync.go index 9aa86cd841..a9233d9ea1 100644 --- a/dot/network/sync.go +++ b/dot/network/sync.go @@ -589,7 +589,7 @@ func (q *syncQueue) trySync(req *syncRequest) { } err = q.pushResponse(resp, peer.pid) - if err != nil && err != errEmptyResponseData /*&& err != errEmptyJustificationData*/ { + if err != nil && err != errEmptyResponseData && err != errEmptyJustificationData { logger.Debug("failed to push block response", "error", err) } else { return diff --git a/dot/state/block.go b/dot/state/block.go index 302d92fe5e..20cd45d6ac 100644 --- a/dot/state/block.go +++ b/dot/state/block.go @@ -38,10 +38,10 @@ const pruneKeyBufferSize = 1000 // BlockState defines fields for manipulating the state of blocks, such as BlockTree, BlockDB and Header type BlockState struct { - bt *blocktree.BlockTree - baseDB chaindb.Database - db chaindb.Database - lock sync.RWMutex + bt *blocktree.BlockTree + baseDB chaindb.Database + db chaindb.Database + sync.RWMutex genesisHash common.Hash // block notifiers @@ -330,9 +330,6 @@ func (bs *BlockState) GetBlockHash(blockNumber *big.Int) (*common.Hash, error) { // SetHeader will set the header into DB func (bs *BlockState) SetHeader(header *types.Header) error { - bs.lock.Lock() - defer bs.lock.Unlock() - hash := header.Hash() // Write the encoded header @@ -366,11 +363,7 @@ func (bs *BlockState) GetBlockBody(hash common.Hash) (*types.Body, error) { // SetBlockBody will add a block body to the db func (bs *BlockState) SetBlockBody(hash common.Hash, body *types.Body) error { - bs.lock.Lock() - defer bs.lock.Unlock() - - err := bs.db.Put(blockBodyKey(hash), body.AsOptional().Value()) - return err + return bs.db.Put(blockBodyKey(hash), body.AsOptional().Value()) } // HasFinalizedBlock returns true if there is a finalized block for a given round and setID, false otherwise @@ -427,6 +420,9 @@ func (bs *BlockState) GetFinalizedHash(round, setID uint64) (common.Hash, error) // SetFinalizedHash sets the latest finalized block header func (bs *BlockState) SetFinalizedHash(hash common.Hash, round, setID uint64) error { + bs.Lock() + defer bs.Unlock() + go bs.notifyFinalized(hash) if round > 0 { err := bs.SetRound(round) @@ -496,6 +492,8 @@ func (bs *BlockState) CompareAndSetBlockData(bd *types.BlockData) error { // AddBlock adds a block to the blocktree and the DB with arrival time as current unix time func (bs *BlockState) AddBlock(block *types.Block) error { + bs.Lock() + defer bs.Unlock() return bs.AddBlockWithArrivalTime(block, time.Now()) } @@ -506,6 +504,8 @@ func (bs *BlockState) AddBlockWithArrivalTime(block *types.Block, arrivalTime ti return err } + prevHead := bs.bt.DeepestBlockHash() + // add block to blocktree err = bs.bt.AddBlock(block.Header, uint64(arrivalTime.UnixNano())) if err != nil { @@ -541,12 +541,62 @@ func (bs *BlockState) AddBlockWithArrivalTime(block *types.Block, arrivalTime ti return err } + // check if there was a re-org, if so, re-set the canonical number->hash mapping + err = bs.handleAddedBlock(prevHead, bs.bt.DeepestBlockHash()) + if err != nil { + return err + } + go bs.notifyImported(block) return bs.baseDB.Flush() } +// handleAddedBlock re-sets the canonical number->hash mapping if there was a chain re-org. +// prev is the previous best block hash before the new block was added to the blocktree. +// curr is the current best blogetck hash. +func (bs *BlockState) handleAddedBlock(prev, curr common.Hash) error { + logger.Info("handling added block", "prev head", prev, "curr head", curr) + + ancestor, err := bs.HighestCommonAncestor(prev, curr) + if err != nil { + return err + } + + logger.Info("handling added block", "ancestor", ancestor) + + // if the highest common ancestor of the previous chain head and current chain head is the previous chain head, + // then the current chain head is the descendant of the previous and thus are on the same chain + if ancestor == prev { + return nil + } + + subchain, err := bs.SubChain(ancestor, curr) + if err != nil { + return err + } + + for _, hash := range subchain { + // TODO: set number from ancestor.Number + i ? + header, err := bs.GetHeader(hash) + if err != nil { + return fmt.Errorf("failed to get header in subchain: %w", err) + } + + // TODO: batch this + err = bs.db.Put(headerHashKey(header.Number.Uint64()), hash.ToBytes()) + if err != nil { + return err + } + } + + return nil +} + // AddBlockToBlockTree adds the given block to the blocktree. It does not write it to the database. func (bs *BlockState) AddBlockToBlockTree(header *types.Header) error { + bs.Lock() + defer bs.Unlock() + arrivalTime, err := bs.GetArrivalTime(header.Hash()) if err != nil { arrivalTime = time.Now() @@ -567,7 +617,7 @@ func (bs *BlockState) isBlockOnCurrentChain(header *types.Header) (bool, error) } // if the new block is ahead of our best block, then it is on our current chain. - if header.Number.Cmp(bestBlock.Number) == 1 { + if header.Number.Cmp(bestBlock.Number) > 0 { return true, nil } diff --git a/dot/state/block_data.go b/dot/state/block_data.go index 67cccd8087..90987d07ff 100644 --- a/dot/state/block_data.go +++ b/dot/state/block_data.go @@ -32,8 +32,8 @@ func (bs *BlockState) HasReceipt(hash common.Hash) (bool, error) { // SetReceipt sets a Receipt in the database func (bs *BlockState) SetReceipt(hash common.Hash, data []byte) error { - bs.lock.Lock() - defer bs.lock.Unlock() + bs.Lock() + defer bs.Unlock() err := bs.db.Put(prefixKey(hash, receiptPrefix), data) if err != nil { @@ -60,8 +60,8 @@ func (bs *BlockState) HasMessageQueue(hash common.Hash) (bool, error) { // SetMessageQueue sets a MessageQueue in the database func (bs *BlockState) SetMessageQueue(hash common.Hash, data []byte) error { - bs.lock.Lock() - defer bs.lock.Unlock() + bs.Lock() + defer bs.Unlock() err := bs.db.Put(prefixKey(hash, messageQueuePrefix), data) if err != nil { @@ -88,8 +88,8 @@ func (bs *BlockState) HasJustification(hash common.Hash) (bool, error) { // SetJustification sets a Justification in the database func (bs *BlockState) SetJustification(hash common.Hash, data []byte) error { - bs.lock.Lock() - defer bs.lock.Unlock() + bs.Lock() + defer bs.Unlock() err := bs.db.Put(prefixKey(hash, justificationPrefix), data) if err != nil { diff --git a/dot/state/block_test.go b/dot/state/block_test.go index 0a54db54a4..31641b8559 100644 --- a/dot/state/block_test.go +++ b/dot/state/block_test.go @@ -391,3 +391,111 @@ func TestGetHashByNumber(t *testing.T) { require.NoError(t, err) require.Equal(t, header.Hash(), res) } + +func TestAddBlock_WithReOrg(t *testing.T) { + bs := newTestBlockState(t, testGenesisHeader) + + header1a := &types.Header{ + Number: big.NewInt(1), + Digest: types.Digest{}, + ParentHash: testGenesisHeader.Hash(), + } + + block1a := &types.Block{ + Header: header1a, + Body: &types.Body{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + } + + err := bs.AddBlock(block1a) + require.NoError(t, err) + + block1hash, err := bs.GetHashByNumber(big.NewInt(1)) + require.NoError(t, err) + require.Equal(t, header1a.Hash(), block1hash) + + header1b := &types.Header{ + Number: big.NewInt(1), + Digest: types.Digest{}, + ParentHash: testGenesisHeader.Hash(), + ExtrinsicsRoot: common.Hash{99}, + } + + block1b := &types.Block{ + Header: header1b, + Body: &types.Body{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + } + + err = bs.AddBlock(block1b) + require.NoError(t, err) + + // should still be hash 1a since it arrived first + block1hash, err = bs.GetHashByNumber(big.NewInt(1)) + require.NoError(t, err) + require.Equal(t, header1a.Hash(), block1hash) + + header2b := &types.Header{ + Number: big.NewInt(2), + Digest: types.Digest{}, + ParentHash: header1b.Hash(), + ExtrinsicsRoot: common.Hash{99}, + } + + block2b := &types.Block{ + Header: header2b, + Body: &types.Body{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + } + + err = bs.AddBlock(block2b) + require.NoError(t, err) + + // should now be hash 1b since it's on the longer chain + block1hash, err = bs.GetHashByNumber(big.NewInt(1)) + require.NoError(t, err) + require.Equal(t, header1b.Hash(), block1hash) + + block2hash, err := bs.GetHashByNumber(big.NewInt(2)) + require.NoError(t, err) + require.Equal(t, header2b.Hash(), block2hash) + + header2a := &types.Header{ + Number: big.NewInt(2), + Digest: types.Digest{}, + ParentHash: header1a.Hash(), + } + + block2a := &types.Block{ + Header: header2a, + Body: &types.Body{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + } + + err = bs.AddBlock(block2a) + require.NoError(t, err) + + header3a := &types.Header{ + Number: big.NewInt(3), + Digest: types.Digest{}, + ParentHash: header2a.Hash(), + } + + block3a := &types.Block{ + Header: header3a, + Body: &types.Body{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + } + + err = bs.AddBlock(block3a) + require.NoError(t, err) + + // should now be hash 1a since it's on the longer chain + block1hash, err = bs.GetHashByNumber(big.NewInt(1)) + require.NoError(t, err) + require.Equal(t, header1a.Hash(), block1hash) + + // should now be hash 2a since it's on the longer chain + block2hash, err = bs.GetHashByNumber(big.NewInt(2)) + require.NoError(t, err) + require.Equal(t, header2a.Hash(), block2hash) + + block3hash, err := bs.GetHashByNumber(big.NewInt(3)) + require.NoError(t, err) + require.Equal(t, header3a.Hash(), block3hash) +} diff --git a/lib/grandpa/message_handler.go b/lib/grandpa/message_handler.go index 8ef9c7773c..514fb8d48f 100644 --- a/lib/grandpa/message_handler.go +++ b/lib/grandpa/message_handler.go @@ -103,11 +103,14 @@ func (h *MessageHandler) handleNeighbourMessage(msg *NeighbourMessage) error { return err } - if uint32(head.Int64()) < msg.Number { + // add small -2 delay, until we add justification request functionality. + // this prevents us from marking the wrong block as final + if uint32(head.Int64())-2 < msg.Number { return nil } - // TODO: is there another way to confirm the hash? + // TODO: instead of assuming the finalized hash is the one we currently know about, + // request the justification from the network before setting it as finalized. hash, err := h.grandpa.blockState.GetHashByNumber(big.NewInt(int64(msg.Number))) if err != nil { return err From 7622914bc03285143bbedeb8896cfdec4a2728e3 Mon Sep 17 00:00:00 2001 From: noot Date: Tue, 13 Apr 2021 18:36:10 -0400 Subject: [PATCH 25/40] add grandpa msg handler tests --- dot/network/sync.go | 23 ++++++------ dot/state/block.go | 17 ++++----- lib/grandpa/message_handler.go | 10 +++--- lib/grandpa/message_handler_test.go | 56 +++++++++++++++++++++++++++++ lib/grandpa/message_test.go | 18 ++++++++++ 5 files changed, 98 insertions(+), 26 deletions(-) diff --git a/dot/network/sync.go b/dot/network/sync.go index 65107e3fe7..ef52b5ce1f 100644 --- a/dot/network/sync.go +++ b/dot/network/sync.go @@ -693,16 +693,9 @@ func (q *syncQueue) handleBlockJustification(data []*types.BlockData) { func (q *syncQueue) handleBlockData(data []*types.BlockData) { end := data[len(data)-1].Number().Int64() - var ( - finalized *types.Header - err error - ) - - for { - finalized, err = q.s.blockState.GetFinalizedHeader(0, 0) - if err == nil { - break // TODO: seems blockState needs better concurrency handling - } + finalized, err := q.s.blockState.GetFinalizedHeader(0, 0) + if err != nil { + panic(err) // this should never happen } if end <= finalized.Number.Int64() { @@ -747,12 +740,22 @@ func (q *syncQueue) handleBlockDataFailure(idx int, err error, data []*types.Blo logger.Warn("failed to handle block data", "failed on block", q.currStart+int64(idx), "error", err) if errors.Is(err, chaindb.ErrKeyNotFound) || errors.Is(err, blocktree.ErrParentNotFound) { + finalized, err := q.s.blockState.GetFinalizedHeader(0, 0) + if err != nil { + panic(err) + } + header, err := types.NewHeaderFromOptional(data[idx].Header) if err != nil { logger.Debug("failed to get header from BlockData", "idx", idx, "error", err) return } + // don't request a chain that's been dropped + if header.Number.Int64() <= finalized.Number.Int64() { + return + } + parentHash := header.ParentHash req := createBlockRequestWithHash(parentHash, 0) diff --git a/dot/state/block.go b/dot/state/block.go index 20cd45d6ac..421fe625f6 100644 --- a/dot/state/block.go +++ b/dot/state/block.go @@ -268,7 +268,7 @@ func (bs *BlockState) GetHeader(hash common.Hash) (*types.Header, error) { func (bs *BlockState) GetHashByNumber(num *big.Int) (common.Hash, error) { bh, err := bs.db.Get(headerHashKey(num.Uint64())) if err != nil { - return common.Hash{}, fmt.Errorf("cannot get block %d: %s", num, err) + return common.Hash{}, fmt.Errorf("cannot get block %d: %w", num, err) } return common.NewHash(bh), nil @@ -278,7 +278,7 @@ func (bs *BlockState) GetHashByNumber(num *big.Int) (common.Hash, error) { func (bs *BlockState) GetHeaderByNumber(num *big.Int) (*types.Header, error) { bh, err := bs.db.Get(headerHashKey(num.Uint64())) if err != nil { - return nil, fmt.Errorf("cannot get block %d: %s", num, err) + return nil, fmt.Errorf("cannot get block %d: %w", num, err) } hash := common.NewHash(bh) @@ -304,7 +304,7 @@ func (bs *BlockState) GetBlockByNumber(num *big.Int) (*types.Block, error) { // First retrieve the block hash in a byte array based on the block number from the database byteHash, err := bs.db.Get(headerHashKey(num.Uint64())) if err != nil { - return nil, fmt.Errorf("cannot get block %d: %s", num, err) + return nil, fmt.Errorf("cannot get block %d: %w", num, err) } // Then find the block based on the hash @@ -322,7 +322,7 @@ func (bs *BlockState) GetBlockHash(blockNumber *big.Int) (*common.Hash, error) { // First retrieve the block hash in a byte array based on the block number from the database byteHash, err := bs.db.Get(headerHashKey(blockNumber.Uint64())) if err != nil { - return nil, fmt.Errorf("cannot get block %d: %s", blockNumber, err) + return nil, fmt.Errorf("cannot get block %d: %w", blockNumber, err) } hash := common.NewHash(byteHash) return &hash, nil @@ -555,15 +555,11 @@ func (bs *BlockState) AddBlockWithArrivalTime(block *types.Block, arrivalTime ti // prev is the previous best block hash before the new block was added to the blocktree. // curr is the current best blogetck hash. func (bs *BlockState) handleAddedBlock(prev, curr common.Hash) error { - logger.Info("handling added block", "prev head", prev, "curr head", curr) - ancestor, err := bs.HighestCommonAncestor(prev, curr) if err != nil { return err } - logger.Info("handling added block", "ancestor", ancestor) - // if the highest common ancestor of the previous chain head and current chain head is the previous chain head, // then the current chain head is the descendant of the previous and thus are on the same chain if ancestor == prev { @@ -575,6 +571,7 @@ func (bs *BlockState) handleAddedBlock(prev, curr common.Hash) error { return err } + batch := bs.db.NewBatch() for _, hash := range subchain { // TODO: set number from ancestor.Number + i ? header, err := bs.GetHeader(hash) @@ -582,13 +579,13 @@ func (bs *BlockState) handleAddedBlock(prev, curr common.Hash) error { return fmt.Errorf("failed to get header in subchain: %w", err) } - // TODO: batch this - err = bs.db.Put(headerHashKey(header.Number.Uint64()), hash.ToBytes()) + err = batch.Put(headerHashKey(header.Number.Uint64()), hash.ToBytes()) if err != nil { return err } } + batch.Flush() return nil } diff --git a/lib/grandpa/message_handler.go b/lib/grandpa/message_handler.go index 514fb8d48f..bc65f79d7d 100644 --- a/lib/grandpa/message_handler.go +++ b/lib/grandpa/message_handler.go @@ -103,9 +103,9 @@ func (h *MessageHandler) handleNeighbourMessage(msg *NeighbourMessage) error { return err } - // add small -2 delay, until we add justification request functionality. + // add -3 delay, until we add justification request functionality. // this prevents us from marking the wrong block as final - if uint32(head.Int64())-2 < msg.Number { + if uint32(head.Int64())-3 < msg.Number { return nil } @@ -116,13 +116,11 @@ func (h *MessageHandler) handleNeighbourMessage(msg *NeighbourMessage) error { return err } - err = h.grandpa.blockState.SetFinalizedHash(hash, msg.Round, msg.SetID) - if err != nil { + if err = h.grandpa.blockState.SetFinalizedHash(hash, msg.Round, msg.SetID); err != nil { return err } - err = h.grandpa.blockState.SetFinalizedHash(hash, 0, 0) - if err != nil { + if err = h.grandpa.blockState.SetFinalizedHash(hash, 0, 0); err != nil { return err } diff --git a/lib/grandpa/message_handler_test.go b/lib/grandpa/message_handler_test.go index 6e091e0db1..bc24be8e0e 100644 --- a/lib/grandpa/message_handler_test.go +++ b/lib/grandpa/message_handler_test.go @@ -17,6 +17,7 @@ package grandpa import ( + "errors" "math/big" "testing" "time" @@ -28,6 +29,7 @@ import ( "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/lib/scale" + "github.com/ChainSafe/chaindb" "github.com/stretchr/testify/require" ) @@ -122,6 +124,23 @@ func TestDecodeMessage_FinalizationMessage(t *testing.T) { require.Equal(t, expected, msg) } +func TestDecodeMessage_NeighbourMessage(t *testing.T) { + cm := &ConsensusMessage{ + Data: common.MustHexToBytes("0x020102000000000000000300000000000000ff000000"), + } + + msg, err := decodeMessage(cm) + require.NoError(t, err) + + expected := &NeighbourMessage{ + Version: 1, + Round: 2, + SetID: 3, + Number: 255, + } + require.Equal(t, expected, msg) +} + func TestDecodeMessage_CatchUpRequest(t *testing.T) { cm := &ConsensusMessage{ Data: common.MustHexToBytes("0x0311000000000000002200000000000000"), @@ -166,6 +185,43 @@ func TestMessageHandler_VoteMessage(t *testing.T) { } } +func TestMessageHandler_NeighbourMessage(t *testing.T) { + gs, st := newTestService(t) + h := NewMessageHandler(gs, st.Block) + + msg := &NeighbourMessage{ + Version: 1, + Round: 2, + SetID: 3, + Number: 1, + } + + cm, err := msg.ToConsensusMessage() + require.NoError(t, err) + + _, err = h.handleMessage(cm) + require.True(t, errors.Is(err, chaindb.ErrKeyNotFound)) + + block := &types.Block{ + Header: &types.Header{ + Number: big.NewInt(1), + ParentHash: st.Block.GenesisHash(), + }, + Body: &types.Body{0}, + } + + err = st.Block.AddBlock(block) + require.NoError(t, err) + + out, err := h.handleMessage(cm) + require.NoError(t, err) + require.Nil(t, out) + + finalized, err := st.Block.GetFinalizedHash(0, 0) + require.NoError(t, err) + require.Equal(t, block.Header.Hash(), finalized) +} + func TestMessageHandler_VerifyJustification_InvalidSig(t *testing.T) { gs, st := newTestService(t) gs.state.round = 77 diff --git a/lib/grandpa/message_test.go b/lib/grandpa/message_test.go index 74e758a941..514c0db3be 100644 --- a/lib/grandpa/message_test.go +++ b/lib/grandpa/message_test.go @@ -142,3 +142,21 @@ func TestNewCatchUpResponse(t *testing.T) { require.Equal(t, expected, resp) } + +func TestNeighbourMessageToConsensusMessage(t *testing.T) { + msg := &NeighbourMessage{ + Version: 1, + Round: 2, + SetID: 3, + Number: 255, + } + + cm, err := msg.ToConsensusMessage() + require.NoError(t, err) + + expected := &ConsensusMessage{ + Data: common.MustHexToBytes("0x020102000000000000000300000000000000ff000000"), + } + + require.Equal(t, expected, cm) +} From 6d108d879d0b232584162aa19492ae279564fe3c Mon Sep 17 00:00:00 2001 From: noot Date: Tue, 13 Apr 2021 18:36:54 -0400 Subject: [PATCH 26/40] lint --- dot/state/block.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dot/state/block.go b/dot/state/block.go index 421fe625f6..f83bc5e91c 100644 --- a/dot/state/block.go +++ b/dot/state/block.go @@ -584,9 +584,8 @@ func (bs *BlockState) handleAddedBlock(prev, curr common.Hash) error { return err } } - - batch.Flush() - return nil + + return batch.Flush() } // AddBlockToBlockTree adds the given block to the blocktree. It does not write it to the database. From 8a7a1450ae6e1dea517f32a5ad345af7e0927e77 Mon Sep 17 00:00:00 2001 From: noot Date: Tue, 13 Apr 2021 18:37:35 -0400 Subject: [PATCH 27/40] lint --- dot/state/block.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dot/state/block.go b/dot/state/block.go index f83bc5e91c..640906386d 100644 --- a/dot/state/block.go +++ b/dot/state/block.go @@ -584,7 +584,7 @@ func (bs *BlockState) handleAddedBlock(prev, curr common.Hash) error { return err } } - + return batch.Flush() } From 6d45402a327bcc9cc64296ed01daf5dfb48d8958 Mon Sep 17 00:00:00 2001 From: noot Date: Tue, 13 Apr 2021 21:02:34 -0400 Subject: [PATCH 28/40] add test --- dot/state/block_test.go | 18 ++++++++++++++++++ lib/grandpa/message_handler.go | 6 +++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/dot/state/block_test.go b/dot/state/block_test.go index 31641b8559..693dd95a42 100644 --- a/dot/state/block_test.go +++ b/dot/state/block_test.go @@ -19,6 +19,7 @@ package state import ( "math/big" "testing" + "time" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" @@ -499,3 +500,20 @@ func TestAddBlock_WithReOrg(t *testing.T) { require.NoError(t, err) require.Equal(t, header3a.Hash(), block3hash) } + +func TestAddBlockToBlockTree(t *testing.T) { + bs := newTestBlockState(t, testGenesisHeader) + + header := &types.Header{ + Number: big.NewInt(1), + Digest: types.Digest{}, + ParentHash: testGenesisHeader.Hash(), + } + + err := bs.setArrivalTime(header.Hash(), time.Now()) + require.NoError(t, err) + + err = bs.AddBlockToBlockTree(header) + require.NoError(t, err) + require.Equal(t, bs.BestBlockHash(), header.Hash()) +} diff --git a/lib/grandpa/message_handler.go b/lib/grandpa/message_handler.go index bc65f79d7d..9fac06081e 100644 --- a/lib/grandpa/message_handler.go +++ b/lib/grandpa/message_handler.go @@ -103,9 +103,9 @@ func (h *MessageHandler) handleNeighbourMessage(msg *NeighbourMessage) error { return err } - // add -3 delay, until we add justification request functionality. - // this prevents us from marking the wrong block as final - if uint32(head.Int64())-3 < msg.Number { + // don't finalize too close to head, until we add justification request + verification functionality. + // this prevents us from marking the wrong block as final and getting stuck on the wrong chain + if uint32(head.Int64())-4 < msg.Number { return nil } From 362cb55dc11b0ef8782b2f4e0e78644f4af13b8b Mon Sep 17 00:00:00 2001 From: noot Date: Wed, 14 Apr 2021 19:13:27 -0400 Subject: [PATCH 29/40] update logs --- dot/network/host.go | 2 +- dot/network/sync.go | 1 + dot/node.go | 2 +- dot/state/service.go | 5 +++++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/dot/network/host.go b/dot/network/host.go index 12c4a386ba..89728ff907 100644 --- a/dot/network/host.go +++ b/dot/network/host.go @@ -257,7 +257,7 @@ func (h *host) bootstrap() { failed++ } } - if failed == len(all) { + if failed == len(all) && len(all) != 0 { logger.Error("failed to bootstrap to any bootnode") } } diff --git a/dot/network/sync.go b/dot/network/sync.go index fcea04e37d..1434913c16 100644 --- a/dot/network/sync.go +++ b/dot/network/sync.go @@ -792,6 +792,7 @@ func (q *syncQueue) handleBlockAnnounceHandshake(blockNum uint32, from peer.ID) func (q *syncQueue) handleBlockAnnounce(msg *BlockAnnounceMessage, from peer.ID) { q.updatePeerScore(from, 1) + logger.Info("received BlockAnnounce", "message", msg, "from", from) header, err := types.NewHeader(msg.ParentHash, msg.StateRoot, msg.ExtrinsicsRoot, msg.Number, msg.Digest) if err != nil { diff --git a/dot/node.go b/dot/node.go index cc11ef1330..3f87fe1aa5 100644 --- a/dot/node.go +++ b/dot/node.go @@ -125,7 +125,7 @@ func NodeInitialized(basepath string, expected bool) bool { _, err := os.Stat(registry) if os.IsNotExist(err) { if expected { - logger.Warn( + logger.Debug( "node has not been initialized", "basepath", basepath, "error", "failed to locate KEYREGISTRY file in data directory", diff --git a/dot/state/service.go b/dot/state/service.go index 6fb9ebdc2c..67839c740d 100644 --- a/dot/state/service.go +++ b/dot/state/service.go @@ -350,6 +350,11 @@ func (s *Service) Rewind(toBlock int64) error { return err } + err = s.Block.SetFinalizedHash(header.Hash(), 0, 0) + if err != nil { + return err + } + return StoreBestBlockHash(s.db, newHead) } From 754bda638f0b022e81c4d3647ada2711ae88ba8d Mon Sep 17 00:00:00 2001 From: noot Date: Thu, 15 Apr 2021 16:25:12 -0400 Subject: [PATCH 30/40] add justification handling logic, create request on neighbour message receipt --- dot/network/sync_justification.go | 106 ++++++++++++++++-------------- dot/node.go | 12 ++-- dot/services.go | 3 +- dot/sync/interface.go | 4 ++ dot/sync/syncer.go | 30 ++++++++- lib/grandpa/message_handler.go | 84 ++++++++++++++--------- lib/grandpa/network.go | 12 ++-- lib/grandpa/state.go | 2 + lib/grandpa/types.go | 12 ++++ 9 files changed, 172 insertions(+), 93 deletions(-) diff --git a/dot/network/sync_justification.go b/dot/network/sync_justification.go index e0897d0d10..59ed4ea694 100644 --- a/dot/network/sync_justification.go +++ b/dot/network/sync_justification.go @@ -18,59 +18,65 @@ package network import ( "math/big" - "time" -) + //"time" -func (q *syncQueue) finalizeAtHead() { - prev, err := q.s.blockState.GetFinalizedHeader(0, 0) - if err != nil { - logger.Error("failed to get latest finalized block header", "error", err) - return - } + "github.com/libp2p/go-libp2p-core/peer" +) - for { - select { - // sleep for average block time TODO: make this configurable from slot duration - case <-time.After(q.slotDuration * 2): - case <-q.ctx.Done(): - return - } - - head, err := q.s.blockState.BestBlockNumber() - if err != nil { - continue - } - - if head.Int64() < q.goal { - continue - } - - curr, err := q.s.blockState.GetFinalizedHeader(0, 0) - if err != nil { - continue - } - - logger.Debug("checking finalized blocks", "curr", curr.Number, "prev", prev.Number) - - if curr.Number.Cmp(prev.Number) > 0 { - prev = curr - continue - } - - prev = curr - - start := head.Uint64() - uint64(blockRequestSize) - if curr.Number.Uint64() > start { - start = curr.Number.Uint64() + 1 - } else if int(start) < int(blockRequestSize) { - start = 1 - } - - q.pushJustificationRequest(start) - } +// func (q *syncQueue) finalizeAtHead() { +// prev, err := q.s.blockState.GetFinalizedHeader(0, 0) +// if err != nil { +// logger.Error("failed to get latest finalized block header", "error", err) +// return +// } + +// for { +// select { +// // sleep for average block time TODO: make this configurable from slot duration +// case <-time.After(q.slotDuration * 2): +// case <-q.ctx.Done(): +// return +// } + +// head, err := q.s.blockState.BestBlockNumber() +// if err != nil { +// continue +// } + +// if head.Int64() < q.goal { +// continue +// } + +// curr, err := q.s.blockState.GetFinalizedHeader(0, 0) +// if err != nil { +// continue +// } + +// logger.Debug("checking finalized blocks", "curr", curr.Number, "prev", prev.Number) + +// if curr.Number.Cmp(prev.Number) > 0 { +// prev = curr +// continue +// } + +// prev = curr + +// start := head.Uint64() - uint64(blockRequestSize) +// if curr.Number.Uint64() > start { +// start = curr.Number.Uint64() + 1 +// } else if int(start) < int(blockRequestSize) { +// start = 1 +// } + +// q.pushJustificationRequest(start) +// } +// } + +func (s *Service) SendJustificationRequest(to peer.ID, num uint32) { + s.syncQueue.pushJustificationRequest(to, uint64(num)) } -func (q *syncQueue) pushJustificationRequest(start uint64) { +func (q *syncQueue) pushJustificationRequest(to peer.ID, start uint64) { startHash, err := q.s.blockState.GetHashByNumber(big.NewInt(int64(start))) if err != nil { logger.Error("failed to get hash for block w/ number", "number", start, "error", err) @@ -87,6 +93,6 @@ func (q *syncQueue) pushJustificationRequest(start uint64) { q.requestCh <- &syncRequest{ req: req, - to: "", + to: to, } } diff --git a/dot/node.go b/dot/node.go index 3f87fe1aa5..8038119e77 100644 --- a/dot/node.go +++ b/dot/node.go @@ -242,12 +242,6 @@ func NewNode(cfg *Config, ks *keystore.GlobalKeystore, stopFunc func()) (*Node, return nil, err } - // Syncer - syncer, err := createSyncService(cfg, stateSrvc, bp, dh, ver, rt) - if err != nil { - return nil, err - } - // create GRANDPA service fg, err := createGRANDPAService(cfg, rt, stateSrvc, dh, ks.Gran, networkSrvc) if err != nil { @@ -256,6 +250,12 @@ func NewNode(cfg *Config, ks *keystore.GlobalKeystore, stopFunc func()) (*Node, nodeSrvcs = append(nodeSrvcs, fg) dh.SetFinalityGadget(fg) // TODO: this should be cleaned up + // Syncer + syncer, err := createSyncService(cfg, stateSrvc, bp, fg, dh, ver, rt) + if err != nil { + return nil, err + } + // Core Service // create core service and append core service to node services diff --git a/dot/services.go b/dot/services.go index 0f5d329dbc..e523b88d5d 100644 --- a/dot/services.go +++ b/dot/services.go @@ -381,13 +381,14 @@ func createBlockVerifier(st *state.Service) (*babe.VerificationManager, error) { return ver, nil } -func createSyncService(cfg *Config, st *state.Service, bp sync.BlockProducer, dh *core.DigestHandler, verifier *babe.VerificationManager, rt runtime.Instance) (*sync.Service, error) { +func createSyncService(cfg *Config, st *state.Service, bp sync.BlockProducer, fg sync.FinalityGadget, dh *core.DigestHandler, verifier *babe.VerificationManager, rt runtime.Instance) (*sync.Service, error) { syncCfg := &sync.Config{ LogLvl: cfg.Log.SyncLvl, BlockState: st.Block, StorageState: st.Storage, TransactionState: st.Transaction, BlockProducer: bp, + FinalityGadget: fg, Verifier: verifier, Runtime: rt, DigestHandler: dh, diff --git a/dot/sync/interface.go b/dot/sync/interface.go index 1a457091e2..e7724e7801 100644 --- a/dot/sync/interface.go +++ b/dot/sync/interface.go @@ -78,3 +78,7 @@ type DigestHandler interface { type Verifier interface { VerifyBlock(header *types.Header) error } + +type FinalityGadget interface { + VerifyBlockJustification([]byte, *big.Int) error +} diff --git a/dot/sync/syncer.go b/dot/sync/syncer.go index 088da86ae5..b2274edba9 100644 --- a/dot/sync/syncer.go +++ b/dot/sync/syncer.go @@ -44,6 +44,7 @@ type Service struct { storageState StorageState transactionState TransactionState blockProducer BlockProducer + finalityGadget FinalityGadget // Synchronization variables synced bool @@ -63,6 +64,7 @@ type Config struct { BlockState BlockState StorageState StorageState BlockProducer BlockProducer + FinalityGadget FinalityGadget TransactionState TransactionState Runtime runtime.Instance Verifier Verifier @@ -105,6 +107,7 @@ func NewService(cfg *Config) (*Service, error) { blockState: cfg.BlockState, storageState: cfg.StorageState, blockProducer: cfg.BlockProducer, + finalityGadget: cfg.FinalityGadget, synced: true, highestSeenBlock: big.NewInt(0), transactionState: cfg.TransactionState, @@ -149,6 +152,26 @@ func (s *Service) HandleBlockAnnounce(msg *network.BlockAnnounceMessage) error { return nil } +func (s *Service) ProcessJustification(data []*types.BlockData) (int, error) { + if len(data) == 0 { + return 0, ErrNilBlockData + } + + for i, bd := range data { + header, err := s.blockState.GetHeader(bd.Hash) + if err != nil { + return i, err + } + + if bd.Justification != nil && bd.Justification.Exists() { + logger.Info("handling Justification...", "number", header.Number, "hash", bd.Hash) + s.handleJustification(header, bd.Justification.Value()) + } + } + + return 0, nil +} + // ProcessBlockData processes the BlockData from a BlockResponse and returns the index of the last BlockData it handled on success, // or the index of the block data that errored on failure. func (s *Service) ProcessBlockData(data []*types.BlockData) (int, error) { @@ -351,7 +374,12 @@ func (s *Service) handleJustification(header *types.Header, justification []byte return } - err := s.blockState.SetFinalizedHash(header.Hash(), 0, 0) + err := s.finalityGadget.VerifyBlockJustification(justification, header.Number) + if err != nil { + logger.Warn("failed to verify block justification", "hash", header.Hash(), "number", header.Number, "error", err) + } + + err = s.blockState.SetFinalizedHash(header.Hash(), 0, 0) if err != nil { logger.Error("failed to set finalized hash", "error", err) return diff --git a/lib/grandpa/message_handler.go b/lib/grandpa/message_handler.go index 9fac06081e..6fefd8a964 100644 --- a/lib/grandpa/message_handler.go +++ b/lib/grandpa/message_handler.go @@ -17,12 +17,17 @@ package grandpa import ( + "bytes" + "fmt" "math/big" "reflect" + "github.com/ChainSafe/gossamer/dot/network" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto/ed25519" "github.com/ChainSafe/gossamer/lib/scale" + + "github.com/libp2p/go-libp2p-core/peer" ) // MessageHandler handles GRANDPA consensus messages @@ -42,7 +47,7 @@ func NewMessageHandler(grandpa *Service, blockState BlockState) *MessageHandler // HandleMessage handles a GRANDPA consensus message // if it is a FinalizationMessage, it updates the BlockState // if it is a VoteMessage, it sends it to the GRANDPA service -func (h *MessageHandler) handleMessage(msg *ConsensusMessage) (*ConsensusMessage, error) { +func (h *MessageHandler) handleMessage(from peer.ID, msg *ConsensusMessage) (network.Message, error) { if msg == nil || len(msg.Data) == 0 { logger.Trace("received nil message or message with nil data") return nil, nil @@ -72,7 +77,7 @@ func (h *MessageHandler) handleMessage(msg *ConsensusMessage) (*ConsensusMessage return nil, nil } - return nil, h.handleNeighbourMessage(nm) + return nil, h.handleNeighbourMessage(from, nm) case catchUpRequestType: if r, ok := m.(*catchUpRequest); ok { return h.handleCatchUpRequest(r) @@ -88,7 +93,7 @@ func (h *MessageHandler) handleMessage(msg *ConsensusMessage) (*ConsensusMessage return nil, nil } -func (h *MessageHandler) handleNeighbourMessage(msg *NeighbourMessage) error { +func (h *MessageHandler) handleNeighbourMessage(from peer.ID, msg *NeighbourMessage) error { currFinalized, err := h.grandpa.blockState.GetFinalizedHeader(0, 0) if err != nil { return err @@ -98,33 +103,37 @@ func (h *MessageHandler) handleNeighbourMessage(msg *NeighbourMessage) error { return nil } - head, err := h.grandpa.blockState.BestBlockNumber() - if err != nil { - return err - } + // TODO: handle setID, round as well; track number->(setID, round) ? - // don't finalize too close to head, until we add justification request + verification functionality. - // this prevents us from marking the wrong block as final and getting stuck on the wrong chain - if uint32(head.Int64())-4 < msg.Number { - return nil - } + h.grandpa.network.SendJustificationRequest(from, msg.Number) - // TODO: instead of assuming the finalized hash is the one we currently know about, - // request the justification from the network before setting it as finalized. - hash, err := h.grandpa.blockState.GetHashByNumber(big.NewInt(int64(msg.Number))) - if err != nil { - return err - } + // head, err := h.grandpa.blockState.BestBlockNumber() + // if err != nil { + // return err + // } - if err = h.grandpa.blockState.SetFinalizedHash(hash, msg.Round, msg.SetID); err != nil { - return err - } + // // don't finalize too close to head, until we add justification request + verification functionality. + // // this prevents us from marking the wrong block as final and getting stuck on the wrong chain + // if uint32(head.Int64())-4 < msg.Number { + // return nil + // } - if err = h.grandpa.blockState.SetFinalizedHash(hash, 0, 0); err != nil { - return err - } + // // TODO: instead of assuming the finalized hash is the one we currently know about, + // // request the justification from the network before setting it as finalized. + // hash, err := h.grandpa.blockState.GetHashByNumber(big.NewInt(int64(msg.Number))) + // if err != nil { + // return err + // } + + // if err = h.grandpa.blockState.SetFinalizedHash(hash, msg.Round, msg.SetID); err != nil { + // return err + // } + + // if err = h.grandpa.blockState.SetFinalizedHash(hash, 0, 0); err != nil { + // return err + // } - logger.Info("🔨 finalized block", "number", msg.Number, "hash", hash) + // logger.Info("🔨 finalized block", "number", msg.Number, "hash", hash) return nil } @@ -300,7 +309,7 @@ func (h *MessageHandler) verifyFinalizationMessageJustification(fm *Finalization // verify justifications count := 0 for _, just := range fm.Justification { - err := h.verifyJustification(just, just.Vote, fm.Round, h.grandpa.state.setID, precommit) + err := h.verifyJustification(just, fm.Round, h.grandpa.state.setID, precommit) if err != nil { continue } @@ -324,7 +333,7 @@ func (h *MessageHandler) verifyPreVoteJustification(msg *catchUpResponse) (commo votes := make(map[common.Hash]uint64) for _, just := range msg.PreVoteJustification { - err := h.verifyJustification(just, just.Vote, msg.Round, msg.SetID, prevote) + err := h.verifyJustification(just, msg.Round, msg.SetID, prevote) if err != nil { continue } @@ -351,7 +360,7 @@ func (h *MessageHandler) verifyPreCommitJustification(msg *catchUpResponse) erro // verify pre-commit justification count := 0 for _, just := range msg.PreCommitJustification { - err := h.verifyJustification(just, just.Vote, msg.Round, msg.SetID, precommit) + err := h.verifyJustification(just, msg.Round, msg.SetID, precommit) if err != nil { continue } @@ -368,11 +377,11 @@ func (h *MessageHandler) verifyPreCommitJustification(msg *catchUpResponse) erro return nil } -func (h *MessageHandler) verifyJustification(just *Justification, vote *Vote, round, setID uint64, stage subround) error { +func (h *MessageHandler) verifyJustification(just *Justification, round, setID uint64, stage subround) error { // verify signature msg, err := scale.Encode(&FullVote{ Stage: stage, - Vote: vote, + Vote: just.Vote, Round: round, SetID: setID, }) @@ -411,3 +420,18 @@ func (h *MessageHandler) verifyJustification(just *Justification, vote *Vote, ro } return nil } + +func (s *Service) VerifyBlockJustification(justification []byte, number *big.Int) error { + logger.Info("verifying block justification", "justification", fmt.Sprintf("0x%x", justification), "len", len(justification), "number", number) + + r := &bytes.Buffer{} + _, _ = r.Write(justification) + fj, err := DecodeFullJustification(r) + if err != nil { + return err + } + + logger.Info("full justification", "fj", fj) + + return nil +} diff --git a/lib/grandpa/network.go b/lib/grandpa/network.go index f596282b5f..f3c449393a 100644 --- a/lib/grandpa/network.go +++ b/lib/grandpa/network.go @@ -124,20 +124,22 @@ func (s *Service) decodeMessage(in []byte) (NotificationsMessage, error) { return msg, err } -func (s *Service) handleNetworkMessage(_ peer.ID, msg NotificationsMessage) error { +func (s *Service) handleNetworkMessage(from peer.ID, msg NotificationsMessage) error { cm, ok := msg.(*network.ConsensusMessage) if !ok { return ErrInvalidMessageType } - resp, err := s.messageHandler.handleMessage(cm) + _, err := s.messageHandler.handleMessage(from, cm) if err != nil { return err } - if resp != nil { - s.network.SendMessage(resp) - } + // // this is a bit convoluted, might be nice to just call this in the neighbour message handler + // if msg, ok := resp.(*network.BlockRequestMessage); ok { + // //s.network.SendMessage(resp) + // s.network.SendJustificationRequest(from, uint32(msg.StartingBlock.Uint64())) + // } return nil } diff --git a/lib/grandpa/state.go b/lib/grandpa/state.go index e4d830d5a6..32e8745dca 100644 --- a/lib/grandpa/state.go +++ b/lib/grandpa/state.go @@ -23,6 +23,7 @@ import ( "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" + "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/protocol" ) @@ -60,6 +61,7 @@ type DigestHandler interface { // Network is the interface required by GRANDPA for the network type Network interface { SendMessage(msg network.NotificationsMessage) + SendJustificationRequest(to peer.ID, num uint32) RegisterNotificationsProtocol(sub protocol.ID, messageID byte, handshakeGetter network.HandshakeGetter, diff --git a/lib/grandpa/types.go b/lib/grandpa/types.go index 7466a82885..b634355485 100644 --- a/lib/grandpa/types.go +++ b/lib/grandpa/types.go @@ -269,6 +269,18 @@ func (j FullJustification) Decode(r io.Reader) (FullJustification, error) { return FullJustification{}, err } + // TODO: this assignment doesn't work, update type j = FullJustification(i.([]*Justification)) return j, nil } + +// DecodeFullJustification returns a SCALE decoded FullJustification +func DecodeFullJustification(r io.Reader) (FullJustification, error) { + sd := &scale.Decoder{Reader: r} + i, err := sd.Decode([]*Justification{}) + if err != nil { + return FullJustification{}, err + } + + return FullJustification(i.([]*Justification)), nil +} From 9f5f2da361cb1a0ee489a7067a16223e75fdc16f Mon Sep 17 00:00:00 2001 From: noot Date: Thu, 15 Apr 2021 17:19:24 -0400 Subject: [PATCH 31/40] update Justification type to have correct format --- dot/sync/syncer.go | 1 + lib/grandpa/grandpa.go | 30 +++++++-------- lib/grandpa/grandpa_test.go | 12 +++--- lib/grandpa/message.go | 17 ++++---- lib/grandpa/message_handler.go | 10 ++--- lib/grandpa/message_handler_test.go | 40 +++++++++---------- lib/grandpa/message_test.go | 4 +- lib/grandpa/round_test.go | 3 ++ lib/grandpa/types.go | 60 ++++++++++++++++------------- lib/grandpa/types_test.go | 25 +++++++++++- lib/scale/decode.go | 1 + 11 files changed, 119 insertions(+), 84 deletions(-) diff --git a/dot/sync/syncer.go b/dot/sync/syncer.go index b2274edba9..a27b0a9a72 100644 --- a/dot/sync/syncer.go +++ b/dot/sync/syncer.go @@ -377,6 +377,7 @@ func (s *Service) handleJustification(header *types.Header, justification []byte err := s.finalityGadget.VerifyBlockJustification(justification, header.Number) if err != nil { logger.Warn("failed to verify block justification", "hash", header.Hash(), "number", header.Number, "error", err) + // return err } err = s.blockState.SetFinalizedHash(header.Hash(), 0, 0) diff --git a/lib/grandpa/grandpa.go b/lib/grandpa/grandpa.go index 79fddf33cd..e73516b724 100644 --- a/lib/grandpa/grandpa.go +++ b/lib/grandpa/grandpa.go @@ -535,7 +535,7 @@ func (s *Service) playGrandpaRound() error { return false } - if completable && finalizable && uint64(s.head.Number.Int64()) >= prevBfc.number { + if completable && finalizable && uint32(s.head.Number.Int64()) >= prevBfc.number { return true } @@ -574,7 +574,7 @@ func (s *Service) attemptToFinalize() error { return err } - if bfc.number >= uint64(s.head.Number.Int64()) && pc >= s.state.threshold() { + if bfc.number >= uint32(s.head.Number.Int64()) && pc >= s.state.threshold() { err = s.finalize() if err != nil { return err @@ -608,7 +608,7 @@ func (s *Service) determinePreVote() (*Vote, error) { prm := s.prevotes[s.derivePrimary().PublicKeyBytes()] s.mapLock.Unlock() - if prm != nil && prm.number >= uint64(s.head.Number.Int64()) { + if prm != nil && prm.number >= uint32(s.head.Number.Int64()) { vote = prm } else { header, err := s.blockState.BestBlockHeader() @@ -620,7 +620,7 @@ func (s *Service) determinePreVote() (*Vote, error) { } nextChange := s.digestHandler.NextGrandpaAuthorityChange() - if vote.number > nextChange { + if vote.number > uint32(nextChange) { header, err := s.blockState.GetHeaderByNumber(big.NewInt(int64(nextChange))) if err != nil { return nil, err @@ -645,7 +645,7 @@ func (s *Service) determinePreCommit() (*Vote, error) { s.mapLock.Unlock() nextChange := s.digestHandler.NextGrandpaAuthorityChange() - if pvb.number > nextChange { + if pvb.number > uint32(nextChange) { header, err := s.blockState.GetHeaderByNumber(big.NewInt(int64(nextChange))) if err != nil { return nil, err @@ -728,12 +728,12 @@ func (s *Service) finalize() error { // set justification s.justification[s.state.round] = s.pcJustifications[bfc.hash] - pvj, err := newFullJustification(s.pvJustifications[bfc.hash]).Encode() + pvj, err := newFullJustification(s.state.round, bfc.hash, bfc.number, s.pvJustifications[bfc.hash]).Encode() if err != nil { return err } - pcj, err := newFullJustification(s.pcJustifications[bfc.hash]).Encode() + pcj, err := newFullJustification(s.state.round, bfc.hash, bfc.number, s.pcJustifications[bfc.hash]).Encode() if err != nil { return err } @@ -897,7 +897,7 @@ func (s *Service) getPreVotedBlock() (Vote, error) { // if there are multiple, find the one with the highest number and return it highest := Vote{ - number: uint64(0), + number: uint32(0), } for h, n := range blocks { if n > highest.number { @@ -916,7 +916,7 @@ func (s *Service) getPreVotedBlock() (Vote, error) { func (s *Service) getGrandpaGHOST() (Vote, error) { threshold := s.state.threshold() - var blocks map[common.Hash]uint64 + var blocks map[common.Hash]uint32 var err error for { @@ -937,7 +937,7 @@ func (s *Service) getGrandpaGHOST() (Vote, error) { // if there are multiple, find the one with the highest number and return it highest := Vote{ - number: uint64(0), + number: uint32(0), } for h, n := range blocks { if n > highest.number { @@ -957,10 +957,10 @@ func (s *Service) getGrandpaGHOST() (Vote, error) { // thus, if there are no blocks with >=threshold total votes, but the sum of votes for blocks A and B is >=threshold, then this function returns // the first common ancestor of A and B. // in general, this function will return the highest block on each chain with >=threshold votes. -func (s *Service) getPossibleSelectedBlocks(stage subround, threshold uint64) (map[common.Hash]uint64, error) { +func (s *Service) getPossibleSelectedBlocks(stage subround, threshold uint64) (map[common.Hash]uint32, error) { // get blocks that were directly voted for votes := s.getDirectVotes(stage) - blocks := make(map[common.Hash]uint64) + blocks := make(map[common.Hash]uint32) // check if any of them have >=threshold votes for v := range votes { @@ -996,7 +996,7 @@ func (s *Service) getPossibleSelectedBlocks(stage subround, threshold uint64) (m // getPossibleSelectedAncestors recursively searches for ancestors with >=2/3 votes // it returns a map of block hash -> number, such that the blocks in the map have >=2/3 votes -func (s *Service) getPossibleSelectedAncestors(votes []Vote, curr common.Hash, selected map[common.Hash]uint64, stage subround, threshold uint64) (map[common.Hash]uint64, error) { +func (s *Service) getPossibleSelectedAncestors(votes []Vote, curr common.Hash, selected map[common.Hash]uint32, stage subround, threshold uint64) (map[common.Hash]uint32, error) { for _, v := range votes { if v.hash == curr { continue @@ -1026,7 +1026,7 @@ func (s *Service) getPossibleSelectedAncestors(votes []Vote, curr common.Hash, s return nil, err } - selected[pred] = uint64(h.Number.Int64()) + selected[pred] = uint32(h.Number.Int64()) } else { selected, err = s.getPossibleSelectedAncestors(votes, pred, selected, stage, threshold) if err != nil { @@ -1123,7 +1123,7 @@ func (s *Service) getVotes(stage subround) []Vote { } // findParentWithNumber returns a Vote for an ancestor with number n given an existing Vote -func (s *Service) findParentWithNumber(v *Vote, n uint64) (*Vote, error) { +func (s *Service) findParentWithNumber(v *Vote, n uint32) (*Vote, error) { if v.number <= n { return v, nil } diff --git a/lib/grandpa/grandpa_test.go b/lib/grandpa/grandpa_test.go index e1eaebfc39..f7ebdc3bb3 100644 --- a/lib/grandpa/grandpa_test.go +++ b/lib/grandpa/grandpa_test.go @@ -263,8 +263,8 @@ func TestGetPossibleSelectedAncestors_SameAncestor(t *testing.T) { } votes := gs.getVotes(prevote) - prevoted := make(map[common.Hash]uint64) - var blocks map[common.Hash]uint64 + prevoted := make(map[common.Hash]uint32) + var blocks map[common.Hash]uint32 for _, curr := range leaves { blocks, err = gs.getPossibleSelectedAncestors(votes, curr, prevoted, prevote, gs.state.threshold()) @@ -313,8 +313,8 @@ func TestGetPossibleSelectedAncestors_VaryingAncestor(t *testing.T) { } votes := gs.getVotes(prevote) - prevoted := make(map[common.Hash]uint64) - var blocks map[common.Hash]uint64 + prevoted := make(map[common.Hash]uint32) + var blocks map[common.Hash]uint32 for _, curr := range leaves { blocks, err = gs.getPossibleSelectedAncestors(votes, curr, prevoted, prevote, gs.state.threshold()) @@ -373,8 +373,8 @@ func TestGetPossibleSelectedAncestors_VaryingAncestor_MoreBranches(t *testing.T) } votes := gs.getVotes(prevote) - prevoted := make(map[common.Hash]uint64) - var blocks map[common.Hash]uint64 + prevoted := make(map[common.Hash]uint32) + var blocks map[common.Hash]uint32 for _, curr := range leaves { blocks, err = gs.getPossibleSelectedAncestors(votes, curr, prevoted, prevote, gs.state.threshold()) diff --git a/lib/grandpa/message.go b/lib/grandpa/message.go index 9e68c30538..e1fecd3d70 100644 --- a/lib/grandpa/message.go +++ b/lib/grandpa/message.go @@ -55,7 +55,7 @@ type FullVote struct { // SignedMessage represents a block hash and number signed by an authority type SignedMessage struct { Hash common.Hash - Number uint64 + Number uint32 Signature [64]byte // ed25519.SignatureLength AuthorityID ed25519.PublicKeyBytes } @@ -181,10 +181,10 @@ func (r *catchUpRequest) ToConsensusMessage() (*ConsensusMessage, error) { type catchUpResponse struct { Round uint64 SetID uint64 - PreVoteJustification FullJustification - PreCommitJustification FullJustification + PreVoteJustification []*Justification + PreCommitJustification []*Justification Hash common.Hash - Number uint64 + Number uint32 } func (s *Service) newCatchUpResponse(round, setID uint64) (*catchUpResponse, error) { @@ -208,20 +208,23 @@ func (s *Service) newCatchUpResponse(round, setID uint64) (*catchUpResponse, err } r := &bytes.Buffer{} + sd := &scale.Decoder{Reader: r} _, err = r.Write(just) if err != nil { return nil, err } - pvj, err := FullJustification{}.Decode(r) + d, err := sd.Decode([]*Justification{}) if err != nil { return nil, err } + pvj := d.([]*Justification) - pcj, err := FullJustification{}.Decode(r) + d, err = sd.Decode([]*Justification{}) if err != nil { return nil, err } + pcj := d.([]*Justification) return &catchUpResponse{ Round: round, @@ -229,7 +232,7 @@ func (s *Service) newCatchUpResponse(round, setID uint64) (*catchUpResponse, err PreVoteJustification: pvj, PreCommitJustification: pcj, Hash: header.Hash(), - Number: header.Number.Uint64(), + Number: uint32(header.Number.Uint64()), }, nil } diff --git a/lib/grandpa/message_handler.go b/lib/grandpa/message_handler.go index 6fefd8a964..6852234da7 100644 --- a/lib/grandpa/message_handler.go +++ b/lib/grandpa/message_handler.go @@ -18,7 +18,7 @@ package grandpa import ( "bytes" - "fmt" + //"fmt" "math/big" "reflect" @@ -422,16 +422,16 @@ func (h *MessageHandler) verifyJustification(just *Justification, round, setID u } func (s *Service) VerifyBlockJustification(justification []byte, number *big.Int) error { - logger.Info("verifying block justification", "justification", fmt.Sprintf("0x%x", justification), "len", len(justification), "number", number) + logger.Info("verifying block justification" /*"justification", fmt.Sprintf("0x%x", justification), */, "len", len(justification), "number", number) r := &bytes.Buffer{} _, _ = r.Write(justification) - fj, err := DecodeFullJustification(r) + fj := new(FullJustification) + err := fj.Decode(r) if err != nil { return err } - logger.Info("full justification", "fj", fj) - + logger.Info("full justification", "round", fj.Round, "hash", fj.Commit.Hash, "number", fj.Commit.Number, "number sigs", len(fj.Commit.Precommits)) return nil } diff --git a/lib/grandpa/message_handler_test.go b/lib/grandpa/message_handler_test.go index bc24be8e0e..6f0e9b1e3c 100644 --- a/lib/grandpa/message_handler_test.go +++ b/lib/grandpa/message_handler_test.go @@ -44,8 +44,8 @@ func buildTestJustifications(t *testing.T, qty int, round, setID uint64, kr *key just := []*Justification{} for i := 0; i < qty; i++ { j := &Justification{ - Vote: NewVote(testHash, round), - Signature: createSignedVoteMsg(t, round, round, setID, kr.Keys[i%len(kr.Keys)], subround), + Vote: NewVote(testHash, uint32(round)), + Signature: createSignedVoteMsg(t, uint32(round), round, setID, kr.Keys[i%len(kr.Keys)], subround), AuthorityID: kr.Keys[i%len(kr.Keys)].Public().(*ed25519.PublicKey).AsBytes(), } just = append(just, j) @@ -54,7 +54,7 @@ func buildTestJustifications(t *testing.T, qty int, round, setID uint64, kr *key } -func createSignedVoteMsg(t *testing.T, number, round, setID uint64, pk *ed25519.Keypair, subround subround) [64]byte { +func createSignedVoteMsg(t *testing.T, number uint32, round, setID uint64, pk *ed25519.Keypair, subround subround) [64]byte { // create vote message msg, err := scale.Encode(&FullVote{ Stage: subround, @@ -173,7 +173,7 @@ func TestMessageHandler_VoteMessage(t *testing.T) { require.NoError(t, err) h := NewMessageHandler(gs, st.Block) - out, err := h.handleMessage(cm) + out, err := h.handleMessage("", cm) require.NoError(t, err) require.Nil(t, out) @@ -199,7 +199,7 @@ func TestMessageHandler_NeighbourMessage(t *testing.T) { cm, err := msg.ToConsensusMessage() require.NoError(t, err) - _, err = h.handleMessage(cm) + _, err = h.handleMessage("", cm) require.True(t, errors.Is(err, chaindb.ErrKeyNotFound)) block := &types.Block{ @@ -213,7 +213,7 @@ func TestMessageHandler_NeighbourMessage(t *testing.T) { err = st.Block.AddBlock(block) require.NoError(t, err) - out, err := h.handleMessage(cm) + out, err := h.handleMessage("", cm) require.NoError(t, err) require.Nil(t, out) @@ -233,7 +233,7 @@ func TestMessageHandler_VerifyJustification_InvalidSig(t *testing.T) { } h := NewMessageHandler(gs, st.Block) - err := h.verifyJustification(just, just.Vote, gs.state.round, gs.state.setID, precommit) + err := h.verifyJustification(just, gs.state.round, gs.state.setID, precommit) require.Equal(t, err, ErrInvalidSignature) } @@ -245,12 +245,12 @@ func TestMessageHandler_FinalizationMessage_NoCatchUpRequest_ValidSig(t *testing gs.justification[round] = buildTestJustifications(t, int(gs.state.threshold()), round, gs.state.setID, kr, precommit) fm := gs.newFinalizationMessage(gs.head, round) - fm.Vote = NewVote(testHash, round) + fm.Vote = NewVote(testHash, uint32(round)) cm, err := fm.ToConsensusMessage() require.NoError(t, err) h := NewMessageHandler(gs, st.Block) - out, err := h.handleMessage(cm) + out, err := h.handleMessage("", cm) require.NoError(t, err) require.Nil(t, out) @@ -276,7 +276,7 @@ func TestMessageHandler_FinalizationMessage_NoCatchUpRequest_MinVoteError(t *tes require.NoError(t, err) h := NewMessageHandler(gs, st.Block) - out, err := h.handleMessage(cm) + out, err := h.handleMessage("", cm) require.EqualError(t, err, ErrMinVotesNotMet.Error()) require.Nil(t, out) } @@ -298,7 +298,7 @@ func TestMessageHandler_FinalizationMessage_WithCatchUpRequest(t *testing.T) { gs.state.voters = gs.state.voters[:1] h := NewMessageHandler(gs, st.Block) - out, err := h.handleMessage(cm) + out, err := h.handleMessage("", cm) require.NoError(t, err) require.NotNil(t, out) @@ -316,7 +316,7 @@ func TestMessageHandler_CatchUpRequest_InvalidRound(t *testing.T) { require.NoError(t, err) h := NewMessageHandler(gs, st.Block) - _, err = h.handleMessage(cm) + _, err = h.handleMessage("", cm) require.Equal(t, ErrInvalidCatchUpRound, err) } @@ -328,7 +328,7 @@ func TestMessageHandler_CatchUpRequest_InvalidSetID(t *testing.T) { require.NoError(t, err) h := NewMessageHandler(gs, st.Block) - _, err = h.handleMessage(cm) + _, err = h.handleMessage("", cm) require.Equal(t, ErrSetIDMismatch, err) } @@ -387,7 +387,7 @@ func TestMessageHandler_CatchUpRequest_WithResponse(t *testing.T) { require.NoError(t, err) h := NewMessageHandler(gs, st.Block) - out, err := h.handleMessage(cm) + out, err := h.handleMessage("", cm) require.NoError(t, err) require.Equal(t, expected, out) } @@ -403,7 +403,7 @@ func TestVerifyJustification(t *testing.T) { AuthorityID: kr.Alice().Public().(*ed25519.PublicKey).AsBytes(), } - err := h.verifyJustification(just, vote, 77, gs.state.setID, precommit) + err := h.verifyJustification(just, 77, gs.state.setID, precommit) require.NoError(t, err) } @@ -419,7 +419,7 @@ func TestVerifyJustification_InvalidSignature(t *testing.T) { AuthorityID: kr.Alice().Public().(*ed25519.PublicKey).AsBytes(), } - err := h.verifyJustification(just, vote, 77, gs.state.setID, precommit) + err := h.verifyJustification(just, 77, gs.state.setID, precommit) require.EqualError(t, err, ErrInvalidSignature.Error()) } @@ -437,7 +437,7 @@ func TestVerifyJustification_InvalidAuthority(t *testing.T) { AuthorityID: fakeKey.Public().(*ed25519.PublicKey).AsBytes(), } - err = h.verifyJustification(just, vote, 77, gs.state.setID, precommit) + err = h.verifyJustification(just, 77, gs.state.setID, precommit) require.EqualError(t, err, ErrVoterNotFound.Error()) } @@ -468,7 +468,7 @@ func TestMessageHandler_VerifyPreCommitJustification(t *testing.T) { SetID: gs.state.setID, PreCommitJustification: just, Hash: testHash, - Number: round, + Number: uint32(round), } err := h.verifyPreCommitJustification(msg) @@ -494,13 +494,13 @@ func TestMessageHandler_HandleCatchUpResponse(t *testing.T) { PreVoteJustification: pvJust, PreCommitJustification: pcJust, Hash: testHash, - Number: round, + Number: uint32(round), } cm, err := msg.ToConsensusMessage() require.NoError(t, err) - out, err := h.handleMessage(cm) + out, err := h.handleMessage("", cm) require.NoError(t, err) require.Nil(t, out) require.Equal(t, round+1, gs.state.round) diff --git a/lib/grandpa/message_test.go b/lib/grandpa/message_test.go index 514c0db3be..5064b5c58a 100644 --- a/lib/grandpa/message_test.go +++ b/lib/grandpa/message_test.go @@ -134,8 +134,8 @@ func TestNewCatchUpResponse(t *testing.T) { expected := &catchUpResponse{ Round: round, SetID: setID, - PreVoteJustification: FullJustification(pvj), - PreCommitJustification: FullJustification(pcj), + PreVoteJustification: pvj, + PreCommitJustification: pcj, Hash: v.hash, Number: v.number, } diff --git a/lib/grandpa/round_test.go b/lib/grandpa/round_test.go index 51c34e814f..58f7be0dd9 100644 --- a/lib/grandpa/round_test.go +++ b/lib/grandpa/round_test.go @@ -30,6 +30,7 @@ import ( "github.com/ChainSafe/gossamer/lib/keystore" log "github.com/ChainSafe/log15" + "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/protocol" "github.com/stretchr/testify/require" ) @@ -64,6 +65,8 @@ func (n *testNetwork) SendMessage(msg NotificationsMessage) { } } +func (n *testNetwork) SendJustificationRequest(_ peer.ID, _ uint32) {} + func (n *testNetwork) RegisterNotificationsProtocol(sub protocol.ID, messageID byte, handshakeGetter network.HandshakeGetter, diff --git a/lib/grandpa/types.go b/lib/grandpa/types.go index b634355485..d9d4641012 100644 --- a/lib/grandpa/types.go +++ b/lib/grandpa/types.go @@ -153,11 +153,11 @@ func (s *State) threshold() uint64 { // Vote represents a vote for a block with the given hash and number type Vote struct { hash common.Hash - number uint64 + number uint32 } // NewVote returns a new Vote given a block hash and number -func NewVote(hash common.Hash, number uint64) *Vote { +func NewVote(hash common.Hash, number uint32) *Vote { return &Vote{ hash: hash, number: number, @@ -168,7 +168,7 @@ func NewVote(hash common.Hash, number uint64) *Vote { func NewVoteFromHeader(h *types.Header) *Vote { return &Vote{ hash: h.Hash(), - number: uint64(h.Number.Int64()), + number: uint32(h.Number.Int64()), } } @@ -193,8 +193,8 @@ func NewVoteFromHash(hash common.Hash, blockState BlockState) (*Vote, error) { // Encode returns the SCALE encoding of a Vote func (v *Vote) Encode() ([]byte, error) { - buf := make([]byte, 8) - binary.LittleEndian.PutUint64(buf, v.number) + buf := make([]byte, 4) + binary.LittleEndian.PutUint32(buf, v.number) return append(v.hash[:], buf...), nil } @@ -210,7 +210,7 @@ func (v *Vote) Decode(r io.Reader) (*Vote, error) { return nil, err } - v.number, err = common.ReadUint64(r) + v.number, err = common.ReadUint32(r) if err != nil { return nil, err } @@ -249,38 +249,44 @@ func (j *Justification) Decode(r io.Reader) (*Justification, error) { return i.(*Justification), err } +type Commit struct { + Hash common.Hash + Number uint32 + Precommits []*Justification +} + // FullJustification represents an array of Justifications, used to respond to catch up requests -type FullJustification []*Justification +type FullJustification struct { + Round uint64 + Commit *Commit // TODO: rename Justification -> Commit, FullJustification -> Justification +} -func newFullJustification(j []*Justification) FullJustification { - return FullJustification(j) +func newFullJustification(round uint64, hash common.Hash, number uint32, j []*Justification) *FullJustification { + return &FullJustification{ + Round: round, + Commit: &Commit{ + Hash: hash, + Number: number, + Precommits: j, + }, + } } // Encode returns the SCALE encoding of a FullJustification -func (j FullJustification) Encode() ([]byte, error) { +func (j *FullJustification) Encode() ([]byte, error) { return scale.Encode(j) } // Decode returns a SCALE decoded FullJustification -func (j FullJustification) Decode(r io.Reader) (FullJustification, error) { - sd := &scale.Decoder{Reader: r} - i, err := sd.Decode([]*Justification{}) - if err != nil { - return FullJustification{}, err - } - - // TODO: this assignment doesn't work, update type - j = FullJustification(i.([]*Justification)) - return j, nil -} - -// DecodeFullJustification returns a SCALE decoded FullJustification -func DecodeFullJustification(r io.Reader) (FullJustification, error) { +func (j *FullJustification) Decode(r io.Reader) error { sd := &scale.Decoder{Reader: r} - i, err := sd.Decode([]*Justification{}) + i, err := sd.Decode(&FullJustification{Commit: &Commit{}}) if err != nil { - return FullJustification{}, err + return err } - return FullJustification(i.([]*Justification)), nil + dec := i.(*FullJustification) + j.Round = dec.Round + j.Commit = dec.Commit + return nil } diff --git a/lib/grandpa/types_test.go b/lib/grandpa/types_test.go index 6ec49d61e7..732a59e69d 100644 --- a/lib/grandpa/types_test.go +++ b/lib/grandpa/types_test.go @@ -20,6 +20,7 @@ import ( "bytes" "testing" + "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto/ed25519" "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/lib/scale" @@ -79,13 +80,33 @@ func TestFullJustification(t *testing.T) { AuthorityID: testAuthorityID, } - fj := FullJustification([]*Justification{just}) + fj := FullJustification{ + Round: 99, + Commit: &Commit{ + Precommits: []*Justification{just}, + }, + } enc, err := fj.Encode() require.NoError(t, err) rw := &bytes.Buffer{} rw.Write(enc) - dec, err := FullJustification{}.Decode(rw) + dec := &FullJustification{} + err = dec.Decode(rw) require.NoError(t, err) require.Equal(t, fj, dec) } + +func TestFullJustification_Decode(t *testing.T) { + data := common.MustHexToBytes("0x3b1b0000000000002a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46001d032a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d5be226e7e7b6b54eb6c830ab47d4fa29bc228f46f176be79bd9b687d01ad7c4441f0c5b7f489462f29ba1641672519a2bbfd9162fb11d646bf1990b0c858e0e026905dab6c71c2a664e9ca8e4f066bdee9265ec45b7885ab14a797ffe1bee362a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c393865ec446b236036e3c846577c930568ea67f1cfe34048cb360d3892d9da61c4b1bd7ee31df96907662e10dae18646ceec91181c5e3dd97605b15f0bfc20d02aabb29f640813f718f1e7495f42415f742457517d536ba5de50990d182df252a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000d68aa7ce3c3902cca498cf6c0051d5b0f901f22092a427faa23227a19bc55eda954191acba6b5c82ec03543633facf3f84176ba3b860428d0e160b5a3a2db0802c70dcaf367b35740713c0e6761d88a2e26f9762e9715fac8bbeabedd8c5eca2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600756e9f4187f078001a7368eedc194df7ac7b1cae92827dae36d345061afa3d49df2041a41387501c5fd1b282707303aa3a7c820cce1b44c081c0051e7fc229070339088379600f5bd507277eb894aba34d2a8f27ce1964f41e60ef2a4142dc6e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ea73d78ec38c271aebb2c9f7ebebb5d3a99edf327bbf4d03b5c245c08d0c2cceaf0030ecb5e04152255f32e4edfbee970f3ab05607f6d8b718826e559bc18b0e03d105a30087d96b5f0684f6ded76f826b01dab61e4136e1d851a24f0088b5ed2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600681cd145a0375e5f72a737bbf92bc57c1e2b9429cf8b66bda13d0096e1898385ada09ccad93971530349592a430aa61331efb46c04a1bd9af7c34d5be9f2d30b044a5968644cf9f9b5d1c7f657a65343af076730a89b07b4995a494aaa0c967a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600620c51368e57db2cbf7e1edee0a746dc950c6da129ae5dbf81f3171fbba241f564b847fe376bf7a6f62d9bcefeba69d7d8b23000c40b7c9ec4f2868e6d748408049603b355867a2d3a1978a771b71b2bee4e8ae1f344d9ee034f47b01a48f89b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000dec8ecfc90b708446f112ad2730149b124d1d4b891edcc7f192ad4f6634fb1529471e902b590049ee6049361537752f8b4780e1900f5134f101039d617a710e069a481209b07b48c1d548d4af68b7584f95b32eb374be0682b342cd64d3406c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46008f6792c7ae77c3dbde17b000c86743bccd9f2247fb323044b12378a0b060f122674fc796d910fb2eaaf7512a5858ce467da7409a2e5fb14edc077c939efcc802088f8736e1cf2ea3d102f0a96ccf51222b8aa1f93d8e42947892f4395cb5477c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460043286aff05190884dc7be3f5b4e42728a1eb766c73cc9745f08322ed672ad944503ec837a379efef89b4c78a5aac7ffe8bd26e649eac482f5324280e259bcc030a790c3ba374fdcf1a1d457adcaa8fc01bb46eba8cae4e1940825364fe2d7ce02a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460025437e042850b2648ecd98937694a76d041c65b471a81344c55d9aae0650459daaa5f5e734ddf02ae030178ee8714c5f6cc241f8148eafeff450424fe62548040cd72d0e6daf0275f2eec96f6129243e01e255ad6b104ed500e614077d0710982a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600fecfa0a948e6cdb687849ea41dc46083ed8562ca4657d751f6c303ae0e35dba38e5c1550cfdd71011239872139770b8151eb4a04df948f666d43811009f12f0b0d49982b323ded139008e150fe97b0e85213a92b411c68f993aaa43ea62a7b282a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460063dc716d5616a3125fabc437ccc98b3e38bb2730a2564a11edf528552245f20657199cbc982288e2ff3127497a8a725852c35352ee3c0a2109d2e603c33c37000e1b3f7368c230b609c745aa7c2119b60218da3ade7e6a0dace3e337703c51992a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a07873b9fc9d6b71bba7749a8886a13c8d5f4c2b66907bcdf5fddbd88df3c6ece1d601799af6ffb7fb53cece0278df81bd34676ecd20a7494515eea9bb61af020ef0093d298f1adee0947ea81400adf0c27bd2e5c10b760d2c8be3b784e1fa002a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46006bc6c891010819529a51deedc4a7033de5e85aede0eb4103118110239cbfdad07327aeafc68acfa3b11872770653290f50c09658d416cb2534f0794ddc4b1f0c10d20c8996f5448cff82ded879bacf7534e4d499ec65f040024bd7c2402eba942a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460090c37f3814f7b66ad1008f43715aa91e3f7713285d44c9b82b88d7125a5a5be38f9c38675891de2455b276ea008321b742f420baa7b2ed8519918edbb8b9b90311addb386a5088eaafbe4c93e265b8c1459a8504f0d31a3799836224c6077d532a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460085114883f06b533bc6494706326c7191f224f0c086d0a21c8534269c3dd2d7a10c1beaffac80e05250d0c230aa3da0ebfbbd4fe47a8323766b09a8e3ba797b04124cb8ef23a2aa6f5d748070a84465b1cf4df73db94257d96c63df4b5ea8b80c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460037c148f4242ac604913f055679371442e9fdfd7f989ae9d73dbd5cd439a31f941e5c4a63f315a3b4c8faefe537dd51fed26ed948d5cc0110c8c89775e5a2a70914b432d81cead21854d70b19f9691b3d2a8ca7862b64a71f4f3172387078c4642a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d3acc5151d7a54944aa00350c835fed6f9dfb4f12b8124903d301a959bd63f087d19016a6f18b732d58d37f93ccdc5a9a27511054f54436dc8bf8a12e1a06c03155763a153e0c02b2eef1d8a9cd8a50a9eaac9d3af7ad1e559b1e2320b521e1c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46007fd97784bffbeb89e3fc4053e6667d73a24a15e5a864f8264d544f93b8d4938a5e57b079247499e1d0de9cfe038754c930d1427f491ba0f8a956c56b6abdc409172fa913b2ba5833a78ed51e13692e9d1045e7516b5ac738e6745c217f49fc1d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000b091d7bd11344b81890eaa9eb51f38fef522d5b38e05449ab04cf720c389c9a9669d8cf69411500ba2a78a4f7783d75c424b0ca60e5a564728a7908b90dec0918a1a6e0e81ae2cf403c328d1195eb73e9b78091bf1e33be4dc1e29736eaa0382a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a3aeb6a64836a35659a253533ca38c5e8f5dec712d764bbbfaaf079f671c4487345d232be14aead855a06873549f712ded1127217560fb315be740e2071812061c67ae7a3c999b95732bdfe95fa8670aac497a1027aba327a1438bc097fa700a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009ed7a0e3b08dafbdfa66248a9abe9f6bdaad694b2c8b1a85c2fb8ba387895d11890d482eea4078d1049373c9a7d95123093e2e63d4472c186c18ae7f47c310071d5ca234f8eeb85fedd882230378855b4e7390a78a802f0be7230156a8ce624c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a5b924123457be20ebb04eb12a248f693620331120a053e1837a343460769d6edb3316966f5784ed15e5e8f6105f8af34ad0bd6a8f11aef6f79eeec6e658090b1dc6da633d00a38d0d0b44056980bafbd624f2e1067027cd2b09267acfb70e9c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ed204b31f36a1c06fb6b69e206525afbb5449064e39aa477602ea878ac65a4e81e24fc35c1a8ba764ed1d75200a2c5681a43da66a8096349365a9302706d1d041e0695d7beee6c4a67c9a1a75f89f3f858a50a09ce1b96a08e685a49e20e7e442a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600124362e041e4e25522fef30f7267b7d52338dabb1a2bfa21d163b06df2261cf465067921371fa5be83279004e174d5cf5bc79102e2208139bf62892c64521e0b20d4e595c50bb9558dad9be6a8784492bbbfc754b9c5fae17edf4f8a84e8b4712a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46002d19070e68ddfa34d42b207919fcdf1583ffa6458a82e2680ee5c880ff145e4b6a18b00c1c683c511e196d7b24cf4abd508f6522ee3e8eb8b335a42a0f7dfc0121699d6780c6aa58e433f27d36b7f1ea5afd4fa404d8254bd62ed5b17c3f75672a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46002bd9ee43aaa4cd4691315b505af64f05fdb9d70e1fe5f8c7d9e536bcf723b67035239765e727d4e1146c786c97b1e900cd33d8c25440d9e38907a6d0af3d0602221a227686d900f6be260a84e81bab79adcac88ee2ceb1f4efe83e0be86fc88a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46001b561a74a01dd3e3957d7930fae671d9a107a0e36b1ab1fe38a2acecf24ce281f0270b516c0d73ca05c1e7e7b6b5870e973ed04f8b4b2d8f486731e2871c5d012275aebac33745b7c0f47957b0f9c1dab14d6dc5cbece544e26830248b9b638d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600f394746cbe78e56e004009e2f374bc8fbca67b5228f33d328ba173673bc7ebdd31669b9272de648f7d19b7b79ea68937a6bcc395695c03c8afd36647bf20f70422b1625c123c72fa70ee375da98f15cf1326b254ef833a27ffaccbbe81e993d42a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460043359e329c56b481de900abf705e49957358bd8261d5215ac63a96b9098eb07d68b12aa67f2e4559fe6fa838233795e32e9f08b1680f89fa7db4da2cbf40010d23192a82e612c4b047a6efe2c0b20d6b8300a81639a6d1578bb94a66a721649f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ef4e7891ef4f0703d2ad1833ba02e1ebc743fd656dc4e0d5b2e9346694f142ab262a9143fda3c3a199e8f57820192a49648a0872e7b1de04bbc101bcbfd0ab0524796621f90c2d9f2d12a962e14f9852757f89de7b1f66c22506321ef38266bf2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d65939fa1869e5e2e2ed5800cac361424eb5449365fee6914719dcb768267f37be9efb0a21b1720308dd5961e3ad43b00eae969a117b8e1121ba36362f0b220225d5c2769f768059b45c6843d37949ab7866678eab000a5e6340e30d1dba306e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600aa0831e40a2cbc1ba04377e7cf9a0c6f5fb037c9516e4e9cd4d6ad0564953b99c8ab329e5f667f7fdaed70e9b8d3b4123e6ed9249b4e999b41dbb82ef04ce50d25ef09e15fe616e090335b55cf729832e2c9f562428fcb00fd3e3bed2dde843a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46001396a84de1c3a492a93b29d74c536ea8231bd01657a1621a3af64b5993383781947c771948a7166dd9d21b4c34a36208dd102d77f3f6c99ef85037ccd4fb5b09271f0ce7fd9e0460b2b5f68afb847a80ea0821bf168dceeca396a1fe35754af52a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600804dd690fd66f6c15a74a4c1c8be43b57f0eba4d45d75f66777a5204a506c6344e189d33c41ee6085420683807345324cf24602b2eac5a307603a1dfeb71040d27a748182c49d7c4ec091c9589da5c9c35fa99ac8ffa4d613129dd4d08e9f1122a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c91a2c346304d33ac0121707248a0560bb32b4446fb0f2d29c6c4f27f4073bf6455e662af07a4c693f199a1b850fa794d45d0640d33883e08681b525f99bfd0627e96e8217ce52d55d5cfe97580a03b8f9ffd41e72ad863300db45749088faed2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d91892fba8d2b41a62f81cc5dc225fd193aa215c9941dfacb197f7b11262609953c57726361b6c1458d500cdc90eed86c0d9ae4cb98ec1457a438af9e706d40e297b86aac8a8843ef944f9633404cf307ae264f031f6dc803bd93c7ea69889742a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46002c85b6e9e5c28ccc33e4d59857cdc0afd0e4f4d1c2218dd018123af7d96ec3b08e49ca35a5231431e2663db3a178dbbe8a1a4f4c0005014770c0cc85876a88062ae745fd0181edf42f9313bef7ddc5d62ae8289212df03f8f73e210e0ed907562a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e4ea8f7d9584724897151d5a1412a73e42c96aecbd84020e52770c1767e8f807befe8dd28f7fc8cb12addd1f1b72d6c58e418702111cd708ce7da9f61574d60d2ba3f6bd3084ac7d9448dd341d4c7ca8c991b659c85f27cc68e80fe7cbd73e422a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46008c5d3fb36a8da0e1770f923bfbac75470ec6408c06f0cb1be5cdb5893a2c09eb1e370073aac480ee2ec8b9c3a9bea659869a4328e8e441851bfb846610ddaa072c3ce4d7102f4236e0df2e8f5797f6dc3d2e6f0e57d373c9a4b89b21d4d228682a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b6e743e03f526851e054ff4766ad6db181f83fbe2370a51431fb8c310f1cbf3788d99480e9fffd8164d31cad3db297d9b26c34523aa25d5c7f83e79b6c02390c2dad5b2212ee688f2eeb9ca1fb6a90574f006dc1c6680ac3a8523363a248940b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460020716a5266a471356b3780eeaf4ad7dc37f50352ece4e1416bbe47b5b2606bb59dbcb1d270aecdfa6eece9d3e8f0862966ea7c7643cf1c05945fc4a56ac65902304d970cf7e07987f8d7a500edaeb1c0973de1ca588512e5d9f268a9ec0874ad2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ed2d6b378c40cb99f357b0458085a0b0504c1622adf0316050396479b08ef1859c2978fa15b4e53fe911857add9ec253cfc8d6b949abaca4437824f324c1070e360e744fe76d5d471445ce6ad9587af67392b8d960d7715dc0efb43c698465a52a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600013ddc65f819aa4c592cbbd59ab1f4a4bb27c3a4b717e19d409fde187da9e2e7dd6c9c72ecb1bb772c112f3229dc5140c202ab9341d04a14dc2c24a2b4e34a0936ea5662f48dd131b91defa20bde06049edcee2982714f78519fb64450d1b62d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600087672b184ff663524bfc5d05631172d3eff695a117cf6c8884053e97697b48dc255835918bcc10f915ff4d8fc3470ff5143eb433c00e2dfc8b46db2ff16360e37035dbee1de6bef71e401cd19d0a26cb8b69cf719ae340ea53c9677d6a9aaae2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a2d8a30c22e7f78ea1a477a0d7971533020276d5d10bbbf530f31ec5d9ae688108c2b4535dcf9204604406f21d104131e88d9c8399151bb67c14a790945ab30439712f967c4e06d284d6da4735cf2823c1f770674b668f7bc896071c3fd41eda2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46007770937b4a1d62bc1df63b1f80162728b198d26492e93765025f9c71dcf992482c9e85ef9100f7fb89931c0d2fd82d063e24f7dcb3d28e549d1d45d8a91f2c013a2f175229490c3169b5942260eb2572198b494e63c984dc364d3f48aaa7bf012a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600f7f152e001649cf1353540ca716715bad9fb6703c1f34b78673baccc8027c3885a6c9660cce85fbf2b2ab696220a2e417964c91912b030a0861ab7ca541daf073d75d8fd47f1074a78cc88248f1f6b9ea6cab42ccb676f94226e3fb5e16249c12a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600dc58eb9647b223730213a6f217010fc35556325166e03edd470b04b9b38f97a98acc1695a71120f06b2beda919889a5c2de43f40f9954894ba861cc2082393003db99a9882de9b7666591c1f9f2d87685a2569bd0c611358d6e144a014c5612d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460070846e878685dbe30ef68636052fcb92da91ff77ad2f7839edab14ff2f5a28cc27e0c464d175c995647ca3a0cedb2ec732e5732789ad3684ec4f08d94034c3023ded8aac5210831cbcc4d0cf1f96f2c711afc90a6f6f35a4ffb766be1dfbaee32a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600674883cb865697effe33d717b63eb6746fd144e8dce5df79892d453b51fa49cdce9eee1500be36a245e33a9254ce65631b0049218fa305c2e9d5ee1a8855b00b401def7965bb1e8373fbbb6561d5ca51832cbb92b67b0bb08c407d2c19cca96e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b4d95db0f1cd96e18f87a0c67e71cf7b8d2facf75d10671bbbc9f7e5642a4c2223100eae4e64a54ca179525b19a763fdf624d78e209f7268d7cf4cf70788200f406c4081abf0f3a151dcaecc6b7ca1a7489cfa1810cb02c8cb249bb67dae09302a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600bda3c89a9f0257273f01c786914fcac144189a20db45d05fb42ded836fe2067362fd1baa2ec4727a7d8d72530474f60a39070cb0f2af31c372a511c55ef2b20a41ef4a31eb7dc1e01f4630604e1908e644d7cdee3f66a60f98d6d59605326f8b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a8bec76286b65de5693a2cbc309bc3c301bd6812eb493113449e9d30d39b60ee58a4d918f127d54522e74d6c6167ed99dc3b10e8aa64f3e4e5a2a497e68d9e0542d0c88f0accb5117e31bc057d4f277a38e01f2325f02d0a9f647db09b67cf202a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a243df7ec7aea8df7944c4474fe09c1a2a006f697e29d15300da8a51ac7258244501ea6c87983d0ec4643c051ee1489087a3017a63c2ac3b7d06aaadb1b88a0943701217f2650ade985ac46e38e548bc1850bf3d895eb6c1ecf42d9e61b788c82a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46005305ec8c29ed3ef51119c80c144ea8714321a31e533c0ed9a1c03a781e4401a279393f06e4e0f0dfd7f21b523a808e0aaf659aa587063d7017dab6ec8df7810543a9a2915f377cd4943f602be38c3bd6ef39e91562c09f18fb672884b4bf8eab2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600fc691469543554a61c6bfbf5d554ebf7cb596ac78579aa1b8d6b0c7e52f8295441a4bb450d8fe1d2579a51986d3f7a6de52a34690afe34fe5699a6bdc1fee103452185859ba92a24c1f836952da8d11c9425c9b35eec979744a345d2276de3ee2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600df5b15104aba6d1d4d70f19eee2d04e22056616f0b54011fc3f15449aadd958124dabcfac319655f72688f14130808f3dd12653e4f948034398589143c24fd0d4532b59911aec8842fd910a35fad7c6210b3c1ac73c7c9799963c635e2b562882a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b11b2845d75099b35ce971277fb82b0a8f2be06f40d2b759a291a095b3ae6f9655279b922667119308fc3d673e3d6f0063bce32f0b2c5370071214ef58b20106469947dc7cb086bd75216dcff8c8ddbbff5e0f112ba397d71b6bc2980bb6cf002a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c589c74c41ac4ba4e067df4a152dd5f34706f74828bbc1166a7ea1863e411058683a6d3b3c27db8540a8538fc50944dd9b3c8dfaa5f49fce6c5aeed834d8d504469e0a875597428e25f79157e441e933f3fff7d2afee478a9ac6e1903e4d4d1d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c0a2143bff781fde49d6989df90169579c588192fb669219f815f47117459f9a94ddadb68a90796f95186e7ec641e6973bc165d0d8b2fe651c9e563551bf960b472bdd18c1be48d5e3c40aa093db02e45c4bfe1a5b62a3884fd65be8eca3c6022a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a5f5af2141ecc0f6a323988e7d8ecdd8b609fb9379c20d108e218ff6c8aa7fc91ae54bde791fdde4d103c43299c033a649e69c8fc933db42b858a3730dc39a0a493c604e7f0a7cd6abf370c10b90eb8ff9d432bab420bdd9fe3da656d5e9b6752a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a1f8ef2a102fa613697a17e7135f7f7e429107d27a74ab15f0f2a46afed1e3257ce683c09017e25cc7abd447e06891350568aace957b76c5a2a8cbd9c0c8c800494d65e6b674eee86acf5ed70b45b37d7498b8c1d05cc0baa0d6473fdb596bfe2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600eda30d06498bcca078360129b8d46b6d8af3b8b9ddb252a00dced827e3b4c86373e299dae8720f622be9bab21766fc970ad786bc5733ec208b0105910b993c01496826b538d97906e14d417b48598d7d591a483ab5f4c6786cd0b96239c2f4cf2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009aed65d04eaf5a5c215e4f3cbf9144b83bcaa41c037e42ee788229ce622b28d3b2ec91c3b1b609aff0108bc920e0f6f43266e1b47598bc99ff5410e8af2aff024b35f8352f2aa4dac039d347d37488de305c7ef6e7aed2dbc3526f537efa2a3a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000c18a3ea31cd3830207a76b80da87192c61c608490c9c8826d4c860f8b39b2962d258dd43bcac5368fc2d9abb0793c188f6197f0a6d4c3346d2e53c6641086014e319a863a469525687eb7dfbc8924054c35d0599b6ba4dc94702edce01671a22a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46005bdabb13bf58fbbe4553d4af8f8fe9ebd0a4aa99e0cf8271621c9975aefa2a5f35ce96bd2897eef6044c3eee4aa475af4288d660c1d64576a770985b9c74890350811bd4b7dd8a5ed9193d4e2d19248020c3d334249a809fb96234f058ff90fe2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600bb6766e42e7a117d09953e5b2505c6720b77655c557be72254b0d946dd0670b94a18a9529dd8c96404596e9b6161a0a5e94f5bce6c190e733da9d4b5504af60451237bd7fe895abb96631987f0f82a41ef6d56f217dd1ef8aef76e9af4e559862a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b1042015c7048dba6b6aec2a38267657d0f221d0a862514b91b4714dab337eb41a014683101b18d98bda3cb4860749f10e5bc3a1927946e1c96c3c937532800b51f980443e5fb020d8498ff88e49fc55f1581d7eed1c66bd7653a4f5a1ba18662a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e17cf09d6059f3140f99a05905c17f597bf7fd8685db6c61fdeb0f91f0d3686a1e218daff23f1964ca2e7f983efad0d0714ed9cb5b23dcc2d428c03cc10f1a00543a164d12ac3f4ce00b176cd41a7af343c56e4ff445a2634d74f3b182c9c7352a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46004c4a8f2ee83d7226b58b1415f57cf93574021a6e828b1852b30faaf97fd75325424bc67043a889d41b4f1010e40e2ce911d02ffb252dbb1f9b15f0936fd9cc0f56682332630f5dd42f160f1e3475d1881b2adab83023216892f538efa1e0e66f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460095af4f1a0b89f8e6458417dc977f40bd63d40cb16c2411f5305f3a12cb44997ddcca4c14c350296b4d2a30cafb1e272d22b772b0ba1d85ead112b93d82a9980f57a7cd79d0feac648df60b94b7eccde724eb7e473fa2368eb5b88181b030239b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46005cfb9e1d0f208dcb64abae8d57bf8bbaeb4495e61da051a28d6247ee4eb02dfe0a5bcbe63a8c7869168caaa8da5ccad40e1d0c64b4c802cd9d41133cdd88f30557ae0c85ebaf333a6ec3251f577cab910cce072f238d1e50046322b83bbc0dd52a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460051eb234aec184ded0063029767fcb3e32d46bfba803f5301e932a4ce8ebfaacbfda1480f48991d6259401a5962804e2949be93d9f96ab7303b7367c6dcf46c0f584b81d2c35f01525cb80a02c259565f0becfa7d1651dd4c313358d339f32d472a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460061ad5b7dcd93f5ac2673bc36633d2a612584685c1ce6f1bab11561a0135ec7b3c20e2c88ae6ca855d4109f6f9892c41ed14bc38b549f00f823486a3e81b22f0459522ea548446804370cb91e2c4eccd2599a018202b0e6c04b7643b143707a382a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600278975698f785c3012217bffdfd48882e43033099ad968e849a14a962e2d6e538d1dacac16df19a0831f52daefbd4ba7c6af77cb7b4a343c575382858d1bcb07596004e838b7d90408d46de62b01e13e631575f348bc59f926286aae4c88702a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d8aa4ba9183770f61c39668721066eabe70fd41d30110134ebcd703a7bb4ea5f0288e28ac58a12dbce2ace9546b0fc28f38c5c467a33cb0de07ce8c5ac14a30b59bba625d971d505a9c7d7c2f3ad69203e69e3e5e1fc1e4905fae7703fa19e032a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000d40e4163b524c69ddb59641228c09fbcb4bff1ed875161e54bb10bee1ee21e618ff0715d5fc9e9ed1f32dc49e01926b4728c8b2664f2aa535929ba72080c0055b29e3e31323bbb46b3d1c53639ef72499c58806c7f4cb6d2e8343e961bb6e3f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600bd7478f49bf6d8d87ef834bb9653a3f0bda54dc653fe7890c69882abaf429231fed0b99dc99d2bd4f504c32e2008b135004c60e2045c7560e0c0c21c144925085b379072ec1f3f70b4650979a47b24f9b080c03450f7e9587d92cb599fcf4d6b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600117c8995b21aab23931a24670b8a885ad04493aa804a80b51572f5d29cf04d6e7d8f9adebc0cfdb1df0e4414a199398fab565d55fa0e32d9f70b828de39fc3005cd0c7b21d4834c5a41a80f7d421c7d0297e42eb409a524d33aa7557df13adaa2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46003d5d559b960da2c238fdb2609b45d125f1940fc8c6e4934d2888080b5f2c7f7da8c34528130afbcdac99c0364b88b5a329c8b742bbcfe91c7e96556b03e70907607b38d0d0b1ce290ae681fd3a7fe09b4299117c3d2f9a0a52c8a3076e268c132a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46008152931c4a5feea2e39f656662f3515ec32256c57aab6ab75c6f0b6f5bd50b508a743ff424f2eb046f1a923486a0566034023cb34413172ecb1e639634c9f00d6088164a2d219f2069d1d51e06c2b4eb70b85be9071afd6e99a92f1232ac645b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009226a52a4f9fd97a75c6c808a961744e731656160509b88038c3e7ba2e88f472b2f8fc5f865043e76558fabfd6b28dc0f8aad8a50d5744072a4bfa9a3d78200b62de44b8fae34cdd6d4df47fa7d420bf8513f35e3f63c66c4bf699675edc33e52a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460014c6bb8be7effc1ee39f3cccad774fc1e71212f75ce887f1b4bbd7184de4691c9c1cea72845d9830bb86b24464fdf7286936ed6ab238adaf78773b335057b60c64bffa0b9f160a67beb033610656b80d6c9342797fd983375e27f91ebc7e6e0c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009dbeeab2dd1d70ae4993bda61bfede77c378865b264b9620d17da5320141d749bdbc6ae1b2251e68f79ebd445b5b93d8a5b2fc5a2257c307ebea37f1d45c740e65bef6e1f3f6609291199f6e0940c80e559588368e2031086afc1730e0584da22a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46004ff39ee2863945517eefde66d42c4ec96da046097b7efbfde7c3111a6c06ce97f552f33374f43a723709a4b3a0cbf6b226af61515805c74fce2527bbe828860b665da232ade423c7280c6552e6d0ec8782f9bc742c0b030fc08e9dbd3ca5d3862a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a363544def43bec4ab3cbf8aa4b637198f7cc7bd89c2a402d40c6c10aed6a9f231ec0ae946d8d21c4563ccbfc1e6d6ec73063aeace3397cfe34f6e78e378220666ed84a2c0bff828dcdd1d82463f642666dfeade46c020621490886912bf5e022a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c2ef4b28110bca12507a617a321e2e5e3abedfa0583e4bd9f320b02d5984f62caff3a28a627819a2f2d54f01f80713248a1e5f0bfaaf8b261a3ceee530b89b00677358fc648638cbd854d2a009dd39b8508dd3047d0c5f13bb403a64d053ad032a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009d76b8b8deb037178f7686154d9228969aa30d5751ad1c2e0097c97ca39f3686a91afba09d50e443f75a8ea25f41f1e63daa1a5e0026e099f9241ba7aa38600068f8fcc2977c5fdb4ee46f234fdcec2f60a22f9c63ac7091b8ded3ec441df9bd2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000b027d479cd33d8d57c998621de2cb6dfffd320f53d27585d3db9aa44c59bec3ecb584f003e5571e488cd92a3485ef2b740c27861a5d780571dfe10c07307a0e69470b335262280ce95a164f7963af49e41ad6173f8db9e3faaf3ab54a8c50322a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009bcb275c92fdc035bc43aada4277e97139993270fd0ab3661350adbb09304b03246c37b02500355654ced15912e9431858d85a741d29f5f9ff65ac597fb3700b6d14338abf210592babaddf4a584f2ddc06c0d333ba8a9f5e284c3be59c828512a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46005c4b0f868037118206170a6b1d858ea81047028e10bddbaba24b1b6722ffadeac7a10dfd3cd1c2cdfd196ae3da5da59a0dd858f6ce15aeb884d39e100a6619096d28713cf7af6e13d24dc67d4540225f637f0384e58e2710d9e294e7473edc2f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600cad6e6d6beae1a9121a104e512d5d9d89f60d0aa1a14e89ad906e5a875cea4d6290956f0f6acfca1f0faa651886556d59ad2d5311f840eda172fdf0fb9ee360b6f7a1a4f9d8c054907f3b6b946a59525199dbcffe9fd3ba89612a5d4b548bede2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460096490d82522f60cceb6f64fe9d7be13cb9d3d5426f34d9c6dc7cc7fa10b51705ad8c0fb4cb8d1df8520af798f4cae294b765f4c918f75f8b80a9110ed4fe2e0674998004d06285d2b8d99a87a9fef38f0fc3109f4919006b8d1831b0dc59b3782a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460012d3db9c5db15a349f1152cfd3edecc7cbcc6b7d6e0170379bbf12e29cbed42c9cebd9bf55832bab8dfe4f761017aa068be5f84101c4d7bccfcb929b34c9ac0d74a47c40733f76b6d0502b67a709fb5e5cbb263b180fa0fba800d9d6758207362a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e1923130437dc6a03eccefc51c3717c7f95851aa9ce0ad76ba0a22b5dc4dfaefa9cf456eef9a10b9ffa1c33a98cae77d6b16df72b1ec7bd09b53d62dac15d40874a8906dd2888c9e7edea13ba3c42ad4d833f4a5de43d5ce0d9b12c654ab2e872a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600cc61c46e1babadb7667708e3ed03990398d9bf080ba41c1fdf67b4f51dcdb7e991b6918a6aebaf36df53f828f950a7698a21eeb1c640f809281a08424776cc0674db730277b3ea5a14e52fb0ebca202e56876c666aa160f623a4ca411b7ced702a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d9838ce8807f0de4ac9b15d648031b40e5ce436051756b3b4c852fe07c67715cab2b3cc237fb7f72e95f970760d9e36ece5107e07f41b43dafb771a42517d4027589ac4c910bd1e08bdb36f0236c05cd20349d62e07bd34d89afd21efe56b5fd2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c3136be04b86dbd5ea6d9151f51d16b47aab73e059569dc775951b16a71ac4fa890bba142e61bd67470cd1fac95ed42da2924bf82826caf7c70c3469acba540a7a92827270cfa82f16145a44f9bdd9ff5038ef1b665dd520a2e61db9749094962a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ffac47e7232095f25fdd02a1a5dfdbf3cb9f9c999881194fa50ffb9a6f6cb00db0897043b0d6542770710c727ae5dbbd231eba181ff1c097ade5be59d01389077be4c78d01b0569bdba6cdf39abb6e02f591942133aa1db230033aa48d18fd552a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c550613a2aa3703d95d343186fb5b9ea3e5dbc71389f842cca278d8a83f7735655df193eaa580de1a0f02082385ebf6a1148d93ce610a165135448a83e6a55017cb4b69ff1f333baa130c02d83fb533cb84fc47155a707b7978e8455431f4acf2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600113efdc304c9dedd0baf19c9b54c235fb707a91a7b6730fae65d3860e960d6f6e757109e78422f7d568864de98a9817fcd1d1c435c9a73f13680525da4694b027d32388dbca301421fe038986d97764ed933927b5b74b91ea5320371a31ef12f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600cd7aa2897e1d6d13af72130d66d65248a25ac7003734db33c3d5c59f344ba6bacdaaba9836f959a6ffd140153f8592a0a30146a9ddeb34b75184c17f8cfae10d7dea0d8ef4c3c5dccd46e8d2af6b2a85e8b36156cb97002a43ebee8229b82bff2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460097ba00cbc15f7d2e6c06f7113afeb36984a2322f18960c5fbb8d96833c6aeba2f7eb6f2b485aa1e470bddfe489527dd782b21d3d70c6a49321202f763bba82007e7ddc9a34f7d5e0acd418405ee795740b3a004c977a9ba4ee103755c05aa76a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c42a2e034b4fd25cd46ffe153c8ff0f6cb764ba36c36b2dc125ce88d8593e5e8ddd636b246e1852e3fd20dee2a7d5b026b89aad6cc9080b4042cdb101b05ae057f406fa8642d4d3834434465fe6b1815ecf25a5875e0334010bdfab4768fcbdf2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460059b47b5455aa16792cef813dafa11c7e7955b33fcf0c39a67b85a64e620403aac36542cea1ea2d2015bdc71c09b9c60c33d77bd28c35a69a656c83743b5e63087f4786d6fc06a89b65579200e5d1bd63caeab25893891f06597232389866b4152a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46001f8980114bf02490af98132d5fd4e13b65eb990e39e0dff52f9e152754b83a19f750eb812a63a4255d621cbc095e504e54506c3a1399d421a36e77364d40c20f7f57f0e89ff23959dbdffc8f66bc433fe9849f7a9c335e0601dd59812db059862a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b90eacba52195a184b96f5f19948facb9d5cfff012278471ec8c2ff572e70fb3040cf595588bba91973fc2163e509e0686f56039fb15790ba172aa96ee39d80a818a546c630b881c2161588866965649678cda0f4110cccc3533d0f20e5e41202a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d679230a0317ac247bbbb605e6460aca06ff81c1dc65c5ea9b00d19b3df57265ce079a8be66d5d4047f1a5d81709dec9efa2f675536d3cdfdbe68fd426981d038930b6c9661fb81a752c137a40cc8091480743856cd77ac965f4f6979ca8f30f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46006869e6d4ca689eed4551201be9c4fd6024e38ec95ca0a6125e156b95f4f18ec4bd6b49e1ff7f5397a6d6ba77c27866d9d089eb660a0e7efdc61a8042dc4b8f028934b9a6c38dd420aefa1e115c839fad7a71147a6efc77ab593485c3b07576ac2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460038c61e1249a82da4c868ae51928fb4d21f980e96a9d32887fb9c4f41d2c6032c03c54e5e45914fa716c1e256af33758a5c7d333fa780c771e0e6f66cb0427b08895238f7e22e70f3f59aab49819d5237d6776eeb65c2d5a925b62ddc8d3f0c772a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b819531987ad0190e399e2d1c87559917590850870b8f938da3574953e6cfc7e0b6c6fdce91bdb0e0787c3bd2bdf1541eafb86da210ca85ae2bf6fb0a6fc69038c2e1ccb3645a2466ae46d3f3743ed89c0831a74720eb5c4d8490b1e04e5bd4d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460075c07804bad1b0a7f1211718a6d644cb500bc66416b6004f80903b51b3efc2617d1db5175485a2ede4d7bff6f0e8cf067630f11d62af1561032380150fef6b018d67a0c4b47eb0e087f2cc4c3973b59c9ecb729fc960775d76138ab09799466e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600bfeb58aeb94d24af2fa1fd94a4b9869d40982e3f21696fb70dc9f03a9b80f5d6389e4f4f11852e43900e505c282c8c8110545d547fd0b18cbd88707e8a5fbc058f02169e09026a2500b440eb43eccec58a6a32d9f5cd9644fcf26f5e0c9692632a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600601161377f0932fc08d9127736fbf0052b763f365aba81899e05c804a1c1fe945d13052e264c550e3524d6430d69700b016113b65b6e6e73b22918fac8cba7008f371abcaa2d51351b62e5b400f52086ef7803c1a1351563c5f405a6ac054fb52a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460082af14bf589502d2963520eca5863fe7498a982e11941fe25b5da9349de59cd245ab8b510a09795ab701ebf008eeaa812c877afcebf54e70d95d45ede4839305908ad289176561ffd2c95f7cc8a2ab5c6a0effd161e3aeb1732140aa501edce12a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c19eeac39b52c944ff50ce5bc10e9726646881c798c6ee4a88de3dda17739fb11742e5e35b5c7222ccc4cf95869e91947b993e668471ab724c2c05fa3a37740b926ca2461d028a766de4efe3d8412bd08adc97c9bb3aa28c07a702ca82ac26a12a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c7eeccb5b57eaa678b568d72fd1459886758029cacf2d33f944d382f8945c56ebfc5adf4322f768116fcce9596469910f7fa214568e2ff581f2e583beb633d0a92c3f7a560edfc7927df1631ff35be263beee400f187c090dd02e4a5801a82102a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460019fe510b724fd4773b7fb1c739f1b0b9313d6ac2f6341f693dd1f898bb10bf53bd09fc398d305c55d598588be1f8b9727fa41a086c4d4f0835aeaf26ed50d30098077019fe554385e0cf6e0f3983b510cb37dea7d42f19ce0eccd86e3e147d352a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46003abdcc324544ba69a1b63ca3db2ac95b35e05d37fa96ae24d6a215db9943ea817a5eae59006784a8e1a6ed3ee554035edc0ca54ab4b04b7f95b48e3548ed36049944dcec71efeb5186d55e2d5acec943bb28d1ee9f84db3a2e2177c3d7ee9b842a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46002a4d4e13d1942c7f953c793731ab5c12a45d0a25826281c2d23eb23a1b74f7c40698bbae7c0f032b74ea44c71cd4f550b9295058f2f1dbd7b6f966b00bea110e998b7545adab70a645393ff37884688b9960c8c04989e8575602625d6cc344f12a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b70a4ca5731238626ccbe30d9e740afa075f92a8d217fd224301d1415dca12556a89e0a2797bb2098cd0ae62bba8c4ff98e7d2089092e729bb27c039b71c15039af39dbe3ec236efd64ad0c099648dac388e424ddaebb0cba1793b1ffb9a5e2a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600dc57933c5a5a2ac87b5b72d60ebb537a7bf9bfa2da6e0d45cd322d5408800d7cb1555298dfe74b847e69ffb237754897847afcc0dcafc80bae56209add6f760d9d10dbeb235ce888f8ed0279a2dea721df2f91c3809999301774fc2a5f4272ec2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b0cdd53676e135af460bc95ec069717e7b78747b262d5140a1a193b855d9fbb159cb499b7fda361fd80b7743d10dd383526b70feb8dbc0b06badc2b911fe22009ec49b1bf8a4c76bff00c0aa80adebe0e9249e37fd977babb7be36028b65b00d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d5cdd8a7eb544a9479ffbb0bc74072ba782768cb615ebaa2e39bed4784b4fab6a86c4db944a889ed7dda78b2c0b5cabe5f73c423376ac92c379202a6814fce069f107b3106c7f7570af198895d8314c9deb526a2311da652e5cc2c049212c0c92a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460033038e1fbc0dcaf9212abe66e29d423b659cca10395f78a86774250986079aed49f81381e1dd793a19cb7e0120b636ccc098604815be2e9e2950a9fc857a0f02a6c5e1d8748c139b8d98fc3bc12c3e3a37ac8e0b85847090093b99d0a3c4b9212a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c9730b655bbad75eedda6adc2f28a30882d8986f28bbb6d2f0eb8f367ae64a9eeb9ff25365ccc11c67db4763dd53f1eb6345169be20199da23559be607ccfd0da6d3f9c048fe3ba16d0edc38626afc398f2c032a6790690d1de77ecc6a65b42d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600feafbf9f8ca6a14c50f918d8fb35a0b296e9308ae8b6c4ed7147d2aad912aea13fe7d2639829a5ed64c309c573dba88894374fd480f595bc1d7018ec8b37ae0ea70673aa688422f1775915bc89d6ef922db7f9bef28218f11bb30996a406d9ed2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c21be32aa3713d12b85ad0f157e3877154384e4049d5ac28a98bf19b4e57e1c822e6350c5ca96658ad6f9887440dcf967085e2e4008346b3cb950d46964aad0caae2b6fd7dd2550a95d384a06011091f788cf7218762e59e686ca94fd09422ab2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460050ffac3d9c7f04901bd6dccb14601d1c6a9b0c80587e74eefbf625893e351cf0dc1d4aa08b3030ad5398a461c065186138e50ce56a92d5e46eab62701a036a0aac1daf1e3e59dae210f2b56a2b194bbad084b0c1cf492289b2f03289c635d68b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e7b07a35b9abac9905f77d01f690f01cadbd540fa75c8147808dc84f5655b17d20951de43322a74e3e9cafd48351d58ea0aab31695c3df00d5fa1bb33a997d0dacab0d24dd65b0df17ef62e2b6faca0662890de57163550c52416630850787292a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600881b5c1abdf8a9f8ef9d9d9b27bd9eed35f8930e8ec3bad5ba82490c139005cc78b856fb9c49eafa6581988f7fca348ad60a75723db53122c73d25e858284806accc6b239b9c0c62f0fd1c9cd9f90b823b41d955865bac175488ed4e7da408682a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009edc50b04aedd78e4771634a8333fcf75cb4519e3da0603bd6203475d78a3524dc9d5e6e1b671bf4f75e30f7e1c8bf044bd4577d94375d33123e365c0bb5bb08ae2734ab095cee7ac2ff74777a6d0293ee47303950df193932f2dad4d28526c22a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b987cb9c38a0a401cf03b2488fc73b7faaa5df95e8384d1153cbb57620d1313cd67a9a610414056b91b02127543d33399252687e6984d867cb1e2ea97d83d801af2d390a8e4d1e464e5abd80e9ef716ffb7e656631bc4778deb7834f4fc20dd32a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600bacd2480f5df4b871ac9d49d2e79c619e51ca03330c6e0eb517496a0f163614d24013930325ef5b76031574977e8747c04c6da8352f8c80a8f05444c18c85105b2bb7fd960d5d7ace8e36e0e4ccb7171e5c3740b2ca8a96d868a09cb5e17e32c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c73e0d10678f068c32248f2b015a3a0f415eeb404fc6f89e8589ca1e208ff5949f230d73d985f35920285cfa018d43158f0972b6588320c3756f3b9df9b5600fb38176139100069f1ff20156a180e55782796df79f4683020b06f2a5019da6dd2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46003b29752db4fed93ac5c20b344bce075851fa5cbafe02523c694bec4e3b4109fc0e88bdbd943f0d5325d62929e124a484cb24460dc06728d2039510b906928b01b7626f7e84ac48645871ac12e66bc747b9b1f64eff2352537811f9712c127c342a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c68b27c9db20c07fb3801dfae33f92407cf8e3b9c952f494687140d9a7514f0db75c71c2221b0960d74b23f1dfe643ba2f6c6fb429744e38b2f7f2ecd563d608b771cf172f891bcbbd1126e3354cdd4e324cd7202af62f5fcdcfd2ec01ef7d252a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b272ced92db8b46f6a4a9c0164fd24f66968b23b0dbbf52ebd736b1ce0a4c0512e4715cb0c0e063d580f4b4bea5811950837fc4882aaf5e59aec026ffb0f2300b7c13f1239888cda5c8e6ac9ea10675df17633368906e66a487f91ddd3268ca62a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e155d70b9ec48021bfb05f7aab92008e03f56a19222718aef3cee767a1882805166889d248053ed9f7b4d0c00b19d1931dac509709fe9cd0a577b71c8ba88c06b9186b95c90d2d00a31e7c68066bd37d73408271762604e3608e2f2c983f83092a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600438f0485ea2ab423a63142417de6065b4243e190cd326656e2ebb895489ea898950cbdc05ce8b99ec21c88a96c3953a2366970bf5a0f36c6d0ca34d121115f02b9404a5fb9f1723fc9fd4c5535dd9ef1d67c5237d9d322854697e9e245064e332a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460057b3433e3f0b68a8787e44e78c1ed5f27cfa4b155d3002193a452bd1351be719f47a4f445d635052945cc21df9e54613c7acec835c90a083e89276e7eefe3206bd81133a8d8a33ad2b084c54536d7a17ef0eda5e810a9994b13c2392fea208a92a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600f53a753bb53e0243dead9f8d43f93525d02a0c69b202ed0cafc6afdd50962cc8a0aaaacac45cbbb936748d75a2554173c94531a5bbe20f9664bcd925bb876007c0988a1b3c91b35a7c3722aba7d3a55f79ae07ac6a46d57b4a49a06eae20333e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600276a7f7126f933b4412dc4db264ac434134d77c174c3d2bca0fbca788d0327b24a2a1cd8134dd39cb9faed737ba496ce35b62f663955a88f69adf8f9f7061b06c3576342cbf99792896ee5329b04ff2eee2fc2bb6d53c5c03d52c8957ee793fb2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b5a298d2a97dbd7bf266ccfd61f20f739fd72f1cefac976ebb786e712643de23f8797aa0f160d327ed5210b6bcebb2429dcc673c4b9a39515cbe07dde7d80002c3ff25a1743a9df92af4ccd9a7aed5cbc90f64fd538c3df0a9539128f59652672a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000b87db26d1a4eeeef1f18331c234e6daeb8aea48568b4424e76478eaf1336e1cce3e3b72d136d638be075d9238c58dca48456e8587ce2ef2c46b1d7bbbadf50ac4025624fdf1544b90a61e85099d9e3ac235396d2d9d37f4921162fa688ba95d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b472fc0441578ae962487a27c9903b0e8cef78c4c69521c759f359290290b1fd4ce33692f3726dc51dad68f8ff3cdc88dc6a137d2fa46ea4e10484605c4ed90bc4153342949e45f683cb6df646fe7aca71502b1139c1dfc929afaf73c0a6de822a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c58f80813694cdd8df0fe0586c341a1de7ff7f6dc7c093c2a7333d7e883354a5047ff31079cc00aacfe59aa12207c037e071f710a156247949099120a6afac0fc4374616443e809c1c763459d10d6bf6a2d999855b8d339a27d20d360ed5f1282a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009bcc51ab91092800c4a9519ac8bd2f37c7a7079916a594cef09dcfff47978517c9b1c276749de14c21d010371cfa8da4d5f477787e0348fc71c7cc5c57322a06c49e3cd2c701bb845963c2870dcca12ed070c3f67ffc20144327a93aa6e896ec2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c85a800ac1f59f0df206ad6fa5f26e2f208deda2c0af58ed8c6811c525a9fda21a781689446edfdc0b8a07062daffebe442699974b64cf1c31688fad59ce710bc61cb1b626fea15085663f7619a1769ffbe4fc1f8c63e6ece773acfe180806c92a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600fe7489b632220f8c23b1d66fd40bac34513f1eed3c9a343a59dc09d8d360517db73b05c2e33b0da1932a7cff38748645d8bcfe843290a561c63ebd7b98ec1e0cc63062763b78629518608de49f42c802ec2fc22f477e10a1a0023b237675d65d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460018b4e42adfd1044bce65ff12455dda705b023cc0ffdef4c073aae2180cffac8375975214bb357988f37441c14e1c2913b2c7babed43a0b270920da6970566c08c676bdd3798340c7b35c624f2647f26816ba4b3a0821339f5a0d6c9f2d84ca3a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460034a88c97937fabdc6e2ec582df7cef3829a2ba8b6e57795bb1832e08d07f12912c546f7452b4d0338ce197a9c0a376202387322f371495ebf891a3cb27551400c6a46dbbd2e87ecd0db7ffcffe24c1a82353bc4371d7c2cfb81e7c832d556fce2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460099d000bd6eb0abc52ab88474dc3e504f6e0b21a00cf196317e471926b93410177ae48f2860c98678769671d81b85ec462596ddb585b6e30f4e71e1a98ef80c01c70ad885a3f3ce3fa4041e1477cd801cbff8daa7c835d4463044fed653b3830b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000bef4c68d2fe10b9b34995633ab9dc2c76f43bc2e837252024d85417a12dcddec9cbe44c70891e541f0d67a19e1929de1b0ee5f610808feabb9d9741a0ac7201c8768f257eeaa2a6636fc68a00eb781941c26d2f5179455ac4949a320f958d6f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46008d426d804b32a7615e207fc5fa9c119c9ace79782226bfb6dab21159f3950f18619faafa59caa9a1cd0e0401698137de6b454e45a74899e74681e7e2ee85ac07cb92ffeace78dbafe6fbf275741b4b38657fb81590712aa0bca7877931f6ad392a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46008319290259d0047497e2825a6ce9166f285a7333ade0a970ad928fd1b259feea5c927aa2c742e7fc7b542c579e65979837a77437cf28370a44feef9b8fdb5003cbfefdf389bc341a7e17139f61146f9fb3a9d7bb84fe93c1f771eafd4d4d9d462a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d54d20eb70a33339ff29f8017a48a857c4772ada28e5a88be923045c7ca2194a5683d51472db38d9ee5295fa892963264055b15599226d1ad75aaf550caf9f00cca95e245ef1d3209f6707fec25036c0a1b93ae5613a5c93095e23520395c57b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46006fcea4a22b6b3b28a52fe41d07f4dfcb20f80b2d3308ed4b5b46ed8e4f982a6968bef3e34ac0657b1807f9f0203d905d8dc8caf4ff0c00fb651489984046510bd060d2b638be7c2ff45b20575a76c4acad1b9264607e1b71286e4e00a03266a22a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46001c0549d687700803d27fa15f67df39a92975721e14adc62d267fc7b676c2bdf4d42c66eca0ca0b6fceaceb73d042a59bd88c040aa870c4636f5ee167776fca04d1c146d2429a5a827660008721c7a880e71f44feaa3dc75524c1a9281bac48cd2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460036ad739a304649cce8e4f18021379959c98bf50e9fa1abfd74a9c3778b35f3ee2c2401a0f51794c4c44fd99d167ec94a52529761c41182fc708a6bc25a32b50ed1deb1ed8d4e155f1b5bbc54610756cf2541f43b7c1776f97d404503878b96002a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46002e1b05424cd82fec9717f23563923e095c71221e13a284261e553617e94c2f4d053c28d9652abefb837ba0d9cedce01d40696794117fca8f2b232ba97d418802d2c1a8caace45c00d73f64f241162f85842c35e557e097fd1749040fedc94df92a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46001b1722198be15749cb2c16b9b0cd42dbb320c3f6ffaa880fa90a8a8db74ad322f69bdc2762796ae752ac4377637aa6e0d757320c78d8a2b3f47a8fd0a1214303d3adef5f9366e150e5a21d8837947fc2378997c7658245fdf0bca95a513f04f92a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d202e0bbf1b01acf647b11dd367019b763c8b37f1a9cf1d1a1fe663f5cf1aa6c3c9f4a6675515e9ff47255a6627e888de370f118abc6be342d624a6a83dd960fd416f2796d696faaeb34d2cdaf1004ede551b62a690f99d69b4ccd5f4a6c248b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460028caec545fd07031c5e73f34814d463636013534942812417d2317c7a557af8d21c742280eeecf514916493746db6d6ee1e059170ea0123220a6ae57c3c8e102d4b350a3b25c27c9ab9366d130560326fe6bb7f3a820767736b8a061405e153e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e5a071915555df4404f783902b68cb3c44e4ae8485d043cec0e615ff63719e6eec2cab48ed142c04efffb4321724cedaa427d27e9bed99344dba219a533b6b09d67211ff6ded7cffc866c81e24a9f54f08cd1df7ab202ae796a01d72cd2d3ab32a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460029a7b0b7a6b4a80c7e89b1cc2bb8635bea1587ad214100da7b11add353cd3e69df0b8dfdf17264eae2753de1ef1d62d267cd7214768bda15f56f9e6b00612902d744af98bbb366011a0fe90c3cee81d21cb301d9bb72d5b40b28da348ecb81672a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d77c1a9378d17bbba63e854c9bfd0abab0b3650a74bd698d7b9feea4f588d5dfa2ce724f24f60d844a4feac5d9b5928f4c537caa9230ef2ac5d1d9953b1b330cd7be80fafe9de0570984b008636808811ccfd82f32639cf6806dfb86c4c4c4c72a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d373bbbb4349a52b00d00a3981a161ed96bd0fa969faf1dbb8b49879f86dd5839b0ab12856f912ebe2c8e8a12d82dc7688729a6449d3bb6b7a6d53f66d82d203d86e516b7564a8ab0b67b503c97976157c05fc2395068b27df5f38a1a9b29b6e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46005065b2652114169f82c0c1483c19f8fd2f0c72f8c98928fe419ce0b93b2ef1583cb684837009c4c97381eb83c880f62055e9068113d9e07d1b29c991521a5308d8a6314d0690c070c764863c7c25fb8e9d6cd462f9bd56b0b236558f66fd74fa2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460016e5038faa65afb9f179af818be6b65c1a93963effe329ecba6786891ac1d8956e3f209365e45bcaec3879ff27891202e219e8061228f7001446f532b0bc0d09d978b1073d9c88d84ece71a915d577b8b381938d07827447ec20a5bb250496d22a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d57606898de72fb911f85da1bb8fb3743b6edc0e1c61a61bc05d3b6d1d429d5b6d86e38ae7289b40efbb3df89d14f5704f8e61bf2bfe195470f8a8fe9146d009db1067e5c50401a17b926e54a98e103abd64c4b83c87b770a8312ff03bc29be72a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ef38ecce8f3b7f2253942c612b029e05a6b37f8b1c406f5d01479ec76f956cd040f3a679a9e77a037401185a665f3162531feab7221d828e4bf7662ea876000ddb231b388a5ba18b2568eb1ab9ec84a637a66d6c1286fc7e1c2351250db635c32a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000d66f46119003f7476f2115373c0b88a81e3044ef83d9955fce655439adcaf22a12b74b5fd18db965e694d5635f4aa2418a263decf6c448b891462432ee7ae05db3aeeb826b44e7808e3865df0da2f479442e2f2fc895a46dffa8d908ec95f512a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460065ba17675d688b0fe1ae25984335d388c61f14a81ceddf6b6585b90a9a59bba0a4070d815018326acd57a88edbef65d60ba226554112f06c1f33b0c6b39b9401dc1aada86981424a634bbc637e58712bc5002a34bb6498487d89d83534b05da02a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46005565dd6e8a390aa49b5491cd0bce4798501a87884ef50ba120262b826ff28ba5741930e988241f4efcc0cd42f7c621033166193fc85b00b792d41135172d780fdcee76cd80cf2218de9e8c4ae1922fe1c26235f9cb479b2950b064025a1d69412a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600f796ea2ed7675e68b13fa0df5a873706de9c3490e006b4b7e330668ca62ba0eb7284b5087a09b9ae01e2ffa3aed1a545a1de99ea38b0bc183d46884315170502e2cc4424464983ada824bbef5aaa8995e80ef1017c15ef3b13902599841637ba2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46006abff33b091bfe069c92ba5bcd25d56b604a45cd1023080614e8e8578f9327c7025a9bb2ba92c4db3e699015ac4f46dacec10245d86b73d41b9e751bc7f6e203e58210689f52468a22c10566f3a2c6f870e61524298a3e781425861d40446b132a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d2a95ca51c964bb35183f2ae626a09a01b7b7e82a046af708da537389d68757b8a5d5c8f230523c5ec089277b7c0e072fcf4d9dca2ecd7bb6a3eec6e95919c01e6ebe15e9e2c9f0fe234241ee50e9c574774807c0d17f8dbc7fdd1802c5c79792a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600254314bb9821d205214651a8c292292089a320ccde7dd38acb21552a855eb0ace84b132f04317b591b84be06679d74df67b46abee0128089679c45e6f7836c00e7210714763e5bb3fab12067d0a55784ae0d70ef14ccf9e243bbfc6d329834102a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e3b32fd44cc8a2dfd85b2717b54a4d21d93a8f40b0c01be21f81dbc482b383d2a4c32911a93fcc4c8a746dc75aa0925c6b266b397a17f05318e6cebf51661001e81206b483fdcf1fe42145f27d1efb8178a57ee24b196285374037da2a53b3232a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46008b0b5494d43206cf55b468597dbb9dcfc51cb59a5b94a2f2150d5bed423057867958b09d96d7b772d41d2ffb7f68125ea119f0d628f2ad338856a34fbdd8ef05e90f0dd5c3d8e23deb6b82b9d7335855f5887c3b77ac1fe30d9112b22319bc922a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d2a3355b20c0bb0286e1951b985ac51bea4439b0c70ede6f38c026495bdc948e2df2f72d00b1d3d7a2bedfabe8a57d6d4f0af59670b5e75eb713a1ec2cf7d802e9836cd1a96c0010f43161443a790d65ea2afdef21b05bd563ed55c6eecb00662a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460015d31429c5579f409756d92155519c4bd60ca1b095c8f5a5aac66459efa937f48989d599106e5749cb74e8718bb96c49119ec5aa11adb3251a9365456809ae0aeb0af0662538c295c85d7da7b1e0d929f8d887a55bf1a136a144f2f7cf3215802a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460083b2ce5b2c3df07146efcb19e6dd1d8755ca7ef52ba3b05e3a2830374a4f984a4a2946c0905d47f962bf14e51bb0aba4303806247135d579317349e421550505ee8f86db1c1d2b7cd11e962fcfd1a7e847204337c8893f1c1ddb75c6bff3f2bb2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600dd14dbdb4c43afa43d3da56daec9d48f09e356082e2317b505fc1a8520eb491d6fd344bc94479d9497be72d2dbc95d193c6bcaaaec8c5df928b37e8373cfb005eecc027b5c05b7158fa0614fd70df47412f181e808bb78cbb63e29adf14ec91c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460053e0270ad201d8ccbd4253715af906a56e2f44c6d78f9344da12e465a5c755ba569b0f5db093834921b8ed11f4df2dd52497b387efdf3948ff632ae40f7da504f1020dda4f7e6af02b444bfcc70a0a18bd48042ad72b1955778bf4e56086a33f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ff32188333a59ee7d640f5897f5415cdf08e97742f82d340687c63b71c0ba9ae42599d522a40233b772d6147edd01a7d5bc865714f95ff979c03e61b9d1a6a03f2a930984c2799a98d298521128eff1add9706c9e57e848d54de41f6b8dac4b42a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460039c0604b3ad216bbdfc17d641114a0d25e0865ff21cc945574cda12f7c41f7e19f3b65550b410b017e0e8f58c94d9abd29f01b7036193305fbecd341e7156509f5054136e1ea0e3956293c422a147eaa4950cfcf0ad9596dc6e6a287bdfb06602a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460029e0bf534f510a4cd43b5011d63057da67b36e2b03cf6087b3bb8ec80fdbb9e63b5e361313e2dee669d1d5644696e5c8b4e71548ec9c6daf4c85873318d9b708f8d4441b4ef1c2ee50272e1767ac4f773a249eb663c7e37bc075951f3b52ede52a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600753c2d7a7193f908ed808903b7cfbc3e2f5a8d9fb380c054f887c0fe21b206d81406d25bb1d0ef6cb6a0feaf015f9e96c0d703e9bc5a0e956feff40fdf0f0d0ef9887f6f0a8673aad0c10d1bf6ae3ae5f0089c8f05d54e180e16030949a8f7782a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d0c3538d97ce8594f8a113d01728ac4b13549646557385c6b5263f4a67a0ba96e2e5f0ee3ac9c85fff368b4013eb43900be5e672375a3f3860298342a0325f05fa786562ceec7e61851571e8eaae35c7f0f4b2ada3ddb4d577c66ab50a1d6ceb2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600eb8636c254b4cec68eea4ef00a3ae43b6dba9c8f85958b514042d0764f66bc9b22e4205ff4d8d4427395df3aeb15fc840356a9eabacee3a23c8cd6aff517db05faf968e9e1468d58947b9217ed7aecf19ce457036a2b46e47c44e5a1d67e5b1f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a7038a3eb4b56c7414816925b26d1756706f2256596c99181ae8746a40cc32c5192c93a13997c9a11f31fa6d6263164e0c06034f0790400f1730c250f4054205fbcc3ce7dbd34cd50cb3563d2b2f62a5ff2bc847e6b6e79faee63257978f9c192a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600762a2c5d7849ffbd8c2560f7e6638ede211a0e59347bb1c7f629445e6af0d38826319366d3597f0773b49515907516f2e1894618f8517297b4624866c28e3e0ffc678679f11bc904720421273689e6826acb42b21e2c4c5c1d7bf532d89668412a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46006dbd8fd05b77b7556cef50351310ca989db725fedbe17fbdc079043ba50dd86f7fe96f621e1eafb13aaf097fed1d441a90e6462d4919a752441455bf49698a0dfd9e354ed59f35c917c42471588142e715ed2151e89ecaf98bb5d837515d74772a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460010d0c657b622c3927cb79d4e34d0d1608e0b4f12294277049528b4a3e4add5745ce5cf9048f16607fcba67325cf06c460d2766421210dbcbeff29b1f0dc39402ff0bf39c82ed573d48585448b0fc19ba3f6203806cb6b4a230848892c097a26f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d072ab3364ee35ffd547d78aba38e764805b6caeff708dcd806acd06ee2d684cccddbacd4c5d3ba73a6f6e03d3749635ee8eaff19137ec7dc686c46af8f76b07fff437ff18629bf1490e5c9b3ec6f1515d46bb9b2aeaa6e39e36611f2479b50d00") + fj := new(FullJustification) + rw := &bytes.Buffer{} + rw.Write(data) + err := fj.Decode(rw) + require.NoError(t, err) + t.Log(fj) + require.Equal(t, uint64(6971), fj.Round) + require.Equal(t, uint32(4635975), fj.Commit.Number) + require.Equal(t, common.MustHexToHash("0x2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f7022"), fj.Commit.Hash) + require.Equal(t, 199, len(fj.Commit.Precommits)) +} diff --git a/lib/scale/decode.go b/lib/scale/decode.go index c8dc52a882..e49a50a299 100644 --- a/lib/scale/decode.go +++ b/lib/scale/decode.go @@ -63,6 +63,7 @@ func (sd *Decoder) Decode(t interface{}) (out interface{}, err error) { out, err = sd.decodeUint128() case int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64: out, err = sd.DecodeFixedWidthInt(t) + fmt.Println("decoded int", out) case []byte, string: out, err = sd.DecodeByteArray() case bool: From 961dc66ef6ef54e83845656fe67aaff16e92fede Mon Sep 17 00:00:00 2001 From: noot Date: Thu, 15 Apr 2021 17:42:40 -0400 Subject: [PATCH 32/40] add justification signature verification if setID is known --- dot/network/sync.go | 2 +- dot/sync/interface.go | 2 +- dot/sync/syncer.go | 4 +- lib/grandpa/message_handler.go | 58 ++++++++++++++++++++++++----- lib/grandpa/message_handler_test.go | 11 ++++++ lib/grandpa/types_test.go | 3 +- 6 files changed, 65 insertions(+), 15 deletions(-) diff --git a/dot/network/sync.go b/dot/network/sync.go index 1434913c16..35918d2936 100644 --- a/dot/network/sync.go +++ b/dot/network/sync.go @@ -792,7 +792,7 @@ func (q *syncQueue) handleBlockAnnounceHandshake(blockNum uint32, from peer.ID) func (q *syncQueue) handleBlockAnnounce(msg *BlockAnnounceMessage, from peer.ID) { q.updatePeerScore(from, 1) - logger.Info("received BlockAnnounce", "message", msg, "from", from) + logger.Info("received BlockAnnounce", "number", msg.Number, "from", from) header, err := types.NewHeader(msg.ParentHash, msg.StateRoot, msg.ExtrinsicsRoot, msg.Number, msg.Digest) if err != nil { diff --git a/dot/sync/interface.go b/dot/sync/interface.go index e7724e7801..408974b3bf 100644 --- a/dot/sync/interface.go +++ b/dot/sync/interface.go @@ -80,5 +80,5 @@ type Verifier interface { } type FinalityGadget interface { - VerifyBlockJustification([]byte, *big.Int) error + VerifyBlockJustification([]byte) error } diff --git a/dot/sync/syncer.go b/dot/sync/syncer.go index a27b0a9a72..a62b9fe969 100644 --- a/dot/sync/syncer.go +++ b/dot/sync/syncer.go @@ -374,10 +374,10 @@ func (s *Service) handleJustification(header *types.Header, justification []byte return } - err := s.finalityGadget.VerifyBlockJustification(justification, header.Number) + err := s.finalityGadget.VerifyBlockJustification(justification) if err != nil { logger.Warn("failed to verify block justification", "hash", header.Hash(), "number", header.Number, "error", err) - // return err + return } err = s.blockState.SetFinalizedHash(header.Hash(), 0, 0) diff --git a/lib/grandpa/message_handler.go b/lib/grandpa/message_handler.go index 6852234da7..9e26ae0f68 100644 --- a/lib/grandpa/message_handler.go +++ b/lib/grandpa/message_handler.go @@ -18,9 +18,8 @@ package grandpa import ( "bytes" - //"fmt" - "math/big" "reflect" + "sync" "github.com/ChainSafe/gossamer/dot/network" "github.com/ChainSafe/gossamer/lib/common" @@ -32,15 +31,17 @@ import ( // MessageHandler handles GRANDPA consensus messages type MessageHandler struct { - grandpa *Service - blockState BlockState + grandpa *Service + blockState BlockState + blockNumToSetID *sync.Map // map[uint32]uint64 } // NewMessageHandler returns a new MessageHandler func NewMessageHandler(grandpa *Service, blockState BlockState) *MessageHandler { return &MessageHandler{ - grandpa: grandpa, - blockState: blockState, + grandpa: grandpa, + blockState: blockState, + blockNumToSetID: new(sync.Map), } } @@ -105,6 +106,8 @@ func (h *MessageHandler) handleNeighbourMessage(from peer.ID, msg *NeighbourMess // TODO: handle setID, round as well; track number->(setID, round) ? + logger.Info("got neighbour message", "number", msg.Number, "set id", msg.SetID, "round", msg.Round) + h.blockNumToSetID.Store(msg.Number, msg.SetID) h.grandpa.network.SendJustificationRequest(from, msg.Number) // head, err := h.grandpa.blockState.BestBlockNumber() @@ -421,9 +424,7 @@ func (h *MessageHandler) verifyJustification(just *Justification, round, setID u return nil } -func (s *Service) VerifyBlockJustification(justification []byte, number *big.Int) error { - logger.Info("verifying block justification" /*"justification", fmt.Sprintf("0x%x", justification), */, "len", len(justification), "number", number) - +func (s *Service) VerifyBlockJustification(justification []byte) error { r := &bytes.Buffer{} _, _ = r.Write(justification) fj := new(FullJustification) @@ -432,6 +433,43 @@ func (s *Service) VerifyBlockJustification(justification []byte, number *big.Int return err } - logger.Info("full justification", "round", fj.Round, "hash", fj.Commit.Hash, "number", fj.Commit.Number, "number sigs", len(fj.Commit.Precommits)) + logger.Info("full justification", "round", fj.Round, "hash", fj.Commit.Hash, "number", fj.Commit.Number, "sig count", len(fj.Commit.Precommits)) + + for _, just := range fj.Commit.Precommits { + // TODO: when catch up is done, we should know all the setIDs + s, has := s.messageHandler.blockNumToSetID.Load(fj.Commit.Number) + if !has { + continue + } + + setID := s.(uint64) + + // verify signature for each precommit + // TODO: verify authority is in set; this requires updating catch-up to get the right set + msg, err := scale.Encode(&FullVote{ + Stage: precommit, + Vote: just.Vote, + Round: fj.Round, + SetID: setID, + }) + if err != nil { + return err + } + + pk, err := ed25519.NewPublicKey(just.AuthorityID[:]) + if err != nil { + return err + } + + ok, err := pk.Verify(msg, just.Signature[:]) + if err != nil { + return err + } + + if !ok { + return ErrInvalidSignature + } + } + return nil } diff --git a/lib/grandpa/message_handler_test.go b/lib/grandpa/message_handler_test.go index 6f0e9b1e3c..3b5ef632e7 100644 --- a/lib/grandpa/message_handler_test.go +++ b/lib/grandpa/message_handler_test.go @@ -505,3 +505,14 @@ func TestMessageHandler_HandleCatchUpResponse(t *testing.T) { require.Nil(t, out) require.Equal(t, round+1, gs.state.round) } + +func TestMessageHandler_VerifyBlockJustification(t *testing.T) { + setID := uint64(450) + // data received from network + data := common.MustHexToBytes("0x3b1b0000000000002a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46001d032a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d5be226e7e7b6b54eb6c830ab47d4fa29bc228f46f176be79bd9b687d01ad7c4441f0c5b7f489462f29ba1641672519a2bbfd9162fb11d646bf1990b0c858e0e026905dab6c71c2a664e9ca8e4f066bdee9265ec45b7885ab14a797ffe1bee362a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c393865ec446b236036e3c846577c930568ea67f1cfe34048cb360d3892d9da61c4b1bd7ee31df96907662e10dae18646ceec91181c5e3dd97605b15f0bfc20d02aabb29f640813f718f1e7495f42415f742457517d536ba5de50990d182df252a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000d68aa7ce3c3902cca498cf6c0051d5b0f901f22092a427faa23227a19bc55eda954191acba6b5c82ec03543633facf3f84176ba3b860428d0e160b5a3a2db0802c70dcaf367b35740713c0e6761d88a2e26f9762e9715fac8bbeabedd8c5eca2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600756e9f4187f078001a7368eedc194df7ac7b1cae92827dae36d345061afa3d49df2041a41387501c5fd1b282707303aa3a7c820cce1b44c081c0051e7fc229070339088379600f5bd507277eb894aba34d2a8f27ce1964f41e60ef2a4142dc6e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ea73d78ec38c271aebb2c9f7ebebb5d3a99edf327bbf4d03b5c245c08d0c2cceaf0030ecb5e04152255f32e4edfbee970f3ab05607f6d8b718826e559bc18b0e03d105a30087d96b5f0684f6ded76f826b01dab61e4136e1d851a24f0088b5ed2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600681cd145a0375e5f72a737bbf92bc57c1e2b9429cf8b66bda13d0096e1898385ada09ccad93971530349592a430aa61331efb46c04a1bd9af7c34d5be9f2d30b044a5968644cf9f9b5d1c7f657a65343af076730a89b07b4995a494aaa0c967a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600620c51368e57db2cbf7e1edee0a746dc950c6da129ae5dbf81f3171fbba241f564b847fe376bf7a6f62d9bcefeba69d7d8b23000c40b7c9ec4f2868e6d748408049603b355867a2d3a1978a771b71b2bee4e8ae1f344d9ee034f47b01a48f89b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000dec8ecfc90b708446f112ad2730149b124d1d4b891edcc7f192ad4f6634fb1529471e902b590049ee6049361537752f8b4780e1900f5134f101039d617a710e069a481209b07b48c1d548d4af68b7584f95b32eb374be0682b342cd64d3406c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46008f6792c7ae77c3dbde17b000c86743bccd9f2247fb323044b12378a0b060f122674fc796d910fb2eaaf7512a5858ce467da7409a2e5fb14edc077c939efcc802088f8736e1cf2ea3d102f0a96ccf51222b8aa1f93d8e42947892f4395cb5477c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460043286aff05190884dc7be3f5b4e42728a1eb766c73cc9745f08322ed672ad944503ec837a379efef89b4c78a5aac7ffe8bd26e649eac482f5324280e259bcc030a790c3ba374fdcf1a1d457adcaa8fc01bb46eba8cae4e1940825364fe2d7ce02a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460025437e042850b2648ecd98937694a76d041c65b471a81344c55d9aae0650459daaa5f5e734ddf02ae030178ee8714c5f6cc241f8148eafeff450424fe62548040cd72d0e6daf0275f2eec96f6129243e01e255ad6b104ed500e614077d0710982a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600fecfa0a948e6cdb687849ea41dc46083ed8562ca4657d751f6c303ae0e35dba38e5c1550cfdd71011239872139770b8151eb4a04df948f666d43811009f12f0b0d49982b323ded139008e150fe97b0e85213a92b411c68f993aaa43ea62a7b282a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460063dc716d5616a3125fabc437ccc98b3e38bb2730a2564a11edf528552245f20657199cbc982288e2ff3127497a8a725852c35352ee3c0a2109d2e603c33c37000e1b3f7368c230b609c745aa7c2119b60218da3ade7e6a0dace3e337703c51992a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a07873b9fc9d6b71bba7749a8886a13c8d5f4c2b66907bcdf5fddbd88df3c6ece1d601799af6ffb7fb53cece0278df81bd34676ecd20a7494515eea9bb61af020ef0093d298f1adee0947ea81400adf0c27bd2e5c10b760d2c8be3b784e1fa002a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46006bc6c891010819529a51deedc4a7033de5e85aede0eb4103118110239cbfdad07327aeafc68acfa3b11872770653290f50c09658d416cb2534f0794ddc4b1f0c10d20c8996f5448cff82ded879bacf7534e4d499ec65f040024bd7c2402eba942a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460090c37f3814f7b66ad1008f43715aa91e3f7713285d44c9b82b88d7125a5a5be38f9c38675891de2455b276ea008321b742f420baa7b2ed8519918edbb8b9b90311addb386a5088eaafbe4c93e265b8c1459a8504f0d31a3799836224c6077d532a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460085114883f06b533bc6494706326c7191f224f0c086d0a21c8534269c3dd2d7a10c1beaffac80e05250d0c230aa3da0ebfbbd4fe47a8323766b09a8e3ba797b04124cb8ef23a2aa6f5d748070a84465b1cf4df73db94257d96c63df4b5ea8b80c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460037c148f4242ac604913f055679371442e9fdfd7f989ae9d73dbd5cd439a31f941e5c4a63f315a3b4c8faefe537dd51fed26ed948d5cc0110c8c89775e5a2a70914b432d81cead21854d70b19f9691b3d2a8ca7862b64a71f4f3172387078c4642a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d3acc5151d7a54944aa00350c835fed6f9dfb4f12b8124903d301a959bd63f087d19016a6f18b732d58d37f93ccdc5a9a27511054f54436dc8bf8a12e1a06c03155763a153e0c02b2eef1d8a9cd8a50a9eaac9d3af7ad1e559b1e2320b521e1c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46007fd97784bffbeb89e3fc4053e6667d73a24a15e5a864f8264d544f93b8d4938a5e57b079247499e1d0de9cfe038754c930d1427f491ba0f8a956c56b6abdc409172fa913b2ba5833a78ed51e13692e9d1045e7516b5ac738e6745c217f49fc1d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000b091d7bd11344b81890eaa9eb51f38fef522d5b38e05449ab04cf720c389c9a9669d8cf69411500ba2a78a4f7783d75c424b0ca60e5a564728a7908b90dec0918a1a6e0e81ae2cf403c328d1195eb73e9b78091bf1e33be4dc1e29736eaa0382a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a3aeb6a64836a35659a253533ca38c5e8f5dec712d764bbbfaaf079f671c4487345d232be14aead855a06873549f712ded1127217560fb315be740e2071812061c67ae7a3c999b95732bdfe95fa8670aac497a1027aba327a1438bc097fa700a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009ed7a0e3b08dafbdfa66248a9abe9f6bdaad694b2c8b1a85c2fb8ba387895d11890d482eea4078d1049373c9a7d95123093e2e63d4472c186c18ae7f47c310071d5ca234f8eeb85fedd882230378855b4e7390a78a802f0be7230156a8ce624c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a5b924123457be20ebb04eb12a248f693620331120a053e1837a343460769d6edb3316966f5784ed15e5e8f6105f8af34ad0bd6a8f11aef6f79eeec6e658090b1dc6da633d00a38d0d0b44056980bafbd624f2e1067027cd2b09267acfb70e9c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ed204b31f36a1c06fb6b69e206525afbb5449064e39aa477602ea878ac65a4e81e24fc35c1a8ba764ed1d75200a2c5681a43da66a8096349365a9302706d1d041e0695d7beee6c4a67c9a1a75f89f3f858a50a09ce1b96a08e685a49e20e7e442a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600124362e041e4e25522fef30f7267b7d52338dabb1a2bfa21d163b06df2261cf465067921371fa5be83279004e174d5cf5bc79102e2208139bf62892c64521e0b20d4e595c50bb9558dad9be6a8784492bbbfc754b9c5fae17edf4f8a84e8b4712a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46002d19070e68ddfa34d42b207919fcdf1583ffa6458a82e2680ee5c880ff145e4b6a18b00c1c683c511e196d7b24cf4abd508f6522ee3e8eb8b335a42a0f7dfc0121699d6780c6aa58e433f27d36b7f1ea5afd4fa404d8254bd62ed5b17c3f75672a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46002bd9ee43aaa4cd4691315b505af64f05fdb9d70e1fe5f8c7d9e536bcf723b67035239765e727d4e1146c786c97b1e900cd33d8c25440d9e38907a6d0af3d0602221a227686d900f6be260a84e81bab79adcac88ee2ceb1f4efe83e0be86fc88a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46001b561a74a01dd3e3957d7930fae671d9a107a0e36b1ab1fe38a2acecf24ce281f0270b516c0d73ca05c1e7e7b6b5870e973ed04f8b4b2d8f486731e2871c5d012275aebac33745b7c0f47957b0f9c1dab14d6dc5cbece544e26830248b9b638d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600f394746cbe78e56e004009e2f374bc8fbca67b5228f33d328ba173673bc7ebdd31669b9272de648f7d19b7b79ea68937a6bcc395695c03c8afd36647bf20f70422b1625c123c72fa70ee375da98f15cf1326b254ef833a27ffaccbbe81e993d42a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460043359e329c56b481de900abf705e49957358bd8261d5215ac63a96b9098eb07d68b12aa67f2e4559fe6fa838233795e32e9f08b1680f89fa7db4da2cbf40010d23192a82e612c4b047a6efe2c0b20d6b8300a81639a6d1578bb94a66a721649f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ef4e7891ef4f0703d2ad1833ba02e1ebc743fd656dc4e0d5b2e9346694f142ab262a9143fda3c3a199e8f57820192a49648a0872e7b1de04bbc101bcbfd0ab0524796621f90c2d9f2d12a962e14f9852757f89de7b1f66c22506321ef38266bf2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d65939fa1869e5e2e2ed5800cac361424eb5449365fee6914719dcb768267f37be9efb0a21b1720308dd5961e3ad43b00eae969a117b8e1121ba36362f0b220225d5c2769f768059b45c6843d37949ab7866678eab000a5e6340e30d1dba306e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600aa0831e40a2cbc1ba04377e7cf9a0c6f5fb037c9516e4e9cd4d6ad0564953b99c8ab329e5f667f7fdaed70e9b8d3b4123e6ed9249b4e999b41dbb82ef04ce50d25ef09e15fe616e090335b55cf729832e2c9f562428fcb00fd3e3bed2dde843a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46001396a84de1c3a492a93b29d74c536ea8231bd01657a1621a3af64b5993383781947c771948a7166dd9d21b4c34a36208dd102d77f3f6c99ef85037ccd4fb5b09271f0ce7fd9e0460b2b5f68afb847a80ea0821bf168dceeca396a1fe35754af52a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600804dd690fd66f6c15a74a4c1c8be43b57f0eba4d45d75f66777a5204a506c6344e189d33c41ee6085420683807345324cf24602b2eac5a307603a1dfeb71040d27a748182c49d7c4ec091c9589da5c9c35fa99ac8ffa4d613129dd4d08e9f1122a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c91a2c346304d33ac0121707248a0560bb32b4446fb0f2d29c6c4f27f4073bf6455e662af07a4c693f199a1b850fa794d45d0640d33883e08681b525f99bfd0627e96e8217ce52d55d5cfe97580a03b8f9ffd41e72ad863300db45749088faed2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d91892fba8d2b41a62f81cc5dc225fd193aa215c9941dfacb197f7b11262609953c57726361b6c1458d500cdc90eed86c0d9ae4cb98ec1457a438af9e706d40e297b86aac8a8843ef944f9633404cf307ae264f031f6dc803bd93c7ea69889742a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46002c85b6e9e5c28ccc33e4d59857cdc0afd0e4f4d1c2218dd018123af7d96ec3b08e49ca35a5231431e2663db3a178dbbe8a1a4f4c0005014770c0cc85876a88062ae745fd0181edf42f9313bef7ddc5d62ae8289212df03f8f73e210e0ed907562a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e4ea8f7d9584724897151d5a1412a73e42c96aecbd84020e52770c1767e8f807befe8dd28f7fc8cb12addd1f1b72d6c58e418702111cd708ce7da9f61574d60d2ba3f6bd3084ac7d9448dd341d4c7ca8c991b659c85f27cc68e80fe7cbd73e422a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46008c5d3fb36a8da0e1770f923bfbac75470ec6408c06f0cb1be5cdb5893a2c09eb1e370073aac480ee2ec8b9c3a9bea659869a4328e8e441851bfb846610ddaa072c3ce4d7102f4236e0df2e8f5797f6dc3d2e6f0e57d373c9a4b89b21d4d228682a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b6e743e03f526851e054ff4766ad6db181f83fbe2370a51431fb8c310f1cbf3788d99480e9fffd8164d31cad3db297d9b26c34523aa25d5c7f83e79b6c02390c2dad5b2212ee688f2eeb9ca1fb6a90574f006dc1c6680ac3a8523363a248940b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460020716a5266a471356b3780eeaf4ad7dc37f50352ece4e1416bbe47b5b2606bb59dbcb1d270aecdfa6eece9d3e8f0862966ea7c7643cf1c05945fc4a56ac65902304d970cf7e07987f8d7a500edaeb1c0973de1ca588512e5d9f268a9ec0874ad2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ed2d6b378c40cb99f357b0458085a0b0504c1622adf0316050396479b08ef1859c2978fa15b4e53fe911857add9ec253cfc8d6b949abaca4437824f324c1070e360e744fe76d5d471445ce6ad9587af67392b8d960d7715dc0efb43c698465a52a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600013ddc65f819aa4c592cbbd59ab1f4a4bb27c3a4b717e19d409fde187da9e2e7dd6c9c72ecb1bb772c112f3229dc5140c202ab9341d04a14dc2c24a2b4e34a0936ea5662f48dd131b91defa20bde06049edcee2982714f78519fb64450d1b62d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600087672b184ff663524bfc5d05631172d3eff695a117cf6c8884053e97697b48dc255835918bcc10f915ff4d8fc3470ff5143eb433c00e2dfc8b46db2ff16360e37035dbee1de6bef71e401cd19d0a26cb8b69cf719ae340ea53c9677d6a9aaae2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a2d8a30c22e7f78ea1a477a0d7971533020276d5d10bbbf530f31ec5d9ae688108c2b4535dcf9204604406f21d104131e88d9c8399151bb67c14a790945ab30439712f967c4e06d284d6da4735cf2823c1f770674b668f7bc896071c3fd41eda2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46007770937b4a1d62bc1df63b1f80162728b198d26492e93765025f9c71dcf992482c9e85ef9100f7fb89931c0d2fd82d063e24f7dcb3d28e549d1d45d8a91f2c013a2f175229490c3169b5942260eb2572198b494e63c984dc364d3f48aaa7bf012a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600f7f152e001649cf1353540ca716715bad9fb6703c1f34b78673baccc8027c3885a6c9660cce85fbf2b2ab696220a2e417964c91912b030a0861ab7ca541daf073d75d8fd47f1074a78cc88248f1f6b9ea6cab42ccb676f94226e3fb5e16249c12a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600dc58eb9647b223730213a6f217010fc35556325166e03edd470b04b9b38f97a98acc1695a71120f06b2beda919889a5c2de43f40f9954894ba861cc2082393003db99a9882de9b7666591c1f9f2d87685a2569bd0c611358d6e144a014c5612d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460070846e878685dbe30ef68636052fcb92da91ff77ad2f7839edab14ff2f5a28cc27e0c464d175c995647ca3a0cedb2ec732e5732789ad3684ec4f08d94034c3023ded8aac5210831cbcc4d0cf1f96f2c711afc90a6f6f35a4ffb766be1dfbaee32a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600674883cb865697effe33d717b63eb6746fd144e8dce5df79892d453b51fa49cdce9eee1500be36a245e33a9254ce65631b0049218fa305c2e9d5ee1a8855b00b401def7965bb1e8373fbbb6561d5ca51832cbb92b67b0bb08c407d2c19cca96e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b4d95db0f1cd96e18f87a0c67e71cf7b8d2facf75d10671bbbc9f7e5642a4c2223100eae4e64a54ca179525b19a763fdf624d78e209f7268d7cf4cf70788200f406c4081abf0f3a151dcaecc6b7ca1a7489cfa1810cb02c8cb249bb67dae09302a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600bda3c89a9f0257273f01c786914fcac144189a20db45d05fb42ded836fe2067362fd1baa2ec4727a7d8d72530474f60a39070cb0f2af31c372a511c55ef2b20a41ef4a31eb7dc1e01f4630604e1908e644d7cdee3f66a60f98d6d59605326f8b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a8bec76286b65de5693a2cbc309bc3c301bd6812eb493113449e9d30d39b60ee58a4d918f127d54522e74d6c6167ed99dc3b10e8aa64f3e4e5a2a497e68d9e0542d0c88f0accb5117e31bc057d4f277a38e01f2325f02d0a9f647db09b67cf202a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a243df7ec7aea8df7944c4474fe09c1a2a006f697e29d15300da8a51ac7258244501ea6c87983d0ec4643c051ee1489087a3017a63c2ac3b7d06aaadb1b88a0943701217f2650ade985ac46e38e548bc1850bf3d895eb6c1ecf42d9e61b788c82a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46005305ec8c29ed3ef51119c80c144ea8714321a31e533c0ed9a1c03a781e4401a279393f06e4e0f0dfd7f21b523a808e0aaf659aa587063d7017dab6ec8df7810543a9a2915f377cd4943f602be38c3bd6ef39e91562c09f18fb672884b4bf8eab2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600fc691469543554a61c6bfbf5d554ebf7cb596ac78579aa1b8d6b0c7e52f8295441a4bb450d8fe1d2579a51986d3f7a6de52a34690afe34fe5699a6bdc1fee103452185859ba92a24c1f836952da8d11c9425c9b35eec979744a345d2276de3ee2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600df5b15104aba6d1d4d70f19eee2d04e22056616f0b54011fc3f15449aadd958124dabcfac319655f72688f14130808f3dd12653e4f948034398589143c24fd0d4532b59911aec8842fd910a35fad7c6210b3c1ac73c7c9799963c635e2b562882a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b11b2845d75099b35ce971277fb82b0a8f2be06f40d2b759a291a095b3ae6f9655279b922667119308fc3d673e3d6f0063bce32f0b2c5370071214ef58b20106469947dc7cb086bd75216dcff8c8ddbbff5e0f112ba397d71b6bc2980bb6cf002a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c589c74c41ac4ba4e067df4a152dd5f34706f74828bbc1166a7ea1863e411058683a6d3b3c27db8540a8538fc50944dd9b3c8dfaa5f49fce6c5aeed834d8d504469e0a875597428e25f79157e441e933f3fff7d2afee478a9ac6e1903e4d4d1d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c0a2143bff781fde49d6989df90169579c588192fb669219f815f47117459f9a94ddadb68a90796f95186e7ec641e6973bc165d0d8b2fe651c9e563551bf960b472bdd18c1be48d5e3c40aa093db02e45c4bfe1a5b62a3884fd65be8eca3c6022a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a5f5af2141ecc0f6a323988e7d8ecdd8b609fb9379c20d108e218ff6c8aa7fc91ae54bde791fdde4d103c43299c033a649e69c8fc933db42b858a3730dc39a0a493c604e7f0a7cd6abf370c10b90eb8ff9d432bab420bdd9fe3da656d5e9b6752a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a1f8ef2a102fa613697a17e7135f7f7e429107d27a74ab15f0f2a46afed1e3257ce683c09017e25cc7abd447e06891350568aace957b76c5a2a8cbd9c0c8c800494d65e6b674eee86acf5ed70b45b37d7498b8c1d05cc0baa0d6473fdb596bfe2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600eda30d06498bcca078360129b8d46b6d8af3b8b9ddb252a00dced827e3b4c86373e299dae8720f622be9bab21766fc970ad786bc5733ec208b0105910b993c01496826b538d97906e14d417b48598d7d591a483ab5f4c6786cd0b96239c2f4cf2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009aed65d04eaf5a5c215e4f3cbf9144b83bcaa41c037e42ee788229ce622b28d3b2ec91c3b1b609aff0108bc920e0f6f43266e1b47598bc99ff5410e8af2aff024b35f8352f2aa4dac039d347d37488de305c7ef6e7aed2dbc3526f537efa2a3a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000c18a3ea31cd3830207a76b80da87192c61c608490c9c8826d4c860f8b39b2962d258dd43bcac5368fc2d9abb0793c188f6197f0a6d4c3346d2e53c6641086014e319a863a469525687eb7dfbc8924054c35d0599b6ba4dc94702edce01671a22a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46005bdabb13bf58fbbe4553d4af8f8fe9ebd0a4aa99e0cf8271621c9975aefa2a5f35ce96bd2897eef6044c3eee4aa475af4288d660c1d64576a770985b9c74890350811bd4b7dd8a5ed9193d4e2d19248020c3d334249a809fb96234f058ff90fe2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600bb6766e42e7a117d09953e5b2505c6720b77655c557be72254b0d946dd0670b94a18a9529dd8c96404596e9b6161a0a5e94f5bce6c190e733da9d4b5504af60451237bd7fe895abb96631987f0f82a41ef6d56f217dd1ef8aef76e9af4e559862a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b1042015c7048dba6b6aec2a38267657d0f221d0a862514b91b4714dab337eb41a014683101b18d98bda3cb4860749f10e5bc3a1927946e1c96c3c937532800b51f980443e5fb020d8498ff88e49fc55f1581d7eed1c66bd7653a4f5a1ba18662a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e17cf09d6059f3140f99a05905c17f597bf7fd8685db6c61fdeb0f91f0d3686a1e218daff23f1964ca2e7f983efad0d0714ed9cb5b23dcc2d428c03cc10f1a00543a164d12ac3f4ce00b176cd41a7af343c56e4ff445a2634d74f3b182c9c7352a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46004c4a8f2ee83d7226b58b1415f57cf93574021a6e828b1852b30faaf97fd75325424bc67043a889d41b4f1010e40e2ce911d02ffb252dbb1f9b15f0936fd9cc0f56682332630f5dd42f160f1e3475d1881b2adab83023216892f538efa1e0e66f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460095af4f1a0b89f8e6458417dc977f40bd63d40cb16c2411f5305f3a12cb44997ddcca4c14c350296b4d2a30cafb1e272d22b772b0ba1d85ead112b93d82a9980f57a7cd79d0feac648df60b94b7eccde724eb7e473fa2368eb5b88181b030239b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46005cfb9e1d0f208dcb64abae8d57bf8bbaeb4495e61da051a28d6247ee4eb02dfe0a5bcbe63a8c7869168caaa8da5ccad40e1d0c64b4c802cd9d41133cdd88f30557ae0c85ebaf333a6ec3251f577cab910cce072f238d1e50046322b83bbc0dd52a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460051eb234aec184ded0063029767fcb3e32d46bfba803f5301e932a4ce8ebfaacbfda1480f48991d6259401a5962804e2949be93d9f96ab7303b7367c6dcf46c0f584b81d2c35f01525cb80a02c259565f0becfa7d1651dd4c313358d339f32d472a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460061ad5b7dcd93f5ac2673bc36633d2a612584685c1ce6f1bab11561a0135ec7b3c20e2c88ae6ca855d4109f6f9892c41ed14bc38b549f00f823486a3e81b22f0459522ea548446804370cb91e2c4eccd2599a018202b0e6c04b7643b143707a382a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600278975698f785c3012217bffdfd48882e43033099ad968e849a14a962e2d6e538d1dacac16df19a0831f52daefbd4ba7c6af77cb7b4a343c575382858d1bcb07596004e838b7d90408d46de62b01e13e631575f348bc59f926286aae4c88702a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d8aa4ba9183770f61c39668721066eabe70fd41d30110134ebcd703a7bb4ea5f0288e28ac58a12dbce2ace9546b0fc28f38c5c467a33cb0de07ce8c5ac14a30b59bba625d971d505a9c7d7c2f3ad69203e69e3e5e1fc1e4905fae7703fa19e032a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000d40e4163b524c69ddb59641228c09fbcb4bff1ed875161e54bb10bee1ee21e618ff0715d5fc9e9ed1f32dc49e01926b4728c8b2664f2aa535929ba72080c0055b29e3e31323bbb46b3d1c53639ef72499c58806c7f4cb6d2e8343e961bb6e3f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600bd7478f49bf6d8d87ef834bb9653a3f0bda54dc653fe7890c69882abaf429231fed0b99dc99d2bd4f504c32e2008b135004c60e2045c7560e0c0c21c144925085b379072ec1f3f70b4650979a47b24f9b080c03450f7e9587d92cb599fcf4d6b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600117c8995b21aab23931a24670b8a885ad04493aa804a80b51572f5d29cf04d6e7d8f9adebc0cfdb1df0e4414a199398fab565d55fa0e32d9f70b828de39fc3005cd0c7b21d4834c5a41a80f7d421c7d0297e42eb409a524d33aa7557df13adaa2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46003d5d559b960da2c238fdb2609b45d125f1940fc8c6e4934d2888080b5f2c7f7da8c34528130afbcdac99c0364b88b5a329c8b742bbcfe91c7e96556b03e70907607b38d0d0b1ce290ae681fd3a7fe09b4299117c3d2f9a0a52c8a3076e268c132a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46008152931c4a5feea2e39f656662f3515ec32256c57aab6ab75c6f0b6f5bd50b508a743ff424f2eb046f1a923486a0566034023cb34413172ecb1e639634c9f00d6088164a2d219f2069d1d51e06c2b4eb70b85be9071afd6e99a92f1232ac645b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009226a52a4f9fd97a75c6c808a961744e731656160509b88038c3e7ba2e88f472b2f8fc5f865043e76558fabfd6b28dc0f8aad8a50d5744072a4bfa9a3d78200b62de44b8fae34cdd6d4df47fa7d420bf8513f35e3f63c66c4bf699675edc33e52a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460014c6bb8be7effc1ee39f3cccad774fc1e71212f75ce887f1b4bbd7184de4691c9c1cea72845d9830bb86b24464fdf7286936ed6ab238adaf78773b335057b60c64bffa0b9f160a67beb033610656b80d6c9342797fd983375e27f91ebc7e6e0c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009dbeeab2dd1d70ae4993bda61bfede77c378865b264b9620d17da5320141d749bdbc6ae1b2251e68f79ebd445b5b93d8a5b2fc5a2257c307ebea37f1d45c740e65bef6e1f3f6609291199f6e0940c80e559588368e2031086afc1730e0584da22a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46004ff39ee2863945517eefde66d42c4ec96da046097b7efbfde7c3111a6c06ce97f552f33374f43a723709a4b3a0cbf6b226af61515805c74fce2527bbe828860b665da232ade423c7280c6552e6d0ec8782f9bc742c0b030fc08e9dbd3ca5d3862a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a363544def43bec4ab3cbf8aa4b637198f7cc7bd89c2a402d40c6c10aed6a9f231ec0ae946d8d21c4563ccbfc1e6d6ec73063aeace3397cfe34f6e78e378220666ed84a2c0bff828dcdd1d82463f642666dfeade46c020621490886912bf5e022a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c2ef4b28110bca12507a617a321e2e5e3abedfa0583e4bd9f320b02d5984f62caff3a28a627819a2f2d54f01f80713248a1e5f0bfaaf8b261a3ceee530b89b00677358fc648638cbd854d2a009dd39b8508dd3047d0c5f13bb403a64d053ad032a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009d76b8b8deb037178f7686154d9228969aa30d5751ad1c2e0097c97ca39f3686a91afba09d50e443f75a8ea25f41f1e63daa1a5e0026e099f9241ba7aa38600068f8fcc2977c5fdb4ee46f234fdcec2f60a22f9c63ac7091b8ded3ec441df9bd2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000b027d479cd33d8d57c998621de2cb6dfffd320f53d27585d3db9aa44c59bec3ecb584f003e5571e488cd92a3485ef2b740c27861a5d780571dfe10c07307a0e69470b335262280ce95a164f7963af49e41ad6173f8db9e3faaf3ab54a8c50322a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009bcb275c92fdc035bc43aada4277e97139993270fd0ab3661350adbb09304b03246c37b02500355654ced15912e9431858d85a741d29f5f9ff65ac597fb3700b6d14338abf210592babaddf4a584f2ddc06c0d333ba8a9f5e284c3be59c828512a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46005c4b0f868037118206170a6b1d858ea81047028e10bddbaba24b1b6722ffadeac7a10dfd3cd1c2cdfd196ae3da5da59a0dd858f6ce15aeb884d39e100a6619096d28713cf7af6e13d24dc67d4540225f637f0384e58e2710d9e294e7473edc2f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600cad6e6d6beae1a9121a104e512d5d9d89f60d0aa1a14e89ad906e5a875cea4d6290956f0f6acfca1f0faa651886556d59ad2d5311f840eda172fdf0fb9ee360b6f7a1a4f9d8c054907f3b6b946a59525199dbcffe9fd3ba89612a5d4b548bede2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460096490d82522f60cceb6f64fe9d7be13cb9d3d5426f34d9c6dc7cc7fa10b51705ad8c0fb4cb8d1df8520af798f4cae294b765f4c918f75f8b80a9110ed4fe2e0674998004d06285d2b8d99a87a9fef38f0fc3109f4919006b8d1831b0dc59b3782a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460012d3db9c5db15a349f1152cfd3edecc7cbcc6b7d6e0170379bbf12e29cbed42c9cebd9bf55832bab8dfe4f761017aa068be5f84101c4d7bccfcb929b34c9ac0d74a47c40733f76b6d0502b67a709fb5e5cbb263b180fa0fba800d9d6758207362a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e1923130437dc6a03eccefc51c3717c7f95851aa9ce0ad76ba0a22b5dc4dfaefa9cf456eef9a10b9ffa1c33a98cae77d6b16df72b1ec7bd09b53d62dac15d40874a8906dd2888c9e7edea13ba3c42ad4d833f4a5de43d5ce0d9b12c654ab2e872a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600cc61c46e1babadb7667708e3ed03990398d9bf080ba41c1fdf67b4f51dcdb7e991b6918a6aebaf36df53f828f950a7698a21eeb1c640f809281a08424776cc0674db730277b3ea5a14e52fb0ebca202e56876c666aa160f623a4ca411b7ced702a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d9838ce8807f0de4ac9b15d648031b40e5ce436051756b3b4c852fe07c67715cab2b3cc237fb7f72e95f970760d9e36ece5107e07f41b43dafb771a42517d4027589ac4c910bd1e08bdb36f0236c05cd20349d62e07bd34d89afd21efe56b5fd2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c3136be04b86dbd5ea6d9151f51d16b47aab73e059569dc775951b16a71ac4fa890bba142e61bd67470cd1fac95ed42da2924bf82826caf7c70c3469acba540a7a92827270cfa82f16145a44f9bdd9ff5038ef1b665dd520a2e61db9749094962a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ffac47e7232095f25fdd02a1a5dfdbf3cb9f9c999881194fa50ffb9a6f6cb00db0897043b0d6542770710c727ae5dbbd231eba181ff1c097ade5be59d01389077be4c78d01b0569bdba6cdf39abb6e02f591942133aa1db230033aa48d18fd552a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c550613a2aa3703d95d343186fb5b9ea3e5dbc71389f842cca278d8a83f7735655df193eaa580de1a0f02082385ebf6a1148d93ce610a165135448a83e6a55017cb4b69ff1f333baa130c02d83fb533cb84fc47155a707b7978e8455431f4acf2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600113efdc304c9dedd0baf19c9b54c235fb707a91a7b6730fae65d3860e960d6f6e757109e78422f7d568864de98a9817fcd1d1c435c9a73f13680525da4694b027d32388dbca301421fe038986d97764ed933927b5b74b91ea5320371a31ef12f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600cd7aa2897e1d6d13af72130d66d65248a25ac7003734db33c3d5c59f344ba6bacdaaba9836f959a6ffd140153f8592a0a30146a9ddeb34b75184c17f8cfae10d7dea0d8ef4c3c5dccd46e8d2af6b2a85e8b36156cb97002a43ebee8229b82bff2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460097ba00cbc15f7d2e6c06f7113afeb36984a2322f18960c5fbb8d96833c6aeba2f7eb6f2b485aa1e470bddfe489527dd782b21d3d70c6a49321202f763bba82007e7ddc9a34f7d5e0acd418405ee795740b3a004c977a9ba4ee103755c05aa76a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c42a2e034b4fd25cd46ffe153c8ff0f6cb764ba36c36b2dc125ce88d8593e5e8ddd636b246e1852e3fd20dee2a7d5b026b89aad6cc9080b4042cdb101b05ae057f406fa8642d4d3834434465fe6b1815ecf25a5875e0334010bdfab4768fcbdf2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460059b47b5455aa16792cef813dafa11c7e7955b33fcf0c39a67b85a64e620403aac36542cea1ea2d2015bdc71c09b9c60c33d77bd28c35a69a656c83743b5e63087f4786d6fc06a89b65579200e5d1bd63caeab25893891f06597232389866b4152a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46001f8980114bf02490af98132d5fd4e13b65eb990e39e0dff52f9e152754b83a19f750eb812a63a4255d621cbc095e504e54506c3a1399d421a36e77364d40c20f7f57f0e89ff23959dbdffc8f66bc433fe9849f7a9c335e0601dd59812db059862a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b90eacba52195a184b96f5f19948facb9d5cfff012278471ec8c2ff572e70fb3040cf595588bba91973fc2163e509e0686f56039fb15790ba172aa96ee39d80a818a546c630b881c2161588866965649678cda0f4110cccc3533d0f20e5e41202a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d679230a0317ac247bbbb605e6460aca06ff81c1dc65c5ea9b00d19b3df57265ce079a8be66d5d4047f1a5d81709dec9efa2f675536d3cdfdbe68fd426981d038930b6c9661fb81a752c137a40cc8091480743856cd77ac965f4f6979ca8f30f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46006869e6d4ca689eed4551201be9c4fd6024e38ec95ca0a6125e156b95f4f18ec4bd6b49e1ff7f5397a6d6ba77c27866d9d089eb660a0e7efdc61a8042dc4b8f028934b9a6c38dd420aefa1e115c839fad7a71147a6efc77ab593485c3b07576ac2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460038c61e1249a82da4c868ae51928fb4d21f980e96a9d32887fb9c4f41d2c6032c03c54e5e45914fa716c1e256af33758a5c7d333fa780c771e0e6f66cb0427b08895238f7e22e70f3f59aab49819d5237d6776eeb65c2d5a925b62ddc8d3f0c772a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b819531987ad0190e399e2d1c87559917590850870b8f938da3574953e6cfc7e0b6c6fdce91bdb0e0787c3bd2bdf1541eafb86da210ca85ae2bf6fb0a6fc69038c2e1ccb3645a2466ae46d3f3743ed89c0831a74720eb5c4d8490b1e04e5bd4d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460075c07804bad1b0a7f1211718a6d644cb500bc66416b6004f80903b51b3efc2617d1db5175485a2ede4d7bff6f0e8cf067630f11d62af1561032380150fef6b018d67a0c4b47eb0e087f2cc4c3973b59c9ecb729fc960775d76138ab09799466e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600bfeb58aeb94d24af2fa1fd94a4b9869d40982e3f21696fb70dc9f03a9b80f5d6389e4f4f11852e43900e505c282c8c8110545d547fd0b18cbd88707e8a5fbc058f02169e09026a2500b440eb43eccec58a6a32d9f5cd9644fcf26f5e0c9692632a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600601161377f0932fc08d9127736fbf0052b763f365aba81899e05c804a1c1fe945d13052e264c550e3524d6430d69700b016113b65b6e6e73b22918fac8cba7008f371abcaa2d51351b62e5b400f52086ef7803c1a1351563c5f405a6ac054fb52a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460082af14bf589502d2963520eca5863fe7498a982e11941fe25b5da9349de59cd245ab8b510a09795ab701ebf008eeaa812c877afcebf54e70d95d45ede4839305908ad289176561ffd2c95f7cc8a2ab5c6a0effd161e3aeb1732140aa501edce12a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c19eeac39b52c944ff50ce5bc10e9726646881c798c6ee4a88de3dda17739fb11742e5e35b5c7222ccc4cf95869e91947b993e668471ab724c2c05fa3a37740b926ca2461d028a766de4efe3d8412bd08adc97c9bb3aa28c07a702ca82ac26a12a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c7eeccb5b57eaa678b568d72fd1459886758029cacf2d33f944d382f8945c56ebfc5adf4322f768116fcce9596469910f7fa214568e2ff581f2e583beb633d0a92c3f7a560edfc7927df1631ff35be263beee400f187c090dd02e4a5801a82102a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460019fe510b724fd4773b7fb1c739f1b0b9313d6ac2f6341f693dd1f898bb10bf53bd09fc398d305c55d598588be1f8b9727fa41a086c4d4f0835aeaf26ed50d30098077019fe554385e0cf6e0f3983b510cb37dea7d42f19ce0eccd86e3e147d352a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46003abdcc324544ba69a1b63ca3db2ac95b35e05d37fa96ae24d6a215db9943ea817a5eae59006784a8e1a6ed3ee554035edc0ca54ab4b04b7f95b48e3548ed36049944dcec71efeb5186d55e2d5acec943bb28d1ee9f84db3a2e2177c3d7ee9b842a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46002a4d4e13d1942c7f953c793731ab5c12a45d0a25826281c2d23eb23a1b74f7c40698bbae7c0f032b74ea44c71cd4f550b9295058f2f1dbd7b6f966b00bea110e998b7545adab70a645393ff37884688b9960c8c04989e8575602625d6cc344f12a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b70a4ca5731238626ccbe30d9e740afa075f92a8d217fd224301d1415dca12556a89e0a2797bb2098cd0ae62bba8c4ff98e7d2089092e729bb27c039b71c15039af39dbe3ec236efd64ad0c099648dac388e424ddaebb0cba1793b1ffb9a5e2a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600dc57933c5a5a2ac87b5b72d60ebb537a7bf9bfa2da6e0d45cd322d5408800d7cb1555298dfe74b847e69ffb237754897847afcc0dcafc80bae56209add6f760d9d10dbeb235ce888f8ed0279a2dea721df2f91c3809999301774fc2a5f4272ec2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b0cdd53676e135af460bc95ec069717e7b78747b262d5140a1a193b855d9fbb159cb499b7fda361fd80b7743d10dd383526b70feb8dbc0b06badc2b911fe22009ec49b1bf8a4c76bff00c0aa80adebe0e9249e37fd977babb7be36028b65b00d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d5cdd8a7eb544a9479ffbb0bc74072ba782768cb615ebaa2e39bed4784b4fab6a86c4db944a889ed7dda78b2c0b5cabe5f73c423376ac92c379202a6814fce069f107b3106c7f7570af198895d8314c9deb526a2311da652e5cc2c049212c0c92a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460033038e1fbc0dcaf9212abe66e29d423b659cca10395f78a86774250986079aed49f81381e1dd793a19cb7e0120b636ccc098604815be2e9e2950a9fc857a0f02a6c5e1d8748c139b8d98fc3bc12c3e3a37ac8e0b85847090093b99d0a3c4b9212a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c9730b655bbad75eedda6adc2f28a30882d8986f28bbb6d2f0eb8f367ae64a9eeb9ff25365ccc11c67db4763dd53f1eb6345169be20199da23559be607ccfd0da6d3f9c048fe3ba16d0edc38626afc398f2c032a6790690d1de77ecc6a65b42d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600feafbf9f8ca6a14c50f918d8fb35a0b296e9308ae8b6c4ed7147d2aad912aea13fe7d2639829a5ed64c309c573dba88894374fd480f595bc1d7018ec8b37ae0ea70673aa688422f1775915bc89d6ef922db7f9bef28218f11bb30996a406d9ed2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c21be32aa3713d12b85ad0f157e3877154384e4049d5ac28a98bf19b4e57e1c822e6350c5ca96658ad6f9887440dcf967085e2e4008346b3cb950d46964aad0caae2b6fd7dd2550a95d384a06011091f788cf7218762e59e686ca94fd09422ab2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460050ffac3d9c7f04901bd6dccb14601d1c6a9b0c80587e74eefbf625893e351cf0dc1d4aa08b3030ad5398a461c065186138e50ce56a92d5e46eab62701a036a0aac1daf1e3e59dae210f2b56a2b194bbad084b0c1cf492289b2f03289c635d68b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e7b07a35b9abac9905f77d01f690f01cadbd540fa75c8147808dc84f5655b17d20951de43322a74e3e9cafd48351d58ea0aab31695c3df00d5fa1bb33a997d0dacab0d24dd65b0df17ef62e2b6faca0662890de57163550c52416630850787292a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600881b5c1abdf8a9f8ef9d9d9b27bd9eed35f8930e8ec3bad5ba82490c139005cc78b856fb9c49eafa6581988f7fca348ad60a75723db53122c73d25e858284806accc6b239b9c0c62f0fd1c9cd9f90b823b41d955865bac175488ed4e7da408682a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009edc50b04aedd78e4771634a8333fcf75cb4519e3da0603bd6203475d78a3524dc9d5e6e1b671bf4f75e30f7e1c8bf044bd4577d94375d33123e365c0bb5bb08ae2734ab095cee7ac2ff74777a6d0293ee47303950df193932f2dad4d28526c22a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b987cb9c38a0a401cf03b2488fc73b7faaa5df95e8384d1153cbb57620d1313cd67a9a610414056b91b02127543d33399252687e6984d867cb1e2ea97d83d801af2d390a8e4d1e464e5abd80e9ef716ffb7e656631bc4778deb7834f4fc20dd32a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600bacd2480f5df4b871ac9d49d2e79c619e51ca03330c6e0eb517496a0f163614d24013930325ef5b76031574977e8747c04c6da8352f8c80a8f05444c18c85105b2bb7fd960d5d7ace8e36e0e4ccb7171e5c3740b2ca8a96d868a09cb5e17e32c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c73e0d10678f068c32248f2b015a3a0f415eeb404fc6f89e8589ca1e208ff5949f230d73d985f35920285cfa018d43158f0972b6588320c3756f3b9df9b5600fb38176139100069f1ff20156a180e55782796df79f4683020b06f2a5019da6dd2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46003b29752db4fed93ac5c20b344bce075851fa5cbafe02523c694bec4e3b4109fc0e88bdbd943f0d5325d62929e124a484cb24460dc06728d2039510b906928b01b7626f7e84ac48645871ac12e66bc747b9b1f64eff2352537811f9712c127c342a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c68b27c9db20c07fb3801dfae33f92407cf8e3b9c952f494687140d9a7514f0db75c71c2221b0960d74b23f1dfe643ba2f6c6fb429744e38b2f7f2ecd563d608b771cf172f891bcbbd1126e3354cdd4e324cd7202af62f5fcdcfd2ec01ef7d252a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b272ced92db8b46f6a4a9c0164fd24f66968b23b0dbbf52ebd736b1ce0a4c0512e4715cb0c0e063d580f4b4bea5811950837fc4882aaf5e59aec026ffb0f2300b7c13f1239888cda5c8e6ac9ea10675df17633368906e66a487f91ddd3268ca62a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e155d70b9ec48021bfb05f7aab92008e03f56a19222718aef3cee767a1882805166889d248053ed9f7b4d0c00b19d1931dac509709fe9cd0a577b71c8ba88c06b9186b95c90d2d00a31e7c68066bd37d73408271762604e3608e2f2c983f83092a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600438f0485ea2ab423a63142417de6065b4243e190cd326656e2ebb895489ea898950cbdc05ce8b99ec21c88a96c3953a2366970bf5a0f36c6d0ca34d121115f02b9404a5fb9f1723fc9fd4c5535dd9ef1d67c5237d9d322854697e9e245064e332a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460057b3433e3f0b68a8787e44e78c1ed5f27cfa4b155d3002193a452bd1351be719f47a4f445d635052945cc21df9e54613c7acec835c90a083e89276e7eefe3206bd81133a8d8a33ad2b084c54536d7a17ef0eda5e810a9994b13c2392fea208a92a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600f53a753bb53e0243dead9f8d43f93525d02a0c69b202ed0cafc6afdd50962cc8a0aaaacac45cbbb936748d75a2554173c94531a5bbe20f9664bcd925bb876007c0988a1b3c91b35a7c3722aba7d3a55f79ae07ac6a46d57b4a49a06eae20333e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600276a7f7126f933b4412dc4db264ac434134d77c174c3d2bca0fbca788d0327b24a2a1cd8134dd39cb9faed737ba496ce35b62f663955a88f69adf8f9f7061b06c3576342cbf99792896ee5329b04ff2eee2fc2bb6d53c5c03d52c8957ee793fb2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b5a298d2a97dbd7bf266ccfd61f20f739fd72f1cefac976ebb786e712643de23f8797aa0f160d327ed5210b6bcebb2429dcc673c4b9a39515cbe07dde7d80002c3ff25a1743a9df92af4ccd9a7aed5cbc90f64fd538c3df0a9539128f59652672a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000b87db26d1a4eeeef1f18331c234e6daeb8aea48568b4424e76478eaf1336e1cce3e3b72d136d638be075d9238c58dca48456e8587ce2ef2c46b1d7bbbadf50ac4025624fdf1544b90a61e85099d9e3ac235396d2d9d37f4921162fa688ba95d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b472fc0441578ae962487a27c9903b0e8cef78c4c69521c759f359290290b1fd4ce33692f3726dc51dad68f8ff3cdc88dc6a137d2fa46ea4e10484605c4ed90bc4153342949e45f683cb6df646fe7aca71502b1139c1dfc929afaf73c0a6de822a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c58f80813694cdd8df0fe0586c341a1de7ff7f6dc7c093c2a7333d7e883354a5047ff31079cc00aacfe59aa12207c037e071f710a156247949099120a6afac0fc4374616443e809c1c763459d10d6bf6a2d999855b8d339a27d20d360ed5f1282a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009bcc51ab91092800c4a9519ac8bd2f37c7a7079916a594cef09dcfff47978517c9b1c276749de14c21d010371cfa8da4d5f477787e0348fc71c7cc5c57322a06c49e3cd2c701bb845963c2870dcca12ed070c3f67ffc20144327a93aa6e896ec2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c85a800ac1f59f0df206ad6fa5f26e2f208deda2c0af58ed8c6811c525a9fda21a781689446edfdc0b8a07062daffebe442699974b64cf1c31688fad59ce710bc61cb1b626fea15085663f7619a1769ffbe4fc1f8c63e6ece773acfe180806c92a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600fe7489b632220f8c23b1d66fd40bac34513f1eed3c9a343a59dc09d8d360517db73b05c2e33b0da1932a7cff38748645d8bcfe843290a561c63ebd7b98ec1e0cc63062763b78629518608de49f42c802ec2fc22f477e10a1a0023b237675d65d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460018b4e42adfd1044bce65ff12455dda705b023cc0ffdef4c073aae2180cffac8375975214bb357988f37441c14e1c2913b2c7babed43a0b270920da6970566c08c676bdd3798340c7b35c624f2647f26816ba4b3a0821339f5a0d6c9f2d84ca3a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460034a88c97937fabdc6e2ec582df7cef3829a2ba8b6e57795bb1832e08d07f12912c546f7452b4d0338ce197a9c0a376202387322f371495ebf891a3cb27551400c6a46dbbd2e87ecd0db7ffcffe24c1a82353bc4371d7c2cfb81e7c832d556fce2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460099d000bd6eb0abc52ab88474dc3e504f6e0b21a00cf196317e471926b93410177ae48f2860c98678769671d81b85ec462596ddb585b6e30f4e71e1a98ef80c01c70ad885a3f3ce3fa4041e1477cd801cbff8daa7c835d4463044fed653b3830b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000bef4c68d2fe10b9b34995633ab9dc2c76f43bc2e837252024d85417a12dcddec9cbe44c70891e541f0d67a19e1929de1b0ee5f610808feabb9d9741a0ac7201c8768f257eeaa2a6636fc68a00eb781941c26d2f5179455ac4949a320f958d6f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46008d426d804b32a7615e207fc5fa9c119c9ace79782226bfb6dab21159f3950f18619faafa59caa9a1cd0e0401698137de6b454e45a74899e74681e7e2ee85ac07cb92ffeace78dbafe6fbf275741b4b38657fb81590712aa0bca7877931f6ad392a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46008319290259d0047497e2825a6ce9166f285a7333ade0a970ad928fd1b259feea5c927aa2c742e7fc7b542c579e65979837a77437cf28370a44feef9b8fdb5003cbfefdf389bc341a7e17139f61146f9fb3a9d7bb84fe93c1f771eafd4d4d9d462a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d54d20eb70a33339ff29f8017a48a857c4772ada28e5a88be923045c7ca2194a5683d51472db38d9ee5295fa892963264055b15599226d1ad75aaf550caf9f00cca95e245ef1d3209f6707fec25036c0a1b93ae5613a5c93095e23520395c57b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46006fcea4a22b6b3b28a52fe41d07f4dfcb20f80b2d3308ed4b5b46ed8e4f982a6968bef3e34ac0657b1807f9f0203d905d8dc8caf4ff0c00fb651489984046510bd060d2b638be7c2ff45b20575a76c4acad1b9264607e1b71286e4e00a03266a22a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46001c0549d687700803d27fa15f67df39a92975721e14adc62d267fc7b676c2bdf4d42c66eca0ca0b6fceaceb73d042a59bd88c040aa870c4636f5ee167776fca04d1c146d2429a5a827660008721c7a880e71f44feaa3dc75524c1a9281bac48cd2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460036ad739a304649cce8e4f18021379959c98bf50e9fa1abfd74a9c3778b35f3ee2c2401a0f51794c4c44fd99d167ec94a52529761c41182fc708a6bc25a32b50ed1deb1ed8d4e155f1b5bbc54610756cf2541f43b7c1776f97d404503878b96002a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46002e1b05424cd82fec9717f23563923e095c71221e13a284261e553617e94c2f4d053c28d9652abefb837ba0d9cedce01d40696794117fca8f2b232ba97d418802d2c1a8caace45c00d73f64f241162f85842c35e557e097fd1749040fedc94df92a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46001b1722198be15749cb2c16b9b0cd42dbb320c3f6ffaa880fa90a8a8db74ad322f69bdc2762796ae752ac4377637aa6e0d757320c78d8a2b3f47a8fd0a1214303d3adef5f9366e150e5a21d8837947fc2378997c7658245fdf0bca95a513f04f92a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d202e0bbf1b01acf647b11dd367019b763c8b37f1a9cf1d1a1fe663f5cf1aa6c3c9f4a6675515e9ff47255a6627e888de370f118abc6be342d624a6a83dd960fd416f2796d696faaeb34d2cdaf1004ede551b62a690f99d69b4ccd5f4a6c248b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460028caec545fd07031c5e73f34814d463636013534942812417d2317c7a557af8d21c742280eeecf514916493746db6d6ee1e059170ea0123220a6ae57c3c8e102d4b350a3b25c27c9ab9366d130560326fe6bb7f3a820767736b8a061405e153e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e5a071915555df4404f783902b68cb3c44e4ae8485d043cec0e615ff63719e6eec2cab48ed142c04efffb4321724cedaa427d27e9bed99344dba219a533b6b09d67211ff6ded7cffc866c81e24a9f54f08cd1df7ab202ae796a01d72cd2d3ab32a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460029a7b0b7a6b4a80c7e89b1cc2bb8635bea1587ad214100da7b11add353cd3e69df0b8dfdf17264eae2753de1ef1d62d267cd7214768bda15f56f9e6b00612902d744af98bbb366011a0fe90c3cee81d21cb301d9bb72d5b40b28da348ecb81672a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d77c1a9378d17bbba63e854c9bfd0abab0b3650a74bd698d7b9feea4f588d5dfa2ce724f24f60d844a4feac5d9b5928f4c537caa9230ef2ac5d1d9953b1b330cd7be80fafe9de0570984b008636808811ccfd82f32639cf6806dfb86c4c4c4c72a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d373bbbb4349a52b00d00a3981a161ed96bd0fa969faf1dbb8b49879f86dd5839b0ab12856f912ebe2c8e8a12d82dc7688729a6449d3bb6b7a6d53f66d82d203d86e516b7564a8ab0b67b503c97976157c05fc2395068b27df5f38a1a9b29b6e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46005065b2652114169f82c0c1483c19f8fd2f0c72f8c98928fe419ce0b93b2ef1583cb684837009c4c97381eb83c880f62055e9068113d9e07d1b29c991521a5308d8a6314d0690c070c764863c7c25fb8e9d6cd462f9bd56b0b236558f66fd74fa2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460016e5038faa65afb9f179af818be6b65c1a93963effe329ecba6786891ac1d8956e3f209365e45bcaec3879ff27891202e219e8061228f7001446f532b0bc0d09d978b1073d9c88d84ece71a915d577b8b381938d07827447ec20a5bb250496d22a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d57606898de72fb911f85da1bb8fb3743b6edc0e1c61a61bc05d3b6d1d429d5b6d86e38ae7289b40efbb3df89d14f5704f8e61bf2bfe195470f8a8fe9146d009db1067e5c50401a17b926e54a98e103abd64c4b83c87b770a8312ff03bc29be72a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ef38ecce8f3b7f2253942c612b029e05a6b37f8b1c406f5d01479ec76f956cd040f3a679a9e77a037401185a665f3162531feab7221d828e4bf7662ea876000ddb231b388a5ba18b2568eb1ab9ec84a637a66d6c1286fc7e1c2351250db635c32a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000d66f46119003f7476f2115373c0b88a81e3044ef83d9955fce655439adcaf22a12b74b5fd18db965e694d5635f4aa2418a263decf6c448b891462432ee7ae05db3aeeb826b44e7808e3865df0da2f479442e2f2fc895a46dffa8d908ec95f512a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460065ba17675d688b0fe1ae25984335d388c61f14a81ceddf6b6585b90a9a59bba0a4070d815018326acd57a88edbef65d60ba226554112f06c1f33b0c6b39b9401dc1aada86981424a634bbc637e58712bc5002a34bb6498487d89d83534b05da02a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46005565dd6e8a390aa49b5491cd0bce4798501a87884ef50ba120262b826ff28ba5741930e988241f4efcc0cd42f7c621033166193fc85b00b792d41135172d780fdcee76cd80cf2218de9e8c4ae1922fe1c26235f9cb479b2950b064025a1d69412a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600f796ea2ed7675e68b13fa0df5a873706de9c3490e006b4b7e330668ca62ba0eb7284b5087a09b9ae01e2ffa3aed1a545a1de99ea38b0bc183d46884315170502e2cc4424464983ada824bbef5aaa8995e80ef1017c15ef3b13902599841637ba2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46006abff33b091bfe069c92ba5bcd25d56b604a45cd1023080614e8e8578f9327c7025a9bb2ba92c4db3e699015ac4f46dacec10245d86b73d41b9e751bc7f6e203e58210689f52468a22c10566f3a2c6f870e61524298a3e781425861d40446b132a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d2a95ca51c964bb35183f2ae626a09a01b7b7e82a046af708da537389d68757b8a5d5c8f230523c5ec089277b7c0e072fcf4d9dca2ecd7bb6a3eec6e95919c01e6ebe15e9e2c9f0fe234241ee50e9c574774807c0d17f8dbc7fdd1802c5c79792a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600254314bb9821d205214651a8c292292089a320ccde7dd38acb21552a855eb0ace84b132f04317b591b84be06679d74df67b46abee0128089679c45e6f7836c00e7210714763e5bb3fab12067d0a55784ae0d70ef14ccf9e243bbfc6d329834102a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e3b32fd44cc8a2dfd85b2717b54a4d21d93a8f40b0c01be21f81dbc482b383d2a4c32911a93fcc4c8a746dc75aa0925c6b266b397a17f05318e6cebf51661001e81206b483fdcf1fe42145f27d1efb8178a57ee24b196285374037da2a53b3232a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46008b0b5494d43206cf55b468597dbb9dcfc51cb59a5b94a2f2150d5bed423057867958b09d96d7b772d41d2ffb7f68125ea119f0d628f2ad338856a34fbdd8ef05e90f0dd5c3d8e23deb6b82b9d7335855f5887c3b77ac1fe30d9112b22319bc922a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d2a3355b20c0bb0286e1951b985ac51bea4439b0c70ede6f38c026495bdc948e2df2f72d00b1d3d7a2bedfabe8a57d6d4f0af59670b5e75eb713a1ec2cf7d802e9836cd1a96c0010f43161443a790d65ea2afdef21b05bd563ed55c6eecb00662a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460015d31429c5579f409756d92155519c4bd60ca1b095c8f5a5aac66459efa937f48989d599106e5749cb74e8718bb96c49119ec5aa11adb3251a9365456809ae0aeb0af0662538c295c85d7da7b1e0d929f8d887a55bf1a136a144f2f7cf3215802a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460083b2ce5b2c3df07146efcb19e6dd1d8755ca7ef52ba3b05e3a2830374a4f984a4a2946c0905d47f962bf14e51bb0aba4303806247135d579317349e421550505ee8f86db1c1d2b7cd11e962fcfd1a7e847204337c8893f1c1ddb75c6bff3f2bb2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600dd14dbdb4c43afa43d3da56daec9d48f09e356082e2317b505fc1a8520eb491d6fd344bc94479d9497be72d2dbc95d193c6bcaaaec8c5df928b37e8373cfb005eecc027b5c05b7158fa0614fd70df47412f181e808bb78cbb63e29adf14ec91c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460053e0270ad201d8ccbd4253715af906a56e2f44c6d78f9344da12e465a5c755ba569b0f5db093834921b8ed11f4df2dd52497b387efdf3948ff632ae40f7da504f1020dda4f7e6af02b444bfcc70a0a18bd48042ad72b1955778bf4e56086a33f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ff32188333a59ee7d640f5897f5415cdf08e97742f82d340687c63b71c0ba9ae42599d522a40233b772d6147edd01a7d5bc865714f95ff979c03e61b9d1a6a03f2a930984c2799a98d298521128eff1add9706c9e57e848d54de41f6b8dac4b42a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460039c0604b3ad216bbdfc17d641114a0d25e0865ff21cc945574cda12f7c41f7e19f3b65550b410b017e0e8f58c94d9abd29f01b7036193305fbecd341e7156509f5054136e1ea0e3956293c422a147eaa4950cfcf0ad9596dc6e6a287bdfb06602a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460029e0bf534f510a4cd43b5011d63057da67b36e2b03cf6087b3bb8ec80fdbb9e63b5e361313e2dee669d1d5644696e5c8b4e71548ec9c6daf4c85873318d9b708f8d4441b4ef1c2ee50272e1767ac4f773a249eb663c7e37bc075951f3b52ede52a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600753c2d7a7193f908ed808903b7cfbc3e2f5a8d9fb380c054f887c0fe21b206d81406d25bb1d0ef6cb6a0feaf015f9e96c0d703e9bc5a0e956feff40fdf0f0d0ef9887f6f0a8673aad0c10d1bf6ae3ae5f0089c8f05d54e180e16030949a8f7782a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d0c3538d97ce8594f8a113d01728ac4b13549646557385c6b5263f4a67a0ba96e2e5f0ee3ac9c85fff368b4013eb43900be5e672375a3f3860298342a0325f05fa786562ceec7e61851571e8eaae35c7f0f4b2ada3ddb4d577c66ab50a1d6ceb2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600eb8636c254b4cec68eea4ef00a3ae43b6dba9c8f85958b514042d0764f66bc9b22e4205ff4d8d4427395df3aeb15fc840356a9eabacee3a23c8cd6aff517db05faf968e9e1468d58947b9217ed7aecf19ce457036a2b46e47c44e5a1d67e5b1f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a7038a3eb4b56c7414816925b26d1756706f2256596c99181ae8746a40cc32c5192c93a13997c9a11f31fa6d6263164e0c06034f0790400f1730c250f4054205fbcc3ce7dbd34cd50cb3563d2b2f62a5ff2bc847e6b6e79faee63257978f9c192a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600762a2c5d7849ffbd8c2560f7e6638ede211a0e59347bb1c7f629445e6af0d38826319366d3597f0773b49515907516f2e1894618f8517297b4624866c28e3e0ffc678679f11bc904720421273689e6826acb42b21e2c4c5c1d7bf532d89668412a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46006dbd8fd05b77b7556cef50351310ca989db725fedbe17fbdc079043ba50dd86f7fe96f621e1eafb13aaf097fed1d441a90e6462d4919a752441455bf49698a0dfd9e354ed59f35c917c42471588142e715ed2151e89ecaf98bb5d837515d74772a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460010d0c657b622c3927cb79d4e34d0d1608e0b4f12294277049528b4a3e4add5745ce5cf9048f16607fcba67325cf06c460d2766421210dbcbeff29b1f0dc39402ff0bf39c82ed573d48585448b0fc19ba3f6203806cb6b4a230848892c097a26f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d072ab3364ee35ffd547d78aba38e764805b6caeff708dcd806acd06ee2d684cccddbacd4c5d3ba73a6f6e03d3749635ee8eaff19137ec7dc686c46af8f76b07fff437ff18629bf1490e5c9b3ec6f1515d46bb9b2aeaa6e39e36611f2479b50d00") + + gs, _ := newTestService(t) + gs.messageHandler.blockNumToSetID.Store(uint32(4635975), setID) + err := gs.VerifyBlockJustification(data) + require.NoError(t, err) +} diff --git a/lib/grandpa/types_test.go b/lib/grandpa/types_test.go index 732a59e69d..8f74d41904 100644 --- a/lib/grandpa/types_test.go +++ b/lib/grandpa/types_test.go @@ -98,13 +98,14 @@ func TestFullJustification(t *testing.T) { } func TestFullJustification_Decode(t *testing.T) { + // data received from network data := common.MustHexToBytes("0x3b1b0000000000002a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46001d032a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d5be226e7e7b6b54eb6c830ab47d4fa29bc228f46f176be79bd9b687d01ad7c4441f0c5b7f489462f29ba1641672519a2bbfd9162fb11d646bf1990b0c858e0e026905dab6c71c2a664e9ca8e4f066bdee9265ec45b7885ab14a797ffe1bee362a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c393865ec446b236036e3c846577c930568ea67f1cfe34048cb360d3892d9da61c4b1bd7ee31df96907662e10dae18646ceec91181c5e3dd97605b15f0bfc20d02aabb29f640813f718f1e7495f42415f742457517d536ba5de50990d182df252a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000d68aa7ce3c3902cca498cf6c0051d5b0f901f22092a427faa23227a19bc55eda954191acba6b5c82ec03543633facf3f84176ba3b860428d0e160b5a3a2db0802c70dcaf367b35740713c0e6761d88a2e26f9762e9715fac8bbeabedd8c5eca2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600756e9f4187f078001a7368eedc194df7ac7b1cae92827dae36d345061afa3d49df2041a41387501c5fd1b282707303aa3a7c820cce1b44c081c0051e7fc229070339088379600f5bd507277eb894aba34d2a8f27ce1964f41e60ef2a4142dc6e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ea73d78ec38c271aebb2c9f7ebebb5d3a99edf327bbf4d03b5c245c08d0c2cceaf0030ecb5e04152255f32e4edfbee970f3ab05607f6d8b718826e559bc18b0e03d105a30087d96b5f0684f6ded76f826b01dab61e4136e1d851a24f0088b5ed2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600681cd145a0375e5f72a737bbf92bc57c1e2b9429cf8b66bda13d0096e1898385ada09ccad93971530349592a430aa61331efb46c04a1bd9af7c34d5be9f2d30b044a5968644cf9f9b5d1c7f657a65343af076730a89b07b4995a494aaa0c967a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600620c51368e57db2cbf7e1edee0a746dc950c6da129ae5dbf81f3171fbba241f564b847fe376bf7a6f62d9bcefeba69d7d8b23000c40b7c9ec4f2868e6d748408049603b355867a2d3a1978a771b71b2bee4e8ae1f344d9ee034f47b01a48f89b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000dec8ecfc90b708446f112ad2730149b124d1d4b891edcc7f192ad4f6634fb1529471e902b590049ee6049361537752f8b4780e1900f5134f101039d617a710e069a481209b07b48c1d548d4af68b7584f95b32eb374be0682b342cd64d3406c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46008f6792c7ae77c3dbde17b000c86743bccd9f2247fb323044b12378a0b060f122674fc796d910fb2eaaf7512a5858ce467da7409a2e5fb14edc077c939efcc802088f8736e1cf2ea3d102f0a96ccf51222b8aa1f93d8e42947892f4395cb5477c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460043286aff05190884dc7be3f5b4e42728a1eb766c73cc9745f08322ed672ad944503ec837a379efef89b4c78a5aac7ffe8bd26e649eac482f5324280e259bcc030a790c3ba374fdcf1a1d457adcaa8fc01bb46eba8cae4e1940825364fe2d7ce02a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460025437e042850b2648ecd98937694a76d041c65b471a81344c55d9aae0650459daaa5f5e734ddf02ae030178ee8714c5f6cc241f8148eafeff450424fe62548040cd72d0e6daf0275f2eec96f6129243e01e255ad6b104ed500e614077d0710982a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600fecfa0a948e6cdb687849ea41dc46083ed8562ca4657d751f6c303ae0e35dba38e5c1550cfdd71011239872139770b8151eb4a04df948f666d43811009f12f0b0d49982b323ded139008e150fe97b0e85213a92b411c68f993aaa43ea62a7b282a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460063dc716d5616a3125fabc437ccc98b3e38bb2730a2564a11edf528552245f20657199cbc982288e2ff3127497a8a725852c35352ee3c0a2109d2e603c33c37000e1b3f7368c230b609c745aa7c2119b60218da3ade7e6a0dace3e337703c51992a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a07873b9fc9d6b71bba7749a8886a13c8d5f4c2b66907bcdf5fddbd88df3c6ece1d601799af6ffb7fb53cece0278df81bd34676ecd20a7494515eea9bb61af020ef0093d298f1adee0947ea81400adf0c27bd2e5c10b760d2c8be3b784e1fa002a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46006bc6c891010819529a51deedc4a7033de5e85aede0eb4103118110239cbfdad07327aeafc68acfa3b11872770653290f50c09658d416cb2534f0794ddc4b1f0c10d20c8996f5448cff82ded879bacf7534e4d499ec65f040024bd7c2402eba942a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460090c37f3814f7b66ad1008f43715aa91e3f7713285d44c9b82b88d7125a5a5be38f9c38675891de2455b276ea008321b742f420baa7b2ed8519918edbb8b9b90311addb386a5088eaafbe4c93e265b8c1459a8504f0d31a3799836224c6077d532a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460085114883f06b533bc6494706326c7191f224f0c086d0a21c8534269c3dd2d7a10c1beaffac80e05250d0c230aa3da0ebfbbd4fe47a8323766b09a8e3ba797b04124cb8ef23a2aa6f5d748070a84465b1cf4df73db94257d96c63df4b5ea8b80c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460037c148f4242ac604913f055679371442e9fdfd7f989ae9d73dbd5cd439a31f941e5c4a63f315a3b4c8faefe537dd51fed26ed948d5cc0110c8c89775e5a2a70914b432d81cead21854d70b19f9691b3d2a8ca7862b64a71f4f3172387078c4642a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d3acc5151d7a54944aa00350c835fed6f9dfb4f12b8124903d301a959bd63f087d19016a6f18b732d58d37f93ccdc5a9a27511054f54436dc8bf8a12e1a06c03155763a153e0c02b2eef1d8a9cd8a50a9eaac9d3af7ad1e559b1e2320b521e1c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46007fd97784bffbeb89e3fc4053e6667d73a24a15e5a864f8264d544f93b8d4938a5e57b079247499e1d0de9cfe038754c930d1427f491ba0f8a956c56b6abdc409172fa913b2ba5833a78ed51e13692e9d1045e7516b5ac738e6745c217f49fc1d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000b091d7bd11344b81890eaa9eb51f38fef522d5b38e05449ab04cf720c389c9a9669d8cf69411500ba2a78a4f7783d75c424b0ca60e5a564728a7908b90dec0918a1a6e0e81ae2cf403c328d1195eb73e9b78091bf1e33be4dc1e29736eaa0382a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a3aeb6a64836a35659a253533ca38c5e8f5dec712d764bbbfaaf079f671c4487345d232be14aead855a06873549f712ded1127217560fb315be740e2071812061c67ae7a3c999b95732bdfe95fa8670aac497a1027aba327a1438bc097fa700a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009ed7a0e3b08dafbdfa66248a9abe9f6bdaad694b2c8b1a85c2fb8ba387895d11890d482eea4078d1049373c9a7d95123093e2e63d4472c186c18ae7f47c310071d5ca234f8eeb85fedd882230378855b4e7390a78a802f0be7230156a8ce624c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a5b924123457be20ebb04eb12a248f693620331120a053e1837a343460769d6edb3316966f5784ed15e5e8f6105f8af34ad0bd6a8f11aef6f79eeec6e658090b1dc6da633d00a38d0d0b44056980bafbd624f2e1067027cd2b09267acfb70e9c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ed204b31f36a1c06fb6b69e206525afbb5449064e39aa477602ea878ac65a4e81e24fc35c1a8ba764ed1d75200a2c5681a43da66a8096349365a9302706d1d041e0695d7beee6c4a67c9a1a75f89f3f858a50a09ce1b96a08e685a49e20e7e442a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600124362e041e4e25522fef30f7267b7d52338dabb1a2bfa21d163b06df2261cf465067921371fa5be83279004e174d5cf5bc79102e2208139bf62892c64521e0b20d4e595c50bb9558dad9be6a8784492bbbfc754b9c5fae17edf4f8a84e8b4712a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46002d19070e68ddfa34d42b207919fcdf1583ffa6458a82e2680ee5c880ff145e4b6a18b00c1c683c511e196d7b24cf4abd508f6522ee3e8eb8b335a42a0f7dfc0121699d6780c6aa58e433f27d36b7f1ea5afd4fa404d8254bd62ed5b17c3f75672a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46002bd9ee43aaa4cd4691315b505af64f05fdb9d70e1fe5f8c7d9e536bcf723b67035239765e727d4e1146c786c97b1e900cd33d8c25440d9e38907a6d0af3d0602221a227686d900f6be260a84e81bab79adcac88ee2ceb1f4efe83e0be86fc88a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46001b561a74a01dd3e3957d7930fae671d9a107a0e36b1ab1fe38a2acecf24ce281f0270b516c0d73ca05c1e7e7b6b5870e973ed04f8b4b2d8f486731e2871c5d012275aebac33745b7c0f47957b0f9c1dab14d6dc5cbece544e26830248b9b638d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600f394746cbe78e56e004009e2f374bc8fbca67b5228f33d328ba173673bc7ebdd31669b9272de648f7d19b7b79ea68937a6bcc395695c03c8afd36647bf20f70422b1625c123c72fa70ee375da98f15cf1326b254ef833a27ffaccbbe81e993d42a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460043359e329c56b481de900abf705e49957358bd8261d5215ac63a96b9098eb07d68b12aa67f2e4559fe6fa838233795e32e9f08b1680f89fa7db4da2cbf40010d23192a82e612c4b047a6efe2c0b20d6b8300a81639a6d1578bb94a66a721649f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ef4e7891ef4f0703d2ad1833ba02e1ebc743fd656dc4e0d5b2e9346694f142ab262a9143fda3c3a199e8f57820192a49648a0872e7b1de04bbc101bcbfd0ab0524796621f90c2d9f2d12a962e14f9852757f89de7b1f66c22506321ef38266bf2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d65939fa1869e5e2e2ed5800cac361424eb5449365fee6914719dcb768267f37be9efb0a21b1720308dd5961e3ad43b00eae969a117b8e1121ba36362f0b220225d5c2769f768059b45c6843d37949ab7866678eab000a5e6340e30d1dba306e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600aa0831e40a2cbc1ba04377e7cf9a0c6f5fb037c9516e4e9cd4d6ad0564953b99c8ab329e5f667f7fdaed70e9b8d3b4123e6ed9249b4e999b41dbb82ef04ce50d25ef09e15fe616e090335b55cf729832e2c9f562428fcb00fd3e3bed2dde843a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46001396a84de1c3a492a93b29d74c536ea8231bd01657a1621a3af64b5993383781947c771948a7166dd9d21b4c34a36208dd102d77f3f6c99ef85037ccd4fb5b09271f0ce7fd9e0460b2b5f68afb847a80ea0821bf168dceeca396a1fe35754af52a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600804dd690fd66f6c15a74a4c1c8be43b57f0eba4d45d75f66777a5204a506c6344e189d33c41ee6085420683807345324cf24602b2eac5a307603a1dfeb71040d27a748182c49d7c4ec091c9589da5c9c35fa99ac8ffa4d613129dd4d08e9f1122a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c91a2c346304d33ac0121707248a0560bb32b4446fb0f2d29c6c4f27f4073bf6455e662af07a4c693f199a1b850fa794d45d0640d33883e08681b525f99bfd0627e96e8217ce52d55d5cfe97580a03b8f9ffd41e72ad863300db45749088faed2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d91892fba8d2b41a62f81cc5dc225fd193aa215c9941dfacb197f7b11262609953c57726361b6c1458d500cdc90eed86c0d9ae4cb98ec1457a438af9e706d40e297b86aac8a8843ef944f9633404cf307ae264f031f6dc803bd93c7ea69889742a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46002c85b6e9e5c28ccc33e4d59857cdc0afd0e4f4d1c2218dd018123af7d96ec3b08e49ca35a5231431e2663db3a178dbbe8a1a4f4c0005014770c0cc85876a88062ae745fd0181edf42f9313bef7ddc5d62ae8289212df03f8f73e210e0ed907562a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e4ea8f7d9584724897151d5a1412a73e42c96aecbd84020e52770c1767e8f807befe8dd28f7fc8cb12addd1f1b72d6c58e418702111cd708ce7da9f61574d60d2ba3f6bd3084ac7d9448dd341d4c7ca8c991b659c85f27cc68e80fe7cbd73e422a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46008c5d3fb36a8da0e1770f923bfbac75470ec6408c06f0cb1be5cdb5893a2c09eb1e370073aac480ee2ec8b9c3a9bea659869a4328e8e441851bfb846610ddaa072c3ce4d7102f4236e0df2e8f5797f6dc3d2e6f0e57d373c9a4b89b21d4d228682a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b6e743e03f526851e054ff4766ad6db181f83fbe2370a51431fb8c310f1cbf3788d99480e9fffd8164d31cad3db297d9b26c34523aa25d5c7f83e79b6c02390c2dad5b2212ee688f2eeb9ca1fb6a90574f006dc1c6680ac3a8523363a248940b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460020716a5266a471356b3780eeaf4ad7dc37f50352ece4e1416bbe47b5b2606bb59dbcb1d270aecdfa6eece9d3e8f0862966ea7c7643cf1c05945fc4a56ac65902304d970cf7e07987f8d7a500edaeb1c0973de1ca588512e5d9f268a9ec0874ad2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ed2d6b378c40cb99f357b0458085a0b0504c1622adf0316050396479b08ef1859c2978fa15b4e53fe911857add9ec253cfc8d6b949abaca4437824f324c1070e360e744fe76d5d471445ce6ad9587af67392b8d960d7715dc0efb43c698465a52a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600013ddc65f819aa4c592cbbd59ab1f4a4bb27c3a4b717e19d409fde187da9e2e7dd6c9c72ecb1bb772c112f3229dc5140c202ab9341d04a14dc2c24a2b4e34a0936ea5662f48dd131b91defa20bde06049edcee2982714f78519fb64450d1b62d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600087672b184ff663524bfc5d05631172d3eff695a117cf6c8884053e97697b48dc255835918bcc10f915ff4d8fc3470ff5143eb433c00e2dfc8b46db2ff16360e37035dbee1de6bef71e401cd19d0a26cb8b69cf719ae340ea53c9677d6a9aaae2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a2d8a30c22e7f78ea1a477a0d7971533020276d5d10bbbf530f31ec5d9ae688108c2b4535dcf9204604406f21d104131e88d9c8399151bb67c14a790945ab30439712f967c4e06d284d6da4735cf2823c1f770674b668f7bc896071c3fd41eda2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46007770937b4a1d62bc1df63b1f80162728b198d26492e93765025f9c71dcf992482c9e85ef9100f7fb89931c0d2fd82d063e24f7dcb3d28e549d1d45d8a91f2c013a2f175229490c3169b5942260eb2572198b494e63c984dc364d3f48aaa7bf012a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600f7f152e001649cf1353540ca716715bad9fb6703c1f34b78673baccc8027c3885a6c9660cce85fbf2b2ab696220a2e417964c91912b030a0861ab7ca541daf073d75d8fd47f1074a78cc88248f1f6b9ea6cab42ccb676f94226e3fb5e16249c12a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600dc58eb9647b223730213a6f217010fc35556325166e03edd470b04b9b38f97a98acc1695a71120f06b2beda919889a5c2de43f40f9954894ba861cc2082393003db99a9882de9b7666591c1f9f2d87685a2569bd0c611358d6e144a014c5612d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460070846e878685dbe30ef68636052fcb92da91ff77ad2f7839edab14ff2f5a28cc27e0c464d175c995647ca3a0cedb2ec732e5732789ad3684ec4f08d94034c3023ded8aac5210831cbcc4d0cf1f96f2c711afc90a6f6f35a4ffb766be1dfbaee32a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600674883cb865697effe33d717b63eb6746fd144e8dce5df79892d453b51fa49cdce9eee1500be36a245e33a9254ce65631b0049218fa305c2e9d5ee1a8855b00b401def7965bb1e8373fbbb6561d5ca51832cbb92b67b0bb08c407d2c19cca96e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b4d95db0f1cd96e18f87a0c67e71cf7b8d2facf75d10671bbbc9f7e5642a4c2223100eae4e64a54ca179525b19a763fdf624d78e209f7268d7cf4cf70788200f406c4081abf0f3a151dcaecc6b7ca1a7489cfa1810cb02c8cb249bb67dae09302a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600bda3c89a9f0257273f01c786914fcac144189a20db45d05fb42ded836fe2067362fd1baa2ec4727a7d8d72530474f60a39070cb0f2af31c372a511c55ef2b20a41ef4a31eb7dc1e01f4630604e1908e644d7cdee3f66a60f98d6d59605326f8b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a8bec76286b65de5693a2cbc309bc3c301bd6812eb493113449e9d30d39b60ee58a4d918f127d54522e74d6c6167ed99dc3b10e8aa64f3e4e5a2a497e68d9e0542d0c88f0accb5117e31bc057d4f277a38e01f2325f02d0a9f647db09b67cf202a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a243df7ec7aea8df7944c4474fe09c1a2a006f697e29d15300da8a51ac7258244501ea6c87983d0ec4643c051ee1489087a3017a63c2ac3b7d06aaadb1b88a0943701217f2650ade985ac46e38e548bc1850bf3d895eb6c1ecf42d9e61b788c82a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46005305ec8c29ed3ef51119c80c144ea8714321a31e533c0ed9a1c03a781e4401a279393f06e4e0f0dfd7f21b523a808e0aaf659aa587063d7017dab6ec8df7810543a9a2915f377cd4943f602be38c3bd6ef39e91562c09f18fb672884b4bf8eab2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600fc691469543554a61c6bfbf5d554ebf7cb596ac78579aa1b8d6b0c7e52f8295441a4bb450d8fe1d2579a51986d3f7a6de52a34690afe34fe5699a6bdc1fee103452185859ba92a24c1f836952da8d11c9425c9b35eec979744a345d2276de3ee2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600df5b15104aba6d1d4d70f19eee2d04e22056616f0b54011fc3f15449aadd958124dabcfac319655f72688f14130808f3dd12653e4f948034398589143c24fd0d4532b59911aec8842fd910a35fad7c6210b3c1ac73c7c9799963c635e2b562882a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b11b2845d75099b35ce971277fb82b0a8f2be06f40d2b759a291a095b3ae6f9655279b922667119308fc3d673e3d6f0063bce32f0b2c5370071214ef58b20106469947dc7cb086bd75216dcff8c8ddbbff5e0f112ba397d71b6bc2980bb6cf002a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c589c74c41ac4ba4e067df4a152dd5f34706f74828bbc1166a7ea1863e411058683a6d3b3c27db8540a8538fc50944dd9b3c8dfaa5f49fce6c5aeed834d8d504469e0a875597428e25f79157e441e933f3fff7d2afee478a9ac6e1903e4d4d1d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c0a2143bff781fde49d6989df90169579c588192fb669219f815f47117459f9a94ddadb68a90796f95186e7ec641e6973bc165d0d8b2fe651c9e563551bf960b472bdd18c1be48d5e3c40aa093db02e45c4bfe1a5b62a3884fd65be8eca3c6022a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a5f5af2141ecc0f6a323988e7d8ecdd8b609fb9379c20d108e218ff6c8aa7fc91ae54bde791fdde4d103c43299c033a649e69c8fc933db42b858a3730dc39a0a493c604e7f0a7cd6abf370c10b90eb8ff9d432bab420bdd9fe3da656d5e9b6752a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a1f8ef2a102fa613697a17e7135f7f7e429107d27a74ab15f0f2a46afed1e3257ce683c09017e25cc7abd447e06891350568aace957b76c5a2a8cbd9c0c8c800494d65e6b674eee86acf5ed70b45b37d7498b8c1d05cc0baa0d6473fdb596bfe2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600eda30d06498bcca078360129b8d46b6d8af3b8b9ddb252a00dced827e3b4c86373e299dae8720f622be9bab21766fc970ad786bc5733ec208b0105910b993c01496826b538d97906e14d417b48598d7d591a483ab5f4c6786cd0b96239c2f4cf2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009aed65d04eaf5a5c215e4f3cbf9144b83bcaa41c037e42ee788229ce622b28d3b2ec91c3b1b609aff0108bc920e0f6f43266e1b47598bc99ff5410e8af2aff024b35f8352f2aa4dac039d347d37488de305c7ef6e7aed2dbc3526f537efa2a3a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000c18a3ea31cd3830207a76b80da87192c61c608490c9c8826d4c860f8b39b2962d258dd43bcac5368fc2d9abb0793c188f6197f0a6d4c3346d2e53c6641086014e319a863a469525687eb7dfbc8924054c35d0599b6ba4dc94702edce01671a22a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46005bdabb13bf58fbbe4553d4af8f8fe9ebd0a4aa99e0cf8271621c9975aefa2a5f35ce96bd2897eef6044c3eee4aa475af4288d660c1d64576a770985b9c74890350811bd4b7dd8a5ed9193d4e2d19248020c3d334249a809fb96234f058ff90fe2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600bb6766e42e7a117d09953e5b2505c6720b77655c557be72254b0d946dd0670b94a18a9529dd8c96404596e9b6161a0a5e94f5bce6c190e733da9d4b5504af60451237bd7fe895abb96631987f0f82a41ef6d56f217dd1ef8aef76e9af4e559862a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b1042015c7048dba6b6aec2a38267657d0f221d0a862514b91b4714dab337eb41a014683101b18d98bda3cb4860749f10e5bc3a1927946e1c96c3c937532800b51f980443e5fb020d8498ff88e49fc55f1581d7eed1c66bd7653a4f5a1ba18662a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e17cf09d6059f3140f99a05905c17f597bf7fd8685db6c61fdeb0f91f0d3686a1e218daff23f1964ca2e7f983efad0d0714ed9cb5b23dcc2d428c03cc10f1a00543a164d12ac3f4ce00b176cd41a7af343c56e4ff445a2634d74f3b182c9c7352a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46004c4a8f2ee83d7226b58b1415f57cf93574021a6e828b1852b30faaf97fd75325424bc67043a889d41b4f1010e40e2ce911d02ffb252dbb1f9b15f0936fd9cc0f56682332630f5dd42f160f1e3475d1881b2adab83023216892f538efa1e0e66f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460095af4f1a0b89f8e6458417dc977f40bd63d40cb16c2411f5305f3a12cb44997ddcca4c14c350296b4d2a30cafb1e272d22b772b0ba1d85ead112b93d82a9980f57a7cd79d0feac648df60b94b7eccde724eb7e473fa2368eb5b88181b030239b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46005cfb9e1d0f208dcb64abae8d57bf8bbaeb4495e61da051a28d6247ee4eb02dfe0a5bcbe63a8c7869168caaa8da5ccad40e1d0c64b4c802cd9d41133cdd88f30557ae0c85ebaf333a6ec3251f577cab910cce072f238d1e50046322b83bbc0dd52a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460051eb234aec184ded0063029767fcb3e32d46bfba803f5301e932a4ce8ebfaacbfda1480f48991d6259401a5962804e2949be93d9f96ab7303b7367c6dcf46c0f584b81d2c35f01525cb80a02c259565f0becfa7d1651dd4c313358d339f32d472a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460061ad5b7dcd93f5ac2673bc36633d2a612584685c1ce6f1bab11561a0135ec7b3c20e2c88ae6ca855d4109f6f9892c41ed14bc38b549f00f823486a3e81b22f0459522ea548446804370cb91e2c4eccd2599a018202b0e6c04b7643b143707a382a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600278975698f785c3012217bffdfd48882e43033099ad968e849a14a962e2d6e538d1dacac16df19a0831f52daefbd4ba7c6af77cb7b4a343c575382858d1bcb07596004e838b7d90408d46de62b01e13e631575f348bc59f926286aae4c88702a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d8aa4ba9183770f61c39668721066eabe70fd41d30110134ebcd703a7bb4ea5f0288e28ac58a12dbce2ace9546b0fc28f38c5c467a33cb0de07ce8c5ac14a30b59bba625d971d505a9c7d7c2f3ad69203e69e3e5e1fc1e4905fae7703fa19e032a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000d40e4163b524c69ddb59641228c09fbcb4bff1ed875161e54bb10bee1ee21e618ff0715d5fc9e9ed1f32dc49e01926b4728c8b2664f2aa535929ba72080c0055b29e3e31323bbb46b3d1c53639ef72499c58806c7f4cb6d2e8343e961bb6e3f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600bd7478f49bf6d8d87ef834bb9653a3f0bda54dc653fe7890c69882abaf429231fed0b99dc99d2bd4f504c32e2008b135004c60e2045c7560e0c0c21c144925085b379072ec1f3f70b4650979a47b24f9b080c03450f7e9587d92cb599fcf4d6b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600117c8995b21aab23931a24670b8a885ad04493aa804a80b51572f5d29cf04d6e7d8f9adebc0cfdb1df0e4414a199398fab565d55fa0e32d9f70b828de39fc3005cd0c7b21d4834c5a41a80f7d421c7d0297e42eb409a524d33aa7557df13adaa2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46003d5d559b960da2c238fdb2609b45d125f1940fc8c6e4934d2888080b5f2c7f7da8c34528130afbcdac99c0364b88b5a329c8b742bbcfe91c7e96556b03e70907607b38d0d0b1ce290ae681fd3a7fe09b4299117c3d2f9a0a52c8a3076e268c132a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46008152931c4a5feea2e39f656662f3515ec32256c57aab6ab75c6f0b6f5bd50b508a743ff424f2eb046f1a923486a0566034023cb34413172ecb1e639634c9f00d6088164a2d219f2069d1d51e06c2b4eb70b85be9071afd6e99a92f1232ac645b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009226a52a4f9fd97a75c6c808a961744e731656160509b88038c3e7ba2e88f472b2f8fc5f865043e76558fabfd6b28dc0f8aad8a50d5744072a4bfa9a3d78200b62de44b8fae34cdd6d4df47fa7d420bf8513f35e3f63c66c4bf699675edc33e52a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460014c6bb8be7effc1ee39f3cccad774fc1e71212f75ce887f1b4bbd7184de4691c9c1cea72845d9830bb86b24464fdf7286936ed6ab238adaf78773b335057b60c64bffa0b9f160a67beb033610656b80d6c9342797fd983375e27f91ebc7e6e0c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009dbeeab2dd1d70ae4993bda61bfede77c378865b264b9620d17da5320141d749bdbc6ae1b2251e68f79ebd445b5b93d8a5b2fc5a2257c307ebea37f1d45c740e65bef6e1f3f6609291199f6e0940c80e559588368e2031086afc1730e0584da22a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46004ff39ee2863945517eefde66d42c4ec96da046097b7efbfde7c3111a6c06ce97f552f33374f43a723709a4b3a0cbf6b226af61515805c74fce2527bbe828860b665da232ade423c7280c6552e6d0ec8782f9bc742c0b030fc08e9dbd3ca5d3862a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a363544def43bec4ab3cbf8aa4b637198f7cc7bd89c2a402d40c6c10aed6a9f231ec0ae946d8d21c4563ccbfc1e6d6ec73063aeace3397cfe34f6e78e378220666ed84a2c0bff828dcdd1d82463f642666dfeade46c020621490886912bf5e022a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c2ef4b28110bca12507a617a321e2e5e3abedfa0583e4bd9f320b02d5984f62caff3a28a627819a2f2d54f01f80713248a1e5f0bfaaf8b261a3ceee530b89b00677358fc648638cbd854d2a009dd39b8508dd3047d0c5f13bb403a64d053ad032a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009d76b8b8deb037178f7686154d9228969aa30d5751ad1c2e0097c97ca39f3686a91afba09d50e443f75a8ea25f41f1e63daa1a5e0026e099f9241ba7aa38600068f8fcc2977c5fdb4ee46f234fdcec2f60a22f9c63ac7091b8ded3ec441df9bd2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000b027d479cd33d8d57c998621de2cb6dfffd320f53d27585d3db9aa44c59bec3ecb584f003e5571e488cd92a3485ef2b740c27861a5d780571dfe10c07307a0e69470b335262280ce95a164f7963af49e41ad6173f8db9e3faaf3ab54a8c50322a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009bcb275c92fdc035bc43aada4277e97139993270fd0ab3661350adbb09304b03246c37b02500355654ced15912e9431858d85a741d29f5f9ff65ac597fb3700b6d14338abf210592babaddf4a584f2ddc06c0d333ba8a9f5e284c3be59c828512a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46005c4b0f868037118206170a6b1d858ea81047028e10bddbaba24b1b6722ffadeac7a10dfd3cd1c2cdfd196ae3da5da59a0dd858f6ce15aeb884d39e100a6619096d28713cf7af6e13d24dc67d4540225f637f0384e58e2710d9e294e7473edc2f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600cad6e6d6beae1a9121a104e512d5d9d89f60d0aa1a14e89ad906e5a875cea4d6290956f0f6acfca1f0faa651886556d59ad2d5311f840eda172fdf0fb9ee360b6f7a1a4f9d8c054907f3b6b946a59525199dbcffe9fd3ba89612a5d4b548bede2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460096490d82522f60cceb6f64fe9d7be13cb9d3d5426f34d9c6dc7cc7fa10b51705ad8c0fb4cb8d1df8520af798f4cae294b765f4c918f75f8b80a9110ed4fe2e0674998004d06285d2b8d99a87a9fef38f0fc3109f4919006b8d1831b0dc59b3782a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460012d3db9c5db15a349f1152cfd3edecc7cbcc6b7d6e0170379bbf12e29cbed42c9cebd9bf55832bab8dfe4f761017aa068be5f84101c4d7bccfcb929b34c9ac0d74a47c40733f76b6d0502b67a709fb5e5cbb263b180fa0fba800d9d6758207362a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e1923130437dc6a03eccefc51c3717c7f95851aa9ce0ad76ba0a22b5dc4dfaefa9cf456eef9a10b9ffa1c33a98cae77d6b16df72b1ec7bd09b53d62dac15d40874a8906dd2888c9e7edea13ba3c42ad4d833f4a5de43d5ce0d9b12c654ab2e872a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600cc61c46e1babadb7667708e3ed03990398d9bf080ba41c1fdf67b4f51dcdb7e991b6918a6aebaf36df53f828f950a7698a21eeb1c640f809281a08424776cc0674db730277b3ea5a14e52fb0ebca202e56876c666aa160f623a4ca411b7ced702a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d9838ce8807f0de4ac9b15d648031b40e5ce436051756b3b4c852fe07c67715cab2b3cc237fb7f72e95f970760d9e36ece5107e07f41b43dafb771a42517d4027589ac4c910bd1e08bdb36f0236c05cd20349d62e07bd34d89afd21efe56b5fd2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c3136be04b86dbd5ea6d9151f51d16b47aab73e059569dc775951b16a71ac4fa890bba142e61bd67470cd1fac95ed42da2924bf82826caf7c70c3469acba540a7a92827270cfa82f16145a44f9bdd9ff5038ef1b665dd520a2e61db9749094962a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ffac47e7232095f25fdd02a1a5dfdbf3cb9f9c999881194fa50ffb9a6f6cb00db0897043b0d6542770710c727ae5dbbd231eba181ff1c097ade5be59d01389077be4c78d01b0569bdba6cdf39abb6e02f591942133aa1db230033aa48d18fd552a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c550613a2aa3703d95d343186fb5b9ea3e5dbc71389f842cca278d8a83f7735655df193eaa580de1a0f02082385ebf6a1148d93ce610a165135448a83e6a55017cb4b69ff1f333baa130c02d83fb533cb84fc47155a707b7978e8455431f4acf2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600113efdc304c9dedd0baf19c9b54c235fb707a91a7b6730fae65d3860e960d6f6e757109e78422f7d568864de98a9817fcd1d1c435c9a73f13680525da4694b027d32388dbca301421fe038986d97764ed933927b5b74b91ea5320371a31ef12f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600cd7aa2897e1d6d13af72130d66d65248a25ac7003734db33c3d5c59f344ba6bacdaaba9836f959a6ffd140153f8592a0a30146a9ddeb34b75184c17f8cfae10d7dea0d8ef4c3c5dccd46e8d2af6b2a85e8b36156cb97002a43ebee8229b82bff2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460097ba00cbc15f7d2e6c06f7113afeb36984a2322f18960c5fbb8d96833c6aeba2f7eb6f2b485aa1e470bddfe489527dd782b21d3d70c6a49321202f763bba82007e7ddc9a34f7d5e0acd418405ee795740b3a004c977a9ba4ee103755c05aa76a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c42a2e034b4fd25cd46ffe153c8ff0f6cb764ba36c36b2dc125ce88d8593e5e8ddd636b246e1852e3fd20dee2a7d5b026b89aad6cc9080b4042cdb101b05ae057f406fa8642d4d3834434465fe6b1815ecf25a5875e0334010bdfab4768fcbdf2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460059b47b5455aa16792cef813dafa11c7e7955b33fcf0c39a67b85a64e620403aac36542cea1ea2d2015bdc71c09b9c60c33d77bd28c35a69a656c83743b5e63087f4786d6fc06a89b65579200e5d1bd63caeab25893891f06597232389866b4152a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46001f8980114bf02490af98132d5fd4e13b65eb990e39e0dff52f9e152754b83a19f750eb812a63a4255d621cbc095e504e54506c3a1399d421a36e77364d40c20f7f57f0e89ff23959dbdffc8f66bc433fe9849f7a9c335e0601dd59812db059862a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b90eacba52195a184b96f5f19948facb9d5cfff012278471ec8c2ff572e70fb3040cf595588bba91973fc2163e509e0686f56039fb15790ba172aa96ee39d80a818a546c630b881c2161588866965649678cda0f4110cccc3533d0f20e5e41202a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d679230a0317ac247bbbb605e6460aca06ff81c1dc65c5ea9b00d19b3df57265ce079a8be66d5d4047f1a5d81709dec9efa2f675536d3cdfdbe68fd426981d038930b6c9661fb81a752c137a40cc8091480743856cd77ac965f4f6979ca8f30f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46006869e6d4ca689eed4551201be9c4fd6024e38ec95ca0a6125e156b95f4f18ec4bd6b49e1ff7f5397a6d6ba77c27866d9d089eb660a0e7efdc61a8042dc4b8f028934b9a6c38dd420aefa1e115c839fad7a71147a6efc77ab593485c3b07576ac2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460038c61e1249a82da4c868ae51928fb4d21f980e96a9d32887fb9c4f41d2c6032c03c54e5e45914fa716c1e256af33758a5c7d333fa780c771e0e6f66cb0427b08895238f7e22e70f3f59aab49819d5237d6776eeb65c2d5a925b62ddc8d3f0c772a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b819531987ad0190e399e2d1c87559917590850870b8f938da3574953e6cfc7e0b6c6fdce91bdb0e0787c3bd2bdf1541eafb86da210ca85ae2bf6fb0a6fc69038c2e1ccb3645a2466ae46d3f3743ed89c0831a74720eb5c4d8490b1e04e5bd4d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460075c07804bad1b0a7f1211718a6d644cb500bc66416b6004f80903b51b3efc2617d1db5175485a2ede4d7bff6f0e8cf067630f11d62af1561032380150fef6b018d67a0c4b47eb0e087f2cc4c3973b59c9ecb729fc960775d76138ab09799466e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600bfeb58aeb94d24af2fa1fd94a4b9869d40982e3f21696fb70dc9f03a9b80f5d6389e4f4f11852e43900e505c282c8c8110545d547fd0b18cbd88707e8a5fbc058f02169e09026a2500b440eb43eccec58a6a32d9f5cd9644fcf26f5e0c9692632a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600601161377f0932fc08d9127736fbf0052b763f365aba81899e05c804a1c1fe945d13052e264c550e3524d6430d69700b016113b65b6e6e73b22918fac8cba7008f371abcaa2d51351b62e5b400f52086ef7803c1a1351563c5f405a6ac054fb52a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460082af14bf589502d2963520eca5863fe7498a982e11941fe25b5da9349de59cd245ab8b510a09795ab701ebf008eeaa812c877afcebf54e70d95d45ede4839305908ad289176561ffd2c95f7cc8a2ab5c6a0effd161e3aeb1732140aa501edce12a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c19eeac39b52c944ff50ce5bc10e9726646881c798c6ee4a88de3dda17739fb11742e5e35b5c7222ccc4cf95869e91947b993e668471ab724c2c05fa3a37740b926ca2461d028a766de4efe3d8412bd08adc97c9bb3aa28c07a702ca82ac26a12a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c7eeccb5b57eaa678b568d72fd1459886758029cacf2d33f944d382f8945c56ebfc5adf4322f768116fcce9596469910f7fa214568e2ff581f2e583beb633d0a92c3f7a560edfc7927df1631ff35be263beee400f187c090dd02e4a5801a82102a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460019fe510b724fd4773b7fb1c739f1b0b9313d6ac2f6341f693dd1f898bb10bf53bd09fc398d305c55d598588be1f8b9727fa41a086c4d4f0835aeaf26ed50d30098077019fe554385e0cf6e0f3983b510cb37dea7d42f19ce0eccd86e3e147d352a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46003abdcc324544ba69a1b63ca3db2ac95b35e05d37fa96ae24d6a215db9943ea817a5eae59006784a8e1a6ed3ee554035edc0ca54ab4b04b7f95b48e3548ed36049944dcec71efeb5186d55e2d5acec943bb28d1ee9f84db3a2e2177c3d7ee9b842a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46002a4d4e13d1942c7f953c793731ab5c12a45d0a25826281c2d23eb23a1b74f7c40698bbae7c0f032b74ea44c71cd4f550b9295058f2f1dbd7b6f966b00bea110e998b7545adab70a645393ff37884688b9960c8c04989e8575602625d6cc344f12a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b70a4ca5731238626ccbe30d9e740afa075f92a8d217fd224301d1415dca12556a89e0a2797bb2098cd0ae62bba8c4ff98e7d2089092e729bb27c039b71c15039af39dbe3ec236efd64ad0c099648dac388e424ddaebb0cba1793b1ffb9a5e2a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600dc57933c5a5a2ac87b5b72d60ebb537a7bf9bfa2da6e0d45cd322d5408800d7cb1555298dfe74b847e69ffb237754897847afcc0dcafc80bae56209add6f760d9d10dbeb235ce888f8ed0279a2dea721df2f91c3809999301774fc2a5f4272ec2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b0cdd53676e135af460bc95ec069717e7b78747b262d5140a1a193b855d9fbb159cb499b7fda361fd80b7743d10dd383526b70feb8dbc0b06badc2b911fe22009ec49b1bf8a4c76bff00c0aa80adebe0e9249e37fd977babb7be36028b65b00d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d5cdd8a7eb544a9479ffbb0bc74072ba782768cb615ebaa2e39bed4784b4fab6a86c4db944a889ed7dda78b2c0b5cabe5f73c423376ac92c379202a6814fce069f107b3106c7f7570af198895d8314c9deb526a2311da652e5cc2c049212c0c92a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460033038e1fbc0dcaf9212abe66e29d423b659cca10395f78a86774250986079aed49f81381e1dd793a19cb7e0120b636ccc098604815be2e9e2950a9fc857a0f02a6c5e1d8748c139b8d98fc3bc12c3e3a37ac8e0b85847090093b99d0a3c4b9212a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c9730b655bbad75eedda6adc2f28a30882d8986f28bbb6d2f0eb8f367ae64a9eeb9ff25365ccc11c67db4763dd53f1eb6345169be20199da23559be607ccfd0da6d3f9c048fe3ba16d0edc38626afc398f2c032a6790690d1de77ecc6a65b42d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600feafbf9f8ca6a14c50f918d8fb35a0b296e9308ae8b6c4ed7147d2aad912aea13fe7d2639829a5ed64c309c573dba88894374fd480f595bc1d7018ec8b37ae0ea70673aa688422f1775915bc89d6ef922db7f9bef28218f11bb30996a406d9ed2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c21be32aa3713d12b85ad0f157e3877154384e4049d5ac28a98bf19b4e57e1c822e6350c5ca96658ad6f9887440dcf967085e2e4008346b3cb950d46964aad0caae2b6fd7dd2550a95d384a06011091f788cf7218762e59e686ca94fd09422ab2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460050ffac3d9c7f04901bd6dccb14601d1c6a9b0c80587e74eefbf625893e351cf0dc1d4aa08b3030ad5398a461c065186138e50ce56a92d5e46eab62701a036a0aac1daf1e3e59dae210f2b56a2b194bbad084b0c1cf492289b2f03289c635d68b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e7b07a35b9abac9905f77d01f690f01cadbd540fa75c8147808dc84f5655b17d20951de43322a74e3e9cafd48351d58ea0aab31695c3df00d5fa1bb33a997d0dacab0d24dd65b0df17ef62e2b6faca0662890de57163550c52416630850787292a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600881b5c1abdf8a9f8ef9d9d9b27bd9eed35f8930e8ec3bad5ba82490c139005cc78b856fb9c49eafa6581988f7fca348ad60a75723db53122c73d25e858284806accc6b239b9c0c62f0fd1c9cd9f90b823b41d955865bac175488ed4e7da408682a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009edc50b04aedd78e4771634a8333fcf75cb4519e3da0603bd6203475d78a3524dc9d5e6e1b671bf4f75e30f7e1c8bf044bd4577d94375d33123e365c0bb5bb08ae2734ab095cee7ac2ff74777a6d0293ee47303950df193932f2dad4d28526c22a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b987cb9c38a0a401cf03b2488fc73b7faaa5df95e8384d1153cbb57620d1313cd67a9a610414056b91b02127543d33399252687e6984d867cb1e2ea97d83d801af2d390a8e4d1e464e5abd80e9ef716ffb7e656631bc4778deb7834f4fc20dd32a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600bacd2480f5df4b871ac9d49d2e79c619e51ca03330c6e0eb517496a0f163614d24013930325ef5b76031574977e8747c04c6da8352f8c80a8f05444c18c85105b2bb7fd960d5d7ace8e36e0e4ccb7171e5c3740b2ca8a96d868a09cb5e17e32c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c73e0d10678f068c32248f2b015a3a0f415eeb404fc6f89e8589ca1e208ff5949f230d73d985f35920285cfa018d43158f0972b6588320c3756f3b9df9b5600fb38176139100069f1ff20156a180e55782796df79f4683020b06f2a5019da6dd2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46003b29752db4fed93ac5c20b344bce075851fa5cbafe02523c694bec4e3b4109fc0e88bdbd943f0d5325d62929e124a484cb24460dc06728d2039510b906928b01b7626f7e84ac48645871ac12e66bc747b9b1f64eff2352537811f9712c127c342a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c68b27c9db20c07fb3801dfae33f92407cf8e3b9c952f494687140d9a7514f0db75c71c2221b0960d74b23f1dfe643ba2f6c6fb429744e38b2f7f2ecd563d608b771cf172f891bcbbd1126e3354cdd4e324cd7202af62f5fcdcfd2ec01ef7d252a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b272ced92db8b46f6a4a9c0164fd24f66968b23b0dbbf52ebd736b1ce0a4c0512e4715cb0c0e063d580f4b4bea5811950837fc4882aaf5e59aec026ffb0f2300b7c13f1239888cda5c8e6ac9ea10675df17633368906e66a487f91ddd3268ca62a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e155d70b9ec48021bfb05f7aab92008e03f56a19222718aef3cee767a1882805166889d248053ed9f7b4d0c00b19d1931dac509709fe9cd0a577b71c8ba88c06b9186b95c90d2d00a31e7c68066bd37d73408271762604e3608e2f2c983f83092a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600438f0485ea2ab423a63142417de6065b4243e190cd326656e2ebb895489ea898950cbdc05ce8b99ec21c88a96c3953a2366970bf5a0f36c6d0ca34d121115f02b9404a5fb9f1723fc9fd4c5535dd9ef1d67c5237d9d322854697e9e245064e332a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460057b3433e3f0b68a8787e44e78c1ed5f27cfa4b155d3002193a452bd1351be719f47a4f445d635052945cc21df9e54613c7acec835c90a083e89276e7eefe3206bd81133a8d8a33ad2b084c54536d7a17ef0eda5e810a9994b13c2392fea208a92a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600f53a753bb53e0243dead9f8d43f93525d02a0c69b202ed0cafc6afdd50962cc8a0aaaacac45cbbb936748d75a2554173c94531a5bbe20f9664bcd925bb876007c0988a1b3c91b35a7c3722aba7d3a55f79ae07ac6a46d57b4a49a06eae20333e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600276a7f7126f933b4412dc4db264ac434134d77c174c3d2bca0fbca788d0327b24a2a1cd8134dd39cb9faed737ba496ce35b62f663955a88f69adf8f9f7061b06c3576342cbf99792896ee5329b04ff2eee2fc2bb6d53c5c03d52c8957ee793fb2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b5a298d2a97dbd7bf266ccfd61f20f739fd72f1cefac976ebb786e712643de23f8797aa0f160d327ed5210b6bcebb2429dcc673c4b9a39515cbe07dde7d80002c3ff25a1743a9df92af4ccd9a7aed5cbc90f64fd538c3df0a9539128f59652672a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000b87db26d1a4eeeef1f18331c234e6daeb8aea48568b4424e76478eaf1336e1cce3e3b72d136d638be075d9238c58dca48456e8587ce2ef2c46b1d7bbbadf50ac4025624fdf1544b90a61e85099d9e3ac235396d2d9d37f4921162fa688ba95d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b472fc0441578ae962487a27c9903b0e8cef78c4c69521c759f359290290b1fd4ce33692f3726dc51dad68f8ff3cdc88dc6a137d2fa46ea4e10484605c4ed90bc4153342949e45f683cb6df646fe7aca71502b1139c1dfc929afaf73c0a6de822a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c58f80813694cdd8df0fe0586c341a1de7ff7f6dc7c093c2a7333d7e883354a5047ff31079cc00aacfe59aa12207c037e071f710a156247949099120a6afac0fc4374616443e809c1c763459d10d6bf6a2d999855b8d339a27d20d360ed5f1282a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009bcc51ab91092800c4a9519ac8bd2f37c7a7079916a594cef09dcfff47978517c9b1c276749de14c21d010371cfa8da4d5f477787e0348fc71c7cc5c57322a06c49e3cd2c701bb845963c2870dcca12ed070c3f67ffc20144327a93aa6e896ec2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c85a800ac1f59f0df206ad6fa5f26e2f208deda2c0af58ed8c6811c525a9fda21a781689446edfdc0b8a07062daffebe442699974b64cf1c31688fad59ce710bc61cb1b626fea15085663f7619a1769ffbe4fc1f8c63e6ece773acfe180806c92a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600fe7489b632220f8c23b1d66fd40bac34513f1eed3c9a343a59dc09d8d360517db73b05c2e33b0da1932a7cff38748645d8bcfe843290a561c63ebd7b98ec1e0cc63062763b78629518608de49f42c802ec2fc22f477e10a1a0023b237675d65d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460018b4e42adfd1044bce65ff12455dda705b023cc0ffdef4c073aae2180cffac8375975214bb357988f37441c14e1c2913b2c7babed43a0b270920da6970566c08c676bdd3798340c7b35c624f2647f26816ba4b3a0821339f5a0d6c9f2d84ca3a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460034a88c97937fabdc6e2ec582df7cef3829a2ba8b6e57795bb1832e08d07f12912c546f7452b4d0338ce197a9c0a376202387322f371495ebf891a3cb27551400c6a46dbbd2e87ecd0db7ffcffe24c1a82353bc4371d7c2cfb81e7c832d556fce2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460099d000bd6eb0abc52ab88474dc3e504f6e0b21a00cf196317e471926b93410177ae48f2860c98678769671d81b85ec462596ddb585b6e30f4e71e1a98ef80c01c70ad885a3f3ce3fa4041e1477cd801cbff8daa7c835d4463044fed653b3830b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000bef4c68d2fe10b9b34995633ab9dc2c76f43bc2e837252024d85417a12dcddec9cbe44c70891e541f0d67a19e1929de1b0ee5f610808feabb9d9741a0ac7201c8768f257eeaa2a6636fc68a00eb781941c26d2f5179455ac4949a320f958d6f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46008d426d804b32a7615e207fc5fa9c119c9ace79782226bfb6dab21159f3950f18619faafa59caa9a1cd0e0401698137de6b454e45a74899e74681e7e2ee85ac07cb92ffeace78dbafe6fbf275741b4b38657fb81590712aa0bca7877931f6ad392a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46008319290259d0047497e2825a6ce9166f285a7333ade0a970ad928fd1b259feea5c927aa2c742e7fc7b542c579e65979837a77437cf28370a44feef9b8fdb5003cbfefdf389bc341a7e17139f61146f9fb3a9d7bb84fe93c1f771eafd4d4d9d462a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d54d20eb70a33339ff29f8017a48a857c4772ada28e5a88be923045c7ca2194a5683d51472db38d9ee5295fa892963264055b15599226d1ad75aaf550caf9f00cca95e245ef1d3209f6707fec25036c0a1b93ae5613a5c93095e23520395c57b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46006fcea4a22b6b3b28a52fe41d07f4dfcb20f80b2d3308ed4b5b46ed8e4f982a6968bef3e34ac0657b1807f9f0203d905d8dc8caf4ff0c00fb651489984046510bd060d2b638be7c2ff45b20575a76c4acad1b9264607e1b71286e4e00a03266a22a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46001c0549d687700803d27fa15f67df39a92975721e14adc62d267fc7b676c2bdf4d42c66eca0ca0b6fceaceb73d042a59bd88c040aa870c4636f5ee167776fca04d1c146d2429a5a827660008721c7a880e71f44feaa3dc75524c1a9281bac48cd2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460036ad739a304649cce8e4f18021379959c98bf50e9fa1abfd74a9c3778b35f3ee2c2401a0f51794c4c44fd99d167ec94a52529761c41182fc708a6bc25a32b50ed1deb1ed8d4e155f1b5bbc54610756cf2541f43b7c1776f97d404503878b96002a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46002e1b05424cd82fec9717f23563923e095c71221e13a284261e553617e94c2f4d053c28d9652abefb837ba0d9cedce01d40696794117fca8f2b232ba97d418802d2c1a8caace45c00d73f64f241162f85842c35e557e097fd1749040fedc94df92a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46001b1722198be15749cb2c16b9b0cd42dbb320c3f6ffaa880fa90a8a8db74ad322f69bdc2762796ae752ac4377637aa6e0d757320c78d8a2b3f47a8fd0a1214303d3adef5f9366e150e5a21d8837947fc2378997c7658245fdf0bca95a513f04f92a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d202e0bbf1b01acf647b11dd367019b763c8b37f1a9cf1d1a1fe663f5cf1aa6c3c9f4a6675515e9ff47255a6627e888de370f118abc6be342d624a6a83dd960fd416f2796d696faaeb34d2cdaf1004ede551b62a690f99d69b4ccd5f4a6c248b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460028caec545fd07031c5e73f34814d463636013534942812417d2317c7a557af8d21c742280eeecf514916493746db6d6ee1e059170ea0123220a6ae57c3c8e102d4b350a3b25c27c9ab9366d130560326fe6bb7f3a820767736b8a061405e153e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e5a071915555df4404f783902b68cb3c44e4ae8485d043cec0e615ff63719e6eec2cab48ed142c04efffb4321724cedaa427d27e9bed99344dba219a533b6b09d67211ff6ded7cffc866c81e24a9f54f08cd1df7ab202ae796a01d72cd2d3ab32a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460029a7b0b7a6b4a80c7e89b1cc2bb8635bea1587ad214100da7b11add353cd3e69df0b8dfdf17264eae2753de1ef1d62d267cd7214768bda15f56f9e6b00612902d744af98bbb366011a0fe90c3cee81d21cb301d9bb72d5b40b28da348ecb81672a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d77c1a9378d17bbba63e854c9bfd0abab0b3650a74bd698d7b9feea4f588d5dfa2ce724f24f60d844a4feac5d9b5928f4c537caa9230ef2ac5d1d9953b1b330cd7be80fafe9de0570984b008636808811ccfd82f32639cf6806dfb86c4c4c4c72a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d373bbbb4349a52b00d00a3981a161ed96bd0fa969faf1dbb8b49879f86dd5839b0ab12856f912ebe2c8e8a12d82dc7688729a6449d3bb6b7a6d53f66d82d203d86e516b7564a8ab0b67b503c97976157c05fc2395068b27df5f38a1a9b29b6e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46005065b2652114169f82c0c1483c19f8fd2f0c72f8c98928fe419ce0b93b2ef1583cb684837009c4c97381eb83c880f62055e9068113d9e07d1b29c991521a5308d8a6314d0690c070c764863c7c25fb8e9d6cd462f9bd56b0b236558f66fd74fa2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460016e5038faa65afb9f179af818be6b65c1a93963effe329ecba6786891ac1d8956e3f209365e45bcaec3879ff27891202e219e8061228f7001446f532b0bc0d09d978b1073d9c88d84ece71a915d577b8b381938d07827447ec20a5bb250496d22a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d57606898de72fb911f85da1bb8fb3743b6edc0e1c61a61bc05d3b6d1d429d5b6d86e38ae7289b40efbb3df89d14f5704f8e61bf2bfe195470f8a8fe9146d009db1067e5c50401a17b926e54a98e103abd64c4b83c87b770a8312ff03bc29be72a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ef38ecce8f3b7f2253942c612b029e05a6b37f8b1c406f5d01479ec76f956cd040f3a679a9e77a037401185a665f3162531feab7221d828e4bf7662ea876000ddb231b388a5ba18b2568eb1ab9ec84a637a66d6c1286fc7e1c2351250db635c32a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000d66f46119003f7476f2115373c0b88a81e3044ef83d9955fce655439adcaf22a12b74b5fd18db965e694d5635f4aa2418a263decf6c448b891462432ee7ae05db3aeeb826b44e7808e3865df0da2f479442e2f2fc895a46dffa8d908ec95f512a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460065ba17675d688b0fe1ae25984335d388c61f14a81ceddf6b6585b90a9a59bba0a4070d815018326acd57a88edbef65d60ba226554112f06c1f33b0c6b39b9401dc1aada86981424a634bbc637e58712bc5002a34bb6498487d89d83534b05da02a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46005565dd6e8a390aa49b5491cd0bce4798501a87884ef50ba120262b826ff28ba5741930e988241f4efcc0cd42f7c621033166193fc85b00b792d41135172d780fdcee76cd80cf2218de9e8c4ae1922fe1c26235f9cb479b2950b064025a1d69412a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600f796ea2ed7675e68b13fa0df5a873706de9c3490e006b4b7e330668ca62ba0eb7284b5087a09b9ae01e2ffa3aed1a545a1de99ea38b0bc183d46884315170502e2cc4424464983ada824bbef5aaa8995e80ef1017c15ef3b13902599841637ba2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46006abff33b091bfe069c92ba5bcd25d56b604a45cd1023080614e8e8578f9327c7025a9bb2ba92c4db3e699015ac4f46dacec10245d86b73d41b9e751bc7f6e203e58210689f52468a22c10566f3a2c6f870e61524298a3e781425861d40446b132a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d2a95ca51c964bb35183f2ae626a09a01b7b7e82a046af708da537389d68757b8a5d5c8f230523c5ec089277b7c0e072fcf4d9dca2ecd7bb6a3eec6e95919c01e6ebe15e9e2c9f0fe234241ee50e9c574774807c0d17f8dbc7fdd1802c5c79792a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600254314bb9821d205214651a8c292292089a320ccde7dd38acb21552a855eb0ace84b132f04317b591b84be06679d74df67b46abee0128089679c45e6f7836c00e7210714763e5bb3fab12067d0a55784ae0d70ef14ccf9e243bbfc6d329834102a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e3b32fd44cc8a2dfd85b2717b54a4d21d93a8f40b0c01be21f81dbc482b383d2a4c32911a93fcc4c8a746dc75aa0925c6b266b397a17f05318e6cebf51661001e81206b483fdcf1fe42145f27d1efb8178a57ee24b196285374037da2a53b3232a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46008b0b5494d43206cf55b468597dbb9dcfc51cb59a5b94a2f2150d5bed423057867958b09d96d7b772d41d2ffb7f68125ea119f0d628f2ad338856a34fbdd8ef05e90f0dd5c3d8e23deb6b82b9d7335855f5887c3b77ac1fe30d9112b22319bc922a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d2a3355b20c0bb0286e1951b985ac51bea4439b0c70ede6f38c026495bdc948e2df2f72d00b1d3d7a2bedfabe8a57d6d4f0af59670b5e75eb713a1ec2cf7d802e9836cd1a96c0010f43161443a790d65ea2afdef21b05bd563ed55c6eecb00662a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460015d31429c5579f409756d92155519c4bd60ca1b095c8f5a5aac66459efa937f48989d599106e5749cb74e8718bb96c49119ec5aa11adb3251a9365456809ae0aeb0af0662538c295c85d7da7b1e0d929f8d887a55bf1a136a144f2f7cf3215802a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460083b2ce5b2c3df07146efcb19e6dd1d8755ca7ef52ba3b05e3a2830374a4f984a4a2946c0905d47f962bf14e51bb0aba4303806247135d579317349e421550505ee8f86db1c1d2b7cd11e962fcfd1a7e847204337c8893f1c1ddb75c6bff3f2bb2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600dd14dbdb4c43afa43d3da56daec9d48f09e356082e2317b505fc1a8520eb491d6fd344bc94479d9497be72d2dbc95d193c6bcaaaec8c5df928b37e8373cfb005eecc027b5c05b7158fa0614fd70df47412f181e808bb78cbb63e29adf14ec91c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460053e0270ad201d8ccbd4253715af906a56e2f44c6d78f9344da12e465a5c755ba569b0f5db093834921b8ed11f4df2dd52497b387efdf3948ff632ae40f7da504f1020dda4f7e6af02b444bfcc70a0a18bd48042ad72b1955778bf4e56086a33f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ff32188333a59ee7d640f5897f5415cdf08e97742f82d340687c63b71c0ba9ae42599d522a40233b772d6147edd01a7d5bc865714f95ff979c03e61b9d1a6a03f2a930984c2799a98d298521128eff1add9706c9e57e848d54de41f6b8dac4b42a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460039c0604b3ad216bbdfc17d641114a0d25e0865ff21cc945574cda12f7c41f7e19f3b65550b410b017e0e8f58c94d9abd29f01b7036193305fbecd341e7156509f5054136e1ea0e3956293c422a147eaa4950cfcf0ad9596dc6e6a287bdfb06602a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460029e0bf534f510a4cd43b5011d63057da67b36e2b03cf6087b3bb8ec80fdbb9e63b5e361313e2dee669d1d5644696e5c8b4e71548ec9c6daf4c85873318d9b708f8d4441b4ef1c2ee50272e1767ac4f773a249eb663c7e37bc075951f3b52ede52a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600753c2d7a7193f908ed808903b7cfbc3e2f5a8d9fb380c054f887c0fe21b206d81406d25bb1d0ef6cb6a0feaf015f9e96c0d703e9bc5a0e956feff40fdf0f0d0ef9887f6f0a8673aad0c10d1bf6ae3ae5f0089c8f05d54e180e16030949a8f7782a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d0c3538d97ce8594f8a113d01728ac4b13549646557385c6b5263f4a67a0ba96e2e5f0ee3ac9c85fff368b4013eb43900be5e672375a3f3860298342a0325f05fa786562ceec7e61851571e8eaae35c7f0f4b2ada3ddb4d577c66ab50a1d6ceb2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600eb8636c254b4cec68eea4ef00a3ae43b6dba9c8f85958b514042d0764f66bc9b22e4205ff4d8d4427395df3aeb15fc840356a9eabacee3a23c8cd6aff517db05faf968e9e1468d58947b9217ed7aecf19ce457036a2b46e47c44e5a1d67e5b1f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a7038a3eb4b56c7414816925b26d1756706f2256596c99181ae8746a40cc32c5192c93a13997c9a11f31fa6d6263164e0c06034f0790400f1730c250f4054205fbcc3ce7dbd34cd50cb3563d2b2f62a5ff2bc847e6b6e79faee63257978f9c192a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600762a2c5d7849ffbd8c2560f7e6638ede211a0e59347bb1c7f629445e6af0d38826319366d3597f0773b49515907516f2e1894618f8517297b4624866c28e3e0ffc678679f11bc904720421273689e6826acb42b21e2c4c5c1d7bf532d89668412a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46006dbd8fd05b77b7556cef50351310ca989db725fedbe17fbdc079043ba50dd86f7fe96f621e1eafb13aaf097fed1d441a90e6462d4919a752441455bf49698a0dfd9e354ed59f35c917c42471588142e715ed2151e89ecaf98bb5d837515d74772a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460010d0c657b622c3927cb79d4e34d0d1608e0b4f12294277049528b4a3e4add5745ce5cf9048f16607fcba67325cf06c460d2766421210dbcbeff29b1f0dc39402ff0bf39c82ed573d48585448b0fc19ba3f6203806cb6b4a230848892c097a26f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d072ab3364ee35ffd547d78aba38e764805b6caeff708dcd806acd06ee2d684cccddbacd4c5d3ba73a6f6e03d3749635ee8eaff19137ec7dc686c46af8f76b07fff437ff18629bf1490e5c9b3ec6f1515d46bb9b2aeaa6e39e36611f2479b50d00") fj := new(FullJustification) rw := &bytes.Buffer{} rw.Write(data) + err := fj.Decode(rw) require.NoError(t, err) - t.Log(fj) require.Equal(t, uint64(6971), fj.Round) require.Equal(t, uint32(4635975), fj.Commit.Number) require.Equal(t, common.MustHexToHash("0x2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f7022"), fj.Commit.Hash) From b6d78ff54242888dacd9a0fb9f84ca634a7bc122 Mon Sep 17 00:00:00 2001 From: noot Date: Thu, 15 Apr 2021 19:40:11 -0400 Subject: [PATCH 33/40] fix tests --- dot/network/sync_justification.go | 51 +------------------------- dot/network/sync_justification_test.go | 32 ---------------- dot/services_test.go | 2 +- dot/sync/syncer_test.go | 10 +++++ lib/grandpa/grandpa_test.go | 22 +++++------ lib/grandpa/message_handler.go | 32 +--------------- lib/grandpa/message_handler_test.go | 18 ++++----- lib/grandpa/message_test.go | 49 +++++++++++++++---------- lib/grandpa/network.go | 10 ++--- lib/grandpa/types.go | 2 + lib/grandpa/types_test.go | 2 +- 11 files changed, 70 insertions(+), 160 deletions(-) diff --git a/dot/network/sync_justification.go b/dot/network/sync_justification.go index 59ed4ea694..23714c9b8d 100644 --- a/dot/network/sync_justification.go +++ b/dot/network/sync_justification.go @@ -18,60 +18,11 @@ package network import ( "math/big" - //"time" "github.com/libp2p/go-libp2p-core/peer" ) -// func (q *syncQueue) finalizeAtHead() { -// prev, err := q.s.blockState.GetFinalizedHeader(0, 0) -// if err != nil { -// logger.Error("failed to get latest finalized block header", "error", err) -// return -// } - -// for { -// select { -// // sleep for average block time TODO: make this configurable from slot duration -// case <-time.After(q.slotDuration * 2): -// case <-q.ctx.Done(): -// return -// } - -// head, err := q.s.blockState.BestBlockNumber() -// if err != nil { -// continue -// } - -// if head.Int64() < q.goal { -// continue -// } - -// curr, err := q.s.blockState.GetFinalizedHeader(0, 0) -// if err != nil { -// continue -// } - -// logger.Debug("checking finalized blocks", "curr", curr.Number, "prev", prev.Number) - -// if curr.Number.Cmp(prev.Number) > 0 { -// prev = curr -// continue -// } - -// prev = curr - -// start := head.Uint64() - uint64(blockRequestSize) -// if curr.Number.Uint64() > start { -// start = curr.Number.Uint64() + 1 -// } else if int(start) < int(blockRequestSize) { -// start = 1 -// } - -// q.pushJustificationRequest(start) -// } -// } - +// SendJustificationRequest pushes a justification request to the queue to be sent out to the network func (s *Service) SendJustificationRequest(to peer.ID, num uint32) { s.syncQueue.pushJustificationRequest(to, uint64(num)) } diff --git a/dot/network/sync_justification_test.go b/dot/network/sync_justification_test.go index 00d854471a..bcb791d9bc 100644 --- a/dot/network/sync_justification_test.go +++ b/dot/network/sync_justification_test.go @@ -18,7 +18,6 @@ package network import ( "context" - "math/big" "testing" "time" @@ -135,34 +134,3 @@ func TestSyncQueue_processBlockResponses_Justification(t *testing.T) { require.True(t, ok) require.Equal(t, 2, score) } - -func TestSyncQueue_finalizeAtHead(t *testing.T) { - q := newTestSyncQueue(t) - q.stop() - time.Sleep(time.Second) - q.ctx = context.Background() - q.slotDuration = time.Millisecond * 200 - - hash, err := q.s.blockState.GetHashByNumber(big.NewInt(1)) - require.NoError(t, err) - - go q.finalizeAtHead() - time.Sleep(time.Second) - - data, has := q.justificationRequestData.Load(hash) - require.True(t, has) - require.Equal(t, requestData{}, data) - - expected := createBlockRequestWithHash(hash, blockRequestSize) - expected.RequestedData = RequestedDataJustification - - select { - case req := <-q.requestCh: - require.Equal(t, &syncRequest{ - req: expected, - to: "", - }, req) - case <-time.After(time.Second): - t.Fatal("did not receive request") - } -} diff --git a/dot/services_test.go b/dot/services_test.go index 91f55d2d09..e5338c7251 100644 --- a/dot/services_test.go +++ b/dot/services_test.go @@ -148,7 +148,7 @@ func TestCreateSyncService(t *testing.T) { ver, err := createBlockVerifier(stateSrvc) require.NoError(t, err) - _, err = createSyncService(cfg, stateSrvc, nil, nil, ver, rt) + _, err = createSyncService(cfg, stateSrvc, nil, nil, nil, ver, rt) require.NoError(t, err) } diff --git a/dot/sync/syncer_test.go b/dot/sync/syncer_test.go index 6872732975..9f3b47904f 100644 --- a/dot/sync/syncer_test.go +++ b/dot/sync/syncer_test.go @@ -41,6 +41,12 @@ import ( "github.com/stretchr/testify/require" ) +type mockFinalityGadget struct{} + +func (m mockFinalityGadget) VerifyBlockJustification(_ []byte) error { + return nil +} + func newTestGenesisWithTrieAndHeader(t *testing.T) (*genesis.Genesis, *trie.Trie, *types.Header) { gen, err := genesis.NewGenesisFromJSONRaw("../../chain/gssmr/genesis.json") require.NoError(t, err) @@ -102,6 +108,10 @@ func newTestSyncer(t *testing.T) *Service { cfg.LogLvl = log.LvlDebug } + if cfg.FinalityGadget == nil { + cfg.FinalityGadget = &mockFinalityGadget{} + } + syncer, err := NewService(cfg) require.NoError(t, err) return syncer diff --git a/lib/grandpa/grandpa_test.go b/lib/grandpa/grandpa_test.go index f7ebdc3bb3..3beab53b47 100644 --- a/lib/grandpa/grandpa_test.go +++ b/lib/grandpa/grandpa_test.go @@ -277,7 +277,7 @@ func TestGetPossibleSelectedAncestors_SameAncestor(t *testing.T) { // this should return the highest common ancestor of (a, b, c) with >=2/3 votes, // which is the node at depth 6. require.Equal(t, 1, len(blocks)) - require.Equal(t, uint64(6), blocks[expected]) + require.Equal(t, uint32(6), blocks[expected]) } func TestGetPossibleSelectedAncestors_VaryingAncestor(t *testing.T) { @@ -330,8 +330,8 @@ func TestGetPossibleSelectedAncestors_VaryingAncestor(t *testing.T) { // this should return the highest common ancestor of (a, b) and (b, c) with >=2/3 votes, // which are the nodes at depth 6 and 7. require.Equal(t, 2, len(blocks)) - require.Equal(t, uint64(6), blocks[expectedAt6]) - require.Equal(t, uint64(7), blocks[expectedAt7]) + require.Equal(t, uint32(6), blocks[expectedAt6]) + require.Equal(t, uint32(7), blocks[expectedAt7]) } func TestGetPossibleSelectedAncestors_VaryingAncestor_MoreBranches(t *testing.T) { @@ -390,8 +390,8 @@ func TestGetPossibleSelectedAncestors_VaryingAncestor_MoreBranches(t *testing.T) // this should return the highest common ancestor of (a, b) and (b, c) with >=2/3 votes, // which are the nodes at depth 6 and 7. require.Equal(t, 2, len(blocks)) - require.Equal(t, uint64(6), blocks[expectedAt6]) - require.Equal(t, uint64(7), blocks[expectedAt7]) + require.Equal(t, uint32(6), blocks[expectedAt6]) + require.Equal(t, uint32(7), blocks[expectedAt7]) } func TestGetPossibleSelectedBlocks_OneBlock(t *testing.T) { @@ -462,7 +462,7 @@ func TestGetPossibleSelectedBlocks_EqualVotes_SameAncestor(t *testing.T) { // this should return the highest common ancestor of (a, b, c) require.Equal(t, 1, len(blocks)) - require.Equal(t, uint64(6), blocks[expected]) + require.Equal(t, uint32(6), blocks[expected]) } func TestGetPossibleSelectedBlocks_EqualVotes_VaryingAncestor(t *testing.T) { @@ -509,8 +509,8 @@ func TestGetPossibleSelectedBlocks_EqualVotes_VaryingAncestor(t *testing.T) { // this should return the highest common ancestor of (a, b) and (b, c) with >=2/3 votes, // which are the nodes at depth 6 and 7. require.Equal(t, 2, len(blocks)) - require.Equal(t, uint64(6), blocks[expectedAt6]) - require.Equal(t, uint64(7), blocks[expectedAt7]) + require.Equal(t, uint32(6), blocks[expectedAt6]) + require.Equal(t, uint32(7), blocks[expectedAt7]) } func TestGetPossibleSelectedBlocks_OneThirdEquivocating(t *testing.T) { @@ -651,7 +651,7 @@ func TestGetPreVotedBlock_MultipleCandidates(t *testing.T) { block, err := gs.getPreVotedBlock() require.NoError(t, err) require.Equal(t, expected, block.hash) - require.Equal(t, uint64(7), block.number) + require.Equal(t, uint32(7), block.number) } func TestGetPreVotedBlock_EvenMoreCandidates(t *testing.T) { @@ -714,7 +714,7 @@ func TestGetPreVotedBlock_EvenMoreCandidates(t *testing.T) { block, err := gs.getPreVotedBlock() require.NoError(t, err) require.Equal(t, expected, block.hash) - require.Equal(t, uint64(5), block.number) + require.Equal(t, uint32(5), block.number) } func TestIsCompletable(t *testing.T) { @@ -1083,7 +1083,7 @@ func TestGetGrandpaGHOST_MultipleCandidates(t *testing.T) { block, err := gs.getGrandpaGHOST() require.NoError(t, err) require.Equal(t, expected, block.hash) - require.Equal(t, uint64(3), block.number) + require.Equal(t, uint32(3), block.number) pv, err := gs.getPreVotedBlock() require.NoError(t, err) diff --git a/lib/grandpa/message_handler.go b/lib/grandpa/message_handler.go index 9e26ae0f68..5fa026b22c 100644 --- a/lib/grandpa/message_handler.go +++ b/lib/grandpa/message_handler.go @@ -48,7 +48,7 @@ func NewMessageHandler(grandpa *Service, blockState BlockState) *MessageHandler // HandleMessage handles a GRANDPA consensus message // if it is a FinalizationMessage, it updates the BlockState // if it is a VoteMessage, it sends it to the GRANDPA service -func (h *MessageHandler) handleMessage(from peer.ID, msg *ConsensusMessage) (network.Message, error) { +func (h *MessageHandler) handleMessage(from peer.ID, msg *ConsensusMessage) (network.NotificationsMessage, error) { if msg == nil || len(msg.Data) == 0 { logger.Trace("received nil message or message with nil data") return nil, nil @@ -104,39 +104,9 @@ func (h *MessageHandler) handleNeighbourMessage(from peer.ID, msg *NeighbourMess return nil } - // TODO: handle setID, round as well; track number->(setID, round) ? - logger.Info("got neighbour message", "number", msg.Number, "set id", msg.SetID, "round", msg.Round) h.blockNumToSetID.Store(msg.Number, msg.SetID) h.grandpa.network.SendJustificationRequest(from, msg.Number) - - // head, err := h.grandpa.blockState.BestBlockNumber() - // if err != nil { - // return err - // } - - // // don't finalize too close to head, until we add justification request + verification functionality. - // // this prevents us from marking the wrong block as final and getting stuck on the wrong chain - // if uint32(head.Int64())-4 < msg.Number { - // return nil - // } - - // // TODO: instead of assuming the finalized hash is the one we currently know about, - // // request the justification from the network before setting it as finalized. - // hash, err := h.grandpa.blockState.GetHashByNumber(big.NewInt(int64(msg.Number))) - // if err != nil { - // return err - // } - - // if err = h.grandpa.blockState.SetFinalizedHash(hash, msg.Round, msg.SetID); err != nil { - // return err - // } - - // if err = h.grandpa.blockState.SetFinalizedHash(hash, 0, 0); err != nil { - // return err - // } - - // logger.Info("🔨 finalized block", "number", msg.Number, "hash", hash) return nil } diff --git a/lib/grandpa/message_handler_test.go b/lib/grandpa/message_handler_test.go index 3b5ef632e7..15e4aeb590 100644 --- a/lib/grandpa/message_handler_test.go +++ b/lib/grandpa/message_handler_test.go @@ -17,7 +17,7 @@ package grandpa import ( - "errors" + //"errors" "math/big" "testing" "time" @@ -29,7 +29,7 @@ import ( "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/lib/scale" - "github.com/ChainSafe/chaindb" + //"github.com/ChainSafe/chaindb" "github.com/stretchr/testify/require" ) @@ -73,7 +73,7 @@ func createSignedVoteMsg(t *testing.T, number uint32, round, setID uint64, pk *e func TestDecodeMessage_VoteMessage(t *testing.T) { cm := &ConsensusMessage{ - Data: common.MustHexToBytes("0x004d000000000000006300000000000000017db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a777700000000000036e6eca85489bebbb0f687ca5404748d5aa2ffabee34e3ed272cc7b2f6d0a82c65b99bc7cd90dbc21bb528289ebf96705dbd7d96918d34d815509b4e0e2a030f34602b88f60513f1c805d87ef52896934baf6a662bc37414dbdbf69356b1a691"), + Data: common.MustHexToBytes("0x004d000000000000006300000000000000017db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a7777000036e6eca85489bebbb0f687ca5404748d5aa2ffabee34e3ed272cc7b2f6d0a82c65b99bc7cd90dbc21bb528289ebf96705dbd7d96918d34d815509b4e0e2a030f34602b88f60513f1c805d87ef52896934baf6a662bc37414dbdbf69356b1a691"), } msg, err := decodeMessage(cm) @@ -100,7 +100,7 @@ func TestDecodeMessage_VoteMessage(t *testing.T) { func TestDecodeMessage_FinalizationMessage(t *testing.T) { cm := &ConsensusMessage{ - Data: common.MustHexToBytes("0x054d000000000000007db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a0000000000000000040a0b0c0d00000000000000000000000000000000000000000000000000000000e7030000000000000102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034602b88f60513f1c805d87ef52896934baf6a662bc37414dbdbf69356b1a691"), + Data: common.MustHexToBytes("0x054d000000000000007db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a00000000040a0b0c0d00000000000000000000000000000000000000000000000000000000e70300000102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034602b88f60513f1c805d87ef52896934baf6a662bc37414dbdbf69356b1a691"), } msg, err := decodeMessage(cm) @@ -199,8 +199,8 @@ func TestMessageHandler_NeighbourMessage(t *testing.T) { cm, err := msg.ToConsensusMessage() require.NoError(t, err) - _, err = h.handleMessage("", cm) - require.True(t, errors.Is(err, chaindb.ErrKeyNotFound)) + // _, err = h.handleMessage("", cm) + // require.True(t, errors.Is(err, chaindb.ErrKeyNotFound)) block := &types.Block{ Header: &types.Header{ @@ -217,9 +217,9 @@ func TestMessageHandler_NeighbourMessage(t *testing.T) { require.NoError(t, err) require.Nil(t, out) - finalized, err := st.Block.GetFinalizedHash(0, 0) - require.NoError(t, err) - require.Equal(t, block.Header.Hash(), finalized) + // finalized, err := st.Block.GetFinalizedHash(0, 0) + // require.NoError(t, err) + // require.Equal(t, block.Header.Hash(), finalized) } func TestMessageHandler_VerifyJustification_InvalidSig(t *testing.T) { diff --git a/lib/grandpa/message_test.go b/lib/grandpa/message_test.go index 5064b5c58a..d0f89a3689 100644 --- a/lib/grandpa/message_test.go +++ b/lib/grandpa/message_test.go @@ -7,6 +7,7 @@ import ( "github.com/ChainSafe/gossamer/dot/state" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/crypto/ed25519" "github.com/ChainSafe/gossamer/lib/scale" "github.com/stretchr/testify/require" @@ -38,28 +39,38 @@ func TestVoteMessageToConsensusMessage(t *testing.T) { // test precommit vm, err := gs.createVoteMessage(v, precommit, gs.keypair) require.NoError(t, err) - - cm, err := vm.ToConsensusMessage() - require.NoError(t, err) - - expected := &ConsensusMessage{ - Data: common.MustHexToBytes("0x014d000000000000006300000000000000017db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a7777000000000000a28633c3a1046351931209fe9182fd530dc659d54ece48e9f88f4277e47f39eb78a84d50e3d37e1b50786d88abafceb5137044b6122fb6b7b5ae8ff62787cc0e34602b88f60513f1c805d87ef52896934baf6a662bc37414dbdbf69356b1a691"), + vm.Message.Signature = [64]byte{} + + expected := &VoteMessage{ + Round: gs.state.round, + SetID: gs.state.setID, + Stage: precommit, + Message: &SignedMessage{ + Hash: v.hash, + Number: v.number, + AuthorityID: gs.keypair.Public().(*ed25519.PublicKey).AsBytes(), + }, } - require.Equal(t, expected, cm) + require.Equal(t, expected, vm) // test prevote vm, err = gs.createVoteMessage(v, prevote, gs.keypair) require.NoError(t, err) - - cm, err = vm.ToConsensusMessage() - require.NoError(t, err) - - expected = &ConsensusMessage{ - Data: common.MustHexToBytes("0x004d000000000000006300000000000000007db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a7777000000000000215cea37b45853e63d4cc2f0a04c7a33aec9fc5683ac46b03a01e6c41ce46e4339bb7456667f14d109b49e8af26090f7087991f3b22494df997551ae44a0ef0034602b88f60513f1c805d87ef52896934baf6a662bc37414dbdbf69356b1a691"), + vm.Message.Signature = [64]byte{} + + expected = &VoteMessage{ + Round: gs.state.round, + SetID: gs.state.setID, + Stage: prevote, + Message: &SignedMessage{ + Hash: v.hash, + Number: v.number, + AuthorityID: gs.keypair.Public().(*ed25519.PublicKey).AsBytes(), + }, } - require.Equal(t, expected, cm) + require.Equal(t, expected, vm) } func TestFinalizationMessageToConsensusMessage(t *testing.T) { @@ -73,14 +84,14 @@ func TestFinalizationMessageToConsensusMessage(t *testing.T) { } fm := gs.newFinalizationMessage(gs.head, 77) - cm, err := fm.ToConsensusMessage() - require.NoError(t, err) - expected := &ConsensusMessage{ - Data: common.MustHexToBytes("0x054d000000000000007db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a0000000000000000040a0b0c0d00000000000000000000000000000000000000000000000000000000e7030000000000000102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034602b88f60513f1c805d87ef52896934baf6a662bc37414dbdbf69356b1a691"), + expected := &FinalizationMessage{ + Round: 77, + Vote: NewVoteFromHeader(gs.head), + Justification: gs.justification[77], } - require.Equal(t, expected, cm) + require.Equal(t, expected, fm) } func TestNewCatchUpResponse(t *testing.T) { diff --git a/lib/grandpa/network.go b/lib/grandpa/network.go index f3c449393a..5fd7e31b1c 100644 --- a/lib/grandpa/network.go +++ b/lib/grandpa/network.go @@ -130,16 +130,14 @@ func (s *Service) handleNetworkMessage(from peer.ID, msg NotificationsMessage) e return ErrInvalidMessageType } - _, err := s.messageHandler.handleMessage(from, cm) + resp, err := s.messageHandler.handleMessage(from, cm) if err != nil { return err } - // // this is a bit convoluted, might be nice to just call this in the neighbour message handler - // if msg, ok := resp.(*network.BlockRequestMessage); ok { - // //s.network.SendMessage(resp) - // s.network.SendJustificationRequest(from, uint32(msg.StartingBlock.Uint64())) - // } + if resp != nil { + s.network.SendMessage(resp) + } return nil } diff --git a/lib/grandpa/types.go b/lib/grandpa/types.go index d9d4641012..d783ce3fd4 100644 --- a/lib/grandpa/types.go +++ b/lib/grandpa/types.go @@ -224,6 +224,7 @@ func (v *Vote) String() string { } // Justification represents a justification for a finalized block +// TODO: rename to SignedPrecommit type Justification struct { Vote *Vote Signature [64]byte @@ -249,6 +250,7 @@ func (j *Justification) Decode(r io.Reader) (*Justification, error) { return i.(*Justification), err } +// Commit contains all the signed precommits for a given block type Commit struct { Hash common.Hash Number uint32 diff --git a/lib/grandpa/types_test.go b/lib/grandpa/types_test.go index 8f74d41904..f788234a6c 100644 --- a/lib/grandpa/types_test.go +++ b/lib/grandpa/types_test.go @@ -80,7 +80,7 @@ func TestFullJustification(t *testing.T) { AuthorityID: testAuthorityID, } - fj := FullJustification{ + fj := &FullJustification{ Round: 99, Commit: &Commit{ Precommits: []*Justification{just}, From 4adad9ad307f81894551754b6f5ed55b9a445d0d Mon Sep 17 00:00:00 2001 From: noot Date: Thu, 15 Apr 2021 19:47:45 -0400 Subject: [PATCH 34/40] lint --- dot/sync/interface.go | 1 + dot/sync/syncer.go | 4 +++- lib/grandpa/message_handler.go | 6 ++++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/dot/sync/interface.go b/dot/sync/interface.go index 408974b3bf..83bd9aa2ca 100644 --- a/dot/sync/interface.go +++ b/dot/sync/interface.go @@ -79,6 +79,7 @@ type Verifier interface { VerifyBlock(header *types.Header) error } +// FinalityGadget implements justification verification functionality type FinalityGadget interface { VerifyBlockJustification([]byte) error } diff --git a/dot/sync/syncer.go b/dot/sync/syncer.go index a62b9fe969..aa401c3769 100644 --- a/dot/sync/syncer.go +++ b/dot/sync/syncer.go @@ -152,7 +152,9 @@ func (s *Service) HandleBlockAnnounce(msg *network.BlockAnnounceMessage) error { return nil } -func (s *Service) ProcessJustification(data []*types.BlockData) (int, error) { +// ProcessJustification processes block data containing justifications +// TODO: use this +func (s *Service) ProcessJustification(data []*types.BlockData) (int, error) { //nolint if len(data) == 0 { return 0, ErrNilBlockData } diff --git a/lib/grandpa/message_handler.go b/lib/grandpa/message_handler.go index 5fa026b22c..b7034756cf 100644 --- a/lib/grandpa/message_handler.go +++ b/lib/grandpa/message_handler.go @@ -104,7 +104,8 @@ func (h *MessageHandler) handleNeighbourMessage(from peer.ID, msg *NeighbourMess return nil } - logger.Info("got neighbour message", "number", msg.Number, "set id", msg.SetID, "round", msg.Round) + // TODO: make linter british + logger.Info("got neighbor message", "number", msg.Number, "set id", msg.SetID, "round", msg.Round) h.blockNumToSetID.Store(msg.Number, msg.SetID) h.grandpa.network.SendJustificationRequest(from, msg.Number) return nil @@ -394,6 +395,7 @@ func (h *MessageHandler) verifyJustification(just *Justification, round, setID u return nil } +// VerifyBlockJustification verifies the finality justification for a block func (s *Service) VerifyBlockJustification(justification []byte) error { r := &bytes.Buffer{} _, _ = r.Write(justification) @@ -403,7 +405,7 @@ func (s *Service) VerifyBlockJustification(justification []byte) error { return err } - logger.Info("full justification", "round", fj.Round, "hash", fj.Commit.Hash, "number", fj.Commit.Number, "sig count", len(fj.Commit.Precommits)) + logger.Debug("verifiying justification", "round", fj.Round, "hash", fj.Commit.Hash, "number", fj.Commit.Number, "sig count", len(fj.Commit.Precommits)) for _, just := range fj.Commit.Precommits { // TODO: when catch up is done, we should know all the setIDs From e6614a14ddc9fe9cc0b5a4f7d55e29f4a9e575ac Mon Sep 17 00:00:00 2001 From: noot Date: Thu, 15 Apr 2021 19:51:13 -0400 Subject: [PATCH 35/40] cleanup --- lib/grandpa/message_handler.go | 2 +- lib/grandpa/message_handler_test.go | 9 --------- lib/scale/decode.go | 1 - 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/lib/grandpa/message_handler.go b/lib/grandpa/message_handler.go index b7034756cf..5c43657213 100644 --- a/lib/grandpa/message_handler.go +++ b/lib/grandpa/message_handler.go @@ -105,7 +105,7 @@ func (h *MessageHandler) handleNeighbourMessage(from peer.ID, msg *NeighbourMess } // TODO: make linter british - logger.Info("got neighbor message", "number", msg.Number, "set id", msg.SetID, "round", msg.Round) + logger.Debug("got neighbor message", "number", msg.Number, "set id", msg.SetID, "round", msg.Round) h.blockNumToSetID.Store(msg.Number, msg.SetID) h.grandpa.network.SendJustificationRequest(from, msg.Number) return nil diff --git a/lib/grandpa/message_handler_test.go b/lib/grandpa/message_handler_test.go index 15e4aeb590..765599ce0b 100644 --- a/lib/grandpa/message_handler_test.go +++ b/lib/grandpa/message_handler_test.go @@ -17,7 +17,6 @@ package grandpa import ( - //"errors" "math/big" "testing" "time" @@ -29,7 +28,6 @@ import ( "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/lib/scale" - //"github.com/ChainSafe/chaindb" "github.com/stretchr/testify/require" ) @@ -199,9 +197,6 @@ func TestMessageHandler_NeighbourMessage(t *testing.T) { cm, err := msg.ToConsensusMessage() require.NoError(t, err) - // _, err = h.handleMessage("", cm) - // require.True(t, errors.Is(err, chaindb.ErrKeyNotFound)) - block := &types.Block{ Header: &types.Header{ Number: big.NewInt(1), @@ -216,10 +211,6 @@ func TestMessageHandler_NeighbourMessage(t *testing.T) { out, err := h.handleMessage("", cm) require.NoError(t, err) require.Nil(t, out) - - // finalized, err := st.Block.GetFinalizedHash(0, 0) - // require.NoError(t, err) - // require.Equal(t, block.Header.Hash(), finalized) } func TestMessageHandler_VerifyJustification_InvalidSig(t *testing.T) { diff --git a/lib/scale/decode.go b/lib/scale/decode.go index e49a50a299..c8dc52a882 100644 --- a/lib/scale/decode.go +++ b/lib/scale/decode.go @@ -63,7 +63,6 @@ func (sd *Decoder) Decode(t interface{}) (out interface{}, err error) { out, err = sd.decodeUint128() case int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64: out, err = sd.DecodeFixedWidthInt(t) - fmt.Println("decoded int", out) case []byte, string: out, err = sd.DecodeByteArray() case bool: From a595e7c4c431ce8d94b05ab9c04ad442cb1f5368 Mon Sep 17 00:00:00 2001 From: noot Date: Thu, 15 Apr 2021 20:03:09 -0400 Subject: [PATCH 36/40] use syncer.ProcessJustification --- dot/core/test_helpers.go | 4 ++++ dot/network/state.go | 2 ++ dot/network/sync.go | 2 +- dot/network/test_helpers.go | 4 ++++ dot/rpc/modules/system_test.go | 4 ++++ dot/sync/syncer.go | 3 +-- lib/grandpa/grandpa.go | 4 ++-- 7 files changed, 18 insertions(+), 5 deletions(-) diff --git a/dot/core/test_helpers.go b/dot/core/test_helpers.go index d5cd894efa..fbe1d6bdd8 100644 --- a/dot/core/test_helpers.go +++ b/dot/core/test_helpers.go @@ -256,6 +256,10 @@ func (s *mockSyncer) ProcessBlockData(_ []*types.BlockData) (int, error) { return 0, nil } +func (s *mockSyncer) ProcessJustification(data []*types.BlockData) (int, error) { + return 0, nil +} + func (s *mockSyncer) IsSynced() bool { return false } diff --git a/dot/network/state.go b/dot/network/state.go index 72a4831e35..70b948d11f 100644 --- a/dot/network/state.go +++ b/dot/network/state.go @@ -38,6 +38,8 @@ type Syncer interface { // CreateBlockResponse is called upon receipt of a BlockRequestMessage to create the response CreateBlockResponse(*BlockRequestMessage) (*BlockResponseMessage, error) + ProcessJustification(data []*types.BlockData) (int, error) + // ProcessBlockData is called to process BlockData received in a BlockResponseMessage ProcessBlockData(data []*types.BlockData) (int, error) diff --git a/dot/network/sync.go b/dot/network/sync.go index 35918d2936..b53d935dbc 100644 --- a/dot/network/sync.go +++ b/dot/network/sync.go @@ -668,7 +668,7 @@ func (q *syncQueue) handleBlockJustification(data []*types.BlockData) { startHash, endHash := data[0].Hash, data[len(data)-1].Hash logger.Debug("sending justification data to syncer", "start", startHash, "end", endHash) - _, err := q.s.syncer.ProcessBlockData(data) + _, err := q.s.syncer.ProcessJustification(data) if err != nil { logger.Warn("failed to handle block justifications", "error", err) return diff --git a/dot/network/test_helpers.go b/dot/network/test_helpers.go index 7659867268..06c97ef3aa 100644 --- a/dot/network/test_helpers.go +++ b/dot/network/test_helpers.go @@ -61,6 +61,10 @@ func (s *mockSyncer) ProcessBlockData(data []*types.BlockData) (int, error) { return 0, nil } +func (s *mockSyncer) ProcessJustification(data []*types.BlockData) (int, error) { + return 0, nil +} + func (s *mockSyncer) IsSynced() bool { return s.synced } diff --git a/dot/rpc/modules/system_test.go b/dot/rpc/modules/system_test.go index 98c52935a1..a632029709 100644 --- a/dot/rpc/modules/system_test.go +++ b/dot/rpc/modules/system_test.go @@ -51,6 +51,10 @@ func (s *mockSyncer) ProcessBlockData(_ []*types.BlockData) (int, error) { return 0, nil } +func (s *mockSyncer) ProcessJustification(_ []*types.BlockData) (int, error) { + return 0, nil +} + func (s *mockSyncer) HandleBlockAnnounce(msg *network.BlockAnnounceMessage) error { return nil } diff --git a/dot/sync/syncer.go b/dot/sync/syncer.go index aa401c3769..012415643b 100644 --- a/dot/sync/syncer.go +++ b/dot/sync/syncer.go @@ -153,8 +153,7 @@ func (s *Service) HandleBlockAnnounce(msg *network.BlockAnnounceMessage) error { } // ProcessJustification processes block data containing justifications -// TODO: use this -func (s *Service) ProcessJustification(data []*types.BlockData) (int, error) { //nolint +func (s *Service) ProcessJustification(data []*types.BlockData) (int, error) { if len(data) == 0 { return 0, ErrNilBlockData } diff --git a/lib/grandpa/grandpa.go b/lib/grandpa/grandpa.go index e73516b724..19fefd9d12 100644 --- a/lib/grandpa/grandpa.go +++ b/lib/grandpa/grandpa.go @@ -620,7 +620,7 @@ func (s *Service) determinePreVote() (*Vote, error) { } nextChange := s.digestHandler.NextGrandpaAuthorityChange() - if vote.number > uint32(nextChange) { + if uint64(vote.number) > nextChange { header, err := s.blockState.GetHeaderByNumber(big.NewInt(int64(nextChange))) if err != nil { return nil, err @@ -645,7 +645,7 @@ func (s *Service) determinePreCommit() (*Vote, error) { s.mapLock.Unlock() nextChange := s.digestHandler.NextGrandpaAuthorityChange() - if pvb.number > uint32(nextChange) { + if uint64(pvb.number) > nextChange { header, err := s.blockState.GetHeaderByNumber(big.NewInt(int64(nextChange))) if err != nil { return nil, err From 1fcfa1f3967efb6b2b363d7c1d65a5cf07314dbf Mon Sep 17 00:00:00 2001 From: noot Date: Thu, 15 Apr 2021 22:35:45 -0400 Subject: [PATCH 37/40] rename Justification->SignedPrecommit, FullJustification->Justification --- dot/network/sync.go | 4 +-- dot/network/sync_justification.go | 4 +-- lib/grandpa/grandpa.go | 24 +++++++++--------- lib/grandpa/message.go | 14 +++++------ lib/grandpa/message_handler.go | 6 ++--- lib/grandpa/message_handler_test.go | 34 ++++++++++++------------- lib/grandpa/message_test.go | 6 ++--- lib/grandpa/network_test.go | 2 +- lib/grandpa/round_test.go | 16 ++++++------ lib/grandpa/types.go | 39 +++++++++++++++++------------ lib/grandpa/types_test.go | 29 ++++++++++----------- lib/grandpa/vote_message.go | 2 +- 12 files changed, 94 insertions(+), 86 deletions(-) diff --git a/dot/network/sync.go b/dot/network/sync.go index b53d935dbc..929cae2116 100644 --- a/dot/network/sync.go +++ b/dot/network/sync.go @@ -666,7 +666,7 @@ func (q *syncQueue) processBlockResponses() { func (q *syncQueue) handleBlockJustification(data []*types.BlockData) { startHash, endHash := data[0].Hash, data[len(data)-1].Hash - logger.Debug("sending justification data to syncer", "start", startHash, "end", endHash) + logger.Info("sending justification data to syncer", "start", startHash, "end", endHash) _, err := q.s.syncer.ProcessJustification(data) if err != nil { @@ -674,7 +674,7 @@ func (q *syncQueue) handleBlockJustification(data []*types.BlockData) { return } - logger.Debug("finished processing justification data", "start", startHash, "end", endHash) + logger.Info("finished processing justification data", "start", startHash, "end", endHash) // update peer's score var from peer.ID diff --git a/dot/network/sync_justification.go b/dot/network/sync_justification.go index 23714c9b8d..b4e4193f5d 100644 --- a/dot/network/sync_justification.go +++ b/dot/network/sync_justification.go @@ -30,14 +30,14 @@ func (s *Service) SendJustificationRequest(to peer.ID, num uint32) { func (q *syncQueue) pushJustificationRequest(to peer.ID, start uint64) { startHash, err := q.s.blockState.GetHashByNumber(big.NewInt(int64(start))) if err != nil { - logger.Error("failed to get hash for block w/ number", "number", start, "error", err) + logger.Debug("failed to get hash for block w/ number", "number", start, "error", err) return } req := createBlockRequestWithHash(startHash, blockRequestSize) req.RequestedData = RequestedDataJustification - logger.Debug("pushing justification request to queue", "start", start, "hash", startHash) + logger.Info("pushing justification request to queue", "start", start, "hash", startHash) q.justificationRequestData.Store(startHash, requestData{ received: false, }) diff --git a/lib/grandpa/grandpa.go b/lib/grandpa/grandpa.go index 19fefd9d12..bb4feabc73 100644 --- a/lib/grandpa/grandpa.go +++ b/lib/grandpa/grandpa.go @@ -60,8 +60,8 @@ type Service struct { state *State // current state prevotes map[ed25519.PublicKeyBytes]*Vote // pre-votes for the current round precommits map[ed25519.PublicKeyBytes]*Vote // pre-commits for the current round - pvJustifications map[common.Hash][]*Justification // pre-vote justifications for the current round - pcJustifications map[common.Hash][]*Justification // pre-commit justifications for the current round + pvJustifications map[common.Hash][]*SignedPrecommit // pre-vote justifications for the current round + pcJustifications map[common.Hash][]*SignedPrecommit // pre-commit justifications for the current round pvEquivocations map[ed25519.PublicKeyBytes][]*Vote // equivocatory votes for current pre-vote stage pcEquivocations map[ed25519.PublicKeyBytes][]*Vote // equivocatory votes for current pre-commit stage tracker *tracker // tracker of vote messages we may need in the future @@ -69,9 +69,9 @@ type Service struct { nextAuthorities []*Voter // if not nil, the updated authorities for the next round // historical information - preVotedBlock map[uint64]*Vote // map of round number -> pre-voted block - bestFinalCandidate map[uint64]*Vote // map of round number -> best final candidate - justification map[uint64][]*Justification // map of round number -> precommit round justification + preVotedBlock map[uint64]*Vote // map of round number -> pre-voted block + bestFinalCandidate map[uint64]*Vote // map of round number -> best final candidate + justification map[uint64][]*SignedPrecommit // map of round number -> precommit round justification // channels for communication with other services in chan GrandpaMessage // only used to receive *VoteMessage @@ -136,13 +136,13 @@ func NewService(cfg *Config) (*Service, error) { authority: cfg.Authority, prevotes: make(map[ed25519.PublicKeyBytes]*Vote), precommits: make(map[ed25519.PublicKeyBytes]*Vote), - pvJustifications: make(map[common.Hash][]*Justification), - pcJustifications: make(map[common.Hash][]*Justification), + pvJustifications: make(map[common.Hash][]*SignedPrecommit), + pcJustifications: make(map[common.Hash][]*SignedPrecommit), pvEquivocations: make(map[ed25519.PublicKeyBytes][]*Vote), pcEquivocations: make(map[ed25519.PublicKeyBytes][]*Vote), preVotedBlock: make(map[uint64]*Vote), bestFinalCandidate: make(map[uint64]*Vote), - justification: make(map[uint64][]*Justification), + justification: make(map[uint64][]*SignedPrecommit), head: head, in: make(chan GrandpaMessage, 128), resumed: make(chan struct{}), @@ -256,10 +256,10 @@ func (s *Service) initiate() error { var err error s.prevotes = make(map[ed25519.PublicKeyBytes]*Vote) s.precommits = make(map[ed25519.PublicKeyBytes]*Vote) - s.pcJustifications = make(map[common.Hash][]*Justification) + s.pcJustifications = make(map[common.Hash][]*SignedPrecommit) s.pvEquivocations = make(map[ed25519.PublicKeyBytes][]*Vote) s.pcEquivocations = make(map[ed25519.PublicKeyBytes][]*Vote) - s.justification = make(map[uint64][]*Justification) + s.justification = make(map[uint64][]*SignedPrecommit) s.tracker, err = newTracker(s.blockState, s.in) if err != nil { @@ -728,12 +728,12 @@ func (s *Service) finalize() error { // set justification s.justification[s.state.round] = s.pcJustifications[bfc.hash] - pvj, err := newFullJustification(s.state.round, bfc.hash, bfc.number, s.pvJustifications[bfc.hash]).Encode() + pvj, err := newJustification(s.state.round, bfc.hash, bfc.number, s.pvJustifications[bfc.hash]).Encode() if err != nil { return err } - pcj, err := newFullJustification(s.state.round, bfc.hash, bfc.number, s.pcJustifications[bfc.hash]).Encode() + pcj, err := newJustification(s.state.round, bfc.hash, bfc.number, s.pcJustifications[bfc.hash]).Encode() if err != nil { return err } diff --git a/lib/grandpa/message.go b/lib/grandpa/message.go index e1fecd3d70..0b76d4603d 100644 --- a/lib/grandpa/message.go +++ b/lib/grandpa/message.go @@ -121,7 +121,7 @@ func (m *NeighbourMessage) Type() byte { type FinalizationMessage struct { Round uint64 Vote *Vote - Justification []*Justification + Justification []*SignedPrecommit } // Type returns finalizationType @@ -181,8 +181,8 @@ func (r *catchUpRequest) ToConsensusMessage() (*ConsensusMessage, error) { type catchUpResponse struct { Round uint64 SetID uint64 - PreVoteJustification []*Justification - PreCommitJustification []*Justification + PreVoteJustification []*SignedPrecommit + PreCommitJustification []*SignedPrecommit Hash common.Hash Number uint32 } @@ -214,17 +214,17 @@ func (s *Service) newCatchUpResponse(round, setID uint64) (*catchUpResponse, err return nil, err } - d, err := sd.Decode([]*Justification{}) + d, err := sd.Decode([]*SignedPrecommit{}) if err != nil { return nil, err } - pvj := d.([]*Justification) + pvj := d.([]*SignedPrecommit) - d, err = sd.Decode([]*Justification{}) + d, err = sd.Decode([]*SignedPrecommit{}) if err != nil { return nil, err } - pcj := d.([]*Justification) + pcj := d.([]*SignedPrecommit) return &catchUpResponse{ Round: round, diff --git a/lib/grandpa/message_handler.go b/lib/grandpa/message_handler.go index 5c43657213..1b09eb936c 100644 --- a/lib/grandpa/message_handler.go +++ b/lib/grandpa/message_handler.go @@ -249,7 +249,7 @@ func decodeMessage(msg *ConsensusMessage) (m GrandpaMessage, err error) { return nil, ErrInvalidMessageType } case finalizationType: - mi, err = scale.Decode(msg.Data[1:], &FinalizationMessage{}) + mi, err = scale.Decode(msg.Data[1:], &FinalizationMessage{Justification: []*SignedPrecommit{}}) if m, ok = mi.(*FinalizationMessage); !ok { return nil, ErrInvalidMessageType } @@ -351,7 +351,7 @@ func (h *MessageHandler) verifyPreCommitJustification(msg *catchUpResponse) erro return nil } -func (h *MessageHandler) verifyJustification(just *Justification, round, setID uint64, stage subround) error { +func (h *MessageHandler) verifyJustification(just *SignedPrecommit, round, setID uint64, stage subround) error { // verify signature msg, err := scale.Encode(&FullVote{ Stage: stage, @@ -399,7 +399,7 @@ func (h *MessageHandler) verifyJustification(just *Justification, round, setID u func (s *Service) VerifyBlockJustification(justification []byte) error { r := &bytes.Buffer{} _, _ = r.Write(justification) - fj := new(FullJustification) + fj := new(Justification) err := fj.Decode(r) if err != nil { return err diff --git a/lib/grandpa/message_handler_test.go b/lib/grandpa/message_handler_test.go index 765599ce0b..3efcf8827b 100644 --- a/lib/grandpa/message_handler_test.go +++ b/lib/grandpa/message_handler_test.go @@ -38,10 +38,10 @@ var testHeader = &types.Header{ var testHash = testHeader.Hash() -func buildTestJustifications(t *testing.T, qty int, round, setID uint64, kr *keystore.Ed25519Keyring, subround subround) []*Justification { - just := []*Justification{} +func buildTestJustification(t *testing.T, qty int, round, setID uint64, kr *keystore.Ed25519Keyring, subround subround) []*SignedPrecommit { + just := []*SignedPrecommit{} for i := 0; i < qty; i++ { - j := &Justification{ + j := &SignedPrecommit{ Vote: NewVote(testHash, uint32(round)), Signature: createSignedVoteMsg(t, uint32(round), round, setID, kr.Keys[i%len(kr.Keys)], subround), AuthorityID: kr.Keys[i%len(kr.Keys)].Public().(*ed25519.PublicKey).AsBytes(), @@ -110,7 +110,7 @@ func TestDecodeMessage_FinalizationMessage(t *testing.T) { hash: common.MustHexToHash("0x7db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a"), number: 0, }, - Justification: []*Justification{ + Justification: []*SignedPrecommit{ { Vote: testVote, Signature: testSignature, @@ -217,7 +217,7 @@ func TestMessageHandler_VerifyJustification_InvalidSig(t *testing.T) { gs, st := newTestService(t) gs.state.round = 77 - just := &Justification{ + just := &SignedPrecommit{ Vote: testVote, Signature: [64]byte{0x1}, AuthorityID: gs.publicKeyBytes(), @@ -233,7 +233,7 @@ func TestMessageHandler_FinalizationMessage_NoCatchUpRequest_ValidSig(t *testing round := uint64(77) gs.state.round = round - gs.justification[round] = buildTestJustifications(t, int(gs.state.threshold()), round, gs.state.setID, kr, precommit) + gs.justification[round] = buildTestJustification(t, int(gs.state.threshold()), round, gs.state.setID, kr, precommit) fm := gs.newFinalizationMessage(gs.head, round) fm.Vote = NewVote(testHash, uint32(round)) @@ -260,7 +260,7 @@ func TestMessageHandler_FinalizationMessage_NoCatchUpRequest_MinVoteError(t *tes round := uint64(77) gs.state.round = round - gs.justification[round] = buildTestJustifications(t, int(gs.state.threshold()), round, gs.state.setID, kr, precommit) + gs.justification[round] = buildTestJustification(t, int(gs.state.threshold()), round, gs.state.setID, kr, precommit) fm := gs.newFinalizationMessage(gs.head, round) cm, err := fm.ToConsensusMessage() @@ -275,7 +275,7 @@ func TestMessageHandler_FinalizationMessage_NoCatchUpRequest_MinVoteError(t *tes func TestMessageHandler_FinalizationMessage_WithCatchUpRequest(t *testing.T) { gs, st := newTestService(t) - gs.justification[77] = []*Justification{ + gs.justification[77] = []*SignedPrecommit{ { Vote: testVote, Signature: testSignature, @@ -341,7 +341,7 @@ func TestMessageHandler_CatchUpRequest_WithResponse(t *testing.T) { err = gs.blockState.(*state.BlockState).SetHeader(testHeader) require.NoError(t, err) - pvj := []*Justification{ + pvj := []*SignedPrecommit{ { Vote: testVote, Signature: testSignature, @@ -352,7 +352,7 @@ func TestMessageHandler_CatchUpRequest_WithResponse(t *testing.T) { pvjEnc, err := scale.Encode(pvj) require.NoError(t, err) - pcj := []*Justification{ + pcj := []*SignedPrecommit{ { Vote: testVote2, Signature: testSignature, @@ -388,7 +388,7 @@ func TestVerifyJustification(t *testing.T) { h := NewMessageHandler(gs, st.Block) vote := NewVote(testHash, 123) - just := &Justification{ + just := &SignedPrecommit{ Vote: vote, Signature: createSignedVoteMsg(t, vote.number, 77, gs.state.setID, kr.Alice().(*ed25519.Keypair), precommit), AuthorityID: kr.Alice().Public().(*ed25519.PublicKey).AsBytes(), @@ -403,7 +403,7 @@ func TestVerifyJustification_InvalidSignature(t *testing.T) { h := NewMessageHandler(gs, st.Block) vote := NewVote(testHash, 123) - just := &Justification{ + just := &SignedPrecommit{ Vote: vote, // create signed vote with mismatched vote number Signature: createSignedVoteMsg(t, vote.number+1, 77, gs.state.setID, kr.Alice().(*ed25519.Keypair), precommit), @@ -422,7 +422,7 @@ func TestVerifyJustification_InvalidAuthority(t *testing.T) { require.NoError(t, err) vote := NewVote(testHash, 123) - just := &Justification{ + just := &SignedPrecommit{ Vote: vote, Signature: createSignedVoteMsg(t, vote.number, 77, gs.state.setID, fakeKey, precommit), AuthorityID: fakeKey.Public().(*ed25519.PublicKey).AsBytes(), @@ -436,7 +436,7 @@ func TestMessageHandler_VerifyPreVoteJustification(t *testing.T) { gs, st := newTestService(t) h := NewMessageHandler(gs, st.Block) - just := buildTestJustifications(t, int(gs.state.threshold()), 1, gs.state.setID, kr, prevote) + just := buildTestJustification(t, int(gs.state.threshold()), 1, gs.state.setID, kr, prevote) msg := &catchUpResponse{ Round: 1, SetID: gs.state.setID, @@ -453,7 +453,7 @@ func TestMessageHandler_VerifyPreCommitJustification(t *testing.T) { h := NewMessageHandler(gs, st.Block) round := uint64(1) - just := buildTestJustifications(t, int(gs.state.threshold()), round, gs.state.setID, kr, precommit) + just := buildTestJustification(t, int(gs.state.threshold()), round, gs.state.setID, kr, precommit) msg := &catchUpResponse{ Round: round, SetID: gs.state.setID, @@ -477,8 +477,8 @@ func TestMessageHandler_HandleCatchUpResponse(t *testing.T) { round := uint64(77) gs.state.round = round + 1 - pvJust := buildTestJustifications(t, int(gs.state.threshold()), round, gs.state.setID, kr, prevote) - pcJust := buildTestJustifications(t, int(gs.state.threshold()), round, gs.state.setID, kr, precommit) + pvJust := buildTestJustification(t, int(gs.state.threshold()), round, gs.state.setID, kr, prevote) + pcJust := buildTestJustification(t, int(gs.state.threshold()), round, gs.state.setID, kr, precommit) msg := &catchUpResponse{ Round: round, SetID: gs.state.setID, diff --git a/lib/grandpa/message_test.go b/lib/grandpa/message_test.go index d0f89a3689..0b39ad9ce1 100644 --- a/lib/grandpa/message_test.go +++ b/lib/grandpa/message_test.go @@ -75,7 +75,7 @@ func TestVoteMessageToConsensusMessage(t *testing.T) { func TestFinalizationMessageToConsensusMessage(t *testing.T) { gs, _ := newTestService(t) - gs.justification[77] = []*Justification{ + gs.justification[77] = []*SignedPrecommit{ { Vote: testVote, Signature: testSignature, @@ -114,7 +114,7 @@ func TestNewCatchUpResponse(t *testing.T) { err = gs.blockState.(*state.BlockState).SetHeader(testHeader) require.NoError(t, err) - pvj := []*Justification{ + pvj := []*SignedPrecommit{ { Vote: testVote, Signature: testSignature, @@ -125,7 +125,7 @@ func TestNewCatchUpResponse(t *testing.T) { pvjEnc, err := scale.Encode(pvj) require.NoError(t, err) - pcj := []*Justification{ + pcj := []*SignedPrecommit{ { Vote: testVote2, Signature: testSignature, diff --git a/lib/grandpa/network_test.go b/lib/grandpa/network_test.go index d8f2fc8ba2..58c9352506 100644 --- a/lib/grandpa/network_test.go +++ b/lib/grandpa/network_test.go @@ -46,7 +46,7 @@ func TestGrandpaHandshake_Encode(t *testing.T) { func TestHandleNetworkMessage(t *testing.T) { gs, st := newTestService(t) - gs.justification[77] = []*Justification{ + gs.justification[77] = []*SignedPrecommit{ { Vote: testVote, Signature: testSignature, diff --git a/lib/grandpa/round_test.go b/lib/grandpa/round_test.go index 58f7be0dd9..815da4473e 100644 --- a/lib/grandpa/round_test.go +++ b/lib/grandpa/round_test.go @@ -290,8 +290,8 @@ func TestPlayGrandpaRound_BaseCase(t *testing.T) { for _, fb := range finalized { require.NotNil(t, fb) require.GreaterOrEqual(t, len(fb.Justification), len(kr.Keys)/2) - finalized[0].Justification = []*Justification{} - fb.Justification = []*Justification{} + finalized[0].Justification = []*SignedPrecommit{} + fb.Justification = []*SignedPrecommit{} require.Equal(t, finalized[0], fb) } } @@ -388,8 +388,8 @@ func TestPlayGrandpaRound_VaryingChain(t *testing.T) { for _, fb := range finalized { require.NotNil(t, fb) require.GreaterOrEqual(t, len(fb.Justification), len(kr.Keys)/2) - finalized[0].Justification = []*Justification{} - fb.Justification = []*Justification{} + finalized[0].Justification = []*SignedPrecommit{} + fb.Justification = []*SignedPrecommit{} require.Equal(t, finalized[0], fb) } } @@ -485,8 +485,8 @@ func TestPlayGrandpaRound_OneThirdEquivocating(t *testing.T) { for _, fb := range finalized { require.NotNil(t, fb) require.GreaterOrEqual(t, len(fb.Justification), len(kr.Keys)/2) - finalized[0].Justification = []*Justification{} - fb.Justification = []*Justification{} + finalized[0].Justification = []*SignedPrecommit{} + fb.Justification = []*SignedPrecommit{} require.Equal(t, finalized[0], fb) } } @@ -568,8 +568,8 @@ func TestPlayGrandpaRound_MultipleRounds(t *testing.T) { require.NotNil(t, fb) require.Equal(t, head, fb.Vote.hash) require.GreaterOrEqual(t, len(fb.Justification), len(kr.Keys)/2) - finalized[0].Justification = []*Justification{} - fb.Justification = []*Justification{} + finalized[0].Justification = []*SignedPrecommit{} + fb.Justification = []*SignedPrecommit{} require.Equal(t, finalized[0], fb) } diff --git a/lib/grandpa/types.go b/lib/grandpa/types.go index d783ce3fd4..45484818b3 100644 --- a/lib/grandpa/types.go +++ b/lib/grandpa/types.go @@ -223,16 +223,15 @@ func (v *Vote) String() string { return fmt.Sprintf("hash=%s number=%d", v.hash, v.number) } -// Justification represents a justification for a finalized block -// TODO: rename to SignedPrecommit -type Justification struct { +// SignedPrecommit represents a signed precommit message for a finalized block +type SignedPrecommit struct { Vote *Vote Signature [64]byte AuthorityID ed25519.PublicKeyBytes } // Encode returns the SCALE encoded Justification -func (j *Justification) Encode() ([]byte, error) { +func (j *SignedPrecommit) Encode() ([]byte, error) { enc, err := j.Vote.Encode() if err != nil { return nil, err @@ -244,27 +243,35 @@ func (j *Justification) Encode() ([]byte, error) { } // Decode returns the SCALE decoded Justification -func (j *Justification) Decode(r io.Reader) (*Justification, error) { +func (j *SignedPrecommit) Decode(r io.Reader) (*SignedPrecommit, error) { sd := &scale.Decoder{Reader: r} i, err := sd.Decode(j) - return i.(*Justification), err + if err != nil { + return nil, err + } + + d := i.(*SignedPrecommit) + j.Vote = d.Vote + j.Signature = d.Signature + j.AuthorityID = d.AuthorityID + return j, nil } // Commit contains all the signed precommits for a given block type Commit struct { Hash common.Hash Number uint32 - Precommits []*Justification + Precommits []*SignedPrecommit } -// FullJustification represents an array of Justifications, used to respond to catch up requests -type FullJustification struct { +// Justification represents a finality justification for a block +type Justification struct { Round uint64 - Commit *Commit // TODO: rename Justification -> Commit, FullJustification -> Justification + Commit *Commit } -func newFullJustification(round uint64, hash common.Hash, number uint32, j []*Justification) *FullJustification { - return &FullJustification{ +func newJustification(round uint64, hash common.Hash, number uint32, j []*SignedPrecommit) *Justification { + return &Justification{ Round: round, Commit: &Commit{ Hash: hash, @@ -275,19 +282,19 @@ func newFullJustification(round uint64, hash common.Hash, number uint32, j []*Ju } // Encode returns the SCALE encoding of a FullJustification -func (j *FullJustification) Encode() ([]byte, error) { +func (j *Justification) Encode() ([]byte, error) { return scale.Encode(j) } // Decode returns a SCALE decoded FullJustification -func (j *FullJustification) Decode(r io.Reader) error { +func (j *Justification) Decode(r io.Reader) error { sd := &scale.Decoder{Reader: r} - i, err := sd.Decode(&FullJustification{Commit: &Commit{}}) + i, err := sd.Decode(&Justification{Commit: &Commit{}}) if err != nil { return err } - dec := i.(*FullJustification) + dec := i.(*Justification) j.Round = dec.Round j.Commit = dec.Commit return nil diff --git a/lib/grandpa/types_test.go b/lib/grandpa/types_test.go index f788234a6c..d8ebc667d6 100644 --- a/lib/grandpa/types_test.go +++ b/lib/grandpa/types_test.go @@ -39,8 +39,8 @@ func TestPubkeyToVoter(t *testing.T) { require.Equal(t, voters[0], voter) } -func TestJustificationEncoding(t *testing.T) { - just := &Justification{ +func TestSignedPrecommitEncoding(t *testing.T) { + just := &SignedPrecommit{ Vote: testVote, Signature: testSignature, AuthorityID: testAuthorityID, @@ -51,13 +51,14 @@ func TestJustificationEncoding(t *testing.T) { rw := &bytes.Buffer{} rw.Write(enc) - dec, err := new(Justification).Decode(rw) + dec := new(SignedPrecommit) + _, err = dec.Decode(rw) require.NoError(t, err) require.Equal(t, just, dec) } -func TestJustificationArrayEncoding(t *testing.T) { - just := []*Justification{ +func TestSignedPrecommitArrayEncoding(t *testing.T) { + just := []*SignedPrecommit{ { Vote: testVote, Signature: testSignature, @@ -68,22 +69,22 @@ func TestJustificationArrayEncoding(t *testing.T) { enc, err := scale.Encode(just) require.NoError(t, err) - dec, err := scale.Decode(enc, make([]*Justification, 1)) + dec, err := scale.Decode(enc, make([]*SignedPrecommit, 1)) require.NoError(t, err) - require.Equal(t, just, dec.([]*Justification)) + require.Equal(t, just, dec.([]*SignedPrecommit)) } -func TestFullJustification(t *testing.T) { - just := &Justification{ +func TesJustification(t *testing.T) { + just := &SignedPrecommit{ Vote: testVote, Signature: testSignature, AuthorityID: testAuthorityID, } - fj := &FullJustification{ + fj := &Justification{ Round: 99, Commit: &Commit{ - Precommits: []*Justification{just}, + Precommits: []*SignedPrecommit{just}, }, } enc, err := fj.Encode() @@ -91,16 +92,16 @@ func TestFullJustification(t *testing.T) { rw := &bytes.Buffer{} rw.Write(enc) - dec := &FullJustification{} + dec := &Justification{} err = dec.Decode(rw) require.NoError(t, err) require.Equal(t, fj, dec) } -func TestFullJustification_Decode(t *testing.T) { +func TestJustification_Decode(t *testing.T) { // data received from network data := common.MustHexToBytes("0x3b1b0000000000002a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46001d032a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d5be226e7e7b6b54eb6c830ab47d4fa29bc228f46f176be79bd9b687d01ad7c4441f0c5b7f489462f29ba1641672519a2bbfd9162fb11d646bf1990b0c858e0e026905dab6c71c2a664e9ca8e4f066bdee9265ec45b7885ab14a797ffe1bee362a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c393865ec446b236036e3c846577c930568ea67f1cfe34048cb360d3892d9da61c4b1bd7ee31df96907662e10dae18646ceec91181c5e3dd97605b15f0bfc20d02aabb29f640813f718f1e7495f42415f742457517d536ba5de50990d182df252a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000d68aa7ce3c3902cca498cf6c0051d5b0f901f22092a427faa23227a19bc55eda954191acba6b5c82ec03543633facf3f84176ba3b860428d0e160b5a3a2db0802c70dcaf367b35740713c0e6761d88a2e26f9762e9715fac8bbeabedd8c5eca2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600756e9f4187f078001a7368eedc194df7ac7b1cae92827dae36d345061afa3d49df2041a41387501c5fd1b282707303aa3a7c820cce1b44c081c0051e7fc229070339088379600f5bd507277eb894aba34d2a8f27ce1964f41e60ef2a4142dc6e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ea73d78ec38c271aebb2c9f7ebebb5d3a99edf327bbf4d03b5c245c08d0c2cceaf0030ecb5e04152255f32e4edfbee970f3ab05607f6d8b718826e559bc18b0e03d105a30087d96b5f0684f6ded76f826b01dab61e4136e1d851a24f0088b5ed2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600681cd145a0375e5f72a737bbf92bc57c1e2b9429cf8b66bda13d0096e1898385ada09ccad93971530349592a430aa61331efb46c04a1bd9af7c34d5be9f2d30b044a5968644cf9f9b5d1c7f657a65343af076730a89b07b4995a494aaa0c967a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600620c51368e57db2cbf7e1edee0a746dc950c6da129ae5dbf81f3171fbba241f564b847fe376bf7a6f62d9bcefeba69d7d8b23000c40b7c9ec4f2868e6d748408049603b355867a2d3a1978a771b71b2bee4e8ae1f344d9ee034f47b01a48f89b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000dec8ecfc90b708446f112ad2730149b124d1d4b891edcc7f192ad4f6634fb1529471e902b590049ee6049361537752f8b4780e1900f5134f101039d617a710e069a481209b07b48c1d548d4af68b7584f95b32eb374be0682b342cd64d3406c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46008f6792c7ae77c3dbde17b000c86743bccd9f2247fb323044b12378a0b060f122674fc796d910fb2eaaf7512a5858ce467da7409a2e5fb14edc077c939efcc802088f8736e1cf2ea3d102f0a96ccf51222b8aa1f93d8e42947892f4395cb5477c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460043286aff05190884dc7be3f5b4e42728a1eb766c73cc9745f08322ed672ad944503ec837a379efef89b4c78a5aac7ffe8bd26e649eac482f5324280e259bcc030a790c3ba374fdcf1a1d457adcaa8fc01bb46eba8cae4e1940825364fe2d7ce02a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460025437e042850b2648ecd98937694a76d041c65b471a81344c55d9aae0650459daaa5f5e734ddf02ae030178ee8714c5f6cc241f8148eafeff450424fe62548040cd72d0e6daf0275f2eec96f6129243e01e255ad6b104ed500e614077d0710982a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600fecfa0a948e6cdb687849ea41dc46083ed8562ca4657d751f6c303ae0e35dba38e5c1550cfdd71011239872139770b8151eb4a04df948f666d43811009f12f0b0d49982b323ded139008e150fe97b0e85213a92b411c68f993aaa43ea62a7b282a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460063dc716d5616a3125fabc437ccc98b3e38bb2730a2564a11edf528552245f20657199cbc982288e2ff3127497a8a725852c35352ee3c0a2109d2e603c33c37000e1b3f7368c230b609c745aa7c2119b60218da3ade7e6a0dace3e337703c51992a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a07873b9fc9d6b71bba7749a8886a13c8d5f4c2b66907bcdf5fddbd88df3c6ece1d601799af6ffb7fb53cece0278df81bd34676ecd20a7494515eea9bb61af020ef0093d298f1adee0947ea81400adf0c27bd2e5c10b760d2c8be3b784e1fa002a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46006bc6c891010819529a51deedc4a7033de5e85aede0eb4103118110239cbfdad07327aeafc68acfa3b11872770653290f50c09658d416cb2534f0794ddc4b1f0c10d20c8996f5448cff82ded879bacf7534e4d499ec65f040024bd7c2402eba942a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460090c37f3814f7b66ad1008f43715aa91e3f7713285d44c9b82b88d7125a5a5be38f9c38675891de2455b276ea008321b742f420baa7b2ed8519918edbb8b9b90311addb386a5088eaafbe4c93e265b8c1459a8504f0d31a3799836224c6077d532a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460085114883f06b533bc6494706326c7191f224f0c086d0a21c8534269c3dd2d7a10c1beaffac80e05250d0c230aa3da0ebfbbd4fe47a8323766b09a8e3ba797b04124cb8ef23a2aa6f5d748070a84465b1cf4df73db94257d96c63df4b5ea8b80c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460037c148f4242ac604913f055679371442e9fdfd7f989ae9d73dbd5cd439a31f941e5c4a63f315a3b4c8faefe537dd51fed26ed948d5cc0110c8c89775e5a2a70914b432d81cead21854d70b19f9691b3d2a8ca7862b64a71f4f3172387078c4642a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d3acc5151d7a54944aa00350c835fed6f9dfb4f12b8124903d301a959bd63f087d19016a6f18b732d58d37f93ccdc5a9a27511054f54436dc8bf8a12e1a06c03155763a153e0c02b2eef1d8a9cd8a50a9eaac9d3af7ad1e559b1e2320b521e1c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46007fd97784bffbeb89e3fc4053e6667d73a24a15e5a864f8264d544f93b8d4938a5e57b079247499e1d0de9cfe038754c930d1427f491ba0f8a956c56b6abdc409172fa913b2ba5833a78ed51e13692e9d1045e7516b5ac738e6745c217f49fc1d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000b091d7bd11344b81890eaa9eb51f38fef522d5b38e05449ab04cf720c389c9a9669d8cf69411500ba2a78a4f7783d75c424b0ca60e5a564728a7908b90dec0918a1a6e0e81ae2cf403c328d1195eb73e9b78091bf1e33be4dc1e29736eaa0382a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a3aeb6a64836a35659a253533ca38c5e8f5dec712d764bbbfaaf079f671c4487345d232be14aead855a06873549f712ded1127217560fb315be740e2071812061c67ae7a3c999b95732bdfe95fa8670aac497a1027aba327a1438bc097fa700a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009ed7a0e3b08dafbdfa66248a9abe9f6bdaad694b2c8b1a85c2fb8ba387895d11890d482eea4078d1049373c9a7d95123093e2e63d4472c186c18ae7f47c310071d5ca234f8eeb85fedd882230378855b4e7390a78a802f0be7230156a8ce624c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a5b924123457be20ebb04eb12a248f693620331120a053e1837a343460769d6edb3316966f5784ed15e5e8f6105f8af34ad0bd6a8f11aef6f79eeec6e658090b1dc6da633d00a38d0d0b44056980bafbd624f2e1067027cd2b09267acfb70e9c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ed204b31f36a1c06fb6b69e206525afbb5449064e39aa477602ea878ac65a4e81e24fc35c1a8ba764ed1d75200a2c5681a43da66a8096349365a9302706d1d041e0695d7beee6c4a67c9a1a75f89f3f858a50a09ce1b96a08e685a49e20e7e442a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600124362e041e4e25522fef30f7267b7d52338dabb1a2bfa21d163b06df2261cf465067921371fa5be83279004e174d5cf5bc79102e2208139bf62892c64521e0b20d4e595c50bb9558dad9be6a8784492bbbfc754b9c5fae17edf4f8a84e8b4712a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46002d19070e68ddfa34d42b207919fcdf1583ffa6458a82e2680ee5c880ff145e4b6a18b00c1c683c511e196d7b24cf4abd508f6522ee3e8eb8b335a42a0f7dfc0121699d6780c6aa58e433f27d36b7f1ea5afd4fa404d8254bd62ed5b17c3f75672a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46002bd9ee43aaa4cd4691315b505af64f05fdb9d70e1fe5f8c7d9e536bcf723b67035239765e727d4e1146c786c97b1e900cd33d8c25440d9e38907a6d0af3d0602221a227686d900f6be260a84e81bab79adcac88ee2ceb1f4efe83e0be86fc88a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46001b561a74a01dd3e3957d7930fae671d9a107a0e36b1ab1fe38a2acecf24ce281f0270b516c0d73ca05c1e7e7b6b5870e973ed04f8b4b2d8f486731e2871c5d012275aebac33745b7c0f47957b0f9c1dab14d6dc5cbece544e26830248b9b638d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600f394746cbe78e56e004009e2f374bc8fbca67b5228f33d328ba173673bc7ebdd31669b9272de648f7d19b7b79ea68937a6bcc395695c03c8afd36647bf20f70422b1625c123c72fa70ee375da98f15cf1326b254ef833a27ffaccbbe81e993d42a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460043359e329c56b481de900abf705e49957358bd8261d5215ac63a96b9098eb07d68b12aa67f2e4559fe6fa838233795e32e9f08b1680f89fa7db4da2cbf40010d23192a82e612c4b047a6efe2c0b20d6b8300a81639a6d1578bb94a66a721649f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ef4e7891ef4f0703d2ad1833ba02e1ebc743fd656dc4e0d5b2e9346694f142ab262a9143fda3c3a199e8f57820192a49648a0872e7b1de04bbc101bcbfd0ab0524796621f90c2d9f2d12a962e14f9852757f89de7b1f66c22506321ef38266bf2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d65939fa1869e5e2e2ed5800cac361424eb5449365fee6914719dcb768267f37be9efb0a21b1720308dd5961e3ad43b00eae969a117b8e1121ba36362f0b220225d5c2769f768059b45c6843d37949ab7866678eab000a5e6340e30d1dba306e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600aa0831e40a2cbc1ba04377e7cf9a0c6f5fb037c9516e4e9cd4d6ad0564953b99c8ab329e5f667f7fdaed70e9b8d3b4123e6ed9249b4e999b41dbb82ef04ce50d25ef09e15fe616e090335b55cf729832e2c9f562428fcb00fd3e3bed2dde843a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46001396a84de1c3a492a93b29d74c536ea8231bd01657a1621a3af64b5993383781947c771948a7166dd9d21b4c34a36208dd102d77f3f6c99ef85037ccd4fb5b09271f0ce7fd9e0460b2b5f68afb847a80ea0821bf168dceeca396a1fe35754af52a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600804dd690fd66f6c15a74a4c1c8be43b57f0eba4d45d75f66777a5204a506c6344e189d33c41ee6085420683807345324cf24602b2eac5a307603a1dfeb71040d27a748182c49d7c4ec091c9589da5c9c35fa99ac8ffa4d613129dd4d08e9f1122a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c91a2c346304d33ac0121707248a0560bb32b4446fb0f2d29c6c4f27f4073bf6455e662af07a4c693f199a1b850fa794d45d0640d33883e08681b525f99bfd0627e96e8217ce52d55d5cfe97580a03b8f9ffd41e72ad863300db45749088faed2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d91892fba8d2b41a62f81cc5dc225fd193aa215c9941dfacb197f7b11262609953c57726361b6c1458d500cdc90eed86c0d9ae4cb98ec1457a438af9e706d40e297b86aac8a8843ef944f9633404cf307ae264f031f6dc803bd93c7ea69889742a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46002c85b6e9e5c28ccc33e4d59857cdc0afd0e4f4d1c2218dd018123af7d96ec3b08e49ca35a5231431e2663db3a178dbbe8a1a4f4c0005014770c0cc85876a88062ae745fd0181edf42f9313bef7ddc5d62ae8289212df03f8f73e210e0ed907562a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e4ea8f7d9584724897151d5a1412a73e42c96aecbd84020e52770c1767e8f807befe8dd28f7fc8cb12addd1f1b72d6c58e418702111cd708ce7da9f61574d60d2ba3f6bd3084ac7d9448dd341d4c7ca8c991b659c85f27cc68e80fe7cbd73e422a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46008c5d3fb36a8da0e1770f923bfbac75470ec6408c06f0cb1be5cdb5893a2c09eb1e370073aac480ee2ec8b9c3a9bea659869a4328e8e441851bfb846610ddaa072c3ce4d7102f4236e0df2e8f5797f6dc3d2e6f0e57d373c9a4b89b21d4d228682a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b6e743e03f526851e054ff4766ad6db181f83fbe2370a51431fb8c310f1cbf3788d99480e9fffd8164d31cad3db297d9b26c34523aa25d5c7f83e79b6c02390c2dad5b2212ee688f2eeb9ca1fb6a90574f006dc1c6680ac3a8523363a248940b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460020716a5266a471356b3780eeaf4ad7dc37f50352ece4e1416bbe47b5b2606bb59dbcb1d270aecdfa6eece9d3e8f0862966ea7c7643cf1c05945fc4a56ac65902304d970cf7e07987f8d7a500edaeb1c0973de1ca588512e5d9f268a9ec0874ad2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ed2d6b378c40cb99f357b0458085a0b0504c1622adf0316050396479b08ef1859c2978fa15b4e53fe911857add9ec253cfc8d6b949abaca4437824f324c1070e360e744fe76d5d471445ce6ad9587af67392b8d960d7715dc0efb43c698465a52a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600013ddc65f819aa4c592cbbd59ab1f4a4bb27c3a4b717e19d409fde187da9e2e7dd6c9c72ecb1bb772c112f3229dc5140c202ab9341d04a14dc2c24a2b4e34a0936ea5662f48dd131b91defa20bde06049edcee2982714f78519fb64450d1b62d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600087672b184ff663524bfc5d05631172d3eff695a117cf6c8884053e97697b48dc255835918bcc10f915ff4d8fc3470ff5143eb433c00e2dfc8b46db2ff16360e37035dbee1de6bef71e401cd19d0a26cb8b69cf719ae340ea53c9677d6a9aaae2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a2d8a30c22e7f78ea1a477a0d7971533020276d5d10bbbf530f31ec5d9ae688108c2b4535dcf9204604406f21d104131e88d9c8399151bb67c14a790945ab30439712f967c4e06d284d6da4735cf2823c1f770674b668f7bc896071c3fd41eda2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46007770937b4a1d62bc1df63b1f80162728b198d26492e93765025f9c71dcf992482c9e85ef9100f7fb89931c0d2fd82d063e24f7dcb3d28e549d1d45d8a91f2c013a2f175229490c3169b5942260eb2572198b494e63c984dc364d3f48aaa7bf012a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600f7f152e001649cf1353540ca716715bad9fb6703c1f34b78673baccc8027c3885a6c9660cce85fbf2b2ab696220a2e417964c91912b030a0861ab7ca541daf073d75d8fd47f1074a78cc88248f1f6b9ea6cab42ccb676f94226e3fb5e16249c12a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600dc58eb9647b223730213a6f217010fc35556325166e03edd470b04b9b38f97a98acc1695a71120f06b2beda919889a5c2de43f40f9954894ba861cc2082393003db99a9882de9b7666591c1f9f2d87685a2569bd0c611358d6e144a014c5612d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460070846e878685dbe30ef68636052fcb92da91ff77ad2f7839edab14ff2f5a28cc27e0c464d175c995647ca3a0cedb2ec732e5732789ad3684ec4f08d94034c3023ded8aac5210831cbcc4d0cf1f96f2c711afc90a6f6f35a4ffb766be1dfbaee32a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600674883cb865697effe33d717b63eb6746fd144e8dce5df79892d453b51fa49cdce9eee1500be36a245e33a9254ce65631b0049218fa305c2e9d5ee1a8855b00b401def7965bb1e8373fbbb6561d5ca51832cbb92b67b0bb08c407d2c19cca96e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b4d95db0f1cd96e18f87a0c67e71cf7b8d2facf75d10671bbbc9f7e5642a4c2223100eae4e64a54ca179525b19a763fdf624d78e209f7268d7cf4cf70788200f406c4081abf0f3a151dcaecc6b7ca1a7489cfa1810cb02c8cb249bb67dae09302a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600bda3c89a9f0257273f01c786914fcac144189a20db45d05fb42ded836fe2067362fd1baa2ec4727a7d8d72530474f60a39070cb0f2af31c372a511c55ef2b20a41ef4a31eb7dc1e01f4630604e1908e644d7cdee3f66a60f98d6d59605326f8b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a8bec76286b65de5693a2cbc309bc3c301bd6812eb493113449e9d30d39b60ee58a4d918f127d54522e74d6c6167ed99dc3b10e8aa64f3e4e5a2a497e68d9e0542d0c88f0accb5117e31bc057d4f277a38e01f2325f02d0a9f647db09b67cf202a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a243df7ec7aea8df7944c4474fe09c1a2a006f697e29d15300da8a51ac7258244501ea6c87983d0ec4643c051ee1489087a3017a63c2ac3b7d06aaadb1b88a0943701217f2650ade985ac46e38e548bc1850bf3d895eb6c1ecf42d9e61b788c82a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46005305ec8c29ed3ef51119c80c144ea8714321a31e533c0ed9a1c03a781e4401a279393f06e4e0f0dfd7f21b523a808e0aaf659aa587063d7017dab6ec8df7810543a9a2915f377cd4943f602be38c3bd6ef39e91562c09f18fb672884b4bf8eab2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600fc691469543554a61c6bfbf5d554ebf7cb596ac78579aa1b8d6b0c7e52f8295441a4bb450d8fe1d2579a51986d3f7a6de52a34690afe34fe5699a6bdc1fee103452185859ba92a24c1f836952da8d11c9425c9b35eec979744a345d2276de3ee2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600df5b15104aba6d1d4d70f19eee2d04e22056616f0b54011fc3f15449aadd958124dabcfac319655f72688f14130808f3dd12653e4f948034398589143c24fd0d4532b59911aec8842fd910a35fad7c6210b3c1ac73c7c9799963c635e2b562882a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b11b2845d75099b35ce971277fb82b0a8f2be06f40d2b759a291a095b3ae6f9655279b922667119308fc3d673e3d6f0063bce32f0b2c5370071214ef58b20106469947dc7cb086bd75216dcff8c8ddbbff5e0f112ba397d71b6bc2980bb6cf002a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c589c74c41ac4ba4e067df4a152dd5f34706f74828bbc1166a7ea1863e411058683a6d3b3c27db8540a8538fc50944dd9b3c8dfaa5f49fce6c5aeed834d8d504469e0a875597428e25f79157e441e933f3fff7d2afee478a9ac6e1903e4d4d1d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c0a2143bff781fde49d6989df90169579c588192fb669219f815f47117459f9a94ddadb68a90796f95186e7ec641e6973bc165d0d8b2fe651c9e563551bf960b472bdd18c1be48d5e3c40aa093db02e45c4bfe1a5b62a3884fd65be8eca3c6022a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a5f5af2141ecc0f6a323988e7d8ecdd8b609fb9379c20d108e218ff6c8aa7fc91ae54bde791fdde4d103c43299c033a649e69c8fc933db42b858a3730dc39a0a493c604e7f0a7cd6abf370c10b90eb8ff9d432bab420bdd9fe3da656d5e9b6752a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a1f8ef2a102fa613697a17e7135f7f7e429107d27a74ab15f0f2a46afed1e3257ce683c09017e25cc7abd447e06891350568aace957b76c5a2a8cbd9c0c8c800494d65e6b674eee86acf5ed70b45b37d7498b8c1d05cc0baa0d6473fdb596bfe2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600eda30d06498bcca078360129b8d46b6d8af3b8b9ddb252a00dced827e3b4c86373e299dae8720f622be9bab21766fc970ad786bc5733ec208b0105910b993c01496826b538d97906e14d417b48598d7d591a483ab5f4c6786cd0b96239c2f4cf2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009aed65d04eaf5a5c215e4f3cbf9144b83bcaa41c037e42ee788229ce622b28d3b2ec91c3b1b609aff0108bc920e0f6f43266e1b47598bc99ff5410e8af2aff024b35f8352f2aa4dac039d347d37488de305c7ef6e7aed2dbc3526f537efa2a3a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000c18a3ea31cd3830207a76b80da87192c61c608490c9c8826d4c860f8b39b2962d258dd43bcac5368fc2d9abb0793c188f6197f0a6d4c3346d2e53c6641086014e319a863a469525687eb7dfbc8924054c35d0599b6ba4dc94702edce01671a22a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46005bdabb13bf58fbbe4553d4af8f8fe9ebd0a4aa99e0cf8271621c9975aefa2a5f35ce96bd2897eef6044c3eee4aa475af4288d660c1d64576a770985b9c74890350811bd4b7dd8a5ed9193d4e2d19248020c3d334249a809fb96234f058ff90fe2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600bb6766e42e7a117d09953e5b2505c6720b77655c557be72254b0d946dd0670b94a18a9529dd8c96404596e9b6161a0a5e94f5bce6c190e733da9d4b5504af60451237bd7fe895abb96631987f0f82a41ef6d56f217dd1ef8aef76e9af4e559862a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b1042015c7048dba6b6aec2a38267657d0f221d0a862514b91b4714dab337eb41a014683101b18d98bda3cb4860749f10e5bc3a1927946e1c96c3c937532800b51f980443e5fb020d8498ff88e49fc55f1581d7eed1c66bd7653a4f5a1ba18662a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e17cf09d6059f3140f99a05905c17f597bf7fd8685db6c61fdeb0f91f0d3686a1e218daff23f1964ca2e7f983efad0d0714ed9cb5b23dcc2d428c03cc10f1a00543a164d12ac3f4ce00b176cd41a7af343c56e4ff445a2634d74f3b182c9c7352a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46004c4a8f2ee83d7226b58b1415f57cf93574021a6e828b1852b30faaf97fd75325424bc67043a889d41b4f1010e40e2ce911d02ffb252dbb1f9b15f0936fd9cc0f56682332630f5dd42f160f1e3475d1881b2adab83023216892f538efa1e0e66f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460095af4f1a0b89f8e6458417dc977f40bd63d40cb16c2411f5305f3a12cb44997ddcca4c14c350296b4d2a30cafb1e272d22b772b0ba1d85ead112b93d82a9980f57a7cd79d0feac648df60b94b7eccde724eb7e473fa2368eb5b88181b030239b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46005cfb9e1d0f208dcb64abae8d57bf8bbaeb4495e61da051a28d6247ee4eb02dfe0a5bcbe63a8c7869168caaa8da5ccad40e1d0c64b4c802cd9d41133cdd88f30557ae0c85ebaf333a6ec3251f577cab910cce072f238d1e50046322b83bbc0dd52a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460051eb234aec184ded0063029767fcb3e32d46bfba803f5301e932a4ce8ebfaacbfda1480f48991d6259401a5962804e2949be93d9f96ab7303b7367c6dcf46c0f584b81d2c35f01525cb80a02c259565f0becfa7d1651dd4c313358d339f32d472a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460061ad5b7dcd93f5ac2673bc36633d2a612584685c1ce6f1bab11561a0135ec7b3c20e2c88ae6ca855d4109f6f9892c41ed14bc38b549f00f823486a3e81b22f0459522ea548446804370cb91e2c4eccd2599a018202b0e6c04b7643b143707a382a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600278975698f785c3012217bffdfd48882e43033099ad968e849a14a962e2d6e538d1dacac16df19a0831f52daefbd4ba7c6af77cb7b4a343c575382858d1bcb07596004e838b7d90408d46de62b01e13e631575f348bc59f926286aae4c88702a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d8aa4ba9183770f61c39668721066eabe70fd41d30110134ebcd703a7bb4ea5f0288e28ac58a12dbce2ace9546b0fc28f38c5c467a33cb0de07ce8c5ac14a30b59bba625d971d505a9c7d7c2f3ad69203e69e3e5e1fc1e4905fae7703fa19e032a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000d40e4163b524c69ddb59641228c09fbcb4bff1ed875161e54bb10bee1ee21e618ff0715d5fc9e9ed1f32dc49e01926b4728c8b2664f2aa535929ba72080c0055b29e3e31323bbb46b3d1c53639ef72499c58806c7f4cb6d2e8343e961bb6e3f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600bd7478f49bf6d8d87ef834bb9653a3f0bda54dc653fe7890c69882abaf429231fed0b99dc99d2bd4f504c32e2008b135004c60e2045c7560e0c0c21c144925085b379072ec1f3f70b4650979a47b24f9b080c03450f7e9587d92cb599fcf4d6b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600117c8995b21aab23931a24670b8a885ad04493aa804a80b51572f5d29cf04d6e7d8f9adebc0cfdb1df0e4414a199398fab565d55fa0e32d9f70b828de39fc3005cd0c7b21d4834c5a41a80f7d421c7d0297e42eb409a524d33aa7557df13adaa2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46003d5d559b960da2c238fdb2609b45d125f1940fc8c6e4934d2888080b5f2c7f7da8c34528130afbcdac99c0364b88b5a329c8b742bbcfe91c7e96556b03e70907607b38d0d0b1ce290ae681fd3a7fe09b4299117c3d2f9a0a52c8a3076e268c132a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46008152931c4a5feea2e39f656662f3515ec32256c57aab6ab75c6f0b6f5bd50b508a743ff424f2eb046f1a923486a0566034023cb34413172ecb1e639634c9f00d6088164a2d219f2069d1d51e06c2b4eb70b85be9071afd6e99a92f1232ac645b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009226a52a4f9fd97a75c6c808a961744e731656160509b88038c3e7ba2e88f472b2f8fc5f865043e76558fabfd6b28dc0f8aad8a50d5744072a4bfa9a3d78200b62de44b8fae34cdd6d4df47fa7d420bf8513f35e3f63c66c4bf699675edc33e52a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460014c6bb8be7effc1ee39f3cccad774fc1e71212f75ce887f1b4bbd7184de4691c9c1cea72845d9830bb86b24464fdf7286936ed6ab238adaf78773b335057b60c64bffa0b9f160a67beb033610656b80d6c9342797fd983375e27f91ebc7e6e0c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009dbeeab2dd1d70ae4993bda61bfede77c378865b264b9620d17da5320141d749bdbc6ae1b2251e68f79ebd445b5b93d8a5b2fc5a2257c307ebea37f1d45c740e65bef6e1f3f6609291199f6e0940c80e559588368e2031086afc1730e0584da22a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46004ff39ee2863945517eefde66d42c4ec96da046097b7efbfde7c3111a6c06ce97f552f33374f43a723709a4b3a0cbf6b226af61515805c74fce2527bbe828860b665da232ade423c7280c6552e6d0ec8782f9bc742c0b030fc08e9dbd3ca5d3862a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a363544def43bec4ab3cbf8aa4b637198f7cc7bd89c2a402d40c6c10aed6a9f231ec0ae946d8d21c4563ccbfc1e6d6ec73063aeace3397cfe34f6e78e378220666ed84a2c0bff828dcdd1d82463f642666dfeade46c020621490886912bf5e022a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c2ef4b28110bca12507a617a321e2e5e3abedfa0583e4bd9f320b02d5984f62caff3a28a627819a2f2d54f01f80713248a1e5f0bfaaf8b261a3ceee530b89b00677358fc648638cbd854d2a009dd39b8508dd3047d0c5f13bb403a64d053ad032a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009d76b8b8deb037178f7686154d9228969aa30d5751ad1c2e0097c97ca39f3686a91afba09d50e443f75a8ea25f41f1e63daa1a5e0026e099f9241ba7aa38600068f8fcc2977c5fdb4ee46f234fdcec2f60a22f9c63ac7091b8ded3ec441df9bd2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000b027d479cd33d8d57c998621de2cb6dfffd320f53d27585d3db9aa44c59bec3ecb584f003e5571e488cd92a3485ef2b740c27861a5d780571dfe10c07307a0e69470b335262280ce95a164f7963af49e41ad6173f8db9e3faaf3ab54a8c50322a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009bcb275c92fdc035bc43aada4277e97139993270fd0ab3661350adbb09304b03246c37b02500355654ced15912e9431858d85a741d29f5f9ff65ac597fb3700b6d14338abf210592babaddf4a584f2ddc06c0d333ba8a9f5e284c3be59c828512a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46005c4b0f868037118206170a6b1d858ea81047028e10bddbaba24b1b6722ffadeac7a10dfd3cd1c2cdfd196ae3da5da59a0dd858f6ce15aeb884d39e100a6619096d28713cf7af6e13d24dc67d4540225f637f0384e58e2710d9e294e7473edc2f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600cad6e6d6beae1a9121a104e512d5d9d89f60d0aa1a14e89ad906e5a875cea4d6290956f0f6acfca1f0faa651886556d59ad2d5311f840eda172fdf0fb9ee360b6f7a1a4f9d8c054907f3b6b946a59525199dbcffe9fd3ba89612a5d4b548bede2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460096490d82522f60cceb6f64fe9d7be13cb9d3d5426f34d9c6dc7cc7fa10b51705ad8c0fb4cb8d1df8520af798f4cae294b765f4c918f75f8b80a9110ed4fe2e0674998004d06285d2b8d99a87a9fef38f0fc3109f4919006b8d1831b0dc59b3782a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460012d3db9c5db15a349f1152cfd3edecc7cbcc6b7d6e0170379bbf12e29cbed42c9cebd9bf55832bab8dfe4f761017aa068be5f84101c4d7bccfcb929b34c9ac0d74a47c40733f76b6d0502b67a709fb5e5cbb263b180fa0fba800d9d6758207362a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e1923130437dc6a03eccefc51c3717c7f95851aa9ce0ad76ba0a22b5dc4dfaefa9cf456eef9a10b9ffa1c33a98cae77d6b16df72b1ec7bd09b53d62dac15d40874a8906dd2888c9e7edea13ba3c42ad4d833f4a5de43d5ce0d9b12c654ab2e872a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600cc61c46e1babadb7667708e3ed03990398d9bf080ba41c1fdf67b4f51dcdb7e991b6918a6aebaf36df53f828f950a7698a21eeb1c640f809281a08424776cc0674db730277b3ea5a14e52fb0ebca202e56876c666aa160f623a4ca411b7ced702a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d9838ce8807f0de4ac9b15d648031b40e5ce436051756b3b4c852fe07c67715cab2b3cc237fb7f72e95f970760d9e36ece5107e07f41b43dafb771a42517d4027589ac4c910bd1e08bdb36f0236c05cd20349d62e07bd34d89afd21efe56b5fd2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c3136be04b86dbd5ea6d9151f51d16b47aab73e059569dc775951b16a71ac4fa890bba142e61bd67470cd1fac95ed42da2924bf82826caf7c70c3469acba540a7a92827270cfa82f16145a44f9bdd9ff5038ef1b665dd520a2e61db9749094962a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ffac47e7232095f25fdd02a1a5dfdbf3cb9f9c999881194fa50ffb9a6f6cb00db0897043b0d6542770710c727ae5dbbd231eba181ff1c097ade5be59d01389077be4c78d01b0569bdba6cdf39abb6e02f591942133aa1db230033aa48d18fd552a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c550613a2aa3703d95d343186fb5b9ea3e5dbc71389f842cca278d8a83f7735655df193eaa580de1a0f02082385ebf6a1148d93ce610a165135448a83e6a55017cb4b69ff1f333baa130c02d83fb533cb84fc47155a707b7978e8455431f4acf2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600113efdc304c9dedd0baf19c9b54c235fb707a91a7b6730fae65d3860e960d6f6e757109e78422f7d568864de98a9817fcd1d1c435c9a73f13680525da4694b027d32388dbca301421fe038986d97764ed933927b5b74b91ea5320371a31ef12f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600cd7aa2897e1d6d13af72130d66d65248a25ac7003734db33c3d5c59f344ba6bacdaaba9836f959a6ffd140153f8592a0a30146a9ddeb34b75184c17f8cfae10d7dea0d8ef4c3c5dccd46e8d2af6b2a85e8b36156cb97002a43ebee8229b82bff2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460097ba00cbc15f7d2e6c06f7113afeb36984a2322f18960c5fbb8d96833c6aeba2f7eb6f2b485aa1e470bddfe489527dd782b21d3d70c6a49321202f763bba82007e7ddc9a34f7d5e0acd418405ee795740b3a004c977a9ba4ee103755c05aa76a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c42a2e034b4fd25cd46ffe153c8ff0f6cb764ba36c36b2dc125ce88d8593e5e8ddd636b246e1852e3fd20dee2a7d5b026b89aad6cc9080b4042cdb101b05ae057f406fa8642d4d3834434465fe6b1815ecf25a5875e0334010bdfab4768fcbdf2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460059b47b5455aa16792cef813dafa11c7e7955b33fcf0c39a67b85a64e620403aac36542cea1ea2d2015bdc71c09b9c60c33d77bd28c35a69a656c83743b5e63087f4786d6fc06a89b65579200e5d1bd63caeab25893891f06597232389866b4152a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46001f8980114bf02490af98132d5fd4e13b65eb990e39e0dff52f9e152754b83a19f750eb812a63a4255d621cbc095e504e54506c3a1399d421a36e77364d40c20f7f57f0e89ff23959dbdffc8f66bc433fe9849f7a9c335e0601dd59812db059862a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b90eacba52195a184b96f5f19948facb9d5cfff012278471ec8c2ff572e70fb3040cf595588bba91973fc2163e509e0686f56039fb15790ba172aa96ee39d80a818a546c630b881c2161588866965649678cda0f4110cccc3533d0f20e5e41202a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d679230a0317ac247bbbb605e6460aca06ff81c1dc65c5ea9b00d19b3df57265ce079a8be66d5d4047f1a5d81709dec9efa2f675536d3cdfdbe68fd426981d038930b6c9661fb81a752c137a40cc8091480743856cd77ac965f4f6979ca8f30f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46006869e6d4ca689eed4551201be9c4fd6024e38ec95ca0a6125e156b95f4f18ec4bd6b49e1ff7f5397a6d6ba77c27866d9d089eb660a0e7efdc61a8042dc4b8f028934b9a6c38dd420aefa1e115c839fad7a71147a6efc77ab593485c3b07576ac2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460038c61e1249a82da4c868ae51928fb4d21f980e96a9d32887fb9c4f41d2c6032c03c54e5e45914fa716c1e256af33758a5c7d333fa780c771e0e6f66cb0427b08895238f7e22e70f3f59aab49819d5237d6776eeb65c2d5a925b62ddc8d3f0c772a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b819531987ad0190e399e2d1c87559917590850870b8f938da3574953e6cfc7e0b6c6fdce91bdb0e0787c3bd2bdf1541eafb86da210ca85ae2bf6fb0a6fc69038c2e1ccb3645a2466ae46d3f3743ed89c0831a74720eb5c4d8490b1e04e5bd4d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460075c07804bad1b0a7f1211718a6d644cb500bc66416b6004f80903b51b3efc2617d1db5175485a2ede4d7bff6f0e8cf067630f11d62af1561032380150fef6b018d67a0c4b47eb0e087f2cc4c3973b59c9ecb729fc960775d76138ab09799466e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600bfeb58aeb94d24af2fa1fd94a4b9869d40982e3f21696fb70dc9f03a9b80f5d6389e4f4f11852e43900e505c282c8c8110545d547fd0b18cbd88707e8a5fbc058f02169e09026a2500b440eb43eccec58a6a32d9f5cd9644fcf26f5e0c9692632a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600601161377f0932fc08d9127736fbf0052b763f365aba81899e05c804a1c1fe945d13052e264c550e3524d6430d69700b016113b65b6e6e73b22918fac8cba7008f371abcaa2d51351b62e5b400f52086ef7803c1a1351563c5f405a6ac054fb52a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460082af14bf589502d2963520eca5863fe7498a982e11941fe25b5da9349de59cd245ab8b510a09795ab701ebf008eeaa812c877afcebf54e70d95d45ede4839305908ad289176561ffd2c95f7cc8a2ab5c6a0effd161e3aeb1732140aa501edce12a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c19eeac39b52c944ff50ce5bc10e9726646881c798c6ee4a88de3dda17739fb11742e5e35b5c7222ccc4cf95869e91947b993e668471ab724c2c05fa3a37740b926ca2461d028a766de4efe3d8412bd08adc97c9bb3aa28c07a702ca82ac26a12a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c7eeccb5b57eaa678b568d72fd1459886758029cacf2d33f944d382f8945c56ebfc5adf4322f768116fcce9596469910f7fa214568e2ff581f2e583beb633d0a92c3f7a560edfc7927df1631ff35be263beee400f187c090dd02e4a5801a82102a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460019fe510b724fd4773b7fb1c739f1b0b9313d6ac2f6341f693dd1f898bb10bf53bd09fc398d305c55d598588be1f8b9727fa41a086c4d4f0835aeaf26ed50d30098077019fe554385e0cf6e0f3983b510cb37dea7d42f19ce0eccd86e3e147d352a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46003abdcc324544ba69a1b63ca3db2ac95b35e05d37fa96ae24d6a215db9943ea817a5eae59006784a8e1a6ed3ee554035edc0ca54ab4b04b7f95b48e3548ed36049944dcec71efeb5186d55e2d5acec943bb28d1ee9f84db3a2e2177c3d7ee9b842a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46002a4d4e13d1942c7f953c793731ab5c12a45d0a25826281c2d23eb23a1b74f7c40698bbae7c0f032b74ea44c71cd4f550b9295058f2f1dbd7b6f966b00bea110e998b7545adab70a645393ff37884688b9960c8c04989e8575602625d6cc344f12a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b70a4ca5731238626ccbe30d9e740afa075f92a8d217fd224301d1415dca12556a89e0a2797bb2098cd0ae62bba8c4ff98e7d2089092e729bb27c039b71c15039af39dbe3ec236efd64ad0c099648dac388e424ddaebb0cba1793b1ffb9a5e2a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600dc57933c5a5a2ac87b5b72d60ebb537a7bf9bfa2da6e0d45cd322d5408800d7cb1555298dfe74b847e69ffb237754897847afcc0dcafc80bae56209add6f760d9d10dbeb235ce888f8ed0279a2dea721df2f91c3809999301774fc2a5f4272ec2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b0cdd53676e135af460bc95ec069717e7b78747b262d5140a1a193b855d9fbb159cb499b7fda361fd80b7743d10dd383526b70feb8dbc0b06badc2b911fe22009ec49b1bf8a4c76bff00c0aa80adebe0e9249e37fd977babb7be36028b65b00d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d5cdd8a7eb544a9479ffbb0bc74072ba782768cb615ebaa2e39bed4784b4fab6a86c4db944a889ed7dda78b2c0b5cabe5f73c423376ac92c379202a6814fce069f107b3106c7f7570af198895d8314c9deb526a2311da652e5cc2c049212c0c92a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460033038e1fbc0dcaf9212abe66e29d423b659cca10395f78a86774250986079aed49f81381e1dd793a19cb7e0120b636ccc098604815be2e9e2950a9fc857a0f02a6c5e1d8748c139b8d98fc3bc12c3e3a37ac8e0b85847090093b99d0a3c4b9212a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c9730b655bbad75eedda6adc2f28a30882d8986f28bbb6d2f0eb8f367ae64a9eeb9ff25365ccc11c67db4763dd53f1eb6345169be20199da23559be607ccfd0da6d3f9c048fe3ba16d0edc38626afc398f2c032a6790690d1de77ecc6a65b42d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600feafbf9f8ca6a14c50f918d8fb35a0b296e9308ae8b6c4ed7147d2aad912aea13fe7d2639829a5ed64c309c573dba88894374fd480f595bc1d7018ec8b37ae0ea70673aa688422f1775915bc89d6ef922db7f9bef28218f11bb30996a406d9ed2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c21be32aa3713d12b85ad0f157e3877154384e4049d5ac28a98bf19b4e57e1c822e6350c5ca96658ad6f9887440dcf967085e2e4008346b3cb950d46964aad0caae2b6fd7dd2550a95d384a06011091f788cf7218762e59e686ca94fd09422ab2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460050ffac3d9c7f04901bd6dccb14601d1c6a9b0c80587e74eefbf625893e351cf0dc1d4aa08b3030ad5398a461c065186138e50ce56a92d5e46eab62701a036a0aac1daf1e3e59dae210f2b56a2b194bbad084b0c1cf492289b2f03289c635d68b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e7b07a35b9abac9905f77d01f690f01cadbd540fa75c8147808dc84f5655b17d20951de43322a74e3e9cafd48351d58ea0aab31695c3df00d5fa1bb33a997d0dacab0d24dd65b0df17ef62e2b6faca0662890de57163550c52416630850787292a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600881b5c1abdf8a9f8ef9d9d9b27bd9eed35f8930e8ec3bad5ba82490c139005cc78b856fb9c49eafa6581988f7fca348ad60a75723db53122c73d25e858284806accc6b239b9c0c62f0fd1c9cd9f90b823b41d955865bac175488ed4e7da408682a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009edc50b04aedd78e4771634a8333fcf75cb4519e3da0603bd6203475d78a3524dc9d5e6e1b671bf4f75e30f7e1c8bf044bd4577d94375d33123e365c0bb5bb08ae2734ab095cee7ac2ff74777a6d0293ee47303950df193932f2dad4d28526c22a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b987cb9c38a0a401cf03b2488fc73b7faaa5df95e8384d1153cbb57620d1313cd67a9a610414056b91b02127543d33399252687e6984d867cb1e2ea97d83d801af2d390a8e4d1e464e5abd80e9ef716ffb7e656631bc4778deb7834f4fc20dd32a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600bacd2480f5df4b871ac9d49d2e79c619e51ca03330c6e0eb517496a0f163614d24013930325ef5b76031574977e8747c04c6da8352f8c80a8f05444c18c85105b2bb7fd960d5d7ace8e36e0e4ccb7171e5c3740b2ca8a96d868a09cb5e17e32c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c73e0d10678f068c32248f2b015a3a0f415eeb404fc6f89e8589ca1e208ff5949f230d73d985f35920285cfa018d43158f0972b6588320c3756f3b9df9b5600fb38176139100069f1ff20156a180e55782796df79f4683020b06f2a5019da6dd2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46003b29752db4fed93ac5c20b344bce075851fa5cbafe02523c694bec4e3b4109fc0e88bdbd943f0d5325d62929e124a484cb24460dc06728d2039510b906928b01b7626f7e84ac48645871ac12e66bc747b9b1f64eff2352537811f9712c127c342a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c68b27c9db20c07fb3801dfae33f92407cf8e3b9c952f494687140d9a7514f0db75c71c2221b0960d74b23f1dfe643ba2f6c6fb429744e38b2f7f2ecd563d608b771cf172f891bcbbd1126e3354cdd4e324cd7202af62f5fcdcfd2ec01ef7d252a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b272ced92db8b46f6a4a9c0164fd24f66968b23b0dbbf52ebd736b1ce0a4c0512e4715cb0c0e063d580f4b4bea5811950837fc4882aaf5e59aec026ffb0f2300b7c13f1239888cda5c8e6ac9ea10675df17633368906e66a487f91ddd3268ca62a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e155d70b9ec48021bfb05f7aab92008e03f56a19222718aef3cee767a1882805166889d248053ed9f7b4d0c00b19d1931dac509709fe9cd0a577b71c8ba88c06b9186b95c90d2d00a31e7c68066bd37d73408271762604e3608e2f2c983f83092a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600438f0485ea2ab423a63142417de6065b4243e190cd326656e2ebb895489ea898950cbdc05ce8b99ec21c88a96c3953a2366970bf5a0f36c6d0ca34d121115f02b9404a5fb9f1723fc9fd4c5535dd9ef1d67c5237d9d322854697e9e245064e332a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460057b3433e3f0b68a8787e44e78c1ed5f27cfa4b155d3002193a452bd1351be719f47a4f445d635052945cc21df9e54613c7acec835c90a083e89276e7eefe3206bd81133a8d8a33ad2b084c54536d7a17ef0eda5e810a9994b13c2392fea208a92a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600f53a753bb53e0243dead9f8d43f93525d02a0c69b202ed0cafc6afdd50962cc8a0aaaacac45cbbb936748d75a2554173c94531a5bbe20f9664bcd925bb876007c0988a1b3c91b35a7c3722aba7d3a55f79ae07ac6a46d57b4a49a06eae20333e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600276a7f7126f933b4412dc4db264ac434134d77c174c3d2bca0fbca788d0327b24a2a1cd8134dd39cb9faed737ba496ce35b62f663955a88f69adf8f9f7061b06c3576342cbf99792896ee5329b04ff2eee2fc2bb6d53c5c03d52c8957ee793fb2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b5a298d2a97dbd7bf266ccfd61f20f739fd72f1cefac976ebb786e712643de23f8797aa0f160d327ed5210b6bcebb2429dcc673c4b9a39515cbe07dde7d80002c3ff25a1743a9df92af4ccd9a7aed5cbc90f64fd538c3df0a9539128f59652672a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000b87db26d1a4eeeef1f18331c234e6daeb8aea48568b4424e76478eaf1336e1cce3e3b72d136d638be075d9238c58dca48456e8587ce2ef2c46b1d7bbbadf50ac4025624fdf1544b90a61e85099d9e3ac235396d2d9d37f4921162fa688ba95d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600b472fc0441578ae962487a27c9903b0e8cef78c4c69521c759f359290290b1fd4ce33692f3726dc51dad68f8ff3cdc88dc6a137d2fa46ea4e10484605c4ed90bc4153342949e45f683cb6df646fe7aca71502b1139c1dfc929afaf73c0a6de822a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c58f80813694cdd8df0fe0586c341a1de7ff7f6dc7c093c2a7333d7e883354a5047ff31079cc00aacfe59aa12207c037e071f710a156247949099120a6afac0fc4374616443e809c1c763459d10d6bf6a2d999855b8d339a27d20d360ed5f1282a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46009bcc51ab91092800c4a9519ac8bd2f37c7a7079916a594cef09dcfff47978517c9b1c276749de14c21d010371cfa8da4d5f477787e0348fc71c7cc5c57322a06c49e3cd2c701bb845963c2870dcca12ed070c3f67ffc20144327a93aa6e896ec2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600c85a800ac1f59f0df206ad6fa5f26e2f208deda2c0af58ed8c6811c525a9fda21a781689446edfdc0b8a07062daffebe442699974b64cf1c31688fad59ce710bc61cb1b626fea15085663f7619a1769ffbe4fc1f8c63e6ece773acfe180806c92a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600fe7489b632220f8c23b1d66fd40bac34513f1eed3c9a343a59dc09d8d360517db73b05c2e33b0da1932a7cff38748645d8bcfe843290a561c63ebd7b98ec1e0cc63062763b78629518608de49f42c802ec2fc22f477e10a1a0023b237675d65d2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460018b4e42adfd1044bce65ff12455dda705b023cc0ffdef4c073aae2180cffac8375975214bb357988f37441c14e1c2913b2c7babed43a0b270920da6970566c08c676bdd3798340c7b35c624f2647f26816ba4b3a0821339f5a0d6c9f2d84ca3a2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460034a88c97937fabdc6e2ec582df7cef3829a2ba8b6e57795bb1832e08d07f12912c546f7452b4d0338ce197a9c0a376202387322f371495ebf891a3cb27551400c6a46dbbd2e87ecd0db7ffcffe24c1a82353bc4371d7c2cfb81e7c832d556fce2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460099d000bd6eb0abc52ab88474dc3e504f6e0b21a00cf196317e471926b93410177ae48f2860c98678769671d81b85ec462596ddb585b6e30f4e71e1a98ef80c01c70ad885a3f3ce3fa4041e1477cd801cbff8daa7c835d4463044fed653b3830b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000bef4c68d2fe10b9b34995633ab9dc2c76f43bc2e837252024d85417a12dcddec9cbe44c70891e541f0d67a19e1929de1b0ee5f610808feabb9d9741a0ac7201c8768f257eeaa2a6636fc68a00eb781941c26d2f5179455ac4949a320f958d6f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46008d426d804b32a7615e207fc5fa9c119c9ace79782226bfb6dab21159f3950f18619faafa59caa9a1cd0e0401698137de6b454e45a74899e74681e7e2ee85ac07cb92ffeace78dbafe6fbf275741b4b38657fb81590712aa0bca7877931f6ad392a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46008319290259d0047497e2825a6ce9166f285a7333ade0a970ad928fd1b259feea5c927aa2c742e7fc7b542c579e65979837a77437cf28370a44feef9b8fdb5003cbfefdf389bc341a7e17139f61146f9fb3a9d7bb84fe93c1f771eafd4d4d9d462a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d54d20eb70a33339ff29f8017a48a857c4772ada28e5a88be923045c7ca2194a5683d51472db38d9ee5295fa892963264055b15599226d1ad75aaf550caf9f00cca95e245ef1d3209f6707fec25036c0a1b93ae5613a5c93095e23520395c57b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46006fcea4a22b6b3b28a52fe41d07f4dfcb20f80b2d3308ed4b5b46ed8e4f982a6968bef3e34ac0657b1807f9f0203d905d8dc8caf4ff0c00fb651489984046510bd060d2b638be7c2ff45b20575a76c4acad1b9264607e1b71286e4e00a03266a22a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46001c0549d687700803d27fa15f67df39a92975721e14adc62d267fc7b676c2bdf4d42c66eca0ca0b6fceaceb73d042a59bd88c040aa870c4636f5ee167776fca04d1c146d2429a5a827660008721c7a880e71f44feaa3dc75524c1a9281bac48cd2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460036ad739a304649cce8e4f18021379959c98bf50e9fa1abfd74a9c3778b35f3ee2c2401a0f51794c4c44fd99d167ec94a52529761c41182fc708a6bc25a32b50ed1deb1ed8d4e155f1b5bbc54610756cf2541f43b7c1776f97d404503878b96002a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46002e1b05424cd82fec9717f23563923e095c71221e13a284261e553617e94c2f4d053c28d9652abefb837ba0d9cedce01d40696794117fca8f2b232ba97d418802d2c1a8caace45c00d73f64f241162f85842c35e557e097fd1749040fedc94df92a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46001b1722198be15749cb2c16b9b0cd42dbb320c3f6ffaa880fa90a8a8db74ad322f69bdc2762796ae752ac4377637aa6e0d757320c78d8a2b3f47a8fd0a1214303d3adef5f9366e150e5a21d8837947fc2378997c7658245fdf0bca95a513f04f92a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d202e0bbf1b01acf647b11dd367019b763c8b37f1a9cf1d1a1fe663f5cf1aa6c3c9f4a6675515e9ff47255a6627e888de370f118abc6be342d624a6a83dd960fd416f2796d696faaeb34d2cdaf1004ede551b62a690f99d69b4ccd5f4a6c248b2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460028caec545fd07031c5e73f34814d463636013534942812417d2317c7a557af8d21c742280eeecf514916493746db6d6ee1e059170ea0123220a6ae57c3c8e102d4b350a3b25c27c9ab9366d130560326fe6bb7f3a820767736b8a061405e153e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e5a071915555df4404f783902b68cb3c44e4ae8485d043cec0e615ff63719e6eec2cab48ed142c04efffb4321724cedaa427d27e9bed99344dba219a533b6b09d67211ff6ded7cffc866c81e24a9f54f08cd1df7ab202ae796a01d72cd2d3ab32a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460029a7b0b7a6b4a80c7e89b1cc2bb8635bea1587ad214100da7b11add353cd3e69df0b8dfdf17264eae2753de1ef1d62d267cd7214768bda15f56f9e6b00612902d744af98bbb366011a0fe90c3cee81d21cb301d9bb72d5b40b28da348ecb81672a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d77c1a9378d17bbba63e854c9bfd0abab0b3650a74bd698d7b9feea4f588d5dfa2ce724f24f60d844a4feac5d9b5928f4c537caa9230ef2ac5d1d9953b1b330cd7be80fafe9de0570984b008636808811ccfd82f32639cf6806dfb86c4c4c4c72a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d373bbbb4349a52b00d00a3981a161ed96bd0fa969faf1dbb8b49879f86dd5839b0ab12856f912ebe2c8e8a12d82dc7688729a6449d3bb6b7a6d53f66d82d203d86e516b7564a8ab0b67b503c97976157c05fc2395068b27df5f38a1a9b29b6e2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46005065b2652114169f82c0c1483c19f8fd2f0c72f8c98928fe419ce0b93b2ef1583cb684837009c4c97381eb83c880f62055e9068113d9e07d1b29c991521a5308d8a6314d0690c070c764863c7c25fb8e9d6cd462f9bd56b0b236558f66fd74fa2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460016e5038faa65afb9f179af818be6b65c1a93963effe329ecba6786891ac1d8956e3f209365e45bcaec3879ff27891202e219e8061228f7001446f532b0bc0d09d978b1073d9c88d84ece71a915d577b8b381938d07827447ec20a5bb250496d22a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d57606898de72fb911f85da1bb8fb3743b6edc0e1c61a61bc05d3b6d1d429d5b6d86e38ae7289b40efbb3df89d14f5704f8e61bf2bfe195470f8a8fe9146d009db1067e5c50401a17b926e54a98e103abd64c4b83c87b770a8312ff03bc29be72a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ef38ecce8f3b7f2253942c612b029e05a6b37f8b1c406f5d01479ec76f956cd040f3a679a9e77a037401185a665f3162531feab7221d828e4bf7662ea876000ddb231b388a5ba18b2568eb1ab9ec84a637a66d6c1286fc7e1c2351250db635c32a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46000d66f46119003f7476f2115373c0b88a81e3044ef83d9955fce655439adcaf22a12b74b5fd18db965e694d5635f4aa2418a263decf6c448b891462432ee7ae05db3aeeb826b44e7808e3865df0da2f479442e2f2fc895a46dffa8d908ec95f512a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460065ba17675d688b0fe1ae25984335d388c61f14a81ceddf6b6585b90a9a59bba0a4070d815018326acd57a88edbef65d60ba226554112f06c1f33b0c6b39b9401dc1aada86981424a634bbc637e58712bc5002a34bb6498487d89d83534b05da02a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46005565dd6e8a390aa49b5491cd0bce4798501a87884ef50ba120262b826ff28ba5741930e988241f4efcc0cd42f7c621033166193fc85b00b792d41135172d780fdcee76cd80cf2218de9e8c4ae1922fe1c26235f9cb479b2950b064025a1d69412a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600f796ea2ed7675e68b13fa0df5a873706de9c3490e006b4b7e330668ca62ba0eb7284b5087a09b9ae01e2ffa3aed1a545a1de99ea38b0bc183d46884315170502e2cc4424464983ada824bbef5aaa8995e80ef1017c15ef3b13902599841637ba2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46006abff33b091bfe069c92ba5bcd25d56b604a45cd1023080614e8e8578f9327c7025a9bb2ba92c4db3e699015ac4f46dacec10245d86b73d41b9e751bc7f6e203e58210689f52468a22c10566f3a2c6f870e61524298a3e781425861d40446b132a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d2a95ca51c964bb35183f2ae626a09a01b7b7e82a046af708da537389d68757b8a5d5c8f230523c5ec089277b7c0e072fcf4d9dca2ecd7bb6a3eec6e95919c01e6ebe15e9e2c9f0fe234241ee50e9c574774807c0d17f8dbc7fdd1802c5c79792a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600254314bb9821d205214651a8c292292089a320ccde7dd38acb21552a855eb0ace84b132f04317b591b84be06679d74df67b46abee0128089679c45e6f7836c00e7210714763e5bb3fab12067d0a55784ae0d70ef14ccf9e243bbfc6d329834102a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600e3b32fd44cc8a2dfd85b2717b54a4d21d93a8f40b0c01be21f81dbc482b383d2a4c32911a93fcc4c8a746dc75aa0925c6b266b397a17f05318e6cebf51661001e81206b483fdcf1fe42145f27d1efb8178a57ee24b196285374037da2a53b3232a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46008b0b5494d43206cf55b468597dbb9dcfc51cb59a5b94a2f2150d5bed423057867958b09d96d7b772d41d2ffb7f68125ea119f0d628f2ad338856a34fbdd8ef05e90f0dd5c3d8e23deb6b82b9d7335855f5887c3b77ac1fe30d9112b22319bc922a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d2a3355b20c0bb0286e1951b985ac51bea4439b0c70ede6f38c026495bdc948e2df2f72d00b1d3d7a2bedfabe8a57d6d4f0af59670b5e75eb713a1ec2cf7d802e9836cd1a96c0010f43161443a790d65ea2afdef21b05bd563ed55c6eecb00662a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460015d31429c5579f409756d92155519c4bd60ca1b095c8f5a5aac66459efa937f48989d599106e5749cb74e8718bb96c49119ec5aa11adb3251a9365456809ae0aeb0af0662538c295c85d7da7b1e0d929f8d887a55bf1a136a144f2f7cf3215802a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460083b2ce5b2c3df07146efcb19e6dd1d8755ca7ef52ba3b05e3a2830374a4f984a4a2946c0905d47f962bf14e51bb0aba4303806247135d579317349e421550505ee8f86db1c1d2b7cd11e962fcfd1a7e847204337c8893f1c1ddb75c6bff3f2bb2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600dd14dbdb4c43afa43d3da56daec9d48f09e356082e2317b505fc1a8520eb491d6fd344bc94479d9497be72d2dbc95d193c6bcaaaec8c5df928b37e8373cfb005eecc027b5c05b7158fa0614fd70df47412f181e808bb78cbb63e29adf14ec91c2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460053e0270ad201d8ccbd4253715af906a56e2f44c6d78f9344da12e465a5c755ba569b0f5db093834921b8ed11f4df2dd52497b387efdf3948ff632ae40f7da504f1020dda4f7e6af02b444bfcc70a0a18bd48042ad72b1955778bf4e56086a33f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600ff32188333a59ee7d640f5897f5415cdf08e97742f82d340687c63b71c0ba9ae42599d522a40233b772d6147edd01a7d5bc865714f95ff979c03e61b9d1a6a03f2a930984c2799a98d298521128eff1add9706c9e57e848d54de41f6b8dac4b42a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460039c0604b3ad216bbdfc17d641114a0d25e0865ff21cc945574cda12f7c41f7e19f3b65550b410b017e0e8f58c94d9abd29f01b7036193305fbecd341e7156509f5054136e1ea0e3956293c422a147eaa4950cfcf0ad9596dc6e6a287bdfb06602a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460029e0bf534f510a4cd43b5011d63057da67b36e2b03cf6087b3bb8ec80fdbb9e63b5e361313e2dee669d1d5644696e5c8b4e71548ec9c6daf4c85873318d9b708f8d4441b4ef1c2ee50272e1767ac4f773a249eb663c7e37bc075951f3b52ede52a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600753c2d7a7193f908ed808903b7cfbc3e2f5a8d9fb380c054f887c0fe21b206d81406d25bb1d0ef6cb6a0feaf015f9e96c0d703e9bc5a0e956feff40fdf0f0d0ef9887f6f0a8673aad0c10d1bf6ae3ae5f0089c8f05d54e180e16030949a8f7782a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d0c3538d97ce8594f8a113d01728ac4b13549646557385c6b5263f4a67a0ba96e2e5f0ee3ac9c85fff368b4013eb43900be5e672375a3f3860298342a0325f05fa786562ceec7e61851571e8eaae35c7f0f4b2ada3ddb4d577c66ab50a1d6ceb2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600eb8636c254b4cec68eea4ef00a3ae43b6dba9c8f85958b514042d0764f66bc9b22e4205ff4d8d4427395df3aeb15fc840356a9eabacee3a23c8cd6aff517db05faf968e9e1468d58947b9217ed7aecf19ce457036a2b46e47c44e5a1d67e5b1f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600a7038a3eb4b56c7414816925b26d1756706f2256596c99181ae8746a40cc32c5192c93a13997c9a11f31fa6d6263164e0c06034f0790400f1730c250f4054205fbcc3ce7dbd34cd50cb3563d2b2f62a5ff2bc847e6b6e79faee63257978f9c192a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600762a2c5d7849ffbd8c2560f7e6638ede211a0e59347bb1c7f629445e6af0d38826319366d3597f0773b49515907516f2e1894618f8517297b4624866c28e3e0ffc678679f11bc904720421273689e6826acb42b21e2c4c5c1d7bf532d89668412a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd46006dbd8fd05b77b7556cef50351310ca989db725fedbe17fbdc079043ba50dd86f7fe96f621e1eafb13aaf097fed1d441a90e6462d4919a752441455bf49698a0dfd9e354ed59f35c917c42471588142e715ed2151e89ecaf98bb5d837515d74772a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd460010d0c657b622c3927cb79d4e34d0d1608e0b4f12294277049528b4a3e4add5745ce5cf9048f16607fcba67325cf06c460d2766421210dbcbeff29b1f0dc39402ff0bf39c82ed573d48585448b0fc19ba3f6203806cb6b4a230848892c097a26f2a82146e771968df054c8036040dea584339df52d8cbac6970d4c22ed59f702247bd4600d072ab3364ee35ffd547d78aba38e764805b6caeff708dcd806acd06ee2d684cccddbacd4c5d3ba73a6f6e03d3749635ee8eaff19137ec7dc686c46af8f76b07fff437ff18629bf1490e5c9b3ec6f1515d46bb9b2aeaa6e39e36611f2479b50d00") - fj := new(FullJustification) + fj := new(Justification) rw := &bytes.Buffer{} rw.Write(data) diff --git a/lib/grandpa/vote_message.go b/lib/grandpa/vote_message.go index 5767426289..2dbc085026 100644 --- a/lib/grandpa/vote_message.go +++ b/lib/grandpa/vote_message.go @@ -184,7 +184,7 @@ func (s *Service) validateMessage(m *VoteMessage) (*Vote, error) { s.mapLock.Lock() defer s.mapLock.Unlock() - just := &Justification{ + just := &SignedPrecommit{ Vote: vote, Signature: m.Message.Signature, AuthorityID: pk.AsBytes(), From 429cb685c124d20d2677e081e55b4397de8ed1a1 Mon Sep 17 00:00:00 2001 From: noot Date: Thu, 15 Apr 2021 22:36:46 -0400 Subject: [PATCH 38/40] fix --- lib/grandpa/types_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/grandpa/types_test.go b/lib/grandpa/types_test.go index d8ebc667d6..7ee29885ec 100644 --- a/lib/grandpa/types_test.go +++ b/lib/grandpa/types_test.go @@ -74,7 +74,7 @@ func TestSignedPrecommitArrayEncoding(t *testing.T) { require.Equal(t, just, dec.([]*SignedPrecommit)) } -func TesJustification(t *testing.T) { +func TestJustification(t *testing.T) { just := &SignedPrecommit{ Vote: testVote, Signature: testSignature, From 1e166e614df3955c156a1a4974e9e277a42af60a Mon Sep 17 00:00:00 2001 From: noot Date: Fri, 16 Apr 2021 13:46:14 -0400 Subject: [PATCH 39/40] re-add finalizing logic in handleNeighourMessage --- lib/grandpa/message_handler.go | 57 +++++++++++++++-------------- lib/grandpa/message_handler_test.go | 14 +++---- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/lib/grandpa/message_handler.go b/lib/grandpa/message_handler.go index ab030538c7..2d31704b56 100644 --- a/lib/grandpa/message_handler.go +++ b/lib/grandpa/message_handler.go @@ -18,6 +18,7 @@ package grandpa import ( "bytes" + "math/big" "reflect" "sync" @@ -109,33 +110,35 @@ func (h *MessageHandler) handleNeighbourMessage(from peer.ID, msg *NeighbourMess h.blockNumToSetID.Store(msg.Number, msg.SetID) h.grandpa.network.SendJustificationRequest(from, msg.Number) - // head, err := h.grandpa.blockState.BestBlockNumber() - // if err != nil { - // return err - // } - - // // don't finalize too close to head, until we add justification request + verification functionality. - // // this prevents us from marking the wrong block as final and getting stuck on the wrong chain - // if uint32(head.Int64())-4 < msg.Number { - // return nil - // } - - // // TODO: instead of assuming the finalized hash is the one we currently know about, - // // request the justification from the network before setting it as finalized. - // hash, err := h.grandpa.blockState.GetHashByNumber(big.NewInt(int64(msg.Number))) - // if err != nil { - // return err - // } - - // if err = h.grandpa.blockState.SetFinalizedHash(hash, msg.Round, msg.SetID); err != nil { - // return err - // } - - // if err = h.grandpa.blockState.SetFinalizedHash(hash, 0, 0); err != nil { - // return err - // } - - // logger.Info("🔨 finalized block", "number", msg.Number, "hash", hash) + // TODO; determine if there is some reason we don't receive justifications in responses near the head (usually), + // and remove the following code if it's fixed. + head, err := h.grandpa.blockState.BestBlockNumber() + if err != nil { + return err + } + + // don't finalize too close to head, until we add justification request + verification functionality. + // this prevents us from marking the wrong block as final and getting stuck on the wrong chain + if uint32(head.Int64())-4 < msg.Number { + return nil + } + + // TODO: instead of assuming the finalized hash is the one we currently know about, + // request the justification from the network before setting it as finalized. + hash, err := h.grandpa.blockState.GetHashByNumber(big.NewInt(int64(msg.Number))) + if err != nil { + return err + } + + if err = h.grandpa.blockState.SetFinalizedHash(hash, msg.Round, msg.SetID); err != nil { + return err + } + + if err = h.grandpa.blockState.SetFinalizedHash(hash, 0, 0); err != nil { + return err + } + + logger.Info("🔨 finalized block", "number", msg.Number, "hash", hash) return nil } diff --git a/lib/grandpa/message_handler_test.go b/lib/grandpa/message_handler_test.go index 8bc5b303b5..42271383ec 100644 --- a/lib/grandpa/message_handler_test.go +++ b/lib/grandpa/message_handler_test.go @@ -17,7 +17,7 @@ package grandpa import ( - //"errors" + "errors" "math/big" "testing" "time" @@ -29,7 +29,7 @@ import ( "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/lib/scale" - //"github.com/ChainSafe/chaindb" + "github.com/ChainSafe/chaindb" "github.com/stretchr/testify/require" ) @@ -199,8 +199,8 @@ func TestMessageHandler_NeighbourMessage(t *testing.T) { cm, err := msg.ToConsensusMessage() require.NoError(t, err) - // _, err = h.handleMessage(cm) - // require.True(t, errors.Is(err, chaindb.ErrKeyNotFound)) + _, err = h.handleMessage("", cm) + require.True(t, errors.Is(err, chaindb.ErrKeyNotFound)) block := &types.Block{ Header: &types.Header{ @@ -217,9 +217,9 @@ func TestMessageHandler_NeighbourMessage(t *testing.T) { require.NoError(t, err) require.Nil(t, out) - // finalized, err := st.Block.GetFinalizedHash(0, 0) - // require.NoError(t, err) - // require.Equal(t, block.Header.Hash(), finalized) + finalized, err := st.Block.GetFinalizedHash(0, 0) + require.NoError(t, err) + require.Equal(t, block.Header.Hash(), finalized) } func TestMessageHandler_VerifyJustification_InvalidSig(t *testing.T) { From 59389ab87da6be7a57473c471289e75149c88457 Mon Sep 17 00:00:00 2001 From: noot Date: Fri, 16 Apr 2021 15:18:45 -0400 Subject: [PATCH 40/40] add test --- dot/network/sync.go | 7 ++++--- dot/network/sync_justification.go | 2 +- dot/sync/syncer.go | 2 +- dot/sync/syncer_test.go | 27 +++++++++++++++++++++++++++ 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/dot/network/sync.go b/dot/network/sync.go index 929cae2116..0f588932fe 100644 --- a/dot/network/sync.go +++ b/dot/network/sync.go @@ -474,6 +474,7 @@ func (q *syncQueue) pushResponse(resp *BlockResponseMessage, pid peer.ID) error } if numJustifications == 0 { + logger.Debug("got empty justification data", "start hash", startHash) return errEmptyJustificationData } @@ -484,7 +485,7 @@ func (q *syncQueue) pushResponse(resp *BlockResponseMessage, pid peer.ID) error from: pid, }) - logger.Info("pushed justification data to queue", "hash", startHash) + logger.Debug("pushed justification data to queue", "hash", startHash) q.responseCh <- justificationResponses return nil } @@ -666,7 +667,7 @@ func (q *syncQueue) processBlockResponses() { func (q *syncQueue) handleBlockJustification(data []*types.BlockData) { startHash, endHash := data[0].Hash, data[len(data)-1].Hash - logger.Info("sending justification data to syncer", "start", startHash, "end", endHash) + logger.Debug("sending justification data to syncer", "start", startHash, "end", endHash) _, err := q.s.syncer.ProcessJustification(data) if err != nil { @@ -674,7 +675,7 @@ func (q *syncQueue) handleBlockJustification(data []*types.BlockData) { return } - logger.Info("finished processing justification data", "start", startHash, "end", endHash) + logger.Debug("finished processing justification data", "start", startHash, "end", endHash) // update peer's score var from peer.ID diff --git a/dot/network/sync_justification.go b/dot/network/sync_justification.go index b4e4193f5d..2015afb576 100644 --- a/dot/network/sync_justification.go +++ b/dot/network/sync_justification.go @@ -37,7 +37,7 @@ func (q *syncQueue) pushJustificationRequest(to peer.ID, start uint64) { req := createBlockRequestWithHash(startHash, blockRequestSize) req.RequestedData = RequestedDataJustification - logger.Info("pushing justification request to queue", "start", start, "hash", startHash) + logger.Debug("pushing justification request to queue", "start", start, "hash", startHash) q.justificationRequestData.Store(startHash, requestData{ received: false, }) diff --git a/dot/sync/syncer.go b/dot/sync/syncer.go index 012415643b..e92b4e7461 100644 --- a/dot/sync/syncer.go +++ b/dot/sync/syncer.go @@ -165,7 +165,7 @@ func (s *Service) ProcessJustification(data []*types.BlockData) (int, error) { } if bd.Justification != nil && bd.Justification.Exists() { - logger.Info("handling Justification...", "number", header.Number, "hash", bd.Hash) + logger.Debug("handling Justification...", "number", header.Number, "hash", bd.Hash) s.handleJustification(header, bd.Justification.Value()) } } diff --git a/dot/sync/syncer_test.go b/dot/sync/syncer_test.go index 9f3b47904f..eeafd7f6ea 100644 --- a/dot/sync/syncer_test.go +++ b/dot/sync/syncer_test.go @@ -27,6 +27,7 @@ import ( "github.com/ChainSafe/gossamer/dot/state" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/common/optional" "github.com/ChainSafe/gossamer/lib/common/variadic" "github.com/ChainSafe/gossamer/lib/genesis" "github.com/ChainSafe/gossamer/lib/runtime" @@ -362,3 +363,29 @@ func TestSyncer_HandleJustification(t *testing.T) { require.NoError(t, err) require.Equal(t, just, res) } + +func TestSyncer_ProcessJustification(t *testing.T) { + syncer := newTestSyncer(t) + + parent, err := syncer.blockState.(*state.BlockState).BestBlockHeader() + require.NoError(t, err) + block := buildBlock(t, syncer.runtime, parent) + err = syncer.blockState.(*state.BlockState).AddBlock(block) + require.NoError(t, err) + + just := []byte("testjustification") + + data := []*types.BlockData{ + { + Hash: syncer.blockState.BestBlockHash(), + Justification: optional.NewBytes(true, just), + }, + } + + _, err = syncer.ProcessJustification(data) + require.NoError(t, err) + + res, err := syncer.blockState.GetJustification(syncer.blockState.BestBlockHash()) + require.NoError(t, err) + require.Equal(t, just, res) +}