fix: bootnode version RPC call (#467)

This commit is contained in:
Dmitry Holodov
2023-05-08 19:19:19 -06:00
committed by GitHub
parent 3859162e58
commit 0abc9e6139
18 changed files with 254 additions and 160 deletions

View File

@@ -9,9 +9,9 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"math/big"
"net/http" "net/http"
"github.com/athanorlabs/atomic-swap/common"
"github.com/athanorlabs/atomic-swap/net" "github.com/athanorlabs/atomic-swap/net"
"github.com/athanorlabs/atomic-swap/rpc" "github.com/athanorlabs/atomic-swap/rpc"
@@ -23,26 +23,27 @@ var log = logging.Logger("bootnode")
// Config provides the configuration for a bootnode. // Config provides the configuration for a bootnode.
type Config struct { type Config struct {
DataDir string Env common.Environment
Bootnodes []string DataDir string
HostListenIP string Bootnodes []string
Libp2pPort uint16 HostListenIP string
Libp2pKeyFile string Libp2pPort uint16
RPCPort uint16 Libp2pKeyFile string
EthereumChainID *big.Int RPCPort uint16
} }
// RunBootnode assembles and runs a bootnode instance, blocking until the node is // RunBootnode assembles and runs a bootnode instance, blocking until the node is
// shut down. Typically, shutdown happens because a signal handler cancels the // shut down. Typically, shutdown happens because a signal handler cancels the
// passed in context, or when the shutdown RPC method is called. // passed in context, or when the shutdown RPC method is called.
func RunBootnode(ctx context.Context, cfg *Config) error { func RunBootnode(ctx context.Context, cfg *Config) error {
chainID := common.ChainIDFromEnv(cfg.Env)
host, err := net.NewHost(&net.Config{ host, err := net.NewHost(&net.Config{
Ctx: ctx, Ctx: ctx,
DataDir: cfg.DataDir, DataDir: cfg.DataDir,
Port: cfg.Libp2pPort, Port: cfg.Libp2pPort,
KeyFile: cfg.Libp2pKeyFile, KeyFile: cfg.Libp2pKeyFile,
Bootnodes: cfg.Bootnodes, Bootnodes: cfg.Bootnodes,
ProtocolID: fmt.Sprintf("%s/%d", net.ProtocolID, cfg.EthereumChainID.Int64()), ProtocolID: fmt.Sprintf("%s/%d", net.ProtocolID, chainID),
ListenIP: cfg.HostListenIP, ListenIP: cfg.HostListenIP,
IsRelayer: false, IsRelayer: false,
IsBootnodeOnly: true, IsBootnodeOnly: true,
@@ -61,9 +62,14 @@ func RunBootnode(ctx context.Context, cfg *Config) error {
} }
rpcServer, err := rpc.NewServer(&rpc.Config{ rpcServer, err := rpc.NewServer(&rpc.Config{
Ctx: ctx, Ctx: ctx,
Address: fmt.Sprintf("127.0.0.1:%d", cfg.RPCPort), Env: cfg.Env,
Net: host, Address: fmt.Sprintf("127.0.0.1:%d", cfg.RPCPort),
Net: host,
XMRTaker: nil,
XMRMaker: nil,
ProtocolBackend: nil,
RecoveryDB: nil,
Namespaces: map[string]struct{}{ Namespaces: map[string]struct{}{
rpc.DaemonNamespace: {}, rpc.DaemonNamespace: {},
rpc.NetNamespace: {}, rpc.NetNamespace: {},

View File

@@ -135,13 +135,13 @@ func runBootnode(c *cli.Context) error {
rpcPort := uint16(c.Uint(flagRPCPort)) rpcPort := uint16(c.Uint(flagRPCPort))
return bootnode.RunBootnode(c.Context, &bootnode.Config{ return bootnode.RunBootnode(c.Context, &bootnode.Config{
DataDir: config.DataDir, Env: config.Env,
Bootnodes: config.Bootnodes, DataDir: config.DataDir,
HostListenIP: hostListenIP, Bootnodes: config.Bootnodes,
Libp2pPort: libp2pPort, HostListenIP: hostListenIP,
Libp2pKeyFile: libp2pKeyFile, Libp2pPort: libp2pPort,
RPCPort: rpcPort, Libp2pKeyFile: libp2pKeyFile,
EthereumChainID: config.EthereumChainID, RPCPort: rpcPort,
}) })
} }

69
cmd/bootnode/main_test.go Normal file
View File

@@ -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()
}

View File

@@ -7,13 +7,9 @@ import (
"context" "context"
"crypto/ecdsa" "crypto/ecdsa"
"fmt" "fmt"
"os"
"path"
"path/filepath"
"time" "time"
"github.com/athanorlabs/atomic-swap/common" "github.com/athanorlabs/atomic-swap/common"
"github.com/athanorlabs/atomic-swap/common/vjson"
contracts "github.com/athanorlabs/atomic-swap/ethereum" contracts "github.com/athanorlabs/atomic-swap/ethereum"
"github.com/athanorlabs/atomic-swap/ethereum/extethclient" "github.com/athanorlabs/atomic-swap/ethereum/extethclient"
@@ -21,23 +17,14 @@ import (
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
) )
const (
contractAddressesFile = "contract-addresses.json"
)
var ( var (
errNoEthPrivateKey = fmt.Errorf("must provide --%s file for non-development environment", flagEthPrivKey) errNoEthPrivateKey = fmt.Errorf("must provide --%s file for non-development environment", flagEthPrivKey)
) )
type contractAddresses struct {
SwapCreatorAddr ethcommon.Address `json:"swapCreatorAddr" validate:"required"`
}
func getOrDeploySwapCreator( func getOrDeploySwapCreator(
ctx context.Context, ctx context.Context,
swapCreatorAddr ethcommon.Address, swapCreatorAddr ethcommon.Address,
env common.Environment, env common.Environment,
dataDir string,
ec extethclient.EthClient, ec extethclient.EthClient,
) (ethcommon.Address, error) { ) (ethcommon.Address, error) {
var err error var err error
@@ -47,7 +34,7 @@ func getOrDeploySwapCreator(
time.Sleep(10 * time.Second) 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 { if err != nil {
return ethcommon.Address{}, fmt.Errorf("failed to deploy swap creator: %w", err) return ethcommon.Address{}, fmt.Errorf("failed to deploy swap creator: %w", err)
} }
@@ -68,7 +55,6 @@ func deploySwapCreator(
ctx context.Context, ctx context.Context,
ec *ethclient.Client, ec *ethclient.Client,
privkey *ecdsa.PrivateKey, privkey *ecdsa.PrivateKey,
dataDir string,
) (ethcommon.Address, error) { ) (ethcommon.Address, error) {
if privkey == nil { if privkey == nil {
return ethcommon.Address{}, errNoEthPrivateKey return ethcommon.Address{}, errNoEthPrivateKey
@@ -79,25 +65,5 @@ func deploySwapCreator(
return ethcommon.Address{}, err 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 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)
}

View File

@@ -18,13 +18,11 @@ import (
func TestGetOrDeploySwapCreator_Deploy(t *testing.T) { func TestGetOrDeploySwapCreator_Deploy(t *testing.T) {
pk := tests.GetTakerTestKey(t) pk := tests.GetTakerTestKey(t)
ec := extethclient.CreateTestClient(t, pk) ec := extethclient.CreateTestClient(t, pk)
tmpDir := t.TempDir()
_, err := getOrDeploySwapCreator( _, err := getOrDeploySwapCreator(
context.Background(), context.Background(),
ethcommon.Address{}, ethcommon.Address{},
common.Development, common.Development,
tmpDir,
ec, ec,
) )
require.NoError(t, err) require.NoError(t, err)
@@ -33,14 +31,12 @@ func TestGetOrDeploySwapCreator_Deploy(t *testing.T) {
func TestGetOrDeploySwapCreator_Get(t *testing.T) { func TestGetOrDeploySwapCreator_Get(t *testing.T) {
pk := tests.GetTakerTestKey(t) pk := tests.GetTakerTestKey(t)
ec := extethclient.CreateTestClient(t, pk) ec := extethclient.CreateTestClient(t, pk)
tmpDir := t.TempDir()
// deploy and get address // deploy and get address
address, err := getOrDeploySwapCreator( address, err := getOrDeploySwapCreator(
context.Background(), context.Background(),
ethcommon.Address{}, ethcommon.Address{},
common.Development, common.Development,
tmpDir,
ec, ec,
) )
require.NoError(t, err) require.NoError(t, err)
@@ -49,7 +45,6 @@ func TestGetOrDeploySwapCreator_Get(t *testing.T) {
context.Background(), context.Background(),
address, address,
common.Development, common.Development,
tmpDir,
ec, ec,
) )
require.NoError(t, err) require.NoError(t, err)

View File

@@ -368,7 +368,6 @@ func validateOrDeployContracts(c *cli.Context, envConf *common.Config, ec exteth
c.Context, c.Context,
envConf.SwapCreatorAddr, envConf.SwapCreatorAddr,
envConf.Env, envConf.Env,
envConf.DataDir,
ec, ec,
) )
if err != nil { if err != nil {

View File

@@ -5,7 +5,6 @@ package main
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"os" "os"
"path" "path"
@@ -78,8 +77,14 @@ func TestDaemon_DevXMRTaker(t *testing.T) {
assert.NoError(t, err) 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) 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() cancel()
wg.Wait() wg.Wait()
@@ -87,21 +92,9 @@ func TestDaemon_DevXMRTaker(t *testing.T) {
return 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) ec, _ := tests.NewEthClient(t)
ecCtx := context.Background() ecCtx := context.Background()
err = contracts.CheckSwapCreatorContractCode(ecCtx, ec, ethcommon.HexToAddress(swapCreatorAddr)) err = contracts.CheckSwapCreatorContractCode(ecCtx, ec, *versionResp.SwapCreatorAddr)
require.NoError(t, err) require.NoError(t, err)
} }
@@ -111,7 +104,7 @@ func TestDaemon_DevXMRMaker(t *testing.T) {
ec, _ := tests.NewEthClient(t) ec, _ := tests.NewEthClient(t)
// We tested --deploy with the taker, so test passing the contract address here // 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) require.NoError(t, err)
flags := []string{ flags := []string{
@@ -148,7 +141,7 @@ func TestDaemon_BadFlags(t *testing.T) {
ec, _ := tests.NewEthClient(t) ec, _ := tests.NewEthClient(t)
ctx, _ := newTestContext(t) ctx, _ := newTestContext(t)
swapCreatorAddr, err := deploySwapCreator(ctx, ec, key, t.TempDir()) swapCreatorAddr, err := deploySwapCreator(ctx, ec, key)
require.NoError(t, err) require.NoError(t, err)
baseFlags := []string{ baseFlags := []string{

View File

@@ -36,7 +36,6 @@ type MoneroNode struct {
type Config struct { type Config struct {
Env Environment Env Environment
DataDir string DataDir string
EthereumChainID *big.Int
EthEndpoint string EthEndpoint string
MoneroNodes []*MoneroNode MoneroNodes []*MoneroNode
SwapCreatorAddr ethcommon.Address SwapCreatorAddr ethcommon.Address
@@ -46,10 +45,9 @@ type Config struct {
// MainnetConfig is the mainnet ethereum and monero configuration // MainnetConfig is the mainnet ethereum and monero configuration
func MainnetConfig() *Config { func MainnetConfig() *Config {
return &Config{ return &Config{
Env: Mainnet, Env: Mainnet,
DataDir: path.Join(baseDir, "mainnet"), DataDir: path.Join(baseDir, "mainnet"),
EthereumChainID: big.NewInt(MainnetChainID), EthEndpoint: "", // No mainnet default (permissionless URLs are not reliable)
EthEndpoint: "", // No mainnet default (permissionless URLs are not reliable)
MoneroNodes: []*MoneroNode{ MoneroNodes: []*MoneroNode{
{ {
Host: "node.sethforprivacy.com", Host: "node.sethforprivacy.com",
@@ -84,10 +82,9 @@ func MainnetConfig() *Config {
// StagenetConfig is the monero stagenet and ethereum Sepolia configuration // StagenetConfig is the monero stagenet and ethereum Sepolia configuration
func StagenetConfig() *Config { func StagenetConfig() *Config {
return &Config{ return &Config{
Env: Stagenet, Env: Stagenet,
DataDir: path.Join(baseDir, "stagenet"), DataDir: path.Join(baseDir, "stagenet"),
EthereumChainID: big.NewInt(SepoliaChainID), EthEndpoint: "https://rpc.sepolia.org/",
EthEndpoint: "https://rpc.sepolia.org/",
MoneroNodes: []*MoneroNode{ MoneroNodes: []*MoneroNode{
{ {
Host: "node.sethforprivacy.com", Host: "node.sethforprivacy.com",
@@ -119,10 +116,9 @@ func StagenetConfig() *Config {
// DevelopmentConfig is the monero and ethereum development environment configuration // DevelopmentConfig is the monero and ethereum development environment configuration
func DevelopmentConfig() *Config { func DevelopmentConfig() *Config {
return &Config{ return &Config{
Env: Development, Env: Development,
EthereumChainID: big.NewInt(1337), DataDir: path.Join(baseDir, "dev"),
DataDir: path.Join(baseDir, "dev"), EthEndpoint: DefaultGanacheEndpoint,
EthEndpoint: DefaultGanacheEndpoint,
MoneroNodes: []*MoneroNode{ MoneroNodes: []*MoneroNode{
{ {
Host: "127.0.0.1", Host: "127.0.0.1",
@@ -190,3 +186,18 @@ func DefaultMoneroPortFromEnv(env Environment) uint {
panic("invalid environment") 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")
}
}

View File

@@ -149,6 +149,7 @@ func RunSwapDaemon(ctx context.Context, conf *SwapdConfig) (err error) {
rpcServer, err := rpc.NewServer(&rpc.Config{ rpcServer, err := rpc.NewServer(&rpc.Config{
Ctx: ctx, Ctx: ctx,
Env: conf.EnvConf.Env,
Address: fmt.Sprintf("127.0.0.1:%d", conf.RPCPort), Address: fmt.Sprintf("127.0.0.1:%d", conf.RPCPort),
Net: host, Net: host,
XMRTaker: xmrTaker, XMRTaker: xmrTaker,

View File

@@ -479,7 +479,7 @@ func TestRunSwapDaemon_RPC_Version(t *testing.T) {
require.Equal(t, conf.EnvConf.Env, versionResp.Env) require.Equal(t, conf.EnvConf.Env, versionResp.Env)
require.NotEmpty(t, versionResp.SwapdVersion) 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) require.Equal(t, protocolVersion, versionResp.P2PVersion)
} }

View File

@@ -99,11 +99,10 @@ example below:
```bash ```bash
BOOT_NODE=/ip4/127.0.0.1/udp/9933/quic-v1/p2p/12D3KooWHRi24PVZ6TBnQJHdVyewDRcKFZtYV3qmB4KQo8iMyqik 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, Now get the ethereum contract address that Alice deployed to. This can be pulled
the file ..., or if you have `jq` installed (available via `sudo apt install jq`), you can set a from the Alice's logs, or from a `version` RPC request.
variable like this:
```bash ```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: Now start Bob's swapd instance:

View File

@@ -13,13 +13,19 @@ import (
// DaemonService handles RPC requests for swapd version, administration and (in the future) status requests. // DaemonService handles RPC requests for swapd version, administration and (in the future) status requests.
type DaemonService struct { type DaemonService struct {
stopServer func() stopServer func()
pb ProtocolBackend env common.Environment
swapCreatorAddr *ethcommon.Address
} }
// NewDaemonService ... // NewDaemonService creates a new daemon service. `swapCreatorAddr` is optional
func NewDaemonService(stopServer func(), pb ProtocolBackend) *DaemonService { // and not set by bootnodes.
return &DaemonService{stopServer, pb} func NewDaemonService(stopServer func(), env common.Environment, swapCreatorAddr *ethcommon.Address) *DaemonService {
return &DaemonService{
stopServer: stopServer,
env: env,
swapCreatorAddr: swapCreatorAddr,
}
} }
// Shutdown swapd // Shutdown swapd
@@ -28,19 +34,20 @@ func (s *DaemonService) Shutdown(_ *http.Request, _ *any, _ *any) error {
return nil 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 { type VersionResponse struct {
SwapdVersion string `json:"swapdVersion" validate:"required"` SwapdVersion string `json:"swapdVersion" validate:"required"`
P2PVersion string `json:"p2pVersion" validate:"required"` P2PVersion string `json:"p2pVersion" validate:"required"`
Env common.Environment `json:"env" 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 // Version returns version & misc info about swapd and its dependencies
func (s *DaemonService) Version(_ *http.Request, _ *any, resp *VersionResponse) error { func (s *DaemonService) Version(_ *http.Request, _ *any, resp *VersionResponse) error {
resp.SwapdVersion = cliutil.GetVersion() resp.SwapdVersion = cliutil.GetVersion()
resp.P2PVersion = fmt.Sprintf("%s/%d", net.ProtocolID, s.pb.ETHClient().ChainID()) resp.P2PVersion = fmt.Sprintf("%s/%d", net.ProtocolID, common.ChainIDFromEnv(s.env))
resp.Env = s.pb.Env() resp.Env = s.env
resp.SwapCreatorAddr = s.pb.SwapCreatorAddr() resp.SwapCreatorAddr = s.swapCreatorAddr
return nil return nil
} }

View File

@@ -235,7 +235,7 @@ func (*mockProtocolBackend) ETHClient() extethclient.EthClient {
} }
func (*mockProtocolBackend) SwapCreatorAddr() ethcommon.Address { func (*mockProtocolBackend) SwapCreatorAddr() ethcommon.Address {
panic("not implemented") return ethcommon.Address{}
} }
func (*mockProtocolBackend) TransferXMR(_ *mcrypto.Address, _ *coins.PiconeroAmount) (string, error) { func (*mockProtocolBackend) TransferXMR(_ *mcrypto.Address, _ *coins.PiconeroAmount) (string, error) {

View File

@@ -22,7 +22,6 @@ import (
"github.com/gorilla/rpc/v2" "github.com/gorilla/rpc/v2"
logging "github.com/ipfs/go-log" logging "github.com/ipfs/go-log"
"github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/peer"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/athanorlabs/atomic-swap/coins" "github.com/athanorlabs/atomic-swap/coins"
@@ -54,12 +53,13 @@ type Server struct {
// Config ... // Config ...
type Config struct { type Config struct {
Ctx context.Context Ctx context.Context
Env common.Environment
Address string // "IP:port" Address string // "IP:port"
Net Net Net Net
XMRTaker XMRTaker XMRTaker XMRTaker // nil on bootnodes
XMRMaker XMRMaker XMRMaker XMRMaker // nil on bootnodes
ProtocolBackend ProtocolBackend ProtocolBackend ProtocolBackend // nil on bootnodes
RecoveryDB RecoveryDB RecoveryDB RecoveryDB // nil on bootnodes
Namespaces map[string]struct{} Namespaces map[string]struct{}
IsBootnodeOnly bool IsBootnodeOnly bool
} }
@@ -81,13 +81,19 @@ func NewServer(cfg *Config) (*Server, error) {
rpcServer.RegisterCodec(NewCodec(), "application/json") rpcServer.RegisterCodec(NewCodec(), "application/json")
serverCtx, serverCancel := context.WithCancel(cfg.Ctx) 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 { if err != nil {
return nil, err return nil, err
} }
var swapManager swap.Manager var swapManager swap.Manager
if cfg.ProtocolBackend != nil { if !cfg.IsBootnodeOnly {
swapManager = cfg.ProtocolBackend.SwapManager() swapManager = cfg.ProtocolBackend.SwapManager()
} }
@@ -139,13 +145,15 @@ func NewServer(cfg *Config) (*Server, error) {
return nil, err 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 := mux.NewRouter()
r.Handle("/", rpcServer) r.Handle("/", rpcServer)
r.Handle("/ws", wsServer) 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"}) headersOk := handlers.AllowedHeaders([]string{"content-type", "username", "password"})
methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "OPTIONS"}) methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "OPTIONS"})
originsOk := handlers.AllowedOrigins([]string{"*"}) originsOk := handlers.AllowedOrigins([]string{"*"})

View File

@@ -15,6 +15,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/athanorlabs/atomic-swap/coins" "github.com/athanorlabs/atomic-swap/coins"
"github.com/athanorlabs/atomic-swap/common"
"github.com/athanorlabs/atomic-swap/common/types" "github.com/athanorlabs/atomic-swap/common/types"
"github.com/athanorlabs/atomic-swap/rpcclient/wsclient" "github.com/athanorlabs/atomic-swap/rpcclient/wsclient"
) )
@@ -30,6 +31,7 @@ func newServer(t *testing.T) *Server {
cfg := &Config{ cfg := &Config{
Ctx: ctx, Ctx: ctx,
Env: common.Development,
Address: "127.0.0.1:0", // OS assigned port Address: "127.0.0.1:0", // OS assigned port
Net: new(mockNet), Net: new(mockNet),
ProtocolBackend: newMockProtocolBackend(), ProtocolBackend: newMockProtocolBackend(),

View File

@@ -21,6 +21,9 @@ fi
echo "Stopping any monero-wallet-rpc instances" echo "Stopping any monero-wallet-rpc instances"
"${pkill_cmd[@]}" '/monero-wallet-rpc ' "${pkill_cmd[@]}" '/monero-wallet-rpc '
echo "Stopping any bootnode instances"
"${pkill_cmd[@]}" '/bootnode '
echo "Stopping any monerod regest instances" echo "Stopping any monerod regest instances"
if "${pkill_cmd[@]}" '/monerod .* --regtest '; then if "${pkill_cmd[@]}" '/monerod .* --regtest '; then
sleep 2 # we don't want to exit the script while it is still running sleep 2 # we don't want to exit the script while it is still running

View File

@@ -32,41 +32,62 @@ create-eth-keys() {
echo "${GANACHE_KEYS[${i}]}" >"${key_file}" echo "${GANACHE_KEYS[${i}]}" >"${key_file}"
done 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 LOG_LEVEL=debug
BOOTNODE_RPC_PORT=4999
ALICE_RPC_PORT=5000 ALICE_RPC_PORT=5000
BOB_RPC_PORT=5001 BOB_RPC_PORT=5001
CHARLIE_RPC_PORT=5002 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() { start-swapd() {
local swapd_user="${1:?}" local swapd_user="${1:?}"
local rpc_port="${2:?}" 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" 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}" & ./bin/swapd "${swapd_flags[@]}" &>"${log_file}" &
local swapd_pid="${!}" local swapd_pid="${!}"
echo "${swapd_pid}" >"${SWAP_TEST_DATA_DIR}/${swapd_user}-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 if wait-rpc-started "${swapd_user}" "${swapd_pid}" "${rpc_port}"; then
for i in {1..60}; do return 0 # success, bypass failure code below
sleep 1 fi
# 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
echo "Failed to start ${swapd_user^}'s swapd" echo "Failed to start ${swapd_user^}'s swapd"
echo "=============== Failed logs ===============" echo "=============== Failed logs ==============="
@@ -81,32 +102,49 @@ stop-swapd() {
stop-program "${swapd_user}-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() { wait-rpc-started() {
local swapd_user="${1}" local user="${1}"
local rpc_port="${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-daemons() {
start-monerod-regtest start-monerod-regtest
start-ganache 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}" \ start-swapd alice "${ALICE_RPC_PORT}" \
--dev-xmrtaker \ --dev-xmrtaker \
"--log-level=${LOG_LEVEL}" \ "--bootnodes=${bootnode_addr}" \
"--data-dir=${SWAP_TEST_DATA_DIR}/alice" \ "--data-dir=${SWAP_TEST_DATA_DIR}/alice" \
"--libp2p-key=${ALICE_LIBP2PKEY}" \
--deploy --deploy
CONTRACT_ADDR_FILE="${SWAP_TEST_DATA_DIR}/alice/contract-addresses.json" local contract_addr
if [[ ! -f "${CONTRACT_ADDR_FILE}" ]]; then contract_addr="$(./bin/swapcli version | grep '^swap creator address' | sed 's/.*: //')"
echo "Failed to get Alice's deployed contract address file" if [[ -z "${contract_addr}" ]]; then
stop-daemons
exit 1
fi
SWAP_CREATOR_ADDR="$(jq -r .swapCreatorAddr "${CONTRACT_ADDR_FILE}")"
if [[ -z "${SWAP_CREATOR_ADDR}" ]]; then
echo "Failed to get Alice's deployed contract addresses" echo "Failed to get Alice's deployed contract addresses"
stop-daemons stop-daemons
exit 1 exit 1
@@ -114,19 +152,16 @@ start-daemons() {
start-swapd bob "${BOB_RPC_PORT}" \ start-swapd bob "${BOB_RPC_PORT}" \
--dev-xmrmaker \ --dev-xmrmaker \
"--log-level=${LOG_LEVEL}" \ "--bootnodes=${bootnode_addr}" \
"--data-dir=${SWAP_TEST_DATA_DIR}/bob" \ "--data-dir=${SWAP_TEST_DATA_DIR}/bob" \
--libp2p-port=9944 \ --libp2p-port=9944 \
"--bootnodes=${ALICE_MULTIADDR}" \ "--contract-address=${contract_addr}"
"--contract-address=${SWAP_CREATOR_ADDR}"
start-swapd charlie "${CHARLIE_RPC_PORT}" \ start-swapd charlie "${CHARLIE_RPC_PORT}" \
"--env=dev" \ "--bootnodes=${bootnode_addr}" \
"--log-level=${LOG_LEVEL}" \
--data-dir "${SWAP_TEST_DATA_DIR}/charlie" \ --data-dir "${SWAP_TEST_DATA_DIR}/charlie" \
--libp2p-port=9955 \ --libp2p-port=9955 \
"--bootnodes=${ALICE_MULTIADDR}" \ "--contract-address=${contract_addr}" \
"--contract-address=${SWAP_CREATOR_ADDR}" \
"--relayer" "--relayer"
} }
@@ -134,6 +169,7 @@ stop-daemons() {
stop-swapd charlie stop-swapd charlie
stop-swapd bob stop-swapd bob
stop-swapd alice stop-swapd alice
stop-bootnode
stop-monerod-regtest stop-monerod-regtest
stop-ganache stop-ganache
} }
@@ -142,14 +178,14 @@ stop-daemons() {
echo "running integration tests..." echo "running integration tests..."
create-eth-keys create-eth-keys
start-daemons 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="${?}" OK="${?}"
KEEP_TEST_DATA="${OK}" stop-daemons KEEP_TEST_DATA="${OK}" stop-daemons
# Cleanup test files if we succeeded # Cleanup test files if we succeeded
if [[ "${OK}" -eq 0 ]]; then if [[ "${OK}" -eq 0 ]]; then
rm -f "${CHARLIE_ETH_KEY}" 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 -f "${SWAP_TEST_DATA_DIR}/"{alice,bob,charlie}/{net,eth}.key
rm -rf "${SWAP_TEST_DATA_DIR}/"{alice,bob,charlie}/{wallet,libp2p-datastore,db} rm -rf "${SWAP_TEST_DATA_DIR}/"{alice,bob,charlie}/{wallet,libp2p-datastore,db}
rmdir "${SWAP_TEST_DATA_DIR}/"{alice,bob,charlie} rmdir "${SWAP_TEST_DATA_DIR}/"{alice,bob,charlie}

View File

@@ -1 +0,0 @@
d97f10b75b1d5772fa07468f20329b13489b2bf9165e57ae0fe777630830ee9405462a30fedaaa5ebe0f30d8a8c4dc8f141876cd3fc1c8b95a898ec64bc70668