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

Onchain Tests for BeaconChainProofs.sol #34

Merged
merged 22 commits into from
Feb 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .github/workflows/go_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ jobs:
run: unzip data/deneb_goerli_slot_7431952.json.zip -d ./data
- name: Unzip the File 3
run: unzip data/deneb_goerli_slot_7421952.json.zip -d ./data
- name: Unzip the File 4
run: unzip data/goerli_slot_6409723.json.zip -d ./data

- name: Set up Go 1.x
uses: actions/setup-go@v2
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ data/deneb_goerli_slot_7416760.json
data/deneb_goerli_slot_7413760.json
data/deneb_goerli_slot_7421952.json
data/deneb_goerli_slot_7431952.json
data/goerli_slot_6409723.json

generation/generation

Expand Down
12 changes: 8 additions & 4 deletions beacon/versioned_beacon_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,15 @@ func CreateVersionedSignedBlock(block interface{}) (spec.VersionedSignedBeaconBl
var versionedBlock spec.VersionedSignedBeaconBlock

switch s := block.(type) {
case *deneb.BeaconBlock:
versionedBlock.Deneb.Message = s
case deneb.BeaconBlock:
var signedBlock deneb.SignedBeaconBlock
signedBlock.Message = &s
versionedBlock.Deneb = &signedBlock
versionedBlock.Version = spec.DataVersionDeneb
case *capella.BeaconBlock:
versionedBlock.Capella.Message = s
case capella.BeaconBlock:
var signedBlock capella.SignedBeaconBlock
signedBlock.Message = &s
versionedBlock.Capella = &signedBlock
versionedBlock.Version = spec.DataVersionCapella
default:
return versionedBlock, errors.New("unsupported beacon block version")
Expand Down
284 changes: 284 additions & 0 deletions bindings/BeaconChainProofs.go

Large diffs are not rendered by default.

143 changes: 143 additions & 0 deletions build/BeaconChainProofs.abi
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
[
Sidu28 marked this conversation as resolved.
Show resolved Hide resolved
{
"inputs": [
{
"internalType": "bytes32",
"name": "latestBlockRoot",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "beaconStateRoot",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "stateRootProof",
"type": "bytes"
}
],
"name": "verifyStateRootAgainstLatestBlockRoot",
"outputs": [],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "beaconStateRoot",
"type": "bytes32"
},
{
"internalType": "bytes32[]",
"name": "validatorFields",
"type": "bytes32[]"
},
{
"internalType": "bytes",
"name": "validatorFieldsProof",
"type": "bytes"
},
{
"internalType": "uint40",
"name": "validatorIndex",
"type": "uint40"
}
],
"name": "verifyValidatorFields",
"outputs": [],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "beaconStateRoot",
"type": "bytes32"
},
{
"internalType": "bytes32[]",
"name": "withdrawalFields",
"type": "bytes32[]"
},
{
"components": [
{
"internalType": "bytes",
"name": "withdrawalProof",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "slotProof",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "executionPayloadProof",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "timestampProof",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "historicalSummaryBlockRootProof",
"type": "bytes"
},
{
"internalType": "uint64",
"name": "blockRootIndex",
"type": "uint64"
},
{
"internalType": "uint64",
"name": "historicalSummaryIndex",
"type": "uint64"
},
{
"internalType": "uint64",
"name": "withdrawalIndex",
"type": "uint64"
},
{
"internalType": "bytes32",
"name": "blockRoot",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "slotRoot",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "timestampRoot",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "executionPayloadRoot",
"type": "bytes32"
}
],
"internalType": "struct BeaconChainProofs.WithdrawalProof",
"name": "withdrawalProof",
"type": "tuple"
},
{
"internalType": "uint64",
"name": "denebForkTimestamp",
"type": "uint64"
}
],
"name": "verifyWithdrawal",
"outputs": [],
"stateMutability": "view",
"type": "function"
}
]
191 changes: 191 additions & 0 deletions chain_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package eigenpodproofs

import (
"context"
"crypto/ecdsa"
"errors"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/rs/zerolog/log"
)

var (
FallbackGasTipCap = big.NewInt(15000000000)
ErrCannotGetECDSAPubKey = errors.New("ErrCannotGetECDSAPubKey")
ErrTransactionFailed = errors.New("ErrTransactionFailed")
)

type ChainClient struct {
*ethclient.Client
privateKey *ecdsa.PrivateKey
AccountAddress common.Address
NoSendTransactOpts *bind.TransactOpts
Contracts map[common.Address]*bind.BoundContract
}

func NewChainClient(ethClient *ethclient.Client, privateKeyString string) (*ChainClient, error) {
var (
accountAddress common.Address
privateKey *ecdsa.PrivateKey
opts *bind.TransactOpts
err error
)

if len(privateKeyString) != 0 {
privateKey, err = crypto.HexToECDSA(privateKeyString)
if err != nil {
return nil, fmt.Errorf("NewClient: cannot parse private key: %w", err)
}
publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)

if !ok {
log.Error().Msg("NewClient: cannot get publicKeyECDSA")
return nil, ErrCannotGetECDSAPubKey
}
accountAddress = crypto.PubkeyToAddress(*publicKeyECDSA)

chainIDBigInt, err := ethClient.ChainID(context.Background())
if err != nil {
return nil, fmt.Errorf("NewClient: cannot get chainId: %w", err)
}

// generate and memoize NoSendTransactOpts
opts, err = bind.NewKeyedTransactorWithChainID(privateKey, chainIDBigInt)
if err != nil {
return nil, fmt.Errorf("NewClient: cannot create NoSendTransactOpts: %w", err)
}
opts.NoSend = true
}

c := &ChainClient{
privateKey: privateKey,
AccountAddress: accountAddress,
Client: ethClient,
Contracts: make(map[common.Address]*bind.BoundContract),
}

c.NoSendTransactOpts = opts

return c, nil
}

func (c *ChainClient) GetCurrentBlockNumber(ctx context.Context) (uint32, error) {
bn, err := c.Client.BlockNumber(ctx)
return uint32(bn), err
}

func (c *ChainClient) GetAccountAddress() common.Address {
return c.AccountAddress
}

func (c *ChainClient) GetNoSendTransactOpts() *bind.TransactOpts {
return c.NoSendTransactOpts
}

// EstimateGasPriceAndLimitAndSendTx sends and returns an otherwise identical txn
// to the one provided but with updated gas prices sampled from the existing network
// conditions and an accurate gasLimit
//
// Note: tx must be a to a contract, not an EOA
//
// Slightly modified from: https://github.com/ethereum-optimism/optimism/blob/ec266098641820c50c39c31048aa4e953bece464/batch-submitter/drivers/sequencer/driver.go#L314
func (c *ChainClient) EstimateGasPriceAndLimitAndSendTx(
ctx context.Context,
tx *types.Transaction,
tag string,
) (*types.Receipt, error) {
gasTipCap, err := c.SuggestGasTipCap(ctx)
if err != nil {
// If the transaction failed because the backend does not support
// eth_maxPriorityFeePerGas, fallback to using the default constant.
// Currently Alchemy is the only backend provider that exposes this
// method, so in the event their API is unreachable we can fallback to a
// degraded mode of operation. This also applies to our test
// environments, as hardhat doesn't support the query either.
log.Debug().Msgf("EstimateGasPriceAndLimitAndSendTx: cannot get gasTipCap: %v", err)
gasTipCap = FallbackGasTipCap
}

header, err := c.HeaderByNumber(ctx, nil)
if err != nil {
return nil, err
}
gasFeeCap := new(big.Int).Add(header.BaseFee, gasTipCap)

// The estimated gas limits performed by RawTransact fail semi-regularly
// with out of gas exceptions. To remedy this we extract the internal calls
// to perform gas price/gas limit estimation here and add a buffer to
// account for any network variability.
gasLimit, err := c.Client.EstimateGas(ctx, ethereum.CallMsg{
From: c.AccountAddress,
To: tx.To(),
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Value: nil,
Data: tx.Data(),
})

if err != nil {
return nil, err
}

opts, err := bind.NewKeyedTransactorWithChainID(c.privateKey, tx.ChainId())
if err != nil {
return nil, fmt.Errorf("EstimateGasPriceAndLimitAndSendTx: cannot create transactOpts: %w", err)
}
opts.Context = ctx
opts.Nonce = new(big.Int).SetUint64(tx.Nonce())
opts.GasTipCap = gasTipCap
opts.GasFeeCap = gasFeeCap
opts.GasLimit = addGasBuffer(gasLimit)

contract := c.Contracts[*tx.To()]
// if the contract has not been cached
if contract == nil {
// create a dummy bound contract tied to the `to` address of the transaction
contract = bind.NewBoundContract(*tx.To(), abi.ABI{}, c.Client, c.Client, c.Client)
// cache the contract for later use
c.Contracts[*tx.To()] = contract
}

tx, err = contract.RawTransact(opts, tx.Data())
if err != nil {
return nil, fmt.Errorf("EstimateGasPriceAndLimitAndSendTx: failed to send txn (%s): %w", tag, err)
}

receipt, err := c.EnsureTransactionEvaled(
tx,
tag,
)
if err != nil {
return nil, err
}

return receipt, err
}

func (c *ChainClient) EnsureTransactionEvaled(tx *types.Transaction, tag string) (*types.Receipt, error) {
receipt, err := bind.WaitMined(context.Background(), c.Client, tx)
if err != nil {
return nil, fmt.Errorf("EnsureTransactionEvaled: failed to wait for transaction (%s) to mine: %w", tag, err)
}
if receipt.Status != 1 {
log.Debug().Msgf("EnsureTransactionEvaled: transaction (%s) failed: %v", tag, receipt)
return nil, ErrTransactionFailed
}
log.Debug().Msgf("EnsureTransactionEvaled: transaction (%s) succeeded: %v", tag, receipt)
return receipt, nil
}

func addGasBuffer(gasLimit uint64) uint64 {
return 6 * gasLimit / 5 // add 20% buffer to gas limit
}
1 change: 1 addition & 0 deletions data/goerli_block_header_6409723.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"execution_optimistic":false,"finalized":true,"data":{"root":"0x34a8f0dcd3ff37c5e51918eeb26f87b4739da30b1f8610991dbbe0a8eef1dda9","canonical":true,"header":{"message":{"slot":"6409723","proposer_index":"409446","parent_root":"0x6d5fa22998d3c20a83876cb5289cb5f95a53524194ea1cfe2e5bc773ad44f152","state_root":"0x6c6e21b6d2565b2236d1fca0cf745d3ccdc21597fe4752c1fcc07b5fea61cf5f","body_root":"0x7cd4465e4bff22fcb3bae146b7cf2cee24152942730332866e98cbf358f62dac"},"signature":"0xa4e5fca736905b64b8ce4458655b481714a6291f05a0dc520a20bc90c492f3e8b3af291005274d26ebc1f8f92a1c115115573c796c03bc3ab4d0377e5fce174cfcddaee78c89e09e311b7322366e992efeedf5482b3f21c35469e3f07fa8500c"}}}
Binary file added data/goerli_slot_6409723.json.zip
Binary file not shown.
Loading
Loading