Skip to content

Commit 6c51219

Browse files
authored
Merge pull request #317 from mohamedawnallah/headersImport
chainimport[2/3]: import block and filter headers on startup before falling back to P2P synchronization
2 parents 5d7c11a + a323221 commit 6c51219

24 files changed

+10361
-79
lines changed

blockmanager.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1371,7 +1371,7 @@ func (b *blockManager) resolveConflict(
13711371
for peer, cp := range checkpoints {
13721372
for i, header := range cp {
13731373
height := uint32((i + 1) * wire.CFCheckptInterval)
1374-
err := chainsync.ControlCFHeader(
1374+
err := chainsync.ValidateCFHeader(
13751375
b.cfg.ChainParams, fType, height, header,
13761376
)
13771377
if err == chainsync.ErrCheckpointMismatch {
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
package chainimport
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"math"
7+
8+
"github.com/btcsuite/btcd/blockchain"
9+
"github.com/btcsuite/btcd/chaincfg"
10+
"github.com/btcsuite/btcd/chaincfg/chainhash"
11+
"github.com/lightninglabs/neutrino/headerfs"
12+
)
13+
14+
// blockHeadersImportSourceValidator implements headersValidator for block
15+
// headers.
16+
type blockHeadersImportSourceValidator struct {
17+
// targetChainParams contains the blockchain network parameters for
18+
// validation against the target chain.
19+
targetChainParams chaincfg.Params
20+
21+
// targetBlockHeaderStore is the destination store where validated
22+
// headers will be written later in the import process.
23+
targetBlockHeaderStore headerfs.BlockHeaderStore
24+
25+
// flags defines the behavior flags used during header validation.
26+
flags blockchain.BehaviorFlags
27+
28+
// blockHeadersImportSource provides access to the source headers
29+
// for validation and lookup operations.
30+
blockHeadersImportSource HeaderImportSource
31+
}
32+
33+
// Compile-time assertion to ensure blockHeadersImportSourceValidator implements
34+
// headersValidator interface.
35+
var _ HeadersValidator = (*blockHeadersImportSourceValidator)(nil)
36+
37+
// newBlockHeadersImportSourceValidator creates a new validator for block
38+
// headers import source.
39+
func newBlockHeadersImportSourceValidator(targetChainParams chaincfg.Params,
40+
targetBlockHeaderStore headerfs.BlockHeaderStore,
41+
flags blockchain.BehaviorFlags,
42+
blockHeadersImportSource HeaderImportSource) HeadersValidator {
43+
44+
return &blockHeadersImportSourceValidator{
45+
targetChainParams: targetChainParams,
46+
targetBlockHeaderStore: targetBlockHeaderStore,
47+
flags: flags,
48+
blockHeadersImportSource: blockHeadersImportSource,
49+
}
50+
}
51+
52+
// Validate performs thorough validation of a batch of block headers.
53+
func (v *blockHeadersImportSourceValidator) Validate(ctx context.Context,
54+
it HeaderIterator) error {
55+
56+
var (
57+
start = it.GetStartIndex()
58+
end = it.GetEndIndex()
59+
batchSize = it.GetBatchSize()
60+
count = 0
61+
lastHeader Header
62+
)
63+
64+
for batch, err := range it.BatchIterator(start, end, batchSize) {
65+
if err != nil {
66+
return fmt.Errorf("failed to get next batch for "+
67+
"validation: %w", err)
68+
}
69+
70+
if err := ctxCancelled(ctx); err != nil {
71+
return nil
72+
}
73+
74+
if err = v.ValidateBatch(batch); err != nil {
75+
return fmt.Errorf("batch validation failed at "+
76+
"position %d: %w", count, err)
77+
}
78+
79+
// If this is not the first batch, validate header connection
80+
// points between batches.
81+
if lastHeader != nil && len(batch) > 0 {
82+
err := v.ValidatePair(lastHeader, batch[0])
83+
if err != nil {
84+
return fmt.Errorf("cross-batch validation "+
85+
"failed at position %d: %w", count, err)
86+
}
87+
}
88+
89+
count += len(batch)
90+
if len(batch) > 0 {
91+
lastHeader = batch[len(batch)-1]
92+
}
93+
}
94+
95+
log.Debugf("Successfully validated %d block headers", count)
96+
return nil
97+
}
98+
99+
// ValidateSingle validates a single block header for basic sanity.
100+
func (v *blockHeadersImportSourceValidator) ValidateSingle(h Header) error {
101+
header, err := assertBlockHeader(h)
102+
if err != nil {
103+
return err
104+
}
105+
106+
return blockchain.CheckBlockHeaderSanity(
107+
header.BlockHeader.BlockHeader, v.targetChainParams.PowLimit,
108+
blockchain.NewMedianTime(), v.flags,
109+
)
110+
}
111+
112+
// ValidatePair verifies that two consecutive block headers form a valid chain
113+
// link.
114+
func (v *blockHeadersImportSourceValidator) ValidatePair(prev,
115+
current Header) error {
116+
117+
prevBlk, err := assertBlockHeader(prev)
118+
if err != nil {
119+
return err
120+
}
121+
currentBlk, err := assertBlockHeader(current)
122+
if err != nil {
123+
return err
124+
}
125+
126+
prevBlockHeader := prevBlk.BlockHeader
127+
currBlockHeader := currentBlk.BlockHeader
128+
prevHeight, currHeight := prevBlk.Height, currentBlk.Height
129+
130+
if currHeight != prevHeight+1 {
131+
return fmt.Errorf("height mismatch: previous height=%d, "+
132+
"current height=%d", prevHeight, currHeight)
133+
}
134+
135+
prevHash := prevBlockHeader.BlockHash()
136+
if !currBlockHeader.PrevBlock.IsEqual(&prevHash) {
137+
return fmt.Errorf("header chain broken: current header's "+
138+
"PrevBlock (%v) doesn't match previous header's hash "+
139+
"(%v)", currBlockHeader.PrevBlock, prevHash)
140+
}
141+
142+
parentCtx := &lightHeaderCtx{
143+
height: int32(prevHeight),
144+
bits: prevBlockHeader.Bits,
145+
timestamp: prevBlockHeader.Timestamp.Unix(),
146+
validator: v,
147+
}
148+
149+
tCP := v.targetChainParams
150+
151+
chainCtx := &lightChainCtx{
152+
params: &tCP,
153+
blocksPerRetarget: int32(tCP.TargetTimespan.Seconds() /
154+
tCP.TargetTimePerBlock.Seconds()),
155+
minRetargetTimespan: int64(tCP.TargetTimespan.Seconds() /
156+
float64(tCP.RetargetAdjustmentFactor)),
157+
maxRetargetTimespan: int64(tCP.TargetTimespan.Seconds() *
158+
float64(tCP.RetargetAdjustmentFactor)),
159+
}
160+
161+
if err := blockchain.CheckBlockHeaderContext(
162+
currBlockHeader.BlockHeader, parentCtx, v.flags, chainCtx, true,
163+
); err != nil {
164+
return fmt.Errorf("block header contextual validation "+
165+
"failed: %w", err)
166+
}
167+
168+
if err := v.ValidateSingle(current); err != nil {
169+
return err
170+
}
171+
172+
return nil
173+
}
174+
175+
// ValidateBatch performs validation on a batch of block headers.
176+
func (v *blockHeadersImportSourceValidator) ValidateBatch(
177+
headers []Header) error {
178+
179+
if len(headers) == 1 {
180+
return v.ValidateSingle(headers[0])
181+
}
182+
183+
for i := 1; i < len(headers); i++ {
184+
if err := v.ValidatePair(headers[i-1], headers[i]); err != nil {
185+
return fmt.Errorf("validation failed at batch "+
186+
"position %d: %w", i, err)
187+
}
188+
}
189+
190+
return nil
191+
}
192+
193+
// lightHeaderCtx implements the blockchain.HeaderCtx interface.
194+
type lightHeaderCtx struct {
195+
height int32
196+
bits uint32
197+
timestamp int64
198+
validator *blockHeadersImportSourceValidator
199+
}
200+
201+
// Compile-time assertion to ensure lightHeaderCtx implements
202+
// blockchain.HeaderCtx interface.
203+
var _ blockchain.HeaderCtx = (*lightHeaderCtx)(nil)
204+
205+
// Height returns the height for the underlying header this context was created
206+
// from.
207+
func (l *lightHeaderCtx) Height() int32 {
208+
return l.height
209+
}
210+
211+
// Bits returns the difficulty bits for the underlying header this context was
212+
// created from.
213+
func (l *lightHeaderCtx) Bits() uint32 {
214+
return l.bits
215+
}
216+
217+
// Timestamp returns the timestamp for the underlying header this context was
218+
// created from.
219+
func (l *lightHeaderCtx) Timestamp() int64 {
220+
return l.timestamp
221+
}
222+
223+
// RelativeAncestorCtx returns the ancestor header context that is distance
224+
// blocks before the current header.
225+
func (l *lightHeaderCtx) RelativeAncestorCtx(
226+
distance int32) blockchain.HeaderCtx {
227+
228+
ancestorHeight := uint32(math.Max(0, float64(l.height-distance)))
229+
230+
ancestorIndex := targetHeightToImportSourceIndex(
231+
ancestorHeight, 0,
232+
)
233+
234+
// Lookup the ancestor in the target store.
235+
targetStore := l.validator.targetBlockHeaderStore
236+
ancestor, err := targetStore.FetchHeaderByHeight(ancestorHeight)
237+
if err == nil {
238+
return &lightHeaderCtx{
239+
height: int32(ancestorHeight),
240+
bits: ancestor.Bits,
241+
timestamp: ancestor.Timestamp.Unix(),
242+
}
243+
}
244+
245+
// Fallback to import source if ancestor not found in target store.
246+
importAncestor, err := l.validator.blockHeadersImportSource.GetHeader(
247+
ancestorIndex,
248+
)
249+
if err != nil {
250+
return nil
251+
}
252+
253+
importBlockAncestor, err := assertBlockHeader(importAncestor)
254+
if err != nil {
255+
return nil
256+
}
257+
258+
return &lightHeaderCtx{
259+
height: int32(ancestorHeight),
260+
bits: importBlockAncestor.BlockHeader.Bits,
261+
timestamp: importBlockAncestor.BlockHeader.Timestamp.Unix(),
262+
}
263+
}
264+
265+
// Parent returns the parent header context.
266+
func (l *lightHeaderCtx) Parent() blockchain.HeaderCtx {
267+
return nil
268+
}
269+
270+
// lightChainCtx implements the blockchain.ChainCtx interface.
271+
type lightChainCtx struct {
272+
params *chaincfg.Params
273+
blocksPerRetarget int32
274+
minRetargetTimespan int64
275+
maxRetargetTimespan int64
276+
}
277+
278+
// Compile-time assertion to ensure lightChainCtx implements
279+
// blockchain.ChainCtx interface.
280+
var _ blockchain.ChainCtx = (*lightChainCtx)(nil)
281+
282+
// ChainParams returns the chain parameters for the underlying chain this
283+
// context was created from.
284+
func (l *lightChainCtx) ChainParams() *chaincfg.Params {
285+
return l.params
286+
}
287+
288+
// BlocksPerRetarget returns the number of blocks before retargeting occurs.
289+
func (l *lightChainCtx) BlocksPerRetarget() int32 {
290+
return l.blocksPerRetarget
291+
}
292+
293+
// MinRetargetTimespan returns the minimum amount of time to use in the
294+
// difficulty calculation.
295+
func (l *lightChainCtx) MinRetargetTimespan() int64 {
296+
return l.minRetargetTimespan
297+
}
298+
299+
// MaxRetargetTimespan returns the maximum amount of time to use in the
300+
// difficulty calculation.
301+
func (l *lightChainCtx) MaxRetargetTimespan() int64 {
302+
return l.maxRetargetTimespan
303+
}
304+
305+
// VerifyCheckpoint returns whether the passed height and hash match the
306+
// checkpoint data.
307+
func (l *lightChainCtx) VerifyCheckpoint(height int32,
308+
hash *chainhash.Hash) bool {
309+
310+
return false
311+
}
312+
313+
// FindPreviousCheckpoint returns the most recent checkpoint that we have
314+
// validated.
315+
func (l *lightChainCtx) FindPreviousCheckpoint() (blockchain.HeaderCtx, error) {
316+
return nil, nil
317+
}

0 commit comments

Comments
 (0)