diff --git a/api/api_storage.go b/api/api_storage.go index 0ccfbd88f6f..f76b0a8b0cf 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -279,15 +279,17 @@ type AddrUse int const ( PreCommitAddr AddrUse = iota CommitAddr + DealPublishAddr PoStAddr TerminateSectorsAddr ) type AddressConfig struct { - PreCommitControl []address.Address - CommitControl []address.Address - TerminateControl []address.Address + PreCommitControl []address.Address + CommitControl []address.Address + TerminateControl []address.Address + DealPublishControl []address.Address DisableOwnerFallback bool DisableWorkerFallback bool diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index cc9a8948a2e..f7ad7b9c0a1 100644 Binary files a/build/openrpc/miner.json.gz and b/build/openrpc/miner.json.gz differ diff --git a/chain/actors/policy/policy.go b/chain/actors/policy/policy.go index c159dc98f22..c06c85d380c 100644 --- a/chain/actors/policy/policy.go +++ b/chain/actors/policy/policy.go @@ -196,6 +196,33 @@ func GetMaxProveCommitDuration(ver actors.Version, t abi.RegisteredSealProof) ab } } +// SetProviderCollateralSupplyTarget sets the percentage of normalized circulating +// supply that must be covered by provider collateral in a deal. This should +// only be used for testing. +func SetProviderCollateralSupplyTarget(num, denom big.Int) { + + market2.ProviderCollateralSupplyTarget = builtin2.BigFrac{ + Numerator: num, + Denominator: denom, + } + + market3.ProviderCollateralSupplyTarget = builtin3.BigFrac{ + Numerator: num, + Denominator: denom, + } + + market4.ProviderCollateralSupplyTarget = builtin4.BigFrac{ + Numerator: num, + Denominator: denom, + } + + market5.ProviderCollateralSupplyTarget = builtin5.BigFrac{ + Numerator: num, + Denominator: denom, + } + +} + func DealProviderCollateralBounds( size abi.PaddedPieceSize, verified bool, rawBytePower, qaPower, baselinePower abi.StoragePower, diff --git a/chain/actors/policy/policy.go.template b/chain/actors/policy/policy.go.template index 17b3eb0ff70..3257feffd41 100644 --- a/chain/actors/policy/policy.go.template +++ b/chain/actors/policy/policy.go.template @@ -132,6 +132,20 @@ func GetMaxProveCommitDuration(ver actors.Version, t abi.RegisteredSealProof) ab } } +// SetProviderCollateralSupplyTarget sets the percentage of normalized circulating +// supply that must be covered by provider collateral in a deal. This should +// only be used for testing. +func SetProviderCollateralSupplyTarget(num, denom big.Int) { +{{range .versions}} + {{if (ge . 2)}} + market{{.}}.ProviderCollateralSupplyTarget = builtin{{.}}.BigFrac{ + Numerator: num, + Denominator: denom, + } + {{end}} +{{end}} +} + func DealProviderCollateralBounds( size abi.PaddedPieceSize, verified bool, rawBytePower, qaPower, baselinePower abi.StoragePower, diff --git a/cmd/lotus-storage-miner/actor.go b/cmd/lotus-storage-miner/actor.go index 7e428d0e4af..3af589ea542 100644 --- a/cmd/lotus-storage-miner/actor.go +++ b/cmd/lotus-storage-miner/actor.go @@ -435,6 +435,7 @@ var actorControlList = &cli.Command{ commit := map[address.Address]struct{}{} precommit := map[address.Address]struct{}{} terminate := map[address.Address]struct{}{} + dealPublish := map[address.Address]struct{}{} post := map[address.Address]struct{}{} for _, ca := range mi.ControlAddresses { @@ -471,6 +472,16 @@ var actorControlList = &cli.Command{ terminate[ca] = struct{}{} } + for _, ca := range ac.DealPublishControl { + ca, err := api.StateLookupID(ctx, ca, types.EmptyTSK) + if err != nil { + return err + } + + delete(post, ca) + dealPublish[ca] = struct{}{} + } + printKey := func(name string, a address.Address) { b, err := api.WalletBalance(ctx, a) if err != nil { @@ -515,6 +526,9 @@ var actorControlList = &cli.Command{ if _, ok := terminate[a]; ok { uses = append(uses, color.YellowString("terminate")) } + if _, ok := dealPublish[a]; ok { + uses = append(uses, color.MagentaString("deals")) + } tw.Write(map[string]interface{}{ "name": name, diff --git a/documentation/en/api-v0-methods-miner.md b/documentation/en/api-v0-methods-miner.md index 496f63a0851..51a08c4f169 100644 --- a/documentation/en/api-v0-methods-miner.md +++ b/documentation/en/api-v0-methods-miner.md @@ -227,6 +227,7 @@ Response: "PreCommitControl": null, "CommitControl": null, "TerminateControl": null, + "DealPublishControl": null, "DisableOwnerFallback": true, "DisableWorkerFallback": true } diff --git a/itests/ccupgrade_test.go b/itests/ccupgrade_test.go index eac2523bf6e..e94a2144187 100644 --- a/itests/ccupgrade_test.go +++ b/itests/ccupgrade_test.go @@ -73,9 +73,13 @@ func runTestCCUpgrade(t *testing.T, upgradeHeight abi.ChainEpoch) { { exp, err := client.StateSectorExpiration(ctx, maddr, CC, types.EmptyTSK) - require.NoError(t, err) - require.NotNil(t, exp) - require.Greater(t, 50000, int(exp.OnTime)) + if err != nil { + require.Contains(t, err.Error(), "failed to find sector 3") // already cleaned up + } else { + require.NoError(t, err) + require.NotNil(t, exp) + require.Greater(t, 50000, int(exp.OnTime)) + } } { exp, err := client.StateSectorExpiration(ctx, maddr, Upgraded, types.EmptyTSK) diff --git a/itests/deals_publish_test.go b/itests/deals_publish_test.go index 16f84038bbb..2fb4f50f9bd 100644 --- a/itests/deals_publish_test.go +++ b/itests/deals_publish_test.go @@ -11,9 +11,13 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors/builtin/market" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/wallet" "github.com/filecoin-project/lotus/itests/kit" "github.com/filecoin-project/lotus/markets/storageadapter" "github.com/filecoin-project/lotus/node" + "github.com/filecoin-project/lotus/node/config" + "github.com/filecoin-project/lotus/node/modules" + "github.com/filecoin-project/lotus/storage" market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" "github.com/stretchr/testify/require" ) @@ -28,16 +32,34 @@ func TestPublishDealsBatching(t *testing.T) { kit.QuietMiningLogs() - opts := node.Override(new(*storageadapter.DealPublisher), - storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{ - Period: publishPeriod, - MaxDealsPerMsg: maxDealsPerMsg, - }), + publisherKey, err := wallet.GenerateKey(types.KTSecp256k1) + require.NoError(t, err) + + opts := node.Options( + node.Override(new(*storageadapter.DealPublisher), + storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{ + Period: publishPeriod, + MaxDealsPerMsg: maxDealsPerMsg, + }), + ), + node.Override(new(*storage.AddressSelector), modules.AddressSelector(&config.MinerAddressConfig{ + DealPublishControl: []string{ + publisherKey.Address.String(), + }, + DisableOwnerFallback: true, + DisableWorkerFallback: true, + })), + kit.LatestActorsAt(-1), ) - client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ConstructorOpts(opts)) + client, miner, ens := kit.EnsembleMinimal(t, kit.Account(publisherKey, types.FromFil(10)), kit.MockProofs(), kit.ConstructorOpts(opts)) ens.InterconnectAll().BeginMining(10 * time.Millisecond) + _, err = client.WalletImport(ctx, &publisherKey.KeyInfo) + require.NoError(t, err) + + miner.SetControlAddresses(publisherKey.Address) + dh := kit.NewDealHarness(t, client, miner) // Starts a deal and waits until it's published @@ -92,6 +114,7 @@ func TestPublishDealsBatching(t *testing.T) { err = pubDealsParams.UnmarshalCBOR(bytes.NewReader(msg.Params)) require.NoError(t, err) require.Len(t, pubDealsParams.Deals, int(maxDealsPerMsg)) + require.Equal(t, publisherKey.Address.String(), msg.From.String()) } } require.Equal(t, 1, count) diff --git a/itests/kit/control.go b/itests/kit/control.go new file mode 100644 index 00000000000..73ac39b7a14 --- /dev/null +++ b/itests/kit/control.go @@ -0,0 +1,42 @@ +package kit + +import ( + "context" + + "github.com/stretchr/testify/require" + + addr "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/big" + miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/types" +) + +func (tm *TestMiner) SetControlAddresses(addrs ...addr.Address) { + ctx := context.TODO() + + mi, err := tm.FullNode.StateMinerInfo(ctx, tm.ActorAddr, types.EmptyTSK) + require.NoError(tm.t, err) + + cwp := &miner2.ChangeWorkerAddressParams{ + NewWorker: mi.Worker, + NewControlAddrs: addrs, + } + + sp, err := actors.SerializeParams(cwp) + require.NoError(tm.t, err) + + smsg, err := tm.FullNode.MpoolPushMessage(ctx, &types.Message{ + From: mi.Owner, + To: tm.ActorAddr, + Method: miner.Methods.ChangeWorkerAddress, + + Value: big.Zero(), + Params: sp, + }, nil) + require.NoError(tm.t, err) + + tm.FullNode.WaitMsg(ctx, smsg.Cid()) +} diff --git a/itests/kit/funds.go b/itests/kit/funds.go index 417cf9ce1b1..e49c708ea9b 100644 --- a/itests/kit/funds.go +++ b/itests/kit/funds.go @@ -4,9 +4,11 @@ import ( "context" "testing" - "github.com/filecoin-project/go-state-types/abi" "github.com/stretchr/testify/require" + "github.com/filecoin-project/go-state-types/abi" + "github.com/ipfs/go-cid" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/types" @@ -27,8 +29,12 @@ func SendFunds(ctx context.Context, t *testing.T, sender *TestFullNode, recipien sm, err := sender.MpoolPushMessage(ctx, msg, nil) require.NoError(t, err) - res, err := sender.StateWaitMsg(ctx, sm.Cid(), 3, api.LookbackNoLimit, true) - require.NoError(t, err) + sender.WaitMsg(ctx, sm.Cid()) +} + +func (f *TestFullNode) WaitMsg(ctx context.Context, msg cid.Cid) { + res, err := f.StateWaitMsg(ctx, msg, 3, api.LookbackNoLimit, true) + require.NoError(f.t, err) - require.EqualValues(t, 0, res.Receipt.ExitCode, "did not successfully send funds") + require.EqualValues(f.t, 0, res.Receipt.ExitCode, "message did not successfully execute") } diff --git a/itests/kit/init.go b/itests/kit/init.go index 8df4922b864..dc8463cb4e4 100644 --- a/itests/kit/init.go +++ b/itests/kit/init.go @@ -5,6 +5,7 @@ import ( "os" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/policy" logging "github.com/ipfs/go-log/v2" @@ -13,6 +14,8 @@ import ( func init() { _ = logging.SetLogLevel("*", "INFO") + policy.SetProviderCollateralSupplyTarget(big.Zero(), big.NewInt(1)) + policy.SetConsensusMinerMinPower(abi.NewStoragePower(2048)) policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1) policy.SetMinVerifiedDealSize(abi.NewStoragePower(256)) diff --git a/markets/storageadapter/client.go b/markets/storageadapter/client.go index 9357cc271d9..80ead2be3b4 100644 --- a/markets/storageadapter/client.go +++ b/markets/storageadapter/client.go @@ -160,8 +160,16 @@ func (c *ClientNodeAdapter) ValidatePublishedDeal(ctx context.Context, deal stor return 0, xerrors.Errorf("failed to resolve from msg ID addr: %w", err) } - if fromid != mi.Worker { - return 0, xerrors.Errorf("deal wasn't published by storage provider: from=%s, provider=%s", pubmsg.From, deal.Proposal.Provider) + var pubOk bool + pubAddrs := append([]address.Address{mi.Worker, mi.Owner}, mi.ControlAddresses...) + for _, a := range pubAddrs { + if fromid == a { + pubOk = true + break + } + } + if !pubOk { + return 0, xerrors.Errorf("deal wasn't published by storage provider: from=%s, provider=%s,%+v", pubmsg.From, deal.Proposal.Provider, pubAddrs) } if pubmsg.To != miner2.StorageMarketActorAddr { diff --git a/markets/storageadapter/dealpublisher.go b/markets/storageadapter/dealpublisher.go index 157c85ed76f..9f7ba162953 100644 --- a/markets/storageadapter/dealpublisher.go +++ b/markets/storageadapter/dealpublisher.go @@ -7,27 +7,33 @@ import ( "sync" "time" + "github.com/ipfs/go-cid" "go.uber.org/fx" + "golang.org/x/xerrors" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/lotus/node/config" + "github.com/filecoin-project/go-state-types/big" + market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" - "github.com/filecoin-project/go-address" "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/builtin/market" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/types" - market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" - "github.com/ipfs/go-cid" - "golang.org/x/xerrors" + "github.com/filecoin-project/lotus/node/config" + "github.com/filecoin-project/lotus/storage" ) type dealPublisherAPI interface { ChainHead(context.Context) (*types.TipSet, error) MpoolPushMessage(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec) (*types.SignedMessage, error) StateMinerInfo(context.Context, address.Address, types.TipSetKey) (miner.MinerInfo, error) + + 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) + StateLookupID(context.Context, address.Address, types.TipSetKey) (address.Address, error) } // DealPublisher batches deal publishing so that many deals can be included in @@ -40,6 +46,7 @@ type dealPublisherAPI interface { // publish message with all deals in the queue. type DealPublisher struct { api dealPublisherAPI + as *storage.AddressSelector ctx context.Context Shutdown context.CancelFunc @@ -87,14 +94,14 @@ type PublishMsgConfig struct { func NewDealPublisher( feeConfig *config.MinerFeeConfig, publishMsgCfg PublishMsgConfig, -) func(lc fx.Lifecycle, full api.FullNode) *DealPublisher { - return func(lc fx.Lifecycle, full api.FullNode) *DealPublisher { +) func(lc fx.Lifecycle, full api.FullNode, as *storage.AddressSelector) *DealPublisher { + return func(lc fx.Lifecycle, full api.FullNode, as *storage.AddressSelector) *DealPublisher { maxFee := abi.NewTokenAmount(0) if feeConfig != nil { maxFee = abi.TokenAmount(feeConfig.MaxPublishDealsFee) } publishSpec := &api.MessageSendSpec{MaxFee: maxFee} - dp := newDealPublisher(full, publishMsgCfg, publishSpec) + dp := newDealPublisher(full, as, publishMsgCfg, publishSpec) lc.Append(fx.Hook{ OnStop: func(ctx context.Context) error { dp.Shutdown() @@ -107,12 +114,14 @@ func NewDealPublisher( func newDealPublisher( dpapi dealPublisherAPI, + as *storage.AddressSelector, publishMsgCfg PublishMsgConfig, publishSpec *api.MessageSendSpec, ) *DealPublisher { ctx, cancel := context.WithCancel(context.Background()) return &DealPublisher{ api: dpapi, + as: as, ctx: ctx, Shutdown: cancel, maxDealsPerPublishMsg: publishMsgCfg.MaxDealsPerMsg, @@ -345,9 +354,14 @@ func (p *DealPublisher) publishDealProposals(deals []market2.ClientDealProposal) return cid.Undef, xerrors.Errorf("serializing PublishStorageDeals params failed: %w", err) } + addr, _, err := p.as.AddressFor(p.ctx, p.api, mi, api.DealPublishAddr, big.Zero(), big.Zero()) + if err != nil { + return cid.Undef, xerrors.Errorf("selecting address for publishing deals: %w", err) + } + smsg, err := p.api.MpoolPushMessage(p.ctx, &types.Message{ To: market.Address, - From: mi.Worker, + From: addr, Value: types.NewInt(0), Method: market.Methods.PublishStorageDeals, Params: params, diff --git a/markets/storageadapter/dealpublisher_test.go b/markets/storageadapter/dealpublisher_test.go index 746c67d0ef9..3f27425aef8 100644 --- a/markets/storageadapter/dealpublisher_test.go +++ b/markets/storageadapter/dealpublisher_test.go @@ -94,7 +94,7 @@ func TestDealPublisher(t *testing.T) { dpapi := newDPAPI(t) // Create a deal publisher - dp := newDealPublisher(dpapi, PublishMsgConfig{ + dp := newDealPublisher(dpapi, nil, PublishMsgConfig{ Period: tc.publishPeriod, MaxDealsPerMsg: tc.maxDealsPerMsg, }, &api.MessageSendSpec{MaxFee: abi.NewTokenAmount(1)}) @@ -134,7 +134,7 @@ func TestForcePublish(t *testing.T) { // Create a deal publisher start := time.Now() publishPeriod := time.Hour - dp := newDealPublisher(dpapi, PublishMsgConfig{ + dp := newDealPublisher(dpapi, nil, PublishMsgConfig{ Period: publishPeriod, MaxDealsPerMsg: 10, }, &api.MessageSendSpec{MaxFee: abi.NewTokenAmount(1)}) @@ -320,6 +320,22 @@ func (d *dpAPI) MpoolPushMessage(ctx context.Context, msg *types.Message, spec * return &types.SignedMessage{Message: *msg}, nil } +func (d *dpAPI) WalletBalance(ctx context.Context, a address.Address) (types.BigInt, error) { + panic("don't call me") +} + +func (d *dpAPI) WalletHas(ctx context.Context, a address.Address) (bool, error) { + panic("don't call me") +} + +func (d *dpAPI) StateAccountKey(ctx context.Context, a address.Address, key types.TipSetKey) (address.Address, error) { + panic("don't call me") +} + +func (d *dpAPI) StateLookupID(ctx context.Context, a address.Address, key types.TipSetKey) (address.Address, error) { + panic("don't call me") +} + func getClientActor(t *testing.T) address.Address { return tutils.NewActorAddr(t, "client") } diff --git a/node/config/def.go b/node/config/def.go index 240fadbd93f..f9a479d75d8 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -182,9 +182,10 @@ type MinerFeeConfig struct { } type MinerAddressConfig struct { - PreCommitControl []string - CommitControl []string - TerminateControl []string + PreCommitControl []string + CommitControl []string + TerminateControl []string + DealPublishControl []string // DisableOwnerFallback disables usage of the owner address for messages // sent automatically @@ -404,8 +405,10 @@ func DefaultStorageMiner() *StorageMiner { }, Addresses: MinerAddressConfig{ - PreCommitControl: []string{}, - CommitControl: []string{}, + PreCommitControl: []string{}, + CommitControl: []string{}, + TerminateControl: []string{}, + DealPublishControl: []string{}, }, } cfg.Common.API.ListenAddress = "/ip4/127.0.0.1/tcp/2345/http" diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 8508850d3e9..0f281327e16 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -188,6 +188,15 @@ func AddressSelector(addrConf *config.MinerAddressConfig) func() (*storage.Addre as.TerminateControl = append(as.TerminateControl, addr) } + for _, s := range addrConf.DealPublishControl { + addr, err := address.NewFromString(s) + if err != nil { + return nil, xerrors.Errorf("parsing deal publishing control address: %w", err) + } + + as.DealPublishControl = append(as.DealPublishControl, addr) + } + return as, nil } } diff --git a/storage/addresses.go b/storage/addresses.go index a8e5e7101e2..f8f06ed9813 100644 --- a/storage/addresses.go +++ b/storage/addresses.go @@ -5,6 +5,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" @@ -24,6 +25,12 @@ type AddressSelector struct { } func (as *AddressSelector) AddressFor(ctx context.Context, a addrSelectApi, mi miner.MinerInfo, use api.AddrUse, goodFunds, minFunds abi.TokenAmount) (address.Address, abi.TokenAmount, error) { + if as == nil { + // should only happen in some tests + log.Warnw("smart address selection disabled, using worker address") + return mi.Worker, big.Zero(), nil + } + var addrs []address.Address switch use { case api.PreCommitAddr: @@ -32,6 +39,8 @@ func (as *AddressSelector) AddressFor(ctx context.Context, a addrSelectApi, mi m addrs = append(addrs, as.CommitControl...) case api.TerminateSectorsAddr: addrs = append(addrs, as.TerminateControl...) + case api.DealPublishAddr: + addrs = append(addrs, as.DealPublishControl...) default: defaultCtl := map[address.Address]struct{}{} for _, a := range mi.ControlAddresses { @@ -43,6 +52,7 @@ func (as *AddressSelector) AddressFor(ctx context.Context, a addrSelectApi, mi m configCtl := append([]address.Address{}, as.PreCommitControl...) configCtl = append(configCtl, as.CommitControl...) configCtl = append(configCtl, as.TerminateControl...) + configCtl = append(configCtl, as.DealPublishControl...) for _, addr := range configCtl { if addr.Protocol() != address.ID {