diff --git a/node/builder.go b/node/builder.go index df183b216..33a947826 100644 --- a/node/builder.go +++ b/node/builder.go @@ -329,6 +329,8 @@ var ChainNode = Options( Override(new(storagemarket.StorageClientNode), storageadapter.NewClientNodeAdapter), Override(HandleMigrateClientFundsKey, modules.HandleMigrateClientFunds), + Override(new(*full.GasPriceCache), full.NewGasPriceCache), + // Lite node API ApplyIf(isLiteNode, Override(new(messagesigner.MpoolNonceAPI), From(new(modules.MpoolNonceAPI))), diff --git a/node/impl/full/gas.go b/node/impl/full/gas.go index ec7074e5b..3d9889c10 100644 --- a/node/impl/full/gas.go +++ b/node/impl/full/gas.go @@ -8,6 +8,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/actors/builtin/paych" + lru "github.com/hashicorp/golang-lru" "go.uber.org/fx" "golang.org/x/xerrors" @@ -39,6 +40,8 @@ type GasModule struct { Chain *store.ChainStore Mpool *messagepool.MessagePool GetMaxFee dtypes.DefaultMaxFeeFunc + + PriceCache *GasPriceCache } var _ GasModuleAPI = (*GasModule)(nil) @@ -51,6 +54,53 @@ type GasAPI struct { Stmgr *stmgr.StateManager Chain *store.ChainStore Mpool *messagepool.MessagePool + + PriceCache *GasPriceCache +} + +func NewGasPriceCache() *GasPriceCache { + // 50 because we usually won't access more than 40 + c, err := lru.New2Q(50) + if err != nil { + // err only if parameter is bad + panic(err) + } + + return &GasPriceCache{ + c: c, + } +} + +type GasPriceCache struct { + c *lru.TwoQueueCache +} + +type GasMeta struct { + Price big.Int + Limit int64 +} + +func (g *GasPriceCache) GetTSGasStats(cstore *store.ChainStore, ts *types.TipSet) ([]GasMeta, error) { + i, has := g.c.Get(ts.Key()) + if has { + return i.([]GasMeta), nil + } + + var prices []GasMeta + msgs, err := cstore.MessagesForTipset(ts) + if err != nil { + return nil, xerrors.Errorf("loading messages: %w", err) + } + for _, msg := range msgs { + prices = append(prices, GasMeta{ + Price: msg.VMMessage().GasPremium, + Limit: msg.VMMessage().GasLimit, + }) + } + + g.c.Add(ts.Key(), prices) + + return prices, nil } const MinGasPremium = 100e3 @@ -88,24 +138,19 @@ func gasEstimateFeeCap(cstore *store.ChainStore, msg *types.Message, maxqueueblk return out, nil } -type gasMeta struct { - price big.Int - limit int64 -} - // finds 55th percntile instead of median to put negative pressure on gas price -func medianGasPremium(prices []gasMeta, blocks int) abi.TokenAmount { +func medianGasPremium(prices []GasMeta, blocks int) abi.TokenAmount { sort.Slice(prices, func(i, j int) bool { // sort desc by price - return prices[i].price.GreaterThan(prices[j].price) + return prices[i].Price.GreaterThan(prices[j].Price) }) at := build.BlockGasTarget * int64(blocks) / 2 // 50th at += build.BlockGasTarget * int64(blocks) / (2 * 20) // move 5% further prev1, prev2 := big.Zero(), big.Zero() for _, price := range prices { - prev1, prev2 = price.price, prev1 - at -= price.limit + prev1, prev2 = price.Price, prev1 + at -= price.Limit if at < 0 { break } @@ -126,7 +171,7 @@ func (a *GasAPI) GasEstimateGasPremium( gaslimit int64, _ types.TipSetKey, ) (types.BigInt, error) { - return gasEstimateGasPremium(a.Chain, nblocksincl) + return gasEstimateGasPremium(a.Chain, a.PriceCache, nblocksincl) } func (m *GasModule) GasEstimateGasPremium( ctx context.Context, @@ -135,14 +180,14 @@ func (m *GasModule) GasEstimateGasPremium( gaslimit int64, _ types.TipSetKey, ) (types.BigInt, error) { - return gasEstimateGasPremium(m.Chain, nblocksincl) + return gasEstimateGasPremium(m.Chain, m.PriceCache, nblocksincl) } -func gasEstimateGasPremium(cstore *store.ChainStore, nblocksincl uint64) (types.BigInt, error) { +func gasEstimateGasPremium(cstore *store.ChainStore, cache *GasPriceCache, nblocksincl uint64) (types.BigInt, error) { if nblocksincl == 0 { nblocksincl = 1 } - var prices []gasMeta + var prices []GasMeta var blocks int ts := cstore.GetHeaviestTipSet() @@ -157,17 +202,11 @@ func gasEstimateGasPremium(cstore *store.ChainStore, nblocksincl uint64) (types. } blocks += len(pts.Blocks()) - - msgs, err := cstore.MessagesForTipset(pts) + meta, err := cache.GetTSGasStats(cstore, pts) if err != nil { - return types.BigInt{}, xerrors.Errorf("loading messages: %w", err) - } - for _, msg := range msgs { - prices = append(prices, gasMeta{ - price: msg.VMMessage().GasPremium, - limit: msg.VMMessage().GasLimit, - }) + return types.BigInt{}, err } + prices = append(prices, meta...) ts = pts } diff --git a/node/impl/full/gas_test.go b/node/impl/full/gas_test.go index 2452ab807..028e039ce 100644 --- a/node/impl/full/gas_test.go +++ b/node/impl/full/gas_test.go @@ -12,27 +12,27 @@ import ( ) func TestMedian(t *testing.T) { - require.Equal(t, types.NewInt(5), medianGasPremium([]gasMeta{ + require.Equal(t, types.NewInt(5), medianGasPremium([]GasMeta{ {big.NewInt(5), build.BlockGasTarget}, }, 1)) - require.Equal(t, types.NewInt(10), medianGasPremium([]gasMeta{ + require.Equal(t, types.NewInt(10), medianGasPremium([]GasMeta{ {big.NewInt(5), build.BlockGasTarget}, {big.NewInt(10), build.BlockGasTarget}, }, 1)) - require.Equal(t, types.NewInt(15), medianGasPremium([]gasMeta{ + require.Equal(t, types.NewInt(15), medianGasPremium([]GasMeta{ {big.NewInt(10), build.BlockGasTarget / 2}, {big.NewInt(20), build.BlockGasTarget / 2}, }, 1)) - require.Equal(t, types.NewInt(25), medianGasPremium([]gasMeta{ + require.Equal(t, types.NewInt(25), medianGasPremium([]GasMeta{ {big.NewInt(10), build.BlockGasTarget / 2}, {big.NewInt(20), build.BlockGasTarget / 2}, {big.NewInt(30), build.BlockGasTarget / 2}, }, 1)) - require.Equal(t, types.NewInt(15), medianGasPremium([]gasMeta{ + require.Equal(t, types.NewInt(15), medianGasPremium([]GasMeta{ {big.NewInt(10), build.BlockGasTarget / 2}, {big.NewInt(20), build.BlockGasTarget / 2}, {big.NewInt(30), build.BlockGasTarget / 2},