diff --git a/bootnode/bootnode.go b/bootnode/bootnode.go index 1b3b0e10..6a29c3a1 100644 --- a/bootnode/bootnode.go +++ b/bootnode/bootnode.go @@ -9,9 +9,9 @@ import ( "context" "errors" "fmt" - "math/big" "net/http" + "github.com/athanorlabs/atomic-swap/common" "github.com/athanorlabs/atomic-swap/net" "github.com/athanorlabs/atomic-swap/rpc" @@ -23,26 +23,27 @@ var log = logging.Logger("bootnode") // Config provides the configuration for a bootnode. type Config struct { - DataDir string - Bootnodes []string - HostListenIP string - Libp2pPort uint16 - Libp2pKeyFile string - RPCPort uint16 - EthereumChainID *big.Int + Env common.Environment + DataDir string + Bootnodes []string + HostListenIP string + Libp2pPort uint16 + Libp2pKeyFile string + RPCPort uint16 } // RunBootnode assembles and runs a bootnode instance, blocking until the node is // shut down. Typically, shutdown happens because a signal handler cancels the // passed in context, or when the shutdown RPC method is called. func RunBootnode(ctx context.Context, cfg *Config) error { + chainID := common.ChainIDFromEnv(cfg.Env) host, err := net.NewHost(&net.Config{ Ctx: ctx, DataDir: cfg.DataDir, Port: cfg.Libp2pPort, KeyFile: cfg.Libp2pKeyFile, Bootnodes: cfg.Bootnodes, - ProtocolID: fmt.Sprintf("%s/%d", net.ProtocolID, cfg.EthereumChainID.Int64()), + ProtocolID: fmt.Sprintf("%s/%d", net.ProtocolID, chainID), ListenIP: cfg.HostListenIP, IsRelayer: false, IsBootnodeOnly: true, @@ -61,9 +62,14 @@ func RunBootnode(ctx context.Context, cfg *Config) error { } rpcServer, err := rpc.NewServer(&rpc.Config{ - Ctx: ctx, - Address: fmt.Sprintf("127.0.0.1:%d", cfg.RPCPort), - Net: host, + Ctx: ctx, + Env: cfg.Env, + Address: fmt.Sprintf("127.0.0.1:%d", cfg.RPCPort), + Net: host, + XMRTaker: nil, + XMRMaker: nil, + ProtocolBackend: nil, + RecoveryDB: nil, Namespaces: map[string]struct{}{ rpc.DaemonNamespace: {}, rpc.NetNamespace: {}, diff --git a/cmd/bootnode/main.go b/cmd/bootnode/main.go index 98dee6b1..bf40168f 100644 --- a/cmd/bootnode/main.go +++ b/cmd/bootnode/main.go @@ -135,13 +135,13 @@ func runBootnode(c *cli.Context) error { rpcPort := uint16(c.Uint(flagRPCPort)) return bootnode.RunBootnode(c.Context, &bootnode.Config{ - DataDir: config.DataDir, - Bootnodes: config.Bootnodes, - HostListenIP: hostListenIP, - Libp2pPort: libp2pPort, - Libp2pKeyFile: libp2pKeyFile, - RPCPort: rpcPort, - EthereumChainID: config.EthereumChainID, + Env: config.Env, + DataDir: config.DataDir, + Bootnodes: config.Bootnodes, + HostListenIP: hostListenIP, + Libp2pPort: libp2pPort, + Libp2pKeyFile: libp2pKeyFile, + RPCPort: rpcPort, }) } diff --git a/cmd/bootnode/main_test.go b/cmd/bootnode/main_test.go new file mode 100644 index 00000000..e8ca90c3 --- /dev/null +++ b/cmd/bootnode/main_test.go @@ -0,0 +1,69 @@ +// Copyright 2023 The AthanorLabs/atomic-swap Authors +// SPDX-License-Identifier: LGPL-3.0-only + +package main + +import ( + "context" + "fmt" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/athanorlabs/atomic-swap/cliutil" + "github.com/athanorlabs/atomic-swap/common" + "github.com/athanorlabs/atomic-swap/daemon" + "github.com/athanorlabs/atomic-swap/rpcclient" +) + +func getFreePort(t *testing.T) uint16 { + port, err := common.GetFreeTCPPort() + require.NoError(t, err) + return uint16(port) +} + +func TestBootnode(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + rpcPort := getFreePort(t) + dataDir := t.TempDir() + + flags := []string{ + "bootnode", + fmt.Sprintf("--%s=dev", flagEnv), + fmt.Sprintf("--%s=debug", cliutil.FlagLogLevel), + fmt.Sprintf("--%s=%s", flagDataDir, dataDir), + fmt.Sprintf("--%s=%d", flagRPCPort, rpcPort), + fmt.Sprintf("--%s=0", flagLibp2pPort), + } + + var wg sync.WaitGroup + wg.Add(1) + + go func() { + defer wg.Done() + err := cliApp().RunContext(ctx, flags) + // We may want to replace context.Cancelled with nil at some point in the code + assert.ErrorIs(t, context.Canceled, err) + }() + + // Ensure the bootnode fully starts before some basic sanity checks + daemon.WaitForSwapdStart(t, rpcPort) + + cli := rpcclient.NewClient(ctx, fmt.Sprintf("http://127.0.0.1:%d", rpcPort)) + versionResp, err := cli.Version() + require.NoError(t, err) + require.NotEmpty(t, versionResp.P2PVersion) + t.Logf("Bootnode p2p version is: %s", versionResp.P2PVersion) + require.Nil(t, versionResp.SwapCreatorAddr) // bootnode does not know the address + + addressResp, err := cli.Addresses() + require.NoError(t, err) + require.Greater(t, len(addressResp.Addrs), 1) + + // We check the contract code below, but we don't need the daemon for that + cli.Shutdown() + wg.Wait() +} diff --git a/cmd/swapd/contract.go b/cmd/swapd/contract.go index c9b89189..53f56933 100644 --- a/cmd/swapd/contract.go +++ b/cmd/swapd/contract.go @@ -7,13 +7,9 @@ import ( "context" "crypto/ecdsa" "fmt" - "os" - "path" - "path/filepath" "time" "github.com/athanorlabs/atomic-swap/common" - "github.com/athanorlabs/atomic-swap/common/vjson" contracts "github.com/athanorlabs/atomic-swap/ethereum" "github.com/athanorlabs/atomic-swap/ethereum/extethclient" @@ -21,23 +17,14 @@ import ( "github.com/ethereum/go-ethereum/ethclient" ) -const ( - contractAddressesFile = "contract-addresses.json" -) - var ( errNoEthPrivateKey = fmt.Errorf("must provide --%s file for non-development environment", flagEthPrivKey) ) -type contractAddresses struct { - SwapCreatorAddr ethcommon.Address `json:"swapCreatorAddr" validate:"required"` -} - func getOrDeploySwapCreator( ctx context.Context, swapCreatorAddr ethcommon.Address, env common.Environment, - dataDir string, ec extethclient.EthClient, ) (ethcommon.Address, error) { var err error @@ -47,7 +34,7 @@ func getOrDeploySwapCreator( time.Sleep(10 * time.Second) } - swapCreatorAddr, err = deploySwapCreator(ctx, ec.Raw(), ec.PrivateKey(), dataDir) + swapCreatorAddr, err = deploySwapCreator(ctx, ec.Raw(), ec.PrivateKey()) if err != nil { return ethcommon.Address{}, fmt.Errorf("failed to deploy swap creator: %w", err) } @@ -68,7 +55,6 @@ func deploySwapCreator( ctx context.Context, ec *ethclient.Client, privkey *ecdsa.PrivateKey, - dataDir string, ) (ethcommon.Address, error) { if privkey == nil { return ethcommon.Address{}, errNoEthPrivateKey @@ -79,25 +65,5 @@ func deploySwapCreator( return ethcommon.Address{}, err } - // store the contract addresses on disk - err = writeContractAddressesToFile( - path.Join(dataDir, contractAddressesFile), - &contractAddresses{ - SwapCreatorAddr: swapCreatorAddr, - }, - ) - if err != nil { - return ethcommon.Address{}, fmt.Errorf("failed to write contract address to file: %w", err) - } - return swapCreatorAddr, nil } - -// writeContractAddressesToFile writes the contract addresses to the given file -func writeContractAddressesToFile(filePath string, addresses *contractAddresses) error { - jsonData, err := vjson.MarshalIndentStruct(addresses, "", " ") - if err != nil { - return err - } - return os.WriteFile(filepath.Clean(filePath), jsonData, 0600) -} diff --git a/cmd/swapd/contract_test.go b/cmd/swapd/contract_test.go index d2936b99..0cbfbc0d 100644 --- a/cmd/swapd/contract_test.go +++ b/cmd/swapd/contract_test.go @@ -18,13 +18,11 @@ import ( func TestGetOrDeploySwapCreator_Deploy(t *testing.T) { pk := tests.GetTakerTestKey(t) ec := extethclient.CreateTestClient(t, pk) - tmpDir := t.TempDir() _, err := getOrDeploySwapCreator( context.Background(), ethcommon.Address{}, common.Development, - tmpDir, ec, ) require.NoError(t, err) @@ -33,14 +31,12 @@ func TestGetOrDeploySwapCreator_Deploy(t *testing.T) { func TestGetOrDeploySwapCreator_Get(t *testing.T) { pk := tests.GetTakerTestKey(t) ec := extethclient.CreateTestClient(t, pk) - tmpDir := t.TempDir() // deploy and get address address, err := getOrDeploySwapCreator( context.Background(), ethcommon.Address{}, common.Development, - tmpDir, ec, ) require.NoError(t, err) @@ -49,7 +45,6 @@ func TestGetOrDeploySwapCreator_Get(t *testing.T) { context.Background(), address, common.Development, - tmpDir, ec, ) require.NoError(t, err) diff --git a/cmd/swapd/main.go b/cmd/swapd/main.go index 312711d5..d3fd7215 100644 --- a/cmd/swapd/main.go +++ b/cmd/swapd/main.go @@ -368,7 +368,6 @@ func validateOrDeployContracts(c *cli.Context, envConf *common.Config, ec exteth c.Context, envConf.SwapCreatorAddr, envConf.Env, - envConf.DataDir, ec, ) if err != nil { diff --git a/cmd/swapd/main_test.go b/cmd/swapd/main_test.go index d319877c..25e27acd 100644 --- a/cmd/swapd/main_test.go +++ b/cmd/swapd/main_test.go @@ -5,7 +5,6 @@ package main import ( "context" - "encoding/json" "fmt" "os" "path" @@ -78,8 +77,14 @@ func TestDaemon_DevXMRTaker(t *testing.T) { assert.NoError(t, err) }() - // Ensure the daemon fully started before we cancel the context + // Ensure the daemon fully before we query the contract address daemon.WaitForSwapdStart(t, rpcPort) + + cli := rpcclient.NewClient(ctx, fmt.Sprintf("http://127.0.0.1:%d", rpcPort)) + versionResp, err := cli.Version() + require.NoError(t, err) + + // We check the contract code below, but we don't need the daemon for that cancel() wg.Wait() @@ -87,21 +92,9 @@ func TestDaemon_DevXMRTaker(t *testing.T) { return } - // - // Validate that --deploy created a contract address file. - // At some future point, we will ask the RPC endpoint - // what the contract addresses are instead of using this file. - // - data, err := os.ReadFile(path.Join(dataDir, contractAddressesFile)) - require.NoError(t, err) - m := make(map[string]string) - require.NoError(t, json.Unmarshal(data, &m)) - swapCreatorAddr, ok := m["swapCreatorAddr"] - require.True(t, ok) - ec, _ := tests.NewEthClient(t) ecCtx := context.Background() - err = contracts.CheckSwapCreatorContractCode(ecCtx, ec, ethcommon.HexToAddress(swapCreatorAddr)) + err = contracts.CheckSwapCreatorContractCode(ecCtx, ec, *versionResp.SwapCreatorAddr) require.NoError(t, err) } @@ -111,7 +104,7 @@ func TestDaemon_DevXMRMaker(t *testing.T) { ec, _ := tests.NewEthClient(t) // We tested --deploy with the taker, so test passing the contract address here - swapCreatorAddr, err := deploySwapCreator(context.Background(), ec, key, t.TempDir()) + swapCreatorAddr, err := deploySwapCreator(context.Background(), ec, key) require.NoError(t, err) flags := []string{ @@ -148,7 +141,7 @@ func TestDaemon_BadFlags(t *testing.T) { ec, _ := tests.NewEthClient(t) ctx, _ := newTestContext(t) - swapCreatorAddr, err := deploySwapCreator(ctx, ec, key, t.TempDir()) + swapCreatorAddr, err := deploySwapCreator(ctx, ec, key) require.NoError(t, err) baseFlags := []string{ diff --git a/common/config.go b/common/config.go index 879e8100..193ece1c 100644 --- a/common/config.go +++ b/common/config.go @@ -36,7 +36,6 @@ type MoneroNode struct { type Config struct { Env Environment DataDir string - EthereumChainID *big.Int EthEndpoint string MoneroNodes []*MoneroNode SwapCreatorAddr ethcommon.Address @@ -46,10 +45,9 @@ type Config struct { // MainnetConfig is the mainnet ethereum and monero configuration func MainnetConfig() *Config { return &Config{ - Env: Mainnet, - DataDir: path.Join(baseDir, "mainnet"), - EthereumChainID: big.NewInt(MainnetChainID), - EthEndpoint: "", // No mainnet default (permissionless URLs are not reliable) + Env: Mainnet, + DataDir: path.Join(baseDir, "mainnet"), + EthEndpoint: "", // No mainnet default (permissionless URLs are not reliable) MoneroNodes: []*MoneroNode{ { Host: "node.sethforprivacy.com", @@ -84,10 +82,9 @@ func MainnetConfig() *Config { // StagenetConfig is the monero stagenet and ethereum Sepolia configuration func StagenetConfig() *Config { return &Config{ - Env: Stagenet, - DataDir: path.Join(baseDir, "stagenet"), - EthereumChainID: big.NewInt(SepoliaChainID), - EthEndpoint: "https://rpc.sepolia.org/", + Env: Stagenet, + DataDir: path.Join(baseDir, "stagenet"), + EthEndpoint: "https://rpc.sepolia.org/", MoneroNodes: []*MoneroNode{ { Host: "node.sethforprivacy.com", @@ -119,10 +116,9 @@ func StagenetConfig() *Config { // DevelopmentConfig is the monero and ethereum development environment configuration func DevelopmentConfig() *Config { return &Config{ - Env: Development, - EthereumChainID: big.NewInt(1337), - DataDir: path.Join(baseDir, "dev"), - EthEndpoint: DefaultGanacheEndpoint, + Env: Development, + DataDir: path.Join(baseDir, "dev"), + EthEndpoint: DefaultGanacheEndpoint, MoneroNodes: []*MoneroNode{ { Host: "127.0.0.1", @@ -190,3 +186,18 @@ func DefaultMoneroPortFromEnv(env Environment) uint { panic("invalid environment") } } + +// ChainIDFromEnv returns the expected chainID that we should find on the +// ethereum endpoint when running int the passed environment. +func ChainIDFromEnv(env Environment) *big.Int { + switch env { + case Development: + return big.NewInt(GanacheChainID) + case Stagenet: + return big.NewInt(SepoliaChainID) + case Mainnet: + return big.NewInt(MainnetChainID) + default: + panic("invalid environment") + } +} diff --git a/daemon/swap_daemon.go b/daemon/swap_daemon.go index d3ce04ae..e2c00ab0 100644 --- a/daemon/swap_daemon.go +++ b/daemon/swap_daemon.go @@ -149,6 +149,7 @@ func RunSwapDaemon(ctx context.Context, conf *SwapdConfig) (err error) { rpcServer, err := rpc.NewServer(&rpc.Config{ Ctx: ctx, + Env: conf.EnvConf.Env, Address: fmt.Sprintf("127.0.0.1:%d", conf.RPCPort), Net: host, XMRTaker: xmrTaker, diff --git a/daemon/swap_daemon_test.go b/daemon/swap_daemon_test.go index 4cc09958..720bcd28 100644 --- a/daemon/swap_daemon_test.go +++ b/daemon/swap_daemon_test.go @@ -479,7 +479,7 @@ func TestRunSwapDaemon_RPC_Version(t *testing.T) { require.Equal(t, conf.EnvConf.Env, versionResp.Env) require.NotEmpty(t, versionResp.SwapdVersion) - require.Equal(t, conf.EnvConf.SwapCreatorAddr, versionResp.SwapCreatorAddr) + require.Equal(t, conf.EnvConf.SwapCreatorAddr, *versionResp.SwapCreatorAddr) require.Equal(t, protocolVersion, versionResp.P2PVersion) } diff --git a/docs/local.md b/docs/local.md index 8e65607b..b0a02882 100644 --- a/docs/local.md +++ b/docs/local.md @@ -99,11 +99,10 @@ example below: ```bash BOOT_NODE=/ip4/127.0.0.1/udp/9933/quic-v1/p2p/12D3KooWHRi24PVZ6TBnQJHdVyewDRcKFZtYV3qmB4KQo8iMyqik ``` -Now get the ethereum contract address that Alice deployed to. This can be pulled from the Alice's logs, -the file ..., or if you have `jq` installed (available via `sudo apt install jq`), you can set a -variable like this: +Now get the ethereum contract address that Alice deployed to. This can be pulled +from the Alice's logs, or from a `version` RPC request. ```bash -CONTRACT_ADDR=$(jq -r .swapCreatorAddr "${TMPDIR-/tmp}"/xmrtaker-*/contract-addresses.json) +CONTRACT_ADDR=$(./bin/swapcli version | grep '^swap creator address' | sed 's/.*: //') ``` Now start Bob's swapd instance: diff --git a/rpc/daemon.go b/rpc/daemon.go index 64a11353..88a810fc 100644 --- a/rpc/daemon.go +++ b/rpc/daemon.go @@ -13,13 +13,19 @@ import ( // DaemonService handles RPC requests for swapd version, administration and (in the future) status requests. type DaemonService struct { - stopServer func() - pb ProtocolBackend + stopServer func() + env common.Environment + swapCreatorAddr *ethcommon.Address } -// NewDaemonService ... -func NewDaemonService(stopServer func(), pb ProtocolBackend) *DaemonService { - return &DaemonService{stopServer, pb} +// NewDaemonService creates a new daemon service. `swapCreatorAddr` is optional +// and not set by bootnodes. +func NewDaemonService(stopServer func(), env common.Environment, swapCreatorAddr *ethcommon.Address) *DaemonService { + return &DaemonService{ + stopServer: stopServer, + env: env, + swapCreatorAddr: swapCreatorAddr, + } } // Shutdown swapd @@ -28,19 +34,20 @@ func (s *DaemonService) Shutdown(_ *http.Request, _ *any, _ *any) error { return nil } -// VersionResponse ... +// VersionResponse contains the version response provided by both swapd and +// bootnodes. In the case of bootnodes, the swapCreatorAddress is nil. type VersionResponse struct { SwapdVersion string `json:"swapdVersion" validate:"required"` P2PVersion string `json:"p2pVersion" validate:"required"` Env common.Environment `json:"env" validate:"required"` - SwapCreatorAddr ethcommon.Address `json:"swapCreatorAddress" validate:"required"` + SwapCreatorAddr *ethcommon.Address `json:"swapCreatorAddress,omitempty"` } // Version returns version & misc info about swapd and its dependencies func (s *DaemonService) Version(_ *http.Request, _ *any, resp *VersionResponse) error { resp.SwapdVersion = cliutil.GetVersion() - resp.P2PVersion = fmt.Sprintf("%s/%d", net.ProtocolID, s.pb.ETHClient().ChainID()) - resp.Env = s.pb.Env() - resp.SwapCreatorAddr = s.pb.SwapCreatorAddr() + resp.P2PVersion = fmt.Sprintf("%s/%d", net.ProtocolID, common.ChainIDFromEnv(s.env)) + resp.Env = s.env + resp.SwapCreatorAddr = s.swapCreatorAddr return nil } diff --git a/rpc/mocks_test.go b/rpc/mocks_test.go index 589fff83..0f03353e 100644 --- a/rpc/mocks_test.go +++ b/rpc/mocks_test.go @@ -235,7 +235,7 @@ func (*mockProtocolBackend) ETHClient() extethclient.EthClient { } func (*mockProtocolBackend) SwapCreatorAddr() ethcommon.Address { - panic("not implemented") + return ethcommon.Address{} } func (*mockProtocolBackend) TransferXMR(_ *mcrypto.Address, _ *coins.PiconeroAmount) (string, error) { diff --git a/rpc/server.go b/rpc/server.go index 2b03f34f..e5c8ce93 100644 --- a/rpc/server.go +++ b/rpc/server.go @@ -22,7 +22,6 @@ import ( "github.com/gorilla/rpc/v2" logging "github.com/ipfs/go-log" "github.com/libp2p/go-libp2p/core/peer" - "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/athanorlabs/atomic-swap/coins" @@ -54,12 +53,13 @@ type Server struct { // Config ... type Config struct { Ctx context.Context + Env common.Environment Address string // "IP:port" Net Net - XMRTaker XMRTaker - XMRMaker XMRMaker - ProtocolBackend ProtocolBackend - RecoveryDB RecoveryDB + XMRTaker XMRTaker // nil on bootnodes + XMRMaker XMRMaker // nil on bootnodes + ProtocolBackend ProtocolBackend // nil on bootnodes + RecoveryDB RecoveryDB // nil on bootnodes Namespaces map[string]struct{} IsBootnodeOnly bool } @@ -81,13 +81,19 @@ func NewServer(cfg *Config) (*Server, error) { rpcServer.RegisterCodec(NewCodec(), "application/json") serverCtx, serverCancel := context.WithCancel(cfg.Ctx) - err := rpcServer.RegisterService(NewDaemonService(serverCancel, cfg.ProtocolBackend), "daemon") + var swapCreatorAddr *ethcommon.Address + if !cfg.IsBootnodeOnly { + addr := cfg.ProtocolBackend.SwapCreatorAddr() + swapCreatorAddr = &addr + } + daemonService := NewDaemonService(serverCancel, cfg.Env, swapCreatorAddr) + err := rpcServer.RegisterService(daemonService, "daemon") if err != nil { return nil, err } var swapManager swap.Manager - if cfg.ProtocolBackend != nil { + if !cfg.IsBootnodeOnly { swapManager = cfg.ProtocolBackend.SwapManager() } @@ -139,13 +145,15 @@ func NewServer(cfg *Config) (*Server, error) { return nil, err } - SetupMetrics(serverCtx, reg, cfg.Net, cfg.ProtocolBackend, cfg.XMRMaker) - + if !cfg.IsBootnodeOnly { + SetupMetrics(serverCtx, reg, cfg.Net, cfg.ProtocolBackend, cfg.XMRMaker) + } r := mux.NewRouter() r.Handle("/", rpcServer) r.Handle("/ws", wsServer) - r.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{})) - + if !cfg.IsBootnodeOnly { + r.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{})) + } headersOk := handlers.AllowedHeaders([]string{"content-type", "username", "password"}) methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "OPTIONS"}) originsOk := handlers.AllowedOrigins([]string{"*"}) diff --git a/rpc/ws_test.go b/rpc/ws_test.go index 553c336c..c2c20ed7 100644 --- a/rpc/ws_test.go +++ b/rpc/ws_test.go @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/require" "github.com/athanorlabs/atomic-swap/coins" + "github.com/athanorlabs/atomic-swap/common" "github.com/athanorlabs/atomic-swap/common/types" "github.com/athanorlabs/atomic-swap/rpcclient/wsclient" ) @@ -30,6 +31,7 @@ func newServer(t *testing.T) *Server { cfg := &Config{ Ctx: ctx, + Env: common.Development, Address: "127.0.0.1:0", // OS assigned port Net: new(mockNet), ProtocolBackend: newMockProtocolBackend(), diff --git a/scripts/cleanup-test-processes.sh b/scripts/cleanup-test-processes.sh index 06a37d0c..7a5839ff 100755 --- a/scripts/cleanup-test-processes.sh +++ b/scripts/cleanup-test-processes.sh @@ -21,6 +21,9 @@ fi echo "Stopping any monero-wallet-rpc instances" "${pkill_cmd[@]}" '/monero-wallet-rpc ' +echo "Stopping any bootnode instances" +"${pkill_cmd[@]}" '/bootnode ' + echo "Stopping any monerod regest instances" if "${pkill_cmd[@]}" '/monerod .* --regtest '; then sleep 2 # we don't want to exit the script while it is still running diff --git a/scripts/run-integration-tests.sh b/scripts/run-integration-tests.sh index cb625b73..156559ed 100755 --- a/scripts/run-integration-tests.sh +++ b/scripts/run-integration-tests.sh @@ -32,41 +32,62 @@ create-eth-keys() { echo "${GANACHE_KEYS[${i}]}" >"${key_file}" done } -# This is the local multiaddr created when using ./tests/alice-libp2p.key on the default libp2p port -ALICE_MULTIADDR=/ip4/127.0.0.1/tcp/9933/p2p/12D3KooWAAxG7eTEHr2uBVw3BDMxYsxyqfKvj3qqqpRGtTfuzTuH -ALICE_LIBP2PKEY=./tests/alice-libp2p.key + LOG_LEVEL=debug +BOOTNODE_RPC_PORT=4999 ALICE_RPC_PORT=5000 BOB_RPC_PORT=5001 CHARLIE_RPC_PORT=5002 +start-bootnode() { + local flags=( + "--rpc-port=${BOOTNODE_RPC_PORT}" + --env=dev + "--log-level=${LOG_LEVEL}" + ) + local log_file="${SWAP_TEST_DATA_DIR}/bootnode.log" + + echo "Starting bootnode, logs in ${log_file}" + ./bin/bootnode "${flags[@]}" &>"${log_file}" & + local pid="${!}" + echo "${pid}" >"${SWAP_TEST_DATA_DIR}/bootnode.pid" + + if wait-rpc-started "bootnode" "${pid}" "${BOOTNODE_RPC_PORT}"; then + return 0 # success + fi + + echo "Failed to start bootnode" + echo "=============== Failed logs ===============" + cat "${log_file}" + echo "============================================" + stop-daemons + exit 1 +} + +stop-bootnode() { + stop-program "bootnode" +} + start-swapd() { local swapd_user="${1:?}" local rpc_port="${2:?}" - local swapd_flags=("${@:3}" "--rpc-port=${rpc_port}") + local swapd_flags=( + "${@:3}" + --env=dev + "--rpc-port=${rpc_port}" + "--log-level=${LOG_LEVEL}" + ) local log_file="${SWAP_TEST_DATA_DIR}/${swapd_user}-swapd.log" - echo "Starting ${swapd_user^}'s swapd, logs in ${SWAP_TEST_DATA_DIR}/${swapd_user}-swapd.log" + echo "Starting ${swapd_user^}'s swapd, logs in ${log_file}" ./bin/swapd "${swapd_flags[@]}" &>"${log_file}" & local swapd_pid="${!}" echo "${swapd_pid}" >"${SWAP_TEST_DATA_DIR}/${swapd_user}-swapd.pid" - # Wait up to 60 seconds for the daemon's port to be listening - for i in {1..60}; do - sleep 1 - - # Test if pid is still alive, leave loop if it is not - if ! kill -0 "${swapd_pid}" 2>/dev/null; then - break - fi - - # Test if RPC port is listening, exit success if it is - if is-port-open "${rpc_port}"; then - echo "${swapd_user^}'s swapd instance is listening after ${i} seconds" - return - fi - done + if wait-rpc-started "${swapd_user}" "${swapd_pid}" "${rpc_port}"; then + return 0 # success, bypass failure code below + fi echo "Failed to start ${swapd_user^}'s swapd" echo "=============== Failed logs ===============" @@ -81,32 +102,49 @@ stop-swapd() { stop-program "${swapd_user}-swapd" } +# Waits for up to 60 seconds for the RPC port to be listening. +# If the passed PID exits, we stop waiting. This method only +# returns success(0)/failure(1) and will not exit the script. wait-rpc-started() { - local swapd_user="${1}" - local rpc_port="${1}" + local user="${1}" + local pid="${2}" + local port="${3}" + # Wait up to 60 seconds for the daemon's port to be listening + for i in {1..60}; do + sleep 1 + + # Test if pid is still alive, fail if it isn't + if ! kill -0 "${pid}" 2>/dev/null; then + return 1 # fail + fi + + # Test if RPC port is listening, return success if it is + if is-port-open "${port}"; then + echo "${user^}'s instance is listening after ${i} seconds" + return 0 # success + fi + done + + return 1 # fail } start-daemons() { start-monerod-regtest start-ganache + start-bootnode + local bootnode_addr + bootnode_addr="$(./bin/swapcli addresses --swapd-port ${BOOTNODE_RPC_PORT} | grep '^1:' | sed 's/.* //')" start-swapd alice "${ALICE_RPC_PORT}" \ --dev-xmrtaker \ - "--log-level=${LOG_LEVEL}" \ + "--bootnodes=${bootnode_addr}" \ "--data-dir=${SWAP_TEST_DATA_DIR}/alice" \ - "--libp2p-key=${ALICE_LIBP2PKEY}" \ --deploy - CONTRACT_ADDR_FILE="${SWAP_TEST_DATA_DIR}/alice/contract-addresses.json" - if [[ ! -f "${CONTRACT_ADDR_FILE}" ]]; then - echo "Failed to get Alice's deployed contract address file" - stop-daemons - exit 1 - fi - - SWAP_CREATOR_ADDR="$(jq -r .swapCreatorAddr "${CONTRACT_ADDR_FILE}")" - if [[ -z "${SWAP_CREATOR_ADDR}" ]]; then + local contract_addr + contract_addr="$(./bin/swapcli version | grep '^swap creator address' | sed 's/.*: //')" + if [[ -z "${contract_addr}" ]]; then echo "Failed to get Alice's deployed contract addresses" stop-daemons exit 1 @@ -114,19 +152,16 @@ start-daemons() { start-swapd bob "${BOB_RPC_PORT}" \ --dev-xmrmaker \ - "--log-level=${LOG_LEVEL}" \ + "--bootnodes=${bootnode_addr}" \ "--data-dir=${SWAP_TEST_DATA_DIR}/bob" \ --libp2p-port=9944 \ - "--bootnodes=${ALICE_MULTIADDR}" \ - "--contract-address=${SWAP_CREATOR_ADDR}" + "--contract-address=${contract_addr}" start-swapd charlie "${CHARLIE_RPC_PORT}" \ - "--env=dev" \ - "--log-level=${LOG_LEVEL}" \ + "--bootnodes=${bootnode_addr}" \ --data-dir "${SWAP_TEST_DATA_DIR}/charlie" \ --libp2p-port=9955 \ - "--bootnodes=${ALICE_MULTIADDR}" \ - "--contract-address=${SWAP_CREATOR_ADDR}" \ + "--contract-address=${contract_addr}" \ "--relayer" } @@ -134,6 +169,7 @@ stop-daemons() { stop-swapd charlie stop-swapd bob stop-swapd alice + stop-bootnode stop-monerod-regtest stop-ganache } @@ -142,14 +178,14 @@ stop-daemons() { echo "running integration tests..." create-eth-keys start-daemons -TESTS=integration CONTRACT_ADDR=${SWAP_CREATOR_ADDR} go test ./tests -v -count=1 -timeout=30m +TESTS=integration go test ./tests -v -count=1 -timeout=30m OK="${?}" KEEP_TEST_DATA="${OK}" stop-daemons # Cleanup test files if we succeeded if [[ "${OK}" -eq 0 ]]; then rm -f "${CHARLIE_ETH_KEY}" - rm -f "${SWAP_TEST_DATA_DIR}/"{alice,bob,charlie}/{contract-addresses.json,monero-wallet-rpc.log} + rm -f "${SWAP_TEST_DATA_DIR}/"{alice,bob,charlie}/monero-wallet-rpc.log rm -f "${SWAP_TEST_DATA_DIR}/"{alice,bob,charlie}/{net,eth}.key rm -rf "${SWAP_TEST_DATA_DIR}/"{alice,bob,charlie}/{wallet,libp2p-datastore,db} rmdir "${SWAP_TEST_DATA_DIR}/"{alice,bob,charlie} diff --git a/tests/alice-libp2p.key b/tests/alice-libp2p.key deleted file mode 100644 index af0af442..00000000 --- a/tests/alice-libp2p.key +++ /dev/null @@ -1 +0,0 @@ -d97f10b75b1d5772fa07468f20329b13489b2bf9165e57ae0fe777630830ee9405462a30fedaaa5ebe0f30d8a8c4dc8f141876cd3fc1c8b95a898ec64bc70668 \ No newline at end of file