diff --git a/.circleci/config.yml b/.circleci/config.yml index 9a74a523d..d3f77c5f1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -815,7 +815,7 @@ workflows: name: test-itest-nonce suite: itest-nonce target: "./itests/nonce_test.go" - + - test: name: test-itest-paych_api suite: itest-paych_api @@ -835,7 +835,12 @@ workflows: name: test-itest-sector_finalize_early suite: itest-sector_finalize_early target: "./itests/sector_finalize_early_test.go" - + + - test: + name: test-itest-sector_miner_collateral + suite: itest-sector_miner_collateral + target: "./itests/sector_miner_collateral_test.go" + - test: name: test-itest-sector_pledge suite: itest-sector_pledge diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index 63bd3c7db..312fb7346 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -225,7 +225,7 @@ func (b *CommitBatcher) maybeStartBatch(notif bool) ([]sealiface.CommitBatchRes, } if individual { - res, err = b.processIndividually() + res, err = b.processIndividually(cfg) } else { res, err = b.processBatch(cfg) } @@ -342,6 +342,10 @@ func (b *CommitBatcher) processBatch(cfg sealiface.Config) ([]sealiface.CommitBa needFunds := big.Add(collateral, aggFee) + if cfg.CollateralFromMinerBalance { + needFunds = big.Zero() + } + goodFunds := big.Add(maxFee, needFunds) from, _, err := b.addrSel(b.mctx, mi, api.CommitAddr, goodFunds, needFunds) @@ -361,7 +365,7 @@ func (b *CommitBatcher) processBatch(cfg sealiface.Config) ([]sealiface.CommitBa return []sealiface.CommitBatchRes{res}, nil } -func (b *CommitBatcher) processIndividually() ([]sealiface.CommitBatchRes, error) { +func (b *CommitBatcher) processIndividually(cfg sealiface.Config) ([]sealiface.CommitBatchRes, error) { mi, err := b.api.StateMinerInfo(b.mctx, b.maddr, nil) if err != nil { return nil, xerrors.Errorf("couldn't get miner info: %w", err) @@ -380,7 +384,7 @@ func (b *CommitBatcher) processIndividually() ([]sealiface.CommitBatchRes, error FailedSectors: map[abi.SectorNumber]string{}, } - mcid, err := b.processSingle(mi, sn, info, tok) + mcid, err := b.processSingle(cfg, mi, sn, info, tok) if err != nil { log.Errorf("process single error: %+v", err) // todo: return to user r.FailedSectors[sn] = err.Error() @@ -394,7 +398,7 @@ func (b *CommitBatcher) processIndividually() ([]sealiface.CommitBatchRes, error return res, nil } -func (b *CommitBatcher) processSingle(mi miner.MinerInfo, sn abi.SectorNumber, info AggregateInput, tok TipSetToken) (cid.Cid, error) { +func (b *CommitBatcher) processSingle(cfg sealiface.Config, mi miner.MinerInfo, sn abi.SectorNumber, info AggregateInput, tok TipSetToken) (cid.Cid, error) { enc := new(bytes.Buffer) params := &miner.ProveCommitSectorParams{ SectorNumber: sn, @@ -410,6 +414,10 @@ func (b *CommitBatcher) processSingle(mi miner.MinerInfo, sn abi.SectorNumber, i return cid.Undef, err } + if cfg.CollateralFromMinerBalance { + collateral = big.Zero() + } + goodFunds := big.Add(collateral, big.Int(b.feeCfg.MaxCommitGasFee)) from, _, err := b.addrSel(b.mctx, mi, api.CommitAddr, goodFunds, collateral) diff --git a/extern/storage-sealing/precommit_batch.go b/extern/storage-sealing/precommit_batch.go index 8b132a2eb..2b00fb792 100644 --- a/extern/storage-sealing/precommit_batch.go +++ b/extern/storage-sealing/precommit_batch.go @@ -225,6 +225,9 @@ func (b *PreCommitBatcher) processBatch(cfg sealiface.Config) ([]sealiface.PreCo params.Sectors = append(params.Sectors, *p.pci) deposit = big.Add(deposit, p.deposit) } + if cfg.CollateralFromMinerBalance { + deposit = big.Zero() + } enc := new(bytes.Buffer) if err := params.MarshalCBOR(enc); err != nil { diff --git a/extern/storage-sealing/sealiface/config.go b/extern/storage-sealing/sealiface/config.go index 0410b92c0..7233895d6 100644 --- a/extern/storage-sealing/sealiface/config.go +++ b/extern/storage-sealing/sealiface/config.go @@ -24,6 +24,8 @@ type Config struct { FinalizeEarly bool + CollateralFromMinerBalance bool + BatchPreCommits bool MaxPreCommitBatch int PreCommitBatchWait time.Duration diff --git a/extern/storage-sealing/states_sealing.go b/extern/storage-sealing/states_sealing.go index 5c798f4eb..710883be8 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -357,11 +357,16 @@ func (m *Sealing) handlePreCommitting(ctx statemachine.Context, sector SectorInf } } - params, deposit, tok, err := m.preCommitParams(ctx, sector) + params, pcd, tok, err := m.preCommitParams(ctx, sector) if params == nil || err != nil { return err } + deposit := pcd + if cfg.CollateralFromMinerBalance { + deposit = big.Zero() // pay using available miner balance + } + enc := new(bytes.Buffer) if err := params.MarshalCBOR(enc); err != nil { return ctx.Send(SectorChainPreCommitFailed{xerrors.Errorf("could not serialize pre-commit sector parameters: %w", err)}) @@ -389,7 +394,7 @@ func (m *Sealing) handlePreCommitting(ctx statemachine.Context, sector SectorInf return ctx.Send(SectorChainPreCommitFailed{xerrors.Errorf("pushing message to mpool: %w", err)}) } - return ctx.Send(SectorPreCommitted{Message: mcid, PreCommitDeposit: deposit, PreCommitInfo: *params}) + return ctx.Send(SectorPreCommitted{Message: mcid, PreCommitDeposit: pcd, PreCommitInfo: *params}) } func (m *Sealing) handleSubmitPreCommitBatch(ctx statemachine.Context, sector SectorInfo) error { @@ -628,6 +633,10 @@ func (m *Sealing) handleSubmitCommit(ctx statemachine.Context, sector SectorInfo collateral = big.Zero() } + if cfg.CollateralFromMinerBalance { + collateral = big.Zero() // pay using available miner balance + } + goodFunds := big.Add(collateral, big.Int(m.feeCfg.MaxCommitGasFee)) from, _, err := m.addrSel(ctx.Context(), mi, api.CommitAddr, goodFunds, collateral) diff --git a/itests/sector_miner_collateral_test.go b/itests/sector_miner_collateral_test.go new file mode 100644 index 000000000..229f594cc --- /dev/null +++ b/itests/sector_miner_collateral_test.go @@ -0,0 +1,129 @@ +package itests + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-state-types/big" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" + sealing "github.com/filecoin-project/lotus/extern/storage-sealing" + "github.com/filecoin-project/lotus/extern/storage-sealing/sealiface" + "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/node" + "github.com/filecoin-project/lotus/node/modules/dtypes" + "github.com/filecoin-project/lotus/node/repo" +) + +func TestMinerBalanceCollateral(t *testing.T) { + kit.QuietMiningLogs() + + blockTime := 5 * time.Millisecond + + runTest := func(t *testing.T, enabled bool, nSectors int, batching bool) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + opts := kit.ConstructorOpts( + kit.LatestActorsAt(-1), + node.ApplyIf(node.IsType(repo.StorageMiner), node.Override(new(dtypes.GetSealingConfigFunc), func() (dtypes.GetSealingConfigFunc, error) { + return func() (sealiface.Config, error) { + return sealiface.Config{ + MaxWaitDealsSectors: 4, + MaxSealingSectors: 4, + MaxSealingSectorsForDeals: 4, + AlwaysKeepUnsealedCopy: true, + WaitDealsDelay: time.Hour, + + BatchPreCommits: batching, + AggregateCommits: batching, + + PreCommitBatchWait: time.Hour, + CommitBatchWait: time.Hour, + + MinCommitBatch: nSectors, + MaxPreCommitBatch: nSectors, + MaxCommitBatch: nSectors, + + CollateralFromMinerBalance: enabled, + }, nil + }, nil + })), + ) + full, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) + ens.InterconnectAll().BeginMining(blockTime) + full.WaitTillChain(ctx, kit.HeightAtLeast(10)) + + toCheck := miner.StartPledge(ctx, nSectors, 0, nil) + + for len(toCheck) > 0 { + states := map[api.SectorState]int{} + for n := range toCheck { + st, err := miner.StorageMiner.SectorsStatus(ctx, n, false) + require.NoError(t, err) + states[st.State]++ + if st.State == api.SectorState(sealing.Proving) { + delete(toCheck, n) + } + if strings.Contains(string(st.State), "Fail") { + t.Fatal("sector in a failed state", st.State) + } + } + + build.Clock.Sleep(100 * time.Millisecond) + } + + // check that sector messages had zero value set + sl, err := miner.SectorsList(ctx) + require.NoError(t, err) + + for _, number := range sl { + si, err := miner.SectorsStatus(ctx, number, false) + require.NoError(t, err) + + require.NotNil(t, si.PreCommitMsg) + pc, err := full.ChainGetMessage(ctx, *si.PreCommitMsg) + require.NoError(t, err) + if enabled { + require.Equal(t, big.Zero(), pc.Value) + } else { + require.NotEqual(t, big.Zero(), pc.Value) + } + + require.NotNil(t, si.CommitMsg) + c, err := full.ChainGetMessage(ctx, *si.CommitMsg) + require.NoError(t, err) + if enabled { + require.Equal(t, big.Zero(), c.Value) + } + // commit value might be zero even with !enabled because in test devnets + // precommit deposit tends to be greater than collateral required at + // commit time. + } + } + + t.Run("nobatch", func(t *testing.T) { + runTest(t, true, 1, false) + }) + t.Run("batch-1", func(t *testing.T) { + runTest(t, true, 1, true) // individual commit instead of aggregate + }) + t.Run("batch-4", func(t *testing.T) { + runTest(t, true, 4, true) + }) + + t.Run("nobatch-frombalance-disabled", func(t *testing.T) { + runTest(t, false, 1, false) + }) + t.Run("batch-1-frombalance-disabled", func(t *testing.T) { + runTest(t, false, 1, true) // individual commit instead of aggregate + }) + t.Run("batch-4-frombalance-disabled", func(t *testing.T) { + runTest(t, false, 4, true) + }) +} diff --git a/node/config/def.go b/node/config/def.go index f9a479d75..f474c56ed 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -125,6 +125,9 @@ type SealingConfig struct { // Run sector finalization before submitting sector proof to the chain FinalizeEarly bool + // Whether to use available miner balance for sector collateral instead of sending it with each message + CollateralFromMinerBalance bool + // enable / disable precommit batching (takes effect after nv13) BatchPreCommits bool // maximum precommit batch size - batches will be sent immediately above this size @@ -317,12 +320,13 @@ func DefaultStorageMiner() *StorageMiner { Common: defCommon(), Sealing: SealingConfig{ - MaxWaitDealsSectors: 2, // 64G with 32G sectors - MaxSealingSectors: 0, - MaxSealingSectorsForDeals: 0, - WaitDealsDelay: Duration(time.Hour * 6), - AlwaysKeepUnsealedCopy: true, - FinalizeEarly: false, + MaxWaitDealsSectors: 2, // 64G with 32G sectors + MaxSealingSectors: 0, + MaxSealingSectorsForDeals: 0, + WaitDealsDelay: Duration(time.Hour * 6), + AlwaysKeepUnsealedCopy: true, + FinalizeEarly: false, + CollateralFromMinerBalance: false, BatchPreCommits: true, MaxPreCommitBatch: miner5.PreCommitSectorBatchMaxSize, // up to 256 sectors diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 0f281327e..fba79785d 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -863,12 +863,13 @@ func NewSetSealConfigFunc(r repo.LockedRepo) (dtypes.SetSealingConfigFunc, error return func(cfg sealiface.Config) (err error) { err = mutateCfg(r, func(c *config.StorageMiner) { c.Sealing = config.SealingConfig{ - MaxWaitDealsSectors: cfg.MaxWaitDealsSectors, - MaxSealingSectors: cfg.MaxSealingSectors, - MaxSealingSectorsForDeals: cfg.MaxSealingSectorsForDeals, - WaitDealsDelay: config.Duration(cfg.WaitDealsDelay), - AlwaysKeepUnsealedCopy: cfg.AlwaysKeepUnsealedCopy, - FinalizeEarly: cfg.FinalizeEarly, + MaxWaitDealsSectors: cfg.MaxWaitDealsSectors, + MaxSealingSectors: cfg.MaxSealingSectors, + MaxSealingSectorsForDeals: cfg.MaxSealingSectorsForDeals, + WaitDealsDelay: config.Duration(cfg.WaitDealsDelay), + AlwaysKeepUnsealedCopy: cfg.AlwaysKeepUnsealedCopy, + FinalizeEarly: cfg.FinalizeEarly, + CollateralFromMinerBalance: cfg.CollateralFromMinerBalance, BatchPreCommits: cfg.BatchPreCommits, MaxPreCommitBatch: cfg.MaxPreCommitBatch, @@ -893,12 +894,13 @@ func NewSetSealConfigFunc(r repo.LockedRepo) (dtypes.SetSealingConfigFunc, error func ToSealingConfig(cfg *config.StorageMiner) sealiface.Config { return sealiface.Config{ - MaxWaitDealsSectors: cfg.Sealing.MaxWaitDealsSectors, - MaxSealingSectors: cfg.Sealing.MaxSealingSectors, - MaxSealingSectorsForDeals: cfg.Sealing.MaxSealingSectorsForDeals, - WaitDealsDelay: time.Duration(cfg.Sealing.WaitDealsDelay), - AlwaysKeepUnsealedCopy: cfg.Sealing.AlwaysKeepUnsealedCopy, - FinalizeEarly: cfg.Sealing.FinalizeEarly, + MaxWaitDealsSectors: cfg.Sealing.MaxWaitDealsSectors, + MaxSealingSectors: cfg.Sealing.MaxSealingSectors, + MaxSealingSectorsForDeals: cfg.Sealing.MaxSealingSectorsForDeals, + WaitDealsDelay: time.Duration(cfg.Sealing.WaitDealsDelay), + AlwaysKeepUnsealedCopy: cfg.Sealing.AlwaysKeepUnsealedCopy, + FinalizeEarly: cfg.Sealing.FinalizeEarly, + CollateralFromMinerBalance: cfg.Sealing.CollateralFromMinerBalance, BatchPreCommits: cfg.Sealing.BatchPreCommits, MaxPreCommitBatch: cfg.Sealing.MaxPreCommitBatch,