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:
Olaoluwa Osuntokun 2016-09-26 19:00:28 -07:00
parent 7d0b8081dc
commit 75569f599f
No known key found for this signature in database
GPG key ID: 9CC5B105D03521A2
4 changed files with 125 additions and 22 deletions

View file

@ -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
}

View file

@ -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

View file

@ -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)
}

View file

@ -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