Objective
Transaction is the way the external world interacting with the Ethereum network. Transaction is used when we wish to modify or update the state stored in the Ethereum network.
In this article, we will first provide some basic concepts on Ethereum. This builds the necessary groundwork for the upcoming parts. We then examine the structure of a generic Ethereum transaction. And after that, we are ready to explore the three types of transactions, each for different purposes, using the Ethereum transaction.
Some Basic Concepts on Accounts and Contracts
Ethereum is an account-based blockchain implementation. There are two types of account: Externally-Owned Account and Contract Account. We will introduce them in a logical way.
Externally-Owned Account (EOA)
We can first consider EOA as individual user in the external world. A user in Ethereum network is represented by a 20-byte (160-bit) address. The representation is done through the holding of a private key that finally derives the 20-byte address.
Within an Ethereum network circulates a native currency: ether. Besides a native currency, ether is mainly used as the transaction fee or service charge (called gas in Ethereum) when Ethereum network is processing the transaction. Each EOA holds an amount of ether as its state. When an EOA initiates a transaction, gas is specified and then spent in the Ethereum network. We will keep seeing gas elements when we deep dive into the transaction part.
Contract
Before we go into Contract Account, let’s have a glimpse on Contract first.
Contract is the “smart contract” capability in Ethereum network, where the business logic is implemented. The contract begins as a human-understandable coding language (Solidity is the most common one in Ethereum). This code is compiled into machine-understandable code, called bytecode, which is to be deployed on an Ethereum network.
Inside the contract code there are functions. These functions define the actual business logic, and will be called and executed once “invoked” after the contract is deployed.
We are going to use a sample contract called SimpleStorage in this article for demonstration.
Contract Account
The bytecode mentioned before is not yet accessible until it is deployed on an Ethereum network. The deployment of a contract bytecode is done through a transaction (we will examine this later). A Contract Account is created corresponding to a deployed contract.
The Contract Account is identified by a Contract Address, again a 20-byte address. This is the address when we interact with this deployed account. Like any EOA, Contract Account can also keep ethers when appropriate to the business logic.
Items Omitted
As this article is mainly on transaction, we omit some important concepts about Ethereum itself, like the peer-to-peer communication and mining. We assume transaction is properly propagated in Ethereum network, successfully mined, and is then included into a new block.
Tools to be Used
Here are some tools I use here for demonstrating the Ethereum transactions.
- Any compiler works fine. Here I use REMIX, an online IDE tool for Ethereum contract. I only take out the bytecode of a sample contract.
- An Ethereum simulator is also used. This speeds up the process of setting up a private network and transactions are mined almost immediately. Here I am using Ganache-CLI.
- To interact with the Ethereum network, I use Go Ethereum client (geth) to access the Ethereum simulator.
Ethereum Transaction
While transactions are used for different purposes, the transaction structure is the same. We will first explore what is inside.
Transaction Structure
From: The transaction sender. It is a 20-byte address representing the account initiating this transaction.
To: The recipient of this transaction. It is also a 20-byte address. Depends on the use, it can be another EOA, a Contract Account, or just left empty.
Value: The amount of fund in wei (1 ether = 10¹⁸ weis) to be transferred from “From” to “To”. If “To” is an EOA, it is simply a fund transfer. If “To” is a Contract Address, it is the amount of fund passed to the deployed contract. The contract is coded such that it can accept fund.
Data/Input: This data field is mainly for contract related activities. For new deployment of contract it is the bytecode and the encoded arguments. For execution of contract function, it contains the function signature and the encoded arguments. It is left empty in fund transfer.
Gas Price and Gas Limit: Both are related to the cost processing this transaction. Each processing step of a transaction performed by the miner has a predefined gas unit (for example, “sload” which gets data from permanent storage costs you 20 gas units). Gas Price is the amount (in wei) per gas unit. This is the unit price transaction sender willing to pay. Gas Limit is the maximum gas units spent for this transaction. The maximum gas unit spent by your transaction will not exceed the Gas Limit, as a protection in case any discrepancy in transaction processing.
Signing a Transaction
As we are using geth, this Ethereum client will handle the signing process on one’s behalf. “Signing” a transaction object is the process of generating a signature on it using the private key of transaction sender. The signed transaction is then handled by geth for all the following steps until the signed transaction is being included in a newly mined block.
Sending Transaction using Geth
There are two ways to send transactions through the RPC: eth_sendTransaction and eth_sendRawTransaction, corresponding to the command we can use in Geth: eth.sendTransaction() and eth.sendRawTransaction().
eth.sendTransaction(): This is for sending a transaction object. Geth will help signing the transaction (assuming geth has control on the private key) and perform all the serialization before sending this to the Ethereum network.
eth.sendRawTransaction(): This is for sending a serialized signed transaction, which is composed according to the required structure. This is usually used when the private key is not handled locally in geth, and the signing is done outside the Geth client.
For sake of simplicity, we are using eth.sendTransaction() for this whole article.
Three Types of Transaction
The generic transaction mentioned above is used for the three different purposes.
- Fund Transfer Between EOA
- Deploy a Contract on Ethereum Network
- Execute a Function on a Deployed Contract
Fund Transfer Between EOA
This transaction is used when an EOA is transferring fund to another EOA.
The transaction looks like this.
In this demonstration, we are transferring 10 ethers from accounts[0] to accounts[1].
Note:
- Here we are using web3.fromWei and web3.toWei to convert between ether and wei for sake of demonstration. Bear in mind that the value in transaction is always in wei.
- Some gas is spent when sending this transaction from accounts[0]. The balance of accounts[0] is a bit less than 90 ethers. The difference is the gas spent for handling this transaction.
Deploy a Contract on Ethereum Network
In this part we will deploy a compiled contract (i.e. the bytecode) on an Ethereum network. In Ethereum the deployment of contract is done through a transaction.
The transaction looks like this.
Here we are using the SimpleStorage contract. The contract looks like this.
pragma solidity ^0.4.0;contract SimpleStorage {uint storedData;function set(uint x) public {
storedData = x;
}function get() constant public returns (uint retVal) {
return storedData;
}}
And the bytecode is here (obtain the bytecode from Remix).
"608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582080122bb351e6e2c021f1c56c0c5933087e762ea6e7a3360b902b39cbed5a38f10029"
Here is how the transaction looks like.
As usual, we see a transaction hash is returned. With this transaction hash we can examine the transaction detail using eth. getTransaction() and eth.getTransactionReceipt().
Here we see
- In the transaction, the to is left empty (‘0x0’ is shown).
- In the input, we only place the bytecode. It is because our SimpleStorage contract does not have a constructor that requires arguments. If arguments are needed in constructor, they are encoded according to the type and appended after the bytecode. We will see how the encoding is done when we call set() function in the next part.
- The Contract Address is found in Transaction Receipt. We will use it in the next part.
- The default Gas Limit (gas) is 90,000 gas. If you do not specify the gas, you will encounter “out of gas” as it takes more than 90,000 gas for processing this transaction. Therefore we specify 200,000 gas for this transaction.
- It turns out the transaction processing only takes 112,213 gas. The remain is returned to transaction sender.
Execute a Function on a Deployed Contract
After a contract is deployed, an EOA can execute functions defined in this contract. Again it is implemented by sending an Ethereum transaction.
Before we perform the sendTransaction() for executing a function defined in the contract, let’s review the mechanism how to identify the function.
In the Solidity code above, two functions are defined: get() and set(uint). When contract code is compiled, these functions are processed through a hashing function (keccak256, implemented as sha3 in web3 library) and the first four bytes are taken out as the function selectors.
Here is how we extract the function selection from these two functions.
So the function selector is
- 0x6d4ce63c for get()
- 0x60fe47b1 for set(uint256)
We see these function selectors are already inside the bytecode.
"608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582080122bb351e6e2c021f1c56c0c5933087e762ea6e7a3360b902b39cbed5a38f10029"
Next, we need to understand the mechanism to execute the functions. These two functions are handled differently.
We see constant (or view) added when we define get(). It allows query of state variable without sending a transaction (and therefore no gas is spent). In this case we are using eth.call(). It is similar to eth.sendTransaction() except that some parameters are now optional. We will continue using eth.sendTransaction() for set(uint) function. They are shown in the demonstration.
In both cases, the data field contains the function selector and the encoded arguments if needed. Here is the transaction structure.
So first, let’s take a look on the existing value.
“0x6d4ce63c” is the selector of get(). Since it is a constant (view) function, we use eth.call(). This does not create new transaction and the result can be returned immediately. The current value is zero.
Then we will execute the set(uint). As it requires uint256 (the default type for uint), the data is now the selector plus the string for the value.
And we check the value again using get(), and the value is updated.
Note that eth.call() does not create a transaction. Only eth.sendTransaction() has a transaction hash returned.
If we take a look on the transaction and the receipt of the transaction, we see the data being sent in the transaction, and the actual gas being spent for this transaction.
Summary
In this article we have examined the three types of most common interaction with Ethereum network: sending ethers among accounts, deploying a contract and executing contract functions. All of them can be done through sending transaction to Ethereum network, with proper construction of transaction parameters inside.
This is not the only way to handle contract deployment and function execution, as we can also use eth.contract() and Application Binary Interface (abi) doing similar job. Nevertheless, using eth.sendTransaction() helps us understand the mechanism behind.