]> Git Repo - VerusCoin.git/blob - src/wallet/asyncrpcoperation_saplingmigration.cpp
Merge pull request #97 from miketout/dev
[VerusCoin.git] / src / wallet / asyncrpcoperation_saplingmigration.cpp
1 #include "assert.h"
2 #include "boost/variant/static_visitor.hpp"
3 #include "asyncrpcoperation_saplingmigration.h"
4 #include "init.h"
5 #include "key_io.h"
6 #include "rpc/protocol.h"
7 #include "random.h"
8 #include "sync.h"
9 #include "tinyformat.h"
10 #include "transaction_builder.h"
11 #include "util.h"
12 #include "utilmoneystr.h"
13 #include "wallet.h"
14
15 const CAmount FEE = 10000;
16 const int MIGRATION_EXPIRY_DELTA = 450;
17
18 AsyncRPCOperation_saplingmigration::AsyncRPCOperation_saplingmigration(int targetHeight) : targetHeight_(targetHeight) {}
19
20 AsyncRPCOperation_saplingmigration::~AsyncRPCOperation_saplingmigration() {}
21
22 void AsyncRPCOperation_saplingmigration::main() {
23     if (isCancelled())
24         return;
25
26     set_state(OperationStatus::EXECUTING);
27     start_execution_clock();
28
29     bool success = false;
30
31     try {
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();
36         set_error_code(code);
37         set_error_message(message);
38     } catch (const runtime_error& e) {
39         set_error_code(-1);
40         set_error_message("runtime error: " + string(e.what()));
41     } catch (const logic_error& e) {
42         set_error_code(-1);
43         set_error_message("logic error: " + string(e.what()));
44     } catch (const exception& e) {
45         set_error_code(-1);
46         set_error_message("general exception: " + string(e.what()));
47     } catch (...) {
48         set_error_code(-2);
49         set_error_message("unknown error");
50     }
51
52     stop_execution_clock();
53
54     if (success) {
55         set_state(OperationStatus::SUCCESS);
56     } else {
57         set_state(OperationStatus::FAILED);
58     }
59
60     std::string s = strprintf("%s: Sprout->Sapling transactions created. (status=%s", getId(), getStateAsString());
61     if (success) {
62         s += strprintf(", success)\n");
63     } else {
64         s += strprintf(", error=%s)\n", getErrorMessage());
65     }
66
67     LogPrintf("%s", s);
68 }
69
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>());
77         return true;
78     }
79
80     std::vector<SproutNoteEntry> sproutEntries;
81     std::vector<SaplingNoteEntry> saplingEntries;
82     {
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);
88     }
89     CAmount availableFunds = 0;
90     for (const SproutNoteEntry& sproutEntry : sproutEntries) {
91         availableFunds += sproutEntry.note.value();
92     }
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>());
98         return true;
99     }
100
101     HDSeed seed = pwalletMain->GetHDSeedForRPC();
102     libzcash::SaplingPaymentAddress migrationDestAddress = getMigrationDestAddress(seed);
103
104
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;
109     int noteIndex = 0;
110     CCoinsViewCache coinsView(pcoinsTip);
111     do {
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();
122         }
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",
127                 getId(),
128                 sproutEntry.jsop.hash.ToString().substr(0, 10),
129                 sproutEntry.jsop.js,
130                 int(sproutEntry.jsop.n),  // uint8_t
131                 FormatMoney(sproutEntry.note.value()),
132                 HexStr(data).substr(0, 10)
133                 );
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
140             uint256 inputAnchor;
141             std::vector<boost::optional<SproutWitness>> vInputWitnesses;
142             pwalletMain->GetSproutNoteWitnesses(vOutPoints, vInputWitnesses, inputAnchor);
143             builder.AddSproutInput(sproutSk, sproutEntry.note, vInputWitnesses[0].get());
144         }
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.
147         builder.SetFee(FEE);
148         builder.AddSaplingOutput(ovkForShieldingFromTaddr(seed), migrationDestAddress, amountToSend - FEE);
149         CTransaction tx = builder.Build().GetTxOrThrow();
150         if (isCancelled()) {
151             LogPrint("zrpcunsafe", "%s: Canceled. Stopping.\n", getId());
152             break;
153         }
154         pwalletMain->AddPendingSaplingMigrationTx(tx);
155         LogPrint("zrpcunsafe", "%s: Added pending migration transaction with txid=%s\n", getId(), tx.GetHash().ToString());
156         ++numTxCreated;
157         amountMigrated += amountToSend - FEE;
158         migrationTxIds.push_back(tx.GetHash().ToString());
159     } while (numTxCreated < 5 && availableFunds > CENT);
160
161     LogPrint("zrpcunsafe", "%s: Created %d transactions with total Sapling output amount=%s\n", getId(), numTxCreated, FormatMoney(amountMigrated));
162     setMigrationResult(numTxCreated, amountMigrated, migrationTxIds);
163     return true;
164 }
165
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);
173     }
174     res.push_back(Pair("migration_txids", txIds));
175     set_result(res);
176 }
177
178 CAmount AsyncRPCOperation_saplingmigration::chooseAmount(const CAmount& availableFunds) {
179     CAmount amount = 0;
180     do {
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);
190     return amount;
191 }
192
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;
201     }
202     // Derive the address for Sapling account 0
203     auto m = libzcash::SaplingExtendedSpendingKey::Master(seed);
204     uint32_t bip44CoinType = Params().BIP44CoinType();
205
206     // We use a fixed keypath scheme of m/32'/coin_type'/account'
207     // Derive m/32'
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);
211
212     // Derive m/32'/coin_type'/0'
213     libzcash::SaplingExtendedSpendingKey xsk = m_32h_cth.Derive(0 | ZIP32_HARDENED_KEY_LIMIT);
214
215     libzcash::SaplingPaymentAddress toAddress = xsk.DefaultAddress();
216
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);
225     }
226
227     return toAddress;
228 }
229
230 void AsyncRPCOperation_saplingmigration::cancel() {
231     set_state(OperationStatus::CANCELLED);
232 }
233
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_));
239     return obj;
240 }
This page took 0.038066 seconds and 4 git commands to generate.