Over the last fortnight, our lead C++ engineer, Gavin Wood, and I have dedicated significant time engaging with the local Ethereum community in San Francisco and Silicon Valley. We were thrilled to witness such an overwhelming enthusiasm for our initiative, highlighted by the formation of a meetup group that assembles weekly after just two months, reminiscent of the Bitcoin gatherings, with more than thirty participants present each time. Community members are proactively creating educational content, coordinating events, and experimenting with contracts, and one individual is even independently commencing the development of an Ethereum implementation in node.js. Concurrently, we had the opportunity to reevaluate the Ethereum protocols, identify lingering imperfections, and reach a consensus on a broad spectrum of modifications that will likely be seamlessly integrated into the PoC 3.5 clients.
Transactions as Closures
In ES1 and ES2, the MKTX opcode, which permitted contracts to dispatch transactions that activate other contracts, featured an unintuitive element: while one might logically predict MKTX to function similarly to a function call—processing the entire transaction instantly before proceeding with the remainder of the code—in actuality, MKTX did not operate this way. Instead, the execution of the call was postponed until the conclusion: when MKTX was invoked, a new transaction would be added to the forefront of the transaction stack within the block, and upon the completion of the first transaction, the second transaction’s execution would commence. For instance, here is something one might expect to operate correctly:
x = array()
x[0] = “george”
x[1] = MYPUBKEY
mktx(NAMECOIN,10^20,x,2)
if contract.storage(NAMECOIN)[“george”] == MYPUBKEY:
registration_successful = 1
else:
registration_successful = 0
// do more stuff…
Utilize the namecoin contract to attempt registering “george”, and subsequently employ the EXTRO opcode to ascertain whether the registration is successful. This seems like it should function properly. However, it unfortunately does not.
In EVM3 (now not ES3), we resolve this issue. We accomplish this by borrowing an idea from ES2—developing a notion of reusable code, functions, and software libraries—and merging it with a concept from ES1—maintaining simplicity by preserving code as a sequential series of instructions in the state, and combining these into a framework of “message calls”. A message call constitutes an operation executed within a contract, which accepts a destination address, an ether value, and some data as input, invoking the contract with that ether value and data, but also, unlike a transaction, returns data as an output. Consequently, there is also a new RETURN opcode that permits contract execution to return data.
With this new system, contracts can now wield significantly more power. Traditional contracts, which perform specific tasks upon receiving message calls, can still coexist. Nevertheless, two additional design patterns also become feasible. First, one can now establish a proprietary data feed contract; for instance, Bloomberg can release a contract wherein they push various asset prices and additional market data, including an API in its contract that returns the internal data as long as the incoming message call transmits at least 1 finney alongside it. The fee cannot be excessively high; otherwise, contracts that retrieve data from the Bloomberg contract once per block, subsequently offering a cheaper passthrough, would become profitable. However, even with fees approximating the value of perhaps a quarter of a transaction fee, such a data-feeding enterprise may prove very viable. The EXTRO opcode has been eliminated to enable this functionality, meaning that contracts are now opaque within the system, although externally, one can obviously inspect the Merkle tree.
Secondly, it is feasible to construct contracts that embody functions; for instance, one could have a SHA256 contract or an ECMUL contract to execute those respective functions. There exists a challenge with this: utilizing twenty bytes to store the address for invoking a specific function might be excessive. However, this can be resolved by creating a single “stdlib” contract that houses numerous clauses for common functions, allowing contracts to store the address of this contract once as a variable and access it multiple times simply as “x” (technically, “PUSH 0 MLOAD”). This represents the EVM3 approach to integrating another key concept from ES2—the idea of standard libraries.
Ether and Gas
Another significant alteration is this: contracts no longer incur costs for execution; transactions do instead. When you initiate a transaction, you must now include a BASEFEE and a maximum number of steps you are willing to pay for. At the onset of transaction execution, the BASEFEE multiplied by the maxsteps is immediately deducted from your balance. A new counter, labeled GAS, is subsequently instantiated, beginning with the number of steps you have remaining. Then, transaction execution commences as before. Each step deducts 1 GAS, and execution persists until it either concludes naturally, at which point all remaining gas multiplied by the specified BASEFEE is refunded to the sender, or the execution exhausts its GAS; in that scenario, all execution is reverted, but the complete fee is still incurred.
This method presents two key advantages. First, it empowers miners to anticipate the maximum amount of GAS that a transaction will utilize. Second, and significantly more crucially, it enables contract developers to allocate considerably less time on ensuring the contract is “defensible” against malicious transactions attempting to disrupt the contract by enforcing fees. For example, consider the previous five-line Namecoin:
if tx.value
Two lines, no validations. Much more straightforward. Concentrate on the logic, not the protocol specifics. The primary weakness of this technique is that it implies that, if you transmit a transaction to a contract, you must pre-calculate how long the execution will take (or at least set a reasonable upper limit you’re open to paying), and the contract is capable of entering an infinite loop, consuming all the gas, and compelling you to cover your fee with no resultant effect. However, this is arguably a non-issue; when you dispatch a transaction to someone, you are inherently placing your trust in them not to squander the funds (or at least refrain from complaining if they do), and it’s the contract’s responsibility to behave reasonably. Contracts might even opt to include a flag indicating how much gas they anticipate requiring (I propose prepending “PUSH 4 JMP” to execution code as a voluntary standard)
There exists one significant extension to this concept, which pertains to the notion of message calls: when a contract executes a message call, it also specifies the gas amount that the contract on the other end of the call must utilize. Just as at the top level, the receiving contract can either complete execution on time or run out of gas, at which point execution rolls back to the beginning of the call, yet the gas is still consumed. Alternatively, contracts can input a zero in the gas fields; in such a case, they trust the sub-contract with all remaining gas. The main rationale behind this…this necessity is to enable automated contracts and human-governed contracts to engage with one another; if solely the option of invoking a contract with all remaining gas were accessible, then automated contracts would be incapable of utilizing any human-controlled contracts without placing complete trust in their proprietors. This would render m-of-n data feed applications virtually impractical. Conversely, this introduces the vulnerability that the execution engine must incorporate the capacity to revert to specific previous states (notably, the commencement of a message call).
The Updated Terminology Guide
With all the fresh concepts we have presented, we have standardized a few new terms for use; hopefully, this will assist in clarifying discussions on the various subjects.
- External Actor: An individual or entity capable of interfacing with an Ethereum node, but outside the Ethereum domain. It can engage with Ethereum by submitting signed Transactions and examining the blockchain and related state. Possesses one (or more) inherent Accounts.
- Address: A 160-bit identifier utilized for recognizing Accounts.
- Account: Accounts maintain an inherent balance and transaction count as part of the Ethereum state. They are owned either by External Actors or intrinsically (as an identity) by an Autonomous Object within Ethereum. If an Account identifies an Autonomous Object, then Ethereum will also sustain a Storage State specific to that Account. Each Account has a unique Address that distinguishes it.
- Transaction: A data item, authenticated by an External Actor. It symbolizes either a Message or a new Autonomous Object. Transactions are documented in each block of the blockchain.
- Autonomous Object: A virtual entity that exists solely within the theoretical framework of Ethereum. Holds an intrinsic address. Incorporated only as part of the storage state’s component within the VM.
- Storage State: The data specific to a given Autonomous Object that is preserved between its executions.
- Message: Data (represented as a series of bytes) and Value (specified in Ether) transferred between two Accounts in a completely trusted manner, either through the deterministic operation of an Autonomous Object or the cryptographically secure signature of the Transaction.
- Message Call: The process of sending a message from one Account to another. If the target account is an Autonomous Object, the VM will be initialized with the state of that Object and the Message processed. If the sender is an Autonomous Object, then the Call forwards any data produced by the VM operation.
- Gas: The basic unit of network cost. Exclusively paid for by Ether (as of PoC-3.5), which can be freely converted to and from Gas as necessary. Gas does not exist outside of the internal Ethereum computational framework; its pricing is determined by the Transaction, and miners are at liberty to disregard Transactions whose Gas price is excessively low.
Future Outlook
Soon, we will unveil a comprehensive formal specification of the aforementioned changes, which will include an updated version of the whitepaper that considers all these adjustments, along with a new client version that implements it. Later, further modifications to the EVM are likely, but the ETH-HLL will remain as unchanged as possible; therefore, it is completely safe to draft contracts in ETH-HLL now, as they will continue to operate even if the language evolves.
We still lack a final strategy regarding mandatory fees; the current interim solution is to establish a block limit of 1,000,000 operations (i.e., GAS consumed) per block. Economically, a compulsory fee and a mandatory block limit are effectively similar; however, the block limit is somewhat more universal and theoretically enables a limited number of transactions to be included without charge. A blog post detailing our latest views on the fee matter will be published shortly. The other concept I had regarding stack traces might also see implementation in the future.
In the long run, perhaps even beyond Ethereum 1.0, the ultimate aspiration is to address the last two “intrinsic” components of the system, and explore the possibility of converting them into contracts: ether and ECDSA. In such a scenario, ether would remain the privileged currency within the system; current thoughts suggest we will premine the ether contract into index “1,” allowing it to require nineteen fewer bytes for utilization. However, the execution engine would be simplified as there would no longer be any notion of currency – it would solely focus on contracts and message calls. An additional interesting advantage is that this would facilitate the decoupling of ether and ECDSA, thus potentially making ether quantum-resistant; should you wish, you could establish an ether account utilizing an NTRU or Lamport contract instead. A downside, however, is that proof of stake would not be achievable without a currency that is intrinsic to the protocol level; this might be a compelling reason to refrain from pursuing this path.