-
Notifications
You must be signed in to change notification settings - Fork 5.3k
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
EIP 105 (Serenity): Binary sharding plus contract calling semantics #53
Comments
Very cool.
Shard id can't be zero or typo? 176-bits address sounds more compatible with existing tools. |
Thinking about the "prime use case" of a sharded currency contract, a transfer would always touch at most two shards, but it could be hard to get the activity ranges of multiple transactions disjoint even though each shard is only touched by at most one transaction. What would be the drawbacks of allowing a list of activity ranges for each transaction group? |
How does this work with account abstraction? Does transactions from address 0 require special treatment? Will this make the shard 0 a special one because of pre-deployed contracts? |
I see a issue with Out-Of-Range being philosophically treated like Out-of-Gas. If I run out out of gas, well, the miner did the work, so I still should pay them. If my code is bad, that's my fault, and the miner should still get paid. But activity groups, and therefore whether a transaction goes out of range, is totally under the miner's control. Suppose they use introspection to find if my high-gas high-price transaction crosses at least one shard boundary, and then manipulate the transaction group to glean as much possible gas from me without actually executing the transaction. Or perhaps the miner made a legitimate bad guess about the activity range, and only after spending over a million gas in computation did they discover the problem. Should the miner still get paid? Something makes me uneasy about having two types of errors, one of which is the user's fault and the miner gets paid, and another which is the miner's fault, and there's not an obvious answer whether the miner should be paid or not. Sure, the programmer via the silent assistance of the compiler can deal with it, but there's a significant philosophical difference. On a similar topic, what algorithm can miners use to determine transaction groups? Seems to be a kind of packing problem, except the bins themselves have to be packed into bigger bins and objects can grow and shrink depending on arbitrary bounded computation. There's probably a reasonably good algorithm to get an answer, in the same way that current miners deal with packing transactions into blocks. Off the top of my head, a naive system might be:
There's clearly issues, though (How much gas should each proto-transaction group be allowed?) |
"Applications designed to be asynchronous will this always benefit from the highest gas" -- Typo here but the meaning seems clear enough |
Ok, I added an important point: transactions now also specify their own activity ranges, and a transaction group's activity range must be a superset of that of all transactions that are contained in it. This should resolve most of the issues around OOR being something that miners can "force" to happen to any transaction. |
Regarding special addresses, the zero address is just a virtual sender account, so I don't see any problems there wrt sharding; if really desired I suppose we can make the sender 0 + m * 2**k to be in the same shard, but not sure that's necessary. Precompiles can probably be copied to every shard. Casper, we'll see; there should be some way to architect it so that it benefits from parallelization but that may have to wait for v2. |
Very awesome. 2 questions: I'm unsure precisely how this choice works/looks?
Would you at a contract level choose which shard to store your contract's state? What if you don't have knowledge on how far the activity range could be? It could be a tx with long cascading hops between contracts, but you are not sure what will be exeuted as a result of it. I assume, one would be able to check, like with current web3 tools to see what happens with a potential state change before submitting it. ie, run this transaction to find the activity range, then submit this along with the transaction? Is this a correct assumption? |
This happens at two levels. First, when creating a contract, you can choose the prefix at which you create it. Second, the contract itself can choose which shard IDs it tries to SSTORE data at.
Then, you would have to set the activity range to be very large, and thus wait a long time and pay high gas costs for it to be accepted (or else set a smaller activity range and risk some of the child messages OOR'ing). Creating long and unpredictable cascading hops between contracts is precisely the thing that is deliberately economically discouraged by this scheme. |
Taking the Alarm contract as an example, does that mean it's best to put my 'callee' contract on the same shard as the alarm contract, and also that (in principle) the alarm contract could live on multiple shard (via multiple contracts) if that provided a good cost saving? |
It does to some extent feel like one is disincentivizing potentially emergent behaviour due to stifling costs? ie. A thought could occur: "We could automatically build a prediction market on information being produced automatically from contract A. But, argh. The prediction market platform is on a shard too far away. If only the 2 developers saw there could be potential synergy! Contract B should perhaps have chosen to deploy the contract in a closer group to the prediction market platform. Now we won't know what the interesting outcomes would be. Too expensive and will take too long to confirm. :(" It's a concern, but a trade-off for scalability's sake. Scalability > emergence at this point in time. I guess it should just be mitigated as much as possible to ensure it keeps most of the benefits. I guess, now your mention of urban economics makes sense. Keep up the good work! |
And then a lightbulb goes off in your head and you realize that you could implement your application and get the best of both worlds by making the information grabbing from contract A asynchronous :) |
You probably want an instance of the alarm contract in every shard. |
Ah, yes! Using log proofs, right? |
Suggestion: define Reasoning: Libraries can work cross-shard without further effort. Keeping the static code of popular libraries (possibly JIT compiled as well) in memory is little burden to miners, and libraries can't alter the state of their own shard anyway. It also helps forward-compatibility: no need to make sure your libraries will be on the same shard as your contract in the current blockchain so your dapp won't become expensive in the future. Libraries can be created and selfdestruct, true. But if we just use the last block's code I can't conceive of any normal situation where it would matter. |
The semantics of self destruct do not make too much sense anyway without blockchain rent. Perhaps we could also consider to add some DELEGATECALL or CALLCODE variant that does not use an address but a code hash. |
Why wouldn't the semantics of self destruct not make sense here? Would not a shard be able to shorten the amount of "block" it puts in with the self destruct mechanism? I'm very curious and want to better understand all of this. |
Yep :)
This feels like it's going into the "protocol-level proofs" area, as it violates the assumption that every shard knows about nothing but itself and previous state roots (an assumption that's not true in Serenity but will be in the further future). If we go that route, then this seems completely sensible, though note that it does mean that transactions will need to include proofs of the code they are calling. Otherwise, the most realistic route may be to simply copy the library code to each shard (note that this can be done just-in-time, and since we store the hash of the code in the tree and not the actual code the duplication costs are greatly reduced). You could argue that this is close to equivalent to a delegate call that uses a code hash. |
The main problem that I see with this proposal is the following: By setting m = 0 and k = 160, an adversary can create a block with a single transaction group that touches as much of the state as possible, being limited only by the global gas limit GL. The adversary can thereby force validators who don't have enough storage capacity to SPV validate their block. In the worst imaginable case, an adversary may be the only node with enough capacity to validate their block, and can thereby introduce an invalid state transition. This begs the following questions:
|
The solution there is to make sure that the gas rules impose a sane upper bound on the portion of the state that a single block can read, and thereby indirectly impose a sane upper bound on the size of the required merkle proof for a block. Read operations probably need to be made more expensive, perhaps to the point where they are a substantial portion of the cost of write operations. |
How would you accomplish that? When you say read operations, you mean inputs into the chain correct? |
I mean SLOAD (and less-known ones like BALANCE). |
I realized a bizarre but potentially quite useful feature of sharding. Suppose the network is now huge and every validator has to trust the validators of other shards. Then suppose out in shard 3f41 a group of validators agree to change consensus rules to add the FOO_BAR opcode. In shard 3f41, the FOO_BAR opcode now works. In all other shards, it doesn't. But as long as the outside validators trust the FOO_BARites of 3f41, there remains only one blockchain. We essentially have a partial hardfork (or a hardfork and a soft-fork at the same time.) In reality, the amount of drama that accompanies any change would mean this could never happen in secret. On the other hand, some gargantuan drama-inducing change could have a compromise: everyone on side A sticks to side A shards, everyone on side B sticks to side B shards, and there's just a few validators which overlap to make sure that no one on the other side is cheating. |
This proposal effectively outsources the problem, via an incentive, without actually solving it. I don't think it's reasonable that a contract or person would know what "activity range" it needs, and I anticipate a ton of out-of-gas conditions as a consequence, and general breakage. Absent a good solution to sharding, incentivizing a solution is an ugly, if not terrible idea, but will most definitely result in unexpected breakage. |
Potential attack: |
@zack-bitcoin not really doable I think....they still have to eventually verify with the other shards. That's when this would fall through. |
Here's a video for the presentation: https://www.youtube.com/watch?v=-QIt3mKLIYU |
There has been no activity on this issue for two months. It will be closed in a week if no further activity occurs. If you would like to move this EIP forward, please respond to any outstanding feedback or add a comment indicating that you have addressed all required feedback and are ready for a review. |
This issue was closed due to inactivity. If you are still pursuing it, feel free to reopen it and respond to any feedback or request a review in a comment. |
…al-edits Final editorial pass on Waves Namespace
This EIP is a solidification of a proposal to implement a simple binary sharding scheme in Ethereum, in the spirit of what was shown at Devcon 1 here: https://docs.google.com/presentation/d/1CjD0W4l4-CwHKUvfF5Vlps76fKLEC6pIwu1a_kC_YRQ
Design goals include:
We can describe the scheme as follows:
2**k
between2**k * m
and2**k * (m+1)
, for some valuesk
,m
where144 <= k <= 160
,m >= 0
and2**k * (m+1) <= 2**160
. The intuition is that any valid activity range constitutes a "shard", with each block of 2**144 being a "bottom level shard". Every shard that is not bottom level can also be decomposed into two "child shards", one for the left half of its range and one for the right half.CALL
or other operation that attempts to access an address which is outside of the transaction's containing group's activity range immediately triggers an out-of-range exception. NewRANGEMIN
andRANGEMAX
opcodes are added to make it easier for developers to deal with this.160-k
bits of the address so that the account that it creates will fit into the containing transaction group's range.CREATE
operations work similarly, except that they always set the first 16 bits of the target address to fit into the same bottom-level shard.SSTORE
andSLOAD
opcodes are introduced along the existing ones, such that these opcodes take an additional "shard ID" as an argument, where the shard ID should be in the range0 < k <= 65535
. A contract at address A can use these opcodes to read and write the storage of addressk * 2**144 + A % 2**144
, provided that this address is within the current activity range. Use of these opcodes will also fill in the target address's code to equal the sender's, if it is not yet set.MINRANGE + (d % 2**144)
whered
is the current address of the receipt storing contract).GL
is the global gas limit, the transaction group gas limits must satisfy the formulasum([max(L[i]**1.5, L[i] *GL / 4) for L in transaction_group_gas_limits]) < GL**1.5
. This allows blocks to include more gas, up to a maximum 4x increase, if they include transactions that can be executed in parallel (this limit can later on be removed or relaxed once proper sharding is implemented).Philosophically speaking, this introduces the following considerations:
STATEROOT
opcode. A precompile contract for doing this will likely be added. Applications designed to be asynchronous will thus always benefit from the highest gas discounts as each transaction will only need to touch one contract.shard = address // 2**144
,shard = sha3(name) % 65536
, etc), allowing contracts to store state across multiple shards.From an economics perspective, the following observations can be made:
In order to process all transaction execution in parallel between transaction groups in a completely disjoint fashion, there are two possible routes from a data structure standpoint. The first is to modify the tree structure so as to have a depth-16 binary tree at the top level. The second is to implement Patricia tree splitting and merging (see python implemented code here: ethereum/pyethereum@81d37fc ), and do it all with the existing hexary Patricia tree: when processing every block, the process becomes split -> process in parallel -> merge. The former route may be slightly more efficient in the long term, the latter seems more efficient in the short term and may require writing slightly less code (though the code that does need to be written for split/merge is somewhat involved).
Note that if the 144-bit address plus 16-bit sharding scheme is undesirable because it unreasonably splits up existing contracts, we can also take the route of 160-bit addresses plus 16-bit shards for a total of 176 bit addresses.
The text was updated successfully, but these errors were encountered: