0
0
Fork 0
mirror of https://github.com/bitcoin/bitcoin.git synced 2025-02-02 09:46:52 -05:00

Merge #18772: rpc: calculate fees in getblock using BlockUndo data

66d012ad7f test: RPC: getblock fee calculations (Elliott Jin)
bf7d6e31b1 RPC: getblock: tx fee calculation for verbosity 2 via Undo data (Elliott Jin)

Pull request description:

  This change is progress towards #18771 .  It adapts the fee calculation part of #16083 and addresses some feedback.  The additional "verbosity level 3" features are planned for a future PR.

  **Original PR description:**

  > Using block undo data (like in #14802) we can now show fee information for each transaction in a block without the need for additional -txindex and/or a ton of costly lookups. For a start we'll add transaction fee information to getblock verbosity level 2. This comes at a negligible speed penalty (<1%).

ACKs for top commit:
  luke-jr:
    tACK 66d012ad7f
  fjahr:
    tACK 66d012ad7f
  MarcoFalke:
    review ACK 66d012ad7f 🗜

Tree-SHA512: be1fe4b866946a8dc36427f7dc72a20e10860e320a28fa49bc85bd2a93a0d699768179be29fa52e18b2ed8505d3ec272e586753ef2239b4230e0aefd233acaa2
This commit is contained in:
MarcoFalke 2020-12-24 15:32:02 +01:00
commit f656165e9c
No known key found for this signature in database
GPG key ID: D2EA4850E7528B25
4 changed files with 85 additions and 11 deletions

View file

@ -18,6 +18,7 @@ class CTransaction;
struct CMutableTransaction;
class uint256;
class UniValue;
class CTxUndo;
// core_read.cpp
CScript ParseScript(const std::string& s);
@ -45,6 +46,6 @@ std::string EncodeHexTx(const CTransaction& tx, const int serializeFlags = 0);
std::string SighashToStr(unsigned char sighash_type);
void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex);
void ScriptToUniv(const CScript& script, UniValue& out, bool include_address);
void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex = true, int serialize_flags = 0);
void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex = true, int serialize_flags = 0, const CTxUndo* txundo = nullptr);
#endif // BITCOIN_CORE_IO_H

View file

@ -11,7 +11,9 @@
#include <script/standard.h>
#include <serialize.h>
#include <streams.h>
#include <undo.h>
#include <univalue.h>
#include <util/check.h>
#include <util/system.h>
#include <util/strencodings.h>
@ -177,7 +179,7 @@ void ScriptPubKeyToUniv(const CScript& scriptPubKey,
out.pushKV("addresses", a);
}
void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex, int serialize_flags)
void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex, int serialize_flags, const CTxUndo* txundo)
{
entry.pushKV("txid", tx.GetHash().GetHex());
entry.pushKV("hash", tx.GetWitnessHash().GetHex());
@ -189,13 +191,20 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
entry.pushKV("weight", GetTransactionWeight(tx));
entry.pushKV("locktime", (int64_t)tx.nLockTime);
UniValue vin(UniValue::VARR);
UniValue vin{UniValue::VARR};
// If available, use Undo data to calculate the fee. Note that txundo == nullptr
// for coinbase transactions and for transactions where undo data is unavailable.
const bool calculate_fee = txundo != nullptr;
CAmount amt_total_in = 0;
CAmount amt_total_out = 0;
for (unsigned int i = 0; i < tx.vin.size(); i++) {
const CTxIn& txin = tx.vin[i];
UniValue in(UniValue::VOBJ);
if (tx.IsCoinBase())
if (tx.IsCoinBase()) {
in.pushKV("coinbase", HexStr(txin.scriptSig));
else {
} else {
in.pushKV("txid", txin.prevout.hash.GetHex());
in.pushKV("vout", (int64_t)txin.prevout.n);
UniValue o(UniValue::VOBJ);
@ -210,6 +219,10 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
}
in.pushKV("txinwitness", txinwitness);
}
if (calculate_fee) {
const CTxOut& prev_txout = txundo->vprevout[i].out;
amt_total_in += prev_txout.nValue;
}
in.pushKV("sequence", (int64_t)txin.nSequence);
vin.push_back(in);
}
@ -228,9 +241,19 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
ScriptPubKeyToUniv(txout.scriptPubKey, o, true);
out.pushKV("scriptPubKey", o);
vout.push_back(out);
if (calculate_fee) {
amt_total_out += txout.nValue;
}
}
entry.pushKV("vout", vout);
if (calculate_fee) {
const CAmount fee = amt_total_in - amt_total_out;
CHECK_NONFATAL(MoneyRange(fee));
entry.pushKV("fee", ValueFromAmount(fee));
}
if (!hashBlock.IsNull())
entry.pushKV("blockhash", hashBlock.GetHex());

View file

@ -172,16 +172,21 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIn
result.pushKV("versionHex", strprintf("%08x", block.nVersion));
result.pushKV("merkleroot", block.hashMerkleRoot.GetHex());
UniValue txs(UniValue::VARR);
for(const auto& tx : block.vtx)
{
if(txDetails)
{
if (txDetails) {
CBlockUndo blockUndo;
const bool have_undo = !IsBlockPruned(blockindex) && UndoReadFromDisk(blockUndo, blockindex);
for (size_t i = 0; i < block.vtx.size(); ++i) {
const CTransactionRef& tx = block.vtx.at(i);
// coinbase transaction (i == 0) doesn't have undo data
const CTxUndo* txundo = (have_undo && i) ? &blockUndo.vtxundo.at(i - 1) : nullptr;
UniValue objTx(UniValue::VOBJ);
TxToUniv(*tx, uint256(), objTx, true, RPCSerializationFlags());
TxToUniv(*tx, uint256(), objTx, true, RPCSerializationFlags(), txundo);
txs.push_back(objTx);
}
else
} else {
for (const CTransactionRef& tx : block.vtx) {
txs.push_back(tx->GetHash().GetHex());
}
}
result.pushKV("tx", txs);
result.pushKV("time", block.GetBlockTime());
@ -936,6 +941,7 @@ static RPCHelpMan getblock()
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::ELISION, "", "The transactions in the format of the getrawtransaction RPC. Different from verbosity = 1 \"tx\" result"},
{RPCResult::Type::NUM, "fee", "The transaction fee in " + CURRENCY_UNIT + ", omitted if block undo data is not available"},
}},
}},
}},

View file

@ -20,6 +20,7 @@ Tests correspond to code in rpc/blockchain.cpp.
from decimal import Decimal
import http.client
import os
import subprocess
from test_framework.blocktools import (
@ -42,7 +43,9 @@ from test_framework.util import (
assert_raises_rpc_error,
assert_is_hex_string,
assert_is_hash_string,
get_datadir_path,
)
from test_framework.wallet import MiniWallet
class BlockchainTest(BitcoinTestFramework):
@ -63,6 +66,7 @@ class BlockchainTest(BitcoinTestFramework):
self._test_getnetworkhashps()
self._test_stopatheight()
self._test_waitforblockheight()
self._test_getblock()
assert self.nodes[0].verifychain(4, 0)
def mine_chain(self):
@ -364,6 +368,46 @@ class BlockchainTest(BitcoinTestFramework):
assert_waitforheight(current_height)
assert_waitforheight(current_height + 1)
def _test_getblock(self):
node = self.nodes[0]
miniwallet = MiniWallet(node)
miniwallet.generate(5)
node.generate(100)
fee_per_byte = Decimal('0.00000010')
fee_per_kb = 1000 * fee_per_byte
miniwallet.send_self_transfer(fee_rate=fee_per_kb, from_node=node)
blockhash = node.generate(1)[0]
self.log.info("Test that getblock with verbosity 1 doesn't include fee")
block = node.getblock(blockhash, 1)
assert 'fee' not in block['tx'][1]
self.log.info('Test that getblock with verbosity 2 includes expected fee')
block = node.getblock(blockhash, 2)
tx = block['tx'][1]
assert 'fee' in tx
assert_equal(tx['fee'], tx['vsize'] * fee_per_byte)
self.log.info("Test that getblock with verbosity 2 still works with pruned Undo data")
datadir = get_datadir_path(self.options.tmpdir, 0)
def move_block_file(old, new):
old_path = os.path.join(datadir, self.chain, 'blocks', old)
new_path = os.path.join(datadir, self.chain, 'blocks', new)
os.rename(old_path, new_path)
# Move instead of deleting so we can restore chain state afterwards
move_block_file('rev00000.dat', 'rev_wrong')
block = node.getblock(blockhash, 2)
assert 'fee' not in block['tx'][1]
# Restore chain state
move_block_file('rev_wrong', 'rev00000.dat')
if __name__ == '__main__':
BlockchainTest().main()