From 21415a98472f3e7f1f5349b1ca3976096aaa2614 Mon Sep 17 00:00:00 2001 From: Mike Greenberg Date: Thu, 28 May 2020 16:48:06 -0400 Subject: [PATCH 01/96] add chainwatch systemd svc config; make install-chainwatch-service --- Makefile | 8 ++++++++ scripts/chainwatch.service | 12 ++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 scripts/chainwatch.service diff --git a/Makefile b/Makefile index 92772ab19..fdf9e7e09 100644 --- a/Makefile +++ b/Makefile @@ -114,6 +114,7 @@ install-services: install clean-services: rm -f /usr/local/lib/systemd/system/lotus-daemon.service rm -f /usr/local/lib/systemd/system/lotus-miner.service + rm -f /usr/local/lib/systemd/system/chainwatch.service systemctl daemon-reload # TOOLS @@ -160,6 +161,13 @@ chainwatch: .PHONY: chainwatch BINS+=chainwatch +install-chainwatch-service: chainwatch + install -C ./chainwatch /usr/local/bin/chainwatch + install -C -m 0644 ./scripts/chainwatch.service /usr/local/lib/systemd/system/chainwatch.service + systemctl daemon-reload + @echo + @echo "chainwatch installed. Don't forget to 'systemctl enable chainwatch' for it to be enabled on startup." + bench: rm -f bench go build -o bench ./cmd/lotus-bench diff --git a/scripts/chainwatch.service b/scripts/chainwatch.service new file mode 100644 index 000000000..47af6b59e --- /dev/null +++ b/scripts/chainwatch.service @@ -0,0 +1,12 @@ +[Unit] +Description=Chainwatch +After=lotus-daemon.service +Requires=lotus-daemon.service + +[Service] +ExecStart=/usr/local/bin/chainwatch run +Environment=LOTUS_DB="postgres://postgres:password@localhost:5432/postgres?sslmode=disable" +Environment=LOTUS_PATH="/root/.lotus" + +[Install] +WantedBy=multiuser.target From c0cdcbb9e0b51b16a0db5832c36df9ba713d54df Mon Sep 17 00:00:00 2001 From: Mike Greenberg Date: Thu, 4 Jun 2020 08:09:01 -0400 Subject: [PATCH 02/96] Adjust service logging and startup settings --- Makefile | 3 ++- scripts/chainwatch.service | 5 ++++- scripts/lotus-daemon.service | 6 +++--- scripts/lotus-miner.service | 3 ++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index fdf9e7e09..5db557d7e 100644 --- a/Makefile +++ b/Makefile @@ -105,11 +105,12 @@ install: install-services: install mkdir -p /usr/local/lib/systemd/system + mkdir -p /var/log/lotus install -C -m 0644 ./scripts/lotus-daemon.service /usr/local/lib/systemd/system/lotus-daemon.service install -C -m 0644 ./scripts/lotus-miner.service /usr/local/lib/systemd/system/lotus-miner.service systemctl daemon-reload @echo - @echo "lotus and lotus-miner services installed. Don't forget to 'systemctl enable lotus|lotus-miner' for it to be enabled on startup." + @echo "lotus-daemon and lotus-miner services installed. Don't forget to 'systemctl enable lotus-daemon|lotus-miner' for it to be enabled on startup." clean-services: rm -f /usr/local/lib/systemd/system/lotus-daemon.service diff --git a/scripts/chainwatch.service b/scripts/chainwatch.service index 47af6b59e..e958ec857 100644 --- a/scripts/chainwatch.service +++ b/scripts/chainwatch.service @@ -1,12 +1,15 @@ [Unit] Description=Chainwatch +PartOf=sentinel.service After=lotus-daemon.service Requires=lotus-daemon.service [Service] -ExecStart=/usr/local/bin/chainwatch run +Environment=GOLOG_FILE="/var/log/lotus/chainwatch.log" +Environment=GOLOG_LOG_FMT="json" Environment=LOTUS_DB="postgres://postgres:password@localhost:5432/postgres?sslmode=disable" Environment=LOTUS_PATH="/root/.lotus" +ExecStart=/usr/local/bin/chainwatch run [Install] WantedBy=multiuser.target diff --git a/scripts/lotus-daemon.service b/scripts/lotus-daemon.service index 46bc47bdb..99a94e217 100644 --- a/scripts/lotus-daemon.service +++ b/scripts/lotus-daemon.service @@ -1,15 +1,15 @@ [Unit] Description=Lotus Daemon After=network-online.target -Wants=network-online.target +Requires=network-online.target [Service] -Environment=GOLOG_FILE="/var/log/lotus-daemon" +Environment=GOLOG_FILE="/var/log/lotus/daemon.log" Environment=GOLOG_LOG_FMT="json" ExecStart=/usr/local/bin/lotus daemon #ExecStop=/bin/sh -a -c /root/capture_lotus_heap.sh Restart=always -RestartSec=30 +RestartSec=10 MemoryAccounting=true MemoryHigh=8G diff --git a/scripts/lotus-miner.service b/scripts/lotus-miner.service index 7b866e043..3a460450f 100644 --- a/scripts/lotus-miner.service +++ b/scripts/lotus-miner.service @@ -2,10 +2,11 @@ Description=Lotus Storage Miner After=network.target After=lotus-daemon.service +Requires=lotus-daemon.service [Service] ExecStart=/usr/local/bin/lotus-storage-miner run -Environment=GOLOG_FILE="/var/log/lotus-miner" +Environment=GOLOG_FILE="/var/log/lotus/miner.log" Environment=GOLOG_LOG_FMT="json" [Install] From ce30162907c8d774a6b8e711bfccfa2e26b02651 Mon Sep 17 00:00:00 2001 From: Mike Greenberg Date: Thu, 4 Jun 2020 08:10:08 -0400 Subject: [PATCH 03/96] Fix documentation link for new systemd page --- documentation/en/.library.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/en/.library.json b/documentation/en/.library.json index 018e6189b..293c871ea 100644 --- a/documentation/en/.library.json +++ b/documentation/en/.library.json @@ -58,8 +58,8 @@ }, { "title": "Use Lotus with systemd", - "slug": "en+install-system-services", - "github": "en/install-system-services.md", + "slug": "en+install-systemd-services", + "github": "en/install-systemd-services.md", "value": null }, { From 01c4726fd5fca11aed50f25a8d6287e72487885d Mon Sep 17 00:00:00 2001 From: Jeromy Date: Mon, 15 Jun 2020 13:02:57 -0700 Subject: [PATCH 04/96] add some smartness to bench analyze gas output --- cmd/lotus-bench/import.go | 66 +++++++++++++++++++++++++++++++++ cmd/lotus-storage-miner/init.go | 2 +- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/cmd/lotus-bench/import.go b/cmd/lotus-bench/import.go index 11d58d9b8..8d62ac0d8 100644 --- a/cmd/lotus-bench/import.go +++ b/cmd/lotus-bench/import.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "math" "os" "runtime/pprof" "sort" @@ -162,6 +163,43 @@ type Invocation struct { Invoc *api.InvocResult } +const GasPerNs = 10 + +func countGasCosts(et *types.ExecutionTrace) (int64, int64) { + var cgas, vgas int64 + + for _, gc := range et.GasCharges { + cgas += gc.ComputeGas + vgas += gc.VirtualComputeGas + } + + for _, sub := range et.Subcalls { + c, v := countGasCosts(&sub) + cgas += c + vgas += v + } + + return cgas, vgas +} + +func compStats(vals []float64) (float64, float64) { + var sum float64 + + for _, v := range vals { + sum += v + } + + av := sum / float64(len(vals)) + + var varsum float64 + for _, v := range vals { + delta := av - v + varsum += delta * delta + } + + return av, math.Sqrt(varsum / float64(len(vals))) +} + var importAnalyzeCmd = &cli.Command{ Name: "analyze", Action: func(cctx *cli.Context) error { @@ -180,6 +218,8 @@ var importAnalyzeCmd = &cli.Command{ return err } + chargeDeltas := make(map[string][]float64) + var invocs []Invocation var totalTime time.Duration for i, r := range results { @@ -191,9 +231,35 @@ var importAnalyzeCmd = &cli.Command{ TipSet: r.TipSet, Invoc: inv, }) + + cgas, vgas := countGasCosts(&inv.ExecutionTrace) + fmt.Printf("Invocation: %d %s: %s %d -> %0.2f\n", inv.Msg.Method, inv.Msg.To, inv.Duration, cgas+vgas, float64(GasPerNs*inv.Duration.Nanoseconds())/float64(cgas+vgas)) + + for _, gc := range inv.ExecutionTrace.GasCharges { + + compGas := gc.ComputeGas + gc.VirtualComputeGas + ratio := float64(compGas) / float64(gc.TimeTaken.Nanoseconds()) + + chargeDeltas[gc.Name] = append(chargeDeltas[gc.Name], 1/(ratio/GasPerNs)) + //fmt.Printf("%s: %d, %s: %0.2f\n", gc.Name, compGas, gc.TimeTaken, 1/(ratio/GasPerNs)) + } } } + var keys []string + for k := range chargeDeltas { + keys = append(keys, k) + } + + fmt.Println("Gas Price Deltas") + sort.Strings(keys) + for _, k := range keys { + vals := chargeDeltas[k] + av, stdev := compStats(vals) + + fmt.Printf("%s: incr by %f (%f)\n", k, av, stdev) + } + sort.Slice(invocs, func(i, j int) bool { return invocs[i].Invoc.Duration > invocs[j].Invoc.Duration }) diff --git a/cmd/lotus-storage-miner/init.go b/cmd/lotus-storage-miner/init.go index 29e82cc2f..14972c69a 100644 --- a/cmd/lotus-storage-miner/init.go +++ b/cmd/lotus-storage-miner/init.go @@ -460,7 +460,7 @@ func storageMinerInit(ctx context.Context, cctx *cli.Context, api lapi.FullNode, } if cerr != nil { - return xerrors.Errorf("failed to configure storage miner: %w", err) + return xerrors.Errorf("failed to configure storage miner: %w", cerr) } } From f8c4b647828139199717ec86834c9f5603e64266 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Mon, 15 Jun 2020 16:05:29 -0700 Subject: [PATCH 05/96] improve chain import analyze output, add some rough virtual gas charges --- chain/vm/gas.go | 11 ++++++++++- chain/vm/gas_v0.go | 10 +++++----- chain/vm/syscalls.go | 4 +++- cmd/lotus-bench/import.go | 30 +++++++++++++++++++++++------- 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/chain/vm/gas.go b/chain/vm/gas.go index f1dcc933d..e6bde25bf 100644 --- a/chain/vm/gas.go +++ b/chain/vm/gas.go @@ -186,6 +186,15 @@ func (ps pricedSyscalls) VerifyConsensusFault(h1 []byte, h2 []byte, extra []byte } func (ps pricedSyscalls) BatchVerifySeals(inp map[address.Address][]abi.SealVerifyInfo) (map[address.Address][]bool, error) { - ps.chargeGas(newGasCharge("BatchVerifySeals", 0, 0)) // TODO: this is only called by the cron actor. Should we even charge gas? + var gasChargeSum GasCharge + gasChargeSum.Name = "BatchVerifySeals" + ps.chargeGas(gasChargeSum) // TODO: this is only called by the cron actor. Should we even charge gas? + + for _, svis := range inp { + for _, svi := range svis { + ch := ps.pl.OnVerifySeal(svi) + ps.chargeGas(newGasCharge("BatchVerifySingle", 0, 0).WithVirtual(ch.VirtualCompute+ch.ComputeGas, 0)) + } + } return ps.under.BatchVerifySeals(inp) } diff --git a/chain/vm/gas_v0.go b/chain/vm/gas_v0.go index 6994fd544..072c1f140 100644 --- a/chain/vm/gas_v0.go +++ b/chain/vm/gas_v0.go @@ -103,17 +103,17 @@ func (pl *pricelistV0) OnMethodInvocation(value abi.TokenAmount, methodNum abi.M if methodNum != builtin.MethodSend { ret += pl.sendInvokeMethod } - return newGasCharge("OnMethodInvocation", ret, 0) + return newGasCharge("OnMethodInvocation", ret, 0).WithVirtual(ret*15000, 0) } // OnIpldGet returns the gas used for storing an object func (pl *pricelistV0) OnIpldGet(dataSize int) GasCharge { - return newGasCharge("OnIpldGet", pl.ipldGetBase+int64(dataSize)*pl.ipldGetPerByte, 0).WithExtra(dataSize) + return newGasCharge("OnIpldGet", pl.ipldGetBase+int64(dataSize)*pl.ipldGetPerByte, 0).WithExtra(dataSize).WithVirtual(pl.ipldGetBase*13750+(pl.ipldGetPerByte*100), 0) } // OnIpldPut returns the gas used for storing an object func (pl *pricelistV0) OnIpldPut(dataSize int) GasCharge { - return newGasCharge("OnIpldPut", pl.ipldPutBase, int64(dataSize)*pl.ipldPutPerByte).WithExtra(dataSize) + return newGasCharge("OnIpldPut", pl.ipldPutBase, int64(dataSize)*pl.ipldPutPerByte).WithExtra(dataSize).WithVirtual(pl.ipldPutBase*8700+(pl.ipldPutPerByte*100), 0) } // OnCreateActor returns the gas used for creating an actor @@ -144,13 +144,13 @@ func (pl *pricelistV0) OnHashing(dataSize int) GasCharge { // OnComputeUnsealedSectorCid func (pl *pricelistV0) OnComputeUnsealedSectorCid(proofType abi.RegisteredSealProof, pieces []abi.PieceInfo) GasCharge { // TODO: this needs more cost tunning, check with @lotus - return newGasCharge("OnComputeUnsealedSectorCid", pl.computeUnsealedSectorCidBase, 0) + return newGasCharge("OnComputeUnsealedSectorCid", pl.computeUnsealedSectorCidBase, 0).WithVirtual(pl.computeUnsealedSectorCidBase*24500, 0) } // OnVerifySeal func (pl *pricelistV0) OnVerifySeal(info abi.SealVerifyInfo) GasCharge { // TODO: this needs more cost tunning, check with @lotus - return newGasCharge("OnVerifySeal", pl.verifySealBase, 0) + return newGasCharge("OnVerifySeal", pl.verifySealBase, 0).WithVirtual(pl.verifySealBase*177500, 0) } // OnVerifyPost diff --git a/chain/vm/syscalls.go b/chain/vm/syscalls.go index a5910121d..a6a589761 100644 --- a/chain/vm/syscalls.go +++ b/chain/vm/syscalls.go @@ -241,10 +241,12 @@ func (ss *syscallShim) VerifySignature(sig crypto.Signature, addr address.Addres return sigs.Verify(&sig, kaddr, input) } +var BatchSealVerifyParallelism = goruntime.NumCPU() + func (ss *syscallShim) BatchVerifySeals(inp map[address.Address][]abi.SealVerifyInfo) (map[address.Address][]bool, error) { out := make(map[address.Address][]bool) - sema := make(chan struct{}, goruntime.NumCPU()) + sema := make(chan struct{}, BatchSealVerifyParallelism) var wg sync.WaitGroup for addr, seals := range inp { diff --git a/cmd/lotus-bench/import.go b/cmd/lotus-bench/import.go index 8d62ac0d8..f9c20ac9a 100644 --- a/cmd/lotus-bench/import.go +++ b/cmd/lotus-bench/import.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "math" "os" + "runtime" "runtime/pprof" "sort" "time" @@ -45,8 +46,14 @@ var importBenchCmd = &cli.Command{ Name: "height", Usage: "halt validation after given height", }, + &cli.IntFlag{ + Name: "batch-seal-verify-threads", + Usage: "set the parallelism factor for batch seal verification", + Value: runtime.NumCPU(), + }, }, Action: func(cctx *cli.Context) error { + vm.BatchSealVerifyParallelism = cctx.Int("batch-seal-verify-threads") if !cctx.Args().Present() { fmt.Println("must pass car file of chain to benchmark importing") return nil @@ -200,6 +207,21 @@ func compStats(vals []float64) (float64, float64) { return av, math.Sqrt(varsum / float64(len(vals))) } +func tallyGasCharges(charges map[string][]float64, et *types.ExecutionTrace) { + for _, gc := range et.GasCharges { + + compGas := gc.ComputeGas + gc.VirtualComputeGas + ratio := float64(compGas) / float64(gc.TimeTaken.Nanoseconds()) + + charges[gc.Name] = append(charges[gc.Name], 1/(ratio/GasPerNs)) + //fmt.Printf("%s: %d, %s: %0.2f\n", gc.Name, compGas, gc.TimeTaken, 1/(ratio/GasPerNs)) + for _, sub := range et.Subcalls { + tallyGasCharges(charges, &sub) + } + } + +} + var importAnalyzeCmd = &cli.Command{ Name: "analyze", Action: func(cctx *cli.Context) error { @@ -235,14 +257,8 @@ var importAnalyzeCmd = &cli.Command{ cgas, vgas := countGasCosts(&inv.ExecutionTrace) fmt.Printf("Invocation: %d %s: %s %d -> %0.2f\n", inv.Msg.Method, inv.Msg.To, inv.Duration, cgas+vgas, float64(GasPerNs*inv.Duration.Nanoseconds())/float64(cgas+vgas)) - for _, gc := range inv.ExecutionTrace.GasCharges { + tallyGasCharges(chargeDeltas, &inv.ExecutionTrace) - compGas := gc.ComputeGas + gc.VirtualComputeGas - ratio := float64(compGas) / float64(gc.TimeTaken.Nanoseconds()) - - chargeDeltas[gc.Name] = append(chargeDeltas[gc.Name], 1/(ratio/GasPerNs)) - //fmt.Printf("%s: %d, %s: %0.2f\n", gc.Name, compGas, gc.TimeTaken, 1/(ratio/GasPerNs)) - } } } From 9d17e0be9d33dbe41cce71f81d5d67c13797c737 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 16 Jun 2020 11:14:49 -0300 Subject: [PATCH 06/96] ClientMinerQueryOfer API & CLI support Signed-off-by: Ignacio Hagopian --- api/api_full.go | 1 + api/apistruct/struct.go | 27 ++++++++++++++---------- cli/client.go | 42 +++++++++++++++++++++++++++----------- node/impl/client/client.go | 39 ++++++++++++++++++++++------------- 4 files changed, 72 insertions(+), 37 deletions(-) diff --git a/api/api_full.go b/api/api_full.go index 61ef1fa1c..573a06859 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -118,6 +118,7 @@ type FullNode interface { ClientListDeals(ctx context.Context) ([]DealInfo, error) ClientHasLocal(ctx context.Context, root cid.Cid) (bool, error) ClientFindData(ctx context.Context, root cid.Cid) ([]QueryOffer, error) + ClientMinerQueryOffer(ctx context.Context, root cid.Cid, miner address.Address) (QueryOffer, error) ClientRetrieve(ctx context.Context, order RetrievalOrder, ref *FileRef) error ClientQueryAsk(ctx context.Context, p peer.ID, miner address.Address) (*storagemarket.SignedStorageAsk, error) ClientCalcCommP(ctx context.Context, inpath string, miner address.Address) (*CommPRet, error) diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index 85ac54c17..18f5a97f6 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -106,17 +106,18 @@ type FullNodeStruct struct { WalletImport func(context.Context, *types.KeyInfo) (address.Address, error) `perm:"admin"` WalletDelete func(context.Context, address.Address) error `perm:"write"` - ClientImport func(ctx context.Context, ref api.FileRef) (cid.Cid, error) `perm:"admin"` - ClientListImports func(ctx context.Context) ([]api.Import, error) `perm:"write"` - ClientHasLocal func(ctx context.Context, root cid.Cid) (bool, error) `perm:"write"` - ClientFindData func(ctx context.Context, root cid.Cid) ([]api.QueryOffer, error) `perm:"read"` - ClientStartDeal func(ctx context.Context, params *api.StartDealParams) (*cid.Cid, error) `perm:"admin"` - ClientGetDealInfo func(context.Context, cid.Cid) (*api.DealInfo, error) `perm:"read"` - ClientListDeals func(ctx context.Context) ([]api.DealInfo, error) `perm:"write"` - ClientRetrieve func(ctx context.Context, order api.RetrievalOrder, ref *api.FileRef) error `perm:"admin"` - ClientQueryAsk func(ctx context.Context, p peer.ID, miner address.Address) (*storagemarket.SignedStorageAsk, error) `perm:"read"` - ClientCalcCommP func(ctx context.Context, inpath string, miner address.Address) (*api.CommPRet, error) `perm:"read"` - ClientGenCar func(ctx context.Context, ref api.FileRef, outpath string) error `perm:"write"` + ClientImport func(ctx context.Context, ref api.FileRef) (cid.Cid, error) `perm:"admin"` + ClientListImports func(ctx context.Context) ([]api.Import, error) `perm:"write"` + ClientHasLocal func(ctx context.Context, root cid.Cid) (bool, error) `perm:"write"` + ClientFindData func(ctx context.Context, root cid.Cid) ([]api.QueryOffer, error) `perm:"read"` + ClientMinerQueryOffer func(ctx context.Context, root cid.Cid, miner address.Address) (api.QueryOffer, error) `perm:"read"` + ClientStartDeal func(ctx context.Context, params *api.StartDealParams) (*cid.Cid, error) `perm:"admin"` + ClientGetDealInfo func(context.Context, cid.Cid) (*api.DealInfo, error) `perm:"read"` + ClientListDeals func(ctx context.Context) ([]api.DealInfo, error) `perm:"write"` + ClientRetrieve func(ctx context.Context, order api.RetrievalOrder, ref *api.FileRef) error `perm:"admin"` + ClientQueryAsk func(ctx context.Context, p peer.ID, miner address.Address) (*storagemarket.SignedStorageAsk, error) `perm:"read"` + ClientCalcCommP func(ctx context.Context, inpath string, miner address.Address) (*api.CommPRet, error) `perm:"read"` + ClientGenCar func(ctx context.Context, ref api.FileRef, outpath string) error `perm:"write"` StateNetworkName func(context.Context) (dtypes.NetworkName, error) `perm:"read"` StateMinerSectors func(context.Context, address.Address, *abi.BitField, bool, types.TipSetKey) ([]*api.ChainSectorInfo, error) `perm:"read"` @@ -318,6 +319,10 @@ func (c *FullNodeStruct) ClientFindData(ctx context.Context, root cid.Cid) ([]ap return c.Internal.ClientFindData(ctx, root) } +func (c *FullNodeStruct) ClientMinerQueryOffer(ctx context.Context, root cid.Cid, miner address.Address) (api.QueryOffer, error) { + return c.Internal.ClientMinerQueryOffer(ctx, root, miner) +} + func (c *FullNodeStruct) ClientStartDeal(ctx context.Context, params *api.StartDealParams) (*cid.Cid, error) { return c.Internal.ClientStartDeal(ctx, params) } diff --git a/cli/client.go b/cli/client.go index 9d4479b62..c692fbe4b 100644 --- a/cli/client.go +++ b/cli/client.go @@ -19,6 +19,7 @@ import ( "github.com/filecoin-project/specs-actors/actors/abi" "github.com/filecoin-project/specs-actors/actors/builtin/market" + "github.com/filecoin-project/lotus/api" lapi "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/types" ) @@ -391,6 +392,10 @@ var clientRetrieveCmd = &cli.Command{ Name: "car", Usage: "export to a car file instead of a regular file", }, + &cli.StringFlag{ + Name: "miner", + Usage: "miner address for retrieval, if not present it'll use local discovery", + }, }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 2 { @@ -398,7 +403,7 @@ var clientRetrieveCmd = &cli.Command{ return nil } - api, closer, err := GetFullNodeAPI(cctx) + fapi, closer, err := GetFullNodeAPI(cctx) if err != nil { return err } @@ -409,7 +414,7 @@ var clientRetrieveCmd = &cli.Command{ if cctx.String("address") != "" { payer, err = address.NewFromString(cctx.String("address")) } else { - payer, err = api.WalletDefaultAddress(ctx) + payer, err = fapi.WalletDefaultAddress(ctx) } if err != nil { return err @@ -432,23 +437,36 @@ var clientRetrieveCmd = &cli.Command{ return nil }*/ // TODO: fix - offers, err := api.ClientFindData(ctx, file) - if err != nil { - return err - } + var offer api.QueryOffer + minerStrAddr := cctx.String("miner") + if minerStrAddr == "" { // Local discovery + offers, err := fapi.ClientFindData(ctx, file) + if err != nil { + return err + } - // TODO: parse offer strings from `client find`, make this smarter - - if len(offers) < 1 { - fmt.Println("Failed to find file") - return nil + // TODO: parse offer strings from `client find`, make this smarter + if len(offers) < 1 { + fmt.Println("Failed to find file") + return nil + } + offer = offers[0] + } else { // Directed retrieval + minerAddr, err := address.NewFromString(minerStrAddr) + if err != nil { + return err + } + offer, err = fapi.ClientMinerQueryOffer(ctx, file, minerAddr) + if err != nil { + return err + } } ref := &lapi.FileRef{ Path: cctx.Args().Get(1), IsCAR: cctx.Bool("car"), } - if err := api.ClientRetrieve(ctx, offers[0].Order(payer), ref); err != nil { + if err := fapi.ClientRetrieve(ctx, offer.Order(payer), ref); err != nil { return xerrors.Errorf("Retrieval Failed: %w", err) } diff --git a/node/impl/client/client.go b/node/impl/client/client.go index ff974e8b2..3ed90b0fa 100644 --- a/node/impl/client/client.go +++ b/node/impl/client/client.go @@ -201,25 +201,36 @@ func (a *API) ClientFindData(ctx context.Context, root cid.Cid) ([]api.QueryOffe out := make([]api.QueryOffer, len(peers)) for k, p := range peers { - queryResponse, err := a.Retrieval.Query(ctx, p, root, retrievalmarket.QueryParams{}) - if err != nil { - out[k] = api.QueryOffer{Err: err.Error(), Miner: p.Address, MinerPeerID: p.ID} - } else { - out[k] = api.QueryOffer{ - Root: root, - Size: queryResponse.Size, - MinPrice: queryResponse.PieceRetrievalPrice(), - PaymentInterval: queryResponse.MaxPaymentInterval, - PaymentIntervalIncrease: queryResponse.MaxPaymentIntervalIncrease, - Miner: queryResponse.PaymentAddress, // TODO: check - MinerPeerID: p.ID, - } - } + out[k] = a.makeRetrievalQuery(ctx, p, root, retrievalmarket.QueryParams{}) } return out, nil } +func (a *API) ClientMinerQueryOffer(ctx context.Context, payload cid.Cid, miner address.Address) (api.QueryOffer, error) { + rp := retrievalmarket.RetrievalPeer{ + Address: miner, + } + return a.makeRetrievalQuery(ctx, rp, payload, retrievalmarket.QueryParams{}), nil +} + +func (a *API) makeRetrievalQuery(ctx context.Context, rp retrievalmarket.RetrievalPeer, payload cid.Cid, qp retrievalmarket.QueryParams) api.QueryOffer { + queryResponse, err := a.Retrieval.Query(ctx, rp, payload, qp) + if err != nil { + return api.QueryOffer{Err: err.Error(), Miner: rp.Address, MinerPeerID: rp.ID} + } + + return api.QueryOffer{ + Root: payload, + Size: queryResponse.Size, + MinPrice: queryResponse.PieceRetrievalPrice(), + PaymentInterval: queryResponse.MaxPaymentInterval, + PaymentIntervalIncrease: queryResponse.MaxPaymentIntervalIncrease, + Miner: queryResponse.PaymentAddress, // TODO: check + MinerPeerID: rp.ID, + } +} + func (a *API) ClientImport(ctx context.Context, ref api.FileRef) (cid.Cid, error) { bufferedDS := ipld.NewBufferedDAG(ctx, a.LocalDAG) From 7fd082e941b69f9fe97c1774720256a8d6c738e3 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 16 Jun 2020 12:22:44 -0300 Subject: [PATCH 07/96] resolve peerid Signed-off-by: Ignacio Hagopian --- node/impl/client/client.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/node/impl/client/client.go b/node/impl/client/client.go index 3ed90b0fa..c2073872f 100644 --- a/node/impl/client/client.go +++ b/node/impl/client/client.go @@ -208,8 +208,13 @@ func (a *API) ClientFindData(ctx context.Context, root cid.Cid) ([]api.QueryOffe } func (a *API) ClientMinerQueryOffer(ctx context.Context, payload cid.Cid, miner address.Address) (api.QueryOffer, error) { + mi, err := a.StateMinerInfo(ctx, miner, types.EmptyTSK) + if err != nil { + return api.QueryOffer{}, err + } rp := retrievalmarket.RetrievalPeer{ Address: miner, + ID: mi.PeerId, } return a.makeRetrievalQuery(ctx, rp, payload, retrievalmarket.QueryParams{}), nil } From 35d9de6b6e577a5d7826b9346cd64a3f7575d205 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 16 Jun 2020 17:32:03 -0300 Subject: [PATCH 08/96] consider response status and err Signed-off-by: Ignacio Hagopian --- cli/client.go | 3 +++ node/impl/client/client.go | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/cli/client.go b/cli/client.go index c692fbe4b..1b1459707 100644 --- a/cli/client.go +++ b/cli/client.go @@ -461,6 +461,9 @@ var clientRetrieveCmd = &cli.Command{ return err } } + if offer.Err != "" { + return fmt.Errorf("The received offer errored: %s", offer.Err) + } ref := &lapi.FileRef{ Path: cctx.Args().Get(1), diff --git a/node/impl/client/client.go b/node/impl/client/client.go index c2073872f..23994fbe5 100644 --- a/node/impl/client/client.go +++ b/node/impl/client/client.go @@ -224,6 +224,15 @@ func (a *API) makeRetrievalQuery(ctx context.Context, rp retrievalmarket.Retriev if err != nil { return api.QueryOffer{Err: err.Error(), Miner: rp.Address, MinerPeerID: rp.ID} } + var errStr string + switch queryResponse.Status { + case retrievalmarket.QueryResponseAvailable: + errStr = "" + case retrievalmarket.QueryResponseUnavailable: + errStr = "retrieval query offer was unavailable" + case retrievalmarket.QueryResponseError: + errStr = "retrieval query offer errored" + } return api.QueryOffer{ Root: payload, @@ -233,6 +242,7 @@ func (a *API) makeRetrievalQuery(ctx context.Context, rp retrievalmarket.Retriev PaymentIntervalIncrease: queryResponse.MaxPaymentIntervalIncrease, Miner: queryResponse.PaymentAddress, // TODO: check MinerPeerID: rp.ID, + Err: errStr, } } From ec9a334f933f2e1066d16ebfc8928411e531f747 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 16 Jun 2020 17:52:47 -0300 Subject: [PATCH 09/96] append message on unavailable and error status Signed-off-by: Ignacio Hagopian --- node/impl/client/client.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/node/impl/client/client.go b/node/impl/client/client.go index 23994fbe5..2779343c4 100644 --- a/node/impl/client/client.go +++ b/node/impl/client/client.go @@ -3,6 +3,7 @@ package client import ( "context" "errors" + "fmt" "github.com/filecoin-project/go-fil-markets/pieceio" basicnode "github.com/ipld/go-ipld-prime/node/basic" @@ -229,9 +230,9 @@ func (a *API) makeRetrievalQuery(ctx context.Context, rp retrievalmarket.Retriev case retrievalmarket.QueryResponseAvailable: errStr = "" case retrievalmarket.QueryResponseUnavailable: - errStr = "retrieval query offer was unavailable" + errStr = fmt.Sprintf("retrieval query offer was unavailable: %s", queryResponse.Message) case retrievalmarket.QueryResponseError: - errStr = "retrieval query offer errored" + errStr = fmt.Sprintf("retrieval query offer errored: %s", queryResponse.Message) } return api.QueryOffer{ From b845013836376c2f466fb047a2bca560490dd634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 16 Jun 2020 13:59:15 +0200 Subject: [PATCH 10/96] Set build params for testnet --- build/params_testnet.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build/params_testnet.go b/build/params_testnet.go index c222862eb..202dfb231 100644 --- a/build/params_testnet.go +++ b/build/params_testnet.go @@ -12,9 +12,8 @@ import ( ) func init() { - power.ConsensusMinerMinPower = big.NewInt(1024 << 20) + power.ConsensusMinerMinPower = big.NewInt(1024 << 30) miner.SupportedProofTypes = map[abi.RegisteredSealProof]struct{}{ - abi.RegisteredSealProof_StackedDrg512MiBV1: {}, abi.RegisteredSealProof_StackedDrg32GiBV1: {}, abi.RegisteredSealProof_StackedDrg64GiBV1: {}, } From 6253c39100f4fcc16099e6f4fbcd620aad11c13c Mon Sep 17 00:00:00 2001 From: laser Date: Tue, 16 Jun 2020 15:38:48 -0700 Subject: [PATCH 11/96] replace "set-price" with "set-ask" Fixes #2027 --- api/api_storage.go | 2 +- api/apistruct/struct.go | 12 +++--- cmd/lotus-storage-miner/main.go | 1 - cmd/lotus-storage-miner/market.go | 71 +++++++++++++++++++++++++------ node/impl/storminer.go | 9 +++- 5 files changed, 72 insertions(+), 23 deletions(-) diff --git a/api/api_storage.go b/api/api_storage.go index 04ff8311c..e07505e3f 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -50,7 +50,7 @@ type StorageMiner interface { MarketImportDealData(ctx context.Context, propcid cid.Cid, path string) error MarketListDeals(ctx context.Context) ([]storagemarket.StorageDeal, error) MarketListIncompleteDeals(ctx context.Context) ([]storagemarket.MinerDeal, error) - MarketSetPrice(context.Context, types.BigInt) error + MarketSetAsk(ctx context.Context, price types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error DealsImportData(ctx context.Context, dealPropCid cid.Cid, file string) error DealsList(ctx context.Context) ([]storagemarket.StorageDeal, error) diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index 02d9e5155..55c87dbd4 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -192,10 +192,10 @@ type StorageMinerStruct struct { MiningBase func(context.Context) (*types.TipSet, error) `perm:"read"` - MarketImportDealData func(context.Context, cid.Cid, string) error `perm:"write"` - MarketListDeals func(ctx context.Context) ([]storagemarket.StorageDeal, error) `perm:"read"` - MarketListIncompleteDeals func(ctx context.Context) ([]storagemarket.MinerDeal, error) `perm:"read"` - MarketSetPrice func(context.Context, types.BigInt) error `perm:"admin"` + MarketImportDealData func(context.Context, cid.Cid, string) error `perm:"write"` + MarketListDeals func(ctx context.Context) ([]storagemarket.StorageDeal, error) `perm:"read"` + MarketListIncompleteDeals func(ctx context.Context) ([]storagemarket.MinerDeal, error) `perm:"read"` + MarketSetAsk func(ctx context.Context, price types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error `perm:"admin"` PledgeSector func(context.Context) error `perm:"write"` @@ -841,8 +841,8 @@ func (c *StorageMinerStruct) MarketListIncompleteDeals(ctx context.Context) ([]s return c.Internal.MarketListIncompleteDeals(ctx) } -func (c *StorageMinerStruct) MarketSetPrice(ctx context.Context, p types.BigInt) error { - return c.Internal.MarketSetPrice(ctx, p) +func (c *StorageMinerStruct) MarketSetAsk(ctx context.Context, price types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error { + return c.Internal.MarketSetAsk(ctx, price, duration, minPieceSize, maxPieceSize) } func (c *StorageMinerStruct) DealsImportData(ctx context.Context, dealPropCid cid.Cid, file string) error { diff --git a/cmd/lotus-storage-miner/main.go b/cmd/lotus-storage-miner/main.go index ba7722e4e..dc6de7029 100644 --- a/cmd/lotus-storage-miner/main.go +++ b/cmd/lotus-storage-miner/main.go @@ -30,7 +30,6 @@ func main() { stopCmd, sectorsCmd, storageCmd, - setPriceCmd, workersCmd, provingCmd, } diff --git a/cmd/lotus-storage-miner/market.go b/cmd/lotus-storage-miner/market.go index c2f2555fa..74a42d85d 100644 --- a/cmd/lotus-storage-miner/market.go +++ b/cmd/lotus-storage-miner/market.go @@ -4,10 +4,13 @@ import ( "encoding/json" "fmt" - "github.com/filecoin-project/lotus/chain/types" - lcli "github.com/filecoin-project/lotus/cli" + "github.com/docker/go-units" + "github.com/filecoin-project/specs-actors/actors/abi" "github.com/ipfs/go-cid" "github.com/urfave/cli/v2" + + "github.com/filecoin-project/lotus/chain/types" + lcli "github.com/filecoin-project/lotus/cli" ) var enableCmd = &cli.Command{ @@ -40,29 +43,70 @@ var disableCmd = &cli.Command{ }, } -var setPriceCmd = &cli.Command{ - Name: "set-price", - Usage: "Set price that miner will accept storage deals at (FIL / GiB / Epoch)", - Flags: []cli.Flag{}, +var setAskCmd = &cli.Command{ + Name: "set-ask", + Usage: "Configure the miner's ask", + Flags: []cli.Flag{ + &cli.Uint64Flag{ + Name: "price", + Usage: "Set the price of the ask (specified as FIL / GiB / Epoch) to `PRICE`", + Required: true, + }, + &cli.Uint64Flag{ + Name: "duration", + Usage: "Set the duration (specified as epochs) of the ask to `DURATION`", + DefaultText: "8640000", + Value: 8640000, + }, + &cli.StringFlag{ + Name: "min-piece-size", + Usage: "Set minimum piece size (without bit-padding, in bytes) in ask to `SIZE`", + DefaultText: "254B", + Value: "254B", + }, + &cli.StringFlag{ + Name: "max-piece-size", + Usage: "Set maximum piece size (without bit-padding, in bytes) in ask to `SIZE`", + DefaultText: "miner sector size, without bit-padding", + }, + }, Action: func(cctx *cli.Context) error { + ctx := lcli.DaemonContext(cctx) + api, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() - ctx := lcli.DaemonContext(cctx) + pri := types.NewInt(cctx.Uint64("price")) + dur := abi.ChainEpoch(cctx.Uint64("duration")) - if !cctx.Args().Present() { - return fmt.Errorf("must specify price to set") - } - - fp, err := types.ParseFIL(cctx.Args().First()) + min, err := units.RAMInBytes(cctx.String("min-piece-size")) if err != nil { return err } - return api.MarketSetPrice(ctx, types.BigInt(fp)) + max, err := units.RAMInBytes(cctx.String("max-piece-size")) + if err != nil { + return err + } + + if max == 0 { + maddr, err := api.ActorAddress(ctx) + if err != nil { + return err + } + + ssize, err := api.ActorSectorSize(ctx, maddr) + if err != nil { + return err + } + + max = int64(abi.PaddedPieceSize(ssize).Unpadded()) + } + + return api.MarketSetAsk(ctx, pri, dur, abi.UnpaddedPieceSize(min).Padded(), abi.UnpaddedPieceSize(max).Padded()) }, } @@ -74,6 +118,7 @@ var dealsCmd = &cli.Command{ dealsListCmd, enableCmd, disableCmd, + setAskCmd, }, } diff --git a/node/impl/storminer.go b/node/impl/storminer.go index 0cc13177e..7ece10ac0 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -201,8 +201,13 @@ func (sm *StorageMinerAPI) MarketListIncompleteDeals(ctx context.Context) ([]sto return sm.StorageProvider.ListLocalDeals() } -func (sm *StorageMinerAPI) MarketSetPrice(ctx context.Context, p types.BigInt) error { - return sm.StorageProvider.SetAsk(p, 60*60*24*100) // lasts for 100 days? +func (sm *StorageMinerAPI) MarketSetAsk(ctx context.Context, price types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error { + options := []storagemarket.StorageAskOption{ + storagemarket.MinPieceSize(minPieceSize), + storagemarket.MaxPieceSize(maxPieceSize), + } + + return sm.StorageProvider.SetAsk(price, duration, options...) } func (sm *StorageMinerAPI) DealsList(ctx context.Context) ([]storagemarket.StorageDeal, error) { From 5eceed81e192a9aca66dbe06e6ae64d28e0c264e Mon Sep 17 00:00:00 2001 From: laser Date: Tue, 16 Jun 2020 17:18:54 -0700 Subject: [PATCH 12/96] add "get-ask" command --- api/api_storage.go | 1 + api/apistruct/struct.go | 5 ++++ cmd/lotus-storage-miner/market.go | 46 +++++++++++++++++++++++++++++-- node/impl/storminer.go | 4 +++ 4 files changed, 53 insertions(+), 3 deletions(-) diff --git a/api/api_storage.go b/api/api_storage.go index e07505e3f..90de01fb9 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -51,6 +51,7 @@ type StorageMiner interface { MarketListDeals(ctx context.Context) ([]storagemarket.StorageDeal, error) MarketListIncompleteDeals(ctx context.Context) ([]storagemarket.MinerDeal, error) MarketSetAsk(ctx context.Context, price types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error + MarketGetAsk(ctx context.Context) (*storagemarket.SignedStorageAsk, error) DealsImportData(ctx context.Context, dealPropCid cid.Cid, file string) error DealsList(ctx context.Context) ([]storagemarket.StorageDeal, error) diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index 55c87dbd4..a1fe69f06 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -196,6 +196,7 @@ type StorageMinerStruct struct { MarketListDeals func(ctx context.Context) ([]storagemarket.StorageDeal, error) `perm:"read"` MarketListIncompleteDeals func(ctx context.Context) ([]storagemarket.MinerDeal, error) `perm:"read"` MarketSetAsk func(ctx context.Context, price types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error `perm:"admin"` + MarketGetAsk func(ctx context.Context) (*storagemarket.SignedStorageAsk, error) `perm:"read"` PledgeSector func(context.Context) error `perm:"write"` @@ -845,6 +846,10 @@ func (c *StorageMinerStruct) MarketSetAsk(ctx context.Context, price types.BigIn return c.Internal.MarketSetAsk(ctx, price, duration, minPieceSize, maxPieceSize) } +func (c *StorageMinerStruct) MarketGetAsk(ctx context.Context) (*storagemarket.SignedStorageAsk, error) { + return c.Internal.MarketGetAsk(ctx) +} + func (c *StorageMinerStruct) DealsImportData(ctx context.Context, dealPropCid cid.Cid, file string) error { return c.Internal.DealsImportData(ctx, dealPropCid, file) } diff --git a/cmd/lotus-storage-miner/market.go b/cmd/lotus-storage-miner/market.go index 74a42d85d..b2b7155c3 100644 --- a/cmd/lotus-storage-miner/market.go +++ b/cmd/lotus-storage-miner/market.go @@ -3,12 +3,16 @@ package main import ( "encoding/json" "fmt" + "os" + "text/tabwriter" "github.com/docker/go-units" - "github.com/filecoin-project/specs-actors/actors/abi" "github.com/ipfs/go-cid" "github.com/urfave/cli/v2" + "github.com/filecoin-project/go-fil-markets/storagemarket" + "github.com/filecoin-project/specs-actors/actors/abi" + "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" ) @@ -55,8 +59,8 @@ var setAskCmd = &cli.Command{ &cli.Uint64Flag{ Name: "duration", Usage: "Set the duration (specified as epochs) of the ask to `DURATION`", - DefaultText: "8640000", - Value: 8640000, + DefaultText: "100000", + Value: 100000, }, &cli.StringFlag{ Name: "min-piece-size", @@ -110,6 +114,41 @@ var setAskCmd = &cli.Command{ }, } +var getAskCmd = &cli.Command{ + Name: "get-ask", + Usage: "Print the miner's ask", + Flags: []cli.Flag{}, + Action: func(cctx *cli.Context) error { + ctx := lcli.DaemonContext(cctx) + + api, closer, err := lcli.GetStorageMinerAPI(cctx) + if err != nil { + return err + } + defer closer() + + sask, err := api.MarketGetAsk(ctx) + if err != nil { + return err + } + + var ask *storagemarket.StorageAsk + if sask != nil && sask.Ask != nil { + ask = sask.Ask + } + + w := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0) + fmt.Fprintf(w, "Price per GiB / Epoch\tMin. Piece Size (bytes, unpadded)\tMax. Piece Size (bytes, unpadded)\tExpiry\tSeq. No.\n") + if ask == nil { + fmt.Fprintf(w, "\n") + } else { + fmt.Fprintf(w, "%s\t%d\t%d\t%d\t%d\n", ask.Price, ask.MinPieceSize.Unpadded(), ask.MaxPieceSize.Unpadded(), ask.Expiry, ask.SeqNo) + } + + return w.Flush() + }, +} + var dealsCmd = &cli.Command{ Name: "deals", Usage: "interact with your deals", @@ -119,6 +158,7 @@ var dealsCmd = &cli.Command{ enableCmd, disableCmd, setAskCmd, + getAskCmd, }, } diff --git a/node/impl/storminer.go b/node/impl/storminer.go index 7ece10ac0..ed94e173d 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -210,6 +210,10 @@ func (sm *StorageMinerAPI) MarketSetAsk(ctx context.Context, price types.BigInt, return sm.StorageProvider.SetAsk(price, duration, options...) } +func (sm *StorageMinerAPI) MarketGetAsk(ctx context.Context) (*storagemarket.SignedStorageAsk, error) { + return sm.StorageProvider.GetAsk(), nil +} + func (sm *StorageMinerAPI) DealsList(ctx context.Context) ([]storagemarket.StorageDeal, error) { return sm.StorageProvider.ListDeals(ctx) } From 4ba1846c11d031716ec0d336d8593d20736559c4 Mon Sep 17 00:00:00 2001 From: laser Date: Tue, 16 Jun 2020 17:32:45 -0700 Subject: [PATCH 13/96] format the piece sizes --- cmd/lotus-storage-miner/market.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-storage-miner/market.go b/cmd/lotus-storage-miner/market.go index b2b7155c3..60c53bff8 100644 --- a/cmd/lotus-storage-miner/market.go +++ b/cmd/lotus-storage-miner/market.go @@ -138,11 +138,11 @@ var getAskCmd = &cli.Command{ } w := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0) - fmt.Fprintf(w, "Price per GiB / Epoch\tMin. Piece Size (bytes, unpadded)\tMax. Piece Size (bytes, unpadded)\tExpiry\tSeq. No.\n") + fmt.Fprintf(w, "Price per GiB / Epoch\tMin. Piece Size (unpadded)\tMax. Piece Size (unpadded)\tExpiry\tSeq. No.\n") if ask == nil { fmt.Fprintf(w, "\n") } else { - fmt.Fprintf(w, "%s\t%d\t%d\t%d\t%d\n", ask.Price, ask.MinPieceSize.Unpadded(), ask.MaxPieceSize.Unpadded(), ask.Expiry, ask.SeqNo) + fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%d\n", ask.Price, types.SizeStr(types.NewInt(uint64(ask.MinPieceSize.Unpadded()))), types.SizeStr(types.NewInt(uint64(ask.MaxPieceSize.Unpadded()))), ask.Expiry, ask.SeqNo) } return w.Flush() From ec693008d7ba5758e0a5380f681c92e468735c6b Mon Sep 17 00:00:00 2001 From: Jeromy Date: Tue, 16 Jun 2020 17:39:51 -0700 Subject: [PATCH 14/96] allow sealing bench to run parallel seals --- cmd/lotus-bench/main.go | 276 +++++++++++++++++++++++----------------- go.mod | 2 + go.sum | 10 -- 3 files changed, 161 insertions(+), 127 deletions(-) diff --git a/cmd/lotus-bench/main.go b/cmd/lotus-bench/main.go index a10c3c00e..06042b3d1 100644 --- a/cmd/lotus-bench/main.go +++ b/cmd/lotus-bench/main.go @@ -139,6 +139,10 @@ var sealBenchCmd = &cli.Command{ Name: "num-sectors", Value: 1, }, + &cli.IntFlag{ + Name: "parallel", + Value: 1, + }, }, Action: func(c *cli.Context) error { if c.Bool("no-gpu") { @@ -235,7 +239,7 @@ var sealBenchCmd = &cli.Command{ if robench == "" { var err error - sealTimings, sealedSectors, err = runSeals(sb, sbfs, c.Int("num-sectors"), mid, sectorSize, []byte(c.String("ticket-preimage")), c.String("save-commit2-input"), c.Bool("skip-commit2"), c.Bool("skip-unseal")) + sealTimings, sealedSectors, err = runSeals(sb, sbfs, c.Int("num-sectors"), c.Int("parallel"), mid, sectorSize, []byte(c.String("ticket-preimage")), c.String("save-commit2-input"), c.Bool("skip-commit2"), c.Bool("skip-unseal")) if err != nil { return xerrors.Errorf("failed to run seals: %w", err) } @@ -447,9 +451,14 @@ var sealBenchCmd = &cli.Command{ }, } -func runSeals(sb *ffiwrapper.Sealer, sbfs *basicfs.Provider, numSectors int, mid abi.ActorID, sectorSize abi.SectorSize, ticketPreimage []byte, saveC2inp string, skipc2, skipunseal bool) ([]SealingResult, []abi.SectorInfo, error) { - var sealTimings []SealingResult - var sealedSectors []abi.SectorInfo +func runSeals(sb *ffiwrapper.Sealer, sbfs *basicfs.Provider, numSectors, parallelism int, mid abi.ActorID, sectorSize abi.SectorSize, ticketPreimage []byte, saveC2inp string, skipc2, skipunseal bool) ([]SealingResult, []abi.SectorInfo, error) { + var pieces []abi.PieceInfo + sealTimings := make([]SealingResult, numSectors) + sealedSectors := make([]abi.SectorInfo, numSectors) + + if numSectors%parallelism != 0 { + return nil, nil, fmt.Errorf("parallelism factor must cleanly divide numSectors") + } for i := abi.SectorNumber(1); i <= abi.SectorNumber(numSectors); i++ { sid := abi.SectorID{ @@ -458,7 +467,7 @@ func runSeals(sb *ffiwrapper.Sealer, sbfs *basicfs.Provider, numSectors int, mid } start := time.Now() - log.Info("Writing piece into sector...") + log.Infof("[%d] Writing piece into sector...", i) r := rand.New(rand.NewSource(100 + int64(i))) @@ -467,129 +476,162 @@ func runSeals(sb *ffiwrapper.Sealer, sbfs *basicfs.Provider, numSectors int, mid return nil, nil, err } - addpiece := time.Now() + pieces = append(pieces, pi) - trand := blake2b.Sum256(ticketPreimage) - ticket := abi.SealRandomness(trand[:]) + sealTimings[i-1].AddPiece = time.Since(start) + } - log.Info("Running replication(1)...") - pieces := []abi.PieceInfo{pi} - pc1o, err := sb.SealPreCommit1(context.TODO(), sid, ticket, pieces) - if err != nil { - return nil, nil, xerrors.Errorf("commit: %w", err) - } + sectorsPerWorker := numSectors / parallelism - precommit1 := time.Now() + errs := make(chan error, parallelism) + for wid := 0; wid < parallelism; wid++ { + go func(worker int) { + sealerr := func() error { + start := 1 + (worker * sectorsPerWorker) + end := start + sectorsPerWorker + for i := abi.SectorNumber(start); i < abi.SectorNumber(end); i++ { + ix := int(i - 1) + sid := abi.SectorID{ + Miner: mid, + Number: i, + } - log.Info("Running replication(2)...") - cids, err := sb.SealPreCommit2(context.TODO(), sid, pc1o) - if err != nil { - return nil, nil, xerrors.Errorf("commit: %w", err) - } + start := time.Now() - precommit2 := time.Now() + trand := blake2b.Sum256(ticketPreimage) + ticket := abi.SealRandomness(trand[:]) - sealedSectors = append(sealedSectors, abi.SectorInfo{ - SealProof: sb.SealProofType(), - SectorNumber: i, - SealedCID: cids.Sealed, - }) + log.Infof("[%d] Running replication(1)...", i) + pieces := []abi.PieceInfo{pieces[ix]} + pc1o, err := sb.SealPreCommit1(context.TODO(), sid, ticket, pieces) + if err != nil { + return xerrors.Errorf("commit: %w", err) + } - seed := lapi.SealSeed{ - Epoch: 101, - Value: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 255}, - } + precommit1 := time.Now() - log.Info("Generating PoRep for sector (1)") - c1o, err := sb.SealCommit1(context.TODO(), sid, ticket, seed.Value, pieces, cids) + log.Infof("[%d] Running replication(2)...", i) + cids, err := sb.SealPreCommit2(context.TODO(), sid, pc1o) + if err != nil { + return xerrors.Errorf("commit: %w", err) + } + + precommit2 := time.Now() + + sealedSectors[ix] = abi.SectorInfo{ + SealProof: sb.SealProofType(), + SectorNumber: i, + SealedCID: cids.Sealed, + } + + seed := lapi.SealSeed{ + Epoch: 101, + Value: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 255}, + } + + log.Infof("[%d] Generating PoRep for sector (1)", i) + c1o, err := sb.SealCommit1(context.TODO(), sid, ticket, seed.Value, pieces, cids) + if err != nil { + return err + } + + sealcommit1 := time.Now() + + log.Infof("[%d] Generating PoRep for sector (2)", i) + + if saveC2inp != "" { + c2in := Commit2In{ + SectorNum: int64(i), + Phase1Out: c1o, + SectorSize: uint64(sectorSize), + } + + b, err := json.Marshal(&c2in) + if err != nil { + return err + } + + if err := ioutil.WriteFile(saveC2inp, b, 0664); err != nil { + log.Warnf("%+v", err) + } + } + + var proof storage.Proof + if !skipc2 { + proof, err = sb.SealCommit2(context.TODO(), sid, c1o) + if err != nil { + return err + } + } + + sealcommit2 := time.Now() + + if !skipc2 { + svi := abi.SealVerifyInfo{ + SectorID: abi.SectorID{Miner: mid, Number: i}, + SealedCID: cids.Sealed, + SealProof: sb.SealProofType(), + Proof: proof, + DealIDs: nil, + Randomness: ticket, + InteractiveRandomness: seed.Value, + UnsealedCID: cids.Unsealed, + } + + ok, err := ffiwrapper.ProofVerifier.VerifySeal(svi) + if err != nil { + return err + } + if !ok { + return xerrors.Errorf("porep proof for sector %d was invalid", i) + } + } + + verifySeal := time.Now() + + if !skipunseal { + log.Infof("[%d] Unsealing sector", i) + { + p, done, err := sbfs.AcquireSector(context.TODO(), abi.SectorID{Miner: mid, Number: 1}, stores.FTUnsealed, stores.FTNone, true) + if err != nil { + return xerrors.Errorf("acquire unsealed sector for removing: %w", err) + } + done() + + if err := os.Remove(p.Unsealed); err != nil { + return xerrors.Errorf("removing unsealed sector: %w", err) + } + } + + err := sb.UnsealPiece(context.TODO(), abi.SectorID{Miner: mid, Number: 1}, 0, abi.PaddedPieceSize(sectorSize).Unpadded(), ticket, cids.Unsealed) + if err != nil { + return err + } + } + unseal := time.Now() + + sealTimings[ix].PreCommit1 = precommit1.Sub(start) + sealTimings[ix].PreCommit2 = precommit2.Sub(precommit1) + sealTimings[ix].Commit1 = sealcommit1.Sub(precommit2) + sealTimings[ix].Commit2 = sealcommit2.Sub(sealcommit1) + sealTimings[ix].Verify = verifySeal.Sub(sealcommit2) + sealTimings[ix].Unseal = unseal.Sub(verifySeal) + } + return nil + }() + if sealerr != nil { + errs <- sealerr + return + } + errs <- nil + }(wid) + } + + for i := 0; i < parallelism; i++ { + err := <-errs if err != nil { return nil, nil, err } - - sealcommit1 := time.Now() - - log.Info("Generating PoRep for sector (2)") - - if saveC2inp != "" { - c2in := Commit2In{ - SectorNum: int64(i), - Phase1Out: c1o, - SectorSize: uint64(sectorSize), - } - - b, err := json.Marshal(&c2in) - if err != nil { - return nil, nil, err - } - - if err := ioutil.WriteFile(saveC2inp, b, 0664); err != nil { - log.Warnf("%+v", err) - } - } - - var proof storage.Proof - if !skipc2 { - proof, err = sb.SealCommit2(context.TODO(), sid, c1o) - if err != nil { - return nil, nil, err - } - } - - sealcommit2 := time.Now() - - if !skipc2 { - svi := abi.SealVerifyInfo{ - SectorID: abi.SectorID{Miner: mid, Number: i}, - SealedCID: cids.Sealed, - SealProof: sb.SealProofType(), - Proof: proof, - DealIDs: nil, - Randomness: ticket, - InteractiveRandomness: seed.Value, - UnsealedCID: cids.Unsealed, - } - - ok, err := ffiwrapper.ProofVerifier.VerifySeal(svi) - if err != nil { - return nil, nil, err - } - if !ok { - return nil, nil, xerrors.Errorf("porep proof for sector %d was invalid", i) - } - } - - verifySeal := time.Now() - - if !skipunseal { - log.Info("Unsealing sector") - { - p, done, err := sbfs.AcquireSector(context.TODO(), abi.SectorID{Miner: mid, Number: 1}, stores.FTUnsealed, stores.FTNone, true) - if err != nil { - return nil, nil, xerrors.Errorf("acquire unsealed sector for removing: %w", err) - } - done() - - if err := os.Remove(p.Unsealed); err != nil { - return nil, nil, xerrors.Errorf("removing unsealed sector: %w", err) - } - } - - err := sb.UnsealPiece(context.TODO(), abi.SectorID{Miner: mid, Number: 1}, 0, abi.PaddedPieceSize(sectorSize).Unpadded(), ticket, cids.Unsealed) - if err != nil { - return nil, nil, err - } - } - unseal := time.Now() - - sealTimings = append(sealTimings, SealingResult{ - AddPiece: addpiece.Sub(start), - PreCommit1: precommit1.Sub(addpiece), - PreCommit2: precommit2.Sub(precommit1), - Commit1: sealcommit1.Sub(precommit2), - Commit2: sealcommit2.Sub(sealcommit1), - Verify: verifySeal.Sub(sealcommit2), - Unseal: unseal.Sub(verifySeal), - }) } return sealTimings, sealedSectors, nil diff --git a/go.mod b/go.mod index d5e684e46..13b68200b 100644 --- a/go.mod +++ b/go.mod @@ -126,4 +126,6 @@ require ( replace github.com/golangci/golangci-lint => github.com/golangci/golangci-lint v1.18.0 +replace github.com/filecoin-project/specs-actors => ../specs-actors + replace github.com/filecoin-project/filecoin-ffi => ./extern/filecoin-ffi diff --git a/go.sum b/go.sum index 53c989e62..f28646534 100644 --- a/go.sum +++ b/go.sum @@ -215,14 +215,11 @@ github.com/fatih/color v1.8.0/go.mod h1:3l45GVGkyrnYNl9HoIjnp2NnNWvh6hLAqD8yTfGj github.com/fd/go-nat v1.0.0/go.mod h1:BTBu/CKvMmOMUPkKVef1pngt2WFH/lg7E6yQnulfp6E= github.com/filecoin-project/chain-validation v0.0.6-0.20200615191232-6be1a8c6ed09 h1:GuiNSEZ9nc05LUpKhABw/SO6t9wqCfsJX1D0ByWQjkc= github.com/filecoin-project/chain-validation v0.0.6-0.20200615191232-6be1a8c6ed09/go.mod h1:HEJn6kOXMNhCNBYNTO/lrEI7wSgqCOR6hN5ecfYUnC8= -github.com/filecoin-project/go-address v0.0.0-20200107215422-da8eea2842b5/go.mod h1:SAOwJoakQ8EPjwNIsiakIQKsoKdkcbx8U3IapgCg9R0= github.com/filecoin-project/go-address v0.0.2-0.20200218010043-eb9bb40ed5be/go.mod h1:SAOwJoakQ8EPjwNIsiakIQKsoKdkcbx8U3IapgCg9R0= github.com/filecoin-project/go-address v0.0.2-0.20200504173055-8b6f2fb2b3ef h1:Wi5E+P1QfHP8IF27eUiTx5vYfqQZwfPxzq3oFEq8w8U= github.com/filecoin-project/go-address v0.0.2-0.20200504173055-8b6f2fb2b3ef/go.mod h1:SrA+pWVoUivqKOfC+ckVYbx41hWz++HxJcrlmHNnebU= -github.com/filecoin-project/go-amt-ipld/v2 v2.0.1-0.20200131012142-05d80eeccc5e/go.mod h1:boRtQhzmxNocrMxOXo1NYn4oUc1NGvR8tEa79wApNXg= github.com/filecoin-project/go-amt-ipld/v2 v2.0.1-0.20200424220931-6263827e49f2 h1:jamfsxfK0Q9yCMHt8MPWx7Aa/O9k2Lve8eSc6FILYGQ= github.com/filecoin-project/go-amt-ipld/v2 v2.0.1-0.20200424220931-6263827e49f2/go.mod h1:boRtQhzmxNocrMxOXo1NYn4oUc1NGvR8tEa79wApNXg= -github.com/filecoin-project/go-bitfield v0.0.0-20200416002808-b3ee67ec9060/go.mod h1:iodsLxOFZnqKtjj2zkgqzoGNrv6vUqj69AT/J8DKXEw= github.com/filecoin-project/go-bitfield v0.0.1/go.mod h1:Ry9/iUlWSyjPUzlAvdnfy4Gtvrq4kWmWDztCU1yEgJY= github.com/filecoin-project/go-bitfield v0.0.2-0.20200518150651-562fdb554b6e h1:gkG/7G+iKy4He+IiQNeQn+nndFznb/vCoOR8iRQsm60= github.com/filecoin-project/go-bitfield v0.0.2-0.20200518150651-562fdb554b6e/go.mod h1:Ry9/iUlWSyjPUzlAvdnfy4Gtvrq4kWmWDztCU1yEgJY= @@ -255,12 +252,6 @@ github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b/ github.com/filecoin-project/sector-storage v0.0.0-20200615154852-728a47ab99d6/go.mod h1:M59QnAeA/oV+Z8oHFLoNpGMv0LZ8Rll+vHVXX7GirPM= github.com/filecoin-project/sector-storage v0.0.0-20200615192001-42c9e08595b7 h1:cjsOpQKvZosPx9/qqq2bucHVdRyXzvBR1f37atiR3/0= github.com/filecoin-project/sector-storage v0.0.0-20200615192001-42c9e08595b7/go.mod h1:M59QnAeA/oV+Z8oHFLoNpGMv0LZ8Rll+vHVXX7GirPM= -github.com/filecoin-project/specs-actors v0.0.0-20200210130641-2d1fbd8672cf/go.mod h1:xtDZUB6pe4Pksa/bAJbJ693OilaC5Wbot9jMhLm3cZA= -github.com/filecoin-project/specs-actors v0.3.0/go.mod h1:nQYnFbQ7Y0bHZyq6HDEuVlCPR+U3z5Q3wMOQ+2aiV+Y= -github.com/filecoin-project/specs-actors v0.6.0 h1:IepUsmDGY60QliENVTkBTAkwqGWw9kNbbHOcU/9oiC0= -github.com/filecoin-project/specs-actors v0.6.0/go.mod h1:dRdy3cURykh2R8O/DKqy8olScl70rmIS7GrB4hB1IDY= -github.com/filecoin-project/specs-actors v0.6.1 h1:rhHlEzqcuuQU6oKc4csuq+/kQBDZ4EXtSomoN2XApCA= -github.com/filecoin-project/specs-actors v0.6.1/go.mod h1:dRdy3cURykh2R8O/DKqy8olScl70rmIS7GrB4hB1IDY= github.com/filecoin-project/specs-storage v0.1.0 h1:PkDgTOT5W5Ao7752onjDl4QSv+sgOVdJbvFjOnD5w94= github.com/filecoin-project/specs-storage v0.1.0/go.mod h1:Pr5ntAaxsh+sLG/LYiL4tKzvA83Vk5vLODYhfNwOg7k= github.com/filecoin-project/storage-fsm v0.0.0-20200615162749-494c3bc48743 h1:a8f1p6UdeD+ZINBKJN4FhEos8uaKeASOAabq5RCpQdg= @@ -1330,7 +1321,6 @@ github.com/whyrusleeping/bencher v0.0.0-20190829221104-bb6607aa8bba/go.mod h1:CH github.com/whyrusleeping/cbor-gen v0.0.0-20191212224538-d370462a7e8a/go.mod h1:xdlJQaiqipF0HW+Mzpg7XRM3fWbGvfgFlcppuvlkIvY= github.com/whyrusleeping/cbor-gen v0.0.0-20191216205031-b047b6acb3c0/go.mod h1:xdlJQaiqipF0HW+Mzpg7XRM3fWbGvfgFlcppuvlkIvY= github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI= -github.com/whyrusleeping/cbor-gen v0.0.0-20200206220010-03c9665e2a66/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI= github.com/whyrusleeping/cbor-gen v0.0.0-20200402171437-3d27c146c105/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI= github.com/whyrusleeping/cbor-gen v0.0.0-20200414195334-429a0b5e922e/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI= github.com/whyrusleeping/cbor-gen v0.0.0-20200501014322-5f9941ef88e0/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI= From 5acf5bf102d9f11eb63f49eaecaeb8604d202380 Mon Sep 17 00:00:00 2001 From: laser Date: Tue, 16 Jun 2020 17:45:11 -0700 Subject: [PATCH 15/96] upper and lower bounds checking --- cmd/lotus-storage-miner/market.go | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/cmd/lotus-storage-miner/market.go b/cmd/lotus-storage-miner/market.go index 60c53bff8..84465f60d 100644 --- a/cmd/lotus-storage-miner/market.go +++ b/cmd/lotus-storage-miner/market.go @@ -8,6 +8,7 @@ import ( "github.com/docker/go-units" "github.com/ipfs/go-cid" + "github.com/pkg/errors" "github.com/urfave/cli/v2" "github.com/filecoin-project/go-fil-markets/storagemarket" @@ -91,23 +92,33 @@ var setAskCmd = &cli.Command{ return err } + if min < 254 { + return errors.New("minimum piece size (unpadded) is 254 B") + } + max, err := units.RAMInBytes(cctx.String("max-piece-size")) if err != nil { return err } + maddr, err := api.ActorAddress(ctx) + if err != nil { + return err + } + + ssize, err := api.ActorSectorSize(ctx, maddr) + if err != nil { + return err + } + + smax := int64(abi.PaddedPieceSize(ssize).Unpadded()) + if max == 0 { - maddr, err := api.ActorAddress(ctx) - if err != nil { - return err - } + max = smax + } - ssize, err := api.ActorSectorSize(ctx, maddr) - if err != nil { - return err - } - - max = int64(abi.PaddedPieceSize(ssize).Unpadded()) + if max > smax { + return errors.Errorf("max piece size (unpadded) %s cannot exceed miner sector size (unpadded) %s", types.SizeStr(types.NewInt(uint64(max))), types.SizeStr(types.NewInt(uint64(smax)))) } return api.MarketSetAsk(ctx, pri, dur, abi.UnpaddedPieceSize(min).Padded(), abi.UnpaddedPieceSize(max).Padded()) From 2676892886cf674528528b46432fa80a76baaefa Mon Sep 17 00:00:00 2001 From: laser Date: Tue, 16 Jun 2020 17:47:04 -0700 Subject: [PATCH 16/96] go mod tidy --- go.mod | 1 + 1 file changed, 1 insertion(+) diff --git a/go.mod b/go.mod index d5e684e46..cb94ad055 100644 --- a/go.mod +++ b/go.mod @@ -103,6 +103,7 @@ require ( github.com/multiformats/go-multibase v0.0.2 github.com/multiformats/go-multihash v0.0.13 github.com/opentracing/opentracing-go v1.1.0 + github.com/pkg/errors v0.9.1 github.com/stretchr/objx v0.2.0 // indirect github.com/stretchr/testify v1.5.1 github.com/syndtr/goleveldb v1.0.0 From 139c3297ab59939285842baea007276019add121 Mon Sep 17 00:00:00 2001 From: laser Date: Wed, 17 Jun 2020 08:34:50 -0700 Subject: [PATCH 17/96] change "duration" help text --- cmd/lotus-storage-miner/market.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/lotus-storage-miner/market.go b/cmd/lotus-storage-miner/market.go index 84465f60d..1163e09e8 100644 --- a/cmd/lotus-storage-miner/market.go +++ b/cmd/lotus-storage-miner/market.go @@ -59,7 +59,7 @@ var setAskCmd = &cli.Command{ }, &cli.Uint64Flag{ Name: "duration", - Usage: "Set the duration (specified as epochs) of the ask to `DURATION`", + Usage: "Set the number of epochs (from now) that the ask will expire `DURATION`", DefaultText: "100000", Value: 100000, }, From 5d4f1bb3f1b8004ce75cff2e3154d501014ea0a3 Mon Sep 17 00:00:00 2001 From: laser Date: Wed, 17 Jun 2020 08:42:30 -0700 Subject: [PATCH 18/96] work with bit-padded byte quantities, as per PR feedback --- cmd/lotus-storage-miner/market.go | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/cmd/lotus-storage-miner/market.go b/cmd/lotus-storage-miner/market.go index 1163e09e8..8d677a55c 100644 --- a/cmd/lotus-storage-miner/market.go +++ b/cmd/lotus-storage-miner/market.go @@ -10,6 +10,7 @@ import ( "github.com/ipfs/go-cid" "github.com/pkg/errors" "github.com/urfave/cli/v2" + "golang.org/x/xerrors" "github.com/filecoin-project/go-fil-markets/storagemarket" "github.com/filecoin-project/specs-actors/actors/abi" @@ -59,20 +60,20 @@ var setAskCmd = &cli.Command{ }, &cli.Uint64Flag{ Name: "duration", - Usage: "Set the number of epochs (from now) that the ask will expire `DURATION`", + Usage: "Set the number of epochs from the current chain head that the ask will expire `DURATION`", DefaultText: "100000", Value: 100000, }, &cli.StringFlag{ Name: "min-piece-size", - Usage: "Set minimum piece size (without bit-padding, in bytes) in ask to `SIZE`", - DefaultText: "254B", - Value: "254B", + Usage: "Set minimum piece size (w/bit-padding, in bytes) in ask to `SIZE`", + DefaultText: "256B", + Value: "256B", }, &cli.StringFlag{ Name: "max-piece-size", - Usage: "Set maximum piece size (without bit-padding, in bytes) in ask to `SIZE`", - DefaultText: "miner sector size, without bit-padding", + Usage: "Set maximum piece size (w/bit-padding, in bytes) in ask to `SIZE`", + DefaultText: "miner sector size", }, }, Action: func(cctx *cli.Context) error { @@ -92,8 +93,8 @@ var setAskCmd = &cli.Command{ return err } - if min < 254 { - return errors.New("minimum piece size (unpadded) is 254 B") + if min < 256 { + return xerrors.New("minimum piece size (w/bit-padding) is 256B") } max, err := units.RAMInBytes(cctx.String("max-piece-size")) @@ -111,17 +112,17 @@ var setAskCmd = &cli.Command{ return err } - smax := int64(abi.PaddedPieceSize(ssize).Unpadded()) + smax := int64(ssize) if max == 0 { max = smax } if max > smax { - return errors.Errorf("max piece size (unpadded) %s cannot exceed miner sector size (unpadded) %s", types.SizeStr(types.NewInt(uint64(max))), types.SizeStr(types.NewInt(uint64(smax)))) + return errors.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, pri, dur, abi.UnpaddedPieceSize(min).Padded(), abi.UnpaddedPieceSize(max).Padded()) + return api.MarketSetAsk(ctx, pri, dur, abi.PaddedPieceSize(min), abi.PaddedPieceSize(max)) }, } @@ -149,11 +150,11 @@ var getAskCmd = &cli.Command{ } w := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0) - fmt.Fprintf(w, "Price per GiB / Epoch\tMin. Piece Size (unpadded)\tMax. Piece Size (unpadded)\tExpiry\tSeq. No.\n") + fmt.Fprintf(w, "Price per GiB / Epoch\tMin. Piece Size (w/bit-padding)\tMax. Piece Size (w/bit-padding)\tExpiry\tSeq. No.\n") if ask == nil { fmt.Fprintf(w, "\n") } else { - fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%d\n", ask.Price, types.SizeStr(types.NewInt(uint64(ask.MinPieceSize.Unpadded()))), types.SizeStr(types.NewInt(uint64(ask.MaxPieceSize.Unpadded()))), ask.Expiry, ask.SeqNo) + fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%d\n", ask.Price, types.SizeStr(types.NewInt(uint64(ask.MinPieceSize))), types.SizeStr(types.NewInt(uint64(ask.MaxPieceSize))), ask.Expiry, ask.SeqNo) } return w.Flush() From 8b8bb5e833b82402fb7e764701227f1d56e18cb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 17 Jun 2020 16:44:59 +0200 Subject: [PATCH 19/96] rpc: add Closing method --- api/api_common.go | 2 ++ api/apistruct/struct.go | 7 ++++++- node/impl/common/common.go | 4 ++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/api/api_common.go b/api/api_common.go index e5556d16f..6d47e35f7 100644 --- a/api/api_common.go +++ b/api/api_common.go @@ -38,6 +38,8 @@ type Common interface { // trigger graceful shutdown Shutdown(context.Context) error + + Closing(context.Context) (<-chan struct{}, error) } // Version provides various build-time information diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index 02d9e5155..dfa7a44ee 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -50,7 +50,8 @@ type CommonStruct struct { LogList func(context.Context) ([]string, error) `perm:"write"` LogSetLevel func(context.Context, string, string) error `perm:"write"` - Shutdown func(context.Context) error `perm:"admin"` + Shutdown func(context.Context) error `perm:"admin"` + Closing func(context.Context) (<-chan struct{}, error) `perm:"read"` } } @@ -313,6 +314,10 @@ func (c *CommonStruct) Shutdown(ctx context.Context) error { return c.Internal.Shutdown(ctx) } +func (c *CommonStruct) Closing(ctx context.Context) (<-chan struct{}, error) { + return c.Internal.Closing(ctx) +} + // FullNodeStruct func (c *FullNodeStruct) ClientListImports(ctx context.Context) ([]api.Import, error) { diff --git a/node/impl/common/common.go b/node/impl/common/common.go index aa85d9182..3a42872d9 100644 --- a/node/impl/common/common.go +++ b/node/impl/common/common.go @@ -139,4 +139,8 @@ func (a *CommonAPI) Shutdown(ctx context.Context) error { return nil } +func (a *CommonAPI) Closing(ctx context.Context) (<-chan struct{}, error) { + return make(chan struct{}), nil // relies on jsonrpc closing +} + var _ api.Common = &CommonAPI{} From fc7195f19ab9ccd2ce0e41cdca819843ee8466d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 17 Jun 2020 17:23:07 +0200 Subject: [PATCH 20/96] seal-worker: Wait for miner API on start --- cmd/lotus-seal-worker/main.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/cmd/lotus-seal-worker/main.go b/cmd/lotus-seal-worker/main.go index 65418ead8..bb83ebf8c 100644 --- a/cmd/lotus-seal-worker/main.go +++ b/cmd/lotus-seal-worker/main.go @@ -3,11 +3,13 @@ package main import ( "context" "encoding/json" + "fmt" "io/ioutil" "net" "net/http" "os" "path/filepath" + "time" "github.com/google/uuid" "github.com/gorilla/mux" @@ -117,10 +119,18 @@ var runCmd = &cli.Command{ } // Connect to storage-miner + var nodeApi api.StorageMiner + var closer func() + var err error + for { + nodeApi, closer, err = lcli.GetStorageMinerAPI(cctx) + if err == nil { + break + } + fmt.Printf("\r\x1b[0KConnecting to miner API... (%s)", err) + time.Sleep(time.Second) + continue - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) - if err != nil { - return xerrors.Errorf("getting miner api: %w", err) } defer closer() ctx := lcli.ReqContext(cctx) From 2761872ea7e77bd709254b1637de541d3b171b8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 17 Jun 2020 17:43:16 +0200 Subject: [PATCH 21/96] seal-worker: Auto-restart when API connection is lost --- cmd/lotus-seal-worker/main.go | 44 ++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/cmd/lotus-seal-worker/main.go b/cmd/lotus-seal-worker/main.go index bb83ebf8c..ff45687f8 100644 --- a/cmd/lotus-seal-worker/main.go +++ b/cmd/lotus-seal-worker/main.go @@ -9,6 +9,7 @@ import ( "net/http" "os" "path/filepath" + "syscall" "time" "github.com/google/uuid" @@ -130,8 +131,8 @@ var runCmd = &cli.Command{ fmt.Printf("\r\x1b[0KConnecting to miner API... (%s)", err) time.Sleep(time.Second) continue - } + defer closer() ctx := lcli.ReqContext(cctx) ctx, cancel := context.WithCancel(ctx) @@ -146,6 +147,8 @@ var runCmd = &cli.Command{ } log.Infof("Remote version %s", v) + watchMinerConn(ctx, cctx, nodeApi) + // Check params act, err := nodeApi.ActorAddress(ctx) @@ -327,3 +330,42 @@ var runCmd = &cli.Command{ return srv.Serve(nl) }, } + +func watchMinerConn(ctx context.Context, cctx *cli.Context, nodeApi api.StorageMiner) { + go func() { + closing, err := nodeApi.Closing(ctx) + if err != nil { + log.Errorf("failed to get remote closing channel: %+v", err) + } + + select { + case <-closing: + case <-ctx.Done(): + } + + if ctx.Err() != nil { + return // graceful shutdown + } + + log.Warnf("Connection with miner node lost, restarting") + + exe, err := os.Executable() + if err != nil { + log.Errorf("getting executable for auto-restart: %+v", err) + } + + log.Sync() + + // TODO: there are probably cleaner/more graceful ways to restart, + // but this is good enough for now (FSM can recover from the mess this creates) + if err := syscall.Exec(exe, []string{exe, "run", + fmt.Sprintf("--address=%s", cctx.String("address")), + fmt.Sprintf("--no-local-storage=%t", cctx.Bool("no-local-storage")), + fmt.Sprintf("--precommit1=%t", cctx.Bool("precommit1")), + fmt.Sprintf("--precommit2=%t", cctx.Bool("precommit2")), + fmt.Sprintf("--commit=%t", cctx.Bool("commit")), + }, os.Environ()); err != nil { + fmt.Println(err) + } + }() +} From 673a64318426dda40ca3244f82c7e9af662c8ceb Mon Sep 17 00:00:00 2001 From: laser Date: Wed, 17 Jun 2020 08:57:18 -0700 Subject: [PATCH 22/96] use xerrors, as per feedback in PR --- cmd/lotus-storage-miner/market.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/lotus-storage-miner/market.go b/cmd/lotus-storage-miner/market.go index 8d677a55c..71be988e9 100644 --- a/cmd/lotus-storage-miner/market.go +++ b/cmd/lotus-storage-miner/market.go @@ -8,7 +8,6 @@ import ( "github.com/docker/go-units" "github.com/ipfs/go-cid" - "github.com/pkg/errors" "github.com/urfave/cli/v2" "golang.org/x/xerrors" @@ -119,7 +118,7 @@ var setAskCmd = &cli.Command{ } if max > smax { - return errors.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 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, pri, dur, abi.PaddedPieceSize(min), abi.PaddedPieceSize(max)) From 9d7be5dcbfac3e6cdb90e9ac5a33effa8b6c879c Mon Sep 17 00:00:00 2001 From: laser Date: Wed, 17 Jun 2020 09:20:43 -0700 Subject: [PATCH 23/96] modify set-ask to work with human-readable clock time/duration --- cmd/lotus-storage-miner/market.go | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/cmd/lotus-storage-miner/market.go b/cmd/lotus-storage-miner/market.go index 71be988e9..a189706f3 100644 --- a/cmd/lotus-storage-miner/market.go +++ b/cmd/lotus-storage-miner/market.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "text/tabwriter" + "time" "github.com/docker/go-units" "github.com/ipfs/go-cid" @@ -14,6 +15,7 @@ import ( "github.com/filecoin-project/go-fil-markets/storagemarket" "github.com/filecoin-project/specs-actors/actors/abi" + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" ) @@ -57,11 +59,11 @@ var setAskCmd = &cli.Command{ Usage: "Set the price of the ask (specified as FIL / GiB / Epoch) to `PRICE`", Required: true, }, - &cli.Uint64Flag{ + &cli.StringFlag{ Name: "duration", - Usage: "Set the number of epochs from the current chain head that the ask will expire `DURATION`", - DefaultText: "100000", - Value: 100000, + Usage: "Set duration of ask (a quantity of time after which the ask expires) `DURATION`", + DefaultText: "30d0h0m0s", + Value: "30d0h0m0s", }, &cli.StringFlag{ Name: "min-piece-size", @@ -85,11 +87,17 @@ var setAskCmd = &cli.Command{ defer closer() pri := types.NewInt(cctx.Uint64("price")) - dur := abi.ChainEpoch(cctx.Uint64("duration")) + + dur, err := time.ParseDuration(cctx.String("duration")) + if err != nil { + return xerrors.Errorf("cannot parse duration: %w", err) + } + + qty := dur.Seconds() / build.BlockDelay min, err := units.RAMInBytes(cctx.String("min-piece-size")) if err != nil { - return err + return xerrors.Errorf("cannot parse min-piece-size to quantity of bytes: %w", err) } if min < 256 { @@ -98,7 +106,7 @@ var setAskCmd = &cli.Command{ max, err := units.RAMInBytes(cctx.String("max-piece-size")) if err != nil { - return err + return xerrors.Errorf("cannot parse max-piece-size to quantity of bytes: %w", err) } maddr, err := api.ActorAddress(ctx) @@ -121,7 +129,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, pri, dur, abi.PaddedPieceSize(min), abi.PaddedPieceSize(max)) + return api.MarketSetAsk(ctx, pri, abi.ChainEpoch(qty), abi.PaddedPieceSize(min), abi.PaddedPieceSize(max)) }, } From e60dd219dc8a89126fbf0c8470187fa59724e464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 17 Jun 2020 18:21:40 +0200 Subject: [PATCH 24/96] wdpost: Don't return nil from checkSectors --- storage/wdpost_run.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/storage/wdpost_run.go b/storage/wdpost_run.go index 902c9b741..5c1170551 100644 --- a/storage/wdpost_run.go +++ b/storage/wdpost_run.go @@ -99,10 +99,6 @@ func (s *WindowPoStScheduler) checkSectors(ctx context.Context, check *abi.BitFi log.Warnw("Checked sectors", "checked", len(tocheck), "good", len(sectors)) - if len(sectors) == 0 { // nothing to recover - return nil, nil - } - sbf := bitfield.New() for s := range sectors { (&sbf).Set(uint64(s.Number)) From 402cd8be1903bb50eff997ad1272e5810178aeb3 Mon Sep 17 00:00:00 2001 From: laser Date: Wed, 17 Jun 2020 10:20:42 -0700 Subject: [PATCH 25/96] get-ask output should use durations, too --- cmd/lotus-storage-miner/market.go | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/cmd/lotus-storage-miner/market.go b/cmd/lotus-storage-miner/market.go index a189706f3..977ae22de 100644 --- a/cmd/lotus-storage-miner/market.go +++ b/cmd/lotus-storage-miner/market.go @@ -62,8 +62,8 @@ var setAskCmd = &cli.Command{ &cli.StringFlag{ Name: "duration", Usage: "Set duration of ask (a quantity of time after which the ask expires) `DURATION`", - DefaultText: "30d0h0m0s", - Value: "30d0h0m0s", + DefaultText: "720h0m0s", + Value: "720h0m0s", }, &cli.StringFlag{ Name: "min-piece-size", @@ -157,13 +157,22 @@ var getAskCmd = &cli.Command{ } w := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0) - fmt.Fprintf(w, "Price per GiB / Epoch\tMin. Piece Size (w/bit-padding)\tMax. Piece Size (w/bit-padding)\tExpiry\tSeq. No.\n") + fmt.Fprintf(w, "Price per GiB / Epoch\tMin. Piece Size (w/bit-padding)\tMax. Piece Size (w/bit-padding)\tExpiry (Epoch)\tExpiry (Appx. Rem. Time)\tSeq. No.\n") if ask == nil { fmt.Fprintf(w, "\n") - } else { - fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%d\n", ask.Price, types.SizeStr(types.NewInt(uint64(ask.MinPieceSize))), types.SizeStr(types.NewInt(uint64(ask.MaxPieceSize))), ask.Expiry, ask.SeqNo) + + return w.Flush() } + miningBaseTs, err := api.MiningBase(ctx) + if err != nil { + return err + } + + rem := time.Second * time.Duration((ask.Expiry-miningBaseTs.Height())*build.BlockDelay) + + fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%s\t%d\n", ask.Price, types.SizeStr(types.NewInt(uint64(ask.MinPieceSize))), types.SizeStr(types.NewInt(uint64(ask.MaxPieceSize))), ask.Expiry, rem, ask.SeqNo) + return w.Flush() }, } From 791dff5a8712c3291a67f1977a08d27bfbdb27ba Mon Sep 17 00:00:00 2001 From: laser Date: Wed, 17 Jun 2020 10:30:16 -0700 Subject: [PATCH 26/96] don't go into negative remaining time --- cmd/lotus-storage-miner/market.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/lotus-storage-miner/market.go b/cmd/lotus-storage-miner/market.go index 977ae22de..dd99a375c 100644 --- a/cmd/lotus-storage-miner/market.go +++ b/cmd/lotus-storage-miner/market.go @@ -169,7 +169,11 @@ var getAskCmd = &cli.Command{ return err } - rem := time.Second * time.Duration((ask.Expiry-miningBaseTs.Height())*build.BlockDelay) + dlt := ask.Expiry - miningBaseTs.Height() + rem := "" + if dlt > 0 { + rem = (time.Second * time.Duration(dlt*build.BlockDelay)).String() + } fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%s\t%d\n", ask.Price, types.SizeStr(types.NewInt(uint64(ask.MinPieceSize))), types.SizeStr(types.NewInt(uint64(ask.MaxPieceSize))), ask.Expiry, rem, ask.SeqNo) From ed5b74b1ae377910aaf4398b5af885412142f806 Mon Sep 17 00:00:00 2001 From: laser Date: Wed, 17 Jun 2020 10:42:09 -0700 Subject: [PATCH 27/96] tidy up --- go.mod | 1 - 1 file changed, 1 deletion(-) diff --git a/go.mod b/go.mod index cb94ad055..d5e684e46 100644 --- a/go.mod +++ b/go.mod @@ -103,7 +103,6 @@ require ( github.com/multiformats/go-multibase v0.0.2 github.com/multiformats/go-multihash v0.0.13 github.com/opentracing/opentracing-go v1.1.0 - github.com/pkg/errors v0.9.1 github.com/stretchr/objx v0.2.0 // indirect github.com/stretchr/testify v1.5.1 github.com/syndtr/goleveldb v1.0.0 From fcdfda8ba2eb2e994d26eabe8be759c947f60ad7 Mon Sep 17 00:00:00 2001 From: laser Date: Wed, 17 Jun 2020 10:56:42 -0700 Subject: [PATCH 28/96] use chain head instead of mining base --- cmd/lotus-storage-miner/market.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/cmd/lotus-storage-miner/market.go b/cmd/lotus-storage-miner/market.go index dd99a375c..4dac236e4 100644 --- a/cmd/lotus-storage-miner/market.go +++ b/cmd/lotus-storage-miner/market.go @@ -140,13 +140,19 @@ var getAskCmd = &cli.Command{ Action: func(cctx *cli.Context) error { ctx := lcli.DaemonContext(cctx) - api, closer, err := lcli.GetStorageMinerAPI(cctx) + fnapi, closer, err := lcli.GetFullNodeAPI(cctx) if err != nil { return err } defer closer() - sask, err := api.MarketGetAsk(ctx) + smapi, closer, err := lcli.GetStorageMinerAPI(cctx) + if err != nil { + return err + } + defer closer() + + sask, err := smapi.MarketGetAsk(ctx) if err != nil { return err } @@ -164,12 +170,12 @@ var getAskCmd = &cli.Command{ return w.Flush() } - miningBaseTs, err := api.MiningBase(ctx) + head, err := fnapi.ChainHead(ctx) if err != nil { return err } - dlt := ask.Expiry - miningBaseTs.Height() + dlt := ask.Expiry - head.Height() rem := "" if dlt > 0 { rem = (time.Second * time.Duration(dlt*build.BlockDelay)).String() From b91e7a98609d382a01cd00b457df2ec454dbd299 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Wed, 17 Jun 2020 20:00:30 +0200 Subject: [PATCH 29/96] Update to specs actors with ChargeGas interface Based on `lotus-0.6.1-chargegas` in specs-actors. Signed-off-by: Jakub Sztandera --- chain/vm/runtime.go | 14 +++++++++++--- go.mod | 2 +- go.sum | 2 ++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/chain/vm/runtime.go b/chain/vm/runtime.go index 48563df64..d6d49c214 100644 --- a/chain/vm/runtime.go +++ b/chain/vm/runtime.go @@ -253,7 +253,7 @@ func (rt *Runtime) CreateActor(codeID cid.Cid, address address.Address) { rt.Abortf(exitcode.SysErrorIllegalArgument, "Actor address already exists") } - rt.ChargeGas(rt.Pricelist().OnCreateActor()) + rt.chargeGas(rt.Pricelist().OnCreateActor()) err = rt.state.SetActor(address, &types.Actor{ Code: codeID, @@ -267,7 +267,7 @@ func (rt *Runtime) CreateActor(codeID cid.Cid, address address.Address) { } func (rt *Runtime) DeleteActor(addr address.Address) { - rt.ChargeGas(rt.Pricelist().OnDeleteActor()) + rt.chargeGas(rt.Pricelist().OnDeleteActor()) act, err := rt.state.GetActor(rt.Message().Receiver()) if err != nil { if xerrors.Is(err, types.ErrActorNotFound) { @@ -496,7 +496,15 @@ func (rt *Runtime) finilizeGasTracing() { } } -func (rt *Runtime) ChargeGas(gas GasCharge) { +// ChargeGas is spec actors function +func (rt *Runtime) ChargeGas(name string, compute int64, virtual int64) { + err := rt.chargeGasInternal(newGasCharge(name, compute, 0).WithVirtual(virtual, 0), 1) + if err != nil { + panic(err) + } +} + +func (rt *Runtime) chargeGas(gas GasCharge) { err := rt.chargeGasInternal(gas, 1) if err != nil { panic(err) diff --git a/go.mod b/go.mod index d5e684e46..c2471b82a 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/filecoin-project/go-statestore v0.1.0 github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b github.com/filecoin-project/sector-storage v0.0.0-20200615192001-42c9e08595b7 - github.com/filecoin-project/specs-actors v0.6.1 + github.com/filecoin-project/specs-actors v0.6.2-0.20200617175406-de392ca14121 github.com/filecoin-project/specs-storage v0.1.0 github.com/filecoin-project/storage-fsm v0.0.0-20200615162749-494c3bc48743 github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1 diff --git a/go.sum b/go.sum index 53c989e62..14308aedb 100644 --- a/go.sum +++ b/go.sum @@ -261,6 +261,8 @@ github.com/filecoin-project/specs-actors v0.6.0 h1:IepUsmDGY60QliENVTkBTAkwqGWw9 github.com/filecoin-project/specs-actors v0.6.0/go.mod h1:dRdy3cURykh2R8O/DKqy8olScl70rmIS7GrB4hB1IDY= github.com/filecoin-project/specs-actors v0.6.1 h1:rhHlEzqcuuQU6oKc4csuq+/kQBDZ4EXtSomoN2XApCA= github.com/filecoin-project/specs-actors v0.6.1/go.mod h1:dRdy3cURykh2R8O/DKqy8olScl70rmIS7GrB4hB1IDY= +github.com/filecoin-project/specs-actors v0.6.2-0.20200617175406-de392ca14121 h1:oRA+b4iN4H86xXDXbU3TOyvmBZp7//c5VqTc0oJ6nLg= +github.com/filecoin-project/specs-actors v0.6.2-0.20200617175406-de392ca14121/go.mod h1:dRdy3cURykh2R8O/DKqy8olScl70rmIS7GrB4hB1IDY= github.com/filecoin-project/specs-storage v0.1.0 h1:PkDgTOT5W5Ao7752onjDl4QSv+sgOVdJbvFjOnD5w94= github.com/filecoin-project/specs-storage v0.1.0/go.mod h1:Pr5ntAaxsh+sLG/LYiL4tKzvA83Vk5vLODYhfNwOg7k= github.com/filecoin-project/storage-fsm v0.0.0-20200615162749-494c3bc48743 h1:a8f1p6UdeD+ZINBKJN4FhEos8uaKeASOAabq5RCpQdg= From d66c70d1e6ae1002811bec1feefadc1c3b211bf3 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Wed, 17 Jun 2020 11:02:48 -0700 Subject: [PATCH 30/96] add configurable paralellism for sub portions of the sealing process --- cmd/lotus-bench/main.go | 38 +++++++++++++++++++++++++++++--------- go.mod | 2 -- go.sum | 9 +++++++++ 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/cmd/lotus-bench/main.go b/cmd/lotus-bench/main.go index 06042b3d1..3a649418f 100644 --- a/cmd/lotus-bench/main.go +++ b/cmd/lotus-bench/main.go @@ -239,7 +239,12 @@ var sealBenchCmd = &cli.Command{ if robench == "" { var err error - sealTimings, sealedSectors, err = runSeals(sb, sbfs, c.Int("num-sectors"), c.Int("parallel"), mid, sectorSize, []byte(c.String("ticket-preimage")), c.String("save-commit2-input"), c.Bool("skip-commit2"), c.Bool("skip-unseal")) + parCfg := ParCfg{ + PreCommit1: c.Int("parallel"), + PreCommit2: 1, + Commit: 1, + } + sealTimings, sealedSectors, err = runSeals(sb, sbfs, c.Int("num-sectors"), parCfg, mid, sectorSize, []byte(c.String("ticket-preimage")), c.String("save-commit2-input"), c.Bool("skip-commit2"), c.Bool("skip-unseal")) if err != nil { return xerrors.Errorf("failed to run seals: %w", err) } @@ -451,12 +456,21 @@ var sealBenchCmd = &cli.Command{ }, } -func runSeals(sb *ffiwrapper.Sealer, sbfs *basicfs.Provider, numSectors, parallelism int, mid abi.ActorID, sectorSize abi.SectorSize, ticketPreimage []byte, saveC2inp string, skipc2, skipunseal bool) ([]SealingResult, []abi.SectorInfo, error) { +type ParCfg struct { + PreCommit1 int + PreCommit2 int + Commit int +} + +func runSeals(sb *ffiwrapper.Sealer, sbfs *basicfs.Provider, numSectors int, par ParCfg, mid abi.ActorID, sectorSize abi.SectorSize, ticketPreimage []byte, saveC2inp string, skipc2, skipunseal bool) ([]SealingResult, []abi.SectorInfo, error) { var pieces []abi.PieceInfo sealTimings := make([]SealingResult, numSectors) sealedSectors := make([]abi.SectorInfo, numSectors) - if numSectors%parallelism != 0 { + preCommit2Sema := make(chan struct{}, par.PreCommit2) + commitSema := make(chan struct{}, par.Commit) + + if numSectors%par.PreCommit1 != 0 { return nil, nil, fmt.Errorf("parallelism factor must cleanly divide numSectors") } @@ -481,10 +495,10 @@ func runSeals(sb *ffiwrapper.Sealer, sbfs *basicfs.Provider, numSectors, paralle sealTimings[i-1].AddPiece = time.Since(start) } - sectorsPerWorker := numSectors / parallelism + sectorsPerWorker := numSectors / par.PreCommit1 - errs := make(chan error, parallelism) - for wid := 0; wid < parallelism; wid++ { + errs := make(chan error, par.PreCommit1) + for wid := 0; wid < par.PreCommit1; wid++ { go func(worker int) { sealerr := func() error { start := 1 + (worker * sectorsPerWorker) @@ -510,6 +524,8 @@ func runSeals(sb *ffiwrapper.Sealer, sbfs *basicfs.Provider, numSectors, paralle precommit1 := time.Now() + preCommit2Sema <- struct{}{} + pc2Start := time.Now() log.Infof("[%d] Running replication(2)...", i) cids, err := sb.SealPreCommit2(context.TODO(), sid, pc1o) if err != nil { @@ -517,6 +533,7 @@ func runSeals(sb *ffiwrapper.Sealer, sbfs *basicfs.Provider, numSectors, paralle } precommit2 := time.Now() + <-preCommit2Sema sealedSectors[ix] = abi.SectorInfo{ SealProof: sb.SealProofType(), @@ -529,6 +546,8 @@ func runSeals(sb *ffiwrapper.Sealer, sbfs *basicfs.Provider, numSectors, paralle Value: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 255}, } + commitSema <- struct{}{} + commitStart := time.Now() log.Infof("[%d] Generating PoRep for sector (1)", i) c1o, err := sb.SealCommit1(context.TODO(), sid, ticket, seed.Value, pieces, cids) if err != nil { @@ -565,6 +584,7 @@ func runSeals(sb *ffiwrapper.Sealer, sbfs *basicfs.Provider, numSectors, paralle } sealcommit2 := time.Now() + <-commitSema if !skipc2 { svi := abi.SealVerifyInfo{ @@ -611,8 +631,8 @@ func runSeals(sb *ffiwrapper.Sealer, sbfs *basicfs.Provider, numSectors, paralle unseal := time.Now() sealTimings[ix].PreCommit1 = precommit1.Sub(start) - sealTimings[ix].PreCommit2 = precommit2.Sub(precommit1) - sealTimings[ix].Commit1 = sealcommit1.Sub(precommit2) + sealTimings[ix].PreCommit2 = precommit2.Sub(pc2Start) + sealTimings[ix].Commit1 = sealcommit1.Sub(commitStart) sealTimings[ix].Commit2 = sealcommit2.Sub(sealcommit1) sealTimings[ix].Verify = verifySeal.Sub(sealcommit2) sealTimings[ix].Unseal = unseal.Sub(verifySeal) @@ -627,7 +647,7 @@ func runSeals(sb *ffiwrapper.Sealer, sbfs *basicfs.Provider, numSectors, paralle }(wid) } - for i := 0; i < parallelism; i++ { + for i := 0; i < par.PreCommit1; i++ { err := <-errs if err != nil { return nil, nil, err diff --git a/go.mod b/go.mod index 13b68200b..d5e684e46 100644 --- a/go.mod +++ b/go.mod @@ -126,6 +126,4 @@ require ( replace github.com/golangci/golangci-lint => github.com/golangci/golangci-lint v1.18.0 -replace github.com/filecoin-project/specs-actors => ../specs-actors - replace github.com/filecoin-project/filecoin-ffi => ./extern/filecoin-ffi diff --git a/go.sum b/go.sum index f28646534..68b7fbd1f 100644 --- a/go.sum +++ b/go.sum @@ -215,11 +215,14 @@ github.com/fatih/color v1.8.0/go.mod h1:3l45GVGkyrnYNl9HoIjnp2NnNWvh6hLAqD8yTfGj github.com/fd/go-nat v1.0.0/go.mod h1:BTBu/CKvMmOMUPkKVef1pngt2WFH/lg7E6yQnulfp6E= github.com/filecoin-project/chain-validation v0.0.6-0.20200615191232-6be1a8c6ed09 h1:GuiNSEZ9nc05LUpKhABw/SO6t9wqCfsJX1D0ByWQjkc= github.com/filecoin-project/chain-validation v0.0.6-0.20200615191232-6be1a8c6ed09/go.mod h1:HEJn6kOXMNhCNBYNTO/lrEI7wSgqCOR6hN5ecfYUnC8= +github.com/filecoin-project/go-address v0.0.0-20200107215422-da8eea2842b5/go.mod h1:SAOwJoakQ8EPjwNIsiakIQKsoKdkcbx8U3IapgCg9R0= github.com/filecoin-project/go-address v0.0.2-0.20200218010043-eb9bb40ed5be/go.mod h1:SAOwJoakQ8EPjwNIsiakIQKsoKdkcbx8U3IapgCg9R0= github.com/filecoin-project/go-address v0.0.2-0.20200504173055-8b6f2fb2b3ef h1:Wi5E+P1QfHP8IF27eUiTx5vYfqQZwfPxzq3oFEq8w8U= github.com/filecoin-project/go-address v0.0.2-0.20200504173055-8b6f2fb2b3ef/go.mod h1:SrA+pWVoUivqKOfC+ckVYbx41hWz++HxJcrlmHNnebU= +github.com/filecoin-project/go-amt-ipld/v2 v2.0.1-0.20200131012142-05d80eeccc5e/go.mod h1:boRtQhzmxNocrMxOXo1NYn4oUc1NGvR8tEa79wApNXg= github.com/filecoin-project/go-amt-ipld/v2 v2.0.1-0.20200424220931-6263827e49f2 h1:jamfsxfK0Q9yCMHt8MPWx7Aa/O9k2Lve8eSc6FILYGQ= github.com/filecoin-project/go-amt-ipld/v2 v2.0.1-0.20200424220931-6263827e49f2/go.mod h1:boRtQhzmxNocrMxOXo1NYn4oUc1NGvR8tEa79wApNXg= +github.com/filecoin-project/go-bitfield v0.0.0-20200416002808-b3ee67ec9060/go.mod h1:iodsLxOFZnqKtjj2zkgqzoGNrv6vUqj69AT/J8DKXEw= github.com/filecoin-project/go-bitfield v0.0.1/go.mod h1:Ry9/iUlWSyjPUzlAvdnfy4Gtvrq4kWmWDztCU1yEgJY= github.com/filecoin-project/go-bitfield v0.0.2-0.20200518150651-562fdb554b6e h1:gkG/7G+iKy4He+IiQNeQn+nndFznb/vCoOR8iRQsm60= github.com/filecoin-project/go-bitfield v0.0.2-0.20200518150651-562fdb554b6e/go.mod h1:Ry9/iUlWSyjPUzlAvdnfy4Gtvrq4kWmWDztCU1yEgJY= @@ -252,6 +255,11 @@ github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b/ github.com/filecoin-project/sector-storage v0.0.0-20200615154852-728a47ab99d6/go.mod h1:M59QnAeA/oV+Z8oHFLoNpGMv0LZ8Rll+vHVXX7GirPM= github.com/filecoin-project/sector-storage v0.0.0-20200615192001-42c9e08595b7 h1:cjsOpQKvZosPx9/qqq2bucHVdRyXzvBR1f37atiR3/0= github.com/filecoin-project/sector-storage v0.0.0-20200615192001-42c9e08595b7/go.mod h1:M59QnAeA/oV+Z8oHFLoNpGMv0LZ8Rll+vHVXX7GirPM= +github.com/filecoin-project/specs-actors v0.0.0-20200210130641-2d1fbd8672cf/go.mod h1:xtDZUB6pe4Pksa/bAJbJ693OilaC5Wbot9jMhLm3cZA= +github.com/filecoin-project/specs-actors v0.3.0/go.mod h1:nQYnFbQ7Y0bHZyq6HDEuVlCPR+U3z5Q3wMOQ+2aiV+Y= +github.com/filecoin-project/specs-actors v0.6.0/go.mod h1:dRdy3cURykh2R8O/DKqy8olScl70rmIS7GrB4hB1IDY= +github.com/filecoin-project/specs-actors v0.6.1 h1:rhHlEzqcuuQU6oKc4csuq+/kQBDZ4EXtSomoN2XApCA= +github.com/filecoin-project/specs-actors v0.6.1/go.mod h1:dRdy3cURykh2R8O/DKqy8olScl70rmIS7GrB4hB1IDY= github.com/filecoin-project/specs-storage v0.1.0 h1:PkDgTOT5W5Ao7752onjDl4QSv+sgOVdJbvFjOnD5w94= github.com/filecoin-project/specs-storage v0.1.0/go.mod h1:Pr5ntAaxsh+sLG/LYiL4tKzvA83Vk5vLODYhfNwOg7k= github.com/filecoin-project/storage-fsm v0.0.0-20200615162749-494c3bc48743 h1:a8f1p6UdeD+ZINBKJN4FhEos8uaKeASOAabq5RCpQdg= @@ -1321,6 +1329,7 @@ github.com/whyrusleeping/bencher v0.0.0-20190829221104-bb6607aa8bba/go.mod h1:CH github.com/whyrusleeping/cbor-gen v0.0.0-20191212224538-d370462a7e8a/go.mod h1:xdlJQaiqipF0HW+Mzpg7XRM3fWbGvfgFlcppuvlkIvY= github.com/whyrusleeping/cbor-gen v0.0.0-20191216205031-b047b6acb3c0/go.mod h1:xdlJQaiqipF0HW+Mzpg7XRM3fWbGvfgFlcppuvlkIvY= github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI= +github.com/whyrusleeping/cbor-gen v0.0.0-20200206220010-03c9665e2a66/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI= github.com/whyrusleeping/cbor-gen v0.0.0-20200402171437-3d27c146c105/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI= github.com/whyrusleeping/cbor-gen v0.0.0-20200414195334-429a0b5e922e/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI= github.com/whyrusleeping/cbor-gen v0.0.0-20200501014322-5f9941ef88e0/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI= From 5c5a3f22648bd410e82fc2fac3e37914454f9f26 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Wed, 17 Jun 2020 20:04:28 +0200 Subject: [PATCH 31/96] go mod tidy Signed-off-by: Jakub Sztandera --- go.sum | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.sum b/go.sum index 14308aedb..c48bc2071 100644 --- a/go.sum +++ b/go.sum @@ -259,8 +259,6 @@ github.com/filecoin-project/specs-actors v0.0.0-20200210130641-2d1fbd8672cf/go.m github.com/filecoin-project/specs-actors v0.3.0/go.mod h1:nQYnFbQ7Y0bHZyq6HDEuVlCPR+U3z5Q3wMOQ+2aiV+Y= github.com/filecoin-project/specs-actors v0.6.0 h1:IepUsmDGY60QliENVTkBTAkwqGWw9kNbbHOcU/9oiC0= github.com/filecoin-project/specs-actors v0.6.0/go.mod h1:dRdy3cURykh2R8O/DKqy8olScl70rmIS7GrB4hB1IDY= -github.com/filecoin-project/specs-actors v0.6.1 h1:rhHlEzqcuuQU6oKc4csuq+/kQBDZ4EXtSomoN2XApCA= -github.com/filecoin-project/specs-actors v0.6.1/go.mod h1:dRdy3cURykh2R8O/DKqy8olScl70rmIS7GrB4hB1IDY= github.com/filecoin-project/specs-actors v0.6.2-0.20200617175406-de392ca14121 h1:oRA+b4iN4H86xXDXbU3TOyvmBZp7//c5VqTc0oJ6nLg= github.com/filecoin-project/specs-actors v0.6.2-0.20200617175406-de392ca14121/go.mod h1:dRdy3cURykh2R8O/DKqy8olScl70rmIS7GrB4hB1IDY= github.com/filecoin-project/specs-storage v0.1.0 h1:PkDgTOT5W5Ao7752onjDl4QSv+sgOVdJbvFjOnD5w94= From 290512ee68c5f57904cb93034add64a3d2718fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 17 Jun 2020 20:42:08 +0200 Subject: [PATCH 32/96] Update storage FSM with a bunch of fixes --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c2471b82a..dea622198 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/filecoin-project/sector-storage v0.0.0-20200615192001-42c9e08595b7 github.com/filecoin-project/specs-actors v0.6.2-0.20200617175406-de392ca14121 github.com/filecoin-project/specs-storage v0.1.0 - github.com/filecoin-project/storage-fsm v0.0.0-20200615162749-494c3bc48743 + github.com/filecoin-project/storage-fsm v0.0.0-20200617183754-4380106d3e94 github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1 github.com/go-kit/kit v0.10.0 github.com/go-ole/go-ole v1.2.4 // indirect diff --git a/go.sum b/go.sum index c48bc2071..11848416b 100644 --- a/go.sum +++ b/go.sum @@ -263,8 +263,8 @@ github.com/filecoin-project/specs-actors v0.6.2-0.20200617175406-de392ca14121 h1 github.com/filecoin-project/specs-actors v0.6.2-0.20200617175406-de392ca14121/go.mod h1:dRdy3cURykh2R8O/DKqy8olScl70rmIS7GrB4hB1IDY= github.com/filecoin-project/specs-storage v0.1.0 h1:PkDgTOT5W5Ao7752onjDl4QSv+sgOVdJbvFjOnD5w94= github.com/filecoin-project/specs-storage v0.1.0/go.mod h1:Pr5ntAaxsh+sLG/LYiL4tKzvA83Vk5vLODYhfNwOg7k= -github.com/filecoin-project/storage-fsm v0.0.0-20200615162749-494c3bc48743 h1:a8f1p6UdeD+ZINBKJN4FhEos8uaKeASOAabq5RCpQdg= -github.com/filecoin-project/storage-fsm v0.0.0-20200615162749-494c3bc48743/go.mod h1:q1YCutTSMq/yGYvDPHReT37bPfDLHltnwJutzR9kOY0= +github.com/filecoin-project/storage-fsm v0.0.0-20200617183754-4380106d3e94 h1:zPKiZPMgkFF0Lq13hsk8lcWlxeVAs6vvJaa3uHn9v70= +github.com/filecoin-project/storage-fsm v0.0.0-20200617183754-4380106d3e94/go.mod h1:q1YCutTSMq/yGYvDPHReT37bPfDLHltnwJutzR9kOY0= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= From c7e470bd5eda2bba34e81cd06be948ba24bb2d86 Mon Sep 17 00:00:00 2001 From: Jim Pick Date: Wed, 17 Jun 2020 12:49:23 -0700 Subject: [PATCH 33/96] Spelling: s/winnnig/winning/ It was bugging me. :-) --- cmd/lotus-bench/main.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/cmd/lotus-bench/main.go b/cmd/lotus-bench/main.go index a10c3c00e..e3d213a86 100644 --- a/cmd/lotus-bench/main.go +++ b/cmd/lotus-bench/main.go @@ -307,7 +307,7 @@ var sealBenchCmd = &cli.Command{ return err } - winnnigpost1 := time.Now() + winningpost1 := time.Now() log.Info("computing winning post snark (hot)") proof2, err := sb.GenerateWinningPoSt(context.TODO(), mid, candidates, challenge[:]) @@ -331,7 +331,7 @@ var sealBenchCmd = &cli.Command{ log.Error("post verification failed") } - verifyWinnnigPost1 := time.Now() + verifyWinningPost1 := time.Now() pvi2 := abi.WinningPoStVerifyInfo{ Randomness: abi.PoStRandomness(challenge[:]), @@ -398,10 +398,10 @@ var sealBenchCmd = &cli.Command{ verifyWindowpost2 := time.Now() bo.PostGenerateCandidates = gencandidates.Sub(beforePost) - bo.PostWinningProofCold = winnnigpost1.Sub(gencandidates) - bo.PostWinningProofHot = winnningpost2.Sub(winnnigpost1) - bo.VerifyWinningPostCold = verifyWinnnigPost1.Sub(winnningpost2) - bo.VerifyWinningPostHot = verifyWinningPost2.Sub(verifyWinnnigPost1) + bo.PostWinningProofCold = winningpost1.Sub(gencandidates) + bo.PostWinningProofHot = winnningpost2.Sub(winningpost1) + bo.VerifyWinningPostCold = verifyWinningPost1.Sub(winnningpost2) + bo.VerifyWinningPostHot = verifyWinningPost2.Sub(verifyWinningPost1) bo.PostWindowProofCold = windowpost1.Sub(verifyWinningPost2) bo.PostWindowProofHot = windowpost2.Sub(windowpost1) @@ -432,10 +432,10 @@ var sealBenchCmd = &cli.Command{ } if !c.Bool("skip-commit2") { fmt.Printf("generate candidates: %s (%s)\n", bo.PostGenerateCandidates, bps(bo.SectorSize*abi.SectorSize(len(bo.SealingResults)), bo.PostGenerateCandidates)) - fmt.Printf("compute winnnig post proof (cold): %s\n", bo.PostWinningProofCold) - fmt.Printf("compute winnnig post proof (hot): %s\n", bo.PostWinningProofHot) - fmt.Printf("verify winnnig post proof (cold): %s\n", bo.VerifyWinningPostCold) - fmt.Printf("verify winnnig post proof (hot): %s\n\n", bo.VerifyWinningPostHot) + fmt.Printf("compute winning post proof (cold): %s\n", bo.PostWinningProofCold) + fmt.Printf("compute winning post proof (hot): %s\n", bo.PostWinningProofHot) + fmt.Printf("verify winning post proof (cold): %s\n", bo.VerifyWinningPostCold) + fmt.Printf("verify winning post proof (hot): %s\n\n", bo.VerifyWinningPostHot) fmt.Printf("compute window post proof (cold): %s\n", bo.PostWindowProofCold) fmt.Printf("compute window post proof (hot): %s\n", bo.PostWindowProofHot) From c02d8225a8223ea951e359e42290d9ec16c9c21f Mon Sep 17 00:00:00 2001 From: Travis Person Date: Wed, 17 Jun 2020 23:01:26 +0000 Subject: [PATCH 34/96] Bootstrap peers --- build/bootstrap/bootstrappers.pi | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/build/bootstrap/bootstrappers.pi b/build/bootstrap/bootstrappers.pi index 61b7dede3..0854ac0ed 100644 --- a/build/bootstrap/bootstrappers.pi +++ b/build/bootstrap/bootstrappers.pi @@ -1,12 +1,12 @@ -/dns4/bootstrap-0-sin.fil-test.net/tcp/1347/p2p/12D3KooWKNF7vNFEhnvB45E9mw2B5z6t419W3ziZPLdUDVnLLKGs -/ip4/86.109.15.57/tcp/1347/p2p/12D3KooWKNF7vNFEhnvB45E9mw2B5z6t419W3ziZPLdUDVnLLKGs -/dns4/bootstrap-0-dfw.fil-test.net/tcp/1347/p2p/12D3KooWECJTm7RUPyGfNbRwm6y2fK4wA7EB8rDJtWsq5AKi7iDr -/ip4/139.178.84.45/tcp/1347/p2p/12D3KooWECJTm7RUPyGfNbRwm6y2fK4wA7EB8rDJtWsq5AKi7iDr -/dns4/bootstrap-0-fra.fil-test.net/tcp/1347/p2p/12D3KooWC7MD6m7iNCuDsYtNr7xVtazihyVUizBbhmhEiyMAm9ym -/ip4/136.144.49.17/tcp/1347/p2p/12D3KooWC7MD6m7iNCuDsYtNr7xVtazihyVUizBbhmhEiyMAm9ym -/dns4/bootstrap-1-sin.fil-test.net/tcp/1347/p2p/12D3KooWD8eYqsKcEMFax6EbWN3rjA7qFsxCez2rmN8dWqkzgNaN -/ip4/86.109.15.55/tcp/1347/p2p/12D3KooWD8eYqsKcEMFax6EbWN3rjA7qFsxCez2rmN8dWqkzgNaN -/dns4/bootstrap-1-dfw.fil-test.net/tcp/1347/p2p/12D3KooWLB3RR8frLAmaK4ntHC2dwrAjyGzQgyUzWxAum1FxyyqD -/ip4/139.178.84.41/tcp/1347/p2p/12D3KooWLB3RR8frLAmaK4ntHC2dwrAjyGzQgyUzWxAum1FxyyqD -/dns4/bootstrap-1-fra.fil-test.net/tcp/1347/p2p/12D3KooWGPDJAw3HW4uVU3JEQBfFaZ1kdpg4HvvwRMVpUYbzhsLQ -/ip4/136.144.49.131/tcp/1347/p2p/12D3KooWGPDJAw3HW4uVU3JEQBfFaZ1kdpg4HvvwRMVpUYbzhsLQ +/dns4/bootstrap-0-sin.fil-test.net/tcp/1347/p2p/12D3KooWPdUquftaQvoQEtEdsRBAhwD6jopbF2oweVTzR59VbHEd +/ip4/86.109.15.57/tcp/1347/p2p/12D3KooWPdUquftaQvoQEtEdsRBAhwD6jopbF2oweVTzR59VbHEd +/dns4/bootstrap-0-dfw.fil-test.net/tcp/1347/p2p/12D3KooWQSCkHCzosEyrh8FgYfLejKgEPM5VB6qWzZE3yDAuXn8d +/ip4/139.178.84.45/tcp/1347/p2p/12D3KooWQSCkHCzosEyrh8FgYfLejKgEPM5VB6qWzZE3yDAuXn8d +/dns4/bootstrap-0-fra.fil-test.net/tcp/1347/p2p/12D3KooWEXN2eQmoyqnNjde9PBAQfQLHN67jcEdWU6JougWrgXJK +/ip4/136.144.49.17/tcp/1347/p2p/12D3KooWEXN2eQmoyqnNjde9PBAQfQLHN67jcEdWU6JougWrgXJK +/dns4/bootstrap-1-sin.fil-test.net/tcp/1347/p2p/12D3KooWLmJkZd33mJhjg5RrpJ6NFep9SNLXWc4uVngV4TXKwzYw +/ip4/86.109.15.123/tcp/1347/p2p/12D3KooWLmJkZd33mJhjg5RrpJ6NFep9SNLXWc4uVngV4TXKwzYw +/dns4/bootstrap-1-dfw.fil-test.net/tcp/1347/p2p/12D3KooWGXLHjiz6pTRu7x2pkgTVCoxcCiVxcNLpMnWcJ3JiNEy5 +/ip4/139.178.86.3/tcp/1347/p2p/12D3KooWGXLHjiz6pTRu7x2pkgTVCoxcCiVxcNLpMnWcJ3JiNEy5 +/dns4/bootstrap-1-fra.fil-test.net/tcp/1347/p2p/12D3KooW9szZmKttS9A1FafH3Zc2pxKwwmvCWCGKkRP4KmbhhC4R +/ip4/136.144.49.131/tcp/1347/p2p/12D3KooW9szZmKttS9A1FafH3Zc2pxKwwmvCWCGKkRP4KmbhhC4R From 2bf781d5918c8f1078e5b0aacc818d9efe04d26f Mon Sep 17 00:00:00 2001 From: Jim Pick Date: Wed, 17 Jun 2020 18:04:46 -0700 Subject: [PATCH 35/96] Fix bad link in hardware mining docs This fixes a bad link. There's also a link to the Github issue thread for results that still works, but is a bit stale (for proofs before v20). I didn't attempt to fix that as there doesn't appear to be a thread for v27 proofs. --- documentation/en/hardware-mining.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/en/hardware-mining.md b/documentation/en/hardware-mining.md index 0379cc4e1..459b6074b 100644 --- a/documentation/en/hardware-mining.md +++ b/documentation/en/hardware-mining.md @@ -51,4 +51,4 @@ To get the number of cores for your GPU, you will need to check your card’s sp ## Benchmarking -Here is a [benchmarking tool](https://github.com/filecoin-project/lotus/tree/testnet-staging/cmd/lotus-bench) and a [GitHub issue thread](https://github.com/filecoin-project/lotus/issues/694) for those who wish to experiment with and contribute hardware setups for the **Filecoin Testnet**. +Here is a [benchmarking tool](https://github.com/filecoin-project/lotus/tree/master/cmd/lotus-bench) and a [GitHub issue thread](https://github.com/filecoin-project/lotus/issues/694) for those who wish to experiment with and contribute hardware setups for the **Filecoin Testnet**. From 324b659d33fe25cb09ff59c44e19425c6b6bc9cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 18 Jun 2020 09:34:28 +0200 Subject: [PATCH 36/96] Update sector-storage with 32G checkFiles fix --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index dea622198..68ac9a884 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/filecoin-project/go-paramfetch v0.0.2-0.20200605171344-fcac609550ca github.com/filecoin-project/go-statestore v0.1.0 github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b - github.com/filecoin-project/sector-storage v0.0.0-20200615192001-42c9e08595b7 + github.com/filecoin-project/sector-storage v0.0.0-20200618073200-d9de9b7cb4b4 github.com/filecoin-project/specs-actors v0.6.2-0.20200617175406-de392ca14121 github.com/filecoin-project/specs-storage v0.1.0 github.com/filecoin-project/storage-fsm v0.0.0-20200617183754-4380106d3e94 diff --git a/go.sum b/go.sum index dd8e57c7b..e21c27f74 100644 --- a/go.sum +++ b/go.sum @@ -253,8 +253,8 @@ github.com/filecoin-project/go-statestore v0.1.0/go.mod h1:LFc9hD+fRxPqiHiaqUEZO github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b h1:fkRZSPrYpk42PV3/lIXiL0LHetxde7vyYYvSsttQtfg= github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b/go.mod h1:Q0GQOBtKf1oE10eSXSlhN45kDBdGvEcVOqMiffqX+N8= github.com/filecoin-project/sector-storage v0.0.0-20200615154852-728a47ab99d6/go.mod h1:M59QnAeA/oV+Z8oHFLoNpGMv0LZ8Rll+vHVXX7GirPM= -github.com/filecoin-project/sector-storage v0.0.0-20200615192001-42c9e08595b7 h1:cjsOpQKvZosPx9/qqq2bucHVdRyXzvBR1f37atiR3/0= -github.com/filecoin-project/sector-storage v0.0.0-20200615192001-42c9e08595b7/go.mod h1:M59QnAeA/oV+Z8oHFLoNpGMv0LZ8Rll+vHVXX7GirPM= +github.com/filecoin-project/sector-storage v0.0.0-20200618073200-d9de9b7cb4b4 h1:lQC8Fbyn31/H4QxYAYwVV3PYZ9vS61EmjktZc5CaiYs= +github.com/filecoin-project/sector-storage v0.0.0-20200618073200-d9de9b7cb4b4/go.mod h1:M59QnAeA/oV+Z8oHFLoNpGMv0LZ8Rll+vHVXX7GirPM= github.com/filecoin-project/specs-actors v0.0.0-20200210130641-2d1fbd8672cf/go.mod h1:xtDZUB6pe4Pksa/bAJbJ693OilaC5Wbot9jMhLm3cZA= github.com/filecoin-project/specs-actors v0.3.0/go.mod h1:nQYnFbQ7Y0bHZyq6HDEuVlCPR+U3z5Q3wMOQ+2aiV+Y= github.com/filecoin-project/specs-actors v0.6.0/go.mod h1:dRdy3cURykh2R8O/DKqy8olScl70rmIS7GrB4hB1IDY= From 55d9ad0b550dfb16397c55c949839213600c7cdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 18 Jun 2020 14:30:00 +0200 Subject: [PATCH 37/96] Fix docgen --- api/docgen/docgen.go | 37 ++++++++++++++++++++++--------------- build/params_testnet.go | 4 ++-- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index 4cd982aa7..ced04e7be 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -73,9 +73,13 @@ func init() { addExample(bitfield.NewFromSet([]uint64{5})) addExample(abi.RegisteredSealProof_StackedDrg32GiBV1) + addExample(abi.RegisteredPoStProof_StackedDrgWindow32GiBV1) addExample(abi.ChainEpoch(10101)) addExample(crypto.SigTypeBLS) addExample(int64(9)) + addExample(12.3) + addExample(123) + addExample(uintptr(0)) addExample(abi.MethodNum(1)) addExample(exitcode.ExitCode(0)) addExample(crypto.DomainSeparationTag_ElectionProofProduction) @@ -94,17 +98,17 @@ func init() { addExample(api.PCHInbound) addExample(time.Minute) addExample(&types.ExecutionTrace{ - Msg: exampleValue(reflect.TypeOf(&types.Message{})).(*types.Message), - MsgRct: exampleValue(reflect.TypeOf(&types.MessageReceipt{})).(*types.MessageReceipt), + Msg: exampleValue(reflect.TypeOf(&types.Message{}), nil).(*types.Message), + MsgRct: exampleValue(reflect.TypeOf(&types.MessageReceipt{}), nil).(*types.MessageReceipt), }) addExample(map[string]types.Actor{ - "t01236": exampleValue(reflect.TypeOf(types.Actor{})).(types.Actor), + "t01236": exampleValue(reflect.TypeOf(types.Actor{}), nil).(types.Actor), }) addExample(map[string]api.MarketDeal{ - "t026363": exampleValue(reflect.TypeOf(api.MarketDeal{})).(api.MarketDeal), + "t026363": exampleValue(reflect.TypeOf(api.MarketDeal{}), nil).(api.MarketDeal), }) addExample(map[string]api.MarketBalance{ - "t026363": exampleValue(reflect.TypeOf(api.MarketBalance{})).(api.MarketBalance), + "t026363": exampleValue(reflect.TypeOf(api.MarketBalance{}), nil).(api.MarketBalance), }) maddr, err := multiaddr.NewMultiaddr("/ip4/52.36.61.156/tcp/1347/p2p/12D3KooWFETiESTf1v4PGUvtnxMAcEFMzLZbJGg4tjWfGEimYior") @@ -117,7 +121,7 @@ func init() { } -func exampleValue(t reflect.Type) interface{} { +func exampleValue(t, parent reflect.Type) interface{} { v, ok := ExampleValues[t] if ok { return v @@ -126,25 +130,25 @@ func exampleValue(t reflect.Type) interface{} { switch t.Kind() { case reflect.Slice: out := reflect.New(t).Elem() - reflect.Append(out, reflect.ValueOf(exampleValue(t.Elem()))) + reflect.Append(out, reflect.ValueOf(exampleValue(t.Elem(), t))) return out.Interface() case reflect.Chan: - return exampleValue(t.Elem()) + return exampleValue(t.Elem(), nil) case reflect.Struct: - es := exampleStruct(t) + es := exampleStruct(t, parent) v := reflect.ValueOf(es).Elem().Interface() ExampleValues[t] = v return v case reflect.Array: out := reflect.New(t).Elem() for i := 0; i < t.Len(); i++ { - out.Index(i).Set(reflect.ValueOf(exampleValue(t.Elem()))) + out.Index(i).Set(reflect.ValueOf(exampleValue(t.Elem(), t))) } return out.Interface() case reflect.Ptr: if t.Elem().Kind() == reflect.Struct { - es := exampleStruct(t.Elem()) + es := exampleStruct(t.Elem(), t) //ExampleValues[t] = es return es } @@ -155,12 +159,15 @@ func exampleValue(t reflect.Type) interface{} { panic(fmt.Sprintf("No example value for type: %s", t)) } -func exampleStruct(t reflect.Type) interface{} { +func exampleStruct(t, parent reflect.Type) interface{} { ns := reflect.New(t) for i := 0; i < t.NumField(); i++ { f := t.Field(i) + if f.Type == parent { + continue + } if strings.Title(f.Name) == f.Name { - ns.Elem().Field(i).Set(reflect.ValueOf(exampleValue(f.Type))) + ns.Elem().Field(i).Set(reflect.ValueOf(exampleValue(f.Type, t))) } } @@ -286,7 +293,7 @@ func main() { ft := m.Func.Type() for j := 2; j < ft.NumIn(); j++ { inp := ft.In(j) - args = append(args, exampleValue(inp)) + args = append(args, exampleValue(inp, nil)) } v, err := json.Marshal(args) @@ -294,7 +301,7 @@ func main() { panic(err) } - outv := exampleValue(ft.Out(0)) + outv := exampleValue(ft.Out(0), nil) ov, err := json.Marshal(outv) if err != nil { diff --git a/build/params_testnet.go b/build/params_testnet.go index 202dfb231..69884f3f8 100644 --- a/build/params_testnet.go +++ b/build/params_testnet.go @@ -14,8 +14,8 @@ import ( func init() { power.ConsensusMinerMinPower = big.NewInt(1024 << 30) miner.SupportedProofTypes = map[abi.RegisteredSealProof]struct{}{ - abi.RegisteredSealProof_StackedDrg32GiBV1: {}, - abi.RegisteredSealProof_StackedDrg64GiBV1: {}, + abi.RegisteredSealProof_StackedDrg32GiBV1: {}, + abi.RegisteredSealProof_StackedDrg64GiBV1: {}, } } From 90fbbea3727659b604bfd7ca6cf1c57c84026c25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 18 Jun 2020 14:37:36 +0200 Subject: [PATCH 38/96] docgen: Make larger inputs/outputs more readable --- api/docgen/docgen.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index ced04e7be..39892c820 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -296,14 +296,14 @@ func main() { args = append(args, exampleValue(inp, nil)) } - v, err := json.Marshal(args) + v, err := json.MarshalIndent(args, "", " ") if err != nil { panic(err) } outv := exampleValue(ft.Out(0), nil) - ov, err := json.Marshal(outv) + ov, err := json.MarshalIndent(outv, "", " ") if err != nil { panic(err) } @@ -338,8 +338,17 @@ func main() { fmt.Printf("### %s\n", m.Name) fmt.Printf("%s\n\n", m.Comment) - fmt.Printf("Inputs: `%s`\n\n", m.InputExample) - fmt.Printf("Response: `%s`\n\n", m.ResponseExample) + if strings.Count(m.InputExample, "\n") > 0 { + fmt.Printf("Inputs:\n```json\n%s\n```\n\n", m.InputExample) + } else { + fmt.Printf("Inputs: `%s`\n\n", m.InputExample) + } + + if strings.Count(m.ResponseExample, "\n") > 0 { + fmt.Printf("Response:\n```json\n%s\n```\n\n", m.ResponseExample) + } else { + fmt.Printf("Response: `%s`\n\n", m.ResponseExample) + } } } } From 382734b057888fb02d7fd3039eb90b53d631a722 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 18 Jun 2020 14:56:00 +0200 Subject: [PATCH 39/96] docgen: Generate simple index --- api/api_common.go | 8 ++++++-- api/docgen/docgen.go | 9 +++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/api/api_common.go b/api/api_common.go index 6d47e35f7..aa63e9815 100644 --- a/api/api_common.go +++ b/api/api_common.go @@ -13,11 +13,13 @@ import ( ) type Common interface { - // Auth + + // MethodGroup: Auth + AuthVerify(ctx context.Context, token string) ([]auth.Permission, error) AuthNew(ctx context.Context, perms []auth.Permission) ([]byte, error) - // network + // MethodGroup: Net NetConnectedness(context.Context, peer.ID) (network.Connectedness, error) NetPeers(context.Context) ([]peer.AddrInfo, error) @@ -27,6 +29,8 @@ type Common interface { NetFindPeer(context.Context, peer.ID) (peer.AddrInfo, error) NetPubsubScores(context.Context) ([]PubsubScore, error) + // MethodGroup: Common + // ID returns peerID of libp2p node backing this API ID(context.Context) (peer.ID, error) diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index 39892c820..f876e280e 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -325,6 +325,15 @@ func main() { return groupslice[i].GroupName < groupslice[j].GroupName }) + fmt.Printf("# Groups\n") + + for _, g := range groupslice { + fmt.Printf("* [%s](#%s)\n", g.GroupName, g.GroupName) + for _, method := range g.Methods { + fmt.Printf(" * [%s](#%s)\n", method.Name, method.Name) + } + } + for _, g := range groupslice { g := g fmt.Printf("## %s\n", g.GroupName) From c0a50a21432b8d82b71ae173137d2d096532b55f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 18 Jun 2020 15:44:47 +0200 Subject: [PATCH 40/96] api: Add a bunch of donstrings --- api/api_full.go | 77 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 5 deletions(-) diff --git a/api/api_full.go b/api/api_full.go index bbd28b39e..17b0fdce6 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -35,26 +35,71 @@ type FullNode interface { // ChainNotify returns channel with chain head updates // First message is guaranteed to be of len == 1, and type == 'current' ChainNotify(context.Context) (<-chan []*HeadChange, error) + // ChainHead returns the current head of the chain ChainHead(context.Context) (*types.TipSet, error) + // ChainGetRandomness is used to sample the chain for randomness ChainGetRandomness(ctx context.Context, tsk types.TipSetKey, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) (abi.Randomness, error) + // ChainGetBlock returns the block specified by the given CID ChainGetBlock(context.Context, cid.Cid) (*types.BlockHeader, error) ChainGetTipSet(context.Context, types.TipSetKey) (*types.TipSet, error) - ChainGetBlockMessages(context.Context, cid.Cid) (*BlockMessages, error) - ChainGetParentReceipts(context.Context, cid.Cid) ([]*types.MessageReceipt, error) - ChainGetParentMessages(context.Context, cid.Cid) ([]Message, error) + + // ChainGetBlockMessages returns messages stored in the specified block + ChainGetBlockMessages(ctx context.Context, blockCid cid.Cid) (*BlockMessages, error) + + // ChainGetParentReceipts returns receipts for messages in parent tipset of + // the specified block + ChainGetParentReceipts(ctx context.Context, blockCid cid.Cid) ([]*types.MessageReceipt, error) + + // ChainGetParentReceipts returns messages stored in parent tipset of the + // specified block + ChainGetParentMessages(ctx context.Context, blockCid cid.Cid) ([]Message, error) + + // ChainGetTipSetByHeight looks back for a tipset at the specified epoch. + // If there are no blocks at the specified epoch, a tipset at higher epoch + // will be returned ChainGetTipSetByHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) + + // ChainReadObj reads ipld nodes referenced by the specified CID from chain + // blockstore and returns raw bytes ChainReadObj(context.Context, cid.Cid) ([]byte, error) + + // ChainHasObj checks if a given CID exists in the chain blockstore ChainHasObj(context.Context, cid.Cid) (bool, error) ChainStatObj(context.Context, cid.Cid, cid.Cid) (ObjStat, error) + + // ChainSetHead forcefully sets current chain head. Use with caution ChainSetHead(context.Context, types.TipSetKey) error + + // ChainGetGenesis returns the genesis tipset ChainGetGenesis(context.Context) (*types.TipSet, error) + + // ChainTipSetWeight computes weight for the specified tipset ChainTipSetWeight(context.Context, types.TipSetKey) (types.BigInt, error) ChainGetNode(ctx context.Context, p string) (*IpldObject, error) + + // ChainGetMessage reads a message referenced by the specified CID from the + // chain blockstore ChainGetMessage(context.Context, cid.Cid) (*types.Message, error) + + // ChainGetPath returns a set of revert/apply operations needed to get from + // one tipset to another, for example: + //``` + // to + // ^ + // from tAA + // ^ ^ + // tBA tAB + // ^---*--^ + // ^ + // tRR + //``` + // Would return `[revert(tBA), apply(tAB), apply(tAA)]` ChainGetPath(ctx context.Context, from types.TipSetKey, to types.TipSetKey) ([]*HeadChange, error) + + // ChainExport returns a stream of bytes with CAR dump of chain data ChainExport(context.Context, types.TipSetKey) (<-chan []byte, error) // MethodGroup: Sync @@ -63,23 +108,45 @@ type FullNode interface { // SyncState returns the current status of the lotus sync system SyncState(context.Context) (*SyncState, error) + // SyncSubmitBlock can be used to submit a newly created block to the // network through this node SyncSubmitBlock(ctx context.Context, blk *types.BlockMsg) error + + // SyncIncomingBlocks returns a channel streaming incoming, potentially not + // yet synced block headers. SyncIncomingBlocks(ctx context.Context) (<-chan *types.BlockHeader, error) + + // SyncMarkBad marks a blocks as bad, meaning that it won't ever by synced. + // Use with extreme caution SyncMarkBad(ctx context.Context, bcid cid.Cid) error + + // SyncCheckBad checks if a block was marked as bad, and if it was, returns + // the reason SyncCheckBad(ctx context.Context, bcid cid.Cid) (string, error) // MethodGroup: Mpool // The Mpool methods are for interacting with the message pool. The message pool // manages all incoming and outgoing 'messages' going over the network. + // MpoolPending returns pending mempool messages MpoolPending(context.Context, types.TipSetKey) ([]*types.SignedMessage, error) + + // MpoolPush pushes a signed message to mempool MpoolPush(context.Context, *types.SignedMessage) (cid.Cid, error) - MpoolPushMessage(context.Context, *types.Message) (*types.SignedMessage, error) // get nonce, sign, push + + // MpoolPushMessage atomically assigns a nonce, signs, and pushes a message + // to mempool + MpoolPushMessage(context.Context, *types.Message) (*types.SignedMessage, error) + + // MpoolGetNonce gets next nonce for the specified sender. + // Note that this method may not be atomic. Use MpoolPushMessage instead MpoolGetNonce(context.Context, address.Address) (uint64, error) MpoolSub(context.Context) (<-chan MpoolUpdate, error) - MpoolEstimateGasPrice(context.Context, uint64, address.Address, int64, types.TipSetKey) (types.BigInt, error) + + // MpoolEstimateGasPrice estimates what gas price should be used for a + // message to have high likelihood of inclusion in `nblocksincl` epochs + MpoolEstimateGasPrice(ctx context.Context, nblocksincl uint64, sender address.Address, gaslimit int64, tsk types.TipSetKey) (types.BigInt, error) // MethodGroup: Miner From 997a46a90f02db165a86dab0025ae71fe23afe6e Mon Sep 17 00:00:00 2001 From: laser Date: Thu, 18 Jun 2020 10:32:32 -0700 Subject: [PATCH 41/96] wombat --- cmd/lotus-storage-miner/market.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/lotus-storage-miner/market.go b/cmd/lotus-storage-miner/market.go index 4dac236e4..cb49854cd 100644 --- a/cmd/lotus-storage-miner/market.go +++ b/cmd/lotus-storage-miner/market.go @@ -140,6 +140,8 @@ var getAskCmd = &cli.Command{ Action: func(cctx *cli.Context) error { ctx := lcli.DaemonContext(cctx) + fmt.Println("wombat") + fnapi, closer, err := lcli.GetFullNodeAPI(cctx) if err != nil { return err From b0edf924b429a99e25312dd0ee133b55561190fc Mon Sep 17 00:00:00 2001 From: laser Date: Thu, 18 Jun 2020 13:15:18 -0700 Subject: [PATCH 42/96] add commands for manipulating storage deal CID blacklist --- api/api_storage.go | 2 + api/apistruct/struct.go | 10 +++ cmd/lotus-storage-miner/market.go | 126 ++++++++++++++++++++++++++++++ node/builder.go | 2 + node/config/def.go | 4 + node/impl/storminer.go | 12 ++- node/modules/dtypes/miner.go | 11 +++ node/modules/storageminer.go | 83 ++++++++++++++------ 8 files changed, 225 insertions(+), 25 deletions(-) diff --git a/api/api_storage.go b/api/api_storage.go index 90de01fb9..0eda830dc 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -56,6 +56,8 @@ type StorageMiner interface { DealsImportData(ctx context.Context, dealPropCid cid.Cid, file string) error DealsList(ctx context.Context) ([]storagemarket.StorageDeal, error) DealsSetAcceptingStorageDeals(context.Context, bool) error + DealsBlacklist(context.Context) ([]cid.Cid, error) + DealsSetBlacklist(context.Context, []cid.Cid) error StorageAddLocal(ctx context.Context, path string) error } diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index 0d69174ab..43c0a423e 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -226,6 +226,8 @@ type StorageMinerStruct struct { DealsImportData func(ctx context.Context, dealPropCid cid.Cid, file string) error `perm:"write"` DealsList func(ctx context.Context) ([]storagemarket.StorageDeal, error) `perm:"read"` DealsSetAcceptingStorageDeals func(context.Context, bool) error `perm:"admin"` + DealsBlacklist func(context.Context) ([]cid.Cid, error) `perm:"admin"` + DealsSetBlacklist func(context.Context, []cid.Cid) error `perm:"read"` StorageAddLocal func(ctx context.Context, path string) error `perm:"admin"` } @@ -872,6 +874,14 @@ func (c *StorageMinerStruct) DealsSetAcceptingStorageDeals(ctx context.Context, return c.Internal.DealsSetAcceptingStorageDeals(ctx, b) } +func (c *StorageMinerStruct) DealsBlacklist(ctx context.Context) ([]cid.Cid, error) { + return c.Internal.DealsBlacklist(ctx) +} + +func (c *StorageMinerStruct) DealsSetBlacklist(ctx context.Context, cids []cid.Cid) error { + return c.Internal.DealsSetBlacklist(ctx, cids) +} + func (c *StorageMinerStruct) StorageAddLocal(ctx context.Context, path string) error { return c.Internal.StorageAddLocal(ctx, path) } diff --git a/cmd/lotus-storage-miner/market.go b/cmd/lotus-storage-miner/market.go index cb49854cd..f12bce62c 100644 --- a/cmd/lotus-storage-miner/market.go +++ b/cmd/lotus-storage-miner/market.go @@ -1,14 +1,18 @@ package main import ( + "bufio" "encoding/json" "fmt" "os" + "path/filepath" "text/tabwriter" "time" "github.com/docker/go-units" "github.com/ipfs/go-cid" + "github.com/ipfs/go-cidutil/cidenc" + "github.com/multiformats/go-multibase" "github.com/urfave/cli/v2" "golang.org/x/xerrors" @@ -20,6 +24,32 @@ import ( lcli "github.com/filecoin-project/lotus/cli" ) +var CidBaseFlag = cli.StringFlag{ + Name: "cid-base", + Hidden: true, + Value: "base32", + Usage: "Multibase encoding used for version 1 CIDs in output.", + DefaultText: "base32", +} + +// GetCidEncoder returns an encoder using the `cid-base` flag if provided, or +// the default (Base32) encoder if not. +func GetCidEncoder(cctx *cli.Context) (cidenc.Encoder, error) { + val := cctx.String("cid-base") + + e := cidenc.Encoder{Base: multibase.MustNewEncoder(multibase.Base32)} + + if val != "" { + var err error + e.Base, err = multibase.EncoderByName(val) + if err != nil { + return e, err + } + } + + return e, nil +} + var enableCmd = &cli.Command{ Name: "enable", Usage: "Configure the miner to consider storage deal proposals", @@ -199,6 +229,9 @@ var dealsCmd = &cli.Command{ disableCmd, setAskCmd, getAskCmd, + setBlacklistCmd, + getBlacklistCmd, + resetBlacklistCmd, }, } @@ -257,3 +290,96 @@ var dealsListCmd = &cli.Command{ return nil }, } + +var getBlacklistCmd = &cli.Command{ + Name: "get-blacklist", + Usage: "List the CIDs in the storage miner's blacklist", + Flags: []cli.Flag{ + &CidBaseFlag, + }, + Action: func(cctx *cli.Context) error { + api, closer, err := lcli.GetStorageMinerAPI(cctx) + if err != nil { + return err + } + defer closer() + + blacklist, err := api.DealsBlacklist(lcli.DaemonContext(cctx)) + if err != nil { + return err + } + + encoder, err := GetCidEncoder(cctx) + if err != nil { + return err + } + + for idx := range blacklist { + fmt.Println(encoder.Encode(blacklist[idx])) + } + + return nil + }, +} + +var setBlacklistCmd = &cli.Command{ + Name: "set-blacklist", + Usage: "Set the storage miner's list of blacklisted CIDs", + ArgsUsage: "[ (optional, will read from stdin if omitted)]", + Flags: []cli.Flag{}, + Action: func(cctx *cli.Context) error { + api, closer, err := lcli.GetStorageMinerAPI(cctx) + if err != nil { + return err + } + defer closer() + + scanner := bufio.NewScanner(os.Stdin) + if cctx.Args().Present() && cctx.Args().First() != "-" { + absPath, err := filepath.Abs(cctx.Args().First()) + if err != nil { + return err + } + + file, err := os.Open(absPath) + if err != nil { + log.Fatal(err) + } + defer file.Close() + + scanner = bufio.NewScanner(file) + } + + var blacklist []cid.Cid + for scanner.Scan() { + decoded, err := cid.Decode(scanner.Text()) + if err != nil { + return err + } + + blacklist = append(blacklist, decoded) + } + + err = scanner.Err() + if err != nil { + return err + } + + return api.DealsSetBlacklist(lcli.DaemonContext(cctx), blacklist) + }, +} + +var resetBlacklistCmd = &cli.Command{ + Name: "reset-blacklist", + Usage: "Remove all entries from the storage miner's blacklist", + Flags: []cli.Flag{}, + Action: func(cctx *cli.Context) error { + api, closer, err := lcli.GetStorageMinerAPI(cctx) + if err != nil { + return err + } + defer closer() + + return api.DealsSetBlacklist(lcli.DaemonContext(cctx), []cid.Cid{}) + }, +} diff --git a/node/builder.go b/node/builder.go index 536d81901..52e089bbe 100644 --- a/node/builder.go +++ b/node/builder.go @@ -314,6 +314,8 @@ func Online() Option { Override(new(dtypes.AcceptingStorageDealsConfigFunc), modules.NewAcceptingStorageDealsConfigFunc), Override(new(dtypes.SetAcceptingStorageDealsConfigFunc), modules.NewSetAcceptingStorageDealsConfigFunc), + Override(new(dtypes.StorageDealCidBlacklistConfigFunc), modules.NewStorageDealCidBlacklistConfigFunc), + Override(new(dtypes.SetStorageDealCidBlacklistConfigFunc), modules.NewSetStorageDealCidBlacklistConfigFunc), ), ) } diff --git a/node/config/def.go b/node/config/def.go index 5c46f77a4..b92ac3b4d 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -4,6 +4,8 @@ import ( "encoding" "time" + "github.com/ipfs/go-cid" + sectorstorage "github.com/filecoin-project/sector-storage" ) @@ -33,6 +35,7 @@ type StorageMiner struct { type DealmakingConfig struct { AcceptingStorageDeals bool + Blacklist []cid.Cid } // API contains configs for API endpoint @@ -121,6 +124,7 @@ func DefaultStorageMiner() *StorageMiner { Dealmaking: DealmakingConfig{ AcceptingStorageDeals: true, + Blacklist: []cid.Cid{}, }, } cfg.Common.API.ListenAddress = "/ip4/127.0.0.1/tcp/2345/http" diff --git a/node/impl/storminer.go b/node/impl/storminer.go index ed94e173d..2863a9d28 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -43,7 +43,9 @@ type StorageMinerAPI struct { StorageMgr *sectorstorage.Manager `optional:"true"` *stores.Index - SetAcceptingStorageDealsConfigFunc dtypes.SetAcceptingStorageDealsConfigFunc + SetAcceptingStorageDealsConfigFunc dtypes.SetAcceptingStorageDealsConfigFunc + StorageDealCidBlacklistConfigFunc dtypes.StorageDealCidBlacklistConfigFunc + SetStorageDealCidBlacklistConfigFunc dtypes.SetStorageDealCidBlacklistConfigFunc } func (sm *StorageMinerAPI) ServeRemote(w http.ResponseWriter, r *http.Request) { @@ -232,6 +234,14 @@ func (sm *StorageMinerAPI) DealsImportData(ctx context.Context, deal cid.Cid, fn return sm.StorageProvider.ImportDataForDeal(ctx, deal, fi) } +func (sm *StorageMinerAPI) DealsBlacklist(ctx context.Context) ([]cid.Cid, error) { + return sm.StorageDealCidBlacklistConfigFunc() +} + +func (sm *StorageMinerAPI) DealsSetBlacklist(ctx context.Context, cids []cid.Cid) error { + return sm.SetStorageDealCidBlacklistConfigFunc(cids) +} + func (sm *StorageMinerAPI) StorageAddLocal(ctx context.Context, path string) error { if sm.StorageMgr == nil { return xerrors.Errorf("no storage manager") diff --git a/node/modules/dtypes/miner.go b/node/modules/dtypes/miner.go index 5c761d3e5..04ae07a53 100644 --- a/node/modules/dtypes/miner.go +++ b/node/modules/dtypes/miner.go @@ -1,6 +1,8 @@ package dtypes import ( + "github.com/ipfs/go-cid" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/specs-actors/actors/abi" ) @@ -15,3 +17,12 @@ type AcceptingStorageDealsConfigFunc func() (bool, error) // SetAcceptingStorageDealsFunc is a function which is used to disable or enable // storage deal acceptance. type SetAcceptingStorageDealsConfigFunc func(bool) error + +// StorageDealCidBlacklistConfigFunc is a function which reads from miner config +// to obtain a list of CIDs for which the storage miner will not accept storage +// proposals. +type StorageDealCidBlacklistConfigFunc func() ([]cid.Cid, error) + +// SetStorageDealCidBlacklistConfigFunc is a function which is used to set a +// list of CIDs for which the storage miner will reject deal proposals. +type SetStorageDealCidBlacklistConfigFunc func([]cid.Cid) error diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index fce9f9e1f..a6ef6304e 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -8,6 +8,7 @@ import ( "github.com/ipfs/go-bitswap" "github.com/ipfs/go-bitswap/network" "github.com/ipfs/go-blockservice" + "github.com/ipfs/go-cid" "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/namespace" graphsync "github.com/ipfs/go-graphsync/impl" @@ -380,35 +381,69 @@ func StorageAuth(ctx helpers.MetricsCtx, ca lapi.Common) (sectorstorage.StorageA } func NewAcceptingStorageDealsConfigFunc(r repo.LockedRepo) (dtypes.AcceptingStorageDealsConfigFunc, error) { - return func() (bool, error) { - raw, err := r.Config() - if err != nil { - return false, err - } - - cfg, ok := raw.(*config.StorageMiner) - if !ok { - return false, xerrors.New("expected address of config.StorageMiner") - } - - return cfg.Dealmaking.AcceptingStorageDeals, nil + return func() (out bool, err error) { + err = readCfg(r, func(cfg *config.StorageMiner) { + out = cfg.Dealmaking.AcceptingStorageDeals + }) + return }, nil } func NewSetAcceptingStorageDealsConfigFunc(r repo.LockedRepo) (dtypes.SetAcceptingStorageDealsConfigFunc, error) { - return func(b bool) error { - var typeErr error - - setConfigErr := r.SetConfig(func(raw interface{}) { - cfg, ok := raw.(*config.StorageMiner) - if !ok { - typeErr = errors.New("expected storage miner config") - return - } - + return func(b bool) (err error) { + err = mutateCfg(r, func(cfg *config.StorageMiner) { cfg.Dealmaking.AcceptingStorageDeals = b }) - - return multierr.Combine(typeErr, setConfigErr) + return }, nil } + +func NewStorageDealCidBlacklistConfigFunc(r repo.LockedRepo) (dtypes.StorageDealCidBlacklistConfigFunc, error) { + return func() (out []cid.Cid, err error) { + err = readCfg(r, func(cfg *config.StorageMiner) { + out = cfg.Dealmaking.Blacklist + }) + return + }, nil +} + +func NewSetStorageDealCidBlacklistConfigFunc(r repo.LockedRepo) (dtypes.SetStorageDealCidBlacklistConfigFunc, error) { + return func(blacklist []cid.Cid) (err error) { + err = mutateCfg(r, func(cfg *config.StorageMiner) { + cfg.Dealmaking.Blacklist = blacklist + }) + return + }, nil +} + +func readCfg(r repo.LockedRepo, accessor func(*config.StorageMiner)) error { + raw, err := r.Config() + if err != nil { + return err + } + + cfg, ok := raw.(*config.StorageMiner) + if !ok { + return xerrors.New("expected address of config.StorageMiner") + } + + accessor(cfg) + + return nil +} + +func mutateCfg(r repo.LockedRepo, mutator func(*config.StorageMiner)) error { + var typeErr error + + setConfigErr := r.SetConfig(func(raw interface{}) { + cfg, ok := raw.(*config.StorageMiner) + if !ok { + typeErr = errors.New("expected storage miner config") + return + } + + mutator(cfg) + }) + + return multierr.Combine(typeErr, setConfigErr) +} From aa18d1985e6835a0b4ea7d3b08913064ea82e9ee Mon Sep 17 00:00:00 2001 From: laser Date: Thu, 18 Jun 2020 13:32:20 -0700 Subject: [PATCH 43/96] reject storage deal proposals containing blacklisted piece CIDs --- node/modules/storageminer.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index a6ef6304e..49893c494 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -3,6 +3,7 @@ package modules import ( "context" "errors" + "fmt" "net/http" "github.com/ipfs/go-bitswap" @@ -308,7 +309,7 @@ func NewStorageAsk(ctx helpers.MetricsCtx, fapi lapi.FullNode, ds dtypes.Metadat return storedAsk, nil } -func StorageProvider(minerAddress dtypes.MinerAddress, ffiConfig *ffiwrapper.Config, storedAsk *storedask.StoredAsk, h host.Host, ds dtypes.MetadataDS, ibs dtypes.StagingBlockstore, r repo.LockedRepo, pieceStore dtypes.ProviderPieceStore, dataTransfer dtypes.ProviderDataTransfer, spn storagemarket.StorageProviderNode, isAcceptingFunc dtypes.AcceptingStorageDealsConfigFunc) (storagemarket.StorageProvider, error) { +func StorageProvider(minerAddress dtypes.MinerAddress, ffiConfig *ffiwrapper.Config, storedAsk *storedask.StoredAsk, h host.Host, ds dtypes.MetadataDS, ibs dtypes.StagingBlockstore, r repo.LockedRepo, pieceStore dtypes.ProviderPieceStore, dataTransfer dtypes.ProviderDataTransfer, spn storagemarket.StorageProviderNode, isAcceptingFunc dtypes.AcceptingStorageDealsConfigFunc, blacklistFunc dtypes.StorageDealCidBlacklistConfigFunc) (storagemarket.StorageProvider, error) { net := smnet.NewFromLibp2pHost(h) store, err := piecefilestore.NewLocalFileStore(piecefilestore.OsPath(r.Path())) if err != nil { @@ -326,6 +327,18 @@ func StorageProvider(minerAddress dtypes.MinerAddress, ffiConfig *ffiwrapper.Con return false, "miner is not accepting storage deals", nil } + blacklist, err := blacklistFunc() + if err != nil { + return false, "miner error", err + } + + for idx := range blacklist { + if deal.Proposal.PieceCID.Equals(blacklist[idx]) { + log.Warnf("piece CID in proposal %s is blacklisted; rejecting storage deal proposal from client: %s", deal.Proposal.PieceCID, deal.Client.String()) + return false, fmt.Sprintf("miner has blacklisted piece CID %s", deal.Proposal.PieceCID), nil + } + } + return true, "", nil }) From 0c8d6489980d2ae2d3630ab33e371c47739eb9a2 Mon Sep 17 00:00:00 2001 From: laser Date: Thu, 18 Jun 2020 13:36:48 -0700 Subject: [PATCH 44/96] specify which CID is being blacklisted (it's the piece) --- api/api_storage.go | 4 ++-- api/apistruct/struct.go | 12 ++++++------ cmd/lotus-storage-miner/market.go | 14 +++++++------- node/builder.go | 4 ++-- node/config/def.go | 4 ++-- node/impl/storminer.go | 14 +++++++------- node/modules/dtypes/miner.go | 4 ++-- node/modules/storageminer.go | 10 +++++----- 8 files changed, 33 insertions(+), 33 deletions(-) diff --git a/api/api_storage.go b/api/api_storage.go index 0eda830dc..afc25f1c5 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -56,8 +56,8 @@ type StorageMiner interface { DealsImportData(ctx context.Context, dealPropCid cid.Cid, file string) error DealsList(ctx context.Context) ([]storagemarket.StorageDeal, error) DealsSetAcceptingStorageDeals(context.Context, bool) error - DealsBlacklist(context.Context) ([]cid.Cid, error) - DealsSetBlacklist(context.Context, []cid.Cid) error + DealsPieceCidBlacklist(context.Context) ([]cid.Cid, error) + DealsSetPieceCidBlacklist(context.Context, []cid.Cid) error StorageAddLocal(ctx context.Context, path string) error } diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index 43c0a423e..3d2375c87 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -226,8 +226,8 @@ type StorageMinerStruct struct { DealsImportData func(ctx context.Context, dealPropCid cid.Cid, file string) error `perm:"write"` DealsList func(ctx context.Context) ([]storagemarket.StorageDeal, error) `perm:"read"` DealsSetAcceptingStorageDeals func(context.Context, bool) error `perm:"admin"` - DealsBlacklist func(context.Context) ([]cid.Cid, error) `perm:"admin"` - DealsSetBlacklist func(context.Context, []cid.Cid) error `perm:"read"` + DealsPieceCidBlacklist func(context.Context) ([]cid.Cid, error) `perm:"admin"` + DealsSetPieceCidBlacklist func(context.Context, []cid.Cid) error `perm:"read"` StorageAddLocal func(ctx context.Context, path string) error `perm:"admin"` } @@ -874,12 +874,12 @@ func (c *StorageMinerStruct) DealsSetAcceptingStorageDeals(ctx context.Context, return c.Internal.DealsSetAcceptingStorageDeals(ctx, b) } -func (c *StorageMinerStruct) DealsBlacklist(ctx context.Context) ([]cid.Cid, error) { - return c.Internal.DealsBlacklist(ctx) +func (c *StorageMinerStruct) DealsPieceCidBlacklist(ctx context.Context) ([]cid.Cid, error) { + return c.Internal.DealsPieceCidBlacklist(ctx) } -func (c *StorageMinerStruct) DealsSetBlacklist(ctx context.Context, cids []cid.Cid) error { - return c.Internal.DealsSetBlacklist(ctx, cids) +func (c *StorageMinerStruct) DealsSetPieceCidBlacklist(ctx context.Context, cids []cid.Cid) error { + return c.Internal.DealsSetPieceCidBlacklist(ctx, cids) } func (c *StorageMinerStruct) StorageAddLocal(ctx context.Context, path string) error { diff --git a/cmd/lotus-storage-miner/market.go b/cmd/lotus-storage-miner/market.go index f12bce62c..1f7b84371 100644 --- a/cmd/lotus-storage-miner/market.go +++ b/cmd/lotus-storage-miner/market.go @@ -293,7 +293,7 @@ var dealsListCmd = &cli.Command{ var getBlacklistCmd = &cli.Command{ Name: "get-blacklist", - Usage: "List the CIDs in the storage miner's blacklist", + Usage: "List the contents of the storage miner's piece CID blacklist", Flags: []cli.Flag{ &CidBaseFlag, }, @@ -304,7 +304,7 @@ var getBlacklistCmd = &cli.Command{ } defer closer() - blacklist, err := api.DealsBlacklist(lcli.DaemonContext(cctx)) + blacklist, err := api.DealsPieceCidBlacklist(lcli.DaemonContext(cctx)) if err != nil { return err } @@ -324,8 +324,8 @@ var getBlacklistCmd = &cli.Command{ var setBlacklistCmd = &cli.Command{ Name: "set-blacklist", - Usage: "Set the storage miner's list of blacklisted CIDs", - ArgsUsage: "[ (optional, will read from stdin if omitted)]", + Usage: "Set the storage miner's list of blacklisted piece CIDs", + ArgsUsage: "[ (optional, will read from stdin if omitted)]", Flags: []cli.Flag{}, Action: func(cctx *cli.Context) error { api, closer, err := lcli.GetStorageMinerAPI(cctx) @@ -365,13 +365,13 @@ var setBlacklistCmd = &cli.Command{ return err } - return api.DealsSetBlacklist(lcli.DaemonContext(cctx), blacklist) + return api.DealsSetPieceCidBlacklist(lcli.DaemonContext(cctx), blacklist) }, } var resetBlacklistCmd = &cli.Command{ Name: "reset-blacklist", - Usage: "Remove all entries from the storage miner's blacklist", + Usage: "Remove all entries from the storage miner's piece CID blacklist", Flags: []cli.Flag{}, Action: func(cctx *cli.Context) error { api, closer, err := lcli.GetStorageMinerAPI(cctx) @@ -380,6 +380,6 @@ var resetBlacklistCmd = &cli.Command{ } defer closer() - return api.DealsSetBlacklist(lcli.DaemonContext(cctx), []cid.Cid{}) + return api.DealsSetPieceCidBlacklist(lcli.DaemonContext(cctx), []cid.Cid{}) }, } diff --git a/node/builder.go b/node/builder.go index 52e089bbe..bf32089e0 100644 --- a/node/builder.go +++ b/node/builder.go @@ -314,8 +314,8 @@ func Online() Option { Override(new(dtypes.AcceptingStorageDealsConfigFunc), modules.NewAcceptingStorageDealsConfigFunc), Override(new(dtypes.SetAcceptingStorageDealsConfigFunc), modules.NewSetAcceptingStorageDealsConfigFunc), - Override(new(dtypes.StorageDealCidBlacklistConfigFunc), modules.NewStorageDealCidBlacklistConfigFunc), - Override(new(dtypes.SetStorageDealCidBlacklistConfigFunc), modules.NewSetStorageDealCidBlacklistConfigFunc), + Override(new(dtypes.StorageDealPieceCidBlacklistConfigFunc), modules.NewStorageDealPieceCidBlacklistConfigFunc), + Override(new(dtypes.SetStorageDealPieceCidBlacklistConfigFunc), modules.NewSetStorageDealPieceCidBlacklistConfigFunc), ), ) } diff --git a/node/config/def.go b/node/config/def.go index b92ac3b4d..f9928ed3f 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -35,7 +35,7 @@ type StorageMiner struct { type DealmakingConfig struct { AcceptingStorageDeals bool - Blacklist []cid.Cid + PieceCidBlacklist []cid.Cid } // API contains configs for API endpoint @@ -124,7 +124,7 @@ func DefaultStorageMiner() *StorageMiner { Dealmaking: DealmakingConfig{ AcceptingStorageDeals: true, - Blacklist: []cid.Cid{}, + PieceCidBlacklist: []cid.Cid{}, }, } cfg.Common.API.ListenAddress = "/ip4/127.0.0.1/tcp/2345/http" diff --git a/node/impl/storminer.go b/node/impl/storminer.go index 2863a9d28..6eba12501 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -43,9 +43,9 @@ type StorageMinerAPI struct { StorageMgr *sectorstorage.Manager `optional:"true"` *stores.Index - SetAcceptingStorageDealsConfigFunc dtypes.SetAcceptingStorageDealsConfigFunc - StorageDealCidBlacklistConfigFunc dtypes.StorageDealCidBlacklistConfigFunc - SetStorageDealCidBlacklistConfigFunc dtypes.SetStorageDealCidBlacklistConfigFunc + SetAcceptingStorageDealsConfigFunc dtypes.SetAcceptingStorageDealsConfigFunc + StorageDealPieceCidBlacklistConfigFunc dtypes.StorageDealPieceCidBlacklistConfigFunc + SetStorageDealPieceCidBlacklistConfigFunc dtypes.SetStorageDealPieceCidBlacklistConfigFunc } func (sm *StorageMinerAPI) ServeRemote(w http.ResponseWriter, r *http.Request) { @@ -234,12 +234,12 @@ func (sm *StorageMinerAPI) DealsImportData(ctx context.Context, deal cid.Cid, fn return sm.StorageProvider.ImportDataForDeal(ctx, deal, fi) } -func (sm *StorageMinerAPI) DealsBlacklist(ctx context.Context) ([]cid.Cid, error) { - return sm.StorageDealCidBlacklistConfigFunc() +func (sm *StorageMinerAPI) DealsPieceCidBlacklist(ctx context.Context) ([]cid.Cid, error) { + return sm.StorageDealPieceCidBlacklistConfigFunc() } -func (sm *StorageMinerAPI) DealsSetBlacklist(ctx context.Context, cids []cid.Cid) error { - return sm.SetStorageDealCidBlacklistConfigFunc(cids) +func (sm *StorageMinerAPI) DealsSetPieceCidBlacklist(ctx context.Context, cids []cid.Cid) error { + return sm.SetStorageDealPieceCidBlacklistConfigFunc(cids) } func (sm *StorageMinerAPI) StorageAddLocal(ctx context.Context, path string) error { diff --git a/node/modules/dtypes/miner.go b/node/modules/dtypes/miner.go index 04ae07a53..a9d278ca4 100644 --- a/node/modules/dtypes/miner.go +++ b/node/modules/dtypes/miner.go @@ -21,8 +21,8 @@ type SetAcceptingStorageDealsConfigFunc func(bool) error // StorageDealCidBlacklistConfigFunc is a function which reads from miner config // to obtain a list of CIDs for which the storage miner will not accept storage // proposals. -type StorageDealCidBlacklistConfigFunc func() ([]cid.Cid, error) +type StorageDealPieceCidBlacklistConfigFunc func() ([]cid.Cid, error) // SetStorageDealCidBlacklistConfigFunc is a function which is used to set a // list of CIDs for which the storage miner will reject deal proposals. -type SetStorageDealCidBlacklistConfigFunc func([]cid.Cid) error +type SetStorageDealPieceCidBlacklistConfigFunc func([]cid.Cid) error diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 49893c494..73e062c9c 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -309,7 +309,7 @@ func NewStorageAsk(ctx helpers.MetricsCtx, fapi lapi.FullNode, ds dtypes.Metadat return storedAsk, nil } -func StorageProvider(minerAddress dtypes.MinerAddress, ffiConfig *ffiwrapper.Config, storedAsk *storedask.StoredAsk, h host.Host, ds dtypes.MetadataDS, ibs dtypes.StagingBlockstore, r repo.LockedRepo, pieceStore dtypes.ProviderPieceStore, dataTransfer dtypes.ProviderDataTransfer, spn storagemarket.StorageProviderNode, isAcceptingFunc dtypes.AcceptingStorageDealsConfigFunc, blacklistFunc dtypes.StorageDealCidBlacklistConfigFunc) (storagemarket.StorageProvider, error) { +func StorageProvider(minerAddress dtypes.MinerAddress, ffiConfig *ffiwrapper.Config, storedAsk *storedask.StoredAsk, h host.Host, ds dtypes.MetadataDS, ibs dtypes.StagingBlockstore, r repo.LockedRepo, pieceStore dtypes.ProviderPieceStore, dataTransfer dtypes.ProviderDataTransfer, spn storagemarket.StorageProviderNode, isAcceptingFunc dtypes.AcceptingStorageDealsConfigFunc, blacklistFunc dtypes.StorageDealPieceCidBlacklistConfigFunc) (storagemarket.StorageProvider, error) { net := smnet.NewFromLibp2pHost(h) store, err := piecefilestore.NewLocalFileStore(piecefilestore.OsPath(r.Path())) if err != nil { @@ -411,19 +411,19 @@ func NewSetAcceptingStorageDealsConfigFunc(r repo.LockedRepo) (dtypes.SetAccepti }, nil } -func NewStorageDealCidBlacklistConfigFunc(r repo.LockedRepo) (dtypes.StorageDealCidBlacklistConfigFunc, error) { +func NewStorageDealPieceCidBlacklistConfigFunc(r repo.LockedRepo) (dtypes.StorageDealPieceCidBlacklistConfigFunc, error) { return func() (out []cid.Cid, err error) { err = readCfg(r, func(cfg *config.StorageMiner) { - out = cfg.Dealmaking.Blacklist + out = cfg.Dealmaking.PieceCidBlacklist }) return }, nil } -func NewSetStorageDealCidBlacklistConfigFunc(r repo.LockedRepo) (dtypes.SetStorageDealCidBlacklistConfigFunc, error) { +func NewSetStorageDealPieceCidBlacklistConfigFunc(r repo.LockedRepo) (dtypes.SetStorageDealPieceCidBlacklistConfigFunc, error) { return func(blacklist []cid.Cid) (err error) { err = mutateCfg(r, func(cfg *config.StorageMiner) { - cfg.Dealmaking.Blacklist = blacklist + cfg.Dealmaking.PieceCidBlacklist = blacklist }) return }, nil From 99060fbb64f6f14e98e3545b71f344f3e740d133 Mon Sep 17 00:00:00 2001 From: laser Date: Thu, 18 Jun 2020 14:05:59 -0700 Subject: [PATCH 45/96] eliminate errant debug line --- cmd/lotus-storage-miner/market.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmd/lotus-storage-miner/market.go b/cmd/lotus-storage-miner/market.go index 1f7b84371..e27bf6081 100644 --- a/cmd/lotus-storage-miner/market.go +++ b/cmd/lotus-storage-miner/market.go @@ -170,8 +170,6 @@ var getAskCmd = &cli.Command{ Action: func(cctx *cli.Context) error { ctx := lcli.DaemonContext(cctx) - fmt.Println("wombat") - fnapi, closer, err := lcli.GetFullNodeAPI(cctx) if err != nil { return err From de7d6c255c00c180860279a6252cb8f4a1fb245e Mon Sep 17 00:00:00 2001 From: laser Date: Thu, 18 Jun 2020 15:42:24 -0700 Subject: [PATCH 46/96] blacklist -> blocklist --- api/api_storage.go | 4 ++-- api/apistruct/struct.go | 12 +++++----- cmd/lotus-storage-miner/market.go | 38 +++++++++++++++---------------- node/builder.go | 4 ++-- node/config/def.go | 4 ++-- node/impl/storminer.go | 12 +++++----- node/modules/dtypes/miner.go | 8 +++---- node/modules/storageminer.go | 22 +++++++++--------- 8 files changed, 52 insertions(+), 52 deletions(-) diff --git a/api/api_storage.go b/api/api_storage.go index afc25f1c5..29ae5ea2e 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -56,8 +56,8 @@ type StorageMiner interface { DealsImportData(ctx context.Context, dealPropCid cid.Cid, file string) error DealsList(ctx context.Context) ([]storagemarket.StorageDeal, error) DealsSetAcceptingStorageDeals(context.Context, bool) error - DealsPieceCidBlacklist(context.Context) ([]cid.Cid, error) - DealsSetPieceCidBlacklist(context.Context, []cid.Cid) error + DealsPieceCidBlocklist(context.Context) ([]cid.Cid, error) + DealsSetPieceCidBlocklist(context.Context, []cid.Cid) error StorageAddLocal(ctx context.Context, path string) error } diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index 3d2375c87..fb11cbda2 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -226,8 +226,8 @@ type StorageMinerStruct struct { DealsImportData func(ctx context.Context, dealPropCid cid.Cid, file string) error `perm:"write"` DealsList func(ctx context.Context) ([]storagemarket.StorageDeal, error) `perm:"read"` DealsSetAcceptingStorageDeals func(context.Context, bool) error `perm:"admin"` - DealsPieceCidBlacklist func(context.Context) ([]cid.Cid, error) `perm:"admin"` - DealsSetPieceCidBlacklist func(context.Context, []cid.Cid) error `perm:"read"` + DealsPieceCidBlocklist func(context.Context) ([]cid.Cid, error) `perm:"admin"` + DealsSetPieceCidBlocklist func(context.Context, []cid.Cid) error `perm:"read"` StorageAddLocal func(ctx context.Context, path string) error `perm:"admin"` } @@ -874,12 +874,12 @@ func (c *StorageMinerStruct) DealsSetAcceptingStorageDeals(ctx context.Context, return c.Internal.DealsSetAcceptingStorageDeals(ctx, b) } -func (c *StorageMinerStruct) DealsPieceCidBlacklist(ctx context.Context) ([]cid.Cid, error) { - return c.Internal.DealsPieceCidBlacklist(ctx) +func (c *StorageMinerStruct) DealsPieceCidBlocklist(ctx context.Context) ([]cid.Cid, error) { + return c.Internal.DealsPieceCidBlocklist(ctx) } -func (c *StorageMinerStruct) DealsSetPieceCidBlacklist(ctx context.Context, cids []cid.Cid) error { - return c.Internal.DealsSetPieceCidBlacklist(ctx, cids) +func (c *StorageMinerStruct) DealsSetPieceCidBlocklist(ctx context.Context, cids []cid.Cid) error { + return c.Internal.DealsSetPieceCidBlocklist(ctx, cids) } func (c *StorageMinerStruct) StorageAddLocal(ctx context.Context, path string) error { diff --git a/cmd/lotus-storage-miner/market.go b/cmd/lotus-storage-miner/market.go index e27bf6081..110411bc6 100644 --- a/cmd/lotus-storage-miner/market.go +++ b/cmd/lotus-storage-miner/market.go @@ -227,9 +227,9 @@ var dealsCmd = &cli.Command{ disableCmd, setAskCmd, getAskCmd, - setBlacklistCmd, - getBlacklistCmd, - resetBlacklistCmd, + setBlocklistCmd, + getBlocklistCmd, + resetBlocklistCmd, }, } @@ -289,9 +289,9 @@ var dealsListCmd = &cli.Command{ }, } -var getBlacklistCmd = &cli.Command{ - Name: "get-blacklist", - Usage: "List the contents of the storage miner's piece CID blacklist", +var getBlocklistCmd = &cli.Command{ + Name: "get-blocklist", + Usage: "List the contents of the storage miner's piece CID blocklist", Flags: []cli.Flag{ &CidBaseFlag, }, @@ -302,7 +302,7 @@ var getBlacklistCmd = &cli.Command{ } defer closer() - blacklist, err := api.DealsPieceCidBlacklist(lcli.DaemonContext(cctx)) + blocklist, err := api.DealsPieceCidBlocklist(lcli.DaemonContext(cctx)) if err != nil { return err } @@ -312,17 +312,17 @@ var getBlacklistCmd = &cli.Command{ return err } - for idx := range blacklist { - fmt.Println(encoder.Encode(blacklist[idx])) + for idx := range blocklist { + fmt.Println(encoder.Encode(blocklist[idx])) } return nil }, } -var setBlacklistCmd = &cli.Command{ - Name: "set-blacklist", - Usage: "Set the storage miner's list of blacklisted piece CIDs", +var setBlocklistCmd = &cli.Command{ + Name: "set-blocklist", + Usage: "Set the storage miner's list of blocklisted piece CIDs", ArgsUsage: "[ (optional, will read from stdin if omitted)]", Flags: []cli.Flag{}, Action: func(cctx *cli.Context) error { @@ -348,14 +348,14 @@ var setBlacklistCmd = &cli.Command{ scanner = bufio.NewScanner(file) } - var blacklist []cid.Cid + var blocklist []cid.Cid for scanner.Scan() { decoded, err := cid.Decode(scanner.Text()) if err != nil { return err } - blacklist = append(blacklist, decoded) + blocklist = append(blocklist, decoded) } err = scanner.Err() @@ -363,13 +363,13 @@ var setBlacklistCmd = &cli.Command{ return err } - return api.DealsSetPieceCidBlacklist(lcli.DaemonContext(cctx), blacklist) + return api.DealsSetPieceCidBlocklist(lcli.DaemonContext(cctx), blocklist) }, } -var resetBlacklistCmd = &cli.Command{ - Name: "reset-blacklist", - Usage: "Remove all entries from the storage miner's piece CID blacklist", +var resetBlocklistCmd = &cli.Command{ + Name: "reset-blocklist", + Usage: "Remove all entries from the storage miner's piece CID blocklist", Flags: []cli.Flag{}, Action: func(cctx *cli.Context) error { api, closer, err := lcli.GetStorageMinerAPI(cctx) @@ -378,6 +378,6 @@ var resetBlacklistCmd = &cli.Command{ } defer closer() - return api.DealsSetPieceCidBlacklist(lcli.DaemonContext(cctx), []cid.Cid{}) + return api.DealsSetPieceCidBlocklist(lcli.DaemonContext(cctx), []cid.Cid{}) }, } diff --git a/node/builder.go b/node/builder.go index bf32089e0..e84c4422c 100644 --- a/node/builder.go +++ b/node/builder.go @@ -314,8 +314,8 @@ func Online() Option { Override(new(dtypes.AcceptingStorageDealsConfigFunc), modules.NewAcceptingStorageDealsConfigFunc), Override(new(dtypes.SetAcceptingStorageDealsConfigFunc), modules.NewSetAcceptingStorageDealsConfigFunc), - Override(new(dtypes.StorageDealPieceCidBlacklistConfigFunc), modules.NewStorageDealPieceCidBlacklistConfigFunc), - Override(new(dtypes.SetStorageDealPieceCidBlacklistConfigFunc), modules.NewSetStorageDealPieceCidBlacklistConfigFunc), + Override(new(dtypes.StorageDealPieceCidBlocklistConfigFunc), modules.NewStorageDealPieceCidBlocklistConfigFunc), + Override(new(dtypes.SetStorageDealPieceCidBlocklistConfigFunc), modules.NewSetStorageDealPieceCidBlocklistConfigFunc), ), ) } diff --git a/node/config/def.go b/node/config/def.go index f9928ed3f..76a6a89ea 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -35,7 +35,7 @@ type StorageMiner struct { type DealmakingConfig struct { AcceptingStorageDeals bool - PieceCidBlacklist []cid.Cid + PieceCidBlocklist []cid.Cid } // API contains configs for API endpoint @@ -124,7 +124,7 @@ func DefaultStorageMiner() *StorageMiner { Dealmaking: DealmakingConfig{ AcceptingStorageDeals: true, - PieceCidBlacklist: []cid.Cid{}, + PieceCidBlocklist: []cid.Cid{}, }, } cfg.Common.API.ListenAddress = "/ip4/127.0.0.1/tcp/2345/http" diff --git a/node/impl/storminer.go b/node/impl/storminer.go index 6eba12501..360a9442c 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -44,8 +44,8 @@ type StorageMinerAPI struct { *stores.Index SetAcceptingStorageDealsConfigFunc dtypes.SetAcceptingStorageDealsConfigFunc - StorageDealPieceCidBlacklistConfigFunc dtypes.StorageDealPieceCidBlacklistConfigFunc - SetStorageDealPieceCidBlacklistConfigFunc dtypes.SetStorageDealPieceCidBlacklistConfigFunc + StorageDealPieceCidBlocklistConfigFunc dtypes.StorageDealPieceCidBlocklistConfigFunc + SetStorageDealPieceCidBlocklistConfigFunc dtypes.SetStorageDealPieceCidBlocklistConfigFunc } func (sm *StorageMinerAPI) ServeRemote(w http.ResponseWriter, r *http.Request) { @@ -234,12 +234,12 @@ func (sm *StorageMinerAPI) DealsImportData(ctx context.Context, deal cid.Cid, fn return sm.StorageProvider.ImportDataForDeal(ctx, deal, fi) } -func (sm *StorageMinerAPI) DealsPieceCidBlacklist(ctx context.Context) ([]cid.Cid, error) { - return sm.StorageDealPieceCidBlacklistConfigFunc() +func (sm *StorageMinerAPI) DealsPieceCidBlocklist(ctx context.Context) ([]cid.Cid, error) { + return sm.StorageDealPieceCidBlocklistConfigFunc() } -func (sm *StorageMinerAPI) DealsSetPieceCidBlacklist(ctx context.Context, cids []cid.Cid) error { - return sm.SetStorageDealPieceCidBlacklistConfigFunc(cids) +func (sm *StorageMinerAPI) DealsSetPieceCidBlocklist(ctx context.Context, cids []cid.Cid) error { + return sm.SetStorageDealPieceCidBlocklistConfigFunc(cids) } func (sm *StorageMinerAPI) StorageAddLocal(ctx context.Context, path string) error { diff --git a/node/modules/dtypes/miner.go b/node/modules/dtypes/miner.go index a9d278ca4..3f0d81d6d 100644 --- a/node/modules/dtypes/miner.go +++ b/node/modules/dtypes/miner.go @@ -18,11 +18,11 @@ type AcceptingStorageDealsConfigFunc func() (bool, error) // storage deal acceptance. type SetAcceptingStorageDealsConfigFunc func(bool) error -// StorageDealCidBlacklistConfigFunc is a function which reads from miner config +// StorageDealCidBlocklistConfigFunc is a function which reads from miner config // to obtain a list of CIDs for which the storage miner will not accept storage // proposals. -type StorageDealPieceCidBlacklistConfigFunc func() ([]cid.Cid, error) +type StorageDealPieceCidBlocklistConfigFunc func() ([]cid.Cid, error) -// SetStorageDealCidBlacklistConfigFunc is a function which is used to set a +// SetStorageDealCidBlocklistConfigFunc is a function which is used to set a // list of CIDs for which the storage miner will reject deal proposals. -type SetStorageDealPieceCidBlacklistConfigFunc func([]cid.Cid) error +type SetStorageDealPieceCidBlocklistConfigFunc func([]cid.Cid) error diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 73e062c9c..67e0e0842 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -309,7 +309,7 @@ func NewStorageAsk(ctx helpers.MetricsCtx, fapi lapi.FullNode, ds dtypes.Metadat return storedAsk, nil } -func StorageProvider(minerAddress dtypes.MinerAddress, ffiConfig *ffiwrapper.Config, storedAsk *storedask.StoredAsk, h host.Host, ds dtypes.MetadataDS, ibs dtypes.StagingBlockstore, r repo.LockedRepo, pieceStore dtypes.ProviderPieceStore, dataTransfer dtypes.ProviderDataTransfer, spn storagemarket.StorageProviderNode, isAcceptingFunc dtypes.AcceptingStorageDealsConfigFunc, blacklistFunc dtypes.StorageDealPieceCidBlacklistConfigFunc) (storagemarket.StorageProvider, error) { +func StorageProvider(minerAddress dtypes.MinerAddress, ffiConfig *ffiwrapper.Config, storedAsk *storedask.StoredAsk, h host.Host, ds dtypes.MetadataDS, ibs dtypes.StagingBlockstore, r repo.LockedRepo, pieceStore dtypes.ProviderPieceStore, dataTransfer dtypes.ProviderDataTransfer, spn storagemarket.StorageProviderNode, isAcceptingFunc dtypes.AcceptingStorageDealsConfigFunc, blocklistFunc dtypes.StorageDealPieceCidBlocklistConfigFunc) (storagemarket.StorageProvider, error) { net := smnet.NewFromLibp2pHost(h) store, err := piecefilestore.NewLocalFileStore(piecefilestore.OsPath(r.Path())) if err != nil { @@ -327,15 +327,15 @@ func StorageProvider(minerAddress dtypes.MinerAddress, ffiConfig *ffiwrapper.Con return false, "miner is not accepting storage deals", nil } - blacklist, err := blacklistFunc() + blocklist, err := blocklistFunc() if err != nil { return false, "miner error", err } - for idx := range blacklist { - if deal.Proposal.PieceCID.Equals(blacklist[idx]) { - log.Warnf("piece CID in proposal %s is blacklisted; rejecting storage deal proposal from client: %s", deal.Proposal.PieceCID, deal.Client.String()) - return false, fmt.Sprintf("miner has blacklisted piece CID %s", deal.Proposal.PieceCID), nil + for idx := range blocklist { + if deal.Proposal.PieceCID.Equals(blocklist[idx]) { + log.Warnf("piece CID in proposal %s is blocklisted; rejecting storage deal proposal from client: %s", deal.Proposal.PieceCID, deal.Client.String()) + return false, fmt.Sprintf("miner has blocklisted piece CID %s", deal.Proposal.PieceCID), nil } } @@ -411,19 +411,19 @@ func NewSetAcceptingStorageDealsConfigFunc(r repo.LockedRepo) (dtypes.SetAccepti }, nil } -func NewStorageDealPieceCidBlacklistConfigFunc(r repo.LockedRepo) (dtypes.StorageDealPieceCidBlacklistConfigFunc, error) { +func NewStorageDealPieceCidBlocklistConfigFunc(r repo.LockedRepo) (dtypes.StorageDealPieceCidBlocklistConfigFunc, error) { return func() (out []cid.Cid, err error) { err = readCfg(r, func(cfg *config.StorageMiner) { - out = cfg.Dealmaking.PieceCidBlacklist + out = cfg.Dealmaking.PieceCidBlocklist }) return }, nil } -func NewSetStorageDealPieceCidBlacklistConfigFunc(r repo.LockedRepo) (dtypes.SetStorageDealPieceCidBlacklistConfigFunc, error) { - return func(blacklist []cid.Cid) (err error) { +func NewSetStorageDealPieceCidBlocklistConfigFunc(r repo.LockedRepo) (dtypes.SetStorageDealPieceCidBlocklistConfigFunc, error) { + return func(blocklist []cid.Cid) (err error) { err = mutateCfg(r, func(cfg *config.StorageMiner) { - cfg.Dealmaking.PieceCidBlacklist = blacklist + cfg.Dealmaking.PieceCidBlocklist = blocklist }) return }, nil From c41c210ced80a25633f08ebb630392c37efda148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 19 Jun 2020 01:24:04 +0200 Subject: [PATCH 47/96] New Genesis --- build/genesis/devnet.car | Bin 254687 -> 25893 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/build/genesis/devnet.car b/build/genesis/devnet.car index 5f13410a61bc8cff76b7ae69500765c777aaad5d..3e156de61764ee49afa5b090f86511a29a661e5e 100644 GIT binary patch literal 25893 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
`0j72gaTX*t_-QT+wQR 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 From 7c4225d01c8239d09c5192bdae568730c79e15b9 Mon Sep 17 00:00:00 2001 From: Mitch Wagner Date: Thu, 18 Jun 2020 21:31:26 -0400 Subject: [PATCH 48/96] Remove redundant glossary item The excised entry previously rendered twice in the glossary under two different keys. This commit removes one of those KV pairs to avoid that. --- documentation/en/.glossary.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/documentation/en/.glossary.json b/documentation/en/.glossary.json index 41126f974..bdc43e319 100644 --- a/documentation/en/.glossary.json +++ b/documentation/en/.glossary.json @@ -63,10 +63,6 @@ "title": "Proof-of-Spacetime(s)", "value": "Filecoin is a protocol token whose blockchain runs on a novel proof, called Proof-of-Spacetime, where blocks are created by miners that are storing data." }, - "lotus-testnet": { - "title": "Filecoin Testnet", - "value": "Until we launch, we are making lots of changes to Lotus. The Testnet is expected to bring a few significant fixes/improvements. During Testnet, you can retrieve test filecoin from our network faucet to use as collateral to start mining. Test filecoin do not have any value – the official filecoin tokens will not be released until Mainnet launch." - }, "filecoin-testnet": { "title": "Filecoin Testnet", "value": "Until we launch, we are making lots of changes to Lotus. The Testnet is expected to bring a few significant fixes/improvements. During Testnet, you can retrieve test filecoin from our network faucet to use as collateral to start mining. Test filecoin do not have any value – the official filecoin tokens will not be released until Mainnet launch." From 5e77b5da9f2e45377e76087a93193af29821ce35 Mon Sep 17 00:00:00 2001 From: laser Date: Fri, 19 Jun 2020 08:51:06 -0700 Subject: [PATCH 49/96] bump from 3 to 4 digits in format-string Fixes #1993 --- chain/types/bigint.go | 2 +- chain/types/bigint_test.go | 25 ++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/chain/types/bigint.go b/chain/types/bigint.go index 22ecf833c..a7b25870a 100644 --- a/chain/types/bigint.go +++ b/chain/types/bigint.go @@ -76,7 +76,7 @@ func SizeStr(bi BigInt) string { } f, _ := r.Float64() - return fmt.Sprintf("%.3g %s", f, byteSizeUnits[i]) + return fmt.Sprintf("%.4g %s", f, byteSizeUnits[i]) } var deciUnits = []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"} diff --git a/chain/types/bigint_test.go b/chain/types/bigint_test.go index 2f632d1d7..43e5633b2 100644 --- a/chain/types/bigint_test.go +++ b/chain/types/bigint_test.go @@ -3,7 +3,12 @@ package types import ( "bytes" "math/big" + "math/rand" + "strings" "testing" + "time" + + "github.com/docker/go-units" "github.com/stretchr/testify/assert" ) @@ -60,8 +65,10 @@ func TestSizeStr(t *testing.T) { }{ {0, "0 B"}, {1, "1 B"}, + {1016, "1016 B"}, {1024, "1 KiB"}, - {2000, "1.95 KiB"}, + {1000 * 1024, "1000 KiB"}, + {2000, "1.953 KiB"}, {5 << 20, "5 MiB"}, {11 << 60, "11 EiB"}, } @@ -71,6 +78,22 @@ func TestSizeStr(t *testing.T) { } } +func TestSizeStrUnitsSymmetry(t *testing.T) { + s := rand.NewSource(time.Now().UnixNano()) + r := rand.New(s) + + for i := 0; i < 1000000; i++ { + n := r.Uint64() + l := strings.ReplaceAll(units.BytesSize(float64(n)), " ", "") + r := strings.ReplaceAll(SizeStr(NewInt(n)), " ", "") + + assert.NotContains(t, l, "e+") + assert.NotContains(t, r, "e+") + + assert.Equal(t, l, r, "wrong formatting for %d", n) + } +} + func TestSizeStrBig(t *testing.T) { ZiB := big.NewInt(50000) ZiB = ZiB.Lsh(ZiB, 70) From fbeaab466acd181f5da39ffc22cd9aa97609bf8a Mon Sep 17 00:00:00 2001 From: laser Date: Fri, 19 Jun 2020 09:19:46 -0700 Subject: [PATCH 50/96] obey the linter --- cmd/lotus-storage-miner/market.go | 2 +- node/modules/dtypes/miner.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/lotus-storage-miner/market.go b/cmd/lotus-storage-miner/market.go index 110411bc6..c668456f0 100644 --- a/cmd/lotus-storage-miner/market.go +++ b/cmd/lotus-storage-miner/market.go @@ -343,7 +343,7 @@ var setBlocklistCmd = &cli.Command{ if err != nil { log.Fatal(err) } - defer file.Close() + defer file.Close() //nolint:errcheck scanner = bufio.NewScanner(file) } diff --git a/node/modules/dtypes/miner.go b/node/modules/dtypes/miner.go index 3f0d81d6d..584642a3b 100644 --- a/node/modules/dtypes/miner.go +++ b/node/modules/dtypes/miner.go @@ -18,11 +18,11 @@ type AcceptingStorageDealsConfigFunc func() (bool, error) // storage deal acceptance. type SetAcceptingStorageDealsConfigFunc func(bool) error -// StorageDealCidBlocklistConfigFunc is a function which reads from miner config +// StorageDealPieceCidBlocklistConfigFunc is a function which reads from miner config // to obtain a list of CIDs for which the storage miner will not accept storage // proposals. type StorageDealPieceCidBlocklistConfigFunc func() ([]cid.Cid, error) -// SetStorageDealCidBlocklistConfigFunc is a function which is used to set a +// SetStorageDealPieceCidBlocklistConfigFunc is a function which is used to set a // list of CIDs for which the storage miner will reject deal proposals. type SetStorageDealPieceCidBlocklistConfigFunc func([]cid.Cid) error From 6b86dcde4ac851f551b7a0549b2cd10514528be7 Mon Sep 17 00:00:00 2001 From: Nathaniel Jensen Date: Fri, 19 Jun 2020 15:41:54 +1000 Subject: [PATCH 51/96] doc: Fix architecture broken link --- documentation/en/architecture.md | 124 +++++++++++++++---------------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/documentation/en/architecture.md b/documentation/en/architecture.md index 970f2a13d..e4808a877 100644 --- a/documentation/en/architecture.md +++ b/documentation/en/architecture.md @@ -1,11 +1,11 @@ # Lotus Lotus is an implementation of the [Filecoin Distributed Storage Network](https://filecoin.io/). -A Lotus node syncs blockchains that follow the +A Lotus node syncs blockchains that follow the Filecoin protocol, validating the blocks and state transitions. The specification for the Filecoin protocol can be found [here](https://filecoin-project.github.io/specs/). -For information on how to setup and operate a Lotus node, +For information on how to setup and operate a Lotus node, please follow the instructions [here](https://lotu.sh/en+getting-started). # Components @@ -24,7 +24,7 @@ FIXME: No mention of block production here, cross-reference with schomatis's min - Other PL dependencies (IPFS, libp2p, IPLD? FIXME, missing) - External libraries used by Lotus and other deps (FIXME, missing) -# Preliminaries +# Preliminaries We discuss some key Filecoin concepts here, aiming to explain them by contrasting them with analogous concepts in other well-known blockchains like Ethereum. We only provide brief descriptions here; elaboration @@ -34,10 +34,10 @@ can be found in the [spec](https://filecoin-project.github.io/specs/). Unlike in Ethereum, a block can have multiple parents in Filecoin. We thus refer to the parent set of a block, instead of a single parent. -A [tipset](https://filecoin-project.github.io/specs/#systems__filecoin_blockchain__struct__tipset) -is any set of blocks that share the same parent set. +A [tipset](https://filecoin-project.github.io/specs/#systems__filecoin_blockchain__struct__tipset) +is any set of blocks that share the same parent set. -There is no concept of "block difficulty" in Filecoin. Instead, +There is no concept of "block difficulty" in Filecoin. Instead, the weight of a tipset is simply the number of blocks in the chain that ends in that tipset. Note that a longer chain can have less weight than a shorter chain with more blocks per tipset. @@ -49,8 +49,8 @@ We call the heaviest tipset in a chain the "head" of the chain. ### Actors and Messages An [Actor](https://filecoin-project.github.io/specs/#systems__filecoin_vm__actor) - is analogous to a smart contract in Ethereum. Filecoin does not allow users to define their own -actors, but comes with several [builtin actors](https://github.com/filecoin-project/specs-actors), + is analogous to a smart contract in Ethereum. Filecoin does not allow users to define their own +actors, but comes with several [builtin actors](https://github.com/filecoin-project/specs-actors), which can be thought of as pre-compiled contracts. A [Message](https://filecoin-project.github.io/specs/#systems__filecoin_vm__message) @@ -70,8 +70,8 @@ We now discuss the various stages of the sync process. ## Sync setup -When a Lotus node connects to a new peer, we exchange the head of our chain -with the new peer through [the `hello` protocol](https://github.com/filecoin-project/lotus/blob/master/node/hello/hello.go). +When a Lotus node connects to a new peer, we exchange the head of our chain +with the new peer through [the `hello` protocol](https://github.com/filecoin-project/lotus/blob/master/node/hello/hello.go). If the peer's head is heavier than ours, we try to sync to it. Note that we do NOT update our chain head at this stage. @@ -79,7 +79,7 @@ that we do NOT update our chain head at this stage. Note: The API refers to these stages as `StageHeaders` and `StagePersistHeaders`. -We proceed in the sync process by requesting block headers from the peer, +We proceed in the sync process by requesting block headers from the peer, moving back from their head, until we reach a tipset that we have in common (such a common tipset must exist, thought it may simply be the genesis block). The functionality can be found in `Syncer::collectHeaders()`. @@ -90,7 +90,7 @@ drop part of our chain to connect to the peer's head (referred to as "forking"). FIXME: This next para might be best replaced with a link to the validation doc Some of the possible causes of failure in this stage include: -- The chain is linked to a block that we have previously marked as bad, +- The chain is linked to a block that we have previously marked as bad, and stored in a [`BadBlockCache`](https://github.com/filecoin-project/lotus/blob/master/chain/badtscache.go). - The beacon entries in a block are inconsistent (FIXME: more details about what is validated here wouldn't be bad). - Switching to this new chain would involve a chain reorganization beyond the allowed threshold (SPECK-CHECK). @@ -101,7 +101,7 @@ Note: The API refers to this stage as `StageMessages`. Having acquired the headers and found a common tipset, we then move forward, requesting the full blocks, including the messages. -For each block, we first confirm the syntactic validity of the block (SPECK-CHECK), +For each block, we first confirm the syntactic validity of the block (SPECK-CHECK), which includes the syntactic validity of messages included in the block. We then apply the messages, running all the state transitions, and compare the state root we calculate with the provided state root. @@ -121,11 +121,11 @@ syntactic validation of messages. Note: The API refers to this stage as `StageSyncComplete`. -If all validations pass we will now set that head as our heaviest tipset in +If all validations pass we will now set that head as our heaviest tipset in [`ChainStore`](https://github.com/filecoin-project/lotus/blob/master/chain/store/store.go). We already have the full state, since we calculated it during the sync process. - + FIXME (aayush) I don't fuilly understand the next 2 paragraphs, but it seems important. Confirm and polish. Relevant issue in IPFS: https://github.com/ipfs/ipfs-docs/issues/264 @@ -135,7 +135,7 @@ FIXME: Create a further reading appendix, move this next para to it, along with extraneous content This is one of the few items we store in `Datastore` by key, location, allowing its contents to change on every sync. This is reflected in the `(*ChainStore) writeHead()` function (called by `takeHeaviestTipSet()` above) where we reference the pointer by the explicit `chainHeadKey` address (the string `"head"`, not a hash embedded in a CID), and similarly in `(*ChainStore).Load()` when we start the node and create the `ChainStore`. Compare this to a Filecoin block or message which are immutable, stored in the `Blockstore` by CID, once created they never change. -## Keeping up with the chain +## Keeping up with the chain A Lotus node also listens for new blocks broadcast by its peers over the `gossipsub` channel (see FIXME for more). If we have validated such a block's parent tipset, and adding it to our tipset at its height would lead to a heavier @@ -144,11 +144,11 @@ process (indeed, it's the same codepath). # State -In Filecoin, the chain state at any given point is a collection of data stored under a root CID +In Filecoin, the chain state at any given point is a collection of data stored under a root CID encapsulated in the [`StateTree`](https://github.com/filecoin-project/lotus/blob/master/chain/state/statetree.go), -and accessed through the +and accessed through the [`StateManager`](https://github.com/filecoin-project/lotus/blob/master/chain/stmgr/stmgr.go). -The state at the chain's head is thus easily tracked and updated in a state root CID. +The state at the chain's head is thus easily tracked and updated in a state root CID. (FIXME: Talk about CIDs somewhere, we might want to explain some of the modify/flush/update-root mechanism here.)) ## Calculating a Tipset State @@ -156,7 +156,7 @@ The state at the chain's head is thus easily tracked and updated in a state root Recall that a tipset is a set of blocks that have identical parents (that is, that are built on top of the same tipset). The genesis tipset comprises the genesis block(s), and has some state corresponding to it. -The methods `TipSetState()` and `computeTipSetState()` in +The methods `TipSetState()` and `computeTipSetState()` in [`StateManager`](https://github.com/filecoin-project/lotus/blob/master/chain/stmgr/stmgr.go) are responsible for computing the state that results from applying a tipset. This involves applying all the messages included @@ -168,25 +168,25 @@ State Root (which is to be expected, since they have the same parent tipset) ### Preparing to apply a tipset -When `StateManager::computeTipsetState()` is called with a tipset, `ts`, +When `StateManager::computeTipsetState()` is called with a tipset, `ts`, it retrieves the parent state root of the blocks in `ts`. It also creates a list of `BlockMessages`, which wraps the BLS -and SecP messages in a block along with the miner that produced the block. +and SecP messages in a block along with the miner that produced the block. -Control then flows to `StateManager::ApplyBlocks()`, which builds a VM to apply the messages given to it. The VM +Control then flows to `StateManager::ApplyBlocks()`, which builds a VM to apply the messages given to it. The VM is initialized with the parent state root of the blocks in `ts`. We apply the blocks in `ts` in order (see FIXME for ordering of blocks in a tipset). ### Applying a block -For each block, we prepare to apply the ordered messages (first BLS, then SecP). Before applying a message, we check if +For each block, we prepare to apply the ordered messages (first BLS, then SecP). Before applying a message, we check if we have already applied a message with that CID within the scope of this method. If so, we simply skip that message; this is how duplicate messages included in the same tipset are skipped (with only the miner of the "first" block to include the message getting the reward). For the actual process of message application, see FIXME (need an -internal link here), for now we -simply assume that the outcome of the VM applying a message is either an error, or a +internal link here), for now we +simply assume that the outcome of the VM applying a message is either an error, or a [`MessageReceipt`](https://github.com/filecoin-project/lotus/blob/master/chain/types/message_receipt.go) and some -other information. +other information. We treat an error from the VM as a showstopper; there is no recovery, and no meaningful state can be computed for `ts`. Given a successful receipt, we add the rewards and penalties to what the miner has earned so far. Once all the messages @@ -205,8 +205,8 @@ is the computed state of the tipset. # Virtual Machine -The Virtual Machine (VM) is responsible for executing messages. -The [Lotus Virtual Machine](https://github.com/filecoin-project/lotus/blob/master/chain/vm/vm.go) +The Virtual Machine (VM) is responsible for executing messages. +The [Lotus Virtual Machine](https://github.com/filecoin-project/lotus/blob/master/chain/vm/vm.go) invokes the appropriate methods in the builtin actors, and provides a [`Runtime`](https://github.com/filecoin-project/specs-actors/blob/master/actors/runtime/runtime.go) interface to the [builtin actors](https://github.com/filecoin-project/specs-actors) @@ -233,10 +233,10 @@ It then transfers the message's value to the recipient, creating a new account a ### Method Invocation We use reflection to translate a Filecoin message for the VM to an actual Go function, relying on the VM's -[`invoker`](https://github.com/filecoin-project/lotus/blob/master/chain/vm/invoker.go) structure. +[`invoker`](https://github.com/filecoin-project/lotus/blob/master/chain/vm/invoker.go) structure. Each actor has its own set of codes defined in `specs-actors/actors/builtin/methods.go`. The `invoker` structure maps the builtin actors' CIDs - to a list of `invokeFunc` (one per exported method), which each take the `Runtime` (for state manipulation) + to a list of `invokeFunc` (one per exported method), which each take the `Runtime` (for state manipulation) and the serialized input parameters. FIXME (aayush) Polish this next para. @@ -245,39 +245,39 @@ The basic layout (without reflection details) of `(*invoker).transform()` is as ### Returning from the VM -Once method invocation is complete (including any subcalls), we return to `ApplyMessage()`, which receives -the serialized response and the [`ActorError`](https://github.com/filecoin-project/lotus/blob/master/chain/actors/aerrors/error.go). +Once method invocation is complete (including any subcalls), we return to `ApplyMessage()`, which receives +the serialized response and the [`ActorError`](https://github.com/filecoin-project/lotus/blob/master/chain/actors/aerrors/error.go). The sender will be charged the appropriate amount of gas for the returned response, which gets put into the [`MessageReceipt`](https://github.com/filecoin-project/lotus/blob/master/chain/types/message_receipt.go). -The method then refunds any unused gas to the sender, sets up the gas reward for the miner, and +The method then refunds any unused gas to the sender, sets up the gas reward for the miner, and wraps all of this into an `ApplyRet`, which is returned. # Building a Lotus node -When we launch a Lotus node with the command `./lotus daemon` +When we launch a Lotus node with the command `./lotus daemon` (see [here](https://github.com/filecoin-project/lotus/blob/master/cmd/lotus/daemon.go) for more), the node is created through [dependency injection](https://godoc.org/go.uber.org/fx). -This relies on reflection, which makes some of the references hard to follow. +This relies on reflection, which makes some of the references hard to follow. The node sets up all of the subsystems it needs to run, such as the repository, the network connections, thechain sync -service, etc. +service, etc. This setup is orchestrated through calls to the `node.Override` function. -The structure of each call indicates the type of component it will set up +The structure of each call indicates the type of component it will set up (many defined in [`node/modules/dtypes/`](https://github.com/filecoin-project/lotus/tree/master/node/modules/dtypes)), -and the function that will provide it. +and the function that will provide it. The dependency is implicit in the argument of the provider function. As an example, consider the `modules.ChainStore()` function that provides the [`ChainStore`](https://github.com/filecoin-project/lotus/blob/master/chain/store/store.go) structure. It takes as one of its parameters the [`ChainBlockstore`](https://github.com/filecoin-project/lotus/blob/master/node/modules/dtypes/storage.go) -type, which becomes one of its dependencies. +type, which becomes one of its dependencies. For the node to be built successfully the `ChainBlockstore` will need to be provided before `ChainStore`, a requirement that is made explicit in another `Override()` call that sets the provider of that type as the `ChainBlockstore()` function. ## The Repository The repo is the directory where all of a node's information is stored. The node is entirely defined by its repo, which -makes it easy to port to another location. This one-to-one relationship means we can speak +makes it easy to port to another location. This one-to-one relationship means we can speak of the node as the repo it is associated with, instead of the daemon process that runs from that repo. Only one daemon can run be running with an associated repo at a time. @@ -292,17 +292,17 @@ lsof ~/.lotus/repo.lock Trying to launch a second daemon hooked to the same repo leads to a `repo is already locked (lotus daemon already running)` error. -The `node.Repo()` function (`node/builder.go`) contains most of the dependencies (specified as `Override()` calls) +The `node.Repo()` function (`node/builder.go`) contains most of the dependencies (specified as `Override()` calls) needed to properly set up the node's repo. We list the most salient ones here. ### Datastore -`Datastore` and `ChainBlockstore`: Data related to the node state is saved in the repo's `Datastore`, -an IPFS interface defined [here](github.com/ipfs/go-datastore/datastore.go). +`Datastore` and `ChainBlockstore`: Data related to the node state is saved in the repo's `Datastore`, +an IPFS interface defined [here](https://github.com/ipfs/go-datastore/blob/master/datastore.go). Lotus creates this interface from a [Badger DB](https://github.com/dgraph-io/badger) in [`FsRepo`](https://github.com/filecoin-project/lotus/blob/master/node/repo/fsrepo.go). Every piece of data is fundamentally a key-value pair in the `datastore` directory of the repo. -There are several abstractions laid on top of it that appear through the code depending on *how* we access it, +There are several abstractions laid on top of it that appear through the code depending on *how* we access it, but it is important to remember that we're always accessing it from the same place. FIXME: Maybe mention the `Batching` interface as the developer will stumble upon it before reaching the `Datastore` one. @@ -314,8 +314,8 @@ FIXME: IPFS blocks vs Filecoin blocks ideally happens before this / here The [`Blockstore` interface](`github.com/ipfs/go-ipfs-blockstore/blockstore.go`) structures the key-value pair into the CID format for the key and the [`Block` interface](`github.com/ipfs/go-block-format/blocks.go`) for the value. The `Block` value is just a raw string of bytes addressed by its hash, which is included in the CID key. - -`ChainBlockstore` creates a `Blockstore` in the repo under the `/blocks` namespace. + +`ChainBlockstore` creates a `Blockstore` in the repo under the `/blocks` namespace. Every key stored there will have the `blocks` prefix so that it does not collide with other stores that use the same repo. FIXME: Link to IPFS documentation about DAG, CID, and related, especially we need a diagram that shows how do we wrap each datastore inside the next layer (datastore, batching, block store, gc, etc). @@ -323,7 +323,7 @@ FIXME: Link to IPFS documentation about DAG, CID, and related, especially we nee #### Metadata `modules.Datastore()` creates a `dtypes.MetadataDS`, which is an alias for the basic `Datastore` interface. -Metadata is stored here under the `/metadata` prefix. +Metadata is stored here under the `/metadata` prefix. (FIXME: Explain *what* is metadata in contrast with the block store, namely we store the pointer to the heaviest chain, we might just link to that unwritten section here later.) FIXME: Explain the key store related calls (maybe remove, per Schomatis) @@ -331,10 +331,10 @@ FIXME: Explain the key store related calls (maybe remove, per Schomatis) ### LockedRepo `LockedRepo()`: This method doesn't create or initialize any new structures, but rather registers an - `OnStop` [hook](https://godoc.org/go.uber.org/fx/internal/lifecycle#Hook) + `OnStop` [hook](https://godoc.org/go.uber.org/fx/internal/lifecycle#Hook) that will close the locked repository associated with it on shutdown. - - + + ### Repo types / Node types FIXME: This section needs to be clarified / corrected...I don't fully understand the config differences (what do they have in common, if anything?) @@ -351,19 +351,19 @@ As we said, the repo fully identifies the node so a repo type is also a *node* t FIXME: Much of this might need to be subsumed into the p2p section The `node.Online()` configuration function (`node/builder.go`) initializes components that involve connecting to, -or interacting with, the Filecoin network. These connections are managed through the libp2p stack (FIXME link to this section when it exists). +or interacting with, the Filecoin network. These connections are managed through the libp2p stack (FIXME link to this section when it exists). We discuss some of the components found in the full node type (that is, included in the `ApplyIf(isType(repo.FullNode),` call). #### Chainstore -`modules.ChainStore()` creates the [`store.ChainStore`](https://github.com/filecoin-project/lotus/blob/master/chain/store/store.go)) +`modules.ChainStore()` creates the [`store.ChainStore`](https://github.com/filecoin-project/lotus/blob/master/chain/store/store.go)) that wraps the stores - previously instantiated in `Repo()`. It is the main point of entry for the node to all chain-related data - (FIXME: this is incorrect, we sometimes access its underlying block store directly, and probably shouldn't). + previously instantiated in `Repo()`. It is the main point of entry for the node to all chain-related data + (FIXME: this is incorrect, we sometimes access its underlying block store directly, and probably shouldn't). It also holds the crucial `heaviest` pointer, which indicates the current head of the chain. - + #### ChainExchange and ChainBlockservice -`ChainExchange()` and `ChainBlockservice()` establish a BitSwap connection (FIXME libp2p link) +`ChainExchange()` and `ChainBlockservice()` establish a BitSwap connection (FIXME libp2p link) to exchange chain information in the form of `blocks.Block`s stored in the repo. (See sync section for more details, the Filecoin blocks and messages are backed by these raw IPFS blocks that together form the different structures that define the state of the current/heaviest chain.) #### Incoming handlers @@ -371,7 +371,7 @@ to exchange chain information in the form of `blocks.Block`s stored in the repo. and messages from the network (see `` for more information about the topics the node is subscribed to, FIXME: should that be part of the libp2p section or should we expand on gossipsub separately?). #### Hello -`RunHello()`: starts the services to both send (`(*Service).SayHello()`) and receive (`(*Service).HandleStream()`, `node/hello/hello.go`) +`RunHello()`: starts the services to both send (`(*Service).SayHello()`) and receive (`(*Service).HandleStream()`, `node/hello/hello.go`) `hello` messages. When nodes establish a new connection with each other, they exchange these messages to share chain-related information (namely their genesis block and their heaviest tipset). @@ -381,14 +381,14 @@ to share chain-related information (namely their genesis block and their heavies ### Ordering the dependencies We can establish the dependency relations by looking at the parameters that each function needs and by understanding -the architecture of the node and how the different components relate to each other (the chief purpose of this document). +the architecture of the node and how the different components relate to each other (the chief purpose of this document). As an example, the sync mechanism depends on the node being able to exchange different IPFS blocks with the network, so as to be able to request the "missing pieces" needed to construct the chain. This dependency is reflected by `NewSyncer()` -having a `blocksync.BlockSync` parameter, which in turn depends on `ChainBlockservice()` and `ChainExchange()`. -The chain exchange service further depends on the chain store to save and retrieve chain data, which is reflected +having a `blocksync.BlockSync` parameter, which in turn depends on `ChainBlockservice()` and `ChainExchange()`. +The chain exchange service further depends on the chain store to save and retrieve chain data, which is reflected in `ChainExchange()` having `ChainGCBlockstore` as a parameter (which is just a wrapper around `ChainBlockstore` capable of garbage collection). This block store is the same store underlying the chain store, which is an indirect dependency of `NewSyncer()` (through the `StateManager`). -(FIXME: This last line is flaky, we need to resolve the hierarchy better, we sometimes refer to the chain store and sometimes to its underlying block store. We need a diagram to visualize all the different components just mentioned otherwise it is too hard to follow. We probably even need to skip some of the connections mentioned.) \ No newline at end of file +(FIXME: This last line is flaky, we need to resolve the hierarchy better, we sometimes refer to the chain store and sometimes to its underlying block store. We need a diagram to visualize all the different components just mentioned otherwise it is too hard to follow. We probably even need to skip some of the connections mentioned.) From 956c397c3c9489b64d8c55b6a4ff4fdbb0c73fd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A5=87?= Date: Mon, 22 Jun 2020 16:00:29 +0800 Subject: [PATCH 52/96] Add provingFaultsCmd for getting sectors information for the currently known faults --- cmd/lotus-storage-miner/proving.go | 55 ++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/cmd/lotus-storage-miner/proving.go b/cmd/lotus-storage-miner/proving.go index 8d3965fa8..d9d7da8e3 100644 --- a/cmd/lotus-storage-miner/proving.go +++ b/cmd/lotus-storage-miner/proving.go @@ -3,6 +3,7 @@ package main import ( "bytes" "fmt" + "math" "os" "text/tabwriter" "time" @@ -26,6 +27,60 @@ var provingCmd = &cli.Command{ Subcommands: []*cli.Command{ provingInfoCmd, provingDeadlinesCmd, + provingFaultsCmd, + }, +} + +var provingFaultsCmd = &cli.Command{ + Name: "faults", + Usage: "View the currently known proving faulty sectors information", + Action: func(cctx *cli.Context) error { + nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + if err != nil { + return err + } + defer closer() + + api, acloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer acloser() + + ctx := lcli.ReqContext(cctx) + + maddr, err := nodeApi.ActorAddress(ctx) + if err != nil { + return xerrors.Errorf("getting actor address: %w", err) + } + + var mas miner.State + { + mact, err := api.StateGetActor(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + rmas, err := api.ChainReadObj(ctx, mact.Head) + if err != nil { + return err + } + if err := mas.UnmarshalCBOR(bytes.NewReader(rmas)); err != nil { + return err + } + } + faults, err := mas.Faults.All(100000000000) + if err != nil { + return err + } + if len(faults) == 0 { + fmt.Println("no sector fault") + } + for _, num := range faults { + num2 := num % (mas.Info.WindowPoStPartitionSectors * (miner.WPoStPeriodDeadlines - 1)) + deadline := uint64(math.Floor(float64(num2)/float64(mas.Info.WindowPoStPartitionSectors))) + 1 + fmt.Printf("sector number = %d,deadline = %d\n", num, deadline) + } + return nil }, } From 49595965279f6ba49092255620b21b55bc0371ae Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Mon, 22 Jun 2020 11:42:26 -0400 Subject: [PATCH 53/96] Clarify expected block win when below minimum power threshold --- cmd/lotus-storage-miner/info.go | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/cmd/lotus-storage-miner/info.go b/cmd/lotus-storage-miner/info.go index 9d98f8569..01077bc83 100644 --- a/cmd/lotus-storage-miner/info.go +++ b/cmd/lotus-storage-miner/info.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "github.com/filecoin-project/specs-actors/actors/builtin/power" "sort" "time" @@ -119,16 +120,20 @@ var infoCmd = &cli.Command{ faultyPercentage) } - expWinChance := float64(types.BigMul(qpercI, types.NewInt(build.BlocksPerEpoch)).Int64()) / 1000000 - if expWinChance > 0 { - if expWinChance > 1 { - expWinChance = 1 - } - winRate := time.Duration(float64(time.Second*build.BlockDelay) / expWinChance) - winPerDay := float64(time.Hour*24) / float64(winRate) + if pow.MinerPower.RawBytePower.LessThan(power.ConsensusMinerMinPower) { + fmt.Print("Below minimum power threshold, no blocks will be won") + } else { + expWinChance := float64(types.BigMul(qpercI, types.NewInt(build.BlocksPerEpoch)).Int64()) / 1000000 + if expWinChance > 0 { + if expWinChance > 1 { + expWinChance = 1 + } + winRate := time.Duration(float64(time.Second*build.BlockDelay) / expWinChance) + winPerDay := float64(time.Hour*24) / float64(winRate) - fmt.Print("Expected block win rate: ") - color.Blue("%.4f/day (every %s)", winPerDay, winRate.Truncate(time.Second)) + fmt.Print("Expected block win rate: ") + color.Blue("%.4f/day (every %s)", winPerDay, winRate.Truncate(time.Second)) + } } fmt.Println() From 8cd2012d27445058fa5730badf94ed9572612e45 Mon Sep 17 00:00:00 2001 From: Lucas Molas Date: Mon, 22 Jun 2020 12:45:24 -0300 Subject: [PATCH 54/96] doc: report a vulnerability --- README.md | 4 ++++ SECURITY.md | 29 +++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 SECURITY.md diff --git a/README.md b/README.md index cd64073a6..a15276ee2 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,10 @@ Lotus is an implementation of the Filecoin Distributed Storage Network. For more For instructions on how to build lotus from source, please visit [https://docs.lotu.sh](https://docs.lotu.sh) or read the source [here](https://github.com/filecoin-project/lotus/tree/master/documentation). +## Reporting a Vulnerability + +Please send an email to security@filecoin.org. See our [security policy](SECURITY.md) for more details. + ## Development All work is tracked via issues. An attempt at keeping an up-to-date view on remaining work is in the [lotus testnet github project board](https://github.com/filecoin-project/lotus/projects/1). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..ecb600deb --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,29 @@ +# Security Policy + +## Reporting a Vulnerability + +For *critical* bugs, please send an email to security@filecoin.org. + +The bug reporting process differs between bugs that are critical and may crash the network, and others that are unlikely to cause problems if malicious parties know about it. For non-critical bugs, please simply file a GitHub [issue](https://github.com/filecoin-project/lotus/issues/new?template=bug_report.md). + +Please try to provide a clear description of any bugs reported, along with how to reproduce the bug if possible. More detailed bug reports (especially those with a PoC included) will help us move forward much faster. Additionally, please avoid reporting bugs that already have open issues. Take a moment to search the issue list of the related GitHub repositories before writing up a new report. + +Here are some examples of bugs we would consider 'critical': + +* If you can spend from a `multisig` wallet you do not control the keys for. +* If you can cause a miner to be slashed without them actually misbehaving. +* If you can maintain power without submitting windowed posts regularly. +* If you can craft a message that causes lotus nodes to panic. +* If you can cause your miner to win significantly more blocks than it should. +* If you can craft a message that causes a persistent fork in the network. +* If you can cause the total amount of Filecoin in the network to no longer be 2 billion. + +This is not an exhaustive list, but should provide some idea of what we consider 'critical'. + +## Supported Versions + +* TODO: This should be defined and set up by Mainnet launch. + +| Version | Supported | +| ------- | ------------------ | +| Testnet | :white_check_mark: | From 101ba0b79667a6d885506861a65bbcb8eb757f91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Mon, 22 Jun 2020 19:03:35 +0200 Subject: [PATCH 55/96] Update deps to support removing sectors --- api/apistruct/struct.go | 26 ++++++++++++++++++-------- cmd/lotus-seed/seed/seed.go | 2 +- go.mod | 6 +++--- go.sum | 10 ++++++---- 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index 0d69174ab..c9646204a 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -241,12 +241,14 @@ type WorkerStruct struct { Paths func(context.Context) ([]stores.StoragePath, error) `perm:"admin"` Info func(context.Context) (storiface.WorkerInfo, error) `perm:"admin"` - SealPreCommit1 func(ctx context.Context, sector abi.SectorID, ticket abi.SealRandomness, pieces []abi.PieceInfo) (storage.PreCommit1Out, error) `perm:"admin"` - SealPreCommit2 func(context.Context, abi.SectorID, storage.PreCommit1Out) (cids storage.SectorCids, err error) `perm:"admin"` - SealCommit1 func(ctx context.Context, sector abi.SectorID, ticket abi.SealRandomness, seed abi.InteractiveSealRandomness, pieces []abi.PieceInfo, cids storage.SectorCids) (storage.Commit1Out, error) `perm:"admin"` - SealCommit2 func(context.Context, abi.SectorID, storage.Commit1Out) (storage.Proof, error) `perm:"admin"` - FinalizeSector func(context.Context, abi.SectorID) error `perm:"admin"` - MoveStorage func(ctx context.Context, sector abi.SectorID) error `perm:"admin"` + SealPreCommit1 func(ctx context.Context, sector abi.SectorID, ticket abi.SealRandomness, pieces []abi.PieceInfo) (storage.PreCommit1Out, error) `perm:"admin"` + SealPreCommit2 func(context.Context, abi.SectorID, storage.PreCommit1Out) (cids storage.SectorCids, err error) `perm:"admin"` + SealCommit1 func(ctx context.Context, sector abi.SectorID, ticket abi.SealRandomness, seed abi.InteractiveSealRandomness, pieces []abi.PieceInfo, cids storage.SectorCids) (storage.Commit1Out, error) `perm:"admin"` + SealCommit2 func(context.Context, abi.SectorID, storage.Commit1Out) (storage.Proof, error) `perm:"admin"` + FinalizeSector func(context.Context, abi.SectorID, []storage.Range) error `perm:"admin"` + ReleaseUnsealed func(ctx context.Context, sector abi.SectorID, safeToFree []storage.Range) error `perm:"admin"` + Remove func(ctx context.Context, sector abi.SectorID) error `perm:"admin"` + MoveStorage func(ctx context.Context, sector abi.SectorID) error `perm:"admin"` UnsealPiece func(context.Context, abi.SectorID, storiface.UnpaddedByteIndex, abi.UnpaddedPieceSize, abi.SealRandomness, cid.Cid) error `perm:"admin"` ReadPiece func(context.Context, io.Writer, abi.SectorID, storiface.UnpaddedByteIndex, abi.UnpaddedPieceSize) error `perm:"admin"` @@ -910,8 +912,16 @@ func (w *WorkerStruct) SealCommit2(ctx context.Context, sector abi.SectorID, c1o return w.Internal.SealCommit2(ctx, sector, c1o) } -func (w *WorkerStruct) FinalizeSector(ctx context.Context, sector abi.SectorID) error { - return w.Internal.FinalizeSector(ctx, sector) +func (w *WorkerStruct) FinalizeSector(ctx context.Context, sector abi.SectorID, keepUnsealed []storage.Range) error { + return w.Internal.FinalizeSector(ctx, sector, keepUnsealed) +} + +func (w *WorkerStruct) ReleaseUnsealed(ctx context.Context, sector abi.SectorID, safeToFree []storage.Range) error { + return w.Internal.ReleaseUnsealed(ctx, sector, safeToFree) +} + +func (w *WorkerStruct) Remove(ctx context.Context, sector abi.SectorID) error { + return w.Internal.Remove(ctx, sector) } func (w *WorkerStruct) MoveStorage(ctx context.Context, sector abi.SectorID) error { diff --git a/cmd/lotus-seed/seed/seed.go b/cmd/lotus-seed/seed/seed.go index be366d4db..08ae91200 100644 --- a/cmd/lotus-seed/seed/seed.go +++ b/cmd/lotus-seed/seed/seed.go @@ -88,7 +88,7 @@ func PreSeal(maddr address.Address, spt abi.RegisteredSealProof, offset abi.Sect return nil, nil, xerrors.Errorf("commit: %w", err) } - if err := sb.FinalizeSector(context.TODO(), sid); err != nil { + if err := sb.FinalizeSector(context.TODO(), sid, nil); err != nil { return nil, nil, xerrors.Errorf("trim cache: %w", err) } diff --git a/go.mod b/go.mod index 68ac9a884..1fc21978a 100644 --- a/go.mod +++ b/go.mod @@ -29,10 +29,10 @@ require ( github.com/filecoin-project/go-paramfetch v0.0.2-0.20200605171344-fcac609550ca github.com/filecoin-project/go-statestore v0.1.0 github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b - github.com/filecoin-project/sector-storage v0.0.0-20200618073200-d9de9b7cb4b4 + github.com/filecoin-project/sector-storage v0.0.0-20200622150609-07cf84cbc787 github.com/filecoin-project/specs-actors v0.6.2-0.20200617175406-de392ca14121 - github.com/filecoin-project/specs-storage v0.1.0 - github.com/filecoin-project/storage-fsm v0.0.0-20200617183754-4380106d3e94 + github.com/filecoin-project/specs-storage v0.1.1-0.20200622113353-88a9704877ea + github.com/filecoin-project/storage-fsm v0.0.0-20200622165553-628c590c009b github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1 github.com/go-kit/kit v0.10.0 github.com/go-ole/go-ole v1.2.4 // indirect diff --git a/go.sum b/go.sum index e21c27f74..79f3fad57 100644 --- a/go.sum +++ b/go.sum @@ -253,8 +253,8 @@ github.com/filecoin-project/go-statestore v0.1.0/go.mod h1:LFc9hD+fRxPqiHiaqUEZO github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b h1:fkRZSPrYpk42PV3/lIXiL0LHetxde7vyYYvSsttQtfg= github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b/go.mod h1:Q0GQOBtKf1oE10eSXSlhN45kDBdGvEcVOqMiffqX+N8= github.com/filecoin-project/sector-storage v0.0.0-20200615154852-728a47ab99d6/go.mod h1:M59QnAeA/oV+Z8oHFLoNpGMv0LZ8Rll+vHVXX7GirPM= -github.com/filecoin-project/sector-storage v0.0.0-20200618073200-d9de9b7cb4b4 h1:lQC8Fbyn31/H4QxYAYwVV3PYZ9vS61EmjktZc5CaiYs= -github.com/filecoin-project/sector-storage v0.0.0-20200618073200-d9de9b7cb4b4/go.mod h1:M59QnAeA/oV+Z8oHFLoNpGMv0LZ8Rll+vHVXX7GirPM= +github.com/filecoin-project/sector-storage v0.0.0-20200622150609-07cf84cbc787 h1:MOnK9/z+ELvPv3+jO7GwnEj5d7tBdUvP+asOj7bvpog= +github.com/filecoin-project/sector-storage v0.0.0-20200622150609-07cf84cbc787/go.mod h1:8f0hWDzzIi1hKs4IVKH9RnDsO4LEHVz8BNat0okDOuY= github.com/filecoin-project/specs-actors v0.0.0-20200210130641-2d1fbd8672cf/go.mod h1:xtDZUB6pe4Pksa/bAJbJ693OilaC5Wbot9jMhLm3cZA= github.com/filecoin-project/specs-actors v0.3.0/go.mod h1:nQYnFbQ7Y0bHZyq6HDEuVlCPR+U3z5Q3wMOQ+2aiV+Y= github.com/filecoin-project/specs-actors v0.6.0/go.mod h1:dRdy3cURykh2R8O/DKqy8olScl70rmIS7GrB4hB1IDY= @@ -262,8 +262,10 @@ github.com/filecoin-project/specs-actors v0.6.2-0.20200617175406-de392ca14121 h1 github.com/filecoin-project/specs-actors v0.6.2-0.20200617175406-de392ca14121/go.mod h1:dRdy3cURykh2R8O/DKqy8olScl70rmIS7GrB4hB1IDY= github.com/filecoin-project/specs-storage v0.1.0 h1:PkDgTOT5W5Ao7752onjDl4QSv+sgOVdJbvFjOnD5w94= github.com/filecoin-project/specs-storage v0.1.0/go.mod h1:Pr5ntAaxsh+sLG/LYiL4tKzvA83Vk5vLODYhfNwOg7k= -github.com/filecoin-project/storage-fsm v0.0.0-20200617183754-4380106d3e94 h1:zPKiZPMgkFF0Lq13hsk8lcWlxeVAs6vvJaa3uHn9v70= -github.com/filecoin-project/storage-fsm v0.0.0-20200617183754-4380106d3e94/go.mod h1:q1YCutTSMq/yGYvDPHReT37bPfDLHltnwJutzR9kOY0= +github.com/filecoin-project/specs-storage v0.1.1-0.20200622113353-88a9704877ea h1:iixjULRQFPn7Q9KlIqfwLJnlAXO10bbkI+xy5GKGdLY= +github.com/filecoin-project/specs-storage v0.1.1-0.20200622113353-88a9704877ea/go.mod h1:Pr5ntAaxsh+sLG/LYiL4tKzvA83Vk5vLODYhfNwOg7k= +github.com/filecoin-project/storage-fsm v0.0.0-20200622165553-628c590c009b h1:4GlA3f/9GAAE4onNE7FzZbZvOeQoHbmwkO4GhKEuYzU= +github.com/filecoin-project/storage-fsm v0.0.0-20200622165553-628c590c009b/go.mod h1:LcDXEG2pUkTxKhIF2W1G5ZZO1S6QyCLzxFypT6NFW30= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= From 5adc18846656549356f99e95432266297fa5b6c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Mon, 22 Jun 2020 19:35:14 +0200 Subject: [PATCH 56/96] miner: Command to remove sectors --- api/api_storage.go | 1 + api/apistruct/struct.go | 5 +++++ cmd/lotus-storage-miner/sectors.go | 34 ++++++++++++++++++++++++++++++ node/impl/storminer.go | 4 ++++ storage/sealing.go | 4 ++++ 5 files changed, 48 insertions(+) diff --git a/api/api_storage.go b/api/api_storage.go index 90de01fb9..fb7dd4ae3 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -36,6 +36,7 @@ type StorageMiner interface { SectorsRefs(context.Context) (map[string][]SealedRef, error) SectorsUpdate(context.Context, abi.SectorNumber, SectorState) error + SectorRemove(context.Context, abi.SectorNumber) error StorageList(ctx context.Context) (map[stores.ID][]stores.Decl, error) StorageLocal(ctx context.Context) (map[stores.ID]string, error) diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index c9646204a..4f5e5e3a7 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -206,6 +206,7 @@ type StorageMinerStruct struct { SectorsList func(context.Context) ([]abi.SectorNumber, error) `perm:"read"` SectorsRefs func(context.Context) (map[string][]api.SealedRef, error) `perm:"read"` SectorsUpdate func(context.Context, abi.SectorNumber, api.SectorState) error `perm:"write"` + SectorRemove func(context.Context, abi.SectorNumber) error `perm:"admin"` WorkerConnect func(context.Context, string) error `perm:"admin"` // TODO: worker perm WorkerStats func(context.Context) (map[uint64]storiface.WorkerStats, error) `perm:"admin"` @@ -786,6 +787,10 @@ func (c *StorageMinerStruct) SectorsUpdate(ctx context.Context, id abi.SectorNum return c.Internal.SectorsUpdate(ctx, id, state) } +func (c *StorageMinerStruct) SectorRemove(ctx context.Context, number abi.SectorNumber) error { + return c.Internal.SectorRemove(ctx, number) +} + func (c *StorageMinerStruct) WorkerConnect(ctx context.Context, url string) error { return c.Internal.WorkerConnect(ctx, url) } diff --git a/cmd/lotus-storage-miner/sectors.go b/cmd/lotus-storage-miner/sectors.go index 4a3109f37..f042edd35 100644 --- a/cmd/lotus-storage-miner/sectors.go +++ b/cmd/lotus-storage-miner/sectors.go @@ -27,6 +27,7 @@ var sectorsCmd = &cli.Command{ sectorsRefsCmd, sectorsUpdateCmd, sectorsPledgeCmd, + sectorsRemoveCmd, }, } @@ -208,6 +209,39 @@ var sectorsRefsCmd = &cli.Command{ }, } + +var sectorsRemoveCmd = &cli.Command{ + Name: "remove", + Usage: "Forcefully remove a sector (WARNING: This means losing power and collateral for the removed sector)", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "really-do-it", + Usage: "pass this flag if you know what you are doing", + }, + }, + Action: func(cctx *cli.Context) error { + if !cctx.Bool("really-do-it") { + return xerrors.Errorf("this is a command for advanced users, only use it if you are sure of what you are doing") + } + nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := lcli.ReqContext(cctx) + if cctx.Args().Len() != 1 { + return xerrors.Errorf("must pass sector ID") + } + + id, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64) + if err != nil { + return xerrors.Errorf("could not parse sector ID: %w", err) + } + + return nodeApi.SectorRemove(ctx, abi.SectorNumber(id)) + }, +} + var sectorsUpdateCmd = &cli.Command{ Name: "update-state", Usage: "ADVANCED: manually update the state of a sector, this may aid in error recovery", diff --git a/node/impl/storminer.go b/node/impl/storminer.go index ed94e173d..a9eb0b86d 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -172,6 +172,10 @@ func (sm *StorageMinerAPI) SectorsUpdate(ctx context.Context, id abi.SectorNumbe return sm.Miner.ForceSectorState(ctx, id, sealing.SectorState(state)) } +func (sm *StorageMinerAPI) SectorRemove(ctx context.Context, id abi.SectorNumber) error { + return sm.Miner.RemoveSector(ctx, id) +} + func (sm *StorageMinerAPI) WorkerConnect(ctx context.Context, url string) error { w, err := connectRemoteWorker(ctx, sm, url) if err != nil { diff --git a/storage/sealing.go b/storage/sealing.go index c8b485379..f5716e85d 100644 --- a/storage/sealing.go +++ b/storage/sealing.go @@ -39,3 +39,7 @@ func (m *Miner) PledgeSector() error { func (m *Miner) ForceSectorState(ctx context.Context, id abi.SectorNumber, state sealing.SectorState) error { return m.sealing.ForceSectorState(ctx, id, state) } + +func (m *Miner) RemoveSector(ctx context.Context, id abi.SectorNumber) error { + return m.sealing.Remove(ctx, id) +} From e2435be0b92ada9438cc114338e1a7176a03ce43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Mon, 22 Jun 2020 19:36:26 +0200 Subject: [PATCH 57/96] build: Bump API version --- build/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/version.go b/build/version.go index d5766ce6e..03d5c0792 100644 --- a/build/version.go +++ b/build/version.go @@ -53,7 +53,7 @@ func (ve Version) EqMajorMinor(v2 Version) bool { } // APIVersion is a semver version of the rpc api exposed -var APIVersion Version = newVer(0, 3, 0) +var APIVersion Version = newVer(0, 4, 0) //nolint:varcheck,deadcode const ( From cd91e42a56f91f4d56e8455c6c4e25cd8e8e285e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Mon, 22 Jun 2020 19:39:08 +0200 Subject: [PATCH 58/96] ci: lint-changes based on master --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b502c8986..0c4f29c87 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -314,7 +314,7 @@ workflows: ci: jobs: - lint-changes: - args: "--new-from-rev origin/next" + args: "--new-from-rev origin/master" - mod-tidy-check - gofmt - test: From d20255b1b4b27596bbd8a27489dd029a31edc124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Mon, 22 Jun 2020 19:41:52 +0100 Subject: [PATCH 59/96] provide an option to disable loading of built-in assets. --- build/bootstrap.go | 4 ++++ build/flags.go | 15 +++++++++++++++ node/modules/storageminer.go | 6 ++++++ 3 files changed, 25 insertions(+) create mode 100644 build/flags.go diff --git a/build/bootstrap.go b/build/bootstrap.go index 0710f0dc0..6343a0172 100644 --- a/build/bootstrap.go +++ b/build/bootstrap.go @@ -13,6 +13,10 @@ import ( ) func BuiltinBootstrap() ([]peer.AddrInfo, error) { + if DisableBuiltinAssets { + return nil, nil + } + var out []peer.AddrInfo b := rice.MustFindBox("bootstrap") diff --git a/build/flags.go b/build/flags.go new file mode 100644 index 000000000..33e9f6ede --- /dev/null +++ b/build/flags.go @@ -0,0 +1,15 @@ +package build + +// DisableBuiltinAssets disables the resolution of go.rice boxes that store +// built-in assets, such as proof parameters, bootstrap peers, genesis blocks, +// etc. +// +// When this value is set to true, it is expected that the user will +// provide any such configurations through the Lotus API itself. +// +// This is useful when you're using Lotus as a library, such as to orchestrate +// test scenarios, or for other purposes where you don't need to use the +// defaults shipped with the binary. +// +// For this flag to be effective, it must be enabled _before_ instantiating Lotus. +var DisableBuiltinAssets = false diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 67e0e0842..c6cadec34 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -75,6 +75,12 @@ func GetParams(sbc *ffiwrapper.Config) error { return err } + // If built-in assets are disabled, we expect the user to have placed the right + // parameters in the right location on the filesystem (/var/tmp/filecoin-proof-parameters). + if build.DisableBuiltinAssets { + return nil + } + if err := paramfetch.GetParams(context.TODO(), build.ParametersJSON(), uint64(ssize)); err != nil { return xerrors.Errorf("fetching proof parameters: %w", err) } From 8649baccf7625c61f3619e778926ff4c02e0bb1c Mon Sep 17 00:00:00 2001 From: Mike Greenberg Date: Thu, 18 Jun 2020 22:18:31 -0400 Subject: [PATCH 60/96] Remove dev values from chainwatch.service --- scripts/chainwatch.service | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/chainwatch.service b/scripts/chainwatch.service index e958ec857..74afee0e9 100644 --- a/scripts/chainwatch.service +++ b/scripts/chainwatch.service @@ -1,15 +1,15 @@ [Unit] Description=Chainwatch -PartOf=sentinel.service After=lotus-daemon.service Requires=lotus-daemon.service [Service] Environment=GOLOG_FILE="/var/log/lotus/chainwatch.log" Environment=GOLOG_LOG_FMT="json" -Environment=LOTUS_DB="postgres://postgres:password@localhost:5432/postgres?sslmode=disable" -Environment=LOTUS_PATH="/root/.lotus" +Environment=LOTUS_DB="" +Environment=LOTUS_PATH="%h/.lotus" +EnvironmentFile=-/etc/lotus/chainwatch.env ExecStart=/usr/local/bin/chainwatch run [Install] -WantedBy=multiuser.target +WantedBy=multi-user.target From b71f771acbfcdbaa273a15e126051b79a51fc542 Mon Sep 17 00:00:00 2001 From: whyrusleeping Date: Mon, 22 Jun 2020 16:09:05 -0700 Subject: [PATCH 61/96] run block validation for tipsets in parallel --- chain/sync.go | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/chain/sync.go b/chain/sync.go index 26f30d95b..19ddac108 100644 --- a/chain/sync.go +++ b/chain/sync.go @@ -466,16 +466,25 @@ func (syncer *Syncer) ValidateTipSet(ctx context.Context, fts *store.FullTipSet) return nil } + var futures []async.ErrorFuture for _, b := range fts.Blocks { - if err := syncer.ValidateBlock(ctx, b); err != nil { - if isPermanent(err) { - syncer.bad.Add(b.Cid(), err.Error()) + futures = append(futures, async.Err(func() error { + if err := syncer.ValidateBlock(ctx, b); err != nil { + if isPermanent(err) { + syncer.bad.Add(b.Cid(), err.Error()) + } + return xerrors.Errorf("validating block %s: %w", b.Cid(), err) } - return xerrors.Errorf("validating block %s: %w", b.Cid(), err) - } - if err := syncer.sm.ChainStore().AddToTipSetTracker(b.Header); err != nil { - return xerrors.Errorf("failed to add validated header to tipset tracker: %w", err) + if err := syncer.sm.ChainStore().AddToTipSetTracker(b.Header); err != nil { + return xerrors.Errorf("failed to add validated header to tipset tracker: %w", err) + } + return nil + })) + } + for _, f := range futures { + if err := f.AwaitContext(ctx); err != nil { + return err } } return nil From 3752311f44f016b8111da3fe28ad0c93587be823 Mon Sep 17 00:00:00 2001 From: whyrusleeping Date: Mon, 22 Jun 2020 16:25:41 -0700 Subject: [PATCH 62/96] update to latest libp2p release --- go.mod | 15 ++++++++------- go.sum | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 68ac9a884..70fa0d19e 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/GeertJohan/go.rice v1.0.0 github.com/Gurpartap/async v0.0.0-20180927173644-4f7f499dd9ee github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect + github.com/alangpierce/go-forceexport v0.0.0-20160317203124-8f1d6941cd75 // indirect github.com/coreos/go-systemd/v22 v22.0.0 github.com/docker/go-units v0.4.0 github.com/drand/drand v0.9.2-0.20200616080806-a94e9c1636a4 @@ -45,7 +46,7 @@ require ( github.com/ipfs/go-bitswap v0.2.8 github.com/ipfs/go-block-format v0.0.2 github.com/ipfs/go-blockservice v0.1.3 - github.com/ipfs/go-cid v0.0.6-0.20200501230655-7c82f3b81c00 + github.com/ipfs/go-cid v0.0.6 github.com/ipfs/go-cidutil v0.0.2 github.com/ipfs/go-datastore v0.4.4 github.com/ipfs/go-ds-badger2 v0.1.0 @@ -76,20 +77,20 @@ require ( github.com/kelseyhightower/envconfig v1.4.0 github.com/lib/pq v1.2.0 github.com/libp2p/go-eventbus v0.2.1 - github.com/libp2p/go-libp2p v0.9.4 + github.com/libp2p/go-libp2p v0.10.0 github.com/libp2p/go-libp2p-connmgr v0.2.4 - github.com/libp2p/go-libp2p-core v0.5.7 + github.com/libp2p/go-libp2p-core v0.6.0 github.com/libp2p/go-libp2p-discovery v0.4.0 github.com/libp2p/go-libp2p-kad-dht v0.8.1 github.com/libp2p/go-libp2p-mplex v0.2.3 github.com/libp2p/go-libp2p-peer v0.2.0 - github.com/libp2p/go-libp2p-peerstore v0.2.4 + github.com/libp2p/go-libp2p-peerstore v0.2.6 github.com/libp2p/go-libp2p-pubsub v0.3.2 github.com/libp2p/go-libp2p-quic-transport v0.5.0 github.com/libp2p/go-libp2p-record v0.1.2 github.com/libp2p/go-libp2p-routing-helpers v0.2.3 github.com/libp2p/go-libp2p-secio v0.2.2 - github.com/libp2p/go-libp2p-swarm v0.2.6 + github.com/libp2p/go-libp2p-swarm v0.2.7 github.com/libp2p/go-libp2p-tls v0.1.3 github.com/libp2p/go-libp2p-yamux v0.2.8 github.com/libp2p/go-maddr-filter v0.1.0 @@ -100,11 +101,11 @@ require ( github.com/multiformats/go-multiaddr v0.2.2 github.com/multiformats/go-multiaddr-dns v0.2.0 github.com/multiformats/go-multiaddr-net v0.1.5 - github.com/multiformats/go-multibase v0.0.2 + github.com/multiformats/go-multibase v0.0.3 github.com/multiformats/go-multihash v0.0.13 github.com/opentracing/opentracing-go v1.1.0 github.com/stretchr/objx v0.2.0 // indirect - github.com/stretchr/testify v1.5.1 + github.com/stretchr/testify v1.6.1 github.com/syndtr/goleveldb v1.0.0 github.com/urfave/cli/v2 v2.2.0 github.com/whyrusleeping/bencher v0.0.0-20190829221104-bb6607aa8bba diff --git a/go.sum b/go.sum index e21c27f74..1c9dc6627 100644 --- a/go.sum +++ b/go.sum @@ -472,6 +472,8 @@ github.com/ipfs/go-cid v0.0.4/go.mod h1:4LLaPOQwmk5z9LBgQnpkivrx8BJjUyGwTXCd5Xfj github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= github.com/ipfs/go-cid v0.0.6-0.20200501230655-7c82f3b81c00 h1:QN88Q0kT2QiDaLxpR/SDsqOBtNIEF/F3n96gSDUimkA= github.com/ipfs/go-cid v0.0.6-0.20200501230655-7c82f3b81c00/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= +github.com/ipfs/go-cid v0.0.6 h1:go0y+GcDOGeJIV01FeBsta4FHngoA4Wz7KMeLkXAhMs= +github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cidutil v0.0.2 h1:CNOboQf1t7Qp0nuNh8QMmhJs0+Q//bRL1axtCnIB1Yo= github.com/ipfs/go-cidutil v0.0.2/go.mod h1:ewllrvrxG6AMYStla3GD7Cqn+XYSLqjK0vc+086tB6s= github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= @@ -557,6 +559,8 @@ github.com/ipfs/go-ipfs-routing v0.1.0 h1:gAJTT1cEeeLj6/DlLX6t+NxD9fQe2ymTO6qWRD github.com/ipfs/go-ipfs-routing v0.1.0/go.mod h1:hYoUkJLyAUKhF58tysKpids8RNDPO42BVMgK5dNsoqY= github.com/ipfs/go-ipfs-util v0.0.1 h1:Wz9bL2wB2YBJqggkA4dD7oSmqB4cAnpNbGrlHJulv50= github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc= +github.com/ipfs/go-ipfs-util v0.0.2 h1:59Sswnk1MFaiq+VcaknX7aYEyGyGDAA73ilhEK2POp8= +github.com/ipfs/go-ipfs-util v0.0.2/go.mod h1:CbPtkWJzjLdEcezDns2XYaehFVNXG9zrdrtMecczcsQ= github.com/ipfs/go-ipld-cbor v0.0.1/go.mod h1:RXHr8s4k0NE0TKhnrxqZC9M888QfsBN9rhS5NjfKzY8= github.com/ipfs/go-ipld-cbor v0.0.2/go.mod h1:wTBtrQZA3SoFKMVkp6cn6HMRteIB1VsmHA0AQFOn7Nc= github.com/ipfs/go-ipld-cbor v0.0.3/go.mod h1:wTBtrQZA3SoFKMVkp6cn6HMRteIB1VsmHA0AQFOn7Nc= @@ -725,6 +729,8 @@ github.com/libp2p/go-libp2p v0.8.3/go.mod h1:EsH1A+8yoWK+L4iKcbPYu6MPluZ+CHWI9El github.com/libp2p/go-libp2p v0.9.2/go.mod h1:cunHNLDVus66Ct9iXXcjKRLdmHdFdHVe1TAnbubJQqQ= github.com/libp2p/go-libp2p v0.9.4 h1:yighwjFvsF/qQaGtHPZfxcF+ph4ydCNnsKvg712lYRo= github.com/libp2p/go-libp2p v0.9.4/go.mod h1:NzQcC2o19xgwGqCmjx7DN+4h2F13qPCZ9UJmweYzsnU= +github.com/libp2p/go-libp2p v0.10.0 h1:7ooOvK1wi8eLpyTppy8TeH43UHy5uI75GAHGJxenUi0= +github.com/libp2p/go-libp2p v0.10.0/go.mod h1:yBJNpb+mGJdgrwbKAKrhPU0u3ogyNFTfjJ6bdM+Q/G8= github.com/libp2p/go-libp2p-autonat v0.0.2/go.mod h1:fs71q5Xk+pdnKU014o2iq1RhMs9/PMaG5zXRFNnIIT4= github.com/libp2p/go-libp2p-autonat v0.0.6/go.mod h1:uZneLdOkZHro35xIhpbtTzLlgYturpu4J5+0cZK3MqE= github.com/libp2p/go-libp2p-autonat v0.1.0/go.mod h1:1tLf2yXxiE/oKGtDwPYWTSYG3PtvYlJmg7NeVtPRqH8= @@ -750,6 +756,8 @@ github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3 github.com/libp2p/go-libp2p-circuit v0.2.1/go.mod h1:BXPwYDN5A8z4OEY9sOfr2DUQMLQvKt/6oku45YUmjIo= github.com/libp2p/go-libp2p-circuit v0.2.2 h1:87RLabJ9lrhoiSDDZyCJ80ZlI5TLJMwfyoGAaWXzWqA= github.com/libp2p/go-libp2p-circuit v0.2.2/go.mod h1:nkG3iE01tR3FoQ2nMm06IUrCpCyJp1Eo4A1xYdpjfs4= +github.com/libp2p/go-libp2p-circuit v0.2.3 h1:3Uw1fPHWrp1tgIhBz0vSOxRUmnKL8L/NGUyEd5WfSGM= +github.com/libp2p/go-libp2p-circuit v0.2.3/go.mod h1:nkG3iE01tR3FoQ2nMm06IUrCpCyJp1Eo4A1xYdpjfs4= github.com/libp2p/go-libp2p-connmgr v0.1.1/go.mod h1:wZxh8veAmU5qdrfJ0ZBLcU8oJe9L82ciVP/fl1VHjXk= github.com/libp2p/go-libp2p-connmgr v0.2.3/go.mod h1:Gqjg29zI8CwXX21zRxy6gOg8VYu3zVerJRt2KyktzH4= github.com/libp2p/go-libp2p-connmgr v0.2.4 h1:TMS0vc0TCBomtQJyWr7fYxcVYYhx+q/2gF++G5Jkl/w= @@ -776,6 +784,8 @@ github.com/libp2p/go-libp2p-core v0.5.5/go.mod h1:vj3awlOr9+GMZJFH9s4mpt9RHHgGqe github.com/libp2p/go-libp2p-core v0.5.6/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= github.com/libp2p/go-libp2p-core v0.5.7 h1:QK3xRwFxqd0Xd9bSZL+8yZ8ncZZbl6Zngd/+Y+A6sgQ= github.com/libp2p/go-libp2p-core v0.5.7/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= +github.com/libp2p/go-libp2p-core v0.6.0 h1:u03qofNYTBN+yVg08PuAKylZogVf0xcTEeM8skGf+ak= +github.com/libp2p/go-libp2p-core v0.6.0/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= github.com/libp2p/go-libp2p-crypto v0.0.1/go.mod h1:yJkNyDmO341d5wwXxDUGO0LykUVT72ImHNUqh5D/dBE= github.com/libp2p/go-libp2p-crypto v0.0.2/go.mod h1:eETI5OUfBnvARGOHrJz2eWNyTUxEGZnBxMcbUjfIj4I= github.com/libp2p/go-libp2p-crypto v0.1.0 h1:k9MFy+o2zGDNGsaoZl0MA3iZ75qXxr9OOoAZF+sD5OQ= @@ -835,6 +845,8 @@ github.com/libp2p/go-libp2p-peerstore v0.2.2/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRj github.com/libp2p/go-libp2p-peerstore v0.2.3/go.mod h1:K8ljLdFn590GMttg/luh4caB/3g0vKuY01psze0upRw= github.com/libp2p/go-libp2p-peerstore v0.2.4 h1:jU9S4jYN30kdzTpDAR7SlHUD+meDUjTODh4waLWF1ws= github.com/libp2p/go-libp2p-peerstore v0.2.4/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= +github.com/libp2p/go-libp2p-peerstore v0.2.6 h1:2ACefBX23iMdJU9Ke+dcXt3w86MIryes9v7In4+Qq3U= +github.com/libp2p/go-libp2p-peerstore v0.2.6/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= github.com/libp2p/go-libp2p-pnet v0.2.0 h1:J6htxttBipJujEjz1y0a5+eYoiPcFHhSYHH6na5f0/k= github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= github.com/libp2p/go-libp2p-protocol v0.0.1/go.mod h1:Af9n4PiruirSDjHycM1QuiMi/1VZNHYcK8cLgFJLZ4s= @@ -873,6 +885,8 @@ github.com/libp2p/go-libp2p-swarm v0.2.3/go.mod h1:P2VO/EpxRyDxtChXz/VPVXyTnszHv github.com/libp2p/go-libp2p-swarm v0.2.4/go.mod h1:/xIpHFPPh3wmSthtxdGbkHZ0OET1h/GGZes8Wku/M5Y= github.com/libp2p/go-libp2p-swarm v0.2.6 h1:UhMXIa+yCOALQyceENEIStMlbTCzOM6aWo6vw8QW17Q= github.com/libp2p/go-libp2p-swarm v0.2.6/go.mod h1:F9hrkZjO7dDbcEiYii/fAB1QdpLuU6h1pa4P5VNsEgc= +github.com/libp2p/go-libp2p-swarm v0.2.7 h1:4lV/sf7f0NuVqunOpt1I11+Z54+xp+m0eeAvxj/LyRc= +github.com/libp2p/go-libp2p-swarm v0.2.7/go.mod h1:ZSJ0Q+oq/B1JgfPHJAT2HTall+xYRNYp1xs4S2FBWKA= github.com/libp2p/go-libp2p-testing v0.0.1/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= @@ -1042,6 +1056,8 @@ github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc= github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI= github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= +github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4= +github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= github.com/multiformats/go-multiaddr v0.0.1/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.0.2/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.0.4/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= @@ -1071,6 +1087,8 @@ github.com/multiformats/go-multiaddr-net v0.1.5/go.mod h1:ilNnaM9HbmVFqsb/qcNysj github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= github.com/multiformats/go-multibase v0.0.2 h1:2pAgScmS1g9XjH7EtAfNhTuyrWYEWcxy0G5Wo85hWDA= github.com/multiformats/go-multibase v0.0.2/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= +github.com/multiformats/go-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77RblWplfIqk= +github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po= github.com/multiformats/go-multihash v0.0.7/go.mod h1:XuKXPp8VHcTygube3OWZC+aZrA+H1IhmjoCDtJc7PXM= @@ -1290,6 +1308,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= @@ -1777,6 +1797,7 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= From 9903eba7fb4ca4a827a3d637c133e4da3eecebfd Mon Sep 17 00:00:00 2001 From: whyrusleeping Date: Mon, 22 Jun 2020 20:08:19 -0700 Subject: [PATCH 63/96] stream bench import results to disk --- cmd/lotus-bench/import.go | 42 ++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/cmd/lotus-bench/import.go b/cmd/lotus-bench/import.go index f9c20ac9a..f7538daec 100644 --- a/cmd/lotus-bench/import.go +++ b/cmd/lotus-bench/import.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "io" "io/ioutil" "math" "os" @@ -119,14 +120,22 @@ var importBenchCmd = &cli.Command{ ts = next } - out := make([]TipSetExec, 0, len(tschain)) + ibj, err := os.Create("import-bench.json") + if err != nil { + return err + } + defer ibj.Close() //nolint:errcheck + + enc := json.NewEncoder(ibj) + + var lastTse *TipSetExec lastState := tschain[len(tschain)-1].ParentState() for i := len(tschain) - 2; i >= 0; i-- { cur := tschain[i] log.Infof("computing state (height: %d, ts=%s)", cur.Height(), cur.Cids()) if cur.ParentState() != lastState { - lastTrace := out[len(out)-1].Trace + lastTrace := lastTse.Trace d, err := json.MarshalIndent(lastTrace, "", " ") if err != nil { panic(err) @@ -140,26 +149,20 @@ var importBenchCmd = &cli.Command{ if err != nil { return err } - out = append(out, TipSetExec{ + + lastTse = &TipSetExec{ TipSet: cur.Key(), Trace: trace, Duration: time.Since(start), - }) + } lastState = st + if err := enc.Encode(lastTse); err != nil { + return xerrors.Errorf("failed to write out tipsetexec: %w", err) + } } pprof.StopCPUProfile() - ibj, err := os.Create("import-bench.json") - if err != nil { - return err - } - defer ibj.Close() //nolint:errcheck - - if err := json.NewEncoder(ibj).Encode(out); err != nil { - return err - } - return nil }, @@ -236,8 +239,15 @@ var importAnalyzeCmd = &cli.Command{ } var results []TipSetExec - if err := json.NewDecoder(fi).Decode(&results); err != nil { - return err + for { + var tse TipSetExec + if err := json.NewDecoder(fi).Decode(&tse); err != nil { + if err != io.EOF { + return err + } + break + } + results = append(results, tse) } chargeDeltas := make(map[string][]float64) From abd400801c1b40b92f3c9442cf86c4d3145340cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 23 Jun 2020 11:46:20 +0200 Subject: [PATCH 64/96] mod tidy --- go.mod | 1 - go.sum | 10 ++-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 70fa0d19e..655f402c9 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( github.com/GeertJohan/go.rice v1.0.0 github.com/Gurpartap/async v0.0.0-20180927173644-4f7f499dd9ee github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect - github.com/alangpierce/go-forceexport v0.0.0-20160317203124-8f1d6941cd75 // indirect github.com/coreos/go-systemd/v22 v22.0.0 github.com/docker/go-units v0.4.0 github.com/drand/drand v0.9.2-0.20200616080806-a94e9c1636a4 diff --git a/go.sum b/go.sum index 1c9dc6627..113c73c53 100644 --- a/go.sum +++ b/go.sum @@ -62,7 +62,6 @@ github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBA github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw= github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= -github.com/alangpierce/go-forceexport v0.0.0-20160317203124-8f1d6941cd75/go.mod h1:uAXEEpARkRhCZfEvy/y0Jcc888f9tHCc1W7/UeEtreE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -727,8 +726,6 @@ github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniV github.com/libp2p/go-libp2p v0.8.2/go.mod h1:NQDA/F/qArMHGe0J7sDScaKjW8Jh4y/ozQqBbYJ+BnA= github.com/libp2p/go-libp2p v0.8.3/go.mod h1:EsH1A+8yoWK+L4iKcbPYu6MPluZ+CHWI9El8cTaefiM= github.com/libp2p/go-libp2p v0.9.2/go.mod h1:cunHNLDVus66Ct9iXXcjKRLdmHdFdHVe1TAnbubJQqQ= -github.com/libp2p/go-libp2p v0.9.4 h1:yighwjFvsF/qQaGtHPZfxcF+ph4ydCNnsKvg712lYRo= -github.com/libp2p/go-libp2p v0.9.4/go.mod h1:NzQcC2o19xgwGqCmjx7DN+4h2F13qPCZ9UJmweYzsnU= github.com/libp2p/go-libp2p v0.10.0 h1:7ooOvK1wi8eLpyTppy8TeH43UHy5uI75GAHGJxenUi0= github.com/libp2p/go-libp2p v0.10.0/go.mod h1:yBJNpb+mGJdgrwbKAKrhPU0u3ogyNFTfjJ6bdM+Q/G8= github.com/libp2p/go-libp2p-autonat v0.0.2/go.mod h1:fs71q5Xk+pdnKU014o2iq1RhMs9/PMaG5zXRFNnIIT4= @@ -852,12 +849,10 @@ github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYc github.com/libp2p/go-libp2p-protocol v0.0.1/go.mod h1:Af9n4PiruirSDjHycM1QuiMi/1VZNHYcK8cLgFJLZ4s= github.com/libp2p/go-libp2p-protocol v0.1.0/go.mod h1:KQPHpAabB57XQxGrXCNvbL6UEXfQqUgC/1adR2Xtflk= github.com/libp2p/go-libp2p-pubsub v0.1.1/go.mod h1:ZwlKzRSe1eGvSIdU5bD7+8RZN/Uzw0t1Bp9R1znpR/Q= -github.com/libp2p/go-libp2p-pubsub v0.3.1/go.mod h1:TxPOBuo1FPdsTjFnv+FGZbNbWYsp74Culx+4ViQpato= github.com/libp2p/go-libp2p-pubsub v0.3.2-0.20200527132641-c0712c6e92cf/go.mod h1:TxPOBuo1FPdsTjFnv+FGZbNbWYsp74Culx+4ViQpato= github.com/libp2p/go-libp2p-pubsub v0.3.2 h1:k3cJm5JW5mjaWZkobS50sJLJWaB2mBi0HW4eRlE8mSo= github.com/libp2p/go-libp2p-pubsub v0.3.2/go.mod h1:Uss7/Cfz872KggNb+doCVPHeCDmXB7z500m/R8DaAUk= github.com/libp2p/go-libp2p-quic-transport v0.1.1/go.mod h1:wqG/jzhF3Pu2NrhJEvE+IE0NTHNXslOPn9JQzyCAxzU= -github.com/libp2p/go-libp2p-quic-transport v0.3.7/go.mod h1:Kr4aDtnfHHNeENn5J+sZIVc+t8HpQn9W6BOxhVGHbgI= github.com/libp2p/go-libp2p-quic-transport v0.5.0 h1:BUN1lgYNUrtv4WLLQ5rQmC9MCJ6uEXusezGvYRNoJXE= github.com/libp2p/go-libp2p-quic-transport v0.5.0/go.mod h1:IEcuC5MLxvZ5KuHKjRu+dr3LjCT1Be3rcD/4d8JrX8M= github.com/libp2p/go-libp2p-record v0.0.1/go.mod h1:grzqg263Rug/sRex85QrDOLntdFAymLDLm7lxMgU79Q= @@ -883,8 +878,6 @@ github.com/libp2p/go-libp2p-swarm v0.2.1/go.mod h1:x07b4zkMFo2EvgPV2bMTlNmdQc8i+ github.com/libp2p/go-libp2p-swarm v0.2.2/go.mod h1:fvmtQ0T1nErXym1/aa1uJEyN7JzaTNyBcHImCxRpPKU= github.com/libp2p/go-libp2p-swarm v0.2.3/go.mod h1:P2VO/EpxRyDxtChXz/VPVXyTnszHvokHKRhfkEgFKNM= github.com/libp2p/go-libp2p-swarm v0.2.4/go.mod h1:/xIpHFPPh3wmSthtxdGbkHZ0OET1h/GGZes8Wku/M5Y= -github.com/libp2p/go-libp2p-swarm v0.2.6 h1:UhMXIa+yCOALQyceENEIStMlbTCzOM6aWo6vw8QW17Q= -github.com/libp2p/go-libp2p-swarm v0.2.6/go.mod h1:F9hrkZjO7dDbcEiYii/fAB1QdpLuU6h1pa4P5VNsEgc= github.com/libp2p/go-libp2p-swarm v0.2.7 h1:4lV/sf7f0NuVqunOpt1I11+Z54+xp+m0eeAvxj/LyRc= github.com/libp2p/go-libp2p-swarm v0.2.7/go.mod h1:ZSJ0Q+oq/B1JgfPHJAT2HTall+xYRNYp1xs4S2FBWKA= github.com/libp2p/go-libp2p-testing v0.0.1/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= @@ -985,7 +978,6 @@ github.com/libp2p/go-yamux v1.3.7/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/h github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lucas-clemente/quic-go v0.11.2/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw= -github.com/lucas-clemente/quic-go v0.15.7/go.mod h1:Myi1OyS0FOjL3not4BxT7KN29bRkcMUV5JVVFLKtDp8= github.com/lucas-clemente/quic-go v0.16.0 h1:jJw36wfzGJhmOhAOaOC2lS36WgeqXQszH47A7spo1LI= github.com/lucas-clemente/quic-go v0.16.0/go.mod h1:I0+fcNTdb9eS1ZcjQZbDVPGchJ86chcIxPALn9lEJqE= github.com/lufia/iostat v1.1.0/go.mod h1:rEPNA0xXgjHQjuI5Cy05sLlS2oRcSlWHRLrvh/AQ+Pg= @@ -1309,6 +1301,7 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= @@ -1797,6 +1790,7 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= From 9e70e9524231ae98375a2ac93b3be4bd22e78992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 23 Jun 2020 13:21:01 +0200 Subject: [PATCH 65/96] sync: Correctly pass blocks to ValidateBlock --- chain/sync.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chain/sync.go b/chain/sync.go index 19ddac108..f20637c9e 100644 --- a/chain/sync.go +++ b/chain/sync.go @@ -468,6 +468,8 @@ func (syncer *Syncer) ValidateTipSet(ctx context.Context, fts *store.FullTipSet) var futures []async.ErrorFuture for _, b := range fts.Blocks { + b := b // rebind to a scoped variable + futures = append(futures, async.Err(func() error { if err := syncer.ValidateBlock(ctx, b); err != nil { if isPermanent(err) { From f8e7901b895a63cfab6d146866cd3d95086ab4f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 23 Jun 2020 14:44:34 +0200 Subject: [PATCH 66/96] Address review --- api/apistruct/struct.go | 2 +- cmd/lotus-storage-miner/sectors.go | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index 4f5e5e3a7..9e71155b3 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -206,7 +206,7 @@ type StorageMinerStruct struct { SectorsList func(context.Context) ([]abi.SectorNumber, error) `perm:"read"` SectorsRefs func(context.Context) (map[string][]api.SealedRef, error) `perm:"read"` SectorsUpdate func(context.Context, abi.SectorNumber, api.SectorState) error `perm:"write"` - SectorRemove func(context.Context, abi.SectorNumber) error `perm:"admin"` + SectorRemove func(context.Context, abi.SectorNumber) error `perm:"admin"` WorkerConnect func(context.Context, string) error `perm:"admin"` // TODO: worker perm WorkerStats func(context.Context) (map[uint64]storiface.WorkerStats, error) `perm:"admin"` diff --git a/cmd/lotus-storage-miner/sectors.go b/cmd/lotus-storage-miner/sectors.go index f042edd35..0dfce2f38 100644 --- a/cmd/lotus-storage-miner/sectors.go +++ b/cmd/lotus-storage-miner/sectors.go @@ -47,8 +47,9 @@ var sectorsPledgeCmd = &cli.Command{ } var sectorsStatusCmd = &cli.Command{ - Name: "status", - Usage: "Get the seal status of a sector by its ID", + Name: "status", + Usage: "Get the seal status of a sector by its number", + ArgsUsage: "", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "log", @@ -64,7 +65,7 @@ var sectorsStatusCmd = &cli.Command{ ctx := lcli.ReqContext(cctx) if !cctx.Args().Present() { - return fmt.Errorf("must specify sector ID to get status of") + return fmt.Errorf("must specify sector number to get status of") } id, err := strconv.ParseUint(cctx.Args().First(), 10, 64) @@ -209,10 +210,10 @@ var sectorsRefsCmd = &cli.Command{ }, } - var sectorsRemoveCmd = &cli.Command{ - Name: "remove", - Usage: "Forcefully remove a sector (WARNING: This means losing power and collateral for the removed sector)", + Name: "remove", + Usage: "Forcefully remove a sector (WARNING: This means losing power and collateral for the removed sector)", + ArgsUsage: "", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "really-do-it", @@ -230,12 +231,12 @@ var sectorsRemoveCmd = &cli.Command{ defer closer() ctx := lcli.ReqContext(cctx) if cctx.Args().Len() != 1 { - return xerrors.Errorf("must pass sector ID") + return xerrors.Errorf("must pass sector number") } id, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64) if err != nil { - return xerrors.Errorf("could not parse sector ID: %w", err) + return xerrors.Errorf("could not parse sector number: %w", err) } return nodeApi.SectorRemove(ctx, abi.SectorNumber(id)) @@ -262,12 +263,12 @@ var sectorsUpdateCmd = &cli.Command{ defer closer() ctx := lcli.ReqContext(cctx) if cctx.Args().Len() < 2 { - return xerrors.Errorf("must pass sector ID and new state") + return xerrors.Errorf("must pass sector number and new state") } id, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64) if err != nil { - return xerrors.Errorf("could not parse sector ID: %w", err) + return xerrors.Errorf("could not parse sector number: %w", err) } return nodeApi.SectorsUpdate(ctx, abi.SectorNumber(id), api.SectorState(cctx.Args().Get(1))) From 55ed45f108aa4a3dd6ed36c91e824c091ac25e13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A5=87?= Date: Tue, 23 Jun 2020 20:46:41 +0800 Subject: [PATCH 67/96] Get the good sectors to run windowPost --- storage/wdpost_run.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/storage/wdpost_run.go b/storage/wdpost_run.go index 5c1170551..fe42d2940 100644 --- a/storage/wdpost_run.go +++ b/storage/wdpost_run.go @@ -383,25 +383,25 @@ func (s *WindowPoStScheduler) runPost(ctx context.Context, di miner.DeadlineInfo return nil, xerrors.Errorf("get need prove sectors: %w", err) } - var skipped *abi.BitField - { - good, err := s.checkSectors(ctx, nps) - if err != nil { - return nil, xerrors.Errorf("checking sectors to skip: %w", err) - } - - skipped, err = bitfield.SubtractBitField(nps, good) - if err != nil { - return nil, xerrors.Errorf("nps - good: %w", err) - } + // var skipped *abi.BitField + // { + good, err := s.checkSectors(ctx, nps) + if err != nil { + return nil, xerrors.Errorf("checking sectors to skip: %w", err) } + skipped, err := bitfield.SubtractBitField(nps, good) + if err != nil { + return nil, xerrors.Errorf("nps - good: %w", err) + } + // } + skipCount, err := skipped.Count() if err != nil { return nil, xerrors.Errorf("getting skipped sector count: %w", err) } - ssi, err := s.sortedSectorInfo(ctx, nps, ts) + ssi, err := s.sortedSectorInfo(ctx, good, ts) if err != nil { return nil, xerrors.Errorf("getting sorted sector info: %w", err) } From 566a99240d9314f925b7aa25c86812960182a818 Mon Sep 17 00:00:00 2001 From: Yusef Napora Date: Tue, 23 Jun 2020 13:23:04 -0400 Subject: [PATCH 68/96] allow overriding drand config --- chain/beacon/drand/drand.go | 27 ++++++++++++++++++--------- node/modules/services.go | 3 ++- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/chain/beacon/drand/drand.go b/chain/beacon/drand/drand.go index 21bd2501a..91fc31aad 100644 --- a/chain/beacon/drand/drand.go +++ b/chain/beacon/drand/drand.go @@ -27,18 +27,22 @@ import ( var log = logging.Logger("drand") -var drandServers = []string{ - "https://pl-eu.testnet.drand.sh", - "https://pl-us.testnet.drand.sh", - "https://pl-sin.testnet.drand.sh", +type DrandConfig struct { + Servers []string + ChainInfo *dchain.Info } -var drandChain *dchain.Info +var defaultConfig = DrandConfig{ + Servers: []string{ + "https://pl-eu.testnet.drand.sh", + "https://pl-us.testnet.drand.sh", + "https://pl-sin.testnet.drand.sh", + }, +} func init() { - var err error - drandChain, err = dchain.InfoFromJSON(bytes.NewReader([]byte(build.DrandChain))) + defaultConfig.ChainInfo, err = dchain.InfoFromJSON(bytes.NewReader([]byte(build.DrandChain))) if err != nil { panic("could not unmarshal chain info: " + err.Error()) } @@ -73,16 +77,21 @@ type DrandBeacon struct { localCache map[uint64]types.BeaconEntry } -func NewDrandBeacon(genesisTs, interval uint64, ps *pubsub.PubSub) (*DrandBeacon, error) { +func NewDrandBeacon(genesisTs, interval uint64, ps *pubsub.PubSub, config *DrandConfig) (*DrandBeacon, error) { if genesisTs == 0 { panic("what are you doing this cant be zero") } + if config == nil { + config = &defaultConfig + } + drandChain := config.ChainInfo + dlogger := dlog.NewKitLoggerFrom(kzap.NewZapSugarLogger( log.SugaredLogger.Desugar(), zapcore.InfoLevel)) var clients []dclient.Client - for _, url := range drandServers { + for _, url := range config.Servers { hc, err := hclient.NewWithInfo(url, drandChain, nil) if err != nil { return nil, xerrors.Errorf("could not create http drand client: %w", err) diff --git a/node/modules/services.go b/node/modules/services.go index 27244ad3a..cbd0fb882 100644 --- a/node/modules/services.go +++ b/node/modules/services.go @@ -108,6 +108,7 @@ func RetrievalResolver(l *discovery.Local) retrievalmarket.PeerResolver { type RandomBeaconParams struct { fx.In + DrandConfig *drand.DrandConfig `optional:"true"` PubSub *pubsub.PubSub `optional:"true"` Cs *store.ChainStore } @@ -119,5 +120,5 @@ func RandomBeacon(p RandomBeaconParams, _ dtypes.AfterGenesisSet) (beacon.Random } //return beacon.NewMockBeacon(build.BlockDelay * time.Second) - return drand.NewDrandBeacon(gen.Timestamp, build.BlockDelay, p.PubSub) + return drand.NewDrandBeacon(gen.Timestamp, build.BlockDelay, p.PubSub, p.DrandConfig) } From 8adc831a3174cb47dc90ab8bfee83a01470bdcec Mon Sep 17 00:00:00 2001 From: laser Date: Tue, 23 Jun 2020 11:08:04 -0700 Subject: [PATCH 69/96] return error if retrieval deal rejected --- node/impl/client/client.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/node/impl/client/client.go b/node/impl/client/client.go index df6fb862c..9f8dd6bb9 100644 --- a/node/impl/client/client.go +++ b/node/impl/client/client.go @@ -348,6 +348,8 @@ func (a *API) ClientRetrieve(ctx context.Context, order api.RetrievalOrder, ref unsubscribe := a.Retrieval.SubscribeToEvents(func(event retrievalmarket.ClientEvent, state retrievalmarket.ClientDealState) { if state.PayloadCID.Equals(order.Root) { switch state.Status { + case retrievalmarket.DealStatusRejected: + retrievalResult <- xerrors.Errorf("Retrieval Proposal Rejected: %s", state.Message) case retrievalmarket.DealStatusFailed, retrievalmarket.DealStatusErrored: retrievalResult <- xerrors.Errorf("Retrieval Error: %s", state.Message) case retrievalmarket.DealStatusCompleted: From fb04b17fad43ac9c8cd7d08c856f4ce2900d90ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 23 Jun 2020 20:55:13 +0200 Subject: [PATCH 70/96] state: Get correct locked table in StateMarketParticipants --- node/impl/full/state.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/node/impl/full/state.go b/node/impl/full/state.go index e39527201..048ae7858 100644 --- a/node/impl/full/state.go +++ b/node/impl/full/state.go @@ -425,7 +425,7 @@ func (a *StateAPI) StateMarketParticipants(ctx context.Context, tsk types.TipSet if err != nil { return nil, err } - locked, err := hamt.LoadNode(ctx, cst, state.EscrowTable, hamt.UseTreeBitWidth(5)) + locked, err := hamt.LoadNode(ctx, cst, state.LockedTable, hamt.UseTreeBitWidth(5)) if err != nil { return nil, err } @@ -489,13 +489,11 @@ func (a *StateAPI) StateMarketDeals(ctx context.Context, tsk types.TipSetKey) (m var s market.DealState if err := sa.Get(ctx, i, &s); err != nil { - if err != nil { - if _, ok := err.(*amt.ErrNotFound); !ok { - return xerrors.Errorf("failed to get state for deal in proposals array: %w", err) - } - - s.SectorStartEpoch = -1 + if _, ok := err.(*amt.ErrNotFound); !ok { + return xerrors.Errorf("failed to get state for deal in proposals array: %w", err) } + + s.SectorStartEpoch = -1 } out[strconv.FormatInt(int64(i), 10)] = api.MarketDeal{ Proposal: d, From 960523f45f87c8925ad9ac268b595917e4527361 Mon Sep 17 00:00:00 2001 From: Yusef Napora Date: Tue, 23 Jun 2020 15:09:28 -0400 Subject: [PATCH 71/96] fix drand test --- chain/beacon/drand/drand_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chain/beacon/drand/drand_test.go b/chain/beacon/drand/drand_test.go index f35f3e4cc..219c86990 100644 --- a/chain/beacon/drand/drand_test.go +++ b/chain/beacon/drand/drand_test.go @@ -10,7 +10,7 @@ import ( ) func TestPrintGroupInfo(t *testing.T) { - c, err := hclient.New(drandServers[0], nil, nil) + c, err := hclient.New(defaultConfig.Servers[0], nil, nil) assert.NoError(t, err) cg := c.(interface { FetchChainInfo(groupHash []byte) (*dchain.Info, error) From 628872d0e425f3d38af8c19f345a846288dfcbf7 Mon Sep 17 00:00:00 2001 From: Yusef Napora Date: Tue, 23 Jun 2020 15:10:27 -0400 Subject: [PATCH 72/96] forgot my go fmt hook --- chain/beacon/drand/drand.go | 2 +- node/modules/services.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/chain/beacon/drand/drand.go b/chain/beacon/drand/drand.go index 91fc31aad..c800366d1 100644 --- a/chain/beacon/drand/drand.go +++ b/chain/beacon/drand/drand.go @@ -28,7 +28,7 @@ import ( var log = logging.Logger("drand") type DrandConfig struct { - Servers []string + Servers []string ChainInfo *dchain.Info } diff --git a/node/modules/services.go b/node/modules/services.go index cbd0fb882..c5d61d580 100644 --- a/node/modules/services.go +++ b/node/modules/services.go @@ -109,8 +109,8 @@ type RandomBeaconParams struct { fx.In DrandConfig *drand.DrandConfig `optional:"true"` - PubSub *pubsub.PubSub `optional:"true"` - Cs *store.ChainStore + PubSub *pubsub.PubSub `optional:"true"` + Cs *store.ChainStore } func RandomBeacon(p RandomBeaconParams, _ dtypes.AfterGenesisSet) (beacon.RandomBeacon, error) { From 06162290af86de9cb3a438bbb491e603104a7f0f Mon Sep 17 00:00:00 2001 From: laser Date: Tue, 23 Jun 2020 12:14:41 -0700 Subject: [PATCH 73/96] explicitly handle each deal status, as per PR feedback --- node/impl/client/client.go | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/node/impl/client/client.go b/node/impl/client/client.go index 9f8dd6bb9..72beda7ac 100644 --- a/node/impl/client/client.go +++ b/node/impl/client/client.go @@ -348,12 +348,34 @@ func (a *API) ClientRetrieve(ctx context.Context, order api.RetrievalOrder, ref unsubscribe := a.Retrieval.SubscribeToEvents(func(event retrievalmarket.ClientEvent, state retrievalmarket.ClientDealState) { if state.PayloadCID.Equals(order.Root) { switch state.Status { - case retrievalmarket.DealStatusRejected: - retrievalResult <- xerrors.Errorf("Retrieval Proposal Rejected: %s", state.Message) - case retrievalmarket.DealStatusFailed, retrievalmarket.DealStatusErrored: - retrievalResult <- xerrors.Errorf("Retrieval Error: %s", state.Message) - case retrievalmarket.DealStatusCompleted: + case + retrievalmarket.DealStatusCompleted: retrievalResult <- nil + case + retrievalmarket.DealStatusRejected: + retrievalResult <- xerrors.Errorf("Retrieval Proposal Rejected: %s", state.Message) + case + retrievalmarket.DealStatusDealNotFound, + retrievalmarket.DealStatusErrored, + retrievalmarket.DealStatusFailed: + retrievalResult <- xerrors.Errorf("Retrieval Error: %s", state.Message) + case + retrievalmarket.DealStatusAccepted, + retrievalmarket.DealStatusAwaitingAcceptance, + retrievalmarket.DealStatusBlocksComplete, + retrievalmarket.DealStatusFinalizing, + retrievalmarket.DealStatusFundsNeeded, + retrievalmarket.DealStatusFundsNeededLastPayment, + retrievalmarket.DealStatusNew, + retrievalmarket.DealStatusOngoing, + retrievalmarket.DealStatusPaymentChannelAddingFunds, + retrievalmarket.DealStatusPaymentChannelAllocatingLane, + retrievalmarket.DealStatusPaymentChannelCreating, + retrievalmarket.DealStatusPaymentChannelReady, + retrievalmarket.DealStatusVerified: + return + default: + retrievalResult <- xerrors.Errorf("Unhandled Retrieval Status: %+v", state.Status) } } }) From 309fbc15b2399421800a17fc25d4fad19e503f92 Mon Sep 17 00:00:00 2001 From: laser Date: Tue, 23 Jun 2020 12:22:33 -0700 Subject: [PATCH 74/96] import aliasing, for legibility --- node/impl/client/client.go | 62 ++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/node/impl/client/client.go b/node/impl/client/client.go index 72beda7ac..6e7f50f26 100644 --- a/node/impl/client/client.go +++ b/node/impl/client/client.go @@ -31,7 +31,7 @@ import ( "go.uber.org/fx" "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-fil-markets/retrievalmarket" + rm "github.com/filecoin-project/go-fil-markets/retrievalmarket" "github.com/filecoin-project/go-fil-markets/storagemarket" "github.com/filecoin-project/sector-storage/ffiwrapper" "github.com/filecoin-project/specs-actors/actors/abi" @@ -59,8 +59,8 @@ type API struct { paych.PaychAPI SMDealClient storagemarket.StorageClient - RetDiscovery retrievalmarket.PeerResolver - Retrieval retrievalmarket.RetrievalClient + RetDiscovery rm.PeerResolver + Retrieval rm.RetrievalClient Chain *store.ChainStore LocalDAG dtypes.ClientDAG @@ -202,7 +202,7 @@ func (a *API) ClientFindData(ctx context.Context, root cid.Cid) ([]api.QueryOffe out := make([]api.QueryOffer, len(peers)) for k, p := range peers { - out[k] = a.makeRetrievalQuery(ctx, p, root, retrievalmarket.QueryParams{}) + out[k] = a.makeRetrievalQuery(ctx, p, root, rm.QueryParams{}) } return out, nil @@ -213,25 +213,25 @@ func (a *API) ClientMinerQueryOffer(ctx context.Context, payload cid.Cid, miner if err != nil { return api.QueryOffer{}, err } - rp := retrievalmarket.RetrievalPeer{ + rp := rm.RetrievalPeer{ Address: miner, ID: mi.PeerId, } - return a.makeRetrievalQuery(ctx, rp, payload, retrievalmarket.QueryParams{}), nil + return a.makeRetrievalQuery(ctx, rp, payload, rm.QueryParams{}), nil } -func (a *API) makeRetrievalQuery(ctx context.Context, rp retrievalmarket.RetrievalPeer, payload cid.Cid, qp retrievalmarket.QueryParams) api.QueryOffer { +func (a *API) makeRetrievalQuery(ctx context.Context, rp rm.RetrievalPeer, payload cid.Cid, qp rm.QueryParams) api.QueryOffer { queryResponse, err := a.Retrieval.Query(ctx, rp, payload, qp) if err != nil { return api.QueryOffer{Err: err.Error(), Miner: rp.Address, MinerPeerID: rp.ID} } var errStr string switch queryResponse.Status { - case retrievalmarket.QueryResponseAvailable: + case rm.QueryResponseAvailable: errStr = "" - case retrievalmarket.QueryResponseUnavailable: + case rm.QueryResponseUnavailable: errStr = fmt.Sprintf("retrieval query offer was unavailable: %s", queryResponse.Message) - case retrievalmarket.QueryResponseError: + case rm.QueryResponseError: errStr = fmt.Sprintf("retrieval query offer errored: %s", queryResponse.Message) } @@ -345,34 +345,32 @@ func (a *API) ClientRetrieve(ctx context.Context, order api.RetrievalOrder, ref retrievalResult := make(chan error, 1) - unsubscribe := a.Retrieval.SubscribeToEvents(func(event retrievalmarket.ClientEvent, state retrievalmarket.ClientDealState) { + unsubscribe := a.Retrieval.SubscribeToEvents(func(event rm.ClientEvent, state rm.ClientDealState) { if state.PayloadCID.Equals(order.Root) { switch state.Status { - case - retrievalmarket.DealStatusCompleted: + case rm.DealStatusCompleted: retrievalResult <- nil - case - retrievalmarket.DealStatusRejected: + case rm.DealStatusRejected: retrievalResult <- xerrors.Errorf("Retrieval Proposal Rejected: %s", state.Message) case - retrievalmarket.DealStatusDealNotFound, - retrievalmarket.DealStatusErrored, - retrievalmarket.DealStatusFailed: + rm.DealStatusDealNotFound, + rm.DealStatusErrored, + rm.DealStatusFailed: retrievalResult <- xerrors.Errorf("Retrieval Error: %s", state.Message) case - retrievalmarket.DealStatusAccepted, - retrievalmarket.DealStatusAwaitingAcceptance, - retrievalmarket.DealStatusBlocksComplete, - retrievalmarket.DealStatusFinalizing, - retrievalmarket.DealStatusFundsNeeded, - retrievalmarket.DealStatusFundsNeededLastPayment, - retrievalmarket.DealStatusNew, - retrievalmarket.DealStatusOngoing, - retrievalmarket.DealStatusPaymentChannelAddingFunds, - retrievalmarket.DealStatusPaymentChannelAllocatingLane, - retrievalmarket.DealStatusPaymentChannelCreating, - retrievalmarket.DealStatusPaymentChannelReady, - retrievalmarket.DealStatusVerified: + rm.DealStatusAccepted, + rm.DealStatusAwaitingAcceptance, + rm.DealStatusBlocksComplete, + rm.DealStatusFinalizing, + rm.DealStatusFundsNeeded, + rm.DealStatusFundsNeededLastPayment, + rm.DealStatusNew, + rm.DealStatusOngoing, + rm.DealStatusPaymentChannelAddingFunds, + rm.DealStatusPaymentChannelAllocatingLane, + rm.DealStatusPaymentChannelCreating, + rm.DealStatusPaymentChannelReady, + rm.DealStatusVerified: return default: retrievalResult <- xerrors.Errorf("Unhandled Retrieval Status: %+v", state.Status) @@ -385,7 +383,7 @@ func (a *API) ClientRetrieve(ctx context.Context, order api.RetrievalOrder, ref _, err := a.Retrieval.Retrieve( ctx, order.Root, - retrievalmarket.NewParamsV0(ppb, order.PaymentInterval, order.PaymentIntervalIncrease), + rm.NewParamsV0(ppb, order.PaymentInterval, order.PaymentIntervalIncrease), order.Total, order.MinerPeerID, order.Client, From b448de422e1f929b02a9ddd719d594c66046d438 Mon Sep 17 00:00:00 2001 From: Yusef Napora Date: Tue, 23 Jun 2020 15:56:03 -0400 Subject: [PATCH 75/96] improve DrandConfig dependency injection --- build/params_shared.go | 9 ++++++++- chain/beacon/drand/drand.go | 32 ++++++++------------------------ chain/beacon/drand/drand_test.go | 5 ++++- node/builder.go | 1 + node/modules/dtypes/beacon.go | 6 ++++++ node/modules/lp2p/pubsub.go | 7 ++++--- node/modules/services.go | 14 +++++++++++--- 7 files changed, 42 insertions(+), 32 deletions(-) create mode 100644 node/modules/dtypes/beacon.go diff --git a/build/params_shared.go b/build/params_shared.go index 4da70aaeb..5ab07bd3f 100644 --- a/build/params_shared.go +++ b/build/params_shared.go @@ -121,4 +121,11 @@ const VerifSigCacheSize = 32000 const BlockMessageLimit = 512 const BlockGasLimit = 100_000_000_000 -var DrandChain = `{"public_key":"922a2e93828ff83345bae533f5172669a26c02dc76d6bf59c80892e12ab1455c229211886f35bb56af6d5bea981024df","period":25,"genesis_time":1590445175,"hash":"138a324aa6540f93d0dad002aa89454b1bec2b6e948682cde6bd4db40f4b7c9b"}` +var DrandConfig = dtypes.DrandConfig{ + Servers: []string{ + "https://pl-eu.testnet.drand.sh", + "https://pl-us.testnet.drand.sh", + "https://pl-sin.testnet.drand.sh", + }, + ChainInfoJSON: `{"public_key":"922a2e93828ff83345bae533f5172669a26c02dc76d6bf59c80892e12ab1455c229211886f35bb56af6d5bea981024df","period":25,"genesis_time":1590445175,"hash":"138a324aa6540f93d0dad002aa89454b1bec2b6e948682cde6bd4db40f4b7c9b"}`, +} diff --git a/chain/beacon/drand/drand.go b/chain/beacon/drand/drand.go index c800366d1..9769a8436 100644 --- a/chain/beacon/drand/drand.go +++ b/chain/beacon/drand/drand.go @@ -19,33 +19,17 @@ import ( logging "github.com/ipfs/go-log" pubsub "github.com/libp2p/go-libp2p-pubsub" - "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/specs-actors/actors/abi" + "github.com/filecoin-project/lotus/chain/beacon" "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/specs-actors/actors/abi" ) var log = logging.Logger("drand") type DrandConfig struct { - Servers []string - ChainInfo *dchain.Info -} - -var defaultConfig = DrandConfig{ - Servers: []string{ - "https://pl-eu.testnet.drand.sh", - "https://pl-us.testnet.drand.sh", - "https://pl-sin.testnet.drand.sh", - }, -} - -func init() { - var err error - defaultConfig.ChainInfo, err = dchain.InfoFromJSON(bytes.NewReader([]byte(build.DrandChain))) - if err != nil { - panic("could not unmarshal chain info: " + err.Error()) - } + Servers []string + ChainInfoJSON string } type drandPeer struct { @@ -77,15 +61,15 @@ type DrandBeacon struct { localCache map[uint64]types.BeaconEntry } -func NewDrandBeacon(genesisTs, interval uint64, ps *pubsub.PubSub, config *DrandConfig) (*DrandBeacon, error) { +func NewDrandBeacon(genesisTs, interval uint64, ps *pubsub.PubSub, config DrandConfig) (*DrandBeacon, error) { if genesisTs == 0 { panic("what are you doing this cant be zero") } - if config == nil { - config = &defaultConfig + drandChain, err := dchain.InfoFromJSON(bytes.NewReader([]byte(config.ChainInfoJSON))) + if err != nil { + return nil, xerrors.Errorf("unable to unmarshal drand chain info: %w", err) } - drandChain := config.ChainInfo dlogger := dlog.NewKitLoggerFrom(kzap.NewZapSugarLogger( log.SugaredLogger.Desugar(), zapcore.InfoLevel)) diff --git a/chain/beacon/drand/drand_test.go b/chain/beacon/drand/drand_test.go index 219c86990..d7d9c4d18 100644 --- a/chain/beacon/drand/drand_test.go +++ b/chain/beacon/drand/drand_test.go @@ -7,10 +7,13 @@ import ( dchain "github.com/drand/drand/chain" hclient "github.com/drand/drand/client/http" "github.com/stretchr/testify/assert" + + "github.com/filecoin-project/lotus/build" ) func TestPrintGroupInfo(t *testing.T) { - c, err := hclient.New(defaultConfig.Servers[0], nil, nil) + server := build.DrandConfig.Servers[0] + c, err := hclient.New(server, nil, nil) assert.NoError(t, err) cg := c.(interface { FetchChainInfo(groupHash []byte) (*dchain.Info, error) diff --git a/node/builder.go b/node/builder.go index e84c4422c..3b6c6fbda 100644 --- a/node/builder.go +++ b/node/builder.go @@ -218,6 +218,7 @@ func Online() Option { Override(new(dtypes.BootstrapPeers), modules.BuiltinBootstrap), Override(new(dtypes.DrandBootstrap), modules.DrandBootstrap), + Override(new(dtypes.DrandConfig), modules.BuiltinDrandConfig), Override(HandleIncomingMessagesKey, modules.HandleIncomingMessages), diff --git a/node/modules/dtypes/beacon.go b/node/modules/dtypes/beacon.go new file mode 100644 index 000000000..f3ebc4fac --- /dev/null +++ b/node/modules/dtypes/beacon.go @@ -0,0 +1,6 @@ +package dtypes + +type DrandConfig struct { + Servers []string + ChainInfoJSON string +} diff --git a/node/modules/lp2p/pubsub.go b/node/modules/lp2p/pubsub.go index bf9d88d42..ac23f56a8 100644 --- a/node/modules/lp2p/pubsub.go +++ b/node/modules/lp2p/pubsub.go @@ -44,13 +44,14 @@ type GossipIn struct { Db dtypes.DrandBootstrap Cfg *config.Pubsub Sk *dtypes.ScoreKeeper + Dr dtypes.DrandConfig } -func getDrandTopic() (string, error) { +func getDrandTopic(chainInfoJSON string) (string, error) { var drandInfo = struct { Hash string `json:"hash"` }{} - err := json.Unmarshal([]byte(build.DrandChain), &drandInfo) + err := json.Unmarshal([]byte(chainInfoJSON), &drandInfo) if err != nil { return "", xerrors.Errorf("could not unmarshal drand chain info: %w", err) } @@ -68,7 +69,7 @@ func GossipSub(in GossipIn) (service *pubsub.PubSub, err error) { } isBootstrapNode := in.Cfg.Bootstrapper - drandTopic, err := getDrandTopic() + drandTopic, err := getDrandTopic(in.Dr.ChainInfoJSON) if err != nil { return nil, err } diff --git a/node/modules/services.go b/node/modules/services.go index c5d61d580..ad7eb056f 100644 --- a/node/modules/services.go +++ b/node/modules/services.go @@ -108,9 +108,13 @@ func RetrievalResolver(l *discovery.Local) retrievalmarket.PeerResolver { type RandomBeaconParams struct { fx.In - DrandConfig *drand.DrandConfig `optional:"true"` - PubSub *pubsub.PubSub `optional:"true"` + PubSub *pubsub.PubSub `optional:"true"` Cs *store.ChainStore + DrandConfig dtypes.DrandConfig +} + +func BuiltinDrandConfig() dtypes.DrandConfig { + return build.DrandConfig } func RandomBeacon(p RandomBeaconParams, _ dtypes.AfterGenesisSet) (beacon.RandomBeacon, error) { @@ -120,5 +124,9 @@ func RandomBeacon(p RandomBeaconParams, _ dtypes.AfterGenesisSet) (beacon.Random } //return beacon.NewMockBeacon(build.BlockDelay * time.Second) - return drand.NewDrandBeacon(gen.Timestamp, build.BlockDelay, p.PubSub, p.DrandConfig) + config := drand.DrandConfig{ + Servers: p.DrandConfig.Servers, + ChainInfoJSON: p.DrandConfig.ChainInfoJSON, + } + return drand.NewDrandBeacon(gen.Timestamp, build.BlockDelay, p.PubSub, config) } From 5074cf8beb2947c2615a73d5506df8a8974423e8 Mon Sep 17 00:00:00 2001 From: Yusef Napora Date: Tue, 23 Jun 2020 16:23:06 -0400 Subject: [PATCH 76/96] import DrandConfig from dtypes --- chain/beacon/drand/drand.go | 8 ++------ node/modules/services.go | 6 +----- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/chain/beacon/drand/drand.go b/chain/beacon/drand/drand.go index 9769a8436..eb51a2af3 100644 --- a/chain/beacon/drand/drand.go +++ b/chain/beacon/drand/drand.go @@ -23,15 +23,11 @@ import ( "github.com/filecoin-project/lotus/chain/beacon" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/node/modules/dtypes" ) var log = logging.Logger("drand") -type DrandConfig struct { - Servers []string - ChainInfoJSON string -} - type drandPeer struct { addr string tls bool @@ -61,7 +57,7 @@ type DrandBeacon struct { localCache map[uint64]types.BeaconEntry } -func NewDrandBeacon(genesisTs, interval uint64, ps *pubsub.PubSub, config DrandConfig) (*DrandBeacon, error) { +func NewDrandBeacon(genesisTs, interval uint64, ps *pubsub.PubSub, config dtypes.DrandConfig) (*DrandBeacon, error) { if genesisTs == 0 { panic("what are you doing this cant be zero") } diff --git a/node/modules/services.go b/node/modules/services.go index ad7eb056f..2cba3a0be 100644 --- a/node/modules/services.go +++ b/node/modules/services.go @@ -124,9 +124,5 @@ func RandomBeacon(p RandomBeaconParams, _ dtypes.AfterGenesisSet) (beacon.Random } //return beacon.NewMockBeacon(build.BlockDelay * time.Second) - config := drand.DrandConfig{ - Servers: p.DrandConfig.Servers, - ChainInfoJSON: p.DrandConfig.ChainInfoJSON, - } - return drand.NewDrandBeacon(gen.Timestamp, build.BlockDelay, p.PubSub, config) + return drand.NewDrandBeacon(gen.Timestamp, build.BlockDelay, p.PubSub, p.DrandConfig) } From d524821c86c488e00190931308f96c4127c69820 Mon Sep 17 00:00:00 2001 From: whyrusleeping Date: Tue, 23 Jun 2020 14:25:45 -0700 Subject: [PATCH 77/96] add command to change registered multiaddrs for miner --- cmd/lotus-storage-miner/info.go | 76 ++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/cmd/lotus-storage-miner/info.go b/cmd/lotus-storage-miner/info.go index 01077bc83..667df5342 100644 --- a/cmd/lotus-storage-miner/info.go +++ b/cmd/lotus-storage-miner/info.go @@ -4,19 +4,22 @@ import ( "bytes" "context" "fmt" - "github.com/filecoin-project/specs-actors/actors/builtin/power" "sort" "time" "github.com/fatih/color" + ma "github.com/multiformats/go-multiaddr" "github.com/urfave/cli/v2" "golang.org/x/xerrors" + "github.com/filecoin-project/specs-actors/actors/abi" "github.com/filecoin-project/specs-actors/actors/builtin/miner" + "github.com/filecoin-project/specs-actors/actors/builtin/power" sealing "github.com/filecoin-project/storage-fsm" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" ) @@ -24,6 +27,9 @@ import ( var infoCmd = &cli.Command{ Name: "info", Usage: "Print storage miner info", + Subcommands: []*cli.Command{ + infoSetAddrsCmd, + }, Flags: []cli.Flag{ &cli.BoolFlag{Name: "color"}, }, @@ -248,3 +254,71 @@ func sectorsInfo(ctx context.Context, napi api.StorageMiner) error { return nil } + +var infoSetAddrsCmd = &cli.Command{ + Name: "set-addrs", + Usage: "set addresses that your miner can be publically dialed on", + Flags: []cli.Flag{ + &cli.Int64Flag{ + Name: "gas-limit", + Usage: "set gas limit", + Value: 100000, + }, + }, + Action: func(cctx *cli.Context) error { + nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + if err != nil { + return err + } + defer closer() + + api, acloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer acloser() + + ctx := lcli.ReqContext(cctx) + + var addrs []abi.Multiaddrs + for _, a := range cctx.Args().Slice() { + maddr, err := ma.NewMultiaddr(a) + if err != nil { + return fmt.Errorf("failed to parse %q as a multiaddr: %w", a, err) + } + + addrs = append(addrs, maddr.Bytes()) + } + + maddr, err := nodeApi.ActorAddress(ctx) + if err != nil { + return err + } + + minfo, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + params, err := actors.SerializeParams(&miner.ChangeMultiaddrsParams{addrs}) + if err != nil { + return err + } + + gasLimit := cctx.Int64("gas-limit") + + smsg, err := api.MpoolPushMessage(ctx, &types.Message{ + To: maddr, + From: minfo.Worker, + Value: types.NewInt(0), + GasPrice: types.NewInt(1), + GasLimit: gasLimit, + Method: 18, + Params: params, + }) + + fmt.Printf("Requested multiaddrs change in message %s\n", smsg.Cid()) + return nil + + }, +} From 69559bcf4f8a1428ffff97e09fffce8b84bc6a06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 23 Jun 2020 23:32:12 +0200 Subject: [PATCH 78/96] Use fsm, sector-storage master --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 1fc21978a..53344428d 100644 --- a/go.mod +++ b/go.mod @@ -29,10 +29,10 @@ require ( github.com/filecoin-project/go-paramfetch v0.0.2-0.20200605171344-fcac609550ca github.com/filecoin-project/go-statestore v0.1.0 github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b - github.com/filecoin-project/sector-storage v0.0.0-20200622150609-07cf84cbc787 + github.com/filecoin-project/sector-storage v0.0.0-20200623210524-47d93356586d github.com/filecoin-project/specs-actors v0.6.2-0.20200617175406-de392ca14121 github.com/filecoin-project/specs-storage v0.1.1-0.20200622113353-88a9704877ea - github.com/filecoin-project/storage-fsm v0.0.0-20200622165553-628c590c009b + github.com/filecoin-project/storage-fsm v0.0.0-20200623213010-fe71d5b42de3 github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1 github.com/go-kit/kit v0.10.0 github.com/go-ole/go-ole v1.2.4 // indirect diff --git a/go.sum b/go.sum index 79f3fad57..5cd3fda3f 100644 --- a/go.sum +++ b/go.sum @@ -253,8 +253,8 @@ github.com/filecoin-project/go-statestore v0.1.0/go.mod h1:LFc9hD+fRxPqiHiaqUEZO github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b h1:fkRZSPrYpk42PV3/lIXiL0LHetxde7vyYYvSsttQtfg= github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b/go.mod h1:Q0GQOBtKf1oE10eSXSlhN45kDBdGvEcVOqMiffqX+N8= github.com/filecoin-project/sector-storage v0.0.0-20200615154852-728a47ab99d6/go.mod h1:M59QnAeA/oV+Z8oHFLoNpGMv0LZ8Rll+vHVXX7GirPM= -github.com/filecoin-project/sector-storage v0.0.0-20200622150609-07cf84cbc787 h1:MOnK9/z+ELvPv3+jO7GwnEj5d7tBdUvP+asOj7bvpog= -github.com/filecoin-project/sector-storage v0.0.0-20200622150609-07cf84cbc787/go.mod h1:8f0hWDzzIi1hKs4IVKH9RnDsO4LEHVz8BNat0okDOuY= +github.com/filecoin-project/sector-storage v0.0.0-20200623210524-47d93356586d h1:yJJqXCMEhvXJoOS6T1O46FXl+A3mlttXhgjcTCp+Tgo= +github.com/filecoin-project/sector-storage v0.0.0-20200623210524-47d93356586d/go.mod h1:8f0hWDzzIi1hKs4IVKH9RnDsO4LEHVz8BNat0okDOuY= github.com/filecoin-project/specs-actors v0.0.0-20200210130641-2d1fbd8672cf/go.mod h1:xtDZUB6pe4Pksa/bAJbJ693OilaC5Wbot9jMhLm3cZA= github.com/filecoin-project/specs-actors v0.3.0/go.mod h1:nQYnFbQ7Y0bHZyq6HDEuVlCPR+U3z5Q3wMOQ+2aiV+Y= github.com/filecoin-project/specs-actors v0.6.0/go.mod h1:dRdy3cURykh2R8O/DKqy8olScl70rmIS7GrB4hB1IDY= @@ -264,8 +264,8 @@ github.com/filecoin-project/specs-storage v0.1.0 h1:PkDgTOT5W5Ao7752onjDl4QSv+sg github.com/filecoin-project/specs-storage v0.1.0/go.mod h1:Pr5ntAaxsh+sLG/LYiL4tKzvA83Vk5vLODYhfNwOg7k= github.com/filecoin-project/specs-storage v0.1.1-0.20200622113353-88a9704877ea h1:iixjULRQFPn7Q9KlIqfwLJnlAXO10bbkI+xy5GKGdLY= github.com/filecoin-project/specs-storage v0.1.1-0.20200622113353-88a9704877ea/go.mod h1:Pr5ntAaxsh+sLG/LYiL4tKzvA83Vk5vLODYhfNwOg7k= -github.com/filecoin-project/storage-fsm v0.0.0-20200622165553-628c590c009b h1:4GlA3f/9GAAE4onNE7FzZbZvOeQoHbmwkO4GhKEuYzU= -github.com/filecoin-project/storage-fsm v0.0.0-20200622165553-628c590c009b/go.mod h1:LcDXEG2pUkTxKhIF2W1G5ZZO1S6QyCLzxFypT6NFW30= +github.com/filecoin-project/storage-fsm v0.0.0-20200623213010-fe71d5b42de3 h1:nH3L7YVqrHINOmvZ+5jFjFNSi9/swXcm+uufXpkFJfo= +github.com/filecoin-project/storage-fsm v0.0.0-20200623213010-fe71d5b42de3/go.mod h1:Nl0JX9I3fIVtPEJ9HzGzO4D8LXehT9PqvUQUbNvcstc= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= From c8104a03e61f56f17c4a39d5efbf0ad23069dc07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Tue, 23 Jun 2020 22:51:25 +0100 Subject: [PATCH 79/96] some initial godocs. (#2118) --- chain/beacon/beacon.go | 4 + chain/beacon/drand/drand.go | 7 ++ chain/blocksync/blocksync.go | 19 +++++ chain/blocksync/blocksync_client.go | 8 ++ chain/store/fts.go | 3 + chain/store/index.go | 2 +- chain/store/store.go | 19 +++++ chain/sync.go | 127 ++++++++++++++++++++++++++-- miner/miner.go | 6 ++ 9 files changed, 188 insertions(+), 7 deletions(-) diff --git a/chain/beacon/beacon.go b/chain/beacon/beacon.go index 34405f3c8..2be2e7f1c 100644 --- a/chain/beacon/beacon.go +++ b/chain/beacon/beacon.go @@ -17,6 +17,10 @@ type Response struct { Err error } +// RandomBeacon represents a system that provides randomness to Lotus. +// Other components interrogate the RandomBeacon to acquire randomness that's +// valid for a specific chain epoch. Also to verify beacon entries that have +// been posted on chain. type RandomBeacon interface { Entry(context.Context, uint64) <-chan Response VerifyEntry(types.BeaconEntry, types.BeaconEntry) error diff --git a/chain/beacon/drand/drand.go b/chain/beacon/drand/drand.go index eb51a2af3..00ff05f81 100644 --- a/chain/beacon/drand/drand.go +++ b/chain/beacon/drand/drand.go @@ -41,6 +41,13 @@ func (dp *drandPeer) IsTLS() bool { return dp.tls } +// DrandBeacon connects Lotus with a drand network in order to provide +// randomness to the system in a way that's aligned with Filecoin rounds/epochs. +// +// We connect to drand peers via their public HTTP endpoints. The peers are +// enumerated in the drandServers variable. +// +// The root trust for the Drand chain is configured from build.DrandChain. type DrandBeacon struct { client dclient.Client diff --git a/chain/blocksync/blocksync.go b/chain/blocksync/blocksync.go index daca9ce20..a9251c419 100644 --- a/chain/blocksync/blocksync.go +++ b/chain/blocksync/blocksync.go @@ -10,6 +10,7 @@ import ( "golang.org/x/xerrors" cborutil "github.com/filecoin-project/go-cbor-util" + "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" @@ -27,6 +28,24 @@ const BlockSyncProtocolID = "/fil/sync/blk/0.0.1" const BlockSyncMaxRequestLength = 800 +// BlockSyncService is the component that services BlockSync requests from +// peers. +// +// BlockSync is the basic chain synchronization protocol of Filecoin. BlockSync +// is an RPC-oriented protocol, with a single operation to request blocks. +// +// A request contains a start anchor block (referred to with a CID), and a +// amount of blocks requested beyond the anchor (including the anchor itself). +// +// A client can also pass options, encoded as a 64-bit bitfield. Lotus supports +// two options at the moment: +// +// - include block contents +// - include block messages +// +// The response will include a status code, an optional message, and the +// response payload in case of success. The payload is a slice of serialized +// tipsets. type BlockSyncService struct { cs *store.ChainStore } diff --git a/chain/blocksync/blocksync_client.go b/chain/blocksync/blocksync_client.go index 129e8d332..daa4b6335 100644 --- a/chain/blocksync/blocksync_client.go +++ b/chain/blocksync/blocksync_client.go @@ -64,6 +64,11 @@ func (bs *BlockSync) processStatus(req *BlockSyncRequest, res *BlockSyncResponse } } +// GetBlocks fetches count blocks from the network, from the provided tipset +// *backwards*, returning as many tipsets as count. +// +// {hint/usage}: This is used by the Syncer during normal chain syncing and when +// resolving forks. func (bs *BlockSync) GetBlocks(ctx context.Context, tsk types.TipSetKey, count int) ([]*types.TipSet, error) { ctx, span := trace.StartSpan(ctx, "bsync.GetBlocks") defer span.End() @@ -80,7 +85,9 @@ func (bs *BlockSync) GetBlocks(ctx context.Context, tsk types.TipSetKey, count i Options: BSOptBlocks, } + // this peerset is sorted by latency and failure counting. peers := bs.getPeers() + // randomize the first few peers so we don't always pick the same peer shufflePrefix(peers) @@ -356,6 +363,7 @@ func (bs *BlockSync) RemovePeer(p peer.ID) { bs.syncPeers.removePeer(p) } +// getPeers returns a preference-sorted set of peers to query. func (bs *BlockSync) getPeers() []peer.ID { return bs.syncPeers.prefSortedPeers() } diff --git a/chain/store/fts.go b/chain/store/fts.go index f9ec4459e..0324938d7 100644 --- a/chain/store/fts.go +++ b/chain/store/fts.go @@ -32,8 +32,11 @@ func (fts *FullTipSet) Cids() []cid.Cid { return cids } +// TipSet returns a narrower view of this FullTipSet elliding the block +// messages. func (fts *FullTipSet) TipSet() *types.TipSet { if fts.tipset != nil { + // FIXME: fts.tipset is actually never set. Should it memoize? return fts.tipset } diff --git a/chain/store/index.go b/chain/store/index.go index bb363ec18..7edbf251f 100644 --- a/chain/store/index.go +++ b/chain/store/index.go @@ -34,7 +34,7 @@ type lbEntry struct { target types.TipSetKey } -func (ci *ChainIndex) GetTipsetByHeight(ctx context.Context, from *types.TipSet, to abi.ChainEpoch) (*types.TipSet, error) { +func (ci *ChainIndex) GetTipsetByHeight(_ context.Context, from *types.TipSet, to abi.ChainEpoch) (*types.TipSet, error) { if from.Height()-to <= ci.skipLength { return ci.walkBack(from, to) } diff --git a/chain/store/store.go b/chain/store/store.go index 0edccb95c..4dabb96f7 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -52,6 +52,15 @@ var blockValidationCacheKeyPrefix = dstore.NewKey("blockValidation") // ReorgNotifee represents a callback that gets called upon reorgs. type ReorgNotifee func(rev, app []*types.TipSet) error +// ChainStore is the main point of access to chain data. +// +// Raw chain data is stored in the Blockstore, with relevant markers (genesis, +// latest head tipset references) being tracked in the Datastore (key-value +// store). +// +// To alleviate disk access, the ChainStore has two ARC caches: +// 1. a tipset cache +// 2. a block => messages references cache. type ChainStore struct { bs bstore.Blockstore ds dstore.Datastore @@ -266,6 +275,9 @@ func (cs *ChainStore) PutTipSet(ctx context.Context, ts *types.TipSet) error { return nil } +// MaybeTakeHeavierTipSet evaluates the incoming tipset and locks it in our +// internal state as our new head, if and only if it is heavier than the current +// head. func (cs *ChainStore) MaybeTakeHeavierTipSet(ctx context.Context, ts *types.TipSet) error { cs.heaviestLk.Lock() defer cs.heaviestLk.Unlock() @@ -331,6 +343,9 @@ func (cs *ChainStore) reorgWorker(ctx context.Context, initialNotifees []ReorgNo return out } +// takeHeaviestTipSet actually sets the incoming tipset as our head both in +// memory and in the ChainStore. It also sends a notification to deliver to +// ReorgNotifees. func (cs *ChainStore) takeHeaviestTipSet(ctx context.Context, ts *types.TipSet) error { _, span := trace.StartSpan(ctx, "takeHeaviestTipSet") defer span.End() @@ -368,6 +383,7 @@ func (cs *ChainStore) SetHead(ts *types.TipSet) error { return cs.takeHeaviestTipSet(context.TODO(), ts) } +// Contains returns whether our BlockStore has all blocks in the supplied TipSet. func (cs *ChainStore) Contains(ts *types.TipSet) (bool, error) { for _, c := range ts.Cids() { has, err := cs.bs.Has(c) @@ -382,6 +398,8 @@ func (cs *ChainStore) Contains(ts *types.TipSet) (bool, error) { return true, nil } +// GetBlock fetches a BlockHeader with the supplied CID. It returns +// blockstore.ErrNotFound if the block was not found in the BlockStore. func (cs *ChainStore) GetBlock(c cid.Cid) (*types.BlockHeader, error) { sb, err := cs.bs.Get(c) if err != nil { @@ -474,6 +492,7 @@ func (cs *ChainStore) ReorgOps(a, b *types.TipSet) ([]*types.TipSet, []*types.Ti return leftChain, rightChain, nil } +// GetHeaviestTipSet returns the current heaviest tipset known (i.e. our head). func (cs *ChainStore) GetHeaviestTipSet() *types.TipSet { cs.heaviestLk.Lock() defer cs.heaviestLk.Unlock() diff --git a/chain/sync.go b/chain/sync.go index f20637c9e..0a08e8b15 100644 --- a/chain/sync.go +++ b/chain/sync.go @@ -53,6 +53,29 @@ var log = logging.Logger("chain") var LocalIncoming = "incoming" +// Syncer is in charge of running the chain synchronization logic. As such, it +// is tasked with these functions, amongst others: +// +// * Fast-forwards the chain as it learns of new TipSets from the network via +// the SyncManager. +// * Applies the fork choice rule to select the correct side when confronted +// with a fork in the network. +// * Requests block headers and messages from other peers when not available +// in our BlockStore. +// * Tracks blocks marked as bad in a cache. +// * Keeps the BlockStore and ChainStore consistent with our view of the world, +// the latter of which in turn informs other components when a reorg has been +// committed. +// +// The Syncer does not run workers itself. It's mainly concerned with +// ensuring a consistent state of chain consensus. The reactive and network- +// interfacing processes are part of other components, such as the SyncManager +// (which owns the sync scheduler and sync workers), BlockSync, the HELLO +// protocol, and the gossipsub block propagation layer. +// +// {hint/concept} The fork-choice rule as it currently stands is: "pick the +// chain with the heaviest weight, so long as it hasn’t deviated one finality +// threshold from our head (900 epochs, parameter determined by spec-actors)". type Syncer struct { // The interface for accessing and putting tipsets into local storage store *store.ChainStore @@ -85,6 +108,7 @@ type Syncer struct { verifier ffiwrapper.Verifier } +// NewSyncer creates a new Syncer object. func NewSyncer(sm *stmgr.StateManager, bsync *blocksync.BlockSync, connmgr connmgr.ConnManager, self peer.ID, beacon beacon.RandomBeacon, verifier ffiwrapper.Verifier) (*Syncer, error) { gen, err := sm.ChainStore().GetGenesis() if err != nil { @@ -182,6 +206,11 @@ func (syncer *Syncer) InformNewHead(from peer.ID, fts *store.FullTipSet) bool { return true } +// IncomingBlocks spawns a goroutine that subscribes to the local eventbus to +// receive new block headers as they arrive from the network, and sends them to +// the returned channel. +// +// These blocks have not necessarily been incorporated to our view of the chain. func (syncer *Syncer) IncomingBlocks(ctx context.Context) (<-chan *types.BlockHeader, error) { sub := syncer.incoming.Sub(LocalIncoming) out := make(chan *types.BlockHeader, 10) @@ -209,11 +238,15 @@ func (syncer *Syncer) IncomingBlocks(ctx context.Context) (<-chan *types.BlockHe return out, nil } +// ValidateMsgMeta performs structural and content hash validation of the +// messages within this block. If validation passes, it stores the messages in +// the underlying IPLD block store. func (syncer *Syncer) ValidateMsgMeta(fblk *types.FullBlock) error { if msgc := len(fblk.BlsMessages) + len(fblk.SecpkMessages); msgc > build.BlockMessageLimit { return xerrors.Errorf("block %s has too many messages (%d)", fblk.Header.Cid(), msgc) } + // Collect the CIDs of both types of messages separately: BLS and Secpk. var bcids, scids []cbg.CBORMarshaler for _, m := range fblk.BlsMessages { c := cbg.CborCid(m.Cid()) @@ -231,11 +264,14 @@ func (syncer *Syncer) ValidateMsgMeta(fblk *types.FullBlock) error { blockstore := syncer.store.Blockstore() bs := cbor.NewCborStore(blockstore) + + // Compute the root CID of the combined message trie. smroot, err := computeMsgMeta(bs, bcids, scids) if err != nil { return xerrors.Errorf("validating msgmeta, compute failed: %w", err) } + // Check that the message trie root matches with what's in the block. if fblk.Header.Messages != smroot { return xerrors.Errorf("messages in full block did not match msgmeta root in header (%s != %s)", fblk.Header.Messages, smroot) } @@ -345,6 +381,8 @@ func zipTipSetAndMessages(bs cbor.IpldStore, ts *types.TipSet, allbmsgs []*types return fts, nil } +// computeMsgMeta computes the root CID of the combined arrays of message CIDs +// of both types (BLS and Secpk). func computeMsgMeta(bs cbor.IpldStore, bmsgCids, smsgCids []cbg.CBORMarshaler) (cid.Cid, error) { ctx := context.TODO() bmroot, err := amt.FromArray(ctx, bs, bmsgCids) @@ -368,14 +406,24 @@ func computeMsgMeta(bs cbor.IpldStore, bmsgCids, smsgCids []cbg.CBORMarshaler) ( return mrcid, nil } +// FetchTipSet tries to load the provided tipset from the store, and falls back +// to the network (BlockSync) by querying the supplied peer if not found +// locally. +// +// {hint/usage} This is used from the HELLO protocol, to fetch the greeting +// peer's heaviest tipset if we don't have it. func (syncer *Syncer) FetchTipSet(ctx context.Context, p peer.ID, tsk types.TipSetKey) (*store.FullTipSet, error) { if fts, err := syncer.tryLoadFullTipSet(tsk); err == nil { return fts, nil } + // fall back to the network. return syncer.Bsync.GetFullTipSet(ctx, p, tsk) } +// tryLoadFullTipSet queries the tipset in the ChainStore, and returns a full +// representation of it containing FullBlocks. If ALL blocks are not found +// locally, it errors entirely with blockstore.ErrNotFound. func (syncer *Syncer) tryLoadFullTipSet(tsk types.TipSetKey) (*store.FullTipSet, error) { ts, err := syncer.store.LoadTipSet(tsk) if err != nil { @@ -400,6 +448,12 @@ func (syncer *Syncer) tryLoadFullTipSet(tsk types.TipSetKey) (*store.FullTipSet, return fts, nil } +// Sync tries to advance our view of the chain to `maybeHead`. It does nothing +// if our current head is heavier than the requested tipset, or if we're already +// at the requested head, or if the head is the genesis. +// +// Most of the heavy-lifting logic happens in syncer#collectChain. Refer to the +// godocs on that method for a more detailed view. func (syncer *Syncer) Sync(ctx context.Context, maybeHead *types.TipSet) error { ctx, span := trace.StartSpan(ctx, "chain.Sync") defer span.End() @@ -1004,6 +1058,39 @@ func extractSyncState(ctx context.Context) *SyncerState { return nil } +// collectHeaders collects the headers from the blocks between any two tipsets. +// +// `from` is the heaviest/projected/target tipset we have learned about, and +// `to` is usually an anchor tipset we already have in our view of the chain +// (which could be the genesis). +// +// collectHeaders checks if portions of the chain are in our ChainStore; falling +// down to the network to retrieve the missing parts. If during the process, any +// portion we receive is in our denylist (bad list), we short-circuit. +// +// {hint/naming}: `from` and `to` is in inverse order. `from` is the highest, +// and `to` is the lowest. This method traverses the chain backwards. +// +// {hint/usage}: This is used by collectChain, which is in turn called from the +// main Sync method (Syncer#Sync), so it's a pretty central method. +// +// {hint/logic}: The logic of this method is as follows: +// +// 1. Check that the from tipset is not linked to a parent block known to be +// bad. +// 2. Check the consistency of beacon entries in the from tipset. We check +// total equality of the BeaconEntries in each block. +// 3. Travers the chain backwards, for each tipset: +// 3a. Load it from the chainstore; if found, it move on to its parent. +// 3b. Query our peers via BlockSync in batches, requesting up to a +// maximum of 500 tipsets every time. +// +// Once we've concluded, if we find a mismatching tipset at the height where the +// anchor tipset should be, we are facing a fork, and we invoke Syncer#syncFork +// to resolve it. Refer to the godocs there. +// +// All throughout the process, we keep checking if the received blocks are in +// the deny list, and short-circuit the process if so. func (syncer *Syncer) collectHeaders(ctx context.Context, from *types.TipSet, to *types.TipSet) ([]*types.TipSet, error) { ctx, span := trace.StartSpan(ctx, "collectHeaders") defer span.End() @@ -1020,6 +1107,8 @@ func (syncer *Syncer) collectHeaders(ctx context.Context, from *types.TipSet, to } } + // Check if the parents of the from block are in the denylist. + // i.e. if a fork of the chain has been requested that we know to be bad. for _, pcid := range from.Parents().Cids() { if reason, ok := syncer.bad.Has(pcid); ok { markBad("linked to %s", pcid) @@ -1090,8 +1179,8 @@ loop: } // NB: GetBlocks validates that the blocks are in-fact the ones we - // requested, and that they are correctly linked to eachother. It does - // not validate any state transitions + // requested, and that they are correctly linked to one another. It does + // not validate any state transitions. window := 500 if gap := int(blockSet[len(blockSet)-1].Height() - untilHeight); gap < window { window = gap @@ -1132,7 +1221,6 @@ loop: at = blks[len(blks)-1].Parents() } - // We have now ascertained that this is *not* a 'fast forward' if !types.CidArrsEqual(blockSet[len(blockSet)-1].Parents().Cids(), to.Cids()) { last := blockSet[len(blockSet)-1] if last.Parents() == to.Parents() { @@ -1140,6 +1228,8 @@ loop: return blockSet, nil } + // We have now ascertained that this is *not* a 'fast forward' + log.Warnf("(fork detected) synced header chain (%s - %d) does not link to our best block (%s - %d)", from.Cids(), from.Height(), to.Cids(), to.Height()) fork, err := syncer.syncFork(ctx, last, to) if err != nil { @@ -1161,6 +1251,12 @@ loop: var ErrForkTooLong = fmt.Errorf("fork longer than threshold") +// syncFork tries to obtain the chain fragment that links a fork into a common +// ancestor in our view of the chain. +// +// If the fork is too long (build.ForkLengthThreshold), we add the entire subchain to the +// denylist. Else, we find the common ancestor, and add the missing chain +// fragment until the fork point to the returned []TipSet. func (syncer *Syncer) syncFork(ctx context.Context, from *types.TipSet, to *types.TipSet) ([]*types.TipSet, error) { tips, err := syncer.Bsync.GetBlocks(ctx, from.Parents(), int(build.ForkLengthThreshold)) if err != nil { @@ -1312,6 +1408,25 @@ func persistMessages(bs bstore.Blockstore, bst *blocksync.BSTipSet) error { return nil } +// collectChain tries to advance our view of the chain to the purported head. +// +// It goes through various stages: +// +// 1. StageHeaders: we proceed in the sync process by requesting block headers +// from our peers, moving back from their heads, until we reach a tipset +// that we have in common (such a common tipset must exist, thought it may +// simply be the genesis block). +// +// If the common tipset is our head, we treat the sync as a "fast-forward", +// else we must drop part of our chain to connect to the peer's head +// (referred to as "forking"). +// +// 2. StagePersistHeaders: now that we've collected the missing headers, +// augmented by those on the other side of a fork, we persist them to the +// BlockStore. +// +// 3. StageMessages: having acquired the headers and found a common tipset, +// we then move forward, requesting the full blocks, including the messages. func (syncer *Syncer) collectChain(ctx context.Context, ts *types.TipSet) error { ctx, span := trace.StartSpan(ctx, "collectChain") defer span.End() @@ -1361,9 +1476,8 @@ func (syncer *Syncer) collectChain(ctx context.Context, ts *types.TipSet) error func VerifyElectionPoStVRF(ctx context.Context, worker address.Address, rand []byte, evrf []byte) error { if build.InsecurePoStValidation { return nil - } else { - return gen.VerifyVRF(ctx, worker, rand, evrf) } + return gen.VerifyVRF(ctx, worker, rand, evrf) } func (syncer *Syncer) State() []SyncerState { @@ -1374,6 +1488,7 @@ func (syncer *Syncer) State() []SyncerState { return out } +// MarkBad manually adds a block to the "bad blocks" cache. func (syncer *Syncer) MarkBad(blk cid.Cid) { syncer.bad.Add(blk, "manually marked bad") } @@ -1381,7 +1496,7 @@ func (syncer *Syncer) MarkBad(blk cid.Cid) { func (syncer *Syncer) CheckBadBlockCache(blk cid.Cid) (string, bool) { return syncer.bad.Has(blk) } -func (syncer *Syncer) getLatestBeaconEntry(ctx context.Context, ts *types.TipSet) (*types.BeaconEntry, error) { +func (syncer *Syncer) getLatestBeaconEntry(_ context.Context, ts *types.TipSet) (*types.BeaconEntry, error) { cur := ts for i := 0; i < 20; i++ { cbe := cur.Blocks()[0].BeaconEntries diff --git a/miner/miner.go b/miner/miner.go index bdeed8ac5..fa97bd265 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -252,6 +252,12 @@ func (m *Miner) hasPower(ctx context.Context, addr address.Address, ts *types.Ti return mpower.MinerPower.QualityAdjPower.GreaterThanEqual(power.ConsensusMinerMinPower), nil } +// mineOne mines a single block, and does so synchronously, if and only if we +// have won the current round. +// +// {hint/landmark}: This method coordinates all the steps involved in mining a +// block, including the condition of whether mine or not at all depending on +// whether we win the round or not. func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (*types.BlockMsg, error) { log.Debugw("attempting to mine a block", "tipset", types.LogCids(base.TipSet.Cids())) start := time.Now() From a7d662a7d8876b11b7125279d248a0ef38b738c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 24 Jun 2020 00:23:39 +0200 Subject: [PATCH 80/96] Update to actually working sector-storage --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index e09898888..33e0e628b 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/filecoin-project/go-paramfetch v0.0.2-0.20200605171344-fcac609550ca github.com/filecoin-project/go-statestore v0.1.0 github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b - github.com/filecoin-project/sector-storage v0.0.0-20200623210524-47d93356586d + github.com/filecoin-project/sector-storage v0.0.0-20200623221933-1cb2b0ac1cd8 github.com/filecoin-project/specs-actors v0.6.2-0.20200617175406-de392ca14121 github.com/filecoin-project/specs-storage v0.1.1-0.20200622113353-88a9704877ea github.com/filecoin-project/storage-fsm v0.0.0-20200623213010-fe71d5b42de3 diff --git a/go.sum b/go.sum index ca866bba5..7b692a145 100644 --- a/go.sum +++ b/go.sum @@ -254,6 +254,8 @@ github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b/ github.com/filecoin-project/sector-storage v0.0.0-20200615154852-728a47ab99d6/go.mod h1:M59QnAeA/oV+Z8oHFLoNpGMv0LZ8Rll+vHVXX7GirPM= github.com/filecoin-project/sector-storage v0.0.0-20200623210524-47d93356586d h1:yJJqXCMEhvXJoOS6T1O46FXl+A3mlttXhgjcTCp+Tgo= github.com/filecoin-project/sector-storage v0.0.0-20200623210524-47d93356586d/go.mod h1:8f0hWDzzIi1hKs4IVKH9RnDsO4LEHVz8BNat0okDOuY= +github.com/filecoin-project/sector-storage v0.0.0-20200623221933-1cb2b0ac1cd8 h1:lqv3F5WgjRUH6qLbWQp3Of5IIQXLRT37R1AsFEFAJPc= +github.com/filecoin-project/sector-storage v0.0.0-20200623221933-1cb2b0ac1cd8/go.mod h1:8f0hWDzzIi1hKs4IVKH9RnDsO4LEHVz8BNat0okDOuY= github.com/filecoin-project/specs-actors v0.0.0-20200210130641-2d1fbd8672cf/go.mod h1:xtDZUB6pe4Pksa/bAJbJ693OilaC5Wbot9jMhLm3cZA= github.com/filecoin-project/specs-actors v0.3.0/go.mod h1:nQYnFbQ7Y0bHZyq6HDEuVlCPR+U3z5Q3wMOQ+2aiV+Y= github.com/filecoin-project/specs-actors v0.6.0/go.mod h1:dRdy3cURykh2R8O/DKqy8olScl70rmIS7GrB4hB1IDY= From 11c53c26ffa3e581d3dfc82b47b6b8a4a0d27f49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 24 Jun 2020 00:47:47 +0200 Subject: [PATCH 81/96] Update sector-storage to fix #2080 and #1917 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 33e0e628b..4c7c91d10 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/filecoin-project/go-paramfetch v0.0.2-0.20200605171344-fcac609550ca github.com/filecoin-project/go-statestore v0.1.0 github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b - github.com/filecoin-project/sector-storage v0.0.0-20200623221933-1cb2b0ac1cd8 + github.com/filecoin-project/sector-storage v0.0.0-20200623224636-de544b531601 github.com/filecoin-project/specs-actors v0.6.2-0.20200617175406-de392ca14121 github.com/filecoin-project/specs-storage v0.1.1-0.20200622113353-88a9704877ea github.com/filecoin-project/storage-fsm v0.0.0-20200623213010-fe71d5b42de3 diff --git a/go.sum b/go.sum index 7b692a145..76d99cc85 100644 --- a/go.sum +++ b/go.sum @@ -254,8 +254,8 @@ github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b/ github.com/filecoin-project/sector-storage v0.0.0-20200615154852-728a47ab99d6/go.mod h1:M59QnAeA/oV+Z8oHFLoNpGMv0LZ8Rll+vHVXX7GirPM= github.com/filecoin-project/sector-storage v0.0.0-20200623210524-47d93356586d h1:yJJqXCMEhvXJoOS6T1O46FXl+A3mlttXhgjcTCp+Tgo= github.com/filecoin-project/sector-storage v0.0.0-20200623210524-47d93356586d/go.mod h1:8f0hWDzzIi1hKs4IVKH9RnDsO4LEHVz8BNat0okDOuY= -github.com/filecoin-project/sector-storage v0.0.0-20200623221933-1cb2b0ac1cd8 h1:lqv3F5WgjRUH6qLbWQp3Of5IIQXLRT37R1AsFEFAJPc= -github.com/filecoin-project/sector-storage v0.0.0-20200623221933-1cb2b0ac1cd8/go.mod h1:8f0hWDzzIi1hKs4IVKH9RnDsO4LEHVz8BNat0okDOuY= +github.com/filecoin-project/sector-storage v0.0.0-20200623224636-de544b531601 h1:EgMmHLoJ4caLU8RzgKQux4TyX/ZploXGtIu5Q1SaxKw= +github.com/filecoin-project/sector-storage v0.0.0-20200623224636-de544b531601/go.mod h1:8f0hWDzzIi1hKs4IVKH9RnDsO4LEHVz8BNat0okDOuY= github.com/filecoin-project/specs-actors v0.0.0-20200210130641-2d1fbd8672cf/go.mod h1:xtDZUB6pe4Pksa/bAJbJ693OilaC5Wbot9jMhLm3cZA= github.com/filecoin-project/specs-actors v0.3.0/go.mod h1:nQYnFbQ7Y0bHZyq6HDEuVlCPR+U3z5Q3wMOQ+2aiV+Y= github.com/filecoin-project/specs-actors v0.6.0/go.mod h1:dRdy3cURykh2R8O/DKqy8olScl70rmIS7GrB4hB1IDY= From d7bb284e577971f7305a85f5e284cd94e4049c0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 24 Jun 2020 00:54:28 +0200 Subject: [PATCH 82/96] miner: Move set-addrs command to a new 'actor' namespace --- cmd/lotus-storage-miner/actor.go | 91 ++++++++++++++++++++++++++++++++ cmd/lotus-storage-miner/info.go | 74 -------------------------- cmd/lotus-storage-miner/main.go | 1 + 3 files changed, 92 insertions(+), 74 deletions(-) create mode 100644 cmd/lotus-storage-miner/actor.go diff --git a/cmd/lotus-storage-miner/actor.go b/cmd/lotus-storage-miner/actor.go new file mode 100644 index 000000000..87dfd7254 --- /dev/null +++ b/cmd/lotus-storage-miner/actor.go @@ -0,0 +1,91 @@ +package main + +import ( + "fmt" + + ma "github.com/multiformats/go-multiaddr" + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/specs-actors/actors/abi" + "github.com/filecoin-project/specs-actors/actors/builtin/miner" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/types" + lcli "github.com/filecoin-project/lotus/cli" +) + +var actorCmd = &cli.Command{ + Name: "actor", + Usage: "manipulate the miner actor", + Subcommands: []*cli.Command{ + actorSetAddrsCmd, + }, +} + +var actorSetAddrsCmd = &cli.Command{ + Name: "set-addrs", + Usage: "set addresses that your miner can be publically dialed on", + Flags: []cli.Flag{ + &cli.Int64Flag{ + Name: "gas-limit", + Usage: "set gas limit", + Value: 100000, + }, + }, + Action: func(cctx *cli.Context) error { + nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + if err != nil { + return err + } + defer closer() + + api, acloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer acloser() + + ctx := lcli.ReqContext(cctx) + + var addrs []abi.Multiaddrs + for _, a := range cctx.Args().Slice() { + maddr, err := ma.NewMultiaddr(a) + if err != nil { + return fmt.Errorf("failed to parse %q as a multiaddr: %w", a, err) + } + + addrs = append(addrs, maddr.Bytes()) + } + + maddr, err := nodeApi.ActorAddress(ctx) + if err != nil { + return err + } + + minfo, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + params, err := actors.SerializeParams(&miner.ChangeMultiaddrsParams{addrs}) + if err != nil { + return err + } + + gasLimit := cctx.Int64("gas-limit") + + smsg, err := api.MpoolPushMessage(ctx, &types.Message{ + To: maddr, + From: minfo.Worker, + Value: types.NewInt(0), + GasPrice: types.NewInt(1), + GasLimit: gasLimit, + Method: 18, + Params: params, + }) + + fmt.Printf("Requested multiaddrs change in message %s\n", smsg.Cid()) + return nil + + }, +} diff --git a/cmd/lotus-storage-miner/info.go b/cmd/lotus-storage-miner/info.go index 667df5342..17e06e214 100644 --- a/cmd/lotus-storage-miner/info.go +++ b/cmd/lotus-storage-miner/info.go @@ -8,18 +8,15 @@ import ( "time" "github.com/fatih/color" - ma "github.com/multiformats/go-multiaddr" "github.com/urfave/cli/v2" "golang.org/x/xerrors" - "github.com/filecoin-project/specs-actors/actors/abi" "github.com/filecoin-project/specs-actors/actors/builtin/miner" "github.com/filecoin-project/specs-actors/actors/builtin/power" sealing "github.com/filecoin-project/storage-fsm" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" ) @@ -27,9 +24,6 @@ import ( var infoCmd = &cli.Command{ Name: "info", Usage: "Print storage miner info", - Subcommands: []*cli.Command{ - infoSetAddrsCmd, - }, Flags: []cli.Flag{ &cli.BoolFlag{Name: "color"}, }, @@ -254,71 +248,3 @@ func sectorsInfo(ctx context.Context, napi api.StorageMiner) error { return nil } - -var infoSetAddrsCmd = &cli.Command{ - Name: "set-addrs", - Usage: "set addresses that your miner can be publically dialed on", - Flags: []cli.Flag{ - &cli.Int64Flag{ - Name: "gas-limit", - Usage: "set gas limit", - Value: 100000, - }, - }, - Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) - if err != nil { - return err - } - defer closer() - - api, acloser, err := lcli.GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer acloser() - - ctx := lcli.ReqContext(cctx) - - var addrs []abi.Multiaddrs - for _, a := range cctx.Args().Slice() { - maddr, err := ma.NewMultiaddr(a) - if err != nil { - return fmt.Errorf("failed to parse %q as a multiaddr: %w", a, err) - } - - addrs = append(addrs, maddr.Bytes()) - } - - maddr, err := nodeApi.ActorAddress(ctx) - if err != nil { - return err - } - - minfo, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } - - params, err := actors.SerializeParams(&miner.ChangeMultiaddrsParams{addrs}) - if err != nil { - return err - } - - gasLimit := cctx.Int64("gas-limit") - - smsg, err := api.MpoolPushMessage(ctx, &types.Message{ - To: maddr, - From: minfo.Worker, - Value: types.NewInt(0), - GasPrice: types.NewInt(1), - GasLimit: gasLimit, - Method: 18, - Params: params, - }) - - fmt.Printf("Requested multiaddrs change in message %s\n", smsg.Cid()) - return nil - - }, -} diff --git a/cmd/lotus-storage-miner/main.go b/cmd/lotus-storage-miner/main.go index dc6de7029..bda94f54a 100644 --- a/cmd/lotus-storage-miner/main.go +++ b/cmd/lotus-storage-miner/main.go @@ -22,6 +22,7 @@ func main() { lotuslog.SetupLogLevels() local := []*cli.Command{ + actorCmd, dealsCmd, infoCmd, initCmd, From 001d4ab0029c0a71ae0ab8fa1ee10a3fbdae43d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 24 Jun 2020 01:00:32 +0200 Subject: [PATCH 83/96] Appease the linter --- cmd/lotus-storage-miner/actor.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/cmd/lotus-storage-miner/actor.go b/cmd/lotus-storage-miner/actor.go index 87dfd7254..44eb7a0e4 100644 --- a/cmd/lotus-storage-miner/actor.go +++ b/cmd/lotus-storage-miner/actor.go @@ -15,7 +15,7 @@ import ( ) var actorCmd = &cli.Command{ - Name: "actor", + Name: "actor", Usage: "manipulate the miner actor", Subcommands: []*cli.Command{ actorSetAddrsCmd, @@ -33,7 +33,7 @@ var actorSetAddrsCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + nodeAPI, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -57,7 +57,7 @@ var actorSetAddrsCmd = &cli.Command{ addrs = append(addrs, maddr.Bytes()) } - maddr, err := nodeApi.ActorAddress(ctx) + maddr, err := nodeAPI.ActorAddress(ctx) if err != nil { return err } @@ -67,7 +67,7 @@ var actorSetAddrsCmd = &cli.Command{ return err } - params, err := actors.SerializeParams(&miner.ChangeMultiaddrsParams{addrs}) + params, err := actors.SerializeParams(&miner.ChangeMultiaddrsParams{NewMultiaddrs: addrs}) if err != nil { return err } @@ -83,6 +83,9 @@ var actorSetAddrsCmd = &cli.Command{ Method: 18, Params: params, }) + if err != nil { + return err + } fmt.Printf("Requested multiaddrs change in message %s\n", smsg.Cid()) return nil From 24c7f47566c8e0e85e4611b0d50d620131767a74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A5=87?= Date: Wed, 24 Jun 2020 10:14:20 +0800 Subject: [PATCH 84/96] Modify print faulty sectors in provingFaultsCmd --- cmd/lotus-storage-miner/proving.go | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/cmd/lotus-storage-miner/proving.go b/cmd/lotus-storage-miner/proving.go index d9d7da8e3..60cf2ab99 100644 --- a/cmd/lotus-storage-miner/proving.go +++ b/cmd/lotus-storage-miner/proving.go @@ -3,7 +3,6 @@ package main import ( "bytes" "fmt" - "math" "os" "text/tabwriter" "time" @@ -73,14 +72,29 @@ var provingFaultsCmd = &cli.Command{ return err } if len(faults) == 0 { - fmt.Println("no sector fault") + fmt.Println("no faulty sectors") } - for _, num := range faults { - num2 := num % (mas.Info.WindowPoStPartitionSectors * (miner.WPoStPeriodDeadlines - 1)) - deadline := uint64(math.Floor(float64(num2)/float64(mas.Info.WindowPoStPartitionSectors))) + 1 - fmt.Printf("sector number = %d,deadline = %d\n", num, deadline) + head, err := api.ChainHead(ctx) + if err != nil { + return xerrors.Errorf("getting chain head: %w", err) } - return nil + deadlines, err := api.StateMinerDeadlines(ctx, maddr, head.Key()) + if err != nil { + return xerrors.Errorf("getting miner deadlines: %w", err) + } + tw := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0) + _, _ = fmt.Fprintln(tw, "deadline\tsectors") + for deadline, sectors := range deadlines.Due { + intersectSectors, _ := bitfield.IntersectBitField(sectors, mas.Faults) + if intersectSectors != nil { + allSectors, _ := intersectSectors.All(100000000000) + for _, num := range allSectors { + _, _ = fmt.Fprintf(tw, "%d\t%d\n", deadline, num) + } + } + + } + return tw.Flush() }, } From 0b1fa54a4dd23b395ece8125591c6a87f3d132e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 24 Jun 2020 12:38:46 +0200 Subject: [PATCH 85/96] wdpost: Remove commented code --- storage/wdpost_run.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/storage/wdpost_run.go b/storage/wdpost_run.go index fe42d2940..fcace70dd 100644 --- a/storage/wdpost_run.go +++ b/storage/wdpost_run.go @@ -383,8 +383,6 @@ func (s *WindowPoStScheduler) runPost(ctx context.Context, di miner.DeadlineInfo return nil, xerrors.Errorf("get need prove sectors: %w", err) } - // var skipped *abi.BitField - // { good, err := s.checkSectors(ctx, nps) if err != nil { return nil, xerrors.Errorf("checking sectors to skip: %w", err) @@ -394,7 +392,6 @@ func (s *WindowPoStScheduler) runPost(ctx context.Context, di miner.DeadlineInfo if err != nil { return nil, xerrors.Errorf("nps - good: %w", err) } - // } skipCount, err := skipped.Count() if err != nil { From 358b36bc66b3923f6b4d82accac9acd22d23a6a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 24 Jun 2020 14:34:53 +0200 Subject: [PATCH 86/96] chainwatch: state_heights_uindex is not unique --- cmd/lotus-chainwatch/storage.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/lotus-chainwatch/storage.go b/cmd/lotus-chainwatch/storage.go index 020ca1d7f..f7f80a9c6 100644 --- a/cmd/lotus-chainwatch/storage.go +++ b/cmd/lotus-chainwatch/storage.go @@ -118,7 +118,7 @@ create unique index if not exists block_cid_uindex create materialized view if not exists state_heights as select distinct height, parentstateroot from blocks; -create unique index if not exists state_heights_uindex +create index if not exists state_heights_index on state_heights (height); create index if not exists state_heights_height_index From 008a2969b2706988036d79f2753d8fa6318329b0 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Wed, 24 Jun 2020 17:05:24 +0200 Subject: [PATCH 87/96] Fix two races in events Also race fix: depends on https://github.com/ipfs/go-blockservice/pull/65 Resolves #2092, #2099, #2108, #1930, #2110 Signed-off-by: Jakub Sztandera --- api/test/deals.go | 13 +++++++------ api/test/mining.go | 5 +++-- chain/events/events_called.go | 4 +++- chain/events/events_height.go | 14 +++++++++++--- go.mod | 2 +- go.sum | 2 ++ miner/miner.go | 3 +++ 7 files changed, 30 insertions(+), 13 deletions(-) diff --git a/api/test/deals.go b/api/test/deals.go index 0150a1315..22152d7ab 100644 --- a/api/test/deals.go +++ b/api/test/deals.go @@ -8,6 +8,7 @@ import ( "math/rand" "os" "path/filepath" + "sync/atomic" "testing" "time" @@ -52,11 +53,11 @@ func TestDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration, carExport } time.Sleep(time.Second) - mine := true + mine := int64(1) done := make(chan struct{}) go func() { defer close(done) - for mine { + for atomic.LoadInt64(&mine) == 1 { time.Sleep(blocktime) if err := sn[0].MineOne(ctx, func(bool) {}); err != nil { t.Error(err) @@ -66,7 +67,7 @@ func TestDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration, carExport makeDeal(t, ctx, 6, client, miner, carExport) - mine = false + atomic.AddInt64(&mine, -1) fmt.Println("shutting down mining") <-done } @@ -89,12 +90,12 @@ func TestDoubleDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration) { } time.Sleep(time.Second) - mine := true + mine := int64(1) done := make(chan struct{}) go func() { defer close(done) - for mine { + for atomic.LoadInt64(&mine) == 1 { time.Sleep(blocktime) if err := sn[0].MineOne(ctx, func(bool) {}); err != nil { t.Error(err) @@ -105,7 +106,7 @@ func TestDoubleDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration) { makeDeal(t, ctx, 6, client, miner, false) makeDeal(t, ctx, 7, client, miner, false) - mine = false + atomic.AddInt64(&mine, -1) fmt.Println("shutting down mining") <-done } diff --git a/api/test/mining.go b/api/test/mining.go index b19095450..f43af2cd5 100644 --- a/api/test/mining.go +++ b/api/test/mining.go @@ -126,6 +126,7 @@ func TestDealMining(t *testing.T, b APIBuilder, blocktime time.Duration, carExpo minedTwo := make(chan struct{}) go func() { + doneMinedTwo := false defer close(done) prevExpect := 0 @@ -175,9 +176,9 @@ func TestDealMining(t *testing.T, b APIBuilder, blocktime time.Duration, carExpo time.Sleep(blocktime) } - if prevExpect == 2 && expect == 2 && minedTwo != nil { + if prevExpect == 2 && expect == 2 && !doneMinedTwo { close(minedTwo) - minedTwo = nil + doneMinedTwo = true } prevExpect = expect diff --git a/chain/events/events_called.go b/chain/events/events_called.go index 04e7be715..0bae99404 100644 --- a/chain/events/events_called.go +++ b/chain/events/events_called.go @@ -84,6 +84,9 @@ type calledEvents struct { } func (e *calledEvents) headChangeCalled(rev, app []*types.TipSet) error { + e.lk.Lock() + defer e.lk.Unlock() + for _, ts := range rev { e.handleReverts(ts) e.at = ts.Height() @@ -134,7 +137,6 @@ func (e *calledEvents) checkNewCalls(ts *types.TipSet) { e.messagesForTs(pts, func(msg *types.Message) { // TODO: provide receipts - for tid, matchFns := range e.matchers { var matched bool for _, matchFn := range matchFns { diff --git a/chain/events/events_height.go b/chain/events/events_height.go index cbf756c20..fc94d6262 100644 --- a/chain/events/events_height.go +++ b/chain/events/events_height.go @@ -26,12 +26,15 @@ type heightEvents struct { } func (e *heightEvents) headChangeAt(rev, app []*types.TipSet) error { + ctx, span := trace.StartSpan(e.ctx, "events.HeightHeadChange") defer span.End() span.AddAttributes(trace.Int64Attribute("endHeight", int64(app[0].Height()))) span.AddAttributes(trace.Int64Attribute("reverts", int64(len(rev)))) span.AddAttributes(trace.Int64Attribute("applies", int64(len(app)))) + e.lk.Lock() + defer e.lk.Unlock() for _, ts := range rev { // TODO: log error if h below gcconfidence // revert height-based triggers @@ -40,7 +43,10 @@ func (e *heightEvents) headChangeAt(rev, app []*types.TipSet) error { for _, tid := range e.htHeights[h] { ctx, span := trace.StartSpan(ctx, "events.HeightRevert") - err := e.heightTriggers[tid].revert(ctx, ts) + rev := e.heightTriggers[tid].revert + e.lk.Unlock() + err := rev(ctx, ts) + e.lk.Lock() e.heightTriggers[tid].called = false span.End() @@ -98,8 +104,10 @@ func (e *heightEvents) headChangeAt(rev, app []*types.TipSet) error { ctx, span := trace.StartSpan(ctx, "events.HeightApply") span.AddAttributes(trace.BoolAttribute("immediate", false)) - - err = hnd.handle(ctx, incTs, h) + handle := hnd.handle + e.lk.Unlock() + err = handle(ctx, incTs, h) + e.lk.Lock() span.End() if err != nil { diff --git a/go.mod b/go.mod index 4c7c91d10..3b8ecf76f 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,7 @@ require ( github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d github.com/ipfs/go-bitswap v0.2.8 github.com/ipfs/go-block-format v0.0.2 - github.com/ipfs/go-blockservice v0.1.3 + github.com/ipfs/go-blockservice v0.1.4-0.20200624145336-a978cec6e834 github.com/ipfs/go-cid v0.0.6 github.com/ipfs/go-cidutil v0.0.2 github.com/ipfs/go-datastore v0.4.4 diff --git a/go.sum b/go.sum index 76d99cc85..7fb39f472 100644 --- a/go.sum +++ b/go.sum @@ -467,6 +467,8 @@ github.com/ipfs/go-blockservice v0.0.7/go.mod h1:EOfb9k/Y878ZTRY/CH0x5+ATtaipfbR github.com/ipfs/go-blockservice v0.1.0/go.mod h1:hzmMScl1kXHg3M2BjTymbVPjv627N7sYcvYaKbop39M= github.com/ipfs/go-blockservice v0.1.3 h1:9XgsPMwwWJSC9uVr2pMDsW2qFTBSkxpGMhmna8mIjPM= github.com/ipfs/go-blockservice v0.1.3/go.mod h1:OTZhFpkgY48kNzbgyvcexW9cHrpjBYIjSR0KoDOFOLU= +github.com/ipfs/go-blockservice v0.1.4-0.20200624145336-a978cec6e834 h1:hFJoI1D2a3MqiNkSb4nKwrdkhCngUxUTFNwVwovZX2s= +github.com/ipfs/go-blockservice v0.1.4-0.20200624145336-a978cec6e834/go.mod h1:OTZhFpkgY48kNzbgyvcexW9cHrpjBYIjSR0KoDOFOLU= github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= diff --git a/miner/miner.go b/miner/miner.go index fa97bd265..1f5f8dad5 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -215,6 +215,9 @@ type MiningBase struct { } func (m *Miner) GetBestMiningCandidate(ctx context.Context) (*MiningBase, error) { + m.lk.Lock() + defer m.lk.Unlock() + bts, err := m.api.ChainHead(ctx) if err != nil { return nil, err From ef0abf2b98ada6a96fcebc4c9c56bccb4fa40681 Mon Sep 17 00:00:00 2001 From: laser Date: Tue, 23 Jun 2020 09:04:46 -0700 Subject: [PATCH 88/96] disable/enable retrieval deal consideration via config --- node/builder.go | 2 ++ node/config/def.go | 10 +++++---- node/modules/dtypes/miner.go | 8 +++++++ node/modules/storageminer.go | 43 ++++++++++++++++++++++++++++++++---- 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/node/builder.go b/node/builder.go index 3b6c6fbda..6b987dc28 100644 --- a/node/builder.go +++ b/node/builder.go @@ -313,6 +313,8 @@ func Online() Option { Override(new(gen.WinningPoStProver), storage.NewWinningPoStProver), Override(new(*miner.Miner), modules.SetupBlockProducer), + Override(new(dtypes.AcceptingRetrievalDealsConfigFunc), modules.NewAcceptingRetrievalDealsConfigFunc), + Override(new(dtypes.SetAcceptingRetrievalDealsConfigFunc), modules.NewSetAcceptingRetrievalDealsConfigFunc), Override(new(dtypes.AcceptingStorageDealsConfigFunc), modules.NewAcceptingStorageDealsConfigFunc), Override(new(dtypes.SetAcceptingStorageDealsConfigFunc), modules.NewSetAcceptingStorageDealsConfigFunc), Override(new(dtypes.StorageDealPieceCidBlocklistConfigFunc), modules.NewStorageDealPieceCidBlocklistConfigFunc), diff --git a/node/config/def.go b/node/config/def.go index 76a6a89ea..a86f87d24 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -34,8 +34,9 @@ type StorageMiner struct { } type DealmakingConfig struct { - AcceptingStorageDeals bool - PieceCidBlocklist []cid.Cid + AcceptingStorageDeals bool + AcceptingRetrievalDeals bool + PieceCidBlocklist []cid.Cid } // API contains configs for API endpoint @@ -123,8 +124,9 @@ func DefaultStorageMiner() *StorageMiner { }, Dealmaking: DealmakingConfig{ - AcceptingStorageDeals: true, - PieceCidBlocklist: []cid.Cid{}, + AcceptingStorageDeals: true, + AcceptingRetrievalDeals: true, + PieceCidBlocklist: []cid.Cid{}, }, } cfg.Common.API.ListenAddress = "/ip4/127.0.0.1/tcp/2345/http" diff --git a/node/modules/dtypes/miner.go b/node/modules/dtypes/miner.go index 584642a3b..a0acb8f18 100644 --- a/node/modules/dtypes/miner.go +++ b/node/modules/dtypes/miner.go @@ -18,6 +18,14 @@ type AcceptingStorageDealsConfigFunc func() (bool, error) // storage deal acceptance. type SetAcceptingStorageDealsConfigFunc func(bool) error +// AcceptingRetrievalDealsFunc is a function which reads from miner config to +// determine if the user has disabled retrieval acceptance (or not). +type AcceptingRetrievalDealsConfigFunc func() (bool, error) + +// SetAcceptingRetrievalDealsFunc is a function which is used to disable or enable +// retrieval deal acceptance. +type SetAcceptingRetrievalDealsConfigFunc func(bool) error + // StorageDealPieceCidBlocklistConfigFunc is a function which reads from miner config // to obtain a list of CIDs for which the storage miner will not accept storage // proposals. diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index c6cadec34..3ccc5daa7 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -357,14 +357,31 @@ func StorageProvider(minerAddress dtypes.MinerAddress, ffiConfig *ffiwrapper.Con } // RetrievalProvider creates a new retrieval provider attached to the provider blockstore -func RetrievalProvider(h host.Host, miner *storage.Miner, sealer sectorstorage.SectorManager, full lapi.FullNode, ds dtypes.MetadataDS, pieceStore dtypes.ProviderPieceStore, ibs dtypes.StagingBlockstore) (retrievalmarket.RetrievalProvider, error) { +func RetrievalProvider(h host.Host, miner *storage.Miner, sealer sectorstorage.SectorManager, full lapi.FullNode, ds dtypes.MetadataDS, pieceStore dtypes.ProviderPieceStore, ibs dtypes.StagingBlockstore, isAcceptingFunc dtypes.AcceptingRetrievalDealsConfigFunc) (retrievalmarket.RetrievalProvider, error) { adapter := retrievaladapter.NewRetrievalProviderNode(miner, sealer, full) - address, err := minerAddrFromDS(ds) + + maddr, err := minerAddrFromDS(ds) if err != nil { return nil, err } - network := rmnet.NewFromLibp2pHost(h) - return retrievalimpl.NewProvider(address, adapter, network, pieceStore, ibs, namespace.Wrap(ds, datastore.NewKey("/retrievals/provider"))) + + netwk := rmnet.NewFromLibp2pHost(h) + + opt := retrievalimpl.DealDeciderOpt(func(ctx context.Context, state retrievalmarket.ProviderDealState) (bool, string, error) { + b, err := isAcceptingFunc() + if err != nil { + return false, "miner error", err + } + + if !b { + log.Warn("retrieval deal acceptance disabled; rejecting retrieval deal proposal from client") + return false, "miner is not accepting retrieval deals", nil + } + + return true, "", nil + }) + + return retrievalimpl.NewProvider(maddr, adapter, netwk, pieceStore, ibs, namespace.Wrap(ds, datastore.NewKey("/retrievals/provider")), opt) } func SectorStorage(mctx helpers.MetricsCtx, lc fx.Lifecycle, ls stores.LocalStorage, si stores.SectorIndex, cfg *ffiwrapper.Config, sc sectorstorage.SealerConfig, urls sectorstorage.URLs, sa sectorstorage.StorageAuth) (*sectorstorage.Manager, error) { @@ -399,6 +416,24 @@ func StorageAuth(ctx helpers.MetricsCtx, ca lapi.Common) (sectorstorage.StorageA return sectorstorage.StorageAuth(headers), nil } +func NewAcceptingRetrievalDealsConfigFunc(r repo.LockedRepo) (dtypes.AcceptingRetrievalDealsConfigFunc, error) { + return func() (out bool, err error) { + err = readCfg(r, func(cfg *config.StorageMiner) { + out = cfg.Dealmaking.AcceptingRetrievalDeals + }) + return + }, nil +} + +func NewSetAcceptingRetrievalDealsConfigFunc(r repo.LockedRepo) (dtypes.SetAcceptingRetrievalDealsConfigFunc, error) { + return func(b bool) (err error) { + err = mutateCfg(r, func(cfg *config.StorageMiner) { + cfg.Dealmaking.AcceptingRetrievalDeals = b + }) + return + }, nil +} + func NewAcceptingStorageDealsConfigFunc(r repo.LockedRepo) (dtypes.AcceptingStorageDealsConfigFunc, error) { return func() (out bool, err error) { err = readCfg(r, func(cfg *config.StorageMiner) { From bc90b857c9fe89d8206f563232b16e6347c6dd23 Mon Sep 17 00:00:00 2001 From: laser Date: Tue, 23 Jun 2020 09:30:34 -0700 Subject: [PATCH 89/96] change "lotus-storage-miner deals" to "lotus-storage-miner storage-deals" --- cmd/lotus-storage-miner/main.go | 2 +- cmd/lotus-storage-miner/market.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/lotus-storage-miner/main.go b/cmd/lotus-storage-miner/main.go index bda94f54a..6d99645fa 100644 --- a/cmd/lotus-storage-miner/main.go +++ b/cmd/lotus-storage-miner/main.go @@ -23,7 +23,7 @@ func main() { local := []*cli.Command{ actorCmd, - dealsCmd, + storageDealsCmd, infoCmd, initCmd, rewardsCmd, diff --git a/cmd/lotus-storage-miner/market.go b/cmd/lotus-storage-miner/market.go index c668456f0..1985d0004 100644 --- a/cmd/lotus-storage-miner/market.go +++ b/cmd/lotus-storage-miner/market.go @@ -217,9 +217,9 @@ var getAskCmd = &cli.Command{ }, } -var dealsCmd = &cli.Command{ - Name: "deals", - Usage: "interact with your deals", +var storageDealsCmd = &cli.Command{ + Name: "storage-deals", + Usage: "interact with your storage deals", Subcommands: []*cli.Command{ dealsImportDataCmd, dealsListCmd, From 176d0bb7c0f94e4c64267ceed02b84bb2d825987 Mon Sep 17 00:00:00 2001 From: laser Date: Tue, 23 Jun 2020 10:17:31 -0700 Subject: [PATCH 90/96] add "lotus-storage-miner retrieval-deals" subcommands - enable retrieval - disable retrieval --- api/api_storage.go | 1 + api/apistruct/struct.go | 15 +++++--- cmd/lotus-storage-miner/main.go | 1 + cmd/lotus-storage-miner/market.go | 2 +- cmd/lotus-storage-miner/retrieval-deals.go | 45 ++++++++++++++++++++++ node/impl/storminer.go | 5 +++ 6 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 cmd/lotus-storage-miner/retrieval-deals.go diff --git a/api/api_storage.go b/api/api_storage.go index 6ce8e7f2e..6d8fe384e 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -57,6 +57,7 @@ type StorageMiner interface { DealsImportData(ctx context.Context, dealPropCid cid.Cid, file string) error DealsList(ctx context.Context) ([]storagemarket.StorageDeal, error) DealsSetAcceptingStorageDeals(context.Context, bool) error + DealsSetAcceptingRetrievalDeals(context.Context, bool) error DealsPieceCidBlocklist(context.Context) ([]cid.Cid, error) DealsSetPieceCidBlocklist(context.Context, []cid.Cid) error diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index e09b57df6..0c0acd05e 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -224,11 +224,12 @@ type StorageMinerStruct struct { StorageLock func(ctx context.Context, sector abi.SectorID, read stores.SectorFileType, write stores.SectorFileType) error `perm:"admin"` StorageTryLock func(ctx context.Context, sector abi.SectorID, read stores.SectorFileType, write stores.SectorFileType) (bool, error) `perm:"admin"` - DealsImportData func(ctx context.Context, dealPropCid cid.Cid, file string) error `perm:"write"` - DealsList func(ctx context.Context) ([]storagemarket.StorageDeal, error) `perm:"read"` - DealsSetAcceptingStorageDeals func(context.Context, bool) error `perm:"admin"` - DealsPieceCidBlocklist func(context.Context) ([]cid.Cid, error) `perm:"admin"` - DealsSetPieceCidBlocklist func(context.Context, []cid.Cid) error `perm:"read"` + DealsImportData func(ctx context.Context, dealPropCid cid.Cid, file string) error `perm:"write"` + DealsList func(ctx context.Context) ([]storagemarket.StorageDeal, error) `perm:"read"` + DealsSetAcceptingStorageDeals func(context.Context, bool) error `perm:"admin"` + DealsSetAcceptingRetrievalDeals func(context.Context, bool) error `perm:"admin"` + DealsPieceCidBlocklist func(context.Context) ([]cid.Cid, error) `perm:"admin"` + DealsSetPieceCidBlocklist func(context.Context, []cid.Cid) error `perm:"read"` StorageAddLocal func(ctx context.Context, path string) error `perm:"admin"` } @@ -881,6 +882,10 @@ func (c *StorageMinerStruct) DealsSetAcceptingStorageDeals(ctx context.Context, return c.Internal.DealsSetAcceptingStorageDeals(ctx, b) } +func (c *StorageMinerStruct) DealsSetAcceptingRetrievalDeals(ctx context.Context, b bool) error { + return c.Internal.DealsSetAcceptingRetrievalDeals(ctx, b) +} + func (c *StorageMinerStruct) DealsPieceCidBlocklist(ctx context.Context) ([]cid.Cid, error) { return c.Internal.DealsPieceCidBlocklist(ctx) } diff --git a/cmd/lotus-storage-miner/main.go b/cmd/lotus-storage-miner/main.go index 6d99645fa..62efe9370 100644 --- a/cmd/lotus-storage-miner/main.go +++ b/cmd/lotus-storage-miner/main.go @@ -24,6 +24,7 @@ func main() { local := []*cli.Command{ actorCmd, storageDealsCmd, + retrievalDealsCmd, infoCmd, initCmd, rewardsCmd, diff --git a/cmd/lotus-storage-miner/market.go b/cmd/lotus-storage-miner/market.go index 1985d0004..e658be1cf 100644 --- a/cmd/lotus-storage-miner/market.go +++ b/cmd/lotus-storage-miner/market.go @@ -219,7 +219,7 @@ var getAskCmd = &cli.Command{ var storageDealsCmd = &cli.Command{ Name: "storage-deals", - Usage: "interact with your storage deals", + Usage: "Manage storage deals and related configuration", Subcommands: []*cli.Command{ dealsImportDataCmd, dealsListCmd, diff --git a/cmd/lotus-storage-miner/retrieval-deals.go b/cmd/lotus-storage-miner/retrieval-deals.go new file mode 100644 index 000000000..ee503fb2b --- /dev/null +++ b/cmd/lotus-storage-miner/retrieval-deals.go @@ -0,0 +1,45 @@ +package main + +import ( + lcli "github.com/filecoin-project/lotus/cli" + "github.com/urfave/cli/v2" +) + +var retrievalDealsCmd = &cli.Command{ + Name: "retrieval-deals", + Usage: "Manage retrieval deals and related configuration", + Subcommands: []*cli.Command{ + enableRetrievalCmd, + disableRetrievalCmd, + }, +} + +var enableRetrievalCmd = &cli.Command{ + Name: "enable", + Usage: "Configure the miner to consider retrieval deal proposals", + Flags: []cli.Flag{}, + Action: func(cctx *cli.Context) error { + api, closer, err := lcli.GetStorageMinerAPI(cctx) + if err != nil { + return err + } + defer closer() + + return api.DealsSetAcceptingRetrievalDeals(lcli.DaemonContext(cctx), true) + }, +} + +var disableRetrievalCmd = &cli.Command{ + Name: "disable", + Usage: "Configure the miner to reject all retrieval deal proposals", + Flags: []cli.Flag{}, + Action: func(cctx *cli.Context) error { + api, closer, err := lcli.GetStorageMinerAPI(cctx) + if err != nil { + return err + } + defer closer() + + return api.DealsSetAcceptingRetrievalDeals(lcli.DaemonContext(cctx), false) + }, +} diff --git a/node/impl/storminer.go b/node/impl/storminer.go index d7a8d4d33..b993d1b46 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -44,6 +44,7 @@ type StorageMinerAPI struct { *stores.Index SetAcceptingStorageDealsConfigFunc dtypes.SetAcceptingStorageDealsConfigFunc + SetAcceptingRetrievalDealsConfigFunc dtypes.SetAcceptingRetrievalDealsConfigFunc StorageDealPieceCidBlocklistConfigFunc dtypes.StorageDealPieceCidBlocklistConfigFunc SetStorageDealPieceCidBlocklistConfigFunc dtypes.SetStorageDealPieceCidBlocklistConfigFunc } @@ -228,6 +229,10 @@ func (sm *StorageMinerAPI) DealsSetAcceptingStorageDeals(ctx context.Context, b return sm.SetAcceptingStorageDealsConfigFunc(b) } +func (sm *StorageMinerAPI) DealsSetAcceptingRetrievalDeals(ctx context.Context, b bool) error { + return sm.SetAcceptingRetrievalDealsConfigFunc(b) +} + func (sm *StorageMinerAPI) DealsImportData(ctx context.Context, deal cid.Cid, fname string) error { fi, err := os.Open(fname) if err != nil { From 4c760ec1b108d9d279cad1ea3501cfeb1cddbd94 Mon Sep 17 00:00:00 2001 From: laser Date: Tue, 23 Jun 2020 13:48:07 -0700 Subject: [PATCH 91/96] lint: update comments to match type declarations --- node/modules/dtypes/miner.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/node/modules/dtypes/miner.go b/node/modules/dtypes/miner.go index a0acb8f18..9ea8c3440 100644 --- a/node/modules/dtypes/miner.go +++ b/node/modules/dtypes/miner.go @@ -10,20 +10,20 @@ import ( type MinerAddress address.Address type MinerID abi.ActorID -// AcceptingStorageDealsFunc is a function which reads from miner config to -// determine if the user has disabled storage deals (or not). +// AcceptingStorageDealsConfigFunc is a function which reads from miner config +// to determine if the user has disabled storage deals (or not). type AcceptingStorageDealsConfigFunc func() (bool, error) -// SetAcceptingStorageDealsFunc is a function which is used to disable or enable -// storage deal acceptance. +// SetAcceptingStorageDealsConfigFunc is a function which is used to disable or +// enable storage deal acceptance. type SetAcceptingStorageDealsConfigFunc func(bool) error -// AcceptingRetrievalDealsFunc is a function which reads from miner config to -// determine if the user has disabled retrieval acceptance (or not). +// AcceptingRetrievalDealsConfigFunc is a function which reads from miner config +// to determine if the user has disabled retrieval acceptance (or not). type AcceptingRetrievalDealsConfigFunc func() (bool, error) -// SetAcceptingRetrievalDealsFunc is a function which is used to disable or enable -// retrieval deal acceptance. +// SetAcceptingRetrievalDealsConfigFunc is a function which is used to disable +// or enable retrieval deal acceptance. type SetAcceptingRetrievalDealsConfigFunc func(bool) error // StorageDealPieceCidBlocklistConfigFunc is a function which reads from miner config From a98c4038f40256074d5a4a44ce3e206306fd65b3 Mon Sep 17 00:00:00 2001 From: frrist Date: Wed, 24 Jun 2020 10:39:45 -0700 Subject: [PATCH 92/96] fix: use tipset corresponding to stateroot - Use te tipsetkey corresponding to the stateroot when fetching actor data from the lotus api. --- cmd/lotus-chainwatch/sync.go | 48 ++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/cmd/lotus-chainwatch/sync.go b/cmd/lotus-chainwatch/sync.go index d42a72b9b..88afb647e 100644 --- a/cmd/lotus-chainwatch/sync.go +++ b/cmd/lotus-chainwatch/sync.go @@ -53,6 +53,7 @@ type minerKey struct { addr address.Address act types.Actor stateroot cid.Cid + tsKey types.TipSetKey } type minerInfo struct { @@ -66,10 +67,11 @@ type minerInfo struct { type actorInfo struct { stateroot cid.Cid + tsKey types.TipSetKey state string } -func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipSet, maxBatch int) { +func syncHead(ctx context.Context, api api.FullNode, st *storage, headTs *types.TipSet, maxBatch int) { var alk sync.Mutex log.Infof("Getting synced block list") @@ -81,7 +83,7 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS allToSync := map[cid.Cid]*types.BlockHeader{} toVisit := list.New() - for _, header := range ts.Blocks() { + for _, header := range headTs.Blocks() { toVisit.PushBack(header) } @@ -116,7 +118,7 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS for len(allToSync) > 0 { actors := map[address.Address]map[types.Actor]actorInfo{} - addresses := map[address.Address]address.Address{} + addressToID := map[address.Address]address.Address{} minH := abi.ChainEpoch(math.MaxInt64) for _, header := range allToSync { @@ -129,7 +131,7 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS for c, header := range allToSync { if header.Height < minH+abi.ChainEpoch(maxBatch) { toSync[c] = header - addresses[header.Miner] = address.Undef + addressToID[header.Miner] = address.Undef } } for c := range toSync { @@ -146,20 +148,20 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS } if len(bh.Parents) == 0 { // genesis case - ts, _ := types.NewTipSet([]*types.BlockHeader{bh}) - aadrs, err := api.StateListActors(ctx, ts.Key()) + genesisTs, _ := types.NewTipSet([]*types.BlockHeader{bh}) + aadrs, err := api.StateListActors(ctx, genesisTs.Key()) if err != nil { log.Error(err) return } parmap.Par(50, aadrs, func(addr address.Address) { - act, err := api.StateGetActor(ctx, addr, ts.Key()) + act, err := api.StateGetActor(ctx, addr, genesisTs.Key()) if err != nil { log.Error(err) return } - ast, err := api.StateReadState(ctx, act, ts.Key()) + ast, err := api.StateReadState(ctx, act, genesisTs.Key()) if err != nil { log.Error(err) return @@ -177,9 +179,10 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS } actors[addr][*act] = actorInfo{ stateroot: bh.ParentStateRoot, + tsKey: genesisTs.Key(), state: string(state), } - addresses[addr] = address.Undef + addressToID[addr] = address.Undef alk.Unlock() }) @@ -206,11 +209,13 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS log.Error(err) return } + ast, err := api.StateReadState(ctx, &act, pts.Key()) if err != nil { log.Error(err) return } + state, err := json.Marshal(ast.State) if err != nil { log.Error(err) @@ -225,8 +230,9 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS actors[addr][act] = actorInfo{ stateroot: bh.ParentStateRoot, state: string(state), + tsKey: pts.Key(), } - addresses[addr] = address.Undef + addressToID[addr] = address.Undef alk.Unlock() } }) @@ -238,18 +244,20 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS log.Infof("Resolving addresses") for _, message := range msgs { - addresses[message.To] = address.Undef - addresses[message.From] = address.Undef + addressToID[message.To] = address.Undef + addressToID[message.From] = address.Undef } - parmap.Par(50, parmap.KMapArr(addresses), func(addr address.Address) { + parmap.Par(50, parmap.KMapArr(addressToID), func(addr address.Address) { + // FIXME: cannot use EmptyTSK here since actorID's can change during reorgs, need to use the corresponding tipset. + // TODO: figure out a way to get the corresponding tipset... raddr, err := api.StateLookupID(ctx, addr, types.EmptyTSK) if err != nil { log.Warn(err) return } alk.Lock() - addresses[addr] = raddr + addressToID[addr] = raddr alk.Unlock() }) @@ -267,6 +275,7 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS addr: addr, act: actor, stateroot: c.stateroot, + tsKey: c.tsKey, }] = &minerInfo{} } } @@ -274,14 +283,17 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS parmap.Par(50, parmap.KVMapArr(miners), func(it func() (minerKey, *minerInfo)) { k, info := it() - pow, err := api.StateMinerPower(ctx, k.addr, types.EmptyTSK) + // TODO: get the storage power actors state and and pull the miner power from there, currently this hits the + // storage power actor once for each miner for each tipset, we can do better by just getting it for each tipset + // and reading each miner power from the result. + pow, err := api.StateMinerPower(ctx, k.addr, k.tsKey) if err != nil { log.Error(err) // Not sure why this would fail, but its probably worth continuing } info.power = pow.MinerPower.QualityAdjPower - sszs, err := api.StateMinerSectorCount(ctx, k.addr, types.EmptyTSK) + sszs, err := api.StateMinerSectorCount(ctx, k.addr, k.tsKey) if err != nil { log.Error(err) return @@ -316,7 +328,7 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS log.Info("Storing address mapping") - if err := st.storeAddressMap(addresses); err != nil { + if err := st.storeAddressMap(addressToID); err != nil { log.Error(err) return } @@ -361,7 +373,7 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS log.Infof("Get deals") // TODO: incremental, gather expired - deals, err := api.StateMarketDeals(ctx, ts.Key()) + deals, err := api.StateMarketDeals(ctx, headTs.Key()) if err != nil { log.Error(err) return From 10c4fae8c08ae2931ea1fd88c5cce7c2fafde9b6 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Thu, 25 Jun 2020 16:46:50 +0200 Subject: [PATCH 93/96] Make gas traces smaller, strip callers in import-bench Signed-off-by: Jakub Sztandera --- chain/types/execresult.go | 18 +++++++++--------- chain/vm/runtime.go | 2 +- cmd/lotus-bench/import.go | 16 ++++++++++++++++ 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/chain/types/execresult.go b/chain/types/execresult.go index 443147f9e..6fc93fac6 100644 --- a/chain/types/execresult.go +++ b/chain/types/execresult.go @@ -21,16 +21,16 @@ type ExecutionTrace struct { type GasTrace struct { Name string - Location []Loc - TotalGas int64 - ComputeGas int64 - StorageGas int64 - TotalVirtualGas int64 - VirtualComputeGas int64 - VirtualStorageGas int64 + Location []Loc `json:"loc"` + TotalGas int64 `json:"tg"` + ComputeGas int64 `json:"cg"` + StorageGas int64 `json:"sg"` + TotalVirtualGas int64 `json:"vtg"` + VirtualComputeGas int64 `json:"vcg"` + VirtualStorageGas int64 `json:"vsg"` - TimeTaken time.Duration - Extra interface{} `json:",omitempty"` + TimeTaken time.Duration `json:"tt"` + Extra interface{} `json:"ex,omitempty"` Callers []uintptr `json:"-"` } diff --git a/chain/vm/runtime.go b/chain/vm/runtime.go index d6d49c214..595664de1 100644 --- a/chain/vm/runtime.go +++ b/chain/vm/runtime.go @@ -408,7 +408,7 @@ func (rt *Runtime) internalSend(from, to address.Address, method abi.MethodNum, if subrt != nil { rt.numActorsCreated = subrt.numActorsCreated } - rt.executionTrace.Subcalls = append(rt.executionTrace.Subcalls, subrt.executionTrace) //&er) + rt.executionTrace.Subcalls = append(rt.executionTrace.Subcalls, subrt.executionTrace) return ret, errSend } diff --git a/cmd/lotus-bench/import.go b/cmd/lotus-bench/import.go index f7538daec..647bb58ed 100644 --- a/cmd/lotus-bench/import.go +++ b/cmd/lotus-bench/import.go @@ -149,6 +149,7 @@ var importBenchCmd = &cli.Command{ if err != nil { return err } + stripCallers(trace) lastTse = &TipSetExec{ TipSet: cur.Key(), @@ -168,6 +169,21 @@ var importBenchCmd = &cli.Command{ }, } +func walkExecutionTrace(et *types.ExecutionTrace) { + for _, gc := range et.GasCharges { + gc.Callers = nil + } + for _, sub := range et.Subcalls { + walkExecutionTrace(&sub) + } +} + +func stripCallers(trace []*api.InvocResult) { + for _, t := range trace { + walkExecutionTrace(&t.ExecutionTrace) + } +} + type Invocation struct { TipSet types.TipSetKey Invoc *api.InvocResult From f98063d60476c4ef6cd0e0876d72dede647a1d13 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Thu, 25 Jun 2020 16:58:55 +0200 Subject: [PATCH 94/96] In this case I want it to be not really safe Signed-off-by: Jakub Sztandera --- cmd/lotus-bench/import.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/lotus-bench/import.go b/cmd/lotus-bench/import.go index 647bb58ed..ebc62aa5d 100644 --- a/cmd/lotus-bench/import.go +++ b/cmd/lotus-bench/import.go @@ -174,7 +174,7 @@ func walkExecutionTrace(et *types.ExecutionTrace) { gc.Callers = nil } for _, sub := range et.Subcalls { - walkExecutionTrace(&sub) + walkExecutionTrace(&sub) //nolint:scopelint,gosec } } From 7e342e60d177703803b3982860c6e26ad73bb418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 25 Jun 2020 00:00:11 +0200 Subject: [PATCH 95/96] sealing: Give priority to sectors with deals --- go.mod | 4 ++-- go.sum | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 3b8ecf76f..404c2d46c 100644 --- a/go.mod +++ b/go.mod @@ -29,10 +29,10 @@ require ( github.com/filecoin-project/go-paramfetch v0.0.2-0.20200605171344-fcac609550ca github.com/filecoin-project/go-statestore v0.1.0 github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b - github.com/filecoin-project/sector-storage v0.0.0-20200623224636-de544b531601 + github.com/filecoin-project/sector-storage v0.0.0-20200625154333-98ef8e4ef246 github.com/filecoin-project/specs-actors v0.6.2-0.20200617175406-de392ca14121 github.com/filecoin-project/specs-storage v0.1.1-0.20200622113353-88a9704877ea - github.com/filecoin-project/storage-fsm v0.0.0-20200623213010-fe71d5b42de3 + github.com/filecoin-project/storage-fsm v0.0.0-20200625160832-379a4655b044 github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1 github.com/go-kit/kit v0.10.0 github.com/go-ole/go-ole v1.2.4 // indirect diff --git a/go.sum b/go.sum index 7fb39f472..2b8a31ba6 100644 --- a/go.sum +++ b/go.sum @@ -252,10 +252,8 @@ github.com/filecoin-project/go-statestore v0.1.0/go.mod h1:LFc9hD+fRxPqiHiaqUEZO github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b h1:fkRZSPrYpk42PV3/lIXiL0LHetxde7vyYYvSsttQtfg= github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b/go.mod h1:Q0GQOBtKf1oE10eSXSlhN45kDBdGvEcVOqMiffqX+N8= github.com/filecoin-project/sector-storage v0.0.0-20200615154852-728a47ab99d6/go.mod h1:M59QnAeA/oV+Z8oHFLoNpGMv0LZ8Rll+vHVXX7GirPM= -github.com/filecoin-project/sector-storage v0.0.0-20200623210524-47d93356586d h1:yJJqXCMEhvXJoOS6T1O46FXl+A3mlttXhgjcTCp+Tgo= -github.com/filecoin-project/sector-storage v0.0.0-20200623210524-47d93356586d/go.mod h1:8f0hWDzzIi1hKs4IVKH9RnDsO4LEHVz8BNat0okDOuY= -github.com/filecoin-project/sector-storage v0.0.0-20200623224636-de544b531601 h1:EgMmHLoJ4caLU8RzgKQux4TyX/ZploXGtIu5Q1SaxKw= -github.com/filecoin-project/sector-storage v0.0.0-20200623224636-de544b531601/go.mod h1:8f0hWDzzIi1hKs4IVKH9RnDsO4LEHVz8BNat0okDOuY= +github.com/filecoin-project/sector-storage v0.0.0-20200625154333-98ef8e4ef246 h1:NfYQRmVRe0LzlNbK5Ket3vbBOwFD5TvtcNtfo/Sd8mg= +github.com/filecoin-project/sector-storage v0.0.0-20200625154333-98ef8e4ef246/go.mod h1:8f0hWDzzIi1hKs4IVKH9RnDsO4LEHVz8BNat0okDOuY= github.com/filecoin-project/specs-actors v0.0.0-20200210130641-2d1fbd8672cf/go.mod h1:xtDZUB6pe4Pksa/bAJbJ693OilaC5Wbot9jMhLm3cZA= github.com/filecoin-project/specs-actors v0.3.0/go.mod h1:nQYnFbQ7Y0bHZyq6HDEuVlCPR+U3z5Q3wMOQ+2aiV+Y= github.com/filecoin-project/specs-actors v0.6.0/go.mod h1:dRdy3cURykh2R8O/DKqy8olScl70rmIS7GrB4hB1IDY= @@ -265,8 +263,8 @@ github.com/filecoin-project/specs-storage v0.1.0 h1:PkDgTOT5W5Ao7752onjDl4QSv+sg github.com/filecoin-project/specs-storage v0.1.0/go.mod h1:Pr5ntAaxsh+sLG/LYiL4tKzvA83Vk5vLODYhfNwOg7k= github.com/filecoin-project/specs-storage v0.1.1-0.20200622113353-88a9704877ea h1:iixjULRQFPn7Q9KlIqfwLJnlAXO10bbkI+xy5GKGdLY= github.com/filecoin-project/specs-storage v0.1.1-0.20200622113353-88a9704877ea/go.mod h1:Pr5ntAaxsh+sLG/LYiL4tKzvA83Vk5vLODYhfNwOg7k= -github.com/filecoin-project/storage-fsm v0.0.0-20200623213010-fe71d5b42de3 h1:nH3L7YVqrHINOmvZ+5jFjFNSi9/swXcm+uufXpkFJfo= -github.com/filecoin-project/storage-fsm v0.0.0-20200623213010-fe71d5b42de3/go.mod h1:Nl0JX9I3fIVtPEJ9HzGzO4D8LXehT9PqvUQUbNvcstc= +github.com/filecoin-project/storage-fsm v0.0.0-20200625160832-379a4655b044 h1:i4oMhv1kx/MAUxRN4EM5tag5fI1uagrwQwINgKrzUt4= +github.com/filecoin-project/storage-fsm v0.0.0-20200625160832-379a4655b044/go.mod h1:JD7fmV1BYADDcy4EYQnqFH/rUzXsh0Je0jXarCjZqSk= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= From 38533631bf8c136d3cb164a236f6d3f4dea50546 Mon Sep 17 00:00:00 2001 From: Rob Quist Date: Fri, 26 Jun 2020 02:09:39 +0200 Subject: [PATCH 96/96] Bump to Go v. 1.14 as requirement --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3038e929c..b143793aa 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ all: build unexport GOFLAGS GOVERSION:=$(shell go version | cut -d' ' -f 3 | cut -d. -f 2) -ifeq ($(shell expr $(GOVERSION) \< 13), 1) +ifeq ($(shell expr $(GOVERSION) \< 14), 1) $(warning Your Golang version is go 1.$(GOVERSION)) $(error Update Golang to version $(shell grep '^go' go.mod)) endif