BIP68(nSequence)
1 BIP地址
BIP: 68 Layer: Consensus (soft fork) Title: Relative lock-time using consensus-enforced sequence numbers Author: Mark Friedenbach <mark@friedenbach.org> BtcDrak <btcdrak@gmail.com> Nicolas Dorier <nicolas.dorier@gmail.com> kinoshitajona <kinoshitajona@gmail.com> Comments-Summary: No comments yet. Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0068 Status: Final Type: Standards Track Created: 2015-05-28
2 原因和目的
BIP68重新定义了 TxIn.Sequence
的语义。影响miner在挖矿时,是否挖该transaction。
type MsgTx struct { Version int32 TxIn []*TxIn TxOut []*TxOut LockTime uint32 } // TxIn defines a bitcoin transaction input. type TxIn struct { PreviousOutPoint OutPoint SignatureScript []byte Witness TxWitness Sequence uint32 // <----- Here } // OutPoint defines a bitcoin data type that is used to track previous // transaction outputs. type OutPoint struct { Hash chainhash.Hash Index uint32 }
原始的 TxIn.Sequence
的目的是,在内存池中的transaction会被指向同一个input但拥有较高的Sequence的transaction替换。
但由于奖励机制(原来的transaction奖励更多),矿工不一定会遵守这个规则。所以BIP68重新定义了sequence字段。
MsgTx.LockTime
用来保证该transaction在某一locktime后才会被挖出。
MsgTx.TxIn.Sequence
用来保证该transaction在花费的TxIn指向的block后的一定blocks或者时间后才会被挖出。
3 细节
MsgTx.Version
要>=2- 用到了bip113(TODO add link)里定义的median-time-past(MTP)
- 由于sequence字段是32位的, 1<<31的bit(Disable Flag), 用来表示是否禁用该bip定义的relative lock time语义
- 1<<22的bit(Type Flag), 用来区分该relative lock-time是基于时间的还是区块的
- 1<<22的bit为1,那么该定义的relative lock time是基于时间的,基本的时间单位是512s(因为一个Block的生成需要600s,为了保证大概相同的时间用time-based和lock-based需要的bit差不多,所以取了最近的
512=2^9
为基本单位)。 如果该值为0, 则表示该input可以被包含到任意block。 如果该值为n, 那么表示可以被包含到MTP在,TxIn.PreviousOutPoint.Hash
指代的transaction所在的Block的MTP+n*512s 之后的blocks - 1<<22的bit为0,那么该定义的relative lock time是基于Block数量的,
如果该值为0, 则表示该input可以被包含到任意block。
如果该值为n, 那么表示可以被包含到,
TxIn.PreviousOutPoint.Hash
指代的transaction所在的Block的n个blocks之后的blocks - BIP65仅定义了sequence32位中的16位,所以计算relative lock time时需要
0x0000ffff
掩码,16位保证了大概(2^16-1)*512 / 2600 / 24 =388.35days
一年的时间
4 其它
- 采用BIP9定义的"versionbits"的bit 0来部署
- Bitcoin主网,开始时间为midnight 1st May 2016 UTC (Epoch timestamp 1462060800), timeout时间为midnight 1st May 2017 UTC (Epoch timestamp 1493596800).
- 需要和BIP112,BIP113同时部署
5 代码示例
代码来自btcd的blockchain/chain.go
计算sequenceLock
// 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(node *blockNode, tx *btcutil.Tx, utxoView *UtxoViewpoint, mempool bool) (*SequenceLock, error) { // A value of -1 for each relative lock type represents a relative time // lock value that will allow a transaction to be included in a block // 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} // 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 { // 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(node.parent, chaincfg.DeploymentCSV) if err != nil { return nil, err } csvSoftforkActive = csvState == ThresholdActive } // If the transaction's version is less than 2, and BIP 68 has not yet // been activated then sequence locks are disabled. Additionally, // sequence locks don't apply to coinbase transactions Therefore, we // return sequence lock values of -1 indicating that this transaction // can be included within a block at any given height or time. mTx := tx.MsgTx() sequenceLockActive := mTx.Version >= 2 && csvSoftforkActive if !sequenceLockActive || IsCoinBase(tx) { return sequenceLock, nil } // Grab the next height from the PoV of the passed blockNode to use for // inputs present in the mempool. nextHeight := node.height + 1 for txInIndex, txIn := range mTx.TxIn { utxo := utxoView.LookupEntry(txIn.PreviousOutPoint) if utxo == nil { str := fmt.Sprintf("output %v referenced from "+ "transaction %s:%d either does not exist or "+ "has already been spent", txIn.PreviousOutPoint, tx.Hash(), txInIndex) return sequenceLock, ruleError(ErrMissingTxOut, 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. prevInputHeight := inputHeight - 1 if prevInputHeight < 0 { prevInputHeight = 0 } blockNode := node.Ancestor(prevInputHeight) medianTime := blockNode.CalcPastMedianTime() // 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 }
检查sequence是否满足条件
// 节选自 blockchain/validate.go 的 checkConnectBlock方法 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 := node.parent.CalcPastMedianTime() // 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) } } } // 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 }