From dafe36eff8edcd6014fb9fe7aef7703ae839a2d4 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 14 Nov 2024 18:45:11 +0700 Subject: [PATCH 1/7] Implement F3 CLI to list power table and proportional power at instance Implement utility CLIs to: * get the power table used by F3 at a given instance ID. * get total proportional power of a list of actors at a given instance ID. These utilities allow us to debug the exact participation power for an instance without having to manually calculate it or estimate it from the latest power. --- cli/f3.go | 280 +++++++++++++++++++++++++++++++++- documentation/en/cli-lotus.md | 147 +----------------- 2 files changed, 287 insertions(+), 140 deletions(-) diff --git a/cli/f3.go b/cli/f3.go index 7ffd8bae419..61f0dc713d3 100644 --- a/cli/f3.go +++ b/cli/f3.go @@ -1,6 +1,8 @@ package cli import ( + "bufio" + "context" "embed" "encoding/json" "errors" @@ -10,13 +12,16 @@ import ( "strings" "text/template" + "github.com/ipfs/go-cid" "github.com/urfave/cli/v2" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-f3/certs" "github.com/filecoin-project/go-f3/gpbft" "github.com/filecoin-project/go-f3/manifest" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/api/v1api" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/lib/tablewriter" ) @@ -29,7 +34,11 @@ var ( { Name: "list-miners", Aliases: []string{"lm"}, - Usage: "Lists the miners that currently participate in F3 via this node.", + Usage: `Lists the miners that currently participate in F3 via this node. + +The instance may be specified as the first argument. If unspecified, +the latest instance is used. +`, Action: func(cctx *cli.Context) error { api, closer, err := GetFullNodeAPIV1(cctx) if err != nil { @@ -70,6 +79,242 @@ var ( return tw.Flush(cctx.App.Writer) }, }, + { + Name: "powertable", + Aliases: []string{"pt"}, + Subcommands: []*cli.Command{ + { + Name: "get", + Aliases: []string{"g"}, + Usage: "Get F3 power table at a specific instance ID or latest instance if none is specified.", + ArgsUsage: "[instance]", + Flags: []cli.Flag{f3FlagPowerTableFromEC}, + Before: func(cctx *cli.Context) error { + if cctx.Args().Len() > 1 { + return fmt.Errorf("too many arguments") + } + return nil + }, + Action: func(cctx *cli.Context) error { + api, closer, err := GetFullNodeAPIV1(cctx) + if err != nil { + return err + } + defer closer() + + progress, err := api.F3GetProgress(cctx.Context) + if err != nil { + return fmt.Errorf("getting progress: %w", err) + } + + var instance uint64 + if cctx.Args().Present() { + instance, err = strconv.ParseUint(cctx.Args().First(), 10, 64) + if err != nil { + return fmt.Errorf("parsing instance: %w", err) + } + if instance > progress.ID { + // TODO: Technically we can return power table for instances ahead as long as + // instance is within lookback. Implement it. + return fmt.Errorf("instance is ahead the current instance in progress: %d > %d", instance, progress.ID) + } + } else { + instance = progress.ID + } + + ltsk, expectedPowerTableCID, err := f3GetPowerTableTSKByInstance(cctx.Context, api, instance) + if err != nil { + return fmt.Errorf("getting power table tsk for instance %d: %w", instance, err) + } + + var result = struct { + Instance uint64 + FromEC bool + PowerTable struct { + CID string + Entries gpbft.PowerEntries + Total gpbft.StoragePower + ScaledTotal int64 + } + }{ + Instance: instance, + FromEC: cctx.Bool(f3FlagPowerTableFromEC.Name), + } + if result.FromEC { + result.PowerTable.Entries, err = api.F3GetECPowerTable(cctx.Context, ltsk) + } else { + result.PowerTable.Entries, err = api.F3GetF3PowerTable(cctx.Context, ltsk) + } + if err != nil { + return fmt.Errorf("getting f3 power table at instance %d: %w", instance, err) + } + + pt := gpbft.NewPowerTable() + if err := pt.Add(result.PowerTable.Entries...); err != nil { + // Sanity check the entries returned by the API. + return fmt.Errorf("retrieved power table is not valid for instance %d: %w", instance, err) + } + result.PowerTable.Total = pt.Total + result.PowerTable.ScaledTotal = pt.ScaledTotal + + actualPowerTableCID, err := certs.MakePowerTableCID(result.PowerTable.Entries) + if err != nil { + return fmt.Errorf("gettingh power table CID at instance %d: %w", instance, err) + } + if !expectedPowerTableCID.Equals(actualPowerTableCID) { + return fmt.Errorf("expected power table CID %s at instance %d, got: %s", expectedPowerTableCID, instance, actualPowerTableCID) + } + result.PowerTable.CID = actualPowerTableCID.String() + + output, err := json.MarshalIndent(result, "", " ") + if err != nil { + return fmt.Errorf("marshalling f3 power table at instance %d: %w", instance, err) + } + _, _ = fmt.Fprint(cctx.App.Writer, string(output)) + return nil + }, + }, + { + Name: "get-proportion", + Aliases: []string{"gp"}, + Usage: `Gets the total proportion of power for a list of actors at a given instance. + +The instance may be specified as the first argument. If unspecified, +the latest instance is used. + +The list of actors may be specified as Actor ID or miner address, space +separated, pied to STDIN. Example: + $ echo "1413 t01234 f12345" | lotus f3 powertable get-proportion 42 +`, + ArgsUsage: "[instance]", + Flags: []cli.Flag{f3FlagPowerTableFromEC}, + Before: func(cctx *cli.Context) error { + if cctx.Args().Len() > 1 { + return fmt.Errorf("too many arguments") + } + return nil + }, + Action: func(cctx *cli.Context) error { + api, closer, err := GetFullNodeAPIV1(cctx) + if err != nil { + return err + } + defer closer() + + progress, err := api.F3GetProgress(cctx.Context) + if err != nil { + return fmt.Errorf("getting progress: %w", err) + } + + var instance uint64 + if cctx.Args().Present() { + instance, err = strconv.ParseUint(cctx.Args().First(), 10, 64) + if err != nil { + return fmt.Errorf("parsing instance: %w", err) + } + if instance > progress.ID { + // TODO: Technically we can return power table for instances ahead as long as + // instance is within lookback. Implement it. + return fmt.Errorf("instance is ahead the current instance in progress: %d > %d", instance, progress.ID) + } + } else { + instance = progress.ID + } + + ltsk, expectedPowerTableCID, err := f3GetPowerTableTSKByInstance(cctx.Context, api, instance) + if err != nil { + return fmt.Errorf("getting power table tsk for instance %d: %w", instance, err) + } + + var result = struct { + Instance uint64 + FromEC bool + PowerTable struct { + CID string + ScaledTotal int64 + } + ScaledSum int64 + Proportion float64 + }{ + Instance: instance, + FromEC: cctx.Bool(f3FlagPowerTableFromEC.Name), + } + + var powerEntries gpbft.PowerEntries + if result.FromEC { + powerEntries, err = api.F3GetECPowerTable(cctx.Context, ltsk) + } else { + powerEntries, err = api.F3GetF3PowerTable(cctx.Context, ltsk) + } + if err != nil { + return fmt.Errorf("getting f3 power table at instance %d: %w", instance, err) + } + + actualPowerTableCID, err := certs.MakePowerTableCID(powerEntries) + if err != nil { + return fmt.Errorf("gettingh power table CID at instance %d: %w", instance, err) + } + if !expectedPowerTableCID.Equals(actualPowerTableCID) { + return fmt.Errorf("expected power table CID %s at instance %d, got: %s", expectedPowerTableCID, instance, actualPowerTableCID) + } + result.PowerTable.CID = actualPowerTableCID.String() + + scanner := bufio.NewScanner(cctx.App.Reader) + if !scanner.Scan() { + return fmt.Errorf("reading actor IDs from stdin: %w", scanner.Err()) + } + inputIDs := strings.Split(strings.TrimSpace(scanner.Text()), " ") + + pt := gpbft.NewPowerTable() + if err := pt.Add(powerEntries...); err != nil { + return fmt.Errorf("constructing power table from entries: %w", err) + } + result.PowerTable.ScaledTotal = pt.ScaledTotal + + seenIDs := map[gpbft.ActorID]struct{}{} + for _, stringID := range inputIDs { + var actorID gpbft.ActorID + switch addr, err := address.NewFromString(stringID); { + case err == nil: + idAddr, err := address.IDFromAddress(addr) + if err != nil { + return fmt.Errorf("parsing ID from address %q: %w", stringID, err) + } + actorID = gpbft.ActorID(idAddr) + case errors.Is(err, address.ErrUnknownNetwork), + errors.Is(err, address.ErrUnknownProtocol): + // Try parsing as uint64 straight up. + id, err := strconv.ParseUint(stringID, 10, 64) + if err != nil { + return fmt.Errorf("parsing as uint64 %q: %w", stringID, err) + } + actorID = gpbft.ActorID(id) + default: + return fmt.Errorf("parsing address %q: %w", stringID, err) + } + // Prune duplicate IDs. + if _, ok := seenIDs[actorID]; ok { + continue + } else { + seenIDs[actorID] = struct{}{} + } + scaled, key := pt.Get(actorID) + if key == nil { + return fmt.Errorf("actor ID %q not found in power table", actorID) + } + result.ScaledSum += scaled + } + result.Proportion = float64(result.ScaledSum) / float64(result.PowerTable.ScaledTotal) + output, err := json.MarshalIndent(result, "", " ") + if err != nil { + return fmt.Errorf("marshalling f3 power table at instance %d: %w", instance, err) + } + _, _ = fmt.Fprint(cctx.App.Writer, string(output)) + return nil + }, + }, + }, + }, { Name: "certs", Aliases: []string{"c"}, @@ -318,6 +563,10 @@ Examples: Name: "reverse", Usage: "Reverses the default order of output. ", } + f3FlagPowerTableFromEC = &cli.BoolFlag{ + Name: "ec", + Usage: "Whether to get the power table from EC.", + } //go:embed templates/f3_*.go.tmpl f3TemplatesFS embed.FS f3Templates = template.Must( @@ -332,6 +581,35 @@ Examples: ) ) +func f3GetPowerTableTSKByInstance(ctx context.Context, api v1api.FullNode, instance uint64) (types.TipSetKey, cid.Cid, error) { + mfst, err := api.F3GetManifest(ctx) + if err != nil { + return types.EmptyTSK, cid.Undef, fmt.Errorf("getting manifest: %w", err) + } + + if instance < mfst.InitialInstance+mfst.CommitteeLookback { + ts, err := api.ChainGetTipSetByHeight(ctx, abi.ChainEpoch(mfst.BootstrapEpoch-mfst.EC.Finality), types.EmptyTSK) + if err != nil { + return types.EmptyTSK, cid.Undef, fmt.Errorf("getting bootstrap epoch tipset: %w", err) + } + return ts.Key(), mfst.InitialPowerTable, nil + } + + previous, err := api.F3GetCertificate(ctx, instance-1) + if err != nil { + return types.EmptyTSK, cid.Undef, fmt.Errorf("getting certificate for previous instance: %w", err) + } + lookback, err := api.F3GetCertificate(ctx, instance-mfst.CommitteeLookback) + if err != nil { + return types.EmptyTSK, cid.Undef, fmt.Errorf("getting certificate for lookback instance: %w", err) + } + ltsk, err := types.TipSetKeyFromBytes(lookback.ECChain.Head().Key) + if err != nil { + return types.EmptyTSK, cid.Undef, fmt.Errorf("getting lotus tipset key from head of lookback certificate: %w", err) + } + return ltsk, previous.SupplementalData.PowerTable, nil +} + func outputFinalityCertificate(cctx *cli.Context, cert *certs.FinalityCertificate) error { switch output := cctx.String(f3FlagOutput.Name); strings.ToLower(output) { diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index 0ea16a39a83..6d62c05c8a1 100644 --- a/documentation/en/cli-lotus.md +++ b/documentation/en/cli-lotus.md @@ -2799,6 +2799,11 @@ USAGE: COMMANDS: list-miners, lm Lists the miners that currently participate in F3 via this node. + + The instance may be specified as the first argument. If unspecified, + the latest instance is used. + + powertable, pt certs, c Manages interactions with F3 finality certificates. manifest Gets the current manifest used by F3. status Checks the F3 status. @@ -2813,148 +2818,12 @@ OPTIONS: NAME: lotus f3 list-miners - Lists the miners that currently participate in F3 via this node. -USAGE: - lotus f3 list-miners [command options] [arguments...] - -OPTIONS: - --help, -h show help -``` - -### lotus f3 certs -``` -NAME: - lotus f3 certs - Manages interactions with F3 finality certificates. - -USAGE: - lotus f3 certs command [command options] [arguments...] - -COMMANDS: - get Gets an F3 finality certificate to a given instance ID, or the latest certificate if no instance is specified. - list Lists a range of F3 finality certificates. - - By default the certificates are listed in newest to oldest order, - i.e. descending instance IDs. The order may be reversed using the - '--reverse' flag. - - A range may optionally be specified as the first argument to indicate - inclusive range of 'from' and 'to' instances in following notation: - '..'. Either or may be omitted, but not both. - An omitted value is always interpreted as 0, and an omitted - value indicates the latest instance. If both are specified, - must never exceed . - - If no range is specified, the latest 10 certificates are listed, i.e. - the range of '0..' with limit of 10. Otherwise, all certificates in - the specified range are listed unless limit is explicitly specified. - - Examples: - * All certificates from newest to oldest: - $ lotus f3 certs list 0.. - - * Three newest certificates: - $ lotus f3 certs list --limit 3 0.. - - * Three oldest certificates: - $ lotus f3 certs list --limit 3 --reverse 0.. - - * Up to three certificates starting from instance 1413 to the oldest: - $ lotus f3 certs list --limit 3 ..1413 - - * Up to 3 certificates starting from instance 1413 to the newest: - $ lotus f3 certs list --limit 3 --reverse 1413.. - - * All certificates from instance 3 to 1413 in order of newest to oldest: - $ lotus f3 certs list 3..1413 - - help, h Shows a list of commands or help for one command - -OPTIONS: - --help, -h show help -``` - -#### lotus f3 certs get -``` -NAME: - lotus f3 certs get - Gets an F3 finality certificate to a given instance ID, or the latest certificate if no instance is specified. - -USAGE: - lotus f3 certs get [command options] [instance] - -OPTIONS: - --output value The output format. Supported formats: text, json (default: "text") - --help, -h show help -``` - -#### lotus f3 certs list -``` -NAME: - lotus f3 certs list - Lists a range of F3 finality certificates. - - By default the certificates are listed in newest to oldest order, - i.e. descending instance IDs. The order may be reversed using the - '--reverse' flag. - - A range may optionally be specified as the first argument to indicate - inclusive range of 'from' and 'to' instances in following notation: - '..'. Either or may be omitted, but not both. - An omitted value is always interpreted as 0, and an omitted - value indicates the latest instance. If both are specified, - must never exceed . - - If no range is specified, the latest 10 certificates are listed, i.e. - the range of '0..' with limit of 10. Otherwise, all certificates in - the specified range are listed unless limit is explicitly specified. - - Examples: - * All certificates from newest to oldest: - $ lotus f3 certs list 0.. - - * Three newest certificates: - $ lotus f3 certs list --limit 3 0.. - - * Three oldest certificates: - $ lotus f3 certs list --limit 3 --reverse 0.. - - * Up to three certificates starting from instance 1413 to the oldest: - $ lotus f3 certs list --limit 3 ..1413 - - * Up to 3 certificates starting from instance 1413 to the newest: - $ lotus f3 certs list --limit 3 --reverse 1413.. - - * All certificates from instance 3 to 1413 in order of newest to oldest: - $ lotus f3 certs list 3..1413 + The instance may be specified as the first argument. If unspecified, + the latest instance is used. USAGE: - lotus f3 certs list [command options] [range] - -OPTIONS: - --output value The output format. Supported formats: text, json (default: "text") - --limit value The maximum number of instances. A value less than 0 indicates no limit. (default: 10 when no range is specified. Otherwise, unlimited.) - --reverse Reverses the default order of output. (default: false) - --help, -h show help -``` - -### lotus f3 manifest -``` -NAME: - lotus f3 manifest - Gets the current manifest used by F3. - -USAGE: - lotus f3 manifest [command options] [arguments...] - -OPTIONS: - --output value The output format. Supported formats: text, json (default: "text") - --help, -h show help -``` - -### lotus f3 status -``` -NAME: - lotus f3 status - Checks the F3 status. - -USAGE: - lotus f3 status [command options] [arguments...] + lotus f3 list-miners [command options] [arguments...] OPTIONS: --help, -h show help From 5a90b0924481bfb78d62e0df64c2aa3cdfacae1e Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 14 Nov 2024 18:51:23 +0700 Subject: [PATCH 2/7] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e76d29b6568..7b0402fe647 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ # UNRELEASED +## New Features + +* Implement F3 utility CLIs to list the power table for a given instance and sum the proportional power of a set of actors that participate in a given instance. See: https://github.com/filecoin-project/lotus/pull/12698. + # UNRELEASED v1.31.0 See https://github.com/filecoin-project/lotus/blob/release/v1.31.0/CHANGELOG.md From 5240b8838b9a5f3f0113063c1c7a3ef950441b7a Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 14 Nov 2024 19:02:43 +0700 Subject: [PATCH 3/7] Address lint issue --- cli/f3.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cli/f3.go b/cli/f3.go index 61f0dc713d3..9c07643c9af 100644 --- a/cli/f3.go +++ b/cli/f3.go @@ -295,9 +295,8 @@ separated, pied to STDIN. Example: // Prune duplicate IDs. if _, ok := seenIDs[actorID]; ok { continue - } else { - seenIDs[actorID] = struct{}{} } + seenIDs[actorID] = struct{}{} scaled, key := pt.Get(actorID) if key == nil { return fmt.Errorf("actor ID %q not found in power table", actorID) From 2b7ec90f09e2293c186bbda55ec83290b74513db Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 14 Nov 2024 23:43:19 +0700 Subject: [PATCH 4/7] Regenerate CLI docs --- cli/f3.go | 18 ++-- documentation/en/cli-lotus.md | 189 ++++++++++++++++++++++++++++++++-- 2 files changed, 190 insertions(+), 17 deletions(-) diff --git a/cli/f3.go b/cli/f3.go index 9c07643c9af..4861c80e1e9 100644 --- a/cli/f3.go +++ b/cli/f3.go @@ -34,11 +34,7 @@ var ( { Name: "list-miners", Aliases: []string{"lm"}, - Usage: `Lists the miners that currently participate in F3 via this node. - -The instance may be specified as the first argument. If unspecified, -the latest instance is used. -`, + Usage: `Lists the miners that currently participate in F3 via this node.`, Action: func(cctx *cli.Context) error { api, closer, err := GetFullNodeAPIV1(cctx) if err != nil { @@ -84,9 +80,12 @@ the latest instance is used. Aliases: []string{"pt"}, Subcommands: []*cli.Command{ { - Name: "get", - Aliases: []string{"g"}, - Usage: "Get F3 power table at a specific instance ID or latest instance if none is specified.", + Name: "get", + Aliases: []string{"g"}, + Usage: `Get F3 power table at a specific instance ID or latest instance if none is specified. + +The instance may be specified as the first argument. If unspecified, +the latest instance is used.`, ArgsUsage: "[instance]", Flags: []cli.Flag{f3FlagPowerTableFromEC}, Before: func(cctx *cli.Context) error { @@ -184,8 +183,7 @@ the latest instance is used. The list of actors may be specified as Actor ID or miner address, space separated, pied to STDIN. Example: - $ echo "1413 t01234 f12345" | lotus f3 powertable get-proportion 42 -`, + $ echo "1413 t01234 f12345" | lotus f3 powertable get-proportion 42`, ArgsUsage: "[instance]", Flags: []cli.Flag{f3FlagPowerTableFromEC}, Before: func(cctx *cli.Context) error { diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index 6d62c05c8a1..51c98f81102 100644 --- a/documentation/en/cli-lotus.md +++ b/documentation/en/cli-lotus.md @@ -2799,10 +2799,6 @@ USAGE: COMMANDS: list-miners, lm Lists the miners that currently participate in F3 via this node. - - The instance may be specified as the first argument. If unspecified, - the latest instance is used. - powertable, pt certs, c Manages interactions with F3 finality certificates. manifest Gets the current manifest used by F3. @@ -2818,12 +2814,191 @@ OPTIONS: NAME: lotus f3 list-miners - Lists the miners that currently participate in F3 via this node. - The instance may be specified as the first argument. If unspecified, - the latest instance is used. +USAGE: + lotus f3 list-miners [command options] [arguments...] + +OPTIONS: + --help, -h show help +``` +### lotus f3 powertable +``` +NAME: + lotus f3 powertable USAGE: - lotus f3 list-miners [command options] [arguments...] + lotus f3 powertable command [command options] [arguments...] + +COMMANDS: + get, g Get F3 power table at a specific instance ID or latest instance if none is specified. + + The instance may be specified as the first argument. If unspecified, + the latest instance is used. + get-proportion, gp Gets the total proportion of power for a list of actors at a given instance. + + The instance may be specified as the first argument. If unspecified, + the latest instance is used. + + The list of actors may be specified as Actor ID or miner address, space + separated, pied to STDIN. Example: + $ echo "1413 t01234 f12345" | lotus f3 powertable get-proportion 42 + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help +``` + +#### lotus f3 powertable get +``` +NAME: + lotus f3 powertable get - Get F3 power table at a specific instance ID or latest instance if none is specified. + + The instance may be specified as the first argument. If unspecified, + the latest instance is used. + +USAGE: + lotus f3 powertable get [command options] [instance] + +OPTIONS: + --ec Whether to get the power table from EC. (default: false) + --help, -h show help +``` + +### lotus f3 certs +``` +NAME: + lotus f3 certs - Manages interactions with F3 finality certificates. + +USAGE: + lotus f3 certs command [command options] [arguments...] + +COMMANDS: + get Gets an F3 finality certificate to a given instance ID, or the latest certificate if no instance is specified. + list Lists a range of F3 finality certificates. + + By default the certificates are listed in newest to oldest order, + i.e. descending instance IDs. The order may be reversed using the + '--reverse' flag. + + A range may optionally be specified as the first argument to indicate + inclusive range of 'from' and 'to' instances in following notation: + '..'. Either or may be omitted, but not both. + An omitted value is always interpreted as 0, and an omitted + value indicates the latest instance. If both are specified, + must never exceed . + + If no range is specified, the latest 10 certificates are listed, i.e. + the range of '0..' with limit of 10. Otherwise, all certificates in + the specified range are listed unless limit is explicitly specified. + + Examples: + * All certificates from newest to oldest: + $ lotus f3 certs list 0.. + + * Three newest certificates: + $ lotus f3 certs list --limit 3 0.. + + * Three oldest certificates: + $ lotus f3 certs list --limit 3 --reverse 0.. + + * Up to three certificates starting from instance 1413 to the oldest: + $ lotus f3 certs list --limit 3 ..1413 + + * Up to 3 certificates starting from instance 1413 to the newest: + $ lotus f3 certs list --limit 3 --reverse 1413.. + + * All certificates from instance 3 to 1413 in order of newest to oldest: + $ lotus f3 certs list 3..1413 + + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help +``` + +#### lotus f3 certs get +``` +NAME: + lotus f3 certs get - Gets an F3 finality certificate to a given instance ID, or the latest certificate if no instance is specified. + +USAGE: + lotus f3 certs get [command options] [instance] + +OPTIONS: + --output value The output format. Supported formats: text, json (default: "text") + --help, -h show help +``` + +#### lotus f3 certs list +``` +NAME: + lotus f3 certs list - Lists a range of F3 finality certificates. + + By default the certificates are listed in newest to oldest order, + i.e. descending instance IDs. The order may be reversed using the + '--reverse' flag. + + A range may optionally be specified as the first argument to indicate + inclusive range of 'from' and 'to' instances in following notation: + '..'. Either or may be omitted, but not both. + An omitted value is always interpreted as 0, and an omitted + value indicates the latest instance. If both are specified, + must never exceed . + + If no range is specified, the latest 10 certificates are listed, i.e. + the range of '0..' with limit of 10. Otherwise, all certificates in + the specified range are listed unless limit is explicitly specified. + + Examples: + * All certificates from newest to oldest: + $ lotus f3 certs list 0.. + + * Three newest certificates: + $ lotus f3 certs list --limit 3 0.. + + * Three oldest certificates: + $ lotus f3 certs list --limit 3 --reverse 0.. + + * Up to three certificates starting from instance 1413 to the oldest: + $ lotus f3 certs list --limit 3 ..1413 + + * Up to 3 certificates starting from instance 1413 to the newest: + $ lotus f3 certs list --limit 3 --reverse 1413.. + + * All certificates from instance 3 to 1413 in order of newest to oldest: + $ lotus f3 certs list 3..1413 + + +USAGE: + lotus f3 certs list [command options] [range] + +OPTIONS: + --output value The output format. Supported formats: text, json (default: "text") + --limit value The maximum number of instances. A value less than 0 indicates no limit. (default: 10 when no range is specified. Otherwise, unlimited.) + --reverse Reverses the default order of output. (default: false) + --help, -h show help +``` + +### lotus f3 manifest +``` +NAME: + lotus f3 manifest - Gets the current manifest used by F3. + +USAGE: + lotus f3 manifest [command options] [arguments...] + +OPTIONS: + --output value The output format. Supported formats: text, json (default: "text") + --help, -h show help +``` + +### lotus f3 status +``` +NAME: + lotus f3 status - Checks the F3 status. + +USAGE: + lotus f3 status [command options] [arguments...] OPTIONS: --help, -h show help From 39f37ccd4612f412dee6ed8fa081d3e0a9600fdd Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Mon, 18 Nov 2024 08:58:46 +0000 Subject: [PATCH 5/7] Take instance ID via flag and actor IDs as args --- cli/f3.go | 42 +++++++++++++++++------------------ documentation/en/cli-lotus.md | 8 +++---- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/cli/f3.go b/cli/f3.go index 4861c80e1e9..ffcdfd759fd 100644 --- a/cli/f3.go +++ b/cli/f3.go @@ -1,7 +1,6 @@ package cli import ( - "bufio" "context" "embed" "encoding/json" @@ -178,17 +177,20 @@ the latest instance is used.`, Aliases: []string{"gp"}, Usage: `Gets the total proportion of power for a list of actors at a given instance. -The instance may be specified as the first argument. If unspecified, -the latest instance is used. +The instance may be specified via --instance flag. If unspecified, the +latest instance is used. The list of actors may be specified as Actor ID or miner address, space -separated, pied to STDIN. Example: - $ echo "1413 t01234 f12345" | lotus f3 powertable get-proportion 42`, - ArgsUsage: "[instance]", - Flags: []cli.Flag{f3FlagPowerTableFromEC}, +separated, via arguments. Example: + $ lotus f3 powertable get-proportion -i 42 1413 t01234 f12345`, + ArgsUsage: " [actor-id] ...", + Flags: []cli.Flag{ + f3FlagPowerTableFromEC, + f3FlagInstanceID, + }, Before: func(cctx *cli.Context) error { - if cctx.Args().Len() > 1 { - return fmt.Errorf("too many arguments") + if cctx.Args().Len() < 1 { + return fmt.Errorf("at least one actor ID must be specified") } return nil }, @@ -205,11 +207,8 @@ separated, pied to STDIN. Example: } var instance uint64 - if cctx.Args().Present() { - instance, err = strconv.ParseUint(cctx.Args().First(), 10, 64) - if err != nil { - return fmt.Errorf("parsing instance: %w", err) - } + if cctx.IsSet(f3FlagInstanceID.Name) { + instance = cctx.Uint64(f3FlagInstanceID.Name) if instance > progress.ID { // TODO: Technically we can return power table for instances ahead as long as // instance is within lookback. Implement it. @@ -257,20 +256,15 @@ separated, pied to STDIN. Example: } result.PowerTable.CID = actualPowerTableCID.String() - scanner := bufio.NewScanner(cctx.App.Reader) - if !scanner.Scan() { - return fmt.Errorf("reading actor IDs from stdin: %w", scanner.Err()) - } - inputIDs := strings.Split(strings.TrimSpace(scanner.Text()), " ") - pt := gpbft.NewPowerTable() if err := pt.Add(powerEntries...); err != nil { return fmt.Errorf("constructing power table from entries: %w", err) } result.PowerTable.ScaledTotal = pt.ScaledTotal + inputActorIDs := cctx.Args().Slice() seenIDs := map[gpbft.ActorID]struct{}{} - for _, stringID := range inputIDs { + for _, stringID := range inputActorIDs { var actorID gpbft.ActorID switch addr, err := address.NewFromString(stringID); { case err == nil: @@ -564,6 +558,12 @@ Examples: Name: "ec", Usage: "Whether to get the power table from EC.", } + f3FlagInstanceID = &cli.Uint64Flag{ + Name: "instance", + Aliases: []string{"i"}, + Usage: "The F3 instance ID.", + DefaultText: "Latest Instance", + } //go:embed templates/f3_*.go.tmpl f3TemplatesFS embed.FS f3Templates = template.Must( diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index 51c98f81102..f67095ffa50 100644 --- a/documentation/en/cli-lotus.md +++ b/documentation/en/cli-lotus.md @@ -2836,12 +2836,12 @@ COMMANDS: the latest instance is used. get-proportion, gp Gets the total proportion of power for a list of actors at a given instance. - The instance may be specified as the first argument. If unspecified, - the latest instance is used. + The instance may be specified via --instance flag. If unspecified, the + latest instance is used. The list of actors may be specified as Actor ID or miner address, space - separated, pied to STDIN. Example: - $ echo "1413 t01234 f12345" | lotus f3 powertable get-proportion 42 + separated, via arguments. Example: + $ lotus f3 powertable get-proportion -i 42 1413 t01234 f12345 help, h Shows a list of commands or help for one command OPTIONS: From 65c673e0d8183cfdfb4995f11512090f5681df6f Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Mon, 18 Nov 2024 16:12:13 +0000 Subject: [PATCH 6/7] Reduce indentation by defiling top level vars --- cli/f3.go | 983 +++++++++++++++++++++++++++--------------------------- 1 file changed, 494 insertions(+), 489 deletions(-) diff --git a/cli/f3.go b/cli/f3.go index ffcdfd759fd..36f302ed6e5 100644 --- a/cli/f3.go +++ b/cli/f3.go @@ -25,157 +25,163 @@ import ( "github.com/filecoin-project/lotus/lib/tablewriter" ) -var ( - F3Cmd = &cli.Command{ - Name: "f3", - Usage: "Manages Filecoin Fast Finality (F3) interactions", - Subcommands: []*cli.Command{ - { - Name: "list-miners", - Aliases: []string{"lm"}, - Usage: `Lists the miners that currently participate in F3 via this node.`, - Action: func(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPIV1(cctx) - if err != nil { - return err - } - defer closer() +var F3Cmd = &cli.Command{ + Name: "f3", + Usage: "Manages Filecoin Fast Finality (F3) interactions", + Subcommands: []*cli.Command{ + f3SbCmdListMiners, + f3SubCmdPowerTable, + f3SubCmdCerts, + f3SubCmdManifest, + f3SubCmdStatus, + }, +} +var f3SbCmdListMiners = &cli.Command{ + Name: "list-miners", + Aliases: []string{"lm"}, + Usage: `Lists the miners that currently participate in F3 via this node.`, + Action: func(cctx *cli.Context) error { + api, closer, err := GetFullNodeAPIV1(cctx) + if err != nil { + return err + } + defer closer() + + miners, err := api.F3ListParticipants(cctx.Context) + if err != nil { + return fmt.Errorf("listing participants: %w", err) + } + if len(miners) == 0 { + _, err = fmt.Fprintln(cctx.App.Writer, "No miners.") + return err + } + const ( + miner = "Miner" + from = "From" + to = "To" + ) + tw := tablewriter.New( + tablewriter.Col(miner), + tablewriter.Col(from), + tablewriter.Col(to), + ) + for _, participant := range miners { + addr, err := address.NewIDAddress(participant.MinerID) + if err != nil { + return fmt.Errorf("converting miner ID to address: %w", err) + } + + tw.Write(map[string]interface{}{ + miner: addr, + from: participant.FromInstance, + to: participant.FromInstance + participant.ValidityTerm, + }) + } + return tw.Flush(cctx.App.Writer) + }, +} +var f3SubCmdPowerTable = &cli.Command{ + Name: "powertable", + Aliases: []string{"pt"}, + Subcommands: []*cli.Command{ + { + Name: "get", + Aliases: []string{"g"}, + Usage: `Get F3 power table at a specific instance ID or latest instance if none is specified. - miners, err := api.F3ListParticipants(cctx.Context) +The instance may be specified as the first argument. If unspecified, +the latest instance is used.`, + ArgsUsage: "[instance]", + Flags: []cli.Flag{f3FlagPowerTableFromEC}, + Before: func(cctx *cli.Context) error { + if cctx.Args().Len() > 1 { + return fmt.Errorf("too many arguments") + } + return nil + }, + Action: func(cctx *cli.Context) error { + api, closer, err := GetFullNodeAPIV1(cctx) + if err != nil { + return err + } + defer closer() + + progress, err := api.F3GetProgress(cctx.Context) + if err != nil { + return fmt.Errorf("getting progress: %w", err) + } + + var instance uint64 + if cctx.Args().Present() { + instance, err = strconv.ParseUint(cctx.Args().First(), 10, 64) if err != nil { - return fmt.Errorf("listing participants: %w", err) + return fmt.Errorf("parsing instance: %w", err) } - if len(miners) == 0 { - _, err = fmt.Fprintln(cctx.App.Writer, "No miners.") - return err + if instance > progress.ID { + // TODO: Technically we can return power table for instances ahead as long as + // instance is within lookback. Implement it. + return fmt.Errorf("instance is ahead the current instance in progress: %d > %d", instance, progress.ID) } - const ( - miner = "Miner" - from = "From" - to = "To" - ) - tw := tablewriter.New( - tablewriter.Col(miner), - tablewriter.Col(from), - tablewriter.Col(to), - ) - for _, participant := range miners { - addr, err := address.NewIDAddress(participant.MinerID) - if err != nil { - return fmt.Errorf("converting miner ID to address: %w", err) - } - - tw.Write(map[string]interface{}{ - miner: addr, - from: participant.FromInstance, - to: participant.FromInstance + participant.ValidityTerm, - }) + } else { + instance = progress.ID + } + + ltsk, expectedPowerTableCID, err := f3GetPowerTableTSKByInstance(cctx.Context, api, instance) + if err != nil { + return fmt.Errorf("getting power table tsk for instance %d: %w", instance, err) + } + + var result = struct { + Instance uint64 + FromEC bool + PowerTable struct { + CID string + Entries gpbft.PowerEntries + Total gpbft.StoragePower + ScaledTotal int64 } - return tw.Flush(cctx.App.Writer) - }, + }{ + Instance: instance, + FromEC: cctx.Bool(f3FlagPowerTableFromEC.Name), + } + if result.FromEC { + result.PowerTable.Entries, err = api.F3GetECPowerTable(cctx.Context, ltsk) + } else { + result.PowerTable.Entries, err = api.F3GetF3PowerTable(cctx.Context, ltsk) + } + if err != nil { + return fmt.Errorf("getting f3 power table at instance %d: %w", instance, err) + } + + pt := gpbft.NewPowerTable() + if err := pt.Add(result.PowerTable.Entries...); err != nil { + // Sanity check the entries returned by the API. + return fmt.Errorf("retrieved power table is not valid for instance %d: %w", instance, err) + } + result.PowerTable.Total = pt.Total + result.PowerTable.ScaledTotal = pt.ScaledTotal + + actualPowerTableCID, err := certs.MakePowerTableCID(result.PowerTable.Entries) + if err != nil { + return fmt.Errorf("gettingh power table CID at instance %d: %w", instance, err) + } + if !expectedPowerTableCID.Equals(actualPowerTableCID) { + return fmt.Errorf("expected power table CID %s at instance %d, got: %s", expectedPowerTableCID, instance, actualPowerTableCID) + } + result.PowerTable.CID = actualPowerTableCID.String() + + output, err := json.MarshalIndent(result, "", " ") + if err != nil { + return fmt.Errorf("marshalling f3 power table at instance %d: %w", instance, err) + } + _, _ = fmt.Fprint(cctx.App.Writer, string(output)) + return nil }, - { - Name: "powertable", - Aliases: []string{"pt"}, - Subcommands: []*cli.Command{ - { - Name: "get", - Aliases: []string{"g"}, - Usage: `Get F3 power table at a specific instance ID or latest instance if none is specified. - -The instance may be specified as the first argument. If unspecified, -the latest instance is used.`, - ArgsUsage: "[instance]", - Flags: []cli.Flag{f3FlagPowerTableFromEC}, - Before: func(cctx *cli.Context) error { - if cctx.Args().Len() > 1 { - return fmt.Errorf("too many arguments") - } - return nil - }, - Action: func(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPIV1(cctx) - if err != nil { - return err - } - defer closer() - - progress, err := api.F3GetProgress(cctx.Context) - if err != nil { - return fmt.Errorf("getting progress: %w", err) - } - - var instance uint64 - if cctx.Args().Present() { - instance, err = strconv.ParseUint(cctx.Args().First(), 10, 64) - if err != nil { - return fmt.Errorf("parsing instance: %w", err) - } - if instance > progress.ID { - // TODO: Technically we can return power table for instances ahead as long as - // instance is within lookback. Implement it. - return fmt.Errorf("instance is ahead the current instance in progress: %d > %d", instance, progress.ID) - } - } else { - instance = progress.ID - } - - ltsk, expectedPowerTableCID, err := f3GetPowerTableTSKByInstance(cctx.Context, api, instance) - if err != nil { - return fmt.Errorf("getting power table tsk for instance %d: %w", instance, err) - } - - var result = struct { - Instance uint64 - FromEC bool - PowerTable struct { - CID string - Entries gpbft.PowerEntries - Total gpbft.StoragePower - ScaledTotal int64 - } - }{ - Instance: instance, - FromEC: cctx.Bool(f3FlagPowerTableFromEC.Name), - } - if result.FromEC { - result.PowerTable.Entries, err = api.F3GetECPowerTable(cctx.Context, ltsk) - } else { - result.PowerTable.Entries, err = api.F3GetF3PowerTable(cctx.Context, ltsk) - } - if err != nil { - return fmt.Errorf("getting f3 power table at instance %d: %w", instance, err) - } - - pt := gpbft.NewPowerTable() - if err := pt.Add(result.PowerTable.Entries...); err != nil { - // Sanity check the entries returned by the API. - return fmt.Errorf("retrieved power table is not valid for instance %d: %w", instance, err) - } - result.PowerTable.Total = pt.Total - result.PowerTable.ScaledTotal = pt.ScaledTotal - - actualPowerTableCID, err := certs.MakePowerTableCID(result.PowerTable.Entries) - if err != nil { - return fmt.Errorf("gettingh power table CID at instance %d: %w", instance, err) - } - if !expectedPowerTableCID.Equals(actualPowerTableCID) { - return fmt.Errorf("expected power table CID %s at instance %d, got: %s", expectedPowerTableCID, instance, actualPowerTableCID) - } - result.PowerTable.CID = actualPowerTableCID.String() - - output, err := json.MarshalIndent(result, "", " ") - if err != nil { - return fmt.Errorf("marshalling f3 power table at instance %d: %w", instance, err) - } - _, _ = fmt.Fprint(cctx.App.Writer, string(output)) - return nil - }, - }, - { - Name: "get-proportion", - Aliases: []string{"gp"}, - Usage: `Gets the total proportion of power for a list of actors at a given instance. + }, + { + Name: "get-proportion", + Aliases: []string{"gp"}, + Usage: `Gets the total proportion of power for a list of actors at a given instance. The instance may be specified via --instance flag. If unspecified, the latest instance is used. @@ -183,182 +189,182 @@ latest instance is used. The list of actors may be specified as Actor ID or miner address, space separated, via arguments. Example: $ lotus f3 powertable get-proportion -i 42 1413 t01234 f12345`, - ArgsUsage: " [actor-id] ...", - Flags: []cli.Flag{ - f3FlagPowerTableFromEC, - f3FlagInstanceID, - }, - Before: func(cctx *cli.Context) error { - if cctx.Args().Len() < 1 { - return fmt.Errorf("at least one actor ID must be specified") - } - return nil - }, - Action: func(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPIV1(cctx) - if err != nil { - return err - } - defer closer() - - progress, err := api.F3GetProgress(cctx.Context) - if err != nil { - return fmt.Errorf("getting progress: %w", err) - } - - var instance uint64 - if cctx.IsSet(f3FlagInstanceID.Name) { - instance = cctx.Uint64(f3FlagInstanceID.Name) - if instance > progress.ID { - // TODO: Technically we can return power table for instances ahead as long as - // instance is within lookback. Implement it. - return fmt.Errorf("instance is ahead the current instance in progress: %d > %d", instance, progress.ID) - } - } else { - instance = progress.ID - } - - ltsk, expectedPowerTableCID, err := f3GetPowerTableTSKByInstance(cctx.Context, api, instance) - if err != nil { - return fmt.Errorf("getting power table tsk for instance %d: %w", instance, err) - } - - var result = struct { - Instance uint64 - FromEC bool - PowerTable struct { - CID string - ScaledTotal int64 - } - ScaledSum int64 - Proportion float64 - }{ - Instance: instance, - FromEC: cctx.Bool(f3FlagPowerTableFromEC.Name), - } - - var powerEntries gpbft.PowerEntries - if result.FromEC { - powerEntries, err = api.F3GetECPowerTable(cctx.Context, ltsk) - } else { - powerEntries, err = api.F3GetF3PowerTable(cctx.Context, ltsk) - } - if err != nil { - return fmt.Errorf("getting f3 power table at instance %d: %w", instance, err) - } - - actualPowerTableCID, err := certs.MakePowerTableCID(powerEntries) - if err != nil { - return fmt.Errorf("gettingh power table CID at instance %d: %w", instance, err) - } - if !expectedPowerTableCID.Equals(actualPowerTableCID) { - return fmt.Errorf("expected power table CID %s at instance %d, got: %s", expectedPowerTableCID, instance, actualPowerTableCID) - } - result.PowerTable.CID = actualPowerTableCID.String() - - pt := gpbft.NewPowerTable() - if err := pt.Add(powerEntries...); err != nil { - return fmt.Errorf("constructing power table from entries: %w", err) - } - result.PowerTable.ScaledTotal = pt.ScaledTotal - - inputActorIDs := cctx.Args().Slice() - seenIDs := map[gpbft.ActorID]struct{}{} - for _, stringID := range inputActorIDs { - var actorID gpbft.ActorID - switch addr, err := address.NewFromString(stringID); { - case err == nil: - idAddr, err := address.IDFromAddress(addr) - if err != nil { - return fmt.Errorf("parsing ID from address %q: %w", stringID, err) - } - actorID = gpbft.ActorID(idAddr) - case errors.Is(err, address.ErrUnknownNetwork), - errors.Is(err, address.ErrUnknownProtocol): - // Try parsing as uint64 straight up. - id, err := strconv.ParseUint(stringID, 10, 64) - if err != nil { - return fmt.Errorf("parsing as uint64 %q: %w", stringID, err) - } - actorID = gpbft.ActorID(id) - default: - return fmt.Errorf("parsing address %q: %w", stringID, err) - } - // Prune duplicate IDs. - if _, ok := seenIDs[actorID]; ok { - continue - } - seenIDs[actorID] = struct{}{} - scaled, key := pt.Get(actorID) - if key == nil { - return fmt.Errorf("actor ID %q not found in power table", actorID) - } - result.ScaledSum += scaled - } - result.Proportion = float64(result.ScaledSum) / float64(result.PowerTable.ScaledTotal) - output, err := json.MarshalIndent(result, "", " ") - if err != nil { - return fmt.Errorf("marshalling f3 power table at instance %d: %w", instance, err) - } - _, _ = fmt.Fprint(cctx.App.Writer, string(output)) - return nil - }, - }, - }, + ArgsUsage: " [actor-id] ...", + Flags: []cli.Flag{ + f3FlagPowerTableFromEC, + f3FlagInstanceID, }, - { - Name: "certs", - Aliases: []string{"c"}, - Usage: "Manages interactions with F3 finality certificates.", - Subcommands: []*cli.Command{ - { - Name: "get", - Usage: "Gets an F3 finality certificate to a given instance ID, " + - "or the latest certificate if no instance is specified.", - ArgsUsage: "[instance]", - Flags: []cli.Flag{ - f3FlagOutput, - }, - Before: func(cctx *cli.Context) error { - if count := cctx.NArg(); count > 1 { - return fmt.Errorf("too many arguments: expected at most 1 but got %d", count) - } - return nil - }, - Action: func(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPIV1(cctx) - if err != nil { - return err - } - defer closer() - - // Get the certificate, either for the given instance or the latest if no - // instance is specified. - var cert *certs.FinalityCertificate - if cctx.Args().Present() { - var instance uint64 - instance, err = strconv.ParseUint(cctx.Args().First(), 10, 64) - if err != nil { - return fmt.Errorf("parsing instance: %w", err) - } - cert, err = api.F3GetCertificate(cctx.Context, instance) - } else { - cert, err = api.F3GetLatestCertificate(cctx.Context) - } - if err != nil { - return fmt.Errorf("getting finality certificate: %w", err) - } - if cert == nil { - _, _ = fmt.Fprintln(cctx.App.ErrWriter, "No certificate.") - return nil - } - - return outputFinalityCertificate(cctx, cert) - }, - }, - { - Name: "list", - Usage: `Lists a range of F3 finality certificates. + Before: func(cctx *cli.Context) error { + if cctx.Args().Len() < 1 { + return fmt.Errorf("at least one actor ID must be specified") + } + return nil + }, + Action: func(cctx *cli.Context) error { + api, closer, err := GetFullNodeAPIV1(cctx) + if err != nil { + return err + } + defer closer() + + progress, err := api.F3GetProgress(cctx.Context) + if err != nil { + return fmt.Errorf("getting progress: %w", err) + } + + var instance uint64 + if cctx.IsSet(f3FlagInstanceID.Name) { + instance = cctx.Uint64(f3FlagInstanceID.Name) + if instance > progress.ID { + // TODO: Technically we can return power table for instances ahead as long as + // instance is within lookback. Implement it. + return fmt.Errorf("instance is ahead the current instance in progress: %d > %d", instance, progress.ID) + } + } else { + instance = progress.ID + } + + ltsk, expectedPowerTableCID, err := f3GetPowerTableTSKByInstance(cctx.Context, api, instance) + if err != nil { + return fmt.Errorf("getting power table tsk for instance %d: %w", instance, err) + } + + var result = struct { + Instance uint64 + FromEC bool + PowerTable struct { + CID string + ScaledTotal int64 + } + ScaledSum int64 + Proportion float64 + }{ + Instance: instance, + FromEC: cctx.Bool(f3FlagPowerTableFromEC.Name), + } + + var powerEntries gpbft.PowerEntries + if result.FromEC { + powerEntries, err = api.F3GetECPowerTable(cctx.Context, ltsk) + } else { + powerEntries, err = api.F3GetF3PowerTable(cctx.Context, ltsk) + } + if err != nil { + return fmt.Errorf("getting f3 power table at instance %d: %w", instance, err) + } + + actualPowerTableCID, err := certs.MakePowerTableCID(powerEntries) + if err != nil { + return fmt.Errorf("gettingh power table CID at instance %d: %w", instance, err) + } + if !expectedPowerTableCID.Equals(actualPowerTableCID) { + return fmt.Errorf("expected power table CID %s at instance %d, got: %s", expectedPowerTableCID, instance, actualPowerTableCID) + } + result.PowerTable.CID = actualPowerTableCID.String() + + pt := gpbft.NewPowerTable() + if err := pt.Add(powerEntries...); err != nil { + return fmt.Errorf("constructing power table from entries: %w", err) + } + result.PowerTable.ScaledTotal = pt.ScaledTotal + + inputActorIDs := cctx.Args().Slice() + seenIDs := map[gpbft.ActorID]struct{}{} + for _, stringID := range inputActorIDs { + var actorID gpbft.ActorID + switch addr, err := address.NewFromString(stringID); { + case err == nil: + idAddr, err := address.IDFromAddress(addr) + if err != nil { + return fmt.Errorf("parsing ID from address %q: %w", stringID, err) + } + actorID = gpbft.ActorID(idAddr) + case errors.Is(err, address.ErrUnknownNetwork), + errors.Is(err, address.ErrUnknownProtocol): + // Try parsing as uint64 straight up. + id, err := strconv.ParseUint(stringID, 10, 64) + if err != nil { + return fmt.Errorf("parsing as uint64 %q: %w", stringID, err) + } + actorID = gpbft.ActorID(id) + default: + return fmt.Errorf("parsing address %q: %w", stringID, err) + } + // Prune duplicate IDs. + if _, ok := seenIDs[actorID]; ok { + continue + } + seenIDs[actorID] = struct{}{} + scaled, key := pt.Get(actorID) + if key == nil { + return fmt.Errorf("actor ID %q not found in power table", actorID) + } + result.ScaledSum += scaled + } + result.Proportion = float64(result.ScaledSum) / float64(result.PowerTable.ScaledTotal) + output, err := json.MarshalIndent(result, "", " ") + if err != nil { + return fmt.Errorf("marshalling f3 power table at instance %d: %w", instance, err) + } + _, _ = fmt.Fprint(cctx.App.Writer, string(output)) + return nil + }, + }, + }, +} +var f3SubCmdCerts = &cli.Command{ + Name: "certs", + Aliases: []string{"c"}, + Usage: "Manages interactions with F3 finality certificates.", + Subcommands: []*cli.Command{ + { + Name: "get", + Usage: "Gets an F3 finality certificate to a given instance ID, " + + "or the latest certificate if no instance is specified.", + ArgsUsage: "[instance]", + Flags: []cli.Flag{ + f3FlagOutput, + }, + Before: func(cctx *cli.Context) error { + if count := cctx.NArg(); count > 1 { + return fmt.Errorf("too many arguments: expected at most 1 but got %d", count) + } + return nil + }, + Action: func(cctx *cli.Context) error { + api, closer, err := GetFullNodeAPIV1(cctx) + if err != nil { + return err + } + defer closer() + + // Get the certificate, either for the given instance or the latest if no + // instance is specified. + var cert *certs.FinalityCertificate + if cctx.Args().Present() { + var instance uint64 + instance, err = strconv.ParseUint(cctx.Args().First(), 10, 64) + if err != nil { + return fmt.Errorf("parsing instance: %w", err) + } + cert, err = api.F3GetCertificate(cctx.Context, instance) + } else { + cert, err = api.F3GetLatestCertificate(cctx.Context) + } + if err != nil { + return fmt.Errorf("getting finality certificate: %w", err) + } + if cert == nil { + _, _ = fmt.Fprintln(cctx.App.ErrWriter, "No certificate.") + return nil + } + + return outputFinalityCertificate(cctx, cert) + }, + }, + { + Name: "list", + Usage: `Lists a range of F3 finality certificates. By default the certificates are listed in newest to oldest order, i.e. descending instance IDs. The order may be reversed using the @@ -394,188 +400,187 @@ Examples: * All certificates from instance 3 to 1413 in order of newest to oldest: $ lotus f3 certs list 3..1413 `, - ArgsUsage: "[range]", - Flags: []cli.Flag{ - f3FlagOutput, - f3FlagInstanceLimit, - f3FlagReverseOrder, - }, - Before: func(cctx *cli.Context) error { - if count := cctx.NArg(); count > 1 { - return fmt.Errorf("too many arguments: expected at most 1 but got %d", count) - } - return nil - }, - Action: func(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPIV1(cctx) - if err != nil { - return err - } - defer closer() - - limit := cctx.Int(f3FlagInstanceLimit.Name) - reverse := cctx.Bool(f3FlagReverseOrder.Name) - fromTo := cctx.Args().First() - if fromTo == "" { - fromTo = "0.." - if !cctx.IsSet(f3FlagInstanceLimit.Name) { - // Default to limit of 10 if no explicit range and limit is given. - limit = 10 - } - } - r, err := newRanger(fromTo, limit, reverse, func() (uint64, error) { - latest, err := api.F3GetLatestCertificate(cctx.Context) - if err != nil { - return 0, fmt.Errorf("getting latest finality certificate: %w", err) - } - if latest == nil { - return 0, errors.New("no latest finality certificate") - } - return latest.GPBFTInstance, nil - }) - if err != nil { - return err - } - - var cert *certs.FinalityCertificate - for cctx.Context.Err() == nil { - next, proceed := r.next() - if !proceed { - return nil - } - cert, err = api.F3GetCertificate(cctx.Context, next) - if err != nil { - return fmt.Errorf("getting finality certificate for instance %d: %w", next, err) - } - if cert == nil { - // This is unexpected, because the range of iteration was determined earlier and - // certstore should to have all the certs. Error out. - return fmt.Errorf("nil finality certificate for instance %d", next) - } - if err := outputFinalityCertificate(cctx, cert); err != nil { - return err - } - _, _ = fmt.Fprintln(cctx.App.Writer) - } - return nil - }, - }, - }, + ArgsUsage: "[range]", + Flags: []cli.Flag{ + f3FlagOutput, + f3FlagInstanceLimit, + f3FlagReverseOrder, }, - { - Name: "manifest", - Usage: "Gets the current manifest used by F3.", - Flags: []cli.Flag{f3FlagOutput}, - Action: func(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPIV1(cctx) - if err != nil { - return err - } - defer closer() - - manifest, err := api.F3GetManifest(cctx.Context) - if err != nil { - return fmt.Errorf("getting manifest: %w", err) - } - switch output := cctx.String(f3FlagOutput.Name); strings.ToLower(output) { - case "text": - return prettyPrintManifest(cctx.App.Writer, manifest) - case "json": - encoder := json.NewEncoder(cctx.App.Writer) - encoder.SetIndent("", " ") - return encoder.Encode(manifest) - default: - return fmt.Errorf("unknown output format: %s", output) - } - }, + Before: func(cctx *cli.Context) error { + if count := cctx.NArg(); count > 1 { + return fmt.Errorf("too many arguments: expected at most 1 but got %d", count) + } + return nil }, - { - Name: "status", - Usage: "Checks the F3 status.", - Action: func(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPIV1(cctx) - if err != nil { - return err + Action: func(cctx *cli.Context) error { + api, closer, err := GetFullNodeAPIV1(cctx) + if err != nil { + return err + } + defer closer() + + limit := cctx.Int(f3FlagInstanceLimit.Name) + reverse := cctx.Bool(f3FlagReverseOrder.Name) + fromTo := cctx.Args().First() + if fromTo == "" { + fromTo = "0.." + if !cctx.IsSet(f3FlagInstanceLimit.Name) { + // Default to limit of 10 if no explicit range and limit is given. + limit = 10 } - defer closer() - - running, err := api.F3IsRunning(cctx.Context) + } + r, err := newRanger(fromTo, limit, reverse, func() (uint64, error) { + latest, err := api.F3GetLatestCertificate(cctx.Context) if err != nil { - return fmt.Errorf("getting running state: %w", err) + return 0, fmt.Errorf("getting latest finality certificate: %w", err) + } + if latest == nil { + return 0, errors.New("no latest finality certificate") } - _, _ = fmt.Fprintf(cctx.App.Writer, "Running: %t\n", running) - if !running { + return latest.GPBFTInstance, nil + }) + if err != nil { + return err + } + + var cert *certs.FinalityCertificate + for cctx.Context.Err() == nil { + next, proceed := r.next() + if !proceed { return nil } - - progress, err := api.F3GetProgress(cctx.Context) + cert, err = api.F3GetCertificate(cctx.Context, next) if err != nil { - return fmt.Errorf("getting progress: %w", err) + return fmt.Errorf("getting finality certificate for instance %d: %w", next, err) } - - _, _ = fmt.Fprintln(cctx.App.Writer, "Progress:") - _, _ = fmt.Fprintf(cctx.App.Writer, " Instance: %d\n", progress.ID) - _, _ = fmt.Fprintf(cctx.App.Writer, " Round: %d\n", progress.Round) - _, _ = fmt.Fprintf(cctx.App.Writer, " Phase: %s\n", progress.Phase) - - manifest, err := api.F3GetManifest(cctx.Context) - if err != nil { - return fmt.Errorf("getting manifest: %w", err) + if cert == nil { + // This is unexpected, because the range of iteration was determined earlier and + // certstore should to have all the certs. Error out. + return fmt.Errorf("nil finality certificate for instance %d", next) } - return prettyPrintManifest(cctx.App.Writer, manifest) - }, + if err := outputFinalityCertificate(cctx, cert); err != nil { + return err + } + _, _ = fmt.Fprintln(cctx.App.Writer) + } + return nil }, }, - } + }, +} +var f3SubCmdManifest = &cli.Command{ + Name: "manifest", + Usage: "Gets the current manifest used by F3.", + Flags: []cli.Flag{f3FlagOutput}, + Action: func(cctx *cli.Context) error { + api, closer, err := GetFullNodeAPIV1(cctx) + if err != nil { + return err + } + defer closer() - // TODO: we should standardise format as a top level flag. For now, here is an f3 - // specific one. - // See: https://github.com/filecoin-project/lotus/issues/12616 - f3FlagOutput = &cli.StringFlag{ - Name: "output", - Usage: "The output format. Supported formats: text, json", - Value: "text", - Action: func(cctx *cli.Context, output string) error { - switch output { - case "text", "json": - return nil - default: - return fmt.Errorf("unknown output format: %s", output) - } - }, - } - f3FlagInstanceLimit = &cli.IntFlag{ - Name: "limit", - Usage: "The maximum number of instances. A value less than 0 indicates no limit.", - DefaultText: "10 when no range is specified. Otherwise, unlimited.", - Value: -1, - } - f3FlagReverseOrder = &cli.BoolFlag{ - Name: "reverse", - Usage: "Reverses the default order of output. ", - } - f3FlagPowerTableFromEC = &cli.BoolFlag{ - Name: "ec", - Usage: "Whether to get the power table from EC.", - } - f3FlagInstanceID = &cli.Uint64Flag{ - Name: "instance", - Aliases: []string{"i"}, - Usage: "The F3 instance ID.", - DefaultText: "Latest Instance", - } - //go:embed templates/f3_*.go.tmpl - f3TemplatesFS embed.FS - f3Templates = template.Must( - template.New(""). - Funcs(template.FuncMap{ - "ptDiffToString": f3PowerTableDiffsToString, - "tipSetKeyToLotusTipSetKey": types.TipSetKeyFromBytes, - "add": func(a, b int) int { return a + b }, - "sub": func(a, b int) int { return a - b }, - }). - ParseFS(f3TemplatesFS, "templates/f3_*.go.tmpl"), - ) + manifest, err := api.F3GetManifest(cctx.Context) + if err != nil { + return fmt.Errorf("getting manifest: %w", err) + } + switch output := cctx.String(f3FlagOutput.Name); strings.ToLower(output) { + case "text": + return prettyPrintManifest(cctx.App.Writer, manifest) + case "json": + encoder := json.NewEncoder(cctx.App.Writer) + encoder.SetIndent("", " ") + return encoder.Encode(manifest) + default: + return fmt.Errorf("unknown output format: %s", output) + } + }, +} +var f3SubCmdStatus = &cli.Command{ + Name: "status", + Usage: "Checks the F3 status.", + Action: func(cctx *cli.Context) error { + api, closer, err := GetFullNodeAPIV1(cctx) + if err != nil { + return err + } + defer closer() + + running, err := api.F3IsRunning(cctx.Context) + if err != nil { + return fmt.Errorf("getting running state: %w", err) + } + _, _ = fmt.Fprintf(cctx.App.Writer, "Running: %t\n", running) + if !running { + return nil + } + + progress, err := api.F3GetProgress(cctx.Context) + if err != nil { + return fmt.Errorf("getting progress: %w", err) + } + + _, _ = fmt.Fprintln(cctx.App.Writer, "Progress:") + _, _ = fmt.Fprintf(cctx.App.Writer, " Instance: %d\n", progress.ID) + _, _ = fmt.Fprintf(cctx.App.Writer, " Round: %d\n", progress.Round) + _, _ = fmt.Fprintf(cctx.App.Writer, " Phase: %s\n", progress.Phase) + + manifest, err := api.F3GetManifest(cctx.Context) + if err != nil { + return fmt.Errorf("getting manifest: %w", err) + } + return prettyPrintManifest(cctx.App.Writer, manifest) + }, +} + +// TODO: we should standardise format as a top level flag. For now, here is an f3 +// +// specific one. +// See: https://github.com/filecoin-project/lotus/issues/12616 +var f3FlagOutput = &cli.StringFlag{ + Name: "output", + Usage: "The output format. Supported formats: text, json", + Value: "text", + Action: func(cctx *cli.Context, output string) error { + switch output { + case "text", "json": + return nil + default: + return fmt.Errorf("unknown output format: %s", output) + } + }, +} +var f3FlagInstanceLimit = &cli.IntFlag{ + Name: "limit", + Usage: "The maximum number of instances. A value less than 0 indicates no limit.", + DefaultText: "10 when no range is specified. Otherwise, unlimited.", + Value: -1, +} +var f3FlagReverseOrder = &cli.BoolFlag{ + Name: "reverse", + Usage: "Reverses the default order of output. ", +} +var f3FlagPowerTableFromEC = &cli.BoolFlag{ + Name: "ec", + Usage: "Whether to get the power table from EC.", +} +var f3FlagInstanceID = &cli.Uint64Flag{ + Name: "instance", + Aliases: []string{"i"}, + Usage: "The F3 instance ID.", + DefaultText: "Latest Instance", +} + +//go:embed templates/f3_*.go.tmpl +var f3TemplatesFS embed.FS +var f3Templates = template.Must( + template.New(""). + Funcs(template.FuncMap{ + "ptDiffToString": f3PowerTableDiffsToString, + "tipSetKeyToLotusTipSetKey": types.TipSetKeyFromBytes, + "add": func(a, b int) int { return a + b }, + "sub": func(a, b int) int { return a - b }, + }). + ParseFS(f3TemplatesFS, "templates/f3_*.go.tmpl"), ) func f3GetPowerTableTSKByInstance(ctx context.Context, api v1api.FullNode, instance uint64) (types.TipSetKey, cid.Cid, error) { From cb52aa91a93c65f1b4aefd14d0e850d2743069ee Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Tue, 19 Nov 2024 10:05:55 +0000 Subject: [PATCH 7/7] Work around bug in docsgencli by using one-liner usage --- cli/f3.go | 22 ++++++---------------- documentation/en/cli-lotus.md | 27 ++++++++++++++------------- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/cli/f3.go b/cli/f3.go index 36f302ed6e5..02874750711 100644 --- a/cli/f3.go +++ b/cli/f3.go @@ -85,12 +85,9 @@ var f3SubCmdPowerTable = &cli.Command{ Aliases: []string{"pt"}, Subcommands: []*cli.Command{ { - Name: "get", - Aliases: []string{"g"}, - Usage: `Get F3 power table at a specific instance ID or latest instance if none is specified. - -The instance may be specified as the first argument. If unspecified, -the latest instance is used.`, + Name: "get", + Aliases: []string{"g"}, + Usage: `Get F3 power table at a specific instance ID or latest instance if none is specified.`, ArgsUsage: "[instance]", Flags: []cli.Flag{f3FlagPowerTableFromEC}, Before: func(cctx *cli.Context) error { @@ -179,16 +176,9 @@ the latest instance is used.`, }, }, { - Name: "get-proportion", - Aliases: []string{"gp"}, - Usage: `Gets the total proportion of power for a list of actors at a given instance. - -The instance may be specified via --instance flag. If unspecified, the -latest instance is used. - -The list of actors may be specified as Actor ID or miner address, space -separated, via arguments. Example: - $ lotus f3 powertable get-proportion -i 42 1413 t01234 f12345`, + Name: "get-proportion", + Aliases: []string{"gp"}, + Usage: `Gets the total proportion of power for a list of actors at a given instance.`, ArgsUsage: " [actor-id] ...", Flags: []cli.Flag{ f3FlagPowerTableFromEC, diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index f67095ffa50..1663411e7ef 100644 --- a/documentation/en/cli-lotus.md +++ b/documentation/en/cli-lotus.md @@ -2831,17 +2831,7 @@ USAGE: COMMANDS: get, g Get F3 power table at a specific instance ID or latest instance if none is specified. - - The instance may be specified as the first argument. If unspecified, - the latest instance is used. get-proportion, gp Gets the total proportion of power for a list of actors at a given instance. - - The instance may be specified via --instance flag. If unspecified, the - latest instance is used. - - The list of actors may be specified as Actor ID or miner address, space - separated, via arguments. Example: - $ lotus f3 powertable get-proportion -i 42 1413 t01234 f12345 help, h Shows a list of commands or help for one command OPTIONS: @@ -2853,9 +2843,6 @@ OPTIONS: NAME: lotus f3 powertable get - Get F3 power table at a specific instance ID or latest instance if none is specified. - The instance may be specified as the first argument. If unspecified, - the latest instance is used. - USAGE: lotus f3 powertable get [command options] [instance] @@ -2864,6 +2851,20 @@ OPTIONS: --help, -h show help ``` +#### lotus f3 powertable get-proportion +``` +NAME: + lotus f3 powertable get-proportion - Gets the total proportion of power for a list of actors at a given instance. + +USAGE: + lotus f3 powertable get-proportion [command options] [actor-id] ... + +OPTIONS: + --ec Whether to get the power table from EC. (default: false) + --instance value, -i value The F3 instance ID. (default: Latest Instance) + --help, -h show help +``` + ### lotus f3 certs ``` NAME: