diff --git a/api/config.go b/api/config.go index b587611611..78d3fb66d9 100644 --- a/api/config.go +++ b/api/config.go @@ -32,6 +32,7 @@ import ( "github.com/ethersphere/swarm/network" "github.com/ethersphere/swarm/pss" "github.com/ethersphere/swarm/storage" + "github.com/ethersphere/swarm/swap" ) const ( @@ -52,12 +53,15 @@ type Config struct { BaseKey []byte // Swap configs - SwapBackendURL string - SwapEnabled bool + SwapBackendURL string // Ethereum API endpoint + SwapEnabled bool // whether SWAP incentives are enabled + SwapPaymentThreshold uint64 // honey amount at which a payment is triggered + SwapDisconnectThreshold uint64 // honey amount at which a peer disconnects + Contract common.Address // address of the chequebook contract + // end of Swap configs *network.HiveParams Pss *pss.Params - Contract common.Address EnsRoot common.Address EnsAPIs []string Path string @@ -81,26 +85,28 @@ type Config struct { privateKey *ecdsa.PrivateKey } -//create a default config with all parameters to set to defaults +//NewConfig creates a default config with all parameters to set to defaults func NewConfig() (c *Config) { c = &Config{ - FileStoreParams: storage.NewFileStoreParams(), - HiveParams: network.NewHiveParams(), - Pss: pss.NewParams(), - ListenAddr: DefaultHTTPListenAddr, - Port: DefaultHTTPPort, - Path: node.DefaultDataDir(), - EnsAPIs: nil, - EnsRoot: ens.TestNetAddress, - NetworkID: network.DefaultNetworkID, - SyncEnabled: true, - SyncingSkipCheck: false, - MaxStreamPeerServers: 10000, - DeliverySkipCheck: true, - SyncUpdateDelay: 15 * time.Second, - SwapEnabled: false, - SwapBackendURL: "", + FileStoreParams: storage.NewFileStoreParams(), + SwapBackendURL: "", + SwapEnabled: false, + SwapPaymentThreshold: swap.DefaultPaymentThreshold, + SwapDisconnectThreshold: swap.DefaultDisconnectThreshold, + HiveParams: network.NewHiveParams(), + Pss: pss.NewParams(), + EnsRoot: ens.TestNetAddress, + EnsAPIs: nil, + Path: node.DefaultDataDir(), + ListenAddr: DefaultHTTPListenAddr, + Port: DefaultHTTPPort, + NetworkID: network.DefaultNetworkID, + SyncEnabled: true, + SyncingSkipCheck: false, + DeliverySkipCheck: true, + MaxStreamPeerServers: 10000, + SyncUpdateDelay: 15 * time.Second, } return diff --git a/cmd/swarm/config.go b/cmd/swarm/config.go index c0a35a777a..e7e4783ea4 100644 --- a/cmd/swarm/config.go +++ b/cmd/swarm/config.go @@ -27,10 +27,10 @@ import ( "strings" "unicode" + "github.com/ethereum/go-ethereum/common" cli "gopkg.in/urfave/cli.v1" "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/naoina/toml" @@ -59,33 +59,35 @@ var ( //constants for environment variables const ( - SwarmEnvChequebookAddr = "SWARM_CHEQUEBOOK_ADDR" - SwarmEnvAccount = "SWARM_ACCOUNT" - SwarmEnvBzzKeyHex = "SWARM_BZZ_KEY_HEX" - SwarmEnvListenAddr = "SWARM_LISTEN_ADDR" - SwarmEnvPort = "SWARM_PORT" - SwarmEnvNetworkID = "SWARM_NETWORK_ID" - SwarmEnvSwapEnable = "SWARM_SWAP_ENABLE" - SwarmEnvSwapBackendURL = "SWARM_SWAP_BACKEND_URL" - SwarmEnvSyncDisable = "SWARM_SYNC_DISABLE" - SwarmEnvSyncUpdateDelay = "SWARM_ENV_SYNC_UPDATE_DELAY" - SwarmEnvMaxStreamPeerServers = "SWARM_ENV_MAX_STREAM_PEER_SERVERS" - SwarmEnvLightNodeEnable = "SWARM_LIGHT_NODE_ENABLE" - SwarmEnvDeliverySkipCheck = "SWARM_DELIVERY_SKIP_CHECK" - SwarmEnvENSAPI = "SWARM_ENS_API" - SwarmEnvENSAddr = "SWARM_ENS_ADDR" - SwarmEnvCORS = "SWARM_CORS" - SwarmEnvBootnodes = "SWARM_BOOTNODES" - SwarmEnvPSSEnable = "SWARM_PSS_ENABLE" - SwarmEnvStorePath = "SWARM_STORE_PATH" - SwarmEnvStoreCapacity = "SWARM_STORE_CAPACITY" - SwarmEnvStoreCacheCapacity = "SWARM_STORE_CACHE_CAPACITY" - SwarmEnvBootnodeMode = "SWARM_BOOTNODE_MODE" - SwarmEnvNATInterface = "SWARM_NAT_INTERFACE" - SwarmAccessPassword = "SWARM_ACCESS_PASSWORD" - SwarmAutoDefaultPath = "SWARM_AUTO_DEFAULTPATH" - SwarmGlobalstoreAPI = "SWARM_GLOBALSTORE_API" - GethEnvDataDir = "GETH_DATADIR" + SwarmEnvChequebookAddr = "SWARM_CHEQUEBOOK_ADDR" + SwarmEnvAccount = "SWARM_ACCOUNT" + SwarmEnvBzzKeyHex = "SWARM_BZZ_KEY_HEX" + SwarmEnvListenAddr = "SWARM_LISTEN_ADDR" + SwarmEnvPort = "SWARM_PORT" + SwarmEnvNetworkID = "SWARM_NETWORK_ID" + SwarmEnvSwapEnable = "SWARM_SWAP_ENABLE" + SwarmEnvSwapBackendURL = "SWARM_SWAP_BACKEND_URL" + SwarmEnvSwapPaymentThreshold = "SWARM_SWAP_PAYMENT_THRESHOLD" + SwarmEnvSwapDisconnectThreshold = "SWARM_SWAP_DISCONNECT_THRESHOLD" + SwarmEnvSyncDisable = "SWARM_SYNC_DISABLE" + SwarmEnvSyncUpdateDelay = "SWARM_ENV_SYNC_UPDATE_DELAY" + SwarmEnvMaxStreamPeerServers = "SWARM_ENV_MAX_STREAM_PEER_SERVERS" + SwarmEnvLightNodeEnable = "SWARM_LIGHT_NODE_ENABLE" + SwarmEnvDeliverySkipCheck = "SWARM_DELIVERY_SKIP_CHECK" + SwarmEnvENSAPI = "SWARM_ENS_API" + SwarmEnvENSAddr = "SWARM_ENS_ADDR" + SwarmEnvCORS = "SWARM_CORS" + SwarmEnvBootnodes = "SWARM_BOOTNODES" + SwarmEnvPSSEnable = "SWARM_PSS_ENABLE" + SwarmEnvStorePath = "SWARM_STORE_PATH" + SwarmEnvStoreCapacity = "SWARM_STORE_CAPACITY" + SwarmEnvStoreCacheCapacity = "SWARM_STORE_CACHE_CAPACITY" + SwarmEnvBootnodeMode = "SWARM_BOOTNODE_MODE" + SwarmEnvNATInterface = "SWARM_NAT_INTERFACE" + SwarmAccessPassword = "SWARM_ACCESS_PASSWORD" + SwarmAutoDefaultPath = "SWARM_AUTO_DEFAULTPATH" + SwarmGlobalstoreAPI = "SWARM_GLOBALSTORE_API" + GethEnvDataDir = "GETH_DATADIR" ) // These settings ensure that TOML keys use the same names as Go struct fields. @@ -176,11 +178,9 @@ func flagsOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Confi if keyid := ctx.GlobalString(SwarmAccountFlag.Name); keyid != "" { currentConfig.BzzAccount = keyid } - - if chbookaddr := ctx.GlobalString(ChequebookAddrFlag.Name); chbookaddr != "" { + if chbookaddr := ctx.GlobalString(SwarmSwapChequebookAddrFlag.Name); chbookaddr != "" { currentConfig.Contract = common.HexToAddress(chbookaddr) } - if networkid := ctx.GlobalString(SwarmNetworkIdFlag.Name); networkid != "" { id, err := strconv.ParseUint(networkid, 10, 64) if err != nil { @@ -190,50 +190,44 @@ func flagsOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Confi currentConfig.NetworkID = id } } - if ctx.GlobalIsSet(utils.DataDirFlag.Name) { if datadir := ctx.GlobalString(utils.DataDirFlag.Name); datadir != "" { currentConfig.Path = expandPath(datadir) } } - bzzport := ctx.GlobalString(SwarmPortFlag.Name) if len(bzzport) > 0 { currentConfig.Port = bzzport } - if bzzaddr := ctx.GlobalString(SwarmListenAddrFlag.Name); bzzaddr != "" { currentConfig.ListenAddr = bzzaddr } - if ctx.GlobalIsSet(SwarmSwapEnabledFlag.Name) { currentConfig.SwapEnabled = true } - + if swapBackendURL := ctx.GlobalString(SwarmSwapBackendURLFlag.Name); swapBackendURL != "" { + currentConfig.SwapBackendURL = swapBackendURL + } + if paymentThreshold := ctx.GlobalUint64(SwarmSwapPaymentThresholdFlag.Name); paymentThreshold != 0 { + currentConfig.SwapPaymentThreshold = paymentThreshold + } + if disconnectThreshold := ctx.GlobalUint64(SwarmSwapDisconnectThresholdFlag.Name); disconnectThreshold != 0 { + currentConfig.SwapDisconnectThreshold = disconnectThreshold + } if ctx.GlobalIsSet(SwarmSyncDisabledFlag.Name) { currentConfig.SyncEnabled = false } - if d := ctx.GlobalDuration(SwarmSyncUpdateDelay.Name); d > 0 { currentConfig.SyncUpdateDelay = d } - // any value including 0 is acceptable currentConfig.MaxStreamPeerServers = ctx.GlobalInt(SwarmMaxStreamPeerServersFlag.Name) - if ctx.GlobalIsSet(SwarmLightNodeEnabled.Name) { currentConfig.LightNodeEnabled = true } - if ctx.GlobalIsSet(SwarmDeliverySkipCheckFlag.Name) { currentConfig.DeliverySkipCheck = true } - - currentConfig.SwapBackendURL = ctx.GlobalString(SwarmSwapBackendURLFlag.Name) - if currentConfig.SwapEnabled && currentConfig.SwapBackendURL == "" { - utils.Fatalf(SwarmErrSwapSetNoBackendURL) - } - if ctx.GlobalIsSet(EnsAPIFlag.Name) { ensAPIs := ctx.GlobalStringSlice(EnsAPIFlag.Name) // preserve backward compatibility to disable ENS with --ens-api="" @@ -243,40 +237,30 @@ func flagsOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Confi for i := range ensAPIs { ensAPIs[i] = expandPath(ensAPIs[i]) } - currentConfig.EnsAPIs = ensAPIs } - if cors := ctx.GlobalString(CorsStringFlag.Name); cors != "" { currentConfig.Cors = cors } - if storePath := ctx.GlobalString(SwarmStorePath.Name); storePath != "" { currentConfig.ChunkDbPath = storePath } - if storeCapacity := ctx.GlobalUint64(SwarmStoreCapacity.Name); storeCapacity != 0 { currentConfig.DbCapacity = storeCapacity } - if ctx.GlobalIsSet(SwarmStoreCacheCapacity.Name) { currentConfig.CacheCapacity = ctx.GlobalUint(SwarmStoreCacheCapacity.Name) } - if ctx.GlobalIsSet(SwarmBootnodeModeFlag.Name) { currentConfig.BootnodeMode = ctx.GlobalBool(SwarmBootnodeModeFlag.Name) } - if ctx.GlobalIsSet(SwarmDisableAutoConnectFlag.Name) { currentConfig.DisableAutoConnect = ctx.GlobalBool(SwarmDisableAutoConnectFlag.Name) } - if ctx.GlobalIsSet(SwarmGlobalStoreAPIFlag.Name) { currentConfig.GlobalStoreAPI = ctx.GlobalString(SwarmGlobalStoreAPIFlag.Name) } - return currentConfig - } // dumpConfig is the dumpconfig command. diff --git a/cmd/swarm/config_test.go b/cmd/swarm/config_test.go index 63867bc280..b0d77b34c2 100644 --- a/cmd/swarm/config_test.go +++ b/cmd/swarm/config_test.go @@ -24,6 +24,7 @@ import ( "net" "os" "os/exec" + "strconv" "testing" "time" @@ -34,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/rpc" "github.com/ethersphere/swarm" "github.com/ethersphere/swarm/api" + "github.com/ethersphere/swarm/swap" "github.com/ethersphere/swarm/testutil" ) @@ -48,20 +50,6 @@ func TestConfigDump(t *testing.T) { swarm.ExpectExit() } -func TestConfigFailsSwapEnabledNoBackendURL(t *testing.T) { - flags := []string{ - fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42", - fmt.Sprintf("--%s", SwarmPortFlag.Name), "54545", - fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0", - fmt.Sprintf("--%s", SwarmSwapEnabledFlag.Name), - "--verbosity", fmt.Sprintf("%d", *testutil.Loglevel), - } - - swarm := runSwarm(t, flags...) - swarm.Expect("Fatal: " + SwarmErrSwapSetNoBackendURL + "\n") - swarm.ExpectExit() -} - func TestBzzKeyFlag(t *testing.T) { key, err := crypto.GenerateKey() if err != nil { @@ -251,7 +239,10 @@ func TestConfigCmdLineOverrides(t *testing.T) { fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir, fmt.Sprintf("--%s", utils.IPCPathFlag.Name), conf.IPCPath, "--verbosity", fmt.Sprintf("%d", *testutil.Loglevel), + fmt.Sprintf("--%s", SwarmSwapPaymentThresholdFlag.Name), strconv.Itoa(swap.DefaultPaymentThreshold + 1), + fmt.Sprintf("--%s", SwarmSwapDisconnectThresholdFlag.Name), strconv.Itoa(swap.DefaultDisconnectThreshold + 1), } + node.Cmd = runSwarm(t, flags...) node.Cmd.InputLine(testPassphrase) defer func() { @@ -296,6 +287,14 @@ func TestConfigCmdLineOverrides(t *testing.T) { t.Fatalf("Expected Cors flag to be set to %s, got %s", "*", info.Cors) } + if info.SwapPaymentThreshold != (swap.DefaultPaymentThreshold + 1) { + t.Fatalf("Expected SwapPaymentThreshold to be %d, but got %d", swap.DefaultPaymentThreshold+1, info.SwapPaymentThreshold) + } + + if info.SwapDisconnectThreshold != (swap.DefaultDisconnectThreshold + 1) { + t.Fatalf("Expected SwapDisconnectThreshold to be %d, but got %d", swap.DefaultDisconnectThreshold+1, info.SwapDisconnectThreshold) + } + node.Shutdown() } diff --git a/cmd/swarm/flags.go b/cmd/swarm/flags.go index 0b62888553..6af8a59151 100644 --- a/cmd/swarm/flags.go +++ b/cmd/swarm/flags.go @@ -20,7 +20,7 @@ package main import cli "gopkg.in/urfave/cli.v1" var ( - ChequebookAddrFlag = cli.StringFlag{ + SwarmSwapChequebookAddrFlag = cli.StringFlag{ Name: "chequebook", Usage: "chequebook contract address", EnvVar: SwarmEnvChequebookAddr, @@ -65,6 +65,16 @@ var ( Usage: "URL of the Ethereum API provider to use to settle SWAP payments", EnvVar: SwarmEnvSwapBackendURL, } + SwarmSwapPaymentThresholdFlag = cli.Uint64Flag{ + Name: "swap-payment-threshold", + Usage: "honey amount at which payment is triggered", + EnvVar: SwarmEnvSwapPaymentThreshold, + } + SwarmSwapDisconnectThresholdFlag = cli.Uint64Flag{ + Name: "swap-disconnect-threshold", + Usage: "honey amount at which a peer disconnects", + EnvVar: SwarmEnvSwapDisconnectThreshold, + } SwarmSyncDisabledFlag = cli.BoolTFlag{ Name: "nosync", Usage: "Disable swarm syncing", diff --git a/cmd/swarm/main.go b/cmd/swarm/main.go index e514bfab6b..2d7de27e5d 100644 --- a/cmd/swarm/main.go +++ b/cmd/swarm/main.go @@ -78,8 +78,7 @@ var gitCommit string //declare a few constant error messages, useful for later error check comparisons in test var ( - SwarmErrNoBZZAccount = "bzzaccount option is required but not set; check your config file, command line or environment variables" - SwarmErrSwapSetNoBackendURL = "SWAP is enabled but --swap-backend-url is not set" + SwarmErrNoBZZAccount = "bzzaccount option is required but not set; check your config file, command line or environment variables" ) // this help command gets added to any subcommand that does not define it explicitly @@ -177,8 +176,13 @@ func init() { CorsStringFlag, EnsAPIFlag, SwarmTomlConfigPathFlag, + //swap flags SwarmSwapEnabledFlag, SwarmSwapBackendURLFlag, + SwarmSwapDisconnectThresholdFlag, + SwarmSwapPaymentThresholdFlag, + SwarmSwapChequebookAddrFlag, + // end of swap flags SwarmSyncDisabledFlag, SwarmSyncUpdateDelay, SwarmMaxStreamPeerServersFlag, @@ -189,7 +193,6 @@ func init() { SwarmAccountFlag, SwarmBzzKeyHexFlag, SwarmNetworkIdFlag, - ChequebookAddrFlag, // upload flags SwarmApiFlag, SwarmRecursiveFlag, diff --git a/swap/config.go b/swap/config.go index 833a1a0470..ee942ae0c5 100644 --- a/swap/config.go +++ b/swap/config.go @@ -33,7 +33,6 @@ const ( // This is the amount of time in seconds which an issuer has to wait to decrease the harddeposit of a beneficiary. // The smart-contract allows for setting this variable differently per beneficiary defaultHarddepositTimeoutDuration = 24 * time.Hour - - // While Swap is unstable, it's only allowed to be run under a specific network ID + // Until we deploy swap officially, it's only allowed to be enabled under a specific network ID (use the --bzznetworkid flag to set it) AllowedNetworkID = 5 ) diff --git a/swap/oracle.go b/swap/honeyOracle.go similarity index 84% rename from swap/oracle.go rename to swap/honeyOracle.go index d907ddf473..50ae044936 100644 --- a/swap/oracle.go +++ b/swap/honeyOracle.go @@ -16,14 +16,15 @@ package swap -// PriceOracle is the interface through which Oracles will deliver prices -type PriceOracle interface { +// HoneyOracle is the interface through which Oracles will deliver prices +type HoneyOracle interface { GetPrice(honey uint64) (uint64, error) } -// NewPriceOracle returns the actual oracle to be used for discovering the price +// NewHoneyPriceOracle returns the actual oracle to be used for discovering the price // It will return a default one -func NewPriceOracle() PriceOracle { +func NewHoneyPriceOracle() HoneyOracle { + return &fixedPriceOracle{ honeyPrice: defaultHoneyPrice, } diff --git a/swap/peer.go b/swap/peer.go index f1043a3fac..debc23e85a 100644 --- a/swap/peer.go +++ b/swap/peer.go @@ -128,7 +128,7 @@ func (p *Peer) createCheque() (*Cheque, error) { // the balance should be negative here, we take the absolute value: honey := uint64(-p.getBalance()) - amount, err := p.swap.oracle.GetPrice(honey) + amount, err := p.swap.honeyPriceOracle.GetPrice(honey) if err != nil { return nil, fmt.Errorf("error getting price from oracle: %v", err) } @@ -166,7 +166,6 @@ func (p *Peer) sendCheque() error { } log.Info("sending cheque", "honey", cheque.Honey, "cumulativePayout", cheque.ChequeParams.CumulativePayout, "beneficiary", cheque.Beneficiary, "contract", cheque.Contract) - return p.Send(context.Background(), &EmitChequeMsg{ Cheque: cheque, }) diff --git a/swap/swap.go b/swap/swap.go index dab3cb393f..08052bd0cd 100644 --- a/swap/swap.go +++ b/swap/swap.go @@ -23,6 +23,7 @@ import ( "errors" "fmt" "math/big" + "path/filepath" "sync" "time" @@ -30,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethersphere/swarm/contracts/swap" contract "github.com/ethersphere/swarm/contracts/swap" @@ -54,9 +56,9 @@ type Swap struct { owner *Owner // contract access params *Params // economic and operational parameters contract swap.Contract // reference to the smart contract - oracle PriceOracle // the oracle providing the ether price for honey - paymentThreshold int64 // balance difference required for sending cheque - disconnectThreshold int64 // balance difference required for dropping peer + honeyPriceOracle HoneyOracle // oracle which resolves the price of honey (in Wei) + paymentThreshold int64 // honey amount at which a payment is triggered + disconnectThreshold int64 // honey amount at which a peer disconnects } // Owner encapsulates information related to accessing the contract @@ -78,20 +80,47 @@ func NewParams() *Params { } } -// New - swap constructor -func New(stateStore state.Store, prvkey *ecdsa.PrivateKey, backend contract.Backend) *Swap { +// new - swap constructor without integrity check +func new(stateStore state.Store, prvkey *ecdsa.PrivateKey, backend contract.Backend, disconnectThreshold uint64, paymentThreshold uint64) *Swap { return &Swap{ store: stateStore, peers: make(map[enode.ID]*Peer), backend: backend, owner: createOwner(prvkey), params: NewParams(), - paymentThreshold: DefaultPaymentThreshold, - disconnectThreshold: DefaultDisconnectThreshold, - oracle: NewPriceOracle(), + disconnectThreshold: int64(disconnectThreshold), + paymentThreshold: int64(paymentThreshold), + honeyPriceOracle: NewHoneyPriceOracle(), } } +// New - swap constructor with integrity checks +func New(dbPath string, prvkey *ecdsa.PrivateKey, backendURL string, disconnectThreshold uint64, paymentThreshold uint64) (*Swap, error) { + // we MUST have a backend + if backendURL == "" { + return nil, errors.New("swap init error: no backend URL given") + } + log.Info("connecting to SWAP API", "url", backendURL) + // initialize the balances store + stateStore, err := state.NewDBStore(filepath.Join(dbPath, "swap.db")) + if err != nil { + return nil, fmt.Errorf("swap init error: %s", err) + } + if disconnectThreshold <= paymentThreshold { + return nil, fmt.Errorf("swap init error: disconnect threshold lower or at payment threshold. DisconnectThreshold: %d, PaymentThreshold: %d", disconnectThreshold, paymentThreshold) + } + backend, err := ethclient.Dial(backendURL) + if err != nil { + return nil, fmt.Errorf("swap init error: error connecting to Ethereum API %s: %s", backendURL, err) + } + return new( + stateStore, + prvkey, + backend, + disconnectThreshold, + paymentThreshold), nil +} + const ( balancePrefix = "balance_" sentChequePrefix = "sent_cheque_" @@ -242,7 +271,8 @@ func (s *Swap) processAndVerifyCheque(cheque *Cheque, p *Peer) (uint64, error) { lastCheque := p.getLastReceivedCheque() - expectedAmount, err := s.oracle.GetPrice(cheque.Honey) + // TODO: there should probably be a lock here? + expectedAmount, err := s.honeyPriceOracle.GetPrice(cheque.Honey) if err != nil { return 0, err } diff --git a/swap/swap_test.go b/swap/swap_test.go index 0665c8f5cf..5f069bf375 100644 --- a/swap/swap_test.go +++ b/swap/swap_test.go @@ -20,13 +20,18 @@ import ( "bytes" "context" "crypto/ecdsa" + "crypto/rand" + "encoding/hex" "encoding/json" "fmt" "io/ioutil" "math/big" mrand "math/rand" "os" + "path" "reflect" + "runtime" + "strings" "testing" "time" @@ -40,6 +45,7 @@ import ( "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/simulations/adapters" + "github.com/ethereum/go-ethereum/rpc" contract "github.com/ethersphere/go-sw3/contracts-v0-1-0/simpleswap" cswap "github.com/ethersphere/swarm/contracts/swap" "github.com/ethersphere/swarm/p2p/protocols" @@ -301,6 +307,166 @@ func TestRepeatedBookings(t *testing.T) { verifyBookings(t, swap, append(bookings, mixedBookings...)) } +//TestNewSwapFailure attempts to initialze SWAP with (a combination of) parameters which are not allowed. The test checks whether there are indeed failures +func TestNewSwapFailure(t *testing.T) { + dir, err := ioutil.TempDir("", "swarmSwap") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + // a simple rpc endpoint for testing dialing + ipcEndpoint := path.Join(dir, "TestSwarmSwap.ipc") + + // windows namedpipes are not on filesystem but on NPFS + if runtime.GOOS == "windows" { + b := make([]byte, 8) + rand.Read(b) + ipcEndpoint = `\\.\pipe\TestSwarm-` + hex.EncodeToString(b) + } + + _, server, err := rpc.StartIPCEndpoint(ipcEndpoint, nil) + if err != nil { + t.Error(err) + } + defer server.Stop() + + prvKey, err := crypto.GenerateKey() + if err != nil { + t.Error(err) + } + + type testSwapConfig struct { + dbPath string + prvkey *ecdsa.PrivateKey + backendURL string + disconnectThreshold uint64 + paymentThreshold uint64 + } + + var config testSwapConfig + + for _, tc := range []struct { + name string + configure func(*testSwapConfig) + check func(*testing.T, *testSwapConfig) + }{ + { + name: "no backedURL", + configure: func(config *testSwapConfig) { + config.dbPath = dir + config.prvkey = prvKey + config.backendURL = "" + config.disconnectThreshold = DefaultDisconnectThreshold + config.paymentThreshold = DefaultPaymentThreshold + }, + check: func(t *testing.T, config *testSwapConfig) { + defer os.RemoveAll(config.dbPath) + _, err := New( + config.dbPath, + config.prvkey, + config.backendURL, + config.disconnectThreshold, + config.paymentThreshold, + ) + if !strings.Contains(err.Error(), "no backend URL given") { + t.Fatal("no backendURL, but created SWAP") + } + }, + }, + { + name: "disconnect threshold lower than payment threshold", + configure: func(config *testSwapConfig) { + config.dbPath = dir + config.prvkey = prvKey + config.backendURL = ipcEndpoint + config.disconnectThreshold = DefaultDisconnectThreshold + config.paymentThreshold = DefaultDisconnectThreshold + 1 + }, + check: func(t *testing.T, config *testSwapConfig) { + _, err := New( + config.dbPath, + config.prvkey, + config.backendURL, + config.disconnectThreshold, + config.paymentThreshold, + ) + if !strings.Contains(err.Error(), "swap init error: disconnect threshold lower or at payment threshold.") { + t.Fatal("disconnect threshold lower than payment threshold, but created SWAP") + } + }, + }, + { + name: "invalid backendURL", + configure: func(config *testSwapConfig) { + config.prvkey = prvKey + config.backendURL = "invalid backendURL" + config.disconnectThreshold = DefaultDisconnectThreshold + config.paymentThreshold = DefaultPaymentThreshold + }, + check: func(t *testing.T, config *testSwapConfig) { + defer os.RemoveAll(config.dbPath) + _, err := New( + config.dbPath, + config.prvkey, + config.backendURL, + config.disconnectThreshold, + config.paymentThreshold, + ) + if !strings.Contains(err.Error(), "swap init error: error connecting to Ethereum API") { + t.Fatal("invalid backendURL, but created SWAP", err) + } + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + dir, err := ioutil.TempDir("", "swarmSwap") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + config.dbPath = dir + + tc.configure(&config) + if tc.check != nil { + tc.check(t, &config) + } + }) + + } +} + +//TestDisconnectThreshold tests that the disconnect threshold is reached when adding the DefaultDisconnectThreshold amount to the peers balance +func TestDisconnectThreshold(t *testing.T) { + swap, clean := newTestSwap(t, ownerKey) + defer clean() + testPeer := newDummyPeer() + testDeploy(context.Background(), swap) + swap.addPeer(testPeer.Peer, swap.owner.address, swap.GetParams().ContractAddress) + swap.Add(DefaultDisconnectThreshold, testPeer.Peer) + err := swap.Add(1, testPeer.Peer) + if !strings.Contains(err.Error(), "disconnect threshold") { + t.Fatal(err) + } +} + +//TestPaymentThreshold tests that the payment threshold is reached when subtracting the DefaultPaymentThreshold amount from the peers balance +func TestPaymentThreshold(t *testing.T) { + swap, clean := newTestSwap(t, ownerKey) + defer clean() + testDeploy(context.Background(), swap) + testPeer := newDummyPeerWithSpec(Spec) + swap.addPeer(testPeer.Peer, swap.owner.address, swap.GetParams().ContractAddress) + if err := swap.Add(-DefaultPaymentThreshold, testPeer.Peer); err != nil { + t.Fatal() + } + + var cheque *Cheque + _ = swap.store.Get(sentChequeKey(testPeer.Peer.ID()), &cheque) + if cheque.CumulativePayout != DefaultPaymentThreshold { + t.Fatal() + } +} + // TestResetBalance tests that balances are correctly reset // The test deploys creates swap instances for each node, // deploys simulated contracts, sets the balance of each @@ -316,9 +482,6 @@ func TestResetBalance(t *testing.T) { defer clean2() ctx := context.Background() - // deploying would strictly speaking not be necessary, as the signing would also just work - // with empty contract addresses. Nevertheless to avoid later suprises and for - // coherence and clarity we deploy here so that we get a simulated contract address err := testDeploy(ctx, creditorSwap) if err != nil { t.Fatal(err) @@ -333,6 +496,7 @@ func TestResetBalance(t *testing.T) { // so creditor is the model of the remote mode for the debitor! (and vice versa) cPeer := newDummyPeerWithSpec(Spec) dPeer := newDummyPeerWithSpec(Spec) + fmt.Println(1) creditor, err := debitorSwap.addPeer(cPeer.Peer, creditorSwap.owner.address, debitorSwap.GetParams().ContractAddress) if err != nil { t.Fatal(err) @@ -487,7 +651,7 @@ func TestRestoreBalanceFromStateStore(t *testing.T) { } var newBalance int64 - stateStore.Get(testPeer.ID().String(), &newBalance) + stateStore.Get(testPeer.Peer.ID().String(), &newBalance) // compare the balances if tmpBalance != newBalance { @@ -522,7 +686,7 @@ func newBaseTestSwap(t *testing.T, key *ecdsa.PrivateKey) (*Swap, string) { } log.Debug("creating simulated backend") - swap := New(stateStore, key, testBackend) + swap := new(stateStore, key, testBackend, DefaultDisconnectThreshold, DefaultPaymentThreshold) return swap, dir } diff --git a/swarm.go b/swarm.go index dc8952063e..303f112f4f 100644 --- a/swarm.go +++ b/swarm.go @@ -44,7 +44,6 @@ import ( "github.com/ethersphere/swarm/bzzeth" "github.com/ethersphere/swarm/chunk" "github.com/ethersphere/swarm/contracts/ens" - cswap "github.com/ethersphere/swarm/contracts/swap" "github.com/ethersphere/swarm/fuse" "github.com/ethersphere/swarm/log" "github.com/ethersphere/swarm/network" @@ -79,7 +78,6 @@ type Swarm struct { retrieval *retrieval.Retrieval bzz *network.Bzz // the logistic manager bzzEth *bzzeth.BzzEth - backend cswap.Backend privateKey *ecdsa.PrivateKey netStore *storage.NetStore sfs *fuse.SwarmFS // need this to cleanup all the active mounts on node exit @@ -117,25 +115,19 @@ func NewSwarm(config *api.Config, mockStore *mock.NodeStore) (self *Swarm, err e if config.SwapEnabled { // for now, Swap can only be enabled in a whitelisted network if self.config.NetworkID != swap.AllowedNetworkID { - return nil, fmt.Errorf("swap can only be enabled under Network ID %d, found Network ID %d instead", swap.AllowedNetworkID, self.config.NetworkID) + return nil, fmt.Errorf("swap can only be enabled under BZZ Network ID %d, found Network ID %d instead", swap.AllowedNetworkID, self.config.NetworkID) } - // if Swap is enabled, we MUST have a contract API - if self.config.SwapBackendURL == "" { - return nil, errors.New("swap enabled but no contract address given; fatal error condition, aborting") - } - log.Info("connecting to SWAP API", "url", self.config.SwapBackendURL) - self.backend, err = ethclient.Dial(self.config.SwapBackendURL) - if err != nil { - return nil, fmt.Errorf("error connecting to SWAP API %s: %s", self.config.SwapBackendURL, err) - } - - // initialize the balances store - swapStore, err := state.NewDBStore(filepath.Join(config.Path, "swap.db")) + // create the accounting objects + self.swap, err = swap.New( + self.config.Path, + self.privateKey, + self.config.SwapBackendURL, + self.config.SwapDisconnectThreshold, + self.config.SwapPaymentThreshold, + ) if err != nil { return nil, err } - // create the accounting objects - self.swap = swap.New(swapStore, self.privateKey, self.backend) // start anonymous metrics collection self.accountingMetrics = protocols.SetupAccountingMetrics(10*time.Second, filepath.Join(config.Path, "metrics.db")) } diff --git a/swarm_test.go b/swarm_test.go index 36638dd7d9..ba4dc72e7a 100644 --- a/swarm_test.go +++ b/swarm_test.go @@ -75,9 +75,6 @@ func TestNewSwarm(t *testing.T) { if s.config != config { t.Error("config is not the same object") } - if s.backend != nil { - t.Error("backend is not nil") - } if s.privateKey == nil { t.Error("private key is not set") } @@ -121,8 +118,8 @@ func TestNewSwarm(t *testing.T) { config.NetworkID = swap.AllowedNetworkID }, check: func(t *testing.T, s *Swarm, _ *api.Config) { - if s.backend == nil { - t.Error("backend is nil") + if s.swap == nil { + t.Error("swap is nil") } }, }, @@ -133,8 +130,8 @@ func TestNewSwarm(t *testing.T) { config.SwapEnabled = false }, check: func(t *testing.T, s *Swarm, _ *api.Config) { - if s.backend != nil { - t.Error("backend is not nil") + if s.swap != nil { + t.Error("swap is not nil") } }, }, @@ -219,19 +216,6 @@ func TestNewSwarmFailure(t *testing.T) { configure func(*api.Config) check func(*testing.T, *Swarm, *api.Config) }{ - { - name: "with swap enabled and api endpoint blank", - configure: func(config *api.Config) { - config.SwapBackendURL = "" - config.SwapEnabled = true - config.NetworkID = swap.AllowedNetworkID - }, - check: func(t *testing.T, s *Swarm, _ *api.Config) { - if s != nil { - t.Error("swarm struct is not nil") - } - }, - }, { name: "with swap enabled and default network ID", configure: func(config *api.Config) {