diff --git a/blockchain/validate.go b/blockchain/validate.go index c2955ab8..ea83763b 100644 --- a/blockchain/validate.go +++ b/blockchain/validate.go @@ -2245,7 +2245,10 @@ func checkTicketSubmissionInput(ticketUtxo *UtxoEntry) error { // // NOTE: This is only intended to be a helper to refactor out common code from // checkVoteInputs and checkRevocationInputs. -func checkTicketRedeemerCommitments(ticketHash *chainhash.Hash, ticketOuts []*stake.MinimalOutput, msgTx *wire.MsgTx, isVote bool, voteSubsidy int64, isTreasuryEnabled bool) error { +func checkTicketRedeemerCommitments(ticketHash *chainhash.Hash, + ticketOuts []*stake.MinimalOutput, msgTx *wire.MsgTx, isVote bool, + voteSubsidy int64, isTreasuryEnabled, isAutoRevocationsEnabled bool) error { + // Make an initial pass over the ticket commitments to calculate the overall // contribution sum. This is necessary because the output amounts are // required to be scaled to maintain the same proportions as the original @@ -2414,7 +2417,10 @@ func checkTicketRedeemerCommitments(ticketHash *chainhash.Hash, ticketOuts []*st // // NOTE: The caller MUST have already determined that the provided transaction // is a vote. -func checkVoteInputs(subsidyCache *standalone.SubsidyCache, tx *dcrutil.Tx, txHeight int64, view *UtxoViewpoint, params *chaincfg.Params, isTreasuryEnabled bool) error { +func checkVoteInputs(subsidyCache *standalone.SubsidyCache, tx *dcrutil.Tx, + txHeight int64, view *UtxoViewpoint, params *chaincfg.Params, + isTreasuryEnabled, isAutoRevocationsEnabled bool) error { + ticketMaturity := int64(params.TicketMaturity) voteHash := tx.Hash() msgTx := tx.MsgTx() @@ -2523,7 +2529,7 @@ func checkVoteInputs(subsidyCache *standalone.SubsidyCache, tx *dcrutil.Tx, txHe // Ensure the outputs adhere to the ticket commitments. return checkTicketRedeemerCommitments(ticketHash, ticketOuts, msgTx, - true, voteSubsidy, isTreasuryEnabled) + true, voteSubsidy, isTreasuryEnabled, isAutoRevocationsEnabled) } // checkRevocationInputs performs a series of checks on the inputs to a @@ -2533,7 +2539,10 @@ func checkVoteInputs(subsidyCache *standalone.SubsidyCache, tx *dcrutil.Tx, txHe // // NOTE: The caller MUST have already determined that the provided transaction // is a revocation. -func checkRevocationInputs(tx *dcrutil.Tx, txHeight int64, view *UtxoViewpoint, params *chaincfg.Params, isTreasuryEnabled bool) error { +func checkRevocationInputs(tx *dcrutil.Tx, txHeight int64, view *UtxoViewpoint, + params *chaincfg.Params, isTreasuryEnabled, + isAutoRevocationsEnabled bool) error { + ticketMaturity := int64(params.TicketMaturity) revokeHash := tx.Hash() msgTx := tx.MsgTx() @@ -2615,7 +2624,7 @@ func checkRevocationInputs(tx *dcrutil.Tx, txHeight int64, view *UtxoViewpoint, // Ensure the outputs adhere to the ticket commitments. Zero is passed for // the vote subsidy since revocations do not produce any subsidy. return checkTicketRedeemerCommitments(ticketHash, ticketOuts, msgTx, - false, 0, isTreasuryEnabled) + false, 0, isTreasuryEnabled, isAutoRevocationsEnabled) } // CheckTransactionInputs performs a series of checks on the inputs to a @@ -2630,7 +2639,11 @@ func checkRevocationInputs(tx *dcrutil.Tx, txHeight int64, view *UtxoViewpoint, // // NOTE: The transaction MUST have already been sanity checked with the // CheckTransactionSanity function prior to calling this function. -func CheckTransactionInputs(subsidyCache *standalone.SubsidyCache, tx *dcrutil.Tx, txHeight int64, view *UtxoViewpoint, checkFraudProof bool, chainParams *chaincfg.Params, isTreasuryEnabled bool) (int64, error) { +func CheckTransactionInputs(subsidyCache *standalone.SubsidyCache, + tx *dcrutil.Tx, txHeight int64, view *UtxoViewpoint, checkFraudProof bool, + chainParams *chaincfg.Params, isTreasuryEnabled, + isAutoRevocationsEnabled bool) (int64, error) { + // Coinbase transactions have no inputs. msgTx := tx.MsgTx() if standalone.IsCoinBaseTx(msgTx, isTreasuryEnabled) { @@ -2666,7 +2679,7 @@ func CheckTransactionInputs(subsidyCache *standalone.SubsidyCache, tx *dcrutil.T isVote := stake.IsSSGen(msgTx, isTreasuryEnabled) if isVote { err := checkVoteInputs(subsidyCache, tx, txHeight, view, - chainParams, isTreasuryEnabled) + chainParams, isTreasuryEnabled, isAutoRevocationsEnabled) if err != nil { return 0, err } @@ -2681,7 +2694,7 @@ func CheckTransactionInputs(subsidyCache *standalone.SubsidyCache, tx *dcrutil.T isRevocation := stake.IsSSRtx(msgTx) if isRevocation { err := checkRevocationInputs(tx, txHeight, view, chainParams, - isTreasuryEnabled) + isTreasuryEnabled, isAutoRevocationsEnabled) if err != nil { return 0, err } @@ -3257,6 +3270,13 @@ func (b *BlockChain) checkTransactionsAndConnect(inputFees dcrutil.Amount, node return err } + // Determine if the automatic ticket revocations agenda is active as of the + // block being checked. + isAutoRevocationsEnabled, err := b.isAutoRevocationsAgendaActive(node.parent) + if err != nil { + return err + } + // Perform several checks on the inputs for each transaction. Also // accumulate the total fees. This could technically be combined with // the loop above instead of running another loop over the @@ -3280,7 +3300,7 @@ func (b *BlockChain) checkTransactionsAndConnect(inputFees dcrutil.Amount, node // spent, so be aware of this. txFee, err := CheckTransactionInputs(b.subsidyCache, tx, node.height, view, true, /* check fraud proofs */ - b.chainParams, isTreasuryEnabled) + b.chainParams, isTreasuryEnabled, isAutoRevocationsEnabled) if err != nil { log.Tracef("CheckTransactionInputs failed; error "+ "returned: %v", err) diff --git a/internal/mempool/mempool.go b/internal/mempool/mempool.go index 7eadfcf0..9ac5b08d 100644 --- a/internal/mempool/mempool.go +++ b/internal/mempool/mempool.go @@ -1244,6 +1244,7 @@ func (mp *TxPool) maybeAcceptTransaction(tx *dcrutil.Tx, isNew, rateLimit, // Determine active agendas based on flags. isTreasuryEnabled := checkTxFlags.IsTreasuryEnabled() + isAutoRevocationsEnabled := checkTxFlags.IsAutoRevocationsEnabled() // A standalone transaction must not be a coinbase transaction. if standalone.IsCoinBaseTx(msgTx, isTreasuryEnabled) { @@ -1490,7 +1491,7 @@ func (mp *TxPool) maybeAcceptTransaction(tx *dcrutil.Tx, isNew, rateLimit, // filled in by the miner. txFee, err := blockchain.CheckTransactionInputs(mp.cfg.SubsidyCache, tx, nextBlockHeight, utxoView, false, mp.cfg.ChainParams, - isTreasuryEnabled) + isTreasuryEnabled, isAutoRevocationsEnabled) if err != nil { var cerr blockchain.RuleError if errors.As(err, &cerr) { diff --git a/internal/mining/mining.go b/internal/mining/mining.go index e2415ca3..8ace38ac 100644 --- a/internal/mining/mining.go +++ b/internal/mining/mining.go @@ -104,7 +104,9 @@ type Config struct { // CheckTransactionInputs defines the function to use to perform a series of // checks on the inputs to a transaction to ensure they are valid. - CheckTransactionInputs func(tx *dcrutil.Tx, txHeight int64, view *blockchain.UtxoViewpoint, checkFraudProof bool, isTreasuryEnabled bool) (int64, error) + CheckTransactionInputs func(tx *dcrutil.Tx, txHeight int64, + view *blockchain.UtxoViewpoint, checkFraudProof, isTreasuryEnabled, + isAutoRevocationsEnabled bool) (int64, error) // CheckTSpendHasVotes defines the function to use to check whether the given // tspend has enough votes to be included in a block AFTER the specified block. @@ -163,6 +165,11 @@ type Config struct { // treasury agenda is active or not for the block AFTER the given block. IsTreasuryAgendaActive func(prevHash *chainhash.Hash) (bool, error) + // IsAutoRevocationsAgendaActive defines the function to use to determine if + // the automatic ticket revocations agenda is active or not for the block + // AFTER the given block. + IsAutoRevocationsAgendaActive func(prevHash *chainhash.Hash) (bool, error) + // MaxTreasuryExpenditure defines the function to use to get the maximum amount // of funds that can be spent from the treasury by a set of TSpends for a block // that extends the given block hash. The function should return 0 if it is @@ -1062,6 +1069,11 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress stdaddr.Address) (*Bloc return nil, err } + isAutoRevocationsEnabled, err := g.cfg.IsAutoRevocationsAgendaActive(&prevHash) + if err != nil { + return nil, err + } + var ( isTVI bool maxTreasurySpend int64 @@ -1551,7 +1563,7 @@ nextPriorityQueueItem: // The fraud proof is not checked because it will be filled in // by the miner. _, err = g.cfg.CheckTransactionInputs(bundledTx.Tx, nextBlockHeight, - blockUtxos, false, isTreasuryEnabled) + blockUtxos, false, isTreasuryEnabled, isAutoRevocationsEnabled) if err != nil { log.Tracef("Skipping tx %s due to error in "+ "CheckTransactionInputs: %v", bundledTx.Tx.Hash(), err) diff --git a/internal/mining/mining_harness_test.go b/internal/mining/mining_harness_test.go index 9f5dc837..808a23ad 100644 --- a/internal/mining/mining_harness_test.go +++ b/internal/mining/mining_harness_test.go @@ -69,6 +69,8 @@ type fakeChain struct { isHeaderCommitmentsAgendaActiveErr error isTreasuryAgendaActive bool isTreasuryAgendaActiveErr error + isAutoRevocationsAgendaActive bool + isAutoRevocationsAgendaActiveErr error maxTreasuryExpenditure int64 maxTreasuryExpenditureErr error parentUtxos *blockchain.UtxoViewpoint @@ -194,6 +196,13 @@ func (c *fakeChain) IsTreasuryAgendaActive(prevHash *chainhash.Hash) (bool, erro return c.isTreasuryAgendaActive, c.isTreasuryAgendaActiveErr } +// IsAutoRevocationsAgendaActive returns a mocked bool representing whether the +// automatic ticket revocations agenda is active or not for the block AFTER the +// given block. +func (c *fakeChain) IsAutoRevocationsAgendaActive(prevHash *chainhash.Hash) (bool, error) { + return c.isAutoRevocationsAgendaActive, c.isAutoRevocationsAgendaActiveErr +} + // MaxTreasuryExpenditure returns a mocked maximum amount of funds that can be // spent from the treasury by a set of TSpends for a block that extends the // given block hash. @@ -635,6 +644,7 @@ func (p *fakeTxSource) maybeAcceptTransaction(tx *dcrutil.Tx, isNew bool) ([]*ch height := p.chain.BestSnapshot().Height nextHeight := height + 1 isTreasuryEnabled := p.chain.isTreasuryAgendaActive + isAutoRevocationsEnabled := p.chain.isAutoRevocationsAgendaActive // Determine what type of transaction we're dealing with (regular or stake). // Then, be sure to set the tx tree correctly as it's possible a user submitted @@ -698,7 +708,7 @@ func (p *fakeTxSource) maybeAcceptTransaction(tx *dcrutil.Tx, isNew bool) ([]*ch } txFee, err := blockchain.CheckTransactionInputs(p.subsidyCache, tx, nextHeight, - utxoView, false, p.chainParams, isTreasuryEnabled) + utxoView, false, p.chainParams, isTreasuryEnabled, isAutoRevocationsEnabled) if err != nil { return nil, err } @@ -1388,11 +1398,12 @@ func newMiningHarness(chainParams *chaincfg.Params) (*miningHarness, []spendable CheckConnectBlockTemplate: chain.CheckConnectBlockTemplate, CheckTicketExhaustion: chain.CheckTicketExhaustion, CheckTransactionInputs: func(tx *dcrutil.Tx, txHeight int64, - view *blockchain.UtxoViewpoint, checkFraudProof bool, - isTreasuryEnabled bool) (int64, error) { + view *blockchain.UtxoViewpoint, checkFraudProof, isTreasuryEnabled, + isAutoRevocationsEnabled bool) (int64, error) { return blockchain.CheckTransactionInputs(subsidyCache, tx, txHeight, - view, checkFraudProof, chainParams, isTreasuryEnabled) + view, checkFraudProof, chainParams, isTreasuryEnabled, + isAutoRevocationsEnabled) }, CheckTSpendHasVotes: chain.CheckTSpendHasVotes, CountSigOps: blockchain.CountSigOps, @@ -1403,6 +1414,7 @@ func newMiningHarness(chainParams *chaincfg.Params) (*miningHarness, []spendable IsFinalizedTransaction: blockchain.IsFinalizedTransaction, IsHeaderCommitmentsAgendaActive: chain.IsHeaderCommitmentsAgendaActive, IsTreasuryAgendaActive: chain.IsTreasuryAgendaActive, + IsAutoRevocationsAgendaActive: chain.IsAutoRevocationsAgendaActive, MaxTreasuryExpenditure: chain.MaxTreasuryExpenditure, NewUtxoViewpoint: chain.NewUtxoViewpoint, TipGeneration: chain.TipGeneration, diff --git a/server.go b/server.go index 82625548..fe56a7fc 100644 --- a/server.go +++ b/server.go @@ -3560,11 +3560,12 @@ func newServer(ctx context.Context, listenAddrs []string, db database.DB, CheckConnectBlockTemplate: s.chain.CheckConnectBlockTemplate, CheckTicketExhaustion: s.chain.CheckTicketExhaustion, CheckTransactionInputs: func(tx *dcrutil.Tx, txHeight int64, - view *blockchain.UtxoViewpoint, checkFraudProof bool, - isTreasuryEnabled bool) (int64, error) { + view *blockchain.UtxoViewpoint, checkFraudProof, isTreasuryEnabled, + isAutoRevocationsEnabled bool) (int64, error) { return blockchain.CheckTransactionInputs(s.subsidyCache, tx, txHeight, - view, checkFraudProof, s.chainParams, isTreasuryEnabled) + view, checkFraudProof, s.chainParams, isTreasuryEnabled, + isAutoRevocationsEnabled) }, CheckTSpendHasVotes: s.chain.CheckTSpendHasVotes, CountSigOps: blockchain.CountSigOps, @@ -3575,6 +3576,7 @@ func newServer(ctx context.Context, listenAddrs []string, db database.DB, IsFinalizedTransaction: blockchain.IsFinalizedTransaction, IsHeaderCommitmentsAgendaActive: s.chain.IsHeaderCommitmentsAgendaActive, IsTreasuryAgendaActive: s.chain.IsTreasuryAgendaActive, + IsAutoRevocationsAgendaActive: s.chain.IsAutoRevocationsAgendaActive, MaxTreasuryExpenditure: s.chain.MaxTreasuryExpenditure, NewUtxoViewpoint: func() *blockchain.UtxoViewpoint { return blockchain.NewUtxoViewpoint(utxoCache)