Skip to content

Commit

Permalink
Merge pull request #9 from testinprod-io/pcw109550/tx-gossip-test
Browse files Browse the repository at this point in the history
Add Transaction Gossip Test
  • Loading branch information
pcw109550 committed Jun 27, 2023
2 parents 1d063bf + 545246b commit aae6800
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 1 deletion.
3 changes: 2 additions & 1 deletion clients/op-erigon/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ set -u
--authrpc.addr=0.0.0.0 \
--nodiscover \
--no-downloader \
--maxpeers=0 \
--maxpeers=50 \
--nat extip:`hostname -i` \
--miner.gaslimit=$GAS_LIMIT \
--networkid=$CHAIN_ID \
$EXTRA_FLAGS \
Expand Down
32 changes: 32 additions & 0 deletions optimism/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,35 @@ func WaitReceipt(ctx context.Context, client *ethclient.Client, hash common.Hash
return receipt, nil
}
}

func WaitPendingTransactionFromTxPool(ctx context.Context, client *OpL2EngineExtended, sender common.Address, tx *types.Transaction) (bool, error) {
nonce := fmt.Sprintf("%d", tx.Nonce())
checkInclusion := func(pendingTxs map[string]*RPCTransactionHash) bool {
if len(pendingTxs) == 0 {
return false
} else if rpcTxHash, ok := pendingTxs[nonce]; !ok {
return false
} else if !bytes.Equal(rpcTxHash.Hash.Bytes(), tx.Hash().Bytes()) {
return false
}
return true
}
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
for {
txpoolcontent, err := client.TxPoolContent(ctx)
if err != nil {
return false, err
}
pendingTxs := txpoolcontent["pending"][sender.String()]
if !checkInclusion(pendingTxs) {
select {
case <-ctx.Done():
return false, ctx.Err()
case <-ticker.C:
continue
}
}
return true, nil
}
}
59 changes: 59 additions & 0 deletions optimism/nodes.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package optimism

import (
"context"
"crypto/ecdsa"
"fmt"

"github.com/ethereum-optimism/optimism/op-node/client"
"github.com/ethereum-optimism/optimism/op-node/p2p"
"github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
gethp2p "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/hive/hivesim"
)

Expand Down Expand Up @@ -65,6 +70,60 @@ type OpL2Engine struct {
ELNode
}

// temporal extenstion of OpL2Engine
// Direct RPC wrapper will be eventually moved to op-erigon or op-geth
type OpL2EngineExtended struct {
*OpL2Engine
}

func (e *OpL2EngineExtended) NodeInfo(ctx context.Context) (*gethp2p.NodeInfo, error) {
var output *gethp2p.NodeInfo
err := e.RPC().CallContext(ctx, &output, "admin_nodeInfo")
return output, err
}

func (e *OpL2EngineExtended) Peers(ctx context.Context) (*gethp2p.PeerInfo, error) {
var output *gethp2p.PeerInfo
err := e.RPC().CallContext(ctx, &output, "admin_peers")
return output, err
}

func (e *OpL2EngineExtended) AddPeer(ctx context.Context, enode string) (bool, error) {
var output bool
err := e.RPC().CallContext(ctx, &output, "admin_addPeer", enode)
return output, err
}

// only interested in txhash for p2p testing
// will be updated to use op-geth's RPCTransaction struct
type RPCTransactionHash struct {
Hash common.Hash `json:"hash"`
}

func (e *OpL2EngineExtended) TxPoolContent(ctx context.Context) (map[string]map[string]map[string]*RPCTransactionHash, error) {
// only return necessary information(tx hash) for testing tx gossip
var output map[string]map[string]map[string]*RPCTransactionHash
err := e.RPC().CallContext(ctx, &output, "txpool_content")
return output, err
}

func (e *OpL2EngineExtended) ConnectPeer(ctx context.Context, neighbor *OpL2EngineExtended) error {
nodeInfo, err := neighbor.NodeInfo(ctx)
if err != nil {
return err
}
// sanity check by parsing result
_, err = enode.Parse(enode.ValidSchemes, nodeInfo.Enode)
if err != nil {
return err
}
_, err = e.AddPeer(ctx, nodeInfo.Enode)
if err != nil {
return err
}
return nil
}

type OpNode struct {
*hivesim.Client
p2pKey *ecdsa.PrivateKey
Expand Down
75 changes: 75 additions & 0 deletions simulators/optimism/p2p/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,86 @@ func main() {
Description: `This test verifies that tx forwarding works`,
Run: func(t *hivesim.T) { txForwardingTest(t) },
})
suite.Add(&hivesim.TestSpec{
Name: "tx gossiping",
Description: `This test verifies that tx gossiping works`,
Run: func(t *hivesim.T) { txGossipingTest(t) },
})

sim := hivesim.New()
hivesim.MustRunSuite(sim, suite)
}

// txGossipingTest verifies that a transaction submitted to a replica execution client
// with tx gossiping enabled shows up on the sequencer tx pool.
func txGossipingTest(t *hivesim.T) {
d := optimism.NewDevnet(t)
sender := d.L2Vault.GenerateKey()
receiver := d.L2Vault.GenerateKey()

d.InitChain(30, 4, 30, core.GenesisAlloc{sender: {Balance: big.NewInt(params.Ether)}})
d.AddEth1()
d.WaitUpEth1(0, time.Second*10)

d.AddOpL2()
d.AddOpNode(0, 0, false)
seqNode := d.GetOpL2Engine(0)

d.AddOpL2()
d.AddOpNode(0, 1, false)
verifNode := d.GetOpL2Engine(1)
verifClient := verifNode.EthClient()

var wg sync.WaitGroup
wg.Add(2)

go func() {
d.WaitUpOpL2Engine(0, time.Second*10)
wg.Done()
}()
go func() {
d.WaitUpOpL2Engine(1, time.Second*10)
wg.Done()
}()

t.Log("waiting for nodes to come up")
wg.Wait()

t.Logf("peering execution clients %s with %s", seqNode.IP.String(), verifNode.IP.String())
seqNodeExtended := &optimism.OpL2EngineExtended{OpL2Engine: seqNode}
verifNodeExtended := &optimism.OpL2EngineExtended{OpL2Engine: verifNode}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
require.NoError(t, seqNodeExtended.ConnectPeer(ctx, verifNodeExtended))

baseTx := types.NewTx(&types.DynamicFeeTx{
ChainID: optimism.L2ChainIDBig,
Nonce: 0,
To: &receiver,
Gas: 75000,
GasTipCap: big.NewInt(10 * params.GWei),
GasFeeCap: big.NewInt(20 * params.GWei),
Value: big.NewInt(0.0001 * params.Ether),
})

tx, err := d.L2Vault.SignTransaction(sender, baseTx)
require.Nil(t, err)
ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
require.NoError(t, verifClient.SendTransaction(ctx, tx))
t.Log("sent tx to verifier, waiting for tx gossip")

<-time.After(10 * time.Second)

ctx, cancel = context.WithTimeout(context.Background(), 180*time.Second)
defer cancel()
found, err := optimism.WaitPendingTransactionFromTxPool(ctx, seqNodeExtended, sender, tx)
if !found {
t.Fatal("transaction not gossiped", "err", err)
}
t.Log("found gossiped transaction on sequencer")
}

// txForwardingTest verifies that a transaction submitted to a replica with tx forwarding enabled shows up on the sequencer.
// TODO: The transaction shows up with `getTransaction`, but it remains pending and is not mined for some reason.
// This is weird, but fine because it still shows that the transaction is received by the sequencer.
Expand Down

0 comments on commit aae6800

Please sign in to comment.