From 5059078cec99555c66e9289960d2845e1533d015 Mon Sep 17 00:00:00 2001 From: timemarkovqtum Date: Tue, 9 Jul 2024 16:27:00 +0200 Subject: [PATCH] Port rpc rawtransaction --- src/rpc/client.cpp | 77 +++ src/rpc/node.cpp | 430 +++++++++++++++ src/rpc/rawtransaction.cpp | 516 +++++++++++++++++- src/test/fuzz/addrman.cpp | 2 +- src/test/fuzz/float.cpp | 2 +- src/test/fuzz/integer.cpp | 2 +- src/test/fuzz/key.cpp | 2 +- src/test/fuzz/message.cpp | 2 +- src/test/fuzz/p2p_transport_serialization.cpp | 2 +- src/test/fuzz/parse_univalue.cpp | 2 +- src/test/fuzz/script.cpp | 2 +- src/test/fuzz/script_format.cpp | 2 +- src/test/fuzz/script_sign.cpp | 2 +- src/test/fuzz/transaction.cpp | 2 +- src/test/fuzz/util/mempool.cpp | 2 +- 15 files changed, 1008 insertions(+), 39 deletions(-) diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 5825efdf82..a07038dd61 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -47,8 +47,14 @@ static const CRPCConvertParam vRPCConvertParams[] = { "sendtoaddress", 8, "avoid_reuse" }, { "sendtoaddress", 9, "fee_rate"}, { "sendtoaddress", 10, "verbose"}, + { "sendtoaddress", 12, "changetosender" }, + { "splitutxosforaddress", 1, "minvalue" }, + { "splitutxosforaddress", 2, "maxvalue" }, + { "splitutxosforaddress", 3, "maxoutputs" }, + { "splitutxosforaddress", 4, "psbt" }, { "settxfee", 0, "amount" }, { "sethdseed", 0, "newkeypool" }, + { "getsubsidy", 0, "height" }, { "getreceivedbyaddress", 1, "minconf" }, { "getreceivedbyaddress", 2, "include_immature_coinbase" }, { "getreceivedbylabel", 1, "minconf" }, @@ -74,6 +80,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "listtransactions", 2, "skip" }, { "listtransactions", 3, "include_watchonly" }, { "walletpassphrase", 1, "timeout" }, + { "walletpassphrase", 2, "stakingonly" }, { "getblocktemplate", 0, "template_request" }, { "listsinceblock", 1, "target_confirmations" }, { "listsinceblock", 2, "include_watchonly" }, @@ -93,8 +100,35 @@ static const CRPCConvertParam vRPCConvertParams[] = { "scanblocks", 5, "options" }, { "scanblocks", 5, "filter_false_positives" }, { "scantxoutset", 1, "scanobjects" }, + { "sendmanywithdupes", 1, "amounts" }, + { "sendmanywithdupes", 2, "minconf" }, + { "sendmanywithdupes", 4, "subtractfeefrom" }, + { "sendmanywithdupes", 5, "replaceable" }, + { "sendmanywithdupes", 6, "conf_target" }, { "addmultisigaddress", 0, "nrequired" }, { "addmultisigaddress", 1, "keys" }, + ////////////////////////////////////////////////// // qtum + { "getaddresstxids", 0, "argument"}, + { "getaddressmempool", 0, "argument"}, + { "getaddressdeltas", 0, "argument"}, + { "getaddressbalance", 0, "argument"}, + { "getaddressutxos", 0, "argument"}, + { "getblockhashes", 0, "high"}, + { "getblockhashes", 1, "low"}, + { "getblockhashes", 2, "options"}, + { "getspentinfo", 0, "argument"}, + { "searchlogs", 0, "fromblock"}, + { "searchlogs", 1, "toblock"}, + { "searchlogs", 2, "addressfilter"}, + { "searchlogs", 3, "topicfilter"}, + { "searchlogs", 4, "minconf"}, + { "waitforlogs", 0, "fromblock"}, + { "waitforlogs", 1, "toblock"}, + { "waitforlogs", 2, "filter"}, + { "waitforlogs", 3, "minconf"}, + { "qrc20listtransactions", 2, "fromblock"}, + { "qrc20listtransactions", 3, "minconf"}, + ////////////////////////////////////////////////// { "createmultisig", 0, "nrequired" }, { "createmultisig", 1, "keys" }, { "listunspent", 0, "minconf" }, @@ -113,6 +147,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "getchaintxstats", 0, "nblocks" }, { "gettransaction", 1, "include_watchonly" }, { "gettransaction", 2, "verbose" }, + { "gettransaction", 3, "waitconf" }, { "getrawtransaction", 1, "verbosity" }, { "getrawtransaction", 1, "verbose" }, { "createrawtransaction", 0, "inputs" }, @@ -122,9 +157,11 @@ static const CRPCConvertParam vRPCConvertParams[] = { "decoderawtransaction", 1, "iswitness" }, { "signrawtransactionwithkey", 1, "privkeys" }, { "signrawtransactionwithkey", 2, "prevtxs" }, + { "signrawsendertransactionwithkey", 1, "privkeys" }, { "signrawtransactionwithwallet", 1, "prevtxs" }, { "sendrawtransaction", 1, "maxfeerate" }, { "sendrawtransaction", 2, "maxburnamount" }, + { "sendrawtransaction", 3, "showcontractdata" }, { "testmempoolaccept", 0, "rawtxs" }, { "testmempoolaccept", 1, "maxfeerate" }, { "submitpackage", 0, "package" }, @@ -275,6 +312,46 @@ static const CRPCConvertParam vRPCConvertParams[] = { "logging", 1, "exclude" }, { "disconnectnode", 1, "nodeid" }, { "upgradewallet", 0, "version" }, + { "createcontract", 1, "gaslimit" }, + { "createcontract", 2, "gasprice" }, + { "createcontract", 4, "broadcast" }, + { "createcontract", 5, "changetosender" }, + { "createcontract", 6, "psbt" }, + { "sendtocontract", 2, "amount" }, + { "sendtocontract", 3, "gaslimit" }, + { "sendtocontract", 4, "gasprice" }, + { "sendtocontract", 6, "broadcast" }, + { "sendtocontract", 7, "changetosender" }, + { "sendtocontract", 8, "psbt" }, + { "removedelegationforaddress", 1, "gaslimit" }, + { "removedelegationforaddress", 2, "gasprice" }, + { "setdelegateforaddress", 1, "fee" }, + { "setdelegateforaddress", 3, "gaslimit" }, + { "setdelegateforaddress", 4, "gasprice" }, + { "setsuperstakervaluesforaddress", 0, "params" }, + { "qrc20approve", 4, "gaslimit" }, + { "qrc20approve", 5, "gasprice" }, + { "qrc20approve", 6, "checkoutputs" }, + { "qrc20transfer", 4, "gaslimit" }, + { "qrc20transfer", 5, "gasprice" }, + { "qrc20transfer", 6, "checkoutputs" }, + { "qrc20transferfrom", 5, "gaslimit" }, + { "qrc20transferfrom", 6, "gasprice" }, + { "qrc20transferfrom", 7, "checkoutputs" }, + { "qrc20burn", 3, "gaslimit" }, + { "qrc20burn", 4, "gasprice" }, + { "qrc20burn", 5, "checkoutputs" }, + { "qrc20burnfrom", 4, "gaslimit" }, + { "qrc20burnfrom", 5, "gasprice" }, + { "qrc20burnfrom", 6, "checkoutputs" }, + { "callcontract", 3, "gaslimit" }, + { "callcontract", 4, "amount" }, + { "reservebalance", 0, "reserve"}, + { "reservebalance", 1, "amount"}, + { "listcontracts", 0, "start" }, + { "listcontracts", 1, "maxdisplay" }, + { "getstorage", 2, "index" }, + { "getstorage", 1, "blocknum" }, // Echo with conversion (For testing only) { "echojson", 0, "arg0" }, { "echojson", 1, "arg1" }, diff --git a/src/rpc/node.cpp b/src/rpc/node.cpp index fd4c2a0153..c4fd343b53 100644 --- a/src/rpc/node.cpp +++ b/src/rpc/node.cpp @@ -831,12 +831,442 @@ static RPCHelpMan getaddressbalance() }, }; } + +static RPCHelpMan getaddressutxos() +{ + return RPCHelpMan{"getaddressutxos", + "\nReturns all unspent outputs for an address (requires addressindex to be enabled).\n", + { + {"argument", RPCArg::Type::OBJ, RPCArg::Optional::NO, "Json object", + { + {"addresses", RPCArg::Type::ARR, RPCArg::Optional::NO, "The qtum addresses", + { + {"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The qtum address"}, + } + }, + {"chainInfo", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Include chain info with results"}, + } + } + }, + { + RPCResult{"if chainInfo is set to false", + RPCResult::Type::ARR, "", "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "address", "The address base58check encoded"}, + {RPCResult::Type::STR_HEX, "txid", "The output txid"}, + {RPCResult::Type::NUM, "height", "The block height"}, + {RPCResult::Type::NUM, "outputIndex", "The output index"}, + {RPCResult::Type::STR_HEX, "script", "The script hex encoded"}, + {RPCResult::Type::NUM, "satoshis", "The number of satoshis of the output"}, + {RPCResult::Type::BOOL, "isStake", "Is coinstake output"}, + }} + }, + }, + RPCResult{"if chainInfo is set to true", + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::ARR, "utxos", "List of utxo", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "address", "The address base58check encoded"}, + {RPCResult::Type::STR_HEX, "txid", "The output txid"}, + {RPCResult::Type::NUM, "height", "The block height"}, + {RPCResult::Type::NUM, "outputIndex", "The output index"}, + {RPCResult::Type::STR_HEX, "script", "The script hex encoded"}, + {RPCResult::Type::NUM, "satoshis", "The number of satoshis of the output"}, + {RPCResult::Type::BOOL, "isStake", "Is coinstake output"}, + }} + }}, + {RPCResult::Type::STR_HEX, "hash", "The tip block hash"}, + {RPCResult::Type::NUM, "height", "The tip block height"}, + }, + }, + }, + RPCExamples{ + HelpExampleCli("getaddressutxos", "'{\"addresses\": [\"QD1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"]}'") + + HelpExampleRpc("getaddressutxos", "{\"addresses\": [\"QD1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"]}") + + HelpExampleCli("getaddressutxos", "'{\"addresses\": [\"QD1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"], \"chainInfo\": true}'") + + HelpExampleRpc("getaddressutxos", "{\"addresses\": [\"QD1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"], \"chainInfo\": true}") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + ChainstateManager& chainman = EnsureAnyChainman(request.context); + + bool includeChainInfo = false; + if (request.params[0].isObject()) { + UniValue chainInfo = request.params[0].get_obj().find_value("chainInfo"); + if (chainInfo.isBool()) { + includeChainInfo = chainInfo.get_bool(); + } + } + + std::vector > addresses; + + if (!getAddressesFromParams(request.params, addresses)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + } + + std::vector > unspentOutputs; + + for (std::vector >::iterator it = addresses.begin(); it != addresses.end(); it++) { + if (!GetAddressUnspent((*it).first, (*it).second, unspentOutputs, chainman.m_blockman)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available for address"); + } + } + + std::sort(unspentOutputs.begin(), unspentOutputs.end(), heightSort); + + UniValue utxos(UniValue::VARR); + + for (std::vector >::const_iterator it=unspentOutputs.begin(); it!=unspentOutputs.end(); it++) { + UniValue output(UniValue::VOBJ); + std::string address; + if (!getAddressFromIndex(it->first.type, it->first.hashBytes, address)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown address type"); + } + + output.pushKV("address", address); + output.pushKV("txid", it->first.txhash.GetHex()); + output.pushKV("outputIndex", (int)it->first.index); + output.pushKV("script", HexStr(MakeUCharSpan(it->second.script))); + output.pushKV("satoshis", it->second.satoshis); + output.pushKV("height", it->second.blockHeight); + output.pushKV("isStake", it->second.coinStake); + utxos.push_back(output); + } + + if (includeChainInfo) { + UniValue result(UniValue::VOBJ); + result.pushKV("utxos", utxos); + + ChainstateManager& chainman = EnsureAnyChainman(request.context); + LOCK(cs_main); + CChain& active_chain = chainman.ActiveChain(); + result.pushKV("hash", active_chain.Tip()->GetBlockHash().GetHex()); + result.pushKV("height", (int)active_chain.Height()); + return result; + } else { + return utxos; + } +}, + }; +} + +static RPCHelpMan getaddressmempool() +{ + return RPCHelpMan{"getaddressmempool", + "\nReturns all mempool deltas for an address (requires addressindex to be enabled).\n", + { + {"argument", RPCArg::Type::OBJ, RPCArg::Optional::NO, "Json object", + { + {"addresses", RPCArg::Type::ARR, RPCArg::Optional::NO, "The qtum addresses", + { + {"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The qtum address"}, + } + }, + } + } + }, + RPCResult{ + RPCResult::Type::ARR, "", "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "address", "The qtum address"}, + {RPCResult::Type::STR_HEX, "txid", "The related txid"}, + {RPCResult::Type::NUM, "index", "The related input or output index"}, + {RPCResult::Type::NUM, "satoshis", "The difference of satoshis"}, + {RPCResult::Type::NUM, "timestamp", "The time the transaction entered the mempool (seconds)"}, + {RPCResult::Type::STR_HEX, "prevtxid", /*optional=*/true, "The previous txid (if spending)"}, + {RPCResult::Type::NUM, "prevout", /*optional=*/true, "The previous transaction output index (if spending)"}, + }} + } + }, + RPCExamples{ + HelpExampleCli("getaddressmempool", "'{\"addresses\": [\"QD1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"]}'") + + HelpExampleRpc("getaddressmempool", "{\"addresses\": [\"QD1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"]}") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + + const NodeContext& node = EnsureAnyNodeContext(request.context); + std::vector > addresses; + + if (!getAddressesFromParams(request.params, addresses)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + } + + std::vector > indexes; + + if (!node.mempool->getAddressIndex(addresses, indexes)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available for address"); + } + + std::sort(indexes.begin(), indexes.end(), timestampSort); + + UniValue result(UniValue::VARR); + + for (std::vector >::iterator it = indexes.begin(); + it != indexes.end(); it++) { + + std::string address; + if (!getAddressFromIndex(it->first.type, it->first.addressBytes, address)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown address type"); + } + + UniValue delta(UniValue::VOBJ); + delta.pushKV("address", address); + delta.pushKV("txid", it->first.txhash.GetHex()); + delta.pushKV("index", (int)it->first.index); + delta.pushKV("satoshis", it->second.amount); + delta.pushKV("timestamp", it->second.time); + if (it->second.amount < 0) { + delta.pushKV("prevtxid", it->second.prevhash.GetHex()); + delta.pushKV("prevout", (int)it->second.prevout); + } + result.push_back(delta); + } + + return result; +}, + }; +} + +static RPCHelpMan getspentinfo() +{ + return RPCHelpMan{"getspentinfo", + "\nReturns the txid and index where an output is spent.\n", + { + {"argument", RPCArg::Type::OBJ, RPCArg::Optional::NO, "Transaction data", + { + {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the txid"}, + {"index", RPCArg::Type::NUM, RPCArg::Optional::NO, "The start block height"}, + }, + }, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR_HEX, "txid", "The transaction id"}, + {RPCResult::Type::NUM, "index", "The spending input index"}, + {RPCResult::Type::NUM, "height", "The spending block height"}, + } + }, + RPCExamples{ + HelpExampleCli("getspentinfo", "'{\"txid\": \"0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9\", \"index\": 0}'") + + HelpExampleRpc("getspentinfo", "{\"txid\": \"0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9\", \"index\": 0}") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + const NodeContext& node = EnsureAnyNodeContext(request.context); + const CTxMemPool& mempool = EnsureMemPool(node); + ChainstateManager& chainman = EnsureAnyChainman(request.context); + + UniValue txidValue = request.params[0].get_obj().find_value("txid"); + UniValue indexValue = request.params[0].get_obj().find_value("index"); + + if (!txidValue.isStr() || !indexValue.isNum()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid txid or index"); + } + + uint256 txid = ParseHashV(txidValue, "txid"); + int outputIndex = indexValue.getInt(); + + CSpentIndexKey key(txid, outputIndex); + CSpentIndexValue value; + + if (!GetSpentIndex(key, value, mempool, chainman.m_blockman)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to get spent info"); + } + + UniValue obj(UniValue::VOBJ); + obj.pushKV("txid", value.txid.GetHex()); + obj.pushKV("index", (int)value.inputIndex); + obj.pushKV("height", value.blockHeight); + + return obj; +}, + }; +} + +static RPCHelpMan getaddresstxids() +{ + return RPCHelpMan{"getaddresstxids", + "\nReturns the txids for an address(es) (requires addressindex to be enabled).\n", + { + {"argument", RPCArg::Type::OBJ, RPCArg::Optional::NO, "Json object", + { + {"addresses", RPCArg::Type::ARR, RPCArg::Optional::NO, "The qtum addresses", + { + {"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The qtum address"}, + } + }, + {"start", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The start block height"}, + {"end", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The end block height"}, + } + } + }, + RPCResult{ + RPCResult::Type::ARR, "", "", + { + {RPCResult::Type::STR_HEX, "transactionid", "The transaction id"}, + } + }, + RPCExamples{ + HelpExampleCli("getaddresstxids", "'{\"addresses\": [\"QD1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"]}'") + + HelpExampleRpc("getaddresstxids", "{\"addresses\": [\"QD1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"]}") + + HelpExampleCli("getaddresstxids", "'{\"addresses\": [\"QD1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"], \"start\": 5000, \"end\": 5500}'") + + HelpExampleRpc("getaddresstxids", "{\"addresses\": [\"QD1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"], \"start\": 5000, \"end\": 5500}") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + ChainstateManager& chainman = EnsureAnyChainman(request.context); + + std::vector > addresses; + + if (!getAddressesFromParams(request.params, addresses)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + } + + int start = 0; + int end = 0; + if (request.params[0].isObject()) { + UniValue startValue = request.params[0].get_obj().find_value("start"); + UniValue endValue = request.params[0].get_obj().find_value("end"); + if (startValue.isNum() && endValue.isNum()) { + start = startValue.getInt(); + end = endValue.getInt(); + } + } + + std::vector > addressIndex; + + for (std::vector >::iterator it = addresses.begin(); it != addresses.end(); it++) { + if (start > 0 && end > 0) { + if (!GetAddressIndex((*it).first, (*it).second, addressIndex, chainman.m_blockman, start, end)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available for address"); + } + } else { + if (!GetAddressIndex((*it).first, (*it).second, addressIndex, chainman.m_blockman)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available for address"); + } + } + } + + std::set > txids; + UniValue result(UniValue::VARR); + + for (std::vector >::const_iterator it=addressIndex.begin(); it!=addressIndex.end(); it++) { + int height = it->first.blockHeight; + std::string txid = it->first.txhash.GetHex(); + + if (addresses.size() > 1) { + txids.insert(std::make_pair(height, txid)); + } else { + if (txids.insert(std::make_pair(height, txid)).second) { + result.push_back(txid); + } + } + } + + if (addresses.size() > 1) { + for (std::set >::const_iterator it=txids.begin(); it!=txids.end(); it++) { + result.push_back(it->second); + } + } + + return result; +}, + }; +} + +std::vector getListArgsType() +{ + std::vector ret = { "-rpcwallet", + "-rpcauth", + "-rpcwhitelist", + "-rpcallowip", + "-rpcbind", + "-blockfilterindex", + "-whitebind", + "-bind", + "-debug", + "-debugexclude", + "-stakingallowlist", + "-stakingexcludelist", + "-uacomment", + "-onlynet", + "-externalip", + "-loadblock", + "-addnode", + "-whitelist", + "-seednode", + "-connect", + "-deprecatedrpc", + "-wallet" }; + return ret; +} + +static RPCHelpMan listconf() +{ + return RPCHelpMan{"listconf", + "\nReturns the current options that qtumd was started with.\n", + {}, + RPCResult{ + RPCResult::Type::OBJ_DYN, "", "", + { + {RPCResult::Type::STR, "param", "Value for param"}, + } + }, + RPCExamples{ + HelpExampleCli("listconf", "") + + HelpExampleRpc("listconf", "") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + + UniValue ret(UniValue::VOBJ); + + std::vector paramListType = getListArgsType(); + for (const auto& arg : gArgs.getArgsList(paramListType)) { + UniValue listValues(UniValue::VARR); + for (const auto& value : arg.second) { + std::optional flags = gArgs.GetArgFlags('-' + arg.first); + if (flags) { + UniValue value_param = (*flags & gArgs.SENSITIVE) ? "****" : value; + listValues.push_back(value_param); + } + } + + int size = listValues.size(); + if(size > 0) + { + ret.pushKV(arg.first, size == 1 ? listValues[0] : listValues); + } + } + return ret; +}, + }; +} + void RegisterNodeRPCCommands(CRPCTable& t) { static const CRPCCommand commands[]{ {"control", &getmemoryinfo}, {"control", &logging}, + {"control", &getdgpinfo}, {"util", &getindexinfo}, + {"util", &getblockhashes}, + {"util", &getaddresstxids}, + {"util", &getaddressdeltas}, + {"util", &getaddressbalance}, + {"util", &getaddressutxos}, + {"util", &getaddressmempool}, + {"util", &getspentinfo}, + {"util", &listconf}, {"hidden", &setmocktime}, {"hidden", &mockscheduler}, {"hidden", &echo}, diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 634be2f7fb..215796e4cd 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -38,8 +38,11 @@ #include #include #include +#include #include #include +#include +#include #include #include @@ -57,10 +60,10 @@ static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& TxVerbosity verbosity = TxVerbosity::SHOW_DETAILS) { CHECK_NONFATAL(verbosity >= TxVerbosity::SHOW_DETAILS); - // Call into TxToUniv() in bitcoin-common to decode the transaction hex. + // Call into TxToUniv() in qtum-common to decode the transaction hex. // // Blockchain contextual information (confirmations and blocktime) is not - // available to code in bitcoin-common, so we query them here and push the + // available to code in qtum-common, so we query them here and push the // data into the returned UniValue. TxToUniv(tx, /*block_hash=*/uint256(), entry, /*include_hex=*/true, txundo, verbosity); @@ -81,6 +84,184 @@ static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& } } +void TxToJSONExpanded(const CTransaction& tx, const uint256 hashBlock, UniValue& entry, const CTxMemPool& mempool, node::BlockManager& blockman, + int nHeight = 0, int nConfirmations = 0, int nBlockTime = 0) +{ + + uint256 txid = tx.GetHash(); + entry.pushKV("hex", EncodeHexTx(tx)); + entry.pushKV("txid", tx.GetHash().GetHex()); + entry.pushKV("hash", tx.GetWitnessHash().GetHex()); + entry.pushKV("version", tx.nVersion); + entry.pushKV("size", (int)::GetSerializeSize(TX_WITH_WITNESS(tx))); + entry.pushKV("vsize", (GetTransactionWeight(tx) + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR); + entry.pushKV("weight", GetTransactionWeight(tx)); + entry.pushKV("locktime", (int64_t)tx.nLockTime); + + UniValue vin(UniValue::VARR); + for(const CTxIn& txin : tx.vin) { + UniValue in(UniValue::VOBJ); + if (tx.IsCoinBase()) + in.pushKV("coinbase", HexStr(txin.scriptSig)); + else { + in.pushKV("txid", txin.prevout.hash.GetHex()); + in.pushKV("vout", (int64_t)txin.prevout.n); + UniValue o(UniValue::VOBJ); + o.pushKV("asm", ScriptToAsmStr(txin.scriptSig, true)); + o.pushKV("hex", HexStr(txin.scriptSig)); + in.pushKV("scriptSig", o); + if (!txin.scriptWitness.IsNull()) { + UniValue txinwitness(UniValue::VARR); + for (const auto& item : txin.scriptWitness.stack) { + txinwitness.push_back(HexStr(item)); + } + in.pushKV("txinwitness", txinwitness); + } + // Add address and value info if spentindex enabled + CSpentIndexValue spentInfo; + CSpentIndexKey spentKey(txin.prevout.hash, txin.prevout.n); + if (GetSpentIndex(spentKey, spentInfo, mempool, blockman)) { + in.pushKV("value", ValueFromAmount(spentInfo.satoshis)); + in.pushKV("valueSat", spentInfo.satoshis); + if (spentInfo.addressType == 1) { + std::vector addressBytes(spentInfo.addressHash.begin(), spentInfo.addressHash.begin() + 20); + in.pushKV("address", EncodeDestination(CTxDestination(PKHash(uint160(addressBytes))))); + } else if (spentInfo.addressType == 2) { + std::vector addressBytes(spentInfo.addressHash.begin(), spentInfo.addressHash.begin() + 20); + in.pushKV("address", EncodeDestination(CTxDestination(ScriptHash(uint160(addressBytes))))); + } else if (spentInfo.addressType == 3) { + in.pushKV("address", EncodeDestination(CTxDestination(WitnessV0ScriptHash(spentInfo.addressHash)))); + } else if (spentInfo.addressType == 4) { + std::vector addressBytes(spentInfo.addressHash.begin(), spentInfo.addressHash.begin() + 20); + in.pushKV("address", EncodeDestination(CTxDestination(WitnessV0KeyHash(uint160(addressBytes))))); + } + } + } + in.pushKV("sequence", (int64_t)txin.nSequence); + vin.push_back(in); + } + entry.pushKV("vin", vin); + UniValue vout(UniValue::VARR); + for (unsigned int i = 0; i < tx.vout.size(); i++) { + const CTxOut& txout = tx.vout[i]; + UniValue out(UniValue::VOBJ); + out.pushKV("value", ValueFromAmount(txout.nValue)); + out.pushKV("valueSat", txout.nValue); + out.pushKV("n", (int64_t)i); + UniValue o(UniValue::VOBJ); + ScriptToUniv(txout.scriptPubKey, o, true, true); + out.pushKV("scriptPubKey", o); + + // Add spent information if spentindex is enabled + CSpentIndexValue spentInfo; + CSpentIndexKey spentKey(txid, i); + if (GetSpentIndex(spentKey, spentInfo, mempool, blockman)) { + out.pushKV("spentTxId", spentInfo.txid.GetHex()); + out.pushKV("spentIndex", (int)spentInfo.inputIndex); + out.pushKV("spentHeight", spentInfo.blockHeight); + } + + vout.push_back(out); + } + entry.pushKV("vout", vout); + + if (!hashBlock.IsNull()) { + entry.pushKV("blockhash", hashBlock.GetHex()); + + if (nConfirmations > 0) { + entry.pushKV("height", nHeight); + entry.pushKV("confirmations", nConfirmations); + entry.pushKV("time", nBlockTime); + entry.pushKV("blocktime", nBlockTime); + } else { + entry.pushKV("height", -1); + entry.pushKV("confirmations", 0); + } + } +} + +static RPCHelpMan gethexaddress() +{ + return RPCHelpMan{"gethexaddress", + "\nConverts a base58 pubkeyhash address to a hex address for use in smart contracts.\n", + { + {"address", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The base58 address"}, + }, + RPCResult{ + RPCResult::Type::STR_HEX, "hexaddress", "The raw hex pubkeyhash address for use in smart contracts"}, + RPCExamples{ + HelpExampleCli("gethexaddress", "\"address\"") + + HelpExampleRpc("gethexaddress", "\"address\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + + CTxDestination dest = DecodeDestination(request.params[0].get_str()); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Qtum address"); + } + + if(!std::holds_alternative(dest)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Only pubkeyhash addresses are supported"); + PKHash keyID = std::get(dest); + + return ToKeyID(keyID).GetReverseHex(); +}, + }; +} + +static RPCHelpMan fromhexaddress() +{ + return RPCHelpMan{"fromhexaddress", + "\nConverts a raw hex address to a base58 pubkeyhash address\n", + { + {"hexaddress", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The raw hex address"}, + }, + RPCResult{ + RPCResult::Type::STR, "address", "The base58 pubkeyhash address"}, + RPCExamples{ + HelpExampleCli("fromhexaddress", "\"hexaddress\"") + + HelpExampleRpc("fromhexaddress", "\"hexaddress\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + + if (request.params[0].get_str().size() != 40) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid pubkeyhash hex size (should be 40 hex characters)"); + CKeyID id; + id.SetReverseHex(request.params[0].get_str()); + PKHash raw(id); + CTxDestination dest(raw); + + return EncodeDestination(dest); +}, + }; +} + +static std::vector DecodeExpanded(bool isExpanded, bool isVin) +{ + if(isExpanded) + { + if(isVin) { + return { + {RPCResult::Type::STR_AMOUNT, "value", /*optional=*/true, "The value in " + CURRENCY_UNIT + " (only if address index is enabled)"}, + {RPCResult::Type::NUM, "valueSat", /*optional=*/true, "The value in Sat (only if address index is enabled)"}, + {RPCResult::Type::STR, "address", /*optional=*/true, "The Qtum address (only if address index is enabled)"}, + }; + } + else { + return { + {RPCResult::Type::NUM, "valueSat", /*optional=*/true, "The value in Sat (only if address index is enabled)"}, + {RPCResult::Type::STR_HEX, "spentTxId", /*optional=*/true, "The spent txid (only if address index is enabled)"}, + {RPCResult::Type::NUM, "spentIndex", /*optional=*/true, "The spent index (only if address index is enabled)"}, + {RPCResult::Type::NUM, "spentHeight", /*optional=*/true, "The spent height (only if address index is enabled)"}, + }; + } + + } + return {}; +} + static std::vector ScriptPubKeyDoc() { return { @@ -92,7 +273,7 @@ static std::vector ScriptPubKeyDoc() { }; } -static std::vector DecodeTxDoc(const std::string& txid_field_doc) +static std::vector DecodeTxDoc(const std::string& txid_field_doc, bool isExpanded = false) { return { {RPCResult::Type::STR_HEX, "txid", txid_field_doc}, @@ -106,28 +287,36 @@ static std::vector DecodeTxDoc(const std::string& txid_field_doc) { {RPCResult::Type::OBJ, "", "", { - {RPCResult::Type::STR_HEX, "coinbase", /*optional=*/true, "The coinbase value (only if coinbase transaction)"}, - {RPCResult::Type::STR_HEX, "txid", /*optional=*/true, "The transaction id (if not coinbase transaction)"}, - {RPCResult::Type::NUM, "vout", /*optional=*/true, "The output number (if not coinbase transaction)"}, - {RPCResult::Type::OBJ, "scriptSig", /*optional=*/true, "The script (if not coinbase transaction)", + Cat>( { - {RPCResult::Type::STR, "asm", "Disassembly of the signature script"}, - {RPCResult::Type::STR_HEX, "hex", "The raw signature script bytes, hex-encoded"}, - }}, - {RPCResult::Type::ARR, "txinwitness", /*optional=*/true, "", - { - {RPCResult::Type::STR_HEX, "hex", "hex-encoded witness data (if any)"}, - }}, - {RPCResult::Type::NUM, "sequence", "The script sequence number"}, + {RPCResult::Type::STR_HEX, "coinbase", /*optional=*/true, "The coinbase value (only if coinbase transaction)"}, + {RPCResult::Type::STR_HEX, "txid", /*optional=*/true, "The transaction id (if not coinbase transaction)"}, + {RPCResult::Type::NUM, "vout", /*optional=*/true, "The output number (if not coinbase transaction)"}, + {RPCResult::Type::OBJ, "scriptSig", /*optional=*/true, "The script (if not coinbase transaction)", + { + {RPCResult::Type::STR, "asm", "Disassembly of the signature script"}, + {RPCResult::Type::STR_HEX, "hex", "The raw signature script bytes, hex-encoded"}, + }}, + {RPCResult::Type::ARR, "txinwitness", /*optional=*/true, "", + { + {RPCResult::Type::STR_HEX, "hex", "hex-encoded witness data (if any)"}, + }}, + {RPCResult::Type::NUM, "sequence", "The script sequence number"}, + }, + DecodeExpanded(isExpanded, true)), }}, }}, {RPCResult::Type::ARR, "vout", "", { {RPCResult::Type::OBJ, "", "", { - {RPCResult::Type::STR_AMOUNT, "value", "The value in " + CURRENCY_UNIT}, - {RPCResult::Type::NUM, "n", "index"}, - {RPCResult::Type::OBJ, "scriptPubKey", "", ScriptPubKeyDoc()}, + Cat>( + { + {RPCResult::Type::STR_AMOUNT, "value", "The value in " + CURRENCY_UNIT}, + {RPCResult::Type::NUM, "n", "index"}, + {RPCResult::Type::OBJ, "scriptPubKey", "", ScriptPubKeyDoc()}, + }, + DecodeExpanded(isExpanded, false)), }}, }}, }; @@ -155,7 +344,7 @@ static std::vector CreateTxDoc() { {"", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::OMITTED, "", { - {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT}, + {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "A key-value pair. The key (string) is the qtum address, the value (float or string) is the amount in " + CURRENCY_UNIT}, }, }, {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", @@ -163,6 +352,24 @@ static std::vector CreateTxDoc() {"data", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A key-value pair. The key must be \"data\", the value is hex-encoded data"}, }, }, + {"contract", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "(send to contract)", + { + {"contractAddress", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Valid contract address (valid hash160 hex data)"}, + {"data", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Hex data to add in the call output"}, + {"amount", RPCArg::Type::AMOUNT, RPCArg::Default{0}, "Value in QTUM to send with the call, should be a valid amount, default 0"}, + {"gasLimit", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The gas limit for the transaction"}, + {"gasPrice", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The gas price for the transaction"}, + {"senderAddress", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The qtum address that will be used to create the contract."}, + }, + }, + {"contract", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "(create contract)", + { + {"bytecode", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "contract bytcode."}, + {"gasLimit", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The gas limit for the transaction"}, + {"gasPrice", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The gas price for the transaction"}, + {"senderAddress", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The qtum address that will be used to create the contract."}, + }, + }, }, RPCArgOptions{.skip_type_check = true}}, {"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"}, @@ -291,9 +498,10 @@ static RPCHelpMan getrawtransaction() {RPCResult::Type::NUM, "confirmations", /*optional=*/true, "The confirmations"}, {RPCResult::Type::NUM_TIME, "blocktime", /*optional=*/true, "The block time expressed in " + UNIX_EPOCH_TIME}, {RPCResult::Type::NUM, "time", /*optional=*/true, "Same as \"blocktime\""}, + {RPCResult::Type::NUM, "height", /*optional=*/true, "The block height"}, {RPCResult::Type::STR_HEX, "hex", "The serialized, hex-encoded data for 'txid'"}, }, - DecodeTxDoc(/*txid_field_doc=*/"The transaction id (same as provided)")), + DecodeTxDoc(/*txid_field_doc=*/"The transaction id (same as provided)", true)), }, RPCResult{"for verbosity = 2", RPCResult::Type::OBJ, "", "", @@ -327,6 +535,7 @@ static RPCHelpMan getrawtransaction() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const NodeContext& node = EnsureAnyNodeContext(request.context); + const CTxMemPool& mempool = EnsureMemPool(node); ChainstateManager& chainman = EnsureChainman(node); uint256 hash = ParseHashV(request.params[0], "parameter 1"); @@ -386,6 +595,29 @@ static RPCHelpMan getrawtransaction() return EncodeHexTx(*tx); } + //////////////////////////////////////////////////////// // qtum + int nHeight = 0; + int nConfirmations = 0; + int nBlockTime = 0; + if(fAddressIndex) { + LOCK(cs_main); + node::BlockMap::iterator mi = chainman.BlockIndex().find(hash_block); + if (mi != chainman.BlockIndex().end()) { + CBlockIndex* pindex = &((*mi).second); + if (chainman.ActiveChain().Contains(pindex)) { + nHeight = pindex->nHeight; + nConfirmations = 1 + chainman.ActiveChain().Height() - pindex->nHeight; + nBlockTime = pindex->GetBlockTime(); + } else { + nHeight = -1; + nConfirmations = 0; + nBlockTime = pindex->GetBlockTime(); + } + } + } + //////////////////////////////////////////////////////// + + UniValue result(UniValue::VOBJ); if (blockindex) { LOCK(cs_main); @@ -398,6 +630,7 @@ static RPCHelpMan getrawtransaction() } if (verbosity == 1) { TxToJSON(*tx, hash_block, result, chainman.ActiveChainstate()); + if (fAddressIndex) TxToJSONExpanded(*tx, hash_block, result, mempool, chainman.m_blockman, nHeight, nConfirmations, nBlockTime); return result; } @@ -407,6 +640,7 @@ static RPCHelpMan getrawtransaction() if (tx->IsCoinBase() || !blockindex || WITH_LOCK(::cs_main, return chainman.m_blockman.IsBlockPruned(*blockindex)) || !(chainman.m_blockman.UndoReadFromDisk(blockUndo, *blockindex) && chainman.m_blockman.ReadBlockFromDisk(block, *blockindex))) { TxToJSON(*tx, hash_block, result, chainman.ActiveChainstate()); + if (fAddressIndex) TxToJSONExpanded(*tx, hash_block, result, mempool, chainman.m_blockman, nHeight, nConfirmations, nBlockTime); return result; } @@ -417,11 +651,143 @@ static RPCHelpMan getrawtransaction() undoTX = &blockUndo.vtxundo.at(it - block.vtx.begin() - 1); } TxToJSON(*tx, hash_block, result, chainman.ActiveChainstate(), undoTX, TxVerbosity::SHOW_DETAILS_AND_PREVOUT); + if (fAddressIndex) TxToJSONExpanded(*tx, hash_block, result, mempool, chainman.m_blockman, nHeight, nConfirmations, nBlockTime); return result; }, }; } +class RawContract : public IRawContract +{ +public: + RawContract(ChainstateManager& _chainman): + chainman(_chainman) + {} + + void addContract(CMutableTransaction& rawTx, const UniValue& Contract) override + { + if(!Contract.isObject()) + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, need to be object: contract")); + + // Get dgp gas limit and gas price + LOCK(cs_main); + CChain& active_chain = chainman.ActiveChain(); + QtumDGP qtumDGP(globalState.get(), chainman.ActiveChainstate(), fGettingValuesDGP); + uint64_t blockGasLimit = qtumDGP.getBlockGasLimit(active_chain.Height()); + uint64_t minGasPrice = CAmount(qtumDGP.getMinGasPrice(active_chain.Height())); + CAmount nGasPrice = (minGasPrice>DEFAULT_GAS_PRICE)?minGasPrice:DEFAULT_GAS_PRICE; + + bool createContract = Contract.exists("bytecode") && Contract["bytecode"].isStr(); + CScript scriptPubKey; + CAmount nAmount = 0; + + // Get gas limit + uint64_t nGasLimit=createContract ? DEFAULT_GAS_LIMIT_OP_CREATE : DEFAULT_GAS_LIMIT_OP_SEND; + if (Contract.exists("gasLimit")){ + nGasLimit = Contract["gasLimit"].getInt(); + if (nGasLimit > blockGasLimit) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid value for gasLimit (Maximum is: "+i64tostr(blockGasLimit)+")"); + if (nGasLimit < MINIMUM_GAS_LIMIT) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid value for gasLimit (Minimum is: "+i64tostr(MINIMUM_GAS_LIMIT)+")"); + if (nGasLimit <= 0) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid value for gasLimit"); + } + + // Get gas price + if (Contract.exists("gasPrice")){ + UniValue uGasPrice = Contract["gasPrice"]; + std::optional optGasPrice = ParseMoney(uGasPrice.getValStr()); + if(!optGasPrice) + { + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid value for gasPrice"); + } + nGasPrice = (uint64_t)(optGasPrice.value_or(0)); + CAmount maxRpcGasPrice = gArgs.GetIntArg("-rpcmaxgasprice", MAX_RPC_GAS_PRICE); + if (nGasPrice > (int64_t)maxRpcGasPrice) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid value for gasPrice, Maximum allowed in RPC calls is: "+FormatMoney(maxRpcGasPrice)+" (use -rpcmaxgasprice to change it)"); + if (nGasPrice < (int64_t)minGasPrice) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid value for gasPrice (Minimum is: "+FormatMoney(minGasPrice)+")"); + if (nGasPrice <= 0) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid value for gasPrice"); + } + + // Get sender address + bool fHasSender=false; + CTxDestination senderAddress; + if (Contract.exists("senderAddress")){ + senderAddress = DecodeDestination(Contract["senderAddress"].get_str()); + if (!IsValidDestination(senderAddress)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Qtum address to send from"); + if (!IsValidContractSenderAddress(senderAddress)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid contract sender address. Only P2PK and P2PKH allowed"); + else + fHasSender=true; + } + + if(createContract) + { + // Get the new contract bytecode + if(!Contract.exists("bytecode") || !Contract["bytecode"].isStr()) + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, bytecode is mandatory.")); + + std::string bytecodehex = Contract["bytecode"].get_str(); + if(bytecodehex.size() % 2 != 0 || !CheckHex(bytecodehex)) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid bytecode (bytecode not hex)"); + + // Add create contract output + scriptPubKey = CScript() << CScriptNum(VersionVM::GetEVMDefault().toRaw()) << CScriptNum(nGasLimit) << CScriptNum(nGasPrice) << ParseHex(bytecodehex) <addressInUse(addrAccount)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "contract address does not exist"); + + // Get the contract data + if(!Contract.exists("data") || !Contract["data"].isStr()) + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, contract data is mandatory.")); + + std::string datahex = Contract["data"].get_str(); + if(datahex.size() % 2 != 0 || !CheckHex(datahex)) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid data (data not hex)"); + + // Get amount + if (Contract.exists("amount")){ + nAmount = AmountFromValue(Contract["amount"]); + if (nAmount < 0) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for call contract"); + } + + // Add call contract output + scriptPubKey = CScript() << CScriptNum(VersionVM::GetEVMDefault().toRaw()) << CScriptNum(nGasLimit) << CScriptNum(nGasPrice) << ParseHex(datahex) << ParseHex(contractaddress) << OP_CALL; + } + + // Build op_sender script + if(fHasSender && active_chain.Height() >= Params().GetConsensus().QIP5Height) + { + if(!std::holds_alternative(senderAddress)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Only pubkeyhash addresses are supported"); + PKHash keyID = std::get(senderAddress); + std::vector scriptSig; + scriptPubKey = (CScript() << CScriptNum(addresstype::PUBKEYHASH) << ToByteVector(keyID) << ToByteVector(scriptSig) << OP_SENDER) + scriptPubKey; + } + + CTxOut out(nAmount, scriptPubKey); + rawTx.vout.push_back(out); + } + +private: + ChainstateManager& chainman; +}; + static RPCHelpMan createrawtransaction() { return RPCHelpMan{"createrawtransaction", @@ -437,8 +803,16 @@ static RPCHelpMan createrawtransaction() RPCExamples{ HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"address\\\":0.01}]\"") + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"contract\\\":{\\\"contractAddress\\\":\\\"mycontract\\\"," + "\\\"data\\\":\\\"00\\\", \\\"gasLimit\\\":250000, \\\"gasPrice\\\":0.00000040, \\\"amount\\\":0}}]\"") + + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"contract\\\":{\\\"bytecode\\\":\\\"contractbytecode\\\"," + "\\\"gasLimit\\\":2500000, \\\"gasPrice\\\":0.00000040, \\\"senderAddress\\\":\\\"QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\\\"}}]\"") + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"address\\\":0.01}]\"") + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"contract\\\":{\\\"contractAddress\\\":\\\"mycontract\\\"," + "\\\"data\\\":\\\"00\\\", \\\"gasLimit\\\":250000, \\\"gasPrice\\\":0.00000040, \\\"amount\\\":0}}]\"") + + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"contract\\\":{\\\"bytecode\\\":\\\"contractbytecode\\\"," + "\\\"gasLimit\\\":2500000, \\\"gasPrice\\\":0.00000040, \\\"senderAddress\\\":\\\"QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\\\"}}]\"") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { @@ -446,7 +820,9 @@ 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); + ChainstateManager& chainman = EnsureAnyChainman(request.context); + RawContract rawContract(chainman); + CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf, &rawContract); return EncodeHexTx(CTransaction(rawTx)); }, @@ -508,7 +884,7 @@ static RPCHelpMan decodescript() {RPCResult::Type::STR, "asm", "Script public key"}, {RPCResult::Type::STR, "desc", "Inferred descriptor for the script"}, {RPCResult::Type::STR, "type", "The output type (e.g. " + GetAllOutputTypes() + ")"}, - {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"}, + {RPCResult::Type::STR, "address", /*optional=*/true, "The Qtum address (only if a well-defined address exists)"}, {RPCResult::Type::STR, "p2sh", /*optional=*/true, "address of P2SH script wrapping this redeem script (not returned for types that should not be wrapped)"}, {RPCResult::Type::OBJ, "segwit", /*optional=*/true, @@ -517,7 +893,7 @@ static RPCHelpMan decodescript() {RPCResult::Type::STR, "asm", "String representation of the script public key"}, {RPCResult::Type::STR_HEX, "hex", "Hex string of the script public key"}, {RPCResult::Type::STR, "type", "The type of the script public key (e.g. witness_v0_keyhash or witness_v0_scripthash)"}, - {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"}, + {RPCResult::Type::STR, "address", /*optional=*/true, "The Qtum address (only if a well-defined address exists)"}, {RPCResult::Type::STR, "desc", "Inferred descriptor for the script"}, {RPCResult::Type::STR, "p2sh-segwit", "address of the P2SH script wrapping this witness redeem script"}, }}, @@ -556,6 +932,10 @@ static RPCHelpMan decodescript() case TxoutType::SCRIPTHASH: case TxoutType::WITNESS_UNKNOWN: case TxoutType::WITNESS_V1_TAPROOT: + case TxoutType::CREATE_SENDER: + case TxoutType::CALL_SENDER: + case TxoutType::CREATE: + case TxoutType::CALL: // Should not be wrapped return false; } // no default case, so the compiler can warn about missing cases @@ -598,6 +978,10 @@ static RPCHelpMan decodescript() case TxoutType::WITNESS_V0_KEYHASH: case TxoutType::WITNESS_V0_SCRIPTHASH: case TxoutType::WITNESS_V1_TAPROOT: + case TxoutType::CREATE_SENDER: + case TxoutType::CALL_SENDER: + case TxoutType::CREATE: + case TxoutType::CALL: // Should not be wrapped return false; } // no default case, so the compiler can warn about missing cases @@ -808,12 +1192,85 @@ static RPCHelpMan signrawtransactionwithkey() ParsePrevouts(request.params[2], &keystore, coins); UniValue result(UniValue::VOBJ); + CheckSenderSignatures(mtx); SignTransaction(mtx, &keystore, coins, request.params[3], result); return result; }, }; } +static RPCHelpMan signrawsendertransactionwithkey() +{ + return RPCHelpMan{"signrawsendertransactionwithkey", + "\nSign OP_SENDER outputs for raw transaction (serialized, hex-encoded).\n" + "The second argument is an array of base58-encoded private\n" + "keys that will be the only keys used to sign the transaction.\n", + { + {"hexstring", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction hex string"}, + {"privkeys", RPCArg::Type::ARR, RPCArg::Optional::NO, "A json array of base58-encoded private keys for signing", + { + {"privatekey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "private key in base58-encoding"}, + }, + }, + {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"ALL"}, "The signature hash type. Must be one of:\n" + " \"ALL\"\n" + " \"NONE\"\n" + " \"SINGLE\"\n" + " \"ALL|ANYONECANPAY\"\n" + " \"NONE|ANYONECANPAY\"\n" + " \"SINGLE|ANYONECANPAY\"\n" + }, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {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::ARR, "errors", /*optional=*/true, "Script verification errors (if there are any)", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::NUM, "amount", "The amount of the output"}, + {RPCResult::Type::STR_HEX, "scriptPubKey", "The hex-encoded public key script of the output"}, + {RPCResult::Type::STR, "error", "Verification or signing error related to the output"}, + }}, + }}, + } + }, + RPCExamples{ + HelpExampleCli("signrawsendertransactionwithkey", "\"myhex\" \"[\\\"key1\\\",\\\"key2\\\"]\"") + + HelpExampleRpc("signrawsendertransactionwithkey", "\"myhex\", \"[\\\"key1\\\",\\\"key2\\\"]\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + + CMutableTransaction mtx; + if (!DecodeHexTx(mtx, request.params[0].get_str(), true)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + } + + FillableSigningProvider keystore; + const UniValue& keys = request.params[1].get_array(); + for (unsigned int idx = 0; idx < keys.size(); ++idx) { + UniValue k = keys[idx]; + CKey key = DecodeSecret(k.get_str()); + if (!key.IsValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key"); + } + keystore.AddKey(key); + } + + UniValue result(UniValue::VOBJ); + UniValue sigHashType = "ALL"; + if (!request.params[2].isNull()) { + sigHashType = request.params[2]; + } + SignTransactionOutput(mtx, &keystore, sigHashType, result); + return result; +}, + }; +} + const RPCResult decodepsbt_inputs{ RPCResult::Type::ARR, "inputs", "", { @@ -832,7 +1289,7 @@ const RPCResult decodepsbt_inputs{ {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"}, {RPCResult::Type::STR_HEX, "hex", "The raw public key script bytes, hex-encoded"}, {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"}, - {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"}, + {RPCResult::Type::STR, "address", /*optional=*/true, "The Qtum address (only if a well-defined address exists)"}, }}, }}, {RPCResult::Type::OBJ_DYN, "partial_signatures", /*optional=*/true, "", @@ -1012,7 +1469,7 @@ static RPCHelpMan decodepsbt() { return RPCHelpMan{ "decodepsbt", - "Return a JSON object representing the serialized, base64-encoded partially signed Bitcoin transaction.", + "Return a JSON object representing the serialized, base64-encoded partially signed Qtum transaction.", { {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The PSBT base64 string"}, }, @@ -1452,7 +1909,7 @@ static RPCHelpMan decodepsbt() static RPCHelpMan combinepsbt() { return RPCHelpMan{"combinepsbt", - "\nCombine multiple partially signed Bitcoin transactions into one transaction.\n" + "\nCombine multiple partially signed Qtum transactions into one transaction.\n" "Implements the Combiner role.\n", { {"txs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The base64 strings of partially signed transactions", @@ -1573,7 +2030,9 @@ 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); + ChainstateManager& chainman = EnsureAnyChainman(request.context); + RawContract rawContract(chainman); + CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf, &rawContract); // Make a blank psbt PartiallySignedTransaction psbtx; @@ -2010,6 +2469,7 @@ void RegisterRawTransactionRPCCommands(CRPCTable& t) {"rawtransactions", &decodescript}, {"rawtransactions", &combinerawtransaction}, {"rawtransactions", &signrawtransactionwithkey}, + {"rawtransactions", &signrawsendertransactionwithkey}, {"rawtransactions", &decodepsbt}, {"rawtransactions", &combinepsbt}, {"rawtransactions", &finalizepsbt}, @@ -2019,6 +2479,8 @@ void RegisterRawTransactionRPCCommands(CRPCTable& t) {"rawtransactions", &descriptorprocesspsbt}, {"rawtransactions", &joinpsbts}, {"rawtransactions", &analyzepsbt}, + {"rawtransactions", &gethexaddress}, + {"rawtransactions", &fromhexaddress}, }; for (const auto& c : commands) { t.appendCommand(c.name, &c); diff --git a/src/test/fuzz/addrman.cpp b/src/test/fuzz/addrman.cpp index 8a54cc656d..a54df8ff3a 100644 --- a/src/test/fuzz/addrman.cpp +++ b/src/test/fuzz/addrman.cpp @@ -35,7 +35,7 @@ int32_t GetCheckRatio() void initialize_addrman() { - static const auto testing_setup = MakeNoLogFileContext<>(ChainType::REGTEST); + static const auto testing_setup = MakeNoLogFileContext<>(ChainType::UNITTEST); g_setup = testing_setup.get(); } diff --git a/src/test/fuzz/float.cpp b/src/test/fuzz/float.cpp index 6897e81494..ee701a1102 100644 --- a/src/test/fuzz/float.cpp +++ b/src/test/fuzz/float.cpp @@ -18,7 +18,7 @@ FUZZ_TARGET(float) { const double d{[&] { - double tmp; + double tmp = 0; CallOneOf( fuzzed_data_provider, // an actual number diff --git a/src/test/fuzz/integer.cpp b/src/test/fuzz/integer.cpp index db246bb84e..160ad46c8e 100644 --- a/src/test/fuzz/integer.cpp +++ b/src/test/fuzz/integer.cpp @@ -42,7 +42,7 @@ void initialize_integer() { - SelectParams(ChainType::REGTEST); + SelectParams(ChainType::UNITTEST); } FUZZ_TARGET(integer, .init = initialize_integer) diff --git a/src/test/fuzz/key.cpp b/src/test/fuzz/key.cpp index 9e1e318e02..f61991cd82 100644 --- a/src/test/fuzz/key.cpp +++ b/src/test/fuzz/key.cpp @@ -33,7 +33,7 @@ void initialize_key() { ECC_Start(); - SelectParams(ChainType::REGTEST); + SelectParams(ChainType::UNITTEST); } FUZZ_TARGET(key, .init = initialize_key) diff --git a/src/test/fuzz/message.cpp b/src/test/fuzz/message.cpp index b5c95441f8..d9a6a0d34e 100644 --- a/src/test/fuzz/message.cpp +++ b/src/test/fuzz/message.cpp @@ -20,7 +20,7 @@ void initialize_message() { ECC_Start(); - SelectParams(ChainType::REGTEST); + SelectParams(ChainType::UNITTEST); } FUZZ_TARGET(message, .init = initialize_message) diff --git a/src/test/fuzz/p2p_transport_serialization.cpp b/src/test/fuzz/p2p_transport_serialization.cpp index a205ce19f4..2fb4b8820b 100644 --- a/src/test/fuzz/p2p_transport_serialization.cpp +++ b/src/test/fuzz/p2p_transport_serialization.cpp @@ -26,7 +26,7 @@ std::vector g_all_messages; void initialize_p2p_transport_serialization() { ECC_Start(); - SelectParams(ChainType::REGTEST); + SelectParams(ChainType::UNITTEST); g_all_messages = getAllNetMessageTypes(); std::sort(g_all_messages.begin(), g_all_messages.end()); } diff --git a/src/test/fuzz/parse_univalue.cpp b/src/test/fuzz/parse_univalue.cpp index a3d6ab6375..19c499e71b 100644 --- a/src/test/fuzz/parse_univalue.cpp +++ b/src/test/fuzz/parse_univalue.cpp @@ -13,7 +13,7 @@ void initialize_parse_univalue() { - SelectParams(ChainType::REGTEST); + SelectParams(ChainType::UNITTEST); } FUZZ_TARGET(parse_univalue, .init = initialize_parse_univalue) diff --git a/src/test/fuzz/script.cpp b/src/test/fuzz/script.cpp index fe41a8c6ae..503a1ddc93 100644 --- a/src/test/fuzz/script.cpp +++ b/src/test/fuzz/script.cpp @@ -33,7 +33,7 @@ void initialize_script() { - SelectParams(ChainType::REGTEST); + SelectParams(ChainType::UNITTEST); } FUZZ_TARGET(script, .init = initialize_script) diff --git a/src/test/fuzz/script_format.cpp b/src/test/fuzz/script_format.cpp index 10150dcd7f..c4251101b0 100644 --- a/src/test/fuzz/script_format.cpp +++ b/src/test/fuzz/script_format.cpp @@ -15,7 +15,7 @@ void initialize_script_format() { - SelectParams(ChainType::REGTEST); + SelectParams(ChainType::UNITTEST); } FUZZ_TARGET(script_format, .init = initialize_script_format) diff --git a/src/test/fuzz/script_sign.cpp b/src/test/fuzz/script_sign.cpp index 9ae150e553..67b8c5935f 100644 --- a/src/test/fuzz/script_sign.cpp +++ b/src/test/fuzz/script_sign.cpp @@ -27,7 +27,7 @@ void initialize_script_sign() { ECC_Start(); - SelectParams(ChainType::REGTEST); + SelectParams(ChainType::UNITTEST); } FUZZ_TARGET(script_sign, .init = initialize_script_sign) diff --git a/src/test/fuzz/transaction.cpp b/src/test/fuzz/transaction.cpp index 2a043f7458..ae7bd8753b 100644 --- a/src/test/fuzz/transaction.cpp +++ b/src/test/fuzz/transaction.cpp @@ -23,7 +23,7 @@ void initialize_transaction() { - SelectParams(ChainType::REGTEST); + SelectParams(ChainType::UNITTEST); } FUZZ_TARGET(transaction, .init = initialize_transaction) diff --git a/src/test/fuzz/util/mempool.cpp b/src/test/fuzz/util/mempool.cpp index 8e7499a860..89a4338661 100644 --- a/src/test/fuzz/util/mempool.cpp +++ b/src/test/fuzz/util/mempool.cpp @@ -26,6 +26,6 @@ CTxMemPoolEntry ConsumeTxMemPoolEntry(FuzzedDataProvider& fuzzed_data_provider, const uint64_t entry_sequence{fuzzed_data_provider.ConsumeIntegral()}; const unsigned int entry_height = fuzzed_data_provider.ConsumeIntegral(); const bool spends_coinbase = fuzzed_data_provider.ConsumeBool(); - const unsigned int sig_op_cost = fuzzed_data_provider.ConsumeIntegralInRange(0, MAX_BLOCK_SIGOPS_COST); + const unsigned int sig_op_cost = fuzzed_data_provider.ConsumeIntegralInRange(0, dgpMaxBlockSigOps); return CTxMemPoolEntry{MakeTransactionRef(tx), fee, time, entry_height, entry_sequence, spends_coinbase, sig_op_cost, {}}; }