forked from ethereum/go-ethereum
-
Notifications
You must be signed in to change notification settings - Fork 1
/
sync.go
204 lines (190 loc) · 7.41 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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// 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/les/downloader"
"github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
)
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 *serverPeer) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
// Fetch the block header corresponding to the checkpoint registration.
wrapPeer := &peerConnection{handler: h, peer: peer}
header, err := wrapPeer.RetrieveSingleHeaderByNumber(ctx, peer.checkpointNumber)
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, peer.checkpoint.SectionIndex, peer.checkpoint.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 *serverPeer) {
// 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.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 local checkpoint or empty checkpoint
// (2) New version server but simple checkpoint syncing is not enabled
// (e.g. mainnet, new testnet or private network)
// => Use local checkpoint or empty checkpoint
// (3) New version server but the provided stable checkpoint is even lower
// than the local one.
// => Use local checkpoint
// (4) New version server with valid and higher stable checkpoint
// => Use provided checkpoint
var (
local bool
checkpoint = &peer.checkpoint
)
if h.checkpoint != nil && h.checkpoint.SectionIndex >= peer.checkpoint.SectionIndex {
local, checkpoint = true, h.checkpoint
}
// Replace the checkpoint with locally configured one If it's required by
// users. Nil checkpoint means synchronization from the scratch.
if h.backend.config.SyncFromCheckpoint {
local, checkpoint = true, h.backend.config.Checkpoint
if h.backend.config.Checkpoint == nil {
checkpoint = ¶ms.TrustedCheckpoint{}
}
}
// 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 local(replaced with local 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 local:
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.syncEnd != nil {
h.syncEnd(h.backend.blockchain.CurrentHeader())
}
}()
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
}
}
if h.syncStart != nil {
h.syncStart(h.backend.blockchain.CurrentHeader())
}
// 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)))
}