blockchain: introduce SequenceLocks for relative lock-time calcs
This commit introduces the concept of “sequence locks” borrowed from Bitcoin Core for converting an input’s relative time-locks to an absolute value based on a particular block for input maturity evaluation. A sequence lock is computed as the most distant maturity height/time amongst all the referenced outputs within a particular transaction. A transaction with sequence locks activated within any of its inputs can *only* be included within a block if from the point-of-view of that block either the time-based or height-based maturity for all referenced inputs has been met. A transaction with sequence locks can only be accepted to the mempool iff from the point-of-view of the *next* (yet to be found block) all referenced inputs within the transaction are mature.
This commit is contained in:
parent
74fe2a4dfd
commit
1914200080
4 changed files with 219 additions and 55 deletions
|
@ -46,15 +46,6 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prune block nodes which are no longer needed before creating
|
|
||||||
// a new node.
|
|
||||||
if !dryRun {
|
|
||||||
err = b.pruneBlockNodes()
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new block node for the block and add it to the in-memory
|
// Create a new block node for the block and add it to the in-memory
|
||||||
// block chain (could be either a side chain or the main chain).
|
// block chain (could be either a side chain or the main chain).
|
||||||
blockHeader := &block.MsgBlock().Header
|
blockHeader := &block.MsgBlock().Header
|
||||||
|
|
|
@ -511,6 +511,51 @@ func (b *BlockChain) getPrevNodeFromNode(node *blockNode) (*blockNode, error) {
|
||||||
return prevBlockNode, err
|
return prevBlockNode, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// relativeNode returns the ancestor block a relative 'distance' blocks before
|
||||||
|
// the passed anchor block. While iterating backwards through the chain, any
|
||||||
|
// block nodes which aren't in the memory chain are loaded in dynamically.
|
||||||
|
//
|
||||||
|
// This function MUST be called with the chain state lock held (for writes).
|
||||||
|
func (b *BlockChain) relativeNode(anchor *blockNode, distance uint32) (*blockNode, error) {
|
||||||
|
var err error
|
||||||
|
iterNode := anchor
|
||||||
|
|
||||||
|
err = b.db.View(func(dbTx database.Tx) error {
|
||||||
|
// Walk backwards in the chian until we've gone 'distance'
|
||||||
|
// steps back.
|
||||||
|
for i := distance; i > 0; i-- {
|
||||||
|
switch {
|
||||||
|
// If the parent of this node has already been loaded
|
||||||
|
// into memory, then we can follow the link without
|
||||||
|
// hitting the database.
|
||||||
|
case iterNode.parent != nil:
|
||||||
|
iterNode = iterNode.parent
|
||||||
|
|
||||||
|
// If this node is the genesis block, then we can't go
|
||||||
|
// back any further, so we exit immediately.
|
||||||
|
case iterNode.hash.IsEqual(b.chainParams.GenesisHash):
|
||||||
|
return nil
|
||||||
|
|
||||||
|
// Otherwise, load the block node from the database,
|
||||||
|
// pulling it into the memory cache in the processes.
|
||||||
|
default:
|
||||||
|
iterNode, err = b.loadBlockNode(dbTx,
|
||||||
|
iterNode.parentHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return iterNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
// removeBlockNode removes the passed block node from the memory chain by
|
// removeBlockNode removes the passed block node from the memory chain by
|
||||||
// unlinking all of its children and removing it from the the node and
|
// unlinking all of its children and removing it from the the node and
|
||||||
// dependency indices.
|
// dependency indices.
|
||||||
|
@ -549,52 +594,6 @@ func (b *BlockChain) removeBlockNode(node *blockNode) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// pruneBlockNodes removes references to old block nodes which are no longer
|
|
||||||
// needed so they may be garbage collected. In order to validate block rules
|
|
||||||
// and choose the best chain, only a portion of the nodes which form the block
|
|
||||||
// chain are needed in memory. This function walks the chain backwards from the
|
|
||||||
// current best chain to find any nodes before the first needed block node.
|
|
||||||
//
|
|
||||||
// This function MUST be called with the chain state lock held (for writes).
|
|
||||||
func (b *BlockChain) pruneBlockNodes() error {
|
|
||||||
// Walk the chain backwards to find what should be the new root node.
|
|
||||||
// Intentionally use node.parent instead of getPrevNodeFromNode since
|
|
||||||
// the latter loads the node and the goal is to find nodes still in
|
|
||||||
// memory that can be pruned.
|
|
||||||
newRootNode := b.bestNode
|
|
||||||
for i := int32(0); i < b.minMemoryNodes-1 && newRootNode != nil; i++ {
|
|
||||||
newRootNode = newRootNode.parent
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nothing to do if there are not enough nodes.
|
|
||||||
if newRootNode == nil || newRootNode.parent == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push the nodes to delete on a list in reverse order since it's easier
|
|
||||||
// to prune them going forwards than it is backwards. This will
|
|
||||||
// typically end up being a single node since pruning is currently done
|
|
||||||
// just before each new node is created. However, that might be tuned
|
|
||||||
// later to only prune at intervals, so the code needs to account for
|
|
||||||
// the possibility of multiple nodes.
|
|
||||||
deleteNodes := list.New()
|
|
||||||
for node := newRootNode.parent; node != nil; node = node.parent {
|
|
||||||
deleteNodes.PushFront(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loop through each node to prune, unlink its children, remove it from
|
|
||||||
// the dependency index, and remove it from the node index.
|
|
||||||
for e := deleteNodes.Front(); e != nil; e = e.Next() {
|
|
||||||
node := e.Value.(*blockNode)
|
|
||||||
err := b.removeBlockNode(node)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// isMajorityVersion determines if a previous number of blocks in the chain
|
// isMajorityVersion determines if a previous number of blocks in the chain
|
||||||
// starting with startNode are at least the minimum passed version.
|
// starting with startNode are at least the minimum passed version.
|
||||||
//
|
//
|
||||||
|
@ -679,6 +678,156 @@ func (b *BlockChain) calcPastMedianTime(startNode *blockNode) (time.Time, error)
|
||||||
return medianTimestamp, nil
|
return medianTimestamp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CalcPastMedianTime calculates the median time of the previous few blocks
|
||||||
|
// prior to, and including, the end of the current best chain. It is primarily
|
||||||
|
// used to ensure new blocks have sane timestamps.
|
||||||
|
//
|
||||||
|
// This function is safe for concurrent access.
|
||||||
|
func (b *BlockChain) CalcPastMedianTime() (time.Time, error) {
|
||||||
|
b.chainLock.Lock()
|
||||||
|
defer b.chainLock.Unlock()
|
||||||
|
|
||||||
|
return b.calcPastMedianTime(b.bestNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SequenceLock represents the converted relative lock-time in seconds, and
|
||||||
|
// absolute block-height for a transaction input's relative lock-times.
|
||||||
|
// According to SequenceLock, after the referenced input has been confirmed
|
||||||
|
// within a block, a transaction spending that input can be included into a
|
||||||
|
// block either after 'seconds' (according to past median time), or once the
|
||||||
|
// 'BlockHeight' has been reached.
|
||||||
|
type SequenceLock struct {
|
||||||
|
Seconds int64
|
||||||
|
BlockHeight int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalcSequenceLock computes a relative lock-time SequenceLock for the passed
|
||||||
|
// transaction using the passed UtxoViewpoint to obtain the past median time
|
||||||
|
// for blocks in which the referenced inputs of the transactions were included
|
||||||
|
// within. The generated SequenceLock lock can be used in conjunction with a
|
||||||
|
// block height, and adjusted median block time to determine if all the inputs
|
||||||
|
// referenced within a transaction have reached sufficient maturity allowing
|
||||||
|
// the candidate transaction to be included in a block.
|
||||||
|
//
|
||||||
|
// This function is safe for concurrent access.
|
||||||
|
func (b *BlockChain) CalcSequenceLock(tx *btcutil.Tx, utxoView *UtxoViewpoint,
|
||||||
|
mempool bool) (*SequenceLock, error) {
|
||||||
|
|
||||||
|
b.chainLock.Lock()
|
||||||
|
defer b.chainLock.Unlock()
|
||||||
|
|
||||||
|
return b.calcSequenceLock(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()
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// at any given height or time. This value is returned as the relative
|
||||||
|
// lock time in the case that BIP 68 is disabled, or has not yet been
|
||||||
|
// activated.
|
||||||
|
sequenceLock := &SequenceLock{Seconds: -1, BlockHeight: -1}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
if !sequenceLockActive || IsCoinBase(tx) {
|
||||||
|
return sequenceLock, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grab the next height to use for inputs present in the mempool.
|
||||||
|
nextHeight := b.BestSnapshot().Height + 1
|
||||||
|
|
||||||
|
for txInIndex, txIn := range mTx.TxIn {
|
||||||
|
utxo := utxoView.LookupEntry(&txIn.PreviousOutPoint.Hash)
|
||||||
|
if utxo == nil {
|
||||||
|
str := fmt.Sprintf("unable to find unspent output "+
|
||||||
|
"%v referenced from transaction %s:%d",
|
||||||
|
txIn.PreviousOutPoint, tx.Hash(), txInIndex)
|
||||||
|
return sequenceLock, ruleError(ErrMissingTx, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the input height is set to the mempool height, then we
|
||||||
|
// assume the transaction makes it into the next block when
|
||||||
|
// evaluating its sequence blocks.
|
||||||
|
inputHeight := utxo.BlockHeight()
|
||||||
|
if inputHeight == 0x7fffffff {
|
||||||
|
inputHeight = nextHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given a sequence number, we apply the relative time lock
|
||||||
|
// mask in order to obtain the time lock delta required before
|
||||||
|
// this input can be spent.
|
||||||
|
sequenceNum := txIn.Sequence
|
||||||
|
relativeLock := int64(sequenceNum & wire.SequenceLockTimeMask)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
// Relative time locks are disabled for this input, so we can
|
||||||
|
// skip any further calculation.
|
||||||
|
case sequenceNum&wire.SequenceLockTimeDisabled == wire.SequenceLockTimeDisabled:
|
||||||
|
continue
|
||||||
|
case sequenceNum&wire.SequenceLockTimeIsSeconds == wire.SequenceLockTimeIsSeconds:
|
||||||
|
// This input requires a relative time lock expressed
|
||||||
|
// in seconds before it can be spent. Therefore, we
|
||||||
|
// need to query for the block prior to the one in
|
||||||
|
// which this input was included within so we can
|
||||||
|
// 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.relativeNode(b.bestNode, inputDepth)
|
||||||
|
if err != nil {
|
||||||
|
return sequenceLock, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// With all the necessary block headers loaded into
|
||||||
|
// memory, we can now finally calculate the MTP of the
|
||||||
|
// block prior to the one which included the output
|
||||||
|
// being spent.
|
||||||
|
medianTime, err := b.calcPastMedianTime(blockNode)
|
||||||
|
if err != nil {
|
||||||
|
return sequenceLock, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time based relative time-locks as defined by BIP 68
|
||||||
|
// have a time granularity of RelativeLockSeconds, so
|
||||||
|
// we shift left by this amount to convert to the
|
||||||
|
// proper relative time-lock. We also subtract one from
|
||||||
|
// the relative lock to maintain the original lockTime
|
||||||
|
// semantics.
|
||||||
|
timeLockSeconds := (relativeLock << wire.SequenceLockTimeGranularity) - 1
|
||||||
|
timeLock := medianTime.Unix() + timeLockSeconds
|
||||||
|
if timeLock > sequenceLock.Seconds {
|
||||||
|
sequenceLock.Seconds = timeLock
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// The relative lock-time for this input is expressed
|
||||||
|
// in blocks so we calculate the relative offset from
|
||||||
|
// the input's height as its converted absolute
|
||||||
|
// lock-time. We subtract one from the relative lock in
|
||||||
|
// order to maintain the original lockTime semantics.
|
||||||
|
blockHeight := inputHeight + int32(relativeLock-1)
|
||||||
|
if blockHeight > sequenceLock.BlockHeight {
|
||||||
|
sequenceLock.BlockHeight = blockHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sequenceLock, nil
|
||||||
|
}
|
||||||
|
|
||||||
// getReorganizeNodes finds the fork point between the main chain and the passed
|
// getReorganizeNodes finds the fork point between the main chain and the passed
|
||||||
// node and returns a list of block nodes that would need to be detached from
|
// node and returns a list of block nodes that would need to be detached from
|
||||||
// the main chain and a list of block nodes that would need to be attached to
|
// the main chain and a list of block nodes that would need to be attached to
|
||||||
|
|
|
@ -118,6 +118,23 @@ func IsCoinBase(tx *btcutil.Tx) bool {
|
||||||
return IsCoinBaseTx(tx.MsgTx())
|
return IsCoinBaseTx(tx.MsgTx())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SequenceLockActive determines if a transaction's sequence locks have been
|
||||||
|
// met, meaning that all the inputs of a given transaction have reached a
|
||||||
|
// height or time sufficient for their relative lock-time maturity.
|
||||||
|
func SequenceLockActive(sequenceLock *SequenceLock, blockHeight int32,
|
||||||
|
medianTimePast time.Time) bool {
|
||||||
|
|
||||||
|
// If either the seconds, or height relative-lock time has not yet
|
||||||
|
// reached, then the transaction is not yet mature according to its
|
||||||
|
// sequence locks.
|
||||||
|
if sequenceLock.Seconds >= medianTimePast.Unix() ||
|
||||||
|
sequenceLock.BlockHeight >= blockHeight {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// IsFinalizedTransaction determines whether or not a transaction is finalized.
|
// IsFinalizedTransaction determines whether or not a transaction is finalized.
|
||||||
func IsFinalizedTransaction(tx *btcutil.Tx, blockHeight int32, blockTime time.Time) bool {
|
func IsFinalizedTransaction(tx *btcutil.Tx, blockHeight int32, blockTime time.Time) bool {
|
||||||
msgTx := tx.MsgTx()
|
msgTx := tx.MsgTx()
|
||||||
|
|
|
@ -39,6 +39,13 @@ const (
|
||||||
// when masked against the transaction input sequence number.
|
// when masked against the transaction input sequence number.
|
||||||
SequenceLockTimeMask = 0x0000ffff
|
SequenceLockTimeMask = 0x0000ffff
|
||||||
|
|
||||||
|
// SequenceLockTimeGranularity is the defined time based granularity
|
||||||
|
// for seconds-based relative time locks. When converting from seconds
|
||||||
|
// to a sequence number, the value is right shifted by this amount,
|
||||||
|
// therefore the granularity of relative time locks in 512 or 2^9
|
||||||
|
// seconds. Enforced relative lock times are multiples of 512 seconds.
|
||||||
|
SequenceLockTimeGranularity = 9
|
||||||
|
|
||||||
// defaultTxInOutAlloc is the default size used for the backing array for
|
// defaultTxInOutAlloc is the default size used for the backing array for
|
||||||
// transaction inputs and outputs. The array will dynamically grow as needed,
|
// transaction inputs and outputs. The array will dynamically grow as needed,
|
||||||
// but this figure is intended to provide enough space for the number of
|
// but this figure is intended to provide enough space for the number of
|
||||||
|
|
Loading…
Add table
Reference in a new issue