Solution - Version Upgrade Issue

Because handling this issue is not simple, let's discuss the method components one by one.

Introducing CNS

First, we introduce the concept of the ContractNameServcie as shown below.

CNS acts like DNS (Domain Name System).

  • Class diagram

    CNS

    This class maps the contract name to the contract address and returns the address given the contract name.

  • Sequence Diagram

    CNSSeq

    When calling a contract, we call the contract by the address corresponding to the contract name in the CNS.

    Doing so makes it possible to migrate to a new contract for bug fixes to the current contract.

The code as follows.

  • Code

    contract ContractNameService {
    
        address provider = msg.sender;
        mapping (bytes32 => address) contracts;
    
        modifier onlyByProvider() { if (msg.sender != provider) throw; _; }
        function setContract(bytes32 _contractName, address _contract) onlyByProvider {
             contracts[_contractName] = _contract;
        }
    
        function getContractAddress(bytes32 _contractName) constant returns(address) {
             return contracts[_contractName];
        }
    }
    

Bug fixes permitted for service provider only

Here we see that provider is assigned the value of msg.sender when creating the ContractNameService instance.

address provider = msg.sender;

Furthermore,

modifier onlyByProvider() { if (msg.sender != provider) throw; _; }
function setContract(bytes32 _contractName, address _contract) onlyByProvider {
    contracts[_contractName] = _contract;
}

Due to this logic, only the provider that initially deployed this CNS can register contracts with it.

In other words, the CNS and the contracts registered in the CNS are all provided by the CNS provider.

The problem of past data

While utilizing the CNS seems to have solved the problem of upgrading, actually there are still issues to be solved.

There is also the problem of data from previous versions.

To clarify, consider creating a contract "Someone" holding the "name" field, then upgrading it and adding a field called "age".

(Fields are separated as structure corresponding to GAS Usage Issue.

  • Class diagram

    SomeoneContract

    At first glance it seems to be able to upgrade normally, but if you look at the object diagram, the problem becomes obvious.

  • Object diagram

    CNS Object

    When upgrading and seeing someone_v1 registered in the CNS, the data group (SomeoneStruct_v1) that was registered at the time of someone_v1 in the past will be separated from the new Contract and becomes lost.

Solution for past data

There are several possible solutions to this.

  1. Set the address of Someone_v1#fields in Someone_v1.

    This is possible in a general programming language, but in a distributed environment it is not possible to simply pass the address of the mapping object.

  2. Move all data stored in fields of Someone_v1 to Someone_v1.

    This is fine for several records, but when the amount of data increases, a lot of writing will occur.

    So this is not feasible because the consumption of GAS for writes is very high.

  3. Include Someone_v 0 as a field in Someone_v 1.

    With this solution, you only have to write the old version address for one field, so you do not incur much additional GAS consumption.

Adding a Field Contract

By holding the address of Someone_v1 as a field of Someone_v1, it is now possible to continue to access the old data in Someone_v1, but from a newly added function in Someone_v1 (Someone_v1#someLogic2) it is not so easy to access Someone_v1#name.

Basically, Fields are dedicated field contracts with accessors (getters/setters) for all fields, since it is difficult to know in advance which accessors will become necessary in the future.

  • Class diagram

    Field separation

    This will eliminate the situation that you can not access the old field after a version upgrade.

    However, if anyone can write (setXxx) to the field, the authenticity of written data will be not be guaranteed.

    Nonetheless, it is difficult to manage the permitted address of callers (Someone_v1 or Someone_v1) to SomeoneField_v1 each time the version is upgraded.

    In this regard, in the Field class, with logic to "Allow all calls from contracts registered as Someone to CNS", we maintain the authenticity of written data even if the version is upgraded.way, in the Field class, with the logic to "Allow all calls from contracts registered as Someone to CNS", we maintain the authenticity of written data even if the version is upgraded.

For access permissions management, we provide the relevant contract sources, so this solution may be easily implemented. access permissions management, we provide the relevant contract sources, so this solution may be easily implemented.

Fields added by the upgrade

Regarding fields added at the time of upgrading (the "age" field in this example), there is also the problem of how to deal with previous data that does not have the "age" field.

If you do something as with a traditional DB where you can simply "patch all historical data", for large data a large amount of GAS will be consumed.

For this reason, after upgrading we have devised within the Field contract so that you do not need to record data unless necessary.

  • getter

    In the case of SomeoneField_v1#getAge in the above example, if the key does not exist (when there is no entry in Someone_v1), it also looks for the past (SomeoneField_v1) field. Then, If we fail to find the key in the old fields, throw, or else set it to return the default value if there is just no new version of the data. registration in Someone_v1), it also looks for the past (SomeoneField_v1) field. Then, If we fail to find the key in the old fields, throw, or else set it to return the default value if there is just no new version of the data.

  • setter

    Likewise, in SomeoneField_v1#setAge, if the key does not exist (when there is no registration as Someone_v1), it also looks at the previous (SomeoneField_v1) field.

    If we fail to find the key in the old fields, throw. If there is only data for the new version, set the value in the age field and set the default values in any other new fields.

Because this logic is quite involved, we created a parent contract that encapsulates the logic, we have made the source available, so please use it.is logic, we have made the source available, so please use it.

The problem of contract bloat due to inheritance

Even with the strategies discussed so far, there are still further problems to consider when upgrading.

Here we discuss the enlargement of inherited contracts.

  • Class diagram

    Field separation

    In this figure, Someone_v 1 inherits Someone_v 0, but when compiled, it will be the contract size of both contracts combined.

    Since Ethereum has a maximum capacity of a transaction to be put included in a block, if the size of the contract becomes too large, it's deployment becomes impossible.

    Therefore we take the following countermeasures.

    1. Separate the interface from the logic.
    2. Interface delegates delegates to Logic contract without containing any logic itself.delegates to Logic contract without containing any logic itself.only to Logic contract without containing logic.
    3. Leaves only the interface part for the inheritance relationship.

As a result we know have the following structure.

  • Class diagram

    CNSFullField

Ether held by the contract

So far, the problems with upgrade have been almost solved, but one last issue remains.

For example, consider creating a Deposit contract and creating a Dapp that manages your Ether.

  • Class diagram

    CNSFullField

If the user passes the Ether to the Deposit contract, the Ether that you passed will be received by the first invoked contract.

That is, within Version 0, Ether will be accumulated at the address of Deposit_v 0, and after upgrading, New Ether will be stored at the address of Deposit_v 1.when upgrading, Ether will be stored at the address of Deposit_v 1.

For the end user, v0 and v1 are the same Deposit contract, so we need to manage the total amount across the versions.

Because Deposit_v1 and Deposit_v1 are interfaces, the actual deposit/withdrawal logic is defined in DepositLogic. If there is a need to withdraw money to send to another address, it is necessary to collect the Ether in Deposit and send it to the destination. there, it is necessary to collect Ether in Deposit and send it to the destination.

Since these processes are complicated, we prepared the parent class of the interface and define it there.
We also provide it as OpenSource, so please use it.

About the structure of the upgradable Contracts

The contract architecture that resolved all the above problems has the following structure.structure that resolved all the above problems has the following structure.

  • Class diagram

    Overall Contract Configuration

The above-mentioned sky blue contract group is provided as an OpenSource framework as part of this service.

If you look at the source of the Version contract group and the AddressGroup contract group in the reference page below, you should be able to grasp how we are dealing with the various problems discussed.

results matching ""

    No results matching ""