Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

les: fix checkpoint sync #20120

Merged
merged 1 commit into from
Sep 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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")
}
}