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:
commit
99bc0b428b
2 changed files with 60 additions and 15 deletions
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue