From 2bcedcf55f82677181efe4dc91475bb6b7aeb854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Wed, 26 May 2021 00:04:13 +0100 Subject: [PATCH 001/257] initial version of the new itest kit. Still need to migrate all integration tests, add godocs, and probably zap bugs. --- itests/api_test.go | 238 ++---- ...tch_deal_test.go => batch_deal_test.go.no} | 0 ...ccupgrade_test.go => ccupgrade_test.go.no} | 1 + itests/{cli_test.go => cli_test.go.no} | 0 ...deadlines_test.go => deadlines_test.go.no} | 0 itests/{deals_test.go => deals_test.go.no} | 28 +- .../{gateway_test.go => gateway_test.go.no} | 2 +- itests/kit/blockminer.go | 6 +- itests/kit/deals.go | 25 +- itests/kit/ensemble.go | 708 ++++++++++++++++++ itests/kit/ensemble_presets.go | 21 + itests/kit/funds.go | 19 +- itests/kit/init.go | 25 + itests/kit/net.go | 125 +--- itests/kit/node_builder.go | 606 --------------- itests/kit/node_full.go | 22 + itests/kit/node_miner.go | 97 +++ itests/kit/nodes.go | 133 ---- itests/kit/pledge.go | 64 -- .../{multisig_test.go => multisig_test.go.no} | 0 ...paych_api_test.go => paych_api_test.go.no} | 0 ...paych_cli_test.go => paych_cli_test.go.no} | 0 ...upgrade_test.go => sdr_upgrade_test.go.no} | 0 ...ledge_test.go => sector_pledge_test.go.no} | 0 ...te_test.go => sector_terminate_test.go.no} | 0 itests/{tape_test.go => tape_test.go.no} | 0 ...pute_test.go => wdpost_dispute_test.go.no} | 0 itests/{wdpost_test.go => wdpost_test.go.no} | 0 28 files changed, 1025 insertions(+), 1095 deletions(-) rename itests/{batch_deal_test.go => batch_deal_test.go.no} (100%) rename itests/{ccupgrade_test.go => ccupgrade_test.go.no} (98%) rename itests/{cli_test.go => cli_test.go.no} (100%) rename itests/{deadlines_test.go => deadlines_test.go.no} (100%) rename itests/{deals_test.go => deals_test.go.no} (94%) rename itests/{gateway_test.go => gateway_test.go.no} (99%) create mode 100644 itests/kit/ensemble.go create mode 100644 itests/kit/ensemble_presets.go create mode 100644 itests/kit/init.go delete mode 100644 itests/kit/node_builder.go create mode 100644 itests/kit/node_full.go create mode 100644 itests/kit/node_miner.go delete mode 100644 itests/kit/nodes.go delete mode 100644 itests/kit/pledge.go rename itests/{multisig_test.go => multisig_test.go.no} (100%) rename itests/{paych_api_test.go => paych_api_test.go.no} (100%) rename itests/{paych_cli_test.go => paych_cli_test.go.no} (100%) rename itests/{sdr_upgrade_test.go => sdr_upgrade_test.go.no} (100%) rename itests/{sector_pledge_test.go => sector_pledge_test.go.no} (100%) rename itests/{sector_terminate_test.go => sector_terminate_test.go.no} (100%) rename itests/{tape_test.go => tape_test.go.no} (100%) rename itests/{wdpost_dispute_test.go => wdpost_dispute_test.go.no} (100%) rename itests/{wdpost_test.go => wdpost_test.go.no} (100%) diff --git a/itests/api_test.go b/itests/api_test.go index ee70a337b..22bbc6f6e 100644 --- a/itests/api_test.go +++ b/itests/api_test.go @@ -8,33 +8,30 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/exitcode" 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/itests/kit" - "github.com/filecoin-project/lotus/node/impl" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestAPI(t *testing.T) { t.Run("direct", func(t *testing.T) { - runAPITest(t, kit.Builder) + runAPITest(t) }) t.Run("rpc", func(t *testing.T) { - runAPITest(t, kit.RPCBuilder) + runAPITest(t, kit.ThroughRPC()) }) } type apiSuite struct { - makeNodes kit.APIBuilder + opts []kit.NodeOpt } // runAPITest is the entry point to API test suite -func runAPITest(t *testing.T, b kit.APIBuilder) { - ts := apiSuite{ - makeNodes: b, - } +func runAPITest(t *testing.T, opts ...kit.NodeOpt) { + ts := apiSuite{opts: opts} t.Run("version", ts.testVersion) t.Run("id", ts.testID) @@ -51,145 +48,114 @@ func (ts *apiSuite) testVersion(t *testing.T) { lapi.RunningNodeType = lapi.NodeUnknown }) - ctx := context.Background() - apis, _ := ts.makeNodes(t, kit.OneFull, kit.OneMiner) - napi := apis[0] + full, _, _ := kit.EnsembleMinimum(t, ts.opts...) + + v, err := full.Version(context.Background()) + require.NoError(t, err) - 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.NotZero(t, len(versions), "empty version") require.Equal(t, versions[0], build.BuildVersion) } -func (ts *apiSuite) testSearchMsg(t *testing.T) { - apis, miners := ts.makeNodes(t, kit.OneFull, kit.OneMiner) +func (ts *apiSuite) testID(t *testing.T) { + ctx := context.Background() - api := apis[0] - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - senderAddr, err := api.WalletDefaultAddress(ctx) + full, _, _ := kit.EnsembleMinimum(t, ts.opts...) + + id, err := full.ID(ctx) if err != nil { t.Fatal(err) } + require.Regexp(t, "^12", id.Pretty()) +} + +func (ts *apiSuite) testConnectTwo(t *testing.T) { + ctx := context.Background() + + one, two, _, ens := kit.EnsembleTwo(t, ts.opts...) + + p, err := one.NetPeers(ctx) + require.NoError(t, err) + require.Empty(t, p, "node one has peers") + + p, err = two.NetPeers(ctx) + require.NoError(t, err) + require.Empty(t, p, "node two has peers") + + ens.InterconnectAll() + + peers, err := one.NetPeers(ctx) + require.NoError(t, err) + require.Lenf(t, peers, 1, "node one doesn't have 1 peer") + + peers, err = two.NetPeers(ctx) + require.NoError(t, err) + require.Lenf(t, peers, 1, "node two doesn't have 1 peer") +} + +func (ts *apiSuite) testSearchMsg(t *testing.T) { + ctx := context.Background() + + full, _, ens := kit.EnsembleMinimum(t, ts.opts...) + + senderAddr, err := full.WalletDefaultAddress(ctx) + require.NoError(t, err) msg := &types.Message{ From: senderAddr, To: senderAddr, Value: big.Zero(), } - bm := kit.NewBlockMiner(t, miners[0]) - bm.MineBlocks(ctx, 100*time.Millisecond) - 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") - } + ens.BeginMining(100 * time.Millisecond) - searchRes, err := api.StateSearchMsg(ctx, types.EmptyTSK, sm.Cid(), lapi.LookbackNoLimit, true) - if err != nil { - t.Fatal(err) - } + sm, err := full.MpoolPushMessage(ctx, msg, nil) + require.NoError(t, err) - if searchRes.TipSet != res.TipSet { - t.Fatalf("search ts: %s, different from wait ts: %s", searchRes.TipSet, res.TipSet) - } + res, err := full.StateWaitMsg(ctx, sm.Cid(), 1, lapi.LookbackNoLimit, true) + require.NoError(t, err) -} + require.Equal(t, exitcode.Ok, res.Receipt.ExitCode, "message not successful") -func (ts *apiSuite) testID(t *testing.T) { - ctx := context.Background() - apis, _ := ts.makeNodes(t, kit.OneFull, kit.OneMiner) - api := apis[0] + searchRes, err := full.StateSearchMsg(ctx, types.EmptyTSK, sm.Cid(), lapi.LookbackNoLimit, true) + require.NoError(t, err) - id, err := api.ID(ctx) - if err != nil { - t.Fatal(err) - } - assert.Regexp(t, "^12", id.Pretty()) -} - -func (ts *apiSuite) testConnectTwo(t *testing.T) { - ctx := context.Background() - apis, _ := ts.makeNodes(t, kit.TwoFull, kit.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") - } + require.Equalf(t, res.TipSet, searchRes.TipSet, "search ts: %s, different from wait ts: %s", searchRes.TipSet, res.TipSet) } func (ts *apiSuite) testMining(t *testing.T) { ctx := context.Background() - fulls, miners := ts.makeNodes(t, kit.OneFull, kit.OneMiner) - api := fulls[0] - newHeads, err := api.ChainNotify(ctx) + full, miner, _ := kit.EnsembleMinimum(t, ts.opts...) + + newHeads, err := full.ChainNotify(ctx) require.NoError(t, err) initHead := (<-newHeads)[0] baseHeight := initHead.Val.Height() - h1, err := api.ChainHead(ctx) + h1, err := full.ChainHead(ctx) require.NoError(t, err) require.Equal(t, int64(h1.Height()), int64(baseHeight)) - bm := kit.NewBlockMiner(t, miners[0]) - bm.MineUntilBlock(ctx, fulls[0], nil) + bm := kit.NewBlockMiner(t, miner) + bm.MineUntilBlock(ctx, full, nil) require.NoError(t, err) <-newHeads - h2, err := api.ChainHead(ctx) + h2, err := full.ChainHead(ctx) require.NoError(t, err) require.Greater(t, int64(h2.Height()), int64(h1.Height())) + + bm.MineUntilBlock(ctx, full, nil) + require.NoError(t, err) + + <-newHeads + + h3, err := full.ChainHead(ctx) + require.NoError(t, err) + require.Greater(t, int64(h3.Height()), int64(h2.Height())) } func (ts *apiSuite) testMiningReal(t *testing.T) { @@ -198,66 +164,26 @@ func (ts *apiSuite) testMiningReal(t *testing.T) { build.InsecurePoStValidation = true }() - ctx := context.Background() - fulls, miners := ts.makeNodes(t, kit.OneFull, kit.OneMiner) - api := fulls[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())) - - bm := kit.NewBlockMiner(t, miners[0]) - - bm.MineUntilBlock(ctx, fulls[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())) - - bm.MineUntilBlock(ctx, fulls[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())) + ts.testMining(t) } func (ts *apiSuite) testNonGenesisMiner(t *testing.T) { ctx := context.Background() - n, sn := ts.makeNodes(t, - []kit.FullNodeOpts{kit.FullNodeWithLatestActorsAt(-1)}, - []kit.StorageMiner{{Full: 0, Preseal: kit.PresealGenesis}}, - ) - full, ok := n[0].FullNode.(*impl.FullNodeAPI) - if !ok { - t.Skip("not testing with a full node") - return - } - genesisMiner := sn[0] + full, genesisMiner, ens := kit.EnsembleMinimum(t, ts.opts...) - bm := kit.NewBlockMiner(t, genesisMiner) - bm.MineBlocks(ctx, 4*time.Millisecond) - t.Cleanup(bm.Stop) + ens.BeginMining(4 * time.Millisecond) gaa, err := genesisMiner.ActorAddress(ctx) require.NoError(t, err) - gmi, err := full.StateMinerInfo(ctx, gaa, types.EmptyTSK) + _, err = full.StateMinerInfo(ctx, gaa, types.EmptyTSK) require.NoError(t, err) - testm := n[0].Stb(ctx, t, kit.TestSpt, gmi.Owner) + var newMiner kit.TestMiner + ens.Miner(&newMiner, full, kit.OwnerAddr(full.DefaultKey)).Start() - ta, err := testm.ActorAddress(ctx) + ta, err := newMiner.ActorAddress(ctx) require.NoError(t, err) tid, err := address.IDFromAddress(ta) diff --git a/itests/batch_deal_test.go b/itests/batch_deal_test.go.no similarity index 100% rename from itests/batch_deal_test.go rename to itests/batch_deal_test.go.no diff --git a/itests/ccupgrade_test.go b/itests/ccupgrade_test.go.no similarity index 98% rename from itests/ccupgrade_test.go rename to itests/ccupgrade_test.go.no index 28abac171..f6ba87820 100644 --- a/itests/ccupgrade_test.go +++ b/itests/ccupgrade_test.go.no @@ -8,6 +8,7 @@ import ( "time" "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/itests/kit2" "github.com/stretchr/testify/require" "github.com/filecoin-project/go-state-types/abi" diff --git a/itests/cli_test.go b/itests/cli_test.go.no similarity index 100% rename from itests/cli_test.go rename to itests/cli_test.go.no diff --git a/itests/deadlines_test.go b/itests/deadlines_test.go.no similarity index 100% rename from itests/deadlines_test.go rename to itests/deadlines_test.go.no diff --git a/itests/deals_test.go b/itests/deals_test.go.no similarity index 94% rename from itests/deals_test.go rename to itests/deals_test.go.no index a7599a8b7..f34aea91d 100644 --- a/itests/deals_test.go +++ b/itests/deals_test.go.no @@ -20,6 +20,7 @@ import ( "github.com/filecoin-project/lotus/miner" "github.com/filecoin-project/lotus/node" "github.com/filecoin-project/lotus/node/impl" + "github.com/filecoin-project/lotus/node/impl/client" market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" "github.com/stretchr/testify/require" ) @@ -66,15 +67,15 @@ func TestAPIDealFlowReal(t *testing.T) { }) t.Run("basic", func(t *testing.T) { - runFullDealCycles(t, 1, kit.Builder, time.Second, false, false, 0) + runFullDealCycles(t, 1, kit.FullNodeBuilder, time.Second, false, false, 0) }) t.Run("fast-retrieval", func(t *testing.T) { - runFullDealCycles(t, 1, kit.Builder, time.Second, false, true, 0) + runFullDealCycles(t, 1, kit.FullNodeBuilder, time.Second, false, true, 0) }) t.Run("retrieval-second", func(t *testing.T) { - runSecondDealRetrievalTest(t, kit.Builder, time.Second) + runSecondDealRetrievalTest(t, kit.FullNodeBuilder, time.Second) }) } @@ -309,6 +310,7 @@ func TestDealMining(t *testing.T) { } func runFullDealCycles(t *testing.T, n int, b kit.APIBuilder, blocktime time.Duration, carExport, fastRet bool, startEpoch abi.ChainEpoch) { + fulls, miners := b(t, kit.OneFull, kit.OneMiner) client, miner := fulls[0].FullNode.(*impl.FullNodeAPI), miners[0] @@ -325,21 +327,23 @@ func runFullDealCycles(t *testing.T, n int, b kit.APIBuilder, blocktime time.Dur func runFastRetrievalDealFlowT(t *testing.T, b kit.APIBuilder, blocktime time.Duration, startEpoch abi.ChainEpoch) { ctx := context.Background() - fulls, miners := b(t, kit.OneFull, kit.OneMiner) - client, miner := fulls[0].FullNode.(*impl.FullNodeAPI), miners[0] + var ( + nb = kit.NewNodeBuilder(t) + full = nb.FullNode() + miner = nb.Miner(full) + ) - kit.ConnectAndStartMining(t, blocktime, miner, client) + nb.Create() - dh := kit.NewDealHarness(t, client, miner) + kit.ConnectAndStartMining(t, blocktime, miner, full) + dh := kit.NewDealHarness(t, full, miner) data := make([]byte, 1600) rand.New(rand.NewSource(int64(8))).Read(data) r := bytes.NewReader(data) - fcid, err := client.ClientImportLocal(ctx, r) - if err != nil { - t.Fatal(err) - } + fcid, err := full.FullNode.(*impl.FullNodeAPI).ClientImportLocal(ctx, r) + require.NoError(t, err) fmt.Println("FILE CID: ", fcid) @@ -349,7 +353,7 @@ func runFastRetrievalDealFlowT(t *testing.T, b kit.APIBuilder, blocktime time.Du fmt.Println("deal published, retrieving") // Retrieval - info, err := client.ClientGetDealInfo(ctx, *deal) + info, err := full.ClientGetDealInfo(ctx, *deal) require.NoError(t, err) dh.TestRetrieval(ctx, fcid, &info.PieceCID, false, data) diff --git a/itests/gateway_test.go b/itests/gateway_test.go.no similarity index 99% rename from itests/gateway_test.go rename to itests/gateway_test.go.no index 5c7fc4be0..d532f3423 100644 --- a/itests/gateway_test.go +++ b/itests/gateway_test.go.no @@ -291,7 +291,7 @@ func startNodes( }, }, ) - n, sn := kit.RPCMockMinerBuilder(t, opts, kit.OneMiner) + n, sn := kit.MinerRPCMockMinerBuilder(t, opts, kit.OneMiner) full := n[0] lite := n[1] diff --git a/itests/kit/blockminer.go b/itests/kit/blockminer.go index 3b1f1fedf..b7951c4f8 100644 --- a/itests/kit/blockminer.go +++ b/itests/kit/blockminer.go @@ -15,14 +15,14 @@ import ( // BlockMiner is a utility that makes a test miner Mine blocks on a timer. type BlockMiner struct { t *testing.T - miner TestMiner + miner *TestMiner nextNulls int64 wg sync.WaitGroup cancel context.CancelFunc } -func NewBlockMiner(t *testing.T, miner TestMiner) *BlockMiner { +func NewBlockMiner(t *testing.T, miner *TestMiner) *BlockMiner { return &BlockMiner{ t: t, miner: miner, @@ -69,7 +69,7 @@ func (bm *BlockMiner) InjectNulls(rounds abi.ChainEpoch) { atomic.AddInt64(&bm.nextNulls, int64(rounds)) } -func (bm *BlockMiner) MineUntilBlock(ctx context.Context, fn TestFullNode, cb func(abi.ChainEpoch)) { +func (bm *BlockMiner) MineUntilBlock(ctx context.Context, fn *TestFullNode, cb func(abi.ChainEpoch)) { for i := 0; i < 1000; i++ { var ( success bool diff --git a/itests/kit/deals.go b/itests/kit/deals.go index 986cda39c..2a1ca5ba5 100644 --- a/itests/kit/deals.go +++ b/itests/kit/deals.go @@ -31,11 +31,11 @@ import ( type DealHarness struct { t *testing.T client api.FullNode - miner TestMiner + miner *TestMiner } // NewDealHarness creates a test harness that contains testing utilities for deals. -func NewDealHarness(t *testing.T, client api.FullNode, miner TestMiner) *DealHarness { +func NewDealHarness(t *testing.T, client api.FullNode, miner *TestMiner) *DealHarness { return &DealHarness{ t: t, client: client, @@ -252,27 +252,6 @@ type DealsScaffold struct { BlockMiner *BlockMiner } -func ConnectAndStartMining(t *testing.T, blocktime time.Duration, miner TestMiner, clients ...api.FullNode) *BlockMiner { - ctx := context.Background() - - for _, c := range clients { - addrinfo, err := c.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - if err := miner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - } - - time.Sleep(time.Second) - - blockMiner := NewBlockMiner(t, miner) - blockMiner.MineBlocks(ctx, blocktime) - - return blockMiner -} - type TestDealState int const ( diff --git a/itests/kit/ensemble.go b/itests/kit/ensemble.go new file mode 100644 index 000000000..90a09c82b --- /dev/null +++ b/itests/kit/ensemble.go @@ -0,0 +1,708 @@ +package kit + +import ( + "bytes" + "context" + "crypto/rand" + "io/ioutil" + "net/http" + "net/http/httptest" + "sync" + "testing" + "time" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-jsonrpc/auth" + "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" + "github.com/filecoin-project/go-storedcounter" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/api/client" + "github.com/filecoin-project/lotus/api/v1api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/gen" + genesis2 "github.com/filecoin-project/lotus/chain/gen/genesis" + "github.com/filecoin-project/lotus/chain/messagepool" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/wallet" + "github.com/filecoin-project/lotus/cmd/lotus-seed/seed" + sectorstorage "github.com/filecoin-project/lotus/extern/sector-storage" + "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" + "github.com/filecoin-project/lotus/extern/sector-storage/mock" + "github.com/filecoin-project/lotus/genesis" + lotusminer "github.com/filecoin-project/lotus/miner" + "github.com/filecoin-project/lotus/node" + "github.com/filecoin-project/lotus/node/modules" + "github.com/filecoin-project/lotus/node/modules/dtypes" + testing2 "github.com/filecoin-project/lotus/node/modules/testing" + "github.com/filecoin-project/lotus/node/repo" + "github.com/filecoin-project/lotus/storage/mockstorage" + miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" + power2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/power" + "github.com/ipfs/go-datastore" + libp2pcrypto "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/peer" + mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr/net" + "github.com/stretchr/testify/require" +) + +func init() { + chain.BootstrapPeerThreshold = 1 + messagepool.HeadChangeCoalesceMinDelay = time.Microsecond + messagepool.HeadChangeCoalesceMaxDelay = 2 * time.Microsecond + messagepool.HeadChangeCoalesceMergeInterval = 100 * time.Nanosecond +} + +type BuilderOpt func(opts *BuilderOpts) error + +type BuilderOpts struct { + pastOffset time.Duration + spt abi.RegisteredSealProof +} + +var DefaultBuilderOpts = BuilderOpts{ + pastOffset: 10000 * time.Second, + spt: abi.RegisteredSealProof_StackedDrg2KiBV1, +} + +func ProofType(proofType abi.RegisteredSealProof) BuilderOpt { + return func(opts *BuilderOpts) error { + opts.spt = proofType + return nil + } +} + +// Ensemble is a collection of nodes instantiated within a test. Ensemble +// supports building full nodes and miners. +type Ensemble struct { + t *testing.T + bootstrapped bool + genesisBlock bytes.Buffer + mn mocknet.Mocknet + options *BuilderOpts + + inactive struct { + fullnodes []*TestFullNode + miners []*TestMiner + } + active struct { + fullnodes []*TestFullNode + miners []*TestMiner + } + genesis struct { + miners []genesis.Miner + accounts []genesis.Actor + } +} + +// NewEnsemble +func NewEnsemble(t *testing.T, opts ...BuilderOpt) *Ensemble { + options := DefaultBuilderOpts + for _, o := range opts { + err := o(&options) + require.NoError(t, err) + } + return &Ensemble{t: t, options: &options} +} + +type NodeOpts struct { + balance abi.TokenAmount + lite bool + sectors int + mockProofs bool + rpc bool + ownerKey *wallet.Key +} + +var DefaultNodeOpts = NodeOpts{ + balance: big.Mul(big.NewInt(100000000), types.NewInt(build.FilecoinPrecision)), + sectors: 2, +} + +type NodeOpt func(opts *NodeOpts) error + +// OwnerBalance specifies the balance to be attributed to a miner's owner account. +// +// Only used when creating a miner. +func OwnerBalance(balance abi.TokenAmount) NodeOpt { + return func(opts *NodeOpts) error { + opts.balance = balance + return nil + } +} + +// LiteNode specifies that this node will be a lite node. +// +// Only used when creating a fullnode. +func LiteNode() NodeOpt { + return func(opts *NodeOpts) error { + opts.lite = true + return nil + } +} + +// PresealSectors specifies the amount of preseal sectors to give to a miner +// at genesis. +// +// Only used when creating a miner. +func PresealSectors(sectors int) NodeOpt { + return func(opts *NodeOpts) error { + opts.sectors = sectors + return nil + } +} + +// MockProofs activates mock proofs for the entire ensemble. +func MockProofs() NodeOpt { + return func(opts *NodeOpts) error { + opts.mockProofs = true + return nil + } +} + +func ThroughRPC() NodeOpt { + return func(opts *NodeOpts) error { + opts.rpc = true + return nil + } +} + +func OwnerAddr(wk *wallet.Key) NodeOpt { + return func(opts *NodeOpts) error { + opts.ownerKey = wk + return nil + } +} + +// FullNode enrolls a new full node. +func (n *Ensemble) FullNode(full *TestFullNode, opts ...NodeOpt) *Ensemble { + options := DefaultNodeOpts + for _, o := range opts { + err := o(&options) + require.NoError(n.t, err) + } + + var key *wallet.Key + if !n.bootstrapped && !options.balance.IsZero() { + // create a key+ddress, and assign it some FIL. + // this will be set as the default wallet. + var err error + key, err = wallet.GenerateKey(types.KTBLS) + require.NoError(n.t, err) + + genacc := genesis.Actor{ + Type: genesis.TAccount, + Balance: options.balance, + Meta: (&genesis.AccountMeta{Owner: key.Address}).ActorMeta(), + } + + n.genesis.accounts = append(n.genesis.accounts, genacc) + } + + *full = TestFullNode{options: options, DefaultKey: key} + n.inactive.fullnodes = append(n.inactive.fullnodes, full) + return n +} + +// Miner enrolls a new miner, using the provided full node for chain +// interactions. +func (n *Ensemble) Miner(miner *TestMiner, full *TestFullNode, opts ...NodeOpt) *Ensemble { + require.NotNil(n.t, full, "full node required when instantiating miner") + + options := DefaultNodeOpts + for _, o := range opts { + err := o(&options) + require.NoError(n.t, err) + } + + privkey, _, err := libp2pcrypto.GenerateEd25519Key(rand.Reader) + require.NoError(n.t, err) + + peerId, err := peer.IDFromPrivateKey(privkey) + require.NoError(n.t, err) + + tdir, err := ioutil.TempDir("", "preseal-memgen") + require.NoError(n.t, err) + + minerCnt := len(n.inactive.miners) + len(n.active.miners) + + actorAddr, err := address.NewIDAddress(genesis2.MinerStart + uint64(minerCnt)) + require.NoError(n.t, err) + + ownerKey := options.ownerKey + if !n.bootstrapped { + var ( + sectors = options.sectors + k *types.KeyInfo + genm *genesis.Miner + ) + + // create the preseal commitment. + if options.mockProofs { + genm, k, err = mockstorage.PreSeal(abi.RegisteredSealProof_StackedDrg2KiBV1, actorAddr, sectors) + } else { + genm, k, err = seed.PreSeal(actorAddr, abi.RegisteredSealProof_StackedDrg2KiBV1, 0, sectors, tdir, []byte("make genesis mem random"), nil, true) + } + require.NoError(n.t, err) + + genm.PeerId = peerId + + // create an owner key, and assign it some FIL. + ownerKey, err = wallet.NewKey(*k) + require.NoError(n.t, err) + + genacc := genesis.Actor{ + Type: genesis.TAccount, + Balance: options.balance, + Meta: (&genesis.AccountMeta{Owner: ownerKey.Address}).ActorMeta(), + } + + n.genesis.miners = append(n.genesis.miners, *genm) + n.genesis.accounts = append(n.genesis.accounts, genacc) + } else { + require.NotNil(n.t, ownerKey, "worker key can't be null if initializing a miner after genesis") + } + + *miner = TestMiner{ + ActorAddr: actorAddr, + OwnerKey: ownerKey, + FullNode: full, + PresealDir: tdir, + options: options, + } + + miner.Libp2p.PeerID = peerId + miner.Libp2p.PrivKey = privkey + + n.inactive.miners = append(n.inactive.miners, miner) + + return n +} + +// Start starts all enrolled nodes. +func (n *Ensemble) Start() *Ensemble { + ctx, cancel := context.WithCancel(context.Background()) + n.t.Cleanup(cancel) + + var gtempl *genesis.Template + if !n.bootstrapped { + // We haven't been bootstrapped yet, we need to generate genesis and + // create the networking backbone. + gtempl = n.generateGenesis() + n.mn = mocknet.New(ctx) + } + + // Create all inactive full nodes. + for i, full := range n.inactive.fullnodes { + opts := []node.Option{ + node.FullAPI(&full.FullNode, node.Lite(full.options.lite)), + node.Online(), + node.Repo(repo.NewMemory(nil)), + node.MockHost(n.mn), + node.Test(), + + // so that we subscribe to pubsub topics immediately + node.Override(new(dtypes.Bootstrapper), dtypes.Bootstrapper(true)), + } + + // Either generate the genesis or inject it. + if i == 0 && !n.bootstrapped { + opts = append(opts, node.Override(new(modules.Genesis), testing2.MakeGenesisMem(&n.genesisBlock, *gtempl))) + } else { + opts = append(opts, node.Override(new(modules.Genesis), modules.LoadGenesis(n.genesisBlock.Bytes()))) + } + + // Are we mocking proofs? + if full.options.mockProofs { + opts = append(opts, node.Override(new(ffiwrapper.Verifier), mock.MockVerifier)) + } + + // Construct the full node. + stop, err := node.New(ctx, opts...) + + // fullOpts[i].Opts(fulls), + require.NoError(n.t, err) + + addr, err := full.WalletImport(context.Background(), &full.DefaultKey.KeyInfo) + require.NoError(n.t, err) + + err = full.WalletSetDefault(context.Background(), addr) + require.NoError(n.t, err) + + // Are we hitting this node through its RPC? + if full.options.rpc { + withRPC := fullRpc(n.t, full) + n.inactive.fullnodes[i] = withRPC + } + + n.t.Cleanup(func() { _ = stop(context.Background()) }) + + n.active.fullnodes = append(n.active.fullnodes, full) + } + + // If we are here, we have processed all inactive fullnodes and moved them + // to active, so clear the slice. + n.inactive.fullnodes = n.inactive.fullnodes[:0] + + // Link all the nodes. + err := n.mn.LinkAll() + require.NoError(n.t, err) + + // Create all inactive miners. + for i, m := range n.inactive.miners { + if n.bootstrapped { + // this is a miner created after genesis, so it won't have a preseal. + // we need to create it on chain. + params, aerr := actors.SerializeParams(&power2.CreateMinerParams{ + Owner: m.OwnerKey.Address, + Worker: m.OwnerKey.Address, + SealProofType: n.options.spt, + Peer: abi.PeerID(m.Libp2p.PeerID), + }) + require.NoError(n.t, aerr) + + createStorageMinerMsg := &types.Message{ + From: m.OwnerKey.Address, + To: power.Address, + Value: big.Zero(), + + Method: power.Methods.CreateMiner, + Params: params, + + GasLimit: 0, + GasPremium: big.NewInt(5252), + } + signed, err := m.FullNode.FullNode.MpoolPushMessage(ctx, createStorageMinerMsg, nil) + require.NoError(n.t, err) + + mw, err := m.FullNode.FullNode.StateWaitMsg(ctx, signed.Cid(), build.MessageConfidence, api.LookbackNoLimit, true) + require.NoError(n.t, err) + require.Equal(n.t, exitcode.Ok, mw.Receipt.ExitCode) + + var retval power2.CreateMinerReturn + err = retval.UnmarshalCBOR(bytes.NewReader(mw.Receipt.Return)) + require.NoError(n.t, err, "failed to create miner") + + m.ActorAddr = retval.IDAddress + } + + has, err := m.FullNode.WalletHas(ctx, m.OwnerKey.Address) + require.NoError(n.t, err) + + // Only import the owner's full key into our companion full node, if we + // don't have it still. + if !has { + _, err = m.FullNode.WalletImport(ctx, &m.OwnerKey.KeyInfo) + require.NoError(n.t, err) + } + + // // Set it as the default address. + // err = m.FullNode.WalletSetDefault(ctx, m.OwnerAddr.Address) + // require.NoError(n.t, err) + + r := repo.NewMemory(nil) + + lr, err := r.Lock(repo.StorageMiner) + require.NoError(n.t, err) + + ks, err := lr.KeyStore() + require.NoError(n.t, err) + + pk, err := m.Libp2p.PrivKey.Bytes() + require.NoError(n.t, err) + + err = ks.Put("libp2p-host", types.KeyInfo{ + Type: "libp2p-host", + PrivateKey: pk, + }) + require.NoError(n.t, err) + + ds, err := lr.Datastore(context.TODO(), "/metadata") + require.NoError(n.t, err) + + err = ds.Put(datastore.NewKey("miner-address"), m.ActorAddr.Bytes()) + require.NoError(n.t, err) + + nic := storedcounter.New(ds, datastore.NewKey(modules.StorageCounterDSPrefix)) + for i := 0; i < m.options.sectors; i++ { + _, err := nic.Next() + require.NoError(n.t, err) + } + _, err = nic.Next() + require.NoError(n.t, err) + + err = lr.Close() + require.NoError(n.t, err) + + enc, err := actors.SerializeParams(&miner2.ChangePeerIDParams{NewID: abi.PeerID(m.Libp2p.PeerID)}) + require.NoError(n.t, err) + + msg := &types.Message{ + From: m.OwnerKey.Address, + To: m.ActorAddr, + Method: miner.Methods.ChangePeerID, + Params: enc, + Value: types.NewInt(0), + } + + _, err = m.FullNode.MpoolPushMessage(ctx, msg, nil) + require.NoError(n.t, err) + + var mineBlock = make(chan lotusminer.MineReq) + opts := []node.Option{ + node.StorageMiner(&m.StorageMiner), + node.Online(), + node.Repo(r), + node.Test(), + + node.MockHost(n.mn), + + node.Override(new(v1api.FullNode), m.FullNode), + node.Override(new(*lotusminer.Miner), lotusminer.NewTestMiner(mineBlock, m.ActorAddr)), + } + + idAddr, err := address.IDFromAddress(m.ActorAddr) + require.NoError(n.t, err) + + if !n.bootstrapped && m.options.mockProofs { + s := n.genesis.miners[i].Sectors + sectors := make([]abi.SectorID, len(s)) + for i, sector := range s { + sectors[i] = abi.SectorID{ + Miner: abi.ActorID(idAddr), + Number: sector.SectorID, + } + } + opts = append(opts, + node.Override(new(sectorstorage.SectorManager), func() (sectorstorage.SectorManager, error) { + return mock.NewMockSectorMgr(sectors), nil + }), + node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), + node.Unset(new(*sectorstorage.Manager)), + ) + } + + // start node + stop, err := node.New(ctx, opts...) + require.NoError(n.t, err) + + // using real proofs, therefore need real sectors. + if !n.bootstrapped && !m.options.mockProofs { + err := m.StorageAddLocal(ctx, m.PresealDir) + require.NoError(n.t, err) + } + + n.t.Cleanup(func() { _ = stop(context.Background()) }) + + // Are we hitting this node through its RPC? + if m.options.rpc { + withRPC := minerRpc(n.t, m) + n.inactive.miners[i] = withRPC + } + + mineOne := func(ctx context.Context, req lotusminer.MineReq) error { + select { + case mineBlock <- req: + return nil + case <-ctx.Done(): + return ctx.Err() + } + } + + m.MineOne = mineOne + m.Stop = stop + + n.active.miners = append(n.active.miners, m) + } + + // If we are here, we have processed all inactive miners and moved them + // to active, so clear the slice. + n.inactive.miners = n.inactive.miners[:0] + + // Link all the nodes. + err = n.mn.LinkAll() + require.NoError(n.t, err) + + if !n.bootstrapped && len(n.active.miners) > 0 { + // We have *just* bootstrapped, so + // mine 2 blocks to setup some CE stuff + // in some actors + var wait sync.Mutex + wait.Lock() + + observer := n.active.fullnodes[0] + + bm := NewBlockMiner(n.t, n.active.miners[0]) + n.t.Cleanup(bm.Stop) + + bm.MineUntilBlock(ctx, observer, func(epoch abi.ChainEpoch) { + wait.Unlock() + }) + wait.Lock() + bm.MineUntilBlock(ctx, observer, func(epoch abi.ChainEpoch) { + wait.Unlock() + }) + wait.Lock() + } + + n.bootstrapped = true + return n +} + +// InterconnectAll connects all full nodes one to another. We do not need to +// take action with miners, because miners only stay connected to their full +// nodes over JSON-RPC. +func (n *Ensemble) InterconnectAll() *Ensemble { + last := len(n.active.fullnodes) - 1 + for i, from := range n.active.fullnodes { + if i == last { + continue + } + n.Connect(from, n.active.fullnodes[i+1:]...) + } + return n +} + +// Connect connects one full node to the provided full nodes. +func (n *Ensemble) Connect(from *TestFullNode, to ...*TestFullNode) *Ensemble { + addr, err := from.NetAddrsListen(context.Background()) + require.NoError(n.t, err) + + for _, other := range to { + err = other.NetConnect(context.Background(), addr) + require.NoError(n.t, err) + } + return n +} + +// BeginMining kicks off mining for the specified miners. If nil or 0-length, +// it will kick off mining for all enrolled and active miners. +func (n *Ensemble) BeginMining(blocktime time.Duration, miners ...*TestMiner) []*BlockMiner { + ctx := context.Background() + + // wait one second to make sure that nodes are connected and have handshaken. + // TODO make this deterministic by listening to identify events on the + // libp2p eventbus instead (or something else). + time.Sleep(1 * time.Second) + + var bms []*BlockMiner + if len(miners) == 0 { + miners = n.active.miners + } + + for _, m := range miners { + bm := NewBlockMiner(n.t, m) + bm.MineBlocks(ctx, blocktime) + bms = append(bms, bm) + } + + return bms +} + +func (n *Ensemble) generateGenesis() *genesis.Template { + templ := &genesis.Template{ + Accounts: n.genesis.accounts, + Miners: n.genesis.miners, + NetworkName: "test", + Timestamp: uint64(time.Now().Unix() - 10000), // some time sufficiently far in the past + VerifregRootKey: gen.DefaultVerifregRootkeyActor, + RemainderAccount: gen.DefaultRemainderAccountActor, + } + + return templ +} + +func CreateRPCServer(t *testing.T, handler http.Handler) (*httptest.Server, multiaddr.Multiaddr) { + testServ := httptest.NewServer(handler) + t.Cleanup(testServ.Close) + t.Cleanup(testServ.CloseClientConnections) + + addr := testServ.Listener.Addr() + maddr, err := manet.FromNetAddr(addr) + require.NoError(t, err) + return testServ, maddr +} + +func fullRpc(t *testing.T, f *TestFullNode) *TestFullNode { + tok, err := f.FullNode.AuthNew(context.Background(), []auth.Permission{"admin", "read", "write", "sign"}) + require.NoError(t, err) + + handler, err := node.FullNodeHandler(f.FullNode) + require.NoError(t, err) + + srv, maddr := CreateRPCServer(t, handler) + + cl, stop, err := client.NewFullNodeRPCV1(context.Background(), "ws://"+srv.Listener.Addr().String()+"/rpc/v1", map[string][]string{ + "Authorization": {"Bearer " + string(tok)}, + }) + require.NoError(t, err) + t.Cleanup(stop) + f.ListenAddr, f.FullNode = maddr, cl + + return f +} + +func minerRpc(t *testing.T, m *TestMiner) *TestMiner { + tok, err := m.StorageMiner.AuthNew(context.Background(), []auth.Permission{"admin", "read", "write"}) + require.NoError(t, err) + + handler, err := node.MinerHandler(m.StorageMiner) + require.NoError(t, err) + + srv, maddr := CreateRPCServer(t, handler) + + cl, stop, err := client.NewStorageMinerRPCV0(context.Background(), "ws://"+srv.Listener.Addr().String()+"/rpc/v0", map[string][]string{ + "Authorization": {"Bearer " + string(tok)}, + }) + require.NoError(t, err) + t.Cleanup(stop) + + m.ListenAddr, m.StorageMiner = maddr, cl + return m +} + +func LatestActorsAt(upgradeHeight abi.ChainEpoch) node.Option { + if upgradeHeight == -1 { + upgradeHeight = 3 + } + + return node.Override(new(stmgr.UpgradeSchedule), stmgr.UpgradeSchedule{{ + // prepare for upgrade. + Network: network.Version9, + Height: 1, + Migration: stmgr.UpgradeActorsV2, + }, { + Network: network.Version10, + Height: 2, + Migration: stmgr.UpgradeActorsV3, + }, { + Network: network.Version12, + Height: upgradeHeight, + Migration: stmgr.UpgradeActorsV4, + }}) +} + +func SDRUpgradeAt(calico, persian abi.ChainEpoch) node.Option { + return node.Override(new(stmgr.UpgradeSchedule), stmgr.UpgradeSchedule{{ + Network: network.Version6, + Height: 1, + Migration: stmgr.UpgradeActorsV2, + }, { + Network: network.Version7, + Height: calico, + Migration: stmgr.UpgradeCalico, + }, { + Network: network.Version8, + Height: persian, + }}) + +} diff --git a/itests/kit/ensemble_presets.go b/itests/kit/ensemble_presets.go new file mode 100644 index 000000000..debad2ed1 --- /dev/null +++ b/itests/kit/ensemble_presets.go @@ -0,0 +1,21 @@ +package kit + +import "testing" + +func EnsembleMinimum(t *testing.T, opts ...NodeOpt) (*TestFullNode, *TestMiner, *Ensemble) { + var ( + full TestFullNode + miner TestMiner + ) + ensemble := NewEnsemble(t).FullNode(&full, opts...).Miner(&miner, &full, opts...).Start() + return &full, &miner, ensemble +} + +func EnsembleTwo(t *testing.T, opts ...NodeOpt) (*TestFullNode, *TestFullNode, *TestMiner, *Ensemble) { + var ( + one, two TestFullNode + miner TestMiner + ) + ensemble := NewEnsemble(t).FullNode(&one, opts...).FullNode(&two, opts...).Miner(&miner, &one, opts...).Start() + return &one, &two, &miner, ensemble +} diff --git a/itests/kit/funds.go b/itests/kit/funds.go index 4c739dc62..2ea822979 100644 --- a/itests/kit/funds.go +++ b/itests/kit/funds.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/filecoin-project/go-state-types/abi" + "github.com/stretchr/testify/require" "github.com/filecoin-project/go-address" "github.com/filecoin-project/lotus/api" @@ -15,9 +16,7 @@ import ( // to the recipient address. func SendFunds(ctx context.Context, t *testing.T, sender TestFullNode, recipient address.Address, amount abi.TokenAmount) { senderAddr, err := sender.WalletDefaultAddress(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) msg := &types.Message{ From: senderAddr, @@ -26,14 +25,10 @@ func SendFunds(ctx context.Context, t *testing.T, sender TestFullNode, recipient } sm, err := sender.MpoolPushMessage(ctx, msg, nil) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + res, err := sender.StateWaitMsg(ctx, sm.Cid(), 3, api.LookbackNoLimit, true) - if err != nil { - t.Fatal(err) - } - if res.Receipt.ExitCode != 0 { - t.Fatal("did not successfully send money") - } + require.NoError(t, err) + + require.Equal(t, 0, res.Receipt.ExitCode, "did not successfully send funds") } diff --git a/itests/kit/init.go b/itests/kit/init.go new file mode 100644 index 000000000..57d60ad2a --- /dev/null +++ b/itests/kit/init.go @@ -0,0 +1,25 @@ +package kit + +import ( + "fmt" + "os" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/build" + "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)) + + err := os.Setenv("BELLMAN_NO_GPU", "1") + if err != nil { + panic(fmt.Sprintf("failed to set BELLMAN_NO_GPU env variable: %s", err)) + } + build.InsecurePoStValidation = true +} diff --git a/itests/kit/net.go b/itests/kit/net.go index 54c72443f..aea609091 100644 --- a/itests/kit/net.go +++ b/itests/kit/net.go @@ -1,87 +1,42 @@ package kit -import ( - "context" - "testing" - "time" - - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/lotus/chain/types" - - "github.com/filecoin-project/go-address" -) - -func StartOneNodeOneMiner(ctx context.Context, t *testing.T, blocktime time.Duration) (TestFullNode, address.Address) { - n, sn := RPCMockMinerBuilder(t, OneFull, OneMiner) - - full := n[0] - miner := sn[0] - - // Get everyone connected - addrs, err := full.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrs); err != nil { - t.Fatal(err) - } - - // Start mining blocks - bm := NewBlockMiner(t, miner) - bm.MineBlocks(ctx, blocktime) - t.Cleanup(bm.Stop) - - // Get the full node's wallet address - fullAddr, err := full.WalletDefaultAddress(ctx) - if err != nil { - t.Fatal(err) - } - - // Create mock CLI - return full, fullAddr -} - -func StartTwoNodesOneMiner(ctx context.Context, t *testing.T, blocktime time.Duration) ([]TestFullNode, []address.Address) { - n, sn := RPCMockMinerBuilder(t, TwoFull, OneMiner) - - fullNode1 := n[0] - fullNode2 := n[1] - miner := sn[0] - - // Get everyone connected - addrs, err := fullNode1.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := fullNode2.NetConnect(ctx, addrs); err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrs); err != nil { - t.Fatal(err) - } - - // Start mining blocks - bm := NewBlockMiner(t, miner) - bm.MineBlocks(ctx, blocktime) - t.Cleanup(bm.Stop) - - // Send some funds to register the second node - fullNodeAddr2, err := fullNode2.WalletNew(ctx, types.KTSecp256k1) - if err != nil { - t.Fatal(err) - } - - SendFunds(ctx, t, fullNode1, fullNodeAddr2, abi.NewTokenAmount(1e18)) - - // Get the first node's address - fullNodeAddr1, err := fullNode1.WalletDefaultAddress(ctx) - if err != nil { - t.Fatal(err) - } - - // Create mock CLI - return n, []address.Address{fullNodeAddr1, fullNodeAddr2} -} +// +// func StartTwoNodesOneMiner(ctx context.Context, t *testing.T, blocktime time.Duration) ([]TestFullNode, []address.Address) { +// n, sn := MinerRPCMockMinerBuilder(t, TwoFull, OneMiner) +// +// fullNode1 := n[0] +// fullNode2 := n[1] +// miner := sn[0] +// +// // Get everyone connected +// addrs, err := fullNode1.NetAddrsListen(ctx) +// if err != nil { +// t.Fatal(err) +// } +// +// if err := fullNode2.NetConnect(ctx, addrs); err != nil { +// t.Fatal(err) +// } +// +// // Start mining blocks +// bm := NewBlockMiner(t, miner) +// bm.MineBlocks(ctx, blocktime) +// t.Cleanup(bm.Stop) +// +// // Send some funds to register the second node +// fullNodeAddr2, err := fullNode2.WalletNew(ctx, types.KTSecp256k1) +// if err != nil { +// t.Fatal(err) +// } +// +// SendFunds(ctx, t, fullNode1, fullNodeAddr2, abi.NewTokenAmount(1e18)) +// +// // Get the first node's address +// fullNodeAddr1, err := fullNode1.WalletDefaultAddress(ctx) +// if err != nil { +// t.Fatal(err) +// } +// +// // Create mock CLI +// return n, []address.Address{fullNodeAddr1, fullNodeAddr2} +// } diff --git a/itests/kit/node_builder.go b/itests/kit/node_builder.go deleted file mode 100644 index 4115a1d2f..000000000 --- a/itests/kit/node_builder.go +++ /dev/null @@ -1,606 +0,0 @@ -package kit - -import ( - "bytes" - "context" - "crypto/rand" - "io/ioutil" - "net/http" - "net/http/httptest" - "sync" - "testing" - "time" - - "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/exitcode" - "github.com/filecoin-project/go-storedcounter" - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/api/client" - "github.com/filecoin-project/lotus/api/v1api" - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain" - "github.com/filecoin-project/lotus/chain/actors" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/actors/builtin/power" - "github.com/filecoin-project/lotus/chain/gen" - genesis2 "github.com/filecoin-project/lotus/chain/gen/genesis" - "github.com/filecoin-project/lotus/chain/messagepool" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/chain/wallet" - "github.com/filecoin-project/lotus/cmd/lotus-seed/seed" - sectorstorage "github.com/filecoin-project/lotus/extern/sector-storage" - "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" - "github.com/filecoin-project/lotus/extern/sector-storage/mock" - "github.com/filecoin-project/lotus/genesis" - lotusminer "github.com/filecoin-project/lotus/miner" - "github.com/filecoin-project/lotus/node" - "github.com/filecoin-project/lotus/node/modules" - "github.com/filecoin-project/lotus/node/modules/dtypes" - testing2 "github.com/filecoin-project/lotus/node/modules/testing" - "github.com/filecoin-project/lotus/node/repo" - "github.com/filecoin-project/lotus/storage/mockstorage" - miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" - power2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/power" - "github.com/ipfs/go-datastore" - "github.com/libp2p/go-libp2p-core/crypto" - "github.com/libp2p/go-libp2p-core/peer" - mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" - "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr/net" - "github.com/stretchr/testify/require" -) - -func init() { - chain.BootstrapPeerThreshold = 1 - messagepool.HeadChangeCoalesceMinDelay = time.Microsecond - messagepool.HeadChangeCoalesceMaxDelay = 2 * time.Microsecond - messagepool.HeadChangeCoalesceMergeInterval = 100 * time.Nanosecond -} - -func CreateTestStorageNode(ctx context.Context, t *testing.T, waddr address.Address, act address.Address, pk crypto.PrivKey, tnd TestFullNode, mn mocknet.Mocknet, opts node.Option) TestMiner { - r := repo.NewMemory(nil) - - lr, err := r.Lock(repo.StorageMiner) - require.NoError(t, err) - - ks, err := lr.KeyStore() - require.NoError(t, err) - - kbytes, err := pk.Bytes() - require.NoError(t, err) - - err = ks.Put("libp2p-host", types.KeyInfo{ - Type: "libp2p-host", - PrivateKey: kbytes, - }) - require.NoError(t, err) - - ds, err := lr.Datastore(context.TODO(), "/metadata") - require.NoError(t, err) - err = ds.Put(datastore.NewKey("miner-address"), act.Bytes()) - require.NoError(t, err) - - nic := storedcounter.New(ds, datastore.NewKey(modules.StorageCounterDSPrefix)) - for i := 0; i < GenesisPreseals; i++ { - _, err := nic.Next() - require.NoError(t, err) - } - _, err = nic.Next() - require.NoError(t, err) - - err = lr.Close() - require.NoError(t, err) - - peerid, err := peer.IDFromPrivateKey(pk) - require.NoError(t, err) - - enc, err := actors.SerializeParams(&miner2.ChangePeerIDParams{NewID: abi.PeerID(peerid)}) - require.NoError(t, err) - - msg := &types.Message{ - To: act, - From: waddr, - Method: miner.Methods.ChangePeerID, - Params: enc, - Value: types.NewInt(0), - } - - _, err = tnd.MpoolPushMessage(ctx, msg, nil) - require.NoError(t, err) - - // start node - var minerapi api.StorageMiner - - mineBlock := make(chan lotusminer.MineReq) - stop, err := node.New(ctx, - node.StorageMiner(&minerapi), - node.Online(), - node.Repo(r), - node.Test(), - - node.MockHost(mn), - - node.Override(new(v1api.FullNode), tnd), - node.Override(new(*lotusminer.Miner), lotusminer.NewTestMiner(mineBlock, act)), - - opts, - ) - if err != nil { - t.Fatalf("failed to construct node: %v", err) - } - - t.Cleanup(func() { _ = stop(context.Background()) }) - - /*// Bootstrap with full node - remoteAddrs, err := tnd.NetAddrsListen(Ctx) - require.NoError(t, err) - - err = minerapi.NetConnect(Ctx, remoteAddrs) - require.NoError(t, err)*/ - mineOne := func(ctx context.Context, req lotusminer.MineReq) error { - select { - case mineBlock <- req: - return nil - case <-ctx.Done(): - return ctx.Err() - } - } - - return TestMiner{StorageMiner: minerapi, MineOne: mineOne, Stop: stop} -} - -func storageBuilder(parentNode TestFullNode, mn mocknet.Mocknet, opts node.Option) MinerBuilder { - return func(ctx context.Context, t *testing.T, spt abi.RegisteredSealProof, owner address.Address) TestMiner { - pk, _, err := crypto.GenerateEd25519Key(rand.Reader) - require.NoError(t, err) - - minerPid, err := peer.IDFromPrivateKey(pk) - require.NoError(t, err) - - params, serr := actors.SerializeParams(&power2.CreateMinerParams{ - Owner: owner, - Worker: owner, - SealProofType: spt, - Peer: abi.PeerID(minerPid), - }) - require.NoError(t, serr) - - createStorageMinerMsg := &types.Message{ - To: power.Address, - From: owner, - Value: big.Zero(), - - Method: power.Methods.CreateMiner, - Params: params, - - GasLimit: 0, - GasPremium: big.NewInt(5252), - } - - signed, err := parentNode.MpoolPushMessage(ctx, createStorageMinerMsg, nil) - require.NoError(t, err) - - mw, err := parentNode.StateWaitMsg(ctx, signed.Cid(), build.MessageConfidence, api.LookbackNoLimit, true) - require.NoError(t, err) - require.Equal(t, exitcode.Ok, mw.Receipt.ExitCode) - - var retval power2.CreateMinerReturn - err = retval.UnmarshalCBOR(bytes.NewReader(mw.Receipt.Return)) - require.NoError(t, err) - - return CreateTestStorageNode(ctx, t, owner, retval.IDAddress, pk, parentNode, mn, opts) - } -} - -func Builder(t *testing.T, fullOpts []FullNodeOpts, storage []StorageMiner) ([]TestFullNode, []TestMiner) { - return mockBuilderOpts(t, fullOpts, storage, false) -} - -func RPCBuilder(t *testing.T, fullOpts []FullNodeOpts, storage []StorageMiner) ([]TestFullNode, []TestMiner) { - return mockBuilderOpts(t, fullOpts, storage, true) -} - -func MockMinerBuilder(t *testing.T, fullOpts []FullNodeOpts, storage []StorageMiner) ([]TestFullNode, []TestMiner) { - return mockMinerBuilderOpts(t, fullOpts, storage, false) -} - -func RPCMockMinerBuilder(t *testing.T, fullOpts []FullNodeOpts, storage []StorageMiner) ([]TestFullNode, []TestMiner) { - return mockMinerBuilderOpts(t, fullOpts, storage, true) -} - -func mockBuilderOpts(t *testing.T, fullOpts []FullNodeOpts, storage []StorageMiner, rpc bool) ([]TestFullNode, []TestMiner) { - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - mn := mocknet.New(ctx) - - fulls := make([]TestFullNode, len(fullOpts)) - miners := make([]TestMiner, len(storage)) - - // ***** - pk, _, err := crypto.GenerateEd25519Key(rand.Reader) - require.NoError(t, err) - - minerPid, err := peer.IDFromPrivateKey(pk) - require.NoError(t, err) - - var genbuf bytes.Buffer - - if len(storage) > 1 { - panic("need more peer IDs") - } - // ***** - - // PRESEAL SECTION, TRY TO REPLACE WITH BETTER IN THE FUTURE - // TODO: would be great if there was a better way to fake the preseals - - var ( - genms []genesis.Miner - maddrs []address.Address - genaccs []genesis.Actor - keys []*wallet.Key - ) - - var presealDirs []string - for i := 0; i < len(storage); i++ { - maddr, err := address.NewIDAddress(genesis2.MinerStart + uint64(i)) - if err != nil { - t.Fatal(err) - } - tdir, err := ioutil.TempDir("", "preseal-memgen") - if err != nil { - t.Fatal(err) - } - genm, k, err := seed.PreSeal(maddr, abi.RegisteredSealProof_StackedDrg2KiBV1, 0, GenesisPreseals, tdir, []byte("make genesis mem random"), nil, true) - if err != nil { - t.Fatal(err) - } - genm.PeerId = minerPid - - wk, err := wallet.NewKey(*k) - if err != nil { - return nil, nil - } - - genaccs = append(genaccs, genesis.Actor{ - Type: genesis.TAccount, - Balance: big.Mul(big.NewInt(400000000), types.NewInt(build.FilecoinPrecision)), - Meta: (&genesis.AccountMeta{Owner: wk.Address}).ActorMeta(), - }) - - keys = append(keys, wk) - presealDirs = append(presealDirs, tdir) - maddrs = append(maddrs, maddr) - genms = append(genms, *genm) - } - templ := &genesis.Template{ - Accounts: genaccs, - Miners: genms, - NetworkName: "test", - Timestamp: uint64(time.Now().Unix() - 10000), // some time sufficiently far in the past - VerifregRootKey: gen.DefaultVerifregRootkeyActor, - RemainderAccount: gen.DefaultRemainderAccountActor, - } - - // END PRESEAL SECTION - - for i := 0; i < len(fullOpts); i++ { - var genesis node.Option - if i == 0 { - genesis = node.Override(new(modules.Genesis), testing2.MakeGenesisMem(&genbuf, *templ)) - } else { - genesis = node.Override(new(modules.Genesis), modules.LoadGenesis(genbuf.Bytes())) - } - - stop, err := node.New(ctx, - node.FullAPI(&fulls[i].FullNode, node.Lite(fullOpts[i].Lite)), - node.Online(), - node.Repo(repo.NewMemory(nil)), - node.MockHost(mn), - node.Test(), - - genesis, - - fullOpts[i].Opts(fulls), - ) - if err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { _ = stop(context.Background()) }) - - if rpc { - fulls[i] = fullRpc(t, fulls[i]) - } - - fulls[i].Stb = storageBuilder(fulls[i], mn, node.Options()) - } - - for i, def := range storage { - // TODO: support non-bootstrap miners - if i != 0 { - t.Fatal("only one storage node supported") - } - if def.Full != 0 { - t.Fatal("storage nodes only supported on the first full node") - } - - f := fulls[def.Full] - if _, err := f.FullNode.WalletImport(ctx, &keys[i].KeyInfo); err != nil { - t.Fatal(err) - } - if err := f.FullNode.WalletSetDefault(ctx, keys[i].Address); err != nil { - t.Fatal(err) - } - - genMiner := maddrs[i] - wa := genms[i].Worker - - opts := def.Opts - if opts == nil { - opts = node.Options() - } - miners[i] = CreateTestStorageNode(ctx, t, wa, genMiner, pk, f, mn, opts) - if err := miners[i].StorageAddLocal(ctx, presealDirs[i]); err != nil { - t.Fatalf("%+v", err) - } - /* - sma := miners[i].StorageMiner.(*impl.StorageMinerAPI) - - psd := presealDirs[i] - */ - if rpc { - miners[i] = storerRpc(t, miners[i]) - } - } - - if err := mn.LinkAll(); err != nil { - t.Fatal(err) - } - - if len(miners) > 0 { - // Mine 2 blocks to setup some CE stuff in some actors - var wait sync.Mutex - wait.Lock() - - bm := NewBlockMiner(t, miners[0]) - t.Cleanup(bm.Stop) - - bm.MineUntilBlock(ctx, fulls[0], func(epoch abi.ChainEpoch) { - wait.Unlock() - }) - - wait.Lock() - bm.MineUntilBlock(ctx, fulls[0], func(epoch abi.ChainEpoch) { - wait.Unlock() - }) - wait.Lock() - } - - return fulls, miners -} - -func mockMinerBuilderOpts(t *testing.T, fullOpts []FullNodeOpts, storage []StorageMiner, rpc bool) ([]TestFullNode, []TestMiner) { - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - mn := mocknet.New(ctx) - - fulls := make([]TestFullNode, len(fullOpts)) - miners := make([]TestMiner, len(storage)) - - var genbuf bytes.Buffer - - // PRESEAL SECTION, TRY TO REPLACE WITH BETTER IN THE FUTURE - // TODO: would be great if there was a better way to fake the preseals - - var ( - genms []genesis.Miner - genaccs []genesis.Actor - maddrs []address.Address - keys []*wallet.Key - pidKeys []crypto.PrivKey - ) - for i := 0; i < len(storage); i++ { - maddr, err := address.NewIDAddress(genesis2.MinerStart + uint64(i)) - if err != nil { - t.Fatal(err) - } - - preseals := storage[i].Preseal - if preseals == PresealGenesis { - preseals = GenesisPreseals - } - - genm, k, err := mockstorage.PreSeal(abi.RegisteredSealProof_StackedDrg2KiBV1, maddr, preseals) - if err != nil { - t.Fatal(err) - } - - pk, _, err := crypto.GenerateEd25519Key(rand.Reader) - require.NoError(t, err) - - minerPid, err := peer.IDFromPrivateKey(pk) - require.NoError(t, err) - - genm.PeerId = minerPid - - wk, err := wallet.NewKey(*k) - if err != nil { - return nil, nil - } - - genaccs = append(genaccs, genesis.Actor{ - Type: genesis.TAccount, - Balance: big.Mul(big.NewInt(400000000), types.NewInt(build.FilecoinPrecision)), - Meta: (&genesis.AccountMeta{Owner: wk.Address}).ActorMeta(), - }) - - keys = append(keys, wk) - pidKeys = append(pidKeys, pk) - maddrs = append(maddrs, maddr) - genms = append(genms, *genm) - } - templ := &genesis.Template{ - Accounts: genaccs, - Miners: genms, - NetworkName: "test", - Timestamp: uint64(time.Now().Unix()) - (build.BlockDelaySecs * 20000), - VerifregRootKey: gen.DefaultVerifregRootkeyActor, - RemainderAccount: gen.DefaultRemainderAccountActor, - } - - // END PRESEAL SECTION - - for i := 0; i < len(fullOpts); i++ { - var genesis node.Option - if i == 0 { - genesis = node.Override(new(modules.Genesis), testing2.MakeGenesisMem(&genbuf, *templ)) - } else { - genesis = node.Override(new(modules.Genesis), modules.LoadGenesis(genbuf.Bytes())) - } - - stop, err := node.New(ctx, - node.FullAPI(&fulls[i].FullNode, node.Lite(fullOpts[i].Lite)), - node.Online(), - node.Repo(repo.NewMemory(nil)), - node.MockHost(mn), - node.Test(), - - node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), - - // so that we subscribe to pubsub topics immediately - node.Override(new(dtypes.Bootstrapper), dtypes.Bootstrapper(true)), - - genesis, - - fullOpts[i].Opts(fulls), - ) - if err != nil { - t.Fatalf("%+v", err) - } - - t.Cleanup(func() { _ = stop(context.Background()) }) - - if rpc { - fulls[i] = fullRpc(t, fulls[i]) - } - - fulls[i].Stb = storageBuilder(fulls[i], mn, node.Options( - node.Override(new(sectorstorage.SectorManager), func() (sectorstorage.SectorManager, error) { - return mock.NewMockSectorMgr(nil), nil - }), - node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), - node.Unset(new(*sectorstorage.Manager)), - )) - } - - for i, def := range storage { - // TODO: support non-bootstrap miners - - minerID := abi.ActorID(genesis2.MinerStart + uint64(i)) - - if def.Full != 0 { - t.Fatal("storage nodes only supported on the first full node") - } - - f := fulls[def.Full] - if _, err := f.FullNode.WalletImport(ctx, &keys[i].KeyInfo); err != nil { - return nil, nil - } - if err := f.FullNode.WalletSetDefault(ctx, keys[i].Address); err != nil { - return nil, nil - } - - sectors := make([]abi.SectorID, len(genms[i].Sectors)) - for i, sector := range genms[i].Sectors { - sectors[i] = abi.SectorID{ - Miner: minerID, - Number: sector.SectorID, - } - } - - opts := def.Opts - if opts == nil { - opts = node.Options() - } - miners[i] = CreateTestStorageNode(ctx, t, genms[i].Worker, maddrs[i], pidKeys[i], f, mn, node.Options( - node.Override(new(sectorstorage.SectorManager), func() (sectorstorage.SectorManager, error) { - return mock.NewMockSectorMgr(sectors), nil - }), - node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), - node.Unset(new(*sectorstorage.Manager)), - opts, - )) - - if rpc { - miners[i] = storerRpc(t, miners[i]) - } - } - - if err := mn.LinkAll(); err != nil { - t.Fatal(err) - } - - bm := NewBlockMiner(t, miners[0]) - - if len(miners) > 0 { - // Mine 2 blocks to setup some CE stuff in some actors - var wait sync.Mutex - wait.Lock() - - bm.MineUntilBlock(ctx, fulls[0], func(abi.ChainEpoch) { - wait.Unlock() - }) - wait.Lock() - bm.MineUntilBlock(ctx, fulls[0], func(abi.ChainEpoch) { - wait.Unlock() - }) - wait.Lock() - } - - return fulls, miners -} - -func CreateRPCServer(t *testing.T, handler http.Handler) (*httptest.Server, multiaddr.Multiaddr) { - testServ := httptest.NewServer(handler) - t.Cleanup(testServ.Close) - t.Cleanup(testServ.CloseClientConnections) - - addr := testServ.Listener.Addr() - maddr, err := manet.FromNetAddr(addr) - require.NoError(t, err) - return testServ, maddr -} - -func fullRpc(t *testing.T, nd TestFullNode) TestFullNode { - handler, err := node.FullNodeHandler(nd.FullNode) - require.NoError(t, err) - - srv, maddr := CreateRPCServer(t, handler) - - var ret TestFullNode - cl, stop, err := client.NewFullNodeRPCV1(context.Background(), srv.Listener.Addr().String()+"/rpc/v1", nil) - require.NoError(t, err) - t.Cleanup(stop) - ret.ListenAddr, ret.FullNode = maddr, cl - - return ret -} - -func storerRpc(t *testing.T, nd TestMiner) TestMiner { - handler, err := node.MinerHandler(nd.StorageMiner) - require.NoError(t, err) - - srv, maddr := CreateRPCServer(t, handler) - - var ret TestMiner - cl, stop, err := client.NewStorageMinerRPCV0(context.Background(), srv.Listener.Addr().String()+"/rpc/v0", nil) - require.NoError(t, err) - t.Cleanup(stop) - - ret.ListenAddr, ret.StorageMiner, ret.MineOne = maddr, cl, nd.MineOne - return ret -} diff --git a/itests/kit/node_full.go b/itests/kit/node_full.go new file mode 100644 index 000000000..f8f13c724 --- /dev/null +++ b/itests/kit/node_full.go @@ -0,0 +1,22 @@ +package kit + +import ( + "testing" + + "github.com/filecoin-project/lotus/api/v1api" + "github.com/filecoin-project/lotus/chain/wallet" + "github.com/multiformats/go-multiaddr" +) + +type TestFullNode struct { + v1api.FullNode + + t *testing.T + + // ListenAddr is the address on which an API server is listening, if an + // API server is created for this Node. + ListenAddr multiaddr.Multiaddr + DefaultKey *wallet.Key + + options NodeOpts +} diff --git a/itests/kit/node_miner.go b/itests/kit/node_miner.go new file mode 100644 index 000000000..22278b64b --- /dev/null +++ b/itests/kit/node_miner.go @@ -0,0 +1,97 @@ +package kit + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + lapi "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/wallet" + sealing "github.com/filecoin-project/lotus/extern/storage-sealing" + "github.com/filecoin-project/lotus/miner" + libp2pcrypto "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/multiformats/go-multiaddr" + "github.com/stretchr/testify/require" +) + +type TestMiner struct { + lapi.StorageMiner + + t *testing.T + + // ListenAddr is the address on which an API server is listening, if an + // API server is created for this Node + ListenAddr multiaddr.Multiaddr + + ActorAddr address.Address + OwnerKey *wallet.Key + MineOne func(context.Context, miner.MineReq) error + Stop func(context.Context) error + + FullNode *TestFullNode + PresealDir string + + Libp2p struct { + PeerID peer.ID + PrivKey libp2pcrypto.PrivKey + } + + options NodeOpts +} + +var MineNext = miner.MineReq{ + InjectNulls: 0, + Done: func(bool, abi.ChainEpoch, error) {}, +} + +func (tm *TestMiner) PledgeSectors(ctx context.Context, n, existing int, blockNotif <-chan struct{}) { //nolint:golint + for i := 0; i < n; i++ { + if i%3 == 0 && blockNotif != nil { + <-blockNotif + tm.t.Log("WAIT") + } + tm.t.Logf("PLEDGING %d", i) + _, err := tm.PledgeSector(ctx) + require.NoError(tm.t, err) + } + + for { + s, err := tm.SectorsList(ctx) // Note - the test builder doesn't import genesis sectors into FSM + require.NoError(tm.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 := tm.SectorsList(ctx) + require.NoError(tm.t, err) + + toCheck := map[abi.SectorNumber]struct{}{} + for _, number := range s { + toCheck[number] = struct{}{} + } + + for len(toCheck) > 0 { + for n := range toCheck { + st, err := tm.SectorsStatus(ctx, n, false) + require.NoError(tm.t, err) + if st.State == lapi.SectorState(sealing.Proving) { + delete(toCheck, n) + } + require.NotContains(tm.t, string(st.State), "Fail", "sector in a failed state") + } + + build.Clock.Sleep(100 * time.Millisecond) + fmt.Printf("WaitSeal: %d\n", len(s)) + } +} diff --git a/itests/kit/nodes.go b/itests/kit/nodes.go deleted file mode 100644 index 2c3f89b9a..000000000 --- a/itests/kit/nodes.go +++ /dev/null @@ -1,133 +0,0 @@ -package kit - -import ( - "context" - "testing" - - "github.com/multiformats/go-multiaddr" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - "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/chain/stmgr" - "github.com/filecoin-project/lotus/miner" - "github.com/filecoin-project/lotus/node" -) - -type MinerBuilder func(context.Context, *testing.T, abi.RegisteredSealProof, address.Address) TestMiner - -type TestFullNode struct { - v1api.FullNode - // ListenAddr is the address on which an API server is listening, if an - // API server is created for this Node - ListenAddr multiaddr.Multiaddr - - Stb MinerBuilder -} - -type TestMiner struct { - lapi.StorageMiner - // ListenAddr is the address on which an API server is listening, if an - // API server is created for this Node - ListenAddr multiaddr.Multiaddr - - MineOne func(context.Context, miner.MineReq) error - Stop func(context.Context) error -} - -var PresealGenesis = -1 - -const GenesisPreseals = 2 - -const TestSpt = abi.RegisteredSealProof_StackedDrg2KiBV1_1 - -// Options for setting up a mock storage Miner -type StorageMiner struct { - Full int - Opts node.Option - Preseal int -} - -type OptionGenerator func([]TestFullNode) node.Option - -// Options for setting up a mock full node -type FullNodeOpts struct { - Lite bool // run node in "lite" mode - Opts OptionGenerator // generate dependency injection options -} - -// APIBuilder is a function which is invoked in test suite to provide -// test nodes and networks -// -// fullOpts array defines options for each full node -// storage array defines storage nodes, numbers in the array specify full node -// index the storage node 'belongs' to -type APIBuilder func(t *testing.T, full []FullNodeOpts, storage []StorageMiner) ([]TestFullNode, []TestMiner) - -func DefaultFullOpts(nFull int) []FullNodeOpts { - full := make([]FullNodeOpts, nFull) - for i := range full { - full[i] = FullNodeOpts{ - Opts: func(nodes []TestFullNode) node.Option { - return node.Options() - }, - } - } - return full -} - -var OneMiner = []StorageMiner{{Full: 0, Preseal: PresealGenesis}} -var OneFull = DefaultFullOpts(1) -var TwoFull = DefaultFullOpts(2) - -var FullNodeWithLatestActorsAt = func(upgradeHeight abi.ChainEpoch) FullNodeOpts { - if upgradeHeight == -1 { - upgradeHeight = 3 - } - - return FullNodeOpts{ - Opts: func(nodes []TestFullNode) node.Option { - return node.Override(new(stmgr.UpgradeSchedule), stmgr.UpgradeSchedule{{ - // prepare for upgrade. - Network: network.Version9, - Height: 1, - Migration: stmgr.UpgradeActorsV2, - }, { - Network: network.Version10, - Height: 2, - Migration: stmgr.UpgradeActorsV3, - }, { - Network: network.Version12, - Height: upgradeHeight, - Migration: stmgr.UpgradeActorsV4, - }}) - }, - } -} - -var FullNodeWithSDRAt = func(calico, persian abi.ChainEpoch) FullNodeOpts { - return FullNodeOpts{ - Opts: func(nodes []TestFullNode) node.Option { - return node.Override(new(stmgr.UpgradeSchedule), stmgr.UpgradeSchedule{{ - Network: network.Version6, - Height: 1, - Migration: stmgr.UpgradeActorsV2, - }, { - Network: network.Version7, - Height: calico, - Migration: stmgr.UpgradeCalico, - }, { - Network: network.Version8, - Height: persian, - }}) - }, - } -} - -var MineNext = miner.MineReq{ - InjectNulls: 0, - Done: func(bool, abi.ChainEpoch, error) {}, -} diff --git a/itests/kit/pledge.go b/itests/kit/pledge.go deleted file mode 100644 index 1f2379e2b..000000000 --- a/itests/kit/pledge.go +++ /dev/null @@ -1,64 +0,0 @@ -package kit - -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 TestMiner, n, existing int, blockNotif <-chan struct{}) { //nolint:golint - 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/multisig_test.go b/itests/multisig_test.go.no similarity index 100% rename from itests/multisig_test.go rename to itests/multisig_test.go.no diff --git a/itests/paych_api_test.go b/itests/paych_api_test.go.no similarity index 100% rename from itests/paych_api_test.go rename to itests/paych_api_test.go.no diff --git a/itests/paych_cli_test.go b/itests/paych_cli_test.go.no similarity index 100% rename from itests/paych_cli_test.go rename to itests/paych_cli_test.go.no diff --git a/itests/sdr_upgrade_test.go b/itests/sdr_upgrade_test.go.no similarity index 100% rename from itests/sdr_upgrade_test.go rename to itests/sdr_upgrade_test.go.no diff --git a/itests/sector_pledge_test.go b/itests/sector_pledge_test.go.no similarity index 100% rename from itests/sector_pledge_test.go rename to itests/sector_pledge_test.go.no diff --git a/itests/sector_terminate_test.go b/itests/sector_terminate_test.go.no similarity index 100% rename from itests/sector_terminate_test.go rename to itests/sector_terminate_test.go.no diff --git a/itests/tape_test.go b/itests/tape_test.go.no similarity index 100% rename from itests/tape_test.go rename to itests/tape_test.go.no diff --git a/itests/wdpost_dispute_test.go b/itests/wdpost_dispute_test.go.no similarity index 100% rename from itests/wdpost_dispute_test.go rename to itests/wdpost_dispute_test.go.no diff --git a/itests/wdpost_test.go b/itests/wdpost_test.go.no similarity index 100% rename from itests/wdpost_test.go rename to itests/wdpost_test.go.no From 71cd26850203924f0851c7d6c6345691f2fd3188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Fri, 4 Jun 2021 15:17:30 +0100 Subject: [PATCH 002/257] fix. --- cmd/lotus-storage-miner/allinfo_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/lotus-storage-miner/allinfo_test.go b/cmd/lotus-storage-miner/allinfo_test.go index cbe65524e..0c99b47dc 100644 --- a/cmd/lotus-storage-miner/allinfo_test.go +++ b/cmd/lotus-storage-miner/allinfo_test.go @@ -40,7 +40,7 @@ func TestMinerAllInfo(t *testing.T) { policy.SetPreCommitChallengeDelay(oldDelay) }) - n, sn := kit.Builder(t, kit.OneFull, kit.OneMiner) + n, sn := kit.FullNodeBuilder(t, kit.OneFull, kit.OneMiner) client, miner := n[0].FullNode, sn[0] kit.ConnectAndStartMining(t, time.Second, miner, client.(*impl.FullNodeAPI)) From 019394b9e5e64b638d3b60c824534a8c8227e5e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Thu, 10 Jun 2021 12:14:37 +0100 Subject: [PATCH 003/257] remove debug statements. --- itests/kit/init.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/itests/kit/init.go b/itests/kit/init.go index 84ca5ecfe..57d60ad2a 100644 --- a/itests/kit/init.go +++ b/itests/kit/init.go @@ -3,8 +3,6 @@ package kit import ( "fmt" "os" - "runtime/debug" - "strings" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/build" @@ -24,9 +22,4 @@ func init() { panic(fmt.Sprintf("failed to set BELLMAN_NO_GPU env variable: %s", err)) } build.InsecurePoStValidation = true - - debug.PrintStack() - - fmt.Println(strings.HasSuffix(os.Args[0], ".test")) - } From 0303a0297d477d27e5b23b42c8cb1246885a19e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Thu, 10 Jun 2021 12:15:03 +0100 Subject: [PATCH 004/257] rename DealHarness.{TestRetrieval=>PerformRetrieval}. --- itests/kit/deals.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/itests/kit/deals.go b/itests/kit/deals.go index 21089a190..0ea19d9c7 100644 --- a/itests/kit/deals.go +++ b/itests/kit/deals.go @@ -62,7 +62,7 @@ func (dh *DealHarness) MakeFullDeal(ctx context.Context, rseed int, carExport, f info, err := dh.client.ClientGetDealInfo(ctx, *deal) require.NoError(dh.t, err) - dh.TestRetrieval(ctx, fcid, &info.PieceCID, carExport, data) + dh.PerformRetrieval(ctx, fcid, &info.PieceCID, carExport, data) } func (dh *DealHarness) StartDeal(ctx context.Context, fcid cid.Cid, fastRet bool, startEpoch abi.ChainEpoch) *cid.Cid { @@ -184,7 +184,7 @@ func (dh *DealHarness) StartSealingWaiting(ctx context.Context) { } } -func (dh *DealHarness) TestRetrieval(ctx context.Context, fcid cid.Cid, piece *cid.Cid, carExport bool, expect []byte) { +func (dh *DealHarness) PerformRetrieval(ctx context.Context, fcid cid.Cid, piece *cid.Cid, carExport bool, expect []byte) { offers, err := dh.client.ClientFindData(ctx, fcid, piece) if err != nil { dh.t.Fatal(err) From 0d69c03a8d3a335c5cbb8240606dd07456dc0ef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Thu, 10 Jun 2021 12:17:39 +0100 Subject: [PATCH 005/257] deals harness: use require. --- itests/kit/deals.go | 77 +++++++++++++++++---------------------------- 1 file changed, 29 insertions(+), 48 deletions(-) diff --git a/itests/kit/deals.go b/itests/kit/deals.go index 0ea19d9c7..125044656 100644 --- a/itests/kit/deals.go +++ b/itests/kit/deals.go @@ -45,9 +45,7 @@ func NewDealHarness(t *testing.T, client api.FullNode, miner *TestMiner) *DealHa func (dh *DealHarness) MakeFullDeal(ctx context.Context, rseed int, carExport, fastRet bool, startEpoch abi.ChainEpoch) { res, _, data, err := CreateImportFile(ctx, dh.client, rseed, 0) - if err != nil { - dh.t.Fatal(err) - } + require.NoError(dh.t, err) fcid := res.Root fmt.Println("FILE CID: ", fcid) @@ -67,14 +65,11 @@ func (dh *DealHarness) MakeFullDeal(ctx context.Context, rseed int, carExport, f func (dh *DealHarness) StartDeal(ctx context.Context, fcid cid.Cid, fastRet bool, startEpoch abi.ChainEpoch) *cid.Cid { maddr, err := dh.miner.ActorAddress(ctx) - if err != nil { - dh.t.Fatal(err) - } + require.NoError(dh.t, err) addr, err := dh.client.WalletDefaultAddress(ctx) - if err != nil { - dh.t.Fatal(err) - } + require.NoError(dh.t, err) + deal, err := dh.client.ClientStartDeal(ctx, &api.StartDealParams{ Data: &storagemarket.DataRef{ TransferType: storagemarket.TTGraphsync, @@ -140,10 +135,10 @@ loop: func (dh *DealHarness) WaitDealPublished(ctx context.Context, deal *cid.Cid) { subCtx, cancel := context.WithCancel(ctx) defer cancel() + updates, err := dh.miner.MarketGetDealUpdates(subCtx) - if err != nil { - dh.t.Fatal(err) - } + require.NoError(dh.t, err) + for { select { case <-ctx.Done(): @@ -186,43 +181,34 @@ func (dh *DealHarness) StartSealingWaiting(ctx context.Context) { func (dh *DealHarness) PerformRetrieval(ctx context.Context, fcid cid.Cid, piece *cid.Cid, carExport bool, expect []byte) { offers, err := dh.client.ClientFindData(ctx, fcid, piece) - if err != nil { - dh.t.Fatal(err) - } + require.NoError(dh.t, err) if len(offers) < 1 { dh.t.Fatal("no offers") } rpath, err := ioutil.TempDir("", "lotus-retrieve-test-") - if err != nil { - dh.t.Fatal(err) - } + require.NoError(dh.t, err) + defer os.RemoveAll(rpath) //nolint:errcheck caddr, err := dh.client.WalletDefaultAddress(ctx) - if err != nil { - dh.t.Fatal(err) - } + require.NoError(dh.t, err) ref := &api.FileRef{ Path: filepath.Join(rpath, "ret"), IsCAR: carExport, } + updates, err := dh.client.ClientRetrieveWithEvents(ctx, offers[0].Order(caddr), ref) - if err != nil { - dh.t.Fatal(err) - } + require.NoError(dh.t, err) + for update := range updates { - if update.Err != "" { - dh.t.Fatalf("retrieval failed: %s", update.Err) - } + require.Emptyf(dh.t, update.Err, "retrieval failed: %s", update.Err) } rdata, err := ioutil.ReadFile(filepath.Join(rpath, "ret")) - if err != nil { - dh.t.Fatal(err) - } + require.NoError(dh.t, err) if carExport { rdata = dh.ExtractCarData(ctx, rdata, rpath) @@ -236,30 +222,25 @@ func (dh *DealHarness) PerformRetrieval(ctx context.Context, fcid cid.Cid, piece func (dh *DealHarness) ExtractCarData(ctx context.Context, rdata []byte, rpath string) []byte { bserv := dstest.Bserv() ch, err := car.LoadCar(bserv.Blockstore(), bytes.NewReader(rdata)) - if err != nil { - dh.t.Fatal(err) - } + require.NoError(dh.t, err) + b, err := bserv.GetBlock(ctx, ch.Roots[0]) - if err != nil { - dh.t.Fatal(err) - } + require.NoError(dh.t, err) + nd, err := ipld.Decode(b) - if err != nil { - dh.t.Fatal(err) - } + require.NoError(dh.t, err) + dserv := dag.NewDAGService(bserv) fil, err := unixfile.NewUnixfsFile(ctx, dserv, nd) - if err != nil { - dh.t.Fatal(err) - } + require.NoError(dh.t, err) + outPath := filepath.Join(rpath, "retLoadedCAR") - if err := files.WriteTo(fil, outPath); err != nil { - dh.t.Fatal(err) - } + err = files.WriteTo(fil, outPath) + require.NoError(dh.t, err) + rdata, err = ioutil.ReadFile(outPath) - if err != nil { - dh.t.Fatal(err) - } + require.NoError(dh.t, err) + return rdata } From c27fdc263c3bdf9a50aaff64a37972ad3a89654b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Thu, 10 Jun 2021 12:22:55 +0100 Subject: [PATCH 006/257] deals harness: more improvements. --- itests/kit/deals.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/itests/kit/deals.go b/itests/kit/deals.go index 125044656..5d57a91b8 100644 --- a/itests/kit/deals.go +++ b/itests/kit/deals.go @@ -3,7 +3,6 @@ package kit import ( "bytes" "context" - "fmt" "io/ioutil" "os" "path/filepath" @@ -48,7 +47,7 @@ func (dh *DealHarness) MakeFullDeal(ctx context.Context, rseed int, carExport, f require.NoError(dh.t, err) fcid := res.Root - fmt.Println("FILE CID: ", fcid) + dh.t.Logf("FILE CID: %s", fcid) deal := dh.StartDeal(ctx, fcid, fastRet, startEpoch) @@ -109,7 +108,7 @@ loop: case storagemarket.StorageDealError: dh.t.Fatal("deal errored", di.Message) case storagemarket.StorageDealActive: - fmt.Println("COMPLETE", di) + dh.t.Log("COMPLETE", di) break loop } @@ -124,7 +123,7 @@ loop: } } - fmt.Printf("Deal %d state: client:%s provider:%s\n", di.DealID, storagemarket.DealStates[di.State], storagemarket.DealStates[minerState]) + dh.t.Logf("Deal %d state: client:%s provider:%s\n", di.DealID, storagemarket.DealStates[di.State], storagemarket.DealStates[minerState]) time.Sleep(time.Second / 2) if cb != nil { cb() @@ -153,10 +152,10 @@ func (dh *DealHarness) WaitDealPublished(ctx context.Context, deal *cid.Cid) { case storagemarket.StorageDealError: dh.t.Fatal("deal errored", di.Message) case storagemarket.StorageDealFinalizing, storagemarket.StorageDealAwaitingPreCommit, storagemarket.StorageDealSealing, storagemarket.StorageDealActive: - fmt.Println("COMPLETE", di) + dh.t.Log("COMPLETE", di) return } - fmt.Println("Deal state: ", storagemarket.DealStates[di.State]) + dh.t.Log("Deal state: ", storagemarket.DealStates[di.State]) } } } @@ -183,9 +182,7 @@ func (dh *DealHarness) PerformRetrieval(ctx context.Context, fcid cid.Cid, piece offers, err := dh.client.ClientFindData(ctx, fcid, piece) require.NoError(dh.t, err) - if len(offers) < 1 { - dh.t.Fatal("no offers") - } + require.NotEmpty(dh.t, offers, "no offers") rpath, err := ioutil.TempDir("", "lotus-retrieve-test-") require.NoError(dh.t, err) From cf0150e05723796b450e31131bfe4d0622109f95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Thu, 10 Jun 2021 12:23:27 +0100 Subject: [PATCH 007/257] deals harness: use require. --- itests/kit/deals.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/itests/kit/deals.go b/itests/kit/deals.go index 5d57a91b8..14511dbd1 100644 --- a/itests/kit/deals.go +++ b/itests/kit/deals.go @@ -81,9 +81,8 @@ func (dh *DealHarness) StartDeal(ctx context.Context, fcid cid.Cid, fastRet bool MinBlocksDuration: uint64(build.MinDealDuration), FastRetrieval: fastRet, }) - if err != nil { - dh.t.Fatalf("%+v", err) - } + require.NoError(dh.t, err) + return deal } From 329970934ace9a12720d0f77b0442b12c9a5289d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Thu, 10 Jun 2021 13:25:36 +0100 Subject: [PATCH 008/257] deals tests: begin migration. --- go.mod | 1 + itests/deals_test.go | 508 ++++++++++++++++++++++++++++++++++++++ itests/deals_test.go.no | 527 ---------------------------------------- itests/kit/ensemble.go | 93 +++++-- 4 files changed, 578 insertions(+), 551 deletions(-) create mode 100644 itests/deals_test.go delete mode 100644 itests/deals_test.go.no diff --git a/go.mod b/go.mod index e22be541a..b34ca9322 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e github.com/cockroachdb/pebble v0.0.0-20201001221639-879f3bfeef07 github.com/coreos/go-systemd/v22 v22.1.0 + github.com/davecgh/go-spew v1.1.1 // indirect github.com/detailyang/go-fallocate v0.0.0-20180908115635-432fa640bd2e github.com/dgraph-io/badger/v2 v2.2007.2 github.com/docker/go-units v0.4.0 diff --git a/itests/deals_test.go b/itests/deals_test.go new file mode 100644 index 000000000..977ebce08 --- /dev/null +++ b/itests/deals_test.go @@ -0,0 +1,508 @@ +package itests + +import ( + "bytes" + "context" + "fmt" + "testing" + "time" + + "github.com/davecgh/go-spew/spew" + "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/types" + "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/markets/storageadapter" + "github.com/filecoin-project/lotus/node" + market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" + "github.com/stretchr/testify/require" +) + +// +// func TestDealCycle(t *testing.T) { +// kit.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, kit.MockMinerBuilder, blockTime, false, false, dealStartEpoch) +// }) +// t.Run("TestFullDealCycle_Two", func(t *testing.T) { +// runFullDealCycles(t, 2, kit.MockMinerBuilder, blockTime, false, false, dealStartEpoch) +// }) +// t.Run("WithExportedCAR", func(t *testing.T) { +// runFullDealCycles(t, 1, kit.MockMinerBuilder, blockTime, true, false, dealStartEpoch) +// }) +// t.Run("TestFastRetrievalDealCycle", func(t *testing.T) { +// runFastRetrievalDealFlowT(t, kit.MockMinerBuilder, blockTime, dealStartEpoch) +// }) +// t.Run("TestZeroPricePerByteRetrievalDealFlow", func(t *testing.T) { +// runZeroPricePerByteRetrievalDealFlow(t, kit.MockMinerBuilder, blockTime, dealStartEpoch) +// }) +// } +// +// func TestAPIDealFlowReal(t *testing.T) { +// if testing.Short() { +// t.Skip("skipping test in short mode") +// } +// +// kit.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, kit.FullNodeBuilder, time.Second, false, false, 0) +// }) +// +// t.Run("fast-retrieval", func(t *testing.T) { +// runFullDealCycles(t, 1, kit.FullNodeBuilder, time.Second, false, true, 0) +// }) +// +// t.Run("retrieval-second", func(t *testing.T) { +// runSecondDealRetrievalTest(t, kit.FullNodeBuilder, time.Second) +// }) +// } + +func TestPublishDealsBatching(t *testing.T) { + var ( + ctx = context.Background() + publishPeriod = 10 * time.Second + maxDealsPerMsg = uint64(2) // Set max deals per publish deals message to 2 + startEpoch = abi.ChainEpoch(2 << 12) + ) + + kit.QuietMiningLogs() + + opts := node.Override(new(*storageadapter.DealPublisher), + storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{ + Period: publishPeriod, + MaxDealsPerMsg: maxDealsPerMsg, + }), + ) + + client, miner, ens := kit.EnsembleMinimum(t, kit.MockProofs(), kit.ExtraNodeOpts(opts)) + ens.InterconnectAll().BeginMining(10 * time.Millisecond) + + dh := kit.NewDealHarness(t, client, miner) + + fmt.Println("***********************") + spew.Dump(client.NetPeers(context.Background())) + + // Starts a deal and waits until it's published + runDealTillPublish := func(rseed int) { + res, _, _, err := kit.CreateImportFile(ctx, client, rseed, 0) + require.NoError(t, err) + + upds, err := client.ClientGetDealUpdates(ctx) + require.NoError(t, err) + + dh.StartDeal(ctx, 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 := client.StateListMessages(ctx, &api.MessageMatch{To: market.Address}, types.EmptyTSK, 1) + require.NoError(t, err) + count := 0 + for _, msgCid := range msgCids { + msg, err := client.ChainGetMessage(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") +// } +// +// kit.QuietMiningLogs() +// +// b := kit.MockMinerBuilder +// blocktime := 50 * time.Millisecond +// +// ctx := context.Background() +// fulls, miners := b(t, +// kit.OneFull, +// []kit.StorageMiner{ +// {Full: 0, Preseal: kit.PresealGenesis}, +// {Full: 0, Preseal: 0}, // TODO: Add support for miners on non-first full node +// }) +// client := fulls[0].FullNode.(*impl.FullNodeAPI) +// genesisMiner := miners[0] +// provider := miners[1] +// +// 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 := miners[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 := miners[0].MineOne(ctx, miner.MineReq{Done: mdone}); err != nil { +// t.Error(err) +// } +// +// if err := miners[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 miners { +// 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 +// } +// +// } +// }() +// +// dh := kit.NewDealHarness(t, client, provider) +// +// deal := dh.StartDeal(ctx, 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) +// +// dh.WaitDealSealed(ctx, deal, false, false, nil) +// +// <-minedTwo +// +// atomic.StoreInt32(&mine, 0) +// fmt.Println("shutting down mining") +// <-done +// } +// +// func TestOfflineDealFlow(t *testing.T) { +// 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 +// startEpoch := abi.ChainEpoch(2 << 12) +// +// runTest := func(t *testing.T, fastRet bool) { +// ctx := context.Background() +// fulls, miners := kit.MockMinerBuilder(t, kit.OneFull, kit.OneMiner) +// client, miner := fulls[0].FullNode.(*impl.FullNodeAPI), miners[0] +// +// kit.ConnectAndStartMining(t, blocktime, miner, client) +// +// dh := kit.NewDealHarness(t, client, miner) +// +// // Create a random file and import on the client. +// res, path, data, err := kit.CreateImportFile(ctx, client, 1, 0) +// require.NoError(t, err) +// +// // Get the piece size and commP +// fcid := res.Root +// pieceInfo, err := client.ClientDealPieceCID(ctx, fcid) +// require.NoError(t, err) +// fmt.Println("FILE CID: ", fcid) +// +// // Create a storage deal with the miner +// maddr, err := miner.ActorAddress(ctx) +// require.NoError(t, err) +// +// addr, err := client.WalletDefaultAddress(ctx) +// require.NoError(t, err) +// +// // Manual storage deal (offline deal) +// dataRef := &storagemarket.DataRef{ +// TransferType: storagemarket.TTManual, +// Root: fcid, +// PieceCid: &pieceInfo.PieceCID, +// PieceSize: pieceInfo.PieceSize.Unpadded(), +// } +// +// proposalCid, err := client.ClientStartDeal(ctx, &api.StartDealParams{ +// Data: dataRef, +// Wallet: addr, +// Miner: maddr, +// EpochPrice: types.NewInt(1000000), +// DealStartEpoch: startEpoch, +// MinBlocksDuration: uint64(build.MinDealDuration), +// FastRetrieval: fastRet, +// }) +// require.NoError(t, err) +// +// // Wait for the deal to reach StorageDealCheckForAcceptance on the client +// cd, err := client.ClientGetDealInfo(ctx, *proposalCid) +// require.NoError(t, err) +// require.Eventually(t, func() bool { +// cd, _ := client.ClientGetDealInfo(ctx, *proposalCid) +// return cd.State == storagemarket.StorageDealCheckForAcceptance +// }, 30*time.Second, 1*time.Second, "actual deal status is %s", storagemarket.DealStates[cd.State]) +// +// // Create a CAR file from the raw file +// carFileDir, err := ioutil.TempDir(os.TempDir(), "test-make-deal-car") +// require.NoError(t, err) +// carFilePath := filepath.Join(carFileDir, "out.car") +// err = client.ClientGenCar(ctx, api.FileRef{Path: path}, carFilePath) +// require.NoError(t, err) +// +// // Import the CAR file on the miner - this is the equivalent to +// // transferring the file across the wire in a normal (non-offline) deal +// err = miner.DealsImportData(ctx, *proposalCid, carFilePath) +// require.NoError(t, err) +// +// // Wait for the deal to be published +// dh.WaitDealPublished(ctx, proposalCid) +// +// t.Logf("deal published, retrieving") +// +// // Retrieve the deal +// dh.PerformRetrieval(ctx, fcid, &pieceInfo.PieceCID, false, data) +// } +// +// t.Run("NormalRetrieval", func(t *testing.T) { +// runTest(t, false) +// }) +// t.Run("FastRetrieval", func(t *testing.T) { +// runTest(t, true) +// }) +// +// } +// +// func runFullDealCycles(t *testing.T, n int, b kit.APIBuilder, blocktime time.Duration, carExport, fastRet bool, startEpoch abi.ChainEpoch) { +// full, _, ens := kit.EnsembleMinimum(t) +// ens.BeginMining() +// dh := kit.NewDealHarness(t, client, miner) +// +// baseseed := 6 +// for i := 0; i < n; i++ { +// dh.MakeFullDeal(context.Background(), baseseed+i, carExport, fastRet, startEpoch) +// } +// } +// +// func runFastRetrievalDealFlowT(t *testing.T, b kit.APIBuilder, blocktime time.Duration, startEpoch abi.ChainEpoch) { +// ctx := context.Background() +// +// var ( +// nb = kit.NewNodeBuilder(t) +// full = nb.FullNode() +// miner = nb.Miner(full) +// ) +// +// nb.Create() +// +// kit.ConnectAndStartMining(t, blocktime, miner, full) +// +// dh := kit.NewDealHarness(t, full, miner) +// data := make([]byte, 1600) +// rand.New(rand.NewSource(int64(8))).Read(data) +// +// r := bytes.NewReader(data) +// fcid, err := full.FullNode.(*impl.FullNodeAPI).ClientImportLocal(ctx, r) +// require.NoError(t, err) +// +// fmt.Println("FILE CID: ", fcid) +// +// deal := dh.StartDeal(ctx, fcid, true, startEpoch) +// dh.WaitDealPublished(ctx, deal) +// +// fmt.Println("deal published, retrieving") +// +// // Retrieval +// info, err := full.ClientGetDealInfo(ctx, *deal) +// require.NoError(t, err) +// +// dh.PerformRetrieval(ctx, fcid, &info.PieceCID, false, data) +// } +// +// func runSecondDealRetrievalTest(t *testing.T, b kit.APIBuilder, blocktime time.Duration) { +// ctx := context.Background() +// +// fulls, miners := b(t, kit.OneFull, kit.OneMiner) +// client, miner := fulls[0].FullNode.(*impl.FullNodeAPI), miners[0] +// +// kit.ConnectAndStartMining(t, blocktime, miner, client) +// +// dh := kit.NewDealHarness(t, client, miner) +// +// { +// data1 := make([]byte, 800) +// rand.New(rand.NewSource(int64(3))).Read(data1) +// r := bytes.NewReader(data1) +// +// fcid1, err := client.ClientImportLocal(ctx, r) +// if err != nil { +// t.Fatal(err) +// } +// +// data2 := make([]byte, 800) +// rand.New(rand.NewSource(int64(9))).Read(data2) +// r2 := bytes.NewReader(data2) +// +// fcid2, err := client.ClientImportLocal(ctx, r2) +// if err != nil { +// t.Fatal(err) +// } +// +// deal1 := dh.StartDeal(ctx, fcid1, true, 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) +// dh.WaitDealSealed(ctx, deal1, true, false, nil) +// +// deal2 := dh.StartDeal(ctx, fcid2, true, 0) +// +// time.Sleep(time.Second) +// dh.WaitDealSealed(ctx, deal2, false, false, nil) +// +// // Retrieval +// info, err := client.ClientGetDealInfo(ctx, *deal2) +// require.NoError(t, err) +// +// rf, _ := miner.SectorsRefs(ctx) +// fmt.Printf("refs: %+v\n", rf) +// +// dh.PerformRetrieval(ctx, fcid2, &info.PieceCID, false, data2) +// } +// } +// +// func runZeroPricePerByteRetrievalDealFlow(t *testing.T, b kit.APIBuilder, blocktime time.Duration, startEpoch abi.ChainEpoch) { +// ctx := context.Background() +// +// fulls, miners := b(t, kit.OneFull, kit.OneMiner) +// client, miner := fulls[0].FullNode.(*impl.FullNodeAPI), miners[0] +// +// kit.ConnectAndStartMining(t, blocktime, miner, client) +// +// dh := kit.NewDealHarness(t, client, miner) +// +// // Set price-per-byte to zero +// ask, err := miner.MarketGetRetrievalAsk(ctx) +// require.NoError(t, err) +// +// ask.PricePerByte = abi.NewTokenAmount(0) +// err = miner.MarketSetRetrievalAsk(ctx, ask) +// require.NoError(t, err) +// +// dh.MakeFullDeal(ctx, 6, false, false, startEpoch) +// } diff --git a/itests/deals_test.go.no b/itests/deals_test.go.no deleted file mode 100644 index b3c7cd9c7..000000000 --- a/itests/deals_test.go.no +++ /dev/null @@ -1,527 +0,0 @@ -package itests - -import ( - "bytes" - "context" - "fmt" - "io/ioutil" - "math/rand" - "os" - "path/filepath" - "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/build" - "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/itests/kit" - "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" - "github.com/filecoin-project/lotus/node/impl/client" - market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" - "github.com/stretchr/testify/require" -) - -func TestDealCycle(t *testing.T) { - kit.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, kit.MockMinerBuilder, blockTime, false, false, dealStartEpoch) - }) - t.Run("TestFullDealCycle_Two", func(t *testing.T) { - runFullDealCycles(t, 2, kit.MockMinerBuilder, blockTime, false, false, dealStartEpoch) - }) - t.Run("WithExportedCAR", func(t *testing.T) { - runFullDealCycles(t, 1, kit.MockMinerBuilder, blockTime, true, false, dealStartEpoch) - }) - t.Run("TestFastRetrievalDealCycle", func(t *testing.T) { - runFastRetrievalDealFlowT(t, kit.MockMinerBuilder, blockTime, dealStartEpoch) - }) - t.Run("TestZeroPricePerByteRetrievalDealFlow", func(t *testing.T) { - runZeroPricePerByteRetrievalDealFlow(t, kit.MockMinerBuilder, blockTime, dealStartEpoch) - }) -} - -func TestAPIDealFlowReal(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode") - } - - kit.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, kit.FullNodeBuilder, time.Second, false, false, 0) - }) - - t.Run("fast-retrieval", func(t *testing.T) { - runFullDealCycles(t, 1, kit.FullNodeBuilder, time.Second, false, true, 0) - }) - - t.Run("retrieval-second", func(t *testing.T) { - runSecondDealRetrievalTest(t, kit.FullNodeBuilder, time.Second) - }) -} - -func TestPublishDealsBatching(t *testing.T) { - ctx := context.Background() - - kit.QuietMiningLogs() - - b := kit.MockMinerBuilder - 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 := []kit.StorageMiner{{ - Full: 0, - Opts: node.Override( - new(*storageadapter.DealPublisher), - storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{ - Period: publishPeriod, - MaxDealsPerMsg: maxDealsPerMsg, - })), - Preseal: kit.PresealGenesis, - }} - - // Create a connect client and miner node - n, sn := b(t, kit.OneFull, minerDef) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - - kit.ConnectAndStartMining(t, blocktime, miner, client) - - dh := kit.NewDealHarness(t, client, miner) - - // Starts a deal and waits until it's published - runDealTillPublish := func(rseed int) { - res, _, _, err := kit.CreateImportFile(ctx, client, rseed, 0) - require.NoError(t, err) - - upds, err := client.ClientGetDealUpdates(ctx) - require.NoError(t, err) - - dh.StartDeal(ctx, 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 := client.StateListMessages(ctx, &api.MessageMatch{To: market.Address}, types.EmptyTSK, 1) - require.NoError(t, err) - count := 0 - for _, msgCid := range msgCids { - msg, err := client.ChainGetMessage(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") - } - - kit.QuietMiningLogs() - - b := kit.MockMinerBuilder - blocktime := 50 * time.Millisecond - - ctx := context.Background() - fulls, miners := b(t, - kit.OneFull, - []kit.StorageMiner{ - {Full: 0, Preseal: kit.PresealGenesis}, - {Full: 0, Preseal: 0}, // TODO: Add support for miners on non-first full node - }) - client := fulls[0].FullNode.(*impl.FullNodeAPI) - genesisMiner := miners[0] - provider := miners[1] - - 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 := miners[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 := miners[0].MineOne(ctx, miner.MineReq{Done: mdone}); err != nil { - t.Error(err) - } - - if err := miners[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 miners { - 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 - } - - } - }() - - dh := kit.NewDealHarness(t, client, provider) - - deal := dh.StartDeal(ctx, 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) - - dh.WaitDealSealed(ctx, deal, false, false, nil) - - <-minedTwo - - atomic.StoreInt32(&mine, 0) - fmt.Println("shutting down mining") - <-done -} - -func TestOfflineDealFlow(t *testing.T) { - 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 - startEpoch := abi.ChainEpoch(2 << 12) - - runTest := func(t *testing.T, fastRet bool) { - ctx := context.Background() - fulls, miners := kit.MockMinerBuilder(t, kit.OneFull, kit.OneMiner) - client, miner := fulls[0].FullNode.(*impl.FullNodeAPI), miners[0] - - kit.ConnectAndStartMining(t, blocktime, miner, client) - - dh := kit.NewDealHarness(t, client, miner) - - // Create a random file and import on the client. - res, path, data, err := kit.CreateImportFile(ctx, client, 1, 0) - require.NoError(t, err) - - // Get the piece size and commP - fcid := res.Root - pieceInfo, err := client.ClientDealPieceCID(ctx, fcid) - require.NoError(t, err) - fmt.Println("FILE CID: ", fcid) - - // Create a storage deal with the miner - maddr, err := miner.ActorAddress(ctx) - require.NoError(t, err) - - addr, err := client.WalletDefaultAddress(ctx) - require.NoError(t, err) - - // Manual storage deal (offline deal) - dataRef := &storagemarket.DataRef{ - TransferType: storagemarket.TTManual, - Root: fcid, - PieceCid: &pieceInfo.PieceCID, - PieceSize: pieceInfo.PieceSize.Unpadded(), - } - - proposalCid, err := client.ClientStartDeal(ctx, &api.StartDealParams{ - Data: dataRef, - Wallet: addr, - Miner: maddr, - EpochPrice: types.NewInt(1000000), - DealStartEpoch: startEpoch, - MinBlocksDuration: uint64(build.MinDealDuration), - FastRetrieval: fastRet, - }) - require.NoError(t, err) - - // Wait for the deal to reach StorageDealCheckForAcceptance on the client - cd, err := client.ClientGetDealInfo(ctx, *proposalCid) - require.NoError(t, err) - require.Eventually(t, func() bool { - cd, _ := client.ClientGetDealInfo(ctx, *proposalCid) - return cd.State == storagemarket.StorageDealCheckForAcceptance - }, 30*time.Second, 1*time.Second, "actual deal status is %s", storagemarket.DealStates[cd.State]) - - // Create a CAR file from the raw file - carFileDir, err := ioutil.TempDir(os.TempDir(), "test-make-deal-car") - require.NoError(t, err) - carFilePath := filepath.Join(carFileDir, "out.car") - err = client.ClientGenCar(ctx, api.FileRef{Path: path}, carFilePath) - require.NoError(t, err) - - // Import the CAR file on the miner - this is the equivalent to - // transferring the file across the wire in a normal (non-offline) deal - err = miner.DealsImportData(ctx, *proposalCid, carFilePath) - require.NoError(t, err) - - // Wait for the deal to be published - dh.WaitDealPublished(ctx, proposalCid) - - t.Logf("deal published, retrieving") - - // Retrieve the deal - dh.TestRetrieval(ctx, fcid, &pieceInfo.PieceCID, false, data) - } - - t.Run("NormalRetrieval", func(t *testing.T) { - runTest(t, false) - }) - t.Run("FastRetrieval", func(t *testing.T) { - runTest(t, true) - }) - -} - -func runFullDealCycles(t *testing.T, n int, b kit.APIBuilder, blocktime time.Duration, carExport, fastRet bool, startEpoch abi.ChainEpoch) { - - fulls, miners := b(t, kit.OneFull, kit.OneMiner) - client, miner := fulls[0].FullNode.(*impl.FullNodeAPI), miners[0] - - kit.ConnectAndStartMining(t, blocktime, miner, client) - - dh := kit.NewDealHarness(t, client, miner) - - baseseed := 6 - for i := 0; i < n; i++ { - dh.MakeFullDeal(context.Background(), baseseed+i, carExport, fastRet, startEpoch) - } -} - -func runFastRetrievalDealFlowT(t *testing.T, b kit.APIBuilder, blocktime time.Duration, startEpoch abi.ChainEpoch) { - ctx := context.Background() - - var ( - nb = kit.NewNodeBuilder(t) - full = nb.FullNode() - miner = nb.Miner(full) - ) - - nb.Create() - - kit.ConnectAndStartMining(t, blocktime, miner, full) - - dh := kit.NewDealHarness(t, full, miner) - data := make([]byte, 1600) - rand.New(rand.NewSource(int64(8))).Read(data) - - r := bytes.NewReader(data) - fcid, err := full.FullNode.(*impl.FullNodeAPI).ClientImportLocal(ctx, r) - require.NoError(t, err) - - fmt.Println("FILE CID: ", fcid) - - deal := dh.StartDeal(ctx, fcid, true, startEpoch) - dh.WaitDealPublished(ctx, deal) - - fmt.Println("deal published, retrieving") - - // Retrieval - info, err := full.ClientGetDealInfo(ctx, *deal) - require.NoError(t, err) - - dh.TestRetrieval(ctx, fcid, &info.PieceCID, false, data) -} - -func runSecondDealRetrievalTest(t *testing.T, b kit.APIBuilder, blocktime time.Duration) { - ctx := context.Background() - - fulls, miners := b(t, kit.OneFull, kit.OneMiner) - client, miner := fulls[0].FullNode.(*impl.FullNodeAPI), miners[0] - - kit.ConnectAndStartMining(t, blocktime, miner, client) - - dh := kit.NewDealHarness(t, client, miner) - - { - data1 := make([]byte, 800) - rand.New(rand.NewSource(int64(3))).Read(data1) - r := bytes.NewReader(data1) - - fcid1, err := client.ClientImportLocal(ctx, r) - if err != nil { - t.Fatal(err) - } - - data2 := make([]byte, 800) - rand.New(rand.NewSource(int64(9))).Read(data2) - r2 := bytes.NewReader(data2) - - fcid2, err := client.ClientImportLocal(ctx, r2) - if err != nil { - t.Fatal(err) - } - - deal1 := dh.StartDeal(ctx, fcid1, true, 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) - dh.WaitDealSealed(ctx, deal1, true, false, nil) - - deal2 := dh.StartDeal(ctx, fcid2, true, 0) - - time.Sleep(time.Second) - dh.WaitDealSealed(ctx, deal2, false, false, nil) - - // Retrieval - info, err := client.ClientGetDealInfo(ctx, *deal2) - require.NoError(t, err) - - rf, _ := miner.SectorsRefs(ctx) - fmt.Printf("refs: %+v\n", rf) - - dh.TestRetrieval(ctx, fcid2, &info.PieceCID, false, data2) - } -} - -func runZeroPricePerByteRetrievalDealFlow(t *testing.T, b kit.APIBuilder, blocktime time.Duration, startEpoch abi.ChainEpoch) { - ctx := context.Background() - - fulls, miners := b(t, kit.OneFull, kit.OneMiner) - client, miner := fulls[0].FullNode.(*impl.FullNodeAPI), miners[0] - - kit.ConnectAndStartMining(t, blocktime, miner, client) - - dh := kit.NewDealHarness(t, client, miner) - - // Set price-per-byte to zero - ask, err := miner.MarketGetRetrievalAsk(ctx) - require.NoError(t, err) - - ask.PricePerByte = abi.NewTokenAmount(0) - err = miner.MarketSetRetrievalAsk(ctx, ask) - require.NoError(t, err) - - dh.MakeFullDeal(ctx, 6, false, false, startEpoch) -} diff --git a/itests/kit/ensemble.go b/itests/kit/ensemble.go index 00c84ddfa..224df64e2 100644 --- a/itests/kit/ensemble.go +++ b/itests/kit/ensemble.go @@ -114,12 +114,13 @@ func NewEnsemble(t *testing.T, opts ...BuilderOpt) *Ensemble { } type NodeOpts struct { - balance abi.TokenAmount - lite bool - sectors int - mockProofs bool - rpc bool - ownerKey *wallet.Key + balance abi.TokenAmount + lite bool + sectors int + mockProofs bool + rpc bool + ownerKey *wallet.Key + extraNodeOpts []node.Option } var DefaultNodeOpts = NodeOpts{ @@ -182,6 +183,13 @@ func OwnerAddr(wk *wallet.Key) NodeOpt { } } +func ExtraNodeOpts(extra ...node.Option) NodeOpt { + return func(opts *NodeOpts) error { + opts.extraNodeOpts = extra + return nil + } +} + // FullNode enrolls a new full node. func (n *Ensemble) FullNode(full *TestFullNode, opts ...NodeOpt) *Ensemble { options := DefaultNodeOpts @@ -300,6 +308,10 @@ func (n *Ensemble) Start() *Ensemble { n.mn = mocknet.New(ctx) } + // --------------------- + // FULL NODES + // --------------------- + // Create all inactive full nodes. for i, full := range n.inactive.fullnodes { opts := []node.Option{ @@ -313,6 +325,9 @@ func (n *Ensemble) Start() *Ensemble { node.Override(new(dtypes.Bootstrapper), dtypes.Bootstrapper(true)), } + // append any node builder options. + opts = append(opts, full.options.extraNodeOpts...) + // Either generate the genesis or inject it. if i == 0 && !n.bootstrapped { opts = append(opts, node.Override(new(modules.Genesis), testing2.MakeGenesisMem(&n.genesisBlock, *gtempl))) @@ -322,7 +337,10 @@ func (n *Ensemble) Start() *Ensemble { // Are we mocking proofs? if full.options.mockProofs { - opts = append(opts, node.Override(new(ffiwrapper.Verifier), mock.MockVerifier)) + opts = append(opts, + node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), + node.Override(new(ffiwrapper.Prover), mock.MockProver), + ) } // Construct the full node. @@ -356,6 +374,10 @@ func (n *Ensemble) Start() *Ensemble { err := n.mn.LinkAll() require.NoError(n.t, err) + // --------------------- + // MINERS + // --------------------- + // Create all inactive miners. for i, m := range n.inactive.miners { if n.bootstrapped { @@ -469,23 +491,35 @@ func (n *Ensemble) Start() *Ensemble { node.Override(new(*lotusminer.Miner), lotusminer.NewTestMiner(mineBlock, m.ActorAddr)), } + // append any node builder options. + opts = append(opts, m.options.extraNodeOpts...) + idAddr, err := address.IDFromAddress(m.ActorAddr) require.NoError(n.t, err) - if !n.bootstrapped && m.options.mockProofs { - s := n.genesis.miners[i].Sectors - sectors := make([]abi.SectorID, len(s)) - for i, sector := range s { - sectors[i] = abi.SectorID{ + // preload preseals if the network still hasn't bootstrapped. + var presealSectors []abi.SectorID + if !n.bootstrapped { + sectors := n.genesis.miners[i].Sectors + for _, sector := range sectors { + presealSectors = append(presealSectors, abi.SectorID{ Miner: abi.ActorID(idAddr), Number: sector.SectorID, - } + }) } + } + + if m.options.mockProofs { opts = append(opts, - node.Override(new(sectorstorage.SectorManager), func() (sectorstorage.SectorManager, error) { - return mock.NewMockSectorMgr(sectors), nil + node.Override(new(*mock.SectorMgr), func() (*mock.SectorMgr, error) { + return mock.NewMockSectorMgr(presealSectors), nil }), + node.Override(new(sectorstorage.SectorManager), node.From(new(*mock.SectorMgr))), + node.Override(new(sectorstorage.Unsealer), node.From(new(*mock.SectorMgr))), + node.Override(new(sectorstorage.PieceProvider), node.From(new(*mock.SectorMgr))), + node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), + node.Override(new(ffiwrapper.Prover), mock.MockProver), node.Unset(new(*sectorstorage.Manager)), ) } @@ -532,9 +566,7 @@ func (n *Ensemble) Start() *Ensemble { require.NoError(n.t, err) if !n.bootstrapped && len(n.active.miners) > 0 { - // We have *just* bootstrapped, so - // mine 2 blocks to setup some CE stuff - // in some actors + // We have *just* bootstrapped, so mine 2 blocks to setup some CE stuff in some actors var wait sync.Mutex wait.Lock() @@ -557,22 +589,32 @@ func (n *Ensemble) Start() *Ensemble { return n } -// InterconnectAll connects all full nodes one to another. We do not need to -// take action with miners, because miners only stay connected to their full -// nodes over JSON-RPC. +// InterconnectAll connects all miners and full nodes to one another. func (n *Ensemble) InterconnectAll() *Ensemble { + // connect full nodes to miners. + for _, from := range n.active.fullnodes { + for _, to := range n.active.miners { + // []*TestMiner to []api.CommonAPI type coercion not possible + // so cannot use variadic form. + n.Connect(from, to) + } + } + + // connect full nodes between each other, skipping ourselves. last := len(n.active.fullnodes) - 1 for i, from := range n.active.fullnodes { if i == last { continue } - n.Connect(from, n.active.fullnodes[i+1:]...) + for _, to := range n.active.fullnodes[i+1:] { + n.Connect(from, to) + } } return n } // Connect connects one full node to the provided full nodes. -func (n *Ensemble) Connect(from *TestFullNode, to ...*TestFullNode) *Ensemble { +func (n *Ensemble) Connect(from api.Common, to ...api.Common) *Ensemble { addr, err := from.NetAddrsListen(context.Background()) require.NoError(n.t, err) @@ -584,7 +626,8 @@ func (n *Ensemble) Connect(from *TestFullNode, to ...*TestFullNode) *Ensemble { } // BeginMining kicks off mining for the specified miners. If nil or 0-length, -// it will kick off mining for all enrolled and active miners. +// it will kick off mining for all enrolled and active miners. It also adds a +// cleanup function to stop all mining operations on test teardown. func (n *Ensemble) BeginMining(blocktime time.Duration, miners ...*TestMiner) []*BlockMiner { ctx := context.Background() @@ -601,6 +644,8 @@ func (n *Ensemble) BeginMining(blocktime time.Duration, miners ...*TestMiner) [] for _, m := range miners { bm := NewBlockMiner(n.t, m) bm.MineBlocks(ctx, blocktime) + n.t.Cleanup(bm.Stop) + bms = append(bms, bm) } From 4f2aaa54d24983cc2fcd2abe1d0f62d61792ef1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Thu, 10 Jun 2021 14:04:39 +0100 Subject: [PATCH 009/257] deals tests: refactor/simplify TestDealMining; now TestFirstDealEnablesMining. --- itests/api_test.go | 12 +- itests/deals_test.go | 202 +++++++++++---------------------- itests/kit/ensemble.go | 7 +- itests/kit/ensemble_presets.go | 24 +++- 4 files changed, 99 insertions(+), 146 deletions(-) diff --git a/itests/api_test.go b/itests/api_test.go index 22bbc6f6e..c6694a554 100644 --- a/itests/api_test.go +++ b/itests/api_test.go @@ -48,7 +48,7 @@ func (ts *apiSuite) testVersion(t *testing.T) { lapi.RunningNodeType = lapi.NodeUnknown }) - full, _, _ := kit.EnsembleMinimum(t, ts.opts...) + full, _, _ := kit.EnsembleMinimal(t, ts.opts...) v, err := full.Version(context.Background()) require.NoError(t, err) @@ -61,7 +61,7 @@ func (ts *apiSuite) testVersion(t *testing.T) { func (ts *apiSuite) testID(t *testing.T) { ctx := context.Background() - full, _, _ := kit.EnsembleMinimum(t, ts.opts...) + full, _, _ := kit.EnsembleMinimal(t, ts.opts...) id, err := full.ID(ctx) if err != nil { @@ -73,7 +73,7 @@ func (ts *apiSuite) testID(t *testing.T) { func (ts *apiSuite) testConnectTwo(t *testing.T) { ctx := context.Background() - one, two, _, ens := kit.EnsembleTwo(t, ts.opts...) + one, two, _, ens := kit.EnsembleTwoOne(t, ts.opts...) p, err := one.NetPeers(ctx) require.NoError(t, err) @@ -97,7 +97,7 @@ func (ts *apiSuite) testConnectTwo(t *testing.T) { func (ts *apiSuite) testSearchMsg(t *testing.T) { ctx := context.Background() - full, _, ens := kit.EnsembleMinimum(t, ts.opts...) + full, _, ens := kit.EnsembleMinimal(t, ts.opts...) senderAddr, err := full.WalletDefaultAddress(ctx) require.NoError(t, err) @@ -127,7 +127,7 @@ func (ts *apiSuite) testSearchMsg(t *testing.T) { func (ts *apiSuite) testMining(t *testing.T) { ctx := context.Background() - full, miner, _ := kit.EnsembleMinimum(t, ts.opts...) + full, miner, _ := kit.EnsembleMinimal(t, ts.opts...) newHeads, err := full.ChainNotify(ctx) require.NoError(t, err) @@ -170,7 +170,7 @@ func (ts *apiSuite) testMiningReal(t *testing.T) { func (ts *apiSuite) testNonGenesisMiner(t *testing.T) { ctx := context.Background() - full, genesisMiner, ens := kit.EnsembleMinimum(t, ts.opts...) + full, genesisMiner, ens := kit.EnsembleMinimal(t, ts.opts...) ens.BeginMining(4 * time.Millisecond) diff --git a/itests/deals_test.go b/itests/deals_test.go index 977ebce08..ad9429dc1 100644 --- a/itests/deals_test.go +++ b/itests/deals_test.go @@ -3,11 +3,9 @@ package itests import ( "bytes" "context" - "fmt" "testing" "time" - "github.com/davecgh/go-spew/spew" "github.com/filecoin-project/go-fil-markets/storagemarket" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/api" @@ -92,14 +90,11 @@ func TestPublishDealsBatching(t *testing.T) { }), ) - client, miner, ens := kit.EnsembleMinimum(t, kit.MockProofs(), kit.ExtraNodeOpts(opts)) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ExtraNodeOpts(opts)) ens.InterconnectAll().BeginMining(10 * time.Millisecond) dh := kit.NewDealHarness(t, client, miner) - fmt.Println("***********************") - spew.Dump(client.NetPeers(context.Background())) - // Starts a deal and waits until it's published runDealTillPublish := func(rseed int) { res, _, _, err := kit.CreateImportFile(ctx, client, rseed, 0) @@ -168,135 +163,70 @@ func TestPublishDealsBatching(t *testing.T) { } } -// -// 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") -// } -// -// kit.QuietMiningLogs() -// -// b := kit.MockMinerBuilder -// blocktime := 50 * time.Millisecond -// -// ctx := context.Background() -// fulls, miners := b(t, -// kit.OneFull, -// []kit.StorageMiner{ -// {Full: 0, Preseal: kit.PresealGenesis}, -// {Full: 0, Preseal: 0}, // TODO: Add support for miners on non-first full node -// }) -// client := fulls[0].FullNode.(*impl.FullNodeAPI) -// genesisMiner := miners[0] -// provider := miners[1] -// -// 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 := miners[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 := miners[0].MineOne(ctx, miner.MineReq{Done: mdone}); err != nil { -// t.Error(err) -// } -// -// if err := miners[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 miners { -// 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 -// } -// -// } -// }() -// -// dh := kit.NewDealHarness(t, client, provider) -// -// deal := dh.StartDeal(ctx, 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) -// -// dh.WaitDealSealed(ctx, deal, false, false, nil) -// -// <-minedTwo -// -// atomic.StoreInt32(&mine, 0) -// fmt.Println("shutting down mining") -// <-done -// } +func TestFirstDealEnablesMining(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") + } + + kit.QuietMiningLogs() + + var ( + client kit.TestFullNode + genMiner kit.TestMiner // bootstrap + provider kit.TestMiner // no sectors, will need to create one + ) + + ens := kit.NewEnsemble(t) + ens.FullNode(&client, kit.MockProofs()) + ens.Miner(&genMiner, &client, kit.MockProofs()) + ens.Miner(&provider, &client, kit.MockProofs(), kit.PresealSectors(0)) + ens.Start().InterconnectAll().BeginMining(50 * time.Millisecond) + + ctx := context.Background() + + dh := kit.NewDealHarness(t, client, &provider) + + ref, _, _, err := kit.CreateImportFile(ctx, client, 5, 0) + require.NoError(t, err) + + t.Log("FILE CID:", ref.Root) + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + // start a goroutine to monitor head changes from the client + // once the provider has mined a block, thanks to the power acquired from the deal, + // we pass the test. + providerMined := make(chan struct{}) + heads, err := client.ChainNotify(ctx) + go func() { + for chg := range heads { + for _, c := range chg { + if c.Type != "apply" { + continue + } + for _, b := range c.Val.Blocks() { + if b.Miner == provider.ActorAddr { + close(providerMined) + return + } + } + } + } + }() + + // now perform the deal. + deal := dh.StartDeal(ctx, ref.Root, 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) + + dh.WaitDealSealed(ctx, deal, false, false, nil) + + <-providerMined +} + // // func TestOfflineDealFlow(t *testing.T) { // blocktime := 10 * time.Millisecond @@ -390,7 +320,7 @@ func TestPublishDealsBatching(t *testing.T) { // } // // func runFullDealCycles(t *testing.T, n int, b kit.APIBuilder, blocktime time.Duration, carExport, fastRet bool, startEpoch abi.ChainEpoch) { -// full, _, ens := kit.EnsembleMinimum(t) +// full, _, ens := kit.EnsembleMinimal(t) // ens.BeginMining() // dh := kit.NewDealHarness(t, client, miner) // diff --git a/itests/kit/ensemble.go b/itests/kit/ensemble.go index 224df64e2..cfc95e968 100644 --- a/itests/kit/ensemble.go +++ b/itests/kit/ensemble.go @@ -123,9 +123,11 @@ type NodeOpts struct { extraNodeOpts []node.Option } +const DefaultPresealsPerBootstrapMiner = 2 + var DefaultNodeOpts = NodeOpts{ balance: big.Mul(big.NewInt(100000000), types.NewInt(build.FilecoinPrecision)), - sectors: 2, + sectors: DefaultPresealsPerBootstrapMiner, } type NodeOpt func(opts *NodeOpts) error @@ -215,7 +217,7 @@ func (n *Ensemble) FullNode(full *TestFullNode, opts ...NodeOpt) *Ensemble { n.genesis.accounts = append(n.genesis.accounts, genacc) } - *full = TestFullNode{options: options, DefaultKey: key} + *full = TestFullNode{t: n.t, options: options, DefaultKey: key} n.inactive.fullnodes = append(n.inactive.fullnodes, full) return n } @@ -280,6 +282,7 @@ func (n *Ensemble) Miner(miner *TestMiner, full *TestFullNode, opts ...NodeOpt) } *miner = TestMiner{ + t: n.t, ActorAddr: actorAddr, OwnerKey: ownerKey, FullNode: full, diff --git a/itests/kit/ensemble_presets.go b/itests/kit/ensemble_presets.go index debad2ed1..fa7746f95 100644 --- a/itests/kit/ensemble_presets.go +++ b/itests/kit/ensemble_presets.go @@ -2,7 +2,9 @@ package kit import "testing" -func EnsembleMinimum(t *testing.T, opts ...NodeOpt) (*TestFullNode, *TestMiner, *Ensemble) { +// EnsembleMinimal creates and starts an ensemble with a single full node and a single miner. +// It does not interconnect nodes nor does it begin mining. +func EnsembleMinimal(t *testing.T, opts ...NodeOpt) (*TestFullNode, *TestMiner, *Ensemble) { var ( full TestFullNode miner TestMiner @@ -11,7 +13,9 @@ func EnsembleMinimum(t *testing.T, opts ...NodeOpt) (*TestFullNode, *TestMiner, return &full, &miner, ensemble } -func EnsembleTwo(t *testing.T, opts ...NodeOpt) (*TestFullNode, *TestFullNode, *TestMiner, *Ensemble) { +// EnsembleTwoOne creates and starts an ensemble with two full nodes and one miner. +// It does not interconnect nodes nor does it begin mining. +func EnsembleTwoOne(t *testing.T, opts ...NodeOpt) (*TestFullNode, *TestFullNode, *TestMiner, *Ensemble) { var ( one, two TestFullNode miner TestMiner @@ -19,3 +23,19 @@ func EnsembleTwo(t *testing.T, opts ...NodeOpt) (*TestFullNode, *TestFullNode, * ensemble := NewEnsemble(t).FullNode(&one, opts...).FullNode(&two, opts...).Miner(&miner, &one, opts...).Start() return &one, &two, &miner, ensemble } + +// EnsembleOneTwo creates and starts an ensemble with one full node and two miners. +// It does not interconnect nodes nor does it begin mining. +func EnsembleOneTwo(t *testing.T, opts ...NodeOpt) (*TestFullNode, *TestMiner, *TestMiner, *Ensemble) { + var ( + full TestFullNode + one, two TestMiner + ) + ensemble := NewEnsemble(t). + FullNode(&full, opts...). + Miner(&one, &full, opts...). + Miner(&two, &full, opts...). + Start() + + return &full, &one, &two, ensemble +} From dcd6fc239bb590864e06a50de9de232327be4dde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Thu, 10 Jun 2021 15:54:16 +0100 Subject: [PATCH 010/257] deals tests: migrate TestOfflineDealFlow. --- cmd/lotus-storage-miner/allinfo_test.go | 2 +- itests/deals_test.go | 189 ++++++++++++------------ itests/kit/deals.go | 2 +- 3 files changed, 98 insertions(+), 95 deletions(-) diff --git a/cmd/lotus-storage-miner/allinfo_test.go b/cmd/lotus-storage-miner/allinfo_test.go index 0c99b47dc..0a4461b31 100644 --- a/cmd/lotus-storage-miner/allinfo_test.go +++ b/cmd/lotus-storage-miner/allinfo_test.go @@ -61,7 +61,7 @@ func TestMinerAllInfo(t *testing.T) { t.Run("pre-info-all", run) dh := kit.NewDealHarness(t, client, miner) - dh.MakeFullDeal(context.Background(), 6, false, false, 0) + dh.MakeOnlineDeal(context.Background(), 6, false, false, 0) t.Run("post-info-all", run) } diff --git a/itests/deals_test.go b/itests/deals_test.go index ad9429dc1..2810f70d6 100644 --- a/itests/deals_test.go +++ b/itests/deals_test.go @@ -3,12 +3,16 @@ package itests import ( "bytes" "context" + "io/ioutil" + "os" + "path/filepath" "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/build" "github.com/filecoin-project/lotus/chain/actors/builtin/market" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/itests/kit" @@ -227,97 +231,96 @@ func TestFirstDealEnablesMining(t *testing.T) { <-providerMined } -// -// func TestOfflineDealFlow(t *testing.T) { -// 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 -// startEpoch := abi.ChainEpoch(2 << 12) -// -// runTest := func(t *testing.T, fastRet bool) { -// ctx := context.Background() -// fulls, miners := kit.MockMinerBuilder(t, kit.OneFull, kit.OneMiner) -// client, miner := fulls[0].FullNode.(*impl.FullNodeAPI), miners[0] -// -// kit.ConnectAndStartMining(t, blocktime, miner, client) -// -// dh := kit.NewDealHarness(t, client, miner) -// -// // Create a random file and import on the client. -// res, path, data, err := kit.CreateImportFile(ctx, client, 1, 0) -// require.NoError(t, err) -// -// // Get the piece size and commP -// fcid := res.Root -// pieceInfo, err := client.ClientDealPieceCID(ctx, fcid) -// require.NoError(t, err) -// fmt.Println("FILE CID: ", fcid) -// -// // Create a storage deal with the miner -// maddr, err := miner.ActorAddress(ctx) -// require.NoError(t, err) -// -// addr, err := client.WalletDefaultAddress(ctx) -// require.NoError(t, err) -// -// // Manual storage deal (offline deal) -// dataRef := &storagemarket.DataRef{ -// TransferType: storagemarket.TTManual, -// Root: fcid, -// PieceCid: &pieceInfo.PieceCID, -// PieceSize: pieceInfo.PieceSize.Unpadded(), -// } -// -// proposalCid, err := client.ClientStartDeal(ctx, &api.StartDealParams{ -// Data: dataRef, -// Wallet: addr, -// Miner: maddr, -// EpochPrice: types.NewInt(1000000), -// DealStartEpoch: startEpoch, -// MinBlocksDuration: uint64(build.MinDealDuration), -// FastRetrieval: fastRet, -// }) -// require.NoError(t, err) -// -// // Wait for the deal to reach StorageDealCheckForAcceptance on the client -// cd, err := client.ClientGetDealInfo(ctx, *proposalCid) -// require.NoError(t, err) -// require.Eventually(t, func() bool { -// cd, _ := client.ClientGetDealInfo(ctx, *proposalCid) -// return cd.State == storagemarket.StorageDealCheckForAcceptance -// }, 30*time.Second, 1*time.Second, "actual deal status is %s", storagemarket.DealStates[cd.State]) -// -// // Create a CAR file from the raw file -// carFileDir, err := ioutil.TempDir(os.TempDir(), "test-make-deal-car") -// require.NoError(t, err) -// carFilePath := filepath.Join(carFileDir, "out.car") -// err = client.ClientGenCar(ctx, api.FileRef{Path: path}, carFilePath) -// require.NoError(t, err) -// -// // Import the CAR file on the miner - this is the equivalent to -// // transferring the file across the wire in a normal (non-offline) deal -// err = miner.DealsImportData(ctx, *proposalCid, carFilePath) -// require.NoError(t, err) -// -// // Wait for the deal to be published -// dh.WaitDealPublished(ctx, proposalCid) -// -// t.Logf("deal published, retrieving") -// -// // Retrieve the deal -// dh.PerformRetrieval(ctx, fcid, &pieceInfo.PieceCID, false, data) -// } -// -// t.Run("NormalRetrieval", func(t *testing.T) { -// runTest(t, false) -// }) -// t.Run("FastRetrieval", func(t *testing.T) { -// runTest(t, true) -// }) -// -// } +func TestOfflineDealFlow(t *testing.T) { + 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 + startEpoch := abi.ChainEpoch(2 << 12) + + runTest := func(t *testing.T, fastRet bool) { + ctx := context.Background() + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) + ens.InterconnectAll().BeginMining(blocktime) + + dh := kit.NewDealHarness(t, client, miner) + + // Create a random file and import on the client. + res, path, data, err := kit.CreateImportFile(ctx, client, 1, 0) + require.NoError(t, err) + + // Get the piece size and commP + fcid := res.Root + pieceInfo, err := client.ClientDealPieceCID(ctx, fcid) + require.NoError(t, err) + t.Log("FILE CID:", fcid) + + // Create a storage deal with the miner + maddr, err := miner.ActorAddress(ctx) + require.NoError(t, err) + + addr, err := client.WalletDefaultAddress(ctx) + require.NoError(t, err) + + // Manual storage deal (offline deal) + dataRef := &storagemarket.DataRef{ + TransferType: storagemarket.TTManual, + Root: fcid, + PieceCid: &pieceInfo.PieceCID, + PieceSize: pieceInfo.PieceSize.Unpadded(), + } + + proposalCid, err := client.ClientStartDeal(ctx, &api.StartDealParams{ + Data: dataRef, + Wallet: addr, + Miner: maddr, + EpochPrice: types.NewInt(1000000), + DealStartEpoch: startEpoch, + MinBlocksDuration: uint64(build.MinDealDuration), + FastRetrieval: fastRet, + }) + require.NoError(t, err) + + // Wait for the deal to reach StorageDealCheckForAcceptance on the client + cd, err := client.ClientGetDealInfo(ctx, *proposalCid) + require.NoError(t, err) + require.Eventually(t, func() bool { + cd, _ := client.ClientGetDealInfo(ctx, *proposalCid) + return cd.State == storagemarket.StorageDealCheckForAcceptance + }, 30*time.Second, 1*time.Second, "actual deal status is %s", storagemarket.DealStates[cd.State]) + + // Create a CAR file from the raw file + carFileDir, err := ioutil.TempDir(os.TempDir(), "test-make-deal-car") + require.NoError(t, err) + + carFilePath := filepath.Join(carFileDir, "out.car") + err = client.ClientGenCar(ctx, api.FileRef{Path: path}, carFilePath) + require.NoError(t, err) + + // Import the CAR file on the miner - this is the equivalent to + // transferring the file across the wire in a normal (non-offline) deal + err = miner.DealsImportData(ctx, *proposalCid, carFilePath) + require.NoError(t, err) + + // Wait for the deal to be published + dh.WaitDealPublished(ctx, proposalCid) + + t.Logf("deal published, retrieving") + + // Retrieve the deal + dh.PerformRetrieval(ctx, fcid, &pieceInfo.PieceCID, false, data) + } + + t.Run("NormalRetrieval", func(t *testing.T) { + runTest(t, false) + }) + t.Run("FastRetrieval", func(t *testing.T) { + runTest(t, true) + }) + +} + // // func runFullDealCycles(t *testing.T, n int, b kit.APIBuilder, blocktime time.Duration, carExport, fastRet bool, startEpoch abi.ChainEpoch) { // full, _, ens := kit.EnsembleMinimal(t) @@ -326,7 +329,7 @@ func TestFirstDealEnablesMining(t *testing.T) { // // baseseed := 6 // for i := 0; i < n; i++ { -// dh.MakeFullDeal(context.Background(), baseseed+i, carExport, fastRet, startEpoch) +// dh.MakeOnlineDeal(context.Background(), baseseed+i, carExport, fastRet, startEpoch) // } // } // @@ -434,5 +437,5 @@ func TestFirstDealEnablesMining(t *testing.T) { // err = miner.MarketSetRetrievalAsk(ctx, ask) // require.NoError(t, err) // -// dh.MakeFullDeal(ctx, 6, false, false, startEpoch) +// dh.MakeOnlineDeal(ctx, 6, false, false, startEpoch) // } diff --git a/itests/kit/deals.go b/itests/kit/deals.go index 14511dbd1..5fc950cc5 100644 --- a/itests/kit/deals.go +++ b/itests/kit/deals.go @@ -42,7 +42,7 @@ func NewDealHarness(t *testing.T, client api.FullNode, miner *TestMiner) *DealHa } } -func (dh *DealHarness) MakeFullDeal(ctx context.Context, rseed int, carExport, fastRet bool, startEpoch abi.ChainEpoch) { +func (dh *DealHarness) MakeOnlineDeal(ctx context.Context, rseed int, carExport, fastRet bool, startEpoch abi.ChainEpoch) { res, _, data, err := CreateImportFile(ctx, dh.client, rseed, 0) require.NoError(dh.t, err) From 8b037e2da3d4b8734ce969e1411f3f4f76004b22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Thu, 10 Jun 2021 18:25:02 +0100 Subject: [PATCH 011/257] deals tests: migrate deals cycles tests and add coverage. --- itests/deals_test.go | 129 ++++++++++++++++++++------------------- itests/kit/blockminer.go | 2 +- itests/kit/client.go | 45 +------------- itests/kit/deals.go | 63 ++++++++----------- itests/kit/files.go | 57 +++++++++++++++++ itests/kit/init.go | 10 ++- itests/kit/node_full.go | 10 +++ 7 files changed, 169 insertions(+), 147 deletions(-) create mode 100644 itests/kit/files.go diff --git a/itests/deals_test.go b/itests/deals_test.go index 2810f70d6..a14a5bbb6 100644 --- a/itests/deals_test.go +++ b/itests/deals_test.go @@ -3,8 +3,7 @@ package itests import ( "bytes" "context" - "io/ioutil" - "os" + "fmt" "path/filepath" "testing" "time" @@ -20,36 +19,54 @@ import ( "github.com/filecoin-project/lotus/node" market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" ) -// -// func TestDealCycle(t *testing.T) { -// kit.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, kit.MockMinerBuilder, blockTime, false, false, dealStartEpoch) -// }) -// t.Run("TestFullDealCycle_Two", func(t *testing.T) { -// runFullDealCycles(t, 2, kit.MockMinerBuilder, blockTime, false, false, dealStartEpoch) -// }) -// t.Run("WithExportedCAR", func(t *testing.T) { -// runFullDealCycles(t, 1, kit.MockMinerBuilder, blockTime, true, false, dealStartEpoch) -// }) -// t.Run("TestFastRetrievalDealCycle", func(t *testing.T) { -// runFastRetrievalDealFlowT(t, kit.MockMinerBuilder, blockTime, dealStartEpoch) -// }) -// t.Run("TestZeroPricePerByteRetrievalDealFlow", func(t *testing.T) { -// runZeroPricePerByteRetrievalDealFlow(t, kit.MockMinerBuilder, blockTime, dealStartEpoch) -// }) -// } -// +func TestDealCyclesConcurrent(t *testing.T) { + kit.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) + + runTest := func(t *testing.T, n int, fastRetrieval bool, carExport bool) { + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) + ens.InterconnectAll().BeginMining(blockTime) + dh := kit.NewDealHarness(t, client, miner) + + errgrp, _ := errgroup.WithContext(context.Background()) + for i := 0; i < n; i++ { + i := i + errgrp.Go(func() (err error) { + defer func() { + // This is necessary because we use require, which invokes t.Fatal, + // and that's not + if r := recover(); r != nil { + err = fmt.Errorf("deal failed: %s", r) + } + }() + deal, res, inPath := dh.MakeOnlineDeal(context.Background(), 5+i, fastRetrieval, dealStartEpoch) + outPath := dh.PerformRetrieval(context.Background(), deal, res.Root, carExport) + kit.FilesEqual(t, inPath, outPath) + return nil + }) + } + require.NoError(t, errgrp.Wait()) + } + + cycles := []int{1, 2, 4, 8} + for _, n := range cycles { + ns := fmt.Sprintf("%d", n) + t.Run(ns+"-fastretrieval-CAR", func(t *testing.T) { runTest(t, n, true, true) }) + t.Run(ns+"-fastretrieval-NoCAR", func(t *testing.T) { runTest(t, n, true, false) }) + t.Run(ns+"-stdretrieval-CAR", func(t *testing.T) { runTest(t, n, true, false) }) + t.Run(ns+"-stdretrieval-NoCAR", func(t *testing.T) { runTest(t, n, false, false) }) + } +} + // func TestAPIDealFlowReal(t *testing.T) { // if testing.Short() { // t.Skip("skipping test in short mode") @@ -101,8 +118,7 @@ func TestPublishDealsBatching(t *testing.T) { // Starts a deal and waits until it's published runDealTillPublish := func(rseed int) { - res, _, _, err := kit.CreateImportFile(ctx, client, rseed, 0) - require.NoError(t, err) + res, _ := client.CreateImportFile(ctx, rseed, 0) upds, err := client.ClientGetDealUpdates(ctx) require.NoError(t, err) @@ -189,10 +205,9 @@ func TestFirstDealEnablesMining(t *testing.T) { ctx := context.Background() - dh := kit.NewDealHarness(t, client, &provider) + dh := kit.NewDealHarness(t, &client, &provider) - ref, _, _, err := kit.CreateImportFile(ctx, client, 5, 0) - require.NoError(t, err) + ref, _ := client.CreateImportFile(ctx, 5, 0) t.Log("FILE CID:", ref.Root) @@ -204,6 +219,8 @@ func TestFirstDealEnablesMining(t *testing.T) { // we pass the test. providerMined := make(chan struct{}) heads, err := client.ChainNotify(ctx) + require.NoError(t, err) + go func() { for chg := range heads { for _, c := range chg { @@ -247,14 +264,13 @@ func TestOfflineDealFlow(t *testing.T) { dh := kit.NewDealHarness(t, client, miner) // Create a random file and import on the client. - res, path, data, err := kit.CreateImportFile(ctx, client, 1, 0) - require.NoError(t, err) + res, inFile := client.CreateImportFile(ctx, 1, 0) // Get the piece size and commP - fcid := res.Root - pieceInfo, err := client.ClientDealPieceCID(ctx, fcid) + rootCid := res.Root + pieceInfo, err := client.ClientDealPieceCID(ctx, rootCid) require.NoError(t, err) - t.Log("FILE CID:", fcid) + t.Log("FILE CID:", rootCid) // Create a storage deal with the miner maddr, err := miner.ActorAddress(ctx) @@ -266,7 +282,7 @@ func TestOfflineDealFlow(t *testing.T) { // Manual storage deal (offline deal) dataRef := &storagemarket.DataRef{ TransferType: storagemarket.TTManual, - Root: fcid, + Root: rootCid, PieceCid: &pieceInfo.PieceCID, PieceSize: pieceInfo.PieceSize.Unpadded(), } @@ -291,11 +307,9 @@ func TestOfflineDealFlow(t *testing.T) { }, 30*time.Second, 1*time.Second, "actual deal status is %s", storagemarket.DealStates[cd.State]) // Create a CAR file from the raw file - carFileDir, err := ioutil.TempDir(os.TempDir(), "test-make-deal-car") - require.NoError(t, err) - + carFileDir := t.TempDir() carFilePath := filepath.Join(carFileDir, "out.car") - err = client.ClientGenCar(ctx, api.FileRef{Path: path}, carFilePath) + err = client.ClientGenCar(ctx, api.FileRef{Path: inFile}, carFilePath) require.NoError(t, err) // Import the CAR file on the miner - this is the equivalent to @@ -309,29 +323,16 @@ func TestOfflineDealFlow(t *testing.T) { t.Logf("deal published, retrieving") // Retrieve the deal - dh.PerformRetrieval(ctx, fcid, &pieceInfo.PieceCID, false, data) + outFile := dh.PerformRetrieval(ctx, proposalCid, rootCid, false) + + equal := kit.FilesEqual(t, inFile, outFile) + require.True(t, equal) } - t.Run("NormalRetrieval", func(t *testing.T) { - runTest(t, false) - }) - t.Run("FastRetrieval", func(t *testing.T) { - runTest(t, true) - }) - + t.Run("NormalRetrieval", func(t *testing.T) { runTest(t, false) }) + t.Run("FastRetrieval", func(t *testing.T) { runTest(t, true) }) } -// -// func runFullDealCycles(t *testing.T, n int, b kit.APIBuilder, blocktime time.Duration, carExport, fastRet bool, startEpoch abi.ChainEpoch) { -// full, _, ens := kit.EnsembleMinimal(t) -// ens.BeginMining() -// dh := kit.NewDealHarness(t, client, miner) -// -// baseseed := 6 -// for i := 0; i < n; i++ { -// dh.MakeOnlineDeal(context.Background(), baseseed+i, carExport, fastRet, startEpoch) -// } -// } // // func runFastRetrievalDealFlowT(t *testing.T, b kit.APIBuilder, blocktime time.Duration, startEpoch abi.ChainEpoch) { // ctx := context.Background() diff --git a/itests/kit/blockminer.go b/itests/kit/blockminer.go index b7951c4f8..2c9bd47c6 100644 --- a/itests/kit/blockminer.go +++ b/itests/kit/blockminer.go @@ -93,7 +93,7 @@ func (bm *BlockMiner) MineUntilBlock(ctx context.Context, fn *TestFullNode, cb f if success { // Wait until it shows up on the given full nodes ChainHead - nloops := 50 + nloops := 200 for i := 0; i < nloops; i++ { ts, err := fn.ChainHead(ctx) require.NoError(bm.t, err) diff --git a/itests/kit/client.go b/itests/kit/client.go index 6b7d46265..0d247043e 100644 --- a/itests/kit/client.go +++ b/itests/kit/client.go @@ -3,16 +3,12 @@ package kit import ( "context" "fmt" - "io/ioutil" - "math/rand" - "os" "path/filepath" "regexp" "strings" "testing" "time" - "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/specs-actors/v2/actors/builtin" @@ -43,7 +39,7 @@ func RunClientTest(t *testing.T, cmds []*lcli.Command, clientNode TestFullNode) // Create a deal (non-interactive) // client deal --start-epoch= 1000000attofil - res, _, _, err := CreateImportFile(ctx, clientNode, 1, 0) + res, _ := clientNode.CreateImportFile(ctx, 1, 0) require.NoError(t, err) startEpoch := fmt.Sprintf("--start-epoch=%d", 2<<12) @@ -60,7 +56,7 @@ func RunClientTest(t *testing.T, cmds []*lcli.Command, clientNode TestFullNode) // // "no" (verified Client) // "yes" (confirm deal) - res, _, _, err = CreateImportFile(ctx, clientNode, 2, 0) + res, _ = clientNode.CreateImportFile(ctx, 2, 0) require.NoError(t, err) dataCid2 := res.Root duration = fmt.Sprintf("%d", build.MinDealDuration/builtin.EpochsInDay) @@ -103,44 +99,9 @@ func RunClientTest(t *testing.T, cmds []*lcli.Command, clientNode TestFullNode) // Retrieve the first file from the Miner // client retrieve - tmpdir, err := ioutil.TempDir(os.TempDir(), "test-cli-Client") - require.NoError(t, err) + tmpdir := t.TempDir() path := filepath.Join(tmpdir, "outfile.dat") out = clientCLI.RunCmd("client", "retrieve", dataCid.String(), path) fmt.Println("retrieve:\n", out) require.Regexp(t, regexp.MustCompile("Success"), out) } - -func CreateImportFile(ctx context.Context, client api.FullNode, rseed int, size int) (res *api.ImportRes, path string, data []byte, err error) { - data, path, err = createRandomFile(rseed, size) - if err != nil { - return nil, "", nil, err - } - - res, err = client.ClientImport(ctx, api.FileRef{Path: path}) - if err != nil { - return nil, "", nil, err - } - return res, path, data, nil -} - -func createRandomFile(rseed, size int) ([]byte, string, error) { - if size == 0 { - size = 1600 - } - data := make([]byte, size) - rand.New(rand.NewSource(int64(rseed))).Read(data) - - dir, err := ioutil.TempDir(os.TempDir(), "test-make-deal-") - if err != nil { - return nil, "", err - } - - path := filepath.Join(dir, "sourcefile.dat") - err = ioutil.WriteFile(path, data, 0644) - if err != nil { - return nil, "", err - } - - return data, path, nil -} diff --git a/itests/kit/deals.go b/itests/kit/deals.go index 5fc950cc5..d62c5a7bd 100644 --- a/itests/kit/deals.go +++ b/itests/kit/deals.go @@ -4,8 +4,6 @@ import ( "bytes" "context" "io/ioutil" - "os" - "path/filepath" "testing" "time" @@ -20,7 +18,6 @@ import ( "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" ipld "github.com/ipfs/go-ipld-format" dag "github.com/ipfs/go-merkledag" dstest "github.com/ipfs/go-merkledag/test" @@ -29,12 +26,12 @@ import ( type DealHarness struct { t *testing.T - client api.FullNode + client *TestFullNode miner *TestMiner } // NewDealHarness creates a test harness that contains testing utilities for deals. -func NewDealHarness(t *testing.T, client api.FullNode, miner *TestMiner) *DealHarness { +func NewDealHarness(t *testing.T, client *TestFullNode, miner *TestMiner) *DealHarness { return &DealHarness{ t: t, client: client, @@ -42,24 +39,18 @@ func NewDealHarness(t *testing.T, client api.FullNode, miner *TestMiner) *DealHa } } -func (dh *DealHarness) MakeOnlineDeal(ctx context.Context, rseed int, carExport, fastRet bool, startEpoch abi.ChainEpoch) { - res, _, data, err := CreateImportFile(ctx, dh.client, rseed, 0) - require.NoError(dh.t, err) +func (dh *DealHarness) MakeOnlineDeal(ctx context.Context, rseed int, fastRet bool, startEpoch abi.ChainEpoch) (deal *cid.Cid, res *api.ImportRes, path string) { + res, path = dh.client.CreateImportFile(ctx, rseed, 0) - fcid := res.Root - dh.t.Logf("FILE CID: %s", fcid) + dh.t.Logf("FILE CID: %s", res.Root) - deal := dh.StartDeal(ctx, fcid, fastRet, startEpoch) + deal = dh.StartDeal(ctx, res.Root, fastRet, 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) dh.WaitDealSealed(ctx, deal, false, false, nil) - // Retrieval - info, err := dh.client.ClientGetDealInfo(ctx, *deal) - require.NoError(dh.t, err) - - dh.PerformRetrieval(ctx, fcid, &info.PieceCID, carExport, data) + return deal, res, path } func (dh *DealHarness) StartDeal(ctx context.Context, fcid cid.Cid, fastRet bool, startEpoch abi.ChainEpoch) *cid.Cid { @@ -177,22 +168,25 @@ func (dh *DealHarness) StartSealingWaiting(ctx context.Context) { } } -func (dh *DealHarness) PerformRetrieval(ctx context.Context, fcid cid.Cid, piece *cid.Cid, carExport bool, expect []byte) { - offers, err := dh.client.ClientFindData(ctx, fcid, piece) +func (dh *DealHarness) PerformRetrieval(ctx context.Context, deal *cid.Cid, root cid.Cid, carExport bool) (path string) { + // perform retrieval. + info, err := dh.client.ClientGetDealInfo(ctx, *deal) require.NoError(dh.t, err) + offers, err := dh.client.ClientFindData(ctx, root, &info.PieceCID) + require.NoError(dh.t, err) require.NotEmpty(dh.t, offers, "no offers") - rpath, err := ioutil.TempDir("", "lotus-retrieve-test-") + tmpfile, err := ioutil.TempFile(dh.t.TempDir(), "ret-car") require.NoError(dh.t, err) - defer os.RemoveAll(rpath) //nolint:errcheck + defer tmpfile.Close() caddr, err := dh.client.WalletDefaultAddress(ctx) require.NoError(dh.t, err) ref := &api.FileRef{ - Path: filepath.Join(rpath, "ret"), + Path: tmpfile.Name(), IsCAR: carExport, } @@ -203,19 +197,17 @@ func (dh *DealHarness) PerformRetrieval(ctx context.Context, fcid cid.Cid, piece require.Emptyf(dh.t, update.Err, "retrieval failed: %s", update.Err) } - rdata, err := ioutil.ReadFile(filepath.Join(rpath, "ret")) + rdata, err := ioutil.ReadFile(tmpfile.Name()) require.NoError(dh.t, err) if carExport { - rdata = dh.ExtractCarData(ctx, rdata, rpath) + rdata = dh.ExtractFileFromCAR(ctx, rdata) } - if !bytes.Equal(rdata, expect) { - dh.t.Fatal("wrong expect retrieved") - } + return tmpfile.Name() } -func (dh *DealHarness) ExtractCarData(ctx context.Context, rdata []byte, rpath string) []byte { +func (dh *DealHarness) ExtractFileFromCAR(ctx context.Context, rdata []byte) []byte { bserv := dstest.Bserv() ch, err := car.LoadCar(bserv.Blockstore(), bytes.NewReader(rdata)) require.NoError(dh.t, err) @@ -230,23 +222,20 @@ func (dh *DealHarness) ExtractCarData(ctx context.Context, rdata []byte, rpath s fil, err := unixfile.NewUnixfsFile(ctx, dserv, nd) require.NoError(dh.t, err) - outPath := filepath.Join(rpath, "retLoadedCAR") - err = files.WriteTo(fil, outPath) + tmpfile, err := ioutil.TempFile(dh.t.TempDir(), "file-in-car") require.NoError(dh.t, err) - rdata, err = ioutil.ReadFile(outPath) + defer tmpfile.Close() + + err = files.WriteTo(fil, tmpfile.Name()) + require.NoError(dh.t, err) + + rdata, err = ioutil.ReadFile(tmpfile.Name()) require.NoError(dh.t, err) return rdata } -type DealsScaffold struct { - Ctx context.Context - Client *impl.FullNodeAPI - Miner TestMiner - BlockMiner *BlockMiner -} - func ConnectAndStartMining(t *testing.T, blocktime time.Duration, miner *TestMiner, clients ...api.FullNode) *BlockMiner { ctx := context.Background() diff --git a/itests/kit/files.go b/itests/kit/files.go new file mode 100644 index 000000000..d4e92fecf --- /dev/null +++ b/itests/kit/files.go @@ -0,0 +1,57 @@ +package kit + +import ( + "bytes" + "io" + "math/rand" + "os" + "testing" + + "github.com/minio/blake2b-simd" + + "github.com/stretchr/testify/require" +) + +// CreateRandomFile creates a random file with the provided seed and the +// provided size. +func CreateRandomFile(t *testing.T, rseed, size int) (path string) { + if size == 0 { + size = 1600 + } + + source := io.LimitReader(rand.New(rand.NewSource(int64(rseed))), int64(size)) + + file, err := os.CreateTemp(t.TempDir(), "sourcefile.dat") + require.NoError(t, err) + + n, err := io.Copy(file, source) + require.NoError(t, err) + require.EqualValues(t, n, size) + + return file.Name() +} + +// FilesEqual compares two files by blake2b hash equality. +func FilesEqual(t *testing.T, left, right string) bool { + // initialize hashes. + leftH, rightH := blake2b.New256(), blake2b.New256() + + // open files. + leftF, err := os.Open(left) + require.NoError(t, err) + + rightF, err := os.Open(right) + require.NoError(t, err) + + // feed hash functions. + _, err = io.Copy(leftH, leftF) + require.NoError(t, err) + + _, err = io.Copy(rightH, rightF) + require.NoError(t, err) + + // compute digests. + leftD, rightD := leftH.Sum(nil), rightH.Sum(nil) + + return bytes.Equal(leftD, rightD) +} diff --git a/itests/kit/init.go b/itests/kit/init.go index 57d60ad2a..8df4922b8 100644 --- a/itests/kit/init.go +++ b/itests/kit/init.go @@ -17,9 +17,13 @@ func init() { policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1) policy.SetMinVerifiedDealSize(abi.NewStoragePower(256)) - err := os.Setenv("BELLMAN_NO_GPU", "1") - if err != nil { + build.InsecurePoStValidation = true + + if err := os.Setenv("BELLMAN_NO_GPU", "1"); err != nil { panic(fmt.Sprintf("failed to set BELLMAN_NO_GPU env variable: %s", err)) } - build.InsecurePoStValidation = true + + if err := os.Setenv("LOTUS_DISABLE_WATCHDOG", "1"); err != nil { + panic(fmt.Sprintf("failed to set LOTUS_DISABLE_WATCHDOG env variable: %s", err)) + } } diff --git a/itests/kit/node_full.go b/itests/kit/node_full.go index f8f13c724..0e9912063 100644 --- a/itests/kit/node_full.go +++ b/itests/kit/node_full.go @@ -1,11 +1,14 @@ package kit import ( + "context" "testing" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api/v1api" "github.com/filecoin-project/lotus/chain/wallet" "github.com/multiformats/go-multiaddr" + "github.com/stretchr/testify/require" ) type TestFullNode struct { @@ -20,3 +23,10 @@ type TestFullNode struct { options NodeOpts } + +func (f *TestFullNode) CreateImportFile(ctx context.Context, rseed int, size int) (res *api.ImportRes, path string) { + path = CreateRandomFile(f.t, rseed, size) + res, err := f.ClientImport(ctx, api.FileRef{Path: path}) + require.NoError(f.t, err) + return res, path +} From e84b8ab3a0190cbd0d720b083cb8d8de0c9c112f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Fri, 11 Jun 2021 18:26:25 +0100 Subject: [PATCH 012/257] move new kit into kit2, re-enable unmigrated tests against kit1. --- cmd/lotus-storage-miner/allinfo_test.go | 4 +- itests/api_test.go | 26 +- ...tch_deal_test.go.no => batch_deal_test.go} | 0 ...ccupgrade_test.go.no => ccupgrade_test.go} | 0 itests/{cli_test.go.no => cli_test.go} | 0 ...deadlines_test.go.no => deadlines_test.go} | 0 itests/deals_test.go | 188 +++-- .../{gateway_test.go.no => gateway_test.go} | 2 +- itests/kit/blockminer.go | 8 +- itests/kit/client.go | 45 +- itests/kit/deals.go | 160 +++-- itests/kit/ensemble_presets.go | 41 -- itests/kit/funds.go | 19 +- itests/kit/init.go | 10 +- itests/kit/net.go | 125 ++-- itests/kit/node_builder.go | 658 ++++++++++++++++++ itests/kit/nodes.go | 153 ++++ itests/kit/pledge.go | 88 +++ itests/kit2/blockminer.go | 124 ++++ itests/kit2/client.go | 107 +++ itests/kit2/deals.go | 245 +++++++ itests/kit2/deals_state.go | 21 + itests/{kit => kit2}/ensemble.go | 263 ++----- itests/kit2/ensemble_opts.go | 35 + itests/kit2/ensemble_presets.go | 70 ++ itests/{kit => kit2}/files.go | 9 +- itests/kit2/funds.go | 34 + itests/kit2/init.go | 29 + itests/kit2/log.go | 19 + itests/{kit => kit2}/node_full.go | 7 +- itests/{kit => kit2}/node_miner.go | 10 +- itests/kit2/node_opts.go | 89 +++ itests/kit2/node_opts_nv.go | 65 ++ itests/kit2/rpc.go | 53 ++ .../{multisig_test.go.no => multisig_test.go} | 0 ...paych_api_test.go.no => paych_api_test.go} | 0 ...paych_cli_test.go.no => paych_cli_test.go} | 0 ...upgrade_test.go.no => sdr_upgrade_test.go} | 0 ...ledge_test.go.no => sector_pledge_test.go} | 0 ...te_test.go.no => sector_terminate_test.go} | 0 itests/{tape_test.go.no => tape_test.go} | 0 .../{verifreg_test.go.no => verifreg_test.go} | 0 ...pute_test.go.no => wdpost_dispute_test.go} | 0 itests/{wdpost_test.go.no => wdpost_test.go} | 0 44 files changed, 2198 insertions(+), 509 deletions(-) rename itests/{batch_deal_test.go.no => batch_deal_test.go} (100%) rename itests/{ccupgrade_test.go.no => ccupgrade_test.go} (100%) rename itests/{cli_test.go.no => cli_test.go} (100%) rename itests/{deadlines_test.go.no => deadlines_test.go} (100%) rename itests/{gateway_test.go.no => gateway_test.go} (99%) delete mode 100644 itests/kit/ensemble_presets.go create mode 100644 itests/kit/node_builder.go create mode 100644 itests/kit/nodes.go create mode 100644 itests/kit/pledge.go create mode 100644 itests/kit2/blockminer.go create mode 100644 itests/kit2/client.go create mode 100644 itests/kit2/deals.go create mode 100644 itests/kit2/deals_state.go rename itests/{kit => kit2}/ensemble.go (75%) create mode 100644 itests/kit2/ensemble_opts.go create mode 100644 itests/kit2/ensemble_presets.go rename itests/{kit => kit2}/files.go (82%) create mode 100644 itests/kit2/funds.go create mode 100644 itests/kit2/init.go create mode 100644 itests/kit2/log.go rename itests/{kit => kit2}/node_full.go (78%) rename itests/{kit => kit2}/node_miner.go (95%) create mode 100644 itests/kit2/node_opts.go create mode 100644 itests/kit2/node_opts_nv.go create mode 100644 itests/kit2/rpc.go rename itests/{multisig_test.go.no => multisig_test.go} (100%) rename itests/{paych_api_test.go.no => paych_api_test.go} (100%) rename itests/{paych_cli_test.go.no => paych_cli_test.go} (100%) rename itests/{sdr_upgrade_test.go.no => sdr_upgrade_test.go} (100%) rename itests/{sector_pledge_test.go.no => sector_pledge_test.go} (100%) rename itests/{sector_terminate_test.go.no => sector_terminate_test.go} (100%) rename itests/{tape_test.go.no => tape_test.go} (100%) rename itests/{verifreg_test.go.no => verifreg_test.go} (100%) rename itests/{wdpost_dispute_test.go.no => wdpost_dispute_test.go} (100%) rename itests/{wdpost_test.go.no => wdpost_test.go} (100%) diff --git a/cmd/lotus-storage-miner/allinfo_test.go b/cmd/lotus-storage-miner/allinfo_test.go index 0a4461b31..cbe65524e 100644 --- a/cmd/lotus-storage-miner/allinfo_test.go +++ b/cmd/lotus-storage-miner/allinfo_test.go @@ -40,7 +40,7 @@ func TestMinerAllInfo(t *testing.T) { policy.SetPreCommitChallengeDelay(oldDelay) }) - n, sn := kit.FullNodeBuilder(t, kit.OneFull, kit.OneMiner) + n, sn := kit.Builder(t, kit.OneFull, kit.OneMiner) client, miner := n[0].FullNode, sn[0] kit.ConnectAndStartMining(t, time.Second, miner, client.(*impl.FullNodeAPI)) @@ -61,7 +61,7 @@ func TestMinerAllInfo(t *testing.T) { t.Run("pre-info-all", run) dh := kit.NewDealHarness(t, client, miner) - dh.MakeOnlineDeal(context.Background(), 6, false, false, 0) + dh.MakeFullDeal(context.Background(), 6, false, false, 0) t.Run("post-info-all", run) } diff --git a/itests/api_test.go b/itests/api_test.go index c6694a554..a8abee92f 100644 --- a/itests/api_test.go +++ b/itests/api_test.go @@ -12,7 +12,7 @@ import ( 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/itests/kit" + "github.com/filecoin-project/lotus/itests/kit2" "github.com/stretchr/testify/require" ) @@ -21,16 +21,16 @@ func TestAPI(t *testing.T) { runAPITest(t) }) t.Run("rpc", func(t *testing.T) { - runAPITest(t, kit.ThroughRPC()) + runAPITest(t, kit2.ThroughRPC()) }) } type apiSuite struct { - opts []kit.NodeOpt + opts []kit2.NodeOpt } // runAPITest is the entry point to API test suite -func runAPITest(t *testing.T, opts ...kit.NodeOpt) { +func runAPITest(t *testing.T, opts ...kit2.NodeOpt) { ts := apiSuite{opts: opts} t.Run("version", ts.testVersion) @@ -48,7 +48,7 @@ func (ts *apiSuite) testVersion(t *testing.T) { lapi.RunningNodeType = lapi.NodeUnknown }) - full, _, _ := kit.EnsembleMinimal(t, ts.opts...) + full, _, _ := kit2.EnsembleMinimal(t, ts.opts...) v, err := full.Version(context.Background()) require.NoError(t, err) @@ -61,7 +61,7 @@ func (ts *apiSuite) testVersion(t *testing.T) { func (ts *apiSuite) testID(t *testing.T) { ctx := context.Background() - full, _, _ := kit.EnsembleMinimal(t, ts.opts...) + full, _, _ := kit2.EnsembleMinimal(t, ts.opts...) id, err := full.ID(ctx) if err != nil { @@ -73,7 +73,7 @@ func (ts *apiSuite) testID(t *testing.T) { func (ts *apiSuite) testConnectTwo(t *testing.T) { ctx := context.Background() - one, two, _, ens := kit.EnsembleTwoOne(t, ts.opts...) + one, two, _, ens := kit2.EnsembleTwoOne(t, ts.opts...) p, err := one.NetPeers(ctx) require.NoError(t, err) @@ -97,7 +97,7 @@ func (ts *apiSuite) testConnectTwo(t *testing.T) { func (ts *apiSuite) testSearchMsg(t *testing.T) { ctx := context.Background() - full, _, ens := kit.EnsembleMinimal(t, ts.opts...) + full, _, ens := kit2.EnsembleMinimal(t, ts.opts...) senderAddr, err := full.WalletDefaultAddress(ctx) require.NoError(t, err) @@ -127,7 +127,7 @@ func (ts *apiSuite) testSearchMsg(t *testing.T) { func (ts *apiSuite) testMining(t *testing.T) { ctx := context.Background() - full, miner, _ := kit.EnsembleMinimal(t, ts.opts...) + full, miner, _ := kit2.EnsembleMinimal(t, ts.opts...) newHeads, err := full.ChainNotify(ctx) require.NoError(t, err) @@ -138,7 +138,7 @@ func (ts *apiSuite) testMining(t *testing.T) { require.NoError(t, err) require.Equal(t, int64(h1.Height()), int64(baseHeight)) - bm := kit.NewBlockMiner(t, miner) + bm := kit2.NewBlockMiner(t, miner) bm.MineUntilBlock(ctx, full, nil) require.NoError(t, err) @@ -170,7 +170,7 @@ func (ts *apiSuite) testMiningReal(t *testing.T) { func (ts *apiSuite) testNonGenesisMiner(t *testing.T) { ctx := context.Background() - full, genesisMiner, ens := kit.EnsembleMinimal(t, ts.opts...) + full, genesisMiner, ens := kit2.EnsembleMinimal(t, ts.opts...) ens.BeginMining(4 * time.Millisecond) @@ -180,8 +180,8 @@ func (ts *apiSuite) testNonGenesisMiner(t *testing.T) { _, err = full.StateMinerInfo(ctx, gaa, types.EmptyTSK) require.NoError(t, err) - var newMiner kit.TestMiner - ens.Miner(&newMiner, full, kit.OwnerAddr(full.DefaultKey)).Start() + var newMiner kit2.TestMiner + ens.Miner(&newMiner, full, kit2.OwnerAddr(full.DefaultKey)).Start() ta, err := newMiner.ActorAddress(ctx) require.NoError(t, err) diff --git a/itests/batch_deal_test.go.no b/itests/batch_deal_test.go similarity index 100% rename from itests/batch_deal_test.go.no rename to itests/batch_deal_test.go diff --git a/itests/ccupgrade_test.go.no b/itests/ccupgrade_test.go similarity index 100% rename from itests/ccupgrade_test.go.no rename to itests/ccupgrade_test.go diff --git a/itests/cli_test.go.no b/itests/cli_test.go similarity index 100% rename from itests/cli_test.go.no rename to itests/cli_test.go diff --git a/itests/deadlines_test.go.no b/itests/deadlines_test.go similarity index 100% rename from itests/deadlines_test.go.no rename to itests/deadlines_test.go diff --git a/itests/deals_test.go b/itests/deals_test.go index a14a5bbb6..b30e5ba69 100644 --- a/itests/deals_test.go +++ b/itests/deals_test.go @@ -14,7 +14,7 @@ import ( "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/builtin/market" "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/itests/kit2" "github.com/filecoin-project/lotus/markets/storageadapter" "github.com/filecoin-project/lotus/node" market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" @@ -23,38 +23,30 @@ import ( ) func TestDealCyclesConcurrent(t *testing.T) { - kit.QuietMiningLogs() + if testing.Short() { + t.Skip("skipping test in short mode") + } + + kit2.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) + startEpoch := abi.ChainEpoch(2 << 12) runTest := func(t *testing.T, n int, fastRetrieval bool, carExport bool) { - client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) + client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs()) ens.InterconnectAll().BeginMining(blockTime) - dh := kit.NewDealHarness(t, client, miner) + dh := kit2.NewDealHarness(t, client, miner) - errgrp, _ := errgroup.WithContext(context.Background()) - for i := 0; i < n; i++ { - i := i - errgrp.Go(func() (err error) { - defer func() { - // This is necessary because we use require, which invokes t.Fatal, - // and that's not - if r := recover(); r != nil { - err = fmt.Errorf("deal failed: %s", r) - } - }() - deal, res, inPath := dh.MakeOnlineDeal(context.Background(), 5+i, fastRetrieval, dealStartEpoch) - outPath := dh.PerformRetrieval(context.Background(), deal, res.Root, carExport) - kit.FilesEqual(t, inPath, outPath) - return nil - }) - } - require.NoError(t, errgrp.Wait()) + runConcurrentDeals(t, dh, fullDealCyclesOpts{ + n: n, + fastRetrieval: fastRetrieval, + carExport: carExport, + startEpoch: startEpoch, + }) } cycles := []int{1, 2, 4, 8} @@ -67,32 +59,60 @@ func TestDealCyclesConcurrent(t *testing.T) { } } -// func TestAPIDealFlowReal(t *testing.T) { -// if testing.Short() { -// t.Skip("skipping test in short mode") -// } -// -// kit.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, kit.FullNodeBuilder, time.Second, false, false, 0) -// }) -// -// t.Run("fast-retrieval", func(t *testing.T) { -// runFullDealCycles(t, 1, kit.FullNodeBuilder, time.Second, false, true, 0) -// }) -// -// t.Run("retrieval-second", func(t *testing.T) { -// runSecondDealRetrievalTest(t, kit.FullNodeBuilder, time.Second) -// }) -// } +type fullDealCyclesOpts struct { + n int + fastRetrieval bool + carExport bool + startEpoch abi.ChainEpoch +} + +func runConcurrentDeals(t *testing.T, dh *kit2.DealHarness, opts fullDealCyclesOpts) { + errgrp, _ := errgroup.WithContext(context.Background()) + for i := 0; i < opts.n; i++ { + i := i + errgrp.Go(func() (err error) { + defer func() { + // This is necessary because golang can't deal with test + // failures being reported from children goroutines ¯\_(ツ)_/¯ + if r := recover(); r != nil { + err = fmt.Errorf("deal failed: %s", r) + } + }() + deal, res, inPath := dh.MakeOnlineDeal(context.Background(), 5+i, opts.fastRetrieval, opts.startEpoch) + outPath := dh.PerformRetrieval(context.Background(), deal, res.Root, opts.carExport) + kit2.AssertFilesEqual(t, inPath, outPath) + return nil + }) + } + require.NoError(t, errgrp.Wait()) +} + +func TestDealsWithSealingAndRPC(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode") + } + + kit2.QuietMiningLogs() + + var blockTime = 1 * time.Second + + client, miner, ens := kit2.EnsembleMinimal(t, kit2.ThroughRPC()) // no mock proofs. + ens.InterconnectAll().BeginMining(blockTime) + dh := kit2.NewDealHarness(t, client, miner) + + t.Run("stdretrieval", func(t *testing.T) { + runConcurrentDeals(t, dh, fullDealCyclesOpts{n: 1}) + }) + + t.Run("fastretrieval", func(t *testing.T) { + runConcurrentDeals(t, dh, fullDealCyclesOpts{n: 1, fastRetrieval: true}) + }) + + t.Run("fastretrieval-twodeals-sequential", func(t *testing.T) { + runConcurrentDeals(t, dh, fullDealCyclesOpts{n: 1, fastRetrieval: true}) + runConcurrentDeals(t, dh, fullDealCyclesOpts{n: 1, fastRetrieval: true}) + }) +} func TestPublishDealsBatching(t *testing.T) { var ( @@ -102,7 +122,7 @@ func TestPublishDealsBatching(t *testing.T) { startEpoch = abi.ChainEpoch(2 << 12) ) - kit.QuietMiningLogs() + kit2.QuietMiningLogs() opts := node.Override(new(*storageadapter.DealPublisher), storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{ @@ -111,10 +131,10 @@ func TestPublishDealsBatching(t *testing.T) { }), ) - client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ExtraNodeOpts(opts)) + client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), kit2.ConstructorOpts(opts)) ens.InterconnectAll().BeginMining(10 * time.Millisecond) - dh := kit.NewDealHarness(t, client, miner) + dh := kit2.NewDealHarness(t, client, miner) // Starts a deal and waits until it's published runDealTillPublish := func(rseed int) { @@ -189,23 +209,23 @@ func TestFirstDealEnablesMining(t *testing.T) { t.Skip("skipping test in short mode") } - kit.QuietMiningLogs() + kit2.QuietMiningLogs() var ( - client kit.TestFullNode - genMiner kit.TestMiner // bootstrap - provider kit.TestMiner // no sectors, will need to create one + client kit2.TestFullNode + genMiner kit2.TestMiner // bootstrap + provider kit2.TestMiner // no sectors, will need to create one ) - ens := kit.NewEnsemble(t) - ens.FullNode(&client, kit.MockProofs()) - ens.Miner(&genMiner, &client, kit.MockProofs()) - ens.Miner(&provider, &client, kit.MockProofs(), kit.PresealSectors(0)) + ens := kit2.NewEnsemble(t, kit2.MockProofs()) + ens.FullNode(&client) + ens.Miner(&genMiner, &client) + ens.Miner(&provider, &client, kit2.PresealSectors(0)) ens.Start().InterconnectAll().BeginMining(50 * time.Millisecond) ctx := context.Background() - dh := kit.NewDealHarness(t, &client, &provider) + dh := kit2.NewDealHarness(t, &client, &provider) ref, _ := client.CreateImportFile(ctx, 5, 0) @@ -258,10 +278,10 @@ func TestOfflineDealFlow(t *testing.T) { runTest := func(t *testing.T, fastRet bool) { ctx := context.Background() - client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) + client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs()) ens.InterconnectAll().BeginMining(blocktime) - dh := kit.NewDealHarness(t, client, miner) + dh := kit2.NewDealHarness(t, client, miner) // Create a random file and import on the client. res, inFile := client.CreateImportFile(ctx, 1, 0) @@ -325,49 +345,13 @@ func TestOfflineDealFlow(t *testing.T) { // Retrieve the deal outFile := dh.PerformRetrieval(ctx, proposalCid, rootCid, false) - equal := kit.FilesEqual(t, inFile, outFile) - require.True(t, equal) + kit2.AssertFilesEqual(t, inFile, outFile) } - t.Run("NormalRetrieval", func(t *testing.T) { runTest(t, false) }) - t.Run("FastRetrieval", func(t *testing.T) { runTest(t, true) }) + t.Run("stdretrieval", func(t *testing.T) { runTest(t, false) }) + t.Run("fastretrieval", func(t *testing.T) { runTest(t, true) }) } -// -// func runFastRetrievalDealFlowT(t *testing.T, b kit.APIBuilder, blocktime time.Duration, startEpoch abi.ChainEpoch) { -// ctx := context.Background() -// -// var ( -// nb = kit.NewNodeBuilder(t) -// full = nb.FullNode() -// miner = nb.Miner(full) -// ) -// -// nb.Create() -// -// kit.ConnectAndStartMining(t, blocktime, miner, full) -// -// dh := kit.NewDealHarness(t, full, miner) -// data := make([]byte, 1600) -// rand.New(rand.NewSource(int64(8))).Read(data) -// -// r := bytes.NewReader(data) -// fcid, err := full.FullNode.(*impl.FullNodeAPI).ClientImportLocal(ctx, r) -// require.NoError(t, err) -// -// fmt.Println("FILE CID: ", fcid) -// -// deal := dh.StartDeal(ctx, fcid, true, startEpoch) -// dh.WaitDealPublished(ctx, deal) -// -// fmt.Println("deal published, retrieving") -// -// // Retrieval -// info, err := full.ClientGetDealInfo(ctx, *deal) -// require.NoError(t, err) -// -// dh.PerformRetrieval(ctx, fcid, &info.PieceCID, false, data) -// } // // func runSecondDealRetrievalTest(t *testing.T, b kit.APIBuilder, blocktime time.Duration) { // ctx := context.Background() diff --git a/itests/gateway_test.go.no b/itests/gateway_test.go similarity index 99% rename from itests/gateway_test.go.no rename to itests/gateway_test.go index 9401f20a0..7f1b70f2d 100644 --- a/itests/gateway_test.go.no +++ b/itests/gateway_test.go @@ -291,7 +291,7 @@ func startNodes( }, }, ) - n, sn := kit.MinerRPCMockMinerBuilder(t, opts, kit.OneMiner) + n, sn := kit.RPCMockMinerBuilder(t, opts, kit.OneMiner) full := n[0] lite := n[1] diff --git a/itests/kit/blockminer.go b/itests/kit/blockminer.go index 2c9bd47c6..3b1f1fedf 100644 --- a/itests/kit/blockminer.go +++ b/itests/kit/blockminer.go @@ -15,14 +15,14 @@ import ( // BlockMiner is a utility that makes a test miner Mine blocks on a timer. type BlockMiner struct { t *testing.T - miner *TestMiner + miner TestMiner nextNulls int64 wg sync.WaitGroup cancel context.CancelFunc } -func NewBlockMiner(t *testing.T, miner *TestMiner) *BlockMiner { +func NewBlockMiner(t *testing.T, miner TestMiner) *BlockMiner { return &BlockMiner{ t: t, miner: miner, @@ -69,7 +69,7 @@ func (bm *BlockMiner) InjectNulls(rounds abi.ChainEpoch) { atomic.AddInt64(&bm.nextNulls, int64(rounds)) } -func (bm *BlockMiner) MineUntilBlock(ctx context.Context, fn *TestFullNode, cb func(abi.ChainEpoch)) { +func (bm *BlockMiner) MineUntilBlock(ctx context.Context, fn TestFullNode, cb func(abi.ChainEpoch)) { for i := 0; i < 1000; i++ { var ( success bool @@ -93,7 +93,7 @@ func (bm *BlockMiner) MineUntilBlock(ctx context.Context, fn *TestFullNode, cb f if success { // Wait until it shows up on the given full nodes ChainHead - nloops := 200 + nloops := 50 for i := 0; i < nloops; i++ { ts, err := fn.ChainHead(ctx) require.NoError(bm.t, err) diff --git a/itests/kit/client.go b/itests/kit/client.go index 0d247043e..6b7d46265 100644 --- a/itests/kit/client.go +++ b/itests/kit/client.go @@ -3,12 +3,16 @@ package kit import ( "context" "fmt" + "io/ioutil" + "math/rand" + "os" "path/filepath" "regexp" "strings" "testing" "time" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/specs-actors/v2/actors/builtin" @@ -39,7 +43,7 @@ func RunClientTest(t *testing.T, cmds []*lcli.Command, clientNode TestFullNode) // Create a deal (non-interactive) // client deal --start-epoch= 1000000attofil - res, _ := clientNode.CreateImportFile(ctx, 1, 0) + res, _, _, err := CreateImportFile(ctx, clientNode, 1, 0) require.NoError(t, err) startEpoch := fmt.Sprintf("--start-epoch=%d", 2<<12) @@ -56,7 +60,7 @@ func RunClientTest(t *testing.T, cmds []*lcli.Command, clientNode TestFullNode) // // "no" (verified Client) // "yes" (confirm deal) - res, _ = clientNode.CreateImportFile(ctx, 2, 0) + res, _, _, err = CreateImportFile(ctx, clientNode, 2, 0) require.NoError(t, err) dataCid2 := res.Root duration = fmt.Sprintf("%d", build.MinDealDuration/builtin.EpochsInDay) @@ -99,9 +103,44 @@ func RunClientTest(t *testing.T, cmds []*lcli.Command, clientNode TestFullNode) // Retrieve the first file from the Miner // client retrieve - tmpdir := t.TempDir() + tmpdir, err := ioutil.TempDir(os.TempDir(), "test-cli-Client") + require.NoError(t, err) path := filepath.Join(tmpdir, "outfile.dat") out = clientCLI.RunCmd("client", "retrieve", dataCid.String(), path) fmt.Println("retrieve:\n", out) require.Regexp(t, regexp.MustCompile("Success"), out) } + +func CreateImportFile(ctx context.Context, client api.FullNode, rseed int, size int) (res *api.ImportRes, path string, data []byte, err error) { + data, path, err = createRandomFile(rseed, size) + if err != nil { + return nil, "", nil, err + } + + res, err = client.ClientImport(ctx, api.FileRef{Path: path}) + if err != nil { + return nil, "", nil, err + } + return res, path, data, nil +} + +func createRandomFile(rseed, size int) ([]byte, string, error) { + if size == 0 { + size = 1600 + } + data := make([]byte, size) + rand.New(rand.NewSource(int64(rseed))).Read(data) + + dir, err := ioutil.TempDir(os.TempDir(), "test-make-deal-") + if err != nil { + return nil, "", err + } + + path := filepath.Join(dir, "sourcefile.dat") + err = ioutil.WriteFile(path, data, 0644) + if err != nil { + return nil, "", err + } + + return data, path, nil +} diff --git a/itests/kit/deals.go b/itests/kit/deals.go index d62c5a7bd..c768eb87f 100644 --- a/itests/kit/deals.go +++ b/itests/kit/deals.go @@ -3,7 +3,10 @@ package kit import ( "bytes" "context" + "fmt" "io/ioutil" + "os" + "path/filepath" "testing" "time" @@ -18,6 +21,7 @@ import ( "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" ipld "github.com/ipfs/go-ipld-format" dag "github.com/ipfs/go-merkledag" dstest "github.com/ipfs/go-merkledag/test" @@ -26,12 +30,12 @@ import ( type DealHarness struct { t *testing.T - client *TestFullNode - miner *TestMiner + client api.FullNode + miner TestMiner } // NewDealHarness creates a test harness that contains testing utilities for deals. -func NewDealHarness(t *testing.T, client *TestFullNode, miner *TestMiner) *DealHarness { +func NewDealHarness(t *testing.T, client api.FullNode, miner TestMiner) *DealHarness { return &DealHarness{ t: t, client: client, @@ -39,27 +43,38 @@ func NewDealHarness(t *testing.T, client *TestFullNode, miner *TestMiner) *DealH } } -func (dh *DealHarness) MakeOnlineDeal(ctx context.Context, rseed int, fastRet bool, startEpoch abi.ChainEpoch) (deal *cid.Cid, res *api.ImportRes, path string) { - res, path = dh.client.CreateImportFile(ctx, rseed, 0) +func (dh *DealHarness) MakeFullDeal(ctx context.Context, rseed int, carExport, fastRet bool, startEpoch abi.ChainEpoch) { + res, _, data, err := CreateImportFile(ctx, dh.client, rseed, 0) + if err != nil { + dh.t.Fatal(err) + } - dh.t.Logf("FILE CID: %s", res.Root) + fcid := res.Root + fmt.Println("FILE CID: ", fcid) - deal = dh.StartDeal(ctx, res.Root, fastRet, startEpoch) + deal := dh.StartDeal(ctx, fcid, fastRet, 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) dh.WaitDealSealed(ctx, deal, false, false, nil) - return deal, res, path + // Retrieval + info, err := dh.client.ClientGetDealInfo(ctx, *deal) + require.NoError(dh.t, err) + + dh.TestRetrieval(ctx, fcid, &info.PieceCID, carExport, data) } func (dh *DealHarness) StartDeal(ctx context.Context, fcid cid.Cid, fastRet bool, startEpoch abi.ChainEpoch) *cid.Cid { maddr, err := dh.miner.ActorAddress(ctx) - require.NoError(dh.t, err) + if err != nil { + dh.t.Fatal(err) + } addr, err := dh.client.WalletDefaultAddress(ctx) - require.NoError(dh.t, err) - + if err != nil { + dh.t.Fatal(err) + } deal, err := dh.client.ClientStartDeal(ctx, &api.StartDealParams{ Data: &storagemarket.DataRef{ TransferType: storagemarket.TTGraphsync, @@ -72,8 +87,9 @@ func (dh *DealHarness) StartDeal(ctx context.Context, fcid cid.Cid, fastRet bool MinBlocksDuration: uint64(build.MinDealDuration), FastRetrieval: fastRet, }) - require.NoError(dh.t, err) - + if err != nil { + dh.t.Fatalf("%+v", err) + } return deal } @@ -98,7 +114,7 @@ loop: case storagemarket.StorageDealError: dh.t.Fatal("deal errored", di.Message) case storagemarket.StorageDealActive: - dh.t.Log("COMPLETE", di) + fmt.Println("COMPLETE", di) break loop } @@ -113,7 +129,7 @@ loop: } } - dh.t.Logf("Deal %d state: client:%s provider:%s\n", di.DealID, storagemarket.DealStates[di.State], storagemarket.DealStates[minerState]) + fmt.Printf("Deal %d state: client:%s provider:%s\n", di.DealID, storagemarket.DealStates[di.State], storagemarket.DealStates[minerState]) time.Sleep(time.Second / 2) if cb != nil { cb() @@ -124,10 +140,10 @@ loop: func (dh *DealHarness) WaitDealPublished(ctx context.Context, deal *cid.Cid) { subCtx, cancel := context.WithCancel(ctx) defer cancel() - updates, err := dh.miner.MarketGetDealUpdates(subCtx) - require.NoError(dh.t, err) - + if err != nil { + dh.t.Fatal(err) + } for { select { case <-ctx.Done(): @@ -142,10 +158,10 @@ func (dh *DealHarness) WaitDealPublished(ctx context.Context, deal *cid.Cid) { case storagemarket.StorageDealError: dh.t.Fatal("deal errored", di.Message) case storagemarket.StorageDealFinalizing, storagemarket.StorageDealAwaitingPreCommit, storagemarket.StorageDealSealing, storagemarket.StorageDealActive: - dh.t.Log("COMPLETE", di) + fmt.Println("COMPLETE", di) return } - dh.t.Log("Deal state: ", storagemarket.DealStates[di.State]) + fmt.Println("Deal state: ", storagemarket.DealStates[di.State]) } } } @@ -164,79 +180,97 @@ func (dh *DealHarness) StartSealingWaiting(ctx context.Context) { require.NoError(dh.t, dh.miner.SectorStartSealing(ctx, snum)) } - dh.miner.FlushSealingBatches(ctx) + flushSealingBatches(dh.t, ctx, dh.miner) } } -func (dh *DealHarness) PerformRetrieval(ctx context.Context, deal *cid.Cid, root cid.Cid, carExport bool) (path string) { - // perform retrieval. - info, err := dh.client.ClientGetDealInfo(ctx, *deal) - require.NoError(dh.t, err) +func (dh *DealHarness) TestRetrieval(ctx context.Context, fcid cid.Cid, piece *cid.Cid, carExport bool, expect []byte) { + offers, err := dh.client.ClientFindData(ctx, fcid, piece) + if err != nil { + dh.t.Fatal(err) + } - offers, err := dh.client.ClientFindData(ctx, root, &info.PieceCID) - require.NoError(dh.t, err) - require.NotEmpty(dh.t, offers, "no offers") + if len(offers) < 1 { + dh.t.Fatal("no offers") + } - tmpfile, err := ioutil.TempFile(dh.t.TempDir(), "ret-car") - require.NoError(dh.t, err) - - defer tmpfile.Close() + rpath, err := ioutil.TempDir("", "lotus-retrieve-test-") + if err != nil { + dh.t.Fatal(err) + } + defer os.RemoveAll(rpath) //nolint:errcheck caddr, err := dh.client.WalletDefaultAddress(ctx) - require.NoError(dh.t, err) + if err != nil { + dh.t.Fatal(err) + } ref := &api.FileRef{ - Path: tmpfile.Name(), + Path: filepath.Join(rpath, "ret"), IsCAR: carExport, } - updates, err := dh.client.ClientRetrieveWithEvents(ctx, offers[0].Order(caddr), ref) - require.NoError(dh.t, err) - + if err != nil { + dh.t.Fatal(err) + } for update := range updates { - require.Emptyf(dh.t, update.Err, "retrieval failed: %s", update.Err) + if update.Err != "" { + dh.t.Fatalf("retrieval failed: %s", update.Err) + } } - rdata, err := ioutil.ReadFile(tmpfile.Name()) - require.NoError(dh.t, err) + rdata, err := ioutil.ReadFile(filepath.Join(rpath, "ret")) + if err != nil { + dh.t.Fatal(err) + } if carExport { - rdata = dh.ExtractFileFromCAR(ctx, rdata) + rdata = dh.ExtractCarData(ctx, rdata, rpath) } - return tmpfile.Name() + if !bytes.Equal(rdata, expect) { + dh.t.Fatal("wrong expect retrieved") + } } -func (dh *DealHarness) ExtractFileFromCAR(ctx context.Context, rdata []byte) []byte { +func (dh *DealHarness) ExtractCarData(ctx context.Context, rdata []byte, rpath string) []byte { bserv := dstest.Bserv() ch, err := car.LoadCar(bserv.Blockstore(), bytes.NewReader(rdata)) - require.NoError(dh.t, err) - + if err != nil { + dh.t.Fatal(err) + } b, err := bserv.GetBlock(ctx, ch.Roots[0]) - require.NoError(dh.t, err) - + if err != nil { + dh.t.Fatal(err) + } nd, err := ipld.Decode(b) - require.NoError(dh.t, err) - + if err != nil { + dh.t.Fatal(err) + } dserv := dag.NewDAGService(bserv) fil, err := unixfile.NewUnixfsFile(ctx, dserv, nd) - require.NoError(dh.t, err) - - tmpfile, err := ioutil.TempFile(dh.t.TempDir(), "file-in-car") - require.NoError(dh.t, err) - - defer tmpfile.Close() - - err = files.WriteTo(fil, tmpfile.Name()) - require.NoError(dh.t, err) - - rdata, err = ioutil.ReadFile(tmpfile.Name()) - require.NoError(dh.t, err) - + if err != nil { + dh.t.Fatal(err) + } + outPath := filepath.Join(rpath, "retLoadedCAR") + if err := files.WriteTo(fil, outPath); err != nil { + dh.t.Fatal(err) + } + rdata, err = ioutil.ReadFile(outPath) + if err != nil { + dh.t.Fatal(err) + } return rdata } -func ConnectAndStartMining(t *testing.T, blocktime time.Duration, miner *TestMiner, clients ...api.FullNode) *BlockMiner { +type DealsScaffold struct { + Ctx context.Context + Client *impl.FullNodeAPI + Miner TestMiner + BlockMiner *BlockMiner +} + +func ConnectAndStartMining(t *testing.T, blocktime time.Duration, miner TestMiner, clients ...api.FullNode) *BlockMiner { ctx := context.Background() for _, c := range clients { diff --git a/itests/kit/ensemble_presets.go b/itests/kit/ensemble_presets.go deleted file mode 100644 index fa7746f95..000000000 --- a/itests/kit/ensemble_presets.go +++ /dev/null @@ -1,41 +0,0 @@ -package kit - -import "testing" - -// EnsembleMinimal creates and starts an ensemble with a single full node and a single miner. -// It does not interconnect nodes nor does it begin mining. -func EnsembleMinimal(t *testing.T, opts ...NodeOpt) (*TestFullNode, *TestMiner, *Ensemble) { - var ( - full TestFullNode - miner TestMiner - ) - ensemble := NewEnsemble(t).FullNode(&full, opts...).Miner(&miner, &full, opts...).Start() - return &full, &miner, ensemble -} - -// EnsembleTwoOne creates and starts an ensemble with two full nodes and one miner. -// It does not interconnect nodes nor does it begin mining. -func EnsembleTwoOne(t *testing.T, opts ...NodeOpt) (*TestFullNode, *TestFullNode, *TestMiner, *Ensemble) { - var ( - one, two TestFullNode - miner TestMiner - ) - ensemble := NewEnsemble(t).FullNode(&one, opts...).FullNode(&two, opts...).Miner(&miner, &one, opts...).Start() - return &one, &two, &miner, ensemble -} - -// EnsembleOneTwo creates and starts an ensemble with one full node and two miners. -// It does not interconnect nodes nor does it begin mining. -func EnsembleOneTwo(t *testing.T, opts ...NodeOpt) (*TestFullNode, *TestMiner, *TestMiner, *Ensemble) { - var ( - full TestFullNode - one, two TestMiner - ) - ensemble := NewEnsemble(t). - FullNode(&full, opts...). - Miner(&one, &full, opts...). - Miner(&two, &full, opts...). - Start() - - return &full, &one, &two, ensemble -} diff --git a/itests/kit/funds.go b/itests/kit/funds.go index 2ea822979..4c739dc62 100644 --- a/itests/kit/funds.go +++ b/itests/kit/funds.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/filecoin-project/go-state-types/abi" - "github.com/stretchr/testify/require" "github.com/filecoin-project/go-address" "github.com/filecoin-project/lotus/api" @@ -16,7 +15,9 @@ import ( // to the recipient address. func SendFunds(ctx context.Context, t *testing.T, sender TestFullNode, recipient address.Address, amount abi.TokenAmount) { senderAddr, err := sender.WalletDefaultAddress(ctx) - require.NoError(t, err) + if err != nil { + t.Fatal(err) + } msg := &types.Message{ From: senderAddr, @@ -25,10 +26,14 @@ func SendFunds(ctx context.Context, t *testing.T, sender TestFullNode, recipient } sm, err := sender.MpoolPushMessage(ctx, msg, nil) - require.NoError(t, err) - + if err != nil { + t.Fatal(err) + } res, err := sender.StateWaitMsg(ctx, sm.Cid(), 3, api.LookbackNoLimit, true) - require.NoError(t, err) - - require.Equal(t, 0, res.Receipt.ExitCode, "did not successfully send funds") + if err != nil { + t.Fatal(err) + } + if res.Receipt.ExitCode != 0 { + t.Fatal("did not successfully send money") + } } diff --git a/itests/kit/init.go b/itests/kit/init.go index 8df4922b8..57d60ad2a 100644 --- a/itests/kit/init.go +++ b/itests/kit/init.go @@ -17,13 +17,9 @@ func init() { policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1) policy.SetMinVerifiedDealSize(abi.NewStoragePower(256)) - build.InsecurePoStValidation = true - - if err := os.Setenv("BELLMAN_NO_GPU", "1"); err != nil { + err := os.Setenv("BELLMAN_NO_GPU", "1") + if err != nil { panic(fmt.Sprintf("failed to set BELLMAN_NO_GPU env variable: %s", err)) } - - if err := os.Setenv("LOTUS_DISABLE_WATCHDOG", "1"); err != nil { - panic(fmt.Sprintf("failed to set LOTUS_DISABLE_WATCHDOG env variable: %s", err)) - } + build.InsecurePoStValidation = true } diff --git a/itests/kit/net.go b/itests/kit/net.go index aea609091..54c72443f 100644 --- a/itests/kit/net.go +++ b/itests/kit/net.go @@ -1,42 +1,87 @@ package kit -// -// func StartTwoNodesOneMiner(ctx context.Context, t *testing.T, blocktime time.Duration) ([]TestFullNode, []address.Address) { -// n, sn := MinerRPCMockMinerBuilder(t, TwoFull, OneMiner) -// -// fullNode1 := n[0] -// fullNode2 := n[1] -// miner := sn[0] -// -// // Get everyone connected -// addrs, err := fullNode1.NetAddrsListen(ctx) -// if err != nil { -// t.Fatal(err) -// } -// -// if err := fullNode2.NetConnect(ctx, addrs); err != nil { -// t.Fatal(err) -// } -// -// // Start mining blocks -// bm := NewBlockMiner(t, miner) -// bm.MineBlocks(ctx, blocktime) -// t.Cleanup(bm.Stop) -// -// // Send some funds to register the second node -// fullNodeAddr2, err := fullNode2.WalletNew(ctx, types.KTSecp256k1) -// if err != nil { -// t.Fatal(err) -// } -// -// SendFunds(ctx, t, fullNode1, fullNodeAddr2, abi.NewTokenAmount(1e18)) -// -// // Get the first node's address -// fullNodeAddr1, err := fullNode1.WalletDefaultAddress(ctx) -// if err != nil { -// t.Fatal(err) -// } -// -// // Create mock CLI -// return n, []address.Address{fullNodeAddr1, fullNodeAddr2} -// } +import ( + "context" + "testing" + "time" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/chain/types" + + "github.com/filecoin-project/go-address" +) + +func StartOneNodeOneMiner(ctx context.Context, t *testing.T, blocktime time.Duration) (TestFullNode, address.Address) { + n, sn := RPCMockMinerBuilder(t, OneFull, OneMiner) + + full := n[0] + miner := sn[0] + + // Get everyone connected + addrs, err := full.NetAddrsListen(ctx) + if err != nil { + t.Fatal(err) + } + + if err := miner.NetConnect(ctx, addrs); err != nil { + t.Fatal(err) + } + + // Start mining blocks + bm := NewBlockMiner(t, miner) + bm.MineBlocks(ctx, blocktime) + t.Cleanup(bm.Stop) + + // Get the full node's wallet address + fullAddr, err := full.WalletDefaultAddress(ctx) + if err != nil { + t.Fatal(err) + } + + // Create mock CLI + return full, fullAddr +} + +func StartTwoNodesOneMiner(ctx context.Context, t *testing.T, blocktime time.Duration) ([]TestFullNode, []address.Address) { + n, sn := RPCMockMinerBuilder(t, TwoFull, OneMiner) + + fullNode1 := n[0] + fullNode2 := n[1] + miner := sn[0] + + // Get everyone connected + addrs, err := fullNode1.NetAddrsListen(ctx) + if err != nil { + t.Fatal(err) + } + + if err := fullNode2.NetConnect(ctx, addrs); err != nil { + t.Fatal(err) + } + + if err := miner.NetConnect(ctx, addrs); err != nil { + t.Fatal(err) + } + + // Start mining blocks + bm := NewBlockMiner(t, miner) + bm.MineBlocks(ctx, blocktime) + t.Cleanup(bm.Stop) + + // Send some funds to register the second node + fullNodeAddr2, err := fullNode2.WalletNew(ctx, types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + SendFunds(ctx, t, fullNode1, fullNodeAddr2, abi.NewTokenAmount(1e18)) + + // Get the first node's address + fullNodeAddr1, err := fullNode1.WalletDefaultAddress(ctx) + if err != nil { + t.Fatal(err) + } + + // Create mock CLI + return n, []address.Address{fullNodeAddr1, fullNodeAddr2} +} diff --git a/itests/kit/node_builder.go b/itests/kit/node_builder.go new file mode 100644 index 000000000..3780a7669 --- /dev/null +++ b/itests/kit/node_builder.go @@ -0,0 +1,658 @@ +package kit + +import ( + "bytes" + "context" + "crypto/rand" + "io/ioutil" + "net/http" + "net/http/httptest" + "sync" + "testing" + "time" + + "github.com/filecoin-project/go-state-types/network" + + "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/exitcode" + "github.com/filecoin-project/go-storedcounter" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/api/client" + "github.com/filecoin-project/lotus/api/v1api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/gen" + genesis2 "github.com/filecoin-project/lotus/chain/gen/genesis" + "github.com/filecoin-project/lotus/chain/messagepool" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/wallet" + "github.com/filecoin-project/lotus/cmd/lotus-seed/seed" + sectorstorage "github.com/filecoin-project/lotus/extern/sector-storage" + "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" + "github.com/filecoin-project/lotus/extern/sector-storage/mock" + "github.com/filecoin-project/lotus/genesis" + lotusminer "github.com/filecoin-project/lotus/miner" + "github.com/filecoin-project/lotus/node" + "github.com/filecoin-project/lotus/node/modules" + "github.com/filecoin-project/lotus/node/modules/dtypes" + testing2 "github.com/filecoin-project/lotus/node/modules/testing" + "github.com/filecoin-project/lotus/node/repo" + "github.com/filecoin-project/lotus/storage/mockstorage" + miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" + power2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/power" + "github.com/ipfs/go-datastore" + "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/peer" + mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr/net" + "github.com/stretchr/testify/require" +) + +func init() { + chain.BootstrapPeerThreshold = 1 + messagepool.HeadChangeCoalesceMinDelay = time.Microsecond + messagepool.HeadChangeCoalesceMaxDelay = 2 * time.Microsecond + messagepool.HeadChangeCoalesceMergeInterval = 100 * time.Nanosecond +} + +func CreateTestStorageNode(ctx context.Context, t *testing.T, waddr address.Address, act address.Address, pk crypto.PrivKey, tnd TestFullNode, mn mocknet.Mocknet, opts node.Option) TestMiner { + r := repo.NewMemory(nil) + + lr, err := r.Lock(repo.StorageMiner) + require.NoError(t, err) + + ks, err := lr.KeyStore() + require.NoError(t, err) + + kbytes, err := pk.Bytes() + require.NoError(t, err) + + err = ks.Put("libp2p-host", types.KeyInfo{ + Type: "libp2p-host", + PrivateKey: kbytes, + }) + require.NoError(t, err) + + ds, err := lr.Datastore(context.TODO(), "/metadata") + require.NoError(t, err) + err = ds.Put(datastore.NewKey("miner-address"), act.Bytes()) + require.NoError(t, err) + + nic := storedcounter.New(ds, datastore.NewKey(modules.StorageCounterDSPrefix)) + for i := 0; i < GenesisPreseals; i++ { + _, err := nic.Next() + require.NoError(t, err) + } + _, err = nic.Next() + require.NoError(t, err) + + err = lr.Close() + require.NoError(t, err) + + peerid, err := peer.IDFromPrivateKey(pk) + require.NoError(t, err) + + enc, err := actors.SerializeParams(&miner2.ChangePeerIDParams{NewID: abi.PeerID(peerid)}) + require.NoError(t, err) + + msg := &types.Message{ + To: act, + From: waddr, + Method: miner.Methods.ChangePeerID, + Params: enc, + Value: types.NewInt(0), + } + + _, err = tnd.MpoolPushMessage(ctx, msg, nil) + require.NoError(t, err) + + // start node + var minerapi api.StorageMiner + + mineBlock := make(chan lotusminer.MineReq) + stop, err := node.New(ctx, + node.StorageMiner(&minerapi), + node.Online(), + node.Repo(r), + node.Test(), + + node.MockHost(mn), + + node.Override(new(v1api.FullNode), tnd), + node.Override(new(*lotusminer.Miner), lotusminer.NewTestMiner(mineBlock, act)), + + opts, + ) + if err != nil { + t.Fatalf("failed to construct node: %v", err) + } + + t.Cleanup(func() { _ = stop(context.Background()) }) + + /*// Bootstrap with full node + remoteAddrs, err := tnd.NetAddrsListen(Ctx) + require.NoError(t, err) + + err = minerapi.NetConnect(Ctx, remoteAddrs) + require.NoError(t, err)*/ + mineOne := func(ctx context.Context, req lotusminer.MineReq) error { + select { + case mineBlock <- req: + return nil + case <-ctx.Done(): + return ctx.Err() + } + } + + return TestMiner{StorageMiner: minerapi, MineOne: mineOne, Stop: stop} +} + +func storageBuilder(parentNode TestFullNode, mn mocknet.Mocknet, opts node.Option) MinerBuilder { + return func(ctx context.Context, t *testing.T, spt abi.RegisteredSealProof, owner address.Address) TestMiner { + pk, _, err := crypto.GenerateEd25519Key(rand.Reader) + require.NoError(t, err) + + minerPid, err := peer.IDFromPrivateKey(pk) + require.NoError(t, err) + + params, serr := actors.SerializeParams(&power2.CreateMinerParams{ + Owner: owner, + Worker: owner, + SealProofType: spt, + Peer: abi.PeerID(minerPid), + }) + require.NoError(t, serr) + + createStorageMinerMsg := &types.Message{ + To: power.Address, + From: owner, + Value: big.Zero(), + + Method: power.Methods.CreateMiner, + Params: params, + + GasLimit: 0, + GasPremium: big.NewInt(5252), + } + + signed, err := parentNode.MpoolPushMessage(ctx, createStorageMinerMsg, nil) + require.NoError(t, err) + + mw, err := parentNode.StateWaitMsg(ctx, signed.Cid(), build.MessageConfidence, api.LookbackNoLimit, true) + require.NoError(t, err) + require.Equal(t, exitcode.Ok, mw.Receipt.ExitCode) + + var retval power2.CreateMinerReturn + err = retval.UnmarshalCBOR(bytes.NewReader(mw.Receipt.Return)) + require.NoError(t, err) + + return CreateTestStorageNode(ctx, t, owner, retval.IDAddress, pk, parentNode, mn, opts) + } +} + +func Builder(t *testing.T, fullOpts []FullNodeOpts, storage []StorageMiner) ([]TestFullNode, []TestMiner) { + return mockBuilderOpts(t, fullOpts, storage, false) +} + +func RPCBuilder(t *testing.T, fullOpts []FullNodeOpts, storage []StorageMiner) ([]TestFullNode, []TestMiner) { + return mockBuilderOpts(t, fullOpts, storage, true) +} + +func MockMinerBuilder(t *testing.T, fullOpts []FullNodeOpts, storage []StorageMiner) ([]TestFullNode, []TestMiner) { + return mockMinerBuilderOpts(t, fullOpts, storage, false) +} + +func RPCMockMinerBuilder(t *testing.T, fullOpts []FullNodeOpts, storage []StorageMiner) ([]TestFullNode, []TestMiner) { + return mockMinerBuilderOpts(t, fullOpts, storage, true) +} + +func mockBuilderOpts(t *testing.T, fullOpts []FullNodeOpts, storage []StorageMiner, rpc bool) ([]TestFullNode, []TestMiner) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + mn := mocknet.New(ctx) + + fulls := make([]TestFullNode, len(fullOpts)) + miners := make([]TestMiner, len(storage)) + + // ***** + pk, _, err := crypto.GenerateEd25519Key(rand.Reader) + require.NoError(t, err) + + minerPid, err := peer.IDFromPrivateKey(pk) + require.NoError(t, err) + + var genbuf bytes.Buffer + + if len(storage) > 1 { + panic("need more peer IDs") + } + // ***** + + // PRESEAL SECTION, TRY TO REPLACE WITH BETTER IN THE FUTURE + // TODO: would be great if there was a better way to fake the preseals + + var ( + genms []genesis.Miner + maddrs []address.Address + genaccs []genesis.Actor + keys []*wallet.Key + ) + + var presealDirs []string + for i := 0; i < len(storage); i++ { + maddr, err := address.NewIDAddress(genesis2.MinerStart + uint64(i)) + if err != nil { + t.Fatal(err) + } + tdir, err := ioutil.TempDir("", "preseal-memgen") + if err != nil { + t.Fatal(err) + } + genm, k, err := seed.PreSeal(maddr, abi.RegisteredSealProof_StackedDrg2KiBV1, 0, GenesisPreseals, tdir, []byte("make genesis mem random"), nil, true) + if err != nil { + t.Fatal(err) + } + genm.PeerId = minerPid + + wk, err := wallet.NewKey(*k) + if err != nil { + return nil, nil + } + + genaccs = append(genaccs, genesis.Actor{ + Type: genesis.TAccount, + Balance: big.Mul(big.NewInt(400000000), types.NewInt(build.FilecoinPrecision)), + Meta: (&genesis.AccountMeta{Owner: wk.Address}).ActorMeta(), + }) + + keys = append(keys, wk) + presealDirs = append(presealDirs, tdir) + maddrs = append(maddrs, maddr) + genms = append(genms, *genm) + } + + rkhKey, err := wallet.GenerateKey(types.KTSecp256k1) + if err != nil { + return nil, nil + } + + vrk := genesis.Actor{ + Type: genesis.TAccount, + Balance: big.Mul(big.Div(big.NewInt(int64(build.FilBase)), big.NewInt(100)), big.NewInt(int64(build.FilecoinPrecision))), + Meta: (&genesis.AccountMeta{Owner: rkhKey.Address}).ActorMeta(), + } + keys = append(keys, rkhKey) + + templ := &genesis.Template{ + NetworkVersion: network.Version0, + Accounts: genaccs, + Miners: genms, + NetworkName: "test", + Timestamp: uint64(time.Now().Unix() - 10000), // some time sufficiently far in the past + VerifregRootKey: vrk, + RemainderAccount: gen.DefaultRemainderAccountActor, + } + + // END PRESEAL SECTION + + for i := 0; i < len(fullOpts); i++ { + var genesis node.Option + if i == 0 { + genesis = node.Override(new(modules.Genesis), testing2.MakeGenesisMem(&genbuf, *templ)) + } else { + genesis = node.Override(new(modules.Genesis), modules.LoadGenesis(genbuf.Bytes())) + } + + stop, err := node.New(ctx, + node.FullAPI(&fulls[i].FullNode, node.Lite(fullOpts[i].Lite)), + node.Online(), + node.Repo(repo.NewMemory(nil)), + node.MockHost(mn), + node.Test(), + + genesis, + + fullOpts[i].Opts(fulls), + ) + + if err != nil { + t.Fatal(err) + } + + t.Cleanup(func() { _ = stop(context.Background()) }) + + if rpc { + fulls[i] = fullRpc(t, fulls[i]) + } + + fulls[i].Stb = storageBuilder(fulls[i], mn, node.Options()) + } + + if _, err := fulls[0].FullNode.WalletImport(ctx, &rkhKey.KeyInfo); err != nil { + t.Fatal(err) + } + + for i, def := range storage { + // TODO: support non-bootstrap miners + if i != 0 { + t.Fatal("only one storage node supported") + } + if def.Full != 0 { + t.Fatal("storage nodes only supported on the first full node") + } + + f := fulls[def.Full] + if _, err := f.FullNode.WalletImport(ctx, &keys[i].KeyInfo); err != nil { + t.Fatal(err) + } + if err := f.FullNode.WalletSetDefault(ctx, keys[i].Address); err != nil { + t.Fatal(err) + } + + genMiner := maddrs[i] + wa := genms[i].Worker + + opts := def.Opts + if opts == nil { + opts = node.Options() + } + miners[i] = CreateTestStorageNode(ctx, t, wa, genMiner, pk, f, mn, opts) + if err := miners[i].StorageAddLocal(ctx, presealDirs[i]); err != nil { + t.Fatalf("%+v", err) + } + /* + sma := miners[i].StorageMiner.(*impl.StorageMinerAPI) + + psd := presealDirs[i] + */ + if rpc { + miners[i] = storerRpc(t, miners[i]) + } + } + + if err := mn.LinkAll(); err != nil { + t.Fatal(err) + } + + if len(miners) > 0 { + // Mine 2 blocks to setup some CE stuff in some actors + var wait sync.Mutex + wait.Lock() + + bm := NewBlockMiner(t, miners[0]) + t.Cleanup(bm.Stop) + + bm.MineUntilBlock(ctx, fulls[0], func(epoch abi.ChainEpoch) { + wait.Unlock() + }) + + wait.Lock() + bm.MineUntilBlock(ctx, fulls[0], func(epoch abi.ChainEpoch) { + wait.Unlock() + }) + wait.Lock() + } + + return fulls, miners +} + +func mockMinerBuilderOpts(t *testing.T, fullOpts []FullNodeOpts, storage []StorageMiner, rpc bool) ([]TestFullNode, []TestMiner) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + mn := mocknet.New(ctx) + + fulls := make([]TestFullNode, len(fullOpts)) + miners := make([]TestMiner, len(storage)) + + var genbuf bytes.Buffer + + // PRESEAL SECTION, TRY TO REPLACE WITH BETTER IN THE FUTURE + // TODO: would be great if there was a better way to fake the preseals + + var ( + genms []genesis.Miner + genaccs []genesis.Actor + maddrs []address.Address + keys []*wallet.Key + pidKeys []crypto.PrivKey + ) + for i := 0; i < len(storage); i++ { + maddr, err := address.NewIDAddress(genesis2.MinerStart + uint64(i)) + if err != nil { + t.Fatal(err) + } + + preseals := storage[i].Preseal + if preseals == PresealGenesis { + preseals = GenesisPreseals + } + + genm, k, err := mockstorage.PreSeal(abi.RegisteredSealProof_StackedDrg2KiBV1, maddr, preseals) + if err != nil { + t.Fatal(err) + } + + pk, _, err := crypto.GenerateEd25519Key(rand.Reader) + require.NoError(t, err) + + minerPid, err := peer.IDFromPrivateKey(pk) + require.NoError(t, err) + + genm.PeerId = minerPid + + wk, err := wallet.NewKey(*k) + if err != nil { + return nil, nil + } + + genaccs = append(genaccs, genesis.Actor{ + Type: genesis.TAccount, + Balance: big.Mul(big.NewInt(400000000), types.NewInt(build.FilecoinPrecision)), + Meta: (&genesis.AccountMeta{Owner: wk.Address}).ActorMeta(), + }) + + keys = append(keys, wk) + pidKeys = append(pidKeys, pk) + maddrs = append(maddrs, maddr) + genms = append(genms, *genm) + } + + rkhKey, err := wallet.GenerateKey(types.KTSecp256k1) + if err != nil { + return nil, nil + } + + vrk := genesis.Actor{ + Type: genesis.TAccount, + Balance: big.Mul(big.Div(big.NewInt(int64(build.FilBase)), big.NewInt(100)), big.NewInt(int64(build.FilecoinPrecision))), + Meta: (&genesis.AccountMeta{Owner: rkhKey.Address}).ActorMeta(), + } + keys = append(keys, rkhKey) + + templ := &genesis.Template{ + NetworkVersion: network.Version0, + Accounts: genaccs, + Miners: genms, + NetworkName: "test", + Timestamp: uint64(time.Now().Unix()) - (build.BlockDelaySecs * 20000), + VerifregRootKey: vrk, + RemainderAccount: gen.DefaultRemainderAccountActor, + } + + // END PRESEAL SECTION + + for i := 0; i < len(fullOpts); i++ { + var genesis node.Option + if i == 0 { + genesis = node.Override(new(modules.Genesis), testing2.MakeGenesisMem(&genbuf, *templ)) + } else { + genesis = node.Override(new(modules.Genesis), modules.LoadGenesis(genbuf.Bytes())) + } + + stop, err := node.New(ctx, + node.FullAPI(&fulls[i].FullNode, node.Lite(fullOpts[i].Lite)), + node.Online(), + node.Repo(repo.NewMemory(nil)), + node.MockHost(mn), + node.Test(), + + node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), + node.Override(new(ffiwrapper.Prover), mock.MockProver), + + // so that we subscribe to pubsub topics immediately + node.Override(new(dtypes.Bootstrapper), dtypes.Bootstrapper(true)), + + genesis, + + fullOpts[i].Opts(fulls), + ) + if err != nil { + t.Fatalf("%+v", err) + } + + t.Cleanup(func() { _ = stop(context.Background()) }) + + if rpc { + fulls[i] = fullRpc(t, fulls[i]) + } + + fulls[i].Stb = storageBuilder(fulls[i], mn, node.Options( + node.Override(new(*mock.SectorMgr), func() (*mock.SectorMgr, error) { + return mock.NewMockSectorMgr(nil), nil + }), + + node.Override(new(sectorstorage.SectorManager), node.From(new(*mock.SectorMgr))), + node.Override(new(sectorstorage.Unsealer), node.From(new(*mock.SectorMgr))), + node.Override(new(sectorstorage.PieceProvider), node.From(new(*mock.SectorMgr))), + + node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), + node.Override(new(ffiwrapper.Prover), mock.MockProver), + node.Unset(new(*sectorstorage.Manager)), + )) + } + + if _, err := fulls[0].FullNode.WalletImport(ctx, &rkhKey.KeyInfo); err != nil { + t.Fatal(err) + } + + for i, def := range storage { + // TODO: support non-bootstrap miners + + minerID := abi.ActorID(genesis2.MinerStart + uint64(i)) + + if def.Full != 0 { + t.Fatal("storage nodes only supported on the first full node") + } + + f := fulls[def.Full] + if _, err := f.FullNode.WalletImport(ctx, &keys[i].KeyInfo); err != nil { + return nil, nil + } + if err := f.FullNode.WalletSetDefault(ctx, keys[i].Address); err != nil { + return nil, nil + } + + sectors := make([]abi.SectorID, len(genms[i].Sectors)) + for i, sector := range genms[i].Sectors { + sectors[i] = abi.SectorID{ + Miner: minerID, + Number: sector.SectorID, + } + } + + opts := def.Opts + if opts == nil { + opts = node.Options() + } + miners[i] = CreateTestStorageNode(ctx, t, genms[i].Worker, maddrs[i], pidKeys[i], f, mn, node.Options( + node.Override(new(*mock.SectorMgr), func() (*mock.SectorMgr, error) { + return mock.NewMockSectorMgr(sectors), nil + }), + + node.Override(new(sectorstorage.SectorManager), node.From(new(*mock.SectorMgr))), + node.Override(new(sectorstorage.Unsealer), node.From(new(*mock.SectorMgr))), + node.Override(new(sectorstorage.PieceProvider), node.From(new(*mock.SectorMgr))), + + node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), + node.Override(new(ffiwrapper.Prover), mock.MockProver), + node.Unset(new(*sectorstorage.Manager)), + opts, + )) + + if rpc { + miners[i] = storerRpc(t, miners[i]) + } + } + + if err := mn.LinkAll(); err != nil { + t.Fatal(err) + } + + bm := NewBlockMiner(t, miners[0]) + + if len(miners) > 0 { + // Mine 2 blocks to setup some CE stuff in some actors + var wait sync.Mutex + wait.Lock() + + bm.MineUntilBlock(ctx, fulls[0], func(abi.ChainEpoch) { + wait.Unlock() + }) + wait.Lock() + bm.MineUntilBlock(ctx, fulls[0], func(abi.ChainEpoch) { + wait.Unlock() + }) + wait.Lock() + } + + return fulls, miners +} + +func CreateRPCServer(t *testing.T, handler http.Handler) (*httptest.Server, multiaddr.Multiaddr) { + testServ := httptest.NewServer(handler) + t.Cleanup(testServ.Close) + t.Cleanup(testServ.CloseClientConnections) + + addr := testServ.Listener.Addr() + maddr, err := manet.FromNetAddr(addr) + require.NoError(t, err) + return testServ, maddr +} + +func fullRpc(t *testing.T, nd TestFullNode) TestFullNode { + handler, err := node.FullNodeHandler(nd.FullNode, false) + require.NoError(t, err) + + srv, maddr := CreateRPCServer(t, handler) + + var ret TestFullNode + cl, stop, err := client.NewFullNodeRPCV1(context.Background(), "ws://"+srv.Listener.Addr().String()+"/rpc/v1", nil) + require.NoError(t, err) + t.Cleanup(stop) + ret.ListenAddr, ret.FullNode = maddr, cl + + return ret +} + +func storerRpc(t *testing.T, nd TestMiner) TestMiner { + handler, err := node.MinerHandler(nd.StorageMiner, false) + require.NoError(t, err) + + srv, maddr := CreateRPCServer(t, handler) + + var ret TestMiner + cl, stop, err := client.NewStorageMinerRPCV0(context.Background(), "ws://"+srv.Listener.Addr().String()+"/rpc/v0", nil) + require.NoError(t, err) + t.Cleanup(stop) + + ret.ListenAddr, ret.StorageMiner, ret.MineOne = maddr, cl, nd.MineOne + return ret +} diff --git a/itests/kit/nodes.go b/itests/kit/nodes.go new file mode 100644 index 000000000..d9b04166a --- /dev/null +++ b/itests/kit/nodes.go @@ -0,0 +1,153 @@ +package kit + +import ( + "context" + "testing" + + "github.com/multiformats/go-multiaddr" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "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/chain/stmgr" + "github.com/filecoin-project/lotus/miner" + "github.com/filecoin-project/lotus/node" +) + +type MinerBuilder func(context.Context, *testing.T, abi.RegisteredSealProof, address.Address) TestMiner + +type TestFullNode struct { + v1api.FullNode + // ListenAddr is the address on which an API server is listening, if an + // API server is created for this Node + ListenAddr multiaddr.Multiaddr + + Stb MinerBuilder +} + +type TestMiner struct { + lapi.StorageMiner + // ListenAddr is the address on which an API server is listening, if an + // API server is created for this Node + ListenAddr multiaddr.Multiaddr + + MineOne func(context.Context, miner.MineReq) error + Stop func(context.Context) error +} + +var PresealGenesis = -1 + +const GenesisPreseals = 2 + +const TestSpt = abi.RegisteredSealProof_StackedDrg2KiBV1_1 + +// Options for setting up a mock storage Miner +type StorageMiner struct { + Full int + Opts node.Option + Preseal int +} + +type OptionGenerator func([]TestFullNode) node.Option + +// Options for setting up a mock full node +type FullNodeOpts struct { + Lite bool // run node in "lite" mode + Opts OptionGenerator // generate dependency injection options +} + +// APIBuilder is a function which is invoked in test suite to provide +// test nodes and networks +// +// fullOpts array defines options for each full node +// storage array defines storage nodes, numbers in the array specify full node +// index the storage node 'belongs' to +type APIBuilder func(t *testing.T, full []FullNodeOpts, storage []StorageMiner) ([]TestFullNode, []TestMiner) + +func DefaultFullOpts(nFull int) []FullNodeOpts { + full := make([]FullNodeOpts, nFull) + for i := range full { + full[i] = FullNodeOpts{ + Opts: func(nodes []TestFullNode) node.Option { + return node.Options() + }, + } + } + return full +} + +var OneMiner = []StorageMiner{{Full: 0, Preseal: PresealGenesis}} +var OneFull = DefaultFullOpts(1) +var TwoFull = DefaultFullOpts(2) + +var FullNodeWithLatestActorsAt = func(upgradeHeight abi.ChainEpoch) FullNodeOpts { + // Attention: Update this when introducing new actor versions or your tests will be sad + return FullNodeWithNetworkUpgradeAt(network.Version13, upgradeHeight) +} + +var FullNodeWithNetworkUpgradeAt = func(version network.Version, upgradeHeight abi.ChainEpoch) FullNodeOpts { + fullSchedule := stmgr.UpgradeSchedule{{ + // prepare for upgrade. + Network: network.Version9, + Height: 1, + Migration: stmgr.UpgradeActorsV2, + }, { + Network: network.Version10, + Height: 2, + Migration: stmgr.UpgradeActorsV3, + }, { + Network: network.Version12, + Height: 3, + Migration: stmgr.UpgradeActorsV4, + }, { + Network: network.Version13, + Height: 4, + Migration: stmgr.UpgradeActorsV5, + }} + + schedule := stmgr.UpgradeSchedule{} + for _, upgrade := range fullSchedule { + if upgrade.Network > version { + break + } + + schedule = append(schedule, upgrade) + } + + if upgradeHeight > 0 { + schedule[len(schedule)-1].Height = upgradeHeight + } + + return FullNodeOpts{ + Opts: func(nodes []TestFullNode) node.Option { + return node.Override(new(stmgr.UpgradeSchedule), schedule) + }, + } +} + +var FullNodeWithSDRAt = func(calico, persian abi.ChainEpoch) FullNodeOpts { + return FullNodeOpts{ + Opts: func(nodes []TestFullNode) node.Option { + return node.Override(new(stmgr.UpgradeSchedule), stmgr.UpgradeSchedule{{ + Network: network.Version6, + Height: 1, + Migration: stmgr.UpgradeActorsV2, + }, { + Network: network.Version7, + Height: calico, + Migration: stmgr.UpgradeCalico, + }, { + Network: network.Version8, + Height: persian, + }}) + }, + } +} + +var MineNext = miner.MineReq{ + InjectNulls: 0, + Done: func(bool, abi.ChainEpoch, error) {}, +} diff --git a/itests/kit/pledge.go b/itests/kit/pledge.go new file mode 100644 index 000000000..254f87bac --- /dev/null +++ b/itests/kit/pledge.go @@ -0,0 +1,88 @@ +package kit + +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 TestMiner, n, existing int, blockNotif <-chan struct{}) { //nolint:golint + toCheck := StartPledge(t, ctx, miner, n, existing, blockNotif) + + for len(toCheck) > 0 { + flushSealingBatches(t, ctx, miner) + + states := map[api.SectorState]int{} + for n := range toCheck { + st, err := miner.SectorsStatus(ctx, n, false) + require.NoError(t, err) + states[st.State]++ + 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 %+v\n", len(toCheck), states) + } +} + +func flushSealingBatches(t *testing.T, ctx context.Context, miner TestMiner) { //nolint:golint + pcb, err := miner.SectorPreCommitFlush(ctx) + require.NoError(t, err) + if pcb != nil { + fmt.Printf("PRECOMMIT BATCH: %+v\n", pcb) + } + + cb, err := miner.SectorCommitFlush(ctx) + require.NoError(t, err) + if cb != nil { + fmt.Printf("COMMIT BATCH: %+v\n", cb) + } +} + +func StartPledge(t *testing.T, ctx context.Context, miner TestMiner, n, existing int, blockNotif <-chan struct{}) map[abi.SectorNumber]struct{} { //nolint:golint + 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{}{} + } + + return toCheck +} diff --git a/itests/kit2/blockminer.go b/itests/kit2/blockminer.go new file mode 100644 index 000000000..04d425dd6 --- /dev/null +++ b/itests/kit2/blockminer.go @@ -0,0 +1,124 @@ +package kit2 + +import ( + "context" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/miner" + "github.com/stretchr/testify/require" +) + +// BlockMiner is a utility that makes a test miner Mine blocks on a timer. +type BlockMiner struct { + t *testing.T + miner *TestMiner + + nextNulls int64 + wg sync.WaitGroup + cancel context.CancelFunc +} + +func NewBlockMiner(t *testing.T, miner *TestMiner) *BlockMiner { + return &BlockMiner{ + t: t, + miner: miner, + cancel: func() {}, + } +} + +func (bm *BlockMiner) MineBlocks(ctx context.Context, blocktime time.Duration) { + time.Sleep(time.Second) + + // wrap context in a cancellable context. + ctx, bm.cancel = context.WithCancel(ctx) + + bm.wg.Add(1) + go func() { + defer bm.wg.Done() + + for { + select { + case <-time.After(blocktime): + case <-ctx.Done(): + return + } + + nulls := atomic.SwapInt64(&bm.nextNulls, 0) + err := bm.miner.MineOne(ctx, miner.MineReq{ + InjectNulls: abi.ChainEpoch(nulls), + Done: func(bool, abi.ChainEpoch, error) {}, + }) + switch { + case err == nil: // wrap around + case ctx.Err() != nil: // context fired. + return + default: // log error + bm.t.Error(err) + } + } + }() +} + +// InjectNulls injects the specified amount of null rounds in the next +// mining rounds. +func (bm *BlockMiner) InjectNulls(rounds abi.ChainEpoch) { + atomic.AddInt64(&bm.nextNulls, int64(rounds)) +} + +func (bm *BlockMiner) MineUntilBlock(ctx context.Context, fn *TestFullNode, cb func(abi.ChainEpoch)) { + for i := 0; i < 1000; i++ { + var ( + success bool + err error + epoch abi.ChainEpoch + wait = make(chan struct{}) + ) + + doneFn := func(win bool, ep abi.ChainEpoch, e error) { + success = win + err = e + epoch = ep + wait <- struct{}{} + } + + mineErr := bm.miner.MineOne(ctx, miner.MineReq{Done: doneFn}) + require.NoError(bm.t, mineErr) + <-wait + + require.NoError(bm.t, err) + + if success { + // Wait until it shows up on the given full nodes ChainHead + nloops := 200 + for i := 0; i < nloops; i++ { + ts, err := fn.ChainHead(ctx) + require.NoError(bm.t, err) + + if ts.Height() == epoch { + break + } + + require.NotEqual(bm.t, i, nloops-1, "block never managed to sync to node") + time.Sleep(time.Millisecond * 10) + } + + if cb != nil { + cb(epoch) + } + return + } + bm.t.Log("did not Mine block, trying again", i) + } + bm.t.Fatal("failed to Mine 1000 times in a row...") +} + +// Stop stops the block miner. +func (bm *BlockMiner) Stop() { + bm.t.Log("shutting down mining") + bm.cancel() + bm.wg.Wait() +} diff --git a/itests/kit2/client.go b/itests/kit2/client.go new file mode 100644 index 000000000..2777d8d25 --- /dev/null +++ b/itests/kit2/client.go @@ -0,0 +1,107 @@ +package kit2 + +import ( + "context" + "fmt" + "path/filepath" + "regexp" + "strings" + "testing" + "time" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/specs-actors/v2/actors/builtin" + "github.com/stretchr/testify/require" + lcli "github.com/urfave/cli/v2" +) + +// RunClientTest exercises some of the Client CLI commands +func RunClientTest(t *testing.T, cmds []*lcli.Command, clientNode TestFullNode) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // Create mock CLI + mockCLI := NewMockCLI(ctx, t, cmds) + clientCLI := mockCLI.Client(clientNode.ListenAddr) + + // Get the Miner address + addrs, err := clientNode.StateListMiners(ctx, types.EmptyTSK) + require.NoError(t, err) + require.Len(t, addrs, 1) + + minerAddr := addrs[0] + fmt.Println("Miner:", minerAddr) + + // client query-ask + out := clientCLI.RunCmd("client", "query-ask", minerAddr.String()) + require.Regexp(t, regexp.MustCompile("Ask:"), out) + + // Create a deal (non-interactive) + // client deal --start-epoch= 1000000attofil + res, _ := clientNode.CreateImportFile(ctx, 1, 0) + + require.NoError(t, err) + startEpoch := fmt.Sprintf("--start-epoch=%d", 2<<12) + dataCid := res.Root + price := "1000000attofil" + duration := fmt.Sprintf("%d", build.MinDealDuration) + out = clientCLI.RunCmd("client", "deal", startEpoch, dataCid.String(), minerAddr.String(), price, duration) + fmt.Println("client deal", out) + + // Create a deal (interactive) + // client deal + // + // (in days) + // + // "no" (verified Client) + // "yes" (confirm deal) + res, _ = clientNode.CreateImportFile(ctx, 2, 0) + require.NoError(t, err) + dataCid2 := res.Root + duration = fmt.Sprintf("%d", build.MinDealDuration/builtin.EpochsInDay) + cmd := []string{"client", "deal"} + interactiveCmds := []string{ + dataCid2.String(), + duration, + minerAddr.String(), + "no", + "yes", + } + out = clientCLI.RunInteractiveCmd(cmd, interactiveCmds) + fmt.Println("client deal:\n", out) + + // Wait for provider to start sealing deal + dealStatus := "" + for { + // client list-deals + out = clientCLI.RunCmd("client", "list-deals") + fmt.Println("list-deals:\n", out) + + lines := strings.Split(out, "\n") + require.GreaterOrEqual(t, len(lines), 2) + re := regexp.MustCompile(`\s+`) + parts := re.Split(lines[1], -1) + if len(parts) < 4 { + require.Fail(t, "bad list-deals output format") + } + dealStatus = parts[3] + fmt.Println(" Deal status:", dealStatus) + + st := CategorizeDealState(dealStatus) + require.NotEqual(t, TestDealStateFailed, st) + if st == TestDealStateComplete { + break + } + + time.Sleep(time.Second) + } + + // Retrieve the first file from the Miner + // client retrieve + tmpdir := t.TempDir() + path := filepath.Join(tmpdir, "outfile.dat") + out = clientCLI.RunCmd("client", "retrieve", dataCid.String(), path) + fmt.Println("retrieve:\n", out) + require.Regexp(t, regexp.MustCompile("Success"), out) +} diff --git a/itests/kit2/deals.go b/itests/kit2/deals.go new file mode 100644 index 000000000..e2dc00d52 --- /dev/null +++ b/itests/kit2/deals.go @@ -0,0 +1,245 @@ +package kit2 + +import ( + "bytes" + "context" + "io/ioutil" + "testing" + "time" + + "github.com/ipfs/go-cid" + files "github.com/ipfs/go-ipfs-files" + "github.com/ipld/go-car" + "github.com/stretchr/testify/require" + + "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/build" + "github.com/filecoin-project/lotus/chain/types" + sealing "github.com/filecoin-project/lotus/extern/storage-sealing" + 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" +) + +type DealHarness struct { + t *testing.T + client *TestFullNode + miner *TestMiner +} + +// NewDealHarness creates a test harness that contains testing utilities for deals. +func NewDealHarness(t *testing.T, client *TestFullNode, miner *TestMiner) *DealHarness { + return &DealHarness{ + t: t, + client: client, + miner: miner, + } +} + +// MakeOnlineDeal makes an online deal, generating a random file with the +// supplied seed, and setting the specified fast retrieval flag and start epoch +// on the storage deal. It returns when the deal is sealed. +// +// TODO: convert input parameters to struct, and add size as an input param. +func (dh *DealHarness) MakeOnlineDeal(ctx context.Context, rseed int, fastRet bool, startEpoch abi.ChainEpoch) (deal *cid.Cid, res *api.ImportRes, path string) { + res, path = dh.client.CreateImportFile(ctx, rseed, 0) + + dh.t.Logf("FILE CID: %s", res.Root) + + deal = dh.StartDeal(ctx, res.Root, fastRet, 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) + dh.WaitDealSealed(ctx, deal, false, false, nil) + + return deal, res, path +} + +// StartDeal starts a storage deal between the client and the miner. +func (dh *DealHarness) StartDeal(ctx context.Context, fcid cid.Cid, fastRet bool, startEpoch abi.ChainEpoch) *cid.Cid { + maddr, err := dh.miner.ActorAddress(ctx) + require.NoError(dh.t, err) + + addr, err := dh.client.WalletDefaultAddress(ctx) + require.NoError(dh.t, err) + + deal, err := dh.client.ClientStartDeal(ctx, &api.StartDealParams{ + Data: &storagemarket.DataRef{ + TransferType: storagemarket.TTGraphsync, + Root: fcid, + }, + Wallet: addr, + Miner: maddr, + EpochPrice: types.NewInt(1000000), + DealStartEpoch: startEpoch, + MinBlocksDuration: uint64(build.MinDealDuration), + FastRetrieval: fastRet, + }) + require.NoError(dh.t, err) + + return deal +} + +// WaitDealSealed waits until the deal is sealed. +func (dh *DealHarness) WaitDealSealed(ctx context.Context, deal *cid.Cid, noseal, noSealStart bool, cb func()) { +loop: + for { + di, err := dh.client.ClientGetDealInfo(ctx, *deal) + require.NoError(dh.t, err) + + switch di.State { + case storagemarket.StorageDealAwaitingPreCommit, storagemarket.StorageDealSealing: + if noseal { + return + } + if !noSealStart { + dh.StartSealingWaiting(ctx) + } + case storagemarket.StorageDealProposalRejected: + dh.t.Fatal("deal rejected") + case storagemarket.StorageDealFailing: + dh.t.Fatal("deal failed") + case storagemarket.StorageDealError: + dh.t.Fatal("deal errored", di.Message) + case storagemarket.StorageDealActive: + dh.t.Log("COMPLETE", di) + break loop + } + + mds, err := dh.miner.MarketListIncompleteDeals(ctx) + require.NoError(dh.t, err) + + var minerState storagemarket.StorageDealStatus + for _, md := range mds { + if md.DealID == di.DealID { + minerState = md.State + break + } + } + + dh.t.Logf("Deal %d state: client:%s provider:%s\n", di.DealID, storagemarket.DealStates[di.State], storagemarket.DealStates[minerState]) + time.Sleep(time.Second / 2) + if cb != nil { + cb() + } + } +} + +// WaitDealSealed waits until the deal is published. +func (dh *DealHarness) WaitDealPublished(ctx context.Context, deal *cid.Cid) { + subCtx, cancel := context.WithCancel(ctx) + defer cancel() + + updates, err := dh.miner.MarketGetDealUpdates(subCtx) + require.NoError(dh.t, err) + + for { + select { + case <-ctx.Done(): + dh.t.Fatal("context timeout") + case di := <-updates: + if deal.Equals(di.ProposalCid) { + switch di.State { + case storagemarket.StorageDealProposalRejected: + dh.t.Fatal("deal rejected") + case storagemarket.StorageDealFailing: + dh.t.Fatal("deal failed") + case storagemarket.StorageDealError: + dh.t.Fatal("deal errored", di.Message) + case storagemarket.StorageDealFinalizing, storagemarket.StorageDealAwaitingPreCommit, storagemarket.StorageDealSealing, storagemarket.StorageDealActive: + dh.t.Log("COMPLETE", di) + return + } + dh.t.Log("Deal state: ", storagemarket.DealStates[di.State]) + } + } + } +} + +func (dh *DealHarness) StartSealingWaiting(ctx context.Context) { + snums, err := dh.miner.SectorsList(ctx) + require.NoError(dh.t, err) + + for _, snum := range snums { + si, err := dh.miner.SectorsStatus(ctx, snum, false) + require.NoError(dh.t, err) + + dh.t.Logf("Sector state: %s", si.State) + if si.State == api.SectorState(sealing.WaitDeals) { + require.NoError(dh.t, dh.miner.SectorStartSealing(ctx, snum)) + } + + dh.miner.FlushSealingBatches(ctx) + } +} + +func (dh *DealHarness) PerformRetrieval(ctx context.Context, deal *cid.Cid, root cid.Cid, carExport bool) (path string) { + // perform retrieval. + info, err := dh.client.ClientGetDealInfo(ctx, *deal) + require.NoError(dh.t, err) + + offers, err := dh.client.ClientFindData(ctx, root, &info.PieceCID) + require.NoError(dh.t, err) + require.NotEmpty(dh.t, offers, "no offers") + + tmpfile, err := ioutil.TempFile(dh.t.TempDir(), "ret-car") + require.NoError(dh.t, err) + + defer tmpfile.Close() + + caddr, err := dh.client.WalletDefaultAddress(ctx) + require.NoError(dh.t, err) + + ref := &api.FileRef{ + Path: tmpfile.Name(), + IsCAR: carExport, + } + + updates, err := dh.client.ClientRetrieveWithEvents(ctx, offers[0].Order(caddr), ref) + require.NoError(dh.t, err) + + for update := range updates { + require.Emptyf(dh.t, update.Err, "retrieval failed: %s", update.Err) + } + + rdata, err := ioutil.ReadFile(tmpfile.Name()) + require.NoError(dh.t, err) + + if carExport { + rdata = dh.ExtractFileFromCAR(ctx, rdata) + } + + return tmpfile.Name() +} + +func (dh *DealHarness) ExtractFileFromCAR(ctx context.Context, rdata []byte) []byte { + bserv := dstest.Bserv() + ch, err := car.LoadCar(bserv.Blockstore(), bytes.NewReader(rdata)) + require.NoError(dh.t, err) + + b, err := bserv.GetBlock(ctx, ch.Roots[0]) + require.NoError(dh.t, err) + + nd, err := ipld.Decode(b) + require.NoError(dh.t, err) + + dserv := dag.NewDAGService(bserv) + fil, err := unixfile.NewUnixfsFile(ctx, dserv, nd) + require.NoError(dh.t, err) + + tmpfile, err := ioutil.TempFile(dh.t.TempDir(), "file-in-car") + require.NoError(dh.t, err) + + defer tmpfile.Close() + + err = files.WriteTo(fil, tmpfile.Name()) + require.NoError(dh.t, err) + + rdata, err = ioutil.ReadFile(tmpfile.Name()) + require.NoError(dh.t, err) + + return rdata +} diff --git a/itests/kit2/deals_state.go b/itests/kit2/deals_state.go new file mode 100644 index 000000000..be3a9e4db --- /dev/null +++ b/itests/kit2/deals_state.go @@ -0,0 +1,21 @@ +package kit2 + +type TestDealState int + +const ( + TestDealStateFailed = TestDealState(-1) + TestDealStateInProgress = TestDealState(0) + TestDealStateComplete = TestDealState(1) +) + +// CategorizeDealState categorizes deal states into one of three states: +// Complete, InProgress, Failed. +func CategorizeDealState(dealStatus string) TestDealState { + switch dealStatus { + case "StorageDealFailing", "StorageDealError": + return TestDealStateFailed + case "StorageDealStaged", "StorageDealAwaitingPreCommit", "StorageDealSealing", "StorageDealActive", "StorageDealExpired", "StorageDealSlashed": + return TestDealStateComplete + } + return TestDealStateInProgress +} diff --git a/itests/kit/ensemble.go b/itests/kit2/ensemble.go similarity index 75% rename from itests/kit/ensemble.go rename to itests/kit2/ensemble.go index cfc95e968..5d12c83e1 100644 --- a/itests/kit/ensemble.go +++ b/itests/kit2/ensemble.go @@ -1,12 +1,10 @@ -package kit +package kit2 import ( "bytes" "context" "crypto/rand" "io/ioutil" - "net/http" - "net/http/httptest" "sync" "testing" "time" @@ -15,10 +13,8 @@ import ( "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" "github.com/filecoin-project/go-storedcounter" "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/api/client" "github.com/filecoin-project/lotus/api/v1api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain" @@ -28,7 +24,6 @@ import ( "github.com/filecoin-project/lotus/chain/gen" genesis2 "github.com/filecoin-project/lotus/chain/gen/genesis" "github.com/filecoin-project/lotus/chain/messagepool" - "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/wallet" "github.com/filecoin-project/lotus/cmd/lotus-seed/seed" @@ -49,8 +44,6 @@ import ( libp2pcrypto "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/peer" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" - "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr/net" "github.com/stretchr/testify/require" ) @@ -61,33 +54,51 @@ func init() { messagepool.HeadChangeCoalesceMergeInterval = 100 * time.Nanosecond } -type BuilderOpt func(opts *BuilderOpts) error - -type BuilderOpts struct { - pastOffset time.Duration - spt abi.RegisteredSealProof -} - -var DefaultBuilderOpts = BuilderOpts{ - pastOffset: 10000 * time.Second, - spt: abi.RegisteredSealProof_StackedDrg2KiBV1, -} - -func ProofType(proofType abi.RegisteredSealProof) BuilderOpt { - return func(opts *BuilderOpts) error { - opts.spt = proofType - return nil - } -} - -// Ensemble is a collection of nodes instantiated within a test. Ensemble -// supports building full nodes and miners. +// Ensemble is a collection of nodes instantiated within a test. +// +// Create a new ensemble with: +// +// ens := kit.NewEnsemble() +// +// Create full nodes and miners: +// +// var full TestFullNode +// var miner TestMiner +// ens.FullNode(&full, opts...) // populates a full node +// ens.Miner(&miner, &full, opts...) // populates a miner, using the full node as its chain daemon +// +// It is possible to pass functional options to set initial balances, +// presealed sectors, owner keys, etc. +// +// After the initial nodes are added, call `ens.Start()` to forge genesis +// and start the network. Mining will NOT be started automatically. It needs +// to be started explicitly by calling `BeginMining`. +// +// Nodes also need to be connected with one another, either via `ens.Connect()` +// or `ens.InterconnectAll()`. A common inchantation for simple tests is to do: +// +// ens.InterconnectAll().BeginMining(blocktime) +// +// You can continue to add more nodes, but you must always follow with +// `ens.Start()` to activate the new nodes. +// +// The API is chainable, so it's possible to do a lot in a very succinct way: +// +// kit.NewEnsemble().FullNode(&full).Miner(&miner, &full).Start().InterconnectAll().BeginMining() +// +// You can also find convenient fullnode:miner presets, such as 1:1, 1:2, +// and 2:1, e.g.: +// +// kit.EnsembleMinimal() +// kit.EnsembleOneTwo() +// kit.EnsembleTwoOne() +// type Ensemble struct { t *testing.T bootstrapped bool genesisBlock bytes.Buffer mn mocknet.Mocknet - options *BuilderOpts + options *ensembleOpts inactive struct { fullnodes []*TestFullNode @@ -103,9 +114,10 @@ type Ensemble struct { } } -// NewEnsemble -func NewEnsemble(t *testing.T, opts ...BuilderOpt) *Ensemble { - options := DefaultBuilderOpts +// NewEnsemble instantiates a new blank Ensemble. This enables you to +// programmatically +func NewEnsemble(t *testing.T, opts ...EnsembleOpt) *Ensemble { + options := DefaultEnsembleOpts for _, o := range opts { err := o(&options) require.NoError(t, err) @@ -113,85 +125,6 @@ func NewEnsemble(t *testing.T, opts ...BuilderOpt) *Ensemble { return &Ensemble{t: t, options: &options} } -type NodeOpts struct { - balance abi.TokenAmount - lite bool - sectors int - mockProofs bool - rpc bool - ownerKey *wallet.Key - extraNodeOpts []node.Option -} - -const DefaultPresealsPerBootstrapMiner = 2 - -var DefaultNodeOpts = NodeOpts{ - balance: big.Mul(big.NewInt(100000000), types.NewInt(build.FilecoinPrecision)), - sectors: DefaultPresealsPerBootstrapMiner, -} - -type NodeOpt func(opts *NodeOpts) error - -// OwnerBalance specifies the balance to be attributed to a miner's owner account. -// -// Only used when creating a miner. -func OwnerBalance(balance abi.TokenAmount) NodeOpt { - return func(opts *NodeOpts) error { - opts.balance = balance - return nil - } -} - -// LiteNode specifies that this node will be a lite node. -// -// Only used when creating a fullnode. -func LiteNode() NodeOpt { - return func(opts *NodeOpts) error { - opts.lite = true - return nil - } -} - -// PresealSectors specifies the amount of preseal sectors to give to a miner -// at genesis. -// -// Only used when creating a miner. -func PresealSectors(sectors int) NodeOpt { - return func(opts *NodeOpts) error { - opts.sectors = sectors - return nil - } -} - -// MockProofs activates mock proofs for the entire ensemble. -func MockProofs() NodeOpt { - return func(opts *NodeOpts) error { - opts.mockProofs = true - return nil - } -} - -func ThroughRPC() NodeOpt { - return func(opts *NodeOpts) error { - opts.rpc = true - return nil - } -} - -func OwnerAddr(wk *wallet.Key) NodeOpt { - return func(opts *NodeOpts) error { - opts.ownerKey = wk - return nil - } -} - -func ExtraNodeOpts(extra ...node.Option) NodeOpt { - return func(opts *NodeOpts) error { - opts.extraNodeOpts = extra - return nil - } -} - // FullNode enrolls a new full node. func (n *Ensemble) FullNode(full *TestFullNode, opts ...NodeOpt) *Ensemble { options := DefaultNodeOpts @@ -256,7 +189,7 @@ func (n *Ensemble) Miner(miner *TestMiner, full *TestFullNode, opts ...NodeOpt) ) // create the preseal commitment. - if options.mockProofs { + if n.options.mockProofs { genm, k, err = mockstorage.PreSeal(abi.RegisteredSealProof_StackedDrg2KiBV1, actorAddr, sectors) } else { genm, k, err = seed.PreSeal(actorAddr, abi.RegisteredSealProof_StackedDrg2KiBV1, 0, sectors, tdir, []byte("make genesis mem random"), nil, true) @@ -339,7 +272,7 @@ func (n *Ensemble) Start() *Ensemble { } // Are we mocking proofs? - if full.options.mockProofs { + if n.options.mockProofs { opts = append(opts, node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), node.Override(new(ffiwrapper.Prover), mock.MockProver), @@ -389,7 +322,7 @@ func (n *Ensemble) Start() *Ensemble { params, aerr := actors.SerializeParams(&power2.CreateMinerParams{ Owner: m.OwnerKey.Address, Worker: m.OwnerKey.Address, - SealProofType: n.options.spt, + SealProofType: n.options.proofType, Peer: abi.PeerID(m.Libp2p.PeerID), }) require.NoError(n.t, aerr) @@ -512,7 +445,7 @@ func (n *Ensemble) Start() *Ensemble { } } - if m.options.mockProofs { + if n.options.mockProofs { opts = append(opts, node.Override(new(*mock.SectorMgr), func() (*mock.SectorMgr, error) { return mock.NewMockSectorMgr(presealSectors), nil @@ -532,7 +465,7 @@ func (n *Ensemble) Start() *Ensemble { require.NoError(n.t, err) // using real proofs, therefore need real sectors. - if !n.bootstrapped && !m.options.mockProofs { + if !n.bootstrapped && !n.options.mockProofs { err := m.StorageAddLocal(ctx, m.PresealDir) require.NoError(n.t, err) } @@ -667,99 +600,3 @@ func (n *Ensemble) generateGenesis() *genesis.Template { return templ } - -func CreateRPCServer(t *testing.T, handler http.Handler) (*httptest.Server, multiaddr.Multiaddr) { - testServ := httptest.NewServer(handler) - t.Cleanup(testServ.Close) - t.Cleanup(testServ.CloseClientConnections) - - addr := testServ.Listener.Addr() - maddr, err := manet.FromNetAddr(addr) - require.NoError(t, err) - return testServ, maddr -} - -func fullRpc(t *testing.T, f *TestFullNode) *TestFullNode { - handler, err := node.FullNodeHandler(f.FullNode, false) - require.NoError(t, err) - - srv, maddr := CreateRPCServer(t, handler) - - cl, stop, err := client.NewFullNodeRPCV1(context.Background(), "ws://"+srv.Listener.Addr().String()+"/rpc/v1", nil) - require.NoError(t, err) - t.Cleanup(stop) - f.ListenAddr, f.FullNode = maddr, cl - - return f -} - -func minerRpc(t *testing.T, m *TestMiner) *TestMiner { - handler, err := node.MinerHandler(m.StorageMiner, false) - require.NoError(t, err) - - srv, maddr := CreateRPCServer(t, handler) - - cl, stop, err := client.NewStorageMinerRPCV0(context.Background(), "ws://"+srv.Listener.Addr().String()+"/rpc/v0", nil) - require.NoError(t, err) - t.Cleanup(stop) - - m.ListenAddr, m.StorageMiner = maddr, cl - return m -} - -func LatestActorsAt(upgradeHeight abi.ChainEpoch) node.Option { - // Attention: Update this when introducing new actor versions or your tests will be sad - return NetworkUpgradeAt(network.Version13, upgradeHeight) -} - -func NetworkUpgradeAt(version network.Version, upgradeHeight abi.ChainEpoch) node.Option { - fullSchedule := stmgr.UpgradeSchedule{{ - // prepare for upgrade. - Network: network.Version9, - Height: 1, - Migration: stmgr.UpgradeActorsV2, - }, { - Network: network.Version10, - Height: 2, - Migration: stmgr.UpgradeActorsV3, - }, { - Network: network.Version12, - Height: 3, - Migration: stmgr.UpgradeActorsV4, - }, { - Network: network.Version13, - Height: 4, - Migration: stmgr.UpgradeActorsV5, - }} - - schedule := stmgr.UpgradeSchedule{} - for _, upgrade := range fullSchedule { - if upgrade.Network > version { - break - } - - schedule = append(schedule, upgrade) - } - - if upgradeHeight > 0 { - schedule[len(schedule)-1].Height = upgradeHeight - } - - return node.Override(new(stmgr.UpgradeSchedule), schedule) -} - -func SDRUpgradeAt(calico, persian abi.ChainEpoch) node.Option { - return node.Override(new(stmgr.UpgradeSchedule), stmgr.UpgradeSchedule{{ - Network: network.Version6, - Height: 1, - Migration: stmgr.UpgradeActorsV2, - }, { - Network: network.Version7, - Height: calico, - Migration: stmgr.UpgradeCalico, - }, { - Network: network.Version8, - Height: persian, - }}) - -} diff --git a/itests/kit2/ensemble_opts.go b/itests/kit2/ensemble_opts.go new file mode 100644 index 000000000..724113bdc --- /dev/null +++ b/itests/kit2/ensemble_opts.go @@ -0,0 +1,35 @@ +package kit2 + +import ( + "time" + + "github.com/filecoin-project/go-state-types/abi" +) + +type EnsembleOpt func(opts *ensembleOpts) error + +type ensembleOpts struct { + pastOffset time.Duration + proofType abi.RegisteredSealProof + mockProofs bool +} + +var DefaultEnsembleOpts = ensembleOpts{ + pastOffset: 10000 * time.Second, + proofType: abi.RegisteredSealProof_StackedDrg2KiBV1, +} + +func ProofType(proofType abi.RegisteredSealProof) EnsembleOpt { + return func(opts *ensembleOpts) error { + opts.proofType = proofType + return nil + } +} + +// MockProofs activates mock proofs for the entire ensemble. +func MockProofs() EnsembleOpt { + return func(opts *ensembleOpts) error { + opts.mockProofs = true + return nil + } +} diff --git a/itests/kit2/ensemble_presets.go b/itests/kit2/ensemble_presets.go new file mode 100644 index 000000000..28a4b5d92 --- /dev/null +++ b/itests/kit2/ensemble_presets.go @@ -0,0 +1,70 @@ +package kit2 + +import "testing" + +// EnsembleMinimal creates and starts an Ensemble with a single full node and a single miner. +// It does not interconnect nodes nor does it begin mining. +// +// This function supports passing both ensemble and node functional options. +// Functional options are applied to all nodes. +func EnsembleMinimal(t *testing.T, opts ...interface{}) (*TestFullNode, *TestMiner, *Ensemble) { + eopts, nopts := siftOptions(t, opts) + + var ( + full TestFullNode + miner TestMiner + ) + ens := NewEnsemble(t, eopts...).FullNode(&full, nopts...).Miner(&miner, &full, nopts...).Start() + return &full, &miner, ens +} + +// EnsembleTwoOne creates and starts an Ensemble with two full nodes and one miner. +// It does not interconnect nodes nor does it begin mining. +// +// This function supports passing both ensemble and node functional options. +// Functional options are applied to all nodes. +func EnsembleTwoOne(t *testing.T, opts ...interface{}) (*TestFullNode, *TestFullNode, *TestMiner, *Ensemble) { + eopts, nopts := siftOptions(t, opts) + + var ( + one, two TestFullNode + miner TestMiner + ) + ens := NewEnsemble(t, eopts...).FullNode(&one, nopts...).FullNode(&two, nopts...).Miner(&miner, &one, nopts...).Start() + return &one, &two, &miner, ens +} + +// EnsembleOneTwo creates and starts an Ensemble with one full node and two miners. +// It does not interconnect nodes nor does it begin mining. +// +// This function supports passing both ensemble and node functional options. +// Functional options are applied to all nodes. +func EnsembleOneTwo(t *testing.T, opts ...interface{}) (*TestFullNode, *TestMiner, *TestMiner, *Ensemble) { + eopts, nopts := siftOptions(t, opts) + + var ( + full TestFullNode + one, two TestMiner + ) + ens := NewEnsemble(t, eopts...). + FullNode(&full, nopts...). + Miner(&one, &full, nopts...). + Miner(&two, &full, nopts...). + Start() + + return &full, &one, &two, ens +} + +func siftOptions(t *testing.T, opts []interface{}) (eopts []EnsembleOpt, nopts []NodeOpt) { + for _, v := range opts { + switch o := v.(type) { + case EnsembleOpt: + eopts = append(eopts, o) + case NodeOpt: + nopts = append(nopts, o) + default: + t.Fatalf("invalid option type: %T", o) + } + } + return eopts, nopts +} diff --git a/itests/kit/files.go b/itests/kit2/files.go similarity index 82% rename from itests/kit/files.go rename to itests/kit2/files.go index d4e92fecf..1e1509858 100644 --- a/itests/kit/files.go +++ b/itests/kit2/files.go @@ -1,4 +1,4 @@ -package kit +package kit2 import ( "bytes" @@ -31,8 +31,9 @@ func CreateRandomFile(t *testing.T, rseed, size int) (path string) { return file.Name() } -// FilesEqual compares two files by blake2b hash equality. -func FilesEqual(t *testing.T, left, right string) bool { +// AssertFilesEqual compares two files by blake2b hash equality and +// fails the test if unequal. +func AssertFilesEqual(t *testing.T, left, right string) { // initialize hashes. leftH, rightH := blake2b.New256(), blake2b.New256() @@ -53,5 +54,5 @@ func FilesEqual(t *testing.T, left, right string) bool { // compute digests. leftD, rightD := leftH.Sum(nil), rightH.Sum(nil) - return bytes.Equal(leftD, rightD) + require.True(t, bytes.Equal(leftD, rightD)) } diff --git a/itests/kit2/funds.go b/itests/kit2/funds.go new file mode 100644 index 000000000..da37ae2ba --- /dev/null +++ b/itests/kit2/funds.go @@ -0,0 +1,34 @@ +package kit2 + +import ( + "context" + "testing" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/types" +) + +// SendFunds sends funds from the default wallet of the specified sender node +// to the recipient address. +func SendFunds(ctx context.Context, t *testing.T, sender TestFullNode, recipient address.Address, amount abi.TokenAmount) { + senderAddr, err := sender.WalletDefaultAddress(ctx) + require.NoError(t, err) + + msg := &types.Message{ + From: senderAddr, + To: recipient, + Value: amount, + } + + sm, err := sender.MpoolPushMessage(ctx, msg, nil) + require.NoError(t, err) + + res, err := sender.StateWaitMsg(ctx, sm.Cid(), 3, api.LookbackNoLimit, true) + require.NoError(t, err) + + require.Equal(t, 0, res.Receipt.ExitCode, "did not successfully send funds") +} diff --git a/itests/kit2/init.go b/itests/kit2/init.go new file mode 100644 index 000000000..dfc5a13f2 --- /dev/null +++ b/itests/kit2/init.go @@ -0,0 +1,29 @@ +package kit2 + +import ( + "fmt" + "os" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/build" + "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)) + + build.InsecurePoStValidation = true + + if err := os.Setenv("BELLMAN_NO_GPU", "1"); err != nil { + panic(fmt.Sprintf("failed to set BELLMAN_NO_GPU env variable: %s", err)) + } + + if err := os.Setenv("LOTUS_DISABLE_WATCHDOG", "1"); err != nil { + panic(fmt.Sprintf("failed to set LOTUS_DISABLE_WATCHDOG env variable: %s", err)) + } +} diff --git a/itests/kit2/log.go b/itests/kit2/log.go new file mode 100644 index 000000000..9b9a14d92 --- /dev/null +++ b/itests/kit2/log.go @@ -0,0 +1,19 @@ +package kit2 + +import ( + "github.com/filecoin-project/lotus/lib/lotuslog" + logging "github.com/ipfs/go-log/v2" +) + +func QuietMiningLogs() { + lotuslog.SetupLogLevels() + + _ = 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/kit/node_full.go b/itests/kit2/node_full.go similarity index 78% rename from itests/kit/node_full.go rename to itests/kit2/node_full.go index 0e9912063..b0b39b471 100644 --- a/itests/kit/node_full.go +++ b/itests/kit2/node_full.go @@ -1,4 +1,4 @@ -package kit +package kit2 import ( "context" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" ) +// TestFullNode represents a full node enrolled in an Ensemble. type TestFullNode struct { v1api.FullNode @@ -21,9 +22,11 @@ type TestFullNode struct { ListenAddr multiaddr.Multiaddr DefaultKey *wallet.Key - options NodeOpts + options nodeOpts } +// CreateImportFile creates a random file with the specified seed and size, and +// imports it into the full node. func (f *TestFullNode) CreateImportFile(ctx context.Context, rseed int, size int) (res *api.ImportRes, path string) { path = CreateRandomFile(f.t, rseed, size) res, err := f.ClientImport(ctx, api.FileRef{Path: path}) diff --git a/itests/kit/node_miner.go b/itests/kit2/node_miner.go similarity index 95% rename from itests/kit/node_miner.go rename to itests/kit2/node_miner.go index 79da005cc..1cd65e20e 100644 --- a/itests/kit/node_miner.go +++ b/itests/kit2/node_miner.go @@ -1,4 +1,4 @@ -package kit +package kit2 import ( "context" @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/require" ) +// TestMiner represents a miner enrolled in an Ensemble. type TestMiner struct { api.StorageMiner @@ -42,12 +43,7 @@ type TestMiner struct { PrivKey libp2pcrypto.PrivKey } - options NodeOpts -} - -var MineNext = miner.MineReq{ - InjectNulls: 0, - Done: func(bool, abi.ChainEpoch, error) {}, + options nodeOpts } func (tm *TestMiner) PledgeSectors(ctx context.Context, n, existing int, blockNotif <-chan struct{}) { diff --git a/itests/kit2/node_opts.go b/itests/kit2/node_opts.go new file mode 100644 index 000000000..59d5454df --- /dev/null +++ b/itests/kit2/node_opts.go @@ -0,0 +1,89 @@ +package kit2 + +import ( + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/wallet" + "github.com/filecoin-project/lotus/node" +) + +// DefaultPresealsPerBootstrapMiner is the number of preseals that every +// bootstrap miner has by default. It can be overridden through the +// PresealSectors option. +const DefaultPresealsPerBootstrapMiner = 2 + +// nodeOpts is an options accumulating struct, where functional options are +// merged into. +type nodeOpts struct { + balance abi.TokenAmount + lite bool + sectors int + rpc bool + ownerKey *wallet.Key + extraNodeOpts []node.Option +} + +// DefaultNodeOpts are the default options that will be applied to test nodes. +var DefaultNodeOpts = nodeOpts{ + balance: big.Mul(big.NewInt(100000000), types.NewInt(build.FilecoinPrecision)), + sectors: DefaultPresealsPerBootstrapMiner, +} + +// NodeOpt is a functional option for test nodes. +type NodeOpt func(opts *nodeOpts) error + +// OwnerBalance specifies the balance to be attributed to a miner's owner +// account. Only relevant when creating a miner. +func OwnerBalance(balance abi.TokenAmount) NodeOpt { + return func(opts *nodeOpts) error { + opts.balance = balance + return nil + } +} + +// LiteNode specifies that this node will be a lite node. Only relevant when +// creating a fullnode. +func LiteNode() NodeOpt { + return func(opts *nodeOpts) error { + opts.lite = true + return nil + } +} + +// PresealSectors specifies the amount of preseal sectors to give to a miner +// at genesis. Only relevant when creating a miner. +func PresealSectors(sectors int) NodeOpt { + return func(opts *nodeOpts) error { + opts.sectors = sectors + return nil + } +} + +// ThroughRPC makes interactions with this node throughout the test flow through +// the JSON-RPC API. +func ThroughRPC() NodeOpt { + return func(opts *nodeOpts) error { + opts.rpc = true + return nil + } +} + +// OwnerAddr sets the owner address of a miner. Only relevant when creating +// a miner. +func OwnerAddr(wk *wallet.Key) NodeOpt { + return func(opts *nodeOpts) error { + opts.ownerKey = wk + return nil + } +} + +// ConstructorOpts are Lotus node constructor options that are passed as-is to +// the node. +func ConstructorOpts(extra ...node.Option) NodeOpt { + return func(opts *nodeOpts) error { + opts.extraNodeOpts = extra + return nil + } +} diff --git a/itests/kit2/node_opts_nv.go b/itests/kit2/node_opts_nv.go new file mode 100644 index 000000000..05d2c2287 --- /dev/null +++ b/itests/kit2/node_opts_nv.go @@ -0,0 +1,65 @@ +package kit2 + +import ( + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/node" +) + +func LatestActorsAt(upgradeHeight abi.ChainEpoch) node.Option { + // Attention: Update this when introducing new actor versions or your tests will be sad + return NetworkUpgradeAt(network.Version13, upgradeHeight) +} + +func NetworkUpgradeAt(version network.Version, upgradeHeight abi.ChainEpoch) node.Option { + fullSchedule := stmgr.UpgradeSchedule{{ + // prepare for upgrade. + Network: network.Version9, + Height: 1, + Migration: stmgr.UpgradeActorsV2, + }, { + Network: network.Version10, + Height: 2, + Migration: stmgr.UpgradeActorsV3, + }, { + Network: network.Version12, + Height: 3, + Migration: stmgr.UpgradeActorsV4, + }, { + Network: network.Version13, + Height: 4, + Migration: stmgr.UpgradeActorsV5, + }} + + schedule := stmgr.UpgradeSchedule{} + for _, upgrade := range fullSchedule { + if upgrade.Network > version { + break + } + + schedule = append(schedule, upgrade) + } + + if upgradeHeight > 0 { + schedule[len(schedule)-1].Height = upgradeHeight + } + + return node.Override(new(stmgr.UpgradeSchedule), schedule) +} + +func SDRUpgradeAt(calico, persian abi.ChainEpoch) node.Option { + return node.Override(new(stmgr.UpgradeSchedule), stmgr.UpgradeSchedule{{ + Network: network.Version6, + Height: 1, + Migration: stmgr.UpgradeActorsV2, + }, { + Network: network.Version7, + Height: calico, + Migration: stmgr.UpgradeCalico, + }, { + Network: network.Version8, + Height: persian, + }}) + +} diff --git a/itests/kit2/rpc.go b/itests/kit2/rpc.go new file mode 100644 index 000000000..873b64257 --- /dev/null +++ b/itests/kit2/rpc.go @@ -0,0 +1,53 @@ +package kit2 + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/filecoin-project/lotus/api/client" + "github.com/filecoin-project/lotus/node" + "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr/net" + "github.com/stretchr/testify/require" +) + +func CreateRPCServer(t *testing.T, handler http.Handler) (*httptest.Server, multiaddr.Multiaddr) { + testServ := httptest.NewServer(handler) + t.Cleanup(testServ.Close) + t.Cleanup(testServ.CloseClientConnections) + + addr := testServ.Listener.Addr() + maddr, err := manet.FromNetAddr(addr) + require.NoError(t, err) + return testServ, maddr +} + +func fullRpc(t *testing.T, f *TestFullNode) *TestFullNode { + handler, err := node.FullNodeHandler(f.FullNode, false) + require.NoError(t, err) + + srv, maddr := CreateRPCServer(t, handler) + + cl, stop, err := client.NewFullNodeRPCV1(context.Background(), "ws://"+srv.Listener.Addr().String()+"/rpc/v1", nil) + require.NoError(t, err) + t.Cleanup(stop) + f.ListenAddr, f.FullNode = maddr, cl + + return f +} + +func minerRpc(t *testing.T, m *TestMiner) *TestMiner { + handler, err := node.MinerHandler(m.StorageMiner, false) + require.NoError(t, err) + + srv, maddr := CreateRPCServer(t, handler) + + cl, stop, err := client.NewStorageMinerRPCV0(context.Background(), "ws://"+srv.Listener.Addr().String()+"/rpc/v0", nil) + require.NoError(t, err) + t.Cleanup(stop) + + m.ListenAddr, m.StorageMiner = maddr, cl + return m +} diff --git a/itests/multisig_test.go.no b/itests/multisig_test.go similarity index 100% rename from itests/multisig_test.go.no rename to itests/multisig_test.go diff --git a/itests/paych_api_test.go.no b/itests/paych_api_test.go similarity index 100% rename from itests/paych_api_test.go.no rename to itests/paych_api_test.go diff --git a/itests/paych_cli_test.go.no b/itests/paych_cli_test.go similarity index 100% rename from itests/paych_cli_test.go.no rename to itests/paych_cli_test.go diff --git a/itests/sdr_upgrade_test.go.no b/itests/sdr_upgrade_test.go similarity index 100% rename from itests/sdr_upgrade_test.go.no rename to itests/sdr_upgrade_test.go diff --git a/itests/sector_pledge_test.go.no b/itests/sector_pledge_test.go similarity index 100% rename from itests/sector_pledge_test.go.no rename to itests/sector_pledge_test.go diff --git a/itests/sector_terminate_test.go.no b/itests/sector_terminate_test.go similarity index 100% rename from itests/sector_terminate_test.go.no rename to itests/sector_terminate_test.go diff --git a/itests/tape_test.go.no b/itests/tape_test.go similarity index 100% rename from itests/tape_test.go.no rename to itests/tape_test.go diff --git a/itests/verifreg_test.go.no b/itests/verifreg_test.go similarity index 100% rename from itests/verifreg_test.go.no rename to itests/verifreg_test.go diff --git a/itests/wdpost_dispute_test.go.no b/itests/wdpost_dispute_test.go similarity index 100% rename from itests/wdpost_dispute_test.go.no rename to itests/wdpost_dispute_test.go diff --git a/itests/wdpost_test.go.no b/itests/wdpost_test.go similarity index 100% rename from itests/wdpost_test.go.no rename to itests/wdpost_test.go From 183814a8266989650563726cfe68166abce2c7c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Sun, 13 Jun 2021 23:43:22 +0100 Subject: [PATCH 013/257] finish migrating deals test. --- itests/api_test.go | 4 +- itests/ccupgrade_test.go | 1 - itests/deals_test.go | 102 +++++++++++-------------------------- itests/kit2/client.go | 107 --------------------------------------- 4 files changed, 32 insertions(+), 182 deletions(-) delete mode 100644 itests/kit2/client.go diff --git a/itests/api_test.go b/itests/api_test.go index a8abee92f..a4539444f 100644 --- a/itests/api_test.go +++ b/itests/api_test.go @@ -26,11 +26,11 @@ func TestAPI(t *testing.T) { } type apiSuite struct { - opts []kit2.NodeOpt + opts []interface{} } // runAPITest is the entry point to API test suite -func runAPITest(t *testing.T, opts ...kit2.NodeOpt) { +func runAPITest(t *testing.T, opts ...interface{}) { ts := apiSuite{opts: opts} t.Run("version", ts.testVersion) diff --git a/itests/ccupgrade_test.go b/itests/ccupgrade_test.go index f6ba87820..28abac171 100644 --- a/itests/ccupgrade_test.go +++ b/itests/ccupgrade_test.go @@ -8,7 +8,6 @@ import ( "time" "github.com/filecoin-project/lotus/itests/kit" - "github.com/filecoin-project/lotus/itests/kit2" "github.com/stretchr/testify/require" "github.com/filecoin-project/go-state-types/abi" diff --git a/itests/deals_test.go b/itests/deals_test.go index b30e5ba69..fed141cb1 100644 --- a/itests/deals_test.go +++ b/itests/deals_test.go @@ -352,75 +352,33 @@ func TestOfflineDealFlow(t *testing.T) { t.Run("fastretrieval", func(t *testing.T) { runTest(t, true) }) } -// -// func runSecondDealRetrievalTest(t *testing.T, b kit.APIBuilder, blocktime time.Duration) { -// ctx := context.Background() -// -// fulls, miners := b(t, kit.OneFull, kit.OneMiner) -// client, miner := fulls[0].FullNode.(*impl.FullNodeAPI), miners[0] -// -// kit.ConnectAndStartMining(t, blocktime, miner, client) -// -// dh := kit.NewDealHarness(t, client, miner) -// -// { -// data1 := make([]byte, 800) -// rand.New(rand.NewSource(int64(3))).Read(data1) -// r := bytes.NewReader(data1) -// -// fcid1, err := client.ClientImportLocal(ctx, r) -// if err != nil { -// t.Fatal(err) -// } -// -// data2 := make([]byte, 800) -// rand.New(rand.NewSource(int64(9))).Read(data2) -// r2 := bytes.NewReader(data2) -// -// fcid2, err := client.ClientImportLocal(ctx, r2) -// if err != nil { -// t.Fatal(err) -// } -// -// deal1 := dh.StartDeal(ctx, fcid1, true, 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) -// dh.WaitDealSealed(ctx, deal1, true, false, nil) -// -// deal2 := dh.StartDeal(ctx, fcid2, true, 0) -// -// time.Sleep(time.Second) -// dh.WaitDealSealed(ctx, deal2, false, false, nil) -// -// // Retrieval -// info, err := client.ClientGetDealInfo(ctx, *deal2) -// require.NoError(t, err) -// -// rf, _ := miner.SectorsRefs(ctx) -// fmt.Printf("refs: %+v\n", rf) -// -// dh.PerformRetrieval(ctx, fcid2, &info.PieceCID, false, data2) -// } -// } -// -// func runZeroPricePerByteRetrievalDealFlow(t *testing.T, b kit.APIBuilder, blocktime time.Duration, startEpoch abi.ChainEpoch) { -// ctx := context.Background() -// -// fulls, miners := b(t, kit.OneFull, kit.OneMiner) -// client, miner := fulls[0].FullNode.(*impl.FullNodeAPI), miners[0] -// -// kit.ConnectAndStartMining(t, blocktime, miner, client) -// -// dh := kit.NewDealHarness(t, client, miner) -// -// // Set price-per-byte to zero -// ask, err := miner.MarketGetRetrievalAsk(ctx) -// require.NoError(t, err) -// -// ask.PricePerByte = abi.NewTokenAmount(0) -// err = miner.MarketSetRetrievalAsk(ctx, ask) -// require.NoError(t, err) -// -// dh.MakeOnlineDeal(ctx, 6, false, false, startEpoch) -// } +func TestZeroPricePerByteRetrieval(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode") + } + + kit2.QuietMiningLogs() + + var ( + blockTime = 10 * time.Millisecond + startEpoch = abi.ChainEpoch(2 << 12) + ) + + client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs()) + ens.InterconnectAll().BeginMining(blockTime) + + ctx := context.Background() + + ask, err := miner.MarketGetRetrievalAsk(ctx) + require.NoError(t, err) + + ask.PricePerByte = abi.NewTokenAmount(0) + err = miner.MarketSetRetrievalAsk(ctx, ask) + require.NoError(t, err) + + dh := kit2.NewDealHarness(t, client, miner) + runConcurrentDeals(t, dh, fullDealCyclesOpts{ + n: 1, + startEpoch: startEpoch, + }) +} diff --git a/itests/kit2/client.go b/itests/kit2/client.go deleted file mode 100644 index 2777d8d25..000000000 --- a/itests/kit2/client.go +++ /dev/null @@ -1,107 +0,0 @@ -package kit2 - -import ( - "context" - "fmt" - "path/filepath" - "regexp" - "strings" - "testing" - "time" - - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/specs-actors/v2/actors/builtin" - "github.com/stretchr/testify/require" - lcli "github.com/urfave/cli/v2" -) - -// RunClientTest exercises some of the Client CLI commands -func RunClientTest(t *testing.T, cmds []*lcli.Command, clientNode TestFullNode) { - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - - // Create mock CLI - mockCLI := NewMockCLI(ctx, t, cmds) - clientCLI := mockCLI.Client(clientNode.ListenAddr) - - // Get the Miner address - addrs, err := clientNode.StateListMiners(ctx, types.EmptyTSK) - require.NoError(t, err) - require.Len(t, addrs, 1) - - minerAddr := addrs[0] - fmt.Println("Miner:", minerAddr) - - // client query-ask - out := clientCLI.RunCmd("client", "query-ask", minerAddr.String()) - require.Regexp(t, regexp.MustCompile("Ask:"), out) - - // Create a deal (non-interactive) - // client deal --start-epoch= 1000000attofil - res, _ := clientNode.CreateImportFile(ctx, 1, 0) - - require.NoError(t, err) - startEpoch := fmt.Sprintf("--start-epoch=%d", 2<<12) - dataCid := res.Root - price := "1000000attofil" - duration := fmt.Sprintf("%d", build.MinDealDuration) - out = clientCLI.RunCmd("client", "deal", startEpoch, dataCid.String(), minerAddr.String(), price, duration) - fmt.Println("client deal", out) - - // Create a deal (interactive) - // client deal - // - // (in days) - // - // "no" (verified Client) - // "yes" (confirm deal) - res, _ = clientNode.CreateImportFile(ctx, 2, 0) - require.NoError(t, err) - dataCid2 := res.Root - duration = fmt.Sprintf("%d", build.MinDealDuration/builtin.EpochsInDay) - cmd := []string{"client", "deal"} - interactiveCmds := []string{ - dataCid2.String(), - duration, - minerAddr.String(), - "no", - "yes", - } - out = clientCLI.RunInteractiveCmd(cmd, interactiveCmds) - fmt.Println("client deal:\n", out) - - // Wait for provider to start sealing deal - dealStatus := "" - for { - // client list-deals - out = clientCLI.RunCmd("client", "list-deals") - fmt.Println("list-deals:\n", out) - - lines := strings.Split(out, "\n") - require.GreaterOrEqual(t, len(lines), 2) - re := regexp.MustCompile(`\s+`) - parts := re.Split(lines[1], -1) - if len(parts) < 4 { - require.Fail(t, "bad list-deals output format") - } - dealStatus = parts[3] - fmt.Println(" Deal status:", dealStatus) - - st := CategorizeDealState(dealStatus) - require.NotEqual(t, TestDealStateFailed, st) - if st == TestDealStateComplete { - break - } - - time.Sleep(time.Second) - } - - // Retrieve the first file from the Miner - // client retrieve - tmpdir := t.TempDir() - path := filepath.Join(tmpdir, "outfile.dat") - out = clientCLI.RunCmd("client", "retrieve", dataCid.String(), path) - fmt.Println("retrieve:\n", out) - require.Regexp(t, regexp.MustCompile("Success"), out) -} From 958b6b54a850424de36b8e664ac8e7f135e42743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Sun, 13 Jun 2021 23:53:13 +0100 Subject: [PATCH 014/257] go mod tidy. --- go.mod | 1 - 1 file changed, 1 deletion(-) diff --git a/go.mod b/go.mod index b34ca9322..e22be541a 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,6 @@ require ( github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e github.com/cockroachdb/pebble v0.0.0-20201001221639-879f3bfeef07 github.com/coreos/go-systemd/v22 v22.1.0 - github.com/davecgh/go-spew v1.1.1 // indirect github.com/detailyang/go-fallocate v0.0.0-20180908115635-432fa640bd2e github.com/dgraph-io/badger/v2 v2.2007.2 github.com/docker/go-units v0.4.0 From f1ec0d609403c11be6d8d6c3933af5d59f2ece4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Mon, 14 Jun 2021 00:10:37 +0100 Subject: [PATCH 015/257] fix lint. --- itests/deals_test.go | 1 + itests/kit2/deals.go | 31 ++++++++++++++----------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/itests/deals_test.go b/itests/deals_test.go index fed141cb1..3a6d9c868 100644 --- a/itests/deals_test.go +++ b/itests/deals_test.go @@ -51,6 +51,7 @@ func TestDealCyclesConcurrent(t *testing.T) { cycles := []int{1, 2, 4, 8} for _, n := range cycles { + n := n ns := fmt.Sprintf("%d", n) t.Run(ns+"-fastretrieval-CAR", func(t *testing.T) { runTest(t, n, true, true) }) t.Run(ns+"-fastretrieval-NoCAR", func(t *testing.T) { runTest(t, n, true, false) }) diff --git a/itests/kit2/deals.go b/itests/kit2/deals.go index e2dc00d52..2e015a9c7 100644 --- a/itests/kit2/deals.go +++ b/itests/kit2/deals.go @@ -1,9 +1,9 @@ package kit2 import ( - "bytes" "context" "io/ioutil" + "os" "testing" "time" @@ -128,7 +128,7 @@ loop: } } -// WaitDealSealed waits until the deal is published. +// WaitDealPublished waits until the deal is published. func (dh *DealHarness) WaitDealPublished(ctx context.Context, deal *cid.Cid) { subCtx, cancel := context.WithCancel(ctx) defer cancel() @@ -185,16 +185,16 @@ func (dh *DealHarness) PerformRetrieval(ctx context.Context, deal *cid.Cid, root require.NoError(dh.t, err) require.NotEmpty(dh.t, offers, "no offers") - tmpfile, err := ioutil.TempFile(dh.t.TempDir(), "ret-car") + carFile, err := ioutil.TempFile(dh.t.TempDir(), "ret-car") require.NoError(dh.t, err) - defer tmpfile.Close() + defer carFile.Close() //nolint:errcheck caddr, err := dh.client.WalletDefaultAddress(ctx) require.NoError(dh.t, err) ref := &api.FileRef{ - Path: tmpfile.Name(), + Path: carFile.Name(), IsCAR: carExport, } @@ -205,19 +205,19 @@ func (dh *DealHarness) PerformRetrieval(ctx context.Context, deal *cid.Cid, root require.Emptyf(dh.t, update.Err, "retrieval failed: %s", update.Err) } - rdata, err := ioutil.ReadFile(tmpfile.Name()) - require.NoError(dh.t, err) - + ret := carFile.Name() if carExport { - rdata = dh.ExtractFileFromCAR(ctx, rdata) + actualFile := dh.ExtractFileFromCAR(ctx, carFile) + ret = actualFile.Name() + _ = actualFile.Close() //nolint:errcheck } - return tmpfile.Name() + return ret } -func (dh *DealHarness) ExtractFileFromCAR(ctx context.Context, rdata []byte) []byte { +func (dh *DealHarness) ExtractFileFromCAR(ctx context.Context, file *os.File) (out *os.File) { bserv := dstest.Bserv() - ch, err := car.LoadCar(bserv.Blockstore(), bytes.NewReader(rdata)) + ch, err := car.LoadCar(bserv.Blockstore(), file) require.NoError(dh.t, err) b, err := bserv.GetBlock(ctx, ch.Roots[0]) @@ -233,13 +233,10 @@ func (dh *DealHarness) ExtractFileFromCAR(ctx context.Context, rdata []byte) []b tmpfile, err := ioutil.TempFile(dh.t.TempDir(), "file-in-car") require.NoError(dh.t, err) - defer tmpfile.Close() + defer tmpfile.Close() //nolint:errcheck err = files.WriteTo(fil, tmpfile.Name()) require.NoError(dh.t, err) - rdata, err = ioutil.ReadFile(tmpfile.Name()) - require.NoError(dh.t, err) - - return rdata + return tmpfile } From ac67e466ec15b2fe71518e1f362975c2cc147db9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Mon, 14 Jun 2021 00:13:15 +0100 Subject: [PATCH 016/257] fix test. --- itests/api_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/itests/api_test.go b/itests/api_test.go index a4539444f..f8567bd2a 100644 --- a/itests/api_test.go +++ b/itests/api_test.go @@ -87,11 +87,11 @@ func (ts *apiSuite) testConnectTwo(t *testing.T) { peers, err := one.NetPeers(ctx) require.NoError(t, err) - require.Lenf(t, peers, 1, "node one doesn't have 1 peer") + require.Lenf(t, peers, 2, "node one doesn't have 2 peers") peers, err = two.NetPeers(ctx) require.NoError(t, err) - require.Lenf(t, peers, 1, "node two doesn't have 1 peer") + require.Lenf(t, peers, 2, "node two doesn't have 2 peers") } func (ts *apiSuite) testSearchMsg(t *testing.T) { From d94a19ff65dbcb37a38ead4f527feaf2fea9d862 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Mon, 14 Jun 2021 12:07:50 +0200 Subject: [PATCH 017/257] refactor: cli test with kit2 --- itests/cli_test.go | 13 ++-- itests/kit2/client.go | 146 +++++++++++++++++++++++++++++++++++++++++ itests/kit2/mockcli.go | 141 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 293 insertions(+), 7 deletions(-) create mode 100644 itests/kit2/client.go create mode 100644 itests/kit2/mockcli.go diff --git a/itests/cli_test.go b/itests/cli_test.go index 10e2af15c..8436f189e 100644 --- a/itests/cli_test.go +++ b/itests/cli_test.go @@ -1,22 +1,21 @@ package itests import ( - "context" "os" "testing" "time" "github.com/filecoin-project/lotus/cli" - "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/itests/kit2" ) // TestClient does a basic test to exercise the client CLI commands. func TestClient(t *testing.T) { _ = os.Setenv("BELLMAN_NO_GPU", "1") - kit.QuietMiningLogs() + kit2.QuietMiningLogs() - blocktime := 5 * time.Millisecond - ctx := context.Background() - clientNode, _ := kit.StartOneNodeOneMiner(ctx, t, blocktime) - kit.RunClientTest(t, cli.Commands, clientNode) + blockTime := 5 * time.Millisecond + client, _, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), kit2.ThroughRPC()) + ens.InterconnectAll().BeginMining(blockTime) + kit2.RunClientTest(t, cli.Commands, *client) } diff --git a/itests/kit2/client.go b/itests/kit2/client.go new file mode 100644 index 000000000..247d20836 --- /dev/null +++ b/itests/kit2/client.go @@ -0,0 +1,146 @@ +package kit2 + +import ( + "context" + "fmt" + "io/ioutil" + "math/rand" + "os" + "path/filepath" + "regexp" + "strings" + "testing" + "time" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/specs-actors/v2/actors/builtin" + "github.com/stretchr/testify/require" + lcli "github.com/urfave/cli/v2" +) + +// RunClientTest exercises some of the Client CLI commands +func RunClientTest(t *testing.T, cmds []*lcli.Command, clientNode TestFullNode) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // Create mock CLI + mockCLI := NewMockCLI(ctx, t, cmds) + clientCLI := mockCLI.Client(clientNode.ListenAddr) + + // Get the Miner address + addrs, err := clientNode.StateListMiners(ctx, types.EmptyTSK) + require.NoError(t, err) + require.Len(t, addrs, 1) + + minerAddr := addrs[0] + fmt.Println("Miner:", minerAddr) + + // client query-ask + out := clientCLI.RunCmd("client", "query-ask", minerAddr.String()) + require.Regexp(t, regexp.MustCompile("Ask:"), out) + + // Create a deal (non-interactive) + // client deal --start-epoch= 1000000attofil + res, _, _, err := CreateImportFile(ctx, clientNode, 1, 0) + + require.NoError(t, err) + startEpoch := fmt.Sprintf("--start-epoch=%d", 2<<12) + dataCid := res.Root + price := "1000000attofil" + duration := fmt.Sprintf("%d", build.MinDealDuration) + out = clientCLI.RunCmd("client", "deal", startEpoch, dataCid.String(), minerAddr.String(), price, duration) + fmt.Println("client deal", out) + + // Create a deal (interactive) + // client deal + // + // (in days) + // + // "no" (verified Client) + // "yes" (confirm deal) + res, _, _, err = CreateImportFile(ctx, clientNode, 2, 0) + require.NoError(t, err) + dataCid2 := res.Root + duration = fmt.Sprintf("%d", build.MinDealDuration/builtin.EpochsInDay) + cmd := []string{"client", "deal"} + interactiveCmds := []string{ + dataCid2.String(), + duration, + minerAddr.String(), + "no", + "yes", + } + out = clientCLI.RunInteractiveCmd(cmd, interactiveCmds) + fmt.Println("client deal:\n", out) + + // Wait for provider to start sealing deal + dealStatus := "" + for { + // client list-deals + out = clientCLI.RunCmd("client", "list-deals") + fmt.Println("list-deals:\n", out) + + lines := strings.Split(out, "\n") + require.GreaterOrEqual(t, len(lines), 2) + re := regexp.MustCompile(`\s+`) + parts := re.Split(lines[1], -1) + if len(parts) < 4 { + require.Fail(t, "bad list-deals output format") + } + dealStatus = parts[3] + fmt.Println(" Deal status:", dealStatus) + + st := CategorizeDealState(dealStatus) + require.NotEqual(t, TestDealStateFailed, st) + if st == TestDealStateComplete { + break + } + + time.Sleep(time.Second) + } + + // Retrieve the first file from the Miner + // client retrieve + tmpdir, err := ioutil.TempDir(os.TempDir(), "test-cli-Client") + require.NoError(t, err) + path := filepath.Join(tmpdir, "outfile.dat") + out = clientCLI.RunCmd("client", "retrieve", dataCid.String(), path) + fmt.Println("retrieve:\n", out) + require.Regexp(t, regexp.MustCompile("Success"), out) +} + +func CreateImportFile(ctx context.Context, client api.FullNode, rseed int, size int) (res *api.ImportRes, path string, data []byte, err error) { + data, path, err = createRandomFile(rseed, size) + if err != nil { + return nil, "", nil, err + } + + res, err = client.ClientImport(ctx, api.FileRef{Path: path}) + if err != nil { + return nil, "", nil, err + } + return res, path, data, nil +} + +func createRandomFile(rseed, size int) ([]byte, string, error) { + if size == 0 { + size = 1600 + } + data := make([]byte, size) + rand.New(rand.NewSource(int64(rseed))).Read(data) + + dir, err := ioutil.TempDir(os.TempDir(), "test-make-deal-") + if err != nil { + return nil, "", err + } + + path := filepath.Join(dir, "sourcefile.dat") + err = ioutil.WriteFile(path, data, 0644) + if err != nil { + return nil, "", err + } + + return data, path, nil +} diff --git a/itests/kit2/mockcli.go b/itests/kit2/mockcli.go new file mode 100644 index 000000000..592c97333 --- /dev/null +++ b/itests/kit2/mockcli.go @@ -0,0 +1,141 @@ +package kit2 + +import ( + "bytes" + "context" + "flag" + "strings" + "testing" + + "github.com/multiformats/go-multiaddr" + "github.com/stretchr/testify/require" + lcli "github.com/urfave/cli/v2" +) + +type MockCLI struct { + t *testing.T + cmds []*lcli.Command + cctx *lcli.Context + out *bytes.Buffer +} + +func NewMockCLI(ctx context.Context, t *testing.T, cmds []*lcli.Command) *MockCLI { + // Create a CLI App with an --api-url flag so that we can specify which node + // the command should be executed against + app := &lcli.App{ + Flags: []lcli.Flag{ + &lcli.StringFlag{ + Name: "api-url", + Hidden: true, + }, + }, + Commands: cmds, + } + + var out bytes.Buffer + app.Writer = &out + app.Setup() + + cctx := lcli.NewContext(app, &flag.FlagSet{}, nil) + cctx.Context = ctx + return &MockCLI{t: t, cmds: cmds, cctx: cctx, out: &out} +} + +func (c *MockCLI) Client(addr multiaddr.Multiaddr) *MockCLIClient { + return &MockCLIClient{t: c.t, cmds: c.cmds, addr: addr, cctx: c.cctx, out: c.out} +} + +// MockCLIClient runs commands against a particular node +type MockCLIClient struct { + t *testing.T + cmds []*lcli.Command + addr multiaddr.Multiaddr + cctx *lcli.Context + out *bytes.Buffer +} + +func (c *MockCLIClient) RunCmd(input ...string) string { + out, err := c.RunCmdRaw(input...) + require.NoError(c.t, err, "output:\n%s", out) + + return out +} + +// Given an input, find the corresponding command or sub-command. +// eg "paych add-funds" +func (c *MockCLIClient) cmdByNameSub(input []string) (*lcli.Command, []string) { + name := input[0] + for _, cmd := range c.cmds { + if cmd.Name == name { + return c.findSubcommand(cmd, input[1:]) + } + } + return nil, []string{} +} + +func (c *MockCLIClient) findSubcommand(cmd *lcli.Command, input []string) (*lcli.Command, []string) { + // If there are no sub-commands, return the current command + if len(cmd.Subcommands) == 0 { + return cmd, input + } + + // Check each sub-command for a match against the name + subName := input[0] + for _, subCmd := range cmd.Subcommands { + if subCmd.Name == subName { + // Found a match, recursively search for sub-commands + return c.findSubcommand(subCmd, input[1:]) + } + } + return nil, []string{} +} + +func (c *MockCLIClient) RunCmdRaw(input ...string) (string, error) { + cmd, input := c.cmdByNameSub(input) + if cmd == nil { + panic("Could not find command " + input[0] + " " + input[1]) + } + + // prepend --api-url= + apiFlag := "--api-url=" + c.addr.String() + input = append([]string{apiFlag}, input...) + + fs := c.flagSet(cmd) + err := fs.Parse(input) + require.NoError(c.t, err) + + err = cmd.Action(lcli.NewContext(c.cctx.App, fs, c.cctx)) + + // Get the output + str := strings.TrimSpace(c.out.String()) + c.out.Reset() + return str, err +} + +func (c *MockCLIClient) flagSet(cmd *lcli.Command) *flag.FlagSet { + // Apply app level flags (so we can process --api-url flag) + fs := &flag.FlagSet{} + for _, f := range c.cctx.App.Flags { + err := f.Apply(fs) + if err != nil { + c.t.Fatal(err) + } + } + // Apply command level flags + for _, f := range cmd.Flags { + err := f.Apply(fs) + if err != nil { + c.t.Fatal(err) + } + } + return fs +} + +func (c *MockCLIClient) RunInteractiveCmd(cmd []string, interactive []string) string { + c.toStdin(strings.Join(interactive, "\n") + "\n") + return c.RunCmd(cmd...) +} + +func (c *MockCLIClient) toStdin(s string) { + c.cctx.App.Metadata["stdin"] = bytes.NewBufferString(s) +} From 86cca7303d503e119e39c42cbb87a9392930dded Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Mon, 14 Jun 2021 14:03:08 +0200 Subject: [PATCH 018/257] refactor: convert multisig tests to kit2 --- itests/multisig_test.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/itests/multisig_test.go b/itests/multisig_test.go index 4c513640d..cc2b38c30 100644 --- a/itests/multisig_test.go +++ b/itests/multisig_test.go @@ -12,26 +12,26 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/cli" - "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/itests/kit2" "github.com/stretchr/testify/require" ) // TestMultisig does a basic test to exercise the multisig CLI commands func TestMultisig(t *testing.T) { _ = os.Setenv("BELLMAN_NO_GPU", "1") - kit.QuietMiningLogs() + kit2.QuietMiningLogs() - blocktime := 5 * time.Millisecond - ctx := context.Background() - clientNode, _ := kit.StartOneNodeOneMiner(ctx, t, blocktime) + blockTime := 5 * time.Millisecond + client, _, ens := kit2.EnsembleMinimal(t, kit2.MockProofs()) + ens.InterconnectAll().BeginMining(blockTime) - runMultisigTests(t, clientNode) + runMultisigTests(t, *client) } -func runMultisigTests(t *testing.T, clientNode kit.TestFullNode) { +func runMultisigTests(t *testing.T, clientNode kit2.TestFullNode) { // Create mock CLI ctx := context.Background() - mockCLI := kit.NewMockCLI(ctx, t, cli.Commands) + mockCLI := kit2.NewMockCLI(ctx, t, cli.Commands) clientCLI := mockCLI.Client(clientNode.ListenAddr) // Create some wallets on the node to use for testing multisig @@ -42,7 +42,7 @@ func runMultisigTests(t *testing.T, clientNode kit.TestFullNode) { walletAddrs = append(walletAddrs, addr) - kit.SendFunds(ctx, t, clientNode, addr, types.NewInt(1e15)) + kit2.SendFunds(ctx, t, clientNode, addr, types.NewInt(1e15)) } // Create an msig with three of the addresses and threshold of two sigs From 7b00b1828b082939d50f9477c8868911bdeafe69 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Mon, 14 Jun 2021 15:28:05 +0200 Subject: [PATCH 019/257] refactor: convert paych to kit2 --- itests/kit2/funds.go | 2 +- itests/paych_api_test.go | 50 ++++++++++++++++------------------------ 2 files changed, 21 insertions(+), 31 deletions(-) diff --git a/itests/kit2/funds.go b/itests/kit2/funds.go index da37ae2ba..fc4a6c294 100644 --- a/itests/kit2/funds.go +++ b/itests/kit2/funds.go @@ -30,5 +30,5 @@ func SendFunds(ctx context.Context, t *testing.T, sender TestFullNode, recipient res, err := sender.StateWaitMsg(ctx, sm.Cid(), 3, api.LookbackNoLimit, true) require.NoError(t, err) - require.Equal(t, 0, res.Receipt.ExitCode, "did not successfully send funds") + require.EqualValues(t, 0, res.Receipt.ExitCode, "did not successfully send funds") } diff --git a/itests/paych_api_test.go b/itests/paych_api_test.go index 23fec855b..900b3d092 100644 --- a/itests/paych_api_test.go +++ b/itests/paych_api_test.go @@ -8,7 +8,6 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" - "github.com/filecoin-project/lotus/itests/kit" "github.com/ipfs/go-cid" "github.com/filecoin-project/go-address" @@ -24,36 +23,30 @@ import ( "github.com/filecoin-project/lotus/chain/events" "github.com/filecoin-project/lotus/chain/events/state" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/itests/kit2" ) func TestPaymentChannelsAPI(t *testing.T) { - kit.QuietMiningLogs() + kit2.QuietMiningLogs() ctx := context.Background() - n, sn := kit.MockMinerBuilder(t, kit.TwoFull, kit.OneMiner) + blockTime := 5 * time.Millisecond - paymentCreator := n[0] - paymentReceiver := n[1] - miner := sn[0] + var ( + paymentCreator kit2.TestFullNode + paymentReceiver kit2.TestFullNode + miner kit2.TestMiner + ) - // get everyone connected - addrs, err := paymentCreator.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := paymentReceiver.NetConnect(ctx, addrs); err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrs); err != nil { - t.Fatal(err) - } - - // start mining blocks - bm := kit.NewBlockMiner(t, miner) - bm.MineBlocks(ctx, 5*time.Millisecond) - t.Cleanup(bm.Stop) + //n, sn := kit2.MockMinerBuilder(t, kit2.TwoFull, kit2.OneMiner) + ens := kit2.NewEnsemble(t, kit2.MockProofs()). + FullNode(&paymentCreator). + FullNode(&paymentReceiver). + Miner(&miner, &paymentCreator). + Start(). + InterconnectAll() + bms := ens.BeginMining(blockTime, &miner) + bm := bms[0] // send some funds to register the receiver receiverAddr, err := paymentReceiver.WalletNew(ctx, types.KTSecp256k1) @@ -61,7 +54,7 @@ func TestPaymentChannelsAPI(t *testing.T) { t.Fatal(err) } - kit.SendFunds(ctx, t, paymentCreator, receiverAddr, abi.NewTokenAmount(1e18)) + kit2.SendFunds(ctx, t, paymentCreator, receiverAddr, abi.NewTokenAmount(1e18)) // setup the payment channel createrAddr, err := paymentCreator.WalletDefaultAddress(ctx) @@ -263,12 +256,9 @@ func TestPaymentChannelsAPI(t *testing.T) { if !delta.Equals(abi.NewTokenAmount(expectedRefund)) { t.Fatalf("did not send correct funds from creator: expected %d, got %d", expectedRefund, delta) } - - // shut down mining - bm.Stop() } -func waitForBlocks(ctx context.Context, t *testing.T, bm *kit.BlockMiner, paymentReceiver kit.TestFullNode, receiverAddr address.Address, count int) { +func waitForBlocks(ctx context.Context, t *testing.T, bm *kit2.BlockMiner, paymentReceiver kit2.TestFullNode, receiverAddr address.Address, count int) { // We need to add null blocks in batches, if we add too many the chain can't sync batchSize := 60 for i := 0; i < count; i += batchSize { @@ -297,7 +287,7 @@ func waitForBlocks(ctx context.Context, t *testing.T, bm *kit.BlockMiner, paymen } } -func waitForMessage(ctx context.Context, t *testing.T, paymentCreator kit.TestFullNode, msgCid cid.Cid, duration time.Duration, desc string) *api.MsgLookup { +func waitForMessage(ctx context.Context, t *testing.T, paymentCreator kit2.TestFullNode, msgCid cid.Cid, duration time.Duration, desc string) *api.MsgLookup { ctx, cancel := context.WithTimeout(ctx, duration) defer cancel() From d2c35db0c923737af5dfff438c2f0503345eb6b5 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Mon, 14 Jun 2021 15:51:10 +0200 Subject: [PATCH 020/257] refactor: convert tape test to kit2 --- itests/tape_test.go | 52 ++++++++------------------------------------- 1 file changed, 9 insertions(+), 43 deletions(-) diff --git a/itests/tape_test.go b/itests/tape_test.go index 5c0cadc3f..6fb3def15 100644 --- a/itests/tape_test.go +++ b/itests/tape_test.go @@ -2,7 +2,6 @@ package itests import ( "context" - "fmt" "testing" "time" @@ -11,24 +10,23 @@ import ( "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/stmgr" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" - "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/itests/kit2" "github.com/filecoin-project/lotus/node" - "github.com/filecoin-project/lotus/node/impl" "github.com/stretchr/testify/require" ) func TestTapeFix(t *testing.T) { - kit.QuietMiningLogs() + kit2.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, kit.MockMinerBuilder, blocktime, true) }) + t.Run("after", func(t *testing.T) { testTapeFix(t, blocktime, true) }) } -func testTapeFix(t *testing.T, b kit.APIBuilder, blocktime time.Duration, after bool) { +func testTapeFix(t *testing.T, blocktime time.Duration, after bool) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -44,46 +42,14 @@ func testTapeFix(t *testing.T, b kit.APIBuilder, blocktime time.Duration, after }) } - n, sn := b(t, []kit.FullNodeOpts{{Opts: func(_ []kit.TestFullNode) node.Option { - return node.Override(new(stmgr.UpgradeSchedule), upgradeSchedule) - }}}, kit.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, kit.MineNext); err != nil { - if ctx.Err() != nil { - // context was canceled, ignore the error. - return - } - t.Error(err) - } - } - }() - defer func() { - cancel() - <-done - }() + nopts := kit2.ConstructorOpts(node.Override(new(stmgr.UpgradeSchedule), upgradeSchedule)) + _, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), nopts) + ens.InterconnectAll().BeginMining(blocktime) sid, err := miner.PledgeSector(ctx) require.NoError(t, err) - fmt.Printf("All sectors is fsm\n") + t.Log("All sectors is fsm") // If before, we expect the precommit to fail successState := api.SectorState(sealing.CommitFailed) @@ -101,6 +67,6 @@ func testTapeFix(t *testing.T, b kit.APIBuilder, blocktime time.Duration, after } require.NotEqual(t, failureState, st.State) build.Clock.Sleep(100 * time.Millisecond) - fmt.Println("WaitSeal") + t.Log("WaitSeal") } } From eb5a263d747706722c96328ff3d4e9005d154f30 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Mon, 14 Jun 2021 16:19:20 +0200 Subject: [PATCH 021/257] refactor: convert verifreg test to kit2 --- itests/verifreg_test.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/itests/verifreg_test.go b/itests/verifreg_test.go index b3555cc06..180a194a6 100644 --- a/itests/verifreg_test.go +++ b/itests/verifreg_test.go @@ -6,25 +6,29 @@ import ( "testing" "time" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/network" - "github.com/filecoin-project/lotus/itests/kit" - - lapi "github.com/filecoin-project/lotus/api" - - "github.com/filecoin-project/lotus/chain/actors" - "github.com/filecoin-project/lotus/chain/actors/builtin/verifreg" - "github.com/filecoin-project/lotus/node/impl" verifreg4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/verifreg" - "github.com/filecoin-project/go-state-types/big" + lapi "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/builtin/verifreg" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/itests/kit2" + "github.com/filecoin-project/lotus/node/impl" ) func TestVerifiedClientTopUp(t *testing.T) { + blockTime := 100 * time.Millisecond + test := func(nv network.Version, shouldWork bool) func(*testing.T) { return func(t *testing.T) { - nodes, miners := kit.MockMinerBuilder(t, []kit.FullNodeOpts{kit.FullNodeWithNetworkUpgradeAt(nv, -1)}, kit.OneMiner) - api := nodes[0].FullNode.(*impl.FullNodeAPI) + nopts := kit2.ConstructorOpts(kit2.NetworkUpgradeAt(nv, -1)) + + node, _, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), nopts) + ens.InterconnectAll().BeginMining(blockTime) + + api := node.FullNode.(*impl.FullNodeAPI) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -52,10 +56,6 @@ func TestVerifiedClientTopUp(t *testing.T) { Value: big.Zero(), } - bm := kit.NewBlockMiner(t, miners[0]) - bm.MineBlocks(ctx, 100*time.Millisecond) - t.Cleanup(bm.Stop) - sm, err := api.MpoolPushMessage(ctx, msg, nil) if err != nil { t.Fatal("AddVerifier failed: ", err) From 6fb31a34b37423861fac44fae65f9dee08553481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Mon, 14 Jun 2021 18:58:12 +0100 Subject: [PATCH 022/257] start mining way more in the past. --- itests/kit2/ensemble.go | 5 ++--- itests/kit2/ensemble_opts.go | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/itests/kit2/ensemble.go b/itests/kit2/ensemble.go index 5d12c83e1..751066de3 100644 --- a/itests/kit2/ensemble.go +++ b/itests/kit2/ensemble.go @@ -114,8 +114,7 @@ type Ensemble struct { } } -// NewEnsemble instantiates a new blank Ensemble. This enables you to -// programmatically +// NewEnsemble instantiates a new blank Ensemble. func NewEnsemble(t *testing.T, opts ...EnsembleOpt) *Ensemble { options := DefaultEnsembleOpts for _, o := range opts { @@ -593,7 +592,7 @@ func (n *Ensemble) generateGenesis() *genesis.Template { Accounts: n.genesis.accounts, Miners: n.genesis.miners, NetworkName: "test", - Timestamp: uint64(time.Now().Unix() - 10000), // some time sufficiently far in the past + Timestamp: uint64(time.Now().Unix() - int64(n.options.pastOffset.Seconds())), VerifregRootKey: gen.DefaultVerifregRootkeyActor, RemainderAccount: gen.DefaultRemainderAccountActor, } diff --git a/itests/kit2/ensemble_opts.go b/itests/kit2/ensemble_opts.go index 724113bdc..a5a8c733f 100644 --- a/itests/kit2/ensemble_opts.go +++ b/itests/kit2/ensemble_opts.go @@ -15,7 +15,7 @@ type ensembleOpts struct { } var DefaultEnsembleOpts = ensembleOpts{ - pastOffset: 10000 * time.Second, + pastOffset: 100000 * time.Second, // time sufficiently in the past to trigger catch-up mining. proofType: abi.RegisteredSealProof_StackedDrg2KiBV1, } From 8a7dba11bd4bc2d05797cc0cd928196748b29508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Mon, 14 Jun 2021 18:58:40 +0100 Subject: [PATCH 023/257] add comment. --- itests/kit2/log.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/itests/kit2/log.go b/itests/kit2/log.go index 9b9a14d92..f255d0639 100644 --- a/itests/kit2/log.go +++ b/itests/kit2/log.go @@ -8,7 +8,7 @@ import ( func QuietMiningLogs() { lotuslog.SetupLogLevels() - _ = logging.SetLogLevel("miner", "ERROR") + _ = logging.SetLogLevel("miner", "ERROR") // set this to INFO to watch mining happen. _ = logging.SetLogLevel("chainstore", "ERROR") _ = logging.SetLogLevel("chain", "ERROR") _ = logging.SetLogLevel("sub", "ERROR") From dae8be0881c24da0144b5c82c144d08226159637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Mon, 14 Jun 2021 18:59:15 +0100 Subject: [PATCH 024/257] migrate to require; use t.Log* instead of fmt.Print*. --- itests/paych_api_test.go | 160 ++++++++++++--------------------------- 1 file changed, 48 insertions(+), 112 deletions(-) diff --git a/itests/paych_api_test.go b/itests/paych_api_test.go index 900b3d092..3639d7b71 100644 --- a/itests/paych_api_test.go +++ b/itests/paych_api_test.go @@ -2,13 +2,13 @@ package itests import ( "context" - "fmt" "testing" "time" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/ipfs/go-cid" + "github.com/stretchr/testify/require" "github.com/filecoin-project/go-address" cbor "github.com/ipfs/go-ipld-cbor" @@ -50,36 +50,26 @@ func TestPaymentChannelsAPI(t *testing.T) { // send some funds to register the receiver receiverAddr, err := paymentReceiver.WalletNew(ctx, types.KTSecp256k1) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) kit2.SendFunds(ctx, t, paymentCreator, receiverAddr, abi.NewTokenAmount(1e18)) // setup the payment channel createrAddr, err := paymentCreator.WalletDefaultAddress(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) channelAmt := int64(7000) channelInfo, err := paymentCreator.PaychGet(ctx, createrAddr, receiverAddr, abi.NewTokenAmount(channelAmt)) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) channel, err := paymentCreator.PaychGetWaitReady(ctx, channelInfo.WaitSentinel) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // allocate three lanes var lanes []uint64 for i := 0; i < 3; i++ { lane, err := paymentCreator.PaychAllocateLane(ctx, channel) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) lanes = append(lanes, lane) } @@ -88,45 +78,28 @@ func TestPaymentChannelsAPI(t *testing.T) { // supersedes the voucher with a value of 1000 for _, lane := range lanes { vouch1, err := paymentCreator.PaychVoucherCreate(ctx, channel, abi.NewTokenAmount(1000), lane) - if err != nil { - t.Fatal(err) - } - if vouch1.Voucher == nil { - t.Fatal(fmt.Errorf("Not enough funds to create voucher: missing %d", vouch1.Shortfall)) - } + require.NoError(t, err) + require.NotNil(t, vouch1.Voucher, "Not enough funds to create voucher: missing %d", vouch1.Shortfall) + vouch2, err := paymentCreator.PaychVoucherCreate(ctx, channel, abi.NewTokenAmount(2000), lane) - if err != nil { - t.Fatal(err) - } - if vouch2.Voucher == nil { - t.Fatal(fmt.Errorf("Not enough funds to create voucher: missing %d", vouch2.Shortfall)) - } + require.NoError(t, err) + require.NotNil(t, vouch2.Voucher, "Not enough funds to create voucher: missing %d", vouch2.Shortfall) + delta1, err := paymentReceiver.PaychVoucherAdd(ctx, channel, vouch1.Voucher, nil, abi.NewTokenAmount(1000)) - if err != nil { - t.Fatal(err) - } - if !delta1.Equals(abi.NewTokenAmount(1000)) { - t.Fatal("voucher didn't have the right amount") - } + require.NoError(t, err) + require.EqualValues(t, abi.NewTokenAmount(1000), delta1, "voucher didn't have the right amount") + delta2, err := paymentReceiver.PaychVoucherAdd(ctx, channel, vouch2.Voucher, nil, abi.NewTokenAmount(1000)) - if err != nil { - t.Fatal(err) - } - if !delta2.Equals(abi.NewTokenAmount(1000)) { - t.Fatal("voucher didn't have the right amount") - } + require.NoError(t, err) + require.EqualValues(t, abi.NewTokenAmount(1000), delta2, "voucher didn't have the right amount") } // settle the payment channel settleMsgCid, err := paymentCreator.PaychSettle(ctx, channel) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) res := waitForMessage(ctx, t, paymentCreator, settleMsgCid, time.Second*10, "settle") - if res.Receipt.ExitCode != 0 { - t.Fatal("Unable to settle payment channel") - } + require.EqualValues(t, 0, res.Receipt.ExitCode, "Unable to settle payment channel") creatorStore := adt.WrapStore(ctx, cbor.NewCborStore(blockstore.NewAPIBlockstore(paymentCreator))) @@ -163,9 +136,7 @@ func TestPaymentChannelsAPI(t *testing.T) { }, int(build.MessageConfidence)+1, build.Finality, func(oldTs, newTs *types.TipSet) (bool, events.StateChange, error) { return preds.OnPaymentChannelActorChanged(channel, preds.OnToSendAmountChanges())(ctx, oldTs.Key(), newTs.Key()) }) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) select { case <-finished: @@ -175,75 +146,49 @@ func TestPaymentChannelsAPI(t *testing.T) { // Create a new voucher now that some vouchers have already been submitted vouchRes, err := paymentCreator.PaychVoucherCreate(ctx, channel, abi.NewTokenAmount(1000), 3) - if err != nil { - t.Fatal(err) - } - if vouchRes.Voucher == nil { - t.Fatal(fmt.Errorf("Not enough funds to create voucher: missing %d", vouchRes.Shortfall)) - } + require.NoError(t, err) + require.NotNil(t, vouchRes.Voucher, "Not enough funds to create voucher: missing %d", vouchRes.Shortfall) + vdelta, err := paymentReceiver.PaychVoucherAdd(ctx, channel, vouchRes.Voucher, nil, abi.NewTokenAmount(1000)) - if err != nil { - t.Fatal(err) - } - if !vdelta.Equals(abi.NewTokenAmount(1000)) { - t.Fatal("voucher didn't have the right amount") - } + require.NoError(t, err) + require.EqualValues(t, abi.NewTokenAmount(1000), vdelta, "voucher didn't have the right amount") // Create a new voucher whose value would exceed the channel balance excessAmt := abi.NewTokenAmount(1000) vouchRes, err = paymentCreator.PaychVoucherCreate(ctx, channel, excessAmt, 4) - if err != nil { - t.Fatal(err) - } - if vouchRes.Voucher != nil { - t.Fatal("Expected not to be able to create voucher whose value would exceed channel balance") - } - if !vouchRes.Shortfall.Equals(excessAmt) { - t.Fatal(fmt.Errorf("Expected voucher shortfall of %d, got %d", excessAmt, vouchRes.Shortfall)) - } + require.NoError(t, err) + require.Nil(t, vouchRes.Voucher, "Expected not to be able to create voucher whose value would exceed channel balance") + require.EqualValues(t, excessAmt, vouchRes.Shortfall, "Expected voucher shortfall of %d, got %d", excessAmt, vouchRes.Shortfall) // Add a voucher whose value would exceed the channel balance vouch := &paych.SignedVoucher{ChannelAddr: channel, Amount: excessAmt, Lane: 4, Nonce: 1} vb, err := vouch.SigningBytes() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + sig, err := paymentCreator.WalletSign(ctx, createrAddr, vb) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + vouch.Signature = sig _, err = paymentReceiver.PaychVoucherAdd(ctx, channel, vouch, nil, abi.NewTokenAmount(1000)) - if err == nil { - t.Fatal(fmt.Errorf("Expected shortfall error of %d", excessAmt)) - } + require.Errorf(t, err, "Expected shortfall error of %d", excessAmt) // wait for the settlement period to pass before collecting waitForBlocks(ctx, t, bm, paymentReceiver, receiverAddr, policy.PaychSettleDelay) creatorPreCollectBalance, err := paymentCreator.WalletBalance(ctx, createrAddr) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // collect funds (from receiver, though either party can do it) collectMsg, err := paymentReceiver.PaychCollect(ctx, channel) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + res, err = paymentReceiver.StateWaitMsg(ctx, collectMsg, 3, api.LookbackNoLimit, true) - if err != nil { - t.Fatal(err) - } - if res.Receipt.ExitCode != 0 { - t.Fatal("unable to collect on payment channel") - } + require.NoError(t, err) + require.EqualValues(t, 0, res.Receipt.ExitCode, "unable to collect on payment channel") // Finally, check the balance for the creator currentCreatorBalance, err := paymentCreator.WalletBalance(ctx, createrAddr) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // The highest nonce voucher that the creator sent on each lane is 2000 totalVouchers := int64(len(lanes) * 2000) @@ -253,9 +198,7 @@ func TestPaymentChannelsAPI(t *testing.T) { // channel amount - total voucher value expectedRefund := channelAmt - totalVouchers delta := big.Sub(currentCreatorBalance, creatorPreCollectBalance) - if !delta.Equals(abi.NewTokenAmount(expectedRefund)) { - t.Fatalf("did not send correct funds from creator: expected %d, got %d", expectedRefund, delta) - } + require.EqualValues(t, abi.NewTokenAmount(expectedRefund), delta, "did not send correct funds from creator: expected %d, got %d", expectedRefund, delta) } func waitForBlocks(ctx context.Context, t *testing.T, bm *kit2.BlockMiner, paymentReceiver kit2.TestFullNode, receiverAddr address.Address, count int) { @@ -276,14 +219,10 @@ func waitForBlocks(ctx context.Context, t *testing.T, bm *kit2.BlockMiner, payme From: receiverAddr, Value: types.NewInt(0), }, nil) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) _, err = paymentReceiver.StateWaitMsg(ctx, m.Cid(), 1, api.LookbackNoLimit, true) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } } @@ -291,15 +230,12 @@ func waitForMessage(ctx context.Context, t *testing.T, paymentCreator kit2.TestF ctx, cancel := context.WithTimeout(ctx, duration) defer cancel() - fmt.Println("Waiting for", desc) + t.Log("Waiting for", desc) + res, err := paymentCreator.StateWaitMsg(ctx, msgCid, 1, api.LookbackNoLimit, true) - if err != nil { - fmt.Println("Error waiting for", desc, err) - t.Fatal(err) - } - if res.Receipt.ExitCode != 0 { - t.Fatalf("did not successfully send %s", desc) - } - fmt.Println("Confirmed", desc) + require.NoError(t, err) + require.EqualValues(t, 0, res.Receipt.ExitCode, "did not successfully send %s", desc) + + t.Log("Confirmed", desc) return res } From 0bb5fffd881b8dde2661870020bd453d25c90cc3 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Tue, 15 Jun 2021 08:09:14 +0200 Subject: [PATCH 025/257] refactor: clean up code --- itests/paych_api_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/itests/paych_api_test.go b/itests/paych_api_test.go index 3639d7b71..8fb4cde0c 100644 --- a/itests/paych_api_test.go +++ b/itests/paych_api_test.go @@ -38,14 +38,13 @@ func TestPaymentChannelsAPI(t *testing.T) { miner kit2.TestMiner ) - //n, sn := kit2.MockMinerBuilder(t, kit2.TwoFull, kit2.OneMiner) ens := kit2.NewEnsemble(t, kit2.MockProofs()). FullNode(&paymentCreator). FullNode(&paymentReceiver). Miner(&miner, &paymentCreator). Start(). InterconnectAll() - bms := ens.BeginMining(blockTime, &miner) + bms := ens.BeginMining(blockTime) bm := bms[0] // send some funds to register the receiver From c27f3deaddbd954ada56a227fe61244633f2bc67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Mon, 14 Jun 2021 18:58:12 +0100 Subject: [PATCH 026/257] start mining way more in the past. --- itests/kit2/ensemble.go | 5 ++--- itests/kit2/ensemble_opts.go | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/itests/kit2/ensemble.go b/itests/kit2/ensemble.go index 5d12c83e1..751066de3 100644 --- a/itests/kit2/ensemble.go +++ b/itests/kit2/ensemble.go @@ -114,8 +114,7 @@ type Ensemble struct { } } -// NewEnsemble instantiates a new blank Ensemble. This enables you to -// programmatically +// NewEnsemble instantiates a new blank Ensemble. func NewEnsemble(t *testing.T, opts ...EnsembleOpt) *Ensemble { options := DefaultEnsembleOpts for _, o := range opts { @@ -593,7 +592,7 @@ func (n *Ensemble) generateGenesis() *genesis.Template { Accounts: n.genesis.accounts, Miners: n.genesis.miners, NetworkName: "test", - Timestamp: uint64(time.Now().Unix() - 10000), // some time sufficiently far in the past + Timestamp: uint64(time.Now().Unix() - int64(n.options.pastOffset.Seconds())), VerifregRootKey: gen.DefaultVerifregRootkeyActor, RemainderAccount: gen.DefaultRemainderAccountActor, } diff --git a/itests/kit2/ensemble_opts.go b/itests/kit2/ensemble_opts.go index 724113bdc..a5a8c733f 100644 --- a/itests/kit2/ensemble_opts.go +++ b/itests/kit2/ensemble_opts.go @@ -15,7 +15,7 @@ type ensembleOpts struct { } var DefaultEnsembleOpts = ensembleOpts{ - pastOffset: 10000 * time.Second, + pastOffset: 100000 * time.Second, // time sufficiently in the past to trigger catch-up mining. proofType: abi.RegisteredSealProof_StackedDrg2KiBV1, } From 803a3df6b46ed7eceecdbd08e92fe71c02be1af4 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Mon, 14 Jun 2021 11:50:40 +0200 Subject: [PATCH 027/257] refactor: ccupgrade test --- itests/ccupgrade_test.go | 79 +++++++++++++++++++------------------ itests/kit2/node_opts_nv.go | 12 ++++-- 2 files changed, 49 insertions(+), 42 deletions(-) diff --git a/itests/ccupgrade_test.go b/itests/ccupgrade_test.go index 28abac171..d90169b70 100644 --- a/itests/ccupgrade_test.go +++ b/itests/ccupgrade_test.go @@ -3,62 +3,67 @@ package itests import ( "context" "fmt" - "sync/atomic" "testing" "time" "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/itests/kit2" "github.com/stretchr/testify/require" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/node/impl" ) func TestCCUpgrade(t *testing.T) { - kit.QuietMiningLogs() + kit2.QuietMiningLogs() for _, height := range []abi.ChainEpoch{ - -1, // before - 162, // while sealing - 530, // after upgrade deal - 5000, // after + -1, // before + //162, // while sealing + //530, // after upgrade deal + //5000, // after } { height := height // make linters happy by copying t.Run(fmt.Sprintf("upgrade-%d", height), func(t *testing.T) { - runTestCCUpgrade(t, kit.MockMinerBuilder, 5*time.Millisecond, height) + runTestCCUpgrade(t, height) }) } } -func runTestCCUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Duration, upgradeHeight abi.ChainEpoch) { +func runTestCCUpgrade(t *testing.T, upgradeHeight abi.ChainEpoch) { ctx := context.Background() - n, sn := b(t, []kit.FullNodeOpts{kit.FullNodeWithLatestActorsAt(upgradeHeight)}, kit.OneMiner) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] + blockTime := 5 * time.Millisecond - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } + client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), kit2.LatestActorsAt(upgradeHeight)) + ens.InterconnectAll().BeginMining(blockTime) - if err := miner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - time.Sleep(time.Second) + //b := kit.MockMinerBuilder + //n, sn := b(t, []kit.FullNodeOpts{kit.FullNodeWithLatestActorsAt(upgradeHeight)}, kit.OneMiner) + //client := n[0].FullNode.(*impl.FullNodeAPI) + //miner := sn[0] - mine := int64(1) - done := make(chan struct{}) - go func() { - defer close(done) - for atomic.LoadInt64(&mine) == 1 { - time.Sleep(blocktime) - if err := sn[0].MineOne(ctx, kit.MineNext); err != nil { - t.Error(err) - } - } - }() + //addrinfo, err := client.NetAddrsListen(ctx) + //if err != nil { + // t.Fatal(err) + //} + // + //if err := miner.NetConnect(ctx, addrinfo); err != nil { + // t.Fatal(err) + //} + //time.Sleep(time.Second) + // + //mine := int64(1) + //done := make(chan struct{}) + //go func() { + // defer close(done) + // for atomic.LoadInt64(&mine) == 1 { + // time.Sleep(blocktime) + // if err := sn[0].MineOne(ctx, kit.MineNext); err != nil { + // t.Error(err) + // } + // } + //}() maddr, err := miner.ActorAddress(ctx) if err != nil { @@ -68,7 +73,7 @@ func runTestCCUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Duration, u CC := abi.SectorNumber(kit.GenesisPreseals + 1) Upgraded := CC + 1 - kit.PledgeSectors(t, ctx, miner, 1, 0, nil) + miner.PledgeSectors(ctx, 1, 0, nil) sl, err := miner.SectorsList(ctx) if err != nil { @@ -92,9 +97,9 @@ func runTestCCUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Duration, u t.Fatal(err) } - dh := kit.NewDealHarness(t, client, miner) + dh := kit2.NewDealHarness(t, client, miner) - dh.MakeFullDeal(context.Background(), 6, false, false, 0) + dh.MakeOnlineDeal(context.Background(), 6, false, 0) // Validate upgrade @@ -123,10 +128,6 @@ func runTestCCUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Duration, u } t.Log("waiting for sector to expire") // wait one deadline per loop. - time.Sleep(time.Duration(dlInfo.WPoStChallengeWindow) * blocktime) + time.Sleep(time.Duration(dlInfo.WPoStChallengeWindow) * blockTime) } - - fmt.Println("shutting down mining") - atomic.AddInt64(&mine, -1) - <-done } diff --git a/itests/kit2/node_opts_nv.go b/itests/kit2/node_opts_nv.go index 05d2c2287..606e3a13f 100644 --- a/itests/kit2/node_opts_nv.go +++ b/itests/kit2/node_opts_nv.go @@ -7,12 +7,12 @@ import ( "github.com/filecoin-project/lotus/node" ) -func LatestActorsAt(upgradeHeight abi.ChainEpoch) node.Option { +func LatestActorsAt(upgradeHeight abi.ChainEpoch) NodeOpt { // Attention: Update this when introducing new actor versions or your tests will be sad return NetworkUpgradeAt(network.Version13, upgradeHeight) } -func NetworkUpgradeAt(version network.Version, upgradeHeight abi.ChainEpoch) node.Option { +func NetworkUpgradeAt(version network.Version, upgradeHeight abi.ChainEpoch) NodeOpt { fullSchedule := stmgr.UpgradeSchedule{{ // prepare for upgrade. Network: network.Version9, @@ -45,7 +45,13 @@ func NetworkUpgradeAt(version network.Version, upgradeHeight abi.ChainEpoch) nod schedule[len(schedule)-1].Height = upgradeHeight } - return node.Override(new(stmgr.UpgradeSchedule), schedule) + return func(opts *nodeOpts) error { + opts.extraNodeOpts = []node.Option{ + node.Override(new(stmgr.UpgradeSchedule), schedule), + } + return nil + } + //return node.Override(new(stmgr.UpgradeSchedule), schedule) } func SDRUpgradeAt(calico, persian abi.ChainEpoch) node.Option { From 4cd0964ab17dbed1f532e88f1115dbbb195e779a Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Tue, 15 Jun 2021 08:27:02 +0200 Subject: [PATCH 028/257] refactor: replace if err with require --- itests/ccupgrade_test.go | 53 +++++++--------------------------------- 1 file changed, 9 insertions(+), 44 deletions(-) diff --git a/itests/ccupgrade_test.go b/itests/ccupgrade_test.go index d90169b70..bdd37f503 100644 --- a/itests/ccupgrade_test.go +++ b/itests/ccupgrade_test.go @@ -19,10 +19,10 @@ func TestCCUpgrade(t *testing.T) { kit2.QuietMiningLogs() for _, height := range []abi.ChainEpoch{ - -1, // before - //162, // while sealing - //530, // after upgrade deal - //5000, // after + -1, // before + 162, // while sealing + 530, // after upgrade deal + 5000, // after } { height := height // make linters happy by copying t.Run(fmt.Sprintf("upgrade-%d", height), func(t *testing.T) { @@ -38,33 +38,6 @@ func runTestCCUpgrade(t *testing.T, upgradeHeight abi.ChainEpoch) { client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), kit2.LatestActorsAt(upgradeHeight)) ens.InterconnectAll().BeginMining(blockTime) - //b := kit.MockMinerBuilder - //n, sn := b(t, []kit.FullNodeOpts{kit.FullNodeWithLatestActorsAt(upgradeHeight)}, kit.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) - //} - //time.Sleep(time.Second) - // - //mine := int64(1) - //done := make(chan struct{}) - //go func() { - // defer close(done) - // for atomic.LoadInt64(&mine) == 1 { - // time.Sleep(blocktime) - // if err := sn[0].MineOne(ctx, kit.MineNext); err != nil { - // t.Error(err) - // } - // } - //}() - maddr, err := miner.ActorAddress(ctx) if err != nil { t.Fatal(err) @@ -76,16 +49,9 @@ func runTestCCUpgrade(t *testing.T, upgradeHeight abi.ChainEpoch) { miner.PledgeSectors(ctx, 1, 0, nil) sl, err := miner.SectorsList(ctx) - if err != nil { - t.Fatal(err) - } - if len(sl) != 1 { - t.Fatal("expected 1 sector") - } - - if sl[0] != CC { - t.Fatal("bad") - } + require.NoError(t, err) + require.Len(t, sl, 1, "expected 1 sector") + require.Equal(t, CC, sl[0], "unexpected sector number") { si, err := client.StateSectorGetInfo(ctx, maddr, CC, types.EmptyTSK) @@ -93,9 +59,8 @@ func runTestCCUpgrade(t *testing.T, upgradeHeight abi.ChainEpoch) { require.Less(t, 50000, int(si.Expiration)) } - if err := miner.SectorMarkForUpgrade(ctx, sl[0]); err != nil { - t.Fatal(err) - } + err = miner.SectorMarkForUpgrade(ctx, sl[0]) + require.NoError(t, err) dh := kit2.NewDealHarness(t, client, miner) From 2d5b798763626198d19282b35b30166269b61724 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Mon, 14 Jun 2021 12:07:50 +0200 Subject: [PATCH 029/257] refactor: cli test with kit2 --- itests/cli_test.go | 13 ++-- itests/kit2/client.go | 146 +++++++++++++++++++++++++++++++++++++++++ itests/kit2/mockcli.go | 141 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 293 insertions(+), 7 deletions(-) create mode 100644 itests/kit2/client.go create mode 100644 itests/kit2/mockcli.go diff --git a/itests/cli_test.go b/itests/cli_test.go index 10e2af15c..8436f189e 100644 --- a/itests/cli_test.go +++ b/itests/cli_test.go @@ -1,22 +1,21 @@ package itests import ( - "context" "os" "testing" "time" "github.com/filecoin-project/lotus/cli" - "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/itests/kit2" ) // TestClient does a basic test to exercise the client CLI commands. func TestClient(t *testing.T) { _ = os.Setenv("BELLMAN_NO_GPU", "1") - kit.QuietMiningLogs() + kit2.QuietMiningLogs() - blocktime := 5 * time.Millisecond - ctx := context.Background() - clientNode, _ := kit.StartOneNodeOneMiner(ctx, t, blocktime) - kit.RunClientTest(t, cli.Commands, clientNode) + blockTime := 5 * time.Millisecond + client, _, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), kit2.ThroughRPC()) + ens.InterconnectAll().BeginMining(blockTime) + kit2.RunClientTest(t, cli.Commands, *client) } diff --git a/itests/kit2/client.go b/itests/kit2/client.go new file mode 100644 index 000000000..247d20836 --- /dev/null +++ b/itests/kit2/client.go @@ -0,0 +1,146 @@ +package kit2 + +import ( + "context" + "fmt" + "io/ioutil" + "math/rand" + "os" + "path/filepath" + "regexp" + "strings" + "testing" + "time" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/specs-actors/v2/actors/builtin" + "github.com/stretchr/testify/require" + lcli "github.com/urfave/cli/v2" +) + +// RunClientTest exercises some of the Client CLI commands +func RunClientTest(t *testing.T, cmds []*lcli.Command, clientNode TestFullNode) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // Create mock CLI + mockCLI := NewMockCLI(ctx, t, cmds) + clientCLI := mockCLI.Client(clientNode.ListenAddr) + + // Get the Miner address + addrs, err := clientNode.StateListMiners(ctx, types.EmptyTSK) + require.NoError(t, err) + require.Len(t, addrs, 1) + + minerAddr := addrs[0] + fmt.Println("Miner:", minerAddr) + + // client query-ask + out := clientCLI.RunCmd("client", "query-ask", minerAddr.String()) + require.Regexp(t, regexp.MustCompile("Ask:"), out) + + // Create a deal (non-interactive) + // client deal --start-epoch= 1000000attofil + res, _, _, err := CreateImportFile(ctx, clientNode, 1, 0) + + require.NoError(t, err) + startEpoch := fmt.Sprintf("--start-epoch=%d", 2<<12) + dataCid := res.Root + price := "1000000attofil" + duration := fmt.Sprintf("%d", build.MinDealDuration) + out = clientCLI.RunCmd("client", "deal", startEpoch, dataCid.String(), minerAddr.String(), price, duration) + fmt.Println("client deal", out) + + // Create a deal (interactive) + // client deal + // + // (in days) + // + // "no" (verified Client) + // "yes" (confirm deal) + res, _, _, err = CreateImportFile(ctx, clientNode, 2, 0) + require.NoError(t, err) + dataCid2 := res.Root + duration = fmt.Sprintf("%d", build.MinDealDuration/builtin.EpochsInDay) + cmd := []string{"client", "deal"} + interactiveCmds := []string{ + dataCid2.String(), + duration, + minerAddr.String(), + "no", + "yes", + } + out = clientCLI.RunInteractiveCmd(cmd, interactiveCmds) + fmt.Println("client deal:\n", out) + + // Wait for provider to start sealing deal + dealStatus := "" + for { + // client list-deals + out = clientCLI.RunCmd("client", "list-deals") + fmt.Println("list-deals:\n", out) + + lines := strings.Split(out, "\n") + require.GreaterOrEqual(t, len(lines), 2) + re := regexp.MustCompile(`\s+`) + parts := re.Split(lines[1], -1) + if len(parts) < 4 { + require.Fail(t, "bad list-deals output format") + } + dealStatus = parts[3] + fmt.Println(" Deal status:", dealStatus) + + st := CategorizeDealState(dealStatus) + require.NotEqual(t, TestDealStateFailed, st) + if st == TestDealStateComplete { + break + } + + time.Sleep(time.Second) + } + + // Retrieve the first file from the Miner + // client retrieve + tmpdir, err := ioutil.TempDir(os.TempDir(), "test-cli-Client") + require.NoError(t, err) + path := filepath.Join(tmpdir, "outfile.dat") + out = clientCLI.RunCmd("client", "retrieve", dataCid.String(), path) + fmt.Println("retrieve:\n", out) + require.Regexp(t, regexp.MustCompile("Success"), out) +} + +func CreateImportFile(ctx context.Context, client api.FullNode, rseed int, size int) (res *api.ImportRes, path string, data []byte, err error) { + data, path, err = createRandomFile(rseed, size) + if err != nil { + return nil, "", nil, err + } + + res, err = client.ClientImport(ctx, api.FileRef{Path: path}) + if err != nil { + return nil, "", nil, err + } + return res, path, data, nil +} + +func createRandomFile(rseed, size int) ([]byte, string, error) { + if size == 0 { + size = 1600 + } + data := make([]byte, size) + rand.New(rand.NewSource(int64(rseed))).Read(data) + + dir, err := ioutil.TempDir(os.TempDir(), "test-make-deal-") + if err != nil { + return nil, "", err + } + + path := filepath.Join(dir, "sourcefile.dat") + err = ioutil.WriteFile(path, data, 0644) + if err != nil { + return nil, "", err + } + + return data, path, nil +} diff --git a/itests/kit2/mockcli.go b/itests/kit2/mockcli.go new file mode 100644 index 000000000..592c97333 --- /dev/null +++ b/itests/kit2/mockcli.go @@ -0,0 +1,141 @@ +package kit2 + +import ( + "bytes" + "context" + "flag" + "strings" + "testing" + + "github.com/multiformats/go-multiaddr" + "github.com/stretchr/testify/require" + lcli "github.com/urfave/cli/v2" +) + +type MockCLI struct { + t *testing.T + cmds []*lcli.Command + cctx *lcli.Context + out *bytes.Buffer +} + +func NewMockCLI(ctx context.Context, t *testing.T, cmds []*lcli.Command) *MockCLI { + // Create a CLI App with an --api-url flag so that we can specify which node + // the command should be executed against + app := &lcli.App{ + Flags: []lcli.Flag{ + &lcli.StringFlag{ + Name: "api-url", + Hidden: true, + }, + }, + Commands: cmds, + } + + var out bytes.Buffer + app.Writer = &out + app.Setup() + + cctx := lcli.NewContext(app, &flag.FlagSet{}, nil) + cctx.Context = ctx + return &MockCLI{t: t, cmds: cmds, cctx: cctx, out: &out} +} + +func (c *MockCLI) Client(addr multiaddr.Multiaddr) *MockCLIClient { + return &MockCLIClient{t: c.t, cmds: c.cmds, addr: addr, cctx: c.cctx, out: c.out} +} + +// MockCLIClient runs commands against a particular node +type MockCLIClient struct { + t *testing.T + cmds []*lcli.Command + addr multiaddr.Multiaddr + cctx *lcli.Context + out *bytes.Buffer +} + +func (c *MockCLIClient) RunCmd(input ...string) string { + out, err := c.RunCmdRaw(input...) + require.NoError(c.t, err, "output:\n%s", out) + + return out +} + +// Given an input, find the corresponding command or sub-command. +// eg "paych add-funds" +func (c *MockCLIClient) cmdByNameSub(input []string) (*lcli.Command, []string) { + name := input[0] + for _, cmd := range c.cmds { + if cmd.Name == name { + return c.findSubcommand(cmd, input[1:]) + } + } + return nil, []string{} +} + +func (c *MockCLIClient) findSubcommand(cmd *lcli.Command, input []string) (*lcli.Command, []string) { + // If there are no sub-commands, return the current command + if len(cmd.Subcommands) == 0 { + return cmd, input + } + + // Check each sub-command for a match against the name + subName := input[0] + for _, subCmd := range cmd.Subcommands { + if subCmd.Name == subName { + // Found a match, recursively search for sub-commands + return c.findSubcommand(subCmd, input[1:]) + } + } + return nil, []string{} +} + +func (c *MockCLIClient) RunCmdRaw(input ...string) (string, error) { + cmd, input := c.cmdByNameSub(input) + if cmd == nil { + panic("Could not find command " + input[0] + " " + input[1]) + } + + // prepend --api-url= + apiFlag := "--api-url=" + c.addr.String() + input = append([]string{apiFlag}, input...) + + fs := c.flagSet(cmd) + err := fs.Parse(input) + require.NoError(c.t, err) + + err = cmd.Action(lcli.NewContext(c.cctx.App, fs, c.cctx)) + + // Get the output + str := strings.TrimSpace(c.out.String()) + c.out.Reset() + return str, err +} + +func (c *MockCLIClient) flagSet(cmd *lcli.Command) *flag.FlagSet { + // Apply app level flags (so we can process --api-url flag) + fs := &flag.FlagSet{} + for _, f := range c.cctx.App.Flags { + err := f.Apply(fs) + if err != nil { + c.t.Fatal(err) + } + } + // Apply command level flags + for _, f := range cmd.Flags { + err := f.Apply(fs) + if err != nil { + c.t.Fatal(err) + } + } + return fs +} + +func (c *MockCLIClient) RunInteractiveCmd(cmd []string, interactive []string) string { + c.toStdin(strings.Join(interactive, "\n") + "\n") + return c.RunCmd(cmd...) +} + +func (c *MockCLIClient) toStdin(s string) { + c.cctx.App.Metadata["stdin"] = bytes.NewBufferString(s) +} From 16cad0e01a2a7e1d43c54214d7d8239bcb450a4f Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Tue, 15 Jun 2021 11:46:32 +0200 Subject: [PATCH 030/257] refactor: convert gateway tests to kit2 --- itests/gateway_test.go | 102 +++++++++++++++++---------------------- itests/kit2/ensemble.go | 5 ++ itests/kit2/funds.go | 2 +- itests/kit2/node_opts.go | 14 ++++++ itests/multisig_test.go | 2 +- 5 files changed, 64 insertions(+), 61 deletions(-) diff --git a/itests/gateway_test.go b/itests/gateway_test.go index 7f1b70f2d..20b0add6a 100644 --- a/itests/gateway_test.go +++ b/itests/gateway_test.go @@ -9,7 +9,6 @@ import ( "testing" "time" - "github.com/filecoin-project/lotus/chain/stmgr" "github.com/stretchr/testify/require" "golang.org/x/xerrors" @@ -21,10 +20,11 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api/client" "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/cli" "github.com/filecoin-project/lotus/gateway" - "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/itests/kit2" "github.com/filecoin-project/lotus/node" init2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/init" @@ -46,7 +46,7 @@ func init() { // node that is connected through a gateway to a full API node func TestGatewayWalletMsig(t *testing.T) { _ = os.Setenv("BELLMAN_NO_GPU", "1") - kit.QuietMiningLogs() + kit2.QuietMiningLogs() blocktime := 5 * time.Millisecond ctx := context.Background() @@ -180,7 +180,7 @@ func TestGatewayWalletMsig(t *testing.T) { // on a lite node that is connected through a gateway to a full API node func TestGatewayMsigCLI(t *testing.T) { _ = os.Setenv("BELLMAN_NO_GPU", "1") - kit.QuietMiningLogs() + kit2.QuietMiningLogs() blocktime := 5 * time.Millisecond ctx := context.Background() @@ -193,7 +193,7 @@ func TestGatewayMsigCLI(t *testing.T) { func TestGatewayDealFlow(t *testing.T) { _ = os.Setenv("BELLMAN_NO_GPU", "1") - kit.QuietMiningLogs() + kit2.QuietMiningLogs() blocktime := 5 * time.Millisecond ctx := context.Background() @@ -205,26 +205,27 @@ func TestGatewayDealFlow(t *testing.T) { // so that the deal starts sealing in time dealStartEpoch := abi.ChainEpoch(2 << 12) - dh := kit.NewDealHarness(t, nodes.lite, nodes.miner) - dh.MakeFullDeal(ctx, 6, false, false, dealStartEpoch) + dh := kit2.NewDealHarness(t, &nodes.lite, &nodes.miner) + dealCid, res, _ := dh.MakeOnlineDeal(ctx, 6, false, dealStartEpoch) + dh.PerformRetrieval(ctx, dealCid, res.Root, false) } func TestGatewayCLIDealFlow(t *testing.T) { _ = os.Setenv("BELLMAN_NO_GPU", "1") - kit.QuietMiningLogs() + kit2.QuietMiningLogs() blocktime := 5 * time.Millisecond ctx := context.Background() nodes := startNodesWithFunds(ctx, t, blocktime, maxLookbackCap, maxStateWaitLookbackLimit) defer nodes.closer() - kit.RunClientTest(t, cli.Commands, nodes.lite) + kit2.RunClientTest(t, cli.Commands, nodes.lite) } type testNodes struct { - lite kit.TestFullNode - full kit.TestFullNode - miner kit.TestMiner + lite kit2.TestFullNode + full kit2.TestFullNode + miner kit2.TestMiner closer jsonrpc.ClientCloser } @@ -261,66 +262,49 @@ func startNodes( ) *testNodes { var closer jsonrpc.ClientCloser - // Create one miner and two full nodes. + var ( + full kit2.TestFullNode + lite kit2.TestFullNode + miner kit2.TestMiner + ) + + // - Create one full node and one lite node // - Put a gateway server in front of full node 1 // - Start full node 2 in lite mode // - Connect lite node -> gateway server -> full node - opts := append( - // Full node - kit.OneFull, - // Lite node - kit.FullNodeOpts{ - Lite: true, - Opts: func(nodes []kit.TestFullNode) node.Option { - fullNode := nodes[0] - // Create a gateway server in front of the full node - gwapi := gateway.NewNode(fullNode, lookbackCap, stateWaitLookbackLimit) - handler, err := gateway.Handler(gwapi) - require.NoError(t, err) + var liteOptBuilder kit2.OptBuilder + liteOptBuilder = func(nodes []*kit2.TestFullNode) node.Option { + fullNode := nodes[0] - srv, _ := kit.CreateRPCServer(t, handler) + // Create a gateway server in front of the full node + gwapi := gateway.NewNode(fullNode, lookbackCap, stateWaitLookbackLimit) + handler, err := gateway.Handler(gwapi) + require.NoError(t, err) - // Create a gateway client API that connects to the gateway server - var gapi api.Gateway - gapi, closer, err = client.NewGatewayRPCV1(ctx, "ws://"+srv.Listener.Addr().String()+"/rpc/v1", nil) - require.NoError(t, err) + srv, _ := kit2.CreateRPCServer(t, handler) - // Provide the gateway API to dependency injection - return node.Override(new(api.Gateway), gapi) - }, - }, - ) - n, sn := kit.RPCMockMinerBuilder(t, opts, kit.OneMiner) + // Create a gateway client API that connects to the gateway server + var gapi api.Gateway + gapi, closer, err = client.NewGatewayRPCV1(ctx, "ws://"+srv.Listener.Addr().String()+"/rpc/v1", nil) + require.NoError(t, err) - full := n[0] - lite := n[1] - miner := sn[0] + // Provide the gateway API to dependency injection + return node.Override(new(api.Gateway), gapi) + } - // Get the listener address for the full node - fullAddr, err := full.NetAddrsListen(ctx) - require.NoError(t, err) - - // Connect the miner and the full node - err = miner.NetConnect(ctx, fullAddr) - require.NoError(t, err) - - // Connect the miner and the lite node (so that the lite node can send - // data to the miner) - liteAddr, err := lite.NetAddrsListen(ctx) - require.NoError(t, err) - err = miner.NetConnect(ctx, liteAddr) - require.NoError(t, err) - - // Start mining blocks - bm := kit.NewBlockMiner(t, miner) - bm.MineBlocks(ctx, blocktime) - t.Cleanup(bm.Stop) + kit2.NewEnsemble(t, kit2.MockProofs()). + FullNode(&full). + FullNode(&lite, kit2.LiteNode(), kit2.ThroughRPC(), kit2.AddOptBuilder(liteOptBuilder)). + Miner(&miner, &full). + Start(). + InterconnectAll(). + BeginMining(blocktime) return &testNodes{lite: lite, full: full, miner: miner, closer: closer} } -func sendFunds(ctx context.Context, fromNode kit.TestFullNode, fromAddr address.Address, toAddr address.Address, amt types.BigInt) error { +func sendFunds(ctx context.Context, fromNode kit2.TestFullNode, fromAddr address.Address, toAddr address.Address, amt types.BigInt) error { msg := &types.Message{ From: fromAddr, To: toAddr, diff --git a/itests/kit2/ensemble.go b/itests/kit2/ensemble.go index 5d12c83e1..d74649c26 100644 --- a/itests/kit2/ensemble.go +++ b/itests/kit2/ensemble.go @@ -279,6 +279,11 @@ func (n *Ensemble) Start() *Ensemble { ) } + // Call option builders, passing active nodes as the parameter + for _, bopt := range full.options.optBuilders { + opts = append(opts, bopt(n.active.fullnodes)) + } + // Construct the full node. stop, err := node.New(ctx, opts...) diff --git a/itests/kit2/funds.go b/itests/kit2/funds.go index da37ae2ba..fc4a6c294 100644 --- a/itests/kit2/funds.go +++ b/itests/kit2/funds.go @@ -30,5 +30,5 @@ func SendFunds(ctx context.Context, t *testing.T, sender TestFullNode, recipient res, err := sender.StateWaitMsg(ctx, sm.Cid(), 3, api.LookbackNoLimit, true) require.NoError(t, err) - require.Equal(t, 0, res.Receipt.ExitCode, "did not successfully send funds") + require.EqualValues(t, 0, res.Receipt.ExitCode, "did not successfully send funds") } diff --git a/itests/kit2/node_opts.go b/itests/kit2/node_opts.go index 59d5454df..bba78262a 100644 --- a/itests/kit2/node_opts.go +++ b/itests/kit2/node_opts.go @@ -23,6 +23,7 @@ type nodeOpts struct { rpc bool ownerKey *wallet.Key extraNodeOpts []node.Option + optBuilders []OptBuilder } // DefaultNodeOpts are the default options that will be applied to test nodes. @@ -31,6 +32,10 @@ var DefaultNodeOpts = nodeOpts{ sectors: DefaultPresealsPerBootstrapMiner, } +// OptBuilder is used to create an option after some other node is already +// active. Takes all active nodes as a parameter. +type OptBuilder func(activeNodes []*TestFullNode) node.Option + // NodeOpt is a functional option for test nodes. type NodeOpt func(opts *nodeOpts) error @@ -87,3 +92,12 @@ func ConstructorOpts(extra ...node.Option) NodeOpt { return nil } } + +// AddOptBuilder adds an OptionBuilder to a node. It is used to add Lotus node +// constructor options after some nodes are already active. +func AddOptBuilder(optBuilder OptBuilder) NodeOpt { + return func(opts *nodeOpts) error { + opts.optBuilders = append(opts.optBuilders, optBuilder) + return nil + } +} diff --git a/itests/multisig_test.go b/itests/multisig_test.go index cc2b38c30..f5df0be1a 100644 --- a/itests/multisig_test.go +++ b/itests/multisig_test.go @@ -22,7 +22,7 @@ func TestMultisig(t *testing.T) { kit2.QuietMiningLogs() blockTime := 5 * time.Millisecond - client, _, ens := kit2.EnsembleMinimal(t, kit2.MockProofs()) + client, _, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), kit2.ThroughRPC()) ens.InterconnectAll().BeginMining(blockTime) runMultisigTests(t, *client) From 5ce482b817befc252830ea80acd35cf269b7b977 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Tue, 15 Jun 2021 13:53:41 +0200 Subject: [PATCH 031/257] refactor: change network upgrade NodeOption to node.Option --- itests/ccupgrade_test.go | 3 ++- itests/kit2/node_opts_nv.go | 12 +++--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/itests/ccupgrade_test.go b/itests/ccupgrade_test.go index bdd37f503..050e6fac1 100644 --- a/itests/ccupgrade_test.go +++ b/itests/ccupgrade_test.go @@ -35,7 +35,8 @@ func runTestCCUpgrade(t *testing.T, upgradeHeight abi.ChainEpoch) { ctx := context.Background() blockTime := 5 * time.Millisecond - client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), kit2.LatestActorsAt(upgradeHeight)) + opts := kit2.ConstructorOpts(kit2.LatestActorsAt(upgradeHeight)) + client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), opts) ens.InterconnectAll().BeginMining(blockTime) maddr, err := miner.ActorAddress(ctx) diff --git a/itests/kit2/node_opts_nv.go b/itests/kit2/node_opts_nv.go index 606e3a13f..05d2c2287 100644 --- a/itests/kit2/node_opts_nv.go +++ b/itests/kit2/node_opts_nv.go @@ -7,12 +7,12 @@ import ( "github.com/filecoin-project/lotus/node" ) -func LatestActorsAt(upgradeHeight abi.ChainEpoch) NodeOpt { +func LatestActorsAt(upgradeHeight abi.ChainEpoch) node.Option { // Attention: Update this when introducing new actor versions or your tests will be sad return NetworkUpgradeAt(network.Version13, upgradeHeight) } -func NetworkUpgradeAt(version network.Version, upgradeHeight abi.ChainEpoch) NodeOpt { +func NetworkUpgradeAt(version network.Version, upgradeHeight abi.ChainEpoch) node.Option { fullSchedule := stmgr.UpgradeSchedule{{ // prepare for upgrade. Network: network.Version9, @@ -45,13 +45,7 @@ func NetworkUpgradeAt(version network.Version, upgradeHeight abi.ChainEpoch) Nod schedule[len(schedule)-1].Height = upgradeHeight } - return func(opts *nodeOpts) error { - opts.extraNodeOpts = []node.Option{ - node.Override(new(stmgr.UpgradeSchedule), schedule), - } - return nil - } - //return node.Override(new(stmgr.UpgradeSchedule), schedule) + return node.Override(new(stmgr.UpgradeSchedule), schedule) } func SDRUpgradeAt(calico, persian abi.ChainEpoch) node.Option { From 2267d15a0f5bb907e9039ab1ecc56b490fe3b6d6 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Tue, 15 Jun 2021 14:37:03 +0200 Subject: [PATCH 032/257] refactor: update paych cli tests to use kit2 --- itests/kit2/funds.go | 2 +- itests/paych_cli_test.go | 87 ++++++++++++++++++++++++++-------------- 2 files changed, 59 insertions(+), 30 deletions(-) diff --git a/itests/kit2/funds.go b/itests/kit2/funds.go index da37ae2ba..fc4a6c294 100644 --- a/itests/kit2/funds.go +++ b/itests/kit2/funds.go @@ -30,5 +30,5 @@ func SendFunds(ctx context.Context, t *testing.T, sender TestFullNode, recipient res, err := sender.StateWaitMsg(ctx, sm.Cid(), 3, api.LookbackNoLimit, true) require.NoError(t, err) - require.Equal(t, 0, res.Receipt.ExitCode, "did not successfully send funds") + require.EqualValues(t, 0, res.Receipt.ExitCode, "did not successfully send funds") } diff --git a/itests/paych_cli_test.go b/itests/paych_cli_test.go index 373b6f43b..2450828d3 100644 --- a/itests/paych_cli_test.go +++ b/itests/paych_cli_test.go @@ -11,7 +11,7 @@ import ( "time" "github.com/filecoin-project/lotus/cli" - "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/itests/kit2" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" @@ -37,18 +37,19 @@ func init() { // commands func TestPaymentChannelsBasic(t *testing.T) { _ = os.Setenv("BELLMAN_NO_GPU", "1") - kit.QuietMiningLogs() + kit2.QuietMiningLogs() blocktime := 5 * time.Millisecond ctx := context.Background() - nodes, addrs := kit.StartTwoNodesOneMiner(ctx, t, blocktime) - paymentCreator := nodes[0] - paymentReceiver := nodes[1] - creatorAddr := addrs[0] - receiverAddr := addrs[1] + + var ( + paymentCreator kit2.TestFullNode + paymentReceiver kit2.TestFullNode + ) + creatorAddr, receiverAddr := startPaychCreatorReceiverMiner(ctx, t, &paymentCreator, &paymentReceiver, blocktime) // Create mock CLI - mockCLI := kit.NewMockCLI(ctx, t, cli.Commands) + mockCLI := kit2.NewMockCLI(ctx, t, cli.Commands) creatorCLI := mockCLI.Client(paymentCreator.ListenAddr) receiverCLI := mockCLI.Client(paymentReceiver.ListenAddr) @@ -70,12 +71,16 @@ func TestPaymentChannelsBasic(t *testing.T) { // creator: paych settle creatorCLI.RunCmd("paych", "settle", chAddr.String()) + t.Log("wait for chain to reach settle height") + // Wait for the chain to reach the settle height chState := getPaychState(ctx, t, paymentReceiver, chAddr) sa, err := chState.SettlingAt() require.NoError(t, err) waitForHeight(ctx, t, paymentReceiver, sa) + t.Log("settle height reached") + // receiver: paych collect receiverCLI.RunCmd("paych", "collect", chAddr.String()) } @@ -89,17 +94,18 @@ type voucherSpec struct { // TestPaymentChannelStatus tests the payment channel status CLI command func TestPaymentChannelStatus(t *testing.T) { _ = os.Setenv("BELLMAN_NO_GPU", "1") - kit.QuietMiningLogs() + kit2.QuietMiningLogs() blocktime := 5 * time.Millisecond ctx := context.Background() - nodes, addrs := kit.StartTwoNodesOneMiner(ctx, t, blocktime) - paymentCreator := nodes[0] - creatorAddr := addrs[0] - receiverAddr := addrs[1] + var ( + paymentCreator kit2.TestFullNode + paymentReceiver kit2.TestFullNode + ) + creatorAddr, receiverAddr := startPaychCreatorReceiverMiner(ctx, t, &paymentCreator, &paymentReceiver, blocktime) // Create mock CLI - mockCLI := kit.NewMockCLI(ctx, t, cli.Commands) + mockCLI := kit2.NewMockCLI(ctx, t, cli.Commands) creatorCLI := mockCLI.Client(paymentCreator.ListenAddr) // creator: paych status-by-from-to @@ -168,18 +174,18 @@ func TestPaymentChannelStatus(t *testing.T) { // channel voucher commands func TestPaymentChannelVouchers(t *testing.T) { _ = os.Setenv("BELLMAN_NO_GPU", "1") - kit.QuietMiningLogs() + kit2.QuietMiningLogs() blocktime := 5 * time.Millisecond ctx := context.Background() - nodes, addrs := kit.StartTwoNodesOneMiner(ctx, t, blocktime) - paymentCreator := nodes[0] - paymentReceiver := nodes[1] - creatorAddr := addrs[0] - receiverAddr := addrs[1] + var ( + paymentCreator kit2.TestFullNode + paymentReceiver kit2.TestFullNode + ) + creatorAddr, receiverAddr := startPaychCreatorReceiverMiner(ctx, t, &paymentCreator, &paymentReceiver, blocktime) // Create mock CLI - mockCLI := kit.NewMockCLI(ctx, t, cli.Commands) + mockCLI := kit2.NewMockCLI(ctx, t, cli.Commands) creatorCLI := mockCLI.Client(paymentCreator.ListenAddr) receiverCLI := mockCLI.Client(paymentReceiver.ListenAddr) @@ -300,17 +306,18 @@ 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") - kit.QuietMiningLogs() + kit2.QuietMiningLogs() blocktime := 5 * time.Millisecond ctx := context.Background() - nodes, addrs := kit.StartTwoNodesOneMiner(ctx, t, blocktime) - paymentCreator := nodes[0] - creatorAddr := addrs[0] - receiverAddr := addrs[1] + var ( + paymentCreator kit2.TestFullNode + paymentReceiver kit2.TestFullNode + ) + creatorAddr, receiverAddr := startPaychCreatorReceiverMiner(ctx, t, &paymentCreator, &paymentReceiver, blocktime) // Create mock CLI - mockCLI := kit.NewMockCLI(ctx, t, cli.Commands) + mockCLI := kit2.NewMockCLI(ctx, t, cli.Commands) creatorCLI := mockCLI.Client(paymentCreator.ListenAddr) // creator: paych add-funds @@ -378,7 +385,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 kit.TestFullNode, height abi.ChainEpoch) { +func waitForHeight(ctx context.Context, t *testing.T, node kit2.TestFullNode, 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 { @@ -396,7 +403,7 @@ func waitForHeight(ctx context.Context, t *testing.T, node kit.TestFullNode, hei } // getPaychState gets the state of the payment channel with the given address -func getPaychState(ctx context.Context, t *testing.T, node kit.TestFullNode, chAddr address.Address) paych.State { +func getPaychState(ctx context.Context, t *testing.T, node kit2.TestFullNode, chAddr address.Address) paych.State { act, err := node.StateGetActor(ctx, chAddr, types.EmptyTSK) require.NoError(t, err) @@ -406,3 +413,25 @@ func getPaychState(ctx context.Context, t *testing.T, node kit.TestFullNode, chA return chState } + +func startPaychCreatorReceiverMiner(ctx context.Context, t *testing.T, paymentCreator *kit2.TestFullNode, paymentReceiver *kit2.TestFullNode, blocktime time.Duration) (address.Address, address.Address) { + var miner kit2.TestMiner + opts := kit2.ThroughRPC() + kit2.NewEnsemble(t, kit2.MockProofs()). + FullNode(paymentCreator, opts). + FullNode(paymentReceiver, opts). + Miner(&miner, paymentCreator). + Start(). + InterconnectAll(). + BeginMining(blocktime) + + // Send some funds to the second node + receiverAddr, err := paymentReceiver.WalletDefaultAddress(ctx) + require.NoError(t, err) + kit2.SendFunds(ctx, t, *paymentCreator, receiverAddr, abi.NewTokenAmount(1e18)) + + // Get the first node's address + creatorAddr, err := paymentCreator.WalletDefaultAddress(ctx) + require.NoError(t, err) + return creatorAddr, receiverAddr +} From cd53942525799a473d2845d110413a9d67d41cc4 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Tue, 15 Jun 2021 15:38:09 +0200 Subject: [PATCH 033/257] refactor: batch deal test to use kit2 --- itests/batch_deal_test.go | 69 ++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 40 deletions(-) diff --git a/itests/batch_deal_test.go b/itests/batch_deal_test.go index 9676dffcc..9cc4d7ac1 100644 --- a/itests/batch_deal_test.go +++ b/itests/batch_deal_test.go @@ -10,16 +10,15 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/extern/storage-sealing/sealiface" - "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/itests/kit2" "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) { - kit.QuietMiningLogs() + kit2.QuietMiningLogs() var ( blockTime = 10 * time.Millisecond @@ -32,50 +31,40 @@ func TestBatchDealInput(t *testing.T) { run := func(piece, deals, expectSectors int) func(t *testing.T) { return func(t *testing.T) { + ctx := context.Background() + publishPeriod := 10 * time.Second maxDealsPerMsg := uint64(deals) // Set max deals per publish deals message to maxDealsPerMsg - minerDef := []kit.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: 2, - MaxSealingSectors: 1, - MaxSealingSectorsForDeals: 3, - AlwaysKeepUnsealedCopy: true, - WaitDealsDelay: time.Hour, - }, nil + opts := kit2.ConstructorOpts(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: 2, + MaxSealingSectors: 1, + MaxSealingSectorsForDeals: 3, + AlwaysKeepUnsealedCopy: true, + WaitDealsDelay: time.Hour, }, nil - }), - ), - Preseal: kit.PresealGenesis, - }} - - // Create a connect client and miner node - n, sn := kit.MockMinerBuilder(t, kit.OneFull, minerDef) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - - blockMiner := kit.ConnectAndStartMining(t, blockTime, miner, client) - t.Cleanup(blockMiner.Stop) - - dh := kit.NewDealHarness(t, client, miner) - ctx := context.Background() + }, nil + }), + )) + client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), opts) + ens.InterconnectAll().BeginMining(blockTime) + dh := kit2.NewDealHarness(t, client, miner) err := miner.MarketSetAsk(ctx, big.Zero(), big.Zero(), 200, 128, 32<<30) require.NoError(t, err) checkNoPadding := func() { - sl, err := sn[0].SectorsList(ctx) + sl, err := miner.SectorsList(ctx) require.NoError(t, err) sort.Slice(sl, func(i, j int) bool { @@ -83,7 +72,7 @@ func TestBatchDealInput(t *testing.T) { }) for _, snum := range sl { - si, err := sn[0].SectorsStatus(ctx, snum, false) + si, err := miner.SectorsStatus(ctx, snum, false) require.NoError(t, err) // fmt.Printf("S %d: %+v %s\n", snum, si.Deals, si.State) @@ -98,7 +87,7 @@ func TestBatchDealInput(t *testing.T) { // Starts a deal and waits until it's published runDealTillSeal := func(rseed int) { - res, _, _, err := kit.CreateImportFile(ctx, client, rseed, piece) + res, _, _, err := kit2.CreateImportFile(ctx, client, rseed, piece) require.NoError(t, err) deal := dh.StartDeal(ctx, res.Root, false, dealStartEpoch) @@ -122,7 +111,7 @@ func TestBatchDealInput(t *testing.T) { checkNoPadding() - sl, err := sn[0].SectorsList(ctx) + sl, err := miner.SectorsList(ctx) require.NoError(t, err) require.Equal(t, len(sl), expectSectors) } From cd903bec5e05f236109a79342f9929d3de4f11d9 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Tue, 15 Jun 2021 17:24:57 +0200 Subject: [PATCH 034/257] refactor: sdr upgrade test to use kit2 --- itests/sdr_upgrade_test.go | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/itests/sdr_upgrade_test.go b/itests/sdr_upgrade_test.go index dfb4284b9..008c2ce61 100644 --- a/itests/sdr_upgrade_test.go +++ b/itests/sdr_upgrade_test.go @@ -10,15 +10,14 @@ import ( "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/itests/kit" + "github.com/filecoin-project/lotus/itests/kit2" 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) { - kit.QuietMiningLogs() + kit2.QuietMiningLogs() // oldDelay := policy.GetPreCommitChallengeDelay() // policy.SetPreCommitChallengeDelay(5) @@ -31,18 +30,10 @@ func TestSDRUpgrade(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - n, sn := kit.MockMinerBuilder(t, []kit.FullNodeOpts{kit.FullNodeWithSDRAt(500, 1000)}, kit.OneMiner) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] + opts := kit2.ConstructorOpts(kit2.SDRUpgradeAt(500, 1000)) + client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), opts) + ens.InterconnectAll() - 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{}) @@ -53,7 +44,7 @@ func TestSDRUpgrade(t *testing.T) { 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) { + if err := miner.MineOne(ctx, bminer.MineReq{Done: func(bool, abi.ChainEpoch, error) { }}); err != nil { t.Error(err) @@ -88,7 +79,7 @@ func TestSDRUpgrade(t *testing.T) { }() // before. - kit.PledgeSectors(t, ctx, miner, 9, 0, pledge) + miner.PledgeSectors(ctx, 9, 0, pledge) s, err := miner.SectorsList(ctx) require.NoError(t, err) From ad8b6baa0327293f09d5d81abd5d8a4ec1fc1dd2 Mon Sep 17 00:00:00 2001 From: Cory Schwartz Date: Tue, 15 Jun 2021 11:58:59 -0700 Subject: [PATCH 035/257] bring appimage to v1.10.0-rc3 --- AppDir/usr/share/icons/icon.svg | 1 + AppImageBuilder.yml | 73 +++++++++++++++++++++++++++++++++ Makefile | 17 +++----- scripts/build-bundle.sh | 3 ++ scripts/publish-release.sh | 14 ++++++- 5 files changed, 95 insertions(+), 13 deletions(-) create mode 100644 AppDir/usr/share/icons/icon.svg create mode 100644 AppImageBuilder.yml diff --git a/AppDir/usr/share/icons/icon.svg b/AppDir/usr/share/icons/icon.svg new file mode 100644 index 000000000..da992296a --- /dev/null +++ b/AppDir/usr/share/icons/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/AppImageBuilder.yml b/AppImageBuilder.yml new file mode 100644 index 000000000..19c74e4a2 --- /dev/null +++ b/AppImageBuilder.yml @@ -0,0 +1,73 @@ +version: 1 +AppDir: + path: ./AppDir + app_info: + id: io.filecoin.lotus + name: Lotus + icon: icon + version: latest + exec: usr/bin/lotus + exec_args: $@ + apt: + arch: amd64 + allow_unauthenticated: true + sources: + - sourceline: deb http://archive.ubuntu.com/ubuntu/ focal main restricted + - sourceline: deb http://archive.ubuntu.com/ubuntu/ focal-updates main restricted + - sourceline: deb http://archive.ubuntu.com/ubuntu/ focal universe + - sourceline: deb http://archive.ubuntu.com/ubuntu/ focal-updates universe + - sourceline: deb http://archive.ubuntu.com/ubuntu/ focal multiverse + - sourceline: deb http://archive.ubuntu.com/ubuntu/ focal-updates multiverse + - sourceline: deb http://archive.ubuntu.com/ubuntu/ focal-backports main restricted + universe multiverse + - sourceline: deb http://security.ubuntu.com/ubuntu focal-security main restricted + - sourceline: deb http://security.ubuntu.com/ubuntu focal-security universe + - sourceline: deb http://security.ubuntu.com/ubuntu focal-security multiverse + - sourceline: deb https://cli-assets.heroku.com/apt ./ + - sourceline: deb http://ppa.launchpad.net/openjdk-r/ppa/ubuntu focal main + - sourceline: deb http://ppa.launchpad.net/git-core/ppa/ubuntu focal main + - sourceline: deb http://archive.canonical.com/ubuntu focal partner + include: + - ocl-icd-libopencl1 + - libhwloc15 + exclude: [] + files: + include: + - /usr/lib/x86_64-linux-gnu/libgcc_s.so.1 + - /usr/lib/x86_64-linux-gnu/libpthread-2.31.so + - /usr/lib/x86_64-linux-gnu/libm-2.31.so + - /usr/lib/x86_64-linux-gnu/libdl-2.31.so + - /usr/lib/x86_64-linux-gnu/libc-2.31.so + - /usr/lib/x86_64-linux-gnu/libudev.so.1.6.17 + exclude: + - usr/share/man + - usr/share/doc/*/README.* + - usr/share/doc/*/changelog.* + - usr/share/doc/*/NEWS.* + - usr/share/doc/*/TODO.* + test: + fedora: + image: appimagecrafters/tests-env:fedora-30 + command: ./AppRun + use_host_x: true + debian: + image: appimagecrafters/tests-env:debian-stable + command: ./AppRun + use_host_x: true + arch: + image: appimagecrafters/tests-env:archlinux-latest + command: ./AppRun + use_host_x: true + centos: + image: appimagecrafters/tests-env:centos-7 + command: ./AppRun + use_host_x: true + ubuntu: + image: appimagecrafters/tests-env:ubuntu-xenial + command: ./AppRun + use_host_x: true +AppImage: + arch: x86_64 + update-information: guess + sign-key: None + diff --git a/Makefile b/Makefile index 93b647942..59e1a1cd4 100644 --- a/Makefile +++ b/Makefile @@ -56,6 +56,12 @@ build/.update-modules: # end git modules +build/rice-box.go: + go run github.com/GeertJohan/go.rice/rice embed-go -i ./build + +BUILD_DEPS+=build/rice-box.go +CLEAN+=build/rice-box.go + ## MAIN BINARIES CLEAN+=build/.update-modules @@ -84,7 +90,6 @@ butterflynet: build-devnets lotus: $(BUILD_DEPS) rm -f lotus go build $(GOFLAGS) -o lotus ./cmd/lotus - go run github.com/GeertJohan/go.rice/rice append --exec lotus -i ./build .PHONY: lotus BINS+=lotus @@ -92,21 +97,18 @@ BINS+=lotus lotus-miner: $(BUILD_DEPS) rm -f lotus-miner go build $(GOFLAGS) -o lotus-miner ./cmd/lotus-storage-miner - go run github.com/GeertJohan/go.rice/rice append --exec lotus-miner -i ./build .PHONY: lotus-miner BINS+=lotus-miner lotus-worker: $(BUILD_DEPS) rm -f lotus-worker go build $(GOFLAGS) -o lotus-worker ./cmd/lotus-seal-worker - go run github.com/GeertJohan/go.rice/rice append --exec lotus-worker -i ./build .PHONY: lotus-worker BINS+=lotus-worker lotus-shed: $(BUILD_DEPS) rm -f lotus-shed go build $(GOFLAGS) -o lotus-shed ./cmd/lotus-shed - go run github.com/GeertJohan/go.rice/rice append --exec lotus-shed -i ./build .PHONY: lotus-shed BINS+=lotus-shed @@ -138,7 +140,6 @@ install-worker: lotus-seed: $(BUILD_DEPS) rm -f lotus-seed go build $(GOFLAGS) -o lotus-seed ./cmd/lotus-seed - go run github.com/GeertJohan/go.rice/rice append --exec lotus-seed -i ./build .PHONY: lotus-seed BINS+=lotus-seed @@ -172,13 +173,11 @@ lotus-townhall-front: .PHONY: lotus-townhall-front lotus-townhall-app: lotus-touch lotus-townhall-front - go run github.com/GeertJohan/go.rice/rice append --exec lotus-townhall -i ./cmd/lotus-townhall -i ./build .PHONY: lotus-townhall-app lotus-fountain: rm -f lotus-fountain go build -o lotus-fountain ./cmd/lotus-fountain - go run github.com/GeertJohan/go.rice/rice append --exec lotus-fountain -i ./cmd/lotus-fountain -i ./build .PHONY: lotus-fountain BINS+=lotus-fountain @@ -191,28 +190,24 @@ BINS+=lotus-chainwatch lotus-bench: rm -f lotus-bench go build -o lotus-bench ./cmd/lotus-bench - go run github.com/GeertJohan/go.rice/rice append --exec lotus-bench -i ./build .PHONY: lotus-bench BINS+=lotus-bench lotus-stats: rm -f lotus-stats go build $(GOFLAGS) -o lotus-stats ./cmd/lotus-stats - go run github.com/GeertJohan/go.rice/rice append --exec lotus-stats -i ./build .PHONY: lotus-stats BINS+=lotus-stats lotus-pcr: rm -f lotus-pcr go build $(GOFLAGS) -o lotus-pcr ./cmd/lotus-pcr - go run github.com/GeertJohan/go.rice/rice append --exec lotus-pcr -i ./build .PHONY: lotus-pcr BINS+=lotus-pcr lotus-health: rm -f lotus-health go build -o lotus-health ./cmd/lotus-health - go run github.com/GeertJohan/go.rice/rice append --exec lotus-health -i ./build .PHONY: lotus-health BINS+=lotus-health diff --git a/scripts/build-bundle.sh b/scripts/build-bundle.sh index 7d37edff8..fe1c88611 100755 --- a/scripts/build-bundle.sh +++ b/scripts/build-bundle.sh @@ -49,4 +49,7 @@ do ipfs add -q "lotus_${CIRCLE_TAG}_${ARCH}-amd64.tar.gz" > "lotus_${CIRCLE_TAG}_${ARCH}-amd64.tar.gz.cid" done +cp "../appimage/Lotus-${CIRCLE_TAG}-x86_64.AppImage" . +sha512sum "Lotus-${CIRCLE_TAG}-x86_64.AppImage" > "Lotus-${CIRCLE_TAG}-x86_64.AppImage.sha512" +ipfs add -q "Lotus-${CIRCLE_TAG}-x86_64.AppImage" > "Lotus-${CIRCLE_TAG}-x86_64.AppImage.cid" popd diff --git a/scripts/publish-release.sh b/scripts/publish-release.sh index e77a6a949..22572de60 100755 --- a/scripts/publish-release.sh +++ b/scripts/publish-release.sh @@ -29,13 +29,20 @@ RELEASE_ID=`echo "${RELEASE_RESPONSE}" | jq '.id'` if [ "${RELEASE_ID}" = "null" ]; then echo "creating release" + COND_CREATE_DISCUSSION="" + PRERELEASE=true + if [[ ${CIRCLE_TAG} =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + COND_CREATE_DISCUSSION="\"discussion_category_name\": \"announcement\"," + PRERELEASE=false + fi + RELEASE_DATA="{ \"tag_name\": \"${CIRCLE_TAG}\", \"target_commitish\": \"${CIRCLE_SHA1}\", - \"discussion_category_name\": \"announcement\", + ${COND_CREATE_DISCUSSION} \"name\": \"${CIRCLE_TAG}\", \"body\": \"\", - \"prerelease\": false + \"prerelease\": ${PRERELEASE} }" # create it if it doesn't exist yet @@ -61,6 +68,9 @@ artifacts=( "lotus_${CIRCLE_TAG}_darwin-amd64.tar.gz" "lotus_${CIRCLE_TAG}_darwin-amd64.tar.gz.cid" "lotus_${CIRCLE_TAG}_darwin-amd64.tar.gz.sha512" + "Lotus-${CIRCLE_TAG}-x86_64.AppImage" + "Lotus-${CIRCLE_TAG}-x86_64.AppImage.cid" + "Lotus-${CIRCLE_TAG}-x86_64.AppImage.sha512" ) for RELEASE_FILE in "${artifacts[@]}" From 45b063648d4e0266785756461f41f6c2472b309b Mon Sep 17 00:00:00 2001 From: Cory Schwartz Date: Tue, 15 Jun 2021 11:59:47 -0700 Subject: [PATCH 036/257] tmp: test build in circleci --- .circleci/config.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 11e8ac506..29b2e1478 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -428,6 +428,40 @@ jobs: - "~/.rustup" - "~/.cargo" + build-appimage: + machine: + image: ubuntu-2004:202104-01 + steps: + - checkout + - attach_workspace: + at: "." + - run: + name: install appimage-builder + command: | + # docs: https://appimage-builder.readthedocs.io/en/latest/intro/install.html + sudo apt update + sudo apt install -y python3-pip python3-setuptools patchelf desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace + sudo curl -Lo /usr/local/bin/appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage + sudo chmod +x /usr/local/bin/appimagetool + sudo pip3 install appimage-builder + - run: + name: install lotus dependencies + command: sudo apt install ocl-icd-opencl-dev libhwloc-dev + - run: + name: build appimage + command: | + sed -i "s/version: latest/version: ${CIRCLE_TAG:-latest}/" AppImageBuilder.yml + make appimage + - run: + name: prepare workspace + command: | + mkdir appimage + mv Lotus-latest-x86_64.AppImage appimage + - persist_to_workspace: + root: "." + paths: + - appimage + gofmt: executor: golang steps: @@ -805,6 +839,7 @@ workflows: requires: - build-all - build-macos + - build-appimage filters: branches: ignore: @@ -812,6 +847,8 @@ workflows: tags: only: - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + + - build-appimage - build-and-push-image: dockerfile: Dockerfile.lotus path: . From 041bf9990e5162fe11325e754233537a7c78614e Mon Sep 17 00:00:00 2001 From: Cory Schwartz Date: Tue, 15 Jun 2021 12:01:56 -0700 Subject: [PATCH 037/257] yaml syntax --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 29b2e1478..254e7814b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -428,7 +428,7 @@ jobs: - "~/.rustup" - "~/.cargo" - build-appimage: + build-appimage: machine: image: ubuntu-2004:202104-01 steps: From 9dc67abee6274fdddb2aa16a85499441132c7e3b Mon Sep 17 00:00:00 2001 From: Cory Schwartz Date: Tue, 15 Jun 2021 12:05:30 -0700 Subject: [PATCH 038/257] add make appimage --- Makefile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Makefile b/Makefile index 59e1a1cd4..a8857556f 100644 --- a/Makefile +++ b/Makefile @@ -330,6 +330,15 @@ api-gen: goimports -w api/apistruct .PHONY: api-gen +appimage: lotus + rm -rf appimage-builder-cache || true + rm AppDir/io.filecoin.lotus.desktop || true + rm AppDir/icon.svg || true + rm Appdir/AppRun || true + mkdir -p AppDir/usr/bin + cp ./lotus AppDir/usr/bin/ + appimage-builder + docsgen: docsgen-md docsgen-openrpc docsgen-md-bin: actors-gen From 2b7e809efed8cacc89f57f5cada2f0f53bd33973 Mon Sep 17 00:00:00 2001 From: Cory Schwartz Date: Tue, 15 Jun 2021 12:20:19 -0700 Subject: [PATCH 039/257] filter build-appimage --- .circleci/config.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 254e7814b..2df973dfb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -835,6 +835,16 @@ workflows: tags: only: - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-appimage: + requires: + - test-short + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - publish: requires: - build-all @@ -848,7 +858,6 @@ workflows: only: - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - - build-appimage - build-and-push-image: dockerfile: Dockerfile.lotus path: . From 21ba7408dd0a55115e0c485a78c78a08cc775aca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 15 Jun 2021 21:04:11 +0200 Subject: [PATCH 040/257] sealing: Fix restartSectors race --- extern/storage-sealing/fsm.go | 3 +++ extern/storage-sealing/garbage.go | 2 ++ extern/storage-sealing/input.go | 4 ++++ extern/storage-sealing/sealing.go | 7 +++++++ 4 files changed, 16 insertions(+) diff --git a/extern/storage-sealing/fsm.go b/extern/storage-sealing/fsm.go index a765d2617..1ad2d7ec0 100644 --- a/extern/storage-sealing/fsm.go +++ b/extern/storage-sealing/fsm.go @@ -514,6 +514,8 @@ func planCommitting(events []statemachine.Event, state *SectorInfo) (uint64, err } func (m *Sealing) restartSectors(ctx context.Context) error { + defer m.startupWait.Done() + trackedSectors, err := m.ListSectors() if err != nil { log.Errorf("loading sector list: %+v", err) @@ -531,6 +533,7 @@ func (m *Sealing) restartSectors(ctx context.Context) error { } func (m *Sealing) ForceSectorState(ctx context.Context, id abi.SectorNumber, state SectorState) error { + m.startupWait.Wait() return m.sectors.Send(id, SectorForceState{state}) } diff --git a/extern/storage-sealing/garbage.go b/extern/storage-sealing/garbage.go index c8ec21a84..d429b5b43 100644 --- a/extern/storage-sealing/garbage.go +++ b/extern/storage-sealing/garbage.go @@ -9,6 +9,8 @@ import ( ) func (m *Sealing) PledgeSector(ctx context.Context) (storage.SectorRef, error) { + m.startupWait.Wait() + m.inputLk.Lock() defer m.inputLk.Unlock() diff --git a/extern/storage-sealing/input.go b/extern/storage-sealing/input.go index 44d2e8275..1ba8b4c2c 100644 --- a/extern/storage-sealing/input.go +++ b/extern/storage-sealing/input.go @@ -376,6 +376,8 @@ func (m *Sealing) updateInput(ctx context.Context, sp abi.RegisteredSealProof) e } func (m *Sealing) tryCreateDealSector(ctx context.Context, sp abi.RegisteredSealProof) error { + m.startupWait.Wait() + cfg, err := m.getConfig() if err != nil { return xerrors.Errorf("getting storage config: %w", err) @@ -422,6 +424,8 @@ func (m *Sealing) createSector(ctx context.Context, cfg sealiface.Config, sp abi } func (m *Sealing) StartPacking(sid abi.SectorNumber) error { + m.startupWait.Wait() + return m.sectors.Send(uint64(sid), SectorStartPacking{}) } diff --git a/extern/storage-sealing/sealing.go b/extern/storage-sealing/sealing.go index cfe4b9f90..ee29a724f 100644 --- a/extern/storage-sealing/sealing.go +++ b/extern/storage-sealing/sealing.go @@ -83,6 +83,8 @@ type Sealing struct { feeCfg config.MinerFeeConfig events Events + startupWait sync.WaitGroup + maddr address.Address sealer sectorstorage.SectorManager @@ -161,6 +163,7 @@ func New(api SealingAPI, fc config.MinerFeeConfig, events Events, maddr address. bySector: map[abi.SectorID]statSectorState{}, }, } + s.startupWait.Add(1) s.sectors = statemachine.New(namespace.Wrap(ds, datastore.NewKey(SectorStorePrefix)), s, SectorInfo{}) @@ -188,10 +191,14 @@ func (m *Sealing) Stop(ctx context.Context) error { } func (m *Sealing) Remove(ctx context.Context, sid abi.SectorNumber) error { + m.startupWait.Wait() + return m.sectors.Send(uint64(sid), SectorRemove{}) } func (m *Sealing) Terminate(ctx context.Context, sid abi.SectorNumber) error { + m.startupWait.Wait() + return m.sectors.Send(uint64(sid), SectorTerminate{}) } From 81b412399ef9a5d6956267d6471850479d1b7c91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 15 Jun 2021 21:04:11 +0200 Subject: [PATCH 041/257] sealing: Fix restartSectors race --- extern/storage-sealing/fsm.go | 3 +++ extern/storage-sealing/garbage.go | 2 ++ extern/storage-sealing/input.go | 4 ++++ extern/storage-sealing/sealing.go | 7 +++++++ 4 files changed, 16 insertions(+) diff --git a/extern/storage-sealing/fsm.go b/extern/storage-sealing/fsm.go index a765d2617..1ad2d7ec0 100644 --- a/extern/storage-sealing/fsm.go +++ b/extern/storage-sealing/fsm.go @@ -514,6 +514,8 @@ func planCommitting(events []statemachine.Event, state *SectorInfo) (uint64, err } func (m *Sealing) restartSectors(ctx context.Context) error { + defer m.startupWait.Done() + trackedSectors, err := m.ListSectors() if err != nil { log.Errorf("loading sector list: %+v", err) @@ -531,6 +533,7 @@ func (m *Sealing) restartSectors(ctx context.Context) error { } func (m *Sealing) ForceSectorState(ctx context.Context, id abi.SectorNumber, state SectorState) error { + m.startupWait.Wait() return m.sectors.Send(id, SectorForceState{state}) } diff --git a/extern/storage-sealing/garbage.go b/extern/storage-sealing/garbage.go index c8ec21a84..d429b5b43 100644 --- a/extern/storage-sealing/garbage.go +++ b/extern/storage-sealing/garbage.go @@ -9,6 +9,8 @@ import ( ) func (m *Sealing) PledgeSector(ctx context.Context) (storage.SectorRef, error) { + m.startupWait.Wait() + m.inputLk.Lock() defer m.inputLk.Unlock() diff --git a/extern/storage-sealing/input.go b/extern/storage-sealing/input.go index 44d2e8275..1ba8b4c2c 100644 --- a/extern/storage-sealing/input.go +++ b/extern/storage-sealing/input.go @@ -376,6 +376,8 @@ func (m *Sealing) updateInput(ctx context.Context, sp abi.RegisteredSealProof) e } func (m *Sealing) tryCreateDealSector(ctx context.Context, sp abi.RegisteredSealProof) error { + m.startupWait.Wait() + cfg, err := m.getConfig() if err != nil { return xerrors.Errorf("getting storage config: %w", err) @@ -422,6 +424,8 @@ func (m *Sealing) createSector(ctx context.Context, cfg sealiface.Config, sp abi } func (m *Sealing) StartPacking(sid abi.SectorNumber) error { + m.startupWait.Wait() + return m.sectors.Send(uint64(sid), SectorStartPacking{}) } diff --git a/extern/storage-sealing/sealing.go b/extern/storage-sealing/sealing.go index cfe4b9f90..ee29a724f 100644 --- a/extern/storage-sealing/sealing.go +++ b/extern/storage-sealing/sealing.go @@ -83,6 +83,8 @@ type Sealing struct { feeCfg config.MinerFeeConfig events Events + startupWait sync.WaitGroup + maddr address.Address sealer sectorstorage.SectorManager @@ -161,6 +163,7 @@ func New(api SealingAPI, fc config.MinerFeeConfig, events Events, maddr address. bySector: map[abi.SectorID]statSectorState{}, }, } + s.startupWait.Add(1) s.sectors = statemachine.New(namespace.Wrap(ds, datastore.NewKey(SectorStorePrefix)), s, SectorInfo{}) @@ -188,10 +191,14 @@ func (m *Sealing) Stop(ctx context.Context) error { } func (m *Sealing) Remove(ctx context.Context, sid abi.SectorNumber) error { + m.startupWait.Wait() + return m.sectors.Send(uint64(sid), SectorRemove{}) } func (m *Sealing) Terminate(ctx context.Context, sid abi.SectorNumber) error { + m.startupWait.Wait() + return m.sectors.Send(uint64(sid), SectorTerminate{}) } From c1529714e63b0ea6932da7aef2a55c8f330d7395 Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Tue, 15 Jun 2021 22:16:41 -0400 Subject: [PATCH 042/257] v1.10.0-rc4 have this version base off the right head = v1.9.0 tag --- CHANGELOG.md | 4 ++-- build/version.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3359a29c..73e27e9ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ # Lotus changelog -# 1.10.0-rc3 / 2021-06-11 +# 1.10.0-rc4 / 2021-06-11 > Note: If you are running a lotus miner, check out the doc [here](https://docs.filecoin.io/mine/lotus/miner-configuration/#precommitsectorsbatch) for new lotus miner configurations explanations of the new features! -This is the third release candidate for Lotus v1.10.0, an upcoming mandatory release of Lotus that will introduce Filecoin network v13. Included in the new network version are the following FIPs: +This is the 4th release candidate for Lotus v1.10.0, an upcoming mandatory release of Lotus that will introduce Filecoin network v13. Included in the new network version are the following FIPs: - [FIP-0008](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0008.md): Add miner batched sector pre-commit method - [FIP-0011](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0011.md): Remove reward auction from reporting consensus faults diff --git a/build/version.go b/build/version.go index cf53219d8..c9d1a5cce 100644 --- a/build/version.go +++ b/build/version.go @@ -29,7 +29,7 @@ func buildType() string { } // BuildVersion is the local build version, set by build system -const BuildVersion = "1.10.0-rc3" +const BuildVersion = "1.10.0-rc4" func UserVersion() string { return BuildVersion + buildType() + CurrentCommit From 1f914053b43cb924766d1d9f38c8287bec8a0d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 16 Jun 2021 11:43:06 +0200 Subject: [PATCH 043/257] sealing: Wire up context to batchers --- extern/storage-sealing/sealing.go | 8 ++++---- storage/miner.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extern/storage-sealing/sealing.go b/extern/storage-sealing/sealing.go index ee29a724f..e753085ef 100644 --- a/extern/storage-sealing/sealing.go +++ b/extern/storage-sealing/sealing.go @@ -131,7 +131,7 @@ type pendingPiece struct { accepted func(abi.SectorNumber, abi.UnpaddedPieceSize, error) } -func New(api SealingAPI, fc config.MinerFeeConfig, events Events, maddr address.Address, ds datastore.Batching, sealer sectorstorage.SectorManager, sc SectorIDCounter, verif ffiwrapper.Verifier, prov ffiwrapper.Prover, pcp PreCommitPolicy, gc GetSealingConfigFunc, notifee SectorStateNotifee, as AddrSel) *Sealing { +func New(mctx context.Context, api SealingAPI, fc config.MinerFeeConfig, events Events, maddr address.Address, ds datastore.Batching, sealer sectorstorage.SectorManager, sc SectorIDCounter, verif ffiwrapper.Verifier, prov ffiwrapper.Prover, pcp PreCommitPolicy, gc GetSealingConfigFunc, notifee SectorStateNotifee, as AddrSel) *Sealing { s := &Sealing{ api: api, feeCfg: fc, @@ -152,9 +152,9 @@ func New(api SealingAPI, fc config.MinerFeeConfig, events Events, maddr address. notifee: notifee, addrSel: as, - terminator: NewTerminationBatcher(context.TODO(), maddr, api, as, fc, gc), - precommiter: NewPreCommitBatcher(context.TODO(), maddr, api, as, fc, gc), - commiter: NewCommitBatcher(context.TODO(), maddr, api, as, fc, gc, prov), + terminator: NewTerminationBatcher(mctx, maddr, api, as, fc, gc), + precommiter: NewPreCommitBatcher(mctx, maddr, api, as, fc, gc), + commiter: NewCommitBatcher(mctx, maddr, api, as, fc, gc, prov), getConfig: gc, dealInfo: &CurrentDealInfoManager{api}, diff --git a/storage/miner.go b/storage/miner.go index a5b2ea82b..e1829a7d6 100644 --- a/storage/miner.go +++ b/storage/miner.go @@ -157,7 +157,7 @@ func (m *Miner) Run(ctx context.Context) error { return m.addrSel.AddressFor(ctx, m.api, mi, use, goodFunds, minFunds) } - m.sealing = sealing.New(adaptedAPI, m.feeCfg, NewEventsAdapter(evts), m.maddr, m.ds, m.sealer, m.sc, m.verif, m.prover, &pcp, sealing.GetSealingConfigFunc(m.getSealConfig), m.handleSealingNotifications, as) + m.sealing = sealing.New(ctx, adaptedAPI, m.feeCfg, NewEventsAdapter(evts), m.maddr, m.ds, m.sealer, m.sc, m.verif, m.prover, &pcp, sealing.GetSealingConfigFunc(m.getSealConfig), m.handleSealingNotifications, as) go m.sealing.Run(ctx) //nolint:errcheck // logged intside the function From 8b2d74bb2c75f57168ae266bfd3d4cb9542ab0a0 Mon Sep 17 00:00:00 2001 From: Rjan Date: Wed, 16 Jun 2021 12:26:33 +0200 Subject: [PATCH 044/257] Initial draft: basic build instructions on Readme Inital draft for basic build instructions on the Readme page. Issue: #6348 --- README.md | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 761838834..ee458788b 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Lotus is an implementation of the Filecoin Distributed Storage Network. For more ## Building & Documentation -For instructions on how to build, install and setup lotus, please visit [https://docs.filecoin.io/get-started/lotus](https://docs.filecoin.io/get-started/lotus/). +For complete instructions on how to build, install and setup lotus, please visit [https://docs.filecoin.io/get-started/lotus](https://docs.filecoin.io/get-started/lotus/). Basic build instructions can be found further down in this readme. ## Reporting a Vulnerability @@ -50,6 +50,112 @@ When implementing a change: 7. Title the PR in a meaningful way and describe the rationale and the thought process in the PR description. 8. Write clean, thoughtful, and detailed [commit messages](https://chris.beams.io/posts/git-commit/). This is even more important than the PR description, because commit messages are stored _inside_ the Git history. One good rule is: if you are happy posting the commit message as the PR description, then it's a good commit message. +## Basic Build Instructions +**System-specific Software Dependencies**: + +Building Lotus requires some system dependencies, usually provided by your distribution. + +Ubuntu/Debian: +``` +sudo apt install mesa-opencl-icd ocl-icd-opencl-dev gcc git bzr jq pkg-config curl clang build-essential hwloc libhwloc-dev wget -y && sudo apt upgrade -y +``` + +Fedora: +``` +sudo dnf -y install gcc make git bzr jq pkgconfig mesa-libOpenCL mesa-libOpenCL-devel opencl-headers ocl-icd ocl-icd-devel clang llvm wget hwloc libhwloc-dev +``` + +For other distributions you can find the required dependencies [here.](https://docs.filecoin.io/get-started/lotus/installation/#system-specific) For instructions specific to macOS, you can find them [here.](https://docs.filecoin.io/get-started/lotus/installation/#macos) + +#### Rustup + +Lotus needs [rustup](https://rustup.rs). The easiest way to install it is: + +```bash +wget -c https://golang.org/dl/go1.16.2.linux-amd64.tar.gz -O - | sudo tar -xz -C /usr/local +``` + +#### Go + +To build Lotus, you need a working installation of [Go 1.15.5 or higher](https://golang.org/dl/): + +```bash +wget -c https://golang.org/dl/go1.16.2.linux-amd64.tar.gz -O - | sudo tar -xz -C /usr/local +``` + +**TIP:** +You'll need to add `/usr/local/go/bin` to your path. For most Linux distributions you can run something like: + +```shell +echo "export PATH=$PATH:/usr/local/go/bin" >> ~/.bashrc && source ~/.bashrc +``` + +See the [official Golang installation instructions](https://golang.org/doc/install) if you get stuck. + +### Build and install Lotus + +Once all the dependencies are installed, you can build and install the Lotus suite (`lotus`, `lotus-miner`, and `lotus-worker`). + +1. Clone the repository: + + ```sh + git clone https://github.com/filecoin-project/lotus.git + cd lotus/ + ``` + +2. To join mainnet, checkout the [latest release](https://github.com/filecoin-project/lotus/releases). + + If you are changing networks from a previous Lotus installation or there has been a network reset, read the [Switch networks guide](https://docs.filecoin.io/get-started/lotus/switch-networks/) before proceeding. + + For networks other than mainnet, look up the current branch or tag/commit for the network you want to join in the [Filecoin networks dashboard](https://network.filecoin.io), then build Lotus for your specific network below. + + ```sh + git checkout + # For example: + git checkout # tag for a release + ``` + + Currently, the latest code on the _master_ branch corresponds to mainnet. + +3. If you are in China, see "[Lotus: tips when running in China](https://docs.filecoin.io/get-started/lotus/tips-running-in-china/)". +4. Depending on your CPU model, you will want to export additional environment variables: + + If you have **an AMD Zen or Intel Ice Lake CPU (or later)**, enable the use of SHA extensions by adding these two environment variables: + + ```sh + export RUSTFLAGS="-C target-cpu=native -g" + export FFI_BUILD_FROM_SOURCE=1 + ``` + + See the [Native Filecoin FFI section](https://docs.filecoin.io/get-started/lotus/installation/#native-filecoin-ffi) for more details about this process. + + Some older Intel and AMD processors without the ADX instruction support may panic with illegal instruction errors. To fix this, add the `CGO_CFLAGS` environment variable: + + ```sh + export CGO_CFLAGS_ALLOW="-D__BLST_PORTABLE__" + export CGO_CFLAGS="-D__BLST_PORTABLE__" + ``` + + This is due to a Lotus bug that prevents Lotus from running on a processor without `adx` instruction support, and should be fixed soon. + +5. Build and install Lotus: + + ```sh + make clean all + + # Or to join a testnet or devnet: + make clean calibnet # Calibration with min 32GiB sectors + make clean nerpanet # Nerpa with min 512MiB sectors + + sudo make install + ``` + + This will put `lotus`, `lotus-miner` and `lotus-worker` in `/usr/local/bin`. + + `lotus` will use the `$HOME/.lotus` folder by default for storage (configuration, chain data, wallets, etc). See [advanced options](https://docs.filecoin.io/get-started/lotus/configuration-and-advanced-usage/) for information on how to customize the Lotus folder. + +6. You should now have Lotus installed. You can now [start the Lotus daemon and sync the chain](https://docs.filecoin.io/get-started/lotus/installation/#start-the-lotus-daemon-and-sync-the-chain). + ## License Dual-licensed under [MIT](https://github.com/filecoin-project/lotus/blob/master/LICENSE-MIT) + [Apache 2.0](https://github.com/filecoin-project/lotus/blob/master/LICENSE-APACHE) From ca4ca8cbcf9bbb81f7cd93672518c95dbfef3ba4 Mon Sep 17 00:00:00 2001 From: Rjan Date: Wed, 16 Jun 2021 13:15:09 +0200 Subject: [PATCH 045/257] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ee458788b..4a0adc8c0 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ wget -c https://golang.org/dl/go1.16.2.linux-amd64.tar.gz -O - | sudo tar -xz -C #### Go -To build Lotus, you need a working installation of [Go 1.15.5 or higher](https://golang.org/dl/): +To build Lotus, you need a working installation of [Go 1.16.1 or higher](https://golang.org/dl/): ```bash wget -c https://golang.org/dl/go1.16.2.linux-amd64.tar.gz -O - | sudo tar -xz -C /usr/local From 4ea73b408867c63d61dd072e5fffdefe863f4c6b Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Wed, 16 Jun 2021 14:42:28 +0200 Subject: [PATCH 046/257] refactor: use genesis preseals from kit2 --- itests/ccupgrade_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/itests/ccupgrade_test.go b/itests/ccupgrade_test.go index 050e6fac1..2c35b425d 100644 --- a/itests/ccupgrade_test.go +++ b/itests/ccupgrade_test.go @@ -6,7 +6,6 @@ import ( "testing" "time" - "github.com/filecoin-project/lotus/itests/kit" "github.com/filecoin-project/lotus/itests/kit2" "github.com/stretchr/testify/require" @@ -44,7 +43,7 @@ func runTestCCUpgrade(t *testing.T, upgradeHeight abi.ChainEpoch) { t.Fatal(err) } - CC := abi.SectorNumber(kit.GenesisPreseals + 1) + CC := abi.SectorNumber(kit2.DefaultPresealsPerBootstrapMiner + 1) Upgraded := CC + 1 miner.PledgeSectors(ctx, 1, 0, nil) From 7640ae47deba94aca2b3516c208a234f9e16bbfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 16 Jun 2021 14:51:40 +0200 Subject: [PATCH 047/257] docsgen --- build/openrpc/full.json.gz | Bin 22485 -> 22484 bytes build/openrpc/miner.json.gz | Bin 8089 -> 8089 bytes build/openrpc/worker.json.gz | Bin 2579 -> 2579 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 1eb1ebf942b6dc8ee9d4eea0dcbc656d37db061a..2fee80bb1d1fe56d7cad5f1d80d458fe0ab222cd 100644 GIT binary patch delta 22342 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 delta 22338 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 diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index d4d24cdd539c616b66c9ef471c391d0a13027209..da845c6ee1c48c7335ecec8f72fee5b5ddd94b68 100644 GIT binary patch delta 22 ecmbPfKhu6f2b0u{ja}^W9EbMIxVo>0kpTc`@d+FN delta 22 ecmbPfKhu6f2h*$B8@t%$IldS3mF(+bWB>qf(+KbY diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index 19d041474e1b52cbe1c66613982bb5bb4e5f834b..945825467fb0703833cb416aba1486f9faac7026 100644 GIT binary patch delta 21 dcmbO%GFfCoBjfLlO`V(^`Bx8h_*XG7003e!2!H?p delta 21 ccmbO%GFfCoBV+Z(rcO?dJoBhB|0)Is08|YI=Kufz From d2ff0cd88ece6e67f3f0671f5a894cb1e545010c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Wed, 16 Jun 2021 18:11:34 +0100 Subject: [PATCH 048/257] fix verifreg test; add VerifierRootKey() and Account() ensemble opts. --- itests/kit2/ensemble.go | 30 ++++++++-- itests/kit2/ensemble_opts.go | 36 +++++++++++- itests/kit2/node_opts_nv.go | 64 ++++++++++++++------ itests/verifreg_test.go | 111 ++++++++++++++++++----------------- 4 files changed, 163 insertions(+), 78 deletions(-) diff --git a/itests/kit2/ensemble.go b/itests/kit2/ensemble.go index 5d12c83e1..a66dac1e3 100644 --- a/itests/kit2/ensemble.go +++ b/itests/kit2/ensemble.go @@ -13,6 +13,7 @@ import ( "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" "github.com/filecoin-project/go-storedcounter" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api/v1api" @@ -122,7 +123,19 @@ func NewEnsemble(t *testing.T, opts ...EnsembleOpt) *Ensemble { err := o(&options) require.NoError(t, err) } - return &Ensemble{t: t, options: &options} + + n := &Ensemble{t: t, options: &options} + + // add accounts from ensemble options to genesis. + for _, acc := range options.accounts { + n.genesis.accounts = append(n.genesis.accounts, genesis.Actor{ + Type: genesis.TAccount, + Balance: acc.initialBalance, + Meta: (&genesis.AccountMeta{Owner: acc.key.Address}).ActorMeta(), + }) + } + + return n } // FullNode enrolls a new full node. @@ -135,8 +148,7 @@ func (n *Ensemble) FullNode(full *TestFullNode, opts ...NodeOpt) *Ensemble { var key *wallet.Key if !n.bootstrapped && !options.balance.IsZero() { - // create a key+ddress, and assign it some FIL. - // this will be set as the default wallet. + // create a key+address, and assign it some FIL; this will be set as the default wallet. var err error key, err = wallet.GenerateKey(types.KTBLS) require.NoError(n.t, err) @@ -589,12 +601,22 @@ func (n *Ensemble) BeginMining(blocktime time.Duration, miners ...*TestMiner) [] } func (n *Ensemble) generateGenesis() *genesis.Template { + var verifRoot = gen.DefaultVerifregRootkeyActor + if k := n.options.verifiedRoot.key; k != nil { + verifRoot = genesis.Actor{ + Type: genesis.TAccount, + Balance: n.options.verifiedRoot.initialBalance, + Meta: (&genesis.AccountMeta{Owner: k.Address}).ActorMeta(), + } + } + templ := &genesis.Template{ + NetworkVersion: network.Version0, Accounts: n.genesis.accounts, Miners: n.genesis.miners, NetworkName: "test", Timestamp: uint64(time.Now().Unix() - 10000), // some time sufficiently far in the past - VerifregRootKey: gen.DefaultVerifregRootkeyActor, + VerifregRootKey: verifRoot, RemainderAccount: gen.DefaultRemainderAccountActor, } diff --git a/itests/kit2/ensemble_opts.go b/itests/kit2/ensemble_opts.go index 724113bdc..5e49976ec 100644 --- a/itests/kit2/ensemble_opts.go +++ b/itests/kit2/ensemble_opts.go @@ -4,14 +4,22 @@ import ( "time" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/chain/wallet" ) type EnsembleOpt func(opts *ensembleOpts) error +type genesisAccount struct { + key *wallet.Key + initialBalance abi.TokenAmount +} + type ensembleOpts struct { - pastOffset time.Duration - proofType abi.RegisteredSealProof - mockProofs bool + pastOffset time.Duration + proofType abi.RegisteredSealProof + verifiedRoot genesisAccount + accounts []genesisAccount + mockProofs bool } var DefaultEnsembleOpts = ensembleOpts{ @@ -33,3 +41,25 @@ func MockProofs() EnsembleOpt { return nil } } + +// VerifierRootKey specifies the key to be enlisted as the verified clients +// registry root, as well as the initial balance to be attributed during +// genesis. +func VerifierRootKey(key *wallet.Key, balance abi.TokenAmount) EnsembleOpt { + return func(opts *ensembleOpts) error { + opts.verifiedRoot.key = key + opts.verifiedRoot.initialBalance = balance + return nil + } +} + +// Account sets up an account at genesis with the specified key and balance. +func Account(key *wallet.Key, balance abi.TokenAmount) EnsembleOpt { + return func(opts *ensembleOpts) error { + opts.accounts = append(opts.accounts, genesisAccount{ + key: key, + initialBalance: balance, + }) + return nil + } +} diff --git a/itests/kit2/node_opts_nv.go b/itests/kit2/node_opts_nv.go index 05d2c2287..6f682bd3a 100644 --- a/itests/kit2/node_opts_nv.go +++ b/itests/kit2/node_opts_nv.go @@ -1,36 +1,64 @@ package kit2 import ( + "context" + "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/network" "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/node" + "github.com/ipfs/go-cid" ) +// DefaultTestUpgradeSchedule +var DefaultTestUpgradeSchedule = stmgr.UpgradeSchedule{{ + Network: network.Version9, + Height: 1, + Migration: stmgr.UpgradeActorsV2, +}, { + Network: network.Version10, + Height: 2, + Migration: stmgr.UpgradeActorsV3, +}, { + Network: network.Version12, + Height: 3, + Migration: stmgr.UpgradeActorsV4, +}, { + Network: network.Version13, + Height: 4, + Migration: stmgr.UpgradeActorsV5, +}} + func LatestActorsAt(upgradeHeight abi.ChainEpoch) node.Option { // Attention: Update this when introducing new actor versions or your tests will be sad return NetworkUpgradeAt(network.Version13, upgradeHeight) } +// InstantaneousNetworkVersion starts the network instantaneously at the +// specified version in height 1. +func InstantaneousNetworkVersion(version network.Version) node.Option { + // composes all migration functions + var mf stmgr.MigrationFunc = func(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, cb stmgr.ExecMonitor, oldState cid.Cid, height abi.ChainEpoch, ts *types.TipSet) (newState cid.Cid, err error) { + var state = oldState + for _, u := range DefaultTestUpgradeSchedule { + if u.Network > version { + break + } + state, err = u.Migration(ctx, sm, cache, cb, state, height, ts) + if err != nil { + return cid.Undef, err + } + } + return state, nil + } + return node.Override(new(stmgr.UpgradeSchedule), stmgr.UpgradeSchedule{ + {Network: version, Height: 1, Migration: mf}, + }) +} + func NetworkUpgradeAt(version network.Version, upgradeHeight abi.ChainEpoch) node.Option { - fullSchedule := stmgr.UpgradeSchedule{{ - // prepare for upgrade. - Network: network.Version9, - Height: 1, - Migration: stmgr.UpgradeActorsV2, - }, { - Network: network.Version10, - Height: 2, - Migration: stmgr.UpgradeActorsV3, - }, { - Network: network.Version12, - Height: 3, - Migration: stmgr.UpgradeActorsV4, - }, { - Network: network.Version13, - Height: 4, - Migration: stmgr.UpgradeActorsV5, - }} + fullSchedule := stmgr.UpgradeSchedule{} schedule := stmgr.UpgradeSchedule{} for _, upgrade := range fullSchedule { diff --git a/itests/verifreg_test.go b/itests/verifreg_test.go index 180a194a6..b1e3bcfa4 100644 --- a/itests/verifreg_test.go +++ b/itests/verifreg_test.go @@ -2,13 +2,17 @@ package itests import ( "context" + "fmt" "strings" "testing" "time" + "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/lotus/chain/wallet" verifreg4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/verifreg" + "github.com/stretchr/testify/require" lapi "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors" @@ -23,101 +27,102 @@ func TestVerifiedClientTopUp(t *testing.T) { test := func(nv network.Version, shouldWork bool) func(*testing.T) { return func(t *testing.T) { - nopts := kit2.ConstructorOpts(kit2.NetworkUpgradeAt(nv, -1)) + rootKey, err := wallet.GenerateKey(types.KTSecp256k1) + require.NoError(t, err) + + verifierKey, err := wallet.GenerateKey(types.KTSecp256k1) + require.NoError(t, err) + + verifiedClientKey, err := wallet.GenerateKey(types.KTBLS) + require.NoError(t, err) + + bal, err := types.ParseFIL("100fil") + require.NoError(t, err) + + node, _, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), + kit2.VerifierRootKey(rootKey, abi.NewTokenAmount(bal.Int64())), + kit2.Account(verifierKey, abi.NewTokenAmount(bal.Int64())), // assign some balance to the verifier so they can send an AddClient message. + kit2.ConstructorOpts(kit2.InstantaneousNetworkVersion(nv))) - node, _, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), nopts) ens.InterconnectAll().BeginMining(blockTime) api := node.FullNode.(*impl.FullNodeAPI) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - //Get VRH + // get VRH vrh, err := api.StateVerifiedRegistryRootKey(ctx, types.TipSetKey{}) - if err != nil { - t.Fatal(err) - } + fmt.Println(vrh.String()) + require.NoError(t, err) - //Add verifier - verifier, err := api.WalletDefaultAddress(ctx) - if err != nil { - t.Fatal(err) - } + // import the root key. + rootAddr, err := api.WalletImport(ctx, &rootKey.KeyInfo) + require.NoError(t, err) + + // import the verifier's key. + verifierAddr, err := api.WalletImport(ctx, &verifierKey.KeyInfo) + require.NoError(t, err) + + // import the verified client's key. + verifiedClientAddr, err := api.WalletImport(ctx, &verifiedClientKey.KeyInfo) + require.NoError(t, err) + + params, err := actors.SerializeParams(&verifreg4.AddVerifierParams{Address: verifierAddr, Allowance: big.NewInt(100000000000)}) + require.NoError(t, err) - params, err := actors.SerializeParams(&verifreg4.AddVerifierParams{Address: verifier, Allowance: big.NewInt(100000000000)}) - if err != nil { - t.Fatal(err) - } msg := &types.Message{ + From: rootAddr, To: verifreg.Address, - From: vrh, Method: verifreg.Methods.AddVerifier, Params: params, Value: big.Zero(), } sm, err := api.MpoolPushMessage(ctx, msg, nil) - if err != nil { - t.Fatal("AddVerifier failed: ", err) - } + require.NoError(t, err, "AddVerifier failed") + 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") - } + require.NoError(t, err) + require.EqualValues(t, 0, res.Receipt.ExitCode) - //Assign datacap to a client + // assign datacap to a client datacap := big.NewInt(10000) - clientAddress, err := api.WalletNew(ctx, types.KTBLS) - if err != nil { - t.Fatal(err) - } - params, err = actors.SerializeParams(&verifreg4.AddVerifiedClientParams{Address: clientAddress, Allowance: datacap}) - if err != nil { - t.Fatal(err) - } + params, err = actors.SerializeParams(&verifreg4.AddVerifiedClientParams{Address: verifiedClientAddr, Allowance: datacap}) + require.NoError(t, err) msg = &types.Message{ + From: verifierAddr, To: verifreg.Address, - From: verifier, Method: verifreg.Methods.AddVerifiedClient, Params: params, Value: big.Zero(), } sm, err = api.MpoolPushMessage(ctx, msg, nil) - if err != nil { - t.Fatal("AddVerifiedClient faield: ", 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") - } + require.NoError(t, err) + + res, err = api.StateWaitMsg(ctx, sm.Cid(), 1, lapi.LookbackNoLimit, true) + require.NoError(t, err) + require.EqualValues(t, 0, res.Receipt.ExitCode) + + // check datacap balance + dcap, err := api.StateVerifiedClientStatus(ctx, verifiedClientAddr, types.EmptyTSK) + require.NoError(t, err) - //check datacap balance - dcap, err := api.StateVerifiedClientStatus(ctx, clientAddress, types.EmptyTSK) - if err != nil { - t.Fatal(err) - } if !dcap.Equals(datacap) { t.Fatal("") } - //try to assign datacap to the same client should fail for actor v4 and below - params, err = actors.SerializeParams(&verifreg4.AddVerifiedClientParams{Address: clientAddress, Allowance: datacap}) + // try to assign datacap to the same client should fail for actor v4 and below + params, err = actors.SerializeParams(&verifreg4.AddVerifiedClientParams{Address: verifiedClientAddr, Allowance: datacap}) if err != nil { t.Fatal(err) } msg = &types.Message{ + From: verifierAddr, To: verifreg.Address, - From: verifier, Method: verifreg.Methods.AddVerifiedClient, Params: params, Value: big.Zero(), From 8ed753a712a1a6d1591ca58c9d45c8db6fa28c5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Wed, 16 Jun 2021 18:16:59 +0100 Subject: [PATCH 049/257] rename RootVerifier option. --- itests/kit2/ensemble_opts.go | 7 +++---- itests/verifreg_test.go | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/itests/kit2/ensemble_opts.go b/itests/kit2/ensemble_opts.go index df7fdf41b..c7edb99a6 100644 --- a/itests/kit2/ensemble_opts.go +++ b/itests/kit2/ensemble_opts.go @@ -42,10 +42,9 @@ func MockProofs() EnsembleOpt { } } -// VerifierRootKey specifies the key to be enlisted as the verified clients -// registry root, as well as the initial balance to be attributed during -// genesis. -func VerifierRootKey(key *wallet.Key, balance abi.TokenAmount) EnsembleOpt { +// RootVerifier specifies the key to be enlisted as the verified registry root, +// as well as the initial balance to be attributed during genesis. +func RootVerifier(key *wallet.Key, balance abi.TokenAmount) EnsembleOpt { return func(opts *ensembleOpts) error { opts.verifiedRoot.key = key opts.verifiedRoot.initialBalance = balance diff --git a/itests/verifreg_test.go b/itests/verifreg_test.go index b1e3bcfa4..108da6ecf 100644 --- a/itests/verifreg_test.go +++ b/itests/verifreg_test.go @@ -40,7 +40,7 @@ func TestVerifiedClientTopUp(t *testing.T) { require.NoError(t, err) node, _, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), - kit2.VerifierRootKey(rootKey, abi.NewTokenAmount(bal.Int64())), + kit2.RootVerifier(rootKey, abi.NewTokenAmount(bal.Int64())), kit2.Account(verifierKey, abi.NewTokenAmount(bal.Int64())), // assign some balance to the verifier so they can send an AddClient message. kit2.ConstructorOpts(kit2.InstantaneousNetworkVersion(nv))) From 3acd846dcd9a5fc425fcb2dd4463b8cb34507f01 Mon Sep 17 00:00:00 2001 From: Peter Rabbitson Date: Fri, 21 May 2021 15:00:21 +0200 Subject: [PATCH 050/257] Fix logging around mineOne - A nil MiningBaseInfo is *NOT* unexpected: it happens when one is in penalty https://github.com/filecoin-project/lotus/blob/v1.9.0/chain/stmgr/utils.go#L500-L502 - Remove the log from IsRoundWinner(): all we care about is the randbase epoch --- chain/gen/gen.go | 9 ------- miner/miner.go | 67 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 45 insertions(+), 31 deletions(-) diff --git a/chain/gen/gen.go b/chain/gen/gen.go index 0cbdb2188..aa0f7df88 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -658,15 +658,6 @@ func IsRoundWinner(ctx context.Context, ts *types.TipSet, round abi.ChainEpoch, ep := &types.ElectionProof{VRFProof: vrfout} j := ep.ComputeWinCount(mbi.MinerPower, mbi.NetworkPower) ep.WinCount = j - - log.Infow("completed winAttemptVRF", - "beaconRound", brand.Round, - "beaconDataB64", b64.EncodeToString(brand.Data), - "electionRandB64", b64.EncodeToString(electionRand), - "vrfB64", b64.EncodeToString(vrfout), - "winCount", j, - ) - if j < 1 { return nil, nil } diff --git a/miner/miner.go b/miner/miner.go index a77e1c18b..5fbc9aae3 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -18,6 +18,7 @@ import ( "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/crypto" lru "github.com/hashicorp/golang-lru" @@ -415,36 +416,51 @@ func (m *Miner) GetBestMiningCandidate(ctx context.Context) (*MiningBase, error) // This method does the following: // // 1. -func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (*types.BlockMsg, error) { +func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (minedBlock *types.BlockMsg, err error) { log.Debugw("attempting to mine a block", "tipset", types.LogCids(base.TipSet.Cids())) start := build.Clock.Now() round := base.TipSet.Height() + base.NullRounds + 1 - mbi, err := m.api.MinerGetBaseInfo(ctx, m.address, round, base.TipSet.Key()) - if err != nil { - return nil, xerrors.Errorf("failed to get mining base info: %w", err) - } - if mbi == nil { - log.Warnf("mineOne: unexpectedly nil MiningBaseInfo for round %d, off tipset %d/%s", round, base.TipSet.Height(), base.TipSet.Key().String()) - return nil, nil - } - - // always write out a log from this point out + // always write out a log var winner *types.ElectionProof + var mbi *api.MiningBaseInfo + var rbase types.BeaconEntry defer func() { + // mbi can be nil if we are deep in penalty and there are 0 eligible sectors + // in the current deadline. If this case - put together a dummy one for reporting + // https://github.com/filecoin-project/lotus/blob/v1.9.0/chain/stmgr/utils.go#L500-L502 + if mbi == nil { + mbi = &api.MiningBaseInfo{ + NetworkPower: big.NewInt(-1), // we do not know how big the network is at this point + EligibleForMining: false, + MinerPower: big.NewInt(0), // but we do know we do not have anything + } + } + log.Infow( "completed mineOne", "forRound", int64(round), "baseEpoch", int64(base.TipSet.Height()), + "beaconEpoch", rbase.Round, "lookbackEpochs", int64(policy.ChainFinality), // hardcoded as it is unlikely to change again: https://github.com/filecoin-project/lotus/blob/v1.8.0/chain/actors/policy/policy.go#L180-L186 "networkPowerAtLookback", mbi.NetworkPower.String(), "minerPowerAtLookback", mbi.MinerPower.String(), "isEligible", mbi.EligibleForMining, "isWinner", (winner != nil), + "error", err, ) }() + mbi, err = m.api.MinerGetBaseInfo(ctx, m.address, round, base.TipSet.Key()) + if err != nil { + err = xerrors.Errorf("failed to get mining base info: %w", err) + return nil, err + } + if mbi == nil { + return nil, nil + } + if !mbi.EligibleForMining { // slashed or just have no power yet return nil, nil @@ -461,19 +477,21 @@ func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (*types.BlockMsg, log.Infof("Time delta between now and our mining base: %ds (nulls: %d)", uint64(build.Clock.Now().Unix())-base.TipSet.MinTimestamp(), base.NullRounds) - rbase := beaconPrev + rbase = beaconPrev if len(bvals) > 0 { rbase = bvals[len(bvals)-1] } ticket, err := m.computeTicket(ctx, &rbase, base, mbi) if err != nil { - return nil, xerrors.Errorf("scratching ticket failed: %w", err) + err = xerrors.Errorf("scratching ticket failed: %w", err) + return nil, err } winner, err = gen.IsRoundWinner(ctx, base.TipSet, round, m.address, rbase, mbi, m.api) if err != nil { - return nil, xerrors.Errorf("failed to check if we win next round: %w", err) + err = xerrors.Errorf("failed to check if we win next round: %w", err) + return nil, err } if winner == nil { @@ -484,12 +502,14 @@ func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (*types.BlockMsg, buf := new(bytes.Buffer) if err := m.address.MarshalCBOR(buf); err != nil { - return nil, xerrors.Errorf("failed to marshal miner address: %w", err) + err = xerrors.Errorf("failed to marshal miner address: %w", err) + return nil, err } rand, err := store.DrawRandomness(rbase.Data, crypto.DomainSeparationTag_WinningPoStChallengeSeed, round, buf.Bytes()) if err != nil { - return nil, xerrors.Errorf("failed to get randomness for winning post: %w", err) + err = xerrors.Errorf("failed to get randomness for winning post: %w", err) + return nil, err } prand := abi.PoStRandomness(rand) @@ -498,7 +518,8 @@ func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (*types.BlockMsg, postProof, err := m.epp.ComputeProof(ctx, mbi.Sectors, prand) if err != nil { - return nil, xerrors.Errorf("failed to compute winning post proof: %w", err) + err = xerrors.Errorf("failed to compute winning post proof: %w", err) + return nil, err } tProof := build.Clock.Now() @@ -506,15 +527,17 @@ func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (*types.BlockMsg, // get pending messages early, msgs, err := m.api.MpoolSelect(context.TODO(), base.TipSet.Key(), ticket.Quality()) if err != nil { - return nil, xerrors.Errorf("failed to select messages for block: %w", err) + err = xerrors.Errorf("failed to select messages for block: %w", err) + return nil, err } tPending := build.Clock.Now() // TODO: winning post proof - b, err := m.createBlock(base, m.address, ticket, winner, bvals, postProof, msgs) + minedBlock, err = m.createBlock(base, m.address, ticket, winner, bvals, postProof, msgs) if err != nil { - return nil, xerrors.Errorf("failed to create block: %w", err) + err = xerrors.Errorf("failed to create block: %w", err) + return nil, err } tCreateBlock := build.Clock.Now() @@ -523,7 +546,7 @@ func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (*types.BlockMsg, for i, header := range base.TipSet.Blocks() { parentMiners[i] = header.Miner } - log.Infow("mined new block", "cid", b.Cid(), "height", int64(b.Header.Height), "miner", b.Header.Miner, "parents", parentMiners, "parentTipset", base.TipSet.Key().String(), "took", dur) + log.Infow("mined new block", "cid", minedBlock.Cid(), "height", int64(minedBlock.Header.Height), "miner", minedBlock.Header.Miner, "parents", parentMiners, "parentTipset", base.TipSet.Key().String(), "took", dur) if dur > time.Second*time.Duration(build.BlockDelaySecs) { log.Warnw("CAUTION: block production took longer than the block delay. Your computer may not be fast enough to keep up", "tMinerBaseInfo ", tMBI.Sub(start), @@ -536,7 +559,7 @@ func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (*types.BlockMsg, "tCreateBlock ", tCreateBlock.Sub(tPending)) } - return b, nil + return minedBlock, nil } func (m *Miner) computeTicket(ctx context.Context, brand *types.BeaconEntry, base *MiningBase, mbi *api.MiningBaseInfo) (*types.Ticket, error) { From 429419f2101210fd3dd59ef145e0eee0286c06a3 Mon Sep 17 00:00:00 2001 From: Peter Rabbitson Date: Fri, 21 May 2021 15:30:08 +0200 Subject: [PATCH 051/257] Forgotten deadcode --- chain/gen/gen.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/chain/gen/gen.go b/chain/gen/gen.go index aa0f7df88..98bb3ea91 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -3,7 +3,6 @@ package gen import ( "bytes" "context" - "encoding/base64" "fmt" "io" "io/ioutil" @@ -635,8 +634,6 @@ func (wpp *wppProvider) ComputeProof(context.Context, []proof5.SectorInfo, abi.P return ValidWpostForTesting, nil } -var b64 = base64.URLEncoding.WithPadding(base64.NoPadding) - func IsRoundWinner(ctx context.Context, ts *types.TipSet, round abi.ChainEpoch, miner address.Address, brand types.BeaconEntry, mbi *api.MiningBaseInfo, a MiningCheckAPI) (*types.ElectionProof, error) { From 400780606804e19fc3f77f6a9bf0a55c86ff9e8e Mon Sep 17 00:00:00 2001 From: Peter Rabbitson Date: Sat, 22 May 2021 17:39:56 +0200 Subject: [PATCH 052/257] Incorporate the 'Time delta between now...' log into the 'completed mineOne' --- miner/miner.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/miner/miner.go b/miner/miner.go index 5fbc9aae3..7b85e558e 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -440,9 +440,12 @@ func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (minedBlock *type log.Infow( "completed mineOne", + "tookMilliseconds", (build.Clock.Now().UnixNano()-start.UnixNano())/1_000_000, "forRound", int64(round), "baseEpoch", int64(base.TipSet.Height()), - "beaconEpoch", rbase.Round, + "baseDeltaSeconds", uint64(start.Unix())-base.TipSet.MinTimestamp(), + "nullRounds", int64(base.NullRounds), + "beaconEpoch", uint64(rbase.Round), "lookbackEpochs", int64(policy.ChainFinality), // hardcoded as it is unlikely to change again: https://github.com/filecoin-project/lotus/blob/v1.8.0/chain/actors/policy/policy.go#L180-L186 "networkPowerAtLookback", mbi.NetworkPower.String(), "minerPowerAtLookback", mbi.MinerPower.String(), @@ -475,8 +478,6 @@ func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (minedBlock *type tPowercheck := build.Clock.Now() - log.Infof("Time delta between now and our mining base: %ds (nulls: %d)", uint64(build.Clock.Now().Unix())-base.TipSet.MinTimestamp(), base.NullRounds) - rbase = beaconPrev if len(bvals) > 0 { rbase = bvals[len(bvals)-1] From ef3ef8596081a6c19fec2c390c195b1998824bdd Mon Sep 17 00:00:00 2001 From: Peter Rabbitson Date: Sat, 22 May 2021 23:55:32 +0200 Subject: [PATCH 053/257] Add a `lateStart` indicator, differentiate on Error/Warn/Info --- miner/miner.go | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/miner/miner.go b/miner/miner.go index 7b85e558e..b2da25d8e 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -13,6 +13,7 @@ import ( proof2 "github.com/filecoin-project/specs-actors/v2/actors/runtime/proof" + "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/gen/slashfilter" @@ -438,13 +439,15 @@ func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (minedBlock *type } } - log.Infow( - "completed mineOne", - "tookMilliseconds", (build.Clock.Now().UnixNano()-start.UnixNano())/1_000_000, + isLate := uint64(start.Unix()) > (base.TipSet.MinTimestamp() + uint64(base.NullRounds*builtin.EpochDurationSeconds) + build.PropagationDelaySecs) + + logStruct := []interface{}{ + "tookMilliseconds", (build.Clock.Now().UnixNano() - start.UnixNano()) / 1_000_000, "forRound", int64(round), "baseEpoch", int64(base.TipSet.Height()), - "baseDeltaSeconds", uint64(start.Unix())-base.TipSet.MinTimestamp(), + "baseDeltaSeconds", uint64(start.Unix()) - base.TipSet.MinTimestamp(), "nullRounds", int64(base.NullRounds), + "lateStart", isLate, "beaconEpoch", uint64(rbase.Round), "lookbackEpochs", int64(policy.ChainFinality), // hardcoded as it is unlikely to change again: https://github.com/filecoin-project/lotus/blob/v1.8.0/chain/actors/policy/policy.go#L180-L186 "networkPowerAtLookback", mbi.NetworkPower.String(), @@ -452,7 +455,15 @@ func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (minedBlock *type "isEligible", mbi.EligibleForMining, "isWinner", (winner != nil), "error", err, - ) + } + + if err != nil { + log.Errorw("completed mineOne", logStruct...) + } else if isLate { + log.Warnw("completed mineOne", logStruct...) + } else { + log.Infow("completed mineOne", logStruct...) + } }() mbi, err = m.api.MinerGetBaseInfo(ctx, m.address, round, base.TipSet.Key()) From c198e7aa9a67d48008bf4c3d4fdb006428367687 Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Mon, 24 May 2021 10:04:37 -0400 Subject: [PATCH 054/257] make lint happy --- miner/miner.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miner/miner.go b/miner/miner.go index b2da25d8e..62bec5b55 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -448,7 +448,7 @@ func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (minedBlock *type "baseDeltaSeconds", uint64(start.Unix()) - base.TipSet.MinTimestamp(), "nullRounds", int64(base.NullRounds), "lateStart", isLate, - "beaconEpoch", uint64(rbase.Round), + "beaconEpoch", rbase.Round, "lookbackEpochs", int64(policy.ChainFinality), // hardcoded as it is unlikely to change again: https://github.com/filecoin-project/lotus/blob/v1.8.0/chain/actors/policy/policy.go#L180-L186 "networkPowerAtLookback", mbi.NetworkPower.String(), "minerPowerAtLookback", mbi.MinerPower.String(), From 36bc96a6bc1472b2493c6165592712bc3a266028 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Wed, 16 Jun 2021 13:41:52 -0400 Subject: [PATCH 055/257] Fix docsgen --- build/openrpc/full.json.gz | Bin 22485 -> 22484 bytes build/openrpc/miner.json.gz | Bin 8089 -> 8089 bytes build/openrpc/worker.json.gz | Bin 2579 -> 2579 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 1eb1ebf942b6dc8ee9d4eea0dcbc656d37db061a..2fee80bb1d1fe56d7cad5f1d80d458fe0ab222cd 100644 GIT binary patch delta 22342 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 delta 22338 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 diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index d4d24cdd539c616b66c9ef471c391d0a13027209..da845c6ee1c48c7335ecec8f72fee5b5ddd94b68 100644 GIT binary patch delta 22 ecmbPfKhu6f2b0u{ja}^W9EbMIxVo>0kpTc`@d+FN delta 22 ecmbPfKhu6f2h*$B8@t%$IldS3mF(+bWB>qf(+KbY diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index 19d041474e1b52cbe1c66613982bb5bb4e5f834b..945825467fb0703833cb416aba1486f9faac7026 100644 GIT binary patch delta 21 dcmbO%GFfCoBjfLlO`V(^`Bx8h_*XG7003e!2!H?p delta 21 ccmbO%GFfCoBV+Z(rcO?dJoBhB|0)Is08|YI=Kufz From b9f864ba9eb29ee55573d1b84d768520e65c71eb Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Wed, 16 Jun 2021 14:15:40 -0400 Subject: [PATCH 056/257] Expand on Drand change testing --- chain/sync_test.go | 52 +++++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/chain/sync_test.go b/chain/sync_test.go index 2cbd7e70c..a1bd4794b 100644 --- a/chain/sync_test.go +++ b/chain/sync_test.go @@ -85,6 +85,7 @@ type syncTestUtil struct { blocks []*store.FullTipSet nds []api.FullNode + us stmgr.UpgradeSchedule } func prepSyncTest(t testing.TB, h int) *syncTestUtil { @@ -104,9 +105,10 @@ func prepSyncTest(t testing.TB, h int) *syncTestUtil { mn: mocknet.New(ctx), g: g, + us: stmgr.DefaultUpgradeSchedule(), } - tu.addSourceNode(stmgr.DefaultUpgradeSchedule(), h) + tu.addSourceNode(h) //tu.checkHeight("source", source, h) @@ -119,7 +121,7 @@ func prepSyncTest(t testing.TB, h int) *syncTestUtil { func prepSyncTestWithV5Height(t testing.TB, h int, v5height abi.ChainEpoch) *syncTestUtil { logging.SetLogLevel("*", "INFO") - us := stmgr.UpgradeSchedule{{ + sched := stmgr.UpgradeSchedule{{ // prepare for upgrade. Network: network.Version9, Height: 1, @@ -138,7 +140,7 @@ func prepSyncTestWithV5Height(t testing.TB, h int, v5height abi.ChainEpoch) *syn Migration: stmgr.UpgradeActorsV5, }} - g, err := gen.NewGeneratorWithUpgradeSchedule(us) + g, err := gen.NewGeneratorWithUpgradeSchedule(sched) if err != nil { t.Fatalf("%+v", err) @@ -153,9 +155,10 @@ func prepSyncTestWithV5Height(t testing.TB, h int, v5height abi.ChainEpoch) *syn mn: mocknet.New(ctx), g: g, + us: sched, } - tu.addSourceNode(us, h) + tu.addSourceNode(h) //tu.checkHeight("source", source, h) // separate logs @@ -266,7 +269,7 @@ func (tu *syncTestUtil) mineNewBlock(src int, miners []int) { tu.g.CurTipset = mts } -func (tu *syncTestUtil) addSourceNode(us stmgr.UpgradeSchedule, gen int) { +func (tu *syncTestUtil) addSourceNode(gen int) { if tu.genesis != nil { tu.t.Fatal("source node already exists") } @@ -282,7 +285,7 @@ func (tu *syncTestUtil) addSourceNode(us stmgr.UpgradeSchedule, gen int) { node.Test(), node.Override(new(modules.Genesis), modules.LoadGenesis(genesis)), - node.Override(new(stmgr.UpgradeSchedule), us), + node.Override(new(stmgr.UpgradeSchedule), tu.us), ) require.NoError(tu.t, err) tu.t.Cleanup(func() { _ = stop(context.Background()) }) @@ -315,6 +318,7 @@ func (tu *syncTestUtil) addClientNode() int { node.Test(), node.Override(new(modules.Genesis), modules.LoadGenesis(tu.genesis)), + node.Override(new(stmgr.UpgradeSchedule), tu.us), ) require.NoError(tu.t, err) tu.t.Cleanup(func() { _ = stop(context.Background()) }) @@ -1010,18 +1014,24 @@ func TestDrandNull(t *testing.T) { build.UpgradeHyperdriveHeight = v5h tu := prepSyncTestWithV5Height(t, H, v5h) + p0 := tu.addClientNode() + p1 := tu.addClientNode() + + tu.loadChainToNode(p0) + tu.loadChainToNode(p1) + entropy := []byte{0, 2, 3, 4} // arbitrarily chosen pers := crypto.DomainSeparationTag_WinningPoStChallengeSeed beforeNull := tu.g.CurTipset - afterNull := tu.mineOnBlock(beforeNull, 0, nil, false, false, nil, 2) + afterNull := tu.mineOnBlock(beforeNull, p0, nil, false, false, nil, 2) nullHeight := beforeNull.TipSet().Height() + 1 if afterNull.TipSet().Height() == nullHeight { t.Fatal("didn't inject nulls as expected") } - rand, err := tu.nds[0].ChainGetRandomnessFromBeacon(tu.ctx, afterNull.TipSet().Key(), pers, nullHeight, entropy) + rand, err := tu.nds[p0].ChainGetRandomnessFromBeacon(tu.ctx, afterNull.TipSet().Key(), pers, nullHeight, entropy) require.NoError(t, err) // calculate the expected randomness based on the beacon BEFORE the null @@ -1032,20 +1042,20 @@ func TestDrandNull(t *testing.T) { require.Equal(t, []byte(rand), expectedRand) // zoom zoom to past the v5 upgrade by injecting many many nulls - postUpgrade := tu.mineOnBlock(afterNull, 0, nil, false, false, nil, v5h) - nv, err := tu.nds[0].StateNetworkVersion(tu.ctx, types.EmptyTSK) + postUpgrade := tu.mineOnBlock(afterNull, p0, nil, false, false, nil, v5h) + nv, err := tu.nds[p0].StateNetworkVersion(tu.ctx, postUpgrade.TipSet().Key()) require.NoError(t, err) if nv != network.Version13 { t.Fatal("expect to be v13 by now") } - afterNull = tu.mineOnBlock(postUpgrade, 0, nil, false, false, nil, 2) + afterNull = tu.mineOnBlock(postUpgrade, p0, nil, false, false, nil, 2) nullHeight = postUpgrade.TipSet().Height() + 1 if afterNull.TipSet().Height() == nullHeight { t.Fatal("didn't inject nulls as expected") } - rand, err = tu.nds[0].ChainGetRandomnessFromBeacon(tu.ctx, afterNull.TipSet().Key(), pers, nullHeight, entropy) + rand0, err := tu.nds[p0].ChainGetRandomnessFromBeacon(tu.ctx, afterNull.TipSet().Key(), pers, nullHeight, entropy) require.NoError(t, err) // calculate the expected randomness based on the beacon AFTER the null @@ -1053,7 +1063,21 @@ func TestDrandNull(t *testing.T) { expectedRand, err = store.DrawRandomness(expectedBE[len(expectedBE)-1].Data, pers, nullHeight, entropy) require.NoError(t, err) - require.Equal(t, []byte(rand), expectedRand) - build.UpgradeHyperdriveHeight = ov5h + require.Equal(t, []byte(rand0), expectedRand) + // Introduce p1 to friendly p0 who has all the blocks + require.NoError(t, tu.mn.LinkAll()) + tu.connect(p0, p1) + tu.waitUntilNodeHasTs(p1, afterNull.TipSet().Key()) + p1Head := tu.getHead(p1) + + // Yes, p1 syncs well to p0's chain + require.Equal(tu.t, p1Head.Key(), afterNull.TipSet().Key()) + + // Yes, p1 sources the same randomness as p0 + rand1, err := tu.nds[p1].ChainGetRandomnessFromBeacon(tu.ctx, afterNull.TipSet().Key(), pers, nullHeight, entropy) + require.NoError(t, err) + require.Equal(t, rand0, rand1) + + build.UpgradeHyperdriveHeight = ov5h } From 526674cefafc15d6665726cb8ef60700aba54c7c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 16 Jun 2021 16:39:23 -0700 Subject: [PATCH 057/257] fix: pick the correct partitions-per-post limit --- chain/actors/policy/policy.go | 6 +++--- chain/actors/policy/policy.go.template | 6 +++--- chain/actors/policy/policy_test.go | 10 ++++++++++ storage/wdpost_run.go | 2 +- storage/wdpost_run_test.go | 3 ++- 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/chain/actors/policy/policy.go b/chain/actors/policy/policy.go index 22ec4c742..c159dc98f 100644 --- a/chain/actors/policy/policy.go +++ b/chain/actors/policy/policy.go @@ -278,13 +278,13 @@ func GetMaxSectorExpirationExtension() abi.ChainEpoch { return miner5.MaxSectorExpirationExtension } -// TODO: we'll probably need to abstract over this better in the future. -func GetMaxPoStPartitions(p abi.RegisteredPoStProof) (int, error) { +func GetMaxPoStPartitions(nv network.Version, p abi.RegisteredPoStProof) (int, error) { sectorsPerPart, err := builtin5.PoStProofWindowPoStPartitionSectors(p) if err != nil { return 0, err } - return int(miner5.AddressedSectorsMax / sectorsPerPart), nil + maxSectors := uint64(GetAddressedSectorsMax(nv)) + return int(maxSectors / sectorsPerPart), nil } func GetDefaultSectorSize() abi.SectorSize { diff --git a/chain/actors/policy/policy.go.template b/chain/actors/policy/policy.go.template index 5d8100675..e27aac6e8 100644 --- a/chain/actors/policy/policy.go.template +++ b/chain/actors/policy/policy.go.template @@ -182,13 +182,13 @@ func GetMaxSectorExpirationExtension() abi.ChainEpoch { return miner{{.latestVersion}}.MaxSectorExpirationExtension } -// TODO: we'll probably need to abstract over this better in the future. -func GetMaxPoStPartitions(p abi.RegisteredPoStProof) (int, error) { +func GetMaxPoStPartitions(nv network.Version, p abi.RegisteredPoStProof) (int, error) { sectorsPerPart, err := builtin{{.latestVersion}}.PoStProofWindowPoStPartitionSectors(p) if err != nil { return 0, err } - return int(miner{{.latestVersion}}.AddressedSectorsMax / sectorsPerPart), nil + maxSectors := uint64(GetAddressedSectorsMax(nv)) + return int(maxSectors / sectorsPerPart), nil } func GetDefaultSectorSize() abi.SectorSize { diff --git a/chain/actors/policy/policy_test.go b/chain/actors/policy/policy_test.go index 24e47aaa0..f40250fba 100644 --- a/chain/actors/policy/policy_test.go +++ b/chain/actors/policy/policy_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/require" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/network" builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" paych0 "github.com/filecoin-project/specs-actors/actors/builtin/paych" @@ -68,3 +69,12 @@ func TestPartitionSizes(t *testing.T) { require.Equal(t, sizeOld, sizeNew) } } + +func TestPoStSize(t *testing.T) { + v12PoStSize, err := GetMaxPoStPartitions(network.Version12, abi.RegisteredPoStProof_StackedDrgWindow64GiBV1) + require.Equal(t, 4, v12PoStSize) + require.NoError(t, err) + v13PoStSize, err := GetMaxPoStPartitions(network.Version13, abi.RegisteredPoStProof_StackedDrgWindow64GiBV1) + require.NoError(t, err) + require.Equal(t, 10, v13PoStSize) +} diff --git a/storage/wdpost_run.go b/storage/wdpost_run.go index d590e3814..eeff94418 100644 --- a/storage/wdpost_run.go +++ b/storage/wdpost_run.go @@ -695,7 +695,7 @@ func (s *WindowPoStScheduler) batchPartitions(partitions []api.Partition, nv net // sectors per partition 3: ooo // partitions per message 2: oooOOO // <1><2> (3rd doesn't fit) - partitionsPerMsg, err := policy.GetMaxPoStPartitions(s.proofType) + partitionsPerMsg, err := policy.GetMaxPoStPartitions(nv, s.proofType) if err != nil { return nil, xerrors.Errorf("getting sectors per partition: %w", err) } diff --git a/storage/wdpost_run_test.go b/storage/wdpost_run_test.go index 916929724..3a009d5c7 100644 --- a/storage/wdpost_run_test.go +++ b/storage/wdpost_run_test.go @@ -31,6 +31,7 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/extern/sector-storage/storiface" "github.com/filecoin-project/lotus/journal" @@ -185,8 +186,8 @@ func TestWDPostDoPost(t *testing.T) { // Work out the number of partitions that can be included in a message // without exceeding the message sector limit + partitionsPerMsg, err := policy.GetMaxPoStPartitions(network.Version13, proofType) require.NoError(t, err) - partitionsPerMsg := int(miner5.AddressedSectorsMax / sectorsPerPartition) if partitionsPerMsg > miner5.AddressedPartitionsMax { partitionsPerMsg = miner5.AddressedPartitionsMax } From 71785f909942b1f4bfbc057fb0533cccc511c03d Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Wed, 16 Jun 2021 21:51:04 -0400 Subject: [PATCH 058/257] Lotus version 1.10.0-rc5 --- CHANGELOG.md | 4 ++-- build/openrpc/full.json.gz | Bin 22484 -> 22485 bytes build/openrpc/miner.json.gz | Bin 8089 -> 8089 bytes build/openrpc/worker.json.gz | Bin 2579 -> 2579 bytes build/version.go | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73e27e9ed..fbf620f50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ # Lotus changelog -# 1.10.0-rc4 / 2021-06-11 +# 1.10.0-rc5 / 2021-06-16 > Note: If you are running a lotus miner, check out the doc [here](https://docs.filecoin.io/mine/lotus/miner-configuration/#precommitsectorsbatch) for new lotus miner configurations explanations of the new features! -This is the 4th release candidate for Lotus v1.10.0, an upcoming mandatory release of Lotus that will introduce Filecoin network v13. Included in the new network version are the following FIPs: +This is the 5th release candidate for Lotus v1.10.0, an upcoming mandatory release of Lotus that will introduce Filecoin network v13. Included in the new network version are the following FIPs: - [FIP-0008](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0008.md): Add miner batched sector pre-commit method - [FIP-0011](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0011.md): Remove reward auction from reporting consensus faults diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 2fee80bb1d1fe56d7cad5f1d80d458fe0ab222cd..82226eccfca98a437a2ba640e842c63f26ca58c9 100644 GIT binary patch delta 22344 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 delta 22338 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 hw*!fosc|+8rz#M-0 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 literal 8089 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 diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index 945825467fb0703833cb416aba1486f9faac7026..83f2c5f436cc261667fca9840862065bba4a45f2 100644 GIT binary patch delta 2476 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 delta 2476 zcmV;d2~+ly6q6K?fq(mWVPOx{j5`t!zCB?^JPrCgb;0J8wLUn`h=L8+!VXAVP+u7o zx3{-be$6HUv(X!ge*4PB9f&I>0$UxSCD@h7V>+j=$n3w>zpWZgx=_nbbmr@Xbp+$UYuJ9gy7-` zJzcYFB|rs&A=$cM3Iro=D?zFzO%tLA)TocH+2Wer-riyh%fR!9nBxIn4PfT1hmqF| zTi7RpcxDL#p_g$yKlZt#;gUsPr>lYCIF-O0ck>Xj)>f z01y$eVnk*Lv9JmWSbkotNZj0a5x4O1cd!*sPpSffZ%Yg}3kwVQJD>s_JWwJ4y&1j$ zn<(89PHthXcB`A&tpbiMwP!4F-5U#21rbwZW{@7stbY}v5)rgE$!|-T8@;#7HX;O> zSD$4sGjhc_)|p3QT~0dP{!PxzDo&$cC|X)kl#{ArNznw9qUc6O0n)n?ckBe3xn|F%)%aJiBC~y!{ZAp zG57t`RDZQqPqs}F{S$YfhP${Vx16uoIEAm!DJ){o z-FZw~aF?=PUsjMxaUGUGT*{_6L6lS?Y}t+vml5M~bb0(9Y7^DCag7^yXl`8ZeSsCH zjDNWF2Sj^O(VVA%tLA@}?U#g2Ygy;@(U)erG0%G;Li=Ez*SM@+uBO7$ex6JusB&u? zCRS=LOwG(sW7!(Zc4(HZ|2E&UU9btSXwNiAerjU<96OMHSFDDPx_@IpsdT2)HtbgE; zY(&&dRx~#uOuiRtqFIow$Q0%lJc0^h@Jy%O2I51)t|0Qb4DT&Z;}%x< zr6wu=TO+s`zwRTqi5t?M_w84p(^;v&e~aP#zKX-!f+NF`;5JBk1yUO3sw5XF;CulI z0_wEBeA(W%NE(s`-a=DgAoMmw9o;#4m;sCWT{GW| zhomAxRVyINle>7HnKev)Q{tSYl;PV+;nHSPb|A7)ka)iYQmfiBhAWCiC~tigRDpC7LdrGhUw!tT9lps)?v)hn zLr$_ed~b|fW84nfxV$NnI8SMMD#K?v)_zgE`OTwU=kn8-Kgh*rh|WOTB`kmwNY_ zwsWCr(@mz2^bXdpP+4dkC=_3DVo^;^;@*%pY=6!ii4;d#iIOQ%pg$-g?~rCxCvdk|NA_#poNWrVKK4#ei_Wx+2ll~n z!mGbkSw0P29B2s2rkglFgR7YRw-zjf$&7N3fF@rE+UBF1ua6ZAvf6(Ud3SCURz)^U zKo+tf5Lat484hC}sCkwh(>Qm}iW&OKzfIe|@3ieOe1B?p8`k_8);udn#^^mU_O=FR z8o;eGW{k4o%U8jdZwrF4<(^c1ekF#>56C>?f6H$CjSQim7MsGfB=&sfT=qc6*FmS# z17uzn`RVX$1L2uf+T6cPN@mIHnsb)yK6nZ%{(PeJqKr}*iPmo$Qy2%HTzFRydztFR zIxj{fAb;z=Uzhhv#B)E>m3tU|ifrn%hSaH%F;CB-xi8S9&P%<8_Y&W4IM|=$I!YU^*0ePZ8%6OW=o=bJDG5%fv- znh`l`r5R^CmjRj|?Pv~~nu8|e3%)PNOh&R?(GQHE^5dFVJv{G+t#It*J;|bpt=e~F z^Li(}?z`GnZ(rG`Ae;D=Q1!2Xf~v?sF+EOKF+Sn{^7XJ+Z0RR7Q)xwBRdH?`GZP1ti diff --git a/build/version.go b/build/version.go index c9d1a5cce..d5c126068 100644 --- a/build/version.go +++ b/build/version.go @@ -29,7 +29,7 @@ func buildType() string { } // BuildVersion is the local build version, set by build system -const BuildVersion = "1.10.0-rc4" +const BuildVersion = "1.10.0-rc5" func UserVersion() string { return BuildVersion + buildType() + CurrentCommit From 8f608dff45370a955472312f71ae4701a6acf45a Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Thu, 17 Jun 2021 09:37:35 +0200 Subject: [PATCH 059/257] refactor: sector pledge test to use kit2 --- itests/kit2/node_opts_nv.go | 4 +- itests/sector_pledge_test.go | 188 ++++++++--------------------------- 2 files changed, 43 insertions(+), 149 deletions(-) diff --git a/itests/kit2/node_opts_nv.go b/itests/kit2/node_opts_nv.go index 6f682bd3a..39de9d9e2 100644 --- a/itests/kit2/node_opts_nv.go +++ b/itests/kit2/node_opts_nv.go @@ -58,10 +58,8 @@ func InstantaneousNetworkVersion(version network.Version) node.Option { } func NetworkUpgradeAt(version network.Version, upgradeHeight abi.ChainEpoch) node.Option { - fullSchedule := stmgr.UpgradeSchedule{} - schedule := stmgr.UpgradeSchedule{} - for _, upgrade := range fullSchedule { + for _, upgrade := range DefaultTestUpgradeSchedule { if upgrade.Network > version { break } diff --git a/itests/sector_pledge_test.go b/itests/sector_pledge_test.go index e3d2a843c..62daa1add 100644 --- a/itests/sector_pledge_test.go +++ b/itests/sector_pledge_test.go @@ -4,70 +4,40 @@ import ( "context" "fmt" "strings" - "sync/atomic" "testing" "time" + "github.com/stretchr/testify/require" + "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/stmgr" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" - "github.com/filecoin-project/lotus/itests/kit" - bminer "github.com/filecoin-project/lotus/miner" - "github.com/filecoin-project/lotus/node" - "github.com/filecoin-project/lotus/node/impl" - "github.com/stretchr/testify/require" + "github.com/filecoin-project/lotus/itests/kit2" ) func TestPledgeSectors(t *testing.T) { - kit.QuietMiningLogs() + kit2.QuietMiningLogs() - runTest := func(t *testing.T, b kit.APIBuilder, blocktime time.Duration, nSectors int) { + blockTime := 50 * time.Millisecond + + runTest := func(t *testing.T, nSectors int) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - n, sn := b(t, kit.OneFull, kit.OneMiner) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] + _, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs()) + ens.InterconnectAll().BeginMining(blockTime) - 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) - } - } - }() - - kit.PledgeSectors(t, ctx, miner, nSectors, 0, nil) - - atomic.StoreInt64(&mine, 0) - <-done + miner.PledgeSectors(ctx, nSectors, 0, nil) } t.Run("1", func(t *testing.T) { - runTest(t, kit.MockMinerBuilder, 50*time.Millisecond, 1) + runTest(t, 1) }) t.Run("100", func(t *testing.T) { - runTest(t, kit.MockMinerBuilder, 50*time.Millisecond, 100) + runTest(t, 100) }) t.Run("1000", func(t *testing.T) { @@ -75,52 +45,24 @@ func TestPledgeSectors(t *testing.T) { t.Skip("skipping test in short mode") } - runTest(t, kit.MockMinerBuilder, 50*time.Millisecond, 1000) + runTest(t, 1000) }) } func TestPledgeBatching(t *testing.T) { - runTest := func(t *testing.T, b kit.APIBuilder, blocktime time.Duration, nSectors int) { + blockTime := 50 * time.Millisecond + + runTest := func(t *testing.T, nSectors int) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - n, sn := b(t, []kit.FullNodeOpts{kit.FullNodeWithLatestActorsAt(-1)}, kit.OneMiner) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] + opts := kit2.ConstructorOpts(kit2.LatestActorsAt(-1)) + client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), opts) + ens.InterconnectAll().BeginMining(blockTime) - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } + waitTillChainHeight(ctx, t, client, 10) - 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) - } - } - }() - - for { - h, err := client.ChainHead(ctx) - require.NoError(t, err) - if h.Height() > 10 { - break - } - } - - toCheck := kit.StartPledge(t, ctx, miner, nSectors, 0, nil) + toCheck := miner.StartPledge(ctx, nSectors, 0, nil) for len(toCheck) > 0 { states := map[api.SectorState]int{} @@ -157,80 +99,27 @@ func TestPledgeBatching(t *testing.T) { build.Clock.Sleep(100 * time.Millisecond) fmt.Printf("WaitSeal: %d %+v\n", len(toCheck), states) } - - atomic.StoreInt64(&mine, 0) - <-done } t.Run("100", func(t *testing.T) { - runTest(t, kit.MockMinerBuilder, 50*time.Millisecond, 100) + runTest(t, 100) }) } func TestPledgeBeforeNv13(t *testing.T) { - runTest := func(t *testing.T, b kit.APIBuilder, blocktime time.Duration, nSectors int) { + blocktime := 50 * time.Millisecond + + runTest := func(t *testing.T, nSectors int) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - n, sn := b(t, []kit.FullNodeOpts{ - { - Opts: func(nodes []kit.TestFullNode) node.Option { - return node.Override(new(stmgr.UpgradeSchedule), stmgr.UpgradeSchedule{{ - Network: network.Version9, - Height: 1, - Migration: stmgr.UpgradeActorsV2, - }, { - Network: network.Version10, - Height: 2, - Migration: stmgr.UpgradeActorsV3, - }, { - Network: network.Version12, - Height: 3, - Migration: stmgr.UpgradeActorsV4, - }, { - Network: network.Version13, - Height: 1000000000, - Migration: stmgr.UpgradeActorsV5, - }}) - }, - }, - }, kit.OneMiner) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] + opts := kit2.ConstructorOpts(kit2.LatestActorsAt(1000000000)) + client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), opts) + ens.InterconnectAll().BeginMining(blocktime) - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } + waitTillChainHeight(ctx, t, client, 10) - 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) - } - } - }() - - for { - h, err := client.ChainHead(ctx) - require.NoError(t, err) - if h.Height() > 10 { - break - } - } - - toCheck := kit.StartPledge(t, ctx, miner, nSectors, 0, nil) + toCheck := miner.StartPledge(ctx, nSectors, 0, nil) for len(toCheck) > 0 { states := map[api.SectorState]int{} @@ -250,12 +139,19 @@ func TestPledgeBeforeNv13(t *testing.T) { build.Clock.Sleep(100 * time.Millisecond) fmt.Printf("WaitSeal: %d %+v\n", len(toCheck), states) } - - atomic.StoreInt64(&mine, 0) - <-done } t.Run("100-before-nv13", func(t *testing.T) { - runTest(t, kit.MockMinerBuilder, 50*time.Millisecond, 100) + runTest(t, 100) }) } + +func waitTillChainHeight(ctx context.Context, t *testing.T, node *kit2.TestFullNode, height int) { + for { + h, err := node.ChainHead(ctx) + require.NoError(t, err) + if h.Height() > abi.ChainEpoch(height) { + return + } + } +} From 6567887e6e409c7a304da2e3bb445c808681adce Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Thu, 17 Jun 2021 10:40:41 +0200 Subject: [PATCH 060/257] refactor: sector terminate test to kit2 --- itests/kit2/chain.go | 21 ++++++++ itests/kit2/ensemble_opts.go | 2 +- itests/sector_pledge_test.go | 16 +----- itests/sector_terminate_test.go | 86 +++++++-------------------------- 4 files changed, 42 insertions(+), 83 deletions(-) create mode 100644 itests/kit2/chain.go diff --git a/itests/kit2/chain.go b/itests/kit2/chain.go new file mode 100644 index 000000000..9563bae9f --- /dev/null +++ b/itests/kit2/chain.go @@ -0,0 +1,21 @@ +package kit2 + +import ( + "context" + "testing" + "time" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/stretchr/testify/require" +) + +func WaitTillChainHeight(ctx context.Context, t *testing.T, node *TestFullNode, blocktime time.Duration, height int) abi.ChainEpoch { + for { + h, err := node.ChainHead(ctx) + require.NoError(t, err) + if h.Height() > abi.ChainEpoch(height) { + return h.Height() + } + time.Sleep(blocktime) + } +} diff --git a/itests/kit2/ensemble_opts.go b/itests/kit2/ensemble_opts.go index c7edb99a6..f8bc81535 100644 --- a/itests/kit2/ensemble_opts.go +++ b/itests/kit2/ensemble_opts.go @@ -23,7 +23,7 @@ type ensembleOpts struct { } var DefaultEnsembleOpts = ensembleOpts{ - pastOffset: 100000 * time.Second, // time sufficiently in the past to trigger catch-up mining. + pastOffset: 10000000 * time.Second, // time sufficiently in the past to trigger catch-up mining. proofType: abi.RegisteredSealProof_StackedDrg2KiBV1, } diff --git a/itests/sector_pledge_test.go b/itests/sector_pledge_test.go index 62daa1add..73fd2204e 100644 --- a/itests/sector_pledge_test.go +++ b/itests/sector_pledge_test.go @@ -9,8 +9,6 @@ import ( "github.com/stretchr/testify/require" - "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" @@ -60,7 +58,7 @@ func TestPledgeBatching(t *testing.T) { client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), opts) ens.InterconnectAll().BeginMining(blockTime) - waitTillChainHeight(ctx, t, client, 10) + kit2.WaitTillChainHeight(ctx, t, client, blockTime, 10) toCheck := miner.StartPledge(ctx, nSectors, 0, nil) @@ -117,7 +115,7 @@ func TestPledgeBeforeNv13(t *testing.T) { client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), opts) ens.InterconnectAll().BeginMining(blocktime) - waitTillChainHeight(ctx, t, client, 10) + kit2.WaitTillChainHeight(ctx, t, client, blocktime, 10) toCheck := miner.StartPledge(ctx, nSectors, 0, nil) @@ -145,13 +143,3 @@ func TestPledgeBeforeNv13(t *testing.T) { runTest(t, 100) }) } - -func waitTillChainHeight(ctx context.Context, t *testing.T, node *kit2.TestFullNode, height int) { - for { - h, err := node.ChainHead(ctx) - require.NoError(t, err) - if h.Height() > abi.ChainEpoch(height) { - return - } - } -} diff --git a/itests/sector_terminate_test.go b/itests/sector_terminate_test.go index b00337c7e..d1852ec39 100644 --- a/itests/sector_terminate_test.go +++ b/itests/sector_terminate_test.go @@ -2,18 +2,15 @@ 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/itests/kit" - "github.com/filecoin-project/lotus/node/impl" + "github.com/filecoin-project/lotus/itests/kit2" "github.com/stretchr/testify/require" ) @@ -22,7 +19,7 @@ func TestTerminate(t *testing.T) { t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") } - kit.QuietMiningLogs() + kit2.QuietMiningLogs() const blocktime = 2 * time.Millisecond @@ -31,42 +28,9 @@ func TestTerminate(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - n, sn := kit.MockMinerBuilder(t, - []kit.FullNodeOpts{kit.FullNodeWithLatestActorsAt(-1)}, - []kit.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, kit.MineNext); err != nil { - if ctx.Err() != nil { - // context was canceled, ignore the error. - return - } - t.Error(err) - } - } - }() - defer func() { - cancel() - <-done - }() + opts := kit2.ConstructorOpts(kit2.LatestActorsAt(-1)) + client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), opts) + ens.InterconnectAll().BeginMining(blocktime) maddr, err := miner.ActorAddress(ctx) require.NoError(t, err) @@ -79,11 +43,11 @@ func TestTerminate(t *testing.T) { require.Equal(t, p.MinerPower, p.TotalPower) require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*nSectors)) - fmt.Printf("Seal a sector\n") + t.Log("Seal a sector") - kit.PledgeSectors(t, ctx, miner, 1, 0, nil) + miner.PledgeSectors(ctx, 1, 0, nil) - fmt.Printf("wait for power\n") + t.Log("wait for power") { // Wait until proven. @@ -91,17 +55,10 @@ func TestTerminate(t *testing.T) { require.NoError(t, err) waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 2 - fmt.Printf("End for head.Height > %d\n", waitUntil) + t.Logf("End for head.Height > %d", 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 - } - } + height := kit2.WaitTillChainHeight(ctx, t, client, blocktime, int(waitUntil)) + t.Logf("Now head.Height = %d", height) } nSectors++ @@ -111,7 +68,7 @@ func TestTerminate(t *testing.T) { require.Equal(t, p.MinerPower, p.TotalPower) require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*nSectors)) - fmt.Println("Terminate a sector") + t.Log("Terminate a sector") toTerminate := abi.SectorNumber(3) @@ -124,7 +81,7 @@ loop: si, err := miner.SectorsStatus(ctx, toTerminate, false) require.NoError(t, err) - fmt.Println("state: ", si.State, msgTriggerred) + t.Log("state: ", si.State, msgTriggerred) switch sealing.SectorState(si.State) { case sealing.Terminating: @@ -140,7 +97,7 @@ loop: require.NoError(t, err) if c != nil { msgTriggerred = true - fmt.Println("terminate message:", c) + t.Log("terminate message:", c) { p, err := miner.SectorTerminatePending(ctx) @@ -180,18 +137,11 @@ loop: 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) + waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 2 + t.Logf("End for head.Height > %d", waitUntil) + height := kit2.WaitTillChainHeight(ctx, t, client, blocktime, int(waitUntil)) + t.Logf("Now head.Height = %d", height) p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) require.NoError(t, err) From e9325fecc5c9f914ffb81751a45021d49dbf7212 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Thu, 17 Jun 2021 11:57:16 +0200 Subject: [PATCH 061/257] refactor: wdpost test to kit2 --- itests/wdpost_test.go | 155 ++++++++++++------------------------------ 1 file changed, 45 insertions(+), 110 deletions(-) diff --git a/itests/wdpost_test.go b/itests/wdpost_test.go index f59465f05..317a7a529 100644 --- a/itests/wdpost_test.go +++ b/itests/wdpost_test.go @@ -7,18 +7,18 @@ import ( "testing" "time" - "github.com/filecoin-project/go-state-types/big" - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/itests/kit" "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/go-state-types/big" "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/types" + "github.com/filecoin-project/lotus/extern/sector-storage/mock" + "github.com/filecoin-project/lotus/itests/kit2" "github.com/filecoin-project/lotus/node/impl" ) @@ -27,7 +27,7 @@ func TestWindowedPost(t *testing.T) { t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") } - kit.QuietMiningLogs() + kit2.QuietMiningLogs() var ( blocktime = 2 * time.Millisecond @@ -41,50 +41,20 @@ func TestWindowedPost(t *testing.T) { } { height := height // copy to satisfy lints t.Run(fmt.Sprintf("upgrade-%d", height), func(t *testing.T) { - testWindowPostUpgrade(t, kit.MockMinerBuilder, blocktime, nSectors, height) + testWindowPostUpgrade(t, blocktime, nSectors, height) }) } } -func testWindowPostUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Duration, nSectors int, upgradeHeight abi.ChainEpoch) { +func testWindowPostUpgrade(t *testing.T, blocktime time.Duration, nSectors int, upgradeHeight abi.ChainEpoch) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - n, sn := b(t, []kit.FullNodeOpts{kit.FullNodeWithLatestActorsAt(upgradeHeight)}, kit.OneMiner) + opts := kit2.ConstructorOpts(kit2.LatestActorsAt(upgradeHeight)) + client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), opts) + ens.InterconnectAll().BeginMining(blocktime) - 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, kit.MineNext); err != nil { - if ctx.Err() != nil { - // context was canceled, ignore the error. - return - } - t.Error(err) - } - } - }() - defer func() { - cancel() - <-done - }() - - kit.PledgeSectors(t, ctx, miner, nSectors, 0, nil) + miner.PledgeSectors(ctx, nSectors, 0, nil) maddr, err := miner.ActorAddress(ctx) require.NoError(t, err) @@ -95,19 +65,12 @@ func testWindowPostUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Durati 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) + t.Log("Running one proving period") + waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 2 + t.Logf("End for head.Height > %d", waitUntil) - 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) - } + height := kit2.WaitTillChainHeight(ctx, t, client, blocktime, int(waitUntil)) + t.Logf("Now head.Height = %d", height) p, err := client.StateMinerPower(ctx, maddr, types.EmptyTSK) require.NoError(t, err) @@ -116,9 +79,9 @@ func testWindowPostUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Durati require.NoError(t, err) require.Equal(t, p.MinerPower, p.TotalPower) - require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*uint64(nSectors+kit.GenesisPreseals))) + require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*uint64(nSectors+kit2.DefaultPresealsPerBootstrapMiner))) - fmt.Printf("Drop some sectors\n") + t.Log("Drop some sectors") // Drop 2 sectors from deadline 2 partition 0 (full partition / deadline) { @@ -162,7 +125,7 @@ func testWindowPostUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Durati all, err := secs.All(2) require.NoError(t, err) - fmt.Println("the sectors", all) + t.Log("the sectors", all) s = storage.SectorRef{ ID: abi.SectorID{ @@ -178,20 +141,12 @@ func testWindowPostUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Durati 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) + t.Log("Go through another PP, wait for sectors to become faulty") + waitUntil = di.PeriodStart + di.WPoStProvingPeriod + 2 + t.Logf("End for head.Height > %d", waitUntil) - 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) - } + height = kit2.WaitTillChainHeight(ctx, t, client, blocktime, int(waitUntil)) + t.Logf("Now head.Height = %d", height) p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) require.NoError(t, err) @@ -199,9 +154,9 @@ func testWindowPostUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Durati require.Equal(t, p.MinerPower, p.TotalPower) sectors := p.MinerPower.RawBytePower.Uint64() / uint64(ssz) - require.Equal(t, nSectors+kit.GenesisPreseals-3, int(sectors)) // -3 just removed sectors + require.Equal(t, nSectors+kit2.DefaultPresealsPerBootstrapMiner-3, int(sectors)) // -3 just removed sectors - fmt.Printf("Recover one sector\n") + t.Log("Recover one sector") err = miner.StorageMiner.(*impl.StorageMinerAPI).IStorageMgr.(*mock.SectorMgr).MarkFailed(s, false) require.NoError(t, err) @@ -209,19 +164,11 @@ func testWindowPostUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Durati 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) + waitUntil = di.PeriodStart + di.WPoStProvingPeriod + 2 + t.Logf("End for head.Height > %d", waitUntil) - 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) - } + height = kit2.WaitTillChainHeight(ctx, t, client, blocktime, int(waitUntil)) + t.Logf("Now head.Height = %d", height) p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) require.NoError(t, err) @@ -229,11 +176,11 @@ func testWindowPostUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Durati require.Equal(t, p.MinerPower, p.TotalPower) sectors = p.MinerPower.RawBytePower.Uint64() / uint64(ssz) - require.Equal(t, nSectors+kit.GenesisPreseals-2, int(sectors)) // -2 not recovered sectors + require.Equal(t, nSectors+kit2.DefaultPresealsPerBootstrapMiner-2, int(sectors)) // -2 not recovered sectors // pledge a sector after recovery - kit.PledgeSectors(t, ctx, miner, 1, nSectors, nil) + miner.PledgeSectors(ctx, 1, nSectors, nil) { // Wait until proven. @@ -241,17 +188,10 @@ func testWindowPostUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Durati require.NoError(t, err) waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 2 - fmt.Printf("End for head.Height > %d\n", waitUntil) + t.Logf("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 - } - } + height = kit2.WaitTillChainHeight(ctx, t, client, blocktime, int(waitUntil)) + t.Logf("Now head.Height = %d", height) } p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) @@ -260,7 +200,7 @@ func testWindowPostUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Durati require.Equal(t, p.MinerPower, p.TotalPower) sectors = p.MinerPower.RawBytePower.Uint64() / uint64(ssz) - require.Equal(t, nSectors+kit.GenesisPreseals-2+1, int(sectors)) // -2 not recovered sectors + 1 just pledged + require.Equal(t, nSectors+kit2.DefaultPresealsPerBootstrapMiner-2+1, int(sectors)) // -2 not recovered sectors + 1 just pledged } func TestWindowPostBaseFeeNoBurn(t *testing.T) { @@ -268,7 +208,7 @@ func TestWindowPostBaseFeeNoBurn(t *testing.T) { t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") } - kit.QuietMiningLogs() + kit2.QuietMiningLogs() var ( blocktime = 2 * time.Millisecond @@ -281,11 +221,8 @@ func TestWindowPostBaseFeeNoBurn(t *testing.T) { och := build.UpgradeClausHeight build.UpgradeClausHeight = 10 - n, sn := kit.MockMinerBuilder(t, kit.DefaultFullOpts(1), kit.OneMiner) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - bm := kit.ConnectAndStartMining(t, blocktime, miner, client) - t.Cleanup(bm.Stop) + client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs()) + ens.InterconnectAll().BeginMining(blocktime) maddr, err := miner.ActorAddress(ctx) require.NoError(t, err) @@ -293,7 +230,7 @@ func TestWindowPostBaseFeeNoBurn(t *testing.T) { mi, err := client.StateMinerInfo(ctx, maddr, types.EmptyTSK) require.NoError(t, err) - kit.PledgeSectors(t, ctx, miner, nSectors, 0, nil) + miner.PledgeSectors(ctx, nSectors, 0, nil) wact, err := client.StateGetActor(ctx, mi.Worker, types.EmptyTSK) require.NoError(t, err) en := wact.Nonce @@ -327,18 +264,16 @@ func TestWindowPostBaseFeeBurn(t *testing.T) { t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") } - kit.QuietMiningLogs() + kit2.QuietMiningLogs() ctx, cancel := context.WithCancel(context.Background()) defer cancel() blocktime := 2 * time.Millisecond - n, sn := kit.MockMinerBuilder(t, []kit.FullNodeOpts{kit.FullNodeWithLatestActorsAt(-1)}, kit.OneMiner) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - bm := kit.ConnectAndStartMining(t, blocktime, miner, client) - t.Cleanup(bm.Stop) + opts := kit2.ConstructorOpts(kit2.LatestActorsAt(-1)) + client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), opts) + ens.InterconnectAll().BeginMining(blocktime) maddr, err := miner.ActorAddress(ctx) require.NoError(t, err) @@ -346,7 +281,7 @@ func TestWindowPostBaseFeeBurn(t *testing.T) { mi, err := client.StateMinerInfo(ctx, maddr, types.EmptyTSK) require.NoError(t, err) - kit.PledgeSectors(t, ctx, miner, 10, 0, nil) + miner.PledgeSectors(ctx, 10, 0, nil) wact, err := client.StateGetActor(ctx, mi.Worker, types.EmptyTSK) require.NoError(t, err) en := wact.Nonce From 3de6beabe3fee146de2e06eb795f911b577a8417 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Thu, 17 Jun 2021 14:22:13 +0200 Subject: [PATCH 062/257] refactor: wdpost dispute test to use kit2 --- itests/wdpost_dispute_test.go | 140 +++++++++------------------------- 1 file changed, 37 insertions(+), 103 deletions(-) diff --git a/itests/wdpost_dispute_test.go b/itests/wdpost_dispute_test.go index 6c7302af3..8661fba00 100644 --- a/itests/wdpost_dispute_test.go +++ b/itests/wdpost_dispute_test.go @@ -2,7 +2,6 @@ package itests import ( "context" - "fmt" "os" "testing" "time" @@ -16,8 +15,7 @@ import ( "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/itests/kit" - "github.com/filecoin-project/lotus/node/impl" + "github.com/filecoin-project/lotus/itests/kit2" proof3 "github.com/filecoin-project/specs-actors/v3/actors/runtime/proof" "github.com/stretchr/testify/require" ) @@ -27,28 +25,29 @@ func TestWindowPostDispute(t *testing.T) { t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") } - kit.QuietMiningLogs() + kit2.QuietMiningLogs() - b := kit.MockMinerBuilder blocktime := 2 * time.Millisecond ctx, cancel := context.WithCancel(context.Background()) defer cancel() + var ( + client kit2.TestFullNode + chainMiner kit2.TestMiner + evilMiner kit2.TestMiner + ) + // 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, - []kit.FullNodeOpts{kit.FullNodeWithLatestActorsAt(-1)}, - []kit.StorageMiner{ - {Full: 0, Preseal: kit.PresealGenesis}, - {Full: 0}, - }) - - client := n[0].FullNode.(*impl.FullNodeAPI) - chainMiner := sn[0] - evilMiner := sn[1] + opts := kit2.ConstructorOpts(kit2.LatestActorsAt(-1)) + ens := kit2.NewEnsemble(t, kit2.MockProofs()). + FullNode(&client, opts). + Miner(&chainMiner, &client, opts). + Miner(&evilMiner, &client, opts, kit2.PresealSectors(0)). + Start() { addrinfo, err := client.NetAddrsListen(ctx) @@ -68,32 +67,13 @@ func TestWindowPostDispute(t *testing.T) { 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, kit.MineNext); err != nil { - if ctx.Err() != nil { - // context was canceled, ignore the error. - return - } - t.Error(err) - } - } - }() - defer func() { - cancel() - <-done - }() + ens.BeginMining(blocktime, &chainMiner) // Give the chain miner enough sectors to win every block. - kit.PledgeSectors(t, ctx, chainMiner, 10, 0, nil) + chainMiner.PledgeSectors(ctx, 10, 0, nil) // And the evil one 1 sector. No cookie for you. - kit.PledgeSectors(t, ctx, evilMiner, 1, 0, nil) + evilMiner.PledgeSectors(ctx, 1, 0, nil) // Let the evil miner's sectors gain power. evilMinerAddr, err := evilMiner.ActorAddress(ctx) @@ -102,19 +82,13 @@ func TestWindowPostDispute(t *testing.T) { 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) + t.Logf("Running one proving period\n") - for { - head, err := client.ChainHead(ctx) - require.NoError(t, err) + waitUntil := di.PeriodStart + di.WPoStProvingPeriod*2 + t.Logf("End for head.Height > %d", waitUntil) - if head.Height() > di.PeriodStart+di.WPoStProvingPeriod*2 { - fmt.Printf("Now head.Height = %d\n", head.Height()) - break - } - build.Clock.Sleep(blocktime) - } + height := kit2.WaitTillChainHeight(ctx, t, &client, blocktime, int(waitUntil)) + t.Logf("Now head.Height = %d", height) p, err := client.StateMinerPower(ctx, evilMinerAddr, types.EmptyTSK) require.NoError(t, err) @@ -131,12 +105,12 @@ func TestWindowPostDispute(t *testing.T) { evilSectorLoc, err := client.StateSectorPartition(ctx, evilMinerAddr, evilSectorNo, types.EmptyTSK) require.NoError(t, err) - fmt.Println("evil miner stopping") + t.Log("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") + t.Log("evil miner stopped") // Wait until we need to prove our sector. for { @@ -161,7 +135,7 @@ func TestWindowPostDispute(t *testing.T) { build.Clock.Sleep(blocktime) } - fmt.Println("accepted evil proof") + t.Log("accepted evil proof") // Make sure the evil node didn't lose any power. p, err = client.StateMinerPower(ctx, evilMinerAddr, types.EmptyTSK) @@ -188,7 +162,7 @@ func TestWindowPostDispute(t *testing.T) { sm, err := client.MpoolPushMessage(ctx, msg, nil) require.NoError(t, err) - fmt.Println("waiting dispute") + t.Log("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()) @@ -258,29 +232,16 @@ func TestWindowPostDisputeFails(t *testing.T) { t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") } - kit.QuietMiningLogs() + kit2.QuietMiningLogs() - b := kit.MockMinerBuilder blocktime := 2 * time.Millisecond ctx, cancel := context.WithCancel(context.Background()) defer cancel() - n, sn := b(t, []kit.FullNodeOpts{kit.FullNodeWithLatestActorsAt(-1)}, kit.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) - } - } + opts := kit2.ConstructorOpts(kit2.LatestActorsAt(-1)) + client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), opts) + ens.InterconnectAll().BeginMining(blocktime) defaultFrom, err := client.WalletDefaultAddress(ctx) require.NoError(t, err) @@ -290,48 +251,21 @@ func TestWindowPostDisputeFails(t *testing.T) { 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, kit.MineNext); err != nil { - if ctx.Err() != nil { - // context was canceled, ignore the error. - return - } - t.Error(err) - } - } - }() - defer func() { - cancel() - <-done - }() - - kit.PledgeSectors(t, ctx, miner, 10, 0, nil) + miner.PledgeSectors(ctx, 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) + t.Log("Running one proving period") + waitUntil := di.PeriodStart + di.WPoStProvingPeriod*2 + t.Logf("End for head.Height > %d", waitUntil) - 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) - } + height := kit2.WaitTillChainHeight(ctx, t, client, blocktime, int(waitUntil)) + t.Logf("Now head.Height = %d", height) ssz, err := miner.ActorSectorSize(ctx, maddr) require.NoError(t, err) - expectedPower := types.NewInt(uint64(ssz) * (kit.GenesisPreseals + 10)) + expectedPower := types.NewInt(uint64(ssz) * (kit2.DefaultPresealsPerBootstrapMiner + 10)) p, err := client.StateMinerPower(ctx, maddr, types.EmptyTSK) require.NoError(t, err) From 7a9769b8078b4c3994ff23b95a91f0cd3c851bde Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Thu, 17 Jun 2021 14:58:35 +0200 Subject: [PATCH 063/257] refactor: deadline toggling test to kit2 --- itests/deadlines_test.go | 93 +++++++++++++++---------------------- itests/kit2/node_opts.go | 2 + itests/kit2/node_opts_nv.go | 4 +- 3 files changed, 41 insertions(+), 58 deletions(-) diff --git a/itests/deadlines_test.go b/itests/deadlines_test.go index 9551465a5..7b75eebbd 100644 --- a/itests/deadlines_test.go +++ b/itests/deadlines_test.go @@ -3,7 +3,6 @@ package itests import ( "bytes" "context" - "fmt" "os" "testing" "time" @@ -22,7 +21,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/extern/sector-storage/mock" - "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/itests/kit2" "github.com/filecoin-project/lotus/node/impl" miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" "github.com/ipfs/go-cid" @@ -75,21 +74,26 @@ func TestDeadlineToggling(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - n, sn := kit.MockMinerBuilder(t, []kit.FullNodeOpts{kit.FullNodeWithNetworkUpgradeAt(network.Version12, upgradeH)}, kit.OneMiner) + var ( + client kit2.TestFullNode + minerA kit2.TestMiner + minerB kit2.TestMiner + minerC kit2.TestMiner + minerD kit2.TestMiner + minerE kit2.TestMiner + ) + opts := []kit2.NodeOpt{kit2.ConstructorOpts(kit2.NetworkUpgradeAt(network.Version12, upgradeH))} + ens := kit2.NewEnsemble(t, kit2.MockProofs()). + FullNode(&client, opts...). + Miner(&minerA, &client, opts...). + Start(). + InterconnectAll() + ens.BeginMining(blocktime) - client := n[0].FullNode.(*impl.FullNodeAPI) - minerA := sn[0] - - { - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := minerA.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - } + opts = append(opts, kit2.OwnerAddr(client.DefaultKey)) + ens.Miner(&minerB, &client, opts...). + Miner(&minerC, &client, opts...). + Start() defaultFrom, err := client.WalletDefaultAddress(ctx) require.NoError(t, err) @@ -99,28 +103,6 @@ func TestDeadlineToggling(t *testing.T) { build.Clock.Sleep(time.Second) - done := make(chan struct{}) - go func() { - defer close(done) - for ctx.Err() == nil { - build.Clock.Sleep(blocktime) - if err := minerA.MineOne(ctx, kit.MineNext); err != nil { - if ctx.Err() != nil { - // context was canceled, ignore the error. - return - } - t.Error(err) - } - } - }() - defer func() { - cancel() - <-done - }() - - minerB := n[0].Stb(ctx, t, kit.TestSpt, defaultFrom) - minerC := n[0].Stb(ctx, t, kit.TestSpt, defaultFrom) - maddrB, err := minerB.ActorAddress(ctx) require.NoError(t, err) maddrC, err := minerC.ActorAddress(ctx) @@ -131,20 +113,20 @@ func TestDeadlineToggling(t *testing.T) { // pledge sectors on C, go through a PP, check for power { - kit.PledgeSectors(t, ctx, minerC, sectorsC, 0, nil) + minerC.PledgeSectors(ctx, sectorsC, 0, nil) di, err := client.StateMinerProvingDeadline(ctx, maddrC, types.EmptyTSK) require.NoError(t, err) - fmt.Printf("Running one proving period (miner C)\n") - fmt.Printf("End for head.Height > %d\n", di.PeriodStart+di.WPoStProvingPeriod*2) + t.Log("Running one proving period (miner C)") + t.Logf("End for head.Height > %d", di.PeriodStart+di.WPoStProvingPeriod*2) for { head, err := client.ChainHead(ctx) require.NoError(t, err) if head.Height() > di.PeriodStart+provingPeriod*2 { - fmt.Printf("Now head.Height = %d\n", head.Height()) + t.Logf("Now head.Height = %d", head.Height()) break } build.Clock.Sleep(blocktime) @@ -165,7 +147,7 @@ func TestDeadlineToggling(t *testing.T) { require.NoError(t, err) if head.Height() > upgradeH+provingPeriod { - fmt.Printf("Now head.Height = %d\n", head.Height()) + t.Logf("Now head.Height = %d", head.Height()) break } build.Clock.Sleep(blocktime) @@ -216,8 +198,9 @@ func TestDeadlineToggling(t *testing.T) { require.NoError(t, err) require.GreaterOrEqual(t, nv, network.Version12) - minerD := n[0].Stb(ctx, t, kit.TestSpt, defaultFrom) - minerE := n[0].Stb(ctx, t, kit.TestSpt, defaultFrom) + ens.Miner(&minerD, &client, opts...). + Miner(&minerE, &client, opts...). + Start() maddrD, err := minerD.ActorAddress(ctx) require.NoError(t, err) @@ -225,7 +208,7 @@ func TestDeadlineToggling(t *testing.T) { require.NoError(t, err) // first round of miner checks - checkMiner(maddrA, types.NewInt(uint64(ssz)*kit.GenesisPreseals), true, true, types.EmptyTSK) + checkMiner(maddrA, types.NewInt(uint64(ssz)*kit2.DefaultPresealsPerBootstrapMiner), true, true, types.EmptyTSK) checkMiner(maddrC, types.NewInt(uint64(ssz)*sectorsC), true, true, types.EmptyTSK) checkMiner(maddrB, types.NewInt(0), false, false, types.EmptyTSK) @@ -233,10 +216,10 @@ func TestDeadlineToggling(t *testing.T) { checkMiner(maddrE, types.NewInt(0), false, false, types.EmptyTSK) // pledge sectors on minerB/minerD, stop post on minerC - kit.PledgeSectors(t, ctx, minerB, sectorsB, 0, nil) + minerB.PledgeSectors(ctx, sectorsB, 0, nil) checkMiner(maddrB, types.NewInt(0), true, true, types.EmptyTSK) - kit.PledgeSectors(t, ctx, minerD, sectorsD, 0, nil) + minerD.PledgeSectors(ctx, sectorsD, 0, nil) checkMiner(maddrD, types.NewInt(0), true, true, types.EmptyTSK) minerC.StorageMiner.(*impl.StorageMinerAPI).IStorageMgr.(*mock.SectorMgr).Fail() @@ -252,7 +235,7 @@ func TestDeadlineToggling(t *testing.T) { params := &miner.SectorPreCommitInfo{ Expiration: 2880 * 300, SectorNumber: 22, - SealProof: kit.TestSpt, + SealProof: kit2.TestSpt, SealedCID: cr, SealRandEpoch: head.Height() - 200, @@ -281,7 +264,7 @@ func TestDeadlineToggling(t *testing.T) { require.NoError(t, err) if head.Height() > upgradeH+provingPeriod+(provingPeriod/2) { - fmt.Printf("Now head.Height = %d\n", head.Height()) + t.Logf("Now head.Height = %d", head.Height()) break } build.Clock.Sleep(blocktime) @@ -295,14 +278,14 @@ func TestDeadlineToggling(t *testing.T) { require.NoError(t, err) if head.Height() > upgradeH+(provingPeriod*3) { - fmt.Printf("Now head.Height = %d\n", head.Height()) + t.Logf("Now head.Height = %d", head.Height()) break } build.Clock.Sleep(blocktime) } // second round of miner checks - checkMiner(maddrA, types.NewInt(uint64(ssz)*kit.GenesisPreseals), true, true, types.EmptyTSK) + checkMiner(maddrA, types.NewInt(uint64(ssz)*kit2.DefaultPresealsPerBootstrapMiner), true, true, types.EmptyTSK) checkMiner(maddrC, types.NewInt(0), true, true, types.EmptyTSK) checkMiner(maddrB, types.NewInt(uint64(ssz)*sectorsB), true, true, types.EmptyTSK) checkMiner(maddrD, types.NewInt(uint64(ssz)*sectorsD), true, true, types.EmptyTSK) @@ -351,7 +334,7 @@ func TestDeadlineToggling(t *testing.T) { }, nil) require.NoError(t, err) - fmt.Println("sent termination message:", smsg.Cid()) + t.Log("sent termination message:", smsg.Cid()) r, err := client.StateWaitMsg(ctx, smsg.Cid(), 2, api.LookbackNoLimit, true) require.NoError(t, err) @@ -367,13 +350,13 @@ func TestDeadlineToggling(t *testing.T) { require.NoError(t, err) if head.Height() > upgradeH+(provingPeriod*5) { - fmt.Printf("Now head.Height = %d\n", head.Height()) + t.Logf("Now head.Height = %d", head.Height()) break } build.Clock.Sleep(blocktime) } - checkMiner(maddrA, types.NewInt(uint64(ssz)*kit.GenesisPreseals), true, true, types.EmptyTSK) + checkMiner(maddrA, types.NewInt(uint64(ssz)*kit2.DefaultPresealsPerBootstrapMiner), true, true, types.EmptyTSK) checkMiner(maddrC, types.NewInt(0), true, true, types.EmptyTSK) checkMiner(maddrB, types.NewInt(0), true, true, types.EmptyTSK) checkMiner(maddrD, types.NewInt(0), false, false, types.EmptyTSK) diff --git a/itests/kit2/node_opts.go b/itests/kit2/node_opts.go index 59d5454df..39c6cb809 100644 --- a/itests/kit2/node_opts.go +++ b/itests/kit2/node_opts.go @@ -14,6 +14,8 @@ import ( // PresealSectors option. const DefaultPresealsPerBootstrapMiner = 2 +const TestSpt = abi.RegisteredSealProof_StackedDrg2KiBV1_1 + // nodeOpts is an options accumulating struct, where functional options are // merged into. type nodeOpts struct { diff --git a/itests/kit2/node_opts_nv.go b/itests/kit2/node_opts_nv.go index 6f682bd3a..39de9d9e2 100644 --- a/itests/kit2/node_opts_nv.go +++ b/itests/kit2/node_opts_nv.go @@ -58,10 +58,8 @@ func InstantaneousNetworkVersion(version network.Version) node.Option { } func NetworkUpgradeAt(version network.Version, upgradeHeight abi.ChainEpoch) node.Option { - fullSchedule := stmgr.UpgradeSchedule{} - schedule := stmgr.UpgradeSchedule{} - for _, upgrade := range fullSchedule { + for _, upgrade := range DefaultTestUpgradeSchedule { if upgrade.Network > version { break } From 9436be5ff03744609d0a28c5fdcecb497ba33ecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Thu, 17 Jun 2021 21:58:29 +0100 Subject: [PATCH 064/257] introduce TestFullNode#WaitTillChain(predicate). --- itests/deals_test.go | 17 ++--------- itests/kit2/chain.go | 21 -------------- itests/kit2/node_full.go | 50 +++++++++++++++++++++++++++++++++ itests/sector_pledge_test.go | 4 +-- itests/sector_terminate_test.go | 8 +++--- 5 files changed, 58 insertions(+), 42 deletions(-) delete mode 100644 itests/kit2/chain.go diff --git a/itests/deals_test.go b/itests/deals_test.go index 3a6d9c868..af0ef68c4 100644 --- a/itests/deals_test.go +++ b/itests/deals_test.go @@ -239,23 +239,10 @@ func TestFirstDealEnablesMining(t *testing.T) { // once the provider has mined a block, thanks to the power acquired from the deal, // we pass the test. providerMined := make(chan struct{}) - heads, err := client.ChainNotify(ctx) - require.NoError(t, err) go func() { - for chg := range heads { - for _, c := range chg { - if c.Type != "apply" { - continue - } - for _, b := range c.Val.Blocks() { - if b.Miner == provider.ActorAddr { - close(providerMined) - return - } - } - } - } + _ = client.WaitTillChain(ctx, kit2.BlockMinedBy(provider.ActorAddr)) + close(providerMined) }() // now perform the deal. diff --git a/itests/kit2/chain.go b/itests/kit2/chain.go deleted file mode 100644 index 9563bae9f..000000000 --- a/itests/kit2/chain.go +++ /dev/null @@ -1,21 +0,0 @@ -package kit2 - -import ( - "context" - "testing" - "time" - - "github.com/filecoin-project/go-state-types/abi" - "github.com/stretchr/testify/require" -) - -func WaitTillChainHeight(ctx context.Context, t *testing.T, node *TestFullNode, blocktime time.Duration, height int) abi.ChainEpoch { - for { - h, err := node.ChainHead(ctx) - require.NoError(t, err) - if h.Height() > abi.ChainEpoch(height) { - return h.Height() - } - time.Sleep(blocktime) - } -} diff --git a/itests/kit2/node_full.go b/itests/kit2/node_full.go index b0b39b471..3dadb4d8d 100644 --- a/itests/kit2/node_full.go +++ b/itests/kit2/node_full.go @@ -4,8 +4,11 @@ import ( "context" "testing" + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api/v1api" + "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/wallet" "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/require" @@ -33,3 +36,50 @@ func (f *TestFullNode) CreateImportFile(ctx context.Context, rseed int, size int require.NoError(f.t, err) return res, path } + +// WaitTillChain waits until a specified chain condition is met. It returns +// the first tipset where the condition is met. +func (f *TestFullNode) WaitTillChain(ctx context.Context, pred ChainPredicate) *types.TipSet { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + heads, err := f.ChainNotify(ctx) + require.NoError(f.t, err) + + for chg := range heads { + for _, c := range chg { + if c.Type != "apply" { + continue + } + if ts := c.Val; pred(ts) { + return ts + } + } + } + require.Fail(f.t, "chain condition not met") + return nil +} + +// ChainPredicate encapsulates a chain condition. +type ChainPredicate func(set *types.TipSet) bool + +// HeightAtLeast returns a ChainPredicate that is satisfied when the chain +// height is equal or higher to the target. +func HeightAtLeast(target abi.ChainEpoch) ChainPredicate { + return func(ts *types.TipSet) bool { + return ts.Height() >= target + } +} + +// BlockMinedBy returns a ChainPredicate that is satisfied when we observe the +// first block mined by the specified miner. +func BlockMinedBy(miner address.Address) ChainPredicate { + return func(ts *types.TipSet) bool { + for _, b := range ts.Blocks() { + if b.Miner == miner { + return true + } + } + return false + } +} diff --git a/itests/sector_pledge_test.go b/itests/sector_pledge_test.go index 73fd2204e..8e87f2658 100644 --- a/itests/sector_pledge_test.go +++ b/itests/sector_pledge_test.go @@ -58,7 +58,7 @@ func TestPledgeBatching(t *testing.T) { client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), opts) ens.InterconnectAll().BeginMining(blockTime) - kit2.WaitTillChainHeight(ctx, t, client, blockTime, 10) + client.WaitTillChain(ctx, kit2.HeightAtLeast(10)) toCheck := miner.StartPledge(ctx, nSectors, 0, nil) @@ -115,7 +115,7 @@ func TestPledgeBeforeNv13(t *testing.T) { client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), opts) ens.InterconnectAll().BeginMining(blocktime) - kit2.WaitTillChainHeight(ctx, t, client, blocktime, 10) + client.WaitTillChain(ctx, kit2.HeightAtLeast(10)) toCheck := miner.StartPledge(ctx, nSectors, 0, nil) diff --git a/itests/sector_terminate_test.go b/itests/sector_terminate_test.go index d1852ec39..faf12228c 100644 --- a/itests/sector_terminate_test.go +++ b/itests/sector_terminate_test.go @@ -57,8 +57,8 @@ func TestTerminate(t *testing.T) { waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 2 t.Logf("End for head.Height > %d", waitUntil) - height := kit2.WaitTillChainHeight(ctx, t, client, blocktime, int(waitUntil)) - t.Logf("Now head.Height = %d", height) + ts := client.WaitTillChain(ctx, kit2.HeightAtLeast(waitUntil)) + t.Logf("Now head.Height = %d", ts.Height()) } nSectors++ @@ -140,8 +140,8 @@ loop: waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 2 t.Logf("End for head.Height > %d", waitUntil) - height := kit2.WaitTillChainHeight(ctx, t, client, blocktime, int(waitUntil)) - t.Logf("Now head.Height = %d", height) + ts := client.WaitTillChain(ctx, kit2.HeightAtLeast(waitUntil)) + t.Logf("Now head.Height = %d", ts.Height()) p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) require.NoError(t, err) From bc27602bea0c1efbda8517b2b4494508135a893e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Sun, 30 May 2021 17:20:14 +0200 Subject: [PATCH 065/257] events: Fix handling of multiple matched events per epoch From 5c8498b6036c5a63795da6408e9ae5f49c5e9673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 14 Apr 2021 20:26:07 +0200 Subject: [PATCH 066/257] storagefsm: Fix batch deal packing behavior --- api/test/deals.go | 133 +++++++++++++++++--------------- cli/test/client.go | 4 +- extern/storage-sealing/fsm.go | 4 + extern/storage-sealing/input.go | 36 ++++++--- node/impl/storminer.go | 6 +- 5 files changed, 106 insertions(+), 77 deletions(-) diff --git a/api/test/deals.go b/api/test/deals.go index e3432ff0d..753cbc230 100644 --- a/api/test/deals.go +++ b/api/test/deals.go @@ -51,7 +51,7 @@ func TestDoubleDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration, sta } 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) + res, data, err := CreateClientFile(ctx, client, rseed, 0) if err != nil { t.Fatal(err) } @@ -72,8 +72,11 @@ func MakeDeal(t *testing.T, ctx context.Context, rseed int, client api.FullNode, testRetrieval(t, ctx, client, fcid, &info.PieceCID, carExport, data) } -func CreateClientFile(ctx context.Context, client api.FullNode, rseed int) (*api.ImportRes, []byte, error) { - data := make([]byte, 1600) +func CreateClientFile(ctx context.Context, client api.FullNode, rseed, size int) (*api.ImportRes, []byte, error) { + if size == 0 { + size = 1600 + } + data := make([]byte, size) rand.New(rand.NewSource(int64(rseed))).Read(data) dir, err := ioutil.TempDir(os.TempDir(), "test-make-deal-") @@ -119,7 +122,7 @@ func TestPublishDealsBatching(t *testing.T, b APIBuilder, blocktime time.Duratio // Starts a deal and waits until it's published runDealTillPublish := func(rseed int) { - res, _, err := CreateClientFile(s.ctx, s.client, rseed) + res, _, err := CreateClientFile(s.ctx, s.client, rseed, 0) require.NoError(t, err) upds, err := client.ClientGetDealUpdates(s.ctx) @@ -186,68 +189,76 @@ func TestPublishDealsBatching(t *testing.T, b APIBuilder, blocktime time.Duratio } func TestBatchDealInput(t *testing.T, b APIBuilder, blocktime time.Duration, startEpoch abi.ChainEpoch) { - publishPeriod := 10 * time.Second - maxDealsPerMsg := uint64(4) + run := func(piece, deals, expectSectors int) func(t *testing.T) { + return func(t *testing.T) { + publishPeriod := 10 * time.Second + maxDealsPerMsg := uint64(deals) - // 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, - }} + // 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() + // 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) + // Starts a deal and waits until it's published + runDealTillSeal := func(rseed int) { + res, _, err := CreateClientFile(s.ctx, s.client, rseed, piece) + 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) + 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), expectSectors) + require.LessOrEqual(t, len(sl), expectSectors+1) + } } - // 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) + t.Run("4-p1600B", run(1600, 4, 4)) + t.Run("4-p513B", run(513, 4, 2)) + t.Run("32-p257B", run(257, 32, 8)) } func TestFastRetrievalDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration, startEpoch abi.ChainEpoch) { @@ -430,7 +441,7 @@ func startSealingWaiting(t *testing.T, ctx context.Context, miner TestStorageNod si, err := miner.SectorsStatus(ctx, snum, false) require.NoError(t, err) - t.Logf("Sector state: %s", si.State) + t.Logf("Sector %d state: %s", snum, si.State) if si.State == api.SectorState(sealing.WaitDeals) { require.NoError(t, miner.SectorStartSealing(ctx, snum)) } diff --git a/cli/test/client.go b/cli/test/client.go index 4a49f732a..2a48b7b64 100644 --- a/cli/test/client.go +++ b/cli/test/client.go @@ -44,7 +44,7 @@ func RunClientTest(t *testing.T, cmds []*lcli.Command, clientNode test.TestNode) // Create a deal (non-interactive) // client deal --start-epoch= 1000000attofil - res, _, err := test.CreateClientFile(ctx, clientNode, 1) + res, _, err := test.CreateClientFile(ctx, clientNode, 1, 0) require.NoError(t, err) startEpoch := fmt.Sprintf("--start-epoch=%d", 2<<12) dataCid := res.Root @@ -60,7 +60,7 @@ func RunClientTest(t *testing.T, cmds []*lcli.Command, clientNode test.TestNode) // // "no" (verified client) // "yes" (confirm deal) - res, _, err = test.CreateClientFile(ctx, clientNode, 2) + res, _, err = test.CreateClientFile(ctx, clientNode, 2, 0) require.NoError(t, err) dataCid2 := res.Root duration = fmt.Sprintf("%d", build.MinDealDuration/builtin.EpochsInDay) diff --git a/extern/storage-sealing/fsm.go b/extern/storage-sealing/fsm.go index 1ad2d7ec0..1baf9203b 100644 --- a/extern/storage-sealing/fsm.go +++ b/extern/storage-sealing/fsm.go @@ -51,6 +51,7 @@ var fsmPlanners = map[SectorState]func(events []statemachine.Event, state *Secto AddPiece: planOne( on(SectorPieceAdded{}, WaitDeals), apply(SectorStartPacking{}), + apply(SectorAddPiece{}), on(SectorAddPieceFailed{}, AddPieceFailed), ), Packing: planOne(on(SectorPacked{}, GetTicket)), @@ -582,6 +583,7 @@ func onReturning(mut mutator) func() (mutator, func(*SectorInfo) (bool, error)) func planOne(ts ...func() (mut mutator, next func(*SectorInfo) (more bool, err error))) func(events []statemachine.Event, state *SectorInfo) (uint64, error) { return func(events []statemachine.Event, state *SectorInfo) (uint64, error) { + eloop: for i, event := range events { if gm, ok := event.User.(globalMutator); ok { gm.applyGlobal(state) @@ -604,6 +606,8 @@ func planOne(ts ...func() (mut mutator, next func(*SectorInfo) (more bool, err e if err != nil || !more { return uint64(i + 1), err } + + continue eloop } _, ok := event.User.(Ignorable) diff --git a/extern/storage-sealing/input.go b/extern/storage-sealing/input.go index 1ba8b4c2c..5cb08c2a5 100644 --- a/extern/storage-sealing/input.go +++ b/extern/storage-sealing/input.go @@ -27,6 +27,14 @@ func (m *Sealing) handleWaitDeals(ctx statemachine.Context, sector SectorInfo) e m.inputLk.Lock() + sid := m.minerSectorID(sector.SectorNumber) + + if len(m.assignedPieces[sid]) > 0 { + m.inputLk.Unlock() + // got assigned more pieces in the AddPiece state + return ctx.Send(SectorAddPiece{}) + } + started, err := m.maybeStartSealing(ctx, sector, used) if err != nil || started { delete(m.openSectors, m.minerSectorID(sector.SectorNumber)) @@ -36,16 +44,16 @@ func (m *Sealing) handleWaitDeals(ctx statemachine.Context, sector SectorInfo) e return err } - m.openSectors[m.minerSectorID(sector.SectorNumber)] = &openSector{ - used: used, - maybeAccept: func(cid cid.Cid) error { - // todo check deal start deadline (configurable) + if _, has := m.openSectors[sid]; !has { + m.openSectors[sid] = &openSector{ + used: used, + maybeAccept: func(cid cid.Cid) error { + // todo check deal start deadline (configurable) + m.assignedPieces[sid] = append(m.assignedPieces[sid], cid) - sid := m.minerSectorID(sector.SectorNumber) - m.assignedPieces[sid] = append(m.assignedPieces[sid], cid) - - return ctx.Send(SectorAddPiece{}) - }, + return ctx.Send(SectorAddPiece{}) + }, + } } go func() { @@ -350,11 +358,19 @@ func (m *Sealing) updateInput(ctx context.Context, sp abi.RegisteredSealProof) e continue } + avail := abi.PaddedPieceSize(ssize).Unpadded() - m.openSectors[mt.sector].used + + if mt.size > avail { + continue + } + err := m.openSectors[mt.sector].maybeAccept(mt.deal) if err != nil { m.pendingPieces[mt.deal].accepted(mt.sector.Number, 0, err) // non-error case in handleAddPiece } + m.openSectors[mt.sector].used += mt.padding + mt.size + m.pendingPieces[mt.deal].assigned = true delete(toAssign, mt.deal) @@ -362,8 +378,6 @@ func (m *Sealing) updateInput(ctx context.Context, sp abi.RegisteredSealProof) e log.Errorf("sector %d rejected deal %s: %+v", mt.sector, mt.deal, err) continue } - - delete(m.openSectors, mt.sector) } if len(toAssign) > 0 { diff --git a/node/impl/storminer.go b/node/impl/storminer.go index 622ac22f1..61c69b2ba 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -245,13 +245,13 @@ func (sm *StorageMinerAPI) SectorsList(context.Context) ([]abi.SectorNumber, err return nil, err } - out := make([]abi.SectorNumber, len(sectors)) - for i, sector := range sectors { + out := make([]abi.SectorNumber, 0, len(sectors)) + for _, sector := range sectors { if sector.State == sealing.UndefinedSectorState { continue // sector ID not set yet } - out[i] = sector.SectorNumber + out = append(out, sector.SectorNumber) } return out, nil } From 097baeb9b0ad3fcc2fdbe3da239a141c691a0989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Sun, 30 May 2021 15:13:38 +0200 Subject: [PATCH 067/257] Make batch deal input test less flaky --- api/test/deals.go | 73 +++++++++++++++---- api/test/mining.go | 2 +- extern/storage-sealing/fsm.go | 2 + extern/storage-sealing/input.go | 1 + extern/storage-sealing/states_sealing.go | 2 +- .../storageadapter/ondealsectorcommitted.go | 2 + node/node_test.go | 18 +++++ 7 files changed, 84 insertions(+), 16 deletions(-) diff --git a/api/test/deals.go b/api/test/deals.go index 753cbc230..740fb8fea 100644 --- a/api/test/deals.go +++ b/api/test/deals.go @@ -8,6 +8,7 @@ import ( "math/rand" "os" "path/filepath" + "sort" "testing" "time" @@ -63,7 +64,7 @@ func MakeDeal(t *testing.T, ctx context.Context, rseed int, client api.FullNode, // 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, miner, client, deal, false) + waitDealSealed(t, ctx, miner, client, deal, false, false, nil) // Retrieval info, err := client.ClientGetDealInfo(ctx, *deal) @@ -207,10 +208,11 @@ func TestBatchDealInput(t *testing.T, b APIBuilder, blocktime time.Duration, sta node.Override(new(dtypes.GetSealingConfigFunc), func() (dtypes.GetSealingConfigFunc, error) { return func() (sealiface.Config, error) { return sealiface.Config{ - MaxWaitDealsSectors: 1, + MaxWaitDealsSectors: 2, MaxSealingSectors: 1, - MaxSealingSectorsForDeals: 2, + MaxSealingSectorsForDeals: 3, AlwaysKeepUnsealedCopy: true, + WaitDealsDelay: time.Hour, }, nil }, nil }), @@ -225,18 +227,40 @@ func TestBatchDealInput(t *testing.T, b APIBuilder, blocktime time.Duration, sta s := connectAndStartMining(t, b, blocktime, client, miner) defer s.blockMiner.Stop() + checkNoPadding := func() { + sl, err := sn[0].SectorsList(s.ctx) + require.NoError(t, err) + + sort.Slice(sl, func(i, j int) bool { + return sl[i] < sl[j] + }) + + for _, snum := range sl { + si, err := sn[0].SectorsStatus(s.ctx, snum, false) + require.NoError(t, err) + + fmt.Printf("S %d: %+v %s\n", snum, si.Deals, si.State) + + for _, deal := range si.Deals { + if deal == 0 { + fmt.Printf("sector %d had a padding piece!\n", snum) + } + } + } + } + // Starts a deal and waits until it's published runDealTillSeal := func(rseed int) { res, _, err := CreateClientFile(s.ctx, s.client, rseed, piece) 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) + waitDealSealed(t, s.ctx, s.miner, s.client, dc, false, true, checkNoPadding) } - // Run maxDealsPerMsg+1 deals in parallel - done := make(chan struct{}, maxDealsPerMsg+1) - for rseed := 1; rseed <= int(maxDealsPerMsg+1); rseed++ { + // Run maxDealsPerMsg deals in parallel + done := make(chan struct{}, maxDealsPerMsg) + for rseed := 0; rseed < int(maxDealsPerMsg); rseed++ { rseed := rseed go func() { runDealTillSeal(rseed) @@ -249,10 +273,12 @@ func TestBatchDealInput(t *testing.T, b APIBuilder, blocktime time.Duration, sta <-done } + checkNoPadding() + sl, err := sn[0].SectorsList(s.ctx) require.NoError(t, err) - require.GreaterOrEqual(t, len(sl), expectSectors) - require.LessOrEqual(t, len(sl), expectSectors+1) + require.Equal(t, len(sl), expectSectors) + //require.LessOrEqual(t, len(sl), expectSectors+1) } } @@ -314,12 +340,12 @@ func TestSecondDealRetrieval(t *testing.T, b APIBuilder, blocktime time.Duration // 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, s.ctx, s.miner, s.client, deal1, true) + waitDealSealed(t, s.ctx, s.miner, s.client, deal1, true, false, nil) deal2 := startDeal(t, s.ctx, s.miner, s.client, fcid2, true, 0) time.Sleep(time.Second) - waitDealSealed(t, s.ctx, s.miner, s.client, deal2, false) + waitDealSealed(t, s.ctx, s.miner, s.client, deal2, false, false, nil) // Retrieval info, err := s.client.ClientGetDealInfo(s.ctx, *deal2) @@ -375,7 +401,7 @@ func startDeal(t *testing.T, ctx context.Context, miner TestStorageNode, client return deal } -func waitDealSealed(t *testing.T, ctx context.Context, miner TestStorageNode, client api.FullNode, deal *cid.Cid, noseal bool) { +func waitDealSealed(t *testing.T, ctx context.Context, miner TestStorageNode, client api.FullNode, deal *cid.Cid, noseal, noSealStart bool, cb func()) { loop: for { di, err := client.ClientGetDealInfo(ctx, *deal) @@ -387,7 +413,9 @@ loop: if noseal { return } - startSealingWaiting(t, ctx, miner) + if !noSealStart { + startSealingWaiting(t, ctx, miner) + } case storagemarket.StorageDealProposalRejected: t.Fatal("deal rejected") case storagemarket.StorageDealFailing: @@ -398,8 +426,25 @@ loop: fmt.Println("COMPLETE", di) break loop } - fmt.Println("Deal state: ", storagemarket.DealStates[di.State]) + + mds, err := miner.MarketListIncompleteDeals(ctx) + if err != nil { + t.Fatal(err) + } + + var minerState storagemarket.StorageDealStatus + for _, md := range mds { + if md.DealID == di.DealID { + minerState = md.State + break + } + } + + fmt.Printf("Deal %d state: client:%s provider:%s\n", di.DealID, storagemarket.DealStates[di.State], storagemarket.DealStates[minerState]) time.Sleep(time.Second / 2) + if cb != nil { + cb() + } } } diff --git a/api/test/mining.go b/api/test/mining.go index 4a4f1e1a4..d6dea9abf 100644 --- a/api/test/mining.go +++ b/api/test/mining.go @@ -194,7 +194,7 @@ func TestDealMining(t *testing.T, b APIBuilder, blocktime time.Duration, carExpo // 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) + waitDealSealed(t, ctx, provider, client, deal, false, false, nil) <-minedTwo diff --git a/extern/storage-sealing/fsm.go b/extern/storage-sealing/fsm.go index 1baf9203b..cfd223364 100644 --- a/extern/storage-sealing/fsm.go +++ b/extern/storage-sealing/fsm.go @@ -225,6 +225,8 @@ var fsmPlanners = map[SectorState]func(events []statemachine.Event, state *Secto func (m *Sealing) logEvents(events []statemachine.Event, state *SectorInfo) { for _, event := range events { + log.Debugw("sector event", "sector", state.SectorNumber, "type", fmt.Sprintf("%T", event.User), "event", event.User) + e, err := json.Marshal(event) if err != nil { log.Errorf("marshaling event for logging: %+v", err) diff --git a/extern/storage-sealing/input.go b/extern/storage-sealing/input.go index 5cb08c2a5..df82f0acf 100644 --- a/extern/storage-sealing/input.go +++ b/extern/storage-sealing/input.go @@ -440,6 +440,7 @@ func (m *Sealing) createSector(ctx context.Context, cfg sealiface.Config, sp abi func (m *Sealing) StartPacking(sid abi.SectorNumber) error { m.startupWait.Wait() + log.Infow("starting to seal deal sector", "sector", sid, "trigger", "user") return m.sectors.Send(uint64(sid), SectorStartPacking{}) } diff --git a/extern/storage-sealing/states_sealing.go b/extern/storage-sealing/states_sealing.go index 4f0f1dc80..31029649a 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -37,7 +37,7 @@ func (m *Sealing) handlePacking(ctx statemachine.Context, sector SectorInfo) err } // todo: return to the sealing queue (this is extremely unlikely to happen) - pp.accepted(sector.SectorNumber, 0, xerrors.Errorf("sector entered packing state early")) + pp.accepted(sector.SectorNumber, 0, xerrors.Errorf("sector %d entered packing state early", sector.SectorNumber)) } delete(m.openSectors, m.minerSectorID(sector.SectorNumber)) diff --git a/markets/storageadapter/ondealsectorcommitted.go b/markets/storageadapter/ondealsectorcommitted.go index 31bc0b8bf..f255c9e55 100644 --- a/markets/storageadapter/ondealsectorcommitted.go +++ b/markets/storageadapter/ondealsectorcommitted.go @@ -104,6 +104,8 @@ func (mgr *SectorCommittedManager) OnDealSectorPreCommitted(ctx context.Context, } } + log.Infow("sub precommit", "deal", dealInfo.DealID) + // Not yet active, start matching against incoming messages return false, true, nil } diff --git a/node/node_test.go b/node/node_test.go index 933a0f614..85b3f2e6d 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -63,6 +63,24 @@ func TestAPIDealFlow(t *testing.T) { }) } +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") + logging.SetLogLevel("sectors", "DEBUG") + + 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") From b0128bd99ecff308ebad0e75575a4337ab6c35dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Sun, 30 May 2021 18:30:38 +0200 Subject: [PATCH 068/257] storagefsm: Fix race spawning more than one new sector at once --- extern/storage-sealing/input.go | 10 ++++++++++ extern/storage-sealing/sealing.go | 1 + 2 files changed, 11 insertions(+) diff --git a/extern/storage-sealing/input.go b/extern/storage-sealing/input.go index df82f0acf..85a5c429f 100644 --- a/extern/storage-sealing/input.go +++ b/extern/storage-sealing/input.go @@ -27,6 +27,10 @@ func (m *Sealing) handleWaitDeals(ctx statemachine.Context, sector SectorInfo) e m.inputLk.Lock() + if m.creating != nil && *m.creating == sector.SectorNumber { + m.creating = nil + } + sid := m.minerSectorID(sector.SectorNumber) if len(m.assignedPieces[sid]) > 0 { @@ -392,6 +396,10 @@ func (m *Sealing) updateInput(ctx context.Context, sp abi.RegisteredSealProof) e func (m *Sealing) tryCreateDealSector(ctx context.Context, sp abi.RegisteredSealProof) error { m.startupWait.Wait() + if m.creating != nil { + return nil // new sector is being created right now + } + cfg, err := m.getConfig() if err != nil { return xerrors.Errorf("getting storage config: %w", err) @@ -410,6 +418,8 @@ func (m *Sealing) tryCreateDealSector(ctx context.Context, sp abi.RegisteredSeal return err } + m.creating = &sid + log.Infow("Creating sector", "number", sid, "type", "deal", "proofType", sp) return m.sectors.Send(uint64(sid), SectorStart{ ID: sid, diff --git a/extern/storage-sealing/sealing.go b/extern/storage-sealing/sealing.go index e753085ef..8a70704c4 100644 --- a/extern/storage-sealing/sealing.go +++ b/extern/storage-sealing/sealing.go @@ -98,6 +98,7 @@ type Sealing struct { sectorTimers map[abi.SectorID]*time.Timer pendingPieces map[cid.Cid]*pendingPiece assignedPieces map[abi.SectorID][]cid.Cid + creating *abi.SectorNumber // used to prevent a race where we could create a new sector more than once upgradeLk sync.Mutex toUpgrade map[abi.SectorNumber]struct{} From 0a5a7cf45df0f14e45f756c0ed4c029e39d42f8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Sun, 30 May 2021 19:24:42 +0200 Subject: [PATCH 069/257] storagefsm: Fix too-long log handling --- extern/storage-sealing/fsm.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extern/storage-sealing/fsm.go b/extern/storage-sealing/fsm.go index cfd223364..24f26a1ee 100644 --- a/extern/storage-sealing/fsm.go +++ b/extern/storage-sealing/fsm.go @@ -237,6 +237,10 @@ func (m *Sealing) logEvents(events []statemachine.Event, state *SectorInfo) { continue // don't log on every fsm restart } + if len(e) > 8000 { + e = []byte(string(e[:8000]) + "... truncated") + } + l := Log{ Timestamp: uint64(time.Now().Unix()), Message: string(e), From 7f247bf553308a4b5f7f248885d79c92bd9c0b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Sun, 30 May 2021 19:26:53 +0200 Subject: [PATCH 070/257] Self-review cleanup --- api/test/deals.go | 15 ++++++++++++--- markets/storageadapter/ondealsectorcommitted.go | 2 -- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/api/test/deals.go b/api/test/deals.go index 740fb8fea..aa7e23bcc 100644 --- a/api/test/deals.go +++ b/api/test/deals.go @@ -19,6 +19,7 @@ import ( "github.com/filecoin-project/go-fil-markets/storagemarket" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/builtin/market" @@ -227,6 +228,9 @@ func TestBatchDealInput(t *testing.T, b APIBuilder, blocktime time.Duration, sta s := connectAndStartMining(t, b, blocktime, client, miner) defer s.blockMiner.Stop() + err := miner.MarketSetAsk(s.ctx, big.Zero(), big.Zero(), 200, 128, 32<<30) + require.NoError(t, err) + checkNoPadding := func() { sl, err := sn[0].SectorsList(s.ctx) require.NoError(t, err) @@ -239,7 +243,7 @@ func TestBatchDealInput(t *testing.T, b APIBuilder, blocktime time.Duration, sta si, err := sn[0].SectorsStatus(s.ctx, snum, false) require.NoError(t, err) - fmt.Printf("S %d: %+v %s\n", snum, si.Deals, si.State) + // fmt.Printf("S %d: %+v %s\n", snum, si.Deals, si.State) for _, deal := range si.Deals { if deal == 0 { @@ -278,13 +282,18 @@ func TestBatchDealInput(t *testing.T, b APIBuilder, blocktime time.Duration, sta sl, err := sn[0].SectorsList(s.ctx) require.NoError(t, err) require.Equal(t, len(sl), expectSectors) - //require.LessOrEqual(t, len(sl), expectSectors+1) } } t.Run("4-p1600B", run(1600, 4, 4)) t.Run("4-p513B", run(513, 4, 2)) - t.Run("32-p257B", run(257, 32, 8)) + if !testing.Short() { + t.Run("32-p257B", run(257, 32, 8)) + t.Run("32-p10B", run(10, 32, 2)) + + // fixme: this appears to break data-transfer / markets in some really creative ways + //t.Run("128-p10B", run(10, 128, 8)) + } } func TestFastRetrievalDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration, startEpoch abi.ChainEpoch) { diff --git a/markets/storageadapter/ondealsectorcommitted.go b/markets/storageadapter/ondealsectorcommitted.go index f255c9e55..31bc0b8bf 100644 --- a/markets/storageadapter/ondealsectorcommitted.go +++ b/markets/storageadapter/ondealsectorcommitted.go @@ -104,8 +104,6 @@ func (mgr *SectorCommittedManager) OnDealSectorPreCommitted(ctx context.Context, } } - log.Infow("sub precommit", "deal", dealInfo.DealID) - // Not yet active, start matching against incoming messages return false, true, nil } From 87f2a4105205d6cb6ed08464df26a97020c0605d Mon Sep 17 00:00:00 2001 From: Cory Schwartz Date: Thu, 17 Jun 2021 23:46:01 -0700 Subject: [PATCH 071/257] only use embed for appimage --- Makefile | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index a8857556f..7de08dfa5 100644 --- a/Makefile +++ b/Makefile @@ -56,12 +56,6 @@ build/.update-modules: # end git modules -build/rice-box.go: - go run github.com/GeertJohan/go.rice/rice embed-go -i ./build - -BUILD_DEPS+=build/rice-box.go -CLEAN+=build/rice-box.go - ## MAIN BINARIES CLEAN+=build/.update-modules @@ -90,6 +84,7 @@ butterflynet: build-devnets lotus: $(BUILD_DEPS) rm -f lotus go build $(GOFLAGS) -o lotus ./cmd/lotus + go run github.com/GeertJohan/go.rice/rice append --exec lotus -i ./build .PHONY: lotus BINS+=lotus @@ -97,18 +92,21 @@ BINS+=lotus lotus-miner: $(BUILD_DEPS) rm -f lotus-miner go build $(GOFLAGS) -o lotus-miner ./cmd/lotus-storage-miner + go run github.com/GeertJohan/go.rice/rice append --exec lotus-miner -i ./build .PHONY: lotus-miner BINS+=lotus-miner lotus-worker: $(BUILD_DEPS) rm -f lotus-worker go build $(GOFLAGS) -o lotus-worker ./cmd/lotus-seal-worker + go run github.com/GeertJohan/go.rice/rice append --exec lotus-worker -i ./build .PHONY: lotus-worker BINS+=lotus-worker lotus-shed: $(BUILD_DEPS) rm -f lotus-shed go build $(GOFLAGS) -o lotus-shed ./cmd/lotus-shed + go run github.com/GeertJohan/go.rice/rice append --exec lotus-shed -i ./build .PHONY: lotus-shed BINS+=lotus-shed @@ -140,6 +138,7 @@ install-worker: lotus-seed: $(BUILD_DEPS) rm -f lotus-seed go build $(GOFLAGS) -o lotus-seed ./cmd/lotus-seed + go run github.com/GeertJohan/go.rice/rice append --exec lotus-seed -i ./build .PHONY: lotus-seed BINS+=lotus-seed @@ -173,11 +172,13 @@ lotus-townhall-front: .PHONY: lotus-townhall-front lotus-townhall-app: lotus-touch lotus-townhall-front + go run github.com/GeertJohan/go.rice/rice append --exec lotus-townhall -i ./cmd/lotus-townhall -i ./build .PHONY: lotus-townhall-app lotus-fountain: rm -f lotus-fountain go build -o lotus-fountain ./cmd/lotus-fountain + go run github.com/GeertJohan/go.rice/rice append --exec lotus-fountain -i ./cmd/lotus-fountain -i ./build .PHONY: lotus-fountain BINS+=lotus-fountain @@ -190,24 +191,28 @@ BINS+=lotus-chainwatch lotus-bench: rm -f lotus-bench go build -o lotus-bench ./cmd/lotus-bench + go run github.com/GeertJohan/go.rice/rice append --exec lotus-bench -i ./build .PHONY: lotus-bench BINS+=lotus-bench lotus-stats: rm -f lotus-stats go build $(GOFLAGS) -o lotus-stats ./cmd/lotus-stats + go run github.com/GeertJohan/go.rice/rice append --exec lotus-stats -i ./build .PHONY: lotus-stats BINS+=lotus-stats lotus-pcr: rm -f lotus-pcr go build $(GOFLAGS) -o lotus-pcr ./cmd/lotus-pcr + go run github.com/GeertJohan/go.rice/rice append --exec lotus-pcr -i ./build .PHONY: lotus-pcr BINS+=lotus-pcr lotus-health: rm -f lotus-health go build -o lotus-health ./cmd/lotus-health + go run github.com/GeertJohan/go.rice/rice append --exec lotus-health -i ./build .PHONY: lotus-health BINS+=lotus-health @@ -330,15 +335,17 @@ api-gen: goimports -w api/apistruct .PHONY: api-gen -appimage: lotus +appimage: rm -rf appimage-builder-cache || true rm AppDir/io.filecoin.lotus.desktop || true rm AppDir/icon.svg || true rm Appdir/AppRun || true mkdir -p AppDir/usr/bin + rm -rf lotus + go run github.com/GeertJohan/go.rice/rice embed-go -i ./build + go build $(GOFLAGS) -o lotus ./cmd/lotus cp ./lotus AppDir/usr/bin/ appimage-builder - docsgen: docsgen-md docsgen-openrpc docsgen-md-bin: actors-gen From 86baa119370fdd086c3cb58d5e165cd01b4b2a50 Mon Sep 17 00:00:00 2001 From: Cory Schwartz Date: Thu, 17 Jun 2021 23:47:08 -0700 Subject: [PATCH 072/257] test: build appimage in ci --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2df973dfb..cf91e6cde 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -845,6 +845,7 @@ workflows: tags: only: - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-appimage - publish: requires: - build-all From 6e8cf9061a39065616ea33f84a8590ddf9b1c66c Mon Sep 17 00:00:00 2001 From: Cory Schwartz Date: Thu, 17 Jun 2021 23:48:35 -0700 Subject: [PATCH 073/257] temp name --- .circleci/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cf91e6cde..b0e349423 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -845,7 +845,8 @@ workflows: tags: only: - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - - build-appimage + - build-appimage: + name: tmp-build-appimage - publish: requires: - build-all From 7f744b2760945923cc27ef9384c9e0bed6ddc0fe Mon Sep 17 00:00:00 2001 From: Cory Schwartz Date: Thu, 17 Jun 2021 23:52:34 -0700 Subject: [PATCH 074/257] require deps --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7de08dfa5..4ae34fd51 100644 --- a/Makefile +++ b/Makefile @@ -335,7 +335,7 @@ api-gen: goimports -w api/apistruct .PHONY: api-gen -appimage: +appimage: $(BUILD_DEPS) rm -rf appimage-builder-cache || true rm AppDir/io.filecoin.lotus.desktop || true rm AppDir/icon.svg || true From d35fcd926fe627a0e579402635411c71d02fcb38 Mon Sep 17 00:00:00 2001 From: Cory Schwartz Date: Fri, 18 Jun 2021 00:02:04 -0700 Subject: [PATCH 075/257] remove temp edits --- .circleci/config.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b0e349423..2df973dfb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -845,8 +845,6 @@ workflows: tags: only: - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - - build-appimage: - name: tmp-build-appimage - publish: requires: - build-all From e56a171ab6737be2bf5f89790bb8c23fa771efbb Mon Sep 17 00:00:00 2001 From: Rjan Date: Fri, 18 Jun 2021 10:59:53 +0200 Subject: [PATCH 076/257] Use prebuilt proof binaries when building Make the basic build instructions use the prebuilt proof binaries instead of building them from source. Removed rustup as a dependency, and linked to it in the full docs instead. --- README.md | 36 ++++++------------------------------ 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 4a0adc8c0..0218e87e9 100644 --- a/README.md +++ b/README.md @@ -67,20 +67,12 @@ sudo dnf -y install gcc make git bzr jq pkgconfig mesa-libOpenCL mesa-libOpenCL- For other distributions you can find the required dependencies [here.](https://docs.filecoin.io/get-started/lotus/installation/#system-specific) For instructions specific to macOS, you can find them [here.](https://docs.filecoin.io/get-started/lotus/installation/#macos) -#### Rustup - -Lotus needs [rustup](https://rustup.rs). The easiest way to install it is: - -```bash -wget -c https://golang.org/dl/go1.16.2.linux-amd64.tar.gz -O - | sudo tar -xz -C /usr/local -``` - #### Go -To build Lotus, you need a working installation of [Go 1.16.1 or higher](https://golang.org/dl/): +To build Lotus, you need a working installation of [Go 1.16.4 or higher](https://golang.org/dl/): ```bash -wget -c https://golang.org/dl/go1.16.2.linux-amd64.tar.gz -O - | sudo tar -xz -C /usr/local +wget -c https://golang.org/dl/go1.16.4.linux-amd64.tar.gz -O - | sudo tar -xz -C /usr/local ``` **TIP:** @@ -102,6 +94,8 @@ Once all the dependencies are installed, you can build and install the Lotus sui git clone https://github.com/filecoin-project/lotus.git cd lotus/ ``` + +Note: The default branch `master` is the dev branch where the latest new features, bug fixes and improvement are in. However, if you want to run lotus on Filecoin mainnet and want to run a production-ready lotus, get the latest release[ here](https://github.com/filecoin-project/lotus/releases). 2. To join mainnet, checkout the [latest release](https://github.com/filecoin-project/lotus/releases). @@ -118,30 +112,12 @@ Once all the dependencies are installed, you can build and install the Lotus sui Currently, the latest code on the _master_ branch corresponds to mainnet. 3. If you are in China, see "[Lotus: tips when running in China](https://docs.filecoin.io/get-started/lotus/tips-running-in-china/)". -4. Depending on your CPU model, you will want to export additional environment variables: - - If you have **an AMD Zen or Intel Ice Lake CPU (or later)**, enable the use of SHA extensions by adding these two environment variables: - - ```sh - export RUSTFLAGS="-C target-cpu=native -g" - export FFI_BUILD_FROM_SOURCE=1 - ``` - - See the [Native Filecoin FFI section](https://docs.filecoin.io/get-started/lotus/installation/#native-filecoin-ffi) for more details about this process. - - Some older Intel and AMD processors without the ADX instruction support may panic with illegal instruction errors. To fix this, add the `CGO_CFLAGS` environment variable: - - ```sh - export CGO_CFLAGS_ALLOW="-D__BLST_PORTABLE__" - export CGO_CFLAGS="-D__BLST_PORTABLE__" - ``` - - This is due to a Lotus bug that prevents Lotus from running on a processor without `adx` instruction support, and should be fixed soon. +4. This build instruction uses the prebuilt proofs binaries. If you want to build the proof binaries from source check the [complete instructions](https://docs.filecoin.io/get-started/lotus/installation/#build-and-install-lotus). Note, if you are building the proof binaries from source, [installing rustup](https://docs.filecoin.io/get-started/lotus/installation/#rustup) is also needed. 5. Build and install Lotus: ```sh - make clean all + make clean all #mainnet # Or to join a testnet or devnet: make clean calibnet # Calibration with min 32GiB sectors From 7f00a6e59c07dd05ad2855baa45443eb775d13dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 18 Jun 2021 11:17:18 +0200 Subject: [PATCH 077/257] Proofs v8.0.2 with fixed aggregate lengths --- extern/filecoin-ffi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index d2e3aa7d6..6f11cdffd 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit d2e3aa7d61501d69bed6e898de13d1312b021e62 +Subproject commit 6f11cdffd4f6c7ae1b05d0e47f578d4c9b21415f From 2548c224c7a08ba5489644742921b6f73fb15aa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Fri, 18 Jun 2021 10:27:20 +0100 Subject: [PATCH 078/257] switch to TestFullNode#WaitTillChain. --- itests/wdpost_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/itests/wdpost_test.go b/itests/wdpost_test.go index 317a7a529..83ddf34e5 100644 --- a/itests/wdpost_test.go +++ b/itests/wdpost_test.go @@ -69,8 +69,8 @@ func testWindowPostUpgrade(t *testing.T, blocktime time.Duration, nSectors int, waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 2 t.Logf("End for head.Height > %d", waitUntil) - height := kit2.WaitTillChainHeight(ctx, t, client, blocktime, int(waitUntil)) - t.Logf("Now head.Height = %d", height) + ts := client.WaitTillChain(ctx, kit2.HeightAtLeast(waitUntil)) + t.Logf("Now head.Height = %d", ts.Height()) p, err := client.StateMinerPower(ctx, maddr, types.EmptyTSK) require.NoError(t, err) From 5516f3492e4a2ee7f595e46b149fa00b88676680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 18 Jun 2021 12:02:42 +0200 Subject: [PATCH 079/257] ffiwrapper: Assert aggregate length --- extern/sector-storage/ffiwrapper/sealer_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/extern/sector-storage/ffiwrapper/sealer_test.go b/extern/sector-storage/ffiwrapper/sealer_test.go index df657f097..4adec48cf 100644 --- a/extern/sector-storage/ffiwrapper/sealer_test.go +++ b/extern/sector-storage/ffiwrapper/sealer_test.go @@ -544,6 +544,7 @@ func TestSealAndVerifyAggregate(t *testing.T) { avi.Proof, err = ProofProver.AggregateSealProofs(avi, toAggregate) require.NoError(t, err) + require.Len(t, avi.Proof, 11188) aggDone := time.Now() From 738ac926d1274c659bf694b06e306d299dc5fdd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 18 Jun 2021 15:49:59 +0200 Subject: [PATCH 080/257] Update ffi --- Makefile | 2 +- extern/filecoin-ffi | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 400788e2e..542ace849 100644 --- a/Makefile +++ b/Makefile @@ -42,7 +42,7 @@ BUILD_DEPS+=build/.filecoin-install CLEAN+=build/.filecoin-install ffi-version-check: - @[[ "$$(awk '/const Version/{print $$5}' extern/filecoin-ffi/version.go)" -eq 2 ]] || (echo "FFI version mismatch, update submodules"; exit 1) + @[[ "$$(awk '/const Version/{print $$5}' extern/filecoin-ffi/version.go)" -eq 3 ]] || (echo "FFI version mismatch, update submodules"; exit 1) BUILD_DEPS+=ffi-version-check .PHONY: ffi-version-check diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index 6f11cdffd..d60fc680a 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit 6f11cdffd4f6c7ae1b05d0e47f578d4c9b21415f +Subproject commit d60fc680aa8abeafba698f738fed5b94c9bda33d From e85af3cba7c9819526d6ce368e3dfc213c125bc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Fri, 18 Jun 2021 16:19:58 +0100 Subject: [PATCH 081/257] fix merge error. --- itests/wdpost_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/itests/wdpost_test.go b/itests/wdpost_test.go index 83ddf34e5..608c377ca 100644 --- a/itests/wdpost_test.go +++ b/itests/wdpost_test.go @@ -145,8 +145,8 @@ func testWindowPostUpgrade(t *testing.T, blocktime time.Duration, nSectors int, waitUntil = di.PeriodStart + di.WPoStProvingPeriod + 2 t.Logf("End for head.Height > %d", waitUntil) - height = kit2.WaitTillChainHeight(ctx, t, client, blocktime, int(waitUntil)) - t.Logf("Now head.Height = %d", height) + ts = client.WaitTillChain(ctx, kit2.HeightAtLeast(waitUntil)) + t.Logf("Now head.Height = %d", ts.Height()) p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) require.NoError(t, err) @@ -167,8 +167,8 @@ func testWindowPostUpgrade(t *testing.T, blocktime time.Duration, nSectors int, waitUntil = di.PeriodStart + di.WPoStProvingPeriod + 2 t.Logf("End for head.Height > %d", waitUntil) - height = kit2.WaitTillChainHeight(ctx, t, client, blocktime, int(waitUntil)) - t.Logf("Now head.Height = %d", height) + ts = client.WaitTillChain(ctx, kit2.HeightAtLeast(waitUntil)) + t.Logf("Now head.Height = %d", ts.Height()) p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) require.NoError(t, err) @@ -190,8 +190,8 @@ func testWindowPostUpgrade(t *testing.T, blocktime time.Duration, nSectors int, waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 2 t.Logf("End for head.Height > %d\n", waitUntil) - height = kit2.WaitTillChainHeight(ctx, t, client, blocktime, int(waitUntil)) - t.Logf("Now head.Height = %d", height) + ts := client.WaitTillChain(ctx, kit2.HeightAtLeast(waitUntil)) + t.Logf("Now head.Height = %d", ts.Height()) } p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) From 5548541e1a08c313c5e1d148ea739293329ced88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Fri, 18 Jun 2021 18:10:42 +0100 Subject: [PATCH 082/257] fix default proof type to use for non-genesis miners. We need to instantiate non-genesis miners with a _concrete_ proof type. --- itests/deadlines_test.go | 8 ++------ itests/kit2/ensemble_opts.go | 4 ++-- itests/kit2/node_opts_nv.go | 1 - 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/itests/deadlines_test.go b/itests/deadlines_test.go index 7b75eebbd..3c8303ac0 100644 --- a/itests/deadlines_test.go +++ b/itests/deadlines_test.go @@ -26,7 +26,6 @@ import ( 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" ) @@ -57,11 +56,8 @@ 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") + + kit2.QuietMiningLogs() const sectorsC, sectorsD, sectorsB = 10, 9, 8 diff --git a/itests/kit2/ensemble_opts.go b/itests/kit2/ensemble_opts.go index f8bc81535..8c6d66d9e 100644 --- a/itests/kit2/ensemble_opts.go +++ b/itests/kit2/ensemble_opts.go @@ -23,8 +23,8 @@ type ensembleOpts struct { } var DefaultEnsembleOpts = ensembleOpts{ - pastOffset: 10000000 * time.Second, // time sufficiently in the past to trigger catch-up mining. - proofType: abi.RegisteredSealProof_StackedDrg2KiBV1, + pastOffset: 10000000 * time.Second, // time sufficiently in the past to trigger catch-up mining. + proofType: abi.RegisteredSealProof_StackedDrg2KiBV1_1, // default _concrete_ proof type for non-genesis miners (notice the _1). } func ProofType(proofType abi.RegisteredSealProof) EnsembleOpt { diff --git a/itests/kit2/node_opts_nv.go b/itests/kit2/node_opts_nv.go index 39de9d9e2..5ffd94f5e 100644 --- a/itests/kit2/node_opts_nv.go +++ b/itests/kit2/node_opts_nv.go @@ -87,5 +87,4 @@ func SDRUpgradeAt(calico, persian abi.ChainEpoch) node.Option { Network: network.Version8, Height: persian, }}) - } From 2c2c37a1384348d1e5e4c819a9e8918d6adad888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 18 Jun 2021 19:15:08 +0200 Subject: [PATCH 083/257] chainstore: Don't take heaviestLk with backlogged reorgCh --- chain/store/store.go | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/chain/store/store.go b/chain/store/store.go index 5414b12fe..e2aa0100d 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -11,6 +11,7 @@ import ( "strconv" "strings" "sync" + "time" "github.com/filecoin-project/lotus/chain/state" @@ -366,7 +367,20 @@ func (cs *ChainStore) PutTipSet(ctx context.Context, ts *types.TipSet) error { // internal state as our new head, if and only if it is heavier than the current // head and does not exceed the maximum fork length. func (cs *ChainStore) MaybeTakeHeavierTipSet(ctx context.Context, ts *types.TipSet) error { - cs.heaviestLk.Lock() + for { + cs.heaviestLk.Lock() + if len(cs.reorgCh) < reorgChBuf/2 { + break + } + cs.heaviestLk.Unlock() + log.Errorf("reorg channel is heavily backlogged, waiting a bit before trying to take process new tipsets") + select { + case <-time.After(time.Second / 2): + case <-ctx.Done(): + return ctx.Err() + } + } + defer cs.heaviestLk.Unlock() w, err := cs.Weight(ctx, ts) if err != nil { @@ -483,8 +497,10 @@ type reorg struct { new *types.TipSet } +const reorgChBuf = 32 + func (cs *ChainStore) reorgWorker(ctx context.Context, initialNotifees []ReorgNotifee) chan<- reorg { - out := make(chan reorg, 32) + out := make(chan reorg, reorgChBuf) notifees := make([]ReorgNotifee, len(initialNotifees)) copy(notifees, initialNotifees) From 3d8eb374bd05fed40e1845a25afa0440587b83a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Fri, 18 Jun 2021 19:23:32 +0100 Subject: [PATCH 084/257] cleaner instantiation of lite and gateway nodes + general cleanup. --- itests/cli_test.go | 2 +- itests/gateway_test.go | 71 ++++++++++++++++++---------------------- itests/kit2/client.go | 2 +- itests/kit2/ensemble.go | 13 ++++---- itests/kit2/funds.go | 2 +- itests/multisig_test.go | 6 ++-- itests/paych_api_test.go | 2 +- itests/paych_cli_test.go | 2 +- 8 files changed, 45 insertions(+), 55 deletions(-) diff --git a/itests/cli_test.go b/itests/cli_test.go index 8436f189e..5c9cf0f69 100644 --- a/itests/cli_test.go +++ b/itests/cli_test.go @@ -17,5 +17,5 @@ func TestClient(t *testing.T) { blockTime := 5 * time.Millisecond client, _, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), kit2.ThroughRPC()) ens.InterconnectAll().BeginMining(blockTime) - kit2.RunClientTest(t, cli.Commands, *client) + kit2.RunClientTest(t, cli.Commands, client) } diff --git a/itests/gateway_test.go b/itests/gateway_test.go index 20b0add6a..36df41d54 100644 --- a/itests/gateway_test.go +++ b/itests/gateway_test.go @@ -5,7 +5,6 @@ import ( "context" "fmt" "math" - "os" "testing" "time" @@ -45,7 +44,6 @@ func init() { // TestGatewayWalletMsig tests that API calls to wallet and msig can be made on a lite // node that is connected through a gateway to a full API node func TestGatewayWalletMsig(t *testing.T) { - _ = os.Setenv("BELLMAN_NO_GPU", "1") kit2.QuietMiningLogs() blocktime := 5 * time.Millisecond @@ -111,7 +109,6 @@ func TestGatewayWalletMsig(t *testing.T) { if err != nil { return cid.Undef, err } - return lite.MpoolPush(ctx, sm) } @@ -179,7 +176,6 @@ func TestGatewayWalletMsig(t *testing.T) { // TestGatewayMsigCLI tests that msig CLI calls can be made // on a lite node that is connected through a gateway to a full API node func TestGatewayMsigCLI(t *testing.T) { - _ = os.Setenv("BELLMAN_NO_GPU", "1") kit2.QuietMiningLogs() blocktime := 5 * time.Millisecond @@ -192,7 +188,6 @@ func TestGatewayMsigCLI(t *testing.T) { } func TestGatewayDealFlow(t *testing.T) { - _ = os.Setenv("BELLMAN_NO_GPU", "1") kit2.QuietMiningLogs() blocktime := 5 * time.Millisecond @@ -200,18 +195,19 @@ func TestGatewayDealFlow(t *testing.T) { nodes := startNodesWithFunds(ctx, t, blocktime, maxLookbackCap, maxStateWaitLookbackLimit) defer nodes.closer() + time.Sleep(5 * time.Second) + // 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) - dh := kit2.NewDealHarness(t, &nodes.lite, &nodes.miner) + dh := kit2.NewDealHarness(t, nodes.lite, nodes.miner) dealCid, res, _ := dh.MakeOnlineDeal(ctx, 6, false, dealStartEpoch) dh.PerformRetrieval(ctx, dealCid, res.Root, false) } func TestGatewayCLIDealFlow(t *testing.T) { - _ = os.Setenv("BELLMAN_NO_GPU", "1") kit2.QuietMiningLogs() blocktime := 5 * time.Millisecond @@ -223,9 +219,9 @@ func TestGatewayCLIDealFlow(t *testing.T) { } type testNodes struct { - lite kit2.TestFullNode - full kit2.TestFullNode - miner kit2.TestMiner + lite *kit2.TestFullNode + full *kit2.TestFullNode + miner *kit2.TestMiner closer jsonrpc.ClientCloser } @@ -242,8 +238,8 @@ func startNodesWithFunds( fullWalletAddr, err := nodes.full.WalletDefaultAddress(ctx) require.NoError(t, err) - // Create a wallet on the lite node - liteWalletAddr, err := nodes.lite.WalletNew(ctx, types.KTSecp256k1) + // Get the lite node default wallet address. + liteWalletAddr, err := nodes.lite.WalletDefaultAddress(ctx) require.NoError(t, err) // Send some funds from the full node to the lite node @@ -263,9 +259,9 @@ func startNodes( var closer jsonrpc.ClientCloser var ( - full kit2.TestFullNode + full *kit2.TestFullNode + miner *kit2.TestMiner lite kit2.TestFullNode - miner kit2.TestMiner ) // - Create one full node and one lite node @@ -273,38 +269,35 @@ func startNodes( // - Start full node 2 in lite mode // - Connect lite node -> gateway server -> full node - var liteOptBuilder kit2.OptBuilder - liteOptBuilder = func(nodes []*kit2.TestFullNode) node.Option { - fullNode := nodes[0] + // create the full node and the miner. + var ens *kit2.Ensemble + full, miner, ens = kit2.EnsembleMinimal(t, kit2.MockProofs()) + ens.InterconnectAll().BeginMining(blocktime) - // Create a gateway server in front of the full node - gwapi := gateway.NewNode(fullNode, lookbackCap, stateWaitLookbackLimit) - handler, err := gateway.Handler(gwapi) - require.NoError(t, err) + // Create a gateway server in front of the full node + gwapi := gateway.NewNode(full, lookbackCap, stateWaitLookbackLimit) + handler, err := gateway.Handler(gwapi) + require.NoError(t, err) - srv, _ := kit2.CreateRPCServer(t, handler) + srv, _ := kit2.CreateRPCServer(t, handler) - // Create a gateway client API that connects to the gateway server - var gapi api.Gateway - gapi, closer, err = client.NewGatewayRPCV1(ctx, "ws://"+srv.Listener.Addr().String()+"/rpc/v1", nil) - require.NoError(t, err) + // Create a gateway client API that connects to the gateway server + var gapi api.Gateway + gapi, closer, err = client.NewGatewayRPCV1(ctx, "ws://"+srv.Listener.Addr().String()+"/rpc/v1", nil) + require.NoError(t, err) - // Provide the gateway API to dependency injection - return node.Override(new(api.Gateway), gapi) - } + ens.FullNode(&lite, + kit2.LiteNode(), + kit2.ThroughRPC(), + kit2.ConstructorOpts( + node.Override(new(api.Gateway), gapi), + ), + ).Start().InterconnectAll() - kit2.NewEnsemble(t, kit2.MockProofs()). - FullNode(&full). - FullNode(&lite, kit2.LiteNode(), kit2.ThroughRPC(), kit2.AddOptBuilder(liteOptBuilder)). - Miner(&miner, &full). - Start(). - InterconnectAll(). - BeginMining(blocktime) - - return &testNodes{lite: lite, full: full, miner: miner, closer: closer} + return &testNodes{lite: &lite, full: full, miner: miner, closer: closer} } -func sendFunds(ctx context.Context, fromNode kit2.TestFullNode, fromAddr address.Address, toAddr address.Address, amt types.BigInt) error { +func sendFunds(ctx context.Context, fromNode *kit2.TestFullNode, fromAddr address.Address, toAddr address.Address, amt types.BigInt) error { msg := &types.Message{ From: fromAddr, To: toAddr, diff --git a/itests/kit2/client.go b/itests/kit2/client.go index 247d20836..78a7034fe 100644 --- a/itests/kit2/client.go +++ b/itests/kit2/client.go @@ -21,7 +21,7 @@ import ( ) // RunClientTest exercises some of the Client CLI commands -func RunClientTest(t *testing.T, cmds []*lcli.Command, clientNode TestFullNode) { +func RunClientTest(t *testing.T, cmds []*lcli.Command, clientNode *TestFullNode) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() diff --git a/itests/kit2/ensemble.go b/itests/kit2/ensemble.go index 67e6b8592..44580920f 100644 --- a/itests/kit2/ensemble.go +++ b/itests/kit2/ensemble.go @@ -145,13 +145,13 @@ func (n *Ensemble) FullNode(full *TestFullNode, opts ...NodeOpt) *Ensemble { require.NoError(n.t, err) } - var key *wallet.Key - if !n.bootstrapped && !options.balance.IsZero() { - // create a key+address, and assign it some FIL; this will be set as the default wallet. - var err error - key, err = wallet.GenerateKey(types.KTBLS) - require.NoError(n.t, err) + key, err := wallet.GenerateKey(types.KTBLS) + require.NoError(n.t, err) + if !n.bootstrapped && !options.balance.IsZero() { + // if we still haven't forged genesis, create a key+address, and assign + // it some FIL; this will be set as the default wallet when the node is + // started. genacc := genesis.Actor{ Type: genesis.TAccount, Balance: options.balance, @@ -298,7 +298,6 @@ func (n *Ensemble) Start() *Ensemble { // Construct the full node. stop, err := node.New(ctx, opts...) - // fullOpts[i].Opts(fulls), require.NoError(n.t, err) addr, err := full.WalletImport(context.Background(), &full.DefaultKey.KeyInfo) diff --git a/itests/kit2/funds.go b/itests/kit2/funds.go index fc4a6c294..b29963353 100644 --- a/itests/kit2/funds.go +++ b/itests/kit2/funds.go @@ -14,7 +14,7 @@ import ( // SendFunds sends funds from the default wallet of the specified sender node // to the recipient address. -func SendFunds(ctx context.Context, t *testing.T, sender TestFullNode, recipient address.Address, amount abi.TokenAmount) { +func SendFunds(ctx context.Context, t *testing.T, sender *TestFullNode, recipient address.Address, amount abi.TokenAmount) { senderAddr, err := sender.WalletDefaultAddress(ctx) require.NoError(t, err) diff --git a/itests/multisig_test.go b/itests/multisig_test.go index f5df0be1a..9b1f59673 100644 --- a/itests/multisig_test.go +++ b/itests/multisig_test.go @@ -3,7 +3,6 @@ package itests import ( "context" "fmt" - "os" "regexp" "strings" "testing" @@ -18,17 +17,16 @@ import ( // TestMultisig does a basic test to exercise the multisig CLI commands func TestMultisig(t *testing.T) { - _ = os.Setenv("BELLMAN_NO_GPU", "1") kit2.QuietMiningLogs() blockTime := 5 * time.Millisecond client, _, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), kit2.ThroughRPC()) ens.InterconnectAll().BeginMining(blockTime) - runMultisigTests(t, *client) + runMultisigTests(t, client) } -func runMultisigTests(t *testing.T, clientNode kit2.TestFullNode) { +func runMultisigTests(t *testing.T, clientNode *kit2.TestFullNode) { // Create mock CLI ctx := context.Background() mockCLI := kit2.NewMockCLI(ctx, t, cli.Commands) diff --git a/itests/paych_api_test.go b/itests/paych_api_test.go index 8fb4cde0c..86a156790 100644 --- a/itests/paych_api_test.go +++ b/itests/paych_api_test.go @@ -51,7 +51,7 @@ func TestPaymentChannelsAPI(t *testing.T) { receiverAddr, err := paymentReceiver.WalletNew(ctx, types.KTSecp256k1) require.NoError(t, err) - kit2.SendFunds(ctx, t, paymentCreator, receiverAddr, abi.NewTokenAmount(1e18)) + kit2.SendFunds(ctx, t, &paymentCreator, receiverAddr, abi.NewTokenAmount(1e18)) // setup the payment channel createrAddr, err := paymentCreator.WalletDefaultAddress(ctx) diff --git a/itests/paych_cli_test.go b/itests/paych_cli_test.go index 2450828d3..84ccee95a 100644 --- a/itests/paych_cli_test.go +++ b/itests/paych_cli_test.go @@ -428,7 +428,7 @@ func startPaychCreatorReceiverMiner(ctx context.Context, t *testing.T, paymentCr // Send some funds to the second node receiverAddr, err := paymentReceiver.WalletDefaultAddress(ctx) require.NoError(t, err) - kit2.SendFunds(ctx, t, *paymentCreator, receiverAddr, abi.NewTokenAmount(1e18)) + kit2.SendFunds(ctx, t, paymentCreator, receiverAddr, abi.NewTokenAmount(1e18)) // Get the first node's address creatorAddr, err := paymentCreator.WalletDefaultAddress(ctx) From 718babd33a75154627c5f5851526ddb54029071c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Fri, 18 Jun 2021 19:38:17 +0100 Subject: [PATCH 085/257] use miner owner address when posting proofs. --- itests/wdpost_dispute_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/itests/wdpost_dispute_test.go b/itests/wdpost_dispute_test.go index 8661fba00..49b41c7e0 100644 --- a/itests/wdpost_dispute_test.go +++ b/itests/wdpost_dispute_test.go @@ -87,8 +87,8 @@ func TestWindowPostDispute(t *testing.T) { waitUntil := di.PeriodStart + di.WPoStProvingPeriod*2 t.Logf("End for head.Height > %d", waitUntil) - height := kit2.WaitTillChainHeight(ctx, t, &client, blocktime, int(waitUntil)) - t.Logf("Now head.Height = %d", height) + ts := client.WaitTillChain(ctx, kit2.HeightAtLeast(waitUntil)) + t.Logf("Now head.Height = %d", ts.Height()) p, err := client.StateMinerPower(ctx, evilMinerAddr, types.EmptyTSK) require.NoError(t, err) @@ -122,7 +122,7 @@ func TestWindowPostDispute(t *testing.T) { build.Clock.Sleep(blocktime) } - err = submitBadProof(ctx, client, evilMinerAddr, di, evilSectorLoc.Deadline, evilSectorLoc.Partition) + err = submitBadProof(ctx, client, evilMiner.OwnerKey.Address, evilMinerAddr, di, evilSectorLoc.Deadline, evilSectorLoc.Partition) require.NoError(t, err, "evil proof not accepted") // Wait until after the proving period. @@ -220,7 +220,7 @@ func TestWindowPostDispute(t *testing.T) { } // Now try to be evil again - err = submitBadProof(ctx, client, evilMinerAddr, di, evilSectorLoc.Deadline, evilSectorLoc.Partition) + err = submitBadProof(ctx, client, evilMiner.OwnerKey.Address, 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") @@ -260,8 +260,8 @@ func TestWindowPostDisputeFails(t *testing.T) { waitUntil := di.PeriodStart + di.WPoStProvingPeriod*2 t.Logf("End for head.Height > %d", waitUntil) - height := kit2.WaitTillChainHeight(ctx, t, client, blocktime, int(waitUntil)) - t.Logf("Now head.Height = %d", height) + ts := client.WaitTillChain(ctx, kit2.HeightAtLeast(waitUntil)) + t.Logf("Now head.Height = %d", ts.Height()) ssz, err := miner.ActorSectorSize(ctx, maddr) require.NoError(t, err) @@ -327,7 +327,7 @@ waitForProof: func submitBadProof( ctx context.Context, - client api.FullNode, maddr address.Address, + client api.FullNode, owner address.Address, maddr address.Address, di *dline.Info, dlIdx, partIdx uint64, ) error { head, err := client.ChainHead(ctx) From a7d8d15c13f18e4c0a6d82bfd7d205741c4053fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Fri, 18 Jun 2021 19:42:06 +0100 Subject: [PATCH 086/257] =?UTF-8?q?kill=20old=20kit=20=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- itests/kit/blockminer.go | 124 ------- itests/kit/client.go | 146 -------- itests/kit/deals.go | 312 ------------------ itests/kit/funds.go | 39 --- itests/kit/init.go | 32 -- itests/kit/log.go | 19 -- itests/kit/mockcli.go | 141 -------- itests/kit/net.go | 87 ----- itests/kit/node_builder.go | 658 ------------------------------------- itests/kit/nodes.go | 153 --------- itests/kit/pledge.go | 88 ----- 11 files changed, 1799 deletions(-) delete mode 100644 itests/kit/blockminer.go delete mode 100644 itests/kit/client.go delete mode 100644 itests/kit/deals.go delete mode 100644 itests/kit/funds.go delete mode 100644 itests/kit/init.go delete mode 100644 itests/kit/log.go delete mode 100644 itests/kit/mockcli.go delete mode 100644 itests/kit/net.go delete mode 100644 itests/kit/node_builder.go delete mode 100644 itests/kit/nodes.go delete mode 100644 itests/kit/pledge.go diff --git a/itests/kit/blockminer.go b/itests/kit/blockminer.go deleted file mode 100644 index 3b1f1fedf..000000000 --- a/itests/kit/blockminer.go +++ /dev/null @@ -1,124 +0,0 @@ -package kit - -import ( - "context" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/lotus/miner" - "github.com/stretchr/testify/require" -) - -// BlockMiner is a utility that makes a test miner Mine blocks on a timer. -type BlockMiner struct { - t *testing.T - miner TestMiner - - nextNulls int64 - wg sync.WaitGroup - cancel context.CancelFunc -} - -func NewBlockMiner(t *testing.T, miner TestMiner) *BlockMiner { - return &BlockMiner{ - t: t, - miner: miner, - cancel: func() {}, - } -} - -func (bm *BlockMiner) MineBlocks(ctx context.Context, blocktime time.Duration) { - time.Sleep(time.Second) - - // wrap context in a cancellable context. - ctx, bm.cancel = context.WithCancel(ctx) - - bm.wg.Add(1) - go func() { - defer bm.wg.Done() - - for { - select { - case <-time.After(blocktime): - case <-ctx.Done(): - return - } - - nulls := atomic.SwapInt64(&bm.nextNulls, 0) - err := bm.miner.MineOne(ctx, miner.MineReq{ - InjectNulls: abi.ChainEpoch(nulls), - Done: func(bool, abi.ChainEpoch, error) {}, - }) - switch { - case err == nil: // wrap around - case ctx.Err() != nil: // context fired. - return - default: // log error - bm.t.Error(err) - } - } - }() -} - -// InjectNulls injects the specified amount of null rounds in the next -// mining rounds. -func (bm *BlockMiner) InjectNulls(rounds abi.ChainEpoch) { - atomic.AddInt64(&bm.nextNulls, int64(rounds)) -} - -func (bm *BlockMiner) MineUntilBlock(ctx context.Context, fn TestFullNode, cb func(abi.ChainEpoch)) { - for i := 0; i < 1000; i++ { - var ( - success bool - err error - epoch abi.ChainEpoch - wait = make(chan struct{}) - ) - - doneFn := func(win bool, ep abi.ChainEpoch, e error) { - success = win - err = e - epoch = ep - wait <- struct{}{} - } - - mineErr := bm.miner.MineOne(ctx, miner.MineReq{Done: doneFn}) - require.NoError(bm.t, mineErr) - <-wait - - require.NoError(bm.t, err) - - if success { - // Wait until it shows up on the given full nodes ChainHead - nloops := 50 - for i := 0; i < nloops; i++ { - ts, err := fn.ChainHead(ctx) - require.NoError(bm.t, err) - - if ts.Height() == epoch { - break - } - - require.NotEqual(bm.t, i, nloops-1, "block never managed to sync to node") - time.Sleep(time.Millisecond * 10) - } - - if cb != nil { - cb(epoch) - } - return - } - bm.t.Log("did not Mine block, trying again", i) - } - bm.t.Fatal("failed to Mine 1000 times in a row...") -} - -// Stop stops the block miner. -func (bm *BlockMiner) Stop() { - bm.t.Log("shutting down mining") - bm.cancel() - bm.wg.Wait() -} diff --git a/itests/kit/client.go b/itests/kit/client.go deleted file mode 100644 index 6b7d46265..000000000 --- a/itests/kit/client.go +++ /dev/null @@ -1,146 +0,0 @@ -package kit - -import ( - "context" - "fmt" - "io/ioutil" - "math/rand" - "os" - "path/filepath" - "regexp" - "strings" - "testing" - "time" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/specs-actors/v2/actors/builtin" - "github.com/stretchr/testify/require" - lcli "github.com/urfave/cli/v2" -) - -// RunClientTest exercises some of the Client CLI commands -func RunClientTest(t *testing.T, cmds []*lcli.Command, clientNode TestFullNode) { - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - - // Create mock CLI - mockCLI := NewMockCLI(ctx, t, cmds) - clientCLI := mockCLI.Client(clientNode.ListenAddr) - - // Get the Miner address - addrs, err := clientNode.StateListMiners(ctx, types.EmptyTSK) - require.NoError(t, err) - require.Len(t, addrs, 1) - - minerAddr := addrs[0] - fmt.Println("Miner:", minerAddr) - - // client query-ask - out := clientCLI.RunCmd("client", "query-ask", minerAddr.String()) - require.Regexp(t, regexp.MustCompile("Ask:"), out) - - // Create a deal (non-interactive) - // client deal --start-epoch= 1000000attofil - res, _, _, err := CreateImportFile(ctx, clientNode, 1, 0) - - require.NoError(t, err) - startEpoch := fmt.Sprintf("--start-epoch=%d", 2<<12) - dataCid := res.Root - price := "1000000attofil" - duration := fmt.Sprintf("%d", build.MinDealDuration) - out = clientCLI.RunCmd("client", "deal", startEpoch, dataCid.String(), minerAddr.String(), price, duration) - fmt.Println("client deal", out) - - // Create a deal (interactive) - // client deal - // - // (in days) - // - // "no" (verified Client) - // "yes" (confirm deal) - res, _, _, err = CreateImportFile(ctx, clientNode, 2, 0) - require.NoError(t, err) - dataCid2 := res.Root - duration = fmt.Sprintf("%d", build.MinDealDuration/builtin.EpochsInDay) - cmd := []string{"client", "deal"} - interactiveCmds := []string{ - dataCid2.String(), - duration, - minerAddr.String(), - "no", - "yes", - } - out = clientCLI.RunInteractiveCmd(cmd, interactiveCmds) - fmt.Println("client deal:\n", out) - - // Wait for provider to start sealing deal - dealStatus := "" - for { - // client list-deals - out = clientCLI.RunCmd("client", "list-deals") - fmt.Println("list-deals:\n", out) - - lines := strings.Split(out, "\n") - require.GreaterOrEqual(t, len(lines), 2) - re := regexp.MustCompile(`\s+`) - parts := re.Split(lines[1], -1) - if len(parts) < 4 { - require.Fail(t, "bad list-deals output format") - } - dealStatus = parts[3] - fmt.Println(" Deal status:", dealStatus) - - st := CategorizeDealState(dealStatus) - require.NotEqual(t, TestDealStateFailed, st) - if st == TestDealStateComplete { - break - } - - time.Sleep(time.Second) - } - - // Retrieve the first file from the Miner - // client retrieve - tmpdir, err := ioutil.TempDir(os.TempDir(), "test-cli-Client") - require.NoError(t, err) - path := filepath.Join(tmpdir, "outfile.dat") - out = clientCLI.RunCmd("client", "retrieve", dataCid.String(), path) - fmt.Println("retrieve:\n", out) - require.Regexp(t, regexp.MustCompile("Success"), out) -} - -func CreateImportFile(ctx context.Context, client api.FullNode, rseed int, size int) (res *api.ImportRes, path string, data []byte, err error) { - data, path, err = createRandomFile(rseed, size) - if err != nil { - return nil, "", nil, err - } - - res, err = client.ClientImport(ctx, api.FileRef{Path: path}) - if err != nil { - return nil, "", nil, err - } - return res, path, data, nil -} - -func createRandomFile(rseed, size int) ([]byte, string, error) { - if size == 0 { - size = 1600 - } - data := make([]byte, size) - rand.New(rand.NewSource(int64(rseed))).Read(data) - - dir, err := ioutil.TempDir(os.TempDir(), "test-make-deal-") - if err != nil { - return nil, "", err - } - - path := filepath.Join(dir, "sourcefile.dat") - err = ioutil.WriteFile(path, data, 0644) - if err != nil { - return nil, "", err - } - - return data, path, nil -} diff --git a/itests/kit/deals.go b/itests/kit/deals.go deleted file mode 100644 index c768eb87f..000000000 --- a/itests/kit/deals.go +++ /dev/null @@ -1,312 +0,0 @@ -package kit - -import ( - "bytes" - "context" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "testing" - "time" - - "github.com/ipfs/go-cid" - files "github.com/ipfs/go-ipfs-files" - "github.com/ipld/go-car" - "github.com/stretchr/testify/require" - - "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/build" - "github.com/filecoin-project/lotus/chain/types" - sealing "github.com/filecoin-project/lotus/extern/storage-sealing" - "github.com/filecoin-project/lotus/node/impl" - 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" -) - -type DealHarness struct { - t *testing.T - client api.FullNode - miner TestMiner -} - -// NewDealHarness creates a test harness that contains testing utilities for deals. -func NewDealHarness(t *testing.T, client api.FullNode, miner TestMiner) *DealHarness { - return &DealHarness{ - t: t, - client: client, - miner: miner, - } -} - -func (dh *DealHarness) MakeFullDeal(ctx context.Context, rseed int, carExport, fastRet bool, startEpoch abi.ChainEpoch) { - res, _, data, err := CreateImportFile(ctx, dh.client, rseed, 0) - if err != nil { - dh.t.Fatal(err) - } - - fcid := res.Root - fmt.Println("FILE CID: ", fcid) - - deal := dh.StartDeal(ctx, fcid, fastRet, 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) - dh.WaitDealSealed(ctx, deal, false, false, nil) - - // Retrieval - info, err := dh.client.ClientGetDealInfo(ctx, *deal) - require.NoError(dh.t, err) - - dh.TestRetrieval(ctx, fcid, &info.PieceCID, carExport, data) -} - -func (dh *DealHarness) StartDeal(ctx context.Context, fcid cid.Cid, fastRet bool, startEpoch abi.ChainEpoch) *cid.Cid { - maddr, err := dh.miner.ActorAddress(ctx) - if err != nil { - dh.t.Fatal(err) - } - - addr, err := dh.client.WalletDefaultAddress(ctx) - if err != nil { - dh.t.Fatal(err) - } - deal, err := dh.client.ClientStartDeal(ctx, &api.StartDealParams{ - Data: &storagemarket.DataRef{ - TransferType: storagemarket.TTGraphsync, - Root: fcid, - }, - Wallet: addr, - Miner: maddr, - EpochPrice: types.NewInt(1000000), - DealStartEpoch: startEpoch, - MinBlocksDuration: uint64(build.MinDealDuration), - FastRetrieval: fastRet, - }) - if err != nil { - dh.t.Fatalf("%+v", err) - } - return deal -} - -func (dh *DealHarness) WaitDealSealed(ctx context.Context, deal *cid.Cid, noseal, noSealStart bool, cb func()) { -loop: - for { - di, err := dh.client.ClientGetDealInfo(ctx, *deal) - require.NoError(dh.t, err) - - switch di.State { - case storagemarket.StorageDealAwaitingPreCommit, storagemarket.StorageDealSealing: - if noseal { - return - } - if !noSealStart { - dh.StartSealingWaiting(ctx) - } - case storagemarket.StorageDealProposalRejected: - dh.t.Fatal("deal rejected") - case storagemarket.StorageDealFailing: - dh.t.Fatal("deal failed") - case storagemarket.StorageDealError: - dh.t.Fatal("deal errored", di.Message) - case storagemarket.StorageDealActive: - fmt.Println("COMPLETE", di) - break loop - } - - mds, err := dh.miner.MarketListIncompleteDeals(ctx) - require.NoError(dh.t, err) - - var minerState storagemarket.StorageDealStatus - for _, md := range mds { - if md.DealID == di.DealID { - minerState = md.State - break - } - } - - fmt.Printf("Deal %d state: client:%s provider:%s\n", di.DealID, storagemarket.DealStates[di.State], storagemarket.DealStates[minerState]) - time.Sleep(time.Second / 2) - if cb != nil { - cb() - } - } -} - -func (dh *DealHarness) WaitDealPublished(ctx context.Context, deal *cid.Cid) { - subCtx, cancel := context.WithCancel(ctx) - defer cancel() - updates, err := dh.miner.MarketGetDealUpdates(subCtx) - if err != nil { - dh.t.Fatal(err) - } - for { - select { - case <-ctx.Done(): - dh.t.Fatal("context timeout") - case di := <-updates: - if deal.Equals(di.ProposalCid) { - switch di.State { - case storagemarket.StorageDealProposalRejected: - dh.t.Fatal("deal rejected") - case storagemarket.StorageDealFailing: - dh.t.Fatal("deal failed") - case storagemarket.StorageDealError: - dh.t.Fatal("deal errored", di.Message) - case storagemarket.StorageDealFinalizing, storagemarket.StorageDealAwaitingPreCommit, storagemarket.StorageDealSealing, storagemarket.StorageDealActive: - fmt.Println("COMPLETE", di) - return - } - fmt.Println("Deal state: ", storagemarket.DealStates[di.State]) - } - } - } -} - -func (dh *DealHarness) StartSealingWaiting(ctx context.Context) { - snums, err := dh.miner.SectorsList(ctx) - require.NoError(dh.t, err) - - for _, snum := range snums { - si, err := dh.miner.SectorsStatus(ctx, snum, false) - require.NoError(dh.t, err) - - dh.t.Logf("Sector state: %s", si.State) - if si.State == api.SectorState(sealing.WaitDeals) { - require.NoError(dh.t, dh.miner.SectorStartSealing(ctx, snum)) - } - - flushSealingBatches(dh.t, ctx, dh.miner) - } -} - -func (dh *DealHarness) TestRetrieval(ctx context.Context, fcid cid.Cid, piece *cid.Cid, carExport bool, expect []byte) { - offers, err := dh.client.ClientFindData(ctx, fcid, piece) - if err != nil { - dh.t.Fatal(err) - } - - if len(offers) < 1 { - dh.t.Fatal("no offers") - } - - rpath, err := ioutil.TempDir("", "lotus-retrieve-test-") - if err != nil { - dh.t.Fatal(err) - } - defer os.RemoveAll(rpath) //nolint:errcheck - - caddr, err := dh.client.WalletDefaultAddress(ctx) - if err != nil { - dh.t.Fatal(err) - } - - ref := &api.FileRef{ - Path: filepath.Join(rpath, "ret"), - IsCAR: carExport, - } - updates, err := dh.client.ClientRetrieveWithEvents(ctx, offers[0].Order(caddr), ref) - if err != nil { - dh.t.Fatal(err) - } - for update := range updates { - if update.Err != "" { - dh.t.Fatalf("retrieval failed: %s", update.Err) - } - } - - rdata, err := ioutil.ReadFile(filepath.Join(rpath, "ret")) - if err != nil { - dh.t.Fatal(err) - } - - if carExport { - rdata = dh.ExtractCarData(ctx, rdata, rpath) - } - - if !bytes.Equal(rdata, expect) { - dh.t.Fatal("wrong expect retrieved") - } -} - -func (dh *DealHarness) ExtractCarData(ctx context.Context, rdata []byte, rpath string) []byte { - bserv := dstest.Bserv() - ch, err := car.LoadCar(bserv.Blockstore(), bytes.NewReader(rdata)) - if err != nil { - dh.t.Fatal(err) - } - b, err := bserv.GetBlock(ctx, ch.Roots[0]) - if err != nil { - dh.t.Fatal(err) - } - nd, err := ipld.Decode(b) - if err != nil { - dh.t.Fatal(err) - } - dserv := dag.NewDAGService(bserv) - fil, err := unixfile.NewUnixfsFile(ctx, dserv, nd) - if err != nil { - dh.t.Fatal(err) - } - outPath := filepath.Join(rpath, "retLoadedCAR") - if err := files.WriteTo(fil, outPath); err != nil { - dh.t.Fatal(err) - } - rdata, err = ioutil.ReadFile(outPath) - if err != nil { - dh.t.Fatal(err) - } - return rdata -} - -type DealsScaffold struct { - Ctx context.Context - Client *impl.FullNodeAPI - Miner TestMiner - BlockMiner *BlockMiner -} - -func ConnectAndStartMining(t *testing.T, blocktime time.Duration, miner TestMiner, clients ...api.FullNode) *BlockMiner { - ctx := context.Background() - - for _, c := range clients { - addrinfo, err := c.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - if err := miner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - } - - time.Sleep(time.Second) - - blockMiner := NewBlockMiner(t, miner) - blockMiner.MineBlocks(ctx, blocktime) - t.Cleanup(blockMiner.Stop) - return blockMiner -} - -type TestDealState int - -const ( - TestDealStateFailed = TestDealState(-1) - TestDealStateInProgress = TestDealState(0) - TestDealStateComplete = TestDealState(1) -) - -// CategorizeDealState categorizes deal states into one of three states: -// Complete, InProgress, Failed. -func CategorizeDealState(dealStatus string) TestDealState { - switch dealStatus { - case "StorageDealFailing", "StorageDealError": - return TestDealStateFailed - case "StorageDealStaged", "StorageDealAwaitingPreCommit", "StorageDealSealing", "StorageDealActive", "StorageDealExpired", "StorageDealSlashed": - return TestDealStateComplete - } - return TestDealStateInProgress -} diff --git a/itests/kit/funds.go b/itests/kit/funds.go deleted file mode 100644 index 4c739dc62..000000000 --- a/itests/kit/funds.go +++ /dev/null @@ -1,39 +0,0 @@ -package kit - -import ( - "context" - "testing" - - "github.com/filecoin-project/go-state-types/abi" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/chain/types" -) - -// SendFunds sends funds from the default wallet of the specified sender node -// to the recipient address. -func SendFunds(ctx context.Context, t *testing.T, sender TestFullNode, recipient address.Address, amount abi.TokenAmount) { - senderAddr, err := sender.WalletDefaultAddress(ctx) - if err != nil { - t.Fatal(err) - } - - msg := &types.Message{ - From: senderAddr, - To: recipient, - Value: amount, - } - - sm, err := sender.MpoolPushMessage(ctx, msg, nil) - if err != nil { - t.Fatal(err) - } - res, err := sender.StateWaitMsg(ctx, sm.Cid(), 3, api.LookbackNoLimit, true) - if err != nil { - t.Fatal(err) - } - if res.Receipt.ExitCode != 0 { - t.Fatal("did not successfully send money") - } -} diff --git a/itests/kit/init.go b/itests/kit/init.go deleted file mode 100644 index 2f40ca0f0..000000000 --- a/itests/kit/init.go +++ /dev/null @@ -1,32 +0,0 @@ -package kit - -import ( - "fmt" - "os" - "strings" - - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/actors/policy" - logging "github.com/ipfs/go-log/v2" -) - -func init() { - bin := os.Args[0] - if !strings.HasSuffix(bin, ".test") { - panic("package itests/kit must only be imported from tests") - } - - _ = logging.SetLogLevel("*", "INFO") - - policy.SetConsensusMinerMinPower(abi.NewStoragePower(2048)) - policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1) - policy.SetMinVerifiedDealSize(abi.NewStoragePower(256)) - - err := os.Setenv("BELLMAN_NO_GPU", "1") - if err != nil { - panic(fmt.Sprintf("failed to set BELLMAN_NO_GPU env variable: %s", err)) - } - build.InsecurePoStValidation = true - -} diff --git a/itests/kit/log.go b/itests/kit/log.go deleted file mode 100644 index 638e768d8..000000000 --- a/itests/kit/log.go +++ /dev/null @@ -1,19 +0,0 @@ -package kit - -import ( - "github.com/filecoin-project/lotus/lib/lotuslog" - logging "github.com/ipfs/go-log/v2" -) - -func QuietMiningLogs() { - lotuslog.SetupLogLevels() - - _ = 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/kit/mockcli.go b/itests/kit/mockcli.go deleted file mode 100644 index c0f218920..000000000 --- a/itests/kit/mockcli.go +++ /dev/null @@ -1,141 +0,0 @@ -package kit - -import ( - "bytes" - "context" - "flag" - "strings" - "testing" - - "github.com/multiformats/go-multiaddr" - "github.com/stretchr/testify/require" - lcli "github.com/urfave/cli/v2" -) - -type MockCLI struct { - t *testing.T - cmds []*lcli.Command - cctx *lcli.Context - out *bytes.Buffer -} - -func NewMockCLI(ctx context.Context, t *testing.T, cmds []*lcli.Command) *MockCLI { - // Create a CLI App with an --api-url flag so that we can specify which node - // the command should be executed against - app := &lcli.App{ - Flags: []lcli.Flag{ - &lcli.StringFlag{ - Name: "api-url", - Hidden: true, - }, - }, - Commands: cmds, - } - - var out bytes.Buffer - app.Writer = &out - app.Setup() - - cctx := lcli.NewContext(app, &flag.FlagSet{}, nil) - cctx.Context = ctx - return &MockCLI{t: t, cmds: cmds, cctx: cctx, out: &out} -} - -func (c *MockCLI) Client(addr multiaddr.Multiaddr) *MockCLIClient { - return &MockCLIClient{t: c.t, cmds: c.cmds, addr: addr, cctx: c.cctx, out: c.out} -} - -// MockCLIClient runs commands against a particular node -type MockCLIClient struct { - t *testing.T - cmds []*lcli.Command - addr multiaddr.Multiaddr - cctx *lcli.Context - out *bytes.Buffer -} - -func (c *MockCLIClient) RunCmd(input ...string) string { - out, err := c.RunCmdRaw(input...) - require.NoError(c.t, err, "output:\n%s", out) - - return out -} - -// Given an input, find the corresponding command or sub-command. -// eg "paych add-funds" -func (c *MockCLIClient) cmdByNameSub(input []string) (*lcli.Command, []string) { - name := input[0] - for _, cmd := range c.cmds { - if cmd.Name == name { - return c.findSubcommand(cmd, input[1:]) - } - } - return nil, []string{} -} - -func (c *MockCLIClient) findSubcommand(cmd *lcli.Command, input []string) (*lcli.Command, []string) { - // If there are no sub-commands, return the current command - if len(cmd.Subcommands) == 0 { - return cmd, input - } - - // Check each sub-command for a match against the name - subName := input[0] - for _, subCmd := range cmd.Subcommands { - if subCmd.Name == subName { - // Found a match, recursively search for sub-commands - return c.findSubcommand(subCmd, input[1:]) - } - } - return nil, []string{} -} - -func (c *MockCLIClient) RunCmdRaw(input ...string) (string, error) { - cmd, input := c.cmdByNameSub(input) - if cmd == nil { - panic("Could not find command " + input[0] + " " + input[1]) - } - - // prepend --api-url= - apiFlag := "--api-url=" + c.addr.String() - input = append([]string{apiFlag}, input...) - - fs := c.flagSet(cmd) - err := fs.Parse(input) - require.NoError(c.t, err) - - err = cmd.Action(lcli.NewContext(c.cctx.App, fs, c.cctx)) - - // Get the output - str := strings.TrimSpace(c.out.String()) - c.out.Reset() - return str, err -} - -func (c *MockCLIClient) flagSet(cmd *lcli.Command) *flag.FlagSet { - // Apply app level flags (so we can process --api-url flag) - fs := &flag.FlagSet{} - for _, f := range c.cctx.App.Flags { - err := f.Apply(fs) - if err != nil { - c.t.Fatal(err) - } - } - // Apply command level flags - for _, f := range cmd.Flags { - err := f.Apply(fs) - if err != nil { - c.t.Fatal(err) - } - } - return fs -} - -func (c *MockCLIClient) RunInteractiveCmd(cmd []string, interactive []string) string { - c.toStdin(strings.Join(interactive, "\n") + "\n") - return c.RunCmd(cmd...) -} - -func (c *MockCLIClient) toStdin(s string) { - c.cctx.App.Metadata["stdin"] = bytes.NewBufferString(s) -} diff --git a/itests/kit/net.go b/itests/kit/net.go deleted file mode 100644 index 54c72443f..000000000 --- a/itests/kit/net.go +++ /dev/null @@ -1,87 +0,0 @@ -package kit - -import ( - "context" - "testing" - "time" - - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/lotus/chain/types" - - "github.com/filecoin-project/go-address" -) - -func StartOneNodeOneMiner(ctx context.Context, t *testing.T, blocktime time.Duration) (TestFullNode, address.Address) { - n, sn := RPCMockMinerBuilder(t, OneFull, OneMiner) - - full := n[0] - miner := sn[0] - - // Get everyone connected - addrs, err := full.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrs); err != nil { - t.Fatal(err) - } - - // Start mining blocks - bm := NewBlockMiner(t, miner) - bm.MineBlocks(ctx, blocktime) - t.Cleanup(bm.Stop) - - // Get the full node's wallet address - fullAddr, err := full.WalletDefaultAddress(ctx) - if err != nil { - t.Fatal(err) - } - - // Create mock CLI - return full, fullAddr -} - -func StartTwoNodesOneMiner(ctx context.Context, t *testing.T, blocktime time.Duration) ([]TestFullNode, []address.Address) { - n, sn := RPCMockMinerBuilder(t, TwoFull, OneMiner) - - fullNode1 := n[0] - fullNode2 := n[1] - miner := sn[0] - - // Get everyone connected - addrs, err := fullNode1.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := fullNode2.NetConnect(ctx, addrs); err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrs); err != nil { - t.Fatal(err) - } - - // Start mining blocks - bm := NewBlockMiner(t, miner) - bm.MineBlocks(ctx, blocktime) - t.Cleanup(bm.Stop) - - // Send some funds to register the second node - fullNodeAddr2, err := fullNode2.WalletNew(ctx, types.KTSecp256k1) - if err != nil { - t.Fatal(err) - } - - SendFunds(ctx, t, fullNode1, fullNodeAddr2, abi.NewTokenAmount(1e18)) - - // Get the first node's address - fullNodeAddr1, err := fullNode1.WalletDefaultAddress(ctx) - if err != nil { - t.Fatal(err) - } - - // Create mock CLI - return n, []address.Address{fullNodeAddr1, fullNodeAddr2} -} diff --git a/itests/kit/node_builder.go b/itests/kit/node_builder.go deleted file mode 100644 index 3780a7669..000000000 --- a/itests/kit/node_builder.go +++ /dev/null @@ -1,658 +0,0 @@ -package kit - -import ( - "bytes" - "context" - "crypto/rand" - "io/ioutil" - "net/http" - "net/http/httptest" - "sync" - "testing" - "time" - - "github.com/filecoin-project/go-state-types/network" - - "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/exitcode" - "github.com/filecoin-project/go-storedcounter" - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/api/client" - "github.com/filecoin-project/lotus/api/v1api" - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain" - "github.com/filecoin-project/lotus/chain/actors" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/actors/builtin/power" - "github.com/filecoin-project/lotus/chain/gen" - genesis2 "github.com/filecoin-project/lotus/chain/gen/genesis" - "github.com/filecoin-project/lotus/chain/messagepool" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/chain/wallet" - "github.com/filecoin-project/lotus/cmd/lotus-seed/seed" - sectorstorage "github.com/filecoin-project/lotus/extern/sector-storage" - "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" - "github.com/filecoin-project/lotus/extern/sector-storage/mock" - "github.com/filecoin-project/lotus/genesis" - lotusminer "github.com/filecoin-project/lotus/miner" - "github.com/filecoin-project/lotus/node" - "github.com/filecoin-project/lotus/node/modules" - "github.com/filecoin-project/lotus/node/modules/dtypes" - testing2 "github.com/filecoin-project/lotus/node/modules/testing" - "github.com/filecoin-project/lotus/node/repo" - "github.com/filecoin-project/lotus/storage/mockstorage" - miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" - power2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/power" - "github.com/ipfs/go-datastore" - "github.com/libp2p/go-libp2p-core/crypto" - "github.com/libp2p/go-libp2p-core/peer" - mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" - "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr/net" - "github.com/stretchr/testify/require" -) - -func init() { - chain.BootstrapPeerThreshold = 1 - messagepool.HeadChangeCoalesceMinDelay = time.Microsecond - messagepool.HeadChangeCoalesceMaxDelay = 2 * time.Microsecond - messagepool.HeadChangeCoalesceMergeInterval = 100 * time.Nanosecond -} - -func CreateTestStorageNode(ctx context.Context, t *testing.T, waddr address.Address, act address.Address, pk crypto.PrivKey, tnd TestFullNode, mn mocknet.Mocknet, opts node.Option) TestMiner { - r := repo.NewMemory(nil) - - lr, err := r.Lock(repo.StorageMiner) - require.NoError(t, err) - - ks, err := lr.KeyStore() - require.NoError(t, err) - - kbytes, err := pk.Bytes() - require.NoError(t, err) - - err = ks.Put("libp2p-host", types.KeyInfo{ - Type: "libp2p-host", - PrivateKey: kbytes, - }) - require.NoError(t, err) - - ds, err := lr.Datastore(context.TODO(), "/metadata") - require.NoError(t, err) - err = ds.Put(datastore.NewKey("miner-address"), act.Bytes()) - require.NoError(t, err) - - nic := storedcounter.New(ds, datastore.NewKey(modules.StorageCounterDSPrefix)) - for i := 0; i < GenesisPreseals; i++ { - _, err := nic.Next() - require.NoError(t, err) - } - _, err = nic.Next() - require.NoError(t, err) - - err = lr.Close() - require.NoError(t, err) - - peerid, err := peer.IDFromPrivateKey(pk) - require.NoError(t, err) - - enc, err := actors.SerializeParams(&miner2.ChangePeerIDParams{NewID: abi.PeerID(peerid)}) - require.NoError(t, err) - - msg := &types.Message{ - To: act, - From: waddr, - Method: miner.Methods.ChangePeerID, - Params: enc, - Value: types.NewInt(0), - } - - _, err = tnd.MpoolPushMessage(ctx, msg, nil) - require.NoError(t, err) - - // start node - var minerapi api.StorageMiner - - mineBlock := make(chan lotusminer.MineReq) - stop, err := node.New(ctx, - node.StorageMiner(&minerapi), - node.Online(), - node.Repo(r), - node.Test(), - - node.MockHost(mn), - - node.Override(new(v1api.FullNode), tnd), - node.Override(new(*lotusminer.Miner), lotusminer.NewTestMiner(mineBlock, act)), - - opts, - ) - if err != nil { - t.Fatalf("failed to construct node: %v", err) - } - - t.Cleanup(func() { _ = stop(context.Background()) }) - - /*// Bootstrap with full node - remoteAddrs, err := tnd.NetAddrsListen(Ctx) - require.NoError(t, err) - - err = minerapi.NetConnect(Ctx, remoteAddrs) - require.NoError(t, err)*/ - mineOne := func(ctx context.Context, req lotusminer.MineReq) error { - select { - case mineBlock <- req: - return nil - case <-ctx.Done(): - return ctx.Err() - } - } - - return TestMiner{StorageMiner: minerapi, MineOne: mineOne, Stop: stop} -} - -func storageBuilder(parentNode TestFullNode, mn mocknet.Mocknet, opts node.Option) MinerBuilder { - return func(ctx context.Context, t *testing.T, spt abi.RegisteredSealProof, owner address.Address) TestMiner { - pk, _, err := crypto.GenerateEd25519Key(rand.Reader) - require.NoError(t, err) - - minerPid, err := peer.IDFromPrivateKey(pk) - require.NoError(t, err) - - params, serr := actors.SerializeParams(&power2.CreateMinerParams{ - Owner: owner, - Worker: owner, - SealProofType: spt, - Peer: abi.PeerID(minerPid), - }) - require.NoError(t, serr) - - createStorageMinerMsg := &types.Message{ - To: power.Address, - From: owner, - Value: big.Zero(), - - Method: power.Methods.CreateMiner, - Params: params, - - GasLimit: 0, - GasPremium: big.NewInt(5252), - } - - signed, err := parentNode.MpoolPushMessage(ctx, createStorageMinerMsg, nil) - require.NoError(t, err) - - mw, err := parentNode.StateWaitMsg(ctx, signed.Cid(), build.MessageConfidence, api.LookbackNoLimit, true) - require.NoError(t, err) - require.Equal(t, exitcode.Ok, mw.Receipt.ExitCode) - - var retval power2.CreateMinerReturn - err = retval.UnmarshalCBOR(bytes.NewReader(mw.Receipt.Return)) - require.NoError(t, err) - - return CreateTestStorageNode(ctx, t, owner, retval.IDAddress, pk, parentNode, mn, opts) - } -} - -func Builder(t *testing.T, fullOpts []FullNodeOpts, storage []StorageMiner) ([]TestFullNode, []TestMiner) { - return mockBuilderOpts(t, fullOpts, storage, false) -} - -func RPCBuilder(t *testing.T, fullOpts []FullNodeOpts, storage []StorageMiner) ([]TestFullNode, []TestMiner) { - return mockBuilderOpts(t, fullOpts, storage, true) -} - -func MockMinerBuilder(t *testing.T, fullOpts []FullNodeOpts, storage []StorageMiner) ([]TestFullNode, []TestMiner) { - return mockMinerBuilderOpts(t, fullOpts, storage, false) -} - -func RPCMockMinerBuilder(t *testing.T, fullOpts []FullNodeOpts, storage []StorageMiner) ([]TestFullNode, []TestMiner) { - return mockMinerBuilderOpts(t, fullOpts, storage, true) -} - -func mockBuilderOpts(t *testing.T, fullOpts []FullNodeOpts, storage []StorageMiner, rpc bool) ([]TestFullNode, []TestMiner) { - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - mn := mocknet.New(ctx) - - fulls := make([]TestFullNode, len(fullOpts)) - miners := make([]TestMiner, len(storage)) - - // ***** - pk, _, err := crypto.GenerateEd25519Key(rand.Reader) - require.NoError(t, err) - - minerPid, err := peer.IDFromPrivateKey(pk) - require.NoError(t, err) - - var genbuf bytes.Buffer - - if len(storage) > 1 { - panic("need more peer IDs") - } - // ***** - - // PRESEAL SECTION, TRY TO REPLACE WITH BETTER IN THE FUTURE - // TODO: would be great if there was a better way to fake the preseals - - var ( - genms []genesis.Miner - maddrs []address.Address - genaccs []genesis.Actor - keys []*wallet.Key - ) - - var presealDirs []string - for i := 0; i < len(storage); i++ { - maddr, err := address.NewIDAddress(genesis2.MinerStart + uint64(i)) - if err != nil { - t.Fatal(err) - } - tdir, err := ioutil.TempDir("", "preseal-memgen") - if err != nil { - t.Fatal(err) - } - genm, k, err := seed.PreSeal(maddr, abi.RegisteredSealProof_StackedDrg2KiBV1, 0, GenesisPreseals, tdir, []byte("make genesis mem random"), nil, true) - if err != nil { - t.Fatal(err) - } - genm.PeerId = minerPid - - wk, err := wallet.NewKey(*k) - if err != nil { - return nil, nil - } - - genaccs = append(genaccs, genesis.Actor{ - Type: genesis.TAccount, - Balance: big.Mul(big.NewInt(400000000), types.NewInt(build.FilecoinPrecision)), - Meta: (&genesis.AccountMeta{Owner: wk.Address}).ActorMeta(), - }) - - keys = append(keys, wk) - presealDirs = append(presealDirs, tdir) - maddrs = append(maddrs, maddr) - genms = append(genms, *genm) - } - - rkhKey, err := wallet.GenerateKey(types.KTSecp256k1) - if err != nil { - return nil, nil - } - - vrk := genesis.Actor{ - Type: genesis.TAccount, - Balance: big.Mul(big.Div(big.NewInt(int64(build.FilBase)), big.NewInt(100)), big.NewInt(int64(build.FilecoinPrecision))), - Meta: (&genesis.AccountMeta{Owner: rkhKey.Address}).ActorMeta(), - } - keys = append(keys, rkhKey) - - templ := &genesis.Template{ - NetworkVersion: network.Version0, - Accounts: genaccs, - Miners: genms, - NetworkName: "test", - Timestamp: uint64(time.Now().Unix() - 10000), // some time sufficiently far in the past - VerifregRootKey: vrk, - RemainderAccount: gen.DefaultRemainderAccountActor, - } - - // END PRESEAL SECTION - - for i := 0; i < len(fullOpts); i++ { - var genesis node.Option - if i == 0 { - genesis = node.Override(new(modules.Genesis), testing2.MakeGenesisMem(&genbuf, *templ)) - } else { - genesis = node.Override(new(modules.Genesis), modules.LoadGenesis(genbuf.Bytes())) - } - - stop, err := node.New(ctx, - node.FullAPI(&fulls[i].FullNode, node.Lite(fullOpts[i].Lite)), - node.Online(), - node.Repo(repo.NewMemory(nil)), - node.MockHost(mn), - node.Test(), - - genesis, - - fullOpts[i].Opts(fulls), - ) - - if err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { _ = stop(context.Background()) }) - - if rpc { - fulls[i] = fullRpc(t, fulls[i]) - } - - fulls[i].Stb = storageBuilder(fulls[i], mn, node.Options()) - } - - if _, err := fulls[0].FullNode.WalletImport(ctx, &rkhKey.KeyInfo); err != nil { - t.Fatal(err) - } - - for i, def := range storage { - // TODO: support non-bootstrap miners - if i != 0 { - t.Fatal("only one storage node supported") - } - if def.Full != 0 { - t.Fatal("storage nodes only supported on the first full node") - } - - f := fulls[def.Full] - if _, err := f.FullNode.WalletImport(ctx, &keys[i].KeyInfo); err != nil { - t.Fatal(err) - } - if err := f.FullNode.WalletSetDefault(ctx, keys[i].Address); err != nil { - t.Fatal(err) - } - - genMiner := maddrs[i] - wa := genms[i].Worker - - opts := def.Opts - if opts == nil { - opts = node.Options() - } - miners[i] = CreateTestStorageNode(ctx, t, wa, genMiner, pk, f, mn, opts) - if err := miners[i].StorageAddLocal(ctx, presealDirs[i]); err != nil { - t.Fatalf("%+v", err) - } - /* - sma := miners[i].StorageMiner.(*impl.StorageMinerAPI) - - psd := presealDirs[i] - */ - if rpc { - miners[i] = storerRpc(t, miners[i]) - } - } - - if err := mn.LinkAll(); err != nil { - t.Fatal(err) - } - - if len(miners) > 0 { - // Mine 2 blocks to setup some CE stuff in some actors - var wait sync.Mutex - wait.Lock() - - bm := NewBlockMiner(t, miners[0]) - t.Cleanup(bm.Stop) - - bm.MineUntilBlock(ctx, fulls[0], func(epoch abi.ChainEpoch) { - wait.Unlock() - }) - - wait.Lock() - bm.MineUntilBlock(ctx, fulls[0], func(epoch abi.ChainEpoch) { - wait.Unlock() - }) - wait.Lock() - } - - return fulls, miners -} - -func mockMinerBuilderOpts(t *testing.T, fullOpts []FullNodeOpts, storage []StorageMiner, rpc bool) ([]TestFullNode, []TestMiner) { - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - mn := mocknet.New(ctx) - - fulls := make([]TestFullNode, len(fullOpts)) - miners := make([]TestMiner, len(storage)) - - var genbuf bytes.Buffer - - // PRESEAL SECTION, TRY TO REPLACE WITH BETTER IN THE FUTURE - // TODO: would be great if there was a better way to fake the preseals - - var ( - genms []genesis.Miner - genaccs []genesis.Actor - maddrs []address.Address - keys []*wallet.Key - pidKeys []crypto.PrivKey - ) - for i := 0; i < len(storage); i++ { - maddr, err := address.NewIDAddress(genesis2.MinerStart + uint64(i)) - if err != nil { - t.Fatal(err) - } - - preseals := storage[i].Preseal - if preseals == PresealGenesis { - preseals = GenesisPreseals - } - - genm, k, err := mockstorage.PreSeal(abi.RegisteredSealProof_StackedDrg2KiBV1, maddr, preseals) - if err != nil { - t.Fatal(err) - } - - pk, _, err := crypto.GenerateEd25519Key(rand.Reader) - require.NoError(t, err) - - minerPid, err := peer.IDFromPrivateKey(pk) - require.NoError(t, err) - - genm.PeerId = minerPid - - wk, err := wallet.NewKey(*k) - if err != nil { - return nil, nil - } - - genaccs = append(genaccs, genesis.Actor{ - Type: genesis.TAccount, - Balance: big.Mul(big.NewInt(400000000), types.NewInt(build.FilecoinPrecision)), - Meta: (&genesis.AccountMeta{Owner: wk.Address}).ActorMeta(), - }) - - keys = append(keys, wk) - pidKeys = append(pidKeys, pk) - maddrs = append(maddrs, maddr) - genms = append(genms, *genm) - } - - rkhKey, err := wallet.GenerateKey(types.KTSecp256k1) - if err != nil { - return nil, nil - } - - vrk := genesis.Actor{ - Type: genesis.TAccount, - Balance: big.Mul(big.Div(big.NewInt(int64(build.FilBase)), big.NewInt(100)), big.NewInt(int64(build.FilecoinPrecision))), - Meta: (&genesis.AccountMeta{Owner: rkhKey.Address}).ActorMeta(), - } - keys = append(keys, rkhKey) - - templ := &genesis.Template{ - NetworkVersion: network.Version0, - Accounts: genaccs, - Miners: genms, - NetworkName: "test", - Timestamp: uint64(time.Now().Unix()) - (build.BlockDelaySecs * 20000), - VerifregRootKey: vrk, - RemainderAccount: gen.DefaultRemainderAccountActor, - } - - // END PRESEAL SECTION - - for i := 0; i < len(fullOpts); i++ { - var genesis node.Option - if i == 0 { - genesis = node.Override(new(modules.Genesis), testing2.MakeGenesisMem(&genbuf, *templ)) - } else { - genesis = node.Override(new(modules.Genesis), modules.LoadGenesis(genbuf.Bytes())) - } - - stop, err := node.New(ctx, - node.FullAPI(&fulls[i].FullNode, node.Lite(fullOpts[i].Lite)), - node.Online(), - node.Repo(repo.NewMemory(nil)), - node.MockHost(mn), - node.Test(), - - node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), - node.Override(new(ffiwrapper.Prover), mock.MockProver), - - // so that we subscribe to pubsub topics immediately - node.Override(new(dtypes.Bootstrapper), dtypes.Bootstrapper(true)), - - genesis, - - fullOpts[i].Opts(fulls), - ) - if err != nil { - t.Fatalf("%+v", err) - } - - t.Cleanup(func() { _ = stop(context.Background()) }) - - if rpc { - fulls[i] = fullRpc(t, fulls[i]) - } - - fulls[i].Stb = storageBuilder(fulls[i], mn, node.Options( - node.Override(new(*mock.SectorMgr), func() (*mock.SectorMgr, error) { - return mock.NewMockSectorMgr(nil), nil - }), - - node.Override(new(sectorstorage.SectorManager), node.From(new(*mock.SectorMgr))), - node.Override(new(sectorstorage.Unsealer), node.From(new(*mock.SectorMgr))), - node.Override(new(sectorstorage.PieceProvider), node.From(new(*mock.SectorMgr))), - - node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), - node.Override(new(ffiwrapper.Prover), mock.MockProver), - node.Unset(new(*sectorstorage.Manager)), - )) - } - - if _, err := fulls[0].FullNode.WalletImport(ctx, &rkhKey.KeyInfo); err != nil { - t.Fatal(err) - } - - for i, def := range storage { - // TODO: support non-bootstrap miners - - minerID := abi.ActorID(genesis2.MinerStart + uint64(i)) - - if def.Full != 0 { - t.Fatal("storage nodes only supported on the first full node") - } - - f := fulls[def.Full] - if _, err := f.FullNode.WalletImport(ctx, &keys[i].KeyInfo); err != nil { - return nil, nil - } - if err := f.FullNode.WalletSetDefault(ctx, keys[i].Address); err != nil { - return nil, nil - } - - sectors := make([]abi.SectorID, len(genms[i].Sectors)) - for i, sector := range genms[i].Sectors { - sectors[i] = abi.SectorID{ - Miner: minerID, - Number: sector.SectorID, - } - } - - opts := def.Opts - if opts == nil { - opts = node.Options() - } - miners[i] = CreateTestStorageNode(ctx, t, genms[i].Worker, maddrs[i], pidKeys[i], f, mn, node.Options( - node.Override(new(*mock.SectorMgr), func() (*mock.SectorMgr, error) { - return mock.NewMockSectorMgr(sectors), nil - }), - - node.Override(new(sectorstorage.SectorManager), node.From(new(*mock.SectorMgr))), - node.Override(new(sectorstorage.Unsealer), node.From(new(*mock.SectorMgr))), - node.Override(new(sectorstorage.PieceProvider), node.From(new(*mock.SectorMgr))), - - node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), - node.Override(new(ffiwrapper.Prover), mock.MockProver), - node.Unset(new(*sectorstorage.Manager)), - opts, - )) - - if rpc { - miners[i] = storerRpc(t, miners[i]) - } - } - - if err := mn.LinkAll(); err != nil { - t.Fatal(err) - } - - bm := NewBlockMiner(t, miners[0]) - - if len(miners) > 0 { - // Mine 2 blocks to setup some CE stuff in some actors - var wait sync.Mutex - wait.Lock() - - bm.MineUntilBlock(ctx, fulls[0], func(abi.ChainEpoch) { - wait.Unlock() - }) - wait.Lock() - bm.MineUntilBlock(ctx, fulls[0], func(abi.ChainEpoch) { - wait.Unlock() - }) - wait.Lock() - } - - return fulls, miners -} - -func CreateRPCServer(t *testing.T, handler http.Handler) (*httptest.Server, multiaddr.Multiaddr) { - testServ := httptest.NewServer(handler) - t.Cleanup(testServ.Close) - t.Cleanup(testServ.CloseClientConnections) - - addr := testServ.Listener.Addr() - maddr, err := manet.FromNetAddr(addr) - require.NoError(t, err) - return testServ, maddr -} - -func fullRpc(t *testing.T, nd TestFullNode) TestFullNode { - handler, err := node.FullNodeHandler(nd.FullNode, false) - require.NoError(t, err) - - srv, maddr := CreateRPCServer(t, handler) - - var ret TestFullNode - cl, stop, err := client.NewFullNodeRPCV1(context.Background(), "ws://"+srv.Listener.Addr().String()+"/rpc/v1", nil) - require.NoError(t, err) - t.Cleanup(stop) - ret.ListenAddr, ret.FullNode = maddr, cl - - return ret -} - -func storerRpc(t *testing.T, nd TestMiner) TestMiner { - handler, err := node.MinerHandler(nd.StorageMiner, false) - require.NoError(t, err) - - srv, maddr := CreateRPCServer(t, handler) - - var ret TestMiner - cl, stop, err := client.NewStorageMinerRPCV0(context.Background(), "ws://"+srv.Listener.Addr().String()+"/rpc/v0", nil) - require.NoError(t, err) - t.Cleanup(stop) - - ret.ListenAddr, ret.StorageMiner, ret.MineOne = maddr, cl, nd.MineOne - return ret -} diff --git a/itests/kit/nodes.go b/itests/kit/nodes.go deleted file mode 100644 index d9b04166a..000000000 --- a/itests/kit/nodes.go +++ /dev/null @@ -1,153 +0,0 @@ -package kit - -import ( - "context" - "testing" - - "github.com/multiformats/go-multiaddr" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - "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/chain/stmgr" - "github.com/filecoin-project/lotus/miner" - "github.com/filecoin-project/lotus/node" -) - -type MinerBuilder func(context.Context, *testing.T, abi.RegisteredSealProof, address.Address) TestMiner - -type TestFullNode struct { - v1api.FullNode - // ListenAddr is the address on which an API server is listening, if an - // API server is created for this Node - ListenAddr multiaddr.Multiaddr - - Stb MinerBuilder -} - -type TestMiner struct { - lapi.StorageMiner - // ListenAddr is the address on which an API server is listening, if an - // API server is created for this Node - ListenAddr multiaddr.Multiaddr - - MineOne func(context.Context, miner.MineReq) error - Stop func(context.Context) error -} - -var PresealGenesis = -1 - -const GenesisPreseals = 2 - -const TestSpt = abi.RegisteredSealProof_StackedDrg2KiBV1_1 - -// Options for setting up a mock storage Miner -type StorageMiner struct { - Full int - Opts node.Option - Preseal int -} - -type OptionGenerator func([]TestFullNode) node.Option - -// Options for setting up a mock full node -type FullNodeOpts struct { - Lite bool // run node in "lite" mode - Opts OptionGenerator // generate dependency injection options -} - -// APIBuilder is a function which is invoked in test suite to provide -// test nodes and networks -// -// fullOpts array defines options for each full node -// storage array defines storage nodes, numbers in the array specify full node -// index the storage node 'belongs' to -type APIBuilder func(t *testing.T, full []FullNodeOpts, storage []StorageMiner) ([]TestFullNode, []TestMiner) - -func DefaultFullOpts(nFull int) []FullNodeOpts { - full := make([]FullNodeOpts, nFull) - for i := range full { - full[i] = FullNodeOpts{ - Opts: func(nodes []TestFullNode) node.Option { - return node.Options() - }, - } - } - return full -} - -var OneMiner = []StorageMiner{{Full: 0, Preseal: PresealGenesis}} -var OneFull = DefaultFullOpts(1) -var TwoFull = DefaultFullOpts(2) - -var FullNodeWithLatestActorsAt = func(upgradeHeight abi.ChainEpoch) FullNodeOpts { - // Attention: Update this when introducing new actor versions or your tests will be sad - return FullNodeWithNetworkUpgradeAt(network.Version13, upgradeHeight) -} - -var FullNodeWithNetworkUpgradeAt = func(version network.Version, upgradeHeight abi.ChainEpoch) FullNodeOpts { - fullSchedule := stmgr.UpgradeSchedule{{ - // prepare for upgrade. - Network: network.Version9, - Height: 1, - Migration: stmgr.UpgradeActorsV2, - }, { - Network: network.Version10, - Height: 2, - Migration: stmgr.UpgradeActorsV3, - }, { - Network: network.Version12, - Height: 3, - Migration: stmgr.UpgradeActorsV4, - }, { - Network: network.Version13, - Height: 4, - Migration: stmgr.UpgradeActorsV5, - }} - - schedule := stmgr.UpgradeSchedule{} - for _, upgrade := range fullSchedule { - if upgrade.Network > version { - break - } - - schedule = append(schedule, upgrade) - } - - if upgradeHeight > 0 { - schedule[len(schedule)-1].Height = upgradeHeight - } - - return FullNodeOpts{ - Opts: func(nodes []TestFullNode) node.Option { - return node.Override(new(stmgr.UpgradeSchedule), schedule) - }, - } -} - -var FullNodeWithSDRAt = func(calico, persian abi.ChainEpoch) FullNodeOpts { - return FullNodeOpts{ - Opts: func(nodes []TestFullNode) node.Option { - return node.Override(new(stmgr.UpgradeSchedule), stmgr.UpgradeSchedule{{ - Network: network.Version6, - Height: 1, - Migration: stmgr.UpgradeActorsV2, - }, { - Network: network.Version7, - Height: calico, - Migration: stmgr.UpgradeCalico, - }, { - Network: network.Version8, - Height: persian, - }}) - }, - } -} - -var MineNext = miner.MineReq{ - InjectNulls: 0, - Done: func(bool, abi.ChainEpoch, error) {}, -} diff --git a/itests/kit/pledge.go b/itests/kit/pledge.go deleted file mode 100644 index 254f87bac..000000000 --- a/itests/kit/pledge.go +++ /dev/null @@ -1,88 +0,0 @@ -package kit - -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 TestMiner, n, existing int, blockNotif <-chan struct{}) { //nolint:golint - toCheck := StartPledge(t, ctx, miner, n, existing, blockNotif) - - for len(toCheck) > 0 { - flushSealingBatches(t, ctx, miner) - - states := map[api.SectorState]int{} - for n := range toCheck { - st, err := miner.SectorsStatus(ctx, n, false) - require.NoError(t, err) - states[st.State]++ - 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 %+v\n", len(toCheck), states) - } -} - -func flushSealingBatches(t *testing.T, ctx context.Context, miner TestMiner) { //nolint:golint - pcb, err := miner.SectorPreCommitFlush(ctx) - require.NoError(t, err) - if pcb != nil { - fmt.Printf("PRECOMMIT BATCH: %+v\n", pcb) - } - - cb, err := miner.SectorCommitFlush(ctx) - require.NoError(t, err) - if cb != nil { - fmt.Printf("COMMIT BATCH: %+v\n", cb) - } -} - -func StartPledge(t *testing.T, ctx context.Context, miner TestMiner, n, existing int, blockNotif <-chan struct{}) map[abi.SectorNumber]struct{} { //nolint:golint - 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{}{} - } - - return toCheck -} From 8a418bf9828f8f112396ca88d130f1e5be4e198b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Fri, 18 Jun 2021 19:45:29 +0100 Subject: [PATCH 087/257] rename {kit2=>kit}. --- itests/api_test.go | 22 +++++----- itests/batch_deal_test.go | 12 +++--- itests/ccupgrade_test.go | 17 ++++---- itests/cli_test.go | 8 ++-- itests/deadlines_test.go | 30 +++++++------- itests/deals_test.go | 52 ++++++++++++------------ itests/gateway_test.go | 40 +++++++++--------- itests/{kit2 => kit}/blockminer.go | 2 +- itests/{kit2 => kit}/client.go | 2 +- itests/{kit2 => kit}/deals.go | 2 +- itests/{kit2 => kit}/deals_state.go | 2 +- itests/{kit2 => kit}/ensemble.go | 2 +- itests/{kit2 => kit}/ensemble_opts.go | 2 +- itests/{kit2 => kit}/ensemble_presets.go | 2 +- itests/{kit2 => kit}/files.go | 2 +- itests/{kit2 => kit}/funds.go | 2 +- itests/{kit2 => kit}/init.go | 2 +- itests/{kit2 => kit}/log.go | 2 +- itests/{kit2 => kit}/mockcli.go | 2 +- itests/{kit2 => kit}/node_full.go | 2 +- itests/{kit2 => kit}/node_miner.go | 2 +- itests/{kit2 => kit}/node_opts.go | 2 +- itests/{kit2 => kit}/node_opts_nv.go | 2 +- itests/{kit2 => kit}/rpc.go | 2 +- itests/multisig_test.go | 12 +++--- itests/paych_api_test.go | 18 ++++---- itests/paych_cli_test.go | 48 +++++++++++----------- itests/sdr_upgrade_test.go | 8 ++-- itests/sector_pledge_test.go | 18 ++++---- itests/sector_terminate_test.go | 12 +++--- itests/tape_test.go | 8 ++-- itests/verifreg_test.go | 10 ++--- itests/wdpost_dispute_test.go | 28 ++++++------- itests/wdpost_test.go | 34 ++++++++-------- 34 files changed, 205 insertions(+), 206 deletions(-) rename itests/{kit2 => kit}/blockminer.go (99%) rename itests/{kit2 => kit}/client.go (99%) rename itests/{kit2 => kit}/deals.go (99%) rename itests/{kit2 => kit}/deals_state.go (98%) rename itests/{kit2 => kit}/ensemble.go (99%) rename itests/{kit2 => kit}/ensemble_opts.go (99%) rename itests/{kit2 => kit}/ensemble_presets.go (99%) rename itests/{kit2 => kit}/files.go (98%) rename itests/{kit2 => kit}/funds.go (98%) rename itests/{kit2 => kit}/init.go (98%) rename itests/{kit2 => kit}/log.go (97%) rename itests/{kit2 => kit}/mockcli.go (99%) rename itests/{kit2 => kit}/node_full.go (99%) rename itests/{kit2 => kit}/node_miner.go (99%) rename itests/{kit2 => kit}/node_opts.go (99%) rename itests/{kit2 => kit}/node_opts_nv.go (99%) rename itests/{kit2 => kit}/rpc.go (99%) diff --git a/itests/api_test.go b/itests/api_test.go index f8567bd2a..45c137a8d 100644 --- a/itests/api_test.go +++ b/itests/api_test.go @@ -12,7 +12,7 @@ import ( 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/itests/kit2" + "github.com/filecoin-project/lotus/itests/kit" "github.com/stretchr/testify/require" ) @@ -21,7 +21,7 @@ func TestAPI(t *testing.T) { runAPITest(t) }) t.Run("rpc", func(t *testing.T) { - runAPITest(t, kit2.ThroughRPC()) + runAPITest(t, kit.ThroughRPC()) }) } @@ -48,7 +48,7 @@ func (ts *apiSuite) testVersion(t *testing.T) { lapi.RunningNodeType = lapi.NodeUnknown }) - full, _, _ := kit2.EnsembleMinimal(t, ts.opts...) + full, _, _ := kit.EnsembleMinimal(t, ts.opts...) v, err := full.Version(context.Background()) require.NoError(t, err) @@ -61,7 +61,7 @@ func (ts *apiSuite) testVersion(t *testing.T) { func (ts *apiSuite) testID(t *testing.T) { ctx := context.Background() - full, _, _ := kit2.EnsembleMinimal(t, ts.opts...) + full, _, _ := kit.EnsembleMinimal(t, ts.opts...) id, err := full.ID(ctx) if err != nil { @@ -73,7 +73,7 @@ func (ts *apiSuite) testID(t *testing.T) { func (ts *apiSuite) testConnectTwo(t *testing.T) { ctx := context.Background() - one, two, _, ens := kit2.EnsembleTwoOne(t, ts.opts...) + one, two, _, ens := kit.EnsembleTwoOne(t, ts.opts...) p, err := one.NetPeers(ctx) require.NoError(t, err) @@ -97,7 +97,7 @@ func (ts *apiSuite) testConnectTwo(t *testing.T) { func (ts *apiSuite) testSearchMsg(t *testing.T) { ctx := context.Background() - full, _, ens := kit2.EnsembleMinimal(t, ts.opts...) + full, _, ens := kit.EnsembleMinimal(t, ts.opts...) senderAddr, err := full.WalletDefaultAddress(ctx) require.NoError(t, err) @@ -127,7 +127,7 @@ func (ts *apiSuite) testSearchMsg(t *testing.T) { func (ts *apiSuite) testMining(t *testing.T) { ctx := context.Background() - full, miner, _ := kit2.EnsembleMinimal(t, ts.opts...) + full, miner, _ := kit.EnsembleMinimal(t, ts.opts...) newHeads, err := full.ChainNotify(ctx) require.NoError(t, err) @@ -138,7 +138,7 @@ func (ts *apiSuite) testMining(t *testing.T) { require.NoError(t, err) require.Equal(t, int64(h1.Height()), int64(baseHeight)) - bm := kit2.NewBlockMiner(t, miner) + bm := kit.NewBlockMiner(t, miner) bm.MineUntilBlock(ctx, full, nil) require.NoError(t, err) @@ -170,7 +170,7 @@ func (ts *apiSuite) testMiningReal(t *testing.T) { func (ts *apiSuite) testNonGenesisMiner(t *testing.T) { ctx := context.Background() - full, genesisMiner, ens := kit2.EnsembleMinimal(t, ts.opts...) + full, genesisMiner, ens := kit.EnsembleMinimal(t, ts.opts...) ens.BeginMining(4 * time.Millisecond) @@ -180,8 +180,8 @@ func (ts *apiSuite) testNonGenesisMiner(t *testing.T) { _, err = full.StateMinerInfo(ctx, gaa, types.EmptyTSK) require.NoError(t, err) - var newMiner kit2.TestMiner - ens.Miner(&newMiner, full, kit2.OwnerAddr(full.DefaultKey)).Start() + var newMiner kit.TestMiner + ens.Miner(&newMiner, full, kit.OwnerAddr(full.DefaultKey)).Start() ta, err := newMiner.ActorAddress(ctx) require.NoError(t, err) diff --git a/itests/batch_deal_test.go b/itests/batch_deal_test.go index 9cc4d7ac1..300a44fa2 100644 --- a/itests/batch_deal_test.go +++ b/itests/batch_deal_test.go @@ -10,7 +10,7 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/extern/storage-sealing/sealiface" - "github.com/filecoin-project/lotus/itests/kit2" + "github.com/filecoin-project/lotus/itests/kit" "github.com/filecoin-project/lotus/markets/storageadapter" "github.com/filecoin-project/lotus/node" "github.com/filecoin-project/lotus/node/modules/dtypes" @@ -18,7 +18,7 @@ import ( ) func TestBatchDealInput(t *testing.T) { - kit2.QuietMiningLogs() + kit.QuietMiningLogs() var ( blockTime = 10 * time.Millisecond @@ -37,7 +37,7 @@ func TestBatchDealInput(t *testing.T) { maxDealsPerMsg := uint64(deals) // Set max deals per publish deals message to maxDealsPerMsg - opts := kit2.ConstructorOpts(node.Options( + opts := kit.ConstructorOpts(node.Options( node.Override( new(*storageadapter.DealPublisher), storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{ @@ -56,9 +56,9 @@ func TestBatchDealInput(t *testing.T) { }, nil }), )) - client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), opts) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) ens.InterconnectAll().BeginMining(blockTime) - dh := kit2.NewDealHarness(t, client, miner) + dh := kit.NewDealHarness(t, client, miner) err := miner.MarketSetAsk(ctx, big.Zero(), big.Zero(), 200, 128, 32<<30) require.NoError(t, err) @@ -87,7 +87,7 @@ func TestBatchDealInput(t *testing.T) { // Starts a deal and waits until it's published runDealTillSeal := func(rseed int) { - res, _, _, err := kit2.CreateImportFile(ctx, client, rseed, piece) + res, _, _, err := kit.CreateImportFile(ctx, client, rseed, piece) require.NoError(t, err) deal := dh.StartDeal(ctx, res.Root, false, dealStartEpoch) diff --git a/itests/ccupgrade_test.go b/itests/ccupgrade_test.go index 2c35b425d..e9961dc3a 100644 --- a/itests/ccupgrade_test.go +++ b/itests/ccupgrade_test.go @@ -6,16 +6,15 @@ import ( "testing" "time" - "github.com/filecoin-project/lotus/itests/kit2" - "github.com/stretchr/testify/require" - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/itests/kit" + + "github.com/stretchr/testify/require" ) func TestCCUpgrade(t *testing.T) { - kit2.QuietMiningLogs() + kit.QuietMiningLogs() for _, height := range []abi.ChainEpoch{ -1, // before @@ -34,8 +33,8 @@ func runTestCCUpgrade(t *testing.T, upgradeHeight abi.ChainEpoch) { ctx := context.Background() blockTime := 5 * time.Millisecond - opts := kit2.ConstructorOpts(kit2.LatestActorsAt(upgradeHeight)) - client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), opts) + opts := kit.ConstructorOpts(kit.LatestActorsAt(upgradeHeight)) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) ens.InterconnectAll().BeginMining(blockTime) maddr, err := miner.ActorAddress(ctx) @@ -43,7 +42,7 @@ func runTestCCUpgrade(t *testing.T, upgradeHeight abi.ChainEpoch) { t.Fatal(err) } - CC := abi.SectorNumber(kit2.DefaultPresealsPerBootstrapMiner + 1) + CC := abi.SectorNumber(kit.DefaultPresealsPerBootstrapMiner + 1) Upgraded := CC + 1 miner.PledgeSectors(ctx, 1, 0, nil) @@ -62,7 +61,7 @@ func runTestCCUpgrade(t *testing.T, upgradeHeight abi.ChainEpoch) { err = miner.SectorMarkForUpgrade(ctx, sl[0]) require.NoError(t, err) - dh := kit2.NewDealHarness(t, client, miner) + dh := kit.NewDealHarness(t, client, miner) dh.MakeOnlineDeal(context.Background(), 6, false, 0) diff --git a/itests/cli_test.go b/itests/cli_test.go index 5c9cf0f69..0bd1ec3b4 100644 --- a/itests/cli_test.go +++ b/itests/cli_test.go @@ -6,16 +6,16 @@ import ( "time" "github.com/filecoin-project/lotus/cli" - "github.com/filecoin-project/lotus/itests/kit2" + "github.com/filecoin-project/lotus/itests/kit" ) // TestClient does a basic test to exercise the client CLI commands. func TestClient(t *testing.T) { _ = os.Setenv("BELLMAN_NO_GPU", "1") - kit2.QuietMiningLogs() + kit.QuietMiningLogs() blockTime := 5 * time.Millisecond - client, _, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), kit2.ThroughRPC()) + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) ens.InterconnectAll().BeginMining(blockTime) - kit2.RunClientTest(t, cli.Commands, client) + kit.RunClientTest(t, cli.Commands, client) } diff --git a/itests/deadlines_test.go b/itests/deadlines_test.go index 3c8303ac0..00c737b72 100644 --- a/itests/deadlines_test.go +++ b/itests/deadlines_test.go @@ -21,7 +21,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/extern/sector-storage/mock" - "github.com/filecoin-project/lotus/itests/kit2" + "github.com/filecoin-project/lotus/itests/kit" "github.com/filecoin-project/lotus/node/impl" miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" "github.com/ipfs/go-cid" @@ -57,7 +57,7 @@ func TestDeadlineToggling(t *testing.T) { t.Skip("this takes a few minutes, set LOTUS_TEST_DEADLINE_TOGGLING=1 to run") } - kit2.QuietMiningLogs() + kit.QuietMiningLogs() const sectorsC, sectorsD, sectorsB = 10, 9, 8 @@ -71,22 +71,22 @@ func TestDeadlineToggling(t *testing.T) { defer cancel() var ( - client kit2.TestFullNode - minerA kit2.TestMiner - minerB kit2.TestMiner - minerC kit2.TestMiner - minerD kit2.TestMiner - minerE kit2.TestMiner + client kit.TestFullNode + minerA kit.TestMiner + minerB kit.TestMiner + minerC kit.TestMiner + minerD kit.TestMiner + minerE kit.TestMiner ) - opts := []kit2.NodeOpt{kit2.ConstructorOpts(kit2.NetworkUpgradeAt(network.Version12, upgradeH))} - ens := kit2.NewEnsemble(t, kit2.MockProofs()). + opts := []kit.NodeOpt{kit.ConstructorOpts(kit.NetworkUpgradeAt(network.Version12, upgradeH))} + ens := kit.NewEnsemble(t, kit.MockProofs()). FullNode(&client, opts...). Miner(&minerA, &client, opts...). Start(). InterconnectAll() ens.BeginMining(blocktime) - opts = append(opts, kit2.OwnerAddr(client.DefaultKey)) + opts = append(opts, kit.OwnerAddr(client.DefaultKey)) ens.Miner(&minerB, &client, opts...). Miner(&minerC, &client, opts...). Start() @@ -204,7 +204,7 @@ func TestDeadlineToggling(t *testing.T) { require.NoError(t, err) // first round of miner checks - checkMiner(maddrA, types.NewInt(uint64(ssz)*kit2.DefaultPresealsPerBootstrapMiner), true, true, types.EmptyTSK) + checkMiner(maddrA, types.NewInt(uint64(ssz)*kit.DefaultPresealsPerBootstrapMiner), true, true, types.EmptyTSK) checkMiner(maddrC, types.NewInt(uint64(ssz)*sectorsC), true, true, types.EmptyTSK) checkMiner(maddrB, types.NewInt(0), false, false, types.EmptyTSK) @@ -231,7 +231,7 @@ func TestDeadlineToggling(t *testing.T) { params := &miner.SectorPreCommitInfo{ Expiration: 2880 * 300, SectorNumber: 22, - SealProof: kit2.TestSpt, + SealProof: kit.TestSpt, SealedCID: cr, SealRandEpoch: head.Height() - 200, @@ -281,7 +281,7 @@ func TestDeadlineToggling(t *testing.T) { } // second round of miner checks - checkMiner(maddrA, types.NewInt(uint64(ssz)*kit2.DefaultPresealsPerBootstrapMiner), true, true, types.EmptyTSK) + checkMiner(maddrA, types.NewInt(uint64(ssz)*kit.DefaultPresealsPerBootstrapMiner), true, true, types.EmptyTSK) checkMiner(maddrC, types.NewInt(0), true, true, types.EmptyTSK) checkMiner(maddrB, types.NewInt(uint64(ssz)*sectorsB), true, true, types.EmptyTSK) checkMiner(maddrD, types.NewInt(uint64(ssz)*sectorsD), true, true, types.EmptyTSK) @@ -352,7 +352,7 @@ func TestDeadlineToggling(t *testing.T) { build.Clock.Sleep(blocktime) } - checkMiner(maddrA, types.NewInt(uint64(ssz)*kit2.DefaultPresealsPerBootstrapMiner), true, true, types.EmptyTSK) + checkMiner(maddrA, types.NewInt(uint64(ssz)*kit.DefaultPresealsPerBootstrapMiner), true, true, types.EmptyTSK) checkMiner(maddrC, types.NewInt(0), true, true, types.EmptyTSK) checkMiner(maddrB, types.NewInt(0), true, true, types.EmptyTSK) checkMiner(maddrD, types.NewInt(0), false, false, types.EmptyTSK) diff --git a/itests/deals_test.go b/itests/deals_test.go index af0ef68c4..3903968d4 100644 --- a/itests/deals_test.go +++ b/itests/deals_test.go @@ -14,7 +14,7 @@ import ( "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/builtin/market" "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/itests/kit2" + "github.com/filecoin-project/lotus/itests/kit" "github.com/filecoin-project/lotus/markets/storageadapter" "github.com/filecoin-project/lotus/node" market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" @@ -27,7 +27,7 @@ func TestDealCyclesConcurrent(t *testing.T) { t.Skip("skipping test in short mode") } - kit2.QuietMiningLogs() + kit.QuietMiningLogs() blockTime := 10 * time.Millisecond @@ -37,9 +37,9 @@ func TestDealCyclesConcurrent(t *testing.T) { startEpoch := abi.ChainEpoch(2 << 12) runTest := func(t *testing.T, n int, fastRetrieval bool, carExport bool) { - client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs()) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) ens.InterconnectAll().BeginMining(blockTime) - dh := kit2.NewDealHarness(t, client, miner) + dh := kit.NewDealHarness(t, client, miner) runConcurrentDeals(t, dh, fullDealCyclesOpts{ n: n, @@ -67,7 +67,7 @@ type fullDealCyclesOpts struct { startEpoch abi.ChainEpoch } -func runConcurrentDeals(t *testing.T, dh *kit2.DealHarness, opts fullDealCyclesOpts) { +func runConcurrentDeals(t *testing.T, dh *kit.DealHarness, opts fullDealCyclesOpts) { errgrp, _ := errgroup.WithContext(context.Background()) for i := 0; i < opts.n; i++ { i := i @@ -81,7 +81,7 @@ func runConcurrentDeals(t *testing.T, dh *kit2.DealHarness, opts fullDealCyclesO }() deal, res, inPath := dh.MakeOnlineDeal(context.Background(), 5+i, opts.fastRetrieval, opts.startEpoch) outPath := dh.PerformRetrieval(context.Background(), deal, res.Root, opts.carExport) - kit2.AssertFilesEqual(t, inPath, outPath) + kit.AssertFilesEqual(t, inPath, outPath) return nil }) } @@ -93,13 +93,13 @@ func TestDealsWithSealingAndRPC(t *testing.T) { t.Skip("skipping test in short mode") } - kit2.QuietMiningLogs() + kit.QuietMiningLogs() var blockTime = 1 * time.Second - client, miner, ens := kit2.EnsembleMinimal(t, kit2.ThroughRPC()) // no mock proofs. + client, miner, ens := kit.EnsembleMinimal(t, kit.ThroughRPC()) // no mock proofs. ens.InterconnectAll().BeginMining(blockTime) - dh := kit2.NewDealHarness(t, client, miner) + dh := kit.NewDealHarness(t, client, miner) t.Run("stdretrieval", func(t *testing.T) { runConcurrentDeals(t, dh, fullDealCyclesOpts{n: 1}) @@ -123,7 +123,7 @@ func TestPublishDealsBatching(t *testing.T) { startEpoch = abi.ChainEpoch(2 << 12) ) - kit2.QuietMiningLogs() + kit.QuietMiningLogs() opts := node.Override(new(*storageadapter.DealPublisher), storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{ @@ -132,10 +132,10 @@ func TestPublishDealsBatching(t *testing.T) { }), ) - client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), kit2.ConstructorOpts(opts)) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ConstructorOpts(opts)) ens.InterconnectAll().BeginMining(10 * time.Millisecond) - dh := kit2.NewDealHarness(t, client, miner) + dh := kit.NewDealHarness(t, client, miner) // Starts a deal and waits until it's published runDealTillPublish := func(rseed int) { @@ -210,23 +210,23 @@ func TestFirstDealEnablesMining(t *testing.T) { t.Skip("skipping test in short mode") } - kit2.QuietMiningLogs() + kit.QuietMiningLogs() var ( - client kit2.TestFullNode - genMiner kit2.TestMiner // bootstrap - provider kit2.TestMiner // no sectors, will need to create one + client kit.TestFullNode + genMiner kit.TestMiner // bootstrap + provider kit.TestMiner // no sectors, will need to create one ) - ens := kit2.NewEnsemble(t, kit2.MockProofs()) + ens := kit.NewEnsemble(t, kit.MockProofs()) ens.FullNode(&client) ens.Miner(&genMiner, &client) - ens.Miner(&provider, &client, kit2.PresealSectors(0)) + ens.Miner(&provider, &client, kit.PresealSectors(0)) ens.Start().InterconnectAll().BeginMining(50 * time.Millisecond) ctx := context.Background() - dh := kit2.NewDealHarness(t, &client, &provider) + dh := kit.NewDealHarness(t, &client, &provider) ref, _ := client.CreateImportFile(ctx, 5, 0) @@ -241,7 +241,7 @@ func TestFirstDealEnablesMining(t *testing.T) { providerMined := make(chan struct{}) go func() { - _ = client.WaitTillChain(ctx, kit2.BlockMinedBy(provider.ActorAddr)) + _ = client.WaitTillChain(ctx, kit.BlockMinedBy(provider.ActorAddr)) close(providerMined) }() @@ -266,10 +266,10 @@ func TestOfflineDealFlow(t *testing.T) { runTest := func(t *testing.T, fastRet bool) { ctx := context.Background() - client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs()) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) ens.InterconnectAll().BeginMining(blocktime) - dh := kit2.NewDealHarness(t, client, miner) + dh := kit.NewDealHarness(t, client, miner) // Create a random file and import on the client. res, inFile := client.CreateImportFile(ctx, 1, 0) @@ -333,7 +333,7 @@ func TestOfflineDealFlow(t *testing.T) { // Retrieve the deal outFile := dh.PerformRetrieval(ctx, proposalCid, rootCid, false) - kit2.AssertFilesEqual(t, inFile, outFile) + kit.AssertFilesEqual(t, inFile, outFile) } t.Run("stdretrieval", func(t *testing.T) { runTest(t, false) }) @@ -345,14 +345,14 @@ func TestZeroPricePerByteRetrieval(t *testing.T) { t.Skip("skipping test in short mode") } - kit2.QuietMiningLogs() + kit.QuietMiningLogs() var ( blockTime = 10 * time.Millisecond startEpoch = abi.ChainEpoch(2 << 12) ) - client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs()) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) ens.InterconnectAll().BeginMining(blockTime) ctx := context.Background() @@ -364,7 +364,7 @@ func TestZeroPricePerByteRetrieval(t *testing.T) { err = miner.MarketSetRetrievalAsk(ctx, ask) require.NoError(t, err) - dh := kit2.NewDealHarness(t, client, miner) + dh := kit.NewDealHarness(t, client, miner) runConcurrentDeals(t, dh, fullDealCyclesOpts{ n: 1, startEpoch: startEpoch, diff --git a/itests/gateway_test.go b/itests/gateway_test.go index 36df41d54..dfbe5bed5 100644 --- a/itests/gateway_test.go +++ b/itests/gateway_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/filecoin-project/lotus/itests/kit" "github.com/stretchr/testify/require" "golang.org/x/xerrors" @@ -23,7 +24,6 @@ import ( "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/cli" "github.com/filecoin-project/lotus/gateway" - "github.com/filecoin-project/lotus/itests/kit2" "github.com/filecoin-project/lotus/node" init2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/init" @@ -44,7 +44,7 @@ func init() { // TestGatewayWalletMsig tests that API calls to wallet and msig can be made on a lite // node that is connected through a gateway to a full API node func TestGatewayWalletMsig(t *testing.T) { - kit2.QuietMiningLogs() + kit.QuietMiningLogs() blocktime := 5 * time.Millisecond ctx := context.Background() @@ -176,7 +176,7 @@ func TestGatewayWalletMsig(t *testing.T) { // TestGatewayMsigCLI tests that msig CLI calls can be made // on a lite node that is connected through a gateway to a full API node func TestGatewayMsigCLI(t *testing.T) { - kit2.QuietMiningLogs() + kit.QuietMiningLogs() blocktime := 5 * time.Millisecond ctx := context.Background() @@ -188,7 +188,7 @@ func TestGatewayMsigCLI(t *testing.T) { } func TestGatewayDealFlow(t *testing.T) { - kit2.QuietMiningLogs() + kit.QuietMiningLogs() blocktime := 5 * time.Millisecond ctx := context.Background() @@ -202,26 +202,26 @@ func TestGatewayDealFlow(t *testing.T) { // so that the deal starts sealing in time dealStartEpoch := abi.ChainEpoch(2 << 12) - dh := kit2.NewDealHarness(t, nodes.lite, nodes.miner) + dh := kit.NewDealHarness(t, nodes.lite, nodes.miner) dealCid, res, _ := dh.MakeOnlineDeal(ctx, 6, false, dealStartEpoch) dh.PerformRetrieval(ctx, dealCid, res.Root, false) } func TestGatewayCLIDealFlow(t *testing.T) { - kit2.QuietMiningLogs() + kit.QuietMiningLogs() blocktime := 5 * time.Millisecond ctx := context.Background() nodes := startNodesWithFunds(ctx, t, blocktime, maxLookbackCap, maxStateWaitLookbackLimit) defer nodes.closer() - kit2.RunClientTest(t, cli.Commands, nodes.lite) + kit.RunClientTest(t, cli.Commands, nodes.lite) } type testNodes struct { - lite *kit2.TestFullNode - full *kit2.TestFullNode - miner *kit2.TestMiner + lite *kit.TestFullNode + full *kit.TestFullNode + miner *kit.TestMiner closer jsonrpc.ClientCloser } @@ -259,9 +259,9 @@ func startNodes( var closer jsonrpc.ClientCloser var ( - full *kit2.TestFullNode - miner *kit2.TestMiner - lite kit2.TestFullNode + full *kit.TestFullNode + miner *kit.TestMiner + lite kit.TestFullNode ) // - Create one full node and one lite node @@ -270,8 +270,8 @@ func startNodes( // - Connect lite node -> gateway server -> full node // create the full node and the miner. - var ens *kit2.Ensemble - full, miner, ens = kit2.EnsembleMinimal(t, kit2.MockProofs()) + var ens *kit.Ensemble + full, miner, ens = kit.EnsembleMinimal(t, kit.MockProofs()) ens.InterconnectAll().BeginMining(blocktime) // Create a gateway server in front of the full node @@ -279,7 +279,7 @@ func startNodes( handler, err := gateway.Handler(gwapi) require.NoError(t, err) - srv, _ := kit2.CreateRPCServer(t, handler) + srv, _ := kit.CreateRPCServer(t, handler) // Create a gateway client API that connects to the gateway server var gapi api.Gateway @@ -287,9 +287,9 @@ func startNodes( require.NoError(t, err) ens.FullNode(&lite, - kit2.LiteNode(), - kit2.ThroughRPC(), - kit2.ConstructorOpts( + kit.LiteNode(), + kit.ThroughRPC(), + kit.ConstructorOpts( node.Override(new(api.Gateway), gapi), ), ).Start().InterconnectAll() @@ -297,7 +297,7 @@ func startNodes( return &testNodes{lite: &lite, full: full, miner: miner, closer: closer} } -func sendFunds(ctx context.Context, fromNode *kit2.TestFullNode, fromAddr address.Address, toAddr address.Address, amt types.BigInt) error { +func sendFunds(ctx context.Context, fromNode *kit.TestFullNode, fromAddr address.Address, toAddr address.Address, amt types.BigInt) error { msg := &types.Message{ From: fromAddr, To: toAddr, diff --git a/itests/kit2/blockminer.go b/itests/kit/blockminer.go similarity index 99% rename from itests/kit2/blockminer.go rename to itests/kit/blockminer.go index 04d425dd6..2c9bd47c6 100644 --- a/itests/kit2/blockminer.go +++ b/itests/kit/blockminer.go @@ -1,4 +1,4 @@ -package kit2 +package kit import ( "context" diff --git a/itests/kit2/client.go b/itests/kit/client.go similarity index 99% rename from itests/kit2/client.go rename to itests/kit/client.go index 78a7034fe..3a16f5204 100644 --- a/itests/kit2/client.go +++ b/itests/kit/client.go @@ -1,4 +1,4 @@ -package kit2 +package kit import ( "context" diff --git a/itests/kit2/deals.go b/itests/kit/deals.go similarity index 99% rename from itests/kit2/deals.go rename to itests/kit/deals.go index 2e015a9c7..98e6efee2 100644 --- a/itests/kit2/deals.go +++ b/itests/kit/deals.go @@ -1,4 +1,4 @@ -package kit2 +package kit import ( "context" diff --git a/itests/kit2/deals_state.go b/itests/kit/deals_state.go similarity index 98% rename from itests/kit2/deals_state.go rename to itests/kit/deals_state.go index be3a9e4db..617a6d28e 100644 --- a/itests/kit2/deals_state.go +++ b/itests/kit/deals_state.go @@ -1,4 +1,4 @@ -package kit2 +package kit type TestDealState int diff --git a/itests/kit2/ensemble.go b/itests/kit/ensemble.go similarity index 99% rename from itests/kit2/ensemble.go rename to itests/kit/ensemble.go index 44580920f..1accdbd61 100644 --- a/itests/kit2/ensemble.go +++ b/itests/kit/ensemble.go @@ -1,4 +1,4 @@ -package kit2 +package kit import ( "bytes" diff --git a/itests/kit2/ensemble_opts.go b/itests/kit/ensemble_opts.go similarity index 99% rename from itests/kit2/ensemble_opts.go rename to itests/kit/ensemble_opts.go index 8c6d66d9e..9233aadd8 100644 --- a/itests/kit2/ensemble_opts.go +++ b/itests/kit/ensemble_opts.go @@ -1,4 +1,4 @@ -package kit2 +package kit import ( "time" diff --git a/itests/kit2/ensemble_presets.go b/itests/kit/ensemble_presets.go similarity index 99% rename from itests/kit2/ensemble_presets.go rename to itests/kit/ensemble_presets.go index 28a4b5d92..7cae12a68 100644 --- a/itests/kit2/ensemble_presets.go +++ b/itests/kit/ensemble_presets.go @@ -1,4 +1,4 @@ -package kit2 +package kit import "testing" diff --git a/itests/kit2/files.go b/itests/kit/files.go similarity index 98% rename from itests/kit2/files.go rename to itests/kit/files.go index 1e1509858..48592b518 100644 --- a/itests/kit2/files.go +++ b/itests/kit/files.go @@ -1,4 +1,4 @@ -package kit2 +package kit import ( "bytes" diff --git a/itests/kit2/funds.go b/itests/kit/funds.go similarity index 98% rename from itests/kit2/funds.go rename to itests/kit/funds.go index b29963353..417cf9ce1 100644 --- a/itests/kit2/funds.go +++ b/itests/kit/funds.go @@ -1,4 +1,4 @@ -package kit2 +package kit import ( "context" diff --git a/itests/kit2/init.go b/itests/kit/init.go similarity index 98% rename from itests/kit2/init.go rename to itests/kit/init.go index dfc5a13f2..8df4922b8 100644 --- a/itests/kit2/init.go +++ b/itests/kit/init.go @@ -1,4 +1,4 @@ -package kit2 +package kit import ( "fmt" diff --git a/itests/kit2/log.go b/itests/kit/log.go similarity index 97% rename from itests/kit2/log.go rename to itests/kit/log.go index f255d0639..3dce3af9d 100644 --- a/itests/kit2/log.go +++ b/itests/kit/log.go @@ -1,4 +1,4 @@ -package kit2 +package kit import ( "github.com/filecoin-project/lotus/lib/lotuslog" diff --git a/itests/kit2/mockcli.go b/itests/kit/mockcli.go similarity index 99% rename from itests/kit2/mockcli.go rename to itests/kit/mockcli.go index 592c97333..c0f218920 100644 --- a/itests/kit2/mockcli.go +++ b/itests/kit/mockcli.go @@ -1,4 +1,4 @@ -package kit2 +package kit import ( "bytes" diff --git a/itests/kit2/node_full.go b/itests/kit/node_full.go similarity index 99% rename from itests/kit2/node_full.go rename to itests/kit/node_full.go index 3dadb4d8d..83586e188 100644 --- a/itests/kit2/node_full.go +++ b/itests/kit/node_full.go @@ -1,4 +1,4 @@ -package kit2 +package kit import ( "context" diff --git a/itests/kit2/node_miner.go b/itests/kit/node_miner.go similarity index 99% rename from itests/kit2/node_miner.go rename to itests/kit/node_miner.go index 1cd65e20e..d3f0d2e3c 100644 --- a/itests/kit2/node_miner.go +++ b/itests/kit/node_miner.go @@ -1,4 +1,4 @@ -package kit2 +package kit import ( "context" diff --git a/itests/kit2/node_opts.go b/itests/kit/node_opts.go similarity index 99% rename from itests/kit2/node_opts.go rename to itests/kit/node_opts.go index b2dacd3cc..c36ca3e26 100644 --- a/itests/kit2/node_opts.go +++ b/itests/kit/node_opts.go @@ -1,4 +1,4 @@ -package kit2 +package kit import ( "github.com/filecoin-project/go-state-types/abi" diff --git a/itests/kit2/node_opts_nv.go b/itests/kit/node_opts_nv.go similarity index 99% rename from itests/kit2/node_opts_nv.go rename to itests/kit/node_opts_nv.go index 5ffd94f5e..d4c84b4f1 100644 --- a/itests/kit2/node_opts_nv.go +++ b/itests/kit/node_opts_nv.go @@ -1,4 +1,4 @@ -package kit2 +package kit import ( "context" diff --git a/itests/kit2/rpc.go b/itests/kit/rpc.go similarity index 99% rename from itests/kit2/rpc.go rename to itests/kit/rpc.go index 873b64257..dab45df07 100644 --- a/itests/kit2/rpc.go +++ b/itests/kit/rpc.go @@ -1,4 +1,4 @@ -package kit2 +package kit import ( "context" diff --git a/itests/multisig_test.go b/itests/multisig_test.go index 9b1f59673..0afdf5f0a 100644 --- a/itests/multisig_test.go +++ b/itests/multisig_test.go @@ -11,25 +11,25 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/cli" - "github.com/filecoin-project/lotus/itests/kit2" + "github.com/filecoin-project/lotus/itests/kit" "github.com/stretchr/testify/require" ) // TestMultisig does a basic test to exercise the multisig CLI commands func TestMultisig(t *testing.T) { - kit2.QuietMiningLogs() + kit.QuietMiningLogs() blockTime := 5 * time.Millisecond - client, _, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), kit2.ThroughRPC()) + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) ens.InterconnectAll().BeginMining(blockTime) runMultisigTests(t, client) } -func runMultisigTests(t *testing.T, clientNode *kit2.TestFullNode) { +func runMultisigTests(t *testing.T, clientNode *kit.TestFullNode) { // Create mock CLI ctx := context.Background() - mockCLI := kit2.NewMockCLI(ctx, t, cli.Commands) + mockCLI := kit.NewMockCLI(ctx, t, cli.Commands) clientCLI := mockCLI.Client(clientNode.ListenAddr) // Create some wallets on the node to use for testing multisig @@ -40,7 +40,7 @@ func runMultisigTests(t *testing.T, clientNode *kit2.TestFullNode) { walletAddrs = append(walletAddrs, addr) - kit2.SendFunds(ctx, t, clientNode, addr, types.NewInt(1e15)) + kit.SendFunds(ctx, t, clientNode, addr, types.NewInt(1e15)) } // Create an msig with three of the addresses and threshold of two sigs diff --git a/itests/paych_api_test.go b/itests/paych_api_test.go index 86a156790..668eb14aa 100644 --- a/itests/paych_api_test.go +++ b/itests/paych_api_test.go @@ -7,6 +7,7 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/lotus/itests/kit" "github.com/ipfs/go-cid" "github.com/stretchr/testify/require" @@ -23,22 +24,21 @@ import ( "github.com/filecoin-project/lotus/chain/events" "github.com/filecoin-project/lotus/chain/events/state" "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/itests/kit2" ) func TestPaymentChannelsAPI(t *testing.T) { - kit2.QuietMiningLogs() + kit.QuietMiningLogs() ctx := context.Background() blockTime := 5 * time.Millisecond var ( - paymentCreator kit2.TestFullNode - paymentReceiver kit2.TestFullNode - miner kit2.TestMiner + paymentCreator kit.TestFullNode + paymentReceiver kit.TestFullNode + miner kit.TestMiner ) - ens := kit2.NewEnsemble(t, kit2.MockProofs()). + ens := kit.NewEnsemble(t, kit.MockProofs()). FullNode(&paymentCreator). FullNode(&paymentReceiver). Miner(&miner, &paymentCreator). @@ -51,7 +51,7 @@ func TestPaymentChannelsAPI(t *testing.T) { receiverAddr, err := paymentReceiver.WalletNew(ctx, types.KTSecp256k1) require.NoError(t, err) - kit2.SendFunds(ctx, t, &paymentCreator, receiverAddr, abi.NewTokenAmount(1e18)) + kit.SendFunds(ctx, t, &paymentCreator, receiverAddr, abi.NewTokenAmount(1e18)) // setup the payment channel createrAddr, err := paymentCreator.WalletDefaultAddress(ctx) @@ -200,7 +200,7 @@ func TestPaymentChannelsAPI(t *testing.T) { require.EqualValues(t, abi.NewTokenAmount(expectedRefund), delta, "did not send correct funds from creator: expected %d, got %d", expectedRefund, delta) } -func waitForBlocks(ctx context.Context, t *testing.T, bm *kit2.BlockMiner, paymentReceiver kit2.TestFullNode, receiverAddr address.Address, count int) { +func waitForBlocks(ctx context.Context, t *testing.T, bm *kit.BlockMiner, paymentReceiver kit.TestFullNode, receiverAddr address.Address, count int) { // We need to add null blocks in batches, if we add too many the chain can't sync batchSize := 60 for i := 0; i < count; i += batchSize { @@ -225,7 +225,7 @@ func waitForBlocks(ctx context.Context, t *testing.T, bm *kit2.BlockMiner, payme } } -func waitForMessage(ctx context.Context, t *testing.T, paymentCreator kit2.TestFullNode, msgCid cid.Cid, duration time.Duration, desc string) *api.MsgLookup { +func waitForMessage(ctx context.Context, t *testing.T, paymentCreator kit.TestFullNode, msgCid cid.Cid, duration time.Duration, desc string) *api.MsgLookup { ctx, cancel := context.WithTimeout(ctx, duration) defer cancel() diff --git a/itests/paych_cli_test.go b/itests/paych_cli_test.go index 84ccee95a..8a0690449 100644 --- a/itests/paych_cli_test.go +++ b/itests/paych_cli_test.go @@ -11,7 +11,7 @@ import ( "time" "github.com/filecoin-project/lotus/cli" - "github.com/filecoin-project/lotus/itests/kit2" + "github.com/filecoin-project/lotus/itests/kit" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" @@ -37,19 +37,19 @@ func init() { // commands func TestPaymentChannelsBasic(t *testing.T) { _ = os.Setenv("BELLMAN_NO_GPU", "1") - kit2.QuietMiningLogs() + kit.QuietMiningLogs() blocktime := 5 * time.Millisecond ctx := context.Background() var ( - paymentCreator kit2.TestFullNode - paymentReceiver kit2.TestFullNode + paymentCreator kit.TestFullNode + paymentReceiver kit.TestFullNode ) creatorAddr, receiverAddr := startPaychCreatorReceiverMiner(ctx, t, &paymentCreator, &paymentReceiver, blocktime) // Create mock CLI - mockCLI := kit2.NewMockCLI(ctx, t, cli.Commands) + mockCLI := kit.NewMockCLI(ctx, t, cli.Commands) creatorCLI := mockCLI.Client(paymentCreator.ListenAddr) receiverCLI := mockCLI.Client(paymentReceiver.ListenAddr) @@ -94,18 +94,18 @@ type voucherSpec struct { // TestPaymentChannelStatus tests the payment channel status CLI command func TestPaymentChannelStatus(t *testing.T) { _ = os.Setenv("BELLMAN_NO_GPU", "1") - kit2.QuietMiningLogs() + kit.QuietMiningLogs() blocktime := 5 * time.Millisecond ctx := context.Background() var ( - paymentCreator kit2.TestFullNode - paymentReceiver kit2.TestFullNode + paymentCreator kit.TestFullNode + paymentReceiver kit.TestFullNode ) creatorAddr, receiverAddr := startPaychCreatorReceiverMiner(ctx, t, &paymentCreator, &paymentReceiver, blocktime) // Create mock CLI - mockCLI := kit2.NewMockCLI(ctx, t, cli.Commands) + mockCLI := kit.NewMockCLI(ctx, t, cli.Commands) creatorCLI := mockCLI.Client(paymentCreator.ListenAddr) // creator: paych status-by-from-to @@ -174,18 +174,18 @@ func TestPaymentChannelStatus(t *testing.T) { // channel voucher commands func TestPaymentChannelVouchers(t *testing.T) { _ = os.Setenv("BELLMAN_NO_GPU", "1") - kit2.QuietMiningLogs() + kit.QuietMiningLogs() blocktime := 5 * time.Millisecond ctx := context.Background() var ( - paymentCreator kit2.TestFullNode - paymentReceiver kit2.TestFullNode + paymentCreator kit.TestFullNode + paymentReceiver kit.TestFullNode ) creatorAddr, receiverAddr := startPaychCreatorReceiverMiner(ctx, t, &paymentCreator, &paymentReceiver, blocktime) // Create mock CLI - mockCLI := kit2.NewMockCLI(ctx, t, cli.Commands) + mockCLI := kit.NewMockCLI(ctx, t, cli.Commands) creatorCLI := mockCLI.Client(paymentCreator.ListenAddr) receiverCLI := mockCLI.Client(paymentReceiver.ListenAddr) @@ -306,18 +306,18 @@ 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") - kit2.QuietMiningLogs() + kit.QuietMiningLogs() blocktime := 5 * time.Millisecond ctx := context.Background() var ( - paymentCreator kit2.TestFullNode - paymentReceiver kit2.TestFullNode + paymentCreator kit.TestFullNode + paymentReceiver kit.TestFullNode ) creatorAddr, receiverAddr := startPaychCreatorReceiverMiner(ctx, t, &paymentCreator, &paymentReceiver, blocktime) // Create mock CLI - mockCLI := kit2.NewMockCLI(ctx, t, cli.Commands) + mockCLI := kit.NewMockCLI(ctx, t, cli.Commands) creatorCLI := mockCLI.Client(paymentCreator.ListenAddr) // creator: paych add-funds @@ -385,7 +385,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 kit2.TestFullNode, height abi.ChainEpoch) { +func waitForHeight(ctx context.Context, t *testing.T, node kit.TestFullNode, 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 { @@ -403,7 +403,7 @@ func waitForHeight(ctx context.Context, t *testing.T, node kit2.TestFullNode, he } // getPaychState gets the state of the payment channel with the given address -func getPaychState(ctx context.Context, t *testing.T, node kit2.TestFullNode, chAddr address.Address) paych.State { +func getPaychState(ctx context.Context, t *testing.T, node kit.TestFullNode, chAddr address.Address) paych.State { act, err := node.StateGetActor(ctx, chAddr, types.EmptyTSK) require.NoError(t, err) @@ -414,10 +414,10 @@ func getPaychState(ctx context.Context, t *testing.T, node kit2.TestFullNode, ch return chState } -func startPaychCreatorReceiverMiner(ctx context.Context, t *testing.T, paymentCreator *kit2.TestFullNode, paymentReceiver *kit2.TestFullNode, blocktime time.Duration) (address.Address, address.Address) { - var miner kit2.TestMiner - opts := kit2.ThroughRPC() - kit2.NewEnsemble(t, kit2.MockProofs()). +func startPaychCreatorReceiverMiner(ctx context.Context, t *testing.T, paymentCreator *kit.TestFullNode, paymentReceiver *kit.TestFullNode, blocktime time.Duration) (address.Address, address.Address) { + var miner kit.TestMiner + opts := kit.ThroughRPC() + kit.NewEnsemble(t, kit.MockProofs()). FullNode(paymentCreator, opts). FullNode(paymentReceiver, opts). Miner(&miner, paymentCreator). @@ -428,7 +428,7 @@ func startPaychCreatorReceiverMiner(ctx context.Context, t *testing.T, paymentCr // Send some funds to the second node receiverAddr, err := paymentReceiver.WalletDefaultAddress(ctx) require.NoError(t, err) - kit2.SendFunds(ctx, t, paymentCreator, receiverAddr, abi.NewTokenAmount(1e18)) + kit.SendFunds(ctx, t, paymentCreator, receiverAddr, abi.NewTokenAmount(1e18)) // Get the first node's address creatorAddr, err := paymentCreator.WalletDefaultAddress(ctx) diff --git a/itests/sdr_upgrade_test.go b/itests/sdr_upgrade_test.go index 008c2ce61..3aa685b09 100644 --- a/itests/sdr_upgrade_test.go +++ b/itests/sdr_upgrade_test.go @@ -10,14 +10,14 @@ import ( "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/itests/kit2" + "github.com/filecoin-project/lotus/itests/kit" bminer "github.com/filecoin-project/lotus/miner" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestSDRUpgrade(t *testing.T) { - kit2.QuietMiningLogs() + kit.QuietMiningLogs() // oldDelay := policy.GetPreCommitChallengeDelay() // policy.SetPreCommitChallengeDelay(5) @@ -30,8 +30,8 @@ func TestSDRUpgrade(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - opts := kit2.ConstructorOpts(kit2.SDRUpgradeAt(500, 1000)) - client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), opts) + opts := kit.ConstructorOpts(kit.SDRUpgradeAt(500, 1000)) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) ens.InterconnectAll() build.Clock.Sleep(time.Second) diff --git a/itests/sector_pledge_test.go b/itests/sector_pledge_test.go index 8e87f2658..d911dcb68 100644 --- a/itests/sector_pledge_test.go +++ b/itests/sector_pledge_test.go @@ -7,16 +7,16 @@ import ( "testing" "time" + "github.com/filecoin-project/lotus/itests/kit" "github.com/stretchr/testify/require" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" - "github.com/filecoin-project/lotus/itests/kit2" ) func TestPledgeSectors(t *testing.T) { - kit2.QuietMiningLogs() + kit.QuietMiningLogs() blockTime := 50 * time.Millisecond @@ -24,7 +24,7 @@ func TestPledgeSectors(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - _, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs()) + _, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) ens.InterconnectAll().BeginMining(blockTime) miner.PledgeSectors(ctx, nSectors, 0, nil) @@ -54,11 +54,11 @@ func TestPledgeBatching(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - opts := kit2.ConstructorOpts(kit2.LatestActorsAt(-1)) - client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), opts) + opts := kit.ConstructorOpts(kit.LatestActorsAt(-1)) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) ens.InterconnectAll().BeginMining(blockTime) - client.WaitTillChain(ctx, kit2.HeightAtLeast(10)) + client.WaitTillChain(ctx, kit.HeightAtLeast(10)) toCheck := miner.StartPledge(ctx, nSectors, 0, nil) @@ -111,11 +111,11 @@ func TestPledgeBeforeNv13(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - opts := kit2.ConstructorOpts(kit2.LatestActorsAt(1000000000)) - client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), opts) + opts := kit.ConstructorOpts(kit.LatestActorsAt(1000000000)) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) ens.InterconnectAll().BeginMining(blocktime) - client.WaitTillChain(ctx, kit2.HeightAtLeast(10)) + client.WaitTillChain(ctx, kit.HeightAtLeast(10)) toCheck := miner.StartPledge(ctx, nSectors, 0, nil) diff --git a/itests/sector_terminate_test.go b/itests/sector_terminate_test.go index faf12228c..94d438437 100644 --- a/itests/sector_terminate_test.go +++ b/itests/sector_terminate_test.go @@ -10,7 +10,7 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/chain/types" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" - "github.com/filecoin-project/lotus/itests/kit2" + "github.com/filecoin-project/lotus/itests/kit" "github.com/stretchr/testify/require" ) @@ -19,7 +19,7 @@ func TestTerminate(t *testing.T) { t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") } - kit2.QuietMiningLogs() + kit.QuietMiningLogs() const blocktime = 2 * time.Millisecond @@ -28,8 +28,8 @@ func TestTerminate(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - opts := kit2.ConstructorOpts(kit2.LatestActorsAt(-1)) - client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), opts) + opts := kit.ConstructorOpts(kit.LatestActorsAt(-1)) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) ens.InterconnectAll().BeginMining(blocktime) maddr, err := miner.ActorAddress(ctx) @@ -57,7 +57,7 @@ func TestTerminate(t *testing.T) { waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 2 t.Logf("End for head.Height > %d", waitUntil) - ts := client.WaitTillChain(ctx, kit2.HeightAtLeast(waitUntil)) + ts := client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) t.Logf("Now head.Height = %d", ts.Height()) } @@ -140,7 +140,7 @@ loop: waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 2 t.Logf("End for head.Height > %d", waitUntil) - ts := client.WaitTillChain(ctx, kit2.HeightAtLeast(waitUntil)) + ts := client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) t.Logf("Now head.Height = %d", ts.Height()) p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) diff --git a/itests/tape_test.go b/itests/tape_test.go index 6fb3def15..08970152f 100644 --- a/itests/tape_test.go +++ b/itests/tape_test.go @@ -10,13 +10,13 @@ import ( "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/stmgr" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" - "github.com/filecoin-project/lotus/itests/kit2" + "github.com/filecoin-project/lotus/itests/kit" "github.com/filecoin-project/lotus/node" "github.com/stretchr/testify/require" ) func TestTapeFix(t *testing.T) { - kit2.QuietMiningLogs() + kit.QuietMiningLogs() var blocktime = 2 * time.Millisecond @@ -42,8 +42,8 @@ func testTapeFix(t *testing.T, blocktime time.Duration, after bool) { }) } - nopts := kit2.ConstructorOpts(node.Override(new(stmgr.UpgradeSchedule), upgradeSchedule)) - _, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), nopts) + nopts := kit.ConstructorOpts(node.Override(new(stmgr.UpgradeSchedule), upgradeSchedule)) + _, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), nopts) ens.InterconnectAll().BeginMining(blocktime) sid, err := miner.PledgeSector(ctx) diff --git a/itests/verifreg_test.go b/itests/verifreg_test.go index 108da6ecf..28a72263e 100644 --- a/itests/verifreg_test.go +++ b/itests/verifreg_test.go @@ -11,6 +11,7 @@ import ( "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/network" "github.com/filecoin-project/lotus/chain/wallet" + "github.com/filecoin-project/lotus/itests/kit" verifreg4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/verifreg" "github.com/stretchr/testify/require" @@ -18,7 +19,6 @@ import ( "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/builtin/verifreg" "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/itests/kit2" "github.com/filecoin-project/lotus/node/impl" ) @@ -39,10 +39,10 @@ func TestVerifiedClientTopUp(t *testing.T) { bal, err := types.ParseFIL("100fil") require.NoError(t, err) - node, _, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), - kit2.RootVerifier(rootKey, abi.NewTokenAmount(bal.Int64())), - kit2.Account(verifierKey, abi.NewTokenAmount(bal.Int64())), // assign some balance to the verifier so they can send an AddClient message. - kit2.ConstructorOpts(kit2.InstantaneousNetworkVersion(nv))) + node, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), + kit.RootVerifier(rootKey, abi.NewTokenAmount(bal.Int64())), + kit.Account(verifierKey, abi.NewTokenAmount(bal.Int64())), // assign some balance to the verifier so they can send an AddClient message. + kit.ConstructorOpts(kit.InstantaneousNetworkVersion(nv))) ens.InterconnectAll().BeginMining(blockTime) diff --git a/itests/wdpost_dispute_test.go b/itests/wdpost_dispute_test.go index 49b41c7e0..742972fc6 100644 --- a/itests/wdpost_dispute_test.go +++ b/itests/wdpost_dispute_test.go @@ -15,7 +15,7 @@ import ( "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/itests/kit2" + "github.com/filecoin-project/lotus/itests/kit" proof3 "github.com/filecoin-project/specs-actors/v3/actors/runtime/proof" "github.com/stretchr/testify/require" ) @@ -25,7 +25,7 @@ func TestWindowPostDispute(t *testing.T) { t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") } - kit2.QuietMiningLogs() + kit.QuietMiningLogs() blocktime := 2 * time.Millisecond @@ -33,20 +33,20 @@ func TestWindowPostDispute(t *testing.T) { defer cancel() var ( - client kit2.TestFullNode - chainMiner kit2.TestMiner - evilMiner kit2.TestMiner + client kit.TestFullNode + chainMiner kit.TestMiner + evilMiner kit.TestMiner ) // 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. - opts := kit2.ConstructorOpts(kit2.LatestActorsAt(-1)) - ens := kit2.NewEnsemble(t, kit2.MockProofs()). + opts := kit.ConstructorOpts(kit.LatestActorsAt(-1)) + ens := kit.NewEnsemble(t, kit.MockProofs()). FullNode(&client, opts). Miner(&chainMiner, &client, opts). - Miner(&evilMiner, &client, opts, kit2.PresealSectors(0)). + Miner(&evilMiner, &client, opts, kit.PresealSectors(0)). Start() { @@ -87,7 +87,7 @@ func TestWindowPostDispute(t *testing.T) { waitUntil := di.PeriodStart + di.WPoStProvingPeriod*2 t.Logf("End for head.Height > %d", waitUntil) - ts := client.WaitTillChain(ctx, kit2.HeightAtLeast(waitUntil)) + ts := client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) t.Logf("Now head.Height = %d", ts.Height()) p, err := client.StateMinerPower(ctx, evilMinerAddr, types.EmptyTSK) @@ -232,15 +232,15 @@ func TestWindowPostDisputeFails(t *testing.T) { t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") } - kit2.QuietMiningLogs() + kit.QuietMiningLogs() blocktime := 2 * time.Millisecond ctx, cancel := context.WithCancel(context.Background()) defer cancel() - opts := kit2.ConstructorOpts(kit2.LatestActorsAt(-1)) - client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), opts) + opts := kit.ConstructorOpts(kit.LatestActorsAt(-1)) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) ens.InterconnectAll().BeginMining(blocktime) defaultFrom, err := client.WalletDefaultAddress(ctx) @@ -260,12 +260,12 @@ func TestWindowPostDisputeFails(t *testing.T) { waitUntil := di.PeriodStart + di.WPoStProvingPeriod*2 t.Logf("End for head.Height > %d", waitUntil) - ts := client.WaitTillChain(ctx, kit2.HeightAtLeast(waitUntil)) + ts := client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) t.Logf("Now head.Height = %d", ts.Height()) ssz, err := miner.ActorSectorSize(ctx, maddr) require.NoError(t, err) - expectedPower := types.NewInt(uint64(ssz) * (kit2.DefaultPresealsPerBootstrapMiner + 10)) + expectedPower := types.NewInt(uint64(ssz) * (kit.DefaultPresealsPerBootstrapMiner + 10)) p, err := client.StateMinerPower(ctx, maddr, types.EmptyTSK) require.NoError(t, err) diff --git a/itests/wdpost_test.go b/itests/wdpost_test.go index 608c377ca..8a4b8ec23 100644 --- a/itests/wdpost_test.go +++ b/itests/wdpost_test.go @@ -18,7 +18,7 @@ import ( "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/extern/sector-storage/mock" - "github.com/filecoin-project/lotus/itests/kit2" + "github.com/filecoin-project/lotus/itests/kit" "github.com/filecoin-project/lotus/node/impl" ) @@ -27,7 +27,7 @@ func TestWindowedPost(t *testing.T) { t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") } - kit2.QuietMiningLogs() + kit.QuietMiningLogs() var ( blocktime = 2 * time.Millisecond @@ -50,8 +50,8 @@ func testWindowPostUpgrade(t *testing.T, blocktime time.Duration, nSectors int, ctx, cancel := context.WithCancel(context.Background()) defer cancel() - opts := kit2.ConstructorOpts(kit2.LatestActorsAt(upgradeHeight)) - client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), opts) + opts := kit.ConstructorOpts(kit.LatestActorsAt(upgradeHeight)) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) ens.InterconnectAll().BeginMining(blocktime) miner.PledgeSectors(ctx, nSectors, 0, nil) @@ -69,7 +69,7 @@ func testWindowPostUpgrade(t *testing.T, blocktime time.Duration, nSectors int, waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 2 t.Logf("End for head.Height > %d", waitUntil) - ts := client.WaitTillChain(ctx, kit2.HeightAtLeast(waitUntil)) + ts := client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) t.Logf("Now head.Height = %d", ts.Height()) p, err := client.StateMinerPower(ctx, maddr, types.EmptyTSK) @@ -79,7 +79,7 @@ func testWindowPostUpgrade(t *testing.T, blocktime time.Duration, nSectors int, require.NoError(t, err) require.Equal(t, p.MinerPower, p.TotalPower) - require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*uint64(nSectors+kit2.DefaultPresealsPerBootstrapMiner))) + require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*uint64(nSectors+kit.DefaultPresealsPerBootstrapMiner))) t.Log("Drop some sectors") @@ -145,7 +145,7 @@ func testWindowPostUpgrade(t *testing.T, blocktime time.Duration, nSectors int, waitUntil = di.PeriodStart + di.WPoStProvingPeriod + 2 t.Logf("End for head.Height > %d", waitUntil) - ts = client.WaitTillChain(ctx, kit2.HeightAtLeast(waitUntil)) + ts = client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) t.Logf("Now head.Height = %d", ts.Height()) p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) @@ -154,7 +154,7 @@ func testWindowPostUpgrade(t *testing.T, blocktime time.Duration, nSectors int, require.Equal(t, p.MinerPower, p.TotalPower) sectors := p.MinerPower.RawBytePower.Uint64() / uint64(ssz) - require.Equal(t, nSectors+kit2.DefaultPresealsPerBootstrapMiner-3, int(sectors)) // -3 just removed sectors + require.Equal(t, nSectors+kit.DefaultPresealsPerBootstrapMiner-3, int(sectors)) // -3 just removed sectors t.Log("Recover one sector") @@ -167,7 +167,7 @@ func testWindowPostUpgrade(t *testing.T, blocktime time.Duration, nSectors int, waitUntil = di.PeriodStart + di.WPoStProvingPeriod + 2 t.Logf("End for head.Height > %d", waitUntil) - ts = client.WaitTillChain(ctx, kit2.HeightAtLeast(waitUntil)) + ts = client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) t.Logf("Now head.Height = %d", ts.Height()) p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) @@ -176,7 +176,7 @@ func testWindowPostUpgrade(t *testing.T, blocktime time.Duration, nSectors int, require.Equal(t, p.MinerPower, p.TotalPower) sectors = p.MinerPower.RawBytePower.Uint64() / uint64(ssz) - require.Equal(t, nSectors+kit2.DefaultPresealsPerBootstrapMiner-2, int(sectors)) // -2 not recovered sectors + require.Equal(t, nSectors+kit.DefaultPresealsPerBootstrapMiner-2, int(sectors)) // -2 not recovered sectors // pledge a sector after recovery @@ -190,7 +190,7 @@ func testWindowPostUpgrade(t *testing.T, blocktime time.Duration, nSectors int, waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 2 t.Logf("End for head.Height > %d\n", waitUntil) - ts := client.WaitTillChain(ctx, kit2.HeightAtLeast(waitUntil)) + ts := client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) t.Logf("Now head.Height = %d", ts.Height()) } @@ -200,7 +200,7 @@ func testWindowPostUpgrade(t *testing.T, blocktime time.Duration, nSectors int, require.Equal(t, p.MinerPower, p.TotalPower) sectors = p.MinerPower.RawBytePower.Uint64() / uint64(ssz) - require.Equal(t, nSectors+kit2.DefaultPresealsPerBootstrapMiner-2+1, int(sectors)) // -2 not recovered sectors + 1 just pledged + require.Equal(t, nSectors+kit.DefaultPresealsPerBootstrapMiner-2+1, int(sectors)) // -2 not recovered sectors + 1 just pledged } func TestWindowPostBaseFeeNoBurn(t *testing.T) { @@ -208,7 +208,7 @@ func TestWindowPostBaseFeeNoBurn(t *testing.T) { t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") } - kit2.QuietMiningLogs() + kit.QuietMiningLogs() var ( blocktime = 2 * time.Millisecond @@ -221,7 +221,7 @@ func TestWindowPostBaseFeeNoBurn(t *testing.T) { och := build.UpgradeClausHeight build.UpgradeClausHeight = 10 - client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs()) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) ens.InterconnectAll().BeginMining(blocktime) maddr, err := miner.ActorAddress(ctx) @@ -264,15 +264,15 @@ func TestWindowPostBaseFeeBurn(t *testing.T) { t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") } - kit2.QuietMiningLogs() + kit.QuietMiningLogs() ctx, cancel := context.WithCancel(context.Background()) defer cancel() blocktime := 2 * time.Millisecond - opts := kit2.ConstructorOpts(kit2.LatestActorsAt(-1)) - client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), opts) + opts := kit.ConstructorOpts(kit.LatestActorsAt(-1)) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) ens.InterconnectAll().BeginMining(blocktime) maddr, err := miner.ActorAddress(ctx) From e2f5c494b009c3b8677f1f9e951d42f1a6375470 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 18 May 2021 17:01:30 -0700 Subject: [PATCH 088/257] feat: implement lotus-sim --- chain/actors/builtin/miner/actor.go.template | 4 + chain/actors/builtin/miner/miner.go | 4 + chain/actors/builtin/miner/state.go.template | 67 ++++- chain/actors/builtin/miner/v0.go | 63 ++++- chain/actors/builtin/miner/v2.go | 63 ++++- chain/actors/builtin/miner/v3.go | 63 ++++- chain/actors/builtin/miner/v4.go | 63 ++++- chain/actors/builtin/miner/v5.go | 63 ++++- chain/vm/vm.go | 6 + cmd/lotus-sim/create.go | 44 +++ cmd/lotus-sim/delete.go | 18 ++ cmd/lotus-sim/list.go | 35 +++ cmd/lotus-sim/main.go | 87 ++++++ cmd/lotus-sim/simulation/commit_queue.go | 187 +++++++++++++ cmd/lotus-sim/simulation/messages.go | 81 ++++++ cmd/lotus-sim/simulation/mock.go | 136 +++++++++ cmd/lotus-sim/simulation/node.go | 167 +++++++++++ cmd/lotus-sim/simulation/power.go | 58 ++++ cmd/lotus-sim/simulation/precommit.go | 205 ++++++++++++++ cmd/lotus-sim/simulation/provecommit.go | 219 +++++++++++++++ cmd/lotus-sim/simulation/simulation.go | 274 +++++++++++++++++++ cmd/lotus-sim/simulation/state.go | 190 +++++++++++++ cmd/lotus-sim/simulation/step.go | 196 +++++++++++++ cmd/lotus-sim/simulation/wdpost.go | 253 +++++++++++++++++ cmd/lotus-sim/step.go | 46 ++++ cmd/lotus-sim/upgrade.go | 53 ++++ cmd/lotus-sim/util.go | 18 ++ 27 files changed, 2651 insertions(+), 12 deletions(-) create mode 100644 cmd/lotus-sim/create.go create mode 100644 cmd/lotus-sim/delete.go create mode 100644 cmd/lotus-sim/list.go create mode 100644 cmd/lotus-sim/main.go create mode 100644 cmd/lotus-sim/simulation/commit_queue.go create mode 100644 cmd/lotus-sim/simulation/messages.go create mode 100644 cmd/lotus-sim/simulation/mock.go create mode 100644 cmd/lotus-sim/simulation/node.go create mode 100644 cmd/lotus-sim/simulation/power.go create mode 100644 cmd/lotus-sim/simulation/precommit.go create mode 100644 cmd/lotus-sim/simulation/provecommit.go create mode 100644 cmd/lotus-sim/simulation/simulation.go create mode 100644 cmd/lotus-sim/simulation/state.go create mode 100644 cmd/lotus-sim/simulation/step.go create mode 100644 cmd/lotus-sim/simulation/wdpost.go create mode 100644 cmd/lotus-sim/step.go create mode 100644 cmd/lotus-sim/upgrade.go create mode 100644 cmd/lotus-sim/util.go diff --git a/chain/actors/builtin/miner/actor.go.template b/chain/actors/builtin/miner/actor.go.template index 619dc699d..8c0b10cb0 100644 --- a/chain/actors/builtin/miner/actor.go.template +++ b/chain/actors/builtin/miner/actor.go.template @@ -97,9 +97,13 @@ type State interface { FindSector(abi.SectorNumber) (*SectorLocation, error) GetSectorExpiration(abi.SectorNumber) (*SectorExpiration, error) GetPrecommittedSector(abi.SectorNumber) (*SectorPreCommitOnChainInfo, error) + ForEachPrecommittedSector(func(SectorPreCommitOnChainInfo) error) error LoadSectors(sectorNos *bitfield.BitField) ([]*SectorOnChainInfo, error) NumLiveSectors() (uint64, error) IsAllocated(abi.SectorNumber) (bool, error) + // UnallocatedSectorNumbers returns up to count unallocated sector numbers (or less than + // count if there aren't enough). + UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error) // Note that ProvingPeriodStart is deprecated and will be renamed / removed in a future version of actors GetProvingPeriodStart() (abi.ChainEpoch, error) diff --git a/chain/actors/builtin/miner/miner.go b/chain/actors/builtin/miner/miner.go index 6e35d4e9f..bb7f80340 100644 --- a/chain/actors/builtin/miner/miner.go +++ b/chain/actors/builtin/miner/miner.go @@ -156,9 +156,13 @@ type State interface { FindSector(abi.SectorNumber) (*SectorLocation, error) GetSectorExpiration(abi.SectorNumber) (*SectorExpiration, error) GetPrecommittedSector(abi.SectorNumber) (*SectorPreCommitOnChainInfo, error) + ForEachPrecommittedSector(func(SectorPreCommitOnChainInfo) error) error LoadSectors(sectorNos *bitfield.BitField) ([]*SectorOnChainInfo, error) NumLiveSectors() (uint64, error) IsAllocated(abi.SectorNumber) (bool, error) + // UnallocatedSectorNumbers returns up to count unallocated sector numbers (or less than + // count if there aren't enough). + UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error) // Note that ProvingPeriodStart is deprecated and will be renamed / removed in a future version of actors GetProvingPeriodStart() (abi.ChainEpoch, error) diff --git a/chain/actors/builtin/miner/state.go.template b/chain/actors/builtin/miner/state.go.template index b7e5f40df..eb7ab00bf 100644 --- a/chain/actors/builtin/miner/state.go.template +++ b/chain/actors/builtin/miner/state.go.template @@ -8,6 +8,7 @@ import ( {{end}} "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" + rle "github.com/filecoin-project/go-bitfield/rle" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/dline" "github.com/ipfs/go-cid" @@ -209,6 +210,26 @@ func (s *state{{.v}}) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCom return &ret, nil } +func (s *state{{.v}}) ForEachPrecommittedSector(cb func(SectorPreCommitOnChainInfo) error) error { +{{if (ge .v 3) -}} + precommitted, err := adt{{.v}}.AsMap(s.store, s.State.PreCommittedSectors, builtin{{.v}}.DefaultHamtBitwidth) +{{- else -}} + precommitted, err := adt{{.v}}.AsMap(s.store, s.State.PreCommittedSectors) +{{- end}} + if err != nil { + return err + } + + var info miner{{.v}}.SectorPreCommitOnChainInfo + if err := precommitted.ForEach(&info, func(_ string) error { + return cb(fromV{{.v}}SectorPreCommitOnChainInfo(info)) + }); err != nil { + return err + } + + return nil +} + func (s *state{{.v}}) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) { sectors, err := miner{{.v}}.LoadSectors(s.store, s.State.Sectors) if err != nil { @@ -242,9 +263,15 @@ func (s *state{{.v}}) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo return infos, nil } -func (s *state{{.v}}) IsAllocated(num abi.SectorNumber) (bool, error) { +func (s *state{{.v}}) loadAllocatedSectorNumbers() (bitfield.BitField, error) { var allocatedSectors bitfield.BitField - if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors) + return allocatedSectors, err +} + +func (s *state{{.v}}) IsAllocated(num abi.SectorNumber) (bool, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { return false, err } @@ -255,6 +282,42 @@ func (s *state{{.v}}) GetProvingPeriodStart() (abi.ChainEpoch, error) { return s.State.ProvingPeriodStart, nil } +func (s *state{{.v}}) UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { + return nil, err + } + + allocatedRuns, err := allocatedSectors.RunIterator() + if err != nil { + return nil, err + } + + unallocatedRuns, err := rle.Subtract( + &rle.RunSliceIterator{Runs: []rle.Run{ {Val: true, Len: abi.MaxSectorNumber} }}, + allocatedRuns, + ) + if err != nil { + return nil, err + } + + iter, err := rle.BitsFromRuns(unallocatedRuns) + if err != nil { + return nil, err + } + + sectors := make([]abi.SectorNumber, 0, count) + for iter.HasNext() && len(sectors) < count { + nextNo, err := iter.Next() + if err != nil { + return nil, err + } + sectors = append(sectors, abi.SectorNumber(nextNo)) + } + + return sectors, nil +} + func (s *state{{.v}}) LoadDeadline(idx uint64) (Deadline, error) { dls, err := s.State.LoadDeadlines(s.store) if err != nil { diff --git a/chain/actors/builtin/miner/v0.go b/chain/actors/builtin/miner/v0.go index 344be1993..c5e887481 100644 --- a/chain/actors/builtin/miner/v0.go +++ b/chain/actors/builtin/miner/v0.go @@ -8,6 +8,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" + rle "github.com/filecoin-project/go-bitfield/rle" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/dline" "github.com/ipfs/go-cid" @@ -206,6 +207,22 @@ func (s *state0) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOn return &ret, nil } +func (s *state0) ForEachPrecommittedSector(cb func(SectorPreCommitOnChainInfo) error) error { + precommitted, err := adt0.AsMap(s.store, s.State.PreCommittedSectors) + if err != nil { + return err + } + + var info miner0.SectorPreCommitOnChainInfo + if err := precommitted.ForEach(&info, func(_ string) error { + return cb(fromV0SectorPreCommitOnChainInfo(info)) + }); err != nil { + return err + } + + return nil +} + func (s *state0) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) { sectors, err := miner0.LoadSectors(s.store, s.State.Sectors) if err != nil { @@ -239,9 +256,15 @@ func (s *state0) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, err return infos, nil } -func (s *state0) IsAllocated(num abi.SectorNumber) (bool, error) { +func (s *state0) loadAllocatedSectorNumbers() (bitfield.BitField, error) { var allocatedSectors bitfield.BitField - if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors) + return allocatedSectors, err +} + +func (s *state0) IsAllocated(num abi.SectorNumber) (bool, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { return false, err } @@ -252,6 +275,42 @@ func (s *state0) GetProvingPeriodStart() (abi.ChainEpoch, error) { return s.State.ProvingPeriodStart, nil } +func (s *state0) UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { + return nil, err + } + + allocatedRuns, err := allocatedSectors.RunIterator() + if err != nil { + return nil, err + } + + unallocatedRuns, err := rle.Subtract( + &rle.RunSliceIterator{Runs: []rle.Run{{Val: true, Len: abi.MaxSectorNumber}}}, + allocatedRuns, + ) + if err != nil { + return nil, err + } + + iter, err := rle.BitsFromRuns(unallocatedRuns) + if err != nil { + return nil, err + } + + sectors := make([]abi.SectorNumber, 0, count) + for iter.HasNext() && len(sectors) < count { + nextNo, err := iter.Next() + if err != nil { + return nil, err + } + sectors = append(sectors, abi.SectorNumber(nextNo)) + } + + return sectors, nil +} + func (s *state0) LoadDeadline(idx uint64) (Deadline, error) { dls, err := s.State.LoadDeadlines(s.store) if err != nil { diff --git a/chain/actors/builtin/miner/v2.go b/chain/actors/builtin/miner/v2.go index 3e76d0b69..45d4a7165 100644 --- a/chain/actors/builtin/miner/v2.go +++ b/chain/actors/builtin/miner/v2.go @@ -6,6 +6,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" + rle "github.com/filecoin-project/go-bitfield/rle" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/dline" "github.com/ipfs/go-cid" @@ -204,6 +205,22 @@ func (s *state2) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOn return &ret, nil } +func (s *state2) ForEachPrecommittedSector(cb func(SectorPreCommitOnChainInfo) error) error { + precommitted, err := adt2.AsMap(s.store, s.State.PreCommittedSectors) + if err != nil { + return err + } + + var info miner2.SectorPreCommitOnChainInfo + if err := precommitted.ForEach(&info, func(_ string) error { + return cb(fromV2SectorPreCommitOnChainInfo(info)) + }); err != nil { + return err + } + + return nil +} + func (s *state2) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) { sectors, err := miner2.LoadSectors(s.store, s.State.Sectors) if err != nil { @@ -237,9 +254,15 @@ func (s *state2) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, err return infos, nil } -func (s *state2) IsAllocated(num abi.SectorNumber) (bool, error) { +func (s *state2) loadAllocatedSectorNumbers() (bitfield.BitField, error) { var allocatedSectors bitfield.BitField - if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors) + return allocatedSectors, err +} + +func (s *state2) IsAllocated(num abi.SectorNumber) (bool, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { return false, err } @@ -250,6 +273,42 @@ func (s *state2) GetProvingPeriodStart() (abi.ChainEpoch, error) { return s.State.ProvingPeriodStart, nil } +func (s *state2) UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { + return nil, err + } + + allocatedRuns, err := allocatedSectors.RunIterator() + if err != nil { + return nil, err + } + + unallocatedRuns, err := rle.Subtract( + &rle.RunSliceIterator{Runs: []rle.Run{{Val: true, Len: abi.MaxSectorNumber}}}, + allocatedRuns, + ) + if err != nil { + return nil, err + } + + iter, err := rle.BitsFromRuns(unallocatedRuns) + if err != nil { + return nil, err + } + + sectors := make([]abi.SectorNumber, 0, count) + for iter.HasNext() && len(sectors) < count { + nextNo, err := iter.Next() + if err != nil { + return nil, err + } + sectors = append(sectors, abi.SectorNumber(nextNo)) + } + + return sectors, nil +} + func (s *state2) LoadDeadline(idx uint64) (Deadline, error) { dls, err := s.State.LoadDeadlines(s.store) if err != nil { diff --git a/chain/actors/builtin/miner/v3.go b/chain/actors/builtin/miner/v3.go index 72986233d..166abe1e7 100644 --- a/chain/actors/builtin/miner/v3.go +++ b/chain/actors/builtin/miner/v3.go @@ -6,6 +6,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" + rle "github.com/filecoin-project/go-bitfield/rle" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/dline" "github.com/ipfs/go-cid" @@ -206,6 +207,22 @@ func (s *state3) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOn return &ret, nil } +func (s *state3) ForEachPrecommittedSector(cb func(SectorPreCommitOnChainInfo) error) error { + precommitted, err := adt3.AsMap(s.store, s.State.PreCommittedSectors, builtin3.DefaultHamtBitwidth) + if err != nil { + return err + } + + var info miner3.SectorPreCommitOnChainInfo + if err := precommitted.ForEach(&info, func(_ string) error { + return cb(fromV3SectorPreCommitOnChainInfo(info)) + }); err != nil { + return err + } + + return nil +} + func (s *state3) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) { sectors, err := miner3.LoadSectors(s.store, s.State.Sectors) if err != nil { @@ -239,9 +256,15 @@ func (s *state3) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, err return infos, nil } -func (s *state3) IsAllocated(num abi.SectorNumber) (bool, error) { +func (s *state3) loadAllocatedSectorNumbers() (bitfield.BitField, error) { var allocatedSectors bitfield.BitField - if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors) + return allocatedSectors, err +} + +func (s *state3) IsAllocated(num abi.SectorNumber) (bool, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { return false, err } @@ -252,6 +275,42 @@ func (s *state3) GetProvingPeriodStart() (abi.ChainEpoch, error) { return s.State.ProvingPeriodStart, nil } +func (s *state3) UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { + return nil, err + } + + allocatedRuns, err := allocatedSectors.RunIterator() + if err != nil { + return nil, err + } + + unallocatedRuns, err := rle.Subtract( + &rle.RunSliceIterator{Runs: []rle.Run{{Val: true, Len: abi.MaxSectorNumber}}}, + allocatedRuns, + ) + if err != nil { + return nil, err + } + + iter, err := rle.BitsFromRuns(unallocatedRuns) + if err != nil { + return nil, err + } + + sectors := make([]abi.SectorNumber, 0, count) + for iter.HasNext() && len(sectors) < count { + nextNo, err := iter.Next() + if err != nil { + return nil, err + } + sectors = append(sectors, abi.SectorNumber(nextNo)) + } + + return sectors, nil +} + func (s *state3) LoadDeadline(idx uint64) (Deadline, error) { dls, err := s.State.LoadDeadlines(s.store) if err != nil { diff --git a/chain/actors/builtin/miner/v4.go b/chain/actors/builtin/miner/v4.go index 96ed21f04..71a2b9f9d 100644 --- a/chain/actors/builtin/miner/v4.go +++ b/chain/actors/builtin/miner/v4.go @@ -6,6 +6,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" + rle "github.com/filecoin-project/go-bitfield/rle" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/dline" "github.com/ipfs/go-cid" @@ -206,6 +207,22 @@ func (s *state4) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOn return &ret, nil } +func (s *state4) ForEachPrecommittedSector(cb func(SectorPreCommitOnChainInfo) error) error { + precommitted, err := adt4.AsMap(s.store, s.State.PreCommittedSectors, builtin4.DefaultHamtBitwidth) + if err != nil { + return err + } + + var info miner4.SectorPreCommitOnChainInfo + if err := precommitted.ForEach(&info, func(_ string) error { + return cb(fromV4SectorPreCommitOnChainInfo(info)) + }); err != nil { + return err + } + + return nil +} + func (s *state4) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) { sectors, err := miner4.LoadSectors(s.store, s.State.Sectors) if err != nil { @@ -239,9 +256,15 @@ func (s *state4) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, err return infos, nil } -func (s *state4) IsAllocated(num abi.SectorNumber) (bool, error) { +func (s *state4) loadAllocatedSectorNumbers() (bitfield.BitField, error) { var allocatedSectors bitfield.BitField - if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors) + return allocatedSectors, err +} + +func (s *state4) IsAllocated(num abi.SectorNumber) (bool, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { return false, err } @@ -252,6 +275,42 @@ func (s *state4) GetProvingPeriodStart() (abi.ChainEpoch, error) { return s.State.ProvingPeriodStart, nil } +func (s *state4) UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { + return nil, err + } + + allocatedRuns, err := allocatedSectors.RunIterator() + if err != nil { + return nil, err + } + + unallocatedRuns, err := rle.Subtract( + &rle.RunSliceIterator{Runs: []rle.Run{{Val: true, Len: abi.MaxSectorNumber}}}, + allocatedRuns, + ) + if err != nil { + return nil, err + } + + iter, err := rle.BitsFromRuns(unallocatedRuns) + if err != nil { + return nil, err + } + + sectors := make([]abi.SectorNumber, 0, count) + for iter.HasNext() && len(sectors) < count { + nextNo, err := iter.Next() + if err != nil { + return nil, err + } + sectors = append(sectors, abi.SectorNumber(nextNo)) + } + + return sectors, nil +} + func (s *state4) LoadDeadline(idx uint64) (Deadline, error) { dls, err := s.State.LoadDeadlines(s.store) if err != nil { diff --git a/chain/actors/builtin/miner/v5.go b/chain/actors/builtin/miner/v5.go index 7996acf32..568834777 100644 --- a/chain/actors/builtin/miner/v5.go +++ b/chain/actors/builtin/miner/v5.go @@ -6,6 +6,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" + rle "github.com/filecoin-project/go-bitfield/rle" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/dline" "github.com/ipfs/go-cid" @@ -206,6 +207,22 @@ func (s *state5) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOn return &ret, nil } +func (s *state5) ForEachPrecommittedSector(cb func(SectorPreCommitOnChainInfo) error) error { + precommitted, err := adt5.AsMap(s.store, s.State.PreCommittedSectors, builtin5.DefaultHamtBitwidth) + if err != nil { + return err + } + + var info miner5.SectorPreCommitOnChainInfo + if err := precommitted.ForEach(&info, func(_ string) error { + return cb(fromV5SectorPreCommitOnChainInfo(info)) + }); err != nil { + return err + } + + return nil +} + func (s *state5) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) { sectors, err := miner5.LoadSectors(s.store, s.State.Sectors) if err != nil { @@ -239,9 +256,15 @@ func (s *state5) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, err return infos, nil } -func (s *state5) IsAllocated(num abi.SectorNumber) (bool, error) { +func (s *state5) loadAllocatedSectorNumbers() (bitfield.BitField, error) { var allocatedSectors bitfield.BitField - if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors) + return allocatedSectors, err +} + +func (s *state5) IsAllocated(num abi.SectorNumber) (bool, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { return false, err } @@ -252,6 +275,42 @@ func (s *state5) GetProvingPeriodStart() (abi.ChainEpoch, error) { return s.State.ProvingPeriodStart, nil } +func (s *state5) UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { + return nil, err + } + + allocatedRuns, err := allocatedSectors.RunIterator() + if err != nil { + return nil, err + } + + unallocatedRuns, err := rle.Subtract( + &rle.RunSliceIterator{Runs: []rle.Run{{Val: true, Len: abi.MaxSectorNumber}}}, + allocatedRuns, + ) + if err != nil { + return nil, err + } + + iter, err := rle.BitsFromRuns(unallocatedRuns) + if err != nil { + return nil, err + } + + sectors := make([]abi.SectorNumber, 0, count) + for iter.HasNext() && len(sectors) < count { + nextNo, err := iter.Next() + if err != nil { + return nil, err + } + sectors = append(sectors, abi.SectorNumber(nextNo)) + } + + return sectors, nil +} + func (s *state5) LoadDeadline(idx uint64) (Deadline, error) { dls, err := s.State.LoadDeadlines(s.store) if err != nil { diff --git a/chain/vm/vm.go b/chain/vm/vm.go index c5bfffc7f..1b8424eee 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -669,6 +669,12 @@ func (vm *VM) Flush(ctx context.Context) (cid.Cid, error) { return root, nil } +// Get the buffered blockstore associated with the VM. This includes any temporary blocks produced +// during thsi VM's execution. +func (vm *VM) ActorStore(ctx context.Context) adt.Store { + return adt.WrapStore(ctx, vm.cst) +} + func linksForObj(blk block.Block, cb func(cid.Cid)) error { switch blk.Cid().Prefix().Codec { case cid.DagCBOR: diff --git a/cmd/lotus-sim/create.go b/cmd/lotus-sim/create.go new file mode 100644 index 000000000..777f1723c --- /dev/null +++ b/cmd/lotus-sim/create.go @@ -0,0 +1,44 @@ +package main + +import ( + "fmt" + + "github.com/filecoin-project/lotus/chain/types" + lcli "github.com/filecoin-project/lotus/cli" + "github.com/urfave/cli/v2" +) + +var createSimCommand = &cli.Command{ + Name: "create", + ArgsUsage: "[tipset]", + Action: func(cctx *cli.Context) error { + node, err := open(cctx) + if err != nil { + return err + } + defer node.Close() + + var ts *types.TipSet + switch cctx.NArg() { + case 0: + if err := node.Chainstore.Load(); err != nil { + return err + } + ts = node.Chainstore.GetHeaviestTipSet() + case 1: + cids, err := lcli.ParseTipSetString(cctx.Args().Get(1)) + if err != nil { + return err + } + tsk := types.NewTipSetKey(cids...) + ts, err = node.Chainstore.LoadTipSet(tsk) + if err != nil { + return err + } + default: + return fmt.Errorf("expected 0 or 1 arguments") + } + _, err = node.CreateSim(cctx.Context, cctx.String("simulation"), ts) + return err + }, +} diff --git a/cmd/lotus-sim/delete.go b/cmd/lotus-sim/delete.go new file mode 100644 index 000000000..472a35a86 --- /dev/null +++ b/cmd/lotus-sim/delete.go @@ -0,0 +1,18 @@ +package main + +import ( + "github.com/urfave/cli/v2" +) + +var deleteSimCommand = &cli.Command{ + Name: "delete", + Action: func(cctx *cli.Context) error { + node, err := open(cctx) + if err != nil { + return err + } + defer node.Close() + + return node.DeleteSim(cctx.Context, cctx.String("simulation")) + }, +} diff --git a/cmd/lotus-sim/list.go b/cmd/lotus-sim/list.go new file mode 100644 index 000000000..69809b188 --- /dev/null +++ b/cmd/lotus-sim/list.go @@ -0,0 +1,35 @@ +package main + +import ( + "fmt" + "text/tabwriter" + + "github.com/urfave/cli/v2" +) + +var listSimCommand = &cli.Command{ + Name: "list", + Action: func(cctx *cli.Context) error { + node, err := open(cctx) + if err != nil { + return err + } + defer node.Close() + + list, err := node.ListSims(cctx.Context) + if err != nil { + return err + } + tw := tabwriter.NewWriter(cctx.App.Writer, 8, 8, 0, ' ', 0) + for _, name := range list { + sim, err := node.LoadSim(cctx.Context, name) + if err != nil { + return err + } + head := sim.GetHead() + fmt.Fprintf(tw, "%s\t%s\t%s\n", name, head.Height(), head.Key()) + sim.Close() + } + return tw.Flush() + }, +} diff --git a/cmd/lotus-sim/main.go b/cmd/lotus-sim/main.go new file mode 100644 index 000000000..9a4d40699 --- /dev/null +++ b/cmd/lotus-sim/main.go @@ -0,0 +1,87 @@ +package main + +import ( + "fmt" + "os" + + logging "github.com/ipfs/go-log/v2" + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/stmgr" +) + +var root []*cli.Command = []*cli.Command{ + createSimCommand, + deleteSimCommand, + listSimCommand, + stepSimCommand, + setUpgradeCommand, +} + +func main() { + if _, set := os.LookupEnv("GOLOG_LOG_LEVEL"); !set { + _ = logging.SetLogLevel("simulation", "DEBUG") + } + app := &cli.App{ + Name: "lotus-sim", + Usage: "A tool to simulate a network.", + Commands: root, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "repo", + EnvVars: []string{"LOTUS_PATH"}, + Hidden: true, + Value: "~/.lotus", + }, + &cli.StringFlag{ + Name: "simulation", + Aliases: []string{"sim"}, + EnvVars: []string{"LOTUS_SIMULATION"}, + Value: "default", + }, + }, + } + + if err := app.Run(os.Args); err != nil { + fmt.Fprintf(os.Stderr, "Error: %s\n", err) + os.Exit(1) + return + } +} + +func run(cctx *cli.Context) error { + ctx := cctx.Context + + node, err := open(cctx) + if err != nil { + return err + } + defer node.Close() + + if err := node.Chainstore.Load(); err != nil { + return err + } + + ts := node.Chainstore.GetHeaviestTipSet() + + st, err := stmgr.NewStateManagerWithUpgradeSchedule(node.Chainstore, nil) + if err != nil { + return err + } + + powerTableActor, err := st.LoadActor(ctx, power.Address, ts) + if err != nil { + return err + } + powerTable, err := power.Load(node.Chainstore.ActorStore(ctx), powerTableActor) + if err != nil { + return err + } + allMiners, err := powerTable.ListAllMiners() + if err != nil { + return err + } + fmt.Printf("miner count: %d\n", len(allMiners)) + return nil +} diff --git a/cmd/lotus-sim/simulation/commit_queue.go b/cmd/lotus-sim/simulation/commit_queue.go new file mode 100644 index 000000000..957d301cf --- /dev/null +++ b/cmd/lotus-sim/simulation/commit_queue.go @@ -0,0 +1,187 @@ +package simulation + +import ( + "sort" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/policy" +) + +type pendingCommitTracker map[address.Address]minerPendingCommits +type minerPendingCommits map[abi.RegisteredSealProof][]abi.SectorNumber + +func (m minerPendingCommits) finish(proof abi.RegisteredSealProof, count int) { + snos := m[proof] + if len(snos) < count { + panic("not enough sector numbers to finish") + } else if len(snos) == count { + delete(m, proof) + } else { + m[proof] = snos[count:] + } +} + +func (m minerPendingCommits) empty() bool { + return len(m) == 0 +} + +func (m minerPendingCommits) count() int { + count := 0 + for _, snos := range m { + count += len(snos) + } + return count +} + +type commitQueue struct { + minerQueue []address.Address + queue []pendingCommitTracker + offset abi.ChainEpoch +} + +func (q *commitQueue) ready() int { + if len(q.queue) == 0 { + return 0 + } + count := 0 + for _, pending := range q.queue[0] { + count += pending.count() + } + return count +} + +func (q *commitQueue) nextMiner() (address.Address, minerPendingCommits, bool) { + if len(q.queue) == 0 { + return address.Undef, nil, false + } + next := q.queue[0] + + // Go through the queue and find the first non-empty batch. + for len(q.minerQueue) > 0 { + addr := q.minerQueue[0] + q.minerQueue = q.minerQueue[1:] + pending := next[addr] + if !pending.empty() { + return addr, pending, true + } + delete(next, addr) + } + + return address.Undef, nil, false +} + +func (q *commitQueue) advanceEpoch(epoch abi.ChainEpoch) { + if epoch < q.offset { + panic("cannot roll epoch backwards") + } + // Now we "roll forwards", merging each epoch we advance over with the next. + for len(q.queue) > 1 && q.offset < epoch { + curr := q.queue[0] + q.queue[0] = nil + q.queue = q.queue[1:] + q.offset++ + + next := q.queue[0] + + // Cleanup empty entries. + for addr, pending := range curr { + if pending.empty() { + delete(curr, addr) + } + } + + // If the entire level is actually empty, just skip to the next one. + if len(curr) == 0 { + continue + } + + // Otherwise, merge the next into the current. + for addr, nextPending := range next { + currPending := curr[addr] + if currPending.empty() { + curr[addr] = nextPending + continue + } + for ty, nextSnos := range nextPending { + currSnos := currPending[ty] + if len(currSnos) == 0 { + currPending[ty] = nextSnos + continue + } + currPending[ty] = append(currSnos, nextSnos...) + } + } + } + q.offset = epoch + if len(q.queue) == 0 { + return + } + + next := q.queue[0] + seenMiners := make(map[address.Address]struct{}, len(q.minerQueue)) + for _, addr := range q.minerQueue { + seenMiners[addr] = struct{}{} + } + + // Find the new miners not already in the queue. + offset := len(q.minerQueue) + for addr, pending := range next { + if pending.empty() { + delete(next, addr) + continue + } + if _, ok := seenMiners[addr]; ok { + continue + } + q.minerQueue = append(q.minerQueue, addr) + } + + // Sort the new miners only. + newMiners := q.minerQueue[offset:] + sort.Slice(newMiners, func(i, j int) bool { + // eh, escape analysis should be fine here... + return string(newMiners[i].Bytes()) < string(newMiners[j].Bytes()) + }) +} + +func (q *commitQueue) enqueueProveCommit(addr address.Address, preCommitEpoch abi.ChainEpoch, info miner.SectorPreCommitInfo) error { + // Compute the epoch at which we can start trying to commit. + preCommitDelay := policy.GetPreCommitChallengeDelay() + minCommitEpoch := preCommitEpoch + preCommitDelay + 1 + + // Figure out the offset in the queue. + i := int(minCommitEpoch - q.offset) + if i < 0 { + i = 0 + } + + // Expand capacity and insert. + if cap(q.queue) <= i { + pc := make([]pendingCommitTracker, i+1, preCommitDelay*2) + copy(pc, q.queue) + q.queue = pc + } else if len(q.queue) <= i { + q.queue = q.queue[:i+1] + } + tracker := q.queue[i] + if tracker == nil { + tracker = make(pendingCommitTracker) + q.queue[i] = tracker + } + minerPending := tracker[addr] + if minerPending == nil { + minerPending = make(minerPendingCommits) + tracker[addr] = minerPending + } + minerPending[info.SealProof] = append(minerPending[info.SealProof], info.SectorNumber) + return nil +} + +func (q *commitQueue) head() pendingCommitTracker { + if len(q.queue) > 0 { + return q.queue[0] + } + return nil +} diff --git a/cmd/lotus-sim/simulation/messages.go b/cmd/lotus-sim/simulation/messages.go new file mode 100644 index 000000000..76b100d75 --- /dev/null +++ b/cmd/lotus-sim/simulation/messages.go @@ -0,0 +1,81 @@ +package simulation + +import ( + "context" + + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/crypto" + blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/types" +) + +func toArray(store blockadt.Store, cids []cid.Cid) (cid.Cid, error) { + arr := blockadt.MakeEmptyArray(store) + for i, c := range cids { + oc := cbg.CborCid(c) + if err := arr.Set(uint64(i), &oc); err != nil { + return cid.Undef, err + } + } + return arr.Root() +} + +func (nd *Node) storeMessages(ctx context.Context, messages []*types.Message) (cid.Cid, error) { + var blsMessages, sekpMessages []cid.Cid + fakeSig := make([]byte, 32) + for _, msg := range messages { + protocol := msg.From.Protocol() + + // It's just a very convenient way to fill up accounts. + if msg.From == builtin.BurntFundsActorAddr { + protocol = address.SECP256K1 + } + switch protocol { + case address.SECP256K1: + chainMsg := &types.SignedMessage{ + Message: *msg, + Signature: crypto.Signature{ + Type: crypto.SigTypeSecp256k1, + Data: fakeSig, + }, + } + c, err := nd.Chainstore.PutMessage(chainMsg) + if err != nil { + return cid.Undef, err + } + sekpMessages = append(sekpMessages, c) + case address.BLS: + c, err := nd.Chainstore.PutMessage(msg) + if err != nil { + return cid.Undef, err + } + blsMessages = append(blsMessages, c) + default: + return cid.Undef, xerrors.Errorf("unexpected from address %q of type %d", msg.From, msg.From.Protocol()) + } + } + adtStore := nd.Chainstore.ActorStore(ctx) + blsMsgArr, err := toArray(adtStore, blsMessages) + if err != nil { + return cid.Undef, err + } + sekpMsgArr, err := toArray(adtStore, sekpMessages) + if err != nil { + return cid.Undef, err + } + + msgsCid, err := adtStore.Put(adtStore.Context(), &types.MsgMeta{ + BlsMessages: blsMsgArr, + SecpkMessages: sekpMsgArr, + }) + if err != nil { + return cid.Undef, err + } + return msgsCid, nil +} diff --git a/cmd/lotus-sim/simulation/mock.go b/cmd/lotus-sim/simulation/mock.go new file mode 100644 index 000000000..e8a7b2d4a --- /dev/null +++ b/cmd/lotus-sim/simulation/mock.go @@ -0,0 +1,136 @@ +package simulation + +import ( + "bytes" + "context" + "encoding/binary" + "fmt" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" +) + +// Ideally, we'd use extern/sector-storage/mock. Unfortunately, those mocks are a bit _too_ accurate +// and would force us to load sector info for window post proofs. + +const ( + mockSealProofPrefix = "valid seal proof:" + mockAggregateSealProofPrefix = "valid aggregate seal proof:" + mockPoStProofPrefix = "valid post proof:" +) + +func mockSealProof(proofType abi.RegisteredSealProof, minerAddr address.Address) ([]byte, error) { + plen, err := proofType.ProofSize() + if err != nil { + return nil, err + } + proof := make([]byte, plen) + i := copy(proof, mockSealProofPrefix) + binary.BigEndian.PutUint64(proof[i:], uint64(proofType)) + i += 8 + i += copy(proof[i:], minerAddr.Bytes()) + return proof, nil +} + +func mockAggregateSealProof(proofType abi.RegisteredSealProof, minerAddr address.Address, count int) ([]byte, error) { + proof := make([]byte, aggProofLen(count)) + i := copy(proof, mockAggregateSealProofPrefix) + binary.BigEndian.PutUint64(proof[i:], uint64(proofType)) + i += 8 + binary.BigEndian.PutUint64(proof[i:], uint64(count)) + i += 8 + i += copy(proof[i:], minerAddr.Bytes()) + + return proof, nil +} + +func mockWpostProof(proofType abi.RegisteredPoStProof, minerAddr address.Address) ([]byte, error) { + plen, err := proofType.ProofSize() + if err != nil { + return nil, err + } + proof := make([]byte, plen) + i := copy(proof, mockPoStProofPrefix) + i += copy(proof[i:], minerAddr.Bytes()) + return proof, nil +} + +// TODO: dedup +func aggProofLen(nproofs int) int { + switch { + case nproofs <= 8: + return 11220 + case nproofs <= 16: + return 14196 + case nproofs <= 32: + return 17172 + case nproofs <= 64: + return 20148 + case nproofs <= 128: + return 23124 + case nproofs <= 256: + return 26100 + case nproofs <= 512: + return 29076 + case nproofs <= 1024: + return 32052 + case nproofs <= 2048: + return 35028 + case nproofs <= 4096: + return 38004 + case nproofs <= 8192: + return 40980 + default: + panic("too many proofs") + } +} + +type mockVerifier struct{} + +func (mockVerifier) VerifySeal(proof proof5.SealVerifyInfo) (bool, error) { + addr, err := address.NewIDAddress(uint64(proof.Miner)) + if err != nil { + return false, err + } + mockProof, err := mockSealProof(proof.SealProof, addr) + if err != nil { + return false, err + } + return bytes.Equal(proof.Proof, mockProof), nil +} + +func (mockVerifier) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProofAndInfos) (bool, error) { + addr, err := address.NewIDAddress(uint64(aggregate.Miner)) + if err != nil { + return false, err + } + mockProof, err := mockAggregateSealProof(aggregate.SealProof, addr, len(aggregate.Infos)) + if err != nil { + return false, err + } + return bytes.Equal(aggregate.Proof, mockProof), nil +} +func (mockVerifier) VerifyWinningPoSt(ctx context.Context, info proof5.WinningPoStVerifyInfo) (bool, error) { + panic("should not be called") +} +func (mockVerifier) VerifyWindowPoSt(ctx context.Context, info proof5.WindowPoStVerifyInfo) (bool, error) { + if len(info.Proofs) != 1 { + return false, fmt.Errorf("expected exactly one proof") + } + proof := info.Proofs[0] + addr, err := address.NewIDAddress(uint64(info.Prover)) + if err != nil { + return false, err + } + mockProof, err := mockWpostProof(proof.PoStProof, addr) + if err != nil { + return false, err + } + return bytes.Equal(proof.ProofBytes, mockProof), nil +} + +func (mockVerifier) GenerateWinningPoStSectorChallenge(context.Context, abi.RegisteredPoStProof, abi.ActorID, abi.PoStRandomness, uint64) ([]uint64, error) { + panic("should not be called") +} diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go new file mode 100644 index 000000000..505f563e9 --- /dev/null +++ b/cmd/lotus-sim/simulation/node.go @@ -0,0 +1,167 @@ +package simulation + +import ( + "context" + "io" + "strings" + + "go.uber.org/multierr" + "golang.org/x/xerrors" + + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/query" + + "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" + "github.com/filecoin-project/lotus/node/repo" +) + +type Node struct { + Repo repo.LockedRepo + Blockstore blockstore.Blockstore + MetadataDS datastore.Batching + Chainstore *store.ChainStore +} + +func OpenNode(ctx context.Context, path string) (*Node, error) { + var node Node + r, err := repo.NewFS(path) + if err != nil { + return nil, err + } + + node.Repo, err = r.Lock(repo.FullNode) + if err != nil { + node.Close() + return nil, err + } + + node.Blockstore, err = node.Repo.Blockstore(ctx, repo.UniversalBlockstore) + if err != nil { + node.Close() + return nil, err + } + + node.MetadataDS, err = node.Repo.Datastore(ctx, "/metadata") + if err != nil { + node.Close() + return nil, err + } + + node.Chainstore = store.NewChainStore(node.Blockstore, node.Blockstore, node.MetadataDS, vm.Syscalls(mockVerifier{}), nil) + return &node, nil +} + +func (nd *Node) Close() error { + var err error + if closer, ok := nd.Blockstore.(io.Closer); ok && closer != nil { + err = multierr.Append(err, closer.Close()) + } + if nd.MetadataDS != nil { + err = multierr.Append(err, nd.MetadataDS.Close()) + } + if nd.Repo != nil { + err = multierr.Append(err, nd.Repo.Close()) + } + return err +} + +func (nd *Node) LoadSim(ctx context.Context, name string) (*Simulation, error) { + sim := &Simulation{ + Node: nd, + name: name, + } + tskBytes, err := nd.MetadataDS.Get(sim.key("head")) + if err != nil { + return nil, xerrors.Errorf("failed to load simulation %s: %w", name, err) + } + tsk, err := types.TipSetKeyFromBytes(tskBytes) + if err != nil { + return nil, xerrors.Errorf("failed to parse simulation %s's tipset %v: %w", name, tskBytes, err) + } + sim.head, err = nd.Chainstore.LoadTipSet(tsk) + if err != nil { + return nil, xerrors.Errorf("failed to load simulation tipset %s: %w", tsk, err) + } + + err = sim.loadConfig() + if err != nil { + return nil, xerrors.Errorf("failed to load config for simulation %s: %w", name, err) + } + + us, err := sim.config.upgradeSchedule() + if err != nil { + return nil, xerrors.Errorf("failed to create upgrade schedule for simulation %s: %w", name, err) + } + sim.sm, err = stmgr.NewStateManagerWithUpgradeSchedule(nd.Chainstore, us) + if err != nil { + return nil, xerrors.Errorf("failed to create state manager for simulation %s: %w", name, err) + } + return sim, nil +} + +func (nd *Node) CreateSim(ctx context.Context, name string, head *types.TipSet) (*Simulation, error) { + if strings.Contains(name, "/") { + return nil, xerrors.Errorf("simulation name %q cannot contain a '/'", name) + } + sim := &Simulation{ + name: name, + Node: nd, + sm: stmgr.NewStateManager(nd.Chainstore), + } + if has, err := nd.MetadataDS.Has(sim.key("head")); err != nil { + return nil, err + } else if has { + return nil, xerrors.Errorf("simulation named %s already exists", name) + } + + if err := sim.SetHead(head); err != nil { + return nil, err + } + + return sim, nil +} + +func (nd *Node) ListSims(ctx context.Context) ([]string, error) { + prefix := simulationPrefix.ChildString("head").String() + items, err := nd.MetadataDS.Query(query.Query{ + Prefix: prefix, + KeysOnly: true, + Orders: []query.Order{query.OrderByKey{}}, + }) + if err != nil { + return nil, xerrors.Errorf("failed to list simulations: %w", err) + } + defer items.Close() + var names []string + for { + select { + case result, ok := <-items.Next(): + if !ok { + return names, nil + } + if result.Error != nil { + return nil, xerrors.Errorf("failed to retrieve next simulation: %w", result.Error) + } + names = append(names, strings.TrimPrefix(result.Key, prefix+"/")) + case <-ctx.Done(): + return nil, ctx.Err() + } + } +} + +func (nd *Node) DeleteSim(ctx context.Context, name string) error { + // TODO: make this a bit more generic? + keys := []datastore.Key{ + simulationPrefix.ChildString("head").ChildString(name), + simulationPrefix.ChildString("config").ChildString(name), + } + var err error + for _, key := range keys { + err = multierr.Append(err, nd.MetadataDS.Delete(key)) + } + return err +} diff --git a/cmd/lotus-sim/simulation/power.go b/cmd/lotus-sim/simulation/power.go new file mode 100644 index 000000000..9a64c3f3a --- /dev/null +++ b/cmd/lotus-sim/simulation/power.go @@ -0,0 +1,58 @@ +package simulation + +import ( + "context" + + "golang.org/x/xerrors" + + "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/lotus/chain/actors/builtin/power" +) + +type powerInfo struct { + powerLookback, powerNow abi.StoragePower +} + +// Load all power claims at the given height. +func (sim *Simulation) loadClaims(ctx context.Context, height abi.ChainEpoch) (map[address.Address]power.Claim, error) { + powerTable := make(map[address.Address]power.Claim) + store := sim.Chainstore.ActorStore(ctx) + + ts, err := sim.Chainstore.GetTipsetByHeight(ctx, height, sim.head, true) + if err != nil { + return nil, xerrors.Errorf("when projecting growth, failed to lookup lookback epoch: %w", err) + } + + powerActor, err := sim.sm.LoadActor(ctx, power.Address, ts) + if err != nil { + return nil, err + } + + powerState, err := power.Load(store, powerActor) + if err != nil { + return nil, err + } + err = powerState.ForEachClaim(func(miner address.Address, claim power.Claim) error { + powerTable[miner] = claim + return nil + }) + if err != nil { + return nil, err + } + return powerTable, nil +} + +// Compute the number of sectors a miner has from their power claim. +func sectorsFromClaim(sectorSize abi.SectorSize, c power.Claim) int64 { + if c.RawBytePower.Int == nil { + return 0 + } + sectorCount := big.Div(c.RawBytePower, big.NewIntUnsigned(uint64(sectorSize))) + if !sectorCount.IsInt64() { + panic("impossible number of sectors") + } + return sectorCount.Int64() +} diff --git a/cmd/lotus-sim/simulation/precommit.go b/cmd/lotus-sim/simulation/precommit.go new file mode 100644 index 000000000..1ede3d5c4 --- /dev/null +++ b/cmd/lotus-sim/simulation/precommit.go @@ -0,0 +1,205 @@ +package simulation + +import ( + "context" + "fmt" + + "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" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/types" + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + tutils "github.com/filecoin-project/specs-actors/v5/support/testing" +) + +func makeCommR(minerAddr address.Address, sno abi.SectorNumber) cid.Cid { + return tutils.MakeCID(fmt.Sprintf("%s:%d", minerAddr, sno), &miner5.SealedCIDPrefix) +} + +var ( + targetFunds = abi.TokenAmount(types.MustParseFIL("1000FIL")) + minFunds = abi.TokenAmount(types.MustParseFIL("100FIL")) +) + +func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (full bool, _err error) { + var top1Count, top10Count, restCount int + defer func() { + if _err != nil { + return + } + log.Debugw("packed pre commits", + "done", top1Count+top10Count+restCount, + "top1", top1Count, + "top10", top10Count, + "rest", restCount, + "filled-block", full, + ) + }() + + var top1Miners, top10Miners, restMiners int + for i := 0; ; i++ { + var ( + minerAddr address.Address + count *int + ) + switch { + case (i%3) <= 0 && top1Miners < ss.minerDist.top1.len(): + count = &top1Count + minerAddr = ss.minerDist.top1.next() + top1Miners++ + case (i%3) <= 1 && top10Miners < ss.minerDist.top10.len(): + count = &top10Count + minerAddr = ss.minerDist.top10.next() + top10Miners++ + case (i%3) <= 2 && restMiners < ss.minerDist.rest.len(): + count = &restCount + minerAddr = ss.minerDist.rest.next() + restMiners++ + default: + // Well, we've run through all miners. + return false, nil + } + added, full, err := ss.packPreCommitsMiner(ctx, cb, minerAddr, maxProveCommitBatchSize) + if err != nil { + return false, xerrors.Errorf("failed to pack precommits for miner %s: %w", minerAddr, err) + } + *count += added + if full { + return true, nil + } + } +} + +func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, minerAddr address.Address, count int) (int, bool, error) { + epoch := ss.nextEpoch() + nv := ss.sm.GetNtwkVersion(ctx, epoch) + st, err := ss.stateTree(ctx) + if err != nil { + return 0, false, err + } + actor, err := st.GetActor(minerAddr) + if err != nil { + return 0, false, err + } + minerState, err := miner.Load(ss.Chainstore.ActorStore(ctx), actor) + if err != nil { + return 0, false, err + } + + minerInfo, err := ss.getMinerInfo(ctx, minerAddr) + if err != nil { + return 0, false, err + } + + // Make sure the miner is funded. + minerBalance, err := minerState.AvailableBalance(actor.Balance) + if err != nil { + return 0, false, err + } + + if big.Cmp(minerBalance, minFunds) < 0 { + full, err := cb(&types.Message{ + From: builtin.BurntFundsActorAddr, + To: minerAddr, + Value: targetFunds, + Method: builtin.MethodSend, + }) + if err != nil { + return 0, false, xerrors.Errorf("failed to fund miner %s: %w", minerAddr, err) + } + if full { + return 0, true, nil + } + } + + sealType, err := miner.PreferredSealProofTypeFromWindowPoStType( + nv, minerInfo.WindowPoStProofType, + ) + if err != nil { + return 0, false, err + } + + sectorNos, err := minerState.UnallocatedSectorNumbers(count) + if err != nil { + return 0, false, err + } + + expiration := epoch + policy.GetMaxSectorExpirationExtension() + infos := make([]miner.SectorPreCommitInfo, len(sectorNos)) + for i, sno := range sectorNos { + infos[i] = miner.SectorPreCommitInfo{ + SealProof: sealType, + SectorNumber: sno, + SealedCID: makeCommR(minerAddr, sno), + SealRandEpoch: epoch - 1, + Expiration: expiration, + } + } + added := 0 + if nv >= network.Version13 { + targetBatchSize := maxPreCommitBatchSize + for targetBatchSize >= minPreCommitBatchSize && len(infos) >= minPreCommitBatchSize { + batch := infos + if len(batch) > targetBatchSize { + batch = batch[:targetBatchSize] + } + params := miner5.PreCommitSectorBatchParams{ + Sectors: batch, + } + enc, err := actors.SerializeParams(¶ms) + if err != nil { + return 0, false, err + } + if full, err := sendAndFund(cb, &types.Message{ + To: minerAddr, + From: minerInfo.Worker, + Value: abi.NewTokenAmount(0), + Method: miner.Methods.PreCommitSectorBatch, + Params: enc, + }); err != nil { + return 0, false, err + } else if full { + // try again with a smaller batch. + targetBatchSize /= 2 + continue + } + + for _, info := range batch { + if err := ss.commitQueue.enqueueProveCommit(minerAddr, epoch, info); err != nil { + return 0, false, err + } + added++ + } + infos = infos[len(batch):] + } + } + for _, info := range infos { + enc, err := actors.SerializeParams(&info) + if err != nil { + return 0, false, err + } + if full, err := sendAndFund(cb, &types.Message{ + To: minerAddr, + From: minerInfo.Worker, + Value: abi.NewTokenAmount(0), + Method: miner.Methods.PreCommitSector, + Params: enc, + }); full || err != nil { + return added, full, err + } + + if err := ss.commitQueue.enqueueProveCommit(minerAddr, epoch, info); err != nil { + return 0, false, err + } + added++ + } + return added, false, nil +} diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/provecommit.go new file mode 100644 index 000000000..0d855bcd1 --- /dev/null +++ b/cmd/lotus-sim/simulation/provecommit.go @@ -0,0 +1,219 @@ +package simulation + +import ( + "context" + + "github.com/filecoin-project/go-bitfield" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/exitcode" + "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/aerrors" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/types" + + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + power5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/power" +) + +func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_full bool, _err error) { + ss.commitQueue.advanceEpoch(ss.nextEpoch()) + + var failed, done, unbatched, count int + defer func() { + if _err != nil { + return + } + remaining := ss.commitQueue.ready() + log.Debugw("packed prove commits", + "remaining", remaining, + "done", done, + "failed", failed, + "unbatched", unbatched, + "miners-processed", count, + "filled-block", _full, + ) + }() + + for { + addr, pending, ok := ss.commitQueue.nextMiner() + if !ok { + return false, nil + } + + res, full, err := ss.packProveCommitsMiner(ctx, cb, addr, pending) + if err != nil { + return false, err + } + failed += res.failed + done += res.done + unbatched += res.unbatched + count++ + if full { + return true, nil + } + } +} + +type proveCommitResult struct { + done, failed, unbatched int +} + +func sendAndFund(send packFunc, msg *types.Message) (bool, error) { + full, err := send(msg) + aerr, ok := err.(aerrors.ActorError) + if !ok || aerr.RetCode() != exitcode.ErrInsufficientFunds { + return full, err + } + // Ok, insufficient funds. Let's fund this miner and try again. + full, err = send(&types.Message{ + From: builtin.BurntFundsActorAddr, + To: msg.To, + Value: targetFunds, + Method: builtin.MethodSend, + }) + if err != nil { + return false, xerrors.Errorf("failed to fund %s: %w", msg.To, err) + } + // ok, nothing's going to work. + if full { + return true, nil + } + return send(msg) +} + +// Enqueue a single prove commit from the given miner. +func (ss *simulationState) packProveCommitsMiner( + ctx context.Context, cb packFunc, minerAddr address.Address, + pending minerPendingCommits, +) (res proveCommitResult, full bool, _err error) { + info, err := ss.getMinerInfo(ctx, minerAddr) + if err != nil { + return res, false, err + } + + nv := ss.sm.GetNtwkVersion(ctx, ss.nextEpoch()) + for sealType, snos := range pending { + if nv >= network.Version13 { + for len(snos) > minProveCommitBatchSize { + batchSize := maxProveCommitBatchSize + if len(snos) < batchSize { + batchSize = len(snos) + } + batch := snos[:batchSize] + snos = snos[batchSize:] + + proof, err := mockAggregateSealProof(sealType, minerAddr, batchSize) + if err != nil { + return res, false, err + } + + params := miner5.ProveCommitAggregateParams{ + SectorNumbers: bitfield.New(), + AggregateProof: proof, + } + for _, sno := range batch { + params.SectorNumbers.Set(uint64(sno)) + } + + enc, err := actors.SerializeParams(¶ms) + if err != nil { + return res, false, err + } + + if full, err := sendAndFund(cb, &types.Message{ + From: info.Worker, + To: minerAddr, + Value: abi.NewTokenAmount(0), + Method: miner.Methods.ProveCommitAggregate, + Params: enc, + }); err != nil { + // If we get a random error, or a fatal actor error, bail. + // Otherwise, just log it. + if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() { + return res, false, err + } + log.Errorw("failed to prove commit sector(s)", + "error", err, + "miner", minerAddr, + "sectors", batch, + "epoch", ss.nextEpoch(), + ) + res.failed += batchSize + } else if full { + return res, true, nil + } else { + res.done += batchSize + } + pending.finish(sealType, batchSize) + } + } + for len(snos) > 0 && res.unbatched < power5.MaxMinerProveCommitsPerEpoch { + sno := snos[0] + snos = snos[1:] + + proof, err := mockSealProof(sealType, minerAddr) + if err != nil { + return res, false, err + } + params := miner.ProveCommitSectorParams{ + SectorNumber: sno, + Proof: proof, + } + enc, err := actors.SerializeParams(¶ms) + if err != nil { + return res, false, err + } + if full, err := sendAndFund(cb, &types.Message{ + From: info.Worker, + To: minerAddr, + Value: abi.NewTokenAmount(0), + Method: miner.Methods.ProveCommitSector, + Params: enc, + }); err != nil { + if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() { + return res, false, err + } + log.Errorw("failed to prove commit sector(s)", + "error", err, + "miner", minerAddr, + "sectors", []abi.SectorNumber{sno}, + "epoch", ss.nextEpoch(), + ) + res.failed++ + } else if full { + return res, true, nil + } else { + res.unbatched++ + res.done++ + } + // mark it as "finished" regardless so we skip it. + pending.finish(sealType, 1) + } + // if we get here, we can't pre-commit anything more. + } + return res, false, nil +} + +// Enqueue all pending prove-commits for the given miner. +func (ss *simulationState) loadProveCommitsMiner(ctx context.Context, addr address.Address, minerState miner.State) error { + // Find all pending prove commits and group by proof type. Really, there should never + // (except during upgrades be more than one type. + nextEpoch := ss.nextEpoch() + nv := ss.sm.GetNtwkVersion(ctx, nextEpoch) + av := actors.VersionForNetwork(nv) + + return minerState.ForEachPrecommittedSector(func(info miner.SectorPreCommitOnChainInfo) error { + msd := policy.GetMaxProveCommitDuration(av, info.Info.SealProof) + if nextEpoch > info.PreCommitEpoch+msd { + log.Warnw("dropping old pre-commit") + return nil + } + return ss.commitQueue.enqueueProveCommit(addr, info.PreCommitEpoch, info.Info) + }) +} diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go new file mode 100644 index 000000000..ac205e1c3 --- /dev/null +++ b/cmd/lotus-sim/simulation/simulation.go @@ -0,0 +1,274 @@ +package simulation + +import ( + "context" + "crypto/sha256" + "encoding/binary" + "encoding/json" + "time" + + "golang.org/x/xerrors" + + "github.com/ipfs/go-datastore" + logging "github.com/ipfs/go-log/v2" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" + + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" +) + +var log = logging.Logger("simulation") + +const onboardingProjectionLookback = 2 * 7 * builtin.EpochsInDay // lookback two weeks + +const ( + minPreCommitBatchSize = 1 + maxPreCommitBatchSize = miner5.PreCommitSectorBatchMaxSize + minProveCommitBatchSize = 4 + maxProveCommitBatchSize = miner5.MaxAggregatedSectors +) + +type config struct { + Upgrades map[network.Version]abi.ChainEpoch +} + +func (c *config) upgradeSchedule() (stmgr.UpgradeSchedule, error) { + upgradeSchedule := stmgr.DefaultUpgradeSchedule() + expected := make(map[network.Version]struct{}, len(c.Upgrades)) + for nv := range c.Upgrades { + expected[nv] = struct{}{} + } + newUpgradeSchedule := upgradeSchedule[:0] + for _, upgrade := range upgradeSchedule { + if height, ok := c.Upgrades[upgrade.Network]; ok { + delete(expected, upgrade.Network) + if height < 0 { + continue + } + upgrade.Height = height + } + newUpgradeSchedule = append(newUpgradeSchedule, upgrade) + } + if len(expected) > 0 { + missing := make([]network.Version, 0, len(expected)) + for nv := range expected { + missing = append(missing, nv) + } + return nil, xerrors.Errorf("unknown network versions %v in config", missing) + } + return newUpgradeSchedule, nil +} + +type Simulation struct { + *Node + + name string + config config + sm *stmgr.StateManager + + // head + st *state.StateTree + head *types.TipSet + + // lazy-loaded state + // access through `simState(ctx)` to load on-demand. + state *simulationState +} + +func (sim *Simulation) loadConfig() error { + configBytes, err := sim.MetadataDS.Get(sim.key("config")) + if err == nil { + err = json.Unmarshal(configBytes, &sim.config) + } + switch err { + case nil: + case datastore.ErrNotFound: + sim.config = config{} + default: + return xerrors.Errorf("failed to load config: %w", err) + } + return nil +} + +func (sim *Simulation) stateTree(ctx context.Context) (*state.StateTree, error) { + if sim.st == nil { + st, _, err := sim.sm.TipSetState(ctx, sim.head) + if err != nil { + return nil, err + } + sim.st, err = sim.sm.StateTree(st) + if err != nil { + return nil, err + } + } + return sim.st, nil +} + +// Loads the simulation state. The state is memoized so this will be fast except the first time. +func (sim *Simulation) simState(ctx context.Context) (*simulationState, error) { + if sim.state == nil { + log.Infow("loading simulation") + state, err := loadSimulationState(ctx, sim) + if err != nil { + return nil, xerrors.Errorf("failed to load simulation state: %w", err) + } + sim.state = state + log.Infow("simulation loaded", "miners", len(sim.state.minerInfos)) + } + + return sim.state, nil +} + +var simulationPrefix = datastore.NewKey("/simulation") + +func (sim *Simulation) key(subkey string) datastore.Key { + return simulationPrefix.ChildString(subkey).ChildString(sim.name) +} + +// Load loads the simulation state. This will happen automatically on first use, but it can be +// useful to preload for timing reasons. +func (sim *Simulation) Load(ctx context.Context) error { + _, err := sim.simState(ctx) + return err +} + +func (sim *Simulation) GetHead() *types.TipSet { + return sim.head +} + +func (sim *Simulation) SetHead(head *types.TipSet) error { + if err := sim.MetadataDS.Put(sim.key("head"), head.Key().Bytes()); err != nil { + return xerrors.Errorf("failed to store simulation head: %w", err) + } + sim.st = nil // we'll compute this on-demand. + sim.head = head + return nil +} + +func (sim *Simulation) Name() string { + return sim.name +} + +func (sim *Simulation) postChainCommitInfo(ctx context.Context, epoch abi.ChainEpoch) (abi.Randomness, error) { + commitRand, err := sim.Chainstore.GetChainRandomness( + ctx, sim.head.Cids(), crypto.DomainSeparationTag_PoStChainCommit, epoch, nil, true) + return commitRand, err +} + +const beaconPrefix = "mockbeacon:" + +func (sim *Simulation) nextBeaconEntries() []types.BeaconEntry { + parentBeacons := sim.head.Blocks()[0].BeaconEntries + lastBeacon := parentBeacons[len(parentBeacons)-1] + beaconRound := lastBeacon.Round + 1 + + buf := make([]byte, len(beaconPrefix)+8) + copy(buf, beaconPrefix) + binary.BigEndian.PutUint64(buf[len(beaconPrefix):], beaconRound) + beaconRand := sha256.Sum256(buf) + return []types.BeaconEntry{{ + Round: beaconRound, + Data: beaconRand[:], + }} +} + +func (sim *Simulation) nextTicket() *types.Ticket { + newProof := sha256.Sum256(sim.head.MinTicket().VRFProof) + return &types.Ticket{ + VRFProof: newProof[:], + } +} + +func (sim *Simulation) makeTipSet(ctx context.Context, messages []*types.Message) (*types.TipSet, error) { + parentTs := sim.head + parentState, parentRec, err := sim.sm.TipSetState(ctx, parentTs) + if err != nil { + return nil, xerrors.Errorf("failed to compute parent tipset: %w", err) + } + msgsCid, err := sim.storeMessages(ctx, messages) + if err != nil { + return nil, xerrors.Errorf("failed to store block messages: %w", err) + } + + uts := parentTs.MinTimestamp() + build.BlockDelaySecs + + blks := []*types.BlockHeader{{ + Miner: parentTs.MinTicketBlock().Miner, // keep reusing the same miner. + Ticket: sim.nextTicket(), + BeaconEntries: sim.nextBeaconEntries(), + Parents: parentTs.Cids(), + Height: parentTs.Height() + 1, + ParentStateRoot: parentState, + ParentMessageReceipts: parentRec, + Messages: msgsCid, + ParentBaseFee: baseFee, + Timestamp: uts, + ElectionProof: &types.ElectionProof{WinCount: 1}, + }} + err = sim.Chainstore.PersistBlockHeaders(blks...) + if err != nil { + return nil, xerrors.Errorf("failed to persist block headers: %w", err) + } + newTipSet, err := types.NewTipSet(blks) + if err != nil { + return nil, xerrors.Errorf("failed to create new tipset: %w", err) + } + now := time.Now() + _, _, err = sim.sm.TipSetState(ctx, newTipSet) + if err != nil { + return nil, xerrors.Errorf("failed to compute new tipset: %w", err) + } + duration := time.Since(now) + log.Infow("computed tipset", "duration", duration, "height", newTipSet.Height()) + + return newTipSet, nil +} + +func (sim *Simulation) SetUpgradeHeight(nv network.Version, epoch abi.ChainEpoch) (_err error) { + if epoch <= sim.head.Height() { + return xerrors.Errorf("cannot set upgrade height in the past (%d <= %d)", epoch, sim.head.Height()) + } + + if sim.config.Upgrades == nil { + sim.config.Upgrades = make(map[network.Version]abi.ChainEpoch, 1) + } + + sim.config.Upgrades[nv] = epoch + defer func() { + if _err != nil { + // try to restore the old config on error. + _ = sim.loadConfig() + } + }() + + newUpgradeSchedule, err := sim.config.upgradeSchedule() + if err != nil { + return err + } + sm, err := stmgr.NewStateManagerWithUpgradeSchedule(sim.Chainstore, newUpgradeSchedule) + if err != nil { + return err + } + err = sim.saveConfig() + if err != nil { + return err + } + + sim.sm = sm + return nil +} + +func (sim *Simulation) saveConfig() error { + buf, err := json.Marshal(sim.config) + if err != nil { + return err + } + return sim.MetadataDS.Put(sim.key("config"), buf) +} diff --git a/cmd/lotus-sim/simulation/state.go b/cmd/lotus-sim/simulation/state.go new file mode 100644 index 000000000..ee664166e --- /dev/null +++ b/cmd/lotus-sim/simulation/state.go @@ -0,0 +1,190 @@ +package simulation + +import ( + "context" + "math/rand" + "sort" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/types" +) + +type perm struct { + miners []address.Address + offset int +} + +func (p *perm) shuffle() { + rand.Shuffle(len(p.miners), func(i, j int) { + p.miners[i], p.miners[j] = p.miners[j], p.miners[i] + }) +} + +func (p *perm) next() address.Address { + next := p.miners[p.offset] + p.offset++ + p.offset %= len(p.miners) + return next +} + +func (p *perm) add(addr address.Address) { + p.miners = append(p.miners, addr) +} + +func (p *perm) len() int { + return len(p.miners) +} + +type simulationState struct { + *Simulation + + // TODO Ideally we'd "learn" this distribution from the network. But this is good enough for + // now. The tiers represent the top 1%, top 10%, and everyone else. When sealing sectors, we + // seal a group of sectors for the top 1%, a group (half that size) for the top 10%, and one + // sector for everyone else. We really should pick a better algorithm. + minerDist struct { + top1, top10, rest perm + } + + // We track the window post periods per miner and assume that no new miners are ever added. + wpostPeriods map[int][]address.Address // (epoch % (epochs in a deadline)) -> miner + // We cache all miner infos for active miners and assume no new miners join. + minerInfos map[address.Address]*miner.MinerInfo + + // We record all pending window post messages, and the epoch up through which we've + // generated window post messages. + pendingWposts []*types.Message + nextWpostEpoch abi.ChainEpoch + + commitQueue commitQueue +} + +func loadSimulationState(ctx context.Context, sim *Simulation) (*simulationState, error) { + state := &simulationState{Simulation: sim} + currentEpoch := sim.head.Height() + + // Lookup the current power table and the power table 2 weeks ago (for onboarding rate + // projections). + currentPowerTable, err := sim.loadClaims(ctx, currentEpoch) + if err != nil { + return nil, err + } + + var lookbackEpoch abi.ChainEpoch + //if epoch > onboardingProjectionLookback { + // lookbackEpoch = epoch - onboardingProjectionLookback + //} + // TODO: Fixme? I really want this to not suck with snapshots. + lookbackEpoch = 770139 // hard coded for now. + lookbackPowerTable, err := sim.loadClaims(ctx, lookbackEpoch) + if err != nil { + return nil, err + } + + // Now load miner state info. + store := sim.Chainstore.ActorStore(ctx) + st, err := sim.stateTree(ctx) + if err != nil { + return nil, err + } + + type onboardingInfo struct { + addr address.Address + onboardingRate uint64 + } + + commitRand, err := sim.postChainCommitInfo(ctx, currentEpoch) + if err != nil { + return nil, err + } + + sealList := make([]onboardingInfo, 0, len(currentPowerTable)) + state.wpostPeriods = make(map[int][]address.Address, miner.WPoStChallengeWindow) + state.minerInfos = make(map[address.Address]*miner.MinerInfo, len(currentPowerTable)) + state.commitQueue.advanceEpoch(state.nextEpoch()) + for addr, claim := range currentPowerTable { + // Load the miner state. + minerActor, err := st.GetActor(addr) + if err != nil { + return nil, err + } + + minerState, err := miner.Load(store, minerActor) + if err != nil { + return nil, err + } + + info, err := minerState.Info() + if err != nil { + return nil, err + } + state.minerInfos[addr] = &info + + // Queue up PoSts + err = state.stepWindowPoStsMiner(ctx, addr, minerState, currentEpoch, commitRand) + if err != nil { + return nil, err + } + + // Qeueu up any pending prove commits. + err = state.loadProveCommitsMiner(ctx, addr, minerState) + if err != nil { + return nil, err + } + + // Record when we need to prove for this miner. + dinfo, err := minerState.DeadlineInfo(state.nextEpoch()) + if err != nil { + return nil, err + } + dinfo = dinfo.NextNotElapsed() + + ppOffset := int(dinfo.PeriodStart % miner.WPoStChallengeWindow) + state.wpostPeriods[ppOffset] = append(state.wpostPeriods[ppOffset], addr) + + sectorsAdded := sectorsFromClaim(info.SectorSize, claim) + if lookbackClaim, ok := lookbackPowerTable[addr]; !ok { + sectorsAdded -= sectorsFromClaim(info.SectorSize, lookbackClaim) + } + + // NOTE: power _could_ have been lost, but that's too much of a pain to care + // about. We _could_ look for faulty power by iterating through all + // deadlines, but I'd rather not. + if sectorsAdded > 0 { + sealList = append(sealList, onboardingInfo{addr, uint64(sectorsAdded)}) + } + } + // We're already done loading for the _next_ epoch. + // Next time, we need to load for the next, next epoch. + // TODO: fix this insanity. + state.nextWpostEpoch = state.nextEpoch() + 1 + + // Now that we have a list of sealing miners, sort them into percentiles. + sort.Slice(sealList, func(i, j int) bool { + return sealList[i].onboardingRate < sealList[j].onboardingRate + }) + + for i, oi := range sealList { + var dist *perm + if i < len(sealList)/100 { + dist = &state.minerDist.top1 + } else if i < len(sealList)/10 { + dist = &state.minerDist.top10 + } else { + dist = &state.minerDist.rest + } + dist.add(oi.addr) + } + + state.minerDist.top1.shuffle() + state.minerDist.top10.shuffle() + state.minerDist.rest.shuffle() + + return state, nil +} + +func (ss *simulationState) nextEpoch() abi.ChainEpoch { + return ss.GetHead().Height() + 1 +} diff --git a/cmd/lotus-sim/simulation/step.go b/cmd/lotus-sim/simulation/step.go new file mode 100644 index 000000000..b44f3be4d --- /dev/null +++ b/cmd/lotus-sim/simulation/step.go @@ -0,0 +1,196 @@ +package simulation + +import ( + "context" + "reflect" + "runtime" + "strings" + + "github.com/filecoin-project/go-address" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/builtin/account" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" +) + +const ( + expectedBlocks = 5 + // TODO: This will produce invalid blocks but it will accurately model the amount of gas + // we're willing to use per-tipset. + // A more correct approach would be to produce 5 blocks. We can do that later. + targetGas = build.BlockGasTarget * expectedBlocks +) + +var baseFee = abi.NewTokenAmount(0) + +// Step steps the simulation forward one step. This may move forward by more than one epoch. +func (sim *Simulation) Step(ctx context.Context) (*types.TipSet, error) { + state, err := sim.simState(ctx) + if err != nil { + return nil, err + } + ts, err := state.step(ctx) + if err != nil { + return nil, xerrors.Errorf("failed to step simulation: %w", err) + } + return ts, nil +} + +func (ss *simulationState) step(ctx context.Context) (*types.TipSet, error) { + log.Infow("step", "epoch", ss.head.Height()+1) + messages, err := ss.popNextMessages(ctx) + if err != nil { + return nil, xerrors.Errorf("failed to select messages for block: %w", err) + } + head, err := ss.makeTipSet(ctx, messages) + if err != nil { + return nil, xerrors.Errorf("failed to make tipset: %w", err) + } + if err := ss.SetHead(head); err != nil { + return nil, xerrors.Errorf("failed to update head: %w", err) + } + return head, nil +} + +type packFunc func(*types.Message) (full bool, err error) +type messageGenerator func(ctx context.Context, cb packFunc) (full bool, err error) + +func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Message, error) { + parentTs := ss.head + parentState, _, err := ss.sm.TipSetState(ctx, parentTs) + if err != nil { + return nil, err + } + nextHeight := parentTs.Height() + 1 + prevVer := ss.sm.GetNtwkVersion(ctx, nextHeight-1) + nextVer := ss.sm.GetNtwkVersion(ctx, nextHeight) + if nextVer != prevVer { + // So... we _could_ actually run the migration, but that's a pain. It's easier to + // just have an empty block then let the state manager run the migration as normal. + log.Warnw("packing no messages for version upgrade block", + "old", prevVer, + "new", nextVer, + "epoch", nextHeight, + ) + return nil, nil + } + + // Then we need to execute messages till we run out of gas. Those messages will become the + // block's messages. + r := store.NewChainRand(ss.sm.ChainStore(), parentTs.Cids()) + // TODO: Factor this out maybe? + vmopt := &vm.VMOpts{ + StateBase: parentState, + Epoch: nextHeight, + Rand: r, + Bstore: ss.sm.ChainStore().StateBlockstore(), + Syscalls: ss.sm.ChainStore().VMSys(), + CircSupplyCalc: ss.sm.GetVMCirculatingSupply, + NtwkVersion: ss.sm.GetNtwkVersion, + BaseFee: abi.NewTokenAmount(0), // FREE! + LookbackState: stmgr.LookbackStateGetterForTipset(ss.sm, parentTs), + } + vmi, err := vm.NewVM(ctx, vmopt) + if err != nil { + return nil, err + } + // TODO: This is the wrong store and may not include important state for what we're doing + // here.... + // Maybe we just track nonces separately? Yeah, probably better that way. + vmStore := vmi.ActorStore(ctx) + var gasTotal int64 + var messages []*types.Message + tryPushMsg := func(msg *types.Message) (bool, error) { + if gasTotal >= targetGas { + return true, nil + } + + // Copy the message before we start mutating it. + msgCpy := *msg + msg = &msgCpy + st := vmi.StateTree().(*state.StateTree) + + actor, err := st.GetActor(msg.From) + if err != nil { + return false, err + } + msg.Nonce = actor.Nonce + if msg.From.Protocol() == address.ID { + state, err := account.Load(vmStore, actor) + if err != nil { + return false, err + } + msg.From, err = state.PubkeyAddress() + if err != nil { + return false, err + } + } + + // TODO: Our gas estimation is broken for payment channels due to horrible hacks in + // gasEstimateGasLimit. + if msg.Value == types.EmptyInt { + msg.Value = abi.NewTokenAmount(0) + } + msg.GasPremium = abi.NewTokenAmount(0) + msg.GasFeeCap = abi.NewTokenAmount(0) + msg.GasLimit = build.BlockGasLimit + + // We manually snapshot so we can revert nonce changes, etc. on failure. + st.Snapshot(ctx) + defer st.ClearSnapshot() + + ret, err := vmi.ApplyMessage(ctx, msg) + if err != nil { + _ = st.Revert() + return false, err + } + if ret.ActorErr != nil { + _ = st.Revert() + return false, ret.ActorErr + } + + // Sometimes there are bugs. Let's catch them. + if ret.GasUsed == 0 { + _ = st.Revert() + return false, xerrors.Errorf("used no gas", + "msg", msg, + "ret", ret, + ) + } + + // TODO: consider applying overestimation? We're likely going to "over pack" here by + // ~25% because we're too accurate. + + // Did we go over? Yes, revert. + newTotal := gasTotal + ret.GasUsed + if newTotal > targetGas { + _ = st.Revert() + return true, nil + } + gasTotal = newTotal + + // Update the gas limit. + msg.GasLimit = ret.GasUsed + + messages = append(messages, msg) + return false, nil + } + for _, mgen := range []messageGenerator{ss.packWindowPoSts, ss.packProveCommits, ss.packPreCommits} { + if full, err := mgen(ctx, tryPushMsg); err != nil { + name := runtime.FuncForPC(reflect.ValueOf(mgen).Pointer()).Name() + lastDot := strings.LastIndexByte(name, '.') + fName := name[lastDot+1 : len(name)-3] + return nil, xerrors.Errorf("when packing messages with %s: %w", fName, err) + } else if full { + break + } + } + + return messages, nil +} diff --git a/cmd/lotus-sim/simulation/wdpost.go b/cmd/lotus-sim/simulation/wdpost.go new file mode 100644 index 000000000..7abb9a83a --- /dev/null +++ b/cmd/lotus-sim/simulation/wdpost.go @@ -0,0 +1,253 @@ +package simulation + +import ( + "context" + "math" + "time" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/aerrors" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/types" + proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" + "golang.org/x/xerrors" +) + +func (ss *simulationState) getMinerInfo(ctx context.Context, addr address.Address) (*miner.MinerInfo, error) { + minerInfo, ok := ss.minerInfos[addr] + if !ok { + st, err := ss.stateTree(ctx) + if err != nil { + return nil, err + } + act, err := st.GetActor(addr) + if err != nil { + return nil, err + } + minerState, err := miner.Load(ss.Chainstore.ActorStore(ctx), act) + if err != nil { + return nil, err + } + info, err := minerState.Info() + if err != nil { + return nil, err + } + minerInfo = &info + ss.minerInfos[addr] = minerInfo + } + return minerInfo, nil +} + +func (ss *simulationState) packWindowPoSts(ctx context.Context, cb packFunc) (full bool, _err error) { + // Push any new window posts into the queue. + if err := ss.queueWindowPoSts(ctx); err != nil { + return false, err + } + done := 0 + failed := 0 + defer func() { + if _err != nil { + return + } + + log.Debugw("packed window posts", + "epoch", ss.nextEpoch(), + "done", done, + "failed", failed, + "remaining", len(ss.pendingWposts), + ) + }() + // Then pack as many as we can. + for len(ss.pendingWposts) > 0 { + next := ss.pendingWposts[0] + if full, err := cb(next); err != nil { + if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() { + return false, err + } + log.Errorw("failed to submit windowed post", + "error", err, + "miner", next.To, + "epoch", ss.nextEpoch(), + ) + failed++ + } else if full { + return true, nil + } else { + done++ + } + + ss.pendingWposts = ss.pendingWposts[1:] + } + ss.pendingWposts = nil + return false, nil +} + +// Enqueue all missing window posts for the current epoch for the given miner. +func (ss *simulationState) stepWindowPoStsMiner( + ctx context.Context, + addr address.Address, minerState miner.State, + commitEpoch abi.ChainEpoch, commitRand abi.Randomness, +) error { + + if active, err := minerState.DeadlineCronActive(); err != nil { + return err + } else if !active { + return nil + } + + minerInfo, err := ss.getMinerInfo(ctx, addr) + if err != nil { + return err + } + + di, err := minerState.DeadlineInfo(ss.nextEpoch()) + if err != nil { + return err + } + di = di.NextNotElapsed() + + dl, err := minerState.LoadDeadline(di.Index) + if err != nil { + return err + } + + provenBf, err := dl.PartitionsPoSted() + if err != nil { + return err + } + proven, err := provenBf.AllMap(math.MaxUint64) + if err != nil { + return err + } + + var ( + partitions []miner.PoStPartition + partitionGroups [][]miner.PoStPartition + ) + // Only prove partitions with live sectors. + err = dl.ForEachPartition(func(idx uint64, part miner.Partition) error { + if proven[idx] { + return nil + } + // TODO: set this to the actual limit from specs-actors. + // NOTE: We're mimicing the behavior of wdpost_run.go here. + if len(partitions) > 0 && idx%4 == 0 { + partitionGroups = append(partitionGroups, partitions) + partitions = nil + + } + live, err := part.LiveSectors() + if err != nil { + return err + } + liveCount, err := live.Count() + if err != nil { + return err + } + faulty, err := part.FaultySectors() + if err != nil { + return err + } + faultyCount, err := faulty.Count() + if err != nil { + return err + } + if liveCount-faultyCount > 0 { + partitions = append(partitions, miner.PoStPartition{Index: idx}) + } + return nil + }) + if err != nil { + return err + } + if len(partitions) > 0 { + partitionGroups = append(partitionGroups, partitions) + partitions = nil + } + + proof, err := mockWpostProof(minerInfo.WindowPoStProofType, addr) + if err != nil { + return err + } + for _, group := range partitionGroups { + params := miner.SubmitWindowedPoStParams{ + Deadline: di.Index, + Partitions: group, + Proofs: []proof5.PoStProof{{ + PoStProof: minerInfo.WindowPoStProofType, + ProofBytes: proof, + }}, + ChainCommitEpoch: commitEpoch, + ChainCommitRand: commitRand, + } + enc, aerr := actors.SerializeParams(¶ms) + if aerr != nil { + return xerrors.Errorf("could not serialize submit window post parameters: %w", aerr) + } + msg := &types.Message{ + To: addr, + From: minerInfo.Worker, + Method: miner.Methods.SubmitWindowedPoSt, + Params: enc, + Value: types.NewInt(0), + } + ss.pendingWposts = append(ss.pendingWposts, msg) + } + return nil +} + +// Enqueue missing window posts for all miners with deadlines opening at the current epoch. +func (ss *simulationState) queueWindowPoSts(ctx context.Context) error { + targetHeight := ss.nextEpoch() + + st, err := ss.stateTree(ctx) + if err != nil { + return err + } + + now := time.Now() + was := len(ss.pendingWposts) + count := 0 + defer func() { + log.Debugw("computed window posts", + "miners", count, + "count", len(ss.pendingWposts)-was, + "duration", time.Since(now), + ) + }() + + // Perform a bit of catch up. This lets us do things like skip blocks at upgrades then catch + // up to make the simualtion easier. + for ; ss.nextWpostEpoch <= targetHeight; ss.nextWpostEpoch++ { + if ss.nextWpostEpoch+miner.WPoStChallengeWindow < targetHeight { + log.Warnw("skipping old window post", "epoch", ss.nextWpostEpoch) + continue + } + commitEpoch := ss.nextWpostEpoch - 1 + commitRand, err := ss.postChainCommitInfo(ctx, commitEpoch) + if err != nil { + return err + } + + store := ss.Chainstore.ActorStore(ctx) + + for _, addr := range ss.wpostPeriods[int(ss.nextWpostEpoch%miner.WPoStChallengeWindow)] { + minerActor, err := st.GetActor(addr) + if err != nil { + return err + } + minerState, err := miner.Load(store, minerActor) + if err != nil { + return err + } + if err := ss.stepWindowPoStsMiner(ctx, addr, minerState, commitEpoch, commitRand); err != nil { + return err + } + count++ + } + + } + return nil +} diff --git a/cmd/lotus-sim/step.go b/cmd/lotus-sim/step.go new file mode 100644 index 000000000..c2dc3f9e2 --- /dev/null +++ b/cmd/lotus-sim/step.go @@ -0,0 +1,46 @@ +package main + +import ( + "fmt" + + "github.com/urfave/cli/v2" +) + +var stepSimCommand = &cli.Command{ + Name: "step", + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "epochs", + Usage: "Advance at least the given number of epochs.", + Value: 1, + }, + }, + Action: func(cctx *cli.Context) error { + node, err := open(cctx) + if err != nil { + return err + } + defer node.Close() + + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) + if err != nil { + return err + } + fmt.Fprintln(cctx.App.Writer, "loading simulation") + err = sim.Load(cctx.Context) + if err != nil { + return err + } + fmt.Fprintln(cctx.App.Writer, "running simulation") + targetEpochs := cctx.Int("epochs") + for i := 0; i < targetEpochs; i++ { + ts, err := sim.Step(cctx.Context) + if err != nil { + return err + } + fmt.Fprintf(cctx.App.Writer, "advanced to %d %s\n", ts.Height(), ts.Key()) + } + fmt.Fprintln(cctx.App.Writer, "simulation done") + return err + }, +} diff --git a/cmd/lotus-sim/upgrade.go b/cmd/lotus-sim/upgrade.go new file mode 100644 index 000000000..9fd25cb7d --- /dev/null +++ b/cmd/lotus-sim/upgrade.go @@ -0,0 +1,53 @@ +package main + +import ( + "fmt" + "strconv" + "strings" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/network" + "github.com/urfave/cli/v2" +) + +var setUpgradeCommand = &cli.Command{ + Name: "set-upgrade", + ArgsUsage: " [+]", + Description: "Set a network upgrade height. prefix with '+' to set it relative to the last epoch.", + Action: func(cctx *cli.Context) error { + args := cctx.Args() + if args.Len() != 2 { + return fmt.Errorf("expected 2 arguments") + } + nvString := args.Get(0) + networkVersion, err := strconv.ParseInt(nvString, 10, 64) + if err != nil { + return fmt.Errorf("failed to parse network version %q: %w", nvString, err) + } + heightString := args.Get(1) + relative := false + if strings.HasPrefix(heightString, "+") { + heightString = heightString[1:] + relative = true + } + height, err := strconv.ParseInt(heightString, 10, 64) + if err != nil { + return fmt.Errorf("failed to parse height version %q: %w", heightString, err) + } + + node, err := open(cctx) + if err != nil { + return err + } + defer node.Close() + + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) + if err != nil { + return err + } + if relative { + height += int64(sim.GetHead().Height()) + } + return sim.SetUpgradeHeight(network.Version(networkVersion), abi.ChainEpoch(height)) + }, +} diff --git a/cmd/lotus-sim/util.go b/cmd/lotus-sim/util.go new file mode 100644 index 000000000..cd15cca0d --- /dev/null +++ b/cmd/lotus-sim/util.go @@ -0,0 +1,18 @@ +package main + +import ( + "fmt" + + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation" + "github.com/filecoin-project/lotus/lib/ulimit" +) + +func open(cctx *cli.Context) (*simulation.Node, error) { + _, _, err := ulimit.ManageFdLimit() + if err != nil { + fmt.Fprintf(cctx.App.ErrWriter, "ERROR: failed to raise ulimit: %s\n", err) + } + return simulation.OpenNode(cctx.Context, cctx.String("repo")) +} From 8000decac6f499e127ff1d081dec08a5a43e9844 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 7 Jun 2021 14:54:20 -0700 Subject: [PATCH 089/257] feat(lotus-sim): add command to list pending upgrades --- cmd/lotus-sim/main.go | 2 +- cmd/lotus-sim/simulation/simulation.go | 15 ++++++++ cmd/lotus-sim/upgrade.go | 52 ++++++++++++++++++++++++-- 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/cmd/lotus-sim/main.go b/cmd/lotus-sim/main.go index 9a4d40699..53e7d660a 100644 --- a/cmd/lotus-sim/main.go +++ b/cmd/lotus-sim/main.go @@ -16,7 +16,7 @@ var root []*cli.Command = []*cli.Command{ deleteSimCommand, listSimCommand, stepSimCommand, - setUpgradeCommand, + upgradeCommand, } func main() { diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index ac205e1c3..8fffd9868 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -265,6 +265,21 @@ func (sim *Simulation) SetUpgradeHeight(nv network.Version, epoch abi.ChainEpoch return nil } +func (sim *Simulation) ListUpgrades() (stmgr.UpgradeSchedule, error) { + upgrades, err := sim.config.upgradeSchedule() + if err != nil { + return nil, err + } + var pending stmgr.UpgradeSchedule + for _, upgrade := range upgrades { + if upgrade.Height < sim.head.Height() { + continue + } + pending = append(pending, upgrade) + } + return pending, nil +} + func (sim *Simulation) saveConfig() error { buf, err := json.Marshal(sim.config) if err != nil { diff --git a/cmd/lotus-sim/upgrade.go b/cmd/lotus-sim/upgrade.go index 9fd25cb7d..17993f847 100644 --- a/cmd/lotus-sim/upgrade.go +++ b/cmd/lotus-sim/upgrade.go @@ -4,16 +4,62 @@ import ( "fmt" "strconv" "strings" + "text/tabwriter" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/network" "github.com/urfave/cli/v2" ) -var setUpgradeCommand = &cli.Command{ - Name: "set-upgrade", +var upgradeCommand = &cli.Command{ + Name: "upgrade", + Description: "Modifies network upgrade heights.", + Subcommands: []*cli.Command{ + upgradeSetCommand, + }, +} + +var upgradeList = &cli.Command{ + Name: "list", + Description: "Lists all pending upgrades.", + Subcommands: []*cli.Command{ + upgradeSetCommand, + }, + Action: func(cctx *cli.Context) error { + node, err := open(cctx) + if err != nil { + return err + } + defer node.Close() + + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) + if err != nil { + return err + } + upgrades, err := sim.ListUpgrades() + if err != nil { + return err + } + + tw := tabwriter.NewWriter(cctx.App.Writer, 8, 8, 0, ' ', 0) + fmt.Fprintf(tw, "version\theight\tepochs\tmigration\texpensive") + epoch := sim.GetHead().Height() + for _, upgrade := range upgrades { + fmt.Fprintf( + tw, "%d\t%d\t%+d\t%t\t%t", + upgrade.Network, upgrade.Height, upgrade.Height-epoch, + upgrade.Migration != nil, + upgrade.Expensive, + ) + } + return nil + }, +} + +var upgradeSetCommand = &cli.Command{ + Name: "set", ArgsUsage: " [+]", - Description: "Set a network upgrade height. prefix with '+' to set it relative to the last epoch.", + Description: "Set a network upgrade height. Prefix with '+' to set it relative to the last epoch.", Action: func(cctx *cli.Context) error { args := cctx.Args() if args.Len() != 2 { From be9e30e39d8be65a6a7b8f2319d15a9d3d05350e Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 7 Jun 2021 14:55:39 -0700 Subject: [PATCH 090/257] fix(lotus-sim): rename step to run And make it run forever by default. --- cmd/lotus-sim/main.go | 2 +- cmd/lotus-sim/{step.go => run.go} | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) rename cmd/lotus-sim/{step.go => run.go} (79%) diff --git a/cmd/lotus-sim/main.go b/cmd/lotus-sim/main.go index 53e7d660a..dfd7c92cd 100644 --- a/cmd/lotus-sim/main.go +++ b/cmd/lotus-sim/main.go @@ -15,7 +15,7 @@ var root []*cli.Command = []*cli.Command{ createSimCommand, deleteSimCommand, listSimCommand, - stepSimCommand, + runSimCommand, upgradeCommand, } diff --git a/cmd/lotus-sim/step.go b/cmd/lotus-sim/run.go similarity index 79% rename from cmd/lotus-sim/step.go rename to cmd/lotus-sim/run.go index c2dc3f9e2..479ce898d 100644 --- a/cmd/lotus-sim/step.go +++ b/cmd/lotus-sim/run.go @@ -6,13 +6,13 @@ import ( "github.com/urfave/cli/v2" ) -var stepSimCommand = &cli.Command{ - Name: "step", +var runSimCommand = &cli.Command{ + Name: "run", + Description: "Run the simulation.", Flags: []cli.Flag{ &cli.IntFlag{ Name: "epochs", - Usage: "Advance at least the given number of epochs.", - Value: 1, + Usage: "Advance the given number of epochs then stop.", }, }, Action: func(cctx *cli.Context) error { @@ -33,7 +33,7 @@ var stepSimCommand = &cli.Command{ } fmt.Fprintln(cctx.App.Writer, "running simulation") targetEpochs := cctx.Int("epochs") - for i := 0; i < targetEpochs; i++ { + for i := 0; targetEpochs == 0 || i < targetEpochs; i++ { ts, err := sim.Step(cctx.Context) if err != nil { return err From b7bfc06ebe3d1bad1c7fa3ca8a7a81b00ea956c0 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 7 Jun 2021 15:05:52 -0700 Subject: [PATCH 091/257] feat(lotus-sim): add an info command --- cmd/lotus-sim/main.go | 1 + cmd/lotus-sim/simulation/simulation.go | 4 ++++ cmd/lotus-sim/stat.go | 31 ++++++++++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 cmd/lotus-sim/stat.go diff --git a/cmd/lotus-sim/main.go b/cmd/lotus-sim/main.go index dfd7c92cd..8fe313355 100644 --- a/cmd/lotus-sim/main.go +++ b/cmd/lotus-sim/main.go @@ -16,6 +16,7 @@ var root []*cli.Command = []*cli.Command{ deleteSimCommand, listSimCommand, runSimCommand, + infoSimCommand, upgradeCommand, } diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index 8fffd9868..384cc79cb 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -143,6 +143,10 @@ func (sim *Simulation) GetHead() *types.TipSet { return sim.head } +func (sim *Simulation) GetNetworkVersion() network.Version { + return sim.sm.GetNtwkVersion(context.TODO(), sim.head.Height()) +} + func (sim *Simulation) SetHead(head *types.TipSet) error { if err := sim.MetadataDS.Put(sim.key("head"), head.Key().Bytes()); err != nil { return xerrors.Errorf("failed to store simulation head: %w", err) diff --git a/cmd/lotus-sim/stat.go b/cmd/lotus-sim/stat.go new file mode 100644 index 000000000..25f9f5d51 --- /dev/null +++ b/cmd/lotus-sim/stat.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + "text/tabwriter" + + "github.com/urfave/cli/v2" +) + +var infoSimCommand = &cli.Command{ + Name: "info", + Description: "Output information about the simulation.", + Action: func(cctx *cli.Context) error { + node, err := open(cctx) + if err != nil { + return err + } + defer node.Close() + + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) + if err != nil { + return err + } + tw := tabwriter.NewWriter(cctx.App.Writer, 8, 8, 0, ' ', 0) + fmt.Fprintln(tw, "Name:\t", sim.Name()) + fmt.Fprintln(tw, "Height:\t", sim.GetHead().Height()) + fmt.Fprintln(tw, "TipSet:\t", sim.GetHead()) + fmt.Fprintln(tw, "Network Version:\t", sim.GetNetworkVersion()) + return tw.Flush() + }, +} From 2f7d7aed31f95eff47e7e14111dd517baa6947fe Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 7 Jun 2021 17:45:53 -0700 Subject: [PATCH 092/257] feat(lotus-sim): refactor and document Hopefully, this'll make this code a bit easier to approach. --- cmd/lotus-sim/simulation/actor_iter.go | 38 ++++++ cmd/lotus-sim/simulation/block.go | 81 +++++++++++++ cmd/lotus-sim/simulation/commit_queue.go | 24 ++-- cmd/lotus-sim/simulation/messages.go | 3 + cmd/lotus-sim/simulation/mock.go | 141 ++++++++++++----------- cmd/lotus-sim/simulation/node.go | 12 ++ cmd/lotus-sim/simulation/power.go | 4 - cmd/lotus-sim/simulation/precommit.go | 26 ++++- cmd/lotus-sim/simulation/provecommit.go | 21 +++- cmd/lotus-sim/simulation/simulation.go | 129 +++++++-------------- cmd/lotus-sim/simulation/state.go | 73 +++++++----- cmd/lotus-sim/simulation/step.go | 104 +++++++++++++---- cmd/lotus-sim/simulation/wdpost.go | 41 +++---- 13 files changed, 446 insertions(+), 251 deletions(-) create mode 100644 cmd/lotus-sim/simulation/actor_iter.go create mode 100644 cmd/lotus-sim/simulation/block.go diff --git a/cmd/lotus-sim/simulation/actor_iter.go b/cmd/lotus-sim/simulation/actor_iter.go new file mode 100644 index 000000000..5df395e11 --- /dev/null +++ b/cmd/lotus-sim/simulation/actor_iter.go @@ -0,0 +1,38 @@ +package simulation + +import ( + "math/rand" + + "github.com/filecoin-project/go-address" +) + +// actorIter is a simple persistent iterator that loops over a set of actors. +type actorIter struct { + actors []address.Address + offset int +} + +// shuffle randomly permutes the set of actors. +func (p *actorIter) shuffle() { + rand.Shuffle(len(p.actors), func(i, j int) { + p.actors[i], p.actors[j] = p.actors[j], p.actors[i] + }) +} + +// next returns the next actor's address and advances the iterator. +func (p *actorIter) next() address.Address { + next := p.actors[p.offset] + p.offset++ + p.offset %= len(p.actors) + return next +} + +// add adds a new actor to the iterator. +func (p *actorIter) add(addr address.Address) { + p.actors = append(p.actors, addr) +} + +// len returns the number of actors in the iterator. +func (p *actorIter) len() int { + return len(p.actors) +} diff --git a/cmd/lotus-sim/simulation/block.go b/cmd/lotus-sim/simulation/block.go new file mode 100644 index 000000000..677ba7a2f --- /dev/null +++ b/cmd/lotus-sim/simulation/block.go @@ -0,0 +1,81 @@ +package simulation + +import ( + "context" + "crypto/sha256" + "encoding/binary" + "time" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" + "golang.org/x/xerrors" +) + +const beaconPrefix = "mockbeacon:" + +func (sim *Simulation) nextBeaconEntries() []types.BeaconEntry { + parentBeacons := sim.head.Blocks()[0].BeaconEntries + lastBeacon := parentBeacons[len(parentBeacons)-1] + beaconRound := lastBeacon.Round + 1 + + buf := make([]byte, len(beaconPrefix)+8) + copy(buf, beaconPrefix) + binary.BigEndian.PutUint64(buf[len(beaconPrefix):], beaconRound) + beaconRand := sha256.Sum256(buf) + return []types.BeaconEntry{{ + Round: beaconRound, + Data: beaconRand[:], + }} +} + +func (sim *Simulation) nextTicket() *types.Ticket { + newProof := sha256.Sum256(sim.head.MinTicket().VRFProof) + return &types.Ticket{ + VRFProof: newProof[:], + } +} + +func (sim *Simulation) makeTipSet(ctx context.Context, messages []*types.Message) (*types.TipSet, error) { + parentTs := sim.head + parentState, parentRec, err := sim.sm.TipSetState(ctx, parentTs) + if err != nil { + return nil, xerrors.Errorf("failed to compute parent tipset: %w", err) + } + msgsCid, err := sim.storeMessages(ctx, messages) + if err != nil { + return nil, xerrors.Errorf("failed to store block messages: %w", err) + } + + uts := parentTs.MinTimestamp() + build.BlockDelaySecs + + blks := []*types.BlockHeader{{ + Miner: parentTs.MinTicketBlock().Miner, // keep reusing the same miner. + Ticket: sim.nextTicket(), + BeaconEntries: sim.nextBeaconEntries(), + Parents: parentTs.Cids(), + Height: parentTs.Height() + 1, + ParentStateRoot: parentState, + ParentMessageReceipts: parentRec, + Messages: msgsCid, + ParentBaseFee: baseFee, + Timestamp: uts, + ElectionProof: &types.ElectionProof{WinCount: 1}, + }} + err = sim.Chainstore.PersistBlockHeaders(blks...) + if err != nil { + return nil, xerrors.Errorf("failed to persist block headers: %w", err) + } + newTipSet, err := types.NewTipSet(blks) + if err != nil { + return nil, xerrors.Errorf("failed to create new tipset: %w", err) + } + now := time.Now() + _, _, err = sim.sm.TipSetState(ctx, newTipSet) + if err != nil { + return nil, xerrors.Errorf("failed to compute new tipset: %w", err) + } + duration := time.Since(now) + log.Infow("computed tipset", "duration", duration, "height", newTipSet.Height()) + + return newTipSet, nil +} diff --git a/cmd/lotus-sim/simulation/commit_queue.go b/cmd/lotus-sim/simulation/commit_queue.go index 957d301cf..63a478120 100644 --- a/cmd/lotus-sim/simulation/commit_queue.go +++ b/cmd/lotus-sim/simulation/commit_queue.go @@ -9,9 +9,13 @@ import ( "github.com/filecoin-project/lotus/chain/actors/policy" ) +// pendingCommitTracker tracks pending commits per-miner for a single epohc. type pendingCommitTracker map[address.Address]minerPendingCommits + +// minerPendingCommits tracks a miner's pending commits during a single epoch (grouped by seal proof type). type minerPendingCommits map[abi.RegisteredSealProof][]abi.SectorNumber +// finish markes count sectors of the given proof type as "prove-committed". func (m minerPendingCommits) finish(proof abi.RegisteredSealProof, count int) { snos := m[proof] if len(snos) < count { @@ -23,10 +27,12 @@ func (m minerPendingCommits) finish(proof abi.RegisteredSealProof, count int) { } } +// empty returns true if there are no pending commits. func (m minerPendingCommits) empty() bool { return len(m) == 0 } +// count returns the number of pending commits. func (m minerPendingCommits) count() int { count := 0 for _, snos := range m { @@ -35,12 +41,17 @@ func (m minerPendingCommits) count() int { return count } +// commitQueue is used to track pending prove-commits. +// +// Miners are processed in round-robin where _all_ commits from a given miner are finished before +// moving on to the next. This is designed to maximize batching. type commitQueue struct { minerQueue []address.Address queue []pendingCommitTracker offset abi.ChainEpoch } +// ready returns the number of prove-commits ready to be proven at the current epoch. Useful for logging. func (q *commitQueue) ready() int { if len(q.queue) == 0 { return 0 @@ -52,6 +63,9 @@ func (q *commitQueue) ready() int { return count } +// nextMiner returns the next miner to be proved and the set of pending prove commits for that +// miner. When some number of sectors have successfully been proven, call "finish" so we don't try +// to prove them again. func (q *commitQueue) nextMiner() (address.Address, minerPendingCommits, bool) { if len(q.queue) == 0 { return address.Undef, nil, false @@ -72,6 +86,8 @@ func (q *commitQueue) nextMiner() (address.Address, minerPendingCommits, bool) { return address.Undef, nil, false } +// advanceEpoch will advance to the next epoch. If some sectors were left unproven in the current +// epoch, they will be "prepended" into the next epochs sector set. func (q *commitQueue) advanceEpoch(epoch abi.ChainEpoch) { if epoch < q.offset { panic("cannot roll epoch backwards") @@ -146,6 +162,7 @@ func (q *commitQueue) advanceEpoch(epoch abi.ChainEpoch) { }) } +// enquueProveCommit enqueues prove-commit for the given pre-commit for the given miner. func (q *commitQueue) enqueueProveCommit(addr address.Address, preCommitEpoch abi.ChainEpoch, info miner.SectorPreCommitInfo) error { // Compute the epoch at which we can start trying to commit. preCommitDelay := policy.GetPreCommitChallengeDelay() @@ -178,10 +195,3 @@ func (q *commitQueue) enqueueProveCommit(addr address.Address, preCommitEpoch ab minerPending[info.SealProof] = append(minerPending[info.SealProof], info.SectorNumber) return nil } - -func (q *commitQueue) head() pendingCommitTracker { - if len(q.queue) > 0 { - return q.queue[0] - } - return nil -} diff --git a/cmd/lotus-sim/simulation/messages.go b/cmd/lotus-sim/simulation/messages.go index 76b100d75..3f1c55179 100644 --- a/cmd/lotus-sim/simulation/messages.go +++ b/cmd/lotus-sim/simulation/messages.go @@ -15,6 +15,7 @@ import ( "github.com/filecoin-project/lotus/chain/types" ) +// toArray converts the given set of CIDs to an AMT. This is usually used to pack messages into blocks. func toArray(store blockadt.Store, cids []cid.Cid) (cid.Cid, error) { arr := blockadt.MakeEmptyArray(store) for i, c := range cids { @@ -26,6 +27,8 @@ func toArray(store blockadt.Store, cids []cid.Cid) (cid.Cid, error) { return arr.Root() } +// storeMessages packs a set of messages into a types.MsgMeta and returns the resulting CID. The +// resulting CID is valid for the BlocKHeader's Messages field. func (nd *Node) storeMessages(ctx context.Context, messages []*types.Message) (cid.Cid, error) { var blsMessages, sekpMessages []cid.Cid fakeSig := make([]byte, 32) diff --git a/cmd/lotus-sim/simulation/mock.go b/cmd/lotus-sim/simulation/mock.go index e8a7b2d4a..b81ee8629 100644 --- a/cmd/lotus-sim/simulation/mock.go +++ b/cmd/lotus-sim/simulation/mock.go @@ -8,6 +8,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" ) @@ -21,74 +22,11 @@ const ( mockPoStProofPrefix = "valid post proof:" ) -func mockSealProof(proofType abi.RegisteredSealProof, minerAddr address.Address) ([]byte, error) { - plen, err := proofType.ProofSize() - if err != nil { - return nil, err - } - proof := make([]byte, plen) - i := copy(proof, mockSealProofPrefix) - binary.BigEndian.PutUint64(proof[i:], uint64(proofType)) - i += 8 - i += copy(proof[i:], minerAddr.Bytes()) - return proof, nil -} - -func mockAggregateSealProof(proofType abi.RegisteredSealProof, minerAddr address.Address, count int) ([]byte, error) { - proof := make([]byte, aggProofLen(count)) - i := copy(proof, mockAggregateSealProofPrefix) - binary.BigEndian.PutUint64(proof[i:], uint64(proofType)) - i += 8 - binary.BigEndian.PutUint64(proof[i:], uint64(count)) - i += 8 - i += copy(proof[i:], minerAddr.Bytes()) - - return proof, nil -} - -func mockWpostProof(proofType abi.RegisteredPoStProof, minerAddr address.Address) ([]byte, error) { - plen, err := proofType.ProofSize() - if err != nil { - return nil, err - } - proof := make([]byte, plen) - i := copy(proof, mockPoStProofPrefix) - i += copy(proof[i:], minerAddr.Bytes()) - return proof, nil -} - -// TODO: dedup -func aggProofLen(nproofs int) int { - switch { - case nproofs <= 8: - return 11220 - case nproofs <= 16: - return 14196 - case nproofs <= 32: - return 17172 - case nproofs <= 64: - return 20148 - case nproofs <= 128: - return 23124 - case nproofs <= 256: - return 26100 - case nproofs <= 512: - return 29076 - case nproofs <= 1024: - return 32052 - case nproofs <= 2048: - return 35028 - case nproofs <= 4096: - return 38004 - case nproofs <= 8192: - return 40980 - default: - panic("too many proofs") - } -} - +// mockVerifier is a simple mock for verifying "fake" proofs. type mockVerifier struct{} +var _ ffiwrapper.Verifier = mockVerifier{} + func (mockVerifier) VerifySeal(proof proof5.SealVerifyInfo) (bool, error) { addr, err := address.NewIDAddress(uint64(proof.Miner)) if err != nil { @@ -134,3 +72,74 @@ func (mockVerifier) VerifyWindowPoSt(ctx context.Context, info proof5.WindowPoSt func (mockVerifier) GenerateWinningPoStSectorChallenge(context.Context, abi.RegisteredPoStProof, abi.ActorID, abi.PoStRandomness, uint64) ([]uint64, error) { panic("should not be called") } + +// mockSealProof generates a mock "seal" proof tied to the specified proof type and the given miner. +func mockSealProof(proofType abi.RegisteredSealProof, minerAddr address.Address) ([]byte, error) { + plen, err := proofType.ProofSize() + if err != nil { + return nil, err + } + proof := make([]byte, plen) + i := copy(proof, mockSealProofPrefix) + binary.BigEndian.PutUint64(proof[i:], uint64(proofType)) + i += 8 + i += copy(proof[i:], minerAddr.Bytes()) + return proof, nil +} + +// mockAggregateSealProof generates a mock "seal" aggregate proof tied to the specified proof type, +// the given miner, and the number of proven sectors. +func mockAggregateSealProof(proofType abi.RegisteredSealProof, minerAddr address.Address, count int) ([]byte, error) { + proof := make([]byte, aggProofLen(count)) + i := copy(proof, mockAggregateSealProofPrefix) + binary.BigEndian.PutUint64(proof[i:], uint64(proofType)) + i += 8 + binary.BigEndian.PutUint64(proof[i:], uint64(count)) + i += 8 + i += copy(proof[i:], minerAddr.Bytes()) + + return proof, nil +} + +// mockWpostProof generates a mock "window post" proof tied to the specified proof type, and the +// given miner. +func mockWpostProof(proofType abi.RegisteredPoStProof, minerAddr address.Address) ([]byte, error) { + plen, err := proofType.ProofSize() + if err != nil { + return nil, err + } + proof := make([]byte, plen) + i := copy(proof, mockPoStProofPrefix) + i += copy(proof[i:], minerAddr.Bytes()) + return proof, nil +} + +// TODO: dedup +func aggProofLen(nproofs int) int { + switch { + case nproofs <= 8: + return 11220 + case nproofs <= 16: + return 14196 + case nproofs <= 32: + return 17172 + case nproofs <= 64: + return 20148 + case nproofs <= 128: + return 23124 + case nproofs <= 256: + return 26100 + case nproofs <= 512: + return 29076 + case nproofs <= 1024: + return 32052 + case nproofs <= 2048: + return 35028 + case nproofs <= 4096: + return 38004 + case nproofs <= 8192: + return 40980 + default: + panic("too many proofs") + } +} diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go index 505f563e9..fa4d71028 100644 --- a/cmd/lotus-sim/simulation/node.go +++ b/cmd/lotus-sim/simulation/node.go @@ -19,6 +19,7 @@ import ( "github.com/filecoin-project/lotus/node/repo" ) +// Node represents the local lotus node, or at least the part of it we care about. type Node struct { Repo repo.LockedRepo Blockstore blockstore.Blockstore @@ -26,6 +27,7 @@ type Node struct { Chainstore *store.ChainStore } +// OpenNode opens the local lotus node for writing. This will fail if the node is online. func OpenNode(ctx context.Context, path string) (*Node, error) { var node Node r, err := repo.NewFS(path) @@ -55,6 +57,7 @@ func OpenNode(ctx context.Context, path string) (*Node, error) { return &node, nil } +// Close cleanly close the node. Please call this on shutdown to make sure everything is flushed. func (nd *Node) Close() error { var err error if closer, ok := nd.Blockstore.(io.Closer); ok && closer != nil { @@ -69,6 +72,7 @@ func (nd *Node) Close() error { return err } +// LoadSim loads func (nd *Node) LoadSim(ctx context.Context, name string) (*Simulation, error) { sim := &Simulation{ Node: nd, @@ -103,6 +107,10 @@ func (nd *Node) LoadSim(ctx context.Context, name string) (*Simulation, error) { return sim, nil } +// Create creates a new simulation. +// +// - This will fail if a simulation already exists with the given name. +// - Name must not contain a '/'. func (nd *Node) CreateSim(ctx context.Context, name string, head *types.TipSet) (*Simulation, error) { if strings.Contains(name, "/") { return nil, xerrors.Errorf("simulation name %q cannot contain a '/'", name) @@ -125,6 +133,7 @@ func (nd *Node) CreateSim(ctx context.Context, name string, head *types.TipSet) return sim, nil } +// ListSims lists all simulations. func (nd *Node) ListSims(ctx context.Context) ([]string, error) { prefix := simulationPrefix.ChildString("head").String() items, err := nd.MetadataDS.Query(query.Query{ @@ -153,6 +162,9 @@ func (nd *Node) ListSims(ctx context.Context) ([]string, error) { } } +// DeleteSim deletes a simulation and all related metadata. +// +// NOTE: This function does not delete associated messages, blocks, or chain state. func (nd *Node) DeleteSim(ctx context.Context, name string) error { // TODO: make this a bit more generic? keys := []datastore.Key{ diff --git a/cmd/lotus-sim/simulation/power.go b/cmd/lotus-sim/simulation/power.go index 9a64c3f3a..a86b691f3 100644 --- a/cmd/lotus-sim/simulation/power.go +++ b/cmd/lotus-sim/simulation/power.go @@ -12,10 +12,6 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin/power" ) -type powerInfo struct { - powerLookback, powerNow abi.StoragePower -} - // Load all power claims at the given height. func (sim *Simulation) loadClaims(ctx context.Context, height abi.ChainEpoch) (map[address.Address]power.Claim, error) { powerTable := make(map[address.Address]power.Claim) diff --git a/cmd/lotus-sim/simulation/precommit.go b/cmd/lotus-sim/simulation/precommit.go index 1ede3d5c4..055918c8c 100644 --- a/cmd/lotus-sim/simulation/precommit.go +++ b/cmd/lotus-sim/simulation/precommit.go @@ -20,15 +20,17 @@ import ( tutils "github.com/filecoin-project/specs-actors/v5/support/testing" ) -func makeCommR(minerAddr address.Address, sno abi.SectorNumber) cid.Cid { - return tutils.MakeCID(fmt.Sprintf("%s:%d", minerAddr, sno), &miner5.SealedCIDPrefix) -} - var ( targetFunds = abi.TokenAmount(types.MustParseFIL("1000FIL")) minFunds = abi.TokenAmount(types.MustParseFIL("100FIL")) ) +// makeCommR generates a "fake" but valid CommR for a sector. It is unique for the given sector/miner. +func makeCommR(minerAddr address.Address, sno abi.SectorNumber) cid.Cid { + return tutils.MakeCID(fmt.Sprintf("%s:%d", minerAddr, sno), &miner5.SealedCIDPrefix) +} + +// packPreCommits packs pre-commit messages until the block is full. func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (full bool, _err error) { var top1Count, top10Count, restCount int defer func() { @@ -50,6 +52,13 @@ func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (ful minerAddr address.Address count *int ) + + // We pre-commit for the top 1%, 10%, and the of the network 1/3rd of the time each. + // This won't yeild the most accurate distribution... but it'll give us a good + // enough distribution. + + // NOTE: We submit at most _one_ 819 sector batch per-miner per-block. See the + // comment on packPreCommitsMiner for why. We should fix this. switch { case (i%3) <= 0 && top1Miners < ss.minerDist.top1.len(): count = &top1Count @@ -67,6 +76,7 @@ func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (ful // Well, we've run through all miners. return false, nil } + added, full, err := ss.packPreCommitsMiner(ctx, cb, minerAddr, maxProveCommitBatchSize) if err != nil { return false, xerrors.Errorf("failed to pack precommits for miner %s: %w", minerAddr, err) @@ -78,7 +88,10 @@ func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (ful } } +// packPreCommitsMiner packs count pre-commits for the given miner. This should only be called once +// per-miner, per-epoch to avoid packing multiple pre-commits with the same sector numbers. func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, minerAddr address.Address, count int) (int, bool, error) { + // Load everything. epoch := ss.nextEpoch() nv := ss.sm.GetNtwkVersion(ctx, epoch) st, err := ss.stateTree(ctx) @@ -120,6 +133,7 @@ func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, } } + // Generate pre-commits. sealType, err := miner.PreferredSealProofTypeFromWindowPoStType( nv, minerInfo.WindowPoStProofType, ) @@ -143,6 +157,8 @@ func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, Expiration: expiration, } } + + // Commit the pre-commits. added := 0 if nv >= network.Version13 { targetBatchSize := maxPreCommitBatchSize @@ -158,6 +174,8 @@ func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, if err != nil { return 0, false, err } + // NOTE: just in-case, sendAndFund will "fund" and re-try for any message + // that fails due to "insufficient funds". if full, err := sendAndFund(cb, &types.Message{ To: minerAddr, From: minerInfo.Worker, diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/provecommit.go index 0d855bcd1..ec0c99c8a 100644 --- a/cmd/lotus-sim/simulation/provecommit.go +++ b/cmd/lotus-sim/simulation/provecommit.go @@ -21,7 +21,10 @@ import ( power5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/power" ) +// packProveCOmmits packs all prove-commits for all "ready to be proven" sectors until it fills the +// block or runs out. func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_full bool, _err error) { + // Roll the commitQueue forward. ss.commitQueue.advanceEpoch(ss.nextEpoch()) var failed, done, unbatched, count int @@ -64,6 +67,14 @@ type proveCommitResult struct { done, failed, unbatched int } +// sendAndFund "packs" the given message, funding the actor if necessary. It: +// +// 1. Tries to send the given message. +// 2. If that fails, it checks to see if the exit code was ErrInsufficientFunds. +// 3. If so, it sends 1K FIL from the "burnt funds actor" (because we need to send it from +// somewhere) and re-tries the message.0 +// +// NOTE: If the message fails a second time, the funds won't be "unsent". func sendAndFund(send packFunc, msg *types.Message) (bool, error) { full, err := send(msg) aerr, ok := err.(aerrors.ActorError) @@ -87,7 +98,10 @@ func sendAndFund(send packFunc, msg *types.Message) (bool, error) { return send(msg) } -// Enqueue a single prove commit from the given miner. +// packProveCommitsMiner enqueues a prove commits from the given miner until it runs out of +// available prove-commits, batching as much as possible. +// +// This function will fund as necessary from the "burnt funds actor" (look, it's convenient). func (ss *simulationState) packProveCommitsMiner( ctx context.Context, cb packFunc, minerAddr address.Address, pending minerPendingCommits, @@ -200,7 +214,10 @@ func (ss *simulationState) packProveCommitsMiner( return res, false, nil } -// Enqueue all pending prove-commits for the given miner. +// loadProveCommitsMiner enqueue all pending prove-commits for the given miner. This is called on +// load to populate the commitQueue and should not need to be called later. +// +// It will drop any pre-commits that have already expired. func (ss *simulationState) loadProveCommitsMiner(ctx context.Context, addr address.Address, minerState miner.State) error { // Find all pending prove commits and group by proof type. Really, there should never // (except during upgrades be more than one type. diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index 384cc79cb..dc5cb3976 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -2,10 +2,7 @@ package simulation import ( "context" - "crypto/sha256" - "encoding/binary" "encoding/json" - "time" "golang.org/x/xerrors" @@ -13,9 +10,7 @@ import ( logging "github.com/ipfs/go-log/v2" "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/network" - "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/stmgr" @@ -35,16 +30,23 @@ const ( maxProveCommitBatchSize = miner5.MaxAggregatedSectors ) +// config is the simulation's config, persisted to the local metadata store and loaded on start. +// +// See simulationState.loadConfig and simulationState.saveConfig. type config struct { Upgrades map[network.Version]abi.ChainEpoch } +// upgradeSchedule constructs an stmgr.StateManager upgrade schedule, overriding any network upgrade +// epochs as specified in the config. func (c *config) upgradeSchedule() (stmgr.UpgradeSchedule, error) { upgradeSchedule := stmgr.DefaultUpgradeSchedule() expected := make(map[network.Version]struct{}, len(c.Upgrades)) for nv := range c.Upgrades { expected[nv] = struct{}{} } + + // Update network upgrade epochs. newUpgradeSchedule := upgradeSchedule[:0] for _, upgrade := range upgradeSchedule { if height, ok := c.Upgrades[upgrade.Network]; ok { @@ -56,6 +58,8 @@ func (c *config) upgradeSchedule() (stmgr.UpgradeSchedule, error) { } newUpgradeSchedule = append(newUpgradeSchedule, upgrade) } + + // Make sure we didn't try to configure an unknown network version. if len(expected) > 0 { missing := make([]network.Version, 0, len(expected)) for nv := range expected { @@ -63,9 +67,16 @@ func (c *config) upgradeSchedule() (stmgr.UpgradeSchedule, error) { } return nil, xerrors.Errorf("unknown network versions %v in config", missing) } + + // Finally, validate it. This ensures we don't change the order of the upgrade or anything + // like that. + if err := newUpgradeSchedule.Validate(); err != nil { + return nil, err + } return newUpgradeSchedule, nil } +// Simulation specifies a lotus-sim simulation. type Simulation struct { *Node @@ -82,6 +93,8 @@ type Simulation struct { state *simulationState } +// loadConfig loads a simulation's config from the datastore. This must be called on startup and may +// be called to restore the config from-disk. func (sim *Simulation) loadConfig() error { configBytes, err := sim.MetadataDS.Get(sim.key("config")) if err == nil { @@ -97,6 +110,18 @@ func (sim *Simulation) loadConfig() error { return nil } +// saveConfig saves the current config to the datastore. This must be called whenever the config is +// changed. +func (sim *Simulation) saveConfig() error { + buf, err := json.Marshal(sim.config) + if err != nil { + return err + } + return sim.MetadataDS.Put(sim.key("config"), buf) +} + +// stateTree returns the current state-tree for the current head, computing the tipset if necessary. +// The state-tree is cached until the head is changed. func (sim *Simulation) stateTree(ctx context.Context) (*state.StateTree, error) { if sim.st == nil { st, _, err := sim.sm.TipSetState(ctx, sim.head) @@ -128,6 +153,8 @@ func (sim *Simulation) simState(ctx context.Context) (*simulationState, error) { var simulationPrefix = datastore.NewKey("/simulation") +// key returns the the key in the form /simulation//. For example, +// /simulation/head/default. func (sim *Simulation) key(subkey string) datastore.Key { return simulationPrefix.ChildString(subkey).ChildString(sim.name) } @@ -139,14 +166,18 @@ func (sim *Simulation) Load(ctx context.Context) error { return err } +// GetHead returns the current simulation head. func (sim *Simulation) GetHead() *types.TipSet { return sim.head } +// GetNetworkVersion returns the current network version for the simulation. func (sim *Simulation) GetNetworkVersion() network.Version { return sim.sm.GetNtwkVersion(context.TODO(), sim.head.Height()) } +// SetHead updates the current head of the simulation and stores it in the metadata store. This is +// called for every Simulation.Step. func (sim *Simulation) SetHead(head *types.TipSet) error { if err := sim.MetadataDS.Put(sim.key("head"), head.Key().Bytes()); err != nil { return xerrors.Errorf("failed to store simulation head: %w", err) @@ -156,85 +187,14 @@ func (sim *Simulation) SetHead(head *types.TipSet) error { return nil } +// Name returns the simulation's name. func (sim *Simulation) Name() string { return sim.name } -func (sim *Simulation) postChainCommitInfo(ctx context.Context, epoch abi.ChainEpoch) (abi.Randomness, error) { - commitRand, err := sim.Chainstore.GetChainRandomness( - ctx, sim.head.Cids(), crypto.DomainSeparationTag_PoStChainCommit, epoch, nil, true) - return commitRand, err -} - -const beaconPrefix = "mockbeacon:" - -func (sim *Simulation) nextBeaconEntries() []types.BeaconEntry { - parentBeacons := sim.head.Blocks()[0].BeaconEntries - lastBeacon := parentBeacons[len(parentBeacons)-1] - beaconRound := lastBeacon.Round + 1 - - buf := make([]byte, len(beaconPrefix)+8) - copy(buf, beaconPrefix) - binary.BigEndian.PutUint64(buf[len(beaconPrefix):], beaconRound) - beaconRand := sha256.Sum256(buf) - return []types.BeaconEntry{{ - Round: beaconRound, - Data: beaconRand[:], - }} -} - -func (sim *Simulation) nextTicket() *types.Ticket { - newProof := sha256.Sum256(sim.head.MinTicket().VRFProof) - return &types.Ticket{ - VRFProof: newProof[:], - } -} - -func (sim *Simulation) makeTipSet(ctx context.Context, messages []*types.Message) (*types.TipSet, error) { - parentTs := sim.head - parentState, parentRec, err := sim.sm.TipSetState(ctx, parentTs) - if err != nil { - return nil, xerrors.Errorf("failed to compute parent tipset: %w", err) - } - msgsCid, err := sim.storeMessages(ctx, messages) - if err != nil { - return nil, xerrors.Errorf("failed to store block messages: %w", err) - } - - uts := parentTs.MinTimestamp() + build.BlockDelaySecs - - blks := []*types.BlockHeader{{ - Miner: parentTs.MinTicketBlock().Miner, // keep reusing the same miner. - Ticket: sim.nextTicket(), - BeaconEntries: sim.nextBeaconEntries(), - Parents: parentTs.Cids(), - Height: parentTs.Height() + 1, - ParentStateRoot: parentState, - ParentMessageReceipts: parentRec, - Messages: msgsCid, - ParentBaseFee: baseFee, - Timestamp: uts, - ElectionProof: &types.ElectionProof{WinCount: 1}, - }} - err = sim.Chainstore.PersistBlockHeaders(blks...) - if err != nil { - return nil, xerrors.Errorf("failed to persist block headers: %w", err) - } - newTipSet, err := types.NewTipSet(blks) - if err != nil { - return nil, xerrors.Errorf("failed to create new tipset: %w", err) - } - now := time.Now() - _, _, err = sim.sm.TipSetState(ctx, newTipSet) - if err != nil { - return nil, xerrors.Errorf("failed to compute new tipset: %w", err) - } - duration := time.Since(now) - log.Infow("computed tipset", "duration", duration, "height", newTipSet.Height()) - - return newTipSet, nil -} - +// SetUpgradeHeight sets the height of the given network version change (and saves the config). +// +// This fails if the specified epoch has already passed or the new upgrade schedule is invalid. func (sim *Simulation) SetUpgradeHeight(nv network.Version, epoch abi.ChainEpoch) (_err error) { if epoch <= sim.head.Height() { return xerrors.Errorf("cannot set upgrade height in the past (%d <= %d)", epoch, sim.head.Height()) @@ -269,6 +229,7 @@ func (sim *Simulation) SetUpgradeHeight(nv network.Version, epoch abi.ChainEpoch return nil } +// ListUpgrades returns any future network upgrades. func (sim *Simulation) ListUpgrades() (stmgr.UpgradeSchedule, error) { upgrades, err := sim.config.upgradeSchedule() if err != nil { @@ -283,11 +244,3 @@ func (sim *Simulation) ListUpgrades() (stmgr.UpgradeSchedule, error) { } return pending, nil } - -func (sim *Simulation) saveConfig() error { - buf, err := json.Marshal(sim.config) - if err != nil { - return err - } - return sim.MetadataDS.Put(sim.key("config"), buf) -} diff --git a/cmd/lotus-sim/simulation/state.go b/cmd/lotus-sim/simulation/state.go index ee664166e..88971c9f0 100644 --- a/cmd/lotus-sim/simulation/state.go +++ b/cmd/lotus-sim/simulation/state.go @@ -2,7 +2,6 @@ package simulation import ( "context" - "math/rand" "sort" "github.com/filecoin-project/go-address" @@ -11,41 +10,19 @@ import ( "github.com/filecoin-project/lotus/chain/types" ) -type perm struct { - miners []address.Address - offset int -} - -func (p *perm) shuffle() { - rand.Shuffle(len(p.miners), func(i, j int) { - p.miners[i], p.miners[j] = p.miners[j], p.miners[i] - }) -} - -func (p *perm) next() address.Address { - next := p.miners[p.offset] - p.offset++ - p.offset %= len(p.miners) - return next -} - -func (p *perm) add(addr address.Address) { - p.miners = append(p.miners, addr) -} - -func (p *perm) len() int { - return len(p.miners) -} - +// simualtionState holds the "state" of the simulation. This is split from the Simulation type so we +// can load it on-dempand if and when we need to actually _run_ the simualation. Loading the +// simulation state requires walking all active miners. type simulationState struct { *Simulation + // The tiers represent the top 1%, top 10%, and everyone else. When sealing sectors, we seal + // a group of sectors for the top 1%, a group (half that size) for the top 10%, and one + // sector for everyone else. We determine these rates by looking at two power tables. // TODO Ideally we'd "learn" this distribution from the network. But this is good enough for - // now. The tiers represent the top 1%, top 10%, and everyone else. When sealing sectors, we - // seal a group of sectors for the top 1%, a group (half that size) for the top 10%, and one - // sector for everyone else. We really should pick a better algorithm. + // now. minerDist struct { - top1, top10, rest perm + top1, top10, rest actorIter } // We track the window post periods per miner and assume that no new miners are ever added. @@ -58,6 +35,9 @@ type simulationState struct { pendingWposts []*types.Message nextWpostEpoch abi.ChainEpoch + // We track the set of pending commits. On simulation load, and when a new pre-commit is + // added to the chain, we put the commit in this queue. advanceEpoch(currentEpoch) should be + // called on this queue at every epoch before using it. commitQueue commitQueue } @@ -167,7 +147,7 @@ func loadSimulationState(ctx context.Context, sim *Simulation) (*simulationState }) for i, oi := range sealList { - var dist *perm + var dist *actorIter if i < len(sealList)/100 { dist = &state.minerDist.top1 } else if i < len(sealList)/10 { @@ -185,6 +165,35 @@ func loadSimulationState(ctx context.Context, sim *Simulation) (*simulationState return state, nil } +// nextEpoch returns the next epoch (head+1). func (ss *simulationState) nextEpoch() abi.ChainEpoch { return ss.GetHead().Height() + 1 } + +// getMinerInfo returns the miner's cached info. +// +// NOTE: we assume that miner infos won't change. We'll need to fix this if we start supporting arbitrary message. +func (ss *simulationState) getMinerInfo(ctx context.Context, addr address.Address) (*miner.MinerInfo, error) { + minerInfo, ok := ss.minerInfos[addr] + if !ok { + st, err := ss.stateTree(ctx) + if err != nil { + return nil, err + } + act, err := st.GetActor(addr) + if err != nil { + return nil, err + } + minerState, err := miner.Load(ss.Chainstore.ActorStore(ctx), act) + if err != nil { + return nil, err + } + info, err := minerState.Info() + if err != nil { + return nil, err + } + minerInfo = &info + ss.minerInfos[addr] = minerInfo + } + return minerInfo, nil +} diff --git a/cmd/lotus-sim/simulation/step.go b/cmd/lotus-sim/simulation/step.go index b44f3be4d..79ace1db2 100644 --- a/cmd/lotus-sim/simulation/step.go +++ b/cmd/lotus-sim/simulation/step.go @@ -20,6 +20,8 @@ import ( ) const ( + // The number of expected blocks in a tipset. We use this to determine how much gas a tipset + // has. expectedBlocks = 5 // TODO: This will produce invalid blocks but it will accurately model the amount of gas // we're willing to use per-tipset. @@ -42,6 +44,7 @@ func (sim *Simulation) Step(ctx context.Context) (*types.TipSet, error) { return ts, nil } +// step steps the simulation state forward one step, producing and executing a new tipset. func (ss *simulationState) step(ctx context.Context) (*types.TipSet, error) { log.Infow("step", "epoch", ss.head.Height()+1) messages, err := ss.popNextMessages(ctx) @@ -59,20 +62,25 @@ func (ss *simulationState) step(ctx context.Context) (*types.TipSet, error) { } type packFunc func(*types.Message) (full bool, err error) -type messageGenerator func(ctx context.Context, cb packFunc) (full bool, err error) +// popNextMessages generates/picks a set of messages to be included in the next block. +// +// - This function is destructive and should only be called once per epoch. +// - This function does not store anything in the repo. +// - This function handles all gas estimation. The returned messages should all fit in a single +// block. func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Message, error) { parentTs := ss.head - parentState, _, err := ss.sm.TipSetState(ctx, parentTs) - if err != nil { - return nil, err - } + + // First we make sure we don't have an upgrade at this epoch. If we do, we return no + // messages so we can just create an empty block at that epoch. + // + // This isn't what the network does, but it makes things easier. Otherwise, we'd need to run + // migrations before this epoch and I'd rather not deal with that. nextHeight := parentTs.Height() + 1 prevVer := ss.sm.GetNtwkVersion(ctx, nextHeight-1) nextVer := ss.sm.GetNtwkVersion(ctx, nextHeight) if nextVer != prevVer { - // So... we _could_ actually run the migration, but that's a pain. It's easier to - // just have an empty block then let the state manager run the migration as normal. log.Warnw("packing no messages for version upgrade block", "old", prevVer, "new", nextVer, @@ -81,10 +89,20 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag return nil, nil } - // Then we need to execute messages till we run out of gas. Those messages will become the - // block's messages. + // Next, we compute the state for the parent tipset. In practice, this will likely be + // cached. + parentState, _, err := ss.sm.TipSetState(ctx, parentTs) + if err != nil { + return nil, err + } + + // Then we construct a VM to execute messages for gas estimation. + // + // Most parts of this VM are "real" except: + // 1. We don't charge a fee. + // 2. The runtime has "fake" proof logic. + // 3. We don't actually save any of the results. r := store.NewChainRand(ss.sm.ChainStore(), parentTs.Cids()) - // TODO: Factor this out maybe? vmopt := &vm.VMOpts{ StateBase: parentState, Epoch: nextHeight, @@ -100,9 +118,15 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag if err != nil { return nil, err } - // TODO: This is the wrong store and may not include important state for what we're doing - // here.... - // Maybe we just track nonces separately? Yeah, probably better that way. + + // Next we define a helper function for "pushing" messages. This is the function that will + // be passed to the "pack" functions. + // + // It. + // + // 1. Tries to execute the message on-top-of the already pushed message. + // 2. Is careful to revert messages on failure to avoid nasties like nonce-gaps. + // 3. Resolves IDs as necessary, fills in missing parts of the message, etc. vmStore := vmi.ActorStore(ctx) var gasTotal int64 var messages []*types.Message @@ -181,16 +205,52 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag messages = append(messages, msg) return false, nil } - for _, mgen := range []messageGenerator{ss.packWindowPoSts, ss.packProveCommits, ss.packPreCommits} { - if full, err := mgen(ctx, tryPushMsg); err != nil { - name := runtime.FuncForPC(reflect.ValueOf(mgen).Pointer()).Name() - lastDot := strings.LastIndexByte(name, '.') - fName := name[lastDot+1 : len(name)-3] - return nil, xerrors.Errorf("when packing messages with %s: %w", fName, err) - } else if full { - break - } + + // Finally, we generate a set of messages to be included in + if err := ss.packMessages(ctx, tryPushMsg); err != nil { + return nil, err } return messages, nil } + +// functionName extracts the name of given function. +func functionName(fn interface{}) string { + name := runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name() + lastDot := strings.LastIndexByte(name, '.') + if lastDot >= 0 { + name = name[lastDot+1 : len(name)-3] + } + lastDash := strings.LastIndexByte(name, '-') + if lastDash > 0 { + name = name[:lastDash] + } + return name +} + +// packMessages packs messages with the given packFunc until the block is full (packFunc returns +// true). +// TODO: Make this more configurable for other simulations. +func (ss *simulationState) packMessages(ctx context.Context, cb packFunc) error { + type messageGenerator func(ctx context.Context, cb packFunc) (full bool, err error) + + // We pack messages in-order: + // 1. Any window posts. We pack window posts as soon as the deadline opens to ensure we only + // miss them if/when we run out of chain bandwidth. + // 2. Prove commits. We do this eagerly to ensure they don't expire. + // 3. Finally, we fill the rest of the space with pre-commits. + messageGenerators := []messageGenerator{ + ss.packWindowPoSts, + ss.packProveCommits, + ss.packPreCommits, + } + + for _, mgen := range messageGenerators { + if full, err := mgen(ctx, cb); err != nil { + return xerrors.Errorf("when packing messages with %s: %w", functionName(mgen), err) + } else if full { + break + } + } + return nil +} diff --git a/cmd/lotus-sim/simulation/wdpost.go b/cmd/lotus-sim/simulation/wdpost.go index 7abb9a83a..fe93a5f0c 100644 --- a/cmd/lotus-sim/simulation/wdpost.go +++ b/cmd/lotus-sim/simulation/wdpost.go @@ -5,41 +5,29 @@ import ( "math" "time" + "golang.org/x/xerrors" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/aerrors" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/types" + proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" - "golang.org/x/xerrors" ) -func (ss *simulationState) getMinerInfo(ctx context.Context, addr address.Address) (*miner.MinerInfo, error) { - minerInfo, ok := ss.minerInfos[addr] - if !ok { - st, err := ss.stateTree(ctx) - if err != nil { - return nil, err - } - act, err := st.GetActor(addr) - if err != nil { - return nil, err - } - minerState, err := miner.Load(ss.Chainstore.ActorStore(ctx), act) - if err != nil { - return nil, err - } - info, err := minerState.Info() - if err != nil { - return nil, err - } - minerInfo = &info - ss.minerInfos[addr] = minerInfo - } - return minerInfo, nil +// postChainCommitInfo returns th +func (sim *Simulation) postChainCommitInfo(ctx context.Context, epoch abi.ChainEpoch) (abi.Randomness, error) { + commitRand, err := sim.Chainstore.GetChainRandomness( + ctx, sim.head.Cids(), crypto.DomainSeparationTag_PoStChainCommit, epoch, nil, true) + return commitRand, err } +// packWindowPoSts packs window posts until either the block is full or all healty sectors +// have been proven. It does not recover sectors. func (ss *simulationState) packWindowPoSts(ctx context.Context, cb packFunc) (full bool, _err error) { // Push any new window posts into the queue. if err := ss.queueWindowPoSts(ctx); err != nil { @@ -84,7 +72,7 @@ func (ss *simulationState) packWindowPoSts(ctx context.Context, cb packFunc) (fu return false, nil } -// Enqueue all missing window posts for the current epoch for the given miner. +// stepWindowPoStsMiner enqueues all missing window posts for the current epoch for the given miner. func (ss *simulationState) stepWindowPoStsMiner( ctx context.Context, addr address.Address, minerState miner.State, @@ -198,7 +186,8 @@ func (ss *simulationState) stepWindowPoStsMiner( return nil } -// Enqueue missing window posts for all miners with deadlines opening at the current epoch. +// queueWindowPoSts enqueues missing window posts for all miners with deadlines opening between the +// last epoch in which this function was called and the current epoch (head+1). func (ss *simulationState) queueWindowPoSts(ctx context.Context) error { targetHeight := ss.nextEpoch() From 7925b695738b757a463fb7504756348dfa78d6a4 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 7 Jun 2021 17:52:57 -0700 Subject: [PATCH 093/257] doc(lotus-sim): document block generation logic --- cmd/lotus-sim/simulation/block.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cmd/lotus-sim/simulation/block.go b/cmd/lotus-sim/simulation/block.go index 677ba7a2f..3a1181c1c 100644 --- a/cmd/lotus-sim/simulation/block.go +++ b/cmd/lotus-sim/simulation/block.go @@ -13,6 +13,7 @@ import ( const beaconPrefix = "mockbeacon:" +// nextBeaconEntries returns a fake beacon entries for the next block. func (sim *Simulation) nextBeaconEntries() []types.BeaconEntry { parentBeacons := sim.head.Blocks()[0].BeaconEntries lastBeacon := parentBeacons[len(parentBeacons)-1] @@ -28,6 +29,7 @@ func (sim *Simulation) nextBeaconEntries() []types.BeaconEntry { }} } +// nextTicket returns a fake ticket for the next block. func (sim *Simulation) nextTicket() *types.Ticket { newProof := sha256.Sum256(sim.head.MinTicket().VRFProof) return &types.Ticket{ @@ -35,6 +37,14 @@ func (sim *Simulation) nextTicket() *types.Ticket { } } +// makeTipSet generates and executes the next tipset from the given messages. This method: +// +// 1. Stores the given messages in the Chainstore. +// 2. Creates and persists a single block mined by f01000. +// 3. Creates a tipset from this block and executes it. +// 4. Returns the resulting tipset. +// +// This method does _not_ mutate local state (although it does add blocks to the datastore). func (sim *Simulation) makeTipSet(ctx context.Context, messages []*types.Message) (*types.TipSet, error) { parentTs := sim.head parentState, parentRec, err := sim.sm.TipSetState(ctx, parentTs) From 0ccf716989256697844af875b35441ce4dedc30c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 7 Jun 2021 18:18:20 -0700 Subject: [PATCH 094/257] fix(lotus-sim): refactor miner state loading Add a helper function so we don't need to constantly repeat ourselves. --- cmd/lotus-sim/simulation/precommit.go | 10 +------ cmd/lotus-sim/simulation/state.go | 41 +++++++++++++-------------- cmd/lotus-sim/simulation/wdpost.go | 13 +-------- 3 files changed, 21 insertions(+), 43 deletions(-) diff --git a/cmd/lotus-sim/simulation/precommit.go b/cmd/lotus-sim/simulation/precommit.go index 055918c8c..619ba467d 100644 --- a/cmd/lotus-sim/simulation/precommit.go +++ b/cmd/lotus-sim/simulation/precommit.go @@ -94,15 +94,7 @@ func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, // Load everything. epoch := ss.nextEpoch() nv := ss.sm.GetNtwkVersion(ctx, epoch) - st, err := ss.stateTree(ctx) - if err != nil { - return 0, false, err - } - actor, err := st.GetActor(minerAddr) - if err != nil { - return 0, false, err - } - minerState, err := miner.Load(ss.Chainstore.ActorStore(ctx), actor) + actor, minerState, err := ss.getMinerState(ctx, minerAddr) if err != nil { return 0, false, err } diff --git a/cmd/lotus-sim/simulation/state.go b/cmd/lotus-sim/simulation/state.go index 88971c9f0..23de7038c 100644 --- a/cmd/lotus-sim/simulation/state.go +++ b/cmd/lotus-sim/simulation/state.go @@ -63,13 +63,6 @@ func loadSimulationState(ctx context.Context, sim *Simulation) (*simulationState return nil, err } - // Now load miner state info. - store := sim.Chainstore.ActorStore(ctx) - st, err := sim.stateTree(ctx) - if err != nil { - return nil, err - } - type onboardingInfo struct { addr address.Address onboardingRate uint64 @@ -86,12 +79,7 @@ func loadSimulationState(ctx context.Context, sim *Simulation) (*simulationState state.commitQueue.advanceEpoch(state.nextEpoch()) for addr, claim := range currentPowerTable { // Load the miner state. - minerActor, err := st.GetActor(addr) - if err != nil { - return nil, err - } - - minerState, err := miner.Load(store, minerActor) + _, minerState, err := state.getMinerState(ctx, addr) if err != nil { return nil, err } @@ -176,15 +164,7 @@ func (ss *simulationState) nextEpoch() abi.ChainEpoch { func (ss *simulationState) getMinerInfo(ctx context.Context, addr address.Address) (*miner.MinerInfo, error) { minerInfo, ok := ss.minerInfos[addr] if !ok { - st, err := ss.stateTree(ctx) - if err != nil { - return nil, err - } - act, err := st.GetActor(addr) - if err != nil { - return nil, err - } - minerState, err := miner.Load(ss.Chainstore.ActorStore(ctx), act) + _, minerState, err := ss.getMinerState(ctx, addr) if err != nil { return nil, err } @@ -197,3 +177,20 @@ func (ss *simulationState) getMinerInfo(ctx context.Context, addr address.Addres } return minerInfo, nil } + +// getMinerState loads the miner actor & state. +func (ss *simulationState) getMinerState(ctx context.Context, addr address.Address) (*types.Actor, miner.State, error) { + st, err := ss.stateTree(ctx) + if err != nil { + return nil, nil, err + } + act, err := st.GetActor(addr) + if err != nil { + return nil, nil, err + } + state, err := miner.Load(ss.Chainstore.ActorStore(ctx), act) + if err != nil { + return nil, nil, err + } + return act, state, err +} diff --git a/cmd/lotus-sim/simulation/wdpost.go b/cmd/lotus-sim/simulation/wdpost.go index fe93a5f0c..c940c8d51 100644 --- a/cmd/lotus-sim/simulation/wdpost.go +++ b/cmd/lotus-sim/simulation/wdpost.go @@ -191,11 +191,6 @@ func (ss *simulationState) stepWindowPoStsMiner( func (ss *simulationState) queueWindowPoSts(ctx context.Context) error { targetHeight := ss.nextEpoch() - st, err := ss.stateTree(ctx) - if err != nil { - return err - } - now := time.Now() was := len(ss.pendingWposts) count := 0 @@ -220,14 +215,8 @@ func (ss *simulationState) queueWindowPoSts(ctx context.Context) error { return err } - store := ss.Chainstore.ActorStore(ctx) - for _, addr := range ss.wpostPeriods[int(ss.nextWpostEpoch%miner.WPoStChallengeWindow)] { - minerActor, err := st.GetActor(addr) - if err != nil { - return err - } - minerState, err := miner.Load(store, minerActor) + _, minerState, err := ss.getMinerState(ctx, addr) if err != nil { return err } From 2e4f526375b73fde3b130c137a685cb84c868821 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 7 Jun 2021 19:21:18 -0700 Subject: [PATCH 095/257] fix(lotus-sim): skip (and log) missing/expired pre-commits --- cmd/lotus-sim/simulation/provecommit.go | 86 +++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 6 deletions(-) diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/provecommit.go index ec0c99c8a..968e4eb2f 100644 --- a/cmd/lotus-sim/simulation/provecommit.go +++ b/cmd/lotus-sim/simulation/provecommit.go @@ -120,7 +120,6 @@ func (ss *simulationState) packProveCommitsMiner( batchSize = len(snos) } batch := snos[:batchSize] - snos = snos[batchSize:] proof, err := mockAggregateSealProof(sealType, minerAddr, batchSize) if err != nil { @@ -149,22 +148,53 @@ func (ss *simulationState) packProveCommitsMiner( }); err != nil { // If we get a random error, or a fatal actor error, bail. // Otherwise, just log it. - if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() { + aerr, ok := err.(aerrors.ActorError) + if !ok || aerr.IsFatal() { return res, false, err } + if aerr.RetCode() == exitcode.ErrNotFound { + good, expired, err := ss.filterProveCommits(ctx, minerAddr, batch) + if err != nil { + log.Errorw("failed to filter prove commits", "miner", minerAddr, "error", err) + // fail with the original error. + return res, false, aerr + } + // If we've removed sectors (and kept some), try again. + // If we've removed all sectors, or no sectors, just + // move on and deliver the error. + if len(good) > 0 && len(expired) > 0 { + res.failed += len(expired) + + // update the pending sector numbers in-place to remove the expired ones. + snos = snos[len(expired):] + copy(snos, good) + pending.finish(sealType, len(expired)) + + log.Errorw("failed to prove commit expired/missing pre-commits", + "error", err, + "miner", minerAddr, + "expired", expired, + "discarded", len(expired), + "kept", len(good), + "epoch", ss.nextEpoch(), + ) + continue + } + } log.Errorw("failed to prove commit sector(s)", "error", err, "miner", minerAddr, "sectors", batch, "epoch", ss.nextEpoch(), ) - res.failed += batchSize + res.failed += len(batch) } else if full { return res, true, nil } else { - res.done += batchSize + res.done += len(batch) } pending.finish(sealType, batchSize) + snos = snos[batchSize:] } } for len(snos) > 0 && res.unbatched < power5.MaxMinerProveCommitsPerEpoch { @@ -225,12 +255,56 @@ func (ss *simulationState) loadProveCommitsMiner(ctx context.Context, addr addre nv := ss.sm.GetNtwkVersion(ctx, nextEpoch) av := actors.VersionForNetwork(nv) - return minerState.ForEachPrecommittedSector(func(info miner.SectorPreCommitOnChainInfo) error { + var total, dropped int + err := minerState.ForEachPrecommittedSector(func(info miner.SectorPreCommitOnChainInfo) error { + total++ msd := policy.GetMaxProveCommitDuration(av, info.Info.SealProof) if nextEpoch > info.PreCommitEpoch+msd { - log.Warnw("dropping old pre-commit") + dropped++ return nil } return ss.commitQueue.enqueueProveCommit(addr, info.PreCommitEpoch, info.Info) }) + if err != nil { + return err + } + if dropped > 0 { + log.Warnw("dropped expired pre-commits on load", + "miner", addr, + "total", total, + "expired", dropped, + ) + } + return nil +} + +// filterProveCommits filters out expired prove-commits. +func (ss *simulationState) filterProveCommits(ctx context.Context, minerAddr address.Address, snos []abi.SectorNumber) (good, expired []abi.SectorNumber, err error) { + _, minerState, err := ss.getMinerState(ctx, minerAddr) + if err != nil { + return nil, nil, err + } + + nextEpoch := ss.nextEpoch() + nv := ss.sm.GetNtwkVersion(ctx, nextEpoch) + av := actors.VersionForNetwork(nv) + + good = make([]abi.SectorNumber, 0, len(snos)) + for _, sno := range snos { + info, err := minerState.GetPrecommittedSector(sno) + if err != nil { + return nil, nil, err + } + if info == nil { + expired = append(expired, sno) + continue + } + msd := policy.GetMaxProveCommitDuration(av, info.Info.SealProof) + if nextEpoch > info.PreCommitEpoch+msd { + expired = append(expired, sno) + continue + } + good = append(good, sno) + } + return good, expired, nil } From 82019ce4744ab57316f15b5b99f4cc2b4f9d45e1 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 7 Jun 2021 19:57:46 -0700 Subject: [PATCH 096/257] fix(lotus-sim): correctly merge forward commit queue. --- cmd/lotus-sim/simulation/commit_queue.go | 2 ++ cmd/lotus-sim/simulation/provecommit.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/lotus-sim/simulation/commit_queue.go b/cmd/lotus-sim/simulation/commit_queue.go index 63a478120..4cfb6e764 100644 --- a/cmd/lotus-sim/simulation/commit_queue.go +++ b/cmd/lotus-sim/simulation/commit_queue.go @@ -129,6 +129,8 @@ func (q *commitQueue) advanceEpoch(epoch abi.ChainEpoch) { currPending[ty] = append(currSnos, nextSnos...) } } + // Now replace next with the merged curr. + q.queue[0] = curr } q.offset = epoch if len(q.queue) == 0 { diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/provecommit.go index 968e4eb2f..b62a8bafe 100644 --- a/cmd/lotus-sim/simulation/provecommit.go +++ b/cmd/lotus-sim/simulation/provecommit.go @@ -171,7 +171,7 @@ func (ss *simulationState) packProveCommitsMiner( pending.finish(sealType, len(expired)) log.Errorw("failed to prove commit expired/missing pre-commits", - "error", err, + "error", aerr, "miner", minerAddr, "expired", expired, "discarded", len(expired), From 5b31ae39ea7fd4743feab29d31173644f030a38d Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 7 Jun 2021 20:18:59 -0700 Subject: [PATCH 097/257] fix: test commit queue --- cmd/lotus-sim/simulation/commit_queue_test.go | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 cmd/lotus-sim/simulation/commit_queue_test.go diff --git a/cmd/lotus-sim/simulation/commit_queue_test.go b/cmd/lotus-sim/simulation/commit_queue_test.go new file mode 100644 index 000000000..1fee98154 --- /dev/null +++ b/cmd/lotus-sim/simulation/commit_queue_test.go @@ -0,0 +1,118 @@ +package simulation + +import ( + "testing" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/stretchr/testify/require" +) + +func TestCommitQueue(t *testing.T) { + var q commitQueue + addr1, err := address.NewIDAddress(1000) + require.NoError(t, err) + proofType := abi.RegisteredSealProof_StackedDrg64GiBV1_1 + require.NoError(t, q.enqueueProveCommit(addr1, 0, miner.SectorPreCommitInfo{ + SealProof: proofType, + SectorNumber: 0, + })) + require.NoError(t, q.enqueueProveCommit(addr1, 0, miner.SectorPreCommitInfo{ + SealProof: proofType, + SectorNumber: 1, + })) + require.NoError(t, q.enqueueProveCommit(addr1, 1, miner.SectorPreCommitInfo{ + SealProof: proofType, + SectorNumber: 2, + })) + require.NoError(t, q.enqueueProveCommit(addr1, 1, miner.SectorPreCommitInfo{ + SealProof: proofType, + SectorNumber: 3, + })) + require.NoError(t, q.enqueueProveCommit(addr1, 3, miner.SectorPreCommitInfo{ + SealProof: proofType, + SectorNumber: 4, + })) + require.NoError(t, q.enqueueProveCommit(addr1, 4, miner.SectorPreCommitInfo{ + SealProof: proofType, + SectorNumber: 5, + })) + require.NoError(t, q.enqueueProveCommit(addr1, 6, miner.SectorPreCommitInfo{ + SealProof: proofType, + SectorNumber: 6, + })) + + epoch := abi.ChainEpoch(0) + q.advanceEpoch(epoch) + _, _, ok := q.nextMiner() + require.False(t, ok) + + epoch += policy.GetPreCommitChallengeDelay() + q.advanceEpoch(epoch) + _, _, ok = q.nextMiner() + require.False(t, ok) + + // 0 : empty + non-empty + epoch++ + q.advanceEpoch(epoch) + addr, sectors, ok := q.nextMiner() + require.True(t, ok) + require.Equal(t, sectors.count(), 2) + require.Equal(t, addr, addr1) + sectors.finish(proofType, 1) + require.Equal(t, sectors.count(), 1) + require.EqualValues(t, []abi.SectorNumber{1}, sectors[proofType]) + + // 1 : non-empty + non-empty + epoch += 1 + q.advanceEpoch(epoch) + addr, sectors, ok = q.nextMiner() + require.True(t, ok) + require.Equal(t, addr, addr1) + require.Equal(t, sectors.count(), 3) + require.EqualValues(t, []abi.SectorNumber{1, 2, 3}, sectors[proofType]) + sectors.finish(proofType, 3) + require.Equal(t, sectors.count(), 0) + + // 2 : empty + empty + epoch += 1 + q.advanceEpoch(epoch) + _, _, ok = q.nextMiner() + require.False(t, ok) + + // 3 : empty + non-empty + epoch += 1 + q.advanceEpoch(epoch) + _, sectors, ok = q.nextMiner() + require.True(t, ok) + require.Equal(t, sectors.count(), 1) + require.EqualValues(t, []abi.SectorNumber{4}, sectors[proofType]) + + // 4 : non-empty + non-empty + epoch += 1 + q.advanceEpoch(epoch) + _, sectors, ok = q.nextMiner() + require.True(t, ok) + require.Equal(t, sectors.count(), 2) + require.EqualValues(t, []abi.SectorNumber{4, 5}, sectors[proofType]) + + // 5 : empty + non-empty + epoch += 1 + q.advanceEpoch(epoch) + _, sectors, ok = q.nextMiner() + require.True(t, ok) + require.Equal(t, sectors.count(), 2) + require.EqualValues(t, []abi.SectorNumber{4, 5}, sectors[proofType]) + sectors.finish(proofType, 1) + require.EqualValues(t, []abi.SectorNumber{5}, sectors[proofType]) + + // 6 + epoch += 1 + q.advanceEpoch(epoch) + _, sectors, ok = q.nextMiner() + require.True(t, ok) + require.Equal(t, sectors.count(), 2) + require.EqualValues(t, []abi.SectorNumber{5, 6}, sectors[proofType]) +} From dfdafa3c157466ab4867ea62420d7bf938c60724 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 7 Jun 2021 21:14:57 -0700 Subject: [PATCH 098/257] fix(lotus-sim): pretend all messages are BLS It doesn't really matter, and it ensures they all get executed in-order. --- cmd/lotus-sim/simulation/messages.go | 47 ++++++---------------------- 1 file changed, 10 insertions(+), 37 deletions(-) diff --git a/cmd/lotus-sim/simulation/messages.go b/cmd/lotus-sim/simulation/messages.go index 3f1c55179..8c12cac1a 100644 --- a/cmd/lotus-sim/simulation/messages.go +++ b/cmd/lotus-sim/simulation/messages.go @@ -3,15 +3,10 @@ package simulation import ( "context" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/crypto" blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" "github.com/ipfs/go-cid" cbg "github.com/whyrusleeping/cbor-gen" - "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/types" ) @@ -30,45 +25,23 @@ func toArray(store blockadt.Store, cids []cid.Cid) (cid.Cid, error) { // storeMessages packs a set of messages into a types.MsgMeta and returns the resulting CID. The // resulting CID is valid for the BlocKHeader's Messages field. func (nd *Node) storeMessages(ctx context.Context, messages []*types.Message) (cid.Cid, error) { - var blsMessages, sekpMessages []cid.Cid - fakeSig := make([]byte, 32) + // We store all messages as "bls" messages so they're executed in-order. This ensures + // accurate gas accounting. It also ensures we don't, e.g., try to fund a miner after we + // fail a pre-commit... + var msgCids []cid.Cid for _, msg := range messages { - protocol := msg.From.Protocol() - - // It's just a very convenient way to fill up accounts. - if msg.From == builtin.BurntFundsActorAddr { - protocol = address.SECP256K1 - } - switch protocol { - case address.SECP256K1: - chainMsg := &types.SignedMessage{ - Message: *msg, - Signature: crypto.Signature{ - Type: crypto.SigTypeSecp256k1, - Data: fakeSig, - }, - } - c, err := nd.Chainstore.PutMessage(chainMsg) - if err != nil { - return cid.Undef, err - } - sekpMessages = append(sekpMessages, c) - case address.BLS: - c, err := nd.Chainstore.PutMessage(msg) - if err != nil { - return cid.Undef, err - } - blsMessages = append(blsMessages, c) - default: - return cid.Undef, xerrors.Errorf("unexpected from address %q of type %d", msg.From, msg.From.Protocol()) + c, err := nd.Chainstore.PutMessage(msg) + if err != nil { + return cid.Undef, err } + msgCids = append(msgCids, c) } adtStore := nd.Chainstore.ActorStore(ctx) - blsMsgArr, err := toArray(adtStore, blsMessages) + blsMsgArr, err := toArray(adtStore, msgCids) if err != nil { return cid.Undef, err } - sekpMsgArr, err := toArray(adtStore, sekpMessages) + sekpMsgArr, err := toArray(adtStore, nil) if err != nil { return cid.Undef, err } From 0725019bdb37293e1e2190cdf061fedab1f6d06e Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 7 Jun 2021 21:36:40 -0700 Subject: [PATCH 099/257] feat(lotus-sim): completely pack block Instead of packing till we see "full". Prove-commits are large, we may have room for some more pre-commits. --- cmd/lotus-sim/simulation/step.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd/lotus-sim/simulation/step.go b/cmd/lotus-sim/simulation/step.go index 79ace1db2..0b92ed13e 100644 --- a/cmd/lotus-sim/simulation/step.go +++ b/cmd/lotus-sim/simulation/step.go @@ -246,10 +246,11 @@ func (ss *simulationState) packMessages(ctx context.Context, cb packFunc) error } for _, mgen := range messageGenerators { - if full, err := mgen(ctx, cb); err != nil { + // We're intentionally ignoring the "full" signal so we can try to pack a few more + // messages. + _, err := mgen(ctx, cb) + if err != nil { return xerrors.Errorf("when packing messages with %s: %w", functionName(mgen), err) - } else if full { - break } } return nil From f9ebe3017d170a4b85bb3c09bbb58120fbd3ac45 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 7 Jun 2021 21:41:40 -0700 Subject: [PATCH 100/257] test(lotus-sim): test commit-queue rollover --- cmd/lotus-sim/simulation/commit_queue_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cmd/lotus-sim/simulation/commit_queue_test.go b/cmd/lotus-sim/simulation/commit_queue_test.go index 1fee98154..1a7bd2749 100644 --- a/cmd/lotus-sim/simulation/commit_queue_test.go +++ b/cmd/lotus-sim/simulation/commit_queue_test.go @@ -115,4 +115,12 @@ func TestCommitQueue(t *testing.T) { require.True(t, ok) require.Equal(t, sectors.count(), 2) require.EqualValues(t, []abi.SectorNumber{5, 6}, sectors[proofType]) + + // 8 + epoch += 2 + q.advanceEpoch(epoch) + _, sectors, ok = q.nextMiner() + require.True(t, ok) + require.Equal(t, sectors.count(), 2) + require.EqualValues(t, []abi.SectorNumber{5, 6}, sectors[proofType]) } From a57c509e1e401c20511514df4127e1345c7bab73 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 10:27:20 -0700 Subject: [PATCH 101/257] fix(lotus-sim): cleanup and document pre-commit filtering --- cmd/lotus-sim/simulation/provecommit.go | 44 ++++++++++++++----------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/provecommit.go index b62a8bafe..3bcc3a720 100644 --- a/cmd/lotus-sim/simulation/provecommit.go +++ b/cmd/lotus-sim/simulation/provecommit.go @@ -147,34 +147,42 @@ func (ss *simulationState) packProveCommitsMiner( Params: enc, }); err != nil { // If we get a random error, or a fatal actor error, bail. - // Otherwise, just log it. aerr, ok := err.(aerrors.ActorError) if !ok || aerr.IsFatal() { return res, false, err } + // If we get a "not-found" error, try to remove any missing + // prove-commits and continue. This can happen either + // because: + // + // 1. The pre-commit failed on execution (but not when + // packing). This shouldn't happen, but we might as well + // gracefully handle it. + // 2. The pre-commit has expired. We'd have to be really + // backloged to hit this case, but we might as well handle + // it. if aerr.RetCode() == exitcode.ErrNotFound { - good, expired, err := ss.filterProveCommits(ctx, minerAddr, batch) + // First, split into "good" and "missing" + good, err := ss.filterProveCommits(ctx, minerAddr, batch) if err != nil { log.Errorw("failed to filter prove commits", "miner", minerAddr, "error", err) // fail with the original error. return res, false, aerr } - // If we've removed sectors (and kept some), try again. - // If we've removed all sectors, or no sectors, just - // move on and deliver the error. - if len(good) > 0 && len(expired) > 0 { - res.failed += len(expired) + removed := len(batch) - len(good) + // If they're all missing, skip. If they're all good, skip too (and log). + if len(good) > 0 && removed > 0 { + res.failed += removed // update the pending sector numbers in-place to remove the expired ones. - snos = snos[len(expired):] + snos = snos[removed:] copy(snos, good) - pending.finish(sealType, len(expired)) + pending.finish(sealType, removed) log.Errorw("failed to prove commit expired/missing pre-commits", "error", aerr, "miner", minerAddr, - "expired", expired, - "discarded", len(expired), + "discarded", removed, "kept", len(good), "epoch", ss.nextEpoch(), ) @@ -278,33 +286,31 @@ func (ss *simulationState) loadProveCommitsMiner(ctx context.Context, addr addre return nil } -// filterProveCommits filters out expired prove-commits. -func (ss *simulationState) filterProveCommits(ctx context.Context, minerAddr address.Address, snos []abi.SectorNumber) (good, expired []abi.SectorNumber, err error) { +// filterProveCommits filters out expired and/or missing pre-commits. +func (ss *simulationState) filterProveCommits(ctx context.Context, minerAddr address.Address, snos []abi.SectorNumber) ([]abi.SectorNumber, error) { _, minerState, err := ss.getMinerState(ctx, minerAddr) if err != nil { - return nil, nil, err + return nil, err } nextEpoch := ss.nextEpoch() nv := ss.sm.GetNtwkVersion(ctx, nextEpoch) av := actors.VersionForNetwork(nv) - good = make([]abi.SectorNumber, 0, len(snos)) + good := make([]abi.SectorNumber, 0, len(snos)) for _, sno := range snos { info, err := minerState.GetPrecommittedSector(sno) if err != nil { - return nil, nil, err + return nil, err } if info == nil { - expired = append(expired, sno) continue } msd := policy.GetMaxProveCommitDuration(av, info.Info.SealProof) if nextEpoch > info.PreCommitEpoch+msd { - expired = append(expired, sno) continue } good = append(good, sno) } - return good, expired, nil + return good, nil } From 0faacbe154229083a38704981b6620962b977007 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 10:42:59 -0700 Subject: [PATCH 102/257] feat(lotus-sim): store start tipset --- cmd/lotus-sim/simulation/node.go | 19 ++++++++------ cmd/lotus-sim/simulation/simulation.go | 35 ++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go index fa4d71028..5046222f3 100644 --- a/cmd/lotus-sim/simulation/node.go +++ b/cmd/lotus-sim/simulation/node.go @@ -78,17 +78,15 @@ func (nd *Node) LoadSim(ctx context.Context, name string) (*Simulation, error) { Node: nd, name: name, } - tskBytes, err := nd.MetadataDS.Get(sim.key("head")) + + var err error + sim.head, err = sim.loadNamedTipSet("head") if err != nil { - return nil, xerrors.Errorf("failed to load simulation %s: %w", name, err) + return nil, err } - tsk, err := types.TipSetKeyFromBytes(tskBytes) + sim.start, err = sim.loadNamedTipSet("start") if err != nil { - return nil, xerrors.Errorf("failed to parse simulation %s's tipset %v: %w", name, tskBytes, err) - } - sim.head, err = nd.Chainstore.LoadTipSet(tsk) - if err != nil { - return nil, xerrors.Errorf("failed to load simulation tipset %s: %w", tsk, err) + return nil, err } err = sim.loadConfig() @@ -126,6 +124,10 @@ func (nd *Node) CreateSim(ctx context.Context, name string, head *types.TipSet) return nil, xerrors.Errorf("simulation named %s already exists", name) } + if err := sim.storeNamedTipSet("start", head); err != nil { + return nil, xerrors.Errorf("failed to set simulation start: %w", err) + } + if err := sim.SetHead(head); err != nil { return nil, err } @@ -169,6 +171,7 @@ func (nd *Node) DeleteSim(ctx context.Context, name string) error { // TODO: make this a bit more generic? keys := []datastore.Key{ simulationPrefix.ChildString("head").ChildString(name), + simulationPrefix.ChildString("start").ChildString(name), simulationPrefix.ChildString("config").ChildString(name), } var err error diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index dc5cb3976..4b13f52f7 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -82,6 +82,7 @@ type Simulation struct { name string config config + start *types.TipSet sm *stmgr.StateManager // head @@ -159,6 +160,31 @@ func (sim *Simulation) key(subkey string) datastore.Key { return simulationPrefix.ChildString(subkey).ChildString(sim.name) } +// loadNamedTipSet the tipset with the given name (for this simulation) +func (sim *Simulation) loadNamedTipSet(name string) (*types.TipSet, error) { + tskBytes, err := sim.MetadataDS.Get(sim.key(name)) + if err != nil { + return nil, xerrors.Errorf("failed to load tipset %s/%s: %w", sim.name, name, err) + } + tsk, err := types.TipSetKeyFromBytes(tskBytes) + if err != nil { + return nil, xerrors.Errorf("failed to parse tipste %v (%s/%s): %w", tskBytes, sim.name, name, err) + } + ts, err := sim.Chainstore.LoadTipSet(tsk) + if err != nil { + return nil, xerrors.Errorf("failed to load tipset %s (%s/%s): %w", tsk, sim.name, name, err) + } + return ts, nil +} + +// storeNamedTipSet stores the tipset at name (relative to the simulation). +func (sim *Simulation) storeNamedTipSet(name string, ts *types.TipSet) error { + if err := sim.MetadataDS.Put(sim.key(name), ts.Key().Bytes()); err != nil { + return xerrors.Errorf("failed to store tipset (%s/%s): %w", sim.name, name, err) + } + return nil +} + // Load loads the simulation state. This will happen automatically on first use, but it can be // useful to preload for timing reasons. func (sim *Simulation) Load(ctx context.Context) error { @@ -171,6 +197,11 @@ func (sim *Simulation) GetHead() *types.TipSet { return sim.head } +// GetStart returns simulation's parent tipset. +func (sim *Simulation) GetStart() *types.TipSet { + return sim.start +} + // GetNetworkVersion returns the current network version for the simulation. func (sim *Simulation) GetNetworkVersion() network.Version { return sim.sm.GetNtwkVersion(context.TODO(), sim.head.Height()) @@ -179,8 +210,8 @@ func (sim *Simulation) GetNetworkVersion() network.Version { // SetHead updates the current head of the simulation and stores it in the metadata store. This is // called for every Simulation.Step. func (sim *Simulation) SetHead(head *types.TipSet) error { - if err := sim.MetadataDS.Put(sim.key("head"), head.Key().Bytes()); err != nil { - return xerrors.Errorf("failed to store simulation head: %w", err) + if err := sim.storeNamedTipSet("head", head); err != nil { + return err } sim.st = nil // we'll compute this on-demand. sim.head = head From 5f6733fe4425f28ccd7af99b70bdafb0910a7140 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 11:22:11 -0700 Subject: [PATCH 103/257] feat(lotus-sim): expose StateManager from Simulation --- cmd/lotus-sim/simulation/block.go | 4 ++-- cmd/lotus-sim/simulation/node.go | 8 ++++---- cmd/lotus-sim/simulation/power.go | 2 +- cmd/lotus-sim/simulation/precommit.go | 2 +- cmd/lotus-sim/simulation/provecommit.go | 6 +++--- cmd/lotus-sim/simulation/simulation.go | 10 +++++----- cmd/lotus-sim/simulation/step.go | 18 +++++++++--------- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/cmd/lotus-sim/simulation/block.go b/cmd/lotus-sim/simulation/block.go index 3a1181c1c..b3d0288b8 100644 --- a/cmd/lotus-sim/simulation/block.go +++ b/cmd/lotus-sim/simulation/block.go @@ -47,7 +47,7 @@ func (sim *Simulation) nextTicket() *types.Ticket { // This method does _not_ mutate local state (although it does add blocks to the datastore). func (sim *Simulation) makeTipSet(ctx context.Context, messages []*types.Message) (*types.TipSet, error) { parentTs := sim.head - parentState, parentRec, err := sim.sm.TipSetState(ctx, parentTs) + parentState, parentRec, err := sim.StateManager.TipSetState(ctx, parentTs) if err != nil { return nil, xerrors.Errorf("failed to compute parent tipset: %w", err) } @@ -80,7 +80,7 @@ func (sim *Simulation) makeTipSet(ctx context.Context, messages []*types.Message return nil, xerrors.Errorf("failed to create new tipset: %w", err) } now := time.Now() - _, _, err = sim.sm.TipSetState(ctx, newTipSet) + _, _, err = sim.StateManager.TipSetState(ctx, newTipSet) if err != nil { return nil, xerrors.Errorf("failed to compute new tipset: %w", err) } diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go index 5046222f3..105c4c490 100644 --- a/cmd/lotus-sim/simulation/node.go +++ b/cmd/lotus-sim/simulation/node.go @@ -98,7 +98,7 @@ func (nd *Node) LoadSim(ctx context.Context, name string) (*Simulation, error) { if err != nil { return nil, xerrors.Errorf("failed to create upgrade schedule for simulation %s: %w", name, err) } - sim.sm, err = stmgr.NewStateManagerWithUpgradeSchedule(nd.Chainstore, us) + sim.StateManager, err = stmgr.NewStateManagerWithUpgradeSchedule(nd.Chainstore, us) if err != nil { return nil, xerrors.Errorf("failed to create state manager for simulation %s: %w", name, err) } @@ -114,9 +114,9 @@ func (nd *Node) CreateSim(ctx context.Context, name string, head *types.TipSet) return nil, xerrors.Errorf("simulation name %q cannot contain a '/'", name) } sim := &Simulation{ - name: name, - Node: nd, - sm: stmgr.NewStateManager(nd.Chainstore), + name: name, + Node: nd, + StateManager: stmgr.NewStateManager(nd.Chainstore), } if has, err := nd.MetadataDS.Has(sim.key("head")); err != nil { return nil, err diff --git a/cmd/lotus-sim/simulation/power.go b/cmd/lotus-sim/simulation/power.go index a86b691f3..f05dadf19 100644 --- a/cmd/lotus-sim/simulation/power.go +++ b/cmd/lotus-sim/simulation/power.go @@ -22,7 +22,7 @@ func (sim *Simulation) loadClaims(ctx context.Context, height abi.ChainEpoch) (m return nil, xerrors.Errorf("when projecting growth, failed to lookup lookback epoch: %w", err) } - powerActor, err := sim.sm.LoadActor(ctx, power.Address, ts) + powerActor, err := sim.StateManager.LoadActor(ctx, power.Address, ts) if err != nil { return nil, err } diff --git a/cmd/lotus-sim/simulation/precommit.go b/cmd/lotus-sim/simulation/precommit.go index 619ba467d..38b745a52 100644 --- a/cmd/lotus-sim/simulation/precommit.go +++ b/cmd/lotus-sim/simulation/precommit.go @@ -93,7 +93,7 @@ func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (ful func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, minerAddr address.Address, count int) (int, bool, error) { // Load everything. epoch := ss.nextEpoch() - nv := ss.sm.GetNtwkVersion(ctx, epoch) + nv := ss.StateManager.GetNtwkVersion(ctx, epoch) actor, minerState, err := ss.getMinerState(ctx, minerAddr) if err != nil { return 0, false, err diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/provecommit.go index 3bcc3a720..208af38a7 100644 --- a/cmd/lotus-sim/simulation/provecommit.go +++ b/cmd/lotus-sim/simulation/provecommit.go @@ -111,7 +111,7 @@ func (ss *simulationState) packProveCommitsMiner( return res, false, err } - nv := ss.sm.GetNtwkVersion(ctx, ss.nextEpoch()) + nv := ss.StateManager.GetNtwkVersion(ctx, ss.nextEpoch()) for sealType, snos := range pending { if nv >= network.Version13 { for len(snos) > minProveCommitBatchSize { @@ -260,7 +260,7 @@ func (ss *simulationState) loadProveCommitsMiner(ctx context.Context, addr addre // Find all pending prove commits and group by proof type. Really, there should never // (except during upgrades be more than one type. nextEpoch := ss.nextEpoch() - nv := ss.sm.GetNtwkVersion(ctx, nextEpoch) + nv := ss.StateManager.GetNtwkVersion(ctx, nextEpoch) av := actors.VersionForNetwork(nv) var total, dropped int @@ -294,7 +294,7 @@ func (ss *simulationState) filterProveCommits(ctx context.Context, minerAddr add } nextEpoch := ss.nextEpoch() - nv := ss.sm.GetNtwkVersion(ctx, nextEpoch) + nv := ss.StateManager.GetNtwkVersion(ctx, nextEpoch) av := actors.VersionForNetwork(nv) good := make([]abi.SectorNumber, 0, len(snos)) diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index 4b13f52f7..d33f3e94f 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -79,11 +79,11 @@ func (c *config) upgradeSchedule() (stmgr.UpgradeSchedule, error) { // Simulation specifies a lotus-sim simulation. type Simulation struct { *Node + StateManager *stmgr.StateManager name string config config start *types.TipSet - sm *stmgr.StateManager // head st *state.StateTree @@ -125,11 +125,11 @@ func (sim *Simulation) saveConfig() error { // The state-tree is cached until the head is changed. func (sim *Simulation) stateTree(ctx context.Context) (*state.StateTree, error) { if sim.st == nil { - st, _, err := sim.sm.TipSetState(ctx, sim.head) + st, _, err := sim.StateManager.TipSetState(ctx, sim.head) if err != nil { return nil, err } - sim.st, err = sim.sm.StateTree(st) + sim.st, err = sim.StateManager.StateTree(st) if err != nil { return nil, err } @@ -204,7 +204,7 @@ func (sim *Simulation) GetStart() *types.TipSet { // GetNetworkVersion returns the current network version for the simulation. func (sim *Simulation) GetNetworkVersion() network.Version { - return sim.sm.GetNtwkVersion(context.TODO(), sim.head.Height()) + return sim.StateManager.GetNtwkVersion(context.TODO(), sim.head.Height()) } // SetHead updates the current head of the simulation and stores it in the metadata store. This is @@ -256,7 +256,7 @@ func (sim *Simulation) SetUpgradeHeight(nv network.Version, epoch abi.ChainEpoch return err } - sim.sm = sm + sim.StateManager = sm return nil } diff --git a/cmd/lotus-sim/simulation/step.go b/cmd/lotus-sim/simulation/step.go index 0b92ed13e..9eddd039b 100644 --- a/cmd/lotus-sim/simulation/step.go +++ b/cmd/lotus-sim/simulation/step.go @@ -78,8 +78,8 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag // This isn't what the network does, but it makes things easier. Otherwise, we'd need to run // migrations before this epoch and I'd rather not deal with that. nextHeight := parentTs.Height() + 1 - prevVer := ss.sm.GetNtwkVersion(ctx, nextHeight-1) - nextVer := ss.sm.GetNtwkVersion(ctx, nextHeight) + prevVer := ss.StateManager.GetNtwkVersion(ctx, nextHeight-1) + nextVer := ss.StateManager.GetNtwkVersion(ctx, nextHeight) if nextVer != prevVer { log.Warnw("packing no messages for version upgrade block", "old", prevVer, @@ -91,7 +91,7 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag // Next, we compute the state for the parent tipset. In practice, this will likely be // cached. - parentState, _, err := ss.sm.TipSetState(ctx, parentTs) + parentState, _, err := ss.StateManager.TipSetState(ctx, parentTs) if err != nil { return nil, err } @@ -102,17 +102,17 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag // 1. We don't charge a fee. // 2. The runtime has "fake" proof logic. // 3. We don't actually save any of the results. - r := store.NewChainRand(ss.sm.ChainStore(), parentTs.Cids()) + r := store.NewChainRand(ss.StateManager.ChainStore(), parentTs.Cids()) vmopt := &vm.VMOpts{ StateBase: parentState, Epoch: nextHeight, Rand: r, - Bstore: ss.sm.ChainStore().StateBlockstore(), - Syscalls: ss.sm.ChainStore().VMSys(), - CircSupplyCalc: ss.sm.GetVMCirculatingSupply, - NtwkVersion: ss.sm.GetNtwkVersion, + Bstore: ss.StateManager.ChainStore().StateBlockstore(), + Syscalls: ss.StateManager.ChainStore().VMSys(), + CircSupplyCalc: ss.StateManager.GetVMCirculatingSupply, + NtwkVersion: ss.StateManager.GetNtwkVersion, BaseFee: abi.NewTokenAmount(0), // FREE! - LookbackState: stmgr.LookbackStateGetterForTipset(ss.sm, parentTs), + LookbackState: stmgr.LookbackStateGetterForTipset(ss.StateManager, parentTs), } vmi, err := vm.NewVM(ctx, vmopt) if err != nil { From ccdd660f0ddf057e91e02c86b0089d1982a1c4e8 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 11:23:51 -0700 Subject: [PATCH 104/257] feat(lotus-sim): more stats --- cmd/lotus-sim/stat.go | 61 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/cmd/lotus-sim/stat.go b/cmd/lotus-sim/stat.go index 25f9f5d51..b51853b31 100644 --- a/cmd/lotus-sim/stat.go +++ b/cmd/lotus-sim/stat.go @@ -1,12 +1,31 @@ package main import ( + "context" "fmt" "text/tabwriter" + "time" "github.com/urfave/cli/v2" + + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" ) +func getTotalPower(ctx context.Context, sm *stmgr.StateManager, ts *types.TipSet) (power.Claim, error) { + actor, err := sm.LoadActor(ctx, power.Address, ts) + if err != nil { + return power.Claim{}, err + } + state, err := power.Load(sm.ChainStore().ActorStore(ctx), actor) + if err != nil { + return power.Claim{}, err + } + return state.TotalPower() +} + var infoSimCommand = &cli.Command{ Name: "info", Description: "Output information about the simulation.", @@ -21,11 +40,45 @@ var infoSimCommand = &cli.Command{ if err != nil { return err } + + powerNow, err := getTotalPower(cctx.Context, sim.StateManager, sim.GetHead()) + if err != nil { + return err + } + powerStart, err := getTotalPower(cctx.Context, sim.StateManager, sim.GetStart()) + if err != nil { + return err + } + powerGrowth := big.Sub(powerNow.RawBytePower, powerStart.RawBytePower) + tw := tabwriter.NewWriter(cctx.App.Writer, 8, 8, 0, ' ', 0) - fmt.Fprintln(tw, "Name:\t", sim.Name()) - fmt.Fprintln(tw, "Height:\t", sim.GetHead().Height()) - fmt.Fprintln(tw, "TipSet:\t", sim.GetHead()) - fmt.Fprintln(tw, "Network Version:\t", sim.GetNetworkVersion()) + + head := sim.GetHead() + start := sim.GetStart() + headEpoch := head.Height() + firstEpoch := start.Height() + 1 + + headTime := time.Unix(int64(head.MinTimestamp()), 0) + startTime := time.Unix(int64(start.MinTimestamp()), 0) + duration := headTime.Sub(startTime) + + // growth rate in size/day + growthRate := big.Div( + big.Mul(powerGrowth, big.NewInt(int64(24*time.Hour))), + big.NewInt(int64(duration)), + ) + + fmt.Fprintf(tw, "Name:\t%s\n", sim.Name()) + fmt.Fprintf(tw, "Head:\t%s\n", head) + fmt.Fprintf(tw, "Last Epoch:\t%d\n", headEpoch) + fmt.Fprintf(tw, "First Epoch:\t%d\n", firstEpoch) + fmt.Fprintf(tw, "Length:\t%d\n", headEpoch-firstEpoch) + fmt.Fprintf(tw, "Date:\t%s\n", headTime) + fmt.Fprintf(tw, "Duration:\t%s\n", duration) + fmt.Fprintf(tw, "Power:\t%s\n", types.SizeStr(powerNow.RawBytePower)) + fmt.Fprintf(tw, "Power Growth:\t%s\n", types.SizeStr(powerGrowth)) + fmt.Fprintf(tw, "Power Growth Rate:\t%s/day\n", types.SizeStr(growthRate)) + fmt.Fprintf(tw, "Network Version:\t%d\n", sim.GetNetworkVersion()) return tw.Flush() }, } From c5dc67ccd8b3d2fd2d83394930b961e7a69416ab Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 11:58:19 -0700 Subject: [PATCH 105/257] feat(lotus-sim): add a walk function This way, we can easily walk the chain and: 1. Inspect messages and their results. 2. Inspect tipset state. --- cmd/lotus-sim/simulation/simulation.go | 54 ++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index d33f3e94f..4b571a808 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -6,6 +6,7 @@ import ( "golang.org/x/xerrors" + "github.com/ipfs/go-cid" "github.com/ipfs/go-datastore" logging "github.com/ipfs/go-log/v2" @@ -16,6 +17,7 @@ import ( "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" + blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" ) @@ -275,3 +277,55 @@ func (sim *Simulation) ListUpgrades() (stmgr.UpgradeSchedule, error) { } return pending, nil } + +type AppliedMessage struct { + types.Message + types.MessageReceipt +} + +// Walk walks the simulation's chain from the current head back to the first tipset. +func (sim *Simulation) Walk( + ctx context.Context, + cb func(sm *stmgr.StateManager, + ts *types.TipSet, + stCid cid.Cid, + messages []*AppliedMessage) error, +) error { + store := sim.Chainstore.ActorStore(ctx) + ts := sim.head + stCid, recCid, err := sim.StateManager.TipSetState(ctx, ts) + if err != nil { + return err + } + for !ts.Equals(sim.start) { + msgs, err := sim.Chainstore.MessagesForTipset(ts) + if err != nil { + return err + } + + recs, err := blockadt.AsArray(store, recCid) + if err != nil { + return xerrors.Errorf("amt load: %w", err) + } + applied := make([]*AppliedMessage, len(msgs)) + var rec types.MessageReceipt + err = recs.ForEach(&rec, func(i int64) error { + applied[i] = &AppliedMessage{ + Message: *msgs[i].VMMessage(), + MessageReceipt: rec, + } + return nil + }) + if err != nil { + return err + } + + if err := cb(sim.StateManager, ts, stCid, applied); err != nil { + return err + } + + stCid = ts.MinTicketBlock().ParentStateRoot + recCid = ts.MinTicketBlock().ParentMessageReceipts + } + return nil +} From 8410b0f79f5d51612613ef5fc2a4d034bded3544 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 13:32:32 -0700 Subject: [PATCH 106/257] feat(lotus-sim): add a feature to copy/rename simulation. Useful to backup old simulations before creating a new one. --- cmd/lotus-sim/simulation/node.go | 52 +++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go index 105c4c490..085a94c32 100644 --- a/cmd/lotus-sim/simulation/node.go +++ b/cmd/lotus-sim/simulation/node.go @@ -164,19 +164,57 @@ func (nd *Node) ListSims(ctx context.Context) ([]string, error) { } } +var simFields = []string{"head", "start", "config"} + // DeleteSim deletes a simulation and all related metadata. // // NOTE: This function does not delete associated messages, blocks, or chain state. func (nd *Node) DeleteSim(ctx context.Context, name string) error { - // TODO: make this a bit more generic? - keys := []datastore.Key{ - simulationPrefix.ChildString("head").ChildString(name), - simulationPrefix.ChildString("start").ChildString(name), - simulationPrefix.ChildString("config").ChildString(name), - } var err error - for _, key := range keys { + for _, field := range simFields { + key := simulationPrefix.ChildString(field).ChildString(name) err = multierr.Append(err, nd.MetadataDS.Delete(key)) } return err } + +// CopySim copies a simulation. +func (nd *Node) CopySim(ctx context.Context, oldName, newName string) error { + values := make(map[string][]byte) + for _, field := range simFields { + key := simulationPrefix.ChildString(field).ChildString(oldName) + value, err := nd.MetadataDS.Get(key) + if err == datastore.ErrNotFound { + continue + } else if err != nil { + return err + } + values[field] = value + } + + if _, ok := values["head"]; !ok { + return xerrors.Errorf("simulation named %s not found", oldName) + } + + for _, field := range simFields { + key := simulationPrefix.ChildString(field).ChildString(newName) + var err error + if value, ok := values[field]; ok { + err = nd.MetadataDS.Put(key, value) + } else { + err = nd.MetadataDS.Delete(key) + } + if err != nil { + return err + } + } + return nil +} + +// RenameSim renames a simulation. +func (nd *Node) RenameSim(ctx context.Context, oldName, newName string) error { + if err := nd.CopySim(ctx, oldName, newName); err != nil { + return err + } + return nd.DeleteSim(ctx, oldName) +} From 4f0b9eefc159f025b5b12e0b2d76e2ed4d9e92c4 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 13:42:01 -0700 Subject: [PATCH 107/257] fix(lotus-sim): check for slash in names on copy --- cmd/lotus-sim/simulation/node.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go index 085a94c32..73c739e5b 100644 --- a/cmd/lotus-sim/simulation/node.go +++ b/cmd/lotus-sim/simulation/node.go @@ -180,6 +180,13 @@ func (nd *Node) DeleteSim(ctx context.Context, name string) error { // CopySim copies a simulation. func (nd *Node) CopySim(ctx context.Context, oldName, newName string) error { + if strings.Contains(newName, "/") { + return xerrors.Errorf("simulation name %q cannot contain a '/'", newName) + } + if strings.Contains(oldName, "/") { + return xerrors.Errorf("simulation name %q cannot contain a '/'", oldName) + } + values := make(map[string][]byte) for _, field := range simFields { key := simulationPrefix.ChildString(field).ChildString(oldName) From bb4753ffbfc7ce2a62554d7eddb80b76d1bdad0c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 13:43:39 -0700 Subject: [PATCH 108/257] feat(lotus-sim): add commands to rename and copy --- cmd/lotus-sim/copy.go | 24 ++++++++++++++++++++++++ cmd/lotus-sim/main.go | 3 +++ cmd/lotus-sim/rename.go | 24 ++++++++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 cmd/lotus-sim/copy.go create mode 100644 cmd/lotus-sim/rename.go diff --git a/cmd/lotus-sim/copy.go b/cmd/lotus-sim/copy.go new file mode 100644 index 000000000..eeb8eb1aa --- /dev/null +++ b/cmd/lotus-sim/copy.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + + "github.com/urfave/cli/v2" +) + +var copySimCommand = &cli.Command{ + Name: "copy", + ArgsUsage: "", + Action: func(cctx *cli.Context) error { + node, err := open(cctx) + if err != nil { + return err + } + defer node.Close() + if cctx.NArg() != 1 { + return fmt.Errorf("expected 1 argument") + } + name := cctx.Args().First() + return node.CopySim(cctx.Context, cctx.String("simulation"), name) + }, +} diff --git a/cmd/lotus-sim/main.go b/cmd/lotus-sim/main.go index 8fe313355..cf8903d72 100644 --- a/cmd/lotus-sim/main.go +++ b/cmd/lotus-sim/main.go @@ -14,7 +14,10 @@ import ( var root []*cli.Command = []*cli.Command{ createSimCommand, deleteSimCommand, + copySimCommand, + renameSimCommand, listSimCommand, + runSimCommand, infoSimCommand, upgradeCommand, diff --git a/cmd/lotus-sim/rename.go b/cmd/lotus-sim/rename.go new file mode 100644 index 000000000..833a57e96 --- /dev/null +++ b/cmd/lotus-sim/rename.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + + "github.com/urfave/cli/v2" +) + +var renameSimCommand = &cli.Command{ + Name: "rename", + ArgsUsage: "", + Action: func(cctx *cli.Context) error { + node, err := open(cctx) + if err != nil { + return err + } + defer node.Close() + if cctx.NArg() != 1 { + return fmt.Errorf("expected 1 argument") + } + name := cctx.Args().First() + return node.RenameSim(cctx.Context, cctx.String("simulation"), name) + }, +} From 77f0fee58e407d6e62babb19798276ba6d2e8bf9 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 14:09:47 -0700 Subject: [PATCH 109/257] chore(lotus-sim): fix comment about simulation block miner --- cmd/lotus-sim/simulation/block.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/lotus-sim/simulation/block.go b/cmd/lotus-sim/simulation/block.go index b3d0288b8..31e7a1a79 100644 --- a/cmd/lotus-sim/simulation/block.go +++ b/cmd/lotus-sim/simulation/block.go @@ -40,7 +40,7 @@ func (sim *Simulation) nextTicket() *types.Ticket { // makeTipSet generates and executes the next tipset from the given messages. This method: // // 1. Stores the given messages in the Chainstore. -// 2. Creates and persists a single block mined by f01000. +// 2. Creates and persists a single block mined by the same miner as the parent. // 3. Creates a tipset from this block and executes it. // 4. Returns the resulting tipset. // From 4578e0dd8d11061700f3dc466214808512be3649 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 14:13:42 -0700 Subject: [PATCH 110/257] chore(lotus-sim): remove dead code --- cmd/lotus-sim/main.go | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/cmd/lotus-sim/main.go b/cmd/lotus-sim/main.go index cf8903d72..13b5d2282 100644 --- a/cmd/lotus-sim/main.go +++ b/cmd/lotus-sim/main.go @@ -6,9 +6,6 @@ import ( logging "github.com/ipfs/go-log/v2" "github.com/urfave/cli/v2" - - "github.com/filecoin-project/lotus/chain/actors/builtin/power" - "github.com/filecoin-project/lotus/chain/stmgr" ) var root []*cli.Command = []*cli.Command{ @@ -53,39 +50,3 @@ func main() { return } } - -func run(cctx *cli.Context) error { - ctx := cctx.Context - - node, err := open(cctx) - if err != nil { - return err - } - defer node.Close() - - if err := node.Chainstore.Load(); err != nil { - return err - } - - ts := node.Chainstore.GetHeaviestTipSet() - - st, err := stmgr.NewStateManagerWithUpgradeSchedule(node.Chainstore, nil) - if err != nil { - return err - } - - powerTableActor, err := st.LoadActor(ctx, power.Address, ts) - if err != nil { - return err - } - powerTable, err := power.Load(node.Chainstore.ActorStore(ctx), powerTableActor) - if err != nil { - return err - } - allMiners, err := powerTable.ListAllMiners() - if err != nil { - return err - } - fmt.Printf("miner count: %d\n", len(allMiners)) - return nil -} From e097ba8640acd532e2cba41f77e4088d77317179 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 14:22:16 -0700 Subject: [PATCH 111/257] feat(lotus-sim): wire up signal handler --- cmd/lotus-sim/main.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cmd/lotus-sim/main.go b/cmd/lotus-sim/main.go index 13b5d2282..bfcad728d 100644 --- a/cmd/lotus-sim/main.go +++ b/cmd/lotus-sim/main.go @@ -1,8 +1,11 @@ package main import ( + "context" "fmt" "os" + "os/signal" + "syscall" logging "github.com/ipfs/go-log/v2" "github.com/urfave/cli/v2" @@ -44,7 +47,11 @@ func main() { }, } - if err := app.Run(os.Args); err != nil { + ctx, cancel := signal.NotifyContext(context.Background(), + syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP) + defer cancel() + + if err := app.RunContext(ctx, os.Args); err != nil { fmt.Fprintf(os.Stderr, "Error: %s\n", err) os.Exit(1) return From ba65a1ba9bf468d5c311e9aa2b2aab56e573534f Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 14:54:18 -0700 Subject: [PATCH 112/257] chore(lotus-sim): rename stat to info --- cmd/lotus-sim/{stat.go => info.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cmd/lotus-sim/{stat.go => info.go} (100%) diff --git a/cmd/lotus-sim/stat.go b/cmd/lotus-sim/info.go similarity index 100% rename from cmd/lotus-sim/stat.go rename to cmd/lotus-sim/info.go From 747b3d3e572ab3f4f504be00acd5e0d44c13681b Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 15:20:52 -0700 Subject: [PATCH 113/257] fix(lotus-sim): skip miners without power when loading --- cmd/lotus-sim/simulation/power.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/lotus-sim/simulation/power.go b/cmd/lotus-sim/simulation/power.go index f05dadf19..9d0aceafe 100644 --- a/cmd/lotus-sim/simulation/power.go +++ b/cmd/lotus-sim/simulation/power.go @@ -32,6 +32,10 @@ func (sim *Simulation) loadClaims(ctx context.Context, height abi.ChainEpoch) (m return nil, err } err = powerState.ForEachClaim(func(miner address.Address, claim power.Claim) error { + // skip miners without power + if claim.RawBytePower.IsZero() { + return nil + } powerTable[miner] = claim return nil }) From 88af350774dfb7563c11f9386b7eebfd66c1ecc8 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Jun 2021 17:05:03 -0700 Subject: [PATCH 114/257] fix(lotus-sim): use global base-fee value --- cmd/lotus-sim/simulation/step.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/lotus-sim/simulation/step.go b/cmd/lotus-sim/simulation/step.go index 9eddd039b..b99f318d6 100644 --- a/cmd/lotus-sim/simulation/step.go +++ b/cmd/lotus-sim/simulation/step.go @@ -111,7 +111,7 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag Syscalls: ss.StateManager.ChainStore().VMSys(), CircSupplyCalc: ss.StateManager.GetVMCirculatingSupply, NtwkVersion: ss.StateManager.GetNtwkVersion, - BaseFee: abi.NewTokenAmount(0), // FREE! + BaseFee: baseFee, // FREE! LookbackState: stmgr.LookbackStateGetterForTipset(ss.StateManager, parentTs), } vmi, err := vm.NewVM(ctx, vmopt) From 0075abea5e280cc355dc524dff246304d8b8e99d Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 13:00:56 -0700 Subject: [PATCH 115/257] fix(vm): always specify an ActorErr when ApplyMessage fails. This case shouldn't actually happen, but we might as well be consistent. --- chain/vm/vm.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chain/vm/vm.go b/chain/vm/vm.go index 1b8424eee..9f9398630 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -439,6 +439,8 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet, }, GasCosts: &gasOutputs, Duration: time.Since(start), + ActorErr: aerrors.Newf(exitcode.SysErrSenderInvalid, + "message gas limit does not cover on-chain gas costs"), }, nil } From 86e459f58502833ffb8d49082026123ec291110c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 13:47:38 -0700 Subject: [PATCH 116/257] feat(lotus-sim): return receipt Instead of returning a "full" boolean and an error, return a receipt and an error. We now use the error to indicate if the block is "full". --- cmd/lotus-sim/simulation/precommit.go | 45 +++--- cmd/lotus-sim/simulation/provecommit.go | 173 +++++++++++++----------- cmd/lotus-sim/simulation/step.go | 34 +++-- cmd/lotus-sim/simulation/wdpost.go | 12 +- 4 files changed, 148 insertions(+), 116 deletions(-) diff --git a/cmd/lotus-sim/simulation/precommit.go b/cmd/lotus-sim/simulation/precommit.go index 38b745a52..b048aa66c 100644 --- a/cmd/lotus-sim/simulation/precommit.go +++ b/cmd/lotus-sim/simulation/precommit.go @@ -31,8 +31,11 @@ func makeCommR(minerAddr address.Address, sno abi.SectorNumber) cid.Cid { } // packPreCommits packs pre-commit messages until the block is full. -func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (full bool, _err error) { - var top1Count, top10Count, restCount int +func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (_err error) { + var ( + full bool + top1Count, top10Count, restCount int + ) defer func() { if _err != nil { return @@ -74,16 +77,20 @@ func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (ful restMiners++ default: // Well, we've run through all miners. - return false, nil + return nil } - added, full, err := ss.packPreCommitsMiner(ctx, cb, minerAddr, maxProveCommitBatchSize) + var ( + added int + err error + ) + added, full, err = ss.packPreCommitsMiner(ctx, cb, minerAddr, maxProveCommitBatchSize) if err != nil { - return false, xerrors.Errorf("failed to pack precommits for miner %s: %w", minerAddr, err) + return xerrors.Errorf("failed to pack precommits for miner %s: %w", minerAddr, err) } *count += added if full { - return true, nil + return nil } } } @@ -111,17 +118,15 @@ func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, } if big.Cmp(minerBalance, minFunds) < 0 { - full, err := cb(&types.Message{ + if _, err := cb(&types.Message{ From: builtin.BurntFundsActorAddr, To: minerAddr, Value: targetFunds, Method: builtin.MethodSend, - }) - if err != nil { - return 0, false, xerrors.Errorf("failed to fund miner %s: %w", minerAddr, err) - } - if full { + }); err == ErrOutOfGas { return 0, true, nil + } else if err != nil { + return 0, false, xerrors.Errorf("failed to fund miner %s: %w", minerAddr, err) } } @@ -168,18 +173,18 @@ func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, } // NOTE: just in-case, sendAndFund will "fund" and re-try for any message // that fails due to "insufficient funds". - if full, err := sendAndFund(cb, &types.Message{ + if _, err := sendAndFund(cb, &types.Message{ To: minerAddr, From: minerInfo.Worker, Value: abi.NewTokenAmount(0), Method: miner.Methods.PreCommitSectorBatch, Params: enc, - }); err != nil { - return 0, false, err - } else if full { + }); err == ErrOutOfGas { // try again with a smaller batch. targetBatchSize /= 2 continue + } else if err != nil { + return 0, false, err } for _, info := range batch { @@ -196,14 +201,16 @@ func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, if err != nil { return 0, false, err } - if full, err := sendAndFund(cb, &types.Message{ + if _, err := sendAndFund(cb, &types.Message{ To: minerAddr, From: minerInfo.Worker, Value: abi.NewTokenAmount(0), Method: miner.Methods.PreCommitSector, Params: enc, - }); full || err != nil { - return added, full, err + }); err == ErrOutOfGas { + return added, true, nil + } else if err != nil { + return added, false, err } if err := ss.commitQueue.enqueueProveCommit(minerAddr, epoch, info); err != nil { diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/provecommit.go index 208af38a7..1a615d830 100644 --- a/cmd/lotus-sim/simulation/provecommit.go +++ b/cmd/lotus-sim/simulation/provecommit.go @@ -23,11 +23,11 @@ import ( // packProveCOmmits packs all prove-commits for all "ready to be proven" sectors until it fills the // block or runs out. -func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_full bool, _err error) { +func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_err error) { // Roll the commitQueue forward. ss.commitQueue.advanceEpoch(ss.nextEpoch()) - var failed, done, unbatched, count int + var full, failed, done, unbatched, count int defer func() { if _err != nil { return @@ -39,32 +39,33 @@ func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_ "failed", failed, "unbatched", unbatched, "miners-processed", count, - "filled-block", _full, + "filled-block", full, ) }() for { addr, pending, ok := ss.commitQueue.nextMiner() if !ok { - return false, nil + return nil } - res, full, err := ss.packProveCommitsMiner(ctx, cb, addr, pending) + res, err := ss.packProveCommitsMiner(ctx, cb, addr, pending) if err != nil { - return false, err + return err } failed += res.failed done += res.done unbatched += res.unbatched count++ - if full { - return true, nil + if res.full { + return nil } } } type proveCommitResult struct { done, failed, unbatched int + full bool } // sendAndFund "packs" the given message, funding the actor if necessary. It: @@ -75,25 +76,24 @@ type proveCommitResult struct { // somewhere) and re-tries the message.0 // // NOTE: If the message fails a second time, the funds won't be "unsent". -func sendAndFund(send packFunc, msg *types.Message) (bool, error) { - full, err := send(msg) +func sendAndFund(send packFunc, msg *types.Message) (*types.MessageReceipt, error) { + res, err := send(msg) aerr, ok := err.(aerrors.ActorError) if !ok || aerr.RetCode() != exitcode.ErrInsufficientFunds { - return full, err + return res, err } // Ok, insufficient funds. Let's fund this miner and try again. - full, err = send(&types.Message{ + _, err = send(&types.Message{ From: builtin.BurntFundsActorAddr, To: msg.To, Value: targetFunds, Method: builtin.MethodSend, }) if err != nil { - return false, xerrors.Errorf("failed to fund %s: %w", msg.To, err) - } - // ok, nothing's going to work. - if full { - return true, nil + if err != ErrOutOfGas { + err = xerrors.Errorf("failed to fund %s: %w", msg.To, err) + } + return nil, err } return send(msg) } @@ -105,10 +105,10 @@ func sendAndFund(send packFunc, msg *types.Message) (bool, error) { func (ss *simulationState) packProveCommitsMiner( ctx context.Context, cb packFunc, minerAddr address.Address, pending minerPendingCommits, -) (res proveCommitResult, full bool, _err error) { +) (res proveCommitResult, _err error) { info, err := ss.getMinerInfo(ctx, minerAddr) if err != nil { - return res, false, err + return res, err } nv := ss.StateManager.GetNtwkVersion(ctx, ss.nextEpoch()) @@ -123,7 +123,7 @@ func (ss *simulationState) packProveCommitsMiner( proof, err := mockAggregateSealProof(sealType, minerAddr, batchSize) if err != nil { - return res, false, err + return res, err } params := miner5.ProveCommitAggregateParams{ @@ -136,24 +136,27 @@ func (ss *simulationState) packProveCommitsMiner( enc, err := actors.SerializeParams(¶ms) if err != nil { - return res, false, err + return res, err } - if full, err := sendAndFund(cb, &types.Message{ + if _, err := sendAndFund(cb, &types.Message{ From: info.Worker, To: minerAddr, Value: abi.NewTokenAmount(0), Method: miner.Methods.ProveCommitAggregate, Params: enc, - }); err != nil { + }); err == nil { + res.done += len(batch) + } else if err == ErrOutOfGas { + res.full = true + return res, nil + } else if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() { // If we get a random error, or a fatal actor error, bail. - aerr, ok := err.(aerrors.ActorError) - if !ok || aerr.IsFatal() { - return res, false, err - } - // If we get a "not-found" error, try to remove any missing - // prove-commits and continue. This can happen either - // because: + return res, err + } else if aerr.RetCode() == exitcode.ErrNotFound || aerr.RetCode() == exitcode.ErrIllegalArgument { + // If we get a "not-found" or illegal argument error, try to + // remove any missing prove-commits and continue. This can + // happen either because: // // 1. The pre-commit failed on execution (but not when // packing). This shouldn't happen, but we might as well @@ -161,34 +164,56 @@ func (ss *simulationState) packProveCommitsMiner( // 2. The pre-commit has expired. We'd have to be really // backloged to hit this case, but we might as well handle // it. - if aerr.RetCode() == exitcode.ErrNotFound { - // First, split into "good" and "missing" - good, err := ss.filterProveCommits(ctx, minerAddr, batch) - if err != nil { - log.Errorw("failed to filter prove commits", "miner", minerAddr, "error", err) - // fail with the original error. - return res, false, aerr - } - removed := len(batch) - len(good) - // If they're all missing, skip. If they're all good, skip too (and log). - if len(good) > 0 && removed > 0 { - res.failed += removed - - // update the pending sector numbers in-place to remove the expired ones. - snos = snos[removed:] - copy(snos, good) - pending.finish(sealType, removed) - - log.Errorw("failed to prove commit expired/missing pre-commits", - "error", aerr, - "miner", minerAddr, - "discarded", removed, - "kept", len(good), - "epoch", ss.nextEpoch(), - ) - continue - } + // First, split into "good" and "missing" + good, err := ss.filterProveCommits(ctx, minerAddr, batch) + if err != nil { + log.Errorw("failed to filter prove commits", "miner", minerAddr, "error", err) + // fail with the original error. + return res, aerr } + removed := len(batch) - len(good) + if removed == 0 { + log.Errorw("failed to prove-commit for unknown reasons", + "error", aerr, + "miner", minerAddr, + "sectors", batch, + "epoch", ss.nextEpoch(), + ) + res.failed += len(batch) + } else if len(good) == 0 { + log.Errorw("failed to prove commit missing pre-commits", + "error", aerr, + "miner", minerAddr, + "discarded", removed, + "epoch", ss.nextEpoch(), + ) + res.failed += len(batch) + } else { + // update the pending sector numbers in-place to remove the expired ones. + snos = snos[removed:] + copy(snos, good) + pending.finish(sealType, removed) + + log.Errorw("failed to prove commit expired/missing pre-commits", + "error", aerr, + "miner", minerAddr, + "discarded", removed, + "kept", len(good), + "epoch", ss.nextEpoch(), + ) + res.failed += removed + + // Then try again. + continue + } + log.Errorw("failed to prove commit missing sector(s)", + "error", err, + "miner", minerAddr, + "sectors", batch, + "epoch", ss.nextEpoch(), + ) + res.failed += len(batch) + } else { log.Errorw("failed to prove commit sector(s)", "error", err, "miner", minerAddr, @@ -196,13 +221,9 @@ func (ss *simulationState) packProveCommitsMiner( "epoch", ss.nextEpoch(), ) res.failed += len(batch) - } else if full { - return res, true, nil - } else { - res.done += len(batch) } - pending.finish(sealType, batchSize) - snos = snos[batchSize:] + pending.finish(sealType, len(batch)) + snos = snos[len(batch):] } } for len(snos) > 0 && res.unbatched < power5.MaxMinerProveCommitsPerEpoch { @@ -211,7 +232,7 @@ func (ss *simulationState) packProveCommitsMiner( proof, err := mockSealProof(sealType, minerAddr) if err != nil { - return res, false, err + return res, err } params := miner.ProveCommitSectorParams{ SectorNumber: sno, @@ -219,18 +240,23 @@ func (ss *simulationState) packProveCommitsMiner( } enc, err := actors.SerializeParams(¶ms) if err != nil { - return res, false, err + return res, err } - if full, err := sendAndFund(cb, &types.Message{ + if _, err := sendAndFund(cb, &types.Message{ From: info.Worker, To: minerAddr, Value: abi.NewTokenAmount(0), Method: miner.Methods.ProveCommitSector, Params: enc, - }); err != nil { - if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() { - return res, false, err - } + }); err == nil { + res.unbatched++ + res.done++ + } else if err == ErrOutOfGas { + res.full = true + return res, nil + } else if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() { + return res, err + } else { log.Errorw("failed to prove commit sector(s)", "error", err, "miner", minerAddr, @@ -238,18 +264,13 @@ func (ss *simulationState) packProveCommitsMiner( "epoch", ss.nextEpoch(), ) res.failed++ - } else if full { - return res, true, nil - } else { - res.unbatched++ - res.done++ } // mark it as "finished" regardless so we skip it. pending.finish(sealType, 1) } // if we get here, we can't pre-commit anything more. } - return res, false, nil + return res, nil } // loadProveCommitsMiner enqueue all pending prove-commits for the given miner. This is called on diff --git a/cmd/lotus-sim/simulation/step.go b/cmd/lotus-sim/simulation/step.go index b99f318d6..e076b7b86 100644 --- a/cmd/lotus-sim/simulation/step.go +++ b/cmd/lotus-sim/simulation/step.go @@ -2,6 +2,7 @@ package simulation import ( "context" + "errors" "reflect" "runtime" "strings" @@ -61,7 +62,13 @@ func (ss *simulationState) step(ctx context.Context) (*types.TipSet, error) { return head, nil } -type packFunc func(*types.Message) (full bool, err error) +var ErrOutOfGas = errors.New("out of gas") + +// packFunc takes a message and attempts to pack it into a block. +// +// - If the block is full, returns the error ErrOutOfGas. +// - If message execution fails, check if error is an ActorError to get the return code. +type packFunc func(*types.Message) (*types.MessageReceipt, error) // popNextMessages generates/picks a set of messages to be included in the next block. // @@ -130,9 +137,9 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag vmStore := vmi.ActorStore(ctx) var gasTotal int64 var messages []*types.Message - tryPushMsg := func(msg *types.Message) (bool, error) { + tryPushMsg := func(msg *types.Message) (*types.MessageReceipt, error) { if gasTotal >= targetGas { - return true, nil + return nil, ErrOutOfGas } // Copy the message before we start mutating it. @@ -142,17 +149,17 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag actor, err := st.GetActor(msg.From) if err != nil { - return false, err + return nil, err } msg.Nonce = actor.Nonce if msg.From.Protocol() == address.ID { state, err := account.Load(vmStore, actor) if err != nil { - return false, err + return nil, err } msg.From, err = state.PubkeyAddress() if err != nil { - return false, err + return nil, err } } @@ -172,17 +179,17 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag ret, err := vmi.ApplyMessage(ctx, msg) if err != nil { _ = st.Revert() - return false, err + return nil, err } if ret.ActorErr != nil { _ = st.Revert() - return false, ret.ActorErr + return nil, ret.ActorErr } // Sometimes there are bugs. Let's catch them. if ret.GasUsed == 0 { _ = st.Revert() - return false, xerrors.Errorf("used no gas", + return nil, xerrors.Errorf("used no gas", "msg", msg, "ret", ret, ) @@ -195,7 +202,7 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag newTotal := gasTotal + ret.GasUsed if newTotal > targetGas { _ = st.Revert() - return true, nil + return nil, ErrOutOfGas } gasTotal = newTotal @@ -203,7 +210,7 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag msg.GasLimit = ret.GasUsed messages = append(messages, msg) - return false, nil + return &ret.MessageReceipt, nil } // Finally, we generate a set of messages to be included in @@ -232,7 +239,7 @@ func functionName(fn interface{}) string { // true). // TODO: Make this more configurable for other simulations. func (ss *simulationState) packMessages(ctx context.Context, cb packFunc) error { - type messageGenerator func(ctx context.Context, cb packFunc) (full bool, err error) + type messageGenerator func(ctx context.Context, cb packFunc) error // We pack messages in-order: // 1. Any window posts. We pack window posts as soon as the deadline opens to ensure we only @@ -248,8 +255,7 @@ func (ss *simulationState) packMessages(ctx context.Context, cb packFunc) error for _, mgen := range messageGenerators { // We're intentionally ignoring the "full" signal so we can try to pack a few more // messages. - _, err := mgen(ctx, cb) - if err != nil { + if err := mgen(ctx, cb); err != nil && !xerrors.Is(err, ErrOutOfGas) { return xerrors.Errorf("when packing messages with %s: %w", functionName(mgen), err) } } diff --git a/cmd/lotus-sim/simulation/wdpost.go b/cmd/lotus-sim/simulation/wdpost.go index c940c8d51..3e8d482ff 100644 --- a/cmd/lotus-sim/simulation/wdpost.go +++ b/cmd/lotus-sim/simulation/wdpost.go @@ -28,10 +28,10 @@ func (sim *Simulation) postChainCommitInfo(ctx context.Context, epoch abi.ChainE // packWindowPoSts packs window posts until either the block is full or all healty sectors // have been proven. It does not recover sectors. -func (ss *simulationState) packWindowPoSts(ctx context.Context, cb packFunc) (full bool, _err error) { +func (ss *simulationState) packWindowPoSts(ctx context.Context, cb packFunc) (_err error) { // Push any new window posts into the queue. if err := ss.queueWindowPoSts(ctx); err != nil { - return false, err + return err } done := 0 failed := 0 @@ -50,9 +50,9 @@ func (ss *simulationState) packWindowPoSts(ctx context.Context, cb packFunc) (fu // Then pack as many as we can. for len(ss.pendingWposts) > 0 { next := ss.pendingWposts[0] - if full, err := cb(next); err != nil { + if _, err := cb(next); err != nil { if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() { - return false, err + return err } log.Errorw("failed to submit windowed post", "error", err, @@ -60,8 +60,6 @@ func (ss *simulationState) packWindowPoSts(ctx context.Context, cb packFunc) (fu "epoch", ss.nextEpoch(), ) failed++ - } else if full { - return true, nil } else { done++ } @@ -69,7 +67,7 @@ func (ss *simulationState) packWindowPoSts(ctx context.Context, cb packFunc) (fu ss.pendingWposts = ss.pendingWposts[1:] } ss.pendingWposts = nil - return false, nil + return nil } // stepWindowPoStsMiner enqueues all missing window posts for the current epoch for the given miner. From e7b1e09ade1e118a5ee27bef4fe4aa0805e24425 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 14:53:14 -0700 Subject: [PATCH 117/257] feat(multisig): expose ApproveReturn --- chain/actors/builtin/multisig/multisig.go | 1 + 1 file changed, 1 insertion(+) diff --git a/chain/actors/builtin/multisig/multisig.go b/chain/actors/builtin/multisig/multisig.go index ae6a9daf3..82f1963be 100644 --- a/chain/actors/builtin/multisig/multisig.go +++ b/chain/actors/builtin/multisig/multisig.go @@ -185,6 +185,7 @@ type MessageBuilder interface { // this type is the same between v0 and v2 type ProposalHashData = msig5.ProposalHashData type ProposeReturn = msig5.ProposeReturn +type ApproveReturn = msig5.ApproveReturn type ProposeParams = msig5.ProposeParams func txnParams(id uint64, data *ProposalHashData) ([]byte, error) { From 2b4f865665a879f3e158d741ddff85e3a9470a39 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 14:51:56 -0700 Subject: [PATCH 118/257] feat(lotus-sim): fund from a funding account Instead of funding the simulation from the burnt-funds actor, fund from a custom account and take a "tax" to fund that account. Otherwise, we run out of funds... --- cmd/lotus-sim/simulation/funding.go | 287 ++++++++++++++++++++++++ cmd/lotus-sim/simulation/precommit.go | 16 +- cmd/lotus-sim/simulation/provecommit.go | 32 --- cmd/lotus-sim/simulation/step.go | 6 +- 4 files changed, 297 insertions(+), 44 deletions(-) create mode 100644 cmd/lotus-sim/simulation/funding.go diff --git a/cmd/lotus-sim/simulation/funding.go b/cmd/lotus-sim/simulation/funding.go new file mode 100644 index 000000000..1d57d8d0f --- /dev/null +++ b/cmd/lotus-sim/simulation/funding.go @@ -0,0 +1,287 @@ +package simulation + +import ( + "bytes" + "context" + "sort" + "time" + + "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/exitcode" + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/aerrors" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/multisig" + "github.com/filecoin-project/lotus/chain/types" +) + +var ( + fundAccount = func() address.Address { + addr, err := address.NewIDAddress(100) + if err != nil { + panic(err) + } + return addr + }() + minFundAcctFunds = abi.TokenAmount(types.MustParseFIL("1000000FIL")) + maxFundAcctFunds = abi.TokenAmount(types.MustParseFIL("100000000FIL")) + taxMin = abi.TokenAmount(types.MustParseFIL("1000FIL")) +) + +func fund(send packFunc, target address.Address) error { + _, err := send(&types.Message{ + From: fundAccount, + To: target, + Value: targetFunds, + Method: builtin.MethodSend, + }) + return err +} + +// sendAndFund "packs" the given message, funding the actor if necessary. It: +// +// 1. Tries to send the given message. +// 2. If that fails, it checks to see if the exit code was ErrInsufficientFunds. +// 3. If so, it sends 1K FIL from the "burnt funds actor" (because we need to send it from +// somewhere) and re-tries the message.0 +// +// NOTE: If the message fails a second time, the funds won't be "unsent". +func sendAndFund(send packFunc, msg *types.Message) (*types.MessageReceipt, error) { + res, err := send(msg) + aerr, ok := err.(aerrors.ActorError) + if !ok || aerr.RetCode() != exitcode.ErrInsufficientFunds { + return res, err + } + // Ok, insufficient funds. Let's fund this miner and try again. + err = fund(send, msg.To) + if err != nil { + if err != ErrOutOfGas { + err = xerrors.Errorf("failed to fund %s: %w", msg.To, err) + } + return nil, err + } + return send(msg) +} + +func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err error) { + st, err := ss.stateTree(ctx) + if err != nil { + return err + } + fundAccActor, err := st.GetActor(fundAccount) + if err != nil { + return err + } + if minFundAcctFunds.LessThan(fundAccActor.Balance) { + return nil + } + + // Ok, we're going to go fund this thing. + start := time.Now() + + type actor struct { + types.Actor + Address address.Address + } + + var targets []*actor + err = st.ForEach(func(addr address.Address, act *types.Actor) error { + if act.Balance.LessThan(taxMin) { + return nil + } + if !(builtin.IsAccountActor(act.Code) || builtin.IsMultisigActor(act.Code)) { + return nil + } + targets = append(targets, &actor{*act, addr}) + return nil + }) + if err != nil { + return err + } + + balance := fundAccActor.Balance.Copy() + + sort.Slice(targets, func(i, j int) bool { + return targets[i].Balance.GreaterThan(targets[j].Balance) + }) + + store := ss.Chainstore.ActorStore(ctx) + + epoch := ss.nextEpoch() + + nv := ss.StateManager.GetNtwkVersion(ctx, epoch) + actorsVersion := actors.VersionForNetwork(nv) + + var accounts, multisigs int + defer func() { + if _err != nil { + return + } + log.Infow("finished funding the simulation", + "duration", time.Since(start), + "targets", len(targets), + "epoch", epoch, + "new-balance", types.FIL(balance), + "old-balance", types.FIL(fundAccActor.Balance), + "multisigs", multisigs, + "accounts", accounts, + ) + }() + + for _, actor := range targets { + switch { + case builtin.IsAccountActor(actor.Code): + if _, err := cb(&types.Message{ + From: actor.Address, + To: fundAccount, + Value: actor.Balance, + }); err == ErrOutOfGas { + return nil + } else if err != nil { + return err + } + accounts++ + case builtin.IsMultisigActor(actor.Code): + msigState, err := multisig.Load(store, &actor.Actor) + if err != nil { + return err + } + + threshold, err := msigState.Threshold() + if err != nil { + return err + } + + if threshold > 16 { + log.Debugw("ignoring multisig with high threshold", + "multisig", actor.Address, + "threshold", threshold, + "max", 16, + ) + continue + } + + locked, err := msigState.LockedBalance(epoch) + if err != nil { + return err + } + + if locked.LessThan(taxMin) { + continue // not worth it. + } + + allSigners, err := msigState.Signers() + if err != nil { + return err + } + signers := make([]address.Address, 0, threshold) + for _, signer := range allSigners { + actor, err := st.GetActor(signer) + if err != nil { + return err + } + if !builtin.IsAccountActor(actor.Code) { + // I am so not dealing with this mess. + continue + } + if uint64(len(signers)) >= threshold { + break + } + } + // Ok, we're not dealing with this one. + if uint64(len(signers)) < threshold { + continue + } + + available := big.Sub(actor.Balance, locked) + + var txnId uint64 + { + msg, err := multisig.Message(actorsVersion, signers[0]).Propose( + actor.Address, fundAccount, available, + builtin.MethodSend, nil, + ) + if err != nil { + return err + } + res, err := cb(msg) + if err != nil { + if err == ErrOutOfGas { + err = nil + } + return err + } + var ret multisig.ProposeReturn + err = ret.UnmarshalCBOR(bytes.NewReader(res.Return)) + if err != nil { + return err + } + if ret.Applied { + if !ret.Code.IsSuccess() { + log.Errorw("failed to tax multisig", + "multisig", actor.Address, + "exitcode", ret.Code, + ) + } + break + } + txnId = uint64(ret.TxnID) + } + var ret multisig.ProposeReturn + for _, signer := range signers[1:] { + msg, err := multisig.Message(actorsVersion, signer).Approve(actor.Address, txnId, nil) + if err != nil { + return err + } + res, err := cb(msg) + if err != nil { + if err == ErrOutOfGas { + err = nil + } + return err + } + var ret multisig.ProposeReturn + err = ret.UnmarshalCBOR(bytes.NewReader(res.Return)) + if err != nil { + return err + } + // A bit redundant, but nice. + if ret.Applied { + break + } + + } + if !ret.Applied { + log.Errorw("failed to apply multisig transaction", + "multisig", actor.Address, + "txnid", txnId, + "signers", len(signers), + "threshold", threshold, + ) + continue + } + if !ret.Code.IsSuccess() { + log.Errorw("failed to tax multisig", + "multisig", actor.Address, + "txnid", txnId, + "exitcode", ret.Code, + ) + } else { + multisigs++ + } + default: + panic("impossible case") + } + balance = big.Int{Int: balance.Add(balance.Int, actor.Balance.Int)} + if balance.GreaterThanEqual(maxFundAcctFunds) { + // There's no need to get greedy. + // Well, really, we're trying to avoid messing with state _too_ much. + return nil + } + } + return nil +} diff --git a/cmd/lotus-sim/simulation/precommit.go b/cmd/lotus-sim/simulation/precommit.go index b048aa66c..23534c257 100644 --- a/cmd/lotus-sim/simulation/precommit.go +++ b/cmd/lotus-sim/simulation/precommit.go @@ -9,7 +9,6 @@ import ( "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/network" "github.com/filecoin-project/lotus/chain/actors" - "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" @@ -118,15 +117,12 @@ func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, } if big.Cmp(minerBalance, minFunds) < 0 { - if _, err := cb(&types.Message{ - From: builtin.BurntFundsActorAddr, - To: minerAddr, - Value: targetFunds, - Method: builtin.MethodSend, - }); err == ErrOutOfGas { - return 0, true, nil - } else if err != nil { - return 0, false, xerrors.Errorf("failed to fund miner %s: %w", minerAddr, err) + err := fund(cb, minerAddr) + if err != nil { + if err == ErrOutOfGas { + return 0, true, nil + } + return 0, false, err } } diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/provecommit.go index 1a615d830..d40b8714b 100644 --- a/cmd/lotus-sim/simulation/provecommit.go +++ b/cmd/lotus-sim/simulation/provecommit.go @@ -4,7 +4,6 @@ import ( "context" "github.com/filecoin-project/go-bitfield" - "golang.org/x/xerrors" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" @@ -12,7 +11,6 @@ import ( "github.com/filecoin-project/go-state-types/network" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/aerrors" - "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" @@ -68,36 +66,6 @@ type proveCommitResult struct { full bool } -// sendAndFund "packs" the given message, funding the actor if necessary. It: -// -// 1. Tries to send the given message. -// 2. If that fails, it checks to see if the exit code was ErrInsufficientFunds. -// 3. If so, it sends 1K FIL from the "burnt funds actor" (because we need to send it from -// somewhere) and re-tries the message.0 -// -// NOTE: If the message fails a second time, the funds won't be "unsent". -func sendAndFund(send packFunc, msg *types.Message) (*types.MessageReceipt, error) { - res, err := send(msg) - aerr, ok := err.(aerrors.ActorError) - if !ok || aerr.RetCode() != exitcode.ErrInsufficientFunds { - return res, err - } - // Ok, insufficient funds. Let's fund this miner and try again. - _, err = send(&types.Message{ - From: builtin.BurntFundsActorAddr, - To: msg.To, - Value: targetFunds, - Method: builtin.MethodSend, - }) - if err != nil { - if err != ErrOutOfGas { - err = xerrors.Errorf("failed to fund %s: %w", msg.To, err) - } - return nil, err - } - return send(msg) -} - // packProveCommitsMiner enqueues a prove commits from the given miner until it runs out of // available prove-commits, batching as much as possible. // diff --git a/cmd/lotus-sim/simulation/step.go b/cmd/lotus-sim/simulation/step.go index e076b7b86..c7b5ecdf2 100644 --- a/cmd/lotus-sim/simulation/step.go +++ b/cmd/lotus-sim/simulation/step.go @@ -244,10 +244,12 @@ func (ss *simulationState) packMessages(ctx context.Context, cb packFunc) error // We pack messages in-order: // 1. Any window posts. We pack window posts as soon as the deadline opens to ensure we only // miss them if/when we run out of chain bandwidth. - // 2. Prove commits. We do this eagerly to ensure they don't expire. - // 3. Finally, we fill the rest of the space with pre-commits. + // 2. We then move funds to our "funding" account, if it's running low. + // 3. Prove commits. We do this eagerly to ensure they don't expire. + // 4. Finally, we fill the rest of the space with pre-commits. messageGenerators := []messageGenerator{ ss.packWindowPoSts, + ss.packFunding, ss.packProveCommits, ss.packPreCommits, } From 868231adc751daa1d0661dfa8764d7b8bfb69c8d Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 17:09:03 -0700 Subject: [PATCH 119/257] fix(lotus-sim): don't take from the fund account when funding --- cmd/lotus-sim/simulation/funding.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/lotus-sim/simulation/funding.go b/cmd/lotus-sim/simulation/funding.go index 1d57d8d0f..1648a0be9 100644 --- a/cmd/lotus-sim/simulation/funding.go +++ b/cmd/lotus-sim/simulation/funding.go @@ -90,6 +90,10 @@ func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err e var targets []*actor err = st.ForEach(func(addr address.Address, act *types.Actor) error { + // Don't steal from ourselves! + if addr == fundAccount { + return nil + } if act.Balance.LessThan(taxMin) { return nil } From 1802ae31a6de48dd713e192e9c87ad746b9d2a92 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 19:35:22 -0700 Subject: [PATCH 120/257] feat(lotus-sim): record timing information for pre/prove-commit packing --- cmd/lotus-sim/simulation/precommit.go | 3 +++ cmd/lotus-sim/simulation/provecommit.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/cmd/lotus-sim/simulation/precommit.go b/cmd/lotus-sim/simulation/precommit.go index 23534c257..02ea45ca1 100644 --- a/cmd/lotus-sim/simulation/precommit.go +++ b/cmd/lotus-sim/simulation/precommit.go @@ -3,6 +3,7 @@ package simulation import ( "context" "fmt" + "time" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" @@ -35,6 +36,7 @@ func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (_er full bool top1Count, top10Count, restCount int ) + start := time.Now() defer func() { if _err != nil { return @@ -45,6 +47,7 @@ func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (_er "top10", top10Count, "rest", restCount, "filled-block", full, + "duration", time.Since(start), ) }() diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/provecommit.go index d40b8714b..5ba4a8f00 100644 --- a/cmd/lotus-sim/simulation/provecommit.go +++ b/cmd/lotus-sim/simulation/provecommit.go @@ -2,6 +2,7 @@ package simulation import ( "context" + "time" "github.com/filecoin-project/go-bitfield" @@ -25,6 +26,7 @@ func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_ // Roll the commitQueue forward. ss.commitQueue.advanceEpoch(ss.nextEpoch()) + start := time.Now() var full, failed, done, unbatched, count int defer func() { if _err != nil { @@ -38,6 +40,7 @@ func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_ "unbatched", unbatched, "miners-processed", count, "filled-block", full, + "duration", time.Since(start), ) }() From 2b77c1754626521534f1ea3bc2f11282f1ce11d0 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 19:40:00 -0700 Subject: [PATCH 121/257] chore(lotus-sim): fix import grouping I got a bit lazy when developing this. --- cmd/lotus-sim/create.go | 3 ++- cmd/lotus-sim/info.go | 1 + cmd/lotus-sim/main.go | 3 ++- cmd/lotus-sim/simulation/block.go | 3 ++- cmd/lotus-sim/simulation/commit_queue.go | 1 + cmd/lotus-sim/simulation/commit_queue_test.go | 4 +++- cmd/lotus-sim/simulation/funding.go | 3 ++- cmd/lotus-sim/simulation/messages.go | 3 ++- cmd/lotus-sim/simulation/mock.go | 3 ++- cmd/lotus-sim/simulation/precommit.go | 12 +++++++----- cmd/lotus-sim/simulation/provecommit.go | 10 +++++----- cmd/lotus-sim/simulation/simulation.go | 10 +++++----- cmd/lotus-sim/simulation/state.go | 1 + cmd/lotus-sim/simulation/step.go | 3 ++- cmd/lotus-sim/simulation/wdpost.go | 4 ++-- cmd/lotus-sim/upgrade.go | 3 ++- 16 files changed, 41 insertions(+), 26 deletions(-) diff --git a/cmd/lotus-sim/create.go b/cmd/lotus-sim/create.go index 777f1723c..cfd93c789 100644 --- a/cmd/lotus-sim/create.go +++ b/cmd/lotus-sim/create.go @@ -3,9 +3,10 @@ package main import ( "fmt" + "github.com/urfave/cli/v2" + "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" - "github.com/urfave/cli/v2" ) var createSimCommand = &cli.Command{ diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index b51853b31..9b4e9daaf 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -9,6 +9,7 @@ import ( "github.com/urfave/cli/v2" "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" diff --git a/cmd/lotus-sim/main.go b/cmd/lotus-sim/main.go index bfcad728d..5c954a8d6 100644 --- a/cmd/lotus-sim/main.go +++ b/cmd/lotus-sim/main.go @@ -7,8 +7,9 @@ import ( "os/signal" "syscall" - logging "github.com/ipfs/go-log/v2" "github.com/urfave/cli/v2" + + logging "github.com/ipfs/go-log/v2" ) var root []*cli.Command = []*cli.Command{ diff --git a/cmd/lotus-sim/simulation/block.go b/cmd/lotus-sim/simulation/block.go index 31e7a1a79..6b3c96e78 100644 --- a/cmd/lotus-sim/simulation/block.go +++ b/cmd/lotus-sim/simulation/block.go @@ -6,9 +6,10 @@ import ( "encoding/binary" "time" + "golang.org/x/xerrors" + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" - "golang.org/x/xerrors" ) const beaconPrefix = "mockbeacon:" diff --git a/cmd/lotus-sim/simulation/commit_queue.go b/cmd/lotus-sim/simulation/commit_queue.go index 4cfb6e764..75dc6f034 100644 --- a/cmd/lotus-sim/simulation/commit_queue.go +++ b/cmd/lotus-sim/simulation/commit_queue.go @@ -5,6 +5,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/policy" ) diff --git a/cmd/lotus-sim/simulation/commit_queue_test.go b/cmd/lotus-sim/simulation/commit_queue_test.go index 1a7bd2749..7c6bc6c8f 100644 --- a/cmd/lotus-sim/simulation/commit_queue_test.go +++ b/cmd/lotus-sim/simulation/commit_queue_test.go @@ -3,11 +3,13 @@ package simulation import ( "testing" + "github.com/stretchr/testify/require" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/policy" - "github.com/stretchr/testify/require" ) func TestCommitQueue(t *testing.T) { diff --git a/cmd/lotus-sim/simulation/funding.go b/cmd/lotus-sim/simulation/funding.go index 1648a0be9..a88d07ae8 100644 --- a/cmd/lotus-sim/simulation/funding.go +++ b/cmd/lotus-sim/simulation/funding.go @@ -6,11 +6,12 @@ import ( "sort" "time" + "golang.org/x/xerrors" + "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/exitcode" - "golang.org/x/xerrors" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/aerrors" diff --git a/cmd/lotus-sim/simulation/messages.go b/cmd/lotus-sim/simulation/messages.go index 8c12cac1a..08e4c12d2 100644 --- a/cmd/lotus-sim/simulation/messages.go +++ b/cmd/lotus-sim/simulation/messages.go @@ -3,10 +3,11 @@ package simulation import ( "context" - blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" "github.com/ipfs/go-cid" cbg "github.com/whyrusleeping/cbor-gen" + blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" + "github.com/filecoin-project/lotus/chain/types" ) diff --git a/cmd/lotus-sim/simulation/mock.go b/cmd/lotus-sim/simulation/mock.go index b81ee8629..37f0a2c6c 100644 --- a/cmd/lotus-sim/simulation/mock.go +++ b/cmd/lotus-sim/simulation/mock.go @@ -8,9 +8,10 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" + + "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" ) // Ideally, we'd use extern/sector-storage/mock. Unfortunately, those mocks are a bit _too_ accurate diff --git a/cmd/lotus-sim/simulation/precommit.go b/cmd/lotus-sim/simulation/precommit.go index 02ea45ca1..41c3f363e 100644 --- a/cmd/lotus-sim/simulation/precommit.go +++ b/cmd/lotus-sim/simulation/precommit.go @@ -5,19 +5,21 @@ import ( "fmt" "time" + "golang.org/x/xerrors" + "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" + "github.com/ipfs/go-cid" + + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + tutils "github.com/filecoin-project/specs-actors/v5/support/testing" + "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" - "github.com/ipfs/go-cid" - "golang.org/x/xerrors" - - miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" - tutils "github.com/filecoin-project/specs-actors/v5/support/testing" ) var ( diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/provecommit.go index 5ba4a8f00..2916c7eaa 100644 --- a/cmd/lotus-sim/simulation/provecommit.go +++ b/cmd/lotus-sim/simulation/provecommit.go @@ -4,20 +4,20 @@ import ( "context" "time" - "github.com/filecoin-project/go-bitfield" - "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/exitcode" "github.com/filecoin-project/go-state-types/network" + + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + power5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/aerrors" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" - - miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" - power5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/power" ) // packProveCOmmits packs all prove-commits for all "ready to be proven" sectors until it fills the diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index 4b571a808..dbce16f4e 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -6,19 +6,19 @@ import ( "golang.org/x/xerrors" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/network" "github.com/ipfs/go-cid" "github.com/ipfs/go-datastore" logging "github.com/ipfs/go-log/v2" - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/network" + blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" - - blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" - miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" ) var log = logging.Logger("simulation") diff --git a/cmd/lotus-sim/simulation/state.go b/cmd/lotus-sim/simulation/state.go index 23de7038c..a45e1ac45 100644 --- a/cmd/lotus-sim/simulation/state.go +++ b/cmd/lotus-sim/simulation/state.go @@ -6,6 +6,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/types" ) diff --git a/cmd/lotus-sim/simulation/step.go b/cmd/lotus-sim/simulation/step.go index c7b5ecdf2..1106e0d6e 100644 --- a/cmd/lotus-sim/simulation/step.go +++ b/cmd/lotus-sim/simulation/step.go @@ -7,10 +7,11 @@ import ( "runtime" "strings" - "github.com/filecoin-project/go-address" "golang.org/x/xerrors" + "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/actors/builtin/account" "github.com/filecoin-project/lotus/chain/state" diff --git a/cmd/lotus-sim/simulation/wdpost.go b/cmd/lotus-sim/simulation/wdpost.go index 3e8d482ff..7e6f2401e 100644 --- a/cmd/lotus-sim/simulation/wdpost.go +++ b/cmd/lotus-sim/simulation/wdpost.go @@ -11,12 +11,12 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/crypto" + proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" + "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/aerrors" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/types" - - proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" ) // postChainCommitInfo returns th diff --git a/cmd/lotus-sim/upgrade.go b/cmd/lotus-sim/upgrade.go index 17993f847..3a30e869b 100644 --- a/cmd/lotus-sim/upgrade.go +++ b/cmd/lotus-sim/upgrade.go @@ -6,9 +6,10 @@ import ( "strings" "text/tabwriter" + "github.com/urfave/cli/v2" + "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/network" - "github.com/urfave/cli/v2" ) var upgradeCommand = &cli.Command{ From dcdb0abe271694ec86c6825ad73d7a3e414b62a1 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 21:27:33 -0700 Subject: [PATCH 122/257] feat(lotus-sim): profile on SIGUSR2 --- cmd/lotus-sim/profile.go | 87 ++++++++++++++++++++++++++++++++++++++++ cmd/lotus-sim/run.go | 3 ++ 2 files changed, 90 insertions(+) create mode 100644 cmd/lotus-sim/profile.go diff --git a/cmd/lotus-sim/profile.go b/cmd/lotus-sim/profile.go new file mode 100644 index 000000000..e18fd22f6 --- /dev/null +++ b/cmd/lotus-sim/profile.go @@ -0,0 +1,87 @@ +package main + +import ( + "archive/tar" + "context" + "fmt" + "io" + "os" + "os/signal" + "runtime/pprof" + "time" + + "github.com/urfave/cli/v2" +) + +func takeProfiles(ctx context.Context) (fname string, _err error) { + file, err := os.CreateTemp(".", ".profiles*.tar") + if err != nil { + return "", err + } + defer file.Close() + + if err := writeProfiles(ctx, file); err != nil { + _ = os.Remove(file.Name()) + return "", err + } + + fname = fmt.Sprintf("pprof-simulation-%s.tar", time.Now()) + if err := os.Rename(file.Name(), fname); err != nil { + _ = os.Remove(file.Name()) + return "", err + } + return fname, nil +} + +func writeProfiles(ctx context.Context, w io.Writer) error { + tw := tar.NewWriter(w) + for _, profile := range pprof.Profiles() { + if err := tw.WriteHeader(&tar.Header{Name: profile.Name()}); err != nil { + return err + } + if err := profile.WriteTo(tw, 0); err != nil { + return err + } + if err := ctx.Err(); err != nil { + return err + } + } + + if err := tw.WriteHeader(&tar.Header{Name: "cpu"}); err != nil { + return err + } + if err := pprof.StartCPUProfile(tw); err != nil { + return err + } + select { + case <-time.After(30 * time.Second): + case <-ctx.Done(): + pprof.StopCPUProfile() + return ctx.Err() + } + pprof.StopCPUProfile() + return tw.Close() +} + +func profileOnSignal(cctx *cli.Context, signals ...os.Signal) { + ch := make(chan os.Signal, 1) + signal.Notify(ch, signals...) + defer signal.Stop(ch) + + for range ch { + select { + case <-ch: + fname, err := takeProfiles(cctx.Context) + switch err { + case context.Canceled: + return + case nil: + fmt.Fprintf(cctx.App.ErrWriter, "Wrote profile to %q\n", fname) + default: + fmt.Fprintf(cctx.App.ErrWriter, "ERROR: failed to write profile: %s\n", err) + } + case <-cctx.Done(): + return + } + } +} diff --git a/cmd/lotus-sim/run.go b/cmd/lotus-sim/run.go index 479ce898d..f696243fa 100644 --- a/cmd/lotus-sim/run.go +++ b/cmd/lotus-sim/run.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "syscall" "github.com/urfave/cli/v2" ) @@ -22,6 +23,8 @@ var runSimCommand = &cli.Command{ } defer node.Close() + go profileOnSignal(cctx, syscall.SIGUSR2) + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) if err != nil { return err From 936659d087ecba1f66b503e6e13900a266d6868e Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 21:27:48 -0700 Subject: [PATCH 123/257] feat(lotus-sim): print info on SIGUSR1 --- cmd/lotus-sim/info.go | 85 +++++++++++++++++++++++-------------------- cmd/lotus-sim/run.go | 19 ++++++++++ 2 files changed, 64 insertions(+), 40 deletions(-) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 9b4e9daaf..187c2236e 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -3,6 +3,7 @@ package main import ( "context" "fmt" + "io" "text/tabwriter" "time" @@ -13,6 +14,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin/power" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation" ) func getTotalPower(ctx context.Context, sm *stmgr.StateManager, ts *types.TipSet) (power.Claim, error) { @@ -27,6 +29,48 @@ func getTotalPower(ctx context.Context, sm *stmgr.StateManager, ts *types.TipSet return state.TotalPower() } +func printInfo(ctx context.Context, sim *simulation.Simulation, out io.Writer) error { + powerNow, err := getTotalPower(ctx, sim.StateManager, sim.GetHead()) + if err != nil { + return err + } + powerStart, err := getTotalPower(ctx, sim.StateManager, sim.GetStart()) + if err != nil { + return err + } + powerGrowth := big.Sub(powerNow.RawBytePower, powerStart.RawBytePower) + + tw := tabwriter.NewWriter(out, 8, 8, 1, ' ', 0) + + head := sim.GetHead() + start := sim.GetStart() + headEpoch := head.Height() + firstEpoch := start.Height() + 1 + + headTime := time.Unix(int64(head.MinTimestamp()), 0) + startTime := time.Unix(int64(start.MinTimestamp()), 0) + duration := headTime.Sub(startTime) + + // growth rate in size/day + growthRate := big.Div( + big.Mul(powerGrowth, big.NewInt(int64(24*time.Hour))), + big.NewInt(int64(duration)), + ) + + fmt.Fprintf(tw, "Name:\t%s\n", sim.Name()) + fmt.Fprintf(tw, "Head:\t%s\n", head) + fmt.Fprintf(tw, "Last Epoch:\t%d\n", headEpoch) + fmt.Fprintf(tw, "First Epoch:\t%d\n", firstEpoch) + fmt.Fprintf(tw, "Length:\t%d\n", headEpoch-firstEpoch) + fmt.Fprintf(tw, "Date:\t%s\n", headTime) + fmt.Fprintf(tw, "Duration:\t%s\n", duration) + fmt.Fprintf(tw, "Power:\t%s\n", types.SizeStr(powerNow.RawBytePower)) + fmt.Fprintf(tw, "Power Growth:\t%s\n", types.SizeStr(powerGrowth)) + fmt.Fprintf(tw, "Power Growth Rate:\t%s/day\n", types.SizeStr(growthRate)) + fmt.Fprintf(tw, "Network Version:\t%d\n", sim.GetNetworkVersion()) + return tw.Flush() +} + var infoSimCommand = &cli.Command{ Name: "info", Description: "Output information about the simulation.", @@ -41,45 +85,6 @@ var infoSimCommand = &cli.Command{ if err != nil { return err } - - powerNow, err := getTotalPower(cctx.Context, sim.StateManager, sim.GetHead()) - if err != nil { - return err - } - powerStart, err := getTotalPower(cctx.Context, sim.StateManager, sim.GetStart()) - if err != nil { - return err - } - powerGrowth := big.Sub(powerNow.RawBytePower, powerStart.RawBytePower) - - tw := tabwriter.NewWriter(cctx.App.Writer, 8, 8, 0, ' ', 0) - - head := sim.GetHead() - start := sim.GetStart() - headEpoch := head.Height() - firstEpoch := start.Height() + 1 - - headTime := time.Unix(int64(head.MinTimestamp()), 0) - startTime := time.Unix(int64(start.MinTimestamp()), 0) - duration := headTime.Sub(startTime) - - // growth rate in size/day - growthRate := big.Div( - big.Mul(powerGrowth, big.NewInt(int64(24*time.Hour))), - big.NewInt(int64(duration)), - ) - - fmt.Fprintf(tw, "Name:\t%s\n", sim.Name()) - fmt.Fprintf(tw, "Head:\t%s\n", head) - fmt.Fprintf(tw, "Last Epoch:\t%d\n", headEpoch) - fmt.Fprintf(tw, "First Epoch:\t%d\n", firstEpoch) - fmt.Fprintf(tw, "Length:\t%d\n", headEpoch-firstEpoch) - fmt.Fprintf(tw, "Date:\t%s\n", headTime) - fmt.Fprintf(tw, "Duration:\t%s\n", duration) - fmt.Fprintf(tw, "Power:\t%s\n", types.SizeStr(powerNow.RawBytePower)) - fmt.Fprintf(tw, "Power Growth:\t%s\n", types.SizeStr(powerGrowth)) - fmt.Fprintf(tw, "Power Growth Rate:\t%s/day\n", types.SizeStr(growthRate)) - fmt.Fprintf(tw, "Network Version:\t%d\n", sim.GetNetworkVersion()) - return tw.Flush() + return printInfo(cctx.Context, sim, cctx.App.Writer) }, } diff --git a/cmd/lotus-sim/run.go b/cmd/lotus-sim/run.go index f696243fa..388b8f763 100644 --- a/cmd/lotus-sim/run.go +++ b/cmd/lotus-sim/run.go @@ -2,6 +2,8 @@ package main import ( "fmt" + "os" + "os/signal" "syscall" "github.com/urfave/cli/v2" @@ -36,12 +38,29 @@ var runSimCommand = &cli.Command{ } fmt.Fprintln(cctx.App.Writer, "running simulation") targetEpochs := cctx.Int("epochs") + + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGUSR1) + defer signal.Stop(ch) + for i := 0; targetEpochs == 0 || i < targetEpochs; i++ { ts, err := sim.Step(cctx.Context) if err != nil { return err } + fmt.Fprintf(cctx.App.Writer, "advanced to %d %s\n", ts.Height(), ts.Key()) + + // Print + select { + case <-ch: + if err := printInfo(cctx.Context, sim, cctx.App.Writer); err != nil { + fmt.Fprintf(cctx.App.ErrWriter, "ERROR: failed to print info: %s\n", err) + } + case <-cctx.Context.Done(): + return cctx.Err() + default: + } } fmt.Fprintln(cctx.App.Writer, "simulation done") return err From 4a80c8353316d4d40fde895c01cb0771fbb0e9f6 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 21:28:05 -0700 Subject: [PATCH 124/257] fix(lotus-sim): fix spelling --- cmd/lotus-sim/simulation/provecommit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/provecommit.go index 2916c7eaa..7e2344890 100644 --- a/cmd/lotus-sim/simulation/provecommit.go +++ b/cmd/lotus-sim/simulation/provecommit.go @@ -20,7 +20,7 @@ import ( "github.com/filecoin-project/lotus/chain/types" ) -// packProveCOmmits packs all prove-commits for all "ready to be proven" sectors until it fills the +// packProveCommits packs all prove-commits for all "ready to be proven" sectors until it fills the // block or runs out. func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_err error) { // Roll the commitQueue forward. From 576600237073e0a84dc1eb47c1f84aa32c0ad281 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 21:31:32 -0700 Subject: [PATCH 125/257] fix(lotus-sim): guard info with dashes --- cmd/lotus-sim/run.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/lotus-sim/run.go b/cmd/lotus-sim/run.go index 388b8f763..0be70ff49 100644 --- a/cmd/lotus-sim/run.go +++ b/cmd/lotus-sim/run.go @@ -54,9 +54,11 @@ var runSimCommand = &cli.Command{ // Print select { case <-ch: + fmt.Fprintln(cctx.App.Writer, "---------------------") if err := printInfo(cctx.Context, sim, cctx.App.Writer); err != nil { fmt.Fprintf(cctx.App.ErrWriter, "ERROR: failed to print info: %s\n", err) } + fmt.Fprintln(cctx.App.Writer, "---------------------") case <-cctx.Context.Done(): return cctx.Err() default: From a3f64e0768f183f8fec5a5ace439fb70c1a9a05e Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 21:35:26 -0700 Subject: [PATCH 126/257] fix(lotus-sim): profile signal handling --- cmd/lotus-sim/profile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/lotus-sim/profile.go b/cmd/lotus-sim/profile.go index e18fd22f6..f2058949f 100644 --- a/cmd/lotus-sim/profile.go +++ b/cmd/lotus-sim/profile.go @@ -68,7 +68,7 @@ func profileOnSignal(cctx *cli.Context, signals ...os.Signal) { signal.Notify(ch, signals...) defer signal.Stop(ch) - for range ch { + for { select { case <-ch: fname, err := takeProfiles(cctx.Context) From 7a8bfd87255593349621b61d3ab0040ff261802a Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 21:39:18 -0700 Subject: [PATCH 127/257] doc(lotus-sim): document signals --- cmd/lotus-sim/run.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-sim/run.go b/cmd/lotus-sim/run.go index 0be70ff49..58eeb1a5e 100644 --- a/cmd/lotus-sim/run.go +++ b/cmd/lotus-sim/run.go @@ -10,8 +10,12 @@ import ( ) var runSimCommand = &cli.Command{ - Name: "run", - Description: "Run the simulation.", + Name: "run", + Description: `Run the simulation. + +Signals: +- SIGUSR1: Print information about the current simulation (equivalent to 'lotus-sim info'). +- SIGUSR2: Write a pprof profile to pprof-simulation-$DATE.tar`, Flags: []cli.Flag{ &cli.IntFlag{ Name: "epochs", From 977bf1cad992e803ea9374a1b7e9a8962c76a556 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 21:51:59 -0700 Subject: [PATCH 128/257] fix(lotus-sim): write pprof profiles to a directory We need to know the sizes up-front for tar, and that's not happening. --- cmd/lotus-sim/profile.go | 43 +++++++++++++++++++++++----------------- cmd/lotus-sim/run.go | 2 +- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/cmd/lotus-sim/profile.go b/cmd/lotus-sim/profile.go index f2058949f..63e0ef3bd 100644 --- a/cmd/lotus-sim/profile.go +++ b/cmd/lotus-sim/profile.go @@ -1,12 +1,11 @@ package main import ( - "archive/tar" "context" "fmt" - "io" "os" "os/signal" + "path/filepath" "runtime/pprof" "time" @@ -14,32 +13,35 @@ import ( ) func takeProfiles(ctx context.Context) (fname string, _err error) { - file, err := os.CreateTemp(".", ".profiles*.tar") + dir, err := os.MkdirTemp(".", ".profiles-temp*") if err != nil { return "", err } - defer file.Close() - if err := writeProfiles(ctx, file); err != nil { - _ = os.Remove(file.Name()) + if err := writeProfiles(ctx, dir); err != nil { + _ = os.RemoveAll(dir) return "", err } - fname = fmt.Sprintf("pprof-simulation-%s.tar", time.Now()) - if err := os.Rename(file.Name(), fname); err != nil { - _ = os.Remove(file.Name()) + fname = fmt.Sprintf("pprof-simulation-%s", time.Now().Format(time.RFC3339)) + if err := os.Rename(dir, fname); err != nil { + _ = os.RemoveAll(dir) return "", err } return fname, nil } -func writeProfiles(ctx context.Context, w io.Writer) error { - tw := tar.NewWriter(w) +func writeProfiles(ctx context.Context, dir string) error { for _, profile := range pprof.Profiles() { - if err := tw.WriteHeader(&tar.Header{Name: profile.Name()}); err != nil { + file, err := os.Create(filepath.Join(dir, profile.Name()+".pprof.gz")) + if err != nil { return err } - if err := profile.WriteTo(tw, 0); err != nil { + if err := profile.WriteTo(file, 0); err != nil { + _ = file.Close() + return err + } + if err := file.Close(); err != nil { return err } if err := ctx.Err(); err != nil { @@ -47,20 +49,25 @@ func writeProfiles(ctx context.Context, w io.Writer) error { } } - if err := tw.WriteHeader(&tar.Header{Name: "cpu"}); err != nil { + file, err := os.Create(filepath.Join(dir, "cpu.pprof.gz")) + if err != nil { return err } - if err := pprof.StartCPUProfile(tw); err != nil { + + if err := pprof.StartCPUProfile(file); err != nil { + _ = file.Close() return err } select { case <-time.After(30 * time.Second): case <-ctx.Done(): - pprof.StopCPUProfile() - return ctx.Err() } pprof.StopCPUProfile() - return tw.Close() + err = file.Close() + if err := ctx.Err(); err != nil { + return err + } + return err } func profileOnSignal(cctx *cli.Context, signals ...os.Signal) { diff --git a/cmd/lotus-sim/run.go b/cmd/lotus-sim/run.go index 58eeb1a5e..ba6534b4b 100644 --- a/cmd/lotus-sim/run.go +++ b/cmd/lotus-sim/run.go @@ -15,7 +15,7 @@ var runSimCommand = &cli.Command{ Signals: - SIGUSR1: Print information about the current simulation (equivalent to 'lotus-sim info'). -- SIGUSR2: Write a pprof profile to pprof-simulation-$DATE.tar`, +- SIGUSR2: Write pprof profiles to ./pprof-simulation-$DATE/`, Flags: []cli.Flag{ &cli.IntFlag{ Name: "epochs", From c18ca60d288ef7c2f50e3c100f215898928abe5f Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 21:56:47 -0700 Subject: [PATCH 129/257] fix(lotus-sim): specify ErrWriter Apparently, it defaults to nil... --- cmd/lotus-sim/main.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cmd/lotus-sim/main.go b/cmd/lotus-sim/main.go index 5c954a8d6..c785f4045 100644 --- a/cmd/lotus-sim/main.go +++ b/cmd/lotus-sim/main.go @@ -29,9 +29,11 @@ func main() { _ = logging.SetLogLevel("simulation", "DEBUG") } app := &cli.App{ - Name: "lotus-sim", - Usage: "A tool to simulate a network.", - Commands: root, + Name: "lotus-sim", + Usage: "A tool to simulate a network.", + Commands: root, + Writer: os.Stdout, + ErrWriter: os.Stderr, Flags: []cli.Flag{ &cli.StringFlag{ Name: "repo", From be713ec04a8207a9309c94426a6a9c8222b26629 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 9 Jun 2021 22:13:38 -0700 Subject: [PATCH 130/257] fix(lotus-sim): we always fill the block with pre-commits --- cmd/lotus-sim/simulation/provecommit.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/provecommit.go index 7e2344890..12c67ba8b 100644 --- a/cmd/lotus-sim/simulation/provecommit.go +++ b/cmd/lotus-sim/simulation/provecommit.go @@ -27,7 +27,7 @@ func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_ ss.commitQueue.advanceEpoch(ss.nextEpoch()) start := time.Now() - var full, failed, done, unbatched, count int + var failed, done, unbatched, count int defer func() { if _err != nil { return @@ -39,7 +39,6 @@ func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_ "failed", failed, "unbatched", unbatched, "miners-processed", count, - "filled-block", full, "duration", time.Since(start), ) }() From 783dc5a33da34afbd9bd3c00c743df25e144aab1 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 07:39:29 -0700 Subject: [PATCH 131/257] fix(lotus-sim): fund multiple times Sometimes, a miner is deep in the red. --- cmd/lotus-sim/simulation/funding.go | 44 ++++++++++++++++----------- cmd/lotus-sim/simulation/precommit.go | 2 +- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/cmd/lotus-sim/simulation/funding.go b/cmd/lotus-sim/simulation/funding.go index a88d07ae8..a58b8c0e4 100644 --- a/cmd/lotus-sim/simulation/funding.go +++ b/cmd/lotus-sim/simulation/funding.go @@ -33,11 +33,18 @@ var ( taxMin = abi.TokenAmount(types.MustParseFIL("1000FIL")) ) -func fund(send packFunc, target address.Address) error { +func fund(send packFunc, target address.Address, times int) error { + amt := targetFunds + if times >= 1 { + if times >= 8 { + times = 8 // cap + } + amt = big.Lsh(amt, uint(times)) + } _, err := send(&types.Message{ From: fundAccount, To: target, - Value: targetFunds, + Value: amt, Method: builtin.MethodSend, }) return err @@ -49,23 +56,26 @@ func fund(send packFunc, target address.Address) error { // 2. If that fails, it checks to see if the exit code was ErrInsufficientFunds. // 3. If so, it sends 1K FIL from the "burnt funds actor" (because we need to send it from // somewhere) and re-tries the message.0 -// -// NOTE: If the message fails a second time, the funds won't be "unsent". -func sendAndFund(send packFunc, msg *types.Message) (*types.MessageReceipt, error) { - res, err := send(msg) - aerr, ok := err.(aerrors.ActorError) - if !ok || aerr.RetCode() != exitcode.ErrInsufficientFunds { - return res, err - } - // Ok, insufficient funds. Let's fund this miner and try again. - err = fund(send, msg.To) - if err != nil { - if err != ErrOutOfGas { - err = xerrors.Errorf("failed to fund %s: %w", msg.To, err) +func sendAndFund(send packFunc, msg *types.Message) (res *types.MessageReceipt, err error) { + for i := 0; i < 10; i++ { + res, err = send(msg) + if err != nil { + return res, nil + } + aerr, ok := err.(aerrors.ActorError) + if !ok || aerr.RetCode() != exitcode.ErrInsufficientFunds { + return nil, err + } + + // Ok, insufficient funds. Let's fund this miner and try again. + if err := fund(send, msg.To, i); err != nil { + if err != ErrOutOfGas { + err = xerrors.Errorf("failed to fund %s: %w", msg.To, err) + } + return nil, err } - return nil, err } - return send(msg) + return res, err } func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err error) { diff --git a/cmd/lotus-sim/simulation/precommit.go b/cmd/lotus-sim/simulation/precommit.go index 41c3f363e..51fdaedc6 100644 --- a/cmd/lotus-sim/simulation/precommit.go +++ b/cmd/lotus-sim/simulation/precommit.go @@ -122,7 +122,7 @@ func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, } if big.Cmp(minerBalance, minFunds) < 0 { - err := fund(cb, minerAddr) + err := fund(cb, minerAddr, 1) if err != nil { if err == ErrOutOfGas { return 0, true, nil From 8d734d81d924433be69dfde50dfea46df6f4e792 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 07:50:51 -0700 Subject: [PATCH 132/257] fix(lotus-sim): log failed pre-commits and continue --- cmd/lotus-sim/simulation/precommit.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/cmd/lotus-sim/simulation/precommit.go b/cmd/lotus-sim/simulation/precommit.go index 51fdaedc6..854722f6a 100644 --- a/cmd/lotus-sim/simulation/precommit.go +++ b/cmd/lotus-sim/simulation/precommit.go @@ -17,6 +17,7 @@ import ( tutils "github.com/filecoin-project/specs-actors/v5/support/testing" "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/aerrors" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" @@ -170,7 +171,7 @@ func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, } enc, err := actors.SerializeParams(¶ms) if err != nil { - return 0, false, err + return added, false, err } // NOTE: just in-case, sendAndFund will "fund" and re-try for any message // that fails due to "insufficient funds". @@ -184,13 +185,22 @@ func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, // try again with a smaller batch. targetBatchSize /= 2 continue + } else if aerr, ok := err.(aerrors.ActorError); ok && !aerr.IsFatal() { + // Log the error and move on. No reason to stop. + log.Errorw("failed to pre-commit for unknown reasons", + "error", aerr, + "miner", minerAddr, + "sectors", batch, + "epoch", ss.nextEpoch(), + ) + return added, false, nil } else if err != nil { - return 0, false, err + return added, false, err } for _, info := range batch { if err := ss.commitQueue.enqueueProveCommit(minerAddr, epoch, info); err != nil { - return 0, false, err + return added, false, err } added++ } @@ -215,7 +225,7 @@ func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, } if err := ss.commitQueue.enqueueProveCommit(minerAddr, epoch, info); err != nil { - return 0, false, err + return added, false, err } added++ } From 16449007ab8e39150575a3f726945836698afc7c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 07:57:27 -0700 Subject: [PATCH 133/257] fix(lotus-sim): fix funding error check --- cmd/lotus-sim/simulation/funding.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/lotus-sim/simulation/funding.go b/cmd/lotus-sim/simulation/funding.go index a58b8c0e4..e29f4f1b8 100644 --- a/cmd/lotus-sim/simulation/funding.go +++ b/cmd/lotus-sim/simulation/funding.go @@ -59,7 +59,7 @@ func fund(send packFunc, target address.Address, times int) error { func sendAndFund(send packFunc, msg *types.Message) (res *types.MessageReceipt, err error) { for i := 0; i < 10; i++ { res, err = send(msg) - if err != nil { + if err == nil { return res, nil } aerr, ok := err.(aerrors.ActorError) From ca9eadd7c7787fd30db624e1b944550669a5fa03 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Thu, 10 Jun 2021 18:39:57 +0200 Subject: [PATCH 134/257] Add gas info command Signed-off-by: Jakub Sztandera --- cmd/lotus-sim/info.go | 70 ++++++++++++++++++++++++++ cmd/lotus-sim/simulation/simulation.go | 4 ++ 2 files changed, 74 insertions(+) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 187c2236e..d87b3da63 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -1,15 +1,21 @@ package main import ( + "bytes" "context" "fmt" "io" + "os" "text/tabwriter" "time" + "github.com/ipfs/go-cid" "github.com/urfave/cli/v2" "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/exitcode" + "github.com/filecoin-project/specs-actors/v5/actors/builtin" + "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/builtin/power" "github.com/filecoin-project/lotus/chain/stmgr" @@ -74,6 +80,9 @@ func printInfo(ctx context.Context, sim *simulation.Simulation, out io.Writer) e var infoSimCommand = &cli.Command{ Name: "info", Description: "Output information about the simulation.", + Subcommands: []*cli.Command{ + infoCommitGasSimCommand, + }, Action: func(cctx *cli.Context) error { node, err := open(cctx) if err != nil { @@ -88,3 +97,64 @@ var infoSimCommand = &cli.Command{ return printInfo(cctx.Context, sim, cctx.App.Writer) }, } + +var infoCommitGasSimCommand = &cli.Command{ + Name: "commit-gas", + Description: "Output information about the gas for committs", + Action: func(cctx *cli.Context) error { + log := func(f string, i ...interface{}) { + fmt.Fprintf(os.Stderr, f, i...) + } + node, err := open(cctx) + if err != nil { + return err + } + defer node.Close() + + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) + if err != nil { + return err + } + + var gasAgg, proofsAgg uint64 + var gasAggMax, proofsAggMax uint64 + + sim.Walk(cctx.Context, func(sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, + messages []*simulation.AppliedMessage) error { + for _, m := range messages { + if m.ExitCode != exitcode.Ok { + continue + } + if m.Method == builtin.MethodsMiner.ProveCommitAggregate { + param := miner.ProveCommitAggregateParams{} + err := param.UnmarshalCBOR(bytes.NewReader(m.Params)) + if err != nil { + log("failed to decode params: %+v", err) + return nil + } + c, err := param.SectorNumbers.Count() + if err != nil { + log("failed to count sectors") + return nil + } + gasAgg += uint64(m.GasUsed) + proofsAgg += c + if c == 819 { + gasAggMax += uint64(m.GasUsed) + proofsAggMax += c + } + } + + if m.Method == builtin.MethodsMiner.ProveCommitSector { + } + } + + return nil + }) + idealGassUsed := float64(gasAggMax) / float64(proofsAggMax) * float64(proofsAgg) + + fmt.Printf("Gas usage efficiency in comparison to all 819: %f%%\n", 100*idealGassUsed/float64(gasAgg)) + + return nil + }, +} diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index dbce16f4e..eb225f651 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -326,6 +326,10 @@ func (sim *Simulation) Walk( stCid = ts.MinTicketBlock().ParentStateRoot recCid = ts.MinTicketBlock().ParentMessageReceipts + ts, err = sim.Chainstore.LoadTipSet(ts.Parents()) + if err != nil { + return xerrors.Errorf("loading parent: %w", err) + } } return nil } From 68593ce9951d82d174bca33d0cdb9ddde3599cca Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 09:54:14 -0700 Subject: [PATCH 135/257] fix(lotus-sim): obey context in walk --- cmd/lotus-sim/simulation/simulation.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index eb225f651..2d8b4f388 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -297,7 +297,7 @@ func (sim *Simulation) Walk( if err != nil { return err } - for !ts.Equals(sim.start) { + for !ts.Equals(sim.start) && ctx.Err() == nil { msgs, err := sim.Chainstore.MessagesForTipset(ts) if err != nil { return err @@ -331,5 +331,5 @@ func (sim *Simulation) Walk( return xerrors.Errorf("loading parent: %w", err) } } - return nil + return ctx.Err() } From 3d3c26fa0c797b6ebae73e8b2f335df50da4ef72 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Thu, 10 Jun 2021 19:04:15 +0200 Subject: [PATCH 136/257] Add lookback limit Signed-off-by: Jakub Sztandera --- cmd/lotus-sim/info.go | 61 ++++++++++++++------------ cmd/lotus-sim/simulation/simulation.go | 8 +++- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index d87b3da63..252642eea 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -101,6 +101,12 @@ var infoSimCommand = &cli.Command{ var infoCommitGasSimCommand = &cli.Command{ Name: "commit-gas", Description: "Output information about the gas for committs", + Flags: []cli.Flag{ + &cli.Int64Flag{ + Name: "lookback", + Value: 0, + }, + }, Action: func(cctx *cli.Context) error { log := func(f string, i ...interface{}) { fmt.Fprintf(os.Stderr, f, i...) @@ -119,38 +125,39 @@ var infoCommitGasSimCommand = &cli.Command{ var gasAgg, proofsAgg uint64 var gasAggMax, proofsAggMax uint64 - sim.Walk(cctx.Context, func(sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, - messages []*simulation.AppliedMessage) error { - for _, m := range messages { - if m.ExitCode != exitcode.Ok { - continue - } - if m.Method == builtin.MethodsMiner.ProveCommitAggregate { - param := miner.ProveCommitAggregateParams{} - err := param.UnmarshalCBOR(bytes.NewReader(m.Params)) - if err != nil { - log("failed to decode params: %+v", err) - return nil + sim.Walk(cctx.Context, cctx.Int64("lookback"), + func(sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, + messages []*simulation.AppliedMessage) error { + for _, m := range messages { + if m.ExitCode != exitcode.Ok { + continue } - c, err := param.SectorNumbers.Count() - if err != nil { - log("failed to count sectors") - return nil + if m.Method == builtin.MethodsMiner.ProveCommitAggregate { + param := miner.ProveCommitAggregateParams{} + err := param.UnmarshalCBOR(bytes.NewReader(m.Params)) + if err != nil { + log("failed to decode params: %+v", err) + return nil + } + c, err := param.SectorNumbers.Count() + if err != nil { + log("failed to count sectors") + return nil + } + gasAgg += uint64(m.GasUsed) + proofsAgg += c + if c == 819 { + gasAggMax += uint64(m.GasUsed) + proofsAggMax += c + } } - gasAgg += uint64(m.GasUsed) - proofsAgg += c - if c == 819 { - gasAggMax += uint64(m.GasUsed) - proofsAggMax += c + + if m.Method == builtin.MethodsMiner.ProveCommitSector { } } - if m.Method == builtin.MethodsMiner.ProveCommitSector { - } - } - - return nil - }) + return nil + }) idealGassUsed := float64(gasAggMax) / float64(proofsAggMax) * float64(proofsAgg) fmt.Printf("Gas usage efficiency in comparison to all 819: %f%%\n", 100*idealGassUsed/float64(gasAgg)) diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index 2d8b4f388..ab021ddd5 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -286,6 +286,7 @@ type AppliedMessage struct { // Walk walks the simulation's chain from the current head back to the first tipset. func (sim *Simulation) Walk( ctx context.Context, + maxLookback int64, cb func(sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, @@ -297,7 +298,12 @@ func (sim *Simulation) Walk( if err != nil { return err } - for !ts.Equals(sim.start) && ctx.Err() == nil { + minEpoch := abi.ChainEpoch(0) + if maxLookback != 0 { + minEpoch = ts.Height() - abi.ChainEpoch(maxLookback) + } + + for !ts.Equals(sim.start) && ctx.Err() == nil && ts.Height() > minEpoch { msgs, err := sim.Chainstore.MessagesForTipset(ts) if err != nil { return err From ab59474c4c1038f2477a7161f8f2dd72e27afef0 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 10:26:09 -0700 Subject: [PATCH 137/257] fix(lotus-sim): count single prove-commits when computing efficiency --- cmd/lotus-sim/info.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 252642eea..9523b5936 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -124,6 +124,7 @@ var infoCommitGasSimCommand = &cli.Command{ var gasAgg, proofsAgg uint64 var gasAggMax, proofsAggMax uint64 + var gasSingle, proofsSingle uint64 sim.Walk(cctx.Context, cctx.Int64("lookback"), func(sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, @@ -153,14 +154,16 @@ var infoCommitGasSimCommand = &cli.Command{ } if m.Method == builtin.MethodsMiner.ProveCommitSector { + gasSingle += uint64(m.GasUsed) + proofsSingle++ } } return nil }) - idealGassUsed := float64(gasAggMax) / float64(proofsAggMax) * float64(proofsAgg) + idealGassUsed := float64(gasAggMax) / float64(proofsAggMax) * float64(proofsAgg+proofsSingle) - fmt.Printf("Gas usage efficiency in comparison to all 819: %f%%\n", 100*idealGassUsed/float64(gasAgg)) + fmt.Printf("Gas usage efficiency in comparison to all 819: %f%%\n", 100*idealGassUsed/float64(gasAgg+gasSingle)) return nil }, From 500fae6a525022895377a506ae50171940b79b4a Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 10:26:43 -0700 Subject: [PATCH 138/257] fix(lotus-sim): less indentation in info --- cmd/lotus-sim/info.go | 61 ++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 9523b5936..9f3c81dca 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -126,41 +126,42 @@ var infoCommitGasSimCommand = &cli.Command{ var gasAggMax, proofsAggMax uint64 var gasSingle, proofsSingle uint64 - sim.Walk(cctx.Context, cctx.Int64("lookback"), - func(sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, - messages []*simulation.AppliedMessage) error { - for _, m := range messages { - if m.ExitCode != exitcode.Ok { - continue + sim.Walk(cctx.Context, cctx.Int64("lookback"), func( + sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, + messages []*simulation.AppliedMessage, + ) error { + for _, m := range messages { + if m.ExitCode != exitcode.Ok { + continue + } + if m.Method == builtin.MethodsMiner.ProveCommitAggregate { + param := miner.ProveCommitAggregateParams{} + err := param.UnmarshalCBOR(bytes.NewReader(m.Params)) + if err != nil { + log("failed to decode params: %+v", err) + return nil } - if m.Method == builtin.MethodsMiner.ProveCommitAggregate { - param := miner.ProveCommitAggregateParams{} - err := param.UnmarshalCBOR(bytes.NewReader(m.Params)) - if err != nil { - log("failed to decode params: %+v", err) - return nil - } - c, err := param.SectorNumbers.Count() - if err != nil { - log("failed to count sectors") - return nil - } - gasAgg += uint64(m.GasUsed) - proofsAgg += c - if c == 819 { - gasAggMax += uint64(m.GasUsed) - proofsAggMax += c - } + c, err := param.SectorNumbers.Count() + if err != nil { + log("failed to count sectors") + return nil } - - if m.Method == builtin.MethodsMiner.ProveCommitSector { - gasSingle += uint64(m.GasUsed) - proofsSingle++ + gasAgg += uint64(m.GasUsed) + proofsAgg += c + if c == 819 { + gasAggMax += uint64(m.GasUsed) + proofsAggMax += c } } - return nil - }) + if m.Method == builtin.MethodsMiner.ProveCommitSector { + gasSingle += uint64(m.GasUsed) + proofsSingle++ + } + } + + return nil + }) idealGassUsed := float64(gasAggMax) / float64(proofsAggMax) * float64(proofsAgg+proofsSingle) fmt.Printf("Gas usage efficiency in comparison to all 819: %f%%\n", 100*idealGassUsed/float64(gasAgg+gasSingle)) From fbaffe86da429ea0b9527a11a5bc9ca49fc0cada Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 11:14:02 -0700 Subject: [PATCH 139/257] fix(lotus-sim): return error from walk --- cmd/lotus-sim/info.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 9f3c81dca..36d4cd3e0 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -126,7 +126,7 @@ var infoCommitGasSimCommand = &cli.Command{ var gasAggMax, proofsAggMax uint64 var gasSingle, proofsSingle uint64 - sim.Walk(cctx.Context, cctx.Int64("lookback"), func( + err = sim.Walk(cctx.Context, cctx.Int64("lookback"), func( sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, messages []*simulation.AppliedMessage, ) error { @@ -162,6 +162,9 @@ var infoCommitGasSimCommand = &cli.Command{ return nil }) + if err != nil { + return err + } idealGassUsed := float64(gasAggMax) / float64(proofsAggMax) * float64(proofsAgg+proofsSingle) fmt.Printf("Gas usage efficiency in comparison to all 819: %f%%\n", 100*idealGassUsed/float64(gasAgg+gasSingle)) From 1df5445ed25137f32a8c5be0176051af40c6cbd7 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 11:16:26 -0700 Subject: [PATCH 140/257] feat(lotus-sim): make walk parallel --- cmd/lotus-sim/simulation/simulation.go | 165 ++++++++++++++++++++----- 1 file changed, 131 insertions(+), 34 deletions(-) diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index ab021ddd5..78e6c8e87 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -3,7 +3,9 @@ package simulation import ( "context" "encoding/json" + "runtime" + "golang.org/x/sync/errgroup" "golang.org/x/xerrors" "github.com/filecoin-project/go-state-types/abi" @@ -293,49 +295,144 @@ func (sim *Simulation) Walk( messages []*AppliedMessage) error, ) error { store := sim.Chainstore.ActorStore(ctx) - ts := sim.head - stCid, recCid, err := sim.StateManager.TipSetState(ctx, ts) - if err != nil { - return err - } minEpoch := abi.ChainEpoch(0) if maxLookback != 0 { - minEpoch = ts.Height() - abi.ChainEpoch(maxLookback) + minEpoch = sim.head.Height() - abi.ChainEpoch(maxLookback) } - for !ts.Equals(sim.start) && ctx.Err() == nil && ts.Height() > minEpoch { - msgs, err := sim.Chainstore.MessagesForTipset(ts) + // Given tha loading messages and receipts can be a little bit slow, we do this in parallel. + // + // 1. We spin up some number of workers. + // 2. We hand tipsets to workers in round-robin order. + // 3. We pull "resolved" tipsets in the same round-robin order. + // 4. We serially call the callback in reverse-chain order. + // + // We have a buffer of size 1 for both resolved tipsets and unresolved tipsets. This should + // ensure that we never block unecessarily. + + type work struct { + ts *types.TipSet + stCid cid.Cid + recCid cid.Cid + } + type result struct { + ts *types.TipSet + stCid cid.Cid + messages []*AppliedMessage + } + + // This is more disk bound than CPU bound, but eh... + workerCount := runtime.NumCPU() * 2 + + workQs := make([]chan *work, workerCount) + resultQs := make([]chan *result, workerCount) + + for i := range workQs { + workQs[i] = make(chan *work, 1) + } + + for i := range resultQs { + resultQs[i] = make(chan *result, 1) + } + + grp, ctx := errgroup.WithContext(ctx) + + // Walk the chain and fire off work items. + grp.Go(func() error { + ts := sim.head + stCid, recCid, err := sim.StateManager.TipSetState(ctx, ts) if err != nil { return err } - - recs, err := blockadt.AsArray(store, recCid) - if err != nil { - return xerrors.Errorf("amt load: %w", err) - } - applied := make([]*AppliedMessage, len(msgs)) - var rec types.MessageReceipt - err = recs.ForEach(&rec, func(i int64) error { - applied[i] = &AppliedMessage{ - Message: *msgs[i].VMMessage(), - MessageReceipt: rec, + i := 0 + for !ts.Equals(sim.start) && ctx.Err() == nil && ts.Height() > minEpoch { + select { + case workQs[i] <- &work{ts, stCid, recCid}: + case <-ctx.Done(): + return ctx.Err() } + + stCid = ts.MinTicketBlock().ParentStateRoot + recCid = ts.MinTicketBlock().ParentMessageReceipts + ts, err = sim.Chainstore.LoadTipSet(ts.Parents()) + if err != nil { + return xerrors.Errorf("loading parent: %w", err) + } + i = (i + 1) % workerCount + } + for _, q := range workQs { + close(q) + } + return nil + }) + + // Spin up one worker per queue pair. + for i := 0; i < workerCount; i++ { + workQ := workQs[i] + resultQ := resultQs[i] + grp.Go(func() error { + for job := range workQ { + msgs, err := sim.Chainstore.MessagesForTipset(job.ts) + if err != nil { + return err + } + + recs, err := blockadt.AsArray(store, job.recCid) + if err != nil { + return xerrors.Errorf("amt load: %w", err) + } + applied := make([]*AppliedMessage, len(msgs)) + var rec types.MessageReceipt + err = recs.ForEach(&rec, func(i int64) error { + applied[i] = &AppliedMessage{ + Message: *msgs[i].VMMessage(), + MessageReceipt: rec, + } + return nil + }) + if err != nil { + return err + } + select { + case resultQ <- &result{ + ts: job.ts, + stCid: job.stCid, + messages: applied, + }: + case <-ctx.Done(): + return ctx.Err() + } + } + close(resultQ) return nil }) - if err != nil { - return err - } - - if err := cb(sim.StateManager, ts, stCid, applied); err != nil { - return err - } - - stCid = ts.MinTicketBlock().ParentStateRoot - recCid = ts.MinTicketBlock().ParentMessageReceipts - ts, err = sim.Chainstore.LoadTipSet(ts.Parents()) - if err != nil { - return xerrors.Errorf("loading parent: %w", err) - } } - return ctx.Err() + + // Process results in the same order we enqueued them. + grp.Go(func() error { + qs := resultQs + for len(qs) > 0 { + newQs := qs[:0] + for _, q := range qs { + select { + case r, ok := <-q: + if !ok { + continue + } + err := cb(sim.StateManager, r.ts, r.stCid, r.messages) + if err != nil { + return err + } + case <-ctx.Done(): + return ctx.Err() + } + newQs = append(newQs, q) + } + qs = newQs + } + return nil + }) + + // Wait for everything to finish. + return grp.Wait() } From d551f2b4bd7b43d94a0127c3fb54e20af82f7b2a Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 12:32:41 -0700 Subject: [PATCH 141/257] feat(lotus-sim): print duration info in days --- cmd/lotus-sim/info.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 36d4cd3e0..11d0a7efd 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -65,11 +65,12 @@ func printInfo(ctx context.Context, sim *simulation.Simulation, out io.Writer) e fmt.Fprintf(tw, "Name:\t%s\n", sim.Name()) fmt.Fprintf(tw, "Head:\t%s\n", head) - fmt.Fprintf(tw, "Last Epoch:\t%d\n", headEpoch) - fmt.Fprintf(tw, "First Epoch:\t%d\n", firstEpoch) + fmt.Fprintf(tw, "Start Epoch:\t%d\n", firstEpoch) + fmt.Fprintf(tw, "End Epoch:\t%d\n", headEpoch) fmt.Fprintf(tw, "Length:\t%d\n", headEpoch-firstEpoch) - fmt.Fprintf(tw, "Date:\t%s\n", headTime) - fmt.Fprintf(tw, "Duration:\t%s\n", duration) + fmt.Fprintf(tw, "Start Date:\t%s\n", startTime) + fmt.Fprintf(tw, "End Date:\t%s\n", headTime) + fmt.Fprintf(tw, "Duration:\t%.2f day(s)\n", duration.Hours()/24) fmt.Fprintf(tw, "Power:\t%s\n", types.SizeStr(powerNow.RawBytePower)) fmt.Fprintf(tw, "Power Growth:\t%s\n", types.SizeStr(powerGrowth)) fmt.Fprintf(tw, "Power Growth Rate:\t%s/day\n", types.SizeStr(growthRate)) From 707b3bf08a8aa72225d74b807b9f7058fdb232cd Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 12:32:52 -0700 Subject: [PATCH 142/257] fix(lotus-sim): refuse to start simulation with no miners --- cmd/lotus-sim/simulation/state.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/lotus-sim/simulation/state.go b/cmd/lotus-sim/simulation/state.go index a45e1ac45..383ef158a 100644 --- a/cmd/lotus-sim/simulation/state.go +++ b/cmd/lotus-sim/simulation/state.go @@ -6,6 +6,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" + "golang.org/x/xerrors" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/types" @@ -125,6 +126,10 @@ func loadSimulationState(ctx context.Context, sim *Simulation) (*simulationState sealList = append(sealList, onboardingInfo{addr, uint64(sectorsAdded)}) } } + if len(sealList) == 0 { + return nil, xerrors.Errorf("simulation has no miners") + } + // We're already done loading for the _next_ epoch. // Next time, we need to load for the next, next epoch. // TODO: fix this insanity. From 985994cc0f21fcc20a1baff4c3cde80520979bc4 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 14:37:20 -0700 Subject: [PATCH 143/257] feat(lotus-sim): add command for analyzing post stats over time --- cmd/lotus-sim/info.go | 49 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 11d0a7efd..ebfe63545 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -83,6 +83,7 @@ var infoSimCommand = &cli.Command{ Description: "Output information about the simulation.", Subcommands: []*cli.Command{ infoCommitGasSimCommand, + infoWindowPostBandwidthSimCommand, }, Action: func(cctx *cli.Context) error { node, err := open(cctx) @@ -99,6 +100,54 @@ var infoSimCommand = &cli.Command{ }, } +var infoWindowPostBandwidthSimCommand = &cli.Command{ + Name: "post-bandwidth", + Action: func(cctx *cli.Context) error { + node, err := open(cctx) + if err != nil { + return err + } + defer node.Close() + + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) + if err != nil { + return err + } + + var postGas, totalGas int64 + printStats := func() { + fmt.Fprintf(cctx.App.Writer, "%.4f%%\n", float64(100*postGas)/float64(totalGas)) + } + idx := 0 + err = sim.Walk(cctx.Context, 0, func( + sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, + messages []*simulation.AppliedMessage, + ) error { + for _, m := range messages { + totalGas += m.GasUsed + if m.ExitCode != exitcode.Ok { + continue + } + if m.Method == builtin.MethodsMiner.SubmitWindowedPoSt { + postGas += m.GasUsed + } + } + idx++ + idx %= builtin.EpochsInDay + if idx == 0 { + printStats() + postGas = 0 + totalGas = 0 + } + return nil + }) + if idx > 0 { + printStats() + } + return err + }, +} + var infoCommitGasSimCommand = &cli.Command{ Name: "commit-gas", Description: "Output information about the gas for committs", From 2721279e871f785cf5e50473ef62d6e147d102df Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 14:43:15 -0700 Subject: [PATCH 144/257] fix(lotus-sim): describe info commands --- cmd/lotus-sim/info.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index ebfe63545..757c6d6e7 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -101,7 +101,8 @@ var infoSimCommand = &cli.Command{ } var infoWindowPostBandwidthSimCommand = &cli.Command{ - Name: "post-bandwidth", + Name: "post-bandwidth", + Description: "List average chain bandwidth used by window posts for each day of the simulation.", Action: func(cctx *cli.Context) error { node, err := open(cctx) if err != nil { @@ -150,7 +151,7 @@ var infoWindowPostBandwidthSimCommand = &cli.Command{ var infoCommitGasSimCommand = &cli.Command{ Name: "commit-gas", - Description: "Output information about the gas for committs", + Description: "Output information about the gas for commits", Flags: []cli.Flag{ &cli.Int64Flag{ Name: "lookback", From 7dd58efb84cb0cfb429946fed97e22cfeae796d0 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Fri, 11 Jun 2021 15:35:13 +0200 Subject: [PATCH 145/257] Add quantiles and histogram Signed-off-by: Jakub Sztandera --- cmd/lotus-sim/info.go | 49 +++++++++++++++++++ go.mod | 1 + go.sum | 2 + lib/stati/covar.go | 104 ++++++++++++++++++++++++++++++++++++++++ lib/stati/histo.go | 56 ++++++++++++++++++++++ lib/stati/meanvar.go | 66 +++++++++++++++++++++++++ lib/stati/stats_test.go | 47 ++++++++++++++++++ 7 files changed, 325 insertions(+) create mode 100644 lib/stati/covar.go create mode 100644 lib/stati/histo.go create mode 100644 lib/stati/meanvar.go create mode 100644 lib/stati/stats_test.go diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 757c6d6e7..6a37f7258 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -10,6 +10,7 @@ import ( "time" "github.com/ipfs/go-cid" + "github.com/streadway/quantile" "github.com/urfave/cli/v2" "github.com/filecoin-project/go-state-types/big" @@ -21,6 +22,7 @@ import ( "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation" + "github.com/filecoin-project/lotus/lib/stati" ) func getTotalPower(ctx context.Context, sm *stmgr.StateManager, ts *types.TipSet) (power.Claim, error) { @@ -177,6 +179,31 @@ var infoCommitGasSimCommand = &cli.Command{ var gasAggMax, proofsAggMax uint64 var gasSingle, proofsSingle uint64 + qpoints := []struct{ q, tol float64 }{ + {0.01, 0.0005}, + {0.05, 0.001}, + {0.20, 0.01}, + {0.25, 0.01}, + {0.30, 0.01}, + {0.40, 0.01}, + {0.45, 0.01}, + {0.50, 0.01}, + {0.60, 0.01}, + {0.80, 0.01}, + {0.95, 0.001}, + {0.99, 0.0005}, + } + estims := make([]quantile.Estimate, len(qpoints)) + for i, p := range qpoints { + estims[i] = quantile.Known(p.q, p.tol) + } + qua := quantile.New(estims...) + hist, err := stati.NewHistogram([]float64{ + 1, 3, 5, 7, 15, 30, 50, 100, 200, 400, 600, 700, 819}) + if err != nil { + return err + } + err = sim.Walk(cctx.Context, cctx.Int64("lookback"), func( sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, messages []*simulation.AppliedMessage, @@ -203,11 +230,17 @@ var infoCommitGasSimCommand = &cli.Command{ gasAggMax += uint64(m.GasUsed) proofsAggMax += c } + for i := uint64(0); i < c; i++ { + qua.Add(float64(c)) + } + hist.Observe(float64(c)) } if m.Method == builtin.MethodsMiner.ProveCommitSector { gasSingle += uint64(m.GasUsed) proofsSingle++ + qua.Add(1) + hist.Observe(1) } } @@ -220,6 +253,22 @@ var infoCommitGasSimCommand = &cli.Command{ fmt.Printf("Gas usage efficiency in comparison to all 819: %f%%\n", 100*idealGassUsed/float64(gasAgg+gasSingle)) + fmt.Printf("Proofs in singles: %d\n", proofsSingle) + fmt.Printf("Proofs in Aggs: %d\n", proofsAgg) + fmt.Printf("Proofs in Aggs(819): %d\n", proofsAggMax) + + fmt.Println() + fmt.Println("Quantiles of proofs in given aggregate size:") + for _, p := range qpoints { + fmt.Printf("%.0f%%\t%.0f\n", p.q*100, qua.Get(p.q)) + } + fmt.Println() + fmt.Println("Histogram of messages:") + fmt.Printf("Total\t%d\n", hist.Total()) + for i, b := range hist.Buckets[1:] { + fmt.Printf("%.0f\t%d\n", b, hist.Get(i)) + } + return nil }, } diff --git a/go.mod b/go.mod index 411522a36..5bf9094f0 100644 --- a/go.mod +++ b/go.mod @@ -133,6 +133,7 @@ require ( github.com/prometheus/client_golang v1.6.0 github.com/raulk/clock v1.1.0 github.com/raulk/go-watchdog v1.0.1 + github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25 github.com/stretchr/objx v0.2.0 // indirect github.com/stretchr/testify v1.7.0 github.com/syndtr/goleveldb v1.0.0 diff --git a/go.sum b/go.sum index 5573587fd..cfc1d4221 100644 --- a/go.sum +++ b/go.sum @@ -1513,6 +1513,8 @@ github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/ github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25 h1:7z3LSn867ex6VSaahyKadf4WtSsJIgne6A1WLOAGM8A= +github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25/go.mod h1:lbP8tGiBjZ5YWIc2fzuRpTaz0b/53vT6PEs3QuAWzuU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= diff --git a/lib/stati/covar.go b/lib/stati/covar.go new file mode 100644 index 000000000..c92fd8b74 --- /dev/null +++ b/lib/stati/covar.go @@ -0,0 +1,104 @@ +package stati + +import "math" + +type Covar struct { + meanX float64 + meanY float64 + c float64 + n float64 + m2x float64 + m2y float64 +} + +func (cov1 *Covar) MeanX() float64 { + return cov1.meanX +} + +func (cov1 *Covar) MeanY() float64 { + return cov1.meanY +} + +func (cov1 *Covar) N() float64 { + return cov1.n +} + +func (cov1 *Covar) Covariance() float64 { + return cov1.c / (cov1.n - 1) +} + +func (cov1 *Covar) VarianceX() float64 { + return cov1.m2x / (cov1.n - 1) +} + +func (cov1 *Covar) StddevX() float64 { + return math.Sqrt(cov1.VarianceX()) +} + +func (cov1 *Covar) VarianceY() float64 { + return cov1.m2y / (cov1.n - 1) +} + +func (cov1 *Covar) StddevY() float64 { + return math.Sqrt(cov1.VarianceY()) +} + +func (cov1 *Covar) AddPoint(x, y float64) { + cov1.n++ + + dx := x - cov1.meanX + cov1.meanX += dx / cov1.n + dx2 := x - cov1.meanX + cov1.m2x += dx * dx2 + + dy := y - cov1.meanY + cov1.meanY += dy / cov1.n + dy2 := y - cov1.meanY + cov1.m2y += dy * dy2 + + cov1.c += dx * dy +} + +func (cov1 *Covar) Combine(cov2 *Covar) { + if cov1.n == 0 { + *cov1 = *cov2 + return + } + if cov2.n == 0 { + return + } + + if cov1.n == 1 { + cpy := *cov2 + cpy.AddPoint(cov2.meanX, cov2.meanY) + *cov1 = cpy + return + } + if cov2.n == 1 { + cov1.AddPoint(cov2.meanX, cov2.meanY) + } + + out := Covar{} + out.n = cov1.n + cov2.n + + dx := cov1.meanX - cov2.meanX + out.meanX = cov1.meanX - dx*cov2.n/out.n + out.m2x = cov1.m2x + cov2.m2x + dx*dx*cov1.n*cov2.n/out.n + + dy := cov1.meanY - cov2.meanY + out.meanY = cov1.meanY - dy*cov2.n/out.n + out.m2y = cov1.m2y + cov2.m2y + dy*dy*cov1.n*cov2.n/out.n + + out.c = cov1.c + cov2.c + dx*dy*cov1.n*cov2.n/out.n + *cov1 = out +} + +func (cov1 *Covar) A() float64 { + return cov1.Covariance() / cov1.VarianceX() +} +func (cov1 *Covar) B() float64 { + return cov1.meanY - cov1.meanX*cov1.A() +} +func (cov1 *Covar) Correl() float64 { + return cov1.Covariance() / cov1.StddevX() / cov1.StddevY() +} diff --git a/lib/stati/histo.go b/lib/stati/histo.go new file mode 100644 index 000000000..3c410c0d0 --- /dev/null +++ b/lib/stati/histo.go @@ -0,0 +1,56 @@ +package stati + +import ( + "math" + + "golang.org/x/xerrors" +) + +type Histogram struct { + Buckets []float64 + Counts []uint64 +} + +// NewHistogram creates a histograme with buckets defined as: +// {x > -Inf, x >= buckets[0], x >= buckets[1], ..., x >= buckets[i]} +func NewHistogram(buckets []float64) (*Histogram, error) { + if len(buckets) == 0 { + return nil, xerrors.Errorf("empty buckets") + } + prev := buckets[0] + for i, v := range buckets[1:] { + if v < prev { + return nil, xerrors.Errorf("bucket at index %d is smaller than previous %f < %f", i+1, v, prev) + } + prev = v + } + h := &Histogram{ + Buckets: append([]float64{math.Inf(-1)}, buckets...), + Counts: make([]uint64, len(buckets)+1), + } + return h, nil +} + +func (h *Histogram) Observe(x float64) { + for i, b := range h.Buckets { + if x >= b { + h.Counts[i]++ + } else { + break + } + } +} + +func (h *Histogram) Total() uint64 { + return h.Counts[0] +} + +func (h *Histogram) Get(i int) uint64 { + if i >= len(h.Counts)-2 { + return h.Counts[i] + } + return h.Counts[i+1] - h.Counts[i+2] +} +func (h *Histogram) GetRatio(i int) float64 { + return float64(h.Get(i)) / float64(h.Total()) +} diff --git a/lib/stati/meanvar.go b/lib/stati/meanvar.go new file mode 100644 index 000000000..b77aaa638 --- /dev/null +++ b/lib/stati/meanvar.go @@ -0,0 +1,66 @@ +package stati + +import ( + "fmt" + "math" +) + +type MeanVar struct { + n float64 + mean float64 + m2 float64 +} + +func (v1 *MeanVar) AddPoint(value float64) { + // based on https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm + v1.n++ + delta := value - v1.mean + v1.mean += delta / v1.n + delta2 := value - v1.mean + v1.m2 += delta * delta2 +} + +func (v1 *MeanVar) Mean() float64 { + return v1.mean +} +func (v1 *MeanVar) N() float64 { + return v1.n +} +func (v1 *MeanVar) Variance() float64 { + return v1.m2 / (v1.n - 1) +} +func (v1 *MeanVar) Stddev() float64 { + return math.Sqrt(v1.Variance()) +} + +func (v1 MeanVar) String() string { + return fmt.Sprintf("%f stddev: %f (%.0f)", v1.Mean(), v1.Stddev(), v1.N()) +} + +func (v1 *MeanVar) Combine(v2 *MeanVar) { + if v1.n == 0 { + *v1 = *v2 + return + } + if v2.n == 0 { + return + } + if v1.n == 1 { + cpy := *v2 + cpy.AddPoint(v1.mean) + *v1 = cpy + return + } + if v2.n == 1 { + v1.AddPoint(v2.mean) + return + } + + newCount := v1.n + v2.n + delta := v2.mean - v1.mean + meanDelta := delta * v2.n / newCount + m2 := v1.m2 + v2.m2 + delta*meanDelta*v1.n + v1.n = newCount + v1.mean += meanDelta + v1.m2 = m2 +} diff --git a/lib/stati/stats_test.go b/lib/stati/stats_test.go new file mode 100644 index 000000000..fa92913b6 --- /dev/null +++ b/lib/stati/stats_test.go @@ -0,0 +1,47 @@ +package stati + +import ( + "math/rand" + "testing" +) + +func TestMeanVar(t *testing.T) { + N := 16 + ss := make([]*MeanVar, N) + rng := rand.New(rand.NewSource(1)) + for i := 0; i < N; i++ { + ss[i] = &MeanVar{} + maxJ := rng.Intn(1000) + for j := 0; j < maxJ; j++ { + ss[i].AddPoint(rng.NormFloat64()*5 + 500) + } + t.Logf("mean: %f, stddev: %f, count %f", ss[i].mean, ss[i].Stddev(), ss[i].n) + } + out := &MeanVar{} + for i := 0; i < N; i++ { + out.Combine(ss[i]) + t.Logf("combine: mean: %f, stddev: %f", out.mean, out.Stddev()) + } +} + +func TestCovar(t *testing.T) { + N := 16 + ss := make([]*Covar, N) + rng := rand.New(rand.NewSource(1)) + for i := 0; i < N; i++ { + ss[i] = &Covar{} + maxJ := rng.Intn(1000) + 500 + for j := 0; j < maxJ; j++ { + x := rng.NormFloat64()*5 + 500 + ss[i].AddPoint(x, x*2-1000) + } + t.Logf("corell: %f, y = %f*x+%f @%.0f", ss[i].Correl(), ss[i].A(), ss[i].B(), ss[i].n) + t.Logf("\txVar: %f yVar: %f covar: %f", ss[i].StddevX(), ss[i].StddevY(), ss[i].Covariance()) + } + out := &Covar{} + for i := 0; i < N; i++ { + out.Combine(ss[i]) + t.Logf("combine: corell: %f, y = %f*x+%f", out.Correl(), out.A(), out.B()) + t.Logf("\txVar: %f yVar: %f covar: %f", out.StddevX(), out.StddevY(), out.Covariance()) + } +} From 8a215df46b6cf6dac97197304caf8bc34acbea17 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Jun 2021 17:41:23 -0700 Subject: [PATCH 146/257] fix(statetree): make StateTree.ForEach take layers into account This likely isn't used anywhere, but this _should_ take layers into account (and I kind of just assumed it did). --- chain/state/statetree.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/chain/state/statetree.go b/chain/state/statetree.go index 40955c48b..72269e4f2 100644 --- a/chain/state/statetree.go +++ b/chain/state/statetree.go @@ -504,6 +504,25 @@ func (st *StateTree) MutateActor(addr address.Address, f func(*types.Actor) erro } func (st *StateTree) ForEach(f func(address.Address, *types.Actor) error) error { + // Walk through layers, if any. + seen := make(map[address.Address]struct{}) + for i := len(st.snaps.layers) - 1; i >= 0; i-- { + for addr, op := range st.snaps.layers[i].actors { + if _, ok := seen[addr]; ok { + continue + } + seen[addr] = struct{}{} + if op.Delete { + continue + } + if err := f(addr, &op.Act); err != nil { + return err + } + } + + } + + // Now walk through the saved actors. var act types.Actor return st.root.ForEach(&act, func(k string) error { act := act // copy @@ -512,6 +531,12 @@ func (st *StateTree) ForEach(f func(address.Address, *types.Actor) error) error return xerrors.Errorf("invalid address (%x) found in state tree key: %w", []byte(k), err) } + // no need to record anything here, there are no duplicates in the actors HAMT + // iself. + if _, ok := seen[addr]; ok { + return nil + } + return f(addr, &act) }) } From 52261fb814e18850356f0e9bb39b3f74be25b11f Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 11 Jun 2021 18:39:15 -0700 Subject: [PATCH 147/257] refactor(lotus-sim): enterprise grade While the previous version "worked", this version nicely separates out the state for the separate stages. Hopefully, we'll be able to use this to build different pipelines with different configs. --- cmd/lotus-sim/run.go | 6 - cmd/lotus-sim/simulation/block.go | 3 +- .../simulation/blockbuilder/blockbuilder.go | 279 ++++++++++++++ .../simulation/blockbuilder/errors.go | 25 ++ cmd/lotus-sim/simulation/{ => mock}/mock.go | 30 +- cmd/lotus-sim/simulation/node.go | 19 +- cmd/lotus-sim/simulation/power.go | 58 --- cmd/lotus-sim/simulation/precommit.go | 233 ------------ cmd/lotus-sim/simulation/simulation.go | 37 +- .../simulation/{ => stages}/actor_iter.go | 2 +- .../simulation/{ => stages}/commit_queue.go | 2 +- .../{ => stages}/commit_queue_test.go | 2 +- .../{funding.go => stages/funding_stage.go} | 134 ++++--- cmd/lotus-sim/simulation/stages/interface.go | 27 ++ cmd/lotus-sim/simulation/stages/pipeline.go | 31 ++ .../simulation/stages/precommit_stage.go | 359 ++++++++++++++++++ .../provecommit_stage.go} | 153 +++++--- cmd/lotus-sim/simulation/stages/util.go | 81 ++++ .../simulation/stages/windowpost_stage.go | 312 +++++++++++++++ cmd/lotus-sim/simulation/state.go | 202 ---------- cmd/lotus-sim/simulation/step.go | 229 +---------- cmd/lotus-sim/simulation/wdpost.go | 229 ----------- 22 files changed, 1353 insertions(+), 1100 deletions(-) create mode 100644 cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go create mode 100644 cmd/lotus-sim/simulation/blockbuilder/errors.go rename cmd/lotus-sim/simulation/{ => mock}/mock.go (75%) delete mode 100644 cmd/lotus-sim/simulation/power.go delete mode 100644 cmd/lotus-sim/simulation/precommit.go rename cmd/lotus-sim/simulation/{ => stages}/actor_iter.go (97%) rename cmd/lotus-sim/simulation/{ => stages}/commit_queue.go (99%) rename cmd/lotus-sim/simulation/{ => stages}/commit_queue_test.go (99%) rename cmd/lotus-sim/simulation/{funding.go => stages/funding_stage.go} (68%) create mode 100644 cmd/lotus-sim/simulation/stages/interface.go create mode 100644 cmd/lotus-sim/simulation/stages/pipeline.go create mode 100644 cmd/lotus-sim/simulation/stages/precommit_stage.go rename cmd/lotus-sim/simulation/{provecommit.go => stages/provecommit_stage.go} (62%) create mode 100644 cmd/lotus-sim/simulation/stages/util.go create mode 100644 cmd/lotus-sim/simulation/stages/windowpost_stage.go delete mode 100644 cmd/lotus-sim/simulation/state.go delete mode 100644 cmd/lotus-sim/simulation/wdpost.go diff --git a/cmd/lotus-sim/run.go b/cmd/lotus-sim/run.go index ba6534b4b..00a3bddd9 100644 --- a/cmd/lotus-sim/run.go +++ b/cmd/lotus-sim/run.go @@ -35,12 +35,6 @@ Signals: if err != nil { return err } - fmt.Fprintln(cctx.App.Writer, "loading simulation") - err = sim.Load(cctx.Context) - if err != nil { - return err - } - fmt.Fprintln(cctx.App.Writer, "running simulation") targetEpochs := cctx.Int("epochs") ch := make(chan os.Signal, 1) diff --git a/cmd/lotus-sim/simulation/block.go b/cmd/lotus-sim/simulation/block.go index 6b3c96e78..47d482f4e 100644 --- a/cmd/lotus-sim/simulation/block.go +++ b/cmd/lotus-sim/simulation/block.go @@ -8,6 +8,7 @@ import ( "golang.org/x/xerrors" + "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" ) @@ -68,7 +69,7 @@ func (sim *Simulation) makeTipSet(ctx context.Context, messages []*types.Message ParentStateRoot: parentState, ParentMessageReceipts: parentRec, Messages: msgsCid, - ParentBaseFee: baseFee, + ParentBaseFee: abi.NewTokenAmount(0), Timestamp: uts, ElectionProof: &types.ElectionProof{WinCount: 1}, }} diff --git a/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go b/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go new file mode 100644 index 000000000..4406f8a4f --- /dev/null +++ b/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go @@ -0,0 +1,279 @@ +package blockbuilder + +import ( + "context" + + "go.uber.org/zap" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "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" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/account" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" +) + +const ( + // The number of expected blocks in a tipset. We use this to determine how much gas a tipset + // has. + expectedBlocks = 5 + // TODO: This will produce invalid blocks but it will accurately model the amount of gas + // we're willing to use per-tipset. + // A more correct approach would be to produce 5 blocks. We can do that later. + targetGas = build.BlockGasTarget * expectedBlocks +) + +type BlockBuilder struct { + ctx context.Context + logger *zap.SugaredLogger + + parentTs *types.TipSet + parentSt *state.StateTree + vm *vm.VM + sm *stmgr.StateManager + + gasTotal int64 + messages []*types.Message +} + +// NewBlockBuilder constructs a new block builder from the parent state. Use this to pack a block +// with messages. +// +// NOTE: The context applies to the life of the block builder itself (but does not need to be canceled). +func NewBlockBuilder(ctx context.Context, logger *zap.SugaredLogger, sm *stmgr.StateManager, parentTs *types.TipSet) (*BlockBuilder, error) { + parentState, _, err := sm.TipSetState(ctx, parentTs) + if err != nil { + return nil, err + } + parentSt, err := sm.StateTree(parentState) + if err != nil { + return nil, err + } + + bb := &BlockBuilder{ + ctx: ctx, + logger: logger.With("epoch", parentTs.Height()+1), + sm: sm, + parentTs: parentTs, + parentSt: parentSt, + } + + // Then we construct a VM to execute messages for gas estimation. + // + // Most parts of this VM are "real" except: + // 1. We don't charge a fee. + // 2. The runtime has "fake" proof logic. + // 3. We don't actually save any of the results. + r := store.NewChainRand(sm.ChainStore(), parentTs.Cids()) + vmopt := &vm.VMOpts{ + StateBase: parentState, + Epoch: parentTs.Height() + 1, + Rand: r, + Bstore: sm.ChainStore().StateBlockstore(), + Syscalls: sm.ChainStore().VMSys(), + CircSupplyCalc: sm.GetVMCirculatingSupply, + NtwkVersion: sm.GetNtwkVersion, + BaseFee: abi.NewTokenAmount(0), + LookbackState: stmgr.LookbackStateGetterForTipset(sm, parentTs), + } + bb.vm, err = vm.NewVM(bb.ctx, vmopt) + if err != nil { + return nil, err + } + return bb, nil +} + +// PushMessages tries to push the specified message into the block. +// +// 1. All messages will be executed in-order. +// 2. Gas computation & nonce selection will be handled internally. +// 3. The base-fee is 0 so the sender does not need funds. +// 4. As usual, the sender must be an account (any account). +// 5. If the message fails to execute, this method will fail. +// +// Returns ErrOutOfGas when out of gas. Check BlockBuilder.GasRemaining and try pushing a cheaper +// message. +func (bb *BlockBuilder) PushMessage(msg *types.Message) (*types.MessageReceipt, error) { + if bb.gasTotal >= targetGas { + return nil, new(ErrOutOfGas) + } + + st := bb.StateTree() + store := bb.ActorStore() + + // Copy the message before we start mutating it. + msgCpy := *msg + msg = &msgCpy + + actor, err := st.GetActor(msg.From) + if err != nil { + return nil, err + } + if !builtin.IsAccountActor(actor.Code) { + return nil, xerrors.Errorf( + "messags may only be sent from account actors, got message from %s (%s)", + msg.From, builtin.ActorNameByCode(actor.Code), + ) + } + msg.Nonce = actor.Nonce + if msg.From.Protocol() == address.ID { + state, err := account.Load(store, actor) + if err != nil { + return nil, err + } + msg.From, err = state.PubkeyAddress() + if err != nil { + return nil, err + } + } + + // TODO: Our gas estimation is broken for payment channels due to horrible hacks in + // gasEstimateGasLimit. + if msg.Value == types.EmptyInt { + msg.Value = abi.NewTokenAmount(0) + } + msg.GasPremium = abi.NewTokenAmount(0) + msg.GasFeeCap = abi.NewTokenAmount(0) + msg.GasLimit = build.BlockGasLimit + + // We manually snapshot so we can revert nonce changes, etc. on failure. + st.Snapshot(bb.ctx) + defer st.ClearSnapshot() + + ret, err := bb.vm.ApplyMessage(bb.ctx, msg) + if err != nil { + _ = st.Revert() + return nil, err + } + if ret.ActorErr != nil { + _ = st.Revert() + return nil, ret.ActorErr + } + + // Sometimes there are bugs. Let's catch them. + if ret.GasUsed == 0 { + _ = st.Revert() + return nil, xerrors.Errorf("used no gas", + "msg", msg, + "ret", ret, + ) + } + + // TODO: consider applying overestimation? We're likely going to "over pack" here by + // ~25% because we're too accurate. + + // Did we go over? Yes, revert. + newTotal := bb.gasTotal + ret.GasUsed + if newTotal > targetGas { + _ = st.Revert() + return nil, &ErrOutOfGas{Available: targetGas - bb.gasTotal, Required: ret.GasUsed} + } + bb.gasTotal = newTotal + + // Update the gas limit. + msg.GasLimit = ret.GasUsed + + bb.messages = append(bb.messages, msg) + return &ret.MessageReceipt, nil +} + +// ActorStore returns the VM's current (pending) blockstore. +func (bb *BlockBuilder) ActorStore() adt.Store { + return bb.vm.ActorStore(bb.ctx) +} + +// StateTree returns the VM's current (pending) state-tree. This includes any changes made by +// successfully pushed messages. +// +// You probably want ParentStateTree +func (bb *BlockBuilder) StateTree() *state.StateTree { + return bb.vm.StateTree().(*state.StateTree) +} + +// ParentStateTree returns the parent state-tree (not the paren't tipset's parent state-tree). +func (bb *BlockBuilder) ParentStateTree() *state.StateTree { + return bb.parentSt +} + +// StateTreeByHeight will return a state-tree up through and including the current in-progress +// epoch. +// +// NOTE: This will return the state after the given epoch, not the parent state for the epoch. +func (bb *BlockBuilder) StateTreeByHeight(epoch abi.ChainEpoch) (*state.StateTree, error) { + now := bb.Height() + if epoch > now { + return nil, xerrors.Errorf( + "cannot load state-tree from future: %d > %d", epoch, bb.Height(), + ) + } else if epoch <= 0 { + return nil, xerrors.Errorf( + "cannot load state-tree: epoch %d <= 0", epoch, + ) + } + + // Manually handle "now" and "previous". + switch epoch { + case now: + return bb.StateTree(), nil + case now - 1: + return bb.ParentStateTree(), nil + } + + // Get the tipset of the block _after_ the target epoch so we can use its parent state. + targetTs, err := bb.sm.ChainStore().GetTipsetByHeight(bb.ctx, epoch+1, bb.parentTs, false) + if err != nil { + return nil, err + } + + return bb.sm.StateTree(targetTs.ParentState()) +} + +// Messages returns all messages currently packed into the next block. +// 1. DO NOT modify the slice, copy it. +// 2. DO NOT retain the slice, copy it. +func (bb *BlockBuilder) Messages() []*types.Message { + return bb.messages +} + +// GasRemaining returns the amount of remaining gas in the next block. +func (bb *BlockBuilder) GasRemaining() int64 { + return targetGas - bb.gasTotal +} + +// ParentTipSet returns the parent tipset. +func (bb *BlockBuilder) ParentTipSet() *types.TipSet { + return bb.parentTs +} + +// Height returns the epoch for the target block. +func (bb *BlockBuilder) Height() abi.ChainEpoch { + return bb.parentTs.Height() + 1 +} + +// NetworkVersion returns the network version for the target block. +func (bb *BlockBuilder) NetworkVersion() network.Version { + return bb.sm.GetNtwkVersion(bb.ctx, bb.Height()) +} + +// StateManager returns the stmgr.StateManager. +func (bb *BlockBuilder) StateManager() *stmgr.StateManager { + return bb.sm +} + +// ActorsVersion returns the actors version for the target block. +func (bb *BlockBuilder) ActorsVersion() actors.Version { + return actors.VersionForNetwork(bb.NetworkVersion()) +} + +func (bb *BlockBuilder) L() *zap.SugaredLogger { + return bb.logger +} diff --git a/cmd/lotus-sim/simulation/blockbuilder/errors.go b/cmd/lotus-sim/simulation/blockbuilder/errors.go new file mode 100644 index 000000000..ddf08ea18 --- /dev/null +++ b/cmd/lotus-sim/simulation/blockbuilder/errors.go @@ -0,0 +1,25 @@ +package blockbuilder + +import ( + "errors" + "fmt" +) + +// ErrOutOfGas is returned from BlockBuilder.PushMessage when the block does not have enough gas to +// fit the given message. +type ErrOutOfGas struct { + Available, Required int64 +} + +func (e *ErrOutOfGas) Error() string { + if e.Available == 0 { + return "out of gas: block full" + } + return fmt.Sprintf("out of gas: %d < %d", e.Required, e.Available) +} + +// IsOutOfGas returns true if the error is an "out of gas" error. +func IsOutOfGas(err error) bool { + var oog *ErrOutOfGas + return errors.As(err, &oog) +} diff --git a/cmd/lotus-sim/simulation/mock.go b/cmd/lotus-sim/simulation/mock/mock.go similarity index 75% rename from cmd/lotus-sim/simulation/mock.go rename to cmd/lotus-sim/simulation/mock/mock.go index 37f0a2c6c..e6651aca0 100644 --- a/cmd/lotus-sim/simulation/mock.go +++ b/cmd/lotus-sim/simulation/mock/mock.go @@ -1,4 +1,4 @@ -package simulation +package mock import ( "bytes" @@ -8,8 +8,11 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" + "github.com/ipfs/go-cid" + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" + tutils "github.com/filecoin-project/specs-actors/v5/support/testing" "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" ) @@ -26,14 +29,14 @@ const ( // mockVerifier is a simple mock for verifying "fake" proofs. type mockVerifier struct{} -var _ ffiwrapper.Verifier = mockVerifier{} +var Verifier ffiwrapper.Verifier = mockVerifier{} func (mockVerifier) VerifySeal(proof proof5.SealVerifyInfo) (bool, error) { addr, err := address.NewIDAddress(uint64(proof.Miner)) if err != nil { return false, err } - mockProof, err := mockSealProof(proof.SealProof, addr) + mockProof, err := MockSealProof(proof.SealProof, addr) if err != nil { return false, err } @@ -45,7 +48,7 @@ func (mockVerifier) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyPro if err != nil { return false, err } - mockProof, err := mockAggregateSealProof(aggregate.SealProof, addr, len(aggregate.Infos)) + mockProof, err := MockAggregateSealProof(aggregate.SealProof, addr, len(aggregate.Infos)) if err != nil { return false, err } @@ -63,7 +66,7 @@ func (mockVerifier) VerifyWindowPoSt(ctx context.Context, info proof5.WindowPoSt if err != nil { return false, err } - mockProof, err := mockWpostProof(proof.PoStProof, addr) + mockProof, err := MockWindowPoStProof(proof.PoStProof, addr) if err != nil { return false, err } @@ -74,8 +77,8 @@ func (mockVerifier) GenerateWinningPoStSectorChallenge(context.Context, abi.Regi panic("should not be called") } -// mockSealProof generates a mock "seal" proof tied to the specified proof type and the given miner. -func mockSealProof(proofType abi.RegisteredSealProof, minerAddr address.Address) ([]byte, error) { +// MockSealProof generates a mock "seal" proof tied to the specified proof type and the given miner. +func MockSealProof(proofType abi.RegisteredSealProof, minerAddr address.Address) ([]byte, error) { plen, err := proofType.ProofSize() if err != nil { return nil, err @@ -88,9 +91,9 @@ func mockSealProof(proofType abi.RegisteredSealProof, minerAddr address.Address) return proof, nil } -// mockAggregateSealProof generates a mock "seal" aggregate proof tied to the specified proof type, +// MockAggregateSealProof generates a mock "seal" aggregate proof tied to the specified proof type, // the given miner, and the number of proven sectors. -func mockAggregateSealProof(proofType abi.RegisteredSealProof, minerAddr address.Address, count int) ([]byte, error) { +func MockAggregateSealProof(proofType abi.RegisteredSealProof, minerAddr address.Address, count int) ([]byte, error) { proof := make([]byte, aggProofLen(count)) i := copy(proof, mockAggregateSealProofPrefix) binary.BigEndian.PutUint64(proof[i:], uint64(proofType)) @@ -102,9 +105,9 @@ func mockAggregateSealProof(proofType abi.RegisteredSealProof, minerAddr address return proof, nil } -// mockWpostProof generates a mock "window post" proof tied to the specified proof type, and the +// MockWindowPoStProof generates a mock "window post" proof tied to the specified proof type, and the // given miner. -func mockWpostProof(proofType abi.RegisteredPoStProof, minerAddr address.Address) ([]byte, error) { +func MockWindowPoStProof(proofType abi.RegisteredPoStProof, minerAddr address.Address) ([]byte, error) { plen, err := proofType.ProofSize() if err != nil { return nil, err @@ -115,6 +118,11 @@ func mockWpostProof(proofType abi.RegisteredPoStProof, minerAddr address.Address return proof, nil } +// makeCommR generates a "fake" but valid CommR for a sector. It is unique for the given sector/miner. +func MockCommR(minerAddr address.Address, sno abi.SectorNumber) cid.Cid { + return tutils.MakeCID(fmt.Sprintf("%s:%d", minerAddr, sno), &miner5.SealedCIDPrefix) +} + // TODO: dedup func aggProofLen(nproofs int) int { switch { diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go index 73c739e5b..0be2de182 100644 --- a/cmd/lotus-sim/simulation/node.go +++ b/cmd/lotus-sim/simulation/node.go @@ -16,6 +16,8 @@ import ( "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/vm" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/mock" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/stages" "github.com/filecoin-project/lotus/node/repo" ) @@ -53,7 +55,7 @@ func OpenNode(ctx context.Context, path string) (*Node, error) { return nil, err } - node.Chainstore = store.NewChainStore(node.Blockstore, node.Blockstore, node.MetadataDS, vm.Syscalls(mockVerifier{}), nil) + node.Chainstore = store.NewChainStore(node.Blockstore, node.Blockstore, node.MetadataDS, vm.Syscalls(mock.Verifier), nil) return &node, nil } @@ -74,12 +76,16 @@ func (nd *Node) Close() error { // LoadSim loads func (nd *Node) LoadSim(ctx context.Context, name string) (*Simulation, error) { + stages, err := stages.DefaultPipeline() + if err != nil { + return nil, err + } sim := &Simulation{ - Node: nd, - name: name, + Node: nd, + name: name, + stages: stages, } - var err error sim.head, err = sim.loadNamedTipSet("head") if err != nil { return nil, err @@ -113,10 +119,15 @@ func (nd *Node) CreateSim(ctx context.Context, name string, head *types.TipSet) if strings.Contains(name, "/") { return nil, xerrors.Errorf("simulation name %q cannot contain a '/'", name) } + stages, err := stages.DefaultPipeline() + if err != nil { + return nil, err + } sim := &Simulation{ name: name, Node: nd, StateManager: stmgr.NewStateManager(nd.Chainstore), + stages: stages, } if has, err := nd.MetadataDS.Has(sim.key("head")); err != nil { return nil, err diff --git a/cmd/lotus-sim/simulation/power.go b/cmd/lotus-sim/simulation/power.go deleted file mode 100644 index 9d0aceafe..000000000 --- a/cmd/lotus-sim/simulation/power.go +++ /dev/null @@ -1,58 +0,0 @@ -package simulation - -import ( - "context" - - "golang.org/x/xerrors" - - "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/lotus/chain/actors/builtin/power" -) - -// Load all power claims at the given height. -func (sim *Simulation) loadClaims(ctx context.Context, height abi.ChainEpoch) (map[address.Address]power.Claim, error) { - powerTable := make(map[address.Address]power.Claim) - store := sim.Chainstore.ActorStore(ctx) - - ts, err := sim.Chainstore.GetTipsetByHeight(ctx, height, sim.head, true) - if err != nil { - return nil, xerrors.Errorf("when projecting growth, failed to lookup lookback epoch: %w", err) - } - - powerActor, err := sim.StateManager.LoadActor(ctx, power.Address, ts) - if err != nil { - return nil, err - } - - powerState, err := power.Load(store, powerActor) - if err != nil { - return nil, err - } - err = powerState.ForEachClaim(func(miner address.Address, claim power.Claim) error { - // skip miners without power - if claim.RawBytePower.IsZero() { - return nil - } - powerTable[miner] = claim - return nil - }) - if err != nil { - return nil, err - } - return powerTable, nil -} - -// Compute the number of sectors a miner has from their power claim. -func sectorsFromClaim(sectorSize abi.SectorSize, c power.Claim) int64 { - if c.RawBytePower.Int == nil { - return 0 - } - sectorCount := big.Div(c.RawBytePower, big.NewIntUnsigned(uint64(sectorSize))) - if !sectorCount.IsInt64() { - panic("impossible number of sectors") - } - return sectorCount.Int64() -} diff --git a/cmd/lotus-sim/simulation/precommit.go b/cmd/lotus-sim/simulation/precommit.go deleted file mode 100644 index 854722f6a..000000000 --- a/cmd/lotus-sim/simulation/precommit.go +++ /dev/null @@ -1,233 +0,0 @@ -package simulation - -import ( - "context" - "fmt" - "time" - - "golang.org/x/xerrors" - - "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" - "github.com/ipfs/go-cid" - - miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" - tutils "github.com/filecoin-project/specs-actors/v5/support/testing" - - "github.com/filecoin-project/lotus/chain/actors" - "github.com/filecoin-project/lotus/chain/actors/aerrors" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/actors/policy" - "github.com/filecoin-project/lotus/chain/types" -) - -var ( - targetFunds = abi.TokenAmount(types.MustParseFIL("1000FIL")) - minFunds = abi.TokenAmount(types.MustParseFIL("100FIL")) -) - -// makeCommR generates a "fake" but valid CommR for a sector. It is unique for the given sector/miner. -func makeCommR(minerAddr address.Address, sno abi.SectorNumber) cid.Cid { - return tutils.MakeCID(fmt.Sprintf("%s:%d", minerAddr, sno), &miner5.SealedCIDPrefix) -} - -// packPreCommits packs pre-commit messages until the block is full. -func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (_err error) { - var ( - full bool - top1Count, top10Count, restCount int - ) - start := time.Now() - defer func() { - if _err != nil { - return - } - log.Debugw("packed pre commits", - "done", top1Count+top10Count+restCount, - "top1", top1Count, - "top10", top10Count, - "rest", restCount, - "filled-block", full, - "duration", time.Since(start), - ) - }() - - var top1Miners, top10Miners, restMiners int - for i := 0; ; i++ { - var ( - minerAddr address.Address - count *int - ) - - // We pre-commit for the top 1%, 10%, and the of the network 1/3rd of the time each. - // This won't yeild the most accurate distribution... but it'll give us a good - // enough distribution. - - // NOTE: We submit at most _one_ 819 sector batch per-miner per-block. See the - // comment on packPreCommitsMiner for why. We should fix this. - switch { - case (i%3) <= 0 && top1Miners < ss.minerDist.top1.len(): - count = &top1Count - minerAddr = ss.minerDist.top1.next() - top1Miners++ - case (i%3) <= 1 && top10Miners < ss.minerDist.top10.len(): - count = &top10Count - minerAddr = ss.minerDist.top10.next() - top10Miners++ - case (i%3) <= 2 && restMiners < ss.minerDist.rest.len(): - count = &restCount - minerAddr = ss.minerDist.rest.next() - restMiners++ - default: - // Well, we've run through all miners. - return nil - } - - var ( - added int - err error - ) - added, full, err = ss.packPreCommitsMiner(ctx, cb, minerAddr, maxProveCommitBatchSize) - if err != nil { - return xerrors.Errorf("failed to pack precommits for miner %s: %w", minerAddr, err) - } - *count += added - if full { - return nil - } - } -} - -// packPreCommitsMiner packs count pre-commits for the given miner. This should only be called once -// per-miner, per-epoch to avoid packing multiple pre-commits with the same sector numbers. -func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, minerAddr address.Address, count int) (int, bool, error) { - // Load everything. - epoch := ss.nextEpoch() - nv := ss.StateManager.GetNtwkVersion(ctx, epoch) - actor, minerState, err := ss.getMinerState(ctx, minerAddr) - if err != nil { - return 0, false, err - } - - minerInfo, err := ss.getMinerInfo(ctx, minerAddr) - if err != nil { - return 0, false, err - } - - // Make sure the miner is funded. - minerBalance, err := minerState.AvailableBalance(actor.Balance) - if err != nil { - return 0, false, err - } - - if big.Cmp(minerBalance, minFunds) < 0 { - err := fund(cb, minerAddr, 1) - if err != nil { - if err == ErrOutOfGas { - return 0, true, nil - } - return 0, false, err - } - } - - // Generate pre-commits. - sealType, err := miner.PreferredSealProofTypeFromWindowPoStType( - nv, minerInfo.WindowPoStProofType, - ) - if err != nil { - return 0, false, err - } - - sectorNos, err := minerState.UnallocatedSectorNumbers(count) - if err != nil { - return 0, false, err - } - - expiration := epoch + policy.GetMaxSectorExpirationExtension() - infos := make([]miner.SectorPreCommitInfo, len(sectorNos)) - for i, sno := range sectorNos { - infos[i] = miner.SectorPreCommitInfo{ - SealProof: sealType, - SectorNumber: sno, - SealedCID: makeCommR(minerAddr, sno), - SealRandEpoch: epoch - 1, - Expiration: expiration, - } - } - - // Commit the pre-commits. - added := 0 - if nv >= network.Version13 { - targetBatchSize := maxPreCommitBatchSize - for targetBatchSize >= minPreCommitBatchSize && len(infos) >= minPreCommitBatchSize { - batch := infos - if len(batch) > targetBatchSize { - batch = batch[:targetBatchSize] - } - params := miner5.PreCommitSectorBatchParams{ - Sectors: batch, - } - enc, err := actors.SerializeParams(¶ms) - if err != nil { - return added, false, err - } - // NOTE: just in-case, sendAndFund will "fund" and re-try for any message - // that fails due to "insufficient funds". - if _, err := sendAndFund(cb, &types.Message{ - To: minerAddr, - From: minerInfo.Worker, - Value: abi.NewTokenAmount(0), - Method: miner.Methods.PreCommitSectorBatch, - Params: enc, - }); err == ErrOutOfGas { - // try again with a smaller batch. - targetBatchSize /= 2 - continue - } else if aerr, ok := err.(aerrors.ActorError); ok && !aerr.IsFatal() { - // Log the error and move on. No reason to stop. - log.Errorw("failed to pre-commit for unknown reasons", - "error", aerr, - "miner", minerAddr, - "sectors", batch, - "epoch", ss.nextEpoch(), - ) - return added, false, nil - } else if err != nil { - return added, false, err - } - - for _, info := range batch { - if err := ss.commitQueue.enqueueProveCommit(minerAddr, epoch, info); err != nil { - return added, false, err - } - added++ - } - infos = infos[len(batch):] - } - } - for _, info := range infos { - enc, err := actors.SerializeParams(&info) - if err != nil { - return 0, false, err - } - if _, err := sendAndFund(cb, &types.Message{ - To: minerAddr, - From: minerInfo.Worker, - Value: abi.NewTokenAmount(0), - Method: miner.Methods.PreCommitSector, - Params: enc, - }); err == ErrOutOfGas { - return added, true, nil - } else if err != nil { - return added, false, err - } - - if err := ss.commitQueue.enqueueProveCommit(minerAddr, epoch, info); err != nil { - return added, false, err - } - added++ - } - return added, false, nil -} diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index 78e6c8e87..0af1120c2 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -15,28 +15,21 @@ import ( logging "github.com/ipfs/go-log/v2" blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" - miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/stages" ) var log = logging.Logger("simulation") const onboardingProjectionLookback = 2 * 7 * builtin.EpochsInDay // lookback two weeks -const ( - minPreCommitBatchSize = 1 - maxPreCommitBatchSize = miner5.PreCommitSectorBatchMaxSize - minProveCommitBatchSize = 4 - maxProveCommitBatchSize = miner5.MaxAggregatedSectors -) - // config is the simulation's config, persisted to the local metadata store and loaded on start. // -// See simulationState.loadConfig and simulationState.saveConfig. +// See Simulation.loadConfig and Simulation.saveConfig. type config struct { Upgrades map[network.Version]abi.ChainEpoch } @@ -93,9 +86,7 @@ type Simulation struct { st *state.StateTree head *types.TipSet - // lazy-loaded state - // access through `simState(ctx)` to load on-demand. - state *simulationState + stages []stages.Stage } // loadConfig loads a simulation's config from the datastore. This must be called on startup and may @@ -141,21 +132,6 @@ func (sim *Simulation) stateTree(ctx context.Context) (*state.StateTree, error) return sim.st, nil } -// Loads the simulation state. The state is memoized so this will be fast except the first time. -func (sim *Simulation) simState(ctx context.Context) (*simulationState, error) { - if sim.state == nil { - log.Infow("loading simulation") - state, err := loadSimulationState(ctx, sim) - if err != nil { - return nil, xerrors.Errorf("failed to load simulation state: %w", err) - } - sim.state = state - log.Infow("simulation loaded", "miners", len(sim.state.minerInfos)) - } - - return sim.state, nil -} - var simulationPrefix = datastore.NewKey("/simulation") // key returns the the key in the form /simulation//. For example, @@ -189,13 +165,6 @@ func (sim *Simulation) storeNamedTipSet(name string, ts *types.TipSet) error { return nil } -// Load loads the simulation state. This will happen automatically on first use, but it can be -// useful to preload for timing reasons. -func (sim *Simulation) Load(ctx context.Context) error { - _, err := sim.simState(ctx) - return err -} - // GetHead returns the current simulation head. func (sim *Simulation) GetHead() *types.TipSet { return sim.head diff --git a/cmd/lotus-sim/simulation/actor_iter.go b/cmd/lotus-sim/simulation/stages/actor_iter.go similarity index 97% rename from cmd/lotus-sim/simulation/actor_iter.go rename to cmd/lotus-sim/simulation/stages/actor_iter.go index 5df395e11..b2c14ebdb 100644 --- a/cmd/lotus-sim/simulation/actor_iter.go +++ b/cmd/lotus-sim/simulation/stages/actor_iter.go @@ -1,4 +1,4 @@ -package simulation +package stages import ( "math/rand" diff --git a/cmd/lotus-sim/simulation/commit_queue.go b/cmd/lotus-sim/simulation/stages/commit_queue.go similarity index 99% rename from cmd/lotus-sim/simulation/commit_queue.go rename to cmd/lotus-sim/simulation/stages/commit_queue.go index 75dc6f034..515e080a0 100644 --- a/cmd/lotus-sim/simulation/commit_queue.go +++ b/cmd/lotus-sim/simulation/stages/commit_queue.go @@ -1,4 +1,4 @@ -package simulation +package stages import ( "sort" diff --git a/cmd/lotus-sim/simulation/commit_queue_test.go b/cmd/lotus-sim/simulation/stages/commit_queue_test.go similarity index 99% rename from cmd/lotus-sim/simulation/commit_queue_test.go rename to cmd/lotus-sim/simulation/stages/commit_queue_test.go index 7c6bc6c8f..180624493 100644 --- a/cmd/lotus-sim/simulation/commit_queue_test.go +++ b/cmd/lotus-sim/simulation/stages/commit_queue_test.go @@ -1,4 +1,4 @@ -package simulation +package stages import ( "testing" diff --git a/cmd/lotus-sim/simulation/funding.go b/cmd/lotus-sim/simulation/stages/funding_stage.go similarity index 68% rename from cmd/lotus-sim/simulation/funding.go rename to cmd/lotus-sim/simulation/stages/funding_stage.go index e29f4f1b8..a0d9f4a22 100644 --- a/cmd/lotus-sim/simulation/funding.go +++ b/cmd/lotus-sim/simulation/stages/funding_stage.go @@ -1,4 +1,4 @@ -package simulation +package stages import ( "bytes" @@ -13,41 +13,44 @@ import ( "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/exitcode" - "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/aerrors" "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/actors/builtin/multisig" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/blockbuilder" ) var ( - fundAccount = func() address.Address { - addr, err := address.NewIDAddress(100) - if err != nil { - panic(err) - } - return addr - }() - minFundAcctFunds = abi.TokenAmount(types.MustParseFIL("1000000FIL")) - maxFundAcctFunds = abi.TokenAmount(types.MustParseFIL("100000000FIL")) - taxMin = abi.TokenAmount(types.MustParseFIL("1000FIL")) + TargetFunds = abi.TokenAmount(types.MustParseFIL("1000FIL")) + MinimumFunds = abi.TokenAmount(types.MustParseFIL("100FIL")) ) -func fund(send packFunc, target address.Address, times int) error { - amt := targetFunds - if times >= 1 { - if times >= 8 { - times = 8 // cap - } - amt = big.Lsh(amt, uint(times)) +type FundingStage struct { + fundAccount address.Address + taxMin abi.TokenAmount + minFunds, maxFunds abi.TokenAmount +} + +func NewFundingStage() (*FundingStage, error) { + // TODO: make all this configurable. + addr, err := address.NewIDAddress(100) + if err != nil { + return nil, err } - _, err := send(&types.Message{ - From: fundAccount, - To: target, - Value: amt, - Method: builtin.MethodSend, - }) - return err + return &FundingStage{ + fundAccount: addr, + taxMin: abi.TokenAmount(types.MustParseFIL("1000FIL")), + minFunds: abi.TokenAmount(types.MustParseFIL("1000000FIL")), + maxFunds: abi.TokenAmount(types.MustParseFIL("100000000FIL")), + }, nil +} + +func (*FundingStage) Name() string { + return "funding" +} + +func (fs *FundingStage) Fund(bb *blockbuilder.BlockBuilder, target address.Address) error { + return fs.fund(bb, target, 0) } // sendAndFund "packs" the given message, funding the actor if necessary. It: @@ -56,9 +59,9 @@ func fund(send packFunc, target address.Address, times int) error { // 2. If that fails, it checks to see if the exit code was ErrInsufficientFunds. // 3. If so, it sends 1K FIL from the "burnt funds actor" (because we need to send it from // somewhere) and re-tries the message.0 -func sendAndFund(send packFunc, msg *types.Message) (res *types.MessageReceipt, err error) { +func (fs *FundingStage) SendAndFund(bb *blockbuilder.BlockBuilder, msg *types.Message) (res *types.MessageReceipt, err error) { for i := 0; i < 10; i++ { - res, err = send(msg) + res, err = bb.PushMessage(msg) if err == nil { return res, nil } @@ -68,8 +71,8 @@ func sendAndFund(send packFunc, msg *types.Message) (res *types.MessageReceipt, } // Ok, insufficient funds. Let's fund this miner and try again. - if err := fund(send, msg.To, i); err != nil { - if err != ErrOutOfGas { + if err := fs.fund(bb, msg.To, i); err != nil { + if !blockbuilder.IsOutOfGas(err) { err = xerrors.Errorf("failed to fund %s: %w", msg.To, err) } return nil, err @@ -78,16 +81,30 @@ func sendAndFund(send packFunc, msg *types.Message) (res *types.MessageReceipt, return res, err } -func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err error) { - st, err := ss.stateTree(ctx) +func (fs *FundingStage) fund(bb *blockbuilder.BlockBuilder, target address.Address, times int) error { + amt := TargetFunds + if times > 0 { + if times >= 8 { + times = 8 // cap + } + amt = big.Lsh(amt, uint(times)) + } + _, err := bb.PushMessage(&types.Message{ + From: fs.fundAccount, + To: target, + Value: amt, + Method: builtin.MethodSend, + }) + return err +} + +func (fs *FundingStage) PackMessages(ctx context.Context, bb *blockbuilder.BlockBuilder) (_err error) { + st := bb.StateTree() + fundAccActor, err := st.GetActor(fs.fundAccount) if err != nil { return err } - fundAccActor, err := st.GetActor(fundAccount) - if err != nil { - return err - } - if minFundAcctFunds.LessThan(fundAccActor.Balance) { + if fs.minFunds.LessThan(fundAccActor.Balance) { return nil } @@ -102,10 +119,10 @@ func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err e var targets []*actor err = st.ForEach(func(addr address.Address, act *types.Actor) error { // Don't steal from ourselves! - if addr == fundAccount { + if addr == fs.fundAccount { return nil } - if act.Balance.LessThan(taxMin) { + if act.Balance.LessThan(fs.taxMin) { return nil } if !(builtin.IsAccountActor(act.Code) || builtin.IsMultisigActor(act.Code)) { @@ -124,19 +141,16 @@ func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err e return targets[i].Balance.GreaterThan(targets[j].Balance) }) - store := ss.Chainstore.ActorStore(ctx) - - epoch := ss.nextEpoch() - - nv := ss.StateManager.GetNtwkVersion(ctx, epoch) - actorsVersion := actors.VersionForNetwork(nv) + store := bb.ActorStore() + epoch := bb.Height() + actorsVersion := bb.ActorsVersion() var accounts, multisigs int defer func() { if _err != nil { return } - log.Infow("finished funding the simulation", + bb.L().Infow("finished funding the simulation", "duration", time.Since(start), "targets", len(targets), "epoch", epoch, @@ -150,11 +164,11 @@ func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err e for _, actor := range targets { switch { case builtin.IsAccountActor(actor.Code): - if _, err := cb(&types.Message{ + if _, err := bb.PushMessage(&types.Message{ From: actor.Address, - To: fundAccount, + To: fs.fundAccount, Value: actor.Balance, - }); err == ErrOutOfGas { + }); blockbuilder.IsOutOfGas(err) { return nil } else if err != nil { return err @@ -172,7 +186,7 @@ func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err e } if threshold > 16 { - log.Debugw("ignoring multisig with high threshold", + bb.L().Debugw("ignoring multisig with high threshold", "multisig", actor.Address, "threshold", threshold, "max", 16, @@ -185,7 +199,7 @@ func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err e return err } - if locked.LessThan(taxMin) { + if locked.LessThan(fs.taxMin) { continue // not worth it. } @@ -217,15 +231,15 @@ func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err e var txnId uint64 { msg, err := multisig.Message(actorsVersion, signers[0]).Propose( - actor.Address, fundAccount, available, + actor.Address, fs.fundAccount, available, builtin.MethodSend, nil, ) if err != nil { return err } - res, err := cb(msg) + res, err := bb.PushMessage(msg) if err != nil { - if err == ErrOutOfGas { + if blockbuilder.IsOutOfGas(err) { err = nil } return err @@ -237,7 +251,7 @@ func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err e } if ret.Applied { if !ret.Code.IsSuccess() { - log.Errorw("failed to tax multisig", + bb.L().Errorw("failed to tax multisig", "multisig", actor.Address, "exitcode", ret.Code, ) @@ -252,9 +266,9 @@ func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err e if err != nil { return err } - res, err := cb(msg) + res, err := bb.PushMessage(msg) if err != nil { - if err == ErrOutOfGas { + if blockbuilder.IsOutOfGas(err) { err = nil } return err @@ -271,7 +285,7 @@ func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err e } if !ret.Applied { - log.Errorw("failed to apply multisig transaction", + bb.L().Errorw("failed to apply multisig transaction", "multisig", actor.Address, "txnid", txnId, "signers", len(signers), @@ -280,7 +294,7 @@ func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err e continue } if !ret.Code.IsSuccess() { - log.Errorw("failed to tax multisig", + bb.L().Errorw("failed to tax multisig", "multisig", actor.Address, "txnid", txnId, "exitcode", ret.Code, @@ -292,7 +306,7 @@ func (ss *simulationState) packFunding(ctx context.Context, cb packFunc) (_err e panic("impossible case") } balance = big.Int{Int: balance.Add(balance.Int, actor.Balance.Int)} - if balance.GreaterThanEqual(maxFundAcctFunds) { + if balance.GreaterThanEqual(fs.maxFunds) { // There's no need to get greedy. // Well, really, we're trying to avoid messing with state _too_ much. return nil diff --git a/cmd/lotus-sim/simulation/stages/interface.go b/cmd/lotus-sim/simulation/stages/interface.go new file mode 100644 index 000000000..0c40a9b23 --- /dev/null +++ b/cmd/lotus-sim/simulation/stages/interface.go @@ -0,0 +1,27 @@ +package stages + +import ( + "context" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/blockbuilder" +) + +// Stage is a stage of the simulation. It's asked to pack messages for every block. +type Stage interface { + Name() string + PackMessages(ctx context.Context, bb *blockbuilder.BlockBuilder) error +} + +type Funding interface { + SendAndFund(*blockbuilder.BlockBuilder, *types.Message) (*types.MessageReceipt, error) + Fund(*blockbuilder.BlockBuilder, address.Address) error +} + +type Committer interface { + EnqueueProveCommit(addr address.Address, preCommitEpoch abi.ChainEpoch, info miner.SectorPreCommitInfo) error +} diff --git a/cmd/lotus-sim/simulation/stages/pipeline.go b/cmd/lotus-sim/simulation/stages/pipeline.go new file mode 100644 index 000000000..317e5b5a9 --- /dev/null +++ b/cmd/lotus-sim/simulation/stages/pipeline.go @@ -0,0 +1,31 @@ +package stages + +// DefaultPipeline returns the default stage pipeline. This pipeline. +// +// 1. Funds a "funding" actor, if necessary. +// 2. Submits any ready window posts. +// 3. Submits any ready prove commits. +// 4. Submits pre-commits with the remaining gas. +func DefaultPipeline() ([]Stage, error) { + // TODO: make this configurable. E.g., through DI? + // Ideally, we'd also be able to change priority, limit throughput (by limiting gas in the + // block builder, etc. + funding, err := NewFundingStage() + if err != nil { + return nil, err + } + wdpost, err := NewWindowPoStStage() + if err != nil { + return nil, err + } + provecommit, err := NewProveCommitStage(funding) + if err != nil { + return nil, err + } + precommit, err := NewPreCommitStage(funding, provecommit) + if err != nil { + return nil, err + } + + return []Stage{funding, wdpost, provecommit, precommit}, nil +} diff --git a/cmd/lotus-sim/simulation/stages/precommit_stage.go b/cmd/lotus-sim/simulation/stages/precommit_stage.go new file mode 100644 index 000000000..641292e0e --- /dev/null +++ b/cmd/lotus-sim/simulation/stages/precommit_stage.go @@ -0,0 +1,359 @@ +package stages + +import ( + "context" + "sort" + "time" + + "golang.org/x/xerrors" + + "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" + + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/aerrors" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/blockbuilder" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/mock" +) + +const ( + minPreCommitBatchSize = 1 + maxPreCommitBatchSize = miner5.PreCommitSectorBatchMaxSize +) + +type PreCommitStage struct { + funding Funding + committer Committer + + // The tiers represent the top 1%, top 10%, and everyone else. When sealing sectors, we seal + // a group of sectors for the top 1%, a group (half that size) for the top 10%, and one + // sector for everyone else. We determine these rates by looking at two power tables. + // TODO Ideally we'd "learn" this distribution from the network. But this is good enough for + // now. + top1, top10, rest actorIter + initialized bool +} + +func NewPreCommitStage(funding Funding, committer Committer) (*PreCommitStage, error) { + return &PreCommitStage{ + funding: funding, + committer: committer, + }, nil +} + +func (*PreCommitStage) Name() string { + return "pre-commit" +} + +// packPreCommits packs pre-commit messages until the block is full. +func (stage *PreCommitStage) PackMessages(ctx context.Context, bb *blockbuilder.BlockBuilder) (_err error) { + if !stage.initialized { + if err := stage.load(ctx, bb); err != nil { + return err + } + } + + var ( + full bool + top1Count, top10Count, restCount int + ) + start := time.Now() + defer func() { + if _err != nil { + return + } + bb.L().Debugw("packed pre commits", + "done", top1Count+top10Count+restCount, + "top1", top1Count, + "top10", top10Count, + "rest", restCount, + "filled-block", full, + "duration", time.Since(start), + ) + }() + + var top1Miners, top10Miners, restMiners int + for i := 0; ; i++ { + var ( + minerAddr address.Address + count *int + ) + + // We pre-commit for the top 1%, 10%, and the of the network 1/3rd of the time each. + // This won't yeild the most accurate distribution... but it'll give us a good + // enough distribution. + switch { + case (i%3) <= 0 && top1Miners < stage.top1.len(): + count = &top1Count + minerAddr = stage.top1.next() + top1Miners++ + case (i%3) <= 1 && top10Miners < stage.top10.len(): + count = &top10Count + minerAddr = stage.top10.next() + top10Miners++ + case (i%3) <= 2 && restMiners < stage.rest.len(): + count = &restCount + minerAddr = stage.rest.next() + restMiners++ + default: + // Well, we've run through all miners. + return nil + } + + var ( + added int + err error + ) + added, full, err = stage.packMiner(ctx, bb, minerAddr, maxProveCommitBatchSize) + if err != nil { + return xerrors.Errorf("failed to pack precommits for miner %s: %w", minerAddr, err) + } + *count += added + if full { + return nil + } + } +} + +// packPreCommitsMiner packs count pre-commits for the given miner. +func (stage *PreCommitStage) packMiner( + ctx context.Context, bb *blockbuilder.BlockBuilder, + minerAddr address.Address, count int, +) (int, bool, error) { + log := bb.L().With("miner", minerAddr) + epoch := bb.Height() + nv := bb.NetworkVersion() + + minerActor, err := bb.StateTree().GetActor(minerAddr) + if err != nil { + return 0, false, err + } + minerState, err := miner.Load(bb.ActorStore(), minerActor) + if err != nil { + return 0, false, err + } + + minerInfo, err := minerState.Info() + if err != nil { + return 0, false, err + } + + // Make sure the miner is funded. + minerBalance, err := minerState.AvailableBalance(minerActor.Balance) + if err != nil { + return 0, false, err + } + + if big.Cmp(minerBalance, MinimumFunds) < 0 { + err := stage.funding.Fund(bb, minerAddr) + if err != nil { + if blockbuilder.IsOutOfGas(err) { + return 0, true, nil + } + return 0, false, err + } + } + + // Generate pre-commits. + sealType, err := miner.PreferredSealProofTypeFromWindowPoStType( + nv, minerInfo.WindowPoStProofType, + ) + if err != nil { + return 0, false, err + } + + sectorNos, err := minerState.UnallocatedSectorNumbers(count) + if err != nil { + return 0, false, err + } + + expiration := epoch + policy.GetMaxSectorExpirationExtension() + infos := make([]miner.SectorPreCommitInfo, len(sectorNos)) + for i, sno := range sectorNos { + infos[i] = miner.SectorPreCommitInfo{ + SealProof: sealType, + SectorNumber: sno, + SealedCID: mock.MockCommR(minerAddr, sno), + SealRandEpoch: epoch - 1, + Expiration: expiration, + } + } + + // Commit the pre-commits. + added := 0 + if nv >= network.Version13 { + targetBatchSize := maxPreCommitBatchSize + for targetBatchSize >= minPreCommitBatchSize && len(infos) >= minPreCommitBatchSize { + batch := infos + if len(batch) > targetBatchSize { + batch = batch[:targetBatchSize] + } + params := miner5.PreCommitSectorBatchParams{ + Sectors: batch, + } + enc, err := actors.SerializeParams(¶ms) + if err != nil { + return added, false, err + } + // NOTE: just in-case, sendAndFund will "fund" and re-try for any message + // that fails due to "insufficient funds". + if _, err := stage.funding.SendAndFund(bb, &types.Message{ + To: minerAddr, + From: minerInfo.Worker, + Value: abi.NewTokenAmount(0), + Method: miner.Methods.PreCommitSectorBatch, + Params: enc, + }); blockbuilder.IsOutOfGas(err) { + // try again with a smaller batch. + targetBatchSize /= 2 + continue + } else if aerr, ok := err.(aerrors.ActorError); ok && !aerr.IsFatal() { + // Log the error and move on. No reason to stop. + log.Errorw("failed to pre-commit for unknown reasons", + "error", aerr, + "sectors", batch, + ) + return added, false, nil + } else if err != nil { + return added, false, err + } + + for _, info := range batch { + if err := stage.committer.EnqueueProveCommit(minerAddr, epoch, info); err != nil { + return added, false, err + } + added++ + } + infos = infos[len(batch):] + } + } + for _, info := range infos { + enc, err := actors.SerializeParams(&info) + if err != nil { + return 0, false, err + } + if _, err := stage.funding.SendAndFund(bb, &types.Message{ + To: minerAddr, + From: minerInfo.Worker, + Value: abi.NewTokenAmount(0), + Method: miner.Methods.PreCommitSector, + Params: enc, + }); blockbuilder.IsOutOfGas(err) { + return added, true, nil + } else if err != nil { + return added, false, err + } + + if err := stage.committer.EnqueueProveCommit(minerAddr, epoch, info); err != nil { + return added, false, err + } + added++ + } + return added, false, nil +} + +func (ps *PreCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBuilder) (_err error) { + bb.L().Infow("loading miner power for pre-commits") + start := time.Now() + defer func() { + if _err != nil { + return + } + bb.L().Infow("loaded miner power for pre-commits", + "duration", time.Since(start), + "top1", ps.top1.len(), + "top10", ps.top10.len(), + "rest", ps.rest.len(), + ) + }() + lookbackEpoch := bb.Height() - (14 * builtin.EpochsInDay) + lookbackPowerTable, err := loadClaims(ctx, bb, lookbackEpoch) + if err != nil { + return xerrors.Errorf("failed to load claims from lookback epoch %d: %w", lookbackEpoch, err) + } + + store := bb.ActorStore() + st := bb.ParentStateTree() + powerState, err := loadPower(store, st) + if err != nil { + return xerrors.Errorf("failed to power actor: %w", err) + } + + type onboardingInfo struct { + addr address.Address + onboardingRate uint64 + } + sealList := make([]onboardingInfo, 0, len(lookbackPowerTable)) + err = powerState.ForEachClaim(func(addr address.Address, claim power.Claim) error { + if claim.RawBytePower.IsZero() { + return nil + } + + minerState, err := loadMiner(store, st, addr) + if err != nil { + return err + } + info, err := minerState.Info() + if err != nil { + return err + } + + sectorsAdded := sectorsFromClaim(info.SectorSize, claim) + if lookbackClaim, ok := lookbackPowerTable[addr]; !ok { + sectorsAdded -= sectorsFromClaim(info.SectorSize, lookbackClaim) + } + + // NOTE: power _could_ have been lost, but that's too much of a pain to care + // about. We _could_ look for faulty power by iterating through all + // deadlines, but I'd rather not. + if sectorsAdded > 0 { + sealList = append(sealList, onboardingInfo{addr, uint64(sectorsAdded)}) + } + return nil + }) + if err != nil { + return err + } + + if len(sealList) == 0 { + return xerrors.Errorf("simulation has no miners") + } + + // Now that we have a list of sealing miners, sort them into percentiles. + sort.Slice(sealList, func(i, j int) bool { + return sealList[i].onboardingRate < sealList[j].onboardingRate + }) + + // reset, just in case. + ps.top1 = actorIter{} + ps.top10 = actorIter{} + ps.rest = actorIter{} + + for i, oi := range sealList { + var dist *actorIter + if i < len(sealList)/100 { + dist = &ps.top1 + } else if i < len(sealList)/10 { + dist = &ps.top10 + } else { + dist = &ps.rest + } + dist.add(oi.addr) + } + + ps.top1.shuffle() + ps.top10.shuffle() + ps.rest.shuffle() + + ps.initialized = true + return nil +} diff --git a/cmd/lotus-sim/simulation/provecommit.go b/cmd/lotus-sim/simulation/stages/provecommit_stage.go similarity index 62% rename from cmd/lotus-sim/simulation/provecommit.go rename to cmd/lotus-sim/simulation/stages/provecommit_stage.go index 12c67ba8b..c2ffb8416 100644 --- a/cmd/lotus-sim/simulation/provecommit.go +++ b/cmd/lotus-sim/simulation/stages/provecommit_stage.go @@ -1,4 +1,4 @@ -package simulation +package stages import ( "context" @@ -16,15 +16,50 @@ import ( "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/aerrors" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/blockbuilder" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/mock" ) +const ( + minProveCommitBatchSize = 4 + maxProveCommitBatchSize = miner5.MaxAggregatedSectors +) + +type ProveCommitStage struct { + funding Funding + // We track the set of pending commits. On simulation load, and when a new pre-commit is + // added to the chain, we put the commit in this queue. advanceEpoch(currentEpoch) should be + // called on this queue at every epoch before using it. + commitQueue commitQueue + initialized bool +} + +func NewProveCommitStage(funding Funding) (*ProveCommitStage, error) { + return &ProveCommitStage{ + funding: funding, + }, nil +} + +func (*ProveCommitStage) Name() string { + return "prove-commit" +} + +func (stage *ProveCommitStage) EnqueueProveCommit( + minerAddr address.Address, preCommitEpoch abi.ChainEpoch, info miner.SectorPreCommitInfo, +) error { + return stage.commitQueue.enqueueProveCommit(minerAddr, preCommitEpoch, info) +} + // packProveCommits packs all prove-commits for all "ready to be proven" sectors until it fills the // block or runs out. -func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_err error) { +func (stage *ProveCommitStage) PackMessages(ctx context.Context, bb *blockbuilder.BlockBuilder) (_err error) { + if !stage.initialized { + } // Roll the commitQueue forward. - ss.commitQueue.advanceEpoch(ss.nextEpoch()) + stage.commitQueue.advanceEpoch(bb.Height()) start := time.Now() var failed, done, unbatched, count int @@ -32,8 +67,8 @@ func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_ if _err != nil { return } - remaining := ss.commitQueue.ready() - log.Debugw("packed prove commits", + remaining := stage.commitQueue.ready() + bb.L().Debugw("packed prove commits", "remaining", remaining, "done", done, "failed", failed, @@ -44,12 +79,12 @@ func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_ }() for { - addr, pending, ok := ss.commitQueue.nextMiner() + addr, pending, ok := stage.commitQueue.nextMiner() if !ok { return nil } - res, err := ss.packProveCommitsMiner(ctx, cb, addr, pending) + res, err := stage.packProveCommitsMiner(ctx, bb, addr, pending) if err != nil { return err } @@ -72,16 +107,26 @@ type proveCommitResult struct { // available prove-commits, batching as much as possible. // // This function will fund as necessary from the "burnt funds actor" (look, it's convenient). -func (ss *simulationState) packProveCommitsMiner( - ctx context.Context, cb packFunc, minerAddr address.Address, +func (stage *ProveCommitStage) packProveCommitsMiner( + ctx context.Context, bb *blockbuilder.BlockBuilder, minerAddr address.Address, pending minerPendingCommits, ) (res proveCommitResult, _err error) { - info, err := ss.getMinerInfo(ctx, minerAddr) + minerActor, err := bb.StateTree().GetActor(minerAddr) + if err != nil { + return res, err + } + minerState, err := miner.Load(bb.ActorStore(), minerActor) + if err != nil { + return res, err + } + info, err := minerState.Info() if err != nil { return res, err } - nv := ss.StateManager.GetNtwkVersion(ctx, ss.nextEpoch()) + log := bb.L().With("miner", minerAddr) + + nv := bb.NetworkVersion() for sealType, snos := range pending { if nv >= network.Version13 { for len(snos) > minProveCommitBatchSize { @@ -91,7 +136,7 @@ func (ss *simulationState) packProveCommitsMiner( } batch := snos[:batchSize] - proof, err := mockAggregateSealProof(sealType, minerAddr, batchSize) + proof, err := mock.MockAggregateSealProof(sealType, minerAddr, batchSize) if err != nil { return res, err } @@ -109,7 +154,7 @@ func (ss *simulationState) packProveCommitsMiner( return res, err } - if _, err := sendAndFund(cb, &types.Message{ + if _, err := stage.funding.SendAndFund(bb, &types.Message{ From: info.Worker, To: minerAddr, Value: abi.NewTokenAmount(0), @@ -117,7 +162,7 @@ func (ss *simulationState) packProveCommitsMiner( Params: enc, }); err == nil { res.done += len(batch) - } else if err == ErrOutOfGas { + } else if blockbuilder.IsOutOfGas(err) { res.full = true return res, nil } else if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() { @@ -135,9 +180,9 @@ func (ss *simulationState) packProveCommitsMiner( // backloged to hit this case, but we might as well handle // it. // First, split into "good" and "missing" - good, err := ss.filterProveCommits(ctx, minerAddr, batch) + good, err := stage.filterProveCommits(ctx, bb, minerAddr, batch) if err != nil { - log.Errorw("failed to filter prove commits", "miner", minerAddr, "error", err) + log.Errorw("failed to filter prove commits", "error", err) // fail with the original error. return res, aerr } @@ -145,17 +190,13 @@ func (ss *simulationState) packProveCommitsMiner( if removed == 0 { log.Errorw("failed to prove-commit for unknown reasons", "error", aerr, - "miner", minerAddr, "sectors", batch, - "epoch", ss.nextEpoch(), ) res.failed += len(batch) } else if len(good) == 0 { log.Errorw("failed to prove commit missing pre-commits", "error", aerr, - "miner", minerAddr, "discarded", removed, - "epoch", ss.nextEpoch(), ) res.failed += len(batch) } else { @@ -166,10 +207,8 @@ func (ss *simulationState) packProveCommitsMiner( log.Errorw("failed to prove commit expired/missing pre-commits", "error", aerr, - "miner", minerAddr, "discarded", removed, "kept", len(good), - "epoch", ss.nextEpoch(), ) res.failed += removed @@ -178,17 +217,13 @@ func (ss *simulationState) packProveCommitsMiner( } log.Errorw("failed to prove commit missing sector(s)", "error", err, - "miner", minerAddr, "sectors", batch, - "epoch", ss.nextEpoch(), ) res.failed += len(batch) } else { log.Errorw("failed to prove commit sector(s)", "error", err, - "miner", minerAddr, "sectors", batch, - "epoch", ss.nextEpoch(), ) res.failed += len(batch) } @@ -200,7 +235,7 @@ func (ss *simulationState) packProveCommitsMiner( sno := snos[0] snos = snos[1:] - proof, err := mockSealProof(sealType, minerAddr) + proof, err := mock.MockSealProof(sealType, minerAddr) if err != nil { return res, err } @@ -212,7 +247,7 @@ func (ss *simulationState) packProveCommitsMiner( if err != nil { return res, err } - if _, err := sendAndFund(cb, &types.Message{ + if _, err := stage.funding.SendAndFund(bb, &types.Message{ From: info.Worker, To: minerAddr, Value: abi.NewTokenAmount(0), @@ -221,7 +256,7 @@ func (ss *simulationState) packProveCommitsMiner( }); err == nil { res.unbatched++ res.done++ - } else if err == ErrOutOfGas { + } else if blockbuilder.IsOutOfGas(err) { res.full = true return res, nil } else if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() { @@ -229,9 +264,7 @@ func (ss *simulationState) packProveCommitsMiner( } else { log.Errorw("failed to prove commit sector(s)", "error", err, - "miner", minerAddr, "sectors", []abi.SectorNumber{sno}, - "epoch", ss.nextEpoch(), ) res.failed++ } @@ -243,32 +276,35 @@ func (ss *simulationState) packProveCommitsMiner( return res, nil } -// loadProveCommitsMiner enqueue all pending prove-commits for the given miner. This is called on -// load to populate the commitQueue and should not need to be called later. +// loadMiner enqueue all pending prove-commits for the given miner. This is called on load to +// populate the commitQueue and should not need to be called later. // // It will drop any pre-commits that have already expired. -func (ss *simulationState) loadProveCommitsMiner(ctx context.Context, addr address.Address, minerState miner.State) error { +func (stage *ProveCommitStage) loadMiner(ctx context.Context, bb *blockbuilder.BlockBuilder, addr address.Address) error { + epoch := bb.Height() + av := bb.ActorsVersion() + minerState, err := loadMiner(bb.ActorStore(), bb.ParentStateTree(), addr) + if err != nil { + return err + } + // Find all pending prove commits and group by proof type. Really, there should never // (except during upgrades be more than one type. - nextEpoch := ss.nextEpoch() - nv := ss.StateManager.GetNtwkVersion(ctx, nextEpoch) - av := actors.VersionForNetwork(nv) - var total, dropped int - err := minerState.ForEachPrecommittedSector(func(info miner.SectorPreCommitOnChainInfo) error { + err = minerState.ForEachPrecommittedSector(func(info miner.SectorPreCommitOnChainInfo) error { total++ msd := policy.GetMaxProveCommitDuration(av, info.Info.SealProof) - if nextEpoch > info.PreCommitEpoch+msd { + if epoch > info.PreCommitEpoch+msd { dropped++ return nil } - return ss.commitQueue.enqueueProveCommit(addr, info.PreCommitEpoch, info.Info) + return stage.commitQueue.enqueueProveCommit(addr, info.PreCommitEpoch, info.Info) }) if err != nil { return err } if dropped > 0 { - log.Warnw("dropped expired pre-commits on load", + bb.L().Warnw("dropped expired pre-commits on load", "miner", addr, "total", total, "expired", dropped, @@ -278,15 +314,22 @@ func (ss *simulationState) loadProveCommitsMiner(ctx context.Context, addr addre } // filterProveCommits filters out expired and/or missing pre-commits. -func (ss *simulationState) filterProveCommits(ctx context.Context, minerAddr address.Address, snos []abi.SectorNumber) ([]abi.SectorNumber, error) { - _, minerState, err := ss.getMinerState(ctx, minerAddr) +func (stage *ProveCommitStage) filterProveCommits( + ctx context.Context, bb *blockbuilder.BlockBuilder, + minerAddr address.Address, snos []abi.SectorNumber, +) ([]abi.SectorNumber, error) { + act, err := bb.StateTree().GetActor(minerAddr) if err != nil { return nil, err } - nextEpoch := ss.nextEpoch() - nv := ss.StateManager.GetNtwkVersion(ctx, nextEpoch) - av := actors.VersionForNetwork(nv) + minerState, err := miner.Load(bb.ActorStore(), act) + if err != nil { + return nil, err + } + + nextEpoch := bb.Height() + av := bb.ActorsVersion() good := make([]abi.SectorNumber, 0, len(snos)) for _, sno := range snos { @@ -305,3 +348,19 @@ func (ss *simulationState) filterProveCommits(ctx context.Context, minerAddr add } return good, nil } + +func (stage *ProveCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBuilder) error { + powerState, err := loadPower(bb.ActorStore(), bb.ParentStateTree()) + if err != nil { + return err + } + + return powerState.ForEachClaim(func(minerAddr address.Address, claim power.Claim) error { + // TODO: If we want to finish pre-commits for "new" miners, we'll need to change + // this. + if claim.RawBytePower.IsZero() { + return nil + } + return stage.loadMiner(ctx, bb, minerAddr) + }) +} diff --git a/cmd/lotus-sim/simulation/stages/util.go b/cmd/lotus-sim/simulation/stages/util.go new file mode 100644 index 000000000..4c23a83d6 --- /dev/null +++ b/cmd/lotus-sim/simulation/stages/util.go @@ -0,0 +1,81 @@ +package stages + +import ( + "context" + + "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/crypto" + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/blockbuilder" +) + +func loadMiner(store adt.Store, st types.StateTree, addr address.Address) (miner.State, error) { + minerActor, err := st.GetActor(addr) + if err != nil { + return nil, err + } + return miner.Load(store, minerActor) +} + +func loadPower(store adt.Store, st types.StateTree) (power.State, error) { + powerActor, err := st.GetActor(power.Address) + if err != nil { + return nil, err + } + return power.Load(store, powerActor) +} + +// Compute the number of sectors a miner has from their power claim. +func sectorsFromClaim(sectorSize abi.SectorSize, c power.Claim) int64 { + if c.RawBytePower.Int == nil { + return 0 + } + sectorCount := big.Div(c.RawBytePower, big.NewIntUnsigned(uint64(sectorSize))) + if !sectorCount.IsInt64() { + panic("impossible number of sectors") + } + return sectorCount.Int64() +} + +// loadClaims will load all non-zero claims at the given epoch. +func loadClaims( + ctx context.Context, bb *blockbuilder.BlockBuilder, height abi.ChainEpoch, +) (map[address.Address]power.Claim, error) { + powerTable := make(map[address.Address]power.Claim) + + st, err := bb.StateTreeByHeight(height) + if err != nil { + return nil, err + } + + powerState, err := loadPower(bb.ActorStore(), st) + if err != nil { + return nil, err + } + + err = powerState.ForEachClaim(func(miner address.Address, claim power.Claim) error { + // skip miners without power + if claim.RawBytePower.IsZero() { + return nil + } + powerTable[miner] = claim + return nil + }) + if err != nil { + return nil, err + } + return powerTable, nil +} + +func postChainCommitInfo(ctx context.Context, bb *blockbuilder.BlockBuilder, epoch abi.ChainEpoch) (abi.Randomness, error) { + cs := bb.StateManager().ChainStore() + ts := bb.ParentTipSet() + commitRand, err := cs.GetChainRandomness(ctx, ts.Cids(), crypto.DomainSeparationTag_PoStChainCommit, epoch, nil, true) + return commitRand, err +} diff --git a/cmd/lotus-sim/simulation/stages/windowpost_stage.go b/cmd/lotus-sim/simulation/stages/windowpost_stage.go new file mode 100644 index 000000000..e6583012d --- /dev/null +++ b/cmd/lotus-sim/simulation/stages/windowpost_stage.go @@ -0,0 +1,312 @@ +package stages + +import ( + "context" + "math" + "time" + + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/aerrors" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/blockbuilder" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/mock" +) + +type WindowPoStStage struct { + // We track the window post periods per miner and assume that no new miners are ever added. + + // We record all pending window post messages, and the epoch up through which we've + // generated window post messages. + pendingWposts []*types.Message + wpostPeriods [][]address.Address // (epoch % (epochs in a deadline)) -> miner + nextWpostEpoch abi.ChainEpoch +} + +func NewWindowPoStStage() (*WindowPoStStage, error) { + return new(WindowPoStStage), nil +} + +func (*WindowPoStStage) Name() string { + return "window-post" +} + +// packWindowPoSts packs window posts until either the block is full or all healty sectors +// have been proven. It does not recover sectors. +func (stage *WindowPoStStage) PackMessages(ctx context.Context, bb *blockbuilder.BlockBuilder) (_err error) { + // Push any new window posts into the queue. + if err := stage.tick(ctx, bb); err != nil { + return err + } + done := 0 + failed := 0 + defer func() { + if _err != nil { + return + } + + bb.L().Debugw("packed window posts", + "done", done, + "failed", failed, + "remaining", len(stage.pendingWposts), + ) + }() + // Then pack as many as we can. + for len(stage.pendingWposts) > 0 { + next := stage.pendingWposts[0] + if _, err := bb.PushMessage(next); err != nil { + if blockbuilder.IsOutOfGas(err) { + return nil + } + if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() { + return err + } + bb.L().Errorw("failed to submit windowed post", + "error", err, + "miner", next.To, + ) + failed++ + } else { + done++ + } + + stage.pendingWposts = stage.pendingWposts[1:] + } + stage.pendingWposts = nil + return nil +} + +// stepWindowPoStsMiner enqueues all missing window posts for the current epoch for the given miner. +func (stage *WindowPoStStage) queueMiner( + ctx context.Context, bb *blockbuilder.BlockBuilder, + addr address.Address, minerState miner.State, + commitEpoch abi.ChainEpoch, commitRand abi.Randomness, +) error { + + if active, err := minerState.DeadlineCronActive(); err != nil { + return err + } else if !active { + return nil + } + + minerInfo, err := minerState.Info() + if err != nil { + return err + } + + di, err := minerState.DeadlineInfo(bb.Height()) + if err != nil { + return err + } + di = di.NextNotElapsed() + + dl, err := minerState.LoadDeadline(di.Index) + if err != nil { + return err + } + + provenBf, err := dl.PartitionsPoSted() + if err != nil { + return err + } + proven, err := provenBf.AllMap(math.MaxUint64) + if err != nil { + return err + } + + var ( + partitions []miner.PoStPartition + partitionGroups [][]miner.PoStPartition + ) + // Only prove partitions with live sectors. + err = dl.ForEachPartition(func(idx uint64, part miner.Partition) error { + if proven[idx] { + return nil + } + // TODO: set this to the actual limit from specs-actors. + // NOTE: We're mimicing the behavior of wdpost_run.go here. + if len(partitions) > 0 && idx%4 == 0 { + partitionGroups = append(partitionGroups, partitions) + partitions = nil + + } + live, err := part.LiveSectors() + if err != nil { + return err + } + liveCount, err := live.Count() + if err != nil { + return err + } + faulty, err := part.FaultySectors() + if err != nil { + return err + } + faultyCount, err := faulty.Count() + if err != nil { + return err + } + if liveCount-faultyCount > 0 { + partitions = append(partitions, miner.PoStPartition{Index: idx}) + } + return nil + }) + if err != nil { + return err + } + if len(partitions) > 0 { + partitionGroups = append(partitionGroups, partitions) + partitions = nil + } + + proof, err := mock.MockWindowPoStProof(minerInfo.WindowPoStProofType, addr) + if err != nil { + return err + } + for _, group := range partitionGroups { + params := miner.SubmitWindowedPoStParams{ + Deadline: di.Index, + Partitions: group, + Proofs: []proof5.PoStProof{{ + PoStProof: minerInfo.WindowPoStProofType, + ProofBytes: proof, + }}, + ChainCommitEpoch: commitEpoch, + ChainCommitRand: commitRand, + } + enc, aerr := actors.SerializeParams(¶ms) + if aerr != nil { + return xerrors.Errorf("could not serialize submit window post parameters: %w", aerr) + } + msg := &types.Message{ + To: addr, + From: minerInfo.Worker, + Method: miner.Methods.SubmitWindowedPoSt, + Params: enc, + Value: types.NewInt(0), + } + stage.pendingWposts = append(stage.pendingWposts, msg) + } + return nil +} + +func (stage *WindowPoStStage) load(ctx context.Context, bb *blockbuilder.BlockBuilder) (_err error) { + bb.L().Info("loading window post info") + + start := time.Now() + defer func() { + if _err != nil { + return + } + + bb.L().Infow("loaded window post info", "duration", time.Since(start)) + }() + + // reset + stage.wpostPeriods = make([][]address.Address, miner.WPoStChallengeWindow) + stage.pendingWposts = nil + stage.nextWpostEpoch = bb.Height() + 1 + + st := bb.ParentStateTree() + store := bb.ActorStore() + + powerState, err := loadPower(store, st) + if err != nil { + return err + } + + commitEpoch := bb.ParentTipSet().Height() + commitRand, err := postChainCommitInfo(ctx, bb, commitEpoch) + if err != nil { + return err + } + + return powerState.ForEachClaim(func(minerAddr address.Address, claim power.Claim) error { + // TODO: If we start recovering power, we'll need to change this. + if claim.RawBytePower.IsZero() { + return nil + } + + minerState, err := loadMiner(store, st, minerAddr) + if err != nil { + return err + } + + // Shouldn't be necessary if the miner has power, but we might as well be safe. + if active, err := minerState.DeadlineCronActive(); err != nil { + return err + } else if !active { + return nil + } + + // Record when we need to prove for this miner. + dinfo, err := minerState.DeadlineInfo(bb.Height()) + if err != nil { + return err + } + dinfo = dinfo.NextNotElapsed() + + ppOffset := int(dinfo.PeriodStart % miner.WPoStChallengeWindow) + stage.wpostPeriods[ppOffset] = append(stage.wpostPeriods[ppOffset], minerAddr) + + return stage.queueMiner(ctx, bb, minerAddr, minerState, commitEpoch, commitRand) + }) +} + +func (stage *WindowPoStStage) tick(ctx context.Context, bb *blockbuilder.BlockBuilder) error { + // If this is our first time, load from scratch. + if stage.wpostPeriods == nil { + return stage.load(ctx, bb) + } + + targetHeight := bb.Height() + now := time.Now() + was := len(stage.pendingWposts) + count := 0 + defer func() { + bb.L().Debugw("computed window posts", + "miners", count, + "count", len(stage.pendingWposts)-was, + "duration", time.Since(now), + ) + }() + + st := bb.ParentStateTree() + store := bb.ActorStore() + + // Perform a bit of catch up. This lets us do things like skip blocks at upgrades then catch + // up to make the simualtion easier. + for ; stage.nextWpostEpoch <= targetHeight; stage.nextWpostEpoch++ { + if stage.nextWpostEpoch+miner.WPoStChallengeWindow < targetHeight { + bb.L().Warnw("skipping old window post", "deadline-open", stage.nextWpostEpoch) + continue + } + commitEpoch := stage.nextWpostEpoch - 1 + commitRand, err := postChainCommitInfo(ctx, bb, commitEpoch) + if err != nil { + return err + } + + for _, addr := range stage.wpostPeriods[int(stage.nextWpostEpoch%miner.WPoStChallengeWindow)] { + minerState, err := loadMiner(store, st, addr) + if err != nil { + return err + } + + if err := stage.queueMiner(ctx, bb, addr, minerState, commitEpoch, commitRand); err != nil { + return err + } + count++ + } + + } + return nil +} diff --git a/cmd/lotus-sim/simulation/state.go b/cmd/lotus-sim/simulation/state.go deleted file mode 100644 index 383ef158a..000000000 --- a/cmd/lotus-sim/simulation/state.go +++ /dev/null @@ -1,202 +0,0 @@ -package simulation - -import ( - "context" - "sort" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - "golang.org/x/xerrors" - - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/types" -) - -// simualtionState holds the "state" of the simulation. This is split from the Simulation type so we -// can load it on-dempand if and when we need to actually _run_ the simualation. Loading the -// simulation state requires walking all active miners. -type simulationState struct { - *Simulation - - // The tiers represent the top 1%, top 10%, and everyone else. When sealing sectors, we seal - // a group of sectors for the top 1%, a group (half that size) for the top 10%, and one - // sector for everyone else. We determine these rates by looking at two power tables. - // TODO Ideally we'd "learn" this distribution from the network. But this is good enough for - // now. - minerDist struct { - top1, top10, rest actorIter - } - - // We track the window post periods per miner and assume that no new miners are ever added. - wpostPeriods map[int][]address.Address // (epoch % (epochs in a deadline)) -> miner - // We cache all miner infos for active miners and assume no new miners join. - minerInfos map[address.Address]*miner.MinerInfo - - // We record all pending window post messages, and the epoch up through which we've - // generated window post messages. - pendingWposts []*types.Message - nextWpostEpoch abi.ChainEpoch - - // We track the set of pending commits. On simulation load, and when a new pre-commit is - // added to the chain, we put the commit in this queue. advanceEpoch(currentEpoch) should be - // called on this queue at every epoch before using it. - commitQueue commitQueue -} - -func loadSimulationState(ctx context.Context, sim *Simulation) (*simulationState, error) { - state := &simulationState{Simulation: sim} - currentEpoch := sim.head.Height() - - // Lookup the current power table and the power table 2 weeks ago (for onboarding rate - // projections). - currentPowerTable, err := sim.loadClaims(ctx, currentEpoch) - if err != nil { - return nil, err - } - - var lookbackEpoch abi.ChainEpoch - //if epoch > onboardingProjectionLookback { - // lookbackEpoch = epoch - onboardingProjectionLookback - //} - // TODO: Fixme? I really want this to not suck with snapshots. - lookbackEpoch = 770139 // hard coded for now. - lookbackPowerTable, err := sim.loadClaims(ctx, lookbackEpoch) - if err != nil { - return nil, err - } - - type onboardingInfo struct { - addr address.Address - onboardingRate uint64 - } - - commitRand, err := sim.postChainCommitInfo(ctx, currentEpoch) - if err != nil { - return nil, err - } - - sealList := make([]onboardingInfo, 0, len(currentPowerTable)) - state.wpostPeriods = make(map[int][]address.Address, miner.WPoStChallengeWindow) - state.minerInfos = make(map[address.Address]*miner.MinerInfo, len(currentPowerTable)) - state.commitQueue.advanceEpoch(state.nextEpoch()) - for addr, claim := range currentPowerTable { - // Load the miner state. - _, minerState, err := state.getMinerState(ctx, addr) - if err != nil { - return nil, err - } - - info, err := minerState.Info() - if err != nil { - return nil, err - } - state.minerInfos[addr] = &info - - // Queue up PoSts - err = state.stepWindowPoStsMiner(ctx, addr, minerState, currentEpoch, commitRand) - if err != nil { - return nil, err - } - - // Qeueu up any pending prove commits. - err = state.loadProveCommitsMiner(ctx, addr, minerState) - if err != nil { - return nil, err - } - - // Record when we need to prove for this miner. - dinfo, err := minerState.DeadlineInfo(state.nextEpoch()) - if err != nil { - return nil, err - } - dinfo = dinfo.NextNotElapsed() - - ppOffset := int(dinfo.PeriodStart % miner.WPoStChallengeWindow) - state.wpostPeriods[ppOffset] = append(state.wpostPeriods[ppOffset], addr) - - sectorsAdded := sectorsFromClaim(info.SectorSize, claim) - if lookbackClaim, ok := lookbackPowerTable[addr]; !ok { - sectorsAdded -= sectorsFromClaim(info.SectorSize, lookbackClaim) - } - - // NOTE: power _could_ have been lost, but that's too much of a pain to care - // about. We _could_ look for faulty power by iterating through all - // deadlines, but I'd rather not. - if sectorsAdded > 0 { - sealList = append(sealList, onboardingInfo{addr, uint64(sectorsAdded)}) - } - } - if len(sealList) == 0 { - return nil, xerrors.Errorf("simulation has no miners") - } - - // We're already done loading for the _next_ epoch. - // Next time, we need to load for the next, next epoch. - // TODO: fix this insanity. - state.nextWpostEpoch = state.nextEpoch() + 1 - - // Now that we have a list of sealing miners, sort them into percentiles. - sort.Slice(sealList, func(i, j int) bool { - return sealList[i].onboardingRate < sealList[j].onboardingRate - }) - - for i, oi := range sealList { - var dist *actorIter - if i < len(sealList)/100 { - dist = &state.minerDist.top1 - } else if i < len(sealList)/10 { - dist = &state.minerDist.top10 - } else { - dist = &state.minerDist.rest - } - dist.add(oi.addr) - } - - state.minerDist.top1.shuffle() - state.minerDist.top10.shuffle() - state.minerDist.rest.shuffle() - - return state, nil -} - -// nextEpoch returns the next epoch (head+1). -func (ss *simulationState) nextEpoch() abi.ChainEpoch { - return ss.GetHead().Height() + 1 -} - -// getMinerInfo returns the miner's cached info. -// -// NOTE: we assume that miner infos won't change. We'll need to fix this if we start supporting arbitrary message. -func (ss *simulationState) getMinerInfo(ctx context.Context, addr address.Address) (*miner.MinerInfo, error) { - minerInfo, ok := ss.minerInfos[addr] - if !ok { - _, minerState, err := ss.getMinerState(ctx, addr) - if err != nil { - return nil, err - } - info, err := minerState.Info() - if err != nil { - return nil, err - } - minerInfo = &info - ss.minerInfos[addr] = minerInfo - } - return minerInfo, nil -} - -// getMinerState loads the miner actor & state. -func (ss *simulationState) getMinerState(ctx context.Context, addr address.Address) (*types.Actor, miner.State, error) { - st, err := ss.stateTree(ctx) - if err != nil { - return nil, nil, err - } - act, err := st.GetActor(addr) - if err != nil { - return nil, nil, err - } - state, err := miner.Load(ss.Chainstore.ActorStore(ctx), act) - if err != nil { - return nil, nil, err - } - return act, state, err -} diff --git a/cmd/lotus-sim/simulation/step.go b/cmd/lotus-sim/simulation/step.go index 1106e0d6e..902f2ad6c 100644 --- a/cmd/lotus-sim/simulation/step.go +++ b/cmd/lotus-sim/simulation/step.go @@ -2,83 +2,38 @@ package simulation import ( "context" - "errors" - "reflect" - "runtime" - "strings" "golang.org/x/xerrors" - "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/actors/builtin/account" - "github.com/filecoin-project/lotus/chain/state" - "github.com/filecoin-project/lotus/chain/stmgr" - "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/chain/vm" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/blockbuilder" ) -const ( - // The number of expected blocks in a tipset. We use this to determine how much gas a tipset - // has. - expectedBlocks = 5 - // TODO: This will produce invalid blocks but it will accurately model the amount of gas - // we're willing to use per-tipset. - // A more correct approach would be to produce 5 blocks. We can do that later. - targetGas = build.BlockGasTarget * expectedBlocks -) - -var baseFee = abi.NewTokenAmount(0) - // Step steps the simulation forward one step. This may move forward by more than one epoch. func (sim *Simulation) Step(ctx context.Context) (*types.TipSet, error) { - state, err := sim.simState(ctx) - if err != nil { - return nil, err - } - ts, err := state.step(ctx) - if err != nil { - return nil, xerrors.Errorf("failed to step simulation: %w", err) - } - return ts, nil -} - -// step steps the simulation state forward one step, producing and executing a new tipset. -func (ss *simulationState) step(ctx context.Context) (*types.TipSet, error) { - log.Infow("step", "epoch", ss.head.Height()+1) - messages, err := ss.popNextMessages(ctx) + log.Infow("step", "epoch", sim.head.Height()+1) + messages, err := sim.popNextMessages(ctx) if err != nil { return nil, xerrors.Errorf("failed to select messages for block: %w", err) } - head, err := ss.makeTipSet(ctx, messages) + head, err := sim.makeTipSet(ctx, messages) if err != nil { return nil, xerrors.Errorf("failed to make tipset: %w", err) } - if err := ss.SetHead(head); err != nil { + if err := sim.SetHead(head); err != nil { return nil, xerrors.Errorf("failed to update head: %w", err) } return head, nil } -var ErrOutOfGas = errors.New("out of gas") - -// packFunc takes a message and attempts to pack it into a block. -// -// - If the block is full, returns the error ErrOutOfGas. -// - If message execution fails, check if error is an ActorError to get the return code. -type packFunc func(*types.Message) (*types.MessageReceipt, error) - // popNextMessages generates/picks a set of messages to be included in the next block. // // - This function is destructive and should only be called once per epoch. // - This function does not store anything in the repo. // - This function handles all gas estimation. The returned messages should all fit in a single // block. -func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Message, error) { - parentTs := ss.head +func (sim *Simulation) popNextMessages(ctx context.Context) ([]*types.Message, error) { + parentTs := sim.head // First we make sure we don't have an upgrade at this epoch. If we do, we return no // messages so we can just create an empty block at that epoch. @@ -86,8 +41,8 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag // This isn't what the network does, but it makes things easier. Otherwise, we'd need to run // migrations before this epoch and I'd rather not deal with that. nextHeight := parentTs.Height() + 1 - prevVer := ss.StateManager.GetNtwkVersion(ctx, nextHeight-1) - nextVer := ss.StateManager.GetNtwkVersion(ctx, nextHeight) + prevVer := sim.StateManager.GetNtwkVersion(ctx, nextHeight-1) + nextVer := sim.StateManager.GetNtwkVersion(ctx, nextHeight) if nextVer != prevVer { log.Warnw("packing no messages for version upgrade block", "old", prevVer, @@ -97,170 +52,20 @@ func (ss *simulationState) popNextMessages(ctx context.Context) ([]*types.Messag return nil, nil } - // Next, we compute the state for the parent tipset. In practice, this will likely be - // cached. - parentState, _, err := ss.StateManager.TipSetState(ctx, parentTs) + bb, err := blockbuilder.NewBlockBuilder( + ctx, log.With("simulation", sim.name), + sim.StateManager, parentTs, + ) if err != nil { return nil, err } - // Then we construct a VM to execute messages for gas estimation. - // - // Most parts of this VM are "real" except: - // 1. We don't charge a fee. - // 2. The runtime has "fake" proof logic. - // 3. We don't actually save any of the results. - r := store.NewChainRand(ss.StateManager.ChainStore(), parentTs.Cids()) - vmopt := &vm.VMOpts{ - StateBase: parentState, - Epoch: nextHeight, - Rand: r, - Bstore: ss.StateManager.ChainStore().StateBlockstore(), - Syscalls: ss.StateManager.ChainStore().VMSys(), - CircSupplyCalc: ss.StateManager.GetVMCirculatingSupply, - NtwkVersion: ss.StateManager.GetNtwkVersion, - BaseFee: baseFee, // FREE! - LookbackState: stmgr.LookbackStateGetterForTipset(ss.StateManager, parentTs), - } - vmi, err := vm.NewVM(ctx, vmopt) - if err != nil { - return nil, err - } - - // Next we define a helper function for "pushing" messages. This is the function that will - // be passed to the "pack" functions. - // - // It. - // - // 1. Tries to execute the message on-top-of the already pushed message. - // 2. Is careful to revert messages on failure to avoid nasties like nonce-gaps. - // 3. Resolves IDs as necessary, fills in missing parts of the message, etc. - vmStore := vmi.ActorStore(ctx) - var gasTotal int64 - var messages []*types.Message - tryPushMsg := func(msg *types.Message) (*types.MessageReceipt, error) { - if gasTotal >= targetGas { - return nil, ErrOutOfGas - } - - // Copy the message before we start mutating it. - msgCpy := *msg - msg = &msgCpy - st := vmi.StateTree().(*state.StateTree) - - actor, err := st.GetActor(msg.From) - if err != nil { - return nil, err - } - msg.Nonce = actor.Nonce - if msg.From.Protocol() == address.ID { - state, err := account.Load(vmStore, actor) - if err != nil { - return nil, err - } - msg.From, err = state.PubkeyAddress() - if err != nil { - return nil, err - } - } - - // TODO: Our gas estimation is broken for payment channels due to horrible hacks in - // gasEstimateGasLimit. - if msg.Value == types.EmptyInt { - msg.Value = abi.NewTokenAmount(0) - } - msg.GasPremium = abi.NewTokenAmount(0) - msg.GasFeeCap = abi.NewTokenAmount(0) - msg.GasLimit = build.BlockGasLimit - - // We manually snapshot so we can revert nonce changes, etc. on failure. - st.Snapshot(ctx) - defer st.ClearSnapshot() - - ret, err := vmi.ApplyMessage(ctx, msg) - if err != nil { - _ = st.Revert() - return nil, err - } - if ret.ActorErr != nil { - _ = st.Revert() - return nil, ret.ActorErr - } - - // Sometimes there are bugs. Let's catch them. - if ret.GasUsed == 0 { - _ = st.Revert() - return nil, xerrors.Errorf("used no gas", - "msg", msg, - "ret", ret, - ) - } - - // TODO: consider applying overestimation? We're likely going to "over pack" here by - // ~25% because we're too accurate. - - // Did we go over? Yes, revert. - newTotal := gasTotal + ret.GasUsed - if newTotal > targetGas { - _ = st.Revert() - return nil, ErrOutOfGas - } - gasTotal = newTotal - - // Update the gas limit. - msg.GasLimit = ret.GasUsed - - messages = append(messages, msg) - return &ret.MessageReceipt, nil - } - - // Finally, we generate a set of messages to be included in - if err := ss.packMessages(ctx, tryPushMsg); err != nil { - return nil, err - } - - return messages, nil -} - -// functionName extracts the name of given function. -func functionName(fn interface{}) string { - name := runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name() - lastDot := strings.LastIndexByte(name, '.') - if lastDot >= 0 { - name = name[lastDot+1 : len(name)-3] - } - lastDash := strings.LastIndexByte(name, '-') - if lastDash > 0 { - name = name[:lastDash] - } - return name -} - -// packMessages packs messages with the given packFunc until the block is full (packFunc returns -// true). -// TODO: Make this more configurable for other simulations. -func (ss *simulationState) packMessages(ctx context.Context, cb packFunc) error { - type messageGenerator func(ctx context.Context, cb packFunc) error - - // We pack messages in-order: - // 1. Any window posts. We pack window posts as soon as the deadline opens to ensure we only - // miss them if/when we run out of chain bandwidth. - // 2. We then move funds to our "funding" account, if it's running low. - // 3. Prove commits. We do this eagerly to ensure they don't expire. - // 4. Finally, we fill the rest of the space with pre-commits. - messageGenerators := []messageGenerator{ - ss.packWindowPoSts, - ss.packFunding, - ss.packProveCommits, - ss.packPreCommits, - } - - for _, mgen := range messageGenerators { + for _, stage := range sim.stages { // We're intentionally ignoring the "full" signal so we can try to pack a few more // messages. - if err := mgen(ctx, cb); err != nil && !xerrors.Is(err, ErrOutOfGas) { - return xerrors.Errorf("when packing messages with %s: %w", functionName(mgen), err) + if err := stage.PackMessages(ctx, bb); err != nil && !blockbuilder.IsOutOfGas(err) { + return nil, xerrors.Errorf("when packing messages with %s: %w", stage.Name(), err) } } - return nil + return bb.Messages(), nil } diff --git a/cmd/lotus-sim/simulation/wdpost.go b/cmd/lotus-sim/simulation/wdpost.go deleted file mode 100644 index 7e6f2401e..000000000 --- a/cmd/lotus-sim/simulation/wdpost.go +++ /dev/null @@ -1,229 +0,0 @@ -package simulation - -import ( - "context" - "math" - "time" - - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/crypto" - - proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" - - "github.com/filecoin-project/lotus/chain/actors" - "github.com/filecoin-project/lotus/chain/actors/aerrors" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/types" -) - -// postChainCommitInfo returns th -func (sim *Simulation) postChainCommitInfo(ctx context.Context, epoch abi.ChainEpoch) (abi.Randomness, error) { - commitRand, err := sim.Chainstore.GetChainRandomness( - ctx, sim.head.Cids(), crypto.DomainSeparationTag_PoStChainCommit, epoch, nil, true) - return commitRand, err -} - -// packWindowPoSts packs window posts until either the block is full or all healty sectors -// have been proven. It does not recover sectors. -func (ss *simulationState) packWindowPoSts(ctx context.Context, cb packFunc) (_err error) { - // Push any new window posts into the queue. - if err := ss.queueWindowPoSts(ctx); err != nil { - return err - } - done := 0 - failed := 0 - defer func() { - if _err != nil { - return - } - - log.Debugw("packed window posts", - "epoch", ss.nextEpoch(), - "done", done, - "failed", failed, - "remaining", len(ss.pendingWposts), - ) - }() - // Then pack as many as we can. - for len(ss.pendingWposts) > 0 { - next := ss.pendingWposts[0] - if _, err := cb(next); err != nil { - if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() { - return err - } - log.Errorw("failed to submit windowed post", - "error", err, - "miner", next.To, - "epoch", ss.nextEpoch(), - ) - failed++ - } else { - done++ - } - - ss.pendingWposts = ss.pendingWposts[1:] - } - ss.pendingWposts = nil - return nil -} - -// stepWindowPoStsMiner enqueues all missing window posts for the current epoch for the given miner. -func (ss *simulationState) stepWindowPoStsMiner( - ctx context.Context, - addr address.Address, minerState miner.State, - commitEpoch abi.ChainEpoch, commitRand abi.Randomness, -) error { - - if active, err := minerState.DeadlineCronActive(); err != nil { - return err - } else if !active { - return nil - } - - minerInfo, err := ss.getMinerInfo(ctx, addr) - if err != nil { - return err - } - - di, err := minerState.DeadlineInfo(ss.nextEpoch()) - if err != nil { - return err - } - di = di.NextNotElapsed() - - dl, err := minerState.LoadDeadline(di.Index) - if err != nil { - return err - } - - provenBf, err := dl.PartitionsPoSted() - if err != nil { - return err - } - proven, err := provenBf.AllMap(math.MaxUint64) - if err != nil { - return err - } - - var ( - partitions []miner.PoStPartition - partitionGroups [][]miner.PoStPartition - ) - // Only prove partitions with live sectors. - err = dl.ForEachPartition(func(idx uint64, part miner.Partition) error { - if proven[idx] { - return nil - } - // TODO: set this to the actual limit from specs-actors. - // NOTE: We're mimicing the behavior of wdpost_run.go here. - if len(partitions) > 0 && idx%4 == 0 { - partitionGroups = append(partitionGroups, partitions) - partitions = nil - - } - live, err := part.LiveSectors() - if err != nil { - return err - } - liveCount, err := live.Count() - if err != nil { - return err - } - faulty, err := part.FaultySectors() - if err != nil { - return err - } - faultyCount, err := faulty.Count() - if err != nil { - return err - } - if liveCount-faultyCount > 0 { - partitions = append(partitions, miner.PoStPartition{Index: idx}) - } - return nil - }) - if err != nil { - return err - } - if len(partitions) > 0 { - partitionGroups = append(partitionGroups, partitions) - partitions = nil - } - - proof, err := mockWpostProof(minerInfo.WindowPoStProofType, addr) - if err != nil { - return err - } - for _, group := range partitionGroups { - params := miner.SubmitWindowedPoStParams{ - Deadline: di.Index, - Partitions: group, - Proofs: []proof5.PoStProof{{ - PoStProof: minerInfo.WindowPoStProofType, - ProofBytes: proof, - }}, - ChainCommitEpoch: commitEpoch, - ChainCommitRand: commitRand, - } - enc, aerr := actors.SerializeParams(¶ms) - if aerr != nil { - return xerrors.Errorf("could not serialize submit window post parameters: %w", aerr) - } - msg := &types.Message{ - To: addr, - From: minerInfo.Worker, - Method: miner.Methods.SubmitWindowedPoSt, - Params: enc, - Value: types.NewInt(0), - } - ss.pendingWposts = append(ss.pendingWposts, msg) - } - return nil -} - -// queueWindowPoSts enqueues missing window posts for all miners with deadlines opening between the -// last epoch in which this function was called and the current epoch (head+1). -func (ss *simulationState) queueWindowPoSts(ctx context.Context) error { - targetHeight := ss.nextEpoch() - - now := time.Now() - was := len(ss.pendingWposts) - count := 0 - defer func() { - log.Debugw("computed window posts", - "miners", count, - "count", len(ss.pendingWposts)-was, - "duration", time.Since(now), - ) - }() - - // Perform a bit of catch up. This lets us do things like skip blocks at upgrades then catch - // up to make the simualtion easier. - for ; ss.nextWpostEpoch <= targetHeight; ss.nextWpostEpoch++ { - if ss.nextWpostEpoch+miner.WPoStChallengeWindow < targetHeight { - log.Warnw("skipping old window post", "epoch", ss.nextWpostEpoch) - continue - } - commitEpoch := ss.nextWpostEpoch - 1 - commitRand, err := ss.postChainCommitInfo(ctx, commitEpoch) - if err != nil { - return err - } - - for _, addr := range ss.wpostPeriods[int(ss.nextWpostEpoch%miner.WPoStChallengeWindow)] { - _, minerState, err := ss.getMinerState(ctx, addr) - if err != nil { - return err - } - if err := ss.stepWindowPoStsMiner(ctx, addr, minerState, commitEpoch, commitRand); err != nil { - return err - } - count++ - } - - } - return nil -} From f6043a025036c1f457cfecd101eb1d20af6c3450 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sat, 12 Jun 2021 09:57:27 -0700 Subject: [PATCH 148/257] feat(lotus-sim): measure daily power growth --- cmd/lotus-sim/info.go | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 6a37f7258..cae8c9375 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -38,20 +38,34 @@ func getTotalPower(ctx context.Context, sm *stmgr.StateManager, ts *types.TipSet } func printInfo(ctx context.Context, sim *simulation.Simulation, out io.Writer) error { - powerNow, err := getTotalPower(ctx, sim.StateManager, sim.GetHead()) + head := sim.GetHead() + start := sim.GetStart() + + powerNow, err := getTotalPower(ctx, sim.StateManager, head) if err != nil { return err } - powerStart, err := getTotalPower(ctx, sim.StateManager, sim.GetStart()) + powerLookbackEpoch := head.Height() - builtin.EpochsInDay + if powerLookbackEpoch < start.Height() { + powerLookbackEpoch = start.Height() + } + lookbackTs, err := sim.Chainstore.GetTipsetByHeight(ctx, powerLookbackEpoch, head, false) if err != nil { return err } - powerGrowth := big.Sub(powerNow.RawBytePower, powerStart.RawBytePower) + powerLookback, err := getTotalPower(ctx, sim.StateManager, lookbackTs) + if err != nil { + return err + } + // growth rate in size/day + growthRate := big.Div( + big.Mul(big.Sub(powerNow.RawBytePower, powerLookback.RawBytePower), + big.NewInt(builtin.EpochsInDay)), + big.NewInt(int64(head.Height()-lookbackTs.Height())), + ) tw := tabwriter.NewWriter(out, 8, 8, 1, ' ', 0) - head := sim.GetHead() - start := sim.GetStart() headEpoch := head.Height() firstEpoch := start.Height() + 1 @@ -59,12 +73,6 @@ func printInfo(ctx context.Context, sim *simulation.Simulation, out io.Writer) e startTime := time.Unix(int64(start.MinTimestamp()), 0) duration := headTime.Sub(startTime) - // growth rate in size/day - growthRate := big.Div( - big.Mul(powerGrowth, big.NewInt(int64(24*time.Hour))), - big.NewInt(int64(duration)), - ) - fmt.Fprintf(tw, "Name:\t%s\n", sim.Name()) fmt.Fprintf(tw, "Head:\t%s\n", head) fmt.Fprintf(tw, "Start Epoch:\t%d\n", firstEpoch) @@ -74,8 +82,7 @@ func printInfo(ctx context.Context, sim *simulation.Simulation, out io.Writer) e fmt.Fprintf(tw, "End Date:\t%s\n", headTime) fmt.Fprintf(tw, "Duration:\t%.2f day(s)\n", duration.Hours()/24) fmt.Fprintf(tw, "Power:\t%s\n", types.SizeStr(powerNow.RawBytePower)) - fmt.Fprintf(tw, "Power Growth:\t%s\n", types.SizeStr(powerGrowth)) - fmt.Fprintf(tw, "Power Growth Rate:\t%s/day\n", types.SizeStr(growthRate)) + fmt.Fprintf(tw, "Daily Power Growth:\t%s/day\n", types.SizeStr(growthRate)) fmt.Fprintf(tw, "Network Version:\t%d\n", sim.GetNetworkVersion()) return tw.Flush() } From ec3f969e9a671ab17f27cc0b24d070019b7e69d0 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sat, 12 Jun 2021 10:08:36 -0700 Subject: [PATCH 149/257] feat(lotus-sim): allow walking back past the start --- cmd/lotus-sim/simulation/simulation.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index 0af1120c2..a8a0e7197 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -257,16 +257,16 @@ type AppliedMessage struct { // Walk walks the simulation's chain from the current head back to the first tipset. func (sim *Simulation) Walk( ctx context.Context, - maxLookback int64, + lookback int64, cb func(sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, messages []*AppliedMessage) error, ) error { store := sim.Chainstore.ActorStore(ctx) - minEpoch := abi.ChainEpoch(0) - if maxLookback != 0 { - minEpoch = sim.head.Height() - abi.ChainEpoch(maxLookback) + minEpoch := sim.start.Height() + if lookback != 0 { + minEpoch = sim.head.Height() - abi.ChainEpoch(lookback) } // Given tha loading messages and receipts can be a little bit slow, we do this in parallel. @@ -314,7 +314,7 @@ func (sim *Simulation) Walk( return err } i := 0 - for !ts.Equals(sim.start) && ctx.Err() == nil && ts.Height() > minEpoch { + for ctx.Err() == nil && ts.Height() > minEpoch { select { case workQs[i] <- &work{ts, stCid, recCid}: case <-ctx.Done(): From f9d2a231329e21584fef67d57fe528eb2a34bc46 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sat, 12 Jun 2021 10:16:50 -0700 Subject: [PATCH 150/257] fix(lotus-sim): correctly handle cancellation in walk 1. Select order is not guaranteed, always check if the context has been canceled explicitly. 2. Never close a work channel unless we're actually done. This can yield out-of-order results due to buffering. --- cmd/lotus-sim/simulation/simulation.go | 28 ++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index a8a0e7197..ef4278178 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -314,7 +314,11 @@ func (sim *Simulation) Walk( return err } i := 0 - for ctx.Err() == nil && ts.Height() > minEpoch { + for ts.Height() > minEpoch { + if err := ctx.Err(); err != nil { + return ctx.Err() + } + select { case workQs[i] <- &work{ts, stCid, recCid}: case <-ctx.Done(): @@ -340,7 +344,23 @@ func (sim *Simulation) Walk( workQ := workQs[i] resultQ := resultQs[i] grp.Go(func() error { - for job := range workQ { + for { + if err := ctx.Err(); err != nil { + return ctx.Err() + } + + var job *work + var ok bool + select { + case job, ok = <-workQ: + case <-ctx.Done(): + return ctx.Err() + } + + if !ok { + break + } + msgs, err := sim.Chainstore.MessagesForTipset(job.ts) if err != nil { return err @@ -381,6 +401,10 @@ func (sim *Simulation) Walk( grp.Go(func() error { qs := resultQs for len(qs) > 0 { + if err := ctx.Err(); err != nil { + return ctx.Err() + } + newQs := qs[:0] for _, q := range qs { select { From 0af7dcdedb7f3f52f447e7be4ebd993e65b4f717 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sat, 12 Jun 2021 10:18:17 -0700 Subject: [PATCH 151/257] fix(lotus-sim): rename power to capacity --- cmd/lotus-sim/info.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index cae8c9375..f21adb304 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -81,8 +81,8 @@ func printInfo(ctx context.Context, sim *simulation.Simulation, out io.Writer) e fmt.Fprintf(tw, "Start Date:\t%s\n", startTime) fmt.Fprintf(tw, "End Date:\t%s\n", headTime) fmt.Fprintf(tw, "Duration:\t%.2f day(s)\n", duration.Hours()/24) - fmt.Fprintf(tw, "Power:\t%s\n", types.SizeStr(powerNow.RawBytePower)) - fmt.Fprintf(tw, "Daily Power Growth:\t%s/day\n", types.SizeStr(growthRate)) + fmt.Fprintf(tw, "Capacity:\t%s\n", types.SizeStr(powerNow.RawBytePower)) + fmt.Fprintf(tw, "Daily Capacity Growth:\t%s/day\n", types.SizeStr(growthRate)) fmt.Fprintf(tw, "Network Version:\t%d\n", sim.GetNetworkVersion()) return tw.Flush() } From 63178ce982c9bcdf46f8cf282e90c1d4ecd7bbcd Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sat, 12 Jun 2021 11:44:38 -0700 Subject: [PATCH 152/257] feat(lotus-sim): daily capacity growth --- cmd/lotus-sim/info.go | 52 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index f21adb304..591e35c80 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -93,6 +93,7 @@ var infoSimCommand = &cli.Command{ Subcommands: []*cli.Command{ infoCommitGasSimCommand, infoWindowPostBandwidthSimCommand, + infoCapacityGrowthSimCommand, }, Action: func(cctx *cli.Context) error { node, err := open(cctx) @@ -158,6 +159,57 @@ var infoWindowPostBandwidthSimCommand = &cli.Command{ }, } +var infoCapacityGrowthSimCommand = &cli.Command{ + Name: "capacity-growth", + Description: "List daily capacity growth over the course of the simulation starting at the end.", + Action: func(cctx *cli.Context) error { + node, err := open(cctx) + if err != nil { + return err + } + defer node.Close() + + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) + if err != nil { + return err + } + + firstEpoch := sim.GetStart().Height() + ts := sim.GetHead() + lastPower, err := getTotalPower(cctx.Context, sim.StateManager, ts) + if err != nil { + return err + } + lastHeight := ts.Height() + + for ts.Height() > firstEpoch && cctx.Err() == nil { + ts, err = sim.Chainstore.LoadTipSet(ts.Parents()) + if err != nil { + return err + } + newEpoch := ts.Height() + if newEpoch != firstEpoch && newEpoch+builtin.EpochsInDay > lastHeight { + continue + } + + newPower, err := getTotalPower(cctx.Context, sim.StateManager, ts) + if err != nil { + return err + } + + growthRate := big.Div( + big.Mul(big.Sub(lastPower.RawBytePower, newPower.RawBytePower), + big.NewInt(builtin.EpochsInDay)), + big.NewInt(int64(lastHeight-newEpoch)), + ) + lastPower = newPower + lastHeight = newEpoch + fmt.Fprintf(cctx.App.Writer, "%s/day\n", types.SizeStr(growthRate)) + } + return cctx.Err() + }, +} + var infoCommitGasSimCommand = &cli.Command{ Name: "commit-gas", Description: "Output information about the gas for commits", From 8fffaa5c47518c5949ba9f43d522d3df11b0e649 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sat, 12 Jun 2021 11:51:35 -0700 Subject: [PATCH 153/257] fix(lotus-sim): average over 2 days There's too much noise per day. --- cmd/lotus-sim/info.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 591e35c80..82e531015 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -45,7 +45,7 @@ func printInfo(ctx context.Context, sim *simulation.Simulation, out io.Writer) e if err != nil { return err } - powerLookbackEpoch := head.Height() - builtin.EpochsInDay + powerLookbackEpoch := head.Height() - builtin.EpochsInDay*2 if powerLookbackEpoch < start.Height() { powerLookbackEpoch = start.Height() } From 95cf57744726b860533481cc2362a47a4bfbf462 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sat, 12 Jun 2021 12:02:36 -0700 Subject: [PATCH 154/257] fix(lotus-sim): really cancel walk immediately --- cmd/lotus-sim/simulation/simulation.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index ef4278178..0b8ab1e56 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -401,12 +401,11 @@ func (sim *Simulation) Walk( grp.Go(func() error { qs := resultQs for len(qs) > 0 { - if err := ctx.Err(); err != nil { - return ctx.Err() - } - newQs := qs[:0] for _, q := range qs { + if err := ctx.Err(); err != nil { + return ctx.Err() + } select { case r, ok := <-q: if !ok { From 5d7b7ce5c156beb0c562b88ea574994477611e7d Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sat, 12 Jun 2021 12:11:55 -0700 Subject: [PATCH 155/257] feat(lotus-sim): allow profile info --- cmd/lotus-sim/info.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 82e531015..ef20b7f26 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "os" + "syscall" "text/tabwriter" "time" @@ -229,6 +230,8 @@ var infoCommitGasSimCommand = &cli.Command{ } defer node.Close() + go profileOnSignal(cctx, syscall.SIGUSR2) + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) if err != nil { return err From 22267eb45dd5fcabb31ec52f49bc3892491b2a9c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 14 Jun 2021 13:05:28 -0700 Subject: [PATCH 156/257] feat(lotus-sim): split info command file --- chain/actors/builtin/miner/miner.go | 2 + cmd/lotus-sim/info.go | 234 +--------------------------- cmd/lotus-sim/info_capacity.go | 63 ++++++++ cmd/lotus-sim/info_commit.go | 144 +++++++++++++++++ cmd/lotus-sim/info_wdpost.go | 65 ++++++++ 5 files changed, 275 insertions(+), 233 deletions(-) create mode 100644 cmd/lotus-sim/info_capacity.go create mode 100644 cmd/lotus-sim/info_commit.go create mode 100644 cmd/lotus-sim/info_wdpost.go diff --git a/chain/actors/builtin/miner/miner.go b/chain/actors/builtin/miner/miner.go index bb7f80340..995dc78cb 100644 --- a/chain/actors/builtin/miner/miner.go +++ b/chain/actors/builtin/miner/miner.go @@ -22,6 +22,7 @@ import ( miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" miner3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/miner" + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" @@ -239,6 +240,7 @@ type DeclareFaultsRecoveredParams = miner0.DeclareFaultsRecoveredParams type SubmitWindowedPoStParams = miner0.SubmitWindowedPoStParams type ProveCommitSectorParams = miner0.ProveCommitSectorParams type DisputeWindowedPoStParams = miner3.DisputeWindowedPoStParams +type ProveCommitAggregateParams = miner5.ProveCommitAggregateParams func PreferredSealProofTypeFromWindowPoStType(nver network.Version, proof abi.RegisteredPoStProof) (abi.RegisteredSealProof, error) { // We added support for the new proofs in network version 7, and removed support for the old diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index ef20b7f26..4cd453440 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -1,29 +1,21 @@ package main import ( - "bytes" "context" "fmt" "io" - "os" - "syscall" "text/tabwriter" "time" - "github.com/ipfs/go-cid" - "github.com/streadway/quantile" "github.com/urfave/cli/v2" "github.com/filecoin-project/go-state-types/big" - "github.com/filecoin-project/go-state-types/exitcode" - "github.com/filecoin-project/specs-actors/v5/actors/builtin" - "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/actors/builtin/power" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation" - "github.com/filecoin-project/lotus/lib/stati" ) func getTotalPower(ctx context.Context, sm *stmgr.StateManager, ts *types.TipSet) (power.Claim, error) { @@ -110,227 +102,3 @@ var infoSimCommand = &cli.Command{ return printInfo(cctx.Context, sim, cctx.App.Writer) }, } - -var infoWindowPostBandwidthSimCommand = &cli.Command{ - Name: "post-bandwidth", - Description: "List average chain bandwidth used by window posts for each day of the simulation.", - Action: func(cctx *cli.Context) error { - node, err := open(cctx) - if err != nil { - return err - } - defer node.Close() - - sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) - if err != nil { - return err - } - - var postGas, totalGas int64 - printStats := func() { - fmt.Fprintf(cctx.App.Writer, "%.4f%%\n", float64(100*postGas)/float64(totalGas)) - } - idx := 0 - err = sim.Walk(cctx.Context, 0, func( - sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, - messages []*simulation.AppliedMessage, - ) error { - for _, m := range messages { - totalGas += m.GasUsed - if m.ExitCode != exitcode.Ok { - continue - } - if m.Method == builtin.MethodsMiner.SubmitWindowedPoSt { - postGas += m.GasUsed - } - } - idx++ - idx %= builtin.EpochsInDay - if idx == 0 { - printStats() - postGas = 0 - totalGas = 0 - } - return nil - }) - if idx > 0 { - printStats() - } - return err - }, -} - -var infoCapacityGrowthSimCommand = &cli.Command{ - Name: "capacity-growth", - Description: "List daily capacity growth over the course of the simulation starting at the end.", - Action: func(cctx *cli.Context) error { - node, err := open(cctx) - if err != nil { - return err - } - defer node.Close() - - sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) - if err != nil { - return err - } - - firstEpoch := sim.GetStart().Height() - ts := sim.GetHead() - lastPower, err := getTotalPower(cctx.Context, sim.StateManager, ts) - if err != nil { - return err - } - lastHeight := ts.Height() - - for ts.Height() > firstEpoch && cctx.Err() == nil { - ts, err = sim.Chainstore.LoadTipSet(ts.Parents()) - if err != nil { - return err - } - newEpoch := ts.Height() - if newEpoch != firstEpoch && newEpoch+builtin.EpochsInDay > lastHeight { - continue - } - - newPower, err := getTotalPower(cctx.Context, sim.StateManager, ts) - if err != nil { - return err - } - - growthRate := big.Div( - big.Mul(big.Sub(lastPower.RawBytePower, newPower.RawBytePower), - big.NewInt(builtin.EpochsInDay)), - big.NewInt(int64(lastHeight-newEpoch)), - ) - lastPower = newPower - lastHeight = newEpoch - fmt.Fprintf(cctx.App.Writer, "%s/day\n", types.SizeStr(growthRate)) - } - return cctx.Err() - }, -} - -var infoCommitGasSimCommand = &cli.Command{ - Name: "commit-gas", - Description: "Output information about the gas for commits", - Flags: []cli.Flag{ - &cli.Int64Flag{ - Name: "lookback", - Value: 0, - }, - }, - Action: func(cctx *cli.Context) error { - log := func(f string, i ...interface{}) { - fmt.Fprintf(os.Stderr, f, i...) - } - node, err := open(cctx) - if err != nil { - return err - } - defer node.Close() - - go profileOnSignal(cctx, syscall.SIGUSR2) - - sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) - if err != nil { - return err - } - - var gasAgg, proofsAgg uint64 - var gasAggMax, proofsAggMax uint64 - var gasSingle, proofsSingle uint64 - - qpoints := []struct{ q, tol float64 }{ - {0.01, 0.0005}, - {0.05, 0.001}, - {0.20, 0.01}, - {0.25, 0.01}, - {0.30, 0.01}, - {0.40, 0.01}, - {0.45, 0.01}, - {0.50, 0.01}, - {0.60, 0.01}, - {0.80, 0.01}, - {0.95, 0.001}, - {0.99, 0.0005}, - } - estims := make([]quantile.Estimate, len(qpoints)) - for i, p := range qpoints { - estims[i] = quantile.Known(p.q, p.tol) - } - qua := quantile.New(estims...) - hist, err := stati.NewHistogram([]float64{ - 1, 3, 5, 7, 15, 30, 50, 100, 200, 400, 600, 700, 819}) - if err != nil { - return err - } - - err = sim.Walk(cctx.Context, cctx.Int64("lookback"), func( - sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, - messages []*simulation.AppliedMessage, - ) error { - for _, m := range messages { - if m.ExitCode != exitcode.Ok { - continue - } - if m.Method == builtin.MethodsMiner.ProveCommitAggregate { - param := miner.ProveCommitAggregateParams{} - err := param.UnmarshalCBOR(bytes.NewReader(m.Params)) - if err != nil { - log("failed to decode params: %+v", err) - return nil - } - c, err := param.SectorNumbers.Count() - if err != nil { - log("failed to count sectors") - return nil - } - gasAgg += uint64(m.GasUsed) - proofsAgg += c - if c == 819 { - gasAggMax += uint64(m.GasUsed) - proofsAggMax += c - } - for i := uint64(0); i < c; i++ { - qua.Add(float64(c)) - } - hist.Observe(float64(c)) - } - - if m.Method == builtin.MethodsMiner.ProveCommitSector { - gasSingle += uint64(m.GasUsed) - proofsSingle++ - qua.Add(1) - hist.Observe(1) - } - } - - return nil - }) - if err != nil { - return err - } - idealGassUsed := float64(gasAggMax) / float64(proofsAggMax) * float64(proofsAgg+proofsSingle) - - fmt.Printf("Gas usage efficiency in comparison to all 819: %f%%\n", 100*idealGassUsed/float64(gasAgg+gasSingle)) - - fmt.Printf("Proofs in singles: %d\n", proofsSingle) - fmt.Printf("Proofs in Aggs: %d\n", proofsAgg) - fmt.Printf("Proofs in Aggs(819): %d\n", proofsAggMax) - - fmt.Println() - fmt.Println("Quantiles of proofs in given aggregate size:") - for _, p := range qpoints { - fmt.Printf("%.0f%%\t%.0f\n", p.q*100, qua.Get(p.q)) - } - fmt.Println() - fmt.Println("Histogram of messages:") - fmt.Printf("Total\t%d\n", hist.Total()) - for i, b := range hist.Buckets[1:] { - fmt.Printf("%.0f\t%d\n", b, hist.Get(i)) - } - - return nil - }, -} diff --git a/cmd/lotus-sim/info_capacity.go b/cmd/lotus-sim/info_capacity.go new file mode 100644 index 000000000..14ee36f08 --- /dev/null +++ b/cmd/lotus-sim/info_capacity.go @@ -0,0 +1,63 @@ +package main + +import ( + "fmt" + + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/go-state-types/big" + + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/types" +) + +var infoCapacityGrowthSimCommand = &cli.Command{ + Name: "capacity-growth", + Description: "List daily capacity growth over the course of the simulation starting at the end.", + Action: func(cctx *cli.Context) error { + node, err := open(cctx) + if err != nil { + return err + } + defer node.Close() + + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) + if err != nil { + return err + } + + firstEpoch := sim.GetStart().Height() + ts := sim.GetHead() + lastPower, err := getTotalPower(cctx.Context, sim.StateManager, ts) + if err != nil { + return err + } + lastHeight := ts.Height() + + for ts.Height() > firstEpoch && cctx.Err() == nil { + ts, err = sim.Chainstore.LoadTipSet(ts.Parents()) + if err != nil { + return err + } + newEpoch := ts.Height() + if newEpoch != firstEpoch && newEpoch+builtin.EpochsInDay > lastHeight { + continue + } + + newPower, err := getTotalPower(cctx.Context, sim.StateManager, ts) + if err != nil { + return err + } + + growthRate := big.Div( + big.Mul(big.Sub(lastPower.RawBytePower, newPower.RawBytePower), + big.NewInt(builtin.EpochsInDay)), + big.NewInt(int64(lastHeight-newEpoch)), + ) + lastPower = newPower + lastHeight = newEpoch + fmt.Fprintf(cctx.App.Writer, "%s/day\n", types.SizeStr(growthRate)) + } + return cctx.Err() + }, +} diff --git a/cmd/lotus-sim/info_commit.go b/cmd/lotus-sim/info_commit.go new file mode 100644 index 000000000..f6b08ea05 --- /dev/null +++ b/cmd/lotus-sim/info_commit.go @@ -0,0 +1,144 @@ +package main + +import ( + "bytes" + "fmt" + "os" + + "github.com/ipfs/go-cid" + "github.com/streadway/quantile" + "github.com/urfave/cli/v2" + "syscall" + + "github.com/filecoin-project/go-state-types/exitcode" + + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation" + "github.com/filecoin-project/lotus/lib/stati" +) + +var infoCommitGasSimCommand = &cli.Command{ + Name: "commit-gas", + Description: "Output information about the gas for commits", + Flags: []cli.Flag{ + &cli.Int64Flag{ + Name: "lookback", + Value: 0, + }, + }, + Action: func(cctx *cli.Context) error { + log := func(f string, i ...interface{}) { + fmt.Fprintf(os.Stderr, f, i...) + } + node, err := open(cctx) + if err != nil { + return err + } + defer node.Close() + + go profileOnSignal(cctx, syscall.SIGUSR2) + + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) + if err != nil { + return err + } + + var gasAgg, proofsAgg uint64 + var gasAggMax, proofsAggMax uint64 + var gasSingle, proofsSingle uint64 + + qpoints := []struct{ q, tol float64 }{ + {0.01, 0.0005}, + {0.05, 0.001}, + {0.20, 0.01}, + {0.25, 0.01}, + {0.30, 0.01}, + {0.40, 0.01}, + {0.45, 0.01}, + {0.50, 0.01}, + {0.60, 0.01}, + {0.80, 0.01}, + {0.95, 0.001}, + {0.99, 0.0005}, + } + estims := make([]quantile.Estimate, len(qpoints)) + for i, p := range qpoints { + estims[i] = quantile.Known(p.q, p.tol) + } + qua := quantile.New(estims...) + hist, err := stati.NewHistogram([]float64{ + 1, 3, 5, 7, 15, 30, 50, 100, 200, 400, 600, 700, 819}) + if err != nil { + return err + } + + err = sim.Walk(cctx.Context, cctx.Int64("lookback"), func( + sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, + messages []*simulation.AppliedMessage, + ) error { + for _, m := range messages { + if m.ExitCode != exitcode.Ok { + continue + } + if m.Method == miner.Methods.ProveCommitAggregate { + param := miner.ProveCommitAggregateParams{} + err := param.UnmarshalCBOR(bytes.NewReader(m.Params)) + if err != nil { + log("failed to decode params: %+v", err) + return nil + } + c, err := param.SectorNumbers.Count() + if err != nil { + log("failed to count sectors") + return nil + } + gasAgg += uint64(m.GasUsed) + proofsAgg += c + if c == 819 { + gasAggMax += uint64(m.GasUsed) + proofsAggMax += c + } + for i := uint64(0); i < c; i++ { + qua.Add(float64(c)) + } + hist.Observe(float64(c)) + } + + if m.Method == miner.Methods.ProveCommitSector { + gasSingle += uint64(m.GasUsed) + proofsSingle++ + qua.Add(1) + hist.Observe(1) + } + } + + return nil + }) + if err != nil { + return err + } + idealGassUsed := float64(gasAggMax) / float64(proofsAggMax) * float64(proofsAgg+proofsSingle) + + fmt.Printf("Gas usage efficiency in comparison to all 819: %f%%\n", 100*idealGassUsed/float64(gasAgg+gasSingle)) + + fmt.Printf("Proofs in singles: %d\n", proofsSingle) + fmt.Printf("Proofs in Aggs: %d\n", proofsAgg) + fmt.Printf("Proofs in Aggs(819): %d\n", proofsAggMax) + + fmt.Println() + fmt.Println("Quantiles of proofs in given aggregate size:") + for _, p := range qpoints { + fmt.Printf("%.0f%%\t%.0f\n", p.q*100, qua.Get(p.q)) + } + fmt.Println() + fmt.Println("Histogram of messages:") + fmt.Printf("Total\t%d\n", hist.Total()) + for i, b := range hist.Buckets[1:] { + fmt.Printf("%.0f\t%d\n", b, hist.Get(i)) + } + + return nil + }, +} diff --git a/cmd/lotus-sim/info_wdpost.go b/cmd/lotus-sim/info_wdpost.go new file mode 100644 index 000000000..d52cd5a8c --- /dev/null +++ b/cmd/lotus-sim/info_wdpost.go @@ -0,0 +1,65 @@ +package main + +import ( + "fmt" + + "github.com/ipfs/go-cid" + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/go-state-types/exitcode" + + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation" +) + +var infoWindowPostBandwidthSimCommand = &cli.Command{ + Name: "post-bandwidth", + Description: "List average chain bandwidth used by window posts for each day of the simulation.", + Action: func(cctx *cli.Context) error { + node, err := open(cctx) + if err != nil { + return err + } + defer node.Close() + + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) + if err != nil { + return err + } + + var postGas, totalGas int64 + printStats := func() { + fmt.Fprintf(cctx.App.Writer, "%.4f%%\n", float64(100*postGas)/float64(totalGas)) + } + idx := 0 + err = sim.Walk(cctx.Context, 0, func( + sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, + messages []*simulation.AppliedMessage, + ) error { + for _, m := range messages { + totalGas += m.GasUsed + if m.ExitCode != exitcode.Ok { + continue + } + if m.Method == miner.Methods.SubmitWindowedPoSt { + postGas += m.GasUsed + } + } + idx++ + idx %= builtin.EpochsInDay + if idx == 0 { + printStats() + postGas = 0 + totalGas = 0 + } + return nil + }) + if idx > 0 { + printStats() + } + return err + }, +} From 73ae1924bc4236438ba18d7a794a40cf9542b939 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 14 Jun 2021 17:38:55 -0700 Subject: [PATCH 157/257] feat(lotus-sim): state size command --- cmd/lotus-sim/info.go | 1 + cmd/lotus-sim/info_state.go | 139 ++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 cmd/lotus-sim/info_state.go diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 4cd453440..8288bb99f 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -87,6 +87,7 @@ var infoSimCommand = &cli.Command{ infoCommitGasSimCommand, infoWindowPostBandwidthSimCommand, infoCapacityGrowthSimCommand, + infoStateGrowthSimCommand, }, Action: func(cctx *cli.Context) error { node, err := open(cctx) diff --git a/cmd/lotus-sim/info_state.go b/cmd/lotus-sim/info_state.go new file mode 100644 index 000000000..c1710ba01 --- /dev/null +++ b/cmd/lotus-sim/info_state.go @@ -0,0 +1,139 @@ +package main + +import ( + "bytes" + "context" + "fmt" + "math" + "runtime" + "sync" + "sync/atomic" + + "github.com/ipfs/go-cid" + "github.com/urfave/cli/v2" + cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/types" +) + +var infoStateGrowthSimCommand = &cli.Command{ + Name: "state-size", + Description: "List daily state size over the course of the simulation starting at the end.", + Action: func(cctx *cli.Context) error { + node, err := open(cctx) + if err != nil { + return err + } + defer node.Close() + + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) + if err != nil { + return err + } + + // NOTE: This code is entirely read-bound. + store := node.Chainstore.StateBlockstore() + stateSize := func(ctx context.Context, c cid.Cid) (uint64, error) { + seen := cid.NewSet() + sema := make(chan struct{}, 40) + var lock sync.Mutex + var recSize func(cid.Cid) (uint64, error) + recSize = func(c cid.Cid) (uint64, error) { + // Not a part of the chain state. + if err := ctx.Err(); err != nil { + return 0, err + } + + lock.Lock() + visit := seen.Visit(c) + lock.Unlock() + // Already seen? + if !visit { + return 0, nil + } + + var links []cid.Cid + var totalSize uint64 + if err := store.View(c, func(data []byte) error { + totalSize += uint64(len(data)) + cbg.ScanForLinks(bytes.NewReader(data), func(c cid.Cid) { + if c.Prefix().Codec != cid.DagCBOR { + return + } + + links = append(links, c) + }) + return nil + }); err != nil { + return 0, err + } + + var wg sync.WaitGroup + errCh := make(chan error, 1) + cb := func(c cid.Cid) { + size, err := recSize(c) + if err != nil { + select { + case errCh <- err: + default: + } + return + } + atomic.AddUint64(&totalSize, size) + } + asyncCb := func(c cid.Cid) { + wg.Add(1) + go func() { + defer wg.Done() + defer func() { <-sema }() + cb(c) + }() + } + for _, link := range links { + select { + case sema <- struct{}{}: + asyncCb(link) + default: + cb(link) + } + + } + wg.Wait() + + select { + case err := <-errCh: + return 0, err + default: + } + + return totalSize, nil + } + return recSize(c) + } + + firstEpoch := sim.GetStart().Height() + ts := sim.GetHead() + lastHeight := abi.ChainEpoch(math.MaxInt64) + for ts.Height() > firstEpoch && cctx.Err() == nil { + if ts.Height()+builtin.EpochsInDay <= lastHeight { + lastHeight = ts.Height() + + parentStateSize, err := stateSize(cctx.Context, ts.ParentState()) + if err != nil { + return err + } + + fmt.Fprintf(cctx.App.Writer, "%d: %s\n", ts.Height(), types.SizeStr(types.NewInt(parentStateSize))) + } + + ts, err = sim.Chainstore.LoadTipSet(ts.Parents()) + if err != nil { + return err + } + } + return cctx.Err() + }, +} From 885062f7123be7357a9bb2d4994618b9794bd7d9 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 15 Jun 2021 18:16:46 -0700 Subject: [PATCH 158/257] fix(lotus-sim): fix info state imports --- cmd/lotus-sim/info_state.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/lotus-sim/info_state.go b/cmd/lotus-sim/info_state.go index c1710ba01..4dbc65848 100644 --- a/cmd/lotus-sim/info_state.go +++ b/cmd/lotus-sim/info_state.go @@ -5,7 +5,6 @@ import ( "context" "fmt" "math" - "runtime" "sync" "sync/atomic" From af33d69357322c5af9e29bbfb89c0ddf78cb5a69 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 15 Jun 2021 18:22:24 -0700 Subject: [PATCH 159/257] fix(lotus-sim): don't close node on list --- cmd/lotus-sim/list.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/lotus-sim/list.go b/cmd/lotus-sim/list.go index 69809b188..8ded8a133 100644 --- a/cmd/lotus-sim/list.go +++ b/cmd/lotus-sim/list.go @@ -28,7 +28,6 @@ var listSimCommand = &cli.Command{ } head := sim.GetHead() fmt.Fprintf(tw, "%s\t%s\t%s\n", name, head.Height(), head.Key()) - sim.Close() } return tw.Flush() }, From e41f0842b0488016607eadb2b1f3b07751005531 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 15 Jun 2021 23:54:01 -0700 Subject: [PATCH 160/257] fix(lotus-sim): load prove-commits (regression from refactor) --- cmd/lotus-sim/simulation/stages/provecommit_stage.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/lotus-sim/simulation/stages/provecommit_stage.go b/cmd/lotus-sim/simulation/stages/provecommit_stage.go index c2ffb8416..cf5f1afbf 100644 --- a/cmd/lotus-sim/simulation/stages/provecommit_stage.go +++ b/cmd/lotus-sim/simulation/stages/provecommit_stage.go @@ -57,6 +57,9 @@ func (stage *ProveCommitStage) EnqueueProveCommit( // block or runs out. func (stage *ProveCommitStage) PackMessages(ctx context.Context, bb *blockbuilder.BlockBuilder) (_err error) { if !stage.initialized { + if err := stage.load(ctx, bb); err != nil { + return err + } } // Roll the commitQueue forward. stage.commitQueue.advanceEpoch(bb.Height()) From ec5fab09a1043f9f6fbc0ade031d27732e0030a4 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 16 Jun 2021 00:13:15 -0700 Subject: [PATCH 161/257] feat(lotus-sim): log failing proofs --- cmd/lotus-sim/simulation/mock/mock.go | 31 ++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/cmd/lotus-sim/simulation/mock/mock.go b/cmd/lotus-sim/simulation/mock/mock.go index e6651aca0..38648f758 100644 --- a/cmd/lotus-sim/simulation/mock/mock.go +++ b/cmd/lotus-sim/simulation/mock/mock.go @@ -9,6 +9,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/ipfs/go-cid" + logging "github.com/ipfs/go-log/v2" miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" @@ -26,6 +27,8 @@ const ( mockPoStProofPrefix = "valid post proof:" ) +var log = logging.Logger("simulation-mock") + // mockVerifier is a simple mock for verifying "fake" proofs. type mockVerifier struct{} @@ -40,7 +43,11 @@ func (mockVerifier) VerifySeal(proof proof5.SealVerifyInfo) (bool, error) { if err != nil { return false, err } - return bytes.Equal(proof.Proof, mockProof), nil + if bytes.Equal(proof.Proof, mockProof) { + return true, nil + } + log.Debugw("invalid seal proof", "expected", mockProof, "actual", proof.Proof, "miner", addr) + return false, nil } func (mockVerifier) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProofAndInfos) (bool, error) { @@ -52,7 +59,16 @@ func (mockVerifier) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyPro if err != nil { return false, err } - return bytes.Equal(aggregate.Proof, mockProof), nil + if bytes.Equal(aggregate.Proof, mockProof) { + return true, nil + } + log.Debugw("invalid aggregate seal proof", + "expected", mockProof, + "actual", aggregate.Proof, + "count", len(aggregate.Infos), + "miner", addr, + ) + return false, nil } func (mockVerifier) VerifyWinningPoSt(ctx context.Context, info proof5.WinningPoStVerifyInfo) (bool, error) { panic("should not be called") @@ -70,7 +86,16 @@ func (mockVerifier) VerifyWindowPoSt(ctx context.Context, info proof5.WindowPoSt if err != nil { return false, err } - return bytes.Equal(proof.ProofBytes, mockProof), nil + if bytes.Equal(proof.ProofBytes, mockProof) { + return true, nil + } + + log.Debugw("invalid window post proof", + "expected", mockProof, + "actual", info.Proofs[0], + "miner", addr, + ) + return false, nil } func (mockVerifier) GenerateWinningPoStSectorChallenge(context.Context, abi.RegisteredPoStProof, abi.ActorID, abi.PoStRandomness, uint64) ([]uint64, error) { From 28e6fa592385be07762212b90cf5d34528841a17 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 16 Jun 2021 00:13:34 -0700 Subject: [PATCH 162/257] fix(lotus-sim): remove duplicate error handling --- cmd/lotus-sim/simulation/stages/provecommit_stage.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cmd/lotus-sim/simulation/stages/provecommit_stage.go b/cmd/lotus-sim/simulation/stages/provecommit_stage.go index cf5f1afbf..2693fa9ed 100644 --- a/cmd/lotus-sim/simulation/stages/provecommit_stage.go +++ b/cmd/lotus-sim/simulation/stages/provecommit_stage.go @@ -218,11 +218,6 @@ func (stage *ProveCommitStage) packProveCommitsMiner( // Then try again. continue } - log.Errorw("failed to prove commit missing sector(s)", - "error", err, - "sectors", batch, - ) - res.failed += len(batch) } else { log.Errorw("failed to prove commit sector(s)", "error", err, From ce29a0ac178fbcf98fd95fb92a0e2e60527f018e Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 15 Jun 2021 23:58:17 -0700 Subject: [PATCH 163/257] fix(lotus-sim): initialize commit queue --- cmd/lotus-sim/simulation/stages/provecommit_stage.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/lotus-sim/simulation/stages/provecommit_stage.go b/cmd/lotus-sim/simulation/stages/provecommit_stage.go index 2693fa9ed..28e3fbfd2 100644 --- a/cmd/lotus-sim/simulation/stages/provecommit_stage.go +++ b/cmd/lotus-sim/simulation/stages/provecommit_stage.go @@ -348,6 +348,8 @@ func (stage *ProveCommitStage) filterProveCommits( } func (stage *ProveCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBuilder) error { + stage.commitQueue.advanceEpoch(bb.Height()) + powerState, err := loadPower(bb.ActorStore(), bb.ParentStateTree()) if err != nil { return err From f0d0b40bd384dc0bba98a701ac0815f94452be76 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 16 Jun 2021 00:19:35 -0700 Subject: [PATCH 164/257] fix(lotus-sim): debug log mock --- cmd/lotus-sim/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/lotus-sim/main.go b/cmd/lotus-sim/main.go index c785f4045..e6cd5d993 100644 --- a/cmd/lotus-sim/main.go +++ b/cmd/lotus-sim/main.go @@ -27,6 +27,7 @@ var root []*cli.Command = []*cli.Command{ func main() { if _, set := os.LookupEnv("GOLOG_LOG_LEVEL"); !set { _ = logging.SetLogLevel("simulation", "DEBUG") + _ = logging.SetLogLevel("simulation-mock", "DEBUG") } app := &cli.App{ Name: "lotus-sim", From a26cd5a809a439f7c58eab512d19e8fe8cfec461 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 16 Jun 2021 00:30:42 -0700 Subject: [PATCH 165/257] fix(lotus-sim): mark provecommit stage initialized --- cmd/lotus-sim/simulation/stages/provecommit_stage.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cmd/lotus-sim/simulation/stages/provecommit_stage.go b/cmd/lotus-sim/simulation/stages/provecommit_stage.go index 28e3fbfd2..6cbca7de9 100644 --- a/cmd/lotus-sim/simulation/stages/provecommit_stage.go +++ b/cmd/lotus-sim/simulation/stages/provecommit_stage.go @@ -348,14 +348,14 @@ func (stage *ProveCommitStage) filterProveCommits( } func (stage *ProveCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBuilder) error { - stage.commitQueue.advanceEpoch(bb.Height()) - + stage.initialized = false // in case something failes while we're doing this. + stage.commitQueue = commitQueue{offset: bb.Height()} powerState, err := loadPower(bb.ActorStore(), bb.ParentStateTree()) if err != nil { return err } - return powerState.ForEachClaim(func(minerAddr address.Address, claim power.Claim) error { + err = powerState.ForEachClaim(func(minerAddr address.Address, claim power.Claim) error { // TODO: If we want to finish pre-commits for "new" miners, we'll need to change // this. if claim.RawBytePower.IsZero() { @@ -363,4 +363,10 @@ func (stage *ProveCommitStage) load(ctx context.Context, bb *blockbuilder.BlockB } return stage.loadMiner(ctx, bb, minerAddr) }) + if err != nil { + return err + } + + stage.initialized = true + return nil } From bc2698a988eefdcb3cecd1553d737aef5c7234ad Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 16 Jun 2021 12:20:17 -0700 Subject: [PATCH 166/257] fix(lotus-sim): simulate using realistic gas numbers Previously, we assumed the network was "optimal". Now, we're using real numbers. --- .../simulation/blockbuilder/blockbuilder.go | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go b/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go index 4406f8a4f..e4dc79b24 100644 --- a/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go +++ b/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go @@ -2,6 +2,7 @@ package blockbuilder import ( "context" + "math" "go.uber.org/zap" "golang.org/x/xerrors" @@ -23,9 +24,12 @@ import ( ) const ( + // 0.25 is the default, but the number below is from the network. + gasOverestimation = 1.0 / 0.808 // The number of expected blocks in a tipset. We use this to determine how much gas a tipset // has. - expectedBlocks = 5 + // 5 per tipset, but we effectively get 4 blocks worth of messages. + expectedBlocks = 4 // TODO: This will produce invalid blocks but it will accurately model the amount of gas // we're willing to use per-tipset. // A more correct approach would be to produce 5 blocks. We can do that later. @@ -143,7 +147,7 @@ func (bb *BlockBuilder) PushMessage(msg *types.Message) (*types.MessageReceipt, } msg.GasPremium = abi.NewTokenAmount(0) msg.GasFeeCap = abi.NewTokenAmount(0) - msg.GasLimit = build.BlockGasLimit + msg.GasLimit = build.BlockGasTarget // We manually snapshot so we can revert nonce changes, etc. on failure. st.Snapshot(bb.ctx) @@ -162,26 +166,20 @@ func (bb *BlockBuilder) PushMessage(msg *types.Message) (*types.MessageReceipt, // Sometimes there are bugs. Let's catch them. if ret.GasUsed == 0 { _ = st.Revert() - return nil, xerrors.Errorf("used no gas", - "msg", msg, - "ret", ret, - ) + return nil, xerrors.Errorf("used no gas %v -> %v", msg, ret) } - // TODO: consider applying overestimation? We're likely going to "over pack" here by - // ~25% because we're too accurate. + // Update the gas limit taking overestimation into account. + msg.GasLimit = int64(math.Ceil(float64(ret.GasUsed) * gasOverestimation)) // Did we go over? Yes, revert. - newTotal := bb.gasTotal + ret.GasUsed + newTotal := bb.gasTotal + msg.GasLimit if newTotal > targetGas { _ = st.Revert() - return nil, &ErrOutOfGas{Available: targetGas - bb.gasTotal, Required: ret.GasUsed} + return nil, &ErrOutOfGas{Available: targetGas - bb.gasTotal, Required: msg.GasLimit} } bb.gasTotal = newTotal - // Update the gas limit. - msg.GasLimit = ret.GasUsed - bb.messages = append(bb.messages, msg) return &ret.MessageReceipt, nil } From 2aedd82c72278ebff6cbc0ff3a32284e26bdd4c5 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 16 Jun 2021 17:02:18 -0700 Subject: [PATCH 167/257] fix(lotus-sim): correct window post batch sizes --- cmd/lotus-sim/simulation/stages/windowpost_stage.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-sim/simulation/stages/windowpost_stage.go b/cmd/lotus-sim/simulation/stages/windowpost_stage.go index e6583012d..e5bbf8145 100644 --- a/cmd/lotus-sim/simulation/stages/windowpost_stage.go +++ b/cmd/lotus-sim/simulation/stages/windowpost_stage.go @@ -16,6 +16,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/aerrors" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/blockbuilder" "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/mock" @@ -122,6 +123,11 @@ func (stage *WindowPoStStage) queueMiner( return err } + poStBatchSize, err := policy.GetMaxPoStPartitions(bb.NetworkVersion(), minerInfo.WindowPoStProofType) + if err != nil { + return err + } + var ( partitions []miner.PoStPartition partitionGroups [][]miner.PoStPartition @@ -131,9 +137,8 @@ func (stage *WindowPoStStage) queueMiner( if proven[idx] { return nil } - // TODO: set this to the actual limit from specs-actors. // NOTE: We're mimicing the behavior of wdpost_run.go here. - if len(partitions) > 0 && idx%4 == 0 { + if len(partitions) > 0 && idx%uint64(poStBatchSize) == 0 { partitionGroups = append(partitionGroups, partitions) partitions = nil From eb2b706156ff744d24b92ac0fae8ff0b5771b9dd Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 18 Jun 2021 11:17:35 -0700 Subject: [PATCH 168/257] chore: fix lint errors in simulation --- chain/vm/vm.go | 2 +- cmd/lotus-sim/copy.go | 8 +++- cmd/lotus-sim/create.go | 8 +++- cmd/lotus-sim/delete.go | 8 +++- cmd/lotus-sim/info.go | 8 +++- cmd/lotus-sim/info_capacity.go | 8 +++- cmd/lotus-sim/info_commit.go | 16 +++++--- cmd/lotus-sim/info_state.go | 11 ++++-- cmd/lotus-sim/info_wdpost.go | 8 +++- cmd/lotus-sim/list.go | 8 +++- cmd/lotus-sim/rename.go | 9 ++++- cmd/lotus-sim/run.go | 8 +++- .../simulation/blockbuilder/blockbuilder.go | 5 ++- cmd/lotus-sim/simulation/node.go | 10 +++-- cmd/lotus-sim/simulation/simulation.go | 3 -- .../simulation/stages/commit_queue.go | 2 +- .../simulation/stages/commit_queue_test.go | 12 +++--- .../simulation/stages/precommit_stage.go | 39 ++++++++++--------- .../simulation/stages/windowpost_stage.go | 2 +- cmd/lotus-sim/upgrade.go | 17 ++++++-- 20 files changed, 124 insertions(+), 68 deletions(-) diff --git a/chain/vm/vm.go b/chain/vm/vm.go index 9f9398630..34aaa028c 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -672,7 +672,7 @@ func (vm *VM) Flush(ctx context.Context) (cid.Cid, error) { } // Get the buffered blockstore associated with the VM. This includes any temporary blocks produced -// during thsi VM's execution. +// during this VM's execution. func (vm *VM) ActorStore(ctx context.Context) adt.Store { return adt.WrapStore(ctx, vm.cst) } diff --git a/cmd/lotus-sim/copy.go b/cmd/lotus-sim/copy.go index eeb8eb1aa..5faba69f2 100644 --- a/cmd/lotus-sim/copy.go +++ b/cmd/lotus-sim/copy.go @@ -9,12 +9,16 @@ import ( var copySimCommand = &cli.Command{ Name: "copy", ArgsUsage: "", - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { node, err := open(cctx) if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() if cctx.NArg() != 1 { return fmt.Errorf("expected 1 argument") } diff --git a/cmd/lotus-sim/create.go b/cmd/lotus-sim/create.go index cfd93c789..4867a5da5 100644 --- a/cmd/lotus-sim/create.go +++ b/cmd/lotus-sim/create.go @@ -12,12 +12,16 @@ import ( var createSimCommand = &cli.Command{ Name: "create", ArgsUsage: "[tipset]", - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { node, err := open(cctx) if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() var ts *types.TipSet switch cctx.NArg() { diff --git a/cmd/lotus-sim/delete.go b/cmd/lotus-sim/delete.go index 472a35a86..c19b3d27d 100644 --- a/cmd/lotus-sim/delete.go +++ b/cmd/lotus-sim/delete.go @@ -6,12 +6,16 @@ import ( var deleteSimCommand = &cli.Command{ Name: "delete", - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { node, err := open(cctx) if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() return node.DeleteSim(cctx.Context, cctx.String("simulation")) }, diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 8288bb99f..67e53b34c 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -89,12 +89,16 @@ var infoSimCommand = &cli.Command{ infoCapacityGrowthSimCommand, infoStateGrowthSimCommand, }, - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { node, err := open(cctx) if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) if err != nil { diff --git a/cmd/lotus-sim/info_capacity.go b/cmd/lotus-sim/info_capacity.go index 14ee36f08..8ba603c20 100644 --- a/cmd/lotus-sim/info_capacity.go +++ b/cmd/lotus-sim/info_capacity.go @@ -14,12 +14,16 @@ import ( var infoCapacityGrowthSimCommand = &cli.Command{ Name: "capacity-growth", Description: "List daily capacity growth over the course of the simulation starting at the end.", - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { node, err := open(cctx) if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) if err != nil { diff --git a/cmd/lotus-sim/info_commit.go b/cmd/lotus-sim/info_commit.go index f6b08ea05..738fcde95 100644 --- a/cmd/lotus-sim/info_commit.go +++ b/cmd/lotus-sim/info_commit.go @@ -4,13 +4,13 @@ import ( "bytes" "fmt" "os" - - "github.com/ipfs/go-cid" - "github.com/streadway/quantile" - "github.com/urfave/cli/v2" "syscall" + "github.com/streadway/quantile" + "github.com/urfave/cli/v2" + "github.com/filecoin-project/go-state-types/exitcode" + "github.com/ipfs/go-cid" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/stmgr" @@ -28,7 +28,7 @@ var infoCommitGasSimCommand = &cli.Command{ Value: 0, }, }, - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { log := func(f string, i ...interface{}) { fmt.Fprintf(os.Stderr, f, i...) } @@ -36,7 +36,11 @@ var infoCommitGasSimCommand = &cli.Command{ if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() go profileOnSignal(cctx, syscall.SIGUSR2) diff --git a/cmd/lotus-sim/info_state.go b/cmd/lotus-sim/info_state.go index 4dbc65848..19a31e19f 100644 --- a/cmd/lotus-sim/info_state.go +++ b/cmd/lotus-sim/info_state.go @@ -21,12 +21,16 @@ import ( var infoStateGrowthSimCommand = &cli.Command{ Name: "state-size", Description: "List daily state size over the course of the simulation starting at the end.", - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { node, err := open(cctx) if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) if err != nil { @@ -58,14 +62,13 @@ var infoStateGrowthSimCommand = &cli.Command{ var totalSize uint64 if err := store.View(c, func(data []byte) error { totalSize += uint64(len(data)) - cbg.ScanForLinks(bytes.NewReader(data), func(c cid.Cid) { + return cbg.ScanForLinks(bytes.NewReader(data), func(c cid.Cid) { if c.Prefix().Codec != cid.DagCBOR { return } links = append(links, c) }) - return nil }); err != nil { return 0, err } diff --git a/cmd/lotus-sim/info_wdpost.go b/cmd/lotus-sim/info_wdpost.go index d52cd5a8c..719a133b1 100644 --- a/cmd/lotus-sim/info_wdpost.go +++ b/cmd/lotus-sim/info_wdpost.go @@ -18,12 +18,16 @@ import ( var infoWindowPostBandwidthSimCommand = &cli.Command{ Name: "post-bandwidth", Description: "List average chain bandwidth used by window posts for each day of the simulation.", - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { node, err := open(cctx) if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) if err != nil { diff --git a/cmd/lotus-sim/list.go b/cmd/lotus-sim/list.go index 8ded8a133..37e767b9a 100644 --- a/cmd/lotus-sim/list.go +++ b/cmd/lotus-sim/list.go @@ -9,12 +9,16 @@ import ( var listSimCommand = &cli.Command{ Name: "list", - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { node, err := open(cctx) if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() list, err := node.ListSims(cctx.Context) if err != nil { diff --git a/cmd/lotus-sim/rename.go b/cmd/lotus-sim/rename.go index 833a57e96..c336717c7 100644 --- a/cmd/lotus-sim/rename.go +++ b/cmd/lotus-sim/rename.go @@ -9,12 +9,17 @@ import ( var renameSimCommand = &cli.Command{ Name: "rename", ArgsUsage: "", - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { node, err := open(cctx) if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() + if cctx.NArg() != 1 { return fmt.Errorf("expected 1 argument") } diff --git a/cmd/lotus-sim/run.go b/cmd/lotus-sim/run.go index 00a3bddd9..a985fdf9e 100644 --- a/cmd/lotus-sim/run.go +++ b/cmd/lotus-sim/run.go @@ -22,12 +22,16 @@ Signals: Usage: "Advance the given number of epochs then stop.", }, }, - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { node, err := open(cctx) if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() go profileOnSignal(cctx, syscall.SIGUSR2) diff --git a/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go b/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go index e4dc79b24..2ffc0bf14 100644 --- a/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go +++ b/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go @@ -150,7 +150,10 @@ func (bb *BlockBuilder) PushMessage(msg *types.Message) (*types.MessageReceipt, msg.GasLimit = build.BlockGasTarget // We manually snapshot so we can revert nonce changes, etc. on failure. - st.Snapshot(bb.ctx) + err = st.Snapshot(bb.ctx) + if err != nil { + return nil, xerrors.Errorf("failed to take a snapshot while estimating message gas: %w", err) + } defer st.ClearSnapshot() ret, err := bb.vm.ApplyMessage(bb.ctx, msg) diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go index 0be2de182..f97808eef 100644 --- a/cmd/lotus-sim/simulation/node.go +++ b/cmd/lotus-sim/simulation/node.go @@ -39,19 +39,19 @@ func OpenNode(ctx context.Context, path string) (*Node, error) { node.Repo, err = r.Lock(repo.FullNode) if err != nil { - node.Close() + _ = node.Close() return nil, err } node.Blockstore, err = node.Repo.Blockstore(ctx, repo.UniversalBlockstore) if err != nil { - node.Close() + _ = node.Close() return nil, err } node.MetadataDS, err = node.Repo.Datastore(ctx, "/metadata") if err != nil { - node.Close() + _ = node.Close() return nil, err } @@ -157,7 +157,9 @@ func (nd *Node) ListSims(ctx context.Context) ([]string, error) { if err != nil { return nil, xerrors.Errorf("failed to list simulations: %w", err) } - defer items.Close() + + defer func() { _ = items.Close() }() + var names []string for { select { diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index 0b8ab1e56..18ccf1c0c 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -16,7 +16,6 @@ import ( blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" - "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" @@ -25,8 +24,6 @@ import ( var log = logging.Logger("simulation") -const onboardingProjectionLookback = 2 * 7 * builtin.EpochsInDay // lookback two weeks - // config is the simulation's config, persisted to the local metadata store and loaded on start. // // See Simulation.loadConfig and Simulation.saveConfig. diff --git a/cmd/lotus-sim/simulation/stages/commit_queue.go b/cmd/lotus-sim/simulation/stages/commit_queue.go index 515e080a0..851dd78f1 100644 --- a/cmd/lotus-sim/simulation/stages/commit_queue.go +++ b/cmd/lotus-sim/simulation/stages/commit_queue.go @@ -16,7 +16,7 @@ type pendingCommitTracker map[address.Address]minerPendingCommits // minerPendingCommits tracks a miner's pending commits during a single epoch (grouped by seal proof type). type minerPendingCommits map[abi.RegisteredSealProof][]abi.SectorNumber -// finish markes count sectors of the given proof type as "prove-committed". +// finish marks count sectors of the given proof type as "prove-committed". func (m minerPendingCommits) finish(proof abi.RegisteredSealProof, count int) { snos := m[proof] if len(snos) < count { diff --git a/cmd/lotus-sim/simulation/stages/commit_queue_test.go b/cmd/lotus-sim/simulation/stages/commit_queue_test.go index 180624493..8ab05250e 100644 --- a/cmd/lotus-sim/simulation/stages/commit_queue_test.go +++ b/cmd/lotus-sim/simulation/stages/commit_queue_test.go @@ -68,7 +68,7 @@ func TestCommitQueue(t *testing.T) { require.EqualValues(t, []abi.SectorNumber{1}, sectors[proofType]) // 1 : non-empty + non-empty - epoch += 1 + epoch++ q.advanceEpoch(epoch) addr, sectors, ok = q.nextMiner() require.True(t, ok) @@ -79,13 +79,13 @@ func TestCommitQueue(t *testing.T) { require.Equal(t, sectors.count(), 0) // 2 : empty + empty - epoch += 1 + epoch++ q.advanceEpoch(epoch) _, _, ok = q.nextMiner() require.False(t, ok) // 3 : empty + non-empty - epoch += 1 + epoch++ q.advanceEpoch(epoch) _, sectors, ok = q.nextMiner() require.True(t, ok) @@ -93,7 +93,7 @@ func TestCommitQueue(t *testing.T) { require.EqualValues(t, []abi.SectorNumber{4}, sectors[proofType]) // 4 : non-empty + non-empty - epoch += 1 + epoch++ q.advanceEpoch(epoch) _, sectors, ok = q.nextMiner() require.True(t, ok) @@ -101,7 +101,7 @@ func TestCommitQueue(t *testing.T) { require.EqualValues(t, []abi.SectorNumber{4, 5}, sectors[proofType]) // 5 : empty + non-empty - epoch += 1 + epoch++ q.advanceEpoch(epoch) _, sectors, ok = q.nextMiner() require.True(t, ok) @@ -111,7 +111,7 @@ func TestCommitQueue(t *testing.T) { require.EqualValues(t, []abi.SectorNumber{5}, sectors[proofType]) // 6 - epoch += 1 + epoch++ q.advanceEpoch(epoch) _, sectors, ok = q.nextMiner() require.True(t, ok) diff --git a/cmd/lotus-sim/simulation/stages/precommit_stage.go b/cmd/lotus-sim/simulation/stages/precommit_stage.go index 641292e0e..3dcfee28f 100644 --- a/cmd/lotus-sim/simulation/stages/precommit_stage.go +++ b/cmd/lotus-sim/simulation/stages/precommit_stage.go @@ -26,8 +26,9 @@ import ( ) const ( - minPreCommitBatchSize = 1 - maxPreCommitBatchSize = miner5.PreCommitSectorBatchMaxSize + minPreCommitBatchSize = 1 + maxPreCommitBatchSize = miner5.PreCommitSectorBatchMaxSize + onboardingProjectionLookback = 2 * 7 * builtin.EpochsInDay // lookback two weeks ) type PreCommitStage struct { @@ -89,7 +90,7 @@ func (stage *PreCommitStage) PackMessages(ctx context.Context, bb *blockbuilder. ) // We pre-commit for the top 1%, 10%, and the of the network 1/3rd of the time each. - // This won't yeild the most accurate distribution... but it'll give us a good + // This won't yield the most accurate distribution... but it'll give us a good // enough distribution. switch { case (i%3) <= 0 && top1Miners < stage.top1.len(): @@ -237,7 +238,7 @@ func (stage *PreCommitStage) packMiner( } } for _, info := range infos { - enc, err := actors.SerializeParams(&info) + enc, err := actors.SerializeParams(&info) //nolint if err != nil { return 0, false, err } @@ -261,7 +262,7 @@ func (stage *PreCommitStage) packMiner( return added, false, nil } -func (ps *PreCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBuilder) (_err error) { +func (stage *PreCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBuilder) (_err error) { bb.L().Infow("loading miner power for pre-commits") start := time.Now() defer func() { @@ -270,12 +271,12 @@ func (ps *PreCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBuilde } bb.L().Infow("loaded miner power for pre-commits", "duration", time.Since(start), - "top1", ps.top1.len(), - "top10", ps.top10.len(), - "rest", ps.rest.len(), + "top1", stage.top1.len(), + "top10", stage.top10.len(), + "rest", stage.rest.len(), ) }() - lookbackEpoch := bb.Height() - (14 * builtin.EpochsInDay) + lookbackEpoch := bb.Height() - onboardingProjectionLookback lookbackPowerTable, err := loadClaims(ctx, bb, lookbackEpoch) if err != nil { return xerrors.Errorf("failed to load claims from lookback epoch %d: %w", lookbackEpoch, err) @@ -334,26 +335,26 @@ func (ps *PreCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBuilde }) // reset, just in case. - ps.top1 = actorIter{} - ps.top10 = actorIter{} - ps.rest = actorIter{} + stage.top1 = actorIter{} + stage.top10 = actorIter{} + stage.rest = actorIter{} for i, oi := range sealList { var dist *actorIter if i < len(sealList)/100 { - dist = &ps.top1 + dist = &stage.top1 } else if i < len(sealList)/10 { - dist = &ps.top10 + dist = &stage.top10 } else { - dist = &ps.rest + dist = &stage.rest } dist.add(oi.addr) } - ps.top1.shuffle() - ps.top10.shuffle() - ps.rest.shuffle() + stage.top1.shuffle() + stage.top10.shuffle() + stage.rest.shuffle() - ps.initialized = true + stage.initialized = true return nil } diff --git a/cmd/lotus-sim/simulation/stages/windowpost_stage.go b/cmd/lotus-sim/simulation/stages/windowpost_stage.go index e5bbf8145..68f8ea179 100644 --- a/cmd/lotus-sim/simulation/stages/windowpost_stage.go +++ b/cmd/lotus-sim/simulation/stages/windowpost_stage.go @@ -288,7 +288,7 @@ func (stage *WindowPoStStage) tick(ctx context.Context, bb *blockbuilder.BlockBu store := bb.ActorStore() // Perform a bit of catch up. This lets us do things like skip blocks at upgrades then catch - // up to make the simualtion easier. + // up to make the simulation easier. for ; stage.nextWpostEpoch <= targetHeight; stage.nextWpostEpoch++ { if stage.nextWpostEpoch+miner.WPoStChallengeWindow < targetHeight { bb.L().Warnw("skipping old window post", "deadline-open", stage.nextWpostEpoch) diff --git a/cmd/lotus-sim/upgrade.go b/cmd/lotus-sim/upgrade.go index 3a30e869b..0cda2c8ee 100644 --- a/cmd/lotus-sim/upgrade.go +++ b/cmd/lotus-sim/upgrade.go @@ -17,6 +17,7 @@ var upgradeCommand = &cli.Command{ Description: "Modifies network upgrade heights.", Subcommands: []*cli.Command{ upgradeSetCommand, + upgradeList, }, } @@ -26,12 +27,16 @@ var upgradeList = &cli.Command{ Subcommands: []*cli.Command{ upgradeSetCommand, }, - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { node, err := open(cctx) if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) if err != nil { @@ -61,7 +66,7 @@ var upgradeSetCommand = &cli.Command{ Name: "set", ArgsUsage: " [+]", Description: "Set a network upgrade height. Prefix with '+' to set it relative to the last epoch.", - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { args := cctx.Args() if args.Len() != 2 { return fmt.Errorf("expected 2 arguments") @@ -86,7 +91,11 @@ var upgradeSetCommand = &cli.Command{ if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) if err != nil { From b30b5dd629ccc93b452a399c23f70ef39ae805f4 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 18 Jun 2021 13:52:03 -0700 Subject: [PATCH 169/257] fix: move actors changes to template files --- chain/actors/builtin/miner/actor.go.template | 2 ++ chain/actors/builtin/multisig/actor.go.template | 1 + chain/actors/builtin/multisig/multisig.go | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/chain/actors/builtin/miner/actor.go.template b/chain/actors/builtin/miner/actor.go.template index 8c0b10cb0..8d46f99fd 100644 --- a/chain/actors/builtin/miner/actor.go.template +++ b/chain/actors/builtin/miner/actor.go.template @@ -22,6 +22,7 @@ import ( miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" miner3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/miner" + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" {{range .versions}} builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" {{end}} @@ -180,6 +181,7 @@ type DeclareFaultsRecoveredParams = miner0.DeclareFaultsRecoveredParams type SubmitWindowedPoStParams = miner0.SubmitWindowedPoStParams type ProveCommitSectorParams = miner0.ProveCommitSectorParams type DisputeWindowedPoStParams = miner3.DisputeWindowedPoStParams +type ProveCommitAggregateParams = miner5.ProveCommitAggregateParams func PreferredSealProofTypeFromWindowPoStType(nver network.Version, proof abi.RegisteredPoStProof) (abi.RegisteredSealProof, error) { // We added support for the new proofs in network version 7, and removed support for the old diff --git a/chain/actors/builtin/multisig/actor.go.template b/chain/actors/builtin/multisig/actor.go.template index 77bc13f67..b899815a6 100644 --- a/chain/actors/builtin/multisig/actor.go.template +++ b/chain/actors/builtin/multisig/actor.go.template @@ -115,6 +115,7 @@ type MessageBuilder interface { type ProposalHashData = msig{{.latestVersion}}.ProposalHashData type ProposeReturn = msig{{.latestVersion}}.ProposeReturn type ProposeParams = msig{{.latestVersion}}.ProposeParams +type ApproveReturn = msig{{.latestVersion}}.ApproveReturn func txnParams(id uint64, data *ProposalHashData) ([]byte, error) { params := msig{{.latestVersion}}.TxnIDParams{ID: msig{{.latestVersion}}.TxnID(id)} diff --git a/chain/actors/builtin/multisig/multisig.go b/chain/actors/builtin/multisig/multisig.go index 82f1963be..c950ced90 100644 --- a/chain/actors/builtin/multisig/multisig.go +++ b/chain/actors/builtin/multisig/multisig.go @@ -185,8 +185,8 @@ type MessageBuilder interface { // this type is the same between v0 and v2 type ProposalHashData = msig5.ProposalHashData type ProposeReturn = msig5.ProposeReturn -type ApproveReturn = msig5.ApproveReturn type ProposeParams = msig5.ProposeParams +type ApproveReturn = msig5.ApproveReturn func txnParams(id uint64, data *ProposalHashData) ([]byte, error) { params := msig5.TxnIDParams{ID: msig5.TxnID(id)} From eb0a15faf0f122fccf303f47fe207a8b919a60ce Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 18 Jun 2021 14:20:48 -0700 Subject: [PATCH 170/257] fix(genesis): set initial balances to 0 --- chain/gen/genesis/f00_system.go | 6 ++++-- chain/gen/genesis/f01_init.go | 6 ++++-- chain/gen/genesis/f03_cron.go | 6 ++++-- chain/gen/genesis/f04_power.go | 6 ++++-- chain/gen/genesis/f05_market.go | 6 ++++-- chain/gen/genesis/f06_vreg.go | 6 ++++-- chain/gen/genesis/genesis.go | 7 +++---- chain/state/statetree.go | 3 ++- 8 files changed, 29 insertions(+), 17 deletions(-) diff --git a/chain/gen/genesis/f00_system.go b/chain/gen/genesis/f00_system.go index d1dd203b6..4fde27107 100644 --- a/chain/gen/genesis/f00_system.go +++ b/chain/gen/genesis/f00_system.go @@ -3,6 +3,7 @@ package genesis import ( "context" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/actors/builtin/system" @@ -32,8 +33,9 @@ func SetupSystemActor(ctx context.Context, bs bstore.Blockstore, av actors.Versi } act := &types.Actor{ - Code: actcid, - Head: statecid, + Code: actcid, + Head: statecid, + Balance: big.Zero(), } return act, nil diff --git a/chain/gen/genesis/f01_init.go b/chain/gen/genesis/f01_init.go index 88d409221..61ec91703 100644 --- a/chain/gen/genesis/f01_init.go +++ b/chain/gen/genesis/f01_init.go @@ -11,6 +11,7 @@ import ( "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/specs-actors/actors/util/adt" @@ -181,8 +182,9 @@ func SetupInitActor(ctx context.Context, bs bstore.Blockstore, netname string, i } act := &types.Actor{ - Code: actcid, - Head: statecid, + Code: actcid, + Head: statecid, + Balance: big.Zero(), } return counter, act, keyToId, nil diff --git a/chain/gen/genesis/f03_cron.go b/chain/gen/genesis/f03_cron.go index c6fd2422a..c9dd0d341 100644 --- a/chain/gen/genesis/f03_cron.go +++ b/chain/gen/genesis/f03_cron.go @@ -3,6 +3,7 @@ package genesis import ( "context" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/actors/builtin/cron" @@ -31,8 +32,9 @@ func SetupCronActor(ctx context.Context, bs bstore.Blockstore, av actors.Version } act := &types.Actor{ - Code: actcid, - Head: statecid, + Code: actcid, + Head: statecid, + Balance: big.Zero(), } return act, nil diff --git a/chain/gen/genesis/f04_power.go b/chain/gen/genesis/f04_power.go index 6fe4d75c0..b5e08cebe 100644 --- a/chain/gen/genesis/f04_power.go +++ b/chain/gen/genesis/f04_power.go @@ -3,6 +3,7 @@ package genesis import ( "context" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/chain/actors/builtin/power" "github.com/filecoin-project/lotus/chain/actors" @@ -33,8 +34,9 @@ func SetupStoragePowerActor(ctx context.Context, bs bstore.Blockstore, av actors } act := &types.Actor{ - Code: actcid, - Head: statecid, + Code: actcid, + Head: statecid, + Balance: big.Zero(), } return act, nil diff --git a/chain/gen/genesis/f05_market.go b/chain/gen/genesis/f05_market.go index 5c39ef38f..ac32294c9 100644 --- a/chain/gen/genesis/f05_market.go +++ b/chain/gen/genesis/f05_market.go @@ -3,6 +3,7 @@ package genesis import ( "context" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/actors/builtin/market" @@ -31,8 +32,9 @@ func SetupStorageMarketActor(ctx context.Context, bs bstore.Blockstore, av actor } act := &types.Actor{ - Code: actcid, - Head: statecid, + Code: actcid, + Head: statecid, + Balance: big.Zero(), } return act, nil diff --git a/chain/gen/genesis/f06_vreg.go b/chain/gen/genesis/f06_vreg.go index d8f5ee2a0..e61c951f5 100644 --- a/chain/gen/genesis/f06_vreg.go +++ b/chain/gen/genesis/f06_vreg.go @@ -3,6 +3,7 @@ package genesis import ( "context" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/chain/actors/builtin/verifreg" "github.com/filecoin-project/lotus/chain/actors" @@ -46,8 +47,9 @@ func SetupVerifiedRegistryActor(ctx context.Context, bs bstore.Blockstore, av ac } act := &types.Actor{ - Code: actcid, - Head: statecid, + Code: actcid, + Head: statecid, + Balance: big.Zero(), } return act, nil diff --git a/chain/gen/genesis/genesis.go b/chain/gen/genesis/genesis.go index 94badbbfb..6dec3fea6 100644 --- a/chain/gen/genesis/genesis.go +++ b/chain/gen/genesis/genesis.go @@ -313,11 +313,10 @@ func MakeInitialStateTree(ctx context.Context, bs bstore.Blockstore, template ge totalFilAllocated := big.Zero() - // flush as ForEach works on the HAMT - if _, err := state.Flush(ctx); err != nil { - return nil, nil, err - } err = state.ForEach(func(addr address.Address, act *types.Actor) error { + if act.Balance.Nil() { + panic(fmt.Sprintf("actor %s (%s) has nil balance", addr, builtin.ActorNameByCode(act.Code))) + } totalFilAllocated = big.Add(totalFilAllocated, act.Balance) return nil }) diff --git a/chain/state/statetree.go b/chain/state/statetree.go index 72269e4f2..dbf150ecd 100644 --- a/chain/state/statetree.go +++ b/chain/state/statetree.go @@ -515,7 +515,8 @@ func (st *StateTree) ForEach(f func(address.Address, *types.Actor) error) error if op.Delete { continue } - if err := f(addr, &op.Act); err != nil { + act := op.Act // copy + if err := f(addr, &act); err != nil { return err } } From 2fdf49e7da7b0cb7ba8f6437c5d1fcb4381460e1 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Thu, 17 Jun 2021 20:32:15 +0200 Subject: [PATCH 171/257] Add histogram and quantiles for message sizes Resolves https://github.com/filecoin-project/lotus/issues/6513 Signed-off-by: Jakub Sztandera --- cmd/lotus-sim/info.go | 1 + cmd/lotus-sim/info_message.go | 91 +++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 cmd/lotus-sim/info_message.go diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 67e53b34c..759f2d815 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -85,6 +85,7 @@ var infoSimCommand = &cli.Command{ Description: "Output information about the simulation.", Subcommands: []*cli.Command{ infoCommitGasSimCommand, + infoMessageSizeSimCommand, infoWindowPostBandwidthSimCommand, infoCapacityGrowthSimCommand, infoStateGrowthSimCommand, diff --git a/cmd/lotus-sim/info_message.go b/cmd/lotus-sim/info_message.go new file mode 100644 index 000000000..b30fadd65 --- /dev/null +++ b/cmd/lotus-sim/info_message.go @@ -0,0 +1,91 @@ +package main + +import ( + "fmt" + "syscall" + + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation" + "github.com/filecoin-project/lotus/lib/stati" + "github.com/ipfs/go-cid" + "github.com/streadway/quantile" + "github.com/urfave/cli/v2" +) + +var infoMessageSizeSimCommand = &cli.Command{ + Name: "message-size", + Description: "Output information about message size distribution", + Flags: []cli.Flag{ + &cli.Int64Flag{ + Name: "lookback", + Value: 0, + }, + }, + Action: func(cctx *cli.Context) error { + node, err := open(cctx) + if err != nil { + return err + } + defer node.Close() + + go profileOnSignal(cctx, syscall.SIGUSR2) + + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) + if err != nil { + return err + } + + qpoints := []struct{ q, tol float64 }{ + {0.30, 0.01}, + {0.40, 0.01}, + {0.60, 0.01}, + {0.70, 0.01}, + {0.80, 0.01}, + {0.85, 0.01}, + {0.90, 0.01}, + {0.95, 0.001}, + {0.99, 0.0005}, + {0.999, 0.0001}, + } + estims := make([]quantile.Estimate, len(qpoints)) + for i, p := range qpoints { + estims[i] = quantile.Known(p.q, p.tol) + } + qua := quantile.New(estims...) + hist, err := stati.NewHistogram([]float64{ + 1 << 8, 1 << 10, 1 << 11, 1 << 12, 1 << 13, 1 << 14, 1 << 15, 1 << 16, + }) + if err != nil { + return err + } + + err = sim.Walk(cctx.Context, cctx.Int64("lookback"), func( + sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, + messages []*simulation.AppliedMessage, + ) error { + for _, m := range messages { + msgSize := float64(m.ChainLength()) + qua.Add(msgSize) + hist.Observe(msgSize) + } + + return nil + }) + if err != nil { + return err + } + fmt.Println("Quantiles of message sizes:") + for _, p := range qpoints { + fmt.Printf("%.1f%%\t%.0f\n", p.q*100, qua.Get(p.q)) + } + fmt.Println() + fmt.Println("Histogram of message sizes:") + fmt.Printf("Total\t%d\n", hist.Total()) + for i, b := range hist.Buckets[1:] { + fmt.Printf("%.0f\t%d\t%.1f%%\n", b, hist.Get(i), 100*hist.GetRatio(i)) + } + + return nil + }, +} From 69a8a6bc0edb6342fae3d031f0c48c78d9de4465 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 18 Jun 2021 14:43:09 -0700 Subject: [PATCH 172/257] fix(lotus-sim): lint --- cmd/lotus-sim/info_message.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-sim/info_message.go b/cmd/lotus-sim/info_message.go index b30fadd65..33c45e728 100644 --- a/cmd/lotus-sim/info_message.go +++ b/cmd/lotus-sim/info_message.go @@ -22,12 +22,16 @@ var infoMessageSizeSimCommand = &cli.Command{ Value: 0, }, }, - Action: func(cctx *cli.Context) error { + Action: func(cctx *cli.Context) (err error) { node, err := open(cctx) if err != nil { return err } - defer node.Close() + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() go profileOnSignal(cctx, syscall.SIGUSR2) From 65844761b69d268ebfaf1c5beae8fda65ffe8d9f Mon Sep 17 00:00:00 2001 From: Travis Person Date: Fri, 18 Jun 2021 19:45:21 +0000 Subject: [PATCH 173/257] Calibration network reset --- build/bootstrap/calibnet.pi | 8 ++++---- build/genesis/calibnet.car | Bin 1103520 -> 0 bytes build/params_calibnet.go | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) delete mode 100644 build/genesis/calibnet.car diff --git a/build/bootstrap/calibnet.pi b/build/bootstrap/calibnet.pi index 0eb9fd2f3..20473eaaa 100644 --- a/build/bootstrap/calibnet.pi +++ b/build/bootstrap/calibnet.pi @@ -1,4 +1,4 @@ -/dns4/bootstrap-0.calibration.fildev.network/tcp/1347/p2p/12D3KooWRLZAseMo9h7fRD6ojn6YYDXHsBSavX5YmjBZ9ngtAEec -/dns4/bootstrap-1.calibration.fildev.network/tcp/1347/p2p/12D3KooWJFtDXgZEQMEkjJPSrbfdvh2xfjVKrXeNFG1t8ioJXAzv -/dns4/bootstrap-2.calibration.fildev.network/tcp/1347/p2p/12D3KooWP1uB9Lo7yCA3S17TD4Y5wStP5Nk7Vqh53m8GsFjkyujD -/dns4/bootstrap-3.calibration.fildev.network/tcp/1347/p2p/12D3KooWLrPM4WPK1YRGPCUwndWcDX8GCYgms3DiuofUmxwvhMCn +/dns4/bootstrap-0.calibration.fildev.network/tcp/1347/p2p/12D3KooWJkikQQkxS58spo76BYzFt4fotaT5NpV2zngvrqm4u5ow +/dns4/bootstrap-1.calibration.fildev.network/tcp/1347/p2p/12D3KooWLce5FDHR4EX4CrYavphA5xS3uDsX6aoowXh5tzDUxJav +/dns4/bootstrap-2.calibration.fildev.network/tcp/1347/p2p/12D3KooWA9hFfQG9GjP6bHeuQQbMD3FDtZLdW1NayxKXUT26PQZu +/dns4/bootstrap-3.calibration.fildev.network/tcp/1347/p2p/12D3KooWMHDi3LVTFG8Szqogt7RkNXvonbQYqSazxBx41A5aeuVz diff --git a/build/genesis/calibnet.car b/build/genesis/calibnet.car deleted file mode 100644 index d2fe7c3afe3c90914600ec094a3591b4bf472543..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1103520 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

i;Q}$0h#yEpT>{!LYJgzmK|=1=qlTgh5X4K4 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 z%0D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 Date: Sat, 19 Jun 2021 00:53:21 +0000 Subject: [PATCH 174/257] Add calibration genesis --- build/genesis/calibnet.car | Bin 0 -> 1101076 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 build/genesis/calibnet.car diff --git a/build/genesis/calibnet.car b/build/genesis/calibnet.car new file mode 100644 index 0000000000000000000000000000000000000000..cbade953f866df5d84bd6256c34f400c0715e421 GIT binary patch literal 1101076 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

g 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%0A_3@}A-6az%m;bp`I!Vf5| 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%0APaAk4Ke`M4hpb}mL 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�+Rv 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
Vm?gp;G& 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 }a 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 Date: Fri, 18 Jun 2021 22:05:19 -0400 Subject: [PATCH 175/257] version bump to lotus v1.10.0-rc6 --- CHANGELOG.md | 4 ++-- build/version.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbf620f50..be302a28a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ # Lotus changelog -# 1.10.0-rc5 / 2021-06-16 +# 1.10.0-rc5 / 2021-06-18 > Note: If you are running a lotus miner, check out the doc [here](https://docs.filecoin.io/mine/lotus/miner-configuration/#precommitsectorsbatch) for new lotus miner configurations explanations of the new features! -This is the 5th release candidate for Lotus v1.10.0, an upcoming mandatory release of Lotus that will introduce Filecoin network v13. Included in the new network version are the following FIPs: +This is the 6th release candidate for Lotus v1.10.0, an upcoming mandatory release of Lotus that will introduce Filecoin network v13. Use this release for syncing with the [reset calibration net](https://github.com/filecoin-project/community/discussions/74#discussioncomment-885580). also Included in the new network version are the following FIPs: - [FIP-0008](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0008.md): Add miner batched sector pre-commit method - [FIP-0011](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0011.md): Remove reward auction from reporting consensus faults diff --git a/build/version.go b/build/version.go index d5c126068..93022740d 100644 --- a/build/version.go +++ b/build/version.go @@ -29,7 +29,7 @@ func buildType() string { } // BuildVersion is the local build version, set by build system -const BuildVersion = "1.10.0-rc5" +const BuildVersion = "1.10.0-rc6" func UserVersion() string { return BuildVersion + buildType() + CurrentCommit From e1c2567136cfd7d6e839b76a668808711201a7c1 Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Fri, 18 Jun 2021 22:17:53 -0400 Subject: [PATCH 176/257] docs gen --- CHANGELOG.md | 2 +- build/openrpc/full.json.gz | Bin 22485 -> 22485 bytes build/openrpc/miner.json.gz | Bin 8089 -> 8090 bytes build/openrpc/worker.json.gz | Bin 2579 -> 2579 bytes 4 files changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be302a28a..c71a931d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ > Note: If you are running a lotus miner, check out the doc [here](https://docs.filecoin.io/mine/lotus/miner-configuration/#precommitsectorsbatch) for new lotus miner configurations explanations of the new features! -This is the 6th release candidate for Lotus v1.10.0, an upcoming mandatory release of Lotus that will introduce Filecoin network v13. Use this release for syncing with the [reset calibration net](https://github.com/filecoin-project/community/discussions/74#discussioncomment-885580). also Included in the new network version are the following FIPs: +This is the 6th release candidate for Lotus v1.10.0, an upcoming mandatory release of Lotus that will introduce Filecoin network v13. Use this release for syncing with the [reset calibration net](https://github.com/filecoin-project/community/discussions/74#discussioncomment-885580). In addition, included in the new network version are the following FIPs: - [FIP-0008](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0008.md): Add miner batched sector pre-commit method - [FIP-0011](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0011.md): Remove reward auction from reporting consensus faults diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 82226eccfca98a437a2ba640e842c63f26ca58c9..5a002a26f20c4ea835f624f5545c42d8271703ef 100644 GIT binary patch delta 22339 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 delta 22369 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%0D06$hA-<# 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 R(a;_ruS1+ntIe^{{sTaPY~BC> diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 9c07dbc8a46b55d0273aa434a80aea54705f859f..c8c1bcfd20b3184b43300b60e53f4425869d0319 100644 GIT binary patch delta 7559 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 diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index 83f2c5f436cc261667fca9840862065bba4a45f2..5339558fe6449c5eed7c7fd9bf6fe11b0340494b 100644 GIT binary patch delta 22 ecmbO%GFfCoBh&d08=E^gIc`o7I_O`;zyJVlun6}6 delta 22 ecmbO%GFfCoBa{Efjm;gL99zC>t?{p7U;qGU Date: Mon, 21 Jun 2021 11:56:10 +0100 Subject: [PATCH 177/257] fix lotus-storage-miner tests. --- cmd/lotus-storage-miner/actor_test.go | 62 +++++-------------------- cmd/lotus-storage-miner/allinfo_test.go | 24 +++------- 2 files changed, 18 insertions(+), 68 deletions(-) diff --git a/cmd/lotus-storage-miner/actor_test.go b/cmd/lotus-storage-miner/actor_test.go index 7f36812bc..073a83059 100644 --- a/cmd/lotus-storage-miner/actor_test.go +++ b/cmd/lotus-storage-miner/actor_test.go @@ -10,14 +10,13 @@ import ( "testing" "time" - logging "github.com/ipfs/go-log/v2" + "github.com/filecoin-project/go-state-types/network" "github.com/stretchr/testify/require" "github.com/urfave/cli/v2" "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/policy" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/itests/kit" @@ -32,36 +31,21 @@ func TestWorkerKeyChange(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - _ = logging.SetLogLevel("*", "INFO") - - policy.SetConsensusMinerMinPower(abi.NewStoragePower(2048)) - policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1) - policy.SetMinVerifiedDealSize(abi.NewStoragePower(256)) - kit.QuietMiningLogs() blocktime := 1 * time.Millisecond - - clients, miners := kit.MockMinerBuilder(t, - []kit.FullNodeOpts{kit.FullNodeWithLatestActorsAt(-1), kit.FullNodeWithLatestActorsAt(-1)}, - kit.OneMiner) - - client1 := clients[0] - client2 := clients[1] - - // Connect the nodes. - addrinfo, err := client1.NetAddrsListen(ctx) - require.NoError(t, err) - err = client2.NetConnect(ctx, addrinfo) - require.NoError(t, err) + client1, client2, miner, ens := kit.EnsembleTwoOne(t, kit.MockProofs(), + kit.ConstructorOpts(kit.InstantaneousNetworkVersion(network.Version13)), + ) + ens.InterconnectAll().BeginMining(blocktime) output := bytes.NewBuffer(nil) run := func(cmd *cli.Command, args ...string) error { app := cli.NewApp() app.Metadata = map[string]interface{}{ "repoType": repo.StorageMiner, - "testnode-full": clients[0], - "testnode-storage": miners[0], + "testnode-full": client1, + "testnode-storage": miner, } app.Writer = output api.RunningNodeType = api.NodeMiner @@ -78,9 +62,6 @@ func TestWorkerKeyChange(t *testing.T) { return cmd.Action(cctx) } - // start mining - kit.ConnectAndStartMining(t, blocktime, miners[0], client1, client2) - newKey, err := client1.WalletNew(ctx, types.KTBLS) require.NoError(t, err) @@ -105,14 +86,8 @@ func TestWorkerKeyChange(t *testing.T) { require.Error(t, run(actorConfirmChangeWorker, "--really-do-it", newKey.String())) output.Reset() - for { - head, err := client1.ChainHead(ctx) - require.NoError(t, err) - if head.Height() >= abi.ChainEpoch(targetEpoch) { - break - } - build.Clock.Sleep(10 * blocktime) - } + client1.WaitTillChain(ctx, kit.HeightAtLeast(abi.ChainEpoch(targetEpoch))) + require.NoError(t, run(actorConfirmChangeWorker, "--really-do-it", newKey.String())) output.Reset() @@ -121,23 +96,8 @@ func TestWorkerKeyChange(t *testing.T) { // Wait for finality (worker key switch). targetHeight := head.Height() + policy.ChainFinality - for { - head, err := client1.ChainHead(ctx) - require.NoError(t, err) - if head.Height() >= targetHeight { - break - } - build.Clock.Sleep(10 * blocktime) - } + client1.WaitTillChain(ctx, kit.HeightAtLeast(targetHeight)) // Make sure the other node can catch up. - for i := 0; i < 20; i++ { - head, err := client2.ChainHead(ctx) - require.NoError(t, err) - if head.Height() >= targetHeight { - return - } - build.Clock.Sleep(10 * blocktime) - } - t.Fatal("failed to reach target epoch on the second miner") + client2.WaitTillChain(ctx, kit.HeightAtLeast(targetHeight)) } diff --git a/cmd/lotus-storage-miner/allinfo_test.go b/cmd/lotus-storage-miner/allinfo_test.go index cbe65524e..f65c10bd8 100644 --- a/cmd/lotus-storage-miner/allinfo_test.go +++ b/cmd/lotus-storage-miner/allinfo_test.go @@ -7,13 +7,9 @@ import ( "time" "github.com/filecoin-project/lotus/itests/kit" - "github.com/filecoin-project/lotus/node/impl" - logging "github.com/ipfs/go-log/v2" "github.com/stretchr/testify/require" "github.com/urfave/cli/v2" - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/node/repo" @@ -24,12 +20,6 @@ func TestMinerAllInfo(t *testing.T) { t.Skip("skipping test in short mode") } - _ = logging.SetLogLevel("*", "INFO") - - policy.SetConsensusMinerMinPower(abi.NewStoragePower(2048)) - policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1) - policy.SetMinVerifiedDealSize(abi.NewStoragePower(256)) - _test = true kit.QuietMiningLogs() @@ -40,16 +30,15 @@ func TestMinerAllInfo(t *testing.T) { policy.SetPreCommitChallengeDelay(oldDelay) }) - n, sn := kit.Builder(t, kit.OneFull, kit.OneMiner) - client, miner := n[0].FullNode, sn[0] - kit.ConnectAndStartMining(t, time.Second, miner, client.(*impl.FullNodeAPI)) + client, miner, ens := kit.EnsembleMinimal(t) + ens.InterconnectAll().BeginMining(time.Second) run := func(t *testing.T) { app := cli.NewApp() app.Metadata = map[string]interface{}{ "repoType": repo.StorageMiner, - "testnode-full": n[0], - "testnode-storage": sn[0], + "testnode-full": client, + "testnode-storage": miner, } api.RunningNodeType = api.NodeMiner @@ -61,7 +50,8 @@ func TestMinerAllInfo(t *testing.T) { t.Run("pre-info-all", run) dh := kit.NewDealHarness(t, client, miner) - dh.MakeFullDeal(context.Background(), 6, false, false, 0) - + deal, res, inPath := dh.MakeOnlineDeal(context.Background(), 6, false, 0) + outPath := dh.PerformRetrieval(context.Background(), deal, res.Root, false) + kit.AssertFilesEqual(t, inPath, outPath) t.Run("post-info-all", run) } From d1b291de5ef7476c395f1f49a0e78b8f0dd1a9b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Mon, 21 Jun 2021 13:24:25 +0100 Subject: [PATCH 178/257] fix proof types. --- itests/api_test.go | 11 ++++++++--- itests/kit/ensemble.go | 2 +- itests/kit/ensemble_opts.go | 11 +---------- itests/kit/node_opts.go | 14 ++++++++------ 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/itests/api_test.go b/itests/api_test.go index 45c137a8d..5487a2c38 100644 --- a/itests/api_test.go +++ b/itests/api_test.go @@ -7,6 +7,7 @@ import ( "time" "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/exitcode" lapi "github.com/filecoin-project/lotus/api" @@ -170,9 +171,10 @@ func (ts *apiSuite) testMiningReal(t *testing.T) { func (ts *apiSuite) testNonGenesisMiner(t *testing.T) { ctx := context.Background() - full, genesisMiner, ens := kit.EnsembleMinimal(t, ts.opts...) + full, genesisMiner, ens := kit.EnsembleMinimal(t, append(ts.opts, kit.MockProofs())...) + ens.InterconnectAll().BeginMining(4 * time.Millisecond) - ens.BeginMining(4 * time.Millisecond) + time.Sleep(1 * time.Second) gaa, err := genesisMiner.ActorAddress(ctx) require.NoError(t, err) @@ -181,7 +183,10 @@ func (ts *apiSuite) testNonGenesisMiner(t *testing.T) { require.NoError(t, err) var newMiner kit.TestMiner - ens.Miner(&newMiner, full, kit.OwnerAddr(full.DefaultKey)).Start() + ens.Miner(&newMiner, full, + kit.OwnerAddr(full.DefaultKey), + kit.ProofType(abi.RegisteredSealProof_StackedDrg2KiBV1), // we're using v0 actors with old proofs. + ).Start().InterconnectAll() ta, err := newMiner.ActorAddress(ctx) require.NoError(t, err) diff --git a/itests/kit/ensemble.go b/itests/kit/ensemble.go index 1accdbd61..9861bcd7e 100644 --- a/itests/kit/ensemble.go +++ b/itests/kit/ensemble.go @@ -337,7 +337,7 @@ func (n *Ensemble) Start() *Ensemble { params, aerr := actors.SerializeParams(&power2.CreateMinerParams{ Owner: m.OwnerKey.Address, Worker: m.OwnerKey.Address, - SealProofType: n.options.proofType, + SealProofType: m.options.proofType, Peer: abi.PeerID(m.Libp2p.PeerID), }) require.NoError(n.t, aerr) diff --git a/itests/kit/ensemble_opts.go b/itests/kit/ensemble_opts.go index 9233aadd8..440362ed1 100644 --- a/itests/kit/ensemble_opts.go +++ b/itests/kit/ensemble_opts.go @@ -16,22 +16,13 @@ type genesisAccount struct { type ensembleOpts struct { pastOffset time.Duration - proofType abi.RegisteredSealProof verifiedRoot genesisAccount accounts []genesisAccount mockProofs bool } var DefaultEnsembleOpts = ensembleOpts{ - pastOffset: 10000000 * time.Second, // time sufficiently in the past to trigger catch-up mining. - proofType: abi.RegisteredSealProof_StackedDrg2KiBV1_1, // default _concrete_ proof type for non-genesis miners (notice the _1). -} - -func ProofType(proofType abi.RegisteredSealProof) EnsembleOpt { - return func(opts *ensembleOpts) error { - opts.proofType = proofType - return nil - } + pastOffset: 10000000 * time.Second, // time sufficiently in the past to trigger catch-up mining. } // MockProofs activates mock proofs for the entire ensemble. diff --git a/itests/kit/node_opts.go b/itests/kit/node_opts.go index c36ca3e26..ae99f3f29 100644 --- a/itests/kit/node_opts.go +++ b/itests/kit/node_opts.go @@ -26,12 +26,14 @@ type nodeOpts struct { ownerKey *wallet.Key extraNodeOpts []node.Option optBuilders []OptBuilder + proofType abi.RegisteredSealProof } // DefaultNodeOpts are the default options that will be applied to test nodes. var DefaultNodeOpts = nodeOpts{ - balance: big.Mul(big.NewInt(100000000), types.NewInt(build.FilecoinPrecision)), - sectors: DefaultPresealsPerBootstrapMiner, + balance: big.Mul(big.NewInt(100000000), types.NewInt(build.FilecoinPrecision)), + sectors: DefaultPresealsPerBootstrapMiner, + proofType: abi.RegisteredSealProof_StackedDrg2KiBV1_1, // default _concrete_ proof type for non-genesis miners (notice the _1) for new actors versions. } // OptBuilder is used to create an option after some other node is already @@ -95,11 +97,11 @@ func ConstructorOpts(extra ...node.Option) NodeOpt { } } -// AddOptBuilder adds an OptionBuilder to a node. It is used to add Lotus node -// constructor options after some nodes are already active. -func AddOptBuilder(optBuilder OptBuilder) NodeOpt { +// ProofType sets the proof type for this node. If you're using new actor +// versions, this should be a _1 proof type. +func ProofType(proofType abi.RegisteredSealProof) NodeOpt { return func(opts *nodeOpts) error { - opts.optBuilders = append(opts.optBuilders, optBuilder) + opts.proofType = proofType return nil } } From d6abcff63c444a00bcb5b5229481649054b6af57 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 21 Jun 2021 09:05:48 -0700 Subject: [PATCH 179/257] fix(lotus-sim): apply code review from magik6k MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Łukasz Magiera --- chain/vm/vm.go | 2 +- cmd/lotus-sim/simulation/stages/commit_queue.go | 2 +- cmd/lotus-sim/upgrade.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/chain/vm/vm.go b/chain/vm/vm.go index 34aaa028c..5a31187b7 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -439,7 +439,7 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet, }, GasCosts: &gasOutputs, Duration: time.Since(start), - ActorErr: aerrors.Newf(exitcode.SysErrSenderInvalid, + ActorErr: aerrors.Newf(exitcode.SysErrOutOfGas, "message gas limit does not cover on-chain gas costs"), }, nil } diff --git a/cmd/lotus-sim/simulation/stages/commit_queue.go b/cmd/lotus-sim/simulation/stages/commit_queue.go index 851dd78f1..d625dedb6 100644 --- a/cmd/lotus-sim/simulation/stages/commit_queue.go +++ b/cmd/lotus-sim/simulation/stages/commit_queue.go @@ -10,7 +10,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/policy" ) -// pendingCommitTracker tracks pending commits per-miner for a single epohc. +// pendingCommitTracker tracks pending commits per-miner for a single epoch. type pendingCommitTracker map[address.Address]minerPendingCommits // minerPendingCommits tracks a miner's pending commits during a single epoch (grouped by seal proof type). diff --git a/cmd/lotus-sim/upgrade.go b/cmd/lotus-sim/upgrade.go index 0cda2c8ee..dfc726d6b 100644 --- a/cmd/lotus-sim/upgrade.go +++ b/cmd/lotus-sim/upgrade.go @@ -72,7 +72,7 @@ var upgradeSetCommand = &cli.Command{ return fmt.Errorf("expected 2 arguments") } nvString := args.Get(0) - networkVersion, err := strconv.ParseInt(nvString, 10, 64) + networkVersion, err := strconv.ParseUint(nvString, 10, 32) if err != nil { return fmt.Errorf("failed to parse network version %q: %w", nvString, err) } From 0879ac496f7daa01e42b037d36f0b9687532be2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Mon, 21 Jun 2021 18:21:10 +0100 Subject: [PATCH 180/257] uncomment lines in TestDealCyclesConcurrent. --- itests/deals_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/itests/deals_test.go b/itests/deals_test.go index 3f3c853aa..96b81be1f 100644 --- a/itests/deals_test.go +++ b/itests/deals_test.go @@ -38,7 +38,7 @@ func TestDealCyclesConcurrent(t *testing.T) { startEpoch := abi.ChainEpoch(2 << 12) runTest := func(t *testing.T, n int, fastRetrieval bool, carExport bool) { - client, miner, ens := kit.EnsembleMinimal(t) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) ens.InterconnectAll().BeginMining(blockTime) dh := kit.NewDealHarness(t, client, miner) @@ -54,9 +54,9 @@ func TestDealCyclesConcurrent(t *testing.T) { for _, n := range cycles { n := n ns := fmt.Sprintf("%d", n) - //t.Run(ns+"-fastretrieval-CAR", func(t *testing.T) { runTest(t, n, true, true) }) - //t.Run(ns+"-fastretrieval-NoCAR", func(t *testing.T) { runTest(t, n, true, false) }) - //t.Run(ns+"-stdretrieval-CAR", func(t *testing.T) { runTest(t, n, true, false) }) + t.Run(ns+"-fastretrieval-CAR", func(t *testing.T) { runTest(t, n, true, true) }) + t.Run(ns+"-fastretrieval-NoCAR", func(t *testing.T) { runTest(t, n, true, false) }) + t.Run(ns+"-stdretrieval-CAR", func(t *testing.T) { runTest(t, n, true, false) }) t.Run(ns+"-stdretrieval-NoCAR", func(t *testing.T) { runTest(t, n, false, false) }) } } From 00fa3878d445e6cee86a42703444b5ce39885103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Mon, 21 Jun 2021 18:22:08 +0100 Subject: [PATCH 181/257] remove debug statement. --- itests/kit/node_miner.go | 1 - 1 file changed, 1 deletion(-) diff --git a/itests/kit/node_miner.go b/itests/kit/node_miner.go index ec38c4303..d3f0d2e3c 100644 --- a/itests/kit/node_miner.go +++ b/itests/kit/node_miner.go @@ -107,7 +107,6 @@ func (tm *TestMiner) StartPledge(ctx context.Context, n, existing int, blockNoti } func (tm *TestMiner) FlushSealingBatches(ctx context.Context) { - fmt.Println("FLUSH SEALING BATCHES***************") pcb, err := tm.StorageMiner.SectorPreCommitFlush(ctx) require.NoError(tm.t, err) if pcb != nil { From 70929a99e6dfdf5bfa2376e44739c21da44d1886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Mon, 21 Jun 2021 18:24:39 +0100 Subject: [PATCH 182/257] speed up test. --- itests/deals_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/itests/deals_test.go b/itests/deals_test.go index 96b81be1f..f35f10e2a 100644 --- a/itests/deals_test.go +++ b/itests/deals_test.go @@ -123,7 +123,7 @@ func TestDealsWithSealingAndRPC(t *testing.T) { func TestQuotePriceForUnsealedRetrieval(t *testing.T) { var ( ctx = context.Background() - blocktime = time.Second + blocktime = 10 * time.Millisecond ) kit.QuietMiningLogs() From c22e10c4a4dc5a21867718c1ddb8fabbcc708fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Mon, 21 Jun 2021 18:29:25 +0100 Subject: [PATCH 183/257] use mock proofs in TestQuotePriceForUnsealedRetrieval. --- itests/deals_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/itests/deals_test.go b/itests/deals_test.go index f35f10e2a..e7cadc173 100644 --- a/itests/deals_test.go +++ b/itests/deals_test.go @@ -128,7 +128,7 @@ func TestQuotePriceForUnsealedRetrieval(t *testing.T) { kit.QuietMiningLogs() - client, miner, ens := kit.EnsembleMinimal(t) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) ens.InterconnectAll().BeginMining(blocktime) var ( From ffb63a93ff47073782794eb579b3d6ad77ad528c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 21 Jun 2021 09:09:06 -0700 Subject: [PATCH 184/257] fix(lotus-sim): make 'fund' easier to understand --- cmd/lotus-sim/simulation/stages/funding_stage.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/cmd/lotus-sim/simulation/stages/funding_stage.go b/cmd/lotus-sim/simulation/stages/funding_stage.go index a0d9f4a22..f57f85293 100644 --- a/cmd/lotus-sim/simulation/stages/funding_stage.go +++ b/cmd/lotus-sim/simulation/stages/funding_stage.go @@ -81,13 +81,15 @@ func (fs *FundingStage) SendAndFund(bb *blockbuilder.BlockBuilder, msg *types.Me return res, err } -func (fs *FundingStage) fund(bb *blockbuilder.BlockBuilder, target address.Address, times int) error { +// fund funds the target actor with 'TargetFunds << shift' FIL. The "shift" parameter allows us to +// keep doubling the amount until the intended operation succeeds. +func (fs *FundingStage) fund(bb *blockbuilder.BlockBuilder, target address.Address, shift int) error { amt := TargetFunds - if times > 0 { - if times >= 8 { - times = 8 // cap + if shift > 0 { + if shift >= 8 { + shift = 8 // cap } - amt = big.Lsh(amt, uint(times)) + amt = big.Lsh(amt, uint(shift)) } _, err := bb.PushMessage(&types.Message{ From: fs.fundAccount, From b5f91487487167222db6862c73b5c2143d0d97f7 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 21 Jun 2021 09:12:00 -0700 Subject: [PATCH 185/257] build(lotus-sim): add a makefile target --- .gitignore | 1 + Makefile | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index eddee0590..467f315b8 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ /lotus-health /lotus-chainwatch /lotus-shed +/lotus-sim /lotus-pond /lotus-townhall /lotus-fountain diff --git a/Makefile b/Makefile index d20343f55..7522706fc 100644 --- a/Makefile +++ b/Makefile @@ -234,6 +234,12 @@ BINS+=tvx install-chainwatch: lotus-chainwatch install -C ./lotus-chainwatch /usr/local/bin/lotus-chainwatch +lotus-sim: $(BUILD_DEPS) + rm -f lotus-sim + go build $(GOFLAGS) -o lotus-sim ./cmd/lotus-sim +.PHONY: lotus-sim +BINS+=lotus-sim + # SYSTEMD install-daemon-service: install-daemon From 80eba1069ad1bd0ef49d4d5bc0a5b93af74d4d3b Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 21 Jun 2021 11:23:48 -0700 Subject: [PATCH 186/257] feat(lotus-sim): NewNode to construct a node without a repo --- cmd/lotus-sim/simulation/node.go | 59 ++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go index f97808eef..eb4e62c8a 100644 --- a/cmd/lotus-sim/simulation/node.go +++ b/cmd/lotus-sim/simulation/node.go @@ -2,7 +2,6 @@ package simulation import ( "context" - "io" "strings" "go.uber.org/multierr" @@ -23,55 +22,63 @@ import ( // Node represents the local lotus node, or at least the part of it we care about. type Node struct { - Repo repo.LockedRepo + repo repo.LockedRepo Blockstore blockstore.Blockstore MetadataDS datastore.Batching Chainstore *store.ChainStore } // OpenNode opens the local lotus node for writing. This will fail if the node is online. -func OpenNode(ctx context.Context, path string) (*Node, error) { - var node Node +func OpenNode(ctx context.Context, path string) (node *Node, _err error) { r, err := repo.NewFS(path) if err != nil { return nil, err } - node.Repo, err = r.Lock(repo.FullNode) + lr, err := r.Lock(repo.FullNode) + if err != nil { + return nil, err + } + defer func() { + if _err != nil { + lr.Close() + } + }() + + bs, err := lr.Blockstore(ctx, repo.UniversalBlockstore) if err != nil { - _ = node.Close() return nil, err } - node.Blockstore, err = node.Repo.Blockstore(ctx, repo.UniversalBlockstore) + ds, err := lr.Datastore(ctx, "/metadata") if err != nil { - _ = node.Close() return nil, err } - node.MetadataDS, err = node.Repo.Datastore(ctx, "/metadata") - if err != nil { - _ = node.Close() - return nil, err - } + node = NewNode(bs, ds) + node.repo = lr - node.Chainstore = store.NewChainStore(node.Blockstore, node.Blockstore, node.MetadataDS, vm.Syscalls(mock.Verifier), nil) - return &node, nil + return node, nil } -// Close cleanly close the node. Please call this on shutdown to make sure everything is flushed. +// NewNode will construct a new simulation node from a datastore (used to store simulation +// configuration) and a blockstore (chain + state). +func NewNode(bs blockstore.Blockstore, ds datastore.Batching) *Node { + return &Node{ + Chainstore: store.NewChainStore(bs, bs, ds, vm.Syscalls(mock.Verifier), nil), + MetadataDS: ds, + Blockstore: bs, + } +} + +// Close cleanly close the repo. Please call this on shutdown to make sure everything is flushed. +// +// This function is a no-op when the node is manually constructed with `NewNode`. func (nd *Node) Close() error { - var err error - if closer, ok := nd.Blockstore.(io.Closer); ok && closer != nil { - err = multierr.Append(err, closer.Close()) + if nd.repo != nil { + return nd.repo.Close() } - if nd.MetadataDS != nil { - err = multierr.Append(err, nd.MetadataDS.Close()) - } - if nd.Repo != nil { - err = multierr.Append(err, nd.Repo.Close()) - } - return err + return nil } // LoadSim loads From c532b1b819b3452062bc6552701ba58f87f12402 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 21 Jun 2021 11:25:03 -0700 Subject: [PATCH 187/257] fix(lotus-sim): remove unused field and function --- cmd/lotus-sim/simulation/simulation.go | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index 18ccf1c0c..c75be3261 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -16,7 +16,6 @@ import ( blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" - "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/stages" @@ -80,7 +79,6 @@ type Simulation struct { start *types.TipSet // head - st *state.StateTree head *types.TipSet stages []stages.Stage @@ -113,22 +111,6 @@ func (sim *Simulation) saveConfig() error { return sim.MetadataDS.Put(sim.key("config"), buf) } -// stateTree returns the current state-tree for the current head, computing the tipset if necessary. -// The state-tree is cached until the head is changed. -func (sim *Simulation) stateTree(ctx context.Context) (*state.StateTree, error) { - if sim.st == nil { - st, _, err := sim.StateManager.TipSetState(ctx, sim.head) - if err != nil { - return nil, err - } - sim.st, err = sim.StateManager.StateTree(st) - if err != nil { - return nil, err - } - } - return sim.st, nil -} - var simulationPrefix = datastore.NewKey("/simulation") // key returns the the key in the form /simulation//. For example, @@ -183,7 +165,6 @@ func (sim *Simulation) SetHead(head *types.TipSet) error { if err := sim.storeNamedTipSet("head", head); err != nil { return err } - sim.st = nil // we'll compute this on-demand. sim.head = head return nil } From b7c36bc02cf258c04458afddd367a7ad779028cb Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 21 Jun 2021 11:32:19 -0700 Subject: [PATCH 188/257] fix(lotus-sim): make NewNode take a repo This is primarily for testing, so we can just pass an in-memory repo. --- cmd/lotus-sim/simulation/node.go | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go index eb4e62c8a..acd63955d 100644 --- a/cmd/lotus-sim/simulation/node.go +++ b/cmd/lotus-sim/simulation/node.go @@ -29,12 +29,17 @@ type Node struct { } // OpenNode opens the local lotus node for writing. This will fail if the node is online. -func OpenNode(ctx context.Context, path string) (node *Node, _err error) { +func OpenNode(ctx context.Context, path string) (*Node, error) { r, err := repo.NewFS(path) if err != nil { return nil, err } + return NewNode(ctx, r) +} + +// NewNode constructs a new node from the given repo. +func NewNode(ctx context.Context, r repo.Repo) (nd *Node, _err error) { lr, err := r.Lock(repo.FullNode) if err != nil { return nil, err @@ -54,26 +59,15 @@ func OpenNode(ctx context.Context, path string) (node *Node, _err error) { if err != nil { return nil, err } - - node = NewNode(bs, ds) - node.repo = lr - - return node, nil -} - -// NewNode will construct a new simulation node from a datastore (used to store simulation -// configuration) and a blockstore (chain + state). -func NewNode(bs blockstore.Blockstore, ds datastore.Batching) *Node { return &Node{ + repo: lr, Chainstore: store.NewChainStore(bs, bs, ds, vm.Syscalls(mock.Verifier), nil), MetadataDS: ds, Blockstore: bs, - } + }, err } // Close cleanly close the repo. Please call this on shutdown to make sure everything is flushed. -// -// This function is a no-op when the node is manually constructed with `NewNode`. func (nd *Node) Close() error { if nd.repo != nil { return nd.repo.Close() From f3b6f8de1a54b648d46965a2ab9452dffcfd4d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Mon, 21 Jun 2021 19:35:47 +0100 Subject: [PATCH 189/257] add ability to ignore worker resources when scheduling. --- extern/sector-storage/manager.go | 4 ++++ extern/sector-storage/sched.go | 19 +++++++++-------- extern/sector-storage/storiface/worker.go | 7 ++++++- extern/sector-storage/worker_local.go | 25 +++++++++++++++-------- itests/kit/node_builder.go | 12 +++++++++++ 5 files changed, 49 insertions(+), 18 deletions(-) diff --git a/extern/sector-storage/manager.go b/extern/sector-storage/manager.go index 51558aaad..b883a174f 100644 --- a/extern/sector-storage/manager.go +++ b/extern/sector-storage/manager.go @@ -96,6 +96,10 @@ type SealerConfig struct { AllowPreCommit2 bool AllowCommit bool AllowUnseal bool + + // IgnoreResourceFiltering instructs the system to ignore available + // resources when assigning tasks to the local worker. + IgnoreResourceFiltering bool } type StorageAuth http.Header diff --git a/extern/sector-storage/sched.go b/extern/sector-storage/sched.go index 61411081a..f70921f91 100644 --- a/extern/sector-storage/sched.go +++ b/extern/sector-storage/sched.go @@ -349,24 +349,24 @@ func (sh *scheduler) trySched() { defer sh.workersLk.RUnlock() windowsLen := len(sh.openWindows) - queuneLen := sh.schedQueue.Len() + queueLen := sh.schedQueue.Len() - log.Debugf("SCHED %d queued; %d open windows", queuneLen, windowsLen) + log.Debugf("SCHED %d queued; %d open windows", queueLen, windowsLen) - if windowsLen == 0 || queuneLen == 0 { + if windowsLen == 0 || queueLen == 0 { // nothing to schedule on return } windows := make([]schedWindow, windowsLen) - acceptableWindows := make([][]int, queuneLen) + acceptableWindows := make([][]int, queueLen) // Step 1 throttle := make(chan struct{}, windowsLen) var wg sync.WaitGroup - wg.Add(queuneLen) - for i := 0; i < queuneLen; i++ { + wg.Add(queueLen) + for i := 0; i < queueLen; i++ { throttle <- struct{}{} go func(sqi int) { @@ -393,7 +393,8 @@ func (sh *scheduler) trySched() { } // TODO: allow bigger windows - if !windows[wnd].allocated.canHandleRequest(needRes, windowRequest.worker, "schedAcceptable", worker.info.Resources) { + ignoringResources := worker.info.IgnoreResources + if !ignoringResources && !windows[wnd].allocated.canHandleRequest(needRes, windowRequest.worker, "schedAcceptable", worker.info.Resources) { continue } @@ -451,9 +452,9 @@ func (sh *scheduler) trySched() { // Step 2 scheduled := 0 - rmQueue := make([]int, 0, queuneLen) + rmQueue := make([]int, 0, queueLen) - for sqi := 0; sqi < queuneLen; sqi++ { + for sqi := 0; sqi < queueLen; sqi++ { task := (*sh.schedQueue)[sqi] needRes := ResourceTable[task.taskType][task.sector.ProofType] diff --git a/extern/sector-storage/storiface/worker.go b/extern/sector-storage/storiface/worker.go index d3f4a2cd1..d1373f4c5 100644 --- a/extern/sector-storage/storiface/worker.go +++ b/extern/sector-storage/storiface/worker.go @@ -18,7 +18,12 @@ import ( type WorkerInfo struct { Hostname string - Resources WorkerResources + // IgnoreResources indicates whether the worker's available resources should + // be used ignored (true) or used (false) for the purposes of scheduling and + // task assignment. Only supported on local workers. Used for testing. + // Default should be false (zero value, i.e. resources taken into account). + IgnoreResources bool + Resources WorkerResources } type WorkerResources struct { diff --git a/extern/sector-storage/worker_local.go b/extern/sector-storage/worker_local.go index 2bb0f8300..3e63f8659 100644 --- a/extern/sector-storage/worker_local.go +++ b/extern/sector-storage/worker_local.go @@ -20,7 +20,7 @@ import ( ffi "github.com/filecoin-project/filecoin-ffi" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-statestore" - storage "github.com/filecoin-project/specs-storage/storage" + "github.com/filecoin-project/specs-storage/storage" "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" "github.com/filecoin-project/lotus/extern/sector-storage/sealtasks" @@ -33,6 +33,11 @@ var pathTypes = []storiface.SectorFileType{storiface.FTUnsealed, storiface.FTSea type WorkerConfig struct { TaskTypes []sealtasks.TaskType NoSwap bool + + // IgnoreResourceFiltering enables task distribution to happen on this + // worker regardless of its currently available resources. Used in testing + // with the local worker. + IgnoreResourceFiltering bool } // used do provide custom proofs impl (mostly used in testing) @@ -46,6 +51,9 @@ type LocalWorker struct { executor ExecutorFunc noSwap bool + // see equivalent field on WorkerConfig. + ignoreResources bool + ct *workerCallTracker acceptTasks map[sealtasks.TaskType]struct{} running sync.WaitGroup @@ -71,12 +79,12 @@ func newLocalWorker(executor ExecutorFunc, wcfg WorkerConfig, store stores.Store ct: &workerCallTracker{ st: cst, }, - acceptTasks: acceptTasks, - executor: executor, - noSwap: wcfg.NoSwap, - - session: uuid.New(), - closing: make(chan struct{}), + acceptTasks: acceptTasks, + executor: executor, + noSwap: wcfg.NoSwap, + ignoreResources: wcfg.IgnoreResourceFiltering, + session: uuid.New(), + closing: make(chan struct{}), } if w.executor == nil { @@ -501,7 +509,8 @@ func (l *LocalWorker) Info(context.Context) (storiface.WorkerInfo, error) { } return storiface.WorkerInfo{ - Hostname: hostname, + Hostname: hostname, + IgnoreResources: l.ignoreResources, Resources: storiface.WorkerResources{ MemPhysical: mem.Total, MemSwap: memSwap, diff --git a/itests/kit/node_builder.go b/itests/kit/node_builder.go index 3780a7669..75cebf6bf 100644 --- a/itests/kit/node_builder.go +++ b/itests/kit/node_builder.go @@ -12,6 +12,7 @@ import ( "time" "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/lotus/node/config" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" @@ -127,6 +128,12 @@ func CreateTestStorageNode(ctx context.Context, t *testing.T, waddr address.Addr node.Override(new(v1api.FullNode), tnd), node.Override(new(*lotusminer.Miner), lotusminer.NewTestMiner(mineBlock, act)), + node.Override(new(*sectorstorage.SealerConfig), func() *sectorstorage.SealerConfig { + scfg := config.DefaultStorageMiner() + scfg.Storage.IgnoreResourceFiltering = true + return &scfg.Storage + }), + opts, ) if err != nil { @@ -532,6 +539,11 @@ func mockMinerBuilderOpts(t *testing.T, fullOpts []FullNodeOpts, storage []Stora node.Override(new(sectorstorage.SectorManager), node.From(new(*mock.SectorMgr))), node.Override(new(sectorstorage.Unsealer), node.From(new(*mock.SectorMgr))), node.Override(new(sectorstorage.PieceProvider), node.From(new(*mock.SectorMgr))), + node.Override(new(*sectorstorage.SealerConfig), func() *sectorstorage.SealerConfig { + scfg := config.DefaultStorageMiner() + scfg.Storage.IgnoreResourceFiltering = true + return &scfg.Storage + }), node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), node.Override(new(ffiwrapper.Prover), mock.MockProver), From b6147fb27f58f2ebb70a78a08874c4d4ec0f68f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Mon, 21 Jun 2021 20:28:15 +0100 Subject: [PATCH 190/257] extern/storage: retype resource filtering strategy to enum. --- extern/sector-storage/manager.go | 31 +++++++++--- extern/sector-storage/sched_test.go | 74 ++++++++++++++++++++--------- itests/kit/node_builder.go | 4 +- node/config/def.go | 3 ++ 4 files changed, 81 insertions(+), 31 deletions(-) diff --git a/extern/sector-storage/manager.go b/extern/sector-storage/manager.go index b883a174f..5bd372db0 100644 --- a/extern/sector-storage/manager.go +++ b/extern/sector-storage/manager.go @@ -87,6 +87,20 @@ type result struct { err error } +// ResourceFilteringStrategy is an enum indicating the kinds of resource +// filtering strategies that can be configured for workers. +type ResourceFilteringStrategy string + +const ( + // ResourceFilteringHardware specifies that available hardware resources + // should be evaluated when scheduling a task against the worker. + ResourceFilteringHardware = ResourceFilteringStrategy("hardware") + + // ResourceFilteringDisabled disables resource filtering against this + // worker. The scheduler may assign any task to this worker. + ResourceFilteringDisabled = ResourceFilteringStrategy("disabled") +) + type SealerConfig struct { ParallelFetchLimit int @@ -97,9 +111,10 @@ type SealerConfig struct { AllowCommit bool AllowUnseal bool - // IgnoreResourceFiltering instructs the system to ignore available - // resources when assigning tasks to the local worker. - IgnoreResourceFiltering bool + // ResourceFiltering instructs the system which resource filtering strategy + // to use when evaluating tasks against this worker. An empty value defaults + // to "hardware". + ResourceFiltering ResourceFilteringStrategy } type StorageAuth http.Header @@ -108,7 +123,6 @@ type WorkerStateStore *statestore.StateStore type ManagerStateStore *statestore.StateStore func New(ctx context.Context, lstor *stores.Local, stor *stores.Remote, ls stores.LocalStorage, si stores.SectorIndex, sc SealerConfig, wss WorkerStateStore, mss ManagerStateStore) (*Manager, error) { - prover, err := ffiwrapper.New(&readonlyProvider{stor: lstor, index: si}) if err != nil { return nil, xerrors.Errorf("creating prover instance: %w", err) @@ -155,9 +169,12 @@ func New(ctx context.Context, lstor *stores.Local, stor *stores.Remote, ls store localTasks = append(localTasks, sealtasks.TTUnseal) } - err = m.AddWorker(ctx, NewLocalWorker(WorkerConfig{ - TaskTypes: localTasks, - }, stor, lstor, si, m, wss)) + wcfg := WorkerConfig{ + IgnoreResourceFiltering: sc.ResourceFiltering == ResourceFilteringHardware, + TaskTypes: localTasks, + } + worker := NewLocalWorker(wcfg, stor, lstor, si, m, wss) + err = m.AddWorker(ctx, worker) if err != nil { return nil, xerrors.Errorf("adding local worker: %w", err) } diff --git a/extern/sector-storage/sched_test.go b/extern/sector-storage/sched_test.go index 63f3de64d..38b3efda9 100644 --- a/extern/sector-storage/sched_test.go +++ b/extern/sector-storage/sched_test.go @@ -38,6 +38,20 @@ func TestWithPriority(t *testing.T) { require.Equal(t, 2222, getPriority(ctx)) } +var decentWorkerResources = storiface.WorkerResources{ + MemPhysical: 128 << 30, + MemSwap: 200 << 30, + MemReserved: 2 << 30, + CPUs: 32, + GPUs: []string{"a GPU"}, +} + +var constrainedWorkerResources = storiface.WorkerResources{ + MemPhysical: 1 << 30, + MemReserved: 2 << 30, + CPUs: 1, +} + type schedTestWorker struct { name string taskTypes map[sealtasks.TaskType]struct{} @@ -45,6 +59,9 @@ type schedTestWorker struct { closed bool session uuid.UUID + + resources storiface.WorkerResources + ignoreResources bool } func (s *schedTestWorker) SealPreCommit1(ctx context.Context, sector storage.SectorRef, ticket abi.SealRandomness, pieces []abi.PieceInfo) (storiface.CallID, error) { @@ -107,18 +124,11 @@ func (s *schedTestWorker) Paths(ctx context.Context) ([]stores.StoragePath, erro return s.paths, nil } -var decentWorkerResources = storiface.WorkerResources{ - MemPhysical: 128 << 30, - MemSwap: 200 << 30, - MemReserved: 2 << 30, - CPUs: 32, - GPUs: []string{"a GPU"}, -} - func (s *schedTestWorker) Info(ctx context.Context) (storiface.WorkerInfo, error) { return storiface.WorkerInfo{ - Hostname: s.name, - Resources: decentWorkerResources, + Hostname: s.name, + IgnoreResources: s.ignoreResources, + Resources: s.resources, }, nil } @@ -137,13 +147,16 @@ func (s *schedTestWorker) Close() error { var _ Worker = &schedTestWorker{} -func addTestWorker(t *testing.T, sched *scheduler, index *stores.Index, name string, taskTypes map[sealtasks.TaskType]struct{}) { +func addTestWorker(t *testing.T, sched *scheduler, index *stores.Index, name string, taskTypes map[sealtasks.TaskType]struct{}, resources storiface.WorkerResources, ignoreResources bool) { w := &schedTestWorker{ name: name, taskTypes: taskTypes, paths: []stores.StoragePath{{ID: "bb-8", Weight: 2, LocalPath: "food", CanSeal: true, CanStore: true}}, session: uuid.New(), + + resources: resources, + ignoreResources: ignoreResources, } for _, path := range w.paths { @@ -169,7 +182,7 @@ func TestSchedStartStop(t *testing.T) { sched := newScheduler() go sched.runSched() - addTestWorker(t, sched, stores.NewIndex(), "fred", nil) + addTestWorker(t, sched, stores.NewIndex(), "fred", nil, decentWorkerResources, false) require.NoError(t, sched.Close(context.TODO())) } @@ -183,6 +196,9 @@ func TestSched(t *testing.T) { type workerSpec struct { name string taskTypes map[sealtasks.TaskType]struct{} + + resources storiface.WorkerResources + ignoreResources bool } noopAction := func(ctx context.Context, w Worker) error { @@ -295,7 +311,7 @@ func TestSched(t *testing.T) { go sched.runSched() for _, worker := range workers { - addTestWorker(t, sched, index, worker.name, worker.taskTypes) + addTestWorker(t, sched, index, worker.name, worker.taskTypes, worker.resources, worker.ignoreResources) } rm := runMeta{ @@ -322,31 +338,45 @@ func TestSched(t *testing.T) { } } + t.Run("constrained-resources-not-scheduled", testFunc([]workerSpec{ + {name: "fred", resources: constrainedWorkerResources, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}}}, + }, []task{ + sched("pc1-1", "fred", 8, sealtasks.TTPreCommit1), + taskNotScheduled("pc1-1"), + })) + + t.Run("constrained-resources-ignored-scheduled", testFunc([]workerSpec{ + {name: "fred", resources: constrainedWorkerResources, ignoreResources: true, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}}}, + }, []task{ + sched("pc1-1", "fred", 8, sealtasks.TTPreCommit1), + taskStarted("pc1-1"), + })) + t.Run("one-pc1", testFunc([]workerSpec{ - {name: "fred", taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}}}, + {name: "fred", resources: decentWorkerResources, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}}}, }, []task{ sched("pc1-1", "fred", 8, sealtasks.TTPreCommit1), taskDone("pc1-1"), })) t.Run("pc1-2workers-1", testFunc([]workerSpec{ - {name: "fred2", taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit2: {}}}, - {name: "fred1", taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}}}, + {name: "fred2", resources: decentWorkerResources, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit2: {}}}, + {name: "fred1", resources: decentWorkerResources, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}}}, }, []task{ sched("pc1-1", "fred1", 8, sealtasks.TTPreCommit1), taskDone("pc1-1"), })) t.Run("pc1-2workers-2", testFunc([]workerSpec{ - {name: "fred1", taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}}}, - {name: "fred2", taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit2: {}}}, + {name: "fred1", resources: decentWorkerResources, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}}}, + {name: "fred2", resources: decentWorkerResources, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit2: {}}}, }, []task{ sched("pc1-1", "fred1", 8, sealtasks.TTPreCommit1), taskDone("pc1-1"), })) t.Run("pc1-block-pc2", testFunc([]workerSpec{ - {name: "fred", taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}, sealtasks.TTPreCommit2: {}}}, + {name: "fred", resources: decentWorkerResources, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}, sealtasks.TTPreCommit2: {}}}, }, []task{ sched("pc1", "fred", 8, sealtasks.TTPreCommit1), taskStarted("pc1"), @@ -359,7 +389,7 @@ func TestSched(t *testing.T) { })) t.Run("pc2-block-pc1", testFunc([]workerSpec{ - {name: "fred", taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}, sealtasks.TTPreCommit2: {}}}, + {name: "fred", resources: decentWorkerResources, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}, sealtasks.TTPreCommit2: {}}}, }, []task{ sched("pc2", "fred", 8, sealtasks.TTPreCommit2), taskStarted("pc2"), @@ -372,7 +402,7 @@ func TestSched(t *testing.T) { })) t.Run("pc1-batching", testFunc([]workerSpec{ - {name: "fred", taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}}}, + {name: "fred", resources: decentWorkerResources, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}}}, }, []task{ sched("t1", "fred", 8, sealtasks.TTPreCommit1), taskStarted("t1"), @@ -459,7 +489,7 @@ func TestSched(t *testing.T) { // run this one a bunch of times, it had a very annoying tendency to fail randomly for i := 0; i < 40; i++ { t.Run("pc1-pc2-prio", testFunc([]workerSpec{ - {name: "fred", taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}, sealtasks.TTPreCommit2: {}}}, + {name: "fred", resources: decentWorkerResources, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}, sealtasks.TTPreCommit2: {}}}, }, []task{ // fill queues twoPC1("w0", 0, taskStarted), diff --git a/itests/kit/node_builder.go b/itests/kit/node_builder.go index 75cebf6bf..3306d427f 100644 --- a/itests/kit/node_builder.go +++ b/itests/kit/node_builder.go @@ -130,7 +130,7 @@ func CreateTestStorageNode(ctx context.Context, t *testing.T, waddr address.Addr node.Override(new(*sectorstorage.SealerConfig), func() *sectorstorage.SealerConfig { scfg := config.DefaultStorageMiner() - scfg.Storage.IgnoreResourceFiltering = true + scfg.Storage.ResourceFiltering = sectorstorage.ResourceFilteringDisabled return &scfg.Storage }), @@ -541,7 +541,7 @@ func mockMinerBuilderOpts(t *testing.T, fullOpts []FullNodeOpts, storage []Stora node.Override(new(sectorstorage.PieceProvider), node.From(new(*mock.SectorMgr))), node.Override(new(*sectorstorage.SealerConfig), func() *sectorstorage.SealerConfig { scfg := config.DefaultStorageMiner() - scfg.Storage.IgnoreResourceFiltering = true + scfg.Storage.ResourceFiltering = sectorstorage.ResourceFilteringDisabled return &scfg.Storage }), diff --git a/node/config/def.go b/node/config/def.go index 700a3f94f..a00ea7307 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -342,6 +342,9 @@ func DefaultStorageMiner() *StorageMiner { // Default to 10 - tcp should still be able to figure this out, and // it's the ratio between 10gbit / 1gbit ParallelFetchLimit: 10, + + // By default use the hardware resource filtering strategy. + ResourceFiltering: sectorstorage.ResourceFilteringHardware, }, Dealmaking: DealmakingConfig{ From 83839362c58edcccf0da2ba0c1c572dd19795800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Mon, 21 Jun 2021 20:35:51 +0100 Subject: [PATCH 191/257] fix boolean condition. --- extern/sector-storage/manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/sector-storage/manager.go b/extern/sector-storage/manager.go index 5bd372db0..136c00252 100644 --- a/extern/sector-storage/manager.go +++ b/extern/sector-storage/manager.go @@ -170,7 +170,7 @@ func New(ctx context.Context, lstor *stores.Local, stor *stores.Remote, ls store } wcfg := WorkerConfig{ - IgnoreResourceFiltering: sc.ResourceFiltering == ResourceFilteringHardware, + IgnoreResourceFiltering: sc.ResourceFiltering == ResourceFilteringDisabled, TaskTypes: localTasks, } worker := NewLocalWorker(wcfg, stor, lstor, si, m, wss) From 59eab2df2593a0d277312b1a01e3a65c2b4752c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Mon, 21 Jun 2021 20:49:16 +0100 Subject: [PATCH 192/257] move scheduling filtering logic down. --- extern/sector-storage/sched.go | 9 ++++----- extern/sector-storage/sched_resources.go | 15 +++++++++++---- extern/sector-storage/sched_worker.go | 6 +++--- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/extern/sector-storage/sched.go b/extern/sector-storage/sched.go index f70921f91..aabf6f0ce 100644 --- a/extern/sector-storage/sched.go +++ b/extern/sector-storage/sched.go @@ -393,8 +393,7 @@ func (sh *scheduler) trySched() { } // TODO: allow bigger windows - ignoringResources := worker.info.IgnoreResources - if !ignoringResources && !windows[wnd].allocated.canHandleRequest(needRes, windowRequest.worker, "schedAcceptable", worker.info.Resources) { + if !windows[wnd].allocated.canHandleRequest(needRes, windowRequest.worker, "schedAcceptable", worker.info) { continue } @@ -461,18 +460,18 @@ func (sh *scheduler) trySched() { selectedWindow := -1 for _, wnd := range acceptableWindows[task.indexHeap] { wid := sh.openWindows[wnd].worker - wr := sh.workers[wid].info.Resources + info := sh.workers[wid].info log.Debugf("SCHED try assign sqi:%d sector %d to window %d", sqi, task.sector.ID.Number, wnd) // TODO: allow bigger windows - if !windows[wnd].allocated.canHandleRequest(needRes, wid, "schedAssign", wr) { + if !windows[wnd].allocated.canHandleRequest(needRes, wid, "schedAssign", info) { continue } log.Debugf("SCHED ASSIGNED sqi:%d sector %d task %s to window %d", sqi, task.sector.ID.Number, task.taskType, wnd) - windows[wnd].allocated.add(wr, needRes) + windows[wnd].allocated.add(info.Resources, needRes) // TODO: We probably want to re-sort acceptableWindows here based on new // workerHandle.utilization + windows[wnd].allocated.utilization (workerHandle.utilization is used in all // task selectors, but not in the same way, so need to figure out how to do that in a non-O(n^2 way), and diff --git a/extern/sector-storage/sched_resources.go b/extern/sector-storage/sched_resources.go index 3e359c121..96a1fa863 100644 --- a/extern/sector-storage/sched_resources.go +++ b/extern/sector-storage/sched_resources.go @@ -6,7 +6,7 @@ import ( "github.com/filecoin-project/lotus/extern/sector-storage/storiface" ) -func (a *activeResources) withResources(id WorkerID, wr storiface.WorkerResources, r Resources, locker sync.Locker, cb func() error) error { +func (a *activeResources) withResources(id WorkerID, wr storiface.WorkerInfo, r Resources, locker sync.Locker, cb func() error) error { for !a.canHandleRequest(r, id, "withResources", wr) { if a.cond == nil { a.cond = sync.NewCond(locker) @@ -14,11 +14,11 @@ func (a *activeResources) withResources(id WorkerID, wr storiface.WorkerResource a.cond.Wait() } - a.add(wr, r) + a.add(wr.Resources, r) err := cb() - a.free(wr, r) + a.free(wr.Resources, r) if a.cond != nil { a.cond.Broadcast() } @@ -44,8 +44,15 @@ func (a *activeResources) free(wr storiface.WorkerResources, r Resources) { a.memUsedMax -= r.MaxMemory } -func (a *activeResources) canHandleRequest(needRes Resources, wid WorkerID, caller string, res storiface.WorkerResources) bool { +// canHandleRequest evaluates if the worker has enough available resources to +// handle the request. +func (a *activeResources) canHandleRequest(needRes Resources, wid WorkerID, caller string, info storiface.WorkerInfo) bool { + if info.IgnoreResources { + // shortcircuit; if this worker is ignoring resources, it can always handle the request. + return true + } + res := info.Resources // TODO: dedupe needRes.BaseMinMemory per task type (don't add if that task is already running) minNeedMem := res.MemReserved + a.memUsedMin + needRes.MinMemory + needRes.BaseMinMemory if minNeedMem > res.MemPhysical { diff --git a/extern/sector-storage/sched_worker.go b/extern/sector-storage/sched_worker.go index 4e18e5c6f..7bc1affc3 100644 --- a/extern/sector-storage/sched_worker.go +++ b/extern/sector-storage/sched_worker.go @@ -296,7 +296,7 @@ func (sw *schedWorker) workerCompactWindows() { for ti, todo := range window.todo { needRes := ResourceTable[todo.taskType][todo.sector.ProofType] - if !lower.allocated.canHandleRequest(needRes, sw.wid, "compactWindows", worker.info.Resources) { + if !lower.allocated.canHandleRequest(needRes, sw.wid, "compactWindows", worker.info) { continue } @@ -352,7 +352,7 @@ assignLoop: worker.lk.Lock() for t, todo := range firstWindow.todo { needRes := ResourceTable[todo.taskType][todo.sector.ProofType] - if worker.preparing.canHandleRequest(needRes, sw.wid, "startPreparing", worker.info.Resources) { + if worker.preparing.canHandleRequest(needRes, sw.wid, "startPreparing", worker.info) { tidx = t break } @@ -424,7 +424,7 @@ func (sw *schedWorker) startProcessingTask(taskDone chan struct{}, req *workerRe } // wait (if needed) for resources in the 'active' window - err = w.active.withResources(sw.wid, w.info.Resources, needRes, &sh.workersLk, func() error { + err = w.active.withResources(sw.wid, w.info, needRes, &sh.workersLk, func() error { w.lk.Lock() w.preparing.free(w.info.Resources, needRes) w.lk.Unlock() From 684cce198fdff0e876487102bf11ecbea0035ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Mon, 21 Jun 2021 20:49:24 +0100 Subject: [PATCH 193/257] add a unit test. --- extern/sector-storage/sched_test.go | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/extern/sector-storage/sched_test.go b/extern/sector-storage/sched_test.go index 38b3efda9..fbc4d83ee 100644 --- a/extern/sector-storage/sched_test.go +++ b/extern/sector-storage/sched_test.go @@ -338,18 +338,15 @@ func TestSched(t *testing.T) { } } - t.Run("constrained-resources-not-scheduled", testFunc([]workerSpec{ - {name: "fred", resources: constrainedWorkerResources, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}}}, + // checks behaviour with workers with constrained resources + // the first one is not ignoring resource constraints, so we assign to the second worker, who is + t.Run("constrained-resources", testFunc([]workerSpec{ + {name: "fred1", resources: constrainedWorkerResources, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}}}, + {name: "fred2", resources: constrainedWorkerResources, ignoreResources: true, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}}}, }, []task{ - sched("pc1-1", "fred", 8, sealtasks.TTPreCommit1), - taskNotScheduled("pc1-1"), - })) - - t.Run("constrained-resources-ignored-scheduled", testFunc([]workerSpec{ - {name: "fred", resources: constrainedWorkerResources, ignoreResources: true, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}}}, - }, []task{ - sched("pc1-1", "fred", 8, sealtasks.TTPreCommit1), + sched("pc1-1", "fred2", 8, sealtasks.TTPreCommit1), taskStarted("pc1-1"), + taskDone("pc1-1"), })) t.Run("one-pc1", testFunc([]workerSpec{ From c0a8a9f5b5a03817586a90db271db8cbaf88589b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Mon, 21 Jun 2021 20:52:59 +0100 Subject: [PATCH 194/257] make gen. --- build/openrpc/miner.json.gz | Bin 8088 -> 8104 bytes build/openrpc/worker.json.gz | Bin 2497 -> 2514 bytes documentation/en/api-v0-methods-miner.md | 1 + documentation/en/api-v0-methods-worker.md | 1 + 4 files changed, 2 insertions(+) diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 1d8ff7579b9810276be694c1bf01a4af2374c10b..b7c053d81c3189f4eb1635bef0b1ba56a9b63bf4 100644 GIT binary patch delta 8052 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 COu8!o delta 8053 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 D0yMtE diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index 3c4fa4f701624af1b7af35092668b932227e41b9..411964f6549af607d0179d0709dbd490dbd836a8 100644 GIT binary patch literal 2514 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 L(#d+JPI3SMs8`^6 diff --git a/documentation/en/api-v0-methods-miner.md b/documentation/en/api-v0-methods-miner.md index 388213666..496f63a08 100644 --- a/documentation/en/api-v0-methods-miner.md +++ b/documentation/en/api-v0-methods-miner.md @@ -2205,6 +2205,7 @@ Response: "ef8d99a2-6865-4189-8ffa-9fef0f806eee": { "Info": { "Hostname": "host", + "IgnoreResources": false, "Resources": { "MemPhysical": 274877906944, "MemSwap": 128849018880, diff --git a/documentation/en/api-v0-methods-worker.md b/documentation/en/api-v0-methods-worker.md index 925f8934b..c620113f4 100644 --- a/documentation/en/api-v0-methods-worker.md +++ b/documentation/en/api-v0-methods-worker.md @@ -89,6 +89,7 @@ Response: ```json { "Hostname": "string value", + "IgnoreResources": true, "Resources": { "MemPhysical": 42, "MemSwap": 42, From 4fcd0b7acaf0437a0d52a713273ae3dfef74bc63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Mon, 21 Jun 2021 21:20:45 +0100 Subject: [PATCH 195/257] disable resource filtering on scheduler. --- itests/deals_test.go | 2 +- itests/kit/ensemble.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/itests/deals_test.go b/itests/deals_test.go index e7cadc173..f35f10e2a 100644 --- a/itests/deals_test.go +++ b/itests/deals_test.go @@ -128,7 +128,7 @@ func TestQuotePriceForUnsealedRetrieval(t *testing.T) { kit.QuietMiningLogs() - client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) + client, miner, ens := kit.EnsembleMinimal(t) ens.InterconnectAll().BeginMining(blocktime) var ( diff --git a/itests/kit/ensemble.go b/itests/kit/ensemble.go index 0955be11b..62b35a269 100644 --- a/itests/kit/ensemble.go +++ b/itests/kit/ensemble.go @@ -444,10 +444,10 @@ func (n *Ensemble) Start() *Ensemble { // disable resource filtering so that local worker gets assigned tasks // regardless of system pressure. - node.Override(new(*sectorstorage.SealerConfig), func() *sectorstorage.SealerConfig { + node.Override(new(sectorstorage.SealerConfig), func() sectorstorage.SealerConfig { scfg := config.DefaultStorageMiner() scfg.Storage.ResourceFiltering = sectorstorage.ResourceFilteringDisabled - return &scfg.Storage + return scfg.Storage }), } From e438ef99f8c168799e056caac3c1292cab22f322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Mon, 21 Jun 2021 21:25:55 +0100 Subject: [PATCH 196/257] fix merge error in window post dispute tests. --- itests/deals_test.go | 1 + itests/wdpost_dispute_test.go | 7 +------ 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/itests/deals_test.go b/itests/deals_test.go index f35f10e2a..ab7f05d3e 100644 --- a/itests/deals_test.go +++ b/itests/deals_test.go @@ -50,6 +50,7 @@ func TestDealCyclesConcurrent(t *testing.T) { }) } + // TOOD: add 2, 4, 8, more when this graphsync issue is fixed: https://github.com/ipfs/go-graphsync/issues/175# cycles := []int{1} for _, n := range cycles { n := n diff --git a/itests/wdpost_dispute_test.go b/itests/wdpost_dispute_test.go index 742972fc6..bf1a01e60 100644 --- a/itests/wdpost_dispute_test.go +++ b/itests/wdpost_dispute_test.go @@ -335,11 +335,6 @@ func submitBadProof( 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 @@ -374,7 +369,7 @@ func submitBadProof( Method: minerActor.Methods.SubmitWindowedPoSt, Params: enc, Value: types.NewInt(0), - From: from, + From: owner, } sm, err := client.MpoolPushMessage(ctx, msg, nil) if err != nil { From 502e104e6aa5f964d33eeffc57400a83605601b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Mon, 21 Jun 2021 21:35:39 +0100 Subject: [PATCH 197/257] typo. --- itests/deals_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/itests/deals_test.go b/itests/deals_test.go index ab7f05d3e..d4da44c43 100644 --- a/itests/deals_test.go +++ b/itests/deals_test.go @@ -50,7 +50,7 @@ func TestDealCyclesConcurrent(t *testing.T) { }) } - // TOOD: add 2, 4, 8, more when this graphsync issue is fixed: https://github.com/ipfs/go-graphsync/issues/175# + // TODO: add 2, 4, 8, more when this graphsync issue is fixed: https://github.com/ipfs/go-graphsync/issues/175# cycles := []int{1} for _, n := range cycles { n := n From 0e2d06fc398069562adb81421a190a9fd8ad931d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Mon, 21 Jun 2021 23:10:17 +0100 Subject: [PATCH 198/257] switch to http API. --- itests/kit/ensemble.go | 2 +- itests/kit/rpc.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/itests/kit/ensemble.go b/itests/kit/ensemble.go index 62b35a269..136eef0a5 100644 --- a/itests/kit/ensemble.go +++ b/itests/kit/ensemble.go @@ -439,7 +439,7 @@ func (n *Ensemble) Start() *Ensemble { node.MockHost(n.mn), - node.Override(new(v1api.FullNode), m.FullNode), + node.Override(new(v1api.FullNode), m.FullNode.FullNode), node.Override(new(*lotusminer.Miner), lotusminer.NewTestMiner(mineBlock, m.ActorAddr)), // disable resource filtering so that local worker gets assigned tasks diff --git a/itests/kit/rpc.go b/itests/kit/rpc.go index dab45df07..3269d2bea 100644 --- a/itests/kit/rpc.go +++ b/itests/kit/rpc.go @@ -30,7 +30,7 @@ func fullRpc(t *testing.T, f *TestFullNode) *TestFullNode { srv, maddr := CreateRPCServer(t, handler) - cl, stop, err := client.NewFullNodeRPCV1(context.Background(), "ws://"+srv.Listener.Addr().String()+"/rpc/v1", nil) + cl, stop, err := client.NewFullNodeRPCV1(context.Background(), "http://"+srv.Listener.Addr().String()+"/rpc/v1", nil) require.NoError(t, err) t.Cleanup(stop) f.ListenAddr, f.FullNode = maddr, cl @@ -44,7 +44,7 @@ func minerRpc(t *testing.T, m *TestMiner) *TestMiner { srv, maddr := CreateRPCServer(t, handler) - cl, stop, err := client.NewStorageMinerRPCV0(context.Background(), "ws://"+srv.Listener.Addr().String()+"/rpc/v0", nil) + cl, stop, err := client.NewStorageMinerRPCV0(context.Background(), "http://"+srv.Listener.Addr().String()+"/rpc/v0", nil) require.NoError(t, err) t.Cleanup(stop) From bb032b526c740cb86e70eccffbdda75a6aa8ca27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Mon, 21 Jun 2021 23:24:59 +0100 Subject: [PATCH 199/257] switch back to ws API. --- itests/kit/rpc.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/itests/kit/rpc.go b/itests/kit/rpc.go index 3269d2bea..dab45df07 100644 --- a/itests/kit/rpc.go +++ b/itests/kit/rpc.go @@ -30,7 +30,7 @@ func fullRpc(t *testing.T, f *TestFullNode) *TestFullNode { srv, maddr := CreateRPCServer(t, handler) - cl, stop, err := client.NewFullNodeRPCV1(context.Background(), "http://"+srv.Listener.Addr().String()+"/rpc/v1", nil) + cl, stop, err := client.NewFullNodeRPCV1(context.Background(), "ws://"+srv.Listener.Addr().String()+"/rpc/v1", nil) require.NoError(t, err) t.Cleanup(stop) f.ListenAddr, f.FullNode = maddr, cl @@ -44,7 +44,7 @@ func minerRpc(t *testing.T, m *TestMiner) *TestMiner { srv, maddr := CreateRPCServer(t, handler) - cl, stop, err := client.NewStorageMinerRPCV0(context.Background(), "http://"+srv.Listener.Addr().String()+"/rpc/v0", nil) + cl, stop, err := client.NewStorageMinerRPCV0(context.Background(), "ws://"+srv.Listener.Addr().String()+"/rpc/v0", nil) require.NoError(t, err) t.Cleanup(stop) From da789939b198c2391d1ca946406d52f43daac37c Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Tue, 22 Jun 2021 14:33:44 +0200 Subject: [PATCH 200/257] fix: bump blocktime of TestQuotePriceForUnsealedRetrieval to 1 second --- itests/deals_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/itests/deals_test.go b/itests/deals_test.go index d4da44c43..ad9d7d63c 100644 --- a/itests/deals_test.go +++ b/itests/deals_test.go @@ -124,7 +124,7 @@ func TestDealsWithSealingAndRPC(t *testing.T) { func TestQuotePriceForUnsealedRetrieval(t *testing.T) { var ( ctx = context.Background() - blocktime = 10 * time.Millisecond + blocktime = time.Second ) kit.QuietMiningLogs() From 2a58f830c08d60efecbc7ef7c6e262571b7cd8eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Tue, 22 Jun 2021 16:34:30 +0100 Subject: [PATCH 201/257] fix sector_terminate_test.go flakiness. --- itests/sector_terminate_test.go | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/itests/sector_terminate_test.go b/itests/sector_terminate_test.go index 94d438437..1ce55be10 100644 --- a/itests/sector_terminate_test.go +++ b/itests/sector_terminate_test.go @@ -21,15 +21,14 @@ func TestTerminate(t *testing.T) { kit.QuietMiningLogs() - const blocktime = 2 * time.Millisecond - - nSectors := uint64(2) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + var ( + blocktime = 2 * time.Millisecond + nSectors = 2 + ctx = context.Background() + ) opts := kit.ConstructorOpts(kit.LatestActorsAt(-1)) - client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.PresealSectors(nSectors), opts) ens.InterconnectAll().BeginMining(blocktime) maddr, err := miner.ActorAddress(ctx) @@ -41,7 +40,7 @@ func TestTerminate(t *testing.T) { 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)) + require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*uint64(nSectors))) t.Log("Seal a sector") @@ -54,7 +53,7 @@ func TestTerminate(t *testing.T) { di, err := client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) require.NoError(t, err) - waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 2 + waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 20 // 20 is some slack for the proof to be submitted + applied t.Logf("End for head.Height > %d", waitUntil) ts := client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) @@ -66,7 +65,7 @@ func TestTerminate(t *testing.T) { 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)) + require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*uint64(nSectors))) t.Log("Terminate a sector") @@ -113,11 +112,14 @@ loop: time.Sleep(100 * time.Millisecond) } + // need to wait for message to be mined and applied. + time.Sleep(5 * time.Second) + // 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))) + require.Equal(t, types.NewInt(uint64(ssz)*uint64(nSectors-1)), p.MinerPower.RawBytePower) // check in terminated set { @@ -138,7 +140,7 @@ loop: di, err := client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) require.NoError(t, err) - waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 2 + waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 20 // slack like above t.Logf("End for head.Height > %d", waitUntil) ts := client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) t.Logf("Now head.Height = %d", ts.Height()) @@ -147,5 +149,5 @@ loop: require.NoError(t, err) require.Equal(t, p.MinerPower, p.TotalPower) - require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*(nSectors-1))) + require.Equal(t, types.NewInt(uint64(ssz)*uint64(nSectors-1)), p.MinerPower.RawBytePower) } From 71a7270c9891088c20e80af08278e57692ceea89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Tue, 22 Jun 2021 16:34:41 +0100 Subject: [PATCH 202/257] cleanup gateway RPC. --- itests/gateway_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/itests/gateway_test.go b/itests/gateway_test.go index e2329bf77..0f73befa5 100644 --- a/itests/gateway_test.go +++ b/itests/gateway_test.go @@ -288,6 +288,7 @@ func startNodes( var gapi api.Gateway gapi, closer, err = client.NewGatewayRPCV1(ctx, "ws://"+srv.Listener.Addr().String()+"/rpc/v1", nil) require.NoError(t, err) + t.Cleanup(closer) ens.FullNode(&lite, kit.LiteNode(), From 098eb6bfff4729824bea385bd9da4d2f8f10a7e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Tue, 22 Jun 2021 16:35:58 +0100 Subject: [PATCH 203/257] try using bg context on constructor. --- itests/kit/ensemble.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/itests/kit/ensemble.go b/itests/kit/ensemble.go index 136eef0a5..2e04c37c8 100644 --- a/itests/kit/ensemble.go +++ b/itests/kit/ensemble.go @@ -245,8 +245,7 @@ func (n *Ensemble) Miner(miner *TestMiner, full *TestFullNode, opts ...NodeOpt) // Start starts all enrolled nodes. func (n *Ensemble) Start() *Ensemble { - ctx, cancel := context.WithCancel(context.Background()) - n.t.Cleanup(cancel) + ctx := context.Background() var gtempl *genesis.Template if !n.bootstrapped { From 9b2efd5ace6b0c3d7bccb8ea1c93644e0096d422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Tue, 22 Jun 2021 17:07:14 +0100 Subject: [PATCH 204/257] try to deflake window post itests. --- itests/wdpost_dispute_test.go | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/itests/wdpost_dispute_test.go b/itests/wdpost_dispute_test.go index bf1a01e60..24b831df3 100644 --- a/itests/wdpost_dispute_test.go +++ b/itests/wdpost_dispute_test.go @@ -49,26 +49,11 @@ func TestWindowPostDispute(t *testing.T) { Miner(&evilMiner, &client, opts, kit.PresealSectors(0)). Start() - { - 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) // Mine with the _second_ node (the good one). - ens.BeginMining(blocktime, &chainMiner) + ens.InterconnectAll().BeginMining(blocktime, &chainMiner) // Give the chain miner enough sectors to win every block. chainMiner.PledgeSectors(ctx, 10, 0, nil) @@ -84,7 +69,7 @@ func TestWindowPostDispute(t *testing.T) { t.Logf("Running one proving period\n") - waitUntil := di.PeriodStart + di.WPoStProvingPeriod*2 + waitUntil := di.PeriodStart + di.WPoStProvingPeriod*2 + 1 t.Logf("End for head.Height > %d", waitUntil) ts := client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) @@ -257,7 +242,7 @@ func TestWindowPostDisputeFails(t *testing.T) { require.NoError(t, err) t.Log("Running one proving period") - waitUntil := di.PeriodStart + di.WPoStProvingPeriod*2 + waitUntil := di.PeriodStart + di.WPoStProvingPeriod*2 + 1 t.Logf("End for head.Height > %d", waitUntil) ts := client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) From 120dd149793a7baf314c78d80bf7f50994ff610a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Tue, 22 Jun 2021 17:18:07 +0100 Subject: [PATCH 205/257] avoid double close. --- itests/gateway_test.go | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/itests/gateway_test.go b/itests/gateway_test.go index 0f73befa5..6d30bb46e 100644 --- a/itests/gateway_test.go +++ b/itests/gateway_test.go @@ -19,7 +19,6 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api/client" - "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/cli" @@ -35,12 +34,6 @@ const ( maxStateWaitLookbackLimit = stmgr.LookbackNoLimit ) -func init() { - policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1) - policy.SetConsensusMinerMinPower(abi.NewStoragePower(2048)) - policy.SetMinVerifiedDealSize(abi.NewStoragePower(256)) -} - // TestGatewayWalletMsig tests that API calls to wallet and msig can be made on a lite // node that is connected through a gateway to a full API node func TestGatewayWalletMsig(t *testing.T) { @@ -49,7 +42,6 @@ func TestGatewayWalletMsig(t *testing.T) { blocktime := 5 * time.Millisecond ctx := context.Background() nodes := startNodes(ctx, t, blocktime, maxLookbackCap, maxStateWaitLookbackLimit) - defer nodes.closer() lite := nodes.lite full := nodes.full @@ -181,7 +173,6 @@ func TestGatewayMsigCLI(t *testing.T) { blocktime := 5 * time.Millisecond ctx := context.Background() nodes := startNodesWithFunds(ctx, t, blocktime, maxLookbackCap, maxStateWaitLookbackLimit) - defer nodes.closer() lite := nodes.lite runMultisigTests(t, lite) @@ -193,7 +184,6 @@ func TestGatewayDealFlow(t *testing.T) { blocktime := 5 * time.Millisecond ctx := context.Background() nodes := startNodesWithFunds(ctx, t, blocktime, maxLookbackCap, maxStateWaitLookbackLimit) - defer nodes.closer() time.Sleep(5 * time.Second) @@ -216,16 +206,14 @@ func TestGatewayCLIDealFlow(t *testing.T) { blocktime := 5 * time.Millisecond ctx := context.Background() nodes := startNodesWithFunds(ctx, t, blocktime, maxLookbackCap, maxStateWaitLookbackLimit) - defer nodes.closer() kit.RunClientTest(t, cli.Commands, nodes.lite) } type testNodes struct { - lite *kit.TestFullNode - full *kit.TestFullNode - miner *kit.TestMiner - closer jsonrpc.ClientCloser + lite *kit.TestFullNode + full *kit.TestFullNode + miner *kit.TestMiner } func startNodesWithFunds( @@ -298,7 +286,7 @@ func startNodes( ), ).Start().InterconnectAll() - return &testNodes{lite: &lite, full: full, miner: miner, closer: closer} + return &testNodes{lite: &lite, full: full, miner: miner} } func sendFunds(ctx context.Context, fromNode *kit.TestFullNode, fromAddr address.Address, toAddr address.Address, amt types.BigInt) error { From 2e9e1c2330b9dcb3eca7fdd6682aab6a8db98176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Tue, 22 Jun 2021 18:15:38 +0100 Subject: [PATCH 206/257] avoid double BlockMiner instantiation. --- itests/kit/ensemble.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/itests/kit/ensemble.go b/itests/kit/ensemble.go index 2e04c37c8..788aa40c0 100644 --- a/itests/kit/ensemble.go +++ b/itests/kit/ensemble.go @@ -109,6 +109,7 @@ type Ensemble struct { active struct { fullnodes []*TestFullNode miners []*TestMiner + bms map[*TestMiner]*BlockMiner } genesis struct { miners []genesis.Miner @@ -125,6 +126,7 @@ func NewEnsemble(t *testing.T, opts ...EnsembleOpt) *Ensemble { } n := &Ensemble{t: t, options: &options} + n.active.bms = make(map[*TestMiner]*BlockMiner) // add accounts from ensemble options to genesis. for _, acc := range options.accounts { @@ -597,7 +599,14 @@ func (n *Ensemble) BeginMining(blocktime time.Duration, miners ...*TestMiner) [] var bms []*BlockMiner if len(miners) == 0 { - miners = n.active.miners + // no miners have been provided explicitly, instantiate block miners + // for all active miners that aren't still mining. + for _, m := range n.active.miners { + if _, ok := n.active.bms[m]; ok { + continue // skip, already have a block miner + } + miners = append(miners, m) + } } for _, m := range miners { @@ -606,6 +615,8 @@ func (n *Ensemble) BeginMining(blocktime time.Duration, miners ...*TestMiner) [] n.t.Cleanup(bm.Stop) bms = append(bms, bm) + + n.active.bms[m] = bm } return bms From bf82b96fd866e4d34021435094b3ca4d112dd312 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Tue, 22 Jun 2021 18:05:14 +0200 Subject: [PATCH 207/257] Update default fees for aggregates Resolves #6422 Signed-off-by: Jakub Sztandera --- node/config/def.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/node/config/def.go b/node/config/def.go index 177871cc5..a39c12556 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -333,12 +333,12 @@ func DefaultStorageMiner() *StorageMiner { MaxCommitGasFee: types.MustParseFIL("0.05"), MaxPreCommitBatchGasFee: BatchFeeConfig{ - Base: types.MustParseFIL("0.025"), // TODO: update before v1.10.0 - PerSector: types.MustParseFIL("0.025"), // TODO: update before v1.10.0 + Base: types.MustParseFIL("0"), + PerSector: types.MustParseFIL("0.02"), }, MaxCommitBatchGasFee: BatchFeeConfig{ - Base: types.MustParseFIL("0.05"), // TODO: update before v1.10.0 - PerSector: types.MustParseFIL("0.05"), // TODO: update before v1.10.0 + Base: types.MustParseFIL("0"), + PerSector: types.MustParseFIL("0.03"), // enough for 6 agg and 1nFIL base fee }, MaxTerminateGasFee: types.MustParseFIL("0.5"), From 51e51d1b902fa93def7a387f9071c75bcc29b5a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Tue, 22 Jun 2021 20:52:14 +0100 Subject: [PATCH 208/257] dynamic circleci config. --- .circleci/config.yml | 972 +---------------------------------------- .circleci/gen.go | 124 ++++++ .circleci/go.mod | 1 + .circleci/template.yml | 915 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 1054 insertions(+), 958 deletions(-) create mode 100644 .circleci/gen.go create mode 100644 .circleci/go.mod create mode 100644 .circleci/template.yml diff --git a/.circleci/config.yml b/.circleci/config.yml index d76be4bdc..b2de04b79 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,972 +1,28 @@ version: 2.1 +setup: true orbs: + continuation: circleci/continuation@0.2.0 go: gotest/tools@0.0.13 - aws-cli: circleci/aws-cli@1.3.2 - packer: salaxander/packer@0.0.3 - executors: golang: docker: - image: circleci/golang:1.16.4 - resource_class: 2xlarge - ubuntu: - docker: - - image: ubuntu:20.04 - -commands: - install-deps: +jobs: + generate-config: + executor: golang steps: - go/install-ssh - - go/install: {package: git} - prepare: - parameters: - linux: - default: true - description: is a linux build environment? - type: boolean - darwin: - default: false - description: is a darwin build environment? - type: boolean - steps: - - checkout - - git_fetch_all_tags - - checkout - - when: - condition: << parameters.linux >> - steps: - - run: sudo apt-get update - - run: sudo apt-get install ocl-icd-opencl-dev libhwloc-dev - - run: git submodule sync - - run: git submodule update --init - download-params: - steps: - - restore_cache: - name: Restore parameters cache - keys: - - 'v25-2k-lotus-params' - paths: - - /var/tmp/filecoin-proof-parameters/ - - run: ./lotus fetch-params 2048 - - save_cache: - name: Save parameters cache - key: 'v25-2k-lotus-params' - paths: - - /var/tmp/filecoin-proof-parameters/ - install_ipfs: - steps: - - run: | - apt update - apt install -y wget - wget https://github.com/ipfs/go-ipfs/releases/download/v0.4.22/go-ipfs_v0.4.22_linux-amd64.tar.gz - wget https://github.com/ipfs/go-ipfs/releases/download/v0.4.22/go-ipfs_v0.4.22_linux-amd64.tar.gz.sha512 - if [ "$(sha512sum go-ipfs_v0.4.22_linux-amd64.tar.gz)" != "$(cat go-ipfs_v0.4.22_linux-amd64.tar.gz.sha512)" ] - then - echo "ipfs failed checksum check" - exit 1 - fi - tar -xf go-ipfs_v0.4.22_linux-amd64.tar.gz - mv go-ipfs/ipfs /usr/local/bin/ipfs - chmod +x /usr/local/bin/ipfs - git_fetch_all_tags: - steps: - - run: - name: fetch all tags - command: | - git fetch --all - -jobs: - mod-tidy-check: - executor: golang - steps: - - install-deps - - prepare - - go/mod-tidy-check - - build-all: - executor: golang - steps: - - install-deps - - prepare - - run: sudo apt-get update - - run: sudo apt-get install npm - - run: - command: make buildall - - store_artifacts: - path: lotus - - store_artifacts: - path: lotus-miner - - store_artifacts: - path: lotus-worker - - run: mkdir linux && mv lotus lotus-miner lotus-worker linux/ - - persist_to_workspace: - root: "." - paths: - - linux - - build-debug: - executor: golang - steps: - - install-deps - - prepare - - run: - command: make debug - - test: &test - description: | - Run tests with gotestsum. - parameters: &test-params - executor: - type: executor - default: golang - go-test-flags: - type: string - default: "-timeout 30m" - description: Flags passed to go test. - packages: - type: string - default: "./..." - description: Import paths of packages to be tested. - winpost-test: - type: string - default: "0" - deadline-test: - type: string - default: "0" - proofs-log-test: - type: string - default: "0" - test-suite-name: - type: string - default: unit - description: Test suite name to report to CircleCI. - gotestsum-format: - type: string - default: pkgname-and-test-fails - description: gotestsum format. https://github.com/gotestyourself/gotestsum#format - coverage: - type: string - default: -coverprofile=coverage.txt -coverpkg=github.com/filecoin-project/lotus/... - description: Coverage flag. Set to the empty string to disable. - codecov-upload: - type: boolean - default: false - description: | - Upload coverage report to https://codecov.io/. Requires the codecov API token to be - set as an environment variable for private projects. - executor: << parameters.executor >> - steps: - - install-deps - - prepare - - run: - command: make deps lotus - no_output_timeout: 30m - - download-params - - go/install-gotestsum: - gobin: $HOME/.local/bin - version: 0.5.2 - - run: - name: go test - environment: - LOTUS_TEST_WINDOW_POST: << parameters.winpost-test >> - LOTUS_TEST_DEADLINE_TOGGLING: << parameters.deadline-test >> - TEST_RUSTPROOFS_LOGS: << parameters.proofs-log-test >> - SKIP_CONFORMANCE: "1" - command: | - mkdir -p /tmp/test-reports/<< parameters.test-suite-name >> - mkdir -p /tmp/test-artifacts - gotestsum \ - --format << parameters.gotestsum-format >> \ - --junitfile /tmp/test-reports/<< parameters.test-suite-name >>/junit.xml \ - --jsonfile /tmp/test-artifacts/<< parameters.test-suite-name >>.json \ - -- \ - << parameters.coverage >> \ - << parameters.go-test-flags >> \ - << parameters.packages >> - no_output_timeout: 30m - - store_test_results: - path: /tmp/test-reports - - store_artifacts: - path: /tmp/test-artifacts/<< parameters.test-suite-name >>.json - - when: - condition: << parameters.codecov-upload >> - steps: - - go/install: {package: bash} - - go/install: {package: curl} - - run: - shell: /bin/bash -eo pipefail - command: | - bash <(curl -s https://codecov.io/bash) - - test-chain: - <<: *test - test-node: - <<: *test - test-storage: - <<: *test - test-cli: - <<: *test - test-short: - <<: *test - test-window-post: - <<: *test - test-window-post-dispute: - <<: *test - test-deadline-toggling: - <<: *test - test-terminate: - <<: *test - check-proofs-multicore-sdr: - <<: *test - test-conformance: - description: | - Run tests using a corpus of interoperable test vectors for Filecoin - implementations to test their correctness and compliance with the Filecoin - specifications. - parameters: - <<: *test-params - vectors-branch: - type: string - default: "" - description: | - Branch on github.com/filecoin-project/test-vectors to checkout and - test with. If empty (the default) the commit defined by the git - submodule is used. - executor: << parameters.executor >> - steps: - - install-deps - - prepare - - run: - command: make deps lotus - no_output_timeout: 30m - - download-params - - when: - condition: - not: - equal: [ "", << parameters.vectors-branch >> ] - steps: - - run: - name: checkout vectors branch - command: | - cd extern/test-vectors - git fetch - git checkout origin/<< parameters.vectors-branch >> - - go/install-gotestsum: - gobin: $HOME/.local/bin - version: 0.5.2 - - run: - name: install statediff globally - command: | - ## statediff is optional; we succeed even if compilation fails. - mkdir -p /tmp/statediff - git clone https://github.com/filecoin-project/statediff.git /tmp/statediff - cd /tmp/statediff - go install ./cmd/statediff || exit 0 - - run: - name: go test - environment: - SKIP_CONFORMANCE: "0" - command: | - mkdir -p /tmp/test-reports - mkdir -p /tmp/test-artifacts - gotestsum \ - --format pkgname-and-test-fails \ - --junitfile /tmp/test-reports/junit.xml \ - -- \ - -v -coverpkg ./chain/vm/,github.com/filecoin-project/specs-actors/... -coverprofile=/tmp/conformance.out ./conformance/ - go tool cover -html=/tmp/conformance.out -o /tmp/test-artifacts/conformance-coverage.html - no_output_timeout: 30m - - store_test_results: - path: /tmp/test-reports - - store_artifacts: - path: /tmp/test-artifacts/conformance-coverage.html - build-ntwk-calibration: - description: | - Compile lotus binaries for the calibration network - parameters: - <<: *test-params - executor: << parameters.executor >> - steps: - - install-deps - - prepare - - run: make calibnet - - run: mkdir linux-calibrationnet && mv lotus lotus-miner lotus-worker linux-calibrationnet - - persist_to_workspace: - root: "." - paths: - - linux-calibrationnet - build-ntwk-butterfly: - description: | - Compile lotus binaries for the butterfly network - parameters: - <<: *test-params - executor: << parameters.executor >> - steps: - - install-deps - - prepare - - run: make butterflynet - - run: mkdir linux-butterflynet && mv lotus lotus-miner lotus-worker linux-butterflynet - - persist_to_workspace: - root: "." - paths: - - linux-butterflynet - build-ntwk-nerpa: - description: | - Compile lotus binaries for the nerpa network - parameters: - <<: *test-params - executor: << parameters.executor >> - steps: - - install-deps - - prepare - - run: make nerpanet - - run: mkdir linux-nerpanet && mv lotus lotus-miner lotus-worker linux-nerpanet - - persist_to_workspace: - root: "." - paths: - - linux-nerpanet - build-lotus-soup: - description: | - Compile `lotus-soup` Testground test plan - parameters: - <<: *test-params - executor: << parameters.executor >> - steps: - - install-deps - - prepare - - run: cd extern/filecoin-ffi && make - - run: - name: "go get lotus@master" - command: cd testplans/lotus-soup && go mod edit -replace=github.com/filecoin-project/lotus=../.. && go mod tidy - - run: - name: "build lotus-soup testplan" - command: pushd testplans/lotus-soup && go build -tags=testground . - trigger-testplans: - description: | - Trigger `lotus-soup` test cases on TaaS - parameters: - <<: *test-params - executor: << parameters.executor >> - steps: - - install-deps - - prepare - - run: - name: "download testground" - command: wget https://gist.github.com/nonsense/5fbf3167cac79945f658771aed32fc44/raw/2e17eb0debf7ec6bdf027c1bdafc2c92dd97273b/testground-d3e9603 -O ~/testground-cli && chmod +x ~/testground-cli - - run: - name: "prepare .env.toml" - command: pushd testplans/lotus-soup && mkdir -p $HOME/testground && cp env-ci.toml $HOME/testground/.env.toml && echo 'endpoint="https://ci.testground.ipfs.team"' >> $HOME/testground/.env.toml && echo 'user="circleci"' >> $HOME/testground/.env.toml - - run: - name: "prepare testground home dir and link test plans" - command: mkdir -p $HOME/testground/plans && ln -s $(pwd)/testplans/lotus-soup $HOME/testground/plans/lotus-soup && ln -s $(pwd)/testplans/graphsync $HOME/testground/plans/graphsync - - run: - name: "go get lotus@master" - command: cd testplans/lotus-soup && go get github.com/filecoin-project/lotus@master - - run: - name: "trigger deals baseline testplan on taas" - command: ~/testground-cli run composition -f $HOME/testground/plans/lotus-soup/_compositions/baseline-k8s-3-1.toml --metadata-commit=$CIRCLE_SHA1 --metadata-repo=filecoin-project/lotus --metadata-branch=$CIRCLE_BRANCH - - run: - name: "trigger payment channel stress testplan on taas" - command: ~/testground-cli run composition -f $HOME/testground/plans/lotus-soup/_compositions/paych-stress-k8s.toml --metadata-commit=$CIRCLE_SHA1 --metadata-repo=filecoin-project/lotus --metadata-branch=$CIRCLE_BRANCH - - run: - name: "trigger graphsync testplan on taas" - command: ~/testground-cli run composition -f $HOME/testground/plans/graphsync/_compositions/stress-k8s.toml --metadata-commit=$CIRCLE_SHA1 --metadata-repo=filecoin-project/lotus --metadata-branch=$CIRCLE_BRANCH - - - build-macos: - description: build darwin lotus binary - macos: - xcode: "10.0.0" - working_directory: ~/go/src/github.com/filecoin-project/lotus - steps: - - prepare: - linux: false - darwin: true - - run: - name: Install go - command: | - curl -O https://dl.google.com/go/go1.16.4.darwin-amd64.pkg && \ - sudo installer -pkg go1.16.4.darwin-amd64.pkg -target / - - run: - name: Install pkg-config - command: HOMEBREW_NO_AUTO_UPDATE=1 brew install pkg-config - - run: go version - - run: - name: Install Rust - command: | - curl https://sh.rustup.rs -sSf | sh -s -- -y - - run: - name: Install jq - command: | - curl --location https://github.com/stedolan/jq/releases/download/jq-1.6/jq-osx-amd64 --output /usr/local/bin/jq - chmod +x /usr/local/bin/jq - - run: - name: Install hwloc - command: | - mkdir ~/hwloc - curl --location https://download.open-mpi.org/release/hwloc/v2.4/hwloc-2.4.1.tar.gz --output ~/hwloc/hwloc-2.4.1.tar.gz - cd ~/hwloc - tar -xvzpf hwloc-2.4.1.tar.gz - cd hwloc-2.4.1 - ./configure && make && sudo make install - - restore_cache: - name: restore cargo cache - key: v3-go-deps-{{ arch }}-{{ checksum "~/go/src/github.com/filecoin-project/lotus/go.sum" }} - - install-deps - - run: - command: make build - no_output_timeout: 30m - - store_artifacts: - path: lotus - - store_artifacts: - path: lotus-miner - - store_artifacts: - path: lotus-worker - - run: mkdir darwin && mv lotus lotus-miner lotus-worker darwin/ - - persist_to_workspace: - root: "." - paths: - - darwin - - save_cache: - name: save cargo cache - key: v3-go-deps-{{ arch }}-{{ checksum "~/go/src/github.com/filecoin-project/lotus/go.sum" }} - paths: - - "~/.rustup" - - "~/.cargo" - - build-appimage: - machine: - image: ubuntu-2004:202104-01 - steps: - - checkout - - attach_workspace: - at: "." - - run: - name: install appimage-builder - command: | - # docs: https://appimage-builder.readthedocs.io/en/latest/intro/install.html - sudo apt update - sudo apt install -y python3-pip python3-setuptools patchelf desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace - sudo curl -Lo /usr/local/bin/appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage - sudo chmod +x /usr/local/bin/appimagetool - sudo pip3 install appimage-builder - - run: - name: install lotus dependencies - command: sudo apt install ocl-icd-opencl-dev libhwloc-dev - - run: - name: build appimage - command: | - sed -i "s/version: latest/version: ${CIRCLE_TAG:-latest}/" AppImageBuilder.yml - make appimage - - run: - name: prepare workspace - command: | - mkdir appimage - mv Lotus-latest-x86_64.AppImage appimage - - persist_to_workspace: - root: "." - paths: - - appimage - - - gofmt: - executor: golang - steps: - - install-deps - - prepare - - run: - command: "! go fmt ./... 2>&1 | read" - - gen-check: - executor: golang - steps: - - install-deps - - prepare - - run: make deps - - run: go install golang.org/x/tools/cmd/goimports - - run: go install github.com/hannahhoward/cbor-gen-for - - run: make gen - - run: git --no-pager diff - - run: git --no-pager diff --quiet - - run: make docsgen-cli - - run: git --no-pager diff - - run: git --no-pager diff --quiet - - docs-check: - executor: golang - steps: - - install-deps - - prepare - - run: go install golang.org/x/tools/cmd/goimports - - run: zcat build/openrpc/full.json.gz | jq > ../pre-openrpc-full - - run: zcat build/openrpc/miner.json.gz | jq > ../pre-openrpc-miner - - run: zcat build/openrpc/worker.json.gz | jq > ../pre-openrpc-worker - - run: make deps - - run: make docsgen - - run: zcat build/openrpc/full.json.gz | jq > ../post-openrpc-full - - run: zcat build/openrpc/miner.json.gz | jq > ../post-openrpc-miner - - run: zcat build/openrpc/worker.json.gz | jq > ../post-openrpc-worker - - run: git --no-pager diff - - run: diff ../pre-openrpc-full ../post-openrpc-full - - run: diff ../pre-openrpc-miner ../post-openrpc-miner - - run: diff ../pre-openrpc-worker ../post-openrpc-worker - - run: git --no-pager diff --quiet - - lint: &lint - description: | - Run golangci-lint. - parameters: - executor: - type: executor - default: golang - golangci-lint-version: - type: string - default: 1.27.0 - concurrency: - type: string - default: '2' - description: | - Concurrency used to run linters. Defaults to 2 because NumCPU is not - aware of container CPU limits. - args: - type: string - default: '' - description: | - Arguments to pass to golangci-lint - executor: << parameters.executor >> - steps: - - install-deps - - prepare - - run: - command: make deps - no_output_timeout: 30m - - go/install-golangci-lint: - gobin: $HOME/.local/bin - version: << parameters.golangci-lint-version >> - - run: - name: Lint - command: | - $HOME/.local/bin/golangci-lint run -v --timeout 2m \ - --concurrency << parameters.concurrency >> << parameters.args >> - lint-all: - <<: *lint - - publish: - description: publish binary artifacts - executor: ubuntu - steps: - - run: - name: Install git jq curl - command: apt update && apt install -y git jq curl - - checkout - - git_fetch_all_tags - - checkout - - install_ipfs - - attach_workspace: - at: "." - - run: - name: Create bundles - command: ./scripts/build-bundle.sh - - run: - name: Publish release - command: ./scripts/publish-release.sh - - publish-snapcraft: - description: build and push snapcraft - machine: - image: ubuntu-2004:202104-01 - resource_class: 2xlarge - parameters: - channel: - type: string - default: "edge" - description: snapcraft channel - steps: + - go/install: { package: git } - checkout - run: - name: install snapcraft - command: sudo snap install snapcraft --classic - - run: - name: create snapcraft config file + name: generate pipeline command: | - mkdir -p ~/.config/snapcraft - echo "$SNAPCRAFT_LOGIN_FILE" | base64 -d > ~/.config/snapcraft/snapcraft.cfg - - run: - name: build snap - command: snapcraft --use-lxd - - run: - name: publish snap - command: snapcraft push *.snap --release << parameters.channel >> - - - build-and-push-image: - description: build and push docker images to public AWS ECR registry - executor: aws-cli/default - parameters: - profile-name: - type: string - default: "default" - description: AWS profile name to be configured. - - aws-access-key-id: - type: env_var_name - default: AWS_ACCESS_KEY_ID - description: > - AWS access key id for IAM role. Set this to the name of - the environment variable you will set to hold this - value, i.e. AWS_ACCESS_KEY. - - aws-secret-access-key: - type: env_var_name - default: AWS_SECRET_ACCESS_KEY - description: > - AWS secret key for IAM role. Set this to the name of - the environment variable you will set to hold this - value, i.e. AWS_SECRET_ACCESS_KEY. - - region: - type: env_var_name - default: AWS_REGION - description: > - Name of env var storing your AWS region information, - defaults to AWS_REGION - - account-url: - type: env_var_name - default: AWS_ECR_ACCOUNT_URL - description: > - Env var storing Amazon ECR account URL that maps to an AWS account, - e.g. {awsAccountNum}.dkr.ecr.us-west-2.amazonaws.com - defaults to AWS_ECR_ACCOUNT_URL - - dockerfile: - type: string - default: Dockerfile - description: Name of dockerfile to use. Defaults to Dockerfile. - - path: - type: string - default: . - description: Path to the directory containing your Dockerfile and build context. Defaults to . (working directory). - - extra-build-args: - type: string - default: "" - description: > - Extra flags to pass to docker build. For examples, see - https://docs.docker.com/engine/reference/commandline/build - - repo: - type: string - description: Name of an Amazon ECR repository - - tag: - type: string - default: "latest" - description: A comma-separated string containing docker image tags to build and push (default = latest) - - steps: - - run: - name: Confirm that environment variables are set - command: | - if [ -z "$AWS_ACCESS_KEY_ID" ]; then - echo "No AWS_ACCESS_KEY_ID is set. Skipping build-and-push job ..." - circleci-agent step halt - fi - - - aws-cli/setup: - profile-name: <> - aws-access-key-id: <> - aws-secret-access-key: <> - aws-region: <> - - - run: - name: Log into Amazon ECR - command: | - aws ecr-public get-login-password --region $<> --profile <> | docker login --username AWS --password-stdin $<> - - - checkout - - - setup_remote_docker: - version: 19.03.13 - docker_layer_caching: false - - - run: - name: Build docker image - command: | - registry_id=$(echo $<> | sed "s;\..*;;g") - - docker_tag_args="" - IFS="," read -ra DOCKER_TAGS \<<< "<< parameters.tag >>" - for tag in "${DOCKER_TAGS[@]}"; do - docker_tag_args="$docker_tag_args -t $<>/<>:$tag" - done - - docker build \ - <<#parameters.extra-build-args>><><> \ - -f <>/<> \ - $docker_tag_args \ - <> - - - run: - name: Push image to Amazon ECR - command: | - IFS="," read -ra DOCKER_TAGS \<<< "<< parameters.tag >>" - for tag in "${DOCKER_TAGS[@]}"; do - docker push $<>/<>:${tag} - done - - publish-packer-mainnet: - description: build and push AWS IAM and DigitalOcean droplet. - executor: - name: packer/default - packer-version: 1.6.6 - steps: - - checkout - - attach_workspace: - at: "." - - packer/build: - template: tools/packer/lotus.pkr.hcl - args: "-var ci_workspace_bins=./linux -var lotus_network=mainnet -var git_tag=$CIRCLE_TAG" - publish-packer-calibrationnet: - description: build and push AWS IAM and DigitalOcean droplet. - executor: - name: packer/default - packer-version: 1.6.6 - steps: - - checkout - - attach_workspace: - at: "." - - packer/build: - template: tools/packer/lotus.pkr.hcl - args: "-var ci_workspace_bins=./linux-calibrationnet -var lotus_network=calibrationnet -var git_tag=$CIRCLE_TAG" - publish-packer-butterflynet: - description: build and push AWS IAM and DigitalOcean droplet. - executor: - name: packer/default - packer-version: 1.6.6 - steps: - - checkout - - attach_workspace: - at: "." - - packer/build: - template: tools/packer/lotus.pkr.hcl - args: "-var ci_workspace_bins=./linux-butterflynet -var lotus_network=butterflynet -var git_tag=$CIRCLE_TAG" - publish-packer-nerpanet: - description: build and push AWS IAM and DigitalOcean droplet. - executor: - name: packer/default - packer-version: 1.6.6 - steps: - - checkout - - attach_workspace: - at: "." - - packer/build: - template: tools/packer/lotus.pkr.hcl - args: "-var ci_workspace_bins=./linux-nerpanet -var lotus_network=nerpanet -var git_tag=$CIRCLE_TAG" - + cd .circleci + go run ./gen.go $(pwd) > generated_config.yml + - continuation/continue: + parameters: '{}' + configuration_path: .circleci/generated_config.yml workflows: - version: 2.1 - ci: + setup-workflow: jobs: - - lint-all: - concurrency: "16" # expend all docker 2xlarge CPUs. - - mod-tidy-check - - gofmt - - gen-check - - docs-check - - test: - codecov-upload: true - test-suite-name: full - - test-chain: - codecov-upload: true - test-suite-name: chain - packages: "./chain/..." - - test-node: - codecov-upload: true - test-suite-name: node - packages: "./node/..." - - test-storage: - codecov-upload: true - test-suite-name: storage - packages: "./storage/... ./extern/..." - - test-cli: - codecov-upload: true - test-suite-name: cli - packages: "./cli/... ./cmd/... ./api/..." - - test-window-post: - codecov-upload: true - go-test-flags: "-run=TestWindowedPost" - winpost-test: "1" - test-suite-name: window-post - - test-window-post-dispute: - codecov-upload: true - go-test-flags: "-run=TestWindowPostDispute" - winpost-test: "1" - test-suite-name: window-post-dispute - - test-terminate: - codecov-upload: true - go-test-flags: "-run=TestTerminate" - winpost-test: "1" - test-suite-name: terminate - - test-deadline-toggling: - codecov-upload: true - go-test-flags: "-run=TestDeadlineToggling" - deadline-test: "1" - test-suite-name: deadline-toggling - - test-short: - go-test-flags: "--timeout 10m --short" - test-suite-name: short - filters: - tags: - only: - - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - - check-proofs-multicore-sdr: - codecov-upload: true - go-test-flags: "-run=TestMulticoreSDR" - test-suite-name: multicore-sdr-check - packages: "./extern/sector-storage/ffiwrapper" - proofs-log-test: "1" - - test-conformance: - test-suite-name: conformance - packages: "./conformance" - - test-conformance: - name: test-conformance-bleeding-edge - test-suite-name: conformance-bleeding-edge - packages: "./conformance" - vectors-branch: master - - trigger-testplans: - filters: - branches: - only: - - master - - build-debug - - build-all: - requires: - - test-short - filters: - tags: - only: - - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - - build-ntwk-calibration: - requires: - - test-short - filters: - tags: - only: - - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - - build-ntwk-butterfly: - requires: - - test-short - filters: - tags: - only: - - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - - build-ntwk-nerpa: - requires: - - test-short - filters: - tags: - only: - - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - - build-lotus-soup - - build-macos: - requires: - - test-short - filters: - branches: - ignore: - - /.*/ - tags: - only: - - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - - build-appimage: - requires: - - test-short - filters: - branches: - ignore: - - /.*/ - tags: - only: - - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - - publish: - requires: - - build-all - - build-macos - - build-appimage - filters: - branches: - ignore: - - /.*/ - tags: - only: - - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - - build-and-push-image: - dockerfile: Dockerfile.lotus - path: . - repo: lotus-dev - tag: '${CIRCLE_SHA1:0:8}' - - publish-packer-mainnet: - requires: - - build-all - filters: - branches: - ignore: - - /.*/ - tags: - only: - - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - - publish-packer-calibrationnet: - requires: - - build-ntwk-calibration - filters: - branches: - ignore: - - /.*/ - tags: - only: - - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - - publish-packer-butterflynet: - requires: - - build-ntwk-butterfly - filters: - branches: - ignore: - - /.*/ - tags: - only: - - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - - publish-packer-nerpanet: - requires: - - build-ntwk-nerpa - filters: - branches: - ignore: - - /.*/ - tags: - only: - - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - - publish-snapcraft: - name: publish-snapcraft-stable - channel: stable - filters: - branches: - ignore: - - /.*/ - tags: - only: - - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - - nightly: - triggers: - - schedule: - cron: "0 0 * * *" - filters: - branches: - only: - - master - jobs: - - publish-snapcraft: - name: publish-snapcraft-nightly - channel: edge + - generate-config \ No newline at end of file diff --git a/.circleci/gen.go b/.circleci/gen.go new file mode 100644 index 000000000..db806b003 --- /dev/null +++ b/.circleci/gen.go @@ -0,0 +1,124 @@ +package main + +import ( + "embed" + "fmt" + "os" + "path/filepath" + "strings" + "text/template" +) + +//go:embed template.yml +var templateFile embed.FS + +type ( + dirs = []string + suite = string +) + +// groupedUnitTests maps suite names to top-level directories that should be +// included in that suite. The program adds an implicit group "rest" that +// includes all other top-level directories. +var groupedUnitTests = map[suite]dirs{ + "unit-node": {"node"}, + "unit-storage": {"storage", "extern"}, + "unit-cli": {"cli", "cmd", "api"}, +} + +func main() { + if len(os.Args) != 2 { + panic("expected path to repo as argument") + } + + repo := os.Args[1] + + tmpl := template.New("template.yml") + tmpl.Delims("[[", "]]") + tmpl.Funcs(template.FuncMap{ + "stripSuffix": func(in string) string { + return strings.TrimSuffix(in, "_test.go") + }, + }) + tmpl = template.Must(tmpl.ParseFS(templateFile, "*")) + + // list all itests. + itests, err := filepath.Glob("./itests/*_test.go") + if err != nil { + panic(err) + } + + // strip the dir from all entries. + for i, f := range itests { + itests[i] = filepath.Base(f) + } + + // calculate the exclusion set of unit test directories to exclude because + // they are already included in a grouped suite. + var excluded = map[string]struct{}{} + for _, ss := range groupedUnitTests { + for _, s := range ss { + e, err := filepath.Abs(filepath.Join(repo, s)) + if err != nil { + panic(err) + } + excluded[e] = struct{}{} + } + } + + // all unit tests top-level dirs that are not itests, nor included in other suites. + var rest = map[string]struct{}{} + err = filepath.Walk(repo, func(path string, f os.FileInfo, err error) error { + // include all tests that aren't in the itests directory. + if strings.Contains(path, "itests") { + return filepath.SkipDir + } + // exclude all tests included in other suites + if f.IsDir() { + if _, ok := excluded[path]; ok { + return filepath.SkipDir + } + } + if strings.HasSuffix(path, "_test.go") { + rel, err := filepath.Rel(repo, path) + if err != nil { + panic(err) + } + // take the first directory + rest[strings.Split(rel, string(os.PathSeparator))[0]] = struct{}{} + } + return err + }) + if err != nil { + panic(err) + } + + // add other directories to a 'rest' suite. + for k := range rest { + groupedUnitTests["unit-rest"] = append(groupedUnitTests["unit-rest"], k) + } + + // form the input data. + type data struct { + ItestFiles []string + UnitSuites map[string]string + } + in := data{ + ItestFiles: itests, + UnitSuites: func() map[string]string { + ret := make(map[string]string) + for name, dirs := range groupedUnitTests { + for i, d := range dirs { + dirs[i] = fmt.Sprintf("./%s/...", d) // turn into package + } + ret[name] = strings.Join(dirs, " ") + } + return ret + }(), + } + + // execute the template. + if err := tmpl.Execute(os.Stdout, in); err != nil { + panic(err) + } +} diff --git a/.circleci/go.mod b/.circleci/go.mod new file mode 100644 index 000000000..9b92b8c78 --- /dev/null +++ b/.circleci/go.mod @@ -0,0 +1 @@ +module ".circleci" \ No newline at end of file diff --git a/.circleci/template.yml b/.circleci/template.yml new file mode 100644 index 000000000..fad4cdee5 --- /dev/null +++ b/.circleci/template.yml @@ -0,0 +1,915 @@ +version: 2.1 +orbs: + go: gotest/tools@0.0.13 + aws-cli: circleci/aws-cli@1.3.2 + packer: salaxander/packer@0.0.3 + +executors: + golang: + docker: + - image: circleci/golang:1.16.4 + resource_class: 2xlarge + ubuntu: + docker: + - image: ubuntu:20.04 + +commands: + install-deps: + steps: + - go/install-ssh + - go/install: {package: git} + prepare: + parameters: + linux: + default: true + description: is a linux build environment? + type: boolean + darwin: + default: false + description: is a darwin build environment? + type: boolean + steps: + - checkout + - git_fetch_all_tags + - checkout + - when: + condition: << parameters.linux >> + steps: + - run: sudo apt-get update + - run: sudo apt-get install ocl-icd-opencl-dev libhwloc-dev + - run: git submodule sync + - run: git submodule update --init + download-params: + steps: + - restore_cache: + name: Restore parameters cache + keys: + - 'v25-2k-lotus-params' + paths: + - /var/tmp/filecoin-proof-parameters/ + - run: ./lotus fetch-params 2048 + - save_cache: + name: Save parameters cache + key: 'v25-2k-lotus-params' + paths: + - /var/tmp/filecoin-proof-parameters/ + install_ipfs: + steps: + - run: | + apt update + apt install -y wget + wget https://github.com/ipfs/go-ipfs/releases/download/v0.4.22/go-ipfs_v0.4.22_linux-amd64.tar.gz + wget https://github.com/ipfs/go-ipfs/releases/download/v0.4.22/go-ipfs_v0.4.22_linux-amd64.tar.gz.sha512 + if [ "$(sha512sum go-ipfs_v0.4.22_linux-amd64.tar.gz)" != "$(cat go-ipfs_v0.4.22_linux-amd64.tar.gz.sha512)" ] + then + echo "ipfs failed checksum check" + exit 1 + fi + tar -xf go-ipfs_v0.4.22_linux-amd64.tar.gz + mv go-ipfs/ipfs /usr/local/bin/ipfs + chmod +x /usr/local/bin/ipfs + git_fetch_all_tags: + steps: + - run: + name: fetch all tags + command: | + git fetch --all + +jobs: + mod-tidy-check: + executor: golang + steps: + - install-deps + - prepare + - go/mod-tidy-check + + build-all: + executor: golang + steps: + - install-deps + - prepare + - run: sudo apt-get update + - run: sudo apt-get install npm + - run: + command: make buildall + - store_artifacts: + path: lotus + - store_artifacts: + path: lotus-miner + - store_artifacts: + path: lotus-worker + - run: mkdir linux && mv lotus lotus-miner lotus-worker linux/ + - persist_to_workspace: + root: "." + paths: + - linux + + build-debug: + executor: golang + steps: + - install-deps + - prepare + - run: + command: make debug + + test: + description: | + Run tests with gotestsum. + parameters: &test-params + executor: + type: executor + default: golang + go-test-flags: + type: string + default: "-timeout 30m" + description: Flags passed to go test. + target: + type: string + default: "./..." + description: Import paths of packages to be tested. + proofs-log-test: + type: string + default: "0" + suite: + type: string + default: unit + description: Test suite name to report to CircleCI. + gotestsum-format: + type: string + default: pkgname-and-test-fails + description: gotestsum format. https://github.com/gotestyourself/gotestsum#format + coverage: + type: string + default: -coverprofile=coverage.txt -coverpkg=github.com/filecoin-project/lotus/... + description: Coverage flag. Set to the empty string to disable. + codecov-upload: + type: boolean + default: true + description: | + Upload coverage report to https://codecov.io/. Requires the codecov API token to be + set as an environment variable for private projects. + executor: << parameters.executor >> + steps: + - install-deps + - prepare + - run: + command: make deps lotus + no_output_timeout: 30m + - download-params + - go/install-gotestsum: + gobin: $HOME/.local/bin + version: 0.5.2 + - run: + name: go test + environment: + TEST_RUSTPROOFS_LOGS: << parameters.proofs-log-test >> + SKIP_CONFORMANCE: "1" + command: | + mkdir -p /tmp/test-reports/<< parameters.suite >> + mkdir -p /tmp/test-artifacts + gotestsum \ + --format << parameters.gotestsum-format >> \ + --junitfile /tmp/test-reports/<< parameters.suite >>/junit.xml \ + --jsonfile /tmp/test-artifacts/<< parameters.suite >>.json \ + -- \ + << parameters.coverage >> \ + << parameters.go-test-flags >> \ + << parameters.packages >> + no_output_timeout: 30m + - store_test_results: + path: /tmp/test-reports + - store_artifacts: + path: /tmp/test-artifacts/<< parameters.suite >>.json + - when: + condition: << parameters.codecov-upload >> + steps: + - go/install: {package: bash} + - go/install: {package: curl} + - run: + shell: /bin/bash -eo pipefail + command: | + bash <(curl -s https://codecov.io/bash) + + test-conformance: + description: | + Run tests using a corpus of interoperable test vectors for Filecoin + implementations to test their correctness and compliance with the Filecoin + specifications. + parameters: + <<: *test-params + vectors-branch: + type: string + default: "" + description: | + Branch on github.com/filecoin-project/test-vectors to checkout and + test with. If empty (the default) the commit defined by the git + submodule is used. + executor: << parameters.executor >> + steps: + - install-deps + - prepare + - run: + command: make deps lotus + no_output_timeout: 30m + - download-params + - when: + condition: + not: + equal: [ "", << parameters.vectors-branch >> ] + steps: + - run: + name: checkout vectors branch + command: | + cd extern/test-vectors + git fetch + git checkout origin/<< parameters.vectors-branch >> + - go/install-gotestsum: + gobin: $HOME/.local/bin + version: 0.5.2 + - run: + name: install statediff globally + command: | + ## statediff is optional; we succeed even if compilation fails. + mkdir -p /tmp/statediff + git clone https://github.com/filecoin-project/statediff.git /tmp/statediff + cd /tmp/statediff + go install ./cmd/statediff || exit 0 + - run: + name: go test + environment: + SKIP_CONFORMANCE: "0" + command: | + mkdir -p /tmp/test-reports + mkdir -p /tmp/test-artifacts + gotestsum \ + --format pkgname-and-test-fails \ + --junitfile /tmp/test-reports/junit.xml \ + -- \ + -v -coverpkg ./chain/vm/,github.com/filecoin-project/specs-actors/... -coverprofile=/tmp/conformance.out ./conformance/ + go tool cover -html=/tmp/conformance.out -o /tmp/test-artifacts/conformance-coverage.html + no_output_timeout: 30m + - store_test_results: + path: /tmp/test-reports + - store_artifacts: + path: /tmp/test-artifacts/conformance-coverage.html + build-ntwk-calibration: + description: | + Compile lotus binaries for the calibration network + parameters: + <<: *test-params + executor: << parameters.executor >> + steps: + - install-deps + - prepare + - run: make calibnet + - run: mkdir linux-calibrationnet && mv lotus lotus-miner lotus-worker linux-calibrationnet + - persist_to_workspace: + root: "." + paths: + - linux-calibrationnet + build-ntwk-butterfly: + description: | + Compile lotus binaries for the butterfly network + parameters: + <<: *test-params + executor: << parameters.executor >> + steps: + - install-deps + - prepare + - run: make butterflynet + - run: mkdir linux-butterflynet && mv lotus lotus-miner lotus-worker linux-butterflynet + - persist_to_workspace: + root: "." + paths: + - linux-butterflynet + build-ntwk-nerpa: + description: | + Compile lotus binaries for the nerpa network + parameters: + <<: *test-params + executor: << parameters.executor >> + steps: + - install-deps + - prepare + - run: make nerpanet + - run: mkdir linux-nerpanet && mv lotus lotus-miner lotus-worker linux-nerpanet + - persist_to_workspace: + root: "." + paths: + - linux-nerpanet + build-lotus-soup: + description: | + Compile `lotus-soup` Testground test plan + parameters: + <<: *test-params + executor: << parameters.executor >> + steps: + - install-deps + - prepare + - run: cd extern/filecoin-ffi && make + - run: + name: "go get lotus@master" + command: cd testplans/lotus-soup && go mod edit -replace=github.com/filecoin-project/lotus=../.. && go mod tidy + - run: + name: "build lotus-soup testplan" + command: pushd testplans/lotus-soup && go build -tags=testground . + trigger-testplans: + description: | + Trigger `lotus-soup` test cases on TaaS + parameters: + <<: *test-params + executor: << parameters.executor >> + steps: + - install-deps + - prepare + - run: + name: "download testground" + command: wget https://gist.github.com/nonsense/5fbf3167cac79945f658771aed32fc44/raw/2e17eb0debf7ec6bdf027c1bdafc2c92dd97273b/testground-d3e9603 -O ~/testground-cli && chmod +x ~/testground-cli + - run: + name: "prepare .env.toml" + command: pushd testplans/lotus-soup && mkdir -p $HOME/testground && cp env-ci.toml $HOME/testground/.env.toml && echo 'endpoint="https://ci.testground.ipfs.team"' >> $HOME/testground/.env.toml && echo 'user="circleci"' >> $HOME/testground/.env.toml + - run: + name: "prepare testground home dir and link test plans" + command: mkdir -p $HOME/testground/plans && ln -s $(pwd)/testplans/lotus-soup $HOME/testground/plans/lotus-soup && ln -s $(pwd)/testplans/graphsync $HOME/testground/plans/graphsync + - run: + name: "go get lotus@master" + command: cd testplans/lotus-soup && go get github.com/filecoin-project/lotus@master + - run: + name: "trigger deals baseline testplan on taas" + command: ~/testground-cli run composition -f $HOME/testground/plans/lotus-soup/_compositions/baseline-k8s-3-1.toml --metadata-commit=$CIRCLE_SHA1 --metadata-repo=filecoin-project/lotus --metadata-branch=$CIRCLE_BRANCH + - run: + name: "trigger payment channel stress testplan on taas" + command: ~/testground-cli run composition -f $HOME/testground/plans/lotus-soup/_compositions/paych-stress-k8s.toml --metadata-commit=$CIRCLE_SHA1 --metadata-repo=filecoin-project/lotus --metadata-branch=$CIRCLE_BRANCH + - run: + name: "trigger graphsync testplan on taas" + command: ~/testground-cli run composition -f $HOME/testground/plans/graphsync/_compositions/stress-k8s.toml --metadata-commit=$CIRCLE_SHA1 --metadata-repo=filecoin-project/lotus --metadata-branch=$CIRCLE_BRANCH + + + build-macos: + description: build darwin lotus binary + macos: + xcode: "10.0.0" + working_directory: ~/go/src/github.com/filecoin-project/lotus + steps: + - prepare: + linux: false + darwin: true + - run: + name: Install go + command: | + curl -O https://dl.google.com/go/go1.16.4.darwin-amd64.pkg && \ + sudo installer -pkg go1.16.4.darwin-amd64.pkg -target / + - run: + name: Install pkg-config + command: HOMEBREW_NO_AUTO_UPDATE=1 brew install pkg-config + - run: go version + - run: + name: Install Rust + command: | + curl https://sh.rustup.rs -sSf | sh -s -- -y + - run: + name: Install jq + command: | + curl --location https://github.com/stedolan/jq/releases/download/jq-1.6/jq-osx-amd64 --output /usr/local/bin/jq + chmod +x /usr/local/bin/jq + - run: + name: Install hwloc + command: | + mkdir ~/hwloc + curl --location https://download.open-mpi.org/release/hwloc/v2.4/hwloc-2.4.1.tar.gz --output ~/hwloc/hwloc-2.4.1.tar.gz + cd ~/hwloc + tar -xvzpf hwloc-2.4.1.tar.gz + cd hwloc-2.4.1 + ./configure && make && sudo make install + - restore_cache: + name: restore cargo cache + key: v3-go-deps-{{ arch }}-{{ checksum "~/go/src/github.com/filecoin-project/lotus/go.sum" }} + - install-deps + - run: + command: make build + no_output_timeout: 30m + - store_artifacts: + path: lotus + - store_artifacts: + path: lotus-miner + - store_artifacts: + path: lotus-worker + - run: mkdir darwin && mv lotus lotus-miner lotus-worker darwin/ + - persist_to_workspace: + root: "." + paths: + - darwin + - save_cache: + name: save cargo cache + key: v3-go-deps-{{ arch }}-{{ checksum "~/go/src/github.com/filecoin-project/lotus/go.sum" }} + paths: + - "~/.rustup" + - "~/.cargo" + + build-appimage: + machine: + image: ubuntu-2004:202104-01 + steps: + - checkout + - attach_workspace: + at: "." + - run: + name: install appimage-builder + command: | + # docs: https://appimage-builder.readthedocs.io/en/latest/intro/install.html + sudo apt update + sudo apt install -y python3-pip python3-setuptools patchelf desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace + sudo curl -Lo /usr/local/bin/appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage + sudo chmod +x /usr/local/bin/appimagetool + sudo pip3 install appimage-builder + - run: + name: install lotus dependencies + command: sudo apt install ocl-icd-opencl-dev libhwloc-dev + - run: + name: build appimage + command: | + sed -i "s/version: latest/version: ${CIRCLE_TAG:-latest}/" AppImageBuilder.yml + make appimage + - run: + name: prepare workspace + command: | + mkdir appimage + mv Lotus-latest-x86_64.AppImage appimage + - persist_to_workspace: + root: "." + paths: + - appimage + + + gofmt: + executor: golang + steps: + - install-deps + - prepare + - run: + command: "! go fmt ./... 2>&1 | read" + + gen-check: + executor: golang + steps: + - install-deps + - prepare + - run: make deps + - run: go install golang.org/x/tools/cmd/goimports + - run: go install github.com/hannahhoward/cbor-gen-for + - run: make gen + - run: git --no-pager diff + - run: git --no-pager diff --quiet + - run: make docsgen-cli + - run: git --no-pager diff + - run: git --no-pager diff --quiet + + docs-check: + executor: golang + steps: + - install-deps + - prepare + - run: go install golang.org/x/tools/cmd/goimports + - run: zcat build/openrpc/full.json.gz | jq > ../pre-openrpc-full + - run: zcat build/openrpc/miner.json.gz | jq > ../pre-openrpc-miner + - run: zcat build/openrpc/worker.json.gz | jq > ../pre-openrpc-worker + - run: make deps + - run: make docsgen + - run: zcat build/openrpc/full.json.gz | jq > ../post-openrpc-full + - run: zcat build/openrpc/miner.json.gz | jq > ../post-openrpc-miner + - run: zcat build/openrpc/worker.json.gz | jq > ../post-openrpc-worker + - run: git --no-pager diff + - run: diff ../pre-openrpc-full ../post-openrpc-full + - run: diff ../pre-openrpc-miner ../post-openrpc-miner + - run: diff ../pre-openrpc-worker ../post-openrpc-worker + - run: git --no-pager diff --quiet + + lint: &lint + description: | + Run golangci-lint. + parameters: + executor: + type: executor + default: golang + golangci-lint-version: + type: string + default: 1.27.0 + concurrency: + type: string + default: '2' + description: | + Concurrency used to run linters. Defaults to 2 because NumCPU is not + aware of container CPU limits. + args: + type: string + default: '' + description: | + Arguments to pass to golangci-lint + executor: << parameters.executor >> + steps: + - install-deps + - prepare + - run: + command: make deps + no_output_timeout: 30m + - go/install-golangci-lint: + gobin: $HOME/.local/bin + version: << parameters.golangci-lint-version >> + - run: + name: Lint + command: | + $HOME/.local/bin/golangci-lint run -v --timeout 2m \ + --concurrency << parameters.concurrency >> << parameters.args >> + lint-all: + <<: *lint + + publish: + description: publish binary artifacts + executor: ubuntu + steps: + - run: + name: Install git jq curl + command: apt update && apt install -y git jq curl + - checkout + - git_fetch_all_tags + - checkout + - install_ipfs + - attach_workspace: + at: "." + - run: + name: Create bundles + command: ./scripts/build-bundle.sh + - run: + name: Publish release + command: ./scripts/publish-release.sh + + publish-snapcraft: + description: build and push snapcraft + machine: + image: ubuntu-2004:202104-01 + resource_class: 2xlarge + parameters: + channel: + type: string + default: "edge" + description: snapcraft channel + steps: + - checkout + - run: + name: install snapcraft + command: sudo snap install snapcraft --classic + - run: + name: create snapcraft config file + command: | + mkdir -p ~/.config/snapcraft + echo "$SNAPCRAFT_LOGIN_FILE" | base64 -d > ~/.config/snapcraft/snapcraft.cfg + - run: + name: build snap + command: snapcraft --use-lxd + - run: + name: publish snap + command: snapcraft push *.snap --release << parameters.channel >> + + build-and-push-image: + description: build and push docker images to public AWS ECR registry + executor: aws-cli/default + parameters: + profile-name: + type: string + default: "default" + description: AWS profile name to be configured. + + aws-access-key-id: + type: env_var_name + default: AWS_ACCESS_KEY_ID + description: > + AWS access key id for IAM role. Set this to the name of + the environment variable you will set to hold this + value, i.e. AWS_ACCESS_KEY. + + aws-secret-access-key: + type: env_var_name + default: AWS_SECRET_ACCESS_KEY + description: > + AWS secret key for IAM role. Set this to the name of + the environment variable you will set to hold this + value, i.e. AWS_SECRET_ACCESS_KEY. + + region: + type: env_var_name + default: AWS_REGION + description: > + Name of env var storing your AWS region information, + defaults to AWS_REGION + + account-url: + type: env_var_name + default: AWS_ECR_ACCOUNT_URL + description: > + Env var storing Amazon ECR account URL that maps to an AWS account, + e.g. {awsAccountNum}.dkr.ecr.us-west-2.amazonaws.com + defaults to AWS_ECR_ACCOUNT_URL + + dockerfile: + type: string + default: Dockerfile + description: Name of dockerfile to use. Defaults to Dockerfile. + + path: + type: string + default: . + description: Path to the directory containing your Dockerfile and build context. Defaults to . (working directory). + + extra-build-args: + type: string + default: "" + description: > + Extra flags to pass to docker build. For examples, see + https://docs.docker.com/engine/reference/commandline/build + + repo: + type: string + description: Name of an Amazon ECR repository + + tag: + type: string + default: "latest" + description: A comma-separated string containing docker image tags to build and push (default = latest) + + steps: + - run: + name: Confirm that environment variables are set + command: | + if [ -z "$AWS_ACCESS_KEY_ID" ]; then + echo "No AWS_ACCESS_KEY_ID is set. Skipping build-and-push job ..." + circleci-agent step halt + fi + + - aws-cli/setup: + profile-name: <> + aws-access-key-id: <> + aws-secret-access-key: <> + aws-region: <> + + - run: + name: Log into Amazon ECR + command: | + aws ecr-public get-login-password --region $<> --profile <> | docker login --username AWS --password-stdin $<> + + - checkout + + - setup_remote_docker: + version: 19.03.13 + docker_layer_caching: false + + - run: + name: Build docker image + command: | + registry_id=$(echo $<> | sed "s;\..*;;g") + + docker_tag_args="" + IFS="," read -ra DOCKER_TAGS \<<< "<< parameters.tag >>" + for tag in "${DOCKER_TAGS[@]}"; do + docker_tag_args="$docker_tag_args -t $<>/<>:$tag" + done + + docker build \ + <<#parameters.extra-build-args>><><> \ + -f <>/<> \ + $docker_tag_args \ + <> + + - run: + name: Push image to Amazon ECR + command: | + IFS="," read -ra DOCKER_TAGS \<<< "<< parameters.tag >>" + for tag in "${DOCKER_TAGS[@]}"; do + docker push $<>/<>:${tag} + done + + publish-packer-mainnet: + description: build and push AWS IAM and DigitalOcean droplet. + executor: + name: packer/default + packer-version: 1.6.6 + steps: + - checkout + - attach_workspace: + at: "." + - packer/build: + template: tools/packer/lotus.pkr.hcl + args: "-var ci_workspace_bins=./linux -var lotus_network=mainnet -var git_tag=$CIRCLE_TAG" + publish-packer-calibrationnet: + description: build and push AWS IAM and DigitalOcean droplet. + executor: + name: packer/default + packer-version: 1.6.6 + steps: + - checkout + - attach_workspace: + at: "." + - packer/build: + template: tools/packer/lotus.pkr.hcl + args: "-var ci_workspace_bins=./linux-calibrationnet -var lotus_network=calibrationnet -var git_tag=$CIRCLE_TAG" + publish-packer-butterflynet: + description: build and push AWS IAM and DigitalOcean droplet. + executor: + name: packer/default + packer-version: 1.6.6 + steps: + - checkout + - attach_workspace: + at: "." + - packer/build: + template: tools/packer/lotus.pkr.hcl + args: "-var ci_workspace_bins=./linux-butterflynet -var lotus_network=butterflynet -var git_tag=$CIRCLE_TAG" + publish-packer-nerpanet: + description: build and push AWS IAM and DigitalOcean droplet. + executor: + name: packer/default + packer-version: 1.6.6 + steps: + - checkout + - attach_workspace: + at: "." + - packer/build: + template: tools/packer/lotus.pkr.hcl + args: "-var ci_workspace_bins=./linux-nerpanet -var lotus_network=nerpanet -var git_tag=$CIRCLE_TAG" + +workflows: + version: 2.1 + ci: + jobs: + - lint-all: + concurrency: "16" # expend all docker 2xlarge CPUs. + - mod-tidy-check + - gofmt + - gen-check + - docs-check + + [[- range $file := .ItestFiles -]] + [[ with $name := $file | stripSuffix ]] + - test: + name: test-itest-[[ $name ]] + suite: itest-[[ $name ]] + target: "./itests/[[ $file ]]" + [[ end ]] + [[- end -]] + + [[range $suite, $pkgs := .UnitSuites]] + - test: + name: test-[[ $suite ]] + suite: utest-[[ $suite ]] + target: "[[ $pkgs ]]" + [[- end]] + + - check-proofs-multicore-sdr: + go-test-flags: "-run=TestMulticoreSDR" + suite: multicore-sdr-check + target: "./extern/sector-storage/ffiwrapper" + proofs-log-test: "1" + - test-conformance: + suite: conformance + codecov-upload: false + target: "./conformance" + - test-conformance: + name: test-conformance-bleeding-edge + codecov-upload: false + suite: conformance-bleeding-edge + target: "./conformance" + vectors-branch: master + - trigger-testplans: + filters: + branches: + only: + - master + - build-debug + - build-all: + requires: + - test-short + filters: + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-ntwk-calibration: + requires: + - test-short + filters: + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-ntwk-butterfly: + requires: + - test-short + filters: + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-ntwk-nerpa: + requires: + - test-short + filters: + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-lotus-soup + - build-macos: + requires: + - test-short + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-appimage: + requires: + - test-short + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - publish: + requires: + - build-all + - build-macos + - build-appimage + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-and-push-image: + dockerfile: Dockerfile.lotus + path: . + repo: lotus-dev + tag: '${CIRCLE_SHA1:0:8}' + - publish-packer-mainnet: + requires: + - build-all + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - publish-packer-calibrationnet: + requires: + - build-ntwk-calibration + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - publish-packer-butterflynet: + requires: + - build-ntwk-butterfly + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - publish-packer-nerpanet: + requires: + - build-ntwk-nerpa + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - publish-snapcraft: + name: publish-snapcraft-stable + channel: stable + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + + nightly: + triggers: + - schedule: + cron: "0 0 * * *" + filters: + branches: + only: + - master + jobs: + - publish-snapcraft: + name: publish-snapcraft-nightly + channel: edge From 07fad6a2014145a9fb688dbf8920a0c404944894 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Tue, 22 Jun 2021 15:46:44 -0400 Subject: [PATCH 209/257] Fix helptext --- cli/client.go | 4 ++-- documentation/en/cli-lotus.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cli/client.go b/cli/client.go index 4f2c58dc2..2cd946424 100644 --- a/cli/client.go +++ b/cli/client.go @@ -308,8 +308,8 @@ var clientDealCmd = &cli.Command{ Description: `Make a deal with a miner. dataCid comes from running 'lotus client import'. miner is the address of the miner you wish to make a deal with. -price is measured in FIL/GB/Epoch. Miners usually don't accept a bid -lower than their advertised ask. You can check a miners listed price +price is measured in FIL/Epoch. Miners usually don't accept a bid +lower than their advertised ask (which is in FIL/GiB/epoch). You can check a miners listed price with 'lotus client query-ask '. duration is how long the miner should store the data for, in blocks. The minimum value is 518400 (6 months).`, diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index 8ffee3d1a..52ccf7ed3 100644 --- a/documentation/en/cli-lotus.md +++ b/documentation/en/cli-lotus.md @@ -558,8 +558,8 @@ DESCRIPTION: Make a deal with a miner. dataCid comes from running 'lotus client import'. miner is the address of the miner you wish to make a deal with. -price is measured in FIL/GB/Epoch. Miners usually don't accept a bid -lower than their advertised ask. You can check a miners listed price +price is measured in FIL/Epoch. Miners usually don't accept a bid +lower than their advertised ask (which is in FIL/GiB/epoch). You can check a miners listed price with 'lotus client query-ask '. duration is how long the miner should store the data for, in blocks. The minimum value is 518400 (6 months). From ec6c394de76489e08bcabe3e567a6d8c446a5e12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Tue, 22 Jun 2021 21:03:05 +0100 Subject: [PATCH 210/257] remove dynamic circleci. --- .circleci/config.yml | 987 ++++++++++++++++++++++++++++++++++++++++- .circleci/gen.go | 12 +- .circleci/go.mod | 1 - .circleci/template.yml | 17 +- 4 files changed, 985 insertions(+), 32 deletions(-) delete mode 100644 .circleci/go.mod diff --git a/.circleci/config.yml b/.circleci/config.yml index b2de04b79..f41a8c513 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,28 +1,987 @@ version: 2.1 -setup: true orbs: - continuation: circleci/continuation@0.2.0 go: gotest/tools@0.0.13 + aws-cli: circleci/aws-cli@1.3.2 + packer: salaxander/packer@0.0.3 + executors: golang: docker: - image: circleci/golang:1.16.4 -jobs: - generate-config: - executor: golang + resource_class: 2xlarge + ubuntu: + docker: + - image: ubuntu:20.04 + +commands: + install-deps: steps: - go/install-ssh - - go/install: { package: git } + - go/install: {package: git} + prepare: + parameters: + linux: + default: true + description: is a linux build environment? + type: boolean + darwin: + default: false + description: is a darwin build environment? + type: boolean + steps: + - checkout + - git_fetch_all_tags + - checkout + - when: + condition: << parameters.linux >> + steps: + - run: sudo apt-get update + - run: sudo apt-get install ocl-icd-opencl-dev libhwloc-dev + - run: git submodule sync + - run: git submodule update --init + download-params: + steps: + - restore_cache: + name: Restore parameters cache + keys: + - 'v25-2k-lotus-params' + paths: + - /var/tmp/filecoin-proof-parameters/ + - run: ./lotus fetch-params 2048 + - save_cache: + name: Save parameters cache + key: 'v25-2k-lotus-params' + paths: + - /var/tmp/filecoin-proof-parameters/ + install_ipfs: + steps: + - run: | + apt update + apt install -y wget + wget https://github.com/ipfs/go-ipfs/releases/download/v0.4.22/go-ipfs_v0.4.22_linux-amd64.tar.gz + wget https://github.com/ipfs/go-ipfs/releases/download/v0.4.22/go-ipfs_v0.4.22_linux-amd64.tar.gz.sha512 + if [ "$(sha512sum go-ipfs_v0.4.22_linux-amd64.tar.gz)" != "$(cat go-ipfs_v0.4.22_linux-amd64.tar.gz.sha512)" ] + then + echo "ipfs failed checksum check" + exit 1 + fi + tar -xf go-ipfs_v0.4.22_linux-amd64.tar.gz + mv go-ipfs/ipfs /usr/local/bin/ipfs + chmod +x /usr/local/bin/ipfs + git_fetch_all_tags: + steps: + - run: + name: fetch all tags + command: | + git fetch --all + +jobs: + mod-tidy-check: + executor: golang + steps: + - install-deps + - prepare + - go/mod-tidy-check + + build-all: + executor: golang + steps: + - install-deps + - prepare + - run: sudo apt-get update + - run: sudo apt-get install npm + - run: + command: make buildall + - store_artifacts: + path: lotus + - store_artifacts: + path: lotus-miner + - store_artifacts: + path: lotus-worker + - run: mkdir linux && mv lotus lotus-miner lotus-worker linux/ + - persist_to_workspace: + root: "." + paths: + - linux + + build-debug: + executor: golang + steps: + - install-deps + - prepare + - run: + command: make debug + + test: + description: | + Run tests with gotestsum. + parameters: &test-params + executor: + type: executor + default: golang + go-test-flags: + type: string + default: "-timeout 30m" + description: Flags passed to go test. + target: + type: string + default: "./..." + description: Import paths of packages to be tested. + proofs-log-test: + type: string + default: "0" + suite: + type: string + default: unit + description: Test suite name to report to CircleCI. + gotestsum-format: + type: string + default: pkgname-and-test-fails + description: gotestsum format. https://github.com/gotestyourself/gotestsum#format + coverage: + type: string + default: -coverprofile=coverage.txt -coverpkg=github.com/filecoin-project/lotus/... + description: Coverage flag. Set to the empty string to disable. + codecov-upload: + type: boolean + default: true + description: | + Upload coverage report to https://codecov.io/. Requires the codecov API token to be + set as an environment variable for private projects. + executor: << parameters.executor >> + steps: + - install-deps + - prepare + - run: + command: make deps lotus + no_output_timeout: 30m + - download-params + - go/install-gotestsum: + gobin: $HOME/.local/bin + version: 0.5.2 + - run: + name: go test + environment: + TEST_RUSTPROOFS_LOGS: << parameters.proofs-log-test >> + SKIP_CONFORMANCE: "1" + command: | + mkdir -p /tmp/test-reports/<< parameters.suite >> + mkdir -p /tmp/test-artifacts + gotestsum \ + --format << parameters.gotestsum-format >> \ + --junitfile /tmp/test-reports/<< parameters.suite >>/junit.xml \ + --jsonfile /tmp/test-artifacts/<< parameters.suite >>.json \ + -- \ + << parameters.coverage >> \ + << parameters.go-test-flags >> \ + << parameters.target >> + no_output_timeout: 30m + - store_test_results: + path: /tmp/test-reports + - store_artifacts: + path: /tmp/test-artifacts/<< parameters.suite >>.json + - when: + condition: << parameters.codecov-upload >> + steps: + - go/install: {package: bash} + - go/install: {package: curl} + - run: + shell: /bin/bash -eo pipefail + command: | + bash <(curl -s https://codecov.io/bash) + + test-conformance: + description: | + Run tests using a corpus of interoperable test vectors for Filecoin + implementations to test their correctness and compliance with the Filecoin + specifications. + parameters: + <<: *test-params + vectors-branch: + type: string + default: "" + description: | + Branch on github.com/filecoin-project/test-vectors to checkout and + test with. If empty (the default) the commit defined by the git + submodule is used. + executor: << parameters.executor >> + steps: + - install-deps + - prepare + - run: + command: make deps lotus + no_output_timeout: 30m + - download-params + - when: + condition: + not: + equal: [ "", << parameters.vectors-branch >> ] + steps: + - run: + name: checkout vectors branch + command: | + cd extern/test-vectors + git fetch + git checkout origin/<< parameters.vectors-branch >> + - go/install-gotestsum: + gobin: $HOME/.local/bin + version: 0.5.2 + - run: + name: install statediff globally + command: | + ## statediff is optional; we succeed even if compilation fails. + mkdir -p /tmp/statediff + git clone https://github.com/filecoin-project/statediff.git /tmp/statediff + cd /tmp/statediff + go install ./cmd/statediff || exit 0 + - run: + name: go test + environment: + SKIP_CONFORMANCE: "0" + command: | + mkdir -p /tmp/test-reports + mkdir -p /tmp/test-artifacts + gotestsum \ + --format pkgname-and-test-fails \ + --junitfile /tmp/test-reports/junit.xml \ + -- \ + -v -coverpkg ./chain/vm/,github.com/filecoin-project/specs-actors/... -coverprofile=/tmp/conformance.out ./conformance/ + go tool cover -html=/tmp/conformance.out -o /tmp/test-artifacts/conformance-coverage.html + no_output_timeout: 30m + - store_test_results: + path: /tmp/test-reports + - store_artifacts: + path: /tmp/test-artifacts/conformance-coverage.html + build-ntwk-calibration: + description: | + Compile lotus binaries for the calibration network + parameters: + <<: *test-params + executor: << parameters.executor >> + steps: + - install-deps + - prepare + - run: make calibnet + - run: mkdir linux-calibrationnet && mv lotus lotus-miner lotus-worker linux-calibrationnet + - persist_to_workspace: + root: "." + paths: + - linux-calibrationnet + build-ntwk-butterfly: + description: | + Compile lotus binaries for the butterfly network + parameters: + <<: *test-params + executor: << parameters.executor >> + steps: + - install-deps + - prepare + - run: make butterflynet + - run: mkdir linux-butterflynet && mv lotus lotus-miner lotus-worker linux-butterflynet + - persist_to_workspace: + root: "." + paths: + - linux-butterflynet + build-ntwk-nerpa: + description: | + Compile lotus binaries for the nerpa network + parameters: + <<: *test-params + executor: << parameters.executor >> + steps: + - install-deps + - prepare + - run: make nerpanet + - run: mkdir linux-nerpanet && mv lotus lotus-miner lotus-worker linux-nerpanet + - persist_to_workspace: + root: "." + paths: + - linux-nerpanet + build-lotus-soup: + description: | + Compile `lotus-soup` Testground test plan + parameters: + <<: *test-params + executor: << parameters.executor >> + steps: + - install-deps + - prepare + - run: cd extern/filecoin-ffi && make + - run: + name: "go get lotus@master" + command: cd testplans/lotus-soup && go mod edit -replace=github.com/filecoin-project/lotus=../.. && go mod tidy + - run: + name: "build lotus-soup testplan" + command: pushd testplans/lotus-soup && go build -tags=testground . + trigger-testplans: + description: | + Trigger `lotus-soup` test cases on TaaS + parameters: + <<: *test-params + executor: << parameters.executor >> + steps: + - install-deps + - prepare + - run: + name: "download testground" + command: wget https://gist.github.com/nonsense/5fbf3167cac79945f658771aed32fc44/raw/2e17eb0debf7ec6bdf027c1bdafc2c92dd97273b/testground-d3e9603 -O ~/testground-cli && chmod +x ~/testground-cli + - run: + name: "prepare .env.toml" + command: pushd testplans/lotus-soup && mkdir -p $HOME/testground && cp env-ci.toml $HOME/testground/.env.toml && echo 'endpoint="https://ci.testground.ipfs.team"' >> $HOME/testground/.env.toml && echo 'user="circleci"' >> $HOME/testground/.env.toml + - run: + name: "prepare testground home dir and link test plans" + command: mkdir -p $HOME/testground/plans && ln -s $(pwd)/testplans/lotus-soup $HOME/testground/plans/lotus-soup && ln -s $(pwd)/testplans/graphsync $HOME/testground/plans/graphsync + - run: + name: "go get lotus@master" + command: cd testplans/lotus-soup && go get github.com/filecoin-project/lotus@master + - run: + name: "trigger deals baseline testplan on taas" + command: ~/testground-cli run composition -f $HOME/testground/plans/lotus-soup/_compositions/baseline-k8s-3-1.toml --metadata-commit=$CIRCLE_SHA1 --metadata-repo=filecoin-project/lotus --metadata-branch=$CIRCLE_BRANCH + - run: + name: "trigger payment channel stress testplan on taas" + command: ~/testground-cli run composition -f $HOME/testground/plans/lotus-soup/_compositions/paych-stress-k8s.toml --metadata-commit=$CIRCLE_SHA1 --metadata-repo=filecoin-project/lotus --metadata-branch=$CIRCLE_BRANCH + - run: + name: "trigger graphsync testplan on taas" + command: ~/testground-cli run composition -f $HOME/testground/plans/graphsync/_compositions/stress-k8s.toml --metadata-commit=$CIRCLE_SHA1 --metadata-repo=filecoin-project/lotus --metadata-branch=$CIRCLE_BRANCH + + + build-macos: + description: build darwin lotus binary + macos: + xcode: "10.0.0" + working_directory: ~/go/src/github.com/filecoin-project/lotus + steps: + - prepare: + linux: false + darwin: true + - run: + name: Install go + command: | + curl -O https://dl.google.com/go/go1.16.4.darwin-amd64.pkg && \ + sudo installer -pkg go1.16.4.darwin-amd64.pkg -target / + - run: + name: Install pkg-config + command: HOMEBREW_NO_AUTO_UPDATE=1 brew install pkg-config + - run: go version + - run: + name: Install Rust + command: | + curl https://sh.rustup.rs -sSf | sh -s -- -y + - run: + name: Install jq + command: | + curl --location https://github.com/stedolan/jq/releases/download/jq-1.6/jq-osx-amd64 --output /usr/local/bin/jq + chmod +x /usr/local/bin/jq + - run: + name: Install hwloc + command: | + mkdir ~/hwloc + curl --location https://download.open-mpi.org/release/hwloc/v2.4/hwloc-2.4.1.tar.gz --output ~/hwloc/hwloc-2.4.1.tar.gz + cd ~/hwloc + tar -xvzpf hwloc-2.4.1.tar.gz + cd hwloc-2.4.1 + ./configure && make && sudo make install + - restore_cache: + name: restore cargo cache + key: v3-go-deps-{{ arch }}-{{ checksum "~/go/src/github.com/filecoin-project/lotus/go.sum" }} + - install-deps + - run: + command: make build + no_output_timeout: 30m + - store_artifacts: + path: lotus + - store_artifacts: + path: lotus-miner + - store_artifacts: + path: lotus-worker + - run: mkdir darwin && mv lotus lotus-miner lotus-worker darwin/ + - persist_to_workspace: + root: "." + paths: + - darwin + - save_cache: + name: save cargo cache + key: v3-go-deps-{{ arch }}-{{ checksum "~/go/src/github.com/filecoin-project/lotus/go.sum" }} + paths: + - "~/.rustup" + - "~/.cargo" + + build-appimage: + machine: + image: ubuntu-2004:202104-01 + steps: + - checkout + - attach_workspace: + at: "." + - run: + name: install appimage-builder + command: | + # docs: https://appimage-builder.readthedocs.io/en/latest/intro/install.html + sudo apt update + sudo apt install -y python3-pip python3-setuptools patchelf desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace + sudo curl -Lo /usr/local/bin/appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage + sudo chmod +x /usr/local/bin/appimagetool + sudo pip3 install appimage-builder + - run: + name: install lotus dependencies + command: sudo apt install ocl-icd-opencl-dev libhwloc-dev + - run: + name: build appimage + command: | + sed -i "s/version: latest/version: ${CIRCLE_TAG:-latest}/" AppImageBuilder.yml + make appimage + - run: + name: prepare workspace + command: | + mkdir appimage + mv Lotus-latest-x86_64.AppImage appimage + - persist_to_workspace: + root: "." + paths: + - appimage + + + gofmt: + executor: golang + steps: + - install-deps + - prepare + - run: + command: "! go fmt ./... 2>&1 | read" + + gen-check: + executor: golang + steps: + - install-deps + - prepare + - run: make deps + - run: go install golang.org/x/tools/cmd/goimports + - run: go install github.com/hannahhoward/cbor-gen-for + - run: make gen + - run: git --no-pager diff + - run: git --no-pager diff --quiet + - run: make docsgen-cli + - run: git --no-pager diff + - run: git --no-pager diff --quiet + + docs-check: + executor: golang + steps: + - install-deps + - prepare + - run: go install golang.org/x/tools/cmd/goimports + - run: zcat build/openrpc/full.json.gz | jq > ../pre-openrpc-full + - run: zcat build/openrpc/miner.json.gz | jq > ../pre-openrpc-miner + - run: zcat build/openrpc/worker.json.gz | jq > ../pre-openrpc-worker + - run: make deps + - run: make docsgen + - run: zcat build/openrpc/full.json.gz | jq > ../post-openrpc-full + - run: zcat build/openrpc/miner.json.gz | jq > ../post-openrpc-miner + - run: zcat build/openrpc/worker.json.gz | jq > ../post-openrpc-worker + - run: git --no-pager diff + - run: diff ../pre-openrpc-full ../post-openrpc-full + - run: diff ../pre-openrpc-miner ../post-openrpc-miner + - run: diff ../pre-openrpc-worker ../post-openrpc-worker + - run: git --no-pager diff --quiet + + lint: &lint + description: | + Run golangci-lint. + parameters: + executor: + type: executor + default: golang + golangci-lint-version: + type: string + default: 1.27.0 + concurrency: + type: string + default: '2' + description: | + Concurrency used to run linters. Defaults to 2 because NumCPU is not + aware of container CPU limits. + args: + type: string + default: '' + description: | + Arguments to pass to golangci-lint + executor: << parameters.executor >> + steps: + - install-deps + - prepare + - run: + command: make deps + no_output_timeout: 30m + - go/install-golangci-lint: + gobin: $HOME/.local/bin + version: << parameters.golangci-lint-version >> + - run: + name: Lint + command: | + $HOME/.local/bin/golangci-lint run -v --timeout 2m \ + --concurrency << parameters.concurrency >> << parameters.args >> + lint-all: + <<: *lint + + publish: + description: publish binary artifacts + executor: ubuntu + steps: + - run: + name: Install git jq curl + command: apt update && apt install -y git jq curl + - checkout + - git_fetch_all_tags + - checkout + - install_ipfs + - attach_workspace: + at: "." + - run: + name: Create bundles + command: ./scripts/build-bundle.sh + - run: + name: Publish release + command: ./scripts/publish-release.sh + + publish-snapcraft: + description: build and push snapcraft + machine: + image: ubuntu-2004:202104-01 + resource_class: 2xlarge + parameters: + channel: + type: string + default: "edge" + description: snapcraft channel + steps: - checkout - run: - name: generate pipeline + name: install snapcraft + command: sudo snap install snapcraft --classic + - run: + name: create snapcraft config file command: | - cd .circleci - go run ./gen.go $(pwd) > generated_config.yml - - continuation/continue: - parameters: '{}' - configuration_path: .circleci/generated_config.yml + mkdir -p ~/.config/snapcraft + echo "$SNAPCRAFT_LOGIN_FILE" | base64 -d > ~/.config/snapcraft/snapcraft.cfg + - run: + name: build snap + command: snapcraft --use-lxd + - run: + name: publish snap + command: snapcraft push *.snap --release << parameters.channel >> + + build-and-push-image: + description: build and push docker images to public AWS ECR registry + executor: aws-cli/default + parameters: + profile-name: + type: string + default: "default" + description: AWS profile name to be configured. + + aws-access-key-id: + type: env_var_name + default: AWS_ACCESS_KEY_ID + description: > + AWS access key id for IAM role. Set this to the name of + the environment variable you will set to hold this + value, i.e. AWS_ACCESS_KEY. + + aws-secret-access-key: + type: env_var_name + default: AWS_SECRET_ACCESS_KEY + description: > + AWS secret key for IAM role. Set this to the name of + the environment variable you will set to hold this + value, i.e. AWS_SECRET_ACCESS_KEY. + + region: + type: env_var_name + default: AWS_REGION + description: > + Name of env var storing your AWS region information, + defaults to AWS_REGION + + account-url: + type: env_var_name + default: AWS_ECR_ACCOUNT_URL + description: > + Env var storing Amazon ECR account URL that maps to an AWS account, + e.g. {awsAccountNum}.dkr.ecr.us-west-2.amazonaws.com + defaults to AWS_ECR_ACCOUNT_URL + + dockerfile: + type: string + default: Dockerfile + description: Name of dockerfile to use. Defaults to Dockerfile. + + path: + type: string + default: . + description: Path to the directory containing your Dockerfile and build context. Defaults to . (working directory). + + extra-build-args: + type: string + default: "" + description: > + Extra flags to pass to docker build. For examples, see + https://docs.docker.com/engine/reference/commandline/build + + repo: + type: string + description: Name of an Amazon ECR repository + + tag: + type: string + default: "latest" + description: A comma-separated string containing docker image tags to build and push (default = latest) + + steps: + - run: + name: Confirm that environment variables are set + command: | + if [ -z "$AWS_ACCESS_KEY_ID" ]; then + echo "No AWS_ACCESS_KEY_ID is set. Skipping build-and-push job ..." + circleci-agent step halt + fi + + - aws-cli/setup: + profile-name: <> + aws-access-key-id: <> + aws-secret-access-key: <> + aws-region: <> + + - run: + name: Log into Amazon ECR + command: | + aws ecr-public get-login-password --region $<> --profile <> | docker login --username AWS --password-stdin $<> + + - checkout + + - setup_remote_docker: + version: 19.03.13 + docker_layer_caching: false + + - run: + name: Build docker image + command: | + registry_id=$(echo $<> | sed "s;\..*;;g") + + docker_tag_args="" + IFS="," read -ra DOCKER_TAGS \<<< "<< parameters.tag >>" + for tag in "${DOCKER_TAGS[@]}"; do + docker_tag_args="$docker_tag_args -t $<>/<>:$tag" + done + + docker build \ + <<#parameters.extra-build-args>><><> \ + -f <>/<> \ + $docker_tag_args \ + <> + + - run: + name: Push image to Amazon ECR + command: | + IFS="," read -ra DOCKER_TAGS \<<< "<< parameters.tag >>" + for tag in "${DOCKER_TAGS[@]}"; do + docker push $<>/<>:${tag} + done + + publish-packer-mainnet: + description: build and push AWS IAM and DigitalOcean droplet. + executor: + name: packer/default + packer-version: 1.6.6 + steps: + - checkout + - attach_workspace: + at: "." + - packer/build: + template: tools/packer/lotus.pkr.hcl + args: "-var ci_workspace_bins=./linux -var lotus_network=mainnet -var git_tag=$CIRCLE_TAG" + publish-packer-calibrationnet: + description: build and push AWS IAM and DigitalOcean droplet. + executor: + name: packer/default + packer-version: 1.6.6 + steps: + - checkout + - attach_workspace: + at: "." + - packer/build: + template: tools/packer/lotus.pkr.hcl + args: "-var ci_workspace_bins=./linux-calibrationnet -var lotus_network=calibrationnet -var git_tag=$CIRCLE_TAG" + publish-packer-butterflynet: + description: build and push AWS IAM and DigitalOcean droplet. + executor: + name: packer/default + packer-version: 1.6.6 + steps: + - checkout + - attach_workspace: + at: "." + - packer/build: + template: tools/packer/lotus.pkr.hcl + args: "-var ci_workspace_bins=./linux-butterflynet -var lotus_network=butterflynet -var git_tag=$CIRCLE_TAG" + publish-packer-nerpanet: + description: build and push AWS IAM and DigitalOcean droplet. + executor: + name: packer/default + packer-version: 1.6.6 + steps: + - checkout + - attach_workspace: + at: "." + - packer/build: + template: tools/packer/lotus.pkr.hcl + args: "-var ci_workspace_bins=./linux-nerpanet -var lotus_network=nerpanet -var git_tag=$CIRCLE_TAG" + workflows: - setup-workflow: + version: 2.1 + ci: jobs: - - generate-config \ No newline at end of file + - lint-all: + concurrency: "16" # expend all docker 2xlarge CPUs. + - mod-tidy-check + - gofmt + - gen-check + - docs-check + - test: + name: test-itest-api + suite: itest-api + target: "./itests/api_test.go" + + - test: + name: test-itest-batch_deal + suite: itest-batch_deal + target: "./itests/batch_deal_test.go" + + - test: + name: test-itest-ccupgrade + suite: itest-ccupgrade + target: "./itests/ccupgrade_test.go" + + - test: + name: test-itest-cli + suite: itest-cli + target: "./itests/cli_test.go" + + - test: + name: test-itest-deadlines + suite: itest-deadlines + target: "./itests/deadlines_test.go" + + - test: + name: test-itest-deals + suite: itest-deals + target: "./itests/deals_test.go" + + - test: + name: test-itest-gateway + suite: itest-gateway + target: "./itests/gateway_test.go" + + - test: + name: test-itest-multisig + suite: itest-multisig + target: "./itests/multisig_test.go" + + - test: + name: test-itest-paych_api + suite: itest-paych_api + target: "./itests/paych_api_test.go" + + - test: + name: test-itest-paych_cli + suite: itest-paych_cli + target: "./itests/paych_cli_test.go" + + - test: + name: test-itest-sdr_upgrade + suite: itest-sdr_upgrade + target: "./itests/sdr_upgrade_test.go" + + - test: + name: test-itest-sector_pledge + suite: itest-sector_pledge + target: "./itests/sector_pledge_test.go" + + - test: + name: test-itest-sector_terminate + suite: itest-sector_terminate + target: "./itests/sector_terminate_test.go" + + - test: + name: test-itest-tape + suite: itest-tape + target: "./itests/tape_test.go" + + - test: + name: test-itest-verifreg + suite: itest-verifreg + target: "./itests/verifreg_test.go" + + - test: + name: test-itest-wdpost_dispute + suite: itest-wdpost_dispute + target: "./itests/wdpost_dispute_test.go" + + - test: + name: test-itest-wdpost + suite: itest-wdpost + target: "./itests/wdpost_test.go" + + - test: + name: test-unit-cli + suite: utest-unit-cli + target: "./cli/... ./cmd/... ./api/..." + - test: + name: test-unit-node + suite: utest-unit-node + target: "./node/..." + - test: + name: test-unit-rest + suite: utest-unit-rest + target: "./blockstore/... ./build/... ./cmd/... ./extern/... ./paychmgr/... ./storage/... ./api/... ./conformance/... ./journal/... ./chain/... ./markets/... ./tools/... ./cli/... ./gateway/... ./lib/... ./node/..." + - test: + name: test-unit-storage + suite: utest-unit-storage + target: "./storage/... ./extern/..." + - test: + go-test-flags: "-run=TestMulticoreSDR" + suite: multicore-sdr-check + target: "./extern/sector-storage/ffiwrapper" + proofs-log-test: "1" + - test-conformance: + suite: conformance + codecov-upload: false + target: "./conformance" + - test-conformance: + name: test-conformance-bleeding-edge + codecov-upload: false + suite: conformance-bleeding-edge + target: "./conformance" + vectors-branch: master + - trigger-testplans: + filters: + branches: + only: + - master + - build-debug + - build-all: + filters: + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-ntwk-calibration: + filters: + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-ntwk-butterfly: + filters: + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-ntwk-nerpa: + filters: + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-lotus-soup + - build-macos: + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-appimage: + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - publish: + requires: + - build-all + - build-macos + - build-appimage + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-and-push-image: + dockerfile: Dockerfile.lotus + path: . + repo: lotus-dev + tag: '${CIRCLE_SHA1:0:8}' + - publish-packer-mainnet: + requires: + - build-all + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - publish-packer-calibrationnet: + requires: + - build-ntwk-calibration + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - publish-packer-butterflynet: + requires: + - build-ntwk-butterfly + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - publish-packer-nerpanet: + requires: + - build-ntwk-nerpa + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - publish-snapcraft: + name: publish-snapcraft-stable + channel: stable + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + + nightly: + triggers: + - schedule: + cron: "0 0 * * *" + filters: + branches: + only: + - master + jobs: + - publish-snapcraft: + name: publish-snapcraft-nightly + channel: edge diff --git a/.circleci/gen.go b/.circleci/gen.go index db806b003..f5c56c6e3 100644 --- a/.circleci/gen.go +++ b/.circleci/gen.go @@ -9,6 +9,8 @@ import ( "text/template" ) +//go:generate go run ./gen.go .. + //go:embed template.yml var templateFile embed.FS @@ -43,7 +45,7 @@ func main() { tmpl = template.Must(tmpl.ParseFS(templateFile, "*")) // list all itests. - itests, err := filepath.Glob("./itests/*_test.go") + itests, err := filepath.Glob(filepath.Join(repo, "./itests/*_test.go")) if err != nil { panic(err) } @@ -117,8 +119,14 @@ func main() { }(), } + out, err := os.Create("./config.yml") + if err != nil { + panic(err) + } + defer out.Close() + // execute the template. - if err := tmpl.Execute(os.Stdout, in); err != nil { + if err := tmpl.Execute(out, in); err != nil { panic(err) } } diff --git a/.circleci/go.mod b/.circleci/go.mod deleted file mode 100644 index 9b92b8c78..000000000 --- a/.circleci/go.mod +++ /dev/null @@ -1 +0,0 @@ -module ".circleci" \ No newline at end of file diff --git a/.circleci/template.yml b/.circleci/template.yml index fad4cdee5..e2227f2fe 100644 --- a/.circleci/template.yml +++ b/.circleci/template.yml @@ -174,7 +174,7 @@ jobs: -- \ << parameters.coverage >> \ << parameters.go-test-flags >> \ - << parameters.packages >> + << parameters.target >> no_output_timeout: 30m - store_test_results: path: /tmp/test-reports @@ -762,8 +762,7 @@ workflows: suite: utest-[[ $suite ]] target: "[[ $pkgs ]]" [[- end]] - - - check-proofs-multicore-sdr: + - test: go-test-flags: "-run=TestMulticoreSDR" suite: multicore-sdr-check target: "./extern/sector-storage/ffiwrapper" @@ -785,37 +784,27 @@ workflows: - master - build-debug - build-all: - requires: - - test-short filters: tags: only: - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - build-ntwk-calibration: - requires: - - test-short filters: tags: only: - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - build-ntwk-butterfly: - requires: - - test-short filters: tags: only: - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - build-ntwk-nerpa: - requires: - - test-short filters: tags: only: - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - build-lotus-soup - build-macos: - requires: - - test-short filters: branches: ignore: @@ -824,8 +813,6 @@ workflows: only: - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - build-appimage: - requires: - - test-short filters: branches: ignore: From 539f8c1f61c688768260656441a19643990313f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Tue, 22 Jun 2021 21:25:04 +0100 Subject: [PATCH 211/257] fix test that references private symbols; avoid hacky test selection flags. --- itests/deadlines_test.go | 4 -- itests/gateway_test.go | 3 +- itests/multisig/suite.go | 96 +++++++++++++++++++++++++++++++++ itests/multisig_test.go | 91 +------------------------------ itests/sector_terminate_test.go | 4 -- itests/wdpost_dispute_test.go | 8 --- itests/wdpost_test.go | 13 ----- 7 files changed, 100 insertions(+), 119 deletions(-) create mode 100644 itests/multisig/suite.go diff --git a/itests/deadlines_test.go b/itests/deadlines_test.go index 9551465a5..bef72297f 100644 --- a/itests/deadlines_test.go +++ b/itests/deadlines_test.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "fmt" - "os" "testing" "time" @@ -55,9 +54,6 @@ import ( // * asserts that miner B loses power // * asserts that miner D loses power, is inactive 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") diff --git a/itests/gateway_test.go b/itests/gateway_test.go index f5eb4e363..c7fe59444 100644 --- a/itests/gateway_test.go +++ b/itests/gateway_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/itests/multisig" "github.com/stretchr/testify/require" "golang.org/x/xerrors" @@ -188,7 +189,7 @@ func TestGatewayMsigCLI(t *testing.T) { defer nodes.closer() lite := nodes.lite - runMultisigTests(t, lite) + multisig.RunMultisigTests(t, lite) } func TestGatewayDealFlow(t *testing.T) { diff --git a/itests/multisig/suite.go b/itests/multisig/suite.go new file mode 100644 index 000000000..31113cb10 --- /dev/null +++ b/itests/multisig/suite.go @@ -0,0 +1,96 @@ +package multisig + +import ( + "context" + "fmt" + "regexp" + "strings" + "testing" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cli" + "github.com/filecoin-project/lotus/itests/kit" + "github.com/stretchr/testify/require" +) + +func RunMultisigTests(t *testing.T, clientNode kit.TestFullNode) { + // Create mock CLI + ctx := context.Background() + mockCLI := kit.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) + + kit.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 4c513640d..f30b26641 100644 --- a/itests/multisig_test.go +++ b/itests/multisig_test.go @@ -2,18 +2,12 @@ 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/filecoin-project/lotus/itests/kit" - "github.com/stretchr/testify/require" + "github.com/filecoin-project/lotus/itests/multisig" ) // TestMultisig does a basic test to exercise the multisig CLI commands @@ -25,86 +19,5 @@ func TestMultisig(t *testing.T) { ctx := context.Background() clientNode, _ := kit.StartOneNodeOneMiner(ctx, t, blocktime) - runMultisigTests(t, clientNode) -} - -func runMultisigTests(t *testing.T, clientNode kit.TestFullNode) { - // Create mock CLI - ctx := context.Background() - mockCLI := kit.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) - - kit.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) + multisig.RunMultisigTests(t, clientNode) } diff --git a/itests/sector_terminate_test.go b/itests/sector_terminate_test.go index b00337c7e..84260fd9d 100644 --- a/itests/sector_terminate_test.go +++ b/itests/sector_terminate_test.go @@ -18,10 +18,6 @@ import ( ) 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") - } - kit.QuietMiningLogs() const blocktime = 2 * time.Millisecond diff --git a/itests/wdpost_dispute_test.go b/itests/wdpost_dispute_test.go index 6c7302af3..b453fee2e 100644 --- a/itests/wdpost_dispute_test.go +++ b/itests/wdpost_dispute_test.go @@ -23,10 +23,6 @@ import ( ) 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") - } - kit.QuietMiningLogs() b := kit.MockMinerBuilder @@ -254,10 +250,6 @@ func TestWindowPostDispute(t *testing.T) { } 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") - } - kit.QuietMiningLogs() b := kit.MockMinerBuilder diff --git a/itests/wdpost_test.go b/itests/wdpost_test.go index f59465f05..70f526e2b 100644 --- a/itests/wdpost_test.go +++ b/itests/wdpost_test.go @@ -3,7 +3,6 @@ package itests import ( "context" "fmt" - "os" "testing" "time" @@ -23,10 +22,6 @@ import ( ) 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") - } - kit.QuietMiningLogs() var ( @@ -264,10 +259,6 @@ func testWindowPostUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Durati } func TestWindowPostBaseFeeNoBurn(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") - } - kit.QuietMiningLogs() var ( @@ -323,10 +314,6 @@ waitForProof: } func TestWindowPostBaseFeeBurn(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") - } - kit.QuietMiningLogs() ctx, cancel := context.WithCancel(context.Background()) From c6c202d7d403fb01d320067659fa34dcac89b71f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Tue, 22 Jun 2021 21:25:36 +0100 Subject: [PATCH 212/257] add makefile target. --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index e6c5f1b54..5821922ea 100644 --- a/Makefile +++ b/Makefile @@ -379,3 +379,6 @@ docsgen-cli: lotus lotus-miner lotus-worker print-%: @echo $*=$($*) + +circleci: + go generate -x ./.circleci \ No newline at end of file From b74ad796ce6224b48465c4cbb39b3cab1761b034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Tue, 22 Jun 2021 21:29:07 +0100 Subject: [PATCH 213/257] fix dangling import. --- itests/sector_terminate_test.go | 1 - itests/wdpost_dispute_test.go | 1 - 2 files changed, 2 deletions(-) diff --git a/itests/sector_terminate_test.go b/itests/sector_terminate_test.go index 84260fd9d..74dbc5fa0 100644 --- a/itests/sector_terminate_test.go +++ b/itests/sector_terminate_test.go @@ -3,7 +3,6 @@ package itests import ( "context" "fmt" - "os" "testing" "time" diff --git a/itests/wdpost_dispute_test.go b/itests/wdpost_dispute_test.go index b453fee2e..ad7024823 100644 --- a/itests/wdpost_dispute_test.go +++ b/itests/wdpost_dispute_test.go @@ -3,7 +3,6 @@ package itests import ( "context" "fmt" - "os" "testing" "time" From da96414bf80d847ed396fbca04c5d4c54141eed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Tue, 22 Jun 2021 21:32:10 +0100 Subject: [PATCH 214/257] switch test output format. --- .circleci/config.yml | 4 ++-- .circleci/template.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f41a8c513..4f6036f45 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -136,7 +136,7 @@ jobs: description: Test suite name to report to CircleCI. gotestsum-format: type: string - default: pkgname-and-test-fails + default: standard-verbose description: gotestsum format. https://github.com/gotestyourself/gotestsum#format coverage: type: string @@ -842,7 +842,7 @@ workflows: - test: name: test-unit-rest suite: utest-unit-rest - target: "./blockstore/... ./build/... ./cmd/... ./extern/... ./paychmgr/... ./storage/... ./api/... ./conformance/... ./journal/... ./chain/... ./markets/... ./tools/... ./cli/... ./gateway/... ./lib/... ./node/..." + target: "./blockstore/... ./build/... ./journal/... ./cli/... ./conformance/... ./node/... ./paychmgr/... ./api/... ./chain/... ./lib/... ./cmd/... ./extern/... ./gateway/... ./markets/... ./storage/... ./tools/..." - test: name: test-unit-storage suite: utest-unit-storage diff --git a/.circleci/template.yml b/.circleci/template.yml index e2227f2fe..75fbdfc76 100644 --- a/.circleci/template.yml +++ b/.circleci/template.yml @@ -136,7 +136,7 @@ jobs: description: Test suite name to report to CircleCI. gotestsum-format: type: string - default: pkgname-and-test-fails + default: standard-verbose description: gotestsum format. https://github.com/gotestyourself/gotestsum#format coverage: type: string From 0b06de2bd3524bcdfff3a84d8dd628e666ad5bfc Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 21 Jun 2021 11:41:06 -0700 Subject: [PATCH 215/257] fix(lotus-sim): unembed Node from Simulation I wanted to expose the node's _fields_, but this also exposed the methods. That got rather confusing. (probably could have used a new type, but eh) foo --- cmd/lotus-sim/info.go | 2 +- cmd/lotus-sim/info_capacity.go | 2 +- cmd/lotus-sim/info_state.go | 2 +- cmd/lotus-sim/simulation/block.go | 2 +- cmd/lotus-sim/simulation/messages.go | 6 +++--- cmd/lotus-sim/simulation/simulation.go | 20 ++++++++++---------- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go index 759f2d815..864adb3bc 100644 --- a/cmd/lotus-sim/info.go +++ b/cmd/lotus-sim/info.go @@ -42,7 +42,7 @@ func printInfo(ctx context.Context, sim *simulation.Simulation, out io.Writer) e if powerLookbackEpoch < start.Height() { powerLookbackEpoch = start.Height() } - lookbackTs, err := sim.Chainstore.GetTipsetByHeight(ctx, powerLookbackEpoch, head, false) + lookbackTs, err := sim.Node.Chainstore.GetTipsetByHeight(ctx, powerLookbackEpoch, head, false) if err != nil { return err } diff --git a/cmd/lotus-sim/info_capacity.go b/cmd/lotus-sim/info_capacity.go index 8ba603c20..4372ee34a 100644 --- a/cmd/lotus-sim/info_capacity.go +++ b/cmd/lotus-sim/info_capacity.go @@ -39,7 +39,7 @@ var infoCapacityGrowthSimCommand = &cli.Command{ lastHeight := ts.Height() for ts.Height() > firstEpoch && cctx.Err() == nil { - ts, err = sim.Chainstore.LoadTipSet(ts.Parents()) + ts, err = sim.Node.Chainstore.LoadTipSet(ts.Parents()) if err != nil { return err } diff --git a/cmd/lotus-sim/info_state.go b/cmd/lotus-sim/info_state.go index 19a31e19f..5c9541513 100644 --- a/cmd/lotus-sim/info_state.go +++ b/cmd/lotus-sim/info_state.go @@ -131,7 +131,7 @@ var infoStateGrowthSimCommand = &cli.Command{ fmt.Fprintf(cctx.App.Writer, "%d: %s\n", ts.Height(), types.SizeStr(types.NewInt(parentStateSize))) } - ts, err = sim.Chainstore.LoadTipSet(ts.Parents()) + ts, err = sim.Node.Chainstore.LoadTipSet(ts.Parents()) if err != nil { return err } diff --git a/cmd/lotus-sim/simulation/block.go b/cmd/lotus-sim/simulation/block.go index 47d482f4e..93e6a3191 100644 --- a/cmd/lotus-sim/simulation/block.go +++ b/cmd/lotus-sim/simulation/block.go @@ -73,7 +73,7 @@ func (sim *Simulation) makeTipSet(ctx context.Context, messages []*types.Message Timestamp: uts, ElectionProof: &types.ElectionProof{WinCount: 1}, }} - err = sim.Chainstore.PersistBlockHeaders(blks...) + err = sim.Node.Chainstore.PersistBlockHeaders(blks...) if err != nil { return nil, xerrors.Errorf("failed to persist block headers: %w", err) } diff --git a/cmd/lotus-sim/simulation/messages.go b/cmd/lotus-sim/simulation/messages.go index 08e4c12d2..5bed27436 100644 --- a/cmd/lotus-sim/simulation/messages.go +++ b/cmd/lotus-sim/simulation/messages.go @@ -25,19 +25,19 @@ func toArray(store blockadt.Store, cids []cid.Cid) (cid.Cid, error) { // storeMessages packs a set of messages into a types.MsgMeta and returns the resulting CID. The // resulting CID is valid for the BlocKHeader's Messages field. -func (nd *Node) storeMessages(ctx context.Context, messages []*types.Message) (cid.Cid, error) { +func (sim *Simulation) storeMessages(ctx context.Context, messages []*types.Message) (cid.Cid, error) { // We store all messages as "bls" messages so they're executed in-order. This ensures // accurate gas accounting. It also ensures we don't, e.g., try to fund a miner after we // fail a pre-commit... var msgCids []cid.Cid for _, msg := range messages { - c, err := nd.Chainstore.PutMessage(msg) + c, err := sim.Node.Chainstore.PutMessage(msg) if err != nil { return cid.Undef, err } msgCids = append(msgCids, c) } - adtStore := nd.Chainstore.ActorStore(ctx) + adtStore := sim.Node.Chainstore.ActorStore(ctx) blsMsgArr, err := toArray(adtStore, msgCids) if err != nil { return cid.Undef, err diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go index c75be3261..d91d30eda 100644 --- a/cmd/lotus-sim/simulation/simulation.go +++ b/cmd/lotus-sim/simulation/simulation.go @@ -71,7 +71,7 @@ func (c *config) upgradeSchedule() (stmgr.UpgradeSchedule, error) { // Simulation specifies a lotus-sim simulation. type Simulation struct { - *Node + Node *Node StateManager *stmgr.StateManager name string @@ -87,7 +87,7 @@ type Simulation struct { // loadConfig loads a simulation's config from the datastore. This must be called on startup and may // be called to restore the config from-disk. func (sim *Simulation) loadConfig() error { - configBytes, err := sim.MetadataDS.Get(sim.key("config")) + configBytes, err := sim.Node.MetadataDS.Get(sim.key("config")) if err == nil { err = json.Unmarshal(configBytes, &sim.config) } @@ -108,7 +108,7 @@ func (sim *Simulation) saveConfig() error { if err != nil { return err } - return sim.MetadataDS.Put(sim.key("config"), buf) + return sim.Node.MetadataDS.Put(sim.key("config"), buf) } var simulationPrefix = datastore.NewKey("/simulation") @@ -121,7 +121,7 @@ func (sim *Simulation) key(subkey string) datastore.Key { // loadNamedTipSet the tipset with the given name (for this simulation) func (sim *Simulation) loadNamedTipSet(name string) (*types.TipSet, error) { - tskBytes, err := sim.MetadataDS.Get(sim.key(name)) + tskBytes, err := sim.Node.MetadataDS.Get(sim.key(name)) if err != nil { return nil, xerrors.Errorf("failed to load tipset %s/%s: %w", sim.name, name, err) } @@ -129,7 +129,7 @@ func (sim *Simulation) loadNamedTipSet(name string) (*types.TipSet, error) { if err != nil { return nil, xerrors.Errorf("failed to parse tipste %v (%s/%s): %w", tskBytes, sim.name, name, err) } - ts, err := sim.Chainstore.LoadTipSet(tsk) + ts, err := sim.Node.Chainstore.LoadTipSet(tsk) if err != nil { return nil, xerrors.Errorf("failed to load tipset %s (%s/%s): %w", tsk, sim.name, name, err) } @@ -138,7 +138,7 @@ func (sim *Simulation) loadNamedTipSet(name string) (*types.TipSet, error) { // storeNamedTipSet stores the tipset at name (relative to the simulation). func (sim *Simulation) storeNamedTipSet(name string, ts *types.TipSet) error { - if err := sim.MetadataDS.Put(sim.key(name), ts.Key().Bytes()); err != nil { + if err := sim.Node.MetadataDS.Put(sim.key(name), ts.Key().Bytes()); err != nil { return xerrors.Errorf("failed to store tipset (%s/%s): %w", sim.name, name, err) } return nil @@ -198,7 +198,7 @@ func (sim *Simulation) SetUpgradeHeight(nv network.Version, epoch abi.ChainEpoch if err != nil { return err } - sm, err := stmgr.NewStateManagerWithUpgradeSchedule(sim.Chainstore, newUpgradeSchedule) + sm, err := stmgr.NewStateManagerWithUpgradeSchedule(sim.Node.Chainstore, newUpgradeSchedule) if err != nil { return err } @@ -241,7 +241,7 @@ func (sim *Simulation) Walk( stCid cid.Cid, messages []*AppliedMessage) error, ) error { - store := sim.Chainstore.ActorStore(ctx) + store := sim.Node.Chainstore.ActorStore(ctx) minEpoch := sim.start.Height() if lookback != 0 { minEpoch = sim.head.Height() - abi.ChainEpoch(lookback) @@ -305,7 +305,7 @@ func (sim *Simulation) Walk( stCid = ts.MinTicketBlock().ParentStateRoot recCid = ts.MinTicketBlock().ParentMessageReceipts - ts, err = sim.Chainstore.LoadTipSet(ts.Parents()) + ts, err = sim.Node.Chainstore.LoadTipSet(ts.Parents()) if err != nil { return xerrors.Errorf("loading parent: %w", err) } @@ -339,7 +339,7 @@ func (sim *Simulation) Walk( break } - msgs, err := sim.Chainstore.MessagesForTipset(job.ts) + msgs, err := sim.Node.Chainstore.MessagesForTipset(job.ts) if err != nil { return err } From 87c306fd47f1baab662b6357bc66b616c2487a31 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 22 Jun 2021 14:35:30 -0700 Subject: [PATCH 216/257] feat(lotus-sim): use current power instead of lookback I'd _really_ like to use lookback, but don't have that when starting from a snapshot. --- .../simulation/stages/precommit_stage.go | 31 ++++++------------- cmd/lotus-sim/simulation/stages/util.go | 30 ------------------ 2 files changed, 9 insertions(+), 52 deletions(-) diff --git a/cmd/lotus-sim/simulation/stages/precommit_stage.go b/cmd/lotus-sim/simulation/stages/precommit_stage.go index 3dcfee28f..5b9fed09e 100644 --- a/cmd/lotus-sim/simulation/stages/precommit_stage.go +++ b/cmd/lotus-sim/simulation/stages/precommit_stage.go @@ -16,7 +16,6 @@ import ( "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/aerrors" - "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/builtin/power" "github.com/filecoin-project/lotus/chain/actors/policy" @@ -26,9 +25,8 @@ import ( ) const ( - minPreCommitBatchSize = 1 - maxPreCommitBatchSize = miner5.PreCommitSectorBatchMaxSize - onboardingProjectionLookback = 2 * 7 * builtin.EpochsInDay // lookback two weeks + minPreCommitBatchSize = 1 + maxPreCommitBatchSize = miner5.PreCommitSectorBatchMaxSize ) type PreCommitStage struct { @@ -276,11 +274,6 @@ func (stage *PreCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBui "rest", stage.rest.len(), ) }() - lookbackEpoch := bb.Height() - onboardingProjectionLookback - lookbackPowerTable, err := loadClaims(ctx, bb, lookbackEpoch) - if err != nil { - return xerrors.Errorf("failed to load claims from lookback epoch %d: %w", lookbackEpoch, err) - } store := bb.ActorStore() st := bb.ParentStateTree() @@ -290,10 +283,10 @@ func (stage *PreCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBui } type onboardingInfo struct { - addr address.Address - onboardingRate uint64 + addr address.Address + sectorCount uint64 } - sealList := make([]onboardingInfo, 0, len(lookbackPowerTable)) + var sealList []onboardingInfo err = powerState.ForEachClaim(func(addr address.Address, claim power.Claim) error { if claim.RawBytePower.IsZero() { return nil @@ -308,16 +301,10 @@ func (stage *PreCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBui return err } - sectorsAdded := sectorsFromClaim(info.SectorSize, claim) - if lookbackClaim, ok := lookbackPowerTable[addr]; !ok { - sectorsAdded -= sectorsFromClaim(info.SectorSize, lookbackClaim) - } + sectorCount := sectorsFromClaim(info.SectorSize, claim) - // NOTE: power _could_ have been lost, but that's too much of a pain to care - // about. We _could_ look for faulty power by iterating through all - // deadlines, but I'd rather not. - if sectorsAdded > 0 { - sealList = append(sealList, onboardingInfo{addr, uint64(sectorsAdded)}) + if sectorCount > 0 { + sealList = append(sealList, onboardingInfo{addr, uint64(sectorCount)}) } return nil }) @@ -331,7 +318,7 @@ func (stage *PreCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBui // Now that we have a list of sealing miners, sort them into percentiles. sort.Slice(sealList, func(i, j int) bool { - return sealList[i].onboardingRate < sealList[j].onboardingRate + return sealList[i].sectorCount < sealList[j].sectorCount }) // reset, just in case. diff --git a/cmd/lotus-sim/simulation/stages/util.go b/cmd/lotus-sim/simulation/stages/util.go index 4c23a83d6..97c1e57af 100644 --- a/cmd/lotus-sim/simulation/stages/util.go +++ b/cmd/lotus-sim/simulation/stages/util.go @@ -43,36 +43,6 @@ func sectorsFromClaim(sectorSize abi.SectorSize, c power.Claim) int64 { return sectorCount.Int64() } -// loadClaims will load all non-zero claims at the given epoch. -func loadClaims( - ctx context.Context, bb *blockbuilder.BlockBuilder, height abi.ChainEpoch, -) (map[address.Address]power.Claim, error) { - powerTable := make(map[address.Address]power.Claim) - - st, err := bb.StateTreeByHeight(height) - if err != nil { - return nil, err - } - - powerState, err := loadPower(bb.ActorStore(), st) - if err != nil { - return nil, err - } - - err = powerState.ForEachClaim(func(miner address.Address, claim power.Claim) error { - // skip miners without power - if claim.RawBytePower.IsZero() { - return nil - } - powerTable[miner] = claim - return nil - }) - if err != nil { - return nil, err - } - return powerTable, nil -} - func postChainCommitInfo(ctx context.Context, bb *blockbuilder.BlockBuilder, epoch abi.ChainEpoch) (abi.Randomness, error) { cs := bb.StateManager().ChainStore() ts := bb.ParentTipSet() From 63e2caae81c168a0cf0f239ed3927c154c2b1150 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 22 Jun 2021 15:06:44 -0700 Subject: [PATCH 217/257] lint(lotus-sim): handle error --- cmd/lotus-sim/simulation/node.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go index acd63955d..5b8bf2bf9 100644 --- a/cmd/lotus-sim/simulation/node.go +++ b/cmd/lotus-sim/simulation/node.go @@ -46,7 +46,7 @@ func NewNode(ctx context.Context, r repo.Repo) (nd *Node, _err error) { } defer func() { if _err != nil { - lr.Close() + _ = lr.Close() } }() From 6ca2a148199b47d9f0eb0eb40fb4bc2a712967bd Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Tue, 22 Jun 2021 19:16:36 -0400 Subject: [PATCH 218/257] Always flush when timer goes off --- extern/storage-sealing/commit_batch.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index 61553601a..929d73306 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -108,7 +108,7 @@ func (b *CommitBatcher) run() { } lastMsg = nil - var sendAboveMax, sendAboveMin bool + var sendAboveMax bool select { case <-b.stop: close(b.stopped) @@ -116,13 +116,13 @@ func (b *CommitBatcher) run() { case <-b.notify: sendAboveMax = true case <-b.batchWait(cfg.CommitBatchWait, cfg.CommitBatchSlack): - sendAboveMin = true + // do nothing case fr := <-b.force: // user triggered forceRes = fr } var err error - lastMsg, err = b.maybeStartBatch(sendAboveMax, sendAboveMin) + lastMsg, err = b.maybeStartBatch(sendAboveMax) if err != nil { log.Warnw("CommitBatcher processBatch error", "error", err) } @@ -170,7 +170,7 @@ func (b *CommitBatcher) batchWait(maxWait, slack time.Duration) <-chan time.Time return time.After(wait) } -func (b *CommitBatcher) maybeStartBatch(notif, after bool) ([]sealiface.CommitBatchRes, error) { +func (b *CommitBatcher) maybeStartBatch(notif bool) ([]sealiface.CommitBatchRes, error) { b.lk.Lock() defer b.lk.Unlock() @@ -188,10 +188,6 @@ func (b *CommitBatcher) maybeStartBatch(notif, after bool) ([]sealiface.CommitBa return nil, nil } - if after && total < cfg.MinCommitBatch { - return nil, nil - } - var res []sealiface.CommitBatchRes if total < cfg.MinCommitBatch || total < miner5.MinAggregatedSectors { From 4af59e01880f4592cfbaa924bef34d460ccb830c Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Tue, 22 Jun 2021 19:23:24 -0400 Subject: [PATCH 219/257] Apply suggestions from code review Co-authored-by: Jennifer <42981373+jennijuju@users.noreply.github.com> --- cli/client.go | 2 +- documentation/en/cli-lotus.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/client.go b/cli/client.go index 2cd946424..06d39769d 100644 --- a/cli/client.go +++ b/cli/client.go @@ -309,7 +309,7 @@ var clientDealCmd = &cli.Command{ dataCid comes from running 'lotus client import'. miner is the address of the miner you wish to make a deal with. price is measured in FIL/Epoch. Miners usually don't accept a bid -lower than their advertised ask (which is in FIL/GiB/epoch). You can check a miners listed price +lower than their advertised ask (which is in FIL/GiB/Epoch). You can check a miners listed price with 'lotus client query-ask '. duration is how long the miner should store the data for, in blocks. The minimum value is 518400 (6 months).`, diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index 52ccf7ed3..a1583d522 100644 --- a/documentation/en/cli-lotus.md +++ b/documentation/en/cli-lotus.md @@ -559,7 +559,7 @@ DESCRIPTION: dataCid comes from running 'lotus client import'. miner is the address of the miner you wish to make a deal with. price is measured in FIL/Epoch. Miners usually don't accept a bid -lower than their advertised ask (which is in FIL/GiB/epoch). You can check a miners listed price +lower than their advertised ask (which is in FIL/GiB/Epoch). You can check a miners listed price with 'lotus client query-ask '. duration is how long the miner should store the data for, in blocks. The minimum value is 518400 (6 months). From 93f7cbe58717f78baa75ad556e100177bf07ae0c Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Tue, 22 Jun 2021 19:30:33 -0400 Subject: [PATCH 220/257] Add a helpful comment --- extern/storage-sealing/commit_batch.go | 1 + 1 file changed, 1 insertion(+) diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index 929d73306..3c0af1176 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -108,6 +108,7 @@ func (b *CommitBatcher) run() { } lastMsg = nil + // indicates whether we should only start a batch if we have reached or exceeded cfg.MaxCommitBatch var sendAboveMax bool select { case <-b.stop: From 2dd498297fd6cade7d8a5f4eb60892f73dee2e14 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Tue, 22 Jun 2021 19:34:03 -0400 Subject: [PATCH 221/257] Set HyperDrive upgrade epoch --- build/params_mainnet.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/params_mainnet.go b/build/params_mainnet.go index 2d708f9e4..d639cbf58 100644 --- a/build/params_mainnet.go +++ b/build/params_mainnet.go @@ -62,8 +62,8 @@ const UpgradeNorwegianHeight = 665280 // 2021-04-29T06:00:00Z const UpgradeTurboHeight = 712320 -// ??? -var UpgradeHyperdriveHeight = abi.ChainEpoch(9999999) +// 2021-06-30T22:00:00Z +var UpgradeHyperdriveHeight = abi.ChainEpoch(892800) func init() { policy.SetConsensusMinerMinPower(abi.NewStoragePower(10 << 40)) From 03261771e271ad8105721364381655a0c2116d43 Mon Sep 17 00:00:00 2001 From: wangchao Date: Wed, 23 Jun 2021 13:58:39 +0800 Subject: [PATCH 222/257] scale up sector expiration to avoid sector expire in batch-pre-commit waitting --- extern/storage-sealing/states_sealing.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/storage-sealing/states_sealing.go b/extern/storage-sealing/states_sealing.go index 718fbf28a..360eeafa6 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -274,7 +274,7 @@ func (m *Sealing) preCommitParams(ctx statemachine.Context, sector SectorInfo) ( msd := policy.GetMaxProveCommitDuration(actors.VersionForNetwork(nv), sector.SectorType) - if minExpiration := height + msd + miner.MinSectorExpiration + 10; expiration < minExpiration { + if minExpiration := sector.TicketEpoch + policy.MaxPreCommitRandomnessLookback + msd + miner.MinSectorExpiration; expiration < minExpiration { expiration = minExpiration } // TODO: enforce a reasonable _maximum_ sector lifetime? From 211751f8b8fe73c9f54d2bae3e0ef5239a181c2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Wed, 23 Jun 2021 16:37:43 +0100 Subject: [PATCH 223/257] declare some tests as expensive. --- itests/deadlines_test.go | 2 ++ itests/kit/run.go | 20 ++++++++++++++++++++ itests/sector_terminate_test.go | 2 ++ itests/wdpost_dispute_test.go | 4 ++++ itests/wdpost_test.go | 6 ++++++ 5 files changed, 34 insertions(+) create mode 100644 itests/kit/run.go diff --git a/itests/deadlines_test.go b/itests/deadlines_test.go index bef72297f..193cdf40b 100644 --- a/itests/deadlines_test.go +++ b/itests/deadlines_test.go @@ -54,6 +54,8 @@ import ( // * asserts that miner B loses power // * asserts that miner D loses power, is inactive func TestDeadlineToggling(t *testing.T) { + kit.Expensive(t) + _ = logging.SetLogLevel("miner", "ERROR") _ = logging.SetLogLevel("chainstore", "ERROR") _ = logging.SetLogLevel("chain", "ERROR") diff --git a/itests/kit/run.go b/itests/kit/run.go new file mode 100644 index 000000000..713efa3b8 --- /dev/null +++ b/itests/kit/run.go @@ -0,0 +1,20 @@ +package kit + +import ( + "os" + "testing" +) + +// EnvRunExpensiveTests is the environment variable that needs to be present +// and set to value "1" to enable running expensive tests outside of CI. +const EnvRunExpensiveTests = "LOTUS_RUN_EXPENSIVE_TESTS" + +// Expensive marks a test as expensive, skipping it immediately if not running an +func Expensive(t *testing.T) { + switch { + case os.Getenv("CI") == "true": + return + case os.Getenv(EnvRunExpensiveTests) != "1": + t.Skipf("skipping expensive test outside of CI; enable by setting env var %s=1", EnvRunExpensiveTests) + } +} diff --git a/itests/sector_terminate_test.go b/itests/sector_terminate_test.go index 74dbc5fa0..73b4e443c 100644 --- a/itests/sector_terminate_test.go +++ b/itests/sector_terminate_test.go @@ -17,6 +17,8 @@ import ( ) func TestTerminate(t *testing.T) { + kit.Expensive(t) + kit.QuietMiningLogs() const blocktime = 2 * time.Millisecond diff --git a/itests/wdpost_dispute_test.go b/itests/wdpost_dispute_test.go index ad7024823..b665667a3 100644 --- a/itests/wdpost_dispute_test.go +++ b/itests/wdpost_dispute_test.go @@ -22,6 +22,8 @@ import ( ) func TestWindowPostDispute(t *testing.T) { + kit.Expensive(t) + kit.QuietMiningLogs() b := kit.MockMinerBuilder @@ -249,6 +251,8 @@ func TestWindowPostDispute(t *testing.T) { } func TestWindowPostDisputeFails(t *testing.T) { + kit.Expensive(t) + kit.QuietMiningLogs() b := kit.MockMinerBuilder diff --git a/itests/wdpost_test.go b/itests/wdpost_test.go index 70f526e2b..81f938379 100644 --- a/itests/wdpost_test.go +++ b/itests/wdpost_test.go @@ -22,6 +22,8 @@ import ( ) func TestWindowedPost(t *testing.T) { + kit.Expensive(t) + kit.QuietMiningLogs() var ( @@ -259,6 +261,8 @@ func testWindowPostUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Durati } func TestWindowPostBaseFeeNoBurn(t *testing.T) { + kit.Expensive(t) + kit.QuietMiningLogs() var ( @@ -314,6 +318,8 @@ waitForProof: } func TestWindowPostBaseFeeBurn(t *testing.T) { + kit.Expensive(t) + kit.QuietMiningLogs() ctx, cancel := context.WithCancel(context.Background()) From 6017715fd7269379ea69ee1a2104c51ea042f4e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Wed, 23 Jun 2021 16:40:20 +0100 Subject: [PATCH 224/257] (test) deliberately check in an outdated .circleci/config.yml to test the gen-check CI target. --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4f6036f45..e73d42988 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -830,7 +830,6 @@ workflows: name: test-itest-wdpost suite: itest-wdpost target: "./itests/wdpost_test.go" - - test: name: test-unit-cli suite: utest-unit-cli From 0c31676bf8911882e540711eb57188f5f74d2256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Wed, 23 Jun 2021 16:46:13 +0100 Subject: [PATCH 225/257] Makefile: add circleci target as dependency of gen target. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5821922ea..1e7cc4cc0 100644 --- a/Makefile +++ b/Makefile @@ -364,7 +364,7 @@ docsgen-openrpc-worker: docsgen-openrpc-bin .PHONY: docsgen docsgen-md-bin docsgen-openrpc-bin -gen: actors-gen type-gen method-gen docsgen api-gen +gen: actors-gen type-gen method-gen docsgen api-gen circleci @echo ">>> IF YOU'VE MODIFIED THE CLI, REMEMBER TO ALSO MAKE docsgen-cli" .PHONY: gen From 2c605e08f1ccec9008c22d1e56c45cf1f1bfb26b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Wed, 23 Jun 2021 16:50:11 +0100 Subject: [PATCH 226/257] Revert "(test) deliberately check in an outdated .circleci/config.yml to test the gen-check CI target." This reverts commit 6017715fd7269379ea69ee1a2104c51ea042f4e0. --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index e73d42988..4f6036f45 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -830,6 +830,7 @@ workflows: name: test-itest-wdpost suite: itest-wdpost target: "./itests/wdpost_test.go" + - test: name: test-unit-cli suite: utest-unit-cli From f4341409a1a8d26769d774979468fe7418d593d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Wed, 23 Jun 2021 17:08:46 +0100 Subject: [PATCH 227/257] deterministic order for rest unit tests. --- .circleci/config.yml | 2 +- .circleci/gen.go | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4f6036f45..133fb4d96 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -842,7 +842,7 @@ workflows: - test: name: test-unit-rest suite: utest-unit-rest - target: "./blockstore/... ./build/... ./journal/... ./cli/... ./conformance/... ./node/... ./paychmgr/... ./api/... ./chain/... ./lib/... ./cmd/... ./extern/... ./gateway/... ./markets/... ./storage/... ./tools/..." + target: "./api/... ./blockstore/... ./build/... ./chain/... ./cli/... ./cmd/... ./conformance/... ./extern/... ./gateway/... ./journal/... ./lib/... ./markets/... ./node/... ./paychmgr/... ./storage/... ./tools/..." - test: name: test-unit-storage suite: utest-unit-storage diff --git a/.circleci/gen.go b/.circleci/gen.go index f5c56c6e3..844348e29 100644 --- a/.circleci/gen.go +++ b/.circleci/gen.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "sort" "strings" "text/template" ) @@ -100,6 +101,9 @@ func main() { groupedUnitTests["unit-rest"] = append(groupedUnitTests["unit-rest"], k) } + // map iteration guarantees no order, so sort the array in-place. + sort.Strings(groupedUnitTests["unit-rest"]) + // form the input data. type data struct { ItestFiles []string From 616e5688fc4a62f3747b1983785b2844e32ba0f2 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Wed, 23 Jun 2021 12:30:32 -0400 Subject: [PATCH 228/257] Remove MinPreCommitBatch --- extern/storage-sealing/precommit_batch.go | 12 ++++-------- extern/storage-sealing/sealiface/config.go | 1 - node/config/def.go | 2 -- node/modules/storageminer.go | 2 -- 4 files changed, 4 insertions(+), 13 deletions(-) diff --git a/extern/storage-sealing/precommit_batch.go b/extern/storage-sealing/precommit_batch.go index 9439ae14c..d1d2f5878 100644 --- a/extern/storage-sealing/precommit_batch.go +++ b/extern/storage-sealing/precommit_batch.go @@ -93,7 +93,7 @@ func (b *PreCommitBatcher) run() { } lastRes = nil - var sendAboveMax, sendAboveMin bool + var sendAboveMax bool select { case <-b.stop: close(b.stopped) @@ -101,13 +101,13 @@ func (b *PreCommitBatcher) run() { case <-b.notify: sendAboveMax = true case <-b.batchWait(cfg.PreCommitBatchWait, cfg.PreCommitBatchSlack): - sendAboveMin = true + // do nothing case fr := <-b.force: // user triggered forceRes = fr } var err error - lastRes, err = b.maybeStartBatch(sendAboveMax, sendAboveMin) + lastRes, err = b.maybeStartBatch(sendAboveMax) if err != nil { log.Warnw("PreCommitBatcher processBatch error", "error", err) } @@ -155,7 +155,7 @@ func (b *PreCommitBatcher) batchWait(maxWait, slack time.Duration) <-chan time.T return time.After(wait) } -func (b *PreCommitBatcher) maybeStartBatch(notif, after bool) ([]sealiface.PreCommitBatchRes, error) { +func (b *PreCommitBatcher) maybeStartBatch(notif bool) ([]sealiface.PreCommitBatchRes, error) { b.lk.Lock() defer b.lk.Unlock() @@ -173,10 +173,6 @@ func (b *PreCommitBatcher) maybeStartBatch(notif, after bool) ([]sealiface.PreCo return nil, nil } - if after && total < cfg.MinPreCommitBatch { - return nil, nil - } - // todo support multiple batches res, err := b.processBatch(cfg) if err != nil && len(res) == 0 { diff --git a/extern/storage-sealing/sealiface/config.go b/extern/storage-sealing/sealiface/config.go index 499a2befa..b237072d3 100644 --- a/extern/storage-sealing/sealiface/config.go +++ b/extern/storage-sealing/sealiface/config.go @@ -22,7 +22,6 @@ type Config struct { BatchPreCommits bool MaxPreCommitBatch int - MinPreCommitBatch int PreCommitBatchWait time.Duration PreCommitBatchSlack time.Duration diff --git a/node/config/def.go b/node/config/def.go index 177871cc5..d1beb843b 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -93,7 +93,6 @@ type SealingConfig struct { BatchPreCommits bool // maximum precommit batch size - batches will be sent immediately above this size MaxPreCommitBatch int - MinPreCommitBatch int // how long to wait before submitting a batch after crossing the minimum batch size PreCommitBatchWait Duration // time buffer for forceful batch submission before sectors/deal in batch would start expiring @@ -285,7 +284,6 @@ func DefaultStorageMiner() *StorageMiner { FinalizeEarly: false, BatchPreCommits: true, - MinPreCommitBatch: 1, // we must have at least one precommit to batch MaxPreCommitBatch: miner5.PreCommitSectorBatchMaxSize, // up to 256 sectors PreCommitBatchWait: Duration(24 * time.Hour), // this should be less than 31.5 hours, which is the expiration of a precommit ticket PreCommitBatchSlack: Duration(3 * time.Hour), // time buffer for forceful batch submission before sectors/deals in batch would start expiring, higher value will lower the chances for message fail due to expiration diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index b7aae0f44..3a28d5c85 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -830,7 +830,6 @@ func NewSetSealConfigFunc(r repo.LockedRepo) (dtypes.SetSealingConfigFunc, error FinalizeEarly: cfg.FinalizeEarly, BatchPreCommits: cfg.BatchPreCommits, - MinPreCommitBatch: cfg.MinPreCommitBatch, MaxPreCommitBatch: cfg.MaxPreCommitBatch, PreCommitBatchWait: config.Duration(cfg.PreCommitBatchWait), PreCommitBatchSlack: config.Duration(cfg.PreCommitBatchSlack), @@ -862,7 +861,6 @@ func NewGetSealConfigFunc(r repo.LockedRepo) (dtypes.GetSealingConfigFunc, error FinalizeEarly: cfg.Sealing.FinalizeEarly, BatchPreCommits: cfg.Sealing.BatchPreCommits, - MinPreCommitBatch: cfg.Sealing.MinPreCommitBatch, MaxPreCommitBatch: cfg.Sealing.MaxPreCommitBatch, PreCommitBatchWait: time.Duration(cfg.Sealing.PreCommitBatchWait), PreCommitBatchSlack: time.Duration(cfg.Sealing.PreCommitBatchSlack), From 779626ceec9beec82c15f17118bd388b22134f83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Wed, 23 Jun 2021 18:05:20 +0100 Subject: [PATCH 229/257] fix circleci. --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index f4f9b421d..133fb4d96 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -440,6 +440,7 @@ jobs: paths: - appimage + gofmt: executor: golang steps: From 9b3188d110bf85148d0bbe26549c589f303b3ba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Wed, 23 Jun 2021 18:13:29 +0100 Subject: [PATCH 230/257] split deals tests. --- .circleci/config.yml | 25 ++ itests/deals_concurrent_test.go | 49 ++++ itests/deals_offline_test.go | 101 ++++++++ itests/deals_power_test.go | 61 +++++ itests/deals_pricing_test.go | 131 ++++++++++ itests/deals_publish_test.go | 108 ++++++++ itests/deals_test.go | 441 +------------------------------- itests/kit/deals.go | 43 +++- 8 files changed, 517 insertions(+), 442 deletions(-) create mode 100644 itests/deals_concurrent_test.go create mode 100644 itests/deals_offline_test.go create mode 100644 itests/deals_power_test.go create mode 100644 itests/deals_pricing_test.go create mode 100644 itests/deals_publish_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 133fb4d96..9a6f377f1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -771,6 +771,31 @@ workflows: suite: itest-deadlines target: "./itests/deadlines_test.go" + - test: + name: test-itest-deals_concurrent + suite: itest-deals_concurrent + target: "./itests/deals_concurrent_test.go" + + - test: + name: test-itest-deals_offline + suite: itest-deals_offline + target: "./itests/deals_offline_test.go" + + - test: + name: test-itest-deals_power + suite: itest-deals_power + target: "./itests/deals_power_test.go" + + - test: + name: test-itest-deals_pricing + suite: itest-deals_pricing + target: "./itests/deals_pricing_test.go" + + - test: + name: test-itest-deals_publish + suite: itest-deals_publish + target: "./itests/deals_publish_test.go" + - test: name: test-itest-deals suite: itest-deals diff --git a/itests/deals_concurrent_test.go b/itests/deals_concurrent_test.go new file mode 100644 index 000000000..33a8218dd --- /dev/null +++ b/itests/deals_concurrent_test.go @@ -0,0 +1,49 @@ +package itests + +import ( + "fmt" + "testing" + "time" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/itests/kit" +) + +func TestDealCyclesConcurrent(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode") + } + + kit.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 + startEpoch := abi.ChainEpoch(2 << 12) + + runTest := func(t *testing.T, n int, fastRetrieval bool, carExport bool) { + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) + ens.InterconnectAll().BeginMining(blockTime) + dh := kit.NewDealHarness(t, client, miner) + + dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{ + N: n, + FastRetrieval: fastRetrieval, + CarExport: carExport, + StartEpoch: startEpoch, + }) + } + + // TODO: add 2, 4, 8, more when this graphsync issue is fixed: https://github.com/ipfs/go-graphsync/issues/175# + cycles := []int{1} + for _, n := range cycles { + n := n + ns := fmt.Sprintf("%d", n) + t.Run(ns+"-fastretrieval-CAR", func(t *testing.T) { runTest(t, n, true, true) }) + t.Run(ns+"-fastretrieval-NoCAR", func(t *testing.T) { runTest(t, n, true, false) }) + t.Run(ns+"-stdretrieval-CAR", func(t *testing.T) { runTest(t, n, true, false) }) + t.Run(ns+"-stdretrieval-NoCAR", func(t *testing.T) { runTest(t, n, false, false) }) + } +} diff --git a/itests/deals_offline_test.go b/itests/deals_offline_test.go new file mode 100644 index 000000000..c3f19048b --- /dev/null +++ b/itests/deals_offline_test.go @@ -0,0 +1,101 @@ +package itests + +import ( + "context" + "path/filepath" + "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/build" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/itests/kit" + "github.com/stretchr/testify/require" +) + +func TestOfflineDealFlow(t *testing.T) { + 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 + startEpoch := abi.ChainEpoch(2 << 12) + + runTest := func(t *testing.T, fastRet bool) { + ctx := context.Background() + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) + ens.InterconnectAll().BeginMining(blocktime) + + dh := kit.NewDealHarness(t, client, miner) + + // Create a random file and import on the client. + res, inFile := client.CreateImportFile(ctx, 1, 0) + + // Get the piece size and commP + rootCid := res.Root + pieceInfo, err := client.ClientDealPieceCID(ctx, rootCid) + require.NoError(t, err) + t.Log("FILE CID:", rootCid) + + // Create a storage deal with the miner + maddr, err := miner.ActorAddress(ctx) + require.NoError(t, err) + + addr, err := client.WalletDefaultAddress(ctx) + require.NoError(t, err) + + // Manual storage deal (offline deal) + dataRef := &storagemarket.DataRef{ + TransferType: storagemarket.TTManual, + Root: rootCid, + PieceCid: &pieceInfo.PieceCID, + PieceSize: pieceInfo.PieceSize.Unpadded(), + } + + proposalCid, err := client.ClientStartDeal(ctx, &api.StartDealParams{ + Data: dataRef, + Wallet: addr, + Miner: maddr, + EpochPrice: types.NewInt(1000000), + DealStartEpoch: startEpoch, + MinBlocksDuration: uint64(build.MinDealDuration), + FastRetrieval: fastRet, + }) + require.NoError(t, err) + + // Wait for the deal to reach StorageDealCheckForAcceptance on the client + cd, err := client.ClientGetDealInfo(ctx, *proposalCid) + require.NoError(t, err) + require.Eventually(t, func() bool { + cd, _ := client.ClientGetDealInfo(ctx, *proposalCid) + return cd.State == storagemarket.StorageDealCheckForAcceptance + }, 30*time.Second, 1*time.Second, "actual deal status is %s", storagemarket.DealStates[cd.State]) + + // Create a CAR file from the raw file + carFileDir := t.TempDir() + carFilePath := filepath.Join(carFileDir, "out.car") + err = client.ClientGenCar(ctx, api.FileRef{Path: inFile}, carFilePath) + require.NoError(t, err) + + // Import the CAR file on the miner - this is the equivalent to + // transferring the file across the wire in a normal (non-offline) deal + err = miner.DealsImportData(ctx, *proposalCid, carFilePath) + require.NoError(t, err) + + // Wait for the deal to be published + dh.WaitDealPublished(ctx, proposalCid) + + t.Logf("deal published, retrieving") + + // Retrieve the deal + outFile := dh.PerformRetrieval(ctx, proposalCid, rootCid, false) + + kit.AssertFilesEqual(t, inFile, outFile) + + } + + t.Run("stdretrieval", func(t *testing.T) { runTest(t, false) }) + t.Run("fastretrieval", func(t *testing.T) { runTest(t, true) }) +} diff --git a/itests/deals_power_test.go b/itests/deals_power_test.go new file mode 100644 index 000000000..ebf1895e3 --- /dev/null +++ b/itests/deals_power_test.go @@ -0,0 +1,61 @@ +package itests + +import ( + "context" + "testing" + "time" + + "github.com/filecoin-project/lotus/itests/kit" +) + +func TestFirstDealEnablesMining(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") + } + + kit.QuietMiningLogs() + + var ( + client kit.TestFullNode + genMiner kit.TestMiner // bootstrap + provider kit.TestMiner // no sectors, will need to create one + ) + + ens := kit.NewEnsemble(t, kit.MockProofs()) + ens.FullNode(&client) + ens.Miner(&genMiner, &client) + ens.Miner(&provider, &client, kit.PresealSectors(0)) + ens.Start().InterconnectAll().BeginMining(50 * time.Millisecond) + + ctx := context.Background() + + dh := kit.NewDealHarness(t, &client, &provider) + + ref, _ := client.CreateImportFile(ctx, 5, 0) + + t.Log("FILE CID:", ref.Root) + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + // start a goroutine to monitor head changes from the client + // once the provider has mined a block, thanks to the power acquired from the deal, + // we pass the test. + providerMined := make(chan struct{}) + + go func() { + _ = client.WaitTillChain(ctx, kit.BlockMinedBy(provider.ActorAddr)) + close(providerMined) + }() + + // now perform the deal. + deal := dh.StartDeal(ctx, ref.Root, 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) + + dh.WaitDealSealed(ctx, deal, false, false, nil) + + <-providerMined +} diff --git a/itests/deals_pricing_test.go b/itests/deals_pricing_test.go new file mode 100644 index 000000000..357abec1e --- /dev/null +++ b/itests/deals_pricing_test.go @@ -0,0 +1,131 @@ +package itests + +import ( + "context" + "testing" + "time" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/extern/sector-storage/storiface" + "github.com/filecoin-project/lotus/itests/kit" + "github.com/stretchr/testify/require" +) + +func TestQuotePriceForUnsealedRetrieval(t *testing.T) { + var ( + ctx = context.Background() + blocktime = time.Second + ) + + kit.QuietMiningLogs() + + client, miner, ens := kit.EnsembleMinimal(t) + ens.InterconnectAll().BeginMining(blocktime) + + var ( + ppb = int64(1) + unsealPrice = int64(77) + ) + + // Set unsealed price to non-zero + ask, err := miner.MarketGetRetrievalAsk(ctx) + require.NoError(t, err) + ask.PricePerByte = abi.NewTokenAmount(ppb) + ask.UnsealPrice = abi.NewTokenAmount(unsealPrice) + err = miner.MarketSetRetrievalAsk(ctx, ask) + require.NoError(t, err) + + dh := kit.NewDealHarness(t, client, miner) + + deal1, res1, _ := dh.MakeOnlineDeal(ctx, kit.MakeFullDealParams{Rseed: 6}) + + // one more storage deal for the same data + _, res2, _ := dh.MakeOnlineDeal(ctx, kit.MakeFullDealParams{Rseed: 6}) + require.Equal(t, res1.Root, res2.Root) + + // Retrieval + dealInfo, err := client.ClientGetDealInfo(ctx, *deal1) + require.NoError(t, err) + + // fetch quote -> zero for unsealed price since unsealed file already exists. + offers, err := client.ClientFindData(ctx, res1.Root, &dealInfo.PieceCID) + require.NoError(t, err) + require.Len(t, offers, 2) + require.Equal(t, offers[0], offers[1]) + require.Equal(t, uint64(0), offers[0].UnsealPrice.Uint64()) + require.Equal(t, dealInfo.Size*uint64(ppb), offers[0].MinPrice.Uint64()) + + // remove ONLY one unsealed file + ss, err := miner.StorageList(context.Background()) + require.NoError(t, err) + _, err = miner.SectorsList(ctx) + require.NoError(t, err) + +iLoop: + for storeID, sd := range ss { + for _, sector := range sd { + err := miner.StorageDropSector(ctx, storeID, sector.SectorID, storiface.FTUnsealed) + require.NoError(t, err) + break iLoop // remove ONLY one + } + } + + // get retrieval quote -> zero for unsealed price as unsealed file exists. + offers, err = client.ClientFindData(ctx, res1.Root, &dealInfo.PieceCID) + require.NoError(t, err) + require.Len(t, offers, 2) + require.Equal(t, offers[0], offers[1]) + require.Equal(t, uint64(0), offers[0].UnsealPrice.Uint64()) + require.Equal(t, dealInfo.Size*uint64(ppb), offers[0].MinPrice.Uint64()) + + // remove the other unsealed file as well + ss, err = miner.StorageList(context.Background()) + require.NoError(t, err) + _, err = miner.SectorsList(ctx) + require.NoError(t, err) + for storeID, sd := range ss { + for _, sector := range sd { + require.NoError(t, miner.StorageDropSector(ctx, storeID, sector.SectorID, storiface.FTUnsealed)) + } + } + + // fetch quote -> non-zero for unseal price as we no more unsealed files. + offers, err = client.ClientFindData(ctx, res1.Root, &dealInfo.PieceCID) + require.NoError(t, err) + require.Len(t, offers, 2) + require.Equal(t, offers[0], offers[1]) + require.Equal(t, uint64(unsealPrice), offers[0].UnsealPrice.Uint64()) + total := (dealInfo.Size * uint64(ppb)) + uint64(unsealPrice) + require.Equal(t, total, offers[0].MinPrice.Uint64()) +} + +func TestZeroPricePerByteRetrieval(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode") + } + + kit.QuietMiningLogs() + + var ( + blockTime = 10 * time.Millisecond + startEpoch = abi.ChainEpoch(2 << 12) + ) + + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) + ens.InterconnectAll().BeginMining(blockTime) + + ctx := context.Background() + + ask, err := miner.MarketGetRetrievalAsk(ctx) + require.NoError(t, err) + + ask.PricePerByte = abi.NewTokenAmount(0) + err = miner.MarketSetRetrievalAsk(ctx, ask) + require.NoError(t, err) + + dh := kit.NewDealHarness(t, client, miner) + dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{ + N: 1, + StartEpoch: startEpoch, + }) +} diff --git a/itests/deals_publish_test.go b/itests/deals_publish_test.go new file mode 100644 index 000000000..16f84038b --- /dev/null +++ b/itests/deals_publish_test.go @@ -0,0 +1,108 @@ +package itests + +import ( + "bytes" + "context" + "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/types" + "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/markets/storageadapter" + "github.com/filecoin-project/lotus/node" + market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" + "github.com/stretchr/testify/require" +) + +func TestPublishDealsBatching(t *testing.T) { + var ( + ctx = context.Background() + publishPeriod = 10 * time.Second + maxDealsPerMsg = uint64(2) // Set max deals per publish deals message to 2 + startEpoch = abi.ChainEpoch(2 << 12) + ) + + kit.QuietMiningLogs() + + opts := node.Override(new(*storageadapter.DealPublisher), + storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{ + Period: publishPeriod, + MaxDealsPerMsg: maxDealsPerMsg, + }), + ) + + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ConstructorOpts(opts)) + ens.InterconnectAll().BeginMining(10 * time.Millisecond) + + dh := kit.NewDealHarness(t, client, miner) + + // Starts a deal and waits until it's published + runDealTillPublish := func(rseed int) { + res, _ := client.CreateImportFile(ctx, rseed, 0) + + upds, err := client.ClientGetDealUpdates(ctx) + require.NoError(t, err) + + dh.StartDeal(ctx, 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 := client.StateListMessages(ctx, &api.MessageMatch{To: market.Address}, types.EmptyTSK, 1) + require.NoError(t, err) + count := 0 + for _, msgCid := range msgCids { + msg, err := client.ChainGetMessage(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 + } +} diff --git a/itests/deals_test.go b/itests/deals_test.go index ad9d7d63c..f8389bbd6 100644 --- a/itests/deals_test.go +++ b/itests/deals_test.go @@ -1,99 +1,12 @@ package itests import ( - "bytes" - "context" - "fmt" - "path/filepath" "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/build" - "github.com/filecoin-project/lotus/chain/actors/builtin/market" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/extern/sector-storage/storiface" "github.com/filecoin-project/lotus/itests/kit" - "github.com/filecoin-project/lotus/markets/storageadapter" - "github.com/filecoin-project/lotus/node" - market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" - "github.com/stretchr/testify/require" - "golang.org/x/sync/errgroup" ) -func TestDealCyclesConcurrent(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode") - } - - kit.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 - startEpoch := abi.ChainEpoch(2 << 12) - - runTest := func(t *testing.T, n int, fastRetrieval bool, carExport bool) { - client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) - ens.InterconnectAll().BeginMining(blockTime) - dh := kit.NewDealHarness(t, client, miner) - - runConcurrentDeals(t, dh, fullDealCyclesOpts{ - n: n, - fastRetrieval: fastRetrieval, - carExport: carExport, - startEpoch: startEpoch, - }) - } - - // TODO: add 2, 4, 8, more when this graphsync issue is fixed: https://github.com/ipfs/go-graphsync/issues/175# - cycles := []int{1} - for _, n := range cycles { - n := n - ns := fmt.Sprintf("%d", n) - t.Run(ns+"-fastretrieval-CAR", func(t *testing.T) { runTest(t, n, true, true) }) - t.Run(ns+"-fastretrieval-NoCAR", func(t *testing.T) { runTest(t, n, true, false) }) - t.Run(ns+"-stdretrieval-CAR", func(t *testing.T) { runTest(t, n, true, false) }) - t.Run(ns+"-stdretrieval-NoCAR", func(t *testing.T) { runTest(t, n, false, false) }) - } -} - -type fullDealCyclesOpts struct { - n int - fastRetrieval bool - carExport bool - startEpoch abi.ChainEpoch -} - -func runConcurrentDeals(t *testing.T, dh *kit.DealHarness, opts fullDealCyclesOpts) { - errgrp, _ := errgroup.WithContext(context.Background()) - for i := 0; i < opts.n; i++ { - i := i - errgrp.Go(func() (err error) { - defer func() { - // This is necessary because golang can't deal with test - // failures being reported from children goroutines ¯\_(ツ)_/¯ - if r := recover(); r != nil { - err = fmt.Errorf("deal failed: %s", r) - } - }() - deal, res, inPath := dh.MakeOnlineDeal(context.Background(), kit.MakeFullDealParams{ - Rseed: 5 + i, - FastRet: opts.fastRetrieval, - StartEpoch: opts.startEpoch, - }) - outPath := dh.PerformRetrieval(context.Background(), deal, res.Root, opts.carExport) - kit.AssertFilesEqual(t, inPath, outPath) - return nil - }) - } - require.NoError(t, errgrp.Wait()) -} - func TestDealsWithSealingAndRPC(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode") @@ -108,361 +21,15 @@ func TestDealsWithSealingAndRPC(t *testing.T) { dh := kit.NewDealHarness(t, client, miner) t.Run("stdretrieval", func(t *testing.T) { - runConcurrentDeals(t, dh, fullDealCyclesOpts{n: 1}) + dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{N: 1}) }) t.Run("fastretrieval", func(t *testing.T) { - runConcurrentDeals(t, dh, fullDealCyclesOpts{n: 1, fastRetrieval: true}) + dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{N: 1, FastRetrieval: true}) }) t.Run("fastretrieval-twodeals-sequential", func(t *testing.T) { - runConcurrentDeals(t, dh, fullDealCyclesOpts{n: 1, fastRetrieval: true}) - runConcurrentDeals(t, dh, fullDealCyclesOpts{n: 1, fastRetrieval: true}) - }) -} - -func TestQuotePriceForUnsealedRetrieval(t *testing.T) { - var ( - ctx = context.Background() - blocktime = time.Second - ) - - kit.QuietMiningLogs() - - client, miner, ens := kit.EnsembleMinimal(t) - ens.InterconnectAll().BeginMining(blocktime) - - var ( - ppb = int64(1) - unsealPrice = int64(77) - ) - - // Set unsealed price to non-zero - ask, err := miner.MarketGetRetrievalAsk(ctx) - require.NoError(t, err) - ask.PricePerByte = abi.NewTokenAmount(ppb) - ask.UnsealPrice = abi.NewTokenAmount(unsealPrice) - err = miner.MarketSetRetrievalAsk(ctx, ask) - require.NoError(t, err) - - dh := kit.NewDealHarness(t, client, miner) - - deal1, res1, _ := dh.MakeOnlineDeal(ctx, kit.MakeFullDealParams{Rseed: 6}) - - // one more storage deal for the same data - _, res2, _ := dh.MakeOnlineDeal(ctx, kit.MakeFullDealParams{Rseed: 6}) - require.Equal(t, res1.Root, res2.Root) - - // Retrieval - dealInfo, err := client.ClientGetDealInfo(ctx, *deal1) - require.NoError(t, err) - - // fetch quote -> zero for unsealed price since unsealed file already exists. - offers, err := client.ClientFindData(ctx, res1.Root, &dealInfo.PieceCID) - require.NoError(t, err) - require.Len(t, offers, 2) - require.Equal(t, offers[0], offers[1]) - require.Equal(t, uint64(0), offers[0].UnsealPrice.Uint64()) - require.Equal(t, dealInfo.Size*uint64(ppb), offers[0].MinPrice.Uint64()) - - // remove ONLY one unsealed file - ss, err := miner.StorageList(context.Background()) - require.NoError(t, err) - _, err = miner.SectorsList(ctx) - require.NoError(t, err) - -iLoop: - for storeID, sd := range ss { - for _, sector := range sd { - err := miner.StorageDropSector(ctx, storeID, sector.SectorID, storiface.FTUnsealed) - require.NoError(t, err) - break iLoop // remove ONLY one - } - } - - // get retrieval quote -> zero for unsealed price as unsealed file exists. - offers, err = client.ClientFindData(ctx, res1.Root, &dealInfo.PieceCID) - require.NoError(t, err) - require.Len(t, offers, 2) - require.Equal(t, offers[0], offers[1]) - require.Equal(t, uint64(0), offers[0].UnsealPrice.Uint64()) - require.Equal(t, dealInfo.Size*uint64(ppb), offers[0].MinPrice.Uint64()) - - // remove the other unsealed file as well - ss, err = miner.StorageList(context.Background()) - require.NoError(t, err) - _, err = miner.SectorsList(ctx) - require.NoError(t, err) - for storeID, sd := range ss { - for _, sector := range sd { - require.NoError(t, miner.StorageDropSector(ctx, storeID, sector.SectorID, storiface.FTUnsealed)) - } - } - - // fetch quote -> non-zero for unseal price as we no more unsealed files. - offers, err = client.ClientFindData(ctx, res1.Root, &dealInfo.PieceCID) - require.NoError(t, err) - require.Len(t, offers, 2) - require.Equal(t, offers[0], offers[1]) - require.Equal(t, uint64(unsealPrice), offers[0].UnsealPrice.Uint64()) - total := (dealInfo.Size * uint64(ppb)) + uint64(unsealPrice) - require.Equal(t, total, offers[0].MinPrice.Uint64()) - -} - -func TestPublishDealsBatching(t *testing.T) { - var ( - ctx = context.Background() - publishPeriod = 10 * time.Second - maxDealsPerMsg = uint64(2) // Set max deals per publish deals message to 2 - startEpoch = abi.ChainEpoch(2 << 12) - ) - - kit.QuietMiningLogs() - - opts := node.Override(new(*storageadapter.DealPublisher), - storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{ - Period: publishPeriod, - MaxDealsPerMsg: maxDealsPerMsg, - }), - ) - - client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ConstructorOpts(opts)) - ens.InterconnectAll().BeginMining(10 * time.Millisecond) - - dh := kit.NewDealHarness(t, client, miner) - - // Starts a deal and waits until it's published - runDealTillPublish := func(rseed int) { - res, _ := client.CreateImportFile(ctx, rseed, 0) - - upds, err := client.ClientGetDealUpdates(ctx) - require.NoError(t, err) - - dh.StartDeal(ctx, 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 := client.StateListMessages(ctx, &api.MessageMatch{To: market.Address}, types.EmptyTSK, 1) - require.NoError(t, err) - count := 0 - for _, msgCid := range msgCids { - msg, err := client.ChainGetMessage(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 TestFirstDealEnablesMining(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") - } - - kit.QuietMiningLogs() - - var ( - client kit.TestFullNode - genMiner kit.TestMiner // bootstrap - provider kit.TestMiner // no sectors, will need to create one - ) - - ens := kit.NewEnsemble(t, kit.MockProofs()) - ens.FullNode(&client) - ens.Miner(&genMiner, &client) - ens.Miner(&provider, &client, kit.PresealSectors(0)) - ens.Start().InterconnectAll().BeginMining(50 * time.Millisecond) - - ctx := context.Background() - - dh := kit.NewDealHarness(t, &client, &provider) - - ref, _ := client.CreateImportFile(ctx, 5, 0) - - t.Log("FILE CID:", ref.Root) - - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - // start a goroutine to monitor head changes from the client - // once the provider has mined a block, thanks to the power acquired from the deal, - // we pass the test. - providerMined := make(chan struct{}) - - go func() { - _ = client.WaitTillChain(ctx, kit.BlockMinedBy(provider.ActorAddr)) - close(providerMined) - }() - - // now perform the deal. - deal := dh.StartDeal(ctx, ref.Root, 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) - - dh.WaitDealSealed(ctx, deal, false, false, nil) - - <-providerMined -} - -func TestOfflineDealFlow(t *testing.T) { - 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 - startEpoch := abi.ChainEpoch(2 << 12) - - runTest := func(t *testing.T, fastRet bool) { - ctx := context.Background() - client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) - ens.InterconnectAll().BeginMining(blocktime) - - dh := kit.NewDealHarness(t, client, miner) - - // Create a random file and import on the client. - res, inFile := client.CreateImportFile(ctx, 1, 0) - - // Get the piece size and commP - rootCid := res.Root - pieceInfo, err := client.ClientDealPieceCID(ctx, rootCid) - require.NoError(t, err) - t.Log("FILE CID:", rootCid) - - // Create a storage deal with the miner - maddr, err := miner.ActorAddress(ctx) - require.NoError(t, err) - - addr, err := client.WalletDefaultAddress(ctx) - require.NoError(t, err) - - // Manual storage deal (offline deal) - dataRef := &storagemarket.DataRef{ - TransferType: storagemarket.TTManual, - Root: rootCid, - PieceCid: &pieceInfo.PieceCID, - PieceSize: pieceInfo.PieceSize.Unpadded(), - } - - proposalCid, err := client.ClientStartDeal(ctx, &api.StartDealParams{ - Data: dataRef, - Wallet: addr, - Miner: maddr, - EpochPrice: types.NewInt(1000000), - DealStartEpoch: startEpoch, - MinBlocksDuration: uint64(build.MinDealDuration), - FastRetrieval: fastRet, - }) - require.NoError(t, err) - - // Wait for the deal to reach StorageDealCheckForAcceptance on the client - cd, err := client.ClientGetDealInfo(ctx, *proposalCid) - require.NoError(t, err) - require.Eventually(t, func() bool { - cd, _ := client.ClientGetDealInfo(ctx, *proposalCid) - return cd.State == storagemarket.StorageDealCheckForAcceptance - }, 30*time.Second, 1*time.Second, "actual deal status is %s", storagemarket.DealStates[cd.State]) - - // Create a CAR file from the raw file - carFileDir := t.TempDir() - carFilePath := filepath.Join(carFileDir, "out.car") - err = client.ClientGenCar(ctx, api.FileRef{Path: inFile}, carFilePath) - require.NoError(t, err) - - // Import the CAR file on the miner - this is the equivalent to - // transferring the file across the wire in a normal (non-offline) deal - err = miner.DealsImportData(ctx, *proposalCid, carFilePath) - require.NoError(t, err) - - // Wait for the deal to be published - dh.WaitDealPublished(ctx, proposalCid) - - t.Logf("deal published, retrieving") - - // Retrieve the deal - outFile := dh.PerformRetrieval(ctx, proposalCid, rootCid, false) - - kit.AssertFilesEqual(t, inFile, outFile) - - } - - t.Run("stdretrieval", func(t *testing.T) { runTest(t, false) }) - t.Run("fastretrieval", func(t *testing.T) { runTest(t, true) }) -} - -func TestZeroPricePerByteRetrieval(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode") - } - - kit.QuietMiningLogs() - - var ( - blockTime = 10 * time.Millisecond - startEpoch = abi.ChainEpoch(2 << 12) - ) - - client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) - ens.InterconnectAll().BeginMining(blockTime) - - ctx := context.Background() - - ask, err := miner.MarketGetRetrievalAsk(ctx) - require.NoError(t, err) - - ask.PricePerByte = abi.NewTokenAmount(0) - err = miner.MarketSetRetrievalAsk(ctx, ask) - require.NoError(t, err) - - dh := kit.NewDealHarness(t, client, miner) - runConcurrentDeals(t, dh, fullDealCyclesOpts{ - n: 1, - startEpoch: startEpoch, + dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{N: 1, FastRetrieval: true}) + dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{N: 1, FastRetrieval: true}) }) } diff --git a/itests/kit/deals.go b/itests/kit/deals.go index bf986a5e7..aa2ee8a07 100644 --- a/itests/kit/deals.go +++ b/itests/kit/deals.go @@ -2,26 +2,27 @@ package kit import ( "context" + "fmt" "io/ioutil" "os" "testing" "time" - "github.com/ipfs/go-cid" - files "github.com/ipfs/go-ipfs-files" - "github.com/ipld/go-car" - "github.com/stretchr/testify/require" - "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/build" "github.com/filecoin-project/lotus/chain/types" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" + "github.com/ipfs/go-cid" + files "github.com/ipfs/go-ipfs-files" 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" + "github.com/ipld/go-car" + "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" ) type DealHarness struct { @@ -246,3 +247,35 @@ func (dh *DealHarness) ExtractFileFromCAR(ctx context.Context, file *os.File) (o return tmpfile } + +type RunConcurrentDealsOpts struct { + N int + FastRetrieval bool + CarExport bool + StartEpoch abi.ChainEpoch +} + +func (dh *DealHarness) RunConcurrentDeals(opts RunConcurrentDealsOpts) { + errgrp, _ := errgroup.WithContext(context.Background()) + for i := 0; i < opts.N; i++ { + i := i + errgrp.Go(func() (err error) { + defer func() { + // This is necessary because golang can't deal with test + // failures being reported from children goroutines ¯\_(ツ)_/¯ + if r := recover(); r != nil { + err = fmt.Errorf("deal failed: %s", r) + } + }() + deal, res, inPath := dh.MakeOnlineDeal(context.Background(), MakeFullDealParams{ + Rseed: 5 + i, + FastRet: opts.FastRetrieval, + StartEpoch: opts.StartEpoch, + }) + outPath := dh.PerformRetrieval(context.Background(), deal, res.Root, opts.CarExport) + AssertFilesEqual(dh.t, inPath, outPath) + return nil + }) + } + require.NoError(dh.t, errgrp.Wait()) +} From a1e57d35f97fcf9c698a75beab1d052cd15e38d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Wed, 23 Jun 2021 18:24:36 +0100 Subject: [PATCH 231/257] circleci was out of sync. --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index f4f9b421d..133fb4d96 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -440,6 +440,7 @@ jobs: paths: - appimage + gofmt: executor: golang steps: From 9521c0b773df169ab1de3bcb4d252e8f6f40aab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 23 Jun 2021 19:45:08 +0200 Subject: [PATCH 232/257] Add miner-side MaxDealStartDelay config --- node/builder.go | 2 ++ node/config/def.go | 3 +++ node/modules/dtypes/miner.go | 3 +++ node/modules/storageminer.go | 28 ++++++++++++++++++++++++++-- 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/node/builder.go b/node/builder.go index ce00fc18d..54e58e28a 100644 --- a/node/builder.go +++ b/node/builder.go @@ -436,6 +436,8 @@ var MinerNode = Options( Override(new(dtypes.GetSealingConfigFunc), modules.NewGetSealConfigFunc), Override(new(dtypes.SetExpectedSealDurationFunc), modules.NewSetExpectedSealDurationFunc), Override(new(dtypes.GetExpectedSealDurationFunc), modules.NewGetExpectedSealDurationFunc), + Override(new(dtypes.SetMaxDealStartDelayFunc), modules.NewSetMaxDealStartDelayFunc), + Override(new(dtypes.GetMaxDealStartDelayFunc), modules.NewGetMaxDealStartDelayFunc), ) // Online sets up basic libp2p node diff --git a/node/config/def.go b/node/config/def.go index 407eb8ad6..5c2c8de03 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -58,6 +58,8 @@ type DealmakingConfig struct { ConsiderUnverifiedStorageDeals bool PieceCidBlocklist []cid.Cid ExpectedSealDuration Duration + // Maximum amount of time proposed deal StartEpoch can be in future + MaxDealStartDelay Duration // The amount of time to wait for more deals to arrive before // publishing PublishMsgPeriod Duration @@ -320,6 +322,7 @@ func DefaultStorageMiner() *StorageMiner { ConsiderUnverifiedStorageDeals: true, PieceCidBlocklist: []cid.Cid{}, // TODO: It'd be nice to set this based on sector size + MaxDealStartDelay: Duration(time.Hour * 24 * 14), ExpectedSealDuration: Duration(time.Hour * 24), PublishMsgPeriod: Duration(time.Hour), MaxDealsPerPublishMsg: 8, diff --git a/node/modules/dtypes/miner.go b/node/modules/dtypes/miner.go index 16af48add..5c8abf3ff 100644 --- a/node/modules/dtypes/miner.go +++ b/node/modules/dtypes/miner.go @@ -88,5 +88,8 @@ type SetExpectedSealDurationFunc func(time.Duration) error // too determine how long sealing is expected to take type GetExpectedSealDurationFunc func() (time.Duration, error) +type SetMaxDealStartDelayFunc func(time.Duration) error +type GetMaxDealStartDelayFunc func() (time.Duration, error) + type StorageDealFilter func(ctx context.Context, deal storagemarket.MinerDeal) (bool, string, error) type RetrievalDealFilter func(ctx context.Context, deal retrievalmarket.ProviderDealState) (bool, string, error) diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 3a28d5c85..715fb9a2b 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -59,7 +59,6 @@ import ( "github.com/filecoin-project/lotus/api/v1api" "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/gen" "github.com/filecoin-project/lotus/chain/gen/slashfilter" @@ -486,6 +485,7 @@ func BasicDealFilter(user dtypes.StorageDealFilter) func(onlineOk dtypes.Conside unverifiedOk dtypes.ConsiderUnverifiedStorageDealsConfigFunc, blocklistFunc dtypes.StorageDealPieceCidBlocklistConfigFunc, expectedSealTimeFunc dtypes.GetExpectedSealDurationFunc, + startDelay dtypes.GetMaxDealStartDelayFunc, spn storagemarket.StorageProviderNode) dtypes.StorageDealFilter { return func(onlineOk dtypes.ConsiderOnlineStorageDealsConfigFunc, offlineOk dtypes.ConsiderOfflineStorageDealsConfigFunc, @@ -493,6 +493,7 @@ func BasicDealFilter(user dtypes.StorageDealFilter) func(onlineOk dtypes.Conside unverifiedOk dtypes.ConsiderUnverifiedStorageDealsConfigFunc, blocklistFunc dtypes.StorageDealPieceCidBlocklistConfigFunc, expectedSealTimeFunc dtypes.GetExpectedSealDurationFunc, + startDelay dtypes.GetMaxDealStartDelayFunc, spn storagemarket.StorageProviderNode) dtypes.StorageDealFilter { return func(ctx context.Context, deal storagemarket.MinerDeal) (bool, string, error) { @@ -564,9 +565,14 @@ func BasicDealFilter(user dtypes.StorageDealFilter) func(onlineOk dtypes.Conside return false, fmt.Sprintf("cannot seal a sector before %s", deal.Proposal.StartEpoch), nil } + sd, err := startDelay() + if err != nil { + return false, "miner error", err + } + // Reject if it's more than 7 days in the future // TODO: read from cfg - maxStartEpoch := earliest + abi.ChainEpoch(7*builtin.SecondsInDay/build.BlockDelaySecs) + maxStartEpoch := earliest + abi.ChainEpoch(uint64(sd.Seconds())/build.BlockDelaySecs) if deal.Proposal.StartEpoch > maxStartEpoch { return false, fmt.Sprintf("deal start epoch is too far in the future: %s > %s", deal.Proposal.StartEpoch, maxStartEpoch), nil } @@ -898,6 +904,24 @@ func NewGetExpectedSealDurationFunc(r repo.LockedRepo) (dtypes.GetExpectedSealDu }, nil } +func NewSetMaxDealStartDelayFunc(r repo.LockedRepo) (dtypes.SetMaxDealStartDelayFunc, error) { + return func(delay time.Duration) (err error) { + err = mutateCfg(r, func(cfg *config.StorageMiner) { + cfg.Dealmaking.MaxDealStartDelay = config.Duration(delay) + }) + return + }, nil +} + +func NewGetMaxDealStartDelayFunc(r repo.LockedRepo) (dtypes.GetMaxDealStartDelayFunc, error) { + return func() (out time.Duration, err error) { + err = readCfg(r, func(cfg *config.StorageMiner) { + out = time.Duration(cfg.Dealmaking.MaxDealStartDelay) + }) + return + }, nil +} + func readCfg(r repo.LockedRepo, accessor func(*config.StorageMiner)) error { raw, err := r.Config() if err != nil { From 58f348cb7f0265198d19fa463af9b41968c749c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Wed, 23 Jun 2021 19:14:27 +0100 Subject: [PATCH 233/257] add ability to suspend deal-making until CE is stable. --- itests/ccupgrade_test.go | 5 ++++- itests/kit/deals.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/itests/ccupgrade_test.go b/itests/ccupgrade_test.go index 71bb4c065..eac2523bf 100644 --- a/itests/ccupgrade_test.go +++ b/itests/ccupgrade_test.go @@ -62,7 +62,10 @@ func runTestCCUpgrade(t *testing.T, upgradeHeight abi.ChainEpoch) { require.NoError(t, err) dh := kit.NewDealHarness(t, client, miner) - deal, res, inPath := dh.MakeOnlineDeal(ctx, kit.MakeFullDealParams{Rseed: 6}) + deal, res, inPath := dh.MakeOnlineDeal(ctx, kit.MakeFullDealParams{ + Rseed: 6, + SuspendUntilCryptoeconStable: true, + }) outPath := dh.PerformRetrieval(context.Background(), deal, res.Root, false) kit.AssertFilesEqual(t, inPath, outPath) diff --git a/itests/kit/deals.go b/itests/kit/deals.go index aa2ee8a07..8d90a032b 100644 --- a/itests/kit/deals.go +++ b/itests/kit/deals.go @@ -35,6 +35,30 @@ type MakeFullDealParams struct { Rseed int FastRet bool StartEpoch abi.ChainEpoch + + // SuspendUntilCryptoeconStable suspends deal-making, until cryptoecon + // parameters are stabilised. This affects projected collateral, and tests + // will fail in network version 13 and higher if deals are started too soon + // after network birth. + // + // The reason is that the formula for collateral calculation takes + // circulating supply into account: + // + // [portion of power this deal will be] * [~1% of tokens]. + // + // In the first epochs after genesis, the total circulating supply is + // changing dramatically in percentual terms. Therefore, if the deal is + // proposed too soon, by the time it gets published on chain, the quoted + // provider collateral will no longer be valid. + // + // The observation is that deals fail with: + // + // GasEstimateMessageGas error: estimating gas used: message execution + // failed: exit 16, reason: Provider collateral out of bounds. (RetCode=16) + // + // Enabling this will suspend deal-making until the network has reached a + // height of 150. + SuspendUntilCryptoeconStable bool } // NewDealHarness creates a test harness that contains testing utilities for deals. @@ -56,6 +80,12 @@ func (dh *DealHarness) MakeOnlineDeal(ctx context.Context, params MakeFullDealPa dh.t.Logf("FILE CID: %s", res.Root) + if params.SuspendUntilCryptoeconStable { + dh.t.Logf("deal-making suspending until cryptecon parameters have stabilised") + ts := dh.client.WaitTillChain(ctx, HeightAtLeast(150)) + dh.t.Logf("deal-making continuing; current height is %d", ts.Height()) + } + deal = dh.StartDeal(ctx, res.Root, params.FastRet, params.StartEpoch) // TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this From 6a48fbbc118493b238f889d1e06bc600e4c4753a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Wed, 23 Jun 2021 19:21:42 +0100 Subject: [PATCH 234/257] increase suspension threshold to 300. --- itests/kit/deals.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/itests/kit/deals.go b/itests/kit/deals.go index 8d90a032b..d9129b76a 100644 --- a/itests/kit/deals.go +++ b/itests/kit/deals.go @@ -57,7 +57,7 @@ type MakeFullDealParams struct { // failed: exit 16, reason: Provider collateral out of bounds. (RetCode=16) // // Enabling this will suspend deal-making until the network has reached a - // height of 150. + // height of 300. SuspendUntilCryptoeconStable bool } @@ -82,7 +82,7 @@ func (dh *DealHarness) MakeOnlineDeal(ctx context.Context, params MakeFullDealPa if params.SuspendUntilCryptoeconStable { dh.t.Logf("deal-making suspending until cryptecon parameters have stabilised") - ts := dh.client.WaitTillChain(ctx, HeightAtLeast(150)) + ts := dh.client.WaitTillChain(ctx, HeightAtLeast(300)) dh.t.Logf("deal-making continuing; current height is %d", ts.Height()) } From 63626e1d0a3f00a525f1e0359f6175b2624143e4 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 23 Jun 2021 15:02:23 -0700 Subject: [PATCH 235/257] feat(scripts): add the mkreleaselog script --- scripts/mkreleaselog | 253 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100755 scripts/mkreleaselog diff --git a/scripts/mkreleaselog b/scripts/mkreleaselog new file mode 100755 index 000000000..c59027075 --- /dev/null +++ b/scripts/mkreleaselog @@ -0,0 +1,253 @@ +#!/bin/zsh +set -euo pipefail +export GO111MODULE=on +export GOPATH="$(go env GOPATH)" + +alias jq="jq --unbuffered" + +AUTHORS=( + # orgs + ipfs + ipld + libp2p + multiformats + filecoin-project + ipfs-shipyard + + # Authors of personal repos used by go-ipfs that should be mentioned in the + # release notes. + whyrusleeping + Kubuxu + jbenet + Stebalien + marten-seemann + hsanjuan + lucas-clemente + warpfork +) + +[[ -n "${REPO_FILTER+x}" ]] || REPO_FILTER="github.com/(${$(printf "|%s" "${AUTHORS[@]}"):1})" + +[[ -n "${IGNORED_FILES+x}" ]] || IGNORED_FILES='^\(\.gx\|package\.json\|\.travis\.yml\|go.mod\|go\.sum|\.github|\.circleci\)$' + +NL=$'\n' + +ROOT_DIR="$(git rev-parse --show-toplevel)" + +msg() { + echo "$*" >&2 +} + +statlog() { + local module="$1" + local rpath="$GOPATH/src/$(strip_version "$module")" + local start="${2:-}" + local end="${3:-HEAD}" + local mailmap_file="$rpath/.mailmap" + if ! [[ -e "$mailmap_file" ]]; then + mailmap_file="$ROOT_DIR/.mailmap" + fi + + local stack=() + git -C "$rpath" -c mailmap.file="$mailmap_file" log --use-mailmap --shortstat --no-merges --pretty="tformat:%H%x09%aN%x09%aE" "$start..$end" | while read -r line; do + if [[ -n "$line" ]]; then + stack+=("$line") + continue + fi + + read -r changes + + changed=0 + insertions=0 + deletions=0 + while read count event; do + if [[ "$event" =~ ^file ]]; then + changed=$count + elif [[ "$event" =~ ^insertion ]]; then + insertions=$count + elif [[ "$event" =~ ^deletion ]]; then + deletions=$count + else + echo "unknown event $event" >&2 + exit 1 + fi + done<<<"${changes//,/$NL}" + + for author in "${stack[@]}"; do + IFS=$'\t' read -r hash name email <<<"$author" + jq -n \ + --arg "hash" "$hash" \ + --arg "name" "$name" \ + --arg "email" "$email" \ + --argjson "changed" "$changed" \ + --argjson "insertions" "$insertions" \ + --argjson "deletions" "$deletions" \ + '{Commit: $hash, Author: $name, Email: $email, Files: $changed, Insertions: $insertions, Deletions: $deletions}' + done + stack=() + done +} + +# Returns a stream of deps changed between $1 and $2. +dep_changes() { + { + <"$1" + <"$2" + } | jq -s 'JOIN(INDEX(.[0][]; .Path); .[1][]; .Path; {Path: .[0].Path, Old: (.[1] | del(.Path)), New: (.[0] | del(.Path))}) | select(.New.Version != .Old.Version)' +} + +# resolve_commits resolves a git ref for each version. +resolve_commits() { + jq '. + {Ref: (.Version|capture("^((?.*)\\+incompatible|v.*-(0\\.)?[0-9]{14}-(?[a-f0-9]{12})|(?v.*))$") | .ref1 // .ref2 // .ref3)}' +} + +pr_link() { + local repo="$1" + local prnum="$2" + local ghname="${repo##github.com/}" + printf -- "[%s#%s](https://%s/pull/%s)" "$ghname" "$prnum" "$repo" "$prnum" +} + +# Generate a release log for a range of commits in a single repo. +release_log() { + setopt local_options BASH_REMATCH + + local module="$1" + local start="$2" + local end="${3:-HEAD}" + local repo="$(strip_version "$1")" + local dir="$GOPATH/src/$repo" + + local commit pr + git -C "$dir" log \ + --format='tformat:%H %s' \ + --first-parent \ + "$start..$end" | + while read commit subject; do + # Skip gx-only PRs. + git -C "$dir" diff-tree --no-commit-id --name-only "$commit^" "$commit" | + grep -v "${IGNORED_FILES}" >/dev/null || continue + + if [[ "$subject" =~ '^Merge pull request #([0-9]+) from' ]]; then + local prnum="${BASH_REMATCH[2]}" + local desc="$(git -C "$dir" show --summary --format='tformat:%b' "$commit" | head -1)" + printf -- "- %s (%s)\n" "$desc" "$(pr_link "$repo" "$prnum")" + elif [[ "$subject" =~ '\(#([0-9]+)\)$' ]]; then + local prnum="${BASH_REMATCH[2]}" + printf -- "- %s (%s)\n" "$subject" "$(pr_link "$repo" "$prnum")" + else + printf -- "- %s\n" "$subject" + fi + done +} + +indent() { + sed -e 's/^/ /' +} + +mod_deps() { + go list -mod=mod -json -m all | jq 'select(.Version != null)' +} + +ensure() { + local repo="$(strip_version "$1")" + local commit="$2" + local rpath="$GOPATH/src/$repo" + if [[ ! -d "$rpath" ]]; then + msg "Cloning $repo..." + git clone "http://$repo" "$rpath" >&2 + fi + + if ! git -C "$rpath" rev-parse --verify "$commit" >/dev/null; then + msg "Fetching $repo..." + git -C "$rpath" fetch --all >&2 + fi + + git -C "$rpath" rev-parse --verify "$commit" >/dev/null || return 1 +} + +statsummary() { + jq -s 'group_by(.Author)[] | {Author: .[0].Author, Commits: (. | length), Insertions: (map(.Insertions) | add), Deletions: (map(.Deletions) | add), Files: (map(.Files) | add)}' | + jq '. + {Lines: (.Deletions + .Insertions)}' +} + +strip_version() { + local repo="$1" + if [[ "$repo" =~ '.*/v[0-9]+$' ]]; then + repo="$(dirname "$repo")" + fi + echo "$repo" +} + +recursive_release_log() { + local start="${1:-$(git tag -l | sort -V | grep -v -- '-rc' | grep 'v'| tail -n1)}" + local end="${2:-$(git rev-parse HEAD)}" + local repo_root="$(git rev-parse --show-toplevel)" + local module="$(go list -m)" + local dir="$(go list -m -f '{{.Dir}}')" + + if [[ "${GOPATH}/${module}" -ef "${dir}" ]]; then + echo "This script requires the target module and all dependencies to live in a GOPATH." + return 1 + fi + + ( + local result=0 + local workspace="$(mktemp -d)" + trap "$(printf 'rm -rf "%q"' "$workspace")" INT TERM EXIT + cd "$workspace" + + mkdir extern + ln -s "$repo_root"/extern/filecoin-ffi extern/filecoin-ffi + ln -s "$repo_root"/extern/test-vectors extern/test-vectors + + echo "Computing old deps..." >&2 + git -C "$repo_root" show "$start:go.mod" >go.mod + mod_deps | resolve_commits | jq -s > old_deps.json + + echo "Computing new deps..." >&2 + git -C "$repo_root" show "$end:go.mod" >go.mod + mod_deps | resolve_commits | jq -s > new_deps.json + + rm -f go.mod go.sum + + printf -- "Generating Changelog for %s %s..%s\n" "$module" "$start" "$end" >&2 + + printf -- "- %s:\n" "$module" + release_log "$module" "$start" "$end" | indent + + + statlog "$module" "$start" "$end" > statlog.json + + dep_changes old_deps.json new_deps.json | + jq --arg filter "$REPO_FILTER" 'select(.Path | match($filter))' | + # Compute changelogs + jq -r '"\(.Path) \(.New.Version) \(.New.Ref) \(.Old.Version) \(.Old.Ref // "")"' | + while read module new new_ref old old_ref; do + if ! ensure "$module" "$new_ref"; then + result=1 + local changelog="failed to fetch repo" + else + statlog "$module" "$old_ref" "$new_ref" >> statlog.json + local changelog="$(release_log "$module" "$old_ref" "$new_ref")" + fi + if [[ -n "$changelog" ]]; then + printf -- "- %s (%s -> %s):\n" "$module" "$old" "$new" + echo "$changelog" | indent + fi + done + + echo + echo "Contributors" + echo + + echo "| Contributor | Commits | Lines ± | Files Changed |" + echo "|-------------|---------|---------|---------------|" + statsummary Date: Wed, 23 Jun 2021 18:51:20 -0400 Subject: [PATCH 236/257] pull actor v5.0.1 --- go.mod | 2 +- go.sum | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d3ea7c57e..a29a108b0 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( github.com/filecoin-project/specs-actors/v2 v2.3.5-0.20210114162132-5b58b773f4fb github.com/filecoin-project/specs-actors/v3 v3.1.0 github.com/filecoin-project/specs-actors/v4 v4.0.0 - github.com/filecoin-project/specs-actors/v5 v5.0.0-20210609212542-73e0409ac77c + github.com/filecoin-project/specs-actors/v5 v5.0.1 github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506 github.com/filecoin-project/test-vectors/schema v0.0.5 github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1 diff --git a/go.sum b/go.sum index 08d6a0689..89af9d973 100644 --- a/go.sum +++ b/go.sum @@ -318,8 +318,8 @@ github.com/filecoin-project/specs-actors/v3 v3.1.0/go.mod h1:mpynccOLlIRy0QnR008 github.com/filecoin-project/specs-actors/v4 v4.0.0 h1:vMALksY5G3J5rj3q9rbcyB+f4Tk1xrLqSgdB3jOok4s= github.com/filecoin-project/specs-actors/v4 v4.0.0/go.mod h1:TkHXf/l7Wyw4ZejyXIPS2rK8bBO0rdwhTZyQQgaglng= github.com/filecoin-project/specs-actors/v5 v5.0.0-20210512015452-4fe3889fff57/go.mod h1:283yBMMUSDB2abcjP/hhrwTkhb9h3sfM6KGrep/ZlBI= -github.com/filecoin-project/specs-actors/v5 v5.0.0-20210609212542-73e0409ac77c h1:GnDJ6q3QEm2ytTKjPFQSvczAltgCSb3j9F1FeynwvPA= -github.com/filecoin-project/specs-actors/v5 v5.0.0-20210609212542-73e0409ac77c/go.mod h1:b/btpRl84Q9SeDKlyIoORBQwe2OTmq14POrYrVvBWCM= +github.com/filecoin-project/specs-actors/v5 v5.0.1 h1:PrYm5AKdMlJ/55eRW5laWcnaX66gyyDYBWvH38kNAMo= +github.com/filecoin-project/specs-actors/v5 v5.0.1/go.mod h1:74euMDIXorusOBs/QL/LNkYsXZdDpLJwojWw6T03pdE= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506 h1:Ur/l2+6qN+lQiqjozWWc5p9UDaAMDZKTlDS98oRnlIw= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506/go.mod h1:nJRRM7Aa9XVvygr3W9k6xGF46RWzr2zxF/iGoAIfA/g= github.com/filecoin-project/test-vectors/schema v0.0.5 h1:w3zHQhzM4pYxJDl21avXjOKBLF8egrvwUwjpT8TquDg= @@ -684,6 +684,7 @@ github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Ax github.com/ipfs/go-merkledag v0.0.3/go.mod h1:Oc5kIXLHokkE1hWGMBHw+oxehkAaTOqtEb7Zbh6BhLA= github.com/ipfs/go-merkledag v0.0.6/go.mod h1:QYPdnlvkOg7GnQRofu9XZimC5ZW5Wi3bKys/4GQQfto= github.com/ipfs/go-merkledag v0.2.3/go.mod h1:SQiXrtSts3KGNmgOzMICy5c0POOpUNQLvB3ClKnBAlk= +github.com/ipfs/go-merkledag v0.2.4/go.mod h1:SQiXrtSts3KGNmgOzMICy5c0POOpUNQLvB3ClKnBAlk= github.com/ipfs/go-merkledag v0.3.1/go.mod h1:fvkZNNZixVW6cKSZ/JfLlON5OlgTXNdRLz0p6QG/I2M= github.com/ipfs/go-merkledag v0.3.2 h1:MRqj40QkrWkvPswXs4EfSslhZ4RVPRbxwX11js0t1xY= github.com/ipfs/go-merkledag v0.3.2/go.mod h1:fvkZNNZixVW6cKSZ/JfLlON5OlgTXNdRLz0p6QG/I2M= @@ -702,6 +703,7 @@ github.com/ipfs/go-peertaskqueue v0.2.0/go.mod h1:5/eNrBEbtSKWCG+kQK8K8fGNixoYUn github.com/ipfs/go-todocounter v0.0.1/go.mod h1:l5aErvQc8qKE2r7NDMjmq5UNAvuZy0rC8BHOplkWvZ4= github.com/ipfs/go-unixfs v0.0.4/go.mod h1:eIo/p9ADu/MFOuyxzwU+Th8D6xoxU//r590vUpWyfz8= github.com/ipfs/go-unixfs v0.2.1/go.mod h1:IwAAgul1UQIcNZzKPYZWOCijryFBeCV79cNubPzol+k= +github.com/ipfs/go-unixfs v0.2.2-0.20190827150610-868af2e9e5cb/go.mod h1:IwAAgul1UQIcNZzKPYZWOCijryFBeCV79cNubPzol+k= github.com/ipfs/go-unixfs v0.2.4 h1:6NwppOXefWIyysZ4LR/qUBPvXd5//8J3jiMdvpbw6Lo= github.com/ipfs/go-unixfs v0.2.4/go.mod h1:SUdisfUjNoSDzzhGVxvCL9QO/nKdwXdr+gbMUdqcbYw= github.com/ipfs/go-verifcid v0.0.1 h1:m2HI7zIuR5TFyQ1b79Da5N9dnnCP1vcu2QqawmWlK2E= @@ -712,13 +714,16 @@ github.com/ipfs/iptb v1.4.0 h1:YFYTrCkLMRwk/35IMyC6+yjoQSHTEcNcefBStLJzgvo= github.com/ipfs/iptb v1.4.0/go.mod h1:1rzHpCYtNp87/+hTxG5TfCVn/yMY3dKnLn8tBiMfdmg= github.com/ipfs/iptb-plugins v0.2.1 h1:au4HWn9/pRPbkxA08pDx2oRAs4cnbgQWgV0teYXuuGA= github.com/ipfs/iptb-plugins v0.2.1/go.mod h1:QXMbtIWZ+jRsW8a4h13qAKU7jcM7qaittO8wOsTP0Rs= +github.com/ipld/go-car v0.1.0/go.mod h1:RCWzaUh2i4mOEkB3W45Vc+9jnS/M6Qay5ooytiBHl3g= github.com/ipld/go-car v0.1.1-0.20200923150018-8cdef32e2da4/go.mod h1:xrMEcuSq+D1vEwl+YAXsg/JfA98XGpXDwnkIL4Aimqw= github.com/ipld/go-car v0.1.1-0.20201119040415-11b6074b6d4d h1:iphSzTuPqyDgH7WUVZsdqUnQNzYgIblsVr1zhVNA33U= github.com/ipld/go-car v0.1.1-0.20201119040415-11b6074b6d4d/go.mod h1:2Gys8L8MJ6zkh1gktTSXreY63t4UbyvNp5JaudTyxHQ= +github.com/ipld/go-ipld-prime v0.0.2-0.20191108012745-28a82f04c785/go.mod h1:bDDSvVz7vaK12FNvMeRYnpRFkSUPNQOiCYQezMD/P3w= github.com/ipld/go-ipld-prime v0.0.2-0.20200428162820-8b59dc292b8e/go.mod h1:uVIwe/u0H4VdKv3kaN1ck7uCb6yD9cFLS9/ELyXbsw8= github.com/ipld/go-ipld-prime v0.5.1-0.20200828233916-988837377a7f/go.mod h1:0xEgdD6MKbZ1vF0GC+YcR/C4SQCAlRuOjIJ2i0HxqzM= github.com/ipld/go-ipld-prime v0.5.1-0.20201021195245-109253e8a018 h1:RbRHv8epkmvBYA5cGfz68GUSbOgx5j/7ObLIl4Rsif0= github.com/ipld/go-ipld-prime v0.5.1-0.20201021195245-109253e8a018/go.mod h1:0xEgdD6MKbZ1vF0GC+YcR/C4SQCAlRuOjIJ2i0HxqzM= +github.com/ipld/go-ipld-prime-proto v0.0.0-20191113031812-e32bd156a1e5/go.mod h1:gcvzoEDBjwycpXt3LBE061wT9f46szXGHAmj9uoP6fU= github.com/ipld/go-ipld-prime-proto v0.0.0-20200428191222-c1ffdadc01e1/go.mod h1:OAV6xBmuTLsPZ+epzKkPB1e25FHk/vCtyatkdHcArLs= github.com/ipld/go-ipld-prime-proto v0.0.0-20200922192210-9a2bfd4440a6/go.mod h1:3pHYooM9Ea65jewRwrb2u5uHZCNkNTe9ABsVB+SrkH0= github.com/ipld/go-ipld-prime-proto v0.1.0 h1:j7gjqrfwbT4+gXpHwEx5iMssma3mnctC7YaCimsFP70= From a290982ca03e912db25ce5bc75ed5ef629624ddf Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Wed, 23 Jun 2021 16:57:34 -0400 Subject: [PATCH 237/257] Lotus version 1.10.0 --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++------ build/openrpc/full.json.gz | Bin 22485 -> 22482 bytes build/openrpc/miner.json.gz | Bin 8090 -> 8086 bytes build/openrpc/worker.json.gz | Bin 2579 -> 2578 bytes build/version.go | 2 +- 5 files changed, 29 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a85726f3..b2db58fec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ # Lotus changelog -# 1.10.0-rc6 / 2021-06-18 +# 1.10.0 / 2021-06-23 -> Note: If you are running a lotus miner, check out the doc [here](https://docs.filecoin.io/mine/lotus/miner-configuration/#precommitsectorsbatch) for new lotus miner configurations explanations of the new features! +> Note: If you are running a Lotus miner, check out the documentation[here](https://docs.filecoin.io/mine/lotus/miner-configuration/#precommitsectorsbatch) for details of the new Lotus miner config options and explanations of the new features. -This is the 6th release candidate for Lotus v1.10.0, an upcoming mandatory release of Lotus that will introduce Filecoin network v13. Use this release for syncing with the [reset calibration net](https://github.com/filecoin-project/community/discussions/74#discussioncomment-885580). In addition, included in the new network version are the following FIPs: +This is a mandatory release of Lotus that introduces Filecoin network v13, codenamed the HyperDrive upgrade. The Filecoin mainnet will upgrade at epoch 892800, which is 2021-06-30T22:00:00Z. The network upgrade introduces the following FIPs: - [FIP-0008](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0008.md): Add miner batched sector pre-commit method - [FIP-0011](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0011.md): Remove reward auction from reporting consensus faults @@ -12,11 +12,33 @@ This is the 6th release candidate for Lotus v1.10.0, an upcoming mandatory relea - [FIP-0013](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0013.md): Add ProveCommitSectorAggregated method to reduce on-chain congestion - [FIP-0015](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0015.md): Revert FIP-0009(Exempt Window PoSts from BaseFee burn) -This release candidate does not set the upgrade epochs for mainnet, but does set the upgrade epoch for the calibration network to 321519. - Note that this release is built on top of Lotus v1.9.0. Enterprising users can use the `master` branch of Lotus to get the latest functionality, including all changes in this release candidate. -A detailed changelog will be included in a later release candidate. +## Proof batching and aggregation + +FIPs [0008](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0008.md) and [0013](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0013.md) combine to allow for a significant increase in the rate of onboarding storage on the Filecoin network. It is hoped that this will lead to more useful data being stored on the network, reduced network congestion, and lower network base fee. + +### Projected state tree growth + +As a result of the accelerated onboarding of storage onto the network, it is possible + +### Hardware requirements and suggestions + +As the size of a single state tree grows, so will the size of the minimal datastore needed to sync the blockchain. The Filecoin protocol requires any node validating the chain to have the last 1800 state trees (2 * `ChainFinality`). Depending on how fast storage providers seal new sectors, this could get as large as AMOUNT in 6 months, and grow as fast as 20 GB per day. This increases the necessary hardware requirements to validate the Filecoin blockchain, but can be supported by images such as [Amazon EC2](https://aws.amazon.com/ec2/instance-explorer/?ec2-instances-cards.sort-by=item.additionalFields.category-order&ec2-instances-cards.sort-order=asc&awsf.ec2-instances-filter-category=*all&awsf.ec2-instances-filter-processors=*all&awsf.ec2-instances-filter-accelerators=*all&awsf.ec2-instances-filter-capabilities=additional-capabilities%23instance-storage&ec2-instances-cards.q=ssd&ec2-instances-cards.q_operator=AND&awsm.page-ec2-instances-cards=1) and [Google Cloud Platform](https://cloud.google.com/compute/docs/machine-types). + +### Future improvements + +Various Lotus improvements are planned moving forward to mitigate the effects of the growing state tree size. The primary improvement is the [Lotus splitstore](LINK TO DISCUSSION), which will soon be enabled by default. The feature allows for [online garbage collection](https://github.com/filecoin-project/lotus/issues/6577) for nodes that do not seek to maintain full chain and state history, thus eliminating the need for users to delete their datastores and sync from snapshots. + +Other improvements including better compressed snapshots, faster premigrations, and improved chain exports. + +## WindowPost base fee burn + +Included in the HyperDrive upgrade is [FIP-0015](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0015.md) which eliminates the special-case gas treatment of `SubmitWindowedPoSt` messages that was introduced in [FIP-0009](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0009.md). Although `SubmitWindowedPoSt` messages will be relatively cheap, thanks to the introduction of optimistic acceptance of these proofs in [FIP-0010](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0010.md), storage providers should pay attention to their `MaxWindowPoStGasFee` config option: too low and PoSts may not land on chain; too high and they may cost an exorbitant amount! + +## Changelog + +TODO # 1.9.0 / 2021-05-17 diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 5a002a26f20c4ea835f624f5545c42d8271703ef..fe80856db35425e416b8d25ce4b9c582d08d2f83 100644 GIT binary patch delta 22358 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 Hp#%O0Br!)H delta 22357 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 C6HI~t diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index c8c1bcfd20b3184b43300b60e53f4425869d0319..989f3b020ec566c763c89dc02d2d7f6a9dae0c78 100644 GIT binary patch delta 8022 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 Yi4*hb>E`MG2LJ&7|GwgoBf5+M0Cs7KF8}}l delta 8026 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 p0uVE2EfR@ Date: Wed, 23 Jun 2021 20:54:52 -0400 Subject: [PATCH 238/257] add changelog --- CHANGELOG.md | 245 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 165 insertions(+), 80 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2db58fec..8c6c2ab54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,7 @@ # 1.10.0 / 2021-06-23 -> Note: If you are running a Lotus miner, check out the documentation[here](https://docs.filecoin.io/mine/lotus/miner-configuration/#precommitsectorsbatch) for details of the new Lotus miner config options and explanations of the new features. - -This is a mandatory release of Lotus that introduces Filecoin network v13, codenamed the HyperDrive upgrade. The Filecoin mainnet will upgrade at epoch 892800, which is 2021-06-30T22:00:00Z. The network upgrade introduces the following FIPs: +This is a mandatory release of Lotus that introduces Filecoin network v13, codenamed the HyperDrive upgrade. The Filecoin mainnet will upgrade, which is 2021-06-30T22:00:00Z. The network upgrade introduces the following FIPs: - [FIP-0008](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0008.md): Add miner batched sector pre-commit method - [FIP-0011](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0011.md): Remove reward auction from reporting consensus faults @@ -18,9 +16,16 @@ Note that this release is built on top of Lotus v1.9.0. Enterprising users can u FIPs [0008](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0008.md) and [0013](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0013.md) combine to allow for a significant increase in the rate of onboarding storage on the Filecoin network. It is hoped that this will lead to more useful data being stored on the network, reduced network congestion, and lower network base fee. +**Check out the documentation [here](https://docs.filecoin.io/mine/lotus/miner-configuration/#precommitsectorsbatch) for details of the new Lotus miner sealing config options, [here](https://docs.filecoin.io/mine/lotus/miner-configuration/#fees-section) for fee config options, and explanations of the new features.** + +Note: + - We recommend to keep `PreCommitSectorsBatch` as 1. + - We recommend miners to set `PreCommitBatchWait` lower than 30 hours. + - We recommend miners to set a longer `CommitBatchSlack` and `PreCommitBatchSlac` to prevent message failures due to expirations. + ### Projected state tree growth -As a result of the accelerated onboarding of storage onto the network, it is possible +As a result of the accelerated onboardiPreCommitSectorsBatcng of storage onto the network, it is possible ### Hardware requirements and suggestions @@ -28,9 +33,9 @@ As the size of a single state tree grows, so will the size of the minimal datast ### Future improvements -Various Lotus improvements are planned moving forward to mitigate the effects of the growing state tree size. The primary improvement is the [Lotus splitstore](LINK TO DISCUSSION), which will soon be enabled by default. The feature allows for [online garbage collection](https://github.com/filecoin-project/lotus/issues/6577) for nodes that do not seek to maintain full chain and state history, thus eliminating the need for users to delete their datastores and sync from snapshots. +Various Lotus improvements are planned moving forward to mitigate the effects of the growing state tree size. The primary improvement is the [Lotus splitstore](https://github.com/filecoin-project/lotus/discussions/5788), which will soon be enabled by default. The feature allows for [online garbage collection](https://github.com/filecoin-project/lotus/issues/6577) for nodes that do not seek to maintain full chain and state history, thus eliminating the need for users to delete their datastores and sync from snapshots. -Other improvements including better compressed snapshots, faster premigrations, and improved chain exports. +Other improvements including better compressed snapshots, faster pre-migrations, and improved chain exports are in the roadmap. ## WindowPost base fee burn @@ -38,7 +43,87 @@ Included in the HyperDrive upgrade is [FIP-0015](https://github.com/filecoin-pro ## Changelog -TODO +### New Features + +- Implement FIP-0015 ([filecoin-project/lotus#6361](https://github.com/filecoin-project/lotus/pull/6361)) +- Integrate FIP0013 and FIP0008 ([filecoin-project/lotus#6235](https://github.com/filecoin-project/lotus/pull/6235)) + - [Configuration docs and cli examples](https://docs.filecoin.io/mine/lotus/miner-configuration/#precommitsectorsbatch) + - [cli docs](https://github.com/filecoin-project/lotus/blob/master/documentation/en/cli-lotus-miner.md#lotus-miner-sectors-batching) + - Introduce gas prices for aggregate verifications ([filecoin-project/lotus#6347](https://github.com/filecoin-project/lotus/pull/6347)) +- Introduce v5 actors ([filecoin-project/lotus#6195](https://github.com/filecoin-project/lotus/pull/6195)) +- Robustify commit batcher ([filecoin-project/lotus#6367](https://github.com/filecoin-project/lotus/pull/6367)) +- Always flush when timer goes off ([filecoin-project/lotus#6563](https://github.com/filecoin-project/lotus/pull/6563)) +- Update default fees for aggregates ([filecoin-project/lotus#6548](https://github.com/filecoin-project/lotus/pull/6548)) +- sealing: Early finalization option ([filecoin-project/lotus#6452](https://github.com/filecoin-project/lotus/pull/6452)) + - `./lotus-miner/config.toml/[Sealing.FinalizeEarly]`: default to false. Enable if you want to FinalizeSector before commiting +- Add filplus utils to CLI ([filecoin-project/lotus#6351](https://github.com/filecoin-project/lotus/pull/6351)) + - cli doc can be found [here](https://github.com/filecoin-project/lotus/blob/master/documentation/en/cli-lotus.md#lotus-filplus) +- Add miner-side MaxDealStartDelay config ([filecoin-project/lotus#6576](https://github.com/filecoin-project/lotus/pull/6576)) + + +### Bug Fixes +- chainstore: Don't take heaviestLk with backlogged reorgCh ([filecoin-project/lotus#6526](https://github.com/filecoin-project/lotus/pull/6526)) +- Backport #6041 - storagefsm: Fix batch deal packing behavior ([filecoin-project/lotus#6519](https://github.com/filecoin-project/lotus/pull/6519)) +- backport: pick the correct partitions-per-post limit ([filecoin-project/lotus#6503](https://github.com/filecoin-project/lotus/pull/6503)) +- failed sectors should be added into res correctly ([filecoin-project/lotus#6472](https://github.com/filecoin-project/lotus/pull/6472)) +- sealing: Fix restartSectors race ([filecoin-project/lotus#6491](https://github.com/filecoin-project/lotus/pull/6491)) +- Fund miners with the aggregate fee when ProveCommitting ([filecoin-project/lotus#6428](https://github.com/filecoin-project/lotus/pull/6428)) +- Commit and Precommit batcher cannot share a getSectorDeadline method ([filecoin-project/lotus#6416](https://github.com/filecoin-project/lotus/pull/6416)) +- Fix supported proof type manipulations for v5 actors ([filecoin-project/lotus#6366](https://github.com/filecoin-project/lotus/pull/6366)) +- events: Fix handling of multiple matched events per epoch ([filecoin-project/lotus#6362](https://github.com/filecoin-project/lotus/pull/6362)) +- Fix randomness fetching around null blocks ([filecoin-project/lotus#6240](https://github.com/filecoin-project/lotus/pull/6240)) + +### Improvements +- Appimage v1.10.0 rc3 ([filecoin-project/lotus#6492](https://github.com/filecoin-project/lotus/pull/6492)) +- Expand on Drand change testing ([filecoin-project/lotus#6500](https://github.com/filecoin-project/lotus/pull/6500)) +- Backport Fix logging around mineOne ([filecoin-project/lotus#6499](https://github.com/filecoin-project/lotus/pull/6499)) +- mpool: Add more metrics ([filecoin-project/lotus#6453](https://github.com/filecoin-project/lotus/pull/6453)) +- Merge backported PRs into v1.10 release branch ([filecoin-project/lotus#6436](https://github.com/filecoin-project/lotus/pull/6436)) +- Fix tests ([filecoin-project/lotus#6371](https://github.com/filecoin-project/lotus/pull/6371)) +- Extend the default deal start epoch delay ([filecoin-project/lotus#6350](https://github.com/filecoin-project/lotus/pull/6350)) +- sealing: Wire up context to batchers ([filecoin-project/lotus#6497](https://github.com/filecoin-project/lotus/pull/6497)) +- Improve address resolution for messages ([filecoin-project/lotus#6364](https://github.com/filecoin-project/lotus/pull/6364)) + +### Dependency Updates +- Proofs v8.0.2 ([filecoin-project/lotus#6524](https://github.com/filecoin-project/lotus/pull/6524)) +- Update to fixed Bellperson ([filecoin-project/lotus#6480](https://github.com/filecoin-project/lotus/pull/6480)) +- Update to go-praamfetch with fslocks ([filecoin-project/lotus#6473](https://github.com/filecoin-project/lotus/pull/6473)) +- Update ffi with fixed multicore sdr support ([filecoin-project/lotus#6471](https://github.com/filecoin-project/lotus/pull/6471)) +- github.com/filecoin-project/go-paramfetch (v0.0.2-0.20200701152213-3e0f0afdc261 -> v0.0.2-0.20210614165157-25a6c7769498) +- github.com/filecoin-project/specs-actors/v5 (v5.0.0-20210512015452-4fe3889fff57 -> v5.0.0) +- github.com/filecoin-project/go-hamt-ipld/v3 (v3.0.1 -> v3.1.0) +- github.com/ipfs/go-log/v2 (v2.1.2-0.20200626104915-0016c0b4b3e4 -> v2.1.3) +- github.com/filecoin-project/go-amt-ipld/v3 (v3.0.0 -> v3.1.0) + +### Network Version v13 HyperDrive Upgrade +- Set HyperDrive upgrade epoch ([filecoin-project/lotus#6565](https://github.com/filecoin-project/lotus/pull/6565)) +- version bump to lotus v1.10.0-rc6 ([filecoin-project/lotus#6529](https://github.com/filecoin-project/lotus/pull/6529)) +- Upgrade epochs for calibration reset ([filecoin-project/lotus#6528](https://github.com/filecoin-project/lotus/pull/6528)) +- Lotus version 1.10.0-rc5 ([filecoin-project/lotus#6504](https://github.com/filecoin-project/lotus/pull/6504)) +- Merge releases into v1.10 release ([filecoin-project/lotus#6494](https://github.com/filecoin-project/lotus/pull/6494)) +- update lotus to v1.10.0-rc3 ([filecoin-project/lotus#6481](https://github.com/filecoin-project/lotus/pull/6481)) +- updated configuration comments for docs +- Lotus version 1.10.0-rc2 ([filecoin-project/lotus#6443](https://github.com/filecoin-project/lotus/pull/6443)) +- Set ntwk v13 HyperDrive Calibration upgrade epoch ([filecoin-project/lotus#6442](https://github.com/filecoin-project/lotus/pull/6442)) + + +## Contributors + +💙Thank you to all the contributors! + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| Łukasz Magiera | 75 | +9088/-1452 | 348 | +| Aayush Rajasekaran | 38 | +6525/-651 | 184 | +| Jennifer Wang | 10 | +575/-104 | 21 | +| Steven Allen | 2 | +272/-8 | 6 | +| vyzo | 6 | +155/-66 | 13 | +| Cory Schwartz | 10 | +171/-27 | 14 | +| Jakub Sztandera | 4 | +177/-13 | 7 | +| Peter Rabbitson | 4 | +65/-42 | 5 | +| Travis Person | 2 | +11/-11 | 4 | +| wangchao | 2 | +3/-2 | 2 | + # 1.9.0 / 2021-05-17 @@ -267,7 +352,7 @@ This release also expands the `lotus-miner sectors extend` CLI, with a new optio - The `expiration-cutoff` flag can be passed to skip sectors whose expiration is past a certain point from the current head. It defaults to infinity (no cutoff), but if, say, 28800 was specified, then only sectors expiring in the next 10 days would be extended (2880 epochs in 1 day). -## Changes +## Changes - Util for miners to extend all v1 sectors (https://github.com/filecoin-project/lotus/pull/5924) - Upgrade the butterfly network (https://github.com/filecoin-project/lotus/pull/5929) @@ -278,7 +363,7 @@ This release also expands the `lotus-miner sectors extend` CLI, with a new optio This is a patch release of Lotus that introduces small fixes to the Storage FSM. -## Changes +## Changes - storagefsm: Fix double unlock with ready WaitDeals sectors (https://github.com/filecoin-project/lotus/pull/5783) - backupds: Allow larger values in write log (https://github.com/filecoin-project/lotus/pull/5776) @@ -294,7 +379,7 @@ This is an hotfix release of Lotus that fixes a critical bug introduced in v1.5. # 1.5.1 / 2021-03-10 -This is an optional release of Lotus that introduces an important fix to the WindowPoSt computation process. The change is to wait for some confidence before drawing beacon randomness for the proof. Without this, invalid proofs might be generated as the result of a null tipset. +This is an optional release of Lotus that introduces an important fix to the WindowPoSt computation process. The change is to wait for some confidence before drawing beacon randomness for the proof. Without this, invalid proofs might be generated as the result of a null tipset. ## Splitstore @@ -387,7 +472,7 @@ FIP-0010 introduces the ability to dispute bad Window PoSts. Node operators are ## Changes - [#5341](https://github.com/filecoin-project/lotus/pull/5341) Add a `LOTUS_DISABLE_V3_ACTOR_MIGRATION` envvar - - Setting this envvar to 1 disables the v3 actor migration, should only be used in the event of a failed migration + - Setting this envvar to 1 disables the v3 actor migration, should only be used in the event of a failed migration # 1.4.2 / 2021-02-17 @@ -395,14 +480,14 @@ This is a large, and highly recommended, optional release with new features and - [FIP-0007 h/amt-v3](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0007.md) which improves the performance of the Filecoin HAMT and AMT. - [FIP-0010 off-chain Window PoSt Verification](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0010.md) which reduces the gas consumption of `SubmitWindowedPoSt` messages significantly by optimistically accepting Window PoSt proofs without verification, and allowing them to be disputed later by off-chain verifiers. - + Note that this release does NOT set an upgrade epoch for v3 actors to take effect. That will be done in the upcoming 1.5.0 release. - - ## New Features - - - [#5341](https://github.com/filecoin-project/lotus/pull/5341) Added sector termination API and CLI - - Run `lotus-miner sectors terminate` -- [#5342](https://github.com/filecoin-project/lotus/pull/5342) Added CLI for using a multisig wallet as miner's owner address + +## New Features + +- [#5341](https://github.com/filecoin-project/lotus/pull/5341) Added sector termination API and CLI + - Run `lotus-miner sectors terminate` +- [#5342](https://github.com/filecoin-project/lotus/pull/5342) Added CLI for using a multisig wallet as miner's owner address - See how to set it up [here](https://github.com/filecoin-project/lotus/pull/5342#issue-554009129) - [#5363](https://github.com/filecoin-project/lotus/pull/5363), [#5418](https://github.com/filecoin-project/lotus/pull/), [#5476](https://github.com/filecoin-project/lotus/pull/5476), [#5459](https://github.com/filecoin-project/lotus/pull/5459) Integrated [spec-actor v3](https://github.com/filecoin-pro5418ject/specs-actors/releases/tag/v3.0.0) - [#5472](https://github.com/filecoin-project/lotus/pull/5472) Generate actor v3 methods for pond @@ -413,7 +498,7 @@ Note that this release does NOT set an upgrade epoch for v3 actors to take effec - [#5411](https://github.com/filecoin-project/lotus/pull/5411) Handle batch `PublishStorageDeals` message in sealing recovery - [#5505](https://github.com/filecoin-project/lotus/pull/5505) Exclude expired deals from batching in `PublishStorageDeals` messages - Added `PublishMsgPeriod` and `MaxDealsPerPublishMsg` to miner `Dealmaking` [configuration](https://docs.filecoin.io/mine/lotus/miner-configuration/#dealmaking-section). See how they work [here](https://docs.filecoin.io/mine/lotus/miner-configuration/#publishing-several-deals-in-one-message). - - [#5538](https://github.com/filecoin-project/lotus/pull/5538), [#5549](https://github.com/filecoin-project/lotus/pull/5549) Added a command to list pending deals and force publish messages. + - [#5538](https://github.com/filecoin-project/lotus/pull/5538), [#5549](https://github.com/filecoin-project/lotus/pull/5549) Added a command to list pending deals and force publish messages. - Run `lotus-miner market pending-publish` - [#5428](https://github.com/filecoin-project/lotus/pull/5428) Moved waiting for `PublishStorageDeals` messages' receipt from markets to lotus - [#5510](https://github.com/filecoin-project/lotus/pull/5510) Added `nerpanet` build option @@ -423,32 +508,32 @@ Note that this release does NOT set an upgrade epoch for v3 actors to take effec - [#5219](https://github.com/filecoin-project/lotus/pull/5219) Added interactive mode for lotus-wallet - [5529](https://github.com/filecoin-project/lotus/pull/5529) Added support for minder nodes in `lotus-shed rpc` util - ## Bug Fixes - - - [#5210](https://github.com/filecoin-project/lotus/pull/5210) Miner should not dial client on restart - - [#5403](https://github.com/filecoin-project/lotus/pull/5403) When estimating GasLimit only apply prior messages up to the nonce - - [#5410](https://github.com/filecoin-project/lotus/pull/510) Fix the calibnet build option - - [#5492](https://github.com/filecoin-project/lotus/pull/5492) Fixed `has` for ipfsbstore for non-existing blocks - - [#5361](https://github.com/filecoin-project/lotus/pull/5361) Fixed retrieval hangs when using `IpfsOnlineMode=true` - - [#5493](https://github.com/filecoin-project/lotus/pull/5493) Fixed retrieval failure when price-per-byte is zero - - [#5506](https://github.com/filecoin-project/lotus/pull/5506) Fixed contexts in the storage adpater - - [#5515](https://github.com/filecoin-project/lotus/pull/5515) Properly wire up `StateReadState` on gateway API - - [#5582](https://github.com/filecoin-project/lotus/pull/5582) Fixed error logging format strings - - [#5614](https://github.com/filecoin-project/lotus/pull/5614) Fixed websocket reconnecting handling +## Bug Fixes + +- [#5210](https://github.com/filecoin-project/lotus/pull/5210) Miner should not dial client on restart +- [#5403](https://github.com/filecoin-project/lotus/pull/5403) When estimating GasLimit only apply prior messages up to the nonce +- [#5410](https://github.com/filecoin-project/lotus/pull/510) Fix the calibnet build option +- [#5492](https://github.com/filecoin-project/lotus/pull/5492) Fixed `has` for ipfsbstore for non-existing blocks +- [#5361](https://github.com/filecoin-project/lotus/pull/5361) Fixed retrieval hangs when using `IpfsOnlineMode=true` +- [#5493](https://github.com/filecoin-project/lotus/pull/5493) Fixed retrieval failure when price-per-byte is zero +- [#5506](https://github.com/filecoin-project/lotus/pull/5506) Fixed contexts in the storage adpater +- [#5515](https://github.com/filecoin-project/lotus/pull/5515) Properly wire up `StateReadState` on gateway API +- [#5582](https://github.com/filecoin-project/lotus/pull/5582) Fixed error logging format strings +- [#5614](https://github.com/filecoin-project/lotus/pull/5614) Fixed websocket reconnecting handling - ## Improvements - - - [#5389](https://github.com/filecoin-project/lotus/pull/5389) Show verified indicator for `./lotus-miner storage-deals list` +## Improvements + +- [#5389](https://github.com/filecoin-project/lotus/pull/5389) Show verified indicator for `./lotus-miner storage-deals list` - [#5229](https://github.com/filecoin-project/lotus/pull/5220) Show power for verified deals in `./lotus-miner setocr list` - [#5407](https://github.com/filecoin-project/lotus/pull/5407) Added explicit check of the miner address protocol - - [#5399](https://github.com/filecoin-project/lotus/pull/5399) watchdog: increase heapprof capture threshold to 90% - - [#5398](https://github.com/filecoin-project/lotus/pull/5398) storageadapter: Look at precommits on-chain since deal publish msg - - [#5470](https://github.com/filecoin-project/lotus/pull/5470) Added `--no-timing` option for `./lotus state compute-state --html` +- [#5399](https://github.com/filecoin-project/lotus/pull/5399) watchdog: increase heapprof capture threshold to 90% +- [#5398](https://github.com/filecoin-project/lotus/pull/5398) storageadapter: Look at precommits on-chain since deal publish msg +- [#5470](https://github.com/filecoin-project/lotus/pull/5470) Added `--no-timing` option for `./lotus state compute-state --html` - [#5417](https://github.com/filecoin-project/lotus/pull/5417) Storage Manager: Always unseal full sectors - [#5393](https://github.com/filecoin-project/lotus/pull/5393) Switched to [filecoin-ffi bls api ](https://github.com/filecoin-project/filecoin-ffi/pull/159)for bls signatures -- [#5380](https://github.com/filecoin-project/lotus/pull/5210) Refactor deals API tests -- [#5397](https://github.com/filecoin-project/lotus/pull/5397) Fixed a flake in the sync manager edge case test +- [#5380](https://github.com/filecoin-project/lotus/pull/5210) Refactor deals API tests +- [#5397](https://github.com/filecoin-project/lotus/pull/5397) Fixed a flake in the sync manager edge case test - [#5406](https://github.com/filecoin-project/lotus/pull/5406) Added a test to ensure a correct window post cannot be disputed - [#5294](https://github.com/filecoin-project/lotus/pull/5394) Added jobs to build Lotus docker image and push it to AWS ECR - [#5387](https://github.com/filecoin-project/lotus/pull/5387) Added network info(mainnet|calibnet) in version @@ -457,7 +542,7 @@ Note that this release does NOT set an upgrade epoch for v3 actors to take effec - [#5047](https://github.com/filecoin-project/lotus/pull/5047) Improved the UX for `./lotus-shed bitfield enc` - [#5282](https://github.com/filecoin-project/lotus/pull/5282) Snake a context through the chian blockstore creation - [#5350](https://github.com/filecoin-project/lotus/pull/5350) Avoid using `mp.cfg` directrly to prevent race condition -- [#5449](https://github.com/filecoin-project/lotus/pull/5449) Documented the block-header better +- [#5449](https://github.com/filecoin-project/lotus/pull/5449) Documented the block-header better - [#5404](https://github.com/filecoin-project/lotus/pull/5404) Added retrying proofs if an incorrect one is generated - [#4545](https://github.com/filecoin-project/lotus/pull/4545) Made state tipset usage consistent in the API - [#5540](https://github.com/filecoin-project/lotus/pull/5540) Removed unnecessary database reads in validation check @@ -474,14 +559,14 @@ Note that this release does NOT set an upgrade epoch for v3 actors to take effec - [#5592](https://github.com/filecoin-project/lotus/pull/5592) Verify FFI version before building ## Dependency Updates - - [#5296](https://github.com/filecoin-project/lotus/pull/5396) Upgraded to [raulk/go-watchdog@v1.0.1](https://github.com/raulk/go-watchdog/releases/tag/v1.0.1) - - [#5450](https://github.com/filecoin-project/lotus/pull/5450) Dependency updates - - [#5425](https://github.com/filecoin-project/lotus/pull/5425) Fixed stale imports in testplans/lotus-soup - - [#5535](https://github.com/filecoin-project/lotus/pull/5535) Updated to [go-fil-markets@v1.1.7](https://github.com/filecoin-project/go-fil-markets/releases/tag/v1.1.7) - - [#5616](https://github.com/filecoin-project/lotus/pull/5600) Updated to [filecoin-ffi@b6e0b35fb49ed0fe](https://github.com/filecoin-project/filecoin-ffi/releases/tag/b6e0b35fb49ed0fe) - - [#5599](https://github.com/filecoin-project/lotus/pull/5599) Updated to [go-bitfield@v0.2.4](https://github.com/filecoin-project/go-bitfield/releases/tag/v0.2.4) - - [#5614](https://github.com/filecoin-project/lotus/pull/5614), , [#5621](https://github.com/filecoin-project/lotus/pull/5621) Updated to [go-jsonrpc@v0.1.3](https://github.com/filecoin-project/go-jsonrpc/releases/tag/v0.1.3) - - [#5459](https://github.com/filecoin-project/lotus/pull/5459) Updated to [spec-actors@v3.0.1](https://github.com/filecoin-project/specs-actors/releases/tag/v3.0.1) +- [#5296](https://github.com/filecoin-project/lotus/pull/5396) Upgraded to [raulk/go-watchdog@v1.0.1](https://github.com/raulk/go-watchdog/releases/tag/v1.0.1) +- [#5450](https://github.com/filecoin-project/lotus/pull/5450) Dependency updates +- [#5425](https://github.com/filecoin-project/lotus/pull/5425) Fixed stale imports in testplans/lotus-soup +- [#5535](https://github.com/filecoin-project/lotus/pull/5535) Updated to [go-fil-markets@v1.1.7](https://github.com/filecoin-project/go-fil-markets/releases/tag/v1.1.7) +- [#5616](https://github.com/filecoin-project/lotus/pull/5600) Updated to [filecoin-ffi@b6e0b35fb49ed0fe](https://github.com/filecoin-project/filecoin-ffi/releases/tag/b6e0b35fb49ed0fe) +- [#5599](https://github.com/filecoin-project/lotus/pull/5599) Updated to [go-bitfield@v0.2.4](https://github.com/filecoin-project/go-bitfield/releases/tag/v0.2.4) +- [#5614](https://github.com/filecoin-project/lotus/pull/5614), , [#5621](https://github.com/filecoin-project/lotus/pull/5621) Updated to [go-jsonrpc@v0.1.3](https://github.com/filecoin-project/go-jsonrpc/releases/tag/v0.1.3) +- [#5459](https://github.com/filecoin-project/lotus/pull/5459) Updated to [spec-actors@v3.0.1](https://github.com/filecoin-project/specs-actors/releases/tag/v3.0.1) ## Network Version v10 Upgrade @@ -489,7 +574,7 @@ Note that this release does NOT set an upgrade epoch for v3 actors to take effec - [#5603](https://github.com/filecoin-project/lotus/pull/5603) Set nerpanet's upgrade epochs up to v3 actors - [#5471](https://github.com/filecoin-project/lotus/pull/5471), [#5456](https://github.com/filecoin-project/lotus/pull/5456) Set calibration net actor v3 migration epochs for testing - [#5434](https://github.com/filecoin-project/lotus/pull/5434) Implemented pre-migration framework -- [#5476](https://github.com/filecoin-project/lotus/pull/5477) Tune migration +- [#5476](https://github.com/filecoin-project/lotus/pull/5477) Tune migration # 1.4.1 / 2021-01-20 @@ -688,9 +773,9 @@ This is an optional Lotus release that introduces various improvements to the mi - Error out deals that are not activated by proposed deal start epoch (https://github.com/filecoin-project/lotus/pull/5061) # 1.2.1 / 2020-11-20 - + This is a very small release of Lotus that fixes an issue users are experiencing when importing snapshots. There is no need to upgrade unless you experience an issue with creating a new datastore directory in the Lotus repo. - + ## Changes - fix blockstore directory not created automatically (https://github.com/filecoin-project/lotus/pull/4922) @@ -710,7 +795,7 @@ The changes that break consensus are: - Correction of the VM circulating supply calculation (https://github.com/filecoin-project/lotus/pull/4862) - Retuning gas costs (https://github.com/filecoin-project/lotus/pull/4830) - Avoid sending messages to the zero BLS address (https://github.com/filecoin-project/lotus/pull/4888) - + ## Other Changes - delayed pubsub subscribe for messages topic (https://github.com/filecoin-project/lotus/pull/3646) @@ -867,7 +952,7 @@ This is an optional release of Lotus that upgrades Lotus dependencies, and inclu This is a patch release of Lotus that builds on the fixes involving worker keys that was introduced in v1.1.1. Miners and node operators should update to this release as soon as possible in order to ensure their blocks are propagated and validated. -## Changes +## Changes - Handle worker key changes correctly in runtime (https://github.com/filecoin-project/lotus/pull/4579) @@ -1110,7 +1195,7 @@ This consensus-breaking release of Lotus upgrades the actors version to v2.0.0. - Fix pond (https://github.com/filecoin-project/lotus/pull/4203) - allow manual setting of noncefix fee cap (https://github.com/filecoin-project/lotus/pull/4205) - implement command to get execution traces of any message (https://github.com/filecoin-project/lotus/pull/4200) -- conformance: minor driver refactors (https://github.com/filecoin-project/lotus/pull/4211) +- conformance: minor driver refactors (https://github.com/filecoin-project/lotus/pull/4211) - lotus-pcr: ignore all other messages (https://github.com/filecoin-project/lotus/pull/4218) - lotus-pcr: zero refund (https://github.com/filecoin-project/lotus/pull/4229) @@ -1137,7 +1222,7 @@ We are grateful for every contribution! This optional release of Lotus introduces a new version of markets which switches to CBOR-map encodings, and allows datastore migrations. The release also introduces several improvements to the mining process, a few performance optimizations, and a battery of UX additions and enhancements. -## Changes +## Changes #### Dependencies @@ -1208,7 +1293,7 @@ This consensus-breaking release of Lotus introduces an upgrade to the network. T This release also updates go-fil-markets to fix an incompatibility issue between v0.7.2 and earlier versions. -## Changes +## Changes #### Dependencies @@ -1297,7 +1382,7 @@ This optional release of Lotus introduces some critical fixes to the window PoSt ## Changes -#### Some notable improvements: +#### Some notable improvements: - Correctly construct params for `SubmitWindowedPoSt` messages (https://github.com/filecoin-project/lotus/pull/3909) - Skip sectors correctly for Window PoSt (https://github.com/filecoin-project/lotus/pull/3839) @@ -1333,7 +1418,7 @@ This consensus-breaking release of Lotus is designed to test a network upgrade o - Drand upgrade (https://github.com/filecoin-project/lotus/pull/3670) - Multisig API additions (https://github.com/filecoin-project/lotus/pull/3590) -#### Storage Miner +#### Storage Miner - Increase the number of times precommit2 is attempted before moving back to precommit1 (https://github.com/filecoin-project/lotus/pull/3720) @@ -1376,7 +1461,7 @@ This release introduces some critical fixes to message selection and gas estimat ## Changes -#### Messagepool +#### Messagepool - Warn when optimal selection fails to pack a block and we fall back to random selection (https://github.com/filecoin-project/lotus/pull/3708) - Add basic command for printing gas performance of messages in the mpool (https://github.com/filecoin-project/lotus/pull/3701) @@ -1446,7 +1531,7 @@ This release also introduces many improvements to Lotus! Among them are a new ve - Add additional info about gas premium (https://github.com/filecoin-project/lotus/pull/3578) - Fix GasPremium capping logic (https://github.com/filecoin-project/lotus/pull/3552) -#### Payment channels +#### Payment channels - Get available funds by address or by from/to (https://github.com/filecoin-project/lotus/pull/3547) - Create `lotus paych status` command (https://github.com/filecoin-project/lotus/pull/3523) @@ -1496,7 +1581,7 @@ This patch includes a crucial fix to the message pool selection logic, strongly This patch includes a hotfix to the `GasEstimateFeeCap` method, capping the estimated fee to a reasonable level by default. -## Changes +## Changes - Added target height to sync wait (https://github.com/filecoin-project/lotus/pull/3502) - Disable codecov annotations (https://github.com/filecoin-project/lotus/pull/3514) @@ -1526,7 +1611,7 @@ This patch includes some bugfixes to the sector sealing process, and updates go- # 0.5.7 / 2020-08-31 -This patch release includes some bugfixes and enhancements to the sector lifecycle and message pool logic. +This patch release includes some bugfixes and enhancements to the sector lifecycle and message pool logic. ## Changes @@ -1546,7 +1631,7 @@ Hotfix release that fixes a panic in the sealing scheduler (https://github.com/f # 0.5.5 This patch release introduces a large number of improvements to the sealing process. -It also updates go-fil-markets to +It also updates go-fil-markets to [version 0.5.8](https://github.com/filecoin-project/go-fil-markets/releases/tag/v0.5.8), and go-libp2p-pubsub to [v0.3.5](https://github.com/libp2p/go-libp2p-pubsub/releases/tag/v0.3.5). @@ -1559,16 +1644,16 @@ and go-libp2p-pubsub to [v0.3.5](https://github.com/libp2p/go-libp2p-pubsub/rele - The following improvements were introduced in https://github.com/filecoin-project/lotus/pull/3350. - - Allow `lotus-miner sectors remove` to remove a sector in any state. - - Create a separate state in the storage FSM dedicated to submitting the Commit message. - - Recovery for when the Deal IDs of deals in a sector get changed in a reorg. - - Auto-retry sending Precommit and Commit messages if they run out of gas - - Auto-retry sector remove tasks when they fail - - Compact worker windows, and allow their tasks to be executed in any order + - Allow `lotus-miner sectors remove` to remove a sector in any state. + - Create a separate state in the storage FSM dedicated to submitting the Commit message. + - Recovery for when the Deal IDs of deals in a sector get changed in a reorg. + - Auto-retry sending Precommit and Commit messages if they run out of gas + - Auto-retry sector remove tasks when they fail + - Compact worker windows, and allow their tasks to be executed in any order - Don't simply skip PoSt for bad sectors (https://github.com/filecoin-project/lotus/pull/3323) -#### Message Pool +#### Message Pool - Spam Protection: Track required funds for pending messages (https://github.com/filecoin-project/lotus/pull/3313) @@ -1593,7 +1678,7 @@ A patch release, containing a few nice bugfixes and improvements: # 0.5.3 -Yet another hotfix release. +Yet another hotfix release. A lesson for readers, having people who have been awake for 12+ hours review your hotfix PR is not a good idea. Find someone who has enough slept recently enough to give you good code review, otherwise you'll end up quickly bumping @@ -1612,9 +1697,9 @@ This is a hotfix release. # 0.5.1 / 2020-08-24 -The Space Race release! +The Space Race release! This release contains the genesis car file and bootstrap peers for the space -race network. +race network. Additionally, we included two small fixes to genesis creation: - Randomize ticket value in genesis generation @@ -1632,9 +1717,9 @@ Among the highlights included in this release are: - Gas changes: We implemented EIP-1559 and introduced real gas values. - Deal-making: We now support "Committed Capacity" sectors, "fast-retrieval" deals, -and the packing of multiple deals into a single sector. + and the packing of multiple deals into a single sector. - Renamed features: We renamed some of the binaries, environment variables, and default -paths associated with a Lotus node. + paths associated with a Lotus node. ### Gas changes @@ -1642,19 +1727,19 @@ We made some significant changes to the mechanics of gas in this release. #### Network fee -We implemented something similar to +We implemented something similar to [Ethereum's EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md). The `Message` structure had three changes: - The `GasPrice` field has been removed - A new `GasFeeCap` field has been added, which controls the maximum cost -the sender incurs for the message + the sender incurs for the message - A new `GasPremium` field has been added, which controls the reward a miner -earns for including the message + earns for including the message -A sender will never be charged more than `GasFeeCap * GasLimit`. +A sender will never be charged more than `GasFeeCap * GasLimit`. A miner will typically earn `GasPremium * GasLimit` as a reward. -The `Blockheader` structure has one new field, called `ParentBaseFee`. +The `Blockheader` structure has one new field, called `ParentBaseFee`. Informally speaking,the `ParentBaseFee` is increased when blocks are densely packed with messages, and decreased otherwise. From 64567d7f3223771b73ed25cf40de2b94f5598cf1 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 23 Jun 2021 18:49:52 -0700 Subject: [PATCH 239/257] add simulation projections to changelog --- CHANGELOG.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c6c2ab54..261f41217 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,16 @@ Note: ### Projected state tree growth -As a result of the accelerated onboardiPreCommitSectorsBatcng of storage onto the network, it is possible +In order to validate the Hyperdrive changes, we wrote a simulation to seal as many sectors as fast as possible assuming the same number and mix of 32GiB and 64GiB miners as the current network. + +Given these assumptions: + +- We'd expect a network storage growth rate of around 530PiB per day. +- We'd expect network bandwidth dedicated to `SubmitWindowedPoSt` to grow by about 0.02% per day. +- We'd expect the [state-tree](https://spec.filecoin.io/#section-systems.filecoin_vm.state_tree) to grow by 1.6GiB per day. + - Nearly all of the state-tree growth is expected to come from new sector metadata. +- We'd expect the daily lotus datastore growth rate to increase by about 10-15%. + - Most "growth" of the lotus datastore is due to "churn", historical data that's no longer referenced by the latest state-tree. ### Hardware requirements and suggestions From efaa7b0d7600cdfa7be7f8f6f7c6cda728997f14 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 23 Jun 2021 19:02:14 -0700 Subject: [PATCH 240/257] update contributors to include all filecoin projects --- CHANGELOG.md | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 261f41217..62180e227 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -120,18 +120,23 @@ Included in the HyperDrive upgrade is [FIP-0015](https://github.com/filecoin-pro 💙Thank you to all the contributors! -| Contributor | Commits | Lines ± | Files Changed | -|-------------|---------|---------|---------------| -| Łukasz Magiera | 75 | +9088/-1452 | 348 | -| Aayush Rajasekaran | 38 | +6525/-651 | 184 | -| Jennifer Wang | 10 | +575/-104 | 21 | -| Steven Allen | 2 | +272/-8 | 6 | -| vyzo | 6 | +155/-66 | 13 | -| Cory Schwartz | 10 | +171/-27 | 14 | -| Jakub Sztandera | 4 | +177/-13 | 7 | -| Peter Rabbitson | 4 | +65/-42 | 5 | -| Travis Person | 2 | +11/-11 | 4 | -| wangchao | 2 | +3/-2 | 2 | +| Contributor | Commits | Lines ± | Files Changed | +|--------------------|---------|-------------|---------------| +| Łukasz Magiera | 81 | +9606/-1536 | 361 | +| Aayush Rajasekaran | 41 | +6543/-679 | 189 | +| ZenGround0 | 11 | +4074/-727 | 110 | +| Alex | 10 | +2035/-1177 | 55 | +| Ian Davis | 1 | +779/-12 | 5 | +| Frrist | 2 | +722/-6 | 6 | +| Steven Allen | 6 | +368/-24 | 15 | +| Jennifer Wang | 11 | +204/-111 | 19 | +| vyzo | 6 | +155/-66 | 13 | +| Cory Schwartz | 10 | +171/-27 | 14 | +| Jakub Sztandera | 4 | +177/-13 | 7 | +| Peter Rabbitson | 4 | +65/-42 | 5 | +| Travis Person | 2 | +11/-11 | 4 | +| Kirk Baird | 1 | +1/-5 | 1 | +| wangchao | 2 | +3/-2 | 2 | # 1.9.0 / 2021-05-17 From 1411cff69e3dd05dc2be073e104606ab88d34490 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 23 Jun 2021 19:03:24 -0700 Subject: [PATCH 241/257] change mkreleaselog script to only include filecoin-project --- scripts/mkreleaselog | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/scripts/mkreleaselog b/scripts/mkreleaselog index c59027075..c9eaef4fb 100755 --- a/scripts/mkreleaselog +++ b/scripts/mkreleaselog @@ -5,26 +5,7 @@ export GOPATH="$(go env GOPATH)" alias jq="jq --unbuffered" -AUTHORS=( - # orgs - ipfs - ipld - libp2p - multiformats - filecoin-project - ipfs-shipyard - - # Authors of personal repos used by go-ipfs that should be mentioned in the - # release notes. - whyrusleeping - Kubuxu - jbenet - Stebalien - marten-seemann - hsanjuan - lucas-clemente - warpfork -) +AUTHORS=(filecoin-project) [[ -n "${REPO_FILTER+x}" ]] || REPO_FILTER="github.com/(${$(printf "|%s" "${AUTHORS[@]}"):1})" From f5fad636cf92b5660b6f315afbc7fa617750dd3a Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 23 Jun 2021 19:09:43 -0700 Subject: [PATCH 242/257] Apply edits Co-authored-by: MollyM --- CHANGELOG.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62180e227..b88ad1d40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,9 +14,9 @@ Note that this release is built on top of Lotus v1.9.0. Enterprising users can u ## Proof batching and aggregation -FIPs [0008](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0008.md) and [0013](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0013.md) combine to allow for a significant increase in the rate of onboarding storage on the Filecoin network. It is hoped that this will lead to more useful data being stored on the network, reduced network congestion, and lower network base fee. +FIPs [0008](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0008.md) and [0013](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0013.md) combine to allow for a significant increase in the rate of onboarding storage on the Filecoin network. This aims to lead to more useful data being stored on the network, reduced network congestion, and lower network base fee. -**Check out the documentation [here](https://docs.filecoin.io/mine/lotus/miner-configuration/#precommitsectorsbatch) for details of the new Lotus miner sealing config options, [here](https://docs.filecoin.io/mine/lotus/miner-configuration/#fees-section) for fee config options, and explanations of the new features.** +**Check out the documentation [here](https://docs.filecoin.io/mine/lotus/miner-configuration/#precommitsectorsbatch) for details on the new Lotus miner sealing config options, [here](https://docs.filecoin.io/mine/lotus/miner-configuration/#fees-section) for fee config options, and explanations of the new features.** Note: - We recommend to keep `PreCommitSectorsBatch` as 1. @@ -25,15 +25,15 @@ Note: ### Projected state tree growth -In order to validate the Hyperdrive changes, we wrote a simulation to seal as many sectors as fast as possible assuming the same number and mix of 32GiB and 64GiB miners as the current network. +In order to validate the Hyperdrive changes, we wrote a simulation to seal as many sectors as quickly as possible, assuming the same number and mix of 32GiB and 64GiB miners as the current network. Given these assumptions: -- We'd expect a network storage growth rate of around 530PiB per day. +- We'd expect a network storage growth rate of around 530PiB per day. 😳 🎉 🥳 😅 - We'd expect network bandwidth dedicated to `SubmitWindowedPoSt` to grow by about 0.02% per day. -- We'd expect the [state-tree](https://spec.filecoin.io/#section-systems.filecoin_vm.state_tree) to grow by 1.6GiB per day. +- We'd expect the [state-tree](https://spec.filecoin.io/#section-systems.filecoin_vm.state_tree) (and therefore [snapshot](https://docs.filecoin.io/get-started/lotus/chain/#lightweight-snapshot)) size to grow by 1.16GiB per day. - Nearly all of the state-tree growth is expected to come from new sector metadata. -- We'd expect the daily lotus datastore growth rate to increase by about 10-15%. +- We'd expect the daily lotus datastore growth rate to increase by about 10-15% (from current ~21GiB/day). - Most "growth" of the lotus datastore is due to "churn", historical data that's no longer referenced by the latest state-tree. ### Hardware requirements and suggestions From 7779d446ce9e235c863fbd183868adb27861b0b6 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 23 Jun 2021 19:18:27 -0700 Subject: [PATCH 243/257] remove hardware suggestion section This isn't useful in its current state and isn't urgent at this point. We can publish recommendations later. --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b88ad1d40..7fac8797c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,10 +36,6 @@ Given these assumptions: - We'd expect the daily lotus datastore growth rate to increase by about 10-15% (from current ~21GiB/day). - Most "growth" of the lotus datastore is due to "churn", historical data that's no longer referenced by the latest state-tree. -### Hardware requirements and suggestions - -As the size of a single state tree grows, so will the size of the minimal datastore needed to sync the blockchain. The Filecoin protocol requires any node validating the chain to have the last 1800 state trees (2 * `ChainFinality`). Depending on how fast storage providers seal new sectors, this could get as large as AMOUNT in 6 months, and grow as fast as 20 GB per day. This increases the necessary hardware requirements to validate the Filecoin blockchain, but can be supported by images such as [Amazon EC2](https://aws.amazon.com/ec2/instance-explorer/?ec2-instances-cards.sort-by=item.additionalFields.category-order&ec2-instances-cards.sort-order=asc&awsf.ec2-instances-filter-category=*all&awsf.ec2-instances-filter-processors=*all&awsf.ec2-instances-filter-accelerators=*all&awsf.ec2-instances-filter-capabilities=additional-capabilities%23instance-storage&ec2-instances-cards.q=ssd&ec2-instances-cards.q_operator=AND&awsm.page-ec2-instances-cards=1) and [Google Cloud Platform](https://cloud.google.com/compute/docs/machine-types). - ### Future improvements Various Lotus improvements are planned moving forward to mitigate the effects of the growing state tree size. The primary improvement is the [Lotus splitstore](https://github.com/filecoin-project/lotus/discussions/5788), which will soon be enabled by default. The feature allows for [online garbage collection](https://github.com/filecoin-project/lotus/issues/6577) for nodes that do not seek to maintain full chain and state history, thus eliminating the need for users to delete their datastores and sync from snapshots. From 28f2f96b65cac8f2ff736d2c6d42c1cb7c6096fa Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Wed, 23 Jun 2021 22:29:03 -0400 Subject: [PATCH 244/257] more edits --- CHANGELOG.md | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fac8797c..a724c841d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ # 1.10.0 / 2021-06-23 -This is a mandatory release of Lotus that introduces Filecoin network v13, codenamed the HyperDrive upgrade. The Filecoin mainnet will upgrade, which is 2021-06-30T22:00:00Z. The network upgrade introduces the following FIPs: +This is a mandatory release of Lotus that introduces Filecoin network v13, codenamed the HyperDrive upgrade. The +Filecoin mainnet will upgrade, which is epoch 892800, on 2021-06-30T22:00:00Z. The network upgrade introduces the +following FIPs: - [FIP-0008](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0008.md): Add miner batched sector pre-commit method - [FIP-0011](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0011.md): Remove reward auction from reporting consensus faults @@ -21,7 +23,8 @@ FIPs [0008](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0008.m Note: - We recommend to keep `PreCommitSectorsBatch` as 1. - We recommend miners to set `PreCommitBatchWait` lower than 30 hours. - - We recommend miners to set a longer `CommitBatchSlack` and `PreCommitBatchSlac` to prevent message failures due to expirations. + - We recommend miners to set a longer `CommitBatchSlack` and `PreCommitBatchSlack` to prevent message failures + due to expirations. ### Projected state tree growth @@ -118,21 +121,21 @@ Included in the HyperDrive upgrade is [FIP-0015](https://github.com/filecoin-pro | Contributor | Commits | Lines ± | Files Changed | |--------------------|---------|-------------|---------------| -| Łukasz Magiera | 81 | +9606/-1536 | 361 | -| Aayush Rajasekaran | 41 | +6543/-679 | 189 | -| ZenGround0 | 11 | +4074/-727 | 110 | -| Alex | 10 | +2035/-1177 | 55 | -| Ian Davis | 1 | +779/-12 | 5 | -| Frrist | 2 | +722/-6 | 6 | -| Steven Allen | 6 | +368/-24 | 15 | -| Jennifer Wang | 11 | +204/-111 | 19 | -| vyzo | 6 | +155/-66 | 13 | -| Cory Schwartz | 10 | +171/-27 | 14 | -| Jakub Sztandera | 4 | +177/-13 | 7 | -| Peter Rabbitson | 4 | +65/-42 | 5 | -| Travis Person | 2 | +11/-11 | 4 | -| Kirk Baird | 1 | +1/-5 | 1 | -| wangchao | 2 | +3/-2 | 2 | +| @magik6k | 81 | +9606/-1536 | 361 | +| @arajasek | 41 | +6543/-679 | 189 | +| @ZenGround0 | 11 | +4074/-727 | 110 | +| @anorth | 10 | +2035/-1177 | 55 | +| @iand | 1 | +779/-12 | 5 | +| @frrist | 2 | +722/-6 | 6 | +| @Stebalien | 6 | +368/-24 | 15 | +| @jennijuju | 11 | +204/-111 | 19 | +| @vyzo | 6 | +155/-66 | 13 | +| @coryschwartz | 10 | +171/-27 | 14 | +| @Kubuxu | 4 | +177/-13 | 7 | +| @ribasushi | 4 | +65/-42 | 5 | +| @travisperson | 2 | +11/-11 | 4 | +| @kirk-baird | 1 | +1/-5 | 1 | +| @wangchao | 2 | +3/-2 | 2 | # 1.9.0 / 2021-05-17 From 7c07dc9ed194041f08a54e42fac2206db18508db Mon Sep 17 00:00:00 2001 From: IPFSUnion Date: Thu, 24 Jun 2021 14:07:15 +0800 Subject: [PATCH 245/257] fix an error in msigLockCancel --- cli/multisig.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/multisig.go b/cli/multisig.go index 9ddfcdfc1..c51677d85 100644 --- a/cli/multisig.go +++ b/cli/multisig.go @@ -1428,7 +1428,7 @@ var msigLockCancelCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 6 { + if cctx.Args().Len() != 5 { return ShowHelp(cctx, fmt.Errorf("must pass multisig address, tx id, start epoch, unlock duration, and amount")) } From 2e5e2f4e0ee676ba70cd9b860620eca2e8d29fd6 Mon Sep 17 00:00:00 2001 From: Cory Schwartz Date: Thu, 24 Jun 2021 06:33:32 -0700 Subject: [PATCH 246/257] move with changed name --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 880ddb667..5a068104b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -456,7 +456,7 @@ jobs: name: prepare workspace command: | mkdir appimage - mv Lotus-latest-x86_64.AppImage appimage + mv Lotus-*-AppImage appimage - persist_to_workspace: root: "." paths: From 3cea8b5e26f83a33e9effcb90f9e9adb5cb03713 Mon Sep 17 00:00:00 2001 From: Cory Schwartz Date: Thu, 24 Jun 2021 06:39:05 -0700 Subject: [PATCH 247/257] .Appimage --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5a068104b..fcd297098 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -456,7 +456,7 @@ jobs: name: prepare workspace command: | mkdir appimage - mv Lotus-*-AppImage appimage + mv Lotus-*.AppImage appimage - persist_to_workspace: root: "." paths: From 0517ef260b9a3e0d39ebb5d68abe423720db1cb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 24 Jun 2021 18:48:12 +0200 Subject: [PATCH 248/257] Fix CircleCI gen --- .circleci/template.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/template.yml b/.circleci/template.yml index 75fbdfc76..fb59f23ea 100644 --- a/.circleci/template.yml +++ b/.circleci/template.yml @@ -434,7 +434,7 @@ jobs: name: prepare workspace command: | mkdir appimage - mv Lotus-latest-x86_64.AppImage appimage + mv Lotus-*.AppImage appimage - persist_to_workspace: root: "." paths: From cb4d7cb9e7d9b529fb427228c300db64b701afdd Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Thu, 24 Jun 2021 12:57:54 -0400 Subject: [PATCH 249/257] Make query-ask CLI more graceful --- cli/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/client.go b/cli/client.go index 06d39769d..dc925b72f 100644 --- a/cli/client.go +++ b/cli/client.go @@ -1759,7 +1759,7 @@ var clientQueryAskCmd = &cli.Command{ return xerrors.Errorf("failed to get peerID for miner: %w", err) } - if *mi.PeerId == peer.ID("SETME") { + if mi.PeerId == nil || *mi.PeerId == peer.ID("SETME") { return fmt.Errorf("the miner hasn't initialized yet") } From 3f3336340f45b1ff5eb7c36333fd8bdd886fcab8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 25 Jun 2021 10:48:47 +0200 Subject: [PATCH 250/257] Fix wallet error messages --- node/impl/client/client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node/impl/client/client.go b/node/impl/client/client.go index 4732e5c92..29eb8550e 100644 --- a/node/impl/client/client.go +++ b/node/impl/client/client.go @@ -151,12 +151,12 @@ func (a *API) dealStarter(ctx context.Context, params *api.StartDealParams, isSt walletKey, err := a.StateAccountKey(ctx, params.Wallet, types.EmptyTSK) if err != nil { - return nil, xerrors.Errorf("failed resolving params.Wallet addr: %w", params.Wallet) + return nil, xerrors.Errorf("failed resolving params.Wallet addr (%s): %w", params.Wallet, err) } exist, err := a.WalletHas(ctx, walletKey) if err != nil { - return nil, xerrors.Errorf("failed getting addr from wallet: %w", params.Wallet) + return nil, xerrors.Errorf("failed getting addr from wallet (%s): %w", params.Wallet, err) } if !exist { return nil, xerrors.Errorf("provided address doesn't exist in wallet") From c10d99e3fdf15ce39d8c41dbd7a6437a8583d6d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 25 Jun 2021 12:17:53 +0200 Subject: [PATCH 251/257] multiwallet: Don't fail if key is found in any wallet --- chain/wallet/multi.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/chain/wallet/multi.go b/chain/wallet/multi.go index 1fee4f040..a88475c2e 100644 --- a/chain/wallet/multi.go +++ b/chain/wallet/multi.go @@ -4,6 +4,7 @@ import ( "context" "go.uber.org/fx" + "go.uber.org/multierr" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" @@ -56,18 +57,18 @@ func nonNil(wallets ...getif) []api.Wallet { func (m MultiWallet) find(ctx context.Context, address address.Address, wallets ...getif) (api.Wallet, error) { ws := nonNil(wallets...) + var merr error + for _, w := range ws { have, err := w.WalletHas(ctx, address) - if err != nil { - return nil, err - } + merr = multierr.Append(merr, err) - if have { + if err == nil && have { return w, nil } } - return nil, nil + return nil, merr } func (m MultiWallet) WalletNew(ctx context.Context, keyType types.KeyType) (address.Address, error) { From ec69ac0b2cd4b6941023e9b04bc8bfeb69965b45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Fri, 25 Jun 2021 22:26:38 +0100 Subject: [PATCH 252/257] downgrade libp2p/go-libp2p-yamux to v0.5.1. --- go.mod | 2 ++ go.sum | 30 ++++++------------------------ 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 1cd2181e3..36ea7835c 100644 --- a/go.mod +++ b/go.mod @@ -161,6 +161,8 @@ require ( honnef.co/go/tools v0.0.1-2020.1.3 // indirect ) +replace github.com/libp2p/go-libp2p-yamux => github.com/libp2p/go-libp2p-yamux v0.5.1 + replace github.com/filecoin-project/lotus => ./ replace github.com/golangci/golangci-lint => github.com/golangci/golangci-lint v1.18.0 diff --git a/go.sum b/go.sum index 62d46ea61..f9499e2f9 100644 --- a/go.sum +++ b/go.sum @@ -704,9 +704,9 @@ github.com/ipfs/go-log/v2 v2.1.1/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHn github.com/ipfs/go-log/v2 v2.1.2-0.20200626104915-0016c0b4b3e4/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= github.com/ipfs/go-log/v2 v2.1.2/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= github.com/ipfs/go-log/v2 v2.1.3 h1:1iS3IU7aXRlbgUpN8yTTpJ53NXYjAe37vcI5+5nYrzk= -github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= github.com/ipfs/go-log/v2 v2.1.3 h1:1iS3IU7aXRlbgUpN8yTTpJ53NXYjAe37vcI5+5nYrzk= github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= +github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= github.com/ipfs/go-merkledag v0.0.3/go.mod h1:Oc5kIXLHokkE1hWGMBHw+oxehkAaTOqtEb7Zbh6BhLA= github.com/ipfs/go-merkledag v0.0.6/go.mod h1:QYPdnlvkOg7GnQRofu9XZimC5ZW5Wi3bKys/4GQQfto= github.com/ipfs/go-merkledag v0.2.3/go.mod h1:SQiXrtSts3KGNmgOzMICy5c0POOpUNQLvB3ClKnBAlk= @@ -1067,18 +1067,8 @@ github.com/libp2p/go-libp2p-transport-upgrader v0.2.0/go.mod h1:mQcrHj4asu6ArfSo github.com/libp2p/go-libp2p-transport-upgrader v0.3.0/go.mod h1:i+SKzbRnvXdVbU3D1dwydnTmKRPXiAR/fyvi1dXuL4o= github.com/libp2p/go-libp2p-transport-upgrader v0.4.2 h1:4JsnbfJzgZeRS9AWN7B9dPqn/LY/HoQTlO9gtdJTIYM= github.com/libp2p/go-libp2p-transport-upgrader v0.4.2/go.mod h1:NR8ne1VwfreD5VIWIU62Agt/J18ekORFU/j1i2y8zvk= -github.com/libp2p/go-libp2p-yamux v0.1.2/go.mod h1:xUoV/RmYkg6BW/qGxA9XJyg+HzXFYkeXbnhjmnYzKp8= -github.com/libp2p/go-libp2p-yamux v0.1.3/go.mod h1:VGSQVrqkh6y4nm0189qqxMtvyBft44MOYYPpYKXiVt4= -github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8= -github.com/libp2p/go-libp2p-yamux v0.2.1/go.mod h1:1FBXiHDk1VyRM1C0aez2bCfHQ4vMZKkAQzZbkSQt5fI= -github.com/libp2p/go-libp2p-yamux v0.2.2/go.mod h1:lIohaR0pT6mOt0AZ0L2dFze9hds9Req3OfS+B+dv4qw= -github.com/libp2p/go-libp2p-yamux v0.2.5/go.mod h1:Zpgj6arbyQrmZ3wxSZxfBmbdnWtbZ48OpsfmQVTErwA= -github.com/libp2p/go-libp2p-yamux v0.2.7/go.mod h1:X28ENrBMU/nm4I3Nx4sZ4dgjZ6VhLEn0XhIoZ5viCwU= -github.com/libp2p/go-libp2p-yamux v0.2.8/go.mod h1:/t6tDqeuZf0INZMTgd0WxIRbtK2EzI2h7HbFm9eAKI4= -github.com/libp2p/go-libp2p-yamux v0.4.0/go.mod h1:+DWDjtFMzoAwYLVkNZftoucn7PelNoy5nm3tZ3/Zw30= -github.com/libp2p/go-libp2p-yamux v0.5.0/go.mod h1:AyR8k5EzyM2QN9Bbdg6X1SkVVuqLwTGf0L4DFq9g6po= -github.com/libp2p/go-libp2p-yamux v0.5.4 h1:/UOPtT/6DHPtr3TtKXBHa6g0Le0szYuI33Xc/Xpd7fQ= -github.com/libp2p/go-libp2p-yamux v0.5.4/go.mod h1:tfrXbyaTqqSU654GTvK3ocnSZL3BuHoeTSqhcel1wsE= +github.com/libp2p/go-libp2p-yamux v0.5.1 h1:sX4WQPHMhRxJE5UZTfjEuBvlQWXB5Bo3A2JK9ZJ9EM0= +github.com/libp2p/go-libp2p-yamux v0.5.1/go.mod h1:dowuvDu8CRWmr0iqySMiSxK+W0iL5cMVO9S94Y6gkv4= github.com/libp2p/go-maddr-filter v0.0.1/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q= github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q= github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M= @@ -1149,19 +1139,11 @@ github.com/libp2p/go-ws-transport v0.3.0/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1f github.com/libp2p/go-ws-transport v0.3.1/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1fiZuEaBYusP5zsk= github.com/libp2p/go-ws-transport v0.4.0 h1:9tvtQ9xbws6cA5LvqdE6Ne3vcmGB4f1z9SByggk4s0k= github.com/libp2p/go-ws-transport v0.4.0/go.mod h1:EcIEKqf/7GDjth6ksuS/6p7R49V4CBY6/E7R/iyhYUA= -github.com/libp2p/go-yamux v1.2.1/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.2.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.3.0/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.3.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.3.5/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= +github.com/libp2p/go-yamux v1.3.6 h1:O5qcBXRcfqecvQ/My9NqDNHB3/5t58yuJYqthcKhhgE= github.com/libp2p/go-yamux v1.3.6/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.3.7/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= -github.com/libp2p/go-yamux v1.4.0/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= -github.com/libp2p/go-yamux v1.4.1 h1:P1Fe9vF4th5JOxxgQvfbOHkrGqIZniTLf+ddhZp8YTI= -github.com/libp2p/go-yamux v1.4.1/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= -github.com/libp2p/go-yamux/v2 v2.2.0 h1:RwtpYZ2/wVviZ5+3pjC8qdQ4TKnrak0/E01N1UWoAFU= -github.com/libp2p/go-yamux/v2 v2.2.0/go.mod h1:3So6P6TV6r75R9jiBpiIKgU/66lOarCZjqROGxzPpPQ= +github.com/libp2p/go-yamux/v2 v2.0.0 h1:vSGhAy5u6iHBq11ZDcyHH4Blcf9xlBhT4WQDoOE90LU= +github.com/libp2p/go-yamux/v2 v2.0.0/go.mod h1:NVWira5+sVUIU6tu1JWvaRn1dRnG+cawOJiflsAM+7U= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lucas-clemente/quic-go v0.11.2/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw= From 37c5dd5afc61199768b37dc521695f248bfab32f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Mon, 28 Jun 2021 11:39:01 +0200 Subject: [PATCH 253/257] Miner SimultaneousTransfers config --- node/builder.go | 6 ++++-- node/config/def.go | 5 +++++ node/modules/storageminer.go | 14 ++++++++------ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/node/builder.go b/node/builder.go index c737e85b8..f5294b8a3 100644 --- a/node/builder.go +++ b/node/builder.go @@ -299,7 +299,7 @@ var ChainNode = Options( Override(new(*dtypes.MpoolLocker), new(dtypes.MpoolLocker)), // Shared graphsync (markets, serving chain) - Override(new(dtypes.Graphsync), modules.Graphsync(config.DefaultFullNode().Client.SimultaneousTransfers)), + Override(new(dtypes.Graphsync), modules.Graphsync(config.DefaultSimultaneousTransfers)), // Service: Wallet Override(new(*messagesigner.MessageSigner), messagesigner.NewMessageSigner), @@ -403,7 +403,7 @@ var MinerNode = Options( Override(new(dtypes.StagingMultiDstore), modules.StagingMultiDatastore), Override(new(dtypes.StagingBlockstore), modules.StagingBlockstore), Override(new(dtypes.StagingDAG), modules.StagingDAG), - Override(new(dtypes.StagingGraphsync), modules.StagingGraphsync), + Override(new(dtypes.StagingGraphsync), modules.StagingGraphsync(config.DefaultSimultaneousTransfers)), Override(new(dtypes.ProviderPieceStore), modules.NewProviderPieceStore), Override(new(*sectorblocks.SectorBlocks), sectorblocks.NewSectorBlocks), @@ -606,6 +606,8 @@ func ConfigStorageMiner(c interface{}) Option { })), Override(new(storagemarket.StorageProviderNode), storageadapter.NewProviderNodeAdapter(&cfg.Fees, &cfg.Dealmaking)), + Override(new(dtypes.StagingGraphsync), modules.StagingGraphsync(cfg.Dealmaking.SimultaneousTransfers)), + Override(new(sectorstorage.SealerConfig), cfg.Storage), Override(new(*storage.AddressSelector), modules.AddressSelector(&cfg.Addresses)), Override(new(*storage.Miner), modules.StorageMiner(cfg.Fees)), diff --git a/node/config/def.go b/node/config/def.go index b2ed43ac6..b331b1f49 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -78,6 +78,9 @@ type DealmakingConfig struct { // as a multiplier of the minimum collateral bound MaxProviderCollateralMultiplier uint64 + // The maximum number of parallel online data transfers (storage+retrieval) + SimultaneousTransfers uint64 + Filter string RetrievalFilter string @@ -362,6 +365,8 @@ func DefaultStorageMiner() *StorageMiner { MaxDealsPerPublishMsg: 8, MaxProviderCollateralMultiplier: 2, + SimultaneousTransfers: DefaultSimultaneousTransfers, + RetrievalPricing: &RetrievalPricing{ Strategy: RetrievalPricingDefaultMode, Default: &RetrievalPricingDefault{ diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 5ed092d3c..09b1e2dfd 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -431,13 +431,15 @@ func StagingDAG(mctx helpers.MetricsCtx, lc fx.Lifecycle, ibs dtypes.StagingBloc // StagingGraphsync creates a graphsync instance which reads and writes blocks // to the StagingBlockstore -func StagingGraphsync(mctx helpers.MetricsCtx, lc fx.Lifecycle, ibs dtypes.StagingBlockstore, h host.Host) dtypes.StagingGraphsync { - graphsyncNetwork := gsnet.NewFromLibp2pHost(h) - loader := storeutil.LoaderForBlockstore(ibs) - storer := storeutil.StorerForBlockstore(ibs) - gs := graphsync.New(helpers.LifecycleCtx(mctx, lc), graphsyncNetwork, loader, storer, graphsync.RejectAllRequestsByDefault()) +func StagingGraphsync(parallelTransfers uint64) func(mctx helpers.MetricsCtx, lc fx.Lifecycle, ibs dtypes.StagingBlockstore, h host.Host) dtypes.StagingGraphsync { + return func(mctx helpers.MetricsCtx, lc fx.Lifecycle, ibs dtypes.StagingBlockstore, h host.Host) dtypes.StagingGraphsync { + graphsyncNetwork := gsnet.NewFromLibp2pHost(h) + loader := storeutil.LoaderForBlockstore(ibs) + storer := storeutil.StorerForBlockstore(ibs) + gs := graphsync.New(helpers.LifecycleCtx(mctx, lc), graphsyncNetwork, loader, storer, graphsync.RejectAllRequestsByDefault(), graphsync.MaxInProgressRequests(parallelTransfers)) - return gs + return gs + } } func SetupBlockProducer(lc fx.Lifecycle, ds dtypes.MetadataDS, api v1api.FullNode, epp gen.WinningPoStProver, sf *slashfilter.SlashFilter, j journal.Journal) (*lotusminer.Miner, error) { From e9dd3e86502d4337c905f5e20571d94e815b4aca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Mon, 28 Jun 2021 18:17:22 +0200 Subject: [PATCH 254/257] Test Miner SimultaneousTransfers --- itests/deals_concurrent_test.go | 78 +++++++++++++++++++++++++++++++++ node/builder.go | 8 ++-- 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/itests/deals_concurrent_test.go b/itests/deals_concurrent_test.go index 33a8218dd..44b25c7b3 100644 --- a/itests/deals_concurrent_test.go +++ b/itests/deals_concurrent_test.go @@ -1,12 +1,22 @@ package itests import ( + "context" "fmt" + "sync" "testing" "time" + "github.com/stretchr/testify/require" + + datatransfer "github.com/filecoin-project/go-data-transfer" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/node" + "github.com/filecoin-project/lotus/node/modules" + "github.com/filecoin-project/lotus/node/modules/dtypes" + "github.com/filecoin-project/lotus/node/repo" ) func TestDealCyclesConcurrent(t *testing.T) { @@ -47,3 +57,71 @@ func TestDealCyclesConcurrent(t *testing.T) { t.Run(ns+"-stdretrieval-NoCAR", func(t *testing.T) { runTest(t, n, false, false) }) } } + +func TestSimultenousTransferLimit(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode") + } + + kit.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 + startEpoch := abi.ChainEpoch(2 << 12) + + runTest := func(t *testing.T) { + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ConstructorOpts( + node.ApplyIf(node.IsType(repo.StorageMiner), node.Override(new(dtypes.StagingGraphsync), modules.StagingGraphsync(2))), + )) + ens.InterconnectAll().BeginMining(blockTime) + dh := kit.NewDealHarness(t, client, miner) + + ctx, cancel := context.WithCancel(context.Background()) + + du, err := miner.MarketDataTransferUpdates(ctx) + require.NoError(t, err) + + var maxOngoing int + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + + ongoing := map[datatransfer.TransferID]struct{}{} + + for { + select { + case u := <-du: + t.Logf("%d - %s", u.TransferID, datatransfer.Statuses[u.Status]) + if u.Status == datatransfer.Ongoing { + ongoing[u.TransferID] = struct{}{} + } else { + delete(ongoing, u.TransferID) + } + + if len(ongoing) > maxOngoing { + maxOngoing = len(ongoing) + } + case <-ctx.Done(): + return + } + } + }() + + dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{ + N: 1, // TODO: set to 20 after https://github.com/ipfs/go-graphsync/issues/175 is fixed + FastRetrieval: true, + StartEpoch: startEpoch, + }) + + cancel() + wg.Wait() + + require.LessOrEqual(t, maxOngoing, 2) + } + + runTest(t) +} diff --git a/node/builder.go b/node/builder.go index f5294b8a3..884261a89 100644 --- a/node/builder.go +++ b/node/builder.go @@ -238,7 +238,7 @@ var LibP2P = Options( Override(ConnGaterKey, lp2p.ConnGaterOption), ) -func isType(t repo.RepoType) func(s *Settings) bool { +func IsType(t repo.RepoType) func(s *Settings) bool { return func(s *Settings) bool { return s.nodeType == t } } @@ -468,7 +468,7 @@ func Online() Option { LibP2P, ApplyIf(isFullOrLiteNode, ChainNode), - ApplyIf(isType(repo.StorageMiner), MinerNode), + ApplyIf(IsType(repo.StorageMiner), MinerNode), ) } @@ -680,8 +680,8 @@ func Repo(r repo.Repo) Option { Override(new(*dtypes.APIAlg), modules.APISecret), - ApplyIf(isType(repo.FullNode), ConfigFullNode(c)), - ApplyIf(isType(repo.StorageMiner), ConfigStorageMiner(c)), + ApplyIf(IsType(repo.FullNode), ConfigFullNode(c)), + ApplyIf(IsType(repo.StorageMiner), ConfigStorageMiner(c)), )(settings) } } From 69e0dff56c7f3bff5c1f7a87df1a9318815e639e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Mon, 28 Jun 2021 19:00:37 +0200 Subject: [PATCH 255/257] api: Fix Wrap for nested proxy structs --- api/wrap.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/api/wrap.go b/api/wrap.go index 1ded67132..b26489a42 100644 --- a/api/wrap.go +++ b/api/wrap.go @@ -26,6 +26,27 @@ func Wrap(proxyT, wrapperT, impl interface{}) interface{} { })) } + for i := 0; i < proxy.Elem().NumField(); i++ { + if proxy.Elem().Type().Field(i).Name == "Internal" { + continue + } + + subProxy := proxy.Elem().Field(i).FieldByName("Internal") + for i := 0; i < ri.NumMethod(); i++ { + mt := ri.Type().Method(i) + if subProxy.FieldByName(mt.Name).Kind() == reflect.Invalid { + continue + } + + fn := ri.Method(i) + of := subProxy.FieldByName(mt.Name) + + subProxy.FieldByName(mt.Name).Set(reflect.MakeFunc(of.Type(), func(args []reflect.Value) (results []reflect.Value) { + return fn.Call(args) + })) + } + } + wp := reflect.New(reflect.TypeOf(wrapperT).Elem()) wp.Elem().Field(0).Set(proxy) return wp.Interface() From c1303f1eacedfe4064f1887c0d093ec6cded705c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Mon, 28 Jun 2021 19:00:49 +0200 Subject: [PATCH 256/257] gateway: Add support for Version method --- api/api_gateway.go | 1 + api/proxy_gen.go | 10 ++++++++++ api/v0api/gateway.go | 1 + api/v0api/proxy_gen.go | 10 ++++++++++ build/openrpc/full.json.gz | Bin 23436 -> 23440 bytes build/openrpc/miner.json.gz | Bin 8104 -> 8102 bytes build/openrpc/worker.json.gz | Bin 2514 -> 2513 bytes gateway/node_test.go | 16 ++++++++++++++++ 8 files changed, 38 insertions(+) diff --git a/api/api_gateway.go b/api/api_gateway.go index 130a18c55..0ee66ac17 100644 --- a/api/api_gateway.go +++ b/api/api_gateway.go @@ -58,4 +58,5 @@ type Gateway interface { StateSearchMsg(ctx context.Context, from types.TipSetKey, msg cid.Cid, limit abi.ChainEpoch, allowReplaced bool) (*MsgLookup, error) StateWaitMsg(ctx context.Context, cid cid.Cid, confidence uint64, limit abi.ChainEpoch, allowReplaced bool) (*MsgLookup, error) WalletBalance(context.Context, address.Address) (types.BigInt, error) + Version(context.Context) (APIVersion, error) } diff --git a/api/proxy_gen.go b/api/proxy_gen.go index 6540ae7cd..8b99b6f19 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -531,6 +531,8 @@ type GatewayStruct struct { StateWaitMsg func(p0 context.Context, p1 cid.Cid, p2 uint64, p3 abi.ChainEpoch, p4 bool) (*MsgLookup, error) `` + Version func(p0 context.Context) (APIVersion, error) `` + WalletBalance func(p0 context.Context, p1 address.Address) (types.BigInt, error) `` } } @@ -2679,6 +2681,14 @@ func (s *GatewayStub) StateWaitMsg(p0 context.Context, p1 cid.Cid, p2 uint64, p3 return nil, xerrors.New("method not supported") } +func (s *GatewayStruct) Version(p0 context.Context) (APIVersion, error) { + return s.Internal.Version(p0) +} + +func (s *GatewayStub) Version(p0 context.Context) (APIVersion, error) { + return *new(APIVersion), xerrors.New("method not supported") +} + func (s *GatewayStruct) WalletBalance(p0 context.Context, p1 address.Address) (types.BigInt, error) { return s.Internal.WalletBalance(p0, p1) } diff --git a/api/v0api/gateway.go b/api/v0api/gateway.go index 8a55b4c27..18a5ec7d6 100644 --- a/api/v0api/gateway.go +++ b/api/v0api/gateway.go @@ -63,6 +63,7 @@ type Gateway interface { StateVerifiedClientStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error) StateWaitMsg(ctx context.Context, msg cid.Cid, confidence uint64) (*api.MsgLookup, error) WalletBalance(context.Context, address.Address) (types.BigInt, error) + Version(context.Context) (api.APIVersion, error) } var _ Gateway = *new(FullNode) diff --git a/api/v0api/proxy_gen.go b/api/v0api/proxy_gen.go index fc2fc4186..0f5d2f918 100644 --- a/api/v0api/proxy_gen.go +++ b/api/v0api/proxy_gen.go @@ -449,6 +449,8 @@ type GatewayStruct struct { StateWaitMsg func(p0 context.Context, p1 cid.Cid, p2 uint64) (*api.MsgLookup, error) `` + Version func(p0 context.Context) (api.APIVersion, error) `` + WalletBalance func(p0 context.Context, p1 address.Address) (types.BigInt, error) `` } } @@ -2096,6 +2098,14 @@ func (s *GatewayStub) StateWaitMsg(p0 context.Context, p1 cid.Cid, p2 uint64) (* return nil, xerrors.New("method not supported") } +func (s *GatewayStruct) Version(p0 context.Context) (api.APIVersion, error) { + return s.Internal.Version(p0) +} + +func (s *GatewayStub) Version(p0 context.Context) (api.APIVersion, error) { + return *new(api.APIVersion), xerrors.New("method not supported") +} + func (s *GatewayStruct) WalletBalance(p0 context.Context, p1 address.Address) (types.BigInt, error) { return s.Internal.WalletBalance(p0, p1) } diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 4490109e9a86b60056fa644e8a120a5908e8ed69..8306fab569c17d4acaffb7bcb42fdeedd2b2f339 100644 GIT binary patch delta 21656 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 BHzEK4 delta 21730 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 diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index b7c053d81c3189f4eb1635bef0b1ba56a9b63bf4..b57a1b3191e1a54cc00a88523c8092f5542bf6ee 100644 GIT binary patch delta 7269 zcmV-r9Gc^(Kc+vh;{ku8&QO1TZmnmlQ-Wt`UYWTBnQRtI6e+@BA<>Pn+4=!keVfl> zPx{z}r^L1qW#8@+OCRB-WwrFohu6@x5e5{lJU>Gom|F1jcMRQ+z_O;mxC>a%fA@vB zcdIX-^ElgyJ$hR0(vyxTwD}ta@d%{jIeY+I7d*1x*}Z=X%1wU>u^_;)lB?g6RWv=W zwe>eW3pJ5`-ZL!e9?+Q9UJSNi@UAR>qca zOq}lStEx|#vd_rLO;H5owB{IP~S4QOGJ*k|_#{ddPl{bB!bcpH73 zU-D(kPH&JcJEwmOXxxoma?gOUl;k8RNabdV+E;LnSpT3oCKEb#37NGtr>A|r^#oae zimxDh-q#rVzSdsZ!1)@@znH>XkGcrwD)QB?v97Qt8Trl1T=wT~jB(4eYxEVpRI3>i zql6yJh`4P9I_ztX5=$Bd=V!TEFIj=}V&)L~%FzG)-12`%QKjs2hHm9Ec+H;{AP~dG z*c=H3U3iN6sVQPIqBJLrdaMJ1Wm6up*NA1Rm+Q-_n6=$rUmxiP!2d%vVDSEGE(E4# z5#vs09drO?Qa?^GNBd38n)4gnJF}?YlR!JMoh5|<@{~aK7y%iuf4!Y9kwp>qC-)rB z2uF1`-wc2Ft^hWPjfJrkm5!-^91Kr(B7pFX!gtn3Z5;U~kQX8|H85kvp?NCsGAPbx z22<_agqWfkidPNTpH+>-@W+8!THmBurre;HQKl*FS@Hw3pfZE&*RY&|wNMksMJ*|D z954F*k&4PTG!gI26k*^#e!vDX8GnuRDe&OoAm)FLt^?k(e6*ujJm^TU*oAc5tI-A2JO>&KZA^^~o5nuA ztz9qRH!LE{nm?hQMi+qcDttH(Z1#yv65S=n2+y_eh%VUsZ}{T#_08n3_a~>n-HiWw ze=>jh@Ynd{`WFr2d*l*qGnBgrE@GCw=0U1aqItxIngi&fr2%a6w!UH37<(4$Yf>x)Y#zU?b`Pv2)MH}e(nN6N&4JpyyNT}NP zG=7Bpv8#ObP7;sInk1i5>gB39(Rz7mg>u7D+u^7~Nt7-*MR+4Xl|i*Gp2{ZOFx7UL z>PQkF$e5#=Q7T2VR)%UY>W8q?P~zewX6OdrLzi3I^|Dkp-I~}+O1xpMEwPr0q5cua zMi?uLYE5hA<5c9`mTlkEl7e*owhcGkIL7s#XKL4(!K)-PXgFM)dpDJ!O=XM9o{ z(I@fFWUN^vUT%1?!X*yzfK@O(;M778)8d(QqTVU7EI^^F&}@pr2W)bdItku9a z<1Cz!_?eXmGNFLNf^2vJJbL4p4B(1$VmG%wT>Vp>nPehyxt-th1K@Na)zEGy0lwJs6n z5U!Jhv&lCsygw{FSQk@Cu|oX_tg-uOo=*t}$Wg6251R!_XUyoJD( z1~#(%m(#`6z~q-#+3C+p@^lYmuugRJXpuht>=NNvdPBF<@3r(x@Q{8cj)w>iv}x(r z$c7#Tw!=zvJKdK4;lV+yQ~u8h{O|8X9}RlXEuDP>5F_`bi+{5pdM%x&3y$=EW2^J$ z%o>-{4Q;QI!9{J4t<(8u7>hM4jZxCa&k;MBu&Y`743 zkfU=ktWA+u%7~LSua-_1|LUSxml&Xuc(fN^e=B=Ls_61eawg`XP4US zrhxarE0-F7?ww;q5un7)O8aI7CJ_6{@aeag(DT3?X6EaErVHqfVWMj6>@5oBF#lxV zm7~GJN^0iUqn*13P=> zHrlRiCx5}xQ}k$~Fl?dn9H^CiV6rq8nOwX78v>4HXDG(7w)E2X3XST!zS0DnymA5FRU^sad7<8dIe)Rd&Nv2{^G7 z2#%Li3weuDb+6#8AomA3kQ?WnEAXA?Sr({42rb^N!1)fLSmID87PpHX%3jpgi`lMf zTz?CTMjHH}-+4Y%XI=S`-%eJ|9BHbYwBsus={nOlIY-E+Pkz z%qnsIRJV7xbDpR|mop4sl{%X>fdgSIdY#UX5XP2%x^&&btMk5jXh2lTJXHPC3pfYH zq*3B?yK$x0Tq_=gvqu`BSsq-R@o1*Mo#LnyE&fp?!L6D`YofWUO>7H3;eVDrt8J*5BPuAoz95=_ou#6Rv#U9!hZ@T-Bp727aBop0zgdwXcq!NN|r`a z(3J*114o|p8F6hu#m+H!K#>jCm?wLZmMXie!&e)@IMt)u($q3loI)mQHmQxMwVy7( zPK>uW}B4NwYghk6<%nk(Kn`(T@Wu+2W$t>BjI zN*5uoldAh*C$fF89G%!WdhPdUevT=Jq?HGKewFa)0wBzdrO=KPHZ!KY=5hgYG*g>Ao5 z9vfyfvT-fc?D77Ad%QD|BF#5xyvQu0gc@RjrH+sG7hX8_To8ljefta*H- zxuwE_Ii2NKs;;1=o*7bLPw1W?xa?Y`5Er8yp-LDYk|~DbVKrqOkAFARAoLU+F-yZTF@JL8CC* z2Aykusc&7}Hv>s_rP&qdt`k$NgO+0MBng?j%bX810|soL0?YC|U-TXLvgT4*=ZV7$2vlY7uE`K))Kt%z_Zfy=g)Plf+Vz(Gf zXPy*M0|`povA@%eHa2*p1$IIUG!WcCun)nJOjXM0ss1$-;|I(4R zU-$EpRRex zl~Orlmw%91UD~GCXq!gc?4q{eT;3>}M$zo4qFKAFJK<*#!Ft@Ah)_v&1M75h61{0O zPosHuQ1igX)oC=)zH6ZF+7<2uR9E$OB0{A!kU*kSWO_BKpT_ZN)Xz?;pNQKPxg=bK ztnGN0Nv-q>GG5epv0Z8LVc5z+Dvh3P^sIuO?SHLJB=FCht(Z#|qq3a@f@FhD@<5|Q zcS46Yu-m|{54(Ly@&Ge05T2h*iJLxQSp{+91XRH`2UAx_Zu|_$21*+!ZE!;1#6Ys* z>&xWCSU~d(fq50I2#Lt>Lfnb=3Nb)na{2U}cY;&~WjSoD7S_zA?RXmUiD{mAudxX> zmwy&?HID)cYdcNn$6c}&8_`s+lJ67%P!3t5OXcjDU6l*T0+k=0;4!q1*3v6?#72sW z?5sh~nnG3a!tVh!7FW>AmydJ?yGblppDUOaLXYrS3rBWXnJ{T1x}uIvi-xQzLC z!d8Y2pNjdh8uVBkv}6F8>yD$7&iW7GFmuDspMErf@i@SoB* zD()zw%dWHz8Guhbw7_FE?heV+$$!#uqB(pV7LAyf^DJEe%4QvYw;m4+7jj!tu<$qh z9U#gNht_Q3Ld}H0^0X5YdgSG&0}zB_I%d9A|QYa<3>;!V|{L6T5@+#oDpEPr&6YGxg@ zCktl=5W*taF@!1WoF704i~fKZLTa?uh7qz#|ENep6cyLx*{Oez5i8G^#uSj zC@Tqlei$GU{+vhvhk+&v&_n?u$?C~&tQ8Y?1#NO)#Z(joYBdoY<$ zQMk|Bg>YXZMSCMfYeOv)#v*>sPZcSOk*U=@Tp^q*NU`F~!efq;0W1re8do{OT0>a- zU_10tBd+!ZAj&Tq7I{qGH%y5}T{NFVH%xYdz_lJ58}8>49NGPlD{evssR?P+Dl&1Z zdGZpLjeMnqYZW+6+J7tN*5EnQDwJp-@`MQX0}(Uidh~UR)yCg2SW;pGikR>{hE5xz z31_X5sGm!sWY03_^IdGW+k@W?5#De5U`}?e$yT z-r#n34&2#QR$Ut;;Cyk8C)`1<%CZ3+na}g}Hgx4Ftjf%>#^kOLXY5#PzyU5r#?k_A z7!#<;Pf2`R$$#UbR;`bcoY+X>Zc3u;G9jO-ui&hb1hV?sO2Y=uSHV=bj7@f(kE`H^IAVFE{~KGKKPT@O2L5#c|My@2 z(px%TJ%0(xT_fX8VAmh~C=~zk2ZtC7hML_@m!pBl#Gk?BDHjL;4*Ln;-yVyY&@!qPyzdyZ2 z6JLEsC{xX!tfE!p~RiKDZj!rMF$|{`i#V9&iLi5lHU+E zvy-W%hY~S3u%SnR?WleEwF`{3ryEJ_kw~mm+DT$dQSS8N;z{R$efF0r!oYp}fDOW< zq<Zus2?3=kSyDluSpnX%SioGvL$bIctBENL-29 z*vcWHFWez58c7ZXtn3nYlfuH}Otw%WB8l zdP~2#x(I`1fh|7hwe(By;5RIOp8|ddMZl2kC+nX0UG#wAfL4}s5K+#0>-&-^pFh@H z70*a~b<8nJQCxNBBR^$hrrbL1ntxzm)nlQ70}MnTd2sdQ9v}-$SAyO8n!gm28f;t)NO|w-5M| z{vLYt#IlG{MVo{~@{hw75YKvX{pdihRw9#qg8qU0hM%bX7|_KvPvd;`^?x=?FXcGc z=??yYJAaZNy)l=hyAhWpIXxTee1Fh?cYM?z_78_Od`9|0mNZK#8nym5sygl2EKbfk zW~H3zQY9<&=QAqyE7Pbiz4buM!5K6x;6nc}`wEbsAe7@r_;<}DlfV-wpVA5gKTkr% z$5`!39N-zLje|V@L^U)~@_#2vZ%0srapmhCB$qrCvt+lHXI`U*ctQGlutPa5Kb>tY zy*k1NCYLxhAZ*q{(;ZI+1lCYt+_ z5l4F2j~MXsCksUN*KHE@#rQVVkY~`a6v1Zg5d*dC6wU0NWV@)Wtcpl0Nwd_{=DSN* zY|&9~Bl}6WbhYfBZ+{W(OD`Vs8M|uUpDgJwC3XT4Rprg6x&?s?69CsqT7fJr8fqJyl_{ML+|Q|!c+@1`~BEjh`9-`{3PL(m-87-0Gh$w z`T+D5bcjoTfxx1bGN9vh)E3tKU!sbru7+$YO>Jc#%r3d*xql$DGODV_9#BAqms?MU zZcEQNR-hob%eH6-eQi>JE2aQf-n`mi>_xvXy{k(AmY*J98e3U?mEdL~A79b+@oE3` zan04e49!l5eS*3Da9sz|o6CeOU%QW0y;q;arXAQAV zk4PKXRfLq?DS!8cxOdP!B^WaSCK(f8YQd?;tkQh z^P--1H9oCzuWz$iefumdyU|TB!>?po?ugB(x_tR$xkP6B<|bV06VMpe%v0EjCN{Q~ zlGDk|rIq|TDmos{hUW1x=p7sm58oXOy2Imx;cNyDj(=xx)|m}EhY-Rrks&xoGN#v4 zi<^F8fPQq?8Fr_jb8y(5ng@e-;N8L0=nN0Qtn+TxHF}5eof#cnmP7Fxcy~;AA+X-w zciu7<=f}9cWm^1diN63u^-+fhQCzBz^j@deIp}r|y1nbp@u=4ub&mejTlz1=qyDfj zh)4B}EPuO3jbuc5#4r}1CMwi>u$T=V&IQ5lVN16Wj{RCqwut6uXN8(UB~nZ3+WII=qRDV!&r=h!9CR6ur1KYyQN;=(KFk)>S&EZP59w6u+>unfZVI4ArI@SLZdW(ZuT_L&B#{Wk5dpCDrp0 z6TGGrLX5MTVgykA&y)Hz&(B@@X1kN$z>fh3}lfg&x<>1r#u&y?2`wWE4U(2{6|k*$m^ z;g~qx-B(qa`Zh6&>2FwlE%}7N@WD%a3f&Z$-}z$=c^c5dBC*fz5Bl$pkNU&@;qW&4 zIKSk}mYwb(TXug=7tpvHyX2k$VJXQ;P>{;a6t%D59I^gEb4(_5>=H6-X--f3dg}?Y z{uEz9_Pnn#^nIT}dZ|`3 zCPoQ8m=ST?3Ut`l93_@C3eL}RwO+CU=f%t+^p&Ci`?-JRkD^N1=M3G-XYiUoEkGcK zjj=fr3cBzV^;1*CWJGCB81+~O0?Vd6Vy_X)R4><;RWWP3y}mxu4S@fLYQW(A)m#Wn z%_7E~&N}D-%A|grV2<{im^J4&xOZkzuP1?aVmnI;1LP@z>@fl|VE=kMT_TGj>`(4F zo)M1fY`%XP@Ld6H5*rI+DJmUP0XZ0+>_ho$O&~8sW@=!@ibL~M;AK#p z&kUy8xd|~vGZe2Hus^FBiQ$g}v$VcRvrM@`FQZIT+Oy;bW$b|0a*~~&1UO|8AB6trh{<4&)=9A5iQ8Sx7&l*yqy^&D0 z?`ix9_hVQ2Y9NV6W=)dMDD`qxoM^o~wL-b!sO@mnJ4uu-IYoFQK$StYE}qIJ-7wX5 znCegxAIO-ano%l6vsQ*`FzSb}(~-o*NzBjAN)qe=+I9}(DT_BH^2Mty`Tfcn0y#($Zq^y{dp7BX- zM4!Yvld)!zc)8)l3YR#<16INGfKv-WOp9mIiF&8RvH*pyLbE9fAF%nsA;v;&apeL` zp_Q>%-O|Sh8nDzlAFs(1rc~E=?wP$({8NLPRfcOqflCVvm@ipl7a4F2-G8uVu~q}y zjI(e?;%8PO$bWTPT-Lf8qWw(+xZ8LLx%okon#s z#+_9O!LmrNH?^MmPc9U zaf#f~d>>1J{>Brue_RgV&st{C54-CEOle-IC&aX#Zv7m*LcQVN{o&u#166KY)| z%pqJS2WOLSSa^R}xW6u@l46DW5m;qSvKsp~_`A2vJ6Pv8#u%aQ<shmE`Fj$Y7o5=+Pp5{MjYKvGj&+r{8Pom*64&OdJmp9B9+h zuaONs3T%g!=ytj-{lkNUSf~7-6ZqfXi9QKfbSdZ@#8KcKp%rg8)C{w;2u~EfKw08vEf4C zL5|MFur@_rDI-qSyjnV4{Hu#%U1ESr;?aHx^RevMKOrN2mOKPj=`8*%LbavxONcl5 z{7pW8lh40pB#|A0C}i{kc{Co7DYwuep(RWFj2Ajx0E~ZOZqmQJg$-qaX7n*5qMTi7 zvzr3m1Fu|a{JD3I5k-I!H!JO%6_`NmC&Q=TUP8|UbC{X0|CuhJJBEp>v9q@*n8W;& zeOHbK3oEIe3%s%gf}e0>GMA{h9dZ_*<&5vSAoJ24yCCySntq~unGR?9VyovZ{|@Zz zncHZ)vYmefOHa|Gjl!^n%5$Jr@`1_HSY&eT{%;64mYty(!`jkI-zzk#@A^s;Z1#RN zd%rSjHhaICyfqMq^vLhNJR6WNAVX#Fj2Y^c*1EF(*@~vHl!6KJ(V9H zo>6kM`r^biKQ6K9og%Y1a-Q4+WPzy#KN2^LzGi>B0g-}9Q4z0R*fKy%_zH;Nvd#jq zRPED`z@s_C%jfV4deFTWMr^U(taH*XAq6I&U<~LYyFhrXe5PiV)@w|a##Gr2QzhWU zQXn{9PA%juO4YrBvx3|ogMR1vP@Q${F@P+|~?rMk}49D_%TfO0o={D7>5YG!}jxqky~5nqRcPst^OA|A&D#B-{sD?iU> z9;8JgHEO+3S)V971=&Vml_V5jxFjoC(BdCnaK^5{_mZA%)fJRtMh&6Bngrf`Zt@|->anE`M@f!^!0xo zx-oPSF}V|~kMvHb(>>t-U3WSo{@YO+Fxh{tqA}%0iaz704Z4- zNkLZ{{0tm<(r3iA0TnyP-~mN8Tw|W>Nm{Dxt`1*q1mjeXZc9_kRB;NKsM(}8qSk)8 z{6g7H8)D?IpvUB;rj4%|)V8+sHM@Up)vd1?wKYH~v>obclxVJa-|T~J_Q5v$V7G!> zvMXJLyiTg_gPq9s!E$tBiB4X;e}(*1u=Nuw~s*uO^NcWKC6wF zTPiG=(^-C{>Iz!wnIQ%Cgzg1`_dBv{l|o#Ma)c^jcu1xgiig#daXf$CP=n+@Gm^_W zS5`{N4~!vEJ&+zt`q*Z&Mw5rPuM6G%j_m3$fsz-GZOkZw=J58qIC3%?yn+x*x zVA|(t~}4#|4eT zXd85{`K7*fao-Fi*_CEjoV!j;u?|{_xsxPh?k;ma&^tCt1 zavy{stX*77wV$8l6*cmOxOdPMBx=jaTK!fc~vW?9O14=0>$Osx42oH6Yu7tPirXJ3M{lLKs-+pDLbp7NIS{ z?rNJ2{x<#)h=({EK-Og6*Cb)mxC;$}&AiK3W08tA94~pGl zFr9f)L=7Y;ZO8sjH`>_XjTYDmEzm%41HnE757(~mO(3vNiESgA1{!tvjZrBCz5b;m zZNKj4C9P(aC;QRZb9tj^8b!0Gie~My?u4H~1nY5cB0?qA4Xo42N%W@C zJdNhrLCpgjSEtcHd#-`HowY0638=2>?L>r1X&`|_r^xhbR6mX5)2N@FR6h~7D{@J= z2wB_lE|Xg66=b}q@nXBu;={0&gH#$l+vr&ZJ==d>n@HfFH(N26EJkHJ2?WUoo8*B; zhwg+9ZD6;7T_1LPlH>tqULZU_nG!dB!mGD;f1&p?G<8xz~u7jIqw9i49arYSS_rXOWW}@kC3UM1ft0H7SQM3>6hGrKAmk_9S1Ji%jVAFZWV?ud;P z71>#XoHd22;)UM>YAmjxmoFdb24upqQc^=Uzt5LK>rwL{Ha&g_AA7Ia!dMfx1bV!t zr-Aw0g>yE6{HQijwEYbWSIN^DN@{2pRR(_^{vXOn)5_<$$ww&TG{iMUODu8}P=vw| z&$`d|BS023#T@XW4xnrb$BFvwH|xyf*y*~L-n@9^DAszt21e3|*!wHk`(4=;>~R_M z?}V)k8$KNwa1=R?F`H~YT=-=**B5l4S;(UrnQ4yy#)pjnDJapVkPE(sFu!FK!v}vF z0Z0H5HNe8!gC0|_H!J>IJKu57#7TDfvzWL~kbZb@pg|da0_$w)0-rU?3YLUowun(= z!*)-Xu#+kdoEVm$( zMMxHQh>QR+Be`M9Owt<vSSES);T|b5ElIbF@)4;tqmh&mHttYgvRy%q0%qAz;#7Hs(b&dr0NR* zWKdQT`us3JB>Xv%01g996rhO$#3W00W38CDD`=DZDyE_!P^*a;l3ceW6&2*;cMpX> zYXeY7v(+kp>oH>2__)V~u_L66CNayLVJf-K6$t zoP?rGQWLJZl9t z?St*mM~%4J7l0_gXjtSidEYQ48g2?E!8Y;3rnOK@cOL$0_96{IGlQLD(r zspiQ`ST^#N60TL?G--dYm|KJAOsi0$fyfgg*bhX^kn7RcEmj+U!(d5?4Jcy5_ZT{D zh$fu1MxuT$iIQDQxx$N8qy9-k*)hL2iWd`0C@RU6Jis3XCUOJ(F+tl>i2sf3isU!B zHze=jEy(GBW+0H^y~rix^DqeE)ynL@XPaf2Me>>cYq!^L zb$f%`-8pb)S6Ov!kbv{WIi7F_xhl&BbYwoy*W1vQr?4tB#~PEnLY%Ql9T6d6kk zxM577CO;+dZ6$w?i(0ilN^)W&iMuI@vde^groMu+N)pKGXDbaGI9~-*-7+=>7BsKm zOi|W+vXxW&vbZw4W2r%q7E{@-d_3%QIB! z@k;$z1_l?$EnQe4BYo`hEEm0{pAy?X(?`tc5I?ShAL5ARk^XONb^e^ZUl{n;1^nNC z{Y!7@eD!}MD0hvFJAqw)@S{-t#~&PGEEsBbJ6(=i;_R(;Wx_l-o~#*lDe>S6QkSFV zpx4r`NstWhkM9NW=m*zbJ)WhdUyzW3IOvam+_0se7!=)$bxqNcmj2 z7EOHh8KF!yf3k{JjgMoF>P+b=pB*=wq#I6~%CN^dNTvJ=(-a+mkn1xNpE=`~vr2wL z*vw9*mL5vP;J}6+1-7I1<<~AS)}C%8wMQbcQfViNEk(K0hl?kj2lm-trU(P~@dGvp zkCK0m^eOP*;ULz{sCEchYUW*tP8+ZWeH1Q|QZF!Cg}RUJTS}IXF@~?!GPyBOPJ8yr zG0hi#c3^lsXRD`TsIqV7zU;cNz=HNosVerqAR+g)W09a+gsG}+ACi?D_5Lpn;Td6O zeO-atj9zDY;ps9erfFb>j=H^0j#FKORPBEoL(48B^4aFbF{`Dj_!J8HuZcLWLgGr) z##Rmqec=vi(MWPAU}cxEn-mr%XR?J7Avq_ec|i=29up-=OVW@Z7|A9rPoh$^ws2CU zE20PryqNIE8-^^;kbT+J3;_dB3XGD;RB;OHPA`RC(3%KRIz4UXEh)0vA9*ZNkyU?# zcGiP`(4ZgrGT}pQ5z6$pD4H((B-5kAS@(E2oF2egzjH7!kKnIkaM(MV4W{57 z80szk=ISC0mIb!>px4qb!Gqth_#grgrhNWb zZ&f@a@zpWMC`EDAosayKjhS-mv}=EYfmM%%1`aR~edNK_lY4+HFl9YIZs{K<$=_Gd zgYG@+%P}8J{^yOy9~2D&Mue4OfMF+9N+Xr|K0IXf7m}9*6zI{trc0Hq(4Wt!*sn~ZzVy}uF$ZVRuz(Bw!|W?Seu7YrAK~9MlS~3npnOUz4E#I^ z6(3`@D{+8lq&5!n{1esCM9F`jD7_s)4aSwPdyri6P|T9uTAq208sY`%>%k7?wET3o zwe;!;ADCR?)PS@hm6~Bz5nbcWZ`F2gV6}nOt-@-9X$;fWuZ4U>*sP9e5taP4$JkxK z5@WHS4j}~gV=QRkudEdf9VEKCRnxM87L^-wUk0?SMaUN!Vc8|aQ!anxYoaO{MPFKM zW$Yr+->f=KAf=KXl-^{shPeu8KvqB3o`0>qDjo9n8`#QKlDlH5XJLavq_tTh-kWId zOGX^&Wj|uT%bzR|)nB(s)EDF1P(z+U!%_sBwMPupvQsp(carU*va%{7tt8D-Q=9KD zU9m++y^ZWA-O|;vd%k}~v@gAQ$Y<=Td4IB`zm(VsL{ybGpXwF_E=&MiCus$;1XV>$ ze`^8?-a3FT^c?Y#?JL?77M0!@6!OAdMGn2ICkj(7(CqhPYa!+)yz-NTS6$9(zCm6<%&V z8M-Y!<5+=$;4a&u9rU$H0j`(=TzT_qgRvL=zVxmx0a$)|d}(ZD^;LqKiF|xT*T<** z)5kSe_cAm)9rg+4_QZ9S-drYR`PzM~>b?3THtoQ^ni8TK42`J+xW7P&hj$^ zeni^9jw0khcBg;b7vkPQ_mp7F1fUE+CMZSTj#yCz{yU zT1rkQGnZEK>!|2>I2)SB$Dns`I6QoJFz61C4~DZDI5>Zv!C7ZE>>NS}!$gMQ9Lbno zPc3fxi2?f2VQ1K#g3iHVcWNFC-hp=qQ=>CH0JF}!S=ZSezf@_Lgb!t0n#d5YT6D+_ z>gVR|=EsH1{G`u_YXipCCZO#2Jcv=28x1Hy;XYoKQfks2g5iMc=G~ zGOwtnZ-O?qpl~vKnWJ!Ib?Q?%Pdd-BOQ5NM?3RCiKF7p`SI{F%*N91J?8%dU3GMOX z(L)BX^il6-PIc`YjWl4;W0XNKh`Jed9s%5@D{Af}h}l|NP`EMX1N` z^(kZjt&cj-IjX2gQlC##>5$r>^I0i=VP`Y*|NI%MO*O90YgVI)*F}bePo2tueDX`G z=OZR~O(}#JXEntNf@SN{l5wU~>_EuV+gys-RTabU2)%v&{{a91|NlH1Fi-o80RTLm B76SkP diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index 411964f6549af607d0179d0709dbd490dbd836a8..d744c508d9c64441ff95016ba9f71082f28a5b20 100644 GIT binary patch delta 1727 zcmV;w20;1J6VVfp8Ujj2u^eXsfA^Jv$Aa_ep9>f8;N9xhz{IkAwun6z+y$Yi9%o8y z2?lm!P0`3D2tp<{g2)0!46J+xqTj0xi@W=-)-8PU15AaJiz+d|_h^LMm4OBPDtr`1WFR*u9LIy2_k_N{@bf>0<@D@e{2>=mLCQW3PZ$nO!& ztv=hRjR-*&>72smTZZWJ_gThw`3iHJCaENISa4C)Y(u!1n zNb0ZzVpBH5F{Y#ve#?F^TuP0L@#XLbsLWIi<7yapYA~+d&++1v5u5&m@GMHY^8|6( z{7=+=McAy8cix^oa?`bYJ_;2&2KT%rWz}*uCARjNXzhov=h`l%7~QrbiH$6OGDTu< z3lan;Q=5|RMYoe}NNlW&vw5MU!5&iqN2?Aww;RWTn;_*iNErxMDZNM#XDf*3 zQLFLw>ux-i8Z28q0zz%11z{FYP`+KL+hw?$Q_5OwDdf$A2G^ucrOr2Vqf{8 z2?HtDPw4nMLrg^UnA$c@z%o7;t1- zd{6Lkom@qH(Q8fDgKn9Wg9iovOv5mx7jBAgF_}Um3z(u)8kgivM(=+RBE)v?Vu#Vv zhPR;ATBjHWOWf23lS~(#$xmVe={`zp-d}V{x34B$c@K{JgFpS{dJFC`xIZS&6c*EN zZ%Vt~qVIXo>pb)Vh`rD8N}9^elsJbLYyYQJ=1eyQKeDSD|^*1Vs3=3Rx% zAL%`8RZtsf;wiMQaAsjmO{{ELORPnu|FW*SWck&UEce^Fr7No18{;;Fd4OxexxJ&R zD>2A3m0jy@$RXlhA~lCdo_~o%5L|0Ua@|0mxk74A^j}mW2})5myI~GlAVuy05j2HI zB*Em&l1Oo+jVPHB1^R~~@*ZhHwLE)|b!30f>+0%4t&iP)C-0tCfWY2cmjC2WS&>gt z=LZ^sve_=lPmPr?{(B1+{A5P4_lLz_F0jw{b-g}O%*ks1N#y-yV1KYmieZd0R|J9B z+KNekn&A2LXT>o&=HW###b5E)+BF|qP16sbnw?rSe?>IAImsA(CV{`LAejN-UIlYK zWG$DkLN0qb!B})AWuLxY(`5%_4spMy5B^sA)XUYbFfBfMws1y0(D{wmYIOkx4;KG& z__c%a)GqDrUnC_H$s4+soJiaUOTXgJB}#9~IFXTP{I)ZNapK8^i<6QFJ^|a4(Flin z*w4vKhPqr52ZmJHan0I1JdeXRV;tmlQh5v8v>(Xlb=%#}yUJegSj8tNoA`}T^)HWt zDk(r~x=ayzHZv0^&6b>X4>Ym4c`uR26t$6>g8)PXAQs$fkw|!q`FCi3QP^Hxs^o1S VuU5CK{{{d6|Nk2USn`T;005q3LCF9B delta 1727 zcmV;w20;1I6Velq8UmU`u^eXsf8EN!W5N0K&xH$k@OE`;U}9N5Tf`mlQxw0j9#qMU@!fdo;rB%D@8t@~8j{ z50vmgABHQyBucmVi(46+)9R#7D@S4rof-3N`_{lzK`0cd6(r{h_6kvnQV7~ws-+D@F|Eqi6wN>(j&5})>Dfw; z9rCtqqB1$%>QKZ61UBPYJD`;rw|)~6WrZiai2O|?3tddwEq{@H_XXwp@ zb94318E&y+{1>g@*=Dg(b`!wZn;>J7% z?RtI<1AL&kxi}_j9ya%4&F@Wr+dpv&D!|2v-C|g=28FK>6y}NN;Skdv;8Gg(r4^}v zkknxb#HMV9V@ydU{FeP-xRe?fP?@P3#?>(H#9-V-tDob=DI+%h3E^3kbms}; zviYB={fe+zCGWgFd*r5T_k0v8bPVo!OUkO{YD#SFm+4f3Dz>+NVx{87RLuO;o~`z5 zr{>w(y=>1mVpCqypJ|fp)Wr6E0NrzcmwyUJJ4l`)i=Sr{H_@qx^8S-cwVp~tXY@d9 zPutJYjBx>1Ah{>ZC_8H4Si=&30%ts5NNN%r4|*L#H(Wmwcm3lEB!~wsN4jA+{wjn2 zeenOM4e+X@#%~y2MgkvV{#6Fg>poG6X0^3~5;F(f7tz`eVb8T)N-?@^M-m%{j1M!BD~R|TA$)1N9B!=0VKbi99E5AYy5g@ zPN$o5gKbRIT$Xe5Mt`tnv z7d1=SR|CaO;ku99B(~3c*41x+KxgwpNrOG60*+Q4a&9+{1vf#;YmhP!u2OoDAkJ10 z&!bl3>(||QDm7TPdIW^pNDIO&?jSuj;#2hKvxa0qZ=e}45c(Lxj`jjQ&VhyfZkQX) zhomAxl`B5W(!2FKQ+pV{Qs5p-A;))+qNm-i>@;+tmy@)=0vm{guS^1yO9dZ)7dd(R zEAWdq%K9`-l;_IGgobv1ba@uIr+n`e4x|aQ9a0#IpeFjF<{F^w$wS4tEU2qF8d^^2 z4lBPUr8UdaefI2hQD)c}%So+)qHVu$)#&Hsp>pCT6&(>j~Hb+ycY;1v9J8l zgn^XnCv<$BA*P}akm}J3pYQR1pCAZsmbn*OQw*skE8xfEQnpJo^*l9>JPL<(3^+0^ zz9;y&POc)p=(VQnLAOlG!Gi*SreT=U3pd5Lm`tIO1x(Q?jZ5+-qxU}u5n?-cvBPL- z!&}g5ty2txC2neiNv4a=wKbRVTP?=QNf+gFpWya&hq!Jqzey#;qM+#eHX3X5sC zH>F)~(f2&)bsl;F#NOw4B~9gK@^a@H--a@j`$R!?yjRD2FB#PSo|BmdV1G8Jawg#s ztHhfd;%Xu1#vNCWIukt9f#*>v(lNO6Oj&z*9=-NUwO=|lzw|Dr=%rd&^M2}?cNH>! zr1!8@L2aOkr_j2>nT0hqv9e_?u@;s7%ev~4$>_KvEq z#30X9cCEW1hlqQL)EpvtCVvt^aIG21bpw6o3aL5Ke^H4fC`H-qhB;(`6uAdP&=ek# z1d}sMBE^w5qGU!C=pTy6d!z-`^6Wjmu zD<=JEg6Gqp702Y5hZn^Zf5l&G*L-L-O+S2Uc52c5718YEBxCfM1pc;yWCnzL70mUJ zwOqamx$NcyW6_zEefoM$mmQEf#QmN=_*>~yFIT(5wD{=R!Ws2I=Qm!f)ddthSp3W3 z*AB*0yR^H1k(5j%>l<2fB5@xq{fa-AD7`7;L`I_V+s+imX(t!@y_1m$J^|g6&$ba{x0Su#v5HSlHt`#w>R%oO zRZ@V~beSUbY-T1-nk_l&9%y27^Ijs4DQY7%2LXr(KrFb|B9ZVI^Y767qOiTXR7t;& VSF79Ae**vj|Npk@_f3j&004CQQ{Mmp diff --git a/gateway/node_test.go b/gateway/node_test.go index 0d33daa35..68711cca6 100644 --- a/gateway/node_test.go +++ b/gateway/node_test.go @@ -233,3 +233,19 @@ func (m *mockGatewayDepsAPI) StateWaitMsgLimited(ctx context.Context, msg cid.Ci func (m *mockGatewayDepsAPI) StateReadState(ctx context.Context, act address.Address, ts types.TipSetKey) (*api.ActorState, error) { panic("implement me") } + +func (m *mockGatewayDepsAPI) Version(context.Context) (api.APIVersion, error) { + return api.APIVersion{ + APIVersion: api.FullAPIVersion1, + }, nil +} + +func TestGatewayVersion(t *testing.T) { + ctx := context.Background() + mock := &mockGatewayDepsAPI{} + a := NewNode(mock, DefaultLookbackCap, DefaultStateWaitLookbackLimit) + + v, err := a.Version(ctx) + require.NoError(t, err) + require.Equal(t, api.FullAPIVersion1, v.APIVersion) +} From 6bbc5be1bdeb99f6984f860e565e94a9845db383 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Mon, 28 Jun 2021 22:16:03 -0400 Subject: [PATCH 257/257] Lotus version 1.11.0 --- CHANGELOG.md | 220 +++++++++++++++++++++++++++++++++++ build/openrpc/full.json.gz | Bin 23440 -> 23439 bytes build/openrpc/miner.json.gz | Bin 8102 -> 8102 bytes build/openrpc/worker.json.gz | Bin 2513 -> 2513 bytes build/version.go | 2 +- 5 files changed, 221 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11f7d300b..0e4ded59a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,225 @@ # Lotus changelog +# 1.11.0-rc1 / 2021-06-28 + +This is the first release candidate for the optional Lotus v1.11.0 release that introduces several months of bugfixes and feature development. + +- github.com/filecoin-project/lotus: + - Lotus version 1.11.0 + - gateway: Add support for Version method ([filecoin-project/lotus#6618](https://github.com/filecoin-project/lotus/pull/6618)) + - Miner SimultaneousTransfers config ([filecoin-project/lotus#6612](https://github.com/filecoin-project/lotus/pull/6612)) + - revamped integration test kit (aka. Operation Sparks Joy) ([filecoin-project/lotus#6329](https://github.com/filecoin-project/lotus/pull/6329)) + - downgrade libp2p/go-libp2p-yamux to v0.5.1. ([filecoin-project/lotus#6605](https://github.com/filecoin-project/lotus/pull/6605)) + - Fix wallet error messages ([filecoin-project/lotus#6594](https://github.com/filecoin-project/lotus/pull/6594)) + - Fix CircleCI gen ([filecoin-project/lotus#6589](https://github.com/filecoin-project/lotus/pull/6589)) + - Make query-ask CLI more graceful ([filecoin-project/lotus#6590](https://github.com/filecoin-project/lotus/pull/6590)) + - ([filecoin-project/lotus#6406](https://github.com/filecoin-project/lotus/pull/6406)) + - move with changed name ([filecoin-project/lotus#6587](https://github.com/filecoin-project/lotus/pull/6587)) + - scale up sector expiration to avoid sector expire in batch-pre-commit waitting ([filecoin-project/lotus#6566](https://github.com/filecoin-project/lotus/pull/6566)) + - Merge release branch into master ([filecoin-project/lotus#6583](https://github.com/filecoin-project/lotus/pull/6583)) + - ([filecoin-project/lotus#6582](https://github.com/filecoin-project/lotus/pull/6582)) + - fix circleci being out of sync. ([filecoin-project/lotus#6573](https://github.com/filecoin-project/lotus/pull/6573)) + - dynamic circleci config for streamlining test execution ([filecoin-project/lotus#6561](https://github.com/filecoin-project/lotus/pull/6561)) + - Merge 1.10 branch into master ([filecoin-project/lotus#6571](https://github.com/filecoin-project/lotus/pull/6571)) + - Fix helptext ([filecoin-project/lotus#6560](https://github.com/filecoin-project/lotus/pull/6560)) + - extern/storage: add ability to ignore worker resources when scheduling. ([filecoin-project/lotus#6542](https://github.com/filecoin-project/lotus/pull/6542)) + - Merge 1.10 branch into master ([filecoin-project/lotus#6540](https://github.com/filecoin-project/lotus/pull/6540)) + - Initial draft: basic build instructions on Readme ([filecoin-project/lotus#6498](https://github.com/filecoin-project/lotus/pull/6498)) + - fix commit finalize failed ([filecoin-project/lotus#6521](https://github.com/filecoin-project/lotus/pull/6521)) + - Dynamic Retrieval pricing ([filecoin-project/lotus#6175](https://github.com/filecoin-project/lotus/pull/6175)) + - Fix soup ([filecoin-project/lotus#6501](https://github.com/filecoin-project/lotus/pull/6501)) + - fix: pick the correct partitions-per-post limit ([filecoin-project/lotus#6502](https://github.com/filecoin-project/lotus/pull/6502)) + - Fix the build + - Adjust various CLI display ratios to arbitrary precision ([filecoin-project/lotus#6309](https://github.com/filecoin-project/lotus/pull/6309)) + - Add utils to use multisigs as miner owners ([filecoin-project/lotus#6490](https://github.com/filecoin-project/lotus/pull/6490)) + - Test multicore SDR support ([filecoin-project/lotus#6479](https://github.com/filecoin-project/lotus/pull/6479)) + - sealing: Fix restartSectors race ([filecoin-project/lotus#6495](https://github.com/filecoin-project/lotus/pull/6495)) + - Merge 1.10 into master ([filecoin-project/lotus#6487](https://github.com/filecoin-project/lotus/pull/6487)) + - Unit tests for sector batchers ([filecoin-project/lotus#6432](https://github.com/filecoin-project/lotus/pull/6432)) + - Merge 1.10 changes into master ([filecoin-project/lotus#6466](https://github.com/filecoin-project/lotus/pull/6466)) + - Update chain list with correct help instructions ([filecoin-project/lotus#6465](https://github.com/filecoin-project/lotus/pull/6465)) + - clean failed sectors in batch commit ([filecoin-project/lotus#6451](https://github.com/filecoin-project/lotus/pull/6451)) + - itests/kit: add guard to ensure imports from tests only. ([filecoin-project/lotus#6445](https://github.com/filecoin-project/lotus/pull/6445)) + - consolidate integration tests into `itests` package; create test kit; cleanup ([filecoin-project/lotus#6311](https://github.com/filecoin-project/lotus/pull/6311)) + - Remove rc changelog, compile the new changelog for final release only ([filecoin-project/lotus#6444](https://github.com/filecoin-project/lotus/pull/6444)) + - updated configuration comments for docs ([filecoin-project/lotus#6440](https://github.com/filecoin-project/lotus/pull/6440)) + - Set ntwk v13 HyperDrive Calibration upgrade epoch ([filecoin-project/lotus#6441](https://github.com/filecoin-project/lotus/pull/6441)) + - Merge release/v1.10.10 into master ([filecoin-project/lotus#6439](https://github.com/filecoin-project/lotus/pull/6439)) + - implement a command to export a car ([filecoin-project/lotus#6405](https://github.com/filecoin-project/lotus/pull/6405)) + - Merge v1.10 release branch into master ([filecoin-project/lotus#6435](https://github.com/filecoin-project/lotus/pull/6435)) + - Fee config for sector batching ([filecoin-project/lotus#6420](https://github.com/filecoin-project/lotus/pull/6420)) + - Fix: correct the change of message size limit ([filecoin-project/lotus#6430](https://github.com/filecoin-project/lotus/pull/6430)) + - UX: lotus state power CLI should fail if called with a not-miner ([filecoin-project/lotus#6425](https://github.com/filecoin-project/lotus/pull/6425)) + - network reset friday + - Increase message size limit ([filecoin-project/lotus#6419](https://github.com/filecoin-project/lotus/pull/6419)) + - polish(stmgr): define ExecMonitor for message application callback ([filecoin-project/lotus#6389](https://github.com/filecoin-project/lotus/pull/6389)) + - upgrade testground action version ([filecoin-project/lotus#6403](https://github.com/filecoin-project/lotus/pull/6403)) + - Fix logging of stringified CIDs double-encoded in hex ([filecoin-project/lotus#6413](https://github.com/filecoin-project/lotus/pull/6413)) + - Update libp2p to 0.14.2 ([filecoin-project/lotus#6404](https://github.com/filecoin-project/lotus/pull/6404)) + - Bypass task scheduler for reading unsealed pieces ([filecoin-project/lotus#6280](https://github.com/filecoin-project/lotus/pull/6280)) + - testplans: lotus-soup: use default WPoStChallengeWindow ([filecoin-project/lotus#6400](https://github.com/filecoin-project/lotus/pull/6400)) + - build snapcraft ([filecoin-project/lotus#6388](https://github.com/filecoin-project/lotus/pull/6388)) + - Fix the doc errors of the sealing config funcs ([filecoin-project/lotus#6399](https://github.com/filecoin-project/lotus/pull/6399)) + - Integration tests for offline deals ([filecoin-project/lotus#6081](https://github.com/filecoin-project/lotus/pull/6081)) + - Fix success handling in Retreival ([filecoin-project/lotus#5921](https://github.com/filecoin-project/lotus/pull/5921)) + - Fix some flaky tests ([filecoin-project/lotus#6397](https://github.com/filecoin-project/lotus/pull/6397)) + - build appimage in CI ([filecoin-project/lotus#6384](https://github.com/filecoin-project/lotus/pull/6384)) + - Add doc on gas balancing ([filecoin-project/lotus#6392](https://github.com/filecoin-project/lotus/pull/6392)) + - Add a command to list retrievals ([filecoin-project/lotus#6337](https://github.com/filecoin-project/lotus/pull/6337)) + - Add interop network ([filecoin-project/lotus#6387](https://github.com/filecoin-project/lotus/pull/6387)) + - Network version 13 (v1.11) ([filecoin-project/lotus#6342](https://github.com/filecoin-project/lotus/pull/6342)) + - Generate AppImage ([filecoin-project/lotus#6208](https://github.com/filecoin-project/lotus/pull/6208)) + - lotus-gateway: add check command ([filecoin-project/lotus#6373](https://github.com/filecoin-project/lotus/pull/6373)) + - Add a warning to the release issue template ([filecoin-project/lotus#6374](https://github.com/filecoin-project/lotus/pull/6374)) + - update to markets-v1.4.0 ([filecoin-project/lotus#6369](https://github.com/filecoin-project/lotus/pull/6369)) + - Add test for AddVerifiedClient ([filecoin-project/lotus#6317](https://github.com/filecoin-project/lotus/pull/6317)) + - Typo fix in error message: "pubusb" -> "pubsub" ([filecoin-project/lotus#6365](https://github.com/filecoin-project/lotus/pull/6365)) + - Improve the cli state call command ([filecoin-project/lotus#6226](https://github.com/filecoin-project/lotus/pull/6226)) + - Upscale mineOne message to a WARN on unexpected ineligibility ([filecoin-project/lotus#6358](https://github.com/filecoin-project/lotus/pull/6358)) + - storagefsm: Fix batch deal packing behavior ([filecoin-project/lotus#6041](https://github.com/filecoin-project/lotus/pull/6041)) + - Remove few useless variable assignments ([filecoin-project/lotus#6359](https://github.com/filecoin-project/lotus/pull/6359)) + - lotus-wallet: JWT Support ([filecoin-project/lotus#6360](https://github.com/filecoin-project/lotus/pull/6360)) + - Reduce noise from 'peer has different genesis' messages ([filecoin-project/lotus#6357](https://github.com/filecoin-project/lotus/pull/6357)) + - events: Fix handling of multiple matched events per epoch ([filecoin-project/lotus#6355](https://github.com/filecoin-project/lotus/pull/6355)) + - Update RELEASE_ISSUE_TEMPLATE.md ([filecoin-project/lotus#6236](https://github.com/filecoin-project/lotus/pull/6236)) + - Get current seal proof when necessary ([filecoin-project/lotus#6339](https://github.com/filecoin-project/lotus/pull/6339)) + - Allow starting networks from arbitrary actor versions ([filecoin-project/lotus#6333](https://github.com/filecoin-project/lotus/pull/6333)) + - Remove log line when tracing is not configured ([filecoin-project/lotus#6334](https://github.com/filecoin-project/lotus/pull/6334)) + - Revert "Allow starting networks from arbitrary actor versions" ([filecoin-project/lotus#6330](https://github.com/filecoin-project/lotus/pull/6330)) + - separate tracing environment variables ([filecoin-project/lotus#6323](https://github.com/filecoin-project/lotus/pull/6323)) + - Allow starting networks from arbitrary actor versions ([filecoin-project/lotus#6305](https://github.com/filecoin-project/lotus/pull/6305)) + - feat: log dispute rate ([filecoin-project/lotus#6322](https://github.com/filecoin-project/lotus/pull/6322)) + - Use new actor tags ([filecoin-project/lotus#6291](https://github.com/filecoin-project/lotus/pull/6291)) + - Fix logging around mineOne ([filecoin-project/lotus#6310](https://github.com/filecoin-project/lotus/pull/6310)) + - Fix shell completions ([filecoin-project/lotus#6316](https://github.com/filecoin-project/lotus/pull/6316)) + - Allow 8MB sectors in devnet ([filecoin-project/lotus#6312](https://github.com/filecoin-project/lotus/pull/6312)) + - fix ticket expired ([filecoin-project/lotus#6304](https://github.com/filecoin-project/lotus/pull/6304)) + - oh, snap! ([filecoin-project/lotus#6202](https://github.com/filecoin-project/lotus/pull/6202)) + - Move verifreg shed utils to CLI ([filecoin-project/lotus#6135](https://github.com/filecoin-project/lotus/pull/6135)) + - consider storiface.PathStorage when calculating storage requirements ([filecoin-project/lotus#6233](https://github.com/filecoin-project/lotus/pull/6233)) + - `storage` module: add go docs and minor code quality refactors ([filecoin-project/lotus#6259](https://github.com/filecoin-project/lotus/pull/6259)) + - Revert "chore: update go-libp2p" ([filecoin-project/lotus#6306](https://github.com/filecoin-project/lotus/pull/6306)) + - Increase data transfer timeouts ([filecoin-project/lotus#6300](https://github.com/filecoin-project/lotus/pull/6300)) + - gateway: spin off from cmd to package ([filecoin-project/lotus#6294](https://github.com/filecoin-project/lotus/pull/6294)) + - Update to markets 1.3 ([filecoin-project/lotus#6149](https://github.com/filecoin-project/lotus/pull/6149)) + - Add a shed util to count 64 GiB miner stats ([filecoin-project/lotus#6290](https://github.com/filecoin-project/lotus/pull/6290)) + - Delete CODEOWNERS ([filecoin-project/lotus#6289](https://github.com/filecoin-project/lotus/pull/6289)) + - Merge v1.9.0 to master ([filecoin-project/lotus#6275](https://github.com/filecoin-project/lotus/pull/6275)) + - Backport 6200 to master ([filecoin-project/lotus#6272](https://github.com/filecoin-project/lotus/pull/6272)) + - Introduce stateless offline dealflow, bypassing the FSM/deallists ([filecoin-project/lotus#5961](https://github.com/filecoin-project/lotus/pull/5961)) + - chore: update go-libp2p ([filecoin-project/lotus#6231](https://github.com/filecoin-project/lotus/pull/6231)) + - fix: wait-api should use GetAPI to acquire binary specific API ([filecoin-project/lotus#6246](https://github.com/filecoin-project/lotus/pull/6246)) + - Update RELEASE_ISSUE_TEMPLATE.md + - fix(ci): Updates to lotus CI build process ([filecoin-project/lotus#6256](https://github.com/filecoin-project/lotus/pull/6256)) + - add flags to control gateway lookback parameters ([filecoin-project/lotus#6247](https://github.com/filecoin-project/lotus/pull/6247)) + - Feat/nerpa v4 ([filecoin-project/lotus#6248](https://github.com/filecoin-project/lotus/pull/6248)) + - chore(ci): Enable build on RC tags ([filecoin-project/lotus#6238](https://github.com/filecoin-project/lotus/pull/6238)) + - Transplant some useful commands to lotus-shed actor ([filecoin-project/lotus#5913](https://github.com/filecoin-project/lotus/pull/5913)) + - wip actor wrapper codegen ([filecoin-project/lotus#6108](https://github.com/filecoin-project/lotus/pull/6108)) + - Robust message management ([filecoin-project/lotus#5822](https://github.com/filecoin-project/lotus/pull/5822)) + - Add a shed util to count miners by post type ([filecoin-project/lotus#6169](https://github.com/filecoin-project/lotus/pull/6169)) + - Introduce a release issue template ([filecoin-project/lotus#5826](https://github.com/filecoin-project/lotus/pull/5826)) + - cron-wc ([filecoin-project/lotus#6178](https://github.com/filecoin-project/lotus/pull/6178)) + - This is a 1:1 forward-port of PR#6183 from 1.9.x to master ([filecoin-project/lotus#6196](https://github.com/filecoin-project/lotus/pull/6196)) + - Allow creation of state tree v3s ([filecoin-project/lotus#6167](https://github.com/filecoin-project/lotus/pull/6167)) + - drand: fix beacon cache ([filecoin-project/lotus#6164](https://github.com/filecoin-project/lotus/pull/6164)) + - Update cli gen ([filecoin-project/lotus#6155](https://github.com/filecoin-project/lotus/pull/6155)) + - mpool: Cleanup pre-nv12 selection logic ([filecoin-project/lotus#6148](https://github.com/filecoin-project/lotus/pull/6148)) + - Update ffi to proofs v7 ([filecoin-project/lotus#6150](https://github.com/filecoin-project/lotus/pull/6150)) + - Generate CLI docs ([filecoin-project/lotus#6145](https://github.com/filecoin-project/lotus/pull/6145)) + - feat: allow checkpointing to forks ([filecoin-project/lotus#6107](https://github.com/filecoin-project/lotus/pull/6107)) + - attempt to do better padding on pieces being written into sectors ([filecoin-project/lotus#5988](https://github.com/filecoin-project/lotus/pull/5988)) + - remove duplicate ask and calculate ping before lock ([filecoin-project/lotus#5968](https://github.com/filecoin-project/lotus/pull/5968)) + - Add a command to get the fees of a deal ([filecoin-project/lotus#5307](https://github.com/filecoin-project/lotus/pull/5307)) + - flaky tests improvement: separate TestBatchDealInput from TestAPIDealFlow ([filecoin-project/lotus#6141](https://github.com/filecoin-project/lotus/pull/6141)) + - Testground checks on push ([filecoin-project/lotus#5887](https://github.com/filecoin-project/lotus/pull/5887)) + - Add a CLI tool for miner proving deadline ([filecoin-project/lotus#6132](https://github.com/filecoin-project/lotus/pull/6132)) + - Use EmptyTSK where appropriate ([filecoin-project/lotus#6134](https://github.com/filecoin-project/lotus/pull/6134)) + - fix: use a consistent tipset in commands ([filecoin-project/lotus#6142](https://github.com/filecoin-project/lotus/pull/6142)) + - go mod tidy for lotus-soup testplans ([filecoin-project/lotus#6124](https://github.com/filecoin-project/lotus/pull/6124)) + - fix testground payment channel tests: use 1 miner ([filecoin-project/lotus#6126](https://github.com/filecoin-project/lotus/pull/6126)) + - fix: use the parent state when listing actors ([filecoin-project/lotus#6143](https://github.com/filecoin-project/lotus/pull/6143)) + - Speed up StateListMessages in some cases ([filecoin-project/lotus#6007](https://github.com/filecoin-project/lotus/pull/6007)) + - Return total power when GetPowerRaw doesn't find miner claim ([filecoin-project/lotus#4938](https://github.com/filecoin-project/lotus/pull/4938)) + - fix(splitstore): fix a panic on revert-only head changes ([filecoin-project/lotus#6133](https://github.com/filecoin-project/lotus/pull/6133)) + - shed: command to list duplicate messages in tipsets (steb) ([filecoin-project/lotus#5847](https://github.com/filecoin-project/lotus/pull/5847)) + - upgrade `lotus-soup` testplans and reduce deals concurrency to a single miner ([filecoin-project/lotus#6122](https://github.com/filecoin-project/lotus/pull/6122)) + - Merge releases (1.8.0) into master ([filecoin-project/lotus#6118](https://github.com/filecoin-project/lotus/pull/6118)) +- github.com/filecoin-project/go-commp-utils (v0.1.0 -> v0.1.1-0.20210427191551-70bf140d31c7): + - add a padding helper function ([filecoin-project/go-commp-utils#3](https://github.com/filecoin-project/go-commp-utils/pull/3)) +- github.com/filecoin-project/go-data-transfer (v1.4.3 -> v1.6.0): + - release: v1.6.0 + - fix: option to disable accept and complete timeouts + - fix: disable restart ack timeout + - release: v1.5.0 + - Add isRestart param to validators (#197) ([filecoin-project/go-data-transfer#197](https://github.com/filecoin-project/go-data-transfer/pull/197)) + - fix: flaky TestChannelMonitorAutoRestart (#198) ([filecoin-project/go-data-transfer#198](https://github.com/filecoin-project/go-data-transfer/pull/198)) + - Channel monitor watches for errors instead of measuring data rate (#190) ([filecoin-project/go-data-transfer#190](https://github.com/filecoin-project/go-data-transfer/pull/190)) + - fix: prevent concurrent restarts for same channel (#195) ([filecoin-project/go-data-transfer#195](https://github.com/filecoin-project/go-data-transfer/pull/195)) + - fix: channel state machine event handling (#194) ([filecoin-project/go-data-transfer#194](https://github.com/filecoin-project/go-data-transfer/pull/194)) + - Dont double count data sent (#185) ([filecoin-project/go-data-transfer#185](https://github.com/filecoin-project/go-data-transfer/pull/185)) + - release: v1.4.3 (#189) ([filecoin-project/go-data-transfer#189](https://github.com/filecoin-project/go-data-transfer/pull/189)) +- github.com/filecoin-project/go-fil-markets (v1.2.5 -> v1.5.0): + - release: v1.5.0 + - Dynamic Retrieval Pricing (#542) ([filecoin-project/go-fil-markets#542](https://github.com/filecoin-project/go-fil-markets/pull/542)) + - release: v1.4.0 (#551) ([filecoin-project/go-fil-markets#551](https://github.com/filecoin-project/go-fil-markets/pull/551)) + - Update to go data transfer v1.6.0 (#550) ([filecoin-project/go-fil-markets#550](https://github.com/filecoin-project/go-fil-markets/pull/550)) + - fix first make error (#548) ([filecoin-project/go-fil-markets#548](https://github.com/filecoin-project/go-fil-markets/pull/548)) + - release: v1.3.0 (#544) ([filecoin-project/go-fil-markets#544](https://github.com/filecoin-project/go-fil-markets/pull/544)) + - fix restarts during data transfer for a retrieval deal (#540) ([filecoin-project/go-fil-markets#540](https://github.com/filecoin-project/go-fil-markets/pull/540)) + - Test Retrieval for offline deals (#541) ([filecoin-project/go-fil-markets#541](https://github.com/filecoin-project/go-fil-markets/pull/541)) + - Allow anonymous submodule checkout (#535) ([filecoin-project/go-fil-markets#535](https://github.com/filecoin-project/go-fil-markets/pull/535)) +- github.com/filecoin-project/specs-actors (v0.9.13 -> v0.9.14): + - Set ConsensusMinerMinPower to 10 TiB (#1427) ([filecoin-project/specs-actors#1427](https://github.com/filecoin-project/specs-actors/pull/1427)) +- github.com/filecoin-project/specs-actors/v2 (v2.3.5-0.20210114162132-5b58b773f4fb -> v2.3.5): + - Set ConsensusMinerMinPower to 10 TiB (#1428) ([filecoin-project/specs-actors#1428](https://github.com/filecoin-project/specs-actors/pull/1428)) + - v2 VM satisfies SimVM interface (#1355) ([filecoin-project/specs-actors#1355](https://github.com/filecoin-project/specs-actors/pull/1355)) +- github.com/filecoin-project/specs-actors/v3 (v3.1.0 -> v3.1.1): + - Set ConsensusMinerMinPower to 10 TiB for all PoStProofPolicies (#1429) ([filecoin-project/specs-actors#1429](https://github.com/filecoin-project/specs-actors/pull/1429)) +- github.com/filecoin-project/specs-actors/v4 (v4.0.0 -> v4.0.1): + - Set ConsensusMinerMinPower to 10 TiB for all PoStProofPolicies (#1430) ([filecoin-project/specs-actors#1430](https://github.com/filecoin-project/specs-actors/pull/1430)) + +Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| Raúl Kripalani | 118 | +11972/-10860 | 472 | +| Łukasz Magiera | 65 | +10824/-4158 | 353 | +| aarshkshah1992 | 59 | +8057/-3355 | 224 | +| Aayush Rajasekaran | 41 | +8786/-1691 | 331 | +| Steven Allen | 106 | +7653/-2718 | 273 | +| dirkmc | 11 | +2580/-1371 | 77 | +| Dirk McCormick | 39 | +1865/-1194 | 79 | +| Jakub Sztandera | 19 | +1973/-485 | 81 | +| vyzo | 4 | +1748/-330 | 50 | +| Aarsh Shah | 5 | +1462/-213 | 27 | +| Cory Schwartz | 35 | +568/-206 | 59 | +| chadwick2143 | 3 | +739/-1 | 4 | +| Peter Rabbitson | 21 | +487/-164 | 36 | +| hannahhoward | 5 | +544/-5 | 19 | +| Jennifer Wang | 8 | +206/-172 | 17 | +| frrist | 1 | +137/-88 | 7 | +| Travis Person | 3 | +175/-6 | 7 | +| Alex Wade | 1 | +48/-129 | 1 | +| whyrusleeping | 8 | +161/-13 | 11 | +| lotus | 1 | +114/-46 | 1 | +| Anton Evangelatov | 8 | +107/-53 | 20 | +| Rjan | 4 | +115/-33 | 4 | +| ZenGround0 | 3 | +114/-1 | 4 | +| Aloxaf | 1 | +43/-61 | 7 | +| yaohcn | 4 | +89/-9 | 5 | +| mitchellsoo | 1 | +51/-0 | 1 | +| Mike Greenberg | 3 | +28/-18 | 4 | +| Jennifer | 6 | +9/-14 | 6 | +| Frank | 2 | +11/-10 | 2 | +| wangchao | 3 | +5/-4 | 4 | +| Steve Loeppky | 1 | +7/-1 | 1 | +| Lion | 1 | +4/-2 | 1 | +| Mimir | 1 | +2/-2 | 1 | +| raulk | 1 | +1/-1 | 1 | +| Jack Yao | 1 | +1/-1 | 1 | +| IPFSUnion | 1 | +1/-1 | 1 | + # 1.10.0 / 2021-06-23 This is a mandatory release of Lotus that introduces Filecoin network v13, codenamed the HyperDrive upgrade. The diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 8306fab569c17d4acaffb7bcb42fdeedd2b2f339..0cbb105e18b2e38b1576c3caadcd6f782ec7e8d4 100644 GIT binary patch literal 23439 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 diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index b57a1b3191e1a54cc00a88523c8092f5542bf6ee..fe77f76c469f2823f39e81872279e8225a81d3f7 100644 GIT binary patch delta 23 fcmZ2xzs!C@8*|L!)f+oSK-I?4~#J~Umf!_)n delta 23 ecmca8d{KBp1G8?$fsM^>oE*~35udt>7#IL;RtPWv diff --git a/build/version.go b/build/version.go index 5a4a494fc..49ec3f446 100644 --- a/build/version.go +++ b/build/version.go @@ -34,7 +34,7 @@ func buildType() string { } // BuildVersion is the local build version, set by build system -const BuildVersion = "1.11.0-dev" +const BuildVersion = "1.11.0-rc1" func UserVersion() string { if os.Getenv("LOTUS_VERSION_IGNORE_COMMIT") == "1" {