mirror of
https://github.com/AthanorLabs/atomic-swap.git
synced 2026-01-10 06:38:04 -05:00
feat: add bootnode binary (#397)
This commit is contained in:
86
bootnode/bootnode.go
Normal file
86
bootnode/bootnode.go
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright 2023 The AthanorLabs/atomic-swap Authors
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
// Package bootnode is responsible for assembling, running and cleanly shutting
|
||||
// down a swap bootnode.
|
||||
package bootnode
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net/http"
|
||||
|
||||
"github.com/athanorlabs/atomic-swap/net"
|
||||
"github.com/athanorlabs/atomic-swap/rpc"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
logging "github.com/ipfs/go-log"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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()),
|
||||
ListenIP: cfg.HostListenIP,
|
||||
IsRelayer: false,
|
||||
IsBootnodeOnly: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if hostErr := host.Stop(); hostErr != nil {
|
||||
err = multierror.Append(err, fmt.Errorf("error shutting down peer-to-peer services: %w", hostErr))
|
||||
}
|
||||
}()
|
||||
|
||||
if err = host.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rpcServer, err := rpc.NewServer(&rpc.Config{
|
||||
Ctx: ctx,
|
||||
Address: fmt.Sprintf("127.0.0.1:%d", cfg.RPCPort),
|
||||
Net: host,
|
||||
Namespaces: map[string]struct{}{
|
||||
rpc.DaemonNamespace: {},
|
||||
rpc.NetNamespace: {},
|
||||
},
|
||||
IsBootnodeOnly: true,
|
||||
})
|
||||
|
||||
log.Infof("starting bootnode with data-dir %s", cfg.DataDir)
|
||||
err = rpcServer.Start()
|
||||
|
||||
if errors.Is(err, http.ErrServerClosed) {
|
||||
// Remove the error for a clean program exit, as ErrServerClosed only
|
||||
// happens when the server is told to shut down
|
||||
err = nil
|
||||
}
|
||||
|
||||
// err can get set in defer blocks, so return err or use an empty
|
||||
// return statement below (not nil)
|
||||
return err
|
||||
}
|
||||
61
cliutil/log.go
Normal file
61
cliutil/log.go
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2023 The AthanorLabs/atomic-swap Authors
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
package cliutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
logging "github.com/ipfs/go-log"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
// FlagLogLevel is the log level flag.
|
||||
FlagLogLevel = "log-level"
|
||||
)
|
||||
|
||||
// SetLogLevelsFromContext sets the log levels for all packages from the CLI context.
|
||||
func SetLogLevelsFromContext(c *cli.Context) error {
|
||||
const (
|
||||
levelError = "error"
|
||||
levelWarn = "warn"
|
||||
levelInfo = "info"
|
||||
levelDebug = "debug"
|
||||
)
|
||||
|
||||
level := c.String(FlagLogLevel)
|
||||
switch level {
|
||||
case levelError, levelWarn, levelInfo, levelDebug:
|
||||
default:
|
||||
return fmt.Errorf("invalid log level %q", level)
|
||||
}
|
||||
|
||||
SetLogLevels(level)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetLogLevels sets the log levels for all packages.
|
||||
func SetLogLevels(level string) {
|
||||
// alphabetically ordered
|
||||
_ = logging.SetLogLevel("bootnode", level)
|
||||
_ = logging.SetLogLevel("cmd", level)
|
||||
_ = logging.SetLogLevel("coins", level)
|
||||
_ = logging.SetLogLevel("common", level)
|
||||
_ = logging.SetLogLevel("contracts", level)
|
||||
_ = logging.SetLogLevel("cmd", level)
|
||||
_ = logging.SetLogLevel("extethclient", level)
|
||||
_ = logging.SetLogLevel("ethereum/watcher", level)
|
||||
_ = logging.SetLogLevel("ethereum/block", level)
|
||||
_ = logging.SetLogLevel("monero", level)
|
||||
_ = logging.SetLogLevel("net", level)
|
||||
_ = logging.SetLogLevel("offers", level)
|
||||
_ = logging.SetLogLevel("p2pnet", level) // external
|
||||
_ = logging.SetLogLevel("pricefeed", level)
|
||||
_ = logging.SetLogLevel("protocol", level)
|
||||
_ = logging.SetLogLevel("relayer", level) // external and internal
|
||||
_ = logging.SetLogLevel("rpc", level)
|
||||
_ = logging.SetLogLevel("txsender", level)
|
||||
_ = logging.SetLogLevel("xmrmaker", level)
|
||||
_ = logging.SetLogLevel("xmrtaker", level)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2023 The AthanorLabs/atomic-swap Authors
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
package main
|
||||
package cliutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -9,9 +9,12 @@ import (
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
logging "github.com/ipfs/go-log"
|
||||
)
|
||||
|
||||
func signalHandler(ctx context.Context, cancel context.CancelFunc) {
|
||||
// SignalHandler handles OS signals and shuts down the program if necessary.
|
||||
func SignalHandler(ctx context.Context, cancel context.CancelFunc, log *logging.ZapEventLogger) {
|
||||
sigc := make(chan os.Signal, 1)
|
||||
signal.Ignore(syscall.SIGHUP)
|
||||
signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)
|
||||
@@ -1,15 +1,17 @@
|
||||
// Copyright 2023 The AthanorLabs/atomic-swap Authors
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
package main
|
||||
package cliutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
logging "github.com/ipfs/go-log"
|
||||
)
|
||||
|
||||
func TestDaemon_signalHandler(_ *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
go signalHandler(ctx, cancel)
|
||||
go SignalHandler(ctx, cancel, logging.Logger("test"))
|
||||
}
|
||||
173
cmd/bootnode/main.go
Normal file
173
cmd/bootnode/main.go
Normal file
@@ -0,0 +1,173 @@
|
||||
// Copyright 2023 The AthanorLabs/atomic-swap Authors
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
// Package main provides the entrypoint of the bootnode executable,
|
||||
// a node that is only used to bootstrap the p2p network and does not run
|
||||
// any swap services.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/athanorlabs/atomic-swap/bootnode"
|
||||
"github.com/athanorlabs/atomic-swap/cliutil"
|
||||
"github.com/athanorlabs/atomic-swap/common"
|
||||
|
||||
logging "github.com/ipfs/go-log"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultLibp2pPort = 9909
|
||||
defaultRPCPort = common.DefaultSwapdPort
|
||||
|
||||
flagDataDir = "data-dir"
|
||||
flagLibp2pKey = "libp2p-key"
|
||||
flagLibp2pPort = "libp2p-port"
|
||||
flagBootnodes = "bootnodes"
|
||||
flagRPCPort = "rpc-port"
|
||||
flagEnv = "env"
|
||||
)
|
||||
|
||||
var log = logging.Logger("cmd")
|
||||
|
||||
func cliApp() *cli.App {
|
||||
return &cli.App{
|
||||
Name: "bootnode",
|
||||
Usage: "A bootnode for the atomic swap p2p network.",
|
||||
Version: cliutil.GetVersion(),
|
||||
Action: runBootnode,
|
||||
EnableBashCompletion: true,
|
||||
Suggest: true,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: flagDataDir,
|
||||
Usage: "Path to store swap artifacts",
|
||||
Value: "{HOME}/.atomicswap/{ENV}/bootnode", // For --help only, actual default replaces variables
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: flagLibp2pKey,
|
||||
Usage: "libp2p private key",
|
||||
Value: fmt.Sprintf("{DATA_DIR}/%s", common.DefaultLibp2pKeyFileName),
|
||||
},
|
||||
&cli.UintFlag{
|
||||
Name: flagLibp2pPort,
|
||||
Usage: "libp2p port to listen on",
|
||||
Value: defaultLibp2pPort,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: flagBootnodes,
|
||||
Aliases: []string{"bn"},
|
||||
Usage: "libp2p bootnode, comma separated if passing multiple to a single flag",
|
||||
EnvVars: []string{"SWAPD_BOOTNODES"},
|
||||
},
|
||||
&cli.UintFlag{
|
||||
Name: flagRPCPort,
|
||||
Usage: "Port for the bootnode RPC server to run on",
|
||||
Value: defaultRPCPort,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: flagEnv,
|
||||
Usage: "Environment to use: one of mainnet, stagenet, or dev",
|
||||
Value: "dev",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: cliutil.FlagLogLevel,
|
||||
Usage: "Set log level: one of [error|warn|info|debug]",
|
||||
Value: "info",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
go cliutil.SignalHandler(ctx, cancel, log)
|
||||
|
||||
err := cliApp().RunContext(ctx, os.Args)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func runBootnode(c *cli.Context) error {
|
||||
// Fail if any non-flag arguments were passed
|
||||
if c.Args().Present() {
|
||||
return fmt.Errorf("unknown command %q", c.Args().First())
|
||||
}
|
||||
|
||||
if err := cliutil.SetLogLevelsFromContext(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config, err := getEnvConfig(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
libp2pKeyFile := config.LibP2PKeyFile()
|
||||
if c.IsSet(flagLibp2pKey) {
|
||||
libp2pKeyFile = c.String(flagLibp2pKey)
|
||||
if libp2pKeyFile == "" {
|
||||
return errFlagValueEmpty(flagLibp2pKey)
|
||||
}
|
||||
}
|
||||
|
||||
if libp2pKeyFile == "" {
|
||||
libp2pKeyFile = path.Join(config.DataDir, common.DefaultLibp2pKeyFileName)
|
||||
}
|
||||
|
||||
libp2pPort := uint16(c.Uint(flagLibp2pPort))
|
||||
|
||||
hostListenIP := "0.0.0.0"
|
||||
if config.Env == common.Development {
|
||||
hostListenIP = "127.0.0.1"
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
func getEnvConfig(c *cli.Context) (*common.Config, error) {
|
||||
env, err := common.NewEnv(c.String(flagEnv))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conf := common.ConfigDefaultsForEnv(env)
|
||||
|
||||
// cfg.DataDir already has a default set, so only override if the user explicitly set the flag
|
||||
if c.IsSet(flagDataDir) {
|
||||
conf.DataDir = c.String(flagDataDir) // override the value derived from `flagEnv`
|
||||
if conf.DataDir == "" {
|
||||
return nil, errFlagValueEmpty(flagDataDir)
|
||||
}
|
||||
}
|
||||
|
||||
conf.DataDir = path.Join(conf.DataDir, "bootnode")
|
||||
if err = common.MakeDir(conf.DataDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.IsSet(flagBootnodes) {
|
||||
conf.Bootnodes = cliutil.ExpandBootnodes(c.StringSlice(flagBootnodes))
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
func errFlagValueEmpty(flag string) error {
|
||||
return fmt.Errorf("flag %q requires a non-empty value", flag)
|
||||
}
|
||||
@@ -77,7 +77,7 @@ const (
|
||||
flagForwarderAddress = "forwarder-address"
|
||||
flagNoTransferBack = "no-transfer-back"
|
||||
|
||||
flagLogLevel = "log-level"
|
||||
flagLogLevel = cliutil.FlagLogLevel
|
||||
flagProfile = "profile"
|
||||
)
|
||||
|
||||
@@ -217,7 +217,7 @@ func main() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
go signalHandler(ctx, cancel)
|
||||
go cliutil.SignalHandler(ctx, cancel, log)
|
||||
|
||||
err := cliApp().RunContext(ctx, os.Args)
|
||||
if err != nil {
|
||||
@@ -225,55 +225,13 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func setLogLevelsFromContext(c *cli.Context) error {
|
||||
const (
|
||||
levelError = "error"
|
||||
levelWarn = "warn"
|
||||
levelInfo = "info"
|
||||
levelDebug = "debug"
|
||||
)
|
||||
|
||||
level := c.String(flagLogLevel)
|
||||
switch level {
|
||||
case levelError, levelWarn, levelInfo, levelDebug:
|
||||
default:
|
||||
return fmt.Errorf("invalid log level %q", level)
|
||||
}
|
||||
|
||||
setLogLevels(level)
|
||||
return nil
|
||||
}
|
||||
|
||||
func setLogLevels(level string) {
|
||||
// alphabetically ordered
|
||||
_ = logging.SetLogLevel("cmd", level)
|
||||
_ = logging.SetLogLevel("coins", level)
|
||||
_ = logging.SetLogLevel("common", level)
|
||||
_ = logging.SetLogLevel("contracts", level)
|
||||
_ = logging.SetLogLevel("cmd", level)
|
||||
_ = logging.SetLogLevel("extethclient", level)
|
||||
_ = logging.SetLogLevel("ethereum/watcher", level)
|
||||
_ = logging.SetLogLevel("ethereum/block", level)
|
||||
_ = logging.SetLogLevel("monero", level)
|
||||
_ = logging.SetLogLevel("net", level)
|
||||
_ = logging.SetLogLevel("offers", level)
|
||||
_ = logging.SetLogLevel("p2pnet", level) // external
|
||||
_ = logging.SetLogLevel("pricefeed", level)
|
||||
_ = logging.SetLogLevel("protocol", level)
|
||||
_ = logging.SetLogLevel("relayer", level) // external and internal
|
||||
_ = logging.SetLogLevel("rpc", level)
|
||||
_ = logging.SetLogLevel("txsender", level)
|
||||
_ = logging.SetLogLevel("xmrmaker", level)
|
||||
_ = logging.SetLogLevel("xmrtaker", level)
|
||||
}
|
||||
|
||||
func runDaemon(c *cli.Context) error {
|
||||
// Fail if any non-flag arguments were passed
|
||||
if c.Args().Present() {
|
||||
return fmt.Errorf("unknown command %q", c.Args().First())
|
||||
}
|
||||
|
||||
if err := setLogLevelsFromContext(c); err != nil {
|
||||
if err := cliutil.SetLogLevelsFromContext(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -86,41 +86,8 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func setLogLevels(c *cli.Context) error {
|
||||
const (
|
||||
levelError = "error"
|
||||
levelWarn = "warn"
|
||||
levelInfo = "info"
|
||||
levelDebug = "debug"
|
||||
)
|
||||
|
||||
_ = logging.SetLogLevel("cmd", levelInfo)
|
||||
|
||||
level := c.String(flagLogLevel)
|
||||
if level == "" {
|
||||
level = levelInfo
|
||||
}
|
||||
|
||||
switch level {
|
||||
case levelError, levelWarn, levelInfo, levelDebug:
|
||||
default:
|
||||
return fmt.Errorf("invalid log level")
|
||||
}
|
||||
|
||||
_ = logging.SetLogLevel("xmrtaker", level)
|
||||
_ = logging.SetLogLevel("xmrmaker", level)
|
||||
_ = logging.SetLogLevel("common", level)
|
||||
_ = logging.SetLogLevel("net", level)
|
||||
_ = logging.SetLogLevel("rpc", level)
|
||||
_ = logging.SetLogLevel("rpcclient", level)
|
||||
_ = logging.SetLogLevel("wsclient", level)
|
||||
_ = logging.SetLogLevel("monero", level)
|
||||
_ = logging.SetLogLevel("contracts", level)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runTester(c *cli.Context) error {
|
||||
err := setLogLevels(c)
|
||||
err := cliutil.SetLogLevelsFromContext(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
@@ -34,6 +35,7 @@ type MoneroNode struct {
|
||||
// Config contains constants that are defaults for various environments
|
||||
type Config struct {
|
||||
Env Environment
|
||||
EthereumChainID *big.Int
|
||||
DataDir string
|
||||
MoneroNodes []*MoneroNode
|
||||
SwapCreatorAddr ethcommon.Address
|
||||
@@ -44,8 +46,9 @@ type Config struct {
|
||||
// MainnetConfig is the mainnet ethereum and monero configuration
|
||||
func MainnetConfig() *Config {
|
||||
return &Config{
|
||||
Env: Mainnet,
|
||||
DataDir: path.Join(baseDir, "mainnet"),
|
||||
Env: Mainnet,
|
||||
EthereumChainID: big.NewInt(MainnetChainID),
|
||||
DataDir: path.Join(baseDir, "mainnet"),
|
||||
MoneroNodes: []*MoneroNode{
|
||||
{
|
||||
Host: "node.sethforprivacy.com",
|
||||
@@ -74,8 +77,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"),
|
||||
Env: Stagenet,
|
||||
EthereumChainID: big.NewInt(SepoliaChainID),
|
||||
DataDir: path.Join(baseDir, "stagenet"),
|
||||
MoneroNodes: []*MoneroNode{
|
||||
{
|
||||
Host: "node.sethforprivacy.com",
|
||||
@@ -108,8 +112,9 @@ func StagenetConfig() *Config {
|
||||
// DevelopmentConfig is the monero and ethereum development environment configuration
|
||||
func DevelopmentConfig() *Config {
|
||||
return &Config{
|
||||
Env: Development,
|
||||
DataDir: path.Join(baseDir, "dev"),
|
||||
Env: Development,
|
||||
EthereumChainID: big.NewInt(1337),
|
||||
DataDir: path.Join(baseDir, "dev"),
|
||||
MoneroNodes: []*MoneroNode{
|
||||
{
|
||||
Host: "127.0.0.1",
|
||||
|
||||
@@ -155,6 +155,7 @@ func RunSwapDaemon(ctx context.Context, conf *SwapdConfig) (err error) {
|
||||
XMRMaker: xmrMaker,
|
||||
ProtocolBackend: swapBackend,
|
||||
RecoveryDB: sdb.RecoveryDB(),
|
||||
Namespaces: rpc.AllNamespaces(),
|
||||
})
|
||||
|
||||
log.Infof("starting swapd with data-dir %s", conf.EnvConf.DataDir)
|
||||
|
||||
@@ -17,10 +17,10 @@ import (
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
logging "github.com/ipfs/go-log"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/athanorlabs/atomic-swap/cliutil"
|
||||
"github.com/athanorlabs/atomic-swap/coins"
|
||||
"github.com/athanorlabs/atomic-swap/common/types"
|
||||
"github.com/athanorlabs/atomic-swap/ethereum/block"
|
||||
@@ -39,27 +39,7 @@ const (
|
||||
)
|
||||
|
||||
func init() {
|
||||
// alphabetically ordered
|
||||
level := "debug"
|
||||
_ = logging.SetLogLevel("cmd", level)
|
||||
_ = logging.SetLogLevel("coins", level)
|
||||
_ = logging.SetLogLevel("common", level)
|
||||
_ = logging.SetLogLevel("contracts", level)
|
||||
_ = logging.SetLogLevel("cmd", level)
|
||||
_ = logging.SetLogLevel("extethclient", level)
|
||||
_ = logging.SetLogLevel("ethereum/watcher", level)
|
||||
_ = logging.SetLogLevel("ethereum/block", level)
|
||||
_ = logging.SetLogLevel("monero", level)
|
||||
_ = logging.SetLogLevel("net", level)
|
||||
_ = logging.SetLogLevel("offers", level)
|
||||
_ = logging.SetLogLevel("p2pnet", level) // external
|
||||
_ = logging.SetLogLevel("pricefeed", level)
|
||||
_ = logging.SetLogLevel("protocol", level)
|
||||
_ = logging.SetLogLevel("relayer", level) // external and internal
|
||||
_ = logging.SetLogLevel("rpc", level)
|
||||
_ = logging.SetLogLevel("txsender", level)
|
||||
_ = logging.SetLogLevel("xmrmaker", level)
|
||||
_ = logging.SetLogLevel("xmrtaker", level)
|
||||
cliutil.SetLogLevels("debug")
|
||||
}
|
||||
|
||||
func privKeyToAddr(privKey *ecdsa.PrivateKey) ethcommon.Address {
|
||||
|
||||
29
docs/bootnode.md
Normal file
29
docs/bootnode.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Bootnode
|
||||
|
||||
The swap daemon uses a p2p network to discover offer-makers to do a swap with, and also the
|
||||
run the actual swap protocol. A node must know addresses of nodes already in the network to
|
||||
join the network. These nodes are often publicly posted and referred to as bootnodes.
|
||||
Bootnodes act as an entry-point into the p2p network.
|
||||
|
||||
This repo comes with a `bootnode` program that runs only the p2p components of a swap node,
|
||||
and thus can be used as a lightweight bootnode.
|
||||
|
||||
## Requirements
|
||||
- see [build instructions](./build.md) for installation requirements.
|
||||
|
||||
## Build and run
|
||||
|
||||
To build and run the bootnode binary:
|
||||
```bash
|
||||
make build-all
|
||||
./bin/bootnode --env ENVIRONMENT
|
||||
```
|
||||
|
||||
`ENVIRONMENT` is one of `mainnet`, `stagenet`, or `dev`.
|
||||
|
||||
To get the p2p addresses of the bootnode:
|
||||
```bash
|
||||
./bin/swapcli addresses
|
||||
```
|
||||
|
||||
You can then distribute these addresses for other swap nodes to connect to.
|
||||
@@ -64,18 +64,14 @@ Stores information on a swap when it reaches the stage where ethereum is locked.
|
||||
Only written when `--deploy` is passed to swapd. This file stores the address
|
||||
that the contract was deployed to along with other data.
|
||||
|
||||
## Relayer default file locations
|
||||
## Bootnode default file locations
|
||||
|
||||
### {DATA_DIR}/relayer
|
||||
### {DATA_DIR}/bootnode
|
||||
|
||||
By default, all relayer-related files will be placed in the `relayer` directory within the data dir.
|
||||
By default, all bootnode-related files will be placed in the `bootnode` directory within the data dir.
|
||||
|
||||
### {DATA_DIR}/relayer/eth.key
|
||||
### {DATA_DIR}/bootnode/net.key
|
||||
|
||||
The location of the Ethereum private key used by the relayer to submit transactions. Fees received by the relayer will also go into this account. Alternate locations can be configured with `--ethereum-privkey`. If the file does not exist, the relayer will error on startup.
|
||||
|
||||
### {DATA_DIR}/relayer/net.key
|
||||
|
||||
The private key to the relayer's libp2p identity. If the file does not exist, a new
|
||||
The private key to the bootnode's libp2p identity. If the file does not exist, a new
|
||||
random key will be generated and placed in this location. Alternate locations can be
|
||||
configured with `--libp2p-key`. It does not necessarily need to be a different key than that used by swapd.
|
||||
|
||||
@@ -8,7 +8,7 @@ support most 64-bit Linux distributions, macOS, and WSL on Windows both with X86
|
||||
and ARM processors.
|
||||
|
||||
#### Installed Dependencies for Building/Testing
|
||||
- go 1.19+ (see [build instructions](./build.md) to download Go.)
|
||||
- go 1.20+ (see [build instructions](./build.md) to download Go.)
|
||||
- node/npm (to install ganache, see suggestions after list)
|
||||
- ganache (can be installed with `npm install --location=global ganache`)
|
||||
- jq, curl, bzip2, realpath
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
errBootnodeCannotRelay = errors.New("bootnode cannot be a relayer")
|
||||
errNilHandler = errors.New("handler is nil")
|
||||
errNoOngoingSwap = errors.New("no swap currently happening")
|
||||
errSwapAlreadyInProgress = errors.New("already have ongoing swap")
|
||||
|
||||
40
net/host.go
40
net/host.go
@@ -62,6 +62,9 @@ type Host struct {
|
||||
h P2pHost
|
||||
isRelayer bool
|
||||
|
||||
// set to true if the node is a bootnode-only node
|
||||
isBootnode bool
|
||||
|
||||
makerHandler MakerHandler
|
||||
relayHandler RelayHandler
|
||||
|
||||
@@ -72,25 +75,31 @@ type Host struct {
|
||||
|
||||
// Config holds the initialization parameters for the NewHost constructor.
|
||||
type Config struct {
|
||||
Ctx context.Context
|
||||
DataDir string
|
||||
Port uint16
|
||||
KeyFile string
|
||||
Bootnodes []string
|
||||
ProtocolID string
|
||||
ListenIP string
|
||||
IsRelayer bool
|
||||
Ctx context.Context
|
||||
DataDir string
|
||||
Port uint16
|
||||
KeyFile string
|
||||
Bootnodes []string
|
||||
ProtocolID string
|
||||
ListenIP string
|
||||
IsRelayer bool
|
||||
IsBootnodeOnly bool
|
||||
}
|
||||
|
||||
// NewHost returns a new Host.
|
||||
// The host implemented in this package is swap-specific; ie. it supports swap-specific
|
||||
// messages (initiate and query).
|
||||
func NewHost(cfg *Config) (*Host, error) {
|
||||
if cfg.IsBootnodeOnly && cfg.IsRelayer {
|
||||
return nil, errBootnodeCannotRelay
|
||||
}
|
||||
|
||||
h := &Host{
|
||||
ctx: cfg.Ctx,
|
||||
h: nil, // set below
|
||||
isRelayer: cfg.IsRelayer,
|
||||
swaps: make(map[types.Hash]*swap),
|
||||
ctx: cfg.Ctx,
|
||||
h: nil, // set below
|
||||
isRelayer: cfg.IsRelayer,
|
||||
isBootnode: cfg.IsBootnodeOnly,
|
||||
swaps: make(map[types.Hash]*swap),
|
||||
}
|
||||
|
||||
var err error
|
||||
@@ -108,17 +117,18 @@ func NewHost(cfg *Config) (*Host, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugf("using base protocol %s", cfg.ProtocolID)
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (h *Host) advertisedNamespaces() []string {
|
||||
provides := []string{""}
|
||||
|
||||
if len(h.makerHandler.GetOffers()) > 0 {
|
||||
if !h.isBootnode && len(h.makerHandler.GetOffers()) > 0 {
|
||||
provides = append(provides, string(coins.ProvidesXMR))
|
||||
}
|
||||
|
||||
if h.isRelayer {
|
||||
if !h.isBootnode && h.isRelayer {
|
||||
provides = append(provides, RelayerProvidesStr)
|
||||
}
|
||||
|
||||
@@ -138,7 +148,7 @@ func (h *Host) SetHandlers(makerHandler MakerHandler, relayHandler RelayHandler)
|
||||
|
||||
// Start starts the bootstrap and discovery process.
|
||||
func (h *Host) Start() error {
|
||||
if h.makerHandler == nil || h.relayHandler == nil {
|
||||
if (h.makerHandler == nil || h.relayHandler == nil) && !h.isBootnode {
|
||||
return errNilHandler
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,11 @@ import (
|
||||
|
||||
var (
|
||||
// net_ errors
|
||||
errNoOfferWithID = errors.New("peer does not have offer with given ID")
|
||||
errNoOfferWithID = errors.New("peer does not have offer with given ID")
|
||||
errUnsupportedForBootnode = errors.New("unsupported for bootnode")
|
||||
|
||||
// ws errors
|
||||
errUnimplemented = errors.New("unimplemented")
|
||||
errInvalidMethod = errors.New("invalid method")
|
||||
errUnimplemented = errors.New("unimplemented")
|
||||
errInvalidMethod = errors.New("invalid method")
|
||||
errNamespaceNotEnabled = errors.New("namespace not enabled")
|
||||
)
|
||||
|
||||
45
rpc/net.go
45
rpc/net.go
@@ -34,19 +34,21 @@ type Net interface {
|
||||
|
||||
// NetService is the RPC service prefixed by net_.
|
||||
type NetService struct {
|
||||
net Net
|
||||
xmrtaker XMRTaker
|
||||
xmrmaker XMRMaker
|
||||
sm SwapManager
|
||||
net Net
|
||||
xmrtaker XMRTaker
|
||||
xmrmaker XMRMaker
|
||||
sm SwapManager
|
||||
isBootnode bool
|
||||
}
|
||||
|
||||
// NewNetService ...
|
||||
func NewNetService(net Net, xmrtaker XMRTaker, xmrmaker XMRMaker, sm SwapManager) *NetService {
|
||||
func NewNetService(net Net, xmrtaker XMRTaker, xmrmaker XMRMaker, sm SwapManager, isBootnode bool) *NetService {
|
||||
return &NetService{
|
||||
net: net,
|
||||
xmrtaker: xmrtaker,
|
||||
xmrmaker: xmrmaker,
|
||||
sm: sm,
|
||||
net: net,
|
||||
xmrtaker: xmrtaker,
|
||||
xmrmaker: xmrmaker,
|
||||
sm: sm,
|
||||
isBootnode: isBootnode,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +74,10 @@ func (s *NetService) Peers(_ *http.Request, _ *interface{}, resp *rpctypes.Peers
|
||||
|
||||
// QueryAll discovers peers who provide a certain coin and queries all of them for their current offers.
|
||||
func (s *NetService) QueryAll(_ *http.Request, req *rpctypes.QueryAllRequest, resp *rpctypes.QueryAllResponse) error {
|
||||
if s.isBootnode {
|
||||
return errUnsupportedForBootnode
|
||||
}
|
||||
|
||||
peerIDs, err := s.discover(req)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -126,8 +132,14 @@ func (s *NetService) Discover(_ *http.Request, req *rpctypes.DiscoverRequest, re
|
||||
}
|
||||
|
||||
// QueryPeer queries a peer for the coins they provide, their maximum amounts, and desired exchange rate.
|
||||
func (s *NetService) QueryPeer(_ *http.Request, req *rpctypes.QueryPeerRequest,
|
||||
resp *rpctypes.QueryPeerResponse) error {
|
||||
func (s *NetService) QueryPeer(
|
||||
_ *http.Request,
|
||||
req *rpctypes.QueryPeerRequest,
|
||||
resp *rpctypes.QueryPeerResponse,
|
||||
) error {
|
||||
if s.isBootnode {
|
||||
return errUnsupportedForBootnode
|
||||
}
|
||||
|
||||
msg, err := s.net.Query(req.PeerID)
|
||||
if err != nil {
|
||||
@@ -144,6 +156,10 @@ func (s *NetService) TakeOffer(
|
||||
req *rpctypes.TakeOfferRequest,
|
||||
_ *interface{},
|
||||
) error {
|
||||
if s.isBootnode {
|
||||
return errUnsupportedForBootnode
|
||||
}
|
||||
|
||||
_, err := s.takeOffer(req.PeerID, req.OfferID, req.ProvidesAmount)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -208,6 +224,9 @@ func (s *NetService) TakeOfferSync(
|
||||
req *rpctypes.TakeOfferRequest,
|
||||
resp *TakeOfferSyncResponse,
|
||||
) error {
|
||||
if s.isBootnode {
|
||||
return errUnsupportedForBootnode
|
||||
}
|
||||
|
||||
if _, err := s.takeOffer(req.PeerID, req.OfferID, req.ProvidesAmount); err != nil {
|
||||
return err
|
||||
@@ -240,6 +259,10 @@ func (s *NetService) MakeOffer(
|
||||
req *rpctypes.MakeOfferRequest,
|
||||
resp *rpctypes.MakeOfferResponse,
|
||||
) error {
|
||||
if s.isBootnode {
|
||||
return errUnsupportedForBootnode
|
||||
}
|
||||
|
||||
offerResp, _, err := s.makeOffer(req)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
func TestNet_Discover(t *testing.T) {
|
||||
ns := NewNetService(new(mockNet), new(mockXMRTaker), nil, new(mockSwapManager))
|
||||
ns := NewNetService(new(mockNet), new(mockXMRTaker), nil, new(mockSwapManager), false)
|
||||
|
||||
req := &rpctypes.DiscoverRequest{
|
||||
Provides: "",
|
||||
@@ -28,7 +28,7 @@ func TestNet_Discover(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNet_Query(t *testing.T) {
|
||||
ns := NewNetService(new(mockNet), new(mockXMRTaker), nil, new(mockSwapManager))
|
||||
ns := NewNetService(new(mockNet), new(mockXMRTaker), nil, new(mockSwapManager), false)
|
||||
|
||||
req := &rpctypes.QueryPeerRequest{
|
||||
PeerID: "12D3KooWDqCzbjexHEa8Rut7bzxHFpRMZyDRW1L6TGkL1KY24JH5",
|
||||
@@ -42,7 +42,7 @@ func TestNet_Query(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNet_TakeOffer(t *testing.T) {
|
||||
ns := NewNetService(new(mockNet), new(mockXMRTaker), nil, new(mockSwapManager))
|
||||
ns := NewNetService(new(mockNet), new(mockXMRTaker), nil, new(mockSwapManager), false)
|
||||
|
||||
req := &rpctypes.TakeOfferRequest{
|
||||
PeerID: "12D3KooWDqCzbjexHEa8Rut7bzxHFpRMZyDRW1L6TGkL1KY24JH5",
|
||||
@@ -55,7 +55,7 @@ func TestNet_TakeOffer(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNet_TakeOfferSync(t *testing.T) {
|
||||
ns := NewNetService(new(mockNet), new(mockXMRTaker), nil, new(mockSwapManager))
|
||||
ns := NewNetService(new(mockNet), new(mockXMRTaker), nil, new(mockSwapManager), false)
|
||||
|
||||
req := &rpctypes.TakeOfferRequest{
|
||||
PeerID: "12D3KooWDqCzbjexHEa8Rut7bzxHFpRMZyDRW1L6TGkL1KY24JH5",
|
||||
|
||||
@@ -32,6 +32,14 @@ import (
|
||||
"github.com/athanorlabs/atomic-swap/protocol/txsender"
|
||||
)
|
||||
|
||||
const (
|
||||
DaemonNamespace = "daemon" //nolint:revive
|
||||
DatabaseNamespace = "database" //nolint:revive
|
||||
NetNamespace = "net" //nolint:revive
|
||||
PersonalName = "personal" //nolint:revive
|
||||
SwapNamespace = "swap" //nolint:revive
|
||||
)
|
||||
|
||||
var log = logging.Logger("rpc")
|
||||
|
||||
// Server represents the JSON-RPC server
|
||||
@@ -50,6 +58,19 @@ type Config struct {
|
||||
XMRMaker XMRMaker
|
||||
ProtocolBackend ProtocolBackend
|
||||
RecoveryDB RecoveryDB
|
||||
Namespaces map[string]struct{}
|
||||
IsBootnodeOnly bool
|
||||
}
|
||||
|
||||
// AllNamespaces returns a map with all RPC namespaces set for usage in the config.
|
||||
func AllNamespaces() map[string]struct{} {
|
||||
return map[string]struct{}{
|
||||
DaemonNamespace: {},
|
||||
DatabaseNamespace: {},
|
||||
NetNamespace: {},
|
||||
PersonalName: {},
|
||||
SwapNamespace: {},
|
||||
}
|
||||
}
|
||||
|
||||
// NewServer ...
|
||||
@@ -57,39 +78,51 @@ func NewServer(cfg *Config) (*Server, error) {
|
||||
rpcServer := rpc.NewServer()
|
||||
rpcServer.RegisterCodec(NewCodec(), "application/json")
|
||||
|
||||
ns := NewNetService(cfg.Net, cfg.XMRTaker, cfg.XMRMaker, cfg.ProtocolBackend.SwapManager())
|
||||
if err := rpcServer.RegisterService(ns, "net"); err != nil {
|
||||
serverCtx, serverCancel := context.WithCancel(cfg.Ctx)
|
||||
err := rpcServer.RegisterService(NewDaemonService(serverCancel, cfg.ProtocolBackend), "daemon")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverCtx, serverCancel := context.WithCancel(cfg.Ctx)
|
||||
var swapManager swap.Manager
|
||||
if cfg.ProtocolBackend != nil {
|
||||
swapManager = cfg.ProtocolBackend.SwapManager()
|
||||
}
|
||||
|
||||
err := rpcServer.RegisterService(NewPersonalService(serverCtx, cfg.XMRMaker, cfg.ProtocolBackend), "personal")
|
||||
var netService *NetService
|
||||
for ns := range cfg.Namespaces {
|
||||
switch ns {
|
||||
case DaemonNamespace:
|
||||
continue
|
||||
case DatabaseNamespace:
|
||||
err = rpcServer.RegisterService(NewDatabaseService(cfg.RecoveryDB), DatabaseNamespace)
|
||||
case NetNamespace:
|
||||
netService = NewNetService(cfg.Net, cfg.XMRTaker, cfg.XMRMaker, swapManager, cfg.IsBootnodeOnly)
|
||||
err = rpcServer.RegisterService(netService, NetNamespace)
|
||||
case PersonalName:
|
||||
err = rpcServer.RegisterService(NewPersonalService(serverCtx, cfg.XMRMaker, cfg.ProtocolBackend), PersonalName)
|
||||
case SwapNamespace:
|
||||
err = rpcServer.RegisterService(
|
||||
NewSwapService(
|
||||
serverCtx,
|
||||
swapManager,
|
||||
cfg.XMRTaker,
|
||||
cfg.XMRMaker,
|
||||
cfg.Net,
|
||||
cfg.ProtocolBackend,
|
||||
),
|
||||
SwapNamespace,
|
||||
)
|
||||
default:
|
||||
err = fmt.Errorf("unknown namespace %s", ns)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
serverCancel()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
swapService := NewSwapService(
|
||||
serverCtx,
|
||||
cfg.ProtocolBackend.SwapManager(),
|
||||
cfg.XMRTaker,
|
||||
cfg.XMRMaker,
|
||||
cfg.Net,
|
||||
cfg.ProtocolBackend,
|
||||
)
|
||||
if err = rpcServer.RegisterService(swapService, "swap"); err != nil {
|
||||
serverCancel()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
databaseService := NewDatabaseService(cfg.RecoveryDB)
|
||||
if err = rpcServer.RegisterService(databaseService, "database"); err != nil {
|
||||
serverCancel()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wsServer := newWsServer(serverCtx, cfg.ProtocolBackend.SwapManager(), ns, cfg.ProtocolBackend, cfg.XMRTaker)
|
||||
wsServer := newWsServer(serverCtx, swapManager, netService, cfg.ProtocolBackend, cfg.XMRTaker)
|
||||
|
||||
lc := net.ListenConfig{}
|
||||
ln, err := lc.Listen(serverCtx, "tcp", cfg.Address)
|
||||
@@ -114,18 +147,11 @@ func NewServer(cfg *Config) (*Server, error) {
|
||||
},
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
return &Server{
|
||||
ctx: serverCtx,
|
||||
listener: ln,
|
||||
httpServer: server,
|
||||
}
|
||||
|
||||
if err = rpcServer.RegisterService(NewDaemonService(serverCancel, cfg.ProtocolBackend), "daemon"); err != nil {
|
||||
serverCancel()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
// HttpURL returns the URL used for HTTP requests
|
||||
|
||||
16
rpc/ws.go
16
rpc/ws.go
@@ -92,6 +92,10 @@ func (s *wsServer) handleRequest(conn *websocket.Conn, req *rpctypes.Request) er
|
||||
case rpctypes.SubscribeNewPeer:
|
||||
return errUnimplemented
|
||||
case rpctypes.NetDiscover:
|
||||
if s.ns == nil {
|
||||
return errNamespaceNotEnabled
|
||||
}
|
||||
|
||||
params := new(rpctypes.DiscoverRequest)
|
||||
if err := vjson.UnmarshalStruct(req.Params, params); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal parameters: %w", err)
|
||||
@@ -105,6 +109,10 @@ func (s *wsServer) handleRequest(conn *websocket.Conn, req *rpctypes.Request) er
|
||||
|
||||
return writeResponse(conn, resp)
|
||||
case rpctypes.NetQueryPeer:
|
||||
if s.ns == nil {
|
||||
return errNamespaceNotEnabled
|
||||
}
|
||||
|
||||
params := new(rpctypes.QueryPeerRequest)
|
||||
if err := vjson.UnmarshalStruct(req.Params, params); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal parameters: %w", err)
|
||||
@@ -125,6 +133,10 @@ func (s *wsServer) handleRequest(conn *websocket.Conn, req *rpctypes.Request) er
|
||||
|
||||
return s.subscribeSwapStatus(s.ctx, conn, params.OfferID)
|
||||
case rpctypes.SubscribeTakeOffer:
|
||||
if s.ns == nil {
|
||||
return errNamespaceNotEnabled
|
||||
}
|
||||
|
||||
params := new(rpctypes.TakeOfferRequest)
|
||||
if err := vjson.UnmarshalStruct(req.Params, params); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal parameters: %w", err)
|
||||
@@ -137,6 +149,10 @@ func (s *wsServer) handleRequest(conn *websocket.Conn, req *rpctypes.Request) er
|
||||
|
||||
return s.subscribeTakeOffer(s.ctx, conn, ch)
|
||||
case rpctypes.SubscribeMakeOffer:
|
||||
if s.ns == nil {
|
||||
return errNamespaceNotEnabled
|
||||
}
|
||||
|
||||
params := new(rpctypes.MakeOfferRequest)
|
||||
if err := vjson.UnmarshalStruct(req.Params, params); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal parameters: %w", err)
|
||||
|
||||
@@ -35,6 +35,7 @@ func newServer(t *testing.T) *Server {
|
||||
ProtocolBackend: newMockProtocolBackend(),
|
||||
XMRTaker: new(mockXMRTaker),
|
||||
XMRMaker: new(mockXMRMaker),
|
||||
Namespaces: AllNamespaces(),
|
||||
}
|
||||
|
||||
s, err := NewServer(cfg)
|
||||
|
||||
Reference in New Issue
Block a user