From f686095be4093e931c9358a5cbfa9e83954718f5 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Fri, 8 Apr 2022 21:48:52 +0800 Subject: [PATCH] les: fix panic in ultralight client sync (#24641) --- les/fetcher.go | 13 ++++++++++++- les/fetcher_test.go | 38 ++++++++++++++++++++++++++++++++------ les/ulc_test.go | 5 +++-- 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/les/fetcher.go b/les/fetcher.go index 9936e767439f..1e1e8e70503a 100644 --- a/les/fetcher.go +++ b/les/fetcher.go @@ -446,6 +446,14 @@ func (f *lightFetcher) mainloop() { if ulc { head := f.chain.CurrentHeader() ancestor := rawdb.FindCommonAncestor(f.chaindb, origin, head) + + // Recap the ancestor with genesis header in case the ancestor + // is not found. It can happen the original head is before the + // checkpoint while the synced headers are after it. In this + // case there is no ancestor between them. + if ancestor == nil { + ancestor = f.chain.Genesis().Header() + } var untrusted []common.Hash for head.Number.Cmp(ancestor.Number) > 0 { hash, number := head.Hash(), head.Number.Uint64() @@ -454,6 +462,9 @@ func (f *lightFetcher) mainloop() { } untrusted = append(untrusted, hash) head = f.chain.GetHeader(head.ParentHash, number-1) + if head == nil { + break // all the synced headers will be dropped + } } if len(untrusted) > 0 { for i, j := 0, len(untrusted)-1; i < j; i, j = i+1, j-1 { @@ -519,7 +530,7 @@ func (f *lightFetcher) requestHeaderByHash(peerid enode.ID) func(common.Hash) er } } -// requestResync invokes synchronisation callback to start syncing. +// startSync invokes synchronisation callback to start syncing. func (f *lightFetcher) startSync(id enode.ID) { defer func(header *types.Header) { f.syncDone <- header diff --git a/les/fetcher_test.go b/les/fetcher_test.go index a922ab0f839c..8d402393dfc3 100644 --- a/les/fetcher_test.go +++ b/les/fetcher_test.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/params" ) @@ -152,6 +153,7 @@ func TestTrustedAnnouncementsLes2(t *testing.T) { testTrustedAnnouncement(t, 2) func TestTrustedAnnouncementsLes3(t *testing.T) { testTrustedAnnouncement(t, 3) } func testTrustedAnnouncement(t *testing.T, protocol int) { + //log.Root().SetHandler(log.LvlFilterHandler(log.LvlDebug, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) var ( servers []*testServer teardowns []func() @@ -159,16 +161,28 @@ func testTrustedAnnouncement(t *testing.T, protocol int) { ids []string cpeers []*clientPeer speers []*serverPeer + + config = light.TestServerIndexerConfig + waitIndexers = func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { + for { + cs, _, _ := cIndexer.Sections() + bts, _, _ := btIndexer.Sections() + if cs >= 2 && bts >= 2 { + break + } + time.Sleep(10 * time.Millisecond) + } + } ) - for i := 0; i < 10; i++ { - s, n, teardown := newTestServerPeer(t, 10, protocol) + for i := 0; i < 4; i++ { + s, n, teardown := newTestServerPeer(t, int(2*config.ChtSize+config.ChtConfirms), protocol, waitIndexers) servers = append(servers, s) nodes = append(nodes, n) teardowns = append(teardowns, teardown) // A half of them are trusted servers. - if i < 5 { + if i < 2 { ids = append(ids, n.String()) } } @@ -185,6 +199,18 @@ func testTrustedAnnouncement(t *testing.T, protocol int) { teardowns[i]() } }() + + // Register the assembled checkpoint as hardcoded one. + head := servers[0].chtIndexer.SectionHead(0) + cp := ¶ms.TrustedCheckpoint{ + SectionIndex: 0, + SectionHead: head, + CHTRoot: light.GetChtRoot(servers[0].db, 0, head), + BloomRoot: light.GetBloomTrieRoot(servers[0].db, 0, head), + } + c.handler.checkpoint = cp + c.handler.backend.blockchain.AddTrustedCheckpoint(cp) + // Connect all server instances. for i := 0; i < len(servers); i++ { sp, cp, err := connect(servers[i].handler, nodes[i].ID(), c.handler, protocol, true) @@ -218,9 +244,9 @@ func testTrustedAnnouncement(t *testing.T, protocol int) { } verifyChainHeight(t, c.handler.fetcher, expected) } - check([]uint64{1}, 1, func() { <-newHead }) // Sequential announcements - check([]uint64{4}, 4, func() { <-newHead }) // ULC-style light syncing, rollback untrusted headers - check([]uint64{10}, 10, func() { <-newHead }) // Sync the whole chain. + check([]uint64{1}, 1, func() { <-newHead }) // Sequential announcements + check([]uint64{config.ChtSize + config.ChtConfirms}, config.ChtSize+config.ChtConfirms, func() { <-newHead }) // ULC-style light syncing, rollback untrusted headers + check([]uint64{2*config.ChtSize + config.ChtConfirms}, 2*config.ChtSize+config.ChtConfirms, func() { <-newHead }) // Sync the whole chain. } func TestInvalidAnnouncesLES2(t *testing.T) { testInvalidAnnounces(t, lpv2) } diff --git a/les/ulc_test.go b/les/ulc_test.go index ecef58d9791e..a4df0795b46d 100644 --- a/les/ulc_test.go +++ b/les/ulc_test.go @@ -55,7 +55,7 @@ func testULCAnnounceThreshold(t *testing.T, protocol int) { ids []string ) for i := 0; i < len(testcase.height); i++ { - s, n, teardown := newTestServerPeer(t, 0, protocol) + s, n, teardown := newTestServerPeer(t, 0, protocol, nil) servers = append(servers, s) nodes = append(nodes, n) @@ -132,10 +132,11 @@ func connect(server *serverHandler, serverId enode.ID, client *clientHandler, pr } // newTestServerPeer creates server peer. -func newTestServerPeer(t *testing.T, blocks int, protocol int) (*testServer, *enode.Node, func()) { +func newTestServerPeer(t *testing.T, blocks int, protocol int, indexFn indexerCallback) (*testServer, *enode.Node, func()) { netconfig := testnetConfig{ blocks: blocks, protocol: protocol, + indexFn: indexFn, nopruning: true, } s, _, teardown := newClientServerEnv(t, netconfig)