From 636d94a3dc97d65e774efe35236920586ab3ed8e Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Thu, 21 Sep 2017 12:53:34 -0500 Subject: [PATCH] mempool: Enforce relative sequence locks. This adds enforcement of the newly introduced relative time locks via transaction sequence numbers when considering candidate transactions for acceptance to the mempool, relaying, and inclusion into block templates. It also raises the maximum standard transaction version to 2 accordingly. This is acceptable because it is a soft forking change and the aforementioned areas only enforce policy. The following is an overview of the changes: - Modify mempool to consider version 2 transactions standard - Introduce a new callback to the mempool config for obtaining the sequence lock for block after the current best chain tip so it can remain decoupled from the chain - Update mempool to enforce relative locks on all candidate transactions - Update the mock chain in the mempool test harness accordingly --- mempool/mempool.go | 22 +++++++++++++++++++++- mempool/mempool_test.go | 10 ++++++++++ server.go | 21 +++++++++++---------- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/mempool/mempool.go b/mempool/mempool.go index 1d9302e1..91e428ff 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -113,6 +113,11 @@ type Config struct { // tip within the best chain. PastMedianTime func() time.Time + // CalcSequenceLock defines the function to use in order to generate + // the current sequence lock for the given transaction using the passed + // utxo view. + CalcSequenceLock func(*dcrutil.Tx, *blockchain.UtxoViewpoint) (*blockchain.SequenceLock, error) + // SubsidyCache defines a subsidy cache to use. SubsidyCache *blockchain.SubsidyCache @@ -831,8 +836,8 @@ func (mp *TxPool) maybeAcceptTransaction(tx *dcrutil.Tx, isNew, rateLimit, allow // Don't allow non-standard transactions if the network parameters // forbid their relaying. + medianTime := mp.cfg.PastMedianTime() if !mp.cfg.Policy.RelayNonStd { - medianTime := mp.cfg.PastMedianTime() err := checkTransactionStandard(tx, txType, nextBlockHeight, medianTime, mp.cfg.Policy.MinRelayTxFee, mp.cfg.Policy.MaxTxVersion) @@ -991,6 +996,21 @@ func (mp *TxPool) maybeAcceptTransaction(tx *dcrutil.Tx, isNew, rateLimit, allow return missingParents, nil } + // Don't allow the transaction into the mempool unless its sequence + // lock is active, meaning that it'll be allowed into the next block + // with respect to its defined relative lock times. + seqLock, err := mp.cfg.CalcSequenceLock(tx, utxoView) + if err != nil { + if cerr, ok := err.(blockchain.RuleError); ok { + return nil, chainRuleError(cerr) + } + return nil, err + } + if !blockchain.SequenceLockActive(seqLock, nextBlockHeight, medianTime) { + return nil, txRuleError(wire.RejectNonstandard, + "transaction sequence locks on inputs not met") + } + // Perform several checks on the transaction inputs using the invariant // rules in chain for what transactions are allowed into blocks. // Also returns the fees associated with the transaction which will be diff --git a/mempool/mempool_test.go b/mempool/mempool_test.go index f9287985..08ca17c2 100644 --- a/mempool/mempool_test.go +++ b/mempool/mempool_test.go @@ -152,6 +152,15 @@ func (s *fakeChain) SetPastMedianTime(medianTime time.Time) { s.Unlock() } +// CalcSequenceLock returns the current sequence lock for the passed transaction +// associated with the fake chain instance. +func (s *fakeChain) CalcSequenceLock(tx *dcrutil.Tx, view *blockchain.UtxoViewpoint) (*blockchain.SequenceLock, error) { + return &blockchain.SequenceLock{ + MinHeight: -1, + MinTime: -1, + }, nil +} + // StandardVerifyFlags returns the standard verification script flags associated // with the fake chain instance. func (s *fakeChain) StandardVerifyFlags() (txscript.ScriptFlags, error) { @@ -389,6 +398,7 @@ func newPoolHarness(chainParams *chaincfg.Params) (*poolHarness, []spendableOutp BestHash: chain.BestHash, BestHeight: chain.BestHeight, PastMedianTime: chain.PastMedianTime, + CalcSequenceLock: chain.CalcSequenceLock, SubsidyCache: subsidyCache, SigCache: nil, AddrIndex: nil, diff --git a/server.go b/server.go index c162f4a3..2b18e2e5 100644 --- a/server.go +++ b/server.go @@ -2420,7 +2420,7 @@ func newServer(listenAddrs []string, db database.DB, chainParams *chaincfg.Param txC := mempool.Config{ Policy: mempool.Policy{ - MaxTxVersion: 1, + MaxTxVersion: 2, DisableRelayPriority: cfg.NoRelayPriority, RelayNonStd: cfg.RelayNonStd, FreeTxRelayLimit: cfg.FreeTxRelayLimit, @@ -2440,15 +2440,16 @@ func newServer(listenAddrs []string, db database.DB, chainParams *chaincfg.Param bm.chainState.Unlock() return sDiff, nil }, - FetchUtxoView: bm.chain.FetchUtxoView, - BlockByHash: bm.chain.BlockByHash, - BestHash: func() *chainhash.Hash { return bm.chain.BestSnapshot().Hash }, - BestHeight: func() int64 { return bm.chain.BestSnapshot().Height }, - SubsidyCache: bm.chain.FetchSubsidyCache(), - SigCache: s.sigCache, - PastMedianTime: func() time.Time { return bm.chain.BestSnapshot().MedianTime }, - AddrIndex: s.addrIndex, - ExistsAddrIndex: s.existsAddrIndex, + FetchUtxoView: bm.chain.FetchUtxoView, + BlockByHash: bm.chain.BlockByHash, + BestHash: func() *chainhash.Hash { return bm.chain.BestSnapshot().Hash }, + BestHeight: func() int64 { return bm.chain.BestSnapshot().Height }, + CalcSequenceLock: bm.chain.CalcSequenceLock, + SubsidyCache: bm.chain.FetchSubsidyCache(), + SigCache: s.sigCache, + PastMedianTime: func() time.Time { return bm.chain.BestSnapshot().MedianTime }, + AddrIndex: s.addrIndex, + ExistsAddrIndex: s.existsAddrIndex, } s.txMemPool = mempool.New(&txC)