New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Return a more informative error message when trying to spend coinbase; select non-coinbase inputs when sending to a transparent output if needed #1431
Conversation
vector<COutput> vAllCoins; | ||
AvailableCoins(vAllCoins, true, coinControl, false, true); | ||
fOnlyCoinbaseCoinsRet = vAllCoins.size()>0; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code is correct but confusing. I suggest adding a comment:
// fOnlyCoinbaseCoinsRet should be true when there are coinbase coins
// available, but no non-coinbase coins.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I read the code correctly it has this behavior:
- If all my coins are coinbases, I'll get the error message "Coinbase funds can only be sent to a zaddr".
- Otherwise, (when I have some coinbases and some non-coinbases, but the non-coinbases are insufficient) I'll get the error "Insufficient funds."
This means that in the case where my non-coinbases aren't enough, but my non-coinbases plus my coinbases would be, then I still get the error "Insufficient funds." Should that be a separate case, i.e. "You have sufficient funds, but coinbases must be spent to a zaddr first"?
There might be TOCTTOU bug: the wallet lock is acquired inside AvailableCoins()
so between the first call and the second, and the wallet state might change in between. fOnlyCoinbaseCoinsRet
would be set incorrectly if new non-coinbase coins were acquired. I'm not sure if that matters.
{ | ||
strFailReason = _("Insufficient funds"); | ||
if (fOnlyCoinbaseCoins) { | ||
strFailReason =_("Coinbase funds can only be sent to a zaddr"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: space after =
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
Code is okay, but it definitely needs a unit test of |
@daira I spent fair bit of time trying to add a unit test for AvailableCoins. Created a chain of transactions, with valid UTXOs, adding them to a wallet, but could not get AvailableCoins to see those UTXOs as being available for selection. At this moment in time I can only provide results from empirical testing on testnet. |
@nathan-at-least please comment re: testing strategy. |
@bitcartel: the fact that your unit test isn't working could tell us something interesting about the wallet, so keep that code around. (In fact, if the I'd accept any other kind of automated test, such as a case in I'm not comfortable merging this with only manual testing. |
@daira @nathan @str4d I'm fairly comfortable to have this merged because:
Note: To write a specific case we will need to create a new qa harness which use new chain parameters, like regtest, but which enforce coinbase validity. If we simply update regtest params, most of the existing qa tests will fail. |
This needs review and see comment above. |
@nathan See review comment above. Ack? |
Have not had time to review. Up to @nathan-at-least whether he is happy to include this tomorrow if it gets review. |
@bitcartel wrong nathan ;) |
IIUC, there is at least one |
@nathan-at-least Kind of. I have to manually:
|
I see, so the process is still manual but the manual part has been reduced to tweaking a single flag and rebuilding then running just that one test... Let's postpone this until rc1 and ensure we have a fully automated test of any type running in our CI. (As an aside, I'd much rather have a localized unit test than a new test harness with different chain params... How feasible is that?) |
ACK, postponing. |
Summary:
|
We can't easily unit test. ;-) Seriously though: it sounds like unit testing would require a lot of work, but it would be simpler to create a new fourth chain params mode, and a new harness just like However, if this is the only thing we can get working for rc1, then that's what we'll have to do. Longer term, if we pay the admittedly heavy up front cost of refactoring systems so that we can mock them out, then eventually we can have just a single mode. This would replace a bunch of runtime branches in application code with consistent (though perhaps verbose) test mocking. Mocing is the systematic use branches or compile-time conditionals which are quarantined in our unittest binary and not present on mainnet. That's the world I want to move towards. It may very well be that rewriting the wallet code from scratch would be less effort than refactoring the existing code to be mockable. So if that's the case, let's seriously think about it for our longer term plans. |
-1 on any fourth chainparams mode. |
121218b
to
9bbc4d6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK modulo possible TOCTTOU bug
vector<COutput> vAllCoins; | ||
AvailableCoins(vAllCoins, true, coinControl, false, true); | ||
fOnlyCoinbaseCoinsRet = vAllCoins.size()>0; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I read the code correctly it has this behavior:
- If all my coins are coinbases, I'll get the error message "Coinbase funds can only be sent to a zaddr".
- Otherwise, (when I have some coinbases and some non-coinbases, but the non-coinbases are insufficient) I'll get the error "Insufficient funds."
This means that in the case where my non-coinbases aren't enough, but my non-coinbases plus my coinbases would be, then I still get the error "Insufficient funds." Should that be a separate case, i.e. "You have sufficient funds, but coinbases must be spent to a zaddr first"?
There might be TOCTTOU bug: the wallet lock is acquired inside AvailableCoins()
so between the first call and the second, and the wallet state might change in between. fOnlyCoinbaseCoinsRet
would be set incorrectly if new non-coinbase coins were acquired. I'm not sure if that matters.
9bbc4d6
to
3f34d62
Compare
Rebased, added test, push -f. Thanks @defuse for comments. (1) I've added a more fine-grained error message, per your suggestion, of "Insufficient funds, coinbase funds can only be spent after they have been sent to a zaddr". (2) Regarding lock behaviour, its inherited from upstream and changed by this commit. The wallet lock is a recursive lock and the caller, CWallet::CreateTransaction, already obtains it, so there shouldn't be any issue. @daira @nathan-at-least Added qa rpc test to test all three conditions (1) can't send coinbase utxos to taddr (2) insufficient funds unless coinbase utxos are sent to zaddr (and then to taddr) (3) insufficient funds even when including coinbase utxos. The test was made possible by getting the qa rpc test to pass a runtime parameter |
0d6650b
to
a2ba409
Compare
Note that a third party needs this merged asap for their integration work with zcashd. |
Although I think there's still no unit test of |
I'll review in the morning. |
except JSONRPCException,e: | ||
errorString = e.error['message'] | ||
print "error: ", errorString | ||
assert_equal("Insufficient funds, coinbase funds can only be spent after they have been sent to a zaddr" in errorString, True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this test need another case in which the send succeeds because there are enough non-coinbase funds, but the coin selection prior to this PR would have chosen a coinbase input?
(Sorry to raise this after already having ACKed; I know we're short on time.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I force pushed this:
+ # Send will succeed because the balance of non-coinbase utxos is 20.0
+ errorString = ""
+ try:
+ self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 19.0)
+ except JSONRPCException,e:
+ errorString = e.error['message']
+ assert_equal("" in errorString, True)
+
+ self.nodes[1].generate(10)
+ self.sync_all()
+
+ # check balance
+ assert_equal(self.nodes[2].getbalance(), Decimal('19'))
+
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"" in errorString
is always True. I think you want assert(False)
in the except: clause. ACK if that is fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@daira Ok, done.
a760cc1
to
f3240a3
Compare
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 19.0) | ||
except JSONRPCException: | ||
assert(False) | ||
assert_equal("" in errorString, True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can/should also remove errorString = ""
and the assert_equal
, since they have no effect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -336,6 +336,11 @@ CChainParams &Params(CBaseChainParams::Network network) { | |||
void SelectParams(CBaseChainParams::Network network) { | |||
SelectBaseParams(network); | |||
pCurrentParams = &Params(network); | |||
|
|||
// Some python qa rpc tests need to enforce the coinbase consensue rule |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consensus
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
f3240a3
to
cdf4f98
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ut(ACK+cov) mod comment
@@ -0,0 +1,126 @@ | |||
#!/usr/bin/env python2 | |||
# Copyright (c) 2014 The Bitcoin Core developers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# Copyright (c) 2016 The Zcash developers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
ut(ACK+cov) |
…ash#1373). Extra parameter added to AvailableCoins to include or exclude Coinbase coins. SelectCoins, used for sending taddr->taddr, will exclude Coinbase coins. Added qa rpc test and a runtime parameter -regtestprotectcoinbase to enforce the coinbase->zaddr consensus rule in regtest mode.
cdf4f98
to
2b1cda3
Compare
📌 Commit 2b1cda3 has been approved by |
…itcartel Return a more informative error message when trying to spend coinbase; select non-coinbase inputs when sending to a transparent output if needed For #1373 and #1519 Code change: - Extra parameter added to AvailableCoins to include or exclude Coinbase coins. Default value of parameter is 'true' as current behaviour is to include Coinbase coins. - SelectCoins, used for sending taddr->taddr, will now exclude Coinbase coins. Unit test: Tried to write a test to focus on the extra parameter added to AvailableCoins but could not. Empirical testing on Testnet: Current behaviour is that upstream RPC commands sendfrom and sendtoaddress try to spend coinbase coins returned by AvailableCoins. So the user will see: ``` ./zcash-cli sendtoaddress mrEGRmGJhmwAa4MQjzGd86ry63vrvovu9b 1000.0 error: {"code":-6,"message":"Insufficient funds"} ./zcash-cli sendtoaddress mrEGRmGJhmwAa4MQjzGd86ry63vrvovu9b 0.00003000 error: {"code":-4,"message":"Error: The transaction was rejected! This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."} ./zcash-cli sendfrom "" mrEGRmGJhmwAa4MQjzGd86ry63vrvovu9b 0.00003000 error: {"code":-4,"message":"Error: The transaction was rejected! This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."} ``` After fix is applied: ``` ./zcash-cli sendtoaddress mrEGRmGJhmwAa4MQjzGd86ry63vrvovu9b 1000.0 error: {"code":-6,"message":"Insufficient funds"} ./zcash-cli sendtoaddress mrEGRmGJhmwAa4MQjzGd86ry63vrvovu9b 0.00003000 error: {"code":-4,"message":"Coinbase funds can only be sent to a zaddr"} ``` When non-coinbase UTXOs exist, they will now be selected and used: ``` ./zcash-cli z_sendmany tnPJZHeVxegCg91utaquBRPEDBGjozfz9iLDHt7zvphFbZdspNgkTVLCGjDcadQBKNyUwKs8pNjDXuEZKrE1aNLpFwHgz4t '[{"address":"mx5fTRhLZwbYE7ZqhAPueZgQGSnwTbdvKU", "amount":0.01}]' ./zcash-cli sendtoaddress mrEGRmGJhmwAa4MQjzGd86ry63vrvovu9b 1000.0 error: {"code":-6,"message":"Insufficient funds"} ./zcash-cli sendtoaddress mrEGRmGJhmwAa4MQjzGd86ry63vrvovu9b 0.00003000 9818e543ac2f689d4ce8b52087607d73fecd771d45d316a1d9db092f0485aff2 ./zcash-cli sendfrom "" mrEGRmGJhmwAa4MQjzGd86ry63vrvovu9b 0.00003000 899f2894823f51f15fc73b5e0871ac943edbe0ff88e1635f86906087b72caf30 ```
☀️ Test successful - zcash |
This was already merged, but yeah my provisional ACK changed into an ACK. |
For #1373 and #1519
Code change:
Unit test:
Tried to write a test to focus on the extra parameter added to AvailableCoins but could not.
Empirical testing on Testnet:
Current behaviour is that upstream RPC commands sendfrom and sendtoaddress try to spend coinbase coins returned by AvailableCoins. So the user will see:
After fix is applied:
When non-coinbase UTXOs exist, they will now be selected and used: