From 091d6d21388df42ab7fb1df923acdee4ee4354bf Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Mon, 20 May 2024 19:37:03 +1000 Subject: [PATCH 01/20] test: actors: manual CC onboarding and proving integration test --- .github/workflows/test.yml | 1 + itests/manual_onboarding_test.go | 209 +++++++++++++++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 itests/manual_onboarding_test.go diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4553158f9bc..c26691b165f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -96,6 +96,7 @@ jobs: "itest-get_messages_in_ts": ["self-hosted", "linux", "x64", "xlarge"], "itest-lite_migration": ["self-hosted", "linux", "x64", "xlarge"], "itest-lookup_robust_address": ["self-hosted", "linux", "x64", "xlarge"], + "itest-manual_onboarding": ["self-hosted", "linux", "x64", "xlarge"], "itest-mempool": ["self-hosted", "linux", "x64", "xlarge"], "itest-mpool_msg_uuid": ["self-hosted", "linux", "x64", "xlarge"], "itest-mpool_push_with_uuid": ["self-hosted", "linux", "x64", "xlarge"], diff --git a/itests/manual_onboarding_test.go b/itests/manual_onboarding_test.go new file mode 100644 index 00000000000..b9e484a9d06 --- /dev/null +++ b/itests/manual_onboarding_test.go @@ -0,0 +1,209 @@ +package itests + +import ( + "bytes" + "context" + "testing" + "time" + + "github.com/ipfs/go-cid" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/builtin" + miner14 "github.com/filecoin-project/go-state-types/builtin/v14/miner" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/proof" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/itests/kit" +) + +// Manually onboard CC sectors, bypassing lotus-miner onboarding pathways +func TestManualCCOnboarding(t *testing.T) { + req := require.New(t) + + kit.QuietMiningLogs() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var ( + blocktime = 2 * time.Millisecond + + client kit.TestFullNode + minerA kit.TestMiner // A is a standard genesis miner + minerB kit.TestMiner // B is a CC miner we will onboard manually + ) + + opts := []kit.NodeOpt{kit.WithAllSubsystems()} + ens := kit.NewEnsemble(t, kit.MockProofs()). + FullNode(&client, opts...). + Miner(&minerA, &client, opts...). + Start(). + InterconnectAll() + ens.BeginMining(blocktime) + + opts = append(opts, kit.OwnerAddr(client.DefaultKey)) + ens.Miner(&minerB, &client, opts...).Start() + + maddrA, err := minerA.ActorAddress(ctx) + req.NoError(err) + + build.Clock.Sleep(time.Second) + + t.Log("Submitting PreCommitSector...") + + maddrB, err := minerB.ActorAddress(ctx) + req.NoError(err) + + head, err := client.ChainHead(ctx) + req.NoError(err) + + minerBInfo, err := client.StateMinerInfo(ctx, maddrB, head.Key()) + req.NoError(err) + + preCommitParams := &miner.PreCommitSectorBatchParams2{ + Sectors: []miner.SectorPreCommitInfo{{ + Expiration: 2880 * 300, + SectorNumber: 22, + SealProof: kit.TestSpt, + SealedCID: cid.MustParse("bagboea4b5abcatlxechwbp7kjpjguna6r6q7ejrhe6mdp3lf34pmswn27pkkiekz"), + SealRandEpoch: head.Height() - 200, + }}, + } + + enc := new(bytes.Buffer) + req.NoError(preCommitParams.MarshalCBOR(enc)) + + m, err := client.MpoolPushMessage(ctx, &types.Message{ + To: maddrB, + From: minerB.OwnerKey.Address, + Value: types.FromFil(1), + Method: builtin.MethodsMiner.PreCommitSectorBatch2, + Params: enc.Bytes(), + }, nil) + req.NoError(err) + + r, err := client.StateWaitMsg(ctx, m.Cid(), 2, api.LookbackNoLimit, true) + req.NoError(err) + require.True(t, r.Receipt.ExitCode.IsSuccess()) + + client.WaitTillChain(ctx, kit.HeightAtLeast(r.Height+miner14.PreCommitChallengeDelay+5)) + + t.Log("Checking initial power...") + + p, err := client.StateMinerPower(ctx, maddrA, r.TipSet) + req.NoError(err) + t.Logf("MinerA RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) + + p, err = client.StateMinerPower(ctx, maddrB, r.TipSet) + req.NoError(err) + t.Logf("MinerB RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) + require.True(t, p.MinerPower.RawBytePower.IsZero()) + + t.Log("Submitting ProveCommitSector...") + + bSectorNum := preCommitParams.Sectors[0].SectorNumber + + proveCommitParams := miner14.ProveCommitSectors3Params{ + SectorActivations: []miner14.SectorActivationManifest{{SectorNumber: bSectorNum}}, + SectorProofs: [][]byte{{0xde, 0xad, 0xbe, 0xef}}, + RequireActivationSuccess: true, + } + + enc = new(bytes.Buffer) + req.NoError(proveCommitParams.MarshalCBOR(enc)) + + m, err = client.MpoolPushMessage(ctx, &types.Message{ + To: minerB.ActorAddr, + From: minerB.OwnerKey.Address, + Value: types.FromFil(0), + Method: builtin.MethodsMiner.ProveCommitSectors3, + Params: enc.Bytes(), + }, nil) + req.NoError(err) + + r, err = client.StateWaitMsg(ctx, m.Cid(), 2, api.LookbackNoLimit, true) + req.NoError(err) + require.True(t, r.Receipt.ExitCode.IsSuccess()) + + soi, err := client.StateSectorGetInfo(ctx, maddrB, bSectorNum, r.TipSet) + req.NoError(err) + t.Logf("SectorOnChainInfo %d: %+v", bSectorNum, soi) + + sp, err := client.StateSectorPartition(ctx, maddrB, bSectorNum, r.TipSet) + req.NoError(err) + t.Logf("SectorPartition %d: %+v", bSectorNum, sp) + bSectorDeadline := sp.Deadline + bSectorPartition := sp.Partition + + p, err = client.StateMinerPower(ctx, maddrB, r.TipSet) + req.NoError(err) + t.Logf("MinerB RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) + require.True(t, p.MinerPower.RawBytePower.IsZero()) + + di, err := client.StateMinerProvingDeadline(ctx, maddrB, types.EmptyTSK) + req.NoError(err) + t.Logf("MinerB Deadline Info: %+v", di) + + // Use the current deadline to work out when the deadline we care about (bSectorDeadline) is open + // and ready to receive posts + deadlineCount := di.WPoStPeriodDeadlines + epochsPerDeadline := uint64(di.WPoStChallengeWindow) + currentDeadline := di.Index + currentDeadlineStart := di.Open + waitTillEpoch := abi.ChainEpoch((deadlineCount-currentDeadline+bSectorDeadline)*epochsPerDeadline) + currentDeadlineStart - di.WPoStProvingPeriod + 1 + + t.Logf("Waiting %d until epoch %d to get to deadline %d", waitTillEpoch-di.CurrentEpoch, waitTillEpoch, bSectorDeadline) + client.WaitTillChain(ctx, kit.HeightAtLeast(waitTillEpoch)) + + // We should be up to the deadline we care about + di, err = client.StateMinerProvingDeadline(ctx, maddrB, types.EmptyTSK) + req.NoError(err) + req.Equal(di.Index, bSectorDeadline, "should be in the deadline of the sector to prove") + + t.Log("Submitting WindowedPoSt...") + + head, err = client.ChainHead(ctx) + req.NoError(err) + rand, err := client.StateGetRandomnessFromTickets(ctx, crypto.DomainSeparationTag_PoStChainCommit, di.Open, nil, head.Key()) + require.NoError(t, err) + + postParams := miner.SubmitWindowedPoStParams{ + ChainCommitEpoch: di.Open, + ChainCommitRand: rand, + Deadline: bSectorDeadline, + Partitions: []miner.PoStPartition{{Index: bSectorPartition}}, + Proofs: []proof.PoStProof{{ + PoStProof: minerBInfo.WindowPoStProofType, + ProofBytes: []byte{0x1, 0x2, 0x3, 0x4}, + }}, + } + + enc = new(bytes.Buffer) + req.NoError(postParams.MarshalCBOR(enc)) + + m, err = client.MpoolPushMessage(ctx, &types.Message{ + To: maddrB, + From: minerB.OwnerKey.Address, + Value: types.NewInt(0), + Method: builtin.MethodsMiner.SubmitWindowedPoSt, + Params: enc.Bytes(), + }, nil) + req.NoError(err) + + r, err = client.StateWaitMsg(ctx, m.Cid(), 2, api.LookbackNoLimit, true) + req.NoError(err) + require.True(t, r.Receipt.ExitCode.IsSuccess()) + + t.Log("Checking power after PoSt...") + + p, err = client.StateMinerPower(ctx, maddrB, r.TipSet) + req.NoError(err) + t.Logf("MinerB RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) + req.Equal(uint64(2<<10), p.MinerPower.RawBytePower.Uint64()) // 2kiB RBP + req.Equal(uint64(2<<10), p.MinerPower.QualityAdjPower.Uint64()) // 2kiB QaP +} From 0b46214a6f11928859bd35c8882d8db324ce868d Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Wed, 22 May 2024 16:45:36 +1000 Subject: [PATCH 02/20] test: actors: manual CC onboarding itest with real proofs --- itests/manual_onboarding_test.go | 579 ++++++++++++++++++++++++------- 1 file changed, 452 insertions(+), 127 deletions(-) diff --git a/itests/manual_onboarding_test.go b/itests/manual_onboarding_test.go index b9e484a9d06..df2ee5205f6 100644 --- a/itests/manual_onboarding_test.go +++ b/itests/manual_onboarding_test.go @@ -3,12 +3,16 @@ package itests import ( "bytes" "context" + "os" + "path/filepath" "testing" "time" "github.com/ipfs/go-cid" "github.com/stretchr/testify/require" + ffi "github.com/filecoin-project/filecoin-ffi" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/builtin" miner14 "github.com/filecoin-project/go-state-types/builtin/v14/miner" @@ -18,6 +22,7 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/itests/kit" ) @@ -26,184 +31,504 @@ import ( func TestManualCCOnboarding(t *testing.T) { req := require.New(t) - kit.QuietMiningLogs() - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - var ( - blocktime = 2 * time.Millisecond + for _, withMockProofs := range []bool{true, false} { + testName := "WithoutMockProofs" + if withMockProofs { + testName = "WithMockProofs" + } + t.Run(testName, func(t *testing.T) { + kit.QuietMiningLogs() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var ( + blocktime = 2 * time.Millisecond + + client kit.TestFullNode + minerA kit.TestMiner // A is a standard genesis miner + minerB kit.TestMiner // B is a CC miner we will onboard manually + + bSectorNum = abi.SectorNumber(22) + + cacheDirPath string + unsealedSectorPath, sealedSectorPath string + sealedCid, unsealedCid cid.Cid + sealTickets abi.SealRandomness + ) + + // Setup and begin mining with a single miner (A) + + kitOpts := []kit.EnsembleOpt{} + if withMockProofs { + kitOpts = append(kitOpts, kit.MockProofs()) + } + nodeOpts := []kit.NodeOpt{kit.WithAllSubsystems()} + ens := kit.NewEnsemble(t, kitOpts...). + FullNode(&client, nodeOpts...). + Miner(&minerA, &client, nodeOpts...). + Start(). + InterconnectAll() + ens.BeginMining(blocktime) + + nodeOpts = append(nodeOpts, kit.OwnerAddr(client.DefaultKey)) + ens.Miner(&minerB, &client, nodeOpts...).Start() + + maddrA, err := minerA.ActorAddress(ctx) + req.NoError(err) + + build.Clock.Sleep(time.Second) + + mAddrB, err := minerB.ActorAddress(ctx) + req.NoError(err) + mAddrBBytes := new(bytes.Buffer) + req.NoError(mAddrB.MarshalCBOR(mAddrBBytes)) + + head, err := client.ChainHead(ctx) + req.NoError(err) + + minerBInfo, err := client.StateMinerInfo(ctx, mAddrB, head.Key()) + req.NoError(err) + + t.Log("Checking initial power ...") + + // Miner A should have power + p, err := client.StateMinerPower(ctx, maddrA, head.Key()) + req.NoError(err) + t.Logf("MinerA RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) + + // Miner B should have no power + p, err = client.StateMinerPower(ctx, mAddrB, head.Key()) + req.NoError(err) + t.Logf("MinerB RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) + req.True(p.MinerPower.RawBytePower.IsZero()) + + // Run precommit for a sector on miner B + + sealRandEpoch := policy.SealRandomnessLookback + t.Logf("Waiting for at least epoch %d for seal randomness (current epoch %d) ...", sealRandEpoch+5, head.Height()) + client.WaitTillChain(ctx, kit.HeightAtLeast(sealRandEpoch+5)) + + if withMockProofs { + sealedCid = cid.MustParse("bagboea4b5abcatlxechwbp7kjpjguna6r6q7ejrhe6mdp3lf34pmswn27pkkiekz") + } else { + cacheDirPath = t.TempDir() + tmpDir := t.TempDir() + unsealedSectorPath = filepath.Join(tmpDir, "unsealed") + sealedSectorPath = filepath.Join(tmpDir, "sealed") + + sealTickets, sealedCid, unsealedCid = manualOnboardingGeneratePreCommit( + t, + ctx, + client, + cacheDirPath, + unsealedSectorPath, + sealedSectorPath, + mAddrB, + bSectorNum, + sealRandEpoch, + ) + } + + t.Log("Submitting PreCommitSector ...") + + preCommitParams := &miner.PreCommitSectorBatchParams2{ + Sectors: []miner.SectorPreCommitInfo{{ + Expiration: 2880 * 300, + SectorNumber: 22, + SealProof: kit.TestSpt, + SealedCID: sealedCid, + SealRandEpoch: sealRandEpoch, + }}, + } + + enc := new(bytes.Buffer) + req.NoError(preCommitParams.MarshalCBOR(enc)) + + m, err := client.MpoolPushMessage(ctx, &types.Message{ + To: mAddrB, + From: minerB.OwnerKey.Address, + Value: types.FromFil(1), + Method: builtin.MethodsMiner.PreCommitSectorBatch2, + Params: enc.Bytes(), + }, nil) + req.NoError(err) + + r, err := client.StateWaitMsg(ctx, m.Cid(), 2, api.LookbackNoLimit, true) + req.NoError(err) + req.True(r.Receipt.ExitCode.IsSuccess()) + + preCommitInfo, err := client.StateSectorPreCommitInfo(ctx, mAddrB, bSectorNum, r.TipSet) + req.NoError(err) + + // Run prove commit for the sector on miner B + + seedRandomnessHeight := preCommitInfo.PreCommitEpoch + policy.GetPreCommitChallengeDelay() + t.Logf("Waiting %d epochs for seed randomness at epoch %d (current epoch %d)...", seedRandomnessHeight-r.Height, seedRandomnessHeight, r.Height) + client.WaitTillChain(ctx, kit.HeightAtLeast(seedRandomnessHeight+5)) + + var sectorProof []byte + if withMockProofs { + sectorProof = []byte{0xde, 0xad, 0xbe, 0xef} + } else { + sectorProof = manualOnboardingGenerateProveCommit( + t, + ctx, + client, + cacheDirPath, + sealedSectorPath, + mAddrB, + bSectorNum, + sealedCid, + unsealedCid, + sealTickets, + ) + } + + t.Log("Submitting ProveCommitSector ...") + + proveCommitParams := miner14.ProveCommitSectors3Params{ + SectorActivations: []miner14.SectorActivationManifest{{SectorNumber: bSectorNum}}, + SectorProofs: [][]byte{sectorProof}, + RequireActivationSuccess: true, + } + + enc = new(bytes.Buffer) + req.NoError(proveCommitParams.MarshalCBOR(enc)) + + m, err = client.MpoolPushMessage(ctx, &types.Message{ + To: minerB.ActorAddr, + From: minerB.OwnerKey.Address, + Value: types.FromFil(0), + Method: builtin.MethodsMiner.ProveCommitSectors3, + Params: enc.Bytes(), + }, nil) + req.NoError(err) + + r, err = client.StateWaitMsg(ctx, m.Cid(), 2, api.LookbackNoLimit, true) + req.NoError(err) + req.True(r.Receipt.ExitCode.IsSuccess()) + + // Check power after proving, should still be zero until the PoSt is submitted + p, err = client.StateMinerPower(ctx, mAddrB, r.TipSet) + req.NoError(err) + t.Logf("MinerB RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) + req.True(p.MinerPower.RawBytePower.IsZero()) + + // Fetch on-chain sector properties + + soi, err := client.StateSectorGetInfo(ctx, mAddrB, bSectorNum, r.TipSet) + req.NoError(err) + t.Logf("SectorOnChainInfo %d: %+v", bSectorNum, soi) + + sp, err := client.StateSectorPartition(ctx, mAddrB, bSectorNum, r.TipSet) + req.NoError(err) + t.Logf("SectorPartition %d: %+v", bSectorNum, sp) + bSectorDeadline := sp.Deadline + bSectorPartition := sp.Partition + + // Wait for the deadline to come around and submit a PoSt + + di, err := client.StateMinerProvingDeadline(ctx, mAddrB, types.EmptyTSK) + req.NoError(err) + t.Logf("MinerB Deadline Info: %+v", di) + + // Use the current deadline to work out when the deadline we care about (bSectorDeadline) is open + // and ready to receive posts + deadlineCount := di.WPoStPeriodDeadlines + epochsPerDeadline := uint64(di.WPoStChallengeWindow) + currentDeadline := di.Index + currentDeadlineStart := di.Open + waitTillEpoch := abi.ChainEpoch((deadlineCount-currentDeadline+bSectorDeadline)*epochsPerDeadline) + currentDeadlineStart + 1 + + t.Logf("Waiting %d until epoch %d to get to deadline %d", waitTillEpoch-di.CurrentEpoch, waitTillEpoch, bSectorDeadline) + head = client.WaitTillChain(ctx, kit.HeightAtLeast(waitTillEpoch)) + + // We should be up to the deadline we care about + di, err = client.StateMinerProvingDeadline(ctx, mAddrB, types.EmptyTSK) + req.NoError(err) + req.Equal(bSectorDeadline, di.Index, "should be in the deadline of the sector to prove") + + var proofBytes []byte + if withMockProofs { + proofBytes = []byte{0xde, 0xad, 0xbe, 0xef} + } else { + proofBytes = manualOnboardingGenerateWindowPost(t, ctx, client, cacheDirPath, sealedSectorPath, mAddrB, bSectorNum, sealedCid) + } + + t.Log("Submitting WindowedPoSt...") + + rand, err := client.StateGetRandomnessFromTickets(ctx, crypto.DomainSeparationTag_PoStChainCommit, di.Open, nil, head.Key()) + req.NoError(err) + + postParams := miner.SubmitWindowedPoStParams{ + ChainCommitEpoch: di.Open, + ChainCommitRand: rand, + Deadline: bSectorDeadline, + Partitions: []miner.PoStPartition{{Index: bSectorPartition}}, + Proofs: []proof.PoStProof{{PoStProof: minerBInfo.WindowPoStProofType, ProofBytes: proofBytes}}, + } + + enc = new(bytes.Buffer) + req.NoError(postParams.MarshalCBOR(enc)) + + m, err = client.MpoolPushMessage(ctx, &types.Message{ + To: mAddrB, + From: minerB.OwnerKey.Address, + Value: types.NewInt(0), + Method: builtin.MethodsMiner.SubmitWindowedPoSt, + Params: enc.Bytes(), + }, nil) + req.NoError(err) + + r, err = client.StateWaitMsg(ctx, m.Cid(), 2, api.LookbackNoLimit, true) + req.NoError(err) + req.True(r.Receipt.ExitCode.IsSuccess()) + + if !withMockProofs { + // Dispute the PoSt to confirm the validity of the PoSt since PoSt acceptance is optimistic + manualOnboardingDisputeWindowPost(t, ctx, client, mAddrB, bSectorNum) + } + + t.Log("Checking power after PoSt ...") + + // Miner B should now have power + p, err = client.StateMinerPower(ctx, mAddrB, r.TipSet) + req.NoError(err) + t.Logf("MinerB RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) + req.Equal(uint64(2<<10), p.MinerPower.RawBytePower.Uint64()) // 2kiB RBP + req.Equal(uint64(2<<10), p.MinerPower.QualityAdjPower.Uint64()) // 2kiB QaP + }) + } +} - client kit.TestFullNode - minerA kit.TestMiner // A is a standard genesis miner - minerB kit.TestMiner // B is a CC miner we will onboard manually - ) +func manualOnboardingGeneratePreCommit( + t *testing.T, + ctx context.Context, + client api.FullNode, + cacheDirPath, + unsealedSectorPath, + sealedSectorPath string, + minerAddr address.Address, + sectorNumber abi.SectorNumber, + sealRandEpoch abi.ChainEpoch, +) (abi.SealRandomness, cid.Cid, cid.Cid) { - opts := []kit.NodeOpt{kit.WithAllSubsystems()} - ens := kit.NewEnsemble(t, kit.MockProofs()). - FullNode(&client, opts...). - Miner(&minerA, &client, opts...). - Start(). - InterconnectAll() - ens.BeginMining(blocktime) + req := require.New(t) + t.Log("Generating PreCommit ...") - opts = append(opts, kit.OwnerAddr(client.DefaultKey)) - ens.Miner(&minerB, &client, opts...).Start() + sectorSize := abi.SectorSize(2 << 10) + unsealedSize := abi.PaddedPieceSize(sectorSize).Unpadded() + req.NoError(os.WriteFile(unsealedSectorPath, make([]byte, unsealedSize), 0644)) + req.NoError(os.WriteFile(sealedSectorPath, make([]byte, sectorSize), 0644)) - maddrA, err := minerA.ActorAddress(ctx) + head, err := client.ChainHead(ctx) req.NoError(err) - build.Clock.Sleep(time.Second) - - t.Log("Submitting PreCommitSector...") + minerAddrBytes := new(bytes.Buffer) + req.NoError(minerAddr.MarshalCBOR(minerAddrBytes)) - maddrB, err := minerB.ActorAddress(ctx) + rand, err := client.StateGetRandomnessFromTickets(ctx, crypto.DomainSeparationTag_SealRandomness, sealRandEpoch, minerAddrBytes.Bytes(), head.Key()) req.NoError(err) + sealTickets := abi.SealRandomness(rand) - head, err := client.ChainHead(ctx) + t.Logf("Running SealPreCommitPhase1 for sector %d...", sectorNumber) + + actorIdNum, err := address.IDFromAddress(minerAddr) + req.NoError(err) + actorId := abi.ActorID(actorIdNum) + + pc1, err := ffi.SealPreCommitPhase1( + kit.TestSpt, + cacheDirPath, + unsealedSectorPath, + sealedSectorPath, + sectorNumber, + actorId, + sealTickets, + []abi.PieceInfo{}, + ) req.NoError(err) + req.NotNil(pc1) + + t.Logf("Running SealPreCommitPhase2 for sector %d...", sectorNumber) - minerBInfo, err := client.StateMinerInfo(ctx, maddrB, head.Key()) + sealedCid, unsealedCid, err := ffi.SealPreCommitPhase2( + pc1, + cacheDirPath, + sealedSectorPath, + ) req.NoError(err) - preCommitParams := &miner.PreCommitSectorBatchParams2{ - Sectors: []miner.SectorPreCommitInfo{{ - Expiration: 2880 * 300, - SectorNumber: 22, - SealProof: kit.TestSpt, - SealedCID: cid.MustParse("bagboea4b5abcatlxechwbp7kjpjguna6r6q7ejrhe6mdp3lf34pmswn27pkkiekz"), - SealRandEpoch: head.Height() - 200, - }}, - } + t.Logf("Unsealed CID: %s", unsealedCid) + t.Logf("Sealed CID: %s", sealedCid) - enc := new(bytes.Buffer) - req.NoError(preCommitParams.MarshalCBOR(enc)) + return sealTickets, sealedCid, unsealedCid +} - m, err := client.MpoolPushMessage(ctx, &types.Message{ - To: maddrB, - From: minerB.OwnerKey.Address, - Value: types.FromFil(1), - Method: builtin.MethodsMiner.PreCommitSectorBatch2, - Params: enc.Bytes(), - }, nil) +func manualOnboardingGenerateProveCommit( + t *testing.T, + ctx context.Context, + client api.FullNode, + cacheDirPath, + sealedSectorPath string, + minerAddr address.Address, + sectorNumber abi.SectorNumber, + sealedCid, unsealedCid cid.Cid, + sealTickets abi.SealRandomness, +) []byte { + req := require.New(t) + + t.Log("Generating Sector Proof ...") + + head, err := client.ChainHead(ctx) req.NoError(err) - r, err := client.StateWaitMsg(ctx, m.Cid(), 2, api.LookbackNoLimit, true) + preCommitInfo, err := client.StateSectorPreCommitInfo(ctx, minerAddr, sectorNumber, head.Key()) req.NoError(err) - require.True(t, r.Receipt.ExitCode.IsSuccess()) - client.WaitTillChain(ctx, kit.HeightAtLeast(r.Height+miner14.PreCommitChallengeDelay+5)) + seedRandomnessHeight := preCommitInfo.PreCommitEpoch + policy.GetPreCommitChallengeDelay() - t.Log("Checking initial power...") + minerAddrBytes := new(bytes.Buffer) + req.NoError(minerAddr.MarshalCBOR(minerAddrBytes)) - p, err := client.StateMinerPower(ctx, maddrA, r.TipSet) + rand, err := client.StateGetRandomnessFromBeacon(ctx, crypto.DomainSeparationTag_InteractiveSealChallengeSeed, seedRandomnessHeight, minerAddrBytes.Bytes(), head.Key()) req.NoError(err) - t.Logf("MinerA RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) + seedRandomness := abi.InteractiveSealRandomness(rand) - p, err = client.StateMinerPower(ctx, maddrB, r.TipSet) + actorIdNum, err := address.IDFromAddress(minerAddr) + req.NoError(err) + actorId := abi.ActorID(actorIdNum) + + t.Logf("Running SealCommitPhase1 for sector %d...", sectorNumber) + + scp1, err := ffi.SealCommitPhase1( + kit.TestSpt, + sealedCid, + unsealedCid, + cacheDirPath, + sealedSectorPath, + sectorNumber, + actorId, + sealTickets, + seedRandomness, + []abi.PieceInfo{}, + ) req.NoError(err) - t.Logf("MinerB RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) - require.True(t, p.MinerPower.RawBytePower.IsZero()) - t.Log("Submitting ProveCommitSector...") + t.Logf("Running SealCommitPhase2 for sector %d...", sectorNumber) - bSectorNum := preCommitParams.Sectors[0].SectorNumber + sectorProof, err := ffi.SealCommitPhase2(scp1, sectorNumber, actorId) + req.NoError(err) - proveCommitParams := miner14.ProveCommitSectors3Params{ - SectorActivations: []miner14.SectorActivationManifest{{SectorNumber: bSectorNum}}, - SectorProofs: [][]byte{{0xde, 0xad, 0xbe, 0xef}}, - RequireActivationSuccess: true, - } + return sectorProof +} - enc = new(bytes.Buffer) - req.NoError(proveCommitParams.MarshalCBOR(enc)) +func manualOnboardingGenerateWindowPost( + t *testing.T, + ctx context.Context, + client api.FullNode, + cacheDirPath string, + sealedSectorPath string, + minerAddr address.Address, + sectorNumber abi.SectorNumber, + sealedCid cid.Cid, +) []byte { - m, err = client.MpoolPushMessage(ctx, &types.Message{ - To: minerB.ActorAddr, - From: minerB.OwnerKey.Address, - Value: types.FromFil(0), - Method: builtin.MethodsMiner.ProveCommitSectors3, - Params: enc.Bytes(), - }, nil) + req := require.New(t) + + head, err := client.ChainHead(ctx) req.NoError(err) - r, err = client.StateWaitMsg(ctx, m.Cid(), 2, api.LookbackNoLimit, true) + minerInfo, err := client.StateMinerInfo(ctx, minerAddr, head.Key()) req.NoError(err) - require.True(t, r.Receipt.ExitCode.IsSuccess()) - soi, err := client.StateSectorGetInfo(ctx, maddrB, bSectorNum, r.TipSet) + di, err := client.StateMinerProvingDeadline(ctx, minerAddr, types.EmptyTSK) req.NoError(err) - t.Logf("SectorOnChainInfo %d: %+v", bSectorNum, soi) - sp, err := client.StateSectorPartition(ctx, maddrB, bSectorNum, r.TipSet) + minerAddrBytes := new(bytes.Buffer) + req.NoError(minerAddr.MarshalCBOR(minerAddrBytes)) + + rand, err := client.StateGetRandomnessFromBeacon(ctx, crypto.DomainSeparationTag_WindowedPoStChallengeSeed, di.Challenge, minerAddrBytes.Bytes(), head.Key()) req.NoError(err) - t.Logf("SectorPartition %d: %+v", bSectorNum, sp) - bSectorDeadline := sp.Deadline - bSectorPartition := sp.Partition + postRand := abi.PoStRandomness(rand) + postRand[31] &= 0x3f // make fr32 compatible + + privateSectorInfo := ffi.PrivateSectorInfo{ + SectorInfo: proof.SectorInfo{ + SealProof: kit.TestSpt, + SectorNumber: sectorNumber, + SealedCID: sealedCid, + }, + CacheDirPath: cacheDirPath, + PoStProofType: minerInfo.WindowPoStProofType, + SealedSectorPath: sealedSectorPath, + } - p, err = client.StateMinerPower(ctx, maddrB, r.TipSet) + actorIdNum, err := address.IDFromAddress(minerAddr) req.NoError(err) - t.Logf("MinerB RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) - require.True(t, p.MinerPower.RawBytePower.IsZero()) + actorId := abi.ActorID(actorIdNum) - di, err := client.StateMinerProvingDeadline(ctx, maddrB, types.EmptyTSK) + windowProofs, faultySectors, err := ffi.GenerateWindowPoSt(actorId, ffi.NewSortedPrivateSectorInfo(privateSectorInfo), postRand) req.NoError(err) - t.Logf("MinerB Deadline Info: %+v", di) + req.Len(faultySectors, 0) + req.Len(windowProofs, 1) + req.Equal(minerInfo.WindowPoStProofType, windowProofs[0].PoStProof) + proofBytes := windowProofs[0].ProofBytes + + info := proof.WindowPoStVerifyInfo{ + Randomness: postRand, + Proofs: []proof.PoStProof{{PoStProof: minerInfo.WindowPoStProofType, ProofBytes: proofBytes}}, + ChallengedSectors: []proof.SectorInfo{{SealProof: kit.TestSpt, SectorNumber: sectorNumber, SealedCID: sealedCid}}, + Prover: actorId, + } + + verified, err := ffi.VerifyWindowPoSt(info) + req.NoError(err) + req.True(verified, "window post verification failed") - // Use the current deadline to work out when the deadline we care about (bSectorDeadline) is open - // and ready to receive posts - deadlineCount := di.WPoStPeriodDeadlines - epochsPerDeadline := uint64(di.WPoStChallengeWindow) - currentDeadline := di.Index - currentDeadlineStart := di.Open - waitTillEpoch := abi.ChainEpoch((deadlineCount-currentDeadline+bSectorDeadline)*epochsPerDeadline) + currentDeadlineStart - di.WPoStProvingPeriod + 1 + return proofBytes +} + +func manualOnboardingDisputeWindowPost( + t *testing.T, + ctx context.Context, + client kit.TestFullNode, + minerAddr address.Address, + sectorNumber abi.SectorNumber, +) { - t.Logf("Waiting %d until epoch %d to get to deadline %d", waitTillEpoch-di.CurrentEpoch, waitTillEpoch, bSectorDeadline) - client.WaitTillChain(ctx, kit.HeightAtLeast(waitTillEpoch)) + req := require.New(t) - // We should be up to the deadline we care about - di, err = client.StateMinerProvingDeadline(ctx, maddrB, types.EmptyTSK) + head, err := client.ChainHead(ctx) req.NoError(err) - req.Equal(di.Index, bSectorDeadline, "should be in the deadline of the sector to prove") - t.Log("Submitting WindowedPoSt...") + sp, err := client.StateSectorPartition(ctx, minerAddr, sectorNumber, head.Key()) + req.NoError(err) - head, err = client.ChainHead(ctx) + di, err := client.StateMinerProvingDeadline(ctx, minerAddr, head.Key()) req.NoError(err) - rand, err := client.StateGetRandomnessFromTickets(ctx, crypto.DomainSeparationTag_PoStChainCommit, di.Open, nil, head.Key()) - require.NoError(t, err) - postParams := miner.SubmitWindowedPoStParams{ - ChainCommitEpoch: di.Open, - ChainCommitRand: rand, - Deadline: bSectorDeadline, - Partitions: []miner.PoStPartition{{Index: bSectorPartition}}, - Proofs: []proof.PoStProof{{ - PoStProof: minerBInfo.WindowPoStProofType, - ProofBytes: []byte{0x1, 0x2, 0x3, 0x4}, - }}, - } + disputeEpoch := di.Challenge + miner14.WPoStDisputeWindow + 5 + t.Logf("Waiting %d until epoch %d to submit dispute", disputeEpoch-head.Height(), disputeEpoch) - enc = new(bytes.Buffer) - req.NoError(postParams.MarshalCBOR(enc)) + client.WaitTillChain(ctx, kit.HeightAtLeast(disputeEpoch)) - m, err = client.MpoolPushMessage(ctx, &types.Message{ - To: maddrB, - From: minerB.OwnerKey.Address, - Value: types.NewInt(0), - Method: builtin.MethodsMiner.SubmitWindowedPoSt, - Params: enc.Bytes(), - }, nil) - req.NoError(err) + t.Logf("Disputing WindowedPoSt to confirm validity...") - r, err = client.StateWaitMsg(ctx, m.Cid(), 2, api.LookbackNoLimit, true) - req.NoError(err) - require.True(t, r.Receipt.ExitCode.IsSuccess()) + disputeParams := &miner14.DisputeWindowedPoStParams{Deadline: sp.Deadline, PoStIndex: 0} + enc := new(bytes.Buffer) + req.NoError(disputeParams.MarshalCBOR(enc)) - t.Log("Checking power after PoSt...") + disputeMsg := &types.Message{ + To: minerAddr, + Method: builtin.MethodsMiner.DisputeWindowedPoSt, + Params: enc.Bytes(), + Value: types.NewInt(0), + From: client.DefaultKey.Address, + } - p, err = client.StateMinerPower(ctx, maddrB, r.TipSet) - req.NoError(err) - t.Logf("MinerB RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) - req.Equal(uint64(2<<10), p.MinerPower.RawBytePower.Uint64()) // 2kiB RBP - req.Equal(uint64(2<<10), p.MinerPower.QualityAdjPower.Uint64()) // 2kiB QaP + _, err = client.MpoolPushMessage(ctx, disputeMsg, nil) + req.Error(err, "expected dispute to fail") + req.Contains(err.Error(), "failed to dispute valid post") + req.Contains(err.Error(), "(RetCode=16)") } From 1d96d68be678f10ec289e4348e354673d30e510f Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Wed, 22 May 2024 17:19:33 +1000 Subject: [PATCH 03/20] test: actors: fix lint issue, require proofs in CI --- .github/workflows/test.yml | 1 + itests/manual_onboarding_test.go | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c26691b165f..ac7b38ee5b1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -129,6 +129,7 @@ jobs: "itest-deals", "itest-direct_data_onboard_verified", "itest-direct_data_onboard", + "itest-manual_onboarding", "itest-net", "itest-path_detach_redeclare", "itest-path_type_filters", diff --git a/itests/manual_onboarding_test.go b/itests/manual_onboarding_test.go index df2ee5205f6..c8ae56b3301 100644 --- a/itests/manual_onboarding_test.go +++ b/itests/manual_onboarding_test.go @@ -117,8 +117,8 @@ func TestManualCCOnboarding(t *testing.T) { sealedSectorPath = filepath.Join(tmpDir, "sealed") sealTickets, sealedCid, unsealedCid = manualOnboardingGeneratePreCommit( - t, ctx, + t, client, cacheDirPath, unsealedSectorPath, @@ -171,8 +171,8 @@ func TestManualCCOnboarding(t *testing.T) { sectorProof = []byte{0xde, 0xad, 0xbe, 0xef} } else { sectorProof = manualOnboardingGenerateProveCommit( - t, ctx, + t, client, cacheDirPath, sealedSectorPath, @@ -252,7 +252,7 @@ func TestManualCCOnboarding(t *testing.T) { if withMockProofs { proofBytes = []byte{0xde, 0xad, 0xbe, 0xef} } else { - proofBytes = manualOnboardingGenerateWindowPost(t, ctx, client, cacheDirPath, sealedSectorPath, mAddrB, bSectorNum, sealedCid) + proofBytes = manualOnboardingGenerateWindowPost(ctx, t, client, cacheDirPath, sealedSectorPath, mAddrB, bSectorNum, sealedCid) } t.Log("Submitting WindowedPoSt...") @@ -286,7 +286,7 @@ func TestManualCCOnboarding(t *testing.T) { if !withMockProofs { // Dispute the PoSt to confirm the validity of the PoSt since PoSt acceptance is optimistic - manualOnboardingDisputeWindowPost(t, ctx, client, mAddrB, bSectorNum) + manualOnboardingDisputeWindowPost(ctx, t, client, mAddrB, bSectorNum) } t.Log("Checking power after PoSt ...") @@ -302,8 +302,8 @@ func TestManualCCOnboarding(t *testing.T) { } func manualOnboardingGeneratePreCommit( - t *testing.T, ctx context.Context, + t *testing.T, client api.FullNode, cacheDirPath, unsealedSectorPath, @@ -366,8 +366,8 @@ func manualOnboardingGeneratePreCommit( } func manualOnboardingGenerateProveCommit( - t *testing.T, ctx context.Context, + t *testing.T, client api.FullNode, cacheDirPath, sealedSectorPath string, @@ -424,8 +424,8 @@ func manualOnboardingGenerateProveCommit( } func manualOnboardingGenerateWindowPost( - t *testing.T, ctx context.Context, + t *testing.T, client api.FullNode, cacheDirPath string, sealedSectorPath string, @@ -490,8 +490,8 @@ func manualOnboardingGenerateWindowPost( } func manualOnboardingDisputeWindowPost( - t *testing.T, ctx context.Context, + t *testing.T, client kit.TestFullNode, minerAddr address.Address, sectorNumber abi.SectorNumber, From f8dd9eadc8dad8cc5c42569f4dc69e66718675aa Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Thu, 23 May 2024 21:01:37 +1000 Subject: [PATCH 04/20] test: actors: rename real proofs test, fix dispute window wait --- itests/manual_onboarding_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/itests/manual_onboarding_test.go b/itests/manual_onboarding_test.go index c8ae56b3301..759bd1f59f0 100644 --- a/itests/manual_onboarding_test.go +++ b/itests/manual_onboarding_test.go @@ -32,7 +32,7 @@ func TestManualCCOnboarding(t *testing.T) { req := require.New(t) for _, withMockProofs := range []bool{true, false} { - testName := "WithoutMockProofs" + testName := "WithRealProofs" if withMockProofs { testName = "WithMockProofs" } @@ -508,7 +508,7 @@ func manualOnboardingDisputeWindowPost( di, err := client.StateMinerProvingDeadline(ctx, minerAddr, head.Key()) req.NoError(err) - disputeEpoch := di.Challenge + miner14.WPoStDisputeWindow + 5 + disputeEpoch := di.Close + 5 t.Logf("Waiting %d until epoch %d to submit dispute", disputeEpoch-head.Height(), disputeEpoch) client.WaitTillChain(ctx, kit.HeightAtLeast(disputeEpoch)) From 39fb5cb0a18f9bb1395012fd75bbfe4f96723af4 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Mon, 27 May 2024 14:58:57 +1000 Subject: [PATCH 05/20] feat: add TestUnmanagedMiner in the itest kit for non-storage managed miners --- itests/kit/ensemble.go | 144 ++++++- itests/kit/node_unmanaged.go | 26 ++ itests/manual_onboarding_test.go | 717 +++++++++++++++++++++++-------- 3 files changed, 690 insertions(+), 197 deletions(-) create mode 100644 itests/kit/node_unmanaged.go diff --git a/itests/kit/ensemble.go b/itests/kit/ensemble.go index a9521896358..55e6dcf198a 100644 --- a/itests/kit/ensemble.go +++ b/itests/kit/ensemble.go @@ -122,17 +122,19 @@ type Ensemble struct { options *ensembleOpts inactive struct { - fullnodes []*TestFullNode - providernodes []*TestCurioNode - miners []*TestMiner - workers []*TestWorker + fullnodes []*TestFullNode + providernodes []*TestCurioNode + miners []*TestMiner + unmanagedMiners []*TestUnmanagedMiner + workers []*TestWorker } active struct { - fullnodes []*TestFullNode - providernodes []*TestCurioNode - miners []*TestMiner - workers []*TestWorker - bms map[*TestMiner]*BlockMiner + fullnodes []*TestFullNode + providernodes []*TestCurioNode + miners []*TestMiner + unmanagedMiners []*TestUnmanagedMiner + workers []*TestWorker + bms map[*TestMiner]*BlockMiner } genesis struct { version network.Version @@ -259,9 +261,7 @@ func (n *Ensemble) MinerEnroll(minerNode *TestMiner, full *TestFullNode, opts .. tdir, err := os.MkdirTemp("", "preseal-memgen") require.NoError(n.t, err) - minerCnt := len(n.inactive.miners) + len(n.active.miners) - - actorAddr, err := address.NewIDAddress(genesis2.MinerStart + uint64(minerCnt)) + actorAddr, err := address.NewIDAddress(genesis2.MinerStart + n.minerCount()) require.NoError(n.t, err) if options.mainMiner != nil { @@ -329,16 +329,59 @@ func (n *Ensemble) MinerEnroll(minerNode *TestMiner, full *TestFullNode, opts .. return n } +func (n *Ensemble) UnmanagedMinerEnroll(minerNode *TestUnmanagedMiner, full *TestFullNode, opts ...NodeOpt) *Ensemble { + require.NotNil(n.t, full, "full node required when instantiating miner") + + options := DefaultNodeOpts + for _, o := range opts { + err := o(&options) + require.NoError(n.t, err) + } + + privkey, _, err := libp2pcrypto.GenerateEd25519Key(rand.Reader) + require.NoError(n.t, err) + + peerId, err := peer.IDFromPrivateKey(privkey) + require.NoError(n.t, err) + + actorAddr, err := address.NewIDAddress(genesis2.MinerStart + n.minerCount()) + require.NoError(n.t, err) + + require.NotNil(n.t, options.ownerKey, "manual miner key can't be null if initializing a miner after genesis") + + *minerNode = TestUnmanagedMiner{ + t: n.t, + options: options, + ActorAddr: actorAddr, + OwnerKey: options.ownerKey, + FullNode: full, + } + minerNode.Libp2p.PeerID = peerId + minerNode.Libp2p.PrivKey = privkey + + return n +} + func (n *Ensemble) AddInactiveMiner(m *TestMiner) { n.inactive.miners = append(n.inactive.miners, m) } +func (n *Ensemble) AddInactiveUnmanagedMiner(m *TestUnmanagedMiner) { + n.inactive.unmanagedMiners = append(n.inactive.unmanagedMiners, m) +} + func (n *Ensemble) Miner(minerNode *TestMiner, full *TestFullNode, opts ...NodeOpt) *Ensemble { n.MinerEnroll(minerNode, full, opts...) n.AddInactiveMiner(minerNode) return n } +func (n *Ensemble) UnmanagedMiner(minerNode *TestUnmanagedMiner, full *TestFullNode, opts ...NodeOpt) *Ensemble { + n.UnmanagedMinerEnroll(minerNode, full, opts...) + n.AddInactiveUnmanagedMiner(minerNode) + return n +} + // Worker enrolls a new worker, using the provided full node for chain // interactions. func (n *Ensemble) Worker(minerNode *TestMiner, worker *TestWorker, opts ...NodeOpt) *Ensemble { @@ -826,6 +869,79 @@ func (n *Ensemble) Start() *Ensemble { // to active, so clear the slice. n.inactive.miners = n.inactive.miners[:0] + // Create all inactive manual miners. + for _, m := range n.inactive.unmanagedMiners { + proofType, err := miner.WindowPoStProofTypeFromSectorSize(m.options.sectorSize, n.genesis.version) + require.NoError(n.t, err) + + params, aerr := actors.SerializeParams(&power3.CreateMinerParams{ + Owner: m.OwnerKey.Address, + Worker: m.OwnerKey.Address, + WindowPoStProofType: proofType, + Peer: abi.PeerID(m.Libp2p.PeerID), + }) + require.NoError(n.t, aerr) + + createStorageMinerMsg := &types.Message{ + From: m.OwnerKey.Address, + To: power.Address, + Value: big.Zero(), + + Method: power.Methods.CreateMiner, + Params: params, + } + signed, err := m.FullNode.FullNode.MpoolPushMessage(ctx, createStorageMinerMsg, &api.MessageSendSpec{ + MsgUuid: uuid.New(), + }) + require.NoError(n.t, err) + + mw, err := m.FullNode.FullNode.StateWaitMsg(ctx, signed.Cid(), build.MessageConfidence, api.LookbackNoLimit, true) + require.NoError(n.t, err) + require.Equal(n.t, exitcode.Ok, mw.Receipt.ExitCode) + + var retval power3.CreateMinerReturn + err = retval.UnmarshalCBOR(bytes.NewReader(mw.Receipt.Return)) + require.NoError(n.t, err, "failed to create miner") + + m.ActorAddr = retval.IDAddress + + has, err := m.FullNode.WalletHas(ctx, m.OwnerKey.Address) + require.NoError(n.t, err) + + // Only import the owner's full key into our companion full node, if we + // don't have it still. + if !has { + _, err = m.FullNode.WalletImport(ctx, &m.OwnerKey.KeyInfo) + require.NoError(n.t, err) + } + + enc, err := actors.SerializeParams(&miner2.ChangePeerIDParams{NewID: abi.PeerID(m.Libp2p.PeerID)}) + require.NoError(n.t, err) + + msg := &types.Message{ + From: m.OwnerKey.Address, + To: m.ActorAddr, + Method: builtin.MethodsMiner.ChangePeerID, + Params: enc, + Value: types.NewInt(0), + } + + _, err2 := m.FullNode.MpoolPushMessage(ctx, msg, &api.MessageSendSpec{ + MsgUuid: uuid.New(), + }) + require.NoError(n.t, err2) + + minerCopy := *m.FullNode + minerCopy.FullNode = modules.MakeUuidWrapper(minerCopy.FullNode) + m.FullNode = &minerCopy + + n.active.unmanagedMiners = append(n.active.unmanagedMiners, m) + } + + // If we are here, we have processed all inactive manual miners and moved them + // to active, so clear the slice. + n.inactive.unmanagedMiners = n.inactive.unmanagedMiners[:0] + // --------------------- // WORKERS // --------------------- @@ -1055,6 +1171,10 @@ func (n *Ensemble) BeginMining(blocktime time.Duration, miners ...*TestMiner) [] return bms } +func (n *Ensemble) minerCount() uint64 { + return uint64(len(n.inactive.miners) + len(n.active.miners) + len(n.inactive.unmanagedMiners) + len(n.active.unmanagedMiners)) +} + func (n *Ensemble) generateGenesis() *genesis.Template { var verifRoot = gen.DefaultVerifregRootkeyActor if k := n.options.verifiedRoot.key; k != nil { diff --git a/itests/kit/node_unmanaged.go b/itests/kit/node_unmanaged.go new file mode 100644 index 00000000000..0f27052406d --- /dev/null +++ b/itests/kit/node_unmanaged.go @@ -0,0 +1,26 @@ +package kit + +import ( + "testing" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/lotus/chain/wallet/key" + libp2pcrypto "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/core/peer" +) + +// TestUnmanagedMiner is a miner that's not managed by the storage/ +// infrastructure, all tasks must be manually executed, managed and scheduled by +// the test or test kit. +type TestUnmanagedMiner struct { + t *testing.T + options nodeOpts + + ActorAddr address.Address + OwnerKey *key.Key + FullNode *TestFullNode + Libp2p struct { + PeerID peer.ID + PrivKey libp2pcrypto.PrivKey + } +} diff --git a/itests/manual_onboarding_test.go b/itests/manual_onboarding_test.go index 759bd1f59f0..b4dcd8b3cb6 100644 --- a/itests/manual_onboarding_test.go +++ b/itests/manual_onboarding_test.go @@ -3,13 +3,16 @@ package itests import ( "bytes" "context" + "fmt" "os" "path/filepath" + "strings" "testing" "time" "github.com/ipfs/go-cid" "github.com/stretchr/testify/require" + cbg "github.com/whyrusleeping/cbor-gen" ffi "github.com/filecoin-project/filecoin-ffi" "github.com/filecoin-project/go-address" @@ -21,12 +24,14 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/itests/kit" ) +const sectorSize = abi.SectorSize(2 << 10) // 2KiB + // Manually onboard CC sectors, bypassing lotus-miner onboarding pathways func TestManualCCOnboarding(t *testing.T) { req := require.New(t) @@ -45,15 +50,38 @@ func TestManualCCOnboarding(t *testing.T) { blocktime = 2 * time.Millisecond client kit.TestFullNode - minerA kit.TestMiner // A is a standard genesis miner - minerB kit.TestMiner // B is a CC miner we will onboard manually - + minerA kit.TestMiner // A is a standard genesis miner + minerB kit.TestUnmanagedMiner // B is a CC miner we will onboard manually + minerC kit.TestUnmanagedMiner // C is a CC miner we will onboard manually (will be used for NI-PoRep) + + // TODO: single sector per miner for now, but this isn't going to scale when refactored into + // TestUnmanagedMiner - they'll need their own list of sectors and their own copy of each of + // the things below, including per-sector maps of some of these too. + // + // Misc thoughts: + // Each TestUnmanagedMiner should have its own temp dir, within which it can have a cache dir + // and a place to put sealed and unsealed sectors. We can't share these between miners. + // We should have a way to "add" CC sectors, which will setup the sealed and unsealed files + // and can move many of the manualOnboarding*() methods into the TestUnmanagedMiner struct. + // + // The manualOnboardingRunWindowPost() Go routine should be owned by TestUnmanagedMiner and + // a simple "miner.StartWindowPost()" should suffice to make it observe all of the sectors + // it knows about and start posting for them. We should be able to ignore most (all?) of the + // special cases that lotus-miner currently has to deal with. + + // sector numbers, make them unique for each miner so our maps work bSectorNum = abi.SectorNumber(22) + cSectorNum = abi.SectorNumber(33) + + tmpDir = t.TempDir() - cacheDirPath string - unsealedSectorPath, sealedSectorPath string - sealedCid, unsealedCid cid.Cid - sealTickets abi.SealRandomness + cacheDirPath = map[abi.SectorNumber]string{} // can't share a cacheDir between miners + unsealedSectorPath, sealedSectorPath = map[abi.SectorNumber]string{}, map[abi.SectorNumber]string{} + sealedCid, unsealedCid = map[abi.SectorNumber]cid.Cid{}, map[abi.SectorNumber]cid.Cid{} + + // note we'll use the same randEpoch for both miners + sealRandEpoch = policy.SealRandomnessLookback + sealTickets = map[abi.SectorNumber]abi.SealRandomness{} ) // Setup and begin mining with a single miner (A) @@ -62,7 +90,7 @@ func TestManualCCOnboarding(t *testing.T) { if withMockProofs { kitOpts = append(kitOpts, kit.MockProofs()) } - nodeOpts := []kit.NodeOpt{kit.WithAllSubsystems()} + nodeOpts := []kit.NodeOpt{kit.SectorSize(sectorSize), kit.WithAllSubsystems()} ens := kit.NewEnsemble(t, kitOpts...). FullNode(&client, nodeOpts...). Miner(&minerA, &client, nodeOpts...). @@ -71,93 +99,74 @@ func TestManualCCOnboarding(t *testing.T) { ens.BeginMining(blocktime) nodeOpts = append(nodeOpts, kit.OwnerAddr(client.DefaultKey)) - ens.Miner(&minerB, &client, nodeOpts...).Start() - - maddrA, err := minerA.ActorAddress(ctx) - req.NoError(err) + ens.UnmanagedMiner(&minerB, &client, nodeOpts...).Start() + ens.UnmanagedMiner(&minerC, &client, nodeOpts...).Start() build.Clock.Sleep(time.Second) - mAddrB, err := minerB.ActorAddress(ctx) - req.NoError(err) - mAddrBBytes := new(bytes.Buffer) - req.NoError(mAddrB.MarshalCBOR(mAddrBBytes)) - head, err := client.ChainHead(ctx) req.NoError(err) - minerBInfo, err := client.StateMinerInfo(ctx, mAddrB, head.Key()) - req.NoError(err) - t.Log("Checking initial power ...") // Miner A should have power - p, err := client.StateMinerPower(ctx, maddrA, head.Key()) + p, err := client.StateMinerPower(ctx, minerA.ActorAddr, head.Key()) req.NoError(err) t.Logf("MinerA RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) // Miner B should have no power - p, err = client.StateMinerPower(ctx, mAddrB, head.Key()) + p, err = client.StateMinerPower(ctx, minerB.ActorAddr, head.Key()) req.NoError(err) t.Logf("MinerB RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) req.True(p.MinerPower.RawBytePower.IsZero()) + // Miner C should have no power + p, err = client.StateMinerPower(ctx, minerC.ActorAddr, head.Key()) + req.NoError(err) + t.Logf("MinerC RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) + req.True(p.MinerPower.RawBytePower.IsZero()) + // Run precommit for a sector on miner B - sealRandEpoch := policy.SealRandomnessLookback t.Logf("Waiting for at least epoch %d for seal randomness (current epoch %d) ...", sealRandEpoch+5, head.Height()) client.WaitTillChain(ctx, kit.HeightAtLeast(sealRandEpoch+5)) if withMockProofs { - sealedCid = cid.MustParse("bagboea4b5abcatlxechwbp7kjpjguna6r6q7ejrhe6mdp3lf34pmswn27pkkiekz") + sealedCid[bSectorNum] = cid.MustParse("bagboea4b5abcatlxechwbp7kjpjguna6r6q7ejrhe6mdp3lf34pmswn27pkkiekz") } else { - cacheDirPath = t.TempDir() - tmpDir := t.TempDir() - unsealedSectorPath = filepath.Join(tmpDir, "unsealed") - sealedSectorPath = filepath.Join(tmpDir, "sealed") + cacheDirPath[bSectorNum] = filepath.Join(tmpDir, "cacheb") + unsealedSectorPath[bSectorNum] = filepath.Join(tmpDir, "unsealedb") + sealedSectorPath[bSectorNum] = filepath.Join(tmpDir, "sealedb") - sealTickets, sealedCid, unsealedCid = manualOnboardingGeneratePreCommit( + sealTickets[bSectorNum], sealedCid[bSectorNum], unsealedCid[bSectorNum] = manualOnboardingGeneratePreCommit( ctx, t, client, - cacheDirPath, - unsealedSectorPath, - sealedSectorPath, - mAddrB, + cacheDirPath[bSectorNum], + unsealedSectorPath[bSectorNum], + sealedSectorPath[bSectorNum], + minerB.ActorAddr, bSectorNum, sealRandEpoch, + kit.TestSpt, ) } - t.Log("Submitting PreCommitSector ...") + t.Log("Submitting MinerB PreCommitSector ...") - preCommitParams := &miner.PreCommitSectorBatchParams2{ - Sectors: []miner.SectorPreCommitInfo{{ + r, err := manualOnboardingSubmitMessage(ctx, client, minerB, &miner14.PreCommitSectorBatchParams2{ + Sectors: []miner14.SectorPreCommitInfo{{ Expiration: 2880 * 300, - SectorNumber: 22, + SectorNumber: bSectorNum, SealProof: kit.TestSpt, - SealedCID: sealedCid, + SealedCID: sealedCid[bSectorNum], SealRandEpoch: sealRandEpoch, }}, - } - - enc := new(bytes.Buffer) - req.NoError(preCommitParams.MarshalCBOR(enc)) - - m, err := client.MpoolPushMessage(ctx, &types.Message{ - To: mAddrB, - From: minerB.OwnerKey.Address, - Value: types.FromFil(1), - Method: builtin.MethodsMiner.PreCommitSectorBatch2, - Params: enc.Bytes(), - }, nil) - req.NoError(err) - - r, err := client.StateWaitMsg(ctx, m.Cid(), 2, api.LookbackNoLimit, true) + }, 1, builtin.MethodsMiner.PreCommitSectorBatch2) req.NoError(err) req.True(r.Receipt.ExitCode.IsSuccess()) - preCommitInfo, err := client.StateSectorPreCommitInfo(ctx, mAddrB, bSectorNum, r.TipSet) + preCommitInfo, err := client.StateSectorPreCommitInfo(ctx, minerB.ActorAddr, bSectorNum, r.TipSet) req.NoError(err) // Run prove commit for the sector on miner B @@ -174,129 +183,210 @@ func TestManualCCOnboarding(t *testing.T) { ctx, t, client, - cacheDirPath, - sealedSectorPath, - mAddrB, + cacheDirPath[bSectorNum], + sealedSectorPath[bSectorNum], + minerB.ActorAddr, bSectorNum, - sealedCid, - unsealedCid, - sealTickets, + sealedCid[bSectorNum], + unsealedCid[bSectorNum], + sealTickets[bSectorNum], + kit.TestSpt, ) } - t.Log("Submitting ProveCommitSector ...") + t.Log("Submitting MinerB ProveCommitSector ...") - proveCommitParams := miner14.ProveCommitSectors3Params{ + r, err = manualOnboardingSubmitMessage(ctx, client, minerB, &miner14.ProveCommitSectors3Params{ SectorActivations: []miner14.SectorActivationManifest{{SectorNumber: bSectorNum}}, SectorProofs: [][]byte{sectorProof}, RequireActivationSuccess: true, - } - - enc = new(bytes.Buffer) - req.NoError(proveCommitParams.MarshalCBOR(enc)) - - m, err = client.MpoolPushMessage(ctx, &types.Message{ - To: minerB.ActorAddr, - From: minerB.OwnerKey.Address, - Value: types.FromFil(0), - Method: builtin.MethodsMiner.ProveCommitSectors3, - Params: enc.Bytes(), - }, nil) - req.NoError(err) - - r, err = client.StateWaitMsg(ctx, m.Cid(), 2, api.LookbackNoLimit, true) + }, 0, builtin.MethodsMiner.ProveCommitSectors3) req.NoError(err) req.True(r.Receipt.ExitCode.IsSuccess()) // Check power after proving, should still be zero until the PoSt is submitted - p, err = client.StateMinerPower(ctx, mAddrB, r.TipSet) + p, err = client.StateMinerPower(ctx, minerB.ActorAddr, r.TipSet) req.NoError(err) t.Logf("MinerB RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) req.True(p.MinerPower.RawBytePower.IsZero()) - // Fetch on-chain sector properties + // start a background PoST scheduler for miner B + bFirstCh, bErrCh := manualOnboardingRunWindowPost( + ctx, + withMockProofs, + client, + minerB, + bSectorNum, + cacheDirPath[bSectorNum], + sealedSectorPath[bSectorNum], + sealedCid[bSectorNum], + kit.TestSpt, + ) - soi, err := client.StateSectorGetInfo(ctx, mAddrB, bSectorNum, r.TipSet) - req.NoError(err) - t.Logf("SectorOnChainInfo %d: %+v", bSectorNum, soi) + // Miner C (will be used for NI-PoRep) - sp, err := client.StateSectorPartition(ctx, mAddrB, bSectorNum, r.TipSet) - req.NoError(err) - t.Logf("SectorPartition %d: %+v", bSectorNum, sp) - bSectorDeadline := sp.Deadline - bSectorPartition := sp.Partition + if withMockProofs { + sealedCid[cSectorNum] = cid.MustParse("bagboea4b5abcatlxechwbp7kjpjguna6r6q7ejrhe6mdp3lf34pmswn27pkkiekz") + } else { + cacheDirPath[cSectorNum] = filepath.Join(tmpDir, "cachec") + unsealedSectorPath[cSectorNum] = filepath.Join(tmpDir, "unsealedc") + sealedSectorPath[cSectorNum] = filepath.Join(tmpDir, "sealedc") + + sealTickets[cSectorNum], sealedCid[cSectorNum], unsealedCid[cSectorNum] = manualOnboardingGeneratePreCommit( + ctx, + t, + client, + cacheDirPath[cSectorNum], + unsealedSectorPath[cSectorNum], + sealedSectorPath[cSectorNum], + minerC.ActorAddr, + cSectorNum, + sealRandEpoch, + kit.TestSpt, + ) + } - // Wait for the deadline to come around and submit a PoSt + t.Log("Submitting MinerC PreCommitSector ...") - di, err := client.StateMinerProvingDeadline(ctx, mAddrB, types.EmptyTSK) + r, err = manualOnboardingSubmitMessage(ctx, client, minerC, &miner14.PreCommitSectorBatchParams2{ + Sectors: []miner14.SectorPreCommitInfo{{ + Expiration: 2880 * 300, + SectorNumber: cSectorNum, + SealProof: kit.TestSpt, + SealedCID: sealedCid[cSectorNum], + SealRandEpoch: sealRandEpoch, + }}, + }, 1, builtin.MethodsMiner.PreCommitSectorBatch2) req.NoError(err) - t.Logf("MinerB Deadline Info: %+v", di) + req.True(r.Receipt.ExitCode.IsSuccess()) - // Use the current deadline to work out when the deadline we care about (bSectorDeadline) is open - // and ready to receive posts - deadlineCount := di.WPoStPeriodDeadlines - epochsPerDeadline := uint64(di.WPoStChallengeWindow) - currentDeadline := di.Index - currentDeadlineStart := di.Open - waitTillEpoch := abi.ChainEpoch((deadlineCount-currentDeadline+bSectorDeadline)*epochsPerDeadline) + currentDeadlineStart + 1 + preCommitInfo, err = client.StateSectorPreCommitInfo(ctx, minerC.ActorAddr, cSectorNum, r.TipSet) + req.NoError(err) - t.Logf("Waiting %d until epoch %d to get to deadline %d", waitTillEpoch-di.CurrentEpoch, waitTillEpoch, bSectorDeadline) - head = client.WaitTillChain(ctx, kit.HeightAtLeast(waitTillEpoch)) + // Run prove commit for the sector on miner C - // We should be up to the deadline we care about - di, err = client.StateMinerProvingDeadline(ctx, mAddrB, types.EmptyTSK) - req.NoError(err) - req.Equal(bSectorDeadline, di.Index, "should be in the deadline of the sector to prove") + seedRandomnessHeight = preCommitInfo.PreCommitEpoch + policy.GetPreCommitChallengeDelay() + t.Logf("Waiting %d epochs for seed randomness at epoch %d (current epoch %d)...", seedRandomnessHeight-r.Height, seedRandomnessHeight, r.Height) + client.WaitTillChain(ctx, kit.HeightAtLeast(seedRandomnessHeight+5)) - var proofBytes []byte if withMockProofs { - proofBytes = []byte{0xde, 0xad, 0xbe, 0xef} + sectorProof = []byte{0xde, 0xad, 0xbe, 0xef} } else { - proofBytes = manualOnboardingGenerateWindowPost(ctx, t, client, cacheDirPath, sealedSectorPath, mAddrB, bSectorNum, sealedCid) + sectorProof = manualOnboardingGenerateProveCommit( + ctx, + t, + client, + cacheDirPath[cSectorNum], + sealedSectorPath[cSectorNum], + minerC.ActorAddr, + cSectorNum, + sealedCid[cSectorNum], + unsealedCid[cSectorNum], + sealTickets[cSectorNum], + kit.TestSpt, + ) } - t.Log("Submitting WindowedPoSt...") + t.Log("Submitting MinerC ProveCommitSector ...") + + r, err = manualOnboardingSubmitMessage(ctx, client, minerC, &miner14.ProveCommitSectors3Params{ + SectorActivations: []miner14.SectorActivationManifest{{SectorNumber: cSectorNum}}, + SectorProofs: [][]byte{sectorProof}, + RequireActivationSuccess: true, + }, 0, builtin.MethodsMiner.ProveCommitSectors3) + req.NoError(err) + req.True(r.Receipt.ExitCode.IsSuccess()) - rand, err := client.StateGetRandomnessFromTickets(ctx, crypto.DomainSeparationTag_PoStChainCommit, di.Open, nil, head.Key()) + // Check power after proving, should still be zero until the PoSt is submitted + p, err = client.StateMinerPower(ctx, minerC.ActorAddr, r.TipSet) req.NoError(err) + t.Logf("MinerC RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) + req.True(p.MinerPower.RawBytePower.IsZero()) + + // start a background PoST scheduler for miner C + cFirstCh, cErrCh := manualOnboardingRunWindowPost( + ctx, + withMockProofs, + client, + minerC, + cSectorNum, + cacheDirPath[cSectorNum], + sealedSectorPath[cSectorNum], + sealedCid[cSectorNum], + kit.TestSpt, + ) + + checkPostSchedulers := func() { + t.Helper() + select { + case err, ok := <-bErrCh: + if ok { + t.Fatalf("Received error from Miner B PoST scheduler: %v", err) + } + case err, ok := <-cErrCh: + if ok { + t.Fatalf("Received error from Miner C PoST scheduler: %v", err) + } + default: + } + } - postParams := miner.SubmitWindowedPoStParams{ - ChainCommitEpoch: di.Open, - ChainCommitRand: rand, - Deadline: bSectorDeadline, - Partitions: []miner.PoStPartition{{Index: bSectorPartition}}, - Proofs: []proof.PoStProof{{PoStProof: minerBInfo.WindowPoStProofType, ProofBytes: proofBytes}}, + isClosed := func(ch <-chan struct{}) bool { + select { + case <-ch: + return true + default: + } + return false } - enc = new(bytes.Buffer) - req.NoError(postParams.MarshalCBOR(enc)) + for ctx.Err() == nil { + checkPostSchedulers() + // wait till the first PoST is submitted for both by checking if both bFirstCh and cFirstCh are closed, if so, break, otherwise sleep for 500ms and check again + if isClosed(bFirstCh) && isClosed(cFirstCh) { + break + } + t.Log("Waiting for first PoST to be submitted for all miners ...") + select { + case <-time.After(2 * time.Second): + case <-ctx.Done(): + t.Fatal("Context cancelled") + } + } + + // Fetch on-chain sector properties - m, err = client.MpoolPushMessage(ctx, &types.Message{ - To: mAddrB, - From: minerB.OwnerKey.Address, - Value: types.NewInt(0), - Method: builtin.MethodsMiner.SubmitWindowedPoSt, - Params: enc.Bytes(), - }, nil) + soi, err := client.StateSectorGetInfo(ctx, minerB.ActorAddr, bSectorNum, r.TipSet) req.NoError(err) + t.Logf("Miner B SectorOnChainInfo %d: %+v", bSectorNum, soi) + soi, err = client.StateSectorGetInfo(ctx, minerC.ActorAddr, cSectorNum, r.TipSet) + req.NoError(err) + t.Logf("Miner C SectorOnChainInfo %d: %+v", cSectorNum, soi) - r, err = client.StateWaitMsg(ctx, m.Cid(), 2, api.LookbackNoLimit, true) + head, err = client.ChainHead(ctx) req.NoError(err) - req.True(r.Receipt.ExitCode.IsSuccess()) - if !withMockProofs { - // Dispute the PoSt to confirm the validity of the PoSt since PoSt acceptance is optimistic - manualOnboardingDisputeWindowPost(ctx, t, client, mAddrB, bSectorNum) - } + head = client.WaitTillChain(ctx, kit.HeightAtLeast(head.Height()+5)) + + checkPostSchedulers() t.Log("Checking power after PoSt ...") // Miner B should now have power - p, err = client.StateMinerPower(ctx, mAddrB, r.TipSet) + p, err = client.StateMinerPower(ctx, minerB.ActorAddr, head.Key()) req.NoError(err) t.Logf("MinerB RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) req.Equal(uint64(2<<10), p.MinerPower.RawBytePower.Uint64()) // 2kiB RBP req.Equal(uint64(2<<10), p.MinerPower.QualityAdjPower.Uint64()) // 2kiB QaP + + // Miner C should now have power + p, err = client.StateMinerPower(ctx, minerC.ActorAddr, head.Key()) + req.NoError(err) + t.Logf("MinerC RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) + req.Equal(uint64(2<<10), p.MinerPower.RawBytePower.Uint64()) // 2kiB RBP + req.Equal(uint64(2<<10), p.MinerPower.QualityAdjPower.Uint64()) // 2kiB QaP + + checkPostSchedulers() }) } } @@ -311,16 +401,20 @@ func manualOnboardingGeneratePreCommit( minerAddr address.Address, sectorNumber abi.SectorNumber, sealRandEpoch abi.ChainEpoch, + proofType abi.RegisteredSealProof, ) (abi.SealRandomness, cid.Cid, cid.Cid) { req := require.New(t) - t.Log("Generating PreCommit ...") + t.Logf("Generating proof type %d PreCommit ...", proofType) - sectorSize := abi.SectorSize(2 << 10) + _ = os.Mkdir(cacheDirPath, 0755) unsealedSize := abi.PaddedPieceSize(sectorSize).Unpadded() req.NoError(os.WriteFile(unsealedSectorPath, make([]byte, unsealedSize), 0644)) req.NoError(os.WriteFile(sealedSectorPath, make([]byte, sectorSize), 0644)) + t.Logf("Wrote unsealed sector to %s", unsealedSectorPath) + t.Logf("Wrote sealed sector to %s", sealedSectorPath) + head, err := client.ChainHead(ctx) req.NoError(err) @@ -331,14 +425,14 @@ func manualOnboardingGeneratePreCommit( req.NoError(err) sealTickets := abi.SealRandomness(rand) - t.Logf("Running SealPreCommitPhase1 for sector %d...", sectorNumber) + t.Logf("Running proof type %d SealPreCommitPhase1 for sector %d...", proofType, sectorNumber) actorIdNum, err := address.IDFromAddress(minerAddr) req.NoError(err) actorId := abi.ActorID(actorIdNum) pc1, err := ffi.SealPreCommitPhase1( - kit.TestSpt, + proofType, cacheDirPath, unsealedSectorPath, sealedSectorPath, @@ -350,7 +444,7 @@ func manualOnboardingGeneratePreCommit( req.NoError(err) req.NotNil(pc1) - t.Logf("Running SealPreCommitPhase2 for sector %d...", sectorNumber) + t.Logf("Running proof type %d SealPreCommitPhase2 for sector %d...", proofType, sectorNumber) sealedCid, unsealedCid, err := ffi.SealPreCommitPhase2( pc1, @@ -375,18 +469,20 @@ func manualOnboardingGenerateProveCommit( sectorNumber abi.SectorNumber, sealedCid, unsealedCid cid.Cid, sealTickets abi.SealRandomness, + proofType abi.RegisteredSealProof, ) []byte { req := require.New(t) - t.Log("Generating Sector Proof ...") + t.Logf("Generating proof type %d Sector Proof ...", proofType) head, err := client.ChainHead(ctx) req.NoError(err) + var seedRandomnessHeight abi.ChainEpoch + preCommitInfo, err := client.StateSectorPreCommitInfo(ctx, minerAddr, sectorNumber, head.Key()) req.NoError(err) - - seedRandomnessHeight := preCommitInfo.PreCommitEpoch + policy.GetPreCommitChallengeDelay() + seedRandomnessHeight = preCommitInfo.PreCommitEpoch + policy.GetPreCommitChallengeDelay() minerAddrBytes := new(bytes.Buffer) req.NoError(minerAddr.MarshalCBOR(minerAddrBytes)) @@ -399,10 +495,10 @@ func manualOnboardingGenerateProveCommit( req.NoError(err) actorId := abi.ActorID(actorIdNum) - t.Logf("Running SealCommitPhase1 for sector %d...", sectorNumber) + t.Logf("Running proof type %d SealCommitPhase1 for sector %d...", proofType, sectorNumber) scp1, err := ffi.SealCommitPhase1( - kit.TestSpt, + proofType, sealedCid, unsealedCid, cacheDirPath, @@ -415,47 +511,57 @@ func manualOnboardingGenerateProveCommit( ) req.NoError(err) - t.Logf("Running SealCommitPhase2 for sector %d...", sectorNumber) + t.Logf("Running proof type %d SealCommitPhase2 for sector %d...", proofType, sectorNumber) sectorProof, err := ffi.SealCommitPhase2(scp1, sectorNumber, actorId) req.NoError(err) + t.Logf("Got proof type %d sector proof of length %d", proofType, len(sectorProof)) + return sectorProof } func manualOnboardingGenerateWindowPost( ctx context.Context, - t *testing.T, client api.FullNode, cacheDirPath string, sealedSectorPath string, minerAddr address.Address, sectorNumber abi.SectorNumber, sealedCid cid.Cid, -) []byte { - - req := require.New(t) + proofType abi.RegisteredSealProof, +) ([]byte, error) { head, err := client.ChainHead(ctx) - req.NoError(err) + if err != nil { + return nil, fmt.Errorf("failed to get chain head: %w", err) + } minerInfo, err := client.StateMinerInfo(ctx, minerAddr, head.Key()) - req.NoError(err) + if err != nil { + return nil, fmt.Errorf("failed to get miner info: %w", err) + } di, err := client.StateMinerProvingDeadline(ctx, minerAddr, types.EmptyTSK) - req.NoError(err) + if err != nil { + return nil, fmt.Errorf("failed to get proving deadline: %w", err) + } minerAddrBytes := new(bytes.Buffer) - req.NoError(minerAddr.MarshalCBOR(minerAddrBytes)) + if err := minerAddr.MarshalCBOR(minerAddrBytes); err != nil { + return nil, fmt.Errorf("failed to marshal miner address: %w", err) + } rand, err := client.StateGetRandomnessFromBeacon(ctx, crypto.DomainSeparationTag_WindowedPoStChallengeSeed, di.Challenge, minerAddrBytes.Bytes(), head.Key()) - req.NoError(err) + if err != nil { + return nil, fmt.Errorf("failed to get randomness: %w", err) + } postRand := abi.PoStRandomness(rand) postRand[31] &= 0x3f // make fr32 compatible privateSectorInfo := ffi.PrivateSectorInfo{ SectorInfo: proof.SectorInfo{ - SealProof: kit.TestSpt, + SealProof: proofType, SectorNumber: sectorNumber, SealedCID: sealedCid, }, @@ -465,70 +571,311 @@ func manualOnboardingGenerateWindowPost( } actorIdNum, err := address.IDFromAddress(minerAddr) - req.NoError(err) + if err != nil { + return nil, fmt.Errorf("failed to get actor ID: %w", err) + } actorId := abi.ActorID(actorIdNum) windowProofs, faultySectors, err := ffi.GenerateWindowPoSt(actorId, ffi.NewSortedPrivateSectorInfo(privateSectorInfo), postRand) - req.NoError(err) - req.Len(faultySectors, 0) - req.Len(windowProofs, 1) - req.Equal(minerInfo.WindowPoStProofType, windowProofs[0].PoStProof) + if err != nil { + return nil, fmt.Errorf("failed to generate window post: %w", err) + } + if len(faultySectors) > 0 { + return nil, fmt.Errorf("post failed for sectors: %v", faultySectors) + } + if len(windowProofs) != 1 { + return nil, fmt.Errorf("expected 1 proof, got %d", len(windowProofs)) + } + if windowProofs[0].PoStProof != minerInfo.WindowPoStProofType { + return nil, fmt.Errorf("expected proof type %d, got %d", minerInfo.WindowPoStProofType, windowProofs[0].PoStProof) + } proofBytes := windowProofs[0].ProofBytes info := proof.WindowPoStVerifyInfo{ Randomness: postRand, Proofs: []proof.PoStProof{{PoStProof: minerInfo.WindowPoStProofType, ProofBytes: proofBytes}}, - ChallengedSectors: []proof.SectorInfo{{SealProof: kit.TestSpt, SectorNumber: sectorNumber, SealedCID: sealedCid}}, + ChallengedSectors: []proof.SectorInfo{{SealProof: proofType, SectorNumber: sectorNumber, SealedCID: sealedCid}}, Prover: actorId, } verified, err := ffi.VerifyWindowPoSt(info) - req.NoError(err) - req.True(verified, "window post verification failed") + if err != nil { + return nil, fmt.Errorf("failed to verify window post: %w", err) + } + if !verified { + return nil, fmt.Errorf("window post verification failed") + } - return proofBytes + return proofBytes, nil } func manualOnboardingDisputeWindowPost( ctx context.Context, - t *testing.T, client kit.TestFullNode, - minerAddr address.Address, + miner kit.TestUnmanagedMiner, sectorNumber abi.SectorNumber, -) { +) error { - req := require.New(t) + head, err := client.ChainHead(ctx) + if err != nil { + return fmt.Errorf("failed to get chain head: %w", err) + } + + sp, err := client.StateSectorPartition(ctx, miner.ActorAddr, sectorNumber, head.Key()) + if err != nil { + return fmt.Errorf("failed to get sector partition: %w", err) + } + + di, err := client.StateMinerProvingDeadline(ctx, miner.ActorAddr, head.Key()) + if err != nil { + return fmt.Errorf("failed to get proving deadline: %w", err) + } + + disputeEpoch := di.Close + 5 + fmt.Printf("WindowPoST(%d): Dispute: Waiting %d until epoch %d to submit dispute\n", sectorNumber, disputeEpoch-head.Height(), disputeEpoch) + + client.WaitTillChain(ctx, kit.HeightAtLeast(disputeEpoch)) + + fmt.Printf("WindowPoST(%d): Dispute: Disputing WindowedPoSt to confirm validity...\n", sectorNumber) + + _, err = manualOnboardingSubmitMessage(ctx, client, miner, &miner14.DisputeWindowedPoStParams{ + Deadline: sp.Deadline, + PoStIndex: 0, + }, 0, builtin.MethodsMiner.DisputeWindowedPoSt) + if err == nil { + return fmt.Errorf("expected dispute to fail") + } + if !strings.Contains(err.Error(), "failed to dispute valid post") { + return fmt.Errorf("expected dispute to fail with 'failed to dispute valid post', got: %w", err) + } + if !strings.Contains(err.Error(), "(RetCode=16)") { + return fmt.Errorf("expected dispute to fail with RetCode=16, got: %w", err) + } + return nil +} + +func manualOnboardingSubmitMessage( + ctx context.Context, + client api.FullNode, + from kit.TestUnmanagedMiner, + params cbg.CBORMarshaler, + value uint64, + method abi.MethodNum, +) (*api.MsgLookup, error) { + + enc, aerr := actors.SerializeParams(params) + if aerr != nil { + return nil, fmt.Errorf("failed to serialize params: %w", aerr) + } + + m, err := client.MpoolPushMessage(ctx, &types.Message{ + To: from.ActorAddr, + From: from.OwnerKey.Address, + Value: types.FromFil(value), + Method: method, + Params: enc, + }, nil) + if err != nil { + return nil, fmt.Errorf("failed to push message: %w", err) + } + + return client.StateWaitMsg(ctx, m.Cid(), 2, api.LookbackNoLimit, true) +} + +// manualOnboardingCalculateNextPostEpoch calculates the first epoch of the deadline proving window +// that we want for the given sector for the given miner. +func manualOnboardingCalculateNextPostEpoch( + ctx context.Context, + client api.FullNode, + minerAddr address.Address, + sectorNumber abi.SectorNumber, +) (abi.ChainEpoch, abi.ChainEpoch, error) { head, err := client.ChainHead(ctx) - req.NoError(err) + if err != nil { + return 0, 0, fmt.Errorf("failed to get chain head: %w", err) + } sp, err := client.StateSectorPartition(ctx, minerAddr, sectorNumber, head.Key()) - req.NoError(err) + if err != nil { + return 0, 0, fmt.Errorf("failed to get sector partition: %w", err) + } + + fmt.Printf("WindowPoST(%d): SectorPartition: %+v\n", sectorNumber, sp) di, err := client.StateMinerProvingDeadline(ctx, minerAddr, head.Key()) - req.NoError(err) + if err != nil { + return 0, 0, fmt.Errorf("failed to get proving deadline: %w", err) + } - disputeEpoch := di.Close + 5 - t.Logf("Waiting %d until epoch %d to submit dispute", disputeEpoch-head.Height(), disputeEpoch) + fmt.Printf("WindowPoST(%d): ProvingDeadline: %+v\n", sectorNumber, di) - client.WaitTillChain(ctx, kit.HeightAtLeast(disputeEpoch)) + // periodStart tells us the first epoch of the current proving period (24h) + // although it may be in the future if we don't need to submit post in this period + periodStart := di.PeriodStart + if di.PeriodStart < di.CurrentEpoch && sp.Deadline <= di.Index { + // the deadline we want has past in this current proving period, so wait till the next one + periodStart += di.WPoStProvingPeriod + } + provingEpoch := periodStart + (di.WPoStProvingPeriod/abi.ChainEpoch(di.WPoStPeriodDeadlines))*abi.ChainEpoch(sp.Deadline) + + return di.CurrentEpoch, provingEpoch, nil +} + +func manualOnboardingSubmitWindowPost( + ctx context.Context, + withMockProofs bool, + client kit.TestFullNode, + miner kit.TestUnmanagedMiner, + sectorNumber abi.SectorNumber, + cacheDirPath, sealedSectorPath string, + sealedCid cid.Cid, + proofType abi.RegisteredSealProof, +) error { + fmt.Printf("WindowPoST(%d): Running WindowPoSt ...\n", sectorNumber) + + head, err := client.ChainHead(ctx) + if err != nil { + return fmt.Errorf("failed to get chain head: %w", err) + } + + sp, err := client.StateSectorPartition(ctx, miner.ActorAddr, sectorNumber, head.Key()) + if err != nil { + return fmt.Errorf("failed to get sector partition: %w", err) + } + + // We should be up to the deadline we care about + di, err := client.StateMinerProvingDeadline(ctx, miner.ActorAddr, head.Key()) + if err != nil { + return fmt.Errorf("failed to get proving deadline: %w", err) + } + fmt.Printf("WindowPoST(%d): SectorPartition: %+v, ProvingDeadline: %+v\n", sectorNumber, sp, di) + if di.Index != sp.Deadline { + return fmt.Errorf("sector %d is not in the deadline %d, but %d", sectorNumber, sp.Deadline, di.Index) + } + + var proofBytes []byte + if withMockProofs { + proofBytes = []byte{0xde, 0xad, 0xbe, 0xef} + } else { + proofBytes, err = manualOnboardingGenerateWindowPost(ctx, client, cacheDirPath, sealedSectorPath, miner.ActorAddr, sectorNumber, sealedCid, proofType) + if err != nil { + return fmt.Errorf("failed to generate window post: %w", err) + } + } + + fmt.Printf("WindowedPoSt(%d) Submitting ...\n", sectorNumber) + + chainRandomnessEpoch := di.Challenge + chainRandomness, err := client.StateGetRandomnessFromTickets(ctx, crypto.DomainSeparationTag_PoStChainCommit, chainRandomnessEpoch, nil, head.Key()) + if err != nil { + return fmt.Errorf("failed to get chain randomness: %w", err) + } - t.Logf("Disputing WindowedPoSt to confirm validity...") + minerInfo, err := client.StateMinerInfo(ctx, miner.ActorAddr, head.Key()) + if err != nil { + return fmt.Errorf("failed to get miner info: %w", err) + } - disputeParams := &miner14.DisputeWindowedPoStParams{Deadline: sp.Deadline, PoStIndex: 0} - enc := new(bytes.Buffer) - req.NoError(disputeParams.MarshalCBOR(enc)) + r, err := manualOnboardingSubmitMessage(ctx, client, miner, &miner14.SubmitWindowedPoStParams{ + ChainCommitEpoch: chainRandomnessEpoch, + ChainCommitRand: chainRandomness, + Deadline: sp.Deadline, + Partitions: []miner14.PoStPartition{{Index: sp.Partition}}, + Proofs: []proof.PoStProof{{PoStProof: minerInfo.WindowPoStProofType, ProofBytes: proofBytes}}, + }, 0, builtin.MethodsMiner.SubmitWindowedPoSt) + if err != nil { + return fmt.Errorf("failed to submit PoSt: %w", err) + } + if !r.Receipt.ExitCode.IsSuccess() { + return fmt.Errorf("submitting PoSt failed: %s", r.Receipt.ExitCode) + } - disputeMsg := &types.Message{ - To: minerAddr, - Method: builtin.MethodsMiner.DisputeWindowedPoSt, - Params: enc.Bytes(), - Value: types.NewInt(0), - From: client.DefaultKey.Address, + if !withMockProofs { + // Dispute the PoSt to confirm the validity of the PoSt since PoSt acceptance is optimistic + if err := manualOnboardingDisputeWindowPost(ctx, client, miner, sectorNumber); err != nil { + return fmt.Errorf("failed to dispute PoSt: %w", err) + } } + return nil +} + +// manualOnboardingRunWindowPost runs a goroutine to continually submit PoSTs for the given sector +// and miner. It will wait until the next proving period for the sector and then submit the PoSt. +// It will continue to do this until the context is cancelled. +// It returns a channel that will be closed when the first PoSt is submitted and a channel that will +// receive any errors that occur. +func manualOnboardingRunWindowPost( + ctx context.Context, + withMockProofs bool, + client kit.TestFullNode, + miner kit.TestUnmanagedMiner, + sectorNumber abi.SectorNumber, + cacheDirPath, + sealedSectorPath string, + sealedCid cid.Cid, + proofType abi.RegisteredSealProof, +) (chan struct{}, chan error) { + + first := make(chan struct{}) + errCh := make(chan error) + + go func() { + for ctx.Err() == nil { + currentEpoch, nextPost, err := manualOnboardingCalculateNextPostEpoch(ctx, client, miner.ActorAddr, sectorNumber) + if err != nil { + errCh <- err + return + } + if ctx.Err() != nil { + return + } + nextPost += 5 // give a little buffer + fmt.Printf("WindowPoST(%d) Waiting %d until epoch %d to submit PoSt\n", sectorNumber, nextPost-currentEpoch, nextPost) + + // Create channel to listen for chain head + heads, err := client.ChainNotify(ctx) + if err != nil { + errCh <- err + return + } + // Wait for nextPost epoch + for chg := range heads { + var ts *types.TipSet + for _, c := range chg { + if c.Type != "apply" { + continue + } + ts = c.Val + if ts.Height() >= nextPost { + break + } + } + if ctx.Err() != nil { + return + } + if ts != nil && ts.Height() >= nextPost { + break + } + } + if ctx.Err() != nil { + return + } + + err = manualOnboardingSubmitWindowPost(ctx, withMockProofs, client, miner, sectorNumber, cacheDirPath, sealedSectorPath, sealedCid, proofType) + if err != nil { + errCh <- err + return + } + + // signal first post is done + select { + case <-first: + default: + close(first) + } + } + }() - _, err = client.MpoolPushMessage(ctx, disputeMsg, nil) - req.Error(err, "expected dispute to fail") - req.Contains(err.Error(), "failed to dispute valid post") - req.Contains(err.Error(), "(RetCode=16)") + return first, errCh } From 184764f759b6c9ff8f74727dfd965ec47666efd2 Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Mon, 27 May 2024 19:49:28 +0530 Subject: [PATCH 06/20] miner tests without NI-Porep --- itests/manual_onboarding_test.go | 127 ++----------------------------- 1 file changed, 8 insertions(+), 119 deletions(-) diff --git a/itests/manual_onboarding_test.go b/itests/manual_onboarding_test.go index b4dcd8b3cb6..b270698762b 100644 --- a/itests/manual_onboarding_test.go +++ b/itests/manual_onboarding_test.go @@ -51,8 +51,7 @@ func TestManualCCOnboarding(t *testing.T) { client kit.TestFullNode minerA kit.TestMiner // A is a standard genesis miner - minerB kit.TestUnmanagedMiner // B is a CC miner we will onboard manually - minerC kit.TestUnmanagedMiner // C is a CC miner we will onboard manually (will be used for NI-PoRep) + minerB kit.TestUnmanagedMiner // B is a CC miner we will onboard manually without NI-Porep // TODO: single sector per miner for now, but this isn't going to scale when refactored into // TestUnmanagedMiner - they'll need their own list of sectors and their own copy of each of @@ -71,7 +70,6 @@ func TestManualCCOnboarding(t *testing.T) { // sector numbers, make them unique for each miner so our maps work bSectorNum = abi.SectorNumber(22) - cSectorNum = abi.SectorNumber(33) tmpDir = t.TempDir() @@ -100,7 +98,6 @@ func TestManualCCOnboarding(t *testing.T) { nodeOpts = append(nodeOpts, kit.OwnerAddr(client.DefaultKey)) ens.UnmanagedMiner(&minerB, &client, nodeOpts...).Start() - ens.UnmanagedMiner(&minerC, &client, nodeOpts...).Start() build.Clock.Sleep(time.Second) @@ -120,12 +117,6 @@ func TestManualCCOnboarding(t *testing.T) { t.Logf("MinerB RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) req.True(p.MinerPower.RawBytePower.IsZero()) - // Miner C should have no power - p, err = client.StateMinerPower(ctx, minerC.ActorAddr, head.Key()) - req.NoError(err) - t.Logf("MinerC RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) - req.True(p.MinerPower.RawBytePower.IsZero()) - // Run precommit for a sector on miner B t.Logf("Waiting for at least epoch %d for seal randomness (current epoch %d) ...", sealRandEpoch+5, head.Height()) @@ -223,99 +214,6 @@ func TestManualCCOnboarding(t *testing.T) { kit.TestSpt, ) - // Miner C (will be used for NI-PoRep) - - if withMockProofs { - sealedCid[cSectorNum] = cid.MustParse("bagboea4b5abcatlxechwbp7kjpjguna6r6q7ejrhe6mdp3lf34pmswn27pkkiekz") - } else { - cacheDirPath[cSectorNum] = filepath.Join(tmpDir, "cachec") - unsealedSectorPath[cSectorNum] = filepath.Join(tmpDir, "unsealedc") - sealedSectorPath[cSectorNum] = filepath.Join(tmpDir, "sealedc") - - sealTickets[cSectorNum], sealedCid[cSectorNum], unsealedCid[cSectorNum] = manualOnboardingGeneratePreCommit( - ctx, - t, - client, - cacheDirPath[cSectorNum], - unsealedSectorPath[cSectorNum], - sealedSectorPath[cSectorNum], - minerC.ActorAddr, - cSectorNum, - sealRandEpoch, - kit.TestSpt, - ) - } - - t.Log("Submitting MinerC PreCommitSector ...") - - r, err = manualOnboardingSubmitMessage(ctx, client, minerC, &miner14.PreCommitSectorBatchParams2{ - Sectors: []miner14.SectorPreCommitInfo{{ - Expiration: 2880 * 300, - SectorNumber: cSectorNum, - SealProof: kit.TestSpt, - SealedCID: sealedCid[cSectorNum], - SealRandEpoch: sealRandEpoch, - }}, - }, 1, builtin.MethodsMiner.PreCommitSectorBatch2) - req.NoError(err) - req.True(r.Receipt.ExitCode.IsSuccess()) - - preCommitInfo, err = client.StateSectorPreCommitInfo(ctx, minerC.ActorAddr, cSectorNum, r.TipSet) - req.NoError(err) - - // Run prove commit for the sector on miner C - - seedRandomnessHeight = preCommitInfo.PreCommitEpoch + policy.GetPreCommitChallengeDelay() - t.Logf("Waiting %d epochs for seed randomness at epoch %d (current epoch %d)...", seedRandomnessHeight-r.Height, seedRandomnessHeight, r.Height) - client.WaitTillChain(ctx, kit.HeightAtLeast(seedRandomnessHeight+5)) - - if withMockProofs { - sectorProof = []byte{0xde, 0xad, 0xbe, 0xef} - } else { - sectorProof = manualOnboardingGenerateProveCommit( - ctx, - t, - client, - cacheDirPath[cSectorNum], - sealedSectorPath[cSectorNum], - minerC.ActorAddr, - cSectorNum, - sealedCid[cSectorNum], - unsealedCid[cSectorNum], - sealTickets[cSectorNum], - kit.TestSpt, - ) - } - - t.Log("Submitting MinerC ProveCommitSector ...") - - r, err = manualOnboardingSubmitMessage(ctx, client, minerC, &miner14.ProveCommitSectors3Params{ - SectorActivations: []miner14.SectorActivationManifest{{SectorNumber: cSectorNum}}, - SectorProofs: [][]byte{sectorProof}, - RequireActivationSuccess: true, - }, 0, builtin.MethodsMiner.ProveCommitSectors3) - req.NoError(err) - req.True(r.Receipt.ExitCode.IsSuccess()) - - // Check power after proving, should still be zero until the PoSt is submitted - p, err = client.StateMinerPower(ctx, minerC.ActorAddr, r.TipSet) - req.NoError(err) - t.Logf("MinerC RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) - req.True(p.MinerPower.RawBytePower.IsZero()) - - // start a background PoST scheduler for miner C - cFirstCh, cErrCh := manualOnboardingRunWindowPost( - ctx, - withMockProofs, - client, - minerC, - cSectorNum, - cacheDirPath[cSectorNum], - sealedSectorPath[cSectorNum], - sealedCid[cSectorNum], - kit.TestSpt, - ) - checkPostSchedulers := func() { t.Helper() select { @@ -323,10 +221,6 @@ func TestManualCCOnboarding(t *testing.T) { if ok { t.Fatalf("Received error from Miner B PoST scheduler: %v", err) } - case err, ok := <-cErrCh: - if ok { - t.Fatalf("Received error from Miner C PoST scheduler: %v", err) - } default: } } @@ -342,8 +236,8 @@ func TestManualCCOnboarding(t *testing.T) { for ctx.Err() == nil { checkPostSchedulers() - // wait till the first PoST is submitted for both by checking if both bFirstCh and cFirstCh are closed, if so, break, otherwise sleep for 500ms and check again - if isClosed(bFirstCh) && isClosed(cFirstCh) { + // wait till the first PoST is submitted for MinerB and check again + if isClosed(bFirstCh) { break } t.Log("Waiting for first PoST to be submitted for all miners ...") @@ -359,9 +253,6 @@ func TestManualCCOnboarding(t *testing.T) { soi, err := client.StateSectorGetInfo(ctx, minerB.ActorAddr, bSectorNum, r.TipSet) req.NoError(err) t.Logf("Miner B SectorOnChainInfo %d: %+v", bSectorNum, soi) - soi, err = client.StateSectorGetInfo(ctx, minerC.ActorAddr, cSectorNum, r.TipSet) - req.NoError(err) - t.Logf("Miner C SectorOnChainInfo %d: %+v", cSectorNum, soi) head, err = client.ChainHead(ctx) req.NoError(err) @@ -379,13 +270,6 @@ func TestManualCCOnboarding(t *testing.T) { req.Equal(uint64(2<<10), p.MinerPower.RawBytePower.Uint64()) // 2kiB RBP req.Equal(uint64(2<<10), p.MinerPower.QualityAdjPower.Uint64()) // 2kiB QaP - // Miner C should now have power - p, err = client.StateMinerPower(ctx, minerC.ActorAddr, head.Key()) - req.NoError(err) - t.Logf("MinerC RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) - req.Equal(uint64(2<<10), p.MinerPower.RawBytePower.Uint64()) // 2kiB RBP - req.Equal(uint64(2<<10), p.MinerPower.QualityAdjPower.Uint64()) // 2kiB QaP - checkPostSchedulers() }) } @@ -518,6 +402,11 @@ func manualOnboardingGenerateProveCommit( t.Logf("Got proof type %d sector proof of length %d", proofType, len(sectorProof)) + /* this variant would be used for aggregating NI-PoRep proofs + sectorProof, err := ffi.SealCommitPhase2CircuitProofs(scp1, sectorNumber) + req.NoError(err) + */ + return sectorProof } From e0bd599f1650385b1d92c1c70eb20fd40f80ba9c Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Tue, 28 May 2024 10:25:52 +0530 Subject: [PATCH 07/20] refactor --- itests/kit/node_unmanaged.go | 208 +++++++++++++++++++++++++++++++ itests/manual_onboarding_test.go | 1 - 2 files changed, 208 insertions(+), 1 deletion(-) diff --git a/itests/kit/node_unmanaged.go b/itests/kit/node_unmanaged.go index 0f27052406d..f1284dd5a0c 100644 --- a/itests/kit/node_unmanaged.go +++ b/itests/kit/node_unmanaged.go @@ -1,6 +1,16 @@ package kit import ( + "context" + "fmt" + "github.com/filecoin-project/go-state-types/abi" + miner14 "github.com/filecoin-project/go-state-types/builtin/v14/miner" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/types" + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" "testing" "github.com/filecoin-project/go-address" @@ -24,3 +34,201 @@ type TestUnmanagedMiner struct { PrivKey libp2pcrypto.PrivKey } } + +func (tm *TestUnmanagedMiner) StartWindowedPostLoop(ctx context.Context, sectorNumber abi.SectorNumber, proofType abi.RegisteredSealProof) { + tm.t.Helper() + + go func() { + for ctx.Err() == nil { + currentEpoch, nextPost, err := tm.manualOnboardingCalculateNextPostEpoch(ctx, sectorNumber) + if err != nil { + errCh <- err + return + } + if ctx.Err() != nil { + return + } + nextPost += 5 // give a little buffer + fmt.Printf("WindowPoST(%d) Waiting %d until epoch %d to submit PoSt\n", sectorNumber, nextPost-currentEpoch, nextPost) + + // Create channel to listen for chain head + heads, err := tm.FullNode.ChainNotify(ctx) + if err != nil { + errCh <- err + return + } + // Wait for nextPost epoch + for chg := range heads { + var ts *types.TipSet + for _, c := range chg { + if c.Type != "apply" { + continue + } + ts = c.Val + if ts.Height() >= nextPost { + break + } + } + if ctx.Err() != nil { + return + } + if ts != nil && ts.Height() >= nextPost { + break + } + } + if ctx.Err() != nil { + return + } + + err = manualOnboardingSubmitWindowPost(ctx, withMockProofs, client, miner, sectorNumber, cacheDirPath, sealedSectorPath, sealedCid, proofType) + if err != nil { + errCh <- err + return + } + + // signal first post is done + select { + case <-first: + default: + close(first) + } + } + }() +} + +func (tm *TestUnmanagedMiner) manualOnboardingCalculateNextPostEpoch( + ctx context.Context, + sectorNumber abi.SectorNumber, +) (abi.ChainEpoch, abi.ChainEpoch, error) { + + head, err := tm.FullNode.ChainHead(ctx) + if err != nil { + return 0, 0, fmt.Errorf("failed to get chain head: %w", err) + } + + sp, err := tm.FullNode.StateSectorPartition(ctx, tm.ActorAddr, sectorNumber, head.Key()) + if err != nil { + return 0, 0, fmt.Errorf("failed to get sector partition: %w", err) + } + + fmt.Printf("WindowPoST(%d): SectorPartition: %+v\n", sectorNumber, sp) + + di, err := tm.FullNode.StateMinerProvingDeadline(ctx, tm.ActorAddr, head.Key()) + if err != nil { + return 0, 0, fmt.Errorf("failed to get proving deadline: %w", err) + } + + fmt.Printf("WindowPoST(%d): ProvingDeadline: %+v\n", sectorNumber, di) + + // periodStart tells us the first epoch of the current proving period (24h) + // although it may be in the future if we don't need to submit post in this period + periodStart := di.PeriodStart + if di.PeriodStart < di.CurrentEpoch && sp.Deadline <= di.Index { + // the deadline we want has past in this current proving period, so wait till the next one + periodStart += di.WPoStProvingPeriod + } + provingEpoch := periodStart + (di.WPoStProvingPeriod/abi.ChainEpoch(di.WPoStPeriodDeadlines))*abi.ChainEpoch(sp.Deadline) + + return di.CurrentEpoch, provingEpoch, nil +} + +func (tm *TestUnmanagedMiner) manualOnboardingSubmitWindowPost( + ctx context.Context, + withMockProofs bool, + sectorNumber abi.SectorNumber, + cacheDirPath, sealedSectorPath string, + sealedCid cid.Cid, + proofType abi.RegisteredSealProof, +) error { + fmt.Printf("WindowPoST(%d): Running WindowPoSt ...\n", sectorNumber) + + head, err := tm.FullNode.ChainHead(ctx) + if err != nil { + return fmt.Errorf("failed to get chain head: %w", err) + } + + sp, err := tm.FullNode.StateSectorPartition(ctx, tm.ActorAddr, sectorNumber, head.Key()) + if err != nil { + return fmt.Errorf("failed to get sector partition: %w", err) + } + + // We should be up to the deadline we care about + di, err := tm.FullNode.StateMinerProvingDeadline(ctx, tm.ActorAddr, head.Key()) + if err != nil { + return fmt.Errorf("failed to get proving deadline: %w", err) + } + fmt.Printf("WindowPoST(%d): SectorPartition: %+v, ProvingDeadline: %+v\n", sectorNumber, sp, di) + if di.Index != sp.Deadline { + return fmt.Errorf("sector %d is not in the deadline %d, but %d", sectorNumber, sp.Deadline, di.Index) + } + + var proofBytes []byte + if withMockProofs { + proofBytes = []byte{0xde, 0xad, 0xbe, 0xef} + } else { + proofBytes, err = manualOnboardingGenerateWindowPost(ctx, tm.FullNode, cacheDirPath, sealedSectorPath, tm.ActorAddr, sectorNumber, sealedCid, proofType) + if err != nil { + return fmt.Errorf("failed to generate window post: %w", err) + } + } + + fmt.Printf("WindowedPoSt(%d) Submitting ...\n", sectorNumber) + + chainRandomnessEpoch := di.Challenge + chainRandomness, err := tm.FullNode.StateGetRandomnessFromTickets(ctx, crypto.DomainSeparationTag_PoStChainCommit, chainRandomnessEpoch, nil, head.Key()) + if err != nil { + return fmt.Errorf("failed to get chain randomness: %w", err) + } + + minerInfo, err := tm.FullNode.StateMinerInfo(ctx, tm.ActorAddr, head.Key()) + if err != nil { + return fmt.Errorf("failed to get miner info: %w", err) + } + + r, err := manualOnboardingSubmitMessage(ctx, tm.FullNode, miner, &miner14.SubmitWindowedPoStParams{ + ChainCommitEpoch: chainRandomnessEpoch, + ChainCommitRand: chainRandomness, + Deadline: sp.Deadline, + Partitions: []miner14.PoStPartition{{Index: sp.Partition}}, + Proofs: []proof.PoStProof{{PoStProof: minerInfo.WindowPoStProofType, ProofBytes: proofBytes}}, + }, 0, builtin.MethodsMiner.SubmitWindowedPoSt) + if err != nil { + return fmt.Errorf("failed to submit PoSt: %w", err) + } + if !r.Receipt.ExitCode.IsSuccess() { + return fmt.Errorf("submitting PoSt failed: %s", r.Receipt.ExitCode) + } + + if !withMockProofs { + // Dispute the PoSt to confirm the validity of the PoSt since PoSt acceptance is optimistic + if err := manualOnboardingDisputeWindowPost(ctx, client, miner, sectorNumber); err != nil { + return fmt.Errorf("failed to dispute PoSt: %w", err) + } + } + return nil +} + +func (tm *TestUnmanagedMiner) manualOnboardingSubmitMessage( + ctx context.Context, + params cbg.CBORMarshaler, + value uint64, + method abi.MethodNum, +) (*api.MsgLookup, error) { + enc, aerr := actors.SerializeParams(params) + if aerr != nil { + return nil, fmt.Errorf("failed to serialize params: %w", aerr) + } + + m, err := tm.FullNode.MpoolPushMessage(ctx, &types.Message{ + To: tm.ActorAddr, + From: tm.OwnerKey.Address, + Value: types.FromFil(value), + Method: method, + Params: enc, + }, nil) + if err != nil { + return nil, fmt.Errorf("failed to push message: %w", err) + } + + return tm.FullNode.StateWaitMsg(ctx, m.Cid(), 2, api.LookbackNoLimit, true) +} diff --git a/itests/manual_onboarding_test.go b/itests/manual_onboarding_test.go index b270698762b..faca6ee2928 100644 --- a/itests/manual_onboarding_test.go +++ b/itests/manual_onboarding_test.go @@ -705,7 +705,6 @@ func manualOnboardingRunWindowPost( sealedCid cid.Cid, proofType abi.RegisteredSealProof, ) (chan struct{}, chan error) { - first := make(chan struct{}) errCh := make(chan error) From 4c87284674785a7ae0d14863ca058e8415a78b8d Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Tue, 28 May 2024 15:21:18 +0530 Subject: [PATCH 08/20] refactor stage 1 --- itests/kit/ensemble.go | 42 +-- itests/kit/node_unmanaged.go | 486 +++++++++++++++++++++---------- itests/manual_onboarding_test.go | 307 ++----------------- 3 files changed, 368 insertions(+), 467 deletions(-) diff --git a/itests/kit/ensemble.go b/itests/kit/ensemble.go index 55e6dcf198a..bc078907161 100644 --- a/itests/kit/ensemble.go +++ b/itests/kit/ensemble.go @@ -329,39 +329,6 @@ func (n *Ensemble) MinerEnroll(minerNode *TestMiner, full *TestFullNode, opts .. return n } -func (n *Ensemble) UnmanagedMinerEnroll(minerNode *TestUnmanagedMiner, full *TestFullNode, opts ...NodeOpt) *Ensemble { - require.NotNil(n.t, full, "full node required when instantiating miner") - - options := DefaultNodeOpts - for _, o := range opts { - err := o(&options) - require.NoError(n.t, err) - } - - privkey, _, err := libp2pcrypto.GenerateEd25519Key(rand.Reader) - require.NoError(n.t, err) - - peerId, err := peer.IDFromPrivateKey(privkey) - require.NoError(n.t, err) - - actorAddr, err := address.NewIDAddress(genesis2.MinerStart + n.minerCount()) - require.NoError(n.t, err) - - require.NotNil(n.t, options.ownerKey, "manual miner key can't be null if initializing a miner after genesis") - - *minerNode = TestUnmanagedMiner{ - t: n.t, - options: options, - ActorAddr: actorAddr, - OwnerKey: options.ownerKey, - FullNode: full, - } - minerNode.Libp2p.PeerID = peerId - minerNode.Libp2p.PrivKey = privkey - - return n -} - func (n *Ensemble) AddInactiveMiner(m *TestMiner) { n.inactive.miners = append(n.inactive.miners, m) } @@ -376,10 +343,13 @@ func (n *Ensemble) Miner(minerNode *TestMiner, full *TestFullNode, opts ...NodeO return n } -func (n *Ensemble) UnmanagedMiner(minerNode *TestUnmanagedMiner, full *TestFullNode, opts ...NodeOpt) *Ensemble { - n.UnmanagedMinerEnroll(minerNode, full, opts...) +func (n *Ensemble) UnmanagedMiner(full *TestFullNode, opts ...NodeOpt) (*TestUnmanagedMiner, *Ensemble) { + actorAddr, err := address.NewIDAddress(genesis2.MinerStart + n.minerCount()) + require.NoError(n.t, err) + + minerNode := NewTestUnmanagedMiner(n.t, full, actorAddr, opts...) n.AddInactiveUnmanagedMiner(minerNode) - return n + return minerNode, n } // Worker enrolls a new worker, using the provided full node for chain diff --git a/itests/kit/node_unmanaged.go b/itests/kit/node_unmanaged.go index f1284dd5a0c..202c4d1b9ec 100644 --- a/itests/kit/node_unmanaged.go +++ b/itests/kit/node_unmanaged.go @@ -1,17 +1,26 @@ package kit import ( + "bytes" "context" + "crypto/rand" "fmt" + "os" + "path/filepath" + "testing" + + ffi "github.com/filecoin-project/filecoin-ffi" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/builtin" miner14 "github.com/filecoin-project/go-state-types/builtin/v14/miner" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" "github.com/ipfs/go-cid" + "github.com/stretchr/testify/require" cbg "github.com/whyrusleeping/cbor-gen" - "testing" "github.com/filecoin-project/go-address" "github.com/filecoin-project/lotus/chain/wallet/key" @@ -23,8 +32,19 @@ import ( // infrastructure, all tasks must be manually executed, managed and scheduled by // the test or test kit. type TestUnmanagedMiner struct { - t *testing.T - options nodeOpts + t *testing.T + options nodeOpts + cacheDir string + unsealedSectorDir string + sealedSectorDir string + sectorSize abi.SectorSize + + CacheDirPaths map[abi.SectorNumber]string + UnsealedSectorPaths map[abi.SectorNumber]string + SealedSectorPaths map[abi.SectorNumber]string + SealedCids map[abi.SectorNumber]cid.Cid + UnsealedCids map[abi.SectorNumber]cid.Cid + SealTickets map[abi.SectorNumber]abi.SealRandomness ActorAddr address.Address OwnerKey *key.Key @@ -33,179 +53,336 @@ type TestUnmanagedMiner struct { PeerID peer.ID PrivKey libp2pcrypto.PrivKey } -} -func (tm *TestUnmanagedMiner) StartWindowedPostLoop(ctx context.Context, sectorNumber abi.SectorNumber, proofType abi.RegisteredSealProof) { - tm.t.Helper() - - go func() { - for ctx.Err() == nil { - currentEpoch, nextPost, err := tm.manualOnboardingCalculateNextPostEpoch(ctx, sectorNumber) - if err != nil { - errCh <- err - return - } - if ctx.Err() != nil { - return - } - nextPost += 5 // give a little buffer - fmt.Printf("WindowPoST(%d) Waiting %d until epoch %d to submit PoSt\n", sectorNumber, nextPost-currentEpoch, nextPost) - - // Create channel to listen for chain head - heads, err := tm.FullNode.ChainNotify(ctx) - if err != nil { - errCh <- err - return - } - // Wait for nextPost epoch - for chg := range heads { - var ts *types.TipSet - for _, c := range chg { - if c.Type != "apply" { - continue - } - ts = c.Val - if ts.Height() >= nextPost { - break - } - } - if ctx.Err() != nil { - return - } - if ts != nil && ts.Height() >= nextPost { - break - } - } - if ctx.Err() != nil { - return - } - - err = manualOnboardingSubmitWindowPost(ctx, withMockProofs, client, miner, sectorNumber, cacheDirPath, sealedSectorPath, sealedCid, proofType) - if err != nil { - errCh <- err - return - } - - // signal first post is done - select { - case <-first: - default: - close(first) - } - } - }() + currentSectorNum abi.SectorNumber } -func (tm *TestUnmanagedMiner) manualOnboardingCalculateNextPostEpoch( - ctx context.Context, - sectorNumber abi.SectorNumber, -) (abi.ChainEpoch, abi.ChainEpoch, error) { +func NewTestUnmanagedMiner(t *testing.T, full *TestFullNode, actorAddr address.Address, opts ...NodeOpt) *TestUnmanagedMiner { + require.NotNil(t, full, "full node required when instantiating miner") - head, err := tm.FullNode.ChainHead(ctx) - if err != nil { - return 0, 0, fmt.Errorf("failed to get chain head: %w", err) + options := DefaultNodeOpts + for _, o := range opts { + err := o(&options) + require.NoError(t, err) } - sp, err := tm.FullNode.StateSectorPartition(ctx, tm.ActorAddr, sectorNumber, head.Key()) - if err != nil { - return 0, 0, fmt.Errorf("failed to get sector partition: %w", err) + privkey, _, err := libp2pcrypto.GenerateEd25519Key(rand.Reader) + require.NoError(t, err) + + require.NotNil(t, options.ownerKey, "manual miner key can't be null if initializing a miner after genesis") + + peerId, err := peer.IDFromPrivateKey(privkey) + require.NoError(t, err) + tmpDir := t.TempDir() + + cacheDir := filepath.Join(tmpDir, fmt.Sprintf("cache-%s", actorAddr)) + unsealedSectorDir := filepath.Join(tmpDir, fmt.Sprintf("unsealed-%s", actorAddr)) + sealedSectorDir := filepath.Join(tmpDir, fmt.Sprintf("sealed-%s", actorAddr)) + + _ = os.Mkdir(cacheDir, 0755) + _ = os.Mkdir(unsealedSectorDir, 0755) + _ = os.Mkdir(sealedSectorDir, 0755) + + tm := TestUnmanagedMiner{ + t: t, + options: options, + cacheDir: cacheDir, + unsealedSectorDir: unsealedSectorDir, + sealedSectorDir: sealedSectorDir, + + CacheDirPaths: make(map[abi.SectorNumber]string), + UnsealedSectorPaths: make(map[abi.SectorNumber]string), + SealedSectorPaths: make(map[abi.SectorNumber]string), + SealedCids: make(map[abi.SectorNumber]cid.Cid), + UnsealedCids: make(map[abi.SectorNumber]cid.Cid), + SealTickets: make(map[abi.SectorNumber]abi.SealRandomness), + + ActorAddr: actorAddr, + OwnerKey: options.ownerKey, + FullNode: full, + currentSectorNum: 101, } + tm.Libp2p.PeerID = peerId + tm.Libp2p.PrivKey = privkey + + return &tm +} + +func (tm *TestUnmanagedMiner) CurrentPower(ctx context.Context) *api.MinerPower { + head, err := tm.FullNode.ChainHead(ctx) + require.NoError(tm.t, err) + + p, err := tm.FullNode.StateMinerPower(ctx, tm.ActorAddr, head.Key()) + require.NoError(tm.t, err) + + return p +} + +func (tm *TestUnmanagedMiner) AssertNoPower(ctx context.Context) { + p := tm.CurrentPower(ctx) + tm.t.Logf("MinerB RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) + require.True(tm.t, p.MinerPower.RawBytePower.IsZero()) +} + +func (tm *TestUnmanagedMiner) AssertPower(ctx context.Context, raw uint64, qa uint64) { + req := require.New(tm.t) + p := tm.CurrentPower(ctx) + tm.t.Logf("MinerB RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) + req.Equal(raw, p.MinerPower.RawBytePower.Uint64()) + req.Equal(qa, p.MinerPower.QualityAdjPower.Uint64()) + +} + +func (tm *TestUnmanagedMiner) OnboardCCSectorWithMockProofs(ctx context.Context, proofType abi.RegisteredSealProof) abi.SectorNumber { + req := require.New(tm.t) + sectorNumber := tm.currentSectorNum + tm.currentSectorNum++ + + tm.SealedCids[sectorNumber] = cid.MustParse("bagboea4b5abcatlxechwbp7kjpjguna6r6q7ejrhe6mdp3lf34pmswn27pkkiekz") - fmt.Printf("WindowPoST(%d): SectorPartition: %+v\n", sectorNumber, sp) + var sealRandEpoch abi.ChainEpoch - di, err := tm.FullNode.StateMinerProvingDeadline(ctx, tm.ActorAddr, head.Key()) - if err != nil { - return 0, 0, fmt.Errorf("failed to get proving deadline: %w", err) + head, err := tm.FullNode.ChainHead(ctx) + require.NoError(tm.t, err) + + if head.Height() > policy.SealRandomnessLookback { + sealRandEpoch = head.Height() - policy.SealRandomnessLookback + } else { + sealRandEpoch = policy.SealRandomnessLookback + tm.t.Logf("Waiting for at least epoch %d for seal randomness (current epoch %d) ...", sealRandEpoch+5, head.Height()) + tm.FullNode.WaitTillChain(ctx, HeightAtLeast(sealRandEpoch+5)) } - fmt.Printf("WindowPoST(%d): ProvingDeadline: %+v\n", sectorNumber, di) + // Step 4 : Submit the Pre-Commit to the network + r := tm.manualOnboardingSubmitMessage(ctx, &miner14.PreCommitSectorBatchParams2{ + Sectors: []miner14.SectorPreCommitInfo{{ + Expiration: 2880 * 300, + SectorNumber: sectorNumber, + SealProof: TestSpt, + SealedCID: tm.SealedCids[sectorNumber], + SealRandEpoch: sealRandEpoch, + }}, + }, 1, builtin.MethodsMiner.PreCommitSectorBatch2) + req.True(r.Receipt.ExitCode.IsSuccess()) + + _, err = tm.FullNode.StateSectorPreCommitInfo(ctx, tm.ActorAddr, sectorNumber, r.TipSet) + req.NoError(err) + + sectorProof := []byte{0xde, 0xad, 0xbe, 0xef} + + var seedRandomnessHeight abi.ChainEpoch - // periodStart tells us the first epoch of the current proving period (24h) - // although it may be in the future if we don't need to submit post in this period - periodStart := di.PeriodStart - if di.PeriodStart < di.CurrentEpoch && sp.Deadline <= di.Index { - // the deadline we want has past in this current proving period, so wait till the next one - periodStart += di.WPoStProvingPeriod + head, err = tm.FullNode.ChainHead(ctx) + require.NoError(tm.t, err) + preCommitInfo, err := tm.FullNode.StateSectorPreCommitInfo(ctx, tm.ActorAddr, sectorNumber, head.Key()) + req.NoError(err) + seedRandomnessHeight = preCommitInfo.PreCommitEpoch + policy.GetPreCommitChallengeDelay() + + tm.t.Logf("Waiting %d epochs for seed randomness at epoch %d (current epoch %d)...", seedRandomnessHeight-head.Height(), seedRandomnessHeight, head.Height()) + tm.FullNode.WaitTillChain(ctx, HeightAtLeast(seedRandomnessHeight+5)) + + r = tm.manualOnboardingSubmitMessage(ctx, &miner14.ProveCommitSectors3Params{ + SectorActivations: []miner14.SectorActivationManifest{{SectorNumber: sectorNumber}}, + SectorProofs: [][]byte{sectorProof}, + RequireActivationSuccess: true, + }, 0, builtin.MethodsMiner.ProveCommitSectors3) + req.NoError(err) + req.True(r.Receipt.ExitCode.IsSuccess()) + + return sectorNumber +} + +func (tm *TestUnmanagedMiner) OnboardCCSectorWithRealProofs(ctx context.Context, proofType abi.RegisteredSealProof) abi.SectorNumber { + req := require.New(tm.t) + sectorNumber := tm.currentSectorNum + tm.currentSectorNum++ + + // --------------------Create pre-commit for the CC sector -> we'll just pre-commit `sector size` worth of 0s for this CC sector + + // Step 1: Wait for the seal randomness to be available -> we want to draw seal randomess from a tipset that has achieved finality as PoReps are expensive to generate + // See if we already have such a epoch and wait if not + head, err := tm.FullNode.ChainHead(ctx) + require.NoError(tm.t, err) + var sealRandEpoch abi.ChainEpoch + + if head.Height() > policy.SealRandomnessLookback { + sealRandEpoch = head.Height() - policy.SealRandomnessLookback + } else { + sealRandEpoch = policy.SealRandomnessLookback + tm.t.Logf("Waiting for at least epoch %d for seal randomness (current epoch %d) ...", sealRandEpoch+5, head.Height()) + tm.FullNode.WaitTillChain(ctx, HeightAtLeast(sealRandEpoch+5)) } - provingEpoch := periodStart + (di.WPoStProvingPeriod/abi.ChainEpoch(di.WPoStPeriodDeadlines))*abi.ChainEpoch(sp.Deadline) - return di.CurrentEpoch, provingEpoch, nil + // Step 2: Write empty 32 bytes that we want to seal i.e. create our CC sector + unsealedSectorPath := filepath.Join(tm.unsealedSectorDir, fmt.Sprintf("%d", sectorNumber)) + sealedSectorPath := filepath.Join(tm.sealedSectorDir, fmt.Sprintf("%d", sectorNumber)) + + unsealedSize := abi.PaddedPieceSize(tm.sectorSize).Unpadded() + req.NoError(os.WriteFile(unsealedSectorPath, make([]byte, unsealedSize), 0644)) + req.NoError(os.WriteFile(sealedSectorPath, make([]byte, tm.sectorSize), 0644)) + + tm.t.Logf("Wrote unsealed sector to %s", unsealedSectorPath) + tm.t.Logf("Wrote sealed sector to %s", sealedSectorPath) + + // Step 3: Generate a Pre-Commit for the CC sector -> this persists the proof on the Miner State + tm.manualOnboardingGeneratePreCommit(ctx, tm.cacheDir, unsealedSectorPath, sealedSectorPath, sectorNumber, sealRandEpoch, proofType) + + // Step 4 : Submit the Pre-Commit to the network + r := tm.manualOnboardingSubmitMessage(ctx, &miner14.PreCommitSectorBatchParams2{ + Sectors: []miner14.SectorPreCommitInfo{{ + Expiration: 2880 * 300, + SectorNumber: sectorNumber, + SealProof: TestSpt, + SealedCID: tm.SealedCids[sectorNumber], + SealRandEpoch: sealRandEpoch, + }}, + }, 1, builtin.MethodsMiner.PreCommitSectorBatch2) + req.True(r.Receipt.ExitCode.IsSuccess()) + + _, err = tm.FullNode.StateSectorPreCommitInfo(ctx, tm.ActorAddr, sectorNumber, r.TipSet) + req.NoError(err) + + // Step 5: Generate a ProveCommit for the CC sector + proveCommit := tm.manualOnboardingGenerateProveCommit(ctx, sectorNumber, proofType) + + // Step 6: Submit the ProveCommit to the network + tm.t.Log("Submitting MinerB ProveCommitSector ...") + + r = tm.manualOnboardingSubmitMessage(ctx, &miner14.ProveCommitSectors3Params{ + SectorActivations: []miner14.SectorActivationManifest{{SectorNumber: sectorNumber}}, + SectorProofs: [][]byte{proveCommit}, + RequireActivationSuccess: true, + }, 0, builtin.MethodsMiner.ProveCommitSectors3) + req.NoError(err) + req.True(r.Receipt.ExitCode.IsSuccess()) + + return sectorNumber } -func (tm *TestUnmanagedMiner) manualOnboardingSubmitWindowPost( +func (tm *TestUnmanagedMiner) manualOnboardingGeneratePreCommit( ctx context.Context, - withMockProofs bool, + cacheDirPath string, + unsealedSectorPath string, + sealedSectorPath string, sectorNumber abi.SectorNumber, - cacheDirPath, sealedSectorPath string, - sealedCid cid.Cid, + sealRandEpoch abi.ChainEpoch, proofType abi.RegisteredSealProof, -) error { - fmt.Printf("WindowPoST(%d): Running WindowPoSt ...\n", sectorNumber) +) { + + req := require.New(tm.t) + tm.t.Logf("Generating proof type %d PreCommit ...", proofType) head, err := tm.FullNode.ChainHead(ctx) - if err != nil { - return fmt.Errorf("failed to get chain head: %w", err) - } + req.NoError(err) - sp, err := tm.FullNode.StateSectorPartition(ctx, tm.ActorAddr, sectorNumber, head.Key()) - if err != nil { - return fmt.Errorf("failed to get sector partition: %w", err) - } + minerAddrBytes := new(bytes.Buffer) + req.NoError(tm.ActorAddr.MarshalCBOR(minerAddrBytes)) - // We should be up to the deadline we care about - di, err := tm.FullNode.StateMinerProvingDeadline(ctx, tm.ActorAddr, head.Key()) - if err != nil { - return fmt.Errorf("failed to get proving deadline: %w", err) - } - fmt.Printf("WindowPoST(%d): SectorPartition: %+v, ProvingDeadline: %+v\n", sectorNumber, sp, di) - if di.Index != sp.Deadline { - return fmt.Errorf("sector %d is not in the deadline %d, but %d", sectorNumber, sp.Deadline, di.Index) - } + rand, err := tm.FullNode.StateGetRandomnessFromTickets(ctx, crypto.DomainSeparationTag_SealRandomness, sealRandEpoch, minerAddrBytes.Bytes(), head.Key()) + req.NoError(err) + sealTickets := abi.SealRandomness(rand) - var proofBytes []byte - if withMockProofs { - proofBytes = []byte{0xde, 0xad, 0xbe, 0xef} - } else { - proofBytes, err = manualOnboardingGenerateWindowPost(ctx, tm.FullNode, cacheDirPath, sealedSectorPath, tm.ActorAddr, sectorNumber, sealedCid, proofType) - if err != nil { - return fmt.Errorf("failed to generate window post: %w", err) - } - } + tm.t.Logf("Running proof type %d SealPreCommitPhase1 for sector %d...", proofType, sectorNumber) - fmt.Printf("WindowedPoSt(%d) Submitting ...\n", sectorNumber) + actorIdNum, err := address.IDFromAddress(tm.ActorAddr) + req.NoError(err) + actorId := abi.ActorID(actorIdNum) - chainRandomnessEpoch := di.Challenge - chainRandomness, err := tm.FullNode.StateGetRandomnessFromTickets(ctx, crypto.DomainSeparationTag_PoStChainCommit, chainRandomnessEpoch, nil, head.Key()) - if err != nil { - return fmt.Errorf("failed to get chain randomness: %w", err) - } + pc1, err := ffi.SealPreCommitPhase1( + proofType, + cacheDirPath, + unsealedSectorPath, + sealedSectorPath, + sectorNumber, + actorId, + sealTickets, + []abi.PieceInfo{}, + ) + req.NoError(err) + req.NotNil(pc1) - minerInfo, err := tm.FullNode.StateMinerInfo(ctx, tm.ActorAddr, head.Key()) - if err != nil { - return fmt.Errorf("failed to get miner info: %w", err) - } + tm.t.Logf("Running proof type %d SealPreCommitPhase2 for sector %d...", proofType, sectorNumber) - r, err := manualOnboardingSubmitMessage(ctx, tm.FullNode, miner, &miner14.SubmitWindowedPoStParams{ - ChainCommitEpoch: chainRandomnessEpoch, - ChainCommitRand: chainRandomness, - Deadline: sp.Deadline, - Partitions: []miner14.PoStPartition{{Index: sp.Partition}}, - Proofs: []proof.PoStProof{{PoStProof: minerInfo.WindowPoStProofType, ProofBytes: proofBytes}}, - }, 0, builtin.MethodsMiner.SubmitWindowedPoSt) - if err != nil { - return fmt.Errorf("failed to submit PoSt: %w", err) - } - if !r.Receipt.ExitCode.IsSuccess() { - return fmt.Errorf("submitting PoSt failed: %s", r.Receipt.ExitCode) - } + sealedCid, unsealedCid, err := ffi.SealPreCommitPhase2( + pc1, + cacheDirPath, + sealedSectorPath, + ) + req.NoError(err) - if !withMockProofs { - // Dispute the PoSt to confirm the validity of the PoSt since PoSt acceptance is optimistic - if err := manualOnboardingDisputeWindowPost(ctx, client, miner, sectorNumber); err != nil { - return fmt.Errorf("failed to dispute PoSt: %w", err) - } - } - return nil + tm.t.Logf("Unsealed CID: %s", unsealedCid) + tm.t.Logf("Sealed CID: %s", sealedCid) + + tm.SealTickets[sectorNumber] = sealTickets + tm.SealedCids[sectorNumber] = sealedCid + tm.UnsealedCids[sectorNumber] = unsealedCid + tm.CacheDirPaths[sectorNumber] = cacheDirPath + tm.UnsealedSectorPaths[sectorNumber] = unsealedSectorPath + tm.SealedSectorPaths[sectorNumber] = sealedSectorPath +} + +func (tm *TestUnmanagedMiner) manualOnboardingGenerateProveCommit( + ctx context.Context, + sectorNumber abi.SectorNumber, + proofType abi.RegisteredSealProof, +) []byte { + req := require.New(tm.t) + + tm.t.Logf("Generating proof type %d Sector Proof ...", proofType) + + head, err := tm.FullNode.ChainHead(ctx) + req.NoError(err) + + var seedRandomnessHeight abi.ChainEpoch + + preCommitInfo, err := tm.FullNode.StateSectorPreCommitInfo(ctx, tm.ActorAddr, sectorNumber, head.Key()) + req.NoError(err) + seedRandomnessHeight = preCommitInfo.PreCommitEpoch + policy.GetPreCommitChallengeDelay() + + tm.t.Logf("Waiting %d epochs for seed randomness at epoch %d (current epoch %d)...", seedRandomnessHeight-head.Height(), seedRandomnessHeight, head.Height()) + tm.FullNode.WaitTillChain(ctx, HeightAtLeast(seedRandomnessHeight+5)) + + head, err = tm.FullNode.ChainHead(ctx) + req.NoError(err) + + minerAddrBytes := new(bytes.Buffer) + req.NoError(tm.ActorAddr.MarshalCBOR(minerAddrBytes)) + + tm.t.Logf("Getting seed randomness from beacon at epoch %d", seedRandomnessHeight) + tm.t.Logf("Getting seed randomness from tickets at epoch %d", head.Height()) + + rand, err := tm.FullNode.StateGetRandomnessFromBeacon(ctx, crypto.DomainSeparationTag_InteractiveSealChallengeSeed, seedRandomnessHeight, minerAddrBytes.Bytes(), head.Key()) + req.NoError(err) + seedRandomness := abi.InteractiveSealRandomness(rand) + + actorIdNum, err := address.IDFromAddress(tm.ActorAddr) + req.NoError(err) + actorId := abi.ActorID(actorIdNum) + + tm.t.Logf("Running proof type %d SealCommitPhase1 for sector %d...", proofType, sectorNumber) + + scp1, err := ffi.SealCommitPhase1( + proofType, + tm.SealedCids[sectorNumber], + tm.UnsealedCids[sectorNumber], + tm.CacheDirPaths[sectorNumber], + tm.SealedSectorPaths[sectorNumber], + sectorNumber, + actorId, + tm.SealTickets[sectorNumber], + seedRandomness, + []abi.PieceInfo{}, + ) + req.NoError(err) + + tm.t.Logf("Running proof type %d SealCommitPhase2 for sector %d...", proofType, sectorNumber) + + sectorProof, err := ffi.SealCommitPhase2(scp1, sectorNumber, actorId) + req.NoError(err) + + tm.t.Logf("Got proof type %d sector proof of length %d", proofType, len(sectorProof)) + + return sectorProof } func (tm *TestUnmanagedMiner) manualOnboardingSubmitMessage( @@ -213,11 +390,10 @@ func (tm *TestUnmanagedMiner) manualOnboardingSubmitMessage( params cbg.CBORMarshaler, value uint64, method abi.MethodNum, -) (*api.MsgLookup, error) { +) *api.MsgLookup { + enc, aerr := actors.SerializeParams(params) - if aerr != nil { - return nil, fmt.Errorf("failed to serialize params: %w", aerr) - } + require.NoError(tm.t, aerr) m, err := tm.FullNode.MpoolPushMessage(ctx, &types.Message{ To: tm.ActorAddr, @@ -226,9 +402,9 @@ func (tm *TestUnmanagedMiner) manualOnboardingSubmitMessage( Method: method, Params: enc, }, nil) - if err != nil { - return nil, fmt.Errorf("failed to push message: %w", err) - } + require.NoError(tm.t, err) - return tm.FullNode.StateWaitMsg(ctx, m.Cid(), 2, api.LookbackNoLimit, true) + msg, err := tm.FullNode.StateWaitMsg(ctx, m.Cid(), 2, api.LookbackNoLimit, true) + require.NoError(tm.t, err) + return msg } diff --git a/itests/manual_onboarding_test.go b/itests/manual_onboarding_test.go index faca6ee2928..acc37526dd5 100644 --- a/itests/manual_onboarding_test.go +++ b/itests/manual_onboarding_test.go @@ -4,8 +4,6 @@ import ( "bytes" "context" "fmt" - "os" - "path/filepath" "strings" "testing" "time" @@ -25,7 +23,6 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors" - "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/itests/kit" ) @@ -41,6 +38,7 @@ func TestManualCCOnboarding(t *testing.T) { if withMockProofs { testName = "WithMockProofs" } + t.Run(testName, func(t *testing.T) { kit.QuietMiningLogs() ctx, cancel := context.WithCancel(context.Background()) @@ -48,42 +46,12 @@ func TestManualCCOnboarding(t *testing.T) { var ( blocktime = 2 * time.Millisecond - - client kit.TestFullNode - minerA kit.TestMiner // A is a standard genesis miner - minerB kit.TestUnmanagedMiner // B is a CC miner we will onboard manually without NI-Porep - - // TODO: single sector per miner for now, but this isn't going to scale when refactored into - // TestUnmanagedMiner - they'll need their own list of sectors and their own copy of each of - // the things below, including per-sector maps of some of these too. - // - // Misc thoughts: - // Each TestUnmanagedMiner should have its own temp dir, within which it can have a cache dir - // and a place to put sealed and unsealed sectors. We can't share these between miners. - // We should have a way to "add" CC sectors, which will setup the sealed and unsealed files - // and can move many of the manualOnboarding*() methods into the TestUnmanagedMiner struct. - // - // The manualOnboardingRunWindowPost() Go routine should be owned by TestUnmanagedMiner and - // a simple "miner.StartWindowPost()" should suffice to make it observe all of the sectors - // it knows about and start posting for them. We should be able to ignore most (all?) of the - // special cases that lotus-miner currently has to deal with. - - // sector numbers, make them unique for each miner so our maps work - bSectorNum = abi.SectorNumber(22) - - tmpDir = t.TempDir() - - cacheDirPath = map[abi.SectorNumber]string{} // can't share a cacheDir between miners - unsealedSectorPath, sealedSectorPath = map[abi.SectorNumber]string{}, map[abi.SectorNumber]string{} - sealedCid, unsealedCid = map[abi.SectorNumber]cid.Cid{}, map[abi.SectorNumber]cid.Cid{} - - // note we'll use the same randEpoch for both miners - sealRandEpoch = policy.SealRandomnessLookback - sealTickets = map[abi.SectorNumber]abi.SealRandomness{} + client kit.TestFullNode + minerA kit.TestMiner // A is a standard genesis miner ) // Setup and begin mining with a single miner (A) - + // Miner A will only be a genesis Miner with power allocated in the genesis block and will not onboard any sectors from here on kitOpts := []kit.EnsembleOpt{} if withMockProofs { kitOpts = append(kitOpts, kit.MockProofs()) @@ -96,110 +64,37 @@ func TestManualCCOnboarding(t *testing.T) { InterconnectAll() ens.BeginMining(blocktime) + // Instantiate MinerB to manually handle sector onboarding and power acquisition through sector activation. + // Unlike other miners managed by the Lotus Miner storage infrastructure, MinerB operates independently, + // performing all related tasks manually. Managed by the TestKit, MinerB has the capability to utilize actual proofs + // for the processes of sector onboarding and activation. nodeOpts = append(nodeOpts, kit.OwnerAddr(client.DefaultKey)) - ens.UnmanagedMiner(&minerB, &client, nodeOpts...).Start() + minerB, ens := ens.UnmanagedMiner(&client, nodeOpts...) + ens.Start() build.Clock.Sleep(time.Second) - head, err := client.ChainHead(ctx) - req.NoError(err) - t.Log("Checking initial power ...") - // Miner A should have power + // Miner A should have power as it has already onboarded sectors in the genesis block + head, err := client.ChainHead(ctx) + req.NoError(err) p, err := client.StateMinerPower(ctx, minerA.ActorAddr, head.Key()) req.NoError(err) t.Logf("MinerA RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) - // Miner B should have no power - p, err = client.StateMinerPower(ctx, minerB.ActorAddr, head.Key()) - req.NoError(err) - t.Logf("MinerB RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) - req.True(p.MinerPower.RawBytePower.IsZero()) - - // Run precommit for a sector on miner B - - t.Logf("Waiting for at least epoch %d for seal randomness (current epoch %d) ...", sealRandEpoch+5, head.Height()) - client.WaitTillChain(ctx, kit.HeightAtLeast(sealRandEpoch+5)) - - if withMockProofs { - sealedCid[bSectorNum] = cid.MustParse("bagboea4b5abcatlxechwbp7kjpjguna6r6q7ejrhe6mdp3lf34pmswn27pkkiekz") - } else { - cacheDirPath[bSectorNum] = filepath.Join(tmpDir, "cacheb") - unsealedSectorPath[bSectorNum] = filepath.Join(tmpDir, "unsealedb") - sealedSectorPath[bSectorNum] = filepath.Join(tmpDir, "sealedb") - - sealTickets[bSectorNum], sealedCid[bSectorNum], unsealedCid[bSectorNum] = manualOnboardingGeneratePreCommit( - ctx, - t, - client, - cacheDirPath[bSectorNum], - unsealedSectorPath[bSectorNum], - sealedSectorPath[bSectorNum], - minerB.ActorAddr, - bSectorNum, - sealRandEpoch, - kit.TestSpt, - ) - } - - t.Log("Submitting MinerB PreCommitSector ...") - - r, err := manualOnboardingSubmitMessage(ctx, client, minerB, &miner14.PreCommitSectorBatchParams2{ - Sectors: []miner14.SectorPreCommitInfo{{ - Expiration: 2880 * 300, - SectorNumber: bSectorNum, - SealProof: kit.TestSpt, - SealedCID: sealedCid[bSectorNum], - SealRandEpoch: sealRandEpoch, - }}, - }, 1, builtin.MethodsMiner.PreCommitSectorBatch2) - req.NoError(err) - req.True(r.Receipt.ExitCode.IsSuccess()) - - preCommitInfo, err := client.StateSectorPreCommitInfo(ctx, minerB.ActorAddr, bSectorNum, r.TipSet) - req.NoError(err) - - // Run prove commit for the sector on miner B - - seedRandomnessHeight := preCommitInfo.PreCommitEpoch + policy.GetPreCommitChallengeDelay() - t.Logf("Waiting %d epochs for seed randomness at epoch %d (current epoch %d)...", seedRandomnessHeight-r.Height, seedRandomnessHeight, r.Height) - client.WaitTillChain(ctx, kit.HeightAtLeast(seedRandomnessHeight+5)) + // Miner B should have no power as it has yet to onboard and activate any sectors + minerB.AssertNoPower(ctx) - var sectorProof []byte + var bSectorNum abi.SectorNumber if withMockProofs { - sectorProof = []byte{0xde, 0xad, 0xbe, 0xef} + bSectorNum = minerB.OnboardCCSectorWithMockProofs(ctx, kit.TestSpt) } else { - sectorProof = manualOnboardingGenerateProveCommit( - ctx, - t, - client, - cacheDirPath[bSectorNum], - sealedSectorPath[bSectorNum], - minerB.ActorAddr, - bSectorNum, - sealedCid[bSectorNum], - unsealedCid[bSectorNum], - sealTickets[bSectorNum], - kit.TestSpt, - ) + bSectorNum = minerB.OnboardCCSectorWithRealProofs(ctx, kit.TestSpt) } - t.Log("Submitting MinerB ProveCommitSector ...") - - r, err = manualOnboardingSubmitMessage(ctx, client, minerB, &miner14.ProveCommitSectors3Params{ - SectorActivations: []miner14.SectorActivationManifest{{SectorNumber: bSectorNum}}, - SectorProofs: [][]byte{sectorProof}, - RequireActivationSuccess: true, - }, 0, builtin.MethodsMiner.ProveCommitSectors3) - req.NoError(err) - req.True(r.Receipt.ExitCode.IsSuccess()) - - // Check power after proving, should still be zero until the PoSt is submitted - p, err = client.StateMinerPower(ctx, minerB.ActorAddr, r.TipSet) - req.NoError(err) - t.Logf("MinerB RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) - req.True(p.MinerPower.RawBytePower.IsZero()) + // Miner B should still not have power as power can only be gained after sector is activated i.e. the first WindowPost is submitted for it + minerB.AssertNoPower(ctx) // start a background PoST scheduler for miner B bFirstCh, bErrCh := manualOnboardingRunWindowPost( @@ -208,9 +103,9 @@ func TestManualCCOnboarding(t *testing.T) { client, minerB, bSectorNum, - cacheDirPath[bSectorNum], - sealedSectorPath[bSectorNum], - sealedCid[bSectorNum], + minerB.CacheDirPaths[bSectorNum], + minerB.SealedSectorPaths[bSectorNum], + minerB.SealedCids[bSectorNum], kit.TestSpt, ) @@ -249,13 +144,12 @@ func TestManualCCOnboarding(t *testing.T) { } // Fetch on-chain sector properties - - soi, err := client.StateSectorGetInfo(ctx, minerB.ActorAddr, bSectorNum, r.TipSet) + head, err = client.ChainHead(ctx) req.NoError(err) - t.Logf("Miner B SectorOnChainInfo %d: %+v", bSectorNum, soi) - head, err = client.ChainHead(ctx) + soi, err := client.StateSectorGetInfo(ctx, minerB.ActorAddr, bSectorNum, head.Key()) req.NoError(err) + t.Logf("Miner B SectorOnChainInfo %d: %+v", bSectorNum, soi) head = client.WaitTillChain(ctx, kit.HeightAtLeast(head.Height()+5)) @@ -264,152 +158,13 @@ func TestManualCCOnboarding(t *testing.T) { t.Log("Checking power after PoSt ...") // Miner B should now have power - p, err = client.StateMinerPower(ctx, minerB.ActorAddr, head.Key()) - req.NoError(err) - t.Logf("MinerB RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) - req.Equal(uint64(2<<10), p.MinerPower.RawBytePower.Uint64()) // 2kiB RBP - req.Equal(uint64(2<<10), p.MinerPower.QualityAdjPower.Uint64()) // 2kiB QaP + minerB.AssertPower(ctx, (uint64(2 << 10)), (uint64(2 << 10))) checkPostSchedulers() }) } } -func manualOnboardingGeneratePreCommit( - ctx context.Context, - t *testing.T, - client api.FullNode, - cacheDirPath, - unsealedSectorPath, - sealedSectorPath string, - minerAddr address.Address, - sectorNumber abi.SectorNumber, - sealRandEpoch abi.ChainEpoch, - proofType abi.RegisteredSealProof, -) (abi.SealRandomness, cid.Cid, cid.Cid) { - - req := require.New(t) - t.Logf("Generating proof type %d PreCommit ...", proofType) - - _ = os.Mkdir(cacheDirPath, 0755) - unsealedSize := abi.PaddedPieceSize(sectorSize).Unpadded() - req.NoError(os.WriteFile(unsealedSectorPath, make([]byte, unsealedSize), 0644)) - req.NoError(os.WriteFile(sealedSectorPath, make([]byte, sectorSize), 0644)) - - t.Logf("Wrote unsealed sector to %s", unsealedSectorPath) - t.Logf("Wrote sealed sector to %s", sealedSectorPath) - - head, err := client.ChainHead(ctx) - req.NoError(err) - - minerAddrBytes := new(bytes.Buffer) - req.NoError(minerAddr.MarshalCBOR(minerAddrBytes)) - - rand, err := client.StateGetRandomnessFromTickets(ctx, crypto.DomainSeparationTag_SealRandomness, sealRandEpoch, minerAddrBytes.Bytes(), head.Key()) - req.NoError(err) - sealTickets := abi.SealRandomness(rand) - - t.Logf("Running proof type %d SealPreCommitPhase1 for sector %d...", proofType, sectorNumber) - - actorIdNum, err := address.IDFromAddress(minerAddr) - req.NoError(err) - actorId := abi.ActorID(actorIdNum) - - pc1, err := ffi.SealPreCommitPhase1( - proofType, - cacheDirPath, - unsealedSectorPath, - sealedSectorPath, - sectorNumber, - actorId, - sealTickets, - []abi.PieceInfo{}, - ) - req.NoError(err) - req.NotNil(pc1) - - t.Logf("Running proof type %d SealPreCommitPhase2 for sector %d...", proofType, sectorNumber) - - sealedCid, unsealedCid, err := ffi.SealPreCommitPhase2( - pc1, - cacheDirPath, - sealedSectorPath, - ) - req.NoError(err) - - t.Logf("Unsealed CID: %s", unsealedCid) - t.Logf("Sealed CID: %s", sealedCid) - - return sealTickets, sealedCid, unsealedCid -} - -func manualOnboardingGenerateProveCommit( - ctx context.Context, - t *testing.T, - client api.FullNode, - cacheDirPath, - sealedSectorPath string, - minerAddr address.Address, - sectorNumber abi.SectorNumber, - sealedCid, unsealedCid cid.Cid, - sealTickets abi.SealRandomness, - proofType abi.RegisteredSealProof, -) []byte { - req := require.New(t) - - t.Logf("Generating proof type %d Sector Proof ...", proofType) - - head, err := client.ChainHead(ctx) - req.NoError(err) - - var seedRandomnessHeight abi.ChainEpoch - - preCommitInfo, err := client.StateSectorPreCommitInfo(ctx, minerAddr, sectorNumber, head.Key()) - req.NoError(err) - seedRandomnessHeight = preCommitInfo.PreCommitEpoch + policy.GetPreCommitChallengeDelay() - - minerAddrBytes := new(bytes.Buffer) - req.NoError(minerAddr.MarshalCBOR(minerAddrBytes)) - - rand, err := client.StateGetRandomnessFromBeacon(ctx, crypto.DomainSeparationTag_InteractiveSealChallengeSeed, seedRandomnessHeight, minerAddrBytes.Bytes(), head.Key()) - req.NoError(err) - seedRandomness := abi.InteractiveSealRandomness(rand) - - actorIdNum, err := address.IDFromAddress(minerAddr) - req.NoError(err) - actorId := abi.ActorID(actorIdNum) - - t.Logf("Running proof type %d SealCommitPhase1 for sector %d...", proofType, sectorNumber) - - scp1, err := ffi.SealCommitPhase1( - proofType, - sealedCid, - unsealedCid, - cacheDirPath, - sealedSectorPath, - sectorNumber, - actorId, - sealTickets, - seedRandomness, - []abi.PieceInfo{}, - ) - req.NoError(err) - - t.Logf("Running proof type %d SealCommitPhase2 for sector %d...", proofType, sectorNumber) - - sectorProof, err := ffi.SealCommitPhase2(scp1, sectorNumber, actorId) - req.NoError(err) - - t.Logf("Got proof type %d sector proof of length %d", proofType, len(sectorProof)) - - /* this variant would be used for aggregating NI-PoRep proofs - sectorProof, err := ffi.SealCommitPhase2CircuitProofs(scp1, sectorNumber) - req.NoError(err) - */ - - return sectorProof -} - func manualOnboardingGenerateWindowPost( ctx context.Context, client api.FullNode, @@ -501,7 +256,7 @@ func manualOnboardingGenerateWindowPost( func manualOnboardingDisputeWindowPost( ctx context.Context, client kit.TestFullNode, - miner kit.TestUnmanagedMiner, + miner *kit.TestUnmanagedMiner, sectorNumber abi.SectorNumber, ) error { @@ -546,7 +301,7 @@ func manualOnboardingDisputeWindowPost( func manualOnboardingSubmitMessage( ctx context.Context, client api.FullNode, - from kit.TestUnmanagedMiner, + from *kit.TestUnmanagedMiner, params cbg.CBORMarshaler, value uint64, method abi.MethodNum, @@ -615,7 +370,7 @@ func manualOnboardingSubmitWindowPost( ctx context.Context, withMockProofs bool, client kit.TestFullNode, - miner kit.TestUnmanagedMiner, + miner *kit.TestUnmanagedMiner, sectorNumber abi.SectorNumber, cacheDirPath, sealedSectorPath string, sealedCid cid.Cid, @@ -698,7 +453,7 @@ func manualOnboardingRunWindowPost( ctx context.Context, withMockProofs bool, client kit.TestFullNode, - miner kit.TestUnmanagedMiner, + miner *kit.TestUnmanagedMiner, sectorNumber abi.SectorNumber, cacheDirPath, sealedSectorPath string, From 4a92e8a82c8923726b0ad0140fc0b83ace8b639f Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Tue, 28 May 2024 19:37:39 +0530 Subject: [PATCH 09/20] refactor windowPost scheduler --- itests/kit/node_unmanaged.go | 473 +++++++++++++++++++++++-------- itests/manual_onboarding_test.go | 438 +--------------------------- 2 files changed, 369 insertions(+), 542 deletions(-) diff --git a/itests/kit/node_unmanaged.go b/itests/kit/node_unmanaged.go index 202c4d1b9ec..01fc7d2a0ad 100644 --- a/itests/kit/node_unmanaged.go +++ b/itests/kit/node_unmanaged.go @@ -14,6 +14,7 @@ import ( "github.com/filecoin-project/go-state-types/builtin" miner14 "github.com/filecoin-project/go-state-types/builtin/v14/miner" "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/proof" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/policy" @@ -28,18 +29,18 @@ import ( "github.com/libp2p/go-libp2p/core/peer" ) -// TestUnmanagedMiner is a miner that's not managed by the storage/ -// infrastructure, all tasks must be manually executed, managed and scheduled by -// the test or test kit. +// TestUnmanagedMiner is a miner that's not managed by the storage/infrastructure, all tasks must be manually executed, managed and scheduled by the test or test kit. +// Note: `TestUnmanagedMiner` is not thread safe and assumes linear access of it's methods type TestUnmanagedMiner struct { t *testing.T options nodeOpts - cacheDir string unsealedSectorDir string sealedSectorDir string sectorSize abi.SectorSize + currentSectorNum abi.SectorNumber + + CacheDir string - CacheDirPaths map[abi.SectorNumber]string UnsealedSectorPaths map[abi.SectorNumber]string SealedSectorPaths map[abi.SectorNumber]string SealedCids map[abi.SectorNumber]cid.Cid @@ -54,7 +55,26 @@ type TestUnmanagedMiner struct { PrivKey libp2pcrypto.PrivKey } - currentSectorNum abi.SectorNumber + activationSectors map[abi.SectorNumber]sectorActivation + sectorActivationCh chan WindowPostReq + mockProofs map[abi.SectorNumber]bool + proofType map[abi.SectorNumber]abi.RegisteredSealProof +} + +type sectorActivation struct { + SectorNumber abi.SectorNumber + RespCh chan WindowPostResp + NextPost abi.ChainEpoch +} + +type WindowPostReq struct { + SectorNumber abi.SectorNumber + RespCh chan WindowPostResp +} + +type WindowPostResp struct { + SectorNumber abi.SectorNumber + Error error } func NewTestUnmanagedMiner(t *testing.T, full *TestFullNode, actorAddr address.Address, opts ...NodeOpt) *TestUnmanagedMiner { @@ -86,21 +106,24 @@ func NewTestUnmanagedMiner(t *testing.T, full *TestFullNode, actorAddr address.A tm := TestUnmanagedMiner{ t: t, options: options, - cacheDir: cacheDir, + CacheDir: cacheDir, unsealedSectorDir: unsealedSectorDir, sealedSectorDir: sealedSectorDir, - CacheDirPaths: make(map[abi.SectorNumber]string), UnsealedSectorPaths: make(map[abi.SectorNumber]string), SealedSectorPaths: make(map[abi.SectorNumber]string), SealedCids: make(map[abi.SectorNumber]cid.Cid), UnsealedCids: make(map[abi.SectorNumber]cid.Cid), SealTickets: make(map[abi.SectorNumber]abi.SealRandomness), - ActorAddr: actorAddr, - OwnerKey: options.ownerKey, - FullNode: full, - currentSectorNum: 101, + ActorAddr: actorAddr, + OwnerKey: options.ownerKey, + FullNode: full, + currentSectorNum: 101, + activationSectors: make(map[abi.SectorNumber]sectorActivation), + mockProofs: make(map[abi.SectorNumber]bool), + proofType: make(map[abi.SectorNumber]abi.RegisteredSealProof), + sectorActivationCh: make(chan WindowPostReq), } tm.Libp2p.PeerID = peerId tm.Libp2p.PrivKey = privkey @@ -108,6 +131,16 @@ func NewTestUnmanagedMiner(t *testing.T, full *TestFullNode, actorAddr address.A return &tm } +func (tm *TestUnmanagedMiner) Start(ctx context.Context) { + go tm.wdPostScheduler(ctx) +} + +func (tm *TestUnmanagedMiner) AssertNoPower(ctx context.Context) { + p := tm.CurrentPower(ctx) + tm.t.Logf("MinerB RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) + require.True(tm.t, p.MinerPower.RawBytePower.IsZero()) +} + func (tm *TestUnmanagedMiner) CurrentPower(ctx context.Context) *api.MinerPower { head, err := tm.FullNode.ChainHead(ctx) require.NoError(tm.t, err) @@ -118,157 +151,368 @@ func (tm *TestUnmanagedMiner) CurrentPower(ctx context.Context) *api.MinerPower return p } -func (tm *TestUnmanagedMiner) AssertNoPower(ctx context.Context) { - p := tm.CurrentPower(ctx) - tm.t.Logf("MinerB RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) - require.True(tm.t, p.MinerPower.RawBytePower.IsZero()) -} - func (tm *TestUnmanagedMiner) AssertPower(ctx context.Context, raw uint64, qa uint64) { req := require.New(tm.t) p := tm.CurrentPower(ctx) tm.t.Logf("MinerB RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) req.Equal(raw, p.MinerPower.RawBytePower.Uint64()) req.Equal(qa, p.MinerPower.QualityAdjPower.Uint64()) - } -func (tm *TestUnmanagedMiner) OnboardCCSectorWithMockProofs(ctx context.Context, proofType abi.RegisteredSealProof) abi.SectorNumber { +func (tm *TestUnmanagedMiner) OnboardCCSectorWithMockProofs(ctx context.Context, proofType abi.RegisteredSealProof) (abi.SectorNumber, chan WindowPostResp) { req := require.New(tm.t) sectorNumber := tm.currentSectorNum tm.currentSectorNum++ tm.SealedCids[sectorNumber] = cid.MustParse("bagboea4b5abcatlxechwbp7kjpjguna6r6q7ejrhe6mdp3lf34pmswn27pkkiekz") - var sealRandEpoch abi.ChainEpoch - - head, err := tm.FullNode.ChainHead(ctx) - require.NoError(tm.t, err) - - if head.Height() > policy.SealRandomnessLookback { - sealRandEpoch = head.Height() - policy.SealRandomnessLookback - } else { - sealRandEpoch = policy.SealRandomnessLookback - tm.t.Logf("Waiting for at least epoch %d for seal randomness (current epoch %d) ...", sealRandEpoch+5, head.Height()) - tm.FullNode.WaitTillChain(ctx, HeightAtLeast(sealRandEpoch+5)) - } + preCommitSealRand := tm.waitPreCommitSealRandomness(ctx) // Step 4 : Submit the Pre-Commit to the network - r := tm.manualOnboardingSubmitMessage(ctx, &miner14.PreCommitSectorBatchParams2{ + r := tm.submitMessage(ctx, &miner14.PreCommitSectorBatchParams2{ Sectors: []miner14.SectorPreCommitInfo{{ Expiration: 2880 * 300, SectorNumber: sectorNumber, SealProof: TestSpt, SealedCID: tm.SealedCids[sectorNumber], - SealRandEpoch: sealRandEpoch, + SealRandEpoch: preCommitSealRand, }}, }, 1, builtin.MethodsMiner.PreCommitSectorBatch2) req.True(r.Receipt.ExitCode.IsSuccess()) - _, err = tm.FullNode.StateSectorPreCommitInfo(ctx, tm.ActorAddr, sectorNumber, r.TipSet) - req.NoError(err) - sectorProof := []byte{0xde, 0xad, 0xbe, 0xef} - var seedRandomnessHeight abi.ChainEpoch + _ = tm.proveCommitWaitSeed(ctx, sectorNumber) - head, err = tm.FullNode.ChainHead(ctx) - require.NoError(tm.t, err) - preCommitInfo, err := tm.FullNode.StateSectorPreCommitInfo(ctx, tm.ActorAddr, sectorNumber, head.Key()) - req.NoError(err) - seedRandomnessHeight = preCommitInfo.PreCommitEpoch + policy.GetPreCommitChallengeDelay() - - tm.t.Logf("Waiting %d epochs for seed randomness at epoch %d (current epoch %d)...", seedRandomnessHeight-head.Height(), seedRandomnessHeight, head.Height()) - tm.FullNode.WaitTillChain(ctx, HeightAtLeast(seedRandomnessHeight+5)) - - r = tm.manualOnboardingSubmitMessage(ctx, &miner14.ProveCommitSectors3Params{ + r = tm.submitMessage(ctx, &miner14.ProveCommitSectors3Params{ SectorActivations: []miner14.SectorActivationManifest{{SectorNumber: sectorNumber}}, SectorProofs: [][]byte{sectorProof}, RequireActivationSuccess: true, }, 0, builtin.MethodsMiner.ProveCommitSectors3) - req.NoError(err) req.True(r.Receipt.ExitCode.IsSuccess()) - return sectorNumber -} - -func (tm *TestUnmanagedMiner) OnboardCCSectorWithRealProofs(ctx context.Context, proofType abi.RegisteredSealProof) abi.SectorNumber { - req := require.New(tm.t) - sectorNumber := tm.currentSectorNum - tm.currentSectorNum++ + tm.mockProofs[sectorNumber] = true + tm.proofType[sectorNumber] = proofType - // --------------------Create pre-commit for the CC sector -> we'll just pre-commit `sector size` worth of 0s for this CC sector + // schedule WindowPosts for this sector + respCh := make(chan WindowPostResp, 1) + tm.sectorActivationCh <- WindowPostReq{SectorNumber: sectorNumber, RespCh: respCh} - // Step 1: Wait for the seal randomness to be available -> we want to draw seal randomess from a tipset that has achieved finality as PoReps are expensive to generate - // See if we already have such a epoch and wait if not - head, err := tm.FullNode.ChainHead(ctx) - require.NoError(tm.t, err) - var sealRandEpoch abi.ChainEpoch + return sectorNumber, respCh +} - if head.Height() > policy.SealRandomnessLookback { - sealRandEpoch = head.Height() - policy.SealRandomnessLookback - } else { - sealRandEpoch = policy.SealRandomnessLookback - tm.t.Logf("Waiting for at least epoch %d for seal randomness (current epoch %d) ...", sealRandEpoch+5, head.Height()) - tm.FullNode.WaitTillChain(ctx, HeightAtLeast(sealRandEpoch+5)) - } +func (tm *TestUnmanagedMiner) createCCSector(ctx context.Context, sectorNumber abi.SectorNumber) { + req := require.New(tm.t) - // Step 2: Write empty 32 bytes that we want to seal i.e. create our CC sector unsealedSectorPath := filepath.Join(tm.unsealedSectorDir, fmt.Sprintf("%d", sectorNumber)) sealedSectorPath := filepath.Join(tm.sealedSectorDir, fmt.Sprintf("%d", sectorNumber)) - unsealedSize := abi.PaddedPieceSize(tm.sectorSize).Unpadded() req.NoError(os.WriteFile(unsealedSectorPath, make([]byte, unsealedSize), 0644)) req.NoError(os.WriteFile(sealedSectorPath, make([]byte, tm.sectorSize), 0644)) - tm.t.Logf("Wrote unsealed sector to %s", unsealedSectorPath) tm.t.Logf("Wrote sealed sector to %s", sealedSectorPath) - // Step 3: Generate a Pre-Commit for the CC sector -> this persists the proof on the Miner State - tm.manualOnboardingGeneratePreCommit(ctx, tm.cacheDir, unsealedSectorPath, sealedSectorPath, sectorNumber, sealRandEpoch, proofType) + tm.UnsealedSectorPaths[sectorNumber] = unsealedSectorPath + tm.SealedSectorPaths[sectorNumber] = sealedSectorPath + + return +} + +func (tm *TestUnmanagedMiner) OnboardCCSectorWithRealProofs(ctx context.Context, proofType abi.RegisteredSealProof) (abi.SectorNumber, chan WindowPostResp) { + req := require.New(tm.t) + sectorNumber := tm.currentSectorNum + tm.currentSectorNum++ + + // --------------------Create pre-commit for the CC sector -> we'll just pre-commit `sector size` worth of 0s for this CC sector + + // Step 1: Wait for the pre-commitseal randomness to be available (we can only draw seal randomness from tipsets that have already achieved finality) + preCommitSealRand := tm.waitPreCommitSealRandomness(ctx) + + // Step 2: Write empty 32 bytes that we want to seal i.e. create our CC sector + tm.createCCSector(ctx, sectorNumber) + + // Step 3: Generate a Pre-Commit for the CC sector -> this persists the proof on the `TestUnmanagedMiner` Miner State + tm.generatePreCommit(ctx, sectorNumber, preCommitSealRand, proofType) // Step 4 : Submit the Pre-Commit to the network - r := tm.manualOnboardingSubmitMessage(ctx, &miner14.PreCommitSectorBatchParams2{ + r := tm.submitMessage(ctx, &miner14.PreCommitSectorBatchParams2{ Sectors: []miner14.SectorPreCommitInfo{{ Expiration: 2880 * 300, SectorNumber: sectorNumber, SealProof: TestSpt, SealedCID: tm.SealedCids[sectorNumber], - SealRandEpoch: sealRandEpoch, + SealRandEpoch: preCommitSealRand, }}, }, 1, builtin.MethodsMiner.PreCommitSectorBatch2) req.True(r.Receipt.ExitCode.IsSuccess()) - _, err = tm.FullNode.StateSectorPreCommitInfo(ctx, tm.ActorAddr, sectorNumber, r.TipSet) - req.NoError(err) - // Step 5: Generate a ProveCommit for the CC sector - proveCommit := tm.manualOnboardingGenerateProveCommit(ctx, sectorNumber, proofType) + waitSeedRandomness := tm.proveCommitWaitSeed(ctx, sectorNumber) + + proveCommit := tm.generateProveCommit(ctx, sectorNumber, proofType, waitSeedRandomness) // Step 6: Submit the ProveCommit to the network - tm.t.Log("Submitting MinerB ProveCommitSector ...") + tm.t.Log("Submitting ProveCommitSector ...") - r = tm.manualOnboardingSubmitMessage(ctx, &miner14.ProveCommitSectors3Params{ + r = tm.submitMessage(ctx, &miner14.ProveCommitSectors3Params{ SectorActivations: []miner14.SectorActivationManifest{{SectorNumber: sectorNumber}}, SectorProofs: [][]byte{proveCommit}, RequireActivationSuccess: true, }, 0, builtin.MethodsMiner.ProveCommitSectors3) - req.NoError(err) req.True(r.Receipt.ExitCode.IsSuccess()) - return sectorNumber + tm.mockProofs[sectorNumber] = false + tm.proofType[sectorNumber] = proofType + + // schedule WindowPosts for this sector + tm.t.Logf("Scheduling WindowPost for sector %d", sectorNumber) + respCh := make(chan WindowPostResp, 1) + tm.sectorActivationCh <- WindowPostReq{SectorNumber: sectorNumber, RespCh: respCh} + + return sectorNumber, respCh } -func (tm *TestUnmanagedMiner) manualOnboardingGeneratePreCommit( +func (tm *TestUnmanagedMiner) wdPostScheduler(ctx context.Context) { + for { + select { + case req := <-tm.sectorActivationCh: + currentEpoch, nextPost, err := tm.calculateNextPostEpoch(ctx, req.SectorNumber) + tm.t.Logf("Activating sector %d, next post %d, current epoch %d", req.SectorNumber, nextPost, currentEpoch) + if err != nil { + req.RespCh <- WindowPostResp{SectorNumber: req.SectorNumber, Error: err} + close(req.RespCh) + delete(tm.activationSectors, req.SectorNumber) + continue + } + nextPost += 5 // give a little buffer + tm.activationSectors[req.SectorNumber] = sectorActivation{SectorNumber: req.SectorNumber, RespCh: req.RespCh, NextPost: nextPost} + + go func() { + tm.FullNode.WaitTillChain(ctx, HeightAtLeast(nextPost)) + err := tm.submitWindowPost(ctx, req.SectorNumber) + req.RespCh <- WindowPostResp{SectorNumber: req.SectorNumber, Error: err} + close(req.RespCh) + delete(tm.activationSectors, req.SectorNumber) + }() + + case <-ctx.Done(): + tm.t.Logf("Context cancelled, stopping window post scheduler") + for sector, sa := range tm.activationSectors { + sa.RespCh <- WindowPostResp{SectorNumber: sector, Error: fmt.Errorf("context cancelled")} + close(sa.RespCh) + } + return + } + } +} + +func (tm *TestUnmanagedMiner) submitWindowPost(ctx context.Context, sectorNumber abi.SectorNumber) error { + fmt.Printf("WindowPoST(%d): Running WindowPoSt ...\n", sectorNumber) + + head, err := tm.FullNode.ChainHead(ctx) + if err != nil { + return fmt.Errorf("failed to get chain head: %w", err) + } + + sp, err := tm.FullNode.StateSectorPartition(ctx, tm.ActorAddr, sectorNumber, head.Key()) + if err != nil { + return fmt.Errorf("failed to get sector partition: %w", err) + } + + // We should be up to the deadline we care about + di, err := tm.FullNode.StateMinerProvingDeadline(ctx, tm.ActorAddr, head.Key()) + if err != nil { + return fmt.Errorf("failed to get proving deadline: %w", err) + } + fmt.Printf("WindowPoST(%d): SectorPartition: %+v, ProvingDeadline: %+v\n", sectorNumber, sp, di) + if di.Index != sp.Deadline { + return fmt.Errorf("sector %d is not in the deadline %d, but %d", sectorNumber, sp.Deadline, di.Index) + } + + var proofBytes []byte + if tm.mockProofs[sectorNumber] { + proofBytes = []byte{0xde, 0xad, 0xbe, 0xef} + } else { + proofBytes, err = tm.generateWindowPost(ctx, sectorNumber) + if err != nil { + return fmt.Errorf("failed to generate window post: %w", err) + } + } + + fmt.Printf("WindowedPoSt(%d) Submitting ...\n", sectorNumber) + + chainRandomnessEpoch := di.Challenge + chainRandomness, err := tm.FullNode.StateGetRandomnessFromTickets(ctx, crypto.DomainSeparationTag_PoStChainCommit, chainRandomnessEpoch, + nil, head.Key()) + if err != nil { + return fmt.Errorf("failed to get chain randomness: %w", err) + } + + minerInfo, err := tm.FullNode.StateMinerInfo(ctx, tm.ActorAddr, head.Key()) + if err != nil { + return fmt.Errorf("failed to get miner info: %w", err) + } + + r := tm.submitMessage(ctx, &miner14.SubmitWindowedPoStParams{ + ChainCommitEpoch: chainRandomnessEpoch, + ChainCommitRand: chainRandomness, + Deadline: sp.Deadline, + Partitions: []miner14.PoStPartition{{Index: sp.Partition}}, + Proofs: []proof.PoStProof{{PoStProof: minerInfo.WindowPoStProofType, ProofBytes: proofBytes}}, + }, 0, builtin.MethodsMiner.SubmitWindowedPoSt) + + fmt.Println("WindowedPoSt(%d) Submitted ...", sectorNumber, r.Receipt.ExitCode) + + if !r.Receipt.ExitCode.IsSuccess() { + return fmt.Errorf("submitting PoSt failed: %s", r.Receipt.ExitCode) + } + + return nil +} + +func (tm *TestUnmanagedMiner) generateWindowPost( + ctx context.Context, + sectorNumber abi.SectorNumber, +) ([]byte, error) { + head, err := tm.FullNode.ChainHead(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get chain head: %w", err) + } + + minerInfo, err := tm.FullNode.StateMinerInfo(ctx, tm.ActorAddr, head.Key()) + if err != nil { + return nil, fmt.Errorf("failed to get miner info: %w", err) + } + + di, err := tm.FullNode.StateMinerProvingDeadline(ctx, tm.ActorAddr, types.EmptyTSK) + if err != nil { + return nil, fmt.Errorf("failed to get proving deadline: %w", err) + } + + minerAddrBytes := new(bytes.Buffer) + if err := tm.ActorAddr.MarshalCBOR(minerAddrBytes); err != nil { + return nil, fmt.Errorf("failed to marshal miner address: %w", err) + } + + rand, err := tm.FullNode.StateGetRandomnessFromBeacon(ctx, crypto.DomainSeparationTag_WindowedPoStChallengeSeed, di.Challenge, minerAddrBytes.Bytes(), head.Key()) + if err != nil { + return nil, fmt.Errorf("failed to get randomness: %w", err) + } + postRand := abi.PoStRandomness(rand) + postRand[31] &= 0x3f // make fr32 compatible + + privateSectorInfo := ffi.PrivateSectorInfo{ + SectorInfo: proof.SectorInfo{ + SealProof: tm.proofType[sectorNumber], + SectorNumber: sectorNumber, + SealedCID: tm.SealedCids[sectorNumber], + }, + CacheDirPath: tm.CacheDir, + PoStProofType: minerInfo.WindowPoStProofType, + SealedSectorPath: tm.SealedSectorPaths[sectorNumber], + } + + actorIdNum, err := address.IDFromAddress(tm.ActorAddr) + if err != nil { + return nil, fmt.Errorf("failed to get actor ID: %w", err) + } + actorId := abi.ActorID(actorIdNum) + + windowProofs, faultySectors, err := ffi.GenerateWindowPoSt(actorId, ffi.NewSortedPrivateSectorInfo(privateSectorInfo), postRand) + if err != nil { + return nil, fmt.Errorf("failed to generate window post: %w", err) + } + if len(faultySectors) > 0 { + return nil, fmt.Errorf("post failed for sectors: %v", faultySectors) + } + if len(windowProofs) != 1 { + return nil, fmt.Errorf("expected 1 proof, got %d", len(windowProofs)) + } + if windowProofs[0].PoStProof != minerInfo.WindowPoStProofType { + return nil, fmt.Errorf("expected proof type %d, got %d", minerInfo.WindowPoStProofType, windowProofs[0].PoStProof) + } + proofBytes := windowProofs[0].ProofBytes + + info := proof.WindowPoStVerifyInfo{ + Randomness: postRand, + Proofs: []proof.PoStProof{{PoStProof: minerInfo.WindowPoStProofType, ProofBytes: proofBytes}}, + ChallengedSectors: []proof.SectorInfo{{SealProof: tm.proofType[sectorNumber], SectorNumber: sectorNumber, SealedCID: tm.SealedCids[sectorNumber]}}, + Prover: actorId, + } + + verified, err := ffi.VerifyWindowPoSt(info) + if err != nil { + return nil, fmt.Errorf("failed to verify window post: %w", err) + } + if !verified { + return nil, fmt.Errorf("window post verification failed") + } + + return proofBytes, nil +} + +func (tm *TestUnmanagedMiner) waitPreCommitSealRandomness(ctx context.Context) abi.ChainEpoch { + // we want to draw seal randomess from a tipset that has already achieved finality as PreCommits are expensive to re-generate. + // See if we already have an epoch that is already final and wait for such an epoch if we don't have one + head, err := tm.FullNode.ChainHead(ctx) + require.NoError(tm.t, err) + + var sealRandEpoch abi.ChainEpoch + if head.Height() > policy.SealRandomnessLookback { + sealRandEpoch = head.Height() - policy.SealRandomnessLookback + } else { + sealRandEpoch = policy.SealRandomnessLookback + tm.t.Logf("Waiting for at least epoch %d for seal randomness (current epoch %d) ...", sealRandEpoch+5, head.Height()) + tm.FullNode.WaitTillChain(ctx, HeightAtLeast(sealRandEpoch+5)) + } + + return sealRandEpoch +} + +// manualOnboardingCalculateNextPostEpoch calculates the first epoch of the deadline proving window +// that we want for the given sector for the given miner. +func (tm *TestUnmanagedMiner) calculateNextPostEpoch( + ctx context.Context, + sectorNumber abi.SectorNumber, +) (abi.ChainEpoch, abi.ChainEpoch, error) { + + head, err := tm.FullNode.ChainHead(ctx) + if err != nil { + return 0, 0, fmt.Errorf("failed to get chain head: %w", err) + } + + sp, err := tm.FullNode.StateSectorPartition(ctx, tm.ActorAddr, sectorNumber, head.Key()) + if err != nil { + return 0, 0, fmt.Errorf("failed to get sector partition: %w", err) + } + + fmt.Printf("WindowPoST(%d): SectorPartition: %+v\n", sectorNumber, sp) + + di, err := tm.FullNode.StateMinerProvingDeadline(ctx, tm.ActorAddr, head.Key()) + if err != nil { + return 0, 0, fmt.Errorf("failed to get proving deadline: %w", err) + } + + fmt.Printf("WindowPoST(%d): ProvingDeadline: %+v\n", sectorNumber, di) + + // periodStart tells us the first epoch of the current proving period (24h) + // although it may be in the future if we don't need to submit post in this period + periodStart := di.PeriodStart + if di.PeriodStart < di.CurrentEpoch && sp.Deadline <= di.Index { + // the deadline we want has past in this current proving period, so wait till the next one + periodStart += di.WPoStProvingPeriod + } + provingEpoch := periodStart + (di.WPoStProvingPeriod/abi.ChainEpoch(di.WPoStPeriodDeadlines))*abi.ChainEpoch(sp.Deadline) + + return di.CurrentEpoch, provingEpoch, nil +} + +func (tm *TestUnmanagedMiner) generatePreCommit( ctx context.Context, - cacheDirPath string, - unsealedSectorPath string, - sealedSectorPath string, sectorNumber abi.SectorNumber, sealRandEpoch abi.ChainEpoch, proofType abi.RegisteredSealProof, ) { - req := require.New(tm.t) tm.t.Logf("Generating proof type %d PreCommit ...", proofType) @@ -290,9 +534,9 @@ func (tm *TestUnmanagedMiner) manualOnboardingGeneratePreCommit( pc1, err := ffi.SealPreCommitPhase1( proofType, - cacheDirPath, - unsealedSectorPath, - sealedSectorPath, + tm.CacheDir, + tm.UnsealedSectorPaths[sectorNumber], + tm.SealedSectorPaths[sectorNumber], sectorNumber, actorId, sealTickets, @@ -305,8 +549,8 @@ func (tm *TestUnmanagedMiner) manualOnboardingGeneratePreCommit( sealedCid, unsealedCid, err := ffi.SealPreCommitPhase2( pc1, - cacheDirPath, - sealedSectorPath, + tm.CacheDir, + tm.SealedSectorPaths[sectorNumber], ) req.NoError(err) @@ -316,45 +560,43 @@ func (tm *TestUnmanagedMiner) manualOnboardingGeneratePreCommit( tm.SealTickets[sectorNumber] = sealTickets tm.SealedCids[sectorNumber] = sealedCid tm.UnsealedCids[sectorNumber] = unsealedCid - tm.CacheDirPaths[sectorNumber] = cacheDirPath - tm.UnsealedSectorPaths[sectorNumber] = unsealedSectorPath - tm.SealedSectorPaths[sectorNumber] = sealedSectorPath } -func (tm *TestUnmanagedMiner) manualOnboardingGenerateProveCommit( - ctx context.Context, - sectorNumber abi.SectorNumber, - proofType abi.RegisteredSealProof, -) []byte { +func (tm *TestUnmanagedMiner) proveCommitWaitSeed(ctx context.Context, sectorNumber abi.SectorNumber) abi.InteractiveSealRandomness { req := require.New(tm.t) - - tm.t.Logf("Generating proof type %d Sector Proof ...", proofType) - head, err := tm.FullNode.ChainHead(ctx) req.NoError(err) - var seedRandomnessHeight abi.ChainEpoch - preCommitInfo, err := tm.FullNode.StateSectorPreCommitInfo(ctx, tm.ActorAddr, sectorNumber, head.Key()) req.NoError(err) - seedRandomnessHeight = preCommitInfo.PreCommitEpoch + policy.GetPreCommitChallengeDelay() + seedRandomnessHeight := preCommitInfo.PreCommitEpoch + policy.GetPreCommitChallengeDelay() tm.t.Logf("Waiting %d epochs for seed randomness at epoch %d (current epoch %d)...", seedRandomnessHeight-head.Height(), seedRandomnessHeight, head.Height()) tm.FullNode.WaitTillChain(ctx, HeightAtLeast(seedRandomnessHeight+5)) - head, err = tm.FullNode.ChainHead(ctx) - req.NoError(err) - minerAddrBytes := new(bytes.Buffer) req.NoError(tm.ActorAddr.MarshalCBOR(minerAddrBytes)) - tm.t.Logf("Getting seed randomness from beacon at epoch %d", seedRandomnessHeight) - tm.t.Logf("Getting seed randomness from tickets at epoch %d", head.Height()) + head, err = tm.FullNode.ChainHead(ctx) + req.NoError(err) rand, err := tm.FullNode.StateGetRandomnessFromBeacon(ctx, crypto.DomainSeparationTag_InteractiveSealChallengeSeed, seedRandomnessHeight, minerAddrBytes.Bytes(), head.Key()) req.NoError(err) seedRandomness := abi.InteractiveSealRandomness(rand) + tm.t.Logf("Got seed randomness for sector %d: %x", sectorNumber, seedRandomness) + return seedRandomness +} + +func (tm *TestUnmanagedMiner) generateProveCommit( + ctx context.Context, + sectorNumber abi.SectorNumber, + proofType abi.RegisteredSealProof, + seedRandomness abi.InteractiveSealRandomness, +) []byte { + tm.t.Logf("Generating proof type %d Sector Proof for sector %d...", proofType, sectorNumber) + req := require.New(tm.t) + actorIdNum, err := address.IDFromAddress(tm.ActorAddr) req.NoError(err) actorId := abi.ActorID(actorIdNum) @@ -365,7 +607,7 @@ func (tm *TestUnmanagedMiner) manualOnboardingGenerateProveCommit( proofType, tm.SealedCids[sectorNumber], tm.UnsealedCids[sectorNumber], - tm.CacheDirPaths[sectorNumber], + tm.CacheDir, tm.SealedSectorPaths[sectorNumber], sectorNumber, actorId, @@ -385,13 +627,12 @@ func (tm *TestUnmanagedMiner) manualOnboardingGenerateProveCommit( return sectorProof } -func (tm *TestUnmanagedMiner) manualOnboardingSubmitMessage( +func (tm *TestUnmanagedMiner) submitMessage( ctx context.Context, params cbg.CBORMarshaler, value uint64, method abi.MethodNum, ) *api.MsgLookup { - enc, aerr := actors.SerializeParams(params) require.NoError(tm.t, aerr) diff --git a/itests/manual_onboarding_test.go b/itests/manual_onboarding_test.go index acc37526dd5..ff953589888 100644 --- a/itests/manual_onboarding_test.go +++ b/itests/manual_onboarding_test.go @@ -1,30 +1,14 @@ package itests import ( - "bytes" "context" - "fmt" - "strings" "testing" "time" - "github.com/ipfs/go-cid" - "github.com/stretchr/testify/require" - cbg "github.com/whyrusleeping/cbor-gen" - - ffi "github.com/filecoin-project/filecoin-ffi" - "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/builtin" - miner14 "github.com/filecoin-project/go-state-types/builtin/v14/miner" - "github.com/filecoin-project/go-state-types/crypto" - "github.com/filecoin-project/go-state-types/proof" - - "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/actors" - "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/itests/kit" + "github.com/stretchr/testify/require" ) const sectorSize = abi.SectorSize(2 << 10) // 2KiB @@ -71,6 +55,7 @@ func TestManualCCOnboarding(t *testing.T) { nodeOpts = append(nodeOpts, kit.OwnerAddr(client.DefaultKey)) minerB, ens := ens.UnmanagedMiner(&client, nodeOpts...) ens.Start() + minerB.Start(ctx) build.Clock.Sleep(time.Second) @@ -87,60 +72,23 @@ func TestManualCCOnboarding(t *testing.T) { minerB.AssertNoPower(ctx) var bSectorNum abi.SectorNumber + var respCh chan kit.WindowPostResp if withMockProofs { - bSectorNum = minerB.OnboardCCSectorWithMockProofs(ctx, kit.TestSpt) + bSectorNum, respCh = minerB.OnboardCCSectorWithMockProofs(ctx, kit.TestSpt) } else { - bSectorNum = minerB.OnboardCCSectorWithRealProofs(ctx, kit.TestSpt) + bSectorNum, respCh = minerB.OnboardCCSectorWithRealProofs(ctx, kit.TestSpt) } // Miner B should still not have power as power can only be gained after sector is activated i.e. the first WindowPost is submitted for it minerB.AssertNoPower(ctx) - // start a background PoST scheduler for miner B - bFirstCh, bErrCh := manualOnboardingRunWindowPost( - ctx, - withMockProofs, - client, - minerB, - bSectorNum, - minerB.CacheDirPaths[bSectorNum], - minerB.SealedSectorPaths[bSectorNum], - minerB.SealedCids[bSectorNum], - kit.TestSpt, - ) - - checkPostSchedulers := func() { - t.Helper() - select { - case err, ok := <-bErrCh: - if ok { - t.Fatalf("Received error from Miner B PoST scheduler: %v", err) - } - default: - } - } - - isClosed := func(ch <-chan struct{}) bool { - select { - case <-ch: - return true - default: - } - return false - } - - for ctx.Err() == nil { - checkPostSchedulers() - // wait till the first PoST is submitted for MinerB and check again - if isClosed(bFirstCh) { - break - } - t.Log("Waiting for first PoST to be submitted for all miners ...") - select { - case <-time.After(2 * time.Second): - case <-ctx.Done(): - t.Fatal("Context cancelled") - } + // wait till sector is activated + select { + case resp := <-respCh: + req.NoError(resp.Error) + req.Equal(resp.SectorNumber, bSectorNum) + case <-ctx.Done(): + t.Fatal("timed out waiting for sector activation") } // Fetch on-chain sector properties @@ -153,372 +101,10 @@ func TestManualCCOnboarding(t *testing.T) { head = client.WaitTillChain(ctx, kit.HeightAtLeast(head.Height()+5)) - checkPostSchedulers() - t.Log("Checking power after PoSt ...") // Miner B should now have power minerB.AssertPower(ctx, (uint64(2 << 10)), (uint64(2 << 10))) - - checkPostSchedulers() }) } } - -func manualOnboardingGenerateWindowPost( - ctx context.Context, - client api.FullNode, - cacheDirPath string, - sealedSectorPath string, - minerAddr address.Address, - sectorNumber abi.SectorNumber, - sealedCid cid.Cid, - proofType abi.RegisteredSealProof, -) ([]byte, error) { - - head, err := client.ChainHead(ctx) - if err != nil { - return nil, fmt.Errorf("failed to get chain head: %w", err) - } - - minerInfo, err := client.StateMinerInfo(ctx, minerAddr, head.Key()) - if err != nil { - return nil, fmt.Errorf("failed to get miner info: %w", err) - } - - di, err := client.StateMinerProvingDeadline(ctx, minerAddr, types.EmptyTSK) - if err != nil { - return nil, fmt.Errorf("failed to get proving deadline: %w", err) - } - - minerAddrBytes := new(bytes.Buffer) - if err := minerAddr.MarshalCBOR(minerAddrBytes); err != nil { - return nil, fmt.Errorf("failed to marshal miner address: %w", err) - } - - rand, err := client.StateGetRandomnessFromBeacon(ctx, crypto.DomainSeparationTag_WindowedPoStChallengeSeed, di.Challenge, minerAddrBytes.Bytes(), head.Key()) - if err != nil { - return nil, fmt.Errorf("failed to get randomness: %w", err) - } - postRand := abi.PoStRandomness(rand) - postRand[31] &= 0x3f // make fr32 compatible - - privateSectorInfo := ffi.PrivateSectorInfo{ - SectorInfo: proof.SectorInfo{ - SealProof: proofType, - SectorNumber: sectorNumber, - SealedCID: sealedCid, - }, - CacheDirPath: cacheDirPath, - PoStProofType: minerInfo.WindowPoStProofType, - SealedSectorPath: sealedSectorPath, - } - - actorIdNum, err := address.IDFromAddress(minerAddr) - if err != nil { - return nil, fmt.Errorf("failed to get actor ID: %w", err) - } - actorId := abi.ActorID(actorIdNum) - - windowProofs, faultySectors, err := ffi.GenerateWindowPoSt(actorId, ffi.NewSortedPrivateSectorInfo(privateSectorInfo), postRand) - if err != nil { - return nil, fmt.Errorf("failed to generate window post: %w", err) - } - if len(faultySectors) > 0 { - return nil, fmt.Errorf("post failed for sectors: %v", faultySectors) - } - if len(windowProofs) != 1 { - return nil, fmt.Errorf("expected 1 proof, got %d", len(windowProofs)) - } - if windowProofs[0].PoStProof != minerInfo.WindowPoStProofType { - return nil, fmt.Errorf("expected proof type %d, got %d", minerInfo.WindowPoStProofType, windowProofs[0].PoStProof) - } - proofBytes := windowProofs[0].ProofBytes - - info := proof.WindowPoStVerifyInfo{ - Randomness: postRand, - Proofs: []proof.PoStProof{{PoStProof: minerInfo.WindowPoStProofType, ProofBytes: proofBytes}}, - ChallengedSectors: []proof.SectorInfo{{SealProof: proofType, SectorNumber: sectorNumber, SealedCID: sealedCid}}, - Prover: actorId, - } - - verified, err := ffi.VerifyWindowPoSt(info) - if err != nil { - return nil, fmt.Errorf("failed to verify window post: %w", err) - } - if !verified { - return nil, fmt.Errorf("window post verification failed") - } - - return proofBytes, nil -} - -func manualOnboardingDisputeWindowPost( - ctx context.Context, - client kit.TestFullNode, - miner *kit.TestUnmanagedMiner, - sectorNumber abi.SectorNumber, -) error { - - head, err := client.ChainHead(ctx) - if err != nil { - return fmt.Errorf("failed to get chain head: %w", err) - } - - sp, err := client.StateSectorPartition(ctx, miner.ActorAddr, sectorNumber, head.Key()) - if err != nil { - return fmt.Errorf("failed to get sector partition: %w", err) - } - - di, err := client.StateMinerProvingDeadline(ctx, miner.ActorAddr, head.Key()) - if err != nil { - return fmt.Errorf("failed to get proving deadline: %w", err) - } - - disputeEpoch := di.Close + 5 - fmt.Printf("WindowPoST(%d): Dispute: Waiting %d until epoch %d to submit dispute\n", sectorNumber, disputeEpoch-head.Height(), disputeEpoch) - - client.WaitTillChain(ctx, kit.HeightAtLeast(disputeEpoch)) - - fmt.Printf("WindowPoST(%d): Dispute: Disputing WindowedPoSt to confirm validity...\n", sectorNumber) - - _, err = manualOnboardingSubmitMessage(ctx, client, miner, &miner14.DisputeWindowedPoStParams{ - Deadline: sp.Deadline, - PoStIndex: 0, - }, 0, builtin.MethodsMiner.DisputeWindowedPoSt) - if err == nil { - return fmt.Errorf("expected dispute to fail") - } - if !strings.Contains(err.Error(), "failed to dispute valid post") { - return fmt.Errorf("expected dispute to fail with 'failed to dispute valid post', got: %w", err) - } - if !strings.Contains(err.Error(), "(RetCode=16)") { - return fmt.Errorf("expected dispute to fail with RetCode=16, got: %w", err) - } - return nil -} - -func manualOnboardingSubmitMessage( - ctx context.Context, - client api.FullNode, - from *kit.TestUnmanagedMiner, - params cbg.CBORMarshaler, - value uint64, - method abi.MethodNum, -) (*api.MsgLookup, error) { - - enc, aerr := actors.SerializeParams(params) - if aerr != nil { - return nil, fmt.Errorf("failed to serialize params: %w", aerr) - } - - m, err := client.MpoolPushMessage(ctx, &types.Message{ - To: from.ActorAddr, - From: from.OwnerKey.Address, - Value: types.FromFil(value), - Method: method, - Params: enc, - }, nil) - if err != nil { - return nil, fmt.Errorf("failed to push message: %w", err) - } - - return client.StateWaitMsg(ctx, m.Cid(), 2, api.LookbackNoLimit, true) -} - -// manualOnboardingCalculateNextPostEpoch calculates the first epoch of the deadline proving window -// that we want for the given sector for the given miner. -func manualOnboardingCalculateNextPostEpoch( - ctx context.Context, - client api.FullNode, - minerAddr address.Address, - sectorNumber abi.SectorNumber, -) (abi.ChainEpoch, abi.ChainEpoch, error) { - - head, err := client.ChainHead(ctx) - if err != nil { - return 0, 0, fmt.Errorf("failed to get chain head: %w", err) - } - - sp, err := client.StateSectorPartition(ctx, minerAddr, sectorNumber, head.Key()) - if err != nil { - return 0, 0, fmt.Errorf("failed to get sector partition: %w", err) - } - - fmt.Printf("WindowPoST(%d): SectorPartition: %+v\n", sectorNumber, sp) - - di, err := client.StateMinerProvingDeadline(ctx, minerAddr, head.Key()) - if err != nil { - return 0, 0, fmt.Errorf("failed to get proving deadline: %w", err) - } - - fmt.Printf("WindowPoST(%d): ProvingDeadline: %+v\n", sectorNumber, di) - - // periodStart tells us the first epoch of the current proving period (24h) - // although it may be in the future if we don't need to submit post in this period - periodStart := di.PeriodStart - if di.PeriodStart < di.CurrentEpoch && sp.Deadline <= di.Index { - // the deadline we want has past in this current proving period, so wait till the next one - periodStart += di.WPoStProvingPeriod - } - provingEpoch := periodStart + (di.WPoStProvingPeriod/abi.ChainEpoch(di.WPoStPeriodDeadlines))*abi.ChainEpoch(sp.Deadline) - - return di.CurrentEpoch, provingEpoch, nil -} - -func manualOnboardingSubmitWindowPost( - ctx context.Context, - withMockProofs bool, - client kit.TestFullNode, - miner *kit.TestUnmanagedMiner, - sectorNumber abi.SectorNumber, - cacheDirPath, sealedSectorPath string, - sealedCid cid.Cid, - proofType abi.RegisteredSealProof, -) error { - fmt.Printf("WindowPoST(%d): Running WindowPoSt ...\n", sectorNumber) - - head, err := client.ChainHead(ctx) - if err != nil { - return fmt.Errorf("failed to get chain head: %w", err) - } - - sp, err := client.StateSectorPartition(ctx, miner.ActorAddr, sectorNumber, head.Key()) - if err != nil { - return fmt.Errorf("failed to get sector partition: %w", err) - } - - // We should be up to the deadline we care about - di, err := client.StateMinerProvingDeadline(ctx, miner.ActorAddr, head.Key()) - if err != nil { - return fmt.Errorf("failed to get proving deadline: %w", err) - } - fmt.Printf("WindowPoST(%d): SectorPartition: %+v, ProvingDeadline: %+v\n", sectorNumber, sp, di) - if di.Index != sp.Deadline { - return fmt.Errorf("sector %d is not in the deadline %d, but %d", sectorNumber, sp.Deadline, di.Index) - } - - var proofBytes []byte - if withMockProofs { - proofBytes = []byte{0xde, 0xad, 0xbe, 0xef} - } else { - proofBytes, err = manualOnboardingGenerateWindowPost(ctx, client, cacheDirPath, sealedSectorPath, miner.ActorAddr, sectorNumber, sealedCid, proofType) - if err != nil { - return fmt.Errorf("failed to generate window post: %w", err) - } - } - - fmt.Printf("WindowedPoSt(%d) Submitting ...\n", sectorNumber) - - chainRandomnessEpoch := di.Challenge - chainRandomness, err := client.StateGetRandomnessFromTickets(ctx, crypto.DomainSeparationTag_PoStChainCommit, chainRandomnessEpoch, nil, head.Key()) - if err != nil { - return fmt.Errorf("failed to get chain randomness: %w", err) - } - - minerInfo, err := client.StateMinerInfo(ctx, miner.ActorAddr, head.Key()) - if err != nil { - return fmt.Errorf("failed to get miner info: %w", err) - } - - r, err := manualOnboardingSubmitMessage(ctx, client, miner, &miner14.SubmitWindowedPoStParams{ - ChainCommitEpoch: chainRandomnessEpoch, - ChainCommitRand: chainRandomness, - Deadline: sp.Deadline, - Partitions: []miner14.PoStPartition{{Index: sp.Partition}}, - Proofs: []proof.PoStProof{{PoStProof: minerInfo.WindowPoStProofType, ProofBytes: proofBytes}}, - }, 0, builtin.MethodsMiner.SubmitWindowedPoSt) - if err != nil { - return fmt.Errorf("failed to submit PoSt: %w", err) - } - if !r.Receipt.ExitCode.IsSuccess() { - return fmt.Errorf("submitting PoSt failed: %s", r.Receipt.ExitCode) - } - - if !withMockProofs { - // Dispute the PoSt to confirm the validity of the PoSt since PoSt acceptance is optimistic - if err := manualOnboardingDisputeWindowPost(ctx, client, miner, sectorNumber); err != nil { - return fmt.Errorf("failed to dispute PoSt: %w", err) - } - } - return nil -} - -// manualOnboardingRunWindowPost runs a goroutine to continually submit PoSTs for the given sector -// and miner. It will wait until the next proving period for the sector and then submit the PoSt. -// It will continue to do this until the context is cancelled. -// It returns a channel that will be closed when the first PoSt is submitted and a channel that will -// receive any errors that occur. -func manualOnboardingRunWindowPost( - ctx context.Context, - withMockProofs bool, - client kit.TestFullNode, - miner *kit.TestUnmanagedMiner, - sectorNumber abi.SectorNumber, - cacheDirPath, - sealedSectorPath string, - sealedCid cid.Cid, - proofType abi.RegisteredSealProof, -) (chan struct{}, chan error) { - first := make(chan struct{}) - errCh := make(chan error) - - go func() { - for ctx.Err() == nil { - currentEpoch, nextPost, err := manualOnboardingCalculateNextPostEpoch(ctx, client, miner.ActorAddr, sectorNumber) - if err != nil { - errCh <- err - return - } - if ctx.Err() != nil { - return - } - nextPost += 5 // give a little buffer - fmt.Printf("WindowPoST(%d) Waiting %d until epoch %d to submit PoSt\n", sectorNumber, nextPost-currentEpoch, nextPost) - - // Create channel to listen for chain head - heads, err := client.ChainNotify(ctx) - if err != nil { - errCh <- err - return - } - // Wait for nextPost epoch - for chg := range heads { - var ts *types.TipSet - for _, c := range chg { - if c.Type != "apply" { - continue - } - ts = c.Val - if ts.Height() >= nextPost { - break - } - } - if ctx.Err() != nil { - return - } - if ts != nil && ts.Height() >= nextPost { - break - } - } - if ctx.Err() != nil { - return - } - - err = manualOnboardingSubmitWindowPost(ctx, withMockProofs, client, miner, sectorNumber, cacheDirPath, sealedSectorPath, sealedCid, proofType) - if err != nil { - errCh <- err - return - } - - // signal first post is done - select { - case <-first: - default: - close(first) - } - } - }() - - return first, errCh -} From 2c1738a14b0d29ab136ea2064ca5925de8419c9e Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Wed, 29 May 2024 08:59:12 +0530 Subject: [PATCH 10/20] refactoring complete for single sector single miner --- itests/kit/node_unmanaged.go | 381 +++++++++++++++---------------- itests/manual_onboarding_test.go | 172 +++++++------- 2 files changed, 270 insertions(+), 283 deletions(-) diff --git a/itests/kit/node_unmanaged.go b/itests/kit/node_unmanaged.go index 01fc7d2a0ad..ebfa7c0327c 100644 --- a/itests/kit/node_unmanaged.go +++ b/itests/kit/node_unmanaged.go @@ -32,20 +32,23 @@ import ( // TestUnmanagedMiner is a miner that's not managed by the storage/infrastructure, all tasks must be manually executed, managed and scheduled by the test or test kit. // Note: `TestUnmanagedMiner` is not thread safe and assumes linear access of it's methods type TestUnmanagedMiner struct { - t *testing.T - options nodeOpts + t *testing.T + options nodeOpts + + cacheDir string unsealedSectorDir string sealedSectorDir string sectorSize abi.SectorSize currentSectorNum abi.SectorNumber - CacheDir string + cacheDirPaths map[abi.SectorNumber]string + unsealedSectorPaths map[abi.SectorNumber]string + sealedSectorPaths map[abi.SectorNumber]string + sealedCids map[abi.SectorNumber]cid.Cid + unsealedCids map[abi.SectorNumber]cid.Cid + sealTickets map[abi.SectorNumber]abi.SealRandomness - UnsealedSectorPaths map[abi.SectorNumber]string - SealedSectorPaths map[abi.SectorNumber]string - SealedCids map[abi.SectorNumber]cid.Cid - UnsealedCids map[abi.SectorNumber]cid.Cid - SealTickets map[abi.SectorNumber]abi.SealRandomness + proofType map[abi.SectorNumber]abi.RegisteredSealProof ActorAddr address.Address OwnerKey *key.Key @@ -54,22 +57,6 @@ type TestUnmanagedMiner struct { PeerID peer.ID PrivKey libp2pcrypto.PrivKey } - - activationSectors map[abi.SectorNumber]sectorActivation - sectorActivationCh chan WindowPostReq - mockProofs map[abi.SectorNumber]bool - proofType map[abi.SectorNumber]abi.RegisteredSealProof -} - -type sectorActivation struct { - SectorNumber abi.SectorNumber - RespCh chan WindowPostResp - NextPost abi.ChainEpoch -} - -type WindowPostReq struct { - SectorNumber abi.SectorNumber - RespCh chan WindowPostResp } type WindowPostResp struct { @@ -89,7 +76,7 @@ func NewTestUnmanagedMiner(t *testing.T, full *TestFullNode, actorAddr address.A privkey, _, err := libp2pcrypto.GenerateEd25519Key(rand.Reader) require.NoError(t, err) - require.NotNil(t, options.ownerKey, "manual miner key can't be null if initializing a miner after genesis") + require.NotNil(t, options.ownerKey, "owner key is required for initializing a miner") peerId, err := peer.IDFromPrivateKey(privkey) require.NoError(t, err) @@ -106,24 +93,22 @@ func NewTestUnmanagedMiner(t *testing.T, full *TestFullNode, actorAddr address.A tm := TestUnmanagedMiner{ t: t, options: options, - CacheDir: cacheDir, + cacheDir: cacheDir, unsealedSectorDir: unsealedSectorDir, sealedSectorDir: sealedSectorDir, - UnsealedSectorPaths: make(map[abi.SectorNumber]string), - SealedSectorPaths: make(map[abi.SectorNumber]string), - SealedCids: make(map[abi.SectorNumber]cid.Cid), - UnsealedCids: make(map[abi.SectorNumber]cid.Cid), - SealTickets: make(map[abi.SectorNumber]abi.SealRandomness), - - ActorAddr: actorAddr, - OwnerKey: options.ownerKey, - FullNode: full, - currentSectorNum: 101, - activationSectors: make(map[abi.SectorNumber]sectorActivation), - mockProofs: make(map[abi.SectorNumber]bool), - proofType: make(map[abi.SectorNumber]abi.RegisteredSealProof), - sectorActivationCh: make(chan WindowPostReq), + unsealedSectorPaths: make(map[abi.SectorNumber]string), + cacheDirPaths: make(map[abi.SectorNumber]string), + sealedSectorPaths: make(map[abi.SectorNumber]string), + sealedCids: make(map[abi.SectorNumber]cid.Cid), + unsealedCids: make(map[abi.SectorNumber]cid.Cid), + sealTickets: make(map[abi.SectorNumber]abi.SealRandomness), + + ActorAddr: actorAddr, + OwnerKey: options.ownerKey, + FullNode: full, + currentSectorNum: 101, + proofType: make(map[abi.SectorNumber]abi.RegisteredSealProof), } tm.Libp2p.PeerID = peerId tm.Libp2p.PrivKey = privkey @@ -131,10 +116,6 @@ func NewTestUnmanagedMiner(t *testing.T, full *TestFullNode, actorAddr address.A return &tm } -func (tm *TestUnmanagedMiner) Start(ctx context.Context) { - go tm.wdPostScheduler(ctx) -} - func (tm *TestUnmanagedMiner) AssertNoPower(ctx context.Context) { p := tm.CurrentPower(ctx) tm.t.Logf("MinerB RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) @@ -159,61 +140,29 @@ func (tm *TestUnmanagedMiner) AssertPower(ctx context.Context, raw uint64, qa ui req.Equal(qa, p.MinerPower.QualityAdjPower.Uint64()) } -func (tm *TestUnmanagedMiner) OnboardCCSectorWithMockProofs(ctx context.Context, proofType abi.RegisteredSealProof) (abi.SectorNumber, chan WindowPostResp) { +func (tm *TestUnmanagedMiner) createCCSector(_ context.Context, sectorNumber abi.SectorNumber) { req := require.New(tm.t) - sectorNumber := tm.currentSectorNum - tm.currentSectorNum++ - - tm.SealedCids[sectorNumber] = cid.MustParse("bagboea4b5abcatlxechwbp7kjpjguna6r6q7ejrhe6mdp3lf34pmswn27pkkiekz") - - preCommitSealRand := tm.waitPreCommitSealRandomness(ctx) - - // Step 4 : Submit the Pre-Commit to the network - r := tm.submitMessage(ctx, &miner14.PreCommitSectorBatchParams2{ - Sectors: []miner14.SectorPreCommitInfo{{ - Expiration: 2880 * 300, - SectorNumber: sectorNumber, - SealProof: TestSpt, - SealedCID: tm.SealedCids[sectorNumber], - SealRandEpoch: preCommitSealRand, - }}, - }, 1, builtin.MethodsMiner.PreCommitSectorBatch2) - req.True(r.Receipt.ExitCode.IsSuccess()) - - sectorProof := []byte{0xde, 0xad, 0xbe, 0xef} - - _ = tm.proveCommitWaitSeed(ctx, sectorNumber) - r = tm.submitMessage(ctx, &miner14.ProveCommitSectors3Params{ - SectorActivations: []miner14.SectorActivationManifest{{SectorNumber: sectorNumber}}, - SectorProofs: [][]byte{sectorProof}, - RequireActivationSuccess: true, - }, 0, builtin.MethodsMiner.ProveCommitSectors3) - req.True(r.Receipt.ExitCode.IsSuccess()) - - tm.mockProofs[sectorNumber] = true - tm.proofType[sectorNumber] = proofType - - // schedule WindowPosts for this sector - respCh := make(chan WindowPostResp, 1) - tm.sectorActivationCh <- WindowPostReq{SectorNumber: sectorNumber, RespCh: respCh} - - return sectorNumber, respCh -} - -func (tm *TestUnmanagedMiner) createCCSector(ctx context.Context, sectorNumber abi.SectorNumber) { - req := require.New(tm.t) + cacheDirPath := filepath.Join(tm.cacheDir, fmt.Sprintf("%d", sectorNumber)) + err := os.Mkdir(cacheDirPath, 0755) + req.NoError(err) + tm.t.Logf("MinerB: Sector %d: created cache directory at %s", sectorNumber, cacheDirPath) unsealedSectorPath := filepath.Join(tm.unsealedSectorDir, fmt.Sprintf("%d", sectorNumber)) sealedSectorPath := filepath.Join(tm.sealedSectorDir, fmt.Sprintf("%d", sectorNumber)) unsealedSize := abi.PaddedPieceSize(tm.sectorSize).Unpadded() - req.NoError(os.WriteFile(unsealedSectorPath, make([]byte, unsealedSize), 0644)) - req.NoError(os.WriteFile(sealedSectorPath, make([]byte, tm.sectorSize), 0644)) - tm.t.Logf("Wrote unsealed sector to %s", unsealedSectorPath) - tm.t.Logf("Wrote sealed sector to %s", sealedSectorPath) - tm.UnsealedSectorPaths[sectorNumber] = unsealedSectorPath - tm.SealedSectorPaths[sectorNumber] = sealedSectorPath + err = os.WriteFile(unsealedSectorPath, make([]byte, unsealedSize), 0644) + req.NoError(err) + tm.t.Logf("MinerB: Sector %d: wrote unsealed CC sector to %s", sectorNumber, unsealedSectorPath) + + err = os.WriteFile(sealedSectorPath, make([]byte, tm.sectorSize), 0644) + req.NoError(err) + tm.t.Logf("MinerB: Sector %d: wrote sealed CC sector to %s", sectorNumber, sealedSectorPath) + + tm.unsealedSectorPaths[sectorNumber] = unsealedSectorPath + tm.sealedSectorPaths[sectorNumber] = sealedSectorPath + tm.cacheDirPaths[sectorNumber] = cacheDirPath return } @@ -226,7 +175,7 @@ func (tm *TestUnmanagedMiner) OnboardCCSectorWithRealProofs(ctx context.Context, // --------------------Create pre-commit for the CC sector -> we'll just pre-commit `sector size` worth of 0s for this CC sector // Step 1: Wait for the pre-commitseal randomness to be available (we can only draw seal randomness from tipsets that have already achieved finality) - preCommitSealRand := tm.waitPreCommitSealRandomness(ctx) + preCommitSealRand := tm.waitPreCommitSealRandomness(ctx, sectorNumber) // Step 2: Write empty 32 bytes that we want to seal i.e. create our CC sector tm.createCCSector(ctx, sectorNumber) @@ -240,7 +189,7 @@ func (tm *TestUnmanagedMiner) OnboardCCSectorWithRealProofs(ctx context.Context, Expiration: 2880 * 300, SectorNumber: sectorNumber, SealProof: TestSpt, - SealedCID: tm.SealedCids[sectorNumber], + SealedCID: tm.sealedCids[sectorNumber], SealRandEpoch: preCommitSealRand, }}, }, 1, builtin.MethodsMiner.PreCommitSectorBatch2) @@ -261,96 +210,124 @@ func (tm *TestUnmanagedMiner) OnboardCCSectorWithRealProofs(ctx context.Context, }, 0, builtin.MethodsMiner.ProveCommitSectors3) req.True(r.Receipt.ExitCode.IsSuccess()) - tm.mockProofs[sectorNumber] = false tm.proofType[sectorNumber] = proofType - // schedule WindowPosts for this sector - tm.t.Logf("Scheduling WindowPost for sector %d", sectorNumber) respCh := make(chan WindowPostResp, 1) - tm.sectorActivationCh <- WindowPostReq{SectorNumber: sectorNumber, RespCh: respCh} - return sectorNumber, respCh -} - -func (tm *TestUnmanagedMiner) wdPostScheduler(ctx context.Context) { - for { - select { - case req := <-tm.sectorActivationCh: - currentEpoch, nextPost, err := tm.calculateNextPostEpoch(ctx, req.SectorNumber) - tm.t.Logf("Activating sector %d, next post %d, current epoch %d", req.SectorNumber, nextPost, currentEpoch) - if err != nil { - req.RespCh <- WindowPostResp{SectorNumber: req.SectorNumber, Error: err} - close(req.RespCh) - delete(tm.activationSectors, req.SectorNumber) - continue + go func() { + currentEpoch, nextPost, err := tm.calculateNextPostEpoch(ctx, sectorNumber) + tm.t.Logf("Activating sector %d, next post %d, current epoch %d", sectorNumber, nextPost, currentEpoch) + if err != nil { + select { + case respCh <- WindowPostResp{SectorNumber: sectorNumber, Error: err}: + case <-ctx.Done(): + return + default: } - nextPost += 5 // give a little buffer - tm.activationSectors[req.SectorNumber] = sectorActivation{SectorNumber: req.SectorNumber, RespCh: req.RespCh, NextPost: nextPost} + return + } - go func() { - tm.FullNode.WaitTillChain(ctx, HeightAtLeast(nextPost)) - err := tm.submitWindowPost(ctx, req.SectorNumber) - req.RespCh <- WindowPostResp{SectorNumber: req.SectorNumber, Error: err} - close(req.RespCh) - delete(tm.activationSectors, req.SectorNumber) - }() + nextPost += 5 // Buffer to ensure readiness for submission + tm.FullNode.WaitTillChain(ctx, HeightAtLeast(nextPost)) + err = tm.submitWindowPost(ctx, sectorNumber) + select { + case respCh <- WindowPostResp{SectorNumber: sectorNumber, Error: err}: case <-ctx.Done(): - tm.t.Logf("Context cancelled, stopping window post scheduler") - for sector, sa := range tm.activationSectors { - sa.RespCh <- WindowPostResp{SectorNumber: sector, Error: fmt.Errorf("context cancelled")} - close(sa.RespCh) - } return + default: } + }() + + return sectorNumber, respCh +} + +func (tm *TestUnmanagedMiner) SubmitPostDispute(ctx context.Context, sectorNumber abi.SectorNumber) error { + tm.t.Logf("MinerB(%s): Starting dispute submission for sector %d", tm.ActorAddr, sectorNumber) + + head, err := tm.FullNode.ChainHead(ctx) + if err != nil { + return fmt.Errorf("MinerB(%s): failed to get chain head: %w", tm.ActorAddr, err) + } + + sp, err := tm.FullNode.StateSectorPartition(ctx, tm.ActorAddr, sectorNumber, head.Key()) + if err != nil { + return fmt.Errorf("MinerB(%s): failed to get sector partition for sector %d: %w", tm.ActorAddr, sectorNumber, err) + } + + di, err := tm.FullNode.StateMinerProvingDeadline(ctx, tm.ActorAddr, head.Key()) + if err != nil { + return fmt.Errorf("MinerB(%s): failed to get proving deadline for sector %d: %w", tm.ActorAddr, sectorNumber, err) + } + + disputeEpoch := di.Close + 5 + tm.t.Logf("MinerB(%s): Sector %d - Waiting %d epochs until epoch %d to submit dispute", tm.ActorAddr, sectorNumber, disputeEpoch-head.Height(), disputeEpoch) + + tm.FullNode.WaitTillChain(ctx, HeightAtLeast(disputeEpoch)) + + tm.t.Logf("MinerB(%s): Sector %d - Disputing WindowedPoSt to confirm validity at epoch %d", tm.ActorAddr, sectorNumber, disputeEpoch) + + params := &miner14.DisputeWindowedPoStParams{ + Deadline: sp.Deadline, + PoStIndex: 0, + } + + enc, aerr := actors.SerializeParams(params) + require.NoError(tm.t, aerr) + + _, err = tm.FullNode.MpoolPushMessage(ctx, &types.Message{ + To: tm.ActorAddr, + From: tm.OwnerKey.Address, + Value: types.FromFil(1), + Method: builtin.MethodsMiner.DisputeWindowedPoSt, + Params: enc, + }, nil) + if err != nil { + tm.t.Logf("MinerB(%s): Failed to push dispute message for sector %d: %s", tm.ActorAddr, sectorNumber, err) } + return err } func (tm *TestUnmanagedMiner) submitWindowPost(ctx context.Context, sectorNumber abi.SectorNumber) error { - fmt.Printf("WindowPoST(%d): Running WindowPoSt ...\n", sectorNumber) + tm.t.Logf("Miner(%s): WindowPoST(%d): Running WindowPoSt ...\n", tm.ActorAddr, sectorNumber) head, err := tm.FullNode.ChainHead(ctx) if err != nil { - return fmt.Errorf("failed to get chain head: %w", err) + return fmt.Errorf("Miner(%s): failed to get chain head: %w", tm.ActorAddr, err) } sp, err := tm.FullNode.StateSectorPartition(ctx, tm.ActorAddr, sectorNumber, head.Key()) if err != nil { - return fmt.Errorf("failed to get sector partition: %w", err) + return fmt.Errorf("Miner(%s): failed to get sector partition for sector %d: %w", tm.ActorAddr, sectorNumber, err) } - // We should be up to the deadline we care about di, err := tm.FullNode.StateMinerProvingDeadline(ctx, tm.ActorAddr, head.Key()) if err != nil { - return fmt.Errorf("failed to get proving deadline: %w", err) + return fmt.Errorf("Miner(%s): failed to get proving deadline for sector %d: %w", tm.ActorAddr, sectorNumber, err) } - fmt.Printf("WindowPoST(%d): SectorPartition: %+v, ProvingDeadline: %+v\n", sectorNumber, sp, di) + tm.t.Logf("Miner(%s): WindowPoST(%d): SectorPartition: %+v, ProvingDeadline: %+v\n", tm.ActorAddr, sectorNumber, sp, di) if di.Index != sp.Deadline { - return fmt.Errorf("sector %d is not in the deadline %d, but %d", sectorNumber, sp.Deadline, di.Index) + return fmt.Errorf("Miner(%s): sector %d is not in the deadline %d, but %d", tm.ActorAddr, sectorNumber, sp.Deadline, di.Index) } var proofBytes []byte - if tm.mockProofs[sectorNumber] { - proofBytes = []byte{0xde, 0xad, 0xbe, 0xef} - } else { - proofBytes, err = tm.generateWindowPost(ctx, sectorNumber) - if err != nil { - return fmt.Errorf("failed to generate window post: %w", err) - } + proofBytes, err = tm.generateWindowPost(ctx, sectorNumber) + if err != nil { + return fmt.Errorf("Miner(%s): failed to generate window post for sector %d: %w", tm.ActorAddr, sectorNumber, err) } - fmt.Printf("WindowedPoSt(%d) Submitting ...\n", sectorNumber) + tm.t.Logf("Miner(%s): WindowedPoSt(%d) Submitting ...\n", tm.ActorAddr, sectorNumber) chainRandomnessEpoch := di.Challenge chainRandomness, err := tm.FullNode.StateGetRandomnessFromTickets(ctx, crypto.DomainSeparationTag_PoStChainCommit, chainRandomnessEpoch, nil, head.Key()) if err != nil { - return fmt.Errorf("failed to get chain randomness: %w", err) + return fmt.Errorf("Miner(%s): failed to get chain randomness for sector %d: %w", tm.ActorAddr, sectorNumber, err) } minerInfo, err := tm.FullNode.StateMinerInfo(ctx, tm.ActorAddr, head.Key()) if err != nil { - return fmt.Errorf("failed to get miner info: %w", err) + return fmt.Errorf("Miner(%s): failed to get miner info for sector %d: %w", tm.ActorAddr, sectorNumber, err) } r := tm.submitMessage(ctx, &miner14.SubmitWindowedPoStParams{ @@ -361,12 +338,12 @@ func (tm *TestUnmanagedMiner) submitWindowPost(ctx context.Context, sectorNumber Proofs: []proof.PoStProof{{PoStProof: minerInfo.WindowPoStProofType, ProofBytes: proofBytes}}, }, 0, builtin.MethodsMiner.SubmitWindowedPoSt) - fmt.Println("WindowedPoSt(%d) Submitted ...", sectorNumber, r.Receipt.ExitCode) - if !r.Receipt.ExitCode.IsSuccess() { - return fmt.Errorf("submitting PoSt failed: %s", r.Receipt.ExitCode) + return fmt.Errorf("Miner(%s): submitting PoSt for sector %d failed: %s", tm.ActorAddr, sectorNumber, r.Receipt.ExitCode) } + tm.t.Logf("Miner(%s): WindowedPoSt(%d) Submitted ...\n", tm.ActorAddr, sectorNumber) + return nil } @@ -405,11 +382,11 @@ func (tm *TestUnmanagedMiner) generateWindowPost( SectorInfo: proof.SectorInfo{ SealProof: tm.proofType[sectorNumber], SectorNumber: sectorNumber, - SealedCID: tm.SealedCids[sectorNumber], + SealedCID: tm.sealedCids[sectorNumber], }, - CacheDirPath: tm.CacheDir, + CacheDirPath: tm.cacheDirPaths[sectorNumber], PoStProofType: minerInfo.WindowPoStProofType, - SealedSectorPath: tm.SealedSectorPaths[sectorNumber], + SealedSectorPath: tm.sealedSectorPaths[sectorNumber], } actorIdNum, err := address.IDFromAddress(tm.ActorAddr) @@ -436,7 +413,7 @@ func (tm *TestUnmanagedMiner) generateWindowPost( info := proof.WindowPoStVerifyInfo{ Randomness: postRand, Proofs: []proof.PoStProof{{PoStProof: minerInfo.WindowPoStProofType, ProofBytes: proofBytes}}, - ChallengedSectors: []proof.SectorInfo{{SealProof: tm.proofType[sectorNumber], SectorNumber: sectorNumber, SealedCID: tm.SealedCids[sectorNumber]}}, + ChallengedSectors: []proof.SectorInfo{{SealProof: tm.proofType[sectorNumber], SectorNumber: sectorNumber, SealedCID: tm.sealedCids[sectorNumber]}}, Prover: actorId, } @@ -450,10 +427,9 @@ func (tm *TestUnmanagedMiner) generateWindowPost( return proofBytes, nil } - -func (tm *TestUnmanagedMiner) waitPreCommitSealRandomness(ctx context.Context) abi.ChainEpoch { - // we want to draw seal randomess from a tipset that has already achieved finality as PreCommits are expensive to re-generate. - // See if we already have an epoch that is already final and wait for such an epoch if we don't have one +func (tm *TestUnmanagedMiner) waitPreCommitSealRandomness(ctx context.Context, sectorNumber abi.SectorNumber) abi.ChainEpoch { + // We want to draw seal randomness from a tipset that has already achieved finality as PreCommits are expensive to re-generate. + // Check if we already have an epoch that is already final and wait for such an epoch if we don't have one. head, err := tm.FullNode.ChainHead(ctx) require.NoError(tm.t, err) @@ -462,48 +438,57 @@ func (tm *TestUnmanagedMiner) waitPreCommitSealRandomness(ctx context.Context) a sealRandEpoch = head.Height() - policy.SealRandomnessLookback } else { sealRandEpoch = policy.SealRandomnessLookback - tm.t.Logf("Waiting for at least epoch %d for seal randomness (current epoch %d) ...", sealRandEpoch+5, head.Height()) + tm.t.Logf("Miner %s waiting for at least epoch %d for seal randomness for sector %d (current epoch %d)...", tm.ActorAddr, sealRandEpoch+5, + sectorNumber, head.Height()) tm.FullNode.WaitTillChain(ctx, HeightAtLeast(sealRandEpoch+5)) } + tm.t.Logf("Miner %s using seal randomness from epoch %d for head %d for sector %d", tm.ActorAddr, sealRandEpoch, head.Height(), sectorNumber) + return sealRandEpoch } -// manualOnboardingCalculateNextPostEpoch calculates the first epoch of the deadline proving window -// that we want for the given sector for the given miner. +// calculateNextPostEpoch calculates the first epoch of the deadline proving window +// that is desired for the given sector for the specified miner. +// This function returns the current epoch and the calculated proving epoch. func (tm *TestUnmanagedMiner) calculateNextPostEpoch( ctx context.Context, sectorNumber abi.SectorNumber, ) (abi.ChainEpoch, abi.ChainEpoch, error) { - + // Retrieve the current blockchain head head, err := tm.FullNode.ChainHead(ctx) if err != nil { return 0, 0, fmt.Errorf("failed to get chain head: %w", err) } + // Fetch the sector partition for the given sector number sp, err := tm.FullNode.StateSectorPartition(ctx, tm.ActorAddr, sectorNumber, head.Key()) if err != nil { return 0, 0, fmt.Errorf("failed to get sector partition: %w", err) } - fmt.Printf("WindowPoST(%d): SectorPartition: %+v\n", sectorNumber, sp) + tm.t.Logf("Miner %s: WindowPoST(%d): SectorPartition: %+v", tm.ActorAddr, sectorNumber, sp) + // Obtain the proving deadline information for the miner di, err := tm.FullNode.StateMinerProvingDeadline(ctx, tm.ActorAddr, head.Key()) if err != nil { return 0, 0, fmt.Errorf("failed to get proving deadline: %w", err) } - fmt.Printf("WindowPoST(%d): ProvingDeadline: %+v\n", sectorNumber, di) + tm.t.Logf("Miner %s: WindowPoST(%d): ProvingDeadline: %+v", tm.ActorAddr, sectorNumber, di) - // periodStart tells us the first epoch of the current proving period (24h) - // although it may be in the future if we don't need to submit post in this period + // Calculate the start of the period, adjusting if the current deadline has passed periodStart := di.PeriodStart if di.PeriodStart < di.CurrentEpoch && sp.Deadline <= di.Index { - // the deadline we want has past in this current proving period, so wait till the next one + // If the deadline has passed in the current proving period, calculate for the next period periodStart += di.WPoStProvingPeriod } + + // Calculate the exact epoch when proving should occur provingEpoch := periodStart + (di.WPoStProvingPeriod/abi.ChainEpoch(di.WPoStPeriodDeadlines))*abi.ChainEpoch(sp.Deadline) + tm.t.Logf("Miner %s: WindowPoST(%d): next ProvingEpoch: %d", tm.ActorAddr, sectorNumber, provingEpoch) + return di.CurrentEpoch, provingEpoch, nil } @@ -514,52 +499,52 @@ func (tm *TestUnmanagedMiner) generatePreCommit( proofType abi.RegisteredSealProof, ) { req := require.New(tm.t) - tm.t.Logf("Generating proof type %d PreCommit ...", proofType) + tm.t.Logf("Miner %s: Generating proof type %d PreCommit for sector %d...", tm.ActorAddr, proofType, sectorNumber) head, err := tm.FullNode.ChainHead(ctx) - req.NoError(err) + req.NoError(err, "Miner %s: Failed to get chain head for sector %d", tm.ActorAddr, sectorNumber) minerAddrBytes := new(bytes.Buffer) - req.NoError(tm.ActorAddr.MarshalCBOR(minerAddrBytes)) + req.NoError(tm.ActorAddr.MarshalCBOR(minerAddrBytes), "Miner %s: Failed to marshal address for sector %d", tm.ActorAddr, sectorNumber) rand, err := tm.FullNode.StateGetRandomnessFromTickets(ctx, crypto.DomainSeparationTag_SealRandomness, sealRandEpoch, minerAddrBytes.Bytes(), head.Key()) - req.NoError(err) + req.NoError(err, "Miner %s: Failed to get randomness for sector %d", tm.ActorAddr, sectorNumber) sealTickets := abi.SealRandomness(rand) - tm.t.Logf("Running proof type %d SealPreCommitPhase1 for sector %d...", proofType, sectorNumber) + tm.t.Logf("Miner %s: Running proof type %d SealPreCommitPhase1 for sector %d...", tm.ActorAddr, proofType, sectorNumber) actorIdNum, err := address.IDFromAddress(tm.ActorAddr) - req.NoError(err) + req.NoError(err, "Miner %s: Failed to get actor ID for sector %d", tm.ActorAddr, sectorNumber) actorId := abi.ActorID(actorIdNum) pc1, err := ffi.SealPreCommitPhase1( proofType, - tm.CacheDir, - tm.UnsealedSectorPaths[sectorNumber], - tm.SealedSectorPaths[sectorNumber], + tm.cacheDirPaths[sectorNumber], + tm.unsealedSectorPaths[sectorNumber], + tm.sealedSectorPaths[sectorNumber], sectorNumber, actorId, sealTickets, []abi.PieceInfo{}, ) - req.NoError(err) - req.NotNil(pc1) + req.NoError(err, "Miner %s: SealPreCommitPhase1 failed for sector %d", tm.ActorAddr, sectorNumber) + req.NotNil(pc1, "Miner %s: SealPreCommitPhase1 returned nil for sector %d", tm.ActorAddr, sectorNumber) - tm.t.Logf("Running proof type %d SealPreCommitPhase2 for sector %d...", proofType, sectorNumber) + tm.t.Logf("Miner %s: Running proof type %d SealPreCommitPhase2 for sector %d...", tm.ActorAddr, proofType, sectorNumber) sealedCid, unsealedCid, err := ffi.SealPreCommitPhase2( pc1, - tm.CacheDir, - tm.SealedSectorPaths[sectorNumber], + tm.cacheDirPaths[sectorNumber], + tm.sealedSectorPaths[sectorNumber], ) - req.NoError(err) + req.NoError(err, "Miner %s: SealPreCommitPhase2 failed for sector %d", tm.ActorAddr, sectorNumber) - tm.t.Logf("Unsealed CID: %s", unsealedCid) - tm.t.Logf("Sealed CID: %s", sealedCid) + tm.t.Logf("Miner %s: Unsealed CID for sector %d: %s", tm.ActorAddr, sectorNumber, unsealedCid) + tm.t.Logf("Miner %s: Sealed CID for sector %d: %s", tm.ActorAddr, sectorNumber, sealedCid) - tm.SealTickets[sectorNumber] = sealTickets - tm.SealedCids[sectorNumber] = sealedCid - tm.UnsealedCids[sectorNumber] = unsealedCid + tm.sealTickets[sectorNumber] = sealTickets + tm.sealedCids[sectorNumber] = sealedCid + tm.unsealedCids[sectorNumber] = unsealedCid } func (tm *TestUnmanagedMiner) proveCommitWaitSeed(ctx context.Context, sectorNumber abi.SectorNumber) abi.InteractiveSealRandomness { @@ -567,11 +552,12 @@ func (tm *TestUnmanagedMiner) proveCommitWaitSeed(ctx context.Context, sectorNum head, err := tm.FullNode.ChainHead(ctx) req.NoError(err) + tm.t.Logf("Miner %s: Fetching pre-commit info for sector %d...", tm.ActorAddr, sectorNumber) preCommitInfo, err := tm.FullNode.StateSectorPreCommitInfo(ctx, tm.ActorAddr, sectorNumber, head.Key()) req.NoError(err) seedRandomnessHeight := preCommitInfo.PreCommitEpoch + policy.GetPreCommitChallengeDelay() - tm.t.Logf("Waiting %d epochs for seed randomness at epoch %d (current epoch %d)...", seedRandomnessHeight-head.Height(), seedRandomnessHeight, head.Height()) + tm.t.Logf("Miner %s: Waiting %d epochs for seed randomness at epoch %d (current epoch %d) for sector %d...", tm.ActorAddr, seedRandomnessHeight-head.Height(), seedRandomnessHeight, head.Height(), sectorNumber) tm.FullNode.WaitTillChain(ctx, HeightAtLeast(seedRandomnessHeight+5)) minerAddrBytes := new(bytes.Buffer) @@ -580,11 +566,12 @@ func (tm *TestUnmanagedMiner) proveCommitWaitSeed(ctx context.Context, sectorNum head, err = tm.FullNode.ChainHead(ctx) req.NoError(err) + tm.t.Logf("Miner %s: Fetching seed randomness for sector %d...", tm.ActorAddr, sectorNumber) rand, err := tm.FullNode.StateGetRandomnessFromBeacon(ctx, crypto.DomainSeparationTag_InteractiveSealChallengeSeed, seedRandomnessHeight, minerAddrBytes.Bytes(), head.Key()) req.NoError(err) seedRandomness := abi.InteractiveSealRandomness(rand) - tm.t.Logf("Got seed randomness for sector %d: %x", sectorNumber, seedRandomness) + tm.t.Logf("Miner %s: Obtained seed randomness for sector %d: %x", tm.ActorAddr, sectorNumber, seedRandomness) return seedRandomness } @@ -594,35 +581,35 @@ func (tm *TestUnmanagedMiner) generateProveCommit( proofType abi.RegisteredSealProof, seedRandomness abi.InteractiveSealRandomness, ) []byte { - tm.t.Logf("Generating proof type %d Sector Proof for sector %d...", proofType, sectorNumber) + tm.t.Logf("Miner %s: Generating proof type %d Sector Proof for sector %d...", tm.ActorAddr, proofType, sectorNumber) req := require.New(tm.t) actorIdNum, err := address.IDFromAddress(tm.ActorAddr) req.NoError(err) actorId := abi.ActorID(actorIdNum) - tm.t.Logf("Running proof type %d SealCommitPhase1 for sector %d...", proofType, sectorNumber) + tm.t.Logf("Miner %s: Running proof type %d SealCommitPhase1 for sector %d...", tm.ActorAddr, proofType, sectorNumber) scp1, err := ffi.SealCommitPhase1( proofType, - tm.SealedCids[sectorNumber], - tm.UnsealedCids[sectorNumber], - tm.CacheDir, - tm.SealedSectorPaths[sectorNumber], + tm.sealedCids[sectorNumber], + tm.unsealedCids[sectorNumber], + tm.cacheDirPaths[sectorNumber], + tm.sealedSectorPaths[sectorNumber], sectorNumber, actorId, - tm.SealTickets[sectorNumber], + tm.sealTickets[sectorNumber], seedRandomness, []abi.PieceInfo{}, ) req.NoError(err) - tm.t.Logf("Running proof type %d SealCommitPhase2 for sector %d...", proofType, sectorNumber) + tm.t.Logf("Miner %s: Running proof type %d SealCommitPhase2 for sector %d...", tm.ActorAddr, proofType, sectorNumber) sectorProof, err := ffi.SealCommitPhase2(scp1, sectorNumber, actorId) req.NoError(err) - tm.t.Logf("Got proof type %d sector proof of length %d", proofType, len(sectorProof)) + tm.t.Logf("Miner %s: Got proof type %d sector proof of length %d for sector %d", tm.ActorAddr, proofType, len(sectorProof), sectorNumber) return sectorProof } @@ -636,6 +623,8 @@ func (tm *TestUnmanagedMiner) submitMessage( enc, aerr := actors.SerializeParams(params) require.NoError(tm.t, aerr) + tm.t.Logf("Submitting message for miner %s with method number %d", tm.ActorAddr, method) + m, err := tm.FullNode.MpoolPushMessage(ctx, &types.Message{ To: tm.ActorAddr, From: tm.OwnerKey.Address, @@ -645,7 +634,11 @@ func (tm *TestUnmanagedMiner) submitMessage( }, nil) require.NoError(tm.t, err) + tm.t.Logf("Pushed message with CID: %s for miner %s", m.Cid(), tm.ActorAddr) + msg, err := tm.FullNode.StateWaitMsg(ctx, m.Cid(), 2, api.LookbackNoLimit, true) require.NoError(tm.t, err) + tm.t.Logf("Message with CID: %s has been confirmed on-chain for miner %s", m.Cid(), tm.ActorAddr) + return msg } diff --git a/itests/manual_onboarding_test.go b/itests/manual_onboarding_test.go index ff953589888..fed41c9d7d8 100644 --- a/itests/manual_onboarding_test.go +++ b/itests/manual_onboarding_test.go @@ -17,94 +17,88 @@ const sectorSize = abi.SectorSize(2 << 10) // 2KiB func TestManualCCOnboarding(t *testing.T) { req := require.New(t) - for _, withMockProofs := range []bool{true, false} { - testName := "WithRealProofs" - if withMockProofs { - testName = "WithMockProofs" - } - - t.Run(testName, func(t *testing.T) { - kit.QuietMiningLogs() - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - var ( - blocktime = 2 * time.Millisecond - client kit.TestFullNode - minerA kit.TestMiner // A is a standard genesis miner - ) - - // Setup and begin mining with a single miner (A) - // Miner A will only be a genesis Miner with power allocated in the genesis block and will not onboard any sectors from here on - kitOpts := []kit.EnsembleOpt{} - if withMockProofs { - kitOpts = append(kitOpts, kit.MockProofs()) - } - nodeOpts := []kit.NodeOpt{kit.SectorSize(sectorSize), kit.WithAllSubsystems()} - ens := kit.NewEnsemble(t, kitOpts...). - FullNode(&client, nodeOpts...). - Miner(&minerA, &client, nodeOpts...). - Start(). - InterconnectAll() - ens.BeginMining(blocktime) - - // Instantiate MinerB to manually handle sector onboarding and power acquisition through sector activation. - // Unlike other miners managed by the Lotus Miner storage infrastructure, MinerB operates independently, - // performing all related tasks manually. Managed by the TestKit, MinerB has the capability to utilize actual proofs - // for the processes of sector onboarding and activation. - nodeOpts = append(nodeOpts, kit.OwnerAddr(client.DefaultKey)) - minerB, ens := ens.UnmanagedMiner(&client, nodeOpts...) - ens.Start() - minerB.Start(ctx) - - build.Clock.Sleep(time.Second) - - t.Log("Checking initial power ...") - - // Miner A should have power as it has already onboarded sectors in the genesis block - head, err := client.ChainHead(ctx) - req.NoError(err) - p, err := client.StateMinerPower(ctx, minerA.ActorAddr, head.Key()) - req.NoError(err) - t.Logf("MinerA RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) - - // Miner B should have no power as it has yet to onboard and activate any sectors - minerB.AssertNoPower(ctx) - - var bSectorNum abi.SectorNumber - var respCh chan kit.WindowPostResp - if withMockProofs { - bSectorNum, respCh = minerB.OnboardCCSectorWithMockProofs(ctx, kit.TestSpt) - } else { - bSectorNum, respCh = minerB.OnboardCCSectorWithRealProofs(ctx, kit.TestSpt) - } - - // Miner B should still not have power as power can only be gained after sector is activated i.e. the first WindowPost is submitted for it - minerB.AssertNoPower(ctx) - - // wait till sector is activated - select { - case resp := <-respCh: - req.NoError(resp.Error) - req.Equal(resp.SectorNumber, bSectorNum) - case <-ctx.Done(): - t.Fatal("timed out waiting for sector activation") - } - - // Fetch on-chain sector properties - head, err = client.ChainHead(ctx) - req.NoError(err) - - soi, err := client.StateSectorGetInfo(ctx, minerB.ActorAddr, bSectorNum, head.Key()) - req.NoError(err) - t.Logf("Miner B SectorOnChainInfo %d: %+v", bSectorNum, soi) - - head = client.WaitTillChain(ctx, kit.HeightAtLeast(head.Height()+5)) - - t.Log("Checking power after PoSt ...") - - // Miner B should now have power - minerB.AssertPower(ctx, (uint64(2 << 10)), (uint64(2 << 10))) - }) + kit.QuietMiningLogs() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var ( + blocktime = 2 * time.Millisecond + client kit.TestFullNode + minerA kit.TestMiner // A is a standard genesis miner + ) + + // Setup and begin mining with a single miner (A) + // Miner A will only be a genesis Miner with power allocated in the genesis block and will not onboard any sectors from here on + kitOpts := []kit.EnsembleOpt{} + nodeOpts := []kit.NodeOpt{kit.SectorSize(sectorSize), kit.WithAllSubsystems()} + ens := kit.NewEnsemble(t, kitOpts...). + FullNode(&client, nodeOpts...). + Miner(&minerA, &client, nodeOpts...). + Start(). + InterconnectAll() + ens.BeginMining(blocktime) + + // Instantiate MinerB to manually handle sector onboarding and power acquisition through sector activation. + // Unlike other miners managed by the Lotus Miner storage infrastructure, MinerB operates independently, + // performing all related tasks manually. Managed by the TestKit, MinerB has the capability to utilize actual proofs + // for the processes of sector onboarding and activation. + nodeOpts = append(nodeOpts, kit.OwnerAddr(client.DefaultKey)) + minerB, ens := ens.UnmanagedMiner(&client, nodeOpts...) + ens.Start() + + build.Clock.Sleep(time.Second) + + t.Log("Checking initial power ...") + + // Miner A should have power as it has already onboarded sectors in the genesis block + head, err := client.ChainHead(ctx) + req.NoError(err) + p, err := client.StateMinerPower(ctx, minerA.ActorAddr, head.Key()) + req.NoError(err) + t.Logf("MinerA RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) + + // Miner B should have no power as it has yet to onboard and activate any sectors + minerB.AssertNoPower(ctx) + + var bSectorNum abi.SectorNumber + var respCh chan kit.WindowPostResp + + bSectorNum, respCh = minerB.OnboardCCSectorWithRealProofs(ctx, kit.TestSpt) + + // Miner B should still not have power as power can only be gained after sector is activated i.e. the first WindowPost is submitted for it + minerB.AssertNoPower(ctx) + + // wait till sector is activated + select { + case resp := <-respCh: + req.NoError(resp.Error) + req.Equal(resp.SectorNumber, bSectorNum) + case <-ctx.Done(): + t.Fatal("timed out waiting for sector activation") } + + // Fetch on-chain sector properties + head, err = client.ChainHead(ctx) + req.NoError(err) + + soi, err := client.StateSectorGetInfo(ctx, minerB.ActorAddr, bSectorNum, head.Key()) + req.NoError(err) + t.Logf("Miner B SectorOnChainInfo %d: %+v", bSectorNum, soi) + + head = client.WaitTillChain(ctx, kit.HeightAtLeast(head.Height()+5)) + + t.Log("Checking power after PoSt ...") + + // Miner B should now have power + minerB.AssertPower(ctx, (uint64(2 << 10)), (uint64(2 << 10))) + + // WindowPost Dispute should fail + assertDisputeFails(t, ctx, minerB, bSectorNum) } + +func assertDisputeFails(t *testing.T, ctx context.Context, miner *kit.TestUnmanagedMiner, sector abi.SectorNumber) { + err := miner.SubmitPostDispute(ctx, sector) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to dispute valid post") + require.Contains(t, err.Error(), "(RetCode=16)") +} \ No newline at end of file From ed24fd6f715eea9761531e9aefcbcbf738f70995 Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Wed, 29 May 2024 09:44:00 +0530 Subject: [PATCH 11/20] make gen --- itests/kit/node_unmanaged.go | 15 ++++++++------- itests/manual_onboarding_test.go | 12 +++++++----- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/itests/kit/node_unmanaged.go b/itests/kit/node_unmanaged.go index ebfa7c0327c..05ff2180d28 100644 --- a/itests/kit/node_unmanaged.go +++ b/itests/kit/node_unmanaged.go @@ -9,24 +9,25 @@ import ( "path/filepath" "testing" + "github.com/ipfs/go-cid" + libp2pcrypto "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/require" + cbg "github.com/whyrusleeping/cbor-gen" + ffi "github.com/filecoin-project/filecoin-ffi" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/builtin" miner14 "github.com/filecoin-project/go-state-types/builtin/v14/miner" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/proof" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" - "github.com/ipfs/go-cid" - "github.com/stretchr/testify/require" - cbg "github.com/whyrusleeping/cbor-gen" - - "github.com/filecoin-project/go-address" "github.com/filecoin-project/lotus/chain/wallet/key" - libp2pcrypto "github.com/libp2p/go-libp2p/core/crypto" - "github.com/libp2p/go-libp2p/core/peer" ) // TestUnmanagedMiner is a miner that's not managed by the storage/infrastructure, all tasks must be manually executed, managed and scheduled by the test or test kit. diff --git a/itests/manual_onboarding_test.go b/itests/manual_onboarding_test.go index fed41c9d7d8..bb3b5d0d034 100644 --- a/itests/manual_onboarding_test.go +++ b/itests/manual_onboarding_test.go @@ -5,10 +5,12 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/itests/kit" - "github.com/stretchr/testify/require" ) const sectorSize = abi.SectorSize(2 << 10) // 2KiB @@ -85,7 +87,7 @@ func TestManualCCOnboarding(t *testing.T) { req.NoError(err) t.Logf("Miner B SectorOnChainInfo %d: %+v", bSectorNum, soi) - head = client.WaitTillChain(ctx, kit.HeightAtLeast(head.Height()+5)) + _ = client.WaitTillChain(ctx, kit.HeightAtLeast(head.Height()+5)) t.Log("Checking power after PoSt ...") @@ -93,12 +95,12 @@ func TestManualCCOnboarding(t *testing.T) { minerB.AssertPower(ctx, (uint64(2 << 10)), (uint64(2 << 10))) // WindowPost Dispute should fail - assertDisputeFails(t, ctx, minerB, bSectorNum) + assertDisputeFails(ctx, t, minerB, bSectorNum) } -func assertDisputeFails(t *testing.T, ctx context.Context, miner *kit.TestUnmanagedMiner, sector abi.SectorNumber) { +func assertDisputeFails(ctx context.Context, t *testing.T, miner *kit.TestUnmanagedMiner, sector abi.SectorNumber) { err := miner.SubmitPostDispute(ctx, sector) require.Error(t, err) require.Contains(t, err.Error(), "failed to dispute valid post") require.Contains(t, err.Error(), "(RetCode=16)") -} \ No newline at end of file +} From fd2a6cad1991e96a1fc8ac703ce9dd293b229662 Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Wed, 29 May 2024 13:14:21 +0530 Subject: [PATCH 12/20] add miner C --- itests/manual_onboarding_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/itests/manual_onboarding_test.go b/itests/manual_onboarding_test.go index bb3b5d0d034..a3dbee6a2f0 100644 --- a/itests/manual_onboarding_test.go +++ b/itests/manual_onboarding_test.go @@ -46,6 +46,8 @@ func TestManualCCOnboarding(t *testing.T) { // for the processes of sector onboarding and activation. nodeOpts = append(nodeOpts, kit.OwnerAddr(client.DefaultKey)) minerB, ens := ens.UnmanagedMiner(&client, nodeOpts...) + minerC, ens := ens.UnmanagedMiner(&client, nodeOpts...) + ens.Start() build.Clock.Sleep(time.Second) @@ -62,14 +64,15 @@ func TestManualCCOnboarding(t *testing.T) { // Miner B should have no power as it has yet to onboard and activate any sectors minerB.AssertNoPower(ctx) + // Miner C should have no power as it has yet to onboard and activate any sectors + minerC.AssertNoPower(ctx) + var bSectorNum abi.SectorNumber var respCh chan kit.WindowPostResp - bSectorNum, respCh = minerB.OnboardCCSectorWithRealProofs(ctx, kit.TestSpt) // Miner B should still not have power as power can only be gained after sector is activated i.e. the first WindowPost is submitted for it minerB.AssertNoPower(ctx) - // wait till sector is activated select { case resp := <-respCh: From c9d5f0aeedd2a28a7437ddfa74b358965be035fd Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Wed, 29 May 2024 16:07:35 +0530 Subject: [PATCH 13/20] fix raciness in WdPosts --- itests/kit/node_unmanaged.go | 1 - itests/manual_onboarding_test.go | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/itests/kit/node_unmanaged.go b/itests/kit/node_unmanaged.go index 05ff2180d28..2cb94b43b78 100644 --- a/itests/kit/node_unmanaged.go +++ b/itests/kit/node_unmanaged.go @@ -228,7 +228,6 @@ func (tm *TestUnmanagedMiner) OnboardCCSectorWithRealProofs(ctx context.Context, return } - nextPost += 5 // Buffer to ensure readiness for submission tm.FullNode.WaitTillChain(ctx, HeightAtLeast(nextPost)) err = tm.submitWindowPost(ctx, sectorNumber) diff --git a/itests/manual_onboarding_test.go b/itests/manual_onboarding_test.go index a3dbee6a2f0..b75a56a59ba 100644 --- a/itests/manual_onboarding_test.go +++ b/itests/manual_onboarding_test.go @@ -24,7 +24,8 @@ func TestManualCCOnboarding(t *testing.T) { defer cancel() var ( - blocktime = 2 * time.Millisecond + // need to pick a balance value so that the test is not racy on CI by running through it's WindowPostDeadlines too fast + blocktime = 5 * time.Millisecond client kit.TestFullNode minerA kit.TestMiner // A is a standard genesis miner ) From 9af13713d5474a8f6df61dc911951b328a70d1da Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Wed, 29 May 2024 17:49:59 +0530 Subject: [PATCH 14/20] sector size --- itests/kit/node_unmanaged.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/itests/kit/node_unmanaged.go b/itests/kit/node_unmanaged.go index 2cb94b43b78..16845ac05e6 100644 --- a/itests/kit/node_unmanaged.go +++ b/itests/kit/node_unmanaged.go @@ -30,6 +30,8 @@ import ( "github.com/filecoin-project/lotus/chain/wallet/key" ) +const sectorSize = abi.SectorSize(2 << 10) // 2KiB + // TestUnmanagedMiner is a miner that's not managed by the storage/infrastructure, all tasks must be manually executed, managed and scheduled by the test or test kit. // Note: `TestUnmanagedMiner` is not thread safe and assumes linear access of it's methods type TestUnmanagedMiner struct { @@ -39,7 +41,6 @@ type TestUnmanagedMiner struct { cacheDir string unsealedSectorDir string sealedSectorDir string - sectorSize abi.SectorSize currentSectorNum abi.SectorNumber cacheDirPaths map[abi.SectorNumber]string @@ -151,13 +152,13 @@ func (tm *TestUnmanagedMiner) createCCSector(_ context.Context, sectorNumber abi unsealedSectorPath := filepath.Join(tm.unsealedSectorDir, fmt.Sprintf("%d", sectorNumber)) sealedSectorPath := filepath.Join(tm.sealedSectorDir, fmt.Sprintf("%d", sectorNumber)) - unsealedSize := abi.PaddedPieceSize(tm.sectorSize).Unpadded() + unsealedSize := abi.PaddedPieceSize(sectorSize).Unpadded() err = os.WriteFile(unsealedSectorPath, make([]byte, unsealedSize), 0644) req.NoError(err) tm.t.Logf("MinerB: Sector %d: wrote unsealed CC sector to %s", sectorNumber, unsealedSectorPath) - err = os.WriteFile(sealedSectorPath, make([]byte, tm.sectorSize), 0644) + err = os.WriteFile(sealedSectorPath, make([]byte, sectorSize), 0644) req.NoError(err) tm.t.Logf("MinerB: Sector %d: wrote sealed CC sector to %s", sectorNumber, sealedSectorPath) From 963095c17dd4530573fb6de47d26bfd7225a51d9 Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Wed, 29 May 2024 23:25:14 +0530 Subject: [PATCH 15/20] finished data onboarding --- itests/kit/node_unmanaged.go | 200 ++++++++++++++++++++++++++++--- itests/manual_onboarding_test.go | 36 ++++-- 2 files changed, 210 insertions(+), 26 deletions(-) diff --git a/itests/kit/node_unmanaged.go b/itests/kit/node_unmanaged.go index 16845ac05e6..f0b4cbd0803 100644 --- a/itests/kit/node_unmanaged.go +++ b/itests/kit/node_unmanaged.go @@ -5,6 +5,7 @@ import ( "context" "crypto/rand" "fmt" + "io" "os" "path/filepath" "testing" @@ -22,6 +23,7 @@ import ( miner14 "github.com/filecoin-project/go-state-types/builtin/v14/miner" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/proof" + prooftypes "github.com/filecoin-project/go-state-types/proof" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors" @@ -142,31 +144,167 @@ func (tm *TestUnmanagedMiner) AssertPower(ctx context.Context, raw uint64, qa ui req.Equal(qa, p.MinerPower.QualityAdjPower.Uint64()) } -func (tm *TestUnmanagedMiner) createCCSector(_ context.Context, sectorNumber abi.SectorNumber) { - req := require.New(tm.t) +func (tm *TestUnmanagedMiner) mkAndSavePiecesToOnboard(_ context.Context, sectorNumber abi.SectorNumber, pt abi.RegisteredSealProof) []abi.PieceInfo { + paddedPieceSize := abi.PaddedPieceSize(sectorSize) + unpaddedPieceSize := paddedPieceSize.Unpadded() + + // Generate random bytes for the piece + randomBytes := make([]byte, unpaddedPieceSize) + _, err := io.ReadFull(rand.Reader, randomBytes) + require.NoError(tm.t, err) + + // Create a temporary file for the first piece + pieceFileA := requireTempFile(tm.t, bytes.NewReader(randomBytes), uint64(unpaddedPieceSize)) + + // Generate the piece CID from the file + pieceCIDA, err := ffi.GeneratePieceCIDFromFile(pt, pieceFileA, unpaddedPieceSize) + require.NoError(tm.t, err) + // Reset file offset to the beginning after CID generation + _, err = pieceFileA.Seek(0, io.SeekStart) + require.NoError(tm.t, err) + + unsealedSectorFile := requireTempFile(tm.t, bytes.NewReader([]byte{}), 0) + defer func() { + _ = unsealedSectorFile.Close() + }() + + // Write the piece to the staged sector file without alignment + writtenBytes, pieceCID, err := ffi.WriteWithoutAlignment(pt, pieceFileA, unpaddedPieceSize, unsealedSectorFile) + require.NoError(tm.t, err) + require.EqualValues(tm.t, unpaddedPieceSize, writtenBytes) + require.True(tm.t, pieceCID.Equals(pieceCIDA)) + + // Create a struct for the piece info + publicPieces := []abi.PieceInfo{{ + Size: paddedPieceSize, + PieceCID: pieceCIDA, + }} + + // Create a temporary file for the sealed sector + sealedSectorFile := requireTempFile(tm.t, bytes.NewReader([]byte{}), 0) + defer func() { + _ = sealedSectorFile.Close() + }() + + // Update paths for the sector + tm.sealedSectorPaths[sectorNumber] = sealedSectorFile.Name() + tm.unsealedSectorPaths[sectorNumber] = unsealedSectorFile.Name() + tm.cacheDirPaths[sectorNumber] = filepath.Join(tm.cacheDir, fmt.Sprintf("%d", sectorNumber)) + + // Ensure the cache directory exists + _ = os.Mkdir(tm.cacheDirPaths[sectorNumber], 0755) + + return publicPieces +} + +func (tm *TestUnmanagedMiner) makeAndSaveCCSector(_ context.Context, sectorNumber abi.SectorNumber) { + requirements := require.New(tm.t) + + // Create cache directory cacheDirPath := filepath.Join(tm.cacheDir, fmt.Sprintf("%d", sectorNumber)) - err := os.Mkdir(cacheDirPath, 0755) - req.NoError(err) + requirements.NoError(os.Mkdir(cacheDirPath, 0755)) tm.t.Logf("MinerB: Sector %d: created cache directory at %s", sectorNumber, cacheDirPath) + // Define paths for unsealed and sealed sectors unsealedSectorPath := filepath.Join(tm.unsealedSectorDir, fmt.Sprintf("%d", sectorNumber)) sealedSectorPath := filepath.Join(tm.sealedSectorDir, fmt.Sprintf("%d", sectorNumber)) unsealedSize := abi.PaddedPieceSize(sectorSize).Unpadded() - err = os.WriteFile(unsealedSectorPath, make([]byte, unsealedSize), 0644) - req.NoError(err) + // Write unsealed sector file + requirements.NoError(os.WriteFile(unsealedSectorPath, make([]byte, unsealedSize), 0644)) tm.t.Logf("MinerB: Sector %d: wrote unsealed CC sector to %s", sectorNumber, unsealedSectorPath) - err = os.WriteFile(sealedSectorPath, make([]byte, sectorSize), 0644) - req.NoError(err) + // Write sealed sector file + requirements.NoError(os.WriteFile(sealedSectorPath, make([]byte, sectorSize), 0644)) tm.t.Logf("MinerB: Sector %d: wrote sealed CC sector to %s", sectorNumber, sealedSectorPath) + // Update paths in the struct tm.unsealedSectorPaths[sectorNumber] = unsealedSectorPath tm.sealedSectorPaths[sectorNumber] = sealedSectorPath tm.cacheDirPaths[sectorNumber] = cacheDirPath +} - return +func (tm *TestUnmanagedMiner) OnboardSectorWithPiecesAndRealProofs(ctx context.Context, proofType abi.RegisteredSealProof) (abi.SectorNumber, chan WindowPostResp) { + req := require.New(tm.t) + sectorNumber := tm.currentSectorNum + tm.currentSectorNum++ + + // --------------------Create pre-commit for the CC sector -> we'll just pre-commit `sector size` worth of 0s for this CC sector + + // Step 1: Wait for the pre-commitseal randomness to be available (we can only draw seal randomness from tipsets that have already achieved finality) + preCommitSealRand := tm.waitPreCommitSealRandomness(ctx, sectorNumber) + + // Step 2: Write empty 32 bytes that we want to seal i.e. create our CC sector + pieces := tm.mkAndSavePiecesToOnboard(ctx, sectorNumber, proofType) + + // Step 3: Generate a Pre-Commit for the CC sector -> this persists the proof on the `TestUnmanagedMiner` Miner State + tm.generatePreCommit(ctx, sectorNumber, preCommitSealRand, proofType, pieces) + + // Step 4 : Submit the Pre-Commit to the network + unsealedCid := tm.unsealedCids[sectorNumber] + r := tm.submitMessage(ctx, &miner14.PreCommitSectorBatchParams2{ + Sectors: []miner14.SectorPreCommitInfo{{ + Expiration: 2880 * 300, + SectorNumber: sectorNumber, + SealProof: TestSpt, + SealedCID: tm.sealedCids[sectorNumber], + SealRandEpoch: preCommitSealRand, + UnsealedCid: &unsealedCid, + }}, + }, 1, builtin.MethodsMiner.PreCommitSectorBatch2) + req.True(r.Receipt.ExitCode.IsSuccess()) + + // Step 5: Generate a ProveCommit for the CC sector + waitSeedRandomness := tm.proveCommitWaitSeed(ctx, sectorNumber) + + proveCommit := tm.generateProveCommit(ctx, sectorNumber, proofType, waitSeedRandomness, pieces) + + minerId, err := address.IDFromAddress(tm.ActorAddr) + require.NoError(tm.t, err) + + // verify the 'ole proofy + isValid, err := ffi.VerifySeal(prooftypes.SealVerifyInfo{ + SectorID: abi.SectorID{ + Miner: abi.ActorID(minerId), + Number: sectorNumber, + }, + SealedCID: tm.sealedCids[sectorNumber], + SealProof: proofType, + Proof: proveCommit, + DealIDs: []abi.DealID{}, + Randomness: tm.sealTickets[sectorNumber], + InteractiveRandomness: waitSeedRandomness, + UnsealedCID: tm.unsealedCids[sectorNumber], + }) + req.NoError(err) + req.True(isValid, "proof wasn't valid") + + // Step 6: Submit the ProveCommit to the network + tm.t.Log("Submitting ProveCommitSector ...") + + var manifest []miner14.PieceActivationManifest + for _, piece := range pieces { + manifest = append(manifest, miner14.PieceActivationManifest{ + CID: piece.PieceCID, + Size: piece.Size, + }) + } + + r = tm.submitMessage(ctx, &miner14.ProveCommitSectors3Params{ + SectorActivations: []miner14.SectorActivationManifest{{SectorNumber: sectorNumber, Pieces: manifest}}, + SectorProofs: [][]byte{proveCommit}, + RequireActivationSuccess: true, + }, 1, builtin.MethodsMiner.ProveCommitSectors3) + req.True(r.Receipt.ExitCode.IsSuccess()) + + tm.proofType[sectorNumber] = proofType + + respCh := make(chan WindowPostResp, 1) + + go tm.wdPostLoop(ctx, sectorNumber, respCh) + + return sectorNumber, respCh } func (tm *TestUnmanagedMiner) OnboardCCSectorWithRealProofs(ctx context.Context, proofType abi.RegisteredSealProof) (abi.SectorNumber, chan WindowPostResp) { @@ -179,11 +317,11 @@ func (tm *TestUnmanagedMiner) OnboardCCSectorWithRealProofs(ctx context.Context, // Step 1: Wait for the pre-commitseal randomness to be available (we can only draw seal randomness from tipsets that have already achieved finality) preCommitSealRand := tm.waitPreCommitSealRandomness(ctx, sectorNumber) - // Step 2: Write empty 32 bytes that we want to seal i.e. create our CC sector - tm.createCCSector(ctx, sectorNumber) + // Step 2: Write empty bytes that we want to seal i.e. create our CC sector + tm.makeAndSaveCCSector(ctx, sectorNumber) // Step 3: Generate a Pre-Commit for the CC sector -> this persists the proof on the `TestUnmanagedMiner` Miner State - tm.generatePreCommit(ctx, sectorNumber, preCommitSealRand, proofType) + tm.generatePreCommit(ctx, sectorNumber, preCommitSealRand, proofType, []abi.PieceInfo{}) // Step 4 : Submit the Pre-Commit to the network r := tm.submitMessage(ctx, &miner14.PreCommitSectorBatchParams2{ @@ -200,7 +338,7 @@ func (tm *TestUnmanagedMiner) OnboardCCSectorWithRealProofs(ctx context.Context, // Step 5: Generate a ProveCommit for the CC sector waitSeedRandomness := tm.proveCommitWaitSeed(ctx, sectorNumber) - proveCommit := tm.generateProveCommit(ctx, sectorNumber, proofType, waitSeedRandomness) + proveCommit := tm.generateProveCommit(ctx, sectorNumber, proofType, waitSeedRandomness, []abi.PieceInfo{}) // Step 6: Submit the ProveCommit to the network tm.t.Log("Submitting ProveCommitSector ...") @@ -216,6 +354,12 @@ func (tm *TestUnmanagedMiner) OnboardCCSectorWithRealProofs(ctx context.Context, respCh := make(chan WindowPostResp, 1) + go tm.wdPostLoop(ctx, sectorNumber, respCh) + + return sectorNumber, respCh +} + +func (tm *TestUnmanagedMiner) wdPostLoop(ctx context.Context, sectorNumber abi.SectorNumber, respCh chan WindowPostResp) { go func() { currentEpoch, nextPost, err := tm.calculateNextPostEpoch(ctx, sectorNumber) tm.t.Logf("Activating sector %d, next post %d, current epoch %d", sectorNumber, nextPost, currentEpoch) @@ -239,8 +383,6 @@ func (tm *TestUnmanagedMiner) OnboardCCSectorWithRealProofs(ctx context.Context, default: } }() - - return sectorNumber, respCh } func (tm *TestUnmanagedMiner) SubmitPostDispute(ctx context.Context, sectorNumber abi.SectorNumber) error { @@ -498,6 +640,7 @@ func (tm *TestUnmanagedMiner) generatePreCommit( sectorNumber abi.SectorNumber, sealRandEpoch abi.ChainEpoch, proofType abi.RegisteredSealProof, + pieceInfo []abi.PieceInfo, ) { req := require.New(tm.t) tm.t.Logf("Miner %s: Generating proof type %d PreCommit for sector %d...", tm.ActorAddr, proofType, sectorNumber) @@ -526,7 +669,7 @@ func (tm *TestUnmanagedMiner) generatePreCommit( sectorNumber, actorId, sealTickets, - []abi.PieceInfo{}, + pieceInfo, ) req.NoError(err, "Miner %s: SealPreCommitPhase1 failed for sector %d", tm.ActorAddr, sectorNumber) req.NotNil(pc1, "Miner %s: SealPreCommitPhase1 returned nil for sector %d", tm.ActorAddr, sectorNumber) @@ -581,6 +724,7 @@ func (tm *TestUnmanagedMiner) generateProveCommit( sectorNumber abi.SectorNumber, proofType abi.RegisteredSealProof, seedRandomness abi.InteractiveSealRandomness, + pieces []abi.PieceInfo, ) []byte { tm.t.Logf("Miner %s: Generating proof type %d Sector Proof for sector %d...", tm.ActorAddr, proofType, sectorNumber) req := require.New(tm.t) @@ -601,7 +745,7 @@ func (tm *TestUnmanagedMiner) generateProveCommit( actorId, tm.sealTickets[sectorNumber], seedRandomness, - []abi.PieceInfo{}, + pieces, ) req.NoError(err) @@ -643,3 +787,25 @@ func (tm *TestUnmanagedMiner) submitMessage( return msg } + +func requireTempFile(t *testing.T, fileContentsReader io.Reader, size uint64) *os.File { + // Create a temporary file + tempFile, err := os.CreateTemp("", "") + require.NoError(t, err) + + // Copy contents from the reader to the temporary file + bytesCopied, err := io.Copy(tempFile, fileContentsReader) + require.NoError(t, err) + + // Ensure the expected size matches the copied size + require.EqualValues(t, size, bytesCopied) + + // Synchronize the file's content to disk + require.NoError(t, tempFile.Sync()) + + // Reset the file pointer to the beginning of the file + _, err = tempFile.Seek(0, io.SeekStart) + require.NoError(t, err) + + return tempFile +} diff --git a/itests/manual_onboarding_test.go b/itests/manual_onboarding_test.go index b75a56a59ba..a3b5f9d8339 100644 --- a/itests/manual_onboarding_test.go +++ b/itests/manual_onboarding_test.go @@ -28,6 +28,7 @@ func TestManualCCOnboarding(t *testing.T) { blocktime = 5 * time.Millisecond client kit.TestFullNode minerA kit.TestMiner // A is a standard genesis miner + minerA2 kit.TestMiner ) // Setup and begin mining with a single miner (A) @@ -37,6 +38,8 @@ func TestManualCCOnboarding(t *testing.T) { ens := kit.NewEnsemble(t, kitOpts...). FullNode(&client, nodeOpts...). Miner(&minerA, &client, nodeOpts...). + // TODO: Figure out we need two genesis miners if we want to use two unmanaged miners in the test + Miner(&minerA2, &client, nodeOpts...). Start(). InterconnectAll() ens.BeginMining(blocktime) @@ -68,38 +71,53 @@ func TestManualCCOnboarding(t *testing.T) { // Miner C should have no power as it has yet to onboard and activate any sectors minerC.AssertNoPower(ctx) + // ---- Miner B onboards a CC sector var bSectorNum abi.SectorNumber var respCh chan kit.WindowPostResp - bSectorNum, respCh = minerB.OnboardCCSectorWithRealProofs(ctx, kit.TestSpt) - + bSectorNum, respCh = minerB.OnboardSectorWithPiecesAndRealProofs(ctx, kit.TestSpt) // Miner B should still not have power as power can only be gained after sector is activated i.e. the first WindowPost is submitted for it minerB.AssertNoPower(ctx) + // Activate CC Sector for Miner B and assert power + activateAndAssertPower(ctx, t, minerB, respCh, bSectorNum) + + // --- Miner C onboards sector with data/pieces + var cSectorNum abi.SectorNumber + var cRespCh chan kit.WindowPostResp + cSectorNum, cRespCh = minerC.OnboardCCSectorWithRealProofs(ctx, kit.TestSpt) + // Miner C should still not have power as power can only be gained after sector is activated i.e. the first WindowPost is submitted for it + minerC.AssertNoPower(ctx) + // Activate CC Sector for Miner C and assert power + activateAndAssertPower(ctx, t, minerC, cRespCh, cSectorNum) +} + +func activateAndAssertPower(ctx context.Context, t *testing.T, miner *kit.TestUnmanagedMiner, respCh chan kit.WindowPostResp, sector abi.SectorNumber) { + req := require.New(t) // wait till sector is activated select { case resp := <-respCh: req.NoError(resp.Error) - req.Equal(resp.SectorNumber, bSectorNum) + req.Equal(resp.SectorNumber, sector) case <-ctx.Done(): t.Fatal("timed out waiting for sector activation") } // Fetch on-chain sector properties - head, err = client.ChainHead(ctx) + head, err := miner.FullNode.ChainHead(ctx) req.NoError(err) - soi, err := client.StateSectorGetInfo(ctx, minerB.ActorAddr, bSectorNum, head.Key()) + soi, err := miner.FullNode.StateSectorGetInfo(ctx, miner.ActorAddr, sector, head.Key()) req.NoError(err) - t.Logf("Miner B SectorOnChainInfo %d: %+v", bSectorNum, soi) + t.Logf("Miner %s SectorOnChainInfo %d: %+v", miner.ActorAddr.String(), sector, soi) - _ = client.WaitTillChain(ctx, kit.HeightAtLeast(head.Height()+5)) + _ = miner.FullNode.WaitTillChain(ctx, kit.HeightAtLeast(head.Height()+5)) t.Log("Checking power after PoSt ...") // Miner B should now have power - minerB.AssertPower(ctx, (uint64(2 << 10)), (uint64(2 << 10))) + miner.AssertPower(ctx, (uint64(sectorSize)), (uint64(sectorSize))) // WindowPost Dispute should fail - assertDisputeFails(ctx, t, minerB, bSectorNum) + assertDisputeFails(ctx, t, miner, sector) } func assertDisputeFails(ctx context.Context, t *testing.T, miner *kit.TestUnmanagedMiner, sector abi.SectorNumber) { From 59899963c1be1ce05665276251bf968823e0211c Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Wed, 29 May 2024 23:27:25 +0530 Subject: [PATCH 16/20] changes to proof verification --- itests/kit/node_unmanaged.go | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/itests/kit/node_unmanaged.go b/itests/kit/node_unmanaged.go index f0b4cbd0803..40877d732c4 100644 --- a/itests/kit/node_unmanaged.go +++ b/itests/kit/node_unmanaged.go @@ -23,8 +23,6 @@ import ( miner14 "github.com/filecoin-project/go-state-types/builtin/v14/miner" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/proof" - prooftypes "github.com/filecoin-project/go-state-types/proof" - "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/policy" @@ -260,26 +258,6 @@ func (tm *TestUnmanagedMiner) OnboardSectorWithPiecesAndRealProofs(ctx context.C proveCommit := tm.generateProveCommit(ctx, sectorNumber, proofType, waitSeedRandomness, pieces) - minerId, err := address.IDFromAddress(tm.ActorAddr) - require.NoError(tm.t, err) - - // verify the 'ole proofy - isValid, err := ffi.VerifySeal(prooftypes.SealVerifyInfo{ - SectorID: abi.SectorID{ - Miner: abi.ActorID(minerId), - Number: sectorNumber, - }, - SealedCID: tm.sealedCids[sectorNumber], - SealProof: proofType, - Proof: proveCommit, - DealIDs: []abi.DealID{}, - Randomness: tm.sealTickets[sectorNumber], - InteractiveRandomness: waitSeedRandomness, - UnsealedCID: tm.unsealedCids[sectorNumber], - }) - req.NoError(err) - req.True(isValid, "proof wasn't valid") - // Step 6: Submit the ProveCommit to the network tm.t.Log("Submitting ProveCommitSector ...") From 3689aab45970962dac5376fb2b7100fcb45735ec Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Thu, 30 May 2024 16:05:15 +0530 Subject: [PATCH 17/20] address reviee #1 --- itests/kit/node_full.go | 25 +++++++++ itests/kit/node_unmanaged.go | 98 ++++++++++++++++++------------------ 2 files changed, 75 insertions(+), 48 deletions(-) diff --git a/itests/kit/node_full.go b/itests/kit/node_full.go index abcd8aea43f..819b510cce7 100644 --- a/itests/kit/node_full.go +++ b/itests/kit/node_full.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "golang.org/x/xerrors" "testing" "time" @@ -109,6 +110,30 @@ func (f *TestFullNode) WaitTillChain(ctx context.Context, pred ChainPredicate) * return nil } +// WaitTillChain waits until a specified chain condition is met. It returns +// the first tipset where the condition is met. +func (f *TestFullNode) WaitTillChainOrError(ctx context.Context, pred ChainPredicate) (*types.TipSet, error) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + heads, err := f.ChainNotify(ctx) + if err != nil { + return nil, err + } + + for chg := range heads { + for _, c := range chg { + if c.Type != "apply" { + continue + } + if ts := c.Val; pred(ts) { + return ts, nil + } + } + } + return nil, xerrors.New("chain condition not met") +} + func (f *TestFullNode) WaitForSectorActive(ctx context.Context, t *testing.T, sn abi.SectorNumber, maddr address.Address) { for { active, err := f.StateMinerActiveSectors(ctx, maddr, types.EmptyTSK) diff --git a/itests/kit/node_unmanaged.go b/itests/kit/node_unmanaged.go index 40877d732c4..09ffe090ed9 100644 --- a/itests/kit/node_unmanaged.go +++ b/itests/kit/node_unmanaged.go @@ -120,7 +120,7 @@ func NewTestUnmanagedMiner(t *testing.T, full *TestFullNode, actorAddr address.A func (tm *TestUnmanagedMiner) AssertNoPower(ctx context.Context) { p := tm.CurrentPower(ctx) - tm.t.Logf("MinerB RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) + tm.t.Logf("Miner %s RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), tm.ActorAddr, p.MinerPower.RawBytePower.String()) require.True(tm.t, p.MinerPower.RawBytePower.IsZero()) } @@ -137,7 +137,7 @@ func (tm *TestUnmanagedMiner) CurrentPower(ctx context.Context) *api.MinerPower func (tm *TestUnmanagedMiner) AssertPower(ctx context.Context, raw uint64, qa uint64) { req := require.New(tm.t) p := tm.CurrentPower(ctx) - tm.t.Logf("MinerB RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) + tm.t.Logf("Miner %s RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), tm.ActorAddr, p.MinerPower.RawBytePower.String()) req.Equal(raw, p.MinerPower.RawBytePower.Uint64()) req.Equal(qa, p.MinerPower.QualityAdjPower.Uint64()) } @@ -202,7 +202,7 @@ func (tm *TestUnmanagedMiner) makeAndSaveCCSector(_ context.Context, sectorNumbe // Create cache directory cacheDirPath := filepath.Join(tm.cacheDir, fmt.Sprintf("%d", sectorNumber)) requirements.NoError(os.Mkdir(cacheDirPath, 0755)) - tm.t.Logf("MinerB: Sector %d: created cache directory at %s", sectorNumber, cacheDirPath) + tm.t.Logf("Miner %s: Sector %d: created cache directory at %s", tm.ActorAddr, sectorNumber, cacheDirPath) // Define paths for unsealed and sealed sectors unsealedSectorPath := filepath.Join(tm.unsealedSectorDir, fmt.Sprintf("%d", sectorNumber)) @@ -211,11 +211,11 @@ func (tm *TestUnmanagedMiner) makeAndSaveCCSector(_ context.Context, sectorNumbe // Write unsealed sector file requirements.NoError(os.WriteFile(unsealedSectorPath, make([]byte, unsealedSize), 0644)) - tm.t.Logf("MinerB: Sector %d: wrote unsealed CC sector to %s", sectorNumber, unsealedSectorPath) + tm.t.Logf("Miner %s: Sector %d: wrote unsealed CC sector to %s", tm.ActorAddr, sectorNumber, unsealedSectorPath) // Write sealed sector file requirements.NoError(os.WriteFile(sealedSectorPath, make([]byte, sectorSize), 0644)) - tm.t.Logf("MinerB: Sector %d: wrote sealed CC sector to %s", sectorNumber, sealedSectorPath) + tm.t.Logf("Miner %s: Sector %d: wrote sealed CC sector to %s", tm.ActorAddr, sectorNumber, sealedSectorPath) // Update paths in the struct tm.unsealedSectorPaths[sectorNumber] = unsealedSectorPath @@ -228,12 +228,10 @@ func (tm *TestUnmanagedMiner) OnboardSectorWithPiecesAndRealProofs(ctx context.C sectorNumber := tm.currentSectorNum tm.currentSectorNum++ - // --------------------Create pre-commit for the CC sector -> we'll just pre-commit `sector size` worth of 0s for this CC sector - // Step 1: Wait for the pre-commitseal randomness to be available (we can only draw seal randomness from tipsets that have already achieved finality) preCommitSealRand := tm.waitPreCommitSealRandomness(ctx, sectorNumber) - // Step 2: Write empty 32 bytes that we want to seal i.e. create our CC sector + // Step 2: Build a sector with non 0 Pieces that we want to onboard pieces := tm.mkAndSavePiecesToOnboard(ctx, sectorNumber, proofType) // Step 3: Generate a Pre-Commit for the CC sector -> this persists the proof on the `TestUnmanagedMiner` Miner State @@ -241,7 +239,7 @@ func (tm *TestUnmanagedMiner) OnboardSectorWithPiecesAndRealProofs(ctx context.C // Step 4 : Submit the Pre-Commit to the network unsealedCid := tm.unsealedCids[sectorNumber] - r := tm.submitMessage(ctx, &miner14.PreCommitSectorBatchParams2{ + r, err := tm.submitMessage(ctx, &miner14.PreCommitSectorBatchParams2{ Sectors: []miner14.SectorPreCommitInfo{{ Expiration: 2880 * 300, SectorNumber: sectorNumber, @@ -251,6 +249,7 @@ func (tm *TestUnmanagedMiner) OnboardSectorWithPiecesAndRealProofs(ctx context.C UnsealedCid: &unsealedCid, }}, }, 1, builtin.MethodsMiner.PreCommitSectorBatch2) + req.NoError(err) req.True(r.Receipt.ExitCode.IsSuccess()) // Step 5: Generate a ProveCommit for the CC sector @@ -269,11 +268,12 @@ func (tm *TestUnmanagedMiner) OnboardSectorWithPiecesAndRealProofs(ctx context.C }) } - r = tm.submitMessage(ctx, &miner14.ProveCommitSectors3Params{ + r, err = tm.submitMessage(ctx, &miner14.ProveCommitSectors3Params{ SectorActivations: []miner14.SectorActivationManifest{{SectorNumber: sectorNumber, Pieces: manifest}}, SectorProofs: [][]byte{proveCommit}, RequireActivationSuccess: true, }, 1, builtin.MethodsMiner.ProveCommitSectors3) + req.NoError(err) req.True(r.Receipt.ExitCode.IsSuccess()) tm.proofType[sectorNumber] = proofType @@ -302,7 +302,7 @@ func (tm *TestUnmanagedMiner) OnboardCCSectorWithRealProofs(ctx context.Context, tm.generatePreCommit(ctx, sectorNumber, preCommitSealRand, proofType, []abi.PieceInfo{}) // Step 4 : Submit the Pre-Commit to the network - r := tm.submitMessage(ctx, &miner14.PreCommitSectorBatchParams2{ + r, err := tm.submitMessage(ctx, &miner14.PreCommitSectorBatchParams2{ Sectors: []miner14.SectorPreCommitInfo{{ Expiration: 2880 * 300, SectorNumber: sectorNumber, @@ -311,6 +311,7 @@ func (tm *TestUnmanagedMiner) OnboardCCSectorWithRealProofs(ctx context.Context, SealRandEpoch: preCommitSealRand, }}, }, 1, builtin.MethodsMiner.PreCommitSectorBatch2) + req.NoError(err) req.True(r.Receipt.ExitCode.IsSuccess()) // Step 5: Generate a ProveCommit for the CC sector @@ -321,11 +322,12 @@ func (tm *TestUnmanagedMiner) OnboardCCSectorWithRealProofs(ctx context.Context, // Step 6: Submit the ProveCommit to the network tm.t.Log("Submitting ProveCommitSector ...") - r = tm.submitMessage(ctx, &miner14.ProveCommitSectors3Params{ + r, err = tm.submitMessage(ctx, &miner14.ProveCommitSectors3Params{ SectorActivations: []miner14.SectorActivationManifest{{SectorNumber: sectorNumber}}, SectorProofs: [][]byte{proveCommit}, RequireActivationSuccess: true, }, 0, builtin.MethodsMiner.ProveCommitSectors3) + req.NoError(err) req.True(r.Receipt.ExitCode.IsSuccess()) tm.proofType[sectorNumber] = proofType @@ -339,32 +341,36 @@ func (tm *TestUnmanagedMiner) OnboardCCSectorWithRealProofs(ctx context.Context, func (tm *TestUnmanagedMiner) wdPostLoop(ctx context.Context, sectorNumber abi.SectorNumber, respCh chan WindowPostResp) { go func() { - currentEpoch, nextPost, err := tm.calculateNextPostEpoch(ctx, sectorNumber) - tm.t.Logf("Activating sector %d, next post %d, current epoch %d", sectorNumber, nextPost, currentEpoch) - if err != nil { + writeRespF := func(respErr error) { select { - case respCh <- WindowPostResp{SectorNumber: sectorNumber, Error: err}: + case respCh <- WindowPostResp{SectorNumber: sectorNumber, Error: respErr}: case <-ctx.Done(): - return default: } + } + + currentEpoch, nextPost, err := tm.calculateNextPostEpoch(ctx, sectorNumber) + tm.t.Logf("Activating sector %d, next post %d, current epoch %d", sectorNumber, nextPost, currentEpoch) + if err != nil { + writeRespF(err) return } - tm.FullNode.WaitTillChain(ctx, HeightAtLeast(nextPost)) + if _, err := tm.FullNode.WaitTillChainOrError(ctx, HeightAtLeast(nextPost)); err != nil { + writeRespF(err) + return + } err = tm.submitWindowPost(ctx, sectorNumber) - select { - case respCh <- WindowPostResp{SectorNumber: sectorNumber, Error: err}: - case <-ctx.Done(): + writeRespF(err) + if ctx.Err() != nil { return - default: } }() } func (tm *TestUnmanagedMiner) SubmitPostDispute(ctx context.Context, sectorNumber abi.SectorNumber) error { - tm.t.Logf("MinerB(%s): Starting dispute submission for sector %d", tm.ActorAddr, sectorNumber) + tm.t.Logf("Miner %s: Starting dispute submission for sector %d", tm.ActorAddr, sectorNumber) head, err := tm.FullNode.ChainHead(ctx) if err != nil { @@ -382,30 +388,16 @@ func (tm *TestUnmanagedMiner) SubmitPostDispute(ctx context.Context, sectorNumbe } disputeEpoch := di.Close + 5 - tm.t.Logf("MinerB(%s): Sector %d - Waiting %d epochs until epoch %d to submit dispute", tm.ActorAddr, sectorNumber, disputeEpoch-head.Height(), disputeEpoch) + tm.t.Logf("Miner %s: Sector %d - Waiting %d epochs until epoch %d to submit dispute", tm.ActorAddr, sectorNumber, disputeEpoch-head.Height(), disputeEpoch) tm.FullNode.WaitTillChain(ctx, HeightAtLeast(disputeEpoch)) - tm.t.Logf("MinerB(%s): Sector %d - Disputing WindowedPoSt to confirm validity at epoch %d", tm.ActorAddr, sectorNumber, disputeEpoch) + tm.t.Logf("Miner %s: Sector %d - Disputing WindowedPoSt to confirm validity at epoch %d", tm.ActorAddr, sectorNumber, disputeEpoch) - params := &miner14.DisputeWindowedPoStParams{ + _, err = tm.submitMessage(ctx, &miner14.DisputeWindowedPoStParams{ Deadline: sp.Deadline, PoStIndex: 0, - } - - enc, aerr := actors.SerializeParams(params) - require.NoError(tm.t, aerr) - - _, err = tm.FullNode.MpoolPushMessage(ctx, &types.Message{ - To: tm.ActorAddr, - From: tm.OwnerKey.Address, - Value: types.FromFil(1), - Method: builtin.MethodsMiner.DisputeWindowedPoSt, - Params: enc, - }, nil) - if err != nil { - tm.t.Logf("MinerB(%s): Failed to push dispute message for sector %d: %s", tm.ActorAddr, sectorNumber, err) - } + }, 1, builtin.MethodsMiner.DisputeWindowedPoSt) return err } @@ -451,13 +443,16 @@ func (tm *TestUnmanagedMiner) submitWindowPost(ctx context.Context, sectorNumber return fmt.Errorf("Miner(%s): failed to get miner info for sector %d: %w", tm.ActorAddr, sectorNumber, err) } - r := tm.submitMessage(ctx, &miner14.SubmitWindowedPoStParams{ + r, err := tm.submitMessage(ctx, &miner14.SubmitWindowedPoStParams{ ChainCommitEpoch: chainRandomnessEpoch, ChainCommitRand: chainRandomness, Deadline: sp.Deadline, Partitions: []miner14.PoStPartition{{Index: sp.Partition}}, Proofs: []proof.PoStProof{{PoStProof: minerInfo.WindowPoStProofType, ProofBytes: proofBytes}}, }, 0, builtin.MethodsMiner.SubmitWindowedPoSt) + if err != nil { + return fmt.Errorf("Miner(%s): failed to submit window post for sector %d: %w", tm.ActorAddr, sectorNumber, err) + } if !r.Receipt.ExitCode.IsSuccess() { return fmt.Errorf("Miner(%s): submitting PoSt for sector %d failed: %s", tm.ActorAddr, sectorNumber, r.Receipt.ExitCode) @@ -742,9 +737,11 @@ func (tm *TestUnmanagedMiner) submitMessage( params cbg.CBORMarshaler, value uint64, method abi.MethodNum, -) *api.MsgLookup { +) (*api.MsgLookup, error) { enc, aerr := actors.SerializeParams(params) - require.NoError(tm.t, aerr) + if aerr != nil { + return nil, aerr + } tm.t.Logf("Submitting message for miner %s with method number %d", tm.ActorAddr, method) @@ -755,20 +752,25 @@ func (tm *TestUnmanagedMiner) submitMessage( Method: method, Params: enc, }, nil) - require.NoError(tm.t, err) + if err != nil { + return nil, err + } tm.t.Logf("Pushed message with CID: %s for miner %s", m.Cid(), tm.ActorAddr) msg, err := tm.FullNode.StateWaitMsg(ctx, m.Cid(), 2, api.LookbackNoLimit, true) - require.NoError(tm.t, err) + if err != nil { + return nil, err + } + tm.t.Logf("Message with CID: %s has been confirmed on-chain for miner %s", m.Cid(), tm.ActorAddr) - return msg + return msg, nil } func requireTempFile(t *testing.T, fileContentsReader io.Reader, size uint64) *os.File { // Create a temporary file - tempFile, err := os.CreateTemp("", "") + tempFile, err := os.CreateTemp(t.TempDir(), "") require.NoError(t, err) // Copy contents from the reader to the temporary file From e89bfe48d710d66e4b54d9a05732fae1ce7b5100 Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Thu, 30 May 2024 16:13:16 +0530 Subject: [PATCH 18/20] use sector size as an option --- itests/kit/node_unmanaged.go | 8 +++----- itests/manual_onboarding_test.go | 13 +++++++------ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/itests/kit/node_unmanaged.go b/itests/kit/node_unmanaged.go index 09ffe090ed9..c9b5dd1a589 100644 --- a/itests/kit/node_unmanaged.go +++ b/itests/kit/node_unmanaged.go @@ -30,8 +30,6 @@ import ( "github.com/filecoin-project/lotus/chain/wallet/key" ) -const sectorSize = abi.SectorSize(2 << 10) // 2KiB - // TestUnmanagedMiner is a miner that's not managed by the storage/infrastructure, all tasks must be manually executed, managed and scheduled by the test or test kit. // Note: `TestUnmanagedMiner` is not thread safe and assumes linear access of it's methods type TestUnmanagedMiner struct { @@ -143,7 +141,7 @@ func (tm *TestUnmanagedMiner) AssertPower(ctx context.Context, raw uint64, qa ui } func (tm *TestUnmanagedMiner) mkAndSavePiecesToOnboard(_ context.Context, sectorNumber abi.SectorNumber, pt abi.RegisteredSealProof) []abi.PieceInfo { - paddedPieceSize := abi.PaddedPieceSize(sectorSize) + paddedPieceSize := abi.PaddedPieceSize(tm.options.sectorSize) unpaddedPieceSize := paddedPieceSize.Unpadded() // Generate random bytes for the piece @@ -207,14 +205,14 @@ func (tm *TestUnmanagedMiner) makeAndSaveCCSector(_ context.Context, sectorNumbe // Define paths for unsealed and sealed sectors unsealedSectorPath := filepath.Join(tm.unsealedSectorDir, fmt.Sprintf("%d", sectorNumber)) sealedSectorPath := filepath.Join(tm.sealedSectorDir, fmt.Sprintf("%d", sectorNumber)) - unsealedSize := abi.PaddedPieceSize(sectorSize).Unpadded() + unsealedSize := abi.PaddedPieceSize(tm.options.sectorSize).Unpadded() // Write unsealed sector file requirements.NoError(os.WriteFile(unsealedSectorPath, make([]byte, unsealedSize), 0644)) tm.t.Logf("Miner %s: Sector %d: wrote unsealed CC sector to %s", tm.ActorAddr, sectorNumber, unsealedSectorPath) // Write sealed sector file - requirements.NoError(os.WriteFile(sealedSectorPath, make([]byte, sectorSize), 0644)) + requirements.NoError(os.WriteFile(sealedSectorPath, make([]byte, tm.options.sectorSize), 0644)) tm.t.Logf("Miner %s: Sector %d: wrote sealed CC sector to %s", tm.ActorAddr, sectorNumber, sealedSectorPath) // Update paths in the struct diff --git a/itests/manual_onboarding_test.go b/itests/manual_onboarding_test.go index a3b5f9d8339..afab97d2034 100644 --- a/itests/manual_onboarding_test.go +++ b/itests/manual_onboarding_test.go @@ -13,7 +13,7 @@ import ( "github.com/filecoin-project/lotus/itests/kit" ) -const sectorSize = abi.SectorSize(2 << 10) // 2KiB +const defaultSectorSize = abi.SectorSize(2 << 10) // 2KiB // Manually onboard CC sectors, bypassing lotus-miner onboarding pathways func TestManualCCOnboarding(t *testing.T) { @@ -34,7 +34,7 @@ func TestManualCCOnboarding(t *testing.T) { // Setup and begin mining with a single miner (A) // Miner A will only be a genesis Miner with power allocated in the genesis block and will not onboard any sectors from here on kitOpts := []kit.EnsembleOpt{} - nodeOpts := []kit.NodeOpt{kit.SectorSize(sectorSize), kit.WithAllSubsystems()} + nodeOpts := []kit.NodeOpt{kit.SectorSize(defaultSectorSize), kit.WithAllSubsystems()} ens := kit.NewEnsemble(t, kitOpts...). FullNode(&client, nodeOpts...). Miner(&minerA, &client, nodeOpts...). @@ -78,7 +78,7 @@ func TestManualCCOnboarding(t *testing.T) { // Miner B should still not have power as power can only be gained after sector is activated i.e. the first WindowPost is submitted for it minerB.AssertNoPower(ctx) // Activate CC Sector for Miner B and assert power - activateAndAssertPower(ctx, t, minerB, respCh, bSectorNum) + activateAndAssertPower(ctx, t, minerB, respCh, bSectorNum, uint64(defaultSectorSize)) // --- Miner C onboards sector with data/pieces var cSectorNum abi.SectorNumber @@ -87,10 +87,11 @@ func TestManualCCOnboarding(t *testing.T) { // Miner C should still not have power as power can only be gained after sector is activated i.e. the first WindowPost is submitted for it minerC.AssertNoPower(ctx) // Activate CC Sector for Miner C and assert power - activateAndAssertPower(ctx, t, minerC, cRespCh, cSectorNum) + activateAndAssertPower(ctx, t, minerC, cRespCh, cSectorNum, uint64(defaultSectorSize)) } -func activateAndAssertPower(ctx context.Context, t *testing.T, miner *kit.TestUnmanagedMiner, respCh chan kit.WindowPostResp, sector abi.SectorNumber) { +func activateAndAssertPower(ctx context.Context, t *testing.T, miner *kit.TestUnmanagedMiner, respCh chan kit.WindowPostResp, sector abi.SectorNumber, + sectorSize uint64) { req := require.New(t) // wait till sector is activated select { @@ -114,7 +115,7 @@ func activateAndAssertPower(ctx context.Context, t *testing.T, miner *kit.TestUn t.Log("Checking power after PoSt ...") // Miner B should now have power - miner.AssertPower(ctx, (uint64(sectorSize)), (uint64(sectorSize))) + miner.AssertPower(ctx, sectorSize, sectorSize) // WindowPost Dispute should fail assertDisputeFails(ctx, t, miner, sector) From 2a9fe8be540f821452f6c27070838e265316a53b Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Thu, 30 May 2024 17:02:57 +0530 Subject: [PATCH 19/20] genesis miner must block for it's own WdPosts --- itests/manual_onboarding_test.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/itests/manual_onboarding_test.go b/itests/manual_onboarding_test.go index afab97d2034..be47d67087d 100644 --- a/itests/manual_onboarding_test.go +++ b/itests/manual_onboarding_test.go @@ -28,7 +28,6 @@ func TestManualCCOnboarding(t *testing.T) { blocktime = 5 * time.Millisecond client kit.TestFullNode minerA kit.TestMiner // A is a standard genesis miner - minerA2 kit.TestMiner ) // Setup and begin mining with a single miner (A) @@ -38,11 +37,9 @@ func TestManualCCOnboarding(t *testing.T) { ens := kit.NewEnsemble(t, kitOpts...). FullNode(&client, nodeOpts...). Miner(&minerA, &client, nodeOpts...). - // TODO: Figure out we need two genesis miners if we want to use two unmanaged miners in the test - Miner(&minerA2, &client, nodeOpts...). Start(). InterconnectAll() - ens.BeginMining(blocktime) + ens.BeginMiningMustPost(blocktime) // Instantiate MinerB to manually handle sector onboarding and power acquisition through sector activation. // Unlike other miners managed by the Lotus Miner storage infrastructure, MinerB operates independently, From 852d0a459edd78043bdfb946838bd91908da093b Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Thu, 30 May 2024 18:22:27 +0530 Subject: [PATCH 20/20] add mock proof tests --- itests/kit/node_full.go | 2 +- itests/kit/node_unmanaged.go | 133 +++++++++++++++++++++++-- itests/manual_onboarding_test.go | 166 ++++++++++++++++++------------- 3 files changed, 223 insertions(+), 78 deletions(-) diff --git a/itests/kit/node_full.go b/itests/kit/node_full.go index 819b510cce7..3b37da05674 100644 --- a/itests/kit/node_full.go +++ b/itests/kit/node_full.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "fmt" - "golang.org/x/xerrors" "testing" "time" @@ -13,6 +12,7 @@ import ( "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/require" cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" diff --git a/itests/kit/node_unmanaged.go b/itests/kit/node_unmanaged.go index c9b5dd1a589..ace9665f061 100644 --- a/itests/kit/node_unmanaged.go +++ b/itests/kit/node_unmanaged.go @@ -23,6 +23,7 @@ import ( miner14 "github.com/filecoin-project/go-state-types/builtin/v14/miner" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/proof" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/policy" @@ -278,7 +279,119 @@ func (tm *TestUnmanagedMiner) OnboardSectorWithPiecesAndRealProofs(ctx context.C respCh := make(chan WindowPostResp, 1) - go tm.wdPostLoop(ctx, sectorNumber, respCh) + go tm.wdPostLoop(ctx, sectorNumber, respCh, false) + + return sectorNumber, respCh +} + +func (tm *TestUnmanagedMiner) OnboardSectorWithPiecesAndMockProofs(ctx context.Context, proofType abi.RegisteredSealProof) (abi.SectorNumber, chan WindowPostResp) { + req := require.New(tm.t) + sectorNumber := tm.currentSectorNum + tm.currentSectorNum++ + + // Step 1: Wait for the pre-commitseal randomness to be available (we can only draw seal randomness from tipsets that have already achieved finality) + preCommitSealRand := tm.waitPreCommitSealRandomness(ctx, sectorNumber) + + // Step 2: Build a sector with non 0 Pieces that we want to onboard + pieces := []abi.PieceInfo{{ + Size: abi.PaddedPieceSize(tm.options.sectorSize), + PieceCID: cid.MustParse("baga6ea4seaqjtovkwk4myyzj56eztkh5pzsk5upksan6f5outesy62bsvl4dsha"), + }} + + // Step 3: Generate a Pre-Commit for the CC sector -> this persists the proof on the `TestUnmanagedMiner` Miner State + tm.sealedCids[sectorNumber] = cid.MustParse("bagboea4b5abcatlxechwbp7kjpjguna6r6q7ejrhe6mdp3lf34pmswn27pkkiekz") + tm.unsealedCids[sectorNumber] = cid.MustParse("baga6ea4seaqjtovkwk4myyzj56eztkh5pzsk5upksan6f5outesy62bsvl4dsha") + + // Step 4 : Submit the Pre-Commit to the network + unsealedCid := tm.unsealedCids[sectorNumber] + r, err := tm.submitMessage(ctx, &miner14.PreCommitSectorBatchParams2{ + Sectors: []miner14.SectorPreCommitInfo{{ + Expiration: 2880 * 300, + SectorNumber: sectorNumber, + SealProof: TestSpt, + SealedCID: tm.sealedCids[sectorNumber], + SealRandEpoch: preCommitSealRand, + UnsealedCid: &unsealedCid, + }}, + }, 1, builtin.MethodsMiner.PreCommitSectorBatch2) + req.NoError(err) + req.True(r.Receipt.ExitCode.IsSuccess()) + + // Step 5: Generate a ProveCommit for the CC sector + _ = tm.proveCommitWaitSeed(ctx, sectorNumber) + sectorProof := []byte{0xde, 0xad, 0xbe, 0xef} + + // Step 6: Submit the ProveCommit to the network + tm.t.Log("Submitting ProveCommitSector ...") + + var manifest []miner14.PieceActivationManifest + for _, piece := range pieces { + manifest = append(manifest, miner14.PieceActivationManifest{ + CID: piece.PieceCID, + Size: piece.Size, + }) + } + + r, err = tm.submitMessage(ctx, &miner14.ProveCommitSectors3Params{ + SectorActivations: []miner14.SectorActivationManifest{{SectorNumber: sectorNumber, Pieces: manifest}}, + SectorProofs: [][]byte{sectorProof}, + RequireActivationSuccess: true, + }, 1, builtin.MethodsMiner.ProveCommitSectors3) + req.NoError(err) + req.True(r.Receipt.ExitCode.IsSuccess()) + + tm.proofType[sectorNumber] = proofType + + respCh := make(chan WindowPostResp, 1) + + go tm.wdPostLoop(ctx, sectorNumber, respCh, true) + + return sectorNumber, respCh +} + +func (tm *TestUnmanagedMiner) OnboardCCSectorWithMockProofs(ctx context.Context, proofType abi.RegisteredSealProof) (abi.SectorNumber, chan WindowPostResp) { + req := require.New(tm.t) + sectorNumber := tm.currentSectorNum + tm.currentSectorNum++ + + // Step 1: Wait for the pre-commitseal randomness to be available (we can only draw seal randomness from tipsets that have already achieved finality) + preCommitSealRand := tm.waitPreCommitSealRandomness(ctx, sectorNumber) + + tm.sealedCids[sectorNumber] = cid.MustParse("bagboea4b5abcatlxechwbp7kjpjguna6r6q7ejrhe6mdp3lf34pmswn27pkkiekz") + + // Step 4 : Submit the Pre-Commit to the network + r, err := tm.submitMessage(ctx, &miner14.PreCommitSectorBatchParams2{ + Sectors: []miner14.SectorPreCommitInfo{{ + Expiration: 2880 * 300, + SectorNumber: sectorNumber, + SealProof: TestSpt, + SealedCID: tm.sealedCids[sectorNumber], + SealRandEpoch: preCommitSealRand, + }}, + }, 1, builtin.MethodsMiner.PreCommitSectorBatch2) + req.NoError(err) + req.True(r.Receipt.ExitCode.IsSuccess()) + + // Step 5: Generate a ProveCommit for the CC sector + _ = tm.proveCommitWaitSeed(ctx, sectorNumber) + sectorProof := []byte{0xde, 0xad, 0xbe, 0xef} + + // Step 6: Submit the ProveCommit to the network + tm.t.Log("Submitting ProveCommitSector ...") + + r, err = tm.submitMessage(ctx, &miner14.ProveCommitSectors3Params{ + SectorActivations: []miner14.SectorActivationManifest{{SectorNumber: sectorNumber}}, + SectorProofs: [][]byte{sectorProof}, + RequireActivationSuccess: true, + }, 0, builtin.MethodsMiner.ProveCommitSectors3) + req.NoError(err) + req.True(r.Receipt.ExitCode.IsSuccess()) + + tm.proofType[sectorNumber] = proofType + + respCh := make(chan WindowPostResp, 1) + + go tm.wdPostLoop(ctx, sectorNumber, respCh, true) return sectorNumber, respCh } @@ -332,12 +445,12 @@ func (tm *TestUnmanagedMiner) OnboardCCSectorWithRealProofs(ctx context.Context, respCh := make(chan WindowPostResp, 1) - go tm.wdPostLoop(ctx, sectorNumber, respCh) + go tm.wdPostLoop(ctx, sectorNumber, respCh, false) return sectorNumber, respCh } -func (tm *TestUnmanagedMiner) wdPostLoop(ctx context.Context, sectorNumber abi.SectorNumber, respCh chan WindowPostResp) { +func (tm *TestUnmanagedMiner) wdPostLoop(ctx context.Context, sectorNumber abi.SectorNumber, respCh chan WindowPostResp, withMockProofs bool) { go func() { writeRespF := func(respErr error) { select { @@ -359,7 +472,7 @@ func (tm *TestUnmanagedMiner) wdPostLoop(ctx context.Context, sectorNumber abi.S return } - err = tm.submitWindowPost(ctx, sectorNumber) + err = tm.submitWindowPost(ctx, sectorNumber, withMockProofs) writeRespF(err) if ctx.Err() != nil { return @@ -399,7 +512,7 @@ func (tm *TestUnmanagedMiner) SubmitPostDispute(ctx context.Context, sectorNumbe return err } -func (tm *TestUnmanagedMiner) submitWindowPost(ctx context.Context, sectorNumber abi.SectorNumber) error { +func (tm *TestUnmanagedMiner) submitWindowPost(ctx context.Context, sectorNumber abi.SectorNumber, withMockProofs bool) error { tm.t.Logf("Miner(%s): WindowPoST(%d): Running WindowPoSt ...\n", tm.ActorAddr, sectorNumber) head, err := tm.FullNode.ChainHead(ctx) @@ -422,9 +535,13 @@ func (tm *TestUnmanagedMiner) submitWindowPost(ctx context.Context, sectorNumber } var proofBytes []byte - proofBytes, err = tm.generateWindowPost(ctx, sectorNumber) - if err != nil { - return fmt.Errorf("Miner(%s): failed to generate window post for sector %d: %w", tm.ActorAddr, sectorNumber, err) + if withMockProofs { + proofBytes = []byte{0xde, 0xad, 0xbe, 0xef} + } else { + proofBytes, err = tm.generateWindowPost(ctx, sectorNumber) + if err != nil { + return fmt.Errorf("Miner(%s): failed to generate window post for sector %d: %w", tm.ActorAddr, sectorNumber, err) + } } tm.t.Logf("Miner(%s): WindowedPoSt(%d) Submitting ...\n", tm.ActorAddr, sectorNumber) diff --git a/itests/manual_onboarding_test.go b/itests/manual_onboarding_test.go index be47d67087d..27abaff5ed8 100644 --- a/itests/manual_onboarding_test.go +++ b/itests/manual_onboarding_test.go @@ -19,76 +19,97 @@ const defaultSectorSize = abi.SectorSize(2 << 10) // 2KiB func TestManualCCOnboarding(t *testing.T) { req := require.New(t) - kit.QuietMiningLogs() - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - var ( - // need to pick a balance value so that the test is not racy on CI by running through it's WindowPostDeadlines too fast - blocktime = 5 * time.Millisecond - client kit.TestFullNode - minerA kit.TestMiner // A is a standard genesis miner - ) - - // Setup and begin mining with a single miner (A) - // Miner A will only be a genesis Miner with power allocated in the genesis block and will not onboard any sectors from here on - kitOpts := []kit.EnsembleOpt{} - nodeOpts := []kit.NodeOpt{kit.SectorSize(defaultSectorSize), kit.WithAllSubsystems()} - ens := kit.NewEnsemble(t, kitOpts...). - FullNode(&client, nodeOpts...). - Miner(&minerA, &client, nodeOpts...). - Start(). - InterconnectAll() - ens.BeginMiningMustPost(blocktime) - - // Instantiate MinerB to manually handle sector onboarding and power acquisition through sector activation. - // Unlike other miners managed by the Lotus Miner storage infrastructure, MinerB operates independently, - // performing all related tasks manually. Managed by the TestKit, MinerB has the capability to utilize actual proofs - // for the processes of sector onboarding and activation. - nodeOpts = append(nodeOpts, kit.OwnerAddr(client.DefaultKey)) - minerB, ens := ens.UnmanagedMiner(&client, nodeOpts...) - minerC, ens := ens.UnmanagedMiner(&client, nodeOpts...) - - ens.Start() - - build.Clock.Sleep(time.Second) - - t.Log("Checking initial power ...") - - // Miner A should have power as it has already onboarded sectors in the genesis block - head, err := client.ChainHead(ctx) - req.NoError(err) - p, err := client.StateMinerPower(ctx, minerA.ActorAddr, head.Key()) - req.NoError(err) - t.Logf("MinerA RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) - - // Miner B should have no power as it has yet to onboard and activate any sectors - minerB.AssertNoPower(ctx) - - // Miner C should have no power as it has yet to onboard and activate any sectors - minerC.AssertNoPower(ctx) - - // ---- Miner B onboards a CC sector - var bSectorNum abi.SectorNumber - var respCh chan kit.WindowPostResp - bSectorNum, respCh = minerB.OnboardSectorWithPiecesAndRealProofs(ctx, kit.TestSpt) - // Miner B should still not have power as power can only be gained after sector is activated i.e. the first WindowPost is submitted for it - minerB.AssertNoPower(ctx) - // Activate CC Sector for Miner B and assert power - activateAndAssertPower(ctx, t, minerB, respCh, bSectorNum, uint64(defaultSectorSize)) - - // --- Miner C onboards sector with data/pieces - var cSectorNum abi.SectorNumber - var cRespCh chan kit.WindowPostResp - cSectorNum, cRespCh = minerC.OnboardCCSectorWithRealProofs(ctx, kit.TestSpt) - // Miner C should still not have power as power can only be gained after sector is activated i.e. the first WindowPost is submitted for it - minerC.AssertNoPower(ctx) - // Activate CC Sector for Miner C and assert power - activateAndAssertPower(ctx, t, minerC, cRespCh, cSectorNum, uint64(defaultSectorSize)) + for _, withMockProofs := range []bool{true, false} { + testName := "WithRealProofs" + if withMockProofs { + testName = "WithMockProofs" + } + t.Run(testName, func(t *testing.T) { + kit.QuietMiningLogs() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var ( + // need to pick a balance value so that the test is not racy on CI by running through it's WindowPostDeadlines too fast + blocktime = 5 * time.Millisecond + client kit.TestFullNode + minerA kit.TestMiner // A is a standard genesis miner + ) + + // Setup and begin mining with a single miner (A) + // Miner A will only be a genesis Miner with power allocated in the genesis block and will not onboard any sectors from here on + kitOpts := []kit.EnsembleOpt{} + if withMockProofs { + kitOpts = append(kitOpts, kit.MockProofs()) + } + nodeOpts := []kit.NodeOpt{kit.SectorSize(defaultSectorSize), kit.WithAllSubsystems()} + ens := kit.NewEnsemble(t, kitOpts...). + FullNode(&client, nodeOpts...). + Miner(&minerA, &client, nodeOpts...). + Start(). + InterconnectAll() + ens.BeginMiningMustPost(blocktime) + + // Instantiate MinerB to manually handle sector onboarding and power acquisition through sector activation. + // Unlike other miners managed by the Lotus Miner storage infrastructure, MinerB operates independently, + // performing all related tasks manually. Managed by the TestKit, MinerB has the capability to utilize actual proofs + // for the processes of sector onboarding and activation. + nodeOpts = append(nodeOpts, kit.OwnerAddr(client.DefaultKey)) + minerB, ens := ens.UnmanagedMiner(&client, nodeOpts...) + minerC, ens := ens.UnmanagedMiner(&client, nodeOpts...) + + ens.Start() + + build.Clock.Sleep(time.Second) + + t.Log("Checking initial power ...") + + // Miner A should have power as it has already onboarded sectors in the genesis block + head, err := client.ChainHead(ctx) + req.NoError(err) + p, err := client.StateMinerPower(ctx, minerA.ActorAddr, head.Key()) + req.NoError(err) + t.Logf("MinerA RBP: %v, QaP: %v", p.MinerPower.QualityAdjPower.String(), p.MinerPower.RawBytePower.String()) + + // Miner B should have no power as it has yet to onboard and activate any sectors + minerB.AssertNoPower(ctx) + + // Miner C should have no power as it has yet to onboard and activate any sectors + minerC.AssertNoPower(ctx) + + // ---- Miner B onboards a CC sector + var bSectorNum abi.SectorNumber + var respCh chan kit.WindowPostResp + + if withMockProofs { + bSectorNum, respCh = minerB.OnboardSectorWithPiecesAndMockProofs(ctx, kit.TestSpt) + } else { + bSectorNum, respCh = minerB.OnboardSectorWithPiecesAndRealProofs(ctx, kit.TestSpt) + } + // Miner B should still not have power as power can only be gained after sector is activated i.e. the first WindowPost is submitted for it + minerB.AssertNoPower(ctx) + // Activate CC Sector for Miner B and assert power + activateAndAssertPower(ctx, t, minerB, respCh, bSectorNum, uint64(defaultSectorSize), withMockProofs) + + // --- Miner C onboards sector with data/pieces + var cSectorNum abi.SectorNumber + var cRespCh chan kit.WindowPostResp + + if withMockProofs { + cSectorNum, cRespCh = minerC.OnboardCCSectorWithMockProofs(ctx, kit.TestSpt) + } else { + cSectorNum, cRespCh = minerC.OnboardCCSectorWithRealProofs(ctx, kit.TestSpt) + } + // Miner C should still not have power as power can only be gained after sector is activated i.e. the first WindowPost is submitted for it + minerC.AssertNoPower(ctx) + // Activate CC Sector for Miner C and assert power + activateAndAssertPower(ctx, t, minerC, cRespCh, cSectorNum, uint64(defaultSectorSize), withMockProofs) + }) + } } func activateAndAssertPower(ctx context.Context, t *testing.T, miner *kit.TestUnmanagedMiner, respCh chan kit.WindowPostResp, sector abi.SectorNumber, - sectorSize uint64) { + sectorSize uint64, withMockProofs bool) { req := require.New(t) // wait till sector is activated select { @@ -114,8 +135,15 @@ func activateAndAssertPower(ctx context.Context, t *testing.T, miner *kit.TestUn // Miner B should now have power miner.AssertPower(ctx, sectorSize, sectorSize) - // WindowPost Dispute should fail - assertDisputeFails(ctx, t, miner, sector) + if withMockProofs { + // WindowPost Dispute should succeed as we are using mock proofs + err := miner.SubmitPostDispute(ctx, sector) + require.NoError(t, err) + } else { + // WindowPost Dispute should fail + assertDisputeFails(ctx, t, miner, sector) + } + } func assertDisputeFails(ctx context.Context, t *testing.T, miner *kit.TestUnmanagedMiner, sector abi.SectorNumber) {