Published: 2018-12-17

BIP68(nSequence)

1 BIP地址

BIP65

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 细节

  1. MsgTx.Version 要>=2
  2. 用到了bip113(TODO add link)里定义的median-time-past(MTP)
  3. 由于sequence字段是32位的, 1<<31的bit(Disable Flag), 用来表示是否禁用该bip定义的relative lock time语义
  4. 1<<22的bit(Type Flag), 用来区分该relative lock-time是基于时间的还是区块的
  5. 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
  6. 1<<22的bit为0,那么该定义的relative lock time是基于Block数量的, 如果该值为0, 则表示该input可以被包含到任意block。 如果该值为n, 那么表示可以被包含到, TxIn.PreviousOutPoint.Hash 指代的transaction所在的Block的n个blocks之后的blocks
  7. BIP65仅定义了sequence32位中的16位,所以计算relative lock time时需要 0x0000ffff 掩码,16位保证了大概 (2^16-1)*512 / 2600 / 24 =388.35days 一年的时间

4 其它

  1. 采用BIP9定义的"versionbits"的bit 0来部署
  2. Bitcoin主网,开始时间为midnight 1st May 2016 UTC (Epoch timestamp 1462060800), timeout时间为midnight 1st May 2017 UTC (Epoch timestamp 1493596800).
  3. 需要和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
}

6 END

Author: Nisen

Email: imnisen@163.com