Skip to content

Commit f656165

Browse files
author
MarcoFalke
committed
Merge #18772: rpc: calculate fees in getblock using BlockUndo data
66d012a test: RPC: getblock fee calculations (Elliott Jin) bf7d6e3 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 66d012a fjahr: tACK 66d012a MarcoFalke: review ACK 66d012a 🗜 Tree-SHA512: be1fe4b866946a8dc36427f7dc72a20e10860e320a28fa49bc85bd2a93a0d699768179be29fa52e18b2ed8505d3ec272e586753ef2239b4230e0aefd233acaa2
2 parents cc592a8 + 66d012a commit f656165

File tree

4 files changed

+85
-11
lines changed

4 files changed

+85
-11
lines changed

src/core_io.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class CTransaction;
1818
struct CMutableTransaction;
1919
class uint256;
2020
class UniValue;
21+
class CTxUndo;
2122

2223
// core_read.cpp
2324
CScript ParseScript(const std::string& s);
@@ -45,6 +46,6 @@ std::string EncodeHexTx(const CTransaction& tx, const int serializeFlags = 0);
4546
std::string SighashToStr(unsigned char sighash_type);
4647
void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex);
4748
void ScriptToUniv(const CScript& script, UniValue& out, bool include_address);
48-
void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex = true, int serialize_flags = 0);
49+
void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex = true, int serialize_flags = 0, const CTxUndo* txundo = nullptr);
4950

5051
#endif // BITCOIN_CORE_IO_H

src/core_write.cpp

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
#include <script/standard.h>
1212
#include <serialize.h>
1313
#include <streams.h>
14+
#include <undo.h>
1415
#include <univalue.h>
16+
#include <util/check.h>
1517
#include <util/system.h>
1618
#include <util/strencodings.h>
1719

@@ -177,7 +179,7 @@ void ScriptPubKeyToUniv(const CScript& scriptPubKey,
177179
out.pushKV("addresses", a);
178180
}
179181

180-
void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex, int serialize_flags)
182+
void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex, int serialize_flags, const CTxUndo* txundo)
181183
{
182184
entry.pushKV("txid", tx.GetHash().GetHex());
183185
entry.pushKV("hash", tx.GetWitnessHash().GetHex());
@@ -189,13 +191,20 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
189191
entry.pushKV("weight", GetTransactionWeight(tx));
190192
entry.pushKV("locktime", (int64_t)tx.nLockTime);
191193

192-
UniValue vin(UniValue::VARR);
194+
UniValue vin{UniValue::VARR};
195+
196+
// If available, use Undo data to calculate the fee. Note that txundo == nullptr
197+
// for coinbase transactions and for transactions where undo data is unavailable.
198+
const bool calculate_fee = txundo != nullptr;
199+
CAmount amt_total_in = 0;
200+
CAmount amt_total_out = 0;
201+
193202
for (unsigned int i = 0; i < tx.vin.size(); i++) {
194203
const CTxIn& txin = tx.vin[i];
195204
UniValue in(UniValue::VOBJ);
196-
if (tx.IsCoinBase())
205+
if (tx.IsCoinBase()) {
197206
in.pushKV("coinbase", HexStr(txin.scriptSig));
198-
else {
207+
} else {
199208
in.pushKV("txid", txin.prevout.hash.GetHex());
200209
in.pushKV("vout", (int64_t)txin.prevout.n);
201210
UniValue o(UniValue::VOBJ);
@@ -210,6 +219,10 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
210219
}
211220
in.pushKV("txinwitness", txinwitness);
212221
}
222+
if (calculate_fee) {
223+
const CTxOut& prev_txout = txundo->vprevout[i].out;
224+
amt_total_in += prev_txout.nValue;
225+
}
213226
in.pushKV("sequence", (int64_t)txin.nSequence);
214227
vin.push_back(in);
215228
}
@@ -228,9 +241,19 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
228241
ScriptPubKeyToUniv(txout.scriptPubKey, o, true);
229242
out.pushKV("scriptPubKey", o);
230243
vout.push_back(out);
244+
245+
if (calculate_fee) {
246+
amt_total_out += txout.nValue;
247+
}
231248
}
232249
entry.pushKV("vout", vout);
233250

251+
if (calculate_fee) {
252+
const CAmount fee = amt_total_in - amt_total_out;
253+
CHECK_NONFATAL(MoneyRange(fee));
254+
entry.pushKV("fee", ValueFromAmount(fee));
255+
}
256+
234257
if (!hashBlock.IsNull())
235258
entry.pushKV("blockhash", hashBlock.GetHex());
236259

src/rpc/blockchain.cpp

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -172,16 +172,21 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIn
172172
result.pushKV("versionHex", strprintf("%08x", block.nVersion));
173173
result.pushKV("merkleroot", block.hashMerkleRoot.GetHex());
174174
UniValue txs(UniValue::VARR);
175-
for(const auto& tx : block.vtx)
176-
{
177-
if(txDetails)
178-
{
175+
if (txDetails) {
176+
CBlockUndo blockUndo;
177+
const bool have_undo = !IsBlockPruned(blockindex) && UndoReadFromDisk(blockUndo, blockindex);
178+
for (size_t i = 0; i < block.vtx.size(); ++i) {
179+
const CTransactionRef& tx = block.vtx.at(i);
180+
// coinbase transaction (i == 0) doesn't have undo data
181+
const CTxUndo* txundo = (have_undo && i) ? &blockUndo.vtxundo.at(i - 1) : nullptr;
179182
UniValue objTx(UniValue::VOBJ);
180-
TxToUniv(*tx, uint256(), objTx, true, RPCSerializationFlags());
183+
TxToUniv(*tx, uint256(), objTx, true, RPCSerializationFlags(), txundo);
181184
txs.push_back(objTx);
182185
}
183-
else
186+
} else {
187+
for (const CTransactionRef& tx : block.vtx) {
184188
txs.push_back(tx->GetHash().GetHex());
189+
}
185190
}
186191
result.pushKV("tx", txs);
187192
result.pushKV("time", block.GetBlockTime());
@@ -936,6 +941,7 @@ static RPCHelpMan getblock()
936941
{RPCResult::Type::OBJ, "", "",
937942
{
938943
{RPCResult::Type::ELISION, "", "The transactions in the format of the getrawtransaction RPC. Different from verbosity = 1 \"tx\" result"},
944+
{RPCResult::Type::NUM, "fee", "The transaction fee in " + CURRENCY_UNIT + ", omitted if block undo data is not available"},
939945
}},
940946
}},
941947
}},

test/functional/rpc_blockchain.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
from decimal import Decimal
2222
import http.client
23+
import os
2324
import subprocess
2425

2526
from test_framework.blocktools import (
@@ -42,7 +43,9 @@
4243
assert_raises_rpc_error,
4344
assert_is_hex_string,
4445
assert_is_hash_string,
46+
get_datadir_path,
4547
)
48+
from test_framework.wallet import MiniWallet
4649

4750

4851
class BlockchainTest(BitcoinTestFramework):
@@ -63,6 +66,7 @@ def run_test(self):
6366
self._test_getnetworkhashps()
6467
self._test_stopatheight()
6568
self._test_waitforblockheight()
69+
self._test_getblock()
6670
assert self.nodes[0].verifychain(4, 0)
6771

6872
def mine_chain(self):
@@ -364,6 +368,46 @@ def assert_waitforheight(height, timeout=2):
364368
assert_waitforheight(current_height)
365369
assert_waitforheight(current_height + 1)
366370

371+
def _test_getblock(self):
372+
node = self.nodes[0]
373+
374+
miniwallet = MiniWallet(node)
375+
miniwallet.generate(5)
376+
node.generate(100)
377+
378+
fee_per_byte = Decimal('0.00000010')
379+
fee_per_kb = 1000 * fee_per_byte
380+
381+
miniwallet.send_self_transfer(fee_rate=fee_per_kb, from_node=node)
382+
blockhash = node.generate(1)[0]
383+
384+
self.log.info("Test that getblock with verbosity 1 doesn't include fee")
385+
block = node.getblock(blockhash, 1)
386+
assert 'fee' not in block['tx'][1]
387+
388+
self.log.info('Test that getblock with verbosity 2 includes expected fee')
389+
block = node.getblock(blockhash, 2)
390+
tx = block['tx'][1]
391+
assert 'fee' in tx
392+
assert_equal(tx['fee'], tx['vsize'] * fee_per_byte)
393+
394+
self.log.info("Test that getblock with verbosity 2 still works with pruned Undo data")
395+
datadir = get_datadir_path(self.options.tmpdir, 0)
396+
397+
def move_block_file(old, new):
398+
old_path = os.path.join(datadir, self.chain, 'blocks', old)
399+
new_path = os.path.join(datadir, self.chain, 'blocks', new)
400+
os.rename(old_path, new_path)
401+
402+
# Move instead of deleting so we can restore chain state afterwards
403+
move_block_file('rev00000.dat', 'rev_wrong')
404+
405+
block = node.getblock(blockhash, 2)
406+
assert 'fee' not in block['tx'][1]
407+
408+
# Restore chain state
409+
move_block_file('rev_wrong', 'rev00000.dat')
410+
367411

368412
if __name__ == '__main__':
369413
BlockchainTest().main()

0 commit comments

Comments
 (0)