2 #include "boost/variant/static_visitor.hpp"
3 #include "asyncrpcoperation_saplingmigration.h"
6 #include "rpc/protocol.h"
9 #include "tinyformat.h"
10 #include "transaction_builder.h"
12 #include "utilmoneystr.h"
15 const CAmount FEE = 10000;
16 const int MIGRATION_EXPIRY_DELTA = 450;
18 AsyncRPCOperation_saplingmigration::AsyncRPCOperation_saplingmigration(int targetHeight) : targetHeight_(targetHeight) {}
20 AsyncRPCOperation_saplingmigration::~AsyncRPCOperation_saplingmigration() {}
22 void AsyncRPCOperation_saplingmigration::main() {
26 set_state(OperationStatus::EXECUTING);
27 start_execution_clock();
32 success = main_impl();
33 } catch (const UniValue& objError) {
34 int code = find_value(objError, "code").get_int();
35 std::string message = find_value(objError, "message").get_str();
37 set_error_message(message);
38 } catch (const runtime_error& e) {
40 set_error_message("runtime error: " + string(e.what()));
41 } catch (const logic_error& e) {
43 set_error_message("logic error: " + string(e.what()));
44 } catch (const exception& e) {
46 set_error_message("general exception: " + string(e.what()));
49 set_error_message("unknown error");
52 stop_execution_clock();
55 set_state(OperationStatus::SUCCESS);
57 set_state(OperationStatus::FAILED);
60 std::string s = strprintf("%s: Sprout->Sapling transactions created. (status=%s", getId(), getStateAsString());
62 s += strprintf(", success)\n");
64 s += strprintf(", error=%s)\n", getErrorMessage());
70 bool AsyncRPCOperation_saplingmigration::main_impl() {
71 LogPrint("zrpcunsafe", "%s: Beginning AsyncRPCOperation_saplingmigration.\n", getId());
72 auto consensusParams = Params().GetConsensus();
73 auto nextActivationHeight = NextActivationHeight(targetHeight_, consensusParams);
74 if (nextActivationHeight && targetHeight_ + MIGRATION_EXPIRY_DELTA >= nextActivationHeight.get()) {
75 LogPrint("zrpcunsafe", "%s: Migration txs would be created before a NU activation but may expire after. Skipping this round.\n", getId());
76 setMigrationResult(0, 0, std::vector<std::string>());
80 std::vector<SproutNoteEntry> sproutEntries;
81 std::vector<SaplingNoteEntry> saplingEntries;
83 LOCK2(cs_main, pwalletMain->cs_wallet);
84 // We set minDepth to 11 to avoid unconfirmed notes and in anticipation of specifying
85 // an anchor at height N-10 for each Sprout JoinSplit description
86 // Consider, should notes be sorted?
87 pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, "", 11);
89 CAmount availableFunds = 0;
90 for (const SproutNoteEntry& sproutEntry : sproutEntries) {
91 availableFunds += sproutEntry.note.value();
93 // If the remaining amount to be migrated is less than 0.01 ZEC, end the migration.
94 if (availableFunds < CENT) {
95 LogPrint("zrpcunsafe", "%s: Available Sprout balance (%s) less than required minimum (%s). Stopping.\n",
96 getId(), FormatMoney(availableFunds), FormatMoney(CENT));
97 setMigrationResult(0, 0, std::vector<std::string>());
101 HDSeed seed = pwalletMain->GetHDSeedForRPC();
102 libzcash::SaplingPaymentAddress migrationDestAddress = getMigrationDestAddress(seed);
105 // Up to the limit of 5, as many transactions are sent as are needed to migrate the remaining funds
106 int numTxCreated = 0;
107 CAmount amountMigrated = 0;
108 std::vector<std::string> migrationTxIds;
110 CCoinsViewCache coinsView(pcoinsTip);
112 CAmount amountToSend = chooseAmount(availableFunds);
113 auto builder = TransactionBuilder(consensusParams, targetHeight_, pwalletMain, pzcashParams, &coinsView, &cs_main);
114 builder.SetExpiryHeight(targetHeight_ + MIGRATION_EXPIRY_DELTA);
115 LogPrint("zrpcunsafe", "%s: Beginning creating transaction with Sapling output amount=%s\n", getId(), FormatMoney(amountToSend - FEE));
116 std::vector<SproutNoteEntry> fromNotes;
117 CAmount fromNoteAmount = 0;
118 while (fromNoteAmount < amountToSend) {
119 auto sproutEntry = sproutEntries[noteIndex++];
120 fromNotes.push_back(sproutEntry);
121 fromNoteAmount += sproutEntry.note.value();
123 availableFunds -= fromNoteAmount;
124 for (const SproutNoteEntry& sproutEntry : fromNotes) {
125 std::string data(sproutEntry.memo.begin(), sproutEntry.memo.end());
126 LogPrint("zrpcunsafe", "%s: Adding Sprout note input (txid=%s, vJoinSplit=%d, jsoutindex=%d, amount=%s, memo=%s)\n",
128 sproutEntry.jsop.hash.ToString().substr(0, 10),
130 int(sproutEntry.jsop.n), // uint8_t
131 FormatMoney(sproutEntry.note.value()),
132 HexStr(data).substr(0, 10)
134 libzcash::SproutSpendingKey sproutSk;
135 pwalletMain->GetSproutSpendingKey(sproutEntry.address, sproutSk);
136 std::vector<JSOutPoint> vOutPoints = {sproutEntry.jsop};
137 // Each migration transaction SHOULD specify an anchor at height N-10
138 // for each Sprout JoinSplit description
139 // TODO: the above functionality (in comment) is not implemented in zcashd
141 std::vector<boost::optional<SproutWitness>> vInputWitnesses;
142 pwalletMain->GetSproutNoteWitnesses(vOutPoints, vInputWitnesses, inputAnchor);
143 builder.AddSproutInput(sproutSk, sproutEntry.note, vInputWitnesses[0].get());
145 // The amount chosen *includes* the 0.0001 ZEC fee for this transaction, i.e.
146 // the value of the Sapling output will be 0.0001 ZEC less.
148 builder.AddSaplingOutput(ovkForShieldingFromTaddr(seed), migrationDestAddress, amountToSend - FEE);
149 CTransaction tx = builder.Build().GetTxOrThrow();
151 LogPrint("zrpcunsafe", "%s: Canceled. Stopping.\n", getId());
154 pwalletMain->AddPendingSaplingMigrationTx(tx);
155 LogPrint("zrpcunsafe", "%s: Added pending migration transaction with txid=%s\n", getId(), tx.GetHash().ToString());
157 amountMigrated += amountToSend - FEE;
158 migrationTxIds.push_back(tx.GetHash().ToString());
159 } while (numTxCreated < 5 && availableFunds > CENT);
161 LogPrint("zrpcunsafe", "%s: Created %d transactions with total Sapling output amount=%s\n", getId(), numTxCreated, FormatMoney(amountMigrated));
162 setMigrationResult(numTxCreated, amountMigrated, migrationTxIds);
166 void AsyncRPCOperation_saplingmigration::setMigrationResult(int numTxCreated, const CAmount& amountMigrated, const std::vector<std::string>& migrationTxIds) {
167 UniValue res(UniValue::VOBJ);
168 res.push_back(Pair("num_tx_created", numTxCreated));
169 res.push_back(Pair("amount_migrated", FormatMoney(amountMigrated)));
170 UniValue txIds(UniValue::VARR);
171 for (const std::string& txId : migrationTxIds) {
172 txIds.push_back(txId);
174 res.push_back(Pair("migration_txids", txIds));
178 CAmount AsyncRPCOperation_saplingmigration::chooseAmount(const CAmount& availableFunds) {
181 // 1. Choose an integer exponent uniformly in the range 6 to 8 inclusive.
182 int exponent = GetRand(3) + 6;
183 // 2. Choose an integer mantissa uniformly in the range 1 to 99 inclusive.
184 uint64_t mantissa = GetRand(99) + 1;
185 // 3. Calculate amount := (mantissa * 10^exponent) zatoshi.
186 int pow = std::pow(10, exponent);
187 amount = mantissa * pow;
188 // 4. If amount is greater than the amount remaining to send, repeat from step 1.
189 } while (amount > availableFunds);
193 // Unless otherwise specified, the migration destination address is the address for Sapling account 0
194 libzcash::SaplingPaymentAddress AsyncRPCOperation_saplingmigration::getMigrationDestAddress(const HDSeed& seed) {
195 if (mapArgs.count("-migrationdestaddress")) {
196 std::string migrationDestAddress = mapArgs["-migrationdestaddress"];
197 auto address = DecodePaymentAddress(migrationDestAddress);
198 auto saplingAddress = boost::get<libzcash::SaplingPaymentAddress>(&address);
199 assert(saplingAddress != nullptr); // This is checked in init.cpp
200 return *saplingAddress;
202 // Derive the address for Sapling account 0
203 auto m = libzcash::SaplingExtendedSpendingKey::Master(seed);
204 uint32_t bip44CoinType = Params().BIP44CoinType();
206 // We use a fixed keypath scheme of m/32'/coin_type'/account'
208 auto m_32h = m.Derive(32 | ZIP32_HARDENED_KEY_LIMIT);
209 // Derive m/32'/coin_type'
210 auto m_32h_cth = m_32h.Derive(bip44CoinType | ZIP32_HARDENED_KEY_LIMIT);
212 // Derive m/32'/coin_type'/0'
213 libzcash::SaplingExtendedSpendingKey xsk = m_32h_cth.Derive(0 | ZIP32_HARDENED_KEY_LIMIT);
215 libzcash::SaplingPaymentAddress toAddress = xsk.DefaultAddress();
217 // Refactor: this is similar logic as in the visitor HaveSpendingKeyForPaymentAddress and is used elsewhere
218 libzcash::SaplingIncomingViewingKey ivk;
219 libzcash::SaplingFullViewingKey fvk;
220 if (!(pwalletMain->GetSaplingIncomingViewingKey(toAddress, ivk) &&
221 pwalletMain->GetSaplingFullViewingKey(ivk, fvk) &&
222 pwalletMain->HaveSaplingSpendingKey(fvk))) {
223 // Sapling account 0 must be the first address returned by GenerateNewSaplingZKey
224 assert(pwalletMain->GenerateNewSaplingZKey() == toAddress);
230 void AsyncRPCOperation_saplingmigration::cancel() {
231 set_state(OperationStatus::CANCELLED);
234 UniValue AsyncRPCOperation_saplingmigration::getStatus() const {
235 UniValue v = AsyncRPCOperation::getStatus();
236 UniValue obj = v.get_obj();
237 obj.push_back(Pair("method", "saplingmigration"));
238 obj.push_back(Pair("target_height", targetHeight_));