Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wallet: Show fee in results for signrawtransaction* for segwit inputs #12911

Closed
wants to merge 4 commits into from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions doc/release-notes-pr12911.md
@@ -0,0 +1,7 @@
RPC changes
------------

### Low-level changes

- `signrawtransactionwithkey` and `signrawtransactionwithwallet` will now include a `fee` entry in the results,
for cases where the fee is known.
2 changes: 2 additions & 0 deletions src/rpc/rawtransaction.cpp
Expand Up @@ -741,6 +741,8 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request)
{
{RPCResult::Type::STR_HEX, "hex", "The hex-encoded raw transaction with signature(s)"},
{RPCResult::Type::BOOL, "complete", "If the transaction has a complete set of signatures"},
{RPCResult::Type::NUM, "fee", "The fee (input amounts minus output amounts), if known"},
{RPCResult::Type::NUM, "feerate", "The fee rate (in " + CURRENCY_UNIT + "/kB), if fee is known"},
{RPCResult::Type::ARR, "errors", "Script verification errors (if there are any)",
{
{RPCResult::Type::OBJ, "", "",
Expand Down
20 changes: 19 additions & 1 deletion src/rpc/rawtransaction_util.cpp
Expand Up @@ -281,20 +281,26 @@ void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore,
// transaction to avoid rehashing.
const CTransaction txConst(mtx);
// Sign what we can:
CAmount inout_amount = 0;
bool known_inputs = true;
for (unsigned int i = 0; i < mtx.vin.size(); i++) {
CTxIn& txin = mtx.vin[i];
auto coin = coins.find(txin.prevout);
if (coin == coins.end() || coin->second.IsSpent()) {
known_inputs = false;
TxInErrorToJSON(txin, vErrors, "Input not found or already spent");
continue;
}
const CScript& prevPubKey = coin->second.out.scriptPubKey;
const CAmount& amount = coin->second.out.nValue;
inout_amount += amount;
known_inputs &= amount > 0;

SignatureData sigdata = DataFromTransaction(mtx, i, coin->second.out);
// Only sign SIGHASH_SINGLE if there's a corresponding output:
if (!fHashSingle || (i < mtx.vout.size())) {
ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, nHashType), prevPubKey, sigdata);
known_inputs &= sigdata.witness;
}

UpdateInput(txin, sigdata);
Expand All @@ -319,8 +325,20 @@ void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore,
}
bool fComplete = vErrors.empty();

result.pushKV("hex", EncodeHexTx(CTransaction(mtx)));
CTransaction tx(mtx);
result.pushKV("hex", EncodeHexTx(tx));
result.pushKV("complete", fComplete);
if (known_inputs) {
for (const CTxOut& txout : mtx.vout) {
inout_amount -= txout.nValue;
}
result.pushKV("fee", ValueFromAmount(inout_amount));
result.pushKV("feerate",
ValueFromAmount(
CFeeRate(inout_amount, GetVirtualTransactionSize(tx)).GetFeePerK()
)
);
}
if (!vErrors.empty()) {
if (result.exists("errors")) {
vErrors.push_backV(result["errors"].getValues());
Expand Down
2 changes: 2 additions & 0 deletions src/wallet/rpcwallet.cpp
Expand Up @@ -3288,6 +3288,8 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request)
{
{RPCResult::Type::STR_HEX, "hex", "The hex-encoded raw transaction with signature(s)"},
{RPCResult::Type::BOOL, "complete", "If the transaction has a complete set of signatures"},
{RPCResult::Type::NUM, "fee", "The fee (input amounts minus output amounts), if known"},
{RPCResult::Type::NUM, "feerate", "The fee rate (in " + CURRENCY_UNIT + "/kB), if fee is known"},
{RPCResult::Type::ARR, "errors", "Script verification errors (if there are any)",
{
{RPCResult::Type::OBJ, "", "",
Expand Down
29 changes: 29 additions & 0 deletions test/functional/feature_segwit.py
Expand Up @@ -19,6 +19,7 @@
from test_framework.script import CScript, OP_HASH160, OP_CHECKSIG, OP_0, hash160, OP_EQUAL, OP_DUP, OP_EQUALVERIFY, OP_1, OP_2, OP_CHECKMULTISIG, OP_TRUE, OP_DROP
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_approx,
assert_equal,
assert_raises_rpc_error,
connect_nodes,
Expand Down Expand Up @@ -278,6 +279,34 @@ def run_test(self):
# Mine a block to clear the gbt cache again.
self.nodes[0].generate(1)

self.log.info("Signing with all-segwit inputs reveals fee rate")
# Test that signing a tx with all-segwit inputs returns the fee
# in the results
addr = self.nodes[0].getnewaddress(address_type='p2sh-segwit')
txid = self.nodes[0].sendtoaddress(addr, 1)
tx = self.nodes[0].getrawtransaction(txid, True)
n = -1
value = -1
for o in tx["vout"]:
if o["scriptPubKey"]["addresses"][0] == addr:
n = o["n"]
value = Decimal(o["value"])
break
assert n > -1 # failure means we could not find the address in the outputs despite sending to it
assert_approx(value, 1.0, 0.01) # failure means we got an unexpected amount of coins, despite trying to send 1
fee = Decimal("0.00010000")
value_out = value - fee
self.nodes[0].generatetoaddress(1, self.nodes[0].getnewaddress())
raw = self.nodes[0].createrawtransaction([{"txid" : txid, "vout" : n}], [{self.nodes[0].getnewaddress() : value_out}])
signed = self.nodes[0].signrawtransactionwithwallet(raw)
assert_equal(signed["complete"], True)
txsize = self.nodes[0].decoderawtransaction(signed['hex'])['vsize']
exp_feerate = 1000 * fee / Decimal(txsize)
assert_approx(signed["feerate"], exp_feerate, Decimal("0.00000010"))
# discrepancy = 100000000 * (exp_feerate - signed["feerate"])
# assert -10 < discrepancy < 10
assert Decimal(signed["fee"]) == fee

self.log.info("Verify behaviour of importaddress and listunspent")

# Some public keys to be used later
Expand Down