diff --git a/itests/api_test.go b/itests/api_test.go new file mode 100644 index 000000000..2c74c34e5 --- /dev/null +++ b/itests/api_test.go @@ -0,0 +1,259 @@ +package itests + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/big" + lapi "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/node/impl" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAPI(t *testing.T) { + runAPITest(t, Builder) +} + +func TestAPIRPC(t *testing.T) { + runAPITest(t, RPCBuilder) +} + +// runAPITest is the entry point to API test suite +func runAPITest(t *testing.T, b APIBuilder) { + ts := testSuite{ + makeNodes: b, + } + + t.Run("version", ts.testVersion) + t.Run("id", ts.testID) + t.Run("testConnectTwo", ts.testConnectTwo) + t.Run("testMining", ts.testMining) + t.Run("testMiningReal", ts.testMiningReal) + t.Run("testSearchMsg", ts.testSearchMsg) + t.Run("testNonGenesisMiner", ts.testNonGenesisMiner) +} + +func (ts *testSuite) testVersion(t *testing.T) { + lapi.RunningNodeType = lapi.NodeFull + t.Cleanup(func() { + lapi.RunningNodeType = lapi.NodeUnknown + }) + + ctx := context.Background() + apis, _ := ts.makeNodes(t, OneFull, OneMiner) + napi := apis[0] + + v, err := napi.Version(ctx) + if err != nil { + t.Fatal(err) + } + versions := strings.Split(v.Version, "+") + if len(versions) <= 0 { + t.Fatal("empty version") + } + require.Equal(t, versions[0], build.BuildVersion) +} + +func (ts *testSuite) testSearchMsg(t *testing.T) { + apis, miners := ts.makeNodes(t, OneFull, OneMiner) + + api := apis[0] + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + senderAddr, err := api.WalletDefaultAddress(ctx) + if err != nil { + t.Fatal(err) + } + + msg := &types.Message{ + From: senderAddr, + To: senderAddr, + Value: big.Zero(), + } + bm := NewBlockMiner(ctx, t, miners[0], 100*time.Millisecond) + bm.MineBlocks() + defer bm.Stop() + + sm, err := api.MpoolPushMessage(ctx, msg, nil) + if err != nil { + t.Fatal(err) + } + res, err := api.StateWaitMsg(ctx, sm.Cid(), 1, lapi.LookbackNoLimit, true) + if err != nil { + t.Fatal(err) + } + if res.Receipt.ExitCode != 0 { + t.Fatal("did not successfully send message") + } + + searchRes, err := api.StateSearchMsg(ctx, types.EmptyTSK, sm.Cid(), lapi.LookbackNoLimit, true) + if err != nil { + t.Fatal(err) + } + + if searchRes.TipSet != res.TipSet { + t.Fatalf("search ts: %s, different from wait ts: %s", searchRes.TipSet, res.TipSet) + } + +} + +func (ts *testSuite) testID(t *testing.T) { + ctx := context.Background() + apis, _ := ts.makeNodes(t, OneFull, OneMiner) + api := apis[0] + + id, err := api.ID(ctx) + if err != nil { + t.Fatal(err) + } + assert.Regexp(t, "^12", id.Pretty()) +} + +func (ts *testSuite) testConnectTwo(t *testing.T) { + ctx := context.Background() + apis, _ := ts.makeNodes(t, TwoFull, OneMiner) + + p, err := apis[0].NetPeers(ctx) + if err != nil { + t.Fatal(err) + } + if len(p) != 0 { + t.Error("Node 0 has a peer") + } + + p, err = apis[1].NetPeers(ctx) + if err != nil { + t.Fatal(err) + } + if len(p) != 0 { + t.Error("Node 1 has a peer") + } + + addrs, err := apis[1].NetAddrsListen(ctx) + if err != nil { + t.Fatal(err) + } + + if err := apis[0].NetConnect(ctx, addrs); err != nil { + t.Fatal(err) + } + + p, err = apis[0].NetPeers(ctx) + if err != nil { + t.Fatal(err) + } + if len(p) != 1 { + t.Error("Node 0 doesn't have 1 peer") + } + + p, err = apis[1].NetPeers(ctx) + if err != nil { + t.Fatal(err) + } + if len(p) != 1 { + t.Error("Node 0 doesn't have 1 peer") + } +} + +func (ts *testSuite) testMining(t *testing.T) { + ctx := context.Background() + apis, sn := ts.makeNodes(t, OneFull, OneMiner) + api := apis[0] + + newHeads, err := api.ChainNotify(ctx) + require.NoError(t, err) + initHead := (<-newHeads)[0] + baseHeight := initHead.Val.Height() + + h1, err := api.ChainHead(ctx) + require.NoError(t, err) + require.Equal(t, int64(h1.Height()), int64(baseHeight)) + + MineUntilBlock(ctx, t, apis[0], sn[0], nil) + require.NoError(t, err) + + <-newHeads + + h2, err := api.ChainHead(ctx) + require.NoError(t, err) + require.Greater(t, int64(h2.Height()), int64(h1.Height())) +} + +func (ts *testSuite) testMiningReal(t *testing.T) { + build.InsecurePoStValidation = false + defer func() { + build.InsecurePoStValidation = true + }() + + ctx := context.Background() + apis, sn := ts.makeNodes(t, OneFull, OneMiner) + api := apis[0] + + newHeads, err := api.ChainNotify(ctx) + require.NoError(t, err) + at := (<-newHeads)[0].Val.Height() + + h1, err := api.ChainHead(ctx) + require.NoError(t, err) + require.Equal(t, int64(at), int64(h1.Height())) + + MineUntilBlock(ctx, t, apis[0], sn[0], nil) + require.NoError(t, err) + + <-newHeads + + h2, err := api.ChainHead(ctx) + require.NoError(t, err) + require.Greater(t, int64(h2.Height()), int64(h1.Height())) + + MineUntilBlock(ctx, t, apis[0], sn[0], nil) + require.NoError(t, err) + + <-newHeads + + h3, err := api.ChainHead(ctx) + require.NoError(t, err) + require.Greater(t, int64(h3.Height()), int64(h2.Height())) +} + +func (ts *testSuite) testNonGenesisMiner(t *testing.T) { + ctx := context.Background() + n, sn := ts.makeNodes(t, []FullNodeOpts{ + FullNodeWithLatestActorsAt(-1), + }, []StorageMiner{ + {Full: 0, Preseal: PresealGenesis}, + }) + + full, ok := n[0].FullNode.(*impl.FullNodeAPI) + if !ok { + t.Skip("not testing with a full node") + return + } + genesisMiner := sn[0] + + bm := NewBlockMiner(ctx, t, genesisMiner, 4*time.Millisecond) + bm.MineBlocks() + t.Cleanup(bm.Stop) + + gaa, err := genesisMiner.ActorAddress(ctx) + require.NoError(t, err) + + gmi, err := full.StateMinerInfo(ctx, gaa, types.EmptyTSK) + require.NoError(t, err) + + testm := n[0].Stb(ctx, t, TestSpt, gmi.Owner) + + ta, err := testm.ActorAddress(ctx) + require.NoError(t, err) + + tid, err := address.IDFromAddress(ta) + require.NoError(t, err) + + require.Equal(t, uint64(1001), tid) +} diff --git a/itests/batch_deal_test.go b/itests/batch_deal_test.go new file mode 100644 index 000000000..f5c425d86 --- /dev/null +++ b/itests/batch_deal_test.go @@ -0,0 +1,90 @@ +package itests + +import ( + "testing" + "time" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/extern/storage-sealing/sealiface" + "github.com/filecoin-project/lotus/markets/storageadapter" + "github.com/filecoin-project/lotus/node" + "github.com/filecoin-project/lotus/node/impl" + "github.com/filecoin-project/lotus/node/modules/dtypes" + "github.com/stretchr/testify/require" +) + +func TestBatchDealInput(t *testing.T) { + QuietMiningLogs() + + var ( + blockTime = 10 * time.Millisecond + + // For these tests where the block time is artificially short, just use + // a deal start epoch that is guaranteed to be far enough in the future + // so that the deal starts sealing in time + dealStartEpoch = abi.ChainEpoch(2 << 12) + + publishPeriod = 10 * time.Second + maxDealsPerMsg = uint64(4) + ) + + // Set max deals per publish deals message to maxDealsPerMsg + minerDef := []StorageMiner{{ + Full: 0, + Opts: node.Options( + node.Override( + new(*storageadapter.DealPublisher), + storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{ + Period: publishPeriod, + MaxDealsPerMsg: maxDealsPerMsg, + })), + node.Override(new(dtypes.GetSealingConfigFunc), func() (dtypes.GetSealingConfigFunc, error) { + return func() (sealiface.Config, error) { + return sealiface.Config{ + MaxWaitDealsSectors: 1, + MaxSealingSectors: 1, + MaxSealingSectorsForDeals: 2, + AlwaysKeepUnsealedCopy: true, + }, nil + }, nil + }), + ), + Preseal: PresealGenesis, + }} + + // Create a connect client and miner node + n, sn := MockSbBuilder(t, OneFull, minerDef) + client := n[0].FullNode.(*impl.FullNodeAPI) + miner := sn[0] + s := connectAndStartMining(t, blockTime, client, miner) + defer s.blockMiner.Stop() + + // Starts a deal and waits until it's published + runDealTillSeal := func(rseed int) { + res, _, err := CreateClientFile(s.ctx, s.client, rseed) + require.NoError(t, err) + + dc := startDeal(t, s.ctx, s.miner, s.client, res.Root, false, dealStartEpoch) + waitDealSealed(t, s.ctx, s.miner, s.client, dc, false) + } + + // Run maxDealsPerMsg+1 deals in parallel + done := make(chan struct{}, maxDealsPerMsg+1) + for rseed := 1; rseed <= int(maxDealsPerMsg+1); rseed++ { + rseed := rseed + go func() { + runDealTillSeal(rseed) + done <- struct{}{} + }() + } + + // Wait for maxDealsPerMsg of the deals to be published + for i := 0; i < int(maxDealsPerMsg); i++ { + <-done + } + + sl, err := sn[0].SectorsList(s.ctx) + require.NoError(t, err) + require.GreaterOrEqual(t, len(sl), 4) + require.LessOrEqual(t, len(sl), 5) +} diff --git a/itests/ccupgrade.go b/itests/ccupgrade_test.go similarity index 95% rename from itests/ccupgrade.go rename to itests/ccupgrade_test.go index dd8d17d90..ceff0cecf 100644 --- a/itests/ccupgrade.go +++ b/itests/ccupgrade_test.go @@ -15,7 +15,9 @@ import ( "github.com/filecoin-project/lotus/node/impl" ) -func TestCCUpgrade(t *testing.T, b APIBuilder, blocktime time.Duration) { +func TestCCUpgrade(t *testing.T) { + QuietMiningLogs() + for _, height := range []abi.ChainEpoch{ -1, // before 162, // while sealing @@ -24,7 +26,7 @@ func TestCCUpgrade(t *testing.T, b APIBuilder, blocktime time.Duration) { } { height := height // make linters happy by copying t.Run(fmt.Sprintf("upgrade-%d", height), func(t *testing.T) { - runTestCCUpgrade(t, b, blocktime, height) + runTestCCUpgrade(t, MockSbBuilder, 5*time.Millisecond, height) }) } } diff --git a/itests/cliclient_test.go b/itests/cli_test.go similarity index 100% rename from itests/cliclient_test.go rename to itests/cli_test.go diff --git a/itests/deadlines.go b/itests/deadlines_test.go similarity index 92% rename from itests/deadlines.go rename to itests/deadlines_test.go index 4d62b5d07..a236f1057 100644 --- a/itests/deadlines.go +++ b/itests/deadlines_test.go @@ -4,23 +4,17 @@ import ( "bytes" "context" "fmt" + "os" "testing" "time" - "github.com/filecoin-project/lotus/api" - - "github.com/stretchr/testify/require" - "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/exitcode" "github.com/filecoin-project/go-state-types/network" - miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" - "github.com/ipfs/go-cid" - cbor "github.com/ipfs/go-ipld-cbor" - + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors" @@ -29,6 +23,11 @@ import ( "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/extern/sector-storage/mock" "github.com/filecoin-project/lotus/node/impl" + miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" + "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + logging "github.com/ipfs/go-log/v2" + "github.com/stretchr/testify/require" ) // TestDeadlineToggling: @@ -54,16 +53,28 @@ import ( // * goes through another PP // * asserts that miner B loses power // * asserts that miner D loses power, is inactive -func TestDeadlineToggling(t *testing.T, b APIBuilder, blocktime time.Duration) { - var upgradeH abi.ChainEpoch = 4000 - var provingPeriod abi.ChainEpoch = 2880 +func TestDeadlineToggling(t *testing.T) { + if os.Getenv("LOTUS_TEST_DEADLINE_TOGGLING") != "1" { + t.Skip("this takes a few minutes, set LOTUS_TEST_DEADLINE_TOGGLING=1 to run") + } + _ = logging.SetLogLevel("miner", "ERROR") + _ = logging.SetLogLevel("chainstore", "ERROR") + _ = logging.SetLogLevel("chain", "ERROR") + _ = logging.SetLogLevel("sub", "ERROR") + _ = logging.SetLogLevel("storageminer", "FATAL") - const sectorsC, sectorsD, sectersB = 10, 9, 8 + const sectorsC, sectorsD, sectorsB = 10, 9, 8 + + var ( + upgradeH abi.ChainEpoch = 4000 + provingPeriod abi.ChainEpoch = 2880 + blocktime = 2 * time.Millisecond + ) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - n, sn := b(t, []FullNodeOpts{FullNodeWithLatestActorsAt(upgradeH)}, OneMiner) + n, sn := MockSbBuilder(t, []FullNodeOpts{FullNodeWithLatestActorsAt(upgradeH)}, OneMiner) client := n[0].FullNode.(*impl.FullNodeAPI) minerA := sn[0] @@ -205,7 +216,7 @@ func TestDeadlineToggling(t *testing.T, b APIBuilder, blocktime time.Duration) { checkMiner(maddrE, types.NewInt(0), false, types.EmptyTSK) // pledge sectors on minerB/minerD, stop post on minerC - pledgeSectors(t, ctx, minerB, sectersB, 0, nil) + pledgeSectors(t, ctx, minerB, sectorsB, 0, nil) checkMiner(maddrB, types.NewInt(0), true, types.EmptyTSK) pledgeSectors(t, ctx, minerD, sectorsD, 0, nil) @@ -276,7 +287,7 @@ func TestDeadlineToggling(t *testing.T, b APIBuilder, blocktime time.Duration) { // second round of miner checks checkMiner(maddrA, types.NewInt(uint64(ssz)*GenesisPreseals), true, types.EmptyTSK) checkMiner(maddrC, types.NewInt(0), true, types.EmptyTSK) - checkMiner(maddrB, types.NewInt(uint64(ssz)*sectersB), true, types.EmptyTSK) + checkMiner(maddrB, types.NewInt(uint64(ssz)*sectorsB), true, types.EmptyTSK) checkMiner(maddrD, types.NewInt(uint64(ssz)*sectorsD), true, types.EmptyTSK) checkMiner(maddrE, types.NewInt(0), false, types.EmptyTSK) diff --git a/itests/deals.go b/itests/deals.go index 63e2d14c8..295436d22 100644 --- a/itests/deals.go +++ b/itests/deals.go @@ -20,36 +20,15 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/actors/builtin/market" "github.com/filecoin-project/lotus/chain/types" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" - "github.com/filecoin-project/lotus/extern/storage-sealing/sealiface" - "github.com/filecoin-project/lotus/markets/storageadapter" - "github.com/filecoin-project/lotus/node" "github.com/filecoin-project/lotus/node/impl" - "github.com/filecoin-project/lotus/node/modules/dtypes" - market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" ipld "github.com/ipfs/go-ipld-format" dag "github.com/ipfs/go-merkledag" dstest "github.com/ipfs/go-merkledag/test" unixfile "github.com/ipfs/go-unixfs/file" ) -func TestDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration, carExport, fastRet bool, startEpoch abi.ChainEpoch) { - s := setupOneClientOneMiner(t, b, blocktime) - defer s.blockMiner.Stop() - - MakeDeal(t, s.ctx, 6, s.client, s.miner, carExport, fastRet, startEpoch) -} - -func TestDoubleDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration, startEpoch abi.ChainEpoch) { - s := setupOneClientOneMiner(t, b, blocktime) - defer s.blockMiner.Stop() - - MakeDeal(t, s.ctx, 6, s.client, s.miner, false, false, startEpoch) - MakeDeal(t, s.ctx, 7, s.client, s.miner, false, false, startEpoch) -} - func MakeDeal(t *testing.T, ctx context.Context, rseed int, client api.FullNode, miner TestStorageNode, carExport, fastRet bool, startEpoch abi.ChainEpoch) { res, data, err := CreateClientFile(ctx, client, rseed) if err != nil { @@ -94,162 +73,6 @@ func CreateClientFile(ctx context.Context, client api.FullNode, rseed int) (*api return res, data, nil } -func TestPublishDealsBatching(t *testing.T, b APIBuilder, blocktime time.Duration, startEpoch abi.ChainEpoch) { - publishPeriod := 10 * time.Second - maxDealsPerMsg := uint64(2) - - // Set max deals per publish deals message to 2 - minerDef := []StorageMiner{{ - Full: 0, - Opts: node.Override( - new(*storageadapter.DealPublisher), - storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{ - Period: publishPeriod, - MaxDealsPerMsg: maxDealsPerMsg, - })), - Preseal: PresealGenesis, - }} - - // Create a connect client and miner node - n, sn := b(t, OneFull, minerDef) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - s := connectAndStartMining(t, b, blocktime, client, miner) - defer s.blockMiner.Stop() - - // Starts a deal and waits until it's published - runDealTillPublish := func(rseed int) { - res, _, err := CreateClientFile(s.ctx, s.client, rseed) - require.NoError(t, err) - - upds, err := client.ClientGetDealUpdates(s.ctx) - require.NoError(t, err) - - startDeal(t, s.ctx, s.miner, s.client, res.Root, false, startEpoch) - - // TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this - time.Sleep(time.Second) - - done := make(chan struct{}) - go func() { - for upd := range upds { - if upd.DataRef.Root == res.Root && upd.State == storagemarket.StorageDealAwaitingPreCommit { - done <- struct{}{} - } - } - }() - <-done - } - - // Run three deals in parallel - done := make(chan struct{}, maxDealsPerMsg+1) - for rseed := 1; rseed <= 3; rseed++ { - rseed := rseed - go func() { - runDealTillPublish(rseed) - done <- struct{}{} - }() - } - - // Wait for two of the deals to be published - for i := 0; i < int(maxDealsPerMsg); i++ { - <-done - } - - // Expect a single PublishStorageDeals message that includes the first two deals - msgCids, err := s.client.StateListMessages(s.ctx, &api.MessageMatch{To: market.Address}, types.EmptyTSK, 1) - require.NoError(t, err) - count := 0 - for _, msgCid := range msgCids { - msg, err := s.client.ChainGetMessage(s.ctx, msgCid) - require.NoError(t, err) - - if msg.Method == market.Methods.PublishStorageDeals { - count++ - var pubDealsParams market2.PublishStorageDealsParams - err = pubDealsParams.UnmarshalCBOR(bytes.NewReader(msg.Params)) - require.NoError(t, err) - require.Len(t, pubDealsParams.Deals, int(maxDealsPerMsg)) - } - } - require.Equal(t, 1, count) - - // The third deal should be published once the publish period expires. - // Allow a little padding as it takes a moment for the state change to - // be noticed by the client. - padding := 10 * time.Second - select { - case <-time.After(publishPeriod + padding): - require.Fail(t, "Expected 3rd deal to be published once publish period elapsed") - case <-done: // Success - } -} - -func TestBatchDealInput(t *testing.T, b APIBuilder, blocktime time.Duration, startEpoch abi.ChainEpoch) { - publishPeriod := 10 * time.Second - maxDealsPerMsg := uint64(4) - - // Set max deals per publish deals message to maxDealsPerMsg - minerDef := []StorageMiner{{ - Full: 0, - Opts: node.Options( - node.Override( - new(*storageadapter.DealPublisher), - storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{ - Period: publishPeriod, - MaxDealsPerMsg: maxDealsPerMsg, - })), - node.Override(new(dtypes.GetSealingConfigFunc), func() (dtypes.GetSealingConfigFunc, error) { - return func() (sealiface.Config, error) { - return sealiface.Config{ - MaxWaitDealsSectors: 1, - MaxSealingSectors: 1, - MaxSealingSectorsForDeals: 2, - AlwaysKeepUnsealedCopy: true, - }, nil - }, nil - }), - ), - Preseal: PresealGenesis, - }} - - // Create a connect client and miner node - n, sn := b(t, OneFull, minerDef) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - s := connectAndStartMining(t, b, blocktime, client, miner) - defer s.blockMiner.Stop() - - // Starts a deal and waits until it's published - runDealTillSeal := func(rseed int) { - res, _, err := CreateClientFile(s.ctx, s.client, rseed) - require.NoError(t, err) - - dc := startDeal(t, s.ctx, s.miner, s.client, res.Root, false, startEpoch) - waitDealSealed(t, s.ctx, s.miner, s.client, dc, false) - } - - // Run maxDealsPerMsg+1 deals in parallel - done := make(chan struct{}, maxDealsPerMsg+1) - for rseed := 1; rseed <= int(maxDealsPerMsg+1); rseed++ { - rseed := rseed - go func() { - runDealTillSeal(rseed) - done <- struct{}{} - }() - } - - // Wait for maxDealsPerMsg of the deals to be published - for i := 0; i < int(maxDealsPerMsg); i++ { - <-done - } - - sl, err := sn[0].SectorsList(s.ctx) - require.NoError(t, err) - require.GreaterOrEqual(t, len(sl), 4) - require.LessOrEqual(t, len(sl), 5) -} - func TestFastRetrievalDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration, startEpoch abi.ChainEpoch) { s := setupOneClientOneMiner(t, b, blocktime) defer s.blockMiner.Stop() @@ -276,7 +99,7 @@ func TestFastRetrievalDealFlow(t *testing.T, b APIBuilder, blocktime time.Durati testRetrieval(t, s.ctx, s.client, fcid, &info.PieceCID, false, data) } -func TestSecondDealRetrieval(t *testing.T, b APIBuilder, blocktime time.Duration) { +func runSecondDealRetrievalTest(t *testing.T, b APIBuilder, blocktime time.Duration) { s := setupOneClientOneMiner(t, b, blocktime) defer s.blockMiner.Stop() @@ -527,10 +350,10 @@ func setupOneClientOneMiner(t *testing.T, b APIBuilder, blocktime time.Duration) n, sn := b(t, OneFull, OneMiner) client := n[0].FullNode.(*impl.FullNodeAPI) miner := sn[0] - return connectAndStartMining(t, b, blocktime, client, miner) + return connectAndStartMining(t, blocktime, client, miner) } -func connectAndStartMining(t *testing.T, b APIBuilder, blocktime time.Duration, client *impl.FullNodeAPI, miner TestStorageNode) *dealsScaffold { +func connectAndStartMining(t *testing.T, blocktime time.Duration, client *impl.FullNodeAPI, miner TestStorageNode) *dealsScaffold { ctx := context.Background() addrinfo, err := client.NetAddrsListen(ctx) if err != nil { diff --git a/itests/deals_test.go b/itests/deals_test.go new file mode 100644 index 000000000..66abcca73 --- /dev/null +++ b/itests/deals_test.go @@ -0,0 +1,307 @@ +package itests + +import ( + "bytes" + "context" + "fmt" + "math/rand" + "sync/atomic" + "testing" + "time" + + "github.com/filecoin-project/go-fil-markets/storagemarket" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/actors/builtin/market" + "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/markets/storageadapter" + "github.com/filecoin-project/lotus/miner" + "github.com/filecoin-project/lotus/node" + "github.com/filecoin-project/lotus/node/impl" + market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" + "github.com/stretchr/testify/require" +) + +func TestDealCycle(t *testing.T) { + QuietMiningLogs() + + blockTime := 10 * time.Millisecond + + // For these tests where the block time is artificially short, just use + // a deal start epoch that is guaranteed to be far enough in the future + // so that the deal starts sealing in time + dealStartEpoch := abi.ChainEpoch(2 << 12) + + t.Run("TestFullDealCycle_Single", func(t *testing.T) { + runFullDealCycles(t, 1, MockSbBuilder, blockTime, false, false, dealStartEpoch) + }) + t.Run("TestFullDealCycle_Two", func(t *testing.T) { + runFullDealCycles(t, 2, MockSbBuilder, blockTime, false, false, dealStartEpoch) + }) + t.Run("WithExportedCAR", func(t *testing.T) { + runFullDealCycles(t, 1, MockSbBuilder, blockTime, true, false, dealStartEpoch) + }) + t.Run("TestFastRetrievalDealCycle", func(t *testing.T) { + TestFastRetrievalDealFlow(t, MockSbBuilder, blockTime, dealStartEpoch) + }) +} + +func TestAPIDealFlowReal(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode") + } + + QuietMiningLogs() + + // TODO: just set this globally? + oldDelay := policy.GetPreCommitChallengeDelay() + policy.SetPreCommitChallengeDelay(5) + t.Cleanup(func() { + policy.SetPreCommitChallengeDelay(oldDelay) + }) + + t.Run("basic", func(t *testing.T) { + runFullDealCycles(t, 1, Builder, time.Second, false, false, 0) + }) + + t.Run("fast-retrieval", func(t *testing.T) { + runFullDealCycles(t, 1, Builder, time.Second, false, true, 0) + }) + + t.Run("retrieval-second", func(t *testing.T) { + runSecondDealRetrievalTest(t, Builder, time.Second) + }) +} + +func TestPublishDealsBatching(t *testing.T) { + QuietMiningLogs() + + b := MockSbBuilder + blocktime := 10 * time.Millisecond + startEpoch := abi.ChainEpoch(2 << 12) + + publishPeriod := 10 * time.Second + maxDealsPerMsg := uint64(2) + + // Set max deals per publish deals message to 2 + minerDef := []StorageMiner{{ + Full: 0, + Opts: node.Override( + new(*storageadapter.DealPublisher), + storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{ + Period: publishPeriod, + MaxDealsPerMsg: maxDealsPerMsg, + })), + Preseal: PresealGenesis, + }} + + // Create a connect client and miner node + n, sn := b(t, OneFull, minerDef) + client := n[0].FullNode.(*impl.FullNodeAPI) + miner := sn[0] + s := connectAndStartMining(t, blocktime, client, miner) + defer s.blockMiner.Stop() + + // Starts a deal and waits until it's published + runDealTillPublish := func(rseed int) { + res, _, err := CreateClientFile(s.ctx, s.client, rseed) + require.NoError(t, err) + + upds, err := client.ClientGetDealUpdates(s.ctx) + require.NoError(t, err) + + startDeal(t, s.ctx, s.miner, s.client, res.Root, false, startEpoch) + + // TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this + time.Sleep(time.Second) + + done := make(chan struct{}) + go func() { + for upd := range upds { + if upd.DataRef.Root == res.Root && upd.State == storagemarket.StorageDealAwaitingPreCommit { + done <- struct{}{} + } + } + }() + <-done + } + + // Run three deals in parallel + done := make(chan struct{}, maxDealsPerMsg+1) + for rseed := 1; rseed <= 3; rseed++ { + rseed := rseed + go func() { + runDealTillPublish(rseed) + done <- struct{}{} + }() + } + + // Wait for two of the deals to be published + for i := 0; i < int(maxDealsPerMsg); i++ { + <-done + } + + // Expect a single PublishStorageDeals message that includes the first two deals + msgCids, err := s.client.StateListMessages(s.ctx, &api.MessageMatch{To: market.Address}, types.EmptyTSK, 1) + require.NoError(t, err) + count := 0 + for _, msgCid := range msgCids { + msg, err := s.client.ChainGetMessage(s.ctx, msgCid) + require.NoError(t, err) + + if msg.Method == market.Methods.PublishStorageDeals { + count++ + var pubDealsParams market2.PublishStorageDealsParams + err = pubDealsParams.UnmarshalCBOR(bytes.NewReader(msg.Params)) + require.NoError(t, err) + require.Len(t, pubDealsParams.Deals, int(maxDealsPerMsg)) + } + } + require.Equal(t, 1, count) + + // The third deal should be published once the publish period expires. + // Allow a little padding as it takes a moment for the state change to + // be noticed by the client. + padding := 10 * time.Second + select { + case <-time.After(publishPeriod + padding): + require.Fail(t, "Expected 3rd deal to be published once publish period elapsed") + case <-done: // Success + } +} + +func TestDealMining(t *testing.T) { + // test making a deal with a fresh miner, and see if it starts to mine. + if testing.Short() { + t.Skip("skipping test in short mode") + } + + QuietMiningLogs() + + b := MockSbBuilder + blocktime := 50 * time.Millisecond + + ctx := context.Background() + n, sn := b(t, OneFull, []StorageMiner{ + {Full: 0, Preseal: PresealGenesis}, + {Full: 0, Preseal: 0}, // TODO: Add support for miners on non-first full node + }) + client := n[0].FullNode.(*impl.FullNodeAPI) + provider := sn[1] + genesisMiner := sn[0] + + addrinfo, err := client.NetAddrsListen(ctx) + if err != nil { + t.Fatal(err) + } + + if err := provider.NetConnect(ctx, addrinfo); err != nil { + t.Fatal(err) + } + + if err := genesisMiner.NetConnect(ctx, addrinfo); err != nil { + t.Fatal(err) + } + + time.Sleep(time.Second) + + data := make([]byte, 600) + rand.New(rand.NewSource(5)).Read(data) + + r := bytes.NewReader(data) + fcid, err := client.ClientImportLocal(ctx, r) + if err != nil { + t.Fatal(err) + } + + fmt.Println("FILE CID: ", fcid) + + var mine int32 = 1 + done := make(chan struct{}) + minedTwo := make(chan struct{}) + + m2addr, err := sn[1].ActorAddress(context.TODO()) + if err != nil { + t.Fatal(err) + } + + go func() { + defer close(done) + + complChan := minedTwo + for atomic.LoadInt32(&mine) != 0 { + wait := make(chan int) + mdone := func(mined bool, _ abi.ChainEpoch, err error) { + n := 0 + if mined { + n = 1 + } + wait <- n + } + + if err := sn[0].MineOne(ctx, miner.MineReq{Done: mdone}); err != nil { + t.Error(err) + } + + if err := sn[1].MineOne(ctx, miner.MineReq{Done: mdone}); err != nil { + t.Error(err) + } + + expect := <-wait + expect += <-wait + + time.Sleep(blocktime) + if expect == 0 { + // null block + continue + } + + var nodeOneMined bool + for _, node := range sn { + mb, err := node.MiningBase(ctx) + if err != nil { + t.Error(err) + return + } + + for _, b := range mb.Blocks() { + if b.Miner == m2addr { + nodeOneMined = true + break + } + } + + } + + if nodeOneMined && complChan != nil { + close(complChan) + complChan = nil + } + + } + }() + + deal := startDeal(t, ctx, provider, client, fcid, false, 0) + + // TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this + time.Sleep(time.Second) + + waitDealSealed(t, ctx, provider, client, deal, false) + + <-minedTwo + + atomic.StoreInt32(&mine, 0) + fmt.Println("shutting down mining") + <-done +} + +func runFullDealCycles(t *testing.T, n int, b APIBuilder, blocktime time.Duration, carExport, fastRet bool, startEpoch abi.ChainEpoch) { + s := setupOneClientOneMiner(t, b, blocktime) + defer s.blockMiner.Stop() + + baseseed := 6 + for i := 0; i < n; i++ { + MakeDeal(t, s.ctx, baseseed+i, s.client, s.miner, carExport, fastRet, startEpoch) + } +} diff --git a/itests/blockminer.go b/itests/h_blockminer.go similarity index 100% rename from itests/blockminer.go rename to itests/h_blockminer.go diff --git a/itests/mockcli.go b/itests/h_mockcli.go similarity index 100% rename from itests/mockcli.go rename to itests/h_mockcli.go diff --git a/itests/net.go b/itests/h_net.go similarity index 88% rename from itests/net.go rename to itests/h_net.go index 315aab267..969ed1ec5 100644 --- a/itests/net.go +++ b/itests/h_net.go @@ -9,8 +9,6 @@ import ( "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/go-address" - "github.com/filecoin-project/lotus/api/test" - test2 "github.com/filecoin-project/lotus/node/test" ) func StartOneNodeOneMiner(ctx context.Context, t *testing.T, blocktime time.Duration) (TestNode, address.Address) { @@ -66,7 +64,7 @@ func StartTwoNodesOneMiner(ctx context.Context, t *testing.T, blocktime time.Dur } // Start mining blocks - bm := test.NewBlockMiner(ctx, t, miner, blocktime) + bm := NewBlockMiner(ctx, t, miner, blocktime) bm.MineBlocks() t.Cleanup(bm.Stop) @@ -76,7 +74,7 @@ func StartTwoNodesOneMiner(ctx context.Context, t *testing.T, blocktime time.Dur t.Fatal(err) } - test.SendFunds(ctx, t, fullNode1, fullNodeAddr2, abi.NewTokenAmount(1e18)) + SendFunds(ctx, t, fullNode1, fullNodeAddr2, abi.NewTokenAmount(1e18)) // Get the first node's address fullNodeAddr1, err := fullNode1.WalletDefaultAddress(ctx) diff --git a/itests/node_builder.go b/itests/h_node_builder.go similarity index 99% rename from itests/node_builder.go rename to itests/h_node_builder.go index 3b3cacb2a..84c2b844e 100644 --- a/itests/node_builder.go +++ b/itests/h_node_builder.go @@ -598,7 +598,7 @@ func CreateRPCServer(t *testing.T, handlers map[string]interface{}) (multiaddr.M rpcServer.Register("Filecoin", handler) m.Handle(path, rpcServer) } - testServ := httpNewServer(m) // todo: close + testServ := httptest.NewServer(m) // todo: close t.Cleanup(testServ.Close) t.Cleanup(testServ.CloseClientConnections) diff --git a/itests/h_pledge.go b/itests/h_pledge.go new file mode 100644 index 000000000..2223d585a --- /dev/null +++ b/itests/h_pledge.go @@ -0,0 +1,64 @@ +package itests + +import ( + "context" + "fmt" + "strings" + "testing" + "time" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" + sealing "github.com/filecoin-project/lotus/extern/storage-sealing" + "github.com/stretchr/testify/require" +) + +func pledgeSectors(t *testing.T, ctx context.Context, miner TestStorageNode, n, existing int, blockNotif <-chan struct{}) { + for i := 0; i < n; i++ { + if i%3 == 0 && blockNotif != nil { + <-blockNotif + t.Log("WAIT") + } + t.Logf("PLEDGING %d", i) + _, err := miner.PledgeSector(ctx) + require.NoError(t, err) + } + + for { + s, err := miner.SectorsList(ctx) // Note - the test builder doesn't import genesis sectors into FSM + require.NoError(t, err) + fmt.Printf("Sectors: %d\n", len(s)) + if len(s) >= n+existing { + break + } + + build.Clock.Sleep(100 * time.Millisecond) + } + + fmt.Printf("All sectors is fsm\n") + + s, err := miner.SectorsList(ctx) + require.NoError(t, err) + + toCheck := map[abi.SectorNumber]struct{}{} + for _, number := range s { + toCheck[number] = struct{}{} + } + + for len(toCheck) > 0 { + for n := range toCheck { + st, err := miner.SectorsStatus(ctx, n, false) + require.NoError(t, err) + if st.State == api.SectorState(sealing.Proving) { + delete(toCheck, n) + } + if strings.Contains(string(st.State), "Fail") { + t.Fatal("sector in a failed state", st.State) + } + } + + build.Clock.Sleep(100 * time.Millisecond) + fmt.Printf("WaitSeal: %d\n", len(s)) + } +} diff --git a/itests/util2.go b/itests/h_util2.go similarity index 100% rename from itests/util2.go rename to itests/h_util2.go diff --git a/itests/init.go b/itests/init.go new file mode 100644 index 000000000..9f306be98 --- /dev/null +++ b/itests/init.go @@ -0,0 +1,15 @@ +package itests + +import ( + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/chain/actors/policy" + logging "github.com/ipfs/go-log/v2" +) + +func init() { + _ = logging.SetLogLevel("*", "INFO") + + policy.SetConsensusMinerMinPower(abi.NewStoragePower(2048)) + policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1) + policy.SetMinVerifiedDealSize(abi.NewStoragePower(256)) +} diff --git a/itests/log.go b/itests/log.go new file mode 100644 index 000000000..47ffe481b --- /dev/null +++ b/itests/log.go @@ -0,0 +1,14 @@ +package itests + +import logging "github.com/ipfs/go-log/v2" + +func QuietMiningLogs() { + _ = logging.SetLogLevel("miner", "ERROR") + _ = logging.SetLogLevel("chainstore", "ERROR") + _ = logging.SetLogLevel("chain", "ERROR") + _ = logging.SetLogLevel("sub", "ERROR") + _ = logging.SetLogLevel("storageminer", "ERROR") + _ = logging.SetLogLevel("pubsub", "ERROR") + _ = logging.SetLogLevel("gen", "ERROR") + _ = logging.SetLogLevel("dht/RtRefreshManager", "ERROR") +} diff --git a/itests/mining.go b/itests/mining.go deleted file mode 100644 index b53e63a63..000000000 --- a/itests/mining.go +++ /dev/null @@ -1,240 +0,0 @@ -package itests - -import ( - "bytes" - "context" - "fmt" - "math/rand" - "sync/atomic" - "testing" - "time" - - logging "github.com/ipfs/go-log/v2" - - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/miner" - "github.com/filecoin-project/lotus/node/impl" -) - -//nolint:deadcode,varcheck -var log = logging.Logger("apitest") - -func (ts *testSuite) testMining(t *testing.T) { - ctx := context.Background() - apis, sn := ts.makeNodes(t, OneFull, OneMiner) - api := apis[0] - - newHeads, err := api.ChainNotify(ctx) - require.NoError(t, err) - initHead := (<-newHeads)[0] - baseHeight := initHead.Val.Height() - - h1, err := api.ChainHead(ctx) - require.NoError(t, err) - require.Equal(t, int64(h1.Height()), int64(baseHeight)) - - MineUntilBlock(ctx, t, apis[0], sn[0], nil) - require.NoError(t, err) - - <-newHeads - - h2, err := api.ChainHead(ctx) - require.NoError(t, err) - require.Greater(t, int64(h2.Height()), int64(h1.Height())) -} - -func (ts *testSuite) testMiningReal(t *testing.T) { - build.InsecurePoStValidation = false - defer func() { - build.InsecurePoStValidation = true - }() - - ctx := context.Background() - apis, sn := ts.makeNodes(t, OneFull, OneMiner) - api := apis[0] - - newHeads, err := api.ChainNotify(ctx) - require.NoError(t, err) - at := (<-newHeads)[0].Val.Height() - - h1, err := api.ChainHead(ctx) - require.NoError(t, err) - require.Equal(t, int64(at), int64(h1.Height())) - - MineUntilBlock(ctx, t, apis[0], sn[0], nil) - require.NoError(t, err) - - <-newHeads - - h2, err := api.ChainHead(ctx) - require.NoError(t, err) - require.Greater(t, int64(h2.Height()), int64(h1.Height())) - - MineUntilBlock(ctx, t, apis[0], sn[0], nil) - require.NoError(t, err) - - <-newHeads - - h3, err := api.ChainHead(ctx) - require.NoError(t, err) - require.Greater(t, int64(h3.Height()), int64(h2.Height())) -} - -func TestDealMining(t *testing.T, b APIBuilder, blocktime time.Duration, carExport bool) { - // test making a deal with a fresh miner, and see if it starts to mine - - ctx := context.Background() - n, sn := b(t, OneFull, []StorageMiner{ - {Full: 0, Preseal: PresealGenesis}, - {Full: 0, Preseal: 0}, // TODO: Add support for miners on non-first full node - }) - client := n[0].FullNode.(*impl.FullNodeAPI) - provider := sn[1] - genesisMiner := sn[0] - - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := provider.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - - if err := genesisMiner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - - time.Sleep(time.Second) - - data := make([]byte, 600) - rand.New(rand.NewSource(5)).Read(data) - - r := bytes.NewReader(data) - fcid, err := client.ClientImportLocal(ctx, r) - if err != nil { - t.Fatal(err) - } - - fmt.Println("FILE CID: ", fcid) - - var mine int32 = 1 - done := make(chan struct{}) - minedTwo := make(chan struct{}) - - m2addr, err := sn[1].ActorAddress(context.TODO()) - if err != nil { - t.Fatal(err) - } - - go func() { - defer close(done) - - complChan := minedTwo - for atomic.LoadInt32(&mine) != 0 { - wait := make(chan int) - mdone := func(mined bool, _ abi.ChainEpoch, err error) { - n := 0 - if mined { - n = 1 - } - wait <- n - } - - if err := sn[0].MineOne(ctx, miner.MineReq{Done: mdone}); err != nil { - t.Error(err) - } - - if err := sn[1].MineOne(ctx, miner.MineReq{Done: mdone}); err != nil { - t.Error(err) - } - - expect := <-wait - expect += <-wait - - time.Sleep(blocktime) - if expect == 0 { - // null block - continue - } - - var nodeOneMined bool - for _, node := range sn { - mb, err := node.MiningBase(ctx) - if err != nil { - t.Error(err) - return - } - - for _, b := range mb.Blocks() { - if b.Miner == m2addr { - nodeOneMined = true - break - } - } - - } - - if nodeOneMined && complChan != nil { - close(complChan) - complChan = nil - } - - } - }() - - deal := startDeal(t, ctx, provider, client, fcid, false, 0) - - // TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this - time.Sleep(time.Second) - - waitDealSealed(t, ctx, provider, client, deal, false) - - <-minedTwo - - atomic.StoreInt32(&mine, 0) - fmt.Println("shutting down mining") - <-done -} - -func (ts *testSuite) testNonGenesisMiner(t *testing.T) { - ctx := context.Background() - n, sn := ts.makeNodes(t, []FullNodeOpts{ - FullNodeWithLatestActorsAt(-1), - }, []StorageMiner{ - {Full: 0, Preseal: PresealGenesis}, - }) - - full, ok := n[0].FullNode.(*impl.FullNodeAPI) - if !ok { - t.Skip("not testing with a full node") - return - } - genesisMiner := sn[0] - - bm := NewBlockMiner(ctx, t, genesisMiner, 4*time.Millisecond) - bm.MineBlocks() - t.Cleanup(bm.Stop) - - gaa, err := genesisMiner.ActorAddress(ctx) - require.NoError(t, err) - - gmi, err := full.StateMinerInfo(ctx, gaa, types.EmptyTSK) - require.NoError(t, err) - - testm := n[0].Stb(ctx, t, TestSpt, gmi.Owner) - - ta, err := testm.ActorAddress(ctx) - require.NoError(t, err) - - tid, err := address.IDFromAddress(ta) - require.NoError(t, err) - - require.Equal(t, uint64(1001), tid) -} diff --git a/itests/multisig.go b/itests/multisig.go deleted file mode 100644 index 5f94a5028..000000000 --- a/itests/multisig.go +++ /dev/null @@ -1,97 +0,0 @@ -package itests - -import ( - "context" - "fmt" - "regexp" - "strings" - "testing" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/lotus/api/test" - "github.com/filecoin-project/lotus/chain/types" - "github.com/stretchr/testify/require" - lcli "github.com/urfave/cli/v2" -) - -func RunMultisigTest(t *testing.T, cmds []*lcli.Command, clientNode test.TestNode) { - ctx := context.Background() - - // Create mock CLI - mockCLI := NewMockCLI(ctx, t, cmds) - clientCLI := mockCLI.Client(clientNode.ListenAddr) - - // Create some wallets on the node to use for testing multisig - var walletAddrs []address.Address - for i := 0; i < 4; i++ { - addr, err := clientNode.WalletNew(ctx, types.KTSecp256k1) - require.NoError(t, err) - - walletAddrs = append(walletAddrs, addr) - - test.SendFunds(ctx, t, clientNode, addr, types.NewInt(1e15)) - } - - // Create an msig with three of the addresses and threshold of two sigs - // msig create --required=2 --duration=50 --value=1000attofil - amtAtto := types.NewInt(1000) - threshold := 2 - paramDuration := "--duration=50" - paramRequired := fmt.Sprintf("--required=%d", threshold) - paramValue := fmt.Sprintf("--value=%dattofil", amtAtto) - out := clientCLI.RunCmd( - "msig", "create", - paramRequired, - paramDuration, - paramValue, - walletAddrs[0].String(), - walletAddrs[1].String(), - walletAddrs[2].String(), - ) - fmt.Println(out) - - // Extract msig robust address from output - expCreateOutPrefix := "Created new multisig:" - require.Regexp(t, regexp.MustCompile(expCreateOutPrefix), out) - parts := strings.Split(strings.TrimSpace(strings.Replace(out, expCreateOutPrefix, "", -1)), " ") - require.Len(t, parts, 2) - msigRobustAddr := parts[1] - fmt.Println("msig robust address:", msigRobustAddr) - - // Propose to add a new address to the msig - // msig add-propose --from= - paramFrom := fmt.Sprintf("--from=%s", walletAddrs[0]) - out = clientCLI.RunCmd( - "msig", "add-propose", - paramFrom, - msigRobustAddr, - walletAddrs[3].String(), - ) - fmt.Println(out) - - // msig inspect - out = clientCLI.RunCmd("msig", "inspect", "--vesting", "--decode-params", msigRobustAddr) - fmt.Println(out) - - // Expect correct balance - require.Regexp(t, regexp.MustCompile("Balance: 0.000000000000001 FIL"), out) - // Expect 1 transaction - require.Regexp(t, regexp.MustCompile(`Transactions:\s*1`), out) - // Expect transaction to be "AddSigner" - require.Regexp(t, regexp.MustCompile(`AddSigner`), out) - - // Approve adding the new address - // msig add-approve --from= 0 false - txnID := "0" - paramFrom = fmt.Sprintf("--from=%s", walletAddrs[1]) - out = clientCLI.RunCmd( - "msig", "add-approve", - paramFrom, - msigRobustAddr, - walletAddrs[0].String(), - txnID, - walletAddrs[3].String(), - "false", - ) - fmt.Println(out) -} diff --git a/itests/multisig_test.go b/itests/multisig_test.go index e285c3955..8593d61c3 100644 --- a/itests/multisig_test.go +++ b/itests/multisig_test.go @@ -2,15 +2,20 @@ package itests import ( "context" + "fmt" "os" + "regexp" + "strings" "testing" "time" + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/cli" + "github.com/stretchr/testify/require" ) -// TestMultisig does a basic test to exercise the multisig CLI -// commands +// TestMultisig does a basic test to exercise the multisig CLI commands func TestMultisig(t *testing.T) { _ = os.Setenv("BELLMAN_NO_GPU", "1") QuietMiningLogs() @@ -18,5 +23,82 @@ func TestMultisig(t *testing.T) { blocktime := 5 * time.Millisecond ctx := context.Background() clientNode, _ := StartOneNodeOneMiner(ctx, t, blocktime) - RunMultisigTest(t, cli.Commands, clientNode) + + // Create mock CLI + mockCLI := NewMockCLI(ctx, t, cli.Commands) + clientCLI := mockCLI.Client(clientNode.ListenAddr) + + // Create some wallets on the node to use for testing multisig + var walletAddrs []address.Address + for i := 0; i < 4; i++ { + addr, err := clientNode.WalletNew(ctx, types.KTSecp256k1) + require.NoError(t, err) + + walletAddrs = append(walletAddrs, addr) + + SendFunds(ctx, t, clientNode, addr, types.NewInt(1e15)) + } + + // Create an msig with three of the addresses and threshold of two sigs + // msig create --required=2 --duration=50 --value=1000attofil + amtAtto := types.NewInt(1000) + threshold := 2 + paramDuration := "--duration=50" + paramRequired := fmt.Sprintf("--required=%d", threshold) + paramValue := fmt.Sprintf("--value=%dattofil", amtAtto) + out := clientCLI.RunCmd( + "msig", "create", + paramRequired, + paramDuration, + paramValue, + walletAddrs[0].String(), + walletAddrs[1].String(), + walletAddrs[2].String(), + ) + fmt.Println(out) + + // Extract msig robust address from output + expCreateOutPrefix := "Created new multisig:" + require.Regexp(t, regexp.MustCompile(expCreateOutPrefix), out) + parts := strings.Split(strings.TrimSpace(strings.Replace(out, expCreateOutPrefix, "", -1)), " ") + require.Len(t, parts, 2) + msigRobustAddr := parts[1] + fmt.Println("msig robust address:", msigRobustAddr) + + // Propose to add a new address to the msig + // msig add-propose --from= + paramFrom := fmt.Sprintf("--from=%s", walletAddrs[0]) + out = clientCLI.RunCmd( + "msig", "add-propose", + paramFrom, + msigRobustAddr, + walletAddrs[3].String(), + ) + fmt.Println(out) + + // msig inspect + out = clientCLI.RunCmd("msig", "inspect", "--vesting", "--decode-params", msigRobustAddr) + fmt.Println(out) + + // Expect correct balance + require.Regexp(t, regexp.MustCompile("Balance: 0.000000000000001 FIL"), out) + // Expect 1 transaction + require.Regexp(t, regexp.MustCompile(`Transactions:\s*1`), out) + // Expect transaction to be "AddSigner" + require.Regexp(t, regexp.MustCompile(`AddSigner`), out) + + // Approve adding the new address + // msig add-approve --from= 0 false + txnID := "0" + paramFrom = fmt.Sprintf("--from=%s", walletAddrs[1]) + out = clientCLI.RunCmd( + "msig", "add-approve", + paramFrom, + msigRobustAddr, + walletAddrs[0].String(), + txnID, + walletAddrs[3].String(), + "false", + ) + fmt.Println(out) } diff --git a/itests/node_test.go b/itests/node_test.go deleted file mode 100644 index 0b01ab660..000000000 --- a/itests/node_test.go +++ /dev/null @@ -1,261 +0,0 @@ -package itests_test - -import ( - "os" - "testing" - "time" - - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/lotus/api/test" - "github.com/filecoin-project/lotus/chain/actors/policy" - "github.com/filecoin-project/lotus/lib/lotuslog" - builder "github.com/filecoin-project/lotus/node/test" - logging "github.com/ipfs/go-log/v2" -) - -func init() { - _ = logging.SetLogLevel("*", "INFO") - - policy.SetConsensusMinerMinPower(abi.NewStoragePower(2048)) - policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1) - policy.SetMinVerifiedDealSize(abi.NewStoragePower(256)) -} - -func TestAPI(t *testing.T) { - test.TestApis(t, builder.Builder) -} - -func TestAPIRPC(t *testing.T) { - test.TestApis(t, builder.RPCBuilder) -} - -func TestAPIDealFlow(t *testing.T) { - logging.SetLogLevel("miner", "ERROR") - logging.SetLogLevel("chainstore", "ERROR") - logging.SetLogLevel("chain", "ERROR") - logging.SetLogLevel("sub", "ERROR") - logging.SetLogLevel("storageminer", "ERROR") - - blockTime := 10 * time.Millisecond - - // For these tests where the block time is artificially short, just use - // a deal start epoch that is guaranteed to be far enough in the future - // so that the deal starts sealing in time - dealStartEpoch := abi.ChainEpoch(2 << 12) - - t.Run("TestDealFlow", func(t *testing.T) { - test.TestDealFlow(t, builder.MockSbBuilder, blockTime, false, false, dealStartEpoch) - }) - t.Run("WithExportedCAR", func(t *testing.T) { - test.TestDealFlow(t, builder.MockSbBuilder, blockTime, true, false, dealStartEpoch) - }) - t.Run("TestDoubleDealFlow", func(t *testing.T) { - test.TestDoubleDealFlow(t, builder.MockSbBuilder, blockTime, dealStartEpoch) - }) - t.Run("TestFastRetrievalDealFlow", func(t *testing.T) { - test.TestFastRetrievalDealFlow(t, builder.MockSbBuilder, blockTime, dealStartEpoch) - }) - t.Run("TestPublishDealsBatching", func(t *testing.T) { - test.TestPublishDealsBatching(t, builder.MockSbBuilder, blockTime, dealStartEpoch) - }) -} - -func TestBatchDealInput(t *testing.T) { - logging.SetLogLevel("miner", "ERROR") - logging.SetLogLevel("chainstore", "ERROR") - logging.SetLogLevel("chain", "ERROR") - logging.SetLogLevel("sub", "ERROR") - logging.SetLogLevel("storageminer", "ERROR") - - blockTime := 10 * time.Millisecond - - // For these tests where the block time is artificially short, just use - // a deal start epoch that is guaranteed to be far enough in the future - // so that the deal starts sealing in time - dealStartEpoch := abi.ChainEpoch(2 << 12) - - test.TestBatchDealInput(t, builder.MockSbBuilder, blockTime, dealStartEpoch) -} - -func TestAPIDealFlowReal(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode") - } - lotuslog.SetupLogLevels() - logging.SetLogLevel("miner", "ERROR") - logging.SetLogLevel("chainstore", "ERROR") - logging.SetLogLevel("chain", "ERROR") - logging.SetLogLevel("sub", "ERROR") - logging.SetLogLevel("storageminer", "ERROR") - - // TODO: just set this globally? - oldDelay := policy.GetPreCommitChallengeDelay() - policy.SetPreCommitChallengeDelay(5) - t.Cleanup(func() { - policy.SetPreCommitChallengeDelay(oldDelay) - }) - - t.Run("basic", func(t *testing.T) { - test.TestDealFlow(t, builder.Builder, time.Second, false, false, 0) - }) - - t.Run("fast-retrieval", func(t *testing.T) { - test.TestDealFlow(t, builder.Builder, time.Second, false, true, 0) - }) - - t.Run("retrieval-second", func(t *testing.T) { - test.TestSecondDealRetrieval(t, builder.Builder, time.Second) - }) -} - -func TestDealMining(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode") - } - - logging.SetLogLevel("miner", "ERROR") - logging.SetLogLevel("chainstore", "ERROR") - logging.SetLogLevel("chain", "ERROR") - logging.SetLogLevel("sub", "ERROR") - logging.SetLogLevel("storageminer", "ERROR") - - test.TestDealMining(t, builder.MockSbBuilder, 50*time.Millisecond, false) -} - -func TestSDRUpgrade(t *testing.T) { - logging.SetLogLevel("miner", "ERROR") - logging.SetLogLevel("chainstore", "ERROR") - logging.SetLogLevel("chain", "ERROR") - logging.SetLogLevel("sub", "ERROR") - logging.SetLogLevel("storageminer", "ERROR") - - oldDelay := policy.GetPreCommitChallengeDelay() - policy.SetPreCommitChallengeDelay(5) - t.Cleanup(func() { - policy.SetPreCommitChallengeDelay(oldDelay) - }) - - test.TestSDRUpgrade(t, builder.MockSbBuilder, 50*time.Millisecond) -} - -func TestPledgeSectors(t *testing.T) { - logging.SetLogLevel("miner", "ERROR") - logging.SetLogLevel("chainstore", "ERROR") - logging.SetLogLevel("chain", "ERROR") - logging.SetLogLevel("sub", "ERROR") - logging.SetLogLevel("storageminer", "ERROR") - - t.Run("1", func(t *testing.T) { - test.TestPledgeSector(t, builder.MockSbBuilder, 50*time.Millisecond, 1) - }) - - t.Run("100", func(t *testing.T) { - test.TestPledgeSector(t, builder.MockSbBuilder, 50*time.Millisecond, 100) - }) - - t.Run("1000", func(t *testing.T) { - if testing.Short() { // takes ~16s - t.Skip("skipping test in short mode") - } - - test.TestPledgeSector(t, builder.MockSbBuilder, 50*time.Millisecond, 1000) - }) -} - -func TestTapeFix(t *testing.T) { - logging.SetLogLevel("miner", "ERROR") - logging.SetLogLevel("chainstore", "ERROR") - logging.SetLogLevel("chain", "ERROR") - logging.SetLogLevel("sub", "ERROR") - logging.SetLogLevel("storageminer", "ERROR") - - test.TestTapeFix(t, builder.MockSbBuilder, 2*time.Millisecond) -} - -func TestWindowedPost(t *testing.T) { - if os.Getenv("LOTUS_TEST_WINDOW_POST") != "1" { - t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") - } - - logging.SetLogLevel("miner", "ERROR") - logging.SetLogLevel("chainstore", "ERROR") - logging.SetLogLevel("chain", "ERROR") - logging.SetLogLevel("sub", "ERROR") - logging.SetLogLevel("storageminer", "ERROR") - - TestWindowPost(t, builder.MockSbBuilder, 2*time.Millisecond, 10) -} - -func TestTerminate(t *testing.T) { - if os.Getenv("LOTUS_TEST_WINDOW_POST") != "1" { - t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") - } - - logging.SetLogLevel("miner", "ERROR") - logging.SetLogLevel("chainstore", "ERROR") - logging.SetLogLevel("chain", "ERROR") - logging.SetLogLevel("sub", "ERROR") - logging.SetLogLevel("storageminer", "ERROR") - - TestTerminate(t, builder.MockSbBuilder, 2*time.Millisecond) -} - -func TestCCUpgrade(t *testing.T) { - logging.SetLogLevel("miner", "ERROR") - logging.SetLogLevel("chainstore", "ERROR") - logging.SetLogLevel("chain", "ERROR") - logging.SetLogLevel("sub", "ERROR") - logging.SetLogLevel("storageminer", "ERROR") - - test.TestCCUpgrade(t, builder.MockSbBuilder, 5*time.Millisecond) -} - -func TestPaymentChannels(t *testing.T) { - logging.SetLogLevel("miner", "ERROR") - logging.SetLogLevel("chainstore", "ERROR") - logging.SetLogLevel("chain", "ERROR") - logging.SetLogLevel("sub", "ERROR") - logging.SetLogLevel("pubsub", "ERROR") - logging.SetLogLevel("storageminer", "ERROR") - - test.TestPaymentChannels(t, builder.MockSbBuilder, 5*time.Millisecond) -} - -func TestWindowPostDispute(t *testing.T) { - if os.Getenv("LOTUS_TEST_WINDOW_POST") != "1" { - t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") - } - logging.SetLogLevel("miner", "ERROR") - logging.SetLogLevel("chainstore", "ERROR") - logging.SetLogLevel("chain", "ERROR") - logging.SetLogLevel("sub", "ERROR") - logging.SetLogLevel("storageminer", "ERROR") - - test.TestWindowPostDispute(t, builder.MockSbBuilder, 2*time.Millisecond) -} - -func TestWindowPostDisputeFails(t *testing.T) { - if os.Getenv("LOTUS_TEST_WINDOW_POST") != "1" { - t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") - } - logging.SetLogLevel("miner", "ERROR") - logging.SetLogLevel("chainstore", "ERROR") - logging.SetLogLevel("chain", "ERROR") - logging.SetLogLevel("sub", "ERROR") - logging.SetLogLevel("storageminer", "ERROR") - - test.TestWindowPostDisputeFails(t, builder.MockSbBuilder, 2*time.Millisecond) -} - -func TestDeadlineToggling(t *testing.T) { - if os.Getenv("LOTUS_TEST_DEADLINE_TOGGLING") != "1" { - t.Skip("this takes a few minutes, set LOTUS_TEST_DEADLINE_TOGGLING=1 to run") - } - logging.SetLogLevel("miner", "ERROR") - logging.SetLogLevel("chainstore", "ERROR") - logging.SetLogLevel("chain", "ERROR") - logging.SetLogLevel("sub", "ERROR") - logging.SetLogLevel("storageminer", "FATAL") - - test.TestDeadlineToggling(t, builder.MockSbBuilder, 2*time.Millisecond) -} diff --git a/itests/paych.go b/itests/paych_api_test.go similarity index 97% rename from itests/paych.go rename to itests/paych_api_test.go index 86b4063ea..7b3d7cf3e 100644 --- a/itests/paych.go +++ b/itests/paych_api_test.go @@ -26,9 +26,11 @@ import ( "github.com/filecoin-project/lotus/chain/types" ) -func TestPaymentChannels(t *testing.T, b APIBuilder, blocktime time.Duration) { +func TestPaymentChannelsAPI(t *testing.T) { + QuietMiningLogs() + ctx := context.Background() - n, sn := b(t, TwoFull, OneMiner) + n, sn := MockSbBuilder(t, TwoFull, OneMiner) paymentCreator := n[0] paymentReceiver := n[1] @@ -49,7 +51,7 @@ func TestPaymentChannels(t *testing.T, b APIBuilder, blocktime time.Duration) { } // start mining blocks - bm := NewBlockMiner(ctx, t, miner, blocktime) + bm := NewBlockMiner(ctx, t, miner, 5*time.Millisecond) bm.MineBlocks() // send some funds to register the receiver @@ -173,7 +175,7 @@ func TestPaymentChannels(t *testing.T, b APIBuilder, blocktime time.Duration) { select { case <-finished: - case <-time.After(time.Second): + case <-time.After(10 * time.Second): t.Fatal("Timed out waiting for receiver to submit vouchers") } diff --git a/itests/paych_test.go b/itests/paych_cli_test.go similarity index 94% rename from itests/paych_test.go rename to itests/paych_cli_test.go index 6c7a081fe..f7541a78e 100644 --- a/itests/paych_test.go +++ b/itests/paych_cli_test.go @@ -11,7 +11,6 @@ import ( "time" "github.com/filecoin-project/lotus/cli" - clitest "github.com/filecoin-project/lotus/cli/test" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" @@ -21,7 +20,6 @@ import ( cbor "github.com/ipfs/go-ipld-cbor" "github.com/stretchr/testify/require" - "github.com/filecoin-project/lotus/api/test" "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/events" @@ -36,7 +34,7 @@ func init() { // TestPaymentChannels does a basic test to exercise the payment channel CLI // commands -func TestPaymentChannels(t *testing.T) { +func TestPaymentChannelsBasic(t *testing.T) { _ = os.Setenv("BELLMAN_NO_GPU", "1") QuietMiningLogs() @@ -90,17 +88,17 @@ type voucherSpec struct { // TestPaymentChannelStatus tests the payment channel status CLI command func TestPaymentChannelStatus(t *testing.T) { _ = os.Setenv("BELLMAN_NO_GPU", "1") - clitest.QuietMiningLogs() + QuietMiningLogs() blocktime := 5 * time.Millisecond ctx := context.Background() - nodes, addrs := clitest.StartTwoNodesOneMiner(ctx, t, blocktime) + nodes, addrs := StartTwoNodesOneMiner(ctx, t, blocktime) paymentCreator := nodes[0] creatorAddr := addrs[0] receiverAddr := addrs[1] // Create mock CLI - mockCLI := clitest.NewMockCLI(ctx, t, Commands) + mockCLI := NewMockCLI(ctx, t, cli.Commands) creatorCLI := mockCLI.Client(paymentCreator.ListenAddr) // creator: paych status-by-from-to @@ -169,18 +167,18 @@ func TestPaymentChannelStatus(t *testing.T) { // channel voucher commands func TestPaymentChannelVouchers(t *testing.T) { _ = os.Setenv("BELLMAN_NO_GPU", "1") - clitest.QuietMiningLogs() + QuietMiningLogs() blocktime := 5 * time.Millisecond ctx := context.Background() - nodes, addrs := clitest.StartTwoNodesOneMiner(ctx, t, blocktime) + nodes, addrs := StartTwoNodesOneMiner(ctx, t, blocktime) paymentCreator := nodes[0] paymentReceiver := nodes[1] creatorAddr := addrs[0] receiverAddr := addrs[1] // Create mock CLI - mockCLI := clitest.NewMockCLI(ctx, t, Commands) + mockCLI := NewMockCLI(ctx, t, cli.Commands) creatorCLI := mockCLI.Client(paymentCreator.ListenAddr) receiverCLI := mockCLI.Client(paymentReceiver.ListenAddr) @@ -301,17 +299,17 @@ func TestPaymentChannelVouchers(t *testing.T) { // is greater than what's left in the channel, voucher create fails func TestPaymentChannelVoucherCreateShortfall(t *testing.T) { _ = os.Setenv("BELLMAN_NO_GPU", "1") - clitest.QuietMiningLogs() + QuietMiningLogs() blocktime := 5 * time.Millisecond ctx := context.Background() - nodes, addrs := clitest.StartTwoNodesOneMiner(ctx, t, blocktime) + nodes, addrs := StartTwoNodesOneMiner(ctx, t, blocktime) paymentCreator := nodes[0] creatorAddr := addrs[0] receiverAddr := addrs[1] // Create mock CLI - mockCLI := clitest.NewMockCLI(ctx, t, Commands) + mockCLI := NewMockCLI(ctx, t, cli.Commands) creatorCLI := mockCLI.Client(paymentCreator.ListenAddr) // creator: paych add-funds @@ -379,7 +377,7 @@ func checkVoucherOutput(t *testing.T, list string, vouchers []voucherSpec) { } // waitForHeight waits for the node to reach the given chain epoch -func waitForHeight(ctx context.Context, t *testing.T, node test.TestNode, height abi.ChainEpoch) { +func waitForHeight(ctx context.Context, t *testing.T, node TestNode, height abi.ChainEpoch) { atHeight := make(chan struct{}) chainEvents := events.NewEvents(ctx, node) err := chainEvents.ChainAt(func(ctx context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { @@ -397,7 +395,7 @@ func waitForHeight(ctx context.Context, t *testing.T, node test.TestNode, height } // getPaychState gets the state of the payment channel with the given address -func getPaychState(ctx context.Context, t *testing.T, node test.TestNode, chAddr address.Address) paych.State { +func getPaychState(ctx context.Context, t *testing.T, node TestNode, chAddr address.Address) paych.State { act, err := node.StateGetActor(ctx, chAddr, types.EmptyTSK) require.NoError(t, err) diff --git a/itests/sdr_upgrade_test.go b/itests/sdr_upgrade_test.go new file mode 100644 index 000000000..c99ff92b8 --- /dev/null +++ b/itests/sdr_upgrade_test.go @@ -0,0 +1,112 @@ +package itests + +import ( + "context" + "sort" + "sync/atomic" + "testing" + "time" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/policy" + bminer "github.com/filecoin-project/lotus/miner" + "github.com/filecoin-project/lotus/node/impl" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSDRUpgrade(t *testing.T) { + QuietMiningLogs() + + oldDelay := policy.GetPreCommitChallengeDelay() + policy.SetPreCommitChallengeDelay(5) + t.Cleanup(func() { + policy.SetPreCommitChallengeDelay(oldDelay) + }) + + blocktime := 50 * time.Millisecond + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + n, sn := MockSbBuilder(t, []FullNodeOpts{FullNodeWithSDRAt(500, 1000)}, OneMiner) + client := n[0].FullNode.(*impl.FullNodeAPI) + miner := sn[0] + + addrinfo, err := client.NetAddrsListen(ctx) + if err != nil { + t.Fatal(err) + } + + if err := miner.NetConnect(ctx, addrinfo); err != nil { + t.Fatal(err) + } + build.Clock.Sleep(time.Second) + + pledge := make(chan struct{}) + mine := int64(1) + done := make(chan struct{}) + go func() { + defer close(done) + round := 0 + for atomic.LoadInt64(&mine) != 0 { + build.Clock.Sleep(blocktime) + if err := sn[0].MineOne(ctx, bminer.MineReq{Done: func(bool, abi.ChainEpoch, error) { + + }}); err != nil { + t.Error(err) + } + + // 3 sealing rounds: before, during after. + if round >= 3 { + continue + } + + head, err := client.ChainHead(ctx) + assert.NoError(t, err) + + // rounds happen every 100 blocks, with a 50 block offset. + if head.Height() >= abi.ChainEpoch(round*500+50) { + round++ + pledge <- struct{}{} + + ver, err := client.StateNetworkVersion(ctx, head.Key()) + assert.NoError(t, err) + switch round { + case 1: + assert.Equal(t, network.Version6, ver) + case 2: + assert.Equal(t, network.Version7, ver) + case 3: + assert.Equal(t, network.Version8, ver) + } + } + + } + }() + + // before. + pledgeSectors(t, ctx, miner, 9, 0, pledge) + + s, err := miner.SectorsList(ctx) + require.NoError(t, err) + sort.Slice(s, func(i, j int) bool { + return s[i] < s[j] + }) + + for i, id := range s { + info, err := miner.SectorsStatus(ctx, id, true) + require.NoError(t, err) + expectProof := abi.RegisteredSealProof_StackedDrg2KiBV1 + if i >= 3 { + // after + expectProof = abi.RegisteredSealProof_StackedDrg2KiBV1_1 + } + assert.Equal(t, expectProof, info.SealProof, "sector %d, id %d", i, id) + } + + atomic.StoreInt64(&mine, 0) + <-done +} diff --git a/itests/sector_pledge_test.go b/itests/sector_pledge_test.go new file mode 100644 index 000000000..c3de173dd --- /dev/null +++ b/itests/sector_pledge_test.go @@ -0,0 +1,71 @@ +package itests + +import ( + "context" + "sync/atomic" + "testing" + "time" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/build" + bminer "github.com/filecoin-project/lotus/miner" + "github.com/filecoin-project/lotus/node/impl" +) + +func TestPledgeSectors(t *testing.T) { + QuietMiningLogs() + + t.Run("1", func(t *testing.T) { + runPledgeSectorTest(t, MockSbBuilder, 50*time.Millisecond, 1) + }) + + t.Run("100", func(t *testing.T) { + runPledgeSectorTest(t, MockSbBuilder, 50*time.Millisecond, 100) + }) + + t.Run("1000", func(t *testing.T) { + if testing.Short() { // takes ~16s + t.Skip("skipping test in short mode") + } + + runPledgeSectorTest(t, MockSbBuilder, 50*time.Millisecond, 1000) + }) +} + +func runPledgeSectorTest(t *testing.T, b APIBuilder, blocktime time.Duration, nSectors int) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + n, sn := b(t, OneFull, OneMiner) + client := n[0].FullNode.(*impl.FullNodeAPI) + miner := sn[0] + + addrinfo, err := client.NetAddrsListen(ctx) + if err != nil { + t.Fatal(err) + } + + if err := miner.NetConnect(ctx, addrinfo); err != nil { + t.Fatal(err) + } + build.Clock.Sleep(time.Second) + + mine := int64(1) + done := make(chan struct{}) + go func() { + defer close(done) + for atomic.LoadInt64(&mine) != 0 { + build.Clock.Sleep(blocktime) + if err := sn[0].MineOne(ctx, bminer.MineReq{Done: func(bool, abi.ChainEpoch, error) { + + }}); err != nil { + t.Error(err) + } + } + }() + + pledgeSectors(t, ctx, miner, nSectors, 0, nil) + + atomic.StoreInt64(&mine, 0) + <-done +} diff --git a/itests/sector_terminate_test.go b/itests/sector_terminate_test.go new file mode 100644 index 000000000..90627be85 --- /dev/null +++ b/itests/sector_terminate_test.go @@ -0,0 +1,200 @@ +package itests + +import ( + "context" + "fmt" + "os" + "testing" + "time" + + "github.com/filecoin-project/go-bitfield" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" + sealing "github.com/filecoin-project/lotus/extern/storage-sealing" + "github.com/filecoin-project/lotus/node/impl" + "github.com/stretchr/testify/require" +) + +func TestTerminate(t *testing.T) { + if os.Getenv("LOTUS_TEST_WINDOW_POST") != "1" { + t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") + } + + QuietMiningLogs() + + const blocktime = 2 * time.Millisecond + + nSectors := uint64(2) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + n, sn := MockSbBuilder(t, + []FullNodeOpts{FullNodeWithLatestActorsAt(-1)}, + []StorageMiner{{Full: 0, Preseal: int(nSectors)}}, + ) + + client := n[0].FullNode.(*impl.FullNodeAPI) + miner := sn[0] + + addrinfo, err := client.NetAddrsListen(ctx) + if err != nil { + t.Fatal(err) + } + + if err := miner.NetConnect(ctx, addrinfo); err != nil { + t.Fatal(err) + } + build.Clock.Sleep(time.Second) + + done := make(chan struct{}) + go func() { + defer close(done) + for ctx.Err() == nil { + build.Clock.Sleep(blocktime) + if err := sn[0].MineOne(ctx, MineNext); err != nil { + if ctx.Err() != nil { + // context was canceled, ignore the error. + return + } + t.Error(err) + } + } + }() + defer func() { + cancel() + <-done + }() + + maddr, err := miner.ActorAddress(ctx) + require.NoError(t, err) + + ssz, err := miner.ActorSectorSize(ctx, maddr) + require.NoError(t, err) + + p, err := client.StateMinerPower(ctx, maddr, types.EmptyTSK) + require.NoError(t, err) + require.Equal(t, p.MinerPower, p.TotalPower) + require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*nSectors)) + + fmt.Printf("Seal a sector\n") + + pledgeSectors(t, ctx, miner, 1, 0, nil) + + fmt.Printf("wait for power\n") + + { + // Wait until proven. + di, err := client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) + require.NoError(t, err) + + waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 2 + fmt.Printf("End for head.Height > %d\n", waitUntil) + + for { + head, err := client.ChainHead(ctx) + require.NoError(t, err) + + if head.Height() > waitUntil { + fmt.Printf("Now head.Height = %d\n", head.Height()) + break + } + } + } + + nSectors++ + + p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) + require.NoError(t, err) + require.Equal(t, p.MinerPower, p.TotalPower) + require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*nSectors)) + + fmt.Println("Terminate a sector") + + toTerminate := abi.SectorNumber(3) + + err = miner.SectorTerminate(ctx, toTerminate) + require.NoError(t, err) + + msgTriggerred := false +loop: + for { + si, err := miner.SectorsStatus(ctx, toTerminate, false) + require.NoError(t, err) + + fmt.Println("state: ", si.State, msgTriggerred) + + switch sealing.SectorState(si.State) { + case sealing.Terminating: + if !msgTriggerred { + { + p, err := miner.SectorTerminatePending(ctx) + require.NoError(t, err) + require.Len(t, p, 1) + require.Equal(t, abi.SectorNumber(3), p[0].Number) + } + + c, err := miner.SectorTerminateFlush(ctx) + require.NoError(t, err) + if c != nil { + msgTriggerred = true + fmt.Println("terminate message:", c) + + { + p, err := miner.SectorTerminatePending(ctx) + require.NoError(t, err) + require.Len(t, p, 0) + } + } + } + case sealing.TerminateWait, sealing.TerminateFinality, sealing.Removed: + break loop + } + + time.Sleep(100 * time.Millisecond) + } + + // check power decreased + p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) + require.NoError(t, err) + require.Equal(t, p.MinerPower, p.TotalPower) + require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*(nSectors-1))) + + // check in terminated set + { + parts, err := client.StateMinerPartitions(ctx, maddr, 1, types.EmptyTSK) + require.NoError(t, err) + require.Greater(t, len(parts), 0) + + bflen := func(b bitfield.BitField) uint64 { + l, err := b.Count() + require.NoError(t, err) + return l + } + + require.Equal(t, uint64(1), bflen(parts[0].AllSectors)) + require.Equal(t, uint64(0), bflen(parts[0].LiveSectors)) + } + + di, err := client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) + require.NoError(t, err) + for { + head, err := client.ChainHead(ctx) + require.NoError(t, err) + + if head.Height() > di.PeriodStart+di.WPoStProvingPeriod+2 { + fmt.Printf("Now head.Height = %d\n", head.Height()) + break + } + build.Clock.Sleep(blocktime) + } + require.NoError(t, err) + fmt.Printf("End for head.Height > %d\n", di.PeriodStart+di.WPoStProvingPeriod+2) + + p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) + require.NoError(t, err) + + require.Equal(t, p.MinerPower, p.TotalPower) + require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*(nSectors-1))) +} diff --git a/itests/test.go b/itests/t.go similarity index 55% rename from itests/test.go rename to itests/t.go index 2664bc626..d90f398b2 100644 --- a/itests/test.go +++ b/itests/t.go @@ -4,26 +4,19 @@ import ( "context" "fmt" "os" - "strings" "testing" - "time" logging "github.com/ipfs/go-log/v2" "github.com/multiformats/go-multiaddr" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/network" lapi "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api/v1api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/stmgr" - "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/miner" "github.com/filecoin-project/lotus/node" ) @@ -90,21 +83,6 @@ type testSuite struct { makeNodes APIBuilder } -// TestApis is the entry point to API test suite -func TestApis(t *testing.T, b APIBuilder) { - ts := testSuite{ - makeNodes: b, - } - - t.Run("version", ts.testVersion) - t.Run("id", ts.testID) - t.Run("testConnectTwo", ts.testConnectTwo) - t.Run("testMining", ts.testMining) - t.Run("testMiningReal", ts.testMiningReal) - t.Run("testSearchMsg", ts.testSearchMsg) - t.Run("testNonGenesisMiner", ts.testNonGenesisMiner) -} - func DefaultFullOpts(nFull int) []FullNodeOpts { full := make([]FullNodeOpts, nFull) for i := range full { @@ -169,125 +147,3 @@ var MineNext = miner.MineReq{ InjectNulls: 0, Done: func(bool, abi.ChainEpoch, error) {}, } - -func (ts *testSuite) testVersion(t *testing.T) { - lapi.RunningNodeType = lapi.NodeFull - t.Cleanup(func() { - lapi.RunningNodeType = lapi.NodeUnknown - }) - - ctx := context.Background() - apis, _ := ts.makeNodes(t, OneFull, OneMiner) - napi := apis[0] - - v, err := napi.Version(ctx) - if err != nil { - t.Fatal(err) - } - versions := strings.Split(v.Version, "+") - if len(versions) <= 0 { - t.Fatal("empty version") - } - require.Equal(t, versions[0], build.BuildVersion) -} - -func (ts *testSuite) testSearchMsg(t *testing.T) { - apis, miners := ts.makeNodes(t, OneFull, OneMiner) - - api := apis[0] - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - senderAddr, err := api.WalletDefaultAddress(ctx) - if err != nil { - t.Fatal(err) - } - - msg := &types.Message{ - From: senderAddr, - To: senderAddr, - Value: big.Zero(), - } - bm := NewBlockMiner(ctx, t, miners[0], 100*time.Millisecond) - bm.MineBlocks() - defer bm.Stop() - - sm, err := api.MpoolPushMessage(ctx, msg, nil) - if err != nil { - t.Fatal(err) - } - res, err := api.StateWaitMsg(ctx, sm.Cid(), 1, lapi.LookbackNoLimit, true) - if err != nil { - t.Fatal(err) - } - if res.Receipt.ExitCode != 0 { - t.Fatal("did not successfully send message") - } - - searchRes, err := api.StateSearchMsg(ctx, types.EmptyTSK, sm.Cid(), lapi.LookbackNoLimit, true) - if err != nil { - t.Fatal(err) - } - - if searchRes.TipSet != res.TipSet { - t.Fatalf("search ts: %s, different from wait ts: %s", searchRes.TipSet, res.TipSet) - } - -} - -func (ts *testSuite) testID(t *testing.T) { - ctx := context.Background() - apis, _ := ts.makeNodes(t, OneFull, OneMiner) - api := apis[0] - - id, err := api.ID(ctx) - if err != nil { - t.Fatal(err) - } - assert.Regexp(t, "^12", id.Pretty()) -} - -func (ts *testSuite) testConnectTwo(t *testing.T) { - ctx := context.Background() - apis, _ := ts.makeNodes(t, TwoFull, OneMiner) - - p, err := apis[0].NetPeers(ctx) - if err != nil { - t.Fatal(err) - } - if len(p) != 0 { - t.Error("Node 0 has a peer") - } - - p, err = apis[1].NetPeers(ctx) - if err != nil { - t.Fatal(err) - } - if len(p) != 0 { - t.Error("Node 1 has a peer") - } - - addrs, err := apis[1].NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := apis[0].NetConnect(ctx, addrs); err != nil { - t.Fatal(err) - } - - p, err = apis[0].NetPeers(ctx) - if err != nil { - t.Fatal(err) - } - if len(p) != 1 { - t.Error("Node 0 doesn't have 1 peer") - } - - p, err = apis[1].NetPeers(ctx) - if err != nil { - t.Fatal(err) - } - if len(p) != 1 { - t.Error("Node 0 doesn't have 1 peer") - } -} diff --git a/itests/tape.go b/itests/tape_test.go similarity index 90% rename from itests/tape.go rename to itests/tape_test.go index 44cc20c68..16cab8270 100644 --- a/itests/tape.go +++ b/itests/tape_test.go @@ -16,12 +16,17 @@ import ( "github.com/stretchr/testify/require" ) -func TestTapeFix(t *testing.T, b APIBuilder, blocktime time.Duration) { +func TestTapeFix(t *testing.T) { + QuietMiningLogs() + + var blocktime = 2 * time.Millisecond + // The "before" case is disabled, because we need the builder to mock 32 GiB sectors to accurately repro this case // TODO: Make the mock sector size configurable and reenable this - //t.Run("before", func(t *testing.T) { testTapeFix(t, b, blocktime, false) }) - t.Run("after", func(t *testing.T) { testTapeFix(t, b, blocktime, true) }) + // t.Run("before", func(t *testing.T) { testTapeFix(t, b, blocktime, false) }) + t.Run("after", func(t *testing.T) { testTapeFix(t, MockSbBuilder, blocktime, true) }) } + func testTapeFix(t *testing.T, b APIBuilder, blocktime time.Duration, after bool) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -97,5 +102,4 @@ func testTapeFix(t *testing.T, b APIBuilder, blocktime time.Duration, after bool build.Clock.Sleep(100 * time.Millisecond) fmt.Println("WaitSeal") } - } diff --git a/itests/util.go b/itests/util.go deleted file mode 100644 index 9fbc3e395..000000000 --- a/itests/util.go +++ /dev/null @@ -1,14 +0,0 @@ -package itests - -import "github.com/ipfs/go-log/v2" - -func QuietMiningLogs() { - _ = log.SetLogLevel("miner", "ERROR") - _ = log.SetLogLevel("chainstore", "ERROR") - _ = log.SetLogLevel("chain", "ERROR") - _ = log.SetLogLevel("sub", "ERROR") - _ = log.SetLogLevel("storageminer", "ERROR") - _ = log.SetLogLevel("pubsub", "ERROR") - _ = log.SetLogLevel("gen", "ERROR") - _ = log.SetLogLevel("dht/RtRefreshManager", "ERROR") -} diff --git a/itests/wdpost_dispute_test.go b/itests/wdpost_dispute_test.go new file mode 100644 index 000000000..a6528b3b6 --- /dev/null +++ b/itests/wdpost_dispute_test.go @@ -0,0 +1,457 @@ +package itests + +import ( + "context" + "fmt" + "os" + "testing" + "time" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-bitfield" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/dline" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors" + minerActor "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/node/impl" + proof3 "github.com/filecoin-project/specs-actors/v3/actors/runtime/proof" + "github.com/stretchr/testify/require" +) + +func TestWindowPostDispute(t *testing.T) { + if os.Getenv("LOTUS_TEST_WINDOW_POST") != "1" { + t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") + } + + QuietMiningLogs() + + b := MockSbBuilder + blocktime := 2 * time.Millisecond + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // First, we configure two miners. After sealing, we're going to turn off the first miner so + // it doesn't submit proofs. + // + // Then we're going to manually submit bad proofs. + n, sn := b(t, []FullNodeOpts{ + FullNodeWithLatestActorsAt(-1), + }, []StorageMiner{ + {Full: 0, Preseal: PresealGenesis}, + {Full: 0}, + }) + + client := n[0].FullNode.(*impl.FullNodeAPI) + chainMiner := sn[0] + evilMiner := sn[1] + + { + addrinfo, err := client.NetAddrsListen(ctx) + if err != nil { + t.Fatal(err) + } + + if err := chainMiner.NetConnect(ctx, addrinfo); err != nil { + t.Fatal(err) + } + + if err := evilMiner.NetConnect(ctx, addrinfo); err != nil { + t.Fatal(err) + } + } + + defaultFrom, err := client.WalletDefaultAddress(ctx) + require.NoError(t, err) + + build.Clock.Sleep(time.Second) + + // Mine with the _second_ node (the good one). + done := make(chan struct{}) + go func() { + defer close(done) + for ctx.Err() == nil { + build.Clock.Sleep(blocktime) + if err := chainMiner.MineOne(ctx, MineNext); err != nil { + if ctx.Err() != nil { + // context was canceled, ignore the error. + return + } + t.Error(err) + } + } + }() + defer func() { + cancel() + <-done + }() + + // Give the chain miner enough sectors to win every block. + pledgeSectors(t, ctx, chainMiner, 10, 0, nil) + // And the evil one 1 sector. No cookie for you. + pledgeSectors(t, ctx, evilMiner, 1, 0, nil) + + // Let the evil miner's sectors gain power. + evilMinerAddr, err := evilMiner.ActorAddress(ctx) + require.NoError(t, err) + + di, err := client.StateMinerProvingDeadline(ctx, evilMinerAddr, types.EmptyTSK) + require.NoError(t, err) + + fmt.Printf("Running one proving period\n") + fmt.Printf("End for head.Height > %d\n", di.PeriodStart+di.WPoStProvingPeriod*2) + + for { + head, err := client.ChainHead(ctx) + require.NoError(t, err) + + if head.Height() > di.PeriodStart+di.WPoStProvingPeriod*2 { + fmt.Printf("Now head.Height = %d\n", head.Height()) + break + } + build.Clock.Sleep(blocktime) + } + + p, err := client.StateMinerPower(ctx, evilMinerAddr, types.EmptyTSK) + require.NoError(t, err) + + ssz, err := evilMiner.ActorSectorSize(ctx, evilMinerAddr) + require.NoError(t, err) + + // make sure it has gained power. + require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz))) + + evilSectors, err := evilMiner.SectorsList(ctx) + require.NoError(t, err) + evilSectorNo := evilSectors[0] // only one. + evilSectorLoc, err := client.StateSectorPartition(ctx, evilMinerAddr, evilSectorNo, types.EmptyTSK) + require.NoError(t, err) + + fmt.Println("evil miner stopping") + + // Now stop the evil miner, and start manually submitting bad proofs. + require.NoError(t, evilMiner.Stop(ctx)) + + fmt.Println("evil miner stopped") + + // Wait until we need to prove our sector. + for { + di, err = client.StateMinerProvingDeadline(ctx, evilMinerAddr, types.EmptyTSK) + require.NoError(t, err) + if di.Index == evilSectorLoc.Deadline { + break + } + build.Clock.Sleep(blocktime) + } + + err = submitBadProof(ctx, client, evilMinerAddr, di, evilSectorLoc.Deadline, evilSectorLoc.Partition) + require.NoError(t, err, "evil proof not accepted") + + // Wait until after the proving period. + for { + di, err = client.StateMinerProvingDeadline(ctx, evilMinerAddr, types.EmptyTSK) + require.NoError(t, err) + if di.Index != evilSectorLoc.Deadline { + break + } + build.Clock.Sleep(blocktime) + } + + fmt.Println("accepted evil proof") + + // Make sure the evil node didn't lose any power. + p, err = client.StateMinerPower(ctx, evilMinerAddr, types.EmptyTSK) + require.NoError(t, err) + require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz))) + + // OBJECTION! The good miner files a DISPUTE!!!! + { + params := &minerActor.DisputeWindowedPoStParams{ + Deadline: evilSectorLoc.Deadline, + PoStIndex: 0, + } + + enc, aerr := actors.SerializeParams(params) + require.NoError(t, aerr) + + msg := &types.Message{ + To: evilMinerAddr, + Method: minerActor.Methods.DisputeWindowedPoSt, + Params: enc, + Value: types.NewInt(0), + From: defaultFrom, + } + sm, err := client.MpoolPushMessage(ctx, msg, nil) + require.NoError(t, err) + + fmt.Println("waiting dispute") + rec, err := client.StateWaitMsg(ctx, sm.Cid(), build.MessageConfidence, api.LookbackNoLimit, true) + require.NoError(t, err) + require.Zero(t, rec.Receipt.ExitCode, "dispute not accepted: %s", rec.Receipt.ExitCode.Error()) + } + + // Objection SUSTAINED! + // Make sure the evil node lost power. + p, err = client.StateMinerPower(ctx, evilMinerAddr, types.EmptyTSK) + require.NoError(t, err) + require.True(t, p.MinerPower.RawBytePower.IsZero()) + + // Now we begin the redemption arc. + require.True(t, p.MinerPower.RawBytePower.IsZero()) + + // First, recover the sector. + + { + minerInfo, err := client.StateMinerInfo(ctx, evilMinerAddr, types.EmptyTSK) + require.NoError(t, err) + + params := &minerActor.DeclareFaultsRecoveredParams{ + Recoveries: []minerActor.RecoveryDeclaration{{ + Deadline: evilSectorLoc.Deadline, + Partition: evilSectorLoc.Partition, + Sectors: bitfield.NewFromSet([]uint64{uint64(evilSectorNo)}), + }}, + } + + enc, aerr := actors.SerializeParams(params) + require.NoError(t, aerr) + + msg := &types.Message{ + To: evilMinerAddr, + Method: minerActor.Methods.DeclareFaultsRecovered, + Params: enc, + Value: types.FromFil(30), // repay debt. + From: minerInfo.Owner, + } + sm, err := client.MpoolPushMessage(ctx, msg, nil) + require.NoError(t, err) + + rec, err := client.StateWaitMsg(ctx, sm.Cid(), build.MessageConfidence, api.LookbackNoLimit, true) + require.NoError(t, err) + require.Zero(t, rec.Receipt.ExitCode, "recovery not accepted: %s", rec.Receipt.ExitCode.Error()) + } + + // Then wait for the deadline. + for { + di, err = client.StateMinerProvingDeadline(ctx, evilMinerAddr, types.EmptyTSK) + require.NoError(t, err) + if di.Index == evilSectorLoc.Deadline { + break + } + build.Clock.Sleep(blocktime) + } + + // Now try to be evil again + err = submitBadProof(ctx, client, evilMinerAddr, di, evilSectorLoc.Deadline, evilSectorLoc.Partition) + require.Error(t, err) + require.Contains(t, err.Error(), "message execution failed: exit 16, reason: window post failed: invalid PoSt") + + // It didn't work because we're recovering. +} + +func TestWindowPostDisputeFails(t *testing.T) { + if os.Getenv("LOTUS_TEST_WINDOW_POST") != "1" { + t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") + } + + QuietMiningLogs() + + b := MockSbBuilder + blocktime := 2 * time.Millisecond + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + n, sn := b(t, []FullNodeOpts{FullNodeWithLatestActorsAt(-1)}, OneMiner) + + client := n[0].FullNode.(*impl.FullNodeAPI) + miner := sn[0] + + { + addrinfo, err := client.NetAddrsListen(ctx) + if err != nil { + t.Fatal(err) + } + + if err := miner.NetConnect(ctx, addrinfo); err != nil { + t.Fatal(err) + } + } + + defaultFrom, err := client.WalletDefaultAddress(ctx) + require.NoError(t, err) + + maddr, err := miner.ActorAddress(ctx) + require.NoError(t, err) + + build.Clock.Sleep(time.Second) + + // Mine with the _second_ node (the good one). + done := make(chan struct{}) + go func() { + defer close(done) + for ctx.Err() == nil { + build.Clock.Sleep(blocktime) + if err := miner.MineOne(ctx, MineNext); err != nil { + if ctx.Err() != nil { + // context was canceled, ignore the error. + return + } + t.Error(err) + } + } + }() + defer func() { + cancel() + <-done + }() + + pledgeSectors(t, ctx, miner, 10, 0, nil) + + di, err := client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) + require.NoError(t, err) + + fmt.Printf("Running one proving period\n") + fmt.Printf("End for head.Height > %d\n", di.PeriodStart+di.WPoStProvingPeriod*2) + + for { + head, err := client.ChainHead(ctx) + require.NoError(t, err) + + if head.Height() > di.PeriodStart+di.WPoStProvingPeriod*2 { + fmt.Printf("Now head.Height = %d\n", head.Height()) + break + } + build.Clock.Sleep(blocktime) + } + + ssz, err := miner.ActorSectorSize(ctx, maddr) + require.NoError(t, err) + expectedPower := types.NewInt(uint64(ssz) * (GenesisPreseals + 10)) + + p, err := client.StateMinerPower(ctx, maddr, types.EmptyTSK) + require.NoError(t, err) + + // make sure it has gained power. + require.Equal(t, p.MinerPower.RawBytePower, expectedPower) + + // Wait until a proof has been submitted. + var targetDeadline uint64 +waitForProof: + for { + deadlines, err := client.StateMinerDeadlines(ctx, maddr, types.EmptyTSK) + require.NoError(t, err) + for dlIdx, dl := range deadlines { + nonEmpty, err := dl.PostSubmissions.IsEmpty() + require.NoError(t, err) + if nonEmpty { + targetDeadline = uint64(dlIdx) + break waitForProof + } + } + + build.Clock.Sleep(blocktime) + } + + for { + di, err := client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) + require.NoError(t, err) + // wait until the deadline finishes. + if di.Index == ((targetDeadline + 1) % di.WPoStPeriodDeadlines) { + break + } + + build.Clock.Sleep(blocktime) + } + + // Try to object to the proof. This should fail. + { + params := &minerActor.DisputeWindowedPoStParams{ + Deadline: targetDeadline, + PoStIndex: 0, + } + + enc, aerr := actors.SerializeParams(params) + require.NoError(t, aerr) + + msg := &types.Message{ + To: maddr, + Method: minerActor.Methods.DisputeWindowedPoSt, + Params: enc, + Value: types.NewInt(0), + From: defaultFrom, + } + _, err := client.MpoolPushMessage(ctx, msg, nil) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to dispute valid post (RetCode=16)") + } +} + +func submitBadProof( + ctx context.Context, + client api.FullNode, maddr address.Address, + di *dline.Info, dlIdx, partIdx uint64, +) error { + head, err := client.ChainHead(ctx) + if err != nil { + return err + } + + from, err := client.WalletDefaultAddress(ctx) + if err != nil { + return err + } + + minerInfo, err := client.StateMinerInfo(ctx, maddr, head.Key()) + if err != nil { + return err + } + + commEpoch := di.Open + commRand, err := client.ChainGetRandomnessFromTickets( + ctx, head.Key(), crypto.DomainSeparationTag_PoStChainCommit, + commEpoch, nil, + ) + if err != nil { + return err + } + params := &minerActor.SubmitWindowedPoStParams{ + ChainCommitEpoch: commEpoch, + ChainCommitRand: commRand, + Deadline: dlIdx, + Partitions: []minerActor.PoStPartition{{Index: partIdx}}, + Proofs: []proof3.PoStProof{{ + PoStProof: minerInfo.WindowPoStProofType, + ProofBytes: []byte("I'm soooo very evil."), + }}, + } + + enc, aerr := actors.SerializeParams(params) + if aerr != nil { + return aerr + } + + msg := &types.Message{ + To: maddr, + Method: minerActor.Methods.SubmitWindowedPoSt, + Params: enc, + Value: types.NewInt(0), + From: from, + } + sm, err := client.MpoolPushMessage(ctx, msg, nil) + if err != nil { + return err + } + + rec, err := client.StateWaitMsg(ctx, sm.Cid(), build.MessageConfidence, api.LookbackNoLimit, true) + if err != nil { + return err + } + if rec.Receipt.ExitCode.IsError() { + return rec.Receipt.ExitCode + } + return nil +} diff --git a/itests/wdpost_test.go b/itests/wdpost_test.go new file mode 100644 index 000000000..c30ad812f --- /dev/null +++ b/itests/wdpost_test.go @@ -0,0 +1,261 @@ +package itests + +import ( + "context" + "fmt" + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/extern/sector-storage/mock" + "github.com/filecoin-project/specs-storage/storage" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/node/impl" +) + +func TestWindowedPost(t *testing.T) { + if os.Getenv("LOTUS_TEST_WINDOW_POST") != "1" { + t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") + } + + QuietMiningLogs() + + var ( + blocktime = 2 * time.Millisecond + nSectors = 10 + ) + + for _, height := range []abi.ChainEpoch{ + -1, // before + 162, // while sealing + 5000, // while proving + } { + height := height // copy to satisfy lints + t.Run(fmt.Sprintf("upgrade-%d", height), func(t *testing.T) { + testWindowPostUpgrade(t, MockSbBuilder, blocktime, nSectors, height) + }) + } +} + +func testWindowPostUpgrade(t *testing.T, b APIBuilder, blocktime time.Duration, nSectors int, upgradeHeight abi.ChainEpoch) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + n, sn := b(t, []FullNodeOpts{FullNodeWithLatestActorsAt(upgradeHeight)}, OneMiner) + + client := n[0].FullNode.(*impl.FullNodeAPI) + miner := sn[0] + + addrinfo, err := client.NetAddrsListen(ctx) + if err != nil { + t.Fatal(err) + } + + if err := miner.NetConnect(ctx, addrinfo); err != nil { + t.Fatal(err) + } + build.Clock.Sleep(time.Second) + + done := make(chan struct{}) + go func() { + defer close(done) + for ctx.Err() == nil { + build.Clock.Sleep(blocktime) + if err := sn[0].MineOne(ctx, MineNext); err != nil { + if ctx.Err() != nil { + // context was canceled, ignore the error. + return + } + t.Error(err) + } + } + }() + defer func() { + cancel() + <-done + }() + + pledgeSectors(t, ctx, miner, nSectors, 0, nil) + + maddr, err := miner.ActorAddress(ctx) + require.NoError(t, err) + + di, err := client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) + require.NoError(t, err) + + mid, err := address.IDFromAddress(maddr) + require.NoError(t, err) + + fmt.Printf("Running one proving period\n") + fmt.Printf("End for head.Height > %d\n", di.PeriodStart+di.WPoStProvingPeriod+2) + + for { + head, err := client.ChainHead(ctx) + require.NoError(t, err) + + if head.Height() > di.PeriodStart+di.WPoStProvingPeriod+2 { + fmt.Printf("Now head.Height = %d\n", head.Height()) + break + } + build.Clock.Sleep(blocktime) + } + + p, err := client.StateMinerPower(ctx, maddr, types.EmptyTSK) + require.NoError(t, err) + + ssz, err := miner.ActorSectorSize(ctx, maddr) + require.NoError(t, err) + + require.Equal(t, p.MinerPower, p.TotalPower) + require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*uint64(nSectors+GenesisPreseals))) + + fmt.Printf("Drop some sectors\n") + + // Drop 2 sectors from deadline 2 partition 0 (full partition / deadline) + { + parts, err := client.StateMinerPartitions(ctx, maddr, 2, types.EmptyTSK) + require.NoError(t, err) + require.Greater(t, len(parts), 0) + + secs := parts[0].AllSectors + n, err := secs.Count() + require.NoError(t, err) + require.Equal(t, uint64(2), n) + + // Drop the partition + err = secs.ForEach(func(sid uint64) error { + return miner.StorageMiner.(*impl.StorageMinerAPI).IStorageMgr.(*mock.SectorMgr).MarkCorrupted(storage.SectorRef{ + ID: abi.SectorID{ + Miner: abi.ActorID(mid), + Number: abi.SectorNumber(sid), + }, + }, true) + }) + require.NoError(t, err) + } + + var s storage.SectorRef + + // Drop 1 sectors from deadline 3 partition 0 + { + parts, err := client.StateMinerPartitions(ctx, maddr, 3, types.EmptyTSK) + require.NoError(t, err) + require.Greater(t, len(parts), 0) + + secs := parts[0].AllSectors + n, err := secs.Count() + require.NoError(t, err) + require.Equal(t, uint64(2), n) + + // Drop the sector + sn, err := secs.First() + require.NoError(t, err) + + all, err := secs.All(2) + require.NoError(t, err) + fmt.Println("the sectors", all) + + s = storage.SectorRef{ + ID: abi.SectorID{ + Miner: abi.ActorID(mid), + Number: abi.SectorNumber(sn), + }, + } + + err = miner.StorageMiner.(*impl.StorageMinerAPI).IStorageMgr.(*mock.SectorMgr).MarkFailed(s, true) + require.NoError(t, err) + } + + di, err = client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) + require.NoError(t, err) + + fmt.Printf("Go through another PP, wait for sectors to become faulty\n") + fmt.Printf("End for head.Height > %d\n", di.PeriodStart+di.WPoStProvingPeriod+2) + + for { + head, err := client.ChainHead(ctx) + require.NoError(t, err) + + if head.Height() > di.PeriodStart+(di.WPoStProvingPeriod)+2 { + fmt.Printf("Now head.Height = %d\n", head.Height()) + break + } + + build.Clock.Sleep(blocktime) + } + + p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) + require.NoError(t, err) + + require.Equal(t, p.MinerPower, p.TotalPower) + + sectors := p.MinerPower.RawBytePower.Uint64() / uint64(ssz) + require.Equal(t, nSectors+GenesisPreseals-3, int(sectors)) // -3 just removed sectors + + fmt.Printf("Recover one sector\n") + + err = miner.StorageMiner.(*impl.StorageMinerAPI).IStorageMgr.(*mock.SectorMgr).MarkFailed(s, false) + require.NoError(t, err) + + di, err = client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) + require.NoError(t, err) + + fmt.Printf("End for head.Height > %d\n", di.PeriodStart+di.WPoStProvingPeriod+2) + + for { + head, err := client.ChainHead(ctx) + require.NoError(t, err) + + if head.Height() > di.PeriodStart+di.WPoStProvingPeriod+2 { + fmt.Printf("Now head.Height = %d\n", head.Height()) + break + } + + build.Clock.Sleep(blocktime) + } + + p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) + require.NoError(t, err) + + require.Equal(t, p.MinerPower, p.TotalPower) + + sectors = p.MinerPower.RawBytePower.Uint64() / uint64(ssz) + require.Equal(t, nSectors+GenesisPreseals-2, int(sectors)) // -2 not recovered sectors + + // pledge a sector after recovery + + pledgeSectors(t, ctx, miner, 1, nSectors, nil) + + { + // Wait until proven. + di, err = client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) + require.NoError(t, err) + + waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 2 + fmt.Printf("End for head.Height > %d\n", waitUntil) + + for { + head, err := client.ChainHead(ctx) + require.NoError(t, err) + + if head.Height() > waitUntil { + fmt.Printf("Now head.Height = %d\n", head.Height()) + break + } + } + } + + p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) + require.NoError(t, err) + + require.Equal(t, p.MinerPower, p.TotalPower) + + sectors = p.MinerPower.RawBytePower.Uint64() / uint64(ssz) + require.Equal(t, nSectors+GenesisPreseals-2+1, int(sectors)) // -2 not recovered sectors + 1 just pledged +} diff --git a/itests/window_post.go b/itests/window_post.go deleted file mode 100644 index 4b021a9fe..000000000 --- a/itests/window_post.go +++ /dev/null @@ -1,1025 +0,0 @@ -package itests - -import ( - "context" - "fmt" - "sort" - "strings" - "sync/atomic" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-bitfield" - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/crypto" - "github.com/filecoin-project/go-state-types/dline" - "github.com/filecoin-project/go-state-types/network" - "github.com/filecoin-project/lotus/extern/sector-storage/mock" - sealing "github.com/filecoin-project/lotus/extern/storage-sealing" - proof3 "github.com/filecoin-project/specs-actors/v3/actors/runtime/proof" - "github.com/filecoin-project/specs-storage/storage" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/actors" - minerActor "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/types" - bminer "github.com/filecoin-project/lotus/miner" - "github.com/filecoin-project/lotus/node/impl" -) - -func TestSDRUpgrade(t *testing.T, b APIBuilder, blocktime time.Duration) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - n, sn := b(t, []FullNodeOpts{FullNodeWithSDRAt(500, 1000)}, OneMiner) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - build.Clock.Sleep(time.Second) - - pledge := make(chan struct{}) - mine := int64(1) - done := make(chan struct{}) - go func() { - defer close(done) - round := 0 - for atomic.LoadInt64(&mine) != 0 { - build.Clock.Sleep(blocktime) - if err := sn[0].MineOne(ctx, bminer.MineReq{Done: func(bool, abi.ChainEpoch, error) { - - }}); err != nil { - t.Error(err) - } - - // 3 sealing rounds: before, during after. - if round >= 3 { - continue - } - - head, err := client.ChainHead(ctx) - assert.NoError(t, err) - - // rounds happen every 100 blocks, with a 50 block offset. - if head.Height() >= abi.ChainEpoch(round*500+50) { - round++ - pledge <- struct{}{} - - ver, err := client.StateNetworkVersion(ctx, head.Key()) - assert.NoError(t, err) - switch round { - case 1: - assert.Equal(t, network.Version6, ver) - case 2: - assert.Equal(t, network.Version7, ver) - case 3: - assert.Equal(t, network.Version8, ver) - } - } - - } - }() - - // before. - pledgeSectors(t, ctx, miner, 9, 0, pledge) - - s, err := miner.SectorsList(ctx) - require.NoError(t, err) - sort.Slice(s, func(i, j int) bool { - return s[i] < s[j] - }) - - for i, id := range s { - info, err := miner.SectorsStatus(ctx, id, true) - require.NoError(t, err) - expectProof := abi.RegisteredSealProof_StackedDrg2KiBV1 - if i >= 3 { - // after - expectProof = abi.RegisteredSealProof_StackedDrg2KiBV1_1 - } - assert.Equal(t, expectProof, info.SealProof, "sector %d, id %d", i, id) - } - - atomic.StoreInt64(&mine, 0) - <-done -} - -func TestPledgeSector(t *testing.T, b APIBuilder, blocktime time.Duration, nSectors int) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - n, sn := b(t, OneFull, OneMiner) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - build.Clock.Sleep(time.Second) - - mine := int64(1) - done := make(chan struct{}) - go func() { - defer close(done) - for atomic.LoadInt64(&mine) != 0 { - build.Clock.Sleep(blocktime) - if err := sn[0].MineOne(ctx, bminer.MineReq{Done: func(bool, abi.ChainEpoch, error) { - - }}); err != nil { - t.Error(err) - } - } - }() - - pledgeSectors(t, ctx, miner, nSectors, 0, nil) - - atomic.StoreInt64(&mine, 0) - <-done -} - -func pledgeSectors(t *testing.T, ctx context.Context, miner TestStorageNode, n, existing int, blockNotif <-chan struct{}) { - for i := 0; i < n; i++ { - if i%3 == 0 && blockNotif != nil { - <-blockNotif - log.Errorf("WAIT") - } - log.Errorf("PLEDGING %d", i) - _, err := miner.PledgeSector(ctx) - require.NoError(t, err) - } - - for { - s, err := miner.SectorsList(ctx) // Note - the test builder doesn't import genesis sectors into FSM - require.NoError(t, err) - fmt.Printf("Sectors: %d\n", len(s)) - if len(s) >= n+existing { - break - } - - build.Clock.Sleep(100 * time.Millisecond) - } - - fmt.Printf("All sectors is fsm\n") - - s, err := miner.SectorsList(ctx) - require.NoError(t, err) - - toCheck := map[abi.SectorNumber]struct{}{} - for _, number := range s { - toCheck[number] = struct{}{} - } - - for len(toCheck) > 0 { - for n := range toCheck { - st, err := miner.SectorsStatus(ctx, n, false) - require.NoError(t, err) - if st.State == api.SectorState(sealing.Proving) { - delete(toCheck, n) - } - if strings.Contains(string(st.State), "Fail") { - t.Fatal("sector in a failed state", st.State) - } - } - - build.Clock.Sleep(100 * time.Millisecond) - fmt.Printf("WaitSeal: %d\n", len(s)) - } -} - -func TestWindowPost(t *testing.T, b APIBuilder, blocktime time.Duration, nSectors int) { - for _, height := range []abi.ChainEpoch{ - -1, // before - 162, // while sealing - 5000, // while proving - } { - height := height // copy to satisfy lints - t.Run(fmt.Sprintf("upgrade-%d", height), func(t *testing.T) { - testWindowPostUpgrade(t, b, blocktime, nSectors, height) - }) - } - -} - -func testWindowPostUpgrade(t *testing.T, b APIBuilder, blocktime time.Duration, nSectors int, - upgradeHeight abi.ChainEpoch) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - n, sn := b(t, []FullNodeOpts{FullNodeWithLatestActorsAt(upgradeHeight)}, OneMiner) - - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - build.Clock.Sleep(time.Second) - - done := make(chan struct{}) - go func() { - defer close(done) - for ctx.Err() == nil { - build.Clock.Sleep(blocktime) - if err := sn[0].MineOne(ctx, MineNext); err != nil { - if ctx.Err() != nil { - // context was canceled, ignore the error. - return - } - t.Error(err) - } - } - }() - defer func() { - cancel() - <-done - }() - - pledgeSectors(t, ctx, miner, nSectors, 0, nil) - - maddr, err := miner.ActorAddress(ctx) - require.NoError(t, err) - - di, err := client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) - require.NoError(t, err) - - mid, err := address.IDFromAddress(maddr) - require.NoError(t, err) - - fmt.Printf("Running one proving period\n") - fmt.Printf("End for head.Height > %d\n", di.PeriodStart+di.WPoStProvingPeriod+2) - - for { - head, err := client.ChainHead(ctx) - require.NoError(t, err) - - if head.Height() > di.PeriodStart+di.WPoStProvingPeriod+2 { - fmt.Printf("Now head.Height = %d\n", head.Height()) - break - } - build.Clock.Sleep(blocktime) - } - - p, err := client.StateMinerPower(ctx, maddr, types.EmptyTSK) - require.NoError(t, err) - - ssz, err := miner.ActorSectorSize(ctx, maddr) - require.NoError(t, err) - - require.Equal(t, p.MinerPower, p.TotalPower) - require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*uint64(nSectors+GenesisPreseals))) - - fmt.Printf("Drop some sectors\n") - - // Drop 2 sectors from deadline 2 partition 0 (full partition / deadline) - { - parts, err := client.StateMinerPartitions(ctx, maddr, 2, types.EmptyTSK) - require.NoError(t, err) - require.Greater(t, len(parts), 0) - - secs := parts[0].AllSectors - n, err := secs.Count() - require.NoError(t, err) - require.Equal(t, uint64(2), n) - - // Drop the partition - err = secs.ForEach(func(sid uint64) error { - return miner.StorageMiner.(*impl.StorageMinerAPI).IStorageMgr.(*mock.SectorMgr).MarkCorrupted(storage.SectorRef{ - ID: abi.SectorID{ - Miner: abi.ActorID(mid), - Number: abi.SectorNumber(sid), - }, - }, true) - }) - require.NoError(t, err) - } - - var s storage.SectorRef - - // Drop 1 sectors from deadline 3 partition 0 - { - parts, err := client.StateMinerPartitions(ctx, maddr, 3, types.EmptyTSK) - require.NoError(t, err) - require.Greater(t, len(parts), 0) - - secs := parts[0].AllSectors - n, err := secs.Count() - require.NoError(t, err) - require.Equal(t, uint64(2), n) - - // Drop the sector - sn, err := secs.First() - require.NoError(t, err) - - all, err := secs.All(2) - require.NoError(t, err) - fmt.Println("the sectors", all) - - s = storage.SectorRef{ - ID: abi.SectorID{ - Miner: abi.ActorID(mid), - Number: abi.SectorNumber(sn), - }, - } - - err = miner.StorageMiner.(*impl.StorageMinerAPI).IStorageMgr.(*mock.SectorMgr).MarkFailed(s, true) - require.NoError(t, err) - } - - di, err = client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) - require.NoError(t, err) - - fmt.Printf("Go through another PP, wait for sectors to become faulty\n") - fmt.Printf("End for head.Height > %d\n", di.PeriodStart+di.WPoStProvingPeriod+2) - - for { - head, err := client.ChainHead(ctx) - require.NoError(t, err) - - if head.Height() > di.PeriodStart+(di.WPoStProvingPeriod)+2 { - fmt.Printf("Now head.Height = %d\n", head.Height()) - break - } - - build.Clock.Sleep(blocktime) - } - - p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) - require.NoError(t, err) - - require.Equal(t, p.MinerPower, p.TotalPower) - - sectors := p.MinerPower.RawBytePower.Uint64() / uint64(ssz) - require.Equal(t, nSectors+GenesisPreseals-3, int(sectors)) // -3 just removed sectors - - fmt.Printf("Recover one sector\n") - - err = miner.StorageMiner.(*impl.StorageMinerAPI).IStorageMgr.(*mock.SectorMgr).MarkFailed(s, false) - require.NoError(t, err) - - di, err = client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) - require.NoError(t, err) - - fmt.Printf("End for head.Height > %d\n", di.PeriodStart+di.WPoStProvingPeriod+2) - - for { - head, err := client.ChainHead(ctx) - require.NoError(t, err) - - if head.Height() > di.PeriodStart+di.WPoStProvingPeriod+2 { - fmt.Printf("Now head.Height = %d\n", head.Height()) - break - } - - build.Clock.Sleep(blocktime) - } - - p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) - require.NoError(t, err) - - require.Equal(t, p.MinerPower, p.TotalPower) - - sectors = p.MinerPower.RawBytePower.Uint64() / uint64(ssz) - require.Equal(t, nSectors+GenesisPreseals-2, int(sectors)) // -2 not recovered sectors - - // pledge a sector after recovery - - pledgeSectors(t, ctx, miner, 1, nSectors, nil) - - { - // Wait until proven. - di, err = client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) - require.NoError(t, err) - - waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 2 - fmt.Printf("End for head.Height > %d\n", waitUntil) - - for { - head, err := client.ChainHead(ctx) - require.NoError(t, err) - - if head.Height() > waitUntil { - fmt.Printf("Now head.Height = %d\n", head.Height()) - break - } - } - } - - p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) - require.NoError(t, err) - - require.Equal(t, p.MinerPower, p.TotalPower) - - sectors = p.MinerPower.RawBytePower.Uint64() / uint64(ssz) - require.Equal(t, nSectors+GenesisPreseals-2+1, int(sectors)) // -2 not recovered sectors + 1 just pledged -} - -func TestTerminate(t *testing.T, b APIBuilder, blocktime time.Duration) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - nSectors := uint64(2) - - n, sn := b(t, []FullNodeOpts{FullNodeWithLatestActorsAt(-1)}, []StorageMiner{{Full: 0, Preseal: int(nSectors)}}) - - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - build.Clock.Sleep(time.Second) - - done := make(chan struct{}) - go func() { - defer close(done) - for ctx.Err() == nil { - build.Clock.Sleep(blocktime) - if err := sn[0].MineOne(ctx, MineNext); err != nil { - if ctx.Err() != nil { - // context was canceled, ignore the error. - return - } - t.Error(err) - } - } - }() - defer func() { - cancel() - <-done - }() - - maddr, err := miner.ActorAddress(ctx) - require.NoError(t, err) - - ssz, err := miner.ActorSectorSize(ctx, maddr) - require.NoError(t, err) - - p, err := client.StateMinerPower(ctx, maddr, types.EmptyTSK) - require.NoError(t, err) - require.Equal(t, p.MinerPower, p.TotalPower) - require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*nSectors)) - - fmt.Printf("Seal a sector\n") - - pledgeSectors(t, ctx, miner, 1, 0, nil) - - fmt.Printf("wait for power\n") - - { - // Wait until proven. - di, err := client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) - require.NoError(t, err) - - waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 2 - fmt.Printf("End for head.Height > %d\n", waitUntil) - - for { - head, err := client.ChainHead(ctx) - require.NoError(t, err) - - if head.Height() > waitUntil { - fmt.Printf("Now head.Height = %d\n", head.Height()) - break - } - } - } - - nSectors++ - - p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) - require.NoError(t, err) - require.Equal(t, p.MinerPower, p.TotalPower) - require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*nSectors)) - - fmt.Println("Terminate a sector") - - toTerminate := abi.SectorNumber(3) - - err = miner.SectorTerminate(ctx, toTerminate) - require.NoError(t, err) - - msgTriggerred := false -loop: - for { - si, err := miner.SectorsStatus(ctx, toTerminate, false) - require.NoError(t, err) - - fmt.Println("state: ", si.State, msgTriggerred) - - switch sealing.SectorState(si.State) { - case sealing.Terminating: - if !msgTriggerred { - { - p, err := miner.SectorTerminatePending(ctx) - require.NoError(t, err) - require.Len(t, p, 1) - require.Equal(t, abi.SectorNumber(3), p[0].Number) - } - - c, err := miner.SectorTerminateFlush(ctx) - require.NoError(t, err) - if c != nil { - msgTriggerred = true - fmt.Println("terminate message:", c) - - { - p, err := miner.SectorTerminatePending(ctx) - require.NoError(t, err) - require.Len(t, p, 0) - } - } - } - case sealing.TerminateWait, sealing.TerminateFinality, sealing.Removed: - break loop - } - - time.Sleep(100 * time.Millisecond) - } - - // check power decreased - p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) - require.NoError(t, err) - require.Equal(t, p.MinerPower, p.TotalPower) - require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*(nSectors-1))) - - // check in terminated set - { - parts, err := client.StateMinerPartitions(ctx, maddr, 1, types.EmptyTSK) - require.NoError(t, err) - require.Greater(t, len(parts), 0) - - bflen := func(b bitfield.BitField) uint64 { - l, err := b.Count() - require.NoError(t, err) - return l - } - - require.Equal(t, uint64(1), bflen(parts[0].AllSectors)) - require.Equal(t, uint64(0), bflen(parts[0].LiveSectors)) - } - - di, err := client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) - require.NoError(t, err) - for { - head, err := client.ChainHead(ctx) - require.NoError(t, err) - - if head.Height() > di.PeriodStart+di.WPoStProvingPeriod+2 { - fmt.Printf("Now head.Height = %d\n", head.Height()) - break - } - build.Clock.Sleep(blocktime) - } - require.NoError(t, err) - fmt.Printf("End for head.Height > %d\n", di.PeriodStart+di.WPoStProvingPeriod+2) - - p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) - require.NoError(t, err) - - require.Equal(t, p.MinerPower, p.TotalPower) - require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*(nSectors-1))) -} - -func TestWindowPostDispute(t *testing.T, b APIBuilder, blocktime time.Duration) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // First, we configure two miners. After sealing, we're going to turn off the first miner so - // it doesn't submit proofs. - /// - // Then we're going to manually submit bad proofs. - n, sn := b(t, []FullNodeOpts{ - FullNodeWithLatestActorsAt(-1), - }, []StorageMiner{ - {Full: 0, Preseal: PresealGenesis}, - {Full: 0}, - }) - - client := n[0].FullNode.(*impl.FullNodeAPI) - chainMiner := sn[0] - evilMiner := sn[1] - - { - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := chainMiner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - - if err := evilMiner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - } - - defaultFrom, err := client.WalletDefaultAddress(ctx) - require.NoError(t, err) - - build.Clock.Sleep(time.Second) - - // Mine with the _second_ node (the good one). - done := make(chan struct{}) - go func() { - defer close(done) - for ctx.Err() == nil { - build.Clock.Sleep(blocktime) - if err := chainMiner.MineOne(ctx, MineNext); err != nil { - if ctx.Err() != nil { - // context was canceled, ignore the error. - return - } - t.Error(err) - } - } - }() - defer func() { - cancel() - <-done - }() - - // Give the chain miner enough sectors to win every block. - pledgeSectors(t, ctx, chainMiner, 10, 0, nil) - // And the evil one 1 sector. No cookie for you. - pledgeSectors(t, ctx, evilMiner, 1, 0, nil) - - // Let the evil miner's sectors gain power. - evilMinerAddr, err := evilMiner.ActorAddress(ctx) - require.NoError(t, err) - - di, err := client.StateMinerProvingDeadline(ctx, evilMinerAddr, types.EmptyTSK) - require.NoError(t, err) - - fmt.Printf("Running one proving period\n") - fmt.Printf("End for head.Height > %d\n", di.PeriodStart+di.WPoStProvingPeriod*2) - - for { - head, err := client.ChainHead(ctx) - require.NoError(t, err) - - if head.Height() > di.PeriodStart+di.WPoStProvingPeriod*2 { - fmt.Printf("Now head.Height = %d\n", head.Height()) - break - } - build.Clock.Sleep(blocktime) - } - - p, err := client.StateMinerPower(ctx, evilMinerAddr, types.EmptyTSK) - require.NoError(t, err) - - ssz, err := evilMiner.ActorSectorSize(ctx, evilMinerAddr) - require.NoError(t, err) - - // make sure it has gained power. - require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz))) - - evilSectors, err := evilMiner.SectorsList(ctx) - require.NoError(t, err) - evilSectorNo := evilSectors[0] // only one. - evilSectorLoc, err := client.StateSectorPartition(ctx, evilMinerAddr, evilSectorNo, types.EmptyTSK) - require.NoError(t, err) - - fmt.Println("evil miner stopping") - - // Now stop the evil miner, and start manually submitting bad proofs. - require.NoError(t, evilMiner.Stop(ctx)) - - fmt.Println("evil miner stopped") - - // Wait until we need to prove our sector. - for { - di, err = client.StateMinerProvingDeadline(ctx, evilMinerAddr, types.EmptyTSK) - require.NoError(t, err) - if di.Index == evilSectorLoc.Deadline { - break - } - build.Clock.Sleep(blocktime) - } - - err = submitBadProof(ctx, client, evilMinerAddr, di, evilSectorLoc.Deadline, evilSectorLoc.Partition) - require.NoError(t, err, "evil proof not accepted") - - // Wait until after the proving period. - for { - di, err = client.StateMinerProvingDeadline(ctx, evilMinerAddr, types.EmptyTSK) - require.NoError(t, err) - if di.Index != evilSectorLoc.Deadline { - break - } - build.Clock.Sleep(blocktime) - } - - fmt.Println("accepted evil proof") - - // Make sure the evil node didn't lose any power. - p, err = client.StateMinerPower(ctx, evilMinerAddr, types.EmptyTSK) - require.NoError(t, err) - require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz))) - - // OBJECTION! The good miner files a DISPUTE!!!! - { - params := &minerActor.DisputeWindowedPoStParams{ - Deadline: evilSectorLoc.Deadline, - PoStIndex: 0, - } - - enc, aerr := actors.SerializeParams(params) - require.NoError(t, aerr) - - msg := &types.Message{ - To: evilMinerAddr, - Method: minerActor.Methods.DisputeWindowedPoSt, - Params: enc, - Value: types.NewInt(0), - From: defaultFrom, - } - sm, err := client.MpoolPushMessage(ctx, msg, nil) - require.NoError(t, err) - - fmt.Println("waiting dispute") - rec, err := client.StateWaitMsg(ctx, sm.Cid(), build.MessageConfidence, api.LookbackNoLimit, true) - require.NoError(t, err) - require.Zero(t, rec.Receipt.ExitCode, "dispute not accepted: %s", rec.Receipt.ExitCode.Error()) - } - - // Objection SUSTAINED! - // Make sure the evil node lost power. - p, err = client.StateMinerPower(ctx, evilMinerAddr, types.EmptyTSK) - require.NoError(t, err) - require.True(t, p.MinerPower.RawBytePower.IsZero()) - - // Now we begin the redemption arc. - require.True(t, p.MinerPower.RawBytePower.IsZero()) - - // First, recover the sector. - - { - minerInfo, err := client.StateMinerInfo(ctx, evilMinerAddr, types.EmptyTSK) - require.NoError(t, err) - - params := &minerActor.DeclareFaultsRecoveredParams{ - Recoveries: []minerActor.RecoveryDeclaration{{ - Deadline: evilSectorLoc.Deadline, - Partition: evilSectorLoc.Partition, - Sectors: bitfield.NewFromSet([]uint64{uint64(evilSectorNo)}), - }}, - } - - enc, aerr := actors.SerializeParams(params) - require.NoError(t, aerr) - - msg := &types.Message{ - To: evilMinerAddr, - Method: minerActor.Methods.DeclareFaultsRecovered, - Params: enc, - Value: types.FromFil(30), // repay debt. - From: minerInfo.Owner, - } - sm, err := client.MpoolPushMessage(ctx, msg, nil) - require.NoError(t, err) - - rec, err := client.StateWaitMsg(ctx, sm.Cid(), build.MessageConfidence, api.LookbackNoLimit, true) - require.NoError(t, err) - require.Zero(t, rec.Receipt.ExitCode, "recovery not accepted: %s", rec.Receipt.ExitCode.Error()) - } - - // Then wait for the deadline. - for { - di, err = client.StateMinerProvingDeadline(ctx, evilMinerAddr, types.EmptyTSK) - require.NoError(t, err) - if di.Index == evilSectorLoc.Deadline { - break - } - build.Clock.Sleep(blocktime) - } - - // Now try to be evil again - err = submitBadProof(ctx, client, evilMinerAddr, di, evilSectorLoc.Deadline, evilSectorLoc.Partition) - require.Error(t, err) - require.Contains(t, err.Error(), "message execution failed: exit 16, reason: window post failed: invalid PoSt") - - // It didn't work because we're recovering. -} - -func submitBadProof( - ctx context.Context, - client api.FullNode, maddr address.Address, - di *dline.Info, dlIdx, partIdx uint64, -) error { - head, err := client.ChainHead(ctx) - if err != nil { - return err - } - - from, err := client.WalletDefaultAddress(ctx) - if err != nil { - return err - } - - minerInfo, err := client.StateMinerInfo(ctx, maddr, head.Key()) - if err != nil { - return err - } - - commEpoch := di.Open - commRand, err := client.ChainGetRandomnessFromTickets( - ctx, head.Key(), crypto.DomainSeparationTag_PoStChainCommit, - commEpoch, nil, - ) - if err != nil { - return err - } - params := &minerActor.SubmitWindowedPoStParams{ - ChainCommitEpoch: commEpoch, - ChainCommitRand: commRand, - Deadline: dlIdx, - Partitions: []minerActor.PoStPartition{{Index: partIdx}}, - Proofs: []proof3.PoStProof{{ - PoStProof: minerInfo.WindowPoStProofType, - ProofBytes: []byte("I'm soooo very evil."), - }}, - } - - enc, aerr := actors.SerializeParams(params) - if aerr != nil { - return aerr - } - - msg := &types.Message{ - To: maddr, - Method: minerActor.Methods.SubmitWindowedPoSt, - Params: enc, - Value: types.NewInt(0), - From: from, - } - sm, err := client.MpoolPushMessage(ctx, msg, nil) - if err != nil { - return err - } - - rec, err := client.StateWaitMsg(ctx, sm.Cid(), build.MessageConfidence, api.LookbackNoLimit, true) - if err != nil { - return err - } - if rec.Receipt.ExitCode.IsError() { - return rec.Receipt.ExitCode - } - return nil -} - -func TestWindowPostDisputeFails(t *testing.T, b APIBuilder, blocktime time.Duration) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - n, sn := b(t, []FullNodeOpts{FullNodeWithLatestActorsAt(-1)}, OneMiner) - - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - - { - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - } - - defaultFrom, err := client.WalletDefaultAddress(ctx) - require.NoError(t, err) - - maddr, err := miner.ActorAddress(ctx) - require.NoError(t, err) - - build.Clock.Sleep(time.Second) - - // Mine with the _second_ node (the good one). - done := make(chan struct{}) - go func() { - defer close(done) - for ctx.Err() == nil { - build.Clock.Sleep(blocktime) - if err := miner.MineOne(ctx, MineNext); err != nil { - if ctx.Err() != nil { - // context was canceled, ignore the error. - return - } - t.Error(err) - } - } - }() - defer func() { - cancel() - <-done - }() - - pledgeSectors(t, ctx, miner, 10, 0, nil) - - di, err := client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) - require.NoError(t, err) - - fmt.Printf("Running one proving period\n") - fmt.Printf("End for head.Height > %d\n", di.PeriodStart+di.WPoStProvingPeriod*2) - - for { - head, err := client.ChainHead(ctx) - require.NoError(t, err) - - if head.Height() > di.PeriodStart+di.WPoStProvingPeriod*2 { - fmt.Printf("Now head.Height = %d\n", head.Height()) - break - } - build.Clock.Sleep(blocktime) - } - - ssz, err := miner.ActorSectorSize(ctx, maddr) - require.NoError(t, err) - expectedPower := types.NewInt(uint64(ssz) * (GenesisPreseals + 10)) - - p, err := client.StateMinerPower(ctx, maddr, types.EmptyTSK) - require.NoError(t, err) - - // make sure it has gained power. - require.Equal(t, p.MinerPower.RawBytePower, expectedPower) - - // Wait until a proof has been submitted. - var targetDeadline uint64 -waitForProof: - for { - deadlines, err := client.StateMinerDeadlines(ctx, maddr, types.EmptyTSK) - require.NoError(t, err) - for dlIdx, dl := range deadlines { - nonEmpty, err := dl.PostSubmissions.IsEmpty() - require.NoError(t, err) - if nonEmpty { - targetDeadline = uint64(dlIdx) - break waitForProof - } - } - - build.Clock.Sleep(blocktime) - } - - for { - di, err := client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) - require.NoError(t, err) - // wait until the deadline finishes. - if di.Index == ((targetDeadline + 1) % di.WPoStPeriodDeadlines) { - break - } - - build.Clock.Sleep(blocktime) - } - - // Try to object to the proof. This should fail. - { - params := &minerActor.DisputeWindowedPoStParams{ - Deadline: targetDeadline, - PoStIndex: 0, - } - - enc, aerr := actors.SerializeParams(params) - require.NoError(t, aerr) - - msg := &types.Message{ - To: maddr, - Method: minerActor.Methods.DisputeWindowedPoSt, - Params: enc, - Value: types.NewInt(0), - From: defaultFrom, - } - _, err := client.MpoolPushMessage(ctx, msg, nil) - require.Error(t, err) - require.Contains(t, err.Error(), "failed to dispute valid post (RetCode=16)") - } -}