220 lines
7.4 KiB
C++
220 lines
7.4 KiB
C++
#include <gtest/gtest.h>
|
|
#include <gmock/gmock.h>
|
|
|
|
#include "main.h"
|
|
#include "primitives/transaction.h"
|
|
#include "consensus/merkle.h"
|
|
#include "consensus/validation.h"
|
|
#include "transaction_builder.h"
|
|
#include "util/test.h"
|
|
#include "gtest/utils.h"
|
|
#include "wallet/asyncrpcoperation_shieldcoinbase.h"
|
|
#include "wallet/asyncrpcoperation_sendmany.h"
|
|
#include "wallet/memo.h"
|
|
#include "zcash/JoinSplit.hpp"
|
|
|
|
#include <librustzcash.h>
|
|
#include <rust/ed25519.h>
|
|
|
|
namespace {
|
|
|
|
bool find_error(const UniValue& objError, const std::string& expected) {
|
|
return find_value(objError, "message").get_str().find(expected) != string::npos;
|
|
}
|
|
|
|
CWalletTx FakeWalletTx() {
|
|
CMutableTransaction mtx;
|
|
mtx.vout.resize(1);
|
|
mtx.vout[0].nValue = 1;
|
|
return CWalletTx(nullptr, mtx);
|
|
}
|
|
|
|
}
|
|
|
|
// TODO: test private methods
|
|
TEST(WalletRPCTests, RPCZMergeToAddressInternals)
|
|
{
|
|
LoadProofParameters();
|
|
|
|
SelectParams(CBaseChainParams::TESTNET);
|
|
LoadGlobalWallet();
|
|
|
|
const Consensus::Params& consensusParams = Params().GetConsensus();
|
|
KeyIO keyIO(Params());
|
|
{
|
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
|
|
UniValue retValue;
|
|
|
|
// Mutable tx containing contextual information we need to build tx
|
|
// We removed the ability to create pre-Sapling Sprout proofs, so we can
|
|
// only create Sapling-onwards transactions.
|
|
int nHeight = consensusParams.vUpgrades[Consensus::UPGRADE_SAPLING].nActivationHeight;
|
|
CMutableTransaction mtx = CreateNewContextualCMutableTransaction(consensusParams, nHeight + 1, false);
|
|
|
|
// Add keys manually
|
|
auto taddr = pwalletMain->GenerateNewKey(true).GetID();
|
|
std::string taddr_string = keyIO.EncodeDestination(taddr);
|
|
|
|
NetAmountRecipient taddr1(keyIO.DecodePaymentAddress(taddr_string).value(), Memo());
|
|
auto pa = pwalletMain->GenerateNewSproutZKey();
|
|
NetAmountRecipient zaddr1(pa, Memo());
|
|
|
|
WalletTxBuilder builder(Params(), minRelayTxFee);
|
|
auto selector = CWallet::LegacyTransparentZTXOSelector(
|
|
true,
|
|
TransparentCoinbasePolicy::Disallow);
|
|
TransactionStrategy strategy(PrivacyPolicy::AllowRevealedSenders);
|
|
|
|
// Insufficient funds
|
|
{
|
|
SpendableInputs inputs;
|
|
auto wtx = FakeWalletTx();
|
|
inputs.utxos.emplace_back(COutput(&wtx, 0, 100, true));
|
|
builder.PrepareTransaction(
|
|
*pwalletMain,
|
|
selector,
|
|
inputs,
|
|
zaddr1,
|
|
chainActive,
|
|
strategy,
|
|
0,
|
|
1)
|
|
.map_error([](const auto& err) {
|
|
EXPECT_TRUE(examine(err, match {
|
|
[](const AddressResolutionError& are) {
|
|
return are == AddressResolutionError::SproutRecipientsNotSupported;
|
|
},
|
|
[](const auto&) { return false; },
|
|
}));
|
|
})
|
|
.map([](const auto&) { EXPECT_TRUE(false); });
|
|
}
|
|
}
|
|
UnloadGlobalWallet();
|
|
}
|
|
|
|
TEST(WalletRPCTests, RPCZsendmanyTaddrToSapling)
|
|
{
|
|
LoadProofParameters();
|
|
SelectParams(CBaseChainParams::TESTNET);
|
|
|
|
LoadGlobalWallet();
|
|
|
|
RegtestActivateSapling();
|
|
{
|
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
|
|
if (!pwalletMain->HaveMnemonicSeed()) {
|
|
pwalletMain->GenerateNewSeed();
|
|
}
|
|
|
|
UniValue retValue;
|
|
|
|
KeyIO keyIO(Params());
|
|
// add keys manually
|
|
auto taddr = pwalletMain->GenerateNewKey(true).GetID();
|
|
auto pa = pwalletMain->GenerateNewLegacySaplingZKey();
|
|
|
|
const Consensus::Params& consensusParams = Params().GetConsensus();
|
|
|
|
int nextBlockHeight = chainActive.Height() + 1;
|
|
|
|
// Add a fake transaction to the wallet
|
|
CMutableTransaction mtx = CreateNewContextualCMutableTransaction(consensusParams, nextBlockHeight, false);
|
|
CScript scriptPubKey = CScript() << OP_DUP << OP_HASH160 << ToByteVector(taddr) << OP_EQUALVERIFY << OP_CHECKSIG;
|
|
mtx.vout.push_back(CTxOut(5 * COIN, scriptPubKey));
|
|
CWalletTx wtx(pwalletMain, mtx);
|
|
pwalletMain->LoadWalletTx(wtx);
|
|
|
|
// Fake-mine the transaction
|
|
EXPECT_EQ(-1, chainActive.Height());
|
|
CBlock block;
|
|
block.vtx.push_back(wtx);
|
|
block.hashMerkleRoot = BlockMerkleRoot(block);
|
|
auto blockHash = block.GetHash();
|
|
CBlockIndex fakeIndex {block};
|
|
mapBlockIndex.insert(std::make_pair(blockHash, &fakeIndex));
|
|
chainActive.SetTip(&fakeIndex);
|
|
EXPECT_TRUE(chainActive.Contains(&fakeIndex));
|
|
EXPECT_EQ(0, chainActive.Height());
|
|
wtx.SetMerkleBranch(block);
|
|
pwalletMain->LoadWalletTx(wtx);
|
|
|
|
// Context that z_sendmany requires
|
|
auto builder = WalletTxBuilder(Params(), minRelayTxFee);
|
|
mtx = CreateNewContextualCMutableTransaction(consensusParams, nextBlockHeight, false);
|
|
|
|
// we need AllowFullyTransparent because the transaction will result
|
|
// in transparent change as a consequence of sending from a legacy taddr
|
|
TransactionStrategy strategy(PrivacyPolicy::AllowFullyTransparent);
|
|
auto selector = pwalletMain->ZTXOSelectorForAddress(
|
|
taddr,
|
|
true,
|
|
TransparentCoinbasePolicy::Disallow,
|
|
false).value();
|
|
std::vector<Payment> recipients = { Payment(pa, 1*COIN, Memo::FromHexOrThrow("ABCD")) };
|
|
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 0, 0, strategy, std::nullopt));
|
|
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
|
|
|
|
// Enable test mode so tx is not sent
|
|
static_cast<AsyncRPCOperation_sendmany *>(operation.get())->testmode = true;
|
|
|
|
// Generate the Sapling shielding transaction
|
|
operation->main();
|
|
if (!operation->isSuccess()) {
|
|
FAIL() << operation->getErrorMessage();
|
|
}
|
|
|
|
// Get the transaction
|
|
auto result = operation->getResult();
|
|
ASSERT_TRUE(result.isObject());
|
|
auto hexTx = result["hex"].getValStr();
|
|
CDataStream ss(ParseHex(hexTx), SER_NETWORK, PROTOCOL_VERSION);
|
|
CTransaction tx;
|
|
ss >> tx;
|
|
ASSERT_FALSE(tx.vShieldedOutput.empty());
|
|
|
|
// We shouldn't be able to decrypt with the empty ovk
|
|
EXPECT_FALSE(AttemptSaplingOutDecryption(
|
|
tx.vShieldedOutput[0].outCiphertext,
|
|
uint256(),
|
|
tx.vShieldedOutput[0].cv,
|
|
tx.vShieldedOutput[0].cmu,
|
|
tx.vShieldedOutput[0].ephemeralKey));
|
|
|
|
// We shouldn't be able to decrypt with a random ovk
|
|
EXPECT_FALSE(AttemptSaplingOutDecryption(
|
|
tx.vShieldedOutput[0].outCiphertext,
|
|
random_uint256(),
|
|
tx.vShieldedOutput[0].cv,
|
|
tx.vShieldedOutput[0].cmu,
|
|
tx.vShieldedOutput[0].ephemeralKey));
|
|
|
|
auto accountKey = pwalletMain->GetLegacyAccountKey().ToAccountPubKey();
|
|
auto ovks = accountKey.GetOVKsForShielding();
|
|
// We should not be able to decrypt with the internal change OVK for shielding
|
|
EXPECT_FALSE(AttemptSaplingOutDecryption(
|
|
tx.vShieldedOutput[0].outCiphertext,
|
|
ovks.first,
|
|
tx.vShieldedOutput[0].cv,
|
|
tx.vShieldedOutput[0].cmu,
|
|
tx.vShieldedOutput[0].ephemeralKey));
|
|
// We should be able to decrypt with the external OVK for shielding
|
|
EXPECT_TRUE(AttemptSaplingOutDecryption(
|
|
tx.vShieldedOutput[0].outCiphertext,
|
|
ovks.second,
|
|
tx.vShieldedOutput[0].cv,
|
|
tx.vShieldedOutput[0].cmu,
|
|
tx.vShieldedOutput[0].ephemeralKey));
|
|
|
|
// Tear down
|
|
chainActive.SetTip(NULL);
|
|
mapBlockIndex.erase(blockHash);
|
|
|
|
}
|
|
// Revert to default
|
|
RegtestDeactivateSapling();
|
|
UnloadGlobalWallet();
|
|
}
|