blockchain: enforce CSV soft-fork validation based on versionbits state
This commit modifies the existing block validation logic to examine the current version bits state of the CSV soft-fork, enforcing the new validation rules (BIPs 68, 112, and 113) accordingly based on the current `ThesholdState`.
This commit is contained in:
parent
7d0b8081dc
commit
75569f599f
4 changed files with 125 additions and 22 deletions
|
@ -351,17 +351,15 @@ func (b *BlockChain) CalcSequenceLock(tx *btcutil.Tx, utxoView *UtxoViewpoint,
|
|||
b.chainLock.Lock()
|
||||
defer b.chainLock.Unlock()
|
||||
|
||||
return b.calcSequenceLock(tx, utxoView, mempool)
|
||||
return b.calcSequenceLock(b.bestNode, tx, utxoView, mempool)
|
||||
}
|
||||
|
||||
// calcSequenceLock computes the relative lock-times for the passed
|
||||
// transaction. See the exported version, CalcSequenceLock for further details.
|
||||
//
|
||||
// This function MUST be called with the chain state lock held (for writes).
|
||||
func (b *BlockChain) calcSequenceLock(tx *btcutil.Tx, utxoView *UtxoViewpoint,
|
||||
mempool bool) (*SequenceLock, error) {
|
||||
|
||||
mTx := tx.MsgTx()
|
||||
func (b *BlockChain) calcSequenceLock(node *blockNode, tx *btcutil.Tx,
|
||||
utxoView *UtxoViewpoint, mempool bool) (*SequenceLock, error) {
|
||||
|
||||
// A value of -1 for each relative lock type represents a relative time
|
||||
// lock value that will allow a transaction to be included in a block
|
||||
|
@ -370,20 +368,42 @@ func (b *BlockChain) calcSequenceLock(tx *btcutil.Tx, utxoView *UtxoViewpoint,
|
|||
// activated.
|
||||
sequenceLock := &SequenceLock{Seconds: -1, BlockHeight: -1}
|
||||
|
||||
// The sequence locks semantics are always active for transactions
|
||||
// within the mempool.
|
||||
csvSoftforkActive := mempool
|
||||
|
||||
// If we're performing block validation, then we need to query the BIP9
|
||||
// state.
|
||||
if !csvSoftforkActive {
|
||||
prevNode, err := b.index.PrevNodeFromNode(node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Obtain the latest BIP9 version bits state for the
|
||||
// CSV-package soft-fork deployment. The adherence of sequence
|
||||
// locks depends on the current soft-fork state.
|
||||
csvState, err := b.deploymentState(prevNode, chaincfg.DeploymentCSV)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
csvSoftforkActive = csvState == ThresholdActive
|
||||
}
|
||||
|
||||
// If the transaction's version is less than 2, and BIP 68 has not yet
|
||||
// been activated then sequence locks are disabled. Additionally,
|
||||
// sequence locks don't apply to coinbase transactions Therefore, we
|
||||
// return sequence lock values of -1 indicating that this transaction
|
||||
// can be included within a block at any given height or time.
|
||||
// TODO(roasbeef): check version bits state or pass as param
|
||||
// * true should be replaced with a version bits state check
|
||||
sequenceLockActive := mTx.Version >= 2 && (mempool || true)
|
||||
mTx := tx.MsgTx()
|
||||
sequenceLockActive := mTx.Version >= 2 && csvSoftforkActive
|
||||
if !sequenceLockActive || IsCoinBase(tx) {
|
||||
return sequenceLock, nil
|
||||
}
|
||||
|
||||
// Grab the next height to use for inputs present in the mempool.
|
||||
nextHeight := b.BestSnapshot().Height + 1
|
||||
// Grab the next height from the PoV of the passed blockNode to use for
|
||||
// inputs present in the mempool.
|
||||
nextHeight := node.height + 1
|
||||
|
||||
for txInIndex, txIn := range mTx.TxIn {
|
||||
utxo := utxoView.LookupEntry(&txIn.PreviousOutPoint.Hash)
|
||||
|
@ -421,9 +441,8 @@ func (b *BlockChain) calcSequenceLock(tx *btcutil.Tx, utxoView *UtxoViewpoint,
|
|||
// compute the past median time for the block prior to
|
||||
// the one which included this referenced output.
|
||||
// TODO: caching should be added to keep this speedy
|
||||
inputDepth := uint32(b.bestNode.height-inputHeight) + 1
|
||||
blockNode, err := b.index.RelativeNode(b.bestNode,
|
||||
inputDepth)
|
||||
inputDepth := uint32(node.height-inputHeight) + 1
|
||||
blockNode, err := b.index.RelativeNode(node, inputDepth)
|
||||
if err != nil {
|
||||
return sequenceLock, err
|
||||
}
|
||||
|
|
|
@ -122,7 +122,7 @@ func TestCalcSequenceLock(t *testing.T) {
|
|||
netParams := &chaincfg.SimNetParams
|
||||
|
||||
// Create a new database and chain instance to run tests against.
|
||||
chain, teardownFunc, err := chainSetup("haveblock", netParams)
|
||||
chain, teardownFunc, err := chainSetup("calcseqlock", netParams)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to setup chain instance: %v", err)
|
||||
return
|
||||
|
@ -190,7 +190,8 @@ func TestCalcSequenceLock(t *testing.T) {
|
|||
prevBlock = block
|
||||
}
|
||||
|
||||
// Create with all the utxos within the create created above.
|
||||
// Create a utxo view with all the utxos within the blocks created
|
||||
// above.
|
||||
utxoView := blockchain.NewUtxoViewpoint()
|
||||
for blockHeight, blockWithMTP := range blocksWithMTP {
|
||||
for _, tx := range blockWithMTP.block.Transactions() {
|
||||
|
@ -200,8 +201,9 @@ func TestCalcSequenceLock(t *testing.T) {
|
|||
utxoView.SetBestHash(blocksWithMTP[len(blocksWithMTP)-1].block.Hash())
|
||||
|
||||
// The median time calculated from the PoV of the best block in our
|
||||
// test chain. For unconfirmed inputs, this value will be used since th
|
||||
// MTP will be calculated from the PoV of the yet-to-be-mined block.
|
||||
// test chain. For unconfirmed inputs, this value will be used since
|
||||
// the MTP will be calculated from the PoV of the yet-to-be-mined
|
||||
// block.
|
||||
nextMedianTime := int64(1401292712)
|
||||
|
||||
// We'll refer to this utxo within each input in the transactions
|
||||
|
|
|
@ -309,14 +309,33 @@ func (b *BlockChain) thresholdState(prevNode *blockNode, checker thresholdCondit
|
|||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (b *BlockChain) ThresholdState(deploymentID uint32) (ThresholdState, error) {
|
||||
b.chainLock.Lock()
|
||||
state, err := b.deploymentState(b.bestNode, deploymentID)
|
||||
b.chainLock.Unlock()
|
||||
|
||||
return state, err
|
||||
}
|
||||
|
||||
// deploymentState returns the current rule change threshold for a given
|
||||
// deploymentID. The threshold is evaluated from the point of view of the block
|
||||
// node passed in as the first argument to this method.
|
||||
//
|
||||
// It is important to note that, as the variable name indicates, this function
|
||||
// expects the block node prior to the block for which the deployment state is
|
||||
// desired. In other words, the returned deployment state is for the block
|
||||
// AFTER the passed node.
|
||||
//
|
||||
// This function MUST be called with the chain state lock held (for writes).
|
||||
func (b *BlockChain) deploymentState(prevNode *blockNode,
|
||||
deploymentID uint32) (ThresholdState, error) {
|
||||
|
||||
if deploymentID > uint32(len(b.chainParams.Deployments)) {
|
||||
return ThresholdFailed, DeploymentError(deploymentID)
|
||||
}
|
||||
|
||||
deployment := &b.chainParams.Deployments[deploymentID]
|
||||
checker := deploymentChecker{deployment: deployment, chain: b}
|
||||
cache := &b.deploymentCaches[deploymentID]
|
||||
b.chainLock.Lock()
|
||||
state, err := b.thresholdState(b.bestNode, checker, cache)
|
||||
b.chainLock.Unlock()
|
||||
return state, err
|
||||
|
||||
return b.thresholdState(prevNode, checker, cache)
|
||||
}
|
||||
|
|
|
@ -752,6 +752,27 @@ func (b *BlockChain) checkBlockContext(block *btcutil.Block, prevNode *blockNode
|
|||
|
||||
fastAdd := flags&BFFastAdd == BFFastAdd
|
||||
if !fastAdd {
|
||||
// Obtain the latest state of the deployed CSV soft-fork in
|
||||
// order to properly guard the new validation behavior based on
|
||||
// the current BIP 9 version bits state.
|
||||
csvState, err := b.deploymentState(prevNode, chaincfg.DeploymentCSV)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Once the CSV soft-fork is fully active, we'll switch to
|
||||
// using the current median time past of the past block's
|
||||
// timestamps for all lock-time based checks.
|
||||
blockTime := header.Timestamp
|
||||
if csvState == ThresholdActive {
|
||||
medianTime, err := b.index.CalcPastMedianTime(prevNode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
blockTime = medianTime
|
||||
}
|
||||
|
||||
// The height of this block is one more than the referenced
|
||||
// previous block.
|
||||
blockHeight := prevNode.height + 1
|
||||
|
@ -759,7 +780,7 @@ func (b *BlockChain) checkBlockContext(block *btcutil.Block, prevNode *blockNode
|
|||
// Ensure all transactions in the block are finalized.
|
||||
for _, tx := range block.Transactions() {
|
||||
if !IsFinalizedTransaction(tx, blockHeight,
|
||||
header.Timestamp) {
|
||||
blockTime) {
|
||||
|
||||
str := fmt.Sprintf("block contains unfinalized "+
|
||||
"transaction %v", tx.Hash())
|
||||
|
@ -1133,6 +1154,48 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block, vi
|
|||
scriptFlags |= txscript.ScriptVerifyCheckLockTimeVerify
|
||||
}
|
||||
|
||||
// Enforce CHECKSEQUENCEVERIFY during all block validation checks once
|
||||
// the soft-fork deployment is fully active.
|
||||
csvState, err := b.deploymentState(node.parent, chaincfg.DeploymentCSV)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if csvState == ThresholdActive {
|
||||
// If the CSV soft-fork is now active, then modify the
|
||||
// scriptFlags to ensure that the CSV op code is properly
|
||||
// validated during the script checks bleow.
|
||||
scriptFlags |= txscript.ScriptVerifyCheckSequenceVerify
|
||||
|
||||
// We obtain the MTP of the *previous* block in order to
|
||||
// determine if transactions in the current block are final.
|
||||
medianTime, err := b.index.CalcPastMedianTime(node.parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Additionally, if the CSV soft-fork package is now active,
|
||||
// then we also enforce the relative sequence number based
|
||||
// lock-times within the inputs of all transactions in this
|
||||
// candidate block.
|
||||
for _, tx := range block.Transactions() {
|
||||
// A transaction can only be included within a block
|
||||
// once the sequence locks of *all* its inputs are
|
||||
// active.
|
||||
sequenceLock, err := b.calcSequenceLock(node, tx, view,
|
||||
false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !SequenceLockActive(sequenceLock, node.height,
|
||||
medianTime) {
|
||||
str := fmt.Sprintf("block contains " +
|
||||
"transaction whose input sequence " +
|
||||
"locks are not met")
|
||||
return ruleError(ErrUnfinalizedTx, str)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now that the inexpensive checks are done and have passed, verify the
|
||||
// transactions are actually allowed to spend the coins by running the
|
||||
// expensive ECDSA signature check scripts. Doing this last helps
|
||||
|
|
Loading…
Reference in a new issue