polkadot-evm / frontier

Ethereum compatibility layer for Substrate.
Apache License 2.0
574 stars 487 forks source link

Client optimizations to support > 1 million smart contracts #445

Open 4meta5 opened 3 years ago

4meta5 commented 3 years ago

In its current state, substrate chains can support up to ~1 million accounts (cc @shawntabrizi ). Although this number will increase with the use of parity-db, it is not yet clear what the new upper bound will be.

For most substrate chains, the existential deposit (ED) set in pallet-balances provides a way to enforce the upper bound by increasing the minimum balance of any given account. Any account with balance below the ED will be reaped.

Unfortunately, setting any nonzero ED violates Ethereum compatibility. There are many situations where smart contracts have value stored in another contract (ERC20, NFTs, etc) but may not own any value directly. If the ED is increased to enforce the upper bound on accounts, these contracts would be reaped, thereby limiting many common use cases.

On Moonbeam every smart contract is an account which implies Moonbeam is limited to at most 1 million smart contracts under the current constraints. Substrate chains which use AccountId32 face the same constraint because they set pallet_evm::AddressMapping to HashedAddressMapping such that each smart contract address maps to a unique substrate account. If there is a limit of 1 million substrate accounts, then there is also a limit of 1 million smart contract addresses that can map to these accounts.

Ethereum is able to handle 165 million accounts because it has many client-side optimizations for reading account data in the trie. I'm opening this issue to coordinate brainstorming of similar client-side optimizations in frontier/client.

JoshOrndorff commented 3 years ago

Can anyone explain why Substrate chains can only support 1 million accounts. I hadn't heard that before. If we're trying to make global financial infra for a globe with 7 billion people, 1 million accounts for humans + contracts will never work.

And is there a jumping off point to learn about client-side optimizations in Ethereum nodes?

librelois commented 3 years ago

One possibility is to store offchain the nonce of an account that disappeared because of the existential deposit, in order to give it back the same nonce if it is reborn. This implies to modify the logic of CheckNonce in order to allow any nonce (and to store the provided nonce) in the case of the 1st transaction of a new account (i.e. if the nonce of this account is zero, as this is the value to return if the account does not exist in the on-chain storage).

This would make replay attack impossible, except for a validator/collator, which can be handled by slashing or other sanction mechanism, depending on the project.

JoshOrndorff commented 3 years ago

@librelois that idea transfers a lot of additional work to block authors and they are not paid for it:

  1. Maintain the offchain nonce storage for every dead account.
  2. Maintain a similar offchain storage for contract code and contract storage for the case when a contract gets reaped.
  3. Check every new account-creating transaction to see whether it has ever been played before in the history of the blockchain (or risk getting slashed).
librelois commented 3 years ago

Maintain the offchain nonce storage for every dead account.

So 4 bytes per address, it's not much, and it can be stored only for a limited time (for example 6 months). We can consider that if an account has been emptied AND is inactive for a certain time, it becomes acceptable to no longer protect its author against replay attacks.

Maintain a similar offchain storage for contract code and contract storage for the case when a contract gets reaped.

We don't have to do this. We can very well say that if the smart contract goes under the existence deposit it is destroyed, and must be redeployed. We can also make sure that the existential deposit does not apply to contract accounts, and on the other hand increase the cost of deploying a contract.

Check every new account-creating transaction to see whether it has ever been played before in the history of the blockchain (or risk getting slashed).

Of course the author of the block wouldn't do that. In case the nonce is neither in the on-chain storage nor in the off-chain storage of the node, the node can decide not to include the transaction in its blocks, but to relay it anyway on the network, so that another node having the nonce in its off-chain storage can write it.

crystalin commented 3 years ago

If we put an expiration date, we still reintroduce the replay attack. Also it would be better to have the expiration on chain in that case.

If we do an offline system, it will still not prevent replay attack (and you can't slash anyone for including it because the replay attack is reusing the same addresses. At best you slash the block authors for not verifying it). If we go with slashing we need a very complex system to report (because we don't control finality)

crystalin commented 3 years ago

So far the best idea i found was to use existential deposit and to set the non e of a new account to 100*block.number. which prevents replay attack as long as you don't submit 100 txs per bocks before it gets reaped.

notlesh commented 3 years ago

So far the best idea i found was to use existential deposit and to set the non e of a new account to 100*block.number. which prevents replay attack as long as you don't submit 100 txs per bocks before it gets reaped.

This might not play well with existing tools, but that's probably a lesser problem than others.

It doesn't help with deployed contracts, however.

crystalin commented 3 years ago

Another possibility is to have custom gas price depending of the action. Transferring to a new account will have a min price of 100 gwei for ex. Contract deployment should also cost gas_per_bytes times more to deploy

notlesh commented 3 years ago

Contracts are still limited to 24k in size, so the size isn't a huge issue. I suppose we could take the gas from the CREATE / CREATE2 opcodes (32000, I think?) and use them as an existential deposit. That doesn't actually change the security aspects of this, however -- an abuse would still cost the same.

sorpaas commented 3 years ago

This might not play well with existing tools, but that's probably a lesser problem than others.

This is the way. I'm not aware of any incompatibility.

crystalin commented 3 years ago

@sorpaas it might be an issue for project expecting given address on contract deployment. I'll test it more

xlc commented 3 years ago

So far the best idea i found was to use existential deposit and to set the non e of a new account to 100*block.number. which prevents replay attack as long as you don't submit 100 txs per bocks before it gets reaped.

This is going to break local nonce tracking code that could be used by code wallets. Also we had some previous attempt https://github.com/paritytech/substrate/pull/8822