diff --git a/chain/messagepool/messagepool_test.go b/chain/messagepool/messagepool_test.go index 484c72746fa..c97c031667c 100644 --- a/chain/messagepool/messagepool_test.go +++ b/chain/messagepool/messagepool_test.go @@ -34,6 +34,8 @@ type testMpoolAPI struct { tipsets []*types.TipSet published int + + baseFee types.BigInt } func newTestMpoolAPI() *testMpoolAPI { @@ -41,6 +43,7 @@ func newTestMpoolAPI() *testMpoolAPI { bmsgs: make(map[cid.Cid][]*types.SignedMessage), statenonce: make(map[address.Address]uint64), balance: make(map[address.Address]types.BigInt), + baseFee: types.NewInt(100), } genesis := mock.MkBlock(nil, 1, 1) tma.tipsets = append(tma.tipsets, mock.TipSet(genesis)) @@ -182,7 +185,7 @@ func (tma *testMpoolAPI) LoadTipSet(tsk types.TipSetKey) (*types.TipSet, error) } func (tma *testMpoolAPI) ChainComputeBaseFee(ctx context.Context, ts *types.TipSet) (types.BigInt, error) { - return types.NewInt(100), nil + return tma.baseFee, nil } func assertNonce(t *testing.T, mp *MessagePool, addr address.Address, val uint64) { diff --git a/chain/messagepool/repub.go b/chain/messagepool/repub.go index 1173bdb48b5..5e767cc3649 100644 --- a/chain/messagepool/repub.go +++ b/chain/messagepool/repub.go @@ -75,6 +75,13 @@ func (mp *MessagePool) republishPendingMessages() error { gasLimit := int64(build.BlockGasLimit) minGas := int64(gasguess.MinGas) + + allowNegative := false + if skyHighBaseFeeThreshold.LessThan(baseFee) { + allowNegative = true + gasLimit = int64(float64(gasLimit) * skyHighBaseFeeGasLimitRatio) + } + var msgs []*types.SignedMessage for i := 0; i < len(chains); { chain := chains[i] @@ -91,7 +98,7 @@ func (mp *MessagePool) republishPendingMessages() error { // we don't republish negative performing chains, as they won't be included in // a block anyway - if chain.gasPerf < 0 { + if !allowNegative && chain.gasPerf < 0 { break } @@ -111,7 +118,7 @@ func (mp *MessagePool) republishPendingMessages() error { // we can't fit the current chain but there is gas to spare // trim it and push it down - chain.Trim(gasLimit, mp, baseFee, ts) + chain.Trim(gasLimit, mp, baseFee, allowNegative) for j := i; j < len(chains)-1; j++ { if chains[j].Before(chains[j+1]) { break diff --git a/chain/messagepool/selection.go b/chain/messagepool/selection.go index 5ba679d761a..303a8bfac56 100644 --- a/chain/messagepool/selection.go +++ b/chain/messagepool/selection.go @@ -16,7 +16,12 @@ import ( abig "github.com/filecoin-project/specs-actors/actors/abi/big" ) -var bigBlockGasLimit = big.NewInt(build.BlockGasLimit) +var ( + bigBlockGasLimit = big.NewInt(build.BlockGasLimit) + + skyHighBaseFeeThreshold = abig.NewInt(111902962585) + skyHighBaseFeeGasLimitRatio = 0.4 +) const MaxBlocks = 15 @@ -59,6 +64,11 @@ func (mp *MessagePool) selectMessagesOptimal(curTs, ts *types.TipSet, tq float64 return nil, xerrors.Errorf("computing basefee: %w", err) } + allowNegative := false + if skyHighBaseFeeThreshold.LessThan(baseFee) { + allowNegative = true + } + // 0. Load messages from the target tipset; if it is the same as the current tipset in // the mpool, then this is just the pending messages pending, err := mp.getPendingMessages(curTs, ts) @@ -100,8 +110,8 @@ func (mp *MessagePool) selectMessagesOptimal(curTs, ts *types.TipSet, tq float64 return chains[i].Before(chains[j]) }) - if len(chains) != 0 && chains[0].gasPerf < 0 { - log.Warnw("all messages in mpool have non-positive gas performance", "bestGasPerf", chains[0].gasPerf) + if len(chains) != 0 && !allowNegative && chains[0].gasPerf < 0 { + log.Warnw("all messages in mpool have negative gas performance", "bestGasPerf", chains[0].gasPerf) return nil, nil } @@ -112,6 +122,12 @@ func (mp *MessagePool) selectMessagesOptimal(curTs, ts *types.TipSet, tq float64 partitions := make([][]*msgChain, MaxBlocks) for i := 0; i < MaxBlocks && nextChain < len(chains); i++ { gasLimit := int64(build.BlockGasLimit) + + // if base fee is sky high and we allow negatives, then the blocks will be smaller + if allowNegative { + gasLimit = int64(float64(gasLimit) * skyHighBaseFeeGasLimitRatio) + } + for nextChain < len(chains) { chain := chains[nextChain] nextChain++ @@ -153,7 +169,7 @@ func (mp *MessagePool) selectMessagesOptimal(curTs, ts *types.TipSet, tq float64 last := len(chains) for i, chain := range chains { // did we run out of performing chains? - if chain.gasPerf < 0 { + if !allowNegative && chain.gasPerf < 0 { break } @@ -217,7 +233,7 @@ tailLoop: for gasLimit >= minGas && last < len(chains) { // trim if necessary if chains[last].gasLimit > gasLimit { - chains[last].Trim(gasLimit, mp, baseFee, ts) + chains[last].Trim(gasLimit, mp, baseFee, allowNegative) } // push down if it hasn't been invalidated @@ -243,7 +259,7 @@ tailLoop: } // if gasPerf < 0 we have no more profitable chains - if chain.gasPerf < 0 { + if !allowNegative && chain.gasPerf < 0 { break tailLoop } @@ -284,7 +300,7 @@ tailLoop: } // dependencies fit, just trim it - chain.Trim(gasLimit-depGasLimit, mp, baseFee, ts) + chain.Trim(gasLimit-depGasLimit, mp, baseFee, allowNegative) last += i continue tailLoop } @@ -308,6 +324,11 @@ func (mp *MessagePool) selectMessagesGreedy(curTs, ts *types.TipSet) ([]*types.S return nil, xerrors.Errorf("computing basefee: %w", err) } + allowNegative := false + if skyHighBaseFeeThreshold.LessThan(baseFee) { + allowNegative = true + } + // 0. Load messages for the target tipset; if it is the same as the current tipset in the mpool // then this is just the pending messages pending, err := mp.getPendingMessages(curTs, ts) @@ -349,8 +370,8 @@ func (mp *MessagePool) selectMessagesGreedy(curTs, ts *types.TipSet) ([]*types.S return chains[i].Before(chains[j]) }) - if len(chains) != 0 && chains[0].gasPerf < 0 { - log.Warnw("all messages in mpool have non-positive gas performance", "bestGasPerf", chains[0].gasPerf) + if len(chains) != 0 && !allowNegative && chains[0].gasPerf < 0 { + log.Warnw("all messages in mpool have negative gas performance", "bestGasPerf", chains[0].gasPerf) return nil, nil } @@ -360,7 +381,7 @@ func (mp *MessagePool) selectMessagesGreedy(curTs, ts *types.TipSet) ([]*types.S last := len(chains) for i, chain := range chains { // did we run out of performing chains? - if chain.gasPerf < 0 { + if !allowNegative && chain.gasPerf < 0 { break } @@ -389,7 +410,7 @@ func (mp *MessagePool) selectMessagesGreedy(curTs, ts *types.TipSet) ([]*types.S tailLoop: for gasLimit >= minGas && last < len(chains) { // trim - chains[last].Trim(gasLimit, mp, baseFee, ts) + chains[last].Trim(gasLimit, mp, baseFee, allowNegative) // push down if it hasn't been invalidated if chains[last].valid { @@ -409,7 +430,7 @@ tailLoop: } // if gasPerf < 0 we have no more profitable chains - if chain.gasPerf < 0 { + if !allowNegative && chain.gasPerf < 0 { break tailLoop } @@ -448,6 +469,12 @@ func (mp *MessagePool) selectPriorityMessages(pending map[address.Address]map[ui gasLimit := int64(build.BlockGasLimit) minGas := int64(gasguess.MinGas) + allowNegative := false + if skyHighBaseFeeThreshold.LessThan(baseFee) { + allowNegative = true + gasLimit = int64(float64(gasLimit) * skyHighBaseFeeGasLimitRatio) + } + // 1. Get priority actor chains var chains []*msgChain priority := mp.cfg.PriorityAddrs @@ -471,7 +498,7 @@ func (mp *MessagePool) selectPriorityMessages(pending map[address.Address]map[ui return chains[i].Before(chains[j]) }) - if len(chains) != 0 && chains[0].gasPerf < 0 { + if len(chains) != 0 && !allowNegative && chains[0].gasPerf < 0 { log.Warnw("all priority messages in mpool have negative gas performance", "bestGasPerf", chains[0].gasPerf) return nil, gasLimit } @@ -479,7 +506,7 @@ func (mp *MessagePool) selectPriorityMessages(pending map[address.Address]map[ui // 3. Merge chains until the block limit, as long as they have non-negative gas performance last := len(chains) for i, chain := range chains { - if chain.gasPerf < 0 { + if !allowNegative && chain.gasPerf < 0 { break } @@ -497,7 +524,7 @@ func (mp *MessagePool) selectPriorityMessages(pending map[address.Address]map[ui tailLoop: for gasLimit >= minGas && last < len(chains) { // trim, discarding negative performing messages - chains[last].Trim(gasLimit, mp, baseFee, ts) + chains[last].Trim(gasLimit, mp, baseFee, allowNegative) // push down if it hasn't been invalidated if chains[last].valid { @@ -517,7 +544,7 @@ tailLoop: } // if gasPerf < 0 we have no more profitable chains - if chain.gasPerf < 0 { + if !allowNegative && chain.gasPerf < 0 { break tailLoop } @@ -775,9 +802,9 @@ func (mc *msgChain) Before(other *msgChain) bool { (mc.gasPerf == other.gasPerf && mc.gasReward.Cmp(other.gasReward) > 0) } -func (mc *msgChain) Trim(gasLimit int64, mp *MessagePool, baseFee types.BigInt, ts *types.TipSet) { +func (mc *msgChain) Trim(gasLimit int64, mp *MessagePool, baseFee types.BigInt, allowNegative bool) { i := len(mc.msgs) - 1 - for i >= 0 && (mc.gasLimit > gasLimit || mc.gasPerf < 0) { + for i >= 0 && (mc.gasLimit > gasLimit || (!allowNegative && mc.gasPerf < 0)) { gasReward := mp.getGasReward(mc.msgs[i], baseFee) mc.gasReward = new(big.Int).Sub(mc.gasReward, gasReward) mc.gasLimit -= mc.msgs[i].Message.GasLimit diff --git a/chain/messagepool/selection_test.go b/chain/messagepool/selection_test.go index a9ead3c0129..235ac342b55 100644 --- a/chain/messagepool/selection_test.go +++ b/chain/messagepool/selection_test.go @@ -728,6 +728,116 @@ func TestPriorityMessageSelection2(t *testing.T) { } } +func TestSkyHighBaseFeeMessageSelection(t *testing.T) { + mp, tma := makeTestMpool() + + w1, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a1, err := w1.GenerateKey(crypto.SigTypeSecp256k1) + if err != nil { + t.Fatal(err) + } + + w2, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a2, err := w2.GenerateKey(crypto.SigTypeSecp256k1) + if err != nil { + t.Fatal(err) + } + + block := tma.nextBlock() + ts := mock.TipSet(block) + tma.applyBlock(t, block) + + gasLimit := gasguess.Costs[gasguess.CostKey{Code: builtin.StorageMarketActorCodeID, M: 2}] + + tma.setBalance(a1, 1) // in FIL + tma.setBalance(a2, 1) // in FIL + + // we create 1 block worth of messages; the selection algorithms should only fill the block + // up to the sky high ratio + tma.baseFee = types.BigAdd(skyHighBaseFeeThreshold, types.NewInt(1_000_000)) + skyHighGasLimit := int64(float64(build.BlockGasLimit) * skyHighBaseFeeGasLimitRatio) + + nMessages := int((build.BlockGasLimit / gasLimit) + 1) + for i := 0; i < nMessages; i++ { + bias := (nMessages - i) / 3 + m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(1+i%3+bias)) + mustAdd(t, mp, m) + } + + // test greedy selection + msgs, err := mp.SelectMessages(ts, 1.0) + if err != nil { + t.Fatal(err) + } + + mGasLimit := int64(0) + for _, m := range msgs { + mGasLimit += m.Message.GasLimit + } + + if mGasLimit > skyHighGasLimit { + t.Fatalf("expected block gas limit to be less than sky high gas limit; got %d, limit is %d", + mGasLimit, skyHighGasLimit) + } + + // test optimal selection + msgs, err = mp.SelectMessages(ts, 0.1) + if err != nil { + t.Fatal(err) + } + + mGasLimit = 0 + for _, m := range msgs { + mGasLimit += m.Message.GasLimit + } + + if mGasLimit > skyHighGasLimit { + t.Fatalf("expected block gas limit to be less than sky high gas limit; got %d, limit is %d", + mGasLimit, skyHighGasLimit) + } + + // test priority selection + mp.cfg.PriorityAddrs = []address.Address{a1} + + msgs, err = mp.SelectMessages(ts, 1.0) + if err != nil { + t.Fatal(err) + } + + mGasLimit = int64(0) + for _, m := range msgs { + mGasLimit += m.Message.GasLimit + } + + if mGasLimit > skyHighGasLimit { + t.Fatalf("expected block gas limit to be less than sky high gas limit; got %d, limit is %d", + mGasLimit, skyHighGasLimit) + } + + msgs, err = mp.SelectMessages(ts, 0.1) + if err != nil { + t.Fatal(err) + } + + mGasLimit = 0 + for _, m := range msgs { + mGasLimit += m.Message.GasLimit + } + + if mGasLimit > skyHighGasLimit { + t.Fatalf("expected block gas limit to be less than sky high gas limit; got %d, limit is %d", + mGasLimit, skyHighGasLimit) + } +} + func TestOptimalMessageSelection1(t *testing.T) { // this test uses just a single actor sending messages with a low tq // the chain depenent merging algorithm should pick messages from the actor