ethclient: add RevertErrorData function and example

This commit is contained in:
Felix Lange 2024-10-24 13:10:31 +02:00
parent 24c5493bec
commit 49b29c0fa6
3 changed files with 120 additions and 15 deletions

View file

@ -630,6 +630,24 @@ func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) er
return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data))
}
// RevertErrorData returns the 'revert reason' data of a contract call.
//
// This can be used with CallContract and EstimateGas, and only when the server is Geth.
func RevertErrorData(err error) ([]byte, bool) {
var ec rpc.Error
var ed rpc.DataError
if errors.As(err, &ec) && errors.As(err, &ed) && ec.ErrorCode() == 3 {
eds, ok := ed.ErrorData().(string)
if ok {
revertData, err := hexutil.Decode(eds)
if err == nil {
return revertData, true
}
}
}
return nil, false
}
func toBlockNumArg(number *big.Int) string {
if number == nil {
return "latest"

View file

@ -20,6 +20,7 @@ import (
"bytes"
"context"
"errors"
"fmt"
"math/big"
"reflect"
"testing"
@ -180,14 +181,19 @@ func TestToFilterArg(t *testing.T) {
}
var (
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
testBalance = big.NewInt(2e15)
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
testBalance = big.NewInt(2e15)
revertContractAddr = common.HexToAddress("290f1b36649a61e369c6276f6d29463335b4400c")
revertCode = common.FromHex("7f08c379a0000000000000000000000000000000000000000000000000000000006000526020600452600a6024527f75736572206572726f7200000000000000000000000000000000000000000000604452604e6000fd")
)
var genesis = &core.Genesis{
Config: params.AllEthashProtocolChanges,
Alloc: types.GenesisAlloc{testAddr: {Balance: testBalance}},
Config: params.AllEthashProtocolChanges,
Alloc: types.GenesisAlloc{
testAddr: {Balance: testBalance},
revertContractAddr: {Code: revertCode},
},
ExtraData: []byte("test genesis"),
Timestamp: 9000,
BaseFee: big.NewInt(params.InitialBaseFee),
@ -209,27 +215,30 @@ var testTx2 = types.MustSignNewTx(testKey, types.LatestSigner(genesis.Config), &
To: &common.Address{2},
})
func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
func newTestBackend(config *node.Config) (*node.Node, []*types.Block, error) {
// Generate test chain.
blocks := generateTestChain()
// Create node
n, err := node.New(&node.Config{})
if config == nil {
config = new(node.Config)
}
n, err := node.New(config)
if err != nil {
t.Fatalf("can't create new node: %v", err)
return nil, nil, fmt.Errorf("can't create new node: %v", err)
}
// Create Ethereum Service
config := &ethconfig.Config{Genesis: genesis, RPCGasCap: 1000000}
ethservice, err := eth.New(n, config)
ecfg := &ethconfig.Config{Genesis: genesis, RPCGasCap: 1000000}
ethservice, err := eth.New(n, ecfg)
if err != nil {
t.Fatalf("can't create new ethereum service: %v", err)
return nil, nil, fmt.Errorf("can't create new ethereum service: %v", err)
}
// Import the test chain.
if err := n.Start(); err != nil {
t.Fatalf("can't start test node: %v", err)
return nil, nil, fmt.Errorf("can't start test node: %v", err)
}
if _, err := ethservice.BlockChain().InsertChain(blocks[1:]); err != nil {
t.Fatalf("can't import test blocks: %v", err)
return nil, nil, fmt.Errorf("can't import test blocks: %v", err)
}
// Ensure the tx indexing is fully generated
for ; ; time.Sleep(time.Millisecond * 100) {
@ -238,7 +247,7 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
break
}
}
return n, blocks
return n, blocks, nil
}
func generateTestChain() []*types.Block {
@ -256,7 +265,10 @@ func generateTestChain() []*types.Block {
}
func TestEthClient(t *testing.T) {
backend, chain := newTestBackend(t)
backend, chain, err := newTestBackend(nil)
if err != nil {
t.Fatal(err)
}
client := backend.Attach()
defer backend.Close()
defer client.Close()

75
ethclient/example_test.go Normal file
View file

@ -0,0 +1,75 @@
// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package ethclient
import (
"context"
"fmt"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/node"
)
var exampleNode *node.Node
// launch example server
func init() {
config := &node.Config{
HTTPHost: "127.0.0.1",
}
n, _, err := newTestBackend(config)
if err != nil {
panic("can't launch node: " + err.Error())
}
exampleNode = n
}
// Here we show how to get the error message of reverted contract call.
func ExampleRevertErrorData() {
// First create an ethclient.Client instance.
ctx := context.Background()
ec, _ := DialContext(ctx, exampleNode.HTTPEndpoint())
// Call the contract.
// Note we expect the call to return an error.
contract := common.HexToAddress("290f1b36649a61e369c6276f6d29463335b4400c")
call := ethereum.CallMsg{To: &contract, Gas: 30000}
result, err := ec.CallContract(ctx, call, nil)
if len(result) > 0 {
panic("got result")
}
if err == nil {
panic("call did not return error")
}
// Extract the low-level revert data from the error.
revertData, ok := RevertErrorData(err)
if !ok {
panic("unpacking revert failed")
}
fmt.Printf("revert: %x\n", revertData)
// Parse the revert data to obtain the error message.
message, err := abi.UnpackRevert(revertData)
fmt.Println("message:", message)
// Output:
// revert: 08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a75736572206572726f72
// message: user error
}