Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/submodule/eth/eth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,12 @@ func TestReward(t *testing.T) {
{maxFeePerGas: big.NewInt(600), maxPriorityFeePerGas: big.NewInt(500), answer: big.NewInt(500)},
{maxFeePerGas: big.NewInt(600), maxPriorityFeePerGas: big.NewInt(600), answer: big.NewInt(500)},
{maxFeePerGas: big.NewInt(600), maxPriorityFeePerGas: big.NewInt(1000), answer: big.NewInt(500)},
{maxFeePerGas: big.NewInt(50), maxPriorityFeePerGas: big.NewInt(200), answer: big.NewInt(-50)},
Comment thread
LinZexiao marked this conversation as resolved.
{maxFeePerGas: big.NewInt(50), maxPriorityFeePerGas: big.NewInt(200), answer: big.Zero()},
}
for _, tc := range testcases {
msg := &types.Message{GasFeeCap: tc.maxFeePerGas, GasPremium: tc.maxPriorityFeePerGas}
reward := msg.EffectiveGasPremium(baseFee)
require.Equal(t, 0, reward.Int.Cmp(tc.answer.Int), reward, tc.answer)
require.True(t, big.Cmp(reward, tc.answer) == 0, "reward: %v, answer: %v", reward, tc.answer)
}
}

Expand Down
5 changes: 5 additions & 0 deletions pkg/chain/export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package chain

// Export internal functions for testing
var NextBaseFeeFromPremium = nextBaseFeeFromPremium
var WeightedQuickSelectInternal = weightedQuickSelect
131 changes: 129 additions & 2 deletions pkg/chain/message_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import (
"bytes"
"context"
"fmt"
"math/rand"
"time"

"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
cbor2 "github.com/filecoin-project/go-state-types/cbor"
"github.com/filecoin-project/specs-actors/actors/util/adt"
xerrors "golang.org/x/xerrors"

blockstore "github.com/ipfs/boxo/blockstore"
blocks "github.com/ipfs/go-block-format"
Expand Down Expand Up @@ -565,15 +568,22 @@ func ComputeNextBaseFee(baseFee abi.TokenAmount, gasLimitUsed int64, noOfBlocks

// todo move to a more suitable position
func (ms *MessageStore) ComputeBaseFee(ctx context.Context, ts *types.TipSet, upgrade *config.ForkUpgradeConfig) (abi.TokenAmount, error) {
zero := abi.NewTokenAmount(0)
baseHeight := ts.Height()

if upgrade.UpgradeBreezeHeight >= 0 && baseHeight > upgrade.UpgradeBreezeHeight && baseHeight < upgrade.UpgradeBreezeHeight+upgrade.BreezeGasTampingDuration {
return abi.NewTokenAmount(100), nil
}
if baseHeight < upgrade.UpgradeFireHorseHeight {
return ms.ComputeNextBaseFeeFromUtilization(ctx, ts, upgrade)
}
return ms.ComputeNextBaseFeeFromPremiums(ctx, ts)
}

func (ms *MessageStore) ComputeNextBaseFeeFromUtilization(ctx context.Context, ts *types.TipSet, upgrade *config.ForkUpgradeConfig) (abi.TokenAmount, error) {
// totalLimit is sum of GasLimits of unique messages in a tipset
totalLimit := int64(0)
zero := abi.NewTokenAmount(0)

baseHeight := ts.Height()

seen := make(map[cid.Cid]struct{})

Expand Down Expand Up @@ -604,6 +614,123 @@ func (ms *MessageStore) ComputeBaseFee(ctx context.Context, ts *types.TipSet, up
return ComputeNextBaseFee(parentBaseFee, totalLimit, len(ts.Blocks()), baseHeight, upgrade), nil
}

type SenderNonce struct {
Sender address.Address
Nonce uint64
}

func (ms *MessageStore) ComputeNextBaseFeeFromPremiums(ctx context.Context, ts *types.TipSet) (abi.TokenAmount, error) {
zero := abi.NewTokenAmount(0)
parentBaseFee := ts.Blocks()[0].ParentBaseFee
var premiums []abi.TokenAmount
var limits []int64
seen := make(map[SenderNonce]struct{})

for _, b := range ts.Blocks() {
secpMsgs, blsMsgs, err := ms.LoadMetaMessages(ctx, b.Messages)
if err != nil {
return zero, xerrors.Errorf("error getting messages for: %s: %w", b.Cid(), err)
}
for _, msg := range blsMsgs {
senderNonce := SenderNonce{msg.From, msg.Nonce}
if _, ok := seen[senderNonce]; !ok {
limits = append(limits, msg.GasLimit)
premiums = append(premiums, msg.EffectiveGasPremium(parentBaseFee))
seen[senderNonce] = struct{}{}
}
}
for _, signed := range secpMsgs {
senderNonce := SenderNonce{signed.Message.From, signed.Message.Nonce}
if _, ok := seen[senderNonce]; !ok {
limits = append(limits, signed.Message.GasLimit)
premiums = append(premiums, signed.Message.EffectiveGasPremium(parentBaseFee))
seen[senderNonce] = struct{}{}
}
}
}

percentilePremium := WeightedQuickSelect(premiums, limits, constants.BlockGasTargetIndex)

return nextBaseFeeFromPremium(parentBaseFee, percentilePremium), nil
}

func nextBaseFeeFromPremium(baseFee, premiumP abi.TokenAmount) abi.TokenAmount {
denom := big.NewInt(constants.BaseFeeMaxChangeDenom)
maxAdj := big.Div(big.Add(baseFee, big.Sub(denom, big.NewInt(1))), denom)
return big.Max(
big.NewInt(constants.MinimumBaseFee),
big.Add(
baseFee,
big.Min(maxAdj, big.Sub(premiumP, maxAdj)),
),
)
}

type RandInt interface {
Intn(n int) int
}

func WeightedQuickSelect(premiums []abi.TokenAmount, limits []int64, index int64) abi.TokenAmount {
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
return weightedQuickSelect(premiums, limits, index, rng)
}
Comment thread
LinZexiao marked this conversation as resolved.

func weightedQuickSelect(premiums []abi.TokenAmount, limits []int64, index int64, randImpl RandInt) abi.TokenAmount {
for {
if len(premiums) == 0 {
return big.Zero()
}
if len(premiums) == 1 {
if limits[0] <= index {
return big.Zero()
}
return premiums[0]
}

pivot := premiums[randImpl.Intn(len(premiums))]

var less []abi.TokenAmount
var lessWeights []int64
var lessW int64
var eqW int64
var more []abi.TokenAmount
var moreWeights []int64
var moreW int64

for i, premium := range premiums {
cmp := big.Cmp(premium, pivot)
if cmp < 0 {
less = append(less, premium)
lessWeights = append(lessWeights, limits[i])
lessW += limits[i]
} else if cmp == 0 {
eqW += limits[i]
} else {
more = append(more, premium)
moreWeights = append(moreWeights, limits[i])
moreW += limits[i]
}
}

if index < moreW {
premiums = more
limits = moreWeights
continue
}
index -= moreW
if index < eqW {
return pivot
}
index -= eqW
if index < lessW {
premiums = less
limits = lessWeights
continue
}
return big.Zero()
}
}

func GetReceiptRoot(receipts []types.MessageReceipt) (cid.Cid, error) {
bs := blockstore.NewBlockstore(datastore.NewMapDatastore())
as := cbor.NewCborStore(bs)
Expand Down
124 changes: 123 additions & 1 deletion pkg/chain/message_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ import (
"io"
"testing"

"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/venus/venus-shared/testutil"
"github.com/ipfs/go-cid"
cbg "github.com/whyrusleeping/cbor-gen"

"github.com/filecoin-project/specs-actors/actors/util/adt"
"github.com/filecoin-project/venus/pkg/chain"
"github.com/filecoin-project/venus/pkg/config"
"github.com/filecoin-project/venus/pkg/constants"
"github.com/filecoin-project/venus/pkg/testhelpers"
"github.com/filecoin-project/venus/pkg/testhelpers/testflags"
blockstoreutil "github.com/filecoin-project/venus/venus-shared/blockstore"
Expand Down Expand Up @@ -92,7 +95,7 @@ func TestMessageStoreMessagesHappy(t *testing.T) {

as := cbor.NewCborStore(bs)

var goodCid = signedMsgs[0].Cid()
goodCid := signedMsgs[0].Cid()

secpMsgArr := adt.MakeEmptyArray(adt.WrapStore(ctx, as))
assert.NoError(t, secpMsgArr.Set(0, (*cbg.CborCid)(&goodCid)))
Expand Down Expand Up @@ -229,3 +232,122 @@ func TestMessageStoreLoadMessage(t *testing.T) {
_, err = ms.LoadUnsignedMessagesFromCids(ctx, []cid.Cid{badMsgCID})
assert.Error(t, err)
}

// Test randomized algorithm by trying all permutations
type AllPermutations struct {
t *testing.T
// current level
level int
// known domain (n) at current level
domain []int
// current iteration positions (i < n) in the domain
dfs []int
}

func (p *AllPermutations) Reset() {
// same outputs should result in same inputs
assert.Equal(p.t, p.level, len(p.domain), "nondeterminism detected (fewer queries)")

for len(p.dfs) > 0 && p.dfs[len(p.dfs)-1]+1 == p.domain[len(p.domain)-1] {
// pop finished domains
p.domain = p.domain[:len(p.domain)-1]
p.dfs = p.dfs[:len(p.dfs)-1]
}
if len(p.dfs) > 0 {
p.dfs[len(p.dfs)-1]++
}
// next iteration in the permutation
p.level = 0
}

func (p *AllPermutations) Done() bool {
return len(p.domain) == 0
}

func (p *AllPermutations) Intn(n int) int {
assert.True(p.t, n > 0, "Intn(0)")
if p.level < len(p.domain) {
// same outputs should result in same inputs
assert.Equal(p.t, p.domain[p.level], n, "nondeterminism detected (different queries)")
} else {
// expand search domain
p.domain = append(p.domain, n)
p.dfs = append(p.dfs, 0)
}
p.level++
return p.dfs[p.level-1]
}

// TestWeightedQuickSelect tests the tipset gas percentile cases.
// BlockGasLimit = 10_000_000_000, P = 20.
func TestWeightedQuickSelect(t *testing.T) {
tests := []struct {
premiums []int64
limits []int64
expected int64
}{
{[]int64{}, []int64{}, 0},
{[]int64{123, 100}, []int64{5_999_999_999, 2_000_000_000}, 0},
{[]int64{123, 0}, []int64{5_999_999_999, 2_000_000_001}, 0},
{[]int64{123, 100}, []int64{5_999_999_999, 2_000_000_001}, 100},
{[]int64{123, 100}, []int64{7_999_999_999, 2_000_000_001}, 100},
{[]int64{123, 100}, []int64{8_000_000_000, 2_000_000_000}, 123},
{[]int64{123, 100}, []int64{8_000_000_000, 9_000_000_000}, 123},
{[]int64{100, 200, 300, 400, 500, 600, 700}, []int64{4_000_000_000, 1_000_000_000, 2_000_000_000, 1_000_000_000, 2_000_000_000, 2_000_000_000, 3_000_000_000}, 400},
}
for _, tc := range tests {
premiums := make([]abi.TokenAmount, len(tc.premiums))
for i, p := range tc.premiums {
premiums[i] = big.NewInt(p)
}
rand := &AllPermutations{}
rand.t = t
for {
got := chain.WeightedQuickSelectInternal(premiums, tc.limits, constants.BlockGasTargetIndex, rand)
assert.Equal(t, big.NewInt(tc.expected).String(), got.String(),
"premiums=%v limits=%v", tc.premiums, tc.limits)
rand.Reset()
if rand.Done() {
break
}
}
}
}

// TestNextBaseFeeFromPremium tests the BaseFee_next formula:
// MaxAdj = ceil(BaseFee / 8)
// BaseFee_next = Max(MinBaseFee, BaseFee + Min(MaxAdj, Premium_P - MaxAdj))
func TestNextBaseFeeFromPremium(t *testing.T) {
tests := []struct {
baseFee int64
premiumP int64
expected int64
}{
{100, 0, 100},
{100, 13, 100},
{100, 14, 101},
{100, 26, 113},
{801, 0, 700},
{801, 20, 720},
{801, 40, 740},
{801, 60, 760},
{801, 80, 780},
{801, 100, 800},
{801, 120, 820},
{801, 140, 840},
{801, 160, 860},
{801, 180, 880},
{801, 200, 900},
{801, 201, 901},
{808, 0, 707},
{808, 1, 708},
{808, 201, 908},
{808, 202, 909},
{808, 203, 909},
}
for _, tc := range tests {
got := chain.NextBaseFeeFromPremium(big.NewInt(tc.baseFee), big.NewInt(tc.premiumP))
assert.Equal(t, big.NewInt(tc.expected).String(), got.String(),
"baseFee=%d premiumP=%d", tc.baseFee, tc.premiumP)
}
}
8 changes: 6 additions & 2 deletions pkg/constants/chain_parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@ var ExpectedLeadersPerEpoch = builtin0.ExpectedLeadersPerEpoch

// BlockGasLimit is the maximum amount of gas that can be used to execute messages in a single block.
const (
BlockGasLimit = 10_000_000_000
BlockGasTarget = BlockGasLimit / 2
BlockGasLimit = 10_000_000_000
BlockGasTargetIndex = BlockGasLimit*80/100 - 1

// todo: consider remove this after fip-0115 is activated
BlockGasTarget = BlockGasLimit / 2

BaseFeeMaxChangeDenom = 8 // 12.5%
InitialBaseFee = 100e6
MinimumBaseFee = 100
Expand Down
5 changes: 5 additions & 0 deletions venus-shared/actors/types/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,11 @@ func (m *Message) ValidForBlockInclusion(minGas int64, version network.Version)
// specified premium.
func (m *Message) EffectiveGasPremium(baseFee abi.TokenAmount) abi.TokenAmount {
available := big.Sub(m.GasFeeCap, baseFee)
// It's possible that storage providers may include messages with gasFeeCap less than the baseFee
// In such cases, their reward should be viewed as zero
if big.Cmp(available, big.Zero()) < 0 {
available = big.Zero()
}
if big.Cmp(m.GasPremium, available) <= 0 {
return m.GasPremium
}
Expand Down
33 changes: 33 additions & 0 deletions venus-shared/actors/types/message_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package types

import (
"testing"

"github.com/filecoin-project/go-state-types/big"
"github.com/stretchr/testify/require"
)

func TestEffectiveGasPremium(t *testing.T) {
tests := []struct {
baseFee int64
maxFeePerGas int64
maxPriorityFeePerGas int64
expected int64
}{
{8, 8, 8, 0},
{8, 16, 7, 7},
{8, 19, 10, 10},
{123456, 123455, 123455, 0},
{123456, 1234567, 1111112, 1111111},
}
for _, tc := range tests {
msg := &Message{
GasFeeCap: big.NewInt(tc.maxFeePerGas),
GasPremium: big.NewInt(tc.maxPriorityFeePerGas),
}
got := msg.EffectiveGasPremium(big.NewInt(tc.baseFee))
expected := big.NewInt(tc.expected)
require.True(t, big.Cmp(expected, got) == 0,
"baseFee=%d maxFeePerGas=%d maxPriorityFeePerGas=%d", tc.baseFee, tc.maxFeePerGas, tc.maxPriorityFeePerGas)
}
}
Loading