diff --git a/client/asset/eth/eth.go b/client/asset/eth/eth.go index 45b9e8b321..074e3f194a 100644 --- a/client/asset/eth/eth.go +++ b/client/asset/eth/eth.go @@ -19,6 +19,7 @@ import ( "decred.org/dcrdex/server/asset/eth" "github.com/decred/dcrd/dcrutil/v3" "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/node" @@ -74,7 +75,8 @@ var ( DefaultConfigPath: defaultAppDir, // Incorrect if changed by user? ConfigOpts: configOpts, } - notImplementedErr = errors.New("not implemented") + notImplementedErr = errors.New("not implemented") + mainnetContractAddr = common.HexToAddress("") ) // Driver implements asset.Driver. @@ -98,21 +100,25 @@ func (d *Driver) Info() *asset.WalletInfo { // ethFetcher represents a blockchain information fetcher. In practice, it is // satisfied by rpcclient. For testing, it can be satisfied by a stub. type ethFetcher interface { - accounts(ctx context.Context) ([]common.Address, error) + accounts() []*accounts.Account addPeer(ctx context.Context, peer string) error - balance(ctx context.Context, acct common.Address) (*big.Int, error) + balance(ctx context.Context, acct *accounts.Account) (*big.Int, error) bestBlockHash(ctx context.Context) (common.Hash, error) block(ctx context.Context, hash common.Hash) (*types.Block, error) blockNumber(ctx context.Context) (uint64, error) - connect(ctx context.Context, node *node.Node) error - importAccount(pw string, privKeyB []byte) (accounts.Account, error) - lock(ctx context.Context, acct common.Address) error - locked(ctx context.Context, acct common.Address) (bool, error) + connect(ctx context.Context, node *node.Node, contractAddr common.Address) error + importAccount(pw string, privKeyB []byte) (*accounts.Account, error) + initiate(opts *bind.TransactOpts, netID int64, refundTimestamp int64, secretHash [32]byte, participant common.Address) (*types.Transaction, error) + lock(ctx context.Context, acct *accounts.Account) error + locked(ctx context.Context, acct *accounts.Account) (bool, error) nodeInfo(ctx context.Context) (*p2p.NodeInfo, error) - sendToAddr(ctx context.Context, acct, addr common.Address, amt, gasFee *big.Int) error + pendingTransactions(ctx context.Context) ([]*types.Transaction, error) + redeem(opts *bind.TransactOpts, netID int64, secret, secretHash [32]byte) (*types.Transaction, error) + refund(opts *bind.TransactOpts, netID int64, secretHash [32]byte) (*types.Transaction, error) + sendToAddr(ctx context.Context, acct *accounts.Account, addr common.Address, amt, gasFee *big.Int) error shutdown() syncStatus(ctx context.Context) (bool, float32, error) - unlock(ctx context.Context, pw string, acct common.Address) error + unlock(ctx context.Context, pw string, acct *accounts.Account) error } // Check that ExchangeWallet satisfies the asset.Wallet interface. @@ -128,13 +134,15 @@ type ExchangeWallet struct { ctx context.Context // the asset subsystem starts with Connect(ctx) node ethFetcher log dex.Logger - acct common.Address tipChange func(error) internalNode *node.Node tipMtx sync.RWMutex currentTip *types.Block + + acctMtx sync.RWMutex + acct *accounts.Account } // Info returns basic information about the wallet and asset. @@ -162,6 +170,7 @@ func NewWallet(assetCFG *asset.WalletConfig, logger dex.Logger, network dex.Netw log: logger, tipChange: assetCFG.TipChange, internalNode: node, + acct: new(accounts.Account), }, nil } @@ -174,7 +183,7 @@ func (eth *ExchangeWallet) shutdown() { // Connect connects to the node RPC server. A dex.Connector. func (eth *ExchangeWallet) Connect(ctx context.Context) (*sync.WaitGroup, error) { c := rpcclient{} - if err := c.connect(ctx, eth.internalNode); err != nil { + if err := c.connect(ctx, eth.internalNode, mainnetContractAddr); err != nil { return nil, err } eth.node = &c @@ -211,7 +220,9 @@ func (eth *ExchangeWallet) Connect(ctx context.Context) (*sync.WaitGroup, error) // // TODO: Consider adding multiple accounts. func (eth *ExchangeWallet) OwnsAddress(address string) (bool, error) { - return eth.acct.String() == address, nil + eth.acctMtx.RLock() + defer eth.acctMtx.RUnlock() + return eth.acct.Address.String() == address, nil } // Balance returns the total available funds in the account. @@ -221,6 +232,8 @@ func (eth *ExchangeWallet) OwnsAddress(address string) (bool, error) { // TODO: Ethereum balances can easily go over the max value of a uint64. // asset.Balance must be changed in a way to accomodate this. func (eth *ExchangeWallet) Balance() (*asset.Balance, error) { + eth.acctMtx.RLock() + defer eth.acctMtx.RUnlock() bigbal, err := eth.node.balance(eth.ctx, eth.acct) if err != nil { return nil, err @@ -334,21 +347,29 @@ func (*ExchangeWallet) Refund(coinID, contract dex.Bytes) (dex.Bytes, error) { // Address returns an address for the exchange wallet. func (eth *ExchangeWallet) Address() (string, error) { - return eth.acct.String(), nil + eth.acctMtx.RLock() + defer eth.acctMtx.RUnlock() + return eth.acct.Address.String(), nil } // Unlock unlocks the exchange wallet. func (eth *ExchangeWallet) Unlock(pw string) error { + eth.acctMtx.RLock() + defer eth.acctMtx.RUnlock() return eth.node.unlock(eth.ctx, pw, eth.acct) } // Lock locks the exchange wallet. func (eth *ExchangeWallet) Lock() error { + eth.acctMtx.RLock() + defer eth.acctMtx.RUnlock() return eth.node.lock(eth.ctx, eth.acct) } // Locked will be true if the wallet is currently locked. func (eth *ExchangeWallet) Locked() bool { + eth.acctMtx.RLock() + defer eth.acctMtx.RUnlock() locked, err := eth.node.locked(eth.ctx, eth.acct) if err != nil { eth.log.Errorf("unable to get locked status of account: %v", err) @@ -373,6 +394,8 @@ func (*ExchangeWallet) PayFee(address string, regFee uint64) (asset.Coin, error) // // TODO: Value could be larger than a uint64. Deal with it... func (eth *ExchangeWallet) Withdraw(addr string, value uint64) (asset.Coin, error) { + eth.acctMtx.RLock() + defer eth.acctMtx.RUnlock() return nil, eth.node.sendToAddr(eth.ctx, eth.acct, common.HexToAddress(addr), new(big.Int).SetUint64(value), new(big.Int).SetUint64(defaultGasFee)) } diff --git a/client/asset/eth/eth_test.go b/client/asset/eth/eth_test.go index 726d0a13c2..af58bd7995 100644 --- a/client/asset/eth/eth_test.go +++ b/client/asset/eth/eth_test.go @@ -12,6 +12,7 @@ import ( "decred.org/dcrdex/dex" "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/node" @@ -31,7 +32,7 @@ type testNode struct { blkErr error } -func (n *testNode) connect(ctx context.Context, node *node.Node) error { +func (n *testNode) connect(ctx context.Context, node *node.Node, addr common.Address) error { return n.connectErr } func (n *testNode) shutdown() {} @@ -41,29 +42,29 @@ func (n *testNode) bestBlockHash(ctx context.Context) (common.Hash, error) { func (n *testNode) block(ctx context.Context, hash common.Hash) (*types.Block, error) { return n.blk, n.blkErr } -func (n *testNode) accounts(ctx context.Context) ([]common.Address, error) { - return nil, nil +func (n *testNode) accounts() []*accounts.Account { + return nil } -func (n *testNode) balance(ctx context.Context, acct common.Address) (*big.Int, error) { +func (n *testNode) balance(ctx context.Context, acct *accounts.Account) (*big.Int, error) { return nil, nil } -func (n *testNode) sendToAddr(ctx context.Context, acct, addr common.Address, amt, gasFee *big.Int) error { +func (n *testNode) sendToAddr(ctx context.Context, acct *accounts.Account, addr common.Address, amt, gasFee *big.Int) error { return nil } func (n *testNode) syncStatus(ctx context.Context) (bool, float32, error) { return false, 0, nil } -func (n *testNode) unlock(ctx context.Context, pw string, acct common.Address) error { +func (n *testNode) unlock(ctx context.Context, pw string, acct *accounts.Account) error { return nil } -func (n *testNode) lock(ctx context.Context, acct common.Address) error { +func (n *testNode) lock(ctx context.Context, acct *accounts.Account) error { return nil } -func (n *testNode) locked(ctx context.Context, acct common.Address) (bool, error) { +func (n *testNode) locked(ctx context.Context, acct *accounts.Account) (bool, error) { return false, nil } -func (n *testNode) importAccount(pw string, privKeyB []byte) (accounts.Account, error) { - return accounts.Account{}, nil +func (n *testNode) importAccount(pw string, privKeyB []byte) (*accounts.Account, error) { + return nil, nil } func (n *testNode) addPeer(ctx context.Context, peer string) error { return nil @@ -74,6 +75,18 @@ func (n *testNode) nodeInfo(ctx context.Context) (*p2p.NodeInfo, error) { func (n *testNode) blockNumber(ctx context.Context) (uint64, error) { return 0, nil } +func (n *testNode) pendingTransactions(ctx context.Context) ([]*types.Transaction, error) { + return nil, nil +} +func (n *testNode) initiate(opts *bind.TransactOpts, netID int64, refundTimestamp int64, secretHash [32]byte, participant common.Address) (*types.Transaction, error) { + return nil, nil +} +func (n *testNode) redeem(opts *bind.TransactOpts, netID int64, secret, secretHash [32]byte) (*types.Transaction, error) { + return nil, nil +} +func (n *testNode) refund(opts *bind.TransactOpts, netID int64, secretHash [32]byte) (*types.Transaction, error) { + return nil, nil +} func TestLoadConfig(t *testing.T) { tests := []struct { diff --git a/client/asset/eth/node.go b/client/asset/eth/node.go index fd570c117a..d02c48e500 100644 --- a/client/asset/eth/node.go +++ b/client/asset/eth/node.go @@ -20,46 +20,22 @@ import ( "github.com/ethereum/go-ethereum/params" ) -// TODO: Store this in a file accessible by the harness and this. const ( - simnetGenesis = `{ - "config": { - "chainId": 42, - "homesteadBlock": 0, - "eip150Block": 0, - "eip155Block": 0, - "eip158Block": 0, - "byzantiumBlock": 0, - "constantinopleBlock": 0, - "petersburgBlock": 0, - "clique": { - "period": 1, - "epoch": 30000 - } - }, - "difficulty": "1", - "gasLimit": "12487783", - "extradata": "0x00000000000000000000000000000000000000000000000000000000000000009ebba10a6136607688ca4f27fab70e23938cd0270000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "alloc": { - "18d65fb8d60c1199bb1ad381be47aa692b482605": { - "balance": "11000000000000000000000" - }, - "4f8ef3892b65ed7fc356ff473a2ef2ae5ec27a06": { - "balance": "11000000000000000000000" - }, - "2b84C791b79Ee37De042AD2ffF1A253c3ce9bc27": { - "balance": "11000000000000000000000" - } - } -}` maxPeers = 10 ) +var simnetGenesis string + type nodeConfig struct { net dex.Network listenAddr, appDir string } +// SetSimnetGenesis should be set before using on simnet. +func SetSimnetGenesis(sng string) { + simnetGenesis = sng +} + func runNode(cfg *nodeConfig) (*node.Node, error) { stackConf := &node.Config{DataDir: cfg.appDir} diff --git a/client/asset/eth/rpcclient.go b/client/asset/eth/rpcclient.go index 897b25bfff..5d85887ffc 100644 --- a/client/asset/eth/rpcclient.go +++ b/client/asset/eth/rpcclient.go @@ -10,7 +10,10 @@ import ( "fmt" "math/big" + "decred.org/dcrdex/server/asset/eth" + //"github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -32,11 +35,12 @@ type rpcclient struct { // ec wraps the client with some useful calls. ec *ethclient.Client n *node.Node + es *eth.Eth } // connect connects to a node. It then wraps ethclient's client and // bundles commands in a form we can easily use. -func (c *rpcclient) connect(ctx context.Context, node *node.Node) error { +func (c *rpcclient) connect(ctx context.Context, node *node.Node, contractAddr common.Address) error { client, err := node.Attach() if err != nil { return fmt.Errorf("unable to dial rpc: %v", err) @@ -44,6 +48,10 @@ func (c *rpcclient) connect(ctx context.Context, node *node.Node) error { c.c = client c.ec = ethclient.NewClient(client) c.n = node + c.es, err = eth.NewEth(contractAddr, c.ec) + if err != nil { + return fmt.Errorf("unable to find swap contract: %v", err) + } return nil } @@ -89,28 +97,40 @@ func (c *rpcclient) block(ctx context.Context, hash common.Hash) (*types.Block, } // Accounts uses a raw request to obtain all accounts from personal.listAccounts. -func (c *rpcclient) accounts(ctx context.Context) ([]common.Address, error) { - var res []common.Address - if err := c.c.CallContext(ctx, &res, "personal_listAccounts"); err != nil { - return nil, err +func (c *rpcclient) accounts() []*accounts.Account { + var accts []*accounts.Account + for _, wallet := range c.n.AccountManager().Wallets() { + for _, acct := range wallet.Accounts() { + accts = append(accts, &acct) + } } - return res, nil + return accts } // Balance gets the current balance of an account. -func (c *rpcclient) balance(ctx context.Context, acct common.Address) (*big.Int, error) { - return c.ec.BalanceAt(ctx, acct, nil) +func (c *rpcclient) balance(ctx context.Context, acct *accounts.Account) (*big.Int, error) { + return c.ec.BalanceAt(ctx, acct.Address, nil) } // Unlock uses a raw request to unlock an account indefinitely. -func (c *rpcclient) unlock(ctx context.Context, pw string, acct common.Address) error { +func (c *rpcclient) unlock(ctx context.Context, pw string, acct *accounts.Account) error { // Passing 0 as the last argument unlocks with not lock time. - return c.c.CallContext(ctx, nil, "personal_unlockAccount", acct, pw, 0) + return c.c.CallContext(ctx, nil, "personal_unlockAccount", acct.Address.String(), pw, 0) } // Lock uses a raw request to unlock an account indefinitely. -func (c *rpcclient) lock(ctx context.Context, acct common.Address) error { - return c.c.CallContext(ctx, nil, "personal_lockAccount", acct) +func (c *rpcclient) lock(ctx context.Context, acct *accounts.Account) error { + return c.c.CallContext(ctx, nil, "personal_lockAccount", acct.Address.String()) +} + +// pendingTransactions returns pending transactions. +func (c *rpcclient) pendingTransactions(ctx context.Context) ([]*types.Transaction, error) { + var ptxs []*types.Transaction + err := c.c.CallContext(ctx, &ptxs, "eth_pendingTransactions") + if err != nil { + return nil, err + } + return ptxs, nil } // addPeer adds a peer. @@ -138,7 +158,7 @@ func (c *rpcclient) nodeInfo(ctx context.Context) (*p2p.NodeInfo, error) { } // Locked uses a raw request to unlock an account indefinitely. -func (c *rpcclient) locked(ctx context.Context, acct common.Address) (bool, error) { +func (c *rpcclient) locked(ctx context.Context, acct *accounts.Account) (bool, error) { type rawWallet struct { URL string `json:"url"` Status string `json:"status"` @@ -153,7 +173,7 @@ func (c *rpcclient) locked(ctx context.Context, acct common.Address) (bool, erro findWallet := func() bool { for _, w := range res { for _, a := range w.Accounts { - if bytes.Equal(a.Address[:], acct[:]) { + if bytes.Equal(a.Address[:], acct.Address[:]) { wallet = w return true } @@ -168,9 +188,9 @@ func (c *rpcclient) locked(ctx context.Context, acct common.Address) (bool, erro } // SendToAddr uses a raw request to send funds to an addr from acct. -func (c *rpcclient) sendToAddr(ctx context.Context, acct, addr common.Address, amt, gasFee *big.Int) error { +func (c *rpcclient) sendToAddr(ctx context.Context, acct *accounts.Account, addr common.Address, amt, gasFee *big.Int) error { tx := map[string]string{ - "from": fmt.Sprintf("0x%x", acct), + "from": fmt.Sprintf("0x%x", acct.Address), "to": fmt.Sprintf("0x%x", addr), "value": fmt.Sprintf("0x%x", amt), "gasPrice": fmt.Sprintf("0x%x", gasFee), @@ -194,11 +214,88 @@ func (c *rpcclient) syncStatus(ctx context.Context) (bool, float32, error) { // importAccount imports an account into the ethereum wallet by private key // that can be unlocked with password. -func (c *rpcclient) importAccount(pw string, privKeyB []byte) (accounts.Account, error) { +func (c *rpcclient) importAccount(pw string, privKeyB []byte) (*accounts.Account, error) { privKey, err := crypto.ToECDSA(privKeyB) if err != nil { - return accounts.Account{}, fmt.Errorf("Error restoring private key: %v \n", err) + return new(accounts.Account), fmt.Errorf("error restoring private key: %v \n", err) } ks := c.n.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) - return ks.ImportECDSA(privKey, pw) + acct, err := ks.ImportECDSA(privKey, pw) + if err != nil { + return nil, err + } + return &acct, nil +} + +// wallet returns a wallet that owns acct from an ethereum wallet. +// accounts.Wallet is an interface. +func (c *rpcclient) wallet(acct accounts.Account) (accounts.Wallet, error) { + wallet, err := c.n.AccountManager().Find(acct) + if err != nil { + return nil, fmt.Errorf("error finding wallet for account %s: %v \n", acct.Address, err) + } + return wallet, nil +} + +func (c *rpcclient) initiate(txOpts *bind.TransactOpts, netID int64, refundTimestamp int64, secretHash [32]byte, participant common.Address) (*types.Transaction, error) { + /* + callOpts := &bind.CallOpts{ + Pending: true, + From: txOpts.From, + Context: txOpts.Context, + } + swap, err := c.es.Swap(callOpts, secretHash) + spew.Dump(swap) + if err != nil { + return nil, err + } + if swap.State != 0 { + return nil, fmt.Errorf("swap already at state %v", swap.State) + } + */ + wallet, err := c.wallet(accounts.Account{Address: txOpts.From}) + if err != nil { + return nil, err + } + txOpts.Signer = func(addr common.Address, tx *types.Transaction) (*types.Transaction, error) { + return wallet.SignTx(accounts.Account{Address: addr}, tx, big.NewInt(netID)) + } + return c.es.Initiate(txOpts, big.NewInt(refundTimestamp), secretHash, participant) +} + +func (c *rpcclient) redeem(txOpts *bind.TransactOpts, netID int64, secret, secretHash [32]byte) (*types.Transaction, error) { + /* + callOpts := &bind.CallOpts{ + Pending: true, + From: txOpts.From, + // BlockNumber *big.Int // Optional the block number on which the call should be performed + Context: txOpts.Context, + } + swap, err := c.es.Swap(callOpts, secretHash) + if err != nil { + return nil, err + } + if swap.State != 1 { + return nil, fmt.Errorf("swap already at state %v", swap.State) + } + */ + wallet, err := c.wallet(accounts.Account{Address: txOpts.From}) + if err != nil { + return nil, err + } + txOpts.Signer = func(addr common.Address, tx *types.Transaction) (*types.Transaction, error) { + return wallet.SignTx(accounts.Account{Address: addr}, tx, big.NewInt(netID)) + } + return c.es.Redeem(txOpts, secret, secretHash) +} + +func (c *rpcclient) refund(opts *bind.TransactOpts, netID int64, secretHash [32]byte) (*types.Transaction, error) { + wallet, err := c.wallet(accounts.Account{Address: opts.From}) + if err != nil { + return nil, err + } + opts.Signer = func(addr common.Address, tx *types.Transaction) (*types.Transaction, error) { + return wallet.SignTx(accounts.Account{Address: addr}, tx, big.NewInt(netID)) + } + return c.es.Refund(opts, secretHash) } diff --git a/client/asset/eth/rpcclient_harness_test.go b/client/asset/eth/rpcclient_harness_test.go index be1b7a8a80..58bc4c5c53 100644 --- a/client/asset/eth/rpcclient_harness_test.go +++ b/client/asset/eth/rpcclient_harness_test.go @@ -10,17 +10,25 @@ package eth import ( "context" + "crypto/sha256" "encoding/hex" + "errors" "fmt" "io/ioutil" "math/big" "os" + "os/exec" + "path/filepath" "testing" "time" "decred.org/dcrdex/client/asset" "decred.org/dcrdex/dex" + "decred.org/dcrdex/dex/encode" "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" ) const ( @@ -29,12 +37,56 @@ const ( ) var ( - homeDir = os.Getenv("HOME") - ethClient = new(rpcclient) - ctx context.Context - tLogger = dex.StdOutLogger("ETHTEST", dex.LevelTrace) + homeDir = os.Getenv("HOME") + contractAddrFile = filepath.Join(homeDir, "dextest", "eth", "contract_addr.txt") + genesisFile = filepath.Join(homeDir, "dextest", "eth", "genesis.json") + alphaNodeDir = filepath.Join(homeDir, "dextest", "eth", "alpha", "node") + ethClient = new(rpcclient) + ctx context.Context + tLogger = dex.StdOutLogger("ETHTEST", dex.LevelTrace) + simnetAddr = common.HexToAddress("2b84C791b79Ee37De042AD2ffF1A253c3ce9bc27") + simnetAcct = &accounts.Account{Address: simnetAddr} + participantAddr = common.HexToAddress("345853e21b1d475582E71cC269124eD5e2dD3422") + participantAcct = &accounts.Account{Address: participantAddr} + simnetID = int64(42) + newTXOpts = func(ctx context.Context, from common.Address, value int64) *bind.TransactOpts { + return &bind.TransactOpts{ + GasPrice: big.NewInt(81000000000), + GasLimit: 1000000, + Context: ctx, + From: from, + Value: big.NewInt(value), + } + } ) +func waitForMined(t *testing.T) error { + t.Helper() + err := exec.Command("geth", "--datadir="+alphaNodeDir, "attach", "--exec", "miner.start()").Run() + if err != nil { + return err + } + defer func() { + _ = exec.Command("geth", "--datadir="+alphaNodeDir, "attach", "--exec", "miner.stop()").Run() + }() + i := 0 + for { + txs, err := ethClient.pendingTransactions(ctx) + if err != nil { + return err + } + if len(txs) == 0 { + return nil + } + if i == 50 { + return errors.New("timed out") + } + i++ + time.Sleep(time.Second) + } + +} + func TestMain(m *testing.M) { var cancel context.CancelFunc ctx, cancel = context.WithCancel(context.Background()) @@ -42,11 +94,37 @@ func TestMain(m *testing.M) { cancel() ethClient.shutdown() }() - tmpDir, err := ioutil.TempDir("", "") + tmpDir, err := ioutil.TempDir("", "dextest") if err != nil { fmt.Printf("error creating temp dir: %v\n", err) os.Exit(1) } + defer os.RemoveAll(tmpDir) + addrBytes, err := ioutil.ReadFile(contractAddrFile) + if err != nil { + fmt.Printf("error reading contract address: %v\n", err) + os.Exit(1) + } + addrLen := len(addrBytes) + if addrLen == 0 { + fmt.Printf("no contract address found at %v\n", contractAddrFile) + os.Exit(1) + } + addrBytes = addrBytes[:addrLen-1] + fmt.Printf("Contract address is %v\n", string(addrBytes)) + genBytes, err := ioutil.ReadFile(genesisFile) + if err != nil { + fmt.Printf("error reading genesis file: %v\n", err) + os.Exit(1) + } + genLen := len(genBytes) + if genLen == 0 { + fmt.Printf("no genesis found at %v\n", genesisFile) + os.Exit(1) + } + genBytes = genBytes[:genLen-1] + fmt.Printf("Genesis is:\n%v\n", string(genBytes)) + SetSimnetGenesis(string(genBytes)) settings := map[string]string{ "appdir": tmpDir, "nodelistenaddr": "localhost:30355", @@ -56,12 +134,12 @@ func TestMain(m *testing.M) { fmt.Printf("error starting node: %v\n", err) os.Exit(1) } - fmt.Printf("node created at: %v\n", tmpDir) + fmt.Printf("Node created at: %v\n", tmpDir) defer func() { wallet.internalNode.Close() wallet.internalNode.Wait() }() - if err := ethClient.connect(ctx, wallet.internalNode); err != nil { + if err := ethClient.connect(ctx, wallet.internalNode, common.BytesToAddress(addrBytes)); err != nil { fmt.Printf("connect error: %v\n", err) os.Exit(1) } @@ -130,7 +208,6 @@ func TestBlock(t *testing.T) { } func TestImportAccounts(t *testing.T) { - pw := "abc" // The address of this will be 2b84C791b79Ee37De042AD2ffF1A253c3ce9bc27. privB, err := hex.DecodeString("9447129055a25c8496fca9e5ee1b9463e47e6043ff0c288d07169e8284860e34") if err != nil { @@ -141,22 +218,25 @@ func TestImportAccounts(t *testing.T) { t.Fatal(err) } spew.Dump(acct) -} - -func TestAccounts(t *testing.T) { - accts, err := ethClient.accounts(ctx) + // The address of this will be 345853e21b1d475582E71cC269124eD5e2dD3422. + privB, err = hex.DecodeString("0695b9347a4dc096ae5c6f1935380ceba550c70b112f1323c211bade4d11651a") if err != nil { t.Fatal(err) } + acct, err = ethClient.importAccount(pw, privB) + if err != nil { + t.Fatal(err) + } + spew.Dump(acct) +} + +func TestAccounts(t *testing.T) { + accts := ethClient.accounts() spew.Dump(accts) } func TestBalance(t *testing.T) { - accts, err := ethClient.accounts(ctx) - if err != nil { - t.Fatal(err) - } - bal, err := ethClient.balance(ctx, accts[0]) + bal, err := ethClient.balance(ctx, simnetAcct) if err != nil { t.Fatal(err) } @@ -164,37 +244,25 @@ func TestBalance(t *testing.T) { } func TestUnlock(t *testing.T) { - accts, err := ethClient.accounts(ctx) - if err != nil { - t.Fatal(err) - } - err = ethClient.unlock(ctx, pw, accts[0]) + err := ethClient.unlock(ctx, pw, simnetAcct) if err != nil { t.Fatal(err) } } func TestLock(t *testing.T) { - accts, err := ethClient.accounts(ctx) - if err != nil { - t.Fatal(err) - } - err = ethClient.lock(ctx, accts[0]) + err := ethClient.lock(ctx, simnetAcct) if err != nil { t.Fatal(err) } } func TestLocked(t *testing.T) { - accts, err := ethClient.accounts(ctx) + err := ethClient.unlock(ctx, pw, simnetAcct) if err != nil { t.Fatal(err) } - err = ethClient.unlock(ctx, pw, accts[0]) - if err != nil { - t.Fatal(err) - } - locked, err := ethClient.locked(ctx, accts[0]) + locked, err := ethClient.locked(ctx, simnetAcct) if err != nil { t.Fatal(err) } @@ -204,17 +272,16 @@ func TestLocked(t *testing.T) { } func TestSendToAddr(t *testing.T) { - accts, err := ethClient.accounts(ctx) + err := ethClient.unlock(ctx, pw, simnetAcct) if err != nil { t.Fatal(err) } - err = ethClient.unlock(ctx, pw, accts[0]) + err = ethClient.sendToAddr(ctx, simnetAcct, simnetAddr, big.NewInt(1), big.NewInt(82000000000)) if err != nil { t.Fatal(err) } - err = ethClient.sendToAddr(ctx, accts[0], accts[0], big.NewInt(1), big.NewInt(82000000000)) - if err != nil { - t.Fatal(err) + if err := waitForMined(t); err != nil { + t.Fatal("timeout") } } @@ -226,3 +293,89 @@ func TestSyncStatus(t *testing.T) { spew.Dump(synced) spew.Dump(ratio) } + +func TestInitiate(t *testing.T) { + now := time.Now().Unix() + var secretHash [32]byte + copy(secretHash[:], encode.RandomBytes(32)) + err := ethClient.unlock(ctx, pw, simnetAcct) + if err != nil { + t.Fatal(err) + } + txOpts := newTXOpts(ctx, simnetAddr, 100) + tests := []struct { + name string + wantErr bool + }{{ + name: "ok", + //}, { + // name: "duplicate secretHash", + // wantErr: true, + }} + + for _, test := range tests { + tx, err := ethClient.initiate(txOpts, simnetID, now, secretHash, participantAddr) + if test.wantErr { + if err == nil { + t.Fatalf("expeted error for test %v", test.name) + } + continue + } + if err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + spew.Dump(tx) + } +} + +func TestRedeem(t *testing.T) { + tests := []struct { + name string + sleep time.Duration + redeemer common.Address + wantErr bool + }{{ + name: "ok", + sleep: 3, + redeemer: participantAcct.Address, + }} + + for _, test := range tests { + err := ethClient.unlock(ctx, pw, simnetAcct) + if err != nil { + t.Fatal(err) + } + err = ethClient.unlock(ctx, pw, participantAcct) + if err != nil { + t.Fatal(err) + } + txOpts := newTXOpts(ctx, simnetAddr, 1000000000000000000) + var secret [32]byte + copy(secret[:], encode.RandomBytes(32)) + secretHash := sha256.Sum256(secret[:]) + inThirtySecs := time.Now().Add(time.Second * 30).Unix() + _, err = ethClient.initiate(txOpts, simnetID, inThirtySecs, secretHash, participantAcct.Address) + if err != nil { + t.Fatalf("unable to initiate swap for test %v: %v ", test.name, err) + } + time.Sleep(time.Second * test.sleep) + if err := waitForMined(t); err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + txOpts = newTXOpts(ctx, test.redeemer, 0) + tx, err := ethClient.redeem(txOpts, simnetID, secret, secretHash) + if test.wantErr { + if err == nil { + t.Fatalf("expeted error for test %v", test.name) + } + continue + } + if err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + if err := waitForMined(t); err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + spew.Dump(tx) + } +} diff --git a/dex/testing/eth/create-node.sh b/dex/testing/eth/create-node.sh index c9d8bf804b..0eb5ed183a 100755 --- a/dex/testing/eth/create-node.sh +++ b/dex/testing/eth/create-node.sh @@ -130,7 +130,7 @@ if [ "${SYNC_MODE}" = "fast" ]; then # localhost, and our custom configuration file. tmux send-keys -t "$TMUX_WIN_ID" "${NODES_ROOT}/harness-ctl/${NAME} --nodiscover " \ "--config ${NODE_DIR}/eth.conf --unlock ${CHAIN_ADDRESS} " \ - "--password ${GROUP_DIR}/password --light.serve 25" C-m + "--password ${GROUP_DIR}/password --light.serve 25 --verbosity 5 --vmdebug" C-m else # Start the eth node listening restricted to localhost and our custom diff --git a/dex/testing/eth/harness.sh b/dex/testing/eth/harness.sh index db7a08d17d..c5912fb344 100755 --- a/dex/testing/eth/harness.sh +++ b/dex/testing/eth/harness.sh @@ -36,6 +36,8 @@ DELTA_NODE_KEY="725394672587b34bbf15580c59e5199c75c2c7e998ba8df3cb38cc4347d46e2b DELTA_ENODE="ca414c361d1a38716170923e4900d9dc9203dbaf8fdcaee73e1f861df9fdf20a1453b76fd218c18bc6f3c7e13cbca0b3416af02a53b8e31188faa45aab398d1c" DELTA_NODE_PORT="30307" +ETH_SWAP_V0="608060405234801561001057600080fd5b50610c07806100206000396000f3fe60806040526004361061003f5760003560e01c80637249fbb61461004457806376467cbd1461006d578063ae052147146100b1578063b31597ad146100cd575b600080fd5b34801561005057600080fd5b5061006b6004803603810190610066919061087a565b6100f6565b005b34801561007957600080fd5b50610094600480360381019061008f919061087a565b6102f1565b6040516100a8989796959493929190610a0d565b60405180910390f35b6100cb60048036038101906100c69190610908565b610386565b005b3480156100d957600080fd5b506100f460048036038101906100ef91906108cc565b6105bc565b005b803360016003811115610132577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60008084815260200190815260200160002060070160009054906101000a900460ff16600381111561018d577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b1461019757600080fd5b8073ffffffffffffffffffffffffffffffffffffffff1660008084815260200190815260200160002060040160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461020457600080fd5b60008060008481526020019081526020016000206001015490508042101561022b57600080fd5b3373ffffffffffffffffffffffffffffffffffffffff166108fc600080878152602001908152602001600020600601549081150290604051600060405180830381858888f19350505050158015610286573d6000803e3d6000fd5b50600360008086815260200190815260200160002060070160006101000a81548160ff021916908360038111156102e6577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b021790555050505050565b60006020528060005260406000206000915090508060000154908060010154908060020154908060030154908060040160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060050160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060060154908060070160009054906101000a900460ff16905088565b826000341161039457600080fd5b600081116103a157600080fd5b82600060038111156103dc577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60008083815260200190815260200160002060070160009054906101000a900460ff166003811115610437577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b1461044157600080fd5b4360008086815260200190815260200160002060000181905550846000808681526020019081526020016000206001018190555083600080868152602001908152602001600020600201819055503360008086815260200190815260200160002060040160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508260008086815260200190815260200160002060050160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055503460008086815260200190815260200160002060060181905550600160008086815260200190815260200160002060070160006101000a81548160ff021916908360038111156105b0577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b02179055505050505050565b808233600160038111156105f9577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60008085815260200190815260200160002060070160009054906101000a900460ff166003811115610654577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b1461065e57600080fd5b8073ffffffffffffffffffffffffffffffffffffffff1660008085815260200190815260200160002060050160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146106cb57600080fd5b826002836040516020016106df91906109db565b6040516020818303038152906040526040516106fb91906109f6565b602060405180830381855afa158015610718573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061073b91906108a3565b1461074557600080fd5b3373ffffffffffffffffffffffffffffffffffffffff166108fc600080878152602001908152602001600020600601549081150290604051600060405180830381858888f193505050501580156107a0573d6000803e3d6000fd5b50600260008086815260200190815260200160002060070160006101000a81548160ff02191690836003811115610800577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b021790555084600080868152602001908152602001600020600301819055505050505050565b60008135905061083581610b8c565b92915050565b60008135905061084a81610ba3565b92915050565b60008151905061085f81610ba3565b92915050565b60008135905061087481610bba565b92915050565b60006020828403121561088c57600080fd5b600061089a8482850161083b565b91505092915050565b6000602082840312156108b557600080fd5b60006108c384828501610850565b91505092915050565b600080604083850312156108df57600080fd5b60006108ed8582860161083b565b92505060206108fe8582860161083b565b9150509250929050565b60008060006060848603121561091d57600080fd5b600061092b86828701610865565b935050602061093c8682870161083b565b925050604061094d86828701610826565b9150509250925092565b61096081610aa1565b82525050565b61096f81610ab3565b82525050565b61098661098182610ab3565b610b3f565b82525050565b600061099782610a8b565b6109a18185610a96565b93506109b1818560208601610b0c565b80840191505092915050565b6109c681610afa565b82525050565b6109d581610af0565b82525050565b60006109e78284610975565b60208201915081905092915050565b6000610a02828461098c565b915081905092915050565b600061010082019050610a23600083018b6109cc565b610a30602083018a6109cc565b610a3d6040830189610966565b610a4a6060830188610966565b610a576080830187610957565b610a6460a0830186610957565b610a7160c08301856109cc565b610a7e60e08301846109bd565b9998505050505050505050565b600081519050919050565b600081905092915050565b6000610aac82610ad0565b9050919050565b6000819050919050565b6000819050610acb82610b78565b919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b6000610b0582610abd565b9050919050565b60005b83811015610b2a578082015181840152602081019050610b0f565b83811115610b39576000848401525b50505050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60048110610b8957610b88610b49565b5b50565b610b9581610aa1565b8114610ba057600080fd5b50565b610bac81610ab3565b8114610bb757600080fd5b50565b610bc381610af0565b8114610bce57600080fd5b5056fea2646970667358221220fc5af60c7e4971f7f4cefff20a2bdc296592966eba445d51d40e0ef6298601a064736f6c63430008010033" + # PASSWORD is the password used to unlock all accounts/wallets/addresses. PASSWORD="abc" @@ -89,6 +91,9 @@ cat > "${NODES_ROOT}/genesis.json" < "${NODES_ROOT}/harness-ctl/send.js" < "${NODES_ROOT}/harness-ctl/deploy.js" < "${NODES_ROOT}/harness-ctl/contractAddress.js" < "${NODES_ROOT}/contract_addr.txt" <