Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6145,6 +6145,77 @@ void FillBlinds(CWallet* pwallet, CMutableTransaction& tx, std::vector<uint256>&
}
}

RPCHelpMan analyzerawtransaction()
{
return RPCHelpMan{"analyzerawtransaction",
"\nGenerate a mapping showing losses and gains resulting in the signing and broadcasting of the given transaction.\n" +
HELP_REQUIRING_PASSPHRASE,
{
{"hexstring", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction hex string"},
},
RPCResult{
RPCResult::Type::OBJ_DYN, "", "",
{
{RPCResult::Type::NUM, "asset", "The wallet change for the given asset (negative means decrease)."},
}
},
RPCExamples{
HelpExampleCli("analyzerawtransaction", "\"myhex\"")
+ HelpExampleRpc("analyzerawtransaction", "\"myhex\"")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) return NullUniValue;
CWallet* const pwallet = wallet.get();

RPCTypeCheck(request.params, {UniValue::VSTR}, true);

LOCK(pwallet->cs_wallet);
EnsureWalletIsUnlocked(pwallet);

// Decode and unblind the transaction
CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input.");
}

std::vector<uint256> output_value_blinds;
std::vector<uint256> output_asset_blinds;
std::vector<CPubKey> output_pubkeys;
std::vector<CKey> asset_keys;
std::vector<CKey> token_keys;
FillBlinds(pwallet, mtx, output_value_blinds, output_asset_blinds, output_pubkeys, asset_keys, token_keys);
CTransaction tx(mtx);

// Calculate changes (+credit, -debit)
CAmountMap changes;

// Fetch debit; we are *spending* these; if the transaction is signed and broadcast, we will lose everything in these
for (size_t i = 0; i < tx.vin.size(); ++i) {
CAmountMap debit = pwallet->GetDebit(tx.vin.at(i), ISMINE_SPENDABLE);
for (const auto& entry : debit) {
changes[entry.first] -= entry.second;
}
}

// Fetch credit; we are *receiving* these; if the transaciton is signed and broadcast, we will receive everything in these
for (size_t i = 0; i < tx.vout.size(); ++i) {
const CTxOut& txout = tx.vout.at(i);
if (!pwallet->IsMine(txout)) continue;
if (!txout.nAsset.IsExplicit() || !txout.nValue.IsExplicit()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Output belongs to me but I can't unblind it");
}
const auto& asset = txout.nAsset.GetAsset();
const auto& value = txout.nValue.GetAmount();
changes[asset] += value;
}

return AmountMapToUniv(changes, "");
}
};
}

static RPCHelpMan blindrawtransaction()
{
return RPCHelpMan{"blindrawtransaction",
Expand Down Expand Up @@ -7022,6 +7093,7 @@ static const CRPCCommand commands[] =
{ "wallet", "getpeginaddress", &getpeginaddress, {} },
{ "wallet", "claimpegin", &claimpegin, {"bitcoin_tx", "txoutproof", "claim_script"} },
{ "wallet", "createrawpegin", &createrawpegin, {"bitcoin_tx", "txoutproof", "claim_script"} },
{ "wallet", "analyzerawtransaction", &analyzerawtransaction, {"hexstring"} },
{ "wallet", "blindrawtransaction", &blindrawtransaction, {"hexstring", "ignoreblindfail", "asset_commitments", "blind_issuances", "totalblinder"} },
{ "wallet", "unblindrawtransaction", &unblindrawtransaction, {"hex"} },
{ "wallet", "sendtomainchain", &sendtomainchain, {"address", "amount", "subtractfeefromamount", "verbose"} },
Expand Down
1 change: 1 addition & 0 deletions test/functional/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@
'wallet_bumpfee.py',
'wallet_bumpfee.py --descriptors',
'wallet_implicitsegwit.py --legacy-wallet',
'wallet_analyzetx.py',
'rpc_named_arguments.py',
'wallet_listsinceblock.py',
'wallet_listsinceblock.py --descriptors',
Expand Down
88 changes: 88 additions & 0 deletions test/functional/wallet_analyzetx.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env python3
# Copyright (c) 2021 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test analyzerawtransaction.
"""

from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_approx,
assert_equal,
)

def assert_analysis(analysis, expected):
if "bitcoin" not in expected:
expected["bitcoin"] = 0.0
assert_equal(len(analysis), len(expected))
for key in expected:
assert_approx(analysis[key], expected[key], 0.0000001)

class AnalyzeTxTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 2
elements_args = ["-blindedaddresses=1", "-initialfreecoins=2100000000000000", "-con_blocksubsidy=0", "-con_connect_genesis_outputs=1", "-anyonecanspendaremine=1"]
self.extra_args = [elements_args, elements_args]

def skip_test_if_missing_module(self):
self.skip_if_no_wallet()

def setup_network(self, split=False):
self.setup_nodes()

def run_test(self):
node0 = self.nodes[0]
node1 = self.nodes[1]
self.connect_nodes(0, 1)

node0.generate(1) # Leave IBD

node0.createwallet(wallet_name='w0')
w0 = node0.get_wallet_rpc('w0')
node1.createwallet(wallet_name='w1')
w1 = node1.get_wallet_rpc('w1')

w0.rescanblockchain()
assetdata = w0.issueasset(10000000, 100)
asset = assetdata["asset"]

address1 = w1.getnewaddress()

tx = node1.createrawtransaction([], [{address1: 5.0}], 0, False, {address1: asset})

# node0 should be unaffected
analysis = w0.analyzerawtransaction(tx)
assert_analysis(analysis, {})

# node1 should see a +5 asset
analysis = w1.analyzerawtransaction(tx)
assert_analysis(analysis, {asset: 5.0})

# w0 funds transaction; it should now see a decrease in bitcoin (tx fee) and the asset, and w1 should see the same as above
funding = w0.fundrawtransaction(tx)
tx = funding["hex"]
bitcoin_fee = float(funding["fee"])

# node0 sees decrease in bitcoin and the asset
analysis = w0.analyzerawtransaction(tx)
assert_analysis(analysis, {"bitcoin": -bitcoin_fee, asset: -5.0})

# node1 sees same as before
analysis = w1.analyzerawtransaction(tx)
assert_analysis(analysis, {asset: 5.0})

# after blinding the transaction, nodes should still be able to read correctly
tx = w0.blindrawtransaction(tx)

# node0 sees decrease in bitcoin and the asset
analysis = w0.analyzerawtransaction(tx)
assert_analysis(analysis, {"bitcoin": -bitcoin_fee, asset: -5.0})

# node1 sees same as before
analysis = w1.analyzerawtransaction(tx)
assert_analysis(analysis, {asset: 5.0})


if __name__ == '__main__':
AnalyzeTxTest().main()