From 8632a05ac3834763a87bda9a9aab0a8d19bfe490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 10 Mar 2021 16:16:44 +0100 Subject: [PATCH 01/17] initial proof deps --- chain/vm/syscalls.go | 36 ++++++++++++++++++++---------------- extern/filecoin-ffi | 2 +- go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/chain/vm/syscalls.go b/chain/vm/syscalls.go index 0bcfe10a78a..cb648f5ce10 100644 --- a/chain/vm/syscalls.go +++ b/chain/vm/syscalls.go @@ -26,8 +26,8 @@ import ( "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" "github.com/filecoin-project/lotus/lib/sigs" - runtime2 "github.com/filecoin-project/specs-actors/v2/actors/runtime" - proof2 "github.com/filecoin-project/specs-actors/v2/actors/runtime/proof" + runtime3 "github.com/filecoin-project/specs-actors/v3/actors/runtime" + proof3 "github.com/filecoin-project/specs-actors/v3/actors/runtime/proof" ) func init() { @@ -36,10 +36,10 @@ func init() { // Actual type is defined in chain/types/vmcontext.go because the VMContext interface is there -type SyscallBuilder func(ctx context.Context, rt *Runtime) runtime2.Syscalls +type SyscallBuilder func(ctx context.Context, rt *Runtime) runtime3.Syscalls func Syscalls(verifier ffiwrapper.Verifier) SyscallBuilder { - return func(ctx context.Context, rt *Runtime) runtime2.Syscalls { + return func(ctx context.Context, rt *Runtime) runtime3.Syscalls { return &syscallShim{ ctx: ctx, @@ -90,7 +90,7 @@ func (ss *syscallShim) HashBlake2b(data []byte) [32]byte { // Checks validity of the submitted consensus fault with the two block headers needed to prove the fault // and an optional extra one to check common ancestry (as needed). // Note that the blocks are ordered: the method requires a.Epoch() <= b.Epoch(). -func (ss *syscallShim) VerifyConsensusFault(a, b, extra []byte) (*runtime2.ConsensusFault, error) { +func (ss *syscallShim) VerifyConsensusFault(a, b, extra []byte) (*runtime3.ConsensusFault, error) { // Note that block syntax is not validated. Any validly signed block will be accepted pursuant to the below conditions. // Whether or not it could ever have been accepted in a chain is not checked/does not matter here. // for that reason when checking block parent relationships, rather than instantiating a Tipset to do so @@ -133,14 +133,14 @@ func (ss *syscallShim) VerifyConsensusFault(a, b, extra []byte) (*runtime2.Conse } // (2) check for the consensus faults themselves - var consensusFault *runtime2.ConsensusFault + var consensusFault *runtime3.ConsensusFault // (a) double-fork mining fault if blockA.Height == blockB.Height { - consensusFault = &runtime2.ConsensusFault{ + consensusFault = &runtime3.ConsensusFault{ Target: blockA.Miner, Epoch: blockB.Height, - Type: runtime2.ConsensusFaultDoubleForkMining, + Type: runtime3.ConsensusFaultDoubleForkMining, } } @@ -148,10 +148,10 @@ func (ss *syscallShim) VerifyConsensusFault(a, b, extra []byte) (*runtime2.Conse // strictly speaking no need to compare heights based on double fork mining check above, // but at same height this would be a different fault. if types.CidArrsEqual(blockA.Parents, blockB.Parents) && blockA.Height != blockB.Height { - consensusFault = &runtime2.ConsensusFault{ + consensusFault = &runtime3.ConsensusFault{ Target: blockA.Miner, Epoch: blockB.Height, - Type: runtime2.ConsensusFaultTimeOffsetMining, + Type: runtime3.ConsensusFaultTimeOffsetMining, } } @@ -171,10 +171,10 @@ func (ss *syscallShim) VerifyConsensusFault(a, b, extra []byte) (*runtime2.Conse if types.CidArrsEqual(blockA.Parents, blockC.Parents) && blockA.Height == blockC.Height && types.CidArrsContains(blockB.Parents, blockC.Cid()) && !types.CidArrsContains(blockB.Parents, blockA.Cid()) { - consensusFault = &runtime2.ConsensusFault{ + consensusFault = &runtime3.ConsensusFault{ Target: blockA.Miner, Epoch: blockB.Height, - Type: runtime2.ConsensusFaultParentGrinding, + Type: runtime3.ConsensusFaultParentGrinding, } } } @@ -243,7 +243,7 @@ func (ss *syscallShim) workerKeyAtLookback(height abi.ChainEpoch) (address.Addre return ResolveToKeyAddr(ss.cstate, ss.cst, info.Worker) } -func (ss *syscallShim) VerifyPoSt(proof proof2.WindowPoStVerifyInfo) error { +func (ss *syscallShim) VerifyPoSt(proof proof3.WindowPoStVerifyInfo) error { ok, err := ss.verifier.VerifyWindowPoSt(context.TODO(), proof) if err != nil { return err @@ -254,7 +254,7 @@ func (ss *syscallShim) VerifyPoSt(proof proof2.WindowPoStVerifyInfo) error { return nil } -func (ss *syscallShim) VerifySeal(info proof2.SealVerifyInfo) error { +func (ss *syscallShim) VerifySeal(info proof3.SealVerifyInfo) error { //_, span := trace.StartSpan(ctx, "ValidatePoRep") //defer span.End() @@ -281,6 +281,10 @@ func (ss *syscallShim) VerifySeal(info proof2.SealVerifyInfo) error { return nil } +func (ss *syscallShim) VerifyAggregateSeals(aggregate proof3.AggregateSealVerifyProofAndInfos) error { + aggregate. +} + func (ss *syscallShim) VerifySignature(sig crypto.Signature, addr address.Address, input []byte) error { // TODO: in genesis setup, we are currently faking signatures @@ -294,7 +298,7 @@ func (ss *syscallShim) VerifySignature(sig crypto.Signature, addr address.Addres var BatchSealVerifyParallelism = goruntime.NumCPU() -func (ss *syscallShim) BatchVerifySeals(inp map[address.Address][]proof2.SealVerifyInfo) (map[address.Address][]bool, error) { +func (ss *syscallShim) BatchVerifySeals(inp map[address.Address][]proof3.SealVerifyInfo) (map[address.Address][]bool, error) { out := make(map[address.Address][]bool) sema := make(chan struct{}, BatchSealVerifyParallelism) @@ -306,7 +310,7 @@ func (ss *syscallShim) BatchVerifySeals(inp map[address.Address][]proof2.SealVer for i, s := range seals { wg.Add(1) - go func(ma address.Address, ix int, svi proof2.SealVerifyInfo, res []bool) { + go func(ma address.Address, ix int, svi proof3.SealVerifyInfo, res []bool) { defer wg.Done() sema <- struct{}{} diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index b6e0b35fb49..a8605976ac4 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit b6e0b35fb49ed0fedb6a6a473b222e3b8a7d7f17 +Subproject commit a8605976ac40f2cf865d86025d22412cae4298e7 diff --git a/go.mod b/go.mod index 428c6cd38c4..da2a5308151 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b github.com/filecoin-project/specs-actors v0.9.13 github.com/filecoin-project/specs-actors/v2 v2.3.4 - github.com/filecoin-project/specs-actors/v3 v3.0.3 + github.com/filecoin-project/specs-actors/v3 v3.0.4-0.20210227000520-b3317b86f4d1 github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506 github.com/filecoin-project/test-vectors/schema v0.0.5 github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1 diff --git a/go.sum b/go.sum index 9ac1f33f70b..d1ded92867f 100644 --- a/go.sum +++ b/go.sum @@ -311,8 +311,8 @@ github.com/filecoin-project/specs-actors/v2 v2.0.1/go.mod h1:v2NZVYinNIKA9acEMBm github.com/filecoin-project/specs-actors/v2 v2.3.2/go.mod h1:UuJQLoTx/HPvvWeqlIFmC/ywlOLHNe8SNQ3OunFbu2Y= github.com/filecoin-project/specs-actors/v2 v2.3.4 h1:NZK2oMCcA71wNsUzDBmLQyRMzcCnX9tDGvwZ53G67j8= github.com/filecoin-project/specs-actors/v2 v2.3.4/go.mod h1:UuJQLoTx/HPvvWeqlIFmC/ywlOLHNe8SNQ3OunFbu2Y= -github.com/filecoin-project/specs-actors/v3 v3.0.3 h1:bq9B1Jnq+Z0A+Yj3KnYhN3kcTpUyP6Umo3MZgai0BRE= -github.com/filecoin-project/specs-actors/v3 v3.0.3/go.mod h1:oMcmEed6B7H/wHabM3RQphTIhq0ibAKsbpYs+bQ/uxQ= +github.com/filecoin-project/specs-actors/v3 v3.0.4-0.20210227000520-b3317b86f4d1 h1:l2zH+oxJwy814pRj4umLBEFlPhoccLCpMrRjWMbKiBk= +github.com/filecoin-project/specs-actors/v3 v3.0.4-0.20210227000520-b3317b86f4d1/go.mod h1:oMcmEed6B7H/wHabM3RQphTIhq0ibAKsbpYs+bQ/uxQ= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506 h1:Ur/l2+6qN+lQiqjozWWc5p9UDaAMDZKTlDS98oRnlIw= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506/go.mod h1:nJRRM7Aa9XVvygr3W9k6xGF46RWzr2zxF/iGoAIfA/g= github.com/filecoin-project/test-vectors/schema v0.0.5 h1:w3zHQhzM4pYxJDl21avXjOKBLF8egrvwUwjpT8TquDg= From 711c1762806728ac4e93f83d589aa407edddeac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 10 Mar 2021 16:54:06 +0100 Subject: [PATCH 02/17] VerifyAggregateSeals plumbing --- chain/gen/gen.go | 20 +++++++++++-------- chain/gen/genesis/miners.go | 6 +++--- chain/vm/syscalls.go | 10 +++++++++- extern/sector-storage/ffiwrapper/types.go | 9 +++++---- .../sector-storage/ffiwrapper/verifier_cgo.go | 19 ++++++++++-------- 5 files changed, 40 insertions(+), 24 deletions(-) diff --git a/chain/gen/gen.go b/chain/gen/gen.go index d06c755fa34..d458fbd2d21 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -24,7 +24,7 @@ import ( "go.opencensus.io/trace" "golang.org/x/xerrors" - proof2 "github.com/filecoin-project/specs-actors/v2/actors/runtime/proof" + proof3 "github.com/filecoin-project/specs-actors/v2/actors/runtime/proof" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/blockstore" @@ -50,7 +50,7 @@ const msgsPerBlock = 20 //nolint:deadcode,varcheck var log = logging.Logger("gen") -var ValidWpostForTesting = []proof2.PoStProof{{ +var ValidWpostForTesting = []proof3.PoStProof{{ ProofBytes: []byte("valid proof"), }} @@ -459,7 +459,7 @@ func (cg *ChainGen) NextTipSetFromMinersWithMessages(base *types.TipSet, miners func (cg *ChainGen) makeBlock(parents *types.TipSet, m address.Address, vrfticket *types.Ticket, eticket *types.ElectionProof, bvals []types.BeaconEntry, height abi.ChainEpoch, - wpost []proof2.PoStProof, msgs []*types.SignedMessage) (*types.FullBlock, error) { + wpost []proof3.PoStProof, msgs []*types.SignedMessage) (*types.FullBlock, error) { var ts uint64 if cg.Timestamper != nil { @@ -597,7 +597,7 @@ func (mca mca) WalletSign(ctx context.Context, a address.Address, v []byte) (*cr type WinningPoStProver interface { GenerateCandidates(context.Context, abi.PoStRandomness, uint64) ([]uint64, error) - ComputeProof(context.Context, []proof2.SectorInfo, abi.PoStRandomness) ([]proof2.PoStProof, error) + ComputeProof(context.Context, []proof3.SectorInfo, abi.PoStRandomness) ([]proof3.PoStProof, error) } type wppProvider struct{} @@ -606,7 +606,7 @@ func (wpp *wppProvider) GenerateCandidates(ctx context.Context, _ abi.PoStRandom return []uint64{0}, nil } -func (wpp *wppProvider) ComputeProof(context.Context, []proof2.SectorInfo, abi.PoStRandomness) ([]proof2.PoStProof, error) { +func (wpp *wppProvider) ComputeProof(context.Context, []proof3.SectorInfo, abi.PoStRandomness) ([]proof3.PoStProof, error) { return ValidWpostForTesting, nil } @@ -673,15 +673,19 @@ type genFakeVerifier struct{} var _ ffiwrapper.Verifier = (*genFakeVerifier)(nil) -func (m genFakeVerifier) VerifySeal(svi proof2.SealVerifyInfo) (bool, error) { +func (m genFakeVerifier) VerifySeal(svi proof3.SealVerifyInfo) (bool, error) { return true, nil } -func (m genFakeVerifier) VerifyWinningPoSt(ctx context.Context, info proof2.WinningPoStVerifyInfo) (bool, error) { +func (m genFakeVerifier) VerifyAggregateSeals(aggregate proof3.AggregateSealVerifyProofAndInfos) (bool, error) { panic("not supported") } -func (m genFakeVerifier) VerifyWindowPoSt(ctx context.Context, info proof2.WindowPoStVerifyInfo) (bool, error) { +func (m genFakeVerifier) VerifyWinningPoSt(ctx context.Context, info proof3.WinningPoStVerifyInfo) (bool, error) { + panic("not supported") +} + +func (m genFakeVerifier) VerifyWindowPoSt(ctx context.Context, info proof3.WindowPoStVerifyInfo) (bool, error) { panic("not supported") } diff --git a/chain/gen/genesis/miners.go b/chain/gen/genesis/miners.go index 297543886dd..c57c75e8979 100644 --- a/chain/gen/genesis/miners.go +++ b/chain/gen/genesis/miners.go @@ -27,7 +27,7 @@ import ( miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" power0 "github.com/filecoin-project/specs-actors/actors/builtin/power" reward0 "github.com/filecoin-project/specs-actors/actors/builtin/reward" - runtime2 "github.com/filecoin-project/specs-actors/v2/actors/runtime" + runtime3 "github.com/filecoin-project/specs-actors/v3/actors/runtime" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/store" @@ -46,7 +46,7 @@ func MinerAddress(genesisIndex uint64) address.Address { } type fakedSigSyscalls struct { - runtime2.Syscalls + runtime3.Syscalls } func (fss *fakedSigSyscalls) VerifySignature(signature crypto.Signature, signer address.Address, plaintext []byte) error { @@ -54,7 +54,7 @@ func (fss *fakedSigSyscalls) VerifySignature(signature crypto.Signature, signer } func mkFakedSigSyscalls(base vm.SyscallBuilder) vm.SyscallBuilder { - return func(ctx context.Context, rt *vm.Runtime) runtime2.Syscalls { + return func(ctx context.Context, rt *vm.Runtime) runtime3.Syscalls { return &fakedSigSyscalls{ base(ctx, rt), } diff --git a/chain/vm/syscalls.go b/chain/vm/syscalls.go index cb648f5ce10..024a1223dde 100644 --- a/chain/vm/syscalls.go +++ b/chain/vm/syscalls.go @@ -282,7 +282,15 @@ func (ss *syscallShim) VerifySeal(info proof3.SealVerifyInfo) error { } func (ss *syscallShim) VerifyAggregateSeals(aggregate proof3.AggregateSealVerifyProofAndInfos) error { - aggregate. + ok, err := ss.verifier.VerifyAggregateSeals(aggregate) + if err != nil { + return xerrors.Errorf("failed to verify aggregated PoRep: %w", err) + } + if !ok { + return fmt.Errorf("invalid aggredate proof") + } + + return nil } func (ss *syscallShim) VerifySignature(sig crypto.Signature, addr address.Address, input []byte) error { diff --git a/extern/sector-storage/ffiwrapper/types.go b/extern/sector-storage/ffiwrapper/types.go index b7e96636a93..5c44b67b28b 100644 --- a/extern/sector-storage/ffiwrapper/types.go +++ b/extern/sector-storage/ffiwrapper/types.go @@ -4,7 +4,7 @@ import ( "context" "io" - proof2 "github.com/filecoin-project/specs-actors/v2/actors/runtime/proof" + proof3 "github.com/filecoin-project/specs-actors/v3/actors/runtime/proof" "github.com/ipfs/go-cid" @@ -34,9 +34,10 @@ type Storage interface { } type Verifier interface { - VerifySeal(proof2.SealVerifyInfo) (bool, error) - VerifyWinningPoSt(ctx context.Context, info proof2.WinningPoStVerifyInfo) (bool, error) - VerifyWindowPoSt(ctx context.Context, info proof2.WindowPoStVerifyInfo) (bool, error) + VerifySeal(proof3.SealVerifyInfo) (bool, error) + VerifyAggregateSeals(aggregate proof3.AggregateSealVerifyProofAndInfos) (bool, error) + VerifyWinningPoSt(ctx context.Context, info proof3.WinningPoStVerifyInfo) (bool, error) + VerifyWindowPoSt(ctx context.Context, info proof3.WindowPoStVerifyInfo) (bool, error) GenerateWinningPoStSectorChallenge(context.Context, abi.RegisteredPoStProof, abi.ActorID, abi.PoStRandomness, uint64) ([]uint64, error) } diff --git a/extern/sector-storage/ffiwrapper/verifier_cgo.go b/extern/sector-storage/ffiwrapper/verifier_cgo.go index 15e0e6ab390..048b6a1502a 100644 --- a/extern/sector-storage/ffiwrapper/verifier_cgo.go +++ b/extern/sector-storage/ffiwrapper/verifier_cgo.go @@ -4,19 +4,18 @@ package ffiwrapper import ( "context" - "go.opencensus.io/trace" "golang.org/x/xerrors" ffi "github.com/filecoin-project/filecoin-ffi" "github.com/filecoin-project/go-state-types/abi" - proof2 "github.com/filecoin-project/specs-actors/v2/actors/runtime/proof" + proof3 "github.com/filecoin-project/specs-actors/v3/actors/runtime/proof" "github.com/filecoin-project/specs-storage/storage" "github.com/filecoin-project/lotus/extern/sector-storage/storiface" ) -func (sb *Sealer) GenerateWinningPoSt(ctx context.Context, minerID abi.ActorID, sectorInfo []proof2.SectorInfo, randomness abi.PoStRandomness) ([]proof2.PoStProof, error) { +func (sb *Sealer) GenerateWinningPoSt(ctx context.Context, minerID abi.ActorID, sectorInfo []proof3.SectorInfo, randomness abi.PoStRandomness) ([]proof3.PoStProof, error) { randomness[31] &= 0x3f privsectors, skipped, done, err := sb.pubSectorToPriv(ctx, minerID, sectorInfo, nil, abi.RegisteredSealProof.RegisteredWinningPoStProof) // TODO: FAULTS? if err != nil { @@ -30,7 +29,7 @@ func (sb *Sealer) GenerateWinningPoSt(ctx context.Context, minerID abi.ActorID, return ffi.GenerateWinningPoSt(minerID, privsectors, randomness) } -func (sb *Sealer) GenerateWindowPoSt(ctx context.Context, minerID abi.ActorID, sectorInfo []proof2.SectorInfo, randomness abi.PoStRandomness) ([]proof2.PoStProof, []abi.SectorID, error) { +func (sb *Sealer) GenerateWindowPoSt(ctx context.Context, minerID abi.ActorID, sectorInfo []proof3.SectorInfo, randomness abi.PoStRandomness) ([]proof3.PoStProof, []abi.SectorID, error) { randomness[31] &= 0x3f privsectors, skipped, done, err := sb.pubSectorToPriv(ctx, minerID, sectorInfo, nil, abi.RegisteredSealProof.RegisteredWindowPoStProof) if err != nil { @@ -55,7 +54,7 @@ func (sb *Sealer) GenerateWindowPoSt(ctx context.Context, minerID abi.ActorID, s return proof, faultyIDs, err } -func (sb *Sealer) pubSectorToPriv(ctx context.Context, mid abi.ActorID, sectorInfo []proof2.SectorInfo, faults []abi.SectorNumber, rpt func(abi.RegisteredSealProof) (abi.RegisteredPoStProof, error)) (ffi.SortedPrivateSectorInfo, []abi.SectorID, func(), error) { +func (sb *Sealer) pubSectorToPriv(ctx context.Context, mid abi.ActorID, sectorInfo []proof3.SectorInfo, faults []abi.SectorNumber, rpt func(abi.RegisteredSealProof) (abi.RegisteredPoStProof, error)) (ffi.SortedPrivateSectorInfo, []abi.SectorID, func(), error) { fmap := map[abi.SectorNumber]struct{}{} for _, fault := range faults { fmap[fault] = struct{}{} @@ -111,11 +110,15 @@ type proofVerifier struct{} var ProofVerifier = proofVerifier{} -func (proofVerifier) VerifySeal(info proof2.SealVerifyInfo) (bool, error) { +func (proofVerifier) VerifySeal(info proof3.SealVerifyInfo) (bool, error) { return ffi.VerifySeal(info) } -func (proofVerifier) VerifyWinningPoSt(ctx context.Context, info proof2.WinningPoStVerifyInfo) (bool, error) { +func (proofVerifier) VerifyAggregateSeals(aggregate proof3.AggregateSealVerifyProofAndInfos) (bool, error) { + return ffi.VerifyAggregateSeals(aggregate) +} + +func (proofVerifier) VerifyWinningPoSt(ctx context.Context, info proof3.WinningPoStVerifyInfo) (bool, error) { info.Randomness[31] &= 0x3f _, span := trace.StartSpan(ctx, "VerifyWinningPoSt") defer span.End() @@ -123,7 +126,7 @@ func (proofVerifier) VerifyWinningPoSt(ctx context.Context, info proof2.WinningP return ffi.VerifyWinningPoSt(info) } -func (proofVerifier) VerifyWindowPoSt(ctx context.Context, info proof2.WindowPoStVerifyInfo) (bool, error) { +func (proofVerifier) VerifyWindowPoSt(ctx context.Context, info proof3.WindowPoStVerifyInfo) (bool, error) { info.Randomness[31] &= 0x3f _, span := trace.StartSpan(ctx, "VerifyWindowPoSt") defer span.End() From 173f70f00196eedf01fb50e4aa7cf5b7912c2b8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 10 Mar 2021 17:20:58 +0100 Subject: [PATCH 03/17] CommitBatcher scaffolding --- extern/storage-sealing/commit_batch.go | 128 +++++++++++++++++++++++++ extern/storage-sealing/sealing.go | 2 + 2 files changed, 130 insertions(+) create mode 100644 extern/storage-sealing/commit_batch.go diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go new file mode 100644 index 00000000000..03938d50929 --- /dev/null +++ b/extern/storage-sealing/commit_batch.go @@ -0,0 +1,128 @@ +package sealing + +import ( + "context" + "sync" + "time" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" +) + +var ( + // TODO: config! + + CommitBatchMax uint64 = 400 // adjust based on real-world gas numbers + CommitBatchMin uint64 = 5 + CommitBatchWait = 5 * time.Minute +) + +type CommitBatcherApi interface { + SendMsg(ctx context.Context, from, to address.Address, method abi.MethodNum, value, maxFee abi.TokenAmount, params []byte) (cid.Cid, error) +} + +type CommitBatcher struct { + api CommitBatcherApi + maddr address.Address + mctx context.Context + addrSel AddrSel + feeCfg FeeConfig + + /*todo map[SectorLocation]*bitfield.BitField // MinerSectorLocation -> BitField + + waiting map[abi.SectorNumber][]chan cid.Cid*/ + + notify, stop, stopped chan struct{} + force chan chan *cid.Cid + lk sync.Mutex +} + +func NewCommitBatcher(mctx context.Context, maddr address.Address, api CommitBatcherApi, addrSel AddrSel, feeCfg FeeConfig) *CommitBatcher { + b := &CommitBatcher{ + api: api, + maddr: maddr, + mctx: mctx, + addrSel: addrSel, + feeCfg: feeCfg, + + notify: make(chan struct{}, 1), + force: make(chan chan *cid.Cid), + stop: make(chan struct{}), + stopped: make(chan struct{}), + } + + go b.run() + + return b +} + +func (b *CommitBatcher) run() { + var forceRes chan *cid.Cid + var lastMsg *cid.Cid + + for { + if forceRes != nil { + forceRes <- lastMsg + forceRes = nil + } + lastMsg = nil + + var sendAboveMax, sendAboveMin bool + select { + case <-b.stop: + close(b.stopped) + return + case <-b.notify: + sendAboveMax = true + case <-time.After(TerminateBatchWait): + sendAboveMin = true + case fr := <-b.force: // user triggered + forceRes = fr + } + + var err error + lastMsg, err = b.processBatch(sendAboveMax, sendAboveMin) + if err != nil { + log.Warnw("TerminateBatcher processBatch error", "error", err) + } + } +} + +func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { + return nil, nil +} + +func (b *CommitBatcher) Flush(ctx context.Context) (*cid.Cid, error) { + resCh := make(chan *cid.Cid, 1) + select { + case b.force <- resCh: + select { + case res := <-resCh: + return res, nil + case <-ctx.Done(): + return nil, ctx.Err() + } + case <-ctx.Done(): + return nil, ctx.Err() + } +} + +func (b *CommitBatcher) Pending(ctx context.Context) ([]abi.SectorID, error) { + b.lk.Lock() + defer b.lk.Unlock() + + panic("todo") +} + +func (b *CommitBatcher) Stop(ctx context.Context) error { + close(b.stop) + + select { + case <-b.stopped: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} diff --git a/extern/storage-sealing/sealing.go b/extern/storage-sealing/sealing.go index 8feca3b7b11..061aa29fcae 100644 --- a/extern/storage-sealing/sealing.go +++ b/extern/storage-sealing/sealing.go @@ -103,6 +103,7 @@ type Sealing struct { stats SectorStats terminator *TerminateBatcher + commiter *CommitBatcher getConfig GetSealingConfigFunc dealInfo *CurrentDealInfoManager @@ -152,6 +153,7 @@ func New(api SealingAPI, fc FeeConfig, events Events, maddr address.Address, ds addrSel: as, terminator: NewTerminationBatcher(context.TODO(), maddr, api, as, fc), + commiter: NewCommitBatcher(context.TODO(), maddr, api, as, fc), getConfig: gc, dealInfo: &CurrentDealInfoManager{api}, From d3c96ce4e05f88b3f09962233cd7bf94007973d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 10 Mar 2021 19:17:35 +0100 Subject: [PATCH 04/17] storagefsm commit aggregation plumbing --- cmd/lotus-storage-miner/info.go | 2 + extern/storage-sealing/commit_batch.go | 40 ++++++-- extern/storage-sealing/fsm.go | 14 +++ extern/storage-sealing/fsm_events.go | 12 +++ extern/storage-sealing/sealiface/config.go | 4 + extern/storage-sealing/sector_state.go | 108 +++++++++++---------- extern/storage-sealing/states_sealing.go | 33 +++++++ node/config/def.go | 4 + node/modules/storageminer.go | 6 ++ 9 files changed, 167 insertions(+), 56 deletions(-) diff --git a/cmd/lotus-storage-miner/info.go b/cmd/lotus-storage-miner/info.go index cf39e5516da..bd42392178b 100644 --- a/cmd/lotus-storage-miner/info.go +++ b/cmd/lotus-storage-miner/info.go @@ -295,6 +295,8 @@ var stateList = []stateMeta{ {col: color.FgYellow, state: sealing.Committing}, {col: color.FgYellow, state: sealing.SubmitCommit}, {col: color.FgYellow, state: sealing.CommitWait}, + {col: color.FgYellow, state: sealing.SubmitCommitAggregate}, + {col: color.FgYellow, state: sealing.CommitAggregateWait}, {col: color.FgYellow, state: sealing.FinalizeSector}, {col: color.FgCyan, state: sealing.Terminating}, diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index 03938d50929..a5dea6ea139 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -9,20 +9,24 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" + proof3 "github.com/filecoin-project/specs-actors/v3/actors/runtime/proof" ) var ( // TODO: config! - CommitBatchMax uint64 = 400 // adjust based on real-world gas numbers - CommitBatchMin uint64 = 5 - CommitBatchWait = 5 * time.Minute + CommitBatchWait = 5 * time.Minute ) type CommitBatcherApi interface { SendMsg(ctx context.Context, from, to address.Address, method abi.MethodNum, value, maxFee abi.TokenAmount, params []byte) (cid.Cid, error) } +type AggregateInput struct { + info proof3.AggregateSealVerifyInfo + proof []byte +} + type CommitBatcher struct { api CommitBatcherApi maddr address.Address @@ -30,9 +34,8 @@ type CommitBatcher struct { addrSel AddrSel feeCfg FeeConfig - /*todo map[SectorLocation]*bitfield.BitField // MinerSectorLocation -> BitField - - waiting map[abi.SectorNumber][]chan cid.Cid*/ + todo map[abi.SectorID]AggregateInput + waiting map[abi.SectorID][]chan cid.Cid notify, stop, stopped chan struct{} force chan chan *cid.Cid @@ -47,6 +50,9 @@ func NewCommitBatcher(mctx context.Context, maddr address.Address, api CommitBat addrSel: addrSel, feeCfg: feeCfg, + todo: map[abi.SectorID]AggregateInput{}, + waiting: map[abi.SectorID][]chan cid.Cid{}, + notify: make(chan struct{}, 1), force: make(chan chan *cid.Cid), stop: make(chan struct{}), @@ -94,6 +100,28 @@ func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { return nil, nil } +// register commit, wait for batch message, return message CID +func (b *CommitBatcher) AddCommit(ctx context.Context, s abi.SectorID, in AggregateInput) (mcid cid.Cid, err error) { + b.lk.Lock() + b.todo[s] = in + + sent := make(chan cid.Cid, 1) + b.waiting[s] = append(b.waiting[s], sent) + + select { + case b.notify <- struct{}{}: + default: // already have a pending notification, don't need more + } + b.lk.Unlock() + + select { + case c := <-sent: + return c, nil + case <-ctx.Done(): + return cid.Undef, ctx.Err() + } +} + func (b *CommitBatcher) Flush(ctx context.Context) (*cid.Cid, error) { resCh := make(chan *cid.Cid, 1) select { diff --git a/extern/storage-sealing/fsm.go b/extern/storage-sealing/fsm.go index 7b60efa68b7..9ed9bdc7e20 100644 --- a/extern/storage-sealing/fsm.go +++ b/extern/storage-sealing/fsm.go @@ -91,12 +91,22 @@ var fsmPlanners = map[SectorState]func(events []statemachine.Event, state *Secto SubmitCommit: planOne( on(SectorCommitSubmitted{}, CommitWait), on(SectorCommitFailed{}, CommitFailed), + on(SectorSubmitCommitAggregate{}, SubmitCommitAggregate), + ), + SubmitCommitAggregate: planOne( + on(SectorCommitAggregateSent{}, CommitWait), + on(SectorCommitFailed{}, CommitFailed), ), CommitWait: planOne( on(SectorProving{}, FinalizeSector), on(SectorCommitFailed{}, CommitFailed), on(SectorRetrySubmitCommit{}, SubmitCommit), ), + CommitAggregateWait: planOne( + on(SectorProving{}, FinalizeSector), + on(SectorCommitFailed{}, CommitFailed), + on(SectorRetrySubmitCommit{}, SubmitCommit), + ), FinalizeSector: planOne( on(SectorFinalized{}, Proving), @@ -330,8 +340,12 @@ func (m *Sealing) plan(events []statemachine.Event, state *SectorInfo) (func(sta return m.handleCommitting, processed, nil case SubmitCommit: return m.handleSubmitCommit, processed, nil + case CommitAggregateWait: + fallthrough case CommitWait: return m.handleCommitWait, processed, nil + case SubmitCommitAggregate: + return m.handleSubmitCommitAggregate, processed, nil case FinalizeSector: return m.handleFinalizeSector, processed, nil diff --git a/extern/storage-sealing/fsm_events.go b/extern/storage-sealing/fsm_events.go index 8d11b248b35..bced1921f4c 100644 --- a/extern/storage-sealing/fsm_events.go +++ b/extern/storage-sealing/fsm_events.go @@ -233,6 +233,10 @@ func (evt SectorCommitted) apply(state *SectorInfo) { state.Proof = evt.Proof } +type SectorSubmitCommitAggregate struct{} + +func (evt SectorSubmitCommitAggregate) apply(*SectorInfo) {} + type SectorCommitSubmitted struct { Message cid.Cid } @@ -241,6 +245,14 @@ func (evt SectorCommitSubmitted) apply(state *SectorInfo) { state.CommitMessage = &evt.Message } +type SectorCommitAggregateSent struct { + Message cid.Cid +} + +func (evt SectorCommitAggregateSent) apply(state *SectorInfo) { + state.CommitMessage = &evt.Message +} + type SectorProving struct{} func (evt SectorProving) apply(*SectorInfo) {} diff --git a/extern/storage-sealing/sealiface/config.go b/extern/storage-sealing/sealiface/config.go index 7ac5f6160d3..f57ac59aede 100644 --- a/extern/storage-sealing/sealiface/config.go +++ b/extern/storage-sealing/sealiface/config.go @@ -17,4 +17,8 @@ type Config struct { WaitDealsDelay time.Duration AlwaysKeepUnsealedCopy bool + + AggregateCommits bool + MinCommitBatch uint64 + MaxCommitBatch uint64 } diff --git a/extern/storage-sealing/sector_state.go b/extern/storage-sealing/sector_state.go index b636614d1e8..b6b7cbf7b3c 100644 --- a/extern/storage-sealing/sector_state.go +++ b/extern/storage-sealing/sector_state.go @@ -3,61 +3,69 @@ package sealing type SectorState string var ExistSectorStateList = map[SectorState]struct{}{ - Empty: {}, - WaitDeals: {}, - Packing: {}, - AddPiece: {}, - AddPieceFailed: {}, - GetTicket: {}, - PreCommit1: {}, - PreCommit2: {}, - PreCommitting: {}, - PreCommitWait: {}, - WaitSeed: {}, - Committing: {}, - SubmitCommit: {}, - CommitWait: {}, - FinalizeSector: {}, - Proving: {}, - FailedUnrecoverable: {}, - SealPreCommit1Failed: {}, - SealPreCommit2Failed: {}, - PreCommitFailed: {}, - ComputeProofFailed: {}, - CommitFailed: {}, - PackingFailed: {}, - FinalizeFailed: {}, - DealsExpired: {}, - RecoverDealIDs: {}, - Faulty: {}, - FaultReported: {}, - FaultedFinal: {}, - Terminating: {}, - TerminateWait: {}, - TerminateFinality: {}, - TerminateFailed: {}, - Removing: {}, - RemoveFailed: {}, - Removed: {}, + Empty: {}, + WaitDeals: {}, + Packing: {}, + AddPiece: {}, + AddPieceFailed: {}, + GetTicket: {}, + PreCommit1: {}, + PreCommit2: {}, + PreCommitting: {}, + PreCommitWait: {}, + WaitSeed: {}, + Committing: {}, + SubmitCommit: {}, + CommitWait: {}, + SubmitCommitAggregate: {}, + CommitAggregateWait: {}, + FinalizeSector: {}, + Proving: {}, + FailedUnrecoverable: {}, + SealPreCommit1Failed: {}, + SealPreCommit2Failed: {}, + PreCommitFailed: {}, + ComputeProofFailed: {}, + CommitFailed: {}, + PackingFailed: {}, + FinalizeFailed: {}, + DealsExpired: {}, + RecoverDealIDs: {}, + Faulty: {}, + FaultReported: {}, + FaultedFinal: {}, + Terminating: {}, + TerminateWait: {}, + TerminateFinality: {}, + TerminateFailed: {}, + Removing: {}, + RemoveFailed: {}, + Removed: {}, } const ( UndefinedSectorState SectorState = "" // happy path - Empty SectorState = "Empty" // deprecated - WaitDeals SectorState = "WaitDeals" // waiting for more pieces (deals) to be added to the sector - AddPiece SectorState = "AddPiece" // put deal data (and padding if required) into the sector - Packing SectorState = "Packing" // sector not in sealStore, and not on chain - GetTicket SectorState = "GetTicket" // generate ticket - PreCommit1 SectorState = "PreCommit1" // do PreCommit1 - PreCommit2 SectorState = "PreCommit2" // do PreCommit2 - PreCommitting SectorState = "PreCommitting" // on chain pre-commit - PreCommitWait SectorState = "PreCommitWait" // waiting for precommit to land on chain - WaitSeed SectorState = "WaitSeed" // waiting for seed - Committing SectorState = "Committing" // compute PoRep - SubmitCommit SectorState = "SubmitCommit" // send commit message to the chain - CommitWait SectorState = "CommitWait" // wait for the commit message to land on chain + Empty SectorState = "Empty" // deprecated + WaitDeals SectorState = "WaitDeals" // waiting for more pieces (deals) to be added to the sector + AddPiece SectorState = "AddPiece" // put deal data (and padding if required) into the sector + Packing SectorState = "Packing" // sector not in sealStore, and not on chain + GetTicket SectorState = "GetTicket" // generate ticket + PreCommit1 SectorState = "PreCommit1" // do PreCommit1 + PreCommit2 SectorState = "PreCommit2" // do PreCommit2 + PreCommitting SectorState = "PreCommitting" // on chain pre-commit + PreCommitWait SectorState = "PreCommitWait" // waiting for precommit to land on chain + WaitSeed SectorState = "WaitSeed" // waiting for seed + Committing SectorState = "Committing" // compute PoRep + + // single commit + SubmitCommit SectorState = "SubmitCommit" // send commit message to the chain + CommitWait SectorState = "CommitWait" // wait for the commit message to land on chain + + SubmitCommitAggregate SectorState = "SubmitCommitAggregate" + CommitAggregateWait SectorState = "CommitAggregateWait" + FinalizeSector SectorState = "FinalizeSector" Proving SectorState = "Proving" // error modes @@ -91,7 +99,7 @@ func toStatState(st SectorState) statSectorState { switch st { case UndefinedSectorState, Empty, WaitDeals, AddPiece: return sstStaging - case Packing, GetTicket, PreCommit1, PreCommit2, PreCommitting, PreCommitWait, WaitSeed, Committing, SubmitCommit, CommitWait, FinalizeSector: + case Packing, GetTicket, PreCommit1, PreCommit2, PreCommitting, PreCommitWait, WaitSeed, Committing, SubmitCommit, CommitWait, SubmitCommitAggregate, CommitAggregateWait, FinalizeSector: return sstSealing case Proving, Removed, Removing, Terminating, TerminateWait, TerminateFinality, TerminateFailed: return sstProving diff --git a/extern/storage-sealing/states_sealing.go b/extern/storage-sealing/states_sealing.go index e371ab33fd6..7211a5fbb2e 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -12,6 +12,7 @@ import ( "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/exitcode" "github.com/filecoin-project/go-statemachine" + "github.com/filecoin-project/specs-actors/v3/actors/runtime/proof" "github.com/filecoin-project/specs-storage/storage" "github.com/filecoin-project/lotus/api" @@ -452,6 +453,14 @@ func (m *Sealing) handleCommitting(ctx statemachine.Context, sector SectorInfo) } func (m *Sealing) handleSubmitCommit(ctx statemachine.Context, sector SectorInfo) error { + cfg, err := m.getConfig() + if err != nil { + return xerrors.Errorf("getting config: %w", err) + } + if cfg.AggregateCommits { + return ctx.Send(SectorSubmitCommitAggregate{}) + } + tok, _, err := m.api.ChainHead(ctx.Context()) if err != nil { log.Errorf("handleCommitting: api error, not proceeding: %+v", err) @@ -514,6 +523,30 @@ func (m *Sealing) handleSubmitCommit(ctx statemachine.Context, sector SectorInfo }) } +func (m *Sealing) handleSubmitCommitAggregate(ctx statemachine.Context, sector SectorInfo) error { + if sector.CommD == nil || sector.CommR == nil { + return ctx.Send(SectorCommitFailed{xerrors.Errorf("sector had nil commR or commD")}) + } + + mcid, err := m.commiter.AddCommit(ctx.Context(), m.minerSectorID(sector.SectorNumber), AggregateInput{ + info: proof.AggregateSealVerifyInfo{ + SealProof: sector.SectorType, + Number: sector.SectorNumber, + DealIDs: sector.dealIDs(), + Randomness: sector.TicketValue, + InteractiveRandomness: sector.SeedValue, + SealedCID: *sector.CommR, + UnsealedCID: *sector.CommD, + }, + proof: sector.Proof, // todo: this correct?? + }) + if err != nil { + return ctx.Send(SectorCommitFailed{xerrors.Errorf("queuing commit for aggregation failed: %w", err)}) + } + + return ctx.Send(SectorCommitAggregateSent{mcid}) +} + func (m *Sealing) handleCommitWait(ctx statemachine.Context, sector SectorInfo) error { if sector.CommitMessage == nil { log.Errorf("sector %d entered commit wait state without a message cid", sector.SectorNumber) diff --git a/node/config/def.go b/node/config/def.go index 63099516b1e..96783fa574e 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -82,6 +82,10 @@ type SealingConfig struct { AlwaysKeepUnsealedCopy bool + AggregateCommits bool + MinCommitBatch uint64 + MaxCommitBatch uint64 + // Keep this many sectors in sealing pipeline, start CC if needed // todo TargetSealingSectors uint64 diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index d89474eeeba..d4275c9bf84 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -825,6 +825,9 @@ func NewSetSealConfigFunc(r repo.LockedRepo) (dtypes.SetSealingConfigFunc, error MaxSealingSectorsForDeals: cfg.MaxSealingSectorsForDeals, WaitDealsDelay: config.Duration(cfg.WaitDealsDelay), AlwaysKeepUnsealedCopy: cfg.AlwaysKeepUnsealedCopy, + AggregateCommits: cfg.AggregateCommits, + MinCommitBatch: cfg.MinCommitBatch, + MaxCommitBatch: cfg.MaxCommitBatch, } }) return @@ -840,6 +843,9 @@ func NewGetSealConfigFunc(r repo.LockedRepo) (dtypes.GetSealingConfigFunc, error MaxSealingSectorsForDeals: cfg.Sealing.MaxSealingSectorsForDeals, WaitDealsDelay: time.Duration(cfg.Sealing.WaitDealsDelay), AlwaysKeepUnsealedCopy: cfg.Sealing.AlwaysKeepUnsealedCopy, + AggregateCommits: cfg.Sealing.AggregateCommits, + MinCommitBatch: cfg.Sealing.MinCommitBatch, + MaxCommitBatch: cfg.Sealing.MaxCommitBatch, } }) return From 31d5dd967292644bb5f4a6acb111fa16320491d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 10 Mar 2021 19:50:28 +0100 Subject: [PATCH 05/17] storagefsm: Implement CommitBatcher processBatch --- extern/storage-sealing/commit_batch.go | 89 ++++++++++++++++++++-- extern/storage-sealing/sealiface/config.go | 4 +- 2 files changed, 84 insertions(+), 9 deletions(-) diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index a5dea6ea139..72db25b61d6 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -1,15 +1,22 @@ package sealing import ( + "bytes" "context" "sync" "time" "github.com/ipfs/go-cid" + "golang.org/x/xerrors" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + miner3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/miner" proof3 "github.com/filecoin-project/specs-actors/v3/actors/runtime/proof" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" ) var ( @@ -20,6 +27,7 @@ var ( type CommitBatcherApi interface { SendMsg(ctx context.Context, from, to address.Address, method abi.MethodNum, value, maxFee abi.TokenAmount, params []byte) (cid.Cid, error) + StateMinerInfo(context.Context, address.Address, TipSetToken) (miner.MinerInfo, error) } type AggregateInput struct { @@ -33,25 +41,27 @@ type CommitBatcher struct { mctx context.Context addrSel AddrSel feeCfg FeeConfig + getConfig GetSealingConfigFunc - todo map[abi.SectorID]AggregateInput - waiting map[abi.SectorID][]chan cid.Cid + todo map[abi.SectorNumber]AggregateInput + waiting map[abi.SectorNumber][]chan cid.Cid notify, stop, stopped chan struct{} force chan chan *cid.Cid lk sync.Mutex } -func NewCommitBatcher(mctx context.Context, maddr address.Address, api CommitBatcherApi, addrSel AddrSel, feeCfg FeeConfig) *CommitBatcher { +func NewCommitBatcher(mctx context.Context, maddr address.Address, api CommitBatcherApi, addrSel AddrSel, feeCfg FeeConfig, getConfig GetSealingConfigFunc) *CommitBatcher { b := &CommitBatcher{ api: api, maddr: maddr, mctx: mctx, addrSel: addrSel, feeCfg: feeCfg, + getConfig: getConfig, - todo: map[abi.SectorID]AggregateInput{}, - waiting: map[abi.SectorID][]chan cid.Cid{}, + todo: map[abi.SectorNumber]AggregateInput{}, + waiting: map[abi.SectorNumber][]chan cid.Cid{}, notify: make(chan struct{}, 1), force: make(chan chan *cid.Cid), @@ -97,11 +107,76 @@ func (b *CommitBatcher) run() { } func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { - return nil, nil + b.lk.Lock() + defer b.lk.Unlock() + params := miner3.ProveCommitAggregateParams{} + + total := len(b.todo) + if total == 0 { + return nil, nil // nothing to do + } + + cfg, err := b.getConfig() + if err != nil { + return nil, xerrors.Errorf("getting config: %w", err) + } + + if notif && total < cfg.MaxCommitBatch { + return nil, nil + } + + if after && total < cfg.MinCommitBatch { + return nil, nil + } + + for id := range b.todo { + params.SectorNumbers.Set(uint64(id)) + } + + // todo: Aggregate here + params.AggregateProof = []byte("this is a valid aggregated proof for some sectors") + + enc := new(bytes.Buffer) + if err := params.MarshalCBOR(enc); err != nil { + return nil, xerrors.Errorf("couldn't serialize TerminateSectors params: %w", err) + } + + mi, err := b.api.StateMinerInfo(b.mctx, b.maddr, nil) + if err != nil { + return nil, xerrors.Errorf("couldn't get miner info: %w", err) + } + + from, _, err := b.addrSel(b.mctx, mi, api.CommitAddr, b.feeCfg.MaxCommitGasFee, b.feeCfg.MaxCommitGasFee) + if err != nil { + return nil, xerrors.Errorf("no good address found: %w", err) + } + + mcid, err := b.api.SendMsg(b.mctx, from, b.maddr, miner.Methods.ProveCommitAggregate, big.Zero(), b.feeCfg.MaxCommitGasFee, enc.Bytes()) + if err != nil { + return nil, xerrors.Errorf("sending message failed: %w", err) + } + + log.Infow("Sent ProveCommitAggregate message", "cid", mcid, "from", from, "sectors", total) + + err = params.SectorNumbers.ForEach(func(us uint64) error { + sn := abi.SectorNumber(us) + + for _, ch := range b.waiting[sn] { + ch <- mcid // buffered + } + delete(b.waiting, sn) + delete(b.todo, sn) + return nil + }) + if err != nil { + return nil, xerrors.Errorf("done sectors foreach: %w", err) + } + + return &mcid, nil } // register commit, wait for batch message, return message CID -func (b *CommitBatcher) AddCommit(ctx context.Context, s abi.SectorID, in AggregateInput) (mcid cid.Cid, err error) { +func (b *CommitBatcher) AddCommit(ctx context.Context, s abi.SectorNumber, in AggregateInput) (mcid cid.Cid, err error) { b.lk.Lock() b.todo[s] = in diff --git a/extern/storage-sealing/sealiface/config.go b/extern/storage-sealing/sealiface/config.go index f57ac59aede..f62911b709e 100644 --- a/extern/storage-sealing/sealiface/config.go +++ b/extern/storage-sealing/sealiface/config.go @@ -19,6 +19,6 @@ type Config struct { AlwaysKeepUnsealedCopy bool AggregateCommits bool - MinCommitBatch uint64 - MaxCommitBatch uint64 + MinCommitBatch int + MaxCommitBatch int } From 0e177261dd13cad257548ee72028e025cffcf69b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 10 Mar 2021 20:29:07 +0100 Subject: [PATCH 06/17] update ffi --- extern/filecoin-ffi | 2 +- extern/sector-storage/mock/mock.go | 24 ++++++++++++++---------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index a8605976ac4..6f6b2361a3f 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit a8605976ac40f2cf865d86025d22412cae4298e7 +Subproject commit 6f6b2361a3f7bdb7074256cd0c45eddf88dedfb2 diff --git a/extern/sector-storage/mock/mock.go b/extern/sector-storage/mock/mock.go index 17e96975858..885b3390be8 100644 --- a/extern/sector-storage/mock/mock.go +++ b/extern/sector-storage/mock/mock.go @@ -9,7 +9,7 @@ import ( "math/rand" "sync" - proof2 "github.com/filecoin-project/specs-actors/v2/actors/runtime/proof" + proof3 "github.com/filecoin-project/specs-actors/v3/actors/runtime/proof" ffiwrapper2 "github.com/filecoin-project/go-commp-utils/ffiwrapper" commcid "github.com/filecoin-project/go-fil-commcid" @@ -291,12 +291,12 @@ func AddOpFinish(ctx context.Context) (context.Context, func()) { } } -func (mgr *SectorMgr) GenerateWinningPoSt(ctx context.Context, minerID abi.ActorID, sectorInfo []proof2.SectorInfo, randomness abi.PoStRandomness) ([]proof2.PoStProof, error) { +func (mgr *SectorMgr) GenerateWinningPoSt(ctx context.Context, minerID abi.ActorID, sectorInfo []proof3.SectorInfo, randomness abi.PoStRandomness) ([]proof3.PoStProof, error) { return generateFakePoSt(sectorInfo, abi.RegisteredSealProof.RegisteredWinningPoStProof, randomness), nil } -func (mgr *SectorMgr) GenerateWindowPoSt(ctx context.Context, minerID abi.ActorID, sectorInfo []proof2.SectorInfo, randomness abi.PoStRandomness) ([]proof2.PoStProof, []abi.SectorID, error) { - si := make([]proof2.SectorInfo, 0, len(sectorInfo)) +func (mgr *SectorMgr) GenerateWindowPoSt(ctx context.Context, minerID abi.ActorID, sectorInfo []proof3.SectorInfo, randomness abi.PoStRandomness) ([]proof3.PoStProof, []abi.SectorID, error) { + si := make([]proof3.SectorInfo, 0, len(sectorInfo)) var skipped []abi.SectorID var err error @@ -324,7 +324,7 @@ func (mgr *SectorMgr) GenerateWindowPoSt(ctx context.Context, minerID abi.ActorI return generateFakePoSt(si, abi.RegisteredSealProof.RegisteredWindowPoStProof, randomness), skipped, nil } -func generateFakePoStProof(sectorInfo []proof2.SectorInfo, randomness abi.PoStRandomness) []byte { +func generateFakePoStProof(sectorInfo []proof3.SectorInfo, randomness abi.PoStRandomness) []byte { randomness[31] &= 0x3f hasher := sha256.New() @@ -339,13 +339,13 @@ func generateFakePoStProof(sectorInfo []proof2.SectorInfo, randomness abi.PoStRa } -func generateFakePoSt(sectorInfo []proof2.SectorInfo, rpt func(abi.RegisteredSealProof) (abi.RegisteredPoStProof, error), randomness abi.PoStRandomness) []proof2.PoStProof { +func generateFakePoSt(sectorInfo []proof3.SectorInfo, rpt func(abi.RegisteredSealProof) (abi.RegisteredPoStProof, error), randomness abi.PoStRandomness) []proof3.PoStProof { wp, err := rpt(sectorInfo[0].SealProof) if err != nil { panic(err) } - return []proof2.PoStProof{ + return []proof3.PoStProof{ { PoStProof: wp, ProofBytes: generateFakePoStProof(sectorInfo, randomness), @@ -470,7 +470,7 @@ func (mgr *SectorMgr) ReturnFetch(ctx context.Context, callID storiface.CallID, panic("not supported") } -func (m mockVerif) VerifySeal(svi proof2.SealVerifyInfo) (bool, error) { +func (m mockVerif) VerifySeal(svi proof3.SealVerifyInfo) (bool, error) { plen, err := svi.SealProof.ProofSize() if err != nil { return false, err @@ -490,12 +490,16 @@ func (m mockVerif) VerifySeal(svi proof2.SealVerifyInfo) (bool, error) { return true, nil } -func (m mockVerif) VerifyWinningPoSt(ctx context.Context, info proof2.WinningPoStVerifyInfo) (bool, error) { +func (m mockVerif) VerifyAggregateSeals(aggregate proof3.AggregateSealVerifyProofAndInfos) (bool, error) { + panic("implement me") +} + +func (m mockVerif) VerifyWinningPoSt(ctx context.Context, info proof3.WinningPoStVerifyInfo) (bool, error) { info.Randomness[31] &= 0x3f return true, nil } -func (m mockVerif) VerifyWindowPoSt(ctx context.Context, info proof2.WindowPoStVerifyInfo) (bool, error) { +func (m mockVerif) VerifyWindowPoSt(ctx context.Context, info proof3.WindowPoStVerifyInfo) (bool, error) { if len(info.Proofs) != 1 { return false, xerrors.Errorf("expected 1 proof entry") } From 431109d3f316521df098ca93c4b637e3e2ae5bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 12 Mar 2021 20:15:45 +0100 Subject: [PATCH 07/17] default batching config --- node/config/def.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/node/config/def.go b/node/config/def.go index 96783fa574e..4f56b8849a4 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -241,6 +241,10 @@ func DefaultStorageMiner() *StorageMiner { MaxSealingSectorsForDeals: 0, WaitDealsDelay: Duration(time.Hour * 6), AlwaysKeepUnsealedCopy: true, + + AggregateCommits: true, + MinCommitBatch: 5, // todo: base this on some real numbers + MaxCommitBatch: 400, }, Storage: sectorstorage.SealerConfig{ From eb6939b6650778ec2e9a6290ba4f7c97491d920e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 30 Mar 2021 13:47:28 +0200 Subject: [PATCH 08/17] ffi with better interfaces --- extern/filecoin-ffi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index 6f6b2361a3f..00a7400a378 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit 6f6b2361a3f7bdb7074256cd0c45eddf88dedfb2 +Subproject commit 00a7400a3788d3c968c3459ea705c87d09987291 From 3fe1992708d0a79b644748b3c5e4cdcb11b7eb8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 30 Mar 2021 14:57:07 +0200 Subject: [PATCH 09/17] Fix build --- chain/gen/gen.go | 2 +- extern/filecoin-ffi | 2 +- extern/storage-sealing/sealing.go | 2 +- extern/storage-sealing/states_sealing.go | 2 +- node/config/def.go | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/chain/gen/gen.go b/chain/gen/gen.go index d458fbd2d21..027fb92ad09 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -24,7 +24,7 @@ import ( "go.opencensus.io/trace" "golang.org/x/xerrors" - proof3 "github.com/filecoin-project/specs-actors/v2/actors/runtime/proof" + proof3 "github.com/filecoin-project/specs-actors/v3/actors/runtime/proof" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/blockstore" diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index 00a7400a378..f2502e014d5 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit 00a7400a3788d3c968c3459ea705c87d09987291 +Subproject commit f2502e014d5f2eb0c57d9d88d57858f32c86a5a8 diff --git a/extern/storage-sealing/sealing.go b/extern/storage-sealing/sealing.go index 061aa29fcae..4af458382df 100644 --- a/extern/storage-sealing/sealing.go +++ b/extern/storage-sealing/sealing.go @@ -153,7 +153,7 @@ func New(api SealingAPI, fc FeeConfig, events Events, maddr address.Address, ds addrSel: as, terminator: NewTerminationBatcher(context.TODO(), maddr, api, as, fc), - commiter: NewCommitBatcher(context.TODO(), maddr, api, as, fc), + commiter: NewCommitBatcher(context.TODO(), maddr, api, as, fc, gc), getConfig: gc, dealInfo: &CurrentDealInfoManager{api}, diff --git a/extern/storage-sealing/states_sealing.go b/extern/storage-sealing/states_sealing.go index 7211a5fbb2e..dc90eaf0335 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -528,7 +528,7 @@ func (m *Sealing) handleSubmitCommitAggregate(ctx statemachine.Context, sector S return ctx.Send(SectorCommitFailed{xerrors.Errorf("sector had nil commR or commD")}) } - mcid, err := m.commiter.AddCommit(ctx.Context(), m.minerSectorID(sector.SectorNumber), AggregateInput{ + mcid, err := m.commiter.AddCommit(ctx.Context(), sector.SectorNumber, AggregateInput{ info: proof.AggregateSealVerifyInfo{ SealProof: sector.SectorType, Number: sector.SectorNumber, diff --git a/node/config/def.go b/node/config/def.go index 4f56b8849a4..cad11cfc54b 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -83,8 +83,8 @@ type SealingConfig struct { AlwaysKeepUnsealedCopy bool AggregateCommits bool - MinCommitBatch uint64 - MaxCommitBatch uint64 + MinCommitBatch int + MaxCommitBatch int // Keep this many sectors in sealing pipeline, start CC if needed // todo TargetSealingSectors uint64 From 69caf15ee665b80c7431bf7ad211d8de157fec17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 30 Mar 2021 15:53:06 +0200 Subject: [PATCH 10/17] ffiwrapper: Test proof aggregation --- extern/filecoin-ffi | 2 +- .../sector-storage/ffiwrapper/sealer_test.go | 91 ++++++++++++++++++- extern/sector-storage/ffiwrapper/types.go | 3 + .../sector-storage/ffiwrapper/verifier_cgo.go | 4 + extern/storage-sealing/commit_batch.go | 2 + 5 files changed, 99 insertions(+), 3 deletions(-) diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index f2502e014d5..c5e646e79e9 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit f2502e014d5f2eb0c57d9d88d57858f32c86a5a8 +Subproject commit c5e646e79e9019b0034bbad1b318a20cbfb774e6 diff --git a/extern/sector-storage/ffiwrapper/sealer_test.go b/extern/sector-storage/ffiwrapper/sealer_test.go index 3b379af6f60..fa13cb67254 100644 --- a/extern/sector-storage/ffiwrapper/sealer_test.go +++ b/extern/sector-storage/ffiwrapper/sealer_test.go @@ -18,6 +18,7 @@ import ( commpffi "github.com/filecoin-project/go-commp-utils/ffiwrapper" proof2 "github.com/filecoin-project/specs-actors/v2/actors/runtime/proof" + proof3 "github.com/filecoin-project/specs-actors/v3/actors/runtime/proof" "github.com/ipfs/go-cid" @@ -83,9 +84,10 @@ func (s *seal) precommit(t *testing.T, sb *Sealer, id storage.SectorRef, done fu s.cids = cids } -func (s *seal) commit(t *testing.T, sb *Sealer, done func()) { +var seed = abi.InteractiveSealRandomness{0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 45, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9} + +func (s *seal) commit(t *testing.T, sb *Sealer, done func()) storage.Proof { defer done() - seed := abi.InteractiveSealRandomness{0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 45, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9} pc1, err := sb.SealCommit1(context.TODO(), s.ref, s.ticket, seed, []abi.PieceInfo{s.pi}, s.cids) if err != nil { @@ -112,6 +114,8 @@ func (s *seal) commit(t *testing.T, sb *Sealer, done func()) { if !ok { t.Fatal("proof failed to validate") } + + return proof } func (s *seal) unseal(t *testing.T, sb *Sealer, sp *basicfs.Provider, si storage.SectorRef, done func()) { @@ -462,6 +466,89 @@ func TestSealAndVerify3(t *testing.T) { post(t, sb, []abi.SectorID{si1.ID, si2.ID}, s1, s2, s3) } +func TestSealAndVerifyAggregate(t *testing.T) { + numAgg := 5 + + if testing.Short() { + t.Skip("skipping test in short mode") + } + + defer requireFDsClosed(t, openFDs(t)) + + if runtime.NumCPU() < 10 && os.Getenv("CI") == "" { // don't bother on slow hardware + t.Skip("this is slow") + } + _ = os.Setenv("RUST_LOG", "info") + + getGrothParamFileAndVerifyingKeys(sectorSize) + + cdir, err := ioutil.TempDir("", "sbtest-c-") + if err != nil { + t.Fatal(err) + } + miner := abi.ActorID(123) + + sp := &basicfs.Provider{ + Root: cdir, + } + sb, err := New(sp) + if err != nil { + t.Fatalf("%+v", err) + } + cleanup := func() { + if t.Failed() { + fmt.Printf("not removing %s\n", cdir) + return + } + if err := os.RemoveAll(cdir); err != nil { + t.Error(err) + } + } + defer cleanup() + + avi := proof3.AggregateSealVerifyProofAndInfos{ + Miner: miner, + Infos: make([]proof3.AggregateSealVerifyInfo, numAgg), + } + + toAggregate := make([][]byte, numAgg) + for i := 0; i < numAgg; i++ { + si := storage.SectorRef{ + ID: abi.SectorID{Miner: miner, Number: abi.SectorNumber(i+1)}, + ProofType: sealProofType, + } + + s := seal{ref: si} + s.precommit(t, sb, si, func() {}) + toAggregate[i] = s.commit(t, sb, func() {}) + + avi.Infos[i] = proof3.AggregateSealVerifyInfo{ + SealProof: sealProofType, + Number: abi.SectorNumber(i + 1), + Randomness: s.ticket, + InteractiveRandomness: seed, + SealedCID: s.cids.Sealed, + UnsealedCID: s.cids.Unsealed, + } + } + + aggStart := time.Now() + + avi.Proof, err = ProofVerifier.AggregateSealProofs(sealProofType, toAggregate) + require.NoError(t, err) + + aggDone := time.Now() + + ok, err := ProofVerifier.VerifyAggregateSeals(avi) + require.NoError(t, err) + require.True(t, ok) + + verifDone := time.Now() + + fmt.Printf("Aggregate: %s\n", aggDone.Sub(aggStart).String()) + fmt.Printf("Verify: %s\n", verifDone.Sub(aggDone).String()) +} + func BenchmarkWriteWithAlignment(b *testing.B) { bt := abi.UnpaddedPieceSize(2 * 127 * 1024 * 1024) b.SetBytes(int64(bt)) diff --git a/extern/sector-storage/ffiwrapper/types.go b/extern/sector-storage/ffiwrapper/types.go index 5c44b67b28b..6598d4aa149 100644 --- a/extern/sector-storage/ffiwrapper/types.go +++ b/extern/sector-storage/ffiwrapper/types.go @@ -40,6 +40,9 @@ type Verifier interface { VerifyWindowPoSt(ctx context.Context, info proof3.WindowPoStVerifyInfo) (bool, error) GenerateWinningPoStSectorChallenge(context.Context, abi.RegisteredPoStProof, abi.ActorID, abi.PoStRandomness, uint64) ([]uint64, error) + + // cheap, makes no sense to put this on the storage interface + AggregateSealProofs(proofType abi.RegisteredSealProof, proofs [][]byte) ([]byte, error) } type SectorProvider interface { diff --git a/extern/sector-storage/ffiwrapper/verifier_cgo.go b/extern/sector-storage/ffiwrapper/verifier_cgo.go index 048b6a1502a..521ec0cfcbb 100644 --- a/extern/sector-storage/ffiwrapper/verifier_cgo.go +++ b/extern/sector-storage/ffiwrapper/verifier_cgo.go @@ -138,3 +138,7 @@ func (proofVerifier) GenerateWinningPoStSectorChallenge(ctx context.Context, pro randomness[31] &= 0x3f return ffi.GenerateWinningPoStSectorChallenge(proofType, minerID, randomness, eligibleSectorCount) } + +func (v proofVerifier) AggregateSealProofs(proofType abi.RegisteredSealProof, proofs [][]byte) ([]byte, error) { + return ffi.AggregateSealProofs(proofType, proofs) +} diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index 72db25b61d6..ea4d946d40a 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -133,6 +133,8 @@ func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { params.SectorNumbers.Set(uint64(id)) } + b. + // todo: Aggregate here params.AggregateProof = []byte("this is a valid aggregated proof for some sectors") From 06d8608cf2a067c9cf4ff38f3f1a5cc08db0384c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 30 Mar 2021 16:12:13 +0200 Subject: [PATCH 11/17] update go-paramfetch with .srs support --- build/parameters.go | 4 ++++ build/proof-params/srs-inner-product.json | 7 +++++++ cli/params.go | 2 +- cmd/lotus-bench/main.go | 4 ++-- cmd/lotus-seal-worker/main.go | 2 +- cmd/lotus-shed/params.go | 2 +- cmd/lotus-storage-miner/init.go | 2 +- cmd/lotus-storage-miner/init_restore.go | 2 +- cmd/lotus/daemon.go | 2 +- extern/sector-storage/ffiwrapper/sealer_test.go | 15 +++++++++++++-- go.mod | 2 +- go.sum | 4 ++-- node/modules/storageminer.go | 2 +- 13 files changed, 36 insertions(+), 14 deletions(-) create mode 100644 build/proof-params/srs-inner-product.json diff --git a/build/parameters.go b/build/parameters.go index 7d34a783122..b70dad1c196 100644 --- a/build/parameters.go +++ b/build/parameters.go @@ -5,3 +5,7 @@ import rice "github.com/GeertJohan/go.rice" func ParametersJSON() []byte { return rice.MustFindBox("proof-params").MustBytes("parameters.json") } + +func SrsJSON() []byte { + return rice.MustFindBox("proof-params").MustBytes("srs-inner-product.json") +} diff --git a/build/proof-params/srs-inner-product.json b/build/proof-params/srs-inner-product.json new file mode 100644 index 00000000000..8566bf5fd89 --- /dev/null +++ b/build/proof-params/srs-inner-product.json @@ -0,0 +1,7 @@ +{ + "v28-fil-inner-product-v1.srs": { + "cid": "Qmdq44DjcQnFfU3PJcdX7J49GCqcUYszr1TxMbHtAkvQ3g", + "digest": "ae20310138f5ba81451d723f858e3797", + "sector_size": 0 + } +} diff --git a/cli/params.go b/cli/params.go index 8419507b874..1aa6555c527 100644 --- a/cli/params.go +++ b/cli/params.go @@ -23,7 +23,7 @@ var FetchParamCmd = &cli.Command{ } sectorSize := uint64(sectorSizeInt) - err = paramfetch.GetParams(ReqContext(cctx), build.ParametersJSON(), sectorSize) + err = paramfetch.GetParams(ReqContext(cctx), build.ParametersJSON(), build.SrsJSON(), sectorSize) if err != nil { return xerrors.Errorf("fetching proof parameters: %w", err) } diff --git a/cmd/lotus-bench/main.go b/cmd/lotus-bench/main.go index 81aa09a75de..0b8ec6fe3fc 100644 --- a/cmd/lotus-bench/main.go +++ b/cmd/lotus-bench/main.go @@ -243,7 +243,7 @@ var sealBenchCmd = &cli.Command{ // Only fetch parameters if actually needed skipc2 := c.Bool("skip-commit2") if !skipc2 { - if err := paramfetch.GetParams(lcli.ReqContext(c), build.ParametersJSON(), uint64(sectorSize)); err != nil { + if err := paramfetch.GetParams(lcli.ReqContext(c), build.ParametersJSON(), build.SrsJSON(), uint64(sectorSize)); err != nil { return xerrors.Errorf("getting params: %w", err) } } @@ -738,7 +738,7 @@ var proveCmd = &cli.Command{ return xerrors.Errorf("unmarshalling input file: %w", err) } - if err := paramfetch.GetParams(lcli.ReqContext(c), build.ParametersJSON(), c2in.SectorSize); err != nil { + if err := paramfetch.GetParams(lcli.ReqContext(c), build.ParametersJSON(), build.SrsJSON(), c2in.SectorSize); err != nil { return xerrors.Errorf("getting params: %w", err) } diff --git a/cmd/lotus-seal-worker/main.go b/cmd/lotus-seal-worker/main.go index 24918e52a39..5a78c6dac66 100644 --- a/cmd/lotus-seal-worker/main.go +++ b/cmd/lotus-seal-worker/main.go @@ -228,7 +228,7 @@ var runCmd = &cli.Command{ } if cctx.Bool("commit") { - if err := paramfetch.GetParams(ctx, build.ParametersJSON(), uint64(ssize)); err != nil { + if err := paramfetch.GetParams(ctx, build.ParametersJSON(), build.SrsJSON(), uint64(ssize)); err != nil { return xerrors.Errorf("get params: %w", err) } } diff --git a/cmd/lotus-shed/params.go b/cmd/lotus-shed/params.go index 3f7e7b6fb7e..e45d9489c35 100644 --- a/cmd/lotus-shed/params.go +++ b/cmd/lotus-shed/params.go @@ -25,7 +25,7 @@ var fetchParamCmd = &cli.Command{ return err } sectorSize := uint64(sectorSizeInt) - err = paramfetch.GetParams(lcli.ReqContext(cctx), build.ParametersJSON(), sectorSize) + err = paramfetch.GetParams(lcli.ReqContext(cctx), build.ParametersJSON(), build.SrsJSON(), sectorSize) if err != nil { return xerrors.Errorf("fetching proof parameters: %w", err) } diff --git a/cmd/lotus-storage-miner/init.go b/cmd/lotus-storage-miner/init.go index 2e38dcc06ca..bac8444cc1e 100644 --- a/cmd/lotus-storage-miner/init.go +++ b/cmd/lotus-storage-miner/init.go @@ -143,7 +143,7 @@ var initCmd = &cli.Command{ log.Info("Checking proof parameters") - if err := paramfetch.GetParams(ctx, build.ParametersJSON(), uint64(ssize)); err != nil { + if err := paramfetch.GetParams(ctx, build.ParametersJSON(), build.SrsJSON(), uint64(ssize)); err != nil { return xerrors.Errorf("fetching proof parameters: %w", err) } diff --git a/cmd/lotus-storage-miner/init_restore.go b/cmd/lotus-storage-miner/init_restore.go index 12358e63a75..af4c43c957d 100644 --- a/cmd/lotus-storage-miner/init_restore.go +++ b/cmd/lotus-storage-miner/init_restore.go @@ -249,7 +249,7 @@ var initRestoreCmd = &cli.Command{ log.Info("Checking proof parameters") - if err := paramfetch.GetParams(ctx, build.ParametersJSON(), uint64(mi.SectorSize)); err != nil { + if err := paramfetch.GetParams(ctx, build.ParametersJSON(), build.SrsJSON(), uint64(mi.SectorSize)); err != nil { return xerrors.Errorf("fetching proof parameters: %w", err) } diff --git a/cmd/lotus/daemon.go b/cmd/lotus/daemon.go index 5a59ec8167f..644892ee24f 100644 --- a/cmd/lotus/daemon.go +++ b/cmd/lotus/daemon.go @@ -231,7 +231,7 @@ var DaemonCmd = &cli.Command{ freshRepo := err != repo.ErrRepoExists if !isLite { - if err := paramfetch.GetParams(lcli.ReqContext(cctx), build.ParametersJSON(), 0); err != nil { + if err := paramfetch.GetParams(lcli.ReqContext(cctx), build.ParametersJSON(), build.SrsJSON(), 0); err != nil { return xerrors.Errorf("fetching proof parameters: %w", err) } } diff --git a/extern/sector-storage/ffiwrapper/sealer_test.go b/extern/sector-storage/ffiwrapper/sealer_test.go index fa13cb67254..d10323b0375 100644 --- a/extern/sector-storage/ffiwrapper/sealer_test.go +++ b/extern/sector-storage/ffiwrapper/sealer_test.go @@ -233,7 +233,12 @@ func getGrothParamFileAndVerifyingKeys(s abi.SectorSize) { panic(err) } - err = paramfetch.GetParams(context.TODO(), dat, uint64(s)) + datSrs, err := ioutil.ReadFile("../../../build/proof-params/srs-inner-product.json") + if err != nil { + panic(err) + } + + err = paramfetch.GetParams(context.TODO(), dat, datSrs, uint64(s)) if err != nil { panic(xerrors.Errorf("failed to acquire Groth parameters for 2KiB sectors: %w", err)) } @@ -539,6 +544,11 @@ func TestSealAndVerifyAggregate(t *testing.T) { aggDone := time.Now() + _, err = ProofVerifier.AggregateSealProofs(sealProofType, toAggregate) + require.NoError(t, err) + + aggHot := time.Now() + ok, err := ProofVerifier.VerifyAggregateSeals(avi) require.NoError(t, err) require.True(t, ok) @@ -546,7 +556,8 @@ func TestSealAndVerifyAggregate(t *testing.T) { verifDone := time.Now() fmt.Printf("Aggregate: %s\n", aggDone.Sub(aggStart).String()) - fmt.Printf("Verify: %s\n", verifDone.Sub(aggDone).String()) + fmt.Printf("Hot: %s\n", aggHot.Sub(aggDone).String()) + fmt.Printf("Verify: %s\n", verifDone.Sub(aggHot).String()) } func BenchmarkWriteWithAlignment(b *testing.B) { diff --git a/go.mod b/go.mod index da2a5308151..96ced716afa 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/filecoin-project/go-jsonrpc v0.1.4-0.20210217175800-45ea43ac2bec github.com/filecoin-project/go-multistore v0.0.3 github.com/filecoin-project/go-padreader v0.0.0-20200903213702-ed5fae088b20 - github.com/filecoin-project/go-paramfetch v0.0.2-0.20200701152213-3e0f0afdc261 + github.com/filecoin-project/go-paramfetch v0.0.2-0.20210330140417-936748d3f5ec github.com/filecoin-project/go-state-types v0.1.0 github.com/filecoin-project/go-statemachine v0.0.0-20200925024713-05bd7c71fbfe github.com/filecoin-project/go-statestore v0.1.1 diff --git a/go.sum b/go.sum index d1ded92867f..2653aa98af2 100644 --- a/go.sum +++ b/go.sum @@ -288,8 +288,8 @@ github.com/filecoin-project/go-multistore v0.0.3 h1:vaRBY4YiA2UZFPK57RNuewypB8u0 github.com/filecoin-project/go-multistore v0.0.3/go.mod h1:kaNqCC4IhU4B1uyr7YWFHd23TL4KM32aChS0jNkyUvQ= github.com/filecoin-project/go-padreader v0.0.0-20200903213702-ed5fae088b20 h1:+/4aUeUoKr6AKfPE3mBhXA5spIV6UcKdTYDPNU2Tdmg= github.com/filecoin-project/go-padreader v0.0.0-20200903213702-ed5fae088b20/go.mod h1:mPn+LRRd5gEKNAtc+r3ScpW2JRU/pj4NBKdADYWHiak= -github.com/filecoin-project/go-paramfetch v0.0.2-0.20200701152213-3e0f0afdc261 h1:A256QonvzRaknIIAuWhe/M2dpV2otzs3NBhi5TWa/UA= -github.com/filecoin-project/go-paramfetch v0.0.2-0.20200701152213-3e0f0afdc261/go.mod h1:fZzmf4tftbwf9S37XRifoJlz7nCjRdIrMGLR07dKLCc= +github.com/filecoin-project/go-paramfetch v0.0.2-0.20210330140417-936748d3f5ec h1:gExwWUiT1TcARkxGneS4nvp9C+wBsKU0bFdg7qFpNco= +github.com/filecoin-project/go-paramfetch v0.0.2-0.20210330140417-936748d3f5ec/go.mod h1:fZzmf4tftbwf9S37XRifoJlz7nCjRdIrMGLR07dKLCc= github.com/filecoin-project/go-state-types v0.0.0-20200903145444-247639ffa6ad/go.mod h1:IQ0MBPnonv35CJHtWSN3YY1Hz2gkPru1Q9qoaYLxx9I= github.com/filecoin-project/go-state-types v0.0.0-20200904021452-1883f36ca2f4/go.mod h1:IQ0MBPnonv35CJHtWSN3YY1Hz2gkPru1Q9qoaYLxx9I= github.com/filecoin-project/go-state-types v0.0.0-20200928172055-2df22083d8ab/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index d4275c9bf84..7a13902aafc 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -99,7 +99,7 @@ func GetParams(spt abi.RegisteredSealProof) error { } // TODO: We should fetch the params for the actual proof type, not just based on the size. - if err := paramfetch.GetParams(context.TODO(), build.ParametersJSON(), uint64(ssize)); err != nil { + if err := paramfetch.GetParams(context.TODO(), build.ParametersJSON(), build.SrsJSON(), uint64(ssize)); err != nil { return xerrors.Errorf("fetching proof parameters: %w", err) } From 334530da62bdd9f33c55e3073e477b1f1f24b9ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 30 Mar 2021 16:20:15 +0200 Subject: [PATCH 12/17] Call real aggregation func in commit batcher --- extern/storage-sealing/commit_batch.go | 44 ++++++++++++++++---------- extern/storage-sealing/sealing.go | 2 +- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index ea4d946d40a..c6d8d2998c8 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -3,6 +3,7 @@ package sealing import ( "bytes" "context" + "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" "sync" "time" @@ -36,12 +37,13 @@ type AggregateInput struct { } type CommitBatcher struct { - api CommitBatcherApi - maddr address.Address - mctx context.Context - addrSel AddrSel - feeCfg FeeConfig + api CommitBatcherApi + maddr address.Address + mctx context.Context + addrSel AddrSel + feeCfg FeeConfig getConfig GetSealingConfigFunc + verif ffiwrapper.Verifier todo map[abi.SectorNumber]AggregateInput waiting map[abi.SectorNumber][]chan cid.Cid @@ -51,14 +53,15 @@ type CommitBatcher struct { lk sync.Mutex } -func NewCommitBatcher(mctx context.Context, maddr address.Address, api CommitBatcherApi, addrSel AddrSel, feeCfg FeeConfig, getConfig GetSealingConfigFunc) *CommitBatcher { +func NewCommitBatcher(mctx context.Context, maddr address.Address, api CommitBatcherApi, addrSel AddrSel, feeCfg FeeConfig, getConfig GetSealingConfigFunc, verif ffiwrapper.Verifier) *CommitBatcher { b := &CommitBatcher{ - api: api, - maddr: maddr, - mctx: mctx, - addrSel: addrSel, - feeCfg: feeCfg, + api: api, + maddr: maddr, + mctx: mctx, + addrSel: addrSel, + feeCfg: feeCfg, getConfig: getConfig, + verif: verif, todo: map[abi.SectorNumber]AggregateInput{}, waiting: map[abi.SectorNumber][]chan cid.Cid{}, @@ -129,14 +132,23 @@ func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { return nil, nil } - for id := range b.todo { + spt := b.todo[0].info.SealProof + proofs := make([][]byte, total) + + for id, p := range b.todo { + if p.info.SealProof != spt { + // todo: handle when we'll have proof upgrade + return nil, xerrors.Errorf("different seal proof types in commit batch: %w", err) + } + params.SectorNumbers.Set(uint64(id)) + proofs[id] = p.proof } - b. - - // todo: Aggregate here - params.AggregateProof = []byte("this is a valid aggregated proof for some sectors") + params.AggregateProof, err = b.verif.AggregateSealProofs(spt, proofs) + if err != nil { + return nil, xerrors.Errorf("aggregating proofs: %w", err) + } enc := new(bytes.Buffer) if err := params.MarshalCBOR(enc); err != nil { diff --git a/extern/storage-sealing/sealing.go b/extern/storage-sealing/sealing.go index 4af458382df..869146671eb 100644 --- a/extern/storage-sealing/sealing.go +++ b/extern/storage-sealing/sealing.go @@ -153,7 +153,7 @@ func New(api SealingAPI, fc FeeConfig, events Events, maddr address.Address, ds addrSel: as, terminator: NewTerminationBatcher(context.TODO(), maddr, api, as, fc), - commiter: NewCommitBatcher(context.TODO(), maddr, api, as, fc, gc), + commiter: NewCommitBatcher(context.TODO(), maddr, api, as, fc, gc, verif), getConfig: gc, dealInfo: &CurrentDealInfoManager{api}, From 9f984da1d17908349c3e85751e0150fa252c40cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 30 Mar 2021 16:20:22 +0200 Subject: [PATCH 13/17] gofmt --- extern/sector-storage/ffiwrapper/sealer_test.go | 2 +- node/config/def.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extern/sector-storage/ffiwrapper/sealer_test.go b/extern/sector-storage/ffiwrapper/sealer_test.go index d10323b0375..3ebc5866049 100644 --- a/extern/sector-storage/ffiwrapper/sealer_test.go +++ b/extern/sector-storage/ffiwrapper/sealer_test.go @@ -519,7 +519,7 @@ func TestSealAndVerifyAggregate(t *testing.T) { toAggregate := make([][]byte, numAgg) for i := 0; i < numAgg; i++ { si := storage.SectorRef{ - ID: abi.SectorID{Miner: miner, Number: abi.SectorNumber(i+1)}, + ID: abi.SectorID{Miner: miner, Number: abi.SectorNumber(i + 1)}, ProofType: sealProofType, } diff --git a/node/config/def.go b/node/config/def.go index cad11cfc54b..94e87be3b51 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -242,9 +242,9 @@ func DefaultStorageMiner() *StorageMiner { WaitDealsDelay: Duration(time.Hour * 6), AlwaysKeepUnsealedCopy: true, - AggregateCommits: true, - MinCommitBatch: 5, // todo: base this on some real numbers - MaxCommitBatch: 400, + AggregateCommits: true, + MinCommitBatch: 5, // todo: base this on some real numbers + MaxCommitBatch: 400, }, Storage: sectorstorage.SealerConfig{ From 62a38bc83e6cbee422bafb322b590d38e19f03b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 30 Mar 2021 16:29:48 +0200 Subject: [PATCH 14/17] proof aggregate mocks --- chain/gen/gen.go | 4 ++++ extern/sector-storage/mock/mock.go | 22 +++++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/chain/gen/gen.go b/chain/gen/gen.go index 027fb92ad09..94442e6367c 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -681,6 +681,10 @@ func (m genFakeVerifier) VerifyAggregateSeals(aggregate proof3.AggregateSealVeri panic("not supported") } +func (m genFakeVerifier) AggregateSealProofs(proofType abi.RegisteredSealProof, proofs [][]byte) ([]byte, error) { + panic("not supported") +} + func (m genFakeVerifier) VerifyWinningPoSt(ctx context.Context, info proof3.WinningPoStVerifyInfo) (bool, error) { panic("not supported") } diff --git a/extern/sector-storage/mock/mock.go b/extern/sector-storage/mock/mock.go index 885b3390be8..0ba7ea3ad86 100644 --- a/extern/sector-storage/mock/mock.go +++ b/extern/sector-storage/mock/mock.go @@ -482,6 +482,7 @@ func (m mockVerif) VerifySeal(svi proof3.SealVerifyInfo) (bool, error) { // only the first 32 bytes, the rest are 0. for i, b := range svi.Proof[:32] { + // unsealed+sealed-seed*ticket if b != svi.UnsealedCID.Bytes()[i]+svi.SealedCID.Bytes()[31-i]-svi.InteractiveRandomness[i]*svi.Randomness[i] { return false, nil } @@ -491,7 +492,26 @@ func (m mockVerif) VerifySeal(svi proof3.SealVerifyInfo) (bool, error) { } func (m mockVerif) VerifyAggregateSeals(aggregate proof3.AggregateSealVerifyProofAndInfos) (bool, error) { - panic("implement me") + out := make([]byte, 200) + for pi, svi := range aggregate.Infos { + for i := 0; i < 32; i++ { + b := svi.UnsealedCID.Bytes()[i] + svi.SealedCID.Bytes()[31-i] - svi.InteractiveRandomness[i]*svi.Randomness[i] // raw proof byte + b *= uint8(pi) // with aggregate index + out[i] += b + } + } + + return bytes.Equal(aggregate.Proof, out), nil +} + +func (m mockVerif) AggregateSealProofs(proofType abi.RegisteredSealProof, proofs [][]byte) ([]byte, error) { + out := make([]byte, 200) // todo: figure out more real length + for pi, proof := range proofs { + for i := range proof[:32] { + out[i] += proof[i] * uint8(pi) + } + } + return out, nil } func (m mockVerif) VerifyWinningPoSt(ctx context.Context, info proof3.WinningPoStVerifyInfo) (bool, error) { From 952755d03f214f7921ff8525af9e00da738b63f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 30 Mar 2021 16:40:25 +0200 Subject: [PATCH 15/17] gas charging for aggergate proof verification --- chain/vm/gas.go | 17 ++++++++++++++--- chain/vm/gas_v0.go | 7 +++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/chain/vm/gas.go b/chain/vm/gas.go index eef431aefee..be94ac3f862 100644 --- a/chain/vm/gas.go +++ b/chain/vm/gas.go @@ -9,8 +9,9 @@ import ( addr "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/crypto" - vmr2 "github.com/filecoin-project/specs-actors/v2/actors/runtime" proof2 "github.com/filecoin-project/specs-actors/v2/actors/runtime/proof" + vmr3 "github.com/filecoin-project/specs-actors/v3/actors/runtime" + proof3 "github.com/filecoin-project/specs-actors/v3/actors/runtime/proof" "github.com/ipfs/go-cid" ) @@ -75,6 +76,7 @@ type Pricelist interface { OnHashing(dataSize int) GasCharge OnComputeUnsealedSectorCid(proofType abi.RegisteredSealProof, pieces []abi.PieceInfo) GasCharge OnVerifySeal(info proof2.SealVerifyInfo) GasCharge + OnVerifyAggregateSeals() GasCharge OnVerifyPost(info proof2.WindowPoStVerifyInfo) GasCharge OnVerifyConsensusFault() GasCharge } @@ -111,6 +113,7 @@ var prices = map[abi.ChainEpoch]Pricelist{ hashingBase: 31355, computeUnsealedSectorCidBase: 98647, verifySealBase: 2000, // TODO gas , it VerifySeal syscall is not used + verifyAggregateSealBase: 0, verifyPostLookup: map[abi.RegisteredPoStProof]scalingCost{ abi.RegisteredPoStProof_StackedDrgWindow512MiBV1: { flat: 123861062, @@ -159,6 +162,7 @@ var prices = map[abi.ChainEpoch]Pricelist{ hashingBase: 31355, computeUnsealedSectorCidBase: 98647, verifySealBase: 2000, // TODO gas , it VerifySeal syscall is not used + verifyAggregateSealBase: 400_000_000, // TODO (~40ms, I think) verifyPostLookup: map[abi.RegisteredPoStProof]scalingCost{ abi.RegisteredPoStProof_StackedDrgWindow512MiBV1: { flat: 117680921, @@ -198,7 +202,7 @@ func PricelistByEpoch(epoch abi.ChainEpoch) Pricelist { } type pricedSyscalls struct { - under vmr2.Syscalls + under vmr3.Syscalls pl Pricelist chargeGas func(GasCharge) } @@ -257,7 +261,7 @@ func (ps pricedSyscalls) VerifyPoSt(vi proof2.WindowPoStVerifyInfo) error { // the "parent grinding fault", in which case it must be the sibling of h1 (same parent tipset) and one of the // blocks in the parent of h2 (i.e. h2's grandparent). // Returns nil and an error if the headers don't prove a fault. -func (ps pricedSyscalls) VerifyConsensusFault(h1 []byte, h2 []byte, extra []byte) (*vmr2.ConsensusFault, error) { +func (ps pricedSyscalls) VerifyConsensusFault(h1 []byte, h2 []byte, extra []byte) (*vmr3.ConsensusFault, error) { ps.chargeGas(ps.pl.OnVerifyConsensusFault()) defer ps.chargeGas(gasOnActorExec) @@ -277,3 +281,10 @@ func (ps pricedSyscalls) BatchVerifySeals(inp map[address.Address][]proof2.SealV return ps.under.BatchVerifySeals(inp) } + +func (ps pricedSyscalls) VerifyAggregateSeals(aggregate proof3.AggregateSealVerifyProofAndInfos) error { + ps.chargeGas(ps.pl.OnVerifyAggregateSeals()) + defer ps.chargeGas(gasOnActorExec) + + return ps.under.VerifyAggregateSeals(aggregate) +} diff --git a/chain/vm/gas_v0.go b/chain/vm/gas_v0.go index 7c864b7f9b6..d54760b69b5 100644 --- a/chain/vm/gas_v0.go +++ b/chain/vm/gas_v0.go @@ -91,6 +91,7 @@ type pricelistV0 struct { computeUnsealedSectorCidBase int64 verifySealBase int64 + verifyAggregateSealBase int64 verifyPostLookup map[abi.RegisteredPoStProof]scalingCost verifyPostDiscount bool verifyConsensusFault int64 @@ -185,6 +186,12 @@ func (pl *pricelistV0) OnVerifySeal(info proof2.SealVerifyInfo) GasCharge { return newGasCharge("OnVerifySeal", pl.verifySealBase, 0) } +// OnVerifyAggregateSeals +func (pl *pricelistV0) OnVerifyAggregateSeals() GasCharge { + // TODO: this needs more cost tunning + return newGasCharge("OnVerifyAggregateSeals", pl.verifyAggregateSealBase, 0) +} + // OnVerifyPost func (pl *pricelistV0) OnVerifyPost(info proof2.WindowPoStVerifyInfo) GasCharge { sectorSize := "unknown" From 0c9301e533131fa948b88d98cf713eac76869b88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 30 Mar 2021 16:57:52 +0200 Subject: [PATCH 16/17] miner api/cli to manage commit batches --- api/api_storage.go | 2 ++ api/apistruct/struct.go | 12 +++++++ build/openrpc/full.json.gz | Bin 22595 -> 22591 bytes build/openrpc/miner.json.gz | Bin 7778 -> 7835 bytes build/openrpc/worker.json.gz | Bin 2580 -> 2577 bytes chain/vm/gas.go | 2 +- cmd/lotus-storage-miner/sectors.go | 47 +++++++++++++++++++++++++ documentation/en/api-methods-miner.md | 20 +++++++++++ extern/storage-sealing/commit_batch.go | 26 ++++++++++++-- extern/storage-sealing/sealing.go | 8 +++++ lotuspond/front/src/chain/methods.json | 3 +- node/impl/storminer.go | 8 +++++ storage/sealing.go | 8 +++++ 13 files changed, 132 insertions(+), 4 deletions(-) diff --git a/api/api_storage.go b/api/api_storage.go index 9662e8cd872..a9dec3d0e9c 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -80,6 +80,8 @@ type StorageMiner interface { // SectorTerminatePending returns a list of pending sector terminations to be sent in the next batch message SectorTerminatePending(ctx context.Context) ([]abi.SectorID, error) //perm:admin SectorMarkForUpgrade(ctx context.Context, id abi.SectorNumber) error //perm:admin + SectorCommitFlush(ctx context.Context) (*cid.Cid, error) //perm:admin + SectorCommitPending(ctx context.Context) ([]abi.SectorID, error) //perm:admin // WorkerConnect tells the node to connect to workers RPC WorkerConnect(context.Context, string) error //perm:admin retry:true diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index 8fe40b574c3..ec429089d24 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -637,6 +637,10 @@ type StorageMinerStruct struct { SealingSchedDiag func(p0 context.Context, p1 bool) (interface{}, error) `perm:"admin"` + SectorCommitFlush func(p0 context.Context) (*cid.Cid, error) `perm:"admin"` + + SectorCommitPending func(p0 context.Context) ([]abi.SectorID, error) `perm:"admin"` + SectorGetExpectedSealDuration func(p0 context.Context) (time.Duration, error) `perm:"read"` SectorGetSealDelay func(p0 context.Context) (time.Duration, error) `perm:"read"` @@ -1917,6 +1921,14 @@ func (s *StorageMinerStruct) SealingSchedDiag(p0 context.Context, p1 bool) (inte return s.Internal.SealingSchedDiag(p0, p1) } +func (s *StorageMinerStruct) SectorCommitFlush(p0 context.Context) (*cid.Cid, error) { + return s.Internal.SectorCommitFlush(p0) +} + +func (s *StorageMinerStruct) SectorCommitPending(p0 context.Context) ([]abi.SectorID, error) { + return s.Internal.SectorCommitPending(p0) +} + func (s *StorageMinerStruct) SectorGetExpectedSealDuration(p0 context.Context) (time.Duration, error) { return s.Internal.SectorGetExpectedSealDuration(p0) } diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 1e145564473343db9948a23339e79cd6467c4c4a..739c2ff8fedd81f5ebf2e6fd9b7bec47a492bc51 100644 GIT binary patch literal 22591 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 literal 22595 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 literal 7778 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 diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index 55bd285301e35f1498945e06b915a0a7bdab0577..64e17f73d76221ed8ce76c8177b93face6942748 100644 GIT binary patch delta 2487 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 B#5Di_ delta 2481 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 diff --git a/chain/vm/gas.go b/chain/vm/gas.go index be94ac3f862..7bb7c1c5585 100644 --- a/chain/vm/gas.go +++ b/chain/vm/gas.go @@ -161,7 +161,7 @@ var prices = map[abi.ChainEpoch]Pricelist{ hashingBase: 31355, computeUnsealedSectorCidBase: 98647, - verifySealBase: 2000, // TODO gas , it VerifySeal syscall is not used + verifySealBase: 2000, // TODO gas , it VerifySeal syscall is not used verifyAggregateSealBase: 400_000_000, // TODO (~40ms, I think) verifyPostLookup: map[abi.RegisteredPoStProof]scalingCost{ abi.RegisteredPoStProof_StackedDrgWindow512MiBV1: { diff --git a/cmd/lotus-storage-miner/sectors.go b/cmd/lotus-storage-miner/sectors.go index 8f6fd374f42..57e41c2e806 100644 --- a/cmd/lotus-storage-miner/sectors.go +++ b/cmd/lotus-storage-miner/sectors.go @@ -45,6 +45,7 @@ var sectorsCmd = &cli.Command{ sectorsStartSealCmd, sectorsSealDelayCmd, sectorsCapacityCollateralCmd, + sectorsPendingCommit, }, } @@ -846,6 +847,52 @@ var sectorsUpdateCmd = &cli.Command{ }, } +var sectorsPendingCommit = &cli.Command{ + Name: "pending-commit", + Usage: "list sectors waiting in batch queue", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "publish-now", + Usage: "send a batch now", + }, + }, + Action: func(cctx *cli.Context) error { + api, closer, err := lcli.GetStorageMinerAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := lcli.ReqContext(cctx) + + if cctx.Bool("publish-now") { + cid, err := api.SectorCommitFlush(ctx) + if err != nil { + return xerrors.Errorf("flush: %w", err) + } + if cid == nil { + return xerrors.Errorf("no sectors to publish") + } + + fmt.Println("sector batch published: ", cid) + return nil + } + + pending, err := api.SectorCommitPending(ctx) + if err != nil { + return xerrors.Errorf("getting pending deals: %w", err) + } + + if len(pending) > 0 { + for _, sector := range pending { + fmt.Println(sector.Number) + } + } + + fmt.Println("No sectors queued to be committed") + return nil + }, +} + func yesno(b bool) string { if b { return color.GreenString("YES") diff --git a/documentation/en/api-methods-miner.md b/documentation/en/api-methods-miner.md index 00d09b0f4df..300d5d4b7dc 100644 --- a/documentation/en/api-methods-miner.md +++ b/documentation/en/api-methods-miner.md @@ -98,6 +98,8 @@ * [SealingAbort](#SealingAbort) * [SealingSchedDiag](#SealingSchedDiag) * [Sector](#Sector) + * [SectorCommitFlush](#SectorCommitFlush) + * [SectorCommitPending](#SectorCommitPending) * [SectorGetExpectedSealDuration](#SectorGetExpectedSealDuration) * [SectorGetSealDelay](#SectorGetSealDelay) * [SectorMarkForUpgrade](#SectorMarkForUpgrade) @@ -1553,6 +1555,24 @@ Response: `{}` ## Sector +### SectorCommitFlush + + +Perms: admin + +Inputs: `null` + +Response: `null` + +### SectorCommitPending + + +Perms: admin + +Inputs: `null` + +Response: `null` + ### SectorGetExpectedSealDuration SectorGetExpectedSealDuration gets the expected time for a sector to seal diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index c6d8d2998c8..fedece11d69 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -3,7 +3,7 @@ package sealing import ( "bytes" "context" - "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" + "sort" "sync" "time" @@ -18,6 +18,7 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" ) var ( @@ -230,7 +231,28 @@ func (b *CommitBatcher) Pending(ctx context.Context) ([]abi.SectorID, error) { b.lk.Lock() defer b.lk.Unlock() - panic("todo") + mid, err := address.IDFromAddress(b.maddr) + if err != nil { + return nil, err + } + + res := make([]abi.SectorID, 0) + for _, s := range b.todo { + res = append(res, abi.SectorID{ + Miner: abi.ActorID(mid), + Number: s.info.Number, + }) + } + + sort.Slice(res, func(i, j int) bool { + if res[i].Miner != res[j].Miner { + return res[i].Miner < res[j].Miner + } + + return res[i].Number < res[j].Number + }) + + return res, nil } func (b *CommitBatcher) Stop(ctx context.Context) error { diff --git a/extern/storage-sealing/sealing.go b/extern/storage-sealing/sealing.go index 869146671eb..d990cb02f46 100644 --- a/extern/storage-sealing/sealing.go +++ b/extern/storage-sealing/sealing.go @@ -204,6 +204,14 @@ func (m *Sealing) TerminatePending(ctx context.Context) ([]abi.SectorID, error) return m.terminator.Pending(ctx) } +func (m *Sealing) CommitFlush(ctx context.Context) (*cid.Cid, error) { + return m.commiter.Flush(ctx) +} + +func (m *Sealing) CommitPending(ctx context.Context) ([]abi.SectorID, error) { + return m.commiter.Pending(ctx) +} + func (m *Sealing) currentSealProof(ctx context.Context) (abi.RegisteredSealProof, error) { mi, err := m.api.StateMinerInfo(ctx, m.maddr, nil) if err != nil { diff --git a/lotuspond/front/src/chain/methods.json b/lotuspond/front/src/chain/methods.json index b3bc1aa7c9e..4f08a8d03b5 100644 --- a/lotuspond/front/src/chain/methods.json +++ b/lotuspond/front/src/chain/methods.json @@ -281,7 +281,8 @@ "ConfirmUpdateWorkerKey", "RepayDebt", "ChangeOwnerAddress", - "DisputeWindowedPoSt" + "DisputeWindowedPoSt", + "ProveCommitAggregate" ], "fil/3/storagepower": [ "Send", diff --git a/node/impl/storminer.go b/node/impl/storminer.go index cad886e2dd4..9517c8a9da2 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -378,6 +378,14 @@ func (sm *StorageMinerAPI) SectorMarkForUpgrade(ctx context.Context, id abi.Sect return sm.Miner.MarkForUpgrade(id) } +func (sm *StorageMinerAPI) SectorCommitFlush(ctx context.Context) (*cid.Cid, error) { + return sm.Miner.TerminateFlush(ctx) +} + +func (sm *StorageMinerAPI) SectorCommitPending(ctx context.Context) ([]abi.SectorID, error) { + return sm.Miner.TerminatePending(ctx) +} + func (sm *StorageMinerAPI) WorkerConnect(ctx context.Context, url string) error { w, err := connectRemoteWorker(ctx, sm, url) if err != nil { diff --git a/storage/sealing.go b/storage/sealing.go index 8981c373866..b3d38909b40 100644 --- a/storage/sealing.go +++ b/storage/sealing.go @@ -59,6 +59,14 @@ func (m *Miner) TerminatePending(ctx context.Context) ([]abi.SectorID, error) { return m.sealing.TerminatePending(ctx) } +func (m *Miner) CommitFlush(ctx context.Context) (*cid.Cid, error) { + return m.sealing.CommitFlush(ctx) +} + +func (m *Miner) CommitPending(ctx context.Context) ([]abi.SectorID, error) { + return m.sealing.CommitPending(ctx) +} + func (m *Miner) MarkForUpgrade(id abi.SectorNumber) error { return m.sealing.MarkForUpgrade(id) } From 5c8e2ee0ee793a36ce26e3dfffa79929762a26db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 30 Mar 2021 18:01:39 +0200 Subject: [PATCH 17/17] make things work --- chain/vm/invoker.go | 2 +- chain/vm/runtime.go | 8 ++++---- cmd/lotus-storage-miner/sectors.go | 1 + extern/storage-sealing/commit_batch.go | 5 ++++- node/impl/storminer.go | 4 ++-- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/chain/vm/invoker.go b/chain/vm/invoker.go index 1c1d04f1999..0d237b74ee0 100644 --- a/chain/vm/invoker.go +++ b/chain/vm/invoker.go @@ -16,7 +16,7 @@ import ( exported0 "github.com/filecoin-project/specs-actors/actors/builtin/exported" exported2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/exported" - vmr "github.com/filecoin-project/specs-actors/v2/actors/runtime" + vmr "github.com/filecoin-project/specs-actors/v3/actors/runtime" exported3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/exported" "github.com/filecoin-project/go-state-types/abi" diff --git a/chain/vm/runtime.go b/chain/vm/runtime.go index cdb1720decd..cd66c249c23 100644 --- a/chain/vm/runtime.go +++ b/chain/vm/runtime.go @@ -16,7 +16,7 @@ import ( "github.com/filecoin-project/go-state-types/network" rtt "github.com/filecoin-project/go-state-types/rt" rt0 "github.com/filecoin-project/specs-actors/actors/runtime" - rt2 "github.com/filecoin-project/specs-actors/v2/actors/runtime" + rt3 "github.com/filecoin-project/specs-actors/v3/actors/runtime" "github.com/ipfs/go-cid" ipldcbor "github.com/ipfs/go-ipld-cbor" "go.opencensus.io/trace" @@ -54,8 +54,8 @@ func (m *Message) ValueReceived() abi.TokenAmount { var EnableGasTracing = false type Runtime struct { - rt2.Message - rt2.Syscalls + rt3.Message + rt3.Syscalls ctx context.Context @@ -136,7 +136,7 @@ func (rt *Runtime) StorePut(x cbor.Marshaler) cid.Cid { } var _ rt0.Runtime = (*Runtime)(nil) -var _ rt2.Runtime = (*Runtime)(nil) +var _ rt3.Runtime = (*Runtime)(nil) func (rt *Runtime) shimCall(f func() interface{}) (rval []byte, aerr aerrors.ActorError) { defer func() { diff --git a/cmd/lotus-storage-miner/sectors.go b/cmd/lotus-storage-miner/sectors.go index 57e41c2e806..c992576d245 100644 --- a/cmd/lotus-storage-miner/sectors.go +++ b/cmd/lotus-storage-miner/sectors.go @@ -886,6 +886,7 @@ var sectorsPendingCommit = &cli.Command{ for _, sector := range pending { fmt.Println(sector.Number) } + return nil } fmt.Println("No sectors queued to be committed") diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index fedece11d69..da3799a7b8f 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -11,6 +11,7 @@ import ( "golang.org/x/xerrors" "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-bitfield" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" miner3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/miner" @@ -113,7 +114,9 @@ func (b *CommitBatcher) run() { func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { b.lk.Lock() defer b.lk.Unlock() - params := miner3.ProveCommitAggregateParams{} + params := miner3.ProveCommitAggregateParams{ + SectorNumbers: bitfield.New(), + } total := len(b.todo) if total == 0 { diff --git a/node/impl/storminer.go b/node/impl/storminer.go index 9517c8a9da2..8766ba15433 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -379,11 +379,11 @@ func (sm *StorageMinerAPI) SectorMarkForUpgrade(ctx context.Context, id abi.Sect } func (sm *StorageMinerAPI) SectorCommitFlush(ctx context.Context) (*cid.Cid, error) { - return sm.Miner.TerminateFlush(ctx) + return sm.Miner.CommitFlush(ctx) } func (sm *StorageMinerAPI) SectorCommitPending(ctx context.Context) ([]abi.SectorID, error) { - return sm.Miner.TerminatePending(ctx) + return sm.Miner.CommitPending(ctx) } func (sm *StorageMinerAPI) WorkerConnect(ctx context.Context, url string) error {