void CAccountViewTest::Init() { vRandomKeyID.reserve(VECTOR_SIZE); vRandomRegID.reserve(VECTOR_SIZE); vAccount.reserve(VECTOR_SIZE); for (int i = 0; i < VECTOR_SIZE; i++) { CKey key; key.MakeNewKey(false); CPubKey pubkey = key.GetPubKey(); CKeyID keyID = pubkey.GetID(); vRandomKeyID.push_back(keyID); } for (int j = 0; j < VECTOR_SIZE; j++) { CRegID accountId(10000 + j, j); vRandomRegID.push_back(accountId); } for (int k = 0; k < VECTOR_SIZE; k++) { CSecureAccount account; account.llValues = k + 1; account.keyID = vRandomKeyID.at(k); vAccount.push_back(account); } }
BOOST_FIXTURE_TEST_CASE(rpc_getnewaddress_test, RPCTestWalletFixture) { BOOST_CHECK(pwalletMain != NULL); LOCK2(cs_main, pwalletMain->cs_wallet); CKey key; CPubKey pubkey; string strRPC = ""; Value r; // show import key tests key.MakeNewKey(true); pubkey = key.GetPubKey(); // import private key strRPC = "importprivkey " + CBitcoinSecret(key).ToString(); CallRPC(strRPC); // address not found before import strRPC = "getnewaddress"; BOOST_CHECK_NO_THROW(r = CallRPC(strRPC)); string addr = r.get_str(); BOOST_CHECK_EQUAL(addr, CBitcoinAddress(pubkey.GetID()).ToString()); // address found after import BOOST_CHECK_THROW(CallRPC(strRPC), runtime_error); }
// Explicit calculation which is used to test the wallet constant // We get the same virtual size due to rounding(weight/4) for both use_max_sig values static size_t CalculateNestedKeyhashInputSize(bool use_max_sig) { // Generate ephemeral valid pubkey CKey key; key.MakeNewKey(true); CPubKey pubkey = key.GetPubKey(); // Generate pubkey hash uint160 key_hash(Hash160(pubkey.begin(), pubkey.end())); // Create inner-script to enter into keystore. Key hash can't be 0... CScript inner_script = CScript() << OP_0 << std::vector<unsigned char>(key_hash.begin(), key_hash.end()); // Create outer P2SH script for the output uint160 script_id(Hash160(inner_script.begin(), inner_script.end())); CScript script_pubkey = CScript() << OP_HASH160 << std::vector<unsigned char>(script_id.begin(), script_id.end()) << OP_EQUAL; // Add inner-script to key store and key to watchonly CBasicKeyStore keystore; keystore.AddCScript(inner_script); keystore.AddKeyPubKey(key, pubkey); // Fill in dummy signatures for fee calculation. SignatureData sig_data; if (!ProduceSignature(keystore, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, script_pubkey, sig_data)) { // We're hand-feeding it correct arguments; shouldn't happen assert(false); } CTxIn tx_in; UpdateInput(tx_in, sig_data); return (size_t)GetVirtualTransactionInputSize(tx_in); }
std::vector<unsigned char> CKeyStore::GenerateNewKey() { RandAddSeedPerfmon(); CKey key; key.MakeNewKey(); if (!AddKey(key)) throw std::runtime_error("CKeyStore::GenerateNewKey() : AddKey failed"); return key.GetPubKey(); }
BOOST_FIXTURE_TEST_CASE(rpc_importprivkey_test, RPCTestWalletFixture) { BOOST_CHECK(pwalletMain != NULL); LOCK2(cs_main, pwalletMain->cs_wallet); CKey key; CPubKey pubkey; string strRPC; key.MakeNewKey(true); pubkey = key.GetPubKey(); strRPC= "importprivkey " + CBitcoinSecret(key).ToString(); BOOST_CHECK_NO_THROW(CallRPC(strRPC)); key.MakeNewKey(true); pubkey = key.GetPubKey(); strRPC = "importprivkey " + CBitcoinSecret(key).ToString() + " import"; BOOST_CHECK_NO_THROW(CallRPC(strRPC)); }
BOOST_FIXTURE_TEST_CASE(rpc_listwalletaddress_test, RPCTestWalletFixture) { BOOST_CHECK(pwalletMain != NULL); LOCK2(cs_main, pwalletMain->cs_wallet); CKey key; CPubKey pubkey; string strRPC = ""; string result; BOOST_CHECK_NO_THROW(CallRPC("listwalletaddress")); BOOST_CHECK_NO_THROW(CallRPC("listwalletaddress -a")); BOOST_CHECK_NO_THROW(CallRPC("listwalletaddress -i")); BOOST_CHECK_NO_THROW(CallRPC("listwalletaddress -p")); // show imported key tests key.MakeNewKey(true); pubkey = key.GetPubKey(); // address not found before import result = write_string(CallRPC("listwalletaddress"), true); BOOST_CHECK(result.find(CBitcoinAddress(pubkey.GetID()).ToString()) == string::npos); // import private key strRPC = "importprivkey " + CBitcoinSecret(key).ToString(); CallRPC(strRPC); // address found after import result = write_string(CallRPC("listwalletaddress -i"), true); BOOST_CHECK(result.find(CBitcoinAddress(pubkey.GetID()).ToString()) != string::npos); // show address of specific labels test key.MakeNewKey(true); pubkey = key.GetPubKey(); strRPC = "importprivkey " + CBitcoinSecret(key).ToString() + " import"; CallRPC(strRPC); result = write_string(CallRPC("listwalletaddress import"), true); BOOST_CHECK(result.find(CBitcoinAddress(pubkey.GetID()).ToString()) != string::npos); result = write_string(CallRPC("listwalletaddress keypool"), true); BOOST_CHECK(result.find(CBitcoinAddress(pubkey.GetID()).ToString()) == string::npos); }
BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) { auto chain = interfaces::MakeChain(); // Cap last block file size, and mine new block in a new block file. CBlockIndex* oldTip = chainActive.Tip(); GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CBlockIndex* newTip = chainActive.Tip(); auto locked_chain = chain->lock(); // Prune the older block file. PruneOneBlockFile(oldTip->GetBlockPos().nFile); UnlinkPrunedFiles({oldTip->GetBlockPos().nFile}); // Verify importmulti RPC returns failure for a key whose creation time is // before the missing block, and success for a key whose creation time is // after. { std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateDummy()); AddWallet(wallet); UniValue keys; keys.setArray(); UniValue key; key.setObject(); key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(coinbaseKey.GetPubKey()))); key.pushKV("timestamp", 0); key.pushKV("internal", UniValue(true)); keys.push_back(key); key.clear(); key.setObject(); CKey futureKey; futureKey.MakeNewKey(true); key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(futureKey.GetPubKey()))); key.pushKV("timestamp", newTip->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1); key.pushKV("internal", UniValue(true)); keys.push_back(key); JSONRPCRequest request; request.params.setArray(); request.params.push_back(keys); UniValue response = importmulti(request); BOOST_CHECK_EQUAL(response.write(), strprintf("[{\"success\":false,\"error\":{\"code\":-1,\"message\":\"Rescan failed for key with creation " "timestamp %d. There was an error reading a block from time %d, which is after or within %d " "seconds of key creation, and could contain transactions pertaining to the key. As a result, " "transactions and coins using this key may not appear in the wallet. This error could be caused " "by pruning or data corruption (see bitcoind log for details) and could be dealt with by " "downloading and rescanning the relevant blocks (see -reindex and -rescan " "options).\"}},{\"success\":true}]", 0, oldTip->GetBlockTimeMax(), TIMESTAMP_WINDOW)); RemoveWallet(wallet); } }
CzPIVWallet::CzPIVWallet(std::string strWalletFile) { this->strWalletFile = strWalletFile; CWalletDB walletdb(strWalletFile); uint256 hashSeed; bool fFirstRun = !walletdb.ReadCurrentSeedHash(hashSeed); //Check for old db version of storing zpiv seed if (fFirstRun) { uint256 seed; if (walletdb.ReadZPIVSeed_deprecated(seed)) { //Update to new format, erase old seedMaster = seed; hashSeed = Hash(seed.begin(), seed.end()); if (pwalletMain->AddDeterministicSeed(seed)) { if (walletdb.EraseZPIVSeed_deprecated()) { LogPrintf("%s: Updated zPIV seed databasing\n", __func__); fFirstRun = false; } else { LogPrintf("%s: failed to remove old zpiv seed\n", __func__); } } } } //Don't try to do anything if the wallet is locked. if (pwalletMain->IsLocked()) { seedMaster = 0; nCountLastUsed = 0; this->mintPool = CMintPool(); return; } //First time running, generate master seed uint256 seed; if (fFirstRun) { // Borrow random generator from the key class so that we don't have to worry about randomness CKey key; key.MakeNewKey(true); seed = key.GetPrivKey_256(); seedMaster = seed; LogPrintf("%s: first run of zpiv wallet detected, new seed generated. Seedhash=%s\n", __func__, Hash(seed.begin(), seed.end()).GetHex()); } else if (!pwalletMain->GetDeterministicSeed(hashSeed, seed)) { LogPrintf("%s: failed to get deterministic seed for hashseed %s\n", __func__, hashSeed.GetHex()); return; } if (!SetMasterSeed(seed)) { LogPrintf("%s: failed to save deterministic seed for hashseed %s\n", __func__, hashSeed.GetHex()); return; } this->mintPool = CMintPool(nCountLastUsed); }
Value getnewaddress(const Array& params, bool fHelp) { if (fHelp || params.size() > 1) throw runtime_error( "getnewaddress (\"IsMiner\")\n" "\nget a new address\n" "\nArguments:\n" "1. \"IsMiner\" (bool, optional) private key Is used for miner if true will create tow key ,another for miner.\n" "\nExamples:\n" + HelpExampleCli("getnewaddress", "") + HelpExampleCli("getnewaddress", "true") ); EnsureWalletIsUnlocked(); CKey mCkey; mCkey.MakeNewKey(); CKey Minter; bool IsForMiner = false; if (params.size() == 1) { RPCTypeCheck(params, list_of(bool_type)); Minter.MakeNewKey(); IsForMiner = params[0].get_bool(); } CPubKey newKey = mCkey.GetPubKey(); CKeyID keyID = newKey.GetKeyID(); if (IsForMiner) { if (!pwalletMain->AddKey(mCkey, Minter)) throw runtime_error("add key failed "); } else if (!pwalletMain->AddKey(mCkey)) { throw runtime_error("add key failed "); } Object obj; obj.push_back(Pair("addr", keyID.ToAddress())); obj.push_back(Pair("minerpubkey", IsForMiner?Minter.GetPubKey().ToString(): "no" )); return obj; }
std::vector<unsigned char> CWallet::GenerateNewKey() { bool fCompressed = true; RandAddSeedPerfmon(); CKey key; key.MakeNewKey(fCompressed); if (!AddKey(key)) throw std::runtime_error("CWallet::GenerateNewKey() : AddKey failed"); return key.GetPubKey(); }
CPubKey CAccount::GenerateNewKey(CWallet& wallet, int keyChain) { CKey secret; secret.MakeNewKey(true); CPubKey pubkey = secret.GetPubKey(); assert(secret.VerifyPubKey(pubkey)); if (!wallet.AddKeyPubKey(secret, pubkey, *this, keyChain)) throw std::runtime_error("CAccount::GenerateNewKey(): AddKeyPubKey failed"); return pubkey; }
CPubKey CAccount::GenerateNewKey(CWallet& wallet, CKeyMetadata& metadata, int keyChain) { if (IsFixedKeyPool()) throw std::runtime_error(strprintf("GenerateNewKey called on a \"%sy\" witness account - this is invalid", GetAccountTypeString(m_Type).c_str())); CKey secret; secret.MakeNewKey(true); CPubKey pubkey = secret.GetPubKey(); assert(secret.VerifyPubKey(pubkey)); if (!wallet.AddKeyPubKey(secret, pubkey, *this, keyChain)) throw std::runtime_error("CAccount::GenerateNewKey(): AddKeyPubKey failed"); return pubkey; }
std::vector<unsigned char> CWallet::GenerateNewKey() { bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets RandAddSeedPerfmon(); CKey key; key.MakeNewKey(fCompressed); // Compressed public keys were introduced in version 0.6.0 if (fCompressed) SetMinVersion(FEATURE_COMPRPUBKEY); if (!AddKey(key)) throw std::runtime_error("CWallet::GenerateNewKey() : AddKey failed"); return key.GetPubKey(); }
bool GenerateBeaconKeys(const std::string &cpid, std::string &sOutPubKey, std::string &sOutPrivKey) { // First Check the Index - if it already exists, use it sOutPrivKey = GetArgument("privatekey" + cpid + GetNetSuffix(), ""); sOutPubKey = GetArgument("publickey" + cpid + GetNetSuffix(), ""); // If current keypair is not empty, but is invalid, allow the new keys to be stored, otherwise return 1: (10-25-2016) if (!sOutPrivKey.empty() && !sOutPubKey.empty()) { uint256 hashBlock = GetRandHash(); std::string sSignature; std::string sError; bool fResult; fResult = SignBlockWithCPID(cpid, hashBlock.GetHex(), sSignature, sError, true); if (!fResult) LogPrintf("GenerateBeaconKeys::Failed to sign block with cpid with existing keys; generating new key pair -> %s", sError); else { fResult = VerifyCPIDSignature(cpid, hashBlock.GetHex(), sSignature); if (fResult) { LogPrintf("GenerateBeaconKeys::Current keypair is valid."); return true; } else LogPrintf("GenerateBeaconKeys::Signing block with CPID was successful; However Verifying CPID Sign was not; Key pair is not valid, generating new key pair"); } } // Generate the Keypair CKey key; key.MakeNewKey(false); CPrivKey vchPrivKey = key.GetPrivKey(); sOutPrivKey = HexStr<CPrivKey::iterator>(vchPrivKey.begin(), vchPrivKey.end()); sOutPubKey = HexStr(key.GetPubKey().Raw()); return true; }
bool TestChainForComputingMediansSetup::GenerateRandomTransaction(CTransaction& txNew) { CAmount amountToSend = 5000; std::vector<CTransaction> res; CKey key; key.MakeNewKey(true); CScript scriptPubKey = CScript() << ToByteVector(key.GetPubKey()) << OP_CHECKSIG; CBasicKeyStore keystore; keystore.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); CTransaction utxo = coinbaseTxns[0]; coinbaseTxns.erase(coinbaseTxns.begin()); txNew.nLockTime = chainActive.Height(); txNew.vin.clear(); txNew.vout.clear(); for (int j = 0; j < nOutputs; ++j) { CTxOut txout(amountToSend, scriptPubKey); txNew.vout.push_back(txout); } //vin CTxIn vin = CTxIn(utxo.GetHash(), 0, CScript(), std::numeric_limits<unsigned int>::max() - 1); txNew.vin.push_back(vin); //previous tx's script pub key that we need to sign CScript& scriptSigRes = txNew.vin[0].scriptSig; CTransaction txNewConst(txNew); ProduceSignature(TransactionSignatureCreator(&keystore, &txNewConst, 0), utxo.vout[0].scriptPubKey, scriptSigRes); res.push_back(txNew); return true; }
BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) { LOCK(cs_main); // Cap last block file size, and mine new block in a new block file. CBlockIndex* const nullBlock = nullptr; CBlockIndex* oldTip = chainActive.Tip(); GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CBlockIndex* newTip = chainActive.Tip(); // Verify ScanForWalletTransactions picks up transactions in both the old // and new block files. { CWallet wallet; LOCK(wallet.cs_wallet); wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); BOOST_CHECK_EQUAL(nullBlock, wallet.ScanForWalletTransactions(oldTip)); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN); } // Prune the older block file. PruneOneBlockFile(oldTip->GetBlockPos().nFile); UnlinkPrunedFiles({oldTip->GetBlockPos().nFile}); // Verify ScanForWalletTransactions only picks transactions in the new block // file. { CWallet wallet; LOCK(wallet.cs_wallet); wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip)); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN); } // Verify importmulti RPC returns failure for a key whose creation time is // before the missing block, and success for a key whose creation time is // after. { CWallet wallet; vpwallets.insert(vpwallets.begin(), &wallet); UniValue keys; keys.setArray(); UniValue key; key.setObject(); key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(coinbaseKey.GetPubKey()))); key.pushKV("timestamp", 0); key.pushKV("internal", UniValue(true)); keys.push_back(key); key.clear(); key.setObject(); CKey futureKey; futureKey.MakeNewKey(true); key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(futureKey.GetPubKey()))); key.pushKV("timestamp", newTip->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1); key.pushKV("internal", UniValue(true)); keys.push_back(key); JSONRPCRequest request; request.params.setArray(); request.params.push_back(keys); UniValue response = importmulti(request); BOOST_CHECK_EQUAL(response.write(), strprintf("[{\"success\":false,\"error\":{\"code\":-1,\"message\":\"Rescan failed for key with creation " "timestamp %d. There was an error reading a block from time %d, which is after or within %d " "seconds of key creation, and could contain transactions pertaining to the key. As a result, " "transactions and coins using this key may not appear in the wallet. This error could be caused " "by pruning or data corruption (see bitcoind log for details) and could be dealt with by " "downloading and rescanning the relevant blocks (see -reindex and -rescan " "options).\"}},{\"success\":true}]", 0, oldTip->GetBlockTimeMax(), TIMESTAMP_WINDOW)); vpwallets.erase(vpwallets.begin()); } }
void PaperWalletDialog::on_getNewAddress_clicked() { // Create a new private key CKey privKey; privKey.MakeNewKey(true); // Derive the public key CPubKey pubkey = privKey.GetPubKey(); // Derive the public key hash CBitcoinAddress pubkeyhash; pubkeyhash.Set(pubkey.GetID()); // Create String versions of each string myPrivKey = CBitcoinSecret(privKey).ToString(); string myPubKey = HexStr(pubkey.begin(), pubkey.end()); string myAddress = pubkeyhash.ToString(); #ifdef USE_QRCODE // Generate the address QR code QRcode *code = QRcode_encodeString(myAddress.c_str(), 0, QR_ECLEVEL_M, QR_MODE_8, 1); if (!code) { ui->addressQRCode->setText(tr("Error encoding Address into QR Code.")); return; } QImage publicKeyImage = QImage(code->width, code->width, QImage::Format_ARGB32); publicKeyImage.fill(0x000000); unsigned char *p = code->data; for (int y = 0; y < code->width; y++) { for (int x = 0; x < code->width; x++) { publicKeyImage.setPixel(x, y, ((*p & 1) ? 0xff000000 : 0x0)); p++; } } QRcode_free(code); // Generate the private key QR code code = QRcode_encodeString(myPrivKey.c_str(), 0, QR_ECLEVEL_M, QR_MODE_8, 1); if (!code) { ui->privateKeyQRCode->setText(tr("Error encoding private key into QR Code.")); return; } QImage privateKeyImage = QImage(code->width, code->width, QImage::Format_ARGB32); privateKeyImage.fill(0x000000); p = code->data; for (int y = 0; y < code->width; y++) { for (int x = 0; x < code->width; x++) { privateKeyImage.setPixel(x, y, ((*p & 1) ? 0xff000000 : 0x0)); p++; } } QRcode_free(code); // Populate the QR Codes ui->addressQRCode->setPixmap(QPixmap::fromImage(publicKeyImage).scaled(ui->addressQRCode->width(), ui->addressQRCode->height())); ui->privateKeyQRCode->setPixmap(QPixmap::fromImage(privateKeyImage).scaled(ui->privateKeyQRCode->width(), ui->privateKeyQRCode->height())); #endif // Populate the Texts ui->addressText->setText(myAddress.c_str()); ui->privateKeyText->setText(tr(myPrivKey.c_str())); ui->publicKey->setHtml(myPubKey.c_str()); // Update the fonts to fit the height of the wallet. // This should only really trigger the first time since the font size persists. double paperHeight = (double) ui->paperTemplate->height(); double maxTextWidth = paperHeight * 0.99; double minTextWidth = paperHeight * 0.95; int pixelSizeStep = 1; int addressTextLength = ui->addressText->fontMetrics().boundingRect(ui->addressText->text()).width(); QFont font = ui->addressText->font(); for(int i = 0; i < PAPER_WALLET_READJUST_LIMIT; i++) { if ( addressTextLength < minTextWidth) { font.setPixelSize(font.pixelSize() + pixelSizeStep); ui->addressText->setFont(font); addressTextLength = ui->addressText->fontMetrics().boundingRect(ui->addressText->text()).width(); } else { break; } } if ( addressTextLength > maxTextWidth ) { font.setPixelSize(font.pixelSize() - pixelSizeStep); ui->addressText->setFont(font); addressTextLength = ui->addressText->fontMetrics().boundingRect(ui->addressText->text()).width(); } int privateKeyTextLength = ui->privateKeyText->fontMetrics().boundingRect(ui->privateKeyText->text()).width(); font = ui->privateKeyText->font(); for(int i = 0; i < PAPER_WALLET_READJUST_LIMIT; i++) { if ( privateKeyTextLength < minTextWidth) { font.setPixelSize(font.pixelSize() + pixelSizeStep); ui->privateKeyText->setFont(font); privateKeyTextLength = ui->privateKeyText->fontMetrics().boundingRect(ui->privateKeyText->text()).width(); } else { break; } } if ( privateKeyTextLength > maxTextWidth ) { font.setPixelSize(font.pixelSize() - pixelSizeStep); ui->privateKeyText->setFont(font); privateKeyTextLength = ui->privateKeyText->fontMetrics().boundingRect(ui->privateKeyText->text()).width(); } }
void AddEditStormNode::on_okButton_clicked() { if(ui->aliasLineEdit->text() == "") { QMessageBox msg; msg.setText("Please enter an alias."); msg.exec(); return; } else if(ui->addressLineEdit->text() == "") { QMessageBox msg; msg.setText("Please enter an address."); msg.exec(); return; } else { CStormNodeConfig c; c.sAlias = ui->aliasLineEdit->text().toStdString(); c.sAddress = ui->addressLineEdit->text().toStdString(); CKey secret; secret.MakeNewKey(false); c.sStormnodePrivKey = CBitcoinSecret(secret).ToString(); CWalletDB walletdb(pwalletMain->strWalletFile); CAccount account; walletdb.ReadAccount(c.sAlias, account); bool bKeyUsed = false; bool bForceNew = false; // Check if the current key has been used if (account.vchPubKey.IsValid()) { CScript scriptPubKey; scriptPubKey.SetDestination(account.vchPubKey.GetID()); for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end() && account.vchPubKey.IsValid(); ++it) { const CWalletTx& wtx = (*it).second; BOOST_FOREACH(const CTxOut& txout, wtx.vout) if (txout.scriptPubKey == scriptPubKey) bKeyUsed = true; } } // Generate a new key if (!account.vchPubKey.IsValid() || bForceNew || bKeyUsed) { if (!pwalletMain->GetKeyFromPool(account.vchPubKey)) { QMessageBox msg; msg.setText("Keypool ran out, please call keypoolrefill first."); msg.exec(); return; } pwalletMain->SetAddressBookName(account.vchPubKey.GetID(), c.sAlias); walletdb.WriteAccount(c.sAlias, account); } c.sCollateralAddress = CBitcoinAddress(account.vchPubKey.GetID()).ToString(); pwalletMain->mapMyStormNodes.insert(make_pair(c.sAddress, c)); walletdb.WriteStormNodeConfig(c.sAddress, c); uiInterface.NotifyStormNodeChanged(c); accept(); }
// Notes: // 1. #1159 Currently there is no limit set on the number of joinsplits, so size of tx could be invalid. // 2. #1360 Note selection is not optimal // 3. #1277 Spendable notes are not locked, so an operation running in parallel could also try to use them bool AsyncRPCOperation_sendmany::main_impl() { assert(isfromtaddr_ != isfromzaddr_); bool isSingleZaddrOutput = (t_outputs_.size()==0 && z_outputs_.size()==1); bool isMultipleZaddrOutput = (t_outputs_.size()==0 && z_outputs_.size()>=1); bool isPureTaddrOnlyTx = (isfromtaddr_ && z_outputs_.size() == 0); CAmount minersFee = fee_; // When spending coinbase utxos, you can only specify a single zaddr as the change must go somewhere // and if there are multiple zaddrs, we don't know where to send it. if (isfromtaddr_) { if (isSingleZaddrOutput) { bool b = find_utxos(true); if (!b) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds, no UTXOs found for taddr from address."); } } else { bool b = find_utxos(false); if (!b) { if (isMultipleZaddrOutput) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Could not find any non-coinbase UTXOs to spend. Coinbase UTXOs can only be sent to a single zaddr recipient."); } else { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Could not find any non-coinbase UTXOs to spend."); } } } } if (isfromzaddr_ && !find_unspent_notes()) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds, no unspent notes found for zaddr from address."); } CAmount t_inputs_total = 0; for (SendManyInputUTXO & t : t_inputs_) { t_inputs_total += std::get<2>(t); } CAmount z_inputs_total = 0; for (SendManyInputJSOP & t : z_inputs_) { z_inputs_total += std::get<2>(t); } CAmount t_outputs_total = 0; for (SendManyRecipient & t : t_outputs_) { t_outputs_total += std::get<1>(t); } CAmount z_outputs_total = 0; for (SendManyRecipient & t : z_outputs_) { z_outputs_total += std::get<1>(t); } CAmount sendAmount = z_outputs_total + t_outputs_total; CAmount targetAmount = sendAmount + minersFee; assert(!isfromtaddr_ || z_inputs_total == 0); assert(!isfromzaddr_ || t_inputs_total == 0); if (isfromtaddr_ && (t_inputs_total < targetAmount)) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strprintf("Insufficient transparent funds, have %s, need %s", FormatMoney(t_inputs_total), FormatMoney(targetAmount))); } if (isfromzaddr_ && (z_inputs_total < targetAmount)) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strprintf("Insufficient protected funds, have %s, need %s", FormatMoney(z_inputs_total), FormatMoney(targetAmount))); } // If from address is a taddr, select UTXOs to spend CAmount selectedUTXOAmount = 0; bool selectedUTXOCoinbase = false; if (isfromtaddr_) { // Get dust threshold CKey secret; secret.MakeNewKey(true); CScript scriptPubKey = GetScriptForDestination(secret.GetPubKey().GetID()); CTxOut out(CAmount(1), scriptPubKey); CAmount dustThreshold = out.GetDustThreshold(minRelayTxFee); CAmount dustChange = -1; std::vector<SendManyInputUTXO> selectedTInputs; for (SendManyInputUTXO & t : t_inputs_) { bool b = std::get<3>(t); if (b) { selectedUTXOCoinbase = true; } selectedUTXOAmount += std::get<2>(t); selectedTInputs.push_back(t); if (selectedUTXOAmount >= targetAmount) { // Select another utxo if there is change less than the dust threshold. dustChange = selectedUTXOAmount - targetAmount; if (dustChange == 0 || dustChange >= dustThreshold) { break; } } } // If there is transparent change, is it valid or is it dust? if (dustChange < dustThreshold && dustChange != 0) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strprintf("Insufficient transparent funds, have %s, need %s more to avoid creating invalid change output %s (dust threshold is %s)", FormatMoney(t_inputs_total), FormatMoney(dustThreshold - dustChange), FormatMoney(dustChange), FormatMoney(dustThreshold))); } t_inputs_ = selectedTInputs; t_inputs_total = selectedUTXOAmount; // Check mempooltxinputlimit to avoid creating a transaction which the local mempool rejects size_t limit = (size_t)GetArg("-mempooltxinputlimit", 0); { LOCK(cs_main); if (NetworkUpgradeActive(chainActive.Height() + 1, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER)) { limit = 0; } } if (limit > 0) { size_t n = t_inputs_.size(); if (n > limit) { throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Too many transparent inputs %zu > limit %zu", n, limit)); } } // update the transaction with these inputs CMutableTransaction rawTx(tx_); for (SendManyInputUTXO & t : t_inputs_) { uint256 txid = std::get<0>(t); int vout = std::get<1>(t); CAmount amount = std::get<2>(t); CTxIn in(COutPoint(txid, vout)); rawTx.vin.push_back(in); } tx_ = CTransaction(rawTx); } LogPrint((isfromtaddr_) ? "zrpc" : "zrpcunsafe", "%s: spending %s to send %s with fee %s\n", getId(), FormatMoney(targetAmount), FormatMoney(sendAmount), FormatMoney(minersFee)); LogPrint("zrpc", "%s: transparent input: %s (to choose from)\n", getId(), FormatMoney(t_inputs_total)); LogPrint("zrpcunsafe", "%s: private input: %s (to choose from)\n", getId(), FormatMoney(z_inputs_total)); LogPrint("zrpc", "%s: transparent output: %s\n", getId(), FormatMoney(t_outputs_total)); LogPrint("zrpcunsafe", "%s: private output: %s\n", getId(), FormatMoney(z_outputs_total)); LogPrint("zrpc", "%s: fee: %s\n", getId(), FormatMoney(minersFee)); // Grab the current consensus branch ID { LOCK(cs_main); consensusBranchId_ = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus()); } /** * SCENARIO #1 * * taddr -> taddrs * * There are no zaddrs or joinsplits involved. */ if (isPureTaddrOnlyTx) { add_taddr_outputs_to_tx(); CAmount funds = selectedUTXOAmount; CAmount fundsSpent = t_outputs_total + minersFee; CAmount change = funds - fundsSpent; if (change > 0) { add_taddr_change_output_to_tx(change); LogPrint("zrpc", "%s: transparent change in transaction output (amount=%s)\n", getId(), FormatMoney(change) ); } UniValue obj(UniValue::VOBJ); obj.push_back(Pair("rawtxn", EncodeHexTx(tx_))); sign_send_raw_transaction(obj); return true; } /** * END SCENARIO #1 */ // Prepare raw transaction to handle JoinSplits CMutableTransaction mtx(tx_); crypto_sign_keypair(joinSplitPubKey_.begin(), joinSplitPrivKey_); mtx.joinSplitPubKey = joinSplitPubKey_; tx_ = CTransaction(mtx); // Copy zinputs and zoutputs to more flexible containers std::deque<SendManyInputJSOP> zInputsDeque; // zInputsDeque stores minimum numbers of notes for target amount CAmount tmp = 0; for (auto o : z_inputs_) { zInputsDeque.push_back(o); tmp += std::get<2>(o); if (tmp >= targetAmount) { break; } } std::deque<SendManyRecipient> zOutputsDeque; for (auto o : z_outputs_) { // TODO: Add Sapling support. For now, ensure we can later convert freely. auto addr = DecodePaymentAddress(std::get<0>(o)); assert(boost::get<libzcash::SproutPaymentAddress>(&addr) != nullptr); zOutputsDeque.push_back(o); } // When spending notes, take a snapshot of note witnesses and anchors as the treestate will // change upon arrival of new blocks which contain joinsplit transactions. This is likely // to happen as creating a chained joinsplit transaction can take longer than the block interval. if (z_inputs_.size() > 0) { LOCK2(cs_main, pwalletMain->cs_wallet); for (auto t : z_inputs_) { JSOutPoint jso = std::get<0>(t); std::vector<JSOutPoint> vOutPoints = { jso }; uint256 inputAnchor; std::vector<boost::optional<ZCIncrementalWitness>> vInputWitnesses; pwalletMain->GetNoteWitnesses(vOutPoints, vInputWitnesses, inputAnchor); jsopWitnessAnchorMap[ jso.ToString() ] = WitnessAnchorData{ vInputWitnesses[0], inputAnchor }; } } /** * SCENARIO #2 * * taddr -> taddrs * -> zaddrs * * Note: Consensus rule states that coinbase utxos can only be sent to a zaddr. * Local wallet rule does not allow any change when sending coinbase utxos * since there is currently no way to specify a change address and we don't * want users accidentally sending excess funds to a recipient. */ if (isfromtaddr_) { add_taddr_outputs_to_tx(); CAmount funds = selectedUTXOAmount; CAmount fundsSpent = t_outputs_total + minersFee + z_outputs_total; CAmount change = funds - fundsSpent; if (change > 0) { if (selectedUTXOCoinbase) { assert(isSingleZaddrOutput); throw JSONRPCError(RPC_WALLET_ERROR, strprintf( "Change %s not allowed. When protecting coinbase funds, the wallet does not " "allow any change as there is currently no way to specify a change address " "in z_sendmany.", FormatMoney(change))); } else { add_taddr_change_output_to_tx(change); LogPrint("zrpc", "%s: transparent change in transaction output (amount=%s)\n", getId(), FormatMoney(change) ); } } // Create joinsplits, where each output represents a zaddr recipient. UniValue obj(UniValue::VOBJ); while (zOutputsDeque.size() > 0) { AsyncJoinSplitInfo info; info.vpub_old = 0; info.vpub_new = 0; int n = 0; while (n++<ZC_NUM_JS_OUTPUTS && zOutputsDeque.size() > 0) { SendManyRecipient smr = zOutputsDeque.front(); std::string address = std::get<0>(smr); CAmount value = std::get<1>(smr); std::string hexMemo = std::get<2>(smr); zOutputsDeque.pop_front(); PaymentAddress pa = DecodePaymentAddress(address); JSOutput jso = JSOutput(boost::get<libzcash::SproutPaymentAddress>(pa), value); if (hexMemo.size() > 0) { jso.memo = get_memo_from_hex_string(hexMemo); } info.vjsout.push_back(jso); // Funds are removed from the value pool and enter the private pool info.vpub_old += value; } obj = perform_joinsplit(info); } sign_send_raw_transaction(obj); return true; } /** * END SCENARIO #2 */ /** * SCENARIO #3 * * zaddr -> taddrs * -> zaddrs * * Send to zaddrs by chaining JoinSplits together and immediately consuming any change * Send to taddrs by creating dummy z outputs and accumulating value in a change note * which is used to set vpub_new in the last chained joinsplit. */ UniValue obj(UniValue::VOBJ); CAmount jsChange = 0; // this is updated after each joinsplit int changeOutputIndex = -1; // this is updated after each joinsplit if jsChange > 0 bool vpubNewProcessed = false; // updated when vpub_new for miner fee and taddr outputs is set in last joinsplit CAmount vpubNewTarget = minersFee; if (t_outputs_total > 0) { add_taddr_outputs_to_tx(); vpubNewTarget += t_outputs_total; } // Keep track of treestate within this transaction boost::unordered_map<uint256, ZCIncrementalMerkleTree, CCoinsKeyHasher> intermediates; std::vector<uint256> previousCommitments; while (!vpubNewProcessed) { AsyncJoinSplitInfo info; info.vpub_old = 0; info.vpub_new = 0; CAmount jsInputValue = 0; uint256 jsAnchor; std::vector<boost::optional<ZCIncrementalWitness>> witnesses; JSDescription prevJoinSplit; // Keep track of previous JoinSplit and its commitments if (tx_.vjoinsplit.size() > 0) { prevJoinSplit = tx_.vjoinsplit.back(); } // If there is no change, the chain has terminated so we can reset the tracked treestate. if (jsChange==0 && tx_.vjoinsplit.size() > 0) { intermediates.clear(); previousCommitments.clear(); } // // Consume change as the first input of the JoinSplit. // if (jsChange > 0) { LOCK2(cs_main, pwalletMain->cs_wallet); // Update tree state with previous joinsplit ZCIncrementalMerkleTree tree; auto it = intermediates.find(prevJoinSplit.anchor); if (it != intermediates.end()) { tree = it->second; } else if (!pcoinsTip->GetSproutAnchorAt(prevJoinSplit.anchor, tree)) { throw JSONRPCError(RPC_WALLET_ERROR, "Could not find previous JoinSplit anchor"); } assert(changeOutputIndex != -1); boost::optional<ZCIncrementalWitness> changeWitness; int n = 0; for (const uint256& commitment : prevJoinSplit.commitments) { tree.append(commitment); previousCommitments.push_back(commitment); if (!changeWitness && changeOutputIndex == n++) { changeWitness = tree.witness(); } else if (changeWitness) { changeWitness.get().append(commitment); } } if (changeWitness) { witnesses.push_back(changeWitness); } jsAnchor = tree.root(); intermediates.insert(std::make_pair(tree.root(), tree)); // chained js are interstitial (found in between block boundaries) // Decrypt the change note's ciphertext to retrieve some data we need ZCNoteDecryption decryptor(boost::get<libzcash::SproutSpendingKey>(spendingkey_).receiving_key()); auto hSig = prevJoinSplit.h_sig(*pzcashParams, tx_.joinSplitPubKey); try { SproutNotePlaintext plaintext = SproutNotePlaintext::decrypt( decryptor, prevJoinSplit.ciphertexts[changeOutputIndex], prevJoinSplit.ephemeralKey, hSig, (unsigned char) changeOutputIndex); SproutNote note = plaintext.note(boost::get<libzcash::SproutPaymentAddress>(frompaymentaddress_)); info.notes.push_back(note); jsInputValue += plaintext.value(); LogPrint("zrpcunsafe", "%s: spending change (amount=%s)\n", getId(), FormatMoney(plaintext.value()) ); } catch (const std::exception& e) { throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Error decrypting output note of previous JoinSplit: %s", e.what())); } } // // Consume spendable non-change notes // std::vector<SproutNote> vInputNotes; std::vector<JSOutPoint> vOutPoints; std::vector<boost::optional<ZCIncrementalWitness>> vInputWitnesses; uint256 inputAnchor; int numInputsNeeded = (jsChange>0) ? 1 : 0; while (numInputsNeeded++ < ZC_NUM_JS_INPUTS && zInputsDeque.size() > 0) { SendManyInputJSOP t = zInputsDeque.front(); JSOutPoint jso = std::get<0>(t); SproutNote note = std::get<1>(t); CAmount noteFunds = std::get<2>(t); zInputsDeque.pop_front(); WitnessAnchorData wad = jsopWitnessAnchorMap[ jso.ToString() ]; vInputWitnesses.push_back(wad.witness); if (inputAnchor.IsNull()) { inputAnchor = wad.anchor; } else if (inputAnchor != wad.anchor) { throw JSONRPCError(RPC_WALLET_ERROR, "Selected input notes do not share the same anchor"); } vOutPoints.push_back(jso); vInputNotes.push_back(note); jsInputValue += noteFunds; int wtxHeight = -1; int wtxDepth = -1; { LOCK2(cs_main, pwalletMain->cs_wallet); const CWalletTx& wtx = pwalletMain->mapWallet[jso.hash]; // Zero-confirmation notes belong to transactions which have not yet been mined if (mapBlockIndex.find(wtx.hashBlock) == mapBlockIndex.end()) { throw JSONRPCError(RPC_WALLET_ERROR, strprintf("mapBlockIndex does not contain block hash %s", wtx.hashBlock.ToString())); } wtxHeight = mapBlockIndex[wtx.hashBlock]->nHeight; wtxDepth = wtx.GetDepthInMainChain(); } LogPrint("zrpcunsafe", "%s: spending note (txid=%s, vjoinsplit=%d, ciphertext=%d, amount=%s, height=%d, confirmations=%d)\n", getId(), jso.hash.ToString().substr(0, 10), jso.js, int(jso.n), // uint8_t FormatMoney(noteFunds), wtxHeight, wtxDepth ); } // Add history of previous commitments to witness if (vInputNotes.size() > 0) { if (vInputWitnesses.size()==0) { throw JSONRPCError(RPC_WALLET_ERROR, "Could not find witness for note commitment"); } for (auto & optionalWitness : vInputWitnesses) { if (!optionalWitness) { throw JSONRPCError(RPC_WALLET_ERROR, "Witness for note commitment is null"); } ZCIncrementalWitness w = *optionalWitness; // could use .get(); if (jsChange > 0) { for (const uint256& commitment : previousCommitments) { w.append(commitment); } if (jsAnchor != w.root()) { throw JSONRPCError(RPC_WALLET_ERROR, "Witness for spendable note does not have same anchor as change input"); } } witnesses.push_back(w); } // The jsAnchor is null if this JoinSplit is at the start of a new chain if (jsAnchor.IsNull()) { jsAnchor = inputAnchor; } // Add spendable notes as inputs std::copy(vInputNotes.begin(), vInputNotes.end(), std::back_inserter(info.notes)); } // Find recipient to transfer funds to std::string address, hexMemo; CAmount value = 0; if (zOutputsDeque.size() > 0) { SendManyRecipient smr = zOutputsDeque.front(); address = std::get<0>(smr); value = std::get<1>(smr); hexMemo = std::get<2>(smr); zOutputsDeque.pop_front(); } // Reset change jsChange = 0; CAmount outAmount = value; // Set vpub_new in the last joinsplit (when there are no more notes to spend or zaddr outputs to satisfy) if (zOutputsDeque.size() == 0 && zInputsDeque.size() == 0) { assert(!vpubNewProcessed); if (jsInputValue < vpubNewTarget) { throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Insufficient funds for vpub_new %s (miners fee %s, taddr outputs %s)", FormatMoney(vpubNewTarget), FormatMoney(minersFee), FormatMoney(t_outputs_total))); } outAmount += vpubNewTarget; info.vpub_new += vpubNewTarget; // funds flowing back to public pool vpubNewProcessed = true; jsChange = jsInputValue - outAmount; assert(jsChange >= 0); } else { // This is not the last joinsplit, so compute change and any amount still due to the recipient if (jsInputValue > outAmount) { jsChange = jsInputValue - outAmount; } else if (outAmount > jsInputValue) { // Any amount due is owed to the recipient. Let the miners fee get paid first. CAmount due = outAmount - jsInputValue; SendManyRecipient r = SendManyRecipient(address, due, hexMemo); zOutputsDeque.push_front(r); // reduce the amount being sent right now to the value of all inputs value = jsInputValue; } } // create output for recipient if (address.empty()) { assert(value==0); info.vjsout.push_back(JSOutput()); // dummy output while we accumulate funds into a change note for vpub_new } else { PaymentAddress pa = DecodePaymentAddress(address); JSOutput jso = JSOutput(boost::get<libzcash::SproutPaymentAddress>(pa), value); if (hexMemo.size() > 0) { jso.memo = get_memo_from_hex_string(hexMemo); } info.vjsout.push_back(jso); } // create output for any change if (jsChange>0) { info.vjsout.push_back(JSOutput(boost::get<libzcash::SproutPaymentAddress>(frompaymentaddress_), jsChange)); LogPrint("zrpcunsafe", "%s: generating note for change (amount=%s)\n", getId(), FormatMoney(jsChange) ); } obj = perform_joinsplit(info, witnesses, jsAnchor); if (jsChange > 0) { changeOutputIndex = find_output(obj, 1); } } // Sanity check in case changes to code block above exits loop by invoking 'break' assert(zInputsDeque.size() == 0); assert(zOutputsDeque.size() == 0); assert(vpubNewProcessed); sign_send_raw_transaction(obj); return true; }
double benchmark_large_tx() { // Number of inputs in the spending transaction that we will simulate const size_t NUM_INPUTS = 555; // Create priv/pub key CKey priv; priv.MakeNewKey(false); auto pub = priv.GetPubKey(); CBasicKeyStore tempKeystore; tempKeystore.AddKey(priv); // The "original" transaction that the spending transaction will spend // from. CMutableTransaction m_orig_tx; m_orig_tx.vout.resize(1); m_orig_tx.vout[0].nValue = 1000000; CScript prevPubKey = GetScriptForDestination(pub.GetID()); m_orig_tx.vout[0].scriptPubKey = prevPubKey; auto orig_tx = CTransaction(m_orig_tx); CMutableTransaction spending_tx; auto input_hash = orig_tx.GetHash(); // Add NUM_INPUTS inputs for (size_t i = 0; i < NUM_INPUTS; i++) { spending_tx.vin.emplace_back(input_hash, 0); } // Sign for all the inputs for (size_t i = 0; i < NUM_INPUTS; i++) { SignSignature(tempKeystore, prevPubKey, spending_tx, i, SIGHASH_ALL); } // Serialize: { CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); ss << spending_tx; //std::cout << "SIZE OF SPENDING TX: " << ss.size() << std::endl; auto error = MAX_TX_SIZE / 20; // 5% error assert(ss.size() < MAX_TX_SIZE + error); assert(ss.size() > MAX_TX_SIZE - error); } // Spending tx has all its inputs signed and does not need to be mutated anymore CTransaction final_spending_tx(spending_tx); // Benchmark signature verification costs: struct timeval tv_start; timer_start(tv_start); for (size_t i = 0; i < NUM_INPUTS; i++) { ScriptError serror = SCRIPT_ERR_OK; assert(VerifyScript(final_spending_tx.vin[i].scriptSig, prevPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&final_spending_tx, i), &serror)); } return timer_stop(tv_start); }
BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) { LOCK(cs_main); // Cap last block file size, and mine new block in a new block file. CBlockIndex* oldTip = chainActive.Tip(); GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CBlockIndex* newTip = chainActive.Tip(); // Verify ScanForWalletTransactions picks up transactions in both the old // and new block files. { CWallet wallet; LOCK(wallet.cs_wallet); wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip)); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN); } // Prune the older block file. PruneOneBlockFile(oldTip->GetBlockPos().nFile); UnlinkPrunedFiles({oldTip->GetBlockPos().nFile}); // Verify ScanForWalletTransactions only picks transactions in the new block // file. { CWallet wallet; LOCK(wallet.cs_wallet); wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); BOOST_CHECK_EQUAL(newTip, wallet.ScanForWalletTransactions(oldTip)); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN); } // Verify importmulti RPC returns failure for a key whose creation time is // before the missing block, and success for a key whose creation time is // after. { CWallet wallet; CWallet *backup = ::pwalletMain; ::pwalletMain = &wallet; UniValue keys; keys.setArray(); UniValue key; key.setObject(); key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(coinbaseKey.GetPubKey()))); key.pushKV("timestamp", 0); key.pushKV("internal", UniValue(true)); keys.push_back(key); key.clear(); key.setObject(); CKey futureKey; futureKey.MakeNewKey(true); key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(futureKey.GetPubKey()))); key.pushKV("timestamp", newTip->GetBlockTimeMax() + TIMESTAMP_WINDOW); key.pushKV("internal", UniValue(true)); keys.push_back(key); JSONRPCRequest request; request.params.setArray(); request.params.push_back(keys); UniValue response = importmulti(request); BOOST_CHECK_EQUAL(response.write(), strprintf("[{\"success\":false,\"error\":{\"code\":-1,\"message\":\"Failed to rescan before time %d, transactions may be missing.\"}},{\"success\":true}]", newTip->GetBlockTimeMax())); ::pwalletMain = backup; } // Verify ScanForWalletTransactions does not return null when the scan is // elided due to the nTimeFirstKey optimization. { CWallet wallet; { LOCK(wallet.cs_wallet); wallet.UpdateTimeFirstKey(newTip->GetBlockTime() + 7200 + 1); } BOOST_CHECK_EQUAL(newTip, wallet.ScanForWalletTransactions(newTip)); } }