Eirik0
5 years ago
13 changed files with 421 additions and 5 deletions
@ -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() |
@ -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; |
|||
} |
@ -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); |
|||
}; |
Loading…
Reference in new issue