Auto merge of #4005 - daira:migration-expiration, r=Eirik0

Change expiry delta for migration transactions to 450 blocks

Documentation: to be done as part of the ZIP 308 update (zcash/zips#229).

Test plan: manually check that migration transactions have the expected expiry height, using getrawtransaction $TXID 1.

Closes #3999
This commit is contained in:
Homu 2019-05-10 12:31:45 -07:00
commit 664b7d32a1
11 changed files with 67 additions and 42 deletions

View File

@ -102,14 +102,25 @@ class SproutSaplingMigration(BitcoinTestFramework):
self.sync_all()
# At 499 % 500 there will be a transaction in the mempool and the note will be locked
assert_equal(1, len(node.getrawmempool()), "mempool size at 499 % 500")
mempool = node.getrawmempool()
print("mempool: {}".format(mempool))
assert_equal(1, len(mempool), "mempool size at 499 % 500")
assert_equal(node.z_getbalance(sproutAddr), Decimal('0'))
assert_equal(node.z_getbalance(saplingAddr), Decimal('0'))
assert_true(node.z_getbalance(saplingAddr, 0) > Decimal('0'), "Unconfirmed sapling balance at 499 % 500")
# Check that unmigrated amount + unfinalized = starting balance - fee
status = node.z_getmigrationstatus()
print("status: {}".format(status))
assert_equal(Decimal('9.9999'), Decimal(status['unmigrated_amount']) + Decimal(status['unfinalized_migrated_amount']))
# The transaction in the mempool should be the one listed in migration_txids,
# and it should expire at the next 450 % 500.
assert_equal(1, len(status['migration_txids']))
txid = status['migration_txids'][0]
assert_equal(txid, mempool[0])
tx = node.getrawtransaction(txid, 1)
assert_equal(target_height + 450, tx['expiryheight'])
node.generate(1)
self.sync_all()

View File

@ -93,7 +93,7 @@ TEST(TransactionBuilder, TransparentToSapling)
// Create a shielding transaction from transparent to Sapling
// 0.0005 t-ZEC in, 0.0004 z-ZEC out, 0.0001 t-ZEC fee
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
auto builder = TransactionBuilder(consensusParams, 1, expiryDelta, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
builder.AddSaplingOutput(fvk_from.ovk, pk, 40000, {});
auto tx = builder.Build().GetTxOrThrow();
@ -125,7 +125,7 @@ TEST(TransactionBuilder, SaplingToSapling) {
// Create a Sapling-only transaction
// 0.0004 z-ZEC in, 0.00025 z-ZEC out, 0.0001 t-ZEC fee, 0.00005 z-ZEC change
auto builder = TransactionBuilder(consensusParams, 2);
auto builder = TransactionBuilder(consensusParams, 2, expiryDelta);
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
// Check that trying to add a different anchor fails
@ -166,7 +166,7 @@ TEST(TransactionBuilder, SaplingToSprout) {
// - 0.0004 Sapling-ZEC in - 0.00025 Sprout-ZEC out
// - 0.00005 Sapling-ZEC change
// - 0.0001 t-ZEC fee
auto builder = TransactionBuilder(consensusParams, 2, nullptr, params);
auto builder = TransactionBuilder(consensusParams, 2, expiryDelta, nullptr, params);
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
builder.AddSproutOutput(sproutAddr, 25000);
auto tx = builder.Build().GetTxOrThrow();
@ -218,7 +218,7 @@ TEST(TransactionBuilder, SproutToSproutAndSapling) {
// - 0.00005 Sprout-ZEC change
// - 0.00005 Sapling-ZEC out
// - 0.00005 t-ZEC fee
auto builder = TransactionBuilder(consensusParams, 2, nullptr, params, &view);
auto builder = TransactionBuilder(consensusParams, 2, expiryDelta, nullptr, params, &view);
builder.SetFee(5000);
builder.AddSproutInput(sproutSk, sproutNote, sproutWitness);
builder.AddSproutOutput(sproutAddr, 6000);
@ -255,7 +255,7 @@ TEST(TransactionBuilder, ThrowsOnSproutOutputWithoutParams)
auto sk = libzcash::SproutSpendingKey::random();
auto addr = sk.address();
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, expiryDelta);
ASSERT_THROW(builder.AddSproutOutput(addr, 10), std::runtime_error);
}
@ -264,7 +264,7 @@ TEST(TransactionBuilder, ThrowsOnTransparentInputWithoutKeyStore)
SelectParams(CBaseChainParams::REGTEST);
auto consensusParams = Params().GetConsensus();
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, expiryDelta);
ASSERT_THROW(builder.AddTransparentInput(COutPoint(), CScript(), 1), std::runtime_error);
}
@ -275,7 +275,7 @@ TEST(TransactionBuilder, RejectsInvalidTransparentOutput)
// Default CTxDestination type is an invalid address
CTxDestination taddr;
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, expiryDelta);
ASSERT_THROW(builder.AddTransparentOutput(taddr, 50), UniValue);
}
@ -286,7 +286,7 @@ TEST(TransactionBuilder, RejectsInvalidTransparentChangeAddress)
// Default CTxDestination type is an invalid address
CTxDestination taddr;
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, expiryDelta);
ASSERT_THROW(builder.SendChangeTo(taddr), UniValue);
}
@ -311,13 +311,13 @@ TEST(TransactionBuilder, FailsWithNegativeChange)
// Fail if there is only a Sapling output
// 0.0005 z-ZEC out, 0.0001 t-ZEC fee
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, expiryDelta);
builder.AddSaplingOutput(fvk.ovk, pa, 50000, {});
EXPECT_EQ("Change cannot be negative", builder.Build().GetError());
// Fail if there is only a transparent output
// 0.0005 t-ZEC out, 0.0001 t-ZEC fee
builder = TransactionBuilder(consensusParams, 1, &keystore);
builder = TransactionBuilder(consensusParams, 1, expiryDelta, &keystore);
builder.AddTransparentOutput(taddr, 50000);
EXPECT_EQ("Change cannot be negative", builder.Build().GetError());
@ -359,14 +359,14 @@ TEST(TransactionBuilder, ChangeOutput)
// No change address and no Sapling spends
{
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
auto builder = TransactionBuilder(consensusParams, 1, expiryDelta, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
EXPECT_EQ("Could not determine change address", builder.Build().GetError());
}
// Change to the same address as the first Sapling spend
{
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
auto builder = TransactionBuilder(consensusParams, 1, expiryDelta, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
auto tx = builder.Build().GetTxOrThrow();
@ -381,7 +381,7 @@ TEST(TransactionBuilder, ChangeOutput)
// Change to a Sapling address
{
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
auto builder = TransactionBuilder(consensusParams, 1, expiryDelta, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
builder.SendChangeTo(zChangeAddr, fvkOut.ovk);
auto tx = builder.Build().GetTxOrThrow();
@ -396,7 +396,7 @@ TEST(TransactionBuilder, ChangeOutput)
// Change to a transparent address
{
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
auto builder = TransactionBuilder(consensusParams, 1, expiryDelta, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
builder.SendChangeTo(taddr);
auto tx = builder.Build().GetTxOrThrow();
@ -428,7 +428,7 @@ TEST(TransactionBuilder, SetFee)
// Default fee
{
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, expiryDelta);
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
builder.AddSaplingOutput(fvk.ovk, pa, 25000, {});
auto tx = builder.Build().GetTxOrThrow();
@ -443,7 +443,7 @@ TEST(TransactionBuilder, SetFee)
// Configured fee
{
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, expiryDelta);
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
builder.AddSaplingOutput(fvk.ovk, pa, 25000, {});
builder.SetFee(20000);
@ -472,7 +472,7 @@ TEST(TransactionBuilder, CheckSaplingTxVersion)
auto pk = sk.default_address();
// Cannot add Sapling outputs to a non-Sapling transaction
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, expiryDelta);
try {
builder.AddSaplingOutput(uint256(), pk, 12345, {});
} catch (std::runtime_error const & err) {

View File

@ -6612,12 +6612,19 @@ public:
// Set default values of new CMutableTransaction based on consensus rules at given height.
CMutableTransaction CreateNewContextualCMutableTransaction(const Consensus::Params& consensusParams, int nHeight)
{
CMutableTransaction mtx;
return CreateNewContextualCMutableTransaction(consensusParams, nHeight, expiryDelta);
}
CMutableTransaction CreateNewContextualCMutableTransaction(const Consensus::Params& consensusParams, int nHeight, int nExpiryDelta) {
CMutableTransaction mtx;
bool isOverwintered = NetworkUpgradeActive(nHeight, consensusParams, Consensus::UPGRADE_OVERWINTER);
if (isOverwintered) {
mtx.fOverwintered = true;
mtx.nExpiryHeight = nHeight + expiryDelta;
mtx.nExpiryHeight = nHeight + nExpiryDelta;
if (mtx.nExpiryHeight <= 0 || mtx.nExpiryHeight >= TX_EXPIRY_HEIGHT_THRESHOLD) {
throw new std::runtime_error("CreateNewContextualCMutableTransaction: invalid expiry height");
}
// NOTE: If the expiry height crosses into an incompatible consensus epoch, and it is changed to the last block
// of the current epoch (see below: Overwinter->Sapling), the transaction will be rejected if it falls within

View File

@ -595,7 +595,10 @@ int GetSpendHeight(const CCoinsViewCache& inputs);
uint64_t CalculateCurrentUsage();
/** Return a CMutableTransaction with contextual default values based on set of consensus rules at height */
/** Return a CMutableTransaction with contextual default values based on set of consensus rules at nHeight, and the default expiry delta. */
CMutableTransaction CreateNewContextualCMutableTransaction(const Consensus::Params& consensusParams, int nHeight);
/** Return a CMutableTransaction with contextual default values based on set of consensus rules at nHeight, and given expiry delta. */
CMutableTransaction CreateNewContextualCMutableTransaction(const Consensus::Params& consensusParams, int nHeight, int nExpiryDelta);
#endif // BITCOIN_MAIN_H

View File

@ -1302,7 +1302,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_taddr_to_sapling)
pwalletMain->AddToWallet(wtx, true, NULL);
// Context that z_sendmany requires
auto builder = TransactionBuilder(consensusParams, nextBlockHeight, pwalletMain);
auto builder = TransactionBuilder(consensusParams, nextBlockHeight, expiryDelta, pwalletMain);
mtx = CreateNewContextualCMutableTransaction(consensusParams, nextBlockHeight);
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1, 1 * COIN, "ABCD") };

View File

@ -50,6 +50,7 @@ std::string TransactionBuilderResult::GetError() {
TransactionBuilder::TransactionBuilder(
const Consensus::Params& consensusParams,
int nHeight,
int nExpiryDelta,
CKeyStore* keystore,
ZCJoinSplit* sproutParams,
CCoinsViewCache* coinsView,
@ -61,7 +62,7 @@ TransactionBuilder::TransactionBuilder(
coinsView(coinsView),
cs_coinsView(cs_coinsView)
{
mtx = CreateNewContextualCMutableTransaction(consensusParams, nHeight);
mtx = CreateNewContextualCMutableTransaction(consensusParams, nHeight, nExpiryDelta);
}
// This exception is thrown in certain scenarios when building JoinSplits fails.

View File

@ -95,6 +95,7 @@ public:
TransactionBuilder(
const Consensus::Params& consensusParams,
int nHeight,
int nExpiryDelta,
CKeyStore* keyStore = nullptr,
ZCJoinSplit* sproutParams = nullptr,
CCoinsViewCache* coinsView = nullptr,

View File

@ -232,7 +232,7 @@ CWalletTx GetValidSaplingReceive(const Consensus::Params& consensusParams,
auto fvk = sk.expsk.full_viewing_key();
auto pa = sk.DefaultAddress();
auto builder = TransactionBuilder(consensusParams, 1, &keyStore);
auto builder = TransactionBuilder(consensusParams, 1, expiryDelta, &keyStore);
builder.SetFee(0);
builder.AddTransparentInput(COutPoint(), scriptPubKey, value);
builder.AddSaplingOutput(fvk.ovk, pa, value, {});

View File

@ -13,6 +13,7 @@
#include "wallet.h"
const CAmount FEE = 10000;
const int MIGRATION_EXPIRY_DELTA = 450;
AsyncRPCOperation_saplingmigration::AsyncRPCOperation_saplingmigration(int targetHeight) : targetHeight_(targetHeight) {}
@ -98,7 +99,8 @@ bool AsyncRPCOperation_saplingmigration::main_impl() {
CCoinsViewCache coinsView(pcoinsTip);
do {
CAmount amountToSend = chooseAmount(availableFunds);
auto builder = TransactionBuilder(consensusParams, targetHeight_, pwalletMain, pzcashParams, &coinsView, &cs_main);
auto builder = TransactionBuilder(consensusParams, targetHeight_, MIGRATION_EXPIRY_DELTA, pwalletMain, pzcashParams,
&coinsView, &cs_main);
std::vector<CSproutNotePlaintextEntry> fromNotes;
CAmount fromNoteAmount = 0;
while (fromNoteAmount < amountToSend) {
@ -182,7 +184,7 @@ libzcash::SaplingPaymentAddress AsyncRPCOperation_saplingmigration::getMigration
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;

View File

@ -379,7 +379,7 @@ TEST(WalletTests, SetSaplingNoteAddrsInCWalletTx) {
ASSERT_TRUE(nf);
uint256 nullifier = nf.get();
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, expiryDelta);
builder.AddSaplingSpend(expsk, note, anchor, witness);
builder.AddSaplingOutput(fvk.ovk, pk, 50000, {});
builder.SetFee(0);
@ -506,7 +506,7 @@ TEST(WalletTests, FindMySaplingNotes) {
auto testNote = GetTestSaplingNote(pa, 50000);
// Generate transaction
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, expiryDelta);
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
builder.AddSaplingOutput(fvk.ovk, pa, 25000, {});
auto tx = builder.Build().GetTxOrThrow();
@ -638,7 +638,7 @@ TEST(WalletTests, GetConflictedSaplingNotes) {
auto witness = saplingTree.witness();
// Generate tx to create output note B
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, expiryDelta);
builder.AddSaplingSpend(expsk, note, anchor, witness);
builder.AddSaplingOutput(fvk.ovk, pk, 35000, {});
auto tx = builder.Build().GetTxOrThrow();
@ -692,13 +692,13 @@ TEST(WalletTests, GetConflictedSaplingNotes) {
anchor = saplingTree.root();
// Create transaction to spend note B
auto builder2 = TransactionBuilder(consensusParams, 2);
auto builder2 = TransactionBuilder(consensusParams, 2, expiryDelta);
builder2.AddSaplingSpend(expsk, note2, anchor, spend_note_witness);
builder2.AddSaplingOutput(fvk.ovk, pk, 20000, {});
auto tx2 = builder2.Build().GetTxOrThrow();
// Create conflicting transaction which also spends note B
auto builder3 = TransactionBuilder(consensusParams, 2);
auto builder3 = TransactionBuilder(consensusParams, 2, expiryDelta);
builder3.AddSaplingSpend(expsk, note2, anchor, spend_note_witness);
builder3.AddSaplingOutput(fvk.ovk, pk, 19999, {});
auto tx3 = builder3.Build().GetTxOrThrow();
@ -785,7 +785,7 @@ TEST(WalletTests, SaplingNullifierIsSpent) {
auto testNote = GetTestSaplingNote(pa, 50000);
// Generate transaction
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, expiryDelta);
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
builder.AddSaplingOutput(fvk.ovk, pa, 25000, {});
auto tx = builder.Build().GetTxOrThrow();
@ -868,7 +868,7 @@ TEST(WalletTests, NavigateFromSaplingNullifierToNote) {
auto testNote = GetTestSaplingNote(pa, 50000);
// Generate transaction
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, expiryDelta);
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
builder.AddSaplingOutput(fvk.ovk, pa, 25000, {});
auto tx = builder.Build().GetTxOrThrow();
@ -996,7 +996,7 @@ TEST(WalletTests, SpentSaplingNoteIsFromMe) {
auto witness = saplingTree.witness();
// Generate transaction, which sends funds to note B
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, expiryDelta);
builder.AddSaplingSpend(expsk, note, anchor, witness);
builder.AddSaplingOutput(fvk.ovk, pk, 25000, {});
auto tx = builder.Build().GetTxOrThrow();
@ -1066,7 +1066,7 @@ TEST(WalletTests, SpentSaplingNoteIsFromMe) {
anchor = saplingTree.root();
// Create transaction to spend note B
auto builder2 = TransactionBuilder(consensusParams, 2);
auto builder2 = TransactionBuilder(consensusParams, 2, expiryDelta);
builder2.AddSaplingSpend(expsk, note2, anchor, spend_note_witness);
builder2.AddSaplingOutput(fvk.ovk, pk, 12500, {});
auto tx2 = builder2.Build().GetTxOrThrow();
@ -1771,7 +1771,7 @@ TEST(WalletTests, UpdatedSaplingNoteData) {
auto testNote = GetTestSaplingNote(pa, 50000);
// Generate transaction
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, expiryDelta);
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
builder.AddSaplingOutput(fvk.ovk, pa2, 25000, {});
auto tx = builder.Build().GetTxOrThrow();
@ -1912,7 +1912,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
// Generate shielding tx from transparent to Sapling
// 0.0005 t-ZEC in, 0.0004 z-ZEC out, 0.0001 t-ZEC fee
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
auto builder = TransactionBuilder(consensusParams, 1, expiryDelta, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
builder.AddSaplingOutput(fvk.ovk, pk, 40000, {});
auto tx1 = builder.Build().GetTxOrThrow();
@ -1967,7 +1967,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
// Create a Sapling-only transaction
// 0.0004 z-ZEC in, 0.00025 z-ZEC out, 0.0001 t-ZEC fee, 0.00005 z-ZEC change
auto builder2 = TransactionBuilder(consensusParams, 2);
auto builder2 = TransactionBuilder(consensusParams, 2, expiryDelta);
builder2.AddSaplingSpend(expsk, note, anchor, witness);
builder2.AddSaplingOutput(fvk.ovk, pk, 25000, {});
auto tx2 = builder2.Build().GetTxOrThrow();

View File

@ -3888,7 +3888,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
// Builder (used if Sapling addresses are involved)
boost::optional<TransactionBuilder> builder;
if (noSproutAddrs) {
builder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, pwalletMain);
builder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, expiryDelta, pwalletMain);
}
// Contextual transaction we will build on
@ -3921,7 +3921,7 @@ UniValue z_setmigration(const UniValue& params, bool fHelp) {
"Sprout balance, this process may take several weeks. The migration works by sending, up to 5, as many\n"
"transactions as possible whenever the blockchain reaches a height equal to 499 modulo 500. The transaction\n"
"amounts are picked according to the random distribution specified in ZIP 308. The migration will end once\n"
"the wallets Sprout balance is below" + strprintf("%s %s", FormatMoney(CENT), CURRENCY_UNIT) + ".\n"
"the wallets Sprout balance is below " + strprintf("%s %s", FormatMoney(CENT), CURRENCY_UNIT) + ".\n"
"\nArguments:\n"
"1. enabled (boolean, required) 'true' or 'false' to enable or disable respectively.\n"
);
@ -4229,7 +4229,7 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp)
// Builder (used if Sapling addresses are involved)
TransactionBuilder builder = TransactionBuilder(
Params().GetConsensus(), nextBlockHeight, pwalletMain);
Params().GetConsensus(), nextBlockHeight, expiryDelta, pwalletMain);
// Contextual transaction we will build on
// (used if no Sapling addresses are involved)
@ -4646,7 +4646,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
// Builder (used if Sapling addresses are involved)
boost::optional<TransactionBuilder> builder;
if (isToSaplingZaddr || saplingNoteInputs.size() > 0) {
builder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, pwalletMain);
builder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, expiryDelta, pwalletMain);
}
// Create operation and add to global queue
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();