Skip to content

Commit f55f48e

Browse files
headerfs: allow rollback multiple block headers
1 parent 5850f2a commit f55f48e

File tree

3 files changed

+315
-20
lines changed

3 files changed

+315
-20
lines changed

headerfs/store.go

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,25 @@ type BlockHeaderStore interface {
7070
// single atomic transaction.
7171
WriteHeaders(...BlockHeader) error
7272

73+
// RollbackBlockHeaders rolls back a specified number of headers from
74+
// the tip of the chain. It removes the most recent 'numHeaders' from
75+
// the block headers file and updates the corresponding indices. This
76+
// method is used during blockchain reorganizations to remove headers
77+
// that are no longer part of the main chain. The function will return
78+
// an error if the rollback would reach or go before the genesis block
79+
// (height 0). The information about the new header tip after truncation
80+
// is returned.
81+
RollbackBlockHeaders(numHeaders uint32) (*BlockStamp, error)
82+
7383
// RollbackLastBlock rolls back the BlockHeaderStore by a _single_
7484
// header. This method is meant to be used in the case of re-org which
7585
// disconnects the latest block header from the end of the main chain.
7686
// The information about the new header tip after truncation is
7787
// returned.
88+
//
89+
// NOTE: This function is maintained for backward compatibility since it
90+
// is a publicly exposed function. It now internally utilizes
91+
// RollbackBlockHeaders API.
7892
RollbackLastBlock() (*BlockStamp, error)
7993
}
8094

@@ -393,55 +407,81 @@ func (h *blockHeaderStore) HeightFromHash(hash *chainhash.Hash) (uint32, error)
393407
return h.heightFromHash(hash)
394408
}
395409

396-
// RollbackLastBlock rollsback both the index, and on-disk header file by a
397-
// _single_ header. This method is meant to be used in the case of re-org which
398-
// disconnects the latest block header from the end of the main chain. The
399-
// information about the new header tip after truncation is returned.
410+
// RollbackBlockHeaders removes the specified number of block headers from the
411+
// end of the chain. It returns a BlockStamp representing the new chain tip. If
412+
// numHeaders is 0, it returns an empty BlockStamp without performing any
413+
// operations.
400414
//
401-
// NOTE: Part of the BlockHeaderStore interface.
402-
func (h *blockHeaderStore) RollbackLastBlock() (*BlockStamp, error) {
403-
// Lock store for write.
415+
// The function ensures rollback doesn't remove or go beyond the genesis block
416+
// (height 0). It determines the current chain tip height, reads the header
417+
// range to be removed along with the new tip header, truncates the headers file
418+
// to remove the specified number of headers, and updates the header indices to
419+
// reflect the new chain tip.
420+
func (h *blockHeaderStore) RollbackBlockHeaders(n uint32) (*BlockStamp, error) {
421+
if n == 0 {
422+
return &BlockStamp{}, nil
423+
}
424+
425+
// Lock store for rollback.
404426
h.mtx.Lock()
405427
defer h.mtx.Unlock()
406428

407429
// First, we'll obtain the latest height that the index knows of.
408-
lastBlock, chainTipHeight, err := h.chainTip()
430+
_, chainTipHeight, err := h.chainTip()
409431
if err != nil {
410432
return nil, err
411433
}
412434

435+
// Ensure the rollback doesn't remove or go beyond the genesis block.
436+
if n > chainTipHeight {
437+
return nil, fmt.Errorf("cannot roll back %d headers when "+
438+
"chain height is %d", n, chainTipHeight)
439+
}
440+
413441
// With this height obtained, we'll use it to read the previous header
414442
// from disk, so we can populate our return value which requires the
415443
// prev header hash.
416-
prevHeader, err := h.readHeader(chainTipHeight - 1)
444+
headers, err := h.readHeaderRange(chainTipHeight-n, chainTipHeight)
417445
if err != nil {
418-
return nil, err
446+
return nil, fmt.Errorf("failed to read headers range: %v", err)
419447
}
448+
prevHeader := headers[0]
420449
prevHeaderHash := prevHeader.BlockHash()
421450

422-
// Compute the block headers to truncate.
423-
headersToTruncate := []*chainhash.Hash{lastBlock}
451+
// Transform to blockhashes for downstream operations, starting at
452+
// headers + 1 skipping the previous header.
453+
headersToTruncate := make([]*chainhash.Hash, len(headers)-1)
454+
for i, header := range headers[1:] {
455+
blkHash := header.BlockHash()
456+
headersToTruncate[i] = &blkHash
457+
}
424458

425-
// Now that we have the information we need to return from this
426-
// function, we can now truncate the header file, and then use the hash
427-
// of the prevHeader to set the proper index chain tip.
428-
if err := h.truncateHeaders(1, h.indexType); err != nil {
459+
if err := h.truncateHeaders(n, h.indexType); err != nil {
429460
return nil, err
430461
}
431462

432-
if err := h.truncateIndices(
433-
&prevHeaderHash, headersToTruncate, true,
434-
); err != nil {
463+
err = h.truncateIndices(&prevHeaderHash, headersToTruncate, true)
464+
if err != nil {
435465
return nil, err
436466
}
437467

438468
return &BlockStamp{
439-
Height: int32(chainTipHeight) - 1,
469+
Height: int32(chainTipHeight - n),
440470
Hash: prevHeaderHash,
441471
Timestamp: prevHeader.Timestamp,
442472
}, nil
443473
}
444474

475+
// RollbackLastBlock rollsback both the index, and on-disk header file by a
476+
// _single_ header. This method is meant to be used in the case of re-org which
477+
// disconnects the latest block header from the end of the main chain. The
478+
// information about the new header tip after truncation is returned.
479+
//
480+
// NOTE: Part of the BlockHeaderStore interface.
481+
func (h *blockHeaderStore) RollbackLastBlock() (*BlockStamp, error) {
482+
return h.RollbackBlockHeaders(1)
483+
}
484+
445485
// BlockHeader is a Bitcoin block header that also has its height included.
446486
type BlockHeader struct {
447487
*wire.BlockHeader

headerfs/store_mock.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,17 @@ func (m *MockBlockHeaderStore) WriteHeaders(hdrs ...BlockHeader) error {
8080
return args.Error(0)
8181
}
8282

83+
// RollbackBlockHeaders rolls back block headers in the mock block header store.
84+
func (m *MockBlockHeaderStore) RollbackBlockHeaders(
85+
numHeaders uint32) (*BlockStamp, error) {
86+
87+
args := m.Called(numHeaders)
88+
if args.Get(0) == nil {
89+
return nil, args.Error(1)
90+
}
91+
return args.Get(0).(*BlockStamp), args.Error(1)
92+
}
93+
8394
// RollbackLastBlock rolls back the last block in the mock block header store.
8495
func (m *MockBlockHeaderStore) RollbackLastBlock() (*BlockStamp, error) {
8596
args := m.Called()

0 commit comments

Comments
 (0)