diff --git a/doc/relnotes/README.md b/doc/relnotes/README.md index b9a3700f8..db1403472 100644 --- a/doc/relnotes/README.md +++ b/doc/relnotes/README.md @@ -13,6 +13,7 @@ evil organizations. # Hush 3.10.4 "" * Updated seed node list * New CLI option -clearnet=0 which disables clearnet networking, i.e. only Tor or i2p are allowed + * Note that at least one of a Tor or i2p daemon are needed for -clearnet=0, both are not needed but supported * -clearnet=0 is equivalent to the following CLI params: ``` -disableipv4=1 @@ -25,7 +26,6 @@ evil organizations. -onion="127.0.0.1:9050 -i2psam="127.0.0.1:7656" ``` - * Note that at least one of a Tor or i2p daemon are needed for -clearnet=0, both are not needed but supported * Add CLI options `-disableipv4` and `-disableipv6` which can be used to disable IPv4 or IPv6 * Updated ASmap, which maps IP addresses to Autonomous System (AS) numbers * Added ASmap health check, which logs stats about the ASmap once per 24 hours diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index d66922437..fab9db288 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -5008,9 +5008,9 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) if (!EnsureWalletIsAvailable(fHelp)) return NullUniValue; - if (fHelp || params.size() < 2 || params.size() > 4) + if (fHelp || params.size() < 2 || params.size() > 5) throw runtime_error( - "z_sendmany \"fromaddress\" [{\"address\":... ,\"amount\":...},...] ( minconf ) ( fee )\n" + "z_sendmany \"fromaddress\" [{\"address\":... ,\"amount\":...},...] ( minconf ) ( fee ) (opreturn)\n" "\nSend multiple times. Amounts are decimal numbers with at most 8 digits of precision." "\nChange generated from a taddr flows to a new taddr address, while change generated from a zaddr returns to itself." "\nWhen sending coinbase UTXOs to a zaddr, change is not allowed. The entire value of the UTXO(s) must be consumed." @@ -5026,6 +5026,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) "3. minconf (numeric, optional, default=1) Only use funds confirmed at least this many times.\n" "4. fee (numeric, optional, default=" + strprintf("%s", FormatMoney(ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE)) + ") The fee amount to attach to this transaction.\n" + "5. opreturn (string, optional) Hex encoded data for OP_RETURN. Or a utf8 string prefixed with 'utf8:' which will be automatically converted to hex\n" "\nResult:\n" "\"operationid\" (string) An operationid to pass to z_getoperationstatus to get the result of the operation.\n" "\nExamples:\n" @@ -5033,6 +5034,8 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) + HelpExampleRpc("z_sendmany", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\", [{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 5.0}]") + HelpExampleCli("z_sendmany", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 3.14}]'") + HelpExampleRpc("z_sendmany", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\", [{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 3.14}]") + + HelpExampleCli("z_sendmany", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 3.14}]' 1 0.0001 \"utf8: this will be converted to hex") + + HelpExampleRpc("z_sendmany", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 3.14}]' 1 0.0001 \"utf8: this will be converted to hex") ); LOCK2(cs_main, pwalletMain->cs_wallet); @@ -5157,11 +5160,28 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) CAmount nTotalOut = 0; // Optional OP_RETURN data CScript opret; - // TODO: enforce that only a single opreturn exists UniValue opretValue; + if(params.size() == 5) { + opretValue = params[4].get_str(); + + // Support a prefix "utf8:" which allows giving utf8 text instead of hex + if(opretValue.get_str().substr(0,5) == "utf8:") { + auto str = opretValue.get_str().substr(5); + if (utf8::is_valid(str)) { + opretValue = HexStr(str); + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid utf8 in opreturn"); + } + } + } bool containsSaplingOutput = false; + // Create the CScript representation of the OP_RETURN + if (!opretValue.isNull()) { + opret << OP_RETURN << ParseHex(opretValue.get_str().c_str()); + } + for (const UniValue& o : outputs.getValues()) { if (!o.isObject()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected object"); @@ -5169,7 +5189,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) // sanity check, report error if unknown key-value pairs for (const string& name_ : o.getKeys()) { std::string s = name_; - if (s != "address" && s != "amount" && s!="memo" && s!="opreturn") { + if (s != "address" && s != "amount" && s!="memo") { throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown key: ")+s); } } @@ -5188,24 +5208,22 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) // throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Extreme Privacy! You must send to a zaddr"); //} - UniValue this_opret = find_value(o, "opreturn"); - if (!this_opret.isNull()) { - opretValue = this_opret; - } - - // Create the CScript representation of the OP_RETURN - if (!opretValue.isNull()) { - opret << OP_RETURN << ParseHex(opretValue.get_str().c_str()); - } - UniValue memoValue = find_value(o, "memo"); string memo; if (!memoValue.isNull()) { memo = memoValue.get_str(); if (!isZaddr) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Memo cannot be used with a taddr. It can only be used with a zaddr."); + } else if(memo.substr(0,5) == "utf8:") { + // Support a prefix "utf8:" which allows giving utf8 text instead of hex + auto str = memo.substr(5); + if (utf8::is_valid(str)) { + memo = HexStr(str); + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid utf8 in memo"); + } } else if (!IsHex(memo)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected memo data in hexadecimal format."); + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected memo data in hexadecimal format or to use 'utf8:' prefix."); } if (memo.length() > HUSH_MEMO_SIZE*2) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, size of memo is larger than maximum allowed %d", HUSH_MEMO_SIZE ));