Merge #11087: Diagnose unsuitable outputs in lockunspent().

28f8b66 Diagnose unsuitable outputs in lockunspent(). (Eelis)

Pull request description:

  Fixes #2667.

  This is a simplified version of pull request #3574, which was abandoned by its author.

  I added some tests as well.

Tree-SHA512: e63e00dec8b1b232079380183805cb0b0b18c78ea6bea769837949aab984689d7f68b2ccfe66b1873517b040b9e616ce0eb058575c3d4382aa8c26eebcf1f14e
This commit is contained in:
Wladimir J. van der Laan 2017-11-16 12:23:55 +01:00
commit 99bc0b428b
No known key found for this signature in database
GPG key ID: 1E4AED62986CD25D
2 changed files with 60 additions and 15 deletions

View file

@ -2531,12 +2531,15 @@ UniValue lockunspent(const JSONRPCRequest& request)
RPCTypeCheckArgument(request.params[1], UniValue::VARR); RPCTypeCheckArgument(request.params[1], UniValue::VARR);
UniValue outputs = request.params[1].get_array(); const UniValue& output_params = request.params[1];
for (unsigned int idx = 0; idx < outputs.size(); idx++) {
const UniValue& output = outputs[idx]; // Create and validate the COutPoints first.
if (!output.isObject())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected object"); std::vector<COutPoint> outputs;
const UniValue& o = output.get_obj(); outputs.reserve(output_params.size());
for (unsigned int idx = 0; idx < output_params.size(); idx++) {
const UniValue& o = output_params[idx].get_obj();
RPCTypeCheckObj(o, RPCTypeCheckObj(o,
{ {
@ -2544,20 +2547,50 @@ UniValue lockunspent(const JSONRPCRequest& request)
{"vout", UniValueType(UniValue::VNUM)}, {"vout", UniValueType(UniValue::VNUM)},
}); });
std::string txid = find_value(o, "txid").get_str(); const std::string& txid = find_value(o, "txid").get_str();
if (!IsHex(txid)) if (!IsHex(txid)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected hex txid"); throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected hex txid");
}
int nOutput = find_value(o, "vout").get_int(); const int nOutput = find_value(o, "vout").get_int();
if (nOutput < 0) if (nOutput < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout must be positive"); throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout must be positive");
}
COutPoint outpt(uint256S(txid), nOutput); const COutPoint outpt(uint256S(txid), nOutput);
if (fUnlock) const auto it = pwallet->mapWallet.find(outpt.hash);
pwallet->UnlockCoin(outpt); if (it == pwallet->mapWallet.end()) {
else throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, unknown transaction");
pwallet->LockCoin(outpt); }
const CWalletTx& trans = it->second;
if (outpt.n >= trans.tx->vout.size()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout index out of bounds");
}
if (pwallet->IsSpent(outpt.hash, outpt.n)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected unspent output");
}
const bool is_locked = pwallet->IsLockedCoin(outpt.hash, outpt.n);
if (fUnlock && !is_locked) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected locked output");
}
if (!fUnlock && is_locked) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output already locked");
}
outputs.push_back(outpt);
}
// Atomically set (un)locked status for the outputs.
for (const COutPoint& outpt : outputs) {
if (fUnlock) pwallet->UnlockCoin(outpt);
else pwallet->LockCoin(outpt);
} }
return true; return true;

View file

@ -100,11 +100,19 @@ class WalletTest(BitcoinTestFramework):
# Exercise locking of unspent outputs # Exercise locking of unspent outputs
unspent_0 = self.nodes[2].listunspent()[0] unspent_0 = self.nodes[2].listunspent()[0]
unspent_0 = {"txid": unspent_0["txid"], "vout": unspent_0["vout"]} unspent_0 = {"txid": unspent_0["txid"], "vout": unspent_0["vout"]}
assert_raises_rpc_error(-8, "Invalid parameter, expected locked output", self.nodes[2].lockunspent, True, [unspent_0])
self.nodes[2].lockunspent(False, [unspent_0]) self.nodes[2].lockunspent(False, [unspent_0])
assert_raises_rpc_error(-8, "Invalid parameter, output already locked", self.nodes[2].lockunspent, False, [unspent_0])
assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[2].sendtoaddress, self.nodes[2].getnewaddress(), 20) assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[2].sendtoaddress, self.nodes[2].getnewaddress(), 20)
assert_equal([unspent_0], self.nodes[2].listlockunspent()) assert_equal([unspent_0], self.nodes[2].listlockunspent())
self.nodes[2].lockunspent(True, [unspent_0]) self.nodes[2].lockunspent(True, [unspent_0])
assert_equal(len(self.nodes[2].listlockunspent()), 0) assert_equal(len(self.nodes[2].listlockunspent()), 0)
assert_raises_rpc_error(-8, "Invalid parameter, unknown transaction",
self.nodes[2].lockunspent, False,
[{"txid": "0000000000000000000000000000000000", "vout": 0}])
assert_raises_rpc_error(-8, "Invalid parameter, vout index out of bounds",
self.nodes[2].lockunspent, False,
[{"txid": unspent_0["txid"], "vout": 999}])
# Have node1 generate 100 blocks (so node0 can recover the fee) # Have node1 generate 100 blocks (so node0 can recover the fee)
self.nodes[1].generate(100) self.nodes[1].generate(100)
@ -143,6 +151,10 @@ class WalletTest(BitcoinTestFramework):
assert_equal(self.nodes[2].getbalance(), 94) assert_equal(self.nodes[2].getbalance(), 94)
assert_equal(self.nodes[2].getbalance("from1"), 94-21) assert_equal(self.nodes[2].getbalance("from1"), 94-21)
# Verify that a spent output cannot be locked anymore
spent_0 = {"txid": node0utxos[0]["txid"], "vout": node0utxos[0]["vout"]}
assert_raises_rpc_error(-8, "Invalid parameter, expected unspent output", self.nodes[0].lockunspent, False, [spent_0])
# Send 10 BTC normal # Send 10 BTC normal
address = self.nodes[0].getnewaddress("test") address = self.nodes[0].getnewaddress("test")
fee_per_byte = Decimal('0.001') / 1000 fee_per_byte = Decimal('0.001') / 1000