mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 14:59:39 -04:00
rpc: Support v3 raw transactions creation
Added support for creating v3 raw transaction: - Overloaded to include additional parameter Co-authored-by: chungeun-choi <cucuridas@gmail.com> Co-authored-by: dongwook-chan <dongwook.chan@gmail.com> Co-authored-by: sean-k1 <uhs2000@naver.com>
This commit is contained in:
parent
cb15291ea2
commit
a43237d11e
7 changed files with 143 additions and 9 deletions
|
@ -102,7 +102,7 @@ bool IsStandard(const CScript& scriptPubKey, const std::optional<unsigned>& max_
|
|||
|
||||
bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_datacarrier_bytes, bool permit_bare_multisig, const CFeeRate& dust_relay_fee, std::string& reason)
|
||||
{
|
||||
if (tx.version > TX_MAX_STANDARD_VERSION || tx.version < 1) {
|
||||
if (tx.version > TX_MAX_STANDARD_VERSION || tx.version < TX_MIN_STANDARD_VERSION) {
|
||||
reason = "version";
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -122,6 +122,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||
{ "createrawtransaction", 1, "outputs" },
|
||||
{ "createrawtransaction", 2, "locktime" },
|
||||
{ "createrawtransaction", 3, "replaceable" },
|
||||
{ "createrawtransaction", 4, "version" },
|
||||
{ "decoderawtransaction", 1, "iswitness" },
|
||||
{ "signrawtransactionwithkey", 1, "privkeys" },
|
||||
{ "signrawtransactionwithkey", 2, "prevtxs" },
|
||||
|
@ -180,6 +181,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||
{ "createpsbt", 1, "outputs" },
|
||||
{ "createpsbt", 2, "locktime" },
|
||||
{ "createpsbt", 3, "replaceable" },
|
||||
{ "createpsbt", 4, "version" },
|
||||
{ "combinepsbt", 0, "txs"},
|
||||
{ "joinpsbts", 0, "txs"},
|
||||
{ "finalizepsbt", 1, "extract"},
|
||||
|
|
|
@ -158,6 +158,7 @@ static std::vector<RPCArg> CreateTxDoc()
|
|||
{"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"},
|
||||
{"replaceable", RPCArg::Type::BOOL, RPCArg::Default{true}, "Marks this transaction as BIP125-replaceable.\n"
|
||||
"Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible."},
|
||||
{"version", RPCArg::Type::NUM, RPCArg::Default{DEFAULT_RAWTX_VERSION}, "Transaction version"},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -433,7 +434,7 @@ static RPCHelpMan createrawtransaction()
|
|||
if (!request.params[3].isNull()) {
|
||||
rbf = request.params[3].get_bool();
|
||||
}
|
||||
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf);
|
||||
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf, request.params[4]);
|
||||
|
||||
return EncodeHexTx(CTransaction(rawTx));
|
||||
},
|
||||
|
@ -1671,7 +1672,7 @@ static RPCHelpMan createpsbt()
|
|||
if (!request.params[3].isNull()) {
|
||||
rbf = request.params[3].get_bool();
|
||||
}
|
||||
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf);
|
||||
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf, 2);
|
||||
|
||||
// Make a blank psbt
|
||||
PartiallySignedTransaction psbtx;
|
||||
|
|
|
@ -143,7 +143,7 @@ void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in)
|
|||
}
|
||||
}
|
||||
|
||||
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf)
|
||||
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf, const UniValue& version)
|
||||
{
|
||||
CMutableTransaction rawTx;
|
||||
|
||||
|
@ -154,6 +154,13 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
|
|||
rawTx.nLockTime = nLockTime;
|
||||
}
|
||||
|
||||
if (!version.isNull()) {
|
||||
uint32_t nVersion = version.getInt<uint32_t>();
|
||||
if (nVersion < TX_MIN_STANDARD_VERSION || nVersion > TX_MAX_STANDARD_VERSION)
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, version out of range(1~3)");
|
||||
rawTx.version = nVersion;
|
||||
}
|
||||
|
||||
AddInputs(rawTx, inputs_in, rbf);
|
||||
AddOutputs(rawTx, outputs_in);
|
||||
|
||||
|
|
|
@ -53,6 +53,6 @@ std::vector<std::pair<CTxDestination, CAmount>> ParseOutputs(const UniValue& out
|
|||
void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in);
|
||||
|
||||
/** Create a transaction from univalue parameters */
|
||||
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf);
|
||||
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf, const UniValue& version);
|
||||
|
||||
#endif // BITCOIN_RPC_RAWTRANSACTION_UTIL_H
|
||||
|
|
|
@ -1301,7 +1301,7 @@ RPCHelpMan send()
|
|||
ParseOutputs(outputs),
|
||||
InterpretSubtractFeeFromOutputInstructions(options["subtract_fee_from_outputs"], outputs.getKeys())
|
||||
);
|
||||
CMutableTransaction rawTx = ConstructTransaction(options["inputs"], request.params[0], options["locktime"], rbf);
|
||||
CMutableTransaction rawTx = ConstructTransaction(options["inputs"], request.params[0], options["locktime"], rbf, 2);
|
||||
CCoinControl coin_control;
|
||||
// Automatically select coins, unless at least one is manually selected. Can
|
||||
// be overridden by options.add_inputs.
|
||||
|
@ -1465,7 +1465,7 @@ RPCHelpMan sendall()
|
|||
throw JSONRPCError(RPC_WALLET_ERROR, "Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.");
|
||||
}
|
||||
|
||||
CMutableTransaction rawTx{ConstructTransaction(options["inputs"], recipient_key_value_pairs, options["locktime"], rbf)};
|
||||
CMutableTransaction rawTx{ConstructTransaction(options["inputs"], recipient_key_value_pairs, options["locktime"], rbf, 2)};
|
||||
LOCK(pwallet->cs_wallet);
|
||||
|
||||
CAmount total_input_value(0);
|
||||
|
@ -1754,7 +1754,7 @@ RPCHelpMan walletcreatefundedpsbt()
|
|||
|
||||
const UniValue &replaceable_arg = options["replaceable"];
|
||||
const bool rbf{replaceable_arg.isNull() ? wallet.m_signal_rbf : replaceable_arg.get_bool()};
|
||||
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf);
|
||||
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf, 2);
|
||||
UniValue outputs(UniValue::VOBJ);
|
||||
outputs = NormalizeOutputs(request.params[1]);
|
||||
std::vector<CRecipient> recipients = CreateRecipients(
|
||||
|
|
|
@ -255,7 +255,11 @@ class RawTransactionsTest(BitcoinTestFramework):
|
|||
assert_raises_rpc_error(-1, "createrawtransaction", self.nodes[0].createrawtransaction, [])
|
||||
|
||||
# Test `createrawtransaction` invalid extra parameters
|
||||
assert_raises_rpc_error(-1, "createrawtransaction", self.nodes[0].createrawtransaction, [], {}, 0, False, 'foo')
|
||||
assert_raises_rpc_error(-1, "createrawtransaction", self.nodes[0].createrawtransaction, [], {}, 0, False, 2, 3, 'foo')
|
||||
|
||||
# Test `createrawtransaction` invalid version parameters
|
||||
assert_raises_rpc_error(-8, "Invalid parameter, version out of range(1~3)", self.nodes[0].createrawtransaction, [], {}, 0, False, 0)
|
||||
assert_raises_rpc_error(-8, "Invalid parameter, version out of range(1~3)", self.nodes[0].createrawtransaction, [], {}, 0, False, 4)
|
||||
|
||||
# Test `createrawtransaction` invalid `inputs`
|
||||
assert_raises_rpc_error(-3, "JSON value of type string is not of expected type array", self.nodes[0].createrawtransaction, 'foo', {})
|
||||
|
@ -487,5 +491,125 @@ class RawTransactionsTest(BitcoinTestFramework):
|
|||
decrawtx = self.nodes[0].decoderawtransaction(rawtx)
|
||||
assert_equal(decrawtx['version'], 0xffffffff)
|
||||
|
||||
def raw_multisig_transaction_legacy_tests(self):
|
||||
self.log.info("Test raw multisig transactions (legacy)")
|
||||
# The traditional multisig workflow does not work with descriptor wallets so these are legacy only.
|
||||
# The multisig workflow with descriptor wallets uses PSBTs and is tested elsewhere, no need to do them here.
|
||||
|
||||
# 2of2 test
|
||||
addr1 = self.nodes[2].getnewaddress()
|
||||
addr2 = self.nodes[2].getnewaddress()
|
||||
|
||||
addr1Obj = self.nodes[2].getaddressinfo(addr1)
|
||||
addr2Obj = self.nodes[2].getaddressinfo(addr2)
|
||||
|
||||
# Tests for createmultisig and addmultisigaddress
|
||||
assert_raises_rpc_error(-5, 'Pubkey "01020304" must have a length of either 33 or 65 bytes', self.nodes[0].createmultisig, 1, ["01020304"])
|
||||
# createmultisig can only take public keys
|
||||
self.nodes[0].createmultisig(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])
|
||||
# addmultisigaddress can take both pubkeys and addresses so long as they are in the wallet, which is tested here
|
||||
assert_raises_rpc_error(-5, f'Pubkey "{addr1}" must be a hex string', self.nodes[0].createmultisig, 2, [addr1Obj['pubkey'], addr1])
|
||||
|
||||
mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr1])['address']
|
||||
|
||||
# use balance deltas instead of absolute values
|
||||
bal = self.nodes[2].getbalance()
|
||||
|
||||
# send 1.2 BTC to msig adr
|
||||
txId = self.nodes[0].sendtoaddress(mSigObj, 1.2)
|
||||
self.sync_all()
|
||||
self.generate(self.nodes[0], 1)
|
||||
# node2 has both keys of the 2of2 ms addr, tx should affect the balance
|
||||
assert_equal(self.nodes[2].getbalance(), bal + Decimal('1.20000000'))
|
||||
|
||||
|
||||
# 2of3 test from different nodes
|
||||
bal = self.nodes[2].getbalance()
|
||||
addr1 = self.nodes[1].getnewaddress()
|
||||
addr2 = self.nodes[2].getnewaddress()
|
||||
addr3 = self.nodes[2].getnewaddress()
|
||||
|
||||
addr1Obj = self.nodes[1].getaddressinfo(addr1)
|
||||
addr2Obj = self.nodes[2].getaddressinfo(addr2)
|
||||
addr3Obj = self.nodes[2].getaddressinfo(addr3)
|
||||
|
||||
mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey'], addr3Obj['pubkey']])['address']
|
||||
|
||||
txId = self.nodes[0].sendtoaddress(mSigObj, 2.2)
|
||||
decTx = self.nodes[0].gettransaction(txId)
|
||||
rawTx = self.nodes[0].decoderawtransaction(decTx['hex'])
|
||||
self.sync_all()
|
||||
self.generate(self.nodes[0], 1)
|
||||
|
||||
# THIS IS AN INCOMPLETE FEATURE
|
||||
# NODE2 HAS TWO OF THREE KEYS AND THE FUNDS SHOULD BE SPENDABLE AND COUNT AT BALANCE CALCULATION
|
||||
assert_equal(self.nodes[2].getbalance(), bal) # for now, assume the funds of a 2of3 multisig tx are not marked as spendable
|
||||
|
||||
txDetails = self.nodes[0].gettransaction(txId, True)
|
||||
rawTx = self.nodes[0].decoderawtransaction(txDetails['hex'])
|
||||
vout = next(o for o in rawTx['vout'] if o['value'] == Decimal('2.20000000'))
|
||||
|
||||
bal = self.nodes[0].getbalance()
|
||||
inputs = [{"txid": txId, "vout": vout['n'], "scriptPubKey": vout['scriptPubKey']['hex'], "amount": vout['value']}]
|
||||
outputs = {self.nodes[0].getnewaddress(): 2.19}
|
||||
rawTx = self.nodes[2].createrawtransaction(inputs, outputs, 3)
|
||||
rawTxPartialSigned = self.nodes[1].signrawtransactionwithwallet(rawTx, inputs)
|
||||
assert_equal(rawTxPartialSigned['complete'], False) # node1 only has one key, can't comp. sign the tx
|
||||
|
||||
rawTxSigned = self.nodes[2].signrawtransactionwithwallet(rawTx, inputs)
|
||||
assert_equal(rawTxSigned['complete'], True) # node2 can sign the tx compl., own two of three keys
|
||||
self.nodes[2].sendrawtransaction(rawTxSigned['hex'])
|
||||
rawTx = self.nodes[0].decoderawtransaction(rawTxSigned['hex'])
|
||||
self.sync_all()
|
||||
self.generate(self.nodes[0], 1)
|
||||
assert_equal(self.nodes[0].getbalance(), bal + Decimal('50.00000000') + Decimal('2.19000000')) # block reward + tx
|
||||
|
||||
# 2of2 test for combining transactions
|
||||
bal = self.nodes[2].getbalance()
|
||||
addr1 = self.nodes[1].getnewaddress()
|
||||
addr2 = self.nodes[2].getnewaddress()
|
||||
|
||||
addr1Obj = self.nodes[1].getaddressinfo(addr1)
|
||||
addr2Obj = self.nodes[2].getaddressinfo(addr2)
|
||||
|
||||
self.nodes[1].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address']
|
||||
mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address']
|
||||
mSigObjValid = self.nodes[2].getaddressinfo(mSigObj)
|
||||
|
||||
txId = self.nodes[0].sendtoaddress(mSigObj, 2.2)
|
||||
decTx = self.nodes[0].gettransaction(txId)
|
||||
rawTx2 = self.nodes[0].decoderawtransaction(decTx['hex'])
|
||||
self.sync_all()
|
||||
self.generate(self.nodes[0], 1)
|
||||
|
||||
assert_equal(self.nodes[2].getbalance(), bal) # the funds of a 2of2 multisig tx should not be marked as spendable
|
||||
|
||||
txDetails = self.nodes[0].gettransaction(txId, True)
|
||||
rawTx2 = self.nodes[0].decoderawtransaction(txDetails['hex'])
|
||||
vout = next(o for o in rawTx2['vout'] if o['value'] == Decimal('2.20000000'))
|
||||
|
||||
bal = self.nodes[0].getbalance()
|
||||
inputs = [{"txid": txId, "vout": vout['n'], "scriptPubKey": vout['scriptPubKey']['hex'], "redeemScript": mSigObjValid['hex'], "amount": vout['value']}]
|
||||
outputs = {self.nodes[0].getnewaddress(): 2.19}
|
||||
rawTx2 = self.nodes[2].createrawtransaction(inputs, outputs, version=3)
|
||||
rawTxPartialSigned1 = self.nodes[1].signrawtransactionwithwallet(rawTx2, inputs)
|
||||
self.log.debug(rawTxPartialSigned1)
|
||||
assert_equal(rawTxPartialSigned1['complete'], False) # node1 only has one key, can't comp. sign the tx
|
||||
|
||||
rawTxPartialSigned2 = self.nodes[2].signrawtransactionwithwallet(rawTx2, inputs)
|
||||
self.log.debug(rawTxPartialSigned2)
|
||||
assert_equal(rawTxPartialSigned2['complete'], False) # node2 only has one key, can't comp. sign the tx
|
||||
assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].combinerawtransaction, [rawTxPartialSigned1['hex'], rawTxPartialSigned2['hex'] + "00"])
|
||||
assert_raises_rpc_error(-22, "Missing transactions", self.nodes[0].combinerawtransaction, [])
|
||||
rawTxComb = self.nodes[2].combinerawtransaction([rawTxPartialSigned1['hex'], rawTxPartialSigned2['hex']])
|
||||
self.log.debug(rawTxComb)
|
||||
self.nodes[2].sendrawtransaction(rawTxComb)
|
||||
rawTx2 = self.nodes[0].decoderawtransaction(rawTxComb)
|
||||
self.sync_all()
|
||||
self.generate(self.nodes[0], 1)
|
||||
assert_equal(self.nodes[0].getbalance(), bal + Decimal('50.00000000') + Decimal('2.19000000')) # block reward + tx
|
||||
assert_raises_rpc_error(-25, "Input not found or already spent", self.nodes[0].combinerawtransaction, [rawTxPartialSigned1['hex'], rawTxPartialSigned2['hex']])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
RawTransactionsTest(__file__).main()
|
||||
|
|
Loading…
Add table
Reference in a new issue