[rpc] Return fee and vsize from testmempoolaccept

Return fee and vsize if tx would pass ATMP.
This commit is contained in:
codeShark149 2020-05-24 18:28:56 +05:30 committed by gzhao408
parent 9366a73d69
commit 2233a93a10
6 changed files with 43 additions and 15 deletions

View file

@ -102,6 +102,9 @@ will trigger BIP 125 (replace-by-fee) opt-in. (#11413)
option `-deprecatedrpc=banscore` is used. The `banscore` field will be fully option `-deprecatedrpc=banscore` is used. The `banscore` field will be fully
removed in the next major release. (#19469) removed in the next major release. (#19469)
- The `testmempoolaccept` RPC returns `vsize` and a `fee` object with the `base` fee
if the transaction passes validation. (#19940)
- The `walletcreatefundedpsbt` RPC call will now fail with - The `walletcreatefundedpsbt` RPC call will now fail with
`Insufficient funds` when inputs are manually selected but are not enough to cover `Insufficient funds` when inputs are manually selected but are not enough to cover
the outputs and fee. Additional inputs can automatically be added through the the outputs and fee. Additional inputs can automatically be added through the

View file

@ -878,6 +878,11 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request)
{ {
{RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"}, {RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"},
{RPCResult::Type::BOOL, "allowed", "If the mempool allows this tx to be inserted"}, {RPCResult::Type::BOOL, "allowed", "If the mempool allows this tx to be inserted"},
{RPCResult::Type::NUM, "vsize", "Virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted (only present when 'allowed' is true)"},
{RPCResult::Type::OBJ, "fees", "Transaction fees (only present if 'allowed' is true)",
{
{RPCResult::Type::STR_AMOUNT, "base", "transaction fee in " + CURRENCY_UNIT},
}},
{RPCResult::Type::STR, "reject-reason", "Rejection string (only present when 'allowed' is false)"}, {RPCResult::Type::STR, "reject-reason", "Rejection string (only present when 'allowed' is false)"},
}}, }},
} }
@ -924,13 +929,22 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request)
TxValidationState state; TxValidationState state;
bool test_accept_res; bool test_accept_res;
CAmount fee;
{ {
LOCK(cs_main); LOCK(cs_main);
test_accept_res = AcceptToMemoryPool(mempool, state, std::move(tx), test_accept_res = AcceptToMemoryPool(mempool, state, std::move(tx),
nullptr /* plTxnReplaced */, false /* bypass_limits */, max_raw_tx_fee, /* test_accept */ true); nullptr /* plTxnReplaced */, false /* bypass_limits */, max_raw_tx_fee, /* test_accept */ true, &fee);
} }
result_0.pushKV("allowed", test_accept_res); result_0.pushKV("allowed", test_accept_res);
if (!test_accept_res) {
// Only return the fee and vsize if the transaction would pass ATMP.
// These can be used to calculate the feerate.
if (test_accept_res) {
result_0.pushKV("vsize", virtual_size);
UniValue fees(UniValue::VOBJ);
fees.pushKV("base", ValueFromAmount(fee));
result_0.pushKV("fees", fees);
} else {
if (state.IsInvalid()) { if (state.IsInvalid()) {
if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) { if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) {
result_0.pushKV("reject-reason", "missing-inputs"); result_0.pushKV("reject-reason", "missing-inputs");

View file

@ -475,6 +475,7 @@ public:
*/ */
std::vector<COutPoint>& m_coins_to_uncache; std::vector<COutPoint>& m_coins_to_uncache;
const bool m_test_accept; const bool m_test_accept;
CAmount* m_fee_out;
}; };
// Single transaction acceptance // Single transaction acceptance
@ -687,6 +688,11 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
return false; // state filled in by CheckTxInputs return false; // state filled in by CheckTxInputs
} }
// If fee_out is passed, return the fee to the caller
if (args.m_fee_out) {
*args.m_fee_out = nFees;
}
// Check for non-standard pay-to-script-hash in inputs // Check for non-standard pay-to-script-hash in inputs
if (fRequireStandard && !AreInputsStandard(tx, m_view)) { if (fRequireStandard && !AreInputsStandard(tx, m_view)) {
return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs"); return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs");
@ -1061,10 +1067,10 @@ bool MemPoolAccept::AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs
/** (try to) add transaction to memory pool with a specified acceptance time **/ /** (try to) add transaction to memory pool with a specified acceptance time **/
static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPool& pool, TxValidationState &state, const CTransactionRef &tx, static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPool& pool, TxValidationState &state, const CTransactionRef &tx,
int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced, int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced,
bool bypass_limits, const CAmount nAbsurdFee, bool test_accept) EXCLUSIVE_LOCKS_REQUIRED(cs_main) bool bypass_limits, const CAmount nAbsurdFee, bool test_accept, CAmount* fee_out=nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{ {
std::vector<COutPoint> coins_to_uncache; std::vector<COutPoint> coins_to_uncache;
MemPoolAccept::ATMPArgs args { chainparams, state, nAcceptTime, plTxnReplaced, bypass_limits, nAbsurdFee, coins_to_uncache, test_accept }; MemPoolAccept::ATMPArgs args { chainparams, state, nAcceptTime, plTxnReplaced, bypass_limits, nAbsurdFee, coins_to_uncache, test_accept, fee_out };
bool res = MemPoolAccept(pool).AcceptSingleTransaction(tx, args); bool res = MemPoolAccept(pool).AcceptSingleTransaction(tx, args);
if (!res) { if (!res) {
// Remove coins that were not present in the coins cache before calling ATMPW; // Remove coins that were not present in the coins cache before calling ATMPW;
@ -1083,10 +1089,10 @@ static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPo
bool AcceptToMemoryPool(CTxMemPool& pool, TxValidationState &state, const CTransactionRef &tx, bool AcceptToMemoryPool(CTxMemPool& pool, TxValidationState &state, const CTransactionRef &tx,
std::list<CTransactionRef>* plTxnReplaced, std::list<CTransactionRef>* plTxnReplaced,
bool bypass_limits, const CAmount nAbsurdFee, bool test_accept) bool bypass_limits, const CAmount nAbsurdFee, bool test_accept, CAmount* fee_out)
{ {
const CChainParams& chainparams = Params(); const CChainParams& chainparams = Params();
return AcceptToMemoryPoolWithTime(chainparams, pool, state, tx, GetTime(), plTxnReplaced, bypass_limits, nAbsurdFee, test_accept); return AcceptToMemoryPoolWithTime(chainparams, pool, state, tx, GetTime(), plTxnReplaced, bypass_limits, nAbsurdFee, test_accept, fee_out);
} }
CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool, const uint256& hash, const Consensus::Params& consensusParams, uint256& hashBlock) CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool, const uint256& hash, const Consensus::Params& consensusParams, uint256& hashBlock)

View file

@ -199,10 +199,11 @@ void UnlinkPrunedFiles(const std::set<int>& setFilesToPrune);
void PruneBlockFilesManual(int nManualPruneHeight); void PruneBlockFilesManual(int nManualPruneHeight);
/** (try to) add transaction to memory pool /** (try to) add transaction to memory pool
* plTxnReplaced will be appended to with all transactions replaced from mempool **/ * plTxnReplaced will be appended to with all transactions replaced from mempool
* @param[out] fee_out optional argument to return tx fee to the caller **/
bool AcceptToMemoryPool(CTxMemPool& pool, TxValidationState &state, const CTransactionRef &tx, bool AcceptToMemoryPool(CTxMemPool& pool, TxValidationState &state, const CTransactionRef &tx,
std::list<CTransactionRef>* plTxnReplaced, std::list<CTransactionRef>* plTxnReplaced,
bool bypass_limits, const CAmount nAbsurdFee, bool test_accept=false) EXCLUSIVE_LOCKS_REQUIRED(cs_main); bool bypass_limits, const CAmount nAbsurdFee, bool test_accept=false, CAmount* fee_out=nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/** Get the BIP9 state for a given deployment at the current tip. */ /** Get the BIP9 state for a given deployment at the current tip. */
ThresholdState VersionBitsTipState(const Consensus::Params& params, Consensus::DeploymentPos pos); ThresholdState VersionBitsTipState(const Consensus::Params& params, Consensus::DeploymentPos pos);

View file

@ -4,6 +4,7 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php. # file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test mempool acceptance of raw transactions.""" """Test mempool acceptance of raw transactions."""
from decimal import Decimal
from io import BytesIO from io import BytesIO
import math import math
@ -91,20 +92,22 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0)))
txid_0 = tx.rehash() txid_0 = tx.rehash()
self.check_mempool_result( self.check_mempool_result(
result_expected=[{'txid': txid_0, 'allowed': True}], result_expected=[{'txid': txid_0, 'allowed': True, 'vsize': 110, 'fees': {'base': Decimal(str(fee))}}],
rawtxs=[raw_tx_0], rawtxs=[raw_tx_0],
) )
self.log.info('A final transaction not in the mempool') self.log.info('A final transaction not in the mempool')
coin = coins.pop() # Pick a random coin(base) to spend coin = coins.pop() # Pick a random coin(base) to spend
output_amount = 0.025
raw_tx_final = node.signrawtransactionwithwallet(node.createrawtransaction( raw_tx_final = node.signrawtransactionwithwallet(node.createrawtransaction(
inputs=[{'txid': coin['txid'], 'vout': coin['vout'], "sequence": 0xffffffff}], # SEQUENCE_FINAL inputs=[{'txid': coin['txid'], 'vout': coin['vout'], "sequence": 0xffffffff}], # SEQUENCE_FINAL
outputs=[{node.getnewaddress(): 0.025}], outputs=[{node.getnewaddress(): output_amount}],
locktime=node.getblockcount() + 2000, # Can be anything locktime=node.getblockcount() + 2000, # Can be anything
))['hex'] ))['hex']
tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_final))) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_final)))
fee_expected = int(coin['amount']) - output_amount
self.check_mempool_result( self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': True}], result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': 188, 'fees': {'base': Decimal(str(fee_expected))}}],
rawtxs=[tx.serialize().hex()], rawtxs=[tx.serialize().hex()],
maxfeerate=0, maxfeerate=0,
) )
@ -127,7 +130,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0)))
txid_0 = tx.rehash() txid_0 = tx.rehash()
self.check_mempool_result( self.check_mempool_result(
result_expected=[{'txid': txid_0, 'allowed': True}], result_expected=[{'txid': txid_0, 'allowed': True, 'vsize': 110, 'fees': {'base': Decimal(str(2 * fee))}}],
rawtxs=[raw_tx_0], rawtxs=[raw_tx_0],
) )
@ -187,7 +190,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
# Reference tx should be valid on itself # Reference tx should be valid on itself
self.check_mempool_result( self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': True}], result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': 110, 'fees': { 'base': Decimal(str(0.1 - 0.05))}}],
rawtxs=[tx.serialize().hex()], rawtxs=[tx.serialize().hex()],
maxfeerate=0, maxfeerate=0,
) )

View file

@ -3,6 +3,7 @@
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php. # file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test segwit transactions and blocks on P2P network.""" """Test segwit transactions and blocks on P2P network."""
from decimal import Decimal
import math import math
import random import random
import struct import struct
@ -695,13 +696,13 @@ class SegWitTest(BitcoinTestFramework):
if not self.segwit_active: if not self.segwit_active:
# Just check mempool acceptance, but don't add the transaction to the mempool, since witness is disallowed # Just check mempool acceptance, but don't add the transaction to the mempool, since witness is disallowed
# in blocks and the tx is impossible to mine right now. # in blocks and the tx is impossible to mine right now.
assert_equal(self.nodes[0].testmempoolaccept([tx3.serialize_with_witness().hex()]), [{'txid': tx3.hash, 'allowed': True}]) assert_equal(self.nodes[0].testmempoolaccept([tx3.serialize_with_witness().hex()]), [{'txid': tx3.hash, 'allowed': True, 'vsize': 93, 'fees': { 'base': Decimal('0.00001000')}}])
# Create the same output as tx3, but by replacing tx # Create the same output as tx3, but by replacing tx
tx3_out = tx3.vout[0] tx3_out = tx3.vout[0]
tx3 = tx tx3 = tx
tx3.vout = [tx3_out] tx3.vout = [tx3_out]
tx3.rehash() tx3.rehash()
assert_equal(self.nodes[0].testmempoolaccept([tx3.serialize_with_witness().hex()]), [{'txid': tx3.hash, 'allowed': True}]) assert_equal(self.nodes[0].testmempoolaccept([tx3.serialize_with_witness().hex()]), [{'txid': tx3.hash, 'allowed': True, 'vsize': 93, 'fees': { 'base': Decimal('0.00011000')}}])
test_transaction_acceptance(self.nodes[0], self.test_node, tx3, with_witness=True, accepted=True) test_transaction_acceptance(self.nodes[0], self.test_node, tx3, with_witness=True, accepted=True)
self.nodes[0].generate(1) self.nodes[0].generate(1)