Skip to content

Commit 02bd6e9

Browse files
committed
Merge #9853: Fix error codes from various RPCs
adaa281 Update release notes to include RPC error code changes. (John Newbery) 338bf06 Add commenting around JSON error codes (John Newbery) dab804c Return correct error codes in fundrawtransaction(). (John Newbery) a012087 Return correct error codes in setban(). (John Newbery) 960bc7f Return correct error codes in removeprunedfunds(). (John Newbery) c119096 Return correct error codes in blockchain.cpp. (John Newbery) 6d07c62 Return correct error codes in bumpfee(). (John Newbery) Tree-SHA512: 4bb39ad221cd8c83d98ac5d7ad642f3a8c265522720dc86b2eebc70e74439a85b06d6ddcd6a874e879d986511de3ab0878bb7fe58b50cb0546b78913632ea809
2 parents 6805c41 + adaa281 commit 02bd6e9

13 files changed

+134
-140
lines changed

doc/release-notes.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,55 @@ frequently tested on them.
3333
Notable changes
3434
===============
3535

36+
Low-level RPC changes
37+
---------------------
38+
39+
- Error codes have been updated to be more accurate for the following error cases:
40+
- `getblock` now returns RPC_MISC_ERROR if the block can't be found on disk (for
41+
example if the block has been pruned). Previously returned RPC_INTERNAL_ERROR.
42+
- `pruneblockchain` now returns RPC_MISC_ERROR if the blocks cannot be pruned
43+
because the node is not in pruned mode. Previously returned RPC_METHOD_NOT_FOUND.
44+
- `pruneblockchain` now returns RPC_INVALID_PARAMETER if the blocks cannot be pruned
45+
because the supplied timestamp is too late. Previously returned RPC_INTERNAL_ERROR.
46+
- `pruneblockchain` now returns RPC_MISC_ERROR if the blocks cannot be pruned
47+
because the blockchain is too short. Previously returned RPC_INTERNAL_ERROR.
48+
- `setban` now returns RPC_CLIENT_INVALID_IP_OR_SUBNET if the supplied IP address
49+
or subnet is invalid. Previously returned RPC_CLIENT_NODE_ALREADY_ADDED.
50+
- `setban` now returns RPC_CLIENT_INVALID_IP_OR_SUBNET if the user tries to unban
51+
a node that has not previously been banned. Previously returned RPC_MISC_ERROR.
52+
- `removeprunedfunds` now returns RPC_WALLET_ERROR if bitcoind is unable to remove
53+
the transaction. Previously returned RPC_INTERNAL_ERROR.
54+
- `removeprunedfunds` now returns RPC_INVALID_PARAMETER if the transaction does not
55+
exist in the wallet. Previously returned RPC_INTERNAL_ERROR.
56+
- `fundrawtransaction` now returns RPC_INVALID_ADDRESS_OR_KEY if an invalid change
57+
address is provided. Previously returned RPC_INVALID_PARAMETER.
58+
- `fundrawtransaction` now returns RPC_WALLET_ERROR if bitcoind is unable to create
59+
the transaction. The error message provides further details. Previously returned
60+
RPC_INTERNAL_ERROR.
61+
- `bumpfee` now returns RPC_INVALID_PARAMETER if the provided transaction has
62+
descendants in the wallet. Previously returned RPC_MISC_ERROR.
63+
- `bumpfee` now returns RPC_INVALID_PARAMETER if the provided transaction has
64+
descendants in the mempool. Previously returned RPC_MISC_ERROR.
65+
- `bumpfee` now returns RPC_WALLET_ERROR if the provided transaction has
66+
has been mined or conflicts with a mined transaction. Previously returned
67+
RPC_INVALID_ADDRESS_OR_KEY.
68+
- `bumpfee` now returns RPC_WALLET_ERROR if the provided transaction is not
69+
BIP 125 replaceable. Previously returned RPC_INVALID_ADDRESS_OR_KEY.
70+
- `bumpfee` now returns RPC_WALLET_ERROR if the provided transaction has already
71+
been bumped by a different transaction. Previously returned RPC_INVALID_REQUEST.
72+
- `bumpfee` now returns RPC_WALLET_ERROR if the provided transaction contains
73+
inputs which don't belong to this wallet. Previously returned RPC_INVALID_ADDRESS_OR_KEY.
74+
- `bumpfee` now returns RPC_WALLET_ERROR if the provided transaction has multiple change
75+
outputs. Previously returned RPC_MISC_ERROR.
76+
- `bumpfee` now returns RPC_WALLET_ERROR if the provided transaction has no change
77+
output. Previously returned RPC_MISC_ERROR.
78+
- `bumpfee` now returns RPC_WALLET_ERROR if the fee is too high. Previously returned
79+
RPC_MISC_ERROR.
80+
- `bumpfee` now returns RPC_WALLET_ERROR if the fee is too low. Previously returned
81+
RPC_MISC_ERROR.
82+
- `bumpfee` now returns RPC_WALLET_ERROR if the change output is too small to bump the
83+
fee. Previously returned RPC_MISC_ERROR.
84+
3685
Credits
3786
=======
3887

qa/rpc-tests/bumpfee.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ def test_segwit_bumpfee_succeeds(rbf_node, dest_address):
128128
def test_nonrbf_bumpfee_fails(peer_node, dest_address):
129129
# cannot replace a non RBF transaction (from node which did not enable RBF)
130130
not_rbfid = create_fund_sign_send(peer_node, {dest_address: 0.00090000})
131-
assert_raises_message(JSONRPCException, "not BIP 125 replaceable", peer_node.bumpfee, not_rbfid)
131+
assert_raises_jsonrpc(-4, "not BIP 125 replaceable", peer_node.bumpfee, not_rbfid)
132132

133133

134134
def test_notmine_bumpfee_fails(rbf_node, peer_node, dest_address):
@@ -148,7 +148,7 @@ def test_notmine_bumpfee_fails(rbf_node, peer_node, dest_address):
148148
signedtx = rbf_node.signrawtransaction(rawtx)
149149
signedtx = peer_node.signrawtransaction(signedtx["hex"])
150150
rbfid = rbf_node.sendrawtransaction(signedtx["hex"])
151-
assert_raises_message(JSONRPCException, "Transaction contains inputs that don't belong to this wallet",
151+
assert_raises_jsonrpc(-4, "Transaction contains inputs that don't belong to this wallet",
152152
rbf_node.bumpfee, rbfid)
153153

154154

@@ -159,7 +159,7 @@ def test_bumpfee_with_descendant_fails(rbf_node, rbf_node_address, dest_address)
159159
tx = rbf_node.createrawtransaction([{"txid": parent_id, "vout": 0}], {dest_address: 0.00020000})
160160
tx = rbf_node.signrawtransaction(tx)
161161
txid = rbf_node.sendrawtransaction(tx["hex"])
162-
assert_raises_message(JSONRPCException, "Transaction has descendants in the wallet", rbf_node.bumpfee, parent_id)
162+
assert_raises_jsonrpc(-8, "Transaction has descendants in the wallet", rbf_node.bumpfee, parent_id)
163163

164164

165165
def test_small_output_fails(rbf_node, dest_address):
@@ -174,7 +174,7 @@ def test_small_output_fails(rbf_node, dest_address):
174174
Decimal("0.00100000"),
175175
{dest_address: 0.00080000,
176176
get_change_address(rbf_node): Decimal("0.00010000")})
177-
assert_raises_message(JSONRPCException, "Change output is too small", rbf_node.bumpfee, rbfid, {"totalFee": 20001})
177+
assert_raises_jsonrpc(-4, "Change output is too small", rbf_node.bumpfee, rbfid, {"totalFee": 20001})
178178

179179

180180
def test_dust_to_fee(rbf_node, dest_address):
@@ -209,15 +209,15 @@ def test_rebumping(rbf_node, dest_address):
209209
rbf_node.settxfee(Decimal("0.00001000"))
210210
rbfid = create_fund_sign_send(rbf_node, {dest_address: 0.00090000})
211211
bumped = rbf_node.bumpfee(rbfid, {"totalFee": 1000})
212-
assert_raises_message(JSONRPCException, "already bumped", rbf_node.bumpfee, rbfid, {"totalFee": 2000})
212+
assert_raises_jsonrpc(-4, "already bumped", rbf_node.bumpfee, rbfid, {"totalFee": 2000})
213213
rbf_node.bumpfee(bumped["txid"], {"totalFee": 2000})
214214

215215

216216
def test_rebumping_not_replaceable(rbf_node, dest_address):
217217
# check that re-bumping a non-replaceable bump tx fails
218218
rbfid = create_fund_sign_send(rbf_node, {dest_address: 0.00090000})
219219
bumped = rbf_node.bumpfee(rbfid, {"totalFee": 10000, "replaceable": False})
220-
assert_raises_message(JSONRPCException, "Transaction is not BIP 125 replaceable", rbf_node.bumpfee, bumped["txid"],
220+
assert_raises_jsonrpc(-4, "Transaction is not BIP 125 replaceable", rbf_node.bumpfee, bumped["txid"],
221221
{"totalFee": 20000})
222222

223223

@@ -268,7 +268,7 @@ def test_bumpfee_metadata(rbf_node, dest_address):
268268
def test_locked_wallet_fails(rbf_node, dest_address):
269269
rbfid = create_fund_sign_send(rbf_node, {dest_address: 0.00090000})
270270
rbf_node.walletlock()
271-
assert_raises_message(JSONRPCException, "Please enter the wallet passphrase with walletpassphrase first.",
271+
assert_raises_jsonrpc(-13, "Please enter the wallet passphrase with walletpassphrase first.",
272272
rbf_node.bumpfee, rbfid)
273273

274274

@@ -315,9 +315,7 @@ def submit_block_with_tx(node, tx):
315315
block.rehash()
316316
block.hashMerkleRoot = block.calc_merkle_root()
317317
block.solve()
318-
error = node.submitblock(bytes_to_hex_str(block.serialize(True)))
319-
if error is not None:
320-
raise Exception(error)
318+
node.submitblock(bytes_to_hex_str(block.serialize(True)))
321319
return block
322320

323321

qa/rpc-tests/fundrawtransaction.py

Lines changed: 6 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,7 @@ def run_test(self):
182182
dec_tx = self.nodes[2].decoderawtransaction(rawtx)
183183
assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
184184

185-
try:
186-
self.nodes[2].fundrawtransaction(rawtx, {'foo': 'bar'})
187-
raise AssertionError("Accepted invalid option foo")
188-
except JSONRPCException as e:
189-
assert("Unexpected key foo" in e.error['message'])
190-
185+
assert_raises_jsonrpc(-3, "Unexpected key foo", self.nodes[2].fundrawtransaction, rawtx, {'foo':'bar'})
191186

192187
############################################################
193188
# test a fundrawtransaction with an invalid change address #
@@ -200,12 +195,7 @@ def run_test(self):
200195
dec_tx = self.nodes[2].decoderawtransaction(rawtx)
201196
assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
202197

203-
try:
204-
self.nodes[2].fundrawtransaction(rawtx, {'changeAddress': 'foobar'})
205-
raise AssertionError("Accepted invalid bitcoin address")
206-
except JSONRPCException as e:
207-
assert("changeAddress must be a valid bitcoin address" in e.error['message'])
208-
198+
assert_raises_jsonrpc(-5, "changeAddress must be a valid bitcoin address", self.nodes[2].fundrawtransaction, rawtx, {'changeAddress':'foobar'})
209199

210200
############################################################
211201
# test a fundrawtransaction with a provided change address #
@@ -219,12 +209,7 @@ def run_test(self):
219209
assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
220210

221211
change = self.nodes[2].getnewaddress()
222-
try:
223-
rawtxfund = self.nodes[2].fundrawtransaction(rawtx, {'changeAddress': change, 'changePosition': 2})
224-
except JSONRPCException as e:
225-
assert('changePosition out of bounds' == e.error['message'])
226-
else:
227-
assert(False)
212+
assert_raises_jsonrpc(-8, "changePosition out of bounds", self.nodes[2].fundrawtransaction, rawtx, {'changeAddress':change, 'changePosition':2})
228213
rawtxfund = self.nodes[2].fundrawtransaction(rawtx, {'changeAddress': change, 'changePosition': 0})
229214
dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
230215
out = dec_tx['vout'][0]
@@ -333,12 +318,7 @@ def run_test(self):
333318
rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
334319
dec_tx = self.nodes[2].decoderawtransaction(rawtx)
335320

336-
try:
337-
rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
338-
raise AssertionError("Spent more than available")
339-
except JSONRPCException as e:
340-
assert("Insufficient" in e.error['message'])
341-
321+
assert_raises_jsonrpc(-4, "Insufficient funds", self.nodes[2].fundrawtransaction, rawtx)
342322

343323
############################################################
344324
#compare fee of a standard pubkeyhash transaction
@@ -494,21 +474,13 @@ def run_test(self):
494474
rawTx = self.nodes[1].createrawtransaction(inputs, outputs)
495475
# fund a transaction that requires a new key for the change output
496476
# creating the key must be impossible because the wallet is locked
497-
try:
498-
fundedTx = self.nodes[1].fundrawtransaction(rawTx)
499-
raise AssertionError("Wallet unlocked without passphrase")
500-
except JSONRPCException as e:
501-
assert('Keypool ran out' in e.error['message'])
477+
assert_raises_jsonrpc(-4, "Insufficient funds", self.nodes[1].fundrawtransaction, rawtx)
502478

503479
#refill the keypool
504480
self.nodes[1].walletpassphrase("test", 100)
505481
self.nodes[1].walletlock()
506482

507-
try:
508-
self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1.2)
509-
raise AssertionError("Wallet unlocked without passphrase")
510-
except JSONRPCException as e:
511-
assert('walletpassphrase' in e.error['message'])
483+
assert_raises_jsonrpc(-13, "walletpassphrase", self.nodes[1].sendtoaddress, self.nodes[0].getnewaddress(), 1.2)
512484

513485
oldBalance = self.nodes[0].getbalance()
514486

qa/rpc-tests/importprunedfunds.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,7 @@ def run_test(self):
7676
self.sync_all()
7777

7878
#Import with no affiliated address
79-
try:
80-
self.nodes[1].importprunedfunds(rawtxn1, proof1)
81-
except JSONRPCException as e:
82-
assert('No addresses' in e.error['message'])
83-
else:
84-
assert(False)
79+
assert_raises_jsonrpc(-5, "No addresses", self.nodes[1].importprunedfunds, rawtxn1, proof1)
8580

8681
balance1 = self.nodes[1].getbalance("", 0, True)
8782
assert_equal(balance1, Decimal(0))
@@ -112,12 +107,7 @@ def run_test(self):
112107
assert_equal(address_info['ismine'], True)
113108

114109
#Remove transactions
115-
try:
116-
self.nodes[1].removeprunedfunds(txnid1)
117-
except JSONRPCException as e:
118-
assert('does not exist' in e.error['message'])
119-
else:
120-
assert(False)
110+
assert_raises_jsonrpc(-8, "Transaction does not exist in wallet.", self.nodes[1].removeprunedfunds, txnid1)
121111

122112
balance1 = self.nodes[1].getbalance("*", 0, True)
123113
assert_equal(balance1, Decimal('0.075'))

qa/rpc-tests/nodehandling.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,13 @@ def run_test(self):
2929
assert_equal(len(self.nodes[2].listbanned()), 0)
3030
self.nodes[2].setban("127.0.0.0/24", "add")
3131
assert_equal(len(self.nodes[2].listbanned()), 1)
32-
try:
33-
self.nodes[2].setban("127.0.0.1", "add") #throws exception because 127.0.0.1 is within range 127.0.0.0/24
34-
except:
35-
pass
32+
# This will throw an exception because 127.0.0.1 is within range 127.0.0.0/24
33+
assert_raises_jsonrpc(-23, "IP/Subnet already banned", self.nodes[2].setban, "127.0.0.1", "add")
34+
# This will throw an exception because 127.0.0.1/42 is not a real subnet
35+
assert_raises_jsonrpc(-30, "Error: Invalid IP/Subnet", self.nodes[2].setban, "127.0.0.1/42", "add")
3636
assert_equal(len(self.nodes[2].listbanned()), 1) #still only one banned ip because 127.0.0.1 is within the range of 127.0.0.0/24
37-
try:
38-
self.nodes[2].setban("127.0.0.1", "remove")
39-
except:
40-
pass
37+
# This will throw an exception because 127.0.0.1 was not added above
38+
assert_raises_jsonrpc(-30, "Error: Unban failed", self.nodes[2].setban, "127.0.0.1", "remove")
4139
assert_equal(len(self.nodes[2].listbanned()), 1)
4240
self.nodes[2].setban("127.0.0.0/24", "remove")
4341
assert_equal(len(self.nodes[2].listbanned()), 0)

qa/rpc-tests/p2p-acceptblock.py

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -197,11 +197,8 @@ def run_test(self):
197197
assert_equal(x['status'], "headers-only")
198198

199199
# But this block should be accepted by node0 since it has more work.
200-
try:
201-
self.nodes[0].getblock(blocks_h3[0].hash)
202-
print("Unrequested more-work block accepted from non-whitelisted peer")
203-
except:
204-
raise AssertionError("Unrequested more work block was not processed")
200+
self.nodes[0].getblock(blocks_h3[0].hash)
201+
print("Unrequested more-work block accepted from non-whitelisted peer")
205202

206203
# Node1 should have accepted and reorged.
207204
assert_equal(self.nodes[1].getblockcount(), 3)
@@ -225,26 +222,17 @@ def run_test(self):
225222
tips[j] = next_block
226223

227224
time.sleep(2)
228-
for x in all_blocks:
229-
try:
230-
self.nodes[0].getblock(x.hash)
231-
if x == all_blocks[287]:
232-
raise AssertionError("Unrequested block too far-ahead should have been ignored")
233-
except:
234-
if x == all_blocks[287]:
235-
print("Unrequested block too far-ahead not processed")
236-
else:
237-
raise AssertionError("Unrequested block with more work should have been accepted")
225+
# Blocks 1-287 should be accepted, block 288 should be ignored because it's too far ahead
226+
for x in all_blocks[:-1]:
227+
self.nodes[0].getblock(x.hash)
228+
assert_raises_jsonrpc(-1, "Block not found on disk", self.nodes[0].getblock, all_blocks[-1].hash)
238229

239230
headers_message.headers.pop() # Ensure the last block is unrequested
240231
white_node.send_message(headers_message) # Send headers leading to tip
241232
white_node.send_message(msg_block(tips[1])) # Now deliver the tip
242-
try:
243-
white_node.sync_with_ping()
244-
self.nodes[1].getblock(tips[1].hash)
245-
print("Unrequested block far ahead of tip accepted from whitelisted peer")
246-
except:
247-
raise AssertionError("Unrequested block from whitelisted peer not accepted")
233+
white_node.sync_with_ping()
234+
self.nodes[1].getblock(tips[1].hash)
235+
print("Unrequested block far ahead of tip accepted from whitelisted peer")
248236

249237
# 5. Test handling of unrequested block on the node that didn't process
250238
# Should still not be processed (even though it has a child that has more

qa/rpc-tests/pruning.py

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -184,11 +184,8 @@ def reorg_test(self):
184184

185185
def reorg_back(self):
186186
# Verify that a block on the old main chain fork has been pruned away
187-
try:
188-
self.nodes[2].getblock(self.forkhash)
189-
raise AssertionError("Old block wasn't pruned so can't test redownload")
190-
except JSONRPCException as e:
191-
print("Will need to redownload block",self.forkheight)
187+
assert_raises_jsonrpc(-1, "Block not available (pruned data)", self.nodes[2].getblock, self.forkhash)
188+
print("Will need to redownload block",self.forkheight)
192189

193190
# Verify that we have enough history to reorg back to the fork point
194191
# Although this is more than 288 blocks, because this chain was written more recently
@@ -233,7 +230,7 @@ def manual_test(self, node_number, use_timestamp):
233230
# at this point, node has 995 blocks and has not yet run in prune mode
234231
node = self.nodes[node_number] = start_node(node_number, self.options.tmpdir, ["-debug=0"], timewait=900)
235232
assert_equal(node.getblockcount(), 995)
236-
assert_raises_message(JSONRPCException, "not in prune mode", node.pruneblockchain, 500)
233+
assert_raises_jsonrpc(-1, "not in prune mode", node.pruneblockchain, 500)
237234
self.stop_node(node_number)
238235

239236
# now re-start in manual pruning mode
@@ -265,14 +262,14 @@ def has_block(index):
265262
return os.path.isfile(self.options.tmpdir + "/node{}/regtest/blocks/blk{:05}.dat".format(node_number, index))
266263

267264
# should not prune because chain tip of node 3 (995) < PruneAfterHeight (1000)
268-
assert_raises_message(JSONRPCException, "Blockchain is too short for pruning", node.pruneblockchain, height(500))
265+
assert_raises_jsonrpc(-1, "Blockchain is too short for pruning", node.pruneblockchain, height(500))
269266

270267
# mine 6 blocks so we are at height 1001 (i.e., above PruneAfterHeight)
271268
node.generate(6)
272269
assert_equal(node.getblockchaininfo()["blocks"], 1001)
273270

274271
# negative heights should raise an exception
275-
assert_raises_message(JSONRPCException, "Negative", node.pruneblockchain, -10)
272+
assert_raises_jsonrpc(-8, "Negative", node.pruneblockchain, -10)
276273

277274
# height=100 too low to prune first block file so this is a no-op
278275
prune(100)
@@ -318,25 +315,19 @@ def has_block(index):
318315
def wallet_test(self):
319316
# check that the pruning node's wallet is still in good shape
320317
print("Stop and start pruning node to trigger wallet rescan")
321-
try:
322-
self.stop_node(2)
323-
start_node(2, self.options.tmpdir, ["-debug=1","-prune=550"])
324-
print("Success")
325-
except Exception as detail:
326-
raise AssertionError("Wallet test: unable to re-start the pruning node")
318+
self.stop_node(2)
319+
start_node(2, self.options.tmpdir, ["-debug=1","-prune=550"])
320+
print("Success")
327321

328322
# check that wallet loads loads successfully when restarting a pruned node after IBD.
329323
# this was reported to fail in #7494.
330324
print ("Syncing node 5 to test wallet")
331325
connect_nodes(self.nodes[0], 5)
332326
nds = [self.nodes[0], self.nodes[5]]
333327
sync_blocks(nds, wait=5, timeout=300)
334-
try:
335-
self.stop_node(5) #stop and start to trigger rescan
336-
start_node(5, self.options.tmpdir, ["-debug=1","-prune=550"])
337-
print ("Success")
338-
except Exception as detail:
339-
raise AssertionError("Wallet test: unable to re-start node5")
328+
self.stop_node(5) #stop and start to trigger rescan
329+
start_node(5, self.options.tmpdir, ["-debug=1","-prune=550"])
330+
print ("Success")
340331

341332
def run_test(self):
342333
print("Warning! This test requires 4GB of disk space and takes over 30 mins (up to 2 hours)")

0 commit comments

Comments
 (0)