Sapling support for z_shieldcoinbase and more

This commit is contained in:
miketout
2018-10-08 16:17:24 -07:00
19 changed files with 297 additions and 78 deletions

View File

@@ -56,12 +56,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
@@ -76,8 +77,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");
@@ -182,7 +181,6 @@ void AsyncRPCOperation_shieldcoinbase::main() {
// !!! Payment disclosure END
}
bool AsyncRPCOperation_shieldcoinbase::main_impl() {
CAmount minersFee = fee_;
@@ -218,32 +216,36 @@ 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));
if (t.amount >= ASSETCHAINS_TIMELOCKGTE)
in.nSequence = 0;
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;
}
@@ -251,6 +253,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"

View File

@@ -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 {

View File

@@ -4133,14 +4133,18 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
// Depending on the input notes, the actual tx size may turn out to be larger and perhaps invalid.
size_t txsize = 0;
for (int i = 0; i < zaddrRecipients.size(); i++) {
// TODO Check whether the recipient is a Sprout or Sapling address
JSDescription jsdesc;
if (mtx.fOverwintered && (mtx.nVersion >= SAPLING_TX_VERSION)) {
jsdesc.proof = GrothProof();
auto address = std::get<0>(zaddrRecipients[i]);
auto res = DecodePaymentAddress(address);
bool toSapling = boost::get<libzcash::SaplingPaymentAddress>(&res) != nullptr;
if (toSapling) {
mtx.vShieldedOutput.push_back(OutputDescription());
} else {
JSDescription jsdesc;
if (mtx.fOverwintered && (mtx.nVersion >= SAPLING_TX_VERSION)) {
jsdesc.proof = GrothProof();
}
mtx.vjoinsplit.push_back(jsdesc);
}
mtx.vjoinsplit.push_back(jsdesc);
}
CTransaction tx(mtx);
txsize += GetSerializeSize(tx, SER_NETWORK, tx.nVersion);
@@ -4360,6 +4364,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) {
@@ -4370,7 +4375,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;
}
@@ -4409,9 +4414,14 @@ 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
int blockHeight = chainActive.LastTip()->GetHeight();
nextBlockHeight = blockHeight + 1;
// (used if no Sapling addresses are involved)
CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction(
Params().GetConsensus(), nextBlockHeight);
contextualTx.nLockTime = blockHeight;
@@ -4421,7 +4431,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();

View File

@@ -2660,7 +2660,7 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate)
}
}
// Increment note witness caches
IncrementNoteWitnesses(pindex, &block, sproutTree, saplingTree);
ChainTip(pindex, &block, sproutTree, saplingTree, true);
pindex = chainActive.Next(pindex);
if (GetTime() >= nNow + 60) {