From 5073c7e6cf7eea3403eb74b826aec2ad71a66fec Mon Sep 17 00:00:00 2001 From: hannahhoward Date: Wed, 28 Jul 2021 14:46:13 -0700 Subject: [PATCH 01/27] feat(deps): update to branches with improved logging update sub repos with improved logging around data transfer processing --- go.mod | 8 ++++---- go.sum | 14 ++++++++------ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 5f968f6e0ec..89db6a3ae4a 100644 --- a/go.mod +++ b/go.mod @@ -33,16 +33,16 @@ require ( github.com/filecoin-project/go-cbor-util v0.0.0-20191219014500-08c40a1e63a2 github.com/filecoin-project/go-commp-utils v0.1.1-0.20210427191551-70bf140d31c7 github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03 - github.com/filecoin-project/go-data-transfer v1.7.0 + github.com/filecoin-project/go-data-transfer v1.7.1 github.com/filecoin-project/go-fil-commcid v0.1.0 github.com/filecoin-project/go-fil-commp-hashhash v0.1.0 - github.com/filecoin-project/go-fil-markets v1.6.0 + github.com/filecoin-project/go-fil-markets v1.6.1 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-20210723183308-812a16dc01b1 github.com/filecoin-project/go-paramfetch v0.0.2-0.20210614165157-25a6c7769498 github.com/filecoin-project/go-state-types v0.1.1-0.20210722133031-ad9bfe54c124 - github.com/filecoin-project/go-statemachine v0.0.0-20200925024713-05bd7c71fbfe + github.com/filecoin-project/go-statemachine v1.0.0 github.com/filecoin-project/go-statestore v0.1.1 github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b github.com/filecoin-project/specs-actors v0.9.14 @@ -78,7 +78,7 @@ require ( github.com/ipfs/go-ds-pebble v0.0.2-0.20200921225637-ce220f8ac459 github.com/ipfs/go-filestore v1.0.0 github.com/ipfs/go-fs-lock v0.0.6 - github.com/ipfs/go-graphsync v0.6.5 + github.com/ipfs/go-graphsync v0.6.6 github.com/ipfs/go-ipfs-blockstore v1.0.3 github.com/ipfs/go-ipfs-chunker v0.0.5 github.com/ipfs/go-ipfs-ds-help v1.0.0 diff --git a/go.sum b/go.sum index b22f3dc1562..e39d883d990 100644 --- a/go.sum +++ b/go.sum @@ -274,8 +274,9 @@ github.com/filecoin-project/go-commp-utils v0.1.1-0.20210427191551-70bf140d31c7/ github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03 h1:2pMXdBnCiXjfCYx/hLqFxccPoqsSveQFxVLvNxy9bus= github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03/go.mod h1:+viYnvGtUTgJRdy6oaeF4MTFKAfatX071MPDPBL11EQ= github.com/filecoin-project/go-data-transfer v1.0.1/go.mod h1:UxvfUAY9v3ub0a21BSK9u3pB2aq30Y0KMsG+w9/ysyo= -github.com/filecoin-project/go-data-transfer v1.7.0 h1:mFRn+UuTdPROmhplLSekzd4rAs9ug8ubtSY4nw9wYkU= github.com/filecoin-project/go-data-transfer v1.7.0/go.mod h1:GLRr5BmLEqsLwXfiRDG7uJvph22KGL2M4iOuF8EINaU= +github.com/filecoin-project/go-data-transfer v1.7.1 h1:Co4bTenvCc3WnOhQWyXRt59FLZvxwH8UeF0ZCOc1ik0= +github.com/filecoin-project/go-data-transfer v1.7.1/go.mod h1:GLRr5BmLEqsLwXfiRDG7uJvph22KGL2M4iOuF8EINaU= github.com/filecoin-project/go-ds-versioning v0.1.0 h1:y/X6UksYTsK8TLCI7rttCKEvl8btmWxyFMEeeWGUxIQ= github.com/filecoin-project/go-ds-versioning v0.1.0/go.mod h1:mp16rb4i2QPmxBnmanUx8i/XANp+PFCCJWiAb+VW4/s= github.com/filecoin-project/go-fil-commcid v0.0.0-20200716160307-8f644712406f/go.mod h1:Eaox7Hvus1JgPrL5+M3+h7aSPHc0cVqpSxA+TxIEpZQ= @@ -285,8 +286,8 @@ github.com/filecoin-project/go-fil-commcid v0.1.0/go.mod h1:Eaox7Hvus1JgPrL5+M3+ github.com/filecoin-project/go-fil-commp-hashhash v0.1.0 h1:imrrpZWEHRnNqqv0tN7LXep5bFEVOVmQWHJvl2mgsGo= github.com/filecoin-project/go-fil-commp-hashhash v0.1.0/go.mod h1:73S8WSEWh9vr0fDJVnKADhfIv/d6dCbAGaAGWbdJEI8= github.com/filecoin-project/go-fil-markets v1.0.5-0.20201113164554-c5eba40d5335/go.mod h1:AJySOJC00JRWEZzRG2KsfUnqEf5ITXxeX09BE9N4f9c= -github.com/filecoin-project/go-fil-markets v1.6.0 h1:+1usyX7rXz6Ey6hbHd/Fhx616ZvGCI94rW7wneMcptU= -github.com/filecoin-project/go-fil-markets v1.6.0/go.mod h1:ZuFDagROUV6GfvBU//KReTQDw+EZci4rH7jMYTD10vs= +github.com/filecoin-project/go-fil-markets v1.6.1 h1:8xdFyWrELfOzwcGa229bLu/olD+1l4sEWFIsZR7oz5U= +github.com/filecoin-project/go-fil-markets v1.6.1/go.mod h1:ZuFDagROUV6GfvBU//KReTQDw+EZci4rH7jMYTD10vs= github.com/filecoin-project/go-hamt-ipld v0.1.5 h1:uoXrKbCQZ49OHpsTCkrThPNelC4W3LPEk0OrS/ytIBM= github.com/filecoin-project/go-hamt-ipld v0.1.5/go.mod h1:6Is+ONR5Cd5R6XZoCse1CWaXZc0Hdb/JeX+EQCQzX24= github.com/filecoin-project/go-hamt-ipld/v2 v2.0.0 h1:b3UDemBYN2HNfk3KOXNuxgTTxlWi3xVvbQP0IT38fvM= @@ -311,8 +312,9 @@ github.com/filecoin-project/go-state-types v0.1.0/go.mod h1:ezYnPf0bNkTsDibL/psS github.com/filecoin-project/go-state-types v0.1.1-0.20210506134452-99b279731c48/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= github.com/filecoin-project/go-state-types v0.1.1-0.20210722133031-ad9bfe54c124 h1:veGrNABg/9I7prngrowkhwbvW5d5JN55MNKmbsr5FqA= github.com/filecoin-project/go-state-types v0.1.1-0.20210722133031-ad9bfe54c124/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= -github.com/filecoin-project/go-statemachine v0.0.0-20200925024713-05bd7c71fbfe h1:dF8u+LEWeIcTcfUcCf3WFVlc81Fr2JKg8zPzIbBDKDw= github.com/filecoin-project/go-statemachine v0.0.0-20200925024713-05bd7c71fbfe/go.mod h1:FGwQgZAt2Gh5mjlwJUlVB62JeYdo+if0xWxSEfBD9ig= +github.com/filecoin-project/go-statemachine v1.0.0 h1:b8FpFewPSklyAIUqH0oHt4nvKf03bU7asop1bJpjAtQ= +github.com/filecoin-project/go-statemachine v1.0.0/go.mod h1:FGwQgZAt2Gh5mjlwJUlVB62JeYdo+if0xWxSEfBD9ig= github.com/filecoin-project/go-statestore v0.1.0/go.mod h1:LFc9hD+fRxPqiHiaqUEZOinUJB4WARkRfNl10O7kTnI= github.com/filecoin-project/go-statestore v0.1.1 h1:ufMFq00VqnT2CAuDpcGnwLnCX1I/c3OROw/kXVNSTZk= github.com/filecoin-project/go-statestore v0.1.1/go.mod h1:LFc9hD+fRxPqiHiaqUEZOinUJB4WARkRfNl10O7kTnI= @@ -631,8 +633,8 @@ github.com/ipfs/go-graphsync v0.1.0/go.mod h1:jMXfqIEDFukLPZHqDPp8tJMbHO9Rmeb9CE github.com/ipfs/go-graphsync v0.4.2/go.mod h1:/VmbZTUdUMTbNkgzAiCEucIIAU3BkLE2cZrDCVUhyi0= github.com/ipfs/go-graphsync v0.4.3/go.mod h1:mPOwDYv128gf8gxPFgXnz4fNrSYPsWyqisJ7ych+XDY= github.com/ipfs/go-graphsync v0.6.4/go.mod h1:5WyaeigpNdpiYQuW2vwpuecOoEfB4h747ZGEOKmAGTg= -github.com/ipfs/go-graphsync v0.6.5 h1:YAJl6Yit23PQcaawzb1rPK9PSnbbq2jjMRPpRpJ0Y5U= -github.com/ipfs/go-graphsync v0.6.5/go.mod h1:GdHT8JeuIZ0R4lSjFR16Oe4zPi5dXwKi9zR9ADVlcdk= +github.com/ipfs/go-graphsync v0.6.6 h1:In7jjzvSXlrAUz4OjN41lxYf/dzkf1bVeVxLpwKMRo8= +github.com/ipfs/go-graphsync v0.6.6/go.mod h1:GdHT8JeuIZ0R4lSjFR16Oe4zPi5dXwKi9zR9ADVlcdk= github.com/ipfs/go-hamt-ipld v0.1.1/go.mod h1:1EZCr2v0jlCnhpa+aZ0JZYp8Tt2w16+JJOAVz17YcDk= github.com/ipfs/go-ipfs-blockstore v0.0.1/go.mod h1:d3WClOmRQKFnJ0Jz/jj/zmksX0ma1gROTlovZKBmN08= github.com/ipfs/go-ipfs-blockstore v0.1.0/go.mod h1:5aD0AvHPi7mZc6Ci1WCAhiBQu2IsfTduLl+422H6Rqw= From 9b7a7713a78ec32316665abf8c99562f7ea55c01 Mon Sep 17 00:00:00 2001 From: Anton Evangelatov Date: Wed, 28 Jul 2021 12:10:17 +0300 Subject: [PATCH 02/27] add RuntimeSubsystems API method; use it in `lotus-miner info` --- api/api_storage.go | 2 + api/api_subsystems.go | 63 +++ api/docgen/docgen.go | 11 +- api/proxy_gen.go | 13 + cmd/lotus-miner/info.go | 480 ++++++++++++----------- documentation/en/api-v0-methods-miner.md | 14 + node/builder_miner.go | 2 + node/impl/storminer.go | 6 + node/modules/storageminer.go | 17 + v1.11.1 | 170 ++++++++ 10 files changed, 539 insertions(+), 239 deletions(-) create mode 100644 api/api_subsystems.go create mode 100644 v1.11.1 diff --git a/api/api_storage.go b/api/api_storage.go index 154abcea713..d5203265088 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -166,6 +166,8 @@ type StorageMiner interface { MarketPendingDeals(ctx context.Context) (PendingDealInfo, error) //perm:write MarketPublishPendingDeals(ctx context.Context) error //perm:admin + RuntimeSubsystems(ctx context.Context) (MinerSubsystems, error) //perm:read + DealsImportData(ctx context.Context, dealPropCid cid.Cid, file string) error //perm:admin DealsList(ctx context.Context) ([]MarketDeal, error) //perm:admin DealsConsiderOnlineStorageDeals(context.Context) (bool, error) //perm:admin diff --git a/api/api_subsystems.go b/api/api_subsystems.go new file mode 100644 index 00000000000..1894bbdd8a6 --- /dev/null +++ b/api/api_subsystems.go @@ -0,0 +1,63 @@ +package api + +import ( + "bytes" + "encoding/json" +) + +type MinerSubsystems []MinerSubsystem + +func (ms MinerSubsystems) Has(entry MinerSubsystem) bool { + for _, v := range ms { + if v == entry { + return true + } + + } + return false +} + +type MinerSubsystem int + +const ( + MarketsSubsystem MinerSubsystem = iota + MiningSubsystem + SealingSubsystem + SectorStorageSubsystem +) + +func (ms MinerSubsystem) String() string { + return MinerSubsystemToString[ms] +} + +var MinerSubsystemToString = map[MinerSubsystem]string{ + MarketsSubsystem: "Markets", + MiningSubsystem: "Mining", + SealingSubsystem: "Sealing", + SectorStorageSubsystem: "SectorStorage", +} + +var MinerSubsystemToID = map[string]MinerSubsystem{ + "Markets": MarketsSubsystem, + "Mining": MiningSubsystem, + "Sealing": SealingSubsystem, + "SectorStorage": SectorStorageSubsystem, +} + +func (ms MinerSubsystem) MarshalJSON() ([]byte, error) { + buffer := bytes.NewBufferString(`"`) + buffer.WriteString(MinerSubsystemToString[ms]) + buffer.WriteString(`"`) + return buffer.Bytes(), nil +} + +func (ms *MinerSubsystem) UnmarshalJSON(b []byte) error { + var j string + err := json.Unmarshal(b, &j) + if err != nil { + return err + } + // TODO: handle zero value + *ms = MinerSubsystemToID[j] + return nil +} diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index 39980023f0a..1e712a0aeab 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -46,11 +46,12 @@ import ( ) var ExampleValues = map[reflect.Type]interface{}{ - reflect.TypeOf(auth.Permission("")): auth.Permission("write"), - reflect.TypeOf(""): "string value", - reflect.TypeOf(uint64(42)): uint64(42), - reflect.TypeOf(byte(7)): byte(7), - reflect.TypeOf([]byte{}): []byte("byte array"), + reflect.TypeOf(api.MinerSubsystem(0)): api.MinerSubsystem(1), + reflect.TypeOf(auth.Permission("")): auth.Permission("write"), + reflect.TypeOf(""): "string value", + reflect.TypeOf(uint64(42)): uint64(42), + reflect.TypeOf(byte(7)): byte(7), + reflect.TypeOf([]byte{}): []byte("byte array"), } func addExample(v interface{}) { diff --git a/api/proxy_gen.go b/api/proxy_gen.go index 7d96425ffab..a4feb7be157 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -699,6 +699,8 @@ type StorageMinerStruct struct { ReturnUnsealPiece func(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error `perm:"admin"` + RuntimeSubsystems func(p0 context.Context) (MinerSubsystems, error) `perm:"read"` + SealingAbort func(p0 context.Context, p1 storiface.CallID) error `perm:"admin"` SealingSchedDiag func(p0 context.Context, p1 bool) (interface{}, error) `perm:"admin"` @@ -4095,6 +4097,17 @@ func (s *StorageMinerStub) ReturnUnsealPiece(p0 context.Context, p1 storiface.Ca return ErrNotSupported } +func (s *StorageMinerStruct) RuntimeSubsystems(p0 context.Context) (MinerSubsystems, error) { + if s.Internal.RuntimeSubsystems == nil { + return *new(MinerSubsystems), ErrNotSupported + } + return s.Internal.RuntimeSubsystems(p0) +} + +func (s *StorageMinerStub) RuntimeSubsystems(p0 context.Context) (MinerSubsystems, error) { + return *new(MinerSubsystems), ErrNotSupported +} + func (s *StorageMinerStruct) SealingAbort(p0 context.Context, p1 storiface.CallID) error { if s.Internal.SealingAbort == nil { return ErrNotSupported diff --git a/cmd/lotus-miner/info.go b/cmd/lotus-miner/info.go index 3941ce5632f..4c409788fbb 100644 --- a/cmd/lotus-miner/info.go +++ b/cmd/lotus-miner/info.go @@ -55,7 +55,7 @@ func infoCmdAct(cctx *cli.Context) error { } defer closer() - api, acloser, err := lcli.GetFullNodeAPI(cctx) + fullapi, acloser, err := lcli.GetFullNodeAPI(cctx) if err != nil { return err } @@ -63,9 +63,16 @@ func infoCmdAct(cctx *cli.Context) error { ctx := lcli.ReqContext(cctx) + subsystems, err := nodeApi.RuntimeSubsystems(ctx) + if err != nil { + return err + } + + fmt.Println("Enabled subsystems:", subsystems) + fmt.Print("Chain: ") - head, err := api.ChainHead(ctx) + head, err := fullapi.ChainHead(ctx) if err != nil { return err } @@ -95,284 +102,289 @@ func infoCmdAct(cctx *cli.Context) error { fmt.Println() - maddr, err := getActorAddress(ctx, cctx) - if err != nil { - return err - } - - mact, err := api.StateGetActor(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } - - tbs := blockstore.NewTieredBstore(blockstore.NewAPIBlockstore(api), blockstore.NewMemory()) - mas, err := miner.Load(adt.WrapStore(ctx, cbor.NewCborStore(tbs)), mact) - if err != nil { - return err - } + if subsystems.Has(api.SectorStorageSubsystem) { + maddr, err := getActorAddress(ctx, cctx) + if err != nil { + return err + } - // Sector size - mi, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } + mact, err := fullapi.StateGetActor(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } - ssize := types.SizeStr(types.NewInt(uint64(mi.SectorSize))) - fmt.Printf("Miner: %s (%s sectors)\n", color.BlueString("%s", maddr), ssize) + tbs := blockstore.NewTieredBstore(blockstore.NewAPIBlockstore(fullapi), blockstore.NewMemory()) + mas, err := miner.Load(adt.WrapStore(ctx, cbor.NewCborStore(tbs)), mact) + if err != nil { + return err + } - pow, err := api.StateMinerPower(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } + // Sector size + mi, err := fullapi.StateMinerInfo(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } - fmt.Printf("Power: %s / %s (%0.4f%%)\n", - color.GreenString(types.DeciStr(pow.MinerPower.QualityAdjPower)), - types.DeciStr(pow.TotalPower.QualityAdjPower), - types.BigDivFloat( - types.BigMul(pow.MinerPower.QualityAdjPower, big.NewInt(100)), - pow.TotalPower.QualityAdjPower, - ), - ) - - fmt.Printf("\tRaw: %s / %s (%0.4f%%)\n", - color.BlueString(types.SizeStr(pow.MinerPower.RawBytePower)), - types.SizeStr(pow.TotalPower.RawBytePower), - types.BigDivFloat( - types.BigMul(pow.MinerPower.RawBytePower, big.NewInt(100)), - pow.TotalPower.RawBytePower, - ), - ) - secCounts, err := api.StateMinerSectorCount(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } + ssize := types.SizeStr(types.NewInt(uint64(mi.SectorSize))) + fmt.Printf("Miner: %s (%s sectors)\n", color.BlueString("%s", maddr), ssize) - proving := secCounts.Active + secCounts.Faulty - nfaults := secCounts.Faulty - fmt.Printf("\tCommitted: %s\n", types.SizeStr(types.BigMul(types.NewInt(secCounts.Live), types.NewInt(uint64(mi.SectorSize))))) - if nfaults == 0 { - fmt.Printf("\tProving: %s\n", types.SizeStr(types.BigMul(types.NewInt(proving), types.NewInt(uint64(mi.SectorSize))))) - } else { - var faultyPercentage float64 - if secCounts.Live != 0 { - faultyPercentage = float64(100*nfaults) / float64(secCounts.Live) + pow, err := fullapi.StateMinerPower(ctx, maddr, types.EmptyTSK) + if err != nil { + return err } - fmt.Printf("\tProving: %s (%s Faulty, %.2f%%)\n", - types.SizeStr(types.BigMul(types.NewInt(proving), types.NewInt(uint64(mi.SectorSize)))), - types.SizeStr(types.BigMul(types.NewInt(nfaults), types.NewInt(uint64(mi.SectorSize)))), - faultyPercentage) - } - if !pow.HasMinPower { - fmt.Print("Below minimum power threshold, no blocks will be won") - } else { - - winRatio := new(corebig.Rat).SetFrac( - types.BigMul(pow.MinerPower.QualityAdjPower, types.NewInt(build.BlocksPerEpoch)).Int, - pow.TotalPower.QualityAdjPower.Int, + fmt.Printf("Power: %s / %s (%0.4f%%)\n", + color.GreenString(types.DeciStr(pow.MinerPower.QualityAdjPower)), + types.DeciStr(pow.TotalPower.QualityAdjPower), + types.BigDivFloat( + types.BigMul(pow.MinerPower.QualityAdjPower, big.NewInt(100)), + pow.TotalPower.QualityAdjPower, + ), ) - if winRatioFloat, _ := winRatio.Float64(); winRatioFloat > 0 { + fmt.Printf("\tRaw: %s / %s (%0.4f%%)\n", + color.BlueString(types.SizeStr(pow.MinerPower.RawBytePower)), + types.SizeStr(pow.TotalPower.RawBytePower), + types.BigDivFloat( + types.BigMul(pow.MinerPower.RawBytePower, big.NewInt(100)), + pow.TotalPower.RawBytePower, + ), + ) + secCounts, err := fullapi.StateMinerSectorCount(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } - // if the corresponding poisson distribution isn't infinitely small then - // throw it into the mix as well, accounting for multi-wins - winRationWithPoissonFloat := -math.Expm1(-winRatioFloat) - winRationWithPoisson := new(corebig.Rat).SetFloat64(winRationWithPoissonFloat) - if winRationWithPoisson != nil { - winRatio = winRationWithPoisson - winRatioFloat = winRationWithPoissonFloat + proving := secCounts.Active + secCounts.Faulty + nfaults := secCounts.Faulty + fmt.Printf("\tCommitted: %s\n", types.SizeStr(types.BigMul(types.NewInt(secCounts.Live), types.NewInt(uint64(mi.SectorSize))))) + if nfaults == 0 { + fmt.Printf("\tProving: %s\n", types.SizeStr(types.BigMul(types.NewInt(proving), types.NewInt(uint64(mi.SectorSize))))) + } else { + var faultyPercentage float64 + if secCounts.Live != 0 { + faultyPercentage = float64(100*nfaults) / float64(secCounts.Live) } + fmt.Printf("\tProving: %s (%s Faulty, %.2f%%)\n", + types.SizeStr(types.BigMul(types.NewInt(proving), types.NewInt(uint64(mi.SectorSize)))), + types.SizeStr(types.BigMul(types.NewInt(nfaults), types.NewInt(uint64(mi.SectorSize)))), + faultyPercentage) + } - weekly, _ := new(corebig.Rat).Mul( - winRatio, - new(corebig.Rat).SetInt64(7*builtin.EpochsInDay), - ).Float64() - - avgDuration, _ := new(corebig.Rat).Mul( - new(corebig.Rat).SetInt64(builtin.EpochDurationSeconds), - new(corebig.Rat).Inv(winRatio), - ).Float64() - - fmt.Print("Projected average block win rate: ") - color.Blue( - "%.02f/week (every %s)", - weekly, - (time.Second * time.Duration(avgDuration)).Truncate(time.Second).String(), - ) + if !pow.HasMinPower { + fmt.Print("Below minimum power threshold, no blocks will be won") + } else { - // Geometric distribution of P(Y < k) calculated as described in https://en.wikipedia.org/wiki/Geometric_distribution#Probability_Outcomes_Examples - // https://www.wolframalpha.com/input/?i=t+%3E+0%3B+p+%3E+0%3B+p+%3C+1%3B+c+%3E+0%3B+c+%3C1%3B+1-%281-p%29%5E%28t%29%3Dc%3B+solve+t - // t == how many dice-rolls (epochs) before win - // p == winRate == ( minerPower / netPower ) - // c == target probability of win ( 99.9% in this case ) - fmt.Print("Projected block win with ") - color.Green( - "99.9%% probability every %s", - (time.Second * time.Duration( - builtin.EpochDurationSeconds*math.Log(1-0.999)/ - math.Log(1-winRatioFloat), - )).Truncate(time.Second).String(), + winRatio := new(corebig.Rat).SetFrac( + types.BigMul(pow.MinerPower.QualityAdjPower, types.NewInt(build.BlocksPerEpoch)).Int, + pow.TotalPower.QualityAdjPower.Int, ) - fmt.Println("(projections DO NOT account for future network and miner growth)") + + if winRatioFloat, _ := winRatio.Float64(); winRatioFloat > 0 { + + // if the corresponding poisson distribution isn't infinitely small then + // throw it into the mix as well, accounting for multi-wins + winRationWithPoissonFloat := -math.Expm1(-winRatioFloat) + winRationWithPoisson := new(corebig.Rat).SetFloat64(winRationWithPoissonFloat) + if winRationWithPoisson != nil { + winRatio = winRationWithPoisson + winRatioFloat = winRationWithPoissonFloat + } + + weekly, _ := new(corebig.Rat).Mul( + winRatio, + new(corebig.Rat).SetInt64(7*builtin.EpochsInDay), + ).Float64() + + avgDuration, _ := new(corebig.Rat).Mul( + new(corebig.Rat).SetInt64(builtin.EpochDurationSeconds), + new(corebig.Rat).Inv(winRatio), + ).Float64() + + fmt.Print("Projected average block win rate: ") + color.Blue( + "%.02f/week (every %s)", + weekly, + (time.Second * time.Duration(avgDuration)).Truncate(time.Second).String(), + ) + + // Geometric distribution of P(Y < k) calculated as described in https://en.wikipedia.org/wiki/Geometric_distribution#Probability_Outcomes_Examples + // https://www.wolframalpha.com/input/?i=t+%3E+0%3B+p+%3E+0%3B+p+%3C+1%3B+c+%3E+0%3B+c+%3C1%3B+1-%281-p%29%5E%28t%29%3Dc%3B+solve+t + // t == how many dice-rolls (epochs) before win + // p == winRate == ( minerPower / netPower ) + // c == target probability of win ( 99.9% in this case ) + fmt.Print("Projected block win with ") + color.Green( + "99.9%% probability every %s", + (time.Second * time.Duration( + builtin.EpochDurationSeconds*math.Log(1-0.999)/ + math.Log(1-winRatioFloat), + )).Truncate(time.Second).String(), + ) + fmt.Println("(projections DO NOT account for future network and miner growth)") + } } - } - fmt.Println() + fmt.Println() - deals, err := nodeApi.MarketListIncompleteDeals(ctx) - if err != nil { - return err - } + spendable := big.Zero() - type dealStat struct { - count, verifCount int - bytes, verifBytes uint64 - } - dsAdd := func(ds *dealStat, deal storagemarket.MinerDeal) { - ds.count++ - ds.bytes += uint64(deal.Proposal.PieceSize) - if deal.Proposal.VerifiedDeal { - ds.verifCount++ - ds.verifBytes += uint64(deal.Proposal.PieceSize) + // NOTE: there's no need to unlock anything here. Funds only + // vest on deadline boundaries, and they're unlocked by cron. + lockedFunds, err := mas.LockedFunds() + if err != nil { + return xerrors.Errorf("getting locked funds: %w", err) } - } + availBalance, err := mas.AvailableBalance(mact.Balance) + if err != nil { + return xerrors.Errorf("getting available balance: %w", err) + } + spendable = big.Add(spendable, availBalance) - showDealStates := map[storagemarket.StorageDealStatus]struct{}{ - storagemarket.StorageDealActive: {}, - storagemarket.StorageDealTransferring: {}, - storagemarket.StorageDealStaged: {}, - storagemarket.StorageDealAwaitingPreCommit: {}, - storagemarket.StorageDealSealing: {}, - storagemarket.StorageDealPublish: {}, - storagemarket.StorageDealCheckForAcceptance: {}, - storagemarket.StorageDealPublishing: {}, - } + fmt.Printf("Miner Balance: %s\n", color.YellowString("%s", types.FIL(mact.Balance).Short())) + fmt.Printf(" PreCommit: %s\n", types.FIL(lockedFunds.PreCommitDeposits).Short()) + fmt.Printf(" Pledge: %s\n", types.FIL(lockedFunds.InitialPledgeRequirement).Short()) + fmt.Printf(" Vesting: %s\n", types.FIL(lockedFunds.VestingFunds).Short()) + colorTokenAmount(" Available: %s\n", availBalance) - var total dealStat - perState := map[storagemarket.StorageDealStatus]*dealStat{} - for _, deal := range deals { - if _, ok := showDealStates[deal.State]; !ok { - continue - } - if perState[deal.State] == nil { - perState[deal.State] = new(dealStat) + mb, err := fullapi.StateMarketBalance(ctx, maddr, types.EmptyTSK) + if err != nil { + return xerrors.Errorf("getting market balance: %w", err) } + spendable = big.Add(spendable, big.Sub(mb.Escrow, mb.Locked)) - dsAdd(&total, deal) - dsAdd(perState[deal.State], deal) - } + fmt.Printf("Market Balance: %s\n", types.FIL(mb.Escrow).Short()) + fmt.Printf(" Locked: %s\n", types.FIL(mb.Locked).Short()) + colorTokenAmount(" Available: %s\n", big.Sub(mb.Escrow, mb.Locked)) - type wstr struct { - str string - status storagemarket.StorageDealStatus - } - sorted := make([]wstr, 0, len(perState)) - for status, stat := range perState { - st := strings.TrimPrefix(storagemarket.DealStates[status], "StorageDeal") - sorted = append(sorted, wstr{ - str: fmt.Sprintf(" %s:\t%d\t\t%s\t(Verified: %d\t%s)\n", st, stat.count, types.SizeStr(types.NewInt(stat.bytes)), stat.verifCount, types.SizeStr(types.NewInt(stat.verifBytes))), - status: status, - }, - ) - } - sort.Slice(sorted, func(i, j int) bool { - if sorted[i].status == storagemarket.StorageDealActive || sorted[j].status == storagemarket.StorageDealActive { - return sorted[i].status == storagemarket.StorageDealActive + wb, err := fullapi.WalletBalance(ctx, mi.Worker) + if err != nil { + return xerrors.Errorf("getting worker balance: %w", err) } - return sorted[i].status > sorted[j].status - }) + spendable = big.Add(spendable, wb) + color.Cyan("Worker Balance: %s", types.FIL(wb).Short()) + if len(mi.ControlAddresses) > 0 { + cbsum := big.Zero() + for _, ca := range mi.ControlAddresses { + b, err := fullapi.WalletBalance(ctx, ca) + if err != nil { + return xerrors.Errorf("getting control address balance: %w", err) + } + cbsum = big.Add(cbsum, b) + } + spendable = big.Add(spendable, cbsum) - fmt.Printf("Storage Deals: %d, %s\n", total.count, types.SizeStr(types.NewInt(total.bytes))) + fmt.Printf(" Control: %s\n", types.FIL(cbsum).Short()) + } + colorTokenAmount("Total Spendable: %s\n", spendable) - tw := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0) - for _, e := range sorted { - _, _ = tw.Write([]byte(e.str)) - } + fmt.Println() - _ = tw.Flush() - fmt.Println() + if !cctx.Bool("hide-sectors-info") { + fmt.Println("Sectors:") + err = sectorsInfo(ctx, nodeApi) + if err != nil { + return err + } + } - retrievals, err := nodeApi.MarketListRetrievalDeals(ctx) - if err != nil { - return xerrors.Errorf("getting retrieval deal list: %w", err) + // TODO: grab actr state / info + // * Sealed sectors (count / bytes) + // * Power } - var retrComplete dealStat - for _, retrieval := range retrievals { - if retrieval.Status == retrievalmarket.DealStatusCompleted { - retrComplete.count++ - retrComplete.bytes += retrieval.TotalSent + if subsystems.Has(api.MarketsSubsystem) { + deals, err := nodeApi.MarketListIncompleteDeals(ctx) + if err != nil { + return err } - } - - fmt.Printf("Retrieval Deals (complete): %d, %s\n", retrComplete.count, types.SizeStr(types.NewInt(retrComplete.bytes))) - fmt.Println() + type dealStat struct { + count, verifCount int + bytes, verifBytes uint64 + } + dsAdd := func(ds *dealStat, deal storagemarket.MinerDeal) { + ds.count++ + ds.bytes += uint64(deal.Proposal.PieceSize) + if deal.Proposal.VerifiedDeal { + ds.verifCount++ + ds.verifBytes += uint64(deal.Proposal.PieceSize) + } + } - spendable := big.Zero() + showDealStates := map[storagemarket.StorageDealStatus]struct{}{ + storagemarket.StorageDealActive: {}, + storagemarket.StorageDealTransferring: {}, + storagemarket.StorageDealStaged: {}, + storagemarket.StorageDealAwaitingPreCommit: {}, + storagemarket.StorageDealSealing: {}, + storagemarket.StorageDealPublish: {}, + storagemarket.StorageDealCheckForAcceptance: {}, + storagemarket.StorageDealPublishing: {}, + } - // NOTE: there's no need to unlock anything here. Funds only - // vest on deadline boundaries, and they're unlocked by cron. - lockedFunds, err := mas.LockedFunds() - if err != nil { - return xerrors.Errorf("getting locked funds: %w", err) - } - availBalance, err := mas.AvailableBalance(mact.Balance) - if err != nil { - return xerrors.Errorf("getting available balance: %w", err) - } - spendable = big.Add(spendable, availBalance) + var total dealStat + perState := map[storagemarket.StorageDealStatus]*dealStat{} + for _, deal := range deals { + if _, ok := showDealStates[deal.State]; !ok { + continue + } + if perState[deal.State] == nil { + perState[deal.State] = new(dealStat) + } - fmt.Printf("Miner Balance: %s\n", color.YellowString("%s", types.FIL(mact.Balance).Short())) - fmt.Printf(" PreCommit: %s\n", types.FIL(lockedFunds.PreCommitDeposits).Short()) - fmt.Printf(" Pledge: %s\n", types.FIL(lockedFunds.InitialPledgeRequirement).Short()) - fmt.Printf(" Vesting: %s\n", types.FIL(lockedFunds.VestingFunds).Short()) - colorTokenAmount(" Available: %s\n", availBalance) + dsAdd(&total, deal) + dsAdd(perState[deal.State], deal) + } - mb, err := api.StateMarketBalance(ctx, maddr, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("getting market balance: %w", err) - } - spendable = big.Add(spendable, big.Sub(mb.Escrow, mb.Locked)) + type wstr struct { + str string + status storagemarket.StorageDealStatus + } + sorted := make([]wstr, 0, len(perState)) + for status, stat := range perState { + st := strings.TrimPrefix(storagemarket.DealStates[status], "StorageDeal") + sorted = append(sorted, wstr{ + str: fmt.Sprintf(" %s:\t%d\t\t%s\t(Verified: %d\t%s)\n", st, stat.count, types.SizeStr(types.NewInt(stat.bytes)), stat.verifCount, types.SizeStr(types.NewInt(stat.verifBytes))), + status: status, + }, + ) + } + sort.Slice(sorted, func(i, j int) bool { + if sorted[i].status == storagemarket.StorageDealActive || sorted[j].status == storagemarket.StorageDealActive { + return sorted[i].status == storagemarket.StorageDealActive + } + return sorted[i].status > sorted[j].status + }) - fmt.Printf("Market Balance: %s\n", types.FIL(mb.Escrow).Short()) - fmt.Printf(" Locked: %s\n", types.FIL(mb.Locked).Short()) - colorTokenAmount(" Available: %s\n", big.Sub(mb.Escrow, mb.Locked)) + fmt.Printf("Storage Deals: %d, %s\n", total.count, types.SizeStr(types.NewInt(total.bytes))) - wb, err := api.WalletBalance(ctx, mi.Worker) - if err != nil { - return xerrors.Errorf("getting worker balance: %w", err) - } - spendable = big.Add(spendable, wb) - color.Cyan("Worker Balance: %s", types.FIL(wb).Short()) - if len(mi.ControlAddresses) > 0 { - cbsum := big.Zero() - for _, ca := range mi.ControlAddresses { - b, err := api.WalletBalance(ctx, ca) - if err != nil { - return xerrors.Errorf("getting control address balance: %w", err) - } - cbsum = big.Add(cbsum, b) + tw := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0) + for _, e := range sorted { + _, _ = tw.Write([]byte(e.str)) } - spendable = big.Add(spendable, cbsum) - - fmt.Printf(" Control: %s\n", types.FIL(cbsum).Short()) - } - colorTokenAmount("Total Spendable: %s\n", spendable) - fmt.Println() + _ = tw.Flush() + fmt.Println() - if !cctx.Bool("hide-sectors-info") { - fmt.Println("Sectors:") - err = sectorsInfo(ctx, nodeApi) + retrievals, err := nodeApi.MarketListRetrievalDeals(ctx) if err != nil { - return err + return xerrors.Errorf("getting retrieval deal list: %w", err) } + + var retrComplete dealStat + for _, retrieval := range retrievals { + if retrieval.Status == retrievalmarket.DealStatusCompleted { + retrComplete.count++ + retrComplete.bytes += retrieval.TotalSent + } + } + + fmt.Printf("Retrieval Deals (complete): %d, %s\n", retrComplete.count, types.SizeStr(types.NewInt(retrComplete.bytes))) + + fmt.Println() } - // TODO: grab actr state / info - // * Sealed sectors (count / bytes) - // * Power return nil } diff --git a/documentation/en/api-v0-methods-miner.md b/documentation/en/api-v0-methods-miner.md index e8598ff0ceb..dcd3abd4df6 100644 --- a/documentation/en/api-v0-methods-miner.md +++ b/documentation/en/api-v0-methods-miner.md @@ -94,6 +94,8 @@ * [ReturnSealPreCommit1](#ReturnSealPreCommit1) * [ReturnSealPreCommit2](#ReturnSealPreCommit2) * [ReturnUnsealPiece](#ReturnUnsealPiece) +* [Runtime](#Runtime) + * [RuntimeSubsystems](#RuntimeSubsystems) * [Sealing](#Sealing) * [SealingAbort](#SealingAbort) * [SealingSchedDiag](#SealingSchedDiag) @@ -1522,6 +1524,18 @@ Inputs: Response: `{}` +## Runtime + + +### RuntimeSubsystems + + +Perms: read + +Inputs: `null` + +Response: `null` + ## Sealing diff --git a/node/builder_miner.go b/node/builder_miner.go index 0c0f9d15af9..d62cbcad9bb 100644 --- a/node/builder_miner.go +++ b/node/builder_miner.go @@ -72,6 +72,7 @@ func ConfigStorageMiner(c interface{}) Option { return Options( ConfigCommon(&cfg.Common, enableLibp2pNode), + Override(new([]api.MinerSubsystem), modules.AddMinerSubsystems(cfg.Subsystems)), Override(new(stores.LocalStorage), From(new(repo.LockedRepo))), Override(new(*stores.Local), modules.LocalStorage), Override(new(*stores.Remote), modules.RemoteStorage), @@ -215,6 +216,7 @@ func StorageMiner(out *api.StorageMiner, subsystemsCfg config.MinerSubsystemConf func(s *Settings) error { resAPI := &impl.StorageMinerAPI{} + s.invokes[ExtractApiKey] = fx.Populate(resAPI) *out = resAPI return nil diff --git a/node/impl/storminer.go b/node/impl/storminer.go index 9db6a3775ce..86c84fee7ea 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -48,6 +48,8 @@ import ( type StorageMinerAPI struct { fx.In + Subsystems api.MinerSubsystems + api.Common api.Net @@ -703,4 +705,8 @@ func (sm *StorageMinerAPI) ComputeProof(ctx context.Context, ssi []builtin.Secto return sm.Epp.ComputeProof(ctx, ssi, rand) } +func (sm *StorageMinerAPI) RuntimeSubsystems(context.Context) (res api.MinerSubsystems, err error) { + return sm.Subsystems, nil +} + var _ api.StorageMiner = &StorageMinerAPI{} diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 3a3914e0c85..06ef78ca0af 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -1007,3 +1007,20 @@ func mutateCfg(r repo.LockedRepo, mutator func(*config.StorageMiner)) error { return multierr.Combine(typeErr, setConfigErr) } + +func AddMinerSubsystems(cfg config.MinerSubsystemConfig) (res api.MinerSubsystems) { + if cfg.EnableMining { + res = append(res, api.MiningSubsystem) + } + if cfg.EnableSealing { + res = append(res, api.SealingSubsystem) + } + if cfg.EnableSectorStorage { + res = append(res, api.SectorStorageSubsystem) + } + if cfg.EnableMarkets { + res = append(res, api.MarketsSubsystem) + } + + return +} diff --git a/v1.11.1 b/v1.11.1 new file mode 100644 index 00000000000..225a346deba --- /dev/null +++ b/v1.11.1 @@ -0,0 +1,170 @@ +- github.com/filecoin-project/lotus: + - Merge branch 'releases' into release/v1.11.1 + - Update to proof v8.0.3 ([filecoin-project/lotus#6890](https://github.com/filecoin-project/lotus/pull/6890)) + - lotus-shed: initial export cmd for markets related metadata ([filecoin-project/lotus#6840](https://github.com/filecoin-project/lotus/pull/6840)) + - add a very verbose -vv flag to lotus and lotus-miner. ([filecoin-project/lotus#6888](https://github.com/filecoin-project/lotus/pull/6888)) + - Update RELEASE_ISSUE_TEMPLATE.md ([filecoin-project/lotus#6880](https://github.com/filecoin-project/lotus/pull/6880)) + - Moving GC for badger ([filecoin-project/lotus#6854](https://github.com/filecoin-project/lotus/pull/6854)) + - Add github actions for staled pr ([filecoin-project/lotus#6879](https://github.com/filecoin-project/lotus/pull/6879)) + - Add allocated sectorid vis ([filecoin-project/lotus#4638](https://github.com/filecoin-project/lotus/pull/4638)) + - rename `cmd/lotus{-storage=>}-miner` to match binary. ([filecoin-project/lotus#6886](https://github.com/filecoin-project/lotus/pull/6886)) + - update to go-fil-market v1.6.0 ([filecoin-project/lotus#6885](https://github.com/filecoin-project/lotus/pull/6885)) + - Bump go-multihash, adjust test for supported version ([filecoin-project/lotus#6674](https://github.com/filecoin-project/lotus/pull/6674)) + - Fix padding of deals, which only partially shipped in #5988 ([filecoin-project/lotus#6683](https://github.com/filecoin-project/lotus/pull/6683)) + - fix racy TestSimultanenousTransferLimit. ([filecoin-project/lotus#6862](https://github.com/filecoin-project/lotus/pull/6862)) + - Improve splitstore warmup ([filecoin-project/lotus#6867](https://github.com/filecoin-project/lotus/pull/6867)) + - ValidateBlock: Assert that block header height's are greater than parents ([filecoin-project/lotus#6872](https://github.com/filecoin-project/lotus/pull/6872)) + - feat: Don't panic when api impl is nil ([filecoin-project/lotus#6857](https://github.com/filecoin-project/lotus/pull/6857)) + - splitstore shed utils ([filecoin-project/lotus#6811](https://github.com/filecoin-project/lotus/pull/6811)) + - Fix links in issue templates + - Update issue templates and add templates for M1 ([filecoin-project/lotus#6856](https://github.com/filecoin-project/lotus/pull/6856)) + - Splitstore: support on-disk marksets using badger ([filecoin-project/lotus#6833](https://github.com/filecoin-project/lotus/pull/6833)) + - Config UX improvements ([filecoin-project/lotus#6848](https://github.com/filecoin-project/lotus/pull/6848)) + - fix deal concurrency test failures by upgrading graphsync and others ([filecoin-project/lotus#6724](https://github.com/filecoin-project/lotus/pull/6724)) + - Update issue templates to forms ([filecoin-project/lotus#6798](https://github.com/filecoin-project/lotus/pull/6798)) + - Nerpa v13 upgrade ([filecoin-project/lotus#6837](https://github.com/filecoin-project/lotus/pull/6837)) + - add docker-compose file ([filecoin-project/lotus#6544](https://github.com/filecoin-project/lotus/pull/6544)) + - fix warmup by decoupling state from message receipt walk ([filecoin-project/lotus#6841](https://github.com/filecoin-project/lotus/pull/6841)) + - add a command for compacting sector numbers bitfield ([filecoin-project/lotus#4640](https://github.com/filecoin-project/lotus/pull/4640)) + - PriceListByVersion ([filecoin-project/lotus#6766](https://github.com/filecoin-project/lotus/pull/6766)) + - easy way to make install app ([filecoin-project/lotus#5183](https://github.com/filecoin-project/lotus/pull/5183)) + - api: Separate the Net interface from Common ([filecoin-project/lotus#6627](https://github.com/filecoin-project/lotus/pull/6627)) + - cache loaded block messages ([filecoin-project/lotus#6760](https://github.com/filecoin-project/lotus/pull/6760)) + - fix: on randomness change, use new rand ([filecoin-project/lotus#6805](https://github.com/filecoin-project/lotus/pull/6805)) + - Splitstore: add retention policy option for keeping messages in the hotstore ([filecoin-project/lotus#6775](https://github.com/filecoin-project/lotus/pull/6775)) + - Introduce the LOTUS_CHAIN_BADGERSTORE_DISABLE_FSYNC envvar ([filecoin-project/lotus#6817](https://github.com/filecoin-project/lotus/pull/6817)) + - add StateReadState to gateway api ([filecoin-project/lotus#6818](https://github.com/filecoin-project/lotus/pull/6818)) + - add SealProof in SectorBuilder ([filecoin-project/lotus#6815](https://github.com/filecoin-project/lotus/pull/6815)) + - release -> master ([filecoin-project/lotus#6828](https://github.com/filecoin-project/lotus/pull/6828)) + - sealing: Handle preCommitParams errors more correctly ([filecoin-project/lotus#6763](https://github.com/filecoin-project/lotus/pull/6763)) + - fix: always check if StateSearchMessage returns nil ([filecoin-project/lotus#6802](https://github.com/filecoin-project/lotus/pull/6802)) + - ClientFindData: always fetch peer id from chain ([filecoin-project/lotus#6807](https://github.com/filecoin-project/lotus/pull/6807)) + - test: fix flaky window post tests ([filecoin-project/lotus#6804](https://github.com/filecoin-project/lotus/pull/6804)) + - ([filecoin-project/lotus#6800](https://github.com/filecoin-project/lotus/pull/6800)) + - fixes #6786 segfault ([filecoin-project/lotus#6787](https://github.com/filecoin-project/lotus/pull/6787)) + - Splitstore: add support for protecting out of chain references in the blockstore ([filecoin-project/lotus#6777](https://github.com/filecoin-project/lotus/pull/6777)) + - Resurrect CODEOWNERS, but for maintainers group ([filecoin-project/lotus#6773](https://github.com/filecoin-project/lotus/pull/6773)) + - update go-libp2p-pubsub to v0.5.0 ([filecoin-project/lotus#6764](https://github.com/filecoin-project/lotus/pull/6764)) + - Implement exposed splitstore ([filecoin-project/lotus#6762](https://github.com/filecoin-project/lotus/pull/6762)) + - Add ChainGetMessagesInTipset API ([filecoin-project/lotus#6642](https://github.com/filecoin-project/lotus/pull/6642)) + - test: handle null blocks in TestForkRefuseCall ([filecoin-project/lotus#6758](https://github.com/filecoin-project/lotus/pull/6758)) + - Master disclaimer ([filecoin-project/lotus#6757](https://github.com/filecoin-project/lotus/pull/6757)) + - Splitstore code reorg ([filecoin-project/lotus#6756](https://github.com/filecoin-project/lotus/pull/6756)) + - Create stale.yml ([filecoin-project/lotus#6747](https://github.com/filecoin-project/lotus/pull/6747)) + - Splitstore: Some small fixes ([filecoin-project/lotus#6754](https://github.com/filecoin-project/lotus/pull/6754)) + - ([filecoin-project/lotus#6746](https://github.com/filecoin-project/lotus/pull/6746)) + - Handle the --color flag via proper global state ([filecoin-project/lotus#6743](https://github.com/filecoin-project/lotus/pull/6743)) + - Config for collateral from miner available balance ([filecoin-project/lotus#6629](https://github.com/filecoin-project/lotus/pull/6629)) + - Support standalone miner-market process ([filecoin-project/lotus#6356](https://github.com/filecoin-project/lotus/pull/6356)) + - Splitstore Enhanchements ([filecoin-project/lotus#6474](https://github.com/filecoin-project/lotus/pull/6474)) + - ([filecoin-project/lotus#6739](https://github.com/filecoin-project/lotus/pull/6739)) + - Add more deal details to lotus-miner info ([filecoin-project/lotus#6708](https://github.com/filecoin-project/lotus/pull/6708)) + - Release template: Update all testnet infra at once ([filecoin-project/lotus#6710](https://github.com/filecoin-project/lotus/pull/6710)) + - Fix Lotus shed + - Fix bugs in sectors extend --v1-sectors ([filecoin-project/lotus#6066](https://github.com/filecoin-project/lotus/pull/6066)) + - add election backtest ([filecoin-project/lotus#5950](https://github.com/filecoin-project/lotus/pull/5950)) + - Envvar to disable slash filter ([filecoin-project/lotus#6620](https://github.com/filecoin-project/lotus/pull/6620)) + - Release Template: remove binary validation step ([filecoin-project/lotus#6709](https://github.com/filecoin-project/lotus/pull/6709)) + - Config for deal publishing control addresses ([filecoin-project/lotus#6697](https://github.com/filecoin-project/lotus/pull/6697)) + - Reset of the interop network ([filecoin-project/lotus#6689](https://github.com/filecoin-project/lotus/pull/6689)) + - Enable color by default only if os.Stdout is a TTY ([filecoin-project/lotus#6696](https://github.com/filecoin-project/lotus/pull/6696)) + - Stop outputing ANSI color on non-TTY ([filecoin-project/lotus#6694](https://github.com/filecoin-project/lotus/pull/6694)) + - add dollar sign ([filecoin-project/lotus#6690](https://github.com/filecoin-project/lotus/pull/6690)) + - get-actor cli spelling fix ([filecoin-project/lotus#6681](https://github.com/filecoin-project/lotus/pull/6681)) + - fix "lotus-seed genesis car" error "merkledag: not found" ([filecoin-project/lotus#6688](https://github.com/filecoin-project/lotus/pull/6688)) + - polish(statetree): accept a context in statetree diff for timeouts ([filecoin-project/lotus#6639](https://github.com/filecoin-project/lotus/pull/6639)) + - Add helptext to lotus chain export ([filecoin-project/lotus#6672](https://github.com/filecoin-project/lotus/pull/6672)) + - Get retrieval pricing input should not error out on a deal state fetch ([filecoin-project/lotus#6679](https://github.com/filecoin-project/lotus/pull/6679)) + - Fix more CID double-encoding as hex ([filecoin-project/lotus#6680](https://github.com/filecoin-project/lotus/pull/6680)) + - add an incremental nonce itest. ([filecoin-project/lotus#6663](https://github.com/filecoin-project/lotus/pull/6663)) + - storage: Fix FinalizeSector with sectors in stoage paths ([filecoin-project/lotus#6653](https://github.com/filecoin-project/lotus/pull/6653)) + - Fix tiny error in check-client-datacap ([filecoin-project/lotus#6664](https://github.com/filecoin-project/lotus/pull/6664)) + - Fix: precommit_batch method used the wrong cfg.CommitBatchWait ([filecoin-project/lotus#6658](https://github.com/filecoin-project/lotus/pull/6658)) + - fix ticket expiration check ([filecoin-project/lotus#6635](https://github.com/filecoin-project/lotus/pull/6635)) + - commit batch: AggregateAboveBaseFee config ([filecoin-project/lotus#6650](https://github.com/filecoin-project/lotus/pull/6650)) + - commit batch: Initialize the FailedSectors map ([filecoin-project/lotus#6647](https://github.com/filecoin-project/lotus/pull/6647)) + - Fast-path retry submitting commit aggregate if commit is still valid ([filecoin-project/lotus#6638](https://github.com/filecoin-project/lotus/pull/6638)) + - remove precommit check in handleCommitFailed ([filecoin-project/lotus#6634](https://github.com/filecoin-project/lotus/pull/6634)) + - Reuse timers in sealing batch logic ([filecoin-project/lotus#6636](https://github.com/filecoin-project/lotus/pull/6636)) + - shed tool to estimate aggregate network fees ([filecoin-project/lotus#6631](https://github.com/filecoin-project/lotus/pull/6631)) + - fix prove commit aggregate send token amount ([filecoin-project/lotus#6625](https://github.com/filecoin-project/lotus/pull/6625)) + - Update version.go to 1.11.1 ([filecoin-project/lotus#6621](https://github.com/filecoin-project/lotus/pull/6621)) +- github.com/filecoin-project/go-data-transfer (v1.6.0 -> v1.7.0): + - release: v1.7.0 + - Fire a transfer queued event when a transfer is queued in Graphsync (#221) ([filecoin-project/go-data-transfer#221](https://github.com/filecoin-project/go-data-transfer/pull/221)) + - feat: pass ChannelID to ValidatePush & ValidatePull (#220) ([filecoin-project/go-data-transfer#220](https://github.com/filecoin-project/go-data-transfer/pull/220)) + - release: v1.6.1 ([filecoin-project/go-data-transfer#218](https://github.com/filecoin-project/go-data-transfer/pull/218)) + - Remove CID lists (#217) ([filecoin-project/go-data-transfer#217](https://github.com/filecoin-project/go-data-transfer/pull/217)) + - Merge v1.6.0 ([filecoin-project/go-data-transfer#214](https://github.com/filecoin-project/go-data-transfer/pull/214)) + - Remove restart ack timeout (#211) ([filecoin-project/go-data-transfer#211](https://github.com/filecoin-project/go-data-transfer/pull/211)) + - feat: use different extension names to fit multiple hooks data in same graphsync message (#204) ([filecoin-project/go-data-transfer#204](https://github.com/filecoin-project/go-data-transfer/pull/204)) + - fix: map race in GS transport (#208) ([filecoin-project/go-data-transfer#208](https://github.com/filecoin-project/go-data-transfer/pull/208)) + - refactor: simplify graphsync transport (#203) ([filecoin-project/go-data-transfer#203](https://github.com/filecoin-project/go-data-transfer/pull/203)) + - release: v1.5.0 (#200) ([filecoin-project/go-data-transfer#200](https://github.com/filecoin-project/go-data-transfer/pull/200)) +- github.com/filecoin-project/go-fil-markets (v1.5.0 -> v1.6.0): + - release: v1.6.0 + - support padding out smaller files (#536) ([filecoin-project/go-fil-markets#536](https://github.com/filecoin-project/go-fil-markets/pull/536)) + - On overloaded CI 10 seconds just isn't enough (#587) ([filecoin-project/go-fil-markets#587](https://github.com/filecoin-project/go-fil-markets/pull/587)) + - Do not hex-encode CIDs in logs (#561) ([filecoin-project/go-fil-markets#561](https://github.com/filecoin-project/go-fil-markets/pull/561)) + - remove wrong peer check in push deal validation (#585) ([filecoin-project/go-fil-markets#585](https://github.com/filecoin-project/go-fil-markets/pull/585)) + - fix: circleci docs-gen task (#574) ([filecoin-project/go-fil-markets#574](https://github.com/filecoin-project/go-fil-markets/pull/574)) + - Storage market request queued event and validation interface changes (#555) ([filecoin-project/go-fil-markets#555](https://github.com/filecoin-project/go-fil-markets/pull/555)) + - build(deps): bump ws from 6.2.1 to 6.2.2 (#554) ([filecoin-project/go-fil-markets#554](https://github.com/filecoin-project/go-fil-markets/pull/554)) + - release: v1.5.0 ([filecoin-project/go-fil-markets#553](https://github.com/filecoin-project/go-fil-markets/pull/553)) +- github.com/filecoin-project/go-padreader (v0.0.0-20200903213702-ed5fae088b20 -> v0.0.0-20210723183308-812a16dc01b1): + - New method to pad harder (#6) ([filecoin-project/go-padreader#6](https://github.com/filecoin-project/go-padreader/pull/6)) + - Create SECURITY.md (#5) ([filecoin-project/go-padreader#5](https://github.com/filecoin-project/go-padreader/pull/5)) +- github.com/filecoin-project/go-state-types (v0.1.1-0.20210506134452-99b279731c48 -> v0.1.1-0.20210722133031-ad9bfe54c124): + - Add version 6.5 (#30) ([filecoin-project/go-state-types#30](https://github.com/filecoin-project/go-state-types/pull/30)) + - rename file +- github.com/filecoin-project/specs-actors/v5 (v5.0.1 -> v5.0.3): + - Adjust code for subtle change in go-multihash 0.0.15 (#1463) ([filecoin-project/specs-actors#1463](https://github.com/filecoin-project/specs-actors/pull/1463)) + - Bump go state types (#1464) ([filecoin-project/specs-actors#1464](https://github.com/filecoin-project/specs-actors/pull/1464)) + - Create CODEOWNERS (#1465) ([filecoin-project/specs-actors#1465](https://github.com/filecoin-project/specs-actors/pull/1465)) + - Test deterministic offset (#1462) ([filecoin-project/specs-actors#1462](https://github.com/filecoin-project/specs-actors/pull/1462)) + +Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| vyzo | 295 | +8700/-5936 | 397 | +| Anton Evangelatov | 94 | +4680/-2965 | 277 | +| Łukasz Magiera | 37 | +3851/-1611 | 146 | +| Mike Greenberg | 1 | +2310/-578 | 8 | +| dirkmc | 7 | +1154/-726 | 29 | +| Jennifer Wang | 9 | +485/-341 | 26 | +| Peter Rabbitson | 18 | +469/-273 | 64 | +| Cory Schwartz | 5 | +576/-135 | 14 | +| hunjixin | 7 | +404/-82 | 19 | +| ZenGround0 | 17 | +284/-135 | 44 | +| Dirk McCormick | 17 | +348/-47 | 17 | +| Raúl Kripalani | 18 | +254/-97 | 62 | +| tchardin | 1 | +261/-33 | 4 | +| Jakub Sztandera | 4 | +254/-16 | 4 | +| Aarsh Shah | 2 | +196/-40 | 28 | +| whyrusleeping | 3 | +150/-9 | 8 | +| Whyrusleeping | 2 | +87/-66 | 10 | +| Aayush Rajasekaran | 10 | +81/-53 | 13 | +| zgfzgf | 2 | +104/-4 | 2 | +| aarshkshah1992 | 4 | +73/-7 | 6 | +| llifezou | 4 | +59/-20 | 4 | +| Steven Allen | 7 | +47/-17 | 9 | +| johnli-helloworld | 3 | +46/-15 | 5 | +| frrist | 1 | +28/-23 | 2 | +| Jennifer | 4 | +31/-2 | 4 | +| wangchao | 1 | +1/-27 | 1 | +| Jiaying Wang | 2 | +7/-21 | 2 | +| hannahhoward | 3 | +21/-2 | 3 | +| chadwick2143 | 1 | +15/-1 | 1 | +| Jerry | 2 | +9/-4 | 2 | +| Steve Loeppky | 2 | +12/-0 | 2 | +| David Dias | 1 | +9/-0 | 1 | +| dependabot[bot] | 1 | +3/-3 | 1 | +| zhoutian527 | 1 | +2/-2 | 1 | +| xloem | 1 | +4/-0 | 1 | +| Travis Person | 2 | +2/-2 | 3 | +| Liviu Damian | 2 | +2/-2 | 2 | +| Jim Pick | 2 | +2/-2 | 2 | +| Frank | 1 | +3/-0 | 1 | +| turuslan | 1 | +1/-1 | 1 | +| Kirk Baird | 1 | +0/-0 | 1 | From 0dd83c675557f36d4295dc1b30cb53e70769e6cc Mon Sep 17 00:00:00 2001 From: Anton Evangelatov Date: Wed, 28 Jul 2021 16:23:04 +0300 Subject: [PATCH 03/27] fixup --- node/builder_miner.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/node/builder_miner.go b/node/builder_miner.go index d62cbcad9bb..830e6d075b2 100644 --- a/node/builder_miner.go +++ b/node/builder_miner.go @@ -72,7 +72,7 @@ func ConfigStorageMiner(c interface{}) Option { return Options( ConfigCommon(&cfg.Common, enableLibp2pNode), - Override(new([]api.MinerSubsystem), modules.AddMinerSubsystems(cfg.Subsystems)), + Override(new(api.MinerSubsystems), modules.AddMinerSubsystems(cfg.Subsystems)), Override(new(stores.LocalStorage), From(new(repo.LockedRepo))), Override(new(*stores.Local), modules.LocalStorage), Override(new(*stores.Remote), modules.RemoteStorage), @@ -216,7 +216,6 @@ func StorageMiner(out *api.StorageMiner, subsystemsCfg config.MinerSubsystemConf func(s *Settings) error { resAPI := &impl.StorageMinerAPI{} - s.invokes[ExtractApiKey] = fx.Populate(resAPI) *out = resAPI return nil From cea26348c44a56ecd46f6443c503cdb3ddab3cf7 Mon Sep 17 00:00:00 2001 From: Anton Evangelatov Date: Wed, 28 Jul 2021 17:30:59 +0300 Subject: [PATCH 04/27] extract outputs for mining node and markets node in separate functions --- cmd/lotus-miner/info.go | 471 +++++++++++++++++++++------------------- 1 file changed, 245 insertions(+), 226 deletions(-) diff --git a/cmd/lotus-miner/info.go b/cmd/lotus-miner/info.go index 4c409788fbb..2a36279595b 100644 --- a/cmd/lotus-miner/info.go +++ b/cmd/lotus-miner/info.go @@ -21,6 +21,7 @@ import ( "github.com/filecoin-project/go-fil-markets/storagemarket" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/lotus/api/v0api" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" "github.com/filecoin-project/lotus/api" @@ -103,288 +104,306 @@ func infoCmdAct(cctx *cli.Context) error { fmt.Println() if subsystems.Has(api.SectorStorageSubsystem) { - maddr, err := getActorAddress(ctx, cctx) + err := handleMiningInfo(cctx, ctx, fullapi, nodeApi) if err != nil { return err } + } - mact, err := fullapi.StateGetActor(ctx, maddr, types.EmptyTSK) + if subsystems.Has(api.MarketsSubsystem) { + err := handleMarketsInfo(ctx, nodeApi) if err != nil { return err } + } - tbs := blockstore.NewTieredBstore(blockstore.NewAPIBlockstore(fullapi), blockstore.NewMemory()) - mas, err := miner.Load(adt.WrapStore(ctx, cbor.NewCborStore(tbs)), mact) - if err != nil { - return err - } + return nil +} - // Sector size - mi, err := fullapi.StateMinerInfo(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } +func handleMiningInfo(cctx *cli.Context, ctx context.Context, fullapi v0api.FullNode, nodeApi api.StorageMiner) error { + maddr, err := getActorAddress(ctx, cctx) + if err != nil { + return err + } - ssize := types.SizeStr(types.NewInt(uint64(mi.SectorSize))) - fmt.Printf("Miner: %s (%s sectors)\n", color.BlueString("%s", maddr), ssize) + mact, err := fullapi.StateGetActor(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } - pow, err := fullapi.StateMinerPower(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } + tbs := blockstore.NewTieredBstore(blockstore.NewAPIBlockstore(fullapi), blockstore.NewMemory()) + mas, err := miner.Load(adt.WrapStore(ctx, cbor.NewCborStore(tbs)), mact) + if err != nil { + return err + } - fmt.Printf("Power: %s / %s (%0.4f%%)\n", - color.GreenString(types.DeciStr(pow.MinerPower.QualityAdjPower)), - types.DeciStr(pow.TotalPower.QualityAdjPower), - types.BigDivFloat( - types.BigMul(pow.MinerPower.QualityAdjPower, big.NewInt(100)), - pow.TotalPower.QualityAdjPower, - ), - ) + // Sector size + mi, err := fullapi.StateMinerInfo(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } - fmt.Printf("\tRaw: %s / %s (%0.4f%%)\n", - color.BlueString(types.SizeStr(pow.MinerPower.RawBytePower)), - types.SizeStr(pow.TotalPower.RawBytePower), - types.BigDivFloat( - types.BigMul(pow.MinerPower.RawBytePower, big.NewInt(100)), - pow.TotalPower.RawBytePower, - ), - ) - secCounts, err := fullapi.StateMinerSectorCount(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } + ssize := types.SizeStr(types.NewInt(uint64(mi.SectorSize))) + fmt.Printf("Miner: %s (%s sectors)\n", color.BlueString("%s", maddr), ssize) - proving := secCounts.Active + secCounts.Faulty - nfaults := secCounts.Faulty - fmt.Printf("\tCommitted: %s\n", types.SizeStr(types.BigMul(types.NewInt(secCounts.Live), types.NewInt(uint64(mi.SectorSize))))) - if nfaults == 0 { - fmt.Printf("\tProving: %s\n", types.SizeStr(types.BigMul(types.NewInt(proving), types.NewInt(uint64(mi.SectorSize))))) - } else { - var faultyPercentage float64 - if secCounts.Live != 0 { - faultyPercentage = float64(100*nfaults) / float64(secCounts.Live) - } - fmt.Printf("\tProving: %s (%s Faulty, %.2f%%)\n", - types.SizeStr(types.BigMul(types.NewInt(proving), types.NewInt(uint64(mi.SectorSize)))), - types.SizeStr(types.BigMul(types.NewInt(nfaults), types.NewInt(uint64(mi.SectorSize)))), - faultyPercentage) + pow, err := fullapi.StateMinerPower(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + fmt.Printf("Power: %s / %s (%0.4f%%)\n", + color.GreenString(types.DeciStr(pow.MinerPower.QualityAdjPower)), + types.DeciStr(pow.TotalPower.QualityAdjPower), + types.BigDivFloat( + types.BigMul(pow.MinerPower.QualityAdjPower, big.NewInt(100)), + pow.TotalPower.QualityAdjPower, + ), + ) + + fmt.Printf("\tRaw: %s / %s (%0.4f%%)\n", + color.BlueString(types.SizeStr(pow.MinerPower.RawBytePower)), + types.SizeStr(pow.TotalPower.RawBytePower), + types.BigDivFloat( + types.BigMul(pow.MinerPower.RawBytePower, big.NewInt(100)), + pow.TotalPower.RawBytePower, + ), + ) + secCounts, err := fullapi.StateMinerSectorCount(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + proving := secCounts.Active + secCounts.Faulty + nfaults := secCounts.Faulty + fmt.Printf("\tCommitted: %s\n", types.SizeStr(types.BigMul(types.NewInt(secCounts.Live), types.NewInt(uint64(mi.SectorSize))))) + if nfaults == 0 { + fmt.Printf("\tProving: %s\n", types.SizeStr(types.BigMul(types.NewInt(proving), types.NewInt(uint64(mi.SectorSize))))) + } else { + var faultyPercentage float64 + if secCounts.Live != 0 { + faultyPercentage = float64(100*nfaults) / float64(secCounts.Live) } + fmt.Printf("\tProving: %s (%s Faulty, %.2f%%)\n", + types.SizeStr(types.BigMul(types.NewInt(proving), types.NewInt(uint64(mi.SectorSize)))), + types.SizeStr(types.BigMul(types.NewInt(nfaults), types.NewInt(uint64(mi.SectorSize)))), + faultyPercentage) + } - if !pow.HasMinPower { - fmt.Print("Below minimum power threshold, no blocks will be won") - } else { + if !pow.HasMinPower { + fmt.Print("Below minimum power threshold, no blocks will be won") + } else { - winRatio := new(corebig.Rat).SetFrac( - types.BigMul(pow.MinerPower.QualityAdjPower, types.NewInt(build.BlocksPerEpoch)).Int, - pow.TotalPower.QualityAdjPower.Int, - ) + winRatio := new(corebig.Rat).SetFrac( + types.BigMul(pow.MinerPower.QualityAdjPower, types.NewInt(build.BlocksPerEpoch)).Int, + pow.TotalPower.QualityAdjPower.Int, + ) - if winRatioFloat, _ := winRatio.Float64(); winRatioFloat > 0 { - - // if the corresponding poisson distribution isn't infinitely small then - // throw it into the mix as well, accounting for multi-wins - winRationWithPoissonFloat := -math.Expm1(-winRatioFloat) - winRationWithPoisson := new(corebig.Rat).SetFloat64(winRationWithPoissonFloat) - if winRationWithPoisson != nil { - winRatio = winRationWithPoisson - winRatioFloat = winRationWithPoissonFloat - } - - weekly, _ := new(corebig.Rat).Mul( - winRatio, - new(corebig.Rat).SetInt64(7*builtin.EpochsInDay), - ).Float64() - - avgDuration, _ := new(corebig.Rat).Mul( - new(corebig.Rat).SetInt64(builtin.EpochDurationSeconds), - new(corebig.Rat).Inv(winRatio), - ).Float64() - - fmt.Print("Projected average block win rate: ") - color.Blue( - "%.02f/week (every %s)", - weekly, - (time.Second * time.Duration(avgDuration)).Truncate(time.Second).String(), - ) - - // Geometric distribution of P(Y < k) calculated as described in https://en.wikipedia.org/wiki/Geometric_distribution#Probability_Outcomes_Examples - // https://www.wolframalpha.com/input/?i=t+%3E+0%3B+p+%3E+0%3B+p+%3C+1%3B+c+%3E+0%3B+c+%3C1%3B+1-%281-p%29%5E%28t%29%3Dc%3B+solve+t - // t == how many dice-rolls (epochs) before win - // p == winRate == ( minerPower / netPower ) - // c == target probability of win ( 99.9% in this case ) - fmt.Print("Projected block win with ") - color.Green( - "99.9%% probability every %s", - (time.Second * time.Duration( - builtin.EpochDurationSeconds*math.Log(1-0.999)/ - math.Log(1-winRatioFloat), - )).Truncate(time.Second).String(), - ) - fmt.Println("(projections DO NOT account for future network and miner growth)") - } - } + if winRatioFloat, _ := winRatio.Float64(); winRatioFloat > 0 { - fmt.Println() + // if the corresponding poisson distribution isn't infinitely small then + // throw it into the mix as well, accounting for multi-wins + winRationWithPoissonFloat := -math.Expm1(-winRatioFloat) + winRationWithPoisson := new(corebig.Rat).SetFloat64(winRationWithPoissonFloat) + if winRationWithPoisson != nil { + winRatio = winRationWithPoisson + winRatioFloat = winRationWithPoissonFloat + } - spendable := big.Zero() + weekly, _ := new(corebig.Rat).Mul( + winRatio, + new(corebig.Rat).SetInt64(7*builtin.EpochsInDay), + ).Float64() + + avgDuration, _ := new(corebig.Rat).Mul( + new(corebig.Rat).SetInt64(builtin.EpochDurationSeconds), + new(corebig.Rat).Inv(winRatio), + ).Float64() + + fmt.Print("Projected average block win rate: ") + color.Blue( + "%.02f/week (every %s)", + weekly, + (time.Second * time.Duration(avgDuration)).Truncate(time.Second).String(), + ) - // NOTE: there's no need to unlock anything here. Funds only - // vest on deadline boundaries, and they're unlocked by cron. - lockedFunds, err := mas.LockedFunds() - if err != nil { - return xerrors.Errorf("getting locked funds: %w", err) - } - availBalance, err := mas.AvailableBalance(mact.Balance) - if err != nil { - return xerrors.Errorf("getting available balance: %w", err) + // Geometric distribution of P(Y < k) calculated as described in https://en.wikipedia.org/wiki/Geometric_distribution#Probability_Outcomes_Examples + // https://www.wolframalpha.com/input/?i=t+%3E+0%3B+p+%3E+0%3B+p+%3C+1%3B+c+%3E+0%3B+c+%3C1%3B+1-%281-p%29%5E%28t%29%3Dc%3B+solve+t + // t == how many dice-rolls (epochs) before win + // p == winRate == ( minerPower / netPower ) + // c == target probability of win ( 99.9% in this case ) + fmt.Print("Projected block win with ") + color.Green( + "99.9%% probability every %s", + (time.Second * time.Duration( + builtin.EpochDurationSeconds*math.Log(1-0.999)/ + math.Log(1-winRatioFloat), + )).Truncate(time.Second).String(), + ) + fmt.Println("(projections DO NOT account for future network and miner growth)") } - spendable = big.Add(spendable, availBalance) + } - fmt.Printf("Miner Balance: %s\n", color.YellowString("%s", types.FIL(mact.Balance).Short())) - fmt.Printf(" PreCommit: %s\n", types.FIL(lockedFunds.PreCommitDeposits).Short()) - fmt.Printf(" Pledge: %s\n", types.FIL(lockedFunds.InitialPledgeRequirement).Short()) - fmt.Printf(" Vesting: %s\n", types.FIL(lockedFunds.VestingFunds).Short()) - colorTokenAmount(" Available: %s\n", availBalance) + fmt.Println() - mb, err := fullapi.StateMarketBalance(ctx, maddr, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("getting market balance: %w", err) - } - spendable = big.Add(spendable, big.Sub(mb.Escrow, mb.Locked)) + spendable := big.Zero() - fmt.Printf("Market Balance: %s\n", types.FIL(mb.Escrow).Short()) - fmt.Printf(" Locked: %s\n", types.FIL(mb.Locked).Short()) - colorTokenAmount(" Available: %s\n", big.Sub(mb.Escrow, mb.Locked)) + // NOTE: there's no need to unlock anything here. Funds only + // vest on deadline boundaries, and they're unlocked by cron. + lockedFunds, err := mas.LockedFunds() + if err != nil { + return xerrors.Errorf("getting locked funds: %w", err) + } + availBalance, err := mas.AvailableBalance(mact.Balance) + if err != nil { + return xerrors.Errorf("getting available balance: %w", err) + } + spendable = big.Add(spendable, availBalance) - wb, err := fullapi.WalletBalance(ctx, mi.Worker) - if err != nil { - return xerrors.Errorf("getting worker balance: %w", err) - } - spendable = big.Add(spendable, wb) - color.Cyan("Worker Balance: %s", types.FIL(wb).Short()) - if len(mi.ControlAddresses) > 0 { - cbsum := big.Zero() - for _, ca := range mi.ControlAddresses { - b, err := fullapi.WalletBalance(ctx, ca) - if err != nil { - return xerrors.Errorf("getting control address balance: %w", err) - } - cbsum = big.Add(cbsum, b) - } - spendable = big.Add(spendable, cbsum) + fmt.Printf("Miner Balance: %s\n", color.YellowString("%s", types.FIL(mact.Balance).Short())) + fmt.Printf(" PreCommit: %s\n", types.FIL(lockedFunds.PreCommitDeposits).Short()) + fmt.Printf(" Pledge: %s\n", types.FIL(lockedFunds.InitialPledgeRequirement).Short()) + fmt.Printf(" Vesting: %s\n", types.FIL(lockedFunds.VestingFunds).Short()) + colorTokenAmount(" Available: %s\n", availBalance) - fmt.Printf(" Control: %s\n", types.FIL(cbsum).Short()) - } - colorTokenAmount("Total Spendable: %s\n", spendable) + mb, err := fullapi.StateMarketBalance(ctx, maddr, types.EmptyTSK) + if err != nil { + return xerrors.Errorf("getting market balance: %w", err) + } + spendable = big.Add(spendable, big.Sub(mb.Escrow, mb.Locked)) - fmt.Println() + fmt.Printf("Market Balance: %s\n", types.FIL(mb.Escrow).Short()) + fmt.Printf(" Locked: %s\n", types.FIL(mb.Locked).Short()) + colorTokenAmount(" Available: %s\n", big.Sub(mb.Escrow, mb.Locked)) - if !cctx.Bool("hide-sectors-info") { - fmt.Println("Sectors:") - err = sectorsInfo(ctx, nodeApi) + wb, err := fullapi.WalletBalance(ctx, mi.Worker) + if err != nil { + return xerrors.Errorf("getting worker balance: %w", err) + } + spendable = big.Add(spendable, wb) + color.Cyan("Worker Balance: %s", types.FIL(wb).Short()) + if len(mi.ControlAddresses) > 0 { + cbsum := big.Zero() + for _, ca := range mi.ControlAddresses { + b, err := fullapi.WalletBalance(ctx, ca) if err != nil { - return err + return xerrors.Errorf("getting control address balance: %w", err) } + cbsum = big.Add(cbsum, b) } + spendable = big.Add(spendable, cbsum) - // TODO: grab actr state / info - // * Sealed sectors (count / bytes) - // * Power + fmt.Printf(" Control: %s\n", types.FIL(cbsum).Short()) } + colorTokenAmount("Total Spendable: %s\n", spendable) - if subsystems.Has(api.MarketsSubsystem) { - deals, err := nodeApi.MarketListIncompleteDeals(ctx) + fmt.Println() + + if !cctx.Bool("hide-sectors-info") { + fmt.Println("Sectors:") + err = sectorsInfo(ctx, nodeApi) if err != nil { return err } + } - type dealStat struct { - count, verifCount int - bytes, verifBytes uint64 - } - dsAdd := func(ds *dealStat, deal storagemarket.MinerDeal) { - ds.count++ - ds.bytes += uint64(deal.Proposal.PieceSize) - if deal.Proposal.VerifiedDeal { - ds.verifCount++ - ds.verifBytes += uint64(deal.Proposal.PieceSize) - } - } + // TODO: grab actr state / info + // * Sealed sectors (count / bytes) + // * Power - showDealStates := map[storagemarket.StorageDealStatus]struct{}{ - storagemarket.StorageDealActive: {}, - storagemarket.StorageDealTransferring: {}, - storagemarket.StorageDealStaged: {}, - storagemarket.StorageDealAwaitingPreCommit: {}, - storagemarket.StorageDealSealing: {}, - storagemarket.StorageDealPublish: {}, - storagemarket.StorageDealCheckForAcceptance: {}, - storagemarket.StorageDealPublishing: {}, - } + return nil +} - var total dealStat - perState := map[storagemarket.StorageDealStatus]*dealStat{} - for _, deal := range deals { - if _, ok := showDealStates[deal.State]; !ok { - continue - } - if perState[deal.State] == nil { - perState[deal.State] = new(dealStat) - } +func handleMarketsInfo(ctx context.Context, nodeApi api.StorageMiner) error { + deals, err := nodeApi.MarketListIncompleteDeals(ctx) + if err != nil { + return err + } - dsAdd(&total, deal) - dsAdd(perState[deal.State], deal) + type dealStat struct { + count, verifCount int + bytes, verifBytes uint64 + } + dsAdd := func(ds *dealStat, deal storagemarket.MinerDeal) { + ds.count++ + ds.bytes += uint64(deal.Proposal.PieceSize) + if deal.Proposal.VerifiedDeal { + ds.verifCount++ + ds.verifBytes += uint64(deal.Proposal.PieceSize) } + } - type wstr struct { - str string - status storagemarket.StorageDealStatus + showDealStates := map[storagemarket.StorageDealStatus]struct{}{ + storagemarket.StorageDealActive: {}, + storagemarket.StorageDealTransferring: {}, + storagemarket.StorageDealStaged: {}, + storagemarket.StorageDealAwaitingPreCommit: {}, + storagemarket.StorageDealSealing: {}, + storagemarket.StorageDealPublish: {}, + storagemarket.StorageDealCheckForAcceptance: {}, + storagemarket.StorageDealPublishing: {}, + } + + var total dealStat + perState := map[storagemarket.StorageDealStatus]*dealStat{} + for _, deal := range deals { + if _, ok := showDealStates[deal.State]; !ok { + continue } - sorted := make([]wstr, 0, len(perState)) - for status, stat := range perState { - st := strings.TrimPrefix(storagemarket.DealStates[status], "StorageDeal") - sorted = append(sorted, wstr{ - str: fmt.Sprintf(" %s:\t%d\t\t%s\t(Verified: %d\t%s)\n", st, stat.count, types.SizeStr(types.NewInt(stat.bytes)), stat.verifCount, types.SizeStr(types.NewInt(stat.verifBytes))), - status: status, - }, - ) + if perState[deal.State] == nil { + perState[deal.State] = new(dealStat) } - sort.Slice(sorted, func(i, j int) bool { - if sorted[i].status == storagemarket.StorageDealActive || sorted[j].status == storagemarket.StorageDealActive { - return sorted[i].status == storagemarket.StorageDealActive - } - return sorted[i].status > sorted[j].status - }) - fmt.Printf("Storage Deals: %d, %s\n", total.count, types.SizeStr(types.NewInt(total.bytes))) + dsAdd(&total, deal) + dsAdd(perState[deal.State], deal) + } - tw := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0) - for _, e := range sorted { - _, _ = tw.Write([]byte(e.str)) + type wstr struct { + str string + status storagemarket.StorageDealStatus + } + sorted := make([]wstr, 0, len(perState)) + for status, stat := range perState { + st := strings.TrimPrefix(storagemarket.DealStates[status], "StorageDeal") + sorted = append(sorted, wstr{ + str: fmt.Sprintf(" %s:\t%d\t\t%s\t(Verified: %d\t%s)\n", st, stat.count, types.SizeStr(types.NewInt(stat.bytes)), stat.verifCount, types.SizeStr(types.NewInt(stat.verifBytes))), + status: status, + }, + ) + } + sort.Slice(sorted, func(i, j int) bool { + if sorted[i].status == storagemarket.StorageDealActive || sorted[j].status == storagemarket.StorageDealActive { + return sorted[i].status == storagemarket.StorageDealActive } + return sorted[i].status > sorted[j].status + }) - _ = tw.Flush() - fmt.Println() + fmt.Printf("Storage Deals: %d, %s\n", total.count, types.SizeStr(types.NewInt(total.bytes))) - retrievals, err := nodeApi.MarketListRetrievalDeals(ctx) - if err != nil { - return xerrors.Errorf("getting retrieval deal list: %w", err) - } + tw := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0) + for _, e := range sorted { + _, _ = tw.Write([]byte(e.str)) + } - var retrComplete dealStat - for _, retrieval := range retrievals { - if retrieval.Status == retrievalmarket.DealStatusCompleted { - retrComplete.count++ - retrComplete.bytes += retrieval.TotalSent - } - } + _ = tw.Flush() + fmt.Println() - fmt.Printf("Retrieval Deals (complete): %d, %s\n", retrComplete.count, types.SizeStr(types.NewInt(retrComplete.bytes))) + retrievals, err := nodeApi.MarketListRetrievalDeals(ctx) + if err != nil { + return xerrors.Errorf("getting retrieval deal list: %w", err) + } - fmt.Println() + var retrComplete dealStat + for _, retrieval := range retrievals { + if retrieval.Status == retrievalmarket.DealStatusCompleted { + retrComplete.count++ + retrComplete.bytes += retrieval.TotalSent + } } + fmt.Printf("Retrieval Deals (complete): %d, %s\n", retrComplete.count, types.SizeStr(types.NewInt(retrComplete.bytes))) + + fmt.Println() + return nil } From 0c036b1157cf8142d0fc440a38935bddafc2d585 Mon Sep 17 00:00:00 2001 From: Anton Evangelatov Date: Wed, 28 Jul 2021 17:47:01 +0300 Subject: [PATCH 05/27] make linter happy --- cmd/lotus-miner/info.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-miner/info.go b/cmd/lotus-miner/info.go index 2a36279595b..92f25667cc1 100644 --- a/cmd/lotus-miner/info.go +++ b/cmd/lotus-miner/info.go @@ -104,7 +104,7 @@ func infoCmdAct(cctx *cli.Context) error { fmt.Println() if subsystems.Has(api.SectorStorageSubsystem) { - err := handleMiningInfo(cctx, ctx, fullapi, nodeApi) + err := handleMiningInfo(ctx, cctx, fullapi, nodeApi) if err != nil { return err } @@ -120,7 +120,7 @@ func infoCmdAct(cctx *cli.Context) error { return nil } -func handleMiningInfo(cctx *cli.Context, ctx context.Context, fullapi v0api.FullNode, nodeApi api.StorageMiner) error { +func handleMiningInfo(ctx context.Context, cctx *cli.Context, fullapi v0api.FullNode, nodeApi api.StorageMiner) error { maddr, err := getActorAddress(ctx, cctx) if err != nil { return err From c119ab6ed970483521ed8793451ed2933cfba6b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Wed, 28 Jul 2021 19:51:45 +0100 Subject: [PATCH 06/27] fix docs and nits. --- api/api_storage.go | 2 + api/api_subsystems.go | 63 ------------------- api/docgen/docgen.go | 10 ++- api/miner_subsystems.go | 79 ++++++++++++++++++++++++ cmd/lotus-miner/info.go | 4 +- documentation/en/api-v0-methods-miner.md | 12 +++- node/builder_miner.go | 2 +- node/impl/storminer.go | 10 +-- node/modules/storageminer.go | 11 ++-- 9 files changed, 113 insertions(+), 80 deletions(-) delete mode 100644 api/api_subsystems.go create mode 100644 api/miner_subsystems.go diff --git a/api/api_storage.go b/api/api_storage.go index d5203265088..c391149290b 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -166,6 +166,8 @@ type StorageMiner interface { MarketPendingDeals(ctx context.Context) (PendingDealInfo, error) //perm:write MarketPublishPendingDeals(ctx context.Context) error //perm:admin + // RuntimeSubsystems returns the subsystems that are enabled + // in this instance. RuntimeSubsystems(ctx context.Context) (MinerSubsystems, error) //perm:read DealsImportData(ctx context.Context, dealPropCid cid.Cid, file string) error //perm:admin diff --git a/api/api_subsystems.go b/api/api_subsystems.go deleted file mode 100644 index 1894bbdd8a6..00000000000 --- a/api/api_subsystems.go +++ /dev/null @@ -1,63 +0,0 @@ -package api - -import ( - "bytes" - "encoding/json" -) - -type MinerSubsystems []MinerSubsystem - -func (ms MinerSubsystems) Has(entry MinerSubsystem) bool { - for _, v := range ms { - if v == entry { - return true - } - - } - return false -} - -type MinerSubsystem int - -const ( - MarketsSubsystem MinerSubsystem = iota - MiningSubsystem - SealingSubsystem - SectorStorageSubsystem -) - -func (ms MinerSubsystem) String() string { - return MinerSubsystemToString[ms] -} - -var MinerSubsystemToString = map[MinerSubsystem]string{ - MarketsSubsystem: "Markets", - MiningSubsystem: "Mining", - SealingSubsystem: "Sealing", - SectorStorageSubsystem: "SectorStorage", -} - -var MinerSubsystemToID = map[string]MinerSubsystem{ - "Markets": MarketsSubsystem, - "Mining": MiningSubsystem, - "Sealing": SealingSubsystem, - "SectorStorage": SectorStorageSubsystem, -} - -func (ms MinerSubsystem) MarshalJSON() ([]byte, error) { - buffer := bytes.NewBufferString(`"`) - buffer.WriteString(MinerSubsystemToString[ms]) - buffer.WriteString(`"`) - return buffer.Bytes(), nil -} - -func (ms *MinerSubsystem) UnmarshalJSON(b []byte) error { - var j string - err := json.Unmarshal(b, &j) - if err != nil { - return err - } - // TODO: handle zero value - *ms = MinerSubsystemToID[j] - return nil -} diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index 1e712a0aeab..f9addc940dd 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -16,10 +16,10 @@ import ( "github.com/google/uuid" "github.com/ipfs/go-cid" "github.com/ipfs/go-filestore" - metrics "github.com/libp2p/go-libp2p-core/metrics" + "github.com/libp2p/go-libp2p-core/metrics" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" - protocol "github.com/libp2p/go-libp2p-core/protocol" + "github.com/libp2p/go-libp2p-core/protocol" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/multiformats/go-multiaddr" @@ -265,6 +265,12 @@ func init() { addExample(api.CheckStatusCode(0)) addExample(map[string]interface{}{"abc": 123}) + addExample(api.MinerSubsystems{ + api.SubsystemMining, + api.SubsystemSealing, + api.SubsystemSectorStorage, + api.SubsystemMarkets, + }) } func GetAPIType(name, pkg string) (i interface{}, t reflect.Type, permStruct []reflect.Type) { diff --git a/api/miner_subsystems.go b/api/miner_subsystems.go new file mode 100644 index 00000000000..a77de7e3c95 --- /dev/null +++ b/api/miner_subsystems.go @@ -0,0 +1,79 @@ +package api + +import ( + "encoding/json" +) + +// MinerSubsystem represents a miner subsystem. Int and string values are not +// guaranteed to be stable over time is not +// guaranteed to be stable over time. +type MinerSubsystem int + +const ( + // SubsystemUnknown is a placeholder for the zero value. It should never + // be used. + SubsystemUnknown MinerSubsystem = iota + // SubsystemMarkets signifies the storage and retrieval + // deal-making subsystem. + SubsystemMarkets + // SubsystemMining signifies the mining subsystem. + SubsystemMining + // SubsystemSealing signifies the sealing subsystem. + SubsystemSealing + // SubsystemSectorStorage signifies the sector storage subsystem. + SubsystemSectorStorage +) + +var MinerSubsystemToString = map[MinerSubsystem]string{ + SubsystemUnknown: "Unknown", + SubsystemMarkets: "Markets", + SubsystemMining: "Mining", + SubsystemSealing: "Sealing", + SubsystemSectorStorage: "SectorStorage", +} + +var MinerSubsystemToID = map[string]MinerSubsystem{ + "Unknown": SubsystemUnknown, + "Markets": SubsystemMarkets, + "Mining": SubsystemMining, + "Sealing": SubsystemSealing, + "SectorStorage": SubsystemSectorStorage, +} + +func (ms MinerSubsystem) MarshalJSON() ([]byte, error) { + return json.Marshal(MinerSubsystemToString[ms]) +} + +func (ms *MinerSubsystem) UnmarshalJSON(b []byte) error { + var j string + err := json.Unmarshal(b, &j) + if err != nil { + return err + } + s, ok := MinerSubsystemToID[j] + if !ok { + *ms = SubsystemUnknown + } else { + *ms = s + } + return nil +} + +type MinerSubsystems []MinerSubsystem + +func (ms MinerSubsystems) Has(entry MinerSubsystem) bool { + for _, v := range ms { + if v == entry { + return true + } + } + return false +} + +func (ms MinerSubsystem) String() string { + s, ok := MinerSubsystemToString[ms] + if !ok { + return MinerSubsystemToString[SubsystemUnknown] + } + return s +} diff --git a/cmd/lotus-miner/info.go b/cmd/lotus-miner/info.go index 92f25667cc1..878361dacb6 100644 --- a/cmd/lotus-miner/info.go +++ b/cmd/lotus-miner/info.go @@ -103,14 +103,14 @@ func infoCmdAct(cctx *cli.Context) error { fmt.Println() - if subsystems.Has(api.SectorStorageSubsystem) { + if subsystems.Has(api.SubsystemSectorStorage) { err := handleMiningInfo(ctx, cctx, fullapi, nodeApi) if err != nil { return err } } - if subsystems.Has(api.MarketsSubsystem) { + if subsystems.Has(api.SubsystemMarkets) { err := handleMarketsInfo(ctx, nodeApi) if err != nil { return err diff --git a/documentation/en/api-v0-methods-miner.md b/documentation/en/api-v0-methods-miner.md index dcd3abd4df6..3b6d5ac5181 100644 --- a/documentation/en/api-v0-methods-miner.md +++ b/documentation/en/api-v0-methods-miner.md @@ -1528,13 +1528,23 @@ Response: `{}` ### RuntimeSubsystems +RuntimeSubsystems returns the subsystems that are enabled +in this instance. Perms: read Inputs: `null` -Response: `null` +Response: +```json +[ + "Mining", + "Sealing", + "SectorStorage", + "Markets" +] +``` ## Sealing diff --git a/node/builder_miner.go b/node/builder_miner.go index 830e6d075b2..acb9d3d43cd 100644 --- a/node/builder_miner.go +++ b/node/builder_miner.go @@ -72,7 +72,7 @@ func ConfigStorageMiner(c interface{}) Option { return Options( ConfigCommon(&cfg.Common, enableLibp2pNode), - Override(new(api.MinerSubsystems), modules.AddMinerSubsystems(cfg.Subsystems)), + Override(new(api.MinerSubsystems), modules.PopulateEnabledMinerSubsystems(cfg.Subsystems)), Override(new(stores.LocalStorage), From(new(repo.LockedRepo))), Override(new(*stores.Local), modules.LocalStorage), Override(new(*stores.Remote), modules.RemoteStorage), diff --git a/node/impl/storminer.go b/node/impl/storminer.go index 86c84fee7ea..0fbd1211143 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -23,8 +23,8 @@ import ( "github.com/filecoin-project/go-address" datatransfer "github.com/filecoin-project/go-data-transfer" "github.com/filecoin-project/go-fil-markets/piecestore" - retrievalmarket "github.com/filecoin-project/go-fil-markets/retrievalmarket" - storagemarket "github.com/filecoin-project/go-fil-markets/storagemarket" + "github.com/filecoin-project/go-fil-markets/retrievalmarket" + "github.com/filecoin-project/go-fil-markets/storagemarket" "github.com/filecoin-project/go-state-types/abi" sectorstorage "github.com/filecoin-project/lotus/extern/sector-storage" @@ -48,11 +48,11 @@ import ( type StorageMinerAPI struct { fx.In - Subsystems api.MinerSubsystems - api.Common api.Net + EnabledSubsystems api.MinerSubsystems + Full api.FullNode LocalStore *stores.Local RemoteStore *stores.Remote @@ -706,7 +706,7 @@ func (sm *StorageMinerAPI) ComputeProof(ctx context.Context, ssi []builtin.Secto } func (sm *StorageMinerAPI) RuntimeSubsystems(context.Context) (res api.MinerSubsystems, err error) { - return sm.Subsystems, nil + return sm.EnabledSubsystems, nil } var _ api.StorageMiner = &StorageMinerAPI{} diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 06ef78ca0af..26792ca197b 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -1008,19 +1008,18 @@ func mutateCfg(r repo.LockedRepo, mutator func(*config.StorageMiner)) error { return multierr.Combine(typeErr, setConfigErr) } -func AddMinerSubsystems(cfg config.MinerSubsystemConfig) (res api.MinerSubsystems) { +func PopulateEnabledMinerSubsystems(cfg config.MinerSubsystemConfig) (res api.MinerSubsystems) { if cfg.EnableMining { - res = append(res, api.MiningSubsystem) + res = append(res, api.SubsystemMining) } if cfg.EnableSealing { - res = append(res, api.SealingSubsystem) + res = append(res, api.SubsystemSealing) } if cfg.EnableSectorStorage { - res = append(res, api.SectorStorageSubsystem) + res = append(res, api.SubsystemSectorStorage) } if cfg.EnableMarkets { - res = append(res, api.MarketsSubsystem) + res = append(res, api.SubsystemMarkets) } - return } From d8c90b91be22983e23fced4a8facac74c0872c21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Wed, 28 Jul 2021 23:46:21 +0100 Subject: [PATCH 07/27] address nits. --- node/builder_miner.go | 2 +- node/modules/storageminer.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/node/builder_miner.go b/node/builder_miner.go index acb9d3d43cd..3be055de79b 100644 --- a/node/builder_miner.go +++ b/node/builder_miner.go @@ -72,7 +72,7 @@ func ConfigStorageMiner(c interface{}) Option { return Options( ConfigCommon(&cfg.Common, enableLibp2pNode), - Override(new(api.MinerSubsystems), modules.PopulateEnabledMinerSubsystems(cfg.Subsystems)), + Override(new(api.MinerSubsystems), modules.ExtractEnabledMinerSubsystems(cfg.Subsystems)), Override(new(stores.LocalStorage), From(new(repo.LockedRepo))), Override(new(*stores.Local), modules.LocalStorage), Override(new(*stores.Remote), modules.RemoteStorage), diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 26792ca197b..5497eab5813 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -1008,7 +1008,7 @@ func mutateCfg(r repo.LockedRepo, mutator func(*config.StorageMiner)) error { return multierr.Combine(typeErr, setConfigErr) } -func PopulateEnabledMinerSubsystems(cfg config.MinerSubsystemConfig) (res api.MinerSubsystems) { +func ExtractEnabledMinerSubsystems(cfg config.MinerSubsystemConfig) (res api.MinerSubsystems) { if cfg.EnableMining { res = append(res, api.SubsystemMining) } @@ -1021,5 +1021,5 @@ func PopulateEnabledMinerSubsystems(cfg config.MinerSubsystemConfig) (res api.Mi if cfg.EnableMarkets { res = append(res, api.SubsystemMarkets) } - return + return res } From d039764a343102dcd856eb26c2842eb212e0120f Mon Sep 17 00:00:00 2001 From: vyzo Date: Wed, 28 Jul 2021 11:49:42 +0300 Subject: [PATCH 08/27] code cosmetics: rename variables for better readability and some comments --- blockstore/badger/blockstore.go | 69 +++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index 8e1a3a1ff8b..49951db6ed1 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -258,16 +258,16 @@ func (b *Blockstore) movingGC() error { b.moveCond.Broadcast() b.moveMx.Unlock() - var path string + var newPath string defer func() { b.lockMove() - db2 := b.dbNext + dbNext := b.dbNext b.dbNext = nil var state bsMoveState - if db2 != nil { + if dbNext != nil { state = moveStateCleanup } else { state = moveStateNone @@ -275,12 +275,13 @@ func (b *Blockstore) movingGC() error { b.unlockMove(state) - if db2 != nil { - err := db2.Close() + if dbNext != nil { + // the move failed and we have a left-over db; delete it. + err := dbNext.Close() if err != nil { log.Warnf("error closing badger db: %s", err) } - b.deleteDB(path) + b.deleteDB(newPath) b.lockMove() b.unlockMove(moveStateNone) @@ -296,63 +297,71 @@ func (b *Blockstore) movingGC() error { } if basePath == linkPath { - path = basePath + newPath = basePath } else { + // we do this dance to create a name adjacent to the current one, while avoiding clown + // shoes with multiple moves (i.e. we can't just take the basename of the linkPath, as it + // could have been created in a previous move and have the timestamp suffix, which would then + // perpetuate itself. name := filepath.Base(basePath) dir := filepath.Dir(linkPath) - path = filepath.Join(dir, name) + newPath = filepath.Join(dir, name) } - path = fmt.Sprintf("%s.%d", path, time.Now().UnixNano()) + newPath = fmt.Sprintf("%s.%d", newPath, time.Now().UnixNano()) - log.Infof("moving blockstore from %s to %s", b.opts.Dir, path) + log.Infof("moving blockstore from %s to %s", b.opts.Dir, newPath) opts := b.opts - opts.Dir = path - opts.ValueDir = path + opts.Dir = newPath + opts.ValueDir = newPath - db2, err := badger.Open(opts.Options) + dbNew, err := badger.Open(opts.Options) if err != nil { - return fmt.Errorf("failed to open badger blockstore in %s: %w", path, err) + return fmt.Errorf("failed to open badger blockstore in %s: %w", newPath, err) } b.lockMove() - b.dbNext = db2 + b.dbNext = dbNew b.unlockMove(moveStateMoving) log.Info("copying blockstore") err = b.doCopy(b.db, b.dbNext) if err != nil { - return fmt.Errorf("error moving badger blockstore to %s: %w", path, err) + return fmt.Errorf("error moving badger blockstore to %s: %w", newPath, err) } b.lockMove() - db1 := b.db + dbOld := b.db b.db = b.dbNext b.dbNext = nil b.unlockMove(moveStateCleanup) - err = db1.Close() + err = dbOld.Close() if err != nil { log.Warnf("error closing old badger db: %s", err) } - dbpath := b.opts.Dir - oldpath := fmt.Sprintf("%s.old.%d", dbpath, time.Now().Unix()) + // this is the canonical db path; this is where our db lives. + dbPath := b.opts.Dir - if err = os.Rename(dbpath, oldpath); err != nil { + // we first move the existing db out of the way, and only delete it after we have symlinked the + // new db to the canonical path + backupPath := fmt.Sprintf("%s.old.%d", dbPath, time.Now().Unix()) + if err = os.Rename(dbPath, backupPath); err != nil { // this is not catastrophic in the sense that we have not lost any data. // but it is pretty bad, as the db path points to the old db, while we are now using to the new // db; we can't continue and leave a ticking bomb for the next restart. // so a panic is appropriate and user can fix. - panic(fmt.Errorf("error renaming old badger db dir from %s to %s: %w; USER ACTION REQUIRED", dbpath, oldpath, err)) //nolint + panic(fmt.Errorf("error renaming old badger db dir from %s to %s: %w; USER ACTION REQUIRED", dbPath, backupPath, err)) //nolint } - if err = os.Symlink(path, dbpath); err != nil { + if err = os.Symlink(newPath, dbPath); err != nil { // same here; the db path is pointing to the void. panic and let the user fix. - panic(fmt.Errorf("error symlinking new badger db dir from %s to %s: %w; USER ACTION REQUIRED", path, dbpath, err)) //nolint + panic(fmt.Errorf("error symlinking new badger db dir from %s to %s: %w; USER ACTION REQUIRED", newPath, dbPath, err)) //nolint } - b.deleteDB(oldpath) + // the delete follows symlinks + b.deleteDB(backupPath) log.Info("moving blockstore done") return nil @@ -390,19 +399,19 @@ func (b *Blockstore) doCopy(from, to *badger.DB) error { func (b *Blockstore) deleteDB(path string) { // follow symbolic links, otherwise the data wil be left behind - lpath, err := filepath.EvalSymlinks(path) + linkPath, err := filepath.EvalSymlinks(path) if err != nil { log.Warnf("error resolving symlinks in %s", path) return } - log.Infof("removing data directory %s", lpath) - if err := os.RemoveAll(lpath); err != nil { - log.Warnf("error deleting db at %s: %s", lpath, err) + log.Infof("removing data directory %s", linkPath) + if err := os.RemoveAll(linkPath); err != nil { + log.Warnf("error deleting db at %s: %s", linkPath, err) return } - if path != lpath { + if path != linkPath { log.Infof("removing link %s", path) if err := os.Remove(path); err != nil { log.Warnf("error removing symbolic link %s", err) From 7a3193a75bf132818e2b617cd8740d6f1b352438 Mon Sep 17 00:00:00 2001 From: vyzo Date: Wed, 28 Jul 2021 11:56:23 +0300 Subject: [PATCH 09/27] make relative links when the canonical and new db paths are in the same directory --- blockstore/badger/blockstore.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index 49951db6ed1..3d186228327 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -355,7 +355,7 @@ func (b *Blockstore) movingGC() error { panic(fmt.Errorf("error renaming old badger db dir from %s to %s: %w; USER ACTION REQUIRED", dbPath, backupPath, err)) //nolint } - if err = os.Symlink(newPath, dbPath); err != nil { + if err = b.symlink(newPath, dbPath); err != nil { // same here; the db path is pointing to the void. panic and let the user fix. panic(fmt.Errorf("error symlinking new badger db dir from %s to %s: %w; USER ACTION REQUIRED", newPath, dbPath, err)) //nolint } @@ -367,6 +367,18 @@ func (b *Blockstore) movingGC() error { return nil } +// symlink creates a symlink from path to linkPath; the link is relative if the two are +// in the same directory +func (b *Blockstore) symlink(path, linkTo string) error { + pathDir := filepath.Dir(path) + linkDir := filepath.Dir(linkTo) + if pathDir == linkDir { + path = filepath.Base(path) + } + + return os.Symlink(path, linkTo) +} + // doCopy copies a badger blockstore to another, with an optional filter; if the filter // is not nil, then only cids that satisfy the filter will be copied. func (b *Blockstore) doCopy(from, to *badger.DB) error { From a24b2436b0d9ded5d3ca91929d3944fe98073fcc Mon Sep 17 00:00:00 2001 From: vyzo Date: Wed, 28 Jul 2021 11:56:35 +0300 Subject: [PATCH 10/27] extend test to check the validity of relative links --- blockstore/badger/blockstore_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/blockstore/badger/blockstore_test.go b/blockstore/badger/blockstore_test.go index ddfa6f28d35..d8ef5241b49 100644 --- a/blockstore/badger/blockstore_test.go +++ b/blockstore/badger/blockstore_test.go @@ -245,6 +245,21 @@ func testMove(t *testing.T, optsF func(string) Options) { checkBlocks() checkPath() + + // reopen the db to make sure our relative link works: + err = db.Close() + if err != nil { + t.Fatal(err) + } + + db, err = Open(optsF(dbPath)) + if err != nil { + t.Fatal(err) + } + + // db.Close() is already deferred + + checkBlocks() } func TestMoveNoPrefix(t *testing.T) { From 0caabf1328d3db16aeda222dc080b701d17c92f1 Mon Sep 17 00:00:00 2001 From: vyzo Date: Wed, 28 Jul 2021 16:15:39 +0300 Subject: [PATCH 11/27] improve detection of relative links --- blockstore/badger/blockstore.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index 3d186228327..094b8eb6c5c 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -370,9 +370,17 @@ func (b *Blockstore) movingGC() error { // symlink creates a symlink from path to linkPath; the link is relative if the two are // in the same directory func (b *Blockstore) symlink(path, linkTo string) error { - pathDir := filepath.Dir(path) - linkDir := filepath.Dir(linkTo) - if pathDir == linkDir { + resolvedPathDir, err := filepath.EvalSymlinks(filepath.Dir(path)) + if err != nil { + return fmt.Errorf("error resolving links in %s: %w", path, err) + } + + resolvedLinkDir, err := filepath.EvalSymlinks(filepath.Dir(linkTo)) + if err != nil { + return fmt.Errorf("error resolving links in %s: %W", linkTo, err) + } + + if resolvedPathDir == resolvedLinkDir { path = filepath.Base(path) } From ec78d3d7b1b7c00e381e74ac41d20fab1bbc3a6f Mon Sep 17 00:00:00 2001 From: vyzo Date: Wed, 28 Jul 2021 16:20:25 +0300 Subject: [PATCH 12/27] fix format specifier --- blockstore/badger/blockstore.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index 094b8eb6c5c..dad37a6b1ff 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -377,7 +377,7 @@ func (b *Blockstore) symlink(path, linkTo string) error { resolvedLinkDir, err := filepath.EvalSymlinks(filepath.Dir(linkTo)) if err != nil { - return fmt.Errorf("error resolving links in %s: %W", linkTo, err) + return fmt.Errorf("error resolving links in %s: %w", linkTo, err) } if resolvedPathDir == resolvedLinkDir { From 4417be81ad647a85cafcf592390e1c59635196d0 Mon Sep 17 00:00:00 2001 From: vyzo Date: Wed, 28 Jul 2021 17:11:04 +0300 Subject: [PATCH 13/27] fix typo Co-authored-by: Jakub Sztandera --- blockstore/badger/blockstore.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index dad37a6b1ff..05e9048c90f 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -367,7 +367,7 @@ func (b *Blockstore) movingGC() error { return nil } -// symlink creates a symlink from path to linkPath; the link is relative if the two are +// symlink creates a symlink from path to linkTo; the link is relative if the two are // in the same directory func (b *Blockstore) symlink(path, linkTo string) error { resolvedPathDir, err := filepath.EvalSymlinks(filepath.Dir(path)) From fd33b96e08d241140e402945bafa0405ae74a772 Mon Sep 17 00:00:00 2001 From: vyzo Date: Thu, 29 Jul 2021 08:35:53 +0300 Subject: [PATCH 14/27] make symlink helper freestanding --- blockstore/badger/blockstore.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go index 05e9048c90f..a0b51d8df61 100644 --- a/blockstore/badger/blockstore.go +++ b/blockstore/badger/blockstore.go @@ -355,7 +355,7 @@ func (b *Blockstore) movingGC() error { panic(fmt.Errorf("error renaming old badger db dir from %s to %s: %w; USER ACTION REQUIRED", dbPath, backupPath, err)) //nolint } - if err = b.symlink(newPath, dbPath); err != nil { + if err = symlink(newPath, dbPath); err != nil { // same here; the db path is pointing to the void. panic and let the user fix. panic(fmt.Errorf("error symlinking new badger db dir from %s to %s: %w; USER ACTION REQUIRED", newPath, dbPath, err)) //nolint } @@ -369,7 +369,7 @@ func (b *Blockstore) movingGC() error { // symlink creates a symlink from path to linkTo; the link is relative if the two are // in the same directory -func (b *Blockstore) symlink(path, linkTo string) error { +func symlink(path, linkTo string) error { resolvedPathDir, err := filepath.EvalSymlinks(filepath.Dir(path)) if err != nil { return fmt.Errorf("error resolving links in %s: %w", path, err) From 786d3e79eb9264b528e2f2588c6787aa30b834f4 Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Thu, 29 Jul 2021 04:51:28 -0400 Subject: [PATCH 15/27] make gen --- build/openrpc/full.json.gz | Bin 25240 -> 25241 bytes build/openrpc/miner.json.gz | Bin 9479 -> 9596 bytes build/openrpc/worker.json.gz | Bin 2710 -> 2710 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index ddcf0611767ea9b47c66180b6063a2252ebf40ab..6100677034b49cd1397ebfb057a848753b073d4e 100644 GIT binary patch delta 20851 zcmYg%Q*b6+6J|WIZQHhO+qN~)8#|fUwr$(CZQGgbeE-%~?bYczmt9@e{U~i1IDHtn z?imDlHSq-`8sMwa*dJRm)-JCu-HDfCQ{);oo~_y*?@X6;TNLVDEU7kaK8~_iD0l@2 zK?pocsDo&Mttc$=f-8&k^oGh=0Ffse;XXf78!eKvEnr-R)NR-lXC`t`(5GX_46I3e zh()Y>(_fwZx_LVHC>`A5lZO0I@A}g9tGmggrFD0u3UF<5L=@W-{nxs+;_yQ4ZMFYq z1<5_N`noM!UfnID!rC?SaxEDE<#<7in8yT-=#+p~UUxk|?quPqqA=~8M|GO$=y*Up zaF3blKSOdAC|JcG3W+h6NaXOPoQxHrcXY;{cL@>BbOeIi+5#@$)>moFQVA?_7p7eH z0Tf7b0U-Xxo2gLG0-n~US>-rCO8dm=KD3$2?^+X&^GfVK>jD%3L-Ltn@(=NDrMNJW+Np|FqBB zE`uq)4f)Z_qBfStI|)L_@A<&5P$#Eq=^96d%nU@0iuyTuxyfFM!AhQ z8LMy}YX|%1!~Lenc~D1^OunEs2kTk@+htkg7{aV`$jK%J*vXYUdfNo)i4gP*txAVztg#LYhf9JL8HYq~4#l54^bj-O330-UOQ z_nQ|p3^`N}HT(}Hr9|r()Nn%*%iHn8Hh%zs|Kf8F9=JX0P^gDX`@w-@&t-|8$jr@ur;<3^=Hvu9+KwRTV zZImC*&Yml#_mXnx&s{Bd8-O8Ir?YynE^3!PaObbfuR)Tli{4D@jC1%6sP|XkY&MB7 z0z&UUb4X7*YJ9^iwvc}X1l`f|u;6tN-=#jd(w`r->ZC=o3ST{R*y7m<#tG(NbS#Mh zqVR^>l*`uX{7$}vtpS^M+lF)M1#@E}jGlaaa<|KM`f`h({*cCP-La*Y zsXXQ9jVUr$29Pmk?ZTxxm#WD!lvNsg1wD%r5h&lXRAQB#ou=eP7nan1HXzT)kfkxR z_qAh7?cJ!Gd==mfp5ohy!;p;t4RPj->^K>yE_JjOClNJe9=I+)k*L$X_14>IaE~py z>F&t3?Gp{EiBS{U6_g9-Fq8{h;f$Yb#<&u{5Dy-do%VCK1PHu{F24YWPZ9QG;B`Fx z-W*sFWWv|n)kB>Ip7+aT$20izo46l2h`&Iet*wiDBk4PNJF~Yo{c~mjXEuGp5G~nW zEq;J7)Z5OVD$!kRt5Xn5HV-dLGac>vN^U%XXC~!7iSu`aOhC_i zYOWw92Sv}RU;LuY0HIiBb_H9;nDxmj(&>WDjOpNc!=Cs+fPB6?^SW48_HPSIrn4EB zhsiUBc9_im4fRZYw`W&C?eMj2Z*`}qHm9lmG*H7dEr%;>p!E94Dnt^ri6(rabJDV> z;~OJ*0uUa}ro?>dUG_~AE6jY#-9`3w2hS8!YecuJ#^JIO3wal}P1+*cG?&Mc z8r>=VwFx+X4t6dT!JqT!H%~-9%tb<@3$iWUVTo}U7e_8JDVz;J%#gQ?MS58h*Vh)iygBb4kUP7uigw&eZTI zBRkyBOkBS2tFC>tT6Zf!E#rDQ3^t+GuiP3!;kTtZ`i)<4oCuT=24MV$ov&9 z!6g^;W$KAWNk<3)mipw3Z>;DjfgVz_1bH~9cLw>l-Y|qO0Z)Tw{)9)w8jLqk2M=eD zk&u45Z*n5S@2KFudALc(C!49;21t2-4_W`BeBGkO|0)Bz3j)1=0~C&a-3g-eb9NE$ z0StP4-Vcr^f0ognd%wPPTfdS+b1K7t|9+K>0Y(rIa$W-fxI-*{j%gpCd+)CT)d3#U z5Id%`AD}!|l~AL>WJwW2Q=8Aw18d*3wU1xG-6f;`3WA<+sYA`2VME0%ftcO|bLkxj zTiR*KY%8K|T>S&5u9s;ugya~Ki~>UaWLZxE0#o2|SD*k4pHF^m$0uA~l-q!Ic@smlWL))kg2O zxy43olZXr^88Kz2x`l*OQQz8t*P>7}_qK$>vBIZ}03_EMva6WEY0-HT&5ileKzIcF_w^#us^SqFcN7YS0KAktF(eG#R$BR|BR!;%p6>jmK ziq0)1Yti!7q{E7liS??C$&zSxatEk^MZU=-c-TEAe<>due|sjGtfZ^#Es8^7Qgk~b z^XI{1Tgj)S0`Vo0I9^BbhmxC$eF?Db6k9NWP-|2gMnU$CVMa+stV8(U1!*JKLkDVo zN)7j6CfmBGUv(Y3D|%o&5_5KR6mDiHe-7pY8D2jYu9v-h#|HyPqWJS&Bc`Gy75C}t zZrho=VOgRah&YDPq>jpx;Ojk@vOtCW!cM$t^f+WYMj+1N>Qo#kMX;}y^t{^yySj$} z%gHftEzeXnt~S}EZPJuF1O5f_qpXf|oKIcghT)Bx5Hv;fr1$7=+2=H1@JaV})oL&N ztly<6!PbmrkF33Wajg)j9S_KY`M7Y#j`wCysb#IGdS@dxb0PNSTwRi9?2Mk`u0!QA zTt{h6S9Pi#3sOMeR`9N0!V-Y*f~jmkZ#1DuuOv?1s3nM)skC|V!YD?B=%m~rmx@+}jb^M-pSXC?Zjdyw1WWPvg9VJbw$jr%Q?)&WFqh0kxkXh?y;92ZfG z#>SH_5*@&ALvpPvpNMm=O-l}lGmw0?Dx%e1P(F$$QZU3XK|a;Lz)%i|1SKpZby!Z> z1rBPgv86j{K*9Ep9@JoUjeKhWyKv@><0$V6f0NpYex+OZ1HxNOXHP7_fe|vu?Ie#Q zoC;%N`lGl@W*w#dM3eHwPe^c`hg%{{rHf^(HR=w%^)+?AtfnYGzDX*?<1q>0SH!_z z^KXf7I2XEnetyoh$%wy)b!Mb3c*~7HR!?MEb)!h)^(oD(+$o|GjFbztIsb?@mL(Cc5#C>Fv__)}QoYL_-Or3u(q=Hru zb1`|7_$NT236kT}I*LUAkW9zGlYrRHX4bUcvua{8yR9duuX1>|vAW71S`%Spd^%X7 zaa~CT3^S+G?2X%10^;T*C`LiB=vPe^mMG40{Z4=2CxdmP5zTx4wDH>ZUt2;C)6_WWLpONqUd`^#~6fPQAwjpL{o3 zlBy1~T`+{nNy8oiAZw)13NeiuF6;~!B;9a83hw3kPZFZ9iF`-P*b38t&Qc2XtwJ=O zuAm$%-7RRd>_t)hwQ>y)gU|kx_^o;nnDx8;gtJdb5&QcNh1>$zUE5UnGZ+e5Tb)M) zLah;$Q!%ROcbGO6)iRH^C9TC}e8Zr+7#r>-g_fK)I`M7Ld>i7(1&*%Zm5X z>g~5YCbB{CaG=*06I)miHHSAj8X!;ESVzZ120m1k6*G+yjV8yKG?S?*Oqg)*>#0MF3s#X7w{eCQp3DS znQLYp#f;D=IxQ=-10WP&VlzR`>sbQ5P7kD~D(y$gr3__->IuF?Ka-&g&#Td4s@RsB*+PoGWNjK(^@`dPgs!(ZBzAmI*U$%ON_C zOP`XtrZhaaMjjI}oU0e_Q%Cw+KMDDJflh(Lvr^t0GB7@o0O+g2hfsnrYU(2De1w)) zgKPL--#beFszZ{r8Z$31FEcXly_471zVE$1Po@tRP&@@@nA3OvPLM!ewj`85uF$N= z*@X#!O35yd#8j!M?|OL z`7U5?E`7r2MBU;z^lhLt{$tJEx_~O~OO!Sw5^jZ4z}y_}`;4l*P!JVi^5WIJgk+h79#SHjS|k{O?_wP+^Er?qe9pi=$?Emx}U zKE7!h!;WdLEVUV(VTOo$iPb*uY;W$yB|o{DaG$jxWiuduUeq+b>)T4#Z+0mopqLVt z`@LhqN%P}ubKP1cI@B!JVv@1XK}C>O0vz?bZM|gKc(v%5_U$+B<=^eG|uEv8k&CHuceX4M*$of!UHM%2IpQYn}z@40jv)!{&>H;9OHiXOzk`qDIQZNvc7Q zz_OS%)xf7HM^;Y{`#%lO8Gd#Pq6Ge2mieecfMewJswXgP(7W#_|1wcCo- zjStjB8%w}Ka8aRTu&ho7p<~%*Hb(*=qq6z8*=>uwy`p(&1l+`{t;NpLcdLds)H8`` z>5`RiHgUy{(AAQxw_b?CG)lW|?eI56=;S-`|ZD`ii(2VW4vg$FJ#q?}{BSys@SA_yzvUTGiN>SRF8LZfA^sxXyHk&03 zgz7{-!*^_${6H1`oRj=fh0BwkuQ(!A>OPJ9`{Kr7sdDF-sQ&@7xt%JevS!lsW2`A| z`??}-);TrDcx3TqrzJx&Kl{OPZnvit#%kG(3xlRf{}NqyK@8?+uK{WKo=1s4pTSqR z1`VAXV(#(4yOft+X7oXScmfGfA|s)HfTyKz-EE)nVz}1>SG4l-9t;nGUtN28BY!>3 zKi-7&q>gM-;8*;{?7kP7S&^#N(cJ~8!9K3rJqCv5CYNM`UQTav9h3+ zM6@lO%w43PNhQ)zuI(n7Y*h|R)es(5PzAbO zdSFWVeBgIO6{V))ggJlv9g2RbN$F$k%&XBXmE3_+h#FgSbu(hwQ1>L;54>C?7VG0((A}K zi*Y$3^f9S0)|C4Jw&7Jz|DBQ1Lxt6hofbL0%rCt!y^*=6TAkFY`9r>Q-mp%D&|vJ+Fe&J_fsbtX=MmxfmOx!`-I<;7-k((us?lULj@Hsd>c) zYg6`YIUtm4#6E(G&Lfl^Hk_2;b?TE{O6V|X%~x|q#auDHjv|(Xh zgLK{&kE`4tfGZre+MK`}3J4DrB;ChMpmbqI77=HkqzHkuW)2ro3ufImWq%MOuhYe) zqyNodD7hI20EYTfi3iT$3ffrn#>=Bu_5nrynvrTZZ zi5qSCZ;+!Gf#l=lYn}7`@ais)!`Uy|26d3j>(ho#qNpV6s0UF3b>pHzqd~bRx3lB& znt7@|2ou!Mgf2&ft`;^i)h-*upRA=bhL@y=0ImSx3_tQB6Mni83QrxMlwTmJo}rjw zECxwy>pSs&$eN0U0R6mPfkpK5v_kV}SS2j!7;DoJ8_hyEXv+w$rz$M0%mfBQQ|Kwb z?^3!>{nZ@^ehC(@AoY%(RQm$yP$@jgZ6}mZY%EhX8IDc4wKd%QN%{vfNXR1u-)sfE zEDr#=Uu0rq&y(dQyTAst_qm*1&5BEZA;^wU!%9NR4XKWeRIi?0cLQ*`Hv8mQU{+!J zEY=<>*hRP8j&)+|#!_0!Y!N};E1DsO6~7oe9r zd`vN^C_33ygFq-TuF>Cj!G`dgjs@W1NS0}|9!wHXbBpxMJEnwaBl%(O-1iKw;4A%% zUt%9Q1jX{C6ruZLS0~GQ&(15_c!${TU@ENZWyC7B*Z#-Ai>jz?YQ@V^Q@2}gbBunc zfk|xh+TJyG;qi6|Fwf2Ie!SLJE@-0Z&7!p<^NMIv84yzx;|3gC6lO0Z5ejey-HR_U zi3WW_Mt(g`gFqQFAU6yK;>Pz*D-)Cl*|m`+28M>?GTaZ(?-glh1U=I=VXpTaK0u4o zpw<7cNT!I^aIXwx8lzban`?*vJ`p5SYlqIKRlv@%HZ_|xOUc9Di@#Zl7r-Q35;H8y znNQ%nSs;;>-9vB7DxhHoi102@4M?|LIb#89Ps% z+-3NCd$)Jix;y*dd$9eJji>7e7QVITk8HKs6#yJcf&d)a*2f(^69-GSyLzuf*h^f% zfu+x%@60a-#yR&W`X}AVn4D@(Vv1x;b$bRVLGH(wfag4YaaNoeI>k)7J-^iXk7gtMi=)l^Yj!D7gqMq`IUpJOdv$9_u66y4 z-hQ1h)w>c+O5}qA6lJUrlW0${muH)VWiT`&EF}p#(O`i!0YEUP_3>&UjeE^>AGVbM zT?_EPLY-y2y=Lf57I+elG{^zB3?0-ef|K;4WC5{(v+8|yII#OUJ#f5;`^hNy`McWd z^{sDJvX=w(xc^2eHA<;*0(@KzU z(gjFbV?NK6m?fD8NRB1#&?5fG!s%2XI6ZOpxQWSgVv1Zb`HwmgJ^QQp+sC6kz#oQa z(wfu*qKBC?qs?gP8Arj9ZoDxq9Yyy0Gb1xAwcgR0l9qu2?Th(SU9}(~tfs?vZrn{) zCC{`P?MPVentVl~hrx73P08Q0x;RkH2bSBB|7au$O`}1JrU&TsQd+JLW zOZo;In!74uAC80@yW#E!)W+q=`Cn^a?7~(wa7p_ZAEV&JHz9wrph5k5O5)38ATNfN z%dE>{Y+tD|8mt?6Qxi^S2uWe@HR)1De51SI9gsDB*DeOt33g0@} z*DH=p5^T3$AjNznn&XAkv*UDyt zpmKrdsak!^E0lLrdo4maf!HbDWfS8%TvTtW@HIxc}l5ueI03#?UQ+mmn$s{503#pz;0 zJVm2{UD|&|--pOmY?}EhuldyN99oG}!i}{Im~uOSvs#lNl%78mz?6P$;z7#$)202> za@zj}1;lx((wou!Dxeiz0cx8EENRCZiVc{cLDfoDJ<(2H#nG8))QcrAskBe~d}UaR&vg#bj4M*K84gbjw`n zR${UOvU&wzDDhJ9UMAo8FOL80XvoG|N+z-NCuoO3awm-y8lsC8EX*JP?r&`g%T!-# zw}lK`#TnDH=3#0+cv)rXwNM4FHI++%gR;pY@##6OJ3CVt0^|qB%mR#(S4}Ru zp&~!R^D0*?r4rOtmJWy{5S>==yShkt`=GSnBcONPBnrlRBH671uh7%kIIml z&#Xq31k47tfpY$7cLvuh_8AH?C-&OByxp8>vLLKTXI?*HCtg8-o|Ek2U9w+DZm81{ zR?n}jZ7I0hiJ6kee~=g5A+aL6o5eb55!4vs4h<{_*h31=#Z|hFjHvIYiQ`Yq6dU?8 zWex57ZF@NB856>4{EIlYMAH5SB6>hGVSVX*WwUVKNsCN!szsu$WG-?QMZ1R)%TaXTANbuyw}(r0TZM^m6cf^Ax&RF>c&E zd*>qMF79!EpMh{?K}1M}L99|D=F*rME1Ri0$L`o|B&vQczD-hut$eaeBbZmWLC;$;SJG*zZ{4jjtupaPk#i9C51g0|Tcl6Cb`%FLVb zENs$I!eB-KG%<~!4aaX1M|p^r9enCl51{I_6%xYYK%?%eir#!5sa)U*-^k7iv)jR= zuq;;!9<<(mJnexfKsqzPuMZI%i z*!1iGY9NiX#rS$r@PngUp_Em3X7n^84rW$t>7@}Smh6+}%&i5LJ+ubf@C%Lh^59XZ zHL*<0qLY~rv_>>L6Pjt3$3L|Lbf|t7={>h(2pG6SL9~)wh)5^pH zct)Ch3)+oli6BMs2h?j#j-32dxWOGudxTG#U0bXk-Mf0Z2Bo2^el!Fz&phf{53z_` zz-pgFy_MK!47`?ia<1vQz^l59k$`R=NUX)S7tfaY%jK6Dw}+(Z6w)hq55CQ&hHR1K4Q<_~X0_9}cDO&IZk4y5jaoIJafT^3P>_vLBzV)rvl4?3 zzI)7?OC4UonX5|&O-nBl0-?Vkd0iH87g?c}pg#3mZzs4dAo-E3NJ@+Rmswcs8$dmu ze+;O#z}A9TekyoA1^tq``Kdt7#sw0y_DMqxsc`t$xBnXwnTam!uxZoA;RP8DfAXgm zJ&Tq-RdBS<)yZGGoUshwxTmTOqdhDz_-73N*xX6FyvDDxYQj*SkwkHx0(u01C9_0w zyWl@t1Mcb!h%D$7hA^LkFv>n44$Oey-gIMg7YkNpR{mU9U}5ad@g6_lWsC`Jwl(y7 zM+*_zRkZwcf$RMG)^7BNtnFqJ4%#)rmJ3X&G5=CmO+`SuPt!fEL}L#6K}jI|*6mpQ zUwdWDtAqEpC>T2)Qr?9X=#K%%m)#YFqh6QPS34;Y;~OU{qlhRytu_Obrh~^-ww-V! zQILAXi-$R87tDe7T!arENy*XgV@R4S;QtBqIvPifZ(huWTGSmtY5gIvB8HRT;ump*Zqb_CgU%zJ zk|GNf78sRLg9FPHH@X+rqv|`MaS{)d3k?L<#jERGO4by9A|DhrK=x)C)`JP^ddut; zt`c_S5zYjAvJyU{kuMh@*bTKwbSMwsu)VRUKM@vXA;ku z1cF6;51}BRp>Y-i^h8Qa*}yshueW@PiJahigd2v_=aKS5 zIhP=!+}ES6UeDAwgn-T^`g={+rHIL)vsF8zo0~)=;i;|S&{nTlH>*zq=zf~DFUe^{ zc-)k`5xs|()mrS=whI~#Y)%I4_vVJlWGo}g+Y9SikKHH-Yr5PJl?4nF$Y4vxW& zGcJtE?G`|1S-V{-mytYuBweSSml9yRroK)AQrDPPirYNA@hG&sNBtKkoo=tmKb2Wi zXuX8vR(3}F`kC9i+kSiX@+#(NF`J`PjmZ%+2e<7e;P%&3NeF>_iC3ZSK!}A1;`aTc zhm3wN_3xI_+E)P@(ZQ+{KFxLqqCPDxckdPy|_lz>wa<4GnBdBYT)mRre z;#4s6Q6T7QGA6equctic-W@ho34ckB#HsLal`c;L-H^>q5eaNc%$y;j@gbF^Rfpx` zp|fm7U@@HxR7R6T+xxVPu`xgADwuzwX+&NZPvFUfEP-k6Y-zYcdvvE&ow@PO)?^0M@-R(FChur1K5DZ!4r*2rZJcxa zrc$Hgq{6Kr1Kw<_SdOhtd3{QTJRRHgp^SiOaIXfMi=@#ci6pM-(r;Mco{DaBpI(p@ zjVCv#<3prWqIYf7S}Ug`flU8>G%`CvKo|HqdXY|Ck9eHDQEf;!`iu6Ojgf=S+Ghu~ zWHyM21@vLlaCgT?MPKWitp{3DW%&j24LRua;kI@aj#5EBDGCW%o?55julQU=FrC$K zXErFRG#jNVv3eXiJEU%HUd#Ku;UIJbZQeC>gr0I}8)~3QYJK==JzQfK3t*rYz<{q_ zCr#1^^-^9p_i1oy1RYbLeY}!W6A-U|u#m(QJ15r-80g6N@kUAraaI|jWyxQZVOxa$ zlvkj-OmKO0POUxDfJG88d56|Y<3;j@pNABmV7tI2^o%nIakU&$n%jr}@V9P91UgK& z{Dv=Rk1qior#5i%xse;(Ou{;#;(-E<1W80Qa+4SuXQ0)OyF_OUOcqCpf4ZOEx7CfB zJABG1P=+uTq(pd(N@NdBiBHG@Mz=&+f3BV!b|T1yQMdxV6bK0&0+QCU3}oRPSQp6^ z2JeQ>)WOPGREmH2Nc%51Xwx#?0c}l_PL4Vt)b*tMY=i zsi@LJcbdQ;Kfn>=JHGd|9npCE_%#he8Gatr^;a< zI86|IK_e9xx-J&VhIk0~k5v);!9d@)>MMCoIftI8bNlBJ?y5M$)``B#|LBRvbifn6uJ?c%%eN4L?=)0|nD zL>((0&1sBsUE+dKjqD4t$|cYNQEGcnVK@|^26nrSvKzNVIuqG^?qT==M!yDt|JRW*7a7F@)X$MfGK zRVrYP3RLoQ6yWzsA>gnyAOi5Tj_oaKBU}hv!d&jfD;UwOCzJoYDXe@@b&2N7EuBcF z-rh3cSmcQ?7Li+XHg<0lY>gS%6dOx$*wXc?t89P++GTqks}#J#c;eJj2D(lAC7wNE z`1;YLbDGL2t>Q`NF*97|<~o?iG+zs6W{-j|Ok$NbZkA3&LPe*Zchpdp(>+yu9>SNb4W0cA@l_=5 z%0Yk&{i_T0$eHeJa&+@n3#IsT(<+^<&uq-l*`HJKPm#MLnh);vD43Pn@eekwUh>P; zC8P|13X1j*?(`X)GF7y;(1$B%CaS)GoQB06ABW~6;wP51F%Sb`<=Yd-oQgUg-nlF8 zoEG_34|kz|Y|t0wz6aPo7dVS&%#vIH6)6A(jPks6-Z5~PtD6`krxM-ujb=WbIu{j` zL=MyWlbbwk2TubG1%pRXyQrY=lNBh~IoT3dA^eFX4I*5w{Iq1?#@jU(LAIX%pjAj) zwZ&;|H?#0r2k*e#j6ir(F7}Rotdr!R^!HHYJ?J_+0~Z;DtOl`>rQEL-vzIRCmQ&Hf z{LRd=2Y-AO?1FaiKKmnt6{fHqcLr0Z&mOoF{0af9P6yJKs4k=r9YjDJlDDjJlPM1r zkazIW^wpI+{{>4A*5J^ZmiSS5A+_6M1G*ma2REPc+&u=hI5_&|sd#EGrBY5EVS~{+ z%1hBaqB8h zvSm$H!DLrjLurt;p&zXIZ}7=C<}QE=C)N+~bS!3lLE;}*F__5@XTPUQNFn$|E9hCm zz}x&0is31o2Z9x2?p7S)LPgM&dCGIR%eK3cv4C7`sOgliOKd=wrb7i*VgGsxsHxa? z!|fbF-Bi)=zUatz?~yYj=|tl|SRS$VcxF^<4G8kCSaH@@c0s9+Del{Q*;hFrWVq<@ zyD5%u5P?D{6F-g^aES&zDpSuqxtnTQ!}Ou}=5N-`3d+`STODuOaNk2O4x2AgxZq$S z@3|A(ro%FsTb8qoI zxy4shXGO70_w<{RhK$6 zJQ#Ei!wadLYZ!b)bvdnVzNqagY-n*#{}3o$q!+BxN*xQT`_`tcH8YySn9xNxGp-D1 z{@>!|#_+0JQ58bV?X2@NJgl{x^T|!`EQ~cS;hsigG_N6D?f%pSoGAve615nPWM>TZ+e0J`eV73^O<#00bXyGVpL4KSh+7D@(P;gOR9BnF+pwp zF|auCJGWiqMQq!RTRmB;zR|hIjbKO_HT&N*g;ZxvXKMu}fT-A9Vs5`Gr%U&p74Xaj z+Xi2Me^t;E_lgw(wNrNUGpv%Zss~rEq(P zq3G;2%yt5_fv3xl4=a>Z@J66^wAIw(VYPC94sRi`8V?mPm1R|k;4y>|hGwL*EdSD_ zke0W{2g|p^J0vZIll)4Rr2@rpO1N2mqJD}XK`G=3P};! zxC-%@0DHY47WE74|F=cs%e&AhrdaE-d8SyKld#OCR~?6B+fTk21^7Wr(u!r*d4&xc z`aPzg&U7!pOI4|1KM?D(l*B!+g}r4eQs_RLu3Vr#t9IAkTggM|nw^_L^R+weVvh^V zGcuYB@N=6e_V3*EqWPZH{vbVRTd$TwOf6rkR!tW55Iv^or2~fF_UGGDC^xJo z4w6DmWkKEQVfv8Z+SQwjZ9crTn#{ba3mVA{>ghRcN|Ll}SNeAx9Vm060u&+m-Z{kK zspYPhR(=t;yVdnJ^5KqoYd-7*k*d#5#D^j6pFf74$%}uiQ{fWTrX`*S4*FkaH7pQr z-FB1MzXa$nS^$9E=dhrkWT=26TLqqA#8hwYXL~kujRWc92CWnqzNt*VJa*9xxFkY< zt+RfgS%evxFYeY0HCx``c&DP4lv=@zYcRLBmGt^asRlh2*qt;dXlIG1;vI^uyOx>W%m_^XsMHaSRjPV*4idZ;Aqr^Yf3@5cv}?nAF{E1mg41-YNbjIIKKLGYS_Tgr;dZH5 zy`kq$f&+}L&83%=X`9UGhazUUl%_T>)7DO#oh;vE+*s-W&+Q?L#;E@k)YChly?v_g zWopG#C)g*uRu=7+C;a8y+q-esy8=H~^w@Ls30kskRU3!7u5w5Gd;JM>KHOTY{V80e zatDRAcuZI?C|!+Uq9#D6`rN?9*dHerxJam6C;=!jD5oVf@r~sk}dD{q_)#)1srSdn?7-X zNk3yYkn|!aD%dA-Q#7o%>(R<@k1YKuN_J_vxg7$XDLz^P3FI|q7xH&WT5Wfus*x4b z`W0Z*IR#pmZ)sdgZy1_j%7N(Zc$B}Kbup2l4X08@5$5S^9QRe%^n*EUe$WK2@vXs>m*a7n7?mUh)!;Y3Cyc>-1G)pVXj+DckeOfegj%dXwA0>3rM+j zVwmG&#rnl;IVN@K>ut9hoKV>HHIIiO56Q@2Paf3(7IO1!>`8h(6)=&69PZ`&^I2Mv z7nSwWX8X}NEG9_>rr9KhEz>Eo%73Pl#H2H%V-XwRDN}xF<69oNrs)lc84M11qwFR! zxFkH^zexvOUM0?2>H~ZzTigV@gD5Y;CNi+p1Tut~g|t?qSb_slT3dHFv2YOpg-@4Z z0Q;Z!Yk{90A%dTYTbwO%v0uDm$nx!tVdYwP7i($)=l@gsy}e*E`XNXE?J2G52aCfU7VET z<4jPG>|`OXSjKyVDyv!Gdz6YzhH-=8$!udyC3sk<$osGbGNZ_Zgbn zL3M&Y2)T=XD>L?rLP~+saEgk0!)%W3)yj5vM;&zrN=fXvvY$}pUgiRNreMw!(v6mX z?3rz7dmwjlJ0LrQAWapqea9v7BAp3#PE*E=gCCu zx9w}9u)rhr$zQVD17MC|5RVh6`K7QtMh&si!HIO6Ie?!Ff`5gA@kPblTq9BQ4Eq#T zL6CnLJ4yzhS~r8wO9%TwqU;}ByBoEuM=26HMa^)i-OXx$-QAwim<*zt;Lr_`l{QV8 z=wvKw6wTxU2H%wnia&*rUrf%CGy1ud=E|2QcCT&joo&Nr>7UkNgHSD$Iq;>s+W|Io z((5=G=>QOT-dZ<3t8DG28ov(c?bES*4oShn6gl32#cPgcqZU$=(nsW*MM2NV51JEk z8Ej_M6y`G*50B8rLq@(VC05gKmSGfMe!E$VDpo5miZ(AE^IfbG$yn;EpR?@2_x(VU zZyo*E?^{H#GzR#s-h@)-bBHkgC@%t{&)^6@Qb6eT+*o|Mfw{yOe3{TBcp%PX`V?su zb}_NQFoFl^pKI1VJG&{8_AlbaQ>Fx;Pd+_8A9dDtty5q%)tXnqJahXFek8<8C3kS$ z<+#Mv@Zv2EfL6uwt)ipHD+4hZgoUh#ZYMHt-Pyd9xTC3TYJ8RXmgF(Lv;Fi z2hf~c$jENuEfGuq%S--j{K^T0Tx`S-h|AD56bRW?v49eDcjQ6Izua4F0gGiZ*#KfJ zkv*}@NfxS+h>_Ms&zeWdU2EPw;kQMSGCwScsOQeNYLy<=5T&2Hu&!(6-8KdR+1&kD zAuL&M$=oAiI;ld@@)zZiD{pMBZ}JSF0g!l9QD*y5=?;UcBZ)Sq9=WL@AB@JJK-G;e zbjmF=#N^Cd7N;MYQ?gSrju^wMy8MNSHthqGh@C#pR-?#_WNyTI1kFfQp&mY8-2J4? z;tW##wlIAuE;H6+I#qK(xEtp9j)aZsTktE zQO1($wB~}P`Ji~Xo}H=VwA|%iRM#Bnj~Tzqy*xHm;j2QD$qB~rM%VC;wnhD?`CAft zE!=t7q{TlD<0Cj;?c2!ZW`7}13SjL}=1EcMY_~oUX{DFAl2Y0>XO7aLE#AhPZ)07L@z`!|kfC`ge;>mS zQ1h$1a5zq*{%dbJ5sA&zf=f3wk%Qgxu~voKDRW3ujbGO#R`!5Fb`l+#3BausW9$uE zJ031OJ_+w;HUB-+g(L&?FOi#cZxYQH8Yg4PXdtVpS)dZ+wkOub0QUz|A&P@n!~Vz&hgmmy3D09a`_6{pXCF)Fsh^fAMc1fSs({z!m!+$;h! z2xaM4T)kuHIG7>)N!)OYfm;gaLz#;nEOrsl=?YpqF@*`xvx8#{I!OE*wMgw|>S?Uv z^L04CYi+??TIkTf=!FF|)42iC{2=T-m0Lfq_x!K|5FjE$uWwJd{nhFWrN6BM@c2AE9r@180?&+ z=oF`YkE~C$i4+CERJ4sUJQoL}9A$Zll8I}?=Pt~ZCUhff=5((KtG`cE->O;S+G>p?l7G`s+fyvb$4hJgQA z7K=6^mizNfBTVU}XrN~3>)+P+yj9_713}j;so?T@%&k>^HPb@Y5^abUPE;>KWH^S? z3s5x^H&){8fP)JKAB@r21fkHqA2STqWz+_OG^Y6l1|rS5m%agsq@60m^Zyd?U>s3i zNQuw7jV4(5KG5r~G?mQmkMElq{nGoUkqBt_rZ2@xZD`{Y9iZ{KYoe^^=MM|T`w1nW zXT1n#v?yk|(sz{5e~H$jMp_660X)MjQ^(gI|0nej3hxh)y+`N>`pL_)0L^eX(+@sL zDcRfK>G#ewGwA)Bc-#NEIGj4dyBIiD`raL0T9z?k)F@Ouop(|^VM;CosFVV`B+Qq@JAvzBbw|nDUcYx7M#pn|+wp&D z0Zm~rNwQE}gd+ii^zViZHVx_!HaUMY)P$6zEGVv+dmth`m8MxIvXscfk+P&qxqMohLJDI!bj!L&Y9u7aMN;zmkw`CKgf+Kg*Rp- znoNpjmQ6mSWS&iJFDdrfqN--YCUcYK%T0TVShz_)T4oqsQ`O?2W&Rf-yPAKFM2oS; z)gqbMZgC~jsKI(l1ef9G{oOdhPb3CgO?=q^6LFU?!NR60tC)gbjVzrh7}a9iL^IWN z7sS}GnWZ!f>%u``(dJzJ`I^qQGlWYp~wV(MXt!_FPHE3fjsgtIn+_70mVa|D*}H({0ZV1p2Zel z%I9&(txvf_mX!7WiX5FB13raBpUb39DD;f{;$Zg|WpD4N8a>x@AR86y465>1MA;)F z_e2zF?$Lt+y<2NLpP7Y>r_X1KQ&M?6BS+l_`XZnl*Pqby=v)U~bpK4wEJS6=T(&^* zJV&h_dEAj};EKqmDUg5c%bxq#b02%|W6yoMxZIQ#;p=mj+}_#V=^*yP?mdMLC{I)n z$%v9X1*um?=Vo0W*l3yC{^&pzJVuVPfhdO8*;?+3mGyfc^{tvV;rc$tH*%KiDXq;W zsW0&;HLmPZX?EZct*X_pL_K63Z>KJ(uex)Vx>DCt)yuZBhKPSnncJL6PX_v5wnCR` zU}4aP7r_fOMJk<&<_o#T%F5GEhXIl54H9*pYN|gQ#_7K0;obS6m+$r>5~?g`0*k(o zOFT>B1;2xVw`eEobycGPwYt+M7o%ivP#1|+eZmijm)_=YAcb@j>lQ&xB9vqZ>skOM z<835;B5KkXhhl$cXc?4bS!$jwaIWi;(DMK$iGOqydQdcj)85_6{usMYivn9IIXHt0 z={h|^06I<>K#oMaGSa0Qh%l@5y`y)4V(Wylw~7$JZ1SxLl`%=26rliqf(O$;;} zC#JFq8z*M70jH6qI7>lYY<1JIcuAnNit;C1bJH4UGr#9<q-Q0E|i|*SL*|`+i{a!u~yf$~lkBelR*j8HP-fnAph`y{%^=(squivUt=BiTM zLQU6jez#fcP8(SLsxBMCAD@nJpz7AH`iE$ z%nNybY{+MG9oD5@$o9}exu_iDGR0yU@71J5%0yHiL_Ph&KtQPsIcov`ePm)NNSc35 zmqZ)Nklxm%Sg%T)U!};gITMEdYy3A*V}cO48B6lTL;V0 zm8*DZ5*B9wLQMGHZg&xKbl2T6(qt?RqT@tVw2h6kr4t9}t0sMbxk`S(Ku9?DGARdO z5a1icQh{zDalr(sP%o0ZCP{Q0O2&WKMZ`hCMLwTy*+4orkd6(cv*CesY)qKVhzU~! zZc2Ww&9iHFuol-Y8_C8-vaykDY$O{S$>#plubrxBr3>O531oH%-RA07s`3)`>p6I) zK5wl~m1?hCr@HE0^%^NvSqf|H;OVXPJvIOd{2UUTyqHX3=Op1A}j2VusZhUyB zk|@?O9NAtu%W1)EBI*p-jp|si78WrZCRmIUzeH{y%n&4T0!YDeDEWm0bb|u16R!cB z!vz=zbOtcDE_e4~CjK0HMHhcq{V>I*W7Wj-9{3I)Rz)OQ5a$yNTH^^_cPX5x-P5{} zE;e*RPyKF=x-4#Gi56;h@Mro$ zI1=Q&{hkXsddcw&mDya{8g>eor+CifQ*%X&!+@JpcnO0^bIV7+_mu|MD9By}TP4+9 zYArmuFa@vos_s*XJr;kCW|$ueq|lD%CH1TD(hCLRWtmTGvIIA!8tei)W$CNOPwB25 zNWchzPyhx_0h6x)1O&~!1#ki+50{l?MjcgdT@ix#T^Y{98XVGXb7?X!&s?PWZ=vRMCj%Y|!hTJ8U4-s@I zc}I94cfI1zUoknO7hKm<&(5F_JuLe(b5S{C2#-ogNpVgo@9tzlHLSU&Hg2=-n%50? z*y7>o#ld6}potKg`#*Y@l7$WWy<^B>#do>HGsHNY`Ra1HTviu&Vp{z@4McGQJ!wwY zk!KL$I(cD~r}=--W65}R@?hln*V%^+MRcao$_wBRML%eE*pe4-$p&vSfw=|Kdc!@0=`RR@21F+!q9x&pz-dQ(j$4_a-4+0w zH9tDlLb-9Ufh?=uA-`%;2@yKbQlbGKNQKbUl;F(UEMA`4q`}9+|r}I`24Uiot&X8URAMb>g`PCm!wRe;>~Oo5~J$ zDz0sQg`S7_A@m?|njhF_Y8zgHe-57M^A|ihA-s+mWSXyuxRGwJQMipf{nh*+OG^5r zf25!3BmT@4_b47;?19Ion9N8%~1=I%SQnDRNE){dK!JM^{;(oJ4(fRpIFV@LaaAy8Die_|nnl z7*=?yN1xh5EgaI)m5s$)rM{eQZB=zSN7*vLr8{-yKPtMcUg6T)yzPp9@B-<{sfD{0 z?%rydyt@?(ujMvTmCaG0cEkA%EtaK5NwTvN8lRCPywk8 zwSs_n@Y^Z-Nvj;MlGi4FSC=~GzsF=I2086Q0WR~VY3BTrZfsRQfQr+tMUw`#*=OUS-XE8FU2%IsZJtxi+VU7twcJef?R#T}A{whU)1i z^OlSMGn&_*eExN|hnWxDnN zyab1*7Xn)+%dg{eSN$Q)1dI^ZUHUFqDcB7kc)MNUO{LcQMSLRAk{u)sbY&|}TI|MN zoS|^p-cNO|T&zltH|76igl=7uk`8l)ZQR1PCgjt>+nox}BE4$xq?nZ5aR1;5X%b2Q z+xO9e3*q|02^K!5TNg=G)@6UL`f!&5xUjy8+I>k0a|OTdf@s6s!FPJ8?UBHOL9K6;_@q(XhxU9;x6p*W zX^WArnH-pj=8&r&n{m1*FnG5NFBy|5-;3zTv%e@Dw`G83Hp67~nXpF`?cJ?zl3&tv zoPzfOouz+&ro_?d(*Ka*3srQ050IVW8CF3FK0x*!p(E%gFV6xr!{JOn_#`9x_x4pB z0r?5a0tT0+ksp#BEPwmiG^3~-b<;bMsd2Zcs^`xz9&bFMO?!&w-lQM>v5ju`>F?N9 zjx0|>Ul@sAe~qg}i+z1ktLB_L!-=bQZ@y5&5Z_N=JNJ7DY%&a*aev$-^=T{*vOK6I z4@%!@POtrqh5C;*p!(Zkty6V$1!y&|>Rc;TN`U%2Z4L`^9e=F#;Gj5|WroB&buk~ga93@L-A_O2=Qi;FA8 zHzv2P$Ytv4g)vJJlgaF+@Fu#t{ZxGCHqR_tn;6PkKdnDxSKRy6o>0kt{=WbK0RR6U KEvu8%>jVI~YL15h delta 20863 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 a*%kMGwJTJzpZ_la0RR6#+m49T>jVIiN@(f; diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 5a65b3a33921ffe18ee4fe002bdec37d7b59b576..963ce6f50c0fa7b5bf03d02e4b213c273755e78f 100644 GIT binary patch delta 9102 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 Z$*9v7o=)$k{|f*B|NkYIA0}CP000Z*f5!j- delta 1731 zcmV;!20Zze6_ypS`vL>BkF}Hj0zZGwNwF&Z3d$ZEDpE*ehGr8-8sz#bM(%d(s**z_R2b2+)eusE23~@q+*ff9rH7ma+ zY-AkvGd*uD{x8Zm0|GSw*a;)u1pu(1$Ll4m42Jgi;ZTCAHnw4QY0ZIIGyPS^&N_DP zTkPze701p?Hspp8Q?m|Nl$GL@=0hJSIX z=3Qyvj~>uZG?=^xn6acG@bAX;(yzrC~G+X^O#jw1@}Uh(m#{x-2k-O)UvYMeP}d zr5UeVf!{^kmE<*YmA0GQI4A4|Vziipxca7MsrYW>6>^yL6Ss*Q@?LcJPoSf5rDVzm zQzW};YiI2eTNB&{DQ}ZG1!gygnSJOKo7pwEKqP!+5{UWUp9Sh4`7CCk>us&n@)Fjo zwVlGAyRcfTpT^Wn!g@)#&r8D7R=1Pa1yz4)_lk{6ugcH4=TVi{p5+#nqBH}gkx~-( zI$S?7kaB)BF0>h9EBXkz8ol^nU3k1EdWh;DFLZHE9cc)1Q(|lj;ATf^anG?9qXxP= z5m9#m=;p0s50A=~+WDK7HNVk$!DDTG)e`n}9%oGBK?xl)Q|QtGrs$BSH91L`{vUtV zFsYF{Ug5N~U$5x3=4qDx3vOzMNrsco;5)H_bf2U%KACh$_e2f4@&P>mFaG?uA56IC z|26Q2Fd24xL)r}{CqV$c&SNiH18lNALsyw}ZZ@CkVKAZGB*>~4r1gUIEo0_R$sx`J z&*j5+Rm|Km-*=-+V;K2+pw@=Do$Y_a+)kat-8I|X1!$XB;Nr=&xqXXs-I5dL z+M!R7lF_PYzQaPQMRb?T!=5jBc&D~zPX;od#zGC=Cu_#REBRAtar=5o89uKCnblP zZ$Pd0lFWp#3?>dZcbDw;OB#6Xj=u~mOLqMw9ePAONTkLvvkD>+ME9CA>u8`aTp=|k z`iDv+K`F{cx6C6Gq{ssxf`)(age2IUIT9(3w0kEbqCj6LA|H@ecPDT+xX19vyq&Y& z#P_LpdRB61GTuJ=(Q(34ai)for7mA&3Cc#x>?hmKtBokfR=Fi@Y(*62gsQzDG5!9L zO}>!u-D&lQlH-Yg5czNs`94F9PEnR>GZ0rhL>YDyy~3hYvs3N8R;z!rtZiSTb7EG1 z{b%#B)wIK29W}QJ^`0|UYxld*?smz!o!Ok2 zzRqDzSG)xFi2p4=@pm$$ewi*?7|&Z?vNT@NywQR=ElIH<%xa*c+o09z0*YQ-@al!| z3Mq0kvbuhigxk!nw3dI)OsoGAZh7^I(t~o9$u~8ATA85Q@6^#*r{q{pw0(D-Z&sx! z*L}6u>_ayHoG7?t4m_{;bXDPYjbmg}r*Tdl!zV5&{ z-*@V@x@VjC#M9n>scO&m&*Trl#NKoD%oJ{N$+mS)9VB%35}1F|qDXLuM^T*tWuquc zFolTY2#^vSMCYwF(52D?Fyz7m2Ms2Op#MbQV6+fH=iC7s9XySxJw}u{REwzl@B&v+ zTi-qMiTwc1ycZR&MhJ5dL7#Q4F_Gg&R&h3S*rxtaME#Vqeo7fZ&2GtQme`gJVP;HK zyhNM3hjN$~S6Ooc@!}dr$S&b(EAQ7U8>*yyOHs7j?sh(`9Y=RmA$uj;&i^D-{X3wb z3{`SYSE!Vw$*(!tYja=e{)^Qw>1yC|`$3|BDe59M1_6i&KrDJnCy@vk3!m-1Qe)p; ZGV1sgPp5a&{{;X5|Np)^+1Ocn000qYVg>*J From 0daee83735ab53db2087b0f78230a9ab059e9e9d Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Thu, 29 Jul 2021 15:28:08 +0530 Subject: [PATCH 16/27] more logging in data transfer and markets --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 89db6a3ae4a..4b26657437e 100644 --- a/go.mod +++ b/go.mod @@ -33,10 +33,10 @@ require ( github.com/filecoin-project/go-cbor-util v0.0.0-20191219014500-08c40a1e63a2 github.com/filecoin-project/go-commp-utils v0.1.1-0.20210427191551-70bf140d31c7 github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03 - github.com/filecoin-project/go-data-transfer v1.7.1 + github.com/filecoin-project/go-data-transfer v1.7.2 github.com/filecoin-project/go-fil-commcid v0.1.0 github.com/filecoin-project/go-fil-commp-hashhash v0.1.0 - github.com/filecoin-project/go-fil-markets v1.6.1 + github.com/filecoin-project/go-fil-markets v1.6.2 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-20210723183308-812a16dc01b1 diff --git a/go.sum b/go.sum index e39d883d990..8c8a4396b36 100644 --- a/go.sum +++ b/go.sum @@ -275,8 +275,8 @@ github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03 h1:2pMX github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03/go.mod h1:+viYnvGtUTgJRdy6oaeF4MTFKAfatX071MPDPBL11EQ= github.com/filecoin-project/go-data-transfer v1.0.1/go.mod h1:UxvfUAY9v3ub0a21BSK9u3pB2aq30Y0KMsG+w9/ysyo= github.com/filecoin-project/go-data-transfer v1.7.0/go.mod h1:GLRr5BmLEqsLwXfiRDG7uJvph22KGL2M4iOuF8EINaU= -github.com/filecoin-project/go-data-transfer v1.7.1 h1:Co4bTenvCc3WnOhQWyXRt59FLZvxwH8UeF0ZCOc1ik0= -github.com/filecoin-project/go-data-transfer v1.7.1/go.mod h1:GLRr5BmLEqsLwXfiRDG7uJvph22KGL2M4iOuF8EINaU= +github.com/filecoin-project/go-data-transfer v1.7.2 h1:iL3q5pxSloA7V2QucFofoVN3lquULz+Ml0KrNqMT5ZU= +github.com/filecoin-project/go-data-transfer v1.7.2/go.mod h1:GLRr5BmLEqsLwXfiRDG7uJvph22KGL2M4iOuF8EINaU= github.com/filecoin-project/go-ds-versioning v0.1.0 h1:y/X6UksYTsK8TLCI7rttCKEvl8btmWxyFMEeeWGUxIQ= github.com/filecoin-project/go-ds-versioning v0.1.0/go.mod h1:mp16rb4i2QPmxBnmanUx8i/XANp+PFCCJWiAb+VW4/s= github.com/filecoin-project/go-fil-commcid v0.0.0-20200716160307-8f644712406f/go.mod h1:Eaox7Hvus1JgPrL5+M3+h7aSPHc0cVqpSxA+TxIEpZQ= @@ -286,8 +286,8 @@ github.com/filecoin-project/go-fil-commcid v0.1.0/go.mod h1:Eaox7Hvus1JgPrL5+M3+ github.com/filecoin-project/go-fil-commp-hashhash v0.1.0 h1:imrrpZWEHRnNqqv0tN7LXep5bFEVOVmQWHJvl2mgsGo= github.com/filecoin-project/go-fil-commp-hashhash v0.1.0/go.mod h1:73S8WSEWh9vr0fDJVnKADhfIv/d6dCbAGaAGWbdJEI8= github.com/filecoin-project/go-fil-markets v1.0.5-0.20201113164554-c5eba40d5335/go.mod h1:AJySOJC00JRWEZzRG2KsfUnqEf5ITXxeX09BE9N4f9c= -github.com/filecoin-project/go-fil-markets v1.6.1 h1:8xdFyWrELfOzwcGa229bLu/olD+1l4sEWFIsZR7oz5U= -github.com/filecoin-project/go-fil-markets v1.6.1/go.mod h1:ZuFDagROUV6GfvBU//KReTQDw+EZci4rH7jMYTD10vs= +github.com/filecoin-project/go-fil-markets v1.6.2 h1:ib1sGUOF+hf50YwP7+p9yoK+9g84YcXzvuenxd6MYoE= +github.com/filecoin-project/go-fil-markets v1.6.2/go.mod h1:ZuFDagROUV6GfvBU//KReTQDw+EZci4rH7jMYTD10vs= github.com/filecoin-project/go-hamt-ipld v0.1.5 h1:uoXrKbCQZ49OHpsTCkrThPNelC4W3LPEk0OrS/ytIBM= github.com/filecoin-project/go-hamt-ipld v0.1.5/go.mod h1:6Is+ONR5Cd5R6XZoCse1CWaXZc0Hdb/JeX+EQCQzX24= github.com/filecoin-project/go-hamt-ipld/v2 v2.0.0 h1:b3UDemBYN2HNfk3KOXNuxgTTxlWi3xVvbQP0IT38fvM= From 029ba39fd961dc216e913d27d1fb6da35594ef1a Mon Sep 17 00:00:00 2001 From: Aarsh Shah Date: Thu, 29 Jul 2021 20:04:50 +0530 Subject: [PATCH 17/27] update deps for logging --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4b26657437e..522a0d568b7 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( github.com/filecoin-project/go-padreader v0.0.0-20210723183308-812a16dc01b1 github.com/filecoin-project/go-paramfetch v0.0.2-0.20210614165157-25a6c7769498 github.com/filecoin-project/go-state-types v0.1.1-0.20210722133031-ad9bfe54c124 - github.com/filecoin-project/go-statemachine v1.0.0 + github.com/filecoin-project/go-statemachine v1.0.1 github.com/filecoin-project/go-statestore v0.1.1 github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b github.com/filecoin-project/specs-actors v0.9.14 diff --git a/go.sum b/go.sum index 8c8a4396b36..90c52baab2b 100644 --- a/go.sum +++ b/go.sum @@ -313,8 +313,8 @@ github.com/filecoin-project/go-state-types v0.1.1-0.20210506134452-99b279731c48/ github.com/filecoin-project/go-state-types v0.1.1-0.20210722133031-ad9bfe54c124 h1:veGrNABg/9I7prngrowkhwbvW5d5JN55MNKmbsr5FqA= github.com/filecoin-project/go-state-types v0.1.1-0.20210722133031-ad9bfe54c124/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= github.com/filecoin-project/go-statemachine v0.0.0-20200925024713-05bd7c71fbfe/go.mod h1:FGwQgZAt2Gh5mjlwJUlVB62JeYdo+if0xWxSEfBD9ig= -github.com/filecoin-project/go-statemachine v1.0.0 h1:b8FpFewPSklyAIUqH0oHt4nvKf03bU7asop1bJpjAtQ= -github.com/filecoin-project/go-statemachine v1.0.0/go.mod h1:FGwQgZAt2Gh5mjlwJUlVB62JeYdo+if0xWxSEfBD9ig= +github.com/filecoin-project/go-statemachine v1.0.1 h1:LQ60+JDVjMdLxXmVFM2jjontzOYnfVE7u02CXV3WKSw= +github.com/filecoin-project/go-statemachine v1.0.1/go.mod h1:jZdXXiHa61n4NmgWFG4w8tnqgvZVHYbJ3yW7+y8bF54= github.com/filecoin-project/go-statestore v0.1.0/go.mod h1:LFc9hD+fRxPqiHiaqUEZOinUJB4WARkRfNl10O7kTnI= github.com/filecoin-project/go-statestore v0.1.1 h1:ufMFq00VqnT2CAuDpcGnwLnCX1I/c3OROw/kXVNSTZk= github.com/filecoin-project/go-statestore v0.1.1/go.mod h1:LFc9hD+fRxPqiHiaqUEZOinUJB4WARkRfNl10O7kTnI= From 8442bac5b2fb25f39c9415eeb5a2ade66b36bfbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Thu, 29 Jul 2021 10:55:37 +0100 Subject: [PATCH 18/27] support MARKETS_API_INFO env var; support markets-repo, markets-api-url flags. --- cli/auth.go | 3 +- cli/util/api.go | 142 +++++++++++++++++++++++++------------------- node/repo/fsrepo.go | 5 +- 3 files changed, 88 insertions(+), 62 deletions(-) diff --git a/cli/auth.go b/cli/auth.go index 20b9bb39428..ccf5dcc8bf8 100644 --- a/cli/auth.go +++ b/cli/auth.go @@ -128,7 +128,8 @@ var AuthApiInfoToken = &cli.Command{ // TODO: Log in audit log when it is implemented - fmt.Printf("%s=%s:%s\n", cliutil.EnvForRepo(t), string(token), ainfo.Addr) + currentEnv, _ := cliutil.EnvsForRepo(t) + fmt.Printf("%s=%s:%s\n", currentEnv, string(token), ainfo.Addr) return nil }, } diff --git a/cli/util/api.go b/cli/util/api.go index 730b75d9d2c..fca667b5ac9 100644 --- a/cli/util/api.go +++ b/cli/util/api.go @@ -27,112 +27,134 @@ const ( metadataTraceContext = "traceContext" ) -// The flag passed on the command line with the listen address of the API -// server (only used by the tests) -func flagForAPI(t repo.RepoType) string { +// The flags passed on the command line with the listen address of the API +// server (only used by the tests), in the order of precedence they should be +// applied for the requested kind of node. +func flagsForAPI(t repo.RepoType) []string { switch t { case repo.FullNode: - return "api-url" + return []string{"api-url"} case repo.StorageMiner: - return "miner-api-url" + return []string{"miner-api-url"} case repo.Worker: - return "worker-api-url" + return []string{"worker-api-url"} + case repo.Markets: + // support split markets-miner and monolith deployments. + return []string{"markets-api-url", "miner-api-url"} default: panic(fmt.Sprintf("Unknown repo type: %v", t)) } } -func flagForRepo(t repo.RepoType) string { +func flagsForRepo(t repo.RepoType) []string { switch t { case repo.FullNode: - return "repo" + return []string{"repo"} case repo.StorageMiner: - return "miner-repo" + return []string{"miner-repo"} case repo.Worker: - return "worker-repo" + return []string{"worker-repo"} + case repo.Markets: + // support split markets-miner and monolith deployments. + return []string{"markets-repo", "miner-repo"} default: panic(fmt.Sprintf("Unknown repo type: %v", t)) } } -func EnvForRepo(t repo.RepoType) string { +// EnvsForRepo returns the environment variables to use in order of precedence +// to determine the API endpoint of the specified node type. +// +// It returns the current variables and deprecated ones separately, so that +// the user can log a warning when deprecated ones are found to be in use. +func EnvsForRepo(t repo.RepoType) (current []string, deprecated []string) { switch t { case repo.FullNode: - return "FULLNODE_API_INFO" + return []string{"FULLNODE_API_INFO"}, nil case repo.StorageMiner: - return "MINER_API_INFO" + // TODO remove deprecated deprecation period + return []string{"MINER_API_INFO"}, []string{"STORAGE_API_INFO"} case repo.Worker: - return "WORKER_API_INFO" - default: - panic(fmt.Sprintf("Unknown repo type: %v", t)) - } -} - -// TODO remove after deprecation period -func envForRepoDeprecation(t repo.RepoType) string { - switch t { - case repo.FullNode: - return "FULLNODE_API_INFO" - case repo.StorageMiner: - return "STORAGE_API_INFO" - case repo.Worker: - return "WORKER_API_INFO" + return []string{"WORKER_API_INFO"}, nil + case repo.Markets: + // support split markets-miner and monolith deployments. + return []string{"MARKETS_API_INFO, MINER_API_INFO"}, nil default: panic(fmt.Sprintf("Unknown repo type: %v", t)) } } +// GetAPIInfo returns the API endpoint to use for the specified kind of repo. +// +// The order of precedence is as follows: +// +// 1. *-api-url command line flags. +// 2. *_API_INFO environment variables +// 3. deprecated *_API_INFO environment variables +// 4. *-repo command line flags. func GetAPIInfo(ctx *cli.Context, t repo.RepoType) (APIInfo, error) { // Check if there was a flag passed with the listen address of the API // server (only used by the tests) - apiFlag := flagForAPI(t) - if ctx.IsSet(apiFlag) { - strma := ctx.String(apiFlag) + apiFlags := flagsForAPI(t) + for _, f := range apiFlags { + if !ctx.IsSet(f) { + continue + } + strma := ctx.String(f) strma = strings.TrimSpace(strma) return APIInfo{Addr: strma}, nil } - envKey := EnvForRepo(t) - env, ok := os.LookupEnv(envKey) - if !ok { - // TODO remove after deprecation period - envKey = envForRepoDeprecation(t) - env, ok = os.LookupEnv(envKey) + currentEnv, deprecatedEnv := EnvsForRepo(t) + for _, env := range currentEnv { + env, ok := os.LookupEnv(env) if ok { - log.Warnf("Use deprecation env(%s) value, please use env(%s) instead.", envKey, EnvForRepo(t)) + return ParseApiInfo(env), nil } } - if ok { - return ParseApiInfo(env), nil + + for _, env := range deprecatedEnv { + env, ok := os.LookupEnv(env) + if ok { + log.Warnf("Use deprecation env(%s) value, please use env(%s) instead.", env, currentEnv) + return ParseApiInfo(env), nil + } } - repoFlag := flagForRepo(t) + repoFlags := flagsForRepo(t) + for _, f := range repoFlags { + if !ctx.IsSet(f) { + continue + } - p, err := homedir.Expand(ctx.String(repoFlag)) - if err != nil { - return APIInfo{}, xerrors.Errorf("could not expand home dir (%s): %w", repoFlag, err) - } + p, err := homedir.Expand(ctx.String(f)) + if err != nil { + return APIInfo{}, xerrors.Errorf("could not expand home dir (%s): %w", f, err) + } - r, err := repo.NewFS(p) - if err != nil { - return APIInfo{}, xerrors.Errorf("could not open repo at path: %s; %w", p, err) - } + r, err := repo.NewFS(p) + if err != nil { + return APIInfo{}, xerrors.Errorf("could not open repo at path: %s; %w", p, err) + } - ma, err := r.APIEndpoint() - if err != nil { - return APIInfo{}, xerrors.Errorf("could not get api endpoint: %w", err) - } + ma, err := r.APIEndpoint() + if err != nil { + return APIInfo{}, xerrors.Errorf("could not get api endpoint: %w", err) + } - token, err := r.APIToken() - if err != nil { - log.Warnf("Couldn't load CLI token, capabilities may be limited: %v", err) + token, err := r.APIToken() + if err != nil { + log.Warnf("Couldn't load CLI token, capabilities may be limited: %v", err) + } + + return APIInfo{ + Addr: ma.String(), + Token: token, + }, nil } - return APIInfo{ - Addr: ma.String(), - Token: token, - }, nil + return APIInfo{}, fmt.Errorf("could not determine API endpoint for node type: %s", t) } func GetRawAPI(ctx *cli.Context, t repo.RepoType, version string) (string, http.Header, error) { diff --git a/node/repo/fsrepo.go b/node/repo/fsrepo.go index 9323410ddd0..d3e6f4f2fc9 100644 --- a/node/repo/fsrepo.go +++ b/node/repo/fsrepo.go @@ -49,13 +49,16 @@ const ( StorageMiner Worker Wallet + Markets ) func defConfForType(t RepoType) interface{} { switch t { case FullNode: return config.DefaultFullNode() - case StorageMiner: + case StorageMiner, Markets: + // markets is a specialised miner service + // this taxonomy needs to be cleaned up return config.DefaultStorageMiner() case Worker: return &struct{}{} From 92056423c3b540f6ff7ac0ee4d1e4cd1693b143d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Thu, 29 Jul 2021 12:37:29 +0100 Subject: [PATCH 19/27] target markets API for markets commands. --- cli/auth.go | 3 ++- cli/cmd.go | 3 ++- cli/util/api.go | 20 +++++++++++++-- cmd/lotus-miner/dagstore.go | 30 ++++++++++++++++++++++ cmd/lotus-miner/main.go | 24 ++++++++++++------ cmd/lotus-miner/market.go | 40 +++++++++++++++++------------- cmd/lotus-miner/retrieval-deals.go | 18 +++++++------- 7 files changed, 101 insertions(+), 37 deletions(-) create mode 100644 cmd/lotus-miner/dagstore.go diff --git a/cli/auth.go b/cli/auth.go index ccf5dcc8bf8..88cbdbb66e1 100644 --- a/cli/auth.go +++ b/cli/auth.go @@ -113,7 +113,7 @@ var AuthApiInfoToken = &cli.Command{ ti, ok := cctx.App.Metadata["repoType"] if !ok { - log.Errorf("unknown repo type, are you sure you want to use GetAPI?") + log.Errorf("unknown repo type, are you sure you want to use GetCommonAPI?") ti = repo.FullNode } t, ok := ti.(repo.RepoType) @@ -128,6 +128,7 @@ var AuthApiInfoToken = &cli.Command{ // TODO: Log in audit log when it is implemented + // WARN: this is unable to tell currentEnv, _ := cliutil.EnvsForRepo(t) fmt.Printf("%s=%s:%s\n", currentEnv, string(token), ainfo.Addr) return nil diff --git a/cli/cmd.go b/cli/cmd.go index 630aae1bc75..7e4a7636c36 100644 --- a/cli/cmd.go +++ b/cli/cmd.go @@ -44,7 +44,7 @@ func GetFullNodeServices(ctx *cli.Context) (ServicesAPI, error) { var GetAPIInfo = cliutil.GetAPIInfo var GetRawAPI = cliutil.GetRawAPI -var GetAPI = cliutil.GetAPI +var GetAPI = cliutil.GetCommonAPI var DaemonContext = cliutil.DaemonContext var ReqContext = cliutil.ReqContext @@ -54,6 +54,7 @@ var GetFullNodeAPIV1 = cliutil.GetFullNodeAPIV1 var GetGatewayAPI = cliutil.GetGatewayAPI var GetStorageMinerAPI = cliutil.GetStorageMinerAPI +var GetMarketsAPI = cliutil.GetMarketsAPI var GetWorkerAPI = cliutil.GetWorkerAPI var CommonCommands = []*cli.Command{ diff --git a/cli/util/api.go b/cli/util/api.go index fca667b5ac9..76289ec7cd2 100644 --- a/cli/util/api.go +++ b/cli/util/api.go @@ -175,10 +175,10 @@ func GetRawAPI(ctx *cli.Context, t repo.RepoType, version string) (string, http. return addr, ainfo.AuthHeader(), nil } -func GetAPI(ctx *cli.Context) (api.CommonNet, jsonrpc.ClientCloser, error) { +func GetCommonAPI(ctx *cli.Context) (api.CommonNet, jsonrpc.ClientCloser, error) { ti, ok := ctx.App.Metadata["repoType"] if !ok { - log.Errorf("unknown repo type, are you sure you want to use GetAPI?") + log.Errorf("unknown repo type, are you sure you want to use GetCommonAPI?") ti = repo.FullNode } t, ok := ti.(repo.RepoType) @@ -296,6 +296,22 @@ func GetWorkerAPI(ctx *cli.Context) (api.Worker, jsonrpc.ClientCloser, error) { return client.NewWorkerRPCV0(ctx.Context, addr, headers) } +func GetMarketsAPI(ctx *cli.Context) (api.StorageMiner, jsonrpc.ClientCloser, error) { + addr, headers, err := GetRawAPI(ctx, repo.Markets, "v0") + if err != nil { + return nil, nil, err + } + + if IsVeryVerbose { + _, _ = fmt.Fprintln(ctx.App.Writer, "using markets API v0 endpoint:", addr) + } + + // the markets node is a specialised miner's node, supporting only the + // markets API, which is a subset of the miner API. All non-markets + // operations will error out with "unsupported". + return client.NewStorageMinerRPCV0(ctx.Context, addr, headers) +} + func GetGatewayAPI(ctx *cli.Context) (api.Gateway, jsonrpc.ClientCloser, error) { addr, headers, err := GetRawAPI(ctx, repo.FullNode, "v1") if err != nil { diff --git a/cmd/lotus-miner/dagstore.go b/cmd/lotus-miner/dagstore.go new file mode 100644 index 00000000000..a50d99d99bd --- /dev/null +++ b/cmd/lotus-miner/dagstore.go @@ -0,0 +1,30 @@ +package main + +import ( + "github.com/urfave/cli/v2" +) + +var dagstoreCmd = &cli.Command{ + Name: "dagstore", + Usage: "Manage the DAG store", + Subcommands: []*cli.Command{ + dagstoreListShardsCmd, + dagstoreGarbageCollectCmd, + }, +} + +var dagstoreListShardsCmd = &cli.Command{ + Name: "list-shards", + Usage: "List shards known to the DAG store", + Action: func(cctx *cli.Context) error { + return nil + }, +} + +var dagstoreGarbageCollectCmd = &cli.Command{ + Name: "gc", + Usage: "Garbage collect the DAG store", + Action: func(cctx *cli.Context) error { + return nil + }, +} diff --git a/cmd/lotus-miner/main.go b/cmd/lotus-miner/main.go index c697de0c9c0..fb8ac35b92c 100644 --- a/cmd/lotus-miner/main.go +++ b/cmd/lotus-miner/main.go @@ -76,10 +76,10 @@ func main() { } app := &cli.App{ - Name: "lotus-miner", - Usage: "Filecoin decentralized storage network miner", - Version: build.UserVersion(), - EnableBashCompletion: true, + Name: "lotus-miner", + Usage: "Filecoin decentralized storage network miner", + Version: build.UserVersion(), + Commands: append(local, lcli.CommonCommands...), Flags: []cli.Flag{ &cli.StringFlag{ Name: "actor", @@ -106,14 +106,24 @@ func main() { Value: "~/.lotusminer", // TODO: Consider XDG_DATA_HOME Usage: fmt.Sprintf("Specify miner repo path. flag(%s) and env(LOTUS_STORAGE_PATH) are DEPRECATION, will REMOVE SOON", FlagMinerRepoDeprecation), }, + &cli.BoolFlag{ + Name: "call-on-markets", + Usage: "(experimental; may be removed) call this command against a markets node; use only with common commands like net, auth, pprof, etc. whose target may be ambiguous", + }, cliutil.FlagVeryVerbose, }, - - Commands: append(local, lcli.CommonCommands...), + EnableBashCompletion: true, + Before: func(c *cli.Context) error { + // this command is explicitly called on markets, inform + // common commands by overriding the repoType. + if c.Bool("call-on-markets") { + c.App.Metadata["repoType"] = repo.Markets + } + return nil + }, } app.Setup() app.Metadata["repoType"] = repo.StorageMiner - lcli.RunApp(app) } diff --git a/cmd/lotus-miner/market.go b/cmd/lotus-miner/market.go index b216d24fcd4..a9d1f2f4658 100644 --- a/cmd/lotus-miner/market.go +++ b/cmd/lotus-miner/market.go @@ -73,7 +73,7 @@ var storageDealSelectionShowCmd = &cli.Command{ Name: "list", Usage: "List storage deal proposal selection criteria", Action: func(cctx *cli.Context) error { - smapi, closer, err := lcli.GetStorageMinerAPI(cctx) + smapi, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } @@ -100,7 +100,7 @@ var storageDealSelectionResetCmd = &cli.Command{ Name: "reset", Usage: "Reset storage deal proposal selection criteria to default values", Action: func(cctx *cli.Context) error { - smapi, closer, err := lcli.GetStorageMinerAPI(cctx) + smapi, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } @@ -148,7 +148,7 @@ var storageDealSelectionRejectCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - smapi, closer, err := lcli.GetStorageMinerAPI(cctx) + smapi, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } @@ -215,7 +215,13 @@ var setAskCmd = &cli.Command{ Action: func(cctx *cli.Context) error { ctx := lcli.DaemonContext(cctx) - api, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) + if err != nil { + return err + } + defer closer() + + marketsApi, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } @@ -252,12 +258,12 @@ var setAskCmd = &cli.Command{ return xerrors.Errorf("cannot parse max-piece-size to quantity of bytes: %w", err) } - maddr, err := api.ActorAddress(ctx) + maddr, err := minerApi.ActorAddress(ctx) if err != nil { return err } - ssize, err := api.ActorSectorSize(ctx, maddr) + ssize, err := minerApi.ActorSectorSize(ctx, maddr) if err != nil { return err } @@ -272,7 +278,7 @@ var setAskCmd = &cli.Command{ return xerrors.Errorf("max piece size (w/bit-padding) %s cannot exceed miner sector size %s", types.SizeStr(types.NewInt(uint64(max))), types.SizeStr(types.NewInt(uint64(smax)))) } - return api.MarketSetAsk(ctx, types.BigInt(pri), types.BigInt(vpri), abi.ChainEpoch(qty), abi.PaddedPieceSize(min), abi.PaddedPieceSize(max)) + return marketsApi.MarketSetAsk(ctx, types.BigInt(pri), types.BigInt(vpri), abi.ChainEpoch(qty), abi.PaddedPieceSize(min), abi.PaddedPieceSize(max)) }, } @@ -289,7 +295,7 @@ var getAskCmd = &cli.Command{ } defer closer() - smapi, closer, err := lcli.GetStorageMinerAPI(cctx) + smapi, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } @@ -352,7 +358,7 @@ var dealsImportDataCmd = &cli.Command{ Usage: "Manually import data for a deal", ArgsUsage: " ", Action: func(cctx *cli.Context) error { - api, closer, err := lcli.GetStorageMinerAPI(cctx) + api, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } @@ -390,7 +396,7 @@ var dealsListCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - api, closer, err := lcli.GetStorageMinerAPI(cctx) + api, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } @@ -494,7 +500,7 @@ var getBlocklistCmd = &cli.Command{ &CidBaseFlag, }, Action: func(cctx *cli.Context) error { - api, closer, err := lcli.GetStorageMinerAPI(cctx) + api, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } @@ -524,7 +530,7 @@ var setBlocklistCmd = &cli.Command{ ArgsUsage: "[ (optional, will read from stdin if omitted)]", Flags: []cli.Flag{}, Action: func(cctx *cli.Context) error { - api, closer, err := lcli.GetStorageMinerAPI(cctx) + api, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } @@ -570,7 +576,7 @@ var resetBlocklistCmd = &cli.Command{ Usage: "Remove all entries from the miner's piece CID blocklist", Flags: []cli.Flag{}, Action: func(cctx *cli.Context) error { - api, closer, err := lcli.GetStorageMinerAPI(cctx) + api, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } @@ -634,7 +640,7 @@ var marketRestartTransfer = &cli.Command{ if !cctx.Args().Present() { return cli.ShowCommandHelp(cctx, cctx.Command.Name) } - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + nodeApi, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } @@ -699,7 +705,7 @@ var marketCancelTransfer = &cli.Command{ if !cctx.Args().Present() { return cli.ShowCommandHelp(cctx, cctx.Command.Name) } - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + nodeApi, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } @@ -775,7 +781,7 @@ var transfersListCmd = &cli.Command{ color.NoColor = !cctx.Bool("color") } - api, closer, err := lcli.GetStorageMinerAPI(cctx) + api, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } @@ -842,7 +848,7 @@ var dealsPendingPublish = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - api, closer, err := lcli.GetStorageMinerAPI(cctx) + api, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } diff --git a/cmd/lotus-miner/retrieval-deals.go b/cmd/lotus-miner/retrieval-deals.go index 0411f7f130a..1ce1f6593df 100644 --- a/cmd/lotus-miner/retrieval-deals.go +++ b/cmd/lotus-miner/retrieval-deals.go @@ -39,7 +39,7 @@ var retrievalDealSelectionShowCmd = &cli.Command{ Name: "list", Usage: "List retrieval deal proposal selection criteria", Action: func(cctx *cli.Context) error { - smapi, closer, err := lcli.GetStorageMinerAPI(cctx) + smapi, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } @@ -66,7 +66,7 @@ var retrievalDealSelectionResetCmd = &cli.Command{ Name: "reset", Usage: "Reset retrieval deal proposal selection criteria to default values", Action: func(cctx *cli.Context) error { - smapi, closer, err := lcli.GetStorageMinerAPI(cctx) + smapi, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } @@ -98,7 +98,7 @@ var retrievalDealSelectionRejectCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - smapi, closer, err := lcli.GetStorageMinerAPI(cctx) + smapi, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } @@ -126,7 +126,7 @@ var retrievalDealsListCmd = &cli.Command{ Name: "list", Usage: "List all active retrieval deals for this miner", Action: func(cctx *cli.Context) error { - api, closer, err := lcli.GetStorageMinerAPI(cctx) + api, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } @@ -186,7 +186,7 @@ var retrievalSetAskCmd = &cli.Command{ Action: func(cctx *cli.Context) error { ctx := lcli.DaemonContext(cctx) - api, closer, err := lcli.GetStorageMinerAPI(cctx) + api, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } @@ -240,7 +240,7 @@ var retrievalGetAskCmd = &cli.Command{ Action: func(cctx *cli.Context) error { ctx := lcli.DaemonContext(cctx) - api, closer, err := lcli.GetStorageMinerAPI(cctx) + api, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } @@ -252,13 +252,13 @@ var retrievalGetAskCmd = &cli.Command{ } w := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0) - fmt.Fprintf(w, "Price per Byte\tUnseal Price\tPayment Interval\tPayment Interval Increase\n") + _, _ = fmt.Fprintf(w, "Price per Byte\tUnseal Price\tPayment Interval\tPayment Interval Increase\n") if ask == nil { - fmt.Fprintf(w, "\n") + _, _ = fmt.Fprintf(w, "\n") return w.Flush() } - fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", + _, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", types.FIL(ask.PricePerByte), types.FIL(ask.UnsealPrice), units.BytesSize(float64(ask.PaymentInterval)), From d37ae957e02259e33b4774695ebeb25616cfff6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Thu, 29 Jul 2021 12:51:26 +0100 Subject: [PATCH 20/27] refactor miner info command. --- cli/auth.go | 1 - cmd/lotus-miner/dagstore.go | 30 ------------------------------ cmd/lotus-miner/info.go | 35 ++++++++++++++++++++++------------- 3 files changed, 22 insertions(+), 44 deletions(-) delete mode 100644 cmd/lotus-miner/dagstore.go diff --git a/cli/auth.go b/cli/auth.go index 88cbdbb66e1..33b0d25c880 100644 --- a/cli/auth.go +++ b/cli/auth.go @@ -128,7 +128,6 @@ var AuthApiInfoToken = &cli.Command{ // TODO: Log in audit log when it is implemented - // WARN: this is unable to tell currentEnv, _ := cliutil.EnvsForRepo(t) fmt.Printf("%s=%s:%s\n", currentEnv, string(token), ainfo.Addr) return nil diff --git a/cmd/lotus-miner/dagstore.go b/cmd/lotus-miner/dagstore.go deleted file mode 100644 index a50d99d99bd..00000000000 --- a/cmd/lotus-miner/dagstore.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import ( - "github.com/urfave/cli/v2" -) - -var dagstoreCmd = &cli.Command{ - Name: "dagstore", - Usage: "Manage the DAG store", - Subcommands: []*cli.Command{ - dagstoreListShardsCmd, - dagstoreGarbageCollectCmd, - }, -} - -var dagstoreListShardsCmd = &cli.Command{ - Name: "list-shards", - Usage: "List shards known to the DAG store", - Action: func(cctx *cli.Context) error { - return nil - }, -} - -var dagstoreGarbageCollectCmd = &cli.Command{ - Name: "gc", - Usage: "Garbage collect the DAG store", - Action: func(cctx *cli.Context) error { - return nil - }, -} diff --git a/cmd/lotus-miner/info.go b/cmd/lotus-miner/info.go index 878361dacb6..67f8180b725 100644 --- a/cmd/lotus-miner/info.go +++ b/cmd/lotus-miner/info.go @@ -50,7 +50,13 @@ var infoCmd = &cli.Command{ } func infoCmdAct(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) + if err != nil { + return err + } + defer closer() + + marketsApi, closer, err := lcli.GetMarketsAPI(cctx) if err != nil { return err } @@ -64,12 +70,19 @@ func infoCmdAct(cctx *cli.Context) error { ctx := lcli.ReqContext(cctx) - subsystems, err := nodeApi.RuntimeSubsystems(ctx) + subsystems, err := minerApi.RuntimeSubsystems(ctx) + if err != nil { + return err + } + + fmt.Println("Enabled subsystems (from miner API):", subsystems) + + subsystems, err = marketsApi.RuntimeSubsystems(ctx) if err != nil { return err } - fmt.Println("Enabled subsystems:", subsystems) + fmt.Println("Enabled subsystems (from markets API):", subsystems) fmt.Print("Chain: ") @@ -103,18 +116,14 @@ func infoCmdAct(cctx *cli.Context) error { fmt.Println() - if subsystems.Has(api.SubsystemSectorStorage) { - err := handleMiningInfo(ctx, cctx, fullapi, nodeApi) - if err != nil { - return err - } + err = handleMiningInfo(ctx, cctx, fullapi, minerApi) + if err != nil { + return err } - if subsystems.Has(api.SubsystemMarkets) { - err := handleMarketsInfo(ctx, nodeApi) - if err != nil { - return err - } + err = handleMarketsInfo(ctx, marketsApi) + if err != nil { + return err } return nil From 107777fdacb8f914d0dc627b9d5b70a86ddde181 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Thu, 29 Jul 2021 12:54:23 +0100 Subject: [PATCH 21/27] polish. --- cli/util/api.go | 6 +++--- cmd/lotus-miner/main.go | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cli/util/api.go b/cli/util/api.go index 76289ec7cd2..4ddaac88a22 100644 --- a/cli/util/api.go +++ b/cli/util/api.go @@ -27,9 +27,9 @@ const ( metadataTraceContext = "traceContext" ) -// The flags passed on the command line with the listen address of the API -// server (only used by the tests), in the order of precedence they should be -// applied for the requested kind of node. +// flagsForAPI returns flags passed on the command line with the listen address +// of the API server (only used by the tests), in the order of precedence they +// should be applied for the requested kind of node. func flagsForAPI(t repo.RepoType) []string { switch t { case repo.FullNode: diff --git a/cmd/lotus-miner/main.go b/cmd/lotus-miner/main.go index fb8ac35b92c..ea6fca0a18f 100644 --- a/cmd/lotus-miner/main.go +++ b/cmd/lotus-miner/main.go @@ -76,10 +76,10 @@ func main() { } app := &cli.App{ - Name: "lotus-miner", - Usage: "Filecoin decentralized storage network miner", - Version: build.UserVersion(), - Commands: append(local, lcli.CommonCommands...), + Name: "lotus-miner", + Usage: "Filecoin decentralized storage network miner", + Version: build.UserVersion(), + EnableBashCompletion: true, Flags: []cli.Flag{ &cli.StringFlag{ Name: "actor", @@ -112,7 +112,7 @@ func main() { }, cliutil.FlagVeryVerbose, }, - EnableBashCompletion: true, + Commands: append(local, lcli.CommonCommands...), Before: func(c *cli.Context) error { // this command is explicitly called on markets, inform // common commands by overriding the repoType. From 1b5a2dd79a20e484fdb222a43b68c5480801592a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Thu, 29 Jul 2021 13:21:55 +0100 Subject: [PATCH 22/27] fix tests. --- cli/util/api.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cli/util/api.go b/cli/util/api.go index 4ddaac88a22..4413fd3bb86 100644 --- a/cli/util/api.go +++ b/cli/util/api.go @@ -117,7 +117,7 @@ func GetAPIInfo(ctx *cli.Context, t repo.RepoType) (APIInfo, error) { for _, env := range deprecatedEnv { env, ok := os.LookupEnv(env) if ok { - log.Warnf("Use deprecation env(%s) value, please use env(%s) instead.", env, currentEnv) + log.Warnf("Using deprecated env(%s) value, please use env(%s) instead.", env, currentEnv) return ParseApiInfo(env), nil } } @@ -125,6 +125,7 @@ func GetAPIInfo(ctx *cli.Context, t repo.RepoType) (APIInfo, error) { repoFlags := flagsForRepo(t) for _, f := range repoFlags { if !ctx.IsSet(f) { + fmt.Println("not set", f) continue } @@ -154,7 +155,7 @@ func GetAPIInfo(ctx *cli.Context, t repo.RepoType) (APIInfo, error) { }, nil } - return APIInfo{}, fmt.Errorf("could not determine API endpoint for node type: %s", t) + return APIInfo{}, fmt.Errorf("could not determine API endpoint for node type: %v", t) } func GetRawAPI(ctx *cli.Context, t repo.RepoType, version string) (string, http.Header, error) { @@ -297,6 +298,11 @@ func GetWorkerAPI(ctx *cli.Context) (api.Worker, jsonrpc.ClientCloser, error) { } func GetMarketsAPI(ctx *cli.Context) (api.StorageMiner, jsonrpc.ClientCloser, error) { + // to support lotus-miner cli tests. + if tn, ok := ctx.App.Metadata["testnode-storage"]; ok { + return tn.(api.StorageMiner), func() {}, nil + } + addr, headers, err := GetRawAPI(ctx, repo.Markets, "v0") if err != nil { return nil, nil, err From 299b106f38beb76d94669f42dc4b90b97781059b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Thu, 29 Jul 2021 13:38:08 +0100 Subject: [PATCH 23/27] fix docs, add flag. --- cli/util/api.go | 1 - cmd/lotus-miner/main.go | 6 ++++++ documentation/en/cli-lotus-miner.md | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/cli/util/api.go b/cli/util/api.go index 4413fd3bb86..c6e3ae9be2f 100644 --- a/cli/util/api.go +++ b/cli/util/api.go @@ -125,7 +125,6 @@ func GetAPIInfo(ctx *cli.Context, t repo.RepoType) (APIInfo, error) { repoFlags := flagsForRepo(t) for _, f := range repoFlags { if !ctx.IsSet(f) { - fmt.Println("not set", f) continue } diff --git a/cmd/lotus-miner/main.go b/cmd/lotus-miner/main.go index ea6fca0a18f..d1c203b573d 100644 --- a/cmd/lotus-miner/main.go +++ b/cmd/lotus-miner/main.go @@ -23,6 +23,7 @@ import ( var log = logging.Logger("main") const FlagMinerRepo = "miner-repo" +const FlagMarketsRepo = "markets-repo" // TODO remove after deprecation period const FlagMinerRepoDeprecation = "storagerepo" @@ -106,6 +107,11 @@ func main() { Value: "~/.lotusminer", // TODO: Consider XDG_DATA_HOME Usage: fmt.Sprintf("Specify miner repo path. flag(%s) and env(LOTUS_STORAGE_PATH) are DEPRECATION, will REMOVE SOON", FlagMinerRepoDeprecation), }, + &cli.StringFlag{ + Name: FlagMarketsRepo, + EnvVars: []string{"LOTUS_MARKETS_PATH"}, + Usage: fmt.Sprintf("Markets repo path"), + }, &cli.BoolFlag{ Name: "call-on-markets", Usage: "(experimental; may be removed) call this command against a markets node; use only with common commands like net, auth, pprof, etc. whose target may be ambiguous", diff --git a/documentation/en/cli-lotus-miner.md b/documentation/en/cli-lotus-miner.md index 863ca8dc93a..775f4e9a77f 100644 --- a/documentation/en/cli-lotus-miner.md +++ b/documentation/en/cli-lotus-miner.md @@ -43,6 +43,7 @@ GLOBAL OPTIONS: --actor value, -a value specify other actor to check state for (read only) --color use color in display output (default: depends on output being a TTY) --miner-repo value, --storagerepo value Specify miner repo path. flag(storagerepo) and env(LOTUS_STORAGE_PATH) are DEPRECATION, will REMOVE SOON (default: "~/.lotusminer") [$LOTUS_MINER_PATH, $LOTUS_STORAGE_PATH] + --call-on-markets (experimental; may be removed) call this command against a markets node; use only with common commands like net, auth, pprof, etc. whose target may be ambiguous (default: false) --vv enables very verbose mode, useful for debugging the CLI (default: false) --help, -h show help (default: false) --version, -v print the version (default: false) From b3c951c924071936c3ad2013f551753781c77413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Thu, 29 Jul 2021 13:49:47 +0100 Subject: [PATCH 24/27] add RepoType#String; adjust repo parsing logic. --- cli/util/api.go | 4 +++- node/repo/fsrepo.go | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/cli/util/api.go b/cli/util/api.go index c6e3ae9be2f..bf34f4421d7 100644 --- a/cli/util/api.go +++ b/cli/util/api.go @@ -124,7 +124,9 @@ func GetAPIInfo(ctx *cli.Context, t repo.RepoType) (APIInfo, error) { repoFlags := flagsForRepo(t) for _, f := range repoFlags { - if !ctx.IsSet(f) { + // cannot use ctx.IsSet because it ignores default values + f := ctx.String(f) + if f == "" { continue } diff --git a/node/repo/fsrepo.go b/node/repo/fsrepo.go index d3e6f4f2fc9..5c1c91bc559 100644 --- a/node/repo/fsrepo.go +++ b/node/repo/fsrepo.go @@ -52,6 +52,21 @@ const ( Markets ) +func (t RepoType) String() string { + s := [...]string{ + "__invalid__", + "FullNode", + "StorageMiner", + "Worker", + "Wallet", + "Markets", + } + if t < 0 || int(t) > len(s) { + return "__invalid__" + } + return s[t] +} + func defConfForType(t RepoType) interface{} { switch t { case FullNode: From 4b6fa79ea205a2847f583d8ca0c9d005f1c7aa8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Thu, 29 Jul 2021 14:36:04 +0100 Subject: [PATCH 25/27] bugfix. --- cli/util/api.go | 8 ++++---- cmd/lotus-miner/info.go | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cli/util/api.go b/cli/util/api.go index bf34f4421d7..766969dfabf 100644 --- a/cli/util/api.go +++ b/cli/util/api.go @@ -78,7 +78,7 @@ func EnvsForRepo(t repo.RepoType) (current []string, deprecated []string) { return []string{"WORKER_API_INFO"}, nil case repo.Markets: // support split markets-miner and monolith deployments. - return []string{"MARKETS_API_INFO, MINER_API_INFO"}, nil + return []string{"MARKETS_API_INFO", "MINER_API_INFO"}, nil default: panic(fmt.Sprintf("Unknown repo type: %v", t)) } @@ -125,12 +125,12 @@ func GetAPIInfo(ctx *cli.Context, t repo.RepoType) (APIInfo, error) { repoFlags := flagsForRepo(t) for _, f := range repoFlags { // cannot use ctx.IsSet because it ignores default values - f := ctx.String(f) - if f == "" { + path := ctx.String(f) + if path == "" { continue } - p, err := homedir.Expand(ctx.String(f)) + p, err := homedir.Expand(path) if err != nil { return APIInfo{}, xerrors.Errorf("could not expand home dir (%s): %w", f, err) } diff --git a/cmd/lotus-miner/info.go b/cmd/lotus-miner/info.go index 67f8180b725..f37952057ff 100644 --- a/cmd/lotus-miner/info.go +++ b/cmd/lotus-miner/info.go @@ -386,6 +386,7 @@ func handleMarketsInfo(ctx context.Context, nodeApi api.StorageMiner) error { return sorted[i].status > sorted[j].status }) + fmt.Println() fmt.Printf("Storage Deals: %d, %s\n", total.count, types.SizeStr(types.NewInt(total.bytes))) tw := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0) From 00c3432e5a646640398f1ba316b56e7578ac877b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Thu, 29 Jul 2021 16:10:04 +0100 Subject: [PATCH 26/27] use fallback api infos last; init service with markets-path. --- cli/auth.go | 2 +- cli/util/api.go | 37 ++++++++++++++++++----------- cmd/lotus-miner/init_restore.go | 11 +++++---- cmd/lotus-miner/init_service.go | 7 +++++- cmd/lotus-miner/main.go | 6 +++-- documentation/en/cli-lotus-miner.md | 1 + 6 files changed, 41 insertions(+), 23 deletions(-) diff --git a/cli/auth.go b/cli/auth.go index 33b0d25c880..342d5e3aa36 100644 --- a/cli/auth.go +++ b/cli/auth.go @@ -128,7 +128,7 @@ var AuthApiInfoToken = &cli.Command{ // TODO: Log in audit log when it is implemented - currentEnv, _ := cliutil.EnvsForRepo(t) + currentEnv, _ := cliutil.EnvsForAPIInfos(t) fmt.Printf("%s=%s:%s\n", currentEnv, string(token), ainfo.Addr) return nil }, diff --git a/cli/util/api.go b/cli/util/api.go index 766969dfabf..37df41a87ab 100644 --- a/cli/util/api.go +++ b/cli/util/api.go @@ -62,23 +62,23 @@ func flagsForRepo(t repo.RepoType) []string { } } -// EnvsForRepo returns the environment variables to use in order of precedence +// EnvsForAPIInfos returns the environment variables to use in order of precedence // to determine the API endpoint of the specified node type. // // It returns the current variables and deprecated ones separately, so that // the user can log a warning when deprecated ones are found to be in use. -func EnvsForRepo(t repo.RepoType) (current []string, deprecated []string) { +func EnvsForAPIInfos(t repo.RepoType) (primary string, fallbacks []string, deprecated []string) { switch t { case repo.FullNode: - return []string{"FULLNODE_API_INFO"}, nil + return "FULLNODE_API_INFO", nil, nil case repo.StorageMiner: // TODO remove deprecated deprecation period - return []string{"MINER_API_INFO"}, []string{"STORAGE_API_INFO"} + return "MINER_API_INFO", nil, []string{"STORAGE_API_INFO"} case repo.Worker: - return []string{"WORKER_API_INFO"}, nil + return "WORKER_API_INFO", nil, nil case repo.Markets: // support split markets-miner and monolith deployments. - return []string{"MARKETS_API_INFO", "MINER_API_INFO"}, nil + return "MARKETS_API_INFO", []string{"MINER_API_INFO"}, nil default: panic(fmt.Sprintf("Unknown repo type: %v", t)) } @@ -106,18 +106,20 @@ func GetAPIInfo(ctx *cli.Context, t repo.RepoType) (APIInfo, error) { return APIInfo{Addr: strma}, nil } - currentEnv, deprecatedEnv := EnvsForRepo(t) - for _, env := range currentEnv { - env, ok := os.LookupEnv(env) - if ok { - return ParseApiInfo(env), nil - } + // + // Note: it is not correct/intuitive to prefer environment variables over + // CLI flags (repo flags below). + // + primaryEnv, fallbacksEnvs, deprecatedEnvs := EnvsForAPIInfos(t) + env, ok := os.LookupEnv(primaryEnv) + if ok { + return ParseApiInfo(env), nil } - for _, env := range deprecatedEnv { + for _, env := range deprecatedEnvs { env, ok := os.LookupEnv(env) if ok { - log.Warnf("Using deprecated env(%s) value, please use env(%s) instead.", env, currentEnv) + log.Warnf("Using deprecated env(%s) value, please use env(%s) instead.", env, primaryEnv) return ParseApiInfo(env), nil } } @@ -156,6 +158,13 @@ func GetAPIInfo(ctx *cli.Context, t repo.RepoType) (APIInfo, error) { }, nil } + for _, env := range fallbacksEnvs { + env, ok := os.LookupEnv(env) + if ok { + return ParseApiInfo(env), nil + } + } + return APIInfo{}, fmt.Errorf("could not determine API endpoint for node type: %v", t) } diff --git a/cmd/lotus-miner/init_restore.go b/cmd/lotus-miner/init_restore.go index 3b4e2b26d2e..393b44dd2d3 100644 --- a/cmd/lotus-miner/init_restore.go +++ b/cmd/lotus-miner/init_restore.go @@ -17,7 +17,7 @@ import ( "gopkg.in/cheggaaa/pb.v1" "github.com/filecoin-project/go-address" - paramfetch "github.com/filecoin-project/go-paramfetch" + "github.com/filecoin-project/go-paramfetch" "github.com/filecoin-project/go-state-types/big" lapi "github.com/filecoin-project/lotus/api" @@ -72,7 +72,9 @@ var restoreCmd = &cli.Command{ } } - if err := restore(ctx, cctx, storageCfg, nil, func(api lapi.FullNode, maddr address.Address, peerid peer.ID, mi miner.MinerInfo) error { + repoPath := cctx.String(FlagMinerRepo) + + if err := restore(ctx, cctx, repoPath, storageCfg, nil, func(api lapi.FullNode, maddr address.Address, peerid peer.ID, mi miner.MinerInfo) error { log.Info("Checking proof parameters") if err := paramfetch.GetParams(ctx, build.ParametersJSON(), build.SrsJSON(), uint64(mi.SectorSize)); err != nil { @@ -94,7 +96,7 @@ var restoreCmd = &cli.Command{ }, } -func restore(ctx context.Context, cctx *cli.Context, strConfig *stores.StorageConfig, manageConfig func(*config.StorageMiner) error, after func(api lapi.FullNode, addr address.Address, peerid peer.ID, mi miner.MinerInfo) error) error { +func restore(ctx context.Context, cctx *cli.Context, targetPath string, strConfig *stores.StorageConfig, manageConfig func(*config.StorageMiner) error, after func(api lapi.FullNode, addr address.Address, peerid peer.ID, mi miner.MinerInfo) error) error { if cctx.Args().Len() != 1 { return xerrors.Errorf("expected 1 argument") } @@ -142,8 +144,7 @@ func restore(ctx context.Context, cctx *cli.Context, strConfig *stores.StorageCo log.Info("Checking if repo exists") - repoPath := cctx.String(FlagMinerRepo) - r, err := repo.NewFS(repoPath) + r, err := repo.NewFS(targetPath) if err != nil { return err } diff --git a/cmd/lotus-miner/init_service.go b/cmd/lotus-miner/init_service.go index ad803a83040..6e874023e44 100644 --- a/cmd/lotus-miner/init_service.go +++ b/cmd/lotus-miner/init_service.go @@ -71,7 +71,12 @@ var serviceCmd = &cli.Command{ return xerrors.Errorf("--api-sector-index is required without the sector storage module enabled") } - if err := restore(ctx, cctx, &stores.StorageConfig{}, func(cfg *config.StorageMiner) error { + repoPath := cctx.String(FlagMarketsRepo) + if repoPath == "" { + return xerrors.Errorf("please provide Lotus markets repo path via flag %s", FlagMarketsRepo) + } + + if err := restore(ctx, cctx, repoPath, &stores.StorageConfig{}, func(cfg *config.StorageMiner) error { cfg.Subsystems.EnableMarkets = es.Contains(MarketsService) cfg.Subsystems.EnableMining = false cfg.Subsystems.EnableSealing = false diff --git a/cmd/lotus-miner/main.go b/cmd/lotus-miner/main.go index d1c203b573d..2916fce1fc0 100644 --- a/cmd/lotus-miner/main.go +++ b/cmd/lotus-miner/main.go @@ -22,8 +22,10 @@ import ( var log = logging.Logger("main") -const FlagMinerRepo = "miner-repo" -const FlagMarketsRepo = "markets-repo" +const ( + FlagMinerRepo = "miner-repo" + FlagMarketsRepo = "markets-repo" +) // TODO remove after deprecation period const FlagMinerRepoDeprecation = "storagerepo" diff --git a/documentation/en/cli-lotus-miner.md b/documentation/en/cli-lotus-miner.md index 775f4e9a77f..bd87774bcab 100644 --- a/documentation/en/cli-lotus-miner.md +++ b/documentation/en/cli-lotus-miner.md @@ -43,6 +43,7 @@ GLOBAL OPTIONS: --actor value, -a value specify other actor to check state for (read only) --color use color in display output (default: depends on output being a TTY) --miner-repo value, --storagerepo value Specify miner repo path. flag(storagerepo) and env(LOTUS_STORAGE_PATH) are DEPRECATION, will REMOVE SOON (default: "~/.lotusminer") [$LOTUS_MINER_PATH, $LOTUS_STORAGE_PATH] + --markets-repo value Markets repo path [$LOTUS_MARKETS_PATH] --call-on-markets (experimental; may be removed) call this command against a markets node; use only with common commands like net, auth, pprof, etc. whose target may be ambiguous (default: false) --vv enables very verbose mode, useful for debugging the CLI (default: false) --help, -h show help (default: false) From bb040abb2c929630e980bbcc418330832285ca64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Thu, 29 Jul 2021 16:18:43 +0100 Subject: [PATCH 27/27] fix compilation error. --- cli/auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/auth.go b/cli/auth.go index 342d5e3aa36..286eb978bbf 100644 --- a/cli/auth.go +++ b/cli/auth.go @@ -128,7 +128,7 @@ var AuthApiInfoToken = &cli.Command{ // TODO: Log in audit log when it is implemented - currentEnv, _ := cliutil.EnvsForAPIInfos(t) + currentEnv, _, _ := cliutil.EnvsForAPIInfos(t) fmt.Printf("%s=%s:%s\n", currentEnv, string(token), ainfo.Addr) return nil },