Browse Source

Add rpc to enable and disable Sprout to Sapling migration

pull/245/head
Eirik0 5 years ago
parent
commit
81a45d6984
  1. 1
      qa/pull-tester/rpc-tests.sh
  2. 96
      qa/rpc-tests/sprout_sapling_migration.py
  3. 13
      qa/rpc-tests/test_framework/util.py
  4. 2
      src/Makefile.am
  5. 5
      src/main.cpp
  6. 1
      src/main.h
  7. 3
      src/validationinterface.cpp
  8. 3
      src/validationinterface.h
  9. 196
      src/wallet/asyncrpcoperation_saplingmigration.cpp
  10. 31
      src/wallet/asyncrpcoperation_saplingmigration.h
  11. 22
      src/wallet/rpcwallet.cpp
  12. 47
      src/wallet/wallet.cpp
  13. 6
      src/wallet/wallet.h

1
qa/pull-tester/rpc-tests.sh

@ -71,6 +71,7 @@ testScripts=(
'p2p_node_bloom.py'
'regtest_signrawtransaction.py'
'finalsaplingroot.py'
'sprout_sapling_migration.py'
'turnstile.py'
);
testScriptsExt=(

96
qa/rpc-tests/sprout_sapling_migration.py

@ -0,0 +1,96 @@
#!/usr/bin/env python
# Copyright (c) 2019 The Zcash developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
import sys; assert sys.version_info < (3,), ur"This script does not run under Python 3. Please use Python 2.7.x."
from decimal import Decimal
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_true, get_coinbase_address, \
initialize_chain_clean, start_nodes, wait_and_assert_operationid_status
class SproutSaplingMigration(BitcoinTestFramework):
def setup_nodes(self):
return start_nodes(4, self.options.tmpdir, [[
'-nuparams=5ba81b19:100', # Overwinter
'-nuparams=76b809bb:100', # Sapling
]] * 4)
def setup_chain(self):
print("Initializing test directory " + self.options.tmpdir)
initialize_chain_clean(self.options.tmpdir, 4)
def run_test(self):
print "Mining blocks..."
self.nodes[0].generate(101)
self.sync_all()
# Send some ZEC to a Sprout address
tAddr = get_coinbase_address(self.nodes[0])
sproutAddr = self.nodes[0].z_getnewaddress('sprout')
saplingAddr = self.nodes[0].z_getnewaddress('sapling')
opid = self.nodes[0].z_sendmany(tAddr, [{"address": sproutAddr, "amount": Decimal('10')}], 1, 0)
wait_and_assert_operationid_status(self.nodes[0], opid)
self.nodes[0].generate(1)
self.sync_all()
assert_equal(self.nodes[0].z_getbalance(sproutAddr), Decimal('10'))
assert_equal(self.nodes[0].z_getbalance(saplingAddr), Decimal('0'))
# Migrate
assert_equal(True, self.nodes[0].z_setmigration(True))
print "Mining to block 494..."
self.nodes[0].generate(392) # 102 -> 494
self.sync_all()
# At 494 we should have no async operations
assert_equal(0, len(self.nodes[0].z_getoperationstatus()), "num async operations at 494")
self.nodes[0].generate(1)
self.sync_all()
# At 495 we should have an async operation
operationstatus = self.nodes[0].z_getoperationstatus()
assert_equal(1, len(operationstatus), "num async operations at 495")
assert_equal('saplingmigration', operationstatus[0]['method'])
assert_equal(500, operationstatus[0]['target_height'])
print "migration operation: {}".format(operationstatus)
migration_opid = operationstatus[0]['id']
result = wait_and_assert_operationid_status(self.nodes[0], migration_opid)
print "result: {}".format(result)
assert_equal(0, len(self.nodes[0].getrawmempool()), "mempool size at 495")
self.nodes[0].generate(3)
self.sync_all()
# At 498 the mempool will be empty and no funds will have moved
assert_equal(0, len(self.nodes[0].getrawmempool()), "mempool size at 498")
assert_equal(self.nodes[0].z_getbalance(sproutAddr), Decimal('10'))
assert_equal(self.nodes[0].z_getbalance(saplingAddr), Decimal('0'))
self.nodes[0].generate(1)
self.sync_all()
# At 499 there will be a transaction in the mempool and the note will be locked
assert_equal(1, len(self.nodes[0].getrawmempool()), "mempool size at 499")
assert_equal(self.nodes[0].z_getbalance(sproutAddr), Decimal('0'))
assert_equal(self.nodes[0].z_getbalance(saplingAddr), Decimal('0'))
assert_true(self.nodes[0].z_getbalance(saplingAddr, 0) > Decimal('0'), "Unconfirmed sapling")
self.nodes[0].generate(1)
self.sync_all()
# At 500 funds will have moved
sprout_balance = self.nodes[0].z_getbalance(sproutAddr)
sapling_balance = self.nodes[0].z_getbalance(saplingAddr)
print "sprout balance: {}, sapling balance: {}".format(sprout_balance, sapling_balance)
assert_true(sprout_balance < Decimal('10'), "Should have less Sprout funds")
assert_true(sapling_balance > Decimal('0'), "Should have more Sapling funds")
if __name__ == '__main__':
SproutSaplingMigration().main()

13
qa/rpc-tests/test_framework/util.py

@ -399,26 +399,29 @@ def wait_and_assert_operationid_status(node, myopid, in_status='success', in_err
assert_true(result is not None, "timeout occured")
status = result['status']
txid = None
ret = None
errormsg = None
if status == "failed":
errormsg = result['error']['message']
elif status == "success":
txid = result['result']['txid']
if type(result['result']) is dict and result['result'].get('txid'):
ret = result['result']['txid']
else:
ret = result['result']
if os.getenv("PYTHON_DEBUG", ""):
print('...returned status: {}'.format(status))
if errormsg is not None:
print('...returned error: {}'.format(errormsg))
assert_equal(in_status, status, "Operation returned mismatched status. Error Message: {}".format(errormsg))
if errormsg is not None:
assert_true(in_errormsg is not None, "No error retured. Expected: {}".format(errormsg))
assert_true(in_errormsg in errormsg, "Error returned: {}. Error expected: {}".format(errormsg, in_errormsg))
return result # if there was an error return the result
return result # if there was an error return the result
else:
return txid # otherwise return the txid
return ret # otherwise return the txid
# Find a coinbase address on the node, filtering by the number of UTXOs it has.
# If no filter is provided, returns the coinbase address on the node containing

2
src/Makefile.am

@ -215,6 +215,7 @@ BITCOIN_CORE_H = \
validationinterface.h \
version.h \
wallet/asyncrpcoperation_mergetoaddress.h \
wallet/asyncrpcoperation_saplingmigration.h \
wallet/asyncrpcoperation_sendmany.h \
wallet/asyncrpcoperation_shieldcoinbase.h \
wallet/crypter.h \
@ -304,6 +305,7 @@ libbitcoin_wallet_a_SOURCES = \
zcbenchmarks.cpp \
zcbenchmarks.h \
wallet/asyncrpcoperation_mergetoaddress.cpp \
wallet/asyncrpcoperation_saplingmigration.cpp \
wallet/asyncrpcoperation_sendmany.cpp \
wallet/asyncrpcoperation_shieldcoinbase.cpp \
wallet/crypter.cpp \

5
src/main.cpp

@ -78,6 +78,7 @@ bool fCoinbaseEnforcedProtectionEnabled = true;
size_t nCoinCacheUsage = 5000 * 300;
uint64_t nPruneTarget = 0;
bool fAlerts = DEFAULT_ALERTS;
bool fSaplingMigrationEnabled = false;
/* If the tip is older than this (in seconds), the node is considered to be in initial block download.
*/
int64_t nMaxTipAge = DEFAULT_MAX_TIP_AGE;
@ -3104,6 +3105,10 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock *
EnforceNodeDeprecation(pindexNew->nHeight);
if (fSaplingMigrationEnabled) {
GetMainSignals().RunSaplingMigration(pindexNew->nHeight);
}
int64_t nTime6 = GetTimeMicros(); nTimePostConnect += nTime6 - nTime5; nTimeTotal += nTime6 - nTime1;
LogPrint("bench", " - Connect postprocess: %.2fms [%.2fs]\n", (nTime6 - nTime5) * 0.001, nTimePostConnect * 0.000001);
LogPrint("bench", "- Connect block: %.2fms [%.2fs]\n", (nTime6 - nTime1) * 0.001, nTimeTotal * 0.000001);

1
src/main.h

@ -154,6 +154,7 @@ extern bool fCoinbaseEnforcedProtectionEnabled;
extern size_t nCoinCacheUsage;
extern CFeeRate minRelayTxFee;
extern bool fAlerts;
extern bool fSaplingMigrationEnabled;
extern int64_t nMaxTipAge;
/** Best header we've seen so far (used for getheaders queries' starting points). */

3
src/validationinterface.cpp

@ -18,6 +18,7 @@ void RegisterValidationInterface(CValidationInterface* pwalletIn) {
g_signals.EraseTransaction.connect(boost::bind(&CValidationInterface::EraseFromWallet, pwalletIn, _1));
g_signals.UpdatedTransaction.connect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1));
g_signals.ChainTip.connect(boost::bind(&CValidationInterface::ChainTip, pwalletIn, _1, _2, _3, _4, _5));
g_signals.RunSaplingMigration.connect(boost::bind(&CValidationInterface::RunSaplingMigration, pwalletIn, _1));
g_signals.SetBestChain.connect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1));
g_signals.Inventory.connect(boost::bind(&CValidationInterface::Inventory, pwalletIn, _1));
g_signals.Broadcast.connect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, _1));
@ -33,6 +34,7 @@ void UnregisterValidationInterface(CValidationInterface* pwalletIn) {
g_signals.Broadcast.disconnect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, _1));
g_signals.Inventory.disconnect(boost::bind(&CValidationInterface::Inventory, pwalletIn, _1));
g_signals.ChainTip.disconnect(boost::bind(&CValidationInterface::ChainTip, pwalletIn, _1, _2, _3, _4, _5));
g_signals.RunSaplingMigration.disconnect(boost::bind(&CValidationInterface::RunSaplingMigration, pwalletIn, _1));
g_signals.SetBestChain.disconnect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1));
g_signals.UpdatedTransaction.disconnect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1));
g_signals.EraseTransaction.disconnect(boost::bind(&CValidationInterface::EraseFromWallet, pwalletIn, _1));
@ -47,6 +49,7 @@ void UnregisterAllValidationInterfaces() {
g_signals.Broadcast.disconnect_all_slots();
g_signals.Inventory.disconnect_all_slots();
g_signals.ChainTip.disconnect_all_slots();
g_signals.RunSaplingMigration.disconnect_all_slots();
g_signals.SetBestChain.disconnect_all_slots();
g_signals.UpdatedTransaction.disconnect_all_slots();
g_signals.EraseTransaction.disconnect_all_slots();

3
src/validationinterface.h

@ -37,6 +37,7 @@ protected:
virtual void SyncTransaction(const CTransaction &tx, const CBlock *pblock) {}
virtual void EraseFromWallet(const uint256 &hash) {}
virtual void ChainTip(const CBlockIndex *pindex, const CBlock *pblock, SproutMerkleTree sproutTree, SaplingMerkleTree saplingTree, bool added) {}
virtual void RunSaplingMigration(int blockHeight) {}
virtual void SetBestChain(const CBlockLocator &locator) {}
virtual void UpdatedTransaction(const uint256 &hash) {}
virtual void Inventory(const uint256 &hash) {}
@ -60,6 +61,8 @@ struct CMainSignals {
boost::signals2::signal<void (const uint256 &)> UpdatedTransaction;
/** Notifies listeners of a change to the tip of the active block chain. */
boost::signals2::signal<void (const CBlockIndex *, const CBlock *, SproutMerkleTree, SaplingMerkleTree, bool)> ChainTip;
/** Notifies listeners that they may need to update the status of the Sprout->Sapling migration */
boost::signals2::signal<void (const int)> RunSaplingMigration;
/** Notifies listeners of a new active block chain. */
boost::signals2::signal<void (const CBlockLocator &)> SetBestChain;
/** Notifies listeners about an inventory item being seen on the network. */

196
src/wallet/asyncrpcoperation_saplingmigration.cpp

@ -0,0 +1,196 @@
#include "assert.h"
#include "boost/variant/static_visitor.hpp"
#include "asyncrpcoperation_saplingmigration.h"
#include "init.h"
#include "rpc/protocol.h"
#include "random.h"
#include "sync.h"
#include "tinyformat.h"
#include "transaction_builder.h"
#include "util.h"
#include "wallet.h"
const CAmount FEE = 10000;
AsyncRPCOperation_saplingmigration::AsyncRPCOperation_saplingmigration(int targetHeight) : targetHeight_(targetHeight) {}
AsyncRPCOperation_saplingmigration::~AsyncRPCOperation_saplingmigration() {}
void AsyncRPCOperation_saplingmigration::main() {
if (isCancelled())
return;
set_state(OperationStatus::EXECUTING);
start_execution_clock();
bool success = false;
try {
success = main_impl();
} catch (const UniValue& objError) {
int code = find_value(objError, "code").get_int();
std::string message = find_value(objError, "message").get_str();
set_error_code(code);
set_error_message(message);
} catch (const runtime_error& e) {
set_error_code(-1);
set_error_message("runtime error: " + string(e.what()));
} catch (const logic_error& e) {
set_error_code(-1);
set_error_message("logic error: " + string(e.what()));
} catch (const exception& e) {
set_error_code(-1);
set_error_message("general exception: " + string(e.what()));
} catch (...) {
set_error_code(-2);
set_error_message("unknown error");
}
stop_execution_clock();
if (success) {
set_state(OperationStatus::SUCCESS);
} else {
set_state(OperationStatus::FAILED);
}
std::string s = strprintf("%s: Sprout->Sapling transactions sent. (status=%s", getId(), getStateAsString());
if (success) {
s += strprintf(", success)\n");
} else {
s += strprintf(", error=%s)\n", getErrorMessage());
}
LogPrintf("%s", s);
}
bool AsyncRPCOperation_saplingmigration::main_impl() {
std::vector<CSproutNotePlaintextEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries;
{
LOCK2(cs_main, pwalletMain->cs_wallet);
// Consider, should notes be sorted?
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, "", 0);
}
if (sproutEntries.empty()) { // Consider, should the migration remain enabled?
fSaplingMigrationEnabled = false;
return true;
}
CAmount availableFunds = 0;
for (const CSproutNotePlaintextEntry& sproutEntry : sproutEntries) {
availableFunds = sproutEntry.plaintext.value();
}
// If the remaining amount to be migrated is less than 0.01 ZEC, end the migration.
if (availableFunds < CENT) {
fSaplingMigrationEnabled = false;
return true;
}
HDSeed seed;
if (!pwalletMain->GetHDSeed(seed)) {
throw JSONRPCError(
RPC_WALLET_ERROR,
"AsyncRPCOperation_AsyncRPCOperation_saplingmigration: HD seed not found");
}
libzcash::SaplingPaymentAddress migrationDestAddress = getMigrationDestAddress(seed);
auto consensusParams = Params().GetConsensus();
// Up to the limit of 5, as many transactions are sent as are needed to migrate the remaining funds
int numTxCreated = 0;
int noteIndex = 0;
do {
CAmount amountToSend = chooseAmount(availableFunds);
auto builder = TransactionBuilder(consensusParams, targetHeight_, pwalletMain, pzcashParams);
std::vector<CSproutNotePlaintextEntry> fromNotes;
CAmount fromNoteAmount = 0;
while (fromNoteAmount < amountToSend) {
auto sproutEntry = sproutEntries[noteIndex++];
fromNotes.push_back(sproutEntry);
fromNoteAmount += sproutEntry.plaintext.value();
}
availableFunds -= fromNoteAmount;
for (const CSproutNotePlaintextEntry& sproutEntry : fromNotes) {
libzcash::SproutNote sproutNote = sproutEntry.plaintext.note(sproutEntry.address);
libzcash::SproutSpendingKey sproutSk;
pwalletMain->GetSproutSpendingKey(sproutEntry.address, sproutSk);
std::vector<JSOutPoint> vOutPoints = {sproutEntry.jsop};
// Each migration transaction SHOULD specify an anchor at height N-10
// for each Sprout JoinSplit description
// TODO: the above functionality (in comment) is not implemented in zcashd
uint256 inputAnchor;
std::vector<boost::optional<SproutWitness>> vInputWitnesses;
pwalletMain->GetSproutNoteWitnesses(vOutPoints, vInputWitnesses, inputAnchor);
builder.AddSproutInput(sproutSk, sproutNote, vInputWitnesses[0].get());
}
// The amount chosen *includes* the 0.0001 ZEC fee for this transaction, i.e.
// the value of the Sapling output will be 0.0001 ZEC less.
builder.SetFee(FEE);
builder.AddSaplingOutput(ovkForShieldingFromTaddr(seed), migrationDestAddress, amountToSend - FEE);
CTransaction tx = builder.Build().GetTxOrThrow();
if (isCancelled()) {
break;
}
pwalletMain->AddPendingSaplingMigrationTx(tx);
++numTxCreated;
} while (numTxCreated < 5 && availableFunds > CENT);
UniValue res(UniValue::VOBJ);
res.push_back(Pair("num_tx_created", numTxCreated));
set_result(res);
return true;
}
CAmount AsyncRPCOperation_saplingmigration::chooseAmount(const CAmount& availableFunds) {
CAmount amount = 0;
do {
// 1. Choose an integer exponent uniformly in the range 6 to 8 inclusive.
int exponent = GetRand(3) + 6;
// 2. Choose an integer mantissa uniformly in the range 1 to 99 inclusive.
uint64_t mantissa = GetRand(99) + 1;
// 3. Calculate amount := (mantissa * 10^exponent) zatoshi.
int pow = std::pow(10, exponent);
amount = mantissa * pow;
// 4. If amount is greater than the amount remaining to send, repeat from step 1.
} while (amount > availableFunds);
return amount;
}
// Unless otherwise specified, the migration destination address is the address for Sapling account 0
libzcash::SaplingPaymentAddress AsyncRPCOperation_saplingmigration::getMigrationDestAddress(const HDSeed& seed) {
// Derive the address for Sapling account 0
auto m = libzcash::SaplingExtendedSpendingKey::Master(seed);
uint32_t bip44CoinType = Params().BIP44CoinType();
// We use a fixed keypath scheme of m/32'/coin_type'/account'
// Derive m/32'
auto m_32h = m.Derive(32 | ZIP32_HARDENED_KEY_LIMIT);
// Derive m/32'/coin_type'
auto m_32h_cth = m_32h.Derive(bip44CoinType | ZIP32_HARDENED_KEY_LIMIT);
// Derive account key at next index, skip keys already known to the wallet
libzcash::SaplingExtendedSpendingKey xsk = m_32h_cth.Derive(0 | ZIP32_HARDENED_KEY_LIMIT);
libzcash::SaplingPaymentAddress toAddress = xsk.DefaultAddress();
// Refactor: this is similar logic as in the visitor HaveSpendingKeyForPaymentAddress and is used elsewhere
libzcash::SaplingIncomingViewingKey ivk;
libzcash::SaplingFullViewingKey fvk;
if (!(pwalletMain->GetSaplingIncomingViewingKey(toAddress, ivk) &&
pwalletMain->GetSaplingFullViewingKey(ivk, fvk) &&
pwalletMain->HaveSaplingSpendingKey(fvk))) {
// Sapling account 0 must be the first address returned by GenerateNewSaplingZKey
assert(pwalletMain->GenerateNewSaplingZKey() == toAddress);
}
return toAddress;
}
UniValue AsyncRPCOperation_saplingmigration::getStatus() const {
UniValue v = AsyncRPCOperation::getStatus();
UniValue obj = v.get_obj();
obj.push_back(Pair("method", "saplingmigration"));
obj.push_back(Pair("target_height", targetHeight_));
return obj;
}

31
src/wallet/asyncrpcoperation_saplingmigration.h

@ -0,0 +1,31 @@
#include "amount.h"
#include "asyncrpcoperation.h"
#include "univalue.h"
#include "zcash/Address.hpp"
#include "zcash/zip32.h"
class AsyncRPCOperation_saplingmigration : public AsyncRPCOperation
{
public:
AsyncRPCOperation_saplingmigration(int targetHeight);
virtual ~AsyncRPCOperation_saplingmigration();
// We don't want to be copied or moved around
AsyncRPCOperation_saplingmigration(AsyncRPCOperation_saplingmigration const&) = delete; // Copy construct
AsyncRPCOperation_saplingmigration(AsyncRPCOperation_saplingmigration&&) = delete; // Move construct
AsyncRPCOperation_saplingmigration& operator=(AsyncRPCOperation_saplingmigration const&) = delete; // Copy assign
AsyncRPCOperation_saplingmigration& operator=(AsyncRPCOperation_saplingmigration&&) = delete; // Move assign
virtual void main();
virtual UniValue getStatus() const;
private:
int targetHeight_;
bool main_impl();
CAmount chooseAmount(const CAmount& availableFunds);
libzcash::SaplingPaymentAddress getMigrationDestAddress(const HDSeed& seed);
};

22
src/wallet/rpcwallet.cpp

@ -3906,6 +3906,27 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
return operationId;
}
UniValue z_setmigration(const UniValue& params, bool fHelp) {
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
if (fHelp || params.size() != 1)
throw runtime_error(
"z_setmigration enabled\n"
"When enabled the Sprout to Sapling migration will attempt to migrate all funds from this wallet’s\n"
"Sprout addresses to either the address for Sapling account 0 or one specified by the parameter\n"
"'-migrationdestaddress'. This migration is designed to minimize information leakage. As a result,\n"
"for wallets with a significant Sprout balance, this process may take several weeks. The migration\n"
"works by sending, up to 5, as many transactions as possible whenever the blockchain reaches a height\n"
"equal to 499 modulo 500. The transaction amounts are picked according to the random distribution\n"
"specified in ZIP 308. The migration will end once the wallet’s Sprout balance is below 0.01 " + CURRENCY_UNIT + ".\n"
"\nArguments:\n"
"1. enabled (boolean, required) 'true' or 'false' to enable or disable respectively.\n"
"\nResult:\n"
"enabled (boolean) Whether or not migration is enabled (echos the argument).\n"
);
fSaplingMigrationEnabled = params[0].get_bool();
return fSaplingMigrationEnabled;
}
/**
When estimating the number of coinbase utxos we can shield in a single transaction:
@ -4660,6 +4681,7 @@ static const CRPCCommand commands[] =
{ "wallet", "z_gettotalbalance", &z_gettotalbalance, false },
{ "wallet", "z_mergetoaddress", &z_mergetoaddress, false },
{ "wallet", "z_sendmany", &z_sendmany, false },
{ "wallet", "z_setmigration", &z_setmigration, false },
{ "wallet", "z_shieldcoinbase", &z_shieldcoinbase, false },
{ "wallet", "z_getoperationstatus", &z_getoperationstatus, true },
{ "wallet", "z_getoperationresult", &z_getoperationresult, true },

47
src/wallet/wallet.cpp

@ -5,8 +5,10 @@
#include "wallet/wallet.h"
#include "asyncrpcqueue.h"
#include "checkpoints.h"
#include "coincontrol.h"
#include "core_io.h"
#include "consensus/upgrades.h"
#include "consensus/validation.h"
#include "consensus/consensus.h"
@ -15,12 +17,14 @@
#include "main.h"
#include "net.h"
#include "rpc/protocol.h"
#include "rpc/server.h"
#include "script/script.h"
#include "script/sign.h"
#include "timedata.h"
#include "utilmoneystr.h"
#include "zcash/Note.hpp"
#include "crypter.h"
#include "wallet/asyncrpcoperation_saplingmigration.h"
#include "zcash/zip32.h"
#include <assert.h>
@ -32,6 +36,8 @@
using namespace std;
using namespace libzcash;
extern UniValue sendrawtransaction(const UniValue& params, bool fHelp);
/**
* Settings
*/
@ -566,6 +572,47 @@ void CWallet::ChainTip(const CBlockIndex *pindex,
UpdateSaplingNullifierNoteMapForBlock(pblock);
}
void CWallet::RunSaplingMigration(int blockHeight) {
if (!NetworkUpgradeActive(blockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) {
return;
}
LOCK(cs_wallet);
// The migration transactions to be sent in a particular batch can take
// significant time to generate, and this time depends on the speed of the user's
// computer. If they were generated only after a block is seen at the target
// height minus 1, then this could leak information. Therefore, for target
// height N, implementations SHOULD start generating the transactions at around
// height N-5
if (blockHeight % 500 == 495) {
if (saplingMigrationOperation != nullptr) {
saplingMigrationOperation->cancel();
}
pendingSaplingMigrationTxs.clear();
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_saplingmigration(blockHeight + 5));
saplingMigrationOperation = operation;
q->addOperation(operation);
} else if (blockHeight % 500 == 499) {
for (const CTransaction& transaction : pendingSaplingMigrationTxs) {
// The following is taken from z_sendmany/z_mergetoaddress
// Send the transaction
// TODO: Use CWallet::CommitTransaction instead of sendrawtransaction
auto signedtxn = EncodeHexTx(transaction);
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.");
}
}
pendingSaplingMigrationTxs.clear();
}
}
void CWallet::AddPendingSaplingMigrationTx(const CTransaction& tx) {
pendingSaplingMigrationTxs.push_back(tx);
}
void CWallet::SetBestChain(const CBlockLocator& loc)
{
CWalletDB walletdb(strWalletFile);

6
src/wallet/wallet.h

@ -7,6 +7,7 @@
#define BITCOIN_WALLET_WALLET_H
#include "amount.h"
#include "asyncrpcoperation.h"
#include "coins.h"
#include "key.h"
#include "keystore.h"
@ -755,6 +756,9 @@ private:
TxNullifiers mapTxSproutNullifiers;
TxNullifiers mapTxSaplingNullifiers;
std::vector<CTransaction> pendingSaplingMigrationTxs;
std::shared_ptr<AsyncRPCOperation> saplingMigrationOperation = nullptr;
void AddToTransparentSpends(const COutPoint& outpoint, const uint256& wtxid);
void AddToSproutSpends(const uint256& nullifier, const uint256& wtxid);
void AddToSaplingSpends(const uint256& nullifier, const uint256& wtxid);
@ -1183,6 +1187,8 @@ public:
CAmount GetCredit(const CTransaction& tx, const isminefilter& filter) const;
CAmount GetChange(const CTransaction& tx) const;
void ChainTip(const CBlockIndex *pindex, const CBlock *pblock, SproutMerkleTree sproutTree, SaplingMerkleTree saplingTree, bool added);
void RunSaplingMigration(int blockHeight);
void AddPendingSaplingMigrationTx(const CTransaction& tx);
/** Saves witness caches and best block locator to disk. */
void SetBestChain(const CBlockLocator& loc);
std::set<std::pair<libzcash::PaymentAddress, uint256>> GetNullifiersForAddresses(const std::set<libzcash::PaymentAddress> & addresses);

Loading…
Cancel
Save