diff options
| -rw-r--r-- | src/walletcmd.cpp | 100 |
1 files changed, 97 insertions, 3 deletions
diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp index fae2095..6d5f32b 100644 --- a/src/walletcmd.cpp +++ b/src/walletcmd.cpp | |||
| @@ -30,6 +30,12 @@ static const int RESTORE_MAX = 10000; | |||
| 30 | // can stop just before that change output. | 30 | // can stop just before that change output. |
| 31 | static const int RESTORE_MIN_SCAN = KEYPOOL_SIZE + RESTORE_BATCH; | 31 | static const int RESTORE_MIN_SCAN = KEYPOOL_SIZE + RESTORE_BATCH; |
| 32 | 32 | ||
| 33 | // Quiet batches required before giving up. Three, because the wallet's own | ||
| 34 | // bookkeeping can leave a gap of two hundred used-nothing indices between one | ||
| 35 | // used address and the next -- see the note at the stop condition. One was not | ||
| 36 | // enough and cost a real balance in testing. | ||
| 37 | static const int RESTORE_QUIET_BATCHES = 3; | ||
| 38 | |||
| 33 | int CmdNewPhrase() | 39 | int CmdNewPhrase() |
| 34 | { | 40 | { |
| 35 | AttachTerminal(); | 41 | AttachTerminal(); |
| @@ -110,6 +116,7 @@ bool RestoreFromPhrase(const std::string& strMnemonic, | |||
| 110 | // because the scan asks the wallet what belongs to it. | 116 | // because the scan asks the wallet what belongs to it. |
| 111 | int nTotalDerived = (int)nHDNext; | 117 | int nTotalDerived = (int)nHDNext; |
| 112 | int nStopDepth = max(nMinDepth, RESTORE_MIN_SCAN); | 118 | int nStopDepth = max(nMinDepth, RESTORE_MIN_SCAN); |
| 119 | int nQuietBatches = 0; | ||
| 113 | while (nTotalDerived < RESTORE_MAX) | 120 | while (nTotalDerived < RESTORE_MAX) |
| 114 | { | 121 | { |
| 115 | // Count wallet transactions, not scan hits. | 122 | // Count wallet transactions, not scan hits. |
| @@ -149,9 +156,22 @@ bool RestoreFromPhrase(const std::string& strMnemonic, | |||
| 149 | fnProgress(pArg, nTotalDerived, (int)nRecoveredNow); | 156 | fnProgress(pArg, nTotalDerived, (int)nRecoveredNow); |
| 150 | } | 157 | } |
| 151 | 158 | ||
| 152 | // A whole batch with nothing in it means far enough -- unless the | 159 | // Stopping needs more than one quiet batch, because this wallet digs |
| 153 | // caller asked to look deeper anyway. | 160 | // gaps in its own derivation. A restore leaves nHDNext at the depth it |
| 154 | if (nWalletAfter == nWalletBefore && nTotalDerived >= nStopDepth) | 161 | // scanned, the key pool then derives KEYPOOL_SIZE more, and change |
| 162 | // takes the index after that -- so the address used *after* a restore | ||
| 163 | // can sit two hundred indices past the last one used before it, with | ||
| 164 | // nothing in between. | ||
| 165 | // | ||
| 166 | // Measured, on a real wallet with real coin: restore, spend once, and | ||
| 167 | // the coins land at indices 201 and 302. Restoring again with the old | ||
| 168 | // one-batch rule stopped at 201 and reported a single transaction. The | ||
| 169 | // money was derived, covered by the phrase, and invisible. | ||
| 170 | if (nWalletAfter == nWalletBefore) | ||
| 171 | nQuietBatches++; | ||
| 172 | else | ||
| 173 | nQuietBatches = 0; | ||
| 174 | if (nQuietBatches >= RESTORE_QUIET_BATCHES && nTotalDerived >= nStopDepth) | ||
| 155 | break; | 175 | break; |
| 156 | } | 176 | } |
| 157 | 177 | ||
| @@ -251,3 +271,77 @@ int CmdShowDerived(int nCount) | |||
| 251 | fflush(stdout); | 271 | fflush(stdout); |
| 252 | return 0; | 272 | return 0; |
| 253 | } | 273 | } |
| 274 | |||
| 275 | // Spend, from the command line. | ||
| 276 | // | ||
| 277 | // SendMoney() has been in this tree since 0.1.0 and only the window ever called | ||
| 278 | // it, so a headless node could be paid and could never pay: it held a balance | ||
| 279 | // with no way to move it. Found while trying to prove that change from a wallet | ||
| 280 | // with a recovery phrase lands on a key the phrase can reproduce -- a test that | ||
| 281 | // needs a spend, and there was no way to make one without a screen. | ||
| 282 | // | ||
| 283 | // Deliberately strict, because this moves money and there is nobody to click | ||
| 284 | // "are you sure": the address must decode, the amount must parse and be | ||
| 285 | // positive, and anything else refuses before touching the wallet. | ||
| 286 | int CmdSendTo(const std::string& strArg) | ||
| 287 | { | ||
| 288 | AttachTerminal(); | ||
| 289 | |||
| 290 | std::string::size_type comma = strArg.rfind(','); | ||
| 291 | if (comma == std::string::npos) | ||
| 292 | { | ||
| 293 | fprintf(stderr, "Usage: -sendto=ADDRESS,AMOUNT (for example -sendto=B7kQ...,1.5)\n"); | ||
| 294 | return 1; | ||
| 295 | } | ||
| 296 | std::string strAddr = strArg.substr(0, comma); | ||
| 297 | std::string strAmount = strArg.substr(comma + 1); | ||
| 298 | |||
| 299 | // Trim, so a quoted argument with stray spaces does not silently become an | ||
| 300 | // invalid address and send nothing. | ||
| 301 | while (!strAddr.empty() && isspace((unsigned char)strAddr[0])) strAddr.erase(0, 1); | ||
| 302 | while (!strAddr.empty() && isspace((unsigned char)strAddr[strAddr.size()-1])) strAddr.erase(strAddr.size()-1); | ||
| 303 | while (!strAmount.empty() && isspace((unsigned char)strAmount[0])) strAmount.erase(0, 1); | ||
| 304 | while (!strAmount.empty() && isspace((unsigned char)strAmount[strAmount.size()-1])) strAmount.erase(strAmount.size()-1); | ||
| 305 | |||
| 306 | uint160 hash160; | ||
| 307 | if (!AddressToHash160(strAddr, hash160)) | ||
| 308 | { | ||
| 309 | fprintf(stderr, "Not a valid address: %s\n", strAddr.c_str()); | ||
| 310 | return 1; | ||
| 311 | } | ||
| 312 | |||
| 313 | int64 nValue = 0; | ||
| 314 | if (!ParseMoney(strAmount.c_str(), nValue) || nValue <= 0) | ||
| 315 | { | ||
| 316 | fprintf(stderr, "Not a valid amount: %s\n", strAmount.c_str()); | ||
| 317 | return 1; | ||
| 318 | } | ||
| 319 | |||
| 320 | std::string strWhy; | ||
| 321 | if (!CanScanWalletTransactions(strWhy)) | ||
| 322 | { | ||
| 323 | // Spending needs the chain: without it the wallet cannot tell which of | ||
| 324 | // its outputs are still unspent, and a transaction built on that guess | ||
| 325 | // is one the network will reject. | ||
| 326 | fprintf(stderr, "The block chain is not loaded, so this wallet cannot tell " | ||
| 327 | "which coins it still has. Start the node and let it sync first.\n"); | ||
| 328 | return 1; | ||
| 329 | } | ||
| 330 | |||
| 331 | CScript scriptPubKey; | ||
| 332 | scriptPubKey << OP_DUP << OP_HASH160 << hash160 << OP_EQUALVERIFY << OP_CHECKSIG; | ||
| 333 | |||
| 334 | CWalletTx wtx; | ||
| 335 | if (!SendMoney(scriptPubKey, nValue, wtx)) | ||
| 336 | { | ||
| 337 | fprintf(stderr, "The transaction was not created. Usually that means the " | ||
| 338 | "balance is too low once the fee is counted.\n"); | ||
| 339 | return 1; | ||
| 340 | } | ||
| 341 | |||
| 342 | printf("Sent %s to %s\n", FormatMoney(nValue).c_str(), strAddr.c_str()); | ||
| 343 | printf(" transaction %s\n", wtx.GetHash().ToString().c_str()); | ||
| 344 | printf(" it needs to be relayed and mined before the other side sees it.\n"); | ||
| 345 | fflush(stdout); | ||
| 346 | return 0; | ||
| 347 | } | ||