Skip to content

Commit

Permalink
feat: mempool: Reduce minimum replace fee from 1.25x to 1.1x (#10416)
Browse files Browse the repository at this point in the history
However, we're leaving the default at 1.25x for backwards compatibility, for now.

Also:

1. Actually use the configured replace fee ratio.
2. Store said ratios as percentages instead of floats. 1.25, or 1+1/(2^2),
can be represented as a float. 1.1, or 1 + 1/(2 * 5), cannot.

fixes #10415
  • Loading branch information
Stebalien authored and jennijuju committed Mar 10, 2023
1 parent 36ffa09 commit b7db4cb
Show file tree
Hide file tree
Showing 12 changed files with 113 additions and 20 deletions.
4 changes: 4 additions & 0 deletions api/docgen/docgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,10 @@ func init() {
FromBlock: pstring("2301220"),
Address: []ethtypes.EthAddress{ethaddr},
})

percent := types.Percent(123)
addExample(percent)
addExample(&percent)
}

func GetAPIType(name, pkg string) (i interface{}, t reflect.Type, permStruct []reflect.Type) {
Expand Down
Binary file modified build/openrpc/full.json.gz
Binary file not shown.
2 changes: 1 addition & 1 deletion build/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func BuildTypeString() string {
}

// BuildVersion is the local build version
const BuildVersion = "1.20.1"
const BuildVersion = "1.20.2"

func UserVersion() string {
if os.Getenv("LOTUS_VERSION_IGNORE_COMMIT") == "1" {
Expand Down
14 changes: 9 additions & 5 deletions chain/messagepool/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import (
)

var (
ReplaceByFeeRatioDefault = 1.25
ReplaceByFeePercentageMinimum types.Percent = 110
ReplaceByFeePercentageDefault types.Percent = 125
)

var (
MemPoolSizeLimitHiDefault = 30000
MemPoolSizeLimitLoDefault = 20000
PruneCooldownDefault = time.Minute
Expand Down Expand Up @@ -60,9 +64,9 @@ func (mp *MessagePool) getConfig() *types.MpoolConfig {
}

func validateConfg(cfg *types.MpoolConfig) error {
if cfg.ReplaceByFeeRatio < ReplaceByFeeRatioDefault {
return fmt.Errorf("'ReplaceByFeeRatio' is less than required %f < %f",
cfg.ReplaceByFeeRatio, ReplaceByFeeRatioDefault)
if cfg.ReplaceByFeeRatio < ReplaceByFeePercentageMinimum {
return fmt.Errorf("'ReplaceByFeeRatio' is less than required %s < %s",
cfg.ReplaceByFeeRatio, ReplaceByFeePercentageMinimum)
}
if cfg.GasLimitOverestimation < 1 {
return fmt.Errorf("'GasLimitOverestimation' cannot be less than 1")
Expand Down Expand Up @@ -91,7 +95,7 @@ func DefaultConfig() *types.MpoolConfig {
return &types.MpoolConfig{
SizeLimitHigh: MemPoolSizeLimitHiDefault,
SizeLimitLow: MemPoolSizeLimitLoDefault,
ReplaceByFeeRatio: ReplaceByFeeRatioDefault,
ReplaceByFeeRatio: ReplaceByFeePercentageDefault,
PruneCooldown: PruneCooldownDefault,
GasLimitOverestimation: GasLimitOverestimation,
}
Expand Down
14 changes: 9 additions & 5 deletions chain/messagepool/messagepool.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,8 @@ var log = logging.Logger("messagepool")

var futureDebug = false

var rbfNumBig = types.NewInt(uint64((ReplaceByFeeRatioDefault - 1) * RbfDenom))
var rbfDenomBig = types.NewInt(RbfDenom)

const RbfDenom = 256
var rbfNumBig = types.NewInt(uint64(ReplaceByFeePercentageMinimum))
var rbfDenomBig = types.NewInt(100)

var RepublishInterval = time.Duration(10*build.BlockDelaySecs+build.PropagationDelaySecs) * time.Second

Expand Down Expand Up @@ -198,7 +196,13 @@ func newMsgSet(nonce uint64) *msgSet {
}

func ComputeMinRBF(curPrem abi.TokenAmount) abi.TokenAmount {
minPrice := types.BigAdd(curPrem, types.BigDiv(types.BigMul(curPrem, rbfNumBig), rbfDenomBig))
minPrice := types.BigDiv(types.BigMul(curPrem, rbfNumBig), rbfDenomBig)
return types.BigAdd(minPrice, types.NewInt(1))
}

func ComputeRBF(curPrem abi.TokenAmount, replaceByFeeRatio types.Percent) abi.TokenAmount {
rbfNumBig := types.NewInt(uint64(replaceByFeeRatio))
minPrice := types.BigDiv(types.BigMul(curPrem, rbfNumBig), rbfDenomBig)
return types.BigAdd(minPrice, types.NewInt(1))
}

Expand Down
2 changes: 1 addition & 1 deletion chain/types/mpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type MpoolConfig struct {
PriorityAddrs []address.Address
SizeLimitHigh int
SizeLimitLow int
ReplaceByFeeRatio float64
ReplaceByFeeRatio Percent
PruneCooldown time.Duration
GasLimitOverestimation float64
}
Expand Down
39 changes: 39 additions & 0 deletions chain/types/percent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package types

import (
"fmt"
"math"
"strconv"

"golang.org/x/xerrors"
)

// Percent stores a signed percentage as an int64. When converted to a string (or json), it's stored
// as a decimal with two places (e.g., 100% -> 1.00).
type Percent int64

func (p Percent) String() string {
abs := p
sign := ""
if abs < 0 {
abs = -abs
sign = "-"
}
return fmt.Sprintf(`%s%d.%d`, sign, abs/100, abs%100)
}

func (p Percent) MarshalJSON() ([]byte, error) {
return []byte(p.String()), nil
}

func (p *Percent) UnmarshalJSON(b []byte) error {
flt, err := strconv.ParseFloat(string(b)+"e2", 64)
if err != nil {
return xerrors.Errorf("unable to parse ratio %s: %w", string(b), err)
}
if math.Trunc(flt) != flt {
return xerrors.Errorf("ratio may only have two decimals: %s", string(b))
}
*p = Percent(flt)
return nil
}
34 changes: 34 additions & 0 deletions chain/types/percent_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package types

import (
"fmt"
"testing"

"github.com/stretchr/testify/require"
)

func TestPercent(t *testing.T) {
for _, tc := range []struct {
p Percent
s string
}{
{100, "1.0"},
{111, "1.11"},
{12, "0.12"},
{-12, "-0.12"},
{1012, "10.12"},
{-1012, "-10.12"},
{0, "0.0"},
} {
tc := tc
t.Run(fmt.Sprintf("%d <> %s", tc.p, tc.s), func(t *testing.T) {
m, err := tc.p.MarshalJSON()
require.NoError(t, err)
require.Equal(t, tc.s, string(m))
var p Percent
require.NoError(t, p.UnmarshalJSON([]byte(tc.s)))
require.Equal(t, tc.p, p)
})
}

}
9 changes: 7 additions & 2 deletions cli/mpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,12 @@ var MpoolReplaceCmd = &cli.Command{
msg := found.Message

if cctx.Bool("auto") {
minRBF := messagepool.ComputeMinRBF(msg.GasPremium)
cfg, err := api.MpoolGetConfig(ctx)
if err != nil {
return xerrors.Errorf("failed to lookup the message pool config: %w", err)
}

defaultRBF := messagepool.ComputeRBF(msg.GasPremium, cfg.ReplaceByFeeRatio)

var mss *lapi.MessageSendSpec
if cctx.IsSet("fee-limit") {
Expand All @@ -482,7 +487,7 @@ var MpoolReplaceCmd = &cli.Command{
return xerrors.Errorf("failed to estimate gas values: %w", err)
}

msg.GasPremium = big.Max(retm.GasPremium, minRBF)
msg.GasPremium = big.Max(retm.GasPremium, defaultRBF)
msg.GasFeeCap = big.Max(retm.GasFeeCap, msg.GasPremium)

mff := func() (abi.TokenAmount, error) {
Expand Down
7 changes: 5 additions & 2 deletions cli/mpool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/filecoin-project/go-state-types/big"

"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/messagepool"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/types/mock"
"github.com/filecoin-project/lotus/chain/wallet"
Expand Down Expand Up @@ -298,6 +299,7 @@ func TestReplace(t *testing.T) {
mockApi.EXPECT().ChainGetMessage(ctx, sm.Cid()).Return(&sm.Message, nil),
mockApi.EXPECT().ChainHead(ctx).Return(nil, nil),
mockApi.EXPECT().MpoolPending(ctx, types.EmptyTSK).Return([]*types.SignedMessage{sm}, nil),
mockApi.EXPECT().MpoolGetConfig(ctx).Return(messagepool.DefaultConfig(), nil),
// use gomock.any to match the message in expected api calls
// since the replace function modifies the message between calls, it would be pointless to try to match the exact argument
mockApi.EXPECT().GasEstimateMessageGas(ctx, gomock.Any(), &mss, types.EmptyTSK).Return(&sm.Message, nil),
Expand Down Expand Up @@ -342,6 +344,7 @@ func TestReplace(t *testing.T) {
gomock.InOrder(
mockApi.EXPECT().ChainHead(ctx).Return(nil, nil),
mockApi.EXPECT().MpoolPending(ctx, types.EmptyTSK).Return([]*types.SignedMessage{sm}, nil),
mockApi.EXPECT().MpoolGetConfig(ctx).Return(messagepool.DefaultConfig(), nil),
// use gomock.any to match the message in expected api calls
// since the replace function modifies the message between calls, it would be pointless to try to match the exact argument
mockApi.EXPECT().GasEstimateMessageGas(ctx, gomock.Any(), &mss, types.EmptyTSK).Return(&sm.Message, nil),
Expand Down Expand Up @@ -538,7 +541,7 @@ func TestConfig(t *testing.T) {
t.Fatal(err)
}

mpoolCfg := &types.MpoolConfig{PriorityAddrs: []address.Address{senderAddr}, SizeLimitHigh: 1234567, SizeLimitLow: 6, ReplaceByFeeRatio: 0.25}
mpoolCfg := &types.MpoolConfig{PriorityAddrs: []address.Address{senderAddr}, SizeLimitHigh: 1234567, SizeLimitLow: 6, ReplaceByFeeRatio: types.Percent(25)}
gomock.InOrder(
mockApi.EXPECT().MpoolGetConfig(ctx).Return(mpoolCfg, nil),
)
Expand Down Expand Up @@ -566,7 +569,7 @@ func TestConfig(t *testing.T) {
t.Fatal(err)
}

mpoolCfg := &types.MpoolConfig{PriorityAddrs: []address.Address{senderAddr}, SizeLimitHigh: 234567, SizeLimitLow: 3, ReplaceByFeeRatio: 0.33}
mpoolCfg := &types.MpoolConfig{PriorityAddrs: []address.Address{senderAddr}, SizeLimitHigh: 234567, SizeLimitLow: 3, ReplaceByFeeRatio: types.Percent(33)}
gomock.InOrder(
mockApi.EXPECT().MpoolSetConfig(ctx, mpoolCfg).Return(nil),
)
Expand Down
4 changes: 2 additions & 2 deletions documentation/en/api-v0-methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -2879,7 +2879,7 @@ Response:
],
"SizeLimitHigh": 123,
"SizeLimitLow": 123,
"ReplaceByFeeRatio": 12.3,
"ReplaceByFeeRatio": 1.23,
"PruneCooldown": 60000000000,
"GasLimitOverestimation": 12.3
}
Expand Down Expand Up @@ -3167,7 +3167,7 @@ Inputs:
],
"SizeLimitHigh": 123,
"SizeLimitLow": 123,
"ReplaceByFeeRatio": 12.3,
"ReplaceByFeeRatio": 1.23,
"PruneCooldown": 60000000000,
"GasLimitOverestimation": 12.3
}
Expand Down
4 changes: 2 additions & 2 deletions documentation/en/api-v1-unstable-methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -3816,7 +3816,7 @@ Response:
],
"SizeLimitHigh": 123,
"SizeLimitLow": 123,
"ReplaceByFeeRatio": 12.3,
"ReplaceByFeeRatio": 1.23,
"PruneCooldown": 60000000000,
"GasLimitOverestimation": 12.3
}
Expand Down Expand Up @@ -4104,7 +4104,7 @@ Inputs:
],
"SizeLimitHigh": 123,
"SizeLimitLow": 123,
"ReplaceByFeeRatio": 12.3,
"ReplaceByFeeRatio": 1.23,
"PruneCooldown": 60000000000,
"GasLimitOverestimation": 12.3
}
Expand Down

0 comments on commit b7db4cb

Please sign in to comment.