Skip to content
This repository has been archived by the owner on May 21, 2024. It is now read-only.

Commit

Permalink
les: fix checkpoint sync (ethereum#20120)
Browse files Browse the repository at this point in the history
  • Loading branch information
rjl493456442 authored and elizabethengelman committed Dec 20, 2019
1 parent 439a1ed commit 0f030d5
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 16 deletions.
7 changes: 2 additions & 5 deletions les/checkpointoracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 6 additions & 4 deletions les/client_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
15 changes: 9 additions & 6 deletions les/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
101 changes: 100 additions & 1 deletion les/sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 := &params.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")
}
}

0 comments on commit 0f030d5

Please sign in to comment.