Over the past two weeks, our lead C++ developer Gavin Wood and I have spent a lot of time meeting the local Ethereum community in San Francisco and Silicon Valley. We were very excited to see so much interest in our project, and in just two months we had a meetup group like Bitcoin Meetup with over 30 people attending each week. People in the community are creating educational videos on their own, organizing events, experimenting with contracts, and one person has independently started writing an Ethereum implementation in node.js. But at the same time, we had the opportunity to take another look at the Ethereum protocol, see where it was still incomplete, and agree on various changes that will be incorporated into PoC 3.5 with minimal modifications. Client.
Transaction as Closing
There was one very non-intuitive feature in the MKTX opcode in ES1 and ES2 that allowed a contract to send a transaction that triggered another contract. Of course, even if you expect MKTX to be like a function call, processing the entire transaction immediately and then continuing with the rest of the code, MKTX didn’t actually work this way. Instead, execution of the call is postponed to the end. When MKTX is called, a new transaction is pushed to the front of the block’s transaction stack, and once the first transaction has finished executing, the second transaction has finished executing. It begins. For example, here’s what you can expect to work:
x = array() x(0) = “George” x(1) = MYPUBKEY
mktx(NAMECOIN,10^20,x,2)
if contract.storage(NAMECOIN)(“george”) == MYPUBKEY: registration_success = 1 else: registration_success = 0
// Do more…
Attempt to register “george” using the namecoin contract, then use the EXTRO opcode to check if registration was successful. Looks like this should work. But of course that isn’t the case.
EVM3 (no longer ES3) fixes this problem. We do this by taking the ideas of ES2 and the ideas of ES1, which creates the concept of reusable code, functions, and software libraries, keeping the code as a set of stateful, sequential instructions, and merging the two to maintain simplicity. This is the concept of “message calling”. A message call is an operation executed inside a contract that takes a destination address, an ether value and some data as input and calls a contract with that ether value and data, but unlike a transaction, it returns data as output. . So there is also a new RETURN opcode that allows data to be returned via contract execution.
With this system, contracts can now become much more powerful. There may still be traditional kinds of contracts that carry out certain data upon receiving a message call. But now two other design patterns are also possible: First, you can now create exclusive data feed contracts. For example, Bloomberg publishes a contract that pushes various asset prices and other market data, and allows the contract to include an API that returns the internal data as long as the incoming message call is sent with at least one finney. Fees can’t be too high. Otherwise, a contract that pulls data from the Bloomberg contract once per block and then provides a cheaper passthrough would be profitable. But even with fees equivalent to a quarter of transaction fees, these data supply businesses can be very successful. The EXTRO opcode has been removed to facilitate this functionality. Contracts are now opaque inside the system, but from outside you only need to look at the Merkle tree.
Second, it is possible to create contracts that represent functionality. For example, you can use the SHA256 contract or the ECMUL contract to calculate that function. There is one problem with this. The problem is that 20 bytes may be a bit large for storing an address to call a specific function. However, this problem can be solved by creating a single “stdlib” contract containing hundreds of clauses for common functions, storing the address of this contract once as a variable, and then simply calling it “x” (technically). You can access it multiple times. “Push 0 MLOAD”). This is EVM3’s way of incorporating the standard library concept, another key idea from ES2.
ether and gas
Another important change is: Contracts no longer pay for contract execution, but transactions do. When sending a transaction you must now include BASEFEE and the maximum number of steps to pay. When a trade execution begins, BASEFEE multiplied by maxsteps is immediately deducted from your balance. A new counter called GAS is then instantiated, starting with the number of steps remaining. Transaction execution then begins as before. Every step costs 1 GAS and execution continues until it stops naturally. At this point, any remaining gas time will either return the provided BASEFEE to the sender or the run will run out of GAS. In this case all executions will be cancelled, but full fees will still be paid.
This approach has two important advantages: First, miners can know in advance the maximum amount of GAS they will spend in a transaction. Second, and much more importantly, contract writers can spend much less time focusing on making the contract “defensible” against dummy transactions that seek to destroy the contract by forcing fee payments. For example, consider Namecoin in the previous five lines.
if tx.value < block.basefee * 200: stop if !contract.storage(tx.data(0)) or tx.data(0) = 100: contract.storage(tx.data(0)) = tx.data (One)
Two lines, no check. It’s much simpler. Focus on logic, not protocol details. The biggest weakness of this approach is that if you send a transaction to a contract, you need to calculate in advance how long it will take to execute (or at least set a reasonable upper limit on what you will pay). Contracts have the power to force you into an endless loop, consuming all your gas, and paying fees with no effect. But this is arguably not a problem. When you send someone a transaction, you are already implicitly trusting that person not to part with your money (or at least not complain if they do), and what is reasonable depends on the contract. The contract may also choose to include a flag indicating the amount of gas expected. We hereby specify as a voluntary standard the addition of “PUSH 4 JMP” before executable code.
This idea has one important extension that applies to the concept of message calls. When a contract makes a message call, the contract specifies the amount of gas that the contract on the other side of the call must use. As at the top level, the receiving contract may complete execution in time or run out of gas, at which point execution reverts back to the start of the call, but gas is still consumed. Alternatively, the gas field can be set to zero through the contract. In this case, they are relying on subcontractors for all the remaining gas. The main reason this is needed is to allow automated contracts and human-controlled contracts to interact with each other. If the only option available is to call the contract with all the remaining gas, automated contracts cannot use human-controlled contracts without absolutely trusting the owner. This makes m/n data feed applications essentially unfeasible. On the other hand, this leads to the weakness that the execution engine must include the ability to revert to a certain previous point (in particular, the start of a message call).
New terminology guide
With every new concept we introduce, we’ve standardized on some new terminology to use. I hope this helps organize discussions on various topics.
- external actor: A person or other entity that has access to an Ethereum node but is outside the Ethereum world. You can interact with Ethereum by depositing signed transactions and inspecting the blockchain and its associated states. There is at least one unique account.
- address: A 160-bit code used to identify your account.
- account: An account has a unique balance and number of transactions that are maintained as part of the Ethereum state. They are either owned by external actors or essentially as autonomous entities within Ethereum. If an account identifies an autonomous object, Ethereum also maintains stored state specific to that account. Each account has a single address that identifies it.
- transaction: A piece of data signed by an external actor. This represents a message or a new autonomous object. Transactions are recorded in each block of the blockchain.
- autonomous object: A virtual object that exists only within the virtual Ethereum state. It has a unique address. It only integrates with the state of the VM’s storage components.
- storage status: Specific information about a particular autonomous object that is maintained between execution times.
- message: Data (a set of bytes) and values (specified in Ether) that are passed between two accounts in a completely trustworthy manner, either through deterministic operations of autonomous entities or secure signatures of encrypted transactions.
- message call: The act of forwarding a message from one account to another. If the target account is an autonomous object, the VM is started with the state of that object and messages accordingly. If the message sender is an autonomous object, the call passes all data returned by the VM operation.
- gas: Basic network cost unit. Payments are made only in Ether (as of PoC-3.5), which is freely converted to gas as needed. Gas does not exist outside of the internal Ethereum computation engine. That price is set by transactions, and miners are free to ignore transactions where the gas price is too low.
long term perspective
Soon we will release a full official specification for the above changes, including a new version of the whitepaper that takes all these modifications into account, and a new version of the client that implements them. Additional changes to the EVM will likely be made in the future, but ETH-HLL will change as little as possible. So now writing contracts in ETH-HLL is completely safe and will continue to work even if the language changes.
We do not yet have a final idea of how we will handle the required fees. The current stopgap approach now has a block limit of 1000000 operations (i.e. GAS consumption) per block. Economically, mandatory fees and mandatory block limits are essentially the same. However, block limits are more common and theoretically allow a limited number of transactions to be free. We have a blog post coming soon covering our latest thinking on the fee issue. Stack tracing, another idea I thought of, may also be implemented in the future.
In the long term, even after Ethereum 1.0, the Holy Grail will be to see if we can attack the last two “intrinsic” parts of the system and convert them into contracts: ether and ECDSA. In such a system, Ethereum would still be a privileged currency within the system. The current idea is to pre-mine the ether contract with index “1” so that using it requires 19 bytes less. However, the execution engine will be simpler since there will no longer be a concept of currency. Instead, it’s all about contracts and message calls. Another interesting advantage is that this allows us to separate the ether and ECDSA, thereby selectively protonating the ether. If you wish, you can create an Ether account using the NTRU or Lamport contract instead. However, the downside is that proof-of-stake is not possible without an embedded currency at the protocol level. That may be a good reason not to go in this direction.