diff --git a/api/test/blockminer.go b/api/test/blockminer.go new file mode 100644 index 000000000..3f4c2b003 --- /dev/null +++ b/api/test/blockminer.go @@ -0,0 +1,56 @@ +package test + +import ( + "context" + "fmt" + "sync/atomic" + "testing" + "time" + + "github.com/filecoin-project/lotus/miner" + "github.com/filecoin-project/specs-actors/actors/abi" +) + +type BlockMiner struct { + ctx context.Context + t *testing.T + miner TestStorageNode + blocktime time.Duration + mine int64 + nulls int64 + done chan struct{} +} + +func NewBlockMiner(ctx context.Context, t *testing.T, miner TestStorageNode, blocktime time.Duration) *BlockMiner { + return &BlockMiner{ + ctx: ctx, + t: t, + miner: miner, + blocktime: blocktime, + mine: int64(1), + done: make(chan struct{}), + } +} + +func (bm *BlockMiner) MineBlocks() { + time.Sleep(time.Second) + go func() { + defer close(bm.done) + for atomic.LoadInt64(&bm.mine) == 1 { + time.Sleep(bm.blocktime) + nulls := atomic.SwapInt64(&bm.nulls, 0) + if err := bm.miner.MineOne(bm.ctx, miner.MineReq{ + InjectNulls: abi.ChainEpoch(nulls), + Done: func(bool, error) {}, + }); err != nil { + bm.t.Error(err) + } + } + }() +} + +func (bm *BlockMiner) Stop() { + atomic.AddInt64(&bm.mine, -1) + fmt.Println("shutting down mining") + <-bm.done +} diff --git a/api/test/ccupgrade.go b/api/test/ccupgrade.go index cce327c6c..3666aa3db 100644 --- a/api/test/ccupgrade.go +++ b/api/test/ccupgrade.go @@ -20,7 +20,7 @@ func TestCCUpgrade(t *testing.T, b APIBuilder, blocktime time.Duration) { _ = os.Setenv("BELLMAN_NO_GPU", "1") ctx := context.Background() - n, sn := b(t, 1, oneMiner) + n, sn := b(t, 1, OneMiner) client := n[0].FullNode.(*impl.FullNodeAPI) miner := sn[0] diff --git a/api/test/deals.go b/api/test/deals.go index f30b5b8ac..a02b8d5b2 100644 --- a/api/test/deals.go +++ b/api/test/deals.go @@ -47,7 +47,7 @@ func TestDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration, carExport _ = os.Setenv("BELLMAN_NO_GPU", "1") ctx := context.Background() - n, sn := b(t, 1, oneMiner) + n, sn := b(t, 1, OneMiner) client := n[0].FullNode.(*impl.FullNodeAPI) miner := sn[0] @@ -84,7 +84,7 @@ func TestDoubleDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration) { _ = os.Setenv("BELLMAN_NO_GPU", "1") ctx := context.Background() - n, sn := b(t, 1, oneMiner) + n, sn := b(t, 1, OneMiner) client := n[0].FullNode.(*impl.FullNodeAPI) miner := sn[0] @@ -148,7 +148,7 @@ func TestFastRetrievalDealFlow(t *testing.T, b APIBuilder, blocktime time.Durati _ = os.Setenv("BELLMAN_NO_GPU", "1") ctx := context.Background() - n, sn := b(t, 1, oneMiner) + n, sn := b(t, 1, OneMiner) client := n[0].FullNode.(*impl.FullNodeAPI) miner := sn[0] @@ -203,7 +203,7 @@ func TestSenondDealRetrieval(t *testing.T, b APIBuilder, blocktime time.Duration _ = os.Setenv("BELLMAN_NO_GPU", "1") ctx := context.Background() - n, sn := b(t, 1, oneMiner) + n, sn := b(t, 1, OneMiner) client := n[0].FullNode.(*impl.FullNodeAPI) miner := sn[0] diff --git a/api/test/mining.go b/api/test/mining.go index 581697440..ee1268ed2 100644 --- a/api/test/mining.go +++ b/api/test/mining.go @@ -24,7 +24,7 @@ var log = logging.Logger("apitest") func (ts *testSuite) testMining(t *testing.T) { ctx := context.Background() - apis, sn := ts.makeNodes(t, 1, oneMiner) + apis, sn := ts.makeNodes(t, 1, OneMiner) api := apis[0] newHeads, err := api.ChainNotify(ctx) @@ -55,7 +55,7 @@ func (ts *testSuite) testMiningReal(t *testing.T) { }() ctx := context.Background() - apis, sn := ts.makeNodes(t, 1, oneMiner) + apis, sn := ts.makeNodes(t, 1, OneMiner) api := apis[0] newHeads, err := api.ChainNotify(ctx) diff --git a/api/test/paych.go b/api/test/paych.go index 1b1d4e438..09e6f5d96 100644 --- a/api/test/paych.go +++ b/api/test/paych.go @@ -23,14 +23,13 @@ import ( "github.com/filecoin-project/lotus/chain/events/state" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/wallet" - "github.com/filecoin-project/lotus/miner" ) func TestPaymentChannels(t *testing.T, b APIBuilder, blocktime time.Duration) { _ = os.Setenv("BELLMAN_NO_GPU", "1") ctx := context.Background() - n, sn := b(t, 2, oneMiner) + n, sn := b(t, 2, OneMiner) paymentCreator := n[0] paymentReceiver := n[1] @@ -51,8 +50,8 @@ func TestPaymentChannels(t *testing.T, b APIBuilder, blocktime time.Duration) { } // start mining blocks - bm := newBlockMiner(ctx, t, miner, blocktime) - bm.mineBlocks() + bm := NewBlockMiner(ctx, t, miner, blocktime) + bm.MineBlocks() // send some funds to register the receiver receiverAddr, err := paymentReceiver.WalletNew(ctx, wallet.ActSigType("secp256k1")) @@ -60,7 +59,7 @@ func TestPaymentChannels(t *testing.T, b APIBuilder, blocktime time.Duration) { t.Fatal(err) } - sendFunds(ctx, t, paymentCreator, receiverAddr, abi.NewTokenAmount(1e18)) + SendFunds(ctx, t, paymentCreator, receiverAddr, abi.NewTokenAmount(1e18)) // setup the payment channel createrAddr, err := paymentCreator.WalletDefaultAddress(ctx) @@ -201,10 +200,10 @@ func TestPaymentChannels(t *testing.T, b APIBuilder, blocktime time.Duration) { } // shut down mining - bm.stop() + bm.Stop() } -func waitForBlocks(ctx context.Context, t *testing.T, bm *blockMiner, paymentReceiver TestNode, receiverAddr address.Address, count int) { +func waitForBlocks(ctx context.Context, t *testing.T, bm *BlockMiner, paymentReceiver TestNode, 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 { @@ -249,73 +248,3 @@ func waitForMessage(ctx context.Context, t *testing.T, paymentCreator TestNode, fmt.Println("Confirmed", desc) return res } - -type blockMiner struct { - ctx context.Context - t *testing.T - miner TestStorageNode - blocktime time.Duration - mine int64 - nulls int64 - done chan struct{} -} - -func newBlockMiner(ctx context.Context, t *testing.T, miner TestStorageNode, blocktime time.Duration) *blockMiner { - return &blockMiner{ - ctx: ctx, - t: t, - miner: miner, - blocktime: blocktime, - mine: int64(1), - done: make(chan struct{}), - } -} - -func (bm *blockMiner) mineBlocks() { - time.Sleep(time.Second) - go func() { - defer close(bm.done) - for atomic.LoadInt64(&bm.mine) == 1 { - time.Sleep(bm.blocktime) - nulls := atomic.SwapInt64(&bm.nulls, 0) - if err := bm.miner.MineOne(bm.ctx, miner.MineReq{ - InjectNulls: abi.ChainEpoch(nulls), - Done: func(bool, error) {}, - }); err != nil { - bm.t.Error(err) - } - } - }() -} - -func (bm *blockMiner) stop() { - atomic.AddInt64(&bm.mine, -1) - fmt.Println("shutting down mining") - <-bm.done -} - -func sendFunds(ctx context.Context, t *testing.T, sender TestNode, addr address.Address, amount abi.TokenAmount) { - - senderAddr, err := sender.WalletDefaultAddress(ctx) - if err != nil { - t.Fatal(err) - } - - msg := &types.Message{ - From: senderAddr, - To: addr, - Value: amount, - } - - sm, err := sender.MpoolPushMessage(ctx, msg, nil) - if err != nil { - t.Fatal(err) - } - res, err := sender.StateWaitMsg(ctx, sm.Cid(), 1) - if err != nil { - t.Fatal(err) - } - if res.Receipt.ExitCode != 0 { - t.Fatal("did not successfully send money") - } -} diff --git a/api/test/test.go b/api/test/test.go index 27325566a..98a9a2e48 100644 --- a/api/test/test.go +++ b/api/test/test.go @@ -4,6 +4,8 @@ import ( "context" "testing" + "github.com/multiformats/go-multiaddr" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -14,10 +16,16 @@ import ( type TestNode struct { api.FullNode + // ListenAddr is the address on which an API server is listening, if an + // API server is created for this Node + ListenAddr multiaddr.Multiaddr } type TestStorageNode struct { api.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 } @@ -54,11 +62,11 @@ func TestApis(t *testing.T, b APIBuilder) { t.Run("testMiningReal", ts.testMiningReal) } -var oneMiner = []StorageMiner{{Full: 0, Preseal: PresealGenesis}} +var OneMiner = []StorageMiner{{Full: 0, Preseal: PresealGenesis}} func (ts *testSuite) testVersion(t *testing.T) { ctx := context.Background() - apis, _ := ts.makeNodes(t, 1, oneMiner) + apis, _ := ts.makeNodes(t, 1, OneMiner) api := apis[0] v, err := api.Version(ctx) @@ -70,7 +78,7 @@ func (ts *testSuite) testVersion(t *testing.T) { func (ts *testSuite) testID(t *testing.T) { ctx := context.Background() - apis, _ := ts.makeNodes(t, 1, oneMiner) + apis, _ := ts.makeNodes(t, 1, OneMiner) api := apis[0] id, err := api.ID(ctx) @@ -82,7 +90,7 @@ func (ts *testSuite) testID(t *testing.T) { func (ts *testSuite) testConnectTwo(t *testing.T) { ctx := context.Background() - apis, _ := ts.makeNodes(t, 2, oneMiner) + apis, _ := ts.makeNodes(t, 2, OneMiner) p, err := apis[0].NetPeers(ctx) if err != nil { diff --git a/api/test/util.go b/api/test/util.go new file mode 100644 index 000000000..7ffd2d312 --- /dev/null +++ b/api/test/util.go @@ -0,0 +1,36 @@ +package test + +import ( + "context" + "testing" + + "github.com/filecoin-project/specs-actors/actors/abi" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/lotus/chain/types" +) + +func SendFunds(ctx context.Context, t *testing.T, sender TestNode, addr address.Address, amount abi.TokenAmount) { + senderAddr, err := sender.WalletDefaultAddress(ctx) + if err != nil { + t.Fatal(err) + } + + msg := &types.Message{ + From: senderAddr, + To: addr, + Value: amount, + } + + sm, err := sender.MpoolPushMessage(ctx, msg, nil) + if err != nil { + t.Fatal(err) + } + res, err := sender.StateWaitMsg(ctx, sm.Cid(), 1) + if err != nil { + t.Fatal(err) + } + if res.Receipt.ExitCode != 0 { + t.Fatal("did not successfully send money") + } +} diff --git a/api/test/window_post.go b/api/test/window_post.go index 2c7913d91..e2a553c21 100644 --- a/api/test/window_post.go +++ b/api/test/window_post.go @@ -28,7 +28,7 @@ func TestPledgeSector(t *testing.T, b APIBuilder, blocktime time.Duration, nSect os.Setenv("BELLMAN_NO_GPU", "1") ctx := context.Background() - n, sn := b(t, 1, oneMiner) + n, sn := b(t, 1, OneMiner) client := n[0].FullNode.(*impl.FullNodeAPI) miner := sn[0] @@ -113,7 +113,7 @@ func TestWindowPost(t *testing.T, b APIBuilder, blocktime time.Duration, nSector os.Setenv("BELLMAN_NO_GPU", "1") ctx := context.Background() - n, sn := b(t, 1, oneMiner) + n, sn := b(t, 1, OneMiner) client := n[0].FullNode.(*impl.FullNodeAPI) miner := sn[0] diff --git a/cli/cmd.go b/cli/cmd.go index 22b95089d..265f6ee4c 100644 --- a/cli/cmd.go +++ b/cli/cmd.go @@ -67,6 +67,19 @@ func (a APIInfo) AuthHeader() http.Header { return nil } +// The flag passed on the command line with the listen address of the API +// server (only used by the tests) +func flagForAPI(t repo.RepoType) string { + switch t { + case repo.FullNode: + return "api" + case repo.StorageMiner: + return "miner-api" + default: + panic(fmt.Sprintf("Unknown repo type: %v", t)) + } +} + func flagForRepo(t repo.RepoType) string { switch t { case repo.FullNode: @@ -102,6 +115,20 @@ func envForRepoDeprecation(t repo.RepoType) string { } func GetAPIInfo(ctx *cli.Context, t repo.RepoType) (APIInfo, error) { + // Check if there was a flag passed with the listen address of the API + // server (only used by the tests) + apiFlag := flagForAPI(t) + if ctx.IsSet(apiFlag) { + strma := ctx.String(apiFlag) + strma = strings.TrimSpace(strma) + + apima, err := multiaddr.NewMultiaddr(strma) + if err != nil { + return APIInfo{}, err + } + return APIInfo{Addr: apima}, nil + } + envKey := envForRepo(t) env, ok := os.LookupEnv(envKey) if !ok { diff --git a/cli/paych.go b/cli/paych.go index 6b7684e56..cf00be72c 100644 --- a/cli/paych.go +++ b/cli/paych.go @@ -5,9 +5,8 @@ import ( "encoding/base64" "fmt" - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/go-address" + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/specs-actors/actors/builtin/paych" "github.com/urfave/cli/v2" @@ -71,7 +70,7 @@ var paychGetCmd = &cli.Command{ return err } - fmt.Println(chAddr) + fmt.Fprintln(cctx.App.Writer, chAddr) return nil }, } @@ -94,7 +93,7 @@ var paychListCmd = &cli.Command{ } for _, v := range chs { - fmt.Println(v.String()) + fmt.Fprintln(cctx.App.Writer, v.String()) } return nil }, @@ -135,7 +134,7 @@ var paychSettleCmd = &cli.Command{ return fmt.Errorf("settle message execution failed (exit code %d)", mwait.Receipt.ExitCode) } - fmt.Printf("Settled channel %s\n", ch) + fmt.Fprintf(cctx.App.Writer, "Settled channel %s\n", ch) return nil }, } @@ -175,7 +174,7 @@ var paychCloseCmd = &cli.Command{ return fmt.Errorf("collect message execution failed (exit code %d)", mwait.Receipt.ExitCode) } - fmt.Printf("Collected funds for channel %s\n", ch) + fmt.Fprintf(cctx.App.Writer, "Collected funds for channel %s\n", ch) return nil }, } @@ -239,7 +238,7 @@ var paychVoucherCreateCmd = &cli.Command{ return err } - fmt.Println(enc) + fmt.Fprintln(cctx.App.Writer, enc) return nil }, } @@ -275,7 +274,7 @@ var paychVoucherCheckCmd = &cli.Command{ return err } - fmt.Println("voucher is valid") + fmt.Fprintln(cctx.App.Writer, "voucher is valid") return nil }, } @@ -356,9 +355,9 @@ var paychVoucherListCmd = &cli.Command{ return err } - fmt.Printf("Lane %d, Nonce %d: %s; %s\n", v.Lane, v.Nonce, v.Amount.String(), enc) + fmt.Fprintf(cctx.App.Writer, "Lane %d, Nonce %d: %s; %s\n", v.Lane, v.Nonce, v.Amount.String(), enc) } else { - fmt.Printf("Lane %d, Nonce %d: %s\n", v.Lane, v.Nonce, v.Amount.String()) + fmt.Fprintf(cctx.App.Writer, "Lane %d, Nonce %d: %s\n", v.Lane, v.Nonce, v.Amount.String()) } } @@ -415,8 +414,8 @@ var paychVoucherBestSpendableCmd = &cli.Command{ return err } - fmt.Println(enc) - fmt.Printf("Amount: %s\n", best.Amount) + fmt.Fprintln(cctx.App.Writer, enc) + fmt.Fprintf(cctx.App.Writer, "Amount: %s\n", best.Amount) return nil }, } @@ -462,7 +461,7 @@ var paychVoucherSubmitCmd = &cli.Command{ return fmt.Errorf("message execution failed (exit code %d)", mwait.Receipt.ExitCode) } - fmt.Println("channel updated successfully") + fmt.Fprintln(cctx.App.Writer, "channel updated successfully") return nil }, diff --git a/cli/paych_test.go b/cli/paych_test.go new file mode 100644 index 000000000..ed883c228 --- /dev/null +++ b/cli/paych_test.go @@ -0,0 +1,228 @@ +package cli + +import ( + "bytes" + "context" + "flag" + "os" + "strconv" + "strings" + "testing" + "time" + + "github.com/filecoin-project/specs-actors/actors/abi/big" + saminer "github.com/filecoin-project/specs-actors/actors/builtin/miner" + "github.com/filecoin-project/specs-actors/actors/builtin/power" + "github.com/filecoin-project/specs-actors/actors/builtin/verifreg" + + "github.com/multiformats/go-multiaddr" + + "github.com/filecoin-project/lotus/chain/events" + + "github.com/filecoin-project/lotus/api/apibstore" + "github.com/filecoin-project/specs-actors/actors/builtin/paych" + cbor "github.com/ipfs/go-ipld-cbor" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/lotus/chain/types" + + "github.com/filecoin-project/lotus/api/test" + "github.com/filecoin-project/lotus/chain/wallet" + builder "github.com/filecoin-project/lotus/node/test" + "github.com/filecoin-project/specs-actors/actors/abi" + "github.com/stretchr/testify/require" + "github.com/urfave/cli/v2" +) + +func init() { + power.ConsensusMinerMinPower = big.NewInt(2048) + saminer.SupportedProofTypes = map[abi.RegisteredSealProof]struct{}{ + abi.RegisteredSealProof_StackedDrg2KiBV1: {}, + } + verifreg.MinVerifiedDealSize = big.NewInt(256) +} + +// TestPaymentChannels does a basic test to exercise the payment channel CLI +// commands +func TestPaymentChannels(t *testing.T) { + _ = os.Setenv("BELLMAN_NO_GPU", "1") + + blocktime := 5 * time.Millisecond + ctx := context.Background() + n, sn := builder.RPCMockSbBuilder(t, 2, test.OneMiner) + + paymentCreator := n[0] + paymentReceiver := n[1] + miner := sn[0] + + // 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 := test.NewBlockMiner(ctx, t, miner, blocktime) + bm.MineBlocks() + + // Send some funds to register the receiver + receiverAddr, err := paymentReceiver.WalletNew(ctx, wallet.ActSigType("secp256k1")) + if err != nil { + t.Fatal(err) + } + + test.SendFunds(ctx, t, paymentCreator, receiverAddr, abi.NewTokenAmount(1e18)) + + // Get the creator's address + creatorAddr, err := paymentCreator.WalletDefaultAddress(ctx) + if err != nil { + t.Fatal(err) + } + + // Create mock CLI + mockCLI := newMockCLI(t) + creatorCLI := mockCLI.client(paymentCreator.ListenAddr) + receiverCLI := mockCLI.client(paymentReceiver.ListenAddr) + + // creator: paych get + channelAmt := "100000" + cmd := []string{creatorAddr.String(), receiverAddr.String(), channelAmt} + chstr := creatorCLI.runCmd(paychGetCmd, cmd) + + chAddr, err := address.NewFromString(chstr) + require.NoError(t, err) + + // creator: paych voucher create + voucherAmt := 100 + vamt := strconv.Itoa(voucherAmt) + cmd = []string{chAddr.String(), vamt} + voucher := creatorCLI.runCmd(paychVoucherCreateCmd, cmd) + + // receiver: paych voucher add + cmd = []string{chAddr.String(), voucher} + receiverCLI.runCmd(paychVoucherAddCmd, cmd) + + // creator: paych settle + cmd = []string{chAddr.String()} + creatorCLI.runCmd(paychSettleCmd, cmd) + + // Wait for the chain to reach the settle height + chState := getPaychState(ctx, t, paymentReceiver, chAddr) + waitForHeight(ctx, t, paymentReceiver, chState.SettlingAt) + + // receiver: paych collect + cmd = []string{chAddr.String()} + receiverCLI.runCmd(paychCloseCmd, cmd) +} + +type mockCLI struct { + t *testing.T + cctx *cli.Context + out *bytes.Buffer +} + +func newMockCLI(t *testing.T) *mockCLI { + // Create a CLI App with an --api flag so that we can specify which node + // the command should be executed against + app := cli.NewApp() + app.Flags = []cli.Flag{ + &cli.StringFlag{ + Name: "api", + Hidden: true, + }, + } + var out bytes.Buffer + app.Writer = &out + app.Setup() + + cctx := cli.NewContext(app, &flag.FlagSet{}, nil) + return &mockCLI{t: t, cctx: cctx, out: &out} +} + +func (c *mockCLI) client(addr multiaddr.Multiaddr) *mockCLIClient { + return &mockCLIClient{t: c.t, addr: addr, cctx: c.cctx, out: c.out} +} + +// mockCLIClient runs commands against a particular node +type mockCLIClient struct { + t *testing.T + addr multiaddr.Multiaddr + cctx *cli.Context + out *bytes.Buffer +} + +func (c *mockCLIClient) runCmd(cmd *cli.Command, input []string) string { + // prepend --api= + apiFlag := "--api=" + c.addr.String() + input = append([]string{apiFlag}, input...) + + fs := c.flagSet(cmd) + err := fs.Parse(input) + require.NoError(c.t, err) + + err = cmd.Action(cli.NewContext(c.cctx.App, fs, c.cctx)) + require.NoError(c.t, err) + + // Get the output + str := strings.TrimSpace(c.out.String()) + c.out.Reset() + return str +} + +func (c *mockCLIClient) flagSet(cmd *cli.Command) *flag.FlagSet { + // Apply app level flags (so we can process --api 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 +} + +// waitForHeight waits for the node to reach the given chain epoch +func waitForHeight(ctx context.Context, t *testing.T, node test.TestNode, height abi.ChainEpoch) { + atHeight := make(chan struct{}) + chainEvents := events.NewEvents(ctx, node) + err := chainEvents.ChainAt(func(ctx context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { + close(atHeight) + return nil + }, nil, 1, height) + if err != nil { + t.Fatal(err) + } + + select { + case <-atHeight: + case <-ctx.Done(): + } +} + +// getPaychState gets the state of the payment channel with the given address +func getPaychState(ctx context.Context, t *testing.T, node test.TestNode, chAddr address.Address) paych.State { + act, err := node.StateGetActor(ctx, chAddr, types.EmptyTSK) + require.NoError(t, err) + + store := cbor.NewCborStore(apibstore.NewAPIBlockstore(node)) + var chState paych.State + err = store.Get(ctx, act.Head, &chState) + require.NoError(t, err) + + return chState +} diff --git a/node/node_test.go b/node/node_test.go index 770ce1f5b..31a14bc20 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -1,55 +1,21 @@ package node_test import ( - "bytes" - "context" - "crypto/rand" - "io/ioutil" - "net/http/httptest" "os" - "sync" "testing" "time" + builder "github.com/filecoin-project/lotus/node/test" + "github.com/filecoin-project/lotus/lib/lotuslog" - "github.com/filecoin-project/lotus/storage/mockstorage" - - "github.com/filecoin-project/go-storedcounter" - "github.com/ipfs/go-datastore" - logging "github.com/ipfs/go-log/v2" - "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/stretchr/testify/require" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-jsonrpc" "github.com/filecoin-project/specs-actors/actors/abi" "github.com/filecoin-project/specs-actors/actors/abi/big" - "github.com/filecoin-project/specs-actors/actors/builtin" saminer "github.com/filecoin-project/specs-actors/actors/builtin/miner" "github.com/filecoin-project/specs-actors/actors/builtin/power" "github.com/filecoin-project/specs-actors/actors/builtin/verifreg" + logging "github.com/ipfs/go-log/v2" - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/api/client" "github.com/filecoin-project/lotus/api/test" - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/actors" - "github.com/filecoin-project/lotus/chain/gen" - genesis2 "github.com/filecoin-project/lotus/chain/gen/genesis" - "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" - "github.com/filecoin-project/lotus/miner" - "github.com/filecoin-project/lotus/node" - "github.com/filecoin-project/lotus/node/modules" - modtest "github.com/filecoin-project/lotus/node/modules/testing" - "github.com/filecoin-project/lotus/node/repo" ) func init() { @@ -62,435 +28,12 @@ func init() { verifreg.MinVerifiedDealSize = big.NewInt(256) } -func testStorageNode(ctx context.Context, t *testing.T, waddr address.Address, act address.Address, pk crypto.PrivKey, tnd test.TestNode, mn mocknet.Mocknet, opts node.Option) test.TestStorageNode { - 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("/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 < test.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(&saminer.ChangePeerIDParams{NewID: abi.PeerID(peerid)}) - require.NoError(t, err) - - msg := &types.Message{ - To: act, - From: waddr, - Method: builtin.MethodsMiner.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 miner.MineReq) - // TODO: use stop - _, err = node.New(ctx, - node.StorageMiner(&minerapi), - node.Online(), - node.Repo(r), - node.Test(), - - node.MockHost(mn), - - node.Override(new(api.FullNode), tnd), - node.Override(new(*miner.Miner), miner.NewTestMiner(mineBlock, act)), - - opts, - ) - if err != nil { - t.Fatalf("failed to construct node: %v", err) - } - - /*// 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 miner.MineReq) error { - select { - case mineBlock <- req: - return nil - case <-ctx.Done(): - return ctx.Err() - } - } - - return test.TestStorageNode{StorageMiner: minerapi, MineOne: mineOne} -} - -func builder(t *testing.T, nFull int, storage []test.StorageMiner) ([]test.TestNode, []test.TestStorageNode) { - ctx := context.Background() - mn := mocknet.New(ctx) - - fulls := make([]test.TestNode, nFull) - storers := make([]test.TestStorageNode, 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 - var maddrs []address.Address - var genaccs []genesis.Actor - var 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, test.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(400_000_000), 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, - Timestamp: uint64(time.Now().Unix() - 10000), // some time sufficiently far in the past - VerifregRootKey: gen.DefaultVerifregRootkeyActor, - } - - // END PRESEAL SECTION - - for i := 0; i < nFull; i++ { - var genesis node.Option - if i == 0 { - genesis = node.Override(new(modules.Genesis), modtest.MakeGenesisMem(&genbuf, *templ)) - } else { - genesis = node.Override(new(modules.Genesis), modules.LoadGenesis(genbuf.Bytes())) - } - - var err error - // TODO: Don't ignore stop - _, err = node.New(ctx, - node.FullAPI(&fulls[i].FullNode), - node.Online(), - node.Repo(repo.NewMemory(nil)), - node.MockHost(mn), - node.Test(), - - genesis, - ) - if 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 - - storers[i] = testStorageNode(ctx, t, wa, genMiner, pk, f, mn, node.Options()) - if err := storers[i].StorageAddLocal(ctx, presealDirs[i]); err != nil { - t.Fatalf("%+v", err) - } - /* - sma := storers[i].StorageMiner.(*impl.StorageMinerAPI) - - psd := presealDirs[i] - */ - } - - if err := mn.LinkAll(); err != nil { - t.Fatal(err) - } - - if len(storers) > 0 { - // Mine 2 blocks to setup some CE stuff in some actors - var wait sync.Mutex - wait.Lock() - - storers[0].MineOne(ctx, miner.MineReq{Done: func(bool, error) { - wait.Unlock() - }}) - wait.Lock() - storers[0].MineOne(ctx, miner.MineReq{Done: func(bool, error) { - wait.Unlock() - }}) - wait.Lock() - } - - return fulls, storers -} - -func mockSbBuilder(t *testing.T, nFull int, storage []test.StorageMiner) ([]test.TestNode, []test.TestStorageNode) { - ctx := context.Background() - mn := mocknet.New(ctx) - - fulls := make([]test.TestNode, nFull) - storers := make([]test.TestStorageNode, 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 - var genaccs []genesis.Actor - var maddrs []address.Address - var presealDirs []string - var keys []*wallet.Key - var pidKeys []crypto.PrivKey - 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) - } - - preseals := storage[i].Preseal - if preseals == test.PresealGenesis { - preseals = test.GenesisPreseals - } - - genm, k, err := mockstorage.PreSeal(2048, 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(400_000_000_000), types.NewInt(build.FilecoinPrecision)), - Meta: (&genesis.AccountMeta{Owner: wk.Address}).ActorMeta(), - }) - - keys = append(keys, wk) - pidKeys = append(pidKeys, pk) - presealDirs = append(presealDirs, tdir) - maddrs = append(maddrs, maddr) - genms = append(genms, *genm) - } - templ := &genesis.Template{ - Accounts: genaccs, - Miners: genms, - Timestamp: uint64(time.Now().Unix()) - (build.BlockDelaySecs * 20000), - VerifregRootKey: gen.DefaultVerifregRootkeyActor, - } - - // END PRESEAL SECTION - - for i := 0; i < nFull; i++ { - var genesis node.Option - if i == 0 { - genesis = node.Override(new(modules.Genesis), modtest.MakeGenesisMem(&genbuf, *templ)) - } else { - genesis = node.Override(new(modules.Genesis), modules.LoadGenesis(genbuf.Bytes())) - } - - var err error - // TODO: Don't ignore stop - _, err = node.New(ctx, - node.FullAPI(&fulls[i].FullNode), - node.Online(), - node.Repo(repo.NewMemory(nil)), - node.MockHost(mn), - node.Test(), - - node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), - - genesis, - ) - if err != nil { - t.Fatalf("%+v", 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, - } - } - - storers[i] = testStorageNode(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(build.DefaultSectorSize(), sectors), nil - }), - node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), - node.Unset(new(*sectorstorage.Manager)), - )) - } - - if err := mn.LinkAll(); err != nil { - t.Fatal(err) - } - - if len(storers) > 0 { - // Mine 2 blocks to setup some CE stuff in some actors - var wait sync.Mutex - wait.Lock() - - storers[0].MineOne(ctx, miner.MineReq{Done: func(bool, error) { - wait.Unlock() - }}) - wait.Lock() - storers[0].MineOne(ctx, miner.MineReq{Done: func(bool, error) { - wait.Unlock() - }}) - wait.Lock() - } - - return fulls, storers -} - func TestAPI(t *testing.T) { - test.TestApis(t, builder) -} - -func rpcBuilder(t *testing.T, nFull int, storage []test.StorageMiner) ([]test.TestNode, []test.TestStorageNode) { - fullApis, storaApis := builder(t, nFull, storage) - fulls := make([]test.TestNode, nFull) - storers := make([]test.TestStorageNode, len(storage)) - - for i, a := range fullApis { - rpcServer := jsonrpc.NewServer() - rpcServer.Register("Filecoin", a) - testServ := httptest.NewServer(rpcServer) // todo: close - - var err error - fulls[i].FullNode, _, err = client.NewFullNodeRPC("ws://"+testServ.Listener.Addr().String(), nil) - if err != nil { - t.Fatal(err) - } - } - - for i, a := range storaApis { - rpcServer := jsonrpc.NewServer() - rpcServer.Register("Filecoin", a) - testServ := httptest.NewServer(rpcServer) // todo: close - - var err error - storers[i].StorageMiner, _, err = client.NewStorageMinerRPC("ws://"+testServ.Listener.Addr().String(), nil) - if err != nil { - t.Fatal(err) - } - storers[i].MineOne = a.MineOne - } - - return fulls, storers + test.TestApis(t, builder.Builder) } func TestAPIRPC(t *testing.T) { - test.TestApis(t, rpcBuilder) + test.TestApis(t, builder.RPCBuilder) } func TestAPIDealFlow(t *testing.T) { @@ -501,16 +44,16 @@ func TestAPIDealFlow(t *testing.T) { logging.SetLogLevel("storageminer", "ERROR") t.Run("TestDealFlow", func(t *testing.T) { - test.TestDealFlow(t, mockSbBuilder, 10*time.Millisecond, false, false) + test.TestDealFlow(t, builder.MockSbBuilder, 10*time.Millisecond, false, false) }) t.Run("WithExportedCAR", func(t *testing.T) { - test.TestDealFlow(t, mockSbBuilder, 10*time.Millisecond, true, false) + test.TestDealFlow(t, builder.MockSbBuilder, 10*time.Millisecond, true, false) }) t.Run("TestDoubleDealFlow", func(t *testing.T) { - test.TestDoubleDealFlow(t, mockSbBuilder, 10*time.Millisecond) + test.TestDoubleDealFlow(t, builder.MockSbBuilder, 10*time.Millisecond) }) t.Run("TestFastRetrievalDealFlow", func(t *testing.T) { - test.TestFastRetrievalDealFlow(t, mockSbBuilder, 10*time.Millisecond) + test.TestFastRetrievalDealFlow(t, builder.MockSbBuilder, 10*time.Millisecond) }) } @@ -528,15 +71,15 @@ func TestAPIDealFlowReal(t *testing.T) { saminer.PreCommitChallengeDelay = 5 t.Run("basic", func(t *testing.T) { - test.TestDealFlow(t, builder, time.Second, false, false) + test.TestDealFlow(t, builder.Builder, time.Second, false, false) }) t.Run("fast-retrieval", func(t *testing.T) { - test.TestDealFlow(t, builder, time.Second, false, true) + test.TestDealFlow(t, builder.Builder, time.Second, false, true) }) t.Run("retrieval-second", func(t *testing.T) { - test.TestSenondDealRetrieval(t, builder, time.Second) + test.TestSenondDealRetrieval(t, builder.Builder, time.Second) }) } @@ -547,7 +90,7 @@ func TestDealMining(t *testing.T) { logging.SetLogLevel("sub", "ERROR") logging.SetLogLevel("storageminer", "ERROR") - test.TestDealMining(t, mockSbBuilder, 50*time.Millisecond, false) + test.TestDealMining(t, builder.MockSbBuilder, 50*time.Millisecond, false) } func TestPledgeSectors(t *testing.T) { @@ -558,11 +101,11 @@ func TestPledgeSectors(t *testing.T) { logging.SetLogLevel("storageminer", "ERROR") t.Run("1", func(t *testing.T) { - test.TestPledgeSector(t, mockSbBuilder, 50*time.Millisecond, 1) + test.TestPledgeSector(t, builder.MockSbBuilder, 50*time.Millisecond, 1) }) t.Run("100", func(t *testing.T) { - test.TestPledgeSector(t, mockSbBuilder, 50*time.Millisecond, 100) + test.TestPledgeSector(t, builder.MockSbBuilder, 50*time.Millisecond, 100) }) t.Run("1000", func(t *testing.T) { @@ -570,7 +113,7 @@ func TestPledgeSectors(t *testing.T) { t.Skip("skipping test in short mode") } - test.TestPledgeSector(t, mockSbBuilder, 50*time.Millisecond, 1000) + test.TestPledgeSector(t, builder.MockSbBuilder, 50*time.Millisecond, 1000) }) } @@ -585,7 +128,7 @@ func TestWindowedPost(t *testing.T) { logging.SetLogLevel("sub", "ERROR") logging.SetLogLevel("storageminer", "ERROR") - test.TestWindowPost(t, mockSbBuilder, 2*time.Millisecond, 10) + test.TestWindowPost(t, builder.MockSbBuilder, 2*time.Millisecond, 10) } func TestCCUpgrade(t *testing.T) { @@ -595,7 +138,7 @@ func TestCCUpgrade(t *testing.T) { logging.SetLogLevel("sub", "ERROR") logging.SetLogLevel("storageminer", "ERROR") - test.TestCCUpgrade(t, mockSbBuilder, 5*time.Millisecond) + test.TestCCUpgrade(t, builder.MockSbBuilder, 5*time.Millisecond) } func TestPaymentChannels(t *testing.T) { @@ -606,5 +149,5 @@ func TestPaymentChannels(t *testing.T) { logging.SetLogLevel("pubsub", "ERROR") logging.SetLogLevel("storageminer", "ERROR") - test.TestPaymentChannels(t, mockSbBuilder, 5*time.Millisecond) + test.TestPaymentChannels(t, builder.MockSbBuilder, 5*time.Millisecond) } diff --git a/node/test/builder.go b/node/test/builder.go new file mode 100644 index 000000000..2498d68ef --- /dev/null +++ b/node/test/builder.go @@ -0,0 +1,498 @@ +package test + +import ( + "bytes" + "context" + "crypto/rand" + "io/ioutil" + "net" + "net/http/httptest" + "sync" + "testing" + "time" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-jsonrpc" + "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/test" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/gen" + genesis2 "github.com/filecoin-project/lotus/chain/gen/genesis" + "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" + miner2 "github.com/filecoin-project/lotus/miner" + "github.com/filecoin-project/lotus/node" + "github.com/filecoin-project/lotus/node/modules" + testing2 "github.com/filecoin-project/lotus/node/modules/testing" + "github.com/filecoin-project/lotus/node/repo" + "github.com/filecoin-project/lotus/storage/mockstorage" + "github.com/filecoin-project/specs-actors/actors/abi" + "github.com/filecoin-project/specs-actors/actors/abi/big" + "github.com/filecoin-project/specs-actors/actors/builtin" + "github.com/filecoin-project/specs-actors/actors/builtin/miner" + "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" + "github.com/stretchr/testify/require" +) + +func CreateTestStorageNode(ctx context.Context, t *testing.T, waddr address.Address, act address.Address, pk crypto.PrivKey, tnd test.TestNode, mn mocknet.Mocknet, opts node.Option) test.TestStorageNode { + 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("/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 < test.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(&miner.ChangePeerIDParams{NewID: abi.PeerID(peerid)}) + require.NoError(t, err) + + msg := &types.Message{ + To: act, + From: waddr, + Method: builtin.MethodsMiner.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 miner2.MineReq) + // TODO: use stop + _, err = node.New(ctx, + node.StorageMiner(&minerapi), + node.Online(), + node.Repo(r), + node.Test(), + + node.MockHost(mn), + + node.Override(new(api.FullNode), tnd), + node.Override(new(*miner2.Miner), miner2.NewTestMiner(mineBlock, act)), + + opts, + ) + if err != nil { + t.Fatalf("failed to construct node: %v", err) + } + + /*// 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 miner2.MineReq) error { + select { + case mineBlock <- req: + return nil + case <-ctx.Done(): + return ctx.Err() + } + } + + return test.TestStorageNode{StorageMiner: minerapi, MineOne: mineOne} +} + +func Builder(t *testing.T, nFull int, storage []test.StorageMiner) ([]test.TestNode, []test.TestStorageNode) { + ctx := context.Background() + mn := mocknet.New(ctx) + + fulls := make([]test.TestNode, nFull) + storers := make([]test.TestStorageNode, 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 + var maddrs []address.Address + var genaccs []genesis.Actor + var 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, test.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, + Timestamp: uint64(time.Now().Unix() - 10000), // some time sufficiently far in the past + VerifregRootKey: gen.DefaultVerifregRootkeyActor, + } + + // END PRESEAL SECTION + + for i := 0; i < nFull; 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())) + } + + var err error + // TODO: Don't ignore stop + _, err = node.New(ctx, + node.FullAPI(&fulls[i].FullNode), + node.Online(), + node.Repo(repo.NewMemory(nil)), + node.MockHost(mn), + node.Test(), + + genesis, + ) + if 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 + + storers[i] = CreateTestStorageNode(ctx, t, wa, genMiner, pk, f, mn, node.Options()) + if err := storers[i].StorageAddLocal(ctx, presealDirs[i]); err != nil { + t.Fatalf("%+v", err) + } + /* + sma := storers[i].StorageMiner.(*impl.StorageMinerAPI) + + psd := presealDirs[i] + */ + } + + if err := mn.LinkAll(); err != nil { + t.Fatal(err) + } + + if len(storers) > 0 { + // Mine 2 blocks to setup some CE stuff in some actors + var wait sync.Mutex + wait.Lock() + + _ = storers[0].MineOne(ctx, miner2.MineReq{Done: func(bool, error) { + wait.Unlock() + }}) + wait.Lock() + _ = storers[0].MineOne(ctx, miner2.MineReq{Done: func(bool, error) { + wait.Unlock() + }}) + wait.Lock() + } + + return fulls, storers +} + +func MockSbBuilder(t *testing.T, nFull int, storage []test.StorageMiner) ([]test.TestNode, []test.TestStorageNode) { + ctx := context.Background() + mn := mocknet.New(ctx) + + fulls := make([]test.TestNode, nFull) + storers := make([]test.TestStorageNode, 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 + var genaccs []genesis.Actor + var maddrs []address.Address + var keys []*wallet.Key + var 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 == test.PresealGenesis { + preseals = test.GenesisPreseals + } + + genm, k, err := mockstorage.PreSeal(2048, 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(400000000000), 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, + Timestamp: uint64(time.Now().Unix()) - (build.BlockDelaySecs * 20000), + VerifregRootKey: gen.DefaultVerifregRootkeyActor, + } + + // END PRESEAL SECTION + + for i := 0; i < nFull; 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())) + } + + var err error + // TODO: Don't ignore stop + _, err = node.New(ctx, + node.FullAPI(&fulls[i].FullNode), + node.Online(), + node.Repo(repo.NewMemory(nil)), + node.MockHost(mn), + node.Test(), + + node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), + + genesis, + ) + if err != nil { + t.Fatalf("%+v", 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, + } + } + + storers[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(build.DefaultSectorSize(), sectors), nil + }), + node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), + node.Unset(new(*sectorstorage.Manager)), + )) + } + + if err := mn.LinkAll(); err != nil { + t.Fatal(err) + } + + if len(storers) > 0 { + // Mine 2 blocks to setup some CE stuff in some actors + var wait sync.Mutex + wait.Lock() + + _ = storers[0].MineOne(ctx, miner2.MineReq{Done: func(bool, error) { + wait.Unlock() + }}) + wait.Lock() + _ = storers[0].MineOne(ctx, miner2.MineReq{Done: func(bool, error) { + wait.Unlock() + }}) + wait.Lock() + } + + return fulls, storers +} + +func RPCBuilder(t *testing.T, nFull int, storage []test.StorageMiner) ([]test.TestNode, []test.TestStorageNode) { + return rpcWithBuilder(t, Builder, nFull, storage) +} + +func RPCMockSbBuilder(t *testing.T, nFull int, storage []test.StorageMiner) ([]test.TestNode, []test.TestStorageNode) { + return rpcWithBuilder(t, MockSbBuilder, nFull, storage) +} + +func rpcWithBuilder(t *testing.T, b test.APIBuilder, nFull int, storage []test.StorageMiner) ([]test.TestNode, []test.TestStorageNode) { + fullApis, storaApis := b(t, nFull, storage) + fulls := make([]test.TestNode, nFull) + storers := make([]test.TestStorageNode, len(storage)) + + for i, a := range fullApis { + rpcServer := jsonrpc.NewServer() + rpcServer.Register("Filecoin", a) + testServ := httptest.NewServer(rpcServer) // todo: close + + addr := testServ.Listener.Addr() + listenAddr := "ws://" + addr.String() + var err error + fulls[i].FullNode, _, err = client.NewFullNodeRPC(listenAddr, nil) + if err != nil { + t.Fatal(err) + } + ma, err := parseWSSMultiAddr(addr) + if err != nil { + t.Fatal(err) + } + fulls[i].ListenAddr = ma + } + + for i, a := range storaApis { + rpcServer := jsonrpc.NewServer() + rpcServer.Register("Filecoin", a) + testServ := httptest.NewServer(rpcServer) // todo: close + + addr := testServ.Listener.Addr() + listenAddr := "ws://" + addr.String() + var err error + storers[i].StorageMiner, _, err = client.NewStorageMinerRPC(listenAddr, nil) + if err != nil { + t.Fatal(err) + } + ma, err := parseWSSMultiAddr(addr) + if err != nil { + t.Fatal(err) + } + storers[i].ListenAddr = ma + storers[i].MineOne = a.MineOne + } + + return fulls, storers +} + +func parseWSSMultiAddr(addr net.Addr) (multiaddr.Multiaddr, error) { + host, port, err := net.SplitHostPort(addr.String()) + if err != nil { + return nil, err + } + ma, err := multiaddr.NewMultiaddr("/ip4/" + host + "/" + addr.Network() + "/" + port + "/wss") + if err != nil { + return nil, err + } + return ma, nil +}