diff --git a/server/asset/eth/eth.go b/server/asset/eth/eth.go index 6a527ec0e4..3cb1d27769 100644 --- a/server/asset/eth/eth.go +++ b/server/asset/eth/eth.go @@ -22,7 +22,9 @@ import ( "decred.org/dcrdex/server/asset" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" ) func init() { @@ -86,7 +88,8 @@ type ethFetcher interface { headerByHeight(ctx context.Context, height uint64) (*types.Header, error) connect(ctx context.Context, ipc string, contractAddr *common.Address) error shutdown() - suggestGasPrice(ctx context.Context) (*big.Int, error) + suggestGasTipCap(ctx context.Context) (*big.Int, error) // suggested priority fee (tip) per gas + suggestGasPrice(ctx context.Context) (*big.Int, error) // suggested gas price for legacy txns syncProgress(ctx context.Context) (*ethereum.SyncProgress, error) swap(ctx context.Context, secretHash [32]byte) (*swapv0.ETHSwapSwap, error) transaction(ctx context.Context, hash common.Hash) (tx *types.Transaction, isMempool bool, err error) @@ -105,14 +108,15 @@ type hashN struct { type Backend struct { // A connection-scoped Context is used to cancel active RPCs on // connection shutdown. - rpcCtx context.Context - cancelRPCs context.CancelFunc - cfg *config - node ethFetcher + rpcCtx context.Context + cancelRPCs context.CancelFunc + cfg *config + node ethFetcher + chainConfig *params.ChainConfig // bestHash caches the last know best block hash and height and is used // to detect reorgs. Only accessed in Connect and poll which is - // syncronous so no locking is needed presently. + // synchronous so no locking is needed presently. bestHash hashN // The backend provides block notification channels through the BlockChannel @@ -145,18 +149,23 @@ func unconnectedETH(logger dex.Logger, cfg *config) *Backend { // possibly a random contract setup, and so this section will need to // change to support multiple contracts. var contractAddr common.Address + var chainConfig *params.ChainConfig switch cfg.network { case dex.Simnet: contractAddr = common.HexToAddress(simnetContractAddr) + chainConfig = params.TestChainConfig // if harness running, may set actual config via dexeth.LoadGenesisFile case dex.Testnet: contractAddr = common.HexToAddress(testnetContractAddr) + chainConfig = params.GoerliChainConfig case dex.Mainnet: contractAddr = common.HexToAddress(mainnetContractAddr) + chainConfig = params.MainnetChainConfig } return &Backend{ rpcCtx: ctx, cancelRPCs: cancel, cfg: cfg, + chainConfig: chainConfig, log: logger, blockChans: make(map[chan *asset.BlockUpdate]struct{}), contractAddr: contractAddr, @@ -235,11 +244,16 @@ func (eth *Backend) InitTxSizeBase() uint32 { // FeeRate returns the current optimal fee rate in gwei / gas. func (eth *Backend) FeeRate(ctx context.Context) (uint64, error) { - bigGP, err := eth.node.suggestGasPrice(ctx) + tip, err := eth.node.suggestGasTipCap(ctx) if err != nil { return 0, err } - return dexeth.ToGwei(bigGP) + hdr, err := eth.node.bestHeader(ctx) + if err != nil { + return 0, fmt.Errorf("error getting best block header from geth: %w", err) + } + base := misc.CalcBaseFee(eth.chainConfig, hdr) + return dexeth.ToGwei(tip.Add(tip, base)) } // BlockChannel creates and returns a new channel on which to receive block diff --git a/server/asset/eth/eth_test.go b/server/asset/eth/eth_test.go index 10aeb18592..97f748bada 100644 --- a/server/asset/eth/eth_test.go +++ b/server/asset/eth/eth_test.go @@ -89,24 +89,26 @@ func mustParseHex(s string) []byte { } type testNode struct { - connectErr error - bestHdr *types.Header - bestHdrErr error - hdrByHeight *types.Header - hdrByHeightErr error - blkNum uint64 - blkNumErr error - syncProg *ethereum.SyncProgress - syncProgErr error - sugGasPrice *big.Int - sugGasPriceErr error - swp *swapv0.ETHSwapSwap - swpErr error - tx *types.Transaction - txIsMempool bool - txErr error - acctBal *big.Int - acctBalErr error + connectErr error + bestHdr *types.Header + bestHdrErr error + hdrByHeight *types.Header + hdrByHeightErr error + blkNum uint64 + blkNumErr error + syncProg *ethereum.SyncProgress + syncProgErr error + sugGasPrice *big.Int + sugGasPriceErr error + sugGasTipCap *big.Int + sugGasTipCapErr error + swp *swapv0.ETHSwapSwap + swpErr error + tx *types.Transaction + txIsMempool bool + txErr error + acctBal *big.Int + acctBalErr error } func (n *testNode) connect(ctx context.Context, ipc string, contractAddr *common.Address) error { @@ -131,6 +133,10 @@ func (n *testNode) syncProgress(ctx context.Context) (*ethereum.SyncProgress, er return n.syncProg, n.syncProgErr } +func (n *testNode) suggestGasTipCap(ctx context.Context) (*big.Int, error) { + return n.sugGasTipCap, n.sugGasTipCapErr +} + func (n *testNode) suggestGasPrice(ctx context.Context) (*big.Int, error) { return n.sugGasPrice, n.sugGasPriceErr } @@ -276,48 +282,60 @@ func TestFeeRate(t *testing.T) { overMaxWei.Add(overMaxWei, gweiFactorBig) tests := []struct { name string - gas *big.Int - gasErr error + base *big.Int + tip *big.Int + tipErr error wantFee uint64 wantErr bool }{{ name: "ok zero", - gas: new(big.Int), + base: new(big.Int), + tip: new(big.Int), wantFee: 0, }, { name: "ok rounded down", - gas: big.NewInt(dexeth.GweiFactor - 1), + base: big.NewInt(dexeth.GweiFactor - 1), + tip: new(big.Int), wantFee: 0, }, { name: "ok one", - gas: big.NewInt(dexeth.GweiFactor), + base: big.NewInt(dexeth.GweiFactor), + tip: new(big.Int), wantFee: 1, }, { name: "ok max int", - gas: maxWei, + base: maxWei, + tip: new(big.Int), wantFee: maxInt, }, { name: "over max int", - gas: overMaxWei, + base: overMaxWei, + tip: new(big.Int), wantErr: true, }, { name: "node suggest gas fee error", - gas: new(big.Int), - gasErr: errors.New(""), + base: new(big.Int), + tip: new(big.Int), + tipErr: errors.New(""), wantErr: true, }} for _, test := range tests { ctx, cancel := context.WithCancel(context.Background()) node := &testNode{ - sugGasPrice: test.gas, - sugGasPriceErr: test.gasErr, + sugGasTipCap: test.tip, + sugGasTipCapErr: test.tipErr, + bestHdr: &types.Header{ + Number: big.NewInt(1), // alway post-london on simnet + BaseFee: test.base, + // GasUsed == GasLimit / ElasticityMultiplier(2) means use parent.BaseFee for next block in concensus/misc.CalcBaseFee + }, } - eth := &Backend{ - node: node, - rpcCtx: ctx, - log: tLogger, + eth, err := NewBackend("", tLogger, dex.Simnet) + if err != nil { + t.Fatal(err) } + eth.node = node fee, err := eth.FeeRate(ctx) cancel() if test.wantErr { @@ -363,20 +381,18 @@ func TestSynced(t *testing.T) { for _, test := range tests { nowInSecs := uint64(time.Now().Unix() / 1000) - ctx, cancel := context.WithCancel(context.Background()) node := &testNode{ syncProg: test.syncProg, syncProgErr: test.syncProgErr, bestHdr: &types.Header{Time: nowInSecs - test.subSecs}, bestHdrErr: test.bestHdrErr, } - eth := &Backend{ - node: node, - rpcCtx: ctx, - log: tLogger, + eth, err := NewBackend("", tLogger, dex.Testnet) + if err != nil { + t.Fatal(err) } + eth.node = node synced, err := eth.Synced() - cancel() if test.wantErr { if err == nil { t.Fatalf("expected error for test %q", test.name) @@ -477,11 +493,12 @@ func TestContract(t *testing.T) { swp: test.swap, swpErr: test.swapErr, } - eth := &Backend{ - node: node, - log: tLogger, - contractAddr: *contractAddr, + eth, err := NewBackend("", tLogger, dex.Testnet) + if err != nil { + t.Fatal(err) } + eth.node = node + eth.contractAddr = *contractAddr contractData := dexeth.EncodeContractData(0, secretHash) // matches initCalldata contract, err := eth.Contract(test.coinID, contractData) if test.wantErr { diff --git a/server/asset/eth/rpcclient.go b/server/asset/eth/rpcclient.go index f733d9b951..36dee8c463 100644 --- a/server/asset/eth/rpcclient.go +++ b/server/asset/eth/rpcclient.go @@ -80,6 +80,11 @@ func (c *rpcclient) suggestGasPrice(ctx context.Context) (sgp *big.Int, err erro return c.ec.SuggestGasPrice(ctx) } +// suggestGasTipCap retrieves the currently suggested priority fee rate. +func (c *rpcclient) suggestGasTipCap(ctx context.Context) (sgp *big.Int, err error) { + return c.ec.SuggestGasTipCap(ctx) +} + // syncProgress return the current sync progress. Returns no error and nil when not syncing. func (c *rpcclient) syncProgress(ctx context.Context) (*ethereum.SyncProgress, error) { return c.ec.SyncProgress(ctx)