Skip to content

Commit

Permalink
server/asset/eth: separate tip/base fee estimates
Browse files Browse the repository at this point in the history
  • Loading branch information
chappjc committed Dec 17, 2021
1 parent 8477d5e commit 64a72b4
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 51 deletions.
30 changes: 22 additions & 8 deletions server/asset/eth/eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)

This comment has been minimized.

Copy link
@chappjc

chappjc Dec 26, 2021

Author Member

Arguably it is overkill to use consensus/misc to get the next base fee when we could just use hdr.BaseFee (current base fee) since we're just going to apply some multiplier and add suggested tip. The next base fee could just as easily be less than current as they could be more, so it's not that important since we are not actually using the computed next base fee for consensus, just a max gas fee for a transaction (which BTW is likely to be made many blocks in the future from this block, especially for the taker, but even for maker).

return dexeth.ToGwei(tip.Add(tip, base))

This comment has been minimized.

Copy link
@chappjc

chappjc Dec 26, 2021

Author Member

A more appropriate formula if this FeeRate is intended to return the max gas fee would be tip + 2 * base (or maybe a bit larger even). See #1372

}

// BlockChannel creates and returns a new channel on which to receive block
Expand Down
103 changes: 60 additions & 43 deletions server/asset/eth/eth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions server/asset/eth/rpcclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 64a72b4

Please sign in to comment.