decred.org/dcrdex@v1.0.5/server/asset/eth/eth.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package eth
     5  
     6  import (
     7  	"bufio"
     8  	"bytes"
     9  	"context"
    10  	"crypto/sha256"
    11  	"errors"
    12  	"fmt"
    13  	"math/big"
    14  	"os"
    15  	"strconv"
    16  	"strings"
    17  	"sync"
    18  	"time"
    19  
    20  	"decred.org/dcrdex/dex"
    21  	"decred.org/dcrdex/dex/config"
    22  	dexeth "decred.org/dcrdex/dex/networks/eth"
    23  	"decred.org/dcrdex/server/asset"
    24  	"github.com/decred/dcrd/dcrec/secp256k1/v4"
    25  	"github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
    26  	"github.com/ethereum/go-ethereum/common"
    27  	"github.com/ethereum/go-ethereum/core/types"
    28  	"github.com/ethereum/go-ethereum/crypto"
    29  	"github.com/ethereum/go-ethereum/params"
    30  )
    31  
    32  type VersionedToken struct {
    33  	*dexeth.Token
    34  	Ver uint32
    35  }
    36  
    37  var registeredTokens = make(map[uint32]*VersionedToken)
    38  
    39  func registerToken(assetID uint32, ver uint32) {
    40  	token, exists := dexeth.Tokens[assetID]
    41  	if !exists {
    42  		panic(fmt.Sprintf("no token constructor for asset ID %d", assetID))
    43  	}
    44  	asset.RegisterToken(assetID, &TokenDriver{
    45  		DriverBase: DriverBase{
    46  			Ver: ver,
    47  			UI:  token.UnitInfo,
    48  			Nam: token.Name,
    49  		},
    50  		Token: token.Token,
    51  	})
    52  	registeredTokens[assetID] = &VersionedToken{
    53  		Token: token,
    54  		Ver:   ver,
    55  	}
    56  }
    57  
    58  func init() {
    59  	asset.Register(BipID, &Driver{
    60  		DriverBase: DriverBase{
    61  			Ver: version,
    62  			UI:  dexeth.UnitInfo,
    63  			Nam: "Ethereum",
    64  		},
    65  	})
    66  
    67  	registerToken(usdcID, 0)
    68  	registerToken(usdtID, 0)
    69  	registerToken(maticID, 0)
    70  }
    71  
    72  const (
    73  	BipID              = 60
    74  	ethContractVersion = 0
    75  	version            = 0
    76  )
    77  
    78  var (
    79  	_ asset.Driver      = (*Driver)(nil)
    80  	_ asset.TokenBacker = (*ETHBackend)(nil)
    81  
    82  	backendInfo = &asset.BackendInfo{
    83  		SupportsDynamicTxFee: true,
    84  	}
    85  
    86  	usdcID, _  = dex.BipSymbolID("usdc.eth")
    87  	usdtID, _  = dex.BipSymbolID("usdt.eth")
    88  	maticID, _ = dex.BipSymbolID("matic.eth")
    89  )
    90  
    91  func networkToken(vToken *VersionedToken, net dex.Network) (netToken *dexeth.NetToken, contract *dexeth.SwapContract, err error) {
    92  	netToken, found := vToken.NetTokens[net]
    93  	if !found {
    94  		return nil, nil, fmt.Errorf("no addresses for %s on %s", vToken.Name, net)
    95  	}
    96  	contract, found = netToken.SwapContracts[vToken.Ver]
    97  	if !found || contract.Address == (common.Address{}) {
    98  		return nil, nil, fmt.Errorf("no version %d address for %s on %s", vToken.Ver, vToken.Name, net)
    99  	}
   100  	return
   101  }
   102  
   103  type DriverBase struct {
   104  	UI  dex.UnitInfo
   105  	Ver uint32
   106  	Nam string
   107  }
   108  
   109  // Version returns the Backend implementation's version number.
   110  func (d *DriverBase) Version() uint32 {
   111  	return d.Ver
   112  }
   113  
   114  // DecodeCoinID creates a human-readable representation of a coin ID for
   115  // Ethereum. This must be a transaction hash.
   116  func (d *DriverBase) DecodeCoinID(coinID []byte) (string, error) {
   117  	txHash, err := dexeth.DecodeCoinID(coinID)
   118  	if err != nil {
   119  		return "", err
   120  	}
   121  	return txHash.String(), nil
   122  }
   123  
   124  // UnitInfo returns the dex.UnitInfo for the asset.
   125  func (d *DriverBase) UnitInfo() dex.UnitInfo {
   126  	return d.UI
   127  }
   128  
   129  // Name is the asset's name.
   130  func (d *DriverBase) Name() string {
   131  	return d.Nam
   132  }
   133  
   134  // Driver implements asset.Driver.
   135  type Driver struct {
   136  	DriverBase
   137  }
   138  
   139  // Setup creates the ETH backend. Start the backend with its Run method.
   140  func (d *Driver) Setup(cfg *asset.BackendConfig) (asset.Backend, error) {
   141  	var chainID uint64
   142  	switch cfg.Net {
   143  	case dex.Mainnet:
   144  		chainID = params.MainnetChainConfig.ChainID.Uint64()
   145  	case dex.Testnet:
   146  		chainID = params.SepoliaChainConfig.ChainID.Uint64()
   147  	default:
   148  		chainID = 42
   149  	}
   150  
   151  	return NewEVMBackend(cfg, chainID, dexeth.ContractAddresses, registeredTokens)
   152  }
   153  
   154  type TokenDriver struct {
   155  	DriverBase
   156  	Token *dex.Token
   157  }
   158  
   159  // TokenInfo returns details for a token asset.
   160  func (d *TokenDriver) TokenInfo() *dex.Token {
   161  	return d.Token
   162  }
   163  
   164  // ethFetcher represents a blockchain information fetcher. In practice, it is
   165  // satisfied by rpcclient. For testing, it can be satisfied by a stub.
   166  //
   167  // TODO: At some point multiple contracts will need to be used, at least for
   168  // transitory periods when updating the contract, and possibly a random
   169  // contract setup, and so contract addresses may need to be an argument in some
   170  // of these methods.
   171  type ethFetcher interface {
   172  	bestHeader(ctx context.Context) (*types.Header, error)
   173  	blockNumber(ctx context.Context) (uint64, error)
   174  	headerByHeight(ctx context.Context, height uint64) (*types.Header, error)
   175  	connect(ctx context.Context) error
   176  	suggestGasTipCap(ctx context.Context) (*big.Int, error)
   177  	transaction(ctx context.Context, hash common.Hash) (tx *types.Transaction, isMempool bool, err error)
   178  	// token- and asset-specific methods
   179  	loadToken(ctx context.Context, assetID uint32, vToken *VersionedToken) error
   180  	swap(ctx context.Context, assetID uint32, secretHash [32]byte) (*dexeth.SwapState, error)
   181  	accountBalance(ctx context.Context, assetID uint32, addr common.Address) (*big.Int, error)
   182  }
   183  
   184  type baseBackend struct {
   185  	// A connection-scoped Context is used to cancel active RPCs on
   186  	// connection shutdown.
   187  	ctx  context.Context
   188  	net  dex.Network
   189  	node ethFetcher
   190  
   191  	baseChainID     uint32
   192  	baseChainName   string
   193  	versionedTokens map[uint32]*VersionedToken
   194  
   195  	// bestHeight is the last best known chain tip height. bestHeight is set
   196  	// in Connect before the poll loop is started, and only updated in the poll
   197  	// loop thereafter. Do not use bestHeight outside of the poll loop unless
   198  	// you change it to an atomic.
   199  	bestHeight uint64
   200  
   201  	// A logger will be provided by the DEX. All logging should use the provided
   202  	// logger.
   203  	baseLogger dex.Logger
   204  
   205  	tokens map[uint32]*TokenBackend
   206  }
   207  
   208  // AssetBackend is an asset backend for Ethereum. It has methods for fetching output
   209  // information and subscribing to block updates.
   210  // AssetBackend implements asset.Backend, so provides exported methods for
   211  // DEX-related blockchain info.
   212  type AssetBackend struct {
   213  	*baseBackend
   214  	assetID uint32
   215  	log     dex.Logger
   216  	atomize func(*big.Int) uint64 // atomize takes floor. use for values, not fee rates
   217  
   218  	// The backend provides block notification channels through the BlockChannel
   219  	// method.
   220  	blockChansMtx sync.RWMutex
   221  	blockChans    map[chan *asset.BlockUpdate]struct{}
   222  
   223  	// initTxSize is the gas used for an initiation transaction with one swap.
   224  	initTxSize uint64
   225  	redeemSize uint64
   226  
   227  	contractAddr common.Address
   228  }
   229  
   230  // ETHBackend implements some Ethereum-specific methods.
   231  type ETHBackend struct {
   232  	*AssetBackend
   233  }
   234  
   235  // TokenBackend implements some token-specific methods.
   236  type TokenBackend struct {
   237  	*AssetBackend
   238  	*VersionedToken
   239  }
   240  
   241  // Check that Backend satisfies the Backend interface.
   242  var _ asset.Backend = (*TokenBackend)(nil)
   243  var _ asset.Backend = (*ETHBackend)(nil)
   244  
   245  // Check that Backend satisfies the AccountBalancer interface.
   246  var _ asset.AccountBalancer = (*TokenBackend)(nil)
   247  var _ asset.AccountBalancer = (*ETHBackend)(nil)
   248  
   249  // unconnectedETH returns a Backend without a node. The node should be set
   250  // before use.
   251  func unconnectedETH(bipID uint32, contractAddr common.Address, vTokens map[uint32]*VersionedToken, logger dex.Logger, net dex.Network) (*ETHBackend, error) {
   252  	// TODO: At some point multiple contracts will need to be used, at
   253  	// least for transitory periods when updating the contract, and
   254  	// possibly a random contract setup, and so this section will need to
   255  	// change to support multiple contracts.
   256  	return &ETHBackend{&AssetBackend{
   257  		baseBackend: &baseBackend{
   258  			net:             net,
   259  			baseLogger:      logger,
   260  			tokens:          make(map[uint32]*TokenBackend),
   261  			baseChainID:     bipID,
   262  			baseChainName:   strings.ToUpper(dex.BipIDSymbol(bipID)),
   263  			versionedTokens: vTokens,
   264  		},
   265  		log:          logger,
   266  		contractAddr: contractAddr,
   267  		blockChans:   make(map[chan *asset.BlockUpdate]struct{}),
   268  		initTxSize:   dexeth.InitGas(1, ethContractVersion),
   269  		redeemSize:   dexeth.RedeemGas(1, ethContractVersion),
   270  		assetID:      bipID,
   271  		atomize:      dexeth.WeiToGwei,
   272  	}}, nil
   273  }
   274  
   275  func parseEndpoints(cfg *asset.BackendConfig) ([]endpoint, error) {
   276  	var endpoints []endpoint
   277  	if cfg.RelayAddr != "" {
   278  		endpoints = append(endpoints, endpoint{
   279  			url:      "http://" + cfg.RelayAddr,
   280  			priority: 10,
   281  		})
   282  	}
   283  	file, err := os.Open(cfg.ConfigPath)
   284  	if err != nil {
   285  		if os.IsNotExist(err) && len(endpoints) > 0 {
   286  			return endpoints, nil
   287  		}
   288  		return nil, err
   289  	}
   290  	defer file.Close()
   291  
   292  	assetName := strings.ToUpper(dex.BipIDSymbol(cfg.AssetID))
   293  
   294  	endpointsMap := make(map[string]bool) // to avoid duplicates
   295  	scanner := bufio.NewScanner(file)
   296  	for scanner.Scan() {
   297  		line := strings.TrimSpace(scanner.Text())
   298  		if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") {
   299  			continue
   300  		}
   301  		ethCfgInstructions := "invalid %s config line: \"%s\". " +
   302  			"Each line must contain URL and optionally a priority (between 0-65535) " +
   303  			"separated by a comma. Example: \"https://www.infura.io/,2\""
   304  		parts := strings.Split(line, ",")
   305  		if len(parts) < 1 || len(parts) > 2 {
   306  			return nil, fmt.Errorf(ethCfgInstructions, assetName, line)
   307  		}
   308  
   309  		url := strings.TrimSpace(parts[0])
   310  		var priority uint16
   311  		if len(parts) == 2 {
   312  			priority64, err := strconv.ParseUint(strings.TrimSpace(parts[1]), 10, 16)
   313  			if err != nil {
   314  				return nil, fmt.Errorf(ethCfgInstructions, line)
   315  			}
   316  			priority = uint16(priority64)
   317  		}
   318  		if endpointsMap[url] {
   319  			continue
   320  		}
   321  		endpointsMap[line] = true
   322  		endpoints = append(endpoints, endpoint{
   323  			url:      url,
   324  			priority: priority,
   325  		})
   326  	}
   327  	if err := scanner.Err(); err != nil {
   328  		return nil, fmt.Errorf("error reading %s config file at %q. %v", assetName, cfg.ConfigPath, err)
   329  	}
   330  	if len(endpoints) == 0 {
   331  		return nil, fmt.Errorf("no endpoint found in the %s config file at %q", assetName, cfg.ConfigPath)
   332  	}
   333  
   334  	return endpoints, nil
   335  }
   336  
   337  // NewEVMBackend is the exported constructor by which the DEX will import the
   338  // Backend.
   339  func NewEVMBackend(
   340  	cfg *asset.BackendConfig,
   341  	chainID uint64,
   342  	contractAddrs map[uint32]map[dex.Network]common.Address,
   343  	vTokens map[uint32]*VersionedToken,
   344  ) (*ETHBackend, error) {
   345  
   346  	endpoints, err := parseEndpoints(cfg)
   347  	if err != nil {
   348  		return nil, err
   349  	}
   350  
   351  	baseChainID, net, log := cfg.AssetID, cfg.Net, cfg.Logger
   352  	assetName := strings.ToUpper(dex.BipIDSymbol(baseChainID))
   353  
   354  	netAddrs, found := contractAddrs[ethContractVersion]
   355  	if !found {
   356  		return nil, fmt.Errorf("no contract address for %s version %d", assetName, ethContractVersion)
   357  	}
   358  	contractAddr, found := netAddrs[net]
   359  	if !found {
   360  		return nil, fmt.Errorf("no contract address for %s version %d on %s", assetName, ethContractVersion, net)
   361  	}
   362  
   363  	eth, err := unconnectedETH(baseChainID, contractAddr, vTokens, log, net)
   364  	if err != nil {
   365  		return nil, err
   366  	}
   367  
   368  	eth.node = newRPCClient(baseChainID, chainID, net, endpoints, contractAddr, log.SubLogger("RPC"))
   369  	return eth, nil
   370  }
   371  
   372  // Connect connects to the node RPC server and initializes some variables.
   373  func (eth *ETHBackend) Connect(ctx context.Context) (*sync.WaitGroup, error) {
   374  	eth.baseBackend.ctx = ctx
   375  
   376  	// Create a separate context for the node so that it will only be cancelled
   377  	// after the ETHBackend's run method has returned.
   378  	nodeContext, cancelNodeContext := context.WithCancel(context.Background())
   379  	if err := eth.node.connect(nodeContext); err != nil {
   380  		cancelNodeContext()
   381  		return nil, err
   382  	}
   383  
   384  	// Prime the best block hash and height.
   385  	bn, err := eth.node.blockNumber(ctx)
   386  	if err != nil {
   387  		cancelNodeContext()
   388  		return nil, fmt.Errorf("error getting best block header: %w", err)
   389  	}
   390  	eth.baseBackend.bestHeight = bn
   391  
   392  	var wg sync.WaitGroup
   393  	wg.Add(1)
   394  	go func() {
   395  		eth.run(ctx)
   396  		cancelNodeContext()
   397  		wg.Done()
   398  	}()
   399  	return &wg, nil
   400  }
   401  
   402  // Connect for TokenBackend just waits for context cancellation and closes the
   403  // WaitGroup.
   404  func (eth *TokenBackend) Connect(ctx context.Context) (*sync.WaitGroup, error) {
   405  	if eth.baseBackend.ctx == nil || eth.baseBackend.ctx.Err() != nil {
   406  		return nil, fmt.Errorf("parent asset not connected")
   407  	}
   408  	var wg sync.WaitGroup
   409  	wg.Add(1)
   410  	go func() {
   411  		defer wg.Done()
   412  		select {
   413  		case <-ctx.Done():
   414  		case <-eth.baseBackend.ctx.Done():
   415  		}
   416  	}()
   417  	return &wg, nil
   418  }
   419  
   420  // TokenBackend creates an *AssetBackend for a token. Part of the
   421  // asset.TokenBacker interface. Do not call TokenBackend concurrently for the
   422  // same asset.
   423  func (eth *ETHBackend) TokenBackend(assetID uint32, configPath string) (asset.Backend, error) {
   424  	if _, found := eth.baseBackend.tokens[assetID]; found {
   425  		return nil, fmt.Errorf("asset %d backend already loaded", assetID)
   426  	}
   427  
   428  	vToken, found := eth.versionedTokens[assetID]
   429  	if !found {
   430  		return nil, fmt.Errorf("no token for asset ID %d", assetID)
   431  	}
   432  
   433  	_, swapContract, err := networkToken(vToken, eth.net)
   434  	if err != nil {
   435  		return nil, err
   436  	}
   437  
   438  	gases := new(configuredTokenGases)
   439  	if configPath != "" {
   440  		if err := config.ParseInto(configPath, gases); err != nil {
   441  			return nil, fmt.Errorf("error parsing fee overrides for token %d: %v", assetID, err)
   442  		}
   443  	}
   444  
   445  	if gases.Swap == 0 {
   446  		gases.Swap = swapContract.Gas.Swap
   447  	}
   448  	if gases.Redeem == 0 {
   449  		gases.Redeem = swapContract.Gas.Redeem
   450  	}
   451  
   452  	if err := eth.node.loadToken(eth.ctx, assetID, vToken); err != nil {
   453  		return nil, fmt.Errorf("error loading token for asset ID %d: %w", assetID, err)
   454  	}
   455  	be := &TokenBackend{
   456  		AssetBackend: &AssetBackend{
   457  			baseBackend:  eth.baseBackend,
   458  			log:          eth.baseLogger.SubLogger(strings.ToUpper(dex.BipIDSymbol(assetID))),
   459  			assetID:      assetID,
   460  			blockChans:   make(map[chan *asset.BlockUpdate]struct{}),
   461  			initTxSize:   gases.Swap,
   462  			redeemSize:   gases.Redeem,
   463  			contractAddr: swapContract.Address,
   464  			atomize:      vToken.EVMToAtomic,
   465  		},
   466  		VersionedToken: vToken,
   467  	}
   468  	eth.baseBackend.tokens[assetID] = be
   469  	return be, nil
   470  }
   471  
   472  // TxData fetches the raw transaction data.
   473  func (eth *baseBackend) TxData(coinID []byte) ([]byte, error) {
   474  	txHash, err := dexeth.DecodeCoinID(coinID)
   475  	if err != nil {
   476  		return nil, fmt.Errorf("coin ID decoding error: %v", err)
   477  	}
   478  
   479  	tx, _, err := eth.node.transaction(eth.ctx, txHash)
   480  	if err != nil {
   481  		return nil, fmt.Errorf("error retrieving transaction: %w", err)
   482  	}
   483  	if tx == nil { // Possible?
   484  		return nil, fmt.Errorf("no transaction %s", txHash)
   485  	}
   486  
   487  	return tx.MarshalBinary()
   488  }
   489  
   490  // InitTxSize is an upper limit on the gas used for an initiation.
   491  func (be *AssetBackend) InitTxSize() uint64 {
   492  	return be.initTxSize
   493  }
   494  
   495  // RedeemSize is the same as (dex.Asset).RedeemSize for the asset.
   496  func (be *AssetBackend) RedeemSize() uint64 {
   497  	return be.redeemSize
   498  }
   499  
   500  // FeeRate returns the current optimal fee rate in gwei / gas.
   501  func (eth *baseBackend) FeeRate(ctx context.Context) (uint64, error) {
   502  	hdr, err := eth.node.bestHeader(ctx)
   503  	if err != nil {
   504  		return 0, fmt.Errorf("error getting best header: %w", err)
   505  	}
   506  
   507  	if hdr.BaseFee == nil {
   508  		return 0, fmt.Errorf("%v block header does not contain base fee", eth.baseChainName)
   509  	}
   510  
   511  	suggestedGasTipCap, err := eth.node.suggestGasTipCap(ctx)
   512  	if err != nil {
   513  		return 0, fmt.Errorf("error getting suggested gas tip cap: %w", err)
   514  	}
   515  
   516  	feeRate := new(big.Int).Add(
   517  		suggestedGasTipCap,
   518  		new(big.Int).Mul(hdr.BaseFee, big.NewInt(2)))
   519  
   520  	feeRateGwei, err := dexeth.WeiToGweiSafe(feeRate)
   521  	if err != nil {
   522  		return 0, fmt.Errorf("failed to convert wei to gwei: %w", err)
   523  	}
   524  
   525  	return feeRateGwei, nil
   526  }
   527  
   528  // Info provides some general information about the backend.
   529  func (*baseBackend) Info() *asset.BackendInfo {
   530  	return backendInfo
   531  }
   532  
   533  // ValidateFeeRate checks that the transaction fees used to initiate the
   534  // contract are sufficient. For most assets only the contract.FeeRate() cannot
   535  // be less than reqFeeRate, but for Eth, the gasTipCap must also be checked.
   536  func (eth *baseBackend) ValidateFeeRate(coin asset.Coin, reqFeeRate uint64) bool {
   537  	sc, ok := coin.(*swapCoin)
   538  	if !ok {
   539  		eth.baseLogger.Error("%v contract coin type must be a swapCoin but got %T", eth.baseChainName, sc)
   540  		return false
   541  	}
   542  
   543  	// Legacy transactions are also supported. In a legacy transaction, the
   544  	// gas tip cap will be equal to the gas price.
   545  	if dexeth.WeiToGwei(sc.gasTipCap) < dexeth.MinGasTipCap {
   546  		sc.backend.log.Errorf("Transaction %s tip cap %d < %d", dexeth.WeiToGwei(sc.gasTipCap), dexeth.MinGasTipCap)
   547  		return false
   548  	}
   549  
   550  	if sc.gasFeeCap.Cmp(dexeth.GweiToWei(reqFeeRate)) < 0 {
   551  		sc.backend.log.Errorf("Transaction %s gas fee cap too low. %s wei / gas < %s gwei / gas", sc.gasFeeCap, reqFeeRate)
   552  		return false
   553  	}
   554  
   555  	return true
   556  }
   557  
   558  // BlockChannel creates and returns a new channel on which to receive block
   559  // updates. If the returned channel is ever blocking, there will be no error
   560  // logged from the eth package. Part of the asset.Backend interface.
   561  func (be *AssetBackend) BlockChannel(size int) <-chan *asset.BlockUpdate {
   562  	c := make(chan *asset.BlockUpdate, size)
   563  	be.blockChansMtx.Lock()
   564  	defer be.blockChansMtx.Unlock()
   565  	be.blockChans[c] = struct{}{}
   566  	return c
   567  }
   568  
   569  // sendBlockUpdate sends the BlockUpdate to all subscribers.
   570  func (be *AssetBackend) sendBlockUpdate(u *asset.BlockUpdate) {
   571  	be.blockChansMtx.RLock()
   572  	defer be.blockChansMtx.RUnlock()
   573  	for c := range be.blockChans {
   574  		select {
   575  		case c <- u:
   576  		default:
   577  			be.log.Error("failed to send block update on blocking channel")
   578  		}
   579  	}
   580  }
   581  
   582  // ValidateContract ensures that contractData encodes both the expected contract
   583  // version and a secret hash.
   584  func (eth *ETHBackend) ValidateContract(contractData []byte) error {
   585  	ver, _, err := dexeth.DecodeContractData(contractData)
   586  	if err != nil { // ensures secretHash is proper length
   587  		return err
   588  	}
   589  
   590  	if ver != version {
   591  		return fmt.Errorf("incorrect swap contract version %d, wanted %d", ver, version)
   592  	}
   593  	return nil
   594  }
   595  
   596  // ValidateContract ensures that contractData encodes both the expected swap
   597  // contract version and a secret hash.
   598  func (eth *TokenBackend) ValidateContract(contractData []byte) error {
   599  	ver, _, err := dexeth.DecodeContractData(contractData)
   600  	if err != nil { // ensures secretHash is proper length
   601  		return err
   602  	}
   603  	_, _, err = networkToken(eth.VersionedToken, eth.net)
   604  	if err != nil {
   605  		return fmt.Errorf("error locating token: %v", err)
   606  	}
   607  	if ver != eth.VersionedToken.Ver {
   608  		return fmt.Errorf("incorrect token swap contract version %d, wanted %d", ver, version)
   609  	}
   610  
   611  	return nil
   612  }
   613  
   614  // Contract is part of the asset.Backend interface. The contractData bytes
   615  // encodes both the contract version targeted and the secret hash.
   616  func (be *AssetBackend) Contract(coinID, contractData []byte) (*asset.Contract, error) {
   617  	// newSwapCoin validates the contractData, extracting version, secret hash,
   618  	// counterparty address, and locktime. The supported version is enforced.
   619  	sc, err := be.newSwapCoin(coinID, contractData)
   620  	if err != nil {
   621  		return nil, fmt.Errorf("unable to create coiner: %w", err)
   622  	}
   623  
   624  	// Confirmations performs some extra swap status checks if the the tx is
   625  	// mined. For init coins, this uses the contract account's state (if it is
   626  	// mined) to verify the value, counterparty, and lock time.
   627  	_, err = sc.Confirmations(be.ctx)
   628  	if err != nil {
   629  		return nil, fmt.Errorf("unable to get confirmations: %v", err)
   630  	}
   631  	return &asset.Contract{
   632  		Coin:         sc,
   633  		SwapAddress:  sc.init.Participant.String(),
   634  		ContractData: contractData,
   635  		SecretHash:   sc.secretHash[:],
   636  		TxData:       sc.serializedTx,
   637  		LockTime:     sc.init.LockTime,
   638  	}, nil
   639  }
   640  
   641  // ValidateSecret checks that the secret satisfies the secret hash.
   642  func (eth *baseBackend) ValidateSecret(secret, contractData []byte) bool {
   643  	_, secretHash, err := dexeth.DecodeContractData(contractData)
   644  	if err != nil {
   645  		return false
   646  	}
   647  	sh := sha256.Sum256(secret)
   648  	return bytes.Equal(sh[:], secretHash[:])
   649  }
   650  
   651  // Synced is true if the blockchain is ready for action.
   652  func (eth *baseBackend) Synced() (bool, error) {
   653  	bh, err := eth.node.bestHeader(eth.ctx)
   654  	if err != nil {
   655  		return false, err
   656  	}
   657  	// Time in the header is in seconds.
   658  	nowInSecs := time.Now().Unix()
   659  	timeDiff := nowInSecs - int64(bh.Time)
   660  	return timeDiff < dexeth.MaxBlockInterval, nil
   661  }
   662  
   663  // Redemption returns a coin that represents a contract redemption. redeemCoinID
   664  // should be the transaction that sent a redemption, while contractCoinID is the
   665  // swap contract this redemption redeems.
   666  func (be *AssetBackend) Redemption(redeemCoinID, _, contractData []byte) (asset.Coin, error) {
   667  	// newRedeemCoin uses the contract account's state to validate the
   668  	// contractData, extracting version, secret, and secret hash. The supported
   669  	// version is enforced.
   670  	rc, err := be.newRedeemCoin(redeemCoinID, contractData)
   671  	if err != nil {
   672  		return nil, fmt.Errorf("unable to create coiner: %w", err)
   673  	}
   674  
   675  	// Confirmations performs some extra swap status checks if the the tx
   676  	// is mined. For redeem coins, this is just a swap state check.
   677  	_, err = rc.Confirmations(be.ctx)
   678  	if err != nil {
   679  		return nil, fmt.Errorf("unable to get confirmations: %v", err)
   680  	}
   681  	return rc, nil
   682  }
   683  
   684  // ValidateCoinID attempts to decode the coinID.
   685  func (eth *baseBackend) ValidateCoinID(coinID []byte) (string, error) {
   686  	txHash, err := dexeth.DecodeCoinID(coinID)
   687  	if err != nil {
   688  		return "<invalid>", err
   689  	}
   690  	return txHash.String(), nil
   691  }
   692  
   693  // CheckSwapAddress checks that the given address is parseable.
   694  func (eth *baseBackend) CheckSwapAddress(addr string) bool {
   695  	return common.IsHexAddress(addr)
   696  }
   697  
   698  // AccountBalance retrieves the current account balance, including the effects
   699  // of known unmined transactions.
   700  func (be *AssetBackend) AccountBalance(addrStr string) (uint64, error) {
   701  	bigBal, err := be.node.accountBalance(be.ctx, be.assetID, common.HexToAddress(addrStr))
   702  	if err != nil {
   703  		return 0, fmt.Errorf("accountBalance error: %w", err)
   704  	}
   705  	return be.atomize(bigBal), nil
   706  }
   707  
   708  // ValidateSignature checks that the pubkey is correct for the address and
   709  // that the signature shows ownership of the associated private key.
   710  func (eth *baseBackend) ValidateSignature(addr string, pubkey, msg, sig []byte) error {
   711  	const sigLen = 64
   712  	if len(sig) != sigLen {
   713  		return fmt.Errorf("expected sig length of %d bytes but got %d", sigLen, len(sig))
   714  	}
   715  	ethPK, err := crypto.UnmarshalPubkey(pubkey)
   716  	if err != nil {
   717  		return fmt.Errorf("unable to unmarshal pubkey: %v", err)
   718  	}
   719  	// Ensure the pubkey matches the funding coin's account address.
   720  	// Internally, this will take the last twenty bytes of crypto.Keccak256
   721  	// of all but the first byte of the pubkey bytes and wrap it as a
   722  	// common.Address.
   723  	pubkeyAddr := crypto.PubkeyToAddress(*ethPK)
   724  	if addr != pubkeyAddr.String() {
   725  		return errors.New("pubkey does not correspond to address")
   726  	}
   727  	r := new(secp256k1.ModNScalar)
   728  	if overflow := r.SetByteSlice(sig[0:32]); overflow {
   729  		return errors.New("invalid signature: r >= group order")
   730  	}
   731  	s := new(secp256k1.ModNScalar)
   732  	if overflow := s.SetByteSlice(sig[32:64]); overflow {
   733  		return errors.New("invalid signature: s >= group order")
   734  	}
   735  	ecdsaSig := ecdsa.NewSignature(r, s)
   736  
   737  	pk, err := secp256k1.ParsePubKey(pubkey)
   738  	if err != nil {
   739  		return err
   740  	}
   741  	// Verify the signature.
   742  	if !ecdsaSig.Verify(crypto.Keccak256(msg), pk) {
   743  		return errors.New("cannot verify signature")
   744  	}
   745  	return nil
   746  }
   747  
   748  // poll pulls the best hash from an eth node and compares that to a stored
   749  // hash. If the same does nothing. If different, updates the stored hash and
   750  // notifies listeners on block chans.
   751  func (eth *ETHBackend) poll(ctx context.Context) {
   752  	send := func(err error) {
   753  		if err != nil {
   754  			eth.log.Error(err)
   755  		}
   756  		u := &asset.BlockUpdate{
   757  			Err: err,
   758  		}
   759  
   760  		eth.sendBlockUpdate(u)
   761  
   762  		for _, be := range eth.tokens {
   763  			be.sendBlockUpdate(u)
   764  		}
   765  	}
   766  	bn, err := eth.node.blockNumber(ctx)
   767  	if err != nil {
   768  		send(fmt.Errorf("error getting best block header: %w", err))
   769  		return
   770  	}
   771  	if bn == eth.bestHeight {
   772  		// Same height, nothing to do.
   773  		return
   774  	}
   775  	eth.log.Debugf("Tip change from %d to %d.", eth.bestHeight, bn)
   776  	eth.bestHeight = bn
   777  	send(nil)
   778  }
   779  
   780  // run processes the queue and monitors the application context.
   781  func (eth *ETHBackend) run(ctx context.Context) {
   782  	// Non-loopback providers are metered at 10 seconds internally to rpcclient,
   783  	// but loopback addresses allow more frequent checks.
   784  	blockPoll := time.NewTicker(time.Second)
   785  	defer blockPoll.Stop()
   786  
   787  	for {
   788  		select {
   789  		case <-blockPoll.C:
   790  			eth.poll(ctx)
   791  		case <-ctx.Done():
   792  			return
   793  		}
   794  	}
   795  }