Merge f656165e9c into merged_master (Bitcoin PR #18772)

I'm gonna be honest, I largely just rewrote this one. It adds a "fee" field
to the RPC transaction output, which I replaced with a feemap which is
directly extracted from the transaction rather than computed implicitly
from inputs - outputs.

Removed a functional test that fee computations become impossible without
chain data, since in Elements there is no chain data lookup involved.
This commit is contained in:
Andrew Poelstra 2021-06-16 00:25:59 +00:00
commit 658b2312da
5 changed files with 77 additions and 11 deletions

View file

@ -19,6 +19,7 @@ class CTransaction;
struct CMutableTransaction;
class uint256;
class UniValue;
class CTxUndo;
// core_read.cpp
CScript ParseScript(const std::string& s);
@ -47,6 +48,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

@ -13,7 +13,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>
@ -225,7 +227,7 @@ void ScriptPubKeyToUniv(const CScript& scriptPubKey,
}
}
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());
@ -241,13 +243,14 @@ 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};
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);
@ -317,6 +320,7 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
}
entry.pushKV("vin", vin);
CAmountMap fee_map{};
UniValue vout(UniValue::VARR);
for (unsigned int i = 0; i < tx.vout.size(); i++) {
const CTxOut& txout = tx.vout[i];
@ -350,6 +354,10 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
out.pushKV("valuecommitment", txout.nValue.GetHex());
}
if (g_con_elementsmode) {
if (txout.IsFee()) {
fee_map[txout.nAsset.GetAsset()] += txout.nValue.GetAmount();
}
if (txout.nAsset.IsExplicit()) {
out.pushKV("asset", txout.nAsset.GetAsset().GetHex());
} else {
@ -369,6 +377,19 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
}
entry.pushKV("vout", vout);
// ELEMENTS: add fee map rather than single fee. Unlike other areas of the RPC,
// we do not look up labels here and will always use the asset hex (contrast
// `AmountMapToUniv` in rpc/util.cpp. This is because this is a pure function
// so we do not have access to `policyAsset` or `gAssetsDir`. (We will get link
// errors if we try to use these.)
if (g_con_elementsmode) {
UniValue fee_obj(UniValue::VOBJ);
for(std::map<CAsset, CAmount>::const_iterator it = fee_map.begin(); it != fee_map.end(); ++it) {
fee_obj.pushKV(it->first.GetHex(), ValueFromAmount(it->second));
}
entry.pushKV("fee", fee_obj);
}
if (!hashBlock.IsNull())
entry.pushKV("blockhash", hashBlock.GetHex());

View file

@ -203,16 +203,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());
@ -1015,6 +1020,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

@ -17,6 +17,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 (
@ -36,7 +37,9 @@ from test_framework.util import (
assert_raises,
assert_raises_rpc_error,
assert_is_hash_string,
get_datadir_path,
)
from test_framework.wallet import MiniWallet
class BlockchainTest(BitcoinTestFramework):
@ -55,6 +58,7 @@ class BlockchainTest(BitcoinTestFramework):
self._test_getblockheader()
self._test_stopatheight()
self._test_waitforblockheight()
self._test_getblock()
assert self.nodes[0].verifychain(4, 0)
def mine_chain(self):
@ -348,6 +352,38 @@ 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
asset_hex = node.getsidechaininfo()['pegged_asset']
assert_equal(tx['fee'], { asset_hex: 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)
if __name__ == '__main__':
BlockchainTest().main()

View file

@ -28,5 +28,7 @@
}
}
],
"fee": {
},
"hex": "01000000000001010000000000000000000000000000000000000000000000000000000000000000010000000005f5e1000017a9146edf12858999f0dae74f9c692e6694ee3621b2ac8700000000"
}