Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: add debug_traceTransaction with opcode logger tests #786

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .github/workflows/opcode-logger-testing.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Opcode logger testing

on:
pull_request:
branches: [ main, release/** ]
push:
branches: [ main, release/** ]
tags: [ v* ]

jobs:
check:
name:
Opcode logger comparison between besu and hedera
runs-on: [self-hosted, Linux, large, ephemeral]
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
submodules: recursive

- name: Use Node.js [18.15]
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
with:
node-version: 18.15
cache: npm

- name: Create .env file
run: cp local.env .env

- name: Install dependencies
run: npm install

- name: Upgrade @hashgraph/hedera-local to v2.27.1
run: npm install @hashgraph/[email protected] --save

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@8f1998e9878d786675189ef566a2e4bf24869773 # v1.2.0
with:
version: nightly

- name: Run besu node
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: why do we need to run besu again.
Is it that the @OpcodeLogger tests rely on comparing outputs from besu and local node?

Copy link
Contributor Author

@natanasow natanasow Jul 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of them (tagged with 'besu comparison') - yes, and that's the reason. Another possible question could be:

Why just don't hardcode the besu's responses in a JSON and compare against it?

Every time we change a solidity's compiler version in the hardhat.config.js, we will have to locally run these tests against besu to generate a new hardcoded JSON which will be used for further comparison. That should be needed because let's get for example changes from solidity 0.8.23 and 0.8.24. Contracts compiled with the older version will not include EIP-5656 (for mcopy opcode) and EIP-1153 (for tstore and tload opcodes) and debug_traceTransaction will return opcodes based on the contract's bytecode. When we change a solidity version to 0.8.24 in hardhat.config.js, contracts will be precompiled and the new opcodes (from EIP-5656 and EIP-1153) will be introduced in the contracts bytecodes, so when we run the tests and compare debug_traceTransaction responses with the hardcoded ones (generated with contracts compiled with solidity 0.8.23) they will differ.

Based on the info above, we have two possible approaches:

  • skip the besu flow in the ci, generate a hardcoded json with responses from the besu beforehand, and rely on developers to keep the hardcoded besu responses up-to-date with the solidity version defined in hardhat.config.js
  • stick to this CI where on each run we generate a json for comparison based on the current solidity version in hardhat.config.js

I prefer the second approach because it's not binding to the developers and we shouldn't take extra care if the solidity version is bumped.

Hope that clears some of the things out. I'm open to changes if you find something disrupting or not right.

Copy link
Collaborator

@Nana-EC Nana-EC Jul 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. I'd like this called out in some doc somewhere so people understand the flow

run: npm run besu:start

- name: Run opcode tests against besu
run: npx hardhat test --grep "besu comparison" --network besu_local
Nana-EC marked this conversation as resolved.
Show resolved Hide resolved

- name: Start the hedera local node
run: npx hedera start -d

- name: Run opcode tests against hedera local node
run: npx hardhat test --grep @OpcodeLogger
16 changes: 16 additions & 0 deletions .github/workflows_documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,19 @@ Examples:
- postMigrationTestTags: @post-migration

The testing matrix offers pretty big coverage as we can see. All options and combinations rely on us, and what's our end goal.

### Opcode logger Testing

In order to make opcode logger testing easier, we decided to use Besu's **debug_traceTransaction** responses as a source of truth for executed opcodes. The pipeline execution is as follows:
- installs dependencies
- compiles contracts
- executes `npm run besu:start` - A helper script that starts the local Besu node, the version is hardcoded to 24.6.0 and it uses an official docker image. Exposed ports are:
- *8540* which is mapped to Besu's HTTP json-rpc relay
- *8541* which is mapped to Besu's WS json-rpc relay

All the overridden node properties as miner address, enabled and included apis including custom genesis file are defined in *utils/besu-configs/customConfigFile.toml*. A custom genesis file (defined in *utils/besu-configs/customGenesisFile.toml*) is needed because starting block number of all existing forks till now must be set to 0 when Besu's node is used as a local private testing network. Start-up accounts are included in *customGenesisFile.json* as well and they easily can be expanded with new user-defined ones.
- executes specific tests - These tests have custom before and after methods that detect the target network, and if it is Besu, then execute **debug_traceTransaction** against Besu node and save the opcodes response into JSON file. That step doesn't gain us any coverage, it's needed to generate a source of truth when the same tests are executed against the Hedera local node.
- starts Hedera local node
- executes specific tests - These tests have custom before and after methods that detect the target network, and if it is Hedera, then execute **debug_traceTransaction** against Hedera local node and compare response opcodes with these generated against Besu and saved in JSON several steps above.

Entire Besu's prerequisites and responses generation are required because each time a solidity's compiler version in the **hardhat.config.js** is changed, the developer, who did the update, must locally run these tests against Besu to generate a new hardcoded JSON which will be used for further comparison. That would be needed because let's get for example changes from solidity *0.8.23* and *0.8.24*. Contracts compiled with the older version will not include EIP-5656 (for `MCOPY` opcode) and EIP-1153 (for `TSTORE` and `TLOAD` opcodes) and **debug_traceTransaction** will return opcodes based on the contract's bytecode. When a solidity version is updated to *0.8.24* in **hardhat.config.js**, contracts will be precompiled and the new opcodes (from EIP-5656 and EIP-1153) will be introduced in the contracts bytecodes, so when we run the tests and compare **debug_traceTransaction** responses with the hardcoded ones (generated with contracts compiled with solidity *0.8.23*) they will differ. After using a CI as above, the solidity version update is not binding to developers and they shouldn't take extra care for new "source of truth" JSON generation.
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "address payable",
"name": "_target",
"type": "address"
},
{
"internalType": "bytes",
"name": "_calldata",
"type": "bytes"
}
],
"name": "call",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address payable",
"name": "_target",
"type": "address"
},
{
"internalType": "bytes",
"name": "_calldata",
"type": "bytes"
}
],
"name": "callCode",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "callsCounter",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address payable",
"name": "_target",
"type": "address"
},
{
"internalType": "bytes",
"name": "_calldata",
"type": "bytes"
}
],
"name": "delegateCall",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "resetCounter",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address payable",
"name": "_target",
"type": "address"
},
{
"internalType": "bytes",
"name": "_calldata",
"type": "bytes"
}
],
"name": "staticCall",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "updateOwner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
]
77 changes: 77 additions & 0 deletions contracts/solidity/opcode-logger/OpcodeLogger.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

contract OpcodeLogger {
address public owner;
mapping(address => uint256) public callsCounter;

constructor() {
owner = msg.sender;
callsCounter[owner]++;
}

function updateOwner() external returns (address) {
owner = msg.sender;
callsCounter[owner]++;

return owner;
}

function resetCounter() external {
callsCounter[msg.sender] = 0;
}

function call(address payable _target, bytes memory _calldata) external payable returns (bool, uint256) {
bool isSuccess;
uint256 res;

assembly {
let resPlaceholder := mload(0x40)
isSuccess := call(gas(), _target, callvalue(), add(_calldata, 0x20), mload(_calldata), resPlaceholder, 0x20)
res := mload(resPlaceholder)
}

callsCounter[msg.sender]++;

return (isSuccess, res);
}

function delegateCall(address payable _target, bytes memory _calldata) external returns (bool) {
bool isSuccess;

assembly {
isSuccess := delegatecall(gas(), _target, add(_calldata, 0x20), mload(_calldata), 0, 0)
}

callsCounter[msg.sender]++;

return isSuccess;
}

function staticCall(address payable _target, bytes memory _calldata) external returns (bool, uint256) {
bool isSuccess;
uint256 res;

assembly {
let resPlaceholder := mload(0x40)
isSuccess := staticcall(gas(), _target, add(_calldata, 0x20), mload(_calldata), resPlaceholder, 0x20)
res := mload(resPlaceholder)
}

callsCounter[msg.sender]++;

return (isSuccess, res);
}

function callCode(address payable _target, bytes memory _calldata) external payable returns (bool) {
bool isSuccess;

assembly {
isSuccess := callcode(gas(), _target, callvalue(), add(_calldata, 0x20), mload(_calldata), 0, 0)
}

callsCounter[msg.sender]++;

return isSuccess;
}
}
8 changes: 3 additions & 5 deletions hardhat.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ module.exports = {
mirrorNode: NETWORKS.previewnet.mirrorNode,
},
},
// besu local node
besu_local: {
url: NETWORKS.besu.url,
allowUnlimitedContractSize: NETWORKS.besu.allowUnlimitedContractSize,
Expand All @@ -109,10 +108,9 @@ module.exports = {
chainId: NETWORKS.besu.chainId,
accounts: [
// private keys are configured in the genesis file https://github.com/hyperledger/besu/blob/main/config/src/main/resources/dev.json#L20
// private key for 0xf17f52151EbEF6C7334FAD080c5704D77216b732
'ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f',
// private key for 0x627306090abaB3A6e1400e9345bC60c78a8BEf57
'c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3',
'0xae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f',
'0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3',
'0x8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63'
],
},
},
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"hedera:start": "npx @hashgraph/hedera-local start --limits=false --dev=true --balance=10000000",
"hedera:stop": "npx @hashgraph/hedera-local stop",
"prepare": "husky install",
"besu:start": "docker run -d -v ./utils/besu-configs:/var/lib/besu/ -p 8540:8545 -p 8541:8546 hyperledger/besu:24.6.0 --config-file=/var/lib/besu/customConfigFile.toml",
"freeze-network-node": "hardhat run scripts/freeze-network-node.js"
},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions test/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ const Contract = {
Inheritance: 'Inheritance',
Functions: 'Functions',
FunctionsChild: 'FunctionsChild',
OpcodeLogger: 'OpcodeLogger',
FunctionsParent: 'FunctionsParent',
Scoping: 'Scoping',
Arithmetic: 'Arithmetic',
Expand Down
Loading
Loading