HIP-513: Smart Contract Traceability Extension
Author | Stoyan Panayotov, Mustafa Uzun, Steven Sheehy |
---|---|
Discussions-To | https://github.com/hashgraph/hedera-improvement-proposal/discussions/547 |
Status | Final ⓘ |
Needs Council Approval | Yes ⓘ |
Review period ends ⓘ | Tue, 19 Jul 2022 07:00:00 +0000 |
Type | Standards Track ⓘ |
Category | Service ⓘ |
Created | 2022-06-08 |
Updated | 2023-06-14 |
Requires | 206, 435, 260 |
Replaces | 260 |
Release | v0.36.0 |
Table of Contents
- Abstract
- Motivation
- Rationale
- User stories
- Specification
- Add ContractStateChanges sidecar record type
- Add ContractAction sidecar record type
- Add ContractBytecode sidecar record type
- Remove ContractStateChange from ContractFunctionResult
- Add new sidecar records and migration field to the TransactionSidecarRecord
- Add new sidecar types tо SidecarType
- Migration of smart contract storage and bytecode
- Mirror Node
- Backwards Compatibility
- Security Implications
- How to Teach This
- Reference Implementation
- Rejected Ideas
- Open Issues
- References
- Copyright/license
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;
}
/**
* The specific operation type of a call. The OP prefix has been added to avoid name collisions for
* the CALL and CREATE operation types since both ContractActionType and CallOperationType enums are
* used in ContractAction
*/
enum CallOperationType {
OP_UNKNOWN = 0;
OP_CALL = 1;
OP_CALLCODE = 2;
OP_DELEGATECALL = 3;
OP_STATICCALL = 4;
OP_CREATE = 5;
OP_CREATE2 = 6;
}
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;
/**
* The call operation type
*/
CallOperationType call_operation_type = 15;
}
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:
- Iterate through all existing accounts.
- Filter smart contract accounts.
- Populate
ContractStateChanges
value read fields for each smart contract storage slot. - Populate
ContractBytecode.runtime_bytecode
for each smart contract. - Create sidecar files which will have the timestamp of the current top-level transaction.
Mirror Node
The mirror node will be updated to be able to conditionally detect and download sidecar files and ingest the data into its database. This information will then be made available via new and existing REST APIs.
Contracts REST API
The existing /api/v1/contracts/{id}
REST API will be enhanced to add the bytecode information that it receives from the sidecar file.
This API already has the bytecode
field which contains the contract initcode. A new runtime_bytecode
field encoded as a hex string
with a 0x
prefix will be added to contain the contract’s runtime bytecode.
Contract Actions REST API
A new /api/v1/contracts/results/{transactionIdOrHash}/actions
contract actions REST API will be added to show the flat traces
associated with a smart contract execution. Either a transaction ID in the form ${payerAccount}-${validStartSeconds}-${validStartNanos}
or a
32 byte hex-encoded transaction hash can be used as the unique identifier in the URL path.
{
"actions": [{
"call_depth": 3,
"call_operation_type": "OP_DELEGATECALL",
"call_type": "CALL",
"caller": "0.0.101",
"caller_type": "CONTRACT",
"from": "0x0000000000000000000000000000000000000065",
"gas": 1000,
"gas_used": 500,
"index": 0,
"input": "0xabcd",
"recipient": "0.0.100",
"recipient_type": "ACCOUNT",
"result_data": "0xabcd",
"result_data_type": "OUTPUT",
"timestamp": "123.456",
"to": "0x0000000000000000000000000000000000000064",
"value": 1000
}],
"links": {
"next": "/api/v1/contracts/results/0.0.1000-123-456/actions?index=gt:99"
}
}
Note: from
and to
are just caller
and recipient
converted to the 20 byte hex encoded EVM address format
Supported query parameters:
index
- The position of the action within the ordered list of actions, starting at zero. Used as the primary key in pagination. Supportseq
,gt
,gte
,lt
,lte
operators.limit
- The maximum number of results to return. Defaults to 25 and max 100 allowed.order
- Sort by the action index. Value ofasc
ordesc
with a default ofasc
.
Contract Results REST API
The existing /api/v1/contracts/{id}/results/{timestamp}
and /api/v1/contracts/results/{transactionIdOrHash}
already contain a list of
contract state changes in the state_changes
field in its response. This information was added when ContractStateChange
was added to
ContractFunctionResult
but never populated. With the implementation of the sidecar, this information will now be filled in for mirror
nodes that enable sidecar functionality. Below is an example of this state change information returned in the API response:
{
"contracts": {
"...": "Other fields omitted for brevity",
"state_changes": [
{
"address": "0000000000000000000000000000000000001f41",
"contract_id": "0.1.2",
"slot": "0x00000000000000000000000000000000000000000000000000000000000000fa",
"value_read": "0x97c1fc0a6ed5551bc831571325e9bdb365d06803100dc20648640ba24ce69750",
"value_written": "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"
}
],
"transaction_index": 1,
"type": 2,
"v": 1
}
}
Contract State REST API
While the state changes above shows how particular slot values change as a result of a contract invocation, it does not provide a complete list of all slots and their values. For that,
we add an /api/v1/contracts/{idOrAddress}/state
REST API where id
is either the shard.realm.num
, realm.num
, num
, or a hex encoded EVM address of the contract.
{
"state": [{
"address": "0x0000000000000000000000000000000000001f41",
"contract_id": "0.0.100",
"slot": "0x",
"timestamp": "10000000.000000001",
"value": "0x"
}],
"links": {
"next": null
}
}
- Supported query parameters:
limit
- The maximum number of results to return. Defaults to 25 and max 100 allowed.order
- Sort by the slot. Value ofasc
ordesc
with a default ofasc
.slot
- The slot value of the contract storage. Used as the primary key in pagination. Supportseq
,gt
,gte
,lt
,lte
operators.
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: