11package cli
22
33import (
4+ "embed"
45 "encoding/json"
6+ "errors"
57 "fmt"
68 "io"
9+ "strconv"
710 "strings"
811 "text/template"
912
1013 "github.com/urfave/cli/v2"
1114
15+ "github.com/filecoin-project/go-address"
16+ "github.com/filecoin-project/go-f3/certs"
17+ "github.com/filecoin-project/go-f3/gpbft"
1218 "github.com/filecoin-project/go-f3/manifest"
1319
20+ "github.com/filecoin-project/lotus/chain/types"
1421 "github.com/filecoin-project/lotus/lib/tablewriter"
1522)
1623
2128 Subcommands : []* cli.Command {
2229 {
2330 Name : "list-miners" ,
24- Aliases : []string {"lp " },
31+ Aliases : []string {"lm " },
2532 Usage : "Lists the miners that currently participate in F3 via this node." ,
2633 Action : func (cctx * cli.Context ) error {
2734 api , closer , err := GetFullNodeAPIV1 (cctx )
@@ -39,25 +46,181 @@ var (
3946 return err
4047 }
4148 const (
42- idColumn = "ID "
43- fromColumn = "From"
44- ToColumn = "To"
49+ miner = "Miner "
50+ from = "From"
51+ to = "To"
4552 )
4653 tw := tablewriter .New (
47- tablewriter .Col (idColumn ),
48- tablewriter .Col (fromColumn ),
49- tablewriter .Col (ToColumn ),
54+ tablewriter .Col (miner ),
55+ tablewriter .Col (from ),
56+ tablewriter .Col (to ),
5057 )
5158 for _ , participant := range miners {
59+ addr , err := address .NewIDAddress (participant .MinerID )
60+ if err != nil {
61+ return fmt .Errorf ("converting miner ID to address: %w" , err )
62+ }
63+
5264 tw .Write (map [string ]interface {}{
53- idColumn : participant . MinerID ,
54- fromColumn : participant .FromInstance ,
55- ToColumn : participant .FromInstance + participant .ValidityTerm ,
65+ miner : addr ,
66+ from : participant .FromInstance ,
67+ to : participant .FromInstance + participant .ValidityTerm ,
5668 })
5769 }
5870 return tw .Flush (cctx .App .Writer )
5971 },
6072 },
73+ {
74+ Name : "certs" ,
75+ Aliases : []string {"c" },
76+ Usage : "Manages interactions with F3 finality certificates." ,
77+ Subcommands : []* cli.Command {
78+ {
79+ Name : "get" ,
80+ Usage : "Gets an F3 finality certificate to a given instance ID, " +
81+ "or the latest certificate if no instance is specified." ,
82+ ArgsUsage : "[instance]" ,
83+ Flags : []cli.Flag {
84+ f3FlagOutput ,
85+ },
86+ Before : func (cctx * cli.Context ) error {
87+ if count := cctx .NArg (); count > 1 {
88+ return fmt .Errorf ("too many arguments: expected at most 1 but got %d" , count )
89+ }
90+ return nil
91+ },
92+ Action : func (cctx * cli.Context ) error {
93+ api , closer , err := GetFullNodeAPIV1 (cctx )
94+ if err != nil {
95+ return err
96+ }
97+ defer closer ()
98+
99+ // Get the certificate, either for the given instance or the latest if no
100+ // instance is specified.
101+ var cert * certs.FinalityCertificate
102+ if cctx .Args ().Present () {
103+ var instance uint64
104+ instance , err = strconv .ParseUint (cctx .Args ().First (), 10 , 64 )
105+ if err != nil {
106+ return fmt .Errorf ("parsing instance: %w" , err )
107+ }
108+ cert , err = api .F3GetCertificate (cctx .Context , instance )
109+ } else {
110+ cert , err = api .F3GetLatestCertificate (cctx .Context )
111+ }
112+ if err != nil {
113+ return fmt .Errorf ("getting finality certificate: %w" , err )
114+ }
115+ if cert == nil {
116+ _ , _ = fmt .Fprintln (cctx .App .ErrWriter , "No certificate." )
117+ return nil
118+ }
119+
120+ return outputFinalityCertificate (cctx , cert )
121+ },
122+ },
123+ {
124+ Name : "list" ,
125+ Usage : `Lists a range of F3 finality certificates.
126+
127+ By default the certificates are listed in newest to oldest order,
128+ i.e. descending instance IDs. The order may be reversed using the
129+ '--reverse' flag.
130+
131+ A range may optionally be specified as the first argument to indicate
132+ inclusive range of 'from' and 'to' instances in following notation:
133+ '<from>..<to>'. Either <from> or <to> may be omitted, but not both.
134+ An omitted <from> value is always interpreted as 0, and an omitted
135+ <to> value indicates the latest instance. If both are specified, <from>
136+ must never exceed <to>.
137+
138+ If no range is specified all certificates are listed, i.e. the range
139+ of '0..'.
140+
141+ Examples:
142+ * All certificates from newest to oldest:
143+ $ lotus f3 certs list 0..
144+
145+ * Three newest certificates:
146+ $ lotus f3 certs list --limit 3 0..
147+
148+ * Three oldest certificates:
149+ $ lotus f3 certs list --limit 3 --reverse 0..
150+
151+ * Up to three certificates starting from instance 1413 to the oldest:
152+ $ lotus f3 certs list --limit 3 ..1413
153+
154+ * Up to 3 certificates starting from instance 1413 to the newest:
155+ $ lotus f3 certs list --limit 3 --reverse 1413..
156+
157+ * All certificates from instance 3 to 1413 in order of newest to oldest:
158+ $ lotus f3 certs list 3..1413
159+ ` ,
160+ ArgsUsage : "[range]" ,
161+ Flags : []cli.Flag {
162+ f3FlagOutput ,
163+ f3FlagInstanceLimit ,
164+ f3FlagReverseOrder ,
165+ },
166+ Before : func (cctx * cli.Context ) error {
167+ if count := cctx .NArg (); count > 1 {
168+ return fmt .Errorf ("too many arguments: expected at most 1 but got %d" , count )
169+ }
170+ return nil
171+ },
172+ Action : func (cctx * cli.Context ) error {
173+ api , closer , err := GetFullNodeAPIV1 (cctx )
174+ if err != nil {
175+ return err
176+ }
177+ defer closer ()
178+
179+ limit := cctx .Int (f3FlagInstanceLimit .Name )
180+ reverse := cctx .Bool (f3FlagReverseOrder .Name )
181+ fromTo := cctx .Args ().First ()
182+ if fromTo == "" {
183+ fromTo = "0.."
184+ }
185+ r , err := newRanger (fromTo , limit , reverse , func () (uint64 , error ) {
186+ latest , err := api .F3GetLatestCertificate (cctx .Context )
187+ if err != nil {
188+ return 0 , fmt .Errorf ("getting latest finality certificate: %w" , err )
189+ }
190+ if latest == nil {
191+ return 0 , errors .New ("no latest finality certificate" )
192+ }
193+ return latest .GPBFTInstance , nil
194+ })
195+ if err != nil {
196+ return err
197+ }
198+
199+ var cert * certs.FinalityCertificate
200+ for cctx .Context .Err () == nil {
201+ next , proceed := r .next ()
202+ if ! proceed {
203+ return nil
204+ }
205+ cert , err = api .F3GetCertificate (cctx .Context , next )
206+ if err != nil {
207+ return fmt .Errorf ("getting finality certificate for instance %d: %w" , next , err )
208+ }
209+ if cert == nil {
210+ // This is unexpected, because the range of iteration was determined earlier and
211+ // certstore should to have all the certs. Error out.
212+ return fmt .Errorf ("nil finality certificate for instance %d" , next )
213+ }
214+ if err := outputFinalityCertificate (cctx , cert ); err != nil {
215+ return err
216+ }
217+ _ , _ = fmt .Fprintln (cctx .App .Writer )
218+ }
219+ return nil
220+ },
221+ },
222+ },
223+ },
61224 {
62225 Name : "manifest" ,
63226 Usage : "Gets the current manifest used by F3." ,
@@ -140,46 +303,132 @@ var (
140303 }
141304 },
142305 }
306+ f3FlagInstanceLimit = & cli.IntFlag {
307+ Name : "limit" ,
308+ Usage : "The maximum number of instances. A value less than 0 indicates no limit." ,
309+ DefaultText : "No limit" ,
310+ Value : - 1 ,
311+ }
312+ f3FlagReverseOrder = & cli.BoolFlag {
313+ Name : "reverse" ,
314+ Usage : "Reverses the default order of output. " ,
315+ }
316+ //go:embed templates/f3_*.go.tmpl
317+ f3TemplatesFS embed.FS
318+ f3Templates = template .Must (
319+ template .New ("" ).
320+ Funcs (template.FuncMap {
321+ "ptDiffToString" : f3PowerTableDiffsToString ,
322+ "tipSetKeyToLotusTipSetKey" : types .TipSetKeyFromBytes ,
323+ "add" : func (a , b int ) int { return a + b },
324+ "sub" : func (a , b int ) int { return a - b },
325+ }).
326+ ParseFS (f3TemplatesFS , "templates/f3_*.go.tmpl" ),
327+ )
143328)
144329
330+ func outputFinalityCertificate (cctx * cli.Context , cert * certs.FinalityCertificate ) error {
331+
332+ switch output := cctx .String (f3FlagOutput .Name ); strings .ToLower (output ) {
333+ case "text" :
334+ return prettyPrintFinalityCertificate (cctx .App .Writer , cert )
335+ case "json" :
336+ encoder := json .NewEncoder (cctx .App .Writer )
337+ encoder .SetIndent ("" , " " )
338+ return encoder .Encode (cert )
339+ default :
340+ return fmt .Errorf ("unknown output format: %s" , output )
341+ }
342+ }
343+
145344func prettyPrintManifest (out io.Writer , manifest * manifest.Manifest ) error {
146345 if manifest == nil {
147346 _ , err := fmt .Fprintln (out , "Manifest: None" )
148347 return err
149348 }
150349
151- const manifestTemplate = `Manifest:
152- Protocol Version: {{.ProtocolVersion}}
153- Paused: {{.Pause}}
154- Initial Instance: {{.InitialInstance}}
155- Initial Power Table: {{if .InitialPowerTable.Defined}}{{.InitialPowerTable}}{{else}}unknown{{end}}
156- Bootstrap Epoch: {{.BootstrapEpoch}}
157- Network Name: {{.NetworkName}}
158- Ignore EC Power: {{.IgnoreECPower}}
159- Committee Lookback: {{.CommitteeLookback}}
160- Catch Up Alignment: {{.CatchUpAlignment}}
161-
162- GPBFT Delta: {{.Gpbft.Delta}}
163- GPBFT Delta BackOff Exponent: {{.Gpbft.DeltaBackOffExponent}}
164- GPBFT Max Lookahead Rounds: {{.Gpbft.MaxLookaheadRounds}}
165- GPBFT Rebroadcast Backoff Base: {{.Gpbft.RebroadcastBackoffBase}}
166- GPBFT Rebroadcast Backoff Spread: {{.Gpbft.RebroadcastBackoffSpread}}
167- GPBFT Rebroadcast Backoff Max: {{.Gpbft.RebroadcastBackoffMax}}
168-
169- EC Period: {{.EC.Period}}
170- EC Finality: {{.EC.Finality}}
171- EC Delay Multiplier: {{.EC.DelayMultiplier}}
172- EC Head Lookback: {{.EC.HeadLookback}}
173- EC Finalize: {{.EC.Finalize}}
174-
175- Certificate Exchange Client Timeout: {{.CertificateExchange.ClientRequestTimeout}}
176- Certificate Exchange Server Timeout: {{.CertificateExchange.ServerRequestTimeout}}
177- Certificate Exchange Min Poll Interval: {{.CertificateExchange.MinimumPollInterval}}
178- Certificate Exchange Max Poll Interval: {{.CertificateExchange.MaximumPollInterval}}
179- `
180- t , err := template .New ("manifest" ).Parse (manifestTemplate )
350+ return f3Templates .ExecuteTemplate (out , "f3_manifest.go.tmpl" , manifest )
351+ }
352+
353+ func prettyPrintFinalityCertificate (out io.Writer , cert * certs.FinalityCertificate ) error {
354+ if cert == nil {
355+ _ , _ = fmt .Fprintln (out , "Certificate: None" )
356+ return nil
357+ }
358+ return f3Templates .ExecuteTemplate (out , "f3_finality_cert.go.tmpl" , cert )
359+ }
360+
361+ func f3PowerTableDiffsToString (diff certs.PowerTableDiff ) (string , error ) {
362+ if len (diff ) == 0 {
363+ return "None" , nil
364+ }
365+ totalDiff := gpbft .NewStoragePower (0 ).Int
366+ for _ , delta := range diff {
367+ if ! delta .IsZero () {
368+ totalDiff = totalDiff .Add (totalDiff , delta .PowerDelta .Int )
369+ }
370+ }
371+ if totalDiff .Cmp (gpbft .NewStoragePower (0 ).Int ) == 0 {
372+ return "None" , nil
373+ }
374+ return fmt .Sprintf ("Total of %s storage power across %d miner(s)." , totalDiff , len (diff )), nil
375+ }
376+
377+ type ranger struct {
378+ from , to uint64
379+ limit int
380+ ascend bool
381+ }
382+
383+ func newRanger (fromTo string , limit int , ascend bool , latest func () (uint64 , error )) (* ranger , error ) {
384+ parts := strings .Split (strings .TrimSpace (fromTo ), ".." )
385+ if len (parts ) != 2 || (parts [0 ] == "" && parts [1 ] == "" ) {
386+ return nil , fmt .Errorf ("invalid range format: expected '<from>..<to>', got: %s" , fromTo )
387+ }
388+
389+ r := ranger {
390+ limit : limit ,
391+ ascend : ascend ,
392+ }
393+ var err error
394+ if parts [0 ] != "" {
395+ r .from , err = strconv .ParseUint (parts [0 ], 10 , 64 )
396+ if err != nil {
397+ return nil , fmt .Errorf ("determining 'from' instance: %v" , err )
398+ }
399+ }
400+ if parts [1 ] != "" {
401+ r .to , err = strconv .ParseUint (parts [1 ], 10 , 64 )
402+ } else {
403+ r .to , err = latest ()
404+ }
181405 if err != nil {
182- return fmt .Errorf ("failed to parse manifest template: %w" , err )
406+ return nil , fmt .Errorf ("determining 'to' instance: %v" , err )
407+ }
408+ if r .from > r .to {
409+ return nil , fmt .Errorf ("invalid range: 'from' cannot exceed 'to': %d > %d" , r .from , r .to )
410+ }
411+ if ! ascend {
412+ r .from , r .to = r .to , r .from
413+ }
414+ return & r , nil
415+ }
416+
417+ func (r * ranger ) next () (uint64 , bool ) {
418+ if r .limit == 0 {
419+ return 0 , false
420+ }
421+
422+ next := r .from
423+ if r .from == r .to {
424+ r .limit = 0
425+ return next , true
426+ }
427+ if r .ascend {
428+ r .from ++
429+ } else if r .from > 0 {
430+ r .from --
183431 }
184- return t .ExecuteTemplate (out , "manifest" , manifest )
432+ r .limit --
433+ return next , true
185434}
0 commit comments