Smart Contract Calling System in the Ethereum blockchain
Task
Often, smart contract developers under Ethereum are faced with a seemingly simple problem - calling a smart contract code in the future or on a schedule. But there is no suitable solution, and you have to develop a separate service for invoking contracts.
Developing contact templates for the MyWish platform , we immediately faced this limitation. And we set a goal to get around this restriction “beautifully” - with the help of a decentralized solution. This system must put contracts into action, and therefore decided to name it Joule, in honor of the English physicist James Joule .
In this article I would like to talk about the development of Joule, about the decisions that formed the basis of the system and the results obtained. We want to believe that the development, to some extent, is innovative and the applied solutions may be interesting and useful for the Habr audience. Before describing the solution, it is necessary to tell the basic things about Ethereum blockchain smart contracts. ')
Smart contracts
Etherium smart network contracts are programs running in the EVM (ethereum virtual machine) environment. Each such contract has a unique address, balance of funds (ethers), state (permanent memory) and a set of functions available for calling (or without functions).
The code, balance and status of the contract is stored in the blockchain.
The user can call the contract function using a transaction. Or without a transaction, if the function does not change the state of the contract or someone else’s balance.
The contract method may have any, in complexity, implementation, and is limited only by the gas required to carry out each instruction. A contract may change its balance, may apply to other contracts, may receive funds from other network participants, etc.
Etherium is so arranged that only a user can launch a transaction into the network with a contract call. If, for example, in order for a contract to work, it is necessary to make a call to its method at some particular moment, then the user should be concerned about this.
Call system
The first obvious solution is a service that will publish a transaction to the network at the right time.
This solution has a key drawback - this solution is centralized. If something happens to the server where the service is located, for example, it is disconnected for non-payment, it will be fined, etc., then calls will not be made. So the contracts will not be executed, which is unacceptable.
To circumvent this drawback, our team came up with the Joule system, which is implemented on the basis of smart contracts.
Joule
Joule is a smart contract on the Ethereum network that allows you to register a call to any contract at a specific point in time. Joule provides the opportunity for any user of the Ethereum network to call registered contracts.
To motivate network members to make a call, a reward and bonus are paid, which are set during call registration. The calculation of the amount required for the reward is done by Joule . The amount of remuneration fully covers the cost of gas spent on the call. In the form of a bonus part, WISH tokens are paid for the call (not implemented in the current version).
The system allows you to register multiple calls of contracts at the same time stamp. Also, the same contract can be registered at different times. The only restriction is that you cannot do registration with a completely identical set of parameters (contract address, time, gas and gas cost).
Joule , like any ethereum network contract, allows you to simultaneously call yourself to multiple users. And it may happen that there will be more calls to Joule than there are contracts available to call. In this case, only the first caller will receive a reward.
At any time, the user (using the smart contract method or the web interface) can find out which contracts are queued for a call and at what point the call must be made. The amount of remuneration and the amount of gas required to fulfill the contract are also available from the Joule contract.
Joule guarantees that the contract will not be called before the time specified at registration, and does not allow to disturb the order of calls.
Technical difficulties of implementation
To develop a contract as complex as Joule, it is necessary to solve a large number of technical problems. One of such tasks is the issue of storing a set of addresses of contracts and time to call them (and other parameters). Receipt of the first in line to call a contract must be made in constant time (independent of the number of contracts registered in the system).
Additional complexity in the design process necessitates the choice of algorithms, with minimal gas consumption. For example, you cannot use solutions that have an algorithmic complexity greater than . In addition to the cost of gas, there is still a limit on the number of gas. For example, if the algorithm with the complexity of , and the search of each element will require 50,000 gas, then after adding 185 elements, the contract will not be able to search (with the current limit of 4,600,000 gas per transaction).
Decision
This chapter will describe the key technical solutions included in the Joule system.
Record structure
To store the state (except for balance), the contract has access to a key-value type repository. The key in the storage can be any 256-bit number. In fact, before saving, EVM calculates a key checksum using the keccak (sha-3) algorithm . The value is also a 256-bit number (the compiler allows you to put values in a larger state by adding an offset parameter to the key). Thus, the contract can store 2 ^ 256 unique values of 256 bits each, which exceeds the requirements by many orders of magnitude.
The key-value storage can be considered as a random access memory with a dimension of 2 ^ 256. In this case, the key is a reference to the memory cell. The data structure necessary for storing call recording consists of the following fields: contract address, time the contract is called, the number of gas required to call the contract and the cost of gas. All this data can fit in 256 bits (32 bytes):
Restrictions
Using this data storage structure, you must accept some restrictions:
Address - 20 bytes (address in the Ethereum network).
Timestamp - 4 bytes, the standard Unix Time value, valid from 1970-01-01 00:00:00 to 2106-02-07T09: 28: 15 (for unsigned values). In simple words - the maximum registration period is 2106. In the future, this value can be increased to 6812 years due to the use of an extra byte from the field to the magnitude of the gas.
Gas - 4 bytes, but in fact the network allows you to make a single transaction for no more than 4,600,000 gas. Taking into account the cost of gas for the operation of the Joule system at the time of calling the contract, a limit of 4 million gas per contract is now registered. Although, of course, 600,000 gas is not needed for Joule operation - an average of 50,000 gas is enough.
Gas Price - 4 bytes. Estimated at registration cost of gas. This value is stored in gwei (10 ^ 9 wei), so the minimum size is 0.000000001 ether, and the maximum is 4.294967296 ether.
Storing a chain of records
The smart contract supports two storage structures for a set of data: an array with access by index and a ratio (mapping) with access by key. But in reality, the array is stored as a mapping, where the key is an index, and does not provide any advantages in terms of access speed. In addition, inserting elements into the middle of an array requires shifting all elements and this may require an unacceptable amount of gas. Therefore, a solution based on linked lists is better suited for this task.
The following technology is used to store the list in the ratio: the value of the record (32 bytes) is considered as a key (or a link, if we draw an analogy with the C ++ language) to the next value.
The image on the left shows the registration records R and key acquisition functions. in the form of a chain of values. On the right is a key-value store in the form of a table. See that the value is lost because it is only the key to the next value. The contract is not possible to read the key from the relationship. For this, the table contains a zero record (Head), which always indicates the value of the beginning of the chain.
The order of entries in the chain is the reverse of chronological, for instantaneous receipt of the next contract to be called. Same registration times are ordered one after another in the order of addition.
Index
To maintain the list of registrations in the sorted order, entries must be inserted according to the date. To speed up the search for a place to insert a new value, a tree-based index was developed.
With this approach, the search for elements in the tree gives a constant value of complexity that does not depend on the number of elements in Devere . There is a worst-case scenario, which in the current implementation is 168 iterations. The balance of the tree can be selected by changing the number of levels and multipliers.
The last level, similar to the previous one, contains the values of the maximum and minimum time stamp.
Placement and components
The code of contracts in the blockchain is not changeable. And in the event of changes, you must post a new version of the contract. At the same time from the old version you need to transfer data (state). This may not always be easy to do, since the state of the contact is not accessible from the outside. And if the contract does not initially have functions to access the state, then the transfer will be virtually impossible. Similar to the state, it is impossible to transfer funds from a contract without functions previously prepared for this.
To solve these problems, the Joule contract is divided into several separate contracts:
The Joule Ecosystem consists of the following contracts:
Joule - a contract that implements the key logic of registering and invoking contracts
Proxy - a contract with a front controller function, to access Joule features. Proxy accepts registrations, calls, and calls to registered contracts originate from its address. Any interaction with Joule by users (regardless of the role of the user) occurs through a proxy .
Vault - a vault of funds to compensate for calls.
Register - a contract that stores a chain of records with registrations.
Index is a contract for quickly finding a place in the register for inserting a new contract.
State - a contract that implements the basic functions of storage. Used by the Index and Register contracts.
This architecture allows you to change the logic of Joule without losing data. No need to transfer or copy data. No need to make mechanisms for transferring funds (which may be vulnerable to hacker attacks).
Using the proxy contract simplifies integration with the Joule system for external developers. They do not need to change the address of the contract if changes occur in Joule . It will simplify the check in the registered contracts that the call comes from Joule .
Using
In this part we describe how you can use the resulting system.
Mining
Any user on the network who has an address and enough funds to make a call can multiply their funds with the help of Joule .
To do this, it is sufficient at the right time to make a transaction with a call to Joule (method invoke or invokeOnce ). During the transaction, Joule will transfer to the caller the reward that was reserved during the registration of the contract.
The moment you should make a call can be easily determined using the getTop method. This method returns the current status of the queue in Joule : the time of the nearest call, the minimum gas, the amount of remuneration for each contract and other values.
The gas size for the transaction that the miner indicates should be no less than the value specified in the invokeGas field for the nearest contract. Gas can be assigned with a reserve - unused gas will be returned back (funds for it will not be written off). There are cases that there are several contracts ready to be called up for execution. In this case, it is better to put gas equal to the amount of gas of all contracts. Then (in the case of the invoke method) several contracts will be called up by one transaction, and the miner will receive a reward for each.
When registering a contract, the developer sets the estimated value of the cost of gas (but not less than the value specified at least). To call a miner must pick up such a value of gas that his transaction was faster than others (if any), but was not more expensive than the amount of remuneration. The value of the gas cost and the amount of remuneration can be found using the getTop method.
In case of a successful call, Joule publishes the Invoked event, by which you can find all your successful calls and find out the exact amount of the reward.
Register your contracts
If for some purposes there is a need to get a call to the contract at the appointed time - then the best solution to this problem is Joule .
To register a call for a contract for a specific time, you must call the register method with the following parameters:
Address - the address of the contract to be called.
Timestamp - the time in the unix timestamp format at which the call should be made. It is important to understand that Joule guarantees only that the call will not be made earlier than this moment.
GasLimit - the maximum gas value that will be provided for the call. It is better to specify the value with a margin so that the situation does not arise, that the call to the contract will end in error due to a gas shortage.
GasPrice - the estimated cost of gas to call the contract.
It is necessary to transfer the sum in the air for the remuneration for the call together with the register call. The exact amount of the sum can be obtained using the getPrice method. In case an excess amount is transferred, the balance will be returned to the caller.
The contract must implement the check method. If early calls can violate the logic of the contract or create a vulnerability, then you should add a check that the call was from Joule . If the contract is already in the network, and there is no possibility to add the check method to it, then you can use the intermediary contract that implements the desired method and invokes the target contract. Then when registering at Joule, you must specify the address of the contract of intermediary.
In case of successful registration, Joule publishes the Registered event, with which you can see all your registrations.
Both types of interaction can be implemented directly by working with a contract through an Ethereum network client, for example, Parity or Geth (Mist) .
Also, for convenience, Joule has a web interface that allows you to make a call or register your contract using only a browser with an extension installed in it, such as Metamask or without an extension at all, for example, through MEW .
Conclusion
The result is a solution that allows you to bypass the platform restriction of smart contracts - to call the contract at the appointed time without the direct participation of the user.