HIP-513: Smart Contract Traceability Extension Source

AuthorStoyan Panayotov, Mustafa Uzun
StatusAccepted
Needs Council ApprovalYes
Review period endsTue, 19 Jul 2022 07:00:00 +0000
TypeStandards Track
CategoryService
Created2022-06-08
Updated2022-07-05, 2022-07-26
Requires 206, 435, 260
Replaces 260

Abstract

This HIP overrides HIP-260 by moving traceability info from ContractFunctionResult to sidecar (HIP-435).

Extends traceability with new sidecar records ContractActions and ContractBytecode.

Proposes additional data placed in sidecar transaction records to enable full replay of transactions by mirror nodes and downstream users of Hedera.

Motivation

Enabling mirror nodes to download traceability info only if they need or want to process it.

Enabling full replay of smart contract service transactions will improve the usability, auditability, and debuggability of smart contract service transactions. Current facilities are insufficient to inspect “internal” transactions executed across contracts, and provide insufficient error messages when correcting errors during smart contract development.

Enabling estimations like gas to be consumed.

Rationale

HIP-435 allows the creation of sidecar record files containing detailed information about transaction executions without bloating the main transaction record stream. This allows us to externalise verbose debug information about smart contract transactions that was previously expensive to download and process every time.

User stories

Аs a mirror node operator, I want to have the option to download verbose contract operations-related data like contract state changes, contract actions, or contract bytecode.

As a client to the mirror node I want to have the option to see the state and traces associated with a previous contract execution and be able to execute a contract against a mirror node with a contract’s actual state without modifying state or charging gas.

Specification

We will introduce a couple of sidecar record types, one containing the minimum needed data that allows a user to fully replay the transaction and derive the same set of results, and the other containing an entire operation-by-operation trace.

Add ContractStateChanges sidecar record type

Add and populate a new sidecar record of type ContractStateChanges. It will have a single field - repeated ContractStateChange, which will be populated whenever a transaction causes contract storage states to change. This field will include the changes across all contract storage and not just the initial contract in the transaction.

message ContractStateChanges {
  repeated ContractStateChange contract_state_changes = 1;
}

For each slot read or written by the smart contract transaction a ContactStateChange record should be added to the ContractStateChanges.

Add ContractAction sidecar record type

Add and populate a new sidecar record of type ContractActions. It will have a single field - repeated ContractAction with fine-grained information about transaction. Which will improve debugging and auditing.

/**
 * The type of action described by the action proto.
 */
enum ContractActionType {
    /**
     * default non-value.
     */
    NO_ACTION = 0;
    
    /**
     * Most CALL, CALLCODE, DELEGATECALL, and STATICCALL, and first action of ContractCall/ContractCallLocal to deployed
     * contracts. This does not include calls to system or precompiled contracts.
     */
    CALL = 1;

    /**
     * CREATE, CREATE2, and first action of ContractCreate.
     */
    CREATE = 2;

    /**
     * like Call, but to precompiled contracts (0x1 to 0x9 as of Berlin)
     */
    PRECOMPILE = 3;

    /**
     * Call, but to system contract like HTS or ERC20 facades over Token accounts
     */
    SYSTEM = 4;
}

message ContractActions {
    repeated ContractAction contract_actions = 1;
}

/**
 * A finer grained action with a function result. Sometimes called "internal transactions." The function call itself
 * will be the first action in a list, followed by sub-action in the order they were executed.
 */
message ContractAction {

    /**
     * The type of this action.
     */
    ContractActionType call_type = 1;

    /**
     * Only the first action can come from an account, the rest will come from contracts.  Because of DELEGATECALL
     * and CALLCODE the caller of actions whose parent is an account may also be an account.
     */
    oneof caller {
        /**
         * If the caller was a regular account, the AccountID.
         */
        AccountID calling_account = 2;

        /**
         * If the caller was a smart contract account, the ContractID.
         */
        ContractID calling_contract = 3;
    }

    /**
     * The upper limit of gas this action can spend.
     */
    int64 gas = 4;

    /**
     * Bytes passed in as input data to this action.
     */
    bytes input = 5;

    /**
     * Who this action is directed to.
     */
    oneof recipient {
        /**
         * The AccountID of the recipient if the recipient is an account. Only HBars will be transferred, no other side
         * effects should be expected.
         */
        AccountID recipient_account = 6;

        /**
         * The ContractID of the recipient if the recipient is a smart contract.
         */
        ContractID recipient_contract = 7;

        /**
         * If the action was directed at an invalid solidity address, what that address was.
         */
        bytes invalid_solidity_address = 8;
    }

    /**
     * The value (in tinybars) that is associated with this action.
     */
    int64 value = 9;

    /**
     * The actual gas spent by this action.
     */
    int64 gas_used = 10;

    /**
     * The result data of the action.
     */
    oneof result_data {

        /**
         * If successful, the output bytes of the action.
         */
        bytes output = 11;

        /**
         * The contract itself caused the transaction to fail via the `REVERT` operation
         */
        bytes revert_reason = 12;

        /**
         * The transaction itself failed without an explicit `REVERT`
         */
        bytes error = 13;
    }

    /**
     * The nesting depth of this call. The original action is at depth=0.
     */
    int32 call_depth = 14;
}

For every smart contract transaction a ContractAction record should be added to the sidecar record.

Add ContractBytecode sidecar record type

Add and populate a new sidecar record of type ContractBytecode. It will have three fields - contract_id, initcode and runtime_bytecode. Fields will allow tracking smart contracts. initcode field will be populated only if ContractCreationTransaction.fileID is populated or we have child contract create. If we have child contract create TransactionSidecarRecord.consensus_timestamp will be associated with it.

message ContractBytecode {
    /**
    * The contract to which the bytecodes apply to
    */
    ContractID contract_id = 1;

    /**
    * Contract bytecode during deployment
    */
    bytes initcode = 2;
    
    /**
    * Contract bytecode after deployment
    */
    bytes runtime_bytecode = 3;
}

For every contract create ContractBytecode should be added to the sidecar record.

Remove ContractStateChange from ContractFunctionResult

ContractStateChange will be removed from ContractFunctionResult. It will be part of sidecar transaction record.

// existing protobuf
message ContractFunctionResult {
    // existing fields
    ContractID contractID = 1;
    bytes contractCallResult = 2;
    string errorMessage = 3;
    bytes bloom = 4;
    uint64 gasUsed = 5;
    repeated ContractLoginfo logInfo = 6;
    repeated ContractID createdContractIDs = 7;
    
    // field to remove
    //repeated ContractStateChange stateChanges = 8;
    reserved 8;
    
    google.protobuf.BytesValue evm_address = 9;
    int64 gas = 10;
    int64 amount = 11;
    bytes functionParameters = 12;
    AccountID sender_id = 13;
}

Add new sidecar records and migration field to the TransactionSidecarRecord

Add and populate new sidecar records to the TransactionSidecarRecord - ContractActions and ContractBytecode. Add new field migration to differentiate between migration and non migration data.

message TransactionSidecarRecord {
  Timestamp consensus_timestamp = 1;
  
  // new field
  bool migration = 2;
  
  oneof sidecar_records {
    ContractStateChanges state_changes = 3;
    
    //new sidecar records
    ContractActions actions = 4;
    ContractBytecode bytecode = 5;
  }
}

Add new sidecar types tо SidecarType

Add new sidecar types to the SidecarType - CONTRACT_ACTION and CONTRACT_BYTECODE.

enum SidecarType {
  SIDECAR_TYPE_UNKNOWN = 0;
  CONTRACT_STATE_CHANGE = 1;
  
  //new sidecar types
  CONTRACT_ACTION = 2;
  CONTRACT_BYTECODE = 3;
}

Migration of smart contract storage and bytecode

Run the following algorithm in order to migrate all existing contract storage:

  1. Iterate through all existing accounts.
  2. Filter smart contract accounts.
  3. Populate ContractStateChanges value read fields for each smart contract storage slot.
  4. Populate ContractBytecode.runtime_bytecode for each smart contract.
  5. Create sidecar files which will have the timestamp of the current top-level transaction.

Backwards Compatibility

This HIP changes ContractFunctionResult protobuf by removing ContractStateChange from it. Though this is a breaking change, the information was never populated, so it shouldn’t break any client logic.

Security Implications

None.

How to Teach This

Respective documentation will be added.

Reference Implementation

  • For protobufs changes, please follow this issue.
  • For traceability info removal from ContractFunctionResult, please follow this issue.
  • For creation of new sidecar files, please follow this issue.

Rejected Ideas

None.

Open Issues

None.

References

  • HIP-435 - Record Stream V6
  • HIP-260 - Smart Contract Traceability
  • HIP-206 - Hedera Token Service Precompiled Contract for Hedera SmartContract Service

Copyright/license

This document is licensed under the Apache License, Version 2.0 – see LICENSE or (https://www.apache.org/licenses/LICENSE-2.0)

Citation

Please cite this document as: