From 191420008079ba0b405f987c9e70e843715f59b6 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 21 Jun 2016 21:51:43 -0700 Subject: [PATCH] blockchain: introduce SequenceLocks for relative lock-time calcs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- blockchain/accept.go | 9 -- blockchain/chain.go | 241 +++++++++++++++++++++++++++++++++-------- blockchain/validate.go | 17 +++ wire/msgtx.go | 7 ++ 4 files changed, 219 insertions(+), 55 deletions(-) diff --git a/blockchain/accept.go b/blockchain/accept.go index f1815b3e..2ecd5bc0 100644 --- a/blockchain/accept.go +++ b/blockchain/accept.go @@ -46,15 +46,6 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags) 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 // block chain (could be either a side chain or the main chain). blockHeader := &block.MsgBlock().Header diff --git a/blockchain/chain.go b/blockchain/chain.go index 1094fea7..9016767f 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -511,6 +511,51 @@ func (b *BlockChain) getPrevNodeFromNode(node *blockNode) (*blockNode, error) { 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 // unlinking all of its children and removing it from the the node and // dependency indices. @@ -549,52 +594,6 @@ func (b *BlockChain) removeBlockNode(node *blockNode) error { 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 // 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 } +// 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 // 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 diff --git a/blockchain/validate.go b/blockchain/validate.go index bc318d26..5833ca56 100644 --- a/blockchain/validate.go +++ b/blockchain/validate.go @@ -118,6 +118,23 @@ func IsCoinBase(tx *btcutil.Tx) bool { 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. func IsFinalizedTransaction(tx *btcutil.Tx, blockHeight int32, blockTime time.Time) bool { msgTx := tx.MsgTx() diff --git a/wire/msgtx.go b/wire/msgtx.go index 3987f893..500d73ce 100644 --- a/wire/msgtx.go +++ b/wire/msgtx.go @@ -39,6 +39,13 @@ const ( // when masked against the transaction input sequence number. 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 // transaction inputs and outputs. The array will dynamically grow as needed, // but this figure is intended to provide enough space for the number of