Skip to content

Commit

Permalink
client/asset/eth: fix client balance calcs
Browse files Browse the repository at this point in the history
Since the client doesn't maintain a pending state, we go back to
parsing redeem and refund data from transactions.

Remove the Pending field from Balance. It wasn't used.

Remove several unused methods, and unused Context args.
  • Loading branch information
buck54321 committed Dec 6, 2021
1 parent 20d8ca8 commit fb049aa
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 154 deletions.
31 changes: 31 additions & 0 deletions client/asset/eth/contractor.go
Expand Up @@ -39,6 +39,11 @@ type contractor interface {
estimateRedeemGas(ctx context.Context, secrets [][32]byte) (uint64, error)
estimateRefundGas(ctx context.Context, secretHash [32]byte) (uint64, error)
isRedeemable(secretHash, secret [32]byte) (bool, error)
// incomingValue checks if the transaction redeems or refunds to the
// contract and sums the incoming value. It is not an error if the
// transaction does not pay to the contract, and the value returned in that
// case will always be zero.
incomingValue(context.Context, *types.Transaction) (uint64, error)
}

type contractorConstructor func(net dex.Network, addr common.Address, ec *ethclient.Client) (contractor, error)
Expand Down Expand Up @@ -223,6 +228,32 @@ func (c *contractorV0) estimateInitGas(ctx context.Context, n int) (uint64, erro
})
}

func (c *contractorV0) incomingValue(ctx context.Context, tx *types.Transaction) (uint64, error) {
if *tx.To() != c.contractAddr {
return 0, nil
}
if redeems, err := dexeth.ParseRedeemData(tx.Data()); err == nil {
var redeemed uint64
for _, redeem := range redeems {
swap, err := c.swap(ctx, redeem.SecretHash)
if err != nil {
return 0, fmt.Errorf("redeem swap error: %w", err)
}
redeemed += swap.Value
}
return redeemed, nil
}
secretHash, err := dexeth.ParseRefundData(tx.Data())
if err != nil {
return 0, nil
}
swap, err := c.swap(ctx, secretHash)
if err != nil {
return 0, fmt.Errorf("refund swap error: %w", err)
}
return swap.Value, nil
}

var contractorConstructors = map[uint32]contractorConstructor{
0: newV0contractor,
}
10 changes: 5 additions & 5 deletions client/asset/eth/eth.go
Expand Up @@ -148,9 +148,9 @@ type ethFetcher interface {
redeem(txOpts *bind.TransactOpts, redemptions []*asset.Redemption, contractVer uint32) (*types.Transaction, error)
refund(txOpts *bind.TransactOpts, secretHash [32]byte, contractVer uint32) (*types.Transaction, error)
swap(ctx context.Context, secretHash [32]byte, contractVer uint32) (*dexeth.SwapState, error)
lock(ctx context.Context) error
lock() error
locked() bool
unlock(ctx context.Context, pw string) error
unlock(pw string) error
signData(addr common.Address, data []byte) ([]byte, error)
sendToAddr(ctx context.Context, addr common.Address, val uint64) (*types.Transaction, error)
transactionConfirmations(context.Context, common.Hash) (uint32, error)
Expand Down Expand Up @@ -734,12 +734,12 @@ func (eth *ExchangeWallet) Address() (string, error) {

// Unlock unlocks the exchange wallet.
func (eth *ExchangeWallet) Unlock(pw []byte) error {
return eth.node.unlock(eth.ctx, string(pw))
return eth.node.unlock(string(pw))
}

// Lock locks the exchange wallet.
func (eth *ExchangeWallet) Lock() error {
return eth.node.lock(eth.ctx)
return eth.node.lock()
}

// Locked will be true if the wallet is currently locked.
Expand Down Expand Up @@ -902,7 +902,7 @@ func (eth *ExchangeWallet) checkForNewBlocks() {
// Balance is the current balance, including information about the pending
// balance.
type Balance struct {
Current, Pending, PendingIn, PendingOut *big.Int
Current, PendingIn, PendingOut *big.Int
}

func versionedBytes(ver uint32, h []byte) []byte {
Expand Down
29 changes: 14 additions & 15 deletions client/asset/eth/eth_test.go
Expand Up @@ -64,10 +64,9 @@ type testNode struct {
nonce uint64
}

func newBalance(current, pending, in, out uint64) *Balance {
func newBalance(current, in, out uint64) *Balance {
return &Balance{
Current: dexeth.GweiToWei(current),
Pending: dexeth.GweiToWei(pending),
PendingIn: dexeth.GweiToWei(in),
PendingOut: dexeth.GweiToWei(out),
}
Expand Down Expand Up @@ -95,10 +94,10 @@ func (n *testNode) balance(ctx context.Context) (*Balance, error) {
func (n *testNode) syncStatus(ctx context.Context) (bool, float32, error) {
return false, 0, nil
}
func (n *testNode) unlock(ctx context.Context, pw string) error {
func (n *testNode) unlock(pw string) error {
return nil
}
func (n *testNode) lock(ctx context.Context) error {
func (n *testNode) lock() error {
return nil
}
func (n *testNode) locked() bool {
Expand Down Expand Up @@ -354,7 +353,7 @@ func TestBalance(t *testing.T) {
// overMaxWei := new(big.Int).Set(maxWei)
// overMaxWei.Add(overMaxWei, gweiFactorBig)

tinyBal := newBalance(0, 0, 0, 0)
tinyBal := newBalance(0, 0, 0)
tinyBal.Current = big.NewInt(dexeth.GweiFactor - 1)

ctx, cancel := context.WithCancel(context.Background())
Expand All @@ -377,7 +376,7 @@ func TestBalance(t *testing.T) {
wantLocked uint64
}{{
name: "ok zero",
bal: newBalance(0, 0, 0, 0),
bal: newBalance(0, 0, 0),
wantBal: 0,
wantImmature: 0,
}, {
Expand All @@ -386,21 +385,21 @@ func TestBalance(t *testing.T) {
wantBal: 0,
}, {
name: "ok one",
bal: newBalance(1, 0, 0, 0),
bal: newBalance(1, 0, 0),
wantBal: 1,
}, {
name: "ok pending out",
bal: newBalance(4e8, (4-1.4)*1e8, 0, 1.4e8),
bal: newBalance(4e8, 0, 1.4e8),
wantBal: 2.6e8,
wantLocked: 1.4e8,
}, {
name: "ok pending in",
bal: newBalance(1e8, 4e8, 3e8, 0),
bal: newBalance(1e8, 3e8, 0),
wantBal: 1e8,
wantImmature: 3e8,
}, {
name: "ok pending out and in",
bal: newBalance(4e8, 5e8, 2e8, 1e8),
bal: newBalance(4e8, 2e8, 1e8),
wantBal: 3e8,
wantLocked: 1e8,
wantImmature: 2e8,
Expand Down Expand Up @@ -475,7 +474,7 @@ func TestFundOrderReturnCoinsFundingCoins(t *testing.T) {
Address: common.HexToAddress(address),
}
node := newTestNode(&account)
node.bal = newBalance(walletBalanceGwei, 0, 0, 0)
node.bal = newBalance(walletBalanceGwei, 0, 0)
eth := &ExchangeWallet{
node: node,
addr: node.address(),
Expand Down Expand Up @@ -822,7 +821,7 @@ func TestPreSwap(t *testing.T) {
FeeSuggestion: test.feeSuggestion,
}
node := newTestNode(nil)
node.bal = newBalance(test.bal*1e9, 0, 0, 0)
node.bal = newBalance(test.bal*1e9, 0, 0)
node.balErr = test.balErr
eth := &ExchangeWallet{
node: node,
Expand Down Expand Up @@ -867,7 +866,7 @@ func TestPreSwap(t *testing.T) {

func TestSwap(t *testing.T) {
node := &testNode{
bal: newBalance(0, 0, 0, 0),
bal: newBalance(0, 0, 0),
}
address := "0xB6De8BB5ed28E6bE6d671975cad20C03931bE981"
receivingAddress := "0x2b84C791b79Ee37De042AD2ffF1A253c3ce9bc27"
Expand Down Expand Up @@ -1219,7 +1218,7 @@ func TestMaxOrder(t *testing.T) {
for _, test := range tests {
ctx, cancel := context.WithCancel(context.Background())
node := newTestNode(nil)
node.bal = newBalance(test.bal*1e9, 0, 0, 0)
node.bal = newBalance(test.bal*1e9, 0, 0)
node.balErr = test.balErr
eth := &ExchangeWallet{
node: node,
Expand Down Expand Up @@ -1428,7 +1427,7 @@ func TestSwapConfirmation(t *testing.T) {
hdr := &types.Header{}

node := &testNode{
bal: newBalance(0, 0, 0, 0),
bal: newBalance(0, 0, 0),
bestHdr: hdr,
swapMap: map[[32]byte]*dexeth.SwapState{
secretHash: state,
Expand Down
120 changes: 52 additions & 68 deletions client/asset/eth/nodeclient.go
Expand Up @@ -155,17 +155,6 @@ func (n *nodeClient) block(ctx context.Context, hash common.Hash) (*types.Block,
return n.leth.ApiBackend.BlockByHash(ctx, hash)
}

// accounts returns all accounts from the internal node.
func (n *nodeClient) accounts() []*accounts.Account {
var accts []*accounts.Account
for _, wallet := range n.node.AccountManager().Wallets() {
for _, acct := range wallet.Accounts() {
accts = append(accts, &acct)
}
}
return accts
}

func (n *nodeClient) stateAt(ctx context.Context, bn rpc.BlockNumber) (*state.StateDB, error) {
state, _, err := n.leth.ApiBackend.StateAndHeaderByNumberOrHash(ctx, rpc.BlockNumberOrHashWithNumber(bn))
if err != nil {
Expand All @@ -191,48 +180,74 @@ func (n *nodeClient) addressBalance(ctx context.Context, addr common.Address) (*

// balance gets the current and pending balances.
func (n *nodeClient) balance(ctx context.Context) (*Balance, error) {
pendingBal, err := n.balanceAt(ctx, n.creds.addr, rpc.PendingBlockNumber)
if err != nil {
return nil, fmt.Errorf("pending balance error: %w", err)
}

bal, err := n.balanceAt(ctx, n.creds.addr, rpc.LatestBlockNumber)
if err != nil {
return nil, fmt.Errorf("pending balance error: %w", err)
}

pendingTxs, err := n.pendingTransactions(ctx)
pendingTxs, err := n.pendingTransactions()
if err != nil {
return nil, fmt.Errorf("error getting pending txs: %w", err)
}

outgoing := new(big.Int)
for _, tx := range pendingTxs {
// TODO: Check that the from address matches addr. types.Transaction
// does not expose the from address directly.
outgoing.Add(outgoing, tx.Value())
incoming := new(big.Int)
zero := new(big.Int)

addFees := func(tx *types.Transaction) {
gas := new(big.Int).SetUint64(tx.Gas())
if gasPrice := tx.GasPrice(); gasPrice != nil && gasPrice.Cmp(zero) > 0 {
outgoing.Add(outgoing, new(big.Int).Mul(gas, gasPrice))
} else if gasFeeCap := tx.GasFeeCap(); gasFeeCap != nil {
outgoing.Add(outgoing, new(big.Int).Mul(gas, gasFeeCap))
} else {
n.log.Errorf("unable to calculate fees for tx %s", tx.Hash())
}
}

// pending = current + incoming - outgoing
// => incoming = pending + outgoing - current
incoming := new(big.Int).Add(pendingBal, outgoing)
incoming.Sub(incoming, bal)
ethSigner := types.LatestSigner(n.leth.ApiBackend.ChainConfig()) // "latest" good for pending

for _, tx := range pendingTxs {
from, _ := ethSigner.Sender(tx) // zero Address on error
if from != n.creds.addr {
continue
}
addFees(tx)
v := tx.Value()
if v.Cmp(zero) == 0 {
// If zero value, attempt to find redemptions or refunds that pay
// to us.
for ver, c := range n.contractors {
in, err := c.incomingValue(ctx, tx)
if err != nil {
n.log.Errorf("version %d contractor incomingValue error: %v", ver, err)
continue
}
if in > 0 {
incoming.Add(incoming, dexeth.GweiToWei(in))
}
}
} else {
// If non-zero outgoing value, this is a swap or a send of some
// type.
outgoing.Add(outgoing, v)
}
}

return &Balance{
Current: bal,
Pending: pendingBal,
PendingOut: outgoing,
PendingIn: incoming,
}, nil
}

// unlock the account indefinitely.
func (n *nodeClient) unlock(ctx context.Context, pw string) error {
func (n *nodeClient) unlock(pw string) error {
return n.creds.ks.TimedUnlock(*n.creds.acct, pw, 0)
}

// lock the account indefinitely.
func (n *nodeClient) lock(ctx context.Context) error {
func (n *nodeClient) lock() error {
return n.creds.ks.Lock(n.creds.addr)
}

Expand Down Expand Up @@ -264,16 +279,20 @@ func (n *nodeClient) transactionReceipt(ctx context.Context, txHash common.Hash)
if len(receipts) <= int(index) {
return nil, fmt.Errorf("no receipt at index %d in block %s for tx %s", index, blockHash, txHash)
}
return receipts[index], nil
receipt := receipts[index]
if receipt == nil {
return nil, fmt.Errorf("nil receipt at index %d in block %s for tx %s", index, blockHash, txHash)
}
return receipt, nil
}

// pendingTransactions returns pending transactions.
func (n *nodeClient) pendingTransactions(ctx context.Context) ([]*types.Transaction, error) {
func (n *nodeClient) pendingTransactions() ([]*types.Transaction, error) {
return n.leth.ApiBackend.GetPoolTransactions()
}

// addPeer adds a peer.
func (n *nodeClient) addPeer(ctx context.Context, peerURL string) error {
func (n *nodeClient) addPeer(peerURL string) error {
peer, err := enode.Parse(enode.ValidSchemes, peerURL)
if err != nil {
return err
Expand All @@ -282,17 +301,6 @@ func (n *nodeClient) addPeer(ctx context.Context, peerURL string) error {
return nil
}

// nodeInfo retrieves useful information about a node.
func (n *nodeClient) nodeInfo(ctx context.Context) *p2p.NodeInfo {
return n.p2pSrv.NodeInfo()
}

// listWallets list all of the wallet's wallets? and accounts along with details
// such as locked status.
func (n *nodeClient) listWallets(ctx context.Context) []accounts.Wallet {
return n.creds.ks.Wallets()
}

// sendTransaction sends a tx.
func (n *nodeClient) sendTransaction(ctx context.Context, txOpts *bind.TransactOpts,
to common.Address, data []byte) (*types.Transaction, error) {
Expand Down Expand Up @@ -328,11 +336,6 @@ func (n *nodeClient) syncProgress() ethereum.SyncProgress {
return n.leth.ApiBackend.SyncProgress()
}

// peers returns connected peers.
func (n *nodeClient) peers(ctx context.Context) []*p2p.Peer {
return n.p2pSrv.Peers()
}

// swap gets a swap keyed by secretHash in the contract.
func (n *nodeClient) swap(ctx context.Context, secretHash [32]byte, contractVer uint32) (swap *dexeth.SwapState, err error) {
return swap, n.withcontractor(contractVer, func(c contractor) error {
Expand All @@ -341,36 +344,17 @@ func (n *nodeClient) swap(ctx context.Context, secretHash [32]byte, contractVer
})
}

// wallet returns a wallet that owns acct from an ethereum wallet.
func (n *nodeClient) wallet(acct accounts.Account) (accounts.Wallet, error) {
wallet, err := n.node.AccountManager().Find(acct)
if err != nil {
return nil, fmt.Errorf("error finding wallet for account %s: %w", acct.Address, err)
}
return wallet, nil
}

// signData uses the private key of the address to sign a piece of data.
// The address must have been imported and unlocked to use this function.
func (n *nodeClient) signData(addr common.Address, data []byte) ([]byte, error) {
account := accounts.Account{Address: addr}
wallet, err := n.wallet(account)
if err != nil {
return nil, err
}

// The mime type argument to SignData is not used in the keystore wallet in geth.
// It treats any data like plain text.
return wallet.SignData(account, accounts.MimetypeTextPlain, data)
return n.creds.wallet.SignData(*n.creds.acct, accounts.MimetypeTextPlain, data)
}

func (n *nodeClient) addSignerToOpts(txOpts *bind.TransactOpts) error {
wallet, err := n.wallet(accounts.Account{Address: txOpts.From})
if err != nil {
return err
}
txOpts.Signer = func(addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
return wallet.SignTx(accounts.Account{Address: addr}, tx, n.chainID)
return n.creds.wallet.SignTx(accounts.Account{Address: addr}, tx, n.chainID)
}
return nil
}
Expand Down

0 comments on commit fb049aa

Please sign in to comment.