Skip to content

Commit

Permalink
client/asset/eth: SwapConfirmations
Browse files Browse the repository at this point in the history
Implement SwapConfirmations, with asset version as part of the contract data bytes.

Co-authored-by: Jonathan Chappelow <chappjc@protonmail.com>
  • Loading branch information
buck54321 and chappjc authored Nov 30, 2021
1 parent b66b579 commit 70df246
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 6 deletions.
45 changes: 42 additions & 3 deletions client/asset/eth/eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"bytes"
"context"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
Expand Down Expand Up @@ -571,6 +572,7 @@ type swapReceipt struct {
// lookup, but we cache these values to avoid this.
expiration time.Time
value uint64
ver uint32
}

// Expiration returns the time after which the contract can be
Expand All @@ -589,7 +591,7 @@ func (r *swapReceipt) Coin() asset.Coin {

// Contract returns the swap's secret hash.
func (r *swapReceipt) Contract() dex.Bytes {
return r.secretHash[:]
return versionedBytes(r.ver, r.secretHash[:])
}

// String returns a string representation of the swapReceipt.
Expand Down Expand Up @@ -652,6 +654,7 @@ func (eth *ExchangeWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin
value: swap.Value,
txHash: txHash,
secretHash: swap.SecretHash,
ver: swaps.AssetVersion,
})
}

Expand Down Expand Up @@ -751,8 +754,37 @@ func (*ExchangeWallet) PayFee(address string, regFee, feeRateSuggestion uint64)
return nil, asset.ErrNotImplemented
}

func (*ExchangeWallet) SwapConfirmations(ctx context.Context, id dex.Bytes, contract dex.Bytes, startTime time.Time) (confs uint32, spent bool, err error) {
return 0, false, asset.ErrNotImplemented
// SwapConfirmations gets the number of confirmations and the spend status
// for the specified swap.
func (eth *ExchangeWallet) SwapConfirmations(ctx context.Context, _ dex.Bytes, contract dex.Bytes, _ time.Time) (confs uint32, spent bool, err error) {
if len(contract) != 36 {
return 0, false, fmt.Errorf("wrong contract length. expected 36, got %d", len(contract))
}

// contract fully specifies this swap in terms of which contract version
// is used and the secret hash that keys the unique swap.
contractVer := binary.BigEndian.Uint32(contract[:4])
var secretHash [32]byte
copy(secretHash[:], contract[4:])

hdr, err := eth.node.bestHeader(ctx)
if err != nil {
return 0, false, fmt.Errorf("error fetching best header: %w", err)
}

swapData, err := eth.node.swap(ctx, secretHash, contractVer)
if err != nil {
return 0, false, fmt.Errorf("error finding swap state: %w", err)
}

if swapData.State == dexeth.SSNone {
return 0, false, asset.CoinNotFoundError
}

spent = swapData.State >= dexeth.SSRedeemed
confs = uint32(hdr.Number.Uint64() - swapData.BlockHeight + 1)

return
}

// Withdraw withdraws funds to the specified address. Value is gwei.
Expand Down Expand Up @@ -867,3 +899,10 @@ func (eth *ExchangeWallet) checkForNewBlocks() {
type Balance struct {
Current, Pending, PendingIn, PendingOut *big.Int
}

func versionedBytes(ver uint32, h []byte) []byte {
b := make([]byte, len(h)+4)
binary.BigEndian.PutUint32(b[:4], ver)
copy(b[4:], h)
return b
}
90 changes: 88 additions & 2 deletions client/asset/eth/eth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import (
"context"
"crypto/ecdsa"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"math/big"
"math/rand"
"strings"
Expand Down Expand Up @@ -55,6 +57,7 @@ type testNode struct {
balErr error
signDataErr error
privKeyForSigning *ecdsa.PrivateKey
swapVers map[uint32]struct{} // For SwapConfirmations -> swap. TODO for other contractor methods
swapMap map[[32]byte]*dexeth.SwapState
swapErr error
initErr error
Expand Down Expand Up @@ -131,7 +134,7 @@ func (n *testNode) initiate(ctx context.Context, contracts []*asset.Contract, ma
Nonce: n.nonce,
}), nil
}
func (n *testNode) redeem(opts *bind.TransactOpts, redeptions []*asset.Redemption, contractVer uint32) (*types.Transaction, error) {
func (n *testNode) redeem(opts *bind.TransactOpts, redemptions []*asset.Redemption, contractVer uint32) (*types.Transaction, error) {
return nil, nil
}
func (n *testNode) refund(opts *bind.TransactOpts, secretHash [32]byte, serverVer uint32) (*types.Transaction, error) {
Expand All @@ -145,6 +148,10 @@ func (n *testNode) swap(ctx context.Context, secretHash [32]byte, contractVer ui
if !ok {
return nil, errors.New("swap not in map")
}
_, found := n.swapVers[contractVer]
if !found {
return nil, errors.New("unknown contract version")
}
return swap, nil
}

Expand Down Expand Up @@ -936,7 +943,11 @@ func TestSwap(t *testing.T) {
t.Fatalf("%v: receipt coin value: %v != expected: %v",
testName, receipt.Coin().Value(), contract.Value)
}
if !bytes.Equal(receipt.Contract(), contract.SecretHash[:]) {
contractData := receipt.Contract()
if swaps.AssetVersion != binary.BigEndian.Uint32(contractData[:4]) {
t.Fatal("wrong contract version")
}
if !bytes.Equal(contractData[4:], contract.SecretHash[:]) {
t.Fatalf("%v, contract: %x != secret hash in input: %x",
testName, receipt.Contract(), contract.SecretHash)
}
Expand Down Expand Up @@ -1406,6 +1417,81 @@ func TestSignMessage(t *testing.T) {
}
}

func TestSwapConfirmation(t *testing.T) {
var secretHash [32]byte
copy(secretHash[:], encode.RandomBytes(32))
state := &dexeth.SwapState{}
hdr := &types.Header{}

node := &testNode{
bal: newBalance(0, 0, 0, 0),
bestHdr: hdr,
swapMap: map[[32]byte]*dexeth.SwapState{
secretHash: state,
},
swapVers: map[uint32]struct{}{
0: {},
},
}

eth := &ExchangeWallet{
node: node,
addr: testAddressA,
}

state.BlockHeight = 5
state.State = dexeth.SSInitiated
hdr.Number = big.NewInt(6)

ver := uint32(0)

checkResult := func(expErr bool, expConfs uint32, expSpent bool) {
confs, spent, err := eth.SwapConfirmations(nil, nil, versionedBytes(ver, secretHash[:]), time.Time{})
if err != nil {
if expErr {
return
}
t.Fatalf("SwapConfirmations error: %v", err)
}
if confs != expConfs {
t.Fatalf("expected %d confs, got %d", expConfs, confs)
}
if spent != expSpent {
t.Fatalf("wrong spent. wanted %t, got %t", expSpent, spent)
}
}

checkResult(false, 2, false)

// unknown asset version
ver = 12
checkResult(true, 0, false)
ver = 0

// swap error
node.swapErr = fmt.Errorf("test error")
checkResult(true, 0, false)
node.swapErr = nil

// header error
node.bestHdrErr = fmt.Errorf("test error")
checkResult(true, 0, false)
node.bestHdrErr = nil

// CoinNotFoundError
state.State = dexeth.SSNone
_, _, err := eth.SwapConfirmations(nil, nil, versionedBytes(0, secretHash[:]), time.Time{})
if !errors.Is(err, asset.CoinNotFoundError) {
t.Fatalf("expected CoinNotFoundError, got %v", err)
}

// 1 conf, spent
state.BlockHeight = 6
state.State = dexeth.SSRedeemed
checkResult(false, 1, true)

}

func ethToGwei(v uint64) uint64 {
return v * dexeth.GweiFactor
}
Expand Down
4 changes: 3 additions & 1 deletion client/asset/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,9 @@ type Swaps struct {
FeeRate uint64
// LockChange can be set to true if the change should be locked for
// subsequent matches.
LockChange bool
LockChange bool
// AssetVersion is the swap protocol version, which may indicate a specific
// contract or form of contract.
AssetVersion uint32
}

Expand Down

0 comments on commit 70df246

Please sign in to comment.