diff --git a/polygon/sync/canonical_chain_builder.go b/polygon/sync/canonical_chain_builder.go index 4a1c7ec5b8c..62e43a00eec 100644 --- a/polygon/sync/canonical_chain_builder.go +++ b/polygon/sync/canonical_chain_builder.go @@ -37,7 +37,7 @@ type CanonicalChainBuilder interface { Root() *types.Header HeadersInRange(start uint64, count uint64) []*types.Header Prune(newRootNum uint64) error - Connect(ctx context.Context, headers []*types.Header) error + Connect(ctx context.Context, headers []*types.Header) (newConnectedHeaders []*types.Header, err error) } type producerSlotIndex uint64 @@ -195,17 +195,20 @@ func (ccb *canonicalChainBuilder) updateTipIfNeeded(tipCandidate *forkTreeNode) } } -func (ccb *canonicalChainBuilder) Connect(ctx context.Context, headers []*types.Header) error { +// Connect connects a list of headers to the canonical chain builder tree. +// Returns the list of newly connected headers (filtering out headers that already exist in the tree) +// or an error in case the header is invalid or the header chain cannot reach any of the nodes in the tree. +func (ccb *canonicalChainBuilder) Connect(ctx context.Context, headers []*types.Header) ([]*types.Header, error) { if (len(headers) > 0) && (headers[0].Number != nil) && (headers[0].Number.Cmp(ccb.root.header.Number) == 0) { headers = headers[1:] } if len(headers) == 0 { - return nil + return nil, nil } parent := ccb.nodeByHash(headers[0].ParentHash) if parent == nil { - return errors.New("canonicalChainBuilder.Connect: can't connect headers") + return nil, errors.New("canonicalChainBuilder.Connect: can't connect headers") } headersHashes := libcommon.SliceMap(headers, func(header *types.Header) libcommon.Hash { @@ -215,7 +218,7 @@ func (ccb *canonicalChainBuilder) Connect(ctx context.Context, headers []*types. // check if headers are linked by ParentHash for i, header := range headers[1:] { if header.ParentHash != headersHashes[i] { - return errors.New("canonicalChainBuilder.Connect: invalid headers slice ParentHash") + return nil, errors.New("canonicalChainBuilder.Connect: invalid headers slice ParentHash") } } @@ -239,35 +242,36 @@ func (ccb *canonicalChainBuilder) Connect(ctx context.Context, headers []*types. // if all headers are already inserted if len(headers) == 0 { - return nil + return nil, nil } // attach nodes for the new headers for i, header := range headers { if (header.Number == nil) || (header.Number.Uint64() != parent.header.Number.Uint64()+1) { - return errors.New("canonicalChainBuilder.Connect: invalid header.Number") + return nil, errors.New("canonicalChainBuilder.Connect: invalid header.Number") } if err := ccb.headerValidator.ValidateHeader(ctx, header, parent.header, time.Now()); err != nil { - return fmt.Errorf("canonicalChainBuilder.Connect: invalid header error %w", err) + return nil, fmt.Errorf("canonicalChainBuilder.Connect: invalid header error %w", err) } difficulty, err := ccb.difficultyCalc.HeaderDifficulty(ctx, header) if err != nil { - return fmt.Errorf("canonicalChainBuilder.Connect: header difficulty error %w", err) + return nil, fmt.Errorf("canonicalChainBuilder.Connect: header difficulty error %w", err) } if (header.Difficulty == nil) || (header.Difficulty.Uint64() != difficulty) { - return &bor.WrongDifficultyError{ + err := &bor.WrongDifficultyError{ Number: header.Number.Uint64(), Expected: difficulty, Actual: header.Difficulty.Uint64(), Signer: []byte{}, } + return nil, err } slot := producerSlotIndex(difficulty) if _, ok := parent.children[slot]; ok { - return errors.New("canonicalChainBuilder.Connect: producer slot is already filled by a different header") + return nil, errors.New("canonicalChainBuilder.Connect: producer slot is already filled by a different header") } node := &forkTreeNode{ @@ -285,5 +289,5 @@ func (ccb *canonicalChainBuilder) Connect(ctx context.Context, headers []*types. ccb.updateTipIfNeeded(node) } - return nil + return headers, nil } diff --git a/polygon/sync/canonical_chain_builder_test.go b/polygon/sync/canonical_chain_builder_test.go index b306d4a8dc3..1f019e0f6fe 100644 --- a/polygon/sync/canonical_chain_builder_test.go +++ b/polygon/sync/canonical_chain_builder_test.go @@ -103,12 +103,14 @@ func (test *connectCCBTest) testConnect( headers []*types.Header, expectedTip *types.Header, expectedHeaders []*types.Header, + expectedNewConnectedHeaders []*types.Header, ) { t := test.t builder := test.builder - err := builder.Connect(ctx, headers) - require.Nil(t, err) + newConnectedHeaders, err := builder.Connect(ctx, headers) + require.NoError(t, err) + require.Equal(t, expectedNewConnectedHeaders, newConnectedHeaders) newTip := builder.Tip() assert.Equal(t, expectedTip.Hash(), newTip.Hash()) @@ -139,7 +141,7 @@ func TestCCBConnectEmpty(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) test, root := newConnectCCBTest(t) - test.testConnect(ctx, []*types.Header{}, root, []*types.Header{root}) + test.testConnect(ctx, []*types.Header{}, root, []*types.Header{root}, nil) } // connect 0 to 0 @@ -147,7 +149,7 @@ func TestCCBConnectRoot(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) test, root := newConnectCCBTest(t) - test.testConnect(ctx, []*types.Header{root}, root, []*types.Header{root}) + test.testConnect(ctx, []*types.Header{root}, root, []*types.Header{root}, nil) } // connect 1 to 0 @@ -156,7 +158,7 @@ func TestCCBConnectOneToRoot(t *testing.T) { t.Cleanup(cancel) test, root := newConnectCCBTest(t) newTip := test.makeHeader(root, 1) - test.testConnect(ctx, []*types.Header{newTip}, newTip, []*types.Header{root, newTip}) + test.testConnect(ctx, []*types.Header{newTip}, newTip, []*types.Header{root, newTip}, []*types.Header{newTip}) } // connect 1-2-3 to 0 @@ -165,7 +167,7 @@ func TestCCBConnectSomeToRoot(t *testing.T) { t.Cleanup(cancel) test, root := newConnectCCBTest(t) headers := test.makeHeaders(root, []uint64{1, 2, 3}) - test.testConnect(ctx, headers, headers[len(headers)-1], append([]*types.Header{root}, headers...)) + test.testConnect(ctx, headers, headers[len(headers)-1], append([]*types.Header{root}, headers...), headers) } // connect any subset of 0-1-2-3 to 0-1-2-3 @@ -174,7 +176,9 @@ func TestCCBConnectOverlapsFull(t *testing.T) { t.Cleanup(cancel) test, root := newConnectCCBTest(t) headers := test.makeHeaders(root, []uint64{1, 2, 3}) - require.Nil(t, test.builder.Connect(ctx, headers)) + newConnectedHeaders, err := test.builder.Connect(ctx, headers) + require.NoError(t, err) + require.Equal(t, headers, newConnectedHeaders) expectedTip := headers[len(headers)-1] expectedHeaders := append([]*types.Header{root}, headers...) @@ -182,7 +186,7 @@ func TestCCBConnectOverlapsFull(t *testing.T) { for subsetLen := 1; subsetLen <= len(headers); subsetLen++ { for i := 0; i+subsetLen-1 < len(expectedHeaders); i++ { headers := expectedHeaders[i : i+subsetLen] - test.testConnect(ctx, headers, expectedTip, expectedHeaders) + test.testConnect(ctx, headers, expectedTip, expectedHeaders, nil) } } } @@ -193,7 +197,7 @@ func TestCCBConnectOverlapPartialOne(t *testing.T) { t.Cleanup(cancel) test, root := newConnectCCBTest(t) newTip := test.makeHeader(root, 1) - test.testConnect(ctx, []*types.Header{root, newTip}, newTip, []*types.Header{root, newTip}) + test.testConnect(ctx, []*types.Header{root, newTip}, newTip, []*types.Header{root, newTip}, []*types.Header{newTip}) } // connect 2-3-4-5 to 0-1-2-3 @@ -202,12 +206,15 @@ func TestCCBConnectOverlapPartialSome(t *testing.T) { t.Cleanup(cancel) test, root := newConnectCCBTest(t) headers := test.makeHeaders(root, []uint64{1, 2, 3}) - require.Nil(t, test.builder.Connect(ctx, headers)) + newConnectedHeaders, err := test.builder.Connect(ctx, headers) + require.NoError(t, err) + require.Equal(t, headers, newConnectedHeaders) - overlapHeaders := append(headers[1:], test.makeHeaders(headers[len(headers)-1], []uint64{4, 5})...) + headers45 := test.makeHeaders(headers[len(headers)-1], []uint64{4, 5}) + overlapHeaders := append(headers[1:], headers45...) expectedTip := overlapHeaders[len(overlapHeaders)-1] expectedHeaders := append([]*types.Header{root, headers[0]}, overlapHeaders...) - test.testConnect(ctx, overlapHeaders, expectedTip, expectedHeaders) + test.testConnect(ctx, overlapHeaders, expectedTip, expectedHeaders, headers45) } // connect 2 to 0-1 at 0, then connect 10 to 0-1 @@ -217,13 +224,15 @@ func TestCCBConnectAltMainBecomesFork(t *testing.T) { test, root := newConnectCCBTest(t) header1 := test.makeHeader(root, 1) header2 := test.makeHeader(root, 2) - require.Nil(t, test.builder.Connect(ctx, []*types.Header{header1})) + newConnectedHeaders, err := test.builder.Connect(ctx, []*types.Header{header1}) + require.NoError(t, err) + require.Equal(t, []*types.Header{header1}, newConnectedHeaders) // the tip changes to header2 - test.testConnect(ctx, []*types.Header{header2}, header2, []*types.Header{root, header2}) + test.testConnect(ctx, []*types.Header{header2}, header2, []*types.Header{root, header2}, []*types.Header{header2}) header10 := test.makeHeader(header1, 10) - test.testConnect(ctx, []*types.Header{header10}, header10, []*types.Header{root, header1, header10}) + test.testConnect(ctx, []*types.Header{header10}, header10, []*types.Header{root, header1, header10}, []*types.Header{header10}) } // connect 1 to 0-2 at 0, then connect 10 to 0-1 @@ -233,13 +242,15 @@ func TestCCBConnectAltForkBecomesMain(t *testing.T) { test, root := newConnectCCBTest(t) header1 := test.makeHeader(root, 1) header2 := test.makeHeader(root, 2) - require.Nil(t, test.builder.Connect(ctx, []*types.Header{header2})) + newConnectedHeaders, err := test.builder.Connect(ctx, []*types.Header{header2}) + require.NoError(t, err) + require.Equal(t, []*types.Header{header2}, newConnectedHeaders) // the tip stays at header2 - test.testConnect(ctx, []*types.Header{header1}, header2, []*types.Header{root, header2}) + test.testConnect(ctx, []*types.Header{header1}, header2, []*types.Header{root, header2}, []*types.Header{header1}) header10 := test.makeHeader(header1, 10) - test.testConnect(ctx, []*types.Header{header10}, header10, []*types.Header{root, header1, header10}) + test.testConnect(ctx, []*types.Header{header10}, header10, []*types.Header{root, header1, header10}, []*types.Header{header10}) } // connect 10 and 11 to 1, then 20 and 22 to 2 one by one starting from a [0-1, 0-2] tree @@ -253,13 +264,17 @@ func TestCCBConnectAltForksAtLevel2(t *testing.T) { header2 := test.makeHeader(root, 2) header20 := test.makeHeader(header2, 20) header22 := test.makeHeader(header2, 22) - require.Nil(t, test.builder.Connect(ctx, []*types.Header{header1})) - require.Nil(t, test.builder.Connect(ctx, []*types.Header{header2})) - - test.testConnect(ctx, []*types.Header{header10}, header10, []*types.Header{root, header1, header10}) - test.testConnect(ctx, []*types.Header{header11}, header11, []*types.Header{root, header1, header11}) - test.testConnect(ctx, []*types.Header{header20}, header20, []*types.Header{root, header2, header20}) - test.testConnect(ctx, []*types.Header{header22}, header22, []*types.Header{root, header2, header22}) + newConnectedHeaders, err := test.builder.Connect(ctx, []*types.Header{header1}) + require.NoError(t, err) + require.Equal(t, []*types.Header{header1}, newConnectedHeaders) + newConnectedHeaders, err = test.builder.Connect(ctx, []*types.Header{header2}) + require.NoError(t, err) + require.Equal(t, []*types.Header{header2}, newConnectedHeaders) + + test.testConnect(ctx, []*types.Header{header10}, header10, []*types.Header{root, header1, header10}, []*types.Header{header10}) + test.testConnect(ctx, []*types.Header{header11}, header11, []*types.Header{root, header1, header11}, []*types.Header{header11}) + test.testConnect(ctx, []*types.Header{header20}, header20, []*types.Header{root, header2, header20}, []*types.Header{header20}) + test.testConnect(ctx, []*types.Header{header22}, header22, []*types.Header{root, header2, header22}, []*types.Header{header22}) } // connect 11 and 10 to 1, then 22 and 20 to 2 one by one starting from a [0-1, 0-2] tree @@ -276,14 +291,18 @@ func TestCCBConnectAltForksAtLevel2Reverse(t *testing.T) { header22 := test.makeHeader(header2, 22) header100 := test.makeHeader(header10, 100) header200 := test.makeHeader(header20, 200) - require.Nil(t, test.builder.Connect(ctx, []*types.Header{header1})) - require.Nil(t, test.builder.Connect(ctx, []*types.Header{header2})) - - test.testConnect(ctx, []*types.Header{header11}, header11, []*types.Header{root, header1, header11}) - test.testConnect(ctx, []*types.Header{header10}, header11, []*types.Header{root, header1, header11}) - test.testConnect(ctx, []*types.Header{header22}, header22, []*types.Header{root, header2, header22}) - test.testConnect(ctx, []*types.Header{header20}, header22, []*types.Header{root, header2, header22}) - - test.testConnect(ctx, []*types.Header{header100}, header100, []*types.Header{root, header1, header10, header100}) - test.testConnect(ctx, []*types.Header{header200}, header200, []*types.Header{root, header2, header20, header200}) + newConnectedHeaders, err := test.builder.Connect(ctx, []*types.Header{header1}) + require.NoError(t, err) + require.Equal(t, []*types.Header{header1}, newConnectedHeaders) + newConnectedHeaders, err = test.builder.Connect(ctx, []*types.Header{header2}) + require.NoError(t, err) + require.Equal(t, []*types.Header{header2}, newConnectedHeaders) + + test.testConnect(ctx, []*types.Header{header11}, header11, []*types.Header{root, header1, header11}, []*types.Header{header11}) + test.testConnect(ctx, []*types.Header{header10}, header11, []*types.Header{root, header1, header11}, []*types.Header{header10}) + test.testConnect(ctx, []*types.Header{header22}, header22, []*types.Header{root, header2, header22}, []*types.Header{header22}) + test.testConnect(ctx, []*types.Header{header20}, header22, []*types.Header{root, header2, header22}, []*types.Header{header20}) + + test.testConnect(ctx, []*types.Header{header100}, header100, []*types.Header{root, header1, header10, header100}, []*types.Header{header100}) + test.testConnect(ctx, []*types.Header{header200}, header200, []*types.Header{root, header2, header20, header200}, []*types.Header{header200}) } diff --git a/polygon/sync/sync.go b/polygon/sync/sync.go index e1cd020a6e1..4e437446741 100644 --- a/polygon/sync/sync.go +++ b/polygon/sync/sync.go @@ -175,9 +175,9 @@ func (s *Sync) applyNewBlockOnTip( return nil } - var newBlocks []*types.Block + var blockChain []*types.Block if ccBuilder.ContainsHash(newBlockHeader.ParentHash) { - newBlocks = []*types.Block{event.NewBlock} + blockChain = []*types.Block{event.NewBlock} } else { blocks, err := s.p2pService.FetchBlocks(ctx, rootNum, newBlockHeaderNum+1, event.PeerId) if err != nil { @@ -195,10 +195,10 @@ func (s *Sync) applyNewBlockOnTip( return err } - newBlocks = blocks.Data + blockChain = blocks.Data } - if err := s.blocksVerifier(newBlocks); err != nil { + if err := s.blocksVerifier(blockChain); err != nil { s.logger.Debug( syncLogPrefix("applyNewBlockOnTip: invalid new block event from peer, penalizing and ignoring"), "err", err, @@ -211,13 +211,14 @@ func (s *Sync) applyNewBlockOnTip( return nil } - newHeaders := make([]*types.Header, len(newBlocks)) - for i, block := range newBlocks { - newHeaders[i] = block.HeaderNoCopy() + headerChain := make([]*types.Header, len(blockChain)) + for i, block := range blockChain { + headerChain[i] = block.HeaderNoCopy() } oldTip := ccBuilder.Tip() - if err := ccBuilder.Connect(ctx, newHeaders); err != nil { + newConnectedHeaders, err := ccBuilder.Connect(ctx, headerChain) + if err != nil { s.logger.Debug( syncLogPrefix("applyNewBlockOnTip: couldn't connect a header to the local chain tip, ignoring"), "err", err, @@ -225,19 +226,22 @@ func (s *Sync) applyNewBlockOnTip( return nil } + if len(newConnectedHeaders) == 0 { + return nil + } - newTip := ccBuilder.Tip() - if newTip != oldTip { - if err := s.store.InsertBlocks(ctx, newBlocks); err != nil { - return err - } + // len(newConnectedHeaders) is always <= len(blockChain) + newConnectedBlocks := blockChain[len(blockChain)-len(newConnectedHeaders):] + if err := s.store.InsertBlocks(ctx, newConnectedBlocks); err != nil { + return err + } - if err := s.commitExecution(ctx, newTip, ccBuilder.Root()); err != nil { - return err - } + newTip := ccBuilder.Tip() + if newTip == oldTip { + return nil } - return nil + return s.commitExecution(ctx, newTip, ccBuilder.Root()) } func (s *Sync) applyNewBlockHashesOnTip(