Skip to content

Commit

Permalink
Fix crash when mining with empty keypool.
Browse files Browse the repository at this point in the history
Since the introduction of the ScriptForMining callback, the mining
functions (setgenerate and generate) crash with an assertion failure
(due to a NULL pointer script returned) if the keypool is empty.  Fix
this by giving a proper error.

Zcash: Adapted to our MinerAddress type.

Co-authored-by: Jack Grigg <jack@electriccoin.co>
  • Loading branch information
domob1812 and str4d committed Nov 20, 2020
1 parent 8772124 commit 95d1f88
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 10 deletions.
17 changes: 16 additions & 1 deletion qa/rpc-tests/keypool.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# Add python-bitcoinrpc to module search path:

from test_framework.authproxy import JSONRPCException
from test_framework.util import check_json_precision, initialize_chain, \
from test_framework.util import assert_equal, check_json_precision, initialize_chain, \
start_nodes, start_node, stop_nodes, wait_bitcoinds, bitcoind_processes

import os
Expand Down Expand Up @@ -72,6 +72,21 @@ def run_test(nodes, tmpdir):
except JSONRPCException as e:
assert(e.error['code']==-12)

# refill keypool with three new addresses
nodes[0].walletpassphrase('test', 12000)
nodes[0].keypoolrefill(3)
nodes[0].walletlock()

# drain them by mining
nodes[0].generate(1)
nodes[0].generate(1)
nodes[0].generate(1)
nodes[0].generate(1)
try:
nodes[0].generate(1)
raise AssertionError('Keypool should be exhausted after three addesses')
except JSONRPCException as e:
assert_equal(e.error['code'], -12)

def main():
import optparse
Expand Down
6 changes: 1 addition & 5 deletions src/miner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,6 @@ void UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams,
}
}

bool IsValidMinerAddress(const MinerAddress& minerAddr) {
return minerAddr.which() != 0;
}

class AddFundingStreamValueToTx : public boost::static_visitor<bool>
{
private:
Expand Down Expand Up @@ -742,7 +738,7 @@ void static BitcoinMiner(const CChainParams& chainparams)

try {
// Throw an error if no address valid for mining was provided.
if (!IsValidMinerAddress(minerAddress)) {
if (!boost::apply_visitor(IsValidMinerAddress(), minerAddress)) {
throw std::runtime_error("No miner address available (mining requires a wallet or -mineraddress)");
}

Expand Down
19 changes: 18 additions & 1 deletion src/miner.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,24 @@ class KeepMinerAddress : public boost::static_visitor<>
}
};

bool IsValidMinerAddress(const MinerAddress& minerAddr);
class IsValidMinerAddress : public boost::static_visitor<bool>
{
public:
IsValidMinerAddress() {}

bool operator()(const InvalidMinerAddress &invalid) const {
return false;
}
bool operator()(const libzcash::SaplingPaymentAddress &pa) const {
return true;
}
bool operator()(const boost::shared_ptr<CReserveScript> &coinbaseScript) const {
// Return false if no script was provided. This can happen
// due to some internal error but also if the keypool is empty.
// In the latter case, already the pointer is NULL.
return coinbaseScript.get() && !coinbaseScript->reserveScript.empty();
}
};

struct CBlockTemplate
{
Expand Down
10 changes: 8 additions & 2 deletions src/rpc/mining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,14 @@ UniValue generate(const UniValue& params, bool fHelp)
MinerAddress minerAddress;
GetMainSignals().AddressForMining(minerAddress);

// If the keypool is exhausted, no script is returned at all. Catch this.
auto resv = boost::get<boost::shared_ptr<CReserveScript>>(&minerAddress);
if (resv && !resv->get()) {
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
}

// Throw an error if no address valid for mining was provided.
if (!IsValidMinerAddress(minerAddress)) {
if (!boost::apply_visitor(IsValidMinerAddress(), minerAddress)) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "No miner address available (mining requires a wallet or -mineraddress)");
}

Expand Down Expand Up @@ -622,7 +628,7 @@ UniValue getblocktemplate(const UniValue& params, bool fHelp)
GetMainSignals().AddressForMining(minerAddress);

// Throw an error if no address valid for mining was provided.
if (!IsValidMinerAddress(minerAddress)) {
if (!boost::apply_visitor(IsValidMinerAddress(), minerAddress)) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "No miner address available (mining requires a wallet or -mineraddress)");
}

Expand Down
6 changes: 5 additions & 1 deletion src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4418,8 +4418,12 @@ void CWallet::GetAddressForMining(MinerAddress &minerAddress)

boost::shared_ptr<CReserveKey> rKey(new CReserveKey(this));
CPubKey pubkey;
if (!rKey->GetReservedKey(pubkey))
if (!rKey->GetReservedKey(pubkey)) {
// Explicitly return nullptr to indicate that the keypool is empty.
rKey = nullptr;
minerAddress = rKey;
return;
}

rKey->reserveScript = CScript() << OP_DUP << OP_HASH160 << ToByteVector(pubkey.GetID()) << OP_EQUALVERIFY << OP_CHECKSIG;
minerAddress = rKey;
Expand Down

0 comments on commit 95d1f88

Please sign in to comment.