package test import ( "context" "fmt" "os" "strings" "testing" "time" logging "github.com/ipfs/go-log/v2" "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/network" lapi "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api/v1api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/miner" "github.com/filecoin-project/lotus/node" ) func init() { logging.SetAllLoggers(logging.LevelInfo) 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 } type StorageBuilder func(context.Context, *testing.T, abi.RegisteredSealProof, address.Address) TestStorageNode type TestNode 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 StorageBuilder } type TestStorageNode 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([]TestNode) 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) ([]TestNode, []TestStorageNode) type testSuite struct { makeNodes APIBuilder } // TestApis is the entry point to API test suite func TestApis(t *testing.T, b APIBuilder) { ts := testSuite{ makeNodes: b, } t.Run("version", ts.testVersion) t.Run("id", ts.testID) t.Run("testConnectTwo", ts.testConnectTwo) t.Run("testMining", ts.testMining) t.Run("testMiningReal", ts.testMiningReal) t.Run("testSearchMsg", ts.testSearchMsg) t.Run("testNonGenesisMiner", ts.testNonGenesisMiner) } func DefaultFullOpts(nFull int) []FullNodeOpts { full := make([]FullNodeOpts, nFull) for i := range full { full[i] = FullNodeOpts{ Opts: func(nodes []TestNode) 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 FullNodeWithActorsUpgradeAt(network.Version13, upgradeHeight) } var FullNodeWithActorsUpgradeAt = 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 []TestNode) node.Option { return node.Override(new(stmgr.UpgradeSchedule), fullSchedule) }, } } var FullNodeWithSDRAt = func(calico, persian abi.ChainEpoch) FullNodeOpts { return FullNodeOpts{ Opts: func(nodes []TestNode) 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 FullNodeWithV4ActorsAt = func(upgradeHeight abi.ChainEpoch) FullNodeOpts { if upgradeHeight == -1 { upgradeHeight = 3 } return FullNodeOpts{ Opts: func(nodes []TestNode) 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 MineNext = miner.MineReq{ InjectNulls: 0, Done: func(bool, abi.ChainEpoch, error) {}, } func (ts *testSuite) testVersion(t *testing.T) { lapi.RunningNodeType = lapi.NodeFull t.Cleanup(func() { lapi.RunningNodeType = lapi.NodeUnknown }) ctx := context.Background() apis, _ := ts.makeNodes(t, OneFull, OneMiner) napi := apis[0] v, err := napi.Version(ctx) if err != nil { t.Fatal(err) } versions := strings.Split(v.Version, "+") if len(versions) <= 0 { t.Fatal("empty version") } require.Equal(t, versions[0], build.BuildVersion) } func (ts *testSuite) testSearchMsg(t *testing.T) { apis, miners := ts.makeNodes(t, OneFull, OneMiner) api := apis[0] ctx, cancel := context.WithCancel(context.Background()) defer cancel() senderAddr, err := api.WalletDefaultAddress(ctx) if err != nil { t.Fatal(err) } msg := &types.Message{ From: senderAddr, To: senderAddr, Value: big.Zero(), } bm := NewBlockMiner(ctx, t, miners[0], 100*time.Millisecond) bm.MineBlocks() defer bm.Stop() sm, err := api.MpoolPushMessage(ctx, msg, nil) if err != nil { t.Fatal(err) } res, err := api.StateWaitMsg(ctx, sm.Cid(), 1, lapi.LookbackNoLimit, true) if err != nil { t.Fatal(err) } if res.Receipt.ExitCode != 0 { t.Fatal("did not successfully send message") } searchRes, err := api.StateSearchMsg(ctx, types.EmptyTSK, sm.Cid(), lapi.LookbackNoLimit, true) if err != nil { t.Fatal(err) } if searchRes.TipSet != res.TipSet { t.Fatalf("search ts: %s, different from wait ts: %s", searchRes.TipSet, res.TipSet) } } func (ts *testSuite) testID(t *testing.T) { ctx := context.Background() apis, _ := ts.makeNodes(t, OneFull, OneMiner) api := apis[0] id, err := api.ID(ctx) if err != nil { t.Fatal(err) } assert.Regexp(t, "^12", id.Pretty()) } func (ts *testSuite) testConnectTwo(t *testing.T) { ctx := context.Background() apis, _ := ts.makeNodes(t, TwoFull, OneMiner) p, err := apis[0].NetPeers(ctx) if err != nil { t.Fatal(err) } if len(p) != 0 { t.Error("Node 0 has a peer") } p, err = apis[1].NetPeers(ctx) if err != nil { t.Fatal(err) } if len(p) != 0 { t.Error("Node 1 has a peer") } addrs, err := apis[1].NetAddrsListen(ctx) if err != nil { t.Fatal(err) } if err := apis[0].NetConnect(ctx, addrs); err != nil { t.Fatal(err) } p, err = apis[0].NetPeers(ctx) if err != nil { t.Fatal(err) } if len(p) != 1 { t.Error("Node 0 doesn't have 1 peer") } p, err = apis[1].NetPeers(ctx) if err != nil { t.Fatal(err) } if len(p) != 1 { t.Error("Node 0 doesn't have 1 peer") } }