Skip to content

Commit c3285ac

Browse files
committed
fix(fip-0118): correct reward bootstrap and genesis state
1 parent 3088b28 commit c3285ac

10 files changed

Lines changed: 225 additions & 54 deletions

File tree

builtin/methods.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,31 @@ var MethodsCron = struct {
3838
}
3939

4040
var MethodsReward = struct {
41-
Constructor abi.MethodNum
42-
AwardBlockReward abi.MethodNum
43-
ThisEpochReward abi.MethodNum
44-
UpdateNetworkKPI abi.MethodNum
41+
Constructor abi.MethodNum
42+
AwardBlockReward abi.MethodNum
43+
ThisEpochReward abi.MethodNum
44+
UpdateNetworkKPI abi.MethodNum
45+
SetWeightRecordsExported abi.MethodNum
46+
StepWeightRecordsExported abi.MethodNum
47+
RegisterStreamExported abi.MethodNum
48+
RemoveStreamExported abi.MethodNum
49+
SetDistributionExported abi.MethodNum
50+
SetSharesExported abi.MethodNum
51+
CancelPendingExported abi.MethodNum
52+
ClaimExported abi.MethodNum
4553
}{
4654
MethodConstructor,
4755
2,
4856
3,
4957
4,
58+
MustGenerateFRCMethodNum("SetWeightRecords"),
59+
MustGenerateFRCMethodNum("StepWeightRecords"),
60+
MustGenerateFRCMethodNum("RegisterStream"),
61+
MustGenerateFRCMethodNum("RemoveStream"),
62+
MustGenerateFRCMethodNum("SetDistribution"),
63+
MustGenerateFRCMethodNum("SetShares"),
64+
MustGenerateFRCMethodNum("CancelPending"),
65+
MustGenerateFRCMethodNum("Claim"),
5066
}
5167

5268
var MethodsMultisig = struct {

builtin/v19/migration/reward.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ type rewardMigrator struct {
2323
swaActor address.Address
2424
}
2525

26-
func newRewardMigrator(config RewardMigrationConfig, activationEpoch abi.ChainEpoch, outCodeCID cid.Cid) (*rewardMigrator, error) {
27-
streams, accruals, err := reward19.ValidateMigrationStreams(config.Streams, activationEpoch)
26+
func newRewardMigrator(config RewardMigrationConfig, outCodeCID cid.Cid) (*rewardMigrator, error) {
27+
streams, accruals, err := reward19.ValidateMigrationStreams(config.Streams, config.ActivationEpoch)
2828
if err != nil {
2929
return nil, xerrors.Errorf("invalid reward migration streams: %w", err)
3030
}

builtin/v19/migration/reward_test.go

Lines changed: 99 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
reward18 "github.com/filecoin-project/go-state-types/builtin/v18/reward"
1212
smoothing18 "github.com/filecoin-project/go-state-types/builtin/v18/util/smoothing"
1313
reward19 "github.com/filecoin-project/go-state-types/builtin/v19/reward"
14+
adt19 "github.com/filecoin-project/go-state-types/builtin/v19/util/adt"
1415
"github.com/filecoin-project/go-state-types/migration"
1516
"github.com/ipfs/go-cid"
1617
cbor "github.com/ipfs/go-ipld-cbor"
@@ -28,6 +29,7 @@ func validRewardMigrationConfig(t *testing.T, activationEpoch abi.ChainEpoch) Re
2829
t.Helper()
2930
pct := reward19.Denom / 100
3031
return RewardMigrationConfig{
32+
ActivationEpoch: activationEpoch,
3133
SWATimelockEpochs: 20_160,
3234
SWAActor: migrationIDAddress(t, 100),
3335
Streams: []reward19.RegisterStreamParams{
@@ -74,7 +76,7 @@ func TestRewardMigration(t *testing.T) {
7476
activationEpoch := abi.ChainEpoch(100)
7577
config := validRewardMigrationConfig(t, activationEpoch)
7678
outCodeCID := cid.MustParse("bafy2bzaca4aaaaaaaaaqk")
77-
migrator, err := newRewardMigrator(config, activationEpoch, outCodeCID)
79+
migrator, err := newRewardMigrator(config, outCodeCID)
7880
req.NoError(err)
7981
result, err := migrator.MigrateState(ctx, store, migration.ActorMigrationInput{Address: address.TestAddress, Head: inHead})
8082
req.NoError(err)
@@ -98,18 +100,90 @@ func TestRewardMigration(t *testing.T) {
98100
req.Equal(config.SWATimelockEpochs, outState.SWATimelockEpochs)
99101
req.Equal(config.SWAActor, outState.SWAActor)
100102

101-
var streams reward19.StreamsState
102-
req.NoError(store.Get(ctx, outState.StreamsRoot, &streams))
103+
streams, err := outState.LoadStreams(adt19.WrapStore(ctx, store))
104+
req.NoError(err)
103105
req.Len(streams.Streams, 2)
104106
req.Equal(reward19.StreamID(1), streams.Streams[0].ID)
107+
req.Equal(config.Streams[0].Weight, streams.Streams[0].Weight)
105108
req.Nil(streams.Streams[0].Distribution)
106109
req.Equal(reward19.StreamID(2), streams.Streams[1].ID)
110+
req.Equal(config.Streams[1].Weight, streams.Streams[1].Weight)
107111
req.Equal(config.Streams[1].Distribution.Writer, streams.Streams[1].Distribution.Writer)
108112
req.Equal(config.Streams[1].Distribution.Shares, streams.Streams[1].Distribution.Shares)
109113
req.Empty(streams.Tombstones)
110114
req.Empty(streams.PendingWrites)
111115
}
112116

117+
func TestRewardMigrationDropsStoredRewardTotals(t *testing.T) {
118+
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
119+
defer cancel()
120+
req := require.New(t)
121+
store := cbor.NewMemCborStore()
122+
activationEpoch := abi.ChainEpoch(100)
123+
migrator, err := newRewardMigrator(
124+
validRewardMigrationConfig(t, activationEpoch),
125+
cid.MustParse("bafy2bzaca4aaaaaaaaaqk"),
126+
)
127+
req.NoError(err)
128+
129+
base := reward18.State{
130+
CumsumBaseline: big.NewInt(1),
131+
CumsumRealized: big.NewInt(2),
132+
EffectiveNetworkTime: 3,
133+
EffectiveBaselinePower: big.NewInt(4),
134+
ThisEpochReward: abi.NewTokenAmount(5),
135+
ThisEpochRewardSmoothed: smoothing18.NewEstimate(big.NewInt(6), big.NewInt(7)),
136+
ThisEpochBaselinePower: big.NewInt(8),
137+
Epoch: 9,
138+
TotalStoragePowerReward: abi.NewTokenAmount(10),
139+
SimpleTotal: abi.NewTokenAmount(11),
140+
BaselineTotal: abi.NewTokenAmount(12),
141+
}
142+
alternate := base
143+
alternate.SimpleTotal = abi.NewTokenAmount(13)
144+
alternate.BaselineTotal = abi.NewTokenAmount(14)
145+
req.NotEqual(base.SimpleTotal, alternate.SimpleTotal)
146+
req.NotEqual(base.BaselineTotal, alternate.BaselineTotal)
147+
148+
migrate := func(input reward18.State) reward19.State {
149+
head, err := store.Put(ctx, &input)
150+
req.NoError(err)
151+
result, err := migrator.MigrateState(ctx, store, migration.ActorMigrationInput{
152+
Address: address.TestAddress,
153+
Head: head,
154+
})
155+
req.NoError(err)
156+
var output reward19.State
157+
req.NoError(store.Get(ctx, result.NewHead, &output))
158+
return output
159+
}
160+
161+
req.Equal(migrate(base), migrate(alternate))
162+
}
163+
164+
func TestNewRewardMigratorAcceptsAlternativeBootstrapWeights(t *testing.T) {
165+
activationEpoch := abi.ChainEpoch(100)
166+
pct := reward19.Denom / 100
167+
config := validRewardMigrationConfig(t, activationEpoch)
168+
config.Streams[0].Weight = reward19.WeightRecord{
169+
VStart: 80 * pct,
170+
Slope: -1,
171+
TStart: activationEpoch,
172+
Floor: 60 * pct,
173+
Cap: 80 * pct,
174+
}
175+
config.Streams[1].Weight = reward19.WeightRecord{
176+
VStart: 20 * pct,
177+
Slope: 1,
178+
TStart: activationEpoch,
179+
Floor: 10 * pct,
180+
Cap: 20 * pct,
181+
}
182+
183+
_, err := newRewardMigrator(config, cid.MustParse("bafy2bzaca4aaaaaaaaaqk"))
184+
require.NoError(t, err)
185+
}
186+
113187
func TestNewRewardMigratorRejectsInvalidConfig(t *testing.T) {
114188
activationEpoch := abi.ChainEpoch(100)
115189
outCodeCID := cid.MustParse("bafy2bzaca4aaaaaaaaaqk")
@@ -140,11 +214,11 @@ func TestNewRewardMigratorRejectsInvalidConfig(t *testing.T) {
140214
expected: "requires exactly two streams",
141215
},
142216
{
143-
name: "wrong bootstrap weight",
217+
name: "starting weights under-sum",
144218
mutate: func(config *RewardMigrationConfig) {
145-
config.Streams[0].Weight.VStart--
219+
config.Streams[1].Weight.VStart--
146220
},
147-
expected: "consensus bootstrap weight is invalid",
221+
expected: "bootstrap starting weights must sum to denominator",
148222
},
149223
{
150224
name: "unequal slopes",
@@ -176,13 +250,31 @@ func TestNewRewardMigratorRejectsInvalidConfig(t *testing.T) {
176250
},
177251
expected: "SWA actor is not an ID address",
178252
},
253+
{
254+
name: "non-ID SRA actor",
255+
mutate: func(config *RewardMigrationConfig) {
256+
addr, err := address.NewDelegatedAddress(10, []byte{1})
257+
require.NoError(t, err)
258+
config.Streams[1].Distribution.Writer = addr
259+
},
260+
expected: "distribution writer",
261+
},
262+
{
263+
name: "non-ID initial orchestrator",
264+
mutate: func(config *RewardMigrationConfig) {
265+
addr, err := address.NewDelegatedAddress(10, []byte{1})
266+
require.NoError(t, err)
267+
config.Streams[1].Distribution.Shares[0].Recipient = addr
268+
},
269+
expected: "share recipient",
270+
},
179271
}
180272

181273
for _, tc := range testCases {
182274
t.Run(tc.name, func(t *testing.T) {
183275
config := validRewardMigrationConfig(t, activationEpoch)
184276
tc.mutate(&config)
185-
_, err := newRewardMigrator(config, activationEpoch, outCodeCID)
277+
_, err := newRewardMigrator(config, outCodeCID)
186278
require.ErrorContains(t, err, tc.expected)
187279
})
188280
}

builtin/v19/migration/top.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
)
2222

2323
type RewardMigrationConfig struct {
24+
ActivationEpoch abi.ChainEpoch
2425
SWATimelockEpochs abi.ChainEpoch
2526
SWAActor address.Address
2627
Streams []reward19.RegisterStreamParams
@@ -108,7 +109,7 @@ func MigrateStateTree(ctx context.Context, store cbor.IpldStore, newManifestCID
108109
if !ok {
109110
return cid.Undef, xerrors.Errorf("code cid for reward actor not found in new manifest")
110111
}
111-
rewardMigrator, err := newRewardMigrator(rewardConfig, priorEpoch+1, reward19CID)
112+
rewardMigrator, err := newRewardMigrator(rewardConfig, reward19CID)
112113
if err != nil {
113114
return cid.Undef, xerrors.Errorf("failed to create reward migrator: %w", err)
114115
}

builtin/v19/reward/invariants.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ func CheckStateInvariants(st *State, store adt.Store, priorEpoch abi.ChainEpoch,
2727
acc.Require(st.EffectiveNetworkTime <= st.Epoch, "effective network time %d greater than state epoch %d", st.EffectiveNetworkTime, st.Epoch)
2828
acc.Require(st.CumsumRealized.LessThanEqual(st.CumsumBaseline), "cumsum realized %v > cumsum baseline %v", st.CumsumRealized, st.CumsumBaseline)
2929
acc.Require(st.CumsumRealized.GreaterThanEqual(big.Zero()), "cumsum realized negative (%v)", st.CumsumRealized)
30-
acc.Require(st.EffectiveBaselinePower.LessThanEqual(st.ThisEpochBaselinePower), "effective baseline power > baseline power")
30+
// Before the first state transition, the effective baseline is anchored at
31+
// epoch 0 while ThisEpochBaselinePower is anchored at epoch -1.
32+
baselineOrdered := st.Epoch == 0 && st.EffectiveNetworkTime == 0 ||
33+
st.EffectiveBaselinePower.LessThanEqual(st.ThisEpochBaselinePower)
34+
acc.Require(baselineOrdered, "effective baseline power > baseline power")
3135

3236
for _, total := range []struct {
3337
name string
@@ -55,8 +59,8 @@ func CheckStateInvariants(st *State, store adt.Store, priorEpoch abi.ChainEpoch,
5559
acc.Require(row.Amount.GreaterThanEqual(big.Zero()), "explicit-stream accrual for stream %d is negative (%v)", row.ID, row.Amount)
5660
}
5761

58-
var streams StreamsState
59-
if err := store.Get(store.Context(), st.StreamsRoot, &streams); err != nil {
62+
streams, err := st.LoadStreams(store)
63+
if err != nil {
6064
acc.Addf("error loading streams state: %v", err)
6165
return &StateSummary{}, acc
6266
}
@@ -67,7 +71,7 @@ func CheckStateInvariants(st *State, store adt.Store, priorEpoch abi.ChainEpoch,
6771
PendingWriteCount: len(streams.PendingWrites),
6872
}
6973
// Mirrors actors/reward/src/streams.rs::validate_streams_state.
70-
if err := validateStreamsState(&streams, st.Accrued, priorEpoch+1); err != nil {
74+
if err := validateStreamsState(streams, st.Accrued, priorEpoch+1); err != nil {
7175
acc.Addf("invalid streams state: %v", err)
7276
}
7377

@@ -122,7 +126,7 @@ func CheckStateInvariants(st *State, store adt.Store, priorEpoch abi.ChainEpoch,
122126
}
123127

124128
// Mirrors actors/reward/src/streams.rs::compute_service_liability.
125-
liabilities, err := computeExplicitLiability(&streams, st.Accrued)
129+
liabilities, err := computeExplicitLiability(streams, st.Accrued)
126130
if err != nil {
127131
acc.Addf("error computing explicit-stream liabilities: %v", err)
128132
} else {

builtin/v19/reward/invariants_test.go

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,18 @@ import (
2121
func validInvariantState(t *testing.T) (*State, *StreamsState, adt.Store) {
2222
t.Helper()
2323
store := adt.WrapStore(context.Background(), cbor.NewCborStore(test_util.NewBlockStoreInMemory()))
24-
st, err := ConstructState(store, big.Zero())
25-
require.NoError(t, err)
26-
st.EffectiveBaselinePower = big.Zero()
24+
st := &State{
25+
CumsumBaseline: big.Zero(),
26+
CumsumRealized: big.Zero(),
27+
EffectiveBaselinePower: big.Zero(),
28+
ThisEpochReward: big.Zero(),
29+
ThisEpochBaselinePower: big.Zero(),
30+
Epoch: 0,
31+
TotalMintedReward: big.Zero(),
32+
TotalBurnMinted: big.Zero(),
33+
TotalExplicitMinted: big.Zero(),
34+
SWAActor: idAddress(t, 99),
35+
}
2736

2837
pct := Denom / 100
2938
streams := &StreamsState{
@@ -79,6 +88,32 @@ func amountRows(t *testing.T, first, count uint64) []RecipientAmount {
7988
return rows
8089
}
8190

91+
func TestConstructState(t *testing.T) {
92+
store := adt.WrapStore(context.Background(), cbor.NewCborStore(test_util.NewBlockStoreInMemory()))
93+
st, err := ConstructState(store, big.Zero())
94+
require.NoError(t, err)
95+
96+
streams, err := st.LoadStreams(store)
97+
require.NoError(t, err)
98+
require.Equal(t, []Stream{{
99+
ID: 1,
100+
Weight: WeightRecord{
101+
VStart: Denom,
102+
TStart: -1,
103+
Floor: Denom,
104+
Cap: Denom,
105+
},
106+
}}, streams.Streams)
107+
require.Empty(t, st.Accrued)
108+
require.Empty(t, streams.Tombstones)
109+
require.Empty(t, streams.PendingWrites)
110+
require.Equal(t, builtin.SystemActorAddr, st.SWAActor)
111+
112+
summary, acc := CheckStateInvariants(st, store, st.Epoch-1, StorageMiningAllocationCheck)
113+
require.Empty(t, acc.Messages())
114+
require.Equal(t, &StateSummary{StreamCount: 1}, summary)
115+
}
116+
82117
func TestCheckStateInvariants(t *testing.T) {
83118
st, _, store := validInvariantState(t)
84119
summary, acc := CheckStateInvariants(st, store, st.Epoch-1, StorageMiningAllocationCheck)

0 commit comments

Comments
 (0)