Skip to content

Commit cb3429c

Browse files
committed
core: remove callback parameter in InsertHeaderChain
The semantics of InsertHeaderChain are now much simpler: it is now an all-or-nothing operation. The new WriteStatus return value allows callers to check for the canonicality of the insertion. This change simplifies use of HeaderChain in package les, where the callback was previously used to post chain events.
1 parent 8699099 commit cb3429c

File tree

4 files changed

+88
-113
lines changed

4 files changed

+88
-113
lines changed

core/blockchain.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2438,7 +2438,8 @@ func (bc *BlockChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i
24382438

24392439
bc.wg.Add(1)
24402440
defer bc.wg.Done()
2441-
return bc.hc.InsertHeaderChain(chain, nil, start)
2441+
_, err := bc.hc.InsertHeaderChain(chain, start)
2442+
return 0, err
24422443
}
24432444

24442445
// CurrentHeader retrieves the current head header of the canonical chain. The

core/headerchain.go

Lines changed: 44 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ func (hc *HeaderChain) GetBlockNumber(hash common.Hash) *uint64 {
130130
}
131131

132132
type headerWriteResult struct {
133+
status WriteStatus
133134
ignored int
134135
imported int
135136
lastHash common.Hash
@@ -145,7 +146,7 @@ type headerWriteResult struct {
145146
// without the real blocks. Hence, writing headers directly should only be done
146147
// in two scenarios: pure-header mode of operation (light clients), or properly
147148
// separated header/block phases (non-archive clients).
148-
func (hc *HeaderChain) writeHeaders(headers []*types.Header, postWriteFn PostWriteCallback) (result *headerWriteResult, err error) {
149+
func (hc *HeaderChain) writeHeaders(headers []*types.Header) (result *headerWriteResult, err error) {
149150
if len(headers) == 0 {
150151
return &headerWriteResult{}, nil
151152
}
@@ -154,39 +155,25 @@ func (hc *HeaderChain) writeHeaders(headers []*types.Header, postWriteFn PostWri
154155
return &headerWriteResult{}, consensus.ErrUnknownAncestor
155156
}
156157
var (
157-
lastHeader *types.Header // Last successfully imported header
158-
lastNumber uint64 // Last successfully imported number
159-
lastHash common.Hash
160-
externTd *big.Int // TD of successfully imported chain
158+
lastNumber = headers[0].Number.Uint64() - 1 // Last successfully imported number
159+
lastHash = headers[0].ParentHash // Last imported header hash
160+
newTD = new(big.Int).Set(ptd) // Total difficulty of inserted chain
161+
162+
lastHeader *types.Header
161163
inserted []numberHash // Ephemeral lookup of number/hash for the chain
162164
firstInserted = -1 // Index of the first non-ignored header
163165
)
164-
lastHash, lastNumber = headers[0].ParentHash, headers[0].Number.Uint64()-1 // Already validated above
166+
165167
batch := hc.chainDb.NewBatch()
166168
for i, header := range headers {
167-
// Short circuit insertion if shutting down
168-
if hc.procInterrupt() {
169-
log.Debug("Premature abort during headers import")
170-
// if we haven't done anything yet, we can return
171-
if i == 0 {
172-
return &headerWriteResult{}, errors.New("aborted")
173-
}
174-
// We only 'break' here - since we want to try and keep the
175-
// db consistent
176-
break
177-
}
178169
hash, number := header.Hash(), header.Number.Uint64()
179-
if header.ParentHash != lastHash {
180-
log.Warn("Non-contiguous header insertion", "parent", header.ParentHash, "expected", lastHash, "number", number)
181-
break
182-
}
183-
externTd = new(big.Int).Add(header.Difficulty, ptd)
170+
newTD.Add(newTD, header.Difficulty)
184171

185172
// If the header is already known, skip it, otherwise store
186173
if !hc.HasHeader(hash, number) {
187-
// Irrelevant of the canonical status, write the td and header to the database
188-
rawdb.WriteTd(batch, hash, number, externTd)
189-
hc.tdCache.Add(hash, new(big.Int).Set(externTd))
174+
// Irrelevant of the canonical status, write the TD and header to the database.
175+
rawdb.WriteTd(batch, hash, number, newTD)
176+
hc.tdCache.Add(hash, new(big.Int).Set(newTD))
190177

191178
rawdb.WriteHeader(batch, header)
192179
inserted = append(inserted, numberHash{number, hash})
@@ -196,22 +183,30 @@ func (hc *HeaderChain) writeHeaders(headers []*types.Header, postWriteFn PostWri
196183
firstInserted = i
197184
}
198185
}
199-
lastHeader, lastHash, lastNumber, ptd = header, hash, number, externTd
186+
lastHeader, lastHash, lastNumber = header, hash, number
187+
}
188+
189+
// Skip the slow disk write of all headers if interrupted.
190+
if hc.procInterrupt() {
191+
log.Debug("Premature abort during headers import")
192+
return &headerWriteResult{}, errors.New("aborted")
200193
}
194+
// Commit to disk!
201195
if err := batch.Write(); err != nil {
202196
log.Crit("Failed to write headers", "error", err)
203197
}
204198
batch.Reset()
199+
205200
var (
206201
head = hc.CurrentHeader().Number.Uint64()
207-
localTd = hc.GetTd(hc.currentHeaderHash, head)
202+
localTD = hc.GetTd(hc.currentHeaderHash, head)
208203
status = SideStatTy
209204
)
210205
// If the total difficulty is higher than our known, add it to the canonical chain
211206
// Second clause in the if statement reduces the vulnerability to selfish mining.
212207
// Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf
213-
reorg := externTd.Cmp(localTd) > 0
214-
if !reorg && externTd.Cmp(localTd) == 0 {
208+
reorg := newTD.Cmp(localTD) > 0
209+
if !reorg && newTD.Cmp(localTD) == 0 {
215210
if lastNumber < head {
216211
reorg = true
217212
} else if lastNumber == head {
@@ -275,31 +270,23 @@ func (hc *HeaderChain) writeHeaders(headers []*types.Header, postWriteFn PostWri
275270
hc.currentHeader.Store(types.CopyHeader(lastHeader))
276271
headHeaderGauge.Update(lastHeader.Number.Int64())
277272

273+
// Chain status is canonical since this insert was a reorg.
274+
// Note that all inserts which have higher TD than existing are 'reorg'.
278275
status = CanonStatTy
279276
}
280-
// Execute any post-write callback function
281-
// - unless we're exiting
282-
// - and unless we ignored everything
283-
if postWriteFn != nil && !hc.procInterrupt() && len(inserted) > 0 {
284-
// The postwrite function is called only for the last imported header.
285-
// It is only used for lightchain event aggregation.
286-
postWriteFn(lastHeader, status)
277+
278+
if len(inserted) == 0 {
279+
status = NonStatTy
287280
}
288281
return &headerWriteResult{
282+
status: status,
289283
ignored: len(headers) - len(inserted),
290284
imported: len(inserted),
291285
lastHash: lastHash,
292286
lastHeader: lastHeader,
293287
}, nil
294288
}
295289

296-
// PostWriteCallback is a callback function for inserting headers,
297-
// which is called once, with the last successfully imported header in the batch.
298-
// The reason for having it is:
299-
// In light-chain mode, status should be processed and light chain events sent,
300-
// whereas in a non-light mode this is not necessary since chain events are sent after inserting blocks.
301-
type PostWriteCallback func(header *types.Header, status WriteStatus)
302-
303290
func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int) (int, error) {
304291
// Do a sanity check that the provided chain is actually ordered and linked
305292
for i := 1; i < len(chain); i++ {
@@ -351,14 +338,22 @@ func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int)
351338
return 0, nil
352339
}
353340

354-
// InsertHeaderChain inserts the given headers, and returns the
355-
// index at which something went wrong, and the error (if any)
356-
// The caller should hold applicable mutexes
357-
func (hc *HeaderChain) InsertHeaderChain(chain []*types.Header, postWriteFn PostWriteCallback, start time.Time) (int, error) {
341+
// InsertHeaderChain inserts the given headers.
342+
//
343+
// The validity of the headers is NOT CHECKED by this method, i.e. they need to be
344+
// validated by ValidateHeaderChain before calling InsertHeaderChain.
345+
//
346+
// This insert is all-or-nothing. If this returns an error, no headers were written,
347+
// otherwise they were all processed successfully.
348+
//
349+
// The returned 'write status' says if the inserted headers are part of the canonical chain
350+
// or a side chain.
351+
func (hc *HeaderChain) InsertHeaderChain(chain []*types.Header, start time.Time) (WriteStatus, error) {
358352
if hc.procInterrupt() {
359353
return 0, errors.New("aborted")
360354
}
361-
res, err := hc.writeHeaders(chain, postWriteFn)
355+
res, err := hc.writeHeaders(chain)
356+
362357
// Report some public statistics so the user has a clue what's going on
363358
context := []interface{}{
364359
"count", res.imported,
@@ -377,7 +372,7 @@ func (hc *HeaderChain) InsertHeaderChain(chain []*types.Header, postWriteFn Post
377372
context = append(context, []interface{}{"ignored", res.ignored}...)
378373
}
379374
log.Info("Imported new block headers", context...)
380-
return 0, err
375+
return res.status, err
381376
}
382377

383378
// GetBlockHashesFromHash retrieves a number of block hashes starting at a given

core/headerchain_test.go

Lines changed: 24 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -50,37 +50,23 @@ func verifyUnbrokenCanonchain(hc *HeaderChain) error {
5050
return nil
5151
}
5252

53-
func testInsert(t *testing.T, hc *HeaderChain, chain []*types.Header, expInsert, expCanon, expSide int) error {
53+
func testInsert(t *testing.T, hc *HeaderChain, chain []*types.Header, wantStatus WriteStatus, wantErr error) {
5454
t.Helper()
55-
gotInsert, gotCanon, gotSide := 0, 0, 0
56-
57-
_, err := hc.InsertHeaderChain(chain, func(header *types.Header, status WriteStatus) {
58-
gotInsert++
59-
switch status {
60-
case CanonStatTy:
61-
gotCanon++
62-
default:
63-
gotSide++
64-
}
65-
}, time.Now())
6655

67-
if gotInsert != expInsert {
68-
t.Errorf("wrong number of callback invocations, got %d, exp %d", gotInsert, expInsert)
69-
}
70-
if gotCanon != expCanon {
71-
t.Errorf("wrong number of canon headers, got %d, exp %d", gotCanon, expCanon)
72-
}
73-
if gotSide != expSide {
74-
t.Errorf("wrong number of side headers, got %d, exp %d", gotSide, expSide)
56+
status, err := hc.InsertHeaderChain(chain, time.Now())
57+
if status != wantStatus {
58+
t.Errorf("wrong write status from InsertHeaderChain: got %v, want %v", status, wantStatus)
7559
}
7660
// Always verify that the header chain is unbroken
7761
if err := verifyUnbrokenCanonchain(hc); err != nil {
7862
t.Fatal(err)
79-
return err
8063
}
81-
return err
64+
if !errors.Is(err, wantErr) {
65+
t.Fatalf("unexpected error from InsertHeaderChain: %v", err)
66+
}
8267
}
8368

69+
// This test checks status reporting of InsertHeaderChain.
8470
func TestHeaderInsertion(t *testing.T) {
8571
var (
8672
db = rawdb.NewMemoryDatabase()
@@ -99,42 +85,31 @@ func TestHeaderInsertion(t *testing.T) {
9985

10086
// Inserting 64 headers on an empty chain, expecting
10187
// 1 callbacks, 1 canon-status, 0 sidestatus,
102-
if err := testInsert(t, hc, chainA[:64], 1, 1, 0); err != nil {
103-
t.Fatal(err)
104-
}
88+
testInsert(t, hc, chainA[:64], CanonStatTy, nil)
10589

10690
// Inserting 64 identical headers, expecting
10791
// 0 callbacks, 0 canon-status, 0 sidestatus,
108-
if err := testInsert(t, hc, chainA[:64], 0, 0, 0); err != nil {
109-
t.Fatal(err)
110-
}
92+
testInsert(t, hc, chainA[:64], NonStatTy, nil)
93+
11194
// Inserting the same some old, some new headers
11295
// 1 callbacks, 1 canon, 0 side
113-
if err := testInsert(t, hc, chainA[32:96], 1, 1, 0); err != nil {
114-
t.Fatal(err)
115-
}
96+
testInsert(t, hc, chainA[32:96], CanonStatTy, nil)
97+
11698
// Inserting side blocks, but not overtaking the canon chain
117-
if err := testInsert(t, hc, chainB[0:32], 1, 0, 1); err != nil {
118-
t.Fatal(err)
119-
}
99+
testInsert(t, hc, chainB[0:32], SideStatTy, nil)
100+
120101
// Inserting more side blocks, but we don't have the parent
121-
if err := testInsert(t, hc, chainB[34:36], 0, 0, 0); !errors.Is(err, consensus.ErrUnknownAncestor) {
122-
t.Fatal(fmt.Errorf("Expected %v, got %v", consensus.ErrUnknownAncestor, err))
123-
}
102+
testInsert(t, hc, chainB[34:36], NonStatTy, consensus.ErrUnknownAncestor)
103+
124104
// Inserting more sideblocks, overtaking the canon chain
125-
if err := testInsert(t, hc, chainB[32:97], 1, 1, 0); err != nil {
126-
t.Fatal(err)
127-
}
105+
testInsert(t, hc, chainB[32:97], CanonStatTy, nil)
106+
128107
// Inserting more A-headers, taking back the canonicality
129-
if err := testInsert(t, hc, chainA[90:100], 1, 1, 0); err != nil {
130-
t.Fatal(err)
131-
}
108+
testInsert(t, hc, chainA[90:100], CanonStatTy, nil)
109+
132110
// And B becomes canon again
133-
if err := testInsert(t, hc, chainB[97:107], 1, 1, 0); err != nil {
134-
t.Fatal(err)
135-
}
111+
testInsert(t, hc, chainB[97:107], CanonStatTy, nil)
112+
136113
// And B becomes even longer
137-
if err := testInsert(t, hc, chainB[107:128], 1, 1, 0); err != nil {
138-
t.Fatal(err)
139-
}
114+
testInsert(t, hc, chainB[107:128], CanonStatTy, nil)
140115
}

light/lightchain.go

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -396,22 +396,26 @@ func (lc *LightChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i
396396
lc.wg.Add(1)
397397
defer lc.wg.Done()
398398

399-
var events []interface{}
400-
postWriteCallback := func(header *types.Header, status core.WriteStatus) {
401-
hash := header.Hash()
402-
switch status {
403-
case core.CanonStatTy:
404-
log.Debug("Inserted new header", "number", header.Number, "hash", hash)
405-
events = append(events, core.ChainEvent{Block: types.NewBlockWithHeader(header), Hash: hash})
406-
407-
case core.SideStatTy:
408-
log.Debug("Inserted forked header", "number", header.Number, "hash", hash)
409-
events = append(events, core.ChainSideEvent{Block: types.NewBlockWithHeader(header)})
410-
}
399+
status, err := lc.hc.InsertHeaderChain(chain, start)
400+
if err != nil || len(chain) == 0 {
401+
return 0, err
402+
}
403+
404+
// Create chain event for the new head block of this insertion.
405+
var (
406+
events = make([]interface{}, 0, 1)
407+
lastHeader = chain[len(chain)-1]
408+
block = types.NewBlockWithHeader(lastHeader)
409+
)
410+
switch status {
411+
case core.CanonStatTy:
412+
events = append(events, core.ChainEvent{Block: block, Hash: block.Hash()})
413+
case core.SideStatTy:
414+
events = append(events, core.ChainSideEvent{Block: block})
411415
}
412-
i, err := lc.hc.InsertHeaderChain(chain, postWriteCallback, start)
413416
lc.postChainEvents(events)
414-
return i, err
417+
418+
return 0, err
415419
}
416420

417421
// CurrentHeader retrieves the current head header of the canonical chain. The

0 commit comments

Comments
 (0)