Browse Source

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
tags/v0.16.1
Wladimir J. van der Laan 3 years ago
parent
commit
99bc0b428b
No account linked to committer's email address
2 changed files with 60 additions and 15 deletions
  1. 48
    15
      src/wallet/rpcwallet.cpp
  2. 12
    0
      test/functional/wallet.py

+ 48
- 15
src/wallet/rpcwallet.cpp View File

@@ -2531,12 +2531,15 @@ UniValue lockunspent(const JSONRPCRequest& request)

RPCTypeCheckArgument(request.params[1], UniValue::VARR);

UniValue outputs = request.params[1].get_array();
for (unsigned int idx = 0; idx < outputs.size(); idx++) {
const UniValue& output = outputs[idx];
if (!output.isObject())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected object");
const UniValue& o = output.get_obj();
const UniValue& output_params = request.params[1];

// Create and validate the COutPoints first.

std::vector<COutPoint> outputs;
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,
{
@@ -2544,20 +2547,50 @@ UniValue lockunspent(const JSONRPCRequest& request)
{"vout", UniValueType(UniValue::VNUM)},
});

std::string txid = find_value(o, "txid").get_str();
if (!IsHex(txid))
const std::string& txid = find_value(o, "txid").get_str();
if (!IsHex(txid)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected hex txid");
}

int nOutput = find_value(o, "vout").get_int();
if (nOutput < 0)
const int nOutput = find_value(o, "vout").get_int();
if (nOutput < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout must be positive");
}

COutPoint outpt(uint256S(txid), nOutput);
const COutPoint outpt(uint256S(txid), nOutput);

if (fUnlock)
pwallet->UnlockCoin(outpt);
else
pwallet->LockCoin(outpt);
const auto it = pwallet->mapWallet.find(outpt.hash);
if (it == pwallet->mapWallet.end()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, unknown transaction");
}

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;

+ 12
- 0
test/functional/wallet.py View File

@@ -100,11 +100,19 @@ class WalletTest(BitcoinTestFramework):
# Exercise locking of unspent outputs
unspent_0 = self.nodes[2].listunspent()[0]
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])
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_equal([unspent_0], self.nodes[2].listlockunspent())
self.nodes[2].lockunspent(True, [unspent_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)
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("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
address = self.nodes[0].getnewaddress("test")
fee_per_byte = Decimal('0.001') / 1000

Loading…
Cancel
Save