mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 06:36:43 +00:00
test: to avoid lag between tx inclusion and accessible state over RPC
for more info, https://github.com/ethereum/go-ethereum/issues/31798
This commit is contained in:
parent
c8be0f9a74
commit
2f088053c9
1 changed files with 316 additions and 0 deletions
316
cmd/geth/lag_between_tx_inclusion_test.go
Normal file
316
cmd/geth/lag_between_tx_inclusion_test.go
Normal file
|
|
@ -0,0 +1,316 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
|
"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/holiman/uint256"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
GREEN_DOT = "\033[32m.\033[0m" // success
|
||||||
|
RED_X = "\033[31mx\033[0m" // expected failure case
|
||||||
|
YELLOW_S = "\033[33ms\033[0m" // unexpected failure, skip
|
||||||
|
basePort = 30000
|
||||||
|
numWorkers = 10
|
||||||
|
numTasks = 20
|
||||||
|
)
|
||||||
|
|
||||||
|
type UnexpectedCodeError struct {
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *UnexpectedCodeError) Error() string {
|
||||||
|
return e.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForClient(port int, timeout time.Duration) (*ethclient.Client, error) {
|
||||||
|
endpoint := fmt.Sprintf("ws://127.0.0.1:%d", port)
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
for time.Since(start) < timeout {
|
||||||
|
client, err := ethclient.Dial(endpoint)
|
||||||
|
if err == nil {
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("failed to connect to client at %s within %s", endpoint, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLagBetweenTxInclusionAndAccessibleState(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tasks := make(chan int, numTasks)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
errG, _ := errgroup.WithContext(ctx)
|
||||||
|
for w := 0; w < numWorkers; w++ {
|
||||||
|
workerID := w
|
||||||
|
errG.Go(func() error {
|
||||||
|
for taskID := range tasks {
|
||||||
|
chErr := make(chan error, 1)
|
||||||
|
task := &task{
|
||||||
|
workerID: workerID,
|
||||||
|
taskID: taskID,
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
chErr <- task.Run(t)
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
t.Logf("Worker %d cancel task %d", workerID, taskID)
|
||||||
|
task.Cleanup(t)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case err := <-chErr:
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *UnexpectedCodeError:
|
||||||
|
fmt.Print(RED_X)
|
||||||
|
cancel()
|
||||||
|
t.Errorf("Worker %d failed on task %d with: %s", workerID, taskID, err.Error())
|
||||||
|
default:
|
||||||
|
fmt.Print(YELLOW_S, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Print(GREEN_DOT)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for i := range numTasks {
|
||||||
|
tasks <- i
|
||||||
|
}
|
||||||
|
close(tasks)
|
||||||
|
errG.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
type task struct {
|
||||||
|
workerID int
|
||||||
|
taskID int
|
||||||
|
testGethRef atomic.Pointer[testgeth]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (task *task) Cleanup(t *testing.T) {
|
||||||
|
if g := task.testGethRef.Load(); g != nil && task.testGethRef.CompareAndSwap(g, nil) {
|
||||||
|
t.Logf("Worker %d cleanup", task.workerID)
|
||||||
|
g.Kill()
|
||||||
|
g.WaitExit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (task *task) Run(t *testing.T) (returnErr error) {
|
||||||
|
tmpDir, err := os.MkdirTemp("", fmt.Sprintf("geth-node-%d-%d-", task.workerID, task.taskID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
password := "123456"
|
||||||
|
pwdFilePath := filepath.Join(tmpDir, "password.txt")
|
||||||
|
pwdFile, err := os.Create(pwdFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.Remove(pwdFilePath)
|
||||||
|
if _, err := pwdFile.WriteString(password + "\n"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pwdFile.Close()
|
||||||
|
datadir := filepath.Join(tmpDir, "datadir")
|
||||||
|
ks := keystore.NewKeyStore(filepath.Join(datadir, "keystore"), keystore.LightScryptN, keystore.LightScryptP)
|
||||||
|
keyfileAcct, err := ks.NewAccount(password)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
keyJson, err := os.ReadFile(keyfileAcct.URL.Path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
key, err := keystore.DecryptKey(keyJson, password)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
port := basePort + task.workerID*100 + task.taskID
|
||||||
|
args := []string{
|
||||||
|
"--datadir",
|
||||||
|
datadir, // passing in a `datadir` is required to reproduce
|
||||||
|
"--dev",
|
||||||
|
"--dev.period",
|
||||||
|
"3",
|
||||||
|
"--ws",
|
||||||
|
"--ws.port",
|
||||||
|
fmt.Sprintf("%d", port),
|
||||||
|
"--ws.api",
|
||||||
|
"admin,eth,web3,debug",
|
||||||
|
"--unlock", keyfileAcct.Address.Hex(),
|
||||||
|
"--password", pwdFilePath,
|
||||||
|
}
|
||||||
|
g := runGeth(t, args...)
|
||||||
|
if !task.testGethRef.CompareAndSwap(nil, g) {
|
||||||
|
// task cancelled
|
||||||
|
go g.Kill()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
task.Cleanup(t)
|
||||||
|
}()
|
||||||
|
|
||||||
|
client, err := waitForClient(port, 20*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
// fund new acct
|
||||||
|
ctx := context.Background()
|
||||||
|
value := big.NewInt(0).Exp(big.NewInt(10), big.NewInt(22), nil)
|
||||||
|
gasLimit := uint64(21000)
|
||||||
|
gasPrice, err := client.SuggestGasPrice(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKey, err := crypto.GenerateKey()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newAcct := crypto.PubkeyToAddress(privateKey.PublicKey)
|
||||||
|
nonce, err := client.PendingNonceAt(ctx, keyfileAcct.Address)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tx := types.NewTransaction(nonce, newAcct, value, gasLimit, gasPrice, nil)
|
||||||
|
chainID, err := client.ChainID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := sendSignedTransaction(ctx, client, tx, key.PrivateKey, chainID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
balance, err := client.BalanceAt(ctx, newAcct, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if balance.Cmp(big.NewInt(0)) == 0 {
|
||||||
|
return errors.New("no funding yet")
|
||||||
|
}
|
||||||
|
|
||||||
|
// send 7702 tx
|
||||||
|
nonce, err = client.PendingNonceAt(ctx, newAcct)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
signed, err := types.SignSetCode(privateKey, types.SetCodeAuthorization{
|
||||||
|
Address: common.HexToAddress("0xdeadbeef00000000000000000000000000000000"),
|
||||||
|
ChainID: *uint256.NewInt(chainID.Uint64()),
|
||||||
|
Nonce: nonce + 1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
codeTx := &types.SetCodeTx{
|
||||||
|
ChainID: uint256.NewInt(chainID.Uint64()),
|
||||||
|
Nonce: nonce,
|
||||||
|
Gas: 200000,
|
||||||
|
To: newAcct,
|
||||||
|
Value: uint256.NewInt(0),
|
||||||
|
Data: []byte("0x"),
|
||||||
|
GasTipCap: uint256.NewInt(10e11),
|
||||||
|
GasFeeCap: uint256.NewInt(10e11),
|
||||||
|
AccessList: []types.AccessTuple{},
|
||||||
|
AuthList: []types.SetCodeAuthorization{signed},
|
||||||
|
}
|
||||||
|
tx = types.NewTx(codeTx)
|
||||||
|
if err := sendSignedTransaction(ctx, client, tx, privateKey, chainID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// the default block id is "latest"
|
||||||
|
code, err := client.CodeAt(ctx, newAcct, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(common.Bytes2Hex(code), "ef0100deadbeef") {
|
||||||
|
return &UnexpectedCodeError{Message: "Code was not set!"}
|
||||||
|
}
|
||||||
|
|
||||||
|
codeTx.Nonce = nonce + 2
|
||||||
|
signed, err = types.SignSetCode(privateKey, types.SetCodeAuthorization{
|
||||||
|
Address: common.HexToAddress("0x0000000000000000000000000000000000000000"),
|
||||||
|
ChainID: *uint256.NewInt(chainID.Uint64()),
|
||||||
|
Nonce: nonce + 3,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
codeTx.AuthList = []types.SetCodeAuthorization{signed}
|
||||||
|
// clear code
|
||||||
|
clearTx := types.NewTx(codeTx)
|
||||||
|
if err := sendSignedTransaction(ctx, client, clearTx, privateKey, chainID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
clearedCode, err := client.CodeAt(ctx, newAcct, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(clearedCode) != 0 {
|
||||||
|
return &UnexpectedCodeError{Message: "Code was not cleared!"}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendSignedTransaction(
|
||||||
|
ctx context.Context,
|
||||||
|
client *ethclient.Client,
|
||||||
|
tx *types.Transaction,
|
||||||
|
privateKey *ecdsa.PrivateKey,
|
||||||
|
chainID *big.Int,
|
||||||
|
) error {
|
||||||
|
signedTx, err := types.SignTx(tx, types.LatestSignerForChainID(chainID), privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := client.SendTransaction(ctx, signedTx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
_, err := client.TransactionReceipt(ctx, signedTx.Hash())
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue