Как лучше всего хранить и извлекать большие данные в смарт-контрактах Solidity?

Предположим, у меня есть страница создания события с некоторыми полями формы в пользовательском интерфейсе. И у меня есть смарт-контракт для хранения события в отображении событий или в публичном массиве в каком-то структурном формате.

struct event{
      bytes32 name;
      uint time;
}

event[] public events;

or

mapping(uint=>event) public events;

Я отправляю пользовательский интерфейс, и данные хранятся в контракте.

У меня есть страница со списком событий, на которой будут отображаться все события, созданные в контракте.

После поиска на многих форумах я получил некоторую информацию, может кто-нибудь подтвердить это.

Подход 1:

Для каждой отправки или сохранения в контракте вызывается событие, и пользовательский интерфейс имеет наблюдателя и сохраняется в локальном хранилище и отображается в пользовательском интерфейсе. Здесь данные о событиях могут быть отображением или массивом.

Я не хочу иметь какое-либо промежуточное хранилище для приложения, поэтому при каждом обновлении пользовательского интерфейса наблюдатель событий будет получать все данные о состоянии и снова обновлять локальное хранилище и отображать в пользовательском интерфейсе.

Подход 2:

Для отправки события создается новый объект события, который помещается в массив событий.

И написать метод получения событий, чтобы перебрать массив событий и вернуть список строковых элементов для каждого ключа в объекте, а затем снова объединить и деструктурировать строки в массиве объектов в пользовательском интерфейсе для привязки к пользовательскому интерфейсу.

Я думаю, что вычисления для второго подхода будут высокими из-за итерации. Поскольку мы разрабатываем Dapp, я не хочу снова иметь какую-либо промежуточную базу данных, которая станет централизованной системой.

Может ли кто-нибудь объяснить различные возможности хранения и извлечения данных из смарт-контракта, что на данный момент является предпочтительным способом.

Это мне очень поможет. Заранее спасибо.

Ответы (3)

eventэто ключевое слово в Solidity , которое позволяет вам легко извлекать данные, сгенерированные контрактом в интерфейсе Javascript. С точки зрения газа дешевле записать событие, чем записать в переменную хранилища (например, в ваш structмассив). Затем вы можете .watchиспользовать эти события и вставлять их в свой пользовательский интерфейс. Это рекомендуемый путь. Из соображений производительности вы можете захотеть каким-то образом кэшировать эти данные событий, потому что синтаксический анализ всех блоков при каждой перезагрузке довольно неэффективен.

Кроме того, не пытайтесь уместить все свои данные в свой смарт-контракт. Задача хорошо спроектированного децентрализованного приложения состоит в том, чтобы содержать только абсолютно необходимые элементы и указатели на внешние структуры , но отделять уровень хранения от блокчейна. Уровень хранения может быть реализован через IPFS или Swarm .

Как сказал Себастьян, вы должны быть осторожны с тем, какие данные вы хотите хранить в блокчейне в контракте.

При этом стоимость газа для хранения данных будет ложиться на эмитента транзакции для создания события. Точно так же любой вызов на основе «транзакций», который должен повторять эти данные, станет очень дорогостоящим в зависимости от объема данных и итераций, которые вам нужно выполнить.

Если функция помечена как константа и не требует изменения данных, ее можно вызвать без затрат на газ, и, следовательно, итерация списка событий, хотя и затратная в вычислениях, если выполняется как транзакция, в основном бесплатна для вызова.

Хорошей практикой было бы добавить диапазон, который вы хотите перечислить, чтобы вы могли ограничить обработку, необходимую для каждого вызова, и просмотреть результаты в своем dApp.

Вот пример контракта, который может это сделать, он далеко не полный, но копипаст в Remix будет работать, чтобы показать базовую функциональность:

pragma solidity ^0.4.0;

contract EventManager {

    struct Event {
        bytes32 name;
        uint time;
    }

    uint totalEvents;

    mapping(uint => Event) EventList;

    event EventAdded(address indexed _senderAddress, uint _eventId);

    function addEvent(bytes32 _name, uint _time) returns(uint eventId) {
        eventId = totalEvents++;
        EventList[eventId] = Event(_name, _time);
        EventAdded(msg.sender, eventId);
    }

    function listEvents(uint _start, uint _count) constant returns(uint[] eventIds, bytes32[] eventNames, uint[] eventTimes) {

        uint maxIters = _count;
        if( (_start+_count) > totalEvents) {
            maxIters = totalEvents - _start;
        }

        eventIds = new uint[](maxIters);
        eventNames = new bytes32[](maxIters);
        eventTimes = new uint[](maxIters);

        for(uint i=0;i<maxIters;i++) {
            uint eventId = _start + i;

            Event memory e = EventList[eventId];
            eventIds[i] = eventId;
            eventNames[i] = e.name;
            eventTimes[i] = e.time;
        }
    }
}

TL/DR: используйте сопоставление с динамическим массивом:

mapping(address => DataStruct[]) public dataStructsOfOwner;

Если кто-то может указать мне, где лучше опубликовать этот ответ, пожалуйста, продолжайте.

У меня была другая задача: сохранить контракты, привязанные к владельцу. Я сделал короткую симуляцию с этими двумя вариантами.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

struct Contract {
    string name;
    address contractAddress;
}

/**
 * @dev This Database saves an array of contracts for each owner
 */
contract Database1 {
    mapping(address => Contract[]) public contractsOfOwner;

    function addContract(
        address owner,
        string memory name,
        address contractAddress
    ) public {
        contractsOfOwner[owner].push(Contract(name, contractAddress));
    }

    function getContractsOfOwner(address owner)
        external
        view
        returns (Contract[] memory contracts)
    {
        return contractsOfOwner[owner];
    }
}

и

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

struct Contract {
    string name;
    address contractAddress;
    address owner;
}

/**
 * @dev This Database saves an array of contracts and iterates over them to get
 * contacts by owner
 */
contract Database2 {
    address[] contracts;
    mapping(address => Contract) contractDetails;
    mapping(address => uint256) balanceOf;

    function addContract(
        address owner,
        string memory name,
        address contractAddress
    ) public {
        contracts.push(contractAddress);
        contractDetails[contractAddress] = Contract(
            name,
            contractAddress,
            owner
        );
        balanceOf[owner] += 1;
    }

    function getContractsOfOwner(address owner)
        external
        view
        returns (Contract[] memory returncontracts)
    {
        uint256 count = balanceOf[owner];
        Contract[] memory contractsOfOwner = new Contract[](count);
        uint256 i;
        uint256 resultIndex;
        for (i = 0; i < contracts.length; i++) {
            if (contractDetails[contracts[i]].owner == owner) {
                contractsOfOwner[resultIndex] = contractDetails[contracts[i]];
                resultIndex++;
            }
        }
        return contractsOfOwner;
    }
}

Удивительный результат:

    ·--------------------------------------|---------------------------|-------------|-----------------------------·
|         Solc version: 0.8.10         ·  Optimizer enabled: true  ·  Runs: 200  ·  Block limit: 30000000 gas  
·······································|···························|·············|······························
|  Methods                             ·              111 gwei/gas               ·       3983.14 eur/eth       
·················|·····················|·············|·············|·············|···············|··············
|  Contract      ·  Method             ·  Min        ·  Max        ·  Avg        ·  # calls      ·  eur (avg)  │
·················|·····················|·············|·············|·············|···············|··············
|  Database1     ·  addContract        ·      72947  ·      90059  ·      81500  ·            4  ·      36.03  
·················|·····················|·············|·············|·············|···············|··············
|  Database2     ·  addContract        ·     122359  ·     156559  ·     135187  ·            4  ·      59.77  
·················|·····················|·············|·············|·············|···············|···························|···············|··············
|  Deployments                         ·                                         ·  % of limit   ·             
·······································|·············|·············|·············|···············|··············
|  Database1                           ·          -  ·          -  ·     406176  ·        1.4 %  ·     179.58  
·······································|·············|·············|·············|···············|··············
|  Database2                           ·          -  ·          -  ·     437581  ·        1.5 %  ·     193.47  
·······································|·············|·············|·············|···············|··············
·--------------------------------------|-------------|-------------|-------------|---------------|-------------·