From 043b63d8d8c5df0308b840f88cb3bf132c8c6304 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Thu, 13 Aug 2020 16:37:09 -0400 Subject: [PATCH 01/13] feat: payment channel CLI tests --- api/test/blockminer.go | 56 +++++ api/test/ccupgrade.go | 2 +- api/test/deals.go | 8 +- api/test/mining.go | 4 +- api/test/paych.go | 83 +------ api/test/test.go | 16 +- api/test/util.go | 36 +++ api/test/window_post.go | 4 +- cli/cmd.go | 27 +++ cli/paych.go | 25 +- cli/paych_test.go | 228 ++++++++++++++++++ node/node_test.go | 495 ++------------------------------------- node/test/builder.go | 498 ++++++++++++++++++++++++++++++++++++++++ 13 files changed, 903 insertions(+), 579 deletions(-) create mode 100644 api/test/blockminer.go create mode 100644 api/test/util.go create mode 100644 cli/paych_test.go create mode 100644 node/test/builder.go 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 +} From 2b1583167227d0bfa29a63ccdc7e809ebeed4b2c Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Tue, 18 Aug 2020 16:07:04 -0400 Subject: [PATCH 02/13] docs: payment channels --- documentation/en/.library.json | 7 +++ documentation/en/payment-channels.md | 82 ++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 documentation/en/payment-channels.md diff --git a/documentation/en/.library.json b/documentation/en/.library.json index 4d348c145..85282779c 100644 --- a/documentation/en/.library.json +++ b/documentation/en/.library.json @@ -143,6 +143,13 @@ "value": null, "posts": [] }, + { + "title": "Payment Channels", + "slug": "en+payment-channels", + "github": "en/payment-channels.md", + "value": null, + "posts": [] + }, { "title": "Command Line Interface", "slug": "en+cli", diff --git a/documentation/en/payment-channels.md b/documentation/en/payment-channels.md new file mode 100644 index 000000000..b1729dc47 --- /dev/null +++ b/documentation/en/payment-channels.md @@ -0,0 +1,82 @@ +# Payment Channels + +Payment channels are used to transfer funds between two actors. + +In lotus a payment channel is created when a client wants to fetch data from a provider. +The client sends vouchers for the payment channel, and the provider sends data in response. + +The payment channel is created on-chain with an initial amount. +Vouchers allow the client and the provider to exchange funds incrementally off-chain. +The provider can submit vouchers to chain at any stage. +Either party to the payment channel can settle the payment channel on chain. +After a settlement period (currently 12 hours) either party to the payment channel can call collect on chain. +Collect sends the value of submitted vouchers to the channel recipient (the provider), and refunds the remaining channel balance to the channel creator (the client). + +Vouchers have a lane, a nonce and a value, where vouchers with a higher nonce supersede vouchers with a lower nonce in the same lane. +Each deal is created on a different lane. + +## Using the CLI + +For example a client creates a payment channel to a provider with value 10. + +```sh +$ lotus paych get 10 + +``` + +The client creates a voucher in lane 0 (implied) with nonce 1 (implied) and value 2. + +```sh +$ lotus paych voucher create 2 + +``` + +The client sends the voucher to the provider and the provider adds the voucher to their local store. + +```sh +$ lotus paych voucher add +``` + +The provider sends some data to the client. + +The client creates a voucher in lane 0 (implied) with nonce 2 (implied) and value 4. + +```sh +$ lotus paych voucher create 4 + +``` + +The client sends the voucher to the provider and the provider adds the voucher and sends back more data. +etc. + +The client can add value to the channel after it has been created by calling `paych get` with the same client and provider addresses. + +```sh +$ lotus paych get 5 + # Same address as above. Channel now has 15 +``` + +Once the client has received all their data, they settle the channel. + +```sh +$ lotus paych settle +``` + +The provider can submit vouchers to chain (note that lotus does this automatically when it sees a settle message appear on chain). +The provider may have received many vouchers with incrementally higher values. +The provider should submit the best vouchers. Note that there will be one best voucher for each lane. + +```sh +$ lotus paych voucher best-spendable + + + + +$ lotus paych voucher submit +``` + +Once the settlement period is over, either the client or provider can call collect to disburse funds. + +```sh +$ lotus paych collect +``` From c7a6e32c962b74790561309746c0cd967801d3c6 Mon Sep 17 00:00:00 2001 From: vyzo Date: Wed, 19 Aug 2020 13:20:16 +0300 Subject: [PATCH 03/13] ignore, don't reject messages with nonce that is too low we seem to have a problem with this leading to many rejected messages and negative scores for potentially innocent peers. --- chain/sub/incoming.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/chain/sub/incoming.go b/chain/sub/incoming.go index 8bc8cce0e..561d2c3f1 100644 --- a/chain/sub/incoming.go +++ b/chain/sub/incoming.go @@ -546,7 +546,11 @@ func (mv *MessageValidator) Validate(ctx context.Context, pid peer.ID, msg *pubs ) stats.Record(ctx, metrics.MessageValidationFailure.M(1)) switch { - case xerrors.Is(err, messagepool.ErrBroadcastAnyway) || xerrors.Is(err, messagepool.ErrRBFTooLowPremium): + case xerrors.Is(err, messagepool.ErrBroadcastAnyway): + fallthrough + case xerrors.Is(err, messagepool.ErrRBFTooLowPremium): + fallthrough + case xerrors.Is(err, messagepool.ErrNonceTooLow): return pubsub.ValidationIgnore default: return pubsub.ValidationReject From 20cb9425cce830f4a53fd4d399b661d6a5bf6951 Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Wed, 19 Aug 2020 08:43:06 -0400 Subject: [PATCH 04/13] docs: paych edits --- documentation/en/payment-channels.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/documentation/en/payment-channels.md b/documentation/en/payment-channels.md index b1729dc47..44f9affd9 100644 --- a/documentation/en/payment-channels.md +++ b/documentation/en/payment-channels.md @@ -2,7 +2,7 @@ Payment channels are used to transfer funds between two actors. -In lotus a payment channel is created when a client wants to fetch data from a provider. +For example in lotus a payment channel is created when a client wants to fetch data from a provider. The client sends vouchers for the payment channel, and the provider sends data in response. The payment channel is created on-chain with an initial amount. @@ -15,9 +15,11 @@ Collect sends the value of submitted vouchers to the channel recipient (the prov Vouchers have a lane, a nonce and a value, where vouchers with a higher nonce supersede vouchers with a lower nonce in the same lane. Each deal is created on a different lane. +Note that payment channels and vouchers can be used for any situation in which two parties need to incrementally transfer value between each other off-chain. + ## Using the CLI -For example a client creates a payment channel to a provider with value 10. +For example a client creates a payment channel to a provider with value 10 FIL. ```sh $ lotus paych get 10 @@ -56,7 +58,9 @@ $ lotus paych get 5 # Same address as above. Channel now has 15 ``` -Once the client has received all their data, they settle the channel. +Once the client has received all their data, they may settle the channel. +Note that settlement doesn't have to be done immediately. +For example the client may keep the channel open as long as it wants to continue making deals with the provider. ```sh $ lotus paych settle From f1632376e7561d60e4c898bf32f6bca7a9095693 Mon Sep 17 00:00:00 2001 From: Travis Person Date: Wed, 19 Aug 2020 19:18:36 +0000 Subject: [PATCH 05/13] lotus-shed: add simple jwt creation support --- cmd/lotus-shed/jwt.go | 89 +++++++++++++++++++++++++++++++++++++++ cmd/lotus-shed/keyinfo.go | 2 +- cmd/lotus-shed/main.go | 1 + node/modules/core.go | 7 +-- 4 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 cmd/lotus-shed/jwt.go diff --git a/cmd/lotus-shed/jwt.go b/cmd/lotus-shed/jwt.go new file mode 100644 index 000000000..d37359f71 --- /dev/null +++ b/cmd/lotus-shed/jwt.go @@ -0,0 +1,89 @@ +package main + +import ( + "crypto/rand" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + + "github.com/gbrlsnchs/jwt/v3" + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/lotus/api/apistruct" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/node/modules" +) + +var jwtCmd = &cli.Command{ + Name: "jwt", + Usage: "work with lotus jwt secrets and tokens", + Description: `The subcommands of jwt provide helpful tools for working with jwt files without + having to run the lotus daemon.`, + Subcommands: []*cli.Command{ + jwtNewCmd, + }, +} + +var jwtNewCmd = &cli.Command{ + Name: "new", + Usage: "create a new jwt secret and token for lotus", + ArgsUsage: "", + Description: `Jwt tokens are used to authenticate api requests to the lotus daemon. + + The created jwt token have full privileges and should not be shared.`, + Flags: []cli.Flag{}, + Action: func(cctx *cli.Context) error { + if !cctx.Args().Present() { + return fmt.Errorf("please specify a name") + } + + keyName := cctx.Args().First() + + sk, err := ioutil.ReadAll(io.LimitReader(rand.Reader, 32)) + if err != nil { + return err + } + + keyInfo := types.KeyInfo{ + Type: modules.KTJwtHmacSecret, + PrivateKey: sk, + } + + p := modules.JwtPayload{ + Allow: apistruct.AllPermissions, + } + + token, err := jwt.Sign(&p, jwt.NewHS256(keyInfo.PrivateKey)) + if err != nil { + return err + } + + filename := fmt.Sprintf("jwt-%s.jwts", keyName) + file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return err + } + + defer func() { + if err := file.Close(); err != nil { + log.Warnf("failed to close output file: %w", err) + } + }() + + bytes, err := json.Marshal(keyInfo) + if err != nil { + return err + } + + encoded := hex.EncodeToString(bytes) + if _, err := file.Write([]byte(encoded)); err != nil { + return err + } + + filenameToken := fmt.Sprintf("jwt-%s.token", keyName) + return ioutil.WriteFile(filenameToken, token, 0600) + }, +} diff --git a/cmd/lotus-shed/keyinfo.go b/cmd/lotus-shed/keyinfo.go index 028ead413..0f608d858 100644 --- a/cmd/lotus-shed/keyinfo.go +++ b/cmd/lotus-shed/keyinfo.go @@ -325,7 +325,7 @@ var keyinfoNewCmd = &cli.Command{ filename = strings.ReplaceAll(filename, "", keyAddr) filename = strings.ReplaceAll(filename, "", keyType) - file, err := os.Create(filename) + file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return err } diff --git a/cmd/lotus-shed/main.go b/cmd/lotus-shed/main.go index c37b93a42..4126fa649 100644 --- a/cmd/lotus-shed/main.go +++ b/cmd/lotus-shed/main.go @@ -19,6 +19,7 @@ func main() { base16Cmd, bitFieldCmd, keyinfoCmd, + jwtCmd, noncefix, bigIntParseCmd, staterootStatsCmd, diff --git a/node/modules/core.go b/node/modules/core.go index 84179bd63..1f21f2720 100644 --- a/node/modules/core.go +++ b/node/modules/core.go @@ -37,8 +37,9 @@ func RecordValidator(ps peerstore.Peerstore) record.Validator { } const JWTSecretName = "auth-jwt-private" //nolint:gosec +const KTJwtHmacSecret = "jwt-hmac-secret" -type jwtPayload struct { +type JwtPayload struct { Allow []auth.Permission } @@ -54,7 +55,7 @@ func APISecret(keystore types.KeyStore, lr repo.LockedRepo) (*dtypes.APIAlg, err } key = types.KeyInfo{ - Type: "jwt-hmac-secret", + Type: KTJwtHmacSecret, PrivateKey: sk, } @@ -63,7 +64,7 @@ func APISecret(keystore types.KeyStore, lr repo.LockedRepo) (*dtypes.APIAlg, err } // TODO: make this configurable - p := jwtPayload{ + p := JwtPayload{ Allow: apistruct.AllPermissions, } From 3df85ca784de27eb3b6041bcaa061054a7f04c26 Mon Sep 17 00:00:00 2001 From: frrist Date: Wed, 19 Aug 2020 11:50:29 -0700 Subject: [PATCH 06/13] refactor(chainwatch): add new values to sector_info table --- cmd/lotus-chainwatch/processor/miner.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cmd/lotus-chainwatch/processor/miner.go b/cmd/lotus-chainwatch/processor/miner.go index 19fa3da95..6e4d40dec 100644 --- a/cmd/lotus-chainwatch/processor/miner.go +++ b/cmd/lotus-chainwatch/processor/miner.go @@ -88,6 +88,8 @@ create table if not exists sector_info verified_deal_weight text not null, initial_pledge text not null, + expected_day_reward text not null, + expected_storage_pledge text not null, constraint sector_info_pk primary key (miner_id, sector_id, sealed_cid) @@ -307,6 +309,7 @@ func (p *Processor) storeMinerPreCommitInfo(ctx context.Context, miners []minerA } stmt, err := tx.Prepare(`copy spi (miner_id, sector_id, sealed_cid, state_root, seal_rand_epoch, expiration_epoch, precommit_deposit, precommit_epoch, deal_weight, verified_deal_weight, is_replace_capacity, replace_sector_deadline, replace_sector_partition, replace_sector_number) from STDIN`) + if err != nil { return xerrors.Errorf("Failed to prepare miner precommit info statement: %w", err) } @@ -431,7 +434,7 @@ func (p *Processor) storeMinerSectorInfo(ctx context.Context, miners []minerActo return xerrors.Errorf("Failed to create temp table for sector_: %w", err) } - stmt, err := tx.Prepare(`copy si (miner_id, sector_id, sealed_cid, state_root, activation_epoch, expiration_epoch, deal_weight, verified_deal_weight, initial_pledge) from STDIN`) + stmt, err := tx.Prepare(`copy si (miner_id, sector_id, sealed_cid, state_root, activation_epoch, expiration_epoch, deal_weight, verified_deal_weight, initial_pledge, expected_day_reward, expected_storage_pledge) from STDIN`) if err != nil { return xerrors.Errorf("Failed to prepare miner sector info statement: %w", err) } @@ -463,6 +466,8 @@ func (p *Processor) storeMinerSectorInfo(ctx context.Context, miners []minerActo added.DealWeight.String(), added.VerifiedDealWeight.String(), added.InitialPledge.String(), + added.ExpectedDayReward.String(), + added.ExpectedStoragePledge.String(), ); err != nil { return err } From fecaeda382310f2625aec6ed529c4e705a7ab504 Mon Sep 17 00:00:00 2001 From: frrist Date: Wed, 19 Aug 2020 12:55:53 -0700 Subject: [PATCH 07/13] feat(chainwatch): reward actor smoothing estimate --- cmd/lotus-chainwatch/processor/reward.go | 60 ++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/cmd/lotus-chainwatch/processor/reward.go b/cmd/lotus-chainwatch/processor/reward.go index 3d5eaa7b4..5411cb68a 100644 --- a/cmd/lotus-chainwatch/processor/reward.go +++ b/cmd/lotus-chainwatch/processor/reward.go @@ -3,6 +3,7 @@ package processor import ( "bytes" "context" + "github.com/filecoin-project/specs-actors/actors/util/smoothing" "time" "golang.org/x/sync/errgroup" @@ -24,6 +25,8 @@ type rewardActorInfo struct { // base reward in attofil for each block found during this epoch baseBlockReward big.Int + + epochSmoothingEstimate *smoothing.FilterEstimate } func (p *Processor) setupRewards() error { @@ -53,6 +56,15 @@ create table if not exists chain_power primary key, baseline_power text not null ); + +create table if not exists reward_smoothing_estimates +( + state_root text not null + constraint reward_smoothing_estimates_pk + primary key, + position_estimate text not null, + velocity_estimate text not null +); `); err != nil { return err } @@ -103,6 +115,7 @@ func (p *Processor) processRewardActors(ctx context.Context, rewardTips ActorTip rw.baseBlockReward = rewardActorState.ThisEpochReward rw.baselinePower = rewardActorState.ThisEpochBaselinePower + rw.epochSmoothingEstimate = rewardActorState.ThisEpochRewardSmoothed out = append(out, rw) } } @@ -162,6 +175,13 @@ func (p *Processor) persistRewardActors(ctx context.Context, rewards []rewardAct return nil }) + grp.Go(func() error { + if err := p.storeRewardSmoothingEstimates(rewards); err != nil { + return err + } + return nil + }) + return grp.Wait() } @@ -243,3 +263,43 @@ func (p *Processor) storeBaseBlockReward(rewards []rewardActorInfo) error { return nil } + +func (p *Processor) storeRewardSmoothingEstimates(rewards []rewardActorInfo) error { + tx, err := p.db.Begin() + if err != nil { + return xerrors.Errorf("begin reward_smoothing_estimates tx: %w", err) + } + + if _, err := tx.Exec(`create temp table rse (like reward_smoothing_estimates) on commit drop`); err != nil { + return xerrors.Errorf("prep reward_smoothing_estimates: %w", err) + } + + stmt, err := tx.Prepare(`copy rse (state_root, position_estimate, velocity_estimate) from stdin;`) + if err != nil { + return xerrors.Errorf("prepare tmp reward_smoothing_estimates: %w", err) + } + + for _, rewardState := range rewards { + if _, err := stmt.Exec( + rewardState.common.stateroot.String(), + rewardState.epochSmoothingEstimate.PositionEstimate.String(), + rewardState.epochSmoothingEstimate.VelocityEstimate.String(), + ); err != nil { + return xerrors.Errorf("failed to store smoothing estimate: %w", err) + } + } + + if err := stmt.Close(); err != nil { + return xerrors.Errorf("close prepared reward_smoothing_estimates: %w", err) + } + + if _, err := tx.Exec(`insert into reward_smoothing_estimates select * from rse on conflict do nothing`); err != nil { + return xerrors.Errorf("insert reward_smoothing_estimates from tmp: %w", err) + } + + if err := tx.Commit(); err != nil { + return xerrors.Errorf("commit reward_smoothing_estimates tx: %w", err) + } + + return nil +} From 05f1a23c949783d1b0cb299ab8e1b8ed817a6739 Mon Sep 17 00:00:00 2001 From: frrist Date: Wed, 19 Aug 2020 13:34:58 -0700 Subject: [PATCH 08/13] feat(chainwatch): power actor smoothing estimate --- cmd/lotus-chainwatch/processor/power.go | 135 ++++++++++++++++++++ cmd/lotus-chainwatch/processor/processor.go | 12 ++ cmd/lotus-chainwatch/processor/reward.go | 4 +- 3 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 cmd/lotus-chainwatch/processor/power.go diff --git a/cmd/lotus-chainwatch/processor/power.go b/cmd/lotus-chainwatch/processor/power.go new file mode 100644 index 000000000..daf17a884 --- /dev/null +++ b/cmd/lotus-chainwatch/processor/power.go @@ -0,0 +1,135 @@ +package processor + +import ( + "bytes" + "context" + "time" + + "golang.org/x/xerrors" + + "github.com/filecoin-project/specs-actors/actors/builtin" + "github.com/filecoin-project/specs-actors/actors/builtin/power" + "github.com/filecoin-project/specs-actors/actors/util/smoothing" +) + +type powerActorInfo struct { + common actorInfo + + epochSmoothingEstimate *smoothing.FilterEstimate +} + +func (p *Processor) setupPower() error { + tx, err := p.db.Begin() + if err != nil { + return err + } + + if _, err := tx.Exec(` +create table if not exists power_smoothing_estimates +( + state_root text not null + constraint power_smoothing_estimates_pk + primary key, + position_estimate text not null, + velocity_estimate text not null +); +`); err != nil { + return err + } + + return tx.Commit() +} + +func (p *Processor) HandlePowerChanges(ctx context.Context, powerTips ActorTips) error { + powerChanges, err := p.processPowerActors(ctx, powerTips) + if err != nil { + return xerrors.Errorf("Failed to process power actors: %w", err) + } + + if err := p.persistPowerActors(ctx, powerChanges); err != nil { + return err + } + + return nil +} + +func (p *Processor) processPowerActors(ctx context.Context, powerTips ActorTips) ([]powerActorInfo, error) { + start := time.Now() + defer func() { + log.Debugw("Processed Power Actors", "duration", time.Since(start).String()) + }() + + var out []powerActorInfo + for tipset, powers := range powerTips { + for _, act := range powers { + var pw powerActorInfo + pw.common = act + + powerActor, err := p.node.StateGetActor(ctx, builtin.StoragePowerActorAddr, tipset) + if err != nil { + return nil, xerrors.Errorf("get power state (@ %s): %w", pw.common.stateroot.String(), err) + } + + powerStateRaw, err := p.node.ChainReadObj(ctx, powerActor.Head) + if err != nil { + return nil, xerrors.Errorf("read state obj (@ %s): %w", pw.common.stateroot.String(), err) + } + + var powerActorState power.State + if err := powerActorState.UnmarshalCBOR(bytes.NewReader(powerStateRaw)); err != nil { + return nil, xerrors.Errorf("unmarshal state (@ %s): %w", pw.common.stateroot.String(), err) + } + + pw.epochSmoothingEstimate = powerActorState.ThisEpochQAPowerSmoothed + out = append(out, pw) + } + } + + return out, nil +} + +func (p *Processor) persistPowerActors(ctx context.Context, powers []powerActorInfo) error { + // NB: use errgroup when there is more than a single store operation + return p.storePowerSmoothingEstimates(powers) +} + +func (p *Processor) storePowerSmoothingEstimates(powers []powerActorInfo) error { + tx, err := p.db.Begin() + if err != nil { + return xerrors.Errorf("begin power_smoothing_estimates tx: %w", err) + } + + if _, err := tx.Exec(`create temp table rse (like power_smoothing_estimates) on commit drop`); err != nil { + return xerrors.Errorf("prep power_smoothing_estimates: %w", err) + } + + stmt, err := tx.Prepare(`copy rse (state_root, position_estimate, velocity_estimate) from stdin;`) + if err != nil { + return xerrors.Errorf("prepare tmp power_smoothing_estimates: %w", err) + } + + for _, powerState := range powers { + if _, err := stmt.Exec( + powerState.common.stateroot.String(), + powerState.epochSmoothingEstimate.PositionEstimate.String(), + powerState.epochSmoothingEstimate.VelocityEstimate.String(), + ); err != nil { + return xerrors.Errorf("failed to store smoothing estimate: %w", err) + } + } + + if err := stmt.Close(); err != nil { + return xerrors.Errorf("close prepared power_smoothing_estimates: %w", err) + } + + if _, err := tx.Exec(`insert into power_smoothing_estimates select * from rse on conflict do nothing`); err != nil { + return xerrors.Errorf("insert power_smoothing_estimates from tmp: %w", err) + } + + if err := tx.Commit(); err != nil { + return xerrors.Errorf("commit power_smoothing_estimates tx: %w", err) + } + + return nil + +} diff --git a/cmd/lotus-chainwatch/processor/processor.go b/cmd/lotus-chainwatch/processor/processor.go index 417cdd03b..2f70f1cb3 100644 --- a/cmd/lotus-chainwatch/processor/processor.go +++ b/cmd/lotus-chainwatch/processor/processor.go @@ -88,6 +88,10 @@ func (p *Processor) setupSchemas() error { return err } + if err := p.setupPower(); err != nil { + return err + } + return nil } @@ -166,6 +170,14 @@ func (p *Processor) Start(ctx context.Context) { return nil }) + grp.Go(func() error { + if err := p.HandlePowerChanges(ctx, actorChanges[builtin.StoragePowerActorCodeID]); err != nil { + return xerrors.Errorf("Failed to handle power actor changes: %w", err) + } + log.Info("Processes Power Changes") + return nil + }) + grp.Go(func() error { if err := p.HandleMessageChanges(ctx, toProcess); err != nil { return xerrors.Errorf("Failed to handle message changes: %w", err) diff --git a/cmd/lotus-chainwatch/processor/reward.go b/cmd/lotus-chainwatch/processor/reward.go index 5411cb68a..84aa1d990 100644 --- a/cmd/lotus-chainwatch/processor/reward.go +++ b/cmd/lotus-chainwatch/processor/reward.go @@ -3,7 +3,6 @@ package processor import ( "bytes" "context" - "github.com/filecoin-project/specs-actors/actors/util/smoothing" "time" "golang.org/x/sync/errgroup" @@ -12,6 +11,7 @@ import ( "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/reward" + "github.com/filecoin-project/specs-actors/actors/util/smoothing" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" @@ -75,7 +75,7 @@ create table if not exists reward_smoothing_estimates func (p *Processor) HandleRewardChanges(ctx context.Context, rewardTips ActorTips, nullRounds []types.TipSetKey) error { rewardChanges, err := p.processRewardActors(ctx, rewardTips, nullRounds) if err != nil { - log.Fatalw("Failed to process reward actors", "error", err) + return xerrors.Errorf("Failed to process reward actors: %w", err) } if err := p.persistRewardActors(ctx, rewardChanges); err != nil { From 5ea61abfe142e700c65e1a69992ecd52bd0a8ec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 19 Aug 2020 22:08:04 +0200 Subject: [PATCH 09/13] Wire up miner control addresses, use for post --- api/types.go | 12 +- cli/state.go | 3 + cmd/lotus-storage-miner/actor.go | 243 ++++++++++++++++++++++++++++++- storage/addresses.go | 92 ++++++++++++ storage/miner.go | 1 + storage/wdpost_run.go | 29 +++- 6 files changed, 370 insertions(+), 10 deletions(-) create mode 100644 storage/addresses.go diff --git a/api/types.go b/api/types.go index 9a874d1c2..6b601e241 100644 --- a/api/types.go +++ b/api/types.go @@ -52,6 +52,7 @@ type MinerInfo struct { Owner address.Address // Must be an ID-address. Worker address.Address // Must be an ID-address. NewWorker address.Address // Must be an ID-address. + ControlAddresses []address.Address // Must be an ID-addresses. WorkerChangeEpoch abi.ChainEpoch PeerId *peer.ID Multiaddrs []abi.Multiaddrs @@ -67,10 +68,13 @@ func NewApiMinerInfo(info *miner.MinerInfo) MinerInfo { } mi := MinerInfo{ - Owner: info.Owner, - Worker: info.Worker, - NewWorker: address.Undef, - WorkerChangeEpoch: -1, + Owner: info.Owner, + Worker: info.Worker, + ControlAddresses: info.ControlAddresses, + + NewWorker: address.Undef, + WorkerChangeEpoch: -1, + PeerId: pid, Multiaddrs: info.Multiaddrs, SealProofType: info.SealProofType, diff --git a/cli/state.go b/cli/state.go index c8daade15..c49994e2a 100644 --- a/cli/state.go +++ b/cli/state.go @@ -105,6 +105,9 @@ var stateMinerInfo = &cli.Command{ fmt.Printf("Owner:\t%s\n", mi.Owner) fmt.Printf("Worker:\t%s\n", mi.Worker) + for i, controlAddress := range mi.ControlAddresses { + fmt.Printf("Control %d: \t%s\n", i, controlAddress) + } fmt.Printf("PeerID:\t%s\n", mi.PeerId) fmt.Printf("SectorSize:\t%s (%d)\n", types.SizeStr(types.NewInt(uint64(mi.SectorSize))), mi.SectorSize) fmt.Printf("Multiaddrs: \t") diff --git a/cmd/lotus-storage-miner/actor.go b/cmd/lotus-storage-miner/actor.go index bd5caf0f0..613b0f99a 100644 --- a/cmd/lotus-storage-miner/actor.go +++ b/cmd/lotus-storage-miner/actor.go @@ -2,19 +2,26 @@ package main import ( "fmt" - "github.com/filecoin-project/specs-actors/actors/builtin" - "github.com/libp2p/go-libp2p-core/peer" - "golang.org/x/xerrors" + "os" + "strings" + "github.com/fatih/color" + "github.com/libp2p/go-libp2p-core/peer" ma "github.com/multiformats/go-multiaddr" "github.com/urfave/cli/v2" + "golang.org/x/xerrors" + "github.com/filecoin-project/go-address" "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/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" + "github.com/filecoin-project/lotus/lib/tablewriter" + "github.com/filecoin-project/lotus/storage" ) var actorCmd = &cli.Command{ @@ -24,6 +31,7 @@ var actorCmd = &cli.Command{ actorSetAddrsCmd, actorWithdrawCmd, actorSetPeeridCmd, + actorControl, }, } @@ -240,3 +248,232 @@ var actorWithdrawCmd = &cli.Command{ return nil }, } + +var actorControl = &cli.Command{ + Name: "control", + Usage: "Manage control addresses", + Subcommands: []*cli.Command{ + actorControlList, + actorControlSet, + }, +} + +var actorControlList = &cli.Command{ + Name: "list", + Usage: "Get currently set control addresses", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "verbose", + }, + &cli.BoolFlag{ + Name: "color", + Value: true, + }, + }, + Action: func(cctx *cli.Context) error { + color.NoColor = !cctx.Bool("color") + + nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + if err != nil { + return err + } + defer closer() + + api, acloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer acloser() + + ctx := lcli.ReqContext(cctx) + + maddr, err := nodeApi.ActorAddress(ctx) + if err != nil { + return err + } + + mi, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + tw := tablewriter.New( + tablewriter.Col("name"), + tablewriter.Col("ID"), + tablewriter.Col("key"), + tablewriter.Col("use"), + tablewriter.Col("balance"), + ) + + postAddr, err := storage.AddressFor(ctx, api, mi, storage.PoStAddr, types.FromFil(1)) + if err != nil { + return xerrors.Errorf("getting address for post: %w", err) + } + + printKey := func(name string, a address.Address) { + b, err := api.WalletBalance(ctx, a) + if err != nil { + fmt.Printf("%s\t%s: error getting balance: %s\n", name, a, err) + return + } + + k, err := api.StateAccountKey(ctx, a, types.EmptyTSK) + if err != nil { + fmt.Printf("%s\t%s: error getting account key: %s\n", name, a, err) + return + } + + kstr := k.String() + if !cctx.Bool("verbose") { + kstr = kstr[:9] + "..." + } + + bstr := types.FIL(b).String() + switch { + case b.LessThan(types.FromFil(10)): + bstr = color.RedString(bstr) + case b.LessThan(types.FromFil(50)): + bstr = color.YellowString(bstr) + default: + bstr = color.GreenString(bstr) + } + + var uses []string + if a == mi.Worker { + uses = append(uses, color.YellowString("other")) + } + if a == postAddr { + uses = append(uses, color.GreenString("post")) + } + + tw.Write(map[string]interface{}{ + "name": name, + "ID": a, + "key": kstr, + "use": strings.Join(uses, " "), + "balance": bstr, + }) + } + + printKey("owner", mi.Owner) + printKey("worker", mi.Worker) + for i, ca := range mi.ControlAddresses { + printKey(fmt.Sprintf("control-%d", i), ca) + } + + return tw.Flush(os.Stdout) + }, +} + +var actorControlSet = &cli.Command{ + Name: "set", + Usage: "Set control address(-es)", + ArgsUsage: "[...address]", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "really-do-it", + Usage: "Actually send transaction performing the action", + Value: false, + }, + }, + Action: func(cctx *cli.Context) error { + nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + if err != nil { + return err + } + defer closer() + + api, acloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer acloser() + + ctx := lcli.ReqContext(cctx) + + maddr, err := nodeApi.ActorAddress(ctx) + if err != nil { + return err + } + + mi, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + del := map[address.Address]struct{}{} + existing := map[address.Address]struct{}{} + for _, controlAddress := range mi.ControlAddresses { + ka, err := api.StateAccountKey(ctx, controlAddress, types.EmptyTSK) + if err != nil { + return err + } + + del[ka] = struct{}{} + existing[ka] = struct{}{} + } + + var toSet []address.Address + + for i, as := range cctx.Args().Slice() { + a, err := address.NewFromString(as) + if err != nil { + return xerrors.Errorf("parsing address %d: %w", i, err) + } + + ka, err := api.StateAccountKey(ctx, a, types.EmptyTSK) + if err != nil { + return err + } + + // make sure the address exists on chain + _, err = api.StateLookupID(ctx, ka, types.EmptyTSK) + if err != nil { + return xerrors.Errorf("looking up %s: %w", ka, err) + } + + delete(del, ka) + toSet = append(toSet, ka) + } + + for a := range del { + fmt.Println("Remove", a) + } + for _, a := range toSet { + if _, exists := existing[a]; !exists { + fmt.Println("Add", a) + } + } + + if !cctx.Bool("really-do-it") { + fmt.Println("Pass --really-do-it to actually execute this action") + return nil + } + + cwp := &miner.ChangeWorkerAddressParams{ + NewWorker: mi.Worker, + NewControlAddrs: toSet, + } + + sp, err := actors.SerializeParams(cwp) + if err != nil { + return xerrors.Errorf("serializing params: %w", err) + } + + smsg, err := api.MpoolPushMessage(ctx, &types.Message{ + From: mi.Owner, + To: maddr, + Method: builtin.MethodsMiner.ChangeWorkerAddress, + + Value: big.Zero(), + Params: sp, + }, nil) + if err != nil { + return xerrors.Errorf("mpool push: %w", err) + } + + fmt.Println("Message CID:", smsg.Cid()) + + return nil + }, +} diff --git a/storage/addresses.go b/storage/addresses.go new file mode 100644 index 000000000..ff5da96ca --- /dev/null +++ b/storage/addresses.go @@ -0,0 +1,92 @@ +package storage + +import ( + "context" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/specs-actors/actors/abi" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/types" +) + +type AddrUse int + +const ( + PreCommitAddr AddrUse = iota + CommitAddr + PoStAddr +) + +type addrSelectApi interface { + WalletBalance(context.Context, address.Address) (types.BigInt, error) + WalletHas(context.Context, address.Address) (bool, error) + + StateAccountKey(context.Context, address.Address, types.TipSetKey) (address.Address, error) +} + +func AddressFor(ctx context.Context, a addrSelectApi, mi api.MinerInfo, use AddrUse, minFunds abi.TokenAmount) (address.Address, error) { + switch use { + case PreCommitAddr, CommitAddr: + // always use worker, at least for now + return mi.Worker, nil + } + + for _, addr := range mi.ControlAddresses { + b, err := a.WalletBalance(ctx, addr) + if err != nil { + return address.Undef, xerrors.Errorf("checking control address balance: %w", err) + } + + if b.GreaterThanEqual(minFunds) { + k, err := a.StateAccountKey(ctx, addr, types.EmptyTSK) + if err != nil { + log.Errorw("getting account key", "error", err) + continue + } + + have, err := a.WalletHas(ctx, k) + if err != nil { + return address.Undef, xerrors.Errorf("failed to check control address: %w", err) + } + + if !have { + log.Errorw("don't have key", "key", k) + continue + } + + return addr, nil + } + + log.Warnw("control address didn't have enough funds for PoSt message", "address", addr, "required", types.FIL(minFunds), "balance", types.FIL(b)) + } + + // Try to use the owner account if we can, fallback to worker if we can't + + b, err := a.WalletBalance(ctx, mi.Owner) + if err != nil { + return address.Undef, xerrors.Errorf("checking owner balance: %w", err) + } + + if !b.GreaterThanEqual(minFunds) { + return mi.Worker, nil + } + + k, err := a.StateAccountKey(ctx, mi.Owner, types.EmptyTSK) + if err != nil { + log.Errorw("getting owner account key", "error", err) + return mi.Worker, nil + } + + have, err := a.WalletHas(ctx, k) + if err != nil { + return address.Undef, xerrors.Errorf("failed to check owner address: %w", err) + } + + if !have { + return mi.Worker, nil + } + + return mi.Owner, nil +} \ No newline at end of file diff --git a/storage/miner.go b/storage/miner.go index 7baffee30..c1e0048ee 100644 --- a/storage/miner.go +++ b/storage/miner.go @@ -67,6 +67,7 @@ type storageMinerApi interface { StateMarketStorageDeal(context.Context, abi.DealID, types.TipSetKey) (*api.MarketDeal, error) StateMinerFaults(context.Context, address.Address, types.TipSetKey) (abi.BitField, error) StateMinerRecoveries(context.Context, address.Address, types.TipSetKey) (abi.BitField, error) + StateAccountKey(context.Context, address.Address, types.TipSetKey) (address.Address, error) MpoolPushMessage(context.Context, *types.Message, *api.MessageSendSpec) (*types.SignedMessage, error) diff --git a/storage/wdpost_run.go b/storage/wdpost_run.go index 8d61f5c9f..aea274b90 100644 --- a/storage/wdpost_run.go +++ b/storage/wdpost_run.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "errors" + "github.com/filecoin-project/specs-actors/actors/abi/big" "time" "github.com/filecoin-project/go-bitfield" @@ -172,11 +173,11 @@ func (s *WindowPoStScheduler) checkNextRecoveries(ctx context.Context, dlIdx uin msg := &types.Message{ To: s.actor, - From: s.worker, Method: builtin.MethodsMiner.DeclareFaultsRecovered, Params: enc, Value: types.NewInt(0), } + s.setSender(ctx, msg) sm, err := s.api.MpoolPushMessage(ctx, msg, &api.MessageSendSpec{MaxFee: abi.TokenAmount(s.feeCfg.MaxWindowPoStGasFee)}) if err != nil { @@ -254,11 +255,11 @@ func (s *WindowPoStScheduler) checkNextFaults(ctx context.Context, dlIdx uint64, msg := &types.Message{ To: s.actor, - From: s.worker, Method: builtin.MethodsMiner.DeclareFaults, Params: enc, Value: types.NewInt(0), // TODO: Is there a fee? } + s.setSender(ctx, msg) sm, err := s.api.MpoolPushMessage(ctx, msg, &api.MessageSendSpec{MaxFee: abi.TokenAmount(s.feeCfg.MaxWindowPoStGasFee)}) if err != nil { @@ -460,11 +461,11 @@ func (s *WindowPoStScheduler) submitPost(ctx context.Context, proof *miner.Submi msg := &types.Message{ To: s.actor, - From: s.worker, Method: builtin.MethodsMiner.SubmitWindowedPoSt, Params: enc, Value: types.NewInt(1000), // currently hard-coded late fee in actor, returned if not late } + s.setSender(ctx, msg) // TODO: consider maybe caring about the output sm, err := s.api.MpoolPushMessage(ctx, msg, &api.MessageSendSpec{MaxFee: abi.TokenAmount(s.feeCfg.MaxWindowPoStGasFee)}) @@ -490,3 +491,25 @@ func (s *WindowPoStScheduler) submitPost(ctx context.Context, proof *miner.Submi return nil } + +func (s *WindowPoStScheduler) setSender(ctx context.Context, msg *types.Message) { + mi, err := s.api.StateMinerInfo(ctx, s.actor, types.EmptyTSK) + if err != nil { + log.Errorw("error getting miner info", "error", err) + + // better than just failing + msg.From = s.worker + return + } + + minFunds := big.Add(msg.RequiredFunds(), msg.Value) + + pa, err := AddressFor(ctx, s.api, mi, PoStAddr, minFunds) + if err != nil { + log.Errorw("error selecting address for post", "error", err) + msg.From = s.worker + return + } + + msg.From = pa +} \ No newline at end of file From 71cf358ee37cfc79f52d7c67d771c76996cced9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 19 Aug 2020 23:25:58 +0200 Subject: [PATCH 10/13] Fix windowpost test --- api/api_full.go | 3 +++ api/apistruct/struct.go | 21 ++++++++++++--------- node/impl/full/gas.go | 31 +++++++++++++++++++++++++++++++ node/impl/full/mpool.go | 27 +++------------------------ storage/miner.go | 2 ++ storage/wdpost_run.go | 26 ++++++++++++++++++++------ 6 files changed, 71 insertions(+), 39 deletions(-) diff --git a/api/api_full.go b/api/api_full.go index 72799c846..42af888b8 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -132,6 +132,9 @@ type FullNode interface { GasEstimateGasPremium(_ context.Context, nblocksincl uint64, sender address.Address, gaslimit int64, tsk types.TipSetKey) (types.BigInt, error) + // GasEstimateMessageGas estimates gas values unset message gas fields + GasEstimateMessageGas(context.Context, *types.Message, *MessageSendSpec, types.TipSetKey) (*types.Message, error) + // MethodGroup: Sync // The Sync method group contains methods for interacting with and // observing the lotus sync service. diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index 282dd3116..d2313c8f7 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -90,9 +90,10 @@ type FullNodeStruct struct { BeaconGetEntry func(ctx context.Context, epoch abi.ChainEpoch) (*types.BeaconEntry, error) `perm:"read"` - GasEstimateGasPremium func(context.Context, uint64, address.Address, int64, types.TipSetKey) (types.BigInt, error) `perm:"read"` - GasEstimateGasLimit func(context.Context, *types.Message, types.TipSetKey) (int64, error) `perm:"read"` - GasEstimateFeeCap func(context.Context, *types.Message, int64, types.TipSetKey) (types.BigInt, error) `perm:"read"` + GasEstimateGasPremium func(context.Context, uint64, address.Address, int64, types.TipSetKey) (types.BigInt, error) `perm:"read"` + GasEstimateGasLimit func(context.Context, *types.Message, types.TipSetKey) (int64, error) `perm:"read"` + GasEstimateFeeCap func(context.Context, *types.Message, int64, types.TipSetKey) (types.BigInt, error) `perm:"read"` + GasEstimateMessageGas func(context.Context, *types.Message, *api.MessageSendSpec, types.TipSetKey) (*types.Message, error) `perm:"read"` SyncState func(context.Context) (*api.SyncState, error) `perm:"read"` SyncSubmitBlock func(ctx context.Context, blk *types.BlockMsg) error `perm:"write"` @@ -459,17 +460,19 @@ func (c *FullNodeStruct) ClientDataTransferUpdates(ctx context.Context) (<-chan return c.Internal.ClientDataTransferUpdates(ctx) } -func (c *FullNodeStruct) GasEstimateGasPremium(ctx context.Context, nblocksincl uint64, - sender address.Address, gaslimit int64, tsk types.TipSetKey) (types.BigInt, error) { +func (c *FullNodeStruct) GasEstimateGasPremium(ctx context.Context, nblocksincl uint64, sender address.Address, gaslimit int64, tsk types.TipSetKey) (types.BigInt, error) { return c.Internal.GasEstimateGasPremium(ctx, nblocksincl, sender, gaslimit, tsk) } -func (c *FullNodeStruct) GasEstimateFeeCap(ctx context.Context, msg *types.Message, - maxqueueblks int64, tsk types.TipSetKey) (types.BigInt, error) { + +func (c *FullNodeStruct) GasEstimateFeeCap(ctx context.Context, msg *types.Message, maxqueueblks int64, tsk types.TipSetKey) (types.BigInt, error) { return c.Internal.GasEstimateFeeCap(ctx, msg, maxqueueblks, tsk) } -func (c *FullNodeStruct) GasEstimateGasLimit(ctx context.Context, msg *types.Message, - tsk types.TipSetKey) (int64, error) { +func (c *FullNodeStruct) GasEstimateMessageGas(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec, tsk types.TipSetKey) (*types.Message, error) { + return c.Internal.GasEstimateMessageGas(ctx, msg, spec, tsk) +} + +func (c *FullNodeStruct) GasEstimateGasLimit(ctx context.Context, msg *types.Message, tsk types.TipSetKey) (int64, error) { return c.Internal.GasEstimateGasLimit(ctx, msg, tsk) } diff --git a/node/impl/full/gas.go b/node/impl/full/gas.go index 0e140c7a1..984df4d15 100644 --- a/node/impl/full/gas.go +++ b/node/impl/full/gas.go @@ -6,6 +6,7 @@ import ( "math/rand" "sort" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/messagepool" "github.com/filecoin-project/lotus/chain/stmgr" @@ -176,3 +177,33 @@ func (a *GasAPI) GasEstimateGasLimit(ctx context.Context, msgIn *types.Message, return res.MsgRct.GasUsed, nil } + +func (a *GasAPI) GasEstimateMessageGas(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec, _ types.TipSetKey) (*types.Message, error) { + if msg.GasLimit == 0 { + gasLimit, err := a.GasEstimateGasLimit(ctx, msg, types.TipSetKey{}) + if err != nil { + return nil, xerrors.Errorf("estimating gas used: %w", err) + } + msg.GasLimit = int64(float64(gasLimit) * a.Mpool.GetConfig().GasLimitOverestimation) + } + + if msg.GasPremium == types.EmptyInt || types.BigCmp(msg.GasPremium, types.NewInt(0)) == 0 { + gasPremium, err := a.GasEstimateGasPremium(ctx, 2, msg.From, msg.GasLimit, types.TipSetKey{}) + if err != nil { + return nil, xerrors.Errorf("estimating gas price: %w", err) + } + msg.GasPremium = gasPremium + } + + if msg.GasFeeCap == types.EmptyInt || types.BigCmp(msg.GasFeeCap, types.NewInt(0)) == 0 { + feeCap, err := a.GasEstimateFeeCap(ctx, msg, 10, types.EmptyTSK) + if err != nil { + return nil, xerrors.Errorf("estimating fee cap: %w", err) + } + msg.GasFeeCap = big.Add(feeCap, msg.GasPremium) + } + + capGasFee(msg, spec.Get().MaxFee) + + return msg, nil +} diff --git a/node/impl/full/mpool.go b/node/impl/full/mpool.go index 64befa738..bde7d4f81 100644 --- a/node/impl/full/mpool.go +++ b/node/impl/full/mpool.go @@ -146,32 +146,12 @@ func (a *MpoolAPI) MpoolPushMessage(ctx context.Context, msg *types.Message, spe if msg.Nonce != 0 { return nil, xerrors.Errorf("MpoolPushMessage expects message nonce to be 0, was %d", msg.Nonce) } - if msg.GasLimit == 0 { - gasLimit, err := a.GasEstimateGasLimit(ctx, msg, types.TipSetKey{}) - if err != nil { - return nil, xerrors.Errorf("estimating gas used: %w", err) - } - msg.GasLimit = int64(float64(gasLimit) * a.Mpool.GetConfig().GasLimitOverestimation) - } - if msg.GasPremium == types.EmptyInt || types.BigCmp(msg.GasPremium, types.NewInt(0)) == 0 { - gasPremium, err := a.GasEstimateGasPremium(ctx, 2, msg.From, msg.GasLimit, types.TipSetKey{}) - if err != nil { - return nil, xerrors.Errorf("estimating gas price: %w", err) - } - msg.GasPremium = gasPremium + msg, err := a.GasAPI.GasEstimateMessageGas(ctx, msg, spec, types.EmptyTSK) + if err != nil { + return nil, xerrors.Errorf("GasEstimateMessageGas error: %w", err) } - if msg.GasFeeCap == types.EmptyInt || types.BigCmp(msg.GasFeeCap, types.NewInt(0)) == 0 { - feeCap, err := a.GasEstimateFeeCap(ctx, msg, 10, types.EmptyTSK) - if err != nil { - return nil, xerrors.Errorf("estimating fee cap: %w", err) - } - msg.GasFeeCap = big.Add(feeCap, msg.GasPremium) - } - - capGasFee(msg, spec.Get().MaxFee) - sign := func(from address.Address, nonce uint64) (*types.SignedMessage, error) { msg.Nonce = nonce if msg.From.Protocol() == address.ID { @@ -192,7 +172,6 @@ func (a *MpoolAPI) MpoolPushMessage(ctx context.Context, msg *types.Message, spe } var m *types.SignedMessage - var err error again: m, err = a.Mpool.PushWithNonce(ctx, msg.From, sign) if err == messagepool.ErrTryAgain { diff --git a/storage/miner.go b/storage/miner.go index c1e0048ee..eb548eb78 100644 --- a/storage/miner.go +++ b/storage/miner.go @@ -71,6 +71,8 @@ type storageMinerApi interface { MpoolPushMessage(context.Context, *types.Message, *api.MessageSendSpec) (*types.SignedMessage, error) + GasEstimateMessageGas(context.Context, *types.Message, *api.MessageSendSpec, types.TipSetKey) (*types.Message, error) + ChainHead(context.Context) (*types.TipSet, error) ChainNotify(context.Context) (<-chan []*api.HeadChange, error) ChainGetRandomnessFromTickets(ctx context.Context, tsk types.TipSetKey, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) (abi.Randomness, error) diff --git a/storage/wdpost_run.go b/storage/wdpost_run.go index aea274b90..5380deb54 100644 --- a/storage/wdpost_run.go +++ b/storage/wdpost_run.go @@ -173,11 +173,13 @@ func (s *WindowPoStScheduler) checkNextRecoveries(ctx context.Context, dlIdx uin msg := &types.Message{ To: s.actor, + From: s.worker, Method: builtin.MethodsMiner.DeclareFaultsRecovered, Params: enc, Value: types.NewInt(0), } - s.setSender(ctx, msg) + spec := &api.MessageSendSpec{MaxFee: abi.TokenAmount(s.feeCfg.MaxWindowPoStGasFee)} + s.setSender(ctx, msg, spec) sm, err := s.api.MpoolPushMessage(ctx, msg, &api.MessageSendSpec{MaxFee: abi.TokenAmount(s.feeCfg.MaxWindowPoStGasFee)}) if err != nil { @@ -255,13 +257,15 @@ func (s *WindowPoStScheduler) checkNextFaults(ctx context.Context, dlIdx uint64, msg := &types.Message{ To: s.actor, + From: s.worker, Method: builtin.MethodsMiner.DeclareFaults, Params: enc, Value: types.NewInt(0), // TODO: Is there a fee? } - s.setSender(ctx, msg) + spec := &api.MessageSendSpec{MaxFee: abi.TokenAmount(s.feeCfg.MaxWindowPoStGasFee)} + s.setSender(ctx, msg, spec) - sm, err := s.api.MpoolPushMessage(ctx, msg, &api.MessageSendSpec{MaxFee: abi.TokenAmount(s.feeCfg.MaxWindowPoStGasFee)}) + sm, err := s.api.MpoolPushMessage(ctx, msg, spec) if err != nil { return xerrors.Errorf("pushing message to mpool: %w", err) } @@ -461,14 +465,16 @@ func (s *WindowPoStScheduler) submitPost(ctx context.Context, proof *miner.Submi msg := &types.Message{ To: s.actor, + From: s.worker, Method: builtin.MethodsMiner.SubmitWindowedPoSt, Params: enc, Value: types.NewInt(1000), // currently hard-coded late fee in actor, returned if not late } - s.setSender(ctx, msg) + spec := &api.MessageSendSpec{MaxFee: abi.TokenAmount(s.feeCfg.MaxWindowPoStGasFee)} + s.setSender(ctx, msg, spec) // TODO: consider maybe caring about the output - sm, err := s.api.MpoolPushMessage(ctx, msg, &api.MessageSendSpec{MaxFee: abi.TokenAmount(s.feeCfg.MaxWindowPoStGasFee)}) + sm, err := s.api.MpoolPushMessage(ctx, msg, spec) if err != nil { return xerrors.Errorf("pushing message to mpool: %w", err) } @@ -492,7 +498,7 @@ func (s *WindowPoStScheduler) submitPost(ctx context.Context, proof *miner.Submi return nil } -func (s *WindowPoStScheduler) setSender(ctx context.Context, msg *types.Message) { +func (s *WindowPoStScheduler) setSender(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec) { mi, err := s.api.StateMinerInfo(ctx, s.actor, types.EmptyTSK) if err != nil { log.Errorw("error getting miner info", "error", err) @@ -502,6 +508,14 @@ func (s *WindowPoStScheduler) setSender(ctx context.Context, msg *types.Message) return } + gm, err := s.api.GasEstimateMessageGas(ctx, msg, spec, types.EmptyTSK) + if err != nil { + log.Errorw("estimating gas", "error", err) + msg.From = s.worker + return + } + *msg = *gm + minFunds := big.Add(msg.RequiredFunds(), msg.Value) pa, err := AddressFor(ctx, s.api, mi, PoStAddr, minFunds) From b42625d8243dd21b83ec1011f6f9afe64ac4d051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 20 Aug 2020 01:16:09 +0200 Subject: [PATCH 11/13] docs: Explain setting miner control addresses --- documentation/en/mining.md | 51 +++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/documentation/en/mining.md b/documentation/en/mining.md index 3a3bef956..84903409a 100644 --- a/documentation/en/mining.md +++ b/documentation/en/mining.md @@ -109,8 +109,8 @@ The addresses passed to `set-addrs` parameter in the commands below should be cu Once the config file has been updated, set the on-chain record of the miner's listen addresses: - ``` - lotus-miner actor set-addrs ... +``` +lotus-miner actor set-addrs ... ``` This updates the `MinerInfo` object in the miner's actor, which will be looked up @@ -119,5 +119,50 @@ when a client attempts to make a deal. Any number of addresses can be provided. Example: ``` - lotus-miner actor set-addrs /ip4/123.123.73.123/tcp/12345 /ip4/223.223.83.223/tcp/23456 +lotus-miner actor set-addrs /ip4/123.123.73.123/tcp/12345 /ip4/223.223.83.223/tcp/23456 +``` + +# Separate address for windowPoSt messages + +WindowPoSt is the mechanism through which storage is verified in Filecoin. It requires miners to submit proofs for all sectors every 24h, which require sending messages to the chain. + +Because many other mining related actions require sending messages to the chain, and not all of those are "high value", it may be desirable to use a separate account to send PoSt messages from. This allows for setting lower GasFeeCaps on the lower value messages without creating head-of-line blocking problems for the PoSt messages in congested chain conditions + +To set this up, first create a new account, and send it some funds for gas fees: +```sh +lotus wallet new bls +t3defg... + +lotus send t3defg... 100 +``` + +Next add the control address +```sh +lotus-miner actor control set t3defg... +Add t3defg... +Pass --really-do-it to actually execute this action +``` + +Now actually set the addresses +```sh +lotus-miner actor control set --really-do-it t3defg... +Add t3defg... +Message CID: bafy2.. +``` + +Wait for the message to land on chain +```sh +lotus state wait-msg bafy2.. +... +Exit Code: 0 +... +``` + +Check miner control address list to make sure the address was correctly setup +```sh +lotus-miner actor control list +name ID key use balance +owner t01111 t3abcd... other 300 FIL +worker t01111 t3abcd... other 300 FIL +control-0 t02222 t3defg... post 100 FIL ``` From a4807b18bcc341caf2865a5b63da0c3055635255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 20 Aug 2020 01:26:13 +0200 Subject: [PATCH 12/13] gofmt, api docstring --- api/api_full.go | 2 +- api/apistruct/struct.go | 4 ++-- api/types.go | 6 +++--- cmd/lotus-storage-miner/actor.go | 26 +++++++++++++------------- storage/addresses.go | 2 +- storage/wdpost_run.go | 2 +- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/api/api_full.go b/api/api_full.go index 42af888b8..5588a3268 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -132,7 +132,7 @@ type FullNode interface { GasEstimateGasPremium(_ context.Context, nblocksincl uint64, sender address.Address, gaslimit int64, tsk types.TipSetKey) (types.BigInt, error) - // GasEstimateMessageGas estimates gas values unset message gas fields + // GasEstimateMessageGas estimates gas values for unset message gas fields GasEstimateMessageGas(context.Context, *types.Message, *MessageSendSpec, types.TipSetKey) (*types.Message, error) // MethodGroup: Sync diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index d2313c8f7..f5203909e 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -460,11 +460,11 @@ func (c *FullNodeStruct) ClientDataTransferUpdates(ctx context.Context) (<-chan return c.Internal.ClientDataTransferUpdates(ctx) } -func (c *FullNodeStruct) GasEstimateGasPremium(ctx context.Context, nblocksincl uint64, sender address.Address, gaslimit int64, tsk types.TipSetKey) (types.BigInt, error) { +func (c *FullNodeStruct) GasEstimateGasPremium(ctx context.Context, nblocksincl uint64, sender address.Address, gaslimit int64, tsk types.TipSetKey) (types.BigInt, error) { return c.Internal.GasEstimateGasPremium(ctx, nblocksincl, sender, gaslimit, tsk) } -func (c *FullNodeStruct) GasEstimateFeeCap(ctx context.Context, msg *types.Message, maxqueueblks int64, tsk types.TipSetKey) (types.BigInt, error) { +func (c *FullNodeStruct) GasEstimateFeeCap(ctx context.Context, msg *types.Message, maxqueueblks int64, tsk types.TipSetKey) (types.BigInt, error) { return c.Internal.GasEstimateFeeCap(ctx, msg, maxqueueblks, tsk) } diff --git a/api/types.go b/api/types.go index 6b601e241..9bbbca1f6 100644 --- a/api/types.go +++ b/api/types.go @@ -49,9 +49,9 @@ type PubsubScore struct { } type MinerInfo struct { - Owner address.Address // Must be an ID-address. - Worker address.Address // Must be an ID-address. - NewWorker address.Address // Must be an ID-address. + Owner address.Address // Must be an ID-address. + Worker address.Address // Must be an ID-address. + NewWorker address.Address // Must be an ID-address. ControlAddresses []address.Address // Must be an ID-addresses. WorkerChangeEpoch abi.ChainEpoch PeerId *peer.ID diff --git a/cmd/lotus-storage-miner/actor.go b/cmd/lotus-storage-miner/actor.go index 613b0f99a..01c6f57d7 100644 --- a/cmd/lotus-storage-miner/actor.go +++ b/cmd/lotus-storage-miner/actor.go @@ -250,8 +250,8 @@ var actorWithdrawCmd = &cli.Command{ } var actorControl = &cli.Command{ - Name: "control", - Usage: "Manage control addresses", + Name: "control", + Usage: "Manage control addresses", Subcommands: []*cli.Command{ actorControlList, actorControlSet, @@ -259,14 +259,14 @@ var actorControl = &cli.Command{ } var actorControlList = &cli.Command{ - Name: "list", - Usage: "Get currently set control addresses", + Name: "list", + Usage: "Get currently set control addresses", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "verbose", }, &cli.BoolFlag{ - Name: "color", + Name: "color", Value: true, }, }, @@ -347,10 +347,10 @@ var actorControlList = &cli.Command{ } tw.Write(map[string]interface{}{ - "name": name, - "ID": a, - "key": kstr, - "use": strings.Join(uses, " "), + "name": name, + "ID": a, + "key": kstr, + "use": strings.Join(uses, " "), "balance": bstr, }) } @@ -371,7 +371,7 @@ var actorControlSet = &cli.Command{ ArgsUsage: "[...address]", Flags: []cli.Flag{ &cli.BoolFlag{ - Name: "really-do-it", + Name: "really-do-it", Usage: "Actually send transaction performing the action", Value: false, }, @@ -461,11 +461,11 @@ var actorControlSet = &cli.Command{ } smsg, err := api.MpoolPushMessage(ctx, &types.Message{ - From: mi.Owner, - To: maddr, + From: mi.Owner, + To: maddr, Method: builtin.MethodsMiner.ChangeWorkerAddress, - Value: big.Zero(), + Value: big.Zero(), Params: sp, }, nil) if err != nil { diff --git a/storage/addresses.go b/storage/addresses.go index ff5da96ca..e041024c6 100644 --- a/storage/addresses.go +++ b/storage/addresses.go @@ -89,4 +89,4 @@ func AddressFor(ctx context.Context, a addrSelectApi, mi api.MinerInfo, use Addr } return mi.Owner, nil -} \ No newline at end of file +} diff --git a/storage/wdpost_run.go b/storage/wdpost_run.go index 5380deb54..7367ceafa 100644 --- a/storage/wdpost_run.go +++ b/storage/wdpost_run.go @@ -526,4 +526,4 @@ func (s *WindowPoStScheduler) setSender(ctx context.Context, msg *types.Message, } msg.From = pa -} \ No newline at end of file +} From de41f63fa42029ace681591cdec9c3fb2b28fd39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 20 Aug 2020 02:03:42 +0200 Subject: [PATCH 13/13] miner info: Also print locked pledge --- cmd/lotus-storage-miner/info.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/lotus-storage-miner/info.go b/cmd/lotus-storage-miner/info.go index 22cf8916a..79487cc2e 100644 --- a/cmd/lotus-storage-miner/info.go +++ b/cmd/lotus-storage-miner/info.go @@ -143,6 +143,7 @@ func infoCmdAct(cctx *cli.Context) error { fmt.Printf("Miner Balance: %s\n", color.YellowString("%s", types.FIL(mact.Balance))) fmt.Printf("\tPreCommit: %s\n", types.FIL(mas.PreCommitDeposits)) + fmt.Printf("\tPledge: %s\n", types.FIL(mas.InitialPledgeRequirement)) fmt.Printf("\tLocked: %s\n", types.FIL(mas.LockedFunds)) color.Green("\tAvailable: %s", types.FIL(types.BigSub(mact.Balance, types.BigAdd(mas.LockedFunds, mas.PreCommitDeposits)))) wb, err := api.WalletBalance(ctx, mi.Worker)