Skip to content
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

Merged
merged 4 commits into from
Dec 20, 2016

Conversation

instagibbs
Copy link
Member

@instagibbs instagibbs commented Dec 2, 2016

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.

@sipa
Copy link
Member

sipa commented Dec 2, 2016

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.

@gmaxwell
Copy link
Contributor

gmaxwell commented Dec 2, 2016

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.

@gmaxwell
Copy link
Contributor

gmaxwell commented Dec 2, 2016

FWIW, I think this should be backported.

@instagibbs
Copy link
Member Author

instagibbs commented Dec 2, 2016

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);
Copy link
Member

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.)

@instagibbs instagibbs changed the title Don't consider coins available if too many ancestors in mempool Prefer coins that have fewer ancestors, sanity check txn before ATMP Dec 2, 2016
@instagibbs
Copy link
Member Author

Updated the pull request to new design discussed on IRC.

Copy link
Member

@sdaftuar sdaftuar left a 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;
Copy link
Member

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)
Copy link
Member

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?

Copy link
Member Author

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
Copy link
Member

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");
Copy link
Member

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...

@instagibbs instagibbs force-pushed the toolong branch 2 times, most recently from 4695526 to ede1939 Compare December 2, 2016 22:13
@instagibbs
Copy link
Member Author

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)
Copy link
Member

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?

@gmaxwell
Copy link
Contributor

gmaxwell commented Dec 4, 2016

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.

@instagibbs instagibbs force-pushed the toolong branch 2 times, most recently from 05ae82b to 1fc42a1 Compare December 4, 2016 19:39
@instagibbs
Copy link
Member Author

instagibbs commented Dec 4, 2016

Couple of fixes/changes:

if (pcoin->InMempool() && pcoin->GetMempoolAncestorCount() > nMaxAncestors)

is now

if (pcoin->InMempool() && pcoin->GetMempoolAncestorCount() >= nMaxAncestors)

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 :)

@gmaxwell
Copy link
Contributor

gmaxwell commented Dec 5, 2016

Current behavior is horrendous indeed. Thank you for working on this.

@sdaftuar
Copy link
Member

sdaftuar commented Dec 5, 2016

@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)
Copy link
Member

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));
Copy link
Member

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.

@instagibbs
Copy link
Member Author

@sdaftuar made sense to me, done

@laanwj
Copy link
Member

laanwj commented Dec 6, 2016

but given that you currently have to restart your node in order to rebroadcast

Why is that so? You can't take the tx hex and send it through sendrawtransaction in this case?

@sipa
Copy link
Member

sipa commented Dec 19, 2016

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)
Copy link
Member

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;

@instagibbs
Copy link
Member Author

fixed @sipa nit

@laanwj
Copy link
Member

laanwj commented Dec 19, 2016

Travis doesn't agree with your change, it seems.

@instagibbs
Copy link
Member Author

@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.

Copy link
Contributor

@TheBlueMatt TheBlueMatt left a 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;
Copy link
Contributor

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
Copy link
Contributor

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);
Copy link
Contributor

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);
Copy link
Contributor

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.

@laanwj laanwj merged commit cee1612 into bitcoin:master Dec 20, 2016
laanwj added a commit that referenced this pull request Dec 20, 2016
… 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)
@laanwj
Copy link
Member

laanwj commented Dec 20, 2016

Backported in #9382, removing label

@sipa sipa mentioned this pull request Jan 10, 2017
18 tasks
codablock pushed a commit to codablock/dash that referenced this pull request Jan 18, 2018
…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)
andvgal pushed a commit to energicryptocurrency/gen2-energi that referenced this pull request Jan 6, 2019
…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)
CryptoCentric pushed a commit to absolute-community/absolute that referenced this pull request Feb 26, 2019
…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)
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Sep 8, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.