-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
sync.go
186 lines (175 loc) · 6.93 KB
/
sync.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package les
import (
"context"
"errors"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/log"
)
var errInvalidCheckpoint = errors.New("invalid advertised checkpoint")
const (
// lightSync starts syncing from the current highest block.
// If the chain is empty, syncing the entire header chain.
lightSync = iota
// legacyCheckpointSync starts syncing from a hardcoded checkpoint.
legacyCheckpointSync
// checkpointSync starts syncing from a checkpoint signed by trusted
// signer or hardcoded checkpoint for compatibility.
checkpointSync
)
// validateCheckpoint verifies the advertised checkpoint by peer is valid or not.
//
// Each network has several hard-coded checkpoint signer addresses. Only the
// checkpoint issued by the specified signer is considered valid.
//
// In addition to the checkpoint registered in the registrar contract, there are
// several legacy hardcoded checkpoints in our codebase. These checkpoints are
// also considered as valid.
func (h *clientHandler) validateCheckpoint(peer *peer) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
// Fetch the block header corresponding to the checkpoint registration.
cp := peer.checkpoint
header, err := light.GetUntrustedHeaderByNumber(ctx, h.backend.odr, peer.checkpointNumber, peer.id)
if err != nil {
return err
}
// Fetch block logs associated with the block header.
logs, err := light.GetUntrustedBlockLogs(ctx, h.backend.odr, header)
if err != nil {
return err
}
events := h.backend.oracle.contract.LookupCheckpointEvents(logs, cp.SectionIndex, cp.Hash())
if len(events) == 0 {
return errInvalidCheckpoint
}
var (
index = events[0].Index
hash = events[0].CheckpointHash
signatures [][]byte
)
for _, event := range events {
signatures = append(signatures, append(event.R[:], append(event.S[:], event.V)...))
}
valid, signers := h.backend.oracle.verifySigners(index, hash, signatures)
if !valid {
return errInvalidCheckpoint
}
log.Warn("Verified advertised checkpoint", "peer", peer.id, "signers", len(signers))
return nil
}
// synchronise tries to sync up our local chain with a remote peer.
func (h *clientHandler) synchronise(peer *peer) {
// Short circuit if the peer is nil.
if peer == nil {
return
}
// Make sure the peer's TD is higher than our own.
latest := h.backend.blockchain.CurrentHeader()
currentTd := rawdb.ReadTd(h.backend.chainDb, latest.Hash(), latest.Number.Uint64())
if currentTd != nil && peer.headBlockInfo().Td.Cmp(currentTd) < 0 {
return
}
// Recap the checkpoint.
//
// The light client may be connected to several different versions of the server.
// (1) Old version server which can not provide stable checkpoint in the handshake packet.
// => Use hardcoded checkpoint or empty checkpoint
// (2) New version server but simple checkpoint syncing is not enabled(e.g. mainnet, new testnet or private network)
// => Use hardcoded checkpoint or empty checkpoint
// (3) New version server but the provided stable checkpoint is even lower than the hardcoded one.
// => Use hardcoded checkpoint
// (4) New version server with valid and higher stable checkpoint
// => Use provided checkpoint
var checkpoint = &peer.checkpoint
var hardcoded bool
if h.checkpoint != nil && h.checkpoint.SectionIndex >= peer.checkpoint.SectionIndex {
checkpoint = h.checkpoint // Use the hardcoded one.
hardcoded = true
}
// Determine whether we should run checkpoint syncing or normal light syncing.
//
// Here has four situations that we will disable the checkpoint syncing:
//
// 1. The checkpoint is empty
// 2. The latest head block of the local chain is above the checkpoint.
// 3. The checkpoint is hardcoded(recap with local hardcoded checkpoint)
// 4. For some networks the checkpoint syncing is not activated.
mode := checkpointSync
switch {
case checkpoint.Empty():
mode = lightSync
log.Debug("Disable checkpoint syncing", "reason", "empty checkpoint")
case latest.Number.Uint64() >= (checkpoint.SectionIndex+1)*h.backend.iConfig.ChtSize-1:
mode = lightSync
log.Debug("Disable checkpoint syncing", "reason", "local chain beyond the checkpoint")
case hardcoded:
mode = legacyCheckpointSync
log.Debug("Disable checkpoint syncing", "reason", "checkpoint is hardcoded")
case h.backend.oracle == nil || !h.backend.oracle.isRunning():
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.syncDone != nil {
h.syncDone()
}
}()
start := time.Now()
if mode == checkpointSync || mode == legacyCheckpointSync {
// Validate the advertised checkpoint
if mode == checkpointSync {
if err := h.validateCheckpoint(peer); err != nil {
log.Debug("Failed to validate checkpoint", "reason", err)
h.removePeer(peer.id)
return
}
h.backend.blockchain.AddTrustedCheckpoint(checkpoint)
}
log.Debug("Checkpoint syncing start", "peer", peer.id, "checkpoint", checkpoint.SectionIndex)
// Fetch the start point block header.
//
// For the ethash consensus engine, the start header is the block header
// of the checkpoint.
//
// For the clique consensus engine, the start header is the block header
// of the latest epoch covered by checkpoint.
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
if !checkpoint.Empty() && !h.backend.blockchain.SyncCheckpoint(ctx, checkpoint) {
log.Debug("Sync checkpoint failed")
h.removePeer(peer.id)
return
}
}
// Fetch the remaining block headers based on the current chain header.
if err := h.downloader.Synchronise(peer.id, peer.Head(), peer.Td(), downloader.LightSync); err != nil {
log.Debug("Synchronise failed", "reason", err)
return
}
log.Debug("Synchronise finished", "elapsed", common.PrettyDuration(time.Since(start)))
}