Auto merge of #3518 - str4d:3216-z_shieldcoinbase, r=str4d
Add Sapling support to z_shieldcoinbase Part of #3216.
This commit is contained in:
@@ -1520,13 +1520,13 @@ BOOST_AUTO_TEST_CASE(rpc_z_shieldcoinbase_parameters)
|
||||
std::string mainnetzaddr = "zcMuhvq8sEkHALuSU2i4NbNQxshSAYrpCExec45ZjtivYPbuiFPwk6WHy4SvsbeZ4siy1WheuRGjtaJmoD1J8bFqNXhsG6U";
|
||||
|
||||
try {
|
||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_shieldcoinbase(mtx, {}, testnetzaddr, -1 ));
|
||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_shieldcoinbase(TransactionBuilder(), 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));
|
||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_shieldcoinbase(TransactionBuilder(), mtx, {}, testnetzaddr, 1));
|
||||
} catch (const UniValue& objError) {
|
||||
BOOST_CHECK( find_error(objError, "Empty inputs"));
|
||||
}
|
||||
@@ -1534,7 +1534,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_shieldcoinbase_parameters)
|
||||
// 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) );
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_shieldcoinbase(TransactionBuilder(), mtx, inputs, mainnetzaddr, 1) );
|
||||
} catch (const UniValue& objError) {
|
||||
BOOST_CHECK( find_error(objError, "Invalid to address"));
|
||||
}
|
||||
@@ -1567,7 +1567,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_shieldcoinbase_internals)
|
||||
// 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) );
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_shieldcoinbase(TransactionBuilder(), mtx, inputs, zaddr) );
|
||||
operation->main();
|
||||
BOOST_CHECK(operation->isFailed());
|
||||
std::string msg = operation->getErrorMessage();
|
||||
@@ -1577,7 +1577,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_shieldcoinbase_internals)
|
||||
// Insufficient funds
|
||||
{
|
||||
std::vector<ShieldCoinbaseUTXO> inputs = { ShieldCoinbaseUTXO{uint256(),0,0} };
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_shieldcoinbase(mtx, inputs, zaddr) );
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_shieldcoinbase(TransactionBuilder(), mtx, inputs, zaddr) );
|
||||
operation->main();
|
||||
BOOST_CHECK(operation->isFailed());
|
||||
std::string msg = operation->getErrorMessage();
|
||||
@@ -1588,7 +1588,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_shieldcoinbase_internals)
|
||||
{
|
||||
// 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> operation( new AsyncRPCOperation_shieldcoinbase(TransactionBuilder(), 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;
|
||||
|
||||
@@ -128,7 +128,7 @@ boost::optional<CTransaction> TransactionBuilder::Build()
|
||||
// Send change to the specified change address. If no change address
|
||||
// was set, send change to the first Sapling address given as input.
|
||||
if (zChangeAddr) {
|
||||
AddSaplingOutput(zChangeAddr->first, zChangeAddr->second, change, {});
|
||||
AddSaplingOutput(zChangeAddr->first, zChangeAddr->second, change);
|
||||
} else if (tChangeAddr) {
|
||||
// tChangeAddr has already been validated.
|
||||
assert(AddTransparentOutput(tChangeAddr.value(), change));
|
||||
@@ -136,7 +136,7 @@ boost::optional<CTransaction> TransactionBuilder::Build()
|
||||
auto fvk = spends[0].expsk.full_viewing_key();
|
||||
auto note = spends[0].note;
|
||||
libzcash::SaplingPaymentAddress changeAddr(note.d, note.pk_d);
|
||||
AddSaplingOutput(fvk.ovk, changeAddr, change, {});
|
||||
AddSaplingOutput(fvk.ovk, changeAddr, change);
|
||||
} else {
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ public:
|
||||
uint256 ovk,
|
||||
libzcash::SaplingPaymentAddress to,
|
||||
CAmount value,
|
||||
std::array<unsigned char, ZC_MEMO_SIZE> memo);
|
||||
std::array<unsigned char, ZC_MEMO_SIZE> memo = {{0xF6}});
|
||||
|
||||
// Assumes that the value correctly corresponds to the provided UTXO.
|
||||
void AddTransparentInput(COutPoint utxo, CScript scriptPubKey, CAmount value);
|
||||
|
||||
@@ -55,12 +55,13 @@ static int find_output(UniValue obj, int n) {
|
||||
}
|
||||
|
||||
AsyncRPCOperation_shieldcoinbase::AsyncRPCOperation_shieldcoinbase(
|
||||
TransactionBuilder builder,
|
||||
CMutableTransaction contextualTx,
|
||||
std::vector<ShieldCoinbaseUTXO> inputs,
|
||||
std::string toAddress,
|
||||
CAmount fee,
|
||||
UniValue contextInfo) :
|
||||
tx_(contextualTx), inputs_(inputs), fee_(fee), contextinfo_(contextInfo)
|
||||
builder_(builder), tx_(contextualTx), inputs_(inputs), fee_(fee), contextinfo_(contextInfo)
|
||||
{
|
||||
assert(contextualTx.nVersion >= 2); // transaction format version must support vjoinsplit
|
||||
|
||||
@@ -75,8 +76,6 @@ AsyncRPCOperation_shieldcoinbase::AsyncRPCOperation_shieldcoinbase(
|
||||
// Check the destination address is valid for this network i.e. not testnet being used on mainnet
|
||||
auto address = DecodePaymentAddress(toAddress);
|
||||
if (IsValidPaymentAddress(address)) {
|
||||
// TODO: Add Sapling support. For now, ensure we can freely convert.
|
||||
assert(boost::get<libzcash::SproutPaymentAddress>(&address) != nullptr);
|
||||
tozaddr_ = address;
|
||||
} else {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid to address");
|
||||
@@ -181,7 +180,6 @@ void AsyncRPCOperation_shieldcoinbase::main() {
|
||||
// !!! Payment disclosure END
|
||||
}
|
||||
|
||||
|
||||
bool AsyncRPCOperation_shieldcoinbase::main_impl() {
|
||||
|
||||
CAmount minersFee = fee_;
|
||||
@@ -217,30 +215,34 @@ bool AsyncRPCOperation_shieldcoinbase::main_impl() {
|
||||
LogPrint("zrpc", "%s: spending %s to shield %s with fee %s\n",
|
||||
getId(), FormatMoney(targetAmount), FormatMoney(sendAmount), FormatMoney(minersFee));
|
||||
|
||||
return boost::apply_visitor(ShieldToAddress(this, sendAmount), tozaddr_);
|
||||
}
|
||||
|
||||
bool ShieldToAddress::operator()(const libzcash::SproutPaymentAddress &zaddr) const {
|
||||
// update the transaction with these inputs
|
||||
CMutableTransaction rawTx(tx_);
|
||||
for (ShieldCoinbaseUTXO & t : inputs_) {
|
||||
CMutableTransaction rawTx(m_op->tx_);
|
||||
for (ShieldCoinbaseUTXO & t : m_op->inputs_) {
|
||||
CTxIn in(COutPoint(t.txid, t.vout));
|
||||
rawTx.vin.push_back(in);
|
||||
}
|
||||
tx_ = CTransaction(rawTx);
|
||||
m_op->tx_ = CTransaction(rawTx);
|
||||
|
||||
// Prepare raw transaction to handle JoinSplits
|
||||
CMutableTransaction mtx(tx_);
|
||||
crypto_sign_keypair(joinSplitPubKey_.begin(), joinSplitPrivKey_);
|
||||
mtx.joinSplitPubKey = joinSplitPubKey_;
|
||||
tx_ = CTransaction(mtx);
|
||||
CMutableTransaction mtx(m_op->tx_);
|
||||
crypto_sign_keypair(m_op->joinSplitPubKey_.begin(), m_op->joinSplitPrivKey_);
|
||||
mtx.joinSplitPubKey = m_op->joinSplitPubKey_;
|
||||
m_op->tx_ = CTransaction(mtx);
|
||||
|
||||
// Create joinsplit
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
ShieldCoinbaseJSInfo info;
|
||||
info.vpub_old = sendAmount;
|
||||
info.vpub_new = 0;
|
||||
JSOutput jso = JSOutput(boost::get<libzcash::SproutPaymentAddress>(tozaddr_), sendAmount);
|
||||
JSOutput jso = JSOutput(zaddr, sendAmount);
|
||||
info.vjsout.push_back(jso);
|
||||
obj = perform_joinsplit(info);
|
||||
obj = m_op->perform_joinsplit(info);
|
||||
|
||||
sign_send_raw_transaction(obj);
|
||||
m_op->sign_send_raw_transaction(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -248,6 +250,69 @@ bool AsyncRPCOperation_shieldcoinbase::main_impl() {
|
||||
extern UniValue signrawtransaction(const UniValue& params, bool fHelp);
|
||||
extern UniValue sendrawtransaction(const UniValue& params, bool fHelp);
|
||||
|
||||
bool ShieldToAddress::operator()(const libzcash::SaplingPaymentAddress &zaddr) const {
|
||||
m_op->builder_.SetFee(m_op->fee_);
|
||||
|
||||
// Sending from a t-address, which we don't have an ovk for. Instead,
|
||||
// generate a common one from the HD seed. This ensures the data is
|
||||
// recoverable, while keeping it logically separate from the ZIP 32
|
||||
// Sapling key hierarchy, which the user might not be using.
|
||||
HDSeed seed;
|
||||
if (!pwalletMain->GetHDSeed(seed)) {
|
||||
throw JSONRPCError(
|
||||
RPC_WALLET_ERROR,
|
||||
"CWallet::GenerateNewSaplingZKey(): HD seed not found");
|
||||
}
|
||||
uint256 ovk = ovkForShieldingFromTaddr(seed);
|
||||
|
||||
// Add transparent inputs
|
||||
for (auto t : m_op->inputs_) {
|
||||
m_op->builder_.AddTransparentInput(COutPoint(t.txid, t.vout), t.scriptPubKey, t.amount);
|
||||
}
|
||||
|
||||
// Send all value to the target z-addr
|
||||
m_op->builder_.SendChangeTo(zaddr, ovk);
|
||||
|
||||
// Build the transaction
|
||||
auto maybe_tx = m_op->builder_.Build();
|
||||
if (!maybe_tx) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Failed to build transaction.");
|
||||
}
|
||||
m_op->tx_ = maybe_tx.get();
|
||||
|
||||
// Send the transaction
|
||||
// TODO: Use CWallet::CommitTransaction instead of sendrawtransaction
|
||||
auto signedtxn = EncodeHexTx(m_op->tx_);
|
||||
if (!m_op->testmode) {
|
||||
UniValue params = UniValue(UniValue::VARR);
|
||||
params.push_back(signedtxn);
|
||||
UniValue sendResultValue = sendrawtransaction(params, false);
|
||||
if (sendResultValue.isNull()) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "sendrawtransaction did not return an error or a txid.");
|
||||
}
|
||||
|
||||
auto txid = sendResultValue.get_str();
|
||||
|
||||
UniValue o(UniValue::VOBJ);
|
||||
o.push_back(Pair("txid", txid));
|
||||
m_op->set_result(o);
|
||||
} else {
|
||||
// Test mode does not send the transaction to the network.
|
||||
UniValue o(UniValue::VOBJ);
|
||||
o.push_back(Pair("test", 1));
|
||||
o.push_back(Pair("txid", m_op->tx_.GetHash().ToString()));
|
||||
o.push_back(Pair("hex", signedtxn));
|
||||
m_op->set_result(o);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShieldToAddress::operator()(const libzcash::InvalidEncoding& no) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sign and send a raw transaction.
|
||||
* Raw transaction as hex string should be in object field "rawtxn"
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "asyncrpcoperation.h"
|
||||
#include "amount.h"
|
||||
#include "primitives/transaction.h"
|
||||
#include "transaction_builder.h"
|
||||
#include "zcash/JoinSplit.hpp"
|
||||
#include "zcash/Address.hpp"
|
||||
#include "wallet.h"
|
||||
@@ -27,6 +28,7 @@ using namespace libzcash;
|
||||
struct ShieldCoinbaseUTXO {
|
||||
uint256 txid;
|
||||
int vout;
|
||||
CScript scriptPubKey;
|
||||
CAmount amount;
|
||||
};
|
||||
|
||||
@@ -41,7 +43,13 @@ struct ShieldCoinbaseJSInfo
|
||||
|
||||
class AsyncRPCOperation_shieldcoinbase : public AsyncRPCOperation {
|
||||
public:
|
||||
AsyncRPCOperation_shieldcoinbase(CMutableTransaction contextualTx, std::vector<ShieldCoinbaseUTXO> inputs, std::string toAddress, CAmount fee = SHIELD_COINBASE_DEFAULT_MINERS_FEE, UniValue contextInfo = NullUniValue);
|
||||
AsyncRPCOperation_shieldcoinbase(
|
||||
TransactionBuilder builder,
|
||||
CMutableTransaction contextualTx,
|
||||
std::vector<ShieldCoinbaseUTXO> inputs,
|
||||
std::string toAddress,
|
||||
CAmount fee = SHIELD_COINBASE_DEFAULT_MINERS_FEE,
|
||||
UniValue contextInfo = NullUniValue);
|
||||
virtual ~AsyncRPCOperation_shieldcoinbase();
|
||||
|
||||
// We don't want to be copied or moved around
|
||||
@@ -59,6 +67,7 @@ public:
|
||||
bool paymentDisclosureMode = false; // Set to true to save esk for encrypted notes in payment disclosure database.
|
||||
|
||||
private:
|
||||
friend class ShieldToAddress;
|
||||
friend class TEST_FRIEND_AsyncRPCOperation_shieldcoinbase; // class for unit testing
|
||||
|
||||
UniValue contextinfo_; // optional data to include in return value from getStatus()
|
||||
@@ -71,6 +80,7 @@ private:
|
||||
|
||||
std::vector<ShieldCoinbaseUTXO> inputs_;
|
||||
|
||||
TransactionBuilder builder_;
|
||||
CTransaction tx_;
|
||||
|
||||
bool main_impl();
|
||||
@@ -88,6 +98,20 @@ private:
|
||||
std::vector<PaymentDisclosureKeyInfo> paymentDisclosureData_;
|
||||
};
|
||||
|
||||
class ShieldToAddress : public boost::static_visitor<bool>
|
||||
{
|
||||
private:
|
||||
AsyncRPCOperation_shieldcoinbase *m_op;
|
||||
CAmount sendAmount;
|
||||
public:
|
||||
ShieldToAddress(AsyncRPCOperation_shieldcoinbase *op, CAmount sendAmount) :
|
||||
m_op(op), sendAmount(sendAmount) {}
|
||||
|
||||
bool operator()(const libzcash::SproutPaymentAddress &zaddr) const;
|
||||
bool operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
|
||||
bool operator()(const libzcash::InvalidEncoding& no) const;
|
||||
};
|
||||
|
||||
|
||||
// To test private methods, a friend class can act as a proxy
|
||||
class TEST_FRIEND_AsyncRPCOperation_shieldcoinbase {
|
||||
|
||||
@@ -4006,6 +4006,7 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp)
|
||||
}
|
||||
|
||||
utxoCounter++;
|
||||
auto scriptPubKey = out.tx->vout[out.i].scriptPubKey;
|
||||
CAmount nValue = out.tx->vout[out.i].nValue;
|
||||
|
||||
if (!maxedOutFlag) {
|
||||
@@ -4016,7 +4017,7 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp)
|
||||
maxedOutFlag = true;
|
||||
} else {
|
||||
estimatedTxSize += increase;
|
||||
ShieldCoinbaseUTXO utxo = {out.tx->GetHash(), out.i, nValue};
|
||||
ShieldCoinbaseUTXO utxo = {out.tx->GetHash(), out.i, scriptPubKey, nValue};
|
||||
inputs.push_back(utxo);
|
||||
shieldedValue += nValue;
|
||||
}
|
||||
@@ -4051,7 +4052,12 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp)
|
||||
contextInfo.push_back(Pair("toaddress", params[1]));
|
||||
contextInfo.push_back(Pair("fee", ValueFromAmount(nFee)));
|
||||
|
||||
// Builder (used if Sapling addresses are involved)
|
||||
TransactionBuilder builder = TransactionBuilder(
|
||||
Params().GetConsensus(), nextBlockHeight, pwalletMain);
|
||||
|
||||
// Contextual transaction we will build on
|
||||
// (used if no Sapling addresses are involved)
|
||||
CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction(
|
||||
Params().GetConsensus(), nextBlockHeight);
|
||||
if (contextualTx.nVersion == 1) {
|
||||
@@ -4060,7 +4066,7 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp)
|
||||
|
||||
// Create operation and add to global queue
|
||||
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_shieldcoinbase(contextualTx, inputs, destaddress, nFee, contextInfo) );
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_shieldcoinbase(builder, contextualTx, inputs, destaddress, nFee, contextInfo) );
|
||||
q->addOperation(operation);
|
||||
AsyncRPCOperationId operationId = operation->getId();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user