// stm:#unit package rand_test import ( "context" "testing" "github.com/stretchr/testify/require" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/network" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/lotus/chain/gen" "github.com/filecoin-project/lotus/chain/rand" "github.com/filecoin-project/lotus/chain/stmgr" ) func init() { policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1) policy.SetConsensusMinerMinPower(abi.NewStoragePower(2048)) policy.SetMinVerifiedDealSize(abi.NewStoragePower(256)) } // in v12 and before, if the tipset corresponding to round X is null, we fetch the latest beacon entry BEFORE X that's in a non-null ts func TestNullRandomnessV1(t *testing.T) { ctx := context.Background() cg, err := gen.NewGenerator() if err != nil { t.Fatal(err) } for i := 0; i < 10; i++ { _, err := cg.NextTipSet() if err != nil { t.Fatal(err) } } offset := cg.CurTipset.Blocks[0].Header.BeaconEntries[len(cg.CurTipset.Blocks[0].Header.BeaconEntries)-1].Round - uint64(cg.CurTipset.TipSet().Height()) beforeNullHeight := cg.CurTipset.TipSet().Height() ts, err := cg.NextTipSetWithNulls(5) if err != nil { t.Fatal(err) } entropy := []byte{0, 2, 3, 4} // arbitrarily chosen pers := crypto.DomainSeparationTag_WinningPoStChallengeSeed randEpoch := ts.TipSet.TipSet().Height() - 2 //stm: @BLOCKCHAIN_RAND_GET_BEACON_RANDOMNESS_V1_01, @BLOCKCHAIN_RAND_EXTRACT_BEACON_ENTRY_FOR_EPOCH_01, @BLOCKCHAIN_RAND_GET_BEACON_RANDOMNESS_TIPSET_02 rand1, err := cg.StateManager().GetRandomnessFromBeacon(ctx, pers, randEpoch, entropy, ts.TipSet.TipSet().Key()) if err != nil { t.Fatal(err) } //stm: @BLOCKCHAIN_BEACON_GET_BEACON_FOR_EPOCH_01 bch := cg.BeaconSchedule().BeaconForEpoch(randEpoch).Entry(ctx, uint64(beforeNullHeight)+offset) select { case resp := <-bch: if resp.Err != nil { t.Fatal(resp.Err) } //stm: @BLOCKCHAIN_RAND_DRAW_RANDOMNESS_01 rand2, err := rand.DrawRandomness(resp.Entry.Data, pers, randEpoch, entropy) if err != nil { t.Fatal(err) } require.Equal(t, rand1, abi.Randomness(rand2)) case <-ctx.Done(): t.Fatal("timed out") } } // at v13, if the tipset corresponding to round X is null, we fetch the latest beacon in the first non-null ts after X func TestNullRandomnessV2(t *testing.T) { ctx := context.Background() sched := stmgr.UpgradeSchedule{ { // prepare for upgrade. Network: network.Version9, Height: 1, Migration: filcns.UpgradeActorsV2, }, { Network: network.Version10, Height: 2, Migration: filcns.UpgradeActorsV3, }, { Network: network.Version12, Height: 3, Migration: filcns.UpgradeActorsV4, }, { Network: network.Version13, Height: 4, Migration: filcns.UpgradeActorsV5, }, } cg, err := gen.NewGeneratorWithUpgradeSchedule(sched) if err != nil { t.Fatal(err) } for i := 0; i < 10; i++ { _, err := cg.NextTipSet() if err != nil { t.Fatal(err) } } offset := cg.CurTipset.Blocks[0].Header.BeaconEntries[len(cg.CurTipset.Blocks[0].Header.BeaconEntries)-1].Round - uint64(cg.CurTipset.TipSet().Height()) ts, err := cg.NextTipSetWithNulls(5) if err != nil { t.Fatal(err) } entropy := []byte{0, 2, 3, 4} // arbitrarily chosen pers := crypto.DomainSeparationTag_WinningPoStChallengeSeed randEpoch := ts.TipSet.TipSet().Height() - 2 //stm: @BLOCKCHAIN_RAND_GET_BEACON_RANDOMNESS_V2_01 rand1, err := cg.StateManager().GetRandomnessFromBeacon(ctx, pers, randEpoch, entropy, ts.TipSet.TipSet().Key()) if err != nil { t.Fatal(err) } //stm: @BLOCKCHAIN_BEACON_GET_BEACON_FOR_EPOCH_01 bch := cg.BeaconSchedule().BeaconForEpoch(randEpoch).Entry(ctx, uint64(ts.TipSet.TipSet().Height())+offset) select { case resp := <-bch: if resp.Err != nil { t.Fatal(resp.Err) } //stm: @BLOCKCHAIN_RAND_DRAW_RANDOMNESS_01, @BLOCKCHAIN_RAND_EXTRACT_BEACON_ENTRY_FOR_EPOCH_01, @BLOCKCHAIN_RAND_GET_BEACON_RANDOMNESS_TIPSET_03 // note that the randEpoch passed to DrawRandomness is still randEpoch (not the latest ts height) rand2, err := rand.DrawRandomness(resp.Entry.Data, pers, randEpoch, entropy) if err != nil { t.Fatal(err) } require.Equal(t, rand1, abi.Randomness(rand2)) case <-ctx.Done(): t.Fatal("timed out") } } // after v14, if the tipset corresponding to round X is null, we still fetch the randomness for X (from the next non-null tipset) func TestNullRandomnessV3(t *testing.T) { ctx := context.Background() sched := stmgr.UpgradeSchedule{ { // prepare for upgrade. Network: network.Version9, Height: 1, Migration: filcns.UpgradeActorsV2, }, { Network: network.Version10, Height: 2, Migration: filcns.UpgradeActorsV3, }, { Network: network.Version12, Height: 3, Migration: filcns.UpgradeActorsV4, }, { Network: network.Version13, Height: 4, Migration: filcns.UpgradeActorsV5, }, { Network: network.Version14, Height: 5, Migration: filcns.UpgradeActorsV6, }, } cg, err := gen.NewGeneratorWithUpgradeSchedule(sched) if err != nil { t.Fatal(err) } for i := 0; i < 10; i++ { _, err := cg.NextTipSet() if err != nil { t.Fatal(err) } } ts, err := cg.NextTipSetWithNulls(5) if err != nil { t.Fatal(err) } offset := cg.CurTipset.Blocks[0].Header.BeaconEntries[len(cg.CurTipset.Blocks[0].Header.BeaconEntries)-1].Round - uint64(cg.CurTipset.TipSet().Height()) entropy := []byte{0, 2, 3, 4} // arbitrarily chosen pers := crypto.DomainSeparationTag_WinningPoStChallengeSeed randEpoch := ts.TipSet.TipSet().Height() - 2 //stm: @BLOCKCHAIN_RAND_GET_BEACON_RANDOMNESS_V3_01, @BLOCKCHAIN_RAND_EXTRACT_BEACON_ENTRY_FOR_EPOCH_01 rand1, err := cg.StateManager().GetRandomnessFromBeacon(ctx, pers, randEpoch, entropy, ts.TipSet.TipSet().Key()) if err != nil { t.Fatal(err) } //stm: @BLOCKCHAIN_BEACON_GET_BEACON_FOR_EPOCH_01 bch := cg.BeaconSchedule().BeaconForEpoch(randEpoch).Entry(ctx, uint64(randEpoch)+offset) select { case resp := <-bch: if resp.Err != nil { t.Fatal(resp.Err) } //stm: @BLOCKCHAIN_RAND_DRAW_RANDOMNESS_01 rand2, err := rand.DrawRandomness(resp.Entry.Data, pers, randEpoch, entropy) if err != nil { t.Fatal(err) } require.Equal(t, rand1, abi.Randomness(rand2)) case <-ctx.Done(): t.Fatal("timed out") } }