dcrd/blockchain/sequencelock_test.go

502 lines
14 KiB
Go
Raw Normal View History

// Copyright (c) 2017-2019 The Decred developers
blockchain: Implement enforced relative seq locks. This repurposes the sequence number of version 2 transaction inputs to provide consensus-enforced relative lock-time semantics so that a transaction can require inputs to have a specified relative age before they are allowed to be included in a block. Each relative time lock can either specify a relative number of seconds (with a granularity of 512 seconds and a maximum value of 33,553,920) or a specific number of blocks (max 65535). The number of seconds is calculated relative to the past median time of the block before the one that contains the referenced output. This is done because, due to other changes that will also be included in the same agenda vote, said time will become the earliest possible time the block that contains the referenced output could have been (technically it will be one second after that, but that complexity is ignored since there is already a granularity involved anyways). It is also possible to disable the behavior by setting bit 31 of the sequence number (which all transactions currently do by default since they are set to the max). In order for the transaction to be permitted to the mempool, relayed, considered for inclusion into block templates, and allowed into a block, the specified relative time locks for all of its inputs must be satisfied. This only implements the required logic and tests to enforce the new behavior. Code to enforce the new behavior when considering candidate transactions for acceptance to the mempool, relaying, and inclusion into block templates will be added in a separate commit. A consensus vote is required in order to reject blocks which contain transactions that violate the new rules at a consensus level. Code to selectively enable consensus enforcement based on the result of an agenda vote will be added in a separate commit. In order to accomplish this new behavior, the concept of a sequence lock is introduced which allows the minimum possible time and height at which a transaction can be included into a block to be calculated from all inputs with non-disabled relative time locks, and functions to calculate and evaluate the sequence lock are added. The following is an overview of the changes: - Introduce a new struct named SequenceLock to represent the previously described sequence lock - Define new constants related to sequence numbers named SequenceLockTimeDisabled, SequenceLockTimeIsSeconds, SequenceLockTimeMask, and SequenceLockTimeGranularity - Add a new function named calcSequenceLock to calculate the sequence lock for a given transaction - Add a new function named SequenceLockActive to determine if a given sequence lock is satisfied for a given block height and past median time - Add a convenience function named LockTimeToSequence which can be used to convert a relative lock time to a sequence number - Add a comprehensive set of tests for all of the new funcs
2017-09-21 17:46:30 +00:00
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package blockchain
import (
"fmt"
"testing"
"time"
"github.com/decred/dcrd/chaincfg/v2"
"github.com/decred/dcrd/dcrutil/v2"
blockchain: Implement enforced relative seq locks. This repurposes the sequence number of version 2 transaction inputs to provide consensus-enforced relative lock-time semantics so that a transaction can require inputs to have a specified relative age before they are allowed to be included in a block. Each relative time lock can either specify a relative number of seconds (with a granularity of 512 seconds and a maximum value of 33,553,920) or a specific number of blocks (max 65535). The number of seconds is calculated relative to the past median time of the block before the one that contains the referenced output. This is done because, due to other changes that will also be included in the same agenda vote, said time will become the earliest possible time the block that contains the referenced output could have been (technically it will be one second after that, but that complexity is ignored since there is already a granularity involved anyways). It is also possible to disable the behavior by setting bit 31 of the sequence number (which all transactions currently do by default since they are set to the max). In order for the transaction to be permitted to the mempool, relayed, considered for inclusion into block templates, and allowed into a block, the specified relative time locks for all of its inputs must be satisfied. This only implements the required logic and tests to enforce the new behavior. Code to enforce the new behavior when considering candidate transactions for acceptance to the mempool, relaying, and inclusion into block templates will be added in a separate commit. A consensus vote is required in order to reject blocks which contain transactions that violate the new rules at a consensus level. Code to selectively enable consensus enforcement based on the result of an agenda vote will be added in a separate commit. In order to accomplish this new behavior, the concept of a sequence lock is introduced which allows the minimum possible time and height at which a transaction can be included into a block to be calculated from all inputs with non-disabled relative time locks, and functions to calculate and evaluate the sequence lock are added. The following is an overview of the changes: - Introduce a new struct named SequenceLock to represent the previously described sequence lock - Define new constants related to sequence numbers named SequenceLockTimeDisabled, SequenceLockTimeIsSeconds, SequenceLockTimeMask, and SequenceLockTimeGranularity - Add a new function named calcSequenceLock to calculate the sequence lock for a given transaction - Add a new function named SequenceLockActive to determine if a given sequence lock is satisfied for a given block height and past median time - Add a convenience function named LockTimeToSequence which can be used to convert a relative lock time to a sequence number - Add a comprehensive set of tests for all of the new funcs
2017-09-21 17:46:30 +00:00
"github.com/decred/dcrd/wire"
)
// mustLockTimeToSeq converts the passed relative lock time to a sequence number
// by using LockTimeToSequence. It only differs in that it will panic if there
// is an error so errors in the source code can be detected. It will only (and
// must only) be called with hard-coded, and therefore known good, values.
func mustLockTimeToSeq(isSeconds bool, lockTime uint32) uint32 {
sequence, err := LockTimeToSequence(isSeconds, lockTime)
if err != nil {
panic(fmt.Sprintf("invalid lock time in source file: "+
"isSeconds: %v, lockTime: %d", isSeconds, lockTime))
}
return sequence
}
// TestCalcSequenceLock exercises several combinations of inputs to the
// CalcSequenceLock function in order to ensure the returned sequence locks are
// as expected.
func TestCalcSequenceLock(t *testing.T) {
// Generate a synthetic simnet chain with enough nodes to properly test
// the sequence lock functionality.
numBlocks := uint32(20)
params := chaincfg.RegNetParams()
blockchain: Implement enforced relative seq locks. This repurposes the sequence number of version 2 transaction inputs to provide consensus-enforced relative lock-time semantics so that a transaction can require inputs to have a specified relative age before they are allowed to be included in a block. Each relative time lock can either specify a relative number of seconds (with a granularity of 512 seconds and a maximum value of 33,553,920) or a specific number of blocks (max 65535). The number of seconds is calculated relative to the past median time of the block before the one that contains the referenced output. This is done because, due to other changes that will also be included in the same agenda vote, said time will become the earliest possible time the block that contains the referenced output could have been (technically it will be one second after that, but that complexity is ignored since there is already a granularity involved anyways). It is also possible to disable the behavior by setting bit 31 of the sequence number (which all transactions currently do by default since they are set to the max). In order for the transaction to be permitted to the mempool, relayed, considered for inclusion into block templates, and allowed into a block, the specified relative time locks for all of its inputs must be satisfied. This only implements the required logic and tests to enforce the new behavior. Code to enforce the new behavior when considering candidate transactions for acceptance to the mempool, relaying, and inclusion into block templates will be added in a separate commit. A consensus vote is required in order to reject blocks which contain transactions that violate the new rules at a consensus level. Code to selectively enable consensus enforcement based on the result of an agenda vote will be added in a separate commit. In order to accomplish this new behavior, the concept of a sequence lock is introduced which allows the minimum possible time and height at which a transaction can be included into a block to be calculated from all inputs with non-disabled relative time locks, and functions to calculate and evaluate the sequence lock are added. The following is an overview of the changes: - Introduce a new struct named SequenceLock to represent the previously described sequence lock - Define new constants related to sequence numbers named SequenceLockTimeDisabled, SequenceLockTimeIsSeconds, SequenceLockTimeMask, and SequenceLockTimeGranularity - Add a new function named calcSequenceLock to calculate the sequence lock for a given transaction - Add a new function named SequenceLockActive to determine if a given sequence lock is satisfied for a given block height and past median time - Add a convenience function named LockTimeToSequence which can be used to convert a relative lock time to a sequence number - Add a comprehensive set of tests for all of the new funcs
2017-09-21 17:46:30 +00:00
bc := newFakeChain(params)
node := bc.bestChain.Tip()
blockchain: Don't store full header in block node. This modifies the block node structure to include only the specifically used fields, some of which in a more compact format, as opposed to copying the entire header and updates all code and tests accordingly. Not only is this a more efficient approach that helps pave the way for future optimizations, it is also consistent with the upstream code which helps minimize the differences to facilitate easier syncs due to less merge conflicts. In particular, since the merkle and stake roots, number of revocations, size, nonce, and extradata fields aren't used currently, they are no longer copied into the block node. Also, the block node already had a height field, which is also in the header, so this change also removes that duplication. Another change is that the block node now stores the timestamp as an int64 unix-style timestamp which is only 8 bytes versus the old timestamp that was in the header which is a time.Time and thus 24 bytes. It should be noted that future optimizations will very likely end up adding most of the omitted header fields back to the block node as individual fields so the headers can be efficiently reconstructed from memory, however, these changes are still beneficial due to the ability to decouple the block node storage format from the header struct which allows more compact representations and reording of the fields for optimal struct packing. Ultimately, the need for the parent hash can also be removed, which will save an additional 32 bytes which would not be possible without this decoupling.
2018-01-28 07:56:36 +00:00
blockTime := time.Unix(node.timestamp, 0)
blockchain: Implement enforced relative seq locks. This repurposes the sequence number of version 2 transaction inputs to provide consensus-enforced relative lock-time semantics so that a transaction can require inputs to have a specified relative age before they are allowed to be included in a block. Each relative time lock can either specify a relative number of seconds (with a granularity of 512 seconds and a maximum value of 33,553,920) or a specific number of blocks (max 65535). The number of seconds is calculated relative to the past median time of the block before the one that contains the referenced output. This is done because, due to other changes that will also be included in the same agenda vote, said time will become the earliest possible time the block that contains the referenced output could have been (technically it will be one second after that, but that complexity is ignored since there is already a granularity involved anyways). It is also possible to disable the behavior by setting bit 31 of the sequence number (which all transactions currently do by default since they are set to the max). In order for the transaction to be permitted to the mempool, relayed, considered for inclusion into block templates, and allowed into a block, the specified relative time locks for all of its inputs must be satisfied. This only implements the required logic and tests to enforce the new behavior. Code to enforce the new behavior when considering candidate transactions for acceptance to the mempool, relaying, and inclusion into block templates will be added in a separate commit. A consensus vote is required in order to reject blocks which contain transactions that violate the new rules at a consensus level. Code to selectively enable consensus enforcement based on the result of an agenda vote will be added in a separate commit. In order to accomplish this new behavior, the concept of a sequence lock is introduced which allows the minimum possible time and height at which a transaction can be included into a block to be calculated from all inputs with non-disabled relative time locks, and functions to calculate and evaluate the sequence lock are added. The following is an overview of the changes: - Introduce a new struct named SequenceLock to represent the previously described sequence lock - Define new constants related to sequence numbers named SequenceLockTimeDisabled, SequenceLockTimeIsSeconds, SequenceLockTimeMask, and SequenceLockTimeGranularity - Add a new function named calcSequenceLock to calculate the sequence lock for a given transaction - Add a new function named SequenceLockActive to determine if a given sequence lock is satisfied for a given block height and past median time - Add a convenience function named LockTimeToSequence which can be used to convert a relative lock time to a sequence number - Add a comprehensive set of tests for all of the new funcs
2017-09-21 17:46:30 +00:00
for i := uint32(0); i < numBlocks; i++ {
blockTime = blockTime.Add(time.Second)
node = newFakeNode(node, 1, 1, 0, blockTime)
bc.index.AddNode(node)
bc.bestChain.SetTip(node)
blockchain: Implement enforced relative seq locks. This repurposes the sequence number of version 2 transaction inputs to provide consensus-enforced relative lock-time semantics so that a transaction can require inputs to have a specified relative age before they are allowed to be included in a block. Each relative time lock can either specify a relative number of seconds (with a granularity of 512 seconds and a maximum value of 33,553,920) or a specific number of blocks (max 65535). The number of seconds is calculated relative to the past median time of the block before the one that contains the referenced output. This is done because, due to other changes that will also be included in the same agenda vote, said time will become the earliest possible time the block that contains the referenced output could have been (technically it will be one second after that, but that complexity is ignored since there is already a granularity involved anyways). It is also possible to disable the behavior by setting bit 31 of the sequence number (which all transactions currently do by default since they are set to the max). In order for the transaction to be permitted to the mempool, relayed, considered for inclusion into block templates, and allowed into a block, the specified relative time locks for all of its inputs must be satisfied. This only implements the required logic and tests to enforce the new behavior. Code to enforce the new behavior when considering candidate transactions for acceptance to the mempool, relaying, and inclusion into block templates will be added in a separate commit. A consensus vote is required in order to reject blocks which contain transactions that violate the new rules at a consensus level. Code to selectively enable consensus enforcement based on the result of an agenda vote will be added in a separate commit. In order to accomplish this new behavior, the concept of a sequence lock is introduced which allows the minimum possible time and height at which a transaction can be included into a block to be calculated from all inputs with non-disabled relative time locks, and functions to calculate and evaluate the sequence lock are added. The following is an overview of the changes: - Introduce a new struct named SequenceLock to represent the previously described sequence lock - Define new constants related to sequence numbers named SequenceLockTimeDisabled, SequenceLockTimeIsSeconds, SequenceLockTimeMask, and SequenceLockTimeGranularity - Add a new function named calcSequenceLock to calculate the sequence lock for a given transaction - Add a new function named SequenceLockActive to determine if a given sequence lock is satisfied for a given block height and past median time - Add a convenience function named LockTimeToSequence which can be used to convert a relative lock time to a sequence number - Add a comprehensive set of tests for all of the new funcs
2017-09-21 17:46:30 +00:00
}
// Create a utxo view with a fake utxo for the inputs used in the
// transactions created below. This utxo is added such that it has an
// age of 4 blocks.
targetTx := dcrutil.NewTx(&wire.MsgTx{
TxOut: []*wire.TxOut{{
Value: 10,
Version: 0,
PkScript: nil,
}},
})
view := NewUtxoViewpoint()
view.AddTxOuts(targetTx, int64(numBlocks)-4, 0)
view.SetBestHash(&node.hash)
// Create a utxo that spends the fake utxo created above for use in the
// transactions created in the tests. It has an age of 4 blocks. Note
// that the sequence lock heights are always calculated from the same
// point of view that they were originally calculated from for a given
// utxo. That is to say, the height prior to it.
utxo := wire.OutPoint{
Hash: *targetTx.Hash(),
Index: 0,
Tree: wire.TxTreeRegular,
}
prevUtxoHeight := int64(numBlocks) - 4
// Obtain the median time past from the PoV of the input created above.
// The median time for the input is the median time from the PoV of the
// block *prior* to the one that included it.
blockchain: Convert to full block index in mem. This reworks the block index code such that it loads all of the headers in the main chain at startup and constructs the full block index accordingly. Since the full index from the current best tip all the way back to the genesis block is now guaranteed to be in memory, this also removes all code related to dynamically loading the nodes and updates some of the logic to take advantage of the fact traversing the block index can no longer potentially fail. There are also many more optimizations and simplifications that can be made in the future as a result of this. Due to removing all of the extra overhead of tracking the dynamic state, and ensuring the block node structs are aligned to eliminate extra padding, the end result of a fully populated block index now takes quite a bit less memory than the previous dynamically loaded version. It also speeds up the initial startup process by roughly 2x since it is faster to bulk load the nodes in order as opposed to dynamically loading only the nodes near the tip in backwards order. For example, here is some startup timing information before and after this commit on a node that contains roughly 238,000 blocks: 7200 RPM HDD: ------------- Startup time before this commit: ~7.71s Startup time after this commit: ~3.47s SSD: ---- Startup time before this commit: ~6.34s Startup time after this commit: ~3.51s Some additional benefits are: - Since every block node is in memory, the code which reconstructs headers from block nodes means that all headers can always be served from memory which will be important since the network will be moving to header-based semantics - Several of the error paths can be removed since they are no longer necessary - It is no longer expensive to calculate CSV sequence locks or median times of blocks way in the past - It is much less expensive to calculate the initial states for the various intervals such as the stake and voter version - It will be possible to create much more efficient iteration and simplified views of the overall index An overview of the logic changes are as follows: - Move AncestorNode from blockIndex to blockNode and greatly simplify since it no longer has to deal with the possibility of dynamically loading nodes and related failures - Replace nodeAtHeightFromTopNode from BlockChain with RelativeAncestor on blockNode and define it in terms of AncestorNode - Move CalcPastMedianTime from blockIndex to blockNode and remove no longer necessary test for nil - Remove findNode and replace all of its uses with direct queries of the block index - Remove blockExists and replace all of its uses with direct queries of the block index - Remove all functions and fields related to dynamically loading nodes - children and parentHash fields from blockNode - depNodes from blockIndex - loadBlockNode from blockIndex - PrevNodeFromBlock from blockIndex - {p,P}revNodeFromNode from blockIndex - RemoveNode - Replace all instances of iterating backwards through nodes to directly access the parent now that nodes don't potentially need to be dynamically loaded - Introduce a lookupNode function on blockIndex which allows the initialization code to locklessly query the index - No longer take the chain lock when only access to the block index, which has its own lock, is needed - Removed the error paths from several functions that can no longer fail - getReorganizeNodes - findPrevTestNetDifficulty - sumPurchasedTickets - findStakeVersionPriorNode - Removed all error paths related to node iteration that can no longer fail - Modify FetchUtxoView to return an empty view for the genesis block
2018-05-27 02:47:45 +00:00
medianTime := node.RelativeAncestor(5).CalcPastMedianTime().Unix()
blockchain: Implement enforced relative seq locks. This repurposes the sequence number of version 2 transaction inputs to provide consensus-enforced relative lock-time semantics so that a transaction can require inputs to have a specified relative age before they are allowed to be included in a block. Each relative time lock can either specify a relative number of seconds (with a granularity of 512 seconds and a maximum value of 33,553,920) or a specific number of blocks (max 65535). The number of seconds is calculated relative to the past median time of the block before the one that contains the referenced output. This is done because, due to other changes that will also be included in the same agenda vote, said time will become the earliest possible time the block that contains the referenced output could have been (technically it will be one second after that, but that complexity is ignored since there is already a granularity involved anyways). It is also possible to disable the behavior by setting bit 31 of the sequence number (which all transactions currently do by default since they are set to the max). In order for the transaction to be permitted to the mempool, relayed, considered for inclusion into block templates, and allowed into a block, the specified relative time locks for all of its inputs must be satisfied. This only implements the required logic and tests to enforce the new behavior. Code to enforce the new behavior when considering candidate transactions for acceptance to the mempool, relaying, and inclusion into block templates will be added in a separate commit. A consensus vote is required in order to reject blocks which contain transactions that violate the new rules at a consensus level. Code to selectively enable consensus enforcement based on the result of an agenda vote will be added in a separate commit. In order to accomplish this new behavior, the concept of a sequence lock is introduced which allows the minimum possible time and height at which a transaction can be included into a block to be calculated from all inputs with non-disabled relative time locks, and functions to calculate and evaluate the sequence lock are added. The following is an overview of the changes: - Introduce a new struct named SequenceLock to represent the previously described sequence lock - Define new constants related to sequence numbers named SequenceLockTimeDisabled, SequenceLockTimeIsSeconds, SequenceLockTimeMask, and SequenceLockTimeGranularity - Add a new function named calcSequenceLock to calculate the sequence lock for a given transaction - Add a new function named SequenceLockActive to determine if a given sequence lock is satisfied for a given block height and past median time - Add a convenience function named LockTimeToSequence which can be used to convert a relative lock time to a sequence number - Add a comprehensive set of tests for all of the new funcs
2017-09-21 17:46:30 +00:00
// The median time calculated from the PoV of the best block in the
// test chain. For unconfirmed inputs, this value will be used since
// the median time will be calculated from the PoV of the
// yet-to-be-mined block.
blockchain: Convert to full block index in mem. This reworks the block index code such that it loads all of the headers in the main chain at startup and constructs the full block index accordingly. Since the full index from the current best tip all the way back to the genesis block is now guaranteed to be in memory, this also removes all code related to dynamically loading the nodes and updates some of the logic to take advantage of the fact traversing the block index can no longer potentially fail. There are also many more optimizations and simplifications that can be made in the future as a result of this. Due to removing all of the extra overhead of tracking the dynamic state, and ensuring the block node structs are aligned to eliminate extra padding, the end result of a fully populated block index now takes quite a bit less memory than the previous dynamically loaded version. It also speeds up the initial startup process by roughly 2x since it is faster to bulk load the nodes in order as opposed to dynamically loading only the nodes near the tip in backwards order. For example, here is some startup timing information before and after this commit on a node that contains roughly 238,000 blocks: 7200 RPM HDD: ------------- Startup time before this commit: ~7.71s Startup time after this commit: ~3.47s SSD: ---- Startup time before this commit: ~6.34s Startup time after this commit: ~3.51s Some additional benefits are: - Since every block node is in memory, the code which reconstructs headers from block nodes means that all headers can always be served from memory which will be important since the network will be moving to header-based semantics - Several of the error paths can be removed since they are no longer necessary - It is no longer expensive to calculate CSV sequence locks or median times of blocks way in the past - It is much less expensive to calculate the initial states for the various intervals such as the stake and voter version - It will be possible to create much more efficient iteration and simplified views of the overall index An overview of the logic changes are as follows: - Move AncestorNode from blockIndex to blockNode and greatly simplify since it no longer has to deal with the possibility of dynamically loading nodes and related failures - Replace nodeAtHeightFromTopNode from BlockChain with RelativeAncestor on blockNode and define it in terms of AncestorNode - Move CalcPastMedianTime from blockIndex to blockNode and remove no longer necessary test for nil - Remove findNode and replace all of its uses with direct queries of the block index - Remove blockExists and replace all of its uses with direct queries of the block index - Remove all functions and fields related to dynamically loading nodes - children and parentHash fields from blockNode - depNodes from blockIndex - loadBlockNode from blockIndex - PrevNodeFromBlock from blockIndex - {p,P}revNodeFromNode from blockIndex - RemoveNode - Replace all instances of iterating backwards through nodes to directly access the parent now that nodes don't potentially need to be dynamically loaded - Introduce a lookupNode function on blockIndex which allows the initialization code to locklessly query the index - No longer take the chain lock when only access to the block index, which has its own lock, is needed - Removed the error paths from several functions that can no longer fail - getReorganizeNodes - findPrevTestNetDifficulty - sumPurchasedTickets - findStakeVersionPriorNode - Removed all error paths related to node iteration that can no longer fail - Modify FetchUtxoView to return an empty view for the genesis block
2018-05-27 02:47:45 +00:00
nextMedianTime := node.CalcPastMedianTime().Unix()
blockchain: Implement enforced relative seq locks. This repurposes the sequence number of version 2 transaction inputs to provide consensus-enforced relative lock-time semantics so that a transaction can require inputs to have a specified relative age before they are allowed to be included in a block. Each relative time lock can either specify a relative number of seconds (with a granularity of 512 seconds and a maximum value of 33,553,920) or a specific number of blocks (max 65535). The number of seconds is calculated relative to the past median time of the block before the one that contains the referenced output. This is done because, due to other changes that will also be included in the same agenda vote, said time will become the earliest possible time the block that contains the referenced output could have been (technically it will be one second after that, but that complexity is ignored since there is already a granularity involved anyways). It is also possible to disable the behavior by setting bit 31 of the sequence number (which all transactions currently do by default since they are set to the max). In order for the transaction to be permitted to the mempool, relayed, considered for inclusion into block templates, and allowed into a block, the specified relative time locks for all of its inputs must be satisfied. This only implements the required logic and tests to enforce the new behavior. Code to enforce the new behavior when considering candidate transactions for acceptance to the mempool, relaying, and inclusion into block templates will be added in a separate commit. A consensus vote is required in order to reject blocks which contain transactions that violate the new rules at a consensus level. Code to selectively enable consensus enforcement based on the result of an agenda vote will be added in a separate commit. In order to accomplish this new behavior, the concept of a sequence lock is introduced which allows the minimum possible time and height at which a transaction can be included into a block to be calculated from all inputs with non-disabled relative time locks, and functions to calculate and evaluate the sequence lock are added. The following is an overview of the changes: - Introduce a new struct named SequenceLock to represent the previously described sequence lock - Define new constants related to sequence numbers named SequenceLockTimeDisabled, SequenceLockTimeIsSeconds, SequenceLockTimeMask, and SequenceLockTimeGranularity - Add a new function named calcSequenceLock to calculate the sequence lock for a given transaction - Add a new function named SequenceLockActive to determine if a given sequence lock is satisfied for a given block height and past median time - Add a convenience function named LockTimeToSequence which can be used to convert a relative lock time to a sequence number - Add a comprehensive set of tests for all of the new funcs
2017-09-21 17:46:30 +00:00
nextBlockHeight := int64(numBlocks) + 1
// Add an additional transaction which will serve as our unconfirmed
// output.
unConfTx := &wire.MsgTx{
TxOut: []*wire.TxOut{{
Value: 5,
Version: 0,
PkScript: nil,
}},
}
unConfUtxo := wire.OutPoint{
Hash: unConfTx.TxHash(),
Index: 0,
Tree: wire.TxTreeRegular,
}
// Adding a utxo with a height of 0x7fffffff indicates that the output
// is currently unmined.
view.AddTxOuts(dcrutil.NewTx(unConfTx), 0x7fffffff, wire.NullBlockIndex)
tests := []struct {
name string
txVersion uint16
inputs []*wire.TxIn
isActive bool
want SequenceLock
}{
{
// A transaction of version one should disable sequence
// locks as the new sequence number semantics only apply
// to transactions version 2 or higher.
name: "v1 transaction",
txVersion: 1,
inputs: []*wire.TxIn{{
PreviousOutPoint: utxo,
Sequence: mustLockTimeToSeq(false, 3),
}},
isActive: true,
want: SequenceLock{
MinHeight: -1,
MinTime: -1,
},
},
{
// A transaction with a single input with max sequence
// number. This sequence number has the high bit set,
// so sequence locks should be disabled.
name: "max sequence number",
txVersion: 2,
inputs: []*wire.TxIn{{
PreviousOutPoint: utxo,
Sequence: wire.MaxTxInSequenceNum,
}},
isActive: true,
want: SequenceLock{
MinHeight: -1,
MinTime: -1,
},
},
{
// A transaction that would result in a specific
// sequence lock except set the agenda is not being
// active yet, so sequence locks should be disabled.
name: "agenda not yet active",
txVersion: 2,
inputs: []*wire.TxIn{{
PreviousOutPoint: utxo,
Sequence: mustLockTimeToSeq(true, 2),
}},
isActive: false,
want: SequenceLock{
MinHeight: -1,
MinTime: -1,
},
},
{
// A transaction with a single input whose locktime is
// expressed in seconds. However, the specified lock
// time is below the required floor for time based lock
// times since they have time granularity of 512
// seconds. As a result, the seconds locktime should be
// just before the median time of the targeted block.
name: "seconds below granularity",
txVersion: 2,
inputs: []*wire.TxIn{{
PreviousOutPoint: utxo,
Sequence: mustLockTimeToSeq(true, 2),
}},
isActive: true,
want: SequenceLock{
MinHeight: -1,
MinTime: medianTime - 1,
},
},
{
// A transaction with a single input whose locktime is
// expressed in seconds. The number of seconds should
// be 1023 seconds after the median past time of the
// input.
name: "1024 seconds",
txVersion: 2,
inputs: []*wire.TxIn{{
PreviousOutPoint: utxo,
Sequence: mustLockTimeToSeq(true, 1024),
}},
isActive: true,
want: SequenceLock{
MinHeight: -1,
MinTime: medianTime + 1023,
},
},
{
// A transaction with multiple inputs. The first input
// has a locktime expressed in seconds. The second
// input has a sequence lock in blocks with a value of
// 4. The last input has a sequence number with a value
// of 5, but has the disable bit set. So the first lock
// should be selected as it's the latest lock that isn't
// disabled.
name: "multiple inputs, 1 disabled",
txVersion: 2,
inputs: []*wire.TxIn{{
PreviousOutPoint: utxo,
Sequence: mustLockTimeToSeq(true, 2560),
}, {
PreviousOutPoint: utxo,
Sequence: mustLockTimeToSeq(false, 4),
}, {
PreviousOutPoint: utxo,
Sequence: mustLockTimeToSeq(false, 5) |
wire.SequenceLockTimeDisabled,
}},
isActive: true,
want: SequenceLock{
MinHeight: prevUtxoHeight + 3,
MinTime: medianTime + (5 << wire.SequenceLockTimeGranularity) - 1,
},
},
{
// A transaction with a single input. The input's
// sequence number encodes a relative locktime in blocks
// (3 blocks). The sequence lock should have a value
blockchain: Implement enforced relative seq locks. This repurposes the sequence number of version 2 transaction inputs to provide consensus-enforced relative lock-time semantics so that a transaction can require inputs to have a specified relative age before they are allowed to be included in a block. Each relative time lock can either specify a relative number of seconds (with a granularity of 512 seconds and a maximum value of 33,553,920) or a specific number of blocks (max 65535). The number of seconds is calculated relative to the past median time of the block before the one that contains the referenced output. This is done because, due to other changes that will also be included in the same agenda vote, said time will become the earliest possible time the block that contains the referenced output could have been (technically it will be one second after that, but that complexity is ignored since there is already a granularity involved anyways). It is also possible to disable the behavior by setting bit 31 of the sequence number (which all transactions currently do by default since they are set to the max). In order for the transaction to be permitted to the mempool, relayed, considered for inclusion into block templates, and allowed into a block, the specified relative time locks for all of its inputs must be satisfied. This only implements the required logic and tests to enforce the new behavior. Code to enforce the new behavior when considering candidate transactions for acceptance to the mempool, relaying, and inclusion into block templates will be added in a separate commit. A consensus vote is required in order to reject blocks which contain transactions that violate the new rules at a consensus level. Code to selectively enable consensus enforcement based on the result of an agenda vote will be added in a separate commit. In order to accomplish this new behavior, the concept of a sequence lock is introduced which allows the minimum possible time and height at which a transaction can be included into a block to be calculated from all inputs with non-disabled relative time locks, and functions to calculate and evaluate the sequence lock are added. The following is an overview of the changes: - Introduce a new struct named SequenceLock to represent the previously described sequence lock - Define new constants related to sequence numbers named SequenceLockTimeDisabled, SequenceLockTimeIsSeconds, SequenceLockTimeMask, and SequenceLockTimeGranularity - Add a new function named calcSequenceLock to calculate the sequence lock for a given transaction - Add a new function named SequenceLockActive to determine if a given sequence lock is satisfied for a given block height and past median time - Add a convenience function named LockTimeToSequence which can be used to convert a relative lock time to a sequence number - Add a comprehensive set of tests for all of the new funcs
2017-09-21 17:46:30 +00:00
// of -1 for seconds, but a height of 2 meaning it can
// be included at height 3.
name: "3 blocks",
txVersion: 2,
inputs: []*wire.TxIn{{
PreviousOutPoint: utxo,
Sequence: mustLockTimeToSeq(false, 3),
}},
isActive: true,
want: SequenceLock{
MinHeight: prevUtxoHeight + 2,
MinTime: -1,
},
},
{
// A transaction with two inputs with locktimes
// expressed in seconds. The selected sequence lock
// value for seconds should be the time further in the
// future.
name: "2 inputs both in seconds",
txVersion: 2,
inputs: []*wire.TxIn{{
PreviousOutPoint: utxo,
Sequence: mustLockTimeToSeq(true, 5120),
}, {
PreviousOutPoint: utxo,
Sequence: mustLockTimeToSeq(true, 2560),
}},
isActive: true,
want: SequenceLock{
MinHeight: -1,
MinTime: medianTime + (10 << wire.SequenceLockTimeGranularity) - 1,
},
},
{
// A transaction with two inputs with locktimes
// expressed in blocks. The selected sequence lock
// value for blocks should be the height further in the
// future, so a height of 10 indicating it can be
// included at height 11.
name: "2 inputs both in blocks",
txVersion: 2,
inputs: []*wire.TxIn{{
PreviousOutPoint: utxo,
Sequence: mustLockTimeToSeq(false, 1),
}, {
PreviousOutPoint: utxo,
Sequence: mustLockTimeToSeq(false, 11),
}},
isActive: true,
want: SequenceLock{
MinHeight: prevUtxoHeight + 10,
MinTime: -1,
},
},
{
// A transaction with multiple inputs. Two inputs are
// seconds and the other two are blocks. The lock
// further into the future for both inputs should be
// chosen.
name: "4 inputs, 2 in seconds, 2 in blocks",
txVersion: 2,
inputs: []*wire.TxIn{{
PreviousOutPoint: utxo,
Sequence: mustLockTimeToSeq(true, 2560),
}, {
PreviousOutPoint: utxo,
Sequence: mustLockTimeToSeq(true, 6656),
}, {
PreviousOutPoint: utxo,
Sequence: mustLockTimeToSeq(false, 3),
}, {
PreviousOutPoint: utxo,
Sequence: mustLockTimeToSeq(false, 9),
}},
isActive: true,
want: SequenceLock{
MinHeight: prevUtxoHeight + 8,
MinTime: medianTime + (13 << wire.SequenceLockTimeGranularity) - 1,
},
},
{
// A transaction with a single unconfirmed input. Since
// the input is unconfirmed, the height of the input
// should be interpreted as the height of the *next*
// block. So, a 2 block relative lock means the
// sequence lock should be for 1 block after the *next*
// block height, indicating it can be included 2 blocks
// after that.
name: "unconfirmed input in blocks",
txVersion: 2,
inputs: []*wire.TxIn{{
PreviousOutPoint: unConfUtxo,
Sequence: mustLockTimeToSeq(false, 2),
}},
isActive: true,
want: SequenceLock{
MinHeight: nextBlockHeight + 1,
MinTime: -1,
},
},
{
// A transaction with a single unconfirmed input. The
// input has locktime in seconds, so the locktime should
// be based off the median time of the *next* block.
name: "unconfirmed input in seconds",
txVersion: 2,
inputs: []*wire.TxIn{{
PreviousOutPoint: unConfUtxo,
Sequence: mustLockTimeToSeq(true, 1024),
}},
isActive: true,
want: SequenceLock{
MinHeight: -1,
MinTime: nextMedianTime + 1023,
},
},
}
for i, test := range tests {
// Create fake spending transaction per the test input data.
tx := wire.MsgTx{
SerType: wire.TxSerializeFull,
Version: test.txVersion,
LockTime: 0,
Expiry: 0,
TxOut: nil,
}
for _, txIn := range test.inputs {
tx.AddTxIn(txIn)
}
utilTx := dcrutil.NewTx(&tx)
// Calculate the sequence lock for the test input data. Since
// the exported function always has the agenda active, use the
// unexported function when simulating the agenda not being
// active, and alternate between them to ensure both are
// exercised.
var seqLock *SequenceLock
var err error
if test.isActive && i%2 == 0 {
seqLock, err = bc.CalcSequenceLock(utilTx, view)
} else {
bc.chainLock.Lock()
seqLock, err = bc.calcSequenceLock(node, utilTx, view,
test.isActive)
bc.chainLock.Unlock()
}
if err != nil {
t.Errorf("%s: unable to calc sequence lock: %v",
test.name, err)
continue
}
// Ensure both the returned sequence lock seconds and block
// height match the expected values.
if seqLock.MinTime != test.want.MinTime {
t.Errorf("%s: mismatched seconds - got %v, want %v",
blockchain: Implement enforced relative seq locks. This repurposes the sequence number of version 2 transaction inputs to provide consensus-enforced relative lock-time semantics so that a transaction can require inputs to have a specified relative age before they are allowed to be included in a block. Each relative time lock can either specify a relative number of seconds (with a granularity of 512 seconds and a maximum value of 33,553,920) or a specific number of blocks (max 65535). The number of seconds is calculated relative to the past median time of the block before the one that contains the referenced output. This is done because, due to other changes that will also be included in the same agenda vote, said time will become the earliest possible time the block that contains the referenced output could have been (technically it will be one second after that, but that complexity is ignored since there is already a granularity involved anyways). It is also possible to disable the behavior by setting bit 31 of the sequence number (which all transactions currently do by default since they are set to the max). In order for the transaction to be permitted to the mempool, relayed, considered for inclusion into block templates, and allowed into a block, the specified relative time locks for all of its inputs must be satisfied. This only implements the required logic and tests to enforce the new behavior. Code to enforce the new behavior when considering candidate transactions for acceptance to the mempool, relaying, and inclusion into block templates will be added in a separate commit. A consensus vote is required in order to reject blocks which contain transactions that violate the new rules at a consensus level. Code to selectively enable consensus enforcement based on the result of an agenda vote will be added in a separate commit. In order to accomplish this new behavior, the concept of a sequence lock is introduced which allows the minimum possible time and height at which a transaction can be included into a block to be calculated from all inputs with non-disabled relative time locks, and functions to calculate and evaluate the sequence lock are added. The following is an overview of the changes: - Introduce a new struct named SequenceLock to represent the previously described sequence lock - Define new constants related to sequence numbers named SequenceLockTimeDisabled, SequenceLockTimeIsSeconds, SequenceLockTimeMask, and SequenceLockTimeGranularity - Add a new function named calcSequenceLock to calculate the sequence lock for a given transaction - Add a new function named SequenceLockActive to determine if a given sequence lock is satisfied for a given block height and past median time - Add a convenience function named LockTimeToSequence which can be used to convert a relative lock time to a sequence number - Add a comprehensive set of tests for all of the new funcs
2017-09-21 17:46:30 +00:00
test.name, seqLock.MinTime, test.want.MinTime)
continue
}
if seqLock.MinHeight != test.want.MinHeight {
t.Errorf("%s: mismatched height - got %v, want %v",
test.name, seqLock.MinHeight,
test.want.MinHeight)
}
}
}
// TestLockTimeToSequence ensure the convenience function to convert relative
// lock times to a sequence number works as expected.
func TestLockTimeToSequence(t *testing.T) {
const (
// The following constants are used over the package-level
// definitions to ensure tests correctly detect any changes to
// them.
secondsGranularityBits = 9
secondsBit = 1 << 22
maxValue = 1<<16 - 1
maxBlockHeight = maxValue
maxSeconds = maxValue << secondsGranularityBits
)
tests := []struct {
name string
locktime uint32
isSeconds bool
expected uint32
invalid bool
}{
{
name: "relative block height 0",
locktime: 0,
isSeconds: false,
expected: 0,
},
{
name: "max relative block height",
locktime: maxBlockHeight,
isSeconds: false,
expected: maxBlockHeight,
},
{
name: "max relative block height +1",
locktime: maxBlockHeight + 1,
isSeconds: false,
expected: 0,
invalid: true,
},
{
name: "relative seconds 0",
locktime: 0,
isSeconds: true,
expected: secondsBit,
},
{
name: "relative seconds granularity - 1",
locktime: (1 << secondsGranularityBits) - 1,
isSeconds: true,
expected: secondsBit,
},
{
name: "relative seconds exact granularity",
locktime: 1 << secondsGranularityBits,
isSeconds: true,
expected: secondsBit + 1,
},
{
name: "relative seconds granularity + 1",
locktime: (1 << secondsGranularityBits) + 1,
isSeconds: true,
expected: secondsBit + 1,
},
{
name: "relative seconds max - 1",
locktime: maxSeconds - 1,
isSeconds: true,
expected: secondsBit + maxValue - 1,
},
{
name: "relative seconds max",
locktime: maxSeconds,
isSeconds: true,
expected: secondsBit + maxValue,
},
{
name: "relative seconds max +1",
locktime: maxSeconds + 1,
isSeconds: true,
expected: 0,
invalid: true,
},
}
for _, test := range tests {
gotSequence, err := LockTimeToSequence(test.isSeconds,
test.locktime)
if err != nil && !test.invalid {
t.Errorf("%s: unexpected error: %v", test.name, err)
blockchain: Implement enforced relative seq locks. This repurposes the sequence number of version 2 transaction inputs to provide consensus-enforced relative lock-time semantics so that a transaction can require inputs to have a specified relative age before they are allowed to be included in a block. Each relative time lock can either specify a relative number of seconds (with a granularity of 512 seconds and a maximum value of 33,553,920) or a specific number of blocks (max 65535). The number of seconds is calculated relative to the past median time of the block before the one that contains the referenced output. This is done because, due to other changes that will also be included in the same agenda vote, said time will become the earliest possible time the block that contains the referenced output could have been (technically it will be one second after that, but that complexity is ignored since there is already a granularity involved anyways). It is also possible to disable the behavior by setting bit 31 of the sequence number (which all transactions currently do by default since they are set to the max). In order for the transaction to be permitted to the mempool, relayed, considered for inclusion into block templates, and allowed into a block, the specified relative time locks for all of its inputs must be satisfied. This only implements the required logic and tests to enforce the new behavior. Code to enforce the new behavior when considering candidate transactions for acceptance to the mempool, relaying, and inclusion into block templates will be added in a separate commit. A consensus vote is required in order to reject blocks which contain transactions that violate the new rules at a consensus level. Code to selectively enable consensus enforcement based on the result of an agenda vote will be added in a separate commit. In order to accomplish this new behavior, the concept of a sequence lock is introduced which allows the minimum possible time and height at which a transaction can be included into a block to be calculated from all inputs with non-disabled relative time locks, and functions to calculate and evaluate the sequence lock are added. The following is an overview of the changes: - Introduce a new struct named SequenceLock to represent the previously described sequence lock - Define new constants related to sequence numbers named SequenceLockTimeDisabled, SequenceLockTimeIsSeconds, SequenceLockTimeMask, and SequenceLockTimeGranularity - Add a new function named calcSequenceLock to calculate the sequence lock for a given transaction - Add a new function named SequenceLockActive to determine if a given sequence lock is satisfied for a given block height and past median time - Add a convenience function named LockTimeToSequence which can be used to convert a relative lock time to a sequence number - Add a comprehensive set of tests for all of the new funcs
2017-09-21 17:46:30 +00:00
continue
}
if err == nil && test.invalid {
t.Errorf("%s: did not receive expected error", test.name)
blockchain: Implement enforced relative seq locks. This repurposes the sequence number of version 2 transaction inputs to provide consensus-enforced relative lock-time semantics so that a transaction can require inputs to have a specified relative age before they are allowed to be included in a block. Each relative time lock can either specify a relative number of seconds (with a granularity of 512 seconds and a maximum value of 33,553,920) or a specific number of blocks (max 65535). The number of seconds is calculated relative to the past median time of the block before the one that contains the referenced output. This is done because, due to other changes that will also be included in the same agenda vote, said time will become the earliest possible time the block that contains the referenced output could have been (technically it will be one second after that, but that complexity is ignored since there is already a granularity involved anyways). It is also possible to disable the behavior by setting bit 31 of the sequence number (which all transactions currently do by default since they are set to the max). In order for the transaction to be permitted to the mempool, relayed, considered for inclusion into block templates, and allowed into a block, the specified relative time locks for all of its inputs must be satisfied. This only implements the required logic and tests to enforce the new behavior. Code to enforce the new behavior when considering candidate transactions for acceptance to the mempool, relaying, and inclusion into block templates will be added in a separate commit. A consensus vote is required in order to reject blocks which contain transactions that violate the new rules at a consensus level. Code to selectively enable consensus enforcement based on the result of an agenda vote will be added in a separate commit. In order to accomplish this new behavior, the concept of a sequence lock is introduced which allows the minimum possible time and height at which a transaction can be included into a block to be calculated from all inputs with non-disabled relative time locks, and functions to calculate and evaluate the sequence lock are added. The following is an overview of the changes: - Introduce a new struct named SequenceLock to represent the previously described sequence lock - Define new constants related to sequence numbers named SequenceLockTimeDisabled, SequenceLockTimeIsSeconds, SequenceLockTimeMask, and SequenceLockTimeGranularity - Add a new function named calcSequenceLock to calculate the sequence lock for a given transaction - Add a new function named SequenceLockActive to determine if a given sequence lock is satisfied for a given block height and past median time - Add a convenience function named LockTimeToSequence which can be used to convert a relative lock time to a sequence number - Add a comprehensive set of tests for all of the new funcs
2017-09-21 17:46:30 +00:00
continue
}
if gotSequence != test.expected {
t.Errorf("%s: mismatched sequence - got %d, want %d",
test.name, gotSequence, test.expected)
continue
}
}
}