mirror of
https://github.com/btcsuite/btcd.git
synced 2026-08-13 12:32:51 +02:00
blockchain: add ProcessBlockHeader
ProcessBlockHeader performs chain selection and context-free & contextual validation for the given block header. The function allows a header-first downloading of blocks even without checkpoints.
This commit is contained in:
parent
fd16786b1e
commit
c1a46122ca
2 changed files with 214 additions and 0 deletions
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/database"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
// BehaviorFlags is a bitmask defining tweaks to the normal behavior when
|
||||
|
|
@ -242,3 +243,34 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bo
|
|||
|
||||
return isMainChain, false, nil
|
||||
}
|
||||
|
||||
// ProcessBlockHeader is the main workhorse for handling insertion of new block
|
||||
// headers into the block chain using headers-first semantics. It includes
|
||||
// functionality such as rejecting headers that do not connect to an existing
|
||||
// known header, ensuring headers follow all rules and insertion into the block
|
||||
// index.
|
||||
//
|
||||
// Block headers that have already been inserted are ignored, unless they have
|
||||
// subsequently been marked invalid, in which case an appropriate error is
|
||||
// returned.
|
||||
//
|
||||
// It should be noted that this function intentionally does not accept block
|
||||
// headers that do not connect to an existing known header or to headers which
|
||||
// are already known to be a part of an invalid branch. This means headers must
|
||||
// be processed in order.
|
||||
//
|
||||
// The skipCheckpoint boolean allows skipping of the check for if the header is
|
||||
// part of the existing checkpoints.
|
||||
//
|
||||
// The returned boolean indicates whether or not the header was in the main chain
|
||||
// or not.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (b *BlockChain) ProcessBlockHeader(header *wire.BlockHeader,
|
||||
flags BehaviorFlags, skipCheckpoint bool) (bool, error) {
|
||||
|
||||
b.chainLock.Lock()
|
||||
defer b.chainLock.Unlock()
|
||||
|
||||
return b.maybeAcceptBlockHeader(header, flags, skipCheckpoint)
|
||||
}
|
||||
|
|
|
|||
182
blockchain/process_test.go
Normal file
182
blockchain/process_test.go
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
package blockchain
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/blockchain/internal/testhelper"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// chainedHeaders returns desired amount of connected headers from the parentHeight.
|
||||
func chainedHeaders(parent *wire.BlockHeader, chainParams *chaincfg.Params,
|
||||
parentHeight int32, numHeaders int) []*wire.BlockHeader {
|
||||
|
||||
headers := make([]*wire.BlockHeader, 0, numHeaders)
|
||||
tip := parent
|
||||
|
||||
blockHeight := parentHeight
|
||||
for range numHeaders {
|
||||
// Use a timestamp that is one second after the previous block unless
|
||||
// this is the first block in which case the current time is used.
|
||||
var ts time.Time
|
||||
if blockHeight == 1 {
|
||||
ts = time.Unix(time.Now().Unix(), 0)
|
||||
} else {
|
||||
ts = tip.Timestamp.Add(time.Second)
|
||||
}
|
||||
|
||||
var randBytes [4]byte
|
||||
rand.Read(randBytes[:])
|
||||
merkle := chainhash.HashH(randBytes[:])
|
||||
|
||||
header := wire.BlockHeader{
|
||||
Version: 1,
|
||||
PrevBlock: tip.BlockHash(),
|
||||
MerkleRoot: merkle,
|
||||
Bits: chainParams.PowLimitBits,
|
||||
Timestamp: ts,
|
||||
Nonce: 0,
|
||||
}
|
||||
if !testhelper.SolveBlock(&header) {
|
||||
panic(fmt.Sprintf("Unable to solve block at height %d",
|
||||
blockHeight))
|
||||
}
|
||||
headers = append(headers, &header)
|
||||
tip = &header
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
func TestProcessBlockHeader(t *testing.T) {
|
||||
chain, params, tearDown := utxoCacheTestChain("TestProcessBlockHeader")
|
||||
defer tearDown()
|
||||
|
||||
// Generate and process the intial 10 block headers.
|
||||
//
|
||||
// genesis -> 1 -> 2 -> ... -> 10 (active)
|
||||
headers := chainedHeaders(¶ms.GenesisBlock.Header, params, 0, 10)
|
||||
|
||||
// Set checkpoint at block 4.
|
||||
fourthHeader := headers[3]
|
||||
fourthHeaderHash := fourthHeader.BlockHash()
|
||||
checkpoint := chaincfg.Checkpoint{
|
||||
Height: 4,
|
||||
Hash: &fourthHeaderHash,
|
||||
}
|
||||
chain.checkpoints = append(chain.checkpoints, checkpoint)
|
||||
chain.checkpointsByHeight = make(map[int32]*chaincfg.Checkpoint)
|
||||
chain.checkpointsByHeight[checkpoint.Height] = &checkpoint
|
||||
|
||||
for _, header := range headers {
|
||||
isMainChain, err := chain.ProcessBlockHeader(header, BFNone, false)
|
||||
require.NoError(t, err)
|
||||
require.True(t, isMainChain)
|
||||
}
|
||||
|
||||
// Check that the tip is correct.
|
||||
lastHeader := headers[len(headers)-1]
|
||||
lastHeaderHash := lastHeader.BlockHash()
|
||||
tipNode := chain.bestHeader.Tip()
|
||||
require.Equal(t, lastHeaderHash, tipNode.hash)
|
||||
require.Equal(t, statusHeaderStored, tipNode.status)
|
||||
require.Equal(t, int32(len(headers)), tipNode.height)
|
||||
|
||||
// Create invalid header at the checkpoint.
|
||||
thirdHeaderHash := headers[2].BlockHash()
|
||||
thirdNode := chain.index.LookupNode(&thirdHeaderHash)
|
||||
invalidForkHeight := thirdNode.height
|
||||
invalidHeaders := chainedHeaders(headers[2], params, invalidForkHeight, 1)
|
||||
|
||||
// Check that the header fails validation.
|
||||
_, err := chain.ProcessBlockHeader(invalidHeaders[0], BFNone, false)
|
||||
require.Errorf(t, err,
|
||||
"invalidHeader %v passed verification but "+
|
||||
"should've failed verification "+
|
||||
"as the header doesn't match the checkpoint",
|
||||
invalidHeaders[0].BlockHash().String(),
|
||||
)
|
||||
|
||||
// Create sidechain block headers.
|
||||
//
|
||||
// genesis -> 1 -> 2 -> 3 -> 4 -> 5 -> ... -> 10 (active)
|
||||
// \-> 6 -> ... -> 8 (valid-fork)
|
||||
blockHash := headers[4].BlockHash()
|
||||
node := chain.index.LookupNode(&blockHash)
|
||||
forkHeight := node.height
|
||||
sideChainHeaders := chainedHeaders(headers[4], params, node.height, 3)
|
||||
sidechainTip := sideChainHeaders[len(sideChainHeaders)-1]
|
||||
|
||||
// Test that the last block header fails as it's missing the previous block
|
||||
// header.
|
||||
_, err = chain.ProcessBlockHeader(sidechainTip, BFNone, false)
|
||||
require.Errorf(t, err,
|
||||
"sideChainHeader %v passed verification but "+
|
||||
"should've failed verification"+
|
||||
"as the previous header is not known",
|
||||
sideChainHeaders[len(sideChainHeaders)-1].BlockHash().String(),
|
||||
)
|
||||
|
||||
// Verify that the side-chain headers verify.
|
||||
for _, header := range sideChainHeaders {
|
||||
isMainChain, err := chain.ProcessBlockHeader(header, BFNone, false)
|
||||
require.NoError(t, err)
|
||||
require.False(t, isMainChain)
|
||||
}
|
||||
|
||||
// Check that the tip is still the same as before.
|
||||
tipNode = chain.bestHeader.Tip()
|
||||
require.Equal(t, lastHeaderHash, tipNode.hash)
|
||||
require.Equal(t, statusHeaderStored, tipNode.status)
|
||||
require.Equal(t, int32(len(headers)), tipNode.height)
|
||||
|
||||
// Verify that the side-chain extending headers verify.
|
||||
sidechainExtendingHeaders := chainedHeaders(
|
||||
sidechainTip, params, forkHeight+int32(len(sideChainHeaders)), 10)
|
||||
for _, header := range sidechainExtendingHeaders {
|
||||
isMainChain, err := chain.ProcessBlockHeader(header, BFNone, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
blockHash := header.BlockHash()
|
||||
node := chain.index.LookupNode(&blockHash)
|
||||
if node.height <= 10 {
|
||||
require.False(t, isMainChain)
|
||||
} else {
|
||||
require.True(t, isMainChain)
|
||||
}
|
||||
}
|
||||
|
||||
// Create more sidechain block headers so that it becomes the active chain.
|
||||
//
|
||||
// genesis -> 1 -> 2 -> 3 -> 4 -> 5 -> ... -> 10 (valid-fork)
|
||||
// \-> 6 -> ... -> 18 (active)
|
||||
lastSideChainHeaderIdx := len(sidechainExtendingHeaders) - 1
|
||||
lastSidechainHeader := sidechainExtendingHeaders[lastSideChainHeaderIdx]
|
||||
lastSidechainHeaderHash := lastSidechainHeader.BlockHash()
|
||||
|
||||
// Check that the tip is now different.
|
||||
tipNode = chain.bestHeader.Tip()
|
||||
require.Equal(t, lastSidechainHeaderHash, tipNode.hash)
|
||||
require.Equal(t, statusHeaderStored, tipNode.status)
|
||||
require.Equal(t,
|
||||
int32(len(sideChainHeaders)+len(sidechainExtendingHeaders))+forkHeight,
|
||||
tipNode.height)
|
||||
|
||||
// Extend the original headers and check it still verifies.
|
||||
extendedOrigHdrs := chainedHeaders(lastHeader, params, int32(len(headers)), 2)
|
||||
for _, header := range extendedOrigHdrs {
|
||||
isMainChain, err := chain.ProcessBlockHeader(header, BFNone, false)
|
||||
require.NoError(t, err)
|
||||
require.False(t, isMainChain)
|
||||
}
|
||||
|
||||
// Check that the tip didn't change.
|
||||
tipNode = chain.bestHeader.Tip()
|
||||
require.Equal(t, lastSidechainHeaderHash, tipNode.hash)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue