From d85d4f60946557e76629a5777997731810965f48 Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Wed, 13 Jul 2022 08:54:19 -0400 Subject: [PATCH 01/85] butterfly artifacts --- build/bootstrap/butterflynet.pi | 4 ++-- build/genesis/butterflynet.car | Bin 2200634 -> 2200632 bytes 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/bootstrap/butterflynet.pi b/build/bootstrap/butterflynet.pi index 919b71aa8..b8b2b1b88 100644 --- a/build/bootstrap/butterflynet.pi +++ b/build/bootstrap/butterflynet.pi @@ -1,2 +1,2 @@ -/dns4/bootstrap-0.butterfly.fildev.network/tcp/1347/p2p/12D3KooWSUZhAY3eyoPUboJ1ZWe4dNPFWTr1EPoDjbTDSAN15uhY -/dns4/bootstrap-1.butterfly.fildev.network/tcp/1347/p2p/12D3KooWDfvNrSRVGWAGbn3sm9C8z98W2x25qCZjaXGHXmGiH24e +/dns4/bootstrap-0.butterfly.fildev.network/tcp/1347/p2p/12D3KooWHc8xB2S1wFeF9ar9bVdXoEEaBPGLqfKxVQH55c4nNmxs +/dns4/bootstrap-1.butterfly.fildev.network/tcp/1347/p2p/12D3KooWPcNcwS3cKarWrN7MfANWNpzXmZA9Ag6eH9FHFdLQ3LFQ diff --git a/build/genesis/butterflynet.car b/build/genesis/butterflynet.car index 2a23ff147badc0ccf791da913b168c480b02b234..d80c3d9ce67d6b7acb5eff6994dcfbfb1f7b2e8e 100644 GIT binary patch delta 2172 zcmb7_XH=7C6vs12B#ba*WKmy&hy|&DLWzqg!-|P)hzJE#0w~Y}gsB)42w{kfsNq-z zR4O0=Z4(KCxWIxDCkS}hN))gJg)khHzCk|poYtm(_`P4A=iYOl|Nq|m)>mXe>p=4L zm>6ye%&6^`tRpt52j@>u1WDgtN!jueghd0M7Bhzv@JLEKwxco_(mOwTKjNhI$O~`XKc}ityrCjCgv???Xa&68KwDA% zsE+^3<-wfA9JqF#-ObJWLGnS8Ai#ukc?I=_i$3?OH4dqq=ZVK~$e zN>vt{rqXRrGlP6C{ZN9Po<%d2|0JvKNCB1g+_g6{(6_&A_Z%ZP()E->3r_>S4p4e2 z2+jfAF&GVFM~OkZSEE*sDy*uDb*qE$*Hbwjk+HApKQLZesrX znu5p2av+}~vj#1E=Wh%_q{|aUMX92B6CPEy3%J!|c&z^{X%f|jbTK<~<3x-Jdk^{i zRWyZM$S9k>a-HXdS_d%q*gr6Bb+u4^ZE(MadB7sXghVX6Fli)y^s(t~xdg}KgO@5z z(ADLhGBs&A;ud;*pO{*P4CLOkLkxf1r~O(xiohE~tp*O!lQjzU;{wJ*RtFtUx#Dh# zJ6f3&FSL@*2B)WRJe$&N)VMZkP!O2dMhy--?5#{&7R?%d>EXb&fZC~LPKSyKxbGdS zx{#+|#S({B=ZG12_2Ad(St@JyiHP&2>ZmpIb?pZHgib11VK!<-tyI6DKL+c#4p=zoa)+cjzs2 zA2sccUhsf({cv}#j$tyksWoFdkp2{9#ls_TV)+6x1>vjA@kJGiuWzVaO&i|3HNlfS zAjmhHeqbuQa2?IqSm@|F)={yglv%#NDQ}MAap0LZ$2#)`jQ0V#iaUl_2BHdr0#O4| z2hjl01VMvnfoOy1faroO2hju32Qd(jAq@Ren97|{-L|;3& zePRvgI4)`A6|ry4p8PN*7A}k28_!^b#?m8`qeB@jDxX#Uk2vOHAlu1QH8VYv&Gvys z5Yb-Q9h8}oNCeoZjx>bJ;Il&mx0}g}+e*!AgtV zdk7;qO;y!P_>1aEw0_hS7Cz2a=3ejs^B?YELRpHqOJ8XUCJZd(Ut101ugJ&kMTEU` zC^Nb&QJa|itspyQJ3TKvvH*|5Zx*nsTKy@F-pyxoX$RcXERX(hx?{2mv!CC{GQ=}| zV6V(^^7D`d`vvlo_4L{~=c`?#u~_&Bdoit@kVRNeVO7|Qf9_*!aU(4}(=~q>M4GMj zp6&{A7FwJF-gfTr4fNb5j1gA!@iFwp$Nm4gV=fLJCSp;Pv=eJR!s!ny`H8Y>(gVM( zP+cF0XmEW_UU`>qX{`T_dQN;!^|P9$Hw&e9Wi}oHOt`|G607?z=Qf{$dQT68|8B`< z_S-_lSokP-(#VL-vl)YOL?Mn`<8Y2;=sV3oEPQNe=^vF874aQc&Nc+WfM7vzAb1dh zSk5*YY+x!|gNph3g7jZherY!-Tb_#4%&%2sDYvqntH^!4(=Ap@Dv@~^-5NEO&~_2e zpv+1nRaupfU;*cLUX*To;cz?5R&bRht&7$a=p8X%%B^fwFG}*2ZNnvzzX4eR0*Fau HW2pCE^kC}` delta 2183 zcmdn7qK$DE5Kj=PuU6Gso$VE+9Q9@9U*nu@yQ1_BH}jen^82fB-cB>qbxbcyEh^5; z&tu%k#8|lC36laL4HKLg7#brK4%q+w`DfiPFY}hnJ+6}uuUebf%uqL(Ws~R?k3FG1 zO4pkh92%P#A|_6bRLFh#tnS8fS?Q&f{h}wsO9TTi^l2>JUX->hbo~eGL&vqJ^G;C& z6Wo)X7?qh<7zk|6U<_yUC8V#J<%U*-Is?Sn3194UYwsAmuy&ev!R^=A=XoJtJ}lek z@cQX8hU}#}oIR6yn2i+(DvD=rRY>-5vwT?hc~fXiU~Ahh`LwpWRSrvc&XkA^6g=8@ zA=zLu3rm;6Db^HjeUGju52gqys;HzYXtg@93+nN-?K)a2gZ zeW@oly(wtB>2>zjce$`*n~$@lF)G}BKk3@Sf5rb&|9x}TnsGUKFxch@6tsp)GQy?PIlomR>gUH?LY&MqwoiV=siDC7h-dziFYjbGd>8%q$$xX?bAz-wRpsS!#a4%f zzWra;+HAztZp6h1#7x_bxR{x0LI{OzV}v2|^m#1Bmojx!;#o^er_SgO{WqRF>8L(g00URETbSviBPnG9ND~BX#2Dqt7p0`s1`qZ+K;VL_gMA&6&j&s z#f7`>h|IrkllC%c@m2X(T`+kjd*1oNOyS-CYlKEXuQfWy^YEfEFWnOB@Nft;b z8ea zOU!C3i;N>%&&)V1+cII1|BqLa%x{4*Wue|?X6KKDE2q_)ML%kM%{c9Py(IH1pvK3UK?)GM@QC(3prHqkv4#T7Wp}P+X_boDbvn0M+t5b2~rg`(X9R9*+Td*c% zpF|qS#K=u2OXa@vDSTMgzGCmG;`w~3rhg=vUpGy^)hI4iPbigPNn{eKVAnL6TWNer z_T<$v3oBSLt*^+P;gh6_?0h3;*Lk7sYmPqg6DsG*vAVuy-!8rQE9;(algI#TLrrq^ zgwvcvdK1X=jqc0R*DYV^Yg`^E>A&J(>)DzkYx{lplUxO_6`ft1E_%Iv{n5h4&08lt zsI*-i&i_s#1*D5mHWxzA=MtIi&zi)6SOSP8fmjNNrGZ#x`?Dt5H@oWzm44uCMWS1w zMFOER6J&Hfslg64n@~{;GJAD2(>wkEjkzoC^1h#QJ1ga6*d6oFi?=;-e3GE0_pn26 zir1Ht`Z&=U@|}sfcHSuq@}OoDDxpDUlbUvA7(*F&(lT@O&Gm~?%M*)IpzL;k2RR^? N2V#Zo{tk*Sg8{^U=-U7Q From 7c00c1e5d51102e5f96a069c3f7be14d90dcdffd Mon Sep 17 00:00:00 2001 From: Phi Date: Thu, 1 Sep 2022 11:54:10 +0200 Subject: [PATCH 02/85] Tune down slice Tuning down slice to only 6 characters to avoid panics if the multisig-actor is of really low character length. --- cmd/lotus-miner/actor.go | 2 +- cmd/lotus-shed/actor.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-miner/actor.go b/cmd/lotus-miner/actor.go index ba7c944d7..3210062cd 100644 --- a/cmd/lotus-miner/actor.go +++ b/cmd/lotus-miner/actor.go @@ -513,7 +513,7 @@ var actorControlList = &cli.Command{ } kstr := k.String() if !cctx.Bool("verbose") { - kstr = kstr[:9] + "..." + kstr = kstr[:6] + "..." } bstr := types.FIL(b).String() diff --git a/cmd/lotus-shed/actor.go b/cmd/lotus-shed/actor.go index fa337d3ec..6bb14b49a 100644 --- a/cmd/lotus-shed/actor.go +++ b/cmd/lotus-shed/actor.go @@ -367,7 +367,7 @@ var actorControlList = &cli.Command{ kstr := k.String() if !cctx.Bool("verbose") { - kstr = kstr[:9] + "..." + kstr = kstr[:6] + "..." } bstr := types.FIL(b).String() From 3e74819519815cba11613146cfa92480a178c90a Mon Sep 17 00:00:00 2001 From: Phi Date: Fri, 2 Sep 2022 10:33:32 +0200 Subject: [PATCH 03/85] Check length of msig address Check the length of multisig address, and print based on that. --- cmd/lotus-miner/actor.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/lotus-miner/actor.go b/cmd/lotus-miner/actor.go index 3210062cd..87b095285 100644 --- a/cmd/lotus-miner/actor.go +++ b/cmd/lotus-miner/actor.go @@ -513,7 +513,9 @@ var actorControlList = &cli.Command{ } kstr := k.String() if !cctx.Bool("verbose") { - kstr = kstr[:6] + "..." + if len(kstr) > 9 { + kstr = kstr[:6] + "..." + } } bstr := types.FIL(b).String() From 9c48922a75e9b7ed681af27db681a44cf98db4e3 Mon Sep 17 00:00:00 2001 From: Phi Date: Mon, 5 Sep 2022 09:58:34 +0200 Subject: [PATCH 04/85] Fix lotus-shed actor cmd Check the length of multisig address in lotus-shed actor cmd as well. --- cmd/lotus-shed/actor.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/lotus-shed/actor.go b/cmd/lotus-shed/actor.go index 6bb14b49a..10a4e1d09 100644 --- a/cmd/lotus-shed/actor.go +++ b/cmd/lotus-shed/actor.go @@ -367,7 +367,9 @@ var actorControlList = &cli.Command{ kstr := k.String() if !cctx.Bool("verbose") { - kstr = kstr[:6] + "..." + if len(kstr) > 9 { + kstr = kstr[:6] + "..." + } } bstr := types.FIL(b).String() From abef90423d0a9b12fb3f4fb2313d2afd35a35d1e Mon Sep 17 00:00:00 2001 From: Eng Zer Jun Date: Fri, 9 Sep 2022 19:53:40 +0800 Subject: [PATCH 05/85] refactor: use `os.ReadDir` for lightweight directory reading `os.ReadDir` was added in Go 1.16 as part of the deprecation of `ioutil` package. It is a more efficient implementation than `ioutil.ReadDir`. Reference: https://pkg.go.dev/io/ioutil#ReadDir Signed-off-by: Eng Zer Jun --- lib/backupds/backupds_test.go | 3 ++- lib/backupds/log.go | 3 +-- storage/paths/local.go | 2 +- storage/sealer/ffiwrapper/sealer_test.go | 4 ++-- storage/sealer/tarutil/systar.go | 10 +++++++--- testplans/lotus-soup/sanity.go | 3 +-- 6 files changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/backupds/backupds_test.go b/lib/backupds/backupds_test.go index 48b2a8d25..b76799bfb 100644 --- a/lib/backupds/backupds_test.go +++ b/lib/backupds/backupds_test.go @@ -6,6 +6,7 @@ import ( "context" "fmt" "io/ioutil" + "os" "path/filepath" "strings" "testing" @@ -72,7 +73,7 @@ func TestLogRestore(t *testing.T) { require.NoError(t, bds.Close()) - fls, err := ioutil.ReadDir(logdir) + fls, err := os.ReadDir(logdir) require.NoError(t, err) require.Equal(t, 1, len(fls)) diff --git a/lib/backupds/log.go b/lib/backupds/log.go index eb1174b46..13ff7227f 100644 --- a/lib/backupds/log.go +++ b/lib/backupds/log.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "io" - "io/ioutil" "os" "path/filepath" "strconv" @@ -23,7 +22,7 @@ func (d *Datastore) startLog(logdir string) error { return xerrors.Errorf("mkdir logdir ('%s'): %w", logdir, err) } - files, err := ioutil.ReadDir(logdir) + files, err := os.ReadDir(logdir) if err != nil { return xerrors.Errorf("read logdir ('%s'): %w", logdir, err) } diff --git a/storage/paths/local.go b/storage/paths/local.go index 552b1738d..ec146ba5a 100644 --- a/storage/paths/local.go +++ b/storage/paths/local.go @@ -370,7 +370,7 @@ func (st *Local) declareSectors(ctx context.Context, p string, id storiface.ID, } for _, t := range storiface.PathTypes { - ents, err := ioutil.ReadDir(filepath.Join(p, t.String())) + ents, err := os.ReadDir(filepath.Join(p, t.String())) if err != nil { if os.IsNotExist(err) { if err := os.MkdirAll(filepath.Join(p, t.String()), 0755); err != nil { // nolint diff --git a/storage/sealer/ffiwrapper/sealer_test.go b/storage/sealer/ffiwrapper/sealer_test.go index fb6a2249c..71fd7c4a0 100644 --- a/storage/sealer/ffiwrapper/sealer_test.go +++ b/storage/sealer/ffiwrapper/sealer_test.go @@ -585,7 +585,7 @@ func BenchmarkWriteWithAlignment(b *testing.B) { } func openFDs(t *testing.T) int { - dent, err := ioutil.ReadDir("/proc/self/fd") + dent, err := os.ReadDir("/proc/self/fd") require.NoError(t, err) var skip int @@ -611,7 +611,7 @@ func requireFDsClosed(t *testing.T, start int) { openNow := openFDs(t) if start != openNow { - dent, err := ioutil.ReadDir("/proc/self/fd") + dent, err := os.ReadDir("/proc/self/fd") require.NoError(t, err) for _, info := range dent { diff --git a/storage/sealer/tarutil/systar.go b/storage/sealer/tarutil/systar.go index 2d7bc34a5..4cd2e2bbb 100644 --- a/storage/sealer/tarutil/systar.go +++ b/storage/sealer/tarutil/systar.go @@ -3,7 +3,6 @@ package tarutil import ( "archive/tar" "io" - "io/ioutil" "os" "path/filepath" @@ -52,13 +51,18 @@ func ExtractTar(body io.Reader, dir string, buf []byte) error { func TarDirectory(dir string, w io.Writer, buf []byte) error { tw := tar.NewWriter(w) - files, err := ioutil.ReadDir(dir) + files, err := os.ReadDir(dir) if err != nil { return err } for _, file := range files { - h, err := tar.FileInfoHeader(file, "") + info, err := file.Info() + if err != nil { + return xerrors.Errorf("getting file info for file %s: %w", file.Name(), err) + } + + h, err := tar.FileInfoHeader(info, "") if err != nil { return xerrors.Errorf("getting header for file %s: %w", file.Name(), err) } diff --git a/testplans/lotus-soup/sanity.go b/testplans/lotus-soup/sanity.go index b06a653c5..ca9799a50 100644 --- a/testplans/lotus-soup/sanity.go +++ b/testplans/lotus-soup/sanity.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "io/ioutil" "os" ) @@ -24,7 +23,7 @@ func sanityCheck() { panic(enhanceMsg("/var/tmp/filecoin-proof-parameters is not a directory; aborting")) } - files, err := ioutil.ReadDir(dir) + files, err := os.ReadDir(dir) if err != nil { panic(enhanceMsg("failed list directory /var/tmp/filecoin-proof-parameters: %s", err)) } From 8d99d27932c78ce54c9121d3c9f88af4cf82089d Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Mon, 12 Sep 2022 10:45:34 -0400 Subject: [PATCH 06/85] add envvar for PROPAGATION_DELAY_SECS --- build/params_calibnet.go | 15 +++++++++++++++ build/params_mainnet.go | 19 +++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/build/params_calibnet.go b/build/params_calibnet.go index 9ecfc048d..44083604d 100644 --- a/build/params_calibnet.go +++ b/build/params_calibnet.go @@ -4,6 +4,9 @@ package build import ( + "os" + "strconv" + "github.com/ipfs/go-cid" "github.com/filecoin-project/go-address" @@ -84,6 +87,18 @@ func init() { Devnet = true + if len(os.Getenv("PROPAGATION_DELAY_SECS")) != 0 { + PropagationDelaySecs, err := strconv.ParseUint(os.Getenv("PROPAGATION_DELAY_SECS"), 10, 64) + if err != nil { + PropagationDelaySecs = uint64(6) + log.Warnw("Error setting PROPAGATION_DELAY_SECS, %v, proceed with default value %s", err, + PropagationDelaySecs) + } else { + log.Warnw(" !!WARNING!! propagation delay is set to be %s second, "+ + "this value impacts your message republish interval and block forming - monitor with caution!!", PropagationDelaySecs) + } + } + BuildType = BuildCalibnet } diff --git a/build/params_mainnet.go b/build/params_mainnet.go index 7a532c174..5c5444e05 100644 --- a/build/params_mainnet.go +++ b/build/params_mainnet.go @@ -6,6 +6,7 @@ package build import ( "math" "os" + "strconv" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" @@ -86,6 +87,7 @@ var SupportedProofTypes = []abi.RegisteredSealProof{ var ConsensusMinerMinPower = abi.NewStoragePower(10 << 40) var MinVerifiedDealSize = abi.NewStoragePower(1 << 20) var PreCommitChallengeDelay = abi.ChainEpoch(150) +var PropagationDelaySecs = uint64(6) func init() { if os.Getenv("LOTUS_USE_TEST_ADDRESSES") != "1" { @@ -96,6 +98,21 @@ func init() { UpgradeSkyrHeight = math.MaxInt64 } + // NOTE: DO NOT change this unless you REALLY know what you're doing. This is not consensus critical, however, + //set this value too high may impacts your block submission; set this value too low may cause you miss + //parent tipsets for blocking forming and mining. + if len(os.Getenv("PROPAGATION_DELAY_SECS")) != 0 { + PropagationDelaySecs, err := strconv.ParseUint(os.Getenv("PROPAGATION_DELAY_SECS"), 10, 64) + if err != nil { + PropagationDelaySecs = uint64(6) + log.Warnw("Error setting PROPAGATION_DELAY_SECS, %v, proceed with default value %s", err, + PropagationDelaySecs) + } else { + log.Warnw(" !!WARNING!! propagation delay is set to be %s second, "+ + "this value impacts your message republish interval and block forming - monitor with caution!!", PropagationDelaySecs) + } + } + Devnet = false BuildType = BuildMainnet @@ -103,8 +120,6 @@ func init() { const BlockDelaySecs = uint64(builtin2.EpochDurationSeconds) -const PropagationDelaySecs = uint64(6) - // BootstrapPeerThreshold is the minimum number peers we need to track for a sync worker to start const BootstrapPeerThreshold = 4 From 001c5500bd54050a5a9e938fde5c5740f931c66b Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Mon, 12 Sep 2022 10:48:55 -0400 Subject: [PATCH 07/85] increase the default propagation delay to 10 sec --- build/params_calibnet.go | 4 ++-- build/params_mainnet.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/params_calibnet.go b/build/params_calibnet.go index 44083604d..c44cf7187 100644 --- a/build/params_calibnet.go +++ b/build/params_calibnet.go @@ -90,7 +90,7 @@ func init() { if len(os.Getenv("PROPAGATION_DELAY_SECS")) != 0 { PropagationDelaySecs, err := strconv.ParseUint(os.Getenv("PROPAGATION_DELAY_SECS"), 10, 64) if err != nil { - PropagationDelaySecs = uint64(6) + PropagationDelaySecs = uint64(10) log.Warnw("Error setting PROPAGATION_DELAY_SECS, %v, proceed with default value %s", err, PropagationDelaySecs) } else { @@ -105,7 +105,7 @@ func init() { const BlockDelaySecs = uint64(builtin2.EpochDurationSeconds) -const PropagationDelaySecs = uint64(6) +const PropagationDelaySecs = uint64(10) // BootstrapPeerThreshold is the minimum number peers we need to track for a sync worker to start const BootstrapPeerThreshold = 4 diff --git a/build/params_mainnet.go b/build/params_mainnet.go index 5c5444e05..e08e1b9b1 100644 --- a/build/params_mainnet.go +++ b/build/params_mainnet.go @@ -87,7 +87,7 @@ var SupportedProofTypes = []abi.RegisteredSealProof{ var ConsensusMinerMinPower = abi.NewStoragePower(10 << 40) var MinVerifiedDealSize = abi.NewStoragePower(1 << 20) var PreCommitChallengeDelay = abi.ChainEpoch(150) -var PropagationDelaySecs = uint64(6) +var PropagationDelaySecs = uint64(10) func init() { if os.Getenv("LOTUS_USE_TEST_ADDRESSES") != "1" { @@ -104,7 +104,7 @@ func init() { if len(os.Getenv("PROPAGATION_DELAY_SECS")) != 0 { PropagationDelaySecs, err := strconv.ParseUint(os.Getenv("PROPAGATION_DELAY_SECS"), 10, 64) if err != nil { - PropagationDelaySecs = uint64(6) + PropagationDelaySecs = uint64(10) log.Warnw("Error setting PROPAGATION_DELAY_SECS, %v, proceed with default value %s", err, PropagationDelaySecs) } else { From a0ec3a7bbe5453461770028b4008f1c22cb550a6 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Fri, 9 Sep 2022 20:50:37 -0400 Subject: [PATCH 08/85] Add option to terminate sectors from any address --- cmd/lotus-shed/sectors.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/cmd/lotus-shed/sectors.go b/cmd/lotus-shed/sectors.go index deb5cff8b..0332b4ba3 100644 --- a/cmd/lotus-shed/sectors.go +++ b/cmd/lotus-shed/sectors.go @@ -58,6 +58,10 @@ var terminateSectorCmd = &cli.Command{ Name: "really-do-it", Usage: "pass this flag if you know what you are doing", }, + &cli.StringFlag{ + Name: "from", + Usage: "specify the address to send the terminate message from", + }, }, Action: func(cctx *cli.Context) error { if cctx.Args().Len() < 1 { @@ -137,8 +141,19 @@ var terminateSectorCmd = &cli.Command{ return xerrors.Errorf("serializing params: %w", err) } + var fromAddr address.Address + if from := cctx.String("from"); from != "" { + var err error + fromAddr, err = address.NewFromString(from) + if err != nil { + return fmt.Errorf("parsing address %s: %w", from, err) + } + } else { + fromAddr = mi.Worker + } + smsg, err := nodeApi.MpoolPushMessage(ctx, &types.Message{ - From: mi.Owner, + From: fromAddr, To: maddr, Method: builtin.MethodsMiner.TerminateSectors, From 4c4b10c6177783622eb51f1b33888bc44034192b Mon Sep 17 00:00:00 2001 From: Shrenuj Bansal Date: Tue, 13 Sep 2022 16:33:55 -0400 Subject: [PATCH 09/85] Ignore uuid check for messages with uuid not set --- itests/mpool_msg_uuid_test.go | 52 +++++++++++++++++++++++++++++++++++ node/impl/full/mpool.go | 7 +++-- 2 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 itests/mpool_msg_uuid_test.go diff --git a/itests/mpool_msg_uuid_test.go b/itests/mpool_msg_uuid_test.go new file mode 100644 index 000000000..74e2f2a23 --- /dev/null +++ b/itests/mpool_msg_uuid_test.go @@ -0,0 +1,52 @@ +package itests + +import ( + "context" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/exitcode" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/node/config" + "github.com/stretchr/testify/require" + "testing" + "time" +) + +func TestMsgWithoutUuidWithMaxFee(t *testing.T) { + ctx := context.Background() + + kit.QuietMiningLogs() + + node, _, ens := kit.EnsembleMinimal(t, kit.MockProofs()) + ens.InterconnectAll().BeginMining(10 * time.Millisecond) + + bal, err := node.WalletBalance(ctx, node.DefaultKey.Address) + require.NoError(t, err) + + // send self half of account balance + msgHalfBal := &types.Message{ + From: node.DefaultKey.Address, + To: node.DefaultKey.Address, + Value: big.Div(bal, big.NewInt(2)), + } + smHalfBal, err := node.MpoolPushMessage(ctx, msgHalfBal, &api.MessageSendSpec{MaxFee: abi.TokenAmount(config.DefaultDefaultMaxFee)}) + require.NoError(t, err) + mLookup, err := node.StateWaitMsg(ctx, smHalfBal.Cid(), 3, api.LookbackNoLimit, true) + require.NoError(t, err) + require.Equal(t, exitcode.Ok, mLookup.Receipt.ExitCode) + + msgQuarterBal := &types.Message{ + From: node.DefaultKey.Address, + To: node.DefaultKey.Address, + Value: big.Div(bal, big.NewInt(4)), + } + smQuarterBal, err := node.MpoolPushMessage(ctx, msgQuarterBal, &api.MessageSendSpec{MaxFee: abi.TokenAmount(config.DefaultDefaultMaxFee)}) + require.NoError(t, err) + + require.Equal(t, msgQuarterBal.Value, smQuarterBal.Message.Value) + mLookup, err = node.StateWaitMsg(ctx, smQuarterBal.Cid(), 3, api.LookbackNoLimit, true) + require.NoError(t, err) + require.Equal(t, exitcode.Ok, mLookup.Receipt.ExitCode) +} diff --git a/node/impl/full/mpool.go b/node/impl/full/mpool.go index 98b2ac8a6..a9f1ebda9 100644 --- a/node/impl/full/mpool.go +++ b/node/impl/full/mpool.go @@ -3,6 +3,7 @@ package full import ( "context" "encoding/json" + "github.com/google/uuid" "github.com/ipfs/go-cid" "go.uber.org/fx" @@ -142,8 +143,8 @@ func (a *MpoolAPI) MpoolPushMessage(ctx context.Context, msg *types.Message, spe msg = &cp inMsg := *msg - // Check if this uuid has already been processed - if spec != nil { + // Check if this uuid has already been processed. Ignore if uuid is not populated + if (spec != nil) && (spec.MsgUuid != uuid.UUID{}) { signedMessage, err := a.MessageSigner.GetSignedMessage(ctx, spec.MsgUuid) if err == nil { log.Warnf("Message already processed. cid=%s", signedMessage.Cid()) @@ -206,7 +207,7 @@ func (a *MpoolAPI) MpoolPushMessage(ctx context.Context, msg *types.Message, spe } // Store uuid->signed message in datastore - if spec != nil { + if (spec != nil) && (spec.MsgUuid != uuid.UUID{}) { err = a.MessageSigner.StoreSignedMessage(ctx, spec.MsgUuid, signedMsg) if err != nil { return nil, err From a50a21b0766279231b6270f385d4f6ca592f45b3 Mon Sep 17 00:00:00 2001 From: Shrenuj Bansal Date: Tue, 13 Sep 2022 16:52:47 -0400 Subject: [PATCH 10/85] cleanup --- .circleci/config.yml | 5 +++++ itests/mpool_msg_uuid_test.go | 9 ++++++--- node/impl/full/mpool.go | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 53ac6bce0..e3bb77b9f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -924,6 +924,11 @@ workflows: suite: itest-mempool target: "./itests/mempool_test.go" + - test: + name: test-itest-mpool_msg_uuid + suite: itest-mpool_msg_uuid + target: "./itests/mpool_msg_uuid_test.go" + - test: name: test-itest-multisig suite: itest-multisig diff --git a/itests/mpool_msg_uuid_test.go b/itests/mpool_msg_uuid_test.go index 74e2f2a23..3eb30a6f6 100644 --- a/itests/mpool_msg_uuid_test.go +++ b/itests/mpool_msg_uuid_test.go @@ -2,16 +2,19 @@ package itests import ( "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/exitcode" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/itests/kit" "github.com/filecoin-project/lotus/node/config" - "github.com/stretchr/testify/require" - "testing" - "time" ) func TestMsgWithoutUuidWithMaxFee(t *testing.T) { diff --git a/node/impl/full/mpool.go b/node/impl/full/mpool.go index a9f1ebda9..a2dbf0a86 100644 --- a/node/impl/full/mpool.go +++ b/node/impl/full/mpool.go @@ -3,8 +3,8 @@ package full import ( "context" "encoding/json" - "github.com/google/uuid" + "github.com/google/uuid" "github.com/ipfs/go-cid" "go.uber.org/fx" "golang.org/x/xerrors" From ea53f12a981288549a9ed873f00a2baaf6c417f5 Mon Sep 17 00:00:00 2001 From: TippyFlitsUK Date: Wed, 14 Sep 2022 00:10:22 +0100 Subject: [PATCH 11/85] chore: Fix dead links to docs.filecoin.io --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- CHANGELOG.md | 24 ++++++++++--------- README.md | 14 +++++------ docker-compose.yaml | 2 +- documentation/en/README.md | 14 ++++------- documentation/en/about.md | 12 +++++----- .../en/default-lotus-miner-config.toml | 4 ++-- node/config/doc_gen.go | 4 ++-- node/config/types.go | 4 ++-- snap/snapcraft.yaml | 2 +- tools/dockers/docker-examples/README.md | 2 +- tools/packer/etc/motd | 4 ++-- 12 files changed, 43 insertions(+), 45 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 9c76b54dd..981a80256 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -81,7 +81,7 @@ body: render: text description: | Please provide debug logs of the problem, remember you can get set log level control for: - * lotus: use `lotus log list` to get all log systems available and set level by `lotus log set-level`. An example can be found [here](https://docs.filecoin.io/get-started/lotus/configuration-and-advanced-usage/#log-level-control). + * lotus: use `lotus log list` to get all log systems available and set level by `lotus log set-level`. An example can be found [here](https://lotus.filecoin.io/lotus/configure/defaults/#log-level-control). * lotus-miner:`lotus-miner log list` to get all log systems available and set level by `lotus-miner log set-level If you don't provide detailed logs when you raise the issue it will almost certainly be the first request I make before furthur diagnosing the problem. validations: diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ce30c688..1e84bb02b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,8 @@ Contributors | Aloxaf | 1 | +2/-2 | 1 | +# Lotus changelog + # v1.17.0 / 2022-08-02 This is an optional release of Lotus. This feature release introduces a lot of new sealing and scheduler improvements, and many other functionalities and bug fixes. @@ -1439,7 +1441,7 @@ storage providers and clients. ## Highlights - 🌟🌟🌟 Introduce Dagstore and CARv2 for deal-making (#6671) ([filecoin-project/lotus#6671](https://github.com/filecoin-project/lotus/pull/6671)) - - **[lotus miner markets' Dagstore](https://docs.filecoin.io/mine/lotus/dagstore/#conceptual-overview)** is a + - **[lotus miner markets' Dagstore](https://lotus.filecoin.io/storage-providers/operate/dagstore/)** is a component of the `markets` subsystem in lotus-miner. It is a sharded store to hold large IPLD graphs efficiently, packaged as location-transparent attachable CAR files and it replaces the former Badger staging blockstore. It is designed to provide high efficiency and throughput, and minimize resource utilization during deal-making operations. @@ -1447,18 +1449,18 @@ storage providers and clients. blockstores, which are served as the direct medium for data exchanges in markets for both storage and retrieval deal making without requiring intermediate buffers. - In the future, lotus will leverage and interact with Dagstore a lot for new features and improvements for deal - making, therefore, it's highly recommended to lotus users to go through [Lotus Miner: About the markets dagstore](https://docs.filecoin.io/mine/lotus/dagstore/#conceptual-overview) thoroughly to learn more about Dagstore's + making, therefore, it's highly recommended to lotus users to go through [Lotus Miner: About the markets dagstore](https://lotus.filecoin.io/storage-providers/operate/dagstore/) thoroughly to learn more about Dagstore's conceptual overview, terminology, directory structure, configuration and so on. - **Note**: - When you first start your lotus-miner or market subsystem with this release, a one-time/first-time **dagstore migration** will be triggered which replaces the former Badger staging blockstore with dagstore. We highly - recommend storage providers to read this [section](https://docs.filecoin.io/mine/lotus/dagstore/#first-time-migration) to learn more about + recommend storage providers to read this [section](https://lotus.filecoin.io/storage-providers/operate/dagstore/#first-time-migration) to learn more about what the process does, what to expect and how monitor it. - It is highly recommended to **wait all ongoing data transfer to finish or cancel inbound storage deals that are still transferring**, using the `lotus-miner data-transfers cancel` command before upgrade your market nodes. Reason being that the new dagstore changes attributes in the internal deal state objects, and the paths to the staging CARs where the deal data was being placed will be lost. - ‼️Having your dags initialized will become important in the near feature for you to provide a better storage and retrieval service. We'd suggest you to start [forced bulk initialization] soon if possible as this process places relatively high IP workload on your storage system and is better to be carried out gradually and over a - longer timeframe. Read how to do properly perform a force bulk initialization [here](https://docs.filecoin.io/mine/lotus/dagstore/#forcing-bulk-initialization). + longer timeframe. Read how to do properly perform a force bulk initialization [here](https://lotus.filecoin.io/storage-providers/operate/dagstore/#forcing-bulk-initialization). - ⏮ Rollback Alert(from v1.11.2-rcX to any version lower): If a storages deal is initiated with M1/v1.11.2(-rcX) release, it needs to get to the `StorageDealAwaitingPrecommit` state before you can do a version rollback or the markets process may panic. - 💙 **Special thanks to [MinerX fellows for testing and providing valuable feedbacks](https://github.com/filecoin-project/lotus/discussions/6852) for Dagstore in the past month!** @@ -1588,8 +1590,8 @@ Contributors This is a **highly recommended** but optional Lotus v1.11.1 release that introduces many deal making and datastore improvements and new features along with other bug fixes. ## Highlights -- ⭐️⭐️⭐️[**lotus-miner market subsystem**](https://docs.filecoin.io/mine/lotus/split-markets-miners/#frontmatter-title) is introduced in this release! It is **highly recommended** for storage providers to run markets processes on a separate machine! Doing so, only this machine needs to exposes public ports for deal making. This also means that the other miner operations can now be completely isolated by from the deal making processes and storage providers can stop and restarts the markets process without affecting an ongoing Winning/Window PoSt! - - More details on the concepts, architecture and how to split the market process can be found [here](https://docs.filecoin.io/mine/lotus/split-markets-miners/#concepts). +- ⭐️⭐️⭐️[**lotus-miner market subsystem**](https://lotus.filecoin.io/storage-providers/advanced-configurations/split-markets-miners/) is introduced in this release! It is **highly recommended** for storage providers to run markets processes on a separate machine! Doing so, only this machine needs to exposes public ports for deal making. This also means that the other miner operations can now be completely isolated by from the deal making processes and storage providers can stop and restarts the markets process without affecting an ongoing Winning/Window PoSt! + - More details on the concepts, architecture and how to split the market process can be found [here](https://lotus.filecoin.io/storage-providers/advanced-configurations/split-markets-miners/#concepts). - Base on your system setup(running on separate machines, same machine and so on), please see the suggested practice by community members [here](https://github.com/filecoin-project/lotus/discussions/7047#discussion-3515335). - Note: if you are running lotus-worker on a different machine, you will need to set `MARKETS_API_INFO` for certain CLI to work properly. This will be improved by #7072. - Huge thanks to MinerX fellows for [helping testing the implementation, reporting the issues so they were fixed by now and providing feedbacks](https://github.com/filecoin-project/lotus/discussions/6861) to user docs in the past three weeks! @@ -1599,7 +1601,7 @@ This is a **highly recommended** but optional Lotus v1.11.1 release that introd - `AvailableBalanceBuffer`: minimum available balance to keep in the miner actor before sending it with messages, default is 0FIL. - `DisableCollateralFallback`: whether to send collateral with messages even if there is no available balance in the miner actor, default is `false`. - Config for deal publishing control addresses ([filecoin-project/lotus#6697](https://github.com/filecoin-project/lotus/pull/6697)) - - Set `DealPublishControl` to set the wallet used for sending `PublishStorageDeals` messages, instructions [here](https://docs.filecoin.io/mine/lotus/miner-addresses/#control-addresses). + - Set `DealPublishControl` to set the wallet used for sending `PublishStorageDeals` messages, instructions [here](https://lotus.filecoin.io/storage-providers/operate/addresses/#control-addresses). - Config UX improvements ([filecoin-project/lotus#6848](https://github.com/filecoin-project/lotus/pull/6848)) - You can now preview the the default and updated node config by running `lotus/lotus-miner config default/updated` @@ -2028,7 +2030,7 @@ Note that this release is built on top of Lotus v1.9.0. Enterprising users can u FIPs [0008](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0008.md) and [0013](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0013.md) combine to allow for a significant increase in the rate of onboarding storage on the Filecoin network. This aims to lead to more useful data being stored on the network, reduced network congestion, and lower network base fee. -**Check out the documentation [here](https://docs.filecoin.io/mine/lotus/miner-configuration/#precommitsectorsbatch) for details on the new Lotus miner sealing config options, [here](https://docs.filecoin.io/mine/lotus/miner-configuration/#fees-section) for fee config options, and explanations of the new features.** +**Check out the documentation [here]((https://lotus.filecoin.io/storage-providers/advanced-configurations/sealing/#precommitsectorsbatch) for details on the new Lotus miner sealing config options, [here](https://lotus.filecoin.io/storage-providers/setup/configuration/#fees-section) for fee config options, and explanations of the new features.** Note: - We recommend to keep `PreCommitSectorsBatch` as 1. @@ -2044,7 +2046,7 @@ Given these assumptions: - We'd expect a network storage growth rate of around 530PiB per day. 😳 🎉 🥳 😅 - We'd expect network bandwidth dedicated to `SubmitWindowedPoSt` to grow by about 0.02% per day. -- We'd expect the [state-tree](https://spec.filecoin.io/#section-systems.filecoin_vm.state_tree) (and therefore [snapshot](https://docs.filecoin.io/get-started/lotus/chain/#lightweight-snapshot)) size to grow by 1.16GiB per day. +- We'd expect the [state-tree](https://spec.filecoin.io/#section-systems.filecoin_vm.state_tree) (and therefore [snapshot](https://lotus.filecoin.io/lotus/manage/chain-management/#lightweight-snapshot)) size to grow by 1.16GiB per day. - Nearly all of the state-tree growth is expected to come from new sector metadata. - We'd expect the daily lotus datastore growth rate to increase by about 10-15% (from current ~21GiB/day). - Most "growth" of the lotus datastore is due to "churn", historical data that's no longer referenced by the latest state-tree. @@ -2065,7 +2067,7 @@ Included in the HyperDrive upgrade is [FIP-0015](https://github.com/filecoin-pro - Implement FIP-0015 ([filecoin-project/lotus#6361](https://github.com/filecoin-project/lotus/pull/6361)) - Integrate FIP0013 and FIP0008 ([filecoin-project/lotus#6235](https://github.com/filecoin-project/lotus/pull/6235)) - - [Configuration docs and cli examples](https://docs.filecoin.io/mine/lotus/miner-configuration/#precommitsectorsbatch) + - [Configuration docs and cli examples](https://lotus.filecoin.io/storage-providers/advanced-configurations/sealing/#precommitsectorsbatch) - [cli docs](https://github.com/filecoin-project/lotus/blob/master/documentation/en/cli-lotus-miner.md#lotus-miner-sectors-batching) - Introduce gas prices for aggregate verifications ([filecoin-project/lotus#6347](https://github.com/filecoin-project/lotus/pull/6347)) - Introduce v5 actors ([filecoin-project/lotus#6195](https://github.com/filecoin-project/lotus/pull/6195)) @@ -2383,7 +2385,7 @@ Note that this release does NOT set an upgrade epoch for v3 actors to take effec - [#5309](https://github.com/filecoin-project/lotus/pull/5309) Batch multiple deals in one `PublishStorageMessages` - [#5411](https://github.com/filecoin-project/lotus/pull/5411) Handle batch `PublishStorageDeals` message in sealing recovery - [#5505](https://github.com/filecoin-project/lotus/pull/5505) Exclude expired deals from batching in `PublishStorageDeals` messages - - Added `PublishMsgPeriod` and `MaxDealsPerPublishMsg` to miner `Dealmaking` [configuration](https://docs.filecoin.io/mine/lotus/miner-configuration/#dealmaking-section). See how they work [here](https://docs.filecoin.io/mine/lotus/miner-configuration/#publishing-several-deals-in-one-message). + - Added `PublishMsgPeriod` and `MaxDealsPerPublishMsg` to miner `Dealmaking` [configuration](https://lotus.filecoin.io/storage-providers/advanced-configurations/market/#dealmaking-section). See how they work [here](https://lotus.filecoin.io/storage-providers/advanced-configurations/market/#publishing-several-deals-in-one-message). - [#5538](https://github.com/filecoin-project/lotus/pull/5538), [#5549](https://github.com/filecoin-project/lotus/pull/5549) Added a command to list pending deals and force publish messages. - Run `lotus-miner market pending-publish` - [#5428](https://github.com/filecoin-project/lotus/pull/5428) Moved waiting for `PublishStorageDeals` messages' receipt from markets to lotus diff --git a/README.md b/README.md index 0b3062520..bab033e72 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- + Project Lotus Logo

@@ -67,7 +67,7 @@ Fedora: sudo dnf -y install gcc make git bzr jq pkgconfig mesa-libOpenCL mesa-libOpenCL-devel opencl-headers ocl-icd ocl-icd-devel clang llvm wget hwloc hwloc-devel ``` -For other distributions you can find the required dependencies [here.](https://docs.filecoin.io/get-started/lotus/installation/#system-specific) For instructions specific to macOS, you can find them [here.](https://docs.filecoin.io/get-started/lotus/installation/#macos) +For other distributions you can find the required dependencies [here.](https://lotus.filecoin.io/lotus/install/prerequisites/#supported-platforms) For instructions specific to macOS, you can find them [here.](https://lotus.filecoin.io/lotus/install/macos/) #### Go @@ -101,7 +101,7 @@ Note: The default branch `master` is the dev branch where the latest new feature 2. To join mainnet, checkout the [latest release](https://github.com/filecoin-project/lotus/releases). - If you are changing networks from a previous Lotus installation or there has been a network reset, read the [Switch networks guide](https://docs.filecoin.io/get-started/lotus/switch-networks/) before proceeding. + If you are changing networks from a previous Lotus installation or there has been a network reset, read the [Switch networks guide](https://lotus.filecoin.io/lotus/manage/switch-networks/) before proceeding. For networks other than mainnet, look up the current branch or tag/commit for the network you want to join in the [Filecoin networks dashboard](https://network.filecoin.io), then build Lotus for your specific network below. @@ -113,8 +113,8 @@ Note: The default branch `master` is the dev branch where the latest new feature Currently, the latest code on the _master_ branch corresponds to mainnet. -3. If you are in China, see "[Lotus: tips when running in China](https://docs.filecoin.io/get-started/lotus/tips-running-in-china/)". -4. This build instruction uses the prebuilt proofs binaries. If you want to build the proof binaries from source check the [complete instructions](https://docs.filecoin.io/get-started/lotus/installation/#build-and-install-lotus). Note, if you are building the proof binaries from source, [installing rustup](https://docs.filecoin.io/get-started/lotus/installation/#rustup) is also needed. +3. If you are in China, see "[Lotus: tips when running in China](https://lotus.filecoin.io/lotus/configure/nodes-in-china/)". +4. This build instruction uses the prebuilt proofs binaries. If you want to build the proof binaries from source check the [complete instructions](https://lotus.filecoin.io/lotus/install/prerequisites/). Note, if you are building the proof binaries from source, [installing rustup](https://lotus.filecoin.io/lotus/install/linux/#rustup) is also needed. 5. Build and install Lotus: @@ -129,9 +129,9 @@ Note: The default branch `master` is the dev branch where the latest new feature This will put `lotus`, `lotus-miner` and `lotus-worker` in `/usr/local/bin`. - `lotus` will use the `$HOME/.lotus` folder by default for storage (configuration, chain data, wallets, etc). See [advanced options](https://docs.filecoin.io/get-started/lotus/configuration-and-advanced-usage/) for information on how to customize the Lotus folder. + `lotus` will use the `$HOME/.lotus` folder by default for storage (configuration, chain data, wallets, etc). See [advanced options](https://lotus.filecoin.io/lotus/configure/defaults/#environment-variables) for information on how to customize the Lotus folder. -6. You should now have Lotus installed. You can now [start the Lotus daemon and sync the chain](https://docs.filecoin.io/get-started/lotus/installation/#start-the-lotus-daemon-and-sync-the-chain). +6. You should now have Lotus installed. You can now [start the Lotus daemon and sync the chain](https://lotus.filecoin.io/lotus/install/linux/#start-the-lotus-daemon-and-sync-the-chain). ## License diff --git a/docker-compose.yaml b/docker-compose.yaml index d68eed8db..a269ba7b5 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -15,7 +15,7 @@ # docker swarm init (if you haven't already) # docker stack deploy -c docker-compose.yaml mylotuscluster # -# for more information, please visit docs.filecoin.io +# for more information, please visit lotus.filecoin.io version: "3.8" diff --git a/documentation/en/README.md b/documentation/en/README.md index 21cdbe8b7..8c99df017 100644 --- a/documentation/en/README.md +++ b/documentation/en/README.md @@ -2,16 +2,12 @@ This folder contains some Lotus documentation mostly intended for Lotus developers. -User documentation (including documentation for miners) has been moved to https://docs.filecoin.io and https://lotus.filecoin.io: +User documentation (including documentation for miners) has been moved to https://lotus.filecoin.io: -- https://docs.filecoin.io/get-started/overview/ - - https://lotus.filecoin.io/lotus/get-started/what-is-lotus/ -- https://docs.filecoin.io/store/ - - https://lotus.filecoin.io/tutorials/lotus/store-and-retrieve/store-data/ -- https://docs.filecoin.io/storage-provider/ - - https://lotus.filecoin.io/tutorials/lotus-miner/run-a-miner/ -- https://docs.filecoin.io/build/ - - https://lotus.filecoin.io/developers/ +- https://lotus.filecoin.io/lotus/get-started/what-is-lotus/ +- https://lotus.filecoin.io/tutorials/lotus/store-and-retrieve/store-data/ +- https://lotus.filecoin.io/tutorials/lotus-miner/run-a-miner/ +- https://lotus.filecoin.io/developers/ ## Documentation Website diff --git a/documentation/en/about.md b/documentation/en/about.md index f2051e00b..de2bb4c37 100644 --- a/documentation/en/about.md +++ b/documentation/en/about.md @@ -8,12 +8,12 @@ It is written in Go and provides a suite of command-line applications: - Lotus Miner (`lotus-miner`): a Filecoin miner. See the the respective Lotus Miner section in the Mine documentation. - Lotus Worker (`lotus-worker`): a worker that assists miners to perform mining-related tasks. See its respective guide for more information. -The [Lotus user documentation](https://docs.filecoin.io/get-started/lotus) is part of the [Filecoin documentation site](https://docs.filecoin.io): +The [Lotus user documentation](https://lotus.filecoin.io/lotus/get-started/what-is-lotus/) is part of the [Filecoin documentation site](https://lotus.filecoin.io): -* To install and get started with Lotus, visit the [Get Started section](https://docs.filecoin.io/get-started/lotus). -* Information about how to perform deals on the Filecoin network using Lotus can be found in the [Store section](https://docs.filecoin.io/store/lotus). -* Miners looking to provide storage to the Network can find the latest guides in the [Mine section](https://docs.filecoin.io/mine/lotus). -* Developers and integrators that wish to use the Lotus APIs can start in the [Build section](https://docs.filecoin.io/mine/lotus). +* To install and get started with Lotus, visit the [Get Started section](https://lotus.filecoin.io/lotus/install/prerequisites/). +* Information about how to perform deals on the Filecoin network using Lotus can be found in the [Store section](https://lotus.filecoin.io/tutorials/lotus/store-and-retrieve/store-data/). +* Miners looking to provide storage to the Network can find the latest guides in the [Mine section](https://lotus.filecoin.io/tutorials/lotus-miner/run-a-miner/). +* Developers and integrators that wish to use the Lotus APIs can start in the [Build section](https://lotus.filecoin.io/tutorials/lotus/build-with-lotus-api/). -For more details about Filecoin, check out the [Filecoin Docs](https://docs.filecoin.io) and [Filecoin Spec](https://spec.filecoin.io/). +For more details about Filecoin, check out the [Filecoin Docs](https://lotus.filecoin.io) and [Filecoin Spec](https://spec.filecoin.io/). diff --git a/documentation/en/default-lotus-miner-config.toml b/documentation/en/default-lotus-miner-config.toml index 786924904..46b21a91c 100644 --- a/documentation/en/default-lotus-miner-config.toml +++ b/documentation/en/default-lotus-miner-config.toml @@ -240,14 +240,14 @@ #StartEpochSealingBuffer = 480 # A command used for fine-grained evaluation of storage deals - # see https://docs.filecoin.io/mine/lotus/miner-configuration/#using-filters-for-fine-grained-storage-and-retrieval-deal-acceptance for more details + # see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/#using-filters-for-fine-grained-storage-and-retrieval-deal-acceptance for more details # # type: string # env var: LOTUS_DEALMAKING_FILTER #Filter = "" # A command used for fine-grained evaluation of retrieval deals - # see https://docs.filecoin.io/mine/lotus/miner-configuration/#using-filters-for-fine-grained-storage-and-retrieval-deal-acceptance for more details + # see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/#using-filters-for-fine-grained-storage-and-retrieval-deal-acceptance for more details # # type: string # env var: LOTUS_DEALMAKING_RETRIEVALFILTER diff --git a/node/config/doc_gen.go b/node/config/doc_gen.go index 68e413ac8..63c1ddabd 100644 --- a/node/config/doc_gen.go +++ b/node/config/doc_gen.go @@ -325,14 +325,14 @@ regardless of this number.`, Type: "string", Comment: `A command used for fine-grained evaluation of storage deals -see https://docs.filecoin.io/mine/lotus/miner-configuration/#using-filters-for-fine-grained-storage-and-retrieval-deal-acceptance for more details`, +see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/#using-filters-for-fine-grained-storage-and-retrieval-deal-acceptance for more details`, }, { Num: "RetrievalFilter", Type: "string", Comment: `A command used for fine-grained evaluation of retrieval deals -see https://docs.filecoin.io/mine/lotus/miner-configuration/#using-filters-for-fine-grained-storage-and-retrieval-deal-acceptance for more details`, +see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/#using-filters-for-fine-grained-storage-and-retrieval-deal-acceptance for more details`, }, { Num: "RetrievalPricing", diff --git a/node/config/types.go b/node/config/types.go index ddc574efb..dbea0ddb6 100644 --- a/node/config/types.go +++ b/node/config/types.go @@ -158,10 +158,10 @@ type DealmakingConfig struct { StartEpochSealingBuffer uint64 // A command used for fine-grained evaluation of storage deals - // see https://docs.filecoin.io/mine/lotus/miner-configuration/#using-filters-for-fine-grained-storage-and-retrieval-deal-acceptance for more details + // see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/#using-filters-for-fine-grained-storage-and-retrieval-deal-acceptance for more details Filter string // A command used for fine-grained evaluation of retrieval deals - // see https://docs.filecoin.io/mine/lotus/miner-configuration/#using-filters-for-fine-grained-storage-and-retrieval-deal-acceptance for more details + // see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/#using-filters-for-fine-grained-storage-and-retrieval-deal-acceptance for more details RetrievalFilter string RetrievalPricing *RetrievalPricing diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 3f83ad16d..8c7323a2b 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -13,7 +13,7 @@ description: | https://fil.org - https://docs.filecoin.io + https://lotus.filecoin.io https://github.com/filecoin-project/lotus diff --git a/tools/dockers/docker-examples/README.md b/tools/dockers/docker-examples/README.md index 3b8c34480..22ddd2476 100644 --- a/tools/dockers/docker-examples/README.md +++ b/tools/dockers/docker-examples/README.md @@ -11,7 +11,7 @@ In this `docker-examples/` directory are community-contributed Docker and Docker - local node for a developer (`api-local-`) - hosted endpoint for apps / multiple developers (`api-hosted-`) - **For a local devnet or shared devnet** - - basic local devnet (also see [lotus docs on setting up a local devnet](https://docs.filecoin.io/build/local-devnet/)) + - basic local devnet (also see [lotus docs on setting up a local devnet](https://lotus.filecoin.io/developers/local-network/)) - shared devnet diff --git a/tools/packer/etc/motd b/tools/packer/etc/motd index 5966d972b..892b156e1 100644 --- a/tools/packer/etc/motd +++ b/tools/packer/etc/motd @@ -55,10 +55,10 @@ You only need to do this once, after which, you can enable and start the miner. Do you want to access your lotus daemon remotely? Learn how to setup token authentication and use client libraries from lotus docs. -https://docs.filecoin.io/build/lotus/enable-remote-api-access/ +https://lotus.filecoin.io/reference/basics/api-access/ -For more information, see https://docs.filecoin.io/ +For more information, see https://lotus.filecoin.io/ Found a bug? let us know! https://github.com/filecoin-project/lotus Chat with us on slack! https://filecoinproject.slack.com/archives/CEGN061C5 From 21906b5a63cc819b707c856485e497c7a59904d2 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Wed, 14 Sep 2022 00:19:47 -0400 Subject: [PATCH 12/85] add api and cli calls for beneficiary withdrawl --- api/api_storage.go | 5 ++++ api/proxy_gen.go | 13 ++++++++++ api/types.go | 4 +++ build/openrpc/full.json.gz | Bin 28281 -> 28423 bytes build/openrpc/gateway.json.gz | Bin 4942 -> 5080 bytes build/openrpc/miner.json.gz | Bin 15449 -> 15508 bytes build/openrpc/worker.json.gz | Bin 5076 -> 5075 bytes cmd/lotus-miner/actor.go | 14 ++++++++-- cmd/lotus-shed/actor.go | 15 +++++++++-- documentation/en/api-v0-methods-miner.md | 27 ++++++++++++++++++++ documentation/en/api-v0-methods.md | 15 ++++++++++- documentation/en/api-v1-unstable-methods.md | 15 ++++++++++- documentation/en/cli-lotus-miner.md | 5 ++-- node/impl/full/state.go | 3 +++ node/impl/storminer.go | 17 +++++++++++- 15 files changed, 124 insertions(+), 9 deletions(-) diff --git a/api/api_storage.go b/api/api_storage.go index 1caf459d6..e9c7aad61 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -54,6 +54,11 @@ type StorageMiner interface { // and does not wait for message execution ActorWithdrawBalance(ctx context.Context, amount abi.TokenAmount) (cid.Cid, error) //perm:admin + // BeneficiaryWithdrawBalance allows the beneficiary of a miner to withdraw balance from miner actor + // Specify amount as "0" to withdraw full balance. This method returns a message CID + // and does not wait for message execution + BeneficiaryWithdrawBalance(context.Context, abi.TokenAmount) (cid.Cid, error) //perm:admin + MiningBase(context.Context) (*types.TipSet, error) //perm:read ComputeWindowPoSt(ctx context.Context, dlIdx uint64, tsk types.TipSetKey) ([]miner.SubmitWindowedPoStParams, error) //perm:admin diff --git a/api/proxy_gen.go b/api/proxy_gen.go index 2b162f346..43bd40f83 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -664,6 +664,8 @@ type StorageMinerStruct struct { ActorWithdrawBalance func(p0 context.Context, p1 abi.TokenAmount) (cid.Cid, error) `perm:"admin"` + BeneficiaryWithdrawBalance func(p0 context.Context, p1 abi.TokenAmount) (cid.Cid, error) `perm:"admin"` + CheckProvable func(p0 context.Context, p1 abi.RegisteredPoStProof, p2 []storiface.SectorRef, p3 bool) (map[abi.SectorNumber]string, error) `perm:"admin"` ComputeDataCid func(p0 context.Context, p1 abi.UnpaddedPieceSize, p2 storiface.Data) (abi.PieceInfo, error) `perm:"admin"` @@ -4036,6 +4038,17 @@ func (s *StorageMinerStub) ActorWithdrawBalance(p0 context.Context, p1 abi.Token return *new(cid.Cid), ErrNotSupported } +func (s *StorageMinerStruct) BeneficiaryWithdrawBalance(p0 context.Context, p1 abi.TokenAmount) (cid.Cid, error) { + if s.Internal.BeneficiaryWithdrawBalance == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.BeneficiaryWithdrawBalance(p0, p1) +} + +func (s *StorageMinerStub) BeneficiaryWithdrawBalance(p0 context.Context, p1 abi.TokenAmount) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + func (s *StorageMinerStruct) CheckProvable(p0 context.Context, p1 abi.RegisteredPoStProof, p2 []storiface.SectorRef, p3 bool) (map[abi.SectorNumber]string, error) { if s.Internal.CheckProvable == nil { return *new(map[abi.SectorNumber]string), ErrNotSupported diff --git a/api/types.go b/api/types.go index 683589ce1..dc83be3c7 100644 --- a/api/types.go +++ b/api/types.go @@ -17,6 +17,7 @@ import ( datatransfer "github.com/filecoin-project/go-data-transfer" "github.com/filecoin-project/go-fil-markets/retrievalmarket" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/builtin/v9/miner" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/node/modules/dtypes" @@ -295,6 +296,9 @@ type MinerInfo struct { SectorSize abi.SectorSize WindowPoStPartitionSectors uint64 ConsensusFaultElapsed abi.ChainEpoch + Beneficiary address.Address + BeneficiaryTerm miner.BeneficiaryTerm + PendingBeneficiaryTerm *miner.PendingBeneficiaryChange } type NetworkParams struct { diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index c14beca7f99bcc8645f9221a4fa5accfc65171b6..4d823b135b775d5c1fb538b9375e35941490c1f0 100644 GIT binary patch delta 26887 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 O0RR7yxRpqhg#`ej^&`Ik delta 26760 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 diff --git a/build/openrpc/gateway.json.gz b/build/openrpc/gateway.json.gz index 6e069623c457896a01bc2dd84d44f4c68bd10eb3..570715c79f051576a54c8854093418ef5ab8fee0 100644 GIT binary patch literal 5080 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 literal 4942 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 delta 12749 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 HC;|Zh#$dB3 delta 3928 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 Date: Wed, 14 Sep 2022 12:41:47 -0400 Subject: [PATCH 13/85] fix: docsgen: revert rename of API Name to Num --- node/config/cfgdocgen/gen.go | 4 +- node/config/doc_gen.go | 326 +++++++++++++++++------------------ node/config/doc_util.go | 6 +- 3 files changed, 168 insertions(+), 168 deletions(-) diff --git a/node/config/cfgdocgen/gen.go b/node/config/cfgdocgen/gen.go index d14b1aecf..513350152 100644 --- a/node/config/cfgdocgen/gen.go +++ b/node/config/cfgdocgen/gen.go @@ -94,7 +94,7 @@ func run() error { package config type DocField struct { - Num string + Name string Type string Comment string } @@ -109,7 +109,7 @@ var Doc = map[string][]DocField{ for _, f := range typ { fmt.Println("\t\t{") - fmt.Printf("\t\t\tNum: \"%s\",\n", f.Name) + fmt.Printf("\t\t\tName: \"%s\",\n", f.Name) fmt.Printf("\t\t\tType: \"%s\",\n\n", f.Type) fmt.Printf("\t\t\tComment: `%s`,\n", f.Comment) fmt.Println("\t\t},") diff --git a/node/config/doc_gen.go b/node/config/doc_gen.go index 63c1ddabd..f317e0606 100644 --- a/node/config/doc_gen.go +++ b/node/config/doc_gen.go @@ -3,7 +3,7 @@ package config type DocField struct { - Num string + Name string Type string Comment string } @@ -11,19 +11,19 @@ type DocField struct { var Doc = map[string][]DocField{ "API": []DocField{ { - Num: "ListenAddress", + Name: "ListenAddress", Type: "string", Comment: `Binding address for the Lotus API`, }, { - Num: "RemoteListenAddress", + Name: "RemoteListenAddress", Type: "string", Comment: ``, }, { - Num: "Timeout", + Name: "Timeout", Type: "Duration", Comment: ``, @@ -31,7 +31,7 @@ var Doc = map[string][]DocField{ }, "Backup": []DocField{ { - Num: "DisableMetadataLog", + Name: "DisableMetadataLog", Type: "bool", Comment: `When set to true disables metadata log (.lotus/kvlog). This can save disk @@ -43,13 +43,13 @@ your node if metadata log is disabled`, }, "BatchFeeConfig": []DocField{ { - Num: "Base", + Name: "Base", Type: "types.FIL", Comment: ``, }, { - Num: "PerSector", + Name: "PerSector", Type: "types.FIL", Comment: ``, @@ -57,13 +57,13 @@ your node if metadata log is disabled`, }, "Chainstore": []DocField{ { - Num: "EnableSplitstore", + Name: "EnableSplitstore", Type: "bool", Comment: ``, }, { - Num: "Splitstore", + Name: "Splitstore", Type: "Splitstore", Comment: ``, @@ -71,45 +71,45 @@ your node if metadata log is disabled`, }, "Client": []DocField{ { - Num: "UseIpfs", + Name: "UseIpfs", Type: "bool", Comment: ``, }, { - Num: "IpfsOnlineMode", + Name: "IpfsOnlineMode", Type: "bool", Comment: ``, }, { - Num: "IpfsMAddr", + Name: "IpfsMAddr", Type: "string", Comment: ``, }, { - Num: "IpfsUseForRetrieval", + Name: "IpfsUseForRetrieval", Type: "bool", Comment: ``, }, { - Num: "SimultaneousTransfersForStorage", + Name: "SimultaneousTransfersForStorage", Type: "uint64", Comment: `The maximum number of simultaneous data transfers between the client and storage providers for storage deals`, }, { - Num: "SimultaneousTransfersForRetrieval", + Name: "SimultaneousTransfersForRetrieval", Type: "uint64", Comment: `The maximum number of simultaneous data transfers between the client and storage providers for retrieval deals`, }, { - Num: "OffChainRetrieval", + Name: "OffChainRetrieval", Type: "bool", Comment: `Require that retrievals perform no on-chain operations. Paid retrievals @@ -119,31 +119,31 @@ of automatically performing on-chain operations.`, }, "Common": []DocField{ { - Num: "API", + Name: "API", Type: "API", Comment: ``, }, { - Num: "Backup", + Name: "Backup", Type: "Backup", Comment: ``, }, { - Num: "Logging", + Name: "Logging", Type: "Logging", Comment: ``, }, { - Num: "Libp2p", + Name: "Libp2p", Type: "Libp2p", Comment: ``, }, { - Num: "Pubsub", + Name: "Pubsub", Type: "Pubsub", Comment: ``, @@ -151,7 +151,7 @@ of automatically performing on-chain operations.`, }, "DAGStoreConfig": []DocField{ { - Num: "RootDir", + Name: "RootDir", Type: "string", Comment: `Path to the dagstore root directory. This directory contains three @@ -166,7 +166,7 @@ Default value: /dagstore (split deployment) or /dagstore (monolith deployment)`, }, { - Num: "MaxConcurrentIndex", + Name: "MaxConcurrentIndex", Type: "int", Comment: `The maximum amount of indexing jobs that can run simultaneously. @@ -174,7 +174,7 @@ Default value: /dagstore (split deployment) or Default value: 5.`, }, { - Num: "MaxConcurrentReadyFetches", + Name: "MaxConcurrentReadyFetches", Type: "int", Comment: `The maximum amount of unsealed deals that can be fetched simultaneously @@ -182,7 +182,7 @@ from the storage subsystem. 0 means unlimited. Default value: 0 (unlimited).`, }, { - Num: "MaxConcurrentUnseals", + Name: "MaxConcurrentUnseals", Type: "int", Comment: `The maximum amount of unseals that can be processed simultaneously @@ -190,7 +190,7 @@ from the storage subsystem. 0 means unlimited. Default value: 0 (unlimited).`, }, { - Num: "MaxConcurrencyStorageCalls", + Name: "MaxConcurrencyStorageCalls", Type: "int", Comment: `The maximum number of simultaneous inflight API calls to the storage @@ -198,7 +198,7 @@ subsystem. Default value: 100.`, }, { - Num: "GCInterval", + Name: "GCInterval", Type: "Duration", Comment: `The time between calls to periodic dagstore GC, in time.Duration string @@ -208,49 +208,49 @@ Default value: 1 minute.`, }, "DealmakingConfig": []DocField{ { - Num: "ConsiderOnlineStorageDeals", + Name: "ConsiderOnlineStorageDeals", Type: "bool", Comment: `When enabled, the miner can accept online deals`, }, { - Num: "ConsiderOfflineStorageDeals", + Name: "ConsiderOfflineStorageDeals", Type: "bool", Comment: `When enabled, the miner can accept offline deals`, }, { - Num: "ConsiderOnlineRetrievalDeals", + Name: "ConsiderOnlineRetrievalDeals", Type: "bool", Comment: `When enabled, the miner can accept retrieval deals`, }, { - Num: "ConsiderOfflineRetrievalDeals", + Name: "ConsiderOfflineRetrievalDeals", Type: "bool", Comment: `When enabled, the miner can accept offline retrieval deals`, }, { - Num: "ConsiderVerifiedStorageDeals", + Name: "ConsiderVerifiedStorageDeals", Type: "bool", Comment: `When enabled, the miner can accept verified deals`, }, { - Num: "ConsiderUnverifiedStorageDeals", + Name: "ConsiderUnverifiedStorageDeals", Type: "bool", Comment: `When enabled, the miner can accept unverified deals`, }, { - Num: "PieceCidBlocklist", + Name: "PieceCidBlocklist", Type: "[]cid.Cid", Comment: `A list of Data CIDs to reject when making deals`, }, { - Num: "ExpectedSealDuration", + Name: "ExpectedSealDuration", Type: "Duration", Comment: `Maximum expected amount of time getting the deal into a sealed sector will take @@ -258,47 +258,47 @@ This includes the time the deal will need to get transferred and published before being assigned to a sector`, }, { - Num: "MaxDealStartDelay", + Name: "MaxDealStartDelay", Type: "Duration", Comment: `Maximum amount of time proposed deal StartEpoch can be in future`, }, { - Num: "PublishMsgPeriod", + Name: "PublishMsgPeriod", Type: "Duration", Comment: `When a deal is ready to publish, the amount of time to wait for more deals to be ready to publish before publishing them all as a batch`, }, { - Num: "MaxDealsPerPublishMsg", + Name: "MaxDealsPerPublishMsg", Type: "uint64", Comment: `The maximum number of deals to include in a single PublishStorageDeals message`, }, { - Num: "MaxProviderCollateralMultiplier", + Name: "MaxProviderCollateralMultiplier", Type: "uint64", Comment: `The maximum collateral that the provider will put up against a deal, as a multiplier of the minimum collateral bound`, }, { - Num: "MaxStagingDealsBytes", + Name: "MaxStagingDealsBytes", Type: "int64", Comment: `The maximum allowed disk usage size in bytes of staging deals not yet passed to the sealing node by the markets service. 0 is unlimited.`, }, { - Num: "SimultaneousTransfersForStorage", + Name: "SimultaneousTransfersForStorage", Type: "uint64", Comment: `The maximum number of parallel online data transfers for storage deals`, }, { - Num: "SimultaneousTransfersForStoragePerClient", + Name: "SimultaneousTransfersForStoragePerClient", Type: "uint64", Comment: `The maximum number of simultaneous data transfers from any single client @@ -309,33 +309,33 @@ across all storage clients is bound by SimultaneousTransfersForStorage regardless of this number.`, }, { - Num: "SimultaneousTransfersForRetrieval", + Name: "SimultaneousTransfersForRetrieval", Type: "uint64", Comment: `The maximum number of parallel online data transfers for retrieval deals`, }, { - Num: "StartEpochSealingBuffer", + Name: "StartEpochSealingBuffer", Type: "uint64", Comment: `Minimum start epoch buffer to give time for sealing of sector with deal.`, }, { - Num: "Filter", + Name: "Filter", Type: "string", Comment: `A command used for fine-grained evaluation of storage deals see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/#using-filters-for-fine-grained-storage-and-retrieval-deal-acceptance for more details`, }, { - Num: "RetrievalFilter", + Name: "RetrievalFilter", Type: "string", Comment: `A command used for fine-grained evaluation of retrieval deals see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/#using-filters-for-fine-grained-storage-and-retrieval-deal-acceptance for more details`, }, { - Num: "RetrievalPricing", + Name: "RetrievalPricing", Type: "*RetrievalPricing", Comment: ``, @@ -343,7 +343,7 @@ see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/# }, "FeeConfig": []DocField{ { - Num: "DefaultMaxFee", + Name: "DefaultMaxFee", Type: "types.FIL", Comment: ``, @@ -351,25 +351,25 @@ see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/# }, "FullNode": []DocField{ { - Num: "Client", + Name: "Client", Type: "Client", Comment: ``, }, { - Num: "Wallet", + Name: "Wallet", Type: "Wallet", Comment: ``, }, { - Num: "Fees", + Name: "Fees", Type: "FeeConfig", Comment: ``, }, { - Num: "Chainstore", + Name: "Chainstore", Type: "Chainstore", Comment: ``, @@ -377,14 +377,14 @@ see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/# }, "IndexProviderConfig": []DocField{ { - Num: "Enable", + Name: "Enable", Type: "bool", Comment: `Enable set whether to enable indexing announcement to the network and expose endpoints that allow indexer nodes to process announcements. Enabled by default.`, }, { - Num: "EntriesCacheCapacity", + Name: "EntriesCacheCapacity", Type: "int", Comment: `EntriesCacheCapacity sets the maximum capacity to use for caching the indexing advertisement @@ -395,7 +395,7 @@ with the default EntriesCacheCapacity, and EntriesChunkSize means the cache size 256MiB when full.`, }, { - Num: "EntriesChunkSize", + Name: "EntriesChunkSize", Type: "int", Comment: `EntriesChunkSize sets the maximum number of multihashes to include in a single entries chunk. @@ -403,7 +403,7 @@ Defaults to 16384 if not specified. Note that chunks are chained together for in advertisements that include more multihashes than the configured EntriesChunkSize.`, }, { - Num: "TopicName", + Name: "TopicName", Type: "string", Comment: `TopicName sets the topic name on which the changes to the advertised content are announced. @@ -412,7 +412,7 @@ in following format: '/indexer/ingest/' Defaults to empty, which implies the topic name is inferred from network name.`, }, { - Num: "PurgeCacheOnStart", + Name: "PurgeCacheOnStart", Type: "bool", Comment: `PurgeCacheOnStart sets whether to clear any cached entries chunks when the provider engine @@ -422,14 +422,14 @@ datastore if any is present.`, }, "Libp2p": []DocField{ { - Num: "ListenAddresses", + Name: "ListenAddresses", Type: "[]string", Comment: `Binding address for the libp2p host - 0 means random port. Format: multiaddress; see https://multiformats.io/multiaddr/`, }, { - Num: "AnnounceAddresses", + Name: "AnnounceAddresses", Type: "[]string", Comment: `Addresses to explicitally announce to other peers. If not specified, @@ -437,26 +437,26 @@ all interface addresses are announced Format: multiaddress`, }, { - Num: "NoAnnounceAddresses", + Name: "NoAnnounceAddresses", Type: "[]string", Comment: `Addresses to not announce Format: multiaddress`, }, { - Num: "BootstrapPeers", + Name: "BootstrapPeers", Type: "[]string", Comment: ``, }, { - Num: "ProtectedPeers", + Name: "ProtectedPeers", Type: "[]string", Comment: ``, }, { - Num: "DisableNatPortMap", + Name: "DisableNatPortMap", Type: "bool", Comment: `When not disabled (default), lotus asks NAT devices (e.g., routers), to @@ -465,14 +465,14 @@ When this works (i.e., when your router supports NAT port forwarding), it makes the local lotus node accessible from the public internet`, }, { - Num: "ConnMgrLow", + Name: "ConnMgrLow", Type: "uint", Comment: `ConnMgrLow is the number of connections that the basic connection manager will trim down to.`, }, { - Num: "ConnMgrHigh", + Name: "ConnMgrHigh", Type: "uint", Comment: `ConnMgrHigh is the number of connections that, when exceeded, will trigger @@ -480,7 +480,7 @@ a connection GC operation. Note: protected/recently formed connections don't count towards this limit.`, }, { - Num: "ConnMgrGrace", + Name: "ConnMgrGrace", Type: "Duration", Comment: `ConnMgrGrace is a time duration that new connections are immune from being @@ -489,7 +489,7 @@ closed by the connection manager.`, }, "Logging": []DocField{ { - Num: "SubsystemLevels", + Name: "SubsystemLevels", Type: "map[string]string", Comment: `SubsystemLevels specify per-subsystem log levels`, @@ -497,38 +497,38 @@ closed by the connection manager.`, }, "MinerAddressConfig": []DocField{ { - Num: "PreCommitControl", + Name: "PreCommitControl", Type: "[]string", Comment: `Addresses to send PreCommit messages from`, }, { - Num: "CommitControl", + Name: "CommitControl", Type: "[]string", Comment: `Addresses to send Commit messages from`, }, { - Num: "TerminateControl", + Name: "TerminateControl", Type: "[]string", Comment: ``, }, { - Num: "DealPublishControl", + Name: "DealPublishControl", Type: "[]string", Comment: ``, }, { - Num: "DisableOwnerFallback", + Name: "DisableOwnerFallback", Type: "bool", Comment: `DisableOwnerFallback disables usage of the owner address for messages sent automatically`, }, { - Num: "DisableWorkerFallback", + Name: "DisableWorkerFallback", Type: "bool", Comment: `DisableWorkerFallback disables usage of the worker address for messages @@ -539,49 +539,49 @@ over the worker address if this flag is set.`, }, "MinerFeeConfig": []DocField{ { - Num: "MaxPreCommitGasFee", + Name: "MaxPreCommitGasFee", Type: "types.FIL", Comment: ``, }, { - Num: "MaxCommitGasFee", + Name: "MaxCommitGasFee", Type: "types.FIL", Comment: ``, }, { - Num: "MaxPreCommitBatchGasFee", + Name: "MaxPreCommitBatchGasFee", Type: "BatchFeeConfig", Comment: `maxBatchFee = maxBase + maxPerSector * nSectors`, }, { - Num: "MaxCommitBatchGasFee", + Name: "MaxCommitBatchGasFee", Type: "BatchFeeConfig", Comment: ``, }, { - Num: "MaxTerminateGasFee", + Name: "MaxTerminateGasFee", Type: "types.FIL", Comment: ``, }, { - Num: "MaxWindowPoStGasFee", + Name: "MaxWindowPoStGasFee", Type: "types.FIL", Comment: `WindowPoSt is a high-value operation, so the default fee should be high.`, }, { - Num: "MaxPublishDealsFee", + Name: "MaxPublishDealsFee", Type: "types.FIL", Comment: ``, }, { - Num: "MaxMarketBalanceAddFee", + Name: "MaxMarketBalanceAddFee", Type: "types.FIL", Comment: ``, @@ -589,37 +589,37 @@ over the worker address if this flag is set.`, }, "MinerSubsystemConfig": []DocField{ { - Num: "EnableMining", + Name: "EnableMining", Type: "bool", Comment: ``, }, { - Num: "EnableSealing", + Name: "EnableSealing", Type: "bool", Comment: ``, }, { - Num: "EnableSectorStorage", + Name: "EnableSectorStorage", Type: "bool", Comment: ``, }, { - Num: "EnableMarkets", + Name: "EnableMarkets", Type: "bool", Comment: ``, }, { - Num: "SealerApiInfo", + Name: "SealerApiInfo", Type: "string", Comment: ``, }, { - Num: "SectorIndexApiInfo", + Name: "SectorIndexApiInfo", Type: "string", Comment: ``, @@ -627,7 +627,7 @@ over the worker address if this flag is set.`, }, "ProvingConfig": []DocField{ { - Num: "ParallelCheckLimit", + Name: "ParallelCheckLimit", Type: "int", Comment: `Maximum number of sector checks to run in parallel. (0 = unlimited) @@ -640,7 +640,7 @@ After changing this option, confirm that the new value works in your setup by in 'lotus-miner proving compute window-post 0'`, }, { - Num: "DisableBuiltinWindowPoSt", + Name: "DisableBuiltinWindowPoSt", Type: "bool", Comment: `Disable Window PoSt computation on the lotus-miner process even if no window PoSt workers are present. @@ -652,7 +652,7 @@ After changing this option, confirm that the new value works in your setup by in 'lotus-miner proving compute window-post 0'`, }, { - Num: "DisableBuiltinWinningPoSt", + Name: "DisableBuiltinWinningPoSt", Type: "bool", Comment: `Disable Winning PoSt computation on the lotus-miner process even if no winning PoSt workers are present. @@ -661,7 +661,7 @@ WARNING: If no WinningPoSt workers are connected, Winning PoSt WILL FAIL resulti Before enabling this option, make sure your PoSt workers work correctly.`, }, { - Num: "DisableWDPoStPreChecks", + Name: "DisableWDPoStPreChecks", Type: "bool", Comment: `Disable WindowPoSt provable sector readability checks. @@ -690,7 +690,7 @@ After changing this option, confirm that the new value works in your setup by in 'lotus-miner proving compute window-post 0'`, }, { - Num: "MaxPartitionsPerPoStMessage", + Name: "MaxPartitionsPerPoStMessage", Type: "int", Comment: `Maximum number of partitions to prove in a single SubmitWindowPoSt messace. 0 = network limit (10 in nv16) @@ -708,7 +708,7 @@ to prove each deadline, resulting in more total gas use (but each message will h Setting this value above the network limit has no effect`, }, { - Num: "MaxPartitionsPerRecoveryMessage", + Name: "MaxPartitionsPerRecoveryMessage", Type: "int", Comment: `In some cases when submitting DeclareFaultsRecovered messages, @@ -720,13 +720,13 @@ resulting in more total gas use (but each message will have lower gas limit)`, }, "Pubsub": []DocField{ { - Num: "Bootstrapper", + Name: "Bootstrapper", Type: "bool", Comment: `Run the node in bootstrap-node mode`, }, { - Num: "DirectPeers", + Name: "DirectPeers", Type: "[]string", Comment: `DirectPeers specifies peers with direct peering agreements. These peers are @@ -737,13 +737,13 @@ symmetrically configured at both ends. Type: Array of multiaddress peerinfo strings, must include peerid (/p2p/12D3K...`, }, { - Num: "IPColocationWhitelist", + Name: "IPColocationWhitelist", Type: "[]string", Comment: ``, }, { - Num: "RemoteTracer", + Name: "RemoteTracer", Type: "string", Comment: ``, @@ -751,19 +751,19 @@ Type: Array of multiaddress peerinfo strings, must include peerid (/p2p/12D3K... }, "RetrievalPricing": []DocField{ { - Num: "Strategy", + Name: "Strategy", Type: "string", Comment: ``, }, { - Num: "Default", + Name: "Default", Type: "*RetrievalPricingDefault", Comment: ``, }, { - Num: "External", + Name: "External", Type: "*RetrievalPricingExternal", Comment: ``, @@ -771,7 +771,7 @@ Type: Array of multiaddress peerinfo strings, must include peerid (/p2p/12D3K... }, "RetrievalPricingDefault": []DocField{ { - Num: "VerifiedDealsFreeTransfer", + Name: "VerifiedDealsFreeTransfer", Type: "bool", Comment: `VerifiedDealsFreeTransfer configures zero fees for data transfer for a retrieval deal @@ -782,7 +782,7 @@ default value is true`, }, "RetrievalPricingExternal": []DocField{ { - Num: "Path", + Name: "Path", Type: "string", Comment: `Path of the external script that will be run to price a retrieval deal. @@ -791,68 +791,68 @@ This parameter is ONLY applicable if the retrieval pricing policy strategy has b }, "SealerConfig": []DocField{ { - Num: "ParallelFetchLimit", + Name: "ParallelFetchLimit", Type: "int", Comment: ``, }, { - Num: "AllowAddPiece", + Name: "AllowAddPiece", Type: "bool", Comment: `Local worker config`, }, { - Num: "AllowPreCommit1", + Name: "AllowPreCommit1", Type: "bool", Comment: ``, }, { - Num: "AllowPreCommit2", + Name: "AllowPreCommit2", Type: "bool", Comment: ``, }, { - Num: "AllowCommit", + Name: "AllowCommit", Type: "bool", Comment: ``, }, { - Num: "AllowUnseal", + Name: "AllowUnseal", Type: "bool", Comment: ``, }, { - Num: "AllowReplicaUpdate", + Name: "AllowReplicaUpdate", Type: "bool", Comment: ``, }, { - Num: "AllowProveReplicaUpdate2", + Name: "AllowProveReplicaUpdate2", Type: "bool", Comment: ``, }, { - Num: "AllowRegenSectorKey", + Name: "AllowRegenSectorKey", Type: "bool", Comment: ``, }, { - Num: "LocalWorkerName", + Name: "LocalWorkerName", Type: "string", Comment: `LocalWorkerName specifies a custom name for the builtin worker. If set to an empty string (default) os hostname will be used`, }, { - Num: "Assigner", + Name: "Assigner", Type: "string", Comment: `Assigner specifies the worker assigner to use when scheduling tasks. @@ -860,7 +860,7 @@ If set to an empty string (default) os hostname will be used`, "spread" - assign tasks to as many distinct workers as possible.`, }, { - Num: "DisallowRemoteFinalize", + Name: "DisallowRemoteFinalize", Type: "bool", Comment: `DisallowRemoteFinalize when set to true will force all Finalize tasks to @@ -876,7 +876,7 @@ If you see stuck Finalize tasks after enabling this setting, check 'lotus-miner sealing sched-diag' and 'lotus-miner storage find [sector num]'`, }, { - Num: "ResourceFiltering", + Name: "ResourceFiltering", Type: "sealer.ResourceFilteringStrategy", Comment: `ResourceFiltering instructs the system which resource filtering strategy @@ -886,7 +886,7 @@ to "hardware".`, }, "SealingConfig": []DocField{ { - Num: "MaxWaitDealsSectors", + Name: "MaxWaitDealsSectors", Type: "uint64", Comment: `Upper bound on how many sectors can be waiting for more deals to be packed in it before it begins sealing at any given time. @@ -896,19 +896,19 @@ Note that setting this number too high in relation to deal ingestion rate may re 0 = no limit`, }, { - Num: "MaxSealingSectors", + Name: "MaxSealingSectors", Type: "uint64", Comment: `Upper bound on how many sectors can be sealing+upgrading at the same time when creating new CC sectors (0 = unlimited)`, }, { - Num: "MaxSealingSectorsForDeals", + Name: "MaxSealingSectorsForDeals", Type: "uint64", Comment: `Upper bound on how many sectors can be sealing+upgrading at the same time when creating new sectors with deals (0 = unlimited)`, }, { - Num: "PreferNewSectorsForDeals", + Name: "PreferNewSectorsForDeals", Type: "bool", Comment: `Prefer creating new sectors even if there are sectors Available for upgrading. @@ -917,13 +917,13 @@ possible to use fast sector upgrades to handle high volumes of storage deals, wh flow when the volume of storage deals is lower.`, }, { - Num: "MaxUpgradingSectors", + Name: "MaxUpgradingSectors", Type: "uint64", Comment: `Upper bound on how many sectors can be sealing+upgrading at the same time when upgrading CC sectors with deals (0 = MaxSealingSectorsForDeals)`, }, { - Num: "CommittedCapacitySectorLifetime", + Name: "CommittedCapacitySectorLifetime", Type: "Duration", Comment: `CommittedCapacitySectorLifetime is the duration a Committed Capacity (CC) sector will @@ -931,27 +931,27 @@ live before it must be extended or converted into sector containing deals before terminated. Value must be between 180-540 days inclusive`, }, { - Num: "WaitDealsDelay", + Name: "WaitDealsDelay", Type: "Duration", Comment: `Period of time that a newly created sector will wait for more deals to be packed in to before it starts to seal. Sectors which are fully filled will start sealing immediately`, }, { - Num: "AlwaysKeepUnsealedCopy", + Name: "AlwaysKeepUnsealedCopy", Type: "bool", Comment: `Whether to keep unsealed copies of deal data regardless of whether the client requested that. This lets the miner avoid the relatively high cost of unsealing the data later, at the cost of more storage space`, }, { - Num: "FinalizeEarly", + Name: "FinalizeEarly", Type: "bool", Comment: `Run sector finalization before submitting sector proof to the chain`, }, { - Num: "MakeNewSectorForDeals", + Name: "MakeNewSectorForDeals", Type: "bool", Comment: `Whether new sectors are created to pack incoming deals @@ -959,111 +959,111 @@ When this is set to false no new sectors will be created for sealing incoming de This is useful for forcing all deals to be assigned as snap deals to sectors marked for upgrade`, }, { - Num: "MakeCCSectorsAvailable", + Name: "MakeCCSectorsAvailable", Type: "bool", Comment: `After sealing CC sectors, make them available for upgrading with deals`, }, { - Num: "CollateralFromMinerBalance", + Name: "CollateralFromMinerBalance", Type: "bool", Comment: `Whether to use available miner balance for sector collateral instead of sending it with each message`, }, { - Num: "AvailableBalanceBuffer", + Name: "AvailableBalanceBuffer", Type: "types.FIL", Comment: `Minimum available balance to keep in the miner actor before sending it with messages`, }, { - Num: "DisableCollateralFallback", + Name: "DisableCollateralFallback", Type: "bool", Comment: `Don't send collateral with messages even if there is no available balance in the miner actor`, }, { - Num: "BatchPreCommits", + Name: "BatchPreCommits", Type: "bool", Comment: `enable / disable precommit batching (takes effect after nv13)`, }, { - Num: "MaxPreCommitBatch", + Name: "MaxPreCommitBatch", Type: "int", Comment: `maximum precommit batch size - batches will be sent immediately above this size`, }, { - Num: "PreCommitBatchWait", + Name: "PreCommitBatchWait", Type: "Duration", Comment: `how long to wait before submitting a batch after crossing the minimum batch size`, }, { - Num: "PreCommitBatchSlack", + Name: "PreCommitBatchSlack", Type: "Duration", Comment: `time buffer for forceful batch submission before sectors/deal in batch would start expiring`, }, { - Num: "AggregateCommits", + Name: "AggregateCommits", Type: "bool", Comment: `enable / disable commit aggregation (takes effect after nv13)`, }, { - Num: "MinCommitBatch", + Name: "MinCommitBatch", Type: "int", Comment: `minimum batched commit size - batches above this size will eventually be sent on a timeout`, }, { - Num: "MaxCommitBatch", + Name: "MaxCommitBatch", Type: "int", Comment: `maximum batched commit size - batches will be sent immediately above this size`, }, { - Num: "CommitBatchWait", + Name: "CommitBatchWait", Type: "Duration", Comment: `how long to wait before submitting a batch after crossing the minimum batch size`, }, { - Num: "CommitBatchSlack", + Name: "CommitBatchSlack", Type: "Duration", Comment: `time buffer for forceful batch submission before sectors/deals in batch would start expiring`, }, { - Num: "BatchPreCommitAboveBaseFee", + Name: "BatchPreCommitAboveBaseFee", Type: "types.FIL", Comment: `network BaseFee below which to stop doing precommit batching, instead sending precommit messages to the chain individually`, }, { - Num: "AggregateAboveBaseFee", + Name: "AggregateAboveBaseFee", Type: "types.FIL", Comment: `network BaseFee below which to stop doing commit aggregation, instead submitting proofs to the chain individually`, }, { - Num: "TerminateBatchMax", + Name: "TerminateBatchMax", Type: "uint64", Comment: ``, }, { - Num: "TerminateBatchMin", + Name: "TerminateBatchMin", Type: "uint64", Comment: ``, }, { - Num: "TerminateBatchWait", + Name: "TerminateBatchWait", Type: "Duration", Comment: ``, @@ -1071,35 +1071,35 @@ submitting proofs to the chain individually`, }, "Splitstore": []DocField{ { - Num: "ColdStoreType", + Name: "ColdStoreType", Type: "string", Comment: `ColdStoreType specifies the type of the coldstore. It can be "universal" (default) or "discard" for discarding cold blocks.`, }, { - Num: "HotStoreType", + Name: "HotStoreType", Type: "string", Comment: `HotStoreType specifies the type of the hotstore. Only currently supported value is "badger".`, }, { - Num: "MarkSetType", + Name: "MarkSetType", Type: "string", Comment: `MarkSetType specifies the type of the markset. It can be "map" for in memory marking or "badger" (default) for on-disk marking.`, }, { - Num: "HotStoreMessageRetention", + Name: "HotStoreMessageRetention", Type: "uint64", Comment: `HotStoreMessageRetention specifies the retention policy for messages, in finalities beyond the compaction boundary; default is 0.`, }, { - Num: "HotStoreFullGCFrequency", + Name: "HotStoreFullGCFrequency", Type: "uint64", Comment: `HotStoreFullGCFrequency specifies how often to perform a full (moving) GC on the hotstore. @@ -1107,7 +1107,7 @@ A value of 0 disables, while a value 1 will do full GC in every compaction. Default is 20 (about once a week).`, }, { - Num: "EnableColdStoreAutoPrune", + Name: "EnableColdStoreAutoPrune", Type: "bool", Comment: `EnableColdStoreAutoPrune turns on compaction of the cold store i.e. pruning @@ -1115,7 +1115,7 @@ where hotstore compaction occurs every finality epochs pruning happens every 3 f Default is false`, }, { - Num: "ColdStoreFullGCFrequency", + Name: "ColdStoreFullGCFrequency", Type: "uint64", Comment: `ColdStoreFullGCFrequency specifies how often to performa a full (moving) GC on the coldstore. @@ -1124,7 +1124,7 @@ full GC in every prune. Default is 7 (about once every a week)`, }, { - Num: "ColdStoreRetention", + Name: "ColdStoreRetention", Type: "int64", Comment: `ColdStoreRetention specifies the retention policy for data reachable from the chain, in @@ -1133,55 +1133,55 @@ finalities beyond the compaction boundary, default is 0, -1 retains everything`, }, "StorageMiner": []DocField{ { - Num: "Subsystems", + Name: "Subsystems", Type: "MinerSubsystemConfig", Comment: ``, }, { - Num: "Dealmaking", + Name: "Dealmaking", Type: "DealmakingConfig", Comment: ``, }, { - Num: "IndexProvider", + Name: "IndexProvider", Type: "IndexProviderConfig", Comment: ``, }, { - Num: "Proving", + Name: "Proving", Type: "ProvingConfig", Comment: ``, }, { - Num: "Sealing", + Name: "Sealing", Type: "SealingConfig", Comment: ``, }, { - Num: "Storage", + Name: "Storage", Type: "SealerConfig", Comment: ``, }, { - Num: "Fees", + Name: "Fees", Type: "MinerFeeConfig", Comment: ``, }, { - Num: "Addresses", + Name: "Addresses", Type: "MinerAddressConfig", Comment: ``, }, { - Num: "DAGStore", + Name: "DAGStore", Type: "DAGStoreConfig", Comment: ``, @@ -1189,19 +1189,19 @@ finalities beyond the compaction boundary, default is 0, -1 retains everything`, }, "Wallet": []DocField{ { - Num: "RemoteBackend", + Name: "RemoteBackend", Type: "string", Comment: ``, }, { - Num: "EnableLedger", + Name: "EnableLedger", Type: "bool", Comment: ``, }, { - Num: "DisableLocal", + Name: "DisableLocal", Type: "bool", Comment: ``, diff --git a/node/config/doc_util.go b/node/config/doc_util.go index 4da94948f..b88333238 100644 --- a/node/config/doc_util.go +++ b/node/config/doc_util.go @@ -16,7 +16,7 @@ func findDoc(root interface{}, section, name string) *DocField { return findDocSect("Common", section, name) } -func findDocSect(root, section, num string) *DocField { +func findDocSect(root, section, name string) *DocField { path := strings.Split(section, ".") docSection := Doc[root] @@ -26,7 +26,7 @@ func findDocSect(root, section, num string) *DocField { } for _, field := range docSection { - if field.Num == e { + if field.Name == e { docSection = Doc[field.Type] break } @@ -35,7 +35,7 @@ func findDocSect(root, section, num string) *DocField { } for _, df := range docSection { - if df.Num == num { + if df.Name == name { return &df } } From d94cdaaade83d7bf4251af42a5ba10155db7e2d3 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Wed, 14 Sep 2022 13:15:17 -0400 Subject: [PATCH 14/85] Fixes node startup --- api/types.go | 2 +- node/impl/full/state.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/types.go b/api/types.go index dc83be3c7..f3cf119bb 100644 --- a/api/types.go +++ b/api/types.go @@ -297,7 +297,7 @@ type MinerInfo struct { WindowPoStPartitionSectors uint64 ConsensusFaultElapsed abi.ChainEpoch Beneficiary address.Address - BeneficiaryTerm miner.BeneficiaryTerm + BeneficiaryTerm *miner.BeneficiaryTerm PendingBeneficiaryTerm *miner.PendingBeneficiaryChange } diff --git a/node/impl/full/state.go b/node/impl/full/state.go index d7da00eb1..5247b9fb5 100644 --- a/node/impl/full/state.go +++ b/node/impl/full/state.go @@ -178,7 +178,7 @@ func (m *StateModule) StateMinerInfo(ctx context.Context, actor address.Address, WindowPoStPartitionSectors: info.WindowPoStPartitionSectors, ConsensusFaultElapsed: info.ConsensusFaultElapsed, Beneficiary: info.Beneficiary, - BeneficiaryTerm: info.BeneficiaryTerm, + BeneficiaryTerm: &info.BeneficiaryTerm, PendingBeneficiaryTerm: info.PendingBeneficiaryTerm, } From 308cef950b3180fec651bccc31da2c21c96c6fc7 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Wed, 14 Sep 2022 14:33:29 -0400 Subject: [PATCH 15/85] cctx.Args().Len() to cctx.NArg() --- cli/backup.go | 2 +- cli/chain.go | 6 ++-- cli/client.go | 4 +-- cli/filplus.go | 4 +-- cli/mpool.go | 6 ++-- cli/multisig.go | 52 +++++++++++++++---------------- cli/net.go | 2 +- cli/paych.go | 22 ++++++------- cli/send.go | 2 +- cli/state.go | 8 ++--- cmd/lotus-bench/simple.go | 2 +- cmd/lotus-miner/init_restore.go | 2 +- cmd/lotus-miner/market.go | 4 +-- cmd/lotus-miner/proving.go | 8 ++--- cmd/lotus-miner/sealing.go | 6 ++-- cmd/lotus-miner/sectors.go | 18 +++++------ cmd/lotus-seed/genesis.go | 12 +++---- cmd/lotus-shed/base16.go | 2 +- cmd/lotus-shed/base32.go | 2 +- cmd/lotus-shed/base64.go | 2 +- cmd/lotus-shed/consensus.go | 2 +- cmd/lotus-shed/datastore.go | 4 +-- cmd/lotus-shed/export-car.go | 2 +- cmd/lotus-shed/keyinfo.go | 4 +-- cmd/lotus-shed/miner.go | 4 +-- cmd/lotus-shed/msg.go | 2 +- cmd/lotus-shed/proofs.go | 2 +- cmd/lotus-shed/rpc.go | 2 +- cmd/lotus-shed/sectors.go | 4 +-- cmd/lotus-shed/signatures.go | 4 +-- cmd/lotus-shed/stateroot-stats.go | 4 +-- cmd/lotus-shed/sync.go | 6 ++-- cmd/lotus-shed/verifreg.go | 8 ++--- 33 files changed, 107 insertions(+), 107 deletions(-) diff --git a/cli/backup.go b/cli/backup.go index 4d88d4bbc..3cb44718f 100644 --- a/cli/backup.go +++ b/cli/backup.go @@ -114,7 +114,7 @@ this command must be within this base path`, }, ArgsUsage: "[backup file path]", Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return xerrors.Errorf("expected 1 argument") } diff --git a/cli/chain.go b/cli/chain.go index 39f1608f5..80df5655b 100644 --- a/cli/chain.go +++ b/cli/chain.go @@ -946,7 +946,7 @@ var ChainBisectCmd = &cli.Command{ defer closer() ctx := ReqContext(cctx) - if cctx.Args().Len() < 4 { + if cctx.NArg() < 4 { return xerrors.New("need at least 4 args") } @@ -1312,7 +1312,7 @@ var chainDecodeParamsCmd = &cli.Command{ defer closer() ctx := ReqContext(cctx) - if cctx.Args().Len() != 3 { + if cctx.NArg() != 3 { return ShowHelp(cctx, fmt.Errorf("incorrect number of arguments")) } @@ -1391,7 +1391,7 @@ var chainEncodeParamsCmd = &cli.Command{ Action: func(cctx *cli.Context) error { afmt := NewAppFmt(cctx.App) - if cctx.Args().Len() != 3 { + if cctx.NArg() != 3 { return ShowHelp(cctx, fmt.Errorf("incorrect number of arguments")) } diff --git a/cli/client.go b/cli/client.go index 5f2342b2b..577a8e840 100644 --- a/cli/client.go +++ b/cli/client.go @@ -212,7 +212,7 @@ var clientCommPCmd = &cli.Command{ defer closer() ctx := ReqContext(cctx) - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return fmt.Errorf("usage: commP ") } @@ -245,7 +245,7 @@ var clientCarGenCmd = &cli.Command{ defer closer() ctx := ReqContext(cctx) - if cctx.Args().Len() != 2 { + if cctx.NArg() != 2 { return fmt.Errorf("usage: generate-car ") } diff --git a/cli/filplus.go b/cli/filplus.go index 66de32d0c..7ac4f0d58 100644 --- a/cli/filplus.go +++ b/cli/filplus.go @@ -61,7 +61,7 @@ var filplusVerifyClientCmd = &cli.Command{ return err } - if cctx.Args().Len() != 2 { + if cctx.NArg() != 2 { return fmt.Errorf("must specify two arguments: address and allowance") } @@ -289,7 +289,7 @@ var filplusSignRemoveDataCapProposal = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 3 { + if cctx.NArg() != 3 { return fmt.Errorf("must specify three arguments: notary address, client address, and allowance to remove") } diff --git a/cli/mpool.go b/cli/mpool.go index 1410814b5..c8003092d 100644 --- a/cli/mpool.go +++ b/cli/mpool.go @@ -404,7 +404,7 @@ var MpoolReplaceCmd = &cli.Command{ var from address.Address var nonce uint64 - switch cctx.Args().Len() { + switch cctx.NArg() { case 1: mcid, err := cid.Decode(cctx.Args().First()) if err != nil { @@ -610,7 +610,7 @@ var MpoolConfig = &cli.Command{ Usage: "get or set current mpool configuration", ArgsUsage: "[new-config]", Action: func(cctx *cli.Context) error { - if cctx.Args().Len() > 1 { + if cctx.NArg() > 1 { return cli.ShowCommandHelp(cctx, cctx.Command.Name) } @@ -624,7 +624,7 @@ var MpoolConfig = &cli.Command{ ctx := ReqContext(cctx) - if cctx.Args().Len() == 0 { + if cctx.NArg() == 0 { cfg, err := api.MpoolGetConfig(ctx) if err != nil { return err diff --git a/cli/multisig.go b/cli/multisig.go index 67c833397..7a86c2d87 100644 --- a/cli/multisig.go +++ b/cli/multisig.go @@ -88,7 +88,7 @@ var msigCreateCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() < 1 { + if cctx.NArg() < 1 { return ShowHelp(cctx, fmt.Errorf("multisigs must have at least one signer")) } @@ -365,11 +365,11 @@ var msigProposeCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() < 3 { + if cctx.NArg() < 3 { return ShowHelp(cctx, fmt.Errorf("must pass at least multisig address, destination, and value")) } - if cctx.Args().Len() > 3 && cctx.Args().Len() != 5 { + if cctx.NArg() > 3 && cctx.NArg() != 5 { return ShowHelp(cctx, fmt.Errorf("must either pass three or five arguments")) } @@ -399,7 +399,7 @@ var msigProposeCmd = &cli.Command{ var method uint64 var params []byte - if cctx.Args().Len() == 5 { + if cctx.NArg() == 5 { m, err := strconv.ParseUint(cctx.Args().Get(3), 10, 64) if err != nil { return err @@ -487,15 +487,15 @@ var msigApproveCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() < 2 { + if cctx.NArg() < 2 { return ShowHelp(cctx, fmt.Errorf("must pass at least multisig address and message ID")) } - if cctx.Args().Len() > 2 && cctx.Args().Len() < 5 { + if cctx.NArg() > 2 && cctx.NArg() < 5 { return ShowHelp(cctx, fmt.Errorf("usage: msig approve ")) } - if cctx.Args().Len() > 5 && cctx.Args().Len() != 7 { + if cctx.NArg() > 5 && cctx.NArg() != 7 { return ShowHelp(cctx, fmt.Errorf("usage: msig approve [ ]")) } @@ -534,7 +534,7 @@ var msigApproveCmd = &cli.Command{ } var msgCid cid.Cid - if cctx.Args().Len() == 2 { + if cctx.NArg() == 2 { proto, err := api.MsigApprove(ctx, msig, txid, from) if err != nil { return err @@ -571,7 +571,7 @@ var msigApproveCmd = &cli.Command{ var method uint64 var params []byte - if cctx.Args().Len() == 7 { + if cctx.NArg() == 7 { m, err := strconv.ParseUint(cctx.Args().Get(5), 10, 64) if err != nil { return err @@ -624,15 +624,15 @@ var msigCancelCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() < 2 { + if cctx.NArg() < 2 { return ShowHelp(cctx, fmt.Errorf("must pass at least multisig address and message ID")) } - if cctx.Args().Len() > 2 && cctx.Args().Len() < 4 { + if cctx.NArg() > 2 && cctx.NArg() < 4 { return ShowHelp(cctx, fmt.Errorf("usage: msig cancel ")) } - if cctx.Args().Len() > 4 && cctx.Args().Len() != 6 { + if cctx.NArg() > 4 && cctx.NArg() != 6 { return ShowHelp(cctx, fmt.Errorf("usage: msig cancel [ ]")) } @@ -671,7 +671,7 @@ var msigCancelCmd = &cli.Command{ } var msgCid cid.Cid - if cctx.Args().Len() == 2 { + if cctx.NArg() == 2 { proto, err := api.MsigCancel(ctx, msig, txid, from) if err != nil { return err @@ -696,7 +696,7 @@ var msigCancelCmd = &cli.Command{ var method uint64 var params []byte - if cctx.Args().Len() == 6 { + if cctx.NArg() == 6 { m, err := strconv.ParseUint(cctx.Args().Get(4), 10, 64) if err != nil { return err @@ -753,7 +753,7 @@ var msigRemoveProposeCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 2 { + if cctx.NArg() != 2 { return ShowHelp(cctx, fmt.Errorf("must pass multisig address and signer address")) } @@ -840,7 +840,7 @@ var msigAddProposeCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 2 { + if cctx.NArg() != 2 { return ShowHelp(cctx, fmt.Errorf("must pass multisig address and signer address")) } @@ -949,7 +949,7 @@ var msigAddApproveCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 5 { + if cctx.NArg() != 5 { return ShowHelp(cctx, fmt.Errorf("must pass multisig address, proposer address, transaction id, new signer address, whether to increase threshold")) } @@ -1040,7 +1040,7 @@ var msigAddCancelCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 4 { + if cctx.NArg() != 4 { return ShowHelp(cctx, fmt.Errorf("must pass multisig address, transaction id, new signer address, whether to increase threshold")) } @@ -1126,7 +1126,7 @@ var msigSwapProposeCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 3 { + if cctx.NArg() != 3 { return ShowHelp(cctx, fmt.Errorf("must pass multisig address, old signer address, new signer address")) } @@ -1207,7 +1207,7 @@ var msigSwapApproveCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 5 { + if cctx.NArg() != 5 { return ShowHelp(cctx, fmt.Errorf("must pass multisig address, proposer address, transaction id, old signer address, new signer address")) } @@ -1298,7 +1298,7 @@ var msigSwapCancelCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 4 { + if cctx.NArg() != 4 { return ShowHelp(cctx, fmt.Errorf("must pass multisig address, transaction id, old signer address, new signer address")) } @@ -1384,7 +1384,7 @@ var msigLockProposeCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 4 { + if cctx.NArg() != 4 { return ShowHelp(cctx, fmt.Errorf("must pass multisig address, start epoch, unlock duration, and amount")) } @@ -1480,7 +1480,7 @@ var msigLockApproveCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 6 { + if cctx.NArg() != 6 { return ShowHelp(cctx, fmt.Errorf("must pass multisig address, proposer address, tx id, start epoch, unlock duration, and amount")) } @@ -1586,7 +1586,7 @@ var msigLockCancelCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 5 { + if cctx.NArg() != 5 { return ShowHelp(cctx, fmt.Errorf("must pass multisig address, tx id, start epoch, unlock duration, and amount")) } @@ -1693,7 +1693,7 @@ var msigVestedCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return ShowHelp(cctx, fmt.Errorf("must pass multisig address")) } @@ -1749,7 +1749,7 @@ var msigProposeThresholdCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 2 { + if cctx.NArg() != 2 { return ShowHelp(cctx, fmt.Errorf("must pass multisig address and new threshold value")) } diff --git a/cli/net.go b/cli/net.go index 5a141e52d..c1ca1ce18 100644 --- a/cli/net.go +++ b/cli/net.go @@ -141,7 +141,7 @@ var NetPing = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return xerrors.Errorf("please provide a peerID") } diff --git a/cli/paych.go b/cli/paych.go index 8277e3123..263aa7f34 100644 --- a/cli/paych.go +++ b/cli/paych.go @@ -50,7 +50,7 @@ var paychAddFundsCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 3 { + if cctx.NArg() != 3 { return ShowHelp(cctx, fmt.Errorf("must pass three arguments: ")) } @@ -112,7 +112,7 @@ var paychStatusByFromToCmd = &cli.Command{ Usage: "Show the status of an active outbound payment channel by from/to addresses", ArgsUsage: "[fromAddress toAddress]", Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 2 { + if cctx.NArg() != 2 { return ShowHelp(cctx, fmt.Errorf("must pass two arguments: ")) } ctx := ReqContext(cctx) @@ -148,7 +148,7 @@ var paychStatusCmd = &cli.Command{ Usage: "Show the status of an outbound payment channel", ArgsUsage: "[channelAddress]", Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return ShowHelp(cctx, fmt.Errorf("must pass an argument: ")) } ctx := ReqContext(cctx) @@ -260,7 +260,7 @@ var paychSettleCmd = &cli.Command{ Usage: "Settle a payment channel", ArgsUsage: "[channelAddress]", Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return fmt.Errorf("must pass payment channel address") } @@ -300,7 +300,7 @@ var paychCloseCmd = &cli.Command{ Usage: "Collect funds for a payment channel", ArgsUsage: "[channelAddress]", Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return fmt.Errorf("must pass payment channel address") } @@ -360,7 +360,7 @@ var paychVoucherCreateCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 2 { + if cctx.NArg() != 2 { return ShowHelp(cctx, fmt.Errorf("must pass two arguments: ")) } @@ -408,7 +408,7 @@ var paychVoucherCheckCmd = &cli.Command{ Usage: "Check validity of payment channel voucher", ArgsUsage: "[channelAddress voucher]", Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 2 { + if cctx.NArg() != 2 { return ShowHelp(cctx, fmt.Errorf("must pass payment channel address and voucher to validate")) } @@ -444,7 +444,7 @@ var paychVoucherAddCmd = &cli.Command{ Usage: "Add payment channel voucher to local datastore", ArgsUsage: "[channelAddress voucher]", Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 2 { + if cctx.NArg() != 2 { return ShowHelp(cctx, fmt.Errorf("must pass payment channel address and voucher")) } @@ -486,7 +486,7 @@ var paychVoucherListCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return ShowHelp(cctx, fmt.Errorf("must pass payment channel address")) } @@ -531,7 +531,7 @@ var paychVoucherBestSpendableCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return ShowHelp(cctx, fmt.Errorf("must pass payment channel address")) } @@ -602,7 +602,7 @@ var paychVoucherSubmitCmd = &cli.Command{ Usage: "Submit voucher to chain to update payment channel state", ArgsUsage: "[channelAddress voucher]", Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 2 { + if cctx.NArg() != 2 { return ShowHelp(cctx, fmt.Errorf("must pass payment channel address and voucher")) } diff --git a/cli/send.go b/cli/send.go index b5bfd3eb0..b2471e7cd 100644 --- a/cli/send.go +++ b/cli/send.go @@ -67,7 +67,7 @@ var sendCmd = &cli.Command{ fmt.Println("'force' flag is deprecated, use global flag 'force-send'") } - if cctx.Args().Len() != 2 { + if cctx.NArg() != 2 { return ShowHelp(cctx, fmt.Errorf("'send' expects two arguments, target and amount")) } diff --git a/cli/state.go b/cli/state.go index bea7ed0df..e4afc6915 100644 --- a/cli/state.go +++ b/cli/state.go @@ -504,7 +504,7 @@ var StateReplayCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { fmt.Println("must provide cid of message to replay") return nil } @@ -1580,7 +1580,7 @@ var StateCallCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() < 2 { + if cctx.NArg() < 2 { return fmt.Errorf("must specify at least actor and method to invoke") } @@ -1619,7 +1619,7 @@ var StateCallCmd = &cli.Command{ var params []byte // If params were passed in, decode them - if cctx.Args().Len() > 2 { + if cctx.NArg() > 2 { switch cctx.String("encoding") { case "base64": params, err = base64.StdEncoding.DecodeString(cctx.Args().Get(2)) @@ -1743,7 +1743,7 @@ var StateSectorCmd = &cli.Command{ ctx := ReqContext(cctx) - if cctx.Args().Len() != 2 { + if cctx.NArg() != 2 { return xerrors.Errorf("expected 2 params: minerAddress and sectorNumber") } diff --git a/cmd/lotus-bench/simple.go b/cmd/lotus-bench/simple.go index b999cd277..87e2c3bc0 100644 --- a/cmd/lotus-bench/simple.go +++ b/cmd/lotus-bench/simple.go @@ -959,7 +959,7 @@ var simpleProveReplicaUpdate2 = &cli.Command{ } func ParsePieceInfos(cctx *cli.Context, firstArg int) ([]abi.PieceInfo, error) { - args := cctx.Args().Len() - firstArg + args := cctx.NArg() - firstArg if args%2 != 0 { return nil, xerrors.Errorf("piece info argunemts need to be supplied in pairs") } diff --git a/cmd/lotus-miner/init_restore.go b/cmd/lotus-miner/init_restore.go index a54146fb2..9d7f977b6 100644 --- a/cmd/lotus-miner/init_restore.go +++ b/cmd/lotus-miner/init_restore.go @@ -96,7 +96,7 @@ var restoreCmd = &cli.Command{ } func restore(ctx context.Context, cctx *cli.Context, targetPath string, strConfig *paths.StorageConfig, manageConfig func(*config.StorageMiner) error, after func(api lapi.FullNode, addr address.Address, peerid peer.ID, mi api.MinerInfo) error) error { - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return xerrors.Errorf("expected 1 argument") } diff --git a/cmd/lotus-miner/market.go b/cmd/lotus-miner/market.go index 37d252efa..78b68214e 100644 --- a/cmd/lotus-miner/market.go +++ b/cmd/lotus-miner/market.go @@ -370,7 +370,7 @@ var dealsImportDataCmd = &cli.Command{ ctx := lcli.DaemonContext(cctx) - if cctx.Args().Len() < 2 { + if cctx.NArg() < 2 { return fmt.Errorf("must specify proposal CID and file path") } @@ -617,7 +617,7 @@ var setSealDurationCmd = &cli.Command{ } defer closer() ctx := lcli.ReqContext(cctx) - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return xerrors.Errorf("must pass duration in minutes") } diff --git a/cmd/lotus-miner/proving.go b/cmd/lotus-miner/proving.go index 85bc48e78..bbb744680 100644 --- a/cmd/lotus-miner/proving.go +++ b/cmd/lotus-miner/proving.go @@ -314,7 +314,7 @@ var provingDeadlineInfoCmd = &cli.Command{ ArgsUsage: "", Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return xerrors.Errorf("must pass deadline index") } @@ -461,7 +461,7 @@ var provingCheckProvableCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return xerrors.Errorf("must pass deadline index") } @@ -616,7 +616,7 @@ var provingComputeWindowPoStCmd = &cli.Command{ It will not send any messages to the chain.`, ArgsUsage: "[deadline index]", Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return xerrors.Errorf("must pass deadline index") } @@ -661,7 +661,7 @@ var provingRecoverFaultsCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() < 1 { + if cctx.NArg() < 1 { return xerrors.Errorf("must pass at least 1 sector number") } diff --git a/cmd/lotus-miner/sealing.go b/cmd/lotus-miner/sealing.go index 970f54a55..334266c79 100644 --- a/cmd/lotus-miner/sealing.go +++ b/cmd/lotus-miner/sealing.go @@ -372,7 +372,7 @@ var sealingAbortCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return xerrors.Errorf("expected 1 argument") } @@ -430,7 +430,7 @@ var sealingDataCidCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() < 1 || cctx.Args().Len() > 2 { + if cctx.NArg() < 1 || cctx.NArg() > 2 { return xerrors.Errorf("expected 1 or 2 arguments") } @@ -484,7 +484,7 @@ var sealingDataCidCmd = &cli.Command{ } var psize abi.PaddedPieceSize - if cctx.Args().Len() == 2 { + if cctx.NArg() == 2 { rps, err := humanize.ParseBytes(cctx.Args().Get(1)) if err != nil { return xerrors.Errorf("parsing piece size: %w", err) diff --git a/cmd/lotus-miner/sectors.go b/cmd/lotus-miner/sectors.go index 45a1256aa..336a06f52 100644 --- a/cmd/lotus-miner/sectors.go +++ b/cmd/lotus-miner/sectors.go @@ -1378,7 +1378,7 @@ var sectorsTerminateCmd = &cli.Command{ } defer closer() ctx := lcli.ReqContext(cctx) - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return xerrors.Errorf("must pass sector number") } @@ -1488,7 +1488,7 @@ var sectorsRemoveCmd = &cli.Command{ } defer closer() ctx := lcli.ReqContext(cctx) - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return xerrors.Errorf("must pass sector number") } @@ -1506,7 +1506,7 @@ var sectorsSnapUpCmd = &cli.Command{ Usage: "Mark a committed capacity sector to be filled with deals", ArgsUsage: "", Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return lcli.ShowHelp(cctx, xerrors.Errorf("must pass sector number")) } @@ -1550,7 +1550,7 @@ var sectorsSnapAbortCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return lcli.ShowHelp(cctx, xerrors.Errorf("must pass sector number")) } @@ -1587,7 +1587,7 @@ var sectorsStartSealCmd = &cli.Command{ } defer closer() ctx := lcli.ReqContext(cctx) - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return xerrors.Errorf("must pass sector number") } @@ -1611,7 +1611,7 @@ var sectorsSealDelayCmd = &cli.Command{ } defer closer() ctx := lcli.ReqContext(cctx) - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return xerrors.Errorf("must pass duration in minutes") } @@ -1714,7 +1714,7 @@ var sectorsUpdateCmd = &cli.Command{ } defer closer() ctx := lcli.ReqContext(cctx) - if cctx.Args().Len() < 2 { + if cctx.NArg() < 2 { return xerrors.Errorf("must pass sector number and new state") } @@ -2310,7 +2310,7 @@ var sectorsNumbersReserveCmd = &cli.Command{ defer closer() ctx := lcli.ReqContext(cctx) - if cctx.Args().Len() != 2 { + if cctx.NArg() != 2 { return xerrors.Errorf("expected 2 arguments: [reservation name] [reserved ranges]") } @@ -2335,7 +2335,7 @@ var sectorsNumbersFreeCmd = &cli.Command{ defer closer() ctx := lcli.ReqContext(cctx) - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return xerrors.Errorf("expected 1 argument: [reservation name]") } diff --git a/cmd/lotus-seed/genesis.go b/cmd/lotus-seed/genesis.go index 9f0a8f7dc..df2a91ad9 100644 --- a/cmd/lotus-seed/genesis.go +++ b/cmd/lotus-seed/genesis.go @@ -93,7 +93,7 @@ var genesisAddMinerCmd = &cli.Command{ Description: "add genesis miner", Flags: []cli.Flag{}, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 2 { + if cctx.NArg() != 2 { return xerrors.New("seed genesis add-miner [genesis.json] [preseal.json]") } @@ -181,7 +181,7 @@ type GenAccountEntry struct { var genesisAddMsigsCmd = &cli.Command{ Name: "add-msigs", Action: func(cctx *cli.Context) error { - if cctx.Args().Len() < 2 { + if cctx.NArg() < 2 { return fmt.Errorf("must specify template file and csv file with accounts") } @@ -329,7 +329,7 @@ var genesisSetVRKCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return fmt.Errorf("must specify template file") } @@ -425,7 +425,7 @@ var genesisSetRemainderCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return fmt.Errorf("must specify template file") } @@ -519,7 +519,7 @@ var genesisSetActorVersionCmd = &cli.Command{ }, ArgsUsage: "", Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return fmt.Errorf("must specify genesis file") } @@ -597,7 +597,7 @@ var genesisSetVRKSignersCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return fmt.Errorf("must specify template file") } diff --git a/cmd/lotus-shed/base16.go b/cmd/lotus-shed/base16.go index adfdfeddb..a5d384815 100644 --- a/cmd/lotus-shed/base16.go +++ b/cmd/lotus-shed/base16.go @@ -24,7 +24,7 @@ var base16Cmd = &cli.Command{ Action: func(cctx *cli.Context) error { var input io.Reader - if cctx.Args().Len() == 0 { + if cctx.NArg() == 0 { input = os.Stdin } else { input = strings.NewReader(cctx.Args().First()) diff --git a/cmd/lotus-shed/base32.go b/cmd/lotus-shed/base32.go index 4ca177316..66e180ddc 100644 --- a/cmd/lotus-shed/base32.go +++ b/cmd/lotus-shed/base32.go @@ -24,7 +24,7 @@ var base32Cmd = &cli.Command{ Action: func(cctx *cli.Context) error { var input io.Reader - if cctx.Args().Len() == 0 { + if cctx.NArg() == 0 { input = os.Stdin } else { input = strings.NewReader(cctx.Args().First()) diff --git a/cmd/lotus-shed/base64.go b/cmd/lotus-shed/base64.go index 9143a6a19..cacc60152 100644 --- a/cmd/lotus-shed/base64.go +++ b/cmd/lotus-shed/base64.go @@ -32,7 +32,7 @@ var base64Cmd = &cli.Command{ Action: func(cctx *cli.Context) error { var input io.Reader - if cctx.Args().Len() == 0 { + if cctx.NArg() == 0 { input = os.Stdin } else { input = strings.NewReader(cctx.Args().First()) diff --git a/cmd/lotus-shed/consensus.go b/cmd/lotus-shed/consensus.go index f6bd7688f..197de56f9 100644 --- a/cmd/lotus-shed/consensus.go +++ b/cmd/lotus-shed/consensus.go @@ -84,7 +84,7 @@ var consensusCheckCmd = &cli.Command{ filePath := cctx.Args().First() var input *bufio.Reader - if cctx.Args().Len() == 0 { + if cctx.NArg() == 0 { input = bufio.NewReader(os.Stdin) } else { var err error diff --git a/cmd/lotus-shed/datastore.go b/cmd/lotus-shed/datastore.go index 7cdb2b1e6..cd650d122 100644 --- a/cmd/lotus-shed/datastore.go +++ b/cmd/lotus-shed/datastore.go @@ -171,7 +171,7 @@ var datastoreBackupStatCmd = &cli.Command{ Description: "validate and print info about datastore backup", ArgsUsage: "[file]", Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return xerrors.Errorf("expected 1 argument") } @@ -220,7 +220,7 @@ var datastoreBackupListCmd = &cli.Command{ }, ArgsUsage: "[file]", Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return xerrors.Errorf("expected 1 argument") } diff --git a/cmd/lotus-shed/export-car.go b/cmd/lotus-shed/export-car.go index 97e4fb6c6..b33b2b4ec 100644 --- a/cmd/lotus-shed/export-car.go +++ b/cmd/lotus-shed/export-car.go @@ -38,7 +38,7 @@ var exportCarCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 2 { + if cctx.NArg() != 2 { return lcli.ShowHelp(cctx, fmt.Errorf("must specify file name and object")) } diff --git a/cmd/lotus-shed/keyinfo.go b/cmd/lotus-shed/keyinfo.go index 135b5bc9d..373964dc6 100644 --- a/cmd/lotus-shed/keyinfo.go +++ b/cmd/lotus-shed/keyinfo.go @@ -149,7 +149,7 @@ var keyinfoImportCmd = &cli.Command{ flagRepo := cctx.String("repo") var input io.Reader - if cctx.Args().Len() == 0 { + if cctx.NArg() == 0 { input = os.Stdin } else { var err error @@ -261,7 +261,7 @@ var keyinfoInfoCmd = &cli.Command{ format := cctx.String("format") var input io.Reader - if cctx.Args().Len() == 0 { + if cctx.NArg() == 0 { input = os.Stdin } else { var err error diff --git a/cmd/lotus-shed/miner.go b/cmd/lotus-shed/miner.go index a23a44410..0d925336d 100644 --- a/cmd/lotus-shed/miner.go +++ b/cmd/lotus-shed/miner.go @@ -135,7 +135,7 @@ var minerCreateCmd = &cli.Command{ defer closer() ctx := lcli.ReqContext(cctx) - if cctx.Args().Len() != 4 { + if cctx.NArg() != 4 { return xerrors.Errorf("expected 4 args (sender owner worker sectorSize)") } @@ -273,7 +273,7 @@ var minerUnpackInfoCmd = &cli.Command{ Usage: "unpack miner info all dump", ArgsUsage: "[allinfo.txt] [dir]", Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 2 { + if cctx.NArg() != 2 { return xerrors.Errorf("expected 2 args") } diff --git a/cmd/lotus-shed/msg.go b/cmd/lotus-shed/msg.go index 49412948c..34b260961 100644 --- a/cmd/lotus-shed/msg.go +++ b/cmd/lotus-shed/msg.go @@ -27,7 +27,7 @@ var msgCmd = &cli.Command{ Usage: "Translate message between various formats", ArgsUsage: "Message in any form", Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return xerrors.Errorf("expected 1 argument") } diff --git a/cmd/lotus-shed/proofs.go b/cmd/lotus-shed/proofs.go index 65cf04918..752469778 100644 --- a/cmd/lotus-shed/proofs.go +++ b/cmd/lotus-shed/proofs.go @@ -42,7 +42,7 @@ var verifySealProofCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 3 { + if cctx.NArg() != 3 { return fmt.Errorf("must specify commR, commD, and proof to verify") } diff --git a/cmd/lotus-shed/rpc.go b/cmd/lotus-shed/rpc.go index b253304a1..82412e317 100644 --- a/cmd/lotus-shed/rpc.go +++ b/cmd/lotus-shed/rpc.go @@ -112,7 +112,7 @@ var rpcCmd = &cli.Command{ } if cctx.Args().Present() { - if cctx.Args().Len() > 2 { + if cctx.NArg() > 2 { return xerrors.Errorf("expected 1 or 2 arguments: method [params]") } diff --git a/cmd/lotus-shed/sectors.go b/cmd/lotus-shed/sectors.go index 0332b4ba3..39e17f045 100644 --- a/cmd/lotus-shed/sectors.go +++ b/cmd/lotus-shed/sectors.go @@ -64,7 +64,7 @@ var terminateSectorCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() < 1 { + if cctx.NArg() < 1 { return fmt.Errorf("at least one sector must be specified") } @@ -200,7 +200,7 @@ var terminateSectorPenaltyEstimationCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() < 1 { + if cctx.NArg() < 1 { return fmt.Errorf("at least one sector must be specified") } diff --git a/cmd/lotus-shed/signatures.go b/cmd/lotus-shed/signatures.go index 0f43503f6..d06ae56ea 100644 --- a/cmd/lotus-shed/signatures.go +++ b/cmd/lotus-shed/signatures.go @@ -31,7 +31,7 @@ var sigsVerifyBlsMsgsCmd = &cli.Command{ Description: "given a block, verifies the bls signature of the messages in the block", Usage: "", Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 1 { + if cctx.NArg() != 1 { return xerrors.Errorf("usage: ") } @@ -101,7 +101,7 @@ var sigsVerifyVoteCmd = &cli.Command{ Usage: " ", Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 3 { + if cctx.NArg() != 3 { return xerrors.Errorf("usage: verify-vote ") } diff --git a/cmd/lotus-shed/stateroot-stats.go b/cmd/lotus-shed/stateroot-stats.go index 7937b3ccd..f429c4e64 100644 --- a/cmd/lotus-shed/stateroot-stats.go +++ b/cmd/lotus-shed/stateroot-stats.go @@ -174,8 +174,8 @@ var staterootStatCmd = &cli.Command{ } outcap := 10 - if cctx.Args().Len() > outcap { - outcap = cctx.Args().Len() + if cctx.NArg() > outcap { + outcap = cctx.NArg() } if len(infos) < outcap { outcap = len(infos) diff --git a/cmd/lotus-shed/sync.go b/cmd/lotus-shed/sync.go index 397e76da3..915ef38d0 100644 --- a/cmd/lotus-shed/sync.go +++ b/cmd/lotus-shed/sync.go @@ -38,7 +38,7 @@ var syncValidateCmd = &cli.Command{ defer closer() ctx := lcli.ReqContext(cctx) - if cctx.Args().Len() < 1 { + if cctx.NArg() < 1 { fmt.Println("usage: ...") fmt.Println("At least one block cid must be provided") return nil @@ -75,7 +75,7 @@ var syncScrapePowerCmd = &cli.Command{ Usage: "given a height and a tipset, reports what percentage of mining power had a winning ticket between the tipset and height", ArgsUsage: "[height tipsetkey]", Action: func(cctx *cli.Context) error { - if cctx.Args().Len() < 1 { + if cctx.NArg() < 1 { fmt.Println("usage: [blockCid1 blockCid2...]") fmt.Println("Any CIDs passed after the height will be used as the tipset key") fmt.Println("If no block CIDs are provided, chain head will be used") @@ -90,7 +90,7 @@ var syncScrapePowerCmd = &cli.Command{ defer closer() ctx := lcli.ReqContext(cctx) - if cctx.Args().Len() < 1 { + if cctx.NArg() < 1 { fmt.Println("usage: ...") fmt.Println("At least one block cid must be provided") return nil diff --git a/cmd/lotus-shed/verifreg.go b/cmd/lotus-shed/verifreg.go index 2731ea11b..97392b2f0 100644 --- a/cmd/lotus-shed/verifreg.go +++ b/cmd/lotus-shed/verifreg.go @@ -46,7 +46,7 @@ var verifRegAddVerifierFromMsigCmd = &cli.Command{ Usage: "make a given account a verifier", ArgsUsage: " ", Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 3 { + if cctx.NArg() != 3 { return fmt.Errorf("must specify three arguments: sender, verifier, and allowance") } @@ -119,7 +119,7 @@ var verifRegAddVerifierFromAccountCmd = &cli.Command{ Usage: "make a given account a verifier", ArgsUsage: " ", Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 3 { + if cctx.NArg() != 3 { return fmt.Errorf("must specify three arguments: sender, verifier, and allowance") } @@ -201,7 +201,7 @@ var verifRegVerifyClientCmd = &cli.Command{ return err } - if cctx.Args().Len() != 2 { + if cctx.NArg() != 2 { return fmt.Errorf("must specify two arguments: address and allowance") } @@ -418,7 +418,7 @@ var verifRegRemoveVerifiedClientDataCapCmd = &cli.Command{ Usage: "Remove data cap from verified client", ArgsUsage: " ", Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 7 { + if cctx.NArg() != 7 { return fmt.Errorf("must specify seven arguments: sender, client, allowance to remove, verifier 1 address, verifier 1 signature, verifier 2 address, verifier 2 signature") } From fb0633453a90c6797bcb9a86dd28cce15dab89a7 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Wed, 14 Sep 2022 14:51:18 -0400 Subject: [PATCH 16/85] api, nodeApi to minerApi --- cmd/lotus-miner/actor.go | 38 +++++++------- cmd/lotus-miner/info_all.go | 4 +- cmd/lotus-miner/main.go | 4 +- cmd/lotus-miner/proving.go | 16 +++--- cmd/lotus-miner/sealing.go | 26 +++++----- cmd/lotus-miner/sectors.go | 100 ++++++++++++++++++------------------ cmd/lotus-miner/storage.go | 62 +++++++++++----------- cmd/lotus-shed/actor.go | 24 ++++----- cmd/lotus-shed/sectors.go | 8 +-- 9 files changed, 141 insertions(+), 141 deletions(-) diff --git a/cmd/lotus-miner/actor.go b/cmd/lotus-miner/actor.go index c1b86b59c..e4e4b4eae 100644 --- a/cmd/lotus-miner/actor.go +++ b/cmd/lotus-miner/actor.go @@ -81,7 +81,7 @@ var actorSetAddrsCmd = &cli.Command{ return fmt.Errorf("unset can only be used with no arguments") } - nodeAPI, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -112,7 +112,7 @@ var actorSetAddrsCmd = &cli.Command{ addrs = append(addrs, maddrNop2p.Bytes()) } - maddr, err := nodeAPI.ActorAddress(ctx) + maddr, err := minerApi.ActorAddress(ctx) if err != nil { return err } @@ -177,7 +177,7 @@ var actorSetPeeridCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - nodeAPI, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -196,7 +196,7 @@ var actorSetPeeridCmd = &cli.Command{ return fmt.Errorf("failed to parse input as a peerId: %w", err) } - maddr, err := nodeAPI.ActorAddress(ctx) + maddr, err := minerApi.ActorAddress(ctx) if err != nil { return err } @@ -258,7 +258,7 @@ var actorWithdrawCmd = &cli.Command{ amount = abi.TokenAmount(f) } - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -274,9 +274,9 @@ var actorWithdrawCmd = &cli.Command{ var res cid.Cid if cctx.IsSet("beneficiary") { - res, err = nodeApi.BeneficiaryWithdrawBalance(ctx, amount) + res, err = minerApi.BeneficiaryWithdrawBalance(ctx, amount) } else { - res, err = nodeApi.ActorWithdrawBalance(ctx, amount) + res, err = minerApi.ActorWithdrawBalance(ctx, amount) } if err != nil { return err @@ -326,7 +326,7 @@ var actorRepayDebtCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -340,7 +340,7 @@ var actorRepayDebtCmd = &cli.Command{ ctx := lcli.ReqContext(cctx) - maddr, err := nodeApi.ActorAddress(ctx) + maddr, err := minerApi.ActorAddress(ctx) if err != nil { return err } @@ -441,7 +441,7 @@ var actorControlList = &cli.Command{ color.NoColor = !cctx.Bool("color") } - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -473,7 +473,7 @@ var actorControlList = &cli.Command{ tablewriter.Col("balance"), ) - ac, err := nodeApi.ActorAddressConfig(ctx) + ac, err := minerApi.ActorAddressConfig(ctx) if err != nil { return err } @@ -610,7 +610,7 @@ var actorControlSet = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -624,7 +624,7 @@ var actorControlSet = &cli.Command{ ctx := lcli.ReqContext(cctx) - maddr, err := nodeApi.ActorAddress(ctx) + maddr, err := minerApi.ActorAddress(ctx) if err != nil { return err } @@ -826,7 +826,7 @@ var actorProposeChangeWorker = &cli.Command{ return fmt.Errorf("must pass address of new worker address") } - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -850,7 +850,7 @@ var actorProposeChangeWorker = &cli.Command{ return err } - maddr, err := nodeApi.ActorAddress(ctx) + maddr, err := minerApi.ActorAddress(ctx) if err != nil { return err } @@ -941,7 +941,7 @@ var actorConfirmChangeWorker = &cli.Command{ return fmt.Errorf("must pass address of new worker address") } - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -965,7 +965,7 @@ var actorConfirmChangeWorker = &cli.Command{ return err } - maddr, err := nodeApi.ActorAddress(ctx) + maddr, err := minerApi.ActorAddress(ctx) if err != nil { return err } @@ -1056,7 +1056,7 @@ var actorCompactAllocatedCmd = &cli.Command{ return fmt.Errorf("must pass address of new owner address") } - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -1070,7 +1070,7 @@ var actorCompactAllocatedCmd = &cli.Command{ ctx := lcli.ReqContext(cctx) - maddr, err := nodeApi.ActorAddress(ctx) + maddr, err := minerApi.ActorAddress(ctx) if err != nil { return err } diff --git a/cmd/lotus-miner/info_all.go b/cmd/lotus-miner/info_all.go index 38cc7fc05..bf6d7e4b9 100644 --- a/cmd/lotus-miner/info_all.go +++ b/cmd/lotus-miner/info_all.go @@ -16,7 +16,7 @@ var infoAllCmd = &cli.Command{ Name: "all", Usage: "dump all related miner info", Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -220,7 +220,7 @@ var infoAllCmd = &cli.Command{ // Very Very Verbose info fmt.Println("\n#: Per Sector Info") - list, err := nodeApi.SectorsList(ctx) + list, err := minerApi.SectorsList(ctx) if err != nil { fmt.Println("ERROR: ", err) } diff --git a/cmd/lotus-miner/main.go b/cmd/lotus-miner/main.go index 0462373ae..3cc796168 100644 --- a/cmd/lotus-miner/main.go +++ b/cmd/lotus-miner/main.go @@ -177,13 +177,13 @@ func getActorAddress(ctx context.Context, cctx *cli.Context) (maddr address.Addr return } - nodeAPI, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return address.Undef, err } defer closer() - maddr, err = nodeAPI.ActorAddress(ctx) + maddr, err = minerApi.ActorAddress(ctx) if err != nil { return maddr, xerrors.Errorf("getting actor address: %w", err) } diff --git a/cmd/lotus-miner/proving.go b/cmd/lotus-miner/proving.go index bbb744680..496e94c50 100644 --- a/cmd/lotus-miner/proving.go +++ b/cmd/lotus-miner/proving.go @@ -476,7 +476,7 @@ var provingCheckProvableCmd = &cli.Command{ } defer closer() - sapi, scloser, err := lcli.GetStorageMinerAPI(cctx) + minerApi, scloser, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -484,7 +484,7 @@ var provingCheckProvableCmd = &cli.Command{ ctx := lcli.ReqContext(cctx) - addr, err := sapi.ActorAddress(ctx) + addr, err := minerApi.ActorAddress(ctx) if err != nil { return err } @@ -510,7 +510,7 @@ var provingCheckProvableCmd = &cli.Command{ var filter map[abi.SectorID]struct{} if cctx.IsSet("storage-id") { - sl, err := sapi.StorageList(ctx) + sl, err := minerApi.StorageList(ctx) if err != nil { return err } @@ -582,7 +582,7 @@ var provingCheckProvableCmd = &cli.Command{ }) } - bad, err := sapi.CheckProvable(ctx, info.WindowPoStProofType, tocheck, cctx.Bool("slow")) + bad, err := minerApi.CheckProvable(ctx, info.WindowPoStProofType, tocheck, cctx.Bool("slow")) if err != nil { return err } @@ -625,7 +625,7 @@ It will not send any messages to the chain.`, return xerrors.Errorf("could not parse deadline index: %w", err) } - sapi, scloser, err := lcli.GetStorageMinerAPI(cctx) + minerApi, scloser, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -634,7 +634,7 @@ It will not send any messages to the chain.`, ctx := lcli.ReqContext(cctx) start := time.Now() - res, err := sapi.ComputeWindowPoSt(ctx, dlIdx, types.EmptyTSK) + res, err := minerApi.ComputeWindowPoSt(ctx, dlIdx, types.EmptyTSK) fmt.Printf("Took %s\n", time.Now().Sub(start)) if err != nil { return err @@ -675,7 +675,7 @@ var provingRecoverFaultsCmd = &cli.Command{ sectors = append(sectors, abi.SectorNumber(s)) } - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -689,7 +689,7 @@ var provingRecoverFaultsCmd = &cli.Command{ ctx := lcli.ReqContext(cctx) - msgs, err := nodeApi.RecoverFault(ctx, sectors) + msgs, err := minerApi.RecoverFault(ctx, sectors) if err != nil { return err } diff --git a/cmd/lotus-miner/sealing.go b/cmd/lotus-miner/sealing.go index 334266c79..adf3b9c2f 100644 --- a/cmd/lotus-miner/sealing.go +++ b/cmd/lotus-miner/sealing.go @@ -57,7 +57,7 @@ func workersCmd(sealing bool) *cli.Command { color.NoColor = !cctx.Bool("color") } - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -65,7 +65,7 @@ func workersCmd(sealing bool) *cli.Command { ctx := lcli.ReqContext(cctx) - stats, err := nodeApi.WorkerStats(ctx) + stats, err := minerApi.WorkerStats(ctx) if err != nil { return err } @@ -233,7 +233,7 @@ var sealingJobsCmd = &cli.Command{ color.NoColor = !cctx.Bool("color") } - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -241,7 +241,7 @@ var sealingJobsCmd = &cli.Command{ ctx := lcli.ReqContext(cctx) - jobs, err := nodeApi.WorkerJobs(ctx) + jobs, err := minerApi.WorkerJobs(ctx) if err != nil { return xerrors.Errorf("getting worker jobs: %w", err) } @@ -275,7 +275,7 @@ var sealingJobsCmd = &cli.Command{ workerHostnames := map[uuid.UUID]string{} - wst, err := nodeApi.WorkerStats(ctx) + wst, err := minerApi.WorkerStats(ctx) if err != nil { return xerrors.Errorf("getting worker stats: %w", err) } @@ -337,7 +337,7 @@ var sealingSchedDiagCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -345,7 +345,7 @@ var sealingSchedDiagCmd = &cli.Command{ ctx := lcli.ReqContext(cctx) - st, err := nodeApi.SealingSchedDiag(ctx, cctx.Bool("force-sched")) + st, err := minerApi.SealingSchedDiag(ctx, cctx.Bool("force-sched")) if err != nil { return err } @@ -376,7 +376,7 @@ var sealingAbortCmd = &cli.Command{ return xerrors.Errorf("expected 1 argument") } - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -385,14 +385,14 @@ var sealingAbortCmd = &cli.Command{ ctx := lcli.ReqContext(cctx) if cctx.Bool("sched") { - err = nodeApi.SealingRemoveRequest(ctx, uuid.Must(uuid.Parse(cctx.Args().First()))) + err = minerApi.SealingRemoveRequest(ctx, uuid.Must(uuid.Parse(cctx.Args().First()))) if err != nil { return xerrors.Errorf("Failed to removed the request with UUID %s: %w", cctx.Args().First(), err) } return nil } - jobs, err := nodeApi.WorkerJobs(ctx) + jobs, err := minerApi.WorkerJobs(ctx) if err != nil { return xerrors.Errorf("getting worker jobs: %w", err) } @@ -415,7 +415,7 @@ var sealingAbortCmd = &cli.Command{ fmt.Printf("aborting job %s, task %s, sector %d, running on host %s\n", job.ID.String(), job.Task.Short(), job.Sector.Number, job.Hostname) - return nodeApi.SealingAbort(ctx, job.ID) + return minerApi.SealingAbort(ctx, job.ID) }, } @@ -434,7 +434,7 @@ var sealingDataCidCmd = &cli.Command{ return xerrors.Errorf("expected 1 or 2 arguments") } - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -500,7 +500,7 @@ var sealingDataCidCmd = &cli.Command{ psize = padreader.PaddedSize(sz).Padded() } - pc, err := nodeApi.ComputeDataCid(ctx, psize.Unpadded(), r) + pc, err := minerApi.ComputeDataCid(ctx, psize.Unpadded(), r) if err != nil { return xerrors.Errorf("computing data CID: %w", err) } diff --git a/cmd/lotus-miner/sectors.go b/cmd/lotus-miner/sectors.go index 336a06f52..b35c38fd4 100644 --- a/cmd/lotus-miner/sectors.go +++ b/cmd/lotus-miner/sectors.go @@ -69,14 +69,14 @@ var sectorsPledgeCmd = &cli.Command{ Name: "pledge", Usage: "store random data in a sector", Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() ctx := lcli.ReqContext(cctx) - id, err := nodeApi.PledgeSector(ctx) + id, err := minerApi.PledgeSector(ctx) if err != nil { return err } @@ -113,7 +113,7 @@ var sectorsStatusCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -130,7 +130,7 @@ var sectorsStatusCmd = &cli.Command{ } onChainInfo := cctx.Bool("on-chain-info") - status, err := nodeApi.SectorsStatus(ctx, abi.SectorNumber(id), onChainInfo) + status, err := minerApi.SectorsStatus(ctx, abi.SectorNumber(id), onChainInfo) if err != nil { return err } @@ -318,7 +318,7 @@ var sectorsListCmd = &cli.Command{ color.NoColor = !cctx.Bool("color") } - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -359,16 +359,16 @@ var sectorsListCmd = &cli.Command{ } if len(states) == 0 { - list, err = nodeApi.SectorsList(ctx) + list, err = minerApi.SectorsList(ctx) } else { - list, err = nodeApi.SectorsListInStates(ctx, states) + list, err = minerApi.SectorsListInStates(ctx, states) } if err != nil { return err } - maddr, err := nodeApi.ActorAddress(ctx) + maddr, err := minerApi.ActorAddress(ctx) if err != nil { return err } @@ -418,7 +418,7 @@ var sectorsListCmd = &cli.Command{ fast := cctx.Bool("fast") for _, s := range list { - st, err := nodeApi.SectorsStatus(ctx, s, !fast) + st, err := minerApi.SectorsStatus(ctx, s, !fast) if err != nil { tw.Write(map[string]interface{}{ "ID": s, @@ -1372,7 +1372,7 @@ var sectorsTerminateCmd = &cli.Command{ if !cctx.Bool("really-do-it") { return xerrors.Errorf("pass --really-do-it to confirm this action") } - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -1387,7 +1387,7 @@ var sectorsTerminateCmd = &cli.Command{ return xerrors.Errorf("could not parse sector number: %w", err) } - return nodeApi.SectorTerminate(ctx, abi.SectorNumber(id)) + return minerApi.SectorTerminate(ctx, abi.SectorNumber(id)) }, } @@ -1395,14 +1395,14 @@ var sectorsTerminateFlushCmd = &cli.Command{ Name: "flush", Usage: "Send a terminate message if there are sectors queued for termination", Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() ctx := lcli.ReqContext(cctx) - mcid, err := nodeApi.SectorTerminateFlush(ctx) + mcid, err := minerApi.SectorTerminateFlush(ctx) if err != nil { return err } @@ -1421,7 +1421,7 @@ var sectorsTerminatePendingCmd = &cli.Command{ Name: "pending", Usage: "List sector numbers of sectors pending termination", Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -1433,12 +1433,12 @@ var sectorsTerminatePendingCmd = &cli.Command{ defer nCloser() ctx := lcli.ReqContext(cctx) - pending, err := nodeApi.SectorTerminatePending(ctx) + pending, err := minerAPI.SectorTerminatePending(ctx) if err != nil { return err } - maddr, err := nodeApi.ActorAddress(ctx) + maddr, err := minerAPI.ActorAddress(ctx) if err != nil { return err } @@ -1482,7 +1482,7 @@ var sectorsRemoveCmd = &cli.Command{ 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) + minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -1497,7 +1497,7 @@ var sectorsRemoveCmd = &cli.Command{ return xerrors.Errorf("could not parse sector number: %w", err) } - return nodeApi.SectorRemove(ctx, abi.SectorNumber(id)) + return minerAPI.SectorRemove(ctx, abi.SectorNumber(id)) }, } @@ -1510,7 +1510,7 @@ var sectorsSnapUpCmd = &cli.Command{ return lcli.ShowHelp(cctx, xerrors.Errorf("must pass sector number")) } - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -1535,7 +1535,7 @@ var sectorsSnapUpCmd = &cli.Command{ return xerrors.Errorf("could not parse sector number: %w", err) } - return nodeApi.SectorMarkForUpgrade(ctx, abi.SectorNumber(id), true) + return minerAPI.SectorMarkForUpgrade(ctx, abi.SectorNumber(id), true) }, } @@ -1560,7 +1560,7 @@ var sectorsSnapAbortCmd = &cli.Command{ return fmt.Errorf("--really-do-it must be specified for this action to have an effect; you have been warned") } - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -1572,7 +1572,7 @@ var sectorsSnapAbortCmd = &cli.Command{ return xerrors.Errorf("could not parse sector number: %w", err) } - return nodeApi.SectorAbortUpgrade(ctx, abi.SectorNumber(id)) + return minerAPI.SectorAbortUpgrade(ctx, abi.SectorNumber(id)) }, } @@ -1581,7 +1581,7 @@ var sectorsStartSealCmd = &cli.Command{ Usage: "Manually start sealing a sector (filling any unused space with junk)", ArgsUsage: "", Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -1596,7 +1596,7 @@ var sectorsStartSealCmd = &cli.Command{ return xerrors.Errorf("could not parse sector number: %w", err) } - return nodeApi.SectorStartSealing(ctx, abi.SectorNumber(id)) + return minerAPI.SectorStartSealing(ctx, abi.SectorNumber(id)) }, } @@ -1605,7 +1605,7 @@ var sectorsSealDelayCmd = &cli.Command{ Usage: "Set the time, in minutes, that a new sector waits for deals before sealing starts", ArgsUsage: "", Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -1622,7 +1622,7 @@ var sectorsSealDelayCmd = &cli.Command{ delay := hs * uint64(time.Minute) - return nodeApi.SectorSetSealDelay(ctx, time.Duration(delay)) + return minerAPI.SectorSetSealDelay(ctx, time.Duration(delay)) }, } @@ -1708,7 +1708,7 @@ var sectorsUpdateCmd = &cli.Command{ 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) + minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -1723,7 +1723,7 @@ var sectorsUpdateCmd = &cli.Command{ return xerrors.Errorf("could not parse sector number: %w", err) } - _, err = nodeApi.SectorsStatus(ctx, abi.SectorNumber(id), false) + _, err = minerAPI.SectorsStatus(ctx, abi.SectorNumber(id), false) if err != nil { return xerrors.Errorf("sector %d not found, could not change state", id) } @@ -1737,7 +1737,7 @@ var sectorsUpdateCmd = &cli.Command{ return nil } - return nodeApi.SectorsUpdate(ctx, abi.SectorNumber(id), api.SectorState(cctx.Args().Get(1))) + return minerAPI.SectorsUpdate(ctx, abi.SectorNumber(id), api.SectorState(cctx.Args().Get(1))) }, } @@ -1765,7 +1765,7 @@ var sectorsExpiredCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -1805,7 +1805,7 @@ var sectorsExpiredCmd = &cli.Command{ return xerrors.Errorf("getting lookback tipset: %w", err) } - maddr, err := nodeApi.ActorAddress(ctx) + maddr, err := minerAPI.ActorAddress(ctx) if err != nil { return xerrors.Errorf("getting actor address: %w", err) } @@ -1813,7 +1813,7 @@ var sectorsExpiredCmd = &cli.Command{ // toCheck is a working bitfield which will only contain terminated sectors toCheck := bitfield.New() { - sectors, err := nodeApi.SectorsList(ctx) + sectors, err := minerAPI.SectorsList(ctx) if err != nil { return xerrors.Errorf("getting sector list: %w", err) } @@ -1890,7 +1890,7 @@ var sectorsExpiredCmd = &cli.Command{ err = toCheck.ForEach(func(u uint64) error { s := abi.SectorNumber(u) - st, err := nodeApi.SectorsStatus(ctx, s, true) + st, err := minerAPI.SectorsStatus(ctx, s, true) if err != nil { fmt.Printf("%d:\tError getting status: %s\n", u, err) return nil @@ -1933,7 +1933,7 @@ var sectorsExpiredCmd = &cli.Command{ for _, number := range toRemove { fmt.Printf("Removing sector\t%s:\t", color.YellowString("%d", number)) - err := nodeApi.SectorRemove(ctx, number) + err := minerAPI.SectorRemove(ctx, number) if err != nil { color.Red("ERROR: %s\n", err.Error()) } else { @@ -1965,7 +1965,7 @@ var sectorsBatchingPendingCommit = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - api, closer, err := lcli.GetStorageMinerAPI(cctx) + minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -1973,7 +1973,7 @@ var sectorsBatchingPendingCommit = &cli.Command{ ctx := lcli.ReqContext(cctx) if cctx.Bool("publish-now") { - res, err := api.SectorCommitFlush(ctx) + res, err := minerAPI.SectorCommitFlush(ctx) if err != nil { return xerrors.Errorf("flush: %w", err) } @@ -2000,7 +2000,7 @@ var sectorsBatchingPendingCommit = &cli.Command{ return nil } - pending, err := api.SectorCommitPending(ctx) + pending, err := minerAPI.SectorCommitPending(ctx) if err != nil { return xerrors.Errorf("getting pending deals: %w", err) } @@ -2027,7 +2027,7 @@ var sectorsBatchingPendingPreCommit = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - api, closer, err := lcli.GetStorageMinerAPI(cctx) + minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -2035,7 +2035,7 @@ var sectorsBatchingPendingPreCommit = &cli.Command{ ctx := lcli.ReqContext(cctx) if cctx.Bool("publish-now") { - res, err := api.SectorPreCommitFlush(ctx) + res, err := minerAPI.SectorPreCommitFlush(ctx) if err != nil { return xerrors.Errorf("flush: %w", err) } @@ -2058,7 +2058,7 @@ var sectorsBatchingPendingPreCommit = &cli.Command{ return nil } - pending, err := api.SectorPreCommitPending(ctx) + pending, err := minerAPI.SectorPreCommitPending(ctx) if err != nil { return xerrors.Errorf("getting pending deals: %w", err) } @@ -2079,14 +2079,14 @@ var sectorsRefreshPieceMatchingCmd = &cli.Command{ Name: "match-pending-pieces", Usage: "force a refreshed match of pending pieces to open sectors without manually waiting for more deals", Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() ctx := lcli.ReqContext(cctx) - if err := nodeApi.SectorMatchPendingPiecesToOpenSectors(ctx); err != nil { + if err := minerAPI.SectorMatchPendingPiecesToOpenSectors(ctx); err != nil { return err } @@ -2219,14 +2219,14 @@ var sectorsNumbersInfoCmd = &cli.Command{ Name: "info", Usage: "view sector assigner state", Action: func(cctx *cli.Context) error { - api, closer, err := lcli.GetStorageMinerAPI(cctx) + minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() ctx := lcli.ReqContext(cctx) - am, err := api.SectorNumAssignerMeta(ctx) + am, err := minerAPI.SectorNumAssignerMeta(ctx) if err != nil { return err } @@ -2253,14 +2253,14 @@ var sectorsNumbersReservationsCmd = &cli.Command{ Name: "reservations", Usage: "list sector number reservations", Action: func(cctx *cli.Context) error { - api, closer, err := lcli.GetStorageMinerAPI(cctx) + minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() ctx := lcli.ReqContext(cctx) - rs, err := api.SectorNumReservations(ctx) + rs, err := minerAPI.SectorNumReservations(ctx) if err != nil { return err } @@ -2303,7 +2303,7 @@ var sectorsNumbersReserveCmd = &cli.Command{ }, ArgsUsage: "[reservation name] [reserved ranges]", Action: func(cctx *cli.Context) error { - api, closer, err := lcli.GetStorageMinerAPI(cctx) + minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -2319,7 +2319,7 @@ var sectorsNumbersReserveCmd = &cli.Command{ return xerrors.Errorf("parsing ranges: %w", err) } - return api.SectorNumReserve(ctx, cctx.Args().First(), bf, cctx.Bool("force")) + return minerAPI.SectorNumReserve(ctx, cctx.Args().First(), bf, cctx.Bool("force")) }, } @@ -2328,7 +2328,7 @@ var sectorsNumbersFreeCmd = &cli.Command{ Usage: "remove sector number reservations", ArgsUsage: "[reservation name]", Action: func(cctx *cli.Context) error { - api, closer, err := lcli.GetStorageMinerAPI(cctx) + minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -2339,6 +2339,6 @@ var sectorsNumbersFreeCmd = &cli.Command{ return xerrors.Errorf("expected 1 argument: [reservation name]") } - return api.SectorNumFree(ctx, cctx.Args().First()) + return minerAPI.SectorNumFree(ctx, cctx.Args().First()) }, } diff --git a/cmd/lotus-miner/storage.go b/cmd/lotus-miner/storage.go index cc5d94534..1200448b1 100644 --- a/cmd/lotus-miner/storage.go +++ b/cmd/lotus-miner/storage.go @@ -109,7 +109,7 @@ over time }, }, Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -172,7 +172,7 @@ over time } } - return nodeApi.StorageAddLocal(ctx, p) + return minerApi.StorageAddLocal(ctx, p) }, } @@ -186,7 +186,7 @@ var storageDetachCmd = &cli.Command{ }, ArgsUsage: "[path]", Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -206,7 +206,7 @@ var storageDetachCmd = &cli.Command{ return xerrors.Errorf("pass --really-do-it to execute the action") } - return nodeApi.StorageDetachLocal(ctx, p) + return minerApi.StorageDetachLocal(ctx, p) }, } @@ -228,7 +228,7 @@ var storageRedeclareCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -241,11 +241,11 @@ var storageRedeclareCmd = &cli.Command{ if cctx.IsSet("id") { id := storiface.ID(cctx.String("id")) - return nodeApi.StorageRedeclareLocal(ctx, &id, cctx.Bool("drop-missing")) + return minerApi.StorageRedeclareLocal(ctx, &id, cctx.Bool("drop-missing")) } if cctx.Bool("all") { - return nodeApi.StorageRedeclareLocal(ctx, nil, cctx.Bool("drop-missing")) + return minerApi.StorageRedeclareLocal(ctx, nil, cctx.Bool("drop-missing")) } return xerrors.Errorf("either --all or --id must be specified") @@ -270,19 +270,19 @@ var storageListCmd = &cli.Command{ color.NoColor = !cctx.Bool("color") } - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() ctx := lcli.ReqContext(cctx) - st, err := nodeApi.StorageList(ctx) + st, err := minerApi.StorageList(ctx) if err != nil { return err } - local, err := nodeApi.StorageLocal(ctx) + local, err := minerApi.StorageLocal(ctx) if err != nil { return err } @@ -295,7 +295,7 @@ var storageListCmd = &cli.Command{ sorted := make([]fsInfo, 0, len(st)) for id, decls := range st { - st, err := nodeApi.StorageStat(ctx, id) + st, err := minerApi.StorageStat(ctx, id) if err != nil { sorted = append(sorted, fsInfo{ID: id, sectors: decls}) continue @@ -325,7 +325,7 @@ var storageListCmd = &cli.Command{ fmt.Printf("%s:\n", s.ID) pingStart := time.Now() - st, err := nodeApi.StorageStat(ctx, s.ID) + st, err := minerApi.StorageStat(ctx, s.ID) if err != nil { fmt.Printf("\t%s: %s:\n", color.RedString("Error"), err) continue @@ -398,7 +398,7 @@ var storageListCmd = &cli.Command{ color.BlueString("Caches: %d", cnt[2]), types.SizeStr(types.NewInt(uint64(st.Reserved)))) - si, err := nodeApi.StorageInfo(ctx, s.ID) + si, err := minerApi.StorageInfo(ctx, s.ID) if err != nil { return err } @@ -469,14 +469,14 @@ var storageFindCmd = &cli.Command{ Usage: "find sector in the storage system", ArgsUsage: "[sector number]", Action: func(cctx *cli.Context) error { - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() ctx := lcli.ReqContext(cctx) - ma, err := nodeApi.ActorAddress(ctx) + ma, err := minerApi.ActorAddress(ctx) if err != nil { return err } @@ -500,27 +500,27 @@ var storageFindCmd = &cli.Command{ Number: abi.SectorNumber(snum), } - u, err := nodeApi.StorageFindSector(ctx, sid, storiface.FTUnsealed, 0, false) + u, err := minerApi.StorageFindSector(ctx, sid, storiface.FTUnsealed, 0, false) if err != nil { return xerrors.Errorf("finding unsealed: %w", err) } - s, err := nodeApi.StorageFindSector(ctx, sid, storiface.FTSealed, 0, false) + s, err := minerApi.StorageFindSector(ctx, sid, storiface.FTSealed, 0, false) if err != nil { return xerrors.Errorf("finding sealed: %w", err) } - c, err := nodeApi.StorageFindSector(ctx, sid, storiface.FTCache, 0, false) + c, err := minerApi.StorageFindSector(ctx, sid, storiface.FTCache, 0, false) if err != nil { return xerrors.Errorf("finding cache: %w", err) } - us, err := nodeApi.StorageFindSector(ctx, sid, storiface.FTUpdate, 0, false) + us, err := minerApi.StorageFindSector(ctx, sid, storiface.FTUpdate, 0, false) if err != nil { return xerrors.Errorf("finding sealed: %w", err) } - uc, err := nodeApi.StorageFindSector(ctx, sid, storiface.FTUpdateCache, 0, false) + uc, err := minerApi.StorageFindSector(ctx, sid, storiface.FTUpdateCache, 0, false) if err != nil { return xerrors.Errorf("finding cache: %w", err) } @@ -582,7 +582,7 @@ var storageFindCmd = &cli.Command{ sts.updatecache = true } - local, err := nodeApi.StorageLocal(ctx) + local, err := minerApi.StorageLocal(ctx) if err != nil { return err } @@ -644,7 +644,7 @@ var storageListSectorsCmd = &cli.Command{ color.NoColor = !cctx.Bool("color") } - nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -658,12 +658,12 @@ var storageListSectorsCmd = &cli.Command{ ctx := lcli.ReqContext(cctx) - sectors, err := nodeApi.SectorsList(ctx) + sectors, err := minerApi.SectorsList(ctx) if err != nil { return xerrors.Errorf("listing sectors: %w", err) } - maddr, err := nodeApi.ActorAddress(ctx) + maddr, err := minerApi.ActorAddress(ctx) if err != nil { return err } @@ -706,7 +706,7 @@ var storageListSectorsCmd = &cli.Command{ var list []entry for _, sector := range sectors { - st, err := nodeApi.SectorsStatus(ctx, sector, false) + st, err := minerApi.SectorsStatus(ctx, sector, false) if err != nil { return xerrors.Errorf("getting sector status for sector %d: %w", sector, err) } @@ -716,7 +716,7 @@ var storageListSectorsCmd = &cli.Command{ } for _, ft := range storiface.PathTypes { - si, err := nodeApi.StorageFindSector(ctx, sid(sector), ft, mi.SectorSize, false) + si, err := minerApi.StorageFindSector(ctx, sid(sector), ft, mi.SectorSize, false) if err != nil { return xerrors.Errorf("find sector %d: %w", sector, err) } @@ -869,7 +869,7 @@ var storageCleanupCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - api, closer, err := lcli.GetStorageMinerAPI(cctx) + minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } @@ -884,7 +884,7 @@ var storageCleanupCmd = &cli.Command{ ctx := lcli.ReqContext(cctx) if cctx.Bool("removed") { - if err := cleanupRemovedSectorData(ctx, api, napi); err != nil { + if err := cleanupRemovedSectorData(ctx, minerAPI, napi); err != nil { return err } } @@ -962,20 +962,20 @@ var storageLocks = &cli.Command{ Name: "locks", Usage: "show active sector locks", Action: func(cctx *cli.Context) error { - api, closer, err := lcli.GetStorageMinerAPI(cctx) + minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() ctx := lcli.ReqContext(cctx) - locks, err := api.StorageGetLocks(ctx) + locks, err := minerAPI.StorageGetLocks(ctx) if err != nil { return err } for _, lock := range locks.Locks { - st, err := api.SectorsStatus(ctx, lock.Sector.Number, false) + st, err := minerAPI.SectorsStatus(ctx, lock.Sector.Number, false) if err != nil { return xerrors.Errorf("getting sector status(%d): %w", lock.Sector.Number, err) } diff --git a/cmd/lotus-shed/actor.go b/cmd/lotus-shed/actor.go index c07b50ffb..53cb5e1ec 100644 --- a/cmd/lotus-shed/actor.go +++ b/cmd/lotus-shed/actor.go @@ -74,13 +74,13 @@ var actorWithdrawCmd = &cli.Command{ ctx := lcli.ReqContext(cctx) if maddr.Empty() { - minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() - maddr, err = minerAPI.ActorAddress(ctx) + maddr, err = minerApi.ActorAddress(ctx) if err != nil { return err } @@ -233,13 +233,13 @@ var actorSetOwnerCmd = &cli.Command{ } if maddr.Empty() { - minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() - maddr, err = minerAPI.ActorAddress(ctx) + maddr, err = minerApi.ActorAddress(ctx) if err != nil { return err } @@ -339,13 +339,13 @@ var actorControlList = &cli.Command{ ctx := lcli.ReqContext(cctx) if maddr.Empty() { - minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() - maddr, err = minerAPI.ActorAddress(ctx) + maddr, err = minerApi.ActorAddress(ctx) if err != nil { return err } @@ -448,13 +448,13 @@ var actorControlSet = &cli.Command{ ctx := lcli.ReqContext(cctx) if maddr.Empty() { - minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() - maddr, err = minerAPI.ActorAddress(ctx) + maddr, err = minerApi.ActorAddress(ctx) if err != nil { return err } @@ -590,13 +590,13 @@ var actorProposeChangeWorker = &cli.Command{ } if maddr.Empty() { - minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() - maddr, err = minerAPI.ActorAddress(ctx) + maddr, err = minerApi.ActorAddress(ctx) if err != nil { return err } @@ -720,13 +720,13 @@ var actorConfirmChangeWorker = &cli.Command{ } if maddr.Empty() { - minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx) + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() - maddr, err = minerAPI.ActorAddress(ctx) + maddr, err = minerApi.ActorAddress(ctx) if err != nil { return err } diff --git a/cmd/lotus-shed/sectors.go b/cmd/lotus-shed/sectors.go index 39e17f045..8fa7803d3 100644 --- a/cmd/lotus-shed/sectors.go +++ b/cmd/lotus-shed/sectors.go @@ -90,13 +90,13 @@ var terminateSectorCmd = &cli.Command{ ctx := lcli.ReqContext(cctx) if maddr.Empty() { - api, acloser, err := lcli.GetStorageMinerAPI(cctx) + minerApi, acloser, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer acloser() - maddr, err = api.ActorAddress(ctx) + maddr, err = minerApi.ActorAddress(ctx) if err != nil { return err } @@ -222,13 +222,13 @@ var terminateSectorPenaltyEstimationCmd = &cli.Command{ ctx := lcli.ReqContext(cctx) if maddr.Empty() { - api, acloser, err := lcli.GetStorageMinerAPI(cctx) + minerApi, acloser, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer acloser() - maddr, err = api.ActorAddress(ctx) + maddr, err = minerApi.ActorAddress(ctx) if err != nil { return err } From 3ca9b1e3312a9461164593fdd6e86802e51795f1 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Wed, 14 Sep 2022 14:53:11 -0400 Subject: [PATCH 17/85] wait.Receipt.ExitCode != 0 to wait.Receipt.ExitCode.IsError() --- cli/filplus.go | 2 +- cli/multisig.go | 30 +++++++++++++++--------------- cli/paych.go | 6 +++--- cli/wallet.go | 2 +- cmd/lotus-miner/actor.go | 10 +++++----- cmd/lotus-miner/proving.go | 2 +- cmd/lotus-miner/sectors.go | 2 +- cmd/lotus-shed/actor.go | 8 ++++---- cmd/lotus-shed/miner-multisig.go | 14 +++++++------- cmd/lotus-shed/miner.go | 4 ++-- cmd/lotus-shed/sectors.go | 2 +- cmd/lotus-shed/verifreg.go | 8 ++++---- paychmgr/simple.go | 4 ++-- 13 files changed, 47 insertions(+), 47 deletions(-) diff --git a/cli/filplus.go b/cli/filplus.go index 7ac4f0d58..5e19e5b04 100644 --- a/cli/filplus.go +++ b/cli/filplus.go @@ -120,7 +120,7 @@ var filplusVerifyClientCmd = &cli.Command{ return err } - if mwait.Receipt.ExitCode != 0 { + if mwait.Receipt.ExitCode.IsError() { return fmt.Errorf("failed to add verified client: %d", mwait.Receipt.ExitCode) } diff --git a/cli/multisig.go b/cli/multisig.go index 7a86c2d87..dc468a2cd 100644 --- a/cli/multisig.go +++ b/cli/multisig.go @@ -167,7 +167,7 @@ var msigCreateCmd = &cli.Command{ } // check it executed successfully - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { fmt.Fprintln(cctx.App.Writer, "actor creation failed!") return err } @@ -456,7 +456,7 @@ var msigProposeCmd = &cli.Command{ return err } - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { return fmt.Errorf("proposal returned exit %d", wait.Receipt.ExitCode) } @@ -605,7 +605,7 @@ var msigApproveCmd = &cli.Command{ return err } - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { return fmt.Errorf("approve returned exit %d", wait.Receipt.ExitCode) } @@ -730,7 +730,7 @@ var msigCancelCmd = &cli.Command{ return err } - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { return fmt.Errorf("cancel returned exit %d", wait.Receipt.ExitCode) } @@ -810,7 +810,7 @@ var msigRemoveProposeCmd = &cli.Command{ return err } - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { return fmt.Errorf("add proposal returned exit %d", wait.Receipt.ExitCode) } @@ -930,7 +930,7 @@ var msigAddProposeCmd = &cli.Command{ return err } - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { return fmt.Errorf("add proposal returned exit %d", wait.Receipt.ExitCode) } @@ -1021,7 +1021,7 @@ var msigAddApproveCmd = &cli.Command{ return err } - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { return fmt.Errorf("add approval returned exit %d", wait.Receipt.ExitCode) } @@ -1107,7 +1107,7 @@ var msigAddCancelCmd = &cli.Command{ return err } - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { return fmt.Errorf("add cancellation returned exit %d", wait.Receipt.ExitCode) } @@ -1188,7 +1188,7 @@ var msigSwapProposeCmd = &cli.Command{ return err } - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { return fmt.Errorf("swap proposal returned exit %d", wait.Receipt.ExitCode) } @@ -1279,7 +1279,7 @@ var msigSwapApproveCmd = &cli.Command{ return err } - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { return fmt.Errorf("swap approval returned exit %d", wait.Receipt.ExitCode) } @@ -1365,7 +1365,7 @@ var msigSwapCancelCmd = &cli.Command{ return err } - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { return fmt.Errorf("swap cancellation returned exit %d", wait.Receipt.ExitCode) } @@ -1461,7 +1461,7 @@ var msigLockProposeCmd = &cli.Command{ return err } - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { return fmt.Errorf("lock proposal returned exit %d", wait.Receipt.ExitCode) } @@ -1567,7 +1567,7 @@ var msigLockApproveCmd = &cli.Command{ return err } - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { return fmt.Errorf("lock approval returned exit %d", wait.Receipt.ExitCode) } @@ -1668,7 +1668,7 @@ var msigLockCancelCmd = &cli.Command{ return err } - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { return fmt.Errorf("lock cancellation returned exit %d", wait.Receipt.ExitCode) } @@ -1814,7 +1814,7 @@ var msigProposeThresholdCmd = &cli.Command{ return err } - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { return fmt.Errorf("change threshold proposal returned exit %d", wait.Receipt.ExitCode) } diff --git a/cli/paych.go b/cli/paych.go index 263aa7f34..09d4fcb11 100644 --- a/cli/paych.go +++ b/cli/paych.go @@ -286,7 +286,7 @@ var paychSettleCmd = &cli.Command{ if err != nil { return nil } - if mwait.Receipt.ExitCode != 0 { + if mwait.Receipt.ExitCode.IsError() { return fmt.Errorf("settle message execution failed (exit code %d)", mwait.Receipt.ExitCode) } @@ -326,7 +326,7 @@ var paychCloseCmd = &cli.Command{ if err != nil { return nil } - if mwait.Receipt.ExitCode != 0 { + if mwait.Receipt.ExitCode.IsError() { return fmt.Errorf("collect message execution failed (exit code %d)", mwait.Receipt.ExitCode) } @@ -634,7 +634,7 @@ var paychVoucherSubmitCmd = &cli.Command{ return err } - if mwait.Receipt.ExitCode != 0 { + if mwait.Receipt.ExitCode.IsError() { return fmt.Errorf("message execution failed (exit code %d)", mwait.Receipt.ExitCode) } diff --git a/cli/wallet.go b/cli/wallet.go index 4193138c3..56c1532f4 100644 --- a/cli/wallet.go +++ b/cli/wallet.go @@ -645,7 +645,7 @@ var walletMarketWithdraw = &cli.Command{ } // check it executed successfully - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { afmt.Println(cctx.App.Writer, "withdrawal failed!") return err } diff --git a/cmd/lotus-miner/actor.go b/cmd/lotus-miner/actor.go index e4e4b4eae..c06711170 100644 --- a/cmd/lotus-miner/actor.go +++ b/cmd/lotus-miner/actor.go @@ -290,7 +290,7 @@ var actorWithdrawCmd = &cli.Command{ return xerrors.Errorf("Timeout waiting for withdrawal message %s", wait.Message) } - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { return xerrors.Errorf("Failed to execute withdrawal message %s: %w", wait.Message, wait.Receipt.ExitCode.Error()) } @@ -799,7 +799,7 @@ var actorSetOwnerCmd = &cli.Command{ } // check it executed successfully - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { fmt.Println("owner change failed!") return err } @@ -905,7 +905,7 @@ var actorProposeChangeWorker = &cli.Command{ } // check it executed successfully - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { fmt.Fprintln(cctx.App.Writer, "Propose worker change failed!") return err } @@ -1011,7 +1011,7 @@ var actorConfirmChangeWorker = &cli.Command{ } // check it executed successfully - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { fmt.Fprintln(cctx.App.Writer, "Worker change failed!") return err } @@ -1170,7 +1170,7 @@ var actorCompactAllocatedCmd = &cli.Command{ } // check it executed successfully - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { fmt.Println("Propose owner change failed!") return err } diff --git a/cmd/lotus-miner/proving.go b/cmd/lotus-miner/proving.go index 496e94c50..1774a8e00 100644 --- a/cmd/lotus-miner/proving.go +++ b/cmd/lotus-miner/proving.go @@ -707,7 +707,7 @@ var provingRecoverFaultsCmd = &cli.Command{ return } - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { results <- xerrors.Errorf("Failed to execute message %s: %w", wait.Message, wait.Receipt.ExitCode.Error()) return } diff --git a/cmd/lotus-miner/sectors.go b/cmd/lotus-miner/sectors.go index b35c38fd4..c6adc85b5 100644 --- a/cmd/lotus-miner/sectors.go +++ b/cmd/lotus-miner/sectors.go @@ -2195,7 +2195,7 @@ var sectorsCompactPartitionsCmd = &cli.Command{ } // check it executed successfully - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { fmt.Println(cctx.App.Writer, "compact partitions failed!") return err } diff --git a/cmd/lotus-shed/actor.go b/cmd/lotus-shed/actor.go index 53cb5e1ec..d91c6de93 100644 --- a/cmd/lotus-shed/actor.go +++ b/cmd/lotus-shed/actor.go @@ -144,7 +144,7 @@ var actorWithdrawCmd = &cli.Command{ } // check it executed successfully - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { fmt.Println(cctx.App.Writer, "withdrawal failed!") return err } @@ -279,7 +279,7 @@ var actorSetOwnerCmd = &cli.Command{ } // check it executed successfully - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { fmt.Println("owner change failed!") return err } @@ -647,7 +647,7 @@ var actorProposeChangeWorker = &cli.Command{ } // check it executed successfully - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { fmt.Fprintln(cctx.App.Writer, "Propose worker change failed!") return err } @@ -768,7 +768,7 @@ var actorConfirmChangeWorker = &cli.Command{ } // check it executed successfully - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { fmt.Fprintln(cctx.App.Writer, "Worker change failed!") return err } diff --git a/cmd/lotus-shed/miner-multisig.go b/cmd/lotus-shed/miner-multisig.go index aba08113f..d58c634b0 100644 --- a/cmd/lotus-shed/miner-multisig.go +++ b/cmd/lotus-shed/miner-multisig.go @@ -101,7 +101,7 @@ var mmProposeWithdrawBalance = &cli.Command{ } // check it executed successfully - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { fmt.Fprintln(cctx.App.Writer, "Propose owner change tx failed!") return err } @@ -180,7 +180,7 @@ var mmApproveWithdrawBalance = &cli.Command{ } // check it executed successfully - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { fmt.Fprintln(cctx.App.Writer, "Approve owner change tx failed!") return err } @@ -261,7 +261,7 @@ var mmProposeChangeOwner = &cli.Command{ } // check it executed successfully - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { fmt.Fprintln(cctx.App.Writer, "Propose owner change tx failed!") return err } @@ -351,7 +351,7 @@ var mmApproveChangeOwner = &cli.Command{ } // check it executed successfully - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { fmt.Fprintln(cctx.App.Writer, "Approve owner change tx failed!") return err } @@ -448,7 +448,7 @@ var mmProposeChangeWorker = &cli.Command{ } // check it executed successfully - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { fmt.Fprintln(cctx.App.Writer, "Propose worker change tx failed!") return err } @@ -532,7 +532,7 @@ var mmConfirmChangeWorker = &cli.Command{ } // check it executed successfully - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { fmt.Fprintln(cctx.App.Writer, "Propose worker change tx failed!") return err } @@ -647,7 +647,7 @@ var mmProposeControlSet = &cli.Command{ } // check it executed successfully - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { fmt.Fprintln(cctx.App.Writer, "Propose worker change tx failed!") return err } diff --git a/cmd/lotus-shed/miner.go b/cmd/lotus-shed/miner.go index 0d925336d..9be1b7fdc 100644 --- a/cmd/lotus-shed/miner.go +++ b/cmd/lotus-shed/miner.go @@ -475,7 +475,7 @@ var sendInvalidWindowPoStCmd = &cli.Command{ } // check it executed successfully - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { fmt.Println(cctx.App.Writer, "Invalid PoST message failed!") return err } @@ -574,7 +574,7 @@ var generateAndSendConsensusFaultCmd = &cli.Command{ } // check it executed successfully - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { fmt.Println(cctx.App.Writer, "Report consensus fault failed!") return err } diff --git a/cmd/lotus-shed/sectors.go b/cmd/lotus-shed/sectors.go index 8fa7803d3..891110859 100644 --- a/cmd/lotus-shed/sectors.go +++ b/cmd/lotus-shed/sectors.go @@ -171,7 +171,7 @@ var terminateSectorCmd = &cli.Command{ return err } - if wait.Receipt.ExitCode != 0 { + if wait.Receipt.ExitCode.IsError() { return fmt.Errorf("terminate sectors message returned exit %d", wait.Receipt.ExitCode) } diff --git a/cmd/lotus-shed/verifreg.go b/cmd/lotus-shed/verifreg.go index 97392b2f0..c04dfa94c 100644 --- a/cmd/lotus-shed/verifreg.go +++ b/cmd/lotus-shed/verifreg.go @@ -104,7 +104,7 @@ var verifRegAddVerifierFromMsigCmd = &cli.Command{ return err } - if mwait.Receipt.ExitCode != 0 { + if mwait.Receipt.ExitCode.IsError() { return fmt.Errorf("failed to add verifier: %d", mwait.Receipt.ExitCode) } @@ -170,7 +170,7 @@ var verifRegAddVerifierFromAccountCmd = &cli.Command{ return err } - if mwait.Receipt.ExitCode != 0 { + if mwait.Receipt.ExitCode.IsError() { return fmt.Errorf("failed to add verified client: %d", mwait.Receipt.ExitCode) } @@ -246,7 +246,7 @@ var verifRegVerifyClientCmd = &cli.Command{ return err } - if mwait.Receipt.ExitCode != 0 { + if mwait.Receipt.ExitCode.IsError() { return fmt.Errorf("failed to add verified client: %d", mwait.Receipt.ExitCode) } @@ -555,7 +555,7 @@ var verifRegRemoveVerifiedClientDataCapCmd = &cli.Command{ return err } - if mwait.Receipt.ExitCode != 0 { + if mwait.Receipt.ExitCode.IsError() { return fmt.Errorf("failed to removed verified data cap: %d", mwait.Receipt.ExitCode) } diff --git a/paychmgr/simple.go b/paychmgr/simple.go index 6b2aee2aa..6d6b78a71 100644 --- a/paychmgr/simple.go +++ b/paychmgr/simple.go @@ -537,7 +537,7 @@ func (ca *channelAccessor) waitPaychCreateMsg(ctx context.Context, channelID str } // If channel creation failed - if mwait.Receipt.ExitCode != 0 { + if mwait.Receipt.ExitCode.IsError() { ca.lk.Lock() defer ca.lk.Unlock() @@ -651,7 +651,7 @@ func (ca *channelAccessor) waitAddFundsMsg(ctx context.Context, channelID string return err } - if mwait.Receipt.ExitCode != 0 { + if mwait.Receipt.ExitCode.IsError() { err := xerrors.Errorf("voucher channel creation failed: adding funds (exit code %d)", mwait.Receipt.ExitCode) log.Error(err) From 4f75e2041c13cdd8be8bdcd790449c2ec7f0091f Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Wed, 14 Sep 2022 15:38:15 -0400 Subject: [PATCH 18/85] Add helper function to print usage when wrong number of arguments are supplied --- cli/backup.go | 2 +- cli/chain.go | 6 +++--- cli/client.go | 8 ++++---- cli/client_retr.go | 6 +++--- cli/filplus.go | 4 ++-- cli/helper.go | 4 ++++ cli/mpool.go | 2 +- cli/multisig.go | 26 +++++++++++++------------- cli/net.go | 2 +- cli/paych.go | 22 +++++++++++----------- cli/send.go | 2 +- cli/state.go | 7 +++---- cmd/lotus-miner/actor.go | 2 +- cmd/lotus-miner/dagstore.go | 8 ++++---- cmd/lotus-miner/index_provider.go | 2 +- cmd/lotus-miner/init_restore.go | 2 +- cmd/lotus-miner/market.go | 6 +++--- cmd/lotus-miner/proving.go | 8 ++++---- cmd/lotus-miner/sealing.go | 4 ++-- cmd/lotus-miner/sectors.go | 16 ++++++++-------- cmd/lotus-shed/actor.go | 2 +- cmd/lotus-shed/chain.go | 2 +- cmd/lotus-shed/datastore.go | 7 ++++--- cmd/lotus-shed/diff.go | 2 +- cmd/lotus-shed/export-car.go | 2 +- cmd/lotus-shed/ledger.go | 2 +- cmd/lotus-shed/migrations.go | 3 ++- cmd/lotus-shed/miner-multisig.go | 4 ++-- cmd/lotus-shed/miner-peerid.go | 3 ++- cmd/lotus-shed/miner.go | 7 ++++--- cmd/lotus-shed/msg.go | 2 +- cmd/lotus-shed/proofs.go | 4 +++- cmd/lotus-shed/rpc.go | 2 +- cmd/lotus-shed/sectors.go | 4 ++-- cmd/lotus-shed/send-csv.go | 2 +- cmd/lotus-shed/signatures.go | 4 ++-- cmd/lotus-shed/sync.go | 8 ++------ cmd/lotus-shed/terminations.go | 3 ++- cmd/lotus-shed/verifreg.go | 8 ++++---- 39 files changed, 108 insertions(+), 102 deletions(-) diff --git a/cli/backup.go b/cli/backup.go index 3cb44718f..83234a423 100644 --- a/cli/backup.go +++ b/cli/backup.go @@ -115,7 +115,7 @@ this command must be within this base path`, ArgsUsage: "[backup file path]", Action: func(cctx *cli.Context) error { if cctx.NArg() != 1 { - return xerrors.Errorf("expected 1 argument") + return IncorrectNumArgs(cctx) } if cctx.Bool("offline") { diff --git a/cli/chain.go b/cli/chain.go index 80df5655b..814aebeed 100644 --- a/cli/chain.go +++ b/cli/chain.go @@ -947,7 +947,7 @@ var ChainBisectCmd = &cli.Command{ ctx := ReqContext(cctx) if cctx.NArg() < 4 { - return xerrors.New("need at least 4 args") + return IncorrectNumArgs(cctx) } start, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64) @@ -1313,7 +1313,7 @@ var chainDecodeParamsCmd = &cli.Command{ ctx := ReqContext(cctx) if cctx.NArg() != 3 { - return ShowHelp(cctx, fmt.Errorf("incorrect number of arguments")) + return IncorrectNumArgs(cctx) } to, err := address.NewFromString(cctx.Args().First()) @@ -1392,7 +1392,7 @@ var chainEncodeParamsCmd = &cli.Command{ afmt := NewAppFmt(cctx.App) if cctx.NArg() != 3 { - return ShowHelp(cctx, fmt.Errorf("incorrect number of arguments")) + return IncorrectNumArgs(cctx) } method, err := strconv.ParseInt(cctx.Args().Get(1), 10, 64) diff --git a/cli/client.go b/cli/client.go index 577a8e840..377505363 100644 --- a/cli/client.go +++ b/cli/client.go @@ -130,7 +130,7 @@ var clientImportCmd = &cli.Command{ ctx := ReqContext(cctx) if cctx.NArg() != 1 { - return xerrors.New("expected input path as the only arg") + return IncorrectNumArgs(cctx) } absPath, err := filepath.Abs(cctx.Args().First()) @@ -213,7 +213,7 @@ var clientCommPCmd = &cli.Command{ ctx := ReqContext(cctx) if cctx.NArg() != 1 { - return fmt.Errorf("usage: commP ") + return IncorrectNumArgs(cctx) } ret, err := api.ClientCalcCommP(ctx, cctx.Args().Get(0)) @@ -246,7 +246,7 @@ var clientCarGenCmd = &cli.Command{ ctx := ReqContext(cctx) if cctx.NArg() != 2 { - return fmt.Errorf("usage: generate-car ") + return IncorrectNumArgs(cctx) } ref := lapi.FileRef{ @@ -376,7 +376,7 @@ The minimum value is 518400 (6 months).`, afmt := NewAppFmt(cctx.App) if cctx.NArg() != 4 { - return xerrors.New(expectedArgsMsg) + return IncorrectNumArgs(cctx) } // [data, miner, price, dur] diff --git a/cli/client_retr.go b/cli/client_retr.go index f32e5d4e4..3fe656f15 100644 --- a/cli/client_retr.go +++ b/cli/client_retr.go @@ -289,7 +289,7 @@ Examples: }, retrFlagsCommon...), Action: func(cctx *cli.Context) error { if cctx.NArg() != 2 { - return ShowHelp(cctx, fmt.Errorf("incorrect number of arguments")) + return IncorrectNumArgs(cctx) } if cctx.Bool("car-export-merkle-proof") { @@ -405,7 +405,7 @@ var clientRetrieveCatCmd = &cli.Command{ }, retrFlagsCommon...), Action: func(cctx *cli.Context) error { if cctx.NArg() != 1 { - return ShowHelp(cctx, fmt.Errorf("incorrect number of arguments")) + return IncorrectNumArgs(cctx) } ainfo, err := GetAPIInfo(cctx, repo.FullNode) @@ -484,7 +484,7 @@ var clientRetrieveLsCmd = &cli.Command{ }, retrFlagsCommon...), Action: func(cctx *cli.Context) error { if cctx.NArg() != 1 { - return ShowHelp(cctx, fmt.Errorf("incorrect number of arguments")) + return IncorrectNumArgs(cctx) } ainfo, err := GetAPIInfo(cctx, repo.FullNode) diff --git a/cli/filplus.go b/cli/filplus.go index 5e19e5b04..d74171dd6 100644 --- a/cli/filplus.go +++ b/cli/filplus.go @@ -62,7 +62,7 @@ var filplusVerifyClientCmd = &cli.Command{ } if cctx.NArg() != 2 { - return fmt.Errorf("must specify two arguments: address and allowance") + return IncorrectNumArgs(cctx) } target, err := address.NewFromString(cctx.Args().Get(0)) @@ -290,7 +290,7 @@ var filplusSignRemoveDataCapProposal = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 3 { - return fmt.Errorf("must specify three arguments: notary address, client address, and allowance to remove") + return IncorrectNumArgs(cctx) } api, closer, err := GetFullNodeAPI(cctx) diff --git a/cli/helper.go b/cli/helper.go index da236bcae..c4a61397c 100644 --- a/cli/helper.go +++ b/cli/helper.go @@ -31,6 +31,10 @@ func ShowHelp(cctx *ufcli.Context, err error) error { return &PrintHelpErr{Err: err, Ctx: cctx} } +func IncorrectNumArgs(cctx *ufcli.Context) error { + return ShowHelp(cctx, fmt.Errorf("incorrect number of arguments, got %d", cctx.NArg())) +} + func RunApp(app *ufcli.App) { if err := app.Run(os.Args); err != nil { if os.Getenv("LOTUS_DEV") != "" { diff --git a/cli/mpool.go b/cli/mpool.go index c8003092d..e098806cb 100644 --- a/cli/mpool.go +++ b/cli/mpool.go @@ -611,7 +611,7 @@ var MpoolConfig = &cli.Command{ ArgsUsage: "[new-config]", Action: func(cctx *cli.Context) error { if cctx.NArg() > 1 { - return cli.ShowCommandHelp(cctx, cctx.Command.Name) + return IncorrectNumArgs(cctx) } afmt := NewAppFmt(cctx.App) diff --git a/cli/multisig.go b/cli/multisig.go index dc468a2cd..1dd7d01b8 100644 --- a/cli/multisig.go +++ b/cli/multisig.go @@ -89,7 +89,7 @@ var msigCreateCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() < 1 { - return ShowHelp(cctx, fmt.Errorf("multisigs must have at least one signer")) + return IncorrectNumArgs(cctx) } srv, err := GetFullNodeServices(cctx) @@ -754,7 +754,7 @@ var msigRemoveProposeCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 2 { - return ShowHelp(cctx, fmt.Errorf("must pass multisig address and signer address")) + return IncorrectNumArgs(cctx) } srv, err := GetFullNodeServices(cctx) @@ -841,7 +841,7 @@ var msigAddProposeCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 2 { - return ShowHelp(cctx, fmt.Errorf("must pass multisig address and signer address")) + return IncorrectNumArgs(cctx) } srv, err := GetFullNodeServices(cctx) @@ -950,7 +950,7 @@ var msigAddApproveCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 5 { - return ShowHelp(cctx, fmt.Errorf("must pass multisig address, proposer address, transaction id, new signer address, whether to increase threshold")) + return IncorrectNumArgs(cctx) } srv, err := GetFullNodeServices(cctx) @@ -1041,7 +1041,7 @@ var msigAddCancelCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 4 { - return ShowHelp(cctx, fmt.Errorf("must pass multisig address, transaction id, new signer address, whether to increase threshold")) + return IncorrectNumArgs(cctx) } srv, err := GetFullNodeServices(cctx) @@ -1127,7 +1127,7 @@ var msigSwapProposeCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 3 { - return ShowHelp(cctx, fmt.Errorf("must pass multisig address, old signer address, new signer address")) + return IncorrectNumArgs(cctx) } srv, err := GetFullNodeServices(cctx) @@ -1208,7 +1208,7 @@ var msigSwapApproveCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 5 { - return ShowHelp(cctx, fmt.Errorf("must pass multisig address, proposer address, transaction id, old signer address, new signer address")) + return IncorrectNumArgs(cctx) } srv, err := GetFullNodeServices(cctx) @@ -1299,7 +1299,7 @@ var msigSwapCancelCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 4 { - return ShowHelp(cctx, fmt.Errorf("must pass multisig address, transaction id, old signer address, new signer address")) + return IncorrectNumArgs(cctx) } srv, err := GetFullNodeServices(cctx) @@ -1385,7 +1385,7 @@ var msigLockProposeCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 4 { - return ShowHelp(cctx, fmt.Errorf("must pass multisig address, start epoch, unlock duration, and amount")) + return IncorrectNumArgs(cctx) } srv, err := GetFullNodeServices(cctx) @@ -1481,7 +1481,7 @@ var msigLockApproveCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 6 { - return ShowHelp(cctx, fmt.Errorf("must pass multisig address, proposer address, tx id, start epoch, unlock duration, and amount")) + return IncorrectNumArgs(cctx) } srv, err := GetFullNodeServices(cctx) @@ -1587,7 +1587,7 @@ var msigLockCancelCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 5 { - return ShowHelp(cctx, fmt.Errorf("must pass multisig address, tx id, start epoch, unlock duration, and amount")) + return IncorrectNumArgs(cctx) } srv, err := GetFullNodeServices(cctx) @@ -1694,7 +1694,7 @@ var msigVestedCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 1 { - return ShowHelp(cctx, fmt.Errorf("must pass multisig address")) + return IncorrectNumArgs(cctx) } api, closer, err := GetFullNodeAPI(cctx) @@ -1750,7 +1750,7 @@ var msigProposeThresholdCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 2 { - return ShowHelp(cctx, fmt.Errorf("must pass multisig address and new threshold value")) + return IncorrectNumArgs(cctx) } srv, err := GetFullNodeServices(cctx) diff --git a/cli/net.go b/cli/net.go index c1ca1ce18..b0615e0b1 100644 --- a/cli/net.go +++ b/cli/net.go @@ -142,7 +142,7 @@ var NetPing = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 1 { - return xerrors.Errorf("please provide a peerID") + return IncorrectNumArgs(cctx) } api, closer, err := GetAPI(cctx) diff --git a/cli/paych.go b/cli/paych.go index 09d4fcb11..1067d0913 100644 --- a/cli/paych.go +++ b/cli/paych.go @@ -51,7 +51,7 @@ var paychAddFundsCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 3 { - return ShowHelp(cctx, fmt.Errorf("must pass three arguments: ")) + return IncorrectNumArgs(cctx) } from, err := address.NewFromString(cctx.Args().Get(0)) @@ -113,7 +113,7 @@ var paychStatusByFromToCmd = &cli.Command{ ArgsUsage: "[fromAddress toAddress]", Action: func(cctx *cli.Context) error { if cctx.NArg() != 2 { - return ShowHelp(cctx, fmt.Errorf("must pass two arguments: ")) + return IncorrectNumArgs(cctx) } ctx := ReqContext(cctx) @@ -149,7 +149,7 @@ var paychStatusCmd = &cli.Command{ ArgsUsage: "[channelAddress]", Action: func(cctx *cli.Context) error { if cctx.NArg() != 1 { - return ShowHelp(cctx, fmt.Errorf("must pass an argument: ")) + return IncorrectNumArgs(cctx) } ctx := ReqContext(cctx) @@ -261,7 +261,7 @@ var paychSettleCmd = &cli.Command{ ArgsUsage: "[channelAddress]", Action: func(cctx *cli.Context) error { if cctx.NArg() != 1 { - return fmt.Errorf("must pass payment channel address") + return IncorrectNumArgs(cctx) } ch, err := address.NewFromString(cctx.Args().Get(0)) @@ -301,7 +301,7 @@ var paychCloseCmd = &cli.Command{ ArgsUsage: "[channelAddress]", Action: func(cctx *cli.Context) error { if cctx.NArg() != 1 { - return fmt.Errorf("must pass payment channel address") + return IncorrectNumArgs(cctx) } ch, err := address.NewFromString(cctx.Args().Get(0)) @@ -361,7 +361,7 @@ var paychVoucherCreateCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 2 { - return ShowHelp(cctx, fmt.Errorf("must pass two arguments: ")) + return IncorrectNumArgs(cctx) } ch, err := address.NewFromString(cctx.Args().Get(0)) @@ -409,7 +409,7 @@ var paychVoucherCheckCmd = &cli.Command{ ArgsUsage: "[channelAddress voucher]", Action: func(cctx *cli.Context) error { if cctx.NArg() != 2 { - return ShowHelp(cctx, fmt.Errorf("must pass payment channel address and voucher to validate")) + return IncorrectNumArgs(cctx) } ch, err := address.NewFromString(cctx.Args().Get(0)) @@ -445,7 +445,7 @@ var paychVoucherAddCmd = &cli.Command{ ArgsUsage: "[channelAddress voucher]", Action: func(cctx *cli.Context) error { if cctx.NArg() != 2 { - return ShowHelp(cctx, fmt.Errorf("must pass payment channel address and voucher")) + return IncorrectNumArgs(cctx) } ch, err := address.NewFromString(cctx.Args().Get(0)) @@ -487,7 +487,7 @@ var paychVoucherListCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 1 { - return ShowHelp(cctx, fmt.Errorf("must pass payment channel address")) + return IncorrectNumArgs(cctx) } ch, err := address.NewFromString(cctx.Args().Get(0)) @@ -532,7 +532,7 @@ var paychVoucherBestSpendableCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 1 { - return ShowHelp(cctx, fmt.Errorf("must pass payment channel address")) + return IncorrectNumArgs(cctx) } ch, err := address.NewFromString(cctx.Args().Get(0)) @@ -603,7 +603,7 @@ var paychVoucherSubmitCmd = &cli.Command{ ArgsUsage: "[channelAddress voucher]", Action: func(cctx *cli.Context) error { if cctx.NArg() != 2 { - return ShowHelp(cctx, fmt.Errorf("must pass payment channel address and voucher")) + return IncorrectNumArgs(cctx) } ch, err := address.NewFromString(cctx.Args().Get(0)) diff --git a/cli/send.go b/cli/send.go index b2471e7cd..4268f8eb2 100644 --- a/cli/send.go +++ b/cli/send.go @@ -68,7 +68,7 @@ var sendCmd = &cli.Command{ } if cctx.NArg() != 2 { - return ShowHelp(cctx, fmt.Errorf("'send' expects two arguments, target and amount")) + return IncorrectNumArgs(cctx) } srv, err := GetFullNodeServices(cctx) diff --git a/cli/state.go b/cli/state.go index e4afc6915..a7e195b1c 100644 --- a/cli/state.go +++ b/cli/state.go @@ -505,8 +505,7 @@ var StateReplayCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 1 { - fmt.Println("must provide cid of message to replay") - return nil + return IncorrectNumArgs(cctx) } mcid, err := cid.Decode(cctx.Args().First()) @@ -1581,7 +1580,7 @@ var StateCallCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() < 2 { - return fmt.Errorf("must specify at least actor and method to invoke") + return ShowHelp(cctx, fmt.Errorf("must specify at least actor and method to invoke")) } api, closer, err := GetFullNodeAPI(cctx) @@ -1744,7 +1743,7 @@ var StateSectorCmd = &cli.Command{ ctx := ReqContext(cctx) if cctx.NArg() != 2 { - return xerrors.Errorf("expected 2 params: minerAddress and sectorNumber") + return IncorrectNumArgs(cctx) } ts, err := LoadTipSet(ctx, cctx, api) diff --git a/cmd/lotus-miner/actor.go b/cmd/lotus-miner/actor.go index c06711170..aad110414 100644 --- a/cmd/lotus-miner/actor.go +++ b/cmd/lotus-miner/actor.go @@ -729,7 +729,7 @@ var actorSetOwnerCmd = &cli.Command{ } if cctx.NArg() != 2 { - return fmt.Errorf("must pass new owner address and sender address") + return lcli.IncorrectNumArgs(cctx) } api, acloser, err := lcli.GetFullNodeAPI(cctx) diff --git a/cmd/lotus-miner/dagstore.go b/cmd/lotus-miner/dagstore.go index 37d048825..519b43cc7 100644 --- a/cmd/lotus-miner/dagstore.go +++ b/cmd/lotus-miner/dagstore.go @@ -77,7 +77,7 @@ var dagstoreRegisterShardCmd = &cli.Command{ } if cctx.NArg() != 1 { - return fmt.Errorf("must provide a single shard key") + return lcli.IncorrectNumArgs(cctx) } marketsAPI, closer, err := lcli.GetMarketsAPI(cctx) @@ -116,7 +116,7 @@ var dagstoreInitializeShardCmd = &cli.Command{ } if cctx.NArg() != 1 { - return fmt.Errorf("must provide a single shard key") + return lcli.IncorrectNumArgs(cctx) } marketsApi, closer, err := lcli.GetMarketsAPI(cctx) @@ -148,7 +148,7 @@ var dagstoreRecoverShardCmd = &cli.Command{ } if cctx.NArg() != 1 { - return fmt.Errorf("must provide a single shard key") + return lcli.IncorrectNumArgs(cctx) } marketsApi, closer, err := lcli.GetMarketsAPI(cctx) @@ -330,7 +330,7 @@ var dagstoreLookupPiecesCmd = &cli.Command{ } if cctx.NArg() != 1 { - return fmt.Errorf("must provide a CID") + return lcli.IncorrectNumArgs(cctx) } cidStr := cctx.Args().First() diff --git a/cmd/lotus-miner/index_provider.go b/cmd/lotus-miner/index_provider.go index 1e4b69d86..4ed14549d 100644 --- a/cmd/lotus-miner/index_provider.go +++ b/cmd/lotus-miner/index_provider.go @@ -36,7 +36,7 @@ var indexProvAnnounceCmd = &cli.Command{ } if cctx.NArg() != 1 { - return fmt.Errorf("must provide the deal proposal CID") + return lcli.IncorrectNumArgs(cctx) } proposalCidStr := cctx.Args().First() diff --git a/cmd/lotus-miner/init_restore.go b/cmd/lotus-miner/init_restore.go index 9d7f977b6..3d179f3ba 100644 --- a/cmd/lotus-miner/init_restore.go +++ b/cmd/lotus-miner/init_restore.go @@ -97,7 +97,7 @@ var restoreCmd = &cli.Command{ func restore(ctx context.Context, cctx *cli.Context, targetPath string, strConfig *paths.StorageConfig, manageConfig func(*config.StorageMiner) error, after func(api lapi.FullNode, addr address.Address, peerid peer.ID, mi api.MinerInfo) error) error { if cctx.NArg() != 1 { - return xerrors.Errorf("expected 1 argument") + return lcli.IncorrectNumArgs(cctx) } log.Info("Trying to connect to full node RPC") diff --git a/cmd/lotus-miner/market.go b/cmd/lotus-miner/market.go index 78b68214e..706e49236 100644 --- a/cmd/lotus-miner/market.go +++ b/cmd/lotus-miner/market.go @@ -370,8 +370,8 @@ var dealsImportDataCmd = &cli.Command{ ctx := lcli.DaemonContext(cctx) - if cctx.NArg() < 2 { - return fmt.Errorf("must specify proposal CID and file path") + if cctx.NArg() != 2 { + return lcli.IncorrectNumArgs(cctx) } propCid, err := cid.Decode(cctx.Args().Get(0)) @@ -618,7 +618,7 @@ var setSealDurationCmd = &cli.Command{ defer closer() ctx := lcli.ReqContext(cctx) if cctx.NArg() != 1 { - return xerrors.Errorf("must pass duration in minutes") + return lcli.IncorrectNumArgs(cctx) } hs, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64) diff --git a/cmd/lotus-miner/proving.go b/cmd/lotus-miner/proving.go index 1774a8e00..6f6fd6635 100644 --- a/cmd/lotus-miner/proving.go +++ b/cmd/lotus-miner/proving.go @@ -315,7 +315,7 @@ var provingDeadlineInfoCmd = &cli.Command{ Action: func(cctx *cli.Context) error { if cctx.NArg() != 1 { - return xerrors.Errorf("must pass deadline index") + return lcli.IncorrectNumArgs(cctx) } dlIdx, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64) @@ -462,7 +462,7 @@ var provingCheckProvableCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 1 { - return xerrors.Errorf("must pass deadline index") + return lcli.IncorrectNumArgs(cctx) } dlIdx, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64) @@ -617,7 +617,7 @@ It will not send any messages to the chain.`, ArgsUsage: "[deadline index]", Action: func(cctx *cli.Context) error { if cctx.NArg() != 1 { - return xerrors.Errorf("must pass deadline index") + return lcli.IncorrectNumArgs(cctx) } dlIdx, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64) @@ -662,7 +662,7 @@ var provingRecoverFaultsCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() < 1 { - return xerrors.Errorf("must pass at least 1 sector number") + return lcli.ShowHelp(cctx, xerrors.Errorf("must pass at least 1 sector number")) } arglist := cctx.Args().Slice() diff --git a/cmd/lotus-miner/sealing.go b/cmd/lotus-miner/sealing.go index adf3b9c2f..34c84772d 100644 --- a/cmd/lotus-miner/sealing.go +++ b/cmd/lotus-miner/sealing.go @@ -373,7 +373,7 @@ var sealingAbortCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 1 { - return xerrors.Errorf("expected 1 argument") + return lcli.IncorrectNumArgs(cctx) } minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) @@ -431,7 +431,7 @@ var sealingDataCidCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() < 1 || cctx.NArg() > 2 { - return xerrors.Errorf("expected 1 or 2 arguments") + return lcli.ShowHelp(cctx, xerrors.Errorf("expected 1 or 2 arguments")) } minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) diff --git a/cmd/lotus-miner/sectors.go b/cmd/lotus-miner/sectors.go index c6adc85b5..bcd60acf2 100644 --- a/cmd/lotus-miner/sectors.go +++ b/cmd/lotus-miner/sectors.go @@ -1379,7 +1379,7 @@ var sectorsTerminateCmd = &cli.Command{ defer closer() ctx := lcli.ReqContext(cctx) if cctx.NArg() != 1 { - return xerrors.Errorf("must pass sector number") + return lcli.IncorrectNumArgs(cctx) } id, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64) @@ -1489,7 +1489,7 @@ var sectorsRemoveCmd = &cli.Command{ defer closer() ctx := lcli.ReqContext(cctx) if cctx.NArg() != 1 { - return xerrors.Errorf("must pass sector number") + return lcli.IncorrectNumArgs(cctx) } id, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64) @@ -1507,7 +1507,7 @@ var sectorsSnapUpCmd = &cli.Command{ ArgsUsage: "", Action: func(cctx *cli.Context) error { if cctx.NArg() != 1 { - return lcli.ShowHelp(cctx, xerrors.Errorf("must pass sector number")) + return lcli.IncorrectNumArgs(cctx) } minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx) @@ -1588,7 +1588,7 @@ var sectorsStartSealCmd = &cli.Command{ defer closer() ctx := lcli.ReqContext(cctx) if cctx.NArg() != 1 { - return xerrors.Errorf("must pass sector number") + return lcli.IncorrectNumArgs(cctx) } id, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64) @@ -1612,7 +1612,7 @@ var sectorsSealDelayCmd = &cli.Command{ defer closer() ctx := lcli.ReqContext(cctx) if cctx.NArg() != 1 { - return xerrors.Errorf("must pass duration in minutes") + return lcli.IncorrectNumArgs(cctx) } hs, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64) @@ -1715,7 +1715,7 @@ var sectorsUpdateCmd = &cli.Command{ defer closer() ctx := lcli.ReqContext(cctx) if cctx.NArg() < 2 { - return xerrors.Errorf("must pass sector number and new state") + return lcli.ShowHelp(cctx, xerrors.Errorf("must pass sector number and new state")) } id, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64) @@ -2311,7 +2311,7 @@ var sectorsNumbersReserveCmd = &cli.Command{ ctx := lcli.ReqContext(cctx) if cctx.NArg() != 2 { - return xerrors.Errorf("expected 2 arguments: [reservation name] [reserved ranges]") + return lcli.IncorrectNumArgs(cctx) } bf, err := strle.HumanRangesToBitField(cctx.Args().Get(1)) @@ -2336,7 +2336,7 @@ var sectorsNumbersFreeCmd = &cli.Command{ ctx := lcli.ReqContext(cctx) if cctx.NArg() != 1 { - return xerrors.Errorf("expected 1 argument: [reservation name]") + return lcli.IncorrectNumArgs(cctx) } return minerAPI.SectorNumFree(ctx, cctx.Args().First()) diff --git a/cmd/lotus-shed/actor.go b/cmd/lotus-shed/actor.go index d91c6de93..9d1fd9d19 100644 --- a/cmd/lotus-shed/actor.go +++ b/cmd/lotus-shed/actor.go @@ -192,7 +192,7 @@ var actorSetOwnerCmd = &cli.Command{ } if cctx.NArg() != 2 { - return fmt.Errorf("must pass new owner address and sender address") + return lcli.IncorrectNumArgs(cctx) } var maddr address.Address diff --git a/cmd/lotus-shed/chain.go b/cmd/lotus-shed/chain.go index efbbd3b62..9eaa42795 100644 --- a/cmd/lotus-shed/chain.go +++ b/cmd/lotus-shed/chain.go @@ -56,7 +56,7 @@ var computeStateRangeCmd = &cli.Command{ ArgsUsage: "[START_TIPSET_REF] [END_TIPSET_REF]", Action: func(cctx *cli.Context) error { if cctx.NArg() != 2 { - return fmt.Errorf("expected two arguments: a start and an end tipset") + return lcli.IncorrectNumArgs(cctx) } api, closer, err := lcli.GetFullNodeAPI(cctx) diff --git a/cmd/lotus-shed/datastore.go b/cmd/lotus-shed/datastore.go index cd650d122..5614e34f6 100644 --- a/cmd/lotus-shed/datastore.go +++ b/cmd/lotus-shed/datastore.go @@ -20,6 +20,7 @@ import ( "go.uber.org/multierr" "golang.org/x/xerrors" + lcli "github.com/filecoin-project/lotus/cli" "github.com/filecoin-project/lotus/lib/backupds" "github.com/filecoin-project/lotus/node/repo" ) @@ -172,7 +173,7 @@ var datastoreBackupStatCmd = &cli.Command{ ArgsUsage: "[file]", Action: func(cctx *cli.Context) error { if cctx.NArg() != 1 { - return xerrors.Errorf("expected 1 argument") + return lcli.IncorrectNumArgs(cctx) } f, err := os.Open(cctx.Args().First()) @@ -221,7 +222,7 @@ var datastoreBackupListCmd = &cli.Command{ ArgsUsage: "[file]", Action: func(cctx *cli.Context) error { if cctx.NArg() != 1 { - return xerrors.Errorf("expected 1 argument") + return lcli.IncorrectNumArgs(cctx) } f, err := os.Open(cctx.Args().First()) @@ -308,7 +309,7 @@ var datastoreRewriteCmd = &cli.Command{ ArgsUsage: "source destination", Action: func(cctx *cli.Context) error { if cctx.NArg() != 2 { - return xerrors.Errorf("expected 2 arguments, got %d", cctx.NArg()) + return lcli.IncorrectNumArgs(cctx) } fromPath, err := homedir.Expand(cctx.Args().Get(0)) if err != nil { diff --git a/cmd/lotus-shed/diff.go b/cmd/lotus-shed/diff.go index 9fc6f7963..22b54981f 100644 --- a/cmd/lotus-shed/diff.go +++ b/cmd/lotus-shed/diff.go @@ -31,7 +31,7 @@ var diffStateTrees = &cli.Command{ ctx := lcli.ReqContext(cctx) if cctx.NArg() != 2 { - return xerrors.Errorf("expected two state-tree roots") + return lcli.IncorrectNumArgs(cctx) } argA := cctx.Args().Get(0) diff --git a/cmd/lotus-shed/export-car.go b/cmd/lotus-shed/export-car.go index b33b2b4ec..214b1d5df 100644 --- a/cmd/lotus-shed/export-car.go +++ b/cmd/lotus-shed/export-car.go @@ -39,7 +39,7 @@ var exportCarCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 2 { - return lcli.ShowHelp(cctx, fmt.Errorf("must specify file name and object")) + return lcli.IncorrectNumArgs(cctx) } outfile := cctx.Args().First() diff --git a/cmd/lotus-shed/ledger.go b/cmd/lotus-shed/ledger.go index 20de8dd89..d9a888d20 100644 --- a/cmd/lotus-shed/ledger.go +++ b/cmd/lotus-shed/ledger.go @@ -300,7 +300,7 @@ var ledgerNewAddressesCmd = &cli.Command{ ctx := lcli.ReqContext(cctx) if cctx.NArg() != 1 { - return fmt.Errorf("must pass account index") + return lcli.IncorrectNumArgs(cctx) } index, err := strconv.ParseUint(cctx.Args().First(), 10, 32) diff --git a/cmd/lotus-shed/migrations.go b/cmd/lotus-shed/migrations.go index 2c75edd74..2e291c947 100644 --- a/cmd/lotus-shed/migrations.go +++ b/cmd/lotus-shed/migrations.go @@ -16,6 +16,7 @@ import ( "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/vm" + lcli "github.com/filecoin-project/lotus/cli" "github.com/filecoin-project/lotus/node/repo" "github.com/filecoin-project/lotus/storage/sealer/ffiwrapper" ) @@ -34,7 +35,7 @@ var migrationsCmd = &cli.Command{ ctx := context.TODO() if cctx.NArg() != 1 { - return fmt.Errorf("must pass block cid") + return lcli.IncorrectNumArgs(cctx) } blkCid, err := cid.Decode(cctx.Args().First()) diff --git a/cmd/lotus-shed/miner-multisig.go b/cmd/lotus-shed/miner-multisig.go index d58c634b0..e4268a291 100644 --- a/cmd/lotus-shed/miner-multisig.go +++ b/cmd/lotus-shed/miner-multisig.go @@ -128,7 +128,7 @@ var mmApproveWithdrawBalance = &cli.Command{ ArgsUsage: "[amount txnId proposer]", Action: func(cctx *cli.Context) error { if cctx.NArg() != 3 { - return fmt.Errorf("must pass amount, txn Id, and proposer address") + return lcli.IncorrectNumArgs(cctx) } api, closer, err := lcli.GetFullNodeAPI(cctx) @@ -287,7 +287,7 @@ var mmApproveChangeOwner = &cli.Command{ ArgsUsage: "[newOwner txnId proposer]", Action: func(cctx *cli.Context) error { if cctx.NArg() != 3 { - return fmt.Errorf("must pass new owner address, txn Id, and proposer address") + return lcli.IncorrectNumArgs(cctx) } api, closer, err := lcli.GetFullNodeAPI(cctx) diff --git a/cmd/lotus-shed/miner-peerid.go b/cmd/lotus-shed/miner-peerid.go index 85f720a10..e43063797 100644 --- a/cmd/lotus-shed/miner-peerid.go +++ b/cmd/lotus-shed/miner-peerid.go @@ -20,6 +20,7 @@ import ( "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" + lcli "github.com/filecoin-project/lotus/cli" "github.com/filecoin-project/lotus/node/repo" ) @@ -35,7 +36,7 @@ var minerPeeridCmd = &cli.Command{ ctx := context.TODO() if cctx.NArg() != 2 { - return fmt.Errorf("must pass peer id and state root") + return lcli.IncorrectNumArgs(cctx) } pid, err := peer.Decode(cctx.Args().Get(0)) diff --git a/cmd/lotus-shed/miner.go b/cmd/lotus-shed/miner.go index 9be1b7fdc..348626fe1 100644 --- a/cmd/lotus-shed/miner.go +++ b/cmd/lotus-shed/miner.go @@ -136,7 +136,7 @@ var minerCreateCmd = &cli.Command{ ctx := lcli.ReqContext(cctx) if cctx.NArg() != 4 { - return xerrors.Errorf("expected 4 args (sender owner worker sectorSize)") + return lcli.IncorrectNumArgs(cctx) } sender, err := address.NewFromString(cctx.Args().First()) @@ -274,7 +274,7 @@ var minerUnpackInfoCmd = &cli.Command{ ArgsUsage: "[allinfo.txt] [dir]", Action: func(cctx *cli.Context) error { if cctx.NArg() != 2 { - return xerrors.Errorf("expected 2 args") + return lcli.IncorrectNumArgs(cctx) } src, err := homedir.Expand(cctx.Args().Get(0)) @@ -488,9 +488,10 @@ var generateAndSendConsensusFaultCmd = &cli.Command{ Name: "generate-and-send-consensus-fault", Usage: "Provided a block CID mined by the miner, will create another block at the same height, and send both block headers to generate a consensus fault.", Description: `Note: This is meant for testing purposes and should NOT be used on mainnet or you will be slashed`, + ArgsUsage: "blockCID", Action: func(cctx *cli.Context) error { if cctx.NArg() != 1 { - return xerrors.Errorf("expected 1 arg (blockCID)") + return lcli.IncorrectNumArgs(cctx) } blockCid, err := cid.Parse(cctx.Args().First()) diff --git a/cmd/lotus-shed/msg.go b/cmd/lotus-shed/msg.go index 34b260961..847b93d9f 100644 --- a/cmd/lotus-shed/msg.go +++ b/cmd/lotus-shed/msg.go @@ -28,7 +28,7 @@ var msgCmd = &cli.Command{ ArgsUsage: "Message in any form", Action: func(cctx *cli.Context) error { if cctx.NArg() != 1 { - return xerrors.Errorf("expected 1 argument") + return lcli.IncorrectNumArgs(cctx) } msg, err := messageFromString(cctx, cctx.Args().First()) diff --git a/cmd/lotus-shed/proofs.go b/cmd/lotus-shed/proofs.go index 752469778..1a16e2fdc 100644 --- a/cmd/lotus-shed/proofs.go +++ b/cmd/lotus-shed/proofs.go @@ -11,6 +11,8 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" prooftypes "github.com/filecoin-project/go-state-types/proof" + + lcli "github.com/filecoin-project/lotus/cli" ) var proofsCmd = &cli.Command{ @@ -43,7 +45,7 @@ var verifySealProofCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 3 { - return fmt.Errorf("must specify commR, commD, and proof to verify") + return lcli.IncorrectNumArgs(cctx) } commr, err := cid.Decode(cctx.Args().Get(0)) diff --git a/cmd/lotus-shed/rpc.go b/cmd/lotus-shed/rpc.go index 82412e317..3be269358 100644 --- a/cmd/lotus-shed/rpc.go +++ b/cmd/lotus-shed/rpc.go @@ -113,7 +113,7 @@ var rpcCmd = &cli.Command{ if cctx.Args().Present() { if cctx.NArg() > 2 { - return xerrors.Errorf("expected 1 or 2 arguments: method [params]") + return lcli.ShowHelp(cctx, xerrors.Errorf("expected 1 or 2 arguments: method [params]")) } params := cctx.Args().Get(1) diff --git a/cmd/lotus-shed/sectors.go b/cmd/lotus-shed/sectors.go index 891110859..52d07f5b6 100644 --- a/cmd/lotus-shed/sectors.go +++ b/cmd/lotus-shed/sectors.go @@ -65,7 +65,7 @@ var terminateSectorCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() < 1 { - return fmt.Errorf("at least one sector must be specified") + return lcli.ShowHelp(cctx, fmt.Errorf("at least one sector must be specified")) } var maddr address.Address @@ -201,7 +201,7 @@ var terminateSectorPenaltyEstimationCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { if cctx.NArg() < 1 { - return fmt.Errorf("at least one sector must be specified") + return lcli.ShowHelp(cctx, fmt.Errorf("at least one sector must be specified")) } var maddr address.Address diff --git a/cmd/lotus-shed/send-csv.go b/cmd/lotus-shed/send-csv.go index ce1c8b68a..17b62150f 100644 --- a/cmd/lotus-shed/send-csv.go +++ b/cmd/lotus-shed/send-csv.go @@ -34,7 +34,7 @@ var sendCsvCmd = &cli.Command{ ArgsUsage: "[csvfile]", Action: func(cctx *cli.Context) error { if cctx.NArg() != 1 { - return xerrors.New("must supply path to csv file") + return lcli.IncorrectNumArgs(cctx) } api, closer, err := lcli.GetFullNodeAPIV1(cctx) diff --git a/cmd/lotus-shed/signatures.go b/cmd/lotus-shed/signatures.go index d06ae56ea..536f8e82d 100644 --- a/cmd/lotus-shed/signatures.go +++ b/cmd/lotus-shed/signatures.go @@ -32,7 +32,7 @@ var sigsVerifyBlsMsgsCmd = &cli.Command{ Usage: "", Action: func(cctx *cli.Context) error { if cctx.NArg() != 1 { - return xerrors.Errorf("usage: ") + return lcli.IncorrectNumArgs(cctx) } api, closer, err := lcli.GetFullNodeAPI(cctx) @@ -102,7 +102,7 @@ var sigsVerifyVoteCmd = &cli.Command{ Action: func(cctx *cli.Context) error { if cctx.NArg() != 3 { - return xerrors.Errorf("usage: verify-vote ") + return lcli.IncorrectNumArgs(cctx) } fip, err := strconv.ParseInt(cctx.Args().First(), 10, 64) diff --git a/cmd/lotus-shed/sync.go b/cmd/lotus-shed/sync.go index 915ef38d0..eb11dea27 100644 --- a/cmd/lotus-shed/sync.go +++ b/cmd/lotus-shed/sync.go @@ -39,9 +39,7 @@ var syncValidateCmd = &cli.Command{ ctx := lcli.ReqContext(cctx) if cctx.NArg() < 1 { - fmt.Println("usage: ...") - fmt.Println("At least one block cid must be provided") - return nil + return lcli.ShowHelp(cctx, fmt.Errorf("at least one block cid must be provided")) } args := cctx.Args().Slice() @@ -91,9 +89,7 @@ var syncScrapePowerCmd = &cli.Command{ ctx := lcli.ReqContext(cctx) if cctx.NArg() < 1 { - fmt.Println("usage: ...") - fmt.Println("At least one block cid must be provided") - return nil + return lcli.ShowHelp(cctx, fmt.Errorf("at least one block cid must be provided")) } h, err := strconv.ParseInt(cctx.Args().Get(0), 10, 0) diff --git a/cmd/lotus-shed/terminations.go b/cmd/lotus-shed/terminations.go index 87855cec7..c5f35995a 100644 --- a/cmd/lotus-shed/terminations.go +++ b/cmd/lotus-shed/terminations.go @@ -23,6 +23,7 @@ import ( "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" + lcli "github.com/filecoin-project/lotus/cli" "github.com/filecoin-project/lotus/node/repo" ) @@ -40,7 +41,7 @@ var terminationsCmd = &cli.Command{ ctx := context.TODO() if cctx.NArg() != 2 { - return fmt.Errorf("must pass block cid && lookback period") + return lcli.IncorrectNumArgs(cctx) } blkCid, err := cid.Decode(cctx.Args().First()) diff --git a/cmd/lotus-shed/verifreg.go b/cmd/lotus-shed/verifreg.go index c04dfa94c..0fa905b5f 100644 --- a/cmd/lotus-shed/verifreg.go +++ b/cmd/lotus-shed/verifreg.go @@ -47,7 +47,7 @@ var verifRegAddVerifierFromMsigCmd = &cli.Command{ ArgsUsage: " ", Action: func(cctx *cli.Context) error { if cctx.NArg() != 3 { - return fmt.Errorf("must specify three arguments: sender, verifier, and allowance") + return lcli.IncorrectNumArgs(cctx) } sender, err := address.NewFromString(cctx.Args().Get(0)) @@ -120,7 +120,7 @@ var verifRegAddVerifierFromAccountCmd = &cli.Command{ ArgsUsage: " ", Action: func(cctx *cli.Context) error { if cctx.NArg() != 3 { - return fmt.Errorf("must specify three arguments: sender, verifier, and allowance") + return lcli.IncorrectNumArgs(cctx) } sender, err := address.NewFromString(cctx.Args().Get(0)) @@ -202,7 +202,7 @@ var verifRegVerifyClientCmd = &cli.Command{ } if cctx.NArg() != 2 { - return fmt.Errorf("must specify two arguments: address and allowance") + return lcli.IncorrectNumArgs(cctx) } target, err := address.NewFromString(cctx.Args().Get(0)) @@ -419,7 +419,7 @@ var verifRegRemoveVerifiedClientDataCapCmd = &cli.Command{ ArgsUsage: " ", Action: func(cctx *cli.Context) error { if cctx.NArg() != 7 { - return fmt.Errorf("must specify seven arguments: sender, client, allowance to remove, verifier 1 address, verifier 1 signature, verifier 2 address, verifier 2 signature") + return lcli.IncorrectNumArgs(cctx) } srv, err := lcli.GetFullNodeServices(cctx) From 84fd51ff50f6d1f641fa8a5a061683184415fdfa Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Wed, 14 Sep 2022 16:12:01 -0400 Subject: [PATCH 19/85] add cli commands for changing beneficiary --- cmd/lotus-miner/actor.go | 268 +++++++++++++++++++++++++++- cmd/lotus-shed/miner-multisig.go | 229 ++++++++++++++++++++++-- documentation/en/cli-lotus-miner.md | 52 ++++-- 3 files changed, 520 insertions(+), 29 deletions(-) diff --git a/cmd/lotus-miner/actor.go b/cmd/lotus-miner/actor.go index aad110414..b1f97d530 100644 --- a/cmd/lotus-miner/actor.go +++ b/cmd/lotus-miner/actor.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "os" + "strconv" "strings" "github.com/fatih/color" @@ -20,7 +21,7 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/builtin" - "github.com/filecoin-project/go-state-types/builtin/v8/miner" + "github.com/filecoin-project/go-state-types/builtin/v9/miner" "github.com/filecoin-project/go-state-types/network" "github.com/filecoin-project/lotus/api" @@ -48,6 +49,8 @@ var actorCmd = &cli.Command{ actorProposeChangeWorker, actorConfirmChangeWorker, actorCompactAllocatedCmd, + actorProposeChangeBeneficiary, + actorConfirmChangeBeneficiary, }, } @@ -906,8 +909,7 @@ var actorProposeChangeWorker = &cli.Command{ // check it executed successfully if wait.Receipt.ExitCode.IsError() { - fmt.Fprintln(cctx.App.Writer, "Propose worker change failed!") - return err + return fmt.Errorf("propose worker change failed") } mi, err = api.StateMinerInfo(ctx, maddr, wait.TipSet) @@ -925,6 +927,139 @@ var actorProposeChangeWorker = &cli.Command{ }, } +var actorProposeChangeBeneficiary = &cli.Command{ + Name: "propose-change-beneficiary", + Usage: "Propose a beneficiary address change", + ArgsUsage: "[beneficiaryAddress quota expiration]", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "really-do-it", + Usage: "Actually send transaction performing the action", + Value: false, + }, + &cli.BoolFlag{ + Name: "overwrite-pending-change", + Usage: "Overwrite the current beneficiary change proposal", + Value: false, + }, + &cli.StringFlag{ + Name: "actor", + Usage: "specify the address of miner actor", + }, + }, + Action: func(cctx *cli.Context) error { + if cctx.NArg() != 3 { + return lcli.IncorrectNumArgs(cctx) + } + + api, acloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return xerrors.Errorf("getting fullnode api: %w", err) + } + defer acloser() + + ctx := lcli.ReqContext(cctx) + + na, err := address.NewFromString(cctx.Args().Get(0)) + if err != nil { + return xerrors.Errorf("parsing beneficiary address: %w", err) + } + + newAddr, err := api.StateLookupID(ctx, na, types.EmptyTSK) + if err != nil { + return xerrors.Errorf("looking up new beneficiary address: %w", err) + } + + quota, err := types.ParseFIL(cctx.Args().Get(1)) + if err != nil { + return xerrors.Errorf("parsing quota: %w", err) + } + + expiration, err := strconv.ParseInt(cctx.Args().Get(2), 10, 64) + if err != nil { + return xerrors.Errorf("parsing expiration: %w", err) + } + + maddr, err := getActorAddress(ctx, cctx) + if err != nil { + return xerrors.Errorf("getting miner address: %w", err) + } + + mi, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK) + if err != nil { + return xerrors.Errorf("getting miner info: %w", err) + } + + if mi.Beneficiary == mi.Owner && newAddr == mi.Owner { + return fmt.Errorf("beneficiary %s already set to owner address", mi.Beneficiary) + } + + if mi.PendingBeneficiaryTerm != nil { + fmt.Println("WARNING: replacing Pending Beneficiary Term of:") + fmt.Println("Beneficiary: ", mi.PendingBeneficiaryTerm.NewBeneficiary) + fmt.Println("Quota:", mi.PendingBeneficiaryTerm.NewQuota) + fmt.Println("Expiration Epoch:", mi.PendingBeneficiaryTerm.NewExpiration) + + if !cctx.Bool("overwrite-pending-change") { + return fmt.Errorf("must pass --overwrite-pending-change to replace current pending beneficiary change. Please review CAREFULLY") + } + } + + if !cctx.Bool("really-do-it") { + fmt.Println("Pass --really-do-it to actually execute this action") + return nil + } + + params := &miner.ChangeBeneficiaryParams{ + NewBeneficiary: newAddr, + NewQuota: abi.TokenAmount(quota), + NewExpiration: abi.ChainEpoch(expiration), + } + + sp, err := actors.SerializeParams(params) + if err != nil { + return xerrors.Errorf("serializing params: %w", err) + } + + smsg, err := api.MpoolPushMessage(ctx, &types.Message{ + From: mi.Owner, + To: maddr, + Method: builtin.MethodsMiner.ChangeBeneficiary, + Value: big.Zero(), + Params: sp, + }, nil) + if err != nil { + return xerrors.Errorf("mpool push: %w", err) + } + + fmt.Println("Propose Message CID:", smsg.Cid()) + + // wait for it to get mined into a block + wait, err := api.StateWaitMsg(ctx, smsg.Cid(), build.MessageConfidence) + if err != nil { + return xerrors.Errorf("waiting for message to be included in block: %w", err) + } + + // check it executed successfully + if wait.Receipt.ExitCode.IsError() { + return fmt.Errorf("propose beneficiary change failed") + } + + updatedMinerInfo, err := api.StateMinerInfo(ctx, maddr, wait.TipSet) + if err != nil { + return xerrors.Errorf("getting miner info: %w", err) + } + + if updatedMinerInfo.PendingBeneficiaryTerm == nil { + fmt.Println("Beneficiary address successfully changed") + } else { + fmt.Println("Beneficiary address change awaiting additional confirmations") + } + + return nil + }, +} + var actorConfirmChangeWorker = &cli.Command{ Name: "confirm-change-worker", Usage: "Confirm a worker address change", @@ -988,7 +1123,7 @@ var actorConfirmChangeWorker = &cli.Command{ } if !cctx.Bool("really-do-it") { - fmt.Fprintln(cctx.App.Writer, "Pass --really-do-it to actually execute this action") + fmt.Println("Pass --really-do-it to actually execute this action") return nil } @@ -1002,7 +1137,7 @@ var actorConfirmChangeWorker = &cli.Command{ return xerrors.Errorf("mpool push: %w", err) } - fmt.Fprintln(cctx.App.Writer, "Confirm Message CID:", smsg.Cid()) + fmt.Println("Confirm Message CID:", smsg.Cid()) // wait for it to get mined into a block wait, err := api.StateWaitMsg(ctx, smsg.Cid(), build.MessageConfidence) @@ -1028,6 +1163,129 @@ var actorConfirmChangeWorker = &cli.Command{ }, } +var actorConfirmChangeBeneficiary = &cli.Command{ + Name: "confirm-change-beneficiary", + Usage: "Confirm a beneficiary address change", + ArgsUsage: "[minerAddress]", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "really-do-it", + Usage: "Actually send transaction performing the action", + Value: false, + }, + &cli.BoolFlag{ + Name: "existing-beneficiary", + Usage: "send confirmation from the existing beneficiary address", + }, + &cli.BoolFlag{ + Name: "new-beneficiary", + Usage: "send confirmation from the new beneficiary address", + }, + }, + Action: func(cctx *cli.Context) error { + if cctx.NArg() != 1 { + return lcli.IncorrectNumArgs(cctx) + } + + api, acloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return xerrors.Errorf("getting fullnode api: %w", err) + } + defer acloser() + + ctx := lcli.ReqContext(cctx) + + maddr, err := address.NewFromString(cctx.Args().First()) + if err != nil { + return xerrors.Errorf("parsing beneficiary address: %w", err) + } + + mi, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK) + if err != nil { + return xerrors.Errorf("getting miner info: %w", err) + } + + if mi.PendingBeneficiaryTerm == nil { + return fmt.Errorf("no pending beneficiary term found for miner %s", maddr) + } + + if (cctx.IsSet("existing-beneficiary") && cctx.IsSet("new-beneficiary")) || (!cctx.IsSet("existing-beneficiary") && !cctx.IsSet("new-beneficiary")) { + return lcli.ShowHelp(cctx, fmt.Errorf("must pass exactly one of --existing-beneficiary or --existing-beneficiary")) + } + + var fromAddr address.Address + if cctx.IsSet("existing-beneficiary") { + if mi.PendingBeneficiaryTerm.ApprovedByBeneficiary { + return fmt.Errorf("beneficiary change already approved by current beneficiary") + } + fromAddr = mi.Beneficiary + } else { + if mi.PendingBeneficiaryTerm.ApprovedByNominee { + return fmt.Errorf("beneficiary change already approved by new beneficiary") + } + fromAddr = mi.PendingBeneficiaryTerm.NewBeneficiary + } + + fmt.Println("Confirming Pending Beneficiary Term of:") + fmt.Println("Beneficiary: ", mi.PendingBeneficiaryTerm.NewBeneficiary) + fmt.Println("Quota:", mi.PendingBeneficiaryTerm.NewQuota) + fmt.Println("Expiration Epoch:", mi.PendingBeneficiaryTerm.NewExpiration) + + if !cctx.Bool("really-do-it") { + fmt.Println("Pass --really-do-it to actually execute this action") + return nil + } + + params := &miner.ChangeBeneficiaryParams{ + NewBeneficiary: mi.PendingBeneficiaryTerm.NewBeneficiary, + NewQuota: mi.PendingBeneficiaryTerm.NewQuota, + NewExpiration: mi.PendingBeneficiaryTerm.NewExpiration, + } + + sp, err := actors.SerializeParams(params) + if err != nil { + return xerrors.Errorf("serializing params: %w", err) + } + + smsg, err := api.MpoolPushMessage(ctx, &types.Message{ + From: fromAddr, + To: maddr, + Method: builtin.MethodsMiner.ChangeBeneficiary, + Value: big.Zero(), + Params: sp, + }, nil) + if err != nil { + return xerrors.Errorf("mpool push: %w", err) + } + + fmt.Println("Confirm Message CID:", smsg.Cid()) + + // wait for it to get mined into a block + wait, err := api.StateWaitMsg(ctx, smsg.Cid(), build.MessageConfidence) + if err != nil { + return xerrors.Errorf("waiting for message to be included in block: %w", err) + } + + // check it executed successfully + if wait.Receipt.ExitCode.IsError() { + return fmt.Errorf("confirm beneficiary change failed") + } + + updatedMinerInfo, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + if updatedMinerInfo.PendingBeneficiaryTerm == nil { + fmt.Println("Beneficiary address successfully changed") + } else { + fmt.Println("Beneficiary address change awaiting additional confirmations") + } + + return nil + }, +} + var actorCompactAllocatedCmd = &cli.Command{ Name: "compact-allocated", Usage: "compact allocated sectors bitfield", diff --git a/cmd/lotus-shed/miner-multisig.go b/cmd/lotus-shed/miner-multisig.go index e4268a291..0ea29c50b 100644 --- a/cmd/lotus-shed/miner-multisig.go +++ b/cmd/lotus-shed/miner-multisig.go @@ -12,9 +12,8 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/builtin" - miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" - miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" - msig5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/multisig" + "github.com/filecoin-project/go-state-types/builtin/v9/miner" + "github.com/filecoin-project/go-state-types/builtin/v9/multisig" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors" @@ -33,6 +32,8 @@ var minerMultisigsCmd = &cli.Command{ mmProposeChangeWorker, mmConfirmChangeWorker, mmProposeControlSet, + mmProposeChangeBeneficiary, + mmConfirmChangeBeneficiary, }, Flags: []cli.Flag{ &cli.StringFlag{ @@ -80,7 +81,7 @@ var mmProposeWithdrawBalance = &cli.Command{ return err } - sp, err := actors.SerializeParams(&miner5.WithdrawBalanceParams{ + sp, err := actors.SerializeParams(&miner.WithdrawBalanceParams{ AmountRequested: abi.TokenAmount(val), }) if err != nil { @@ -106,7 +107,7 @@ var mmProposeWithdrawBalance = &cli.Command{ return err } - var retval msig5.ProposeReturn + var retval multisig.ProposeReturn if err := retval.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return)); err != nil { return fmt.Errorf("failed to unmarshal propose return value: %w", err) } @@ -149,7 +150,7 @@ var mmApproveWithdrawBalance = &cli.Command{ return err } - sp, err := actors.SerializeParams(&miner5.WithdrawBalanceParams{ + sp, err := actors.SerializeParams(&miner.WithdrawBalanceParams{ AmountRequested: abi.TokenAmount(val), }) if err != nil { @@ -185,7 +186,7 @@ var mmApproveWithdrawBalance = &cli.Command{ return err } - var retval msig5.ApproveReturn + var retval multisig.ApproveReturn if err := retval.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return)); err != nil { return fmt.Errorf("failed to unmarshal approve return value: %w", err) } @@ -266,7 +267,7 @@ var mmProposeChangeOwner = &cli.Command{ return err } - var retval msig5.ProposeReturn + var retval multisig.ProposeReturn if err := retval.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return)); err != nil { return fmt.Errorf("failed to unmarshal propose return value: %w", err) } @@ -356,7 +357,7 @@ var mmApproveChangeOwner = &cli.Command{ return err } - var retval msig5.ApproveReturn + var retval multisig.ApproveReturn if err := retval.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return)); err != nil { return fmt.Errorf("failed to unmarshal approve return value: %w", err) } @@ -421,7 +422,7 @@ var mmProposeChangeWorker = &cli.Command{ } } - cwp := &miner2.ChangeWorkerAddressParams{ + cwp := &miner.ChangeWorkerAddressParams{ NewWorker: newAddr, NewControlAddrs: mi.ControlAddresses, } @@ -453,7 +454,7 @@ var mmProposeChangeWorker = &cli.Command{ return err } - var retval msig5.ProposeReturn + var retval multisig.ProposeReturn if err := retval.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return)); err != nil { return fmt.Errorf("failed to unmarshal propose return value: %w", err) } @@ -469,6 +470,114 @@ var mmProposeChangeWorker = &cli.Command{ }, } +var mmProposeChangeBeneficiary = &cli.Command{ + Name: "propose-change-beneficiary", + Usage: "Propose a beneficiary address change", + ArgsUsage: "[beneficiaryAddress quota expiration]", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "really-do-it", + Usage: "Actually send transaction performing the action", + Value: false, + }, + &cli.BoolFlag{ + Name: "overwrite-pending-change", + Usage: "Overwrite the current beneficiary change proposal", + Value: false, + }, + }, + Action: func(cctx *cli.Context) error { + if cctx.NArg() != 3 { + return lcli.IncorrectNumArgs(cctx) + } + + api, acloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return xerrors.Errorf("getting fullnode api: %w", err) + } + defer acloser() + + ctx := lcli.ReqContext(cctx) + + na, err := address.NewFromString(cctx.Args().Get(0)) + if err != nil { + return xerrors.Errorf("parsing beneficiary address: %w", err) + } + + newAddr, err := api.StateLookupID(ctx, na, types.EmptyTSK) + if err != nil { + return xerrors.Errorf("looking up new beneficiary address: %w", err) + } + + quota, err := types.ParseFIL(cctx.Args().Get(1)) + if err != nil { + return xerrors.Errorf("parsing quota: %w", err) + } + + expiration, err := types.BigFromString(cctx.Args().Get(2)) + if err != nil { + return xerrors.Errorf("parsing expiration: %w", err) + } + + multisigAddr, sender, minerAddr, err := getInputs(cctx) + if err != nil { + return err + } + + mi, err := api.StateMinerInfo(ctx, minerAddr, types.EmptyTSK) + if err != nil { + return err + } + + if mi.PendingBeneficiaryTerm != nil { + fmt.Println("WARNING: replacing Pending Beneficiary Term of:") + fmt.Println("Beneficiary: ", mi.PendingBeneficiaryTerm.NewBeneficiary) + fmt.Println("Quota:", mi.PendingBeneficiaryTerm.NewQuota) + fmt.Println("Expiration Epoch:", mi.PendingBeneficiaryTerm.NewExpiration) + + if !cctx.Bool("overwrite-pending-change") { + return fmt.Errorf("must pass --overwrite-pending-change to replace current pending beneficiary change. Please review CAREFULLY") + } + } + + if !cctx.Bool("really-do-it") { + fmt.Println("Pass --really-do-it to actually execute this action") + return nil + } + + params := &miner.ChangeBeneficiaryParams{ + NewBeneficiary: newAddr, + NewQuota: abi.TokenAmount(quota), + NewExpiration: abi.ChainEpoch(expiration.Int64()), + } + + sp, err := actors.SerializeParams(params) + if err != nil { + return xerrors.Errorf("serializing params: %w", err) + } + + pcid, err := api.MsigPropose(ctx, multisigAddr, minerAddr, big.Zero(), sender, uint64(builtin.MethodsMiner.ChangeBeneficiary), sp) + if err != nil { + return xerrors.Errorf("proposing message: %w", err) + } + + fmt.Println("Propose Message CID: ", pcid) + + // wait for it to get mined into a block + wait, err := api.StateWaitMsg(ctx, pcid, build.MessageConfidence) + if err != nil { + return xerrors.Errorf("waiting for message to be included in block: %w", err) + } + + // check it executed successfully + if wait.Receipt.ExitCode.IsError() { + return fmt.Errorf("propose beneficiary change failed") + } + + return nil + }, +} + var mmConfirmChangeWorker = &cli.Command{ Name: "confirm-change-worker", Usage: "Confirm an worker address change", @@ -537,7 +646,7 @@ var mmConfirmChangeWorker = &cli.Command{ return err } - var retval msig5.ProposeReturn + var retval multisig.ProposeReturn if err := retval.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return)); err != nil { return fmt.Errorf("failed to unmarshal propose return value: %w", err) } @@ -552,6 +661,98 @@ var mmConfirmChangeWorker = &cli.Command{ }, } +var mmConfirmChangeBeneficiary = &cli.Command{ + Name: "confirm-change-beneficiary", + Usage: "Confirm a beneficiary address change", + ArgsUsage: "[minerAddress]", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "really-do-it", + Usage: "Actually send transaction performing the action", + Value: false, + }, + }, + Action: func(cctx *cli.Context) error { + if cctx.NArg() != 1 { + return lcli.IncorrectNumArgs(cctx) + } + + api, acloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return xerrors.Errorf("getting fullnode api: %w", err) + } + defer acloser() + + ctx := lcli.ReqContext(cctx) + + multisigAddr, sender, minerAddr, err := getInputs(cctx) + if err != nil { + return err + } + + mi, err := api.StateMinerInfo(ctx, minerAddr, types.EmptyTSK) + if err != nil { + return err + } + + if mi.PendingBeneficiaryTerm == nil { + return fmt.Errorf("no pending beneficiary term found for miner %s", minerAddr) + } + + fmt.Println("Confirming Pending Beneficiary Term of:") + fmt.Println("Beneficiary: ", mi.PendingBeneficiaryTerm.NewBeneficiary) + fmt.Println("Quota:", mi.PendingBeneficiaryTerm.NewQuota) + fmt.Println("Expiration Epoch:", mi.PendingBeneficiaryTerm.NewExpiration) + + if !cctx.Bool("really-do-it") { + fmt.Println("Pass --really-do-it to actually execute this action") + return nil + } + + params := &miner.ChangeBeneficiaryParams{ + NewBeneficiary: mi.PendingBeneficiaryTerm.NewBeneficiary, + NewQuota: mi.PendingBeneficiaryTerm.NewQuota, + NewExpiration: mi.PendingBeneficiaryTerm.NewExpiration, + } + + sp, err := actors.SerializeParams(params) + if err != nil { + return xerrors.Errorf("serializing params: %w", err) + } + + pcid, err := api.MsigPropose(ctx, multisigAddr, minerAddr, big.Zero(), sender, uint64(builtin.MethodsMiner.ChangeBeneficiary), sp) + if err != nil { + return xerrors.Errorf("proposing message: %w", err) + } + + fmt.Println("Confirm Message CID:", pcid) + + // wait for it to get mined into a block + wait, err := api.StateWaitMsg(ctx, pcid, build.MessageConfidence) + if err != nil { + return xerrors.Errorf("waiting for message to be included in block: %w", err) + } + + // check it executed successfully + if wait.Receipt.ExitCode != 0 { + return fmt.Errorf("confirm beneficiary change failed") + } + + updatedMinerInfo, err := api.StateMinerInfo(ctx, minerAddr, types.EmptyTSK) + if err != nil { + return err + } + + if updatedMinerInfo.PendingBeneficiaryTerm == nil { + fmt.Println("Beneficiary address successfully changed") + } else { + fmt.Println("Beneficiary address change awaiting additional confirmations") + } + + return nil + }, +} + var mmProposeControlSet = &cli.Command{ Name: "propose-control-set", Usage: "Set control address(-es)", @@ -623,7 +824,7 @@ var mmProposeControlSet = &cli.Command{ } } - cwp := &miner2.ChangeWorkerAddressParams{ + cwp := &miner.ChangeWorkerAddressParams{ NewWorker: mi.Worker, NewControlAddrs: toSet, } @@ -652,7 +853,7 @@ var mmProposeControlSet = &cli.Command{ return err } - var retval msig5.ProposeReturn + var retval multisig.ProposeReturn if err := retval.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return)); err != nil { return fmt.Errorf("failed to unmarshal propose return value: %w", err) } diff --git a/documentation/en/cli-lotus-miner.md b/documentation/en/cli-lotus-miner.md index 235465aa2..322fe10de 100644 --- a/documentation/en/cli-lotus-miner.md +++ b/documentation/en/cli-lotus-miner.md @@ -231,16 +231,18 @@ USAGE: lotus-miner actor command [command options] [arguments...] COMMANDS: - set-addresses, set-addrs set addresses that your miner can be publicly dialed on - withdraw withdraw available balance to beneficiary - repay-debt pay down a miner's debt - set-peer-id set the peer id of your miner - set-owner Set owner address (this command should be invoked twice, first with the old owner as the senderAddress, and then with the new owner) - control Manage control addresses - propose-change-worker Propose a worker address change - confirm-change-worker Confirm a worker address change - compact-allocated compact allocated sectors bitfield - help, h Shows a list of commands or help for one command + set-addresses, set-addrs set addresses that your miner can be publicly dialed on + withdraw withdraw available balance to beneficiary + repay-debt pay down a miner's debt + set-peer-id set the peer id of your miner + set-owner Set owner address (this command should be invoked twice, first with the old owner as the senderAddress, and then with the new owner) + control Manage control addresses + propose-change-worker Propose a worker address change + confirm-change-worker Confirm a worker address change + compact-allocated compact allocated sectors bitfield + propose-change-beneficiary Propose a beneficiary address change + confirm-change-beneficiary Confirm a beneficiary address change + help, h Shows a list of commands or help for one command OPTIONS: --help, -h show help (default: false) @@ -390,6 +392,36 @@ OPTIONS: ``` +### lotus-miner actor propose-change-beneficiary +``` +NAME: + lotus-miner actor propose-change-beneficiary - Propose a beneficiary address change + +USAGE: + lotus-miner actor propose-change-beneficiary [command options] [beneficiaryAddress quota expiration] + +OPTIONS: + --actor value specify the address of miner actor + --overwrite-pending-change Overwrite the current beneficiary change proposal (default: false) + --really-do-it Actually send transaction performing the action (default: false) + +``` + +### lotus-miner actor confirm-change-beneficiary +``` +NAME: + lotus-miner actor confirm-change-beneficiary - Confirm a beneficiary address change + +USAGE: + lotus-miner actor confirm-change-beneficiary [command options] [minerAddress] + +OPTIONS: + --existing-beneficiary send confirmation from the existing beneficiary address (default: false) + --new-beneficiary send confirmation from the new beneficiary address (default: false) + --really-do-it Actually send transaction performing the action (default: false) + +``` + ## lotus-miner info ``` NAME: From 3c193287640eb9a9bb903e8541d4f7b474e0c277 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Wed, 14 Sep 2022 17:38:01 -0400 Subject: [PATCH 20/85] Review fixes --- cmd/lotus-miner/actor.go | 12 ++++++------ cmd/lotus-shed/miner-multisig.go | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cmd/lotus-miner/actor.go b/cmd/lotus-miner/actor.go index b1f97d530..7b0e81c3b 100644 --- a/cmd/lotus-miner/actor.go +++ b/cmd/lotus-miner/actor.go @@ -1006,7 +1006,7 @@ var actorProposeChangeBeneficiary = &cli.Command{ } if !cctx.Bool("really-do-it") { - fmt.Println("Pass --really-do-it to actually execute this action") + fmt.Println("Pass --really-do-it to actually execute this action. Review what you're about to approve CAREFULLY please") return nil } @@ -1050,7 +1050,7 @@ var actorProposeChangeBeneficiary = &cli.Command{ return xerrors.Errorf("getting miner info: %w", err) } - if updatedMinerInfo.PendingBeneficiaryTerm == nil { + if updatedMinerInfo.PendingBeneficiaryTerm == nil && updatedMinerInfo.Beneficiary == newAddr { fmt.Println("Beneficiary address successfully changed") } else { fmt.Println("Beneficiary address change awaiting additional confirmations") @@ -1210,7 +1210,7 @@ var actorConfirmChangeBeneficiary = &cli.Command{ } if (cctx.IsSet("existing-beneficiary") && cctx.IsSet("new-beneficiary")) || (!cctx.IsSet("existing-beneficiary") && !cctx.IsSet("new-beneficiary")) { - return lcli.ShowHelp(cctx, fmt.Errorf("must pass exactly one of --existing-beneficiary or --existing-beneficiary")) + return lcli.ShowHelp(cctx, fmt.Errorf("must pass exactly one of --existing-beneficiary or --new-beneficiary")) } var fromAddr address.Address @@ -1232,7 +1232,7 @@ var actorConfirmChangeBeneficiary = &cli.Command{ fmt.Println("Expiration Epoch:", mi.PendingBeneficiaryTerm.NewExpiration) if !cctx.Bool("really-do-it") { - fmt.Println("Pass --really-do-it to actually execute this action") + fmt.Println("Pass --really-do-it to actually execute this action. Review what you're about to approve CAREFULLY please") return nil } @@ -1268,7 +1268,7 @@ var actorConfirmChangeBeneficiary = &cli.Command{ // check it executed successfully if wait.Receipt.ExitCode.IsError() { - return fmt.Errorf("confirm beneficiary change failed") + return fmt.Errorf("confirm beneficiary change failed with code %d", wait.Receipt.ExitCode) } updatedMinerInfo, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK) @@ -1276,7 +1276,7 @@ var actorConfirmChangeBeneficiary = &cli.Command{ return err } - if updatedMinerInfo.PendingBeneficiaryTerm == nil { + if updatedMinerInfo.PendingBeneficiaryTerm == nil && updatedMinerInfo.Beneficiary == mi.PendingBeneficiaryTerm.NewBeneficiary { fmt.Println("Beneficiary address successfully changed") } else { fmt.Println("Beneficiary address change awaiting additional confirmations") diff --git a/cmd/lotus-shed/miner-multisig.go b/cmd/lotus-shed/miner-multisig.go index 0ea29c50b..080229a6a 100644 --- a/cmd/lotus-shed/miner-multisig.go +++ b/cmd/lotus-shed/miner-multisig.go @@ -541,7 +541,7 @@ var mmProposeChangeBeneficiary = &cli.Command{ } if !cctx.Bool("really-do-it") { - fmt.Println("Pass --really-do-it to actually execute this action") + fmt.Println("Pass --really-do-it to actually execute this action. Review what you're about to approve CAREFULLY please") return nil } @@ -705,7 +705,7 @@ var mmConfirmChangeBeneficiary = &cli.Command{ fmt.Println("Expiration Epoch:", mi.PendingBeneficiaryTerm.NewExpiration) if !cctx.Bool("really-do-it") { - fmt.Println("Pass --really-do-it to actually execute this action") + fmt.Println("Pass --really-do-it to actually execute this action. Review what you're about to approve CAREFULLY please") return nil } @@ -734,8 +734,8 @@ var mmConfirmChangeBeneficiary = &cli.Command{ } // check it executed successfully - if wait.Receipt.ExitCode != 0 { - return fmt.Errorf("confirm beneficiary change failed") + if wait.Receipt.ExitCode.IsError() { + return fmt.Errorf("confirm beneficiary change failed with code %d", wait.Receipt.ExitCode) } updatedMinerInfo, err := api.StateMinerInfo(ctx, minerAddr, types.EmptyTSK) @@ -743,7 +743,7 @@ var mmConfirmChangeBeneficiary = &cli.Command{ return err } - if updatedMinerInfo.PendingBeneficiaryTerm == nil { + if updatedMinerInfo.PendingBeneficiaryTerm == nil && updatedMinerInfo.Beneficiary == mi.PendingBeneficiaryTerm.NewBeneficiary { fmt.Println("Beneficiary address successfully changed") } else { fmt.Println("Beneficiary address change awaiting additional confirmations") From 9d63e1412e6bbe52766a3a0fa8335c72fba4b020 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Wed, 14 Sep 2022 01:04:15 -0400 Subject: [PATCH 21/85] print beneficiary info in state miner-info and lotus-miner info --- cli/state.go | 16 ++++++++++++++++ cmd/lotus-miner/info.go | 17 +++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/cli/state.go b/cli/state.go index a7e195b1c..a0fc49f30 100644 --- a/cli/state.go +++ b/cli/state.go @@ -169,6 +169,22 @@ var StateMinerInfo = &cli.Command{ for i, controlAddress := range mi.ControlAddresses { fmt.Printf("Control %d: \t%s\n", i, controlAddress) } + if mi.Beneficiary != address.Undef { + fmt.Printf("Beneficiary:\t%s\n", mi.Beneficiary) + if mi.Beneficiary != mi.Owner { + fmt.Printf("Beneficiary Quota:\t%s\n", mi.BeneficiaryTerm.Quota) + fmt.Printf("Beneficiary Used Quota:\t%s\n", mi.BeneficiaryTerm.UsedQuota) + fmt.Printf("Beneficiary Expiration:\t%s\n", mi.BeneficiaryTerm.Expiration) + } + } + if mi.PendingBeneficiaryTerm != nil { + fmt.Printf("Pending Beneficiary Term:\n") + fmt.Printf("New Beneficiary:\t%s\n", mi.PendingBeneficiaryTerm.NewBeneficiary) + fmt.Printf("New Quota:\t%s\n", mi.PendingBeneficiaryTerm.NewQuota) + fmt.Printf("New Expiration:\t%s\n", mi.PendingBeneficiaryTerm.NewExpiration) + fmt.Printf("Approved By Beneficiary:\t%t\n", mi.PendingBeneficiaryTerm.ApprovedByBeneficiary) + fmt.Printf("Approved By Nominee:\t%t\n", mi.PendingBeneficiaryTerm.ApprovedByNominee) + } fmt.Printf("PeerID:\t%s\n", mi.PeerId) fmt.Printf("Multiaddrs:\t") diff --git a/cmd/lotus-miner/info.go b/cmd/lotus-miner/info.go index 408482a89..e8cbfd8b7 100644 --- a/cmd/lotus-miner/info.go +++ b/cmd/lotus-miner/info.go @@ -313,6 +313,23 @@ func handleMiningInfo(ctx context.Context, cctx *cli.Context, fullapi v1api.Full } colorTokenAmount("Total Spendable: %s\n", spendable) + if mi.Beneficiary != address.Undef { + fmt.Println() + fmt.Printf("Beneficiary:\t%s\n", mi.Beneficiary) + if mi.Beneficiary != mi.Owner { + fmt.Printf("Beneficiary Quota:\t%s\n", mi.BeneficiaryTerm.Quota) + fmt.Printf("Beneficiary Used Quota:\t%s\n", mi.BeneficiaryTerm.UsedQuota) + fmt.Printf("Beneficiary Expiration:\t%s\n", mi.BeneficiaryTerm.Expiration) + } + } + if mi.PendingBeneficiaryTerm != nil { + fmt.Printf("Pending Beneficiary Term:\n") + fmt.Printf("New Beneficiary:\t%s\n", mi.PendingBeneficiaryTerm.NewBeneficiary) + fmt.Printf("New Quota:\t%s\n", mi.PendingBeneficiaryTerm.NewQuota) + fmt.Printf("New Expiration:\t%s\n", mi.PendingBeneficiaryTerm.NewExpiration) + fmt.Printf("Approved By Beneficiary:\t%t\n", mi.PendingBeneficiaryTerm.ApprovedByBeneficiary) + fmt.Printf("Approved By Nominee:\t%t\n", mi.PendingBeneficiaryTerm.ApprovedByNominee) + } fmt.Println() if !cctx.Bool("hide-sectors-info") { From 236a21a9d7a42c430dcd3d3aa7e4937d9d3284d8 Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Thu, 15 Sep 2022 03:34:24 -0400 Subject: [PATCH 22/85] add get all msig into lotus-shed --- cmd/lotus-shed/main.go | 1 + cmd/lotus-shed/msig.go | 184 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 cmd/lotus-shed/msig.go diff --git a/cmd/lotus-shed/main.go b/cmd/lotus-shed/main.go index 0f10a617f..792d72968 100644 --- a/cmd/lotus-shed/main.go +++ b/cmd/lotus-shed/main.go @@ -73,6 +73,7 @@ func main() { migrationsCmd, diffCmd, itestdCmd, + msigCmd, } app := &cli.App{ diff --git a/cmd/lotus-shed/msig.go b/cmd/lotus-shed/msig.go new file mode 100644 index 000000000..dafa9a55b --- /dev/null +++ b/cmd/lotus-shed/msig.go @@ -0,0 +1,184 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/lotus/api/v0api" + "github.com/filecoin-project/lotus/chain/types" + + "github.com/filecoin-project/go-address" + + "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/lotus/build" + + "github.com/filecoin-project/go-state-types/abi" + cliutil "github.com/filecoin-project/lotus/cli/util" + "github.com/urfave/cli/v2" + "golang.org/x/xerrors" +) + +var GetFullNodeAPI = cliutil.GetFullNodeAPI +var ReqContext = cliutil.ReqContext + +func LoadTipSet(ctx context.Context, cctx *cli.Context, api v0api.FullNode) (*types.TipSet, error) { + tss := cctx.String("tipset") + if tss == "" { + return api.ChainHead(ctx) + } + + return ParseTipSetRef(ctx, api, tss) +} + +func ParseTipSetRef(ctx context.Context, api v0api.FullNode, tss string) (*types.TipSet, error) { + if tss[0] == '@' { + if tss == "@head" { + return api.ChainHead(ctx) + } + + var h uint64 + if _, err := fmt.Sscanf(tss, "@%d", &h); err != nil { + return nil, xerrors.Errorf("parsing height tipset ref: %w", err) + } + + return api.ChainGetTipSetByHeight(ctx, abi.ChainEpoch(h), types.EmptyTSK) + } + + cids, err := ParseTipSetString(tss) + if err != nil { + return nil, err + } + + if len(cids) == 0 { + return nil, nil + } + + k := types.NewTipSetKey(cids...) + ts, err := api.ChainGetTipSet(ctx, k) + if err != nil { + return nil, err + } + + return ts, nil +} + +func ParseTipSetString(ts string) ([]cid.Cid, error) { + strs := strings.Split(ts, ",") + + var cids []cid.Cid + for _, s := range strs { + c, err := cid.Parse(strings.TrimSpace(s)) + if err != nil { + return nil, err + } + cids = append(cids, c) + } + + return cids, nil +} + +type msigBriefInfo struct { + ID address.Address + Signer interface{} + Balance abi.TokenAmount + Threshold float64 +} + +var msigCmd = &cli.Command{ + Name: "multisig", + Usage: "utils for multisig actors", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "tipset", + Usage: "specify tipset to call method on (pass comma separated array of cids)", + }, + }, + Subcommands: []*cli.Command{ + multisigGetAllCmd, + }, +} + +var multisigGetAllCmd = &cli.Command{ + Name: "all", + Usage: "get all multisig actor on chain with id, siigners, threshold and balance", + Flags: []cli.Flag{ + &cli.UintFlag{ + Name: "network-version", + Value: uint(build.NewestNetworkVersion), + }, + }, + Action: func(cctx *cli.Context) error { + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + + ctx := ReqContext(cctx) + + ts, err := LoadTipSet(ctx, cctx, api) + if err != nil { + return err + } + + actors, err := api.StateListActors(ctx, ts.Key()) + if err != nil { + return err + } + + nv := network.Version(cctx.Uint64("network-version")) + codeCids, err := api.StateActorCodeCIDs(ctx, nv) + if err != nil { + return err + } + + msigCid, exists := codeCids["multisig"] + if !exists { + return xerrors.Errorf("bad code cid key") + } + + var msigActorsInfo []msigBriefInfo + for _, actor := range actors { + + act, err := api.StateGetActor(ctx, actor, ts.Key()) + if err != nil { + return err + } + + if act.Code == msigCid { + + actorState, err := api.StateReadState(ctx, actor, ts.Key()) + if err != nil { + return err + } + + stateI, ok := actorState.State.(map[string]interface{}) + if !ok { + return xerrors.Errorf("fail to map msig state") + } + + signersI, _ := stateI["Signers"] + signers := signersI.([]interface{}) + thresholdI, _ := stateI["NumApprovalsThreshold"] + threshold := thresholdI.(float64) + info := msigBriefInfo{ + ID: actor, + Signer: signers, + Balance: actorState.Balance, + Threshold: threshold, + } + msigActorsInfo = append(msigActorsInfo, info) + } + } + out, err := json.MarshalIndent(msigActorsInfo, "", " ") + if err != nil { + return err + } + fmt.Println(string(out)) + return nil + }, +} From d2c726cd28a4d5ae2dbac48ce58d9e0a594b0a71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 14 Sep 2022 11:35:07 +0200 Subject: [PATCH 23/85] fix: sealing: Abort upgrades in sectors with no deals --- storage/pipeline/states_replica_update.go | 5 +++++ storage/pipeline/states_sealing.go | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/storage/pipeline/states_replica_update.go b/storage/pipeline/states_replica_update.go index ae90d2535..d0e896357 100644 --- a/storage/pipeline/states_replica_update.go +++ b/storage/pipeline/states_replica_update.go @@ -21,6 +21,11 @@ import ( ) func (m *Sealing) handleReplicaUpdate(ctx statemachine.Context, sector SectorInfo) error { + // if the sector ended up not having any deals, abort the upgrade + if !sector.hasDeals() { + return ctx.Send(SectorAbortUpgrade{xerrors.New("sector had no deals")}) + } + if err := checkPieces(ctx.Context(), m.maddr, sector, m.Api, true); err != nil { // Sanity check state return handleErrors(ctx, err, sector) } diff --git a/storage/pipeline/states_sealing.go b/storage/pipeline/states_sealing.go index c31c36335..f769341dd 100644 --- a/storage/pipeline/states_sealing.go +++ b/storage/pipeline/states_sealing.go @@ -49,6 +49,11 @@ func (m *Sealing) handlePacking(ctx statemachine.Context, sector SectorInfo) err delete(m.assignedPieces, m.minerSectorID(sector.SectorNumber)) m.inputLk.Unlock() + // if this is a snapdeals sector, but it ended up not having any deals, abort the upgrade + if sector.State == SnapDealsPacking && !sector.hasDeals() { + return ctx.Send(SectorAbortUpgrade{xerrors.New("sector had no deals")}) + } + log.Infow("performing filling up rest of the sector...", "sector", sector.SectorNumber) var allocated abi.UnpaddedPieceSize From 305cfa1f69b99f90f283c9c527126e2aed0ef21d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 14 Sep 2022 12:13:22 +0200 Subject: [PATCH 24/85] sealing: Pick safer minTarget in calcTargetExpiration --- storage/pipeline/input.go | 15 ++++++++++----- storage/pipeline/states_replica_update.go | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/storage/pipeline/input.go b/storage/pipeline/input.go index c7af7783e..2f203a17b 100644 --- a/storage/pipeline/input.go +++ b/storage/pipeline/input.go @@ -396,7 +396,7 @@ func (m *Sealing) updateInput(ctx context.Context, sp abi.RegisteredSealProof) e e abi.ChainEpoch p abi.TokenAmount }) - expF := func(sn abi.SectorNumber) (abi.ChainEpoch, abi.TokenAmount, error) { + getExpirationCached := func(sn abi.SectorNumber) (abi.ChainEpoch, abi.TokenAmount, error) { if e, ok := memo[sn]; ok { return e.e, e.p, nil } @@ -440,13 +440,13 @@ func (m *Sealing) updateInput(ctx context.Context, sp abi.RegisteredSealProof) e avail := abi.PaddedPieceSize(ssize).Unpadded() - sector.used // check that sector lifetime is long enough to fit deal using latest expiration from on chain - ok, err := sector.dealFitsInLifetime(piece.deal.DealProposal.EndEpoch, expF) + ok, err := sector.dealFitsInLifetime(piece.deal.DealProposal.EndEpoch, getExpirationCached) if err != nil { log.Errorf("failed to check expiration for cc Update sector %d", sector.number) continue } if !ok { - exp, _, _ := expF(sector.number) + exp, _, _ := getExpirationCached(sector.number) log.Debugf("CC update sector %d cannot fit deal, expiration %d before deal end epoch %d", id, exp, piece.deal.DealProposal.EndEpoch) continue } @@ -513,7 +513,7 @@ func (m *Sealing) updateInput(ctx context.Context, sp abi.RegisteredSealProof) e if len(toAssign) > 0 { log.Errorf("we are trying to create a new sector with open sectors %v", m.openSectors) - if err := m.tryGetDealSector(ctx, sp, expF); err != nil { + if err := m.tryGetDealSector(ctx, sp, getExpirationCached); err != nil { log.Errorw("Failed to create a new sector for deals", "error", err) } } @@ -551,8 +551,13 @@ func (m *Sealing) calcTargetExpiration(ctx context.Context, ssize abi.SectorSize } minDur, maxDur := policy.DealDurationBounds(0) + minTarget = ts.Height() + minDur - return ts.Height() + minDur, ts.Height() + maxDur, nil + if len(candidates) > 0 && candidates[0].deal.DealProposal.EndEpoch > minTarget { + minTarget = candidates[0].deal.DealProposal.EndEpoch + } + + return minTarget, ts.Height() + maxDur, nil } func (m *Sealing) maybeUpgradeSector(ctx context.Context, sp abi.RegisteredSealProof, ef expFn) (bool, error) { diff --git a/storage/pipeline/states_replica_update.go b/storage/pipeline/states_replica_update.go index d0e896357..0261201f3 100644 --- a/storage/pipeline/states_replica_update.go +++ b/storage/pipeline/states_replica_update.go @@ -302,6 +302,6 @@ func handleErrors(ctx statemachine.Context, err error, sector SectorInfo) error case *ErrExpiredDeals: // Probably not much we can do here, maybe re-pack the sector? return ctx.Send(SectorDealsExpired{xerrors.Errorf("expired dealIDs in sector: %w", err)}) default: - return xerrors.Errorf("checkPieces sanity check error: %w", err) + return xerrors.Errorf("checkPieces sanity check error: %w (%+v)", err, err) } } From bf3daea12455149b40948d7d6b43b1e8496c0328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 14 Sep 2022 12:45:22 +0200 Subject: [PATCH 25/85] sealing: Minimum upgrade sector expiration/target config --- .../en/default-lotus-miner-config.toml | 24 ++++++++++ node/config/doc_gen.go | 24 ++++++++++ node/config/types.go | 18 +++++++ node/modules/storageminer.go | 37 ++++++++------- storage/pipeline/input.go | 47 +++++++++++++------ storage/pipeline/sealiface/config.go | 4 ++ 6 files changed, 124 insertions(+), 30 deletions(-) diff --git a/documentation/en/default-lotus-miner-config.toml b/documentation/en/default-lotus-miner-config.toml index 46b21a91c..ae235e01e 100644 --- a/documentation/en/default-lotus-miner-config.toml +++ b/documentation/en/default-lotus-miner-config.toml @@ -442,6 +442,30 @@ # env var: LOTUS_SEALING_MAXUPGRADINGSECTORS #MaxUpgradingSectors = 0 + # When set to a non-zero value, minimum number of epochs until sector expiration required for sectors to be considered + # for upgrades (0 = DealMinDuration = 180 days = 518400 epochs) + # + # Note that if all deals waiting in the input queue have lifetimes longer than this value, upgrade sectors will be + # required to have expiration of at least the soonest-ending deal + # + # type: uint64 + # env var: LOTUS_SEALING_MINUPGRADESECTOREXPIRATION + #MinUpgradeSectorExpiration = 0 + + # When set to a non-zero value, minimum number of epochs until sector expiration above which upgrade candidates will + # be selected based on lowest initial pledge. + # + # Target sector expiration is calculated by looking at the input deal queue, sorting it by deal expiration, and + # selecting N deals from the queue up to sector size. The target expiration will be Nth deal end epoch, or in case + # where there weren't enough deals to fill a sector, DealMaxDuration (540 days = 1555200 epochs) + # + # Setting this to a high value (for example to maximum deal duration - 1555200) will disable selection based on + # initial pledge - upgrade sectors will always be chosen based on longest expiration + # + # type: uint64 + # env var: LOTUS_SEALING_MINTARGETUPGRADESECTOREXPIRATION + #MinTargetUpgradeSectorExpiration = 0 + # CommittedCapacitySectorLifetime is the duration a Committed Capacity (CC) sector will # live before it must be extended or converted into sector containing deals before it is # terminated. Value must be between 180-540 days inclusive diff --git a/node/config/doc_gen.go b/node/config/doc_gen.go index f317e0606..6b5927448 100644 --- a/node/config/doc_gen.go +++ b/node/config/doc_gen.go @@ -922,6 +922,30 @@ flow when the volume of storage deals is lower.`, Comment: `Upper bound on how many sectors can be sealing+upgrading at the same time when upgrading CC sectors with deals (0 = MaxSealingSectorsForDeals)`, }, + { + Name: "MinUpgradeSectorExpiration", + Type: "uint64", + + Comment: `When set to a non-zero value, minimum number of epochs until sector expiration required for sectors to be considered +for upgrades (0 = DealMinDuration = 180 days = 518400 epochs) + +Note that if all deals waiting in the input queue have lifetimes longer than this value, upgrade sectors will be +required to have expiration of at least the soonest-ending deal`, + }, + { + Name: "MinTargetUpgradeSectorExpiration", + Type: "uint64", + + Comment: `When set to a non-zero value, minimum number of epochs until sector expiration above which upgrade candidates will +be selected based on lowest initial pledge. + +Target sector expiration is calculated by looking at the input deal queue, sorting it by deal expiration, and +selecting N deals from the queue up to sector size. The target expiration will be Nth deal end epoch, or in case +where there weren't enough deals to fill a sector, DealMaxDuration (540 days = 1555200 epochs) + +Setting this to a high value (for example to maximum deal duration - 1555200) will disable selection based on +initial pledge - upgrade sectors will always be chosen based on longest expiration`, + }, { Name: "CommittedCapacitySectorLifetime", Type: "Duration", diff --git a/node/config/types.go b/node/config/types.go index dbea0ddb6..3c85587c3 100644 --- a/node/config/types.go +++ b/node/config/types.go @@ -319,6 +319,24 @@ type SealingConfig struct { // Upper bound on how many sectors can be sealing+upgrading at the same time when upgrading CC sectors with deals (0 = MaxSealingSectorsForDeals) MaxUpgradingSectors uint64 + // When set to a non-zero value, minimum number of epochs until sector expiration required for sectors to be considered + // for upgrades (0 = DealMinDuration = 180 days = 518400 epochs) + // + // Note that if all deals waiting in the input queue have lifetimes longer than this value, upgrade sectors will be + // required to have expiration of at least the soonest-ending deal + MinUpgradeSectorExpiration uint64 + + // When set to a non-zero value, minimum number of epochs until sector expiration above which upgrade candidates will + // be selected based on lowest initial pledge. + // + // Target sector expiration is calculated by looking at the input deal queue, sorting it by deal expiration, and + // selecting N deals from the queue up to sector size. The target expiration will be Nth deal end epoch, or in case + // where there weren't enough deals to fill a sector, DealMaxDuration (540 days = 1555200 epochs) + // + // Setting this to a high value (for example to maximum deal duration - 1555200) will disable selection based on + // initial pledge - upgrade sectors will always be chosen based on longest expiration + MinTargetUpgradeSectorExpiration uint64 + // CommittedCapacitySectorLifetime is the duration a Committed Capacity (CC) sector will // live before it must be extended or converted into sector containing deals before it is // terminated. Value must be between 180-540 days inclusive diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 2ea733605..0d85cd168 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -983,17 +983,19 @@ func NewSetSealConfigFunc(r repo.LockedRepo) (dtypes.SetSealingConfigFunc, error return func(cfg sealiface.Config) (err error) { err = mutateSealingCfg(r, func(c config.SealingConfiger) { newCfg := config.SealingConfig{ - MaxWaitDealsSectors: cfg.MaxWaitDealsSectors, - MaxSealingSectors: cfg.MaxSealingSectors, - MaxSealingSectorsForDeals: cfg.MaxSealingSectorsForDeals, - PreferNewSectorsForDeals: cfg.PreferNewSectorsForDeals, - MaxUpgradingSectors: cfg.MaxUpgradingSectors, - CommittedCapacitySectorLifetime: config.Duration(cfg.CommittedCapacitySectorLifetime), - WaitDealsDelay: config.Duration(cfg.WaitDealsDelay), - MakeNewSectorForDeals: cfg.MakeNewSectorForDeals, - MakeCCSectorsAvailable: cfg.MakeCCSectorsAvailable, - AlwaysKeepUnsealedCopy: cfg.AlwaysKeepUnsealedCopy, - FinalizeEarly: cfg.FinalizeEarly, + MaxWaitDealsSectors: cfg.MaxWaitDealsSectors, + MaxSealingSectors: cfg.MaxSealingSectors, + MaxSealingSectorsForDeals: cfg.MaxSealingSectorsForDeals, + PreferNewSectorsForDeals: cfg.PreferNewSectorsForDeals, + MaxUpgradingSectors: cfg.MaxUpgradingSectors, + CommittedCapacitySectorLifetime: config.Duration(cfg.CommittedCapacitySectorLifetime), + WaitDealsDelay: config.Duration(cfg.WaitDealsDelay), + MakeNewSectorForDeals: cfg.MakeNewSectorForDeals, + MinUpgradeSectorExpiration: cfg.MinUpgradeSectorExpiration, + MinTargetUpgradeSectorExpiration: cfg.MinTargetUpgradeSectorExpiration, + MakeCCSectorsAvailable: cfg.MakeCCSectorsAvailable, + AlwaysKeepUnsealedCopy: cfg.AlwaysKeepUnsealedCopy, + FinalizeEarly: cfg.FinalizeEarly, CollateralFromMinerBalance: cfg.CollateralFromMinerBalance, AvailableBalanceBuffer: types.FIL(cfg.AvailableBalanceBuffer), @@ -1024,11 +1026,14 @@ func NewSetSealConfigFunc(r repo.LockedRepo) (dtypes.SetSealingConfigFunc, error func ToSealingConfig(dealmakingCfg config.DealmakingConfig, sealingCfg config.SealingConfig) sealiface.Config { return sealiface.Config{ - MaxWaitDealsSectors: sealingCfg.MaxWaitDealsSectors, - MaxSealingSectors: sealingCfg.MaxSealingSectors, - MaxSealingSectorsForDeals: sealingCfg.MaxSealingSectorsForDeals, - PreferNewSectorsForDeals: sealingCfg.PreferNewSectorsForDeals, - MaxUpgradingSectors: sealingCfg.MaxUpgradingSectors, + MaxWaitDealsSectors: sealingCfg.MaxWaitDealsSectors, + MaxSealingSectors: sealingCfg.MaxSealingSectors, + MaxSealingSectorsForDeals: sealingCfg.MaxSealingSectorsForDeals, + PreferNewSectorsForDeals: sealingCfg.PreferNewSectorsForDeals, + MinUpgradeSectorExpiration: sealingCfg.MinUpgradeSectorExpiration, + MinTargetUpgradeSectorExpiration: sealingCfg.MinTargetUpgradeSectorExpiration, + MaxUpgradingSectors: sealingCfg.MaxUpgradingSectors, + StartEpochSealingBuffer: abi.ChainEpoch(dealmakingCfg.StartEpochSealingBuffer), MakeNewSectorForDeals: sealingCfg.MakeNewSectorForDeals, CommittedCapacitySectorLifetime: time.Duration(sealingCfg.CommittedCapacitySectorLifetime), diff --git a/storage/pipeline/input.go b/storage/pipeline/input.go index 2f203a17b..e86f7b9a9 100644 --- a/storage/pipeline/input.go +++ b/storage/pipeline/input.go @@ -521,7 +521,7 @@ func (m *Sealing) updateInput(ctx context.Context, sp abi.RegisteredSealProof) e return nil } -func (m *Sealing) calcTargetExpiration(ctx context.Context, ssize abi.SectorSize) (minTarget, target abi.ChainEpoch, err error) { +func (m *Sealing) calcTargetExpiration(ctx context.Context, ssize abi.SectorSize, cfg sealiface.Config) (minExp, target abi.ChainEpoch, err error) { var candidates []*pendingPiece for _, piece := range m.pendingPieces { @@ -537,30 +537,49 @@ func (m *Sealing) calcTargetExpiration(ctx context.Context, ssize abi.SectorSize }) var totalBytes uint64 + var full bool + + // Find the expiration of the last deal which can fit into the sector, use that as the initial target for _, candidate := range candidates { totalBytes += uint64(candidate.size) + target = candidate.deal.DealProposal.EndEpoch if totalBytes >= uint64(abi.PaddedPieceSize(ssize).Unpadded()) { - return candidates[0].deal.DealProposal.EndEpoch, candidate.deal.DealProposal.EndEpoch, nil + full = true + break } } - ts, err := m.Api.ChainHead(ctx) - if err != nil { - return 0, 0, xerrors.Errorf("getting current epoch: %w", err) + // if the sector isn't full, use max deal duration as the target + if !full { + ts, err := m.Api.ChainHead(ctx) + if err != nil { + return 0, 0, xerrors.Errorf("getting current epoch: %w", err) + } + + minDur, maxDur := policy.DealDurationBounds(0) + minExp = ts.Height() + minDur + + target = maxDur } - minDur, maxDur := policy.DealDurationBounds(0) - minTarget = ts.Height() + minDur - - if len(candidates) > 0 && candidates[0].deal.DealProposal.EndEpoch > minTarget { - minTarget = candidates[0].deal.DealProposal.EndEpoch + // make sure that at least one deal in the queue is within the expiration + if len(candidates) > 0 && candidates[0].deal.DealProposal.EndEpoch > minExp { + minExp = candidates[0].deal.DealProposal.EndEpoch } - return minTarget, ts.Height() + maxDur, nil + // apply user minimums + if abi.ChainEpoch(cfg.MinUpgradeSectorExpiration) > minExp { + minExp = abi.ChainEpoch(cfg.MinUpgradeSectorExpiration) + } + if abi.ChainEpoch(cfg.MinTargetUpgradeSectorExpiration) > target { + target = abi.ChainEpoch(cfg.MinTargetUpgradeSectorExpiration) + } + + return minExp, target, nil } -func (m *Sealing) maybeUpgradeSector(ctx context.Context, sp abi.RegisteredSealProof, ef expFn) (bool, error) { +func (m *Sealing) maybeUpgradeSector(ctx context.Context, sp abi.RegisteredSealProof, cfg sealiface.Config, ef expFn) (bool, error) { if len(m.available) == 0 { return false, nil } @@ -569,7 +588,7 @@ func (m *Sealing) maybeUpgradeSector(ctx context.Context, sp abi.RegisteredSealP if err != nil { return false, xerrors.Errorf("getting sector size: %w", err) } - minExpiration, targetExpiration, err := m.calcTargetExpiration(ctx, ssize) + minExpiration, targetExpiration, err := m.calcTargetExpiration(ctx, ssize, cfg) if err != nil { return false, xerrors.Errorf("calculating min target expiration: %w", err) } @@ -687,7 +706,7 @@ func (m *Sealing) tryGetDealSector(ctx context.Context, sp abi.RegisteredSealPro "shouldUpgrade", shouldUpgrade) if shouldUpgrade { - got, err := m.maybeUpgradeSector(ctx, sp, ef) + got, err := m.maybeUpgradeSector(ctx, sp, cfg, ef) if err != nil { return err } diff --git a/storage/pipeline/sealiface/config.go b/storage/pipeline/sealiface/config.go index 0470db38e..2db155d5c 100644 --- a/storage/pipeline/sealiface/config.go +++ b/storage/pipeline/sealiface/config.go @@ -20,6 +20,10 @@ type Config struct { PreferNewSectorsForDeals bool + MinUpgradeSectorExpiration uint64 + + MinTargetUpgradeSectorExpiration uint64 + MaxUpgradingSectors uint64 MakeNewSectorForDeals bool From 609dad02a2d223a276c7fc56feb25b881f775d32 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Thu, 15 Sep 2022 10:42:44 -0400 Subject: [PATCH 26/85] actor-cids cli command now defaults to current network version instead of newest --- cli/state.go | 11 +++++++++-- documentation/en/cli-lotus.md | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/cli/state.go b/cli/state.go index a0fc49f30..51c3b3911 100644 --- a/cli/state.go +++ b/cli/state.go @@ -1900,7 +1900,6 @@ var StateSysActorCIDsCmd = &cli.Command{ &cli.UintFlag{ Name: "network-version", Usage: "specify network version", - Value: uint(build.NewestNetworkVersion), }, }, Action: func(cctx *cli.Context) error { @@ -1916,7 +1915,15 @@ var StateSysActorCIDsCmd = &cli.Command{ ctx := ReqContext(cctx) - nv := network.Version(cctx.Uint64("network-version")) + var nv network.Version + if cctx.IsSet("network-version") { + nv = network.Version(cctx.Uint64("network-version")) + } else { + nv, err = api.StateNetworkVersion(ctx, types.EmptyTSK) + if err != nil { + return err + } + } fmt.Printf("Network Version: %d\n", nv) diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index 0a064be76..ad978fd4b 100644 --- a/documentation/en/cli-lotus.md +++ b/documentation/en/cli-lotus.md @@ -2029,7 +2029,7 @@ USAGE: lotus state actor-cids [command options] [arguments...] OPTIONS: - --network-version value specify network version (default: 17) + --network-version value specify network version (default: 0) ``` From b87ade64022c4aa59125190aa11becb931c91321 Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Thu, 15 Sep 2022 17:05:07 -0400 Subject: [PATCH 27/85] docsjen --- cmd/lotus-shed/msig.go | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/cmd/lotus-shed/msig.go b/cmd/lotus-shed/msig.go index dafa9a55b..d70634e72 100644 --- a/cmd/lotus-shed/msig.go +++ b/cmd/lotus-shed/msig.go @@ -7,19 +7,17 @@ import ( "strings" "github.com/ipfs/go-cid" - - "github.com/filecoin-project/lotus/api/v0api" - "github.com/filecoin-project/lotus/chain/types" - - "github.com/filecoin-project/go-address" - - "github.com/filecoin-project/go-state-types/network" - "github.com/filecoin-project/lotus/build" - - "github.com/filecoin-project/go-state-types/abi" - cliutil "github.com/filecoin-project/lotus/cli/util" "github.com/urfave/cli/v2" "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/network" + + "github.com/filecoin-project/lotus/api/v0api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" + cliutil "github.com/filecoin-project/lotus/cli/util" ) var GetFullNodeAPI = cliutil.GetFullNodeAPI From 32ce8f0d9bea93db71e1da213e1dfa9df5f7d569 Mon Sep 17 00:00:00 2001 From: LexLuthr Date: Fri, 16 Sep 2022 11:37:12 +0530 Subject: [PATCH 28/85] display updated & update-cache for storage --- cmd/lotus-miner/storage.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-miner/storage.go b/cmd/lotus-miner/storage.go index 1200448b1..290d128e4 100644 --- a/cmd/lotus-miner/storage.go +++ b/cmd/lotus-miner/storage.go @@ -313,7 +313,7 @@ var storageListCmd = &cli.Command{ for _, s := range sorted { - var cnt [3]int + var cnt [5]int for _, decl := range s.sectors { for i := range cnt { if decl.SectorFileType&(1< Date: Fri, 16 Sep 2022 12:41:48 +0200 Subject: [PATCH 29/85] fix expiration config handling in calcTargetExpiration --- storage/pipeline/input.go | 50 +++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/storage/pipeline/input.go b/storage/pipeline/input.go index e86f7b9a9..e4ca53493 100644 --- a/storage/pipeline/input.go +++ b/storage/pipeline/input.go @@ -521,7 +521,7 @@ func (m *Sealing) updateInput(ctx context.Context, sp abi.RegisteredSealProof) e return nil } -func (m *Sealing) calcTargetExpiration(ctx context.Context, ssize abi.SectorSize, cfg sealiface.Config) (minExp, target abi.ChainEpoch, err error) { +func (m *Sealing) calcTargetExpiration(ctx context.Context, ssize abi.SectorSize, cfg sealiface.Config) (minExpEpoch, targetEpoch abi.ChainEpoch, err error) { var candidates []*pendingPiece for _, piece := range m.pendingPieces { @@ -542,7 +542,7 @@ func (m *Sealing) calcTargetExpiration(ctx context.Context, ssize abi.SectorSize // Find the expiration of the last deal which can fit into the sector, use that as the initial target for _, candidate := range candidates { totalBytes += uint64(candidate.size) - target = candidate.deal.DealProposal.EndEpoch + targetEpoch = candidate.deal.DealProposal.EndEpoch if totalBytes >= uint64(abi.PaddedPieceSize(ssize).Unpadded()) { full = true @@ -550,33 +550,33 @@ func (m *Sealing) calcTargetExpiration(ctx context.Context, ssize abi.SectorSize } } + ts, err := m.Api.ChainHead(ctx) + if err != nil { + return 0, 0, xerrors.Errorf("getting current epoch: %w", err) + } + // if the sector isn't full, use max deal duration as the target if !full { - ts, err := m.Api.ChainHead(ctx) - if err != nil { - return 0, 0, xerrors.Errorf("getting current epoch: %w", err) - } - minDur, maxDur := policy.DealDurationBounds(0) - minExp = ts.Height() + minDur - target = maxDur + minExpEpoch = ts.Height() + minDur + targetEpoch = ts.Height() + maxDur } // make sure that at least one deal in the queue is within the expiration - if len(candidates) > 0 && candidates[0].deal.DealProposal.EndEpoch > minExp { - minExp = candidates[0].deal.DealProposal.EndEpoch + if len(candidates) > 0 && candidates[0].deal.DealProposal.EndEpoch > minExpEpoch { + minExpEpoch = candidates[0].deal.DealProposal.EndEpoch } // apply user minimums - if abi.ChainEpoch(cfg.MinUpgradeSectorExpiration) > minExp { - minExp = abi.ChainEpoch(cfg.MinUpgradeSectorExpiration) + if abi.ChainEpoch(cfg.MinUpgradeSectorExpiration)+ts.Height() > minExpEpoch { + minExpEpoch = abi.ChainEpoch(cfg.MinUpgradeSectorExpiration) + ts.Height() } - if abi.ChainEpoch(cfg.MinTargetUpgradeSectorExpiration) > target { - target = abi.ChainEpoch(cfg.MinTargetUpgradeSectorExpiration) + if abi.ChainEpoch(cfg.MinTargetUpgradeSectorExpiration)+ts.Height() > targetEpoch { + targetEpoch = abi.ChainEpoch(cfg.MinTargetUpgradeSectorExpiration) + ts.Height() } - return minExp, target, nil + return minExpEpoch, targetEpoch, nil } func (m *Sealing) maybeUpgradeSector(ctx context.Context, sp abi.RegisteredSealProof, cfg sealiface.Config, ef expFn) (bool, error) { @@ -588,7 +588,7 @@ func (m *Sealing) maybeUpgradeSector(ctx context.Context, sp abi.RegisteredSealP if err != nil { return false, xerrors.Errorf("getting sector size: %w", err) } - minExpiration, targetExpiration, err := m.calcTargetExpiration(ctx, ssize, cfg) + minExpirationEpoch, targetExpirationEpoch, err := m.calcTargetExpiration(ctx, ssize, cfg) if err != nil { return false, xerrors.Errorf("calculating min target expiration: %w", err) } @@ -598,7 +598,7 @@ func (m *Sealing) maybeUpgradeSector(ctx context.Context, sp abi.RegisteredSealP bestPledge := types.TotalFilecoinInt for s := range m.available { - expiration, pledge, err := ef(s.Number) + expirationEpoch, pledge, err := ef(s.Number) if err != nil { log.Errorw("checking sector expiration", "error", err) continue @@ -620,24 +620,24 @@ func (m *Sealing) maybeUpgradeSector(ctx context.Context, sp abi.RegisteredSealP // if best is below target, we want larger expirations // if best is above target, we want lower pledge, but only if still above target - if bestExpiration < targetExpiration { - if expiration > bestExpiration && slowChecks(s.Number) { - bestExpiration = expiration + if bestExpiration < targetExpirationEpoch { + if expirationEpoch > bestExpiration && slowChecks(s.Number) { + bestExpiration = expirationEpoch bestPledge = pledge candidate = s } continue } - if expiration >= targetExpiration && pledge.LessThan(bestPledge) && slowChecks(s.Number) { - bestExpiration = expiration + if expirationEpoch >= targetExpirationEpoch && pledge.LessThan(bestPledge) && slowChecks(s.Number) { + bestExpiration = expirationEpoch bestPledge = pledge candidate = s } } - if bestExpiration < minExpiration { - log.Infow("Not upgrading any sectors", "available", len(m.available), "pieces", len(m.pendingPieces), "bestExp", bestExpiration, "target", targetExpiration, "min", minExpiration, "candidate", candidate) + if bestExpiration < minExpirationEpoch { + log.Infow("Not upgrading any sectors", "available", len(m.available), "pieces", len(m.pendingPieces), "bestExp", bestExpiration, "target", targetExpirationEpoch, "min", minExpirationEpoch, "candidate", candidate) // didn't find a good sector / no sectors were available return false, nil } From f1e4ecaaa3442457a5fffc3cd7e808d5f73d8de7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 16 Sep 2022 17:51:09 +0200 Subject: [PATCH 30/85] fix: Drop unused PreCommitInfo from pipeline.SectorInfo --- storage/pipeline/cbor_gen.go | 39 +------------------------------ storage/pipeline/fsm_events.go | 1 - storage/pipeline/states_failed.go | 5 ---- storage/pipeline/types.go | 2 -- 4 files changed, 1 insertion(+), 46 deletions(-) diff --git a/storage/pipeline/cbor_gen.go b/storage/pipeline/cbor_gen.go index fe7fa2d53..308508ce7 100644 --- a/storage/pipeline/cbor_gen.go +++ b/storage/pipeline/cbor_gen.go @@ -13,7 +13,6 @@ import ( xerrors "golang.org/x/xerrors" abi "github.com/filecoin-project/go-state-types/abi" - miner "github.com/filecoin-project/go-state-types/builtin/v9/miner" api "github.com/filecoin-project/lotus/api" ) @@ -154,7 +153,7 @@ func (t *SectorInfo) MarshalCBOR(w io.Writer) error { cw := cbg.NewCborWriter(w) - if _, err := cw.Write([]byte{184, 32}); err != nil { + if _, err := cw.Write([]byte{184, 31}); err != nil { return err } @@ -404,22 +403,6 @@ func (t *SectorInfo) MarshalCBOR(w io.Writer) error { return err } - // t.PreCommitInfo (miner.SectorPreCommitInfo) (struct) - if len("PreCommitInfo") > cbg.MaxLength { - return xerrors.Errorf("Value in field \"PreCommitInfo\" was too long") - } - - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("PreCommitInfo"))); err != nil { - return err - } - if _, err := io.WriteString(w, string("PreCommitInfo")); err != nil { - return err - } - - if err := t.PreCommitInfo.MarshalCBOR(cw); err != nil { - return err - } - // t.PreCommitDeposit (big.Int) (struct) if len("PreCommitDeposit") > cbg.MaxLength { return xerrors.Errorf("Value in field \"PreCommitDeposit\" was too long") @@ -1128,26 +1111,6 @@ func (t *SectorInfo) UnmarshalCBOR(r io.Reader) (err error) { if _, err := io.ReadFull(cr, t.Proof[:]); err != nil { return err } - // t.PreCommitInfo (miner.SectorPreCommitInfo) (struct) - case "PreCommitInfo": - - { - - b, err := cr.ReadByte() - if err != nil { - return err - } - if b != cbg.CborNull[0] { - if err := cr.UnreadByte(); err != nil { - return err - } - t.PreCommitInfo = new(miner.SectorPreCommitInfo) - if err := t.PreCommitInfo.UnmarshalCBOR(cr); err != nil { - return xerrors.Errorf("unmarshaling t.PreCommitInfo pointer: %w", err) - } - } - - } // t.PreCommitDeposit (big.Int) (struct) case "PreCommitDeposit": diff --git a/storage/pipeline/fsm_events.go b/storage/pipeline/fsm_events.go index 8ddd529a6..4d41f9070 100644 --- a/storage/pipeline/fsm_events.go +++ b/storage/pipeline/fsm_events.go @@ -205,7 +205,6 @@ type SectorPreCommitted struct { func (evt SectorPreCommitted) apply(state *SectorInfo) { state.PreCommitMessage = &evt.Message state.PreCommitDeposit = evt.PreCommitDeposit - state.PreCommitInfo = &evt.PreCommitInfo } type SectorSeedReady struct { diff --git a/storage/pipeline/states_failed.go b/storage/pipeline/states_failed.go index 38f030333..09e090bc8 100644 --- a/storage/pipeline/states_failed.go +++ b/storage/pipeline/states_failed.go @@ -391,11 +391,6 @@ func (m *Sealing) handleDealsExpired(ctx statemachine.Context, sector SectorInfo return xerrors.Errorf("sector is committed on-chain, but we're in DealsExpired") } - if sector.PreCommitInfo == nil { - // TODO: Create a separate state which will remove those pieces, and go back to PC1 - log.Errorf("non-precommitted sector with expired deals, can't recover from this yet") - } - // Not much to do here, we can't go back in time to commit this sector return ctx.Send(SectorRemove{}) } diff --git a/storage/pipeline/types.go b/storage/pipeline/types.go index 0419aabc2..69e2b4ee0 100644 --- a/storage/pipeline/types.go +++ b/storage/pipeline/types.go @@ -7,7 +7,6 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" - "github.com/filecoin-project/go-state-types/builtin/v9/miner" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/types" @@ -74,7 +73,6 @@ type SectorInfo struct { CommR *cid.Cid // SectorKey Proof []byte - PreCommitInfo *miner.SectorPreCommitInfo PreCommitDeposit big.Int PreCommitMessage *cid.Cid PreCommitTipSet types.TipSetKey From 4382bbcc966440f4369a53a691f3c54f9d7e0772 Mon Sep 17 00:00:00 2001 From: Aayush Date: Fri, 16 Sep 2022 14:30:05 -0400 Subject: [PATCH 31/85] feat: add logs to markets event handler --- chain/events/events_called.go | 2 +- markets/storageadapter/ondealsectorcommitted.go | 8 ++++---- markets/storageadapter/ondealsectorcommitted_test.go | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/chain/events/events_called.go b/chain/events/events_called.go index 7fc92e6a9..3ac02b2f7 100644 --- a/chain/events/events_called.go +++ b/chain/events/events_called.go @@ -554,7 +554,7 @@ func (me *messageEvents) Called(ctx context.Context, check CheckFunc, msgHnd Msg id, err := me.hcAPI.onHeadChanged(ctx, check, hnd, rev, confidence, timeout) if err != nil { - return err + return xerrors.Errorf("on head changed error: %w", err) } me.lk.Lock() diff --git a/markets/storageadapter/ondealsectorcommitted.go b/markets/storageadapter/ondealsectorcommitted.go index 3c3f28824..53171eaeb 100644 --- a/markets/storageadapter/ondealsectorcommitted.go +++ b/markets/storageadapter/ondealsectorcommitted.go @@ -72,7 +72,7 @@ func (mgr *SectorCommittedManager) OnDealSectorPreCommitted(ctx context.Context, // Note: the error returned from here will end up being returned // from OnDealSectorPreCommitted so no need to call the callback // with the error - return false, false, err + return false, false, xerrors.Errorf("failed to check deal activity: %w", err) } if isActive { @@ -89,7 +89,7 @@ func (mgr *SectorCommittedManager) OnDealSectorPreCommitted(ctx context.Context, diff, err := mgr.dpc.diffPreCommits(ctx, provider, dealInfo.PublishMsgTipSet, ts.Key()) if err != nil { - return false, false, err + return false, false, xerrors.Errorf("failed to diff precommits: %w", err) } for _, info := range diff.Added { @@ -139,7 +139,7 @@ func (mgr *SectorCommittedManager) OnDealSectorPreCommitted(ctx context.Context, // current deal ID from the publish message CID res, err := mgr.dealInfo.GetCurrentDealInfo(ctx, ts.Key(), &proposal, publishCid) if err != nil { - return false, err + return false, xerrors.Errorf("failed to get dealinfo: %w", err) } // If this is a replica update method that succeeded the deal is active @@ -159,7 +159,7 @@ func (mgr *SectorCommittedManager) OnDealSectorPreCommitted(ctx context.Context, // Extract the message parameters sn, err := dealSectorInPreCommitMsg(msg, res) if err != nil { - return false, err + return false, xerrors.Errorf("failed to extract message params: %w", err) } if sn != nil { diff --git a/markets/storageadapter/ondealsectorcommitted_test.go b/markets/storageadapter/ondealsectorcommitted_test.go index 2ed1d2f39..22367420a 100644 --- a/markets/storageadapter/ondealsectorcommitted_test.go +++ b/markets/storageadapter/ondealsectorcommitted_test.go @@ -145,7 +145,7 @@ func TestOnDealSectorPreCommitted(t *testing.T) { "error getting current deal info in check func": { currentDealInfoErr: errors.New("something went wrong"), expectedCBCallCount: 0, - expectedError: xerrors.Errorf("failed to set up called handler: failed to look up deal on chain: something went wrong"), + expectedError: xerrors.Errorf("failed to set up called handler: failed to check deal activity: failed to look up deal on chain: something went wrong"), }, "sector already active": { currentDealInfo: pipeline.CurrentDealInfo{ @@ -162,7 +162,7 @@ func TestOnDealSectorPreCommitted(t *testing.T) { PublishMsgTipSet: types.EmptyTSK, }, expectedCBCallCount: 0, - expectedError: xerrors.Errorf("failed to set up called handler: deal %d was slashed at epoch %d", dealID, slashedDeal.State.SlashEpoch), + expectedError: xerrors.Errorf("failed to set up called handler: failed to check deal activity: deal %d was slashed at epoch %d", dealID, slashedDeal.State.SlashEpoch), }, "error getting current deal info in called func": { currentDealInfo: pipeline.CurrentDealInfo{ @@ -180,7 +180,7 @@ func TestOnDealSectorPreCommitted(t *testing.T) { }, }, expectedCBCallCount: 1, - expectedCBError: errors.New("handling applied event: something went wrong"), + expectedCBError: errors.New("handling applied event: failed to get dealinfo: something went wrong"), }, "proposed deal epoch timeout": { currentDealInfo: pipeline.CurrentDealInfo{ From d68bb937d81cd83b9e5b46d8870e6e88a9942cdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Mon, 19 Sep 2022 09:49:27 +0200 Subject: [PATCH 32/85] fix: ffiwrapper: Close readers in AddPiece --- storage/sealer/ffiwrapper/sealer_cgo.go | 20 +++++- storage/sealer/ffiwrapper/sealer_test.go | 84 ++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 3 deletions(-) diff --git a/storage/sealer/ffiwrapper/sealer_cgo.go b/storage/sealer/ffiwrapper/sealer_cgo.go index cf4eddff0..785d11eb8 100644 --- a/storage/sealer/ffiwrapper/sealer_cgo.go +++ b/storage/sealer/ffiwrapper/sealer_cgo.go @@ -60,7 +60,9 @@ func (sb *Sealer) DataCid(ctx context.Context, pieceSize abi.UnpaddedPieceSize, log.Warnf("DataCid: cannot close pieceData reader %T because it is not an io.Closer", origPieceData) return } - closer.Close() //nolint:errcheck + if err := closer.Close(); err != nil { + log.Warnw("closing pieceData in DataCid", "error", err) + } }() pieceData = io.LimitReader(io.MultiReader( @@ -182,7 +184,19 @@ func (sb *Sealer) DataCid(ctx context.Context, pieceSize abi.UnpaddedPieceSize, }, nil } -func (sb *Sealer) AddPiece(ctx context.Context, sector storiface.SectorRef, existingPieceSizes []abi.UnpaddedPieceSize, pieceSize abi.UnpaddedPieceSize, file storiface.Data) (abi.PieceInfo, error) { +func (sb *Sealer) AddPiece(ctx context.Context, sector storiface.SectorRef, existingPieceSizes []abi.UnpaddedPieceSize, pieceSize abi.UnpaddedPieceSize, pieceData storiface.Data) (abi.PieceInfo, error) { + origPieceData := pieceData + defer func() { + closer, ok := origPieceData.(io.Closer) + if !ok { + log.Warnf("AddPiece: cannot close pieceData reader %T because it is not an io.Closer", origPieceData) + return + } + if err := closer.Close(); err != nil { + log.Warnw("closing pieceData in AddPiece", "error", err) + } + }() + // TODO: allow tuning those: chunk := abi.PaddedPieceSize(4 << 20) parallel := runtime.NumCPU() @@ -248,7 +262,7 @@ func (sb *Sealer) AddPiece(ctx context.Context, sector storiface.SectorRef, exis pw := fr32.NewPadWriter(w) - pr := io.TeeReader(io.LimitReader(file, int64(pieceSize)), pw) + pr := io.TeeReader(io.LimitReader(pieceData, int64(pieceSize)), pw) throttle := make(chan []byte, parallel) piecePromises := make([]func() (abi.PieceInfo, error), 0) diff --git a/storage/sealer/ffiwrapper/sealer_test.go b/storage/sealer/ffiwrapper/sealer_test.go index 71fd7c4a0..654caf784 100644 --- a/storage/sealer/ffiwrapper/sealer_test.go +++ b/storage/sealer/ffiwrapper/sealer_test.go @@ -991,3 +991,87 @@ func TestPoStChallengeAssumptions(t *testing.T) { require.Len(t, c1.Challenges, 3) } } + +func TestDCAPCloses(t *testing.T) { + sz := abi.PaddedPieceSize(2 << 10).Unpadded() + + cdir, err := ioutil.TempDir("", "sbtest-c-") + if err != nil { + t.Fatal(err) + } + miner := abi.ActorID(123) + + sp := &basicfs.Provider{ + Root: cdir, + } + sb, err := New(sp) + if err != nil { + t.Fatalf("%+v", err) + } + cleanup := func() { + if t.Failed() { + fmt.Printf("not removing %s\n", cdir) + return + } + if err := os.RemoveAll(cdir); err != nil { + t.Error(err) + } + } + t.Cleanup(cleanup) + + t.Run("DataCid", func(t *testing.T) { + r := rand.New(rand.NewSource(0x7e5)) + + clr := &closeAssertReader{ + Reader: io.LimitReader(r, int64(sz)), + } + + c, err := sb.DataCid(context.TODO(), sz, clr) + if err != nil { + t.Fatal(err) + } + + require.Equal(t, "baga6ea4seaqeje7jy4hufnybpo7ckxzujaigqbcxhdjq7ojb4b6xzgqdugkyciq", c.PieceCID.String()) + require.True(t, clr.closed) + }) + + t.Run("AddPiece", func(t *testing.T) { + r := rand.New(rand.NewSource(0x7e5)) + + clr := &closeAssertReader{ + Reader: io.LimitReader(r, int64(sz)), + } + + c, err := sb.AddPiece(context.TODO(), storiface.SectorRef{ + ID: abi.SectorID{ + Miner: miner, + Number: 0, + }, + ProofType: abi.RegisteredSealProof_StackedDrg2KiBV1_1, + }, nil, sz, clr) + if err != nil { + t.Fatal(err) + } + + require.Equal(t, "baga6ea4seaqeje7jy4hufnybpo7ckxzujaigqbcxhdjq7ojb4b6xzgqdugkyciq", c.PieceCID.String()) + require.True(t, clr.closed) + }) + +} + +type closeAssertReader struct { + io.Reader + closed bool +} + +func (c *closeAssertReader) Close() error { + if c.closed { + panic("double close") + } + + c.closed = true + + return nil +} + +var _ io.Closer = &closeAssertReader{} From 29135aa77cccc4362119cca76440d123f7f28cdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 24 Aug 2022 15:27:27 -0400 Subject: [PATCH 33/85] sector import: Initial api scaffolding --- api/api_storage.go | 88 +++++++++++++++ api/docgen/docgen.go | 4 + api/proxy_gen.go | 13 +++ build/openrpc/miner.json.gz | Bin 15508 -> 15743 bytes build/openrpc/worker.json.gz | Bin 5075 -> 5035 bytes documentation/en/api-v0-methods-miner.md | 126 ++++++++++++++++++++++ node/impl/storminer.go | 4 + storage/pipeline/checks.go | 22 ++-- storage/pipeline/receive.go | 78 ++++++++++++++ storage/pipeline/states_replica_update.go | 4 +- storage/pipeline/states_sealing.go | 2 +- 11 files changed, 327 insertions(+), 14 deletions(-) create mode 100644 storage/pipeline/receive.go diff --git a/api/api_storage.go b/api/api_storage.go index e9c7aad61..02af793c9 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -3,6 +3,7 @@ package api import ( "bytes" "context" + "net/http" "time" "github.com/google/uuid" @@ -17,6 +18,7 @@ import ( "github.com/filecoin-project/go-fil-markets/storagemarket" "github.com/filecoin-project/go-jsonrpc/auth" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/builtin/v8/market" "github.com/filecoin-project/go-state-types/builtin/v9/miner" abinetwork "github.com/filecoin-project/go-state-types/network" @@ -144,6 +146,8 @@ type StorageMiner interface { // SectorNumFree drops a sector reservation SectorNumFree(ctx context.Context, name string) error //perm:admin + SectorReceive(ctx context.Context, meta RemoteSectorMeta) error + // WorkerConnect tells the node to connect to workers RPC WorkerConnect(context.Context, string) error //perm:admin retry:true WorkerStats(context.Context) (map[uuid.UUID]storiface.WorkerStats, error) //perm:admin @@ -504,3 +508,87 @@ type NumAssignerMeta struct { Next abi.SectorNumber } + +type RemoteSectorMeta struct { + //////// + // BASIC SECTOR INFORMATION + + // State specifies the first state the sector will enter after being imported + // Must be one of the following states: + // * Packing + // * GetTicket + // * PreCommitting + // * SubmitCommit + // * Proving/Available + State SectorState + + Sector abi.SectorID + Type abi.RegisteredSealProof + + //////// + // SEALING METADATA + // (allows lotus to continue the sealing process) + + // Required in Packing and later + Pieces []SectorPiece // todo better type? + + // Required in PreCommitting and later + TicketValue abi.SealRandomness + TicketEpoch abi.ChainEpoch + PreCommit1Out storiface.PreCommit1Out // todo specify better + + CommD *cid.Cid + CommR *cid.Cid // SectorKey + + // Required in SubmitCommit and later + PreCommitInfo *miner.SectorPreCommitInfo + PreCommitDeposit big.Int + PreCommitMessage *cid.Cid + PreCommitTipSet types.TipSetKey + + SeedValue abi.InteractiveSealRandomness + SeedEpoch abi.ChainEpoch + + CommitProof []byte + + // Required in Proving/Available + CommitMessage *cid.Cid + + // Optional sector metadata to import + Log []SectorLog + + //////// + // SECTOR DATA SOURCE + + // Sector urls - lotus will use those for fetching files into local storage + + // Required in all states + DataUnsealed *SectorData + + // Required in PreCommitting and later + DataSealed *SectorData + DataCache *SectorData + + //////// + // SEALING SERVICE HOOKS + + // todo Commit1Provider + // todo OnDone / OnStateChange +} + +type SectorData struct { + // Local when set to true indicates to lotus that sector data is already + // available locally; When set lotus will skip fetching sector data, and + // only check that sector data exists in sector storage + Local bool + + // URL to the sector data + // For sealed/unsealed sector, lotus expects octet-stream + // For cache, lotus expects a tar archive with cache files (todo maybe use not-tar; specify what files with what paths must be present) + // Valid schemas: + // - http:// / https:// + URL string + + // optional http headers to use when requesting sector data + Headers http.Header +} diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index 40b6e6078..47192bfd9 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -6,6 +6,7 @@ import ( "go/ast" "go/parser" "go/token" + "net/http" "path/filepath" "reflect" "strings" @@ -340,6 +341,9 @@ func init() { "": bitfield.NewFromSet([]uint64{5, 6, 7, 10}), }) + addExample(http.Header{ + "Authorization": []string{"Bearer ey.."}, + }) } func GetAPIType(name, pkg string) (i interface{}, t reflect.Type, permStruct []reflect.Type) { diff --git a/api/proxy_gen.go b/api/proxy_gen.go index 43bd40f83..fd31166a9 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -846,6 +846,8 @@ type StorageMinerStruct struct { SectorPreCommitPending func(p0 context.Context) ([]abi.SectorID, error) `perm:"admin"` + SectorReceive func(p0 context.Context, p1 RemoteSectorMeta) error `` + SectorRemove func(p0 context.Context, p1 abi.SectorNumber) error `perm:"admin"` SectorSetExpectedSealDuration func(p0 context.Context, p1 time.Duration) error `perm:"write"` @@ -5039,6 +5041,17 @@ func (s *StorageMinerStub) SectorPreCommitPending(p0 context.Context) ([]abi.Sec return *new([]abi.SectorID), ErrNotSupported } +func (s *StorageMinerStruct) SectorReceive(p0 context.Context, p1 RemoteSectorMeta) error { + if s.Internal.SectorReceive == nil { + return ErrNotSupported + } + return s.Internal.SectorReceive(p0, p1) +} + +func (s *StorageMinerStub) SectorReceive(p0 context.Context, p1 RemoteSectorMeta) error { + return ErrNotSupported +} + func (s *StorageMinerStruct) SectorRemove(p0 context.Context, p1 abi.SectorNumber) error { if s.Internal.SectorRemove == nil { return ErrNotSupported diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index e9637ed2e2521a7ea0c87413fd84da1b6fc76906..99ca3b3666cabaaf974b06194236752f869832ef 100644 GIT binary patch literal 15743 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 R+xq9ngH2O10SpxA{{f?KKKB3s literal 15508 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 WJw4q%{l5SJ0RR70n%LKz_5uK5cl+!B diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index 17e7677083f186be67190da912ff10587e430214..f0d2db3c69241b2c6b7cfd999ad7f49e94f7a063 100644 GIT binary patch literal 5035 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 deal.Proposal.StartEpoch { - return &ErrExpiredDeals{xerrors.Errorf("piece %d (of %d) of sector %d refers expired deal %d - should start at %d, head %d", i, len(si.Pieces), si.SectorNumber, p.DealInfo.DealID, deal.Proposal.StartEpoch, ts.Height())} + return &ErrExpiredDeals{xerrors.Errorf("piece %d (of %d) of sector %d refers expired deal %d - should start at %d, head %d", i, len(pieces), sn, p.DealInfo.DealID, deal.Proposal.StartEpoch, ts.Height())} } } if mustHaveDeals && dealCount <= 0 { - return &ErrNoDeals{(xerrors.Errorf("sector %d must have deals, but does not", si.SectorNumber))} + return &ErrNoDeals{xerrors.Errorf("sector %d must have deals, but does not", sn)} } return nil @@ -95,7 +95,7 @@ func checkPieces(ctx context.Context, maddr address.Address, si SectorInfo, api // // matches pieces, and that the seal ticket isn't expired func checkPrecommit(ctx context.Context, maddr address.Address, si SectorInfo, tsk types.TipSetKey, height abi.ChainEpoch, api SealingAPI) (err error) { - if err := checkPieces(ctx, maddr, si, api, false); err != nil { + if err := checkPieces(ctx, maddr, si.SectorNumber, si.Pieces, api, false); err != nil { return err } @@ -210,7 +210,7 @@ func (m *Sealing) checkCommit(ctx context.Context, si SectorInfo, proof []byte, return &ErrInvalidProof{xerrors.New("invalid proof (compute error?)")} } - if err := checkPieces(ctx, m.maddr, si, m.Api, false); err != nil { + if err := checkPieces(ctx, m.maddr, si.SectorNumber, si.Pieces, m.Api, false); err != nil { return err } @@ -220,7 +220,7 @@ func (m *Sealing) checkCommit(ctx context.Context, si SectorInfo, proof []byte, // check that sector info is good after running a replica update func checkReplicaUpdate(ctx context.Context, maddr address.Address, si SectorInfo, tsk types.TipSetKey, api SealingAPI) error { - if err := checkPieces(ctx, maddr, si, api, true); err != nil { + if err := checkPieces(ctx, maddr, si.SectorNumber, si.Pieces, api, true); err != nil { return err } if !si.CCUpdate { diff --git a/storage/pipeline/receive.go b/storage/pipeline/receive.go new file mode 100644 index 000000000..e32fd4f86 --- /dev/null +++ b/storage/pipeline/receive.go @@ -0,0 +1,78 @@ +package sealing + +import ( + "context" + + "github.com/ipfs/go-datastore" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/api" +) + +func (m *Sealing) Receive(ctx context.Context, meta api.RemoteSectorMeta) error { + if err := m.checkSectorMeta(ctx, meta); err != nil { + return err + } + + panic("impl me") +} + +func (m *Sealing) checkSectorMeta(ctx context.Context, meta api.RemoteSectorMeta) error { + { + mid, err := address.IDFromAddress(m.maddr) + if err != nil { + panic(err) + } + + if meta.Sector.Miner != abi.ActorID(mid) { + return xerrors.Errorf("sector for wrong actor - expected actor id %d, sector was for actor %d", mid, meta.Sector.Miner) + } + } + + { + // initial sanity check, doesn't prevent races + _, err := m.GetSectorInfo(meta.Sector.Number) + if err != nil && !xerrors.Is(err, datastore.ErrNotFound) { + return err + } + if err == nil { + return xerrors.Errorf("sector with ID %d already exists in the sealing pipeline", meta.Sector.Number) + } + } + + { + spt, err := m.currentSealProof(ctx) + if err != nil { + return err + } + + if meta.Type != spt { + return xerrors.Errorf("sector seal proof type doesn't match current seal proof type (%d!=%d)", meta.Type, spt) + } + } + + switch SectorState(meta.State) { + case Packing: + //checkPieces(ctx, m.maddr, meta.Sector.Number, meta.Pieces, m.Api, false) + + fallthrough + case GetTicket: + + fallthrough + case PreCommitting: + + fallthrough + case SubmitCommit: + + fallthrough + case Proving, Available: + + return nil + default: + return xerrors.Errorf("imported sector State in not supported") + } + +} diff --git a/storage/pipeline/states_replica_update.go b/storage/pipeline/states_replica_update.go index 0261201f3..a78d6dee8 100644 --- a/storage/pipeline/states_replica_update.go +++ b/storage/pipeline/states_replica_update.go @@ -26,7 +26,7 @@ func (m *Sealing) handleReplicaUpdate(ctx statemachine.Context, sector SectorInf return ctx.Send(SectorAbortUpgrade{xerrors.New("sector had no deals")}) } - if err := checkPieces(ctx.Context(), m.maddr, sector, m.Api, true); err != nil { // Sanity check state + if err := checkPieces(ctx.Context(), m.maddr, sector.SectorNumber, sector.Pieces, m.Api, true); err != nil { // Sanity check state return handleErrors(ctx, err, sector) } out, err := m.sealer.ReplicaUpdate(sector.sealingCtx(ctx.Context()), m.minerSector(sector.SectorType, sector.SectorNumber), sector.pieceInfos()) @@ -66,7 +66,7 @@ func (m *Sealing) handleProveReplicaUpdate(ctx statemachine.Context, sector Sect return ctx.Send(SectorProveReplicaUpdateFailed{xerrors.Errorf("prove replica update (1) failed: %w", err)}) } - if err := checkPieces(ctx.Context(), m.maddr, sector, m.Api, true); err != nil { // Sanity check state + if err := checkPieces(ctx.Context(), m.maddr, sector.SectorNumber, sector.Pieces, m.Api, true); err != nil { // Sanity check state return handleErrors(ctx, err, sector) } diff --git a/storage/pipeline/states_sealing.go b/storage/pipeline/states_sealing.go index f769341dd..f2d61fc62 100644 --- a/storage/pipeline/states_sealing.go +++ b/storage/pipeline/states_sealing.go @@ -207,7 +207,7 @@ func (m *Sealing) handleGetTicket(ctx statemachine.Context, sector SectorInfo) e } func (m *Sealing) handlePreCommit1(ctx statemachine.Context, sector SectorInfo) error { - if err := checkPieces(ctx.Context(), m.maddr, sector, m.Api, false); err != nil { // Sanity check state + if err := checkPieces(ctx.Context(), m.maddr, sector.SectorNumber, sector.Pieces, m.Api, false); err != nil { // Sanity check state switch err.(type) { case *ErrApi: log.Errorf("handlePreCommit1: api error, not proceeding: %+v", err) From 5c485c337566b4bd84a297b8b129c7f4c13f2deb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 25 Aug 2022 20:37:36 -0400 Subject: [PATCH 34/85] sealing: Drop redundant Piece type --- api/cbor_gen.go | 123 +++++++++++++++++++ cmd/lotus-miner/init.go | 2 +- gen/main.go | 1 + itests/kit/ensemble.go | 2 +- storage/pipeline/cbor_gen.go | 139 ++-------------------- storage/pipeline/checks.go | 3 +- storage/pipeline/fsm_events.go | 5 +- storage/pipeline/gen/main.go | 1 - storage/pipeline/input.go | 4 +- storage/pipeline/precommit_policy.go | 5 +- storage/pipeline/precommit_policy_test.go | 6 +- storage/pipeline/states_failed_test.go | 2 +- storage/pipeline/types.go | 18 +-- storage/pipeline/types_test.go | 2 +- 14 files changed, 152 insertions(+), 161 deletions(-) diff --git a/api/cbor_gen.go b/api/cbor_gen.go index 66655fd75..0932bdbc0 100644 --- a/api/cbor_gen.go +++ b/api/cbor_gen.go @@ -1005,6 +1005,129 @@ func (t *PieceDealInfo) UnmarshalCBOR(r io.Reader) (err error) { return nil } +func (t *SectorPiece) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write([]byte{162}); err != nil { + return err + } + + // t.Piece (abi.PieceInfo) (struct) + if len("Piece") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Piece\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("Piece"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Piece")); err != nil { + return err + } + + if err := t.Piece.MarshalCBOR(cw); err != nil { + return err + } + + // t.DealInfo (api.PieceDealInfo) (struct) + if len("DealInfo") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"DealInfo\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("DealInfo"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("DealInfo")); err != nil { + return err + } + + if err := t.DealInfo.MarshalCBOR(cw); err != nil { + return err + } + return nil +} + +func (t *SectorPiece) UnmarshalCBOR(r io.Reader) (err error) { + *t = SectorPiece{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("SectorPiece: map struct too large (%d)", extra) + } + + var name string + n := extra + + for i := uint64(0); i < n; i++ { + + { + sval, err := cbg.ReadString(cr) + if err != nil { + return err + } + + name = string(sval) + } + + switch name { + // t.Piece (abi.PieceInfo) (struct) + case "Piece": + + { + + if err := t.Piece.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.Piece: %w", err) + } + + } + // t.DealInfo (api.PieceDealInfo) (struct) + case "DealInfo": + + { + + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + t.DealInfo = new(PieceDealInfo) + if err := t.DealInfo.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.DealInfo pointer: %w", err) + } + } + + } + + default: + // Field doesn't exist on this type, so ignore it + cbg.ScanForLinks(r, func(cid.Cid) {}) + } + } + + return nil +} func (t *DealSchedule) MarshalCBOR(w io.Writer) error { if t == nil { _, err := w.Write(cbg.CborNull) diff --git a/cmd/lotus-miner/init.go b/cmd/lotus-miner/init.go index dd245f038..ce69fad5b 100644 --- a/cmd/lotus-miner/init.go +++ b/cmd/lotus-miner/init.go @@ -314,7 +314,7 @@ func migratePreSealMeta(ctx context.Context, api v1api.FullNode, metadata string info := &pipeline.SectorInfo{ State: pipeline.Proving, SectorNumber: sector.SectorID, - Pieces: []pipeline.Piece{ + Pieces: []lapi.SectorPiece{ { Piece: abi.PieceInfo{ Size: abi.PaddedPieceSize(meta.SectorSize), diff --git a/gen/main.go b/gen/main.go index e5fc2ed5f..77f340813 100644 --- a/gen/main.go +++ b/gen/main.go @@ -65,6 +65,7 @@ func main() { api.SealTicket{}, api.SealSeed{}, api.PieceDealInfo{}, + api.SectorPiece{}, api.DealSchedule{}, ) if err != nil { diff --git a/itests/kit/ensemble.go b/itests/kit/ensemble.go index 98d7b178f..bcc2d93c2 100644 --- a/itests/kit/ensemble.go +++ b/itests/kit/ensemble.go @@ -962,7 +962,7 @@ func importPreSealMeta(ctx context.Context, meta genesis.Miner, mds dtypes.Metad info := &pipeline.SectorInfo{ State: pipeline.Proving, SectorNumber: sector.SectorID, - Pieces: []pipeline.Piece{ + Pieces: []api.SectorPiece{ { Piece: abi.PieceInfo{ Size: abi.PaddedPieceSize(meta.SectorSize), diff --git a/storage/pipeline/cbor_gen.go b/storage/pipeline/cbor_gen.go index 308508ce7..0d9d1a918 100644 --- a/storage/pipeline/cbor_gen.go +++ b/storage/pipeline/cbor_gen.go @@ -22,129 +22,6 @@ var _ = cid.Undef var _ = math.E var _ = sort.Sort -func (t *Piece) MarshalCBOR(w io.Writer) error { - if t == nil { - _, err := w.Write(cbg.CborNull) - return err - } - - cw := cbg.NewCborWriter(w) - - if _, err := cw.Write([]byte{162}); err != nil { - return err - } - - // t.Piece (abi.PieceInfo) (struct) - if len("Piece") > cbg.MaxLength { - return xerrors.Errorf("Value in field \"Piece\" was too long") - } - - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("Piece"))); err != nil { - return err - } - if _, err := io.WriteString(w, string("Piece")); err != nil { - return err - } - - if err := t.Piece.MarshalCBOR(cw); err != nil { - return err - } - - // t.DealInfo (api.PieceDealInfo) (struct) - if len("DealInfo") > cbg.MaxLength { - return xerrors.Errorf("Value in field \"DealInfo\" was too long") - } - - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("DealInfo"))); err != nil { - return err - } - if _, err := io.WriteString(w, string("DealInfo")); err != nil { - return err - } - - if err := t.DealInfo.MarshalCBOR(cw); err != nil { - return err - } - return nil -} - -func (t *Piece) UnmarshalCBOR(r io.Reader) (err error) { - *t = Piece{} - - cr := cbg.NewCborReader(r) - - maj, extra, err := cr.ReadHeader() - if err != nil { - return err - } - defer func() { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - }() - - if maj != cbg.MajMap { - return fmt.Errorf("cbor input should be of type map") - } - - if extra > cbg.MaxLength { - return fmt.Errorf("Piece: map struct too large (%d)", extra) - } - - var name string - n := extra - - for i := uint64(0); i < n; i++ { - - { - sval, err := cbg.ReadString(cr) - if err != nil { - return err - } - - name = string(sval) - } - - switch name { - // t.Piece (abi.PieceInfo) (struct) - case "Piece": - - { - - if err := t.Piece.UnmarshalCBOR(cr); err != nil { - return xerrors.Errorf("unmarshaling t.Piece: %w", err) - } - - } - // t.DealInfo (api.PieceDealInfo) (struct) - case "DealInfo": - - { - - b, err := cr.ReadByte() - if err != nil { - return err - } - if b != cbg.CborNull[0] { - if err := cr.UnreadByte(); err != nil { - return err - } - t.DealInfo = new(api.PieceDealInfo) - if err := t.DealInfo.UnmarshalCBOR(cr); err != nil { - return xerrors.Errorf("unmarshaling t.DealInfo pointer: %w", err) - } - } - - } - - default: - // Field doesn't exist on this type, so ignore it - cbg.ScanForLinks(r, func(cid.Cid) {}) - } - } - - return nil -} func (t *SectorInfo) MarshalCBOR(w io.Writer) error { if t == nil { _, err := w.Write(cbg.CborNull) @@ -240,7 +117,7 @@ func (t *SectorInfo) MarshalCBOR(w io.Writer) error { } } - // t.Pieces ([]sealing.Piece) (slice) + // t.Pieces ([]api.SectorPiece) (slice) if len("Pieces") > cbg.MaxLength { return xerrors.Errorf("Value in field \"Pieces\" was too long") } @@ -573,7 +450,7 @@ func (t *SectorInfo) MarshalCBOR(w io.Writer) error { return err } - // t.CCPieces ([]sealing.Piece) (slice) + // t.CCPieces ([]api.SectorPiece) (slice) if len("CCPieces") > cbg.MaxLength { return xerrors.Errorf("Value in field \"CCPieces\" was too long") } @@ -943,7 +820,7 @@ func (t *SectorInfo) UnmarshalCBOR(r io.Reader) (err error) { t.CreationTime = int64(extraI) } - // t.Pieces ([]sealing.Piece) (slice) + // t.Pieces ([]api.SectorPiece) (slice) case "Pieces": maj, extra, err = cr.ReadHeader() @@ -960,12 +837,12 @@ func (t *SectorInfo) UnmarshalCBOR(r io.Reader) (err error) { } if extra > 0 { - t.Pieces = make([]Piece, extra) + t.Pieces = make([]api.SectorPiece, extra) } for i := 0; i < int(extra); i++ { - var v Piece + var v api.SectorPiece if err := v.UnmarshalCBOR(cr); err != nil { return err } @@ -1273,7 +1150,7 @@ func (t *SectorInfo) UnmarshalCBOR(r io.Reader) (err error) { default: return fmt.Errorf("booleans are either major type 7, value 20 or 21 (got %d)", extra) } - // t.CCPieces ([]sealing.Piece) (slice) + // t.CCPieces ([]api.SectorPiece) (slice) case "CCPieces": maj, extra, err = cr.ReadHeader() @@ -1290,12 +1167,12 @@ func (t *SectorInfo) UnmarshalCBOR(r io.Reader) (err error) { } if extra > 0 { - t.CCPieces = make([]Piece, extra) + t.CCPieces = make([]api.SectorPiece, extra) } for i := 0; i < int(extra); i++ { - var v Piece + var v api.SectorPiece if err := v.UnmarshalCBOR(cr); err != nil { return err } diff --git a/storage/pipeline/checks.go b/storage/pipeline/checks.go index ce0512c2b..10f7a9eaf 100644 --- a/storage/pipeline/checks.go +++ b/storage/pipeline/checks.go @@ -13,6 +13,7 @@ import ( "github.com/filecoin-project/go-state-types/crypto" prooftypes "github.com/filecoin-project/go-state-types/proof" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" ) @@ -41,7 +42,7 @@ type ErrCommitWaitFailed struct{ error } type ErrBadRU struct{ error } type ErrBadPR struct{ error } -func checkPieces(ctx context.Context, maddr address.Address, sn abi.SectorNumber, pieces []Piece, api SealingAPI, mustHaveDeals bool) error { +func checkPieces(ctx context.Context, maddr address.Address, sn abi.SectorNumber, pieces []api.SectorPiece, api SealingAPI, mustHaveDeals bool) error { ts, err := api.ChainHead(ctx) if err != nil { return &ErrApi{xerrors.Errorf("getting chain head: %w", err)} diff --git a/storage/pipeline/fsm_events.go b/storage/pipeline/fsm_events.go index 4d41f9070..ac9c5775d 100644 --- a/storage/pipeline/fsm_events.go +++ b/storage/pipeline/fsm_events.go @@ -10,6 +10,7 @@ import ( "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/builtin/v9/miner" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/storage/sealer/storiface" ) @@ -87,7 +88,7 @@ func (evt SectorAddPiece) apply(state *SectorInfo) { } type SectorPieceAdded struct { - NewPieces []Piece + NewPieces []api.SectorPiece } func (evt SectorPieceAdded) apply(state *SectorInfo) { @@ -113,7 +114,7 @@ type SectorPacked struct{ FillerPieces []abi.PieceInfo } func (evt SectorPacked) apply(state *SectorInfo) { for idx := range evt.FillerPieces { - state.Pieces = append(state.Pieces, Piece{ + state.Pieces = append(state.Pieces, api.SectorPiece{ Piece: evt.FillerPieces[idx], DealInfo: nil, // filler pieces don't have deals associated with them }) diff --git a/storage/pipeline/gen/main.go b/storage/pipeline/gen/main.go index 6da1d96b7..578c81478 100644 --- a/storage/pipeline/gen/main.go +++ b/storage/pipeline/gen/main.go @@ -11,7 +11,6 @@ import ( func main() { err := gen.WriteMapEncodersToFile("./cbor_gen.go", "sealing", - sealing.Piece{}, sealing.SectorInfo{}, sealing.Log{}, ) diff --git a/storage/pipeline/input.go b/storage/pipeline/input.go index e4ca53493..824fee255 100644 --- a/storage/pipeline/input.go +++ b/storage/pipeline/input.go @@ -234,7 +234,7 @@ func (m *Sealing) handleAddPiece(ctx statemachine.Context, sector SectorInfo) er } pieceSizes = append(pieceSizes, p.Unpadded()) - res.NewPieces = append(res.NewPieces, Piece{ + res.NewPieces = append(res.NewPieces, api.SectorPiece{ Piece: ppi, }) } @@ -262,7 +262,7 @@ func (m *Sealing) handleAddPiece(ctx statemachine.Context, sector SectorInfo) er offset += deal.size pieceSizes = append(pieceSizes, deal.size) - res.NewPieces = append(res.NewPieces, Piece{ + res.NewPieces = append(res.NewPieces, api.SectorPiece{ Piece: ppi, DealInfo: &deal.deal, }) diff --git a/storage/pipeline/precommit_policy.go b/storage/pipeline/precommit_policy.go index e0761d209..04f69a93a 100644 --- a/storage/pipeline/precommit_policy.go +++ b/storage/pipeline/precommit_policy.go @@ -9,6 +9,7 @@ import ( "github.com/filecoin-project/go-state-types/builtin/v8/miner" "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" @@ -16,7 +17,7 @@ import ( ) type PreCommitPolicy interface { - Expiration(ctx context.Context, ps ...Piece) (abi.ChainEpoch, error) + Expiration(ctx context.Context, ps ...api.SectorPiece) (abi.ChainEpoch, error) } type Chain interface { @@ -59,7 +60,7 @@ func NewBasicPreCommitPolicy(api Chain, cfgGetter dtypes.GetSealingConfigFunc, p // Expiration produces the pre-commit sector expiration epoch for an encoded // replica containing the provided enumeration of pieces and deals. -func (p *BasicPreCommitPolicy) Expiration(ctx context.Context, ps ...Piece) (abi.ChainEpoch, error) { +func (p *BasicPreCommitPolicy) Expiration(ctx context.Context, ps ...api.SectorPiece) (abi.ChainEpoch, error) { ts, err := p.api.ChainHead(ctx) if err != nil { return 0, err diff --git a/storage/pipeline/precommit_policy_test.go b/storage/pipeline/precommit_policy_test.go index 5d2da1ee0..b81086362 100644 --- a/storage/pipeline/precommit_policy_test.go +++ b/storage/pipeline/precommit_policy_test.go @@ -95,7 +95,7 @@ func TestBasicPolicyMostConstrictiveSchedule(t *testing.T) { h: abi.ChainEpoch(55), }, cfg, 2) longestDealEpochEnd := abi.ChainEpoch(547300) - pieces := []pipeline.Piece{ + pieces := []api.SectorPiece{ { Piece: abi.PieceInfo{ Size: abi.PaddedPieceSize(1024), @@ -136,7 +136,7 @@ func TestBasicPolicyIgnoresExistingScheduleIfExpired(t *testing.T) { h: abi.ChainEpoch(55), }, cfg, 0) - pieces := []pipeline.Piece{ + pieces := []api.SectorPiece{ { Piece: abi.PieceInfo{ Size: abi.PaddedPieceSize(1024), @@ -165,7 +165,7 @@ func TestMissingDealIsIgnored(t *testing.T) { h: abi.ChainEpoch(55), }, cfg, 0) - pieces := []pipeline.Piece{ + pieces := []api.SectorPiece{ { Piece: abi.PieceInfo{ Size: abi.PaddedPieceSize(1024), diff --git a/storage/pipeline/states_failed_test.go b/storage/pipeline/states_failed_test.go index 0243af126..f6846c8f5 100644 --- a/storage/pipeline/states_failed_test.go +++ b/storage/pipeline/states_failed_test.go @@ -76,7 +76,7 @@ func TestStateRecoverDealIDs(t *testing.T) { // TODO sctx should satisfy an interface so it can be useable for mocking. This will fail because we are passing in an empty context now to get this to build. // https://github.com/filecoin-project/lotus/issues/7867 err := fakeSealing.HandleRecoverDealIDs(statemachine.Context{}, pipeline.SectorInfo{ - Pieces: []pipeline.Piece{ + Pieces: []api2.SectorPiece{ { DealInfo: &api2.PieceDealInfo{ DealID: dealId, diff --git a/storage/pipeline/types.go b/storage/pipeline/types.go index 69e2b4ee0..4f175ae61 100644 --- a/storage/pipeline/types.go +++ b/storage/pipeline/types.go @@ -22,18 +22,6 @@ type Context interface { Send(evt interface{}) error } -// Piece is a tuple of piece and deal info -type PieceWithDealInfo struct { - Piece abi.PieceInfo - DealInfo api.PieceDealInfo -} - -// Piece is a tuple of piece info and optional deal -type Piece struct { - Piece abi.PieceInfo - DealInfo *api.PieceDealInfo // nil for pieces which do not appear in deals (e.g. filler pieces) -} - type Log struct { Timestamp uint64 Trace string // for errors @@ -61,7 +49,7 @@ type SectorInfo struct { // Packing CreationTime int64 // unix seconds - Pieces []Piece + Pieces []api.SectorPiece // PreCommit1 TicketValue abi.SealRandomness @@ -89,7 +77,7 @@ type SectorInfo struct { // CCUpdate CCUpdate bool - CCPieces []Piece + CCPieces []api.SectorPiece UpdateSealed *cid.Cid UpdateUnsealed *cid.Cid ReplicaUpdateProof storiface.ReplicaUpdateProof @@ -161,7 +149,7 @@ func (t *SectorInfo) sealingCtx(ctx context.Context) context.Context { // Returns list of offset/length tuples of sector data ranges which clients // requested to keep unsealed -func (t *SectorInfo) keepUnsealedRanges(pieces []Piece, invert, alwaysKeep bool) []storiface.Range { +func (t *SectorInfo) keepUnsealedRanges(pieces []api.SectorPiece, invert, alwaysKeep bool) []storiface.Range { var out []storiface.Range var at abi.UnpaddedPieceSize diff --git a/storage/pipeline/types_test.go b/storage/pipeline/types_test.go index 69e102bf4..5aa0f7dc2 100644 --- a/storage/pipeline/types_test.go +++ b/storage/pipeline/types_test.go @@ -43,7 +43,7 @@ func TestSectorInfoSerialization(t *testing.T) { si := &SectorInfo{ State: "stateful", SectorNumber: 234, - Pieces: []Piece{{ + Pieces: []api.SectorPiece{{ Piece: abi.PieceInfo{ Size: 5, PieceCID: dummyCid, From 332d83bd18aa4b17030b19c30d225f63933fb991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 25 Aug 2022 20:48:57 -0400 Subject: [PATCH 35/85] sector import: Check pieces in Sealing.Receive --- storage/pipeline/receive.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/storage/pipeline/receive.go b/storage/pipeline/receive.go index e32fd4f86..134e3b7eb 100644 --- a/storage/pipeline/receive.go +++ b/storage/pipeline/receive.go @@ -56,7 +56,9 @@ func (m *Sealing) checkSectorMeta(ctx context.Context, meta api.RemoteSectorMeta switch SectorState(meta.State) { case Packing: - //checkPieces(ctx, m.maddr, meta.Sector.Number, meta.Pieces, m.Api, false) + if err := checkPieces(ctx, m.maddr, meta.Sector.Number, meta.Pieces, m.Api, false); err != nil { + return xerrors.Errorf("checking pieces: %w", err) + } fallthrough case GetTicket: From ca790b4c69d4f2f5aa415d2d6aeb620efbd28320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 25 Aug 2022 20:53:21 -0400 Subject: [PATCH 36/85] sealing: Validate piece alignment in checkPieces --- storage/pipeline/checks.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/storage/pipeline/checks.go b/storage/pipeline/checks.go index 10f7a9eaf..6381ce898 100644 --- a/storage/pipeline/checks.go +++ b/storage/pipeline/checks.go @@ -49,8 +49,15 @@ func checkPieces(ctx context.Context, maddr address.Address, sn abi.SectorNumber } dealCount := 0 + var offset abi.PaddedPieceSize for i, p := range pieces { + // check that the piece is correctly aligned + if offset%p.Piece.Size != 0 { + return &ErrInvalidPiece{xerrors.Errorf("sector %d piece %d is not aligned: size=%xh offset=%xh off-by=%xh", sn, i, p.Piece.Size, offset, offset%p.Piece.Size)} + } + offset += p.Piece.Size + // if no deal is associated with the piece, ensure that we added it as // filler (i.e. ensure that it has a zero PieceCID) if p.DealInfo == nil { From 23184192913ad30097a6f5a5d7de4bc2633d3c94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 25 Aug 2022 21:24:12 -0400 Subject: [PATCH 37/85] sector import: Fix missing perm tag on SectorReceive --- api/api_storage.go | 2 +- api/proxy_gen.go | 2 +- build/openrpc/miner.json.gz | Bin 15743 -> 15711 bytes documentation/en/api-v0-methods-miner.md | 4 ++-- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/api_storage.go b/api/api_storage.go index 02af793c9..32309f388 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -146,7 +146,7 @@ type StorageMiner interface { // SectorNumFree drops a sector reservation SectorNumFree(ctx context.Context, name string) error //perm:admin - SectorReceive(ctx context.Context, meta RemoteSectorMeta) error + SectorReceive(ctx context.Context, meta RemoteSectorMeta) error //perm:admin // WorkerConnect tells the node to connect to workers RPC WorkerConnect(context.Context, string) error //perm:admin retry:true diff --git a/api/proxy_gen.go b/api/proxy_gen.go index fd31166a9..6cb86ce54 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -846,7 +846,7 @@ type StorageMinerStruct struct { SectorPreCommitPending func(p0 context.Context) ([]abi.SectorID, error) `perm:"admin"` - SectorReceive func(p0 context.Context, p1 RemoteSectorMeta) error `` + SectorReceive func(p0 context.Context, p1 RemoteSectorMeta) error `perm:"admin"` SectorRemove func(p0 context.Context, p1 abi.SectorNumber) error `perm:"admin"` diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 99ca3b3666cabaaf974b06194236752f869832ef..4912f83066e10949969be4d4b166088da92f0aa2 100644 GIT binary patch delta 5504 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 C&(e4R delta 5496 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 diff --git a/documentation/en/api-v0-methods-miner.md b/documentation/en/api-v0-methods-miner.md index 32912980b..8960857bc 100644 --- a/documentation/en/api-v0-methods-miner.md +++ b/documentation/en/api-v0-methods-miner.md @@ -3153,9 +3153,9 @@ Response: ``` ### SectorReceive -There are not yet any comments for this method. -Perms: + +Perms: admin Inputs: ```json From 2b644525f8a02e1fa1878c8acaa68b7a64550259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 30 Aug 2022 21:45:36 +0200 Subject: [PATCH 38/85] sector import: More check progress --- storage/pipeline/receive.go | 61 +++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/storage/pipeline/receive.go b/storage/pipeline/receive.go index 134e3b7eb..1d6813ae8 100644 --- a/storage/pipeline/receive.go +++ b/storage/pipeline/receive.go @@ -2,6 +2,8 @@ package sealing import ( "context" + "errors" + cbg "github.com/whyrusleeping/cbor-gen" "github.com/ipfs/go-datastore" "golang.org/x/xerrors" @@ -17,6 +19,15 @@ func (m *Sealing) Receive(ctx context.Context, meta api.RemoteSectorMeta) error return err } + err := m.sectors.Get(uint64(meta.Sector.Number)).Get(&cbg.Deferred{}) + if errors.Is(err, datastore.ErrNotFound) { + + } else if err != nil { + return xerrors.Errorf("checking if sector exists: %w", err) + } else if err == nil { + return xerrors.Errorf("sector %d state already exists", meta.Sector.Number) + } + panic("impl me") } @@ -54,27 +65,53 @@ func (m *Sealing) checkSectorMeta(ctx context.Context, meta api.RemoteSectorMeta } } + var info SectorInfo + switch SectorState(meta.State) { - case Packing: - if err := checkPieces(ctx, m.maddr, meta.Sector.Number, meta.Pieces, m.Api, false); err != nil { - return xerrors.Errorf("checking pieces: %w", err) - } - - fallthrough - case GetTicket: - - fallthrough - case PreCommitting: + case Proving, Available: + // todo possibly check + info.CommitMessage = meta.CommitMessage fallthrough case SubmitCommit: + info.PreCommitInfo = meta.PreCommitInfo + info.PreCommitDeposit = meta.PreCommitDeposit + info.PreCommitMessage = meta.PreCommitMessage + info.PreCommitTipSet = meta.PreCommitTipSet + + // todo check + info.SeedValue = meta.SeedValue + info.SeedEpoch = meta.SeedEpoch + + // todo validate + info.Proof = meta.CommitProof fallthrough - case Proving, Available: + case PreCommitting: + info.TicketValue = meta.TicketValue + info.TicketEpoch = meta.TicketEpoch + + info.PreCommit1Out = meta.PreCommit1Out + + info.CommD = meta.CommD // todo check cid prefixes + info.CommR = meta.CommR + + fallthrough + case GetTicket: + fallthrough + case Packing: + // todo check num free + info.State = SectorState(meta.State) // todo dedupe states + info.SectorNumber = meta.Sector.Number + info.Pieces = meta.Pieces + info.SectorType = meta.Type + + if err := checkPieces(ctx, m.maddr, meta.Sector.Number, meta.Pieces, m.Api, false); err != nil { + return xerrors.Errorf("checking pieces: %w", err) + } return nil default: return xerrors.Errorf("imported sector State in not supported") } - } From 39e4845f42eaa15506ae9841e3b430924f11a301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 31 Aug 2022 12:43:40 +0200 Subject: [PATCH 39/85] sector import: Put the imported sector into the sealing pipeline --- cmd/lotus-miner/info.go | 2 ++ storage/pipeline/fsm.go | 8 +++++ storage/pipeline/fsm_events.go | 12 ++++++++ storage/pipeline/receive.go | 53 ++++++++++++++++++++------------ storage/pipeline/sector_state.go | 6 +++- storage/pipeline/types.go | 2 +- 6 files changed, 61 insertions(+), 22 deletions(-) diff --git a/cmd/lotus-miner/info.go b/cmd/lotus-miner/info.go index e8cbfd8b7..ecce9c6c2 100644 --- a/cmd/lotus-miner/info.go +++ b/cmd/lotus-miner/info.go @@ -498,6 +498,8 @@ var stateList = []stateMeta{ {col: color.FgGreen, state: sealing.Available}, {col: color.FgGreen, state: sealing.UpdateActivating}, + {col: color.FgMagenta, state: sealing.ReceiveSector}, + {col: color.FgBlue, state: sealing.Empty}, {col: color.FgBlue, state: sealing.WaitDeals}, {col: color.FgBlue, state: sealing.AddPiece}, diff --git a/storage/pipeline/fsm.go b/storage/pipeline/fsm.go index 3df57a501..59e99bc59 100644 --- a/storage/pipeline/fsm.go +++ b/storage/pipeline/fsm.go @@ -40,6 +40,11 @@ func (m *Sealing) Plan(events []statemachine.Event, user interface{}) (interface } var fsmPlanners = map[SectorState]func(events []statemachine.Event, state *SectorInfo) (uint64, error){ + // external import + ReceiveSector: planOne( + onReturning(SectorReceived{}), + ), + // Sealing UndefinedSectorState: planOne( @@ -457,6 +462,9 @@ func (m *Sealing) plan(events []statemachine.Event, state *SectorInfo) (func(sta } switch state.State { + case ReceiveSector: + return m.handleReceiveSector, processed, nil + // Happy path case Empty: fallthrough diff --git a/storage/pipeline/fsm_events.go b/storage/pipeline/fsm_events.go index ac9c5775d..ec87f8e38 100644 --- a/storage/pipeline/fsm_events.go +++ b/storage/pipeline/fsm_events.go @@ -526,3 +526,15 @@ type SectorRemoveFailed struct{ error } func (evt SectorRemoveFailed) FormatError(xerrors.Printer) (next error) { return evt.error } func (evt SectorRemoveFailed) apply(*SectorInfo) {} + +type SectorReceive struct { + State SectorInfo +} + +func (evt SectorReceive) apply(state *SectorInfo) { + *state = evt.State +} + +type SectorReceived struct{} + +func (evt SectorReceived) apply(state *SectorInfo) {} diff --git a/storage/pipeline/receive.go b/storage/pipeline/receive.go index 1d6813ae8..4a70932a5 100644 --- a/storage/pipeline/receive.go +++ b/storage/pipeline/receive.go @@ -2,9 +2,7 @@ package sealing import ( "context" - "errors" - cbg "github.com/whyrusleeping/cbor-gen" - + "github.com/filecoin-project/go-statemachine" "github.com/ipfs/go-datastore" "golang.org/x/xerrors" @@ -15,23 +13,30 @@ import ( ) func (m *Sealing) Receive(ctx context.Context, meta api.RemoteSectorMeta) error { - if err := m.checkSectorMeta(ctx, meta); err != nil { + si, err := m.checkSectorMeta(ctx, meta) + if err != nil { return err } - err := m.sectors.Get(uint64(meta.Sector.Number)).Get(&cbg.Deferred{}) - if errors.Is(err, datastore.ErrNotFound) { - - } else if err != nil { + exists, err := m.sectors.Has(uint64(meta.Sector.Number)) + if err != nil { return xerrors.Errorf("checking if sector exists: %w", err) - } else if err == nil { + } + if exists { return xerrors.Errorf("sector %d state already exists", meta.Sector.Number) } - panic("impl me") + err = m.sectors.Send(uint64(meta.Sector.Number), SectorReceive{ + State: si, + }) + if err != nil { + return xerrors.Errorf("receiving sector: %w", err) + } + + return nil } -func (m *Sealing) checkSectorMeta(ctx context.Context, meta api.RemoteSectorMeta) error { +func (m *Sealing) checkSectorMeta(ctx context.Context, meta api.RemoteSectorMeta) (SectorInfo, error) { { mid, err := address.IDFromAddress(m.maddr) if err != nil { @@ -39,7 +44,7 @@ func (m *Sealing) checkSectorMeta(ctx context.Context, meta api.RemoteSectorMeta } if meta.Sector.Miner != abi.ActorID(mid) { - return xerrors.Errorf("sector for wrong actor - expected actor id %d, sector was for actor %d", mid, meta.Sector.Miner) + return SectorInfo{}, xerrors.Errorf("sector for wrong actor - expected actor id %d, sector was for actor %d", mid, meta.Sector.Miner) } } @@ -47,21 +52,21 @@ func (m *Sealing) checkSectorMeta(ctx context.Context, meta api.RemoteSectorMeta // initial sanity check, doesn't prevent races _, err := m.GetSectorInfo(meta.Sector.Number) if err != nil && !xerrors.Is(err, datastore.ErrNotFound) { - return err + return SectorInfo{}, err } if err == nil { - return xerrors.Errorf("sector with ID %d already exists in the sealing pipeline", meta.Sector.Number) + return SectorInfo{}, xerrors.Errorf("sector with ID %d already exists in the sealing pipeline", meta.Sector.Number) } } { spt, err := m.currentSealProof(ctx) if err != nil { - return err + return SectorInfo{}, err } if meta.Type != spt { - return xerrors.Errorf("sector seal proof type doesn't match current seal proof type (%d!=%d)", meta.Type, spt) + return SectorInfo{}, xerrors.Errorf("sector seal proof type doesn't match current seal proof type (%d!=%d)", meta.Type, spt) } } @@ -101,17 +106,25 @@ func (m *Sealing) checkSectorMeta(ctx context.Context, meta api.RemoteSectorMeta fallthrough case Packing: // todo check num free - info.State = SectorState(meta.State) // todo dedupe states + info.Return = ReturnState(meta.State) // todo dedupe states + info.State = ReceiveSector + info.SectorNumber = meta.Sector.Number info.Pieces = meta.Pieces info.SectorType = meta.Type if err := checkPieces(ctx, m.maddr, meta.Sector.Number, meta.Pieces, m.Api, false); err != nil { - return xerrors.Errorf("checking pieces: %w", err) + return SectorInfo{}, xerrors.Errorf("checking pieces: %w", err) } - return nil + return info, nil default: - return xerrors.Errorf("imported sector State in not supported") + return SectorInfo{}, xerrors.Errorf("imported sector State in not supported") } } + +func (m *Sealing) handleReceiveSector(ctx statemachine.Context, sector SectorInfo) error { + // todo fetch stuff + + return ctx.Send(SectorReceived{}) +} diff --git a/storage/pipeline/sector_state.go b/storage/pipeline/sector_state.go index 4f81f5544..ad77fdbd5 100644 --- a/storage/pipeline/sector_state.go +++ b/storage/pipeline/sector_state.go @@ -63,6 +63,7 @@ var ExistSectorStateList = map[SectorState]struct{}{ ReleaseSectorKeyFailed: {}, FinalizeReplicaUpdateFailed: {}, AbortUpgrade: {}, + ReceiveSector: {}, } // cmd/lotus-miner/info.go defines CLI colors corresponding to these states @@ -113,6 +114,9 @@ const ( UpdateActivating SectorState = "UpdateActivating" ReleaseSectorKey SectorState = "ReleaseSectorKey" + // external import + ReceiveSector SectorState = "ReceiveSector" + // error modes FailedUnrecoverable SectorState = "FailedUnrecoverable" AddPieceFailed SectorState = "AddPieceFailed" @@ -153,7 +157,7 @@ func toStatState(st SectorState, finEarly bool) statSectorState { switch st { case UndefinedSectorState, Empty, WaitDeals, AddPiece, AddPieceFailed, SnapDealsWaitDeals, SnapDealsAddPiece: return sstStaging - case Packing, GetTicket, PreCommit1, PreCommit2, PreCommitting, PreCommitWait, SubmitPreCommitBatch, PreCommitBatchWait, WaitSeed, Committing, CommitFinalize, FinalizeSector, SnapDealsPacking, UpdateReplica, ProveReplicaUpdate, FinalizeReplicaUpdate: + case Packing, GetTicket, PreCommit1, PreCommit2, PreCommitting, PreCommitWait, SubmitPreCommitBatch, PreCommitBatchWait, WaitSeed, Committing, CommitFinalize, FinalizeSector, SnapDealsPacking, UpdateReplica, ProveReplicaUpdate, FinalizeReplicaUpdate, ReceiveSector: return sstSealing case SubmitCommit, CommitWait, SubmitCommitAggregate, CommitAggregateWait, SubmitReplicaUpdate, ReplicaUpdateWait: if finEarly { diff --git a/storage/pipeline/types.go b/storage/pipeline/types.go index 4f175ae61..0321d113f 100644 --- a/storage/pipeline/types.go +++ b/storage/pipeline/types.go @@ -86,7 +86,7 @@ type SectorInfo struct { // Faults FaultReportMsg *cid.Cid - // Recovery + // Recovery / Import Return ReturnState // Termination From fbb487ae2b649b92a2b911bb6fefaaec77f45bed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 31 Aug 2022 13:56:25 +0200 Subject: [PATCH 40/85] sector import: Plumbing for DownloadSectorData in the sealing system --- api/api_storage.go | 25 ++----- api/api_worker.go | 1 + api/docgen/docgen.go | 8 +++ api/proxy_gen.go | 26 ++++++++ build/openrpc/full.json.gz | Bin 28423 -> 28355 bytes build/openrpc/gateway.json.gz | Bin 5080 -> 4943 bytes build/openrpc/miner.json.gz | Bin 15711 -> 15751 bytes build/openrpc/worker.json.gz | Bin 5035 -> 5202 bytes documentation/en/api-v0-methods-miner.md | 25 +++++++ documentation/en/api-v0-methods-worker.md | 42 ++++++++++++ storage/pipeline/receive.go | 6 +- storage/sealer/ffiwrapper/sealer_cgo.go | 4 ++ storage/sealer/manager.go | 77 ++++++++++++++++++++++ storage/sealer/sealtasks/task.go | 13 ++-- storage/sealer/storiface/storage.go | 20 ++++++ storage/sealer/storiface/worker.go | 2 + storage/sealer/worker_local.go | 13 ++++ 17 files changed, 235 insertions(+), 27 deletions(-) diff --git a/api/api_storage.go b/api/api_storage.go index 32309f388..7ae95cde2 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -3,7 +3,6 @@ package api import ( "bytes" "context" - "net/http" "time" "github.com/google/uuid" @@ -170,6 +169,7 @@ type StorageMiner interface { ReturnMoveStorage(ctx context.Context, callID storiface.CallID, err *storiface.CallError) error //perm:admin retry:true ReturnUnsealPiece(ctx context.Context, callID storiface.CallID, err *storiface.CallError) error //perm:admin retry:true ReturnReadPiece(ctx context.Context, callID storiface.CallID, ok bool, err *storiface.CallError) error //perm:admin retry:true + ReturnDownloadSector(ctx context.Context, callID storiface.CallID, err *storiface.CallError) error //perm:admin retry:true ReturnFetch(ctx context.Context, callID storiface.CallID, err *storiface.CallError) error //perm:admin retry:true // SealingSchedDiag dumps internal sealing scheduler state @@ -563,11 +563,11 @@ type RemoteSectorMeta struct { // Sector urls - lotus will use those for fetching files into local storage // Required in all states - DataUnsealed *SectorData + DataUnsealed *storiface.SectorData // Required in PreCommitting and later - DataSealed *SectorData - DataCache *SectorData + DataSealed *storiface.SectorData + DataCache *storiface.SectorData //////// // SEALING SERVICE HOOKS @@ -575,20 +575,3 @@ type RemoteSectorMeta struct { // todo Commit1Provider // todo OnDone / OnStateChange } - -type SectorData struct { - // Local when set to true indicates to lotus that sector data is already - // available locally; When set lotus will skip fetching sector data, and - // only check that sector data exists in sector storage - Local bool - - // URL to the sector data - // For sealed/unsealed sector, lotus expects octet-stream - // For cache, lotus expects a tar archive with cache files (todo maybe use not-tar; specify what files with what paths must be present) - // Valid schemas: - // - http:// / https:// - URL string - - // optional http headers to use when requesting sector data - Headers http.Header -} diff --git a/api/api_worker.go b/api/api_worker.go index 609cb4271..4b56d1154 100644 --- a/api/api_worker.go +++ b/api/api_worker.go @@ -49,6 +49,7 @@ type Worker interface { MoveStorage(ctx context.Context, sector storiface.SectorRef, types storiface.SectorFileType) (storiface.CallID, error) //perm:admin UnsealPiece(context.Context, storiface.SectorRef, storiface.UnpaddedByteIndex, abi.UnpaddedPieceSize, abi.SealRandomness, cid.Cid) (storiface.CallID, error) //perm:admin Fetch(context.Context, storiface.SectorRef, storiface.SectorFileType, storiface.PathType, storiface.AcquireMode) (storiface.CallID, error) //perm:admin + DownloadSectorData(ctx context.Context, sector storiface.SectorRef, finalized bool, src map[storiface.SectorFileType]storiface.SectorData) (storiface.CallID, error) //perm:admin GenerateWinningPoSt(ctx context.Context, ppt abi.RegisteredPoStProof, mid abi.ActorID, sectors []storiface.PostSectorChallenge, randomness abi.PoStRandomness) ([]proof.PoStProof, error) //perm:admin GenerateWindowPoSt(ctx context.Context, ppt abi.RegisteredPoStProof, mid abi.ActorID, sectors []storiface.PostSectorChallenge, partitionIdx int, randomness abi.PoStRandomness) (storiface.WindowPoStResult, error) //perm:admin diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index 47192bfd9..e078cbe0f 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -344,6 +344,14 @@ func init() { addExample(http.Header{ "Authorization": []string{"Bearer ey.."}, }) + + addExample(map[storiface.SectorFileType]storiface.SectorData{ + storiface.FTSealed: { + Local: false, + URL: "https://example.com/sealingservice/sectors/s-f0123-12345", + Headers: nil, + }, + }) } func GetAPIType(name, pkg string) (i interface{}, t reflect.Type, permStruct []reflect.Type) { diff --git a/api/proxy_gen.go b/api/proxy_gen.go index 6cb86ce54..04e79c2de 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -778,6 +778,8 @@ type StorageMinerStruct struct { ReturnDataCid func(p0 context.Context, p1 storiface.CallID, p2 abi.PieceInfo, p3 *storiface.CallError) error `perm:"admin"` + ReturnDownloadSector func(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error `perm:"admin"` + ReturnFetch func(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error `perm:"admin"` ReturnFinalizeReplicaUpdate func(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error `perm:"admin"` @@ -953,6 +955,8 @@ type WorkerStruct struct { DataCid func(p0 context.Context, p1 abi.UnpaddedPieceSize, p2 storiface.Data) (storiface.CallID, error) `perm:"admin"` + DownloadSectorData func(p0 context.Context, p1 storiface.SectorRef, p2 bool, p3 map[storiface.SectorFileType]storiface.SectorData) (storiface.CallID, error) `perm:"admin"` + Enabled func(p0 context.Context) (bool, error) `perm:"admin"` Fetch func(p0 context.Context, p1 storiface.SectorRef, p2 storiface.SectorFileType, p3 storiface.PathType, p4 storiface.AcquireMode) (storiface.CallID, error) `perm:"admin"` @@ -4667,6 +4671,17 @@ func (s *StorageMinerStub) ReturnDataCid(p0 context.Context, p1 storiface.CallID return ErrNotSupported } +func (s *StorageMinerStruct) ReturnDownloadSector(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error { + if s.Internal.ReturnDownloadSector == nil { + return ErrNotSupported + } + return s.Internal.ReturnDownloadSector(p0, p1, p2) +} + +func (s *StorageMinerStub) ReturnDownloadSector(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error { + return ErrNotSupported +} + func (s *StorageMinerStruct) ReturnFetch(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error { if s.Internal.ReturnFetch == nil { return ErrNotSupported @@ -5536,6 +5551,17 @@ func (s *WorkerStub) DataCid(p0 context.Context, p1 abi.UnpaddedPieceSize, p2 st return *new(storiface.CallID), ErrNotSupported } +func (s *WorkerStruct) DownloadSectorData(p0 context.Context, p1 storiface.SectorRef, p2 bool, p3 map[storiface.SectorFileType]storiface.SectorData) (storiface.CallID, error) { + if s.Internal.DownloadSectorData == nil { + return *new(storiface.CallID), ErrNotSupported + } + return s.Internal.DownloadSectorData(p0, p1, p2, p3) +} + +func (s *WorkerStub) DownloadSectorData(p0 context.Context, p1 storiface.SectorRef, p2 bool, p3 map[storiface.SectorFileType]storiface.SectorData) (storiface.CallID, error) { + return *new(storiface.CallID), ErrNotSupported +} + func (s *WorkerStruct) Enabled(p0 context.Context) (bool, error) { if s.Internal.Enabled == nil { return false, ErrNotSupported diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 4d823b135b775d5c1fb538b9375e35941490c1f0..942fb190afe07f2f1df19e969c1a4b3db8e725ac 100644 GIT binary patch delta 25762 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�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 delta 25823 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Ñ{_A{2Gf`Yb0C-rWyl8yzAx35 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 diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 4912f83066e10949969be4d4b166088da92f0aa2..2f9273ea76d6742fd51aa1b7520002c8fb22b20e 100644 GIT binary patch delta 14867 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 F0|2U=zT5x+ delta 14848 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 diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index f0d2db3c69241b2c6b7cfd999ad7f49e94f7a063..2a35ab13436378a088b78f9d6a1f6cc745c78365 100644 GIT binary patch literal 5202 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 M|2}5G{{S%o0313bApigX literal 5035 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 Date: Wed, 31 Aug 2022 19:34:08 +0200 Subject: [PATCH 41/85] sector import: Sector data download --- build/openrpc/miner.json.gz | Bin 15751 -> 15794 bytes build/openrpc/worker.json.gz | Bin 5202 -> 5244 bytes storage/paths/fetch.go | 104 ++++++++++++++++++++++++ storage/paths/remote.go | 62 +------------- storage/sealer/ffiwrapper/sealer_cgo.go | 32 +++++++- storage/sealer/tarutil/systar.go | 20 +++-- 6 files changed, 150 insertions(+), 68 deletions(-) create mode 100644 storage/paths/fetch.go diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 2f9273ea76d6742fd51aa1b7520002c8fb22b20e..30cd2019132fbd79c8a551d954c3a5aad52f9bee 100644 GIT binary patch delta 15304 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 Ju{kmd0|4V`bv6J1 diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index 2a35ab13436378a088b78f9d6a1f6cc745c78365..76d71cb4c69e880e20c466b9fc20202b66dbad68 100644 GIT binary patch delta 5221 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 delta 5174 zcmV-66v^xSDAFi@ABzY8000000RQY=eRG>QxBn^{?mtZj+Zga$X8K2)?)J&O?QWiA zvpe@DnOhlTTN^A`kdwL@fA?nu*yi2HhL|Lpo#{HDmm}$%^ZQ9UNKcaOAmTcPbSib* zYP&DV5+)N{I(?ETrWTS;rLQ(!c;NEl41Byemt^SyxgNHEiJzl8Xsay~JxD8Anj<>1 zO;0+#d6Ed6v#h^j3mG;h?T@B;fsuhESu&AlxY(gV5#PUmpW3%%vLFU{=YjWs+3r1Z zuc*5)=m}V7d*>*?un9#!X!|VqzYLr%@Tl$L2{cgq3K`UPFVRE>jtXw>;25{Bi33d& z1w~xppGXFOjt27H=#kPsLkds8yX}KBXj$i*ep}4P?61*EmM&b|p8T92XgovL}AyR`Pbd)YN$8sD7z2yXh*bes?EJ zn4(ZIQf-LW7W5Xt9)KXx%$s$dJ2txOXV7)wlE0l=s%Fks?$eG)&-fja(K60v=mqA* zd`>yb%C{CKLMfTQC4;+UD7=j$ht{?P{t1gexuMAFoTwqLGWKIC_Uol1^v z+w$j)bLiY86EQ&<+rLY`*vIv!M6C^f1+qFNI;j*B%7)l^6DE|*07?xID?I|VEFXfT zin$)Q>6KI{l|A`t8_?Pe^Y!J|h-V8^`>xGH(86TuA@>0rsN?ySc^$7cQM#(u>Z+RF zlVs^%2%5T{>M)zr{6gT^LcG(_Fjoe9IRWnij*?F;!pzxxBIq#= z`mhz{V4jd=_aWXK1%w)^O4=w}m3~Fkm~kqSpNE$O=Yg+JjJGc@Af1Jeqq`{|4Zq{R z*(T!T#J;d~(cpDYi0u@?eoHKW^29a2zU8V4)~eDBI&Gt+v`z|1uM`m~WPg?JeMCG& zIl1Qc1KL|C!8Qfkd~IyAr{qusOrV8-qD$mh*nroL2`O(BMrIlOAsO2@myk@ksX&$x zwNZnd78mcD2#W9~DMCuK%f(teNxx4f9-?fdR9%+*O)&rWDjtHt(d+Af63cXZP~U!i zwr4bO++Z3;Ea{;Gi(BXmsRf}LYag1kTq!qX~vjL1A`+JK+|-Q=OGi|O=)d#PMKSu z*zO#eV7vqn{26?RBm)S}Y!eya-Qx@!Gk}SSSrb_u^MysR!QQqUXxt-nQw!^?699<` zAo#{o&7e1H#~0(Dqz=<_I9aOWpU^;1d-Tj%f2l#OPsf^fkLJ#Q-}aAxoW;_fJAaSo z6SSOY`UGo>Wq+|iy856?E3r;35M3a8fapW57}5X2#IzqT>?^t_ly3_t3f;d%Q_N&U zCQIr2Ss%yQyN^s87;rmUY$|#u|ekMx~Hm|!wJj0vwxcrGfONr7+f99C#MQhX`qyB5BH`lgEBm+?kA z>RH1<1>F^NSI}KSceCj3>!pq=@g~Rkc%7cZ6imcaTTasGx<1Fewb270x z2@uPY{h?IyTnm=6h>fuKV?Zd$jYQbRPY=r?WO&C7jHBLvx!oRaNw%EPaDN;t#%DTwhac;h!+* zvaX+h<^sA5B`xgDGah}pxRy>8S^5M$lvTr-O^k)>B0{B8WhG19geXI8IP5ENX*SO) z6ewCxQYJMaQ&$;i1HC&`^_W=Qepl&t)q&QpRI+VtN~UyLLOM-VyQyr@BAgzx8fML()n0C6=0zm4~u>qj0K+g{VWhLVTfJO;d0r{^_xat7l zV6Cqj0IJaXdH|rJv8!MfP@ixW%mM0u5-tq@s#x@SEBgT(@sx@e8>`w6P!(P2DkHt$ z>x=z>CKCA+av>#@2-<{<+E--oLBp z!``5u80GL;h4Ytej86i;V@i|s=Es#L>ClfWP1==zA5)t2 zlFl)uNw47?Pnw8#c3f1?!u^|I=tHHaDgE^%t#+!i)tFr#yRUTBQBPO9dYnbYW^L8W zR?>m1irO0~LtPyvI!+tt+Hlm<^g-2Jr9eEv&$;+{V{|_0-y5a#DRPU7*Ynui{lf&UKo+h1J;eXOyoxC^%gGtHy##)W3pt z`x@0h#rPYh{z*`1mios~!G1t)a9_j1|8`fz^&D*hU&Qr@xE>MLBjS3F71xsi1|r7i zS!N0%#;3s{4kE@!#Q2C9p8z6UG({x1#Qh#E#YV!BW(p$IM}+#kq^*R1MrpkW^*MN^ z5St_j6c8vNP(Yx7K!G=g0t>=^3HQ(K{u>inA)<*yG?9oVYLL!1JDO;<;=J);d)nz! z`gG~kfv4$1qZ4P6@|?%*pK)5|nBgyB%FUtODM6D5aK}zzEmOtjU{;RA!NIL1Gn7vp zKjRfrid=FL>$&#(9j`clMj@eFC5I!X%1;>$jw@!VKH6GN$>cHP;m_k+SE+0~-BWPW z-F{DA$F(5+Za@1N>~Ho1x}vrV{Tm5*PQ#AmBDvscEpaUuJ>&>jc+pWjU8$Q@d^a%YPr9SwaNI%@O=;=o0JX*%GFl_p zR|gY)40~`W$E$UXWh~)9{8mGfZeL!Q%{(Fx@S2g8G0* zp-a76a-b*KG&(gr(KjWopen@{YPq6nH~zm4Gt)=8X7+%8E`v?&;0m+!%w?yoB}?F) zHyYS%pJCI#Hgfx0U;pNuK4u71*EBM_?az86q?lH|tZ8Ma)4OMpnHLqt%R0j*a9Q;p zB9ys04_T8|kY>z?lM$8yP18l5hfIJsrM1C1We##;yK`iM@e)ArCvpQIf#A$GkpbR4 z&ag29n3$M}|_|#yv7OwNQVl2NDxN@QtOKL2uTMPjaid%re4(LRwY9 z#hzkhnlUozn=Qr29C2jC>XKMpdTpyqeWffLP6IeE8&)f3!!mGsk-TADe?o#S3;O)V zJD4!5&qtWm48qKsC`fZdY+sP(H%>0Q%9y(fIeB^U2cCReLH=8`y(ZF zh1c;5_Al7~OJ@IrZj-ML8Gk8n&{U%Ox(Ix_l_GemVdmNd!k*IRvQY!SPbMCsY@}3O zmagzm6#U+QcYj@CnQjm2d)9q<0k8sKUmL&<)jWWm+Yh|)$U&w&IE7;zLc;mylK>vG z(UBFB4T?PA7mC8F&c?fS+;Ru}PG`vF3b7~xz-T*5T6>O-^Oy1iMxX_o1P8A$jaA3iK4~6N0ld%suf9it# zcyEavW&!?%;GedTa*^ww9zT4jCo+An=<}kj+Cpo8;jK+?e+o8tEH+ni?{fDnSU;|&xGXbrQ_CUEF1MSh9Ae^MZxK)lz6cxui$QOvDhxM=?sscq(p7Nri`PKt|E zCm6;W9$jA&nAi{-ZUQF0fpsOZ(jxZF1W^%0r5d85)=iAKtPtdoRzuz2v zE5oxe6ERb7puskQmE>sjOQa?O0|f@YHVo8r&cxgAFucQ0!K>@|yItqre|^RGC~=mb z48{uzHV5L7FyuRO${n);6MJxMvAV?xcW>l`yFoAKgu5$5e<8j$aNEr~V;*4~+qPBk z(ZRNWLq9x-iBj$lQ7r8MzXCv+dTeo@pOt@;DrefMu#`2FThoty14-TCLcVB89qDI$dytX1Tc%(rq8m%kQInj>eb zr}NX5S<@_aL&vGqe=#A29S72xzi?S_NnS-^$4xZ`H7|5D(1=T9BEy32AyyGDBhTr- zkB+&&Pqk|~BCGU@a3Ce}vur!Xx}Y2?bhXh4LPc+>eYMxp^>Mc~hNj*co62C^8;w=f z(Dz1*!aWzUtnq9%s^tW`{QZf3-W|pL5;nnGezuVYR1tmK~!}Xjf2{tCd67U`h9%QUczk}kcQ16XkLc8CDcK34j zKi6Zzdja4kU%^x(HSml^FAVy$Dw=X^O5Qc!$S-yilQM9+%|Xc zTKA4tWY0kMVA#*u1`R$N^}|~^ttXOm|H!#( z;y}|x=BFh^=fp%mHV06w-}n_=;h)Gqcp~%TfZm=rf<;ZK&^i%k<8(B~pf5V@uR3nvp& %s", url, outname) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return xerrors.Errorf("request: %w", err) + } + req.Header = header + req = req.WithContext(ctx) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return xerrors.Errorf("do request: %w", err) + } + defer resp.Body.Close() // nolint + + if resp.StatusCode != 200 { + return xerrors.Errorf("non-200 code: %d", resp.StatusCode) + } + + start := time.Now() + var bytes int64 + defer func() { + took := time.Now().Sub(start) + mibps := float64(bytes) / 1024 / 1024 * float64(time.Second) / float64(took) + log.Infow("Fetch done", "url", url, "out", outname, "took", took.Round(time.Millisecond), "bytes", bytes, "MiB/s", mibps, "err", rerr) + }() + + mediatype, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return xerrors.Errorf("parse media type: %w", err) + } + + if err := os.RemoveAll(outname); err != nil { + return xerrors.Errorf("removing dest: %w", err) + } + + switch mediatype { + case "application/x-tar": + bytes, err = tarutil.ExtractTar(resp.Body, outname, make([]byte, CopyBuf)) + return err + case "application/octet-stream": + f, err := os.Create(outname) + if err != nil { + return err + } + bytes, err = io.CopyBuffer(f, resp.Body, make([]byte, CopyBuf)) + if err != nil { + f.Close() // nolint + return err + } + return f.Close() + default: + return xerrors.Errorf("unknown content type: '%s'", mediatype) + } +} + +// FetchWithTemp fetches data into a temp 'fetching' directory, then moves the file to destination +func FetchWithTemp(ctx context.Context, urls []string, dest string, header http.Header) (string, error) { + var merr error + for _, url := range urls { + tempDest, err := tempFetchDest(dest, true) + if err != nil { + return "", err + } + + if err := os.RemoveAll(dest); err != nil { + return "", xerrors.Errorf("removing dest: %w", err) + } + + err = fetch(ctx, url, tempDest, header) + if err != nil { + merr = multierror.Append(merr, xerrors.Errorf("fetch error %s -> %s: %w", url, tempDest, err)) + continue + } + + if err := move(tempDest, dest); err != nil { + return "", xerrors.Errorf("fetch move error %s -> %s: %w", tempDest, dest, err) + } + + if merr != nil { + log.Warnw("acquireFromRemote encountered errors when fetching sector from remote", "errors", merr) + } + return url, nil + } + + return "", xerrors.Errorf("failed to fetch sector file (tried %v): %w", urls, merr) +} diff --git a/storage/paths/remote.go b/storage/paths/remote.go index 331f2cf7a..06d1080b3 100644 --- a/storage/paths/remote.go +++ b/storage/paths/remote.go @@ -7,7 +7,6 @@ import ( "io" "io/ioutil" "math/bits" - "mime" "net/http" "net/url" "os" @@ -24,7 +23,6 @@ import ( "github.com/filecoin-project/lotus/storage/sealer/fsutil" "github.com/filecoin-project/lotus/storage/sealer/storiface" - "github.com/filecoin-project/lotus/storage/sealer/tarutil" ) var FetchTempSubdir = "fetching" @@ -236,7 +234,7 @@ func (r *Remote) acquireFromRemote(ctx context.Context, s abi.SectorID, fileType return "", xerrors.Errorf("removing dest: %w", err) } - err = r.fetch(ctx, url, tempDest) + err = r.fetchThrottled(ctx, url, tempDest) if err != nil { merr = multierror.Append(merr, xerrors.Errorf("fetch error %s (storage %s) -> %s: %w", url, info.ID, tempDest, err)) continue @@ -256,9 +254,7 @@ func (r *Remote) acquireFromRemote(ctx context.Context, s abi.SectorID, fileType return "", xerrors.Errorf("failed to acquire sector %v from remote (tried %v): %w", s, si, merr) } -func (r *Remote) fetch(ctx context.Context, url, outname string) error { - log.Infof("Fetch %s -> %s", url, outname) - +func (r *Remote) fetchThrottled(ctx context.Context, url, outname string) (rerr error) { if len(r.limit) >= cap(r.limit) { log.Infof("Throttling fetch, %d already running", len(r.limit)) } @@ -274,59 +270,7 @@ func (r *Remote) fetch(ctx context.Context, url, outname string) error { return xerrors.Errorf("context error while waiting for fetch limiter: %w", ctx.Err()) } - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return xerrors.Errorf("request: %w", err) - } - req.Header = r.auth - req = req.WithContext(ctx) - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return xerrors.Errorf("do request: %w", err) - } - defer resp.Body.Close() // nolint - - if resp.StatusCode != 200 { - return xerrors.Errorf("non-200 code: %d", resp.StatusCode) - } - - /*bar := pb.New64(w.sizeForType(typ)) - bar.ShowPercent = true - bar.ShowSpeed = true - bar.Units = pb.U_BYTES - - barreader := bar.NewProxyReader(resp.Body) - - bar.Start() - defer bar.Finish()*/ - - mediatype, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) - if err != nil { - return xerrors.Errorf("parse media type: %w", err) - } - - if err := os.RemoveAll(outname); err != nil { - return xerrors.Errorf("removing dest: %w", err) - } - - switch mediatype { - case "application/x-tar": - return tarutil.ExtractTar(resp.Body, outname, make([]byte, CopyBuf)) - case "application/octet-stream": - f, err := os.Create(outname) - if err != nil { - return err - } - _, err = io.CopyBuffer(f, resp.Body, make([]byte, CopyBuf)) - if err != nil { - f.Close() // nolint - return err - } - return f.Close() - default: - return xerrors.Errorf("unknown content type: '%s'", mediatype) - } + return fetch(ctx, url, outname, r.auth) } func (r *Remote) checkAllocated(ctx context.Context, url string, spt abi.RegisteredSealProof, offset, size abi.PaddedPieceSize) (bool, error) { diff --git a/storage/sealer/ffiwrapper/sealer_cgo.go b/storage/sealer/ffiwrapper/sealer_cgo.go index c1d557713..0ed53183c 100644 --- a/storage/sealer/ffiwrapper/sealer_cgo.go +++ b/storage/sealer/ffiwrapper/sealer_cgo.go @@ -28,6 +28,7 @@ import ( "github.com/filecoin-project/go-state-types/proof" "github.com/filecoin-project/lotus/lib/nullreader" + spaths "github.com/filecoin-project/lotus/storage/paths" nr "github.com/filecoin-project/lotus/storage/pipeline/lib/nullreader" "github.com/filecoin-project/lotus/storage/sealer/fr32" "github.com/filecoin-project/lotus/storage/sealer/partialfile" @@ -1128,7 +1129,36 @@ func (sb *Sealer) Remove(ctx context.Context, sector storiface.SectorRef) error } func (sb *Sealer) DownloadSectorData(ctx context.Context, sector storiface.SectorRef, finalized bool, src map[storiface.SectorFileType]storiface.SectorData) error { - panic("todo") + var todo storiface.SectorFileType + for fileType := range src { + todo |= fileType + } + + ptype := storiface.PathSealing + if finalized { + ptype = storiface.PathStorage + } + + paths, done, err := sb.sectors.AcquireSector(ctx, sector, storiface.FTNone, todo, ptype) + if err != nil { + return xerrors.Errorf("failed to acquire sector paths: %w", err) + } + defer done() + + for fileType, data := range src { + out := storiface.PathByType(paths, fileType) + + if data.Local { + return xerrors.Errorf("sector(%v) with local data (%#v) requested in DownloadSectorData", sector, data) + } + + _, err := spaths.FetchWithTemp(ctx, []string{data.URL}, out, data.Headers) + if err != nil { + return xerrors.Errorf("downloading sector data: %w", err) + } + } + + return nil } func GetRequiredPadding(oldLength abi.PaddedPieceSize, newPieceLength abi.PaddedPieceSize) ([]abi.PaddedPieceSize, abi.PaddedPieceSize) { diff --git a/storage/sealer/tarutil/systar.go b/storage/sealer/tarutil/systar.go index 4cd2e2bbb..96dcce875 100644 --- a/storage/sealer/tarutil/systar.go +++ b/storage/sealer/tarutil/systar.go @@ -12,19 +12,20 @@ import ( var log = logging.Logger("tarutil") // nolint -func ExtractTar(body io.Reader, dir string, buf []byte) error { +func ExtractTar(body io.Reader, dir string, buf []byte) (int64, error) { if err := os.MkdirAll(dir, 0755); err != nil { // nolint - return xerrors.Errorf("mkdir: %w", err) + return 0, xerrors.Errorf("mkdir: %w", err) } tr := tar.NewReader(body) + var read int64 for { header, err := tr.Next() switch err { default: - return err + return read, err case io.EOF: - return nil + return read, nil case nil: } @@ -33,17 +34,20 @@ func ExtractTar(body io.Reader, dir string, buf []byte) error { f, err := os.Create(filepath.Join(dir, header.Name)) if err != nil { //nolint:gosec - return xerrors.Errorf("creating file %s: %w", filepath.Join(dir, header.Name), err) + return read, xerrors.Errorf("creating file %s: %w", filepath.Join(dir, header.Name), err) } // This data is coming from a trusted source, no need to check the size. + // TODO: now it's actually not coming from a trusted source, check size / paths //nolint:gosec - if _, err := io.CopyBuffer(f, tr, buf); err != nil { - return err + r, err := io.CopyBuffer(f, tr, buf) + read += r + if err != nil { + return read, err } if err := f.Close(); err != nil { - return err + return read, err } } } From cde23be559d302ba0956b3b5f8f0f589af06e25e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 2 Sep 2022 14:19:29 +0200 Subject: [PATCH 42/85] sector import: Fix tests --- storage/sealer/mock/mock.go | 8 ++++++++ storage/sealer/sched_test.go | 4 ++++ storage/sealer/teststorage_test.go | 4 ++++ 3 files changed, 16 insertions(+) diff --git a/storage/sealer/mock/mock.go b/storage/sealer/mock/mock.go index 5b0afd35c..173a4ddf2 100644 --- a/storage/sealer/mock/mock.go +++ b/storage/sealer/mock/mock.go @@ -517,6 +517,10 @@ func (mgr *SectorMgr) ReleaseSectorKey(ctx context.Context, sector storiface.Sec return nil } +func (mgr *SectorMgr) DownloadSectorData(ctx context.Context, sector storiface.SectorRef, finalized bool, src map[storiface.SectorFileType]storiface.SectorData) error { + return xerrors.Errorf("not supported") +} + func (mgr *SectorMgr) Remove(ctx context.Context, sector storiface.SectorRef) error { mgr.lk.Lock() defer mgr.lk.Unlock() @@ -613,6 +617,10 @@ func (mgr *SectorMgr) ReturnFinalizeReplicaUpdate(ctx context.Context, callID st panic("not supported") } +func (mgr *SectorMgr) ReturnDownloadSector(ctx context.Context, callID storiface.CallID, err *storiface.CallError) error { + panic("not supported") +} + func (m mockVerifProver) VerifySeal(svi prooftypes.SealVerifyInfo) (bool, error) { plen, err := svi.SealProof.ProofSize() if err != nil { diff --git a/storage/sealer/sched_test.go b/storage/sealer/sched_test.go index a30a4d261..bf4b90b19 100644 --- a/storage/sealer/sched_test.go +++ b/storage/sealer/sched_test.go @@ -67,6 +67,10 @@ type schedTestWorker struct { ignoreResources bool } +func (s *schedTestWorker) DownloadSectorData(ctx context.Context, sector storiface.SectorRef, finalized bool, src map[storiface.SectorFileType]storiface.SectorData) (storiface.CallID, error) { + panic("implement me") +} + func (s *schedTestWorker) DataCid(ctx context.Context, pieceSize abi.UnpaddedPieceSize, pieceData storiface.Data) (storiface.CallID, error) { panic("implement me") } diff --git a/storage/sealer/teststorage_test.go b/storage/sealer/teststorage_test.go index f88aa9355..ee200d9bb 100644 --- a/storage/sealer/teststorage_test.go +++ b/storage/sealer/teststorage_test.go @@ -21,6 +21,10 @@ type testExec struct { apch chan chan apres } +func (t *testExec) DownloadSectorData(ctx context.Context, sector storiface.SectorRef, finalized bool, src map[storiface.SectorFileType]storiface.SectorData) error { + panic("implement me") +} + func (t *testExec) DataCid(ctx context.Context, pieceSize abi.UnpaddedPieceSize, pieceData storiface.Data) (abi.PieceInfo, error) { panic("implement me") } From cdadf5e5a9b33f3e9e8bf6286a0153235f8c7632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 2 Sep 2022 15:47:35 +0200 Subject: [PATCH 43/85] sector import: Implement handleReceiveSector --- storage/pipeline/receive.go | 49 ++++++++++++++++++++++++++++++++++--- storage/pipeline/types.go | 6 +++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/storage/pipeline/receive.go b/storage/pipeline/receive.go index b21829772..a9e56a845 100644 --- a/storage/pipeline/receive.go +++ b/storage/pipeline/receive.go @@ -11,6 +11,7 @@ import ( "github.com/filecoin-project/go-statemachine" "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/storage/sealer/storiface" ) func (m *Sealing) Receive(ctx context.Context, meta api.RemoteSectorMeta) error { @@ -102,6 +103,19 @@ func (m *Sealing) checkSectorMeta(ctx context.Context, meta api.RemoteSectorMeta info.CommD = meta.CommD // todo check cid prefixes info.CommR = meta.CommR + if meta.DataSealed == nil { + return SectorInfo{}, xerrors.Errorf("expected DataSealed to be set") + } + if meta.DataCache == nil { + return SectorInfo{}, xerrors.Errorf("expected DataCache to be set") + } + info.RemoteDataSealed = meta.DataSealed + info.RemoteDataCache = meta.DataCache + + // If we get a sector after PC2, assume that we're getting finalized sector data + // todo: maybe only set if C1 provider is set? + info.RemoteDataFinalized = true + fallthrough case GetTicket: fallthrough @@ -118,6 +132,11 @@ func (m *Sealing) checkSectorMeta(ctx context.Context, meta api.RemoteSectorMeta return SectorInfo{}, xerrors.Errorf("checking pieces: %w", err) } + if meta.DataUnsealed == nil { + return SectorInfo{}, xerrors.Errorf("expected DataUnsealed to be set") + } + info.RemoteDataUnsealed = meta.DataUnsealed + return info, nil default: return SectorInfo{}, xerrors.Errorf("imported sector State in not supported") @@ -125,8 +144,32 @@ func (m *Sealing) checkSectorMeta(ctx context.Context, meta api.RemoteSectorMeta } func (m *Sealing) handleReceiveSector(ctx statemachine.Context, sector SectorInfo) error { - // todo fetch stuff - // m.sealer.DownloadSectorData(ctx, m.minerSector(sector.SectorType, sector.SectorNumber), ) - panic("todo") + toFetch := map[storiface.SectorFileType]storiface.SectorData{} + + for fileType, data := range map[storiface.SectorFileType]*storiface.SectorData{ + storiface.FTUnsealed: sector.RemoteDataUnsealed, + storiface.FTSealed: sector.RemoteDataSealed, + storiface.FTCache: sector.RemoteDataCache, + } { + if data == nil { + continue + } + + if data.Local { + // todo check exists + continue + } + + toFetch[fileType] = *data + } + + if len(toFetch) > 0 { + if err := m.sealer.DownloadSectorData(ctx.Context(), m.minerSector(sector.SectorType, sector.SectorNumber), sector.RemoteDataFinalized, toFetch); err != nil { + return xerrors.Errorf("downloading sector data: %w", err) // todo send err event + } + } + + // todo data checks? + return ctx.Send(SectorReceived{}) } diff --git a/storage/pipeline/types.go b/storage/pipeline/types.go index 0321d113f..025b5a768 100644 --- a/storage/pipeline/types.go +++ b/storage/pipeline/types.go @@ -93,6 +93,12 @@ type SectorInfo struct { TerminateMessage *cid.Cid TerminatedAt abi.ChainEpoch + // Remote import + RemoteDataUnsealed *storiface.SectorData + RemoteDataSealed *storiface.SectorData + RemoteDataCache *storiface.SectorData + RemoteDataFinalized bool + // Debug LastErr string From 3caa0107791798174b6bb682c4acd02f5f71e356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 2 Sep 2022 17:12:58 +0200 Subject: [PATCH 44/85] sector import: Update type-gen --- build/openrpc/miner.json.gz | Bin 15794 -> 15786 bytes build/openrpc/worker.json.gz | Bin 5244 -> 5256 bytes documentation/en/api-v0-methods-miner.md | 33 +-- gen/main.go | 2 + storage/pipeline/cbor_gen.go | 145 ++++++++++- storage/sealer/ffiwrapper/sealer_cgo.go | 2 +- storage/sealer/storiface/cbor_gen.go | 312 +++++++++++++++++++++++ storage/sealer/storiface/storage.go | 16 +- 8 files changed, 492 insertions(+), 18 deletions(-) diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 30cd2019132fbd79c8a551d954c3a5aad52f9bee..550389d525e47b28a21a1318a66003fef389fe26 100644 GIT binary patch delta 2019 zcmV<92ORjad#Zb|f+>F^#ObeSn{MDwM53=J(b?7Wb#(&u720u6rHgHIxlwip=Si3< zpG63&+UFj@e$%**fc-JY1UlRIM7Q`ZhjEaLCZouXFoY3XhY}MNEJ$R}TH(l`8iD~0gbajJ0U3n)J};I`4z{+wS>%Zlv#hfKc+-fHjbW>sWd;n zDPC=KQLDPGh11r;X=~xMwQ!{U1>5`*b5fnYRY^Ois(PNtjPF2q>%*7r&B}9OVh3DW zRPX$#BEI=m3olRXjAcZeu}>}mKfYHk{b6JSu2wiHa80N-kN6c-KMA}WRZgwy+*u&= zD3sP+N#8?heG-2-D)+{XAHxonTh74Gv1$yd0gT2NFo4($_eBvb zV!g2IV6Pe5n);hg@P=&*$&#r%Y5^GTBrW2F^)7#~}zsE;D%ajNHOE%ZLsOZ zG`87one)eD&S`EQdO(YHvx?$?)7a?V1=a0IJAcDZ)FhMB7 zz0Z^)0dF)7yB+XEZwcgP5OVXXTTcfLXJ ziM3imch!wc6gKIVGdK5?&u6*W)d4Bj&&o-HY9=cu33ZoQIg$}5iGkv*9N$wEvU;1B z)8^&WWbJ-GH!;{f$CWQk556yEV!x0=#!whc&8GKn0z^u3d+P_^^y@_2vkgJYfI z`*^_tNeu$FNPi-{T=``IqNEmkaYiu3(7Z5_!MkVAekTm+5I%Iwtv}5rFFS}}t7783 zNNRVLa$wPUESt^X$;F*#=ZIxfnf9`YJPD#HI{^VFNfHxYao(=aR!aGsZH{l7JG+khgI>Y0Km)A%+PG$to8IIKn?`?F8#Op50w()f z4$BdB%Ka4H1$W1X&p#6z8U`}nZ7Dh<272T-8NoRY-UN1d2iPOeyDzFXSZjh@w*%}k z@Pi-=L&c+5Hxn(Mn1|>UNDO0!-cAe9O6FLR$%OrpS#6fvGhWHrlAb03d2gc0;+rjh zP`j&Wkb6XtL;E{BZ0UdZM!n&5)WDHT`qCvSukLUpgC>SC$~gFn3Kq2+WVrWxf8ssD zA&%Qf@dhKs+w_z3asMzkMbi}c$ zfni)=%dpiMcAAyUDOm8>FhqIRN-P_PQT_VtIGozdnL=ylS3rNX7h}H!ZO^iDAw2@1 z6zUa|Afpfzwzi_Vh$^!OVBu~kHoG5qgqH&0^KP0Vdmra~on%A0a{Uw{w)1H4@0rk0#_cB6*4ga@RLU}g92T~v&=Hy3J8T1Z8s$g0{|E4 B;vfJ3 delta 2027 zcmVI4O&o9N%5v~mkBGi#?+|p51JJhH)o!^QgcM%w68H(!SEzB= zrAugYX;5}w=E;pJpGC-|+UFh_e$)8QpZzh%1UlRIM7Q|fh4F=pCZotcFN6`B`4STq zS%RFi(_oXD>NjJ<83`Ej$Uv5E&xt}Ql`~GYuXB7+rhk1Sz?yj0w8GC4j^L#0mkjXC}T!k($4z33Rw|5eTIsun~bK6M@Z^<=yYZ>Xx6+$GuT*R`XTk zMRPJBW^sJHK1UI|X8>=8|FbbK5(rsnxjQP7lgF0dO$CpZJI}|xac*w+RWr4BuJUW| zRO7>f;yZs8m@kT>0>_02xcg9Rr{&4l!jqHSOlj`13oqNO%##r?jakD{pbfEQ9L++u zj3SVE>kKB~{o1BmU^(DVi0LWkLVn$odqa3q8D1`t6Wj@fpkr=Z!}Z5v!^7N^?O<d-W(~D_rv)eM~kHwtR+*Iy>7V9SF!~w~J-GA1x(LKQmd5Ljo4PAjK zMN^4UvvX`hZ&6{6O@w=&Da*#6@6@tJUKFsb^n4%=>;xOYVWiJ+7A0BC#erxu=hlS> z0~&vZgIpIG00+>x>kV@|e~k<<-vS8!K#t1`i$ZW>8Au0jg!2s(1M6Pu=XzsOth2WV zHq`Hs5%j`X_b2XrgWwZuwSw-d8Eev=WAi%2!D4W- z_MBgC16K+~b-2av=vlZXGOFy2D(9ex6Uc@R#rnpCPT>4$XcrlqL^WOsa>x?glC*#3 zDpG#S`TNQF!`uW<_Yy5?aXkCJum~WdR%jyQ5-k*Xn}Jj-`WJy)g|9u_cT&Vzj%8kH zzIZd0D9y~kcR%i73!O?VlIO%nQtWW;?si=lumH7Jd_ZF&#)mIOZRVmc) zJ@fV`HDN29lVMCX` zcTz>3cAlUeFCTEMQ+yxK*_Wj8vqkz7;pNKDtdEjf?8OO=a54Ch{bRrtAa+oFqw1c*S|U zK3gf}Z?-wUZH{l7)FSsVh}bSHVIrCd#@bX|}VeDW+= zgwjmSf_%tVIONC{MJNGa#{z#1uR) zJnz1!+F-2-a@`KF$G{JQEDRNoV%6 zKC<}-`0c?wi6*Kl1_p+4fi1&UXV__0GN)j{W5W>TT`RF{97ccj8>r)OYBOgFt)1VZ z(B3Wm60|+b%7yd@fKsSeOoEI;P}tgv<|3-h9)N|rq1fzx;1OO5gwMNaitK%y^L3I9 z>B{v}h}h1f!TSp((^6tTsvS-i{n>OnKR}CN?_gvcqJ#Mm=?61785}M~b2x@mjn|%* z@{{j29Su6#C-^bwAt8RBKynog?tz584#G`7I@ZRP0zEcO>*3nUezmpb+QP3ll2;_2DZ1+q==!lV35c0=EW}4KgtSGm}3ug96=Wv&1sq3J7^R Ja#v^y0{}*G`tJY$ diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index 76d71cb4c69e880e20c466b9fc20202b66dbad68..f60f1fc3a794c45d1ce14f3b5d957fbb2986c5e9 100644 GIT binary patch delta 5252 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

UdF>d!|Y8np4hw3bk(;M$9=Nr*zH}K@FbJ0bR zb3x8uGCA+{_mOknxX(z=>ZfLeeD(Jwt$)m?8;jhGH&85~wah}>z@f7g`6cp7fp`M( zUK`@6Ip;(%w|?QG>MK&a%rz}a9k!hm7pX}wj5R#Ez9cZQB{tj!Ond|DN@Ar&?3)Rq zB8W;oL`BOv_$vmvRc7bA%+)98JIt>(N8ictEX+jA)LUq#W>2Qg6!K0er0xPb1x2k0DatO9xf z1@BHa*3kvO*%Z(@WdC3fc!*L=W`DP20at*+djueuqDSB%!zQK&s12BVhcieK`-4L7 z9x!z`msX9RI7RVet2Xi6`O|Y3lUb1>#ed5=Sf5g2 z<$K=R=F$FYVkt%uI~%*s!`L}APgw#TI6$}awE8`Gw}8&gZkg`< z^Ib4*h07F?LJQU^a!Te~xqpbu--sv8ku%lP`RU56X_mU7<5W5^A%!Onq;r4avfz@u zn!=9TY7A;#=xCr3m&inh1>HleB3?$G)Bhzp=Kem_zU7Fl(rdzjl*rGr?G)>Ra;VVL z#$yN-y`v7)en;0Qz0L%hdS_xP!%2TUQB^~)j24A^E@D~Z*=$tH34eC`1A6zLgT*gS z%Z%jAC1D6!6ngibUrliBVXiGk$~)8o<|stbD|{J zm;_6}6%;(kSet$a#nqwSTfu~OzX|Q`=j?xO#)S6*z-_*QsYYtx8I4{V^l4o*<=B?I zZ@!UVJlDJ<+LD|_?td@^=-e(9MMEICZSLT;?mex@o|P1)FEcyY*cH>#jcJ`et@lhp z3L_L=ckIAukh2XMd^YNbw{lufBxm)=xohG;(?sT{6-DR7L_f9%P;B1#6+`rvF}IL^)M*n~yJmW1H>J6|iXi z+3L5eYGT>Oy)g%tgklTOIdTzzE&?P7*7Sh8B*YK#6j2^rN;OtgX}!K%|9=1g0RR8z KmP3(eF#!MxyG1Sl delta 5240 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 diff --git a/documentation/en/api-v0-methods-miner.md b/documentation/en/api-v0-methods-miner.md index 4034c4407..e28ce4846 100644 --- a/documentation/en/api-v0-methods-miner.md +++ b/documentation/en/api-v0-methods-miner.md @@ -3272,29 +3272,32 @@ Inputs: "DataUnsealed": { "Local": true, "URL": "string value", - "Headers": { - "Authorization": [ - "Bearer ey.." - ] - } + "Headers": [ + { + "Key": "string value", + "Value": "string value" + } + ] }, "DataSealed": { "Local": true, "URL": "string value", - "Headers": { - "Authorization": [ - "Bearer ey.." - ] - } + "Headers": [ + { + "Key": "string value", + "Value": "string value" + } + ] }, "DataCache": { "Local": true, "URL": "string value", - "Headers": { - "Authorization": [ - "Bearer ey.." - ] - } + "Headers": [ + { + "Key": "string value", + "Value": "string value" + } + ] } } ] diff --git a/gen/main.go b/gen/main.go index 77f340813..b7aaa0a9a 100644 --- a/gen/main.go +++ b/gen/main.go @@ -103,6 +103,8 @@ func main() { err = gen.WriteMapEncodersToFile("./storage/sealer/storiface/cbor_gen.go", "storiface", storiface.CallID{}, + storiface.SecDataHttpHeader{}, + storiface.SectorData{}, ) if err != nil { fmt.Println(err) diff --git a/storage/pipeline/cbor_gen.go b/storage/pipeline/cbor_gen.go index 0d9d1a918..a86e5f768 100644 --- a/storage/pipeline/cbor_gen.go +++ b/storage/pipeline/cbor_gen.go @@ -15,6 +15,7 @@ import ( abi "github.com/filecoin-project/go-state-types/abi" api "github.com/filecoin-project/lotus/api" + storiface "github.com/filecoin-project/lotus/storage/sealer/storiface" ) var _ = xerrors.Errorf @@ -30,7 +31,7 @@ func (t *SectorInfo) MarshalCBOR(w io.Writer) error { cw := cbg.NewCborWriter(w) - if _, err := cw.Write([]byte{184, 31}); err != nil { + if _, err := cw.Write([]byte{184, 36}); err != nil { return err } @@ -654,6 +655,70 @@ func (t *SectorInfo) MarshalCBOR(w io.Writer) error { } } + // t.RemoteDataUnsealed (storiface.SectorData) (struct) + if len("RemoteDataUnsealed") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"RemoteDataUnsealed\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("RemoteDataUnsealed"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("RemoteDataUnsealed")); err != nil { + return err + } + + if err := t.RemoteDataUnsealed.MarshalCBOR(cw); err != nil { + return err + } + + // t.RemoteDataSealed (storiface.SectorData) (struct) + if len("RemoteDataSealed") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"RemoteDataSealed\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("RemoteDataSealed"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("RemoteDataSealed")); err != nil { + return err + } + + if err := t.RemoteDataSealed.MarshalCBOR(cw); err != nil { + return err + } + + // t.RemoteDataCache (storiface.SectorData) (struct) + if len("RemoteDataCache") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"RemoteDataCache\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("RemoteDataCache"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("RemoteDataCache")); err != nil { + return err + } + + if err := t.RemoteDataCache.MarshalCBOR(cw); err != nil { + return err + } + + // t.RemoteDataFinalized (bool) (bool) + if len("RemoteDataFinalized") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"RemoteDataFinalized\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("RemoteDataFinalized"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("RemoteDataFinalized")); err != nil { + return err + } + + if err := cbg.WriteBool(w, t.RemoteDataFinalized); err != nil { + return err + } + // t.LastErr (string) (string) if len("LastErr") > cbg.MaxLength { return xerrors.Errorf("Value in field \"LastErr\" was too long") @@ -1354,6 +1419,84 @@ func (t *SectorInfo) UnmarshalCBOR(r io.Reader) (err error) { t.TerminatedAt = abi.ChainEpoch(extraI) } + // t.RemoteDataUnsealed (storiface.SectorData) (struct) + case "RemoteDataUnsealed": + + { + + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + t.RemoteDataUnsealed = new(storiface.SectorData) + if err := t.RemoteDataUnsealed.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.RemoteDataUnsealed pointer: %w", err) + } + } + + } + // t.RemoteDataSealed (storiface.SectorData) (struct) + case "RemoteDataSealed": + + { + + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + t.RemoteDataSealed = new(storiface.SectorData) + if err := t.RemoteDataSealed.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.RemoteDataSealed pointer: %w", err) + } + } + + } + // t.RemoteDataCache (storiface.SectorData) (struct) + case "RemoteDataCache": + + { + + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + t.RemoteDataCache = new(storiface.SectorData) + if err := t.RemoteDataCache.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.RemoteDataCache pointer: %w", err) + } + } + + } + // t.RemoteDataFinalized (bool) (bool) + case "RemoteDataFinalized": + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajOther { + return fmt.Errorf("booleans must be major type 7") + } + switch extra { + case 20: + t.RemoteDataFinalized = false + case 21: + t.RemoteDataFinalized = true + default: + return fmt.Errorf("booleans are either major type 7, value 20 or 21 (got %d)", extra) + } // t.LastErr (string) (string) case "LastErr": diff --git a/storage/sealer/ffiwrapper/sealer_cgo.go b/storage/sealer/ffiwrapper/sealer_cgo.go index 0ed53183c..cc137151f 100644 --- a/storage/sealer/ffiwrapper/sealer_cgo.go +++ b/storage/sealer/ffiwrapper/sealer_cgo.go @@ -1152,7 +1152,7 @@ func (sb *Sealer) DownloadSectorData(ctx context.Context, sector storiface.Secto return xerrors.Errorf("sector(%v) with local data (%#v) requested in DownloadSectorData", sector, data) } - _, err := spaths.FetchWithTemp(ctx, []string{data.URL}, out, data.Headers) + _, err := spaths.FetchWithTemp(ctx, []string{data.URL}, out, data.HttpHeaders()) if err != nil { return xerrors.Errorf("downloading sector data: %w", err) } diff --git a/storage/sealer/storiface/cbor_gen.go b/storage/sealer/storiface/cbor_gen.go index 5b4623175..6fec11557 100644 --- a/storage/sealer/storiface/cbor_gen.go +++ b/storage/sealer/storiface/cbor_gen.go @@ -153,3 +153,315 @@ func (t *CallID) UnmarshalCBOR(r io.Reader) (err error) { return nil } +func (t *SecDataHttpHeader) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write([]byte{162}); err != nil { + return err + } + + // t.Key (string) (string) + if len("Key") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Key\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("Key"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Key")); err != nil { + return err + } + + if len(t.Key) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.Key was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Key))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.Key)); err != nil { + return err + } + + // t.Value (string) (string) + if len("Value") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Value\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("Value"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Value")); err != nil { + return err + } + + if len(t.Value) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.Value was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Value))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.Value)); err != nil { + return err + } + return nil +} + +func (t *SecDataHttpHeader) UnmarshalCBOR(r io.Reader) (err error) { + *t = SecDataHttpHeader{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("SecDataHttpHeader: map struct too large (%d)", extra) + } + + var name string + n := extra + + for i := uint64(0); i < n; i++ { + + { + sval, err := cbg.ReadString(cr) + if err != nil { + return err + } + + name = string(sval) + } + + switch name { + // t.Key (string) (string) + case "Key": + + { + sval, err := cbg.ReadString(cr) + if err != nil { + return err + } + + t.Key = string(sval) + } + // t.Value (string) (string) + case "Value": + + { + sval, err := cbg.ReadString(cr) + if err != nil { + return err + } + + t.Value = string(sval) + } + + default: + // Field doesn't exist on this type, so ignore it + cbg.ScanForLinks(r, func(cid.Cid) {}) + } + } + + return nil +} +func (t *SectorData) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write([]byte{163}); err != nil { + return err + } + + // t.Local (bool) (bool) + if len("Local") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Local\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("Local"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Local")); err != nil { + return err + } + + if err := cbg.WriteBool(w, t.Local); err != nil { + return err + } + + // t.URL (string) (string) + if len("URL") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"URL\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("URL"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("URL")); err != nil { + return err + } + + if len(t.URL) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.URL was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.URL))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.URL)); err != nil { + return err + } + + // t.Headers ([]storiface.SecDataHttpHeader) (slice) + if len("Headers") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Headers\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("Headers"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Headers")); err != nil { + return err + } + + if len(t.Headers) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.Headers was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Headers))); err != nil { + return err + } + for _, v := range t.Headers { + if err := v.MarshalCBOR(cw); err != nil { + return err + } + } + return nil +} + +func (t *SectorData) UnmarshalCBOR(r io.Reader) (err error) { + *t = SectorData{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("SectorData: map struct too large (%d)", extra) + } + + var name string + n := extra + + for i := uint64(0); i < n; i++ { + + { + sval, err := cbg.ReadString(cr) + if err != nil { + return err + } + + name = string(sval) + } + + switch name { + // t.Local (bool) (bool) + case "Local": + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajOther { + return fmt.Errorf("booleans must be major type 7") + } + switch extra { + case 20: + t.Local = false + case 21: + t.Local = true + default: + return fmt.Errorf("booleans are either major type 7, value 20 or 21 (got %d)", extra) + } + // t.URL (string) (string) + case "URL": + + { + sval, err := cbg.ReadString(cr) + if err != nil { + return err + } + + t.URL = string(sval) + } + // t.Headers ([]storiface.SecDataHttpHeader) (slice) + case "Headers": + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.Headers: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.Headers = make([]SecDataHttpHeader, extra) + } + + for i := 0; i < int(extra); i++ { + + var v SecDataHttpHeader + if err := v.UnmarshalCBOR(cr); err != nil { + return err + } + + t.Headers[i] = v + } + + default: + // Field doesn't exist on this type, so ignore it + cbg.ScanForLinks(r, func(cid.Cid) {}) + } + } + + return nil +} diff --git a/storage/sealer/storiface/storage.go b/storage/sealer/storiface/storage.go index 4248cd71e..975011a0b 100644 --- a/storage/sealer/storiface/storage.go +++ b/storage/sealer/storiface/storage.go @@ -137,5 +137,19 @@ type SectorData struct { URL string // optional http headers to use when requesting sector data - Headers http.Header + Headers []SecDataHttpHeader +} + +func (sd *SectorData) HttpHeaders() http.Header { + out := http.Header{} + for _, header := range sd.Headers { + out[header.Key] = append(out[header.Key], header.Value) + } + return out +} + +// note: we can't use http.Header as that's backed by a go map, which is all kinds of messy +type SecDataHttpHeader struct { + Key string + Value string } From fef7232c6b0147d4dcfc80626eae7fdbecb21950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Mon, 5 Sep 2022 15:28:36 +0200 Subject: [PATCH 45/85] sector import: Initial SectorReceive itest --- .circleci/config.yml | 5 + api/api_storage.go | 2 +- itests/sector_import_test.go | 230 ++++++++++++++++++++++++++++++++++ storage/paths/http_handler.go | 10 +- storage/pipeline/receive.go | 6 +- 5 files changed, 246 insertions(+), 7 deletions(-) create mode 100644 itests/sector_import_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml index e3bb77b9f..58c89202b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -974,6 +974,11 @@ workflows: suite: itest-sector_finalize_early target: "./itests/sector_finalize_early_test.go" + - test: + name: test-itest-sector_import + suite: itest-sector_import + target: "./itests/sector_import_test.go" + - test: name: test-itest-sector_make_cc_avail suite: itest-sector_make_cc_avail diff --git a/api/api_storage.go b/api/api_storage.go index 7ae95cde2..d58576bb4 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -542,7 +542,7 @@ type RemoteSectorMeta struct { // Required in SubmitCommit and later PreCommitInfo *miner.SectorPreCommitInfo - PreCommitDeposit big.Int + PreCommitDeposit *big.Int PreCommitMessage *cid.Cid PreCommitTipSet types.TipSetKey diff --git a/itests/sector_import_test.go b/itests/sector_import_test.go new file mode 100644 index 000000000..848d8f8e6 --- /dev/null +++ b/itests/sector_import_test.go @@ -0,0 +1,230 @@ +package itests + +import ( + "bytes" + "context" + "fmt" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + "time" + + "github.com/gorilla/mux" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/api" + lminer "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/itests/kit" + spaths "github.com/filecoin-project/lotus/storage/paths" + "github.com/filecoin-project/lotus/storage/sealer/ffiwrapper" + "github.com/filecoin-project/lotus/storage/sealer/ffiwrapper/basicfs" + "github.com/filecoin-project/lotus/storage/sealer/storiface" + "github.com/filecoin-project/lotus/storage/sealer/tarutil" +) + +func TestSectorImportAfterPC2(t *testing.T) { + kit.QuietMiningLogs() + + var blockTime = 50 * time.Millisecond + + client, miner, ens := kit.EnsembleMinimal(t, kit.ThroughRPC()) + ens.InterconnectAll().BeginMining(blockTime) + + ctx := context.Background() + + // get some sector numbers + snums, err := miner.SectorNumReserveCount(ctx, "test-reservation-0001", 16) + require.NoError(t, err) + + sectorDir := t.TempDir() + + maddr, err := miner.ActorAddress(ctx) + require.NoError(t, err) + + mid, err := address.IDFromAddress(maddr) + require.NoError(t, err) + + spt, err := currentSealProof(ctx, client, maddr) + require.NoError(t, err) + + ssize, err := spt.SectorSize() + require.NoError(t, err) + + pieceSize := abi.PaddedPieceSize(ssize) + + //////// + // seal a sector up to pc2 outside of the pipeline + + sn, err := snums.First() + require.NoError(t, err) + snum := abi.SectorNumber(sn) + sid := abi.SectorID{ + Miner: abi.ActorID(mid), + Number: snum, + } + + sref := storiface.SectorRef{ + ID: sid, + ProofType: 0, + } + + sealer, err := ffiwrapper.New(&basicfs.Provider{ + Root: sectorDir, + }) + require.NoError(t, err) + + dataReader := bytes.NewReader(bytes.Repeat([]byte{0}, int(pieceSize.Unpadded()))) + + // create the unsealed sector file + pieceInfo, err := sealer.AddPiece(ctx, sref, nil, pieceSize.Unpadded(), dataReader) + require.NoError(t, err) + + // get most recent valid ticket epoch + ts, err := client.ChainHead(ctx) + require.NoError(t, err) + ticketEpoch := ts.Height() - policy.SealRandomnessLookback + + // ticket entropy is cbor-seriasized miner addr + buf := new(bytes.Buffer) + require.NoError(t, maddr.MarshalCBOR(buf)) + + // generate ticket randomness + rand, err := client.StateGetRandomnessFromTickets(ctx, crypto.DomainSeparationTag_SealRandomness, ticketEpoch, buf.Bytes(), ts.Key()) + require.NoError(t, err) + + // run PC1 + pc1out, err := sealer.SealPreCommit1(ctx, sref, abi.SealRandomness(rand), []abi.PieceInfo{pieceInfo}) + require.NoError(t, err) + + // run pc2 + scids, err := sealer.SealPreCommit2(ctx, sref, pc1out) + require.NoError(t, err) + + // todo split-finalize! + + //////// + // start http server serving sector data + + m := mux.NewRouter() + m.HandleFunc("/sectors/{type}/{id}", remoteGetSector(sectorDir)).Methods("GET") + srv := httptest.NewServer(m) + + //////// + // import the sector and continue sealing + + err = miner.SectorReceive(ctx, api.RemoteSectorMeta{ + State: "PreCommitting", + Sector: sid, + Type: spt, + + Pieces: []api.SectorPiece{ + { + Piece: pieceInfo, + DealInfo: nil, + }, + }, + + TicketValue: abi.SealRandomness(rand), + TicketEpoch: ticketEpoch, + + PreCommit1Out: pc1out, + + CommD: &scids.Unsealed, + CommR: &scids.Sealed, + + DataUnsealed: &storiface.SectorData{ + Local: false, + URL: fmt.Sprintf("%s/sectors/unsealed/s-t0%d-%d", srv.URL, mid, snum), + }, + DataSealed: &storiface.SectorData{ + Local: false, + URL: fmt.Sprintf("%s/sectors/sealed/s-t0%d-%d", srv.URL, mid, snum), + }, + DataCache: &storiface.SectorData{ + Local: false, + URL: fmt.Sprintf("%s/sectors/cache/s-t0%d-%d", srv.URL, mid, snum), + }, + }) + require.NoError(t, err) + + // check that we see the imported sector + ng, err := miner.SectorsListNonGenesis(ctx) + require.NoError(t, err) + require.Len(t, ng, 1) + require.Equal(t, snum, ng[0]) + + // todo wait sealed + +} + +func remoteGetSector(sectorRoot string) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + + vars := mux.Vars(r) + + // validate sector id + id, err := storiface.ParseSectorID(vars["id"]) + if err != nil { + w.WriteHeader(500) + return + } + + // validate type + _, err = spaths.FileTypeFromString(vars["type"]) + if err != nil { + w.WriteHeader(500) + return + } + + path := filepath.Join(sectorRoot, vars["type"], vars["id"]) + + stat, err := os.Stat(path) + if err != nil { + w.WriteHeader(500) + return + } + + if stat.IsDir() { + if _, has := r.Header["Range"]; has { + w.WriteHeader(500) + return + } + + w.Header().Set("Content-Type", "application/x-tar") + w.WriteHeader(200) + + err := tarutil.TarDirectory(path, w, make([]byte, 1<<20)) + if err != nil { + return + } + } else { + w.Header().Set("Content-Type", "application/octet-stream") + // will do a ranged read over the file at the given path if the caller has asked for a ranged read in the request headers. + http.ServeFile(w, r, path) + } + + fmt.Printf("served sector file/dir, sectorID=%+v, fileType=%s, path=%s\n", id, vars["type"], path) + } +} + +func currentSealProof(ctx context.Context, api api.FullNode, maddr address.Address) (abi.RegisteredSealProof, error) { + mi, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK) + if err != nil { + return 0, err + } + + ver, err := api.StateNetworkVersion(ctx, types.EmptyTSK) + if err != nil { + return 0, err + } + + return lminer.PreferredSealProofTypeFromWindowPoStType(ver, mi.WindowPoStProofType) +} diff --git a/storage/paths/http_handler.go b/storage/paths/http_handler.go index c8f465f25..fbf1c85b0 100644 --- a/storage/paths/http_handler.go +++ b/storage/paths/http_handler.go @@ -95,7 +95,7 @@ func (handler *FetchHandler) remoteGetSector(w http.ResponseWriter, r *http.Requ return } - ft, err := ftFromString(vars["type"]) + ft, err := FileTypeFromString(vars["type"]) if err != nil { log.Errorf("%+v", err) w.WriteHeader(500) @@ -167,7 +167,7 @@ func (handler *FetchHandler) remoteDeleteSector(w http.ResponseWriter, r *http.R return } - ft, err := ftFromString(vars["type"]) + ft, err := FileTypeFromString(vars["type"]) if err != nil { log.Errorf("%+v", err) w.WriteHeader(500) @@ -195,9 +195,9 @@ func (handler *FetchHandler) remoteGetAllocated(w http.ResponseWriter, r *http.R return } - ft, err := ftFromString(vars["type"]) + ft, err := FileTypeFromString(vars["type"]) if err != nil { - log.Errorf("ftFromString: %+v", err) + log.Errorf("FileTypeFromString: %+v", err) w.WriteHeader(500) return } @@ -311,7 +311,7 @@ func (handler *FetchHandler) generateSingleVanillaProof(w http.ResponseWriter, r http.ServeContent(w, r, "", time.Time{}, bytes.NewReader(vanilla)) } -func ftFromString(t string) (storiface.SectorFileType, error) { +func FileTypeFromString(t string) (storiface.SectorFileType, error) { switch t { case storiface.FTUnsealed.String(): return storiface.FTUnsealed, nil diff --git a/storage/pipeline/receive.go b/storage/pipeline/receive.go index a9e56a845..3e1de43d9 100644 --- a/storage/pipeline/receive.go +++ b/storage/pipeline/receive.go @@ -81,8 +81,12 @@ func (m *Sealing) checkSectorMeta(ctx context.Context, meta api.RemoteSectorMeta fallthrough case SubmitCommit: + if meta.PreCommitDeposit == nil { + return SectorInfo{}, xerrors.Errorf("sector PreCommitDeposit was null") + } + info.PreCommitInfo = meta.PreCommitInfo - info.PreCommitDeposit = meta.PreCommitDeposit + info.PreCommitDeposit = *meta.PreCommitDeposit info.PreCommitMessage = meta.PreCommitMessage info.PreCommitTipSet = meta.PreCommitTipSet From 12a8ab5ac78f4606fe15cb17423194df23840f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 6 Sep 2022 10:52:56 +0200 Subject: [PATCH 46/85] sector import: Add missing initial fsm state transition --- node/impl/storminer.go | 15 ++++++++++++--- storage/pipeline/fsm.go | 1 + storage/pipeline/receive.go | 1 + 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/node/impl/storminer.go b/node/impl/storminer.go index 9f1b9af35..c26d50410 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -182,16 +182,20 @@ func (sm *StorageMinerAPI) PledgeSector(ctx context.Context) (abi.SectorID, erro return abi.SectorID{}, err } + return sm.waitSectorStarted(ctx, sr.ID) +} + +func (sm *StorageMinerAPI) waitSectorStarted(ctx context.Context, si abi.SectorID) (abi.SectorID, error) { // wait for the sector to enter the Packing state // TODO: instead of polling implement some pubsub-type thing in storagefsm for { - info, err := sm.Miner.SectorsStatus(ctx, sr.ID.Number, false) + info, err := sm.Miner.SectorsStatus(ctx, si.Number, false) if err != nil { return abi.SectorID{}, xerrors.Errorf("getting pledged sector info: %w", err) } if info.State != api.SectorState(sealing.UndefinedSectorState) { - return sr.ID, nil + return si, nil } select { @@ -449,7 +453,12 @@ func (sm *StorageMinerAPI) SectorNumFree(ctx context.Context, name string) error } func (sm *StorageMinerAPI) SectorReceive(ctx context.Context, meta api.RemoteSectorMeta) error { - return sm.Miner.Receive(ctx, meta) + if err := sm.Miner.Receive(ctx, meta); err != nil { + return err + } + + _, err := sm.waitSectorStarted(ctx, meta.Sector) + return err } func (sm *StorageMinerAPI) ComputeWindowPoSt(ctx context.Context, dlIdx uint64, tsk types.TipSetKey) ([]minertypes.SubmitWindowedPoStParams, error) { diff --git a/storage/pipeline/fsm.go b/storage/pipeline/fsm.go index 59e99bc59..8512d7775 100644 --- a/storage/pipeline/fsm.go +++ b/storage/pipeline/fsm.go @@ -50,6 +50,7 @@ var fsmPlanners = map[SectorState]func(events []statemachine.Event, state *Secto UndefinedSectorState: planOne( on(SectorStart{}, WaitDeals), on(SectorStartCC{}, Packing), + on(SectorReceive{}, ReceiveSector), ), Empty: planOne( // deprecated on(SectorAddPiece{}, AddPiece), diff --git a/storage/pipeline/receive.go b/storage/pipeline/receive.go index 3e1de43d9..ef6496a35 100644 --- a/storage/pipeline/receive.go +++ b/storage/pipeline/receive.go @@ -14,6 +14,7 @@ import ( "github.com/filecoin-project/lotus/storage/sealer/storiface" ) +// todo m.inputLk? func (m *Sealing) Receive(ctx context.Context, meta api.RemoteSectorMeta) error { si, err := m.checkSectorMeta(ctx, meta) if err != nil { From 9c6d531ae73891ce29b9355010bb2b80f9dfcee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 6 Sep 2022 11:06:30 +0200 Subject: [PATCH 47/85] sector import: Plumb sector download task canfig --- cmd/lotus-worker/main.go | 9 +++++++++ documentation/en/cli-lotus-worker.md | 1 + documentation/en/default-lotus-miner-config.toml | 6 ++++-- itests/kit/ensemble.go | 1 + itests/sector_import_test.go | 3 +-- node/config/def.go | 1 + node/config/doc_gen.go | 8 +++++++- node/config/storage.go | 1 + node/config/types.go | 2 +- storage/sealer/manager.go | 4 ++++ 10 files changed, 30 insertions(+), 6 deletions(-) diff --git a/cmd/lotus-worker/main.go b/cmd/lotus-worker/main.go index 286cce883..8b8db5aa7 100644 --- a/cmd/lotus-worker/main.go +++ b/cmd/lotus-worker/main.go @@ -224,6 +224,12 @@ var runCmd = &cli.Command{ Value: true, EnvVars: []string{"LOTUS_WORKER_REGEN_SECTOR_KEY"}, }, + &cli.BoolFlag{ + Name: "sector-download", + Usage: "enable external sector data download", + Value: false, + EnvVars: []string{"LOTUS_WORKER_SECTOR_DOWNLOAD"}, + }, &cli.BoolFlag{ Name: "windowpost", Usage: "enable window post", @@ -373,6 +379,9 @@ var runCmd = &cli.Command{ if (workerType == sealtasks.WorkerSealing || cctx.IsSet("addpiece")) && cctx.Bool("addpiece") { taskTypes = append(taskTypes, sealtasks.TTAddPiece, sealtasks.TTDataCid) } + if (workerType == sealtasks.WorkerSealing || cctx.IsSet("sector-download")) && cctx.Bool("sector-download") { + taskTypes = append(taskTypes, sealtasks.TTDownloadSector) + } if (workerType == sealtasks.WorkerSealing || cctx.IsSet("precommit1")) && cctx.Bool("precommit1") { taskTypes = append(taskTypes, sealtasks.TTPreCommit1) } diff --git a/documentation/en/cli-lotus-worker.md b/documentation/en/cli-lotus-worker.md index ec81d28d7..920fb3001 100644 --- a/documentation/en/cli-lotus-worker.md +++ b/documentation/en/cli-lotus-worker.md @@ -53,6 +53,7 @@ OPTIONS: --prove-replica-update2 enable prove replica update 2 (default: true) [$LOTUS_WORKER_PROVE_REPLICA_UPDATE2] --regen-sector-key enable regen sector key (default: true) [$LOTUS_WORKER_REGEN_SECTOR_KEY] --replica-update enable replica update (default: true) [$LOTUS_WORKER_REPLICA_UPDATE] + --sector-download enable external sector data download (default: false) [$LOTUS_WORKER_SECTOR_DOWNLOAD] --timeout value used when 'listen' is unspecified. must be a valid duration recognized by golang's time.ParseDuration function (default: "30m") [$LOTUS_WORKER_TIMEOUT] --unseal enable unsealing (32G sectors: 1 core, 128GiB Memory) (default: true) [$LOTUS_WORKER_UNSEAL] --windowpost enable window post (default: false) [$LOTUS_WORKER_WINDOWPOST] diff --git a/documentation/en/default-lotus-miner-config.toml b/documentation/en/default-lotus-miner-config.toml index ae235e01e..0147f979e 100644 --- a/documentation/en/default-lotus-miner-config.toml +++ b/documentation/en/default-lotus-miner-config.toml @@ -612,8 +612,10 @@ # env var: LOTUS_STORAGE_PARALLELFETCHLIMIT #ParallelFetchLimit = 10 - # Local worker config - # + # type: bool + # env var: LOTUS_STORAGE_ALLOWSECTORDOWNLOAD + #AllowSectorDownload = true + # type: bool # env var: LOTUS_STORAGE_ALLOWADDPIECE #AllowAddPiece = true diff --git a/itests/kit/ensemble.go b/itests/kit/ensemble.go index bcc2d93c2..8fa781e65 100644 --- a/itests/kit/ensemble.go +++ b/itests/kit/ensemble.go @@ -636,6 +636,7 @@ func (n *Ensemble) Start() *Ensemble { scfg := config.DefaultStorageMiner() if noLocal { + scfg.Storage.AllowSectorDownload = false scfg.Storage.AllowAddPiece = false scfg.Storage.AllowPreCommit1 = false scfg.Storage.AllowPreCommit2 = false diff --git a/itests/sector_import_test.go b/itests/sector_import_test.go index 848d8f8e6..e820d9d80 100644 --- a/itests/sector_import_test.go +++ b/itests/sector_import_test.go @@ -161,8 +161,7 @@ func TestSectorImportAfterPC2(t *testing.T) { require.Len(t, ng, 1) require.Equal(t, snum, ng[0]) - // todo wait sealed - + miner.WaitSectorsProving(ctx, map[abi.SectorNumber]struct{}{snum: {}}) } func remoteGetSector(sectorRoot string) func(w http.ResponseWriter, r *http.Request) { diff --git a/node/config/def.go b/node/config/def.go index ad2150af3..0566c7d99 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -146,6 +146,7 @@ func DefaultStorageMiner() *StorageMiner { }, Storage: SealerConfig{ + AllowSectorDownload: true, AllowAddPiece: true, AllowPreCommit1: true, AllowPreCommit2: true, diff --git a/node/config/doc_gen.go b/node/config/doc_gen.go index 6b5927448..3d90afd0b 100644 --- a/node/config/doc_gen.go +++ b/node/config/doc_gen.go @@ -796,11 +796,17 @@ This parameter is ONLY applicable if the retrieval pricing policy strategy has b Comment: ``, }, + { + Num: "AllowSectorDownload", + Type: "bool", + + Comment: ``, + }, { Name: "AllowAddPiece", Type: "bool", - Comment: `Local worker config`, + Comment: ``, }, { Name: "AllowPreCommit1", diff --git a/node/config/storage.go b/node/config/storage.go index 20f1791e6..0c72eabd1 100644 --- a/node/config/storage.go +++ b/node/config/storage.go @@ -54,6 +54,7 @@ func WriteStorageFile(path string, config paths.StorageConfig) error { func (c *StorageMiner) StorageManager() sealer.Config { return sealer.Config{ ParallelFetchLimit: c.Storage.ParallelFetchLimit, + AllowSectorDownload: c.Storage.AllowSectorDownload, AllowAddPiece: c.Storage.AllowAddPiece, AllowPreCommit1: c.Storage.AllowPreCommit1, AllowPreCommit2: c.Storage.AllowPreCommit2, diff --git a/node/config/types.go b/node/config/types.go index 3c85587c3..1f55da1c6 100644 --- a/node/config/types.go +++ b/node/config/types.go @@ -409,7 +409,7 @@ type SealingConfig struct { type SealerConfig struct { ParallelFetchLimit int - // Local worker config + AllowSectorDownload bool AllowAddPiece bool AllowPreCommit1 bool AllowPreCommit2 bool diff --git a/storage/sealer/manager.go b/storage/sealer/manager.go index 78c0622f4..a4d31e21b 100644 --- a/storage/sealer/manager.go +++ b/storage/sealer/manager.go @@ -108,6 +108,7 @@ type Config struct { ParallelFetchLimit int // Local worker config + AllowSectorDownload bool AllowAddPiece bool AllowPreCommit1 bool AllowPreCommit2 bool @@ -182,6 +183,9 @@ func New(ctx context.Context, lstor *paths.Local, stor paths.Store, ls paths.Loc localTasks := []sealtasks.TaskType{ sealtasks.TTCommit1, sealtasks.TTProveReplicaUpdate1, sealtasks.TTFinalize, sealtasks.TTFetch, sealtasks.TTFinalizeReplicaUpdate, } + if sc.AllowSectorDownload { + localTasks = append(localTasks, sealtasks.TTDownloadSector) + } if sc.AllowAddPiece { localTasks = append(localTasks, sealtasks.TTAddPiece, sealtasks.TTDataCid) } From 3086e8aa01ac7a2e9c74fd69ee601391e06cff44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 6 Sep 2022 11:27:07 +0200 Subject: [PATCH 48/85] sector import: Seal with the correct proof type in saas itest --- itests/sector_import_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/itests/sector_import_test.go b/itests/sector_import_test.go index e820d9d80..c578ab347 100644 --- a/itests/sector_import_test.go +++ b/itests/sector_import_test.go @@ -73,7 +73,7 @@ func TestSectorImportAfterPC2(t *testing.T) { sref := storiface.SectorRef{ ID: sid, - ProofType: 0, + ProofType: spt, } sealer, err := ffiwrapper.New(&basicfs.Provider{ From 830c2ffdf583f409ee3e2e09f85e3b8da48ff2a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 6 Sep 2022 17:09:27 +0200 Subject: [PATCH 49/85] sector import: FinalizeInto, send finalized cacde in itest --- itests/sector_import_test.go | 13 ++++++-- storage/sealer/ffiwrapper/sealer_cgo.go | 41 +++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/itests/sector_import_test.go b/itests/sector_import_test.go index c578ab347..818675828 100644 --- a/itests/sector_import_test.go +++ b/itests/sector_import_test.go @@ -108,7 +108,10 @@ func TestSectorImportAfterPC2(t *testing.T) { scids, err := sealer.SealPreCommit2(ctx, sref, pc1out) require.NoError(t, err) - // todo split-finalize! + // make finalized cache, put it in [sectorDir]/fin-cache + finDst := filepath.Join(sectorDir, "fin-cache", fmt.Sprintf("s-t01000-%d", snum)) + require.NoError(t, os.MkdirAll(finDst, 0777)) + require.NoError(t, sealer.FinalizeSectorInto(ctx, sref, finDst)) //////// // start http server serving sector data @@ -183,7 +186,13 @@ func remoteGetSector(sectorRoot string) func(w http.ResponseWriter, r *http.Requ return } - path := filepath.Join(sectorRoot, vars["type"], vars["id"]) + typ := vars["type"] + if typ == "cache" { + // if cache is requested, send the finalized cache we've created above + typ = "fin-cache" + } + + path := filepath.Join(sectorRoot, typ, vars["id"]) stat, err := os.Stat(path) if err != nil { diff --git a/storage/sealer/ffiwrapper/sealer_cgo.go b/storage/sealer/ffiwrapper/sealer_cgo.go index cc137151f..d73b72787 100644 --- a/storage/sealer/ffiwrapper/sealer_cgo.go +++ b/storage/sealer/ffiwrapper/sealer_cgo.go @@ -11,9 +11,12 @@ import ( "encoding/base64" "encoding/json" "io" + "io/ioutil" "math/bits" "os" + "path/filepath" "runtime" + "syscall" "github.com/detailyang/go-fallocate" "github.com/ipfs/go-cid" @@ -1069,6 +1072,44 @@ func (sb *Sealer) FinalizeSector(ctx context.Context, sector storiface.SectorRef return ffi.ClearCache(uint64(ssize), paths.Cache) } +// FinalizeSectorInto is like FinalizeSector, but writes finalized sector cache into a new path +func (sb *Sealer) FinalizeSectorInto(ctx context.Context, sector storiface.SectorRef, dest string) error { + ssize, err := sector.ProofType.SectorSize() + if err != nil { + return err + } + + paths, done, err := sb.sectors.AcquireSector(ctx, sector, storiface.FTCache, 0, storiface.PathStorage) + if err != nil { + return xerrors.Errorf("acquiring sector cache path: %w", err) + } + defer done() + + files, err := ioutil.ReadDir(paths.Cache) + if err != nil { + return err + } + for _, file := range files { + if file.Name() != "t_aux" && file.Name() != "p_aux" { + // link all the non-aux files + if err := syscall.Link(filepath.Join(paths.Cache, file.Name()), filepath.Join(dest, file.Name())); err != nil { + return xerrors.Errorf("link %s: %w", file.Name(), err) + } + continue + } + + d, err := os.ReadFile(filepath.Join(paths.Cache, file.Name())) + if err != nil { + return xerrors.Errorf("read %s: %w", file.Name(), err) + } + if err := os.WriteFile(filepath.Join(dest, file.Name()), d, 0666); err != nil { + return xerrors.Errorf("write %s: %w", file.Name(), err) + } + } + + return ffi.ClearCache(uint64(ssize), dest) +} + func (sb *Sealer) FinalizeReplicaUpdate(ctx context.Context, sector storiface.SectorRef, keepUnsealed []storiface.Range) error { ssize, err := sector.ProofType.SectorSize() if err != nil { From 9f03569cd0bf49d5d49ad786e14b9ad5f19cc0ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 9 Sep 2022 10:32:27 +0200 Subject: [PATCH 50/85] sector import: Remote Commit1 --- api/api_storage.go | 14 +++++- build/openrpc/miner.json.gz | Bin 15786 -> 15801 bytes cmd/lotus-miner/info.go | 1 + documentation/en/api-v0-methods-miner.md | 3 +- itests/sector_import_test.go | 52 ++++++++++++++++++++++ storage/pipeline/cbor_gen.go | 36 ++++++++++++++- storage/pipeline/fsm.go | 7 +++ storage/pipeline/fsm_events.go | 5 +++ storage/pipeline/receive.go | 3 +- storage/pipeline/sector_state.go | 2 + storage/pipeline/states_failed.go | 12 +++++ storage/pipeline/states_sealing.go | 54 ++++++++++++++++++++--- storage/pipeline/types.go | 9 ++-- 13 files changed, 183 insertions(+), 15 deletions(-) diff --git a/api/api_storage.go b/api/api_storage.go index d58576bb4..cf44614e1 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -572,6 +572,18 @@ type RemoteSectorMeta struct { //////// // SEALING SERVICE HOOKS - // todo Commit1Provider + // URL + // todo better doc + RemoteCommit1Endpoint string + // todo OnDone / OnStateChange } + +type RemoteCommit1Params struct { + Ticket, Seed []byte + + Unsealed cid.Cid + Sealed cid.Cid + + ProofType abi.RegisteredSealProof +} diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 550389d525e47b28a21a1318a66003fef389fe26..61fcef75cdf265fe7db147deffcb16be1289e05c 100644 GIT binary patch delta 5200 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 diff --git a/cmd/lotus-miner/info.go b/cmd/lotus-miner/info.go index ecce9c6c2..9bc891c5b 100644 --- a/cmd/lotus-miner/info.go +++ b/cmd/lotus-miner/info.go @@ -545,6 +545,7 @@ var stateList = []stateMeta{ {col: color.FgRed, state: sealing.SealPreCommit2Failed}, {col: color.FgRed, state: sealing.PreCommitFailed}, {col: color.FgRed, state: sealing.ComputeProofFailed}, + {col: color.FgRed, state: sealing.RemoteCommit1Failed}, {col: color.FgRed, state: sealing.CommitFailed}, {col: color.FgRed, state: sealing.CommitFinalizeFailed}, {col: color.FgRed, state: sealing.PackingFailed}, diff --git a/documentation/en/api-v0-methods-miner.md b/documentation/en/api-v0-methods-miner.md index e28ce4846..94ac08cf5 100644 --- a/documentation/en/api-v0-methods-miner.md +++ b/documentation/en/api-v0-methods-miner.md @@ -3298,7 +3298,8 @@ Inputs: "Value": "string value" } ] - } + }, + "RemoteCommit1Endpoint": "string value" } ] ``` diff --git a/itests/sector_import_test.go b/itests/sector_import_test.go index 818675828..7ff4fa48f 100644 --- a/itests/sector_import_test.go +++ b/itests/sector_import_test.go @@ -3,6 +3,7 @@ package itests import ( "bytes" "context" + "encoding/json" "fmt" "net/http" "net/http/httptest" @@ -118,6 +119,7 @@ func TestSectorImportAfterPC2(t *testing.T) { m := mux.NewRouter() m.HandleFunc("/sectors/{type}/{id}", remoteGetSector(sectorDir)).Methods("GET") + m.HandleFunc("/sectors/{id}/commit1", remoteCommit1(sealer)).Methods("POST") srv := httptest.NewServer(m) //////// @@ -155,6 +157,8 @@ func TestSectorImportAfterPC2(t *testing.T) { Local: false, URL: fmt.Sprintf("%s/sectors/cache/s-t0%d-%d", srv.URL, mid, snum), }, + + RemoteCommit1Endpoint: fmt.Sprintf("%s/sectors/s-t0%d-%d/commit1", srv.URL, mid, snum), }) require.NoError(t, err) @@ -167,6 +171,54 @@ func TestSectorImportAfterPC2(t *testing.T) { miner.WaitSectorsProving(ctx, map[abi.SectorNumber]struct{}{snum: {}}) } +func remoteCommit1(s *ffiwrapper.Sealer) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + // validate sector id + id, err := storiface.ParseSectorID(vars["id"]) + if err != nil { + w.WriteHeader(500) + return + } + + var params api.RemoteCommit1Params + if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { + w.WriteHeader(500) + return + } + + sref := storiface.SectorRef{ + ID: id, + ProofType: params.ProofType, + } + + ssize, err := params.ProofType.SectorSize() + if err != nil { + w.WriteHeader(500) + return + } + + p, err := s.SealCommit1(r.Context(), sref, params.Ticket, params.Seed, []abi.PieceInfo{ + { + Size: abi.PaddedPieceSize(ssize), + PieceCID: params.Unsealed, + }, + }, storiface.SectorCids{ + Unsealed: params.Unsealed, + Sealed: params.Sealed, + }) + if err != nil { + w.WriteHeader(500) + return + } + + if _, err := w.Write(p); err != nil { + fmt.Println("c1 write error") + } + } +} + func remoteGetSector(sectorRoot string) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { diff --git a/storage/pipeline/cbor_gen.go b/storage/pipeline/cbor_gen.go index a86e5f768..4839a3202 100644 --- a/storage/pipeline/cbor_gen.go +++ b/storage/pipeline/cbor_gen.go @@ -31,7 +31,7 @@ func (t *SectorInfo) MarshalCBOR(w io.Writer) error { cw := cbg.NewCborWriter(w) - if _, err := cw.Write([]byte{184, 36}); err != nil { + if _, err := cw.Write([]byte{184, 37}); err != nil { return err } @@ -703,6 +703,29 @@ func (t *SectorInfo) MarshalCBOR(w io.Writer) error { return err } + // t.RemoteCommit1Endpoint (string) (string) + if len("RemoteCommit1Endpoint") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"RemoteCommit1Endpoint\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("RemoteCommit1Endpoint"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("RemoteCommit1Endpoint")); err != nil { + return err + } + + if len(t.RemoteCommit1Endpoint) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.RemoteCommit1Endpoint was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.RemoteCommit1Endpoint))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.RemoteCommit1Endpoint)); err != nil { + return err + } + // t.RemoteDataFinalized (bool) (bool) if len("RemoteDataFinalized") > cbg.MaxLength { return xerrors.Errorf("Value in field \"RemoteDataFinalized\" was too long") @@ -1479,6 +1502,17 @@ func (t *SectorInfo) UnmarshalCBOR(r io.Reader) (err error) { } } + // t.RemoteCommit1Endpoint (string) (string) + case "RemoteCommit1Endpoint": + + { + sval, err := cbg.ReadString(cr) + if err != nil { + return err + } + + t.RemoteCommit1Endpoint = string(sval) + } // t.RemoteDataFinalized (bool) (bool) case "RemoteDataFinalized": diff --git a/storage/pipeline/fsm.go b/storage/pipeline/fsm.go index 8512d7775..fb0d16b38 100644 --- a/storage/pipeline/fsm.go +++ b/storage/pipeline/fsm.go @@ -225,6 +225,9 @@ var fsmPlanners = map[SectorState]func(events []statemachine.Event, state *Secto on(SectorRetryComputeProof{}, Committing), on(SectorSealPreCommit1Failed{}, SealPreCommit1Failed), ), + RemoteCommit1Failed: planOne( + on(SectorRetryComputeProof{}, Committing), + ), CommitFinalizeFailed: planOne( on(SectorRetryFinalize{}, CommitFinalize), ), @@ -539,6 +542,8 @@ func (m *Sealing) plan(events []statemachine.Event, state *SectorInfo) (func(sta return m.handlePreCommitFailed, processed, nil case ComputeProofFailed: return m.handleComputeProofFailed, processed, nil + case RemoteCommit1Failed: + return m.handleRemoteCommit1Failed, processed, nil case CommitFailed: return m.handleCommitFailed, processed, nil case CommitFinalizeFailed: @@ -666,6 +671,8 @@ func planCommitting(events []statemachine.Event, state *SectorInfo) (uint64, err return uint64(i + 1), nil case SectorComputeProofFailed: state.State = ComputeProofFailed + case SectorRemoteCommit1Failed: + state.State = RemoteCommit1Failed case SectorSealPreCommit1Failed: state.State = SealPreCommit1Failed case SectorCommitFailed: diff --git a/storage/pipeline/fsm_events.go b/storage/pipeline/fsm_events.go index ec87f8e38..600770c01 100644 --- a/storage/pipeline/fsm_events.go +++ b/storage/pipeline/fsm_events.go @@ -218,6 +218,11 @@ func (evt SectorSeedReady) apply(state *SectorInfo) { state.SeedValue = evt.SeedValue } +type SectorRemoteCommit1Failed struct{ error } + +func (evt SectorRemoteCommit1Failed) FormatError(xerrors.Printer) (next error) { return evt.error } +func (evt SectorRemoteCommit1Failed) apply(*SectorInfo) {} + type SectorComputeProofFailed struct{ error } func (evt SectorComputeProofFailed) FormatError(xerrors.Printer) (next error) { return evt.error } diff --git a/storage/pipeline/receive.go b/storage/pipeline/receive.go index ef6496a35..fd4f64a66 100644 --- a/storage/pipeline/receive.go +++ b/storage/pipeline/receive.go @@ -114,8 +114,9 @@ func (m *Sealing) checkSectorMeta(ctx context.Context, meta api.RemoteSectorMeta if meta.DataCache == nil { return SectorInfo{}, xerrors.Errorf("expected DataCache to be set") } - info.RemoteDataSealed = meta.DataSealed + info.RemoteDataSealed = meta.DataSealed // todo make head requests to check? info.RemoteDataCache = meta.DataCache + info.RemoteCommit1Endpoint = meta.RemoteCommit1Endpoint // If we get a sector after PC2, assume that we're getting finalized sector data // todo: maybe only set if C1 provider is set? diff --git a/storage/pipeline/sector_state.go b/storage/pipeline/sector_state.go index ad77fdbd5..136e72a16 100644 --- a/storage/pipeline/sector_state.go +++ b/storage/pipeline/sector_state.go @@ -31,6 +31,7 @@ var ExistSectorStateList = map[SectorState]struct{}{ SealPreCommit2Failed: {}, PreCommitFailed: {}, ComputeProofFailed: {}, + RemoteCommit1Failed: {}, CommitFailed: {}, PackingFailed: {}, FinalizeFailed: {}, @@ -124,6 +125,7 @@ const ( SealPreCommit2Failed SectorState = "SealPreCommit2Failed" PreCommitFailed SectorState = "PreCommitFailed" ComputeProofFailed SectorState = "ComputeProofFailed" + RemoteCommit1Failed SectorState = "RemoteCommit1Failed" CommitFailed SectorState = "CommitFailed" PackingFailed SectorState = "PackingFailed" // TODO: deprecated, remove FinalizeFailed SectorState = "FinalizeFailed" diff --git a/storage/pipeline/states_failed.go b/storage/pipeline/states_failed.go index 09e090bc8..fb7145f50 100644 --- a/storage/pipeline/states_failed.go +++ b/storage/pipeline/states_failed.go @@ -184,6 +184,18 @@ func (m *Sealing) handleComputeProofFailed(ctx statemachine.Context, sector Sect return ctx.Send(SectorRetryComputeProof{}) } +func (m *Sealing) handleRemoteCommit1Failed(ctx statemachine.Context, sector SectorInfo) error { + if err := failedCooldown(ctx, sector); err != nil { + return err + } + + if sector.InvalidProofs > 1 { + log.Errorw("consecutive remote commit1 fails", "sector", sector.SectorNumber, "c1url", sector.RemoteCommit1Endpoint) + } + + return ctx.Send(SectorRetryComputeProof{}) +} + func (m *Sealing) handleSubmitReplicaUpdateFailed(ctx statemachine.Context, sector SectorInfo) error { if err := failedCooldown(ctx, sector); err != nil { return err diff --git a/storage/pipeline/states_sealing.go b/storage/pipeline/states_sealing.go index f2d61fc62..56b7eb37a 100644 --- a/storage/pipeline/states_sealing.go +++ b/storage/pipeline/states_sealing.go @@ -3,6 +3,9 @@ package sealing import ( "bytes" "context" + "encoding/json" + "io" + "net/http" "github.com/ipfs/go-cid" "golang.org/x/xerrors" @@ -569,13 +572,50 @@ func (m *Sealing) handleCommitting(ctx statemachine.Context, sector SectorInfo) return ctx.Send(SectorCommitFailed{xerrors.Errorf("sector had nil commR or commD")}) } - cids := storiface.SectorCids{ - Unsealed: *sector.CommD, - Sealed: *sector.CommR, - } - c2in, err := m.sealer.SealCommit1(sector.sealingCtx(ctx.Context()), m.minerSector(sector.SectorType, sector.SectorNumber), sector.TicketValue, sector.SeedValue, sector.pieceInfos(), cids) - if err != nil { - return ctx.Send(SectorComputeProofFailed{xerrors.Errorf("computing seal proof failed(1): %w", err)}) + var c2in storiface.Commit1Out + if sector.RemoteCommit1Endpoint == "" { + cids := storiface.SectorCids{ + Unsealed: *sector.CommD, + Sealed: *sector.CommR, + } + c2in, err = m.sealer.SealCommit1(sector.sealingCtx(ctx.Context()), m.minerSector(sector.SectorType, sector.SectorNumber), sector.TicketValue, sector.SeedValue, sector.pieceInfos(), cids) + if err != nil { + return ctx.Send(SectorComputeProofFailed{xerrors.Errorf("computing seal proof failed(1): %w", err)}) + } + } else { + reqData := api.RemoteCommit1Params{ + Ticket: sector.TicketValue, + Seed: sector.SeedValue, + Unsealed: *sector.CommD, + Sealed: *sector.CommR, + ProofType: sector.SectorType, + } + reqBody, err := json.Marshal(&reqData) + if err != nil { + return xerrors.Errorf("marshaling remote commit1 request: %w", err) + } + + req, err := http.NewRequest("POST", sector.RemoteCommit1Endpoint, bytes.NewReader(reqBody)) + if err != nil { + return ctx.Send(SectorRemoteCommit1Failed{xerrors.Errorf("creating new remote commit1 request: %w", err)}) + } + req.Header.Set("Content-Type", "application/json") + req = req.WithContext(ctx.Context()) + resp, err := http.DefaultClient.Do(req) + if err != nil { + return ctx.Send(SectorRemoteCommit1Failed{xerrors.Errorf("requesting remote commit1: %w", err)}) + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return ctx.Send(SectorRemoteCommit1Failed{xerrors.Errorf("remote commit1 received non-200 http response %s", resp.Status)}) + } + + c2in, err = io.ReadAll(resp.Body) // todo some len constraint + if err != nil { + return ctx.Send(SectorRemoteCommit1Failed{xerrors.Errorf("reading commit1 response: %w", err)}) + } } proof, err := m.sealer.SealCommit2(sector.sealingCtx(ctx.Context()), m.minerSector(sector.SectorType, sector.SectorNumber), c2in) diff --git a/storage/pipeline/types.go b/storage/pipeline/types.go index 025b5a768..f59755dce 100644 --- a/storage/pipeline/types.go +++ b/storage/pipeline/types.go @@ -94,10 +94,11 @@ type SectorInfo struct { TerminatedAt abi.ChainEpoch // Remote import - RemoteDataUnsealed *storiface.SectorData - RemoteDataSealed *storiface.SectorData - RemoteDataCache *storiface.SectorData - RemoteDataFinalized bool + RemoteDataUnsealed *storiface.SectorData + RemoteDataSealed *storiface.SectorData + RemoteDataCache *storiface.SectorData + RemoteCommit1Endpoint string + RemoteDataFinalized bool // Debug LastErr string From b2dfaae68c7688dbbfd02142edd76ce18162688a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 9 Sep 2022 11:11:47 +0200 Subject: [PATCH 51/85] sector import: Test remote commit1 retry --- itests/kit/node_miner.go | 8 +- itests/sector_import_full_test.go | 221 ++++++++++++++++++ ...t_test.go => sector_import_simple_test.go} | 47 ++-- storage/pipeline/receive.go | 1 - storage/pipeline/states_failed.go | 4 +- 5 files changed, 260 insertions(+), 21 deletions(-) create mode 100644 itests/sector_import_full_test.go rename itests/{sector_import_test.go => sector_import_simple_test.go} (85%) diff --git a/itests/kit/node_miner.go b/itests/kit/node_miner.go index 8805ac36c..b08ce1847 100644 --- a/itests/kit/node_miner.go +++ b/itests/kit/node_miner.go @@ -97,6 +97,10 @@ func (tm *TestMiner) PledgeSectors(ctx context.Context, n, existing int, blockNo } func (tm *TestMiner) WaitSectorsProving(ctx context.Context, toCheck map[abi.SectorNumber]struct{}) { + tm.WaitSectorsProvingAllowFails(ctx, toCheck, map[api.SectorState]struct{}{}) +} + +func (tm *TestMiner) WaitSectorsProvingAllowFails(ctx context.Context, toCheck map[abi.SectorNumber]struct{}, okFails map[api.SectorState]struct{}) { for len(toCheck) > 0 { tm.FlushSealingBatches(ctx) @@ -109,7 +113,9 @@ func (tm *TestMiner) WaitSectorsProving(ctx context.Context, toCheck map[abi.Sec delete(toCheck, n) } if strings.Contains(string(st.State), "Fail") { - tm.t.Fatal("sector in a failed state", st.State) + if _, ok := okFails[st.State]; !ok { + tm.t.Fatal("sector in a failed state", st.State) + } } } diff --git a/itests/sector_import_full_test.go b/itests/sector_import_full_test.go new file mode 100644 index 000000000..3be72e8eb --- /dev/null +++ b/itests/sector_import_full_test.go @@ -0,0 +1,221 @@ +package itests + +import ( + "bytes" + "context" + "fmt" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + "time" + + "github.com/gorilla/mux" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/itests/kit" + sealing "github.com/filecoin-project/lotus/storage/pipeline" + "github.com/filecoin-project/lotus/storage/sealer/ffiwrapper" + "github.com/filecoin-project/lotus/storage/sealer/ffiwrapper/basicfs" + "github.com/filecoin-project/lotus/storage/sealer/storiface" +) + +func TestSectorImport(t *testing.T) { + + type testCase struct { + c1handler func(s *ffiwrapper.Sealer) func(w http.ResponseWriter, r *http.Request) + } + + makeTest := func(mut func(*testCase)) *testCase { + tc := &testCase{ + c1handler: remoteCommit1, + } + mut(tc) + return tc + } + + runTest := func(tc *testCase) func(t *testing.T) { + return func(t *testing.T) { + kit.QuietMiningLogs() + + var blockTime = 50 * time.Millisecond + + //////// + // Start a miner node + + client, miner, ens := kit.EnsembleMinimal(t, kit.ThroughRPC()) + ens.InterconnectAll().BeginMining(blockTime) + + ctx := context.Background() + + //////// + // Reserve some sector numbers on the miner node; We'll use one of those when creating the sector "remotely" + snums, err := miner.SectorNumReserveCount(ctx, "test-reservation-0001", 16) + require.NoError(t, err) + + sectorDir := t.TempDir() + + maddr, err := miner.ActorAddress(ctx) + require.NoError(t, err) + + mid, err := address.IDFromAddress(maddr) + require.NoError(t, err) + + spt, err := currentSealProof(ctx, client, maddr) + require.NoError(t, err) + + ssize, err := spt.SectorSize() + require.NoError(t, err) + + pieceSize := abi.PaddedPieceSize(ssize) + + //////// + // Create/Seal a sector up to pc2 outside of the pipeline + + // get one sector number from the reservation done on the miner above + sn, err := snums.First() + require.NoError(t, err) + + // create all the sector identifiers + snum := abi.SectorNumber(sn) + sid := abi.SectorID{Miner: abi.ActorID(mid), Number: snum} + sref := storiface.SectorRef{ID: sid, ProofType: spt} + + // create a low-level sealer instance + sealer, err := ffiwrapper.New(&basicfs.Provider{ + Root: sectorDir, + }) + require.NoError(t, err) + + // CRETE THE UNSEALED FILE + + // create a reader for all-zero (CC) data + dataReader := bytes.NewReader(bytes.Repeat([]byte{0}, int(pieceSize.Unpadded()))) + + // create the unsealed CC sector file + pieceInfo, err := sealer.AddPiece(ctx, sref, nil, pieceSize.Unpadded(), dataReader) + require.NoError(t, err) + + // GENERATE THE TICKET + + // get most recent valid ticket epoch + ts, err := client.ChainHead(ctx) + require.NoError(t, err) + ticketEpoch := ts.Height() - policy.SealRandomnessLookback + + // ticket entropy is cbor-seriasized miner addr + buf := new(bytes.Buffer) + require.NoError(t, maddr.MarshalCBOR(buf)) + + // generate ticket randomness + rand, err := client.StateGetRandomnessFromTickets(ctx, crypto.DomainSeparationTag_SealRandomness, ticketEpoch, buf.Bytes(), ts.Key()) + require.NoError(t, err) + + // EXECUTE PRECOMMIT 1 / 2 + + // run PC1 + pc1out, err := sealer.SealPreCommit1(ctx, sref, abi.SealRandomness(rand), []abi.PieceInfo{pieceInfo}) + require.NoError(t, err) + + // run pc2 + scids, err := sealer.SealPreCommit2(ctx, sref, pc1out) + require.NoError(t, err) + + // make finalized cache, put it in [sectorDir]/fin-cache while keeping the large cache for remote C1 + finDst := filepath.Join(sectorDir, "fin-cache", fmt.Sprintf("s-t01000-%d", snum)) + require.NoError(t, os.MkdirAll(finDst, 0777)) + require.NoError(t, sealer.FinalizeSectorInto(ctx, sref, finDst)) + + //////// + // start http server serving sector data + + m := mux.NewRouter() + m.HandleFunc("/sectors/{type}/{id}", remoteGetSector(sectorDir)).Methods("GET") + m.HandleFunc("/sectors/{id}/commit1", tc.c1handler(sealer)).Methods("POST") + srv := httptest.NewServer(m) + + unsealedURL := fmt.Sprintf("%s/sectors/unsealed/s-t0%d-%d", srv.URL, mid, snum) + sealedURL := fmt.Sprintf("%s/sectors/sealed/s-t0%d-%d", srv.URL, mid, snum) + cacheURL := fmt.Sprintf("%s/sectors/cache/s-t0%d-%d", srv.URL, mid, snum) + remoteC1URL := fmt.Sprintf("%s/sectors/s-t0%d-%d/commit1", srv.URL, mid, snum) + + //////// + // import the sector and continue sealing + + err = miner.SectorReceive(ctx, api.RemoteSectorMeta{ + State: "PreCommitting", + Sector: sid, + Type: spt, + + Pieces: []api.SectorPiece{ + { + Piece: pieceInfo, + DealInfo: nil, + }, + }, + + TicketValue: abi.SealRandomness(rand), + TicketEpoch: ticketEpoch, + + PreCommit1Out: pc1out, + + CommD: &scids.Unsealed, + CommR: &scids.Sealed, + + DataUnsealed: &storiface.SectorData{ + Local: false, + URL: unsealedURL, + }, + DataSealed: &storiface.SectorData{ + Local: false, + URL: sealedURL, + }, + DataCache: &storiface.SectorData{ + Local: false, + URL: cacheURL, + }, + + RemoteCommit1Endpoint: remoteC1URL, + }) + require.NoError(t, err) + + // check that we see the imported sector + ng, err := miner.SectorsListNonGenesis(ctx) + require.NoError(t, err) + require.Len(t, ng, 1) + require.Equal(t, snum, ng[0]) + + miner.WaitSectorsProvingAllowFails(ctx, map[abi.SectorNumber]struct{}{snum: {}}, map[api.SectorState]struct{}{api.SectorState(sealing.RemoteCommit1Failed): {}}) + } + } + + // fail first remote c1, verifies that c1 retry works + t.Run("c1-retry", runTest(makeTest(func(testCase *testCase) { + prt := sealing.MinRetryTime + sealing.MinRetryTime = time.Second + t.Cleanup(func() { + sealing.MinRetryTime = prt + }) + + testCase.c1handler = func(s *ffiwrapper.Sealer) func(w http.ResponseWriter, r *http.Request) { + var failedOnce bool + + return func(w http.ResponseWriter, r *http.Request) { + if !failedOnce { + failedOnce = true + w.WriteHeader(http.StatusBadGateway) + return + } + + remoteCommit1(s)(w, r) + } + } + }))) +} diff --git a/itests/sector_import_test.go b/itests/sector_import_simple_test.go similarity index 85% rename from itests/sector_import_test.go rename to itests/sector_import_simple_test.go index 7ff4fa48f..8b1b72fd7 100644 --- a/itests/sector_import_test.go +++ b/itests/sector_import_simple_test.go @@ -36,12 +36,16 @@ func TestSectorImportAfterPC2(t *testing.T) { var blockTime = 50 * time.Millisecond + //////// + // Start a miner node + client, miner, ens := kit.EnsembleMinimal(t, kit.ThroughRPC()) ens.InterconnectAll().BeginMining(blockTime) ctx := context.Background() - // get some sector numbers + //////// + // Reserve some sector numbers on the miner node; We'll use one of those when creating the sector "remotely" snums, err := miner.SectorNumReserveCount(ctx, "test-reservation-0001", 16) require.NoError(t, err) @@ -62,32 +66,34 @@ func TestSectorImportAfterPC2(t *testing.T) { pieceSize := abi.PaddedPieceSize(ssize) //////// - // seal a sector up to pc2 outside of the pipeline + // Create/Seal a sector up to pc2 outside of the pipeline + // get one sector number from the reservation done on the miner above sn, err := snums.First() require.NoError(t, err) + + // create all the sector identifiers snum := abi.SectorNumber(sn) - sid := abi.SectorID{ - Miner: abi.ActorID(mid), - Number: snum, - } - - sref := storiface.SectorRef{ - ID: sid, - ProofType: spt, - } + sid := abi.SectorID{Miner: abi.ActorID(mid), Number: snum} + sref := storiface.SectorRef{ID: sid, ProofType: spt} + // create a low-level sealer instance sealer, err := ffiwrapper.New(&basicfs.Provider{ Root: sectorDir, }) require.NoError(t, err) + // CRETE THE UNSEALED FILE + + // create a reader for all-zero (CC) data dataReader := bytes.NewReader(bytes.Repeat([]byte{0}, int(pieceSize.Unpadded()))) - // create the unsealed sector file + // create the unsealed CC sector file pieceInfo, err := sealer.AddPiece(ctx, sref, nil, pieceSize.Unpadded(), dataReader) require.NoError(t, err) + // GENERATE THE TICKET + // get most recent valid ticket epoch ts, err := client.ChainHead(ctx) require.NoError(t, err) @@ -101,6 +107,8 @@ func TestSectorImportAfterPC2(t *testing.T) { rand, err := client.StateGetRandomnessFromTickets(ctx, crypto.DomainSeparationTag_SealRandomness, ticketEpoch, buf.Bytes(), ts.Key()) require.NoError(t, err) + // EXECUTE PRECOMMIT 1 / 2 + // run PC1 pc1out, err := sealer.SealPreCommit1(ctx, sref, abi.SealRandomness(rand), []abi.PieceInfo{pieceInfo}) require.NoError(t, err) @@ -109,7 +117,7 @@ func TestSectorImportAfterPC2(t *testing.T) { scids, err := sealer.SealPreCommit2(ctx, sref, pc1out) require.NoError(t, err) - // make finalized cache, put it in [sectorDir]/fin-cache + // make finalized cache, put it in [sectorDir]/fin-cache while keeping the large cache for remote C1 finDst := filepath.Join(sectorDir, "fin-cache", fmt.Sprintf("s-t01000-%d", snum)) require.NoError(t, os.MkdirAll(finDst, 0777)) require.NoError(t, sealer.FinalizeSectorInto(ctx, sref, finDst)) @@ -122,6 +130,11 @@ func TestSectorImportAfterPC2(t *testing.T) { m.HandleFunc("/sectors/{id}/commit1", remoteCommit1(sealer)).Methods("POST") srv := httptest.NewServer(m) + unsealedURL := fmt.Sprintf("%s/sectors/unsealed/s-t0%d-%d", srv.URL, mid, snum) + sealedURL := fmt.Sprintf("%s/sectors/sealed/s-t0%d-%d", srv.URL, mid, snum) + cacheURL := fmt.Sprintf("%s/sectors/cache/s-t0%d-%d", srv.URL, mid, snum) + remoteC1URL := fmt.Sprintf("%s/sectors/s-t0%d-%d/commit1", srv.URL, mid, snum) + //////// // import the sector and continue sealing @@ -147,18 +160,18 @@ func TestSectorImportAfterPC2(t *testing.T) { DataUnsealed: &storiface.SectorData{ Local: false, - URL: fmt.Sprintf("%s/sectors/unsealed/s-t0%d-%d", srv.URL, mid, snum), + URL: unsealedURL, }, DataSealed: &storiface.SectorData{ Local: false, - URL: fmt.Sprintf("%s/sectors/sealed/s-t0%d-%d", srv.URL, mid, snum), + URL: sealedURL, }, DataCache: &storiface.SectorData{ Local: false, - URL: fmt.Sprintf("%s/sectors/cache/s-t0%d-%d", srv.URL, mid, snum), + URL: cacheURL, }, - RemoteCommit1Endpoint: fmt.Sprintf("%s/sectors/s-t0%d-%d/commit1", srv.URL, mid, snum), + RemoteCommit1Endpoint: remoteC1URL, }) require.NoError(t, err) diff --git a/storage/pipeline/receive.go b/storage/pipeline/receive.go index fd4f64a66..13ee0ff02 100644 --- a/storage/pipeline/receive.go +++ b/storage/pipeline/receive.go @@ -126,7 +126,6 @@ func (m *Sealing) checkSectorMeta(ctx context.Context, meta api.RemoteSectorMeta case GetTicket: fallthrough case Packing: - // todo check num free info.Return = ReturnState(meta.State) // todo dedupe states info.State = ReceiveSector diff --git a/storage/pipeline/states_failed.go b/storage/pipeline/states_failed.go index fb7145f50..7292961d2 100644 --- a/storage/pipeline/states_failed.go +++ b/storage/pipeline/states_failed.go @@ -19,12 +19,12 @@ import ( "github.com/filecoin-project/lotus/chain/types" ) -const minRetryTime = 1 * time.Minute +var MinRetryTime = 1 * time.Minute func failedCooldown(ctx statemachine.Context, sector SectorInfo) error { // TODO: Exponential backoff when we see consecutive failures - retryStart := time.Unix(int64(sector.Log[len(sector.Log)-1].Timestamp), 0).Add(minRetryTime) + retryStart := time.Unix(int64(sector.Log[len(sector.Log)-1].Timestamp), 0).Add(MinRetryTime) if len(sector.Log) > 0 && !time.Now().After(retryStart) { log.Infof("%s(%d), waiting %s before retrying", sector.State, sector.SectorNumber, time.Until(retryStart)) select { From 142894895d8b235d7cdd3e0b1e06698122c5ffe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 9 Sep 2022 12:22:21 +0200 Subject: [PATCH 52/85] sector import: Implement seed/ticket/commr/commd checks --- .circleci/config.yml | 11 +++- itests/sector_import_full_test.go | 73 ++++++++++++++++++++- storage/pipeline/receive.go | 102 ++++++++++++++++++++++++++---- 3 files changed, 170 insertions(+), 16 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 58c89202b..7fcd20677 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -975,9 +975,14 @@ workflows: target: "./itests/sector_finalize_early_test.go" - test: - name: test-itest-sector_import - suite: itest-sector_import - target: "./itests/sector_import_test.go" + name: test-itest-sector_import_full + suite: itest-sector_import_full + target: "./itests/sector_import_full_test.go" + + - test: + name: test-itest-sector_import_simple + suite: itest-sector_import_simple + target: "./itests/sector_import_simple_test.go" - test: name: test-itest-sector_make_cc_avail diff --git a/itests/sector_import_full_test.go b/itests/sector_import_full_test.go index 3be72e8eb..7be290793 100644 --- a/itests/sector_import_full_test.go +++ b/itests/sector_import_full_test.go @@ -31,6 +31,10 @@ func TestSectorImport(t *testing.T) { type testCase struct { c1handler func(s *ffiwrapper.Sealer) func(w http.ResponseWriter, r *http.Request) + + mutateRemoteMeta func(*api.RemoteSectorMeta) + + expectImportErrContains string } makeTest := func(mut func(*testCase)) *testCase { @@ -149,7 +153,7 @@ func TestSectorImport(t *testing.T) { //////// // import the sector and continue sealing - err = miner.SectorReceive(ctx, api.RemoteSectorMeta{ + rmeta := api.RemoteSectorMeta{ State: "PreCommitting", Sector: sid, Type: spt, @@ -183,7 +187,18 @@ func TestSectorImport(t *testing.T) { }, RemoteCommit1Endpoint: remoteC1URL, - }) + } + + if tc.mutateRemoteMeta != nil { + tc.mutateRemoteMeta(&rmeta) + } + + err = miner.SectorReceive(ctx, rmeta) + if tc.expectImportErrContains != "" { + require.ErrorContains(t, err, tc.expectImportErrContains) + return + } + require.NoError(t, err) // check that we see the imported sector @@ -218,4 +233,58 @@ func TestSectorImport(t *testing.T) { } } }))) + + t.Run("nil-commd", runTest(makeTest(func(testCase *testCase) { + testCase.mutateRemoteMeta = func(meta *api.RemoteSectorMeta) { + meta.CommD = nil + } + testCase.expectImportErrContains = "both CommR/CommD cids need to be set for sectors in PreCommitting and later states" + }))) + t.Run("nil-commr", runTest(makeTest(func(testCase *testCase) { + testCase.mutateRemoteMeta = func(meta *api.RemoteSectorMeta) { + meta.CommR = nil + } + testCase.expectImportErrContains = "both CommR/CommD cids need to be set for sectors in PreCommitting and later states" + }))) + + t.Run("nil-uns", runTest(makeTest(func(testCase *testCase) { + testCase.mutateRemoteMeta = func(meta *api.RemoteSectorMeta) { + meta.DataUnsealed = nil + } + testCase.expectImportErrContains = "expected DataUnsealed to be set" + }))) + t.Run("nil-sealed", runTest(makeTest(func(testCase *testCase) { + testCase.mutateRemoteMeta = func(meta *api.RemoteSectorMeta) { + meta.DataSealed = nil + } + testCase.expectImportErrContains = "expected DataSealed to be set" + }))) + t.Run("nil-cache", runTest(makeTest(func(testCase *testCase) { + testCase.mutateRemoteMeta = func(meta *api.RemoteSectorMeta) { + meta.DataCache = nil + } + testCase.expectImportErrContains = "expected DataCache to be set" + }))) + + t.Run("bad-commd", runTest(makeTest(func(testCase *testCase) { + testCase.mutateRemoteMeta = func(meta *api.RemoteSectorMeta) { + meta.CommD = meta.CommR + } + testCase.expectImportErrContains = "CommD cid has wrong prefix" + }))) + t.Run("bad-commr", runTest(makeTest(func(testCase *testCase) { + testCase.mutateRemoteMeta = func(meta *api.RemoteSectorMeta) { + meta.CommR = meta.CommD + } + testCase.expectImportErrContains = "CommR cid has wrong prefix" + }))) + + t.Run("bad-ticket", runTest(makeTest(func(testCase *testCase) { + testCase.mutateRemoteMeta = func(meta *api.RemoteSectorMeta) { + // flip one bit + meta.TicketValue[23] ^= 4 + } + testCase.expectImportErrContains = "tickets differ" + }))) + } diff --git a/storage/pipeline/receive.go b/storage/pipeline/receive.go index 13ee0ff02..d7eb5de7e 100644 --- a/storage/pipeline/receive.go +++ b/storage/pipeline/receive.go @@ -1,21 +1,28 @@ package sealing import ( + "bytes" "context" + "github.com/ipfs/go-cid" "github.com/ipfs/go-datastore" + "github.com/multiformats/go-multihash" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/proof" "github.com/filecoin-project/go-statemachine" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/storage/sealer/storiface" ) -// todo m.inputLk? func (m *Sealing) Receive(ctx context.Context, meta api.RemoteSectorMeta) error { + m.inputLk.Lock() + defer m.inputLk.Unlock() + si, err := m.checkSectorMeta(ctx, meta) if err != nil { return err @@ -73,7 +80,13 @@ func (m *Sealing) checkSectorMeta(ctx context.Context, meta api.RemoteSectorMeta } } + ts, err := m.Api.ChainHead(ctx) + if err != nil { + return SectorInfo{}, xerrors.Errorf("getting chain head: %w", err) + } + var info SectorInfo + var validatePoRep bool switch SectorState(meta.State) { case Proving, Available: @@ -91,21 +104,69 @@ func (m *Sealing) checkSectorMeta(ctx context.Context, meta api.RemoteSectorMeta info.PreCommitMessage = meta.PreCommitMessage info.PreCommitTipSet = meta.PreCommitTipSet - // todo check + // check provided seed + if len(meta.SeedValue) != abi.RandomnessLength { + return SectorInfo{}, xerrors.Errorf("seed randomness had wrong length %d", len(meta.SeedValue)) + } + + maddrBuf := new(bytes.Buffer) + if err := m.maddr.MarshalCBOR(maddrBuf); err != nil { + return SectorInfo{}, xerrors.Errorf("marshal miner address for seed check: %w", err) + } + rand, err := m.Api.StateGetRandomnessFromTickets(ctx, crypto.DomainSeparationTag_InteractiveSealChallengeSeed, meta.SeedEpoch, maddrBuf.Bytes(), ts.Key()) + if err != nil { + return SectorInfo{}, xerrors.Errorf("generating check seed: %w", err) + } + if !bytes.Equal(rand, meta.SeedValue) { + return SectorInfo{}, xerrors.Errorf("provided(%x) and generated(%x) seeds differ", meta.SeedValue, rand) + } + info.SeedValue = meta.SeedValue info.SeedEpoch = meta.SeedEpoch - // todo validate info.Proof = meta.CommitProof + validatePoRep = true fallthrough case PreCommitting: + // check provided ticket + if len(meta.TicketValue) != abi.RandomnessLength { + return SectorInfo{}, xerrors.Errorf("ticket randomness had wrong length %d", len(meta.TicketValue)) + } + + maddrBuf := new(bytes.Buffer) + if err := m.maddr.MarshalCBOR(maddrBuf); err != nil { + return SectorInfo{}, xerrors.Errorf("marshal miner address for ticket check: %w", err) + } + rand, err := m.Api.StateGetRandomnessFromTickets(ctx, crypto.DomainSeparationTag_SealRandomness, meta.TicketEpoch, maddrBuf.Bytes(), ts.Key()) + if err != nil { + return SectorInfo{}, xerrors.Errorf("generating check ticket: %w", err) + } + if !bytes.Equal(rand, meta.TicketValue) { + return SectorInfo{}, xerrors.Errorf("provided(%x) and generated(%x) tickets differ", meta.TicketValue, rand) + } + info.TicketValue = meta.TicketValue info.TicketEpoch = meta.TicketEpoch info.PreCommit1Out = meta.PreCommit1Out - info.CommD = meta.CommD // todo check cid prefixes + // check CommD/R + if meta.CommD == nil || meta.CommR == nil { + return SectorInfo{}, xerrors.Errorf("both CommR/CommD cids need to be set for sectors in PreCommitting and later states") + } + + dp := meta.CommD.Prefix() + if dp.Version != 1 || dp.Codec != cid.FilCommitmentUnsealed || dp.MhType != multihash.SHA2_256_TRUNC254_PADDED || dp.MhLength != 32 { + return SectorInfo{}, xerrors.Errorf("CommD cid has wrong prefix") + } + + rp := meta.CommR.Prefix() + if rp.Version != 1 || rp.Codec != cid.FilCommitmentSealed || rp.MhType != multihash.POSEIDON_BLS12_381_A1_FC1 || rp.MhLength != 32 { + return SectorInfo{}, xerrors.Errorf("CommR cid has wrong prefix") + } + + info.CommD = meta.CommD info.CommR = meta.CommR if meta.DataSealed == nil { @@ -118,15 +179,14 @@ func (m *Sealing) checkSectorMeta(ctx context.Context, meta api.RemoteSectorMeta info.RemoteDataCache = meta.DataCache info.RemoteCommit1Endpoint = meta.RemoteCommit1Endpoint - // If we get a sector after PC2, assume that we're getting finalized sector data - // todo: maybe only set if C1 provider is set? - info.RemoteDataFinalized = true + // If we get a sector after PC2, and remote C1 endpoint is set, assume that we're getting finalized sector data + if info.RemoteCommit1Endpoint != "" { + info.RemoteDataFinalized = true + } fallthrough - case GetTicket: - fallthrough - case Packing: - info.Return = ReturnState(meta.State) // todo dedupe states + case GetTicket, Packing: + info.Return = ReturnState(meta.State) info.State = ReceiveSector info.SectorNumber = meta.Sector.Number @@ -142,6 +202,26 @@ func (m *Sealing) checkSectorMeta(ctx context.Context, meta api.RemoteSectorMeta } info.RemoteDataUnsealed = meta.DataUnsealed + // some late checks which require previous checks + if validatePoRep { + ok, err := m.verif.VerifySeal(proof.SealVerifyInfo{ + SealProof: meta.Type, + SectorID: meta.Sector, + DealIDs: nil, + Randomness: meta.TicketValue, + InteractiveRandomness: meta.SeedValue, + Proof: meta.CommitProof, + SealedCID: *meta.CommR, + UnsealedCID: *meta.CommD, + }) + if err != nil { + return SectorInfo{}, xerrors.Errorf("validating seal proof: %w", err) + } + if !ok { + return SectorInfo{}, xerrors.Errorf("seal proof invalid") + } + } + return info, nil default: return SectorInfo{}, xerrors.Errorf("imported sector State in not supported") From c17ab761c5fc2b22ca2b1e741f6aff40526cac0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 9 Sep 2022 12:54:48 +0200 Subject: [PATCH 53/85] sector import: Remote Commit2 --- api/api_storage.go | 10 ++++ build/openrpc/miner.json.gz | Bin 15801 -> 15813 bytes cmd/lotus-miner/info.go | 2 +- documentation/en/api-v0-methods-miner.md | 3 +- itests/sector_import_full_test.go | 2 +- itests/sector_import_simple_test.go | 29 ++++++++++++ storage/pipeline/cbor_gen.go | 36 ++++++++++++++- storage/pipeline/fsm.go | 10 ++-- storage/pipeline/fsm_events.go | 5 ++ storage/pipeline/receive.go | 20 +++++++- storage/pipeline/sector_state.go | 4 +- storage/pipeline/states_failed.go | 4 +- storage/pipeline/states_sealing.go | 56 ++++++++++++++++++++--- storage/pipeline/types.go | 1 + 14 files changed, 162 insertions(+), 20 deletions(-) diff --git a/api/api_storage.go b/api/api_storage.go index cf44614e1..6273c4881 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -576,6 +576,8 @@ type RemoteSectorMeta struct { // todo better doc RemoteCommit1Endpoint string + RemoteCommit2Endpoint string + // todo OnDone / OnStateChange } @@ -587,3 +589,11 @@ type RemoteCommit1Params struct { ProofType abi.RegisteredSealProof } + +type RemoteCommit2Params struct { + Sector abi.SectorID + ProofType abi.RegisteredSealProof + + // todo spec better + Commit1Out storiface.Commit1Out +} diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 61fcef75cdf265fe7db147deffcb16be1289e05c..3d42364ddb4334b44a3ef885e0d3e628097916d1 100644 GIT binary patch delta 5199 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 J10Kf<0|3Mv2m1g3 delta 5187 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 xs cbg.MaxLength { + return xerrors.Errorf("Value in field \"RemoteCommit2Endpoint\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("RemoteCommit2Endpoint"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("RemoteCommit2Endpoint")); err != nil { + return err + } + + if len(t.RemoteCommit2Endpoint) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.RemoteCommit2Endpoint was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.RemoteCommit2Endpoint))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.RemoteCommit2Endpoint)); err != nil { + return err + } + // t.RemoteDataFinalized (bool) (bool) if len("RemoteDataFinalized") > cbg.MaxLength { return xerrors.Errorf("Value in field \"RemoteDataFinalized\" was too long") @@ -1513,6 +1536,17 @@ func (t *SectorInfo) UnmarshalCBOR(r io.Reader) (err error) { t.RemoteCommit1Endpoint = string(sval) } + // t.RemoteCommit2Endpoint (string) (string) + case "RemoteCommit2Endpoint": + + { + sval, err := cbg.ReadString(cr) + if err != nil { + return err + } + + t.RemoteCommit2Endpoint = string(sval) + } // t.RemoteDataFinalized (bool) (bool) case "RemoteDataFinalized": diff --git a/storage/pipeline/fsm.go b/storage/pipeline/fsm.go index fb0d16b38..fc0a93a85 100644 --- a/storage/pipeline/fsm.go +++ b/storage/pipeline/fsm.go @@ -225,7 +225,7 @@ var fsmPlanners = map[SectorState]func(events []statemachine.Event, state *Secto on(SectorRetryComputeProof{}, Committing), on(SectorSealPreCommit1Failed{}, SealPreCommit1Failed), ), - RemoteCommit1Failed: planOne( + RemoteCommitFailed: planOne( on(SectorRetryComputeProof{}, Committing), ), CommitFinalizeFailed: planOne( @@ -542,8 +542,8 @@ func (m *Sealing) plan(events []statemachine.Event, state *SectorInfo) (func(sta return m.handlePreCommitFailed, processed, nil case ComputeProofFailed: return m.handleComputeProofFailed, processed, nil - case RemoteCommit1Failed: - return m.handleRemoteCommit1Failed, processed, nil + case RemoteCommitFailed: + return m.handleRemoteCommitFailed, processed, nil case CommitFailed: return m.handleCommitFailed, processed, nil case CommitFinalizeFailed: @@ -671,8 +671,8 @@ func planCommitting(events []statemachine.Event, state *SectorInfo) (uint64, err return uint64(i + 1), nil case SectorComputeProofFailed: state.State = ComputeProofFailed - case SectorRemoteCommit1Failed: - state.State = RemoteCommit1Failed + case SectorRemoteCommit1Failed, SectorRemoteCommit2Failed: + state.State = RemoteCommitFailed case SectorSealPreCommit1Failed: state.State = SealPreCommit1Failed case SectorCommitFailed: diff --git a/storage/pipeline/fsm_events.go b/storage/pipeline/fsm_events.go index 600770c01..1fbd94fd0 100644 --- a/storage/pipeline/fsm_events.go +++ b/storage/pipeline/fsm_events.go @@ -223,6 +223,11 @@ type SectorRemoteCommit1Failed struct{ error } func (evt SectorRemoteCommit1Failed) FormatError(xerrors.Printer) (next error) { return evt.error } func (evt SectorRemoteCommit1Failed) apply(*SectorInfo) {} +type SectorRemoteCommit2Failed struct{ error } + +func (evt SectorRemoteCommit2Failed) FormatError(xerrors.Printer) (next error) { return evt.error } +func (evt SectorRemoteCommit2Failed) apply(*SectorInfo) {} + type SectorComputeProofFailed struct{ error } func (evt SectorComputeProofFailed) FormatError(xerrors.Printer) (next error) { return evt.error } diff --git a/storage/pipeline/receive.go b/storage/pipeline/receive.go index d7eb5de7e..f06848bcb 100644 --- a/storage/pipeline/receive.go +++ b/storage/pipeline/receive.go @@ -3,6 +3,7 @@ package sealing import ( "bytes" "context" + "net/url" "github.com/ipfs/go-cid" "github.com/ipfs/go-datastore" @@ -177,7 +178,24 @@ func (m *Sealing) checkSectorMeta(ctx context.Context, meta api.RemoteSectorMeta } info.RemoteDataSealed = meta.DataSealed // todo make head requests to check? info.RemoteDataCache = meta.DataCache - info.RemoteCommit1Endpoint = meta.RemoteCommit1Endpoint + + if meta.RemoteCommit1Endpoint != "" { + // validate the url + if _, err := url.Parse(meta.RemoteCommit1Endpoint); err != nil { + return SectorInfo{}, xerrors.Errorf("parsing remote c1 endpoint url: %w", err) + } + + info.RemoteCommit1Endpoint = meta.RemoteCommit1Endpoint + } + + if meta.RemoteCommit2Endpoint != "" { + // validate the url + if _, err := url.Parse(meta.RemoteCommit2Endpoint); err != nil { + return SectorInfo{}, xerrors.Errorf("parsing remote c2 endpoint url: %w", err) + } + + info.RemoteCommit2Endpoint = meta.RemoteCommit2Endpoint + } // If we get a sector after PC2, and remote C1 endpoint is set, assume that we're getting finalized sector data if info.RemoteCommit1Endpoint != "" { diff --git a/storage/pipeline/sector_state.go b/storage/pipeline/sector_state.go index 136e72a16..7a56c136b 100644 --- a/storage/pipeline/sector_state.go +++ b/storage/pipeline/sector_state.go @@ -31,7 +31,7 @@ var ExistSectorStateList = map[SectorState]struct{}{ SealPreCommit2Failed: {}, PreCommitFailed: {}, ComputeProofFailed: {}, - RemoteCommit1Failed: {}, + RemoteCommitFailed: {}, CommitFailed: {}, PackingFailed: {}, FinalizeFailed: {}, @@ -125,7 +125,7 @@ const ( SealPreCommit2Failed SectorState = "SealPreCommit2Failed" PreCommitFailed SectorState = "PreCommitFailed" ComputeProofFailed SectorState = "ComputeProofFailed" - RemoteCommit1Failed SectorState = "RemoteCommit1Failed" + RemoteCommitFailed SectorState = "RemoteCommitFailed" CommitFailed SectorState = "CommitFailed" PackingFailed SectorState = "PackingFailed" // TODO: deprecated, remove FinalizeFailed SectorState = "FinalizeFailed" diff --git a/storage/pipeline/states_failed.go b/storage/pipeline/states_failed.go index 7292961d2..b90833b2d 100644 --- a/storage/pipeline/states_failed.go +++ b/storage/pipeline/states_failed.go @@ -184,13 +184,13 @@ func (m *Sealing) handleComputeProofFailed(ctx statemachine.Context, sector Sect return ctx.Send(SectorRetryComputeProof{}) } -func (m *Sealing) handleRemoteCommit1Failed(ctx statemachine.Context, sector SectorInfo) error { +func (m *Sealing) handleRemoteCommitFailed(ctx statemachine.Context, sector SectorInfo) error { if err := failedCooldown(ctx, sector); err != nil { return err } if sector.InvalidProofs > 1 { - log.Errorw("consecutive remote commit1 fails", "sector", sector.SectorNumber, "c1url", sector.RemoteCommit1Endpoint) + log.Errorw("consecutive remote commit fails", "sector", sector.SectorNumber, "c1url", sector.RemoteCommit1Endpoint, "c2url", sector.RemoteCommit2Endpoint) } return ctx.Send(SectorRetryComputeProof{}) diff --git a/storage/pipeline/states_sealing.go b/storage/pipeline/states_sealing.go index 56b7eb37a..9b815053a 100644 --- a/storage/pipeline/states_sealing.go +++ b/storage/pipeline/states_sealing.go @@ -574,6 +574,7 @@ func (m *Sealing) handleCommitting(ctx statemachine.Context, sector SectorInfo) var c2in storiface.Commit1Out if sector.RemoteCommit1Endpoint == "" { + // Local Commit1 cids := storiface.SectorCids{ Unsealed: *sector.CommD, Sealed: *sector.CommR, @@ -583,6 +584,8 @@ func (m *Sealing) handleCommitting(ctx statemachine.Context, sector SectorInfo) return ctx.Send(SectorComputeProofFailed{xerrors.Errorf("computing seal proof failed(1): %w", err)}) } } else { + // Remote Commit1 + reqData := api.RemoteCommit1Params{ Ticket: sector.TicketValue, Seed: sector.SeedValue, @@ -618,9 +621,50 @@ func (m *Sealing) handleCommitting(ctx statemachine.Context, sector SectorInfo) } } - proof, err := m.sealer.SealCommit2(sector.sealingCtx(ctx.Context()), m.minerSector(sector.SectorType, sector.SectorNumber), c2in) - if err != nil { - return ctx.Send(SectorComputeProofFailed{xerrors.Errorf("computing seal proof failed(2): %w", err)}) + var porepProof storiface.Proof + + if sector.RemoteCommit2Endpoint == "" { + // Local Commit2 + + porepProof, err = m.sealer.SealCommit2(sector.sealingCtx(ctx.Context()), m.minerSector(sector.SectorType, sector.SectorNumber), c2in) + if err != nil { + return ctx.Send(SectorComputeProofFailed{xerrors.Errorf("computing seal proof failed(2): %w", err)}) + } + } else { + // Remote Commit2 + + reqData := api.RemoteCommit2Params{ + ProofType: sector.SectorType, + Sector: m.minerSectorID(sector.SectorNumber), + + Commit1Out: c2in, + } + reqBody, err := json.Marshal(&reqData) + if err != nil { + return xerrors.Errorf("marshaling remote commit2 request: %w", err) + } + + req, err := http.NewRequest("POST", sector.RemoteCommit2Endpoint, bytes.NewReader(reqBody)) + if err != nil { + return ctx.Send(SectorRemoteCommit2Failed{xerrors.Errorf("creating new remote commit2 request: %w", err)}) + } + req.Header.Set("Content-Type", "application/json") + req = req.WithContext(ctx.Context()) + resp, err := http.DefaultClient.Do(req) + if err != nil { + return ctx.Send(SectorRemoteCommit2Failed{xerrors.Errorf("requesting remote commit2: %w", err)}) + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return ctx.Send(SectorRemoteCommit2Failed{xerrors.Errorf("remote commit2 received non-200 http response %s", resp.Status)}) + } + + porepProof, err = io.ReadAll(resp.Body) // todo some len constraint + if err != nil { + return ctx.Send(SectorRemoteCommit2Failed{xerrors.Errorf("reading commit2 response: %w", err)}) + } } { @@ -630,19 +674,19 @@ func (m *Sealing) handleCommitting(ctx statemachine.Context, sector SectorInfo) return nil } - if err := m.checkCommit(ctx.Context(), sector, proof, ts.Key()); err != nil { + if err := m.checkCommit(ctx.Context(), sector, porepProof, ts.Key()); err != nil { return ctx.Send(SectorCommitFailed{xerrors.Errorf("commit check error: %w", err)}) } } if cfg.FinalizeEarly { return ctx.Send(SectorProofReady{ - Proof: proof, + Proof: porepProof, }) } return ctx.Send(SectorCommitted{ - Proof: proof, + Proof: porepProof, }) } diff --git a/storage/pipeline/types.go b/storage/pipeline/types.go index f59755dce..81a2a2fec 100644 --- a/storage/pipeline/types.go +++ b/storage/pipeline/types.go @@ -98,6 +98,7 @@ type SectorInfo struct { RemoteDataSealed *storiface.SectorData RemoteDataCache *storiface.SectorData RemoteCommit1Endpoint string + RemoteCommit2Endpoint string RemoteDataFinalized bool // Debug From 474c60203bb41f6e63e833193a58b28d7058b96f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 9 Sep 2022 13:31:03 +0200 Subject: [PATCH 54/85] tarutil: Strict header checks --- storage/sealer/storiface/storage.go | 2 +- storage/sealer/tarutil/systar.go | 68 +++++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/storage/sealer/storiface/storage.go b/storage/sealer/storiface/storage.go index 975011a0b..5246a1a1a 100644 --- a/storage/sealer/storiface/storage.go +++ b/storage/sealer/storiface/storage.go @@ -131,7 +131,7 @@ type SectorData struct { // URL to the sector data // For sealed/unsealed sector, lotus expects octet-stream - // For cache, lotus expects a tar archive with cache files (todo maybe use not-tar; specify what files with what paths must be present) + // For cache, lotus expects a tar archive with cache files // Valid schemas: // - http:// / https:// URL string diff --git a/storage/sealer/tarutil/systar.go b/storage/sealer/tarutil/systar.go index 96dcce875..f7437d1f2 100644 --- a/storage/sealer/tarutil/systar.go +++ b/storage/sealer/tarutil/systar.go @@ -12,6 +12,63 @@ import ( var log = logging.Logger("tarutil") // nolint +var CacheFileConstraints = map[string]int64{ + "p_aux": 64, + "t_aux": 10240, + + "sc-02-data-tree-r-last.dat": 10_000_000, // small sectors + + "sc-02-data-tree-r-last-0.dat": 10_000_000, + "sc-02-data-tree-r-last-1.dat": 10_000_000, + "sc-02-data-tree-r-last-2.dat": 10_000_000, + "sc-02-data-tree-r-last-3.dat": 10_000_000, + "sc-02-data-tree-r-last-4.dat": 10_000_000, + "sc-02-data-tree-r-last-5.dat": 10_000_000, + "sc-02-data-tree-r-last-6.dat": 10_000_000, + "sc-02-data-tree-r-last-7.dat": 10_000_000, + + "sc-02-data-tree-r-last-8.dat": 10_000_000, + "sc-02-data-tree-r-last-9.dat": 10_000_000, + "sc-02-data-tree-r-last-10.dat": 10_000_000, + "sc-02-data-tree-r-last-11.dat": 10_000_000, + "sc-02-data-tree-r-last-12.dat": 10_000_000, + "sc-02-data-tree-r-last-13.dat": 10_000_000, + "sc-02-data-tree-r-last-14.dat": 10_000_000, + "sc-02-data-tree-r-last-15.dat": 10_000_000, + + "sc-02-data-layer-1.dat": 65 << 30, // 1x sector size + small buffer + "sc-02-data-layer-2.dat": 65 << 30, // 1x sector size + small buffer + "sc-02-data-layer-3.dat": 65 << 30, // 1x sector size + small buffer + "sc-02-data-layer-4.dat": 65 << 30, // 1x sector size + small buffer + "sc-02-data-layer-5.dat": 65 << 30, // 1x sector size + small buffer + "sc-02-data-layer-6.dat": 65 << 30, // 1x sector size + small buffer + "sc-02-data-layer-7.dat": 65 << 30, // 1x sector size + small buffer + "sc-02-data-layer-8.dat": 65 << 30, // 1x sector size + small buffer + "sc-02-data-layer-9.dat": 65 << 30, // 1x sector size + small buffer + "sc-02-data-layer-10.dat": 65 << 30, // 1x sector size + small buffer + "sc-02-data-layer-11.dat": 65 << 30, // 1x sector size + small buffer + + "sc-02-data-tree-c-0.dat": 5 << 30, // ~4.6G + "sc-02-data-tree-c-1.dat": 5 << 30, + "sc-02-data-tree-c-2.dat": 5 << 30, + "sc-02-data-tree-c-3.dat": 5 << 30, + "sc-02-data-tree-c-4.dat": 5 << 30, + "sc-02-data-tree-c-5.dat": 5 << 30, + "sc-02-data-tree-c-6.dat": 5 << 30, + "sc-02-data-tree-c-7.dat": 5 << 30, + + "sc-02-data-tree-c-8.dat": 5 << 30, + "sc-02-data-tree-c-9.dat": 5 << 30, + "sc-02-data-tree-c-10.dat": 5 << 30, + "sc-02-data-tree-c-11.dat": 5 << 30, + "sc-02-data-tree-c-12.dat": 5 << 30, + "sc-02-data-tree-c-13.dat": 5 << 30, + "sc-02-data-tree-c-14.dat": 5 << 30, + "sc-02-data-tree-c-15.dat": 5 << 30, + + "sc-02-data-tree-d.dat": 130 << 30, // 2x sector size, ~130G accunting for small buffer on 64G sectors +} + func ExtractTar(body io.Reader, dir string, buf []byte) (int64, error) { if err := os.MkdirAll(dir, 0755); err != nil { // nolint return 0, xerrors.Errorf("mkdir: %w", err) @@ -37,9 +94,14 @@ func ExtractTar(body io.Reader, dir string, buf []byte) (int64, error) { return read, xerrors.Errorf("creating file %s: %w", filepath.Join(dir, header.Name), err) } - // This data is coming from a trusted source, no need to check the size. - // TODO: now it's actually not coming from a trusted source, check size / paths - //nolint:gosec + sz, found := CacheFileConstraints[header.Name] + if !found { + return read, xerrors.Errorf("tar file %#v isn't expected") + } + if header.Size > sz { + return read, xerrors.Errorf("tar file %#v is bigger than expected: %d > %d", header.Name, header.Size, sz) + } + r, err := io.CopyBuffer(f, tr, buf) read += r if err != nil { From ef834b988c5f1f2ab05c4f80e416f81a6f614a8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 9 Sep 2022 13:42:04 +0200 Subject: [PATCH 55/85] sector import: Fix test build --- itests/sector_import_full_test.go | 150 +++++++++++++++++++++++++++- itests/sector_import_simple_test.go | 20 +--- 2 files changed, 152 insertions(+), 18 deletions(-) diff --git a/itests/sector_import_full_test.go b/itests/sector_import_full_test.go index 3e67b94fc..b26a24145 100644 --- a/itests/sector_import_full_test.go +++ b/itests/sector_import_full_test.go @@ -3,7 +3,12 @@ package itests import ( "bytes" "context" + "encoding/json" "fmt" + lminer "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/types" + spaths "github.com/filecoin-project/lotus/storage/paths" + "github.com/filecoin-project/lotus/storage/sealer/tarutil" "net/http" "net/http/httptest" "os" @@ -39,7 +44,7 @@ func TestSectorImport(t *testing.T) { makeTest := func(mut func(*testCase)) *testCase { tc := &testCase{ - c1handler: remoteCommit1, + c1handler: testRemoteCommit1, } mut(tc) return tc @@ -72,7 +77,11 @@ func TestSectorImport(t *testing.T) { mid, err := address.IDFromAddress(maddr) require.NoError(t, err) - spt, err := currentSealProof(ctx, client, maddr) + mi, err := client.StateMinerInfo(ctx, maddr, types.EmptyTSK) + require.NoError(t, err) + ver, err := client.StateNetworkVersion(ctx, types.EmptyTSK) + require.NoError(t, err) + spt, err := lminer.PreferredSealProofTypeFromWindowPoStType(ver, mi.WindowPoStProofType) require.NoError(t, err) ssize, err := spt.SectorSize() @@ -141,8 +150,9 @@ func TestSectorImport(t *testing.T) { // start http server serving sector data m := mux.NewRouter() - m.HandleFunc("/sectors/{type}/{id}", remoteGetSector(sectorDir)).Methods("GET") + m.HandleFunc("/sectors/{type}/{id}", testRemoteGetSector(sectorDir)).Methods("GET") m.HandleFunc("/sectors/{id}/commit1", tc.c1handler(sealer)).Methods("POST") + m.HandleFunc("/commit2", testRemoteCommit2(sealer)).Methods("POST") srv := httptest.NewServer(m) unsealedURL := fmt.Sprintf("%s/sectors/unsealed/s-t0%d-%d", srv.URL, mid, snum) @@ -288,3 +298,137 @@ func TestSectorImport(t *testing.T) { }))) } + +// note: stuff below is almost the same as in _simple version of this file; We need +// to copy it because on Circle we can't call those functions between test files, +// and for the _simple test we want everything in one file to make it easy to follow + +func testRemoteCommit1(s *ffiwrapper.Sealer) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + // validate sector id + id, err := storiface.ParseSectorID(vars["id"]) + if err != nil { + w.WriteHeader(500) + return + } + + var params api.RemoteCommit1Params + if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { + w.WriteHeader(500) + return + } + + sref := storiface.SectorRef{ + ID: id, + ProofType: params.ProofType, + } + + ssize, err := params.ProofType.SectorSize() + if err != nil { + w.WriteHeader(500) + return + } + + p, err := s.SealCommit1(r.Context(), sref, params.Ticket, params.Seed, []abi.PieceInfo{ + { + Size: abi.PaddedPieceSize(ssize), + PieceCID: params.Unsealed, + }, + }, storiface.SectorCids{ + Unsealed: params.Unsealed, + Sealed: params.Sealed, + }) + if err != nil { + w.WriteHeader(500) + return + } + + if _, err := w.Write(p); err != nil { + fmt.Println("c1 write error") + } + } +} + +func testRemoteCommit2(s *ffiwrapper.Sealer) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + var params api.RemoteCommit2Params + if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { + w.WriteHeader(500) + return + } + + sref := storiface.SectorRef{ + ID: params.Sector, + ProofType: params.ProofType, + } + + p, err := s.SealCommit2(r.Context(), sref, params.Commit1Out) + if err != nil { + fmt.Println("c2 error: ", err) + w.WriteHeader(500) + return + } + + if _, err := w.Write(p); err != nil { + fmt.Println("c2 write error") + } + } +} + +func testRemoteGetSector(sectorRoot string) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + + vars := mux.Vars(r) + + // validate sector id + id, err := storiface.ParseSectorID(vars["id"]) + if err != nil { + w.WriteHeader(500) + return + } + + // validate type + _, err = spaths.FileTypeFromString(vars["type"]) + if err != nil { + w.WriteHeader(500) + return + } + + typ := vars["type"] + if typ == "cache" { + // if cache is requested, send the finalized cache we've created above + typ = "fin-cache" + } + + path := filepath.Join(sectorRoot, typ, vars["id"]) + + stat, err := os.Stat(path) + if err != nil { + w.WriteHeader(500) + return + } + + if stat.IsDir() { + if _, has := r.Header["Range"]; has { + w.WriteHeader(500) + return + } + + w.Header().Set("Content-Type", "application/x-tar") + w.WriteHeader(200) + + err := tarutil.TarDirectory(path, w, make([]byte, 1<<20)) + if err != nil { + return + } + } else { + w.Header().Set("Content-Type", "application/octet-stream") + // will do a ranged read over the file at the given path if the caller has asked for a ranged read in the request headers. + http.ServeFile(w, r, path) + } + + fmt.Printf("served sector file/dir, sectorID=%+v, fileType=%s, path=%s\n", id, vars["type"], path) + } +} diff --git a/itests/sector_import_simple_test.go b/itests/sector_import_simple_test.go index 419415c1b..f75d0d842 100644 --- a/itests/sector_import_simple_test.go +++ b/itests/sector_import_simple_test.go @@ -57,7 +57,11 @@ func TestSectorImportAfterPC2(t *testing.T) { mid, err := address.IDFromAddress(maddr) require.NoError(t, err) - spt, err := currentSealProof(ctx, client, maddr) + mi, err := client.StateMinerInfo(ctx, maddr, types.EmptyTSK) + require.NoError(t, err) + ver, err := client.StateNetworkVersion(ctx, types.EmptyTSK) + require.NoError(t, err) + spt, err := lminer.PreferredSealProofTypeFromWindowPoStType(ver, mi.WindowPoStProofType) require.NoError(t, err) ssize, err := spt.SectorSize() @@ -316,17 +320,3 @@ func remoteGetSector(sectorRoot string) func(w http.ResponseWriter, r *http.Requ fmt.Printf("served sector file/dir, sectorID=%+v, fileType=%s, path=%s\n", id, vars["type"], path) } } - -func currentSealProof(ctx context.Context, api api.FullNode, maddr address.Address) (abi.RegisteredSealProof, error) { - mi, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK) - if err != nil { - return 0, err - } - - ver, err := api.StateNetworkVersion(ctx, types.EmptyTSK) - if err != nil { - return 0, err - } - - return lminer.PreferredSealProofTypeFromWindowPoStType(ver, mi.WindowPoStProofType) -} From 061a990eb8e566bbfbb56379882b077a13493bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 9 Sep 2022 14:38:23 +0200 Subject: [PATCH 56/85] sector import: RemoteSealingDoneEndpoint --- api/api_storage.go | 21 +++++- build/openrpc/miner.json.gz | Bin 15813 -> 15826 bytes documentation/en/api-v0-methods-miner.md | 3 +- itests/kit/node_miner.go | 2 +- itests/sector_import_full_test.go | 78 ++++++++++++++++++++--- itests/sector_import_simple_test.go | 27 +++++++- storage/pipeline/cbor_gen.go | 36 ++++++++++- storage/pipeline/fsm.go | 61 ++++++++++++++++-- storage/pipeline/fsm_events.go | 3 + storage/pipeline/receive.go | 9 +++ storage/pipeline/types.go | 13 ++-- 11 files changed, 226 insertions(+), 27 deletions(-) diff --git a/api/api_storage.go b/api/api_storage.go index 6273c4881..5d7455340 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -573,12 +573,17 @@ type RemoteSectorMeta struct { // SEALING SERVICE HOOKS // URL - // todo better doc + // RemoteCommit1Endpoint is an URL of POST endpoint which lotus will call requesting Commit1 (seal_commit_phase1) + // request body will be json-serialized RemoteCommit1Params struct RemoteCommit1Endpoint string + // RemoteCommit2Endpoint is an URL of POST endpoint which lotus will call requesting Commit2 (seal_commit_phase2) + // request body will be json-serialized RemoteCommit2Params struct RemoteCommit2Endpoint string - // todo OnDone / OnStateChange + // RemoteSealingDoneEndpoint is called after the sector exists the sealing pipeline + // request body will be json-serialized RemoteSealingDoneParams struct + RemoteSealingDoneEndpoint string } type RemoteCommit1Params struct { @@ -597,3 +602,15 @@ type RemoteCommit2Params struct { // todo spec better Commit1Out storiface.Commit1Out } + +type RemoteSealingDoneParams struct { + // Successful is true if the sector has entered state considered as "successfully sealed" + Successful bool + + // State is the state the sector has entered + // For example "Proving" / "Removing" + State string + + // Optional commit message CID + CommitMessage *cid.Cid +} diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 3d42364ddb4334b44a3ef885e0d3e628097916d1..f2bd95d2fb4f845af7ee46ac47065bd04a687dad 100644 GIT binary patch literal 15826 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 literal 15813 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 cbg.MaxLength { + return xerrors.Errorf("Value in field \"RemoteSealingDoneEndpoint\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("RemoteSealingDoneEndpoint"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("RemoteSealingDoneEndpoint")); err != nil { + return err + } + + if len(t.RemoteSealingDoneEndpoint) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.RemoteSealingDoneEndpoint was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.RemoteSealingDoneEndpoint))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.RemoteSealingDoneEndpoint)); err != nil { + return err + } + // t.RemoteDataFinalized (bool) (bool) if len("RemoteDataFinalized") > cbg.MaxLength { return xerrors.Errorf("Value in field \"RemoteDataFinalized\" was too long") @@ -1547,6 +1570,17 @@ func (t *SectorInfo) UnmarshalCBOR(r io.Reader) (err error) { t.RemoteCommit2Endpoint = string(sval) } + // t.RemoteSealingDoneEndpoint (string) (string) + case "RemoteSealingDoneEndpoint": + + { + sval, err := cbg.ReadString(cr) + if err != nil { + return err + } + + t.RemoteSealingDoneEndpoint = string(sval) + } // t.RemoteDataFinalized (bool) (bool) case "RemoteDataFinalized": diff --git a/storage/pipeline/fsm.go b/storage/pipeline/fsm.go index fc0a93a85..264a6ca49 100644 --- a/storage/pipeline/fsm.go +++ b/storage/pipeline/fsm.go @@ -7,6 +7,7 @@ import ( "context" "encoding/json" "fmt" + "net/http" "reflect" "time" @@ -14,6 +15,8 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-statemachine" + + "github.com/filecoin-project/lotus/api" ) func (m *Sealing) Plan(events []statemachine.Event, user interface{}) (interface{}, uint64, error) { @@ -142,8 +145,8 @@ var fsmPlanners = map[SectorState]func(events []statemachine.Event, state *Secto ), FinalizeSector: planOne( - on(SectorFinalized{}, Proving), - on(SectorFinalizedAvailable{}, Available), + onWithCB(SectorFinalized{}, Proving, maybeNotifyRemoteDone(true, "Proving")), + onWithCB(SectorFinalizedAvailable{}, Available, maybeNotifyRemoteDone(true, "Available")), on(SectorFinalizeFailed{}, FinalizeFailed), ), @@ -218,7 +221,7 @@ var fsmPlanners = map[SectorState]func(events []statemachine.Event, state *Secto on(SectorRetryWaitSeed{}, WaitSeed), on(SectorSealPreCommit1Failed{}, SealPreCommit1Failed), on(SectorPreCommitLanded{}, WaitSeed), - on(SectorDealsExpired{}, DealsExpired), + onWithCB(SectorDealsExpired{}, DealsExpired, maybeNotifyRemoteDone(false, "DealsExpired")), on(SectorInvalidDealIDs{}, RecoverDealIDs), ), ComputeProofFailed: planOne( @@ -241,9 +244,9 @@ var fsmPlanners = map[SectorState]func(events []statemachine.Event, state *Secto on(SectorRetryPreCommit{}, PreCommitting), on(SectorRetryCommitWait{}, CommitWait), on(SectorRetrySubmitCommit{}, SubmitCommit), - on(SectorDealsExpired{}, DealsExpired), + onWithCB(SectorDealsExpired{}, DealsExpired, maybeNotifyRemoteDone(false, "DealsExpired")), on(SectorInvalidDealIDs{}, RecoverDealIDs), - on(SectorTicketExpired{}, Removing), + onWithCB(SectorTicketExpired{}, Removing, maybeNotifyRemoteDone(false, "Removing")), ), FinalizeFailed: planOne( on(SectorRetryFinalize{}, FinalizeSector), @@ -736,6 +739,16 @@ func on(mut mutator, next SectorState) func() (mutator, func(*SectorInfo) (bool, } } +func onWithCB(mut mutator, next SectorState, cb func(info *SectorInfo)) func() (mutator, func(*SectorInfo) (bool, error)) { + return func() (mutator, func(*SectorInfo) (bool, error)) { + return mut, func(state *SectorInfo) (bool, error) { + cb(state) + state.State = next + return false, nil + } + } +} + // like `on`, but doesn't change state func apply(mut mutator) func() (mutator, func(*SectorInfo) (bool, error)) { return func() (mutator, func(*SectorInfo) (bool, error)) { @@ -812,3 +825,41 @@ func planOneOrIgnore(ts ...func() (mut mutator, next func(*SectorInfo) (more boo return cnt, nil } } + +func maybeNotifyRemoteDone(success bool, state string) func(*SectorInfo) { + return func(sector *SectorInfo) { + if sector.RemoteSealingDoneEndpoint == "" { + return + } + + reqData := api.RemoteSealingDoneParams{ + Successful: success, + State: state, + CommitMessage: sector.CommitMessage, + } + reqBody, err := json.Marshal(&reqData) + if err != nil { + log.Errorf("marshaling remote done notification request params: %s", err) + return + } + + req, err := http.NewRequest("POST", sector.RemoteSealingDoneEndpoint, bytes.NewReader(reqBody)) + if err != nil { + log.Errorf("creating new remote done notification request: %s", err) + return + } + req.Header.Set("Content-Type", "application/json") + resp, err := http.DefaultClient.Do(req) + if err != nil { + log.Errorf("sending remote done notification: %s", err) + return + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + log.Errorf("remote done notification received non-200 http response %s", resp.Status) + return + } + } +} diff --git a/storage/pipeline/fsm_events.go b/storage/pipeline/fsm_events.go index 1fbd94fd0..f92f527ad 100644 --- a/storage/pipeline/fsm_events.go +++ b/storage/pipeline/fsm_events.go @@ -524,6 +524,9 @@ func (evt SectorTerminateFailed) apply(*SectorInfo) {} type SectorRemove struct{} func (evt SectorRemove) applyGlobal(state *SectorInfo) bool { + // because this event is global we need to send the notification here instead through an fsm callback + maybeNotifyRemoteDone(false, "Removing")(state) + state.State = Removing return true } diff --git a/storage/pipeline/receive.go b/storage/pipeline/receive.go index f06848bcb..e5eec5ab9 100644 --- a/storage/pipeline/receive.go +++ b/storage/pipeline/receive.go @@ -211,6 +211,15 @@ func (m *Sealing) checkSectorMeta(ctx context.Context, meta api.RemoteSectorMeta info.Pieces = meta.Pieces info.SectorType = meta.Type + if meta.RemoteSealingDoneEndpoint != "" { + // validate the url + if _, err := url.Parse(meta.RemoteSealingDoneEndpoint); err != nil { + return SectorInfo{}, xerrors.Errorf("parsing remote sealing-done endpoint url: %w", err) + } + + info.RemoteSealingDoneEndpoint = meta.RemoteSealingDoneEndpoint + } + if err := checkPieces(ctx, m.maddr, meta.Sector.Number, meta.Pieces, m.Api, false); err != nil { return SectorInfo{}, xerrors.Errorf("checking pieces: %w", err) } diff --git a/storage/pipeline/types.go b/storage/pipeline/types.go index 81a2a2fec..cb1d84383 100644 --- a/storage/pipeline/types.go +++ b/storage/pipeline/types.go @@ -94,12 +94,13 @@ type SectorInfo struct { TerminatedAt abi.ChainEpoch // Remote import - RemoteDataUnsealed *storiface.SectorData - RemoteDataSealed *storiface.SectorData - RemoteDataCache *storiface.SectorData - RemoteCommit1Endpoint string - RemoteCommit2Endpoint string - RemoteDataFinalized bool + RemoteDataUnsealed *storiface.SectorData + RemoteDataSealed *storiface.SectorData + RemoteDataCache *storiface.SectorData + RemoteCommit1Endpoint string + RemoteCommit2Endpoint string + RemoteSealingDoneEndpoint string + RemoteDataFinalized bool // Debug LastErr string From fec9c0f98169fa37e11514ebaaf45e370ba560a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 9 Sep 2022 14:57:56 +0200 Subject: [PATCH 57/85] sector import: Fix lint --- build/openrpc/full.json.gz | Bin 28355 -> 28281 bytes build/openrpc/gateway.json.gz | Bin 4943 -> 4943 bytes build/openrpc/miner.json.gz | Bin 15826 -> 15968 bytes build/openrpc/worker.json.gz | Bin 5256 -> 5256 bytes documentation/en/api-v0-methods-miner.md | 5 +---- itests/sector_import_full_test.go | 2 +- node/config/doc_gen.go | 2 +- storage/pipeline/fsm.go | 2 +- storage/pipeline/states_sealing.go | 4 ++-- storage/sealer/tarutil/systar.go | 4 +++- 10 files changed, 9 insertions(+), 10 deletions(-) diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 942fb190afe07f2f1df19e969c1a4b3db8e725ac..d71446f65e6c5d0ad2be9ca2632365942a8d33bd 100644 GIT binary patch delta 23574 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Ñ{_A{2Gf`Yb0C-re`UxA2fi=WmMC-eI^lrBYou8F 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%0DmV01q*JJP+FgOuIlo$D5kfE$cNws z@7IGv!UtcbX=}AF*gsmqy18NIcatLOF0#G3$c}Te=hyHe83XncIiq0 zw5dA^68@lP^pvEmp4qw)_FSr)vDrk%e+i$zq9HpVo$F6c#B*(|hkQwDsvHG0P9I74 zfPXo+SUPuzD2jZ&+^&CeOZTqb!h)53{X5j^?ludmk6NNf8FTV z5LcV(46v*N32DZB;M4A7BS$*CK1c;hi-!R zUbIa7buqtnKgO!n1CjsscU^E$MzIR{)pXD}`CQ3RphcVQYuep}482)kaeun*l)zJFAZ zZWb~nJaYC}%`jB=6e_Ct?0hd_0>+ABD~w2=y7dI9TfDmCDM~(`t2j866zl##zK_Wm z`6#rzr)d;cE4DuP+xm+Kr#$ydNScH}0enNN6%k&iJ}YJXd-o=4(KDbOp@T=#LXqV>e?ga$5)5loK}sm0K(=;TYaA&L(Fk3sZU6^+Q%Ac zh~i>p^(H+pi_kWcfNB6}MEDm6l&tNlIg(_QBLoV1NXCc6tDo zByx3$c?rNa%>$4`w_b^IJ(0|MWbE-7>6)9Lk!r&{1=mpq(vZi0NJOzsLeQty`-h6> zL2tX|L+_ZWu-02i?F^%A)31ck`luSe#fed5(9=GR>d8?(4hYKADsm?MX^I81tNaiLQ*HOY7Z4H+P){ z3I*06PAU*J1PaH0w@H)$7QUAdn7JsrgYZV$yNO^B zf#3yQ%W{rhHV=#>IX`UIFYro}1upQaIsW+-udg6z-d)9B;MJq*SB86=a-~?XQl^BJ zHq{??^Woym=uJGAzW45JczTaK9~w5(^D=yuch=NThE{@q?ZxQmc)l2%>>_dY)G4(%wxD>7Rr?$N{iVe!A8;b`^K{{L}s zKbi%9(VVQN&chr%943R`T?fj-TX(ID6y957DgSE2&ci`&HYv97csgk!Y)vnNdKsiC zgT%^ak@lahssCD=>5q;ZQ>)WLpv`itOHgDAhXA%cI>Ca{10ItVZXkaO`~D8EW#lhm z>{Rf_TfO&Q{zv5Ke;V__VN_}N+0)|U?mkMGQdsd3DQC8^#~Mv;%ictsP1wl93{&C&CE~GMu6Nw19hCGU%S4TeNW~ hSnvJpvm#AN?~`MCB>NBjZvX%Q|NlkGVb&#r1pvI%kre;{ delta 23711 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%0DscL)(9Rf2hM8ECNGgBt)-LDK>?r+rG4Eh822 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 diff --git a/build/openrpc/gateway.json.gz b/build/openrpc/gateway.json.gz index b3db3736c003becfd4c77c46a95bd6af4b99ca32..a1210796764b08656a29b994459fc36024e2a03b 100644 GIT binary patch delta 3391 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 VySwjq{}%uN|NjdmZ0;lS007sox6J?m delta 3391 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 B9|`~f delta 15364 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 TyY>GE00960aeL+GXE6Z)tIN5S delta 2505 zcmV;)2{!hKDTpbs4Fi9#3|O1e+=Cg`88$hoN0=**dE~JO^)MC@*4h+_*fnayA+ou{ zu9BcrSXJIFsfkj&NY|Z4TH(5d>lUuN7T4Vy=D2SAkyth~gRuzBjLVCs3gDeePHfxq z=Zy>K+$0k*K^fb>OTMV$`ctAdh5}ig5}j0v31v&{ybTjdW&nSs28fj&0a}(1K~l$D zkK6Q0DwN8ee6(D> zd>q|=`DpkZ|IIcLCyDljZHhNfL)v&mgrrc>8Ev0o*$daXq zP$B!Pb+aVmAxLNrDVa#~!x%_1nmo1_RS%`O+5@g)5|oqC9}ky3S8@^{Vr-|KkH21l=N zN-WduL4AMK`mACEhyY#zysr&-2fbq84acGy;s%+I1+_t`O=Ik~jRIU-W~u^Q1-J@u z72tY2a2=}I;Q9p-y*zBHRlM`rjt8WT(i`pyTw`IoM z2KWv2djD#-ZzP0*a}yDYr#NHf`wW}FWz~C#Q09N2J!DNgL7E9Bv%ug81<*8Ipg(P1Q2{R}XET(8R)$}RWmaD;XiFEZrm)2sPS|GYW^Z?OETC*tzR{>ZX5gzM%i7>%r zb{G@hl<-_sIFkb3+&Qe!Zlw59%y%Pv0rgE4U6t`hI_g=&K?U6vbXU+_L3gw0?(3zF zDYu~k8{SBioHs?yPfXUP3bC4Vp?+-xPsQw^9U8wai@GK-g2- zTsCUp_vzF_l#P_C%hDD8iGttNclS3Xmg)AOzGvN+7XT{&_O$`*NX?TI4_1HNxW-zZ zaYa^Dxos?xm!G*Z<-EYiK<8v?bMw|n)y=Wic>4rrZHN80jI+k{xGZDca8yl|B|r07 z(8+ZyKUHVFQAr9H`cl!Uf&&W(Y-3 zCdl*r-DURJ$4F>U{D4v^^dx__C-$C$O3tv!yW<>bN>h*@?=7*zEWp1I{L>avE^__T zq=s!MeLgiq9TY&Jw!#zIru9Exm9N8yUf)m=sV1>Hb>vd@GQ(k%+yaVMjht{d?B|?tcZKM$#McIHyV+#SBa9Q}t<8k3U>In;{o?VZwva;mzsGDCf)R@T;efrs-1*aU7n50$ zBE`!&Sf5g2<$HhL+UC*zYGNry5jz{Z&cof_oK}x>K35DC;4`Aon19UZlu~NF&Ccl+ z6S-^B=BPA{oG$;?v}dH`e2*9F@B`dpq<}*`W2C5B)k*lrSFPc_yiZvI9XLR@^R)Us zc(;Jg&2E|Q{PSHfZiUMfkwOdBDsoEZTe*nK--sv8ku!hQ)A{MjtZA0Iq2p9KF(HK~ z4y1E`;j-Y8yqdy}+iDDIUg&6`5tqnBh6UY2tRh}Up40y&I_Ca9)xPD3tkP@3ft1M4 zvh5V>f^w+P)5c>66}_Vl)P6_TC%w)DntEqqD#J;CJW*9cuZ$LjdoE&GQso?lIH?P0DhM#?+X0^_1JPr*{nEJ?@0tJ4jCxA8<3 zlEv)f~iJo;2Dix z8uV#hH09Wqyl=jdUp&{mBifRjMeZ;K=-e(9MMHlexNYv>weCHw$exuHrY|!)+1M4+ z(v4}IKCSmmK?)-jUU%%kXpplF8hkeDhqrQCPb6pc$hm9cK+{C#rxiu##6&-~2T*L@ z_!V5?pU6LWBJ<;be#INXA}wet?k?U*;}t2(yQJw zz4w1?iIgEX*b=plW~2{|cy(WEqz_;a*8+&Uv_!C~2v)6)aE@|fh(cTDB%5Kb?1|de zJ>*lpFi8Q;3sZ2hyPZ^x?DXVy)$VyiII8tVx>6ZQTdI)boFJvYAg2FbVnjJra+{AY zfMc8O&=s&~{n_fbt7>A|#=QrYgklTOIdUlxfGz?g2-fs~yClR9@f1-WTuL=oRB64w TTmOFm00960=axf}XE6Z)2TSY& diff --git a/documentation/en/api-v0-methods-miner.md b/documentation/en/api-v0-methods-miner.md index c473f1904..848600bc6 100644 --- a/documentation/en/api-v0-methods-miner.md +++ b/documentation/en/api-v0-methods-miner.md @@ -3242,10 +3242,7 @@ Inputs: 5432 ], "Expiration": 10101, - "ReplaceCapacity": true, - "ReplaceSectorDeadline": 42, - "ReplaceSectorPartition": 42, - "ReplaceSectorNumber": 9 + "UnsealedCid": null }, "PreCommitDeposit": "0", "PreCommitMessage": null, diff --git a/itests/sector_import_full_test.go b/itests/sector_import_full_test.go index 761bee3a5..00a29b82b 100644 --- a/itests/sector_import_full_test.go +++ b/itests/sector_import_full_test.go @@ -259,7 +259,7 @@ func TestSectorImport(t *testing.T) { return } - remoteCommit1(s)(w, r) + testRemoteCommit1(s, m)(w, r) } } }))) diff --git a/node/config/doc_gen.go b/node/config/doc_gen.go index 3d90afd0b..7bd739f99 100644 --- a/node/config/doc_gen.go +++ b/node/config/doc_gen.go @@ -797,7 +797,7 @@ This parameter is ONLY applicable if the retrieval pricing policy strategy has b Comment: ``, }, { - Num: "AllowSectorDownload", + Num: "AllowSectorDownload", Type: "bool", Comment: ``, diff --git a/storage/pipeline/fsm.go b/storage/pipeline/fsm.go index 264a6ca49..46c0fd9b5 100644 --- a/storage/pipeline/fsm.go +++ b/storage/pipeline/fsm.go @@ -855,7 +855,7 @@ func maybeNotifyRemoteDone(success bool, state string) func(*SectorInfo) { return } - defer resp.Body.Close() + defer resp.Body.Close() //nolint:errcheck if resp.StatusCode != http.StatusOK { log.Errorf("remote done notification received non-200 http response %s", resp.Status) diff --git a/storage/pipeline/states_sealing.go b/storage/pipeline/states_sealing.go index 9b815053a..b40a9bf45 100644 --- a/storage/pipeline/states_sealing.go +++ b/storage/pipeline/states_sealing.go @@ -609,7 +609,7 @@ func (m *Sealing) handleCommitting(ctx statemachine.Context, sector SectorInfo) return ctx.Send(SectorRemoteCommit1Failed{xerrors.Errorf("requesting remote commit1: %w", err)}) } - defer resp.Body.Close() + defer resp.Body.Close() //nolint:errcheck if resp.StatusCode != http.StatusOK { return ctx.Send(SectorRemoteCommit1Failed{xerrors.Errorf("remote commit1 received non-200 http response %s", resp.Status)}) @@ -655,7 +655,7 @@ func (m *Sealing) handleCommitting(ctx statemachine.Context, sector SectorInfo) return ctx.Send(SectorRemoteCommit2Failed{xerrors.Errorf("requesting remote commit2: %w", err)}) } - defer resp.Body.Close() + defer resp.Body.Close() //nolint:errcheck if resp.StatusCode != http.StatusOK { return ctx.Send(SectorRemoteCommit2Failed{xerrors.Errorf("remote commit2 received non-200 http response %s", resp.Status)}) diff --git a/storage/sealer/tarutil/systar.go b/storage/sealer/tarutil/systar.go index f7437d1f2..309cc882a 100644 --- a/storage/sealer/tarutil/systar.go +++ b/storage/sealer/tarutil/systar.go @@ -102,7 +102,9 @@ func ExtractTar(body io.Reader, dir string, buf []byte) (int64, error) { return read, xerrors.Errorf("tar file %#v is bigger than expected: %d > %d", header.Name, header.Size, sz) } - r, err := io.CopyBuffer(f, tr, buf) + ltr := io.LimitReader(tr, header.Size) + + r, err := io.CopyBuffer(f, ltr, buf) read += r if err != nil { return read, err From a05593da5bd0939349806ec0bb50ba5b09432baf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 14 Sep 2022 09:57:38 +0200 Subject: [PATCH 58/85] systar: Create file after header checks --- storage/sealer/tarutil/systar.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/storage/sealer/tarutil/systar.go b/storage/sealer/tarutil/systar.go index 309cc882a..e13e5ff21 100644 --- a/storage/sealer/tarutil/systar.go +++ b/storage/sealer/tarutil/systar.go @@ -5,6 +5,7 @@ import ( "io" "os" "path/filepath" + "strings" logging "github.com/ipfs/go-log/v2" "golang.org/x/xerrors" @@ -87,13 +88,6 @@ func ExtractTar(body io.Reader, dir string, buf []byte) (int64, error) { case nil: } - //nolint:gosec - f, err := os.Create(filepath.Join(dir, header.Name)) - if err != nil { - //nolint:gosec - return read, xerrors.Errorf("creating file %s: %w", filepath.Join(dir, header.Name), err) - } - sz, found := CacheFileConstraints[header.Name] if !found { return read, xerrors.Errorf("tar file %#v isn't expected") @@ -102,6 +96,17 @@ func ExtractTar(body io.Reader, dir string, buf []byte) (int64, error) { return read, xerrors.Errorf("tar file %#v is bigger than expected: %d > %d", header.Name, header.Size, sz) } + out := filepath.Join(dir, header.Name) //nolint:gosec + + if !strings.HasPrefix(out, filepath.Clean(dir)) { + return read, xerrors.Errorf("unsafe tar path %#v (must be within %#v)", out, filepath.Clean(dir)) + } + + f, err := os.Create(out) + if err != nil { + return read, xerrors.Errorf("creating file %s: %w", out, err) + } + ltr := io.LimitReader(tr, header.Size) r, err := io.CopyBuffer(f, ltr, buf) From 859c2606f073d66c4669632a1b67fc7b9aec0d4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 16 Sep 2022 23:45:23 +0200 Subject: [PATCH 59/85] sealing: Address review --- api/api_storage.go | 6 ++--- api/api_worker.go | 2 +- api/docgen/docgen.go | 2 +- api/proxy_gen.go | 6 ++--- build/openrpc/full.json.gz | Bin 28281 -> 28424 bytes build/openrpc/gateway.json.gz | Bin 4943 -> 5080 bytes build/openrpc/miner.json.gz | Bin 15968 -> 16025 bytes build/openrpc/worker.json.gz | Bin 5256 -> 5260 bytes gen/main.go | 2 +- itests/sector_import_full_test.go | 8 +++---- itests/sector_import_simple_test.go | 8 +++---- node/config/doc_gen.go | 2 +- storage/paths/fetch.go | 1 + storage/pipeline/cbor_gen.go | 20 ++++++++-------- storage/pipeline/receive.go | 29 +++++++++++++++++++----- storage/pipeline/types.go | 6 ++--- storage/sealer/ffiwrapper/sealer_cgo.go | 2 +- storage/sealer/manager.go | 6 ++--- storage/sealer/mock/mock.go | 2 +- storage/sealer/sched_test.go | 2 +- storage/sealer/storiface/cbor_gen.go | 8 +++---- storage/sealer/storiface/storage.go | 6 ++--- storage/sealer/storiface/worker.go | 2 +- storage/sealer/teststorage_test.go | 2 +- storage/sealer/worker_local.go | 2 +- 25 files changed, 71 insertions(+), 53 deletions(-) diff --git a/api/api_storage.go b/api/api_storage.go index 5d7455340..0cccbeb7a 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -563,11 +563,11 @@ type RemoteSectorMeta struct { // Sector urls - lotus will use those for fetching files into local storage // Required in all states - DataUnsealed *storiface.SectorData + DataUnsealed *storiface.SectorLocation // Required in PreCommitting and later - DataSealed *storiface.SectorData - DataCache *storiface.SectorData + DataSealed *storiface.SectorLocation + DataCache *storiface.SectorLocation //////// // SEALING SERVICE HOOKS diff --git a/api/api_worker.go b/api/api_worker.go index 4b56d1154..cca929d39 100644 --- a/api/api_worker.go +++ b/api/api_worker.go @@ -49,7 +49,7 @@ type Worker interface { MoveStorage(ctx context.Context, sector storiface.SectorRef, types storiface.SectorFileType) (storiface.CallID, error) //perm:admin UnsealPiece(context.Context, storiface.SectorRef, storiface.UnpaddedByteIndex, abi.UnpaddedPieceSize, abi.SealRandomness, cid.Cid) (storiface.CallID, error) //perm:admin Fetch(context.Context, storiface.SectorRef, storiface.SectorFileType, storiface.PathType, storiface.AcquireMode) (storiface.CallID, error) //perm:admin - DownloadSectorData(ctx context.Context, sector storiface.SectorRef, finalized bool, src map[storiface.SectorFileType]storiface.SectorData) (storiface.CallID, error) //perm:admin + DownloadSectorData(ctx context.Context, sector storiface.SectorRef, finalized bool, src map[storiface.SectorFileType]storiface.SectorLocation) (storiface.CallID, error) //perm:admin GenerateWinningPoSt(ctx context.Context, ppt abi.RegisteredPoStProof, mid abi.ActorID, sectors []storiface.PostSectorChallenge, randomness abi.PoStRandomness) ([]proof.PoStProof, error) //perm:admin GenerateWindowPoSt(ctx context.Context, ppt abi.RegisteredPoStProof, mid abi.ActorID, sectors []storiface.PostSectorChallenge, partitionIdx int, randomness abi.PoStRandomness) (storiface.WindowPoStResult, error) //perm:admin diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index e078cbe0f..cd1f03337 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -345,7 +345,7 @@ func init() { "Authorization": []string{"Bearer ey.."}, }) - addExample(map[storiface.SectorFileType]storiface.SectorData{ + addExample(map[storiface.SectorFileType]storiface.SectorLocation{ storiface.FTSealed: { Local: false, URL: "https://example.com/sealingservice/sectors/s-f0123-12345", diff --git a/api/proxy_gen.go b/api/proxy_gen.go index 04e79c2de..935eaca94 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -955,7 +955,7 @@ type WorkerStruct struct { DataCid func(p0 context.Context, p1 abi.UnpaddedPieceSize, p2 storiface.Data) (storiface.CallID, error) `perm:"admin"` - DownloadSectorData func(p0 context.Context, p1 storiface.SectorRef, p2 bool, p3 map[storiface.SectorFileType]storiface.SectorData) (storiface.CallID, error) `perm:"admin"` + DownloadSectorData func(p0 context.Context, p1 storiface.SectorRef, p2 bool, p3 map[storiface.SectorFileType]storiface.SectorLocation) (storiface.CallID, error) `perm:"admin"` Enabled func(p0 context.Context) (bool, error) `perm:"admin"` @@ -5551,14 +5551,14 @@ func (s *WorkerStub) DataCid(p0 context.Context, p1 abi.UnpaddedPieceSize, p2 st return *new(storiface.CallID), ErrNotSupported } -func (s *WorkerStruct) DownloadSectorData(p0 context.Context, p1 storiface.SectorRef, p2 bool, p3 map[storiface.SectorFileType]storiface.SectorData) (storiface.CallID, error) { +func (s *WorkerStruct) DownloadSectorData(p0 context.Context, p1 storiface.SectorRef, p2 bool, p3 map[storiface.SectorFileType]storiface.SectorLocation) (storiface.CallID, error) { if s.Internal.DownloadSectorData == nil { return *new(storiface.CallID), ErrNotSupported } return s.Internal.DownloadSectorData(p0, p1, p2, p3) } -func (s *WorkerStub) DownloadSectorData(p0 context.Context, p1 storiface.SectorRef, p2 bool, p3 map[storiface.SectorFileType]storiface.SectorData) (storiface.CallID, error) { +func (s *WorkerStub) DownloadSectorData(p0 context.Context, p1 storiface.SectorRef, p2 bool, p3 map[storiface.SectorFileType]storiface.SectorLocation) (storiface.CallID, error) { return *new(storiface.CallID), ErrNotSupported } diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index d71446f65e6c5d0ad2be9ca2632365942a8d33bd..a8f16011a9c1b9424393842c633d5ddaabae5853 100644 GIT binary patch delta 27068 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 delta 26943 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 un|4BCb8FgWW`F37(gTH@1~%s$U|tkk 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 N|NjdmZ0;lS008OX!tek9 diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 0138f28c17ff4fd91e898511a9452d3fe977e5e8..75b4eeaaf87c49d4acc624efd2e3735e7f39a040 100644 GIT binary patch delta 12279 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 AiU0rr diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index 90167b6e09ec40c5a7dd6f5a19b1b5c28082a2fa..d73bda90317ed7ea9db5e25e1406858fd053f91b 100644 GIT binary patch delta 4522 zcmV;b5moMpDU2zw4Fi9#sjN+D?!gS}44a(PBg~b@Jn~qCdKilcYi)`|>>9P<5ZT;e zS4q$*tSax8)I=#>r0Y&2t#IAKbqm*Bi|f|49M^3>63d2WFczViae47n0lZVmiEUf{ zym0}Yn`9y;C}aC~$roR312O@5-G55l#*iSZW1<)Bv&4BVfz& zAxP?&?^QCtl1inrCoGS(9qQ}LuMrOyruJQ&hoFVY%tP)2HqeRZSLmI1ovG4OwN6jf z^u8oZ|3uJ4t|y(6rDd&|E;^7h4t=pCx!A6ek|3WKYivp$bsEf&r>DZsp zTB3|MS^Ahn%CLXuws|+Bz7D&wM4?r+R*)eg(BL=vM+K7UG@9hPg6m&wKDL zAUFAZB+Q&mLV_OS;2b+q4ki~_b{~S&QIM;psx;_ltI|IZHRha(~cXGPtxzxsfQ>VDOHyxfA`G)y^hCj zaP<17#4>-~9@JN@&niZO2;ddK``UnaG%N<*a4f1JZjkv{P#cumG{$b*D8RL4rYgWy zfU5vk0j|db*YPMDT)!ZKTu4Lg_!nCJ!?hO`;!1MM;e}iSo82>Pasq8`^-W=JQF{ga zw#=B@0KcJL?_cfqjf7BeZX!bQ6lbh_pJ5ZYta^VB5y~92hpcHQNHf7?78o3%0Gg(Y zJP(-wZ%gZfbIRQM)OHuh1d|nj;LqR-CK*6*W}Cy$6t(0D^BU)f{^BZhSHRNjhPA4yP-1@)H^eYLA{dAFMQ}4d_Jk?$N^e z+x~y?A7{C;7tY_4#T2cknm)zaay3{kk*+@I(ps!j3q%))?j!m@85bk^UznKom?#eHx4qgv?sv(0|v&hdY>5n)x?ON0p~v%{G1riACB!kHBK=FVY-b|b}? zV!j*U3#f0Z=&Fo2(oxSE4l3xbpu2+Z3c8y`cV91cOu6k0qfDnLWrkebu(FLDjmFiW zaxLXUS}&Uqos+4}Nq|_E><^`q2V$_4MQntDJpUTqtjMy%^M4r8Ljm@DLvJ`pnBz;4Ri}}jK{wJrpDzgXI9#hHi zkE=)M#Qxa5J-fK}f?r?!UpH|9?h;iwwntAMl#@q)?&)mIRtYCF-_XKlO;r_rBun4m zkNATx7uVMmTli=2CGu&{Pji0(U51ht_7)kBzFb^Or;03nf*#7M;mjt+(sdD`(y6kR zrCvgmkv19)l(;n8XEh2GttTmynvkid47H)&8>xCstlprf40`HN8`LV>KncIh^>G-EvS9keJ z+g$zu0@X1aC>YWMBujtPw$J

R2BShU$0_6E6Dr`GiaQ{!xE49&37kti}g4^7#3L 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 IL2oeu0L9hTivR!s delta 4518 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 E0ORPNRR910 diff --git a/gen/main.go b/gen/main.go index b7aaa0a9a..02548e18f 100644 --- a/gen/main.go +++ b/gen/main.go @@ -104,7 +104,7 @@ func main() { err = gen.WriteMapEncodersToFile("./storage/sealer/storiface/cbor_gen.go", "storiface", storiface.CallID{}, storiface.SecDataHttpHeader{}, - storiface.SectorData{}, + storiface.SectorLocation{}, ) if err != nil { fmt.Println(err) diff --git a/itests/sector_import_full_test.go b/itests/sector_import_full_test.go index 00a29b82b..35fc3e623 100644 --- a/itests/sector_import_full_test.go +++ b/itests/sector_import_full_test.go @@ -113,7 +113,7 @@ func TestSectorImport(t *testing.T) { }) require.NoError(t, err) - // CRETE THE UNSEALED FILE + // CREATE THE UNSEALED FILE // create a reader for all-zero (CC) data dataReader := bytes.NewReader(bytes.Repeat([]byte{0}, int(pieceSize.Unpadded()))) @@ -194,15 +194,15 @@ func TestSectorImport(t *testing.T) { CommD: &scids.Unsealed, CommR: &scids.Sealed, - DataUnsealed: &storiface.SectorData{ + DataUnsealed: &storiface.SectorLocation{ Local: false, URL: unsealedURL, }, - DataSealed: &storiface.SectorData{ + DataSealed: &storiface.SectorLocation{ Local: false, URL: sealedURL, }, - DataCache: &storiface.SectorData{ + DataCache: &storiface.SectorLocation{ Local: false, URL: cacheURL, }, diff --git a/itests/sector_import_simple_test.go b/itests/sector_import_simple_test.go index 94c1e6810..0cf7e098b 100644 --- a/itests/sector_import_simple_test.go +++ b/itests/sector_import_simple_test.go @@ -87,7 +87,7 @@ func TestSectorImportAfterPC2(t *testing.T) { }) require.NoError(t, err) - // CRETE THE UNSEALED FILE + // CREATE THE UNSEALED FILE // create a reader for all-zero (CC) data dataReader := bytes.NewReader(bytes.Repeat([]byte{0}, int(pieceSize.Unpadded()))) @@ -169,15 +169,15 @@ func TestSectorImportAfterPC2(t *testing.T) { CommD: &scids.Unsealed, CommR: &scids.Sealed, - DataUnsealed: &storiface.SectorData{ + DataUnsealed: &storiface.SectorLocation{ Local: false, URL: unsealedURL, }, - DataSealed: &storiface.SectorData{ + DataSealed: &storiface.SectorLocation{ Local: false, URL: sealedURL, }, - DataCache: &storiface.SectorData{ + DataCache: &storiface.SectorLocation{ Local: false, URL: cacheURL, }, diff --git a/node/config/doc_gen.go b/node/config/doc_gen.go index 7bd739f99..20b175ccb 100644 --- a/node/config/doc_gen.go +++ b/node/config/doc_gen.go @@ -797,7 +797,7 @@ This parameter is ONLY applicable if the retrieval pricing policy strategy has b Comment: ``, }, { - Num: "AllowSectorDownload", + Name: "AllowSectorDownload", Type: "bool", Comment: ``, diff --git a/storage/paths/fetch.go b/storage/paths/fetch.go index 4c2a62290..2d87380bd 100644 --- a/storage/paths/fetch.go +++ b/storage/paths/fetch.go @@ -72,6 +72,7 @@ func fetch(ctx context.Context, url, outname string, header http.Header) (rerr e } // FetchWithTemp fetches data into a temp 'fetching' directory, then moves the file to destination +// The set of URLs must refer to the same object, if one fails, another one will be tried. func FetchWithTemp(ctx context.Context, urls []string, dest string, header http.Header) (string, error) { var merr error for _, url := range urls { diff --git a/storage/pipeline/cbor_gen.go b/storage/pipeline/cbor_gen.go index f4dbbd3ee..88dac5d64 100644 --- a/storage/pipeline/cbor_gen.go +++ b/storage/pipeline/cbor_gen.go @@ -31,7 +31,7 @@ func (t *SectorInfo) MarshalCBOR(w io.Writer) error { cw := cbg.NewCborWriter(w) - if _, err := cw.Write([]byte{184, 39}); err != nil { + if _, err := cw.Write([]byte{184, 38}); err != nil { return err } @@ -655,7 +655,7 @@ func (t *SectorInfo) MarshalCBOR(w io.Writer) error { } } - // t.RemoteDataUnsealed (storiface.SectorData) (struct) + // t.RemoteDataUnsealed (storiface.SectorLocation) (struct) if len("RemoteDataUnsealed") > cbg.MaxLength { return xerrors.Errorf("Value in field \"RemoteDataUnsealed\" was too long") } @@ -671,7 +671,7 @@ func (t *SectorInfo) MarshalCBOR(w io.Writer) error { return err } - // t.RemoteDataSealed (storiface.SectorData) (struct) + // t.RemoteDataSealed (storiface.SectorLocation) (struct) if len("RemoteDataSealed") > cbg.MaxLength { return xerrors.Errorf("Value in field \"RemoteDataSealed\" was too long") } @@ -687,7 +687,7 @@ func (t *SectorInfo) MarshalCBOR(w io.Writer) error { return err } - // t.RemoteDataCache (storiface.SectorData) (struct) + // t.RemoteDataCache (storiface.SectorLocation) (struct) if len("RemoteDataCache") > cbg.MaxLength { return xerrors.Errorf("Value in field \"RemoteDataCache\" was too long") } @@ -1488,7 +1488,7 @@ func (t *SectorInfo) UnmarshalCBOR(r io.Reader) (err error) { t.TerminatedAt = abi.ChainEpoch(extraI) } - // t.RemoteDataUnsealed (storiface.SectorData) (struct) + // t.RemoteDataUnsealed (storiface.SectorLocation) (struct) case "RemoteDataUnsealed": { @@ -1501,14 +1501,14 @@ func (t *SectorInfo) UnmarshalCBOR(r io.Reader) (err error) { if err := cr.UnreadByte(); err != nil { return err } - t.RemoteDataUnsealed = new(storiface.SectorData) + t.RemoteDataUnsealed = new(storiface.SectorLocation) if err := t.RemoteDataUnsealed.UnmarshalCBOR(cr); err != nil { return xerrors.Errorf("unmarshaling t.RemoteDataUnsealed pointer: %w", err) } } } - // t.RemoteDataSealed (storiface.SectorData) (struct) + // t.RemoteDataSealed (storiface.SectorLocation) (struct) case "RemoteDataSealed": { @@ -1521,14 +1521,14 @@ func (t *SectorInfo) UnmarshalCBOR(r io.Reader) (err error) { if err := cr.UnreadByte(); err != nil { return err } - t.RemoteDataSealed = new(storiface.SectorData) + t.RemoteDataSealed = new(storiface.SectorLocation) if err := t.RemoteDataSealed.UnmarshalCBOR(cr); err != nil { return xerrors.Errorf("unmarshaling t.RemoteDataSealed pointer: %w", err) } } } - // t.RemoteDataCache (storiface.SectorData) (struct) + // t.RemoteDataCache (storiface.SectorLocation) (struct) case "RemoteDataCache": { @@ -1541,7 +1541,7 @@ func (t *SectorInfo) UnmarshalCBOR(r io.Reader) (err error) { if err := cr.UnreadByte(); err != nil { return err } - t.RemoteDataCache = new(storiface.SectorData) + t.RemoteDataCache = new(storiface.SectorLocation) if err := t.RemoteDataCache.UnmarshalCBOR(cr); err != nil { return xerrors.Errorf("unmarshaling t.RemoteDataCache pointer: %w", err) } diff --git a/storage/pipeline/receive.go b/storage/pipeline/receive.go index e5eec5ab9..b1b0ce99f 100644 --- a/storage/pipeline/receive.go +++ b/storage/pipeline/receive.go @@ -91,8 +91,13 @@ func (m *Sealing) checkSectorMeta(ctx context.Context, meta api.RemoteSectorMeta switch SectorState(meta.State) { case Proving, Available: - // todo possibly check - info.CommitMessage = meta.CommitMessage + if meta.CommitMessage != nil { + if err := checkMessagePrefix(*meta.CommitMessage); err != nil { + return SectorInfo{}, xerrors.Errorf("commit message prefix: %w", err) + } + + info.CommitMessage = meta.CommitMessage + } fallthrough case SubmitCommit: @@ -100,10 +105,14 @@ func (m *Sealing) checkSectorMeta(ctx context.Context, meta api.RemoteSectorMeta return SectorInfo{}, xerrors.Errorf("sector PreCommitDeposit was null") } - info.PreCommitInfo = meta.PreCommitInfo info.PreCommitDeposit = *meta.PreCommitDeposit - info.PreCommitMessage = meta.PreCommitMessage info.PreCommitTipSet = meta.PreCommitTipSet + if info.PreCommitMessage != nil { + if err := checkMessagePrefix(*meta.PreCommitMessage); err != nil { + return SectorInfo{}, xerrors.Errorf("commit message prefix: %w", err) + } + info.PreCommitMessage = meta.PreCommitMessage + } // check provided seed if len(meta.SeedValue) != abi.RandomnessLength { @@ -256,9 +265,9 @@ func (m *Sealing) checkSectorMeta(ctx context.Context, meta api.RemoteSectorMeta } func (m *Sealing) handleReceiveSector(ctx statemachine.Context, sector SectorInfo) error { - toFetch := map[storiface.SectorFileType]storiface.SectorData{} + toFetch := map[storiface.SectorFileType]storiface.SectorLocation{} - for fileType, data := range map[storiface.SectorFileType]*storiface.SectorData{ + for fileType, data := range map[storiface.SectorFileType]*storiface.SectorLocation{ storiface.FTUnsealed: sector.RemoteDataUnsealed, storiface.FTSealed: sector.RemoteDataSealed, storiface.FTCache: sector.RemoteDataCache, @@ -285,3 +294,11 @@ func (m *Sealing) handleReceiveSector(ctx statemachine.Context, sector SectorInf return ctx.Send(SectorReceived{}) } + +func checkMessagePrefix(c cid.Cid) error { + p := c.Prefix() + if p.Version != 1 || p.MhLength != 32 || p.MhType != multihash.BLAKE2B_MIN+31 || p.Codec != cid.DagCBOR { + return xerrors.New("invalid message prefix") + } + return nil +} diff --git a/storage/pipeline/types.go b/storage/pipeline/types.go index cb1d84383..6329b5666 100644 --- a/storage/pipeline/types.go +++ b/storage/pipeline/types.go @@ -94,9 +94,9 @@ type SectorInfo struct { TerminatedAt abi.ChainEpoch // Remote import - RemoteDataUnsealed *storiface.SectorData - RemoteDataSealed *storiface.SectorData - RemoteDataCache *storiface.SectorData + RemoteDataUnsealed *storiface.SectorLocation + RemoteDataSealed *storiface.SectorLocation + RemoteDataCache *storiface.SectorLocation RemoteCommit1Endpoint string RemoteCommit2Endpoint string RemoteSealingDoneEndpoint string diff --git a/storage/sealer/ffiwrapper/sealer_cgo.go b/storage/sealer/ffiwrapper/sealer_cgo.go index d73b72787..f4ecc2e7b 100644 --- a/storage/sealer/ffiwrapper/sealer_cgo.go +++ b/storage/sealer/ffiwrapper/sealer_cgo.go @@ -1169,7 +1169,7 @@ func (sb *Sealer) Remove(ctx context.Context, sector storiface.SectorRef) error return xerrors.Errorf("not supported at this layer") // happens in localworker } -func (sb *Sealer) DownloadSectorData(ctx context.Context, sector storiface.SectorRef, finalized bool, src map[storiface.SectorFileType]storiface.SectorData) error { +func (sb *Sealer) DownloadSectorData(ctx context.Context, sector storiface.SectorRef, finalized bool, src map[storiface.SectorFileType]storiface.SectorLocation) error { var todo storiface.SectorFileType for fileType := range src { todo |= fileType diff --git a/storage/sealer/manager.go b/storage/sealer/manager.go index a4d31e21b..b0c023b09 100644 --- a/storage/sealer/manager.go +++ b/storage/sealer/manager.go @@ -1089,7 +1089,7 @@ func (m *Manager) ProveReplicaUpdate2(ctx context.Context, sector storiface.Sect return out, waitErr } -func (m *Manager) DownloadSectorData(ctx context.Context, sector storiface.SectorRef, finalized bool, src map[storiface.SectorFileType]storiface.SectorData) error { +func (m *Manager) DownloadSectorData(ctx context.Context, sector storiface.SectorRef, finalized bool, src map[storiface.SectorFileType]storiface.SectorLocation) error { ctx, cancel := context.WithCancel(ctx) defer cancel() @@ -1098,7 +1098,7 @@ func (m *Manager) DownloadSectorData(ctx context.Context, sector storiface.Secto // get a sorted list of sectors files to make a consistent work key from ents := make([]struct { T storiface.SectorFileType - S storiface.SectorData + S storiface.SectorLocation }, 0, len(src)) for fileType, data := range src { if len(fileType.AllSet()) != 1 { @@ -1109,7 +1109,7 @@ func (m *Manager) DownloadSectorData(ctx context.Context, sector storiface.Secto ents = append(ents, struct { T storiface.SectorFileType - S storiface.SectorData + S storiface.SectorLocation }{T: fileType, S: data}) } sort.Slice(ents, func(i, j int) bool { diff --git a/storage/sealer/mock/mock.go b/storage/sealer/mock/mock.go index 173a4ddf2..0797bf549 100644 --- a/storage/sealer/mock/mock.go +++ b/storage/sealer/mock/mock.go @@ -517,7 +517,7 @@ func (mgr *SectorMgr) ReleaseSectorKey(ctx context.Context, sector storiface.Sec return nil } -func (mgr *SectorMgr) DownloadSectorData(ctx context.Context, sector storiface.SectorRef, finalized bool, src map[storiface.SectorFileType]storiface.SectorData) error { +func (mgr *SectorMgr) DownloadSectorData(ctx context.Context, sector storiface.SectorRef, finalized bool, src map[storiface.SectorFileType]storiface.SectorLocation) error { return xerrors.Errorf("not supported") } diff --git a/storage/sealer/sched_test.go b/storage/sealer/sched_test.go index bf4b90b19..89a286bdc 100644 --- a/storage/sealer/sched_test.go +++ b/storage/sealer/sched_test.go @@ -67,7 +67,7 @@ type schedTestWorker struct { ignoreResources bool } -func (s *schedTestWorker) DownloadSectorData(ctx context.Context, sector storiface.SectorRef, finalized bool, src map[storiface.SectorFileType]storiface.SectorData) (storiface.CallID, error) { +func (s *schedTestWorker) DownloadSectorData(ctx context.Context, sector storiface.SectorRef, finalized bool, src map[storiface.SectorFileType]storiface.SectorLocation) (storiface.CallID, error) { panic("implement me") } diff --git a/storage/sealer/storiface/cbor_gen.go b/storage/sealer/storiface/cbor_gen.go index 6fec11557..73a299dec 100644 --- a/storage/sealer/storiface/cbor_gen.go +++ b/storage/sealer/storiface/cbor_gen.go @@ -282,7 +282,7 @@ func (t *SecDataHttpHeader) UnmarshalCBOR(r io.Reader) (err error) { return nil } -func (t *SectorData) MarshalCBOR(w io.Writer) error { +func (t *SectorLocation) MarshalCBOR(w io.Writer) error { if t == nil { _, err := w.Write(cbg.CborNull) return err @@ -360,8 +360,8 @@ func (t *SectorData) MarshalCBOR(w io.Writer) error { return nil } -func (t *SectorData) UnmarshalCBOR(r io.Reader) (err error) { - *t = SectorData{} +func (t *SectorLocation) UnmarshalCBOR(r io.Reader) (err error) { + *t = SectorLocation{} cr := cbg.NewCborReader(r) @@ -380,7 +380,7 @@ func (t *SectorData) UnmarshalCBOR(r io.Reader) (err error) { } if extra > cbg.MaxLength { - return fmt.Errorf("SectorData: map struct too large (%d)", extra) + return fmt.Errorf("SectorLocation: map struct too large (%d)", extra) } var name string diff --git a/storage/sealer/storiface/storage.go b/storage/sealer/storiface/storage.go index 5246a1a1a..6d6063c54 100644 --- a/storage/sealer/storiface/storage.go +++ b/storage/sealer/storiface/storage.go @@ -87,7 +87,7 @@ type Sealer interface { FinalizeReplicaUpdate(ctx context.Context, sector SectorRef, keepUnsealed []Range) error - DownloadSectorData(ctx context.Context, sector SectorRef, finalized bool, src map[SectorFileType]SectorData) error + DownloadSectorData(ctx context.Context, sector SectorRef, finalized bool, src map[SectorFileType]SectorLocation) error } type Unsealer interface { @@ -123,7 +123,7 @@ type Prover interface { AggregateSealProofs(aggregateInfo proof.AggregateSealVerifyProofAndInfos, proofs [][]byte) ([]byte, error) } -type SectorData struct { +type SectorLocation struct { // Local when set to true indicates to lotus that sector data is already // available locally; When set lotus will skip fetching sector data, and // only check that sector data exists in sector storage @@ -140,7 +140,7 @@ type SectorData struct { Headers []SecDataHttpHeader } -func (sd *SectorData) HttpHeaders() http.Header { +func (sd *SectorLocation) HttpHeaders() http.Header { out := http.Header{} for _, header := range sd.Headers { out[header.Key] = append(out[header.Key], header.Value) diff --git a/storage/sealer/storiface/worker.go b/storage/sealer/storiface/worker.go index 11a4d265d..51a7901b0 100644 --- a/storage/sealer/storiface/worker.go +++ b/storage/sealer/storiface/worker.go @@ -134,7 +134,7 @@ type WorkerCalls interface { MoveStorage(ctx context.Context, sector SectorRef, types SectorFileType) (CallID, error) UnsealPiece(context.Context, SectorRef, UnpaddedByteIndex, abi.UnpaddedPieceSize, abi.SealRandomness, cid.Cid) (CallID, error) Fetch(context.Context, SectorRef, SectorFileType, PathType, AcquireMode) (CallID, error) - DownloadSectorData(ctx context.Context, sector SectorRef, finalized bool, src map[SectorFileType]SectorData) (CallID, error) + DownloadSectorData(ctx context.Context, sector SectorRef, finalized bool, src map[SectorFileType]SectorLocation) (CallID, error) // sync GenerateWinningPoSt(ctx context.Context, ppt abi.RegisteredPoStProof, mid abi.ActorID, sectors []PostSectorChallenge, randomness abi.PoStRandomness) ([]proof.PoStProof, error) diff --git a/storage/sealer/teststorage_test.go b/storage/sealer/teststorage_test.go index ee200d9bb..4b30d5fff 100644 --- a/storage/sealer/teststorage_test.go +++ b/storage/sealer/teststorage_test.go @@ -21,7 +21,7 @@ type testExec struct { apch chan chan apres } -func (t *testExec) DownloadSectorData(ctx context.Context, sector storiface.SectorRef, finalized bool, src map[storiface.SectorFileType]storiface.SectorData) error { +func (t *testExec) DownloadSectorData(ctx context.Context, sector storiface.SectorRef, finalized bool, src map[storiface.SectorFileType]storiface.SectorLocation) error { panic("implement me") } diff --git a/storage/sealer/worker_local.go b/storage/sealer/worker_local.go index 2e8846ab1..326f38366 100644 --- a/storage/sealer/worker_local.go +++ b/storage/sealer/worker_local.go @@ -588,7 +588,7 @@ func (l *LocalWorker) UnsealPiece(ctx context.Context, sector storiface.SectorRe }) } -func (l *LocalWorker) DownloadSectorData(ctx context.Context, sector storiface.SectorRef, finalized bool, src map[storiface.SectorFileType]storiface.SectorData) (storiface.CallID, error) { +func (l *LocalWorker) DownloadSectorData(ctx context.Context, sector storiface.SectorRef, finalized bool, src map[storiface.SectorFileType]storiface.SectorLocation) (storiface.CallID, error) { sb, err := l.executor() if err != nil { return storiface.UndefCall, err From 015139d451a5a17488b98dc4d4cb18907f521dba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Mon, 19 Sep 2022 12:16:11 +0200 Subject: [PATCH 60/85] sealing pipeline: Clarify maybeNotifyRemoteDone --- storage/pipeline/fsm.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/storage/pipeline/fsm.go b/storage/pipeline/fsm.go index 46c0fd9b5..0a75d88c8 100644 --- a/storage/pipeline/fsm.go +++ b/storage/pipeline/fsm.go @@ -826,6 +826,9 @@ func planOneOrIgnore(ts ...func() (mut mutator, next func(*SectorInfo) (more boo } } +// maybeNotifyRemoteDone will send sealing-done notification to the RemoteSealingDone +// if the RemoteSealingDoneEndpoint is set. If RemoteSealingDoneEndpoint is not set, +// this is no-op func maybeNotifyRemoteDone(success bool, state string) func(*SectorInfo) { return func(sector *SectorInfo) { if sector.RemoteSealingDoneEndpoint == "" { From 291a8895955988948e0e8b2795ee618e4aa255ee Mon Sep 17 00:00:00 2001 From: Aayush Date: Mon, 19 Sep 2022 10:42:41 -0400 Subject: [PATCH 61/85] chore: deps: update FFI --- extern/filecoin-ffi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index c70caaf14..f0a7de699 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit c70caaf14f8ff5b15eb5166093d9e57055e571f8 +Subproject commit f0a7de6991e037a7c355fdb62a0f1ae7fb7324e7 From 66ad2b810237382e2365092ec606004ecd97acb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Mon, 19 Sep 2022 17:32:02 +0200 Subject: [PATCH 62/85] sealing: Avoid panicking in handleUpdateActivating on startup --- storage/pipeline/states_replica_update.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/storage/pipeline/states_replica_update.go b/storage/pipeline/states_replica_update.go index a78d6dee8..9170f5fb5 100644 --- a/storage/pipeline/states_replica_update.go +++ b/storage/pipeline/states_replica_update.go @@ -240,6 +240,10 @@ func (m *Sealing) handleFinalizeReplicaUpdate(ctx statemachine.Context, sector S } func (m *Sealing) handleUpdateActivating(ctx statemachine.Context, sector SectorInfo) error { + if sector.ReplicaUpdateMessage == nil { + return xerrors.Errorf("nil sector.ReplicaUpdateMessage!") + } + try := func() error { mw, err := m.Api.StateWaitMsg(ctx.Context(), *sector.ReplicaUpdateMessage, build.MessageConfidence, api.LookbackNoLimit, true) if err != nil { From 9be712de99c3f2fe9196d0b8324e536b862badbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 20 Sep 2022 17:30:07 +0200 Subject: [PATCH 63/85] deps: Use latest cbor-gen --- chain/vm/cbor_gen.go | 8 ++++---- go.mod | 2 +- go.sum | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/chain/vm/cbor_gen.go b/chain/vm/cbor_gen.go index c2bb4fa79..edcf06560 100644 --- a/chain/vm/cbor_gen.go +++ b/chain/vm/cbor_gen.go @@ -57,7 +57,7 @@ func (t *FvmExecutionTrace) MarshalCBOR(w io.Writer) error { } // t.GasCharges ([]vm.FvmGasCharge) (slice) - if len(t.GasCharges) > cbg.MaxLength { + if len(t.GasCharges) > 1000000000 { return xerrors.Errorf("Slice value in field t.GasCharges was too long") } @@ -71,7 +71,7 @@ func (t *FvmExecutionTrace) MarshalCBOR(w io.Writer) error { } // t.Subcalls ([]vm.FvmExecutionTrace) (slice) - if len(t.Subcalls) > cbg.MaxLength { + if len(t.Subcalls) > 1000000000 { return xerrors.Errorf("Slice value in field t.Subcalls was too long") } @@ -164,7 +164,7 @@ func (t *FvmExecutionTrace) UnmarshalCBOR(r io.Reader) (err error) { return err } - if extra > cbg.MaxLength { + if extra > 1000000000 { return fmt.Errorf("t.GasCharges: array too large (%d)", extra) } @@ -193,7 +193,7 @@ func (t *FvmExecutionTrace) UnmarshalCBOR(r io.Reader) (err error) { return err } - if extra > cbg.MaxLength { + if extra > 1000000000 { return fmt.Errorf("t.Subcalls: array too large (%d)", extra) } diff --git a/go.mod b/go.mod index efd792e98..62404cf5c 100644 --- a/go.mod +++ b/go.mod @@ -135,7 +135,7 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 github.com/urfave/cli/v2 v2.8.1 github.com/whyrusleeping/bencher v0.0.0-20190829221104-bb6607aa8bba - github.com/whyrusleeping/cbor-gen v0.0.0-20220323183124-98fa8256a799 + github.com/whyrusleeping/cbor-gen v0.0.0-20220514204315-f29c37e9c44c github.com/whyrusleeping/ledger-filecoin-go v0.9.1-0.20201010031517-c3dcc1bddce4 github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 github.com/xorcare/golden v0.6.1-0.20191112154924-b87f686d7542 diff --git a/go.sum b/go.sum index 9d9ed1fe3..86ebfd96d 100644 --- a/go.sum +++ b/go.sum @@ -1744,8 +1744,9 @@ github.com/whyrusleeping/cbor-gen v0.0.0-20200812213548-958ddffe352c/go.mod h1:f github.com/whyrusleeping/cbor-gen v0.0.0-20200826160007-0b9f6c5fb163/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/cbor-gen v0.0.0-20210118024343-169e9d70c0c2/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/cbor-gen v0.0.0-20210303213153-67a261a1d291/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= -github.com/whyrusleeping/cbor-gen v0.0.0-20220323183124-98fa8256a799 h1:DOOT2B85S0tHoLGTzV+FakaSSihgRCVwZkjqKQP5L/w= github.com/whyrusleeping/cbor-gen v0.0.0-20220323183124-98fa8256a799/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= +github.com/whyrusleeping/cbor-gen v0.0.0-20220514204315-f29c37e9c44c h1:6VPKXBDRt7mDUyiHx9X8ROnPYFDf3L7OfEuKCI5dZDI= +github.com/whyrusleeping/cbor-gen v0.0.0-20220514204315-f29c37e9c44c/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E= github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f/go.mod h1:p9UJB6dDgdPgMJZs7UjUOdulKyRr9fqkS+6JKAInPy8= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= From 2d82527ee970706c23c39e4722ca9d20396e17bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 20 Sep 2022 17:40:24 +0200 Subject: [PATCH 64/85] cli: Fix compute-state --html --- cli/state.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/state.go b/cli/state.go index 51c3b3911..c88df8da2 100644 --- a/cli/state.go +++ b/cli/state.go @@ -1247,7 +1247,7 @@ var compStateMsg = `

Gas Trace - + {{define "virt" -}} {{- if . -}} +({{.}}) @@ -1259,7 +1259,7 @@ var compStateMsg = ` {{- end}} {{range .GasCharges}} - + {{template "gasC" .}}
NumTotal/Compute/StorageTime TakenLocation
NameTotal/Compute/StorageTime TakenLocation
{{.Num}}{{if .Extra}}:{{.Extra}}{{end}}
{{.Name}}{{if .Extra}}:{{.Extra}}{{end}}{{if PrintTiming}}{{.TimeTaken}}{{end}} From 1f804714794dcc683ca4328851038a0be26710d9 Mon Sep 17 00:00:00 2001 From: Aayush Date: Tue, 20 Sep 2022 11:57:18 -0400 Subject: [PATCH 65/85] Update to go-state-types v0.1.12-beta --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 62404cf5c..dd603bcad 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( github.com/filecoin-project/go-legs v0.4.4 github.com/filecoin-project/go-padreader v0.0.1 github.com/filecoin-project/go-paramfetch v0.0.4 - github.com/filecoin-project/go-state-types v0.1.12-alpha + github.com/filecoin-project/go-state-types v0.1.12-beta github.com/filecoin-project/go-statemachine v1.0.2 github.com/filecoin-project/go-statestore v0.2.0 github.com/filecoin-project/go-storedcounter v0.1.0 diff --git a/go.sum b/go.sum index 86ebfd96d..ee452c5fe 100644 --- a/go.sum +++ b/go.sum @@ -343,8 +343,8 @@ github.com/filecoin-project/go-state-types v0.1.0/go.mod h1:ezYnPf0bNkTsDibL/psS github.com/filecoin-project/go-state-types v0.1.6/go.mod h1:UwGVoMsULoCK+bWjEdd/xLCvLAQFBC7EDT477SKml+Q= github.com/filecoin-project/go-state-types v0.1.8/go.mod h1:UwGVoMsULoCK+bWjEdd/xLCvLAQFBC7EDT477SKml+Q= github.com/filecoin-project/go-state-types v0.1.10/go.mod h1:UwGVoMsULoCK+bWjEdd/xLCvLAQFBC7EDT477SKml+Q= -github.com/filecoin-project/go-state-types v0.1.12-alpha h1:6BbnnrFqCOkiogZd6CTe7Bf1nRKrJDE6CP/I/dqM4C8= -github.com/filecoin-project/go-state-types v0.1.12-alpha/go.mod h1:n/kujdC9JphvYTrmaD1+vJpvDPy/DwzckoMzP0nBKWI= +github.com/filecoin-project/go-state-types v0.1.12-beta h1:QZE00g75shqwhPn0/bZL38sFxVAqnXC7zjmYltRdhxI= +github.com/filecoin-project/go-state-types v0.1.12-beta/go.mod h1:n/kujdC9JphvYTrmaD1+vJpvDPy/DwzckoMzP0nBKWI= github.com/filecoin-project/go-statemachine v0.0.0-20200925024713-05bd7c71fbfe/go.mod h1:FGwQgZAt2Gh5mjlwJUlVB62JeYdo+if0xWxSEfBD9ig= github.com/filecoin-project/go-statemachine v1.0.2 h1:421SSWBk8GIoCoWYYTE/d+qCWccgmRH0uXotXRDjUbc= github.com/filecoin-project/go-statemachine v1.0.2/go.mod h1:jZdXXiHa61n4NmgWFG4w8tnqgvZVHYbJ3yW7+y8bF54= From 8ca1f89ba65d9aacacbd17bcd5e1a51f0d90e64f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 20 Sep 2022 18:19:13 +0200 Subject: [PATCH 66/85] build: Bump version to v1.17.3-dev --- build/openrpc/full.json.gz | Bin 28424 -> 28424 bytes build/openrpc/gateway.json.gz | Bin 5080 -> 5080 bytes build/openrpc/miner.json.gz | Bin 16025 -> 16025 bytes build/openrpc/worker.json.gz | Bin 5260 -> 5260 bytes build/version.go | 2 +- documentation/en/cli-lotus-miner.md | 2 +- documentation/en/cli-lotus-worker.md | 2 +- documentation/en/cli-lotus.md | 2 +- 8 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index a8f16011a9c1b9424393842c633d5ddaabae5853..5e5c0e76275f150057a6da56aa13224ce7191d68 100644 GIT binary patch delta 23 fcmeCU$JlX?aY85K=Z#%uc^s=hHDpX_Wn};Wh#v~3 delta 23 fcmeCU$JlX?aY85K-;G^mc^qP=JpN8;Wn};WgoFxE diff --git a/build/openrpc/gateway.json.gz b/build/openrpc/gateway.json.gz index b6c2778b683eaf2dcc6a58277f1416781343810b..7ba78074e64aed30a9546d5e951a08ced686efa5 100644 GIT binary patch delta 21 dcmcbienWjiJ>%z%4FSR&U*y*xne>%`0RV4#39A4A delta 21 dcmcbienWjiJ>%bv4FSR&0^*B)PWsBg003!d2=M>_ diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 75b4eeaaf87c49d4acc624efd2e3735e7f39a040..8bf6e7c11b13615d891ffdc86a5d6fcb40c29016 100644 GIT binary patch delta 21 ccmbPPJF|8|8{@@|?Ob*onpSpSwZ)hj0AzXxcK`qY delta 21 dcmbPPJF|8|8{@-`?Ob*oeQ#9PYKt*5003)Z2t5D* diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index d73bda90317ed7ea9db5e25e1406858fd053f91b..3db86b03c8086182e3b97a7dbe8b0260795be66f 100644 GIT binary patch delta 21 ccmeCt?9rUi%2>Lw?Vm7*xQMcRz9Az6092O-Q~&?~ delta 21 ccmeCt?9rUi%2>9s?Vm7*XR@+mz9Az609gnI_y7O^ diff --git a/build/version.go b/build/version.go index cc289bfe6..6dd116436 100644 --- a/build/version.go +++ b/build/version.go @@ -37,7 +37,7 @@ func BuildTypeString() string { } // BuildVersion is the local build version -const BuildVersion = "1.17.2-dev" +const BuildVersion = "1.17.3-dev" func UserVersion() string { if os.Getenv("LOTUS_VERSION_IGNORE_COMMIT") == "1" { diff --git a/documentation/en/cli-lotus-miner.md b/documentation/en/cli-lotus-miner.md index 322fe10de..0e43e2e49 100644 --- a/documentation/en/cli-lotus-miner.md +++ b/documentation/en/cli-lotus-miner.md @@ -7,7 +7,7 @@ USAGE: lotus-miner [global options] command [command options] [arguments...] VERSION: - 1.17.2-dev + 1.17.3-dev COMMANDS: init Initialize a lotus miner repo diff --git a/documentation/en/cli-lotus-worker.md b/documentation/en/cli-lotus-worker.md index 920fb3001..266eb619c 100644 --- a/documentation/en/cli-lotus-worker.md +++ b/documentation/en/cli-lotus-worker.md @@ -7,7 +7,7 @@ USAGE: lotus-worker [global options] command [command options] [arguments...] VERSION: - 1.17.2-dev + 1.17.3-dev COMMANDS: run Start lotus worker diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index ad978fd4b..eb57455b5 100644 --- a/documentation/en/cli-lotus.md +++ b/documentation/en/cli-lotus.md @@ -7,7 +7,7 @@ USAGE: lotus [global options] command [command options] [arguments...] VERSION: - 1.17.2-dev + 1.17.3-dev COMMANDS: daemon Start a lotus daemon process From 2bdab5a9006d8bc101d131a1b7d848020869e05e Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Tue, 20 Sep 2022 12:26:56 -0400 Subject: [PATCH 67/85] docs: release template: clarify location of version.go --- documentation/misc/RELEASE_ISSUE_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/misc/RELEASE_ISSUE_TEMPLATE.md b/documentation/misc/RELEASE_ISSUE_TEMPLATE.md index 5a4884669..92c223b36 100644 --- a/documentation/misc/RELEASE_ISSUE_TEMPLATE.md +++ b/documentation/misc/RELEASE_ISSUE_TEMPLATE.md @@ -21,7 +21,7 @@ First steps: - [ ] Fork a new branch (`release/vX.Y.Z`) from `master` and make any further release related changes to this branch. If any "non-trivial" changes get added to the release, uncheck all the checkboxes and return to this stage. - - [ ] Bump the version in `version.go` in the `master` branch to `vX.Y.(Z+1)-dev` (bump from feature release) or `vX.(Y+1).0-dev` (bump from mandatory release) + - [ ] Bump the version in `build/version.go` in the `master` branch to `vX.Y.(Z+1)-dev` (bump from feature release) or `vX.(Y+1).0-dev` (bump from mandatory release) Prepping an RC: From 788b864362bba5cfee66e1fbcf709c4afdfd9d45 Mon Sep 17 00:00:00 2001 From: Phi Date: Wed, 21 Sep 2022 11:42:26 +0200 Subject: [PATCH 68/85] Update Go-badge in readme Update Go-badge in readme to reflect that go-version 1.18.1 or higher is needed. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bab033e72..23f3eb2d9 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ - +

From f5048ec61cb525c93e419309694d69ffa7e8291f Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Wed, 21 Sep 2022 12:20:26 -0400 Subject: [PATCH 69/85] Don't publish new homebrew releases for RC builds --- .circleci/config.yml | 7 +++++-- .circleci/template.yml | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 53ac6bce0..2dc6779a0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1114,12 +1114,15 @@ workflows: - /.*/ tags: only: - - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - /^v\d+\.\d+\.\d+$/ - build-macos: filters: branches: only: - /^release\/v\d+\.\d+\.\d+(-rc\d+)?$/ + tags: + only: + - /^v\d+\.\d+\.\d+-rc\d+$/ - build-appimage: filters: branches: @@ -1250,4 +1253,4 @@ workflows: only: - master jobs: - - publish-packer-snap \ No newline at end of file + - publish-packer-snap diff --git a/.circleci/template.yml b/.circleci/template.yml index 9843463df..6e7a44364 100644 --- a/.circleci/template.yml +++ b/.circleci/template.yml @@ -854,12 +854,15 @@ workflows: - /.*/ tags: only: - - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - /^v\d+\.\d+\.\d+$/ - build-macos: filters: branches: only: - /^release\/v\d+\.\d+\.\d+(-rc\d+)?$/ + tags: + only: + - /^v\d+\.\d+\.\d+-rc\d+$/ - build-appimage: filters: branches: @@ -990,4 +993,4 @@ workflows: only: - master jobs: - - publish-packer-snap \ No newline at end of file + - publish-packer-snap From a1e08e56640e49fc79a57a0ce9c17989ac295b4d Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Wed, 21 Sep 2022 15:29:34 -0400 Subject: [PATCH 70/85] Fix a calculation error in price-per-byte in retrievals --- node/impl/client/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/impl/client/client.go b/node/impl/client/client.go index cefdfbc82..745a530fd 100644 --- a/node/impl/client/client.go +++ b/node/impl/client/client.go @@ -837,7 +837,7 @@ func (a *API) doRetrieval(ctx context.Context, order api.RetrievalOrder, sel dat return 0, xerrors.Errorf("cannot make retrieval deal for zero bytes") } - ppb := types.BigDiv(order.Total, types.NewInt(order.Size)) + ppb := types.BigDiv(big.Sub(order.Total, order.UnsealPrice), types.NewInt(order.Size)) params, err := rm.NewParamsV1(ppb, order.PaymentInterval, order.PaymentIntervalIncrease, sel, order.Piece, order.UnsealPrice) if err != nil { From 7a2bc47486accd1b1f55069b4f1db3781d7710b0 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Wed, 21 Sep 2022 17:16:24 -0400 Subject: [PATCH 71/85] fix: test: flaky TestDeadlineToggling around nulls (#9354) --- itests/deadlines_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/itests/deadlines_test.go b/itests/deadlines_test.go index a00c33b68..18f68150b 100644 --- a/itests/deadlines_test.go +++ b/itests/deadlines_test.go @@ -185,7 +185,7 @@ func TestDeadlineToggling(t *testing.T) { require.NoError(t, err) // cron happened on the same epoch some other condition would have happened - if di.Open == ts.Height() { + if di.Open <= ts.Height() { act, err := mst.DeadlineCronActive() require.NoError(t, err) require.Equal(t, activeIfCron, act) From 906d8bffdff908bd1a8811b9bb159a5ff0f7f0d9 Mon Sep 17 00:00:00 2001 From: Aayush Date: Thu, 22 Sep 2022 11:42:06 -0400 Subject: [PATCH 72/85] fix: build: set PropagationDelaySecs correctly --- build/params_calibnet.go | 9 ++++++--- build/params_mainnet.go | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/build/params_calibnet.go b/build/params_calibnet.go index cdf8dc6e9..faedd3ea8 100644 --- a/build/params_calibnet.go +++ b/build/params_calibnet.go @@ -89,13 +89,16 @@ func init() { Devnet = true + // NOTE: DO NOT change this unless you REALLY know what you're doing. This is not consensus critical, however, + //set this value too high may impacts your block submission; set this value too low may cause you miss + //parent tipsets for blocking forming and mining. if len(os.Getenv("PROPAGATION_DELAY_SECS")) != 0 { - PropagationDelaySecs, err := strconv.ParseUint(os.Getenv("PROPAGATION_DELAY_SECS"), 10, 64) + pds, err := strconv.ParseUint(os.Getenv("PROPAGATION_DELAY_SECS"), 10, 64) if err != nil { - PropagationDelaySecs = uint64(10) log.Warnw("Error setting PROPAGATION_DELAY_SECS, %v, proceed with default value %s", err, PropagationDelaySecs) } else { + PropagationDelaySecs = pds log.Warnw(" !!WARNING!! propagation delay is set to be %s second, "+ "this value impacts your message republish interval and block forming - monitor with caution!!", PropagationDelaySecs) } @@ -107,7 +110,7 @@ func init() { const BlockDelaySecs = uint64(builtin2.EpochDurationSeconds) -const PropagationDelaySecs = uint64(10) +var PropagationDelaySecs = uint64(10) // BootstrapPeerThreshold is the minimum number peers we need to track for a sync worker to start const BootstrapPeerThreshold = 4 diff --git a/build/params_mainnet.go b/build/params_mainnet.go index 45ceb89bd..c3864ffd2 100644 --- a/build/params_mainnet.go +++ b/build/params_mainnet.go @@ -103,12 +103,12 @@ func init() { //set this value too high may impacts your block submission; set this value too low may cause you miss //parent tipsets for blocking forming and mining. if len(os.Getenv("PROPAGATION_DELAY_SECS")) != 0 { - PropagationDelaySecs, err := strconv.ParseUint(os.Getenv("PROPAGATION_DELAY_SECS"), 10, 64) + pds, err := strconv.ParseUint(os.Getenv("PROPAGATION_DELAY_SECS"), 10, 64) if err != nil { - PropagationDelaySecs = uint64(10) log.Warnw("Error setting PROPAGATION_DELAY_SECS, %v, proceed with default value %s", err, PropagationDelaySecs) } else { + PropagationDelaySecs = pds log.Warnw(" !!WARNING!! propagation delay is set to be %s second, "+ "this value impacts your message republish interval and block forming - monitor with caution!!", PropagationDelaySecs) } From 2ec4ac3c0fb9cd0d796e427daab2b586aed9d08a Mon Sep 17 00:00:00 2001 From: jennijuju Date: Thu, 22 Sep 2022 02:56:08 -0400 Subject: [PATCH 73/85] use offline mode instead --- cmd/lotus-shed/msig.go | 177 +++++++++++++++-------------------------- 1 file changed, 63 insertions(+), 114 deletions(-) diff --git a/cmd/lotus-shed/msig.go b/cmd/lotus-shed/msig.go index d70634e72..b5cc9fea2 100644 --- a/cmd/lotus-shed/msig.go +++ b/cmd/lotus-shed/msig.go @@ -4,174 +4,123 @@ import ( "context" "encoding/json" "fmt" - "strings" + "io" + + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/multisig" + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/consensus/filcns" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/node/repo" + cbor "github.com/ipfs/go-ipld-cbor" "github.com/ipfs/go-cid" "github.com/urfave/cli/v2" - "golang.org/x/xerrors" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/network" - - "github.com/filecoin-project/lotus/api/v0api" - "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" - cliutil "github.com/filecoin-project/lotus/cli/util" ) -var GetFullNodeAPI = cliutil.GetFullNodeAPI -var ReqContext = cliutil.ReqContext - -func LoadTipSet(ctx context.Context, cctx *cli.Context, api v0api.FullNode) (*types.TipSet, error) { - tss := cctx.String("tipset") - if tss == "" { - return api.ChainHead(ctx) - } - - return ParseTipSetRef(ctx, api, tss) -} - -func ParseTipSetRef(ctx context.Context, api v0api.FullNode, tss string) (*types.TipSet, error) { - if tss[0] == '@' { - if tss == "@head" { - return api.ChainHead(ctx) - } - - var h uint64 - if _, err := fmt.Sscanf(tss, "@%d", &h); err != nil { - return nil, xerrors.Errorf("parsing height tipset ref: %w", err) - } - - return api.ChainGetTipSetByHeight(ctx, abi.ChainEpoch(h), types.EmptyTSK) - } - - cids, err := ParseTipSetString(tss) - if err != nil { - return nil, err - } - - if len(cids) == 0 { - return nil, nil - } - - k := types.NewTipSetKey(cids...) - ts, err := api.ChainGetTipSet(ctx, k) - if err != nil { - return nil, err - } - - return ts, nil -} - -func ParseTipSetString(ts string) ([]cid.Cid, error) { - strs := strings.Split(ts, ",") - - var cids []cid.Cid - for _, s := range strs { - c, err := cid.Parse(strings.TrimSpace(s)) - if err != nil { - return nil, err - } - cids = append(cids, c) - } - - return cids, nil -} - type msigBriefInfo struct { ID address.Address Signer interface{} Balance abi.TokenAmount - Threshold float64 + Threshold uint64 } var msigCmd = &cli.Command{ - Name: "multisig", - Usage: "utils for multisig actors", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "tipset", - Usage: "specify tipset to call method on (pass comma separated array of cids)", - }, - }, + Name: "msig", Subcommands: []*cli.Command{ multisigGetAllCmd, }, } var multisigGetAllCmd = &cli.Command{ - Name: "all", - Usage: "get all multisig actor on chain with id, siigners, threshold and balance", + Name: "all", + Usage: "get all multisig actor on chain with id, siigners, threshold and balance at a tipset", + ArgsUsage: "[state root]", Flags: []cli.Flag{ - &cli.UintFlag{ - Name: "network-version", - Value: uint(build.NewestNetworkVersion), + &cli.StringFlag{ + Name: "repo", + Value: "~/.lotus", }, }, Action: func(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err + ctx := context.TODO() + if !cctx.Args().Present() { + return fmt.Errorf("must pass state root") } - defer closer() - ctx := ReqContext(cctx) + sroot, err := cid.Decode(cctx.Args().First()) + if err != nil { + return fmt.Errorf("failed to parse input: %w", err) + } - ts, err := LoadTipSet(ctx, cctx, api) + fsrepo, err := repo.NewFS(cctx.String("repo")) if err != nil { return err } - actors, err := api.StateListActors(ctx, ts.Key()) + lkrepo, err := fsrepo.Lock(repo.FullNode) if err != nil { return err } - nv := network.Version(cctx.Uint64("network-version")) - codeCids, err := api.StateActorCodeCIDs(ctx, nv) + defer lkrepo.Close() //nolint:errcheck + + bs, err := lkrepo.Blockstore(ctx, repo.UniversalBlockstore) + if err != nil { + return fmt.Errorf("failed to open blockstore: %w", err) + } + + defer func() { + if c, ok := bs.(io.Closer); ok { + if err := c.Close(); err != nil { + log.Warnf("failed to close blockstore: %s", err) + } + } + }() + + mds, err := lkrepo.Datastore(context.Background(), "/metadata") if err != nil { return err } - msigCid, exists := codeCids["multisig"] - if !exists { - return xerrors.Errorf("bad code cid key") + cs := store.NewChainStore(bs, bs, mds, filcns.Weight, nil) + defer cs.Close() //nolint:errcheck + + cst := cbor.NewCborStore(bs) + store := adt.WrapStore(ctx, cst) + + tree, err := state.LoadStateTree(cst, sroot) + if err != nil { + return err } var msigActorsInfo []msigBriefInfo - for _, actor := range actors { - - act, err := api.StateGetActor(ctx, actor, ts.Key()) - if err != nil { - return err - } - - if act.Code == msigCid { - - actorState, err := api.StateReadState(ctx, actor, ts.Key()) + err = tree.ForEach(func(addr address.Address, act *types.Actor) error { + if builtin.IsMultisigActor(act.Code) { + ms, err := multisig.Load(store, act) if err != nil { return err } - stateI, ok := actorState.State.(map[string]interface{}) - if !ok { - return xerrors.Errorf("fail to map msig state") - } - - signersI, _ := stateI["Signers"] - signers := signersI.([]interface{}) - thresholdI, _ := stateI["NumApprovalsThreshold"] - threshold := thresholdI.(float64) + signers, _ := ms.Signers() + threshold, _ := ms.Threshold() info := msigBriefInfo{ - ID: actor, + ID: addr, Signer: signers, - Balance: actorState.Balance, + Balance: act.Balance, Threshold: threshold, } msigActorsInfo = append(msigActorsInfo, info) + } - } + return nil + }) out, err := json.MarshalIndent(msigActorsInfo, "", " ") if err != nil { return err From aea9c535e0e0980aaee79db6c6baac7b0df4cf3e Mon Sep 17 00:00:00 2001 From: Jennifer Wang Date: Thu, 22 Sep 2022 13:12:12 -0400 Subject: [PATCH 74/85] fix typo, add wrapper on error and make jen --- cmd/lotus-shed/msig.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/cmd/lotus-shed/msig.go b/cmd/lotus-shed/msig.go index b5cc9fea2..66e4885c3 100644 --- a/cmd/lotus-shed/msig.go +++ b/cmd/lotus-shed/msig.go @@ -6,22 +6,21 @@ import ( "fmt" "io" - "github.com/filecoin-project/lotus/chain/actors/builtin" - "github.com/filecoin-project/lotus/chain/actors/builtin/multisig" - - "github.com/filecoin-project/lotus/chain/actors/adt" - "github.com/filecoin-project/lotus/chain/consensus/filcns" - "github.com/filecoin-project/lotus/chain/state" - "github.com/filecoin-project/lotus/chain/store" - "github.com/filecoin-project/lotus/node/repo" - cbor "github.com/ipfs/go-ipld-cbor" - "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" "github.com/urfave/cli/v2" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/multisig" + "github.com/filecoin-project/lotus/chain/consensus/filcns" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/node/repo" ) type msigBriefInfo struct { @@ -40,7 +39,7 @@ var msigCmd = &cli.Command{ var multisigGetAllCmd = &cli.Command{ Name: "all", - Usage: "get all multisig actor on chain with id, siigners, threshold and balance at a tipset", + Usage: "get all multisig actor on chain with id, signers, threshold and balance at a tipset", ArgsUsage: "[state root]", Flags: []cli.Flag{ &cli.StringFlag{ @@ -105,7 +104,8 @@ var multisigGetAllCmd = &cli.Command{ if builtin.IsMultisigActor(act.Code) { ms, err := multisig.Load(store, act) if err != nil { - return err + return fmt.Errorf("load msig failed %v", err) + } signers, _ := ms.Signers() From 63a02a6c74dd5afb3ea706930446299e70e463b3 Mon Sep 17 00:00:00 2001 From: Aayush Date: Wed, 21 Sep 2022 19:39:00 -0400 Subject: [PATCH 75/85] fix: test: flaky TestDeadlineToggling --- itests/deadlines_test.go | 61 ++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/itests/deadlines_test.go b/itests/deadlines_test.go index 18f68150b..472e66abc 100644 --- a/itests/deadlines_test.go +++ b/itests/deadlines_test.go @@ -64,7 +64,7 @@ func TestDeadlineToggling(t *testing.T) { //stm: @CHAIN_INCOMING_HANDLE_INCOMING_BLOCKS_001, @CHAIN_INCOMING_VALIDATE_BLOCK_PUBSUB_001, @CHAIN_INCOMING_VALIDATE_MESSAGE_PUBSUB_001 //stm: @MINER_SECTOR_LIST_001 - //kit.Expensive(t) + kit.Expensive(t) kit.QuietMiningLogs() @@ -160,7 +160,7 @@ func TestDeadlineToggling(t *testing.T) { build.Clock.Sleep(blocktime) } - checkMiner := func(ma address.Address, power abi.StoragePower, active, activeIfCron bool, tsk types.TipSetKey) { + checkMiner := func(ma address.Address, power abi.StoragePower, active bool, tsk types.TipSetKey) { //stm: @CHAIN_STATE_MINER_POWER_001 p, err := client.StateMinerPower(ctx, ma, tsk) require.NoError(t, err) @@ -178,21 +178,6 @@ func TestDeadlineToggling(t *testing.T) { act, err := mst.DeadlineCronActive() require.NoError(t, err) - if tsk != types.EmptyTSK { - ts, err := client.ChainGetTipSet(ctx, tsk) - require.NoError(t, err) - di, err := mst.DeadlineInfo(ts.Height()) - require.NoError(t, err) - - // cron happened on the same epoch some other condition would have happened - if di.Open <= ts.Height() { - act, err := mst.DeadlineCronActive() - require.NoError(t, err) - require.Equal(t, activeIfCron, act) - return - } - } - require.Equal(t, active, act) } @@ -200,7 +185,7 @@ func TestDeadlineToggling(t *testing.T) { { uts, err := client.ChainGetTipSetByHeight(ctx, upgradeH+2, types.EmptyTSK) require.NoError(t, err) - checkMiner(maddrB, types.NewInt(0), true, true, uts.Key()) + checkMiner(maddrB, types.NewInt(0), true, uts.Key()) } //stm: @CHAIN_STATE_NETWORK_VERSION_001 @@ -218,19 +203,19 @@ func TestDeadlineToggling(t *testing.T) { require.NoError(t, err) // first round of miner checks - checkMiner(maddrA, types.NewInt(uint64(ssz)*kit.DefaultPresealsPerBootstrapMiner), true, true, types.EmptyTSK) - checkMiner(maddrC, types.NewInt(uint64(ssz)*sectorsC), true, true, types.EmptyTSK) + checkMiner(maddrA, types.NewInt(uint64(ssz)*kit.DefaultPresealsPerBootstrapMiner), true, types.EmptyTSK) + checkMiner(maddrC, types.NewInt(uint64(ssz)*sectorsC), true, types.EmptyTSK) - checkMiner(maddrB, types.NewInt(0), false, false, types.EmptyTSK) - checkMiner(maddrD, types.NewInt(0), false, false, types.EmptyTSK) - checkMiner(maddrE, types.NewInt(0), false, false, types.EmptyTSK) + checkMiner(maddrB, types.NewInt(0), false, types.EmptyTSK) + checkMiner(maddrD, types.NewInt(0), false, types.EmptyTSK) + checkMiner(maddrE, types.NewInt(0), false, types.EmptyTSK) // pledge sectors on minerB/minerD, stop post on minerC minerB.PledgeSectors(ctx, sectorsB, 0, nil) - checkMiner(maddrB, types.NewInt(0), true, true, types.EmptyTSK) + checkMiner(maddrB, types.NewInt(0), true, types.EmptyTSK) minerD.PledgeSectors(ctx, sectorsD, 0, nil) - checkMiner(maddrD, types.NewInt(0), true, true, types.EmptyTSK) + checkMiner(maddrD, types.NewInt(0), true, types.EmptyTSK) minerC.StorageMiner.(*impl.StorageMinerAPI).IStorageMgr.(*mock.SectorMgr).Fail() @@ -281,7 +266,7 @@ func TestDeadlineToggling(t *testing.T) { build.Clock.Sleep(blocktime) } - checkMiner(maddrE, types.NewInt(0), true, true, types.EmptyTSK) + checkMiner(maddrE, types.NewInt(0), true, types.EmptyTSK) // go through rest of the PP for { @@ -296,11 +281,11 @@ func TestDeadlineToggling(t *testing.T) { } // second round of miner checks - checkMiner(maddrA, types.NewInt(uint64(ssz)*kit.DefaultPresealsPerBootstrapMiner), true, true, types.EmptyTSK) - checkMiner(maddrC, types.NewInt(0), true, true, types.EmptyTSK) - checkMiner(maddrB, types.NewInt(uint64(ssz)*sectorsB), true, true, types.EmptyTSK) - checkMiner(maddrD, types.NewInt(uint64(ssz)*sectorsD), true, true, types.EmptyTSK) - checkMiner(maddrE, types.NewInt(0), false, false, types.EmptyTSK) + checkMiner(maddrA, types.NewInt(uint64(ssz)*kit.DefaultPresealsPerBootstrapMiner), true, types.EmptyTSK) + checkMiner(maddrC, types.NewInt(0), true, types.EmptyTSK) + checkMiner(maddrB, types.NewInt(uint64(ssz)*sectorsB), true, types.EmptyTSK) + checkMiner(maddrD, types.NewInt(uint64(ssz)*sectorsD), true, types.EmptyTSK) + checkMiner(maddrE, types.NewInt(0), false, types.EmptyTSK) // disable post on minerB minerB.StorageMiner.(*impl.StorageMinerAPI).IStorageMgr.(*mock.SectorMgr).Fail() @@ -353,8 +338,10 @@ func TestDeadlineToggling(t *testing.T) { require.NoError(t, err) require.Equal(t, exitcode.Ok, r.Receipt.ExitCode) - // assert inactive if the message landed in the tipset we run cron in - checkMiner(maddrD, types.NewInt(0), true, false, r.TipSet) + // assert miner has no power + p, err := client.StateMinerPower(ctx, maddrD, r.TipSet) + require.NoError(t, err) + require.True(t, p.MinerPower.RawBytePower.IsZero()) } // go through another PP @@ -369,8 +356,8 @@ func TestDeadlineToggling(t *testing.T) { build.Clock.Sleep(blocktime) } - checkMiner(maddrA, types.NewInt(uint64(ssz)*kit.DefaultPresealsPerBootstrapMiner), true, true, types.EmptyTSK) - checkMiner(maddrC, types.NewInt(0), true, true, types.EmptyTSK) - checkMiner(maddrB, types.NewInt(0), true, true, types.EmptyTSK) - checkMiner(maddrD, types.NewInt(0), false, false, types.EmptyTSK) + checkMiner(maddrA, types.NewInt(uint64(ssz)*kit.DefaultPresealsPerBootstrapMiner), true, types.EmptyTSK) + checkMiner(maddrC, types.NewInt(0), true, types.EmptyTSK) + checkMiner(maddrB, types.NewInt(0), true, types.EmptyTSK) + checkMiner(maddrD, types.NewInt(0), false, types.EmptyTSK) } From 971cdf51a3c104ffba38c74dc91073fd9bb2db64 Mon Sep 17 00:00:00 2001 From: Aayush Date: Fri, 23 Sep 2022 10:53:17 -0400 Subject: [PATCH 76/85] fix: miner: init miner's with 32GiB sectors by default --- cmd/lotus-miner/init.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/cmd/lotus-miner/init.go b/cmd/lotus-miner/init.go index ce69fad5b..1aa6bb59b 100644 --- a/cmd/lotus-miner/init.go +++ b/cmd/lotus-miner/init.go @@ -90,7 +90,6 @@ var initCmd = &cli.Command{ &cli.StringFlag{ Name: "sector-size", Usage: "specify sector size to use", - Value: units.BytesSize(float64(abi.SectorSize(2048))), }, &cli.StringSliceFlag{ Name: "pre-sealed-sectors", @@ -129,11 +128,18 @@ var initCmd = &cli.Command{ Action: func(cctx *cli.Context) error { log.Info("Initializing lotus miner") - sectorSizeInt, err := units.RAMInBytes(cctx.String("sector-size")) + ssize, err := abi.RegisteredSealProof_StackedDrg32GiBV1.SectorSize() if err != nil { - return err + return xerrors.Errorf("failed to calculate default sector size: %w", err) + } + + if cctx.IsSet("sector-size") { + sectorSizeInt, err := units.RAMInBytes(cctx.String("sector-size")) + if err != nil { + return err + } + ssize = abi.SectorSize(sectorSizeInt) } - ssize := abi.SectorSize(sectorSizeInt) gasPrice, err := types.BigFromString(cctx.String("gas-premium")) if err != nil { From 54a4f14d0933c7ca4481c4e7aa44a07dfcc77787 Mon Sep 17 00:00:00 2001 From: Aayush Date: Fri, 23 Sep 2022 12:23:42 -0400 Subject: [PATCH 77/85] feat: dealpublisher: check for duplicate deals before adding --- markets/storageadapter/dealpublisher.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/markets/storageadapter/dealpublisher.go b/markets/storageadapter/dealpublisher.go index c2864bdf9..419bc6a04 100644 --- a/markets/storageadapter/dealpublisher.go +++ b/markets/storageadapter/dealpublisher.go @@ -204,6 +204,26 @@ func (p *DealPublisher) processNewDeal(pdeal *pendingDeal) { return } + pdealPropCid, err := pdeal.deal.Proposal.Cid() + if err != nil { + log.Warn("failed to calculate proposal CID for new pending Deal with piece cid %s", pdeal.deal.Proposal.PieceCID) + return + } + + // Sanity check that new deal isn't already in the queue + for _, pd := range p.pending { + pdPropCid, err := pd.deal.Proposal.Cid() + if err != nil { + log.Warn("failed to calculate proposal CID for pending Deal already in publish queue with piece cid %s", pd.deal.Proposal.PieceCID) + return + } + + if pdPropCid.Equals(pdealPropCid) { + log.Warn("tried to process new pending deal with piece CID %s that is already in publish queue; returning", pdeal.deal.Proposal.PieceCID) + return + } + } + // Add the new deal to the queue p.pending = append(p.pending, pdeal) log.Infof("add deal with piece CID %s to publish deals queue - %d deals in queue (max queue size %d)", From 1ae896e73f4abac1091859b0e20f4bf2fd5ae9ae Mon Sep 17 00:00:00 2001 From: Aayush Date: Fri, 23 Sep 2022 12:43:00 -0400 Subject: [PATCH 78/85] deflake TestRetryDealsNoFunds: increase wait 10x --- itests/deals_retry_deal_no_funds_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/itests/deals_retry_deal_no_funds_test.go b/itests/deals_retry_deal_no_funds_test.go index 8d5a02172..650b2436e 100644 --- a/itests/deals_retry_deal_no_funds_test.go +++ b/itests/deals_retry_deal_no_funds_test.go @@ -101,7 +101,7 @@ func testDealsRetryLackOfFunds(t *testing.T, publishStorageAccountFunds abi.Toke propcid := *deal go func() { - time.Sleep(3 * time.Second) + time.Sleep(30 * time.Second) kit.SendFunds(ctx, t, minerFullNode, publishStorageDealKey.Address, types.FromFil(1)) From 72b676843bef55824938c277cf4c66ad8b0f869e Mon Sep 17 00:00:00 2001 From: Aayush Date: Fri, 23 Sep 2022 11:20:02 -0400 Subject: [PATCH 79/85] fix: miner: init: only get sector-size once --- cmd/lotus-miner/init.go | 11 +++-------- documentation/en/cli-lotus-miner.md | 2 +- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/cmd/lotus-miner/init.go b/cmd/lotus-miner/init.go index 1aa6bb59b..f0f84eb67 100644 --- a/cmd/lotus-miner/init.go +++ b/cmd/lotus-miner/init.go @@ -547,7 +547,7 @@ func storageMinerInit(ctx context.Context, cctx *cli.Context, api v1api.FullNode addr = a } else { - a, err := createStorageMiner(ctx, api, peerid, gasPrice, cctx) + a, err := createStorageMiner(ctx, api, ssize, peerid, gasPrice, cctx) if err != nil { return xerrors.Errorf("creating miner failed: %w", err) } @@ -627,7 +627,7 @@ func configureStorageMiner(ctx context.Context, api v1api.FullNode, addr address return nil } -func createStorageMiner(ctx context.Context, api v1api.FullNode, peerid peer.ID, gasPrice types.BigInt, cctx *cli.Context) (address.Address, error) { +func createStorageMiner(ctx context.Context, api v1api.FullNode, ssize abi.SectorSize, peerid peer.ID, gasPrice types.BigInt, cctx *cli.Context) (address.Address, error) { var err error var owner address.Address if cctx.String("owner") != "" { @@ -639,11 +639,6 @@ func createStorageMiner(ctx context.Context, api v1api.FullNode, peerid peer.ID, return address.Undef, err } - ssize, err := units.RAMInBytes(cctx.String("sector-size")) - if err != nil { - return address.Undef, fmt.Errorf("failed to parse sector size: %w", err) - } - worker := owner if cctx.String("worker") != "" { worker, err = address.NewFromString(cctx.String("worker")) @@ -718,7 +713,7 @@ func createStorageMiner(ctx context.Context, api v1api.FullNode, peerid peer.ID, } // Note: the correct thing to do would be to call SealProofTypeFromSectorSize if actors version is v3 or later, but this still works - spt, err := miner.WindowPoStProofTypeFromSectorSize(abi.SectorSize(ssize)) + spt, err := miner.WindowPoStProofTypeFromSectorSize(ssize) if err != nil { return address.Undef, xerrors.Errorf("getting post proof type: %w", err) } diff --git a/documentation/en/cli-lotus-miner.md b/documentation/en/cli-lotus-miner.md index 0e43e2e49..a10cb45ba 100644 --- a/documentation/en/cli-lotus-miner.md +++ b/documentation/en/cli-lotus-miner.md @@ -71,7 +71,7 @@ OPTIONS: --create-worker-key create separate worker key (default: false) --worker value, -w value worker key to use (overrides --create-worker-key) --owner value, -o value owner key to use - --sector-size value specify sector size to use (default: "2KiB") + --sector-size value specify sector size to use --pre-sealed-sectors value specify set of presealed sectors for starting as a genesis miner (accepts multiple inputs) --pre-sealed-metadata value specify the metadata file for the presealed sectors --nosync don't check full-node sync status (default: false) From 27880ece2bd864770213a91df85c014784d7dc59 Mon Sep 17 00:00:00 2001 From: Aayush Date: Mon, 18 Jul 2022 12:36:51 -0400 Subject: [PATCH 80/85] feat: support typed errors over RPC --- api/api_errors.go | 29 +++++++++++++ api/client/client.go | 13 +++--- build/params_shared_vals.go | 5 ++- chain/messagepool/repub.go | 2 +- chain/messagepool/selection.go | 4 +- chain/store/basefee_test.go | 2 +- .../simulation/blockbuilder/blockbuilder.go | 9 ++-- cmd/lotus-wallet/main.go | 2 +- cmd/lotus-worker/sealworker/rpc.go | 2 +- cmd/lotus/daemon.go | 5 ++- gateway/handler.go | 2 +- itests/api_test.go | 43 +++++++++++++++++++ itests/paych_cli_test.go | 16 +++++++ node/impl/full/gas.go | 6 ++- node/impl/full/state.go | 7 ++- node/rpc.go | 4 +- 16 files changed, 127 insertions(+), 24 deletions(-) create mode 100644 api/api_errors.go diff --git a/api/api_errors.go b/api/api_errors.go new file mode 100644 index 000000000..953cf63bf --- /dev/null +++ b/api/api_errors.go @@ -0,0 +1,29 @@ +package api + +import ( + "github.com/filecoin-project/go-jsonrpc" +) + +const ( + EOutOfGas = iota + jsonrpc.FirstUserCode + EActorNotFound +) + +type ErrOutOfGas struct{} + +func (e ErrOutOfGas) Error() string { + return "call ran out of gas" +} + +type ErrActorNotFound struct{} + +func (e ErrActorNotFound) Error() string { + return "actor not found" +} + +var RPCErrors = jsonrpc.NewErrors() + +func init() { + RPCErrors.Register(EOutOfGas, new(ErrOutOfGas)) + RPCErrors.Register(EActorNotFound, new(ErrActorNotFound)) +} diff --git a/api/client/client.go b/api/client/client.go index 669c58f27..32583097e 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -19,7 +19,7 @@ import ( func NewCommonRPCV0(ctx context.Context, addr string, requestHeader http.Header) (api.CommonNet, jsonrpc.ClientCloser, error) { var res v0api.CommonNetStruct closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin", - api.GetInternalStructs(&res), requestHeader) + api.GetInternalStructs(&res), requestHeader, jsonrpc.WithErrors(api.RPCErrors)) return &res, closer, err } @@ -29,7 +29,7 @@ func NewFullNodeRPCV0(ctx context.Context, addr string, requestHeader http.Heade var res v0api.FullNodeStruct closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin", - api.GetInternalStructs(&res), requestHeader) + api.GetInternalStructs(&res), requestHeader, jsonrpc.WithErrors(api.RPCErrors)) return &res, closer, err } @@ -38,7 +38,7 @@ func NewFullNodeRPCV0(ctx context.Context, addr string, requestHeader http.Heade func NewFullNodeRPCV1(ctx context.Context, addr string, requestHeader http.Header) (api.FullNode, jsonrpc.ClientCloser, error) { var res v1api.FullNodeStruct closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin", - api.GetInternalStructs(&res), requestHeader) + api.GetInternalStructs(&res), requestHeader, jsonrpc.WithErrors(api.RPCErrors)) return &res, closer, err } @@ -72,6 +72,7 @@ func NewStorageMinerRPCV0(ctx context.Context, addr string, requestHeader http.H api.GetInternalStructs(&res), requestHeader, append([]jsonrpc.Option{ rpcenc.ReaderParamEncoder(pushUrl), + jsonrpc.WithErrors(api.RPCErrors), }, opts...)...) return &res, closer, err @@ -90,6 +91,7 @@ func NewWorkerRPCV0(ctx context.Context, addr string, requestHeader http.Header) rpcenc.ReaderParamEncoder(pushUrl), jsonrpc.WithNoReconnect(), jsonrpc.WithTimeout(30*time.Second), + jsonrpc.WithErrors(api.RPCErrors), ) return &res, closer, err @@ -101,7 +103,7 @@ func NewGatewayRPCV1(ctx context.Context, addr string, requestHeader http.Header closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin", api.GetInternalStructs(&res), requestHeader, - opts..., + append(opts, jsonrpc.WithErrors(api.RPCErrors))..., ) return &res, closer, err @@ -113,7 +115,7 @@ func NewGatewayRPCV0(ctx context.Context, addr string, requestHeader http.Header closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin", api.GetInternalStructs(&res), requestHeader, - opts..., + append(opts, jsonrpc.WithErrors(api.RPCErrors))..., ) return &res, closer, err @@ -124,6 +126,7 @@ func NewWalletRPCV0(ctx context.Context, addr string, requestHeader http.Header) closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin", api.GetInternalStructs(&res), requestHeader, + jsonrpc.WithErrors(api.RPCErrors), ) return &res, closer, err diff --git a/build/params_shared_vals.go b/build/params_shared_vals.go index a77feb9d8..f7cc2374e 100644 --- a/build/params_shared_vals.go +++ b/build/params_shared_vals.go @@ -118,8 +118,9 @@ const VerifSigCacheSize = 32000 // TODO: If this is gonna stay, it should move to specs-actors const BlockMessageLimit = 10000 -const BlockGasLimit = 10_000_000_000 -const BlockGasTarget = BlockGasLimit / 2 +var BlockGasLimit = int64(10_000_000_000) +var BlockGasTarget = BlockGasLimit / 2 + const BaseFeeMaxChangeDenom = 8 // 12.5% const InitialBaseFee = 100e6 const MinimumBaseFee = 100 diff --git a/chain/messagepool/repub.go b/chain/messagepool/repub.go index c4f1b26e3..9a1e19b60 100644 --- a/chain/messagepool/repub.go +++ b/chain/messagepool/repub.go @@ -79,7 +79,7 @@ func (mp *MessagePool) republishPendingMessages(ctx context.Context) error { return chains[i].Before(chains[j]) }) - gasLimit := int64(build.BlockGasLimit) + gasLimit := build.BlockGasLimit minGas := int64(gasguess.MinGas) var msgs []*types.SignedMessage loop: diff --git a/chain/messagepool/selection.go b/chain/messagepool/selection.go index 0d93f2af9..e84962869 100644 --- a/chain/messagepool/selection.go +++ b/chain/messagepool/selection.go @@ -258,7 +258,7 @@ func (mp *MessagePool) selectMessagesOptimal(ctx context.Context, curTs, ts *typ nextChain := 0 partitions := make([][]*msgChain, MaxBlocks) for i := 0; i < MaxBlocks && nextChain < len(chains); i++ { - gasLimit := int64(build.BlockGasLimit) + gasLimit := build.BlockGasLimit msgLimit := build.BlockMessageLimit for nextChain < len(chains) { chain := chains[nextChain] @@ -590,7 +590,7 @@ func (mp *MessagePool) selectPriorityMessages(ctx context.Context, pending map[a mpCfg := mp.getConfig() result := &selectedMessages{ msgs: make([]*types.SignedMessage, 0, mpCfg.SizeLimitLow), - gasLimit: int64(build.BlockGasLimit), + gasLimit: build.BlockGasLimit, blsLimit: cbg.MaxLength, secpLimit: cbg.MaxLength, } diff --git a/chain/store/basefee_test.go b/chain/store/basefee_test.go index ea45a7673..8dd61f709 100644 --- a/chain/store/basefee_test.go +++ b/chain/store/basefee_test.go @@ -25,7 +25,7 @@ func TestBaseFee(t *testing.T) { {100e6, build.BlockGasTarget, 1, 103.125e6, 100e6}, {100e6, build.BlockGasTarget * 2, 2, 103.125e6, 100e6}, {100e6, build.BlockGasLimit * 2, 2, 112.5e6, 112.5e6}, - {100e6, build.BlockGasLimit * 1.5, 2, 110937500, 106.250e6}, + {100e6, (build.BlockGasLimit * 15) / 10, 2, 110937500, 106.250e6}, } for _, test := range tests { diff --git a/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go b/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go index d9a32481c..f984c993d 100644 --- a/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go +++ b/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go @@ -31,12 +31,13 @@ const ( // has. // 5 per tipset, but we effectively get 4 blocks worth of messages. expectedBlocks = 4 - // TODO: This will produce invalid blocks but it will accurately model the amount of gas - // we're willing to use per-tipset. - // A more correct approach would be to produce 5 blocks. We can do that later. - targetGas = build.BlockGasTarget * expectedBlocks ) +// TODO: This will produce invalid blocks but it will accurately model the amount of gas +// we're willing to use per-tipset. +// A more correct approach would be to produce 5 blocks. We can do that later. +var targetGas = build.BlockGasTarget * expectedBlocks + type BlockBuilder struct { ctx context.Context logger *zap.SugaredLogger diff --git a/cmd/lotus-wallet/main.go b/cmd/lotus-wallet/main.go index f15b9e84d..75d31da56 100644 --- a/cmd/lotus-wallet/main.go +++ b/cmd/lotus-wallet/main.go @@ -209,7 +209,7 @@ var runCmd = &cli.Command{ rpcApi = api.PermissionedWalletAPI(rpcApi) } - rpcServer := jsonrpc.NewServer() + rpcServer := jsonrpc.NewServer(jsonrpc.WithServerErrors(api.RPCErrors)) rpcServer.Register("Filecoin", rpcApi) mux.Handle("/rpc/v0", rpcServer) diff --git a/cmd/lotus-worker/sealworker/rpc.go b/cmd/lotus-worker/sealworker/rpc.go index 1b98ccb83..7d84b5c8b 100644 --- a/cmd/lotus-worker/sealworker/rpc.go +++ b/cmd/lotus-worker/sealworker/rpc.go @@ -29,7 +29,7 @@ var log = logging.Logger("sealworker") func WorkerHandler(authv func(ctx context.Context, token string) ([]auth.Permission, error), remote http.HandlerFunc, a api.Worker, permissioned bool) http.Handler { mux := mux.NewRouter() readerHandler, readerServerOpt := rpcenc.ReaderParamDecoder() - rpcServer := jsonrpc.NewServer(readerServerOpt) + rpcServer := jsonrpc.NewServer(jsonrpc.WithServerErrors(api.RPCErrors), readerServerOpt) wapi := proxy.MetricedWorkerAPI(a) if permissioned { diff --git a/cmd/lotus/daemon.go b/cmd/lotus/daemon.go index 33c579e3a..8086ab2d1 100644 --- a/cmd/lotus/daemon.go +++ b/cmd/lotus/daemon.go @@ -31,6 +31,7 @@ import ( "github.com/filecoin-project/go-paramfetch" "github.com/filecoin-project/lotus/api" + lapi "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/lotus/chain/stmgr" @@ -303,7 +304,7 @@ var DaemonCmd = &cli.Command{ } defer closer() - liteModeDeps = node.Override(new(api.Gateway), gapi) + liteModeDeps = node.Override(new(lapi.Gateway), gapi) } // some libraries like ipfs/go-ds-measure and ipfs/go-ipfs-blockstore @@ -360,7 +361,7 @@ var DaemonCmd = &cli.Command{ // ---- // Populate JSON-RPC options. - serverOptions := make([]jsonrpc.ServerOption, 0) + serverOptions := []jsonrpc.ServerOption{jsonrpc.WithServerErrors(lapi.RPCErrors)} if maxRequestSize := cctx.Int("api-max-req-size"); maxRequestSize != 0 { serverOptions = append(serverOptions, jsonrpc.WithMaxRequestSize(int64(maxRequestSize))) } diff --git a/gateway/handler.go b/gateway/handler.go index 648065a44..be824b430 100644 --- a/gateway/handler.go +++ b/gateway/handler.go @@ -30,7 +30,7 @@ func Handler(gwapi lapi.Gateway, api lapi.FullNode, rateLimit int64, connPerMinu m := mux.NewRouter() serveRpc := func(path string, hnd interface{}) { - rpcServer := jsonrpc.NewServer(opts...) + rpcServer := jsonrpc.NewServer(append(opts, jsonrpc.WithServerErrors(lapi.RPCErrors))...) rpcServer.Register("Filecoin", hnd) rpcServer.AliasMethod("rpc.discover", "Filecoin.Discover") diff --git a/itests/api_test.go b/itests/api_test.go index 7eac4e8a3..36b495c61 100644 --- a/itests/api_test.go +++ b/itests/api_test.go @@ -10,6 +10,7 @@ import ( logging "github.com/ipfs/go-log/v2" "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/require" + "golang.org/x/xerrors" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/big" @@ -51,6 +52,8 @@ func runAPITest(t *testing.T, opts ...interface{}) { t.Run("testMiningReal", ts.testMiningReal) t.Run("testSlowNotify", ts.testSlowNotify) t.Run("testSearchMsg", ts.testSearchMsg) + t.Run("testOutOfGasError", ts.testOutOfGasError) + t.Run("testLookupNotFoundError", ts.testLookupNotFoundError) t.Run("testNonGenesisMiner", ts.testNonGenesisMiner) } @@ -149,6 +152,46 @@ func (ts *apiSuite) testSearchMsg(t *testing.T) { require.Equalf(t, res.TipSet, searchRes.TipSet, "search ts: %s, different from wait ts: %s", searchRes.TipSet, res.TipSet) } +func (ts *apiSuite) testOutOfGasError(t *testing.T) { + ctx := context.Background() + + full, _, _ := kit.EnsembleMinimal(t, ts.opts...) + + senderAddr, err := full.WalletDefaultAddress(ctx) + require.NoError(t, err) + + // the gas estimator API executes the message with gasLimit = BlockGasLimit + // Lowering it to 2 will cause it to run out of gas, testing the failure case we want + originalLimit := build.BlockGasLimit + build.BlockGasLimit = 2 + defer func() { + build.BlockGasLimit = originalLimit + }() + + msg := &types.Message{ + From: senderAddr, + To: senderAddr, + Value: big.Zero(), + } + + _, err = full.GasEstimateMessageGas(ctx, msg, nil, types.EmptyTSK) + require.Error(t, err, "should have failed") + require.True(t, xerrors.Is(err, lapi.ErrOutOfGas{})) +} + +func (ts *apiSuite) testLookupNotFoundError(t *testing.T) { + ctx := context.Background() + + full, _, _ := kit.EnsembleMinimal(t, ts.opts...) + + addr, err := full.WalletNew(ctx, types.KTSecp256k1) + require.NoError(t, err) + + _, err = full.StateLookupID(ctx, addr, types.EmptyTSK) + require.Error(t, err) + require.True(t, xerrors.Is(err, lapi.ErrActorNotFound{})) +} + func (ts *apiSuite) testMining(t *testing.T) { ctx := context.Background() diff --git a/itests/paych_cli_test.go b/itests/paych_cli_test.go index d73f96905..0075a1204 100644 --- a/itests/paych_cli_test.go +++ b/itests/paych_cli_test.go @@ -13,6 +13,7 @@ import ( cbor "github.com/ipfs/go-ipld-cbor" "github.com/stretchr/testify/require" + "golang.org/x/xerrors" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" @@ -66,6 +67,21 @@ func TestPaymentChannelsBasic(t *testing.T) { vamt := strconv.Itoa(voucherAmt) voucher := creatorCLI.RunCmd("paych", "voucher", "create", chAddr.String(), vamt) + // DEFLAKE: We have observed this test flakily failing when the receiver node hasn't seen the paych create message + // This makes us wait as much as 10 epochs before giving up and failing + retry := 0 + _, err = paymentReceiver.StateLookupID(ctx, chAddr, types.EmptyTSK) + for err != nil && xerrors.Is(err, api.ErrActorNotFound{}) { + time.Sleep(blocktime) + _, err = paymentReceiver.StateLookupID(ctx, chAddr, types.EmptyTSK) + retry++ + if retry > 10 { + break + } + } + + require.NoError(t, err) + // receiver: paych voucher add receiverCLI.RunCmd("paych", "voucher", "add", chAddr.String(), voucher) diff --git a/node/impl/full/gas.go b/node/impl/full/gas.go index c179c27d6..4b7ebc4a0 100644 --- a/node/impl/full/gas.go +++ b/node/impl/full/gas.go @@ -290,6 +290,10 @@ func gasEstimateGasLimit( if err != nil { return -1, xerrors.Errorf("CallWithGas failed: %w", err) } + if res.MsgRct.ExitCode == exitcode.SysErrOutOfGas { + return -1, api.ErrOutOfGas{} + } + if res.MsgRct.ExitCode != exitcode.Ok { return -1, xerrors.Errorf("message execution failed: exit %s, reason: %s", res.MsgRct.ExitCode, res.Error) } @@ -356,7 +360,7 @@ func (m *GasModule) GasEstimateMessageGas(ctx context.Context, msg *types.Messag if msg.GasLimit == 0 { gasLimit, err := m.GasEstimateGasLimit(ctx, msg, types.EmptyTSK) if err != nil { - return nil, xerrors.Errorf("estimating gas used: %w", err) + return nil, err } msg.GasLimit = int64(float64(gasLimit) * m.Mpool.GetConfig().GasLimitOverestimation) } diff --git a/node/impl/full/state.go b/node/impl/full/state.go index 5247b9fb5..d11037d49 100644 --- a/node/impl/full/state.go +++ b/node/impl/full/state.go @@ -482,7 +482,12 @@ func (m *StateModule) StateLookupID(ctx context.Context, addr address.Address, t return address.Undef, xerrors.Errorf("loading tipset %s: %w", tsk, err) } - return m.StateManager.LookupID(ctx, addr, ts) + ret, err := m.StateManager.LookupID(ctx, addr, ts) + if err != nil && xerrors.Is(err, types.ErrActorNotFound) { + return address.Undef, api.ErrActorNotFound{} + } + + return ret, err } func (a *StateAPI) StateLookupRobustAddress(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) { diff --git a/node/rpc.go b/node/rpc.go index bee540500..2c85c71be 100644 --- a/node/rpc.go +++ b/node/rpc.go @@ -69,7 +69,7 @@ func FullNodeHandler(a v1api.FullNode, permissioned bool, opts ...jsonrpc.Server m := mux.NewRouter() serveRpc := func(path string, hnd interface{}) { - rpcServer := jsonrpc.NewServer(opts...) + rpcServer := jsonrpc.NewServer(append(opts, jsonrpc.WithServerErrors(api.RPCErrors))...) rpcServer.Register("Filecoin", hnd) rpcServer.AliasMethod("rpc.discover", "Filecoin.Discover") @@ -130,7 +130,7 @@ func MinerHandler(a api.StorageMiner, permissioned bool) (http.Handler, error) { } readerHandler, readerServerOpt := rpcenc.ReaderParamDecoder() - rpcServer := jsonrpc.NewServer(readerServerOpt) + rpcServer := jsonrpc.NewServer(jsonrpc.WithServerErrors(api.RPCErrors), readerServerOpt) rpcServer.Register("Filecoin", mapi) rpcServer.AliasMethod("rpc.discover", "Filecoin.Discover") From 5d28eea96853cfa0086cf5cebff33618fdc0d625 Mon Sep 17 00:00:00 2001 From: Aayush Date: Wed, 21 Sep 2022 12:28:11 -0400 Subject: [PATCH 81/85] chore: deps: update to go-jsonrpc 0.1.8 --- api/api_errors.go | 8 ++++---- go.mod | 2 +- go.sum | 4 ++-- itests/api_test.go | 4 ++-- itests/paych_cli_test.go | 2 +- node/impl/full/gas.go | 2 +- node/impl/full/state.go | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/api/api_errors.go b/api/api_errors.go index 953cf63bf..53460aea7 100644 --- a/api/api_errors.go +++ b/api/api_errors.go @@ -11,19 +11,19 @@ const ( type ErrOutOfGas struct{} -func (e ErrOutOfGas) Error() string { +func (e *ErrOutOfGas) Error() string { return "call ran out of gas" } type ErrActorNotFound struct{} -func (e ErrActorNotFound) Error() string { +func (e *ErrActorNotFound) Error() string { return "actor not found" } var RPCErrors = jsonrpc.NewErrors() func init() { - RPCErrors.Register(EOutOfGas, new(ErrOutOfGas)) - RPCErrors.Register(EActorNotFound, new(ErrActorNotFound)) + RPCErrors.Register(EOutOfGas, new(*ErrOutOfGas)) + RPCErrors.Register(EActorNotFound, new(*ErrActorNotFound)) } diff --git a/go.mod b/go.mod index dd603bcad..1e5c437f0 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/filecoin-project/go-fil-commcid v0.1.0 github.com/filecoin-project/go-fil-commp-hashhash v0.1.0 github.com/filecoin-project/go-fil-markets v1.24.0 - github.com/filecoin-project/go-jsonrpc v0.1.7 + github.com/filecoin-project/go-jsonrpc v0.1.8 github.com/filecoin-project/go-legs v0.4.4 github.com/filecoin-project/go-padreader v0.0.1 github.com/filecoin-project/go-paramfetch v0.0.4 diff --git a/go.sum b/go.sum index ee452c5fe..3784686b0 100644 --- a/go.sum +++ b/go.sum @@ -327,8 +327,8 @@ github.com/filecoin-project/go-hamt-ipld/v2 v2.0.0/go.mod h1:7aWZdaQ1b16BVoQUYR+ github.com/filecoin-project/go-hamt-ipld/v3 v3.0.1/go.mod h1:gXpNmr3oQx8l3o7qkGyDjJjYSRX7hp/FGOStdqrWyDI= github.com/filecoin-project/go-hamt-ipld/v3 v3.1.0 h1:rVVNq0x6RGQIzCo1iiJlGFm9AGIZzeifggxtKMU7zmI= github.com/filecoin-project/go-hamt-ipld/v3 v3.1.0/go.mod h1:bxmzgT8tmeVQA1/gvBwFmYdT8SOFUwB3ovSUfG1Ux0g= -github.com/filecoin-project/go-jsonrpc v0.1.7 h1:Ti/QkQLI31v+6hvidA+i9Wv/NrS4CfHk0yXLntHX3Uk= -github.com/filecoin-project/go-jsonrpc v0.1.7/go.mod h1:XBBpuKIMaXIIzeqzO1iucq4GvbF8CxmXRFoezRh+Cx4= +github.com/filecoin-project/go-jsonrpc v0.1.8 h1:uXX/ikAk3Q4f/k8DRd9Zw+fWnfiYb5I+UI1tzlQgHog= +github.com/filecoin-project/go-jsonrpc v0.1.8/go.mod h1:XBBpuKIMaXIIzeqzO1iucq4GvbF8CxmXRFoezRh+Cx4= github.com/filecoin-project/go-legs v0.4.4 h1:mpMmAOOnamaz0CV9rgeKhEWA8j9kMC+f+UGCGrxKaZo= github.com/filecoin-project/go-legs v0.4.4/go.mod h1:JQ3hA6xpJdbR8euZ2rO0jkxaMxeidXf0LDnVuqPAe9s= github.com/filecoin-project/go-padreader v0.0.0-20200903213702-ed5fae088b20/go.mod h1:mPn+LRRd5gEKNAtc+r3ScpW2JRU/pj4NBKdADYWHiak= diff --git a/itests/api_test.go b/itests/api_test.go index 36b495c61..2afbc0bd0 100644 --- a/itests/api_test.go +++ b/itests/api_test.go @@ -176,7 +176,7 @@ func (ts *apiSuite) testOutOfGasError(t *testing.T) { _, err = full.GasEstimateMessageGas(ctx, msg, nil, types.EmptyTSK) require.Error(t, err, "should have failed") - require.True(t, xerrors.Is(err, lapi.ErrOutOfGas{})) + require.True(t, xerrors.Is(err, &lapi.ErrOutOfGas{})) } func (ts *apiSuite) testLookupNotFoundError(t *testing.T) { @@ -189,7 +189,7 @@ func (ts *apiSuite) testLookupNotFoundError(t *testing.T) { _, err = full.StateLookupID(ctx, addr, types.EmptyTSK) require.Error(t, err) - require.True(t, xerrors.Is(err, lapi.ErrActorNotFound{})) + require.True(t, xerrors.Is(err, &lapi.ErrActorNotFound{})) } func (ts *apiSuite) testMining(t *testing.T) { diff --git a/itests/paych_cli_test.go b/itests/paych_cli_test.go index 0075a1204..f86f5d8de 100644 --- a/itests/paych_cli_test.go +++ b/itests/paych_cli_test.go @@ -71,7 +71,7 @@ func TestPaymentChannelsBasic(t *testing.T) { // This makes us wait as much as 10 epochs before giving up and failing retry := 0 _, err = paymentReceiver.StateLookupID(ctx, chAddr, types.EmptyTSK) - for err != nil && xerrors.Is(err, api.ErrActorNotFound{}) { + for err != nil && xerrors.Is(err, &api.ErrActorNotFound{}) { time.Sleep(blocktime) _, err = paymentReceiver.StateLookupID(ctx, chAddr, types.EmptyTSK) retry++ diff --git a/node/impl/full/gas.go b/node/impl/full/gas.go index 4b7ebc4a0..8cbe9ea1c 100644 --- a/node/impl/full/gas.go +++ b/node/impl/full/gas.go @@ -291,7 +291,7 @@ func gasEstimateGasLimit( return -1, xerrors.Errorf("CallWithGas failed: %w", err) } if res.MsgRct.ExitCode == exitcode.SysErrOutOfGas { - return -1, api.ErrOutOfGas{} + return -1, &api.ErrOutOfGas{} } if res.MsgRct.ExitCode != exitcode.Ok { diff --git a/node/impl/full/state.go b/node/impl/full/state.go index d11037d49..97f3c83dc 100644 --- a/node/impl/full/state.go +++ b/node/impl/full/state.go @@ -484,7 +484,7 @@ func (m *StateModule) StateLookupID(ctx context.Context, addr address.Address, t ret, err := m.StateManager.LookupID(ctx, addr, ts) if err != nil && xerrors.Is(err, types.ErrActorNotFound) { - return address.Undef, api.ErrActorNotFound{} + return address.Undef, &api.ErrActorNotFound{} } return ret, err From 34bbd2ad26978e2e61661c733c9f3422d4766da8 Mon Sep 17 00:00:00 2001 From: Aayush Date: Wed, 21 Sep 2022 17:03:07 -0400 Subject: [PATCH 82/85] remove duplicate import --- cmd/lotus/daemon.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/lotus/daemon.go b/cmd/lotus/daemon.go index 8086ab2d1..0c7874dfc 100644 --- a/cmd/lotus/daemon.go +++ b/cmd/lotus/daemon.go @@ -30,7 +30,6 @@ import ( "github.com/filecoin-project/go-jsonrpc" "github.com/filecoin-project/go-paramfetch" - "github.com/filecoin-project/lotus/api" lapi "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/consensus/filcns" @@ -314,7 +313,7 @@ var DaemonCmd = &cli.Command{ log.Warnf("unable to inject prometheus ipfs/go-metrics exporter; some metrics will be unavailable; err: %s", err) } - var api api.FullNode + var api lapi.FullNode stop, err := node.New(ctx, node.FullAPI(&api, node.Lite(isLite)), @@ -393,7 +392,7 @@ var DaemonCmd = &cli.Command{ }, } -func importKey(ctx context.Context, api api.FullNode, f string) error { +func importKey(ctx context.Context, api lapi.FullNode, f string) error { f, err := homedir.Expand(f) if err != nil { return err From 13f3e0aca4ab5fa50cf2a8e73c483f99a536301a Mon Sep 17 00:00:00 2001 From: Aayush Date: Thu, 22 Sep 2022 12:50:05 -0400 Subject: [PATCH 83/85] fix: itest: check for closed connection --- api/api_errors.go | 13 +++++++++++++ itests/kit/blockminer.go | 3 ++- lib/retry/retry.go | 16 +++------------- lib/retry/retry_test.go | 8 +++++--- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/api/api_errors.go b/api/api_errors.go index 53460aea7..fd157be5f 100644 --- a/api/api_errors.go +++ b/api/api_errors.go @@ -1,6 +1,9 @@ package api import ( + "errors" + "reflect" + "github.com/filecoin-project/go-jsonrpc" ) @@ -23,6 +26,16 @@ func (e *ErrActorNotFound) Error() string { var RPCErrors = jsonrpc.NewErrors() +func ErrorIsIn(err error, errorTypes []error) bool { + for _, etype := range errorTypes { + tmp := reflect.New(reflect.PointerTo(reflect.ValueOf(etype).Elem().Type())).Interface() + if errors.As(err, tmp) { + return true + } + } + return false +} + func init() { RPCErrors.Register(EOutOfGas, new(*ErrOutOfGas)) RPCErrors.Register(EActorNotFound, new(*ErrActorNotFound)) diff --git a/itests/kit/blockminer.go b/itests/kit/blockminer.go index 48cebe993..0b99c7830 100644 --- a/itests/kit/blockminer.go +++ b/itests/kit/blockminer.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/filecoin-project/go-bitfield" + "github.com/filecoin-project/go-jsonrpc" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/builtin" minertypes "github.com/filecoin-project/go-state-types/builtin/v8/miner" @@ -191,7 +192,7 @@ func (bm *BlockMiner) MineBlocksMustPost(ctx context.Context, blocktime time.Dur reportSuccessFn := func(success bool, epoch abi.ChainEpoch, err error) { // if api shuts down before mining, we may get an error which we should probably just ignore // (fixing it will require rewriting most of the mining loop) - if err != nil && !strings.Contains(err.Error(), "websocket connection closed") { + if err != nil && !strings.Contains(err.Error(), "websocket connection closed") && !api.ErrorIsIn(err, []error{&jsonrpc.RPCConnectionError{}}) { require.NoError(bm.t, err) } diff --git a/lib/retry/retry.go b/lib/retry/retry.go index 0b53c0fa3..e69d1ef35 100644 --- a/lib/retry/retry.go +++ b/lib/retry/retry.go @@ -1,25 +1,15 @@ package retry import ( - "errors" - "reflect" "time" logging "github.com/ipfs/go-log/v2" + + "github.com/filecoin-project/lotus/api" ) var log = logging.Logger("retry") -func ErrorIsIn(err error, errorTypes []error) bool { - for _, etype := range errorTypes { - tmp := reflect.New(reflect.PointerTo(reflect.ValueOf(etype).Elem().Type())).Interface() - if errors.As(err, tmp) { - return true - } - } - return false -} - func Retry[T any](attempts int, initialBackoff time.Duration, errorTypes []error, f func() (T, error)) (result T, err error) { for i := 0; i < attempts; i++ { if i > 0 { @@ -28,7 +18,7 @@ func Retry[T any](attempts int, initialBackoff time.Duration, errorTypes []error initialBackoff *= 2 } result, err = f() - if err == nil || !ErrorIsIn(err, errorTypes) { + if err == nil || !api.ErrorIsIn(err, errorTypes) { return result, err } } diff --git a/lib/retry/retry_test.go b/lib/retry/retry_test.go index a60d47db2..d3483249b 100644 --- a/lib/retry/retry_test.go +++ b/lib/retry/retry_test.go @@ -7,19 +7,21 @@ import ( "golang.org/x/xerrors" "github.com/filecoin-project/go-jsonrpc" + + "github.com/filecoin-project/lotus/api" ) func TestRetryErrorIsInTrue(t *testing.T) { errorsToRetry := []error{&jsonrpc.RPCConnectionError{}} - require.True(t, ErrorIsIn(&jsonrpc.RPCConnectionError{}, errorsToRetry)) + require.True(t, api.ErrorIsIn(&jsonrpc.RPCConnectionError{}, errorsToRetry)) } func TestRetryErrorIsInFalse(t *testing.T) { errorsToRetry := []error{&jsonrpc.RPCConnectionError{}} - require.False(t, ErrorIsIn(xerrors.Errorf("random error"), errorsToRetry)) + require.False(t, api.ErrorIsIn(xerrors.Errorf("random error"), errorsToRetry)) } func TestRetryWrappedErrorIsInTrue(t *testing.T) { errorsToRetry := []error{&jsonrpc.RPCConnectionError{}} - require.True(t, ErrorIsIn(xerrors.Errorf("wrapped: %w", &jsonrpc.RPCConnectionError{}), errorsToRetry)) + require.True(t, api.ErrorIsIn(xerrors.Errorf("wrapped: %w", &jsonrpc.RPCConnectionError{}), errorsToRetry)) } From 8244297b89eed77dafaa1f4d3031700bae558fbd Mon Sep 17 00:00:00 2001 From: Aayush Date: Fri, 23 Sep 2022 11:35:30 -0400 Subject: [PATCH 84/85] chore: refactor: move retry test to API --- api/api_test.go | 18 ++++++++++++++++++ lib/retry/retry_test.go | 27 --------------------------- 2 files changed, 18 insertions(+), 27 deletions(-) delete mode 100644 lib/retry/retry_test.go diff --git a/api/api_test.go b/api/api_test.go index c03216365..f976b4f22 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -11,6 +11,9 @@ import ( "strings" "testing" + "github.com/filecoin-project/go-jsonrpc" + "golang.org/x/xerrors" + "github.com/stretchr/testify/require" ) @@ -124,3 +127,18 @@ func TestPermTags(t *testing.T) { _ = PermissionedStorMinerAPI(&StorageMinerStruct{}) _ = PermissionedWorkerAPI(&WorkerStruct{}) } + +func TestRetryErrorIsInTrue(t *testing.T) { + errorsToRetry := []error{&jsonrpc.RPCConnectionError{}} + require.True(t, ErrorIsIn(&jsonrpc.RPCConnectionError{}, errorsToRetry)) +} + +func TestRetryErrorIsInFalse(t *testing.T) { + errorsToRetry := []error{&jsonrpc.RPCConnectionError{}} + require.False(t, ErrorIsIn(xerrors.Errorf("random error"), errorsToRetry)) +} + +func TestRetryWrappedErrorIsInTrue(t *testing.T) { + errorsToRetry := []error{&jsonrpc.RPCConnectionError{}} + require.True(t, ErrorIsIn(xerrors.Errorf("wrapped: %w", &jsonrpc.RPCConnectionError{}), errorsToRetry)) +} diff --git a/lib/retry/retry_test.go b/lib/retry/retry_test.go deleted file mode 100644 index d3483249b..000000000 --- a/lib/retry/retry_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package retry - -import ( - "testing" - - "github.com/stretchr/testify/require" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-jsonrpc" - - "github.com/filecoin-project/lotus/api" -) - -func TestRetryErrorIsInTrue(t *testing.T) { - errorsToRetry := []error{&jsonrpc.RPCConnectionError{}} - require.True(t, api.ErrorIsIn(&jsonrpc.RPCConnectionError{}, errorsToRetry)) -} - -func TestRetryErrorIsInFalse(t *testing.T) { - errorsToRetry := []error{&jsonrpc.RPCConnectionError{}} - require.False(t, api.ErrorIsIn(xerrors.Errorf("random error"), errorsToRetry)) -} - -func TestRetryWrappedErrorIsInTrue(t *testing.T) { - errorsToRetry := []error{&jsonrpc.RPCConnectionError{}} - require.True(t, api.ErrorIsIn(xerrors.Errorf("wrapped: %w", &jsonrpc.RPCConnectionError{}), errorsToRetry)) -} From 9c242a80129f8604645597dd0548dab62d485e59 Mon Sep 17 00:00:00 2001 From: Aayush Date: Fri, 23 Sep 2022 11:36:38 -0400 Subject: [PATCH 85/85] address magik supernit --- api/api_test.go | 4 ++-- itests/kit/blockminer.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/api_test.go b/api/api_test.go index f976b4f22..1316d9fa4 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -11,10 +11,10 @@ import ( "strings" "testing" - "github.com/filecoin-project/go-jsonrpc" + "github.com/stretchr/testify/require" "golang.org/x/xerrors" - "github.com/stretchr/testify/require" + "github.com/filecoin-project/go-jsonrpc" ) func goCmd() string { diff --git a/itests/kit/blockminer.go b/itests/kit/blockminer.go index 0b99c7830..9dd83ee47 100644 --- a/itests/kit/blockminer.go +++ b/itests/kit/blockminer.go @@ -192,7 +192,7 @@ func (bm *BlockMiner) MineBlocksMustPost(ctx context.Context, blocktime time.Dur reportSuccessFn := func(success bool, epoch abi.ChainEpoch, err error) { // if api shuts down before mining, we may get an error which we should probably just ignore // (fixing it will require rewriting most of the mining loop) - if err != nil && !strings.Contains(err.Error(), "websocket connection closed") && !api.ErrorIsIn(err, []error{&jsonrpc.RPCConnectionError{}}) { + if err != nil && !strings.Contains(err.Error(), "websocket connection closed") && !api.ErrorIsIn(err, []error{new(jsonrpc.RPCConnectionError)}) { require.NoError(bm.t, err) }