-
Notifications
You must be signed in to change notification settings - Fork 36.2k
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
Prefer coins that have fewer ancestors, sanity check txn before ATMP #9262
Conversation
There should probably be a function in the place where mempool acceptance is decided (main.cpp now, validation.cpp after The Main Split) to do preliminary checking like thid. |
Probably not for now but when I read this code I wondered if we need an "available balance"-- e.g. the sum of the coins that selectcoins would consider using... so that transaction failures due to this (and spendunconfirmed set off) are more explicable. |
FWIW, I think this should be backported. |
While writing tests I found that this logic somehow is falling through on non-default ancestorcount limit. Debugging. |
@@ -1957,6 +1957,20 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const | |||
if (nDepth == 0 && !pcoin->InMempool()) | |||
continue; | |||
|
|||
LockPoints lp; | |||
CTxMemPoolEntry entry(*pcoin, 0, 0, 0, 0, false, 0, false, 0, lp); |
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.
Since we've already checked that pcoin is in the mempool, we don't need to do any of this -- we can just use the cached values. (I think the only way the calculation you do below can fail is if the limits are violated during a reorg.)
Updated the pull request to new design discussed on IRC. |
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.
Code review ack, just a couple nits. Will test.
@@ -400,6 +400,7 @@ class CWalletTx : public CMerkleTx | |||
bool IsEquivalentTo(const CWalletTx& tx) const; | |||
|
|||
bool InMempool() const; | |||
uint64_t GetMempoolAncestorCount() const; |
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: add documentation to this function that the tx must be in the mempool in order to call?
@@ -2042,6 +2048,9 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int | |||
if (output.nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? nConfMine : nConfTheirs)) | |||
continue; | |||
|
|||
if (pcoin->InMempool() && pcoin->GetMempoolAncestorCount() > nMaxAncestors) |
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.
Perhaps worth a comment here that we're assuming AvailableCoins()
has already filtered out any unconfirmed and not-in-mempool 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.
Inequality here is wrong, should be >=
I believe. This will never fire as-is. I should be able to easily test this fix.
@@ -2558,6 +2558,21 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt | |||
} | |||
} | |||
|
|||
// Lastly, ensure it doesn't have too many ancestors |
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: perhaps this comment should be "Lastly, ensure this tx will pass the mempool's chain limits"
size_t nLimitDescendantSize = GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000; | ||
std::string errString; | ||
if (!mempool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, nLimitDescendants, nLimitDescendantSize, errString)) { | ||
strFailReason = _("Transaction has too long of a mempool chain"); |
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 errString filled in by CMPA might be useful for debugging issues (eg a LogPrintf() so that someone debugging could inspect), though i see we don't log any info about the transaction we tried to generate, so maybe not worth it...
4695526
to
ede1939
Compare
I also think this is good for backport. |
@@ -2042,6 +2048,9 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int | |||
if (output.nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? nConfMine : nConfTheirs)) | |||
continue; | |||
|
|||
if (pcoin->GetMempoolAncestorCount() > nMaxAncestors) |
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 think you need the pcoin->InMempool() check you had before, in case you're looking at in-chain coins?
Looks good! -- so, I think the additional test in "Don't return success" should be controlled by an option, since the failure is only temporary (at least once we fix the rebroadcast bug) and many callers handle failure to create a transaction poorly. I might even go as far as to suggest that this option be set in 0.13.2 to continue to create the transaction because that would be less of an API change. Perhaps @sdaftuar or @laanwj has an opinion on that? Other than that, utACK. |
05ae82b
to
1fc42a1
Compare
Couple of fixes/changes:
is now
so it actually filters correctly on the very last call of SelectCoinsMinConf. I also revamped the tests to catch the various cases. @gmaxwell we'll have to catch that logic in two places, SelectCoinsMinConf(at the last instance of it) and at the end of CreateTransaction. I suppose once rebroadcasting is fixed, we can set the default to "false" and let it take care of it. I'll wait to see what others are thinking. I think current behavior is horrendous :) |
Current behavior is horrendous indeed. Thank you for working on this. |
@gmaxwell No strong opinion on the issue of default behavior for 0.13.2, but given that you currently have to restart your node in order to rebroadcast, I'd lean towards making the default behavior be to enforce this new restriction, rather than create a tx that doesn't make it to the mempool. |
@@ -2042,6 +2048,9 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int | |||
if (output.nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? nConfMine : nConfTheirs)) | |||
continue; | |||
|
|||
if (pcoin->InMempool() && pcoin->GetMempoolAncestorCount() >= nMaxAncestors) |
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.
@morcos and I were discussing, I think this would make more sense to limit based on both ancestor and descendant count, something like:
if (pcoin->InMempool() && max(pcoin->GetMempoolAncestorCount(), pcoin->GetMempoolDescendantCount()) >= nMaxChain)
continue;
Otherwise, if you have an unspent output with 1 ancestor but 25 descendants, you (needlessly) could fail on one of the early calls.
SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 1, 1, 0, vCoins, setCoinsRet, nValueRet) || | ||
(bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, nLimitAncestors/4, vCoins, setCoinsRet, nValueRet)) || | ||
(bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, nLimitAncestors/2, vCoins, setCoinsRet, nValueRet)) || | ||
(bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, nLimitAncestors, vCoins, setCoinsRet, nValueRet)); |
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.
Instead of N/4, N/2, and N for the ancestor/chain limit passed in to SCMC, how about: 2, 4, N/2, N? (And set N = min(limitancestorcount, limitdescendantcount), assuming we go with ancestor and descendant limiting as I mentioned in another comment.) This way we first try utxo's from transactions that have no ancestors or descendants, which should provide a strong preference for not creating long chains.
@sdaftuar made sense to me, done |
Why is that so? You can't take the tx hex and send it through |
utACK 5882c09, with one nit. |
|
||
bool CTxMemPool::TransactionWithinChainLimit(const uint256& txid, size_t chainLimit) const { | ||
LOCK(cs); | ||
if (exists(txid) && std::max(mapTx.find(txid)->GetCountWithAncestors(), mapTx.find(txid)->GetCountWithDescendants()) >= chainLimit) |
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're doing 3 map lookups here. What about
auto it = mapTx.find(txid);
return it != mapTx.end() && it->GetCountWithAncestors() < chainLimit &&
it->GetCountWithDescendants() < chainLimit;
fixed @sipa nit |
Travis doesn't agree with your change, it seems. |
@laanwj yes the logic made no sense, it will reject anything not in mempool rather than anything in mempool with too long a chain. 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.
utACK ff79329ccfe3ee4387b9de72d82431c7b8132272 some comments but none worth delaying 0.13.2 for.
@@ -685,7 +687,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface | |||
* completion the coin set and corresponding actual target value is | |||
* assembled | |||
*/ | |||
bool SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, std::vector<COutput> vCoins, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet) const; | |||
bool SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, uint64_t nMaxAncestors, std::vector<COutput> vCoins, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet) const; |
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: you forgot to const-ify the first two ints which you did in the .cpp
@@ -2545,6 +2567,21 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt | |||
} | |||
} | |||
|
|||
if (GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { | |||
// Lastly, ensure this tx will pass the mempool's chain limits |
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.
Agreed, though in the interest of backporting and getting 0.13.2 out cleaing up the interface here probably isnt the priority. Lets do it in another pr.
if (GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { | ||
// Lastly, ensure this tx will pass the mempool's chain limits | ||
LockPoints lp; | ||
CTxMemPoolEntry entry(txNew, 0, 0, 0, 0, false, 0, false, 0, lp); |
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.
Note that by not filling in the sigOpsCost here we dont have that information for the virtual transaction size, so you could still run over the limit here. I don't think its a big deal, but we should fix when we clean things up in 0.14 as suggested two lines up.
return true; | ||
auto it = mapTx.find(txid); | ||
return it == mapTx.end() || (it->GetCountWithAncestors() < chainLimit && | ||
it->GetCountWithDescendants() < chainLimit); |
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.
This really should be indented.
… before ATMP cee1612 reduce number of lookups in TransactionWithinChainLimit (Gregory Sanders) af9bedb Test for fix of txn chaining in wallet (Gregory Sanders) 5882c09 CreateTransaction: Don't return success with too-many-ancestor txn (Gregory Sanders) 0b2294a SelectCoinsMinConf: Prefer coins with fewer ancestors (Gregory Sanders)
Backported in #9382, removing label |
…eck txn before ATMP cee1612 reduce number of lookups in TransactionWithinChainLimit (Gregory Sanders) af9bedb Test for fix of txn chaining in wallet (Gregory Sanders) 5882c09 CreateTransaction: Don't return success with too-many-ancestor txn (Gregory Sanders) 0b2294a SelectCoinsMinConf: Prefer coins with fewer ancestors (Gregory Sanders)
…eck txn before ATMP cee1612 reduce number of lookups in TransactionWithinChainLimit (Gregory Sanders) af9bedb Test for fix of txn chaining in wallet (Gregory Sanders) 5882c09 CreateTransaction: Don't return success with too-many-ancestor txn (Gregory Sanders) 0b2294a SelectCoinsMinConf: Prefer coins with fewer ancestors (Gregory Sanders)
…eck txn before ATMP cee1612 reduce number of lookups in TransactionWithinChainLimit (Gregory Sanders) af9bedb Test for fix of txn chaining in wallet (Gregory Sanders) 5882c09 CreateTransaction: Don't return success with too-many-ancestor txn (Gregory Sanders) 0b2294a SelectCoinsMinConf: Prefer coins with fewer ancestors (Gregory Sanders)
This does a few extra possible runs of SelectCoinsMinConf, each with larger allowable mempool ancestor numbers, up to the acceptable mempool limit. As long as each input is not above this value, it passes. This means the sum of the history could actually be larger.
This is why I then catch the transaction at the end of CreateTransaction, and directly check. It simply fails, and does not retry. This logic is gated by a new command line argument
-rejectlongwalletchains
to regain previous behavior. We can most likely remove this check once we have better rebroadcasting systems.