Files
dragonx/src/test/rpc_wallet_tests.cpp
Jack Grigg b7f9a7ae02 wallet: Comment out HDSeed and CHDChain persistence to disk
Sapling keys generated from the seed are not yet persisted, so we don't
want to persist the seed or chain state either, otherwise the wallet
could end up in an inconsistent state.

Some tests are temporarily disabled because commenting out HDSeed
persistence breaks invariants inside CCryptoKeyStore.

Revert this commit during the PR for #3388.
2018-09-03 10:45:37 +01:00

1827 lines
83 KiB
C++

// Copyright (c) 2013-2014 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "rpc/server.h"
#include "rpc/client.h"
#include "key_io.h"
#include "main.h"
#include "wallet/wallet.h"
#include "test/test_bitcoin.h"
#include "zcash/Address.hpp"
#include "asyncrpcqueue.h"
#include "asyncrpcoperation.h"
#include "wallet/asyncrpcoperation_mergetoaddress.h"
#include "wallet/asyncrpcoperation_sendmany.h"
#include "wallet/asyncrpcoperation_shieldcoinbase.h"
#include "init.h"
#include <array>
#include <chrono>
#include <thread>
#include <fstream>
#include <unordered_set>
#include <boost/algorithm/string.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/format.hpp>
#include <boost/filesystem.hpp>
#include <univalue.h>
using namespace std;
extern UniValue createArgs(int nRequired, const char* address1 = NULL, const char* address2 = NULL);
extern UniValue CallRPC(string args);
extern CWallet* pwalletMain;
bool find_error(const UniValue& objError, const std::string& expected) {
return find_value(objError, "message").get_str().find(expected) != string::npos;
}
static UniValue ValueFromString(const std::string &str)
{
UniValue value;
BOOST_CHECK(value.setNumStr(str));
return value;
}
BOOST_FIXTURE_TEST_SUITE(rpc_wallet_tests, TestingSetup)
BOOST_AUTO_TEST_CASE(rpc_addmultisig)
{
LOCK(pwalletMain->cs_wallet);
rpcfn_type addmultisig = tableRPC["addmultisigaddress"]->actor;
// old, 65-byte-long:
const char address1Hex[] = "0434e3e09f49ea168c5bbf53f877ff4206923858aab7c7e1df25bc263978107c95e35065a27ef6f1b27222db0ec97e0e895eaca603d3ee0d4c060ce3d8a00286c8";
// new, compressed:
const char address2Hex[] = "0388c2037017c62240b6b72ac1a2a5f94da790596ebd06177c8572752922165cb4";
UniValue v;
CTxDestination address;
BOOST_CHECK_NO_THROW(v = addmultisig(createArgs(1, address1Hex), false));
address = DecodeDestination(v.get_str());
BOOST_CHECK(IsValidDestination(address) && boost::get<CScriptID>(&address) != nullptr);
BOOST_CHECK_NO_THROW(v = addmultisig(createArgs(1, address1Hex, address2Hex), false));
address = DecodeDestination(v.get_str());
BOOST_CHECK(IsValidDestination(address) && boost::get<CScriptID>(&address) != nullptr);
BOOST_CHECK_NO_THROW(v = addmultisig(createArgs(2, address1Hex, address2Hex), false));
address = DecodeDestination(v.get_str());
BOOST_CHECK(IsValidDestination(address) && boost::get<CScriptID>(&address) != nullptr);
BOOST_CHECK_THROW(addmultisig(createArgs(0), false), runtime_error);
BOOST_CHECK_THROW(addmultisig(createArgs(1), false), runtime_error);
BOOST_CHECK_THROW(addmultisig(createArgs(2, address1Hex), false), runtime_error);
BOOST_CHECK_THROW(addmultisig(createArgs(1, ""), false), runtime_error);
BOOST_CHECK_THROW(addmultisig(createArgs(1, "NotAValidPubkey"), false), runtime_error);
string short1(address1Hex, address1Hex + sizeof(address1Hex) - 2); // last byte missing
BOOST_CHECK_THROW(addmultisig(createArgs(2, short1.c_str()), false), runtime_error);
string short2(address1Hex + 1, address1Hex + sizeof(address1Hex)); // first byte missing
BOOST_CHECK_THROW(addmultisig(createArgs(2, short2.c_str()), false), runtime_error);
}
BOOST_AUTO_TEST_CASE(rpc_wallet)
{
// Test RPC calls for various wallet statistics
UniValue r;
LOCK2(cs_main, pwalletMain->cs_wallet);
CPubKey demoPubkey = pwalletMain->GenerateNewKey();
CTxDestination demoAddress(CTxDestination(demoPubkey.GetID()));
UniValue retValue;
string strAccount = "";
string strPurpose = "receive";
BOOST_CHECK_NO_THROW({ /*Initialize Wallet with an account */
CWalletDB walletdb(pwalletMain->strWalletFile);
CAccount account;
account.vchPubKey = demoPubkey;
pwalletMain->SetAddressBook(account.vchPubKey.GetID(), strAccount, strPurpose);
walletdb.WriteAccount(strAccount, account);
});
CPubKey setaccountDemoPubkey = pwalletMain->GenerateNewKey();
CTxDestination setaccountDemoAddress(CTxDestination(setaccountDemoPubkey.GetID()));
/*********************************
* setaccount
*********************************/
BOOST_CHECK_NO_THROW(CallRPC("setaccount " + EncodeDestination(setaccountDemoAddress) + " \"\""));
/* Accounts are disabled */
BOOST_CHECK_THROW(CallRPC("setaccount " + EncodeDestination(setaccountDemoAddress) + " nullaccount"), runtime_error);
/* t1VtArtnn1dGPiD2WFfMXYXW5mHM3q1GpgV is not owned by the test wallet. */
BOOST_CHECK_THROW(CallRPC("setaccount t1VtArtnn1dGPiD2WFfMXYXW5mHM3q1GpgV nullaccount"), runtime_error);
BOOST_CHECK_THROW(CallRPC("setaccount"), runtime_error);
/* t1VtArtnn1dGPiD2WFfMXYXW5mHM3q1Gpg (34 chars) is an illegal address (should be 35 chars) */
BOOST_CHECK_THROW(CallRPC("setaccount t1VtArtnn1dGPiD2WFfMXYXW5mHM3q1Gpg nullaccount"), runtime_error);
/*********************************
* getbalance
*********************************/
BOOST_CHECK_NO_THROW(CallRPC("getbalance"));
BOOST_CHECK_THROW(CallRPC("getbalance " + EncodeDestination(demoAddress)), runtime_error);
/*********************************
* listunspent
*********************************/
BOOST_CHECK_NO_THROW(CallRPC("listunspent"));
BOOST_CHECK_THROW(CallRPC("listunspent string"), runtime_error);
BOOST_CHECK_THROW(CallRPC("listunspent 0 string"), runtime_error);
BOOST_CHECK_THROW(CallRPC("listunspent 0 1 not_array"), runtime_error);
BOOST_CHECK_THROW(CallRPC("listunspent 0 1 [] extra"), runtime_error);
BOOST_CHECK_NO_THROW(r = CallRPC("listunspent 0 1 []"));
BOOST_CHECK(r.get_array().empty());
/*********************************
* listreceivedbyaddress
*********************************/
BOOST_CHECK_NO_THROW(CallRPC("listreceivedbyaddress"));
BOOST_CHECK_NO_THROW(CallRPC("listreceivedbyaddress 0"));
BOOST_CHECK_THROW(CallRPC("listreceivedbyaddress not_int"), runtime_error);
BOOST_CHECK_THROW(CallRPC("listreceivedbyaddress 0 not_bool"), runtime_error);
BOOST_CHECK_NO_THROW(CallRPC("listreceivedbyaddress 0 true"));
BOOST_CHECK_THROW(CallRPC("listreceivedbyaddress 0 true extra"), runtime_error);
/*********************************
* listreceivedbyaccount
*********************************/
BOOST_CHECK_NO_THROW(CallRPC("listreceivedbyaccount"));
BOOST_CHECK_NO_THROW(CallRPC("listreceivedbyaccount 0"));
BOOST_CHECK_THROW(CallRPC("listreceivedbyaccount not_int"), runtime_error);
BOOST_CHECK_THROW(CallRPC("listreceivedbyaccount 0 not_bool"), runtime_error);
BOOST_CHECK_NO_THROW(CallRPC("listreceivedbyaccount 0 true"));
BOOST_CHECK_THROW(CallRPC("listreceivedbyaccount 0 true extra"), runtime_error);
/*********************************
* listsinceblock
*********************************/
BOOST_CHECK_NO_THROW(CallRPC("listsinceblock"));
/*********************************
* listtransactions
*********************************/
BOOST_CHECK_NO_THROW(CallRPC("listtransactions"));
BOOST_CHECK_NO_THROW(CallRPC("listtransactions " + EncodeDestination(demoAddress)));
BOOST_CHECK_NO_THROW(CallRPC("listtransactions " + EncodeDestination(demoAddress) + " 20"));
BOOST_CHECK_NO_THROW(CallRPC("listtransactions " + EncodeDestination(demoAddress) + " 20 0"));
BOOST_CHECK_THROW(CallRPC("listtransactions " + EncodeDestination(demoAddress) + " not_int"), runtime_error);
/*********************************
* listlockunspent
*********************************/
BOOST_CHECK_NO_THROW(CallRPC("listlockunspent"));
/*********************************
* listaccounts
*********************************/
BOOST_CHECK_NO_THROW(CallRPC("listaccounts"));
/*********************************
* listaddressgroupings
*********************************/
BOOST_CHECK_NO_THROW(CallRPC("listaddressgroupings"));
/*********************************
* getrawchangeaddress
*********************************/
BOOST_CHECK_NO_THROW(CallRPC("getrawchangeaddress"));
/*********************************
* getnewaddress
*********************************/
BOOST_CHECK_NO_THROW(CallRPC("getnewaddress"));
BOOST_CHECK_NO_THROW(CallRPC("getnewaddress \"\""));
/* Accounts are deprecated */
BOOST_CHECK_THROW(CallRPC("getnewaddress getnewaddress_demoaccount"), runtime_error);
/*********************************
* getaccountaddress
*********************************/
BOOST_CHECK_NO_THROW(CallRPC("getaccountaddress \"\""));
/* Accounts are deprecated */
BOOST_CHECK_THROW(CallRPC("getaccountaddress accountThatDoesntExists"), runtime_error);
BOOST_CHECK_NO_THROW(retValue = CallRPC("getaccountaddress " + strAccount));
BOOST_CHECK(DecodeDestination(retValue.get_str()) == demoAddress);
/*********************************
* getaccount
*********************************/
BOOST_CHECK_THROW(CallRPC("getaccount"), runtime_error);
BOOST_CHECK_NO_THROW(CallRPC("getaccount " + EncodeDestination(demoAddress)));
/*********************************
* signmessage + verifymessage
*********************************/
BOOST_CHECK_NO_THROW(retValue = CallRPC("signmessage " + EncodeDestination(demoAddress) + " mymessage"));
BOOST_CHECK_THROW(CallRPC("signmessage"), runtime_error);
/* Should throw error because this address is not loaded in the wallet */
BOOST_CHECK_THROW(CallRPC("signmessage t1h8SqgtM3QM5e2M8EzhhT1yL2PXXtA6oqe mymessage"), runtime_error);
/* missing arguments */
BOOST_CHECK_THROW(CallRPC("verifymessage " + EncodeDestination(demoAddress)), runtime_error);
BOOST_CHECK_THROW(CallRPC("verifymessage " + EncodeDestination(demoAddress) + " " + retValue.get_str()), runtime_error);
/* Illegal address */
BOOST_CHECK_THROW(CallRPC("verifymessage t1VtArtnn1dGPiD2WFfMXYXW5mHM3q1Gpg " + retValue.get_str() + " mymessage"), runtime_error);
/* wrong address */
BOOST_CHECK(CallRPC("verifymessage t1VtArtnn1dGPiD2WFfMXYXW5mHM3q1GpgV " + retValue.get_str() + " mymessage").get_bool() == false);
/* Correct address and signature but wrong message */
BOOST_CHECK(CallRPC("verifymessage " + EncodeDestination(demoAddress) + " " + retValue.get_str() + " wrongmessage").get_bool() == false);
/* Correct address, message and signature*/
BOOST_CHECK(CallRPC("verifymessage " + EncodeDestination(demoAddress) + " " + retValue.get_str() + " mymessage").get_bool() == true);
/*********************************
* getaddressesbyaccount
*********************************/
BOOST_CHECK_THROW(CallRPC("getaddressesbyaccount"), runtime_error);
BOOST_CHECK_NO_THROW(retValue = CallRPC("getaddressesbyaccount " + strAccount));
UniValue arr = retValue.get_array();
BOOST_CHECK_EQUAL(4, arr.size());
bool notFound = true;
for (auto a : arr.getValues()) {
notFound &= DecodeDestination(a.get_str()) != demoAddress;
}
BOOST_CHECK(!notFound);
/*********************************
* fundrawtransaction
*********************************/
BOOST_CHECK_THROW(CallRPC("fundrawtransaction 28z"), runtime_error);
BOOST_CHECK_THROW(CallRPC("fundrawtransaction 01000000000180969800000000001976a91450ce0a4b0ee0ddeb633da85199728b940ac3fe9488ac00000000"), runtime_error);
/*
* getblocksubsidy
*/
BOOST_CHECK_THROW(CallRPC("getblocksubsidy too many args"), runtime_error);
BOOST_CHECK_THROW(CallRPC("getblocksubsidy -1"), runtime_error);
BOOST_CHECK_NO_THROW(retValue = CallRPC("getblocksubsidy 50000"));
UniValue obj = retValue.get_obj();
BOOST_CHECK_EQUAL(find_value(obj, "miner").get_real(), 10.0);
BOOST_CHECK_EQUAL(find_value(obj, "founders").get_real(), 2.5);
BOOST_CHECK_NO_THROW(retValue = CallRPC("getblocksubsidy 1000000"));
obj = retValue.get_obj();
BOOST_CHECK_EQUAL(find_value(obj, "miner").get_real(), 6.25);
BOOST_CHECK_EQUAL(find_value(obj, "founders").get_real(), 0.0);
BOOST_CHECK_NO_THROW(retValue = CallRPC("getblocksubsidy 2000000"));
obj = retValue.get_obj();
BOOST_CHECK_EQUAL(find_value(obj, "miner").get_real(), 3.125);
BOOST_CHECK_EQUAL(find_value(obj, "founders").get_real(), 0.0);
/*
* getblock
*/
BOOST_CHECK_THROW(CallRPC("getblock too many args"), runtime_error);
BOOST_CHECK_THROW(CallRPC("getblock -1"), runtime_error);
BOOST_CHECK_THROW(CallRPC("getblock 2147483647"), runtime_error); // allowed, but > height of active chain tip
BOOST_CHECK_THROW(CallRPC("getblock 2147483648"), runtime_error); // not allowed, > int32 used for nHeight
BOOST_CHECK_THROW(CallRPC("getblock 100badchars"), runtime_error);
BOOST_CHECK_NO_THROW(CallRPC("getblock 0"));
BOOST_CHECK_NO_THROW(CallRPC("getblock 0 0"));
BOOST_CHECK_NO_THROW(CallRPC("getblock 0 1"));
BOOST_CHECK_NO_THROW(CallRPC("getblock 0 2"));
BOOST_CHECK_THROW(CallRPC("getblock 0 -1"), runtime_error); // bad verbosity
BOOST_CHECK_THROW(CallRPC("getblock 0 3"), runtime_error); // bad verbosity
}
BOOST_AUTO_TEST_CASE(rpc_wallet_getbalance)
{
SelectParams(CBaseChainParams::TESTNET);
LOCK(pwalletMain->cs_wallet);
BOOST_CHECK_THROW(CallRPC("z_getbalance too many args"), runtime_error);
BOOST_CHECK_THROW(CallRPC("z_getbalance invalidaddress"), runtime_error);
BOOST_CHECK_NO_THROW(CallRPC("z_getbalance tmC6YZnCUhm19dEXxh3Jb7srdBJxDawaCab"));
BOOST_CHECK_THROW(CallRPC("z_getbalance tmC6YZnCUhm19dEXxh3Jb7srdBJxDawaCab -1"), runtime_error);
BOOST_CHECK_NO_THROW(CallRPC("z_getbalance tmC6YZnCUhm19dEXxh3Jb7srdBJxDawaCab 0"));
BOOST_CHECK_THROW(CallRPC("z_getbalance tnRZ8bPq2pff3xBWhTJhNkVUkm2uhzksDeW5PvEa7aFKGT9Qi3YgTALZfjaY4jU3HLVKBtHdSXxoPoLA3naMPcHBcY88FcF 1"), runtime_error);
BOOST_CHECK_THROW(CallRPC("z_gettotalbalance too manyargs"), runtime_error);
BOOST_CHECK_THROW(CallRPC("z_gettotalbalance -1"), runtime_error);
BOOST_CHECK_NO_THROW(CallRPC("z_gettotalbalance 0"));
BOOST_CHECK_THROW(CallRPC("z_listreceivedbyaddress too many args"), runtime_error);
// negative minconf not allowed
BOOST_CHECK_THROW(CallRPC("z_listreceivedbyaddress tmC6YZnCUhm19dEXxh3Jb7srdBJxDawaCab -1"), runtime_error);
// invalid zaddr, taddr not allowed
BOOST_CHECK_THROW(CallRPC("z_listreceivedbyaddress tmC6YZnCUhm19dEXxh3Jb7srdBJxDawaCab 0"), runtime_error);
// don't have the spending key
BOOST_CHECK_THROW(CallRPC("z_listreceivedbyaddress tnRZ8bPq2pff3xBWhTJhNkVUkm2uhzksDeW5PvEa7aFKGT9Qi3YgTALZfjaY4jU3HLVKBtHdSXxoPoLA3naMPcHBcY88FcF 1"), runtime_error);
}
/**
* This test covers RPC command z_validateaddress
*/
BOOST_AUTO_TEST_CASE(rpc_wallet_z_validateaddress)
{
SelectParams(CBaseChainParams::MAIN);
LOCK2(cs_main, pwalletMain->cs_wallet);
UniValue retValue;
// Check number of args
BOOST_CHECK_THROW(CallRPC("z_validateaddress"), runtime_error);
BOOST_CHECK_THROW(CallRPC("z_validateaddress toomany args"), runtime_error);
// Wallet should be empty
std::set<libzcash::SproutPaymentAddress> addrs;
pwalletMain->GetSproutPaymentAddresses(addrs);
BOOST_CHECK(addrs.size()==0);
// This address is not valid, it belongs to another network
BOOST_CHECK_NO_THROW(retValue = CallRPC("z_validateaddress ztaaga95QAPyp1kSQ1hD2kguCpzyMHjxWZqaYDEkzbvo7uYQYAw2S8X4Kx98AvhhofMtQL8PAXKHuZsmhRcanavKRKmdCzk"));
UniValue resultObj = retValue.get_obj();
bool b = find_value(resultObj, "isvalid").get_bool();
BOOST_CHECK_EQUAL(b, false);
// This address is valid, but the spending key is not in this wallet
BOOST_CHECK_NO_THROW(retValue = CallRPC("z_validateaddress zcfA19SDAKRYHLoRDoShcoz4nPohqWxuHcqg8WAxsiB2jFrrs6k7oSvst3UZvMYqpMNSRBkxBsnyjjngX5L55FxMzLKach8"));
resultObj = retValue.get_obj();
b = find_value(resultObj, "isvalid").get_bool();
BOOST_CHECK_EQUAL(b, true);
BOOST_CHECK_EQUAL(find_value(resultObj, "type").get_str(), "sprout");
b = find_value(resultObj, "ismine").get_bool();
BOOST_CHECK_EQUAL(b, false);
// Let's import a spending key to the wallet and validate its payment address
BOOST_CHECK_NO_THROW(CallRPC("z_importkey SKxoWv77WGwFnUJitQKNEcD636bL4X5Gd6wWmgaA4Q9x8jZBPJXT"));
BOOST_CHECK_NO_THROW(retValue = CallRPC("z_validateaddress zcWsmqT4X2V4jgxbgiCzyrAfRT1vi1F4sn7M5Pkh66izzw8Uk7LBGAH3DtcSMJeUb2pi3W4SQF8LMKkU2cUuVP68yAGcomL"));
resultObj = retValue.get_obj();
b = find_value(resultObj, "isvalid").get_bool();
BOOST_CHECK_EQUAL(b, true);
BOOST_CHECK_EQUAL(find_value(resultObj, "type").get_str(), "sprout");
b = find_value(resultObj, "ismine").get_bool();
BOOST_CHECK_EQUAL(b, true);
BOOST_CHECK_EQUAL(find_value(resultObj, "payingkey").get_str(), "f5bb3c888ccc9831e3f6ba06e7528e26a312eec3acc1823be8918b6a3a5e20ad");
BOOST_CHECK_EQUAL(find_value(resultObj, "transmissionkey").get_str(), "7a58c7132446564e6b810cf895c20537b3528357dc00150a8e201f491efa9c1a");
// This Sapling address is not valid, it belongs to another network
BOOST_CHECK_NO_THROW(retValue = CallRPC("z_validateaddress ztestsapling1knww2nyjc62njkard0jmx7hlsj6twxmxwprn7anvrv4dc2zxanl3nemc0qx2hvplxmd2uau8gyw"));
resultObj = retValue.get_obj();
b = find_value(resultObj, "isvalid").get_bool();
BOOST_CHECK_EQUAL(b, false);
// This Sapling address is valid, but the spending key is not in this wallet
BOOST_CHECK_NO_THROW(retValue = CallRPC("z_validateaddress zs1z7rejlpsa98s2rrrfkwmaxu53e4ue0ulcrw0h4x5g8jl04tak0d3mm47vdtahatqrlkngh9slya"));
resultObj = retValue.get_obj();
b = find_value(resultObj, "isvalid").get_bool();
// TODO: Revert when we re-enable Sapling addresses on mainnet
BOOST_CHECK_EQUAL(b, false);
// BOOST_CHECK_EQUAL(find_value(resultObj, "type").get_str(), "sapling");
// b = find_value(resultObj, "ismine").get_bool();
// BOOST_CHECK_EQUAL(b, false);
// BOOST_CHECK_EQUAL(find_value(resultObj, "diversifier").get_str(), "1787997c30e94f050c634d");
// BOOST_CHECK_EQUAL(find_value(resultObj, "diversifiedtransmissionkey").get_str(), "34ed1f60f5db5763beee1ddbb37dd5f7e541d4d4fbdcc09fbfcc6b8e949bbe9d");
}
/*
* This test covers RPC command z_exportwallet
*/
BOOST_AUTO_TEST_CASE(rpc_wallet_z_exportwallet)
{
LOCK2(cs_main, pwalletMain->cs_wallet);
// wallet should be empty
std::set<libzcash::SproutPaymentAddress> addrs;
pwalletMain->GetSproutPaymentAddresses(addrs);
BOOST_CHECK(addrs.size()==0);
// wallet should have one key
auto address = pwalletMain->GenerateNewZKey();
BOOST_CHECK(IsValidPaymentAddress(address));
BOOST_ASSERT(boost::get<libzcash::SproutPaymentAddress>(&address) != nullptr);
auto addr = boost::get<libzcash::SproutPaymentAddress>(address);
pwalletMain->GetSproutPaymentAddresses(addrs);
BOOST_CHECK(addrs.size()==1);
// Set up paths
boost::filesystem::path tmppath = boost::filesystem::temp_directory_path();
boost::filesystem::path tmpfilename = boost::filesystem::unique_path("%%%%%%%%");
boost::filesystem::path exportfilepath = tmppath / tmpfilename;
// export will fail since exportdir is not set
BOOST_CHECK_THROW(CallRPC(string("z_exportwallet ") + tmpfilename.string()), runtime_error);
// set exportdir
mapArgs["-exportdir"] = tmppath.string();
// run some tests
BOOST_CHECK_THROW(CallRPC("z_exportwallet"), runtime_error);
BOOST_CHECK_THROW(CallRPC("z_exportwallet toomany args"), runtime_error);
BOOST_CHECK_THROW(CallRPC(string("z_exportwallet invalid!*/_chars.txt")), runtime_error);
BOOST_CHECK_NO_THROW(CallRPC(string("z_exportwallet ") + tmpfilename.string()));
libzcash::SproutSpendingKey key;
BOOST_CHECK(pwalletMain->GetSproutSpendingKey(addr, key));
std::string s1 = EncodePaymentAddress(addr);
std::string s2 = EncodeSpendingKey(key);
// There's no way to really delete a private key so we will read in the
// exported wallet file and search for the spending key and payment address.
EnsureWalletIsUnlocked();
ifstream file;
file.open(exportfilepath.string().c_str(), std::ios::in | std::ios::ate);
BOOST_CHECK(file.is_open());
bool fVerified = false;
int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg());
file.seekg(0, file.beg);
while (file.good()) {
std::string line;
std::getline(file, line);
if (line.empty() || line[0] == '#')
continue;
if (line.find(s1) != std::string::npos && line.find(s2) != std::string::npos) {
fVerified = true;
break;
}
}
BOOST_CHECK(fVerified);
}
/*
* This test covers RPC command z_importwallet
*/
BOOST_AUTO_TEST_CASE(rpc_wallet_z_importwallet)
{
LOCK2(cs_main, pwalletMain->cs_wallet);
// error if no args
BOOST_CHECK_THROW(CallRPC("z_importwallet"), runtime_error);
// error if too many args
BOOST_CHECK_THROW(CallRPC("z_importwallet toomany args"), runtime_error);
// create a random key locally
auto testSpendingKey = libzcash::SproutSpendingKey::random();
auto testPaymentAddress = testSpendingKey.address();
std::string testAddr = EncodePaymentAddress(testPaymentAddress);
std::string testKey = EncodeSpendingKey(testSpendingKey);
// create test data using the random key
std::string format_str = "# Wallet dump created by Zcash v0.11.2.0.z8-9155cc6-dirty (2016-08-11 11:37:00 -0700)\n"
"# * Created on 2016-08-12T21:55:36Z\n"
"# * Best block at time of backup was 0 (0de0a3851fef2d433b9b4f51d4342bdd24c5ddd793eb8fba57189f07e9235d52),\n"
"# mined on 2009-01-03T18:15:05Z\n"
"\n"
"# Zkeys\n"
"\n"
"%s 2016-08-12T21:55:36Z # zaddr=%s\n"
"\n"
"\n# End of dump";
boost::format formatobject(format_str);
std::string testWalletDump = (formatobject % testKey % testAddr).str();
// write test data to file
boost::filesystem::path temp = boost::filesystem::temp_directory_path() /
boost::filesystem::unique_path();
const std::string path = temp.string();
std::ofstream file(path);
file << testWalletDump;
file << std::flush;
// wallet should currently be empty
std::set<libzcash::SproutPaymentAddress> addrs;
pwalletMain->GetSproutPaymentAddresses(addrs);
BOOST_CHECK(addrs.size()==0);
// import test data from file into wallet
BOOST_CHECK_NO_THROW(CallRPC(string("z_importwallet ") + path));
// wallet should now have one zkey
pwalletMain->GetSproutPaymentAddresses(addrs);
BOOST_CHECK(addrs.size()==1);
// check that we have the spending key for the address
auto address = DecodePaymentAddress(testAddr);
BOOST_CHECK(IsValidPaymentAddress(address));
BOOST_ASSERT(boost::get<libzcash::SproutPaymentAddress>(&address) != nullptr);
auto addr = boost::get<libzcash::SproutPaymentAddress>(address);
BOOST_CHECK(pwalletMain->HaveSproutSpendingKey(addr));
// Verify the spending key is the same as the test data
libzcash::SproutSpendingKey k;
BOOST_CHECK(pwalletMain->GetSproutSpendingKey(addr, k));
BOOST_CHECK_EQUAL(testKey, EncodeSpendingKey(k));
}
/*
* This test covers RPC commands z_listaddresses, z_importkey, z_exportkey
*/
BOOST_AUTO_TEST_CASE(rpc_wallet_z_importexport)
{
SelectParams(CBaseChainParams::REGTEST);
LOCK2(cs_main, pwalletMain->cs_wallet);
UniValue retValue;
int n1 = 1000; // number of times to import/export
int n2 = 1000; // number of addresses to create and list
// error if no args
BOOST_CHECK_THROW(CallRPC("z_importkey"), runtime_error);
BOOST_CHECK_THROW(CallRPC("z_exportkey"), runtime_error);
// error if too many args
BOOST_CHECK_THROW(CallRPC("z_importkey way too many args"), runtime_error);
BOOST_CHECK_THROW(CallRPC("z_exportkey toomany args"), runtime_error);
// error if invalid args
auto sk = libzcash::SproutSpendingKey::random();
std::string prefix = std::string("z_importkey ") + EncodeSpendingKey(sk) + " yes ";
BOOST_CHECK_THROW(CallRPC(prefix + "-1"), runtime_error);
BOOST_CHECK_THROW(CallRPC(prefix + "2147483647"), runtime_error); // allowed, but > height of active chain tip
BOOST_CHECK_THROW(CallRPC(prefix + "2147483648"), runtime_error); // not allowed, > int32 used for nHeight
BOOST_CHECK_THROW(CallRPC(prefix + "100badchars"), runtime_error);
// wallet should currently be empty
std::set<libzcash::SproutPaymentAddress> addrs;
pwalletMain->GetSproutPaymentAddresses(addrs);
BOOST_CHECK(addrs.size()==0);
std::set<libzcash::SaplingPaymentAddress> saplingAddrs;
pwalletMain->GetSaplingPaymentAddresses(saplingAddrs);
BOOST_CHECK(saplingAddrs.empty());
std::vector<unsigned char, secure_allocator<unsigned char>> rawSeed(32);
HDSeed seed(rawSeed);
auto m = libzcash::SaplingExtendedSpendingKey::Master(seed);
// verify import and export key
for (int i = 0; i < n1; i++) {
// create a random Sprout key locally
auto testSpendingKey = libzcash::SproutSpendingKey::random();
auto testPaymentAddress = testSpendingKey.address();
std::string testAddr = EncodePaymentAddress(testPaymentAddress);
std::string testKey = EncodeSpendingKey(testSpendingKey);
BOOST_CHECK_NO_THROW(CallRPC(string("z_importkey ") + testKey));
BOOST_CHECK_NO_THROW(retValue = CallRPC(string("z_exportkey ") + testAddr));
BOOST_CHECK_EQUAL(retValue.get_str(), testKey);
// create a random Sapling key locally
auto testSaplingSpendingKey = m.Derive(i);
auto testSaplingPaymentAddress = testSaplingSpendingKey.DefaultAddress();
std::string testSaplingAddr = EncodePaymentAddress(testSaplingPaymentAddress);
std::string testSaplingKey = EncodeSpendingKey(testSaplingSpendingKey);
BOOST_CHECK_NO_THROW(CallRPC(string("z_importkey ") + testSaplingKey));
BOOST_CHECK_NO_THROW(retValue = CallRPC(string("z_exportkey ") + testSaplingAddr));
BOOST_CHECK_EQUAL(retValue.get_str(), testSaplingKey);
}
// Verify we can list the keys imported
BOOST_CHECK_NO_THROW(retValue = CallRPC("z_listaddresses"));
UniValue arr = retValue.get_array();
BOOST_CHECK(arr.size() == (2 * n1));
// Put addresses into a set
std::unordered_set<std::string> myaddrs;
for (UniValue element : arr.getValues()) {
myaddrs.insert(element.get_str());
}
// Make new addresses for the set
for (int i=0; i<n2; i++) {
myaddrs.insert(EncodePaymentAddress(pwalletMain->GenerateNewZKey()));
}
// Verify number of addresses stored in wallet is n1+n2
int numAddrs = myaddrs.size();
BOOST_CHECK(numAddrs == (2 * n1) + n2);
pwalletMain->GetSproutPaymentAddresses(addrs);
pwalletMain->GetSaplingPaymentAddresses(saplingAddrs);
BOOST_CHECK(addrs.size() + saplingAddrs.size() == numAddrs);
// Ask wallet to list addresses
BOOST_CHECK_NO_THROW(retValue = CallRPC("z_listaddresses"));
arr = retValue.get_array();
BOOST_CHECK(arr.size() == numAddrs);
// Create a set from them
std::unordered_set<std::string> listaddrs;
for (UniValue element : arr.getValues()) {
listaddrs.insert(element.get_str());
}
// Verify the two sets of addresses are the same
BOOST_CHECK(listaddrs.size() == numAddrs);
BOOST_CHECK(myaddrs == listaddrs);
// Add one more address
BOOST_CHECK_NO_THROW(retValue = CallRPC("z_getnewaddress"));
std::string newaddress = retValue.get_str();
auto address = DecodePaymentAddress(newaddress);
BOOST_CHECK(IsValidPaymentAddress(address));
BOOST_ASSERT(boost::get<libzcash::SproutPaymentAddress>(&address) != nullptr);
auto newAddr = boost::get<libzcash::SproutPaymentAddress>(address);
BOOST_CHECK(pwalletMain->HaveSproutSpendingKey(newAddr));
// Check if too many args
BOOST_CHECK_THROW(CallRPC("z_getnewaddress toomanyargs"), runtime_error);
}
/**
* Test Async RPC operations.
* Tip: Create mock operations by subclassing AsyncRPCOperation.
*/
class MockSleepOperation : public AsyncRPCOperation {
public:
std::chrono::milliseconds naptime;
MockSleepOperation(int t=1000) {
this->naptime = std::chrono::milliseconds(t);
}
virtual ~MockSleepOperation() {
}
virtual void main() {
set_state(OperationStatus::EXECUTING);
start_execution_clock();
std::this_thread::sleep_for(std::chrono::milliseconds(naptime));
stop_execution_clock();
set_result(UniValue(UniValue::VSTR, "done"));
set_state(OperationStatus::SUCCESS);
}
};
/*
* Test Aysnc RPC queue and operations.
*/
BOOST_AUTO_TEST_CASE(rpc_wallet_async_operations)
{
std::shared_ptr<AsyncRPCQueue> q = std::make_shared<AsyncRPCQueue>();
BOOST_CHECK(q->getNumberOfWorkers() == 0);
std::vector<AsyncRPCOperationId> ids = q->getAllOperationIds();
BOOST_CHECK(ids.size()==0);
std::shared_ptr<AsyncRPCOperation> op1 = std::make_shared<AsyncRPCOperation>();
q->addOperation(op1);
BOOST_CHECK(q->getOperationCount() == 1);
OperationStatus status = op1->getState();
BOOST_CHECK(status == OperationStatus::READY);
AsyncRPCOperationId id1 = op1->getId();
int64_t creationTime1 = op1->getCreationTime();
q->addWorker();
BOOST_CHECK(q->getNumberOfWorkers() == 1);
// an AsyncRPCOperation doesn't do anything so will finish immediately
std::this_thread::sleep_for(std::chrono::seconds(1));
BOOST_CHECK(q->getOperationCount() == 0);
// operation should be a success
BOOST_CHECK_EQUAL(op1->isCancelled(), false);
BOOST_CHECK_EQUAL(op1->isExecuting(), false);
BOOST_CHECK_EQUAL(op1->isReady(), false);
BOOST_CHECK_EQUAL(op1->isFailed(), false);
BOOST_CHECK_EQUAL(op1->isSuccess(), true);
BOOST_CHECK_EQUAL(op1->getError().isNull(), true);
BOOST_CHECK_EQUAL(op1->getResult().isNull(), false);
BOOST_CHECK_EQUAL(op1->getStateAsString(), "success");
BOOST_CHECK_NE(op1->getStateAsString(), "executing");
// Create a second operation which just sleeps
std::shared_ptr<AsyncRPCOperation> op2(new MockSleepOperation(2500));
AsyncRPCOperationId id2 = op2->getId();
int64_t creationTime2 = op2->getCreationTime();
// it's different from the previous operation
BOOST_CHECK_NE(op1.get(), op2.get());
BOOST_CHECK_NE(id1, id2);
BOOST_CHECK_NE(creationTime1, creationTime2);
// Only the first operation has been added to the queue
std::vector<AsyncRPCOperationId> v = q->getAllOperationIds();
std::set<AsyncRPCOperationId> opids(v.begin(), v.end());
BOOST_CHECK(opids.size() == 1);
BOOST_CHECK(opids.count(id1)==1);
BOOST_CHECK(opids.count(id2)==0);
std::shared_ptr<AsyncRPCOperation> p1 = q->getOperationForId(id1);
BOOST_CHECK_EQUAL(p1.get(), op1.get());
std::shared_ptr<AsyncRPCOperation> p2 = q->getOperationForId(id2);
BOOST_CHECK(!p2); // null ptr as not added to queue yet
// Add operation 2 and 3 to the queue
q->addOperation(op2);
std::shared_ptr<AsyncRPCOperation> op3(new MockSleepOperation(1000));
q->addOperation(op3);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
BOOST_CHECK_EQUAL(op2->isExecuting(), true);
op2->cancel(); // too late, already executing
op3->cancel();
std::this_thread::sleep_for(std::chrono::milliseconds(3000));
BOOST_CHECK_EQUAL(op2->isSuccess(), true);
BOOST_CHECK_EQUAL(op2->isCancelled(), false);
BOOST_CHECK_EQUAL(op3->isCancelled(), true);
v = q->getAllOperationIds();
std::copy( v.begin(), v.end(), std::inserter( opids, opids.end() ) );
BOOST_CHECK(opids.size() == 3);
BOOST_CHECK(opids.count(id1)==1);
BOOST_CHECK(opids.count(id2)==1);
BOOST_CHECK(opids.count(op3->getId())==1);
q->finishAndWait();
}
// The CountOperation will increment this global
std::atomic<int64_t> gCounter(0);
class CountOperation : public AsyncRPCOperation {
public:
CountOperation() {}
virtual ~CountOperation() {}
virtual void main() {
set_state(OperationStatus::EXECUTING);
gCounter++;
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
set_state(OperationStatus::SUCCESS);
}
};
// This tests the queue waiting for multiple workers to finish
BOOST_AUTO_TEST_CASE(rpc_wallet_async_operations_parallel_wait)
{
gCounter = 0;
std::shared_ptr<AsyncRPCQueue> q = std::make_shared<AsyncRPCQueue>();
q->addWorker();
q->addWorker();
q->addWorker();
q->addWorker();
BOOST_CHECK(q->getNumberOfWorkers() == 4);
int64_t numOperations = 10; // 10 * 1000ms / 4 = 2.5 secs to finish
for (int i=0; i<numOperations; i++) {
std::shared_ptr<AsyncRPCOperation> op(new CountOperation());
q->addOperation(op);
}
std::vector<AsyncRPCOperationId> ids = q->getAllOperationIds();
BOOST_CHECK(ids.size()==numOperations);
q->finishAndWait();
BOOST_CHECK_EQUAL(q->isFinishing(), true);
BOOST_CHECK_EQUAL(numOperations, gCounter.load());
}
// This tests the queue shutting down immediately
BOOST_AUTO_TEST_CASE(rpc_wallet_async_operations_parallel_cancel)
{
gCounter = 0;
std::shared_ptr<AsyncRPCQueue> q = std::make_shared<AsyncRPCQueue>();
q->addWorker();
q->addWorker();
BOOST_CHECK(q->getNumberOfWorkers() == 2);
int numOperations = 10000; // 10000 seconds to complete
for (int i=0; i<numOperations; i++) {
std::shared_ptr<AsyncRPCOperation> op(new CountOperation());
q->addOperation(op);
}
std::vector<AsyncRPCOperationId> ids = q->getAllOperationIds();
BOOST_CHECK(ids.size()==numOperations);
q->closeAndWait();
int numSuccess = 0;
int numCancelled = 0;
for (auto & id : ids) {
std::shared_ptr<AsyncRPCOperation> ptr = q->popOperationForId(id);
if (ptr->isCancelled()) {
numCancelled++;
} else if (ptr->isSuccess()) {
numSuccess++;
}
}
BOOST_CHECK_EQUAL(numOperations, numSuccess+numCancelled);
BOOST_CHECK_EQUAL(gCounter.load(), numSuccess);
BOOST_CHECK(q->getOperationCount() == 0);
ids = q->getAllOperationIds();
BOOST_CHECK(ids.size()==0);
}
// This tests z_getoperationstatus, z_getoperationresult, z_listoperationids
BOOST_AUTO_TEST_CASE(rpc_z_getoperations)
{
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
std::shared_ptr<AsyncRPCQueue> sharedInstance = AsyncRPCQueue::sharedInstance();
BOOST_CHECK(q == sharedInstance);
BOOST_CHECK_NO_THROW(CallRPC("z_getoperationstatus"));
BOOST_CHECK_NO_THROW(CallRPC("z_getoperationstatus []"));
BOOST_CHECK_NO_THROW(CallRPC("z_getoperationstatus [\"opid-1234\"]"));
BOOST_CHECK_THROW(CallRPC("z_getoperationstatus [] toomanyargs"), runtime_error);
BOOST_CHECK_THROW(CallRPC("z_getoperationstatus not_an_array"), runtime_error);
BOOST_CHECK_NO_THROW(CallRPC("z_getoperationresult"));
BOOST_CHECK_NO_THROW(CallRPC("z_getoperationresult []"));
BOOST_CHECK_NO_THROW(CallRPC("z_getoperationresult [\"opid-1234\"]"));
BOOST_CHECK_THROW(CallRPC("z_getoperationresult [] toomanyargs"), runtime_error);
BOOST_CHECK_THROW(CallRPC("z_getoperationresult not_an_array"), runtime_error);
std::shared_ptr<AsyncRPCOperation> op1 = std::make_shared<AsyncRPCOperation>();
q->addOperation(op1);
std::shared_ptr<AsyncRPCOperation> op2 = std::make_shared<AsyncRPCOperation>();
q->addOperation(op2);
BOOST_CHECK(q->getOperationCount() == 2);
BOOST_CHECK(q->getNumberOfWorkers() == 0);
q->addWorker();
BOOST_CHECK(q->getNumberOfWorkers() == 1);
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
BOOST_CHECK(q->getOperationCount() == 0);
// Check if too many args
BOOST_CHECK_THROW(CallRPC("z_listoperationids toomany args"), runtime_error);
UniValue retValue;
BOOST_CHECK_NO_THROW(retValue = CallRPC("z_listoperationids"));
BOOST_CHECK(retValue.get_array().size() == 2);
BOOST_CHECK_NO_THROW(retValue = CallRPC("z_getoperationstatus"));
UniValue array = retValue.get_array();
BOOST_CHECK(array.size() == 2);
// idempotent
BOOST_CHECK_NO_THROW(retValue = CallRPC("z_getoperationstatus"));
array = retValue.get_array();
BOOST_CHECK(array.size() == 2);
for (UniValue v : array.getValues()) {
UniValue obj = v.get_obj();
UniValue id = find_value(obj, "id");
UniValue result;
// removes result from internal storage
BOOST_CHECK_NO_THROW(result = CallRPC("z_getoperationresult [\"" + id.get_str() + "\"]"));
UniValue resultArray = result.get_array();
BOOST_CHECK(resultArray.size() == 1);
UniValue resultObj = resultArray[0].get_obj();
UniValue resultId = find_value(resultObj, "id");
BOOST_CHECK_EQUAL(id.get_str(), resultId.get_str());
// verify the operation has been removed
BOOST_CHECK_NO_THROW(result = CallRPC("z_getoperationresult [\"" + id.get_str() + "\"]"));
resultArray = result.get_array();
BOOST_CHECK(resultArray.size() == 0);
}
// operations removed
BOOST_CHECK_NO_THROW(retValue = CallRPC("z_getoperationstatus"));
array = retValue.get_array();
BOOST_CHECK(array.size() == 0);
q->close();
}
BOOST_AUTO_TEST_CASE(rpc_z_sendmany_parameters)
{
SelectParams(CBaseChainParams::TESTNET);
LOCK(pwalletMain->cs_wallet);
BOOST_CHECK_THROW(CallRPC("z_sendmany"), runtime_error);
BOOST_CHECK_THROW(CallRPC("z_sendmany toofewargs"), runtime_error);
BOOST_CHECK_THROW(CallRPC("z_sendmany just too many args here"), runtime_error);
// bad from address
BOOST_CHECK_THROW(CallRPC("z_sendmany "
"INVALIDtmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ []"), runtime_error);
// empty amounts
BOOST_CHECK_THROW(CallRPC("z_sendmany "
"tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ []"), runtime_error);
// don't have the spending key for this address
BOOST_CHECK_THROW(CallRPC("z_sendmany "
"tnpoQJVnYBZZqkFadj2bJJLThNCxbADGB5gSGeYTAGGrT5tejsxY9Zc1BtY8nnHmZkB"
"UkJ1oSfbhTJhm72WiZizvkZz5aH1 []"), runtime_error);
// duplicate address
BOOST_CHECK_THROW(CallRPC("z_sendmany "
"tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ "
"[{\"address\":\"tmQP9L3s31cLsghVYf2Jb5MhKj1jRBPoeQn\", \"amount\":50.0},"
" {\"address\":\"tmQP9L3s31cLsghVYf2Jb5MhKj1jRBPoeQn\", \"amount\":12.0} ]"
), runtime_error);
// invalid fee amount, cannot be negative
BOOST_CHECK_THROW(CallRPC("z_sendmany "
"tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ "
"[{\"address\":\"tmQP9L3s31cLsghVYf2Jb5MhKj1jRBPoeQn\", \"amount\":50.0}] "
"1 -0.0001"
), runtime_error);
// invalid fee amount, bigger than MAX_MONEY
BOOST_CHECK_THROW(CallRPC("z_sendmany "
"tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ "
"[{\"address\":\"tmQP9L3s31cLsghVYf2Jb5MhKj1jRBPoeQn\", \"amount\":50.0}] "
"1 21000001"
), runtime_error);
// fee amount is bigger than sum of outputs
BOOST_CHECK_THROW(CallRPC("z_sendmany "
"tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ "
"[{\"address\":\"tmQP9L3s31cLsghVYf2Jb5MhKj1jRBPoeQn\", \"amount\":50.0}] "
"1 50.00000001"
), runtime_error);
// memo bigger than allowed length of ZC_MEMO_SIZE
std::vector<char> v (2 * (ZC_MEMO_SIZE+1)); // x2 for hexadecimal string format
std::fill(v.begin(),v.end(), 'A');
std::string badmemo(v.begin(), v.end());
auto pa = pwalletMain->GenerateNewZKey();
std::string zaddr1 = EncodePaymentAddress(pa);
BOOST_CHECK_THROW(CallRPC(string("z_sendmany tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ ")
+ "[{\"address\":\"" + zaddr1 + "\", \"amount\":123.456}]"), runtime_error);
// Mutable tx containing contextual information we need to build tx
UniValue retValue = CallRPC("getblockcount");
int nHeight = retValue.get_int();
CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), nHeight + 1);
if (mtx.nVersion == 1) {
mtx.nVersion = 2;
}
// Test constructor of AsyncRPCOperation_sendmany
try {
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(boost::none, mtx, "",{}, {}, -1));
} catch (const UniValue& objError) {
BOOST_CHECK( find_error(objError, "Minconf cannot be negative"));
}
try {
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(boost::none, mtx, "",{}, {}, 1));
} catch (const UniValue& objError) {
BOOST_CHECK( find_error(objError, "From address parameter missing"));
}
try {
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(boost::none, mtx, "tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ", {}, {}, 1) );
} catch (const UniValue& objError) {
BOOST_CHECK( find_error(objError, "No recipients"));
}
try {
std::vector<SendManyRecipient> recipients = { SendManyRecipient("dummy",1.0, "") };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(boost::none, mtx, "INVALID", recipients, {}, 1) );
} catch (const UniValue& objError) {
BOOST_CHECK( find_error(objError, "Invalid from address"));
}
// Testnet payment addresses begin with 'zt'. This test detects an incorrect prefix.
try {
std::vector<SendManyRecipient> recipients = { SendManyRecipient("dummy",1.0, "") };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(boost::none, mtx, "zcMuhvq8sEkHALuSU2i4NbNQxshSAYrpCExec45ZjtivYPbuiFPwk6WHy4SvsbeZ4siy1WheuRGjtaJmoD1J8bFqNXhsG6U", recipients, {}, 1) );
} catch (const UniValue& objError) {
BOOST_CHECK( find_error(objError, "Invalid from address"));
}
// Note: The following will crash as a google test because AsyncRPCOperation_sendmany
// invokes a method on pwalletMain, which is undefined in the google test environment.
try {
std::vector<SendManyRecipient> recipients = { SendManyRecipient("dummy",1.0, "") };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(boost::none, mtx, "ztjiDe569DPNbyTE6TSdJTaSDhoXEHLGvYoUnBU1wfVNU52TEyT6berYtySkd21njAeEoh8fFJUT42kua9r8EnhBaEKqCpP", recipients, {}, 1) );
} catch (const UniValue& objError) {
BOOST_CHECK( find_error(objError, "no spending key found for zaddr"));
}
}
// TODO: test private methods
BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
{
SelectParams(CBaseChainParams::TESTNET);
LOCK(pwalletMain->cs_wallet);
UniValue retValue;
// Mutable tx containing contextual information we need to build tx
retValue = CallRPC("getblockcount");
int nHeight = retValue.get_int();
CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), nHeight + 1);
if (mtx.nVersion == 1) {
mtx.nVersion = 2;
}
// add keys manually
BOOST_CHECK_NO_THROW(retValue = CallRPC("getnewaddress"));
std::string taddr1 = retValue.get_str();
auto pa = pwalletMain->GenerateNewZKey();
std::string zaddr1 = EncodePaymentAddress(pa);
// there are no utxos to spend
{
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1,100.0, "DEADBEEF") };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(boost::none, mtx, taddr1, {}, recipients, 1) );
operation->main();
BOOST_CHECK(operation->isFailed());
std::string msg = operation->getErrorMessage();
BOOST_CHECK( msg.find("Insufficient funds, no UTXOs found") != string::npos);
}
// minconf cannot be zero when sending from zaddr
{
try {
std::vector<SendManyRecipient> recipients = {SendManyRecipient(taddr1, 100.0, "DEADBEEF")};
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(boost::none, mtx, zaddr1, recipients, {}, 0));
BOOST_CHECK(false); // Fail test if an exception is not thrown
} catch (const UniValue& objError) {
BOOST_CHECK(find_error(objError, "Minconf cannot be zero when sending from zaddr"));
}
}
// there are no unspent notes to spend
{
std::vector<SendManyRecipient> recipients = { SendManyRecipient(taddr1,100.0, "DEADBEEF") };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(boost::none, mtx, zaddr1, recipients, {}, 1) );
operation->main();
BOOST_CHECK(operation->isFailed());
std::string msg = operation->getErrorMessage();
BOOST_CHECK( msg.find("Insufficient funds, no unspent notes") != string::npos);
}
// get_memo_from_hex_string())
{
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1,100.0, "DEADBEEF") };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(boost::none, mtx, zaddr1, recipients, {}, 1) );
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr);
std::string memo = "DEADBEEF";
std::array<unsigned char, ZC_MEMO_SIZE> array = proxy.get_memo_from_hex_string(memo);
BOOST_CHECK_EQUAL(array[0], 0xDE);
BOOST_CHECK_EQUAL(array[1], 0xAD);
BOOST_CHECK_EQUAL(array[2], 0xBE);
BOOST_CHECK_EQUAL(array[3], 0xEF);
for (int i=4; i<ZC_MEMO_SIZE; i++) {
BOOST_CHECK_EQUAL(array[i], 0x00); // zero padding
}
// memo is longer than allowed
std::vector<char> v (2 * (ZC_MEMO_SIZE+1));
std::fill(v.begin(),v.end(), 'A');
std::string bigmemo(v.begin(), v.end());
try {
proxy.get_memo_from_hex_string(bigmemo);
} catch (const UniValue& objError) {
BOOST_CHECK( find_error(objError, "too big"));
}
// invalid hexadecimal string
std::fill(v.begin(),v.end(), '@'); // not a hex character
std::string badmemo(v.begin(), v.end());
try {
proxy.get_memo_from_hex_string(badmemo);
} catch (const UniValue& objError) {
BOOST_CHECK( find_error(objError, "hexadecimal format"));
}
// odd length hexadecimal string
std::fill(v.begin(),v.end(), 'A');
v.resize(v.size() - 1);
assert(v.size() %2 == 1); // odd length
std::string oddmemo(v.begin(), v.end());
try {
proxy.get_memo_from_hex_string(oddmemo);
} catch (const UniValue& objError) {
BOOST_CHECK( find_error(objError, "hexadecimal format"));
}
}
// add_taddr_change_output_to_tx() will append a vout to a raw transaction
{
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1,100.0, "DEADBEEF") };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(boost::none, mtx, zaddr1, recipients, {}, 1) );
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr);
CTransaction tx = proxy.getTx();
BOOST_CHECK(tx.vout.size() == 0);
CAmount amount = AmountFromValue(ValueFromString("123.456"));
proxy.add_taddr_change_output_to_tx(amount);
tx = proxy.getTx();
BOOST_CHECK(tx.vout.size() == 1);
CTxOut out = tx.vout[0];
BOOST_CHECK_EQUAL(out.nValue, amount);
amount = AmountFromValue(ValueFromString("1.111"));
proxy.add_taddr_change_output_to_tx(amount);
tx = proxy.getTx();
BOOST_CHECK(tx.vout.size() == 2);
out = tx.vout[1];
BOOST_CHECK_EQUAL(out.nValue, amount);
}
// add_taddr_outputs_to_tx() will append many vouts to a raw transaction
{
std::vector<SendManyRecipient> recipients = {
SendManyRecipient("tmTGScYwiLMzHe4uGZtBYmuqoW4iEoYNMXt",CAmount(1.23), ""),
SendManyRecipient("tmUSbHz3vxnwLvRyNDXbwkZxjVyDodMJEhh",CAmount(4.56), ""),
SendManyRecipient("tmYZAXYPCP56Xa5JQWWPZuK7o7bfUQW6kkd",CAmount(7.89), ""),
};
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(boost::none, mtx, zaddr1, recipients, {}, 1) );
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr);
proxy.add_taddr_outputs_to_tx();
CTransaction tx = proxy.getTx();
BOOST_CHECK(tx.vout.size() == 3);
BOOST_CHECK_EQUAL(tx.vout[0].nValue, CAmount(1.23));
BOOST_CHECK_EQUAL(tx.vout[1].nValue, CAmount(4.56));
BOOST_CHECK_EQUAL(tx.vout[2].nValue, CAmount(7.89));
}
// Raw joinsplit is a zaddr->zaddr
{
std::string raw = "020000000000000000000100000000000000001027000000000000183a0d4c46c369078705e39bcfebee59a978dbd210ce8de3efc9555a03fbabfd3cea16693d730c63850d7e48ccde79854c19adcb7e9dcd7b7d18805ee09083f6b16e1860729d2d4a90e2f2acd009cf78b5eb0f4a6ee4bdb64b1262d7ce9eb910c460b02022991e968d0c50ee44908e4ccccbc591d0053bcca154dd6d6fc400a29fa686af4682339832ccea362a62aeb9df0d5aa74f86a1e75ac0f48a8ccc41e0a940643c6c33e1d09223b0a46eaf47a1bb4407cfc12b1dcf83a29c0cef51e45c7876ca5b9e5bae86d92976eb3ef68f29cd29386a8be8451b50f82bf9da10c04651868655194da8f6ed3d241bb5b5ff93a3e2bbe44644544d88bcde5cc35978032ee92699c7a61fcbb395e7583f47e698c4d53ede54f956629400bf510fb5e22d03158cc10bdcaaf29e418ef18eb6480dd9c8b9e2a377809f9f32a556ef872febd0021d4ad013aa9f0b7255e98e408d302abefd33a71180b720271835b487ab309e160b06dfe51932120fb84a7ede16b20c53599a11071592109e10260f265ee60d48c62bfe24074020e9b586ce9e9356e68f2ad1a9538258234afe4b83a209f178f45202270eaeaeecaf2ce3100b2c5a714f75f35777a9ebff5ebf47059d2bbf6f3726190216468f2b152673b766225b093f3a2f827c86d6b48b42117fec1d0ac38dd7af700308dcfb02eba821612b16a2c164c47715b9b0c93900893b1aba2ea03765c94d87022db5be06ab338d1912e0936dfe87586d0a8ee49144a6cd2e306abdcb652faa3e0222739deb23154d778b50de75069a4a2cce1208cd1ced3cb4744c9888ce1c2fcd2e66dc31e62d3aa9e423d7275882525e9981f92e84ac85975b8660739407efbe1e34c2249420fde7e17db3096d5b22e83d051d01f0e6e7690dca7d168db338aadf0897fedac10de310db2b1bff762d322935dddbb60c2efb8b15d231fa17b84630371cb275c209f0c4c7d0c68b150ea5cd514122215e3f7fcfb351d69514788d67c2f3c8922581946e3a04bdf1f07f15696ca76eb95b10698bf1188fd882945c57657515889d042a6fc45d38cbc943540c4f0f6d1c45a1574c81f3e42d1eb8702328b729909adee8a5cfed7c79d54627d1fd389af941d878376f7927b9830ca659bf9ab18c5ca5192d52d02723008728d03701b8ab3e1c4a3109409ec0b13df334c7deec3523eeef4c97b5603e643de3a647b873f4c1b47fbfc6586ba66724f112e51fc93839648005043620aa3ce458e246d77977b19c53d98e3e812de006afc1a79744df236582943631d04cc02941ac4be500e4ed9fb9e3e7cc187b1c4050fad1d9d09d5fd70d5d01d615b439d8c0015d2eb10398bcdbf8c4b2bd559dbe4c288a186aed3f86f608da4d582e120c4a896e015e2241900d1daeccd05db968852677c71d752bec46de9962174b46f980e8cc603654daf8b98a3ee92dac066033954164a89568b70b1780c2ce2410b2f816dbeddb2cd463e0c8f21a52cf6427d9647a6fd4bafa8fb4cd4d47ac057b0160bee86c6b2fb8adce214c2bcdda277512200adf0eaa5d2114a2c077b009836a68ec254bfe56f51d147b9afe2ddd9cb917c0c2de19d81b7b8fd9f4574f51fa1207630dc13976f4d7587c962f761af267de71f3909a576e6bedaf6311633910d291ac292c467cc8331ef577aef7646a5d949322fa0367a49f20597a13def53136ee31610395e3e48d291fd8f58504374031fe9dcfba5e06086ebcf01a9106f6a4d6e16e19e4c5bb893f7da79419c94eca31a384be6fa1747284dee0fc3bbc8b1b860172c10b29c1594bb8c747d7fe05827358ff2160f49050001625ffe2e880bd7fc26cd0ffd89750745379a8e862816e08a5a2008043921ab6a4976064ac18f7ee37b6628bc0127d8d5ebd3548e41d8881a082d86f20b32e33094f15a0e6ea6074b08c6cd28142de94713451640a55985051f5577eb54572699d838cb34a79c8939e981c0c277d06a6e2ce69ccb74f8a691ff08f81d8b99e6a86223d29a2b7c8e7b041aba44ea678ae654277f7e91cbfa79158b989164a3d549d9f4feb0cc43169699c13e321fe3f4b94258c75d198ff9184269cd6986c55409e07528c93f64942c6c283ce3917b4bf4c3be2fe3173c8c38cccb35f1fbda0ca88b35a599c0678cb22aa8eabea8249dbd2e4f849fffe69803d299e435ebcd7df95854003d8eda17a74d98b4be0e62d45d7fe48c06a6f464a14f8e0570077cc631279092802a89823f031eef5e1028a6d6fdbd502869a731ee7d28b4d6c71b419462a30d31442d3ee444ffbcbd16d558c9000c97e949c2b1f9d6f6d8db7b9131ebd963620d3fc8595278d6f8fdf49084325373196d53e64142fa5a23eccd6ef908c4d80b8b3e6cc334b7f7012103a3682e4678e9b518163d262a39a2c1a69bf88514c52b7ccd7cc8dc80e71f7c2ec0701cff982573eb0c2c4daeb47fa0b586f4451c10d1da2e5d182b03dd067a5e971b3a6138ca6667aaf853d2ac03b80a1d5870905f2cfb6c78ec3c3719c02f973d638a0f973424a2b0f2b0023f136d60092fe15fba4bc180b9176bd0ff576e053f1af6939fe9ca256203ffaeb3e569f09774d2a6cbf91873e56651f4d6ff77e0b5374b0a1a201d7e523604e0247644544cc571d48c458a4f96f45580b";
UniValue obj(UniValue::VOBJ);
obj.push_back(Pair("rawtxn", raw));
// we have the spending key for the dummy recipient zaddr1
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1, 0.0005, "ABCD") };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(boost::none, mtx, zaddr1, {}, recipients, 1) );
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr);
// Enable test mode so tx is not sent
static_cast<AsyncRPCOperation_sendmany *>(operation.get())->testmode = true;
// Pretend that the operation completed successfully
proxy.set_state(OperationStatus::SUCCESS);
// Verify test mode is returning output (since no input taddrs, signed and unsigned are the same).
BOOST_CHECK_NO_THROW( proxy.sign_send_raw_transaction(obj) );
UniValue result = operation->getResult();
BOOST_CHECK(!result.isNull());
UniValue resultObj = result.get_obj();
std::string hex = find_value(resultObj, "hex").get_str();
BOOST_CHECK_EQUAL(hex, raw);
}
// Test the perform_joinsplit methods.
{
// Dummy input so the operation object can be instantiated.
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1, 0.0005, "ABCD") };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(boost::none, mtx, zaddr1, {}, recipients, 1) );
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr);
// Enable test mode so tx is not sent and proofs are not generated
static_cast<AsyncRPCOperation_sendmany *>(operation.get())->testmode = true;
AsyncJoinSplitInfo info;
std::vector<boost::optional < SproutWitness>> witnesses;
uint256 anchor;
try {
proxy.perform_joinsplit(info, witnesses, anchor);
} catch (const std::runtime_error & e) {
BOOST_CHECK( string(e.what()).find("anchor is null")!= string::npos);
}
try {
std::vector<JSOutPoint> v;
proxy.perform_joinsplit(info, v);
} catch (const std::runtime_error & e) {
BOOST_CHECK( string(e.what()).find("anchor is null")!= string::npos);
}
info.notes.push_back(SproutNote());
try {
proxy.perform_joinsplit(info);
} catch (const std::runtime_error & e) {
BOOST_CHECK( string(e.what()).find("number of notes")!= string::npos);
}
info.notes.clear();
info.vjsin.push_back(JSInput());
info.vjsin.push_back(JSInput());
info.vjsin.push_back(JSInput());
try {
proxy.perform_joinsplit(info);
} catch (const std::runtime_error & e) {
BOOST_CHECK( string(e.what()).find("unsupported joinsplit input")!= string::npos);
}
info.vjsin.clear();
try {
proxy.perform_joinsplit(info);
} catch (const std::runtime_error & e) {
BOOST_CHECK( string(e.what()).find("error verifying joinsplit")!= string::npos);
}
}
}
/*
* This test covers storing encrypted zkeys in the wallet.
*/
/* TODO: Uncomment during PR for #3388
BOOST_AUTO_TEST_CASE(rpc_wallet_encrypted_wallet_zkeys)
{
LOCK2(cs_main, pwalletMain->cs_wallet);
UniValue retValue;
int n = 100;
// wallet should currently be empty
std::set<libzcash::SproutPaymentAddress> addrs;
pwalletMain->GetSproutPaymentAddresses(addrs);
BOOST_CHECK(addrs.size()==0);
// create keys
for (int i = 0; i < n; i++) {
CallRPC("z_getnewaddress");
}
// Verify we can list the keys imported
BOOST_CHECK_NO_THROW(retValue = CallRPC("z_listaddresses"));
UniValue arr = retValue.get_array();
BOOST_CHECK(arr.size() == n);
// Verify that the wallet encryption RPC is disabled
BOOST_CHECK_THROW(CallRPC("encryptwallet passphrase"), runtime_error);
// Encrypt the wallet (we can't call RPC encryptwallet as that shuts down node)
SecureString strWalletPass;
strWalletPass.reserve(100);
strWalletPass = "hello";
boost::filesystem::current_path(GetArg("-datadir","/tmp/thisshouldnothappen"));
BOOST_CHECK(pwalletMain->EncryptWallet(strWalletPass));
// Verify we can still list the keys imported
BOOST_CHECK_NO_THROW(retValue = CallRPC("z_listaddresses"));
arr = retValue.get_array();
BOOST_CHECK(arr.size() == n);
// Try to add a new key, but we can't as the wallet is locked
BOOST_CHECK_THROW(CallRPC("z_getnewaddress"), runtime_error);
// We can't call RPC walletpassphrase as that invokes RPCRunLater which breaks tests.
// So we manually unlock.
BOOST_CHECK(pwalletMain->Unlock(strWalletPass));
// Now add a key
BOOST_CHECK_NO_THROW(CallRPC("z_getnewaddress"));
// Verify the key has been added
BOOST_CHECK_NO_THROW(retValue = CallRPC("z_listaddresses"));
arr = retValue.get_array();
BOOST_CHECK(arr.size() == n+1);
// We can't simulate over RPC the wallet closing and being reloaded
// but there are tests for this in gtest.
}
*/
BOOST_AUTO_TEST_CASE(rpc_z_listunspent_parameters)
{
SelectParams(CBaseChainParams::TESTNET);
LOCK(pwalletMain->cs_wallet);
UniValue retValue;
// too many args
BOOST_CHECK_THROW(CallRPC("z_listunspent 1 2 3 4 5"), runtime_error);
// minconf must be >= 0
BOOST_CHECK_THROW(CallRPC("z_listunspent -1"), runtime_error);
// maxconf must be > minconf
BOOST_CHECK_THROW(CallRPC("z_listunspent 2 1"), runtime_error);
// maxconf must not be out of range
BOOST_CHECK_THROW(CallRPC("z_listunspent 1 9999999999"), runtime_error);
// must be an array of addresses
BOOST_CHECK_THROW(CallRPC("z_listunspent 1 999 false ztjiDe569DPNbyTE6TSdJTaSDhoXEHLGvYoUnBU1wfVNU52TEyT6berYtySkd21njAeEoh8fFJUT42kua9r8EnhBaEKqCpP"), runtime_error);
// address must be string
BOOST_CHECK_THROW(CallRPC("z_listunspent 1 999 false [123456]"), runtime_error);
// no spending key
BOOST_CHECK_THROW(CallRPC("z_listunspent 1 999 false [\"ztjiDe569DPNbyTE6TSdJTaSDhoXEHLGvYoUnBU1wfVNU52TEyT6berYtySkd21njAeEoh8fFJUT42kua9r8EnhBaEKqCpP\"]"), runtime_error);
// allow watch only
BOOST_CHECK_NO_THROW(CallRPC("z_listunspent 1 999 true [\"ztjiDe569DPNbyTE6TSdJTaSDhoXEHLGvYoUnBU1wfVNU52TEyT6berYtySkd21njAeEoh8fFJUT42kua9r8EnhBaEKqCpP\"]"));
// wrong network, mainnet instead of testnet
BOOST_CHECK_THROW(CallRPC("z_listunspent 1 999 true [\"zcMuhvq8sEkHALuSU2i4NbNQxshSAYrpCExec45ZjtivYPbuiFPwk6WHy4SvsbeZ4siy1WheuRGjtaJmoD1J8bFqNXhsG6U\"]"), runtime_error);
// create shielded address so we have the spending key
BOOST_CHECK_NO_THROW(retValue = CallRPC("z_getnewaddress"));
std::string myzaddr = retValue.get_str();
// return empty array for this address
BOOST_CHECK_NO_THROW(retValue = CallRPC("z_listunspent 1 999 false [\"" + myzaddr + "\"]"));
UniValue arr = retValue.get_array();
BOOST_CHECK_EQUAL(0, arr.size());
// duplicate address error
BOOST_CHECK_THROW(CallRPC("z_listunspent 1 999 false [\"" + myzaddr + "\", \"" + myzaddr + "\"]"), runtime_error);
}
BOOST_AUTO_TEST_CASE(rpc_z_shieldcoinbase_parameters)
{
SelectParams(CBaseChainParams::TESTNET);
LOCK(pwalletMain->cs_wallet);
BOOST_CHECK_THROW(CallRPC("z_shieldcoinbase"), runtime_error);
BOOST_CHECK_THROW(CallRPC("z_shieldcoinbase toofewargs"), runtime_error);
BOOST_CHECK_THROW(CallRPC("z_shieldcoinbase too many args shown here"), runtime_error);
// bad from address
BOOST_CHECK_THROW(CallRPC("z_shieldcoinbase "
"INVALIDtmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ tnpoQJVnYBZZqkFadj2bJJLThNCxbADGB5gSGeYTAGGrT5tejsxY9Zc1BtY8nnHmZkB"), runtime_error);
// bad from address
BOOST_CHECK_THROW(CallRPC("z_shieldcoinbase "
"** tnpoQJVnYBZZqkFadj2bJJLThNCxbADGB5gSGeYTAGGrT5tejsxY9Zc1BtY8nnHmZkB"), runtime_error);
// bad to address
BOOST_CHECK_THROW(CallRPC("z_shieldcoinbase "
"tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ INVALIDtnpoQJVnYBZZqkFadj2bJJLThNCxbADGB5gSGeYTAGGrT5tejsxY9Zc1BtY8nnHmZkB"), runtime_error);
// invalid fee amount, cannot be negative
BOOST_CHECK_THROW(CallRPC("z_shieldcoinbase "
"tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ "
"tnpoQJVnYBZZqkFadj2bJJLThNCxbADGB5gSGeYTAGGrT5tejsxY9Zc1BtY8nnHmZkB "
"-0.0001"
), runtime_error);
// invalid fee amount, bigger than MAX_MONEY
BOOST_CHECK_THROW(CallRPC("z_shieldcoinbase "
"tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ "
"tnpoQJVnYBZZqkFadj2bJJLThNCxbADGB5gSGeYTAGGrT5tejsxY9Zc1BtY8nnHmZkB "
"21000001"
), runtime_error);
// invalid limit, must be at least 0
BOOST_CHECK_THROW(CallRPC("z_shieldcoinbase "
"tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ "
"tnpoQJVnYBZZqkFadj2bJJLThNCxbADGB5gSGeYTAGGrT5tejsxY9Zc1BtY8nnHmZkB "
"100 -1"
), runtime_error);
// Mutable tx containing contextual information we need to build tx
UniValue retValue = CallRPC("getblockcount");
int nHeight = retValue.get_int();
CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), nHeight + 1);
if (mtx.nVersion == 1) {
mtx.nVersion = 2;
}
// Test constructor of AsyncRPCOperation_sendmany
std::string testnetzaddr = "ztjiDe569DPNbyTE6TSdJTaSDhoXEHLGvYoUnBU1wfVNU52TEyT6berYtySkd21njAeEoh8fFJUT42kua9r8EnhBaEKqCpP";
std::string mainnetzaddr = "zcMuhvq8sEkHALuSU2i4NbNQxshSAYrpCExec45ZjtivYPbuiFPwk6WHy4SvsbeZ4siy1WheuRGjtaJmoD1J8bFqNXhsG6U";
try {
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_shieldcoinbase(mtx, {}, testnetzaddr, -1 ));
} catch (const UniValue& objError) {
BOOST_CHECK( find_error(objError, "Fee is out of range"));
}
try {
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_shieldcoinbase(mtx, {}, testnetzaddr, 1));
} catch (const UniValue& objError) {
BOOST_CHECK( find_error(objError, "Empty inputs"));
}
// Testnet payment addresses begin with 'zt'. This test detects an incorrect prefix.
try {
std::vector<ShieldCoinbaseUTXO> inputs = { ShieldCoinbaseUTXO{uint256(),0,0} };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_shieldcoinbase(mtx, inputs, mainnetzaddr, 1) );
} catch (const UniValue& objError) {
BOOST_CHECK( find_error(objError, "Invalid to address"));
}
}
BOOST_AUTO_TEST_CASE(rpc_z_shieldcoinbase_internals)
{
SelectParams(CBaseChainParams::TESTNET);
LOCK(pwalletMain->cs_wallet);
// Mutable tx containing contextual information we need to build tx
UniValue retValue = CallRPC("getblockcount");
int nHeight = retValue.get_int();
CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), nHeight + 1);
if (mtx.nVersion == 1) {
mtx.nVersion = 2;
}
// Test that option -mempooltxinputlimit is respected.
mapArgs["-mempooltxinputlimit"] = "1";
// Add keys manually
auto pa = pwalletMain->GenerateNewZKey();
std::string zaddr = EncodePaymentAddress(pa);
// Supply 2 inputs when mempool limit is 1
{
std::vector<ShieldCoinbaseUTXO> inputs = { ShieldCoinbaseUTXO{uint256(),0,0}, ShieldCoinbaseUTXO{uint256(),0,0} };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_shieldcoinbase(mtx, inputs, zaddr) );
operation->main();
BOOST_CHECK(operation->isFailed());
std::string msg = operation->getErrorMessage();
BOOST_CHECK( msg.find("Number of inputs 2 is greater than mempooltxinputlimit of 1") != string::npos);
}
// Insufficient funds
{
std::vector<ShieldCoinbaseUTXO> inputs = { ShieldCoinbaseUTXO{uint256(),0,0} };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_shieldcoinbase(mtx, inputs, zaddr) );
operation->main();
BOOST_CHECK(operation->isFailed());
std::string msg = operation->getErrorMessage();
BOOST_CHECK( msg.find("Insufficient coinbase funds") != string::npos);
}
// Test the perform_joinsplit methods.
{
// Dummy input so the operation object can be instantiated.
std::vector<ShieldCoinbaseUTXO> inputs = { ShieldCoinbaseUTXO{uint256(),0,100000} };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_shieldcoinbase(mtx, inputs, zaddr) );
std::shared_ptr<AsyncRPCOperation_shieldcoinbase> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_shieldcoinbase> (operation);
TEST_FRIEND_AsyncRPCOperation_shieldcoinbase proxy(ptr);
static_cast<AsyncRPCOperation_shieldcoinbase *>(operation.get())->testmode = true;
ShieldCoinbaseJSInfo info;
info.vjsin.push_back(JSInput());
info.vjsin.push_back(JSInput());
info.vjsin.push_back(JSInput());
try {
proxy.perform_joinsplit(info);
} catch (const std::runtime_error & e) {
BOOST_CHECK( string(e.what()).find("unsupported joinsplit input")!= string::npos);
}
info.vjsin.clear();
try {
proxy.perform_joinsplit(info);
} catch (const std::runtime_error & e) {
BOOST_CHECK( string(e.what()).find("error verifying joinsplit")!= string::npos);
}
}
}
BOOST_AUTO_TEST_CASE(rpc_z_mergetoaddress_parameters)
{
SelectParams(CBaseChainParams::TESTNET);
LOCK(pwalletMain->cs_wallet);
BOOST_CHECK_THROW(CallRPC("z_mergetoaddress"), runtime_error);
BOOST_CHECK_THROW(CallRPC("z_mergetoaddress toofewargs"), runtime_error);
BOOST_CHECK_THROW(CallRPC("z_mergetoaddress just too many args present for this method"), runtime_error);
// bad from address
BOOST_CHECK_THROW(CallRPC("z_mergetoaddress "
"[\"INVALIDtmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ\"] tnpoQJVnYBZZqkFadj2bJJLThNCxbADGB5gSGeYTAGGrT5tejsxY9Zc1BtY8nnHmZkB"), runtime_error);
// bad from address
BOOST_CHECK_THROW(CallRPC("z_mergetoaddress "
"** tnpoQJVnYBZZqkFadj2bJJLThNCxbADGB5gSGeYTAGGrT5tejsxY9Zc1BtY8nnHmZkB"), runtime_error);
// bad from address
BOOST_CHECK_THROW(CallRPC("z_mergetoaddress "
"[\"**\"] tnpoQJVnYBZZqkFadj2bJJLThNCxbADGB5gSGeYTAGGrT5tejsxY9Zc1BtY8nnHmZkB"), runtime_error);
// bad from address
BOOST_CHECK_THROW(CallRPC("z_mergetoaddress "
"tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ tnpoQJVnYBZZqkFadj2bJJLThNCxbADGB5gSGeYTAGGrT5tejsxY9Zc1BtY8nnHmZkB"), runtime_error);
// bad from address
BOOST_CHECK_THROW(CallRPC("z_mergetoaddress "
"[tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ] tnpoQJVnYBZZqkFadj2bJJLThNCxbADGB5gSGeYTAGGrT5tejsxY9Zc1BtY8nnHmZkB"), runtime_error);
// bad to address
BOOST_CHECK_THROW(CallRPC("z_mergetoaddress "
"[\"tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ\"] INVALIDtnpoQJVnYBZZqkFadj2bJJLThNCxbADGB5gSGeYTAGGrT5tejsxY9Zc1BtY8nnHmZkB"), runtime_error);
// duplicate address
BOOST_CHECK_THROW(CallRPC("z_mergetoaddress "
"[\"tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ\", \"tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ\"] "
"tmQP9L3s31cLsghVYf2Jb5MhKj1jRBPoeQn"
), runtime_error);
// invalid fee amount, cannot be negative
BOOST_CHECK_THROW(CallRPC("z_mergetoaddress "
"[\"tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ\"] "
"tnpoQJVnYBZZqkFadj2bJJLThNCxbADGB5gSGeYTAGGrT5tejsxY9Zc1BtY8nnHmZkB "
"-0.0001"
), runtime_error);
// invalid fee amount, bigger than MAX_MONEY
BOOST_CHECK_THROW(CallRPC("z_mergetoaddress "
"[\"tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ\"] "
"tnpoQJVnYBZZqkFadj2bJJLThNCxbADGB5gSGeYTAGGrT5tejsxY9Zc1BtY8nnHmZkB "
"21000001"
), runtime_error);
// invalid transparent limit, must be at least 0
BOOST_CHECK_THROW(CallRPC("z_mergetoaddress "
"[\"tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ\"] "
"tnpoQJVnYBZZqkFadj2bJJLThNCxbADGB5gSGeYTAGGrT5tejsxY9Zc1BtY8nnHmZkB "
"0.0001 -1"
), runtime_error);
// invalid shielded limit, must be at least 0
BOOST_CHECK_THROW(CallRPC("z_mergetoaddress "
"[\"tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ\"] "
"tnpoQJVnYBZZqkFadj2bJJLThNCxbADGB5gSGeYTAGGrT5tejsxY9Zc1BtY8nnHmZkB "
"0.0001 100 -1"
), runtime_error);
// memo bigger than allowed length of ZC_MEMO_SIZE
std::vector<char> v (2 * (ZC_MEMO_SIZE+1)); // x2 for hexadecimal string format
std::fill(v.begin(),v.end(), 'A');
std::string badmemo(v.begin(), v.end());
auto pa = pwalletMain->GenerateNewZKey();
std::string zaddr1 = EncodePaymentAddress(pa);
BOOST_CHECK_THROW(CallRPC(string("z_mergetoaddress [\"tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ\"] ")
+ zaddr1 + " 0.0001 100 100 " + badmemo), runtime_error);
// Mutable tx containing contextual information we need to build tx
UniValue retValue = CallRPC("getblockcount");
int nHeight = retValue.get_int();
CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), nHeight + 1);
// Test constructor of AsyncRPCOperation_mergetoaddress
MergeToAddressRecipient testnetzaddr(
"ztjiDe569DPNbyTE6TSdJTaSDhoXEHLGvYoUnBU1wfVNU52TEyT6berYtySkd21njAeEoh8fFJUT42kua9r8EnhBaEKqCpP",
"testnet memo");
MergeToAddressRecipient mainnetzaddr(
"zcMuhvq8sEkHALuSU2i4NbNQxshSAYrpCExec45ZjtivYPbuiFPwk6WHy4SvsbeZ4siy1WheuRGjtaJmoD1J8bFqNXhsG6U",
"mainnet memo");
try {
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_mergetoaddress(mtx, {}, {}, testnetzaddr, -1 ));
BOOST_FAIL("Should have caused an error");
} catch (const UniValue& objError) {
BOOST_CHECK( find_error(objError, "Fee is out of range"));
}
try {
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_mergetoaddress(mtx, {}, {}, testnetzaddr, 1));
BOOST_FAIL("Should have caused an error");
} catch (const UniValue& objError) {
BOOST_CHECK( find_error(objError, "No inputs"));
}
std::vector<MergeToAddressInputUTXO> inputs = { MergeToAddressInputUTXO{ COutPoint{uint256(), 0}, 0} };
try {
MergeToAddressRecipient badaddr("", "memo");
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_mergetoaddress(mtx, inputs, {}, badaddr, 1));
BOOST_FAIL("Should have caused an error");
} catch (const UniValue& objError) {
BOOST_CHECK( find_error(objError, "Recipient parameter missing"));
}
// Testnet payment addresses begin with 'zt'. This test detects an incorrect prefix.
try {
std::vector<MergeToAddressInputUTXO> inputs = { MergeToAddressInputUTXO{ COutPoint{uint256(), 0}, 0} };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_mergetoaddress(mtx, inputs, {}, mainnetzaddr, 1) );
BOOST_FAIL("Should have caused an error");
} catch (const UniValue& objError) {
BOOST_CHECK( find_error(objError, "Invalid recipient address"));
}
}
// TODO: test private methods
BOOST_AUTO_TEST_CASE(rpc_z_mergetoaddress_internals)
{
SelectParams(CBaseChainParams::TESTNET);
LOCK(pwalletMain->cs_wallet);
// Mutable tx containing contextual information we need to build tx
UniValue retValue = CallRPC("getblockcount");
int nHeight = retValue.get_int();
CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), nHeight + 1);
// Test that option -mempooltxinputlimit is respected.
mapArgs["-mempooltxinputlimit"] = "1";
// Add keys manually
BOOST_CHECK_NO_THROW(retValue = CallRPC("getnewaddress"));
MergeToAddressRecipient taddr1(retValue.get_str(), "");
auto pa = pwalletMain->GenerateNewZKey();
MergeToAddressRecipient zaddr1(EncodePaymentAddress(pa), "DEADBEEF");
// Supply 2 inputs when mempool limit is 1
{
std::vector<MergeToAddressInputUTXO> inputs = {
MergeToAddressInputUTXO{COutPoint{uint256(),0},0},
MergeToAddressInputUTXO{COutPoint{uint256(),0},0}
};
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_mergetoaddress(mtx, inputs, {}, zaddr1) );
operation->main();
BOOST_CHECK(operation->isFailed());
std::string msg = operation->getErrorMessage();
BOOST_CHECK( msg.find("Number of transparent inputs 2 is greater than mempooltxinputlimit of 1") != string::npos);
}
// Insufficient funds
{
std::vector<MergeToAddressInputUTXO> inputs = { MergeToAddressInputUTXO{COutPoint{uint256(),0},0} };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_mergetoaddress(mtx, inputs, {}, zaddr1) );
operation->main();
BOOST_CHECK(operation->isFailed());
std::string msg = operation->getErrorMessage();
BOOST_CHECK( msg.find("Insufficient funds, have 0.00 and miners fee is 0.0001") != string::npos);
}
// get_memo_from_hex_string())
{
std::vector<MergeToAddressInputUTXO> inputs = { MergeToAddressInputUTXO{COutPoint{uint256(),0},100000} };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_mergetoaddress(mtx, inputs, {}, zaddr1) );
std::shared_ptr<AsyncRPCOperation_mergetoaddress> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_mergetoaddress> (operation);
TEST_FRIEND_AsyncRPCOperation_mergetoaddress proxy(ptr);
std::string memo = "DEADBEEF";
std::array<unsigned char, ZC_MEMO_SIZE> array = proxy.get_memo_from_hex_string(memo);
BOOST_CHECK_EQUAL(array[0], 0xDE);
BOOST_CHECK_EQUAL(array[1], 0xAD);
BOOST_CHECK_EQUAL(array[2], 0xBE);
BOOST_CHECK_EQUAL(array[3], 0xEF);
for (int i=4; i<ZC_MEMO_SIZE; i++) {
BOOST_CHECK_EQUAL(array[i], 0x00); // zero padding
}
// memo is longer than allowed
std::vector<char> v (2 * (ZC_MEMO_SIZE+1));
std::fill(v.begin(),v.end(), 'A');
std::string bigmemo(v.begin(), v.end());
try {
proxy.get_memo_from_hex_string(bigmemo);
BOOST_FAIL("Should have caused an error");
} catch (const UniValue& objError) {
BOOST_CHECK( find_error(objError, "too big"));
}
// invalid hexadecimal string
std::fill(v.begin(),v.end(), '@'); // not a hex character
std::string badmemo(v.begin(), v.end());
try {
proxy.get_memo_from_hex_string(badmemo);
BOOST_FAIL("Should have caused an error");
} catch (const UniValue& objError) {
BOOST_CHECK( find_error(objError, "hexadecimal format"));
}
// odd length hexadecimal string
std::fill(v.begin(),v.end(), 'A');
v.resize(v.size() - 1);
assert(v.size() %2 == 1); // odd length
std::string oddmemo(v.begin(), v.end());
try {
proxy.get_memo_from_hex_string(oddmemo);
BOOST_FAIL("Should have caused an error");
} catch (const UniValue& objError) {
BOOST_CHECK( find_error(objError, "hexadecimal format"));
}
}
// Test the perform_joinsplit methods.
{
// Dummy input so the operation object can be instantiated.
std::vector<MergeToAddressInputUTXO> inputs = { MergeToAddressInputUTXO{COutPoint{uint256(),0},100000} };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_mergetoaddress(mtx, inputs, {}, zaddr1) );
std::shared_ptr<AsyncRPCOperation_mergetoaddress> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_mergetoaddress> (operation);
TEST_FRIEND_AsyncRPCOperation_mergetoaddress proxy(ptr);
// Enable test mode so tx is not sent and proofs are not generated
static_cast<AsyncRPCOperation_sendmany *>(operation.get())->testmode = true;
MergeToAddressJSInfo info;
std::vector<boost::optional < SproutWitness>> witnesses;
uint256 anchor;
try {
proxy.perform_joinsplit(info, witnesses, anchor);
BOOST_FAIL("Should have caused an error");
} catch (const std::runtime_error & e) {
BOOST_CHECK( string(e.what()).find("anchor is null")!= string::npos);
}
try {
std::vector<JSOutPoint> v;
proxy.perform_joinsplit(info, v);
BOOST_FAIL("Should have caused an error");
} catch (const std::runtime_error & e) {
BOOST_CHECK( string(e.what()).find("anchor is null")!= string::npos);
}
info.notes.push_back(SproutNote());
try {
proxy.perform_joinsplit(info);
BOOST_FAIL("Should have caused an error");
} catch (const std::runtime_error & e) {
BOOST_CHECK( string(e.what()).find("number of notes")!= string::npos);
}
info.notes.clear();
info.vjsin.push_back(JSInput());
info.vjsin.push_back(JSInput());
info.vjsin.push_back(JSInput());
try {
proxy.perform_joinsplit(info);
BOOST_FAIL("Should have caused an error");
} catch (const std::runtime_error & e) {
BOOST_CHECK( string(e.what()).find("unsupported joinsplit input")!= string::npos);
}
info.vjsin.clear();
try {
proxy.perform_joinsplit(info);
BOOST_FAIL("Should have caused an error");
} catch (const std::runtime_error & e) {
BOOST_CHECK( string(e.what()).find("error verifying joinsplit")!= string::npos);
}
}
// Raw joinsplit is a zaddr->zaddr
{
std::string raw = "020000000000000000000100000000000000001027000000000000183a0d4c46c369078705e39bcfebee59a978dbd210ce8de3efc9555a03fbabfd3cea16693d730c63850d7e48ccde79854c19adcb7e9dcd7b7d18805ee09083f6b16e1860729d2d4a90e2f2acd009cf78b5eb0f4a6ee4bdb64b1262d7ce9eb910c460b02022991e968d0c50ee44908e4ccccbc591d0053bcca154dd6d6fc400a29fa686af4682339832ccea362a62aeb9df0d5aa74f86a1e75ac0f48a8ccc41e0a940643c6c33e1d09223b0a46eaf47a1bb4407cfc12b1dcf83a29c0cef51e45c7876ca5b9e5bae86d92976eb3ef68f29cd29386a8be8451b50f82bf9da10c04651868655194da8f6ed3d241bb5b5ff93a3e2bbe44644544d88bcde5cc35978032ee92699c7a61fcbb395e7583f47e698c4d53ede54f956629400bf510fb5e22d03158cc10bdcaaf29e418ef18eb6480dd9c8b9e2a377809f9f32a556ef872febd0021d4ad013aa9f0b7255e98e408d302abefd33a71180b720271835b487ab309e160b06dfe51932120fb84a7ede16b20c53599a11071592109e10260f265ee60d48c62bfe24074020e9b586ce9e9356e68f2ad1a9538258234afe4b83a209f178f45202270eaeaeecaf2ce3100b2c5a714f75f35777a9ebff5ebf47059d2bbf6f3726190216468f2b152673b766225b093f3a2f827c86d6b48b42117fec1d0ac38dd7af700308dcfb02eba821612b16a2c164c47715b9b0c93900893b1aba2ea03765c94d87022db5be06ab338d1912e0936dfe87586d0a8ee49144a6cd2e306abdcb652faa3e0222739deb23154d778b50de75069a4a2cce1208cd1ced3cb4744c9888ce1c2fcd2e66dc31e62d3aa9e423d7275882525e9981f92e84ac85975b8660739407efbe1e34c2249420fde7e17db3096d5b22e83d051d01f0e6e7690dca7d168db338aadf0897fedac10de310db2b1bff762d322935dddbb60c2efb8b15d231fa17b84630371cb275c209f0c4c7d0c68b150ea5cd514122215e3f7fcfb351d69514788d67c2f3c8922581946e3a04bdf1f07f15696ca76eb95b10698bf1188fd882945c57657515889d042a6fc45d38cbc943540c4f0f6d1c45a1574c81f3e42d1eb8702328b729909adee8a5cfed7c79d54627d1fd389af941d878376f7927b9830ca659bf9ab18c5ca5192d52d02723008728d03701b8ab3e1c4a3109409ec0b13df334c7deec3523eeef4c97b5603e643de3a647b873f4c1b47fbfc6586ba66724f112e51fc93839648005043620aa3ce458e246d77977b19c53d98e3e812de006afc1a79744df236582943631d04cc02941ac4be500e4ed9fb9e3e7cc187b1c4050fad1d9d09d5fd70d5d01d615b439d8c0015d2eb10398bcdbf8c4b2bd559dbe4c288a186aed3f86f608da4d582e120c4a896e015e2241900d1daeccd05db968852677c71d752bec46de9962174b46f980e8cc603654daf8b98a3ee92dac066033954164a89568b70b1780c2ce2410b2f816dbeddb2cd463e0c8f21a52cf6427d9647a6fd4bafa8fb4cd4d47ac057b0160bee86c6b2fb8adce214c2bcdda277512200adf0eaa5d2114a2c077b009836a68ec254bfe56f51d147b9afe2ddd9cb917c0c2de19d81b7b8fd9f4574f51fa1207630dc13976f4d7587c962f761af267de71f3909a576e6bedaf6311633910d291ac292c467cc8331ef577aef7646a5d949322fa0367a49f20597a13def53136ee31610395e3e48d291fd8f58504374031fe9dcfba5e06086ebcf01a9106f6a4d6e16e19e4c5bb893f7da79419c94eca31a384be6fa1747284dee0fc3bbc8b1b860172c10b29c1594bb8c747d7fe05827358ff2160f49050001625ffe2e880bd7fc26cd0ffd89750745379a8e862816e08a5a2008043921ab6a4976064ac18f7ee37b6628bc0127d8d5ebd3548e41d8881a082d86f20b32e33094f15a0e6ea6074b08c6cd28142de94713451640a55985051f5577eb54572699d838cb34a79c8939e981c0c277d06a6e2ce69ccb74f8a691ff08f81d8b99e6a86223d29a2b7c8e7b041aba44ea678ae654277f7e91cbfa79158b989164a3d549d9f4feb0cc43169699c13e321fe3f4b94258c75d198ff9184269cd6986c55409e07528c93f64942c6c283ce3917b4bf4c3be2fe3173c8c38cccb35f1fbda0ca88b35a599c0678cb22aa8eabea8249dbd2e4f849fffe69803d299e435ebcd7df95854003d8eda17a74d98b4be0e62d45d7fe48c06a6f464a14f8e0570077cc631279092802a89823f031eef5e1028a6d6fdbd502869a731ee7d28b4d6c71b419462a30d31442d3ee444ffbcbd16d558c9000c97e949c2b1f9d6f6d8db7b9131ebd963620d3fc8595278d6f8fdf49084325373196d53e64142fa5a23eccd6ef908c4d80b8b3e6cc334b7f7012103a3682e4678e9b518163d262a39a2c1a69bf88514c52b7ccd7cc8dc80e71f7c2ec0701cff982573eb0c2c4daeb47fa0b586f4451c10d1da2e5d182b03dd067a5e971b3a6138ca6667aaf853d2ac03b80a1d5870905f2cfb6c78ec3c3719c02f973d638a0f973424a2b0f2b0023f136d60092fe15fba4bc180b9176bd0ff576e053f1af6939fe9ca256203ffaeb3e569f09774d2a6cbf91873e56651f4d6ff77e0b5374b0a1a201d7e523604e0247644544cc571d48c458a4f96f45580b";
UniValue obj(UniValue::VOBJ);
obj.push_back(Pair("rawtxn", raw));
// we have the spending key for the dummy recipient zaddr1
std::vector<MergeToAddressInputUTXO> inputs = { MergeToAddressInputUTXO{COutPoint{uint256(),0},100000} };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_mergetoaddress(mtx, inputs, {}, zaddr1) );
std::shared_ptr<AsyncRPCOperation_mergetoaddress> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_mergetoaddress> (operation);
TEST_FRIEND_AsyncRPCOperation_mergetoaddress proxy(ptr);
// Enable test mode so tx is not sent
static_cast<AsyncRPCOperation_sendmany *>(operation.get())->testmode = true;
// Pretend that the operation completed successfully
proxy.set_state(OperationStatus::SUCCESS);
// Verify test mode is returning output (since no input taddrs, signed and unsigned are the same).
BOOST_CHECK_NO_THROW( proxy.sign_send_raw_transaction(obj) );
UniValue result = operation->getResult();
BOOST_CHECK(!result.isNull());
UniValue resultObj = result.get_obj();
std::string hex = find_value(resultObj, "hex").get_str();
BOOST_CHECK_EQUAL(hex, raw);
}
}
BOOST_AUTO_TEST_SUITE_END()