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()
|
b.chainLock.Lock()
|
||||||
defer b.chainLock.Unlock()
|
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
|
// calcSequenceLock computes the relative lock-times for the passed
|
||||||
// transaction. See the exported version, CalcSequenceLock for further details.
|
// transaction. See the exported version, CalcSequenceLock for further details.
|
||||||
//
|
//
|
||||||
// This function MUST be called with the chain state lock held (for writes).
|
// This function MUST be called with the chain state lock held (for writes).
|
||||||
func (b *BlockChain) calcSequenceLock(tx *btcutil.Tx, utxoView *UtxoViewpoint,
|
func (b *BlockChain) calcSequenceLock(node *blockNode, tx *btcutil.Tx,
|
||||||
mempool bool) (*SequenceLock, error) {
|
utxoView *UtxoViewpoint, mempool bool) (*SequenceLock, error) {
|
||||||
|
|
||||||
mTx := tx.MsgTx()
|
|
||||||
|
|
||||||
// A value of -1 for each relative lock type represents a relative time
|
// 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
|
// 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.
|
// activated.
|
||||||
sequenceLock := &SequenceLock{Seconds: -1, BlockHeight: -1}
|
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
|
// If the transaction's version is less than 2, and BIP 68 has not yet
|
||||||
// been activated then sequence locks are disabled. Additionally,
|
// been activated then sequence locks are disabled. Additionally,
|
||||||
// sequence locks don't apply to coinbase transactions Therefore, we
|
// sequence locks don't apply to coinbase transactions Therefore, we
|
||||||
// return sequence lock values of -1 indicating that this transaction
|
// return sequence lock values of -1 indicating that this transaction
|
||||||
// can be included within a block at any given height or time.
|
// can be included within a block at any given height or time.
|
||||||
// TODO(roasbeef): check version bits state or pass as param
|
mTx := tx.MsgTx()
|
||||||
// * true should be replaced with a version bits state check
|
sequenceLockActive := mTx.Version >= 2 && csvSoftforkActive
|
||||||
sequenceLockActive := mTx.Version >= 2 && (mempool || true)
|
|
||||||
if !sequenceLockActive || IsCoinBase(tx) {
|
if !sequenceLockActive || IsCoinBase(tx) {
|
||||||
return sequenceLock, nil
|
return sequenceLock, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grab the next height to use for inputs present in the mempool.
|
// Grab the next height from the PoV of the passed blockNode to use for
|
||||||
nextHeight := b.BestSnapshot().Height + 1
|
// inputs present in the mempool.
|
||||||
|
nextHeight := node.height + 1
|
||||||
|
|
||||||
for txInIndex, txIn := range mTx.TxIn {
|
for txInIndex, txIn := range mTx.TxIn {
|
||||||
utxo := utxoView.LookupEntry(&txIn.PreviousOutPoint.Hash)
|
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
|
// compute the past median time for the block prior to
|
||||||
// the one which included this referenced output.
|
// the one which included this referenced output.
|
||||||
// TODO: caching should be added to keep this speedy
|
// TODO: caching should be added to keep this speedy
|
||||||
inputDepth := uint32(b.bestNode.height-inputHeight) + 1
|
inputDepth := uint32(node.height-inputHeight) + 1
|
||||||
blockNode, err := b.index.RelativeNode(b.bestNode,
|
blockNode, err := b.index.RelativeNode(node, inputDepth)
|
||||||
inputDepth)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sequenceLock, err
|
return sequenceLock, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,7 +122,7 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||||
netParams := &chaincfg.SimNetParams
|
netParams := &chaincfg.SimNetParams
|
||||||
|
|
||||||
// Create a new database and chain instance to run tests against.
|
// 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 {
|
if err != nil {
|
||||||
t.Errorf("Failed to setup chain instance: %v", err)
|
t.Errorf("Failed to setup chain instance: %v", err)
|
||||||
return
|
return
|
||||||
|
@ -190,7 +190,8 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||||
prevBlock = block
|
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()
|
utxoView := blockchain.NewUtxoViewpoint()
|
||||||
for blockHeight, blockWithMTP := range blocksWithMTP {
|
for blockHeight, blockWithMTP := range blocksWithMTP {
|
||||||
for _, tx := range blockWithMTP.block.Transactions() {
|
for _, tx := range blockWithMTP.block.Transactions() {
|
||||||
|
@ -200,8 +201,9 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||||
utxoView.SetBestHash(blocksWithMTP[len(blocksWithMTP)-1].block.Hash())
|
utxoView.SetBestHash(blocksWithMTP[len(blocksWithMTP)-1].block.Hash())
|
||||||
|
|
||||||
// The median time calculated from the PoV of the best block in our
|
// 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
|
// test chain. For unconfirmed inputs, this value will be used since
|
||||||
// MTP will be calculated from the PoV of the yet-to-be-mined block.
|
// the MTP will be calculated from the PoV of the yet-to-be-mined
|
||||||
|
// block.
|
||||||
nextMedianTime := int64(1401292712)
|
nextMedianTime := int64(1401292712)
|
||||||
|
|
||||||
// We'll refer to this utxo within each input in the transactions
|
// 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.
|
// This function is safe for concurrent access.
|
||||||
func (b *BlockChain) ThresholdState(deploymentID uint32) (ThresholdState, error) {
|
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)) {
|
if deploymentID > uint32(len(b.chainParams.Deployments)) {
|
||||||
return ThresholdFailed, DeploymentError(deploymentID)
|
return ThresholdFailed, DeploymentError(deploymentID)
|
||||||
}
|
}
|
||||||
|
|
||||||
deployment := &b.chainParams.Deployments[deploymentID]
|
deployment := &b.chainParams.Deployments[deploymentID]
|
||||||
checker := deploymentChecker{deployment: deployment, chain: b}
|
checker := deploymentChecker{deployment: deployment, chain: b}
|
||||||
cache := &b.deploymentCaches[deploymentID]
|
cache := &b.deploymentCaches[deploymentID]
|
||||||
b.chainLock.Lock()
|
|
||||||
state, err := b.thresholdState(b.bestNode, checker, cache)
|
return b.thresholdState(prevNode, checker, cache)
|
||||||
b.chainLock.Unlock()
|
|
||||||
return state, err
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -752,6 +752,27 @@ func (b *BlockChain) checkBlockContext(block *btcutil.Block, prevNode *blockNode
|
||||||
|
|
||||||
fastAdd := flags&BFFastAdd == BFFastAdd
|
fastAdd := flags&BFFastAdd == BFFastAdd
|
||||||
if !fastAdd {
|
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
|
// The height of this block is one more than the referenced
|
||||||
// previous block.
|
// previous block.
|
||||||
blockHeight := prevNode.height + 1
|
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.
|
// Ensure all transactions in the block are finalized.
|
||||||
for _, tx := range block.Transactions() {
|
for _, tx := range block.Transactions() {
|
||||||
if !IsFinalizedTransaction(tx, blockHeight,
|
if !IsFinalizedTransaction(tx, blockHeight,
|
||||||
header.Timestamp) {
|
blockTime) {
|
||||||
|
|
||||||
str := fmt.Sprintf("block contains unfinalized "+
|
str := fmt.Sprintf("block contains unfinalized "+
|
||||||
"transaction %v", tx.Hash())
|
"transaction %v", tx.Hash())
|
||||||
|
@ -1133,6 +1154,48 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block, vi
|
||||||
scriptFlags |= txscript.ScriptVerifyCheckLockTimeVerify
|
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
|
// Now that the inexpensive checks are done and have passed, verify the
|
||||||
// transactions are actually allowed to spend the coins by running the
|
// transactions are actually allowed to spend the coins by running the
|
||||||
// expensive ECDSA signature check scripts. Doing this last helps
|
// expensive ECDSA signature check scripts. Doing this last helps
|
||||||
|
|
Loading…
Add table
Reference in a new issue