Skip to content

Commit

Permalink
dex/networks,server/eth: decode swap data message blob
Browse files Browse the repository at this point in the history
The msgjson.Init.Contract and server/asset.Contract.RedeemScript fields
contain an encoding of the ETH contract version concatenated with the
swap's secret hash that is the unique key for the swap.

This updates server/asset/eth's ValidateContract and Contract methods
to decode and check this data.

This also adds the dexeth.EncodeSwapData and DecodeSwapData functions.
  • Loading branch information
chappjc committed Dec 7, 2021
1 parent 607cdde commit 360e6d7
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 89 deletions.
23 changes: 9 additions & 14 deletions client/asset/eth/eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ func (d *Driver) Create(params *asset.CreateWalletParams) error {
return CreateWallet(params)
}

// Balance is the current balance, including information about the pending
// balance.
type Balance struct {
Current, PendingIn, PendingOut *big.Int
}

// 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 {
Expand Down Expand Up @@ -587,7 +593,9 @@ func (r *swapReceipt) Coin() asset.Coin {

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

// String returns a string representation of the swapReceipt.
Expand Down Expand Up @@ -893,16 +901,3 @@ func (eth *ExchangeWallet) checkForNewBlocks() {
prevTip.Hash(), newTip.NumberU64(), newTip.Hash())
go eth.tipChange(nil)
}

// Balance is the current balance, including information about the pending
// balance.
type Balance struct {
Current, 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
}
15 changes: 9 additions & 6 deletions client/asset/eth/eth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"context"
"crypto/ecdsa"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
Expand Down Expand Up @@ -947,12 +946,16 @@ func TestSwap(t *testing.T) {
testName, receipt.Coin().Value(), contract.Value)
}
contractData := receipt.Contract()
if swaps.AssetVersion != binary.BigEndian.Uint32(contractData[:4]) {
ver, secretHash, err := dexeth.DecodeSwapData(contractData)
if err != nil {
t.Fatalf("failed to decode contract data: %v", err)
}
if swaps.AssetVersion != ver {
t.Fatal("wrong contract version")
}
if !bytes.Equal(contractData[4:], contract.SecretHash[:]) {
if !bytes.Equal(contractData[4:], secretHash[:]) {
t.Fatalf("%v, contract: %x != secret hash in input: %x",
testName, receipt.Contract(), contract.SecretHash)
testName, receipt.Contract(), secretHash)
}

totalCoinValue += receipt.Coin().Value()
Expand Down Expand Up @@ -1449,7 +1452,7 @@ func TestSwapConfirmation(t *testing.T) {
ver := uint32(0)

checkResult := func(expErr bool, expConfs uint32, expSpent bool) {
confs, spent, err := eth.SwapConfirmations(nil, nil, versionedBytes(ver, secretHash[:]), time.Time{})
confs, spent, err := eth.SwapConfirmations(nil, nil, dexeth.EncodeSwapData(ver, secretHash), time.Time{})
if err != nil {
if expErr {
return
Expand Down Expand Up @@ -1483,7 +1486,7 @@ func TestSwapConfirmation(t *testing.T) {

// CoinNotFoundError
state.State = dexeth.SSNone
_, _, err := eth.SwapConfirmations(nil, nil, versionedBytes(0, secretHash[:]), time.Time{})
_, _, err := eth.SwapConfirmations(nil, nil, dexeth.EncodeSwapData(0, secretHash), time.Time{})
if !errors.Is(err, asset.CoinNotFoundError) {
t.Fatalf("expected CoinNotFoundError, got %v", err)
}
Expand Down
24 changes: 12 additions & 12 deletions client/webserver/live_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//go:build live
// +build live
//go:build live && lgpl
// +build live,lgpl

// Run a test server with
// go test -v -tags live -run Server -timeout 60m
Expand Down Expand Up @@ -305,37 +305,37 @@ var tExchanges = map[string]*core.Exchange{
},
Connected: true,
RegFees: map[string]*core.FeeAsset{
"dcr": &core.FeeAsset{
"dcr": {
ID: 42,
Confs: 1,
Amt: 1e8,
},
"btc": &core.FeeAsset{
"btc": {
ID: 0,
Confs: 2,
Amt: 1e5,
},
"eth": &core.FeeAsset{
"eth": {
ID: 60,
Confs: 5,
Amt: 1e7,
},
"bch": &core.FeeAsset{
"bch": {
ID: 145,
Confs: 2,
Amt: 1e10,
},
"ltc": &core.FeeAsset{
"ltc": {
ID: 2,
Confs: 5,
Amt: 1e10,
},
"doge": &core.FeeAsset{
"doge": {
ID: 3,
Confs: 10,
Amt: 1e12,
},
"kmd": &core.FeeAsset{ // Not-supported
"kmd": { // Not-supported
ID: 141,
Confs: 10,
Amt: 1e12,
Expand All @@ -354,12 +354,12 @@ var tExchanges = map[string]*core.Exchange{
},
Connected: true,
RegFees: map[string]*core.FeeAsset{
"dcr": &core.FeeAsset{
"dcr": {
ID: 42,
Confs: 1,
Amt: 1e8,
},
"btc": &core.FeeAsset{
"btc": {
ID: 0,
Confs: 2,
Amt: 1e6,
Expand Down Expand Up @@ -1412,7 +1412,7 @@ out:
c.noteFeed <- &core.SpotPriceNote{
Host: dexAddr,
Notification: db.NewNotification(core.NoteTypeSpots, core.TopicSpotsUpdate, "", "", db.Data),
Spots: map[string]*msgjson.Spot{mktID: &msgjson.Spot{
Spots: map[string]*msgjson.Spot{mktID: {
Stamp: encode.UnixMilliU(time.Now()),
BaseID: baseID,
QuoteID: quoteID,
Expand Down
25 changes: 25 additions & 0 deletions dex/networks/eth/params.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
// This code is available on the terms of the project LICENSE.md file,
// also available online at https://blueoakcouncil.org/license/1.0.0.

//go:build lgpl
// +build lgpl

package eth

import (
"encoding/binary"
"errors"
"math"
"math/big"
"time"
Expand Down Expand Up @@ -65,6 +70,26 @@ var v0Gases = &Gases{
RefundGas: 43000,
}

// EncodeSwapData packs the contract version and the secret hash into a byte
// slice for communicating a swap's identity.
func EncodeSwapData(contractVersion uint32, swapKey [SecretHashSize]byte) []byte {
b := make([]byte, SecretHashSize+4)
binary.BigEndian.PutUint32(b[:4], contractVersion)
copy(b[4:], swapKey[:])
return b
}

// DecodeSwapData unpacks the contract version and secret hash.
func DecodeSwapData(data []byte) (contractVersion uint32, swapKey [SecretHashSize]byte, err error) {
if len(data) != SecretHashSize+4 {
err = errors.New("invalid swap data")
return
}
contractVersion = binary.BigEndian.Uint32(data[:4])
copy(swapKey[:], data[4:])
return
}

// InitGas calculates the gas required for a batch of n inits.
func InitGas(n int, contractVer uint32) uint64 {
if n == 0 {
Expand Down
2 changes: 1 addition & 1 deletion run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ done

cd "$dir"
dumptags=(-c -o /dev/null -tags)
go test "${dumptags[@]}" live ./client/webserver
go test "${dumptags[@]}" live,lgpl ./client/webserver
go test "${dumptags[@]}" harness ./client/asset/dcr
go test "${dumptags[@]}" harness ./client/asset/btc/livetest
go test "${dumptags[@]}" harness ./client/asset/ltc
Expand Down
29 changes: 19 additions & 10 deletions server/asset/eth/coiner.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ type swapCoin struct {
// an error is returned then if something is different than expected. As such,
// the swapCoin expects Confirmations to be called with confirmations
// available at least once before the swap be trusted for swap initializations.
func (backend *Backend) newSwapCoin(coinID []byte, contract []byte, sct swapCoinType) (*swapCoin, error) {
func (backend *Backend) newSwapCoin(coinID []byte, contractData []byte, sct swapCoinType) (*swapCoin, error) {
switch sct {
case sctInit, sctRedeem:
default:
Expand Down Expand Up @@ -95,11 +95,13 @@ func (backend *Backend) newSwapCoin(coinID []byte, contract []byte, sct swapCoin
locktime int64
)

if len(contract) != 32 {
return nil, fmt.Errorf("expected contract to have length 32 but got %v", len(contract))
contractVer, secretHash, err := dexeth.DecodeSwapData(contractData)
if err != nil {
return nil, err
}
if contractVer != 0 {
return nil, errors.New("TODO contract version with ParseInitiateData/ParseRedeemData")
}

copy(secretHash[:], contract)

switch sct {
case sctInit:
Expand All @@ -117,7 +119,7 @@ func (backend *Backend) newSwapCoin(coinID []byte, contract []byte, sct swapCoin
return nil, fmt.Errorf("tx %v does not contain initiation with secret hash %x", txHash, secretHash)
}
counterParty = &initiation.Participant
secretHash = initiation.SecretHash
secretHash = initiation.SecretHash // no-op
locktime = initiation.RefundTimestamp.Int64()
case sctRedeem:
redemptions, err := dexeth.ParseRedeemData(txdata)
Expand All @@ -134,7 +136,7 @@ func (backend *Backend) newSwapCoin(coinID []byte, contract []byte, sct swapCoin
return nil, fmt.Errorf("tx %v does not contain redemption with secret hash %x", txHash, secretHash)
}
secret = redemption.Secret
secretHash = redemption.SecretHash
secretHash = redemption.SecretHash // no-op
}

contractAddr := tx.To()
Expand Down Expand Up @@ -179,13 +181,20 @@ func (backend *Backend) newSwapCoin(coinID []byte, contract []byte, sct swapCoin

// validateRedeem ensures that a redeem swap coin redeems a certain contract by
// comparing the contract and secret hash.
func (c *swapCoin) validateRedeem(contractID []byte) error {
func (c *swapCoin) validateRedeem(contractData []byte) error {
if c.sct != sctRedeem {
return errors.New("can only validate redeem")
}
if !bytes.Equal(c.secretHash[:], contractID) {
ver, secretHash, err := dexeth.DecodeSwapData(contractData)
if err != nil {
return err
}
if ver != version {
return fmt.Errorf("unsupported contract version %d", ver)
}
if !bytes.Equal(c.secretHash[:], secretHash[:]) {
return fmt.Errorf("redeem secret hash %x does not match contract %x",
c.secretHash, contractID)
c.secretHash, secretHash)
}
return nil
}
Expand Down

0 comments on commit 360e6d7

Please sign in to comment.