From 4a3081c77a99fc1d6ec989545ad4d55b532ab465 Mon Sep 17 00:00:00 2001 From: Travis Person Date: Tue, 6 Oct 2020 20:13:48 +0000 Subject: [PATCH 1/4] lotus-pcr: limit refunds to properly priced messages --- cmd/lotus-pcr/main.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/cmd/lotus-pcr/main.go b/cmd/lotus-pcr/main.go index 4ce5bbb9f..f446b2c9d 100644 --- a/cmd/lotus-pcr/main.go +++ b/cmd/lotus-pcr/main.go @@ -376,6 +376,18 @@ var runCmd = &cli.Command{ Usage: "percent of refund to issue", Value: 110, }, + &cli.StringFlag{ + Name: "pre-fee-cap-max", + EnvVars: []string{"LOTUS_PCR_PRE_FEE_CAP_MAX"}, + Usage: "messages with a fee cap larger than this will be skipped when processing pre commit messages", + Value: "0.0000000001", + }, + &cli.StringFlag{ + Name: "prove-fee-cap-max", + EnvVars: []string{"LOTUS_PCR_PROVE_FEE_CAP_MAX"}, + Usage: "messages with a prove cap larger than this will be skipped when processing pre commit messages", + Value: "0.0000000001", + }, }, Action: func(cctx *cli.Context) error { go func() { @@ -426,6 +438,16 @@ var runCmd = &cli.Command{ minerRecoveryCutoff := uint64(cctx.Int("miner-recovery-cutoff")) minerRecoveryBonus := uint64(cctx.Int("miner-recovery-bonus")) + preFeeCapMax, err := types.ParseFIL(cctx.String("pre-fee-cap-max")) + if err != nil { + return err + } + + proveFeeCapMax, err := types.ParseFIL(cctx.String("prove-fee-cap-max")) + if err != nil { + return err + } + rf := &refunder{ api: api, wallet: from, @@ -436,6 +458,8 @@ var runCmd = &cli.Command{ dryRun: dryRun, preCommitEnabled: preCommitEnabled, proveCommitEnabled: proveCommitEnabled, + preFeeCapMax: types.BigInt(preFeeCapMax), + proveFeeCapMax: types.BigInt(proveFeeCapMax), } var refunds *MinersRefund = NewMinersRefund() @@ -588,6 +612,9 @@ type refunder struct { preCommitEnabled bool proveCommitEnabled bool threshold big.Int + + preFeeCapMax big.Int + proveFeeCapMax big.Int } func (r *refunder) FindMiners(ctx context.Context, tipset *types.TipSet, refunds *MinersRefund, owner, worker, control bool) (*MinersRefund, error) { @@ -869,6 +896,11 @@ func (r *refunder) ProcessTipset(ctx context.Context, tipset *types.TipSet, refu continue } + if m.GasFeeCap.GreaterThan(r.proveFeeCapMax) { + log.Debugw("skipping high fee cap message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "gas_fee_cap", m.GasFeeCap, "fee_cap_max", r.proveFeeCapMax) + continue + } + var sn abi.SectorNumber var proveCommitSector miner0.ProveCommitSectorParams @@ -916,6 +948,11 @@ func (r *refunder) ProcessTipset(ctx context.Context, tipset *types.TipSet, refu continue } + if m.GasFeeCap.GreaterThan(r.preFeeCapMax) { + log.Debugw("skipping high fee cap message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "gas_fee_cap", m.GasFeeCap, "fee_cap_max", r.preFeeCapMax) + continue + } + var precommitInfo miner.SectorPreCommitInfo if err := precommitInfo.UnmarshalCBOR(bytes.NewBuffer(m.Params)); err != nil { log.Warnw("failed to decode precommit params", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To) From 087030fe371476cfad576796a2060f81ec2b7487 Mon Sep 17 00:00:00 2001 From: Travis Person Date: Tue, 6 Oct 2020 22:35:04 +0000 Subject: [PATCH 2/4] lotus-pcr: refund windowed post and storage deal gas fees --- cmd/lotus-pcr/main.go | 260 ++++++++++++++++++++++++++++-------------- 1 file changed, 174 insertions(+), 86 deletions(-) diff --git a/cmd/lotus-pcr/main.go b/cmd/lotus-pcr/main.go index f446b2c9d..14f4778c2 100644 --- a/cmd/lotus-pcr/main.go +++ b/cmd/lotus-pcr/main.go @@ -340,6 +340,18 @@ var runCmd = &cli.Command{ Usage: "process ProveCommitSector messages", Value: true, }, + &cli.BoolFlag{ + Name: "windowed-post", + EnvVars: []string{"LOTUS_PCR_WINDOWED_POST"}, + Usage: "process SubmitWindowedPoSt messages and refund gas fees", + Value: false, + }, + &cli.BoolFlag{ + Name: "storage-deals", + EnvVars: []string{"LOTUS_PCR_STORAGE_DEALS"}, + Usage: "process PublishStorageDeals messages and refund gas fees", + Value: false, + }, &cli.IntFlag{ Name: "head-delay", EnvVars: []string{"LOTUS_PCR_HEAD_DELAY"}, @@ -431,6 +443,8 @@ var runCmd = &cli.Command{ dryRun := cctx.Bool("dry-run") preCommitEnabled := cctx.Bool("pre-commit") proveCommitEnabled := cctx.Bool("prove-commit") + windowedPoStEnabled := cctx.Bool("windowed-post") + publishStorageDealsEnabled := cctx.Bool("storage-deals") aggregateTipsets := cctx.Int("aggregate-tipsets") minerRecoveryEnabled := cctx.Bool("miner-recovery") minerRecoveryPeriod := abi.ChainEpoch(int64(cctx.Int("miner-recovery-period"))) @@ -458,6 +472,8 @@ var runCmd = &cli.Command{ dryRun: dryRun, preCommitEnabled: preCommitEnabled, proveCommitEnabled: proveCommitEnabled, + windowedPoStEnabled: windowedPoStEnabled, + publishStorageDealsEnabled: publishStorageDealsEnabled, preFeeCapMax: types.BigInt(preFeeCapMax), proveFeeCapMax: types.BigInt(proveFeeCapMax), } @@ -611,6 +627,8 @@ type refunder struct { dryRun bool preCommitEnabled bool proveCommitEnabled bool + windowedPoStEnabled bool + publishStorageDealsEnabled bool threshold big.Int preFeeCapMax big.Int @@ -842,6 +860,147 @@ func (r *refunder) EnsureMinerMinimums(ctx context.Context, tipset *types.TipSet return refunds, nil } +func (r *refunder) processTipsetStorageMarketActor(ctx context.Context, tipset *types.TipSet, msg api.Message, recp *types.MessageReceipt) (bool, string, types.BigInt, error) { + + m := msg.Message + refundValue := types.NewInt(0) + var messageMethod string + + switch m.Method { + case builtin.MethodsMarket.PublishStorageDeals: + if !r.publishStorageDealsEnabled { + return false, messageMethod, types.NewInt(0), nil + } + + messageMethod = "PublishStorageDeals" + + if recp.ExitCode != exitcode.Ok { + log.Debugw("skipping non-ok exitcode message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "exitcode", recp.ExitCode) + return false, messageMethod, types.NewInt(0), nil + } + + refundValue = types.BigMul(types.NewInt(uint64(recp.GasUsed)), tipset.Blocks()[0].ParentBaseFee) + } + + return true, messageMethod, refundValue, nil +} + +func (r *refunder) processTipsetStorageMinerActor(ctx context.Context, tipset *types.TipSet, msg api.Message, recp *types.MessageReceipt) (bool, string, types.BigInt, error) { + + m := msg.Message + refundValue := types.NewInt(0) + var messageMethod string + + switch m.Method { + case builtin.MethodsMiner.SubmitWindowedPoSt: + if !r.windowedPoStEnabled { + return false, messageMethod, types.NewInt(0), nil + } + + messageMethod = "SubmitWindowedPoSt" + + if recp.ExitCode != exitcode.Ok { + log.Debugw("skipping non-ok exitcode message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "exitcode", recp.ExitCode) + return false, messageMethod, types.NewInt(0), nil + } + + refundValue = types.BigMul(types.NewInt(uint64(recp.GasUsed)), tipset.Blocks()[0].ParentBaseFee) + case builtin.MethodsMiner.ProveCommitSector: + if !r.proveCommitEnabled { + return false, messageMethod, types.NewInt(0), nil + } + + messageMethod = "ProveCommitSector" + + if recp.ExitCode != exitcode.Ok { + log.Debugw("skipping non-ok exitcode message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "exitcode", recp.ExitCode) + return false, messageMethod, types.NewInt(0), nil + } + + if m.GasFeeCap.GreaterThan(r.proveFeeCapMax) { + log.Debugw("skipping high fee cap message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "gas_fee_cap", m.GasFeeCap, "fee_cap_max", r.proveFeeCapMax) + return false, messageMethod, types.NewInt(0), nil + } + + var sn abi.SectorNumber + + var proveCommitSector miner0.ProveCommitSectorParams + if err := proveCommitSector.UnmarshalCBOR(bytes.NewBuffer(m.Params)); err != nil { + log.Warnw("failed to decode provecommit params", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To) + return false, messageMethod, types.NewInt(0), nil + } + + sn = proveCommitSector.SectorNumber + + // We use the parent tipset key because precommit information is removed when ProveCommitSector is executed + precommitChainInfo, err := r.api.StateSectorPreCommitInfo(ctx, m.To, sn, tipset.Parents()) + if err != nil { + log.Warnw("failed to get precommit info for sector", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", sn) + return false, messageMethod, types.NewInt(0), nil + } + + precommitTipset, err := r.api.ChainGetTipSetByHeight(ctx, precommitChainInfo.PreCommitEpoch, tipset.Key()) + if err != nil { + log.Warnf("failed to lookup precommit epoch", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", sn) + return false, messageMethod, types.NewInt(0), nil + } + + collateral, err := r.api.StateMinerInitialPledgeCollateral(ctx, m.To, precommitChainInfo.Info, precommitTipset.Key()) + if err != nil { + log.Warnw("failed to get initial pledge collateral", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", sn) + return false, messageMethod, types.NewInt(0), nil + } + + collateral = big.Sub(collateral, precommitChainInfo.PreCommitDeposit) + if collateral.LessThan(big.Zero()) { + log.Debugw("skipping zero pledge collateral difference", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", sn) + return false, messageMethod, types.NewInt(0), nil + } + + refundValue = collateral + if r.refundPercent > 0 { + refundValue = types.BigMul(types.BigDiv(refundValue, types.NewInt(100)), types.NewInt(uint64(r.refundPercent))) + } + case builtin.MethodsMiner.PreCommitSector: + if !r.preCommitEnabled { + return false, messageMethod, types.NewInt(0), nil + } + + messageMethod = "PreCommitSector" + + if recp.ExitCode != exitcode.Ok { + log.Debugw("skipping non-ok exitcode message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "exitcode", recp.ExitCode) + return false, messageMethod, types.NewInt(0), nil + } + + if m.GasFeeCap.GreaterThan(r.preFeeCapMax) { + log.Debugw("skipping high fee cap message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "gas_fee_cap", m.GasFeeCap, "fee_cap_max", r.preFeeCapMax) + return false, messageMethod, types.NewInt(0), nil + } + + var precommitInfo miner.SectorPreCommitInfo + if err := precommitInfo.UnmarshalCBOR(bytes.NewBuffer(m.Params)); err != nil { + log.Warnw("failed to decode precommit params", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To) + return false, messageMethod, types.NewInt(0), nil + } + + collateral, err := r.api.StateMinerInitialPledgeCollateral(ctx, m.To, precommitInfo, tipset.Key()) + if err != nil { + log.Warnw("failed to calculate initial pledge collateral", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", precommitInfo.SectorNumber) + return false, messageMethod, types.NewInt(0), nil + } + + refundValue = collateral + if r.refundPercent > 0 { + refundValue = types.BigMul(types.BigDiv(refundValue, types.NewInt(100)), types.NewInt(uint64(r.refundPercent))) + } + default: + return false, messageMethod, types.NewInt(0), nil + } + + return true, messageMethod, refundValue, nil +} + func (r *refunder) ProcessTipset(ctx context.Context, tipset *types.TipSet, refunds *MinersRefund) (*MinersRefund, error) { cids := tipset.Cids() if len(cids) == 0 { @@ -877,101 +1036,30 @@ func (r *refunder) ProcessTipset(ctx context.Context, tipset *types.TipSet, refu continue } - if !a.IsStorageMinerActor() { - continue - } - var messageMethod string - switch m.Method { - case builtin.MethodsMiner.ProveCommitSector: - if !r.proveCommitEnabled { - continue - } - - messageMethod = "ProveCommitSector" - - if recps[i].ExitCode != exitcode.Ok { - log.Debugw("skipping non-ok exitcode message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "exitcode", recps[i].ExitCode) - continue - } - - if m.GasFeeCap.GreaterThan(r.proveFeeCapMax) { - log.Debugw("skipping high fee cap message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "gas_fee_cap", m.GasFeeCap, "fee_cap_max", r.proveFeeCapMax) - continue - } - - var sn abi.SectorNumber - - var proveCommitSector miner0.ProveCommitSectorParams - if err := proveCommitSector.UnmarshalCBOR(bytes.NewBuffer(m.Params)); err != nil { - log.Warnw("failed to decode provecommit params", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To) - continue - } - - sn = proveCommitSector.SectorNumber - - // We use the parent tipset key because precommit information is removed when ProveCommitSector is executed - precommitChainInfo, err := r.api.StateSectorPreCommitInfo(ctx, m.To, sn, tipset.Parents()) + if m.To == builtin.StorageMarketActorAddr { + var err error + var processed bool + processed, messageMethod, refundValue, err = r.processTipsetStorageMarketActor(ctx, tipset, msg, recps[i]) if err != nil { - log.Warnw("failed to get precommit info for sector", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", sn) continue } - - precommitTipset, err := r.api.ChainGetTipSetByHeight(ctx, precommitChainInfo.PreCommitEpoch, tipset.Key()) - if err != nil { - log.Warnf("failed to lookup precommit epoch", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", sn) + if !processed { continue } - - collateral, err := r.api.StateMinerInitialPledgeCollateral(ctx, m.To, precommitChainInfo.Info, precommitTipset.Key()) - if err != nil { - log.Warnw("failed to get initial pledge collateral", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", sn) - } - - collateral = big.Sub(collateral, precommitChainInfo.PreCommitDeposit) - if collateral.LessThan(big.Zero()) { - log.Debugw("skipping zero pledge collateral difference", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", sn) - continue - } - - refundValue = collateral - case builtin.MethodsMiner.PreCommitSector: - if !r.preCommitEnabled { - continue - } - - messageMethod = "PreCommitSector" - - if recps[i].ExitCode != exitcode.Ok { - log.Debugw("skipping non-ok exitcode message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "exitcode", recps[i].ExitCode) - continue - } - - if m.GasFeeCap.GreaterThan(r.preFeeCapMax) { - log.Debugw("skipping high fee cap message", "method", messageMethod, "cid", msg.Cid, "miner", m.To, "gas_fee_cap", m.GasFeeCap, "fee_cap_max", r.preFeeCapMax) - continue - } - - var precommitInfo miner.SectorPreCommitInfo - if err := precommitInfo.UnmarshalCBOR(bytes.NewBuffer(m.Params)); err != nil { - log.Warnw("failed to decode precommit params", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To) - continue - } - - collateral, err := r.api.StateMinerInitialPledgeCollateral(ctx, m.To, precommitInfo, tipset.Key()) - if err != nil { - log.Warnw("failed to calculate initial pledge collateral", "err", err, "method", messageMethod, "cid", msg.Cid, "miner", m.To, "sector_number", precommitInfo.SectorNumber) - continue - } - - refundValue = collateral - default: - continue } - if r.refundPercent > 0 { - refundValue = types.BigMul(types.BigDiv(refundValue, types.NewInt(100)), types.NewInt(uint64(r.refundPercent))) + if a.IsStorageMinerActor() { + var err error + var processed bool + processed, messageMethod, refundValue, err = r.processTipsetStorageMinerActor(ctx, tipset, msg, recps[i]) + if err != nil { + continue + } + if !processed { + continue + } } log.Debugw( From bf959e45fb5f1c5f9a406ffba407583af5517a21 Mon Sep 17 00:00:00 2001 From: Animesh Date: Wed, 7 Oct 2020 11:43:30 +0530 Subject: [PATCH 3/4] Add api for getting status given a code --- api/api_full.go | 2 ++ api/apistruct/struct.go | 21 +++++++++++++-------- node/impl/client/client.go | 9 +++++++++ 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/api/api_full.go b/api/api_full.go index 88f18943c..072d1cea6 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -271,6 +271,8 @@ type FullNode interface { ClientListDeals(ctx context.Context) ([]DealInfo, error) // ClientGetDealUpdates returns the status of updated deals ClientGetDealUpdates(ctx context.Context) (<-chan DealInfo, error) + // ClientGetDealStatus returns status given a code + ClientGetDealStatus(ctx context.Context, statusCode uint64) (string, error) // ClientHasLocal indicates whether a certain CID is locally stored. ClientHasLocal(ctx context.Context, root cid.Cid) (bool, error) // ClientFindData identifies peers that have a certain file, and returns QueryOffers (one per peer). diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index a09700eb9..dd47c79a4 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -146,14 +146,15 @@ type FullNodeStruct struct { WalletDelete func(context.Context, address.Address) error `perm:"write"` WalletValidateAddress func(context.Context, string) (address.Address, error) `perm:"read"` - ClientImport func(ctx context.Context, ref api.FileRef) (*api.ImportRes, error) `perm:"admin"` - ClientListImports func(ctx context.Context) ([]api.Import, error) `perm:"write"` - ClientRemoveImport func(ctx context.Context, importID multistore.StoreID) error `perm:"admin"` - ClientHasLocal func(ctx context.Context, root cid.Cid) (bool, error) `perm:"write"` - ClientFindData func(ctx context.Context, root cid.Cid, piece *cid.Cid) ([]api.QueryOffer, error) `perm:"read"` - ClientMinerQueryOffer func(ctx context.Context, miner address.Address, root cid.Cid, piece *cid.Cid) (api.QueryOffer, error) `perm:"read"` - ClientStartDeal func(ctx context.Context, params *api.StartDealParams) (*cid.Cid, error) `perm:"admin"` - ClientGetDealInfo func(context.Context, cid.Cid) (*api.DealInfo, error) `perm:"read"` + ClientImport func(ctx context.Context, ref api.FileRef) (*api.ImportRes, error) `perm:"admin"` + ClientListImports func(ctx context.Context) ([]api.Import, error) `perm:"write"` + ClientRemoveImport func(ctx context.Context, importID multistore.StoreID) error `perm:"admin"` + ClientHasLocal func(ctx context.Context, root cid.Cid) (bool, error) `perm:"write"` + ClientFindData func(ctx context.Context, root cid.Cid, piece *cid.Cid) ([]api.QueryOffer, error) `perm:"read"` + ClientMinerQueryOffer func(ctx context.Context, miner address.Address, root cid.Cid, piece *cid.Cid) (api.QueryOffer, error) `perm:"read"` + ClientStartDeal func(ctx context.Context, params *api.StartDealParams) (*cid.Cid, error) `perm:"admin"` + ClientGetDealInfo func(context.Context, cid.Cid) (*api.DealInfo, error) `perm:"read"` + ClientGetDealStatus func(context.Context, uint64) (string, error) ClientListDeals func(ctx context.Context) ([]api.DealInfo, error) `perm:"write"` ClientGetDealUpdates func(ctx context.Context) (<-chan api.DealInfo, error) `perm:"read"` ClientRetrieve func(ctx context.Context, order api.RetrievalOrder, ref *api.FileRef) error `perm:"admin"` @@ -478,6 +479,10 @@ func (c *FullNodeStruct) ClientGetDealInfo(ctx context.Context, deal cid.Cid) (* return c.Internal.ClientGetDealInfo(ctx, deal) } +func (c *FullNodeStruct) ClientGetDealStatus(ctx context.Context, statusCode uint64) (string, error) { + return c.Internal.ClientGetDealStatus(ctx, statusCode) +} + func (c *FullNodeStruct) ClientListDeals(ctx context.Context) ([]api.DealInfo, error) { return c.Internal.ClientListDeals(ctx) } diff --git a/node/impl/client/client.go b/node/impl/client/client.go index 5c1752154..6af266baa 100644 --- a/node/impl/client/client.go +++ b/node/impl/client/client.go @@ -863,3 +863,12 @@ func newDealInfo(v storagemarket.ClientDeal) api.DealInfo { func (a *API) ClientRetrieveTryRestartInsufficientFunds(ctx context.Context, paymentChannel address.Address) error { return a.Retrieval.TryRestartInsufficientFunds(paymentChannel) } + +func (a *API) ClientGetDealStatus(ctx context.Context, statusCode uint64) (string, error) { + ststr, ok := storagemarket.DealStates[statusCode] + if !ok { + return "", fmt.Errorf("no such deal state %d", statusCode) + } + + return ststr, nil +} From 673de6aeb67eac8b0536b86cfe3c105a96ee8213 Mon Sep 17 00:00:00 2001 From: Animesh Date: Wed, 7 Oct 2020 12:35:42 +0530 Subject: [PATCH 4/4] Add perm tag to ClientGetDealStatus --- api/apistruct/struct.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index dd47c79a4..b27e9082c 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -146,15 +146,15 @@ type FullNodeStruct struct { WalletDelete func(context.Context, address.Address) error `perm:"write"` WalletValidateAddress func(context.Context, string) (address.Address, error) `perm:"read"` - ClientImport func(ctx context.Context, ref api.FileRef) (*api.ImportRes, error) `perm:"admin"` - ClientListImports func(ctx context.Context) ([]api.Import, error) `perm:"write"` - ClientRemoveImport func(ctx context.Context, importID multistore.StoreID) error `perm:"admin"` - ClientHasLocal func(ctx context.Context, root cid.Cid) (bool, error) `perm:"write"` - ClientFindData func(ctx context.Context, root cid.Cid, piece *cid.Cid) ([]api.QueryOffer, error) `perm:"read"` - ClientMinerQueryOffer func(ctx context.Context, miner address.Address, root cid.Cid, piece *cid.Cid) (api.QueryOffer, error) `perm:"read"` - ClientStartDeal func(ctx context.Context, params *api.StartDealParams) (*cid.Cid, error) `perm:"admin"` - ClientGetDealInfo func(context.Context, cid.Cid) (*api.DealInfo, error) `perm:"read"` - ClientGetDealStatus func(context.Context, uint64) (string, error) + ClientImport func(ctx context.Context, ref api.FileRef) (*api.ImportRes, error) `perm:"admin"` + ClientListImports func(ctx context.Context) ([]api.Import, error) `perm:"write"` + ClientRemoveImport func(ctx context.Context, importID multistore.StoreID) error `perm:"admin"` + ClientHasLocal func(ctx context.Context, root cid.Cid) (bool, error) `perm:"write"` + ClientFindData func(ctx context.Context, root cid.Cid, piece *cid.Cid) ([]api.QueryOffer, error) `perm:"read"` + ClientMinerQueryOffer func(ctx context.Context, miner address.Address, root cid.Cid, piece *cid.Cid) (api.QueryOffer, error) `perm:"read"` + ClientStartDeal func(ctx context.Context, params *api.StartDealParams) (*cid.Cid, error) `perm:"admin"` + ClientGetDealInfo func(context.Context, cid.Cid) (*api.DealInfo, error) `perm:"read"` + ClientGetDealStatus func(context.Context, uint64) (string, error) `perm:"read"` ClientListDeals func(ctx context.Context) ([]api.DealInfo, error) `perm:"write"` ClientGetDealUpdates func(ctx context.Context) (<-chan api.DealInfo, error) `perm:"read"` ClientRetrieve func(ctx context.Context, order api.RetrievalOrder, ref *api.FileRef) error `perm:"admin"`