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

Super Duper MemPool Limiter #6470

Closed
wants to merge 9 commits into from
Closed

Conversation

morcos
Copy link
Member

@morcos morcos commented Jul 23, 2015

@sipa @sdaftuar @jtimon @petertodd
OK, this is the best combination of approaches I could put together.

I took #6455 and I removed the floating relay fee commit. I really like that idea, but it needs to be much slower acting I think and not subject to potential abuse. That can be a later improvement.

I added 3 things:

  • Reserved space between the soft cap and hard cap. The soft cap is currently set to 70% of the hard cap. Once the soft cap is hit, you first try to evict as in 6455, but if you fail, you have another chance to get in if you are still under the hard cap. There are 10 "rate zones" between the hard cap and soft cap and the effective minRelayRate to get into the mempool doubles for each additional zone.
  • Any required minRelayFee for your transaction alone is considered inside the StageTrimToSize loop.
  • Periodically (once a second) we use the knowledge that surplus fees over the minRelayRate must have been paid if the size of the mempool is over the soft cap. We use these fees in aggregate to try to trim from the bottom of the mempool. This allows us to aggregate many small high fee transactions to evict a low paying large transaction or long chain.

The reject rates in my test setup have dropped to 0.3% for 30k feerate tx's and 0.05% for 60k feerate tx's. (See other results in #6455).

I made an attempt to tweak the looping parameters in StageTrimToSize to something that I think made sense, but with the contrived test setup, and only one set of simulation data, they are probably best evaluated on the basis of intuition and not relying entirely on the resulting rejection rates.

It turns out the slowest part of StageTrimToSize was GetRand() by a long shot, so I hacked it out, but I'm sure @sipa will want to replace my hack with something nicer.

The code works as is, but could still use some work, but I think its time to get more eyes on this suggestion for a plan forward.

@@ -852,6 +877,12 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
hash.ToString(), nSigOps, MAX_STANDARD_TX_SIGOPS),
REJECT_NONSTANDARD, "bad-txns-too-many-sigops");

// Expire old transactions before trying to replace low-priority ones.
int expired = pool.Expire(GetTime() - GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't - GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60 be inside pool.Expire?, it may become an attribute of the txmempool in the future or something.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer not doing that, and keeping CTxMempool as much as possible a dumb data structure - the decisions about what happens to it (policy?) should stay out of it, IMHO.

EDIT: We're already failing at that pretty badly anyway, it seems, with the feerate index and the trim code inside CTxMempool. Too bad, but disregard this comment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is just as easy to move things from txmempool to the policy dir than it is from main (if not easier, given that main is always the part with more development conflicts [unrelated things in theory conflict in code there]).
But whatever, I guess it will be a new global in main...

@laanwj
Copy link
Member

laanwj commented Jul 24, 2015

re: GetRand slowness, wouldn't insecure_rand() be good enough here?
If you need both fast and cryptographic randomness you'd have to wait for #5885

@sipa
Copy link
Member

sipa commented Jul 24, 2015

I like the approach; I'll review the code in detail soon.

@dgenr8
Copy link
Contributor

dgenr8 commented Jul 25, 2015

The idea that a transaction must be "paid for" when evicted by an unrelated transaction is mistaken. It creates zero incentive for the evicted author to pay more fees in the future. He got what he wanted -- his tx was relayed.

If the decision does not affect the cost, the cost should not affect the decision.

@sipa
Copy link
Member

sipa commented Jul 25, 2015

The assumption is that when a transaction is evicted, it won't be mined, thus it won't have paid anything at all.

@dgenr8
Copy link
Contributor

dgenr8 commented Jul 25, 2015

@sipa In that case, it also received no benefit.

@sipa
Copy link
Member

sipa commented Jul 25, 2015

It got relayed, which has network costs.

The purpose of this payment requirement is to prevent someone from spamming the network by constantly replacing the lowest priority one, and only paying once.

@dgenr8
Copy link
Contributor

dgenr8 commented Jul 25, 2015

For DoS protection, all that's needed is (new fee/kb) - (old fee/kb) >= (minimum fee/kb).

@sipa
Copy link
Member

sipa commented Jul 27, 2015

@dgenr8 No, that would let a small transaction erase a large transaction, making space for several new transactions to be cheaply relayed.


if (!mempool.StageTrimToSize(softcap, entry, stagedelete, nFeesRequired, nFeesDeleted)) {
size_t expsize = mempool.DynamicMemoryUsage() + mempool.GuessDynamicMemoryUsage(entry);
if (expsize > hardcap)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use { } around the then block.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No objection to changing, but just want to understand what the style request is related to this. All then blocks should have braces or only because the else block is multi-line? A single-line then block without braces appears as example code in developer-notes.md.

Copy link
Member

@sipa sipa Jul 27, 2015 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fact is that braces are not necessary in our clang style. And btw, when they're used, the shouldn't be in the next line but in the same line as the if/for/while. I'm happy changing the style, but that's in clang-format.

@morcos
Copy link
Member Author

morcos commented Jul 27, 2015

@jtimon I'd like to add some sanity checking for the command line arguments that are passed in. I assume that should go in init.cpp? Where is the appropriate place for me to store variables such as hard cap, mempool expiry time, etc.. so they aren't recalculated every time.

indexed_transaction_set::nth_index<1>::type::reverse_iterator it = mapTx.get<1>().rbegin();
int fails = 0; // Number of mempool transactions iterated over that were not included in the stage.
int itertotal = 0;
int iterextra = mustTrimAllSize ? 10 : 100; //Allow many more iterations to find large size during SurplusTrim
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mind turning these into arguments, that get passed from the 2 callsites instead?

@sipa
Copy link
Member

sipa commented Jul 27, 2015

Needs rebase.

@dgenr8
Copy link
Contributor

dgenr8 commented Jul 27, 2015

that would let a small transaction erase a large transaction, making space for several new transactions to be cheaply relayed

@sipa That is good. You don't want a 500KB spam-monster tx paying (minimum fee/kb) to require a regular-sized tx to pay 1001 * (minimum fee/kb) to dislodge it. Bad guy could get 1000 little txes relayed anyway by sending them first -- the pay-for-evicted rule creates a race to be first or pay double.

@sdaftuar
Copy link
Member

@dgenr8 While that is a potential problem for optimizing what gets into the mempool, note that this PR is attempting to provide a solution so that many small transactions can be used to evict large transaction packages.

@morcos
Copy link
Member Author

morcos commented Jul 27, 2015

@dgenr8 This conversation has been spread over a couple of PR's and IRC, so I apologize if I'm repeating a prior argument, but I think it would be good to explain the reasoning behind the logic in this pull.

Prior to limiting the mempool (or having RBF implemented), we had protection against bandwidth spamming attacks by requiring all transactions that were relayed to be accepted into the mempool and have either fee or priority. Since those transactions are now in the mempool and can't be recalled, those fees are now at risk for being consumed by being included in a block. This pull isn't meant to provide some new protection against that attack, but is only meant to protect against OOM attacks by ever expanding mempools. However, it must be sure not to break that pre-existing protection.

Once we create a way for transactions to be removed from the mempool, we have to realize that the fees placed on those transactions are no longer at risk. The approach we've been using is to say that (modulo transactions that have already been mined) the mempool must contain enough fees to pay for the relay cost of all transactions that have been historically broadcast whether they are still in the mempool or not. What this prevents is someone replacing or evicting their own transactions "cheaply" and thereby achieving free relay of their original transactions. The fact that there might be a race to get into the mempool before it fills up is a slight aberration that occurs as a one off. We still need a mechanism by which to adjust the minimum relay fee over time in the event that it is too small to dissuade spam.

Your concern about a monster transaction of low fees preventing a small transaction from getting in the mempool is addressed in 4 separate ways in this pull.

  1. Multiple attempts are made to evict transactions, so being unlucky and trying against one large transaction is insufficient to block you.
  2. Some portion of the mempool space is reserved for higher and higher fee transactions. That space is not cheaply fillable with spam and should serve to act as a temporarily higher min relay rate which will keep the relay path open for high fee paying tx's (large or small).
  3. Any tx's occupying the reserve space are periodically looked at in aggregate and used to remove large tx's or chains of tx's from the bottom of the mempool that would otherwise be unevictable.
  4. There is a time based eviction mechanism as a last fall back.

No doubt further improvements are possible, but I think this is a good step in the right direction.

@dgenr8
Copy link
Contributor

dgenr8 commented Jul 28, 2015

@morcos Thanks. I feel the capping part of this pull is too complex, and could be simplifed (a little) by removing the "pay for eviction" idea, which has no good incentive effect.

The person paid is the miner, who doesn't need to be paid ~double to mine the evictor tx. The person who pays is the unlucky evictor. Evictee gets a chance at cheap fees, and others who come after eviction could pay even less, since there might then be extra space for them.

The case where evictor == evictee could be handled explicitly, if it were actually possible to target this kind of replacement with all the factors that affect the selection of the evictees, such as propagation variation, node start time variation, mining, and nodes having different mempool size limits.

@morcos
Copy link
Member Author

morcos commented Jul 28, 2015

I rebased, addressed many of the comments and did some various cleanups in place.

Please note I changed the defaults for -maxmempool and -mempoolexpiry to 500MB and 1 week respectively.

@jtimon
Copy link
Contributor

jtimon commented Jul 29, 2015

@morcos everybody is creating new globals in main, but I hate that. I would at the very least move them to globals/server ot something, but whatever...
It would be much nicer if you make them an attribute of an existing class that is used as a global (say, mempool or globalPolicy). Here's a commit in which you can see what I would like to move towards: jtimon@5a42e27
I've been trying to do something like that since 2014 but unfortunately there's still no right place for introducing new policy options and people keep doing everything in main. Next step is #6068 (still without the preparations in util that you could reuse, let me know if you want me to separate those preparations).

Even if you don't do it "the right way" ( @luke-jr wanted to create a dynamic GUI form for command line options using something like jtimon@5a42e27#diff-01e64f27a2a21a3116825fa22aee0537R30 ), for the case of -maxmempool and -mempoolexpiry, you could at least put them in CTxMempool.
If we want them in policy (no strong opinion), the easiest thing IMO is leaving them as locals for now (even though that's not very efficient) and hide them in CStandardPolicy or the policy estimator later.

But, whatever, there's many things to cleanup already and everybody is doing it wrong, even for new policy options (like fRequireStandard), so why would you spend any "mental power" on putting new things in the right place when you're actually solving an urgent problem?
Do everything in main like everybody else and hopefully it will be cleaned up eventually.

@morcos
Copy link
Member Author

morcos commented Jul 29, 2015

@jtimon OK thanks. Yes I'm going to leave them as locals for now and if we end up adding sanity checking it can just be done separately in init.cpp for now. But I do like your idea of moving them to a policy class. It puts all the logic of what are the reasonable parameters for these arguments in the same place..

@ABISprotocol
Copy link

I have a few questions,

  1. I noted that @morcos stated that he "removed the floating relay fee commit," but is there another commit added that provides some (roughly) equivalent function? It strikes me that having a floating relay fee added has a necessary dynamic effect ~ the purpose of the floating relay, as discussed in Limited mempool + floating relay fee + rejection caching + mempool expiry #6455, is to dampen the effect of the limited memory pool, and make it more constant over time.

  2. Noted from prior remarks I have made in Limited mempool + floating relay fee + rejection caching + mempool expiry #6455 that a mempool is not necessarily needed (e.g., relay nodes would not need to be using a mempool; the wallet nodes, would be using a mempool, as was indicated).

  3. It was my understanding that there was a resolution that "Mempool limiting and dynamic fee determination are superior to a static parameter change" at the end of Restore minimum feerate to 10000 satoshis #6201, which when closed led to Limited mempool + floating relay fee + rejection caching + mempool expiry #6455 and now, to this pull request.

So, why not time based expiration as well as a floating relay fee?  Just curious.

@sipa
Copy link
Member

sipa commented Jul 30, 2015

Testing & benchmarking.

@morcos
Copy link
Member Author

morcos commented Jul 30, 2015

@ABISprotocol, I still believe we need a floating relay fee. I just think it needs to act over a much longer time horizon and be less abusable than the one implemented in the commit I removed.

@ABISprotocol
Copy link

@morcos @sipa In what pull request or issue should I look for the floating relay fee, or is that TBD?

@morcos
Copy link
Member Author

morcos commented Aug 3, 2015

@ABISprotocol This was the commit I removed, sipa@6498673. But I think the correct answer is TBD.

@ABISprotocol
Copy link

@morcos @sipa Please refer to the number of the pull request for the the floating relay fee in this one at such time when it is created so that the discussion / progress on this can be followed. So far I have been following this as follows:
#6201
#6455
I'm assuming there will be another pullreq (with a floating relay fee) TBD, my request is that when it is created, please refer to the number of that pullreq here. Thank you in advance.

@morcos
Copy link
Member Author

morcos commented Aug 5, 2015

Rebased now that #6498 has been merged

@sipa sipa mentioned this pull request Aug 8, 2015
ashleyholman and others added 9 commits August 11, 2015 11:23
Indexes on:
- Tx Hash
- Fee Rate (fee-per-kb)
The mempool will now have a soft cap set below its hard cap.  After it fills up to the soft cap, transactions much first try using StageTrimToSize to evict the amount of size they are adding.  If they fail they can still be let into the mempool if they pass a higher relay minimum.  It doubles 10 times between the soft cap and hard cap.
StageTrimToSize will make several attempts to find a set of transactions it can evict from the mempool to make room for the new transaction.  It should be aware of any required minimum relay fee that needs to be paid for by the new transaction after accounting for the fees of the deleted transactions.
Use reserve space between soft cap and hard cap as a reservoir of surplus fees that have been paid above the minRelayTxFee and occasionally use the aggregate usage there to trim from the bottom of the mempool.
Improve logging
Use insecure_rand in TrimMempool
Tweak logic of TrimMempool
Add occasional larger SurplusTrim.
Bypass eviction on disconnected block txs
Additional SurplusTrim for bypassed size
Acquire locks appropriately
@morcos
Copy link
Member Author

morcos commented Aug 14, 2015

Rebased and squashed various cleanups and small changes into the last commit.

@morcos
Copy link
Member Author

morcos commented Sep 2, 2015

Closing in lieu of #6557, will reopen if we decide for some reason we want the mempool limiting without the descendant package tracking...

@morcos morcos closed this Sep 2, 2015
@ABISprotocol
Copy link

I'm beginning to wonder just how far down the rabbit hole this all goes. And what happened to the floating relay fee bit?

@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.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants