diff --git a/les/checkpointoracle.go b/les/checkpointoracle.go index 4695fbc16ced..5494e3d6d9d5 100644 --- a/les/checkpointoracle.go +++ b/les/checkpointoracle.go @@ -35,11 +35,8 @@ type checkpointOracle struct { config *params.CheckpointOracleConfig contract *checkpointoracle.CheckpointOracle - // Whether the contract backend is set. - running int32 - - getLocal func(uint64) params.TrustedCheckpoint // Function used to retrieve local checkpoint - syncDoneHook func() // Function used to notify that light syncing has completed. + running int32 // Flag whether the contract backend is set or not + getLocal func(uint64) params.TrustedCheckpoint // Function used to retrieve local checkpoint } // newCheckpointOracle returns a checkpoint registrar handler. diff --git a/les/client_handler.go b/les/client_handler.go index aff05ddbc9d5..7fdb1657194c 100644 --- a/les/client_handler.go +++ b/les/client_handler.go @@ -40,14 +40,16 @@ type clientHandler struct { downloader *downloader.Downloader backend *LightEthereum - closeCh chan struct{} - wg sync.WaitGroup // WaitGroup used to track all connected peers. + closeCh chan struct{} + wg sync.WaitGroup // WaitGroup used to track all connected peers. + syncDone func() // Test hooks when syncing is done. } func newClientHandler(ulcServers []string, ulcFraction int, checkpoint *params.TrustedCheckpoint, backend *LightEthereum) *clientHandler { handler := &clientHandler{ - backend: backend, - closeCh: make(chan struct{}), + checkpoint: checkpoint, + backend: backend, + closeCh: make(chan struct{}), } if ulcServers != nil { ulc, err := newULC(ulcServers, ulcFraction) diff --git a/les/sync.go b/les/sync.go index 693394464c12..1214fefcaf13 100644 --- a/les/sync.go +++ b/les/sync.go @@ -135,21 +135,24 @@ func (h *clientHandler) synchronise(peer *peer) { mode = legacyCheckpointSync log.Debug("Disable checkpoint syncing", "reason", "checkpoint is hardcoded") case h.backend.oracle == nil || !h.backend.oracle.isRunning(): - mode = legacyCheckpointSync + if h.checkpoint == nil { + mode = lightSync // Downgrade to light sync unfortunately. + } else { + checkpoint = h.checkpoint + mode = legacyCheckpointSync + } log.Debug("Disable checkpoint syncing", "reason", "checkpoint syncing is not activated") } // Notify testing framework if syncing has completed(for testing purpose). defer func() { - if h.backend.oracle != nil && h.backend.oracle.syncDoneHook != nil { - h.backend.oracle.syncDoneHook() + if h.syncDone != nil { + h.syncDone() } }() start := time.Now() if mode == checkpointSync || mode == legacyCheckpointSync { // Validate the advertised checkpoint - if mode == legacyCheckpointSync { - checkpoint = h.checkpoint - } else if mode == checkpointSync { + if mode == checkpointSync { if err := h.validateCheckpoint(peer); err != nil { log.Debug("Failed to validate checkpoint", "reason", err) h.removePeer(peer.id) diff --git a/les/sync_test.go b/les/sync_test.go index 63833c1ab434..b02c3582f09d 100644 --- a/les/sync_test.go +++ b/les/sync_test.go @@ -102,7 +102,7 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { } done := make(chan error) - client.handler.backend.oracle.syncDoneHook = func() { + client.handler.syncDone = func() { header := client.handler.backend.blockchain.CurrentHeader() if header.Number.Uint64() == expected { done <- nil @@ -131,3 +131,102 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { t.Error("checkpoint syncing timeout") } } + +func TestMissOracleBackend(t *testing.T) { testMissOracleBackend(t, true) } +func TestMissOracleBackendNoCheckpoint(t *testing.T) { testMissOracleBackend(t, false) } + +func testMissOracleBackend(t *testing.T, hasCheckpoint bool) { + config := light.TestServerIndexerConfig + + waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { + for { + cs, _, _ := cIndexer.Sections() + bts, _, _ := btIndexer.Sections() + if cs >= 1 && bts >= 1 { + break + } + time.Sleep(10 * time.Millisecond) + } + } + // Generate 512+4 blocks (totally 1 CHT sections) + server, client, tearDown := newClientServerEnv(t, int(config.ChtSize+config.ChtConfirms), 3, waitIndexers, nil, 0, false, false) + defer tearDown() + + expected := config.ChtSize + config.ChtConfirms + + s, _, head := server.chtIndexer.Sections() + cp := ¶ms.TrustedCheckpoint{ + SectionIndex: 0, + SectionHead: head, + CHTRoot: light.GetChtRoot(server.db, s-1, head), + BloomRoot: light.GetBloomTrieRoot(server.db, s-1, head), + } + // Register the assembled checkpoint into oracle. + header := server.backend.Blockchain().CurrentHeader() + + data := append([]byte{0x19, 0x00}, append(registrarAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...) + sig, _ := crypto.Sign(crypto.Keccak256(data), signerKey) + sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper + if _, err := server.handler.server.oracle.contract.RegisterCheckpoint(bind.NewKeyedTransactor(signerKey), cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil { + t.Error("register checkpoint failed", err) + } + server.backend.Commit() + + // Wait for the checkpoint registration + for { + _, hash, _, err := server.handler.server.oracle.contract.Contract().GetLatestCheckpoint(nil) + if err != nil || hash == [32]byte{} { + time.Sleep(100 * time.Millisecond) + continue + } + break + } + expected += 1 + + // Explicitly set the oracle as nil. In normal use case it can happen + // that user wants to unlock something which blocks the oracle backend + // initialisation. But at the same time syncing starts. + // + // See https://github.com/ethereum/go-ethereum/issues/20097 for more detail. + // + // In this case, client should run light sync or legacy checkpoint sync + // if hardcoded checkpoint is configured. + client.handler.backend.oracle = nil + + // For some private networks it can happen checkpoint syncing is enabled + // but there is no hardcoded checkpoint configured. + if hasCheckpoint { + client.handler.checkpoint = cp + client.handler.backend.blockchain.AddTrustedCheckpoint(cp) + } + + done := make(chan error) + client.handler.syncDone = func() { + header := client.handler.backend.blockchain.CurrentHeader() + if header.Number.Uint64() == expected { + done <- nil + } else { + done <- fmt.Errorf("blockchain length mismatch, want %d, got %d", expected, header.Number) + } + } + + // Create connected peer pair. + _, err1, _, err2 := newTestPeerPair("peer", 2, server.handler, client.handler) + select { + case <-time.After(time.Millisecond * 100): + case err := <-err1: + t.Fatalf("peer 1 handshake error: %v", err) + case err := <-err2: + t.Fatalf("peer 2 handshake error: %v", err) + } + + select { + case err := <-done: + if err != nil { + t.Error("sync failed", err) + } + return + case <-time.NewTimer(10 * time.Second).C: + t.Error("checkpoint syncing timeout") + } +}