Me and Robby had a lengthy conversation about the gas limit per block and felt that based on a summary of that we could have a good community discussion about this, so I’m posting a summary of what we discussed here and hope we get some input ;);
so it’s a given that we’ll need a gaslimit per block to prevent a DoS attack where 1 block takes (way) too long to process.
this block gaslimit will be so that it’s doable to catch up with a fresh node,
so you should be able to parse the limit of block gaslimit amount of executions in probably < 1min on mediocre hardware so that you can catchup 1 day of blocks in 2.4hrs.
it should probably be even lower, but we’ll benchmark to see what is feasible (1min can contain A LOT of executions).
one thing in case of a DoS attack that is hard to deal with is;
if the block gas limit is reached before the end of parsing all smart contract executions in that block it will be very awkward for the ‘users’ of the later executions, because their transactions be ignored / dropped.
there’s 2 options to deal with it:
- they need to do another bitcoin TX to repeat the execution
- we need to queue all the executions and on the next block parse the queue before any new executions from that block
the latter seems like the only solution that wont make users go insane during a DoS on the block gaslimit.
to draw out how this would work:
gaslimit = 100 XCP
block 1
-------
- TX1 executes for 20 XCP
- TX2 executes for 100 XCP
- TX3 executes for 20 XCP
parsing block:
- TX1 : parsed, gaslimit = 80 XCP
- TX2 : parsed, hits block gaslimit, halted and placed in queue
- TX3 : placed in queue, block gaslimit already hit
block 2
-------
- TX4 executes for 40 XCP
- TX5 executes for 60 XCP
parsing queue:
- TX2 : parsed, gaslimit = 0 XCP
- TX3 : left in queue, block gaslimit already hit
parsing block:
- TX4 : left in queue, block gaslimit already hit
- TX5 : left in queue, block gaslimit already hit
block 3
-------
- TX6 executes 20 XCP
- TX7 executes 20 XCP
parsing queue:
- TX3 : parsed, gaslimit = 80 XCP
- TX4 : parsed, gaslimit = 60 XCP
- TX5 : parsed, gaslimit = 0 XCP
parsing block:
- TX6 : placed in queue, block gaslimit already hit
- TX7 : placed in queue, block gaslimit already hit
block 4
-------
- TX8 executes 40 XCP
parsing queue:
- TX6 : parsed, gaslimit = 80 XCP
- TX7 : parsed, gaslimit = 60 XCP
parsing block:
- TX8 : parsed, gaslimit = 20 XCP
an attacker could queue up a week of executions, however, it’s a lot more transparent than the first option and an attacker willing to spend enough XCP to mount a DoS attack that last hours or even days should be able to do so with option 1 as well, he’d just have to do a bunch of extra bitcoin TXs.
from our perspective we can consider the order of TXs in the block to be random, so in case of a DoS a few lucky other people might get a couple of executions higher on the list of TXs in the block,
but the impact of the DoS will still be very high.
we think the queue is worth it, reducing the awkwardness of having to rebroadcast your executions when they didn’t get executed.
we discussed letting the user set a gasprice and order the queue by the gasprice to get bumped up in the queue,
essentially how ethereum and bitcoin work with the mempool and the assumption that miners are rational and pick the TXs that pay them the most.
(but for us the queue would be deterministic instead of the (almost) complete unknown that the bitcoin / eth mempool is.)
but that would also mess with double spends,
a simple example is a provible fair ponzie smart contract (this is an actual thing on ethereum atm!):
if I’d see you buy in to the ponzie I’d create a TX and outbid you on the gasprice
you’ve set, that way I’ll be before you in the queue and I’d guarantee myself a profit.
which sort-of is already possible in ethereum, except the mempool is more unknown and harder to game then if it would be deterministic and with 10min blocks.
this could be mitigated a bit by only applying this to when the queue is actually used, but if there’s enough money at stake an attacker could be willing to keep a DoS rolling to keep the queue in effect.
it would at least mean to game that system you’d have to spend enough XCP to fill the blocks continuesly.
the problem without this mechanism would be that no matter how much it is worth to you to get your execution off you have to wait for the queue to empty.
while in bitcoin if I transfer $1mil I can easily pay $10 in fees to ensure I get around any DoS of an attacker paying $0.50 fees.
the biggest / only real weak point with the queue I see is that if you’d queue up a bunch of TXs in 1 blocks which all use 51% of the block gaslimit we’d continuously waste the remaining 49% because the 2nd get’s pushed back into the queue all the time.
essentially the cost to DoS the network with this would be 51% of the block gaslimit, not 100%.
again to illustrate:
gaslimit = 100 XCP
block 1
-------
- [attacker] TX1 executes for 51 XCP
- [attacker] TX2 executes for 51 XCP
- [attacker] TX3 executes for 51 XCP
- [attacker] TX4 executes for 51 XCP
- [attacker] TX5 executes for 51 XCP
- [attacker] TX6 executes for 51 XCP
- [attacker] TX7 executes for 51 XCP
parsing block:
- TX1 : parsed, gaslimit = 49 XCP
- TX2 : parsed, hits block gaslimit, halted and placed in queue
block 2
-------
- [normal user] TX8 executes for 20 XCP
- [normal user] TX9 executes for 30 XCP
parsing queue:
- TX2 : parsed, gaslimit = 49 XCP
- TX3 : parsed, hits block gaslimit, halted and placed back in queue
- TX4, TX5, TX6, TX7: left in queue, block gaslimit already hit
parsing block:
- TX8 : placed in queue, block gaslimit already hit
- TX9 : placed in queue, block gaslimit already hit
block 3
-------
parsing queue:
- TX3 : parsed, gaslimit = 49 XCP
- TX4 : parsed, hits block gaslimit, halted and placed back in queue
- TX5, TX6, TX7, TX8, TX9: left in queue, block gaslimit already hit
block 4
-------
parsing queue:
- TX4 : parsed, gaslimit = 49 XCP
- TX5 : parsed, hits block gaslimit, halted and placed back in queue
- TX6, TX7, TX8, TX9: left in queue, block gaslimit already hit
block 5
-------
parsing queue:
- TX5 : parsed, gaslimit = 49 XCP
- TX6 : parsed, hits block gaslimit, halted and placed back in queue
- TX7, TX8, TX9: left in queue, block gaslimit already hit
... etc ...
another question is if we should age out TXs in the queue after some period if not “parsed”, but again this would result in awkwardness, I don’t think there’s a reason to do so, or maybe it should be opt-in for users who are doing something that is time sensitive and they’d rather have it not execute after X blocks have gone by.