@@ -5,23 +5,30 @@ package docker_e2e
55import (
66 "context"
77 "fmt"
8+ "net"
9+ "net/http"
810 "os"
911 "strings"
1012 "testing"
13+ "time"
1114
1215 "cosmossdk.io/math"
1316 tastoradocker "github.com/celestiaorg/tastora/framework/docker"
1417 "github.com/celestiaorg/tastora/framework/docker/container"
1518 "github.com/celestiaorg/tastora/framework/docker/cosmos"
1619 da "github.com/celestiaorg/tastora/framework/docker/dataavailability"
1720 "github.com/celestiaorg/tastora/framework/docker/evstack"
21+ "github.com/celestiaorg/tastora/framework/docker/evstack/evmsingle"
22+ "github.com/celestiaorg/tastora/framework/docker/evstack/reth"
1823 "github.com/celestiaorg/tastora/framework/testutil/sdkacc"
1924 tastoratypes "github.com/celestiaorg/tastora/framework/types"
2025 sdk "github.com/cosmos/cosmos-sdk/types"
2126 "github.com/cosmos/cosmos-sdk/types/module/testutil"
2227 "github.com/cosmos/cosmos-sdk/x/auth"
2328 "github.com/cosmos/cosmos-sdk/x/bank"
2429 banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
30+ "github.com/ethereum/go-ethereum/common"
31+ "github.com/ethereum/go-ethereum/ethclient"
2532 "github.com/stretchr/testify/suite"
2633)
2734
@@ -243,3 +250,113 @@ func getEvNodeImage() container.Image {
243250
244251 return container .NewImage (repo , tag , "10001:10001" )
245252}
253+
254+ // RethSetup holds the configuration returned after setting up a Reth node.
255+ type RethSetup struct {
256+ Node * reth.Node
257+ EngineURL string // internal container-to-container URL
258+ EthURL string // internal container-to-container URL
259+ EthURLExternal string // host-accessible URL
260+ JWTSecret string
261+ GenesisHash string
262+ }
263+
264+ // SetupRethNode creates and starts a Reth node, waiting for it to be ready.
265+ func (s * DockerTestSuite ) SetupRethNode (ctx context.Context ) RethSetup {
266+ rethNode , err := reth .NewNodeBuilder (s .T ()).
267+ WithGenesis ([]byte (reth .DefaultEvolveGenesisJSON ())).
268+ WithDockerClient (s .dockerClient ).
269+ WithDockerNetworkID (s .dockerNetworkID ).
270+ Build (ctx )
271+ s .Require ().NoError (err )
272+ s .Require ().NoError (rethNode .Start (ctx ))
273+
274+ var setup RethSetup
275+ s .Require ().Eventually (func () bool {
276+ networkInfo , err := rethNode .GetNetworkInfo (ctx )
277+ if err != nil {
278+ return false
279+ }
280+ conn , err := net .DialTimeout ("tcp" , net .JoinHostPort ("0.0.0.0" , networkInfo .External .Ports .RPC ), time .Second )
281+ if err != nil {
282+ return false
283+ }
284+ conn .Close ()
285+ setup .EngineURL = fmt .Sprintf ("http://%s:%s" , networkInfo .Internal .Hostname , networkInfo .Internal .Ports .Engine )
286+ setup .EthURL = fmt .Sprintf ("http://%s:%s" , networkInfo .Internal .Hostname , networkInfo .Internal .Ports .RPC )
287+ setup .EthURLExternal = fmt .Sprintf ("http://0.0.0.0:%s" , networkInfo .External .Ports .RPC )
288+ return true
289+ }, 60 * time .Second , 2 * time .Second , "reth did not start in time" )
290+
291+ genesisHash , err := rethNode .GenesisHash (ctx )
292+ s .Require ().NoError (err )
293+
294+ setup .Node = rethNode
295+ setup .JWTSecret = rethNode .JWTSecretHex ()
296+ setup .GenesisHash = genesisHash
297+ return setup
298+ }
299+
300+ // SetupCelestiaAndDABridge starts Celestia chain and DA bridge, returns the DA address.
301+ func (s * DockerTestSuite ) SetupCelestiaAndDABridge (ctx context.Context ) string {
302+ s .celestia = s .CreateChain ()
303+ s .Require ().NoError (s .celestia .Start (ctx ))
304+
305+ s .daNetwork = s .CreateDANetwork ()
306+ bridgeNode := s .daNetwork .GetBridgeNodes ()[0 ]
307+
308+ chainID := s .celestia .GetChainID ()
309+ genesisHash := s .getGenesisHash (ctx )
310+ networkInfo , err := s .celestia .GetNodes ()[0 ].GetNetworkInfo (ctx )
311+ s .Require ().NoError (err )
312+
313+ s .StartBridgeNode (ctx , bridgeNode , chainID , genesisHash , networkInfo .Internal .Hostname )
314+
315+ daWallet , err := bridgeNode .GetWallet ()
316+ s .Require ().NoError (err )
317+ s .FundWallet (ctx , daWallet , 100_000_000_00 )
318+
319+ bridgeNetworkInfo , err := bridgeNode .GetNetworkInfo (ctx )
320+ s .Require ().NoError (err )
321+ return fmt .Sprintf ("http://%s:%s" , bridgeNetworkInfo .Internal .IP , bridgeNetworkInfo .Internal .Ports .RPC )
322+ }
323+
324+ // WaitForEVMHealthy waits for an evmsingle node to respond to health checks.
325+ func (s * DockerTestSuite ) WaitForEVMHealthy (ctx context.Context , node * evmsingle.Node ) {
326+ networkInfo , err := node .GetNetworkInfo (ctx )
327+ s .Require ().NoError (err )
328+
329+ healthURL := fmt .Sprintf ("http://0.0.0.0:%s/health/live" , networkInfo .External .Ports .RPC )
330+ s .Require ().Eventually (func () bool {
331+ req , _ := http .NewRequestWithContext (ctx , http .MethodGet , healthURL , nil )
332+ resp , err := http .DefaultClient .Do (req )
333+ if err != nil {
334+ return false
335+ }
336+ defer resp .Body .Close ()
337+ return resp .StatusCode == http .StatusOK
338+ }, 120 * time .Second , 2 * time .Second , "evm node did not become healthy" )
339+ }
340+
341+ // SetupEthClient creates an Ethereum client and verifies connectivity.
342+ func (s * DockerTestSuite ) SetupEthClient (ctx context.Context , url , expectedChainID string ) * ethclient.Client {
343+ ethClient , err := ethclient .Dial (url )
344+ s .Require ().NoError (err )
345+
346+ chainID , err := ethClient .ChainID (ctx )
347+ s .Require ().NoError (err )
348+ s .Require ().Equal (expectedChainID , chainID .String ())
349+
350+ return ethClient
351+ }
352+
353+ // WaitForTxIncluded waits for a transaction to be included in a block successfully.
354+ func (s * DockerTestSuite ) WaitForTxIncluded (ctx context.Context , client * ethclient.Client , txHash common.Hash ) {
355+ s .Require ().Eventually (func () bool {
356+ receipt , err := client .TransactionReceipt (ctx , txHash )
357+ if err != nil {
358+ return false
359+ }
360+ return receipt .Status == 1
361+ }, 30 * time .Second , time .Second , "transaction %s was not included" , txHash .Hex ())
362+ }
0 commit comments