mirror of
https://github.com/kvazar-network/kevacoin.git
synced 2025-01-10 23:27:54 +00:00
Move transaction combining from signrawtransaction to new RPC
Create a combinerawtransaction RPC which accepts a json array of hex raw transactions to combine them into one transaction. Signrawtransaction is changed to no longer combine transactions and only accept one transaction at a time.
This commit is contained in:
parent
0b019357ff
commit
6b4f231f5f
@ -95,6 +95,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||||||
{ "signrawtransaction", 1, "prevtxs" },
|
{ "signrawtransaction", 1, "prevtxs" },
|
||||||
{ "signrawtransaction", 2, "privkeys" },
|
{ "signrawtransaction", 2, "privkeys" },
|
||||||
{ "sendrawtransaction", 1, "allowhighfees" },
|
{ "sendrawtransaction", 1, "allowhighfees" },
|
||||||
|
{ "combinerawtransaction", 0, "txs" },
|
||||||
{ "fundrawtransaction", 1, "options" },
|
{ "fundrawtransaction", 1, "options" },
|
||||||
{ "gettxout", 1, "n" },
|
{ "gettxout", 1, "n" },
|
||||||
{ "gettxout", 2, "include_mempool" },
|
{ "gettxout", 2, "include_mempool" },
|
||||||
|
@ -554,6 +554,93 @@ static void TxInErrorToJSON(const CTxIn& txin, UniValue& vErrorsRet, const std::
|
|||||||
vErrorsRet.push_back(entry);
|
vErrorsRet.push_back(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UniValue combinerawtransaction(const JSONRPCRequest& request)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (request.fHelp || request.params.size() != 1)
|
||||||
|
throw std::runtime_error(
|
||||||
|
"combinerawtransaction [\"hexstring\",...]\n"
|
||||||
|
"\nCombine multiple partially signed transactions into one transaction.\n"
|
||||||
|
"The combined transaction may be another partially signed transaction or a \n"
|
||||||
|
"fully signed transaction."
|
||||||
|
|
||||||
|
"\nArguments:\n"
|
||||||
|
"1. \"txs\" (string) A json array of hex strings of partially signed transactions\n"
|
||||||
|
" [\n"
|
||||||
|
" \"hexstring\" (string) A transaction hash\n"
|
||||||
|
" ,...\n"
|
||||||
|
" ]\n"
|
||||||
|
|
||||||
|
"\nResult:\n"
|
||||||
|
"\"hex\" : \"value\", (string) The hex-encoded raw transaction with signature(s)\n"
|
||||||
|
|
||||||
|
"\nExamples:\n"
|
||||||
|
+ HelpExampleCli("combinerawtransaction", "[\"myhex1\", \"myhex2\", \"myhex3\"]")
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
UniValue txs = request.params[0].get_array();
|
||||||
|
std::vector<CMutableTransaction> txVariants(txs.size());
|
||||||
|
|
||||||
|
for (unsigned int idx = 0; idx < txs.size(); idx++) {
|
||||||
|
if (!DecodeHexTx(txVariants[idx], txs[idx].get_str(), true)) {
|
||||||
|
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed for tx %d", idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (txVariants.empty()) {
|
||||||
|
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Missing transactions");
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergedTx will end up with all the signatures; it
|
||||||
|
// starts as a clone of the rawtx:
|
||||||
|
CMutableTransaction mergedTx(txVariants[0]);
|
||||||
|
|
||||||
|
// Fetch previous transactions (inputs):
|
||||||
|
CCoinsView viewDummy;
|
||||||
|
CCoinsViewCache view(&viewDummy);
|
||||||
|
{
|
||||||
|
LOCK(cs_main);
|
||||||
|
LOCK(mempool.cs);
|
||||||
|
CCoinsViewCache &viewChain = *pcoinsTip;
|
||||||
|
CCoinsViewMemPool viewMempool(&viewChain, mempool);
|
||||||
|
view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view
|
||||||
|
|
||||||
|
for (const CTxIn& txin : mergedTx.vin) {
|
||||||
|
view.AccessCoin(txin.prevout); // Load entries from viewChain into view; can fail.
|
||||||
|
}
|
||||||
|
|
||||||
|
view.SetBackend(viewDummy); // switch back to avoid locking mempool for too long
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use CTransaction for the constant parts of the
|
||||||
|
// transaction to avoid rehashing.
|
||||||
|
const CTransaction txConst(mergedTx);
|
||||||
|
// Sign what we can:
|
||||||
|
for (unsigned int i = 0; i < mergedTx.vin.size(); i++) {
|
||||||
|
CTxIn& txin = mergedTx.vin[i];
|
||||||
|
const Coin& coin = view.AccessCoin(txin.prevout);
|
||||||
|
if (coin.IsSpent()) {
|
||||||
|
throw JSONRPCError(RPC_VERIFY_ERROR, "Input not found or already spent");
|
||||||
|
}
|
||||||
|
const CScript& prevPubKey = coin.out.scriptPubKey;
|
||||||
|
const CAmount& amount = coin.out.nValue;
|
||||||
|
|
||||||
|
SignatureData sigdata;
|
||||||
|
|
||||||
|
// ... and merge in other signatures:
|
||||||
|
for (const CMutableTransaction& txv : txVariants) {
|
||||||
|
if (txv.vin.size() > i) {
|
||||||
|
sigdata = CombineSignatures(prevPubKey, TransactionSignatureChecker(&txConst, i, amount), sigdata, DataFromTransaction(txv, i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateTransaction(mergedTx, i, sigdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
return EncodeHexTx(mergedTx);
|
||||||
|
}
|
||||||
|
|
||||||
UniValue signrawtransaction(const JSONRPCRequest& request)
|
UniValue signrawtransaction(const JSONRPCRequest& request)
|
||||||
{
|
{
|
||||||
#ifdef ENABLE_WALLET
|
#ifdef ENABLE_WALLET
|
||||||
@ -626,26 +713,9 @@ UniValue signrawtransaction(const JSONRPCRequest& request)
|
|||||||
#endif
|
#endif
|
||||||
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR}, true);
|
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR}, true);
|
||||||
|
|
||||||
std::vector<unsigned char> txData(ParseHexV(request.params[0], "argument 1"));
|
CMutableTransaction mtx;
|
||||||
CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION);
|
if (!DecodeHexTx(mtx, request.params[0].get_str(), true))
|
||||||
std::vector<CMutableTransaction> txVariants;
|
|
||||||
while (!ssData.empty()) {
|
|
||||||
try {
|
|
||||||
CMutableTransaction tx;
|
|
||||||
ssData >> tx;
|
|
||||||
txVariants.push_back(tx);
|
|
||||||
}
|
|
||||||
catch (const std::exception&) {
|
|
||||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
|
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (txVariants.empty())
|
|
||||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Missing transaction");
|
|
||||||
|
|
||||||
// mergedTx will end up with all the signatures; it
|
|
||||||
// starts as a clone of the rawtx:
|
|
||||||
CMutableTransaction mergedTx(txVariants[0]);
|
|
||||||
|
|
||||||
// Fetch previous transactions (inputs):
|
// Fetch previous transactions (inputs):
|
||||||
CCoinsView viewDummy;
|
CCoinsView viewDummy;
|
||||||
@ -656,7 +726,7 @@ UniValue signrawtransaction(const JSONRPCRequest& request)
|
|||||||
CCoinsViewMemPool viewMempool(&viewChain, mempool);
|
CCoinsViewMemPool viewMempool(&viewChain, mempool);
|
||||||
view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view
|
view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view
|
||||||
|
|
||||||
for (const CTxIn& txin : mergedTx.vin) {
|
for (const CTxIn& txin : mtx.vin) {
|
||||||
view.AccessCoin(txin.prevout); // Load entries from viewChain into view; can fail.
|
view.AccessCoin(txin.prevout); // Load entries from viewChain into view; can fail.
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -781,10 +851,10 @@ UniValue signrawtransaction(const JSONRPCRequest& request)
|
|||||||
|
|
||||||
// Use CTransaction for the constant parts of the
|
// Use CTransaction for the constant parts of the
|
||||||
// transaction to avoid rehashing.
|
// transaction to avoid rehashing.
|
||||||
const CTransaction txConst(mergedTx);
|
const CTransaction txConst(mtx);
|
||||||
// Sign what we can:
|
// Sign what we can:
|
||||||
for (unsigned int i = 0; i < mergedTx.vin.size(); i++) {
|
for (unsigned int i = 0; i < mtx.vin.size(); i++) {
|
||||||
CTxIn& txin = mergedTx.vin[i];
|
CTxIn& txin = mtx.vin[i];
|
||||||
const Coin& coin = view.AccessCoin(txin.prevout);
|
const Coin& coin = view.AccessCoin(txin.prevout);
|
||||||
if (coin.IsSpent()) {
|
if (coin.IsSpent()) {
|
||||||
TxInErrorToJSON(txin, vErrors, "Input not found or already spent");
|
TxInErrorToJSON(txin, vErrors, "Input not found or already spent");
|
||||||
@ -795,17 +865,11 @@ UniValue signrawtransaction(const JSONRPCRequest& request)
|
|||||||
|
|
||||||
SignatureData sigdata;
|
SignatureData sigdata;
|
||||||
// Only sign SIGHASH_SINGLE if there's a corresponding output:
|
// Only sign SIGHASH_SINGLE if there's a corresponding output:
|
||||||
if (!fHashSingle || (i < mergedTx.vout.size()))
|
if (!fHashSingle || (i < mtx.vout.size()))
|
||||||
ProduceSignature(MutableTransactionSignatureCreator(&keystore, &mergedTx, i, amount, nHashType), prevPubKey, sigdata);
|
ProduceSignature(MutableTransactionSignatureCreator(&keystore, &mtx, i, amount, nHashType), prevPubKey, sigdata);
|
||||||
|
sigdata = CombineSignatures(prevPubKey, TransactionSignatureChecker(&txConst, i, amount), sigdata, DataFromTransaction(mtx, i));
|
||||||
|
|
||||||
// ... and merge in other signatures:
|
UpdateTransaction(mtx, i, sigdata);
|
||||||
for (const CMutableTransaction& txv : txVariants) {
|
|
||||||
if (txv.vin.size() > i) {
|
|
||||||
sigdata = CombineSignatures(prevPubKey, TransactionSignatureChecker(&txConst, i, amount), sigdata, DataFromTransaction(txv, i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateTransaction(mergedTx, i, sigdata);
|
|
||||||
|
|
||||||
ScriptError serror = SCRIPT_ERR_OK;
|
ScriptError serror = SCRIPT_ERR_OK;
|
||||||
if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount), &serror)) {
|
if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount), &serror)) {
|
||||||
@ -815,7 +879,7 @@ UniValue signrawtransaction(const JSONRPCRequest& request)
|
|||||||
bool fComplete = vErrors.empty();
|
bool fComplete = vErrors.empty();
|
||||||
|
|
||||||
UniValue result(UniValue::VOBJ);
|
UniValue result(UniValue::VOBJ);
|
||||||
result.push_back(Pair("hex", EncodeHexTx(mergedTx)));
|
result.push_back(Pair("hex", EncodeHexTx(mtx)));
|
||||||
result.push_back(Pair("complete", fComplete));
|
result.push_back(Pair("complete", fComplete));
|
||||||
if (!vErrors.empty()) {
|
if (!vErrors.empty()) {
|
||||||
result.push_back(Pair("errors", vErrors));
|
result.push_back(Pair("errors", vErrors));
|
||||||
@ -905,6 +969,7 @@ static const CRPCCommand commands[] =
|
|||||||
{ "rawtransactions", "decoderawtransaction", &decoderawtransaction, true, {"hexstring"} },
|
{ "rawtransactions", "decoderawtransaction", &decoderawtransaction, true, {"hexstring"} },
|
||||||
{ "rawtransactions", "decodescript", &decodescript, true, {"hexstring"} },
|
{ "rawtransactions", "decodescript", &decodescript, true, {"hexstring"} },
|
||||||
{ "rawtransactions", "sendrawtransaction", &sendrawtransaction, false, {"hexstring","allowhighfees"} },
|
{ "rawtransactions", "sendrawtransaction", &sendrawtransaction, false, {"hexstring","allowhighfees"} },
|
||||||
|
{ "rawtransactions", "combinerawtransaction", &combinerawtransaction, true, {"txs"} },
|
||||||
{ "rawtransactions", "signrawtransaction", &signrawtransaction, false, {"hexstring","prevtxs","privkeys","sighashtype"} }, /* uses wallet if enabled */
|
{ "rawtransactions", "signrawtransaction", &signrawtransaction, false, {"hexstring","prevtxs","privkeys","sighashtype"} }, /* uses wallet if enabled */
|
||||||
|
|
||||||
{ "blockchain", "gettxoutproof", &gettxoutproof, true, {"txids", "blockhash"} },
|
{ "blockchain", "gettxoutproof", &gettxoutproof, true, {"txids", "blockhash"} },
|
||||||
|
@ -124,6 +124,55 @@ class RawTransactionsTest(BitcoinTestFramework):
|
|||||||
self.sync_all()
|
self.sync_all()
|
||||||
assert_equal(self.nodes[0].getbalance(), bal+Decimal('50.00000000')+Decimal('2.19000000')) #block reward + tx
|
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].validateaddress(addr1)
|
||||||
|
addr2Obj = self.nodes[2].validateaddress(addr2)
|
||||||
|
|
||||||
|
self.nodes[1].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])
|
||||||
|
mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])
|
||||||
|
mSigObjValid = self.nodes[2].validateaddress(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.nodes[0].generate(1)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
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 = False
|
||||||
|
for outpoint in rawTx2['vout']:
|
||||||
|
if outpoint['value'] == Decimal('2.20000000'):
|
||||||
|
vout = outpoint
|
||||||
|
break
|
||||||
|
|
||||||
|
bal = self.nodes[0].getbalance()
|
||||||
|
inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex'], "redeemScript" : mSigObjValid['hex']}]
|
||||||
|
outputs = { self.nodes[0].getnewaddress() : 2.19 }
|
||||||
|
rawTx2 = self.nodes[2].createrawtransaction(inputs, outputs)
|
||||||
|
rawTxPartialSigned1 = self.nodes[1].signrawtransaction(rawTx2, inputs)
|
||||||
|
self.log.info(rawTxPartialSigned1)
|
||||||
|
assert_equal(rawTxPartialSigned['complete'], False) #node1 only has one key, can't comp. sign the tx
|
||||||
|
|
||||||
|
rawTxPartialSigned2 = self.nodes[2].signrawtransaction(rawTx2, inputs)
|
||||||
|
self.log.info(rawTxPartialSigned2)
|
||||||
|
assert_equal(rawTxPartialSigned2['complete'], False) #node2 only has one key, can't comp. sign the tx
|
||||||
|
rawTxComb = self.nodes[2].combinerawtransaction([rawTxPartialSigned1['hex'], rawTxPartialSigned2['hex']])
|
||||||
|
self.log.info(rawTxComb)
|
||||||
|
self.nodes[2].sendrawtransaction(rawTxComb)
|
||||||
|
rawTx2 = self.nodes[0].decoderawtransaction(rawTxComb)
|
||||||
|
self.sync_all()
|
||||||
|
self.nodes[0].generate(1)
|
||||||
|
self.sync_all()
|
||||||
|
assert_equal(self.nodes[0].getbalance(), bal+Decimal('50.00000000')+Decimal('2.19000000')) #block reward + tx
|
||||||
|
|
||||||
# getrawtransaction tests
|
# getrawtransaction tests
|
||||||
# 1. valid parameters - only supply txid
|
# 1. valid parameters - only supply txid
|
||||||
txHash = rawTx["hash"]
|
txHash = rawTx["hash"]
|
||||||
|
@ -43,22 +43,6 @@ class SignRawTransactionsTest(BitcoinTestFramework):
|
|||||||
# 2) No script verification error occurred
|
# 2) No script verification error occurred
|
||||||
assert 'errors' not in rawTxSigned
|
assert 'errors' not in rawTxSigned
|
||||||
|
|
||||||
# Check that signrawtransaction doesn't blow up on garbage merge attempts
|
|
||||||
dummyTxInconsistent = self.nodes[0].createrawtransaction([inputs[0]], outputs)
|
|
||||||
rawTxUnsigned = self.nodes[0].signrawtransaction(rawTx + dummyTxInconsistent, inputs)
|
|
||||||
|
|
||||||
assert 'complete' in rawTxUnsigned
|
|
||||||
assert_equal(rawTxUnsigned['complete'], False)
|
|
||||||
|
|
||||||
# Check that signrawtransaction properly merges unsigned and signed txn, even with garbage in the middle
|
|
||||||
rawTxSigned2 = self.nodes[0].signrawtransaction(rawTxUnsigned["hex"] + dummyTxInconsistent + rawTxSigned["hex"], inputs)
|
|
||||||
|
|
||||||
assert 'complete' in rawTxSigned2
|
|
||||||
assert_equal(rawTxSigned2['complete'], True)
|
|
||||||
|
|
||||||
assert 'errors' not in rawTxSigned2
|
|
||||||
|
|
||||||
|
|
||||||
def script_verification_error_test(self):
|
def script_verification_error_test(self):
|
||||||
"""Create and sign a raw transaction with valid (vin 0), invalid (vin 1) and one missing (vin 2) input script.
|
"""Create and sign a raw transaction with valid (vin 0), invalid (vin 1) and one missing (vin 2) input script.
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user