diff --git a/.github/workflows/ci-lint.yml b/.github/workflows/ci-lint.yml index ded674f6..57f9cba2 100644 --- a/.github/workflows/ci-lint.yml +++ b/.github/workflows/ci-lint.yml @@ -34,10 +34,10 @@ jobs: uses: actions/setup-python@v5 with: python-version: '3.x' - - name: Set up Node.js 18 + - name: Set up Node.js 20 uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 - name: Set up Go uses: actions/setup-go@v5 with: diff --git a/cmd/common/json.go b/cmd/common/json.go index afe25c03..fb125cec 100644 --- a/cmd/common/json.go +++ b/cmd/common/json.go @@ -1,11 +1,18 @@ package common import ( + "context" "encoding/json" "fmt" "strings" "unicode/utf8" + "github.com/oasisprotocol/oasis-core/go/common" + coreSignature "github.com/oasisprotocol/oasis-core/go/common/crypto/signature" + consensusPretty "github.com/oasisprotocol/oasis-core/go/common/prettyprint" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/config" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/crypto/signature" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/types" "github.com/spf13/cobra" "github.com/oasisprotocol/oasis-core/go/common/cbor" @@ -98,3 +105,59 @@ func JSONMarshalUniversalValue(v interface{}) []byte { cobra.CheckErr(err) return vJSON } + +// PrettyPrint transforms generic JSON-formatted data into a pretty-printed string. +// For types implementing consensusPretty.PrettyPrinter, it uses the custom pretty printer. +// For other types, it does basic JSON indentation and cleanup of common delimiters. +func PrettyPrint(npa *NPASelection, prefix string, blob interface{}) string { + ret := "" + switch rtx := blob.(type) { + case consensusPretty.PrettyPrinter: + // Signed or unsigned consensus or runtime transaction. + var ns common.Namespace + if npa.ParaTime != nil { + ns = npa.ParaTime.Namespace() + } + sigCtx := signature.RichContext{ + RuntimeID: ns, + ChainContext: npa.Network.ChainContext, + Base: types.SignatureContextBase, + } + ctx := context.Background() + ctx = context.WithValue(ctx, consensusPretty.ContextKeyTokenSymbol, npa.Network.Denomination.Symbol) + ctx = context.WithValue(ctx, consensusPretty.ContextKeyTokenValueExponent, npa.Network.Denomination.Decimals) + if npa.ParaTime != nil { + ctx = context.WithValue(ctx, config.ContextKeyParaTimeCfg, npa.ParaTime) + } + ctx = context.WithValue(ctx, signature.ContextKeySigContext, &sigCtx) + ctx = context.WithValue(ctx, types.ContextKeyAccountNames, GenAccountNames()) + + // Set up chain context for signature verification during pretty-printing. + coreSignature.UnsafeResetChainContext() + coreSignature.SetChainContext(npa.Network.ChainContext) + var pp strings.Builder + rtx.PrettyPrint(ctx, prefix, &pp) + ret = pp.String() + default: + pp, err := PrettyJSONMarshal(blob) + cobra.CheckErr(err) + + out := string(pp) + out = strings.ReplaceAll(out, "{", "") + out = strings.ReplaceAll(out, "}", "") + out = strings.ReplaceAll(out, "[", "") + out = strings.ReplaceAll(out, "]", "") + out = strings.ReplaceAll(out, ",", "") + out = strings.ReplaceAll(out, "\"", "") + + for _, line := range strings.Split(out, "\n") { + line = strings.TrimRight(line, " \n") + if len(line) == 0 { + continue + } + ret += line + "\n" + } + } + + return ret +} diff --git a/cmd/common/transaction.go b/cmd/common/transaction.go index d1dd43c3..b3a82d98 100644 --- a/cmd/common/transaction.go +++ b/cmd/common/transaction.go @@ -10,7 +10,6 @@ import ( "github.com/spf13/cobra" flag "github.com/spf13/pflag" - "github.com/oasisprotocol/oasis-core/go/common" "github.com/oasisprotocol/oasis-core/go/common/cbor" coreSignature "github.com/oasisprotocol/oasis-core/go/common/crypto/signature" consensusPretty "github.com/oasisprotocol/oasis-core/go/common/prettyprint" @@ -352,31 +351,9 @@ func SignParaTimeTransaction( // PrintTransactionRaw prints the transaction which can be either signed or unsigned. func PrintTransactionRaw(npa *NPASelection, tx interface{}) { - switch rtx := tx.(type) { + switch tx.(type) { case consensusPretty.PrettyPrinter: - // Signed or unsigned consensus or runtime transaction. - var ns common.Namespace - if npa.ParaTime != nil { - ns = npa.ParaTime.Namespace() - } - sigCtx := signature.RichContext{ - RuntimeID: ns, - ChainContext: npa.Network.ChainContext, - Base: types.SignatureContextBase, - } - ctx := context.Background() - ctx = context.WithValue(ctx, consensusPretty.ContextKeyTokenSymbol, npa.Network.Denomination.Symbol) - ctx = context.WithValue(ctx, consensusPretty.ContextKeyTokenValueExponent, npa.Network.Denomination.Decimals) - if npa.ParaTime != nil { - ctx = context.WithValue(ctx, config.ContextKeyParaTimeCfg, npa.ParaTime) - } - ctx = context.WithValue(ctx, signature.ContextKeySigContext, &sigCtx) - ctx = context.WithValue(ctx, types.ContextKeyAccountNames, GenAccountNames()) - - // Set up chain context for signature verification during pretty-printing. - coreSignature.UnsafeResetChainContext() - coreSignature.SetChainContext(npa.Network.ChainContext) - rtx.PrettyPrint(ctx, "", os.Stdout) + fmt.Print(PrettyPrint(npa, "", tx)) default: fmt.Printf("[unsupported transaction type: %T]\n", tx) } diff --git a/cmd/network/show.go b/cmd/network/show.go index eb8dad15..c1ebbd4c 100644 --- a/cmd/network/show.go +++ b/cmd/network/show.go @@ -253,7 +253,7 @@ var showCmd = &cobra.Command{ } return case selParameters: - showParameters(ctx, height, consensusConn) + showParameters(ctx, npa, height, consensusConn) return default: @@ -390,31 +390,7 @@ func showNativeToken(ctx context.Context, height int64, npa *common.NPASelection } } -func PrettifyFromJSON(blob interface{}) string { - pp, err := json.MarshalIndent(blob, "", " ") - cobra.CheckErr(err) - - out := string(pp) - out = strings.ReplaceAll(out, "{", "") - out = strings.ReplaceAll(out, "}", "") - out = strings.ReplaceAll(out, "[", "") - out = strings.ReplaceAll(out, "]", "") - out = strings.ReplaceAll(out, ",", "") - out = strings.ReplaceAll(out, "\"", "") - - ret := "" - for _, line := range strings.Split(out, "\n") { - line = strings.TrimRight(line, " \n") - if len(line) == 0 { - continue - } - ret += line + "\n" - } - - return ret -} - -func showParameters(ctx context.Context, height int64, cons consensus.ClientBackend) { +func showParameters(ctx context.Context, npa *common.NPASelection, height int64, cons consensus.ClientBackend) { checkErr := func(what string, err error) { if err != nil { cobra.CheckErr(fmt.Errorf("%s: %w", what, err)) @@ -456,7 +432,7 @@ func showParameters(ctx context.Context, height int64, cons consensus.ClientBack doc[name] = params } else { fmt.Printf("=== %s PARAMETERS ===\n", strings.ToUpper(name)) - out := PrettifyFromJSON(params) + out := common.PrettyPrint(npa, " ", params) fmt.Printf("%s\n", out) } } diff --git a/cmd/paratime/show.go b/cmd/paratime/show.go index 5bc07c70..27f16bc2 100644 --- a/cmd/paratime/show.go +++ b/cmd/paratime/show.go @@ -11,6 +11,7 @@ import ( ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/spf13/cobra" + flag "github.com/spf13/pflag" "github.com/oasisprotocol/oasis-core/go/common/cbor" "github.com/oasisprotocol/oasis-core/go/common/crypto/hash" @@ -23,229 +24,276 @@ import ( cliConfig "github.com/oasisprotocol/cli/config" ) +type propertySelector int + const ( - blockLatest = "latest" + selRoundLatest = "latest" + formatText = "text" + formatJSON = "json" ) -var showCmd = &cobra.Command{ - Use: "show [ | ]", - Short: "Show information about a block and its transactions", - Long: "Show information about a given block number and (optionally) its transactions. Use \"latest\" to use the last block.", - Aliases: []string{"s"}, - Args: cobra.RangeArgs(1, 2), - Run: func(cmd *cobra.Command, args []string) { - cfg := cliConfig.Global() - npa := common.GetNPASelection(cfg) - - var ( - err error - blkNum uint64 - txIndex int - txHash hash.Hash - ) - switch blkNumRaw := args[0]; blkNumRaw { - case blockLatest: - // The latest block. - blkNum = client.RoundLatest // TODO: Support consensus. - default: - // A specific block. - blkNum, err = strconv.ParseUint(blkNumRaw, 10, 64) - if err != nil { - cobra.CheckErr(fmt.Errorf("malformed block number: %w", err)) - } - } - - if len(args) >= 2 { - txIndexOrHash := args[1] +const ( + selInvalid propertySelector = iota + selParameters +) - txIndex, err = strconv.Atoi(txIndexOrHash) - if err != nil { - txIndex = -1 - if err = txHash.UnmarshalHex(txIndexOrHash); err != nil { - cobra.CheckErr(fmt.Errorf("malformed tx hash: %w", err)) - } +var ( + outputFormat string + selectedRound uint64 + + showCmd = &cobra.Command{ + Use: "show { [ | ] | parameters }", + Short: "Show information about a ParaTime block, its transactions or other parameters", + Long: "Show ParaTime-specific information about a given block round, (optionally) its transactions or other information. Use \"latest\" to use the last round.", + Aliases: []string{"s"}, + Args: cobra.RangeArgs(1, 2), + Run: func(cmd *cobra.Command, args []string) { + cfg := cliConfig.Global() + npa := common.GetNPASelection(cfg) + + if npa.ParaTime == nil { + cobra.CheckErr("no ParaTimes to investigate") } - } - // Establish connection with the target network. - ctx := context.Background() - conn, err := connection.Connect(ctx, npa.Network) - cobra.CheckErr(err) + p, err := parseBlockNum(args[0]) + cobra.CheckErr(err) - fmt.Printf("Network: %s", npa.NetworkName) - if len(npa.Network.Description) > 0 { - fmt.Printf(" (%s)", npa.Network.Description) - } - fmt.Println() + var ( + // err error + // blkNum uint64 + txIndex int + txHash hash.Hash + ) - switch npa.ParaTime { - case nil: - // Consensus layer. - cobra.CheckErr("inspecting consensus layer blocks not yet supported") // TODO - default: - // Runtime layer. - rt := conn.Runtime(npa.ParaTime) + if len(args) >= 2 { + txIndexOrHash := args[1] - evDecoders := []client.EventDecoder{ - rt.Accounts, - rt.ConsensusAccounts, - rt.Contracts, - rt.Evm, + txIndex, err = strconv.Atoi(txIndexOrHash) + if err != nil { + txIndex = -1 + if err = txHash.UnmarshalHex(txIndexOrHash); err != nil { + cobra.CheckErr(fmt.Errorf("malformed tx hash: %w", err)) + } + } } - blk, err := rt.GetBlock(ctx, blkNum) + // Establish connection with the target network. + ctx := context.Background() + conn, err := connection.Connect(ctx, npa.Network) cobra.CheckErr(err) - fmt.Printf("ParaTime: %s", npa.ParaTimeName) - if len(npa.ParaTime.Description) > 0 { - fmt.Printf(" (%s)", npa.ParaTime.Description) + if outputFormat == formatText { + fmt.Printf("Network: %s", npa.NetworkName) + if len(npa.Network.Description) > 0 { + fmt.Printf(" (%s)", npa.Network.Description) + } + fmt.Println() + fmt.Printf("ParaTime: %s", npa.ParaTimeName) + if len(npa.ParaTime.Description) > 0 { + fmt.Printf(" (%s)", npa.ParaTime.Description) + } + fmt.Println() } - fmt.Println() - fmt.Printf("Round: %d\n", blk.Header.Round) - fmt.Printf("Version: %d\n", blk.Header.Version) - fmt.Printf("Namespace: %s\n", blk.Header.Namespace) - - // TODO: Fix when timestamp has a String method. - ts, _ := blk.Header.Timestamp.MarshalText() - fmt.Printf("Timestamp: %s\n", string(ts)) - - // TODO: Fix when type has a String method. - fmt.Printf("Type: %d\n", blk.Header.HeaderType) - fmt.Printf("Previous: %s\n", blk.Header.PreviousHash) - fmt.Printf("I/O root: %s\n", blk.Header.IORoot) - fmt.Printf("State root: %s\n", blk.Header.StateRoot) - fmt.Printf("Messages (out): %s\n", blk.Header.MessagesHash) - fmt.Printf("Messages (in): %s\n", blk.Header.InMessagesHash) - - txs, err := rt.GetTransactionsWithResults(ctx, blk.Header.Round) - cobra.CheckErr(err) - fmt.Printf("Transactions: %d\n", len(txs)) + // Runtime layer. + rt := conn.Runtime(npa.ParaTime) - evs, err := rt.GetEventsRaw(ctx, blkNum) - cobra.CheckErr(err) - if len(evs) > 0 { - // Check if there were any block events emitted. - var blockEvs []*types.Event - for _, ev := range evs { - if ev.TxHash.Equal(&runtimeTx.TagBlockTxHash) { - blockEvs = append(blockEvs, ev) - } + switch v := p.(type) { + case uint64: + blkNum := v + evDecoders := []client.EventDecoder{ + rt.Accounts, + rt.ConsensusAccounts, + rt.Contracts, + rt.Evm, } - if numEvents := len(blockEvs); numEvents > 0 { - fmt.Printf("=== Block events ===\n") - fmt.Printf("Events: %d\n", numEvents) - fmt.Println() - - for evIndex, ev := range blockEvs { - prettyPrintEvent(" ", evIndex, ev, evDecoders) - fmt.Println() + blk, err := rt.GetBlock(ctx, blkNum) + cobra.CheckErr(err) + + fmt.Printf("Round: %d\n", blk.Header.Round) + fmt.Printf("Version: %d\n", blk.Header.Version) + fmt.Printf("Namespace: %s\n", blk.Header.Namespace) + + // TODO: Fix when timestamp has a String method. + ts, _ := blk.Header.Timestamp.MarshalText() + fmt.Printf("Timestamp: %s\n", string(ts)) + + // TODO: Fix when type has a String method. + fmt.Printf("Type: %d\n", blk.Header.HeaderType) + fmt.Printf("Previous: %s\n", blk.Header.PreviousHash) + fmt.Printf("I/O root: %s\n", blk.Header.IORoot) + fmt.Printf("State root: %s\n", blk.Header.StateRoot) + fmt.Printf("Messages (out): %s\n", blk.Header.MessagesHash) + fmt.Printf("Messages (in): %s\n", blk.Header.InMessagesHash) + + txs, err := rt.GetTransactionsWithResults(ctx, blk.Header.Round) + cobra.CheckErr(err) + + fmt.Printf("Transactions: %d\n", len(txs)) + + evs, err := rt.GetEventsRaw(ctx, blkNum) + cobra.CheckErr(err) + if len(evs) > 0 { + // Check if there were any block events emitted. + var blockEvs []*types.Event + for _, ev := range evs { + if ev.TxHash.Equal(&runtimeTx.TagBlockTxHash) { + blockEvs = append(blockEvs, ev) + } } - } - } - if len(args) >= 2 { - fmt.Println() + if numEvents := len(blockEvs); numEvents > 0 { + fmt.Println() + fmt.Printf("=== Block events ===\n") + fmt.Printf("Events: %d\n", numEvents) + fmt.Println() - // Resolve transaction index if needed. - if txIndex == -1 { - for i, tx := range txs { - if h := tx.Tx.Hash(); h.Equal(&txHash) { - txIndex = i - break + for evIndex, ev := range blockEvs { + prettyPrintEvent(" ", evIndex, ev, evDecoders) + fmt.Println() } } - - if txIndex == -1 { - cobra.CheckErr(fmt.Errorf("failed to find transaction with hash %s", txHash)) - } } - if txIndex >= len(txs) { - cobra.CheckErr(fmt.Errorf("transaction index %d is out of range", txIndex)) - } - tx := txs[txIndex] - - fmt.Printf("=== Transaction %d ===\n", txIndex) - - if len(tx.Tx.AuthProofs) == 1 && tx.Tx.AuthProofs[0].Module != "" { - // Module-specific transaction encoding scheme. - scheme := tx.Tx.AuthProofs[0].Module + if len(args) >= 2 { + fmt.Println() - switch scheme { - case "evm.ethereum.v0": - // Ethereum transaction encoding. - var ethTx ethTypes.Transaction - if err := ethTx.UnmarshalBinary(tx.Tx.Body); err != nil { - fmt.Printf("[malformed 'evm.ethereum.v0' transaction: %s]\n", err) - break + // Resolve transaction index if needed. + if txIndex == -1 { + for i, tx := range txs { + if h := tx.Tx.Hash(); h.Equal(&txHash) { + txIndex = i + break + } } - fmt.Printf("Kind: evm.ethereum.v0\n") - fmt.Printf("Hash: %s\n", tx.Tx.Hash()) - fmt.Printf("Eth hash: %s\n", ethTx.Hash()) - fmt.Printf("Chain ID: %s\n", ethTx.ChainId()) - fmt.Printf("Nonce: %d\n", ethTx.Nonce()) - fmt.Printf("Type: %d\n", ethTx.Type()) - fmt.Printf("To: %s\n", ethTx.To()) - fmt.Printf("Value: %s\n", ethTx.Value()) - fmt.Printf("Gas limit: %d\n", ethTx.Gas()) - fmt.Printf("Gas price: %s\n", ethTx.GasPrice()) - fmt.Printf("Data:\n") - if len(ethTx.Data()) > 0 { - fmt.Printf(" %s\n", hex.EncodeToString(ethTx.Data())) - } else { - fmt.Printf(" (none)\n") + if txIndex == -1 { + cobra.CheckErr(fmt.Errorf("failed to find transaction with hash %s", txHash)) } - default: - fmt.Printf("[module-specific transaction encoding scheme: %s]\n", scheme) } - } else { - // Regular SDK transaction. - fmt.Printf("Kind: oasis\n") - common.PrintTransactionRaw(npa, &tx.Tx) - } - fmt.Println() + if txIndex >= len(txs) { + cobra.CheckErr(fmt.Errorf("transaction index %d is out of range", txIndex)) + } + tx := txs[txIndex] + + fmt.Printf("=== Transaction %d ===\n", txIndex) + + if len(tx.Tx.AuthProofs) == 1 && tx.Tx.AuthProofs[0].Module != "" { + // Module-specific transaction encoding scheme. + scheme := tx.Tx.AuthProofs[0].Module + + switch scheme { + case "evm.ethereum.v0": + // Ethereum transaction encoding. + var ethTx ethTypes.Transaction + if err := ethTx.UnmarshalBinary(tx.Tx.Body); err != nil { + fmt.Printf("[malformed 'evm.ethereum.v0' transaction: %s]\n", err) + break + } + + fmt.Printf("Kind: evm.ethereum.v0\n") + fmt.Printf("Hash: %s\n", tx.Tx.Hash()) + fmt.Printf("Eth hash: %s\n", ethTx.Hash()) + fmt.Printf("Chain ID: %s\n", ethTx.ChainId()) + fmt.Printf("Nonce: %d\n", ethTx.Nonce()) + fmt.Printf("Type: %d\n", ethTx.Type()) + fmt.Printf("To: %s\n", ethTx.To()) + fmt.Printf("Value: %s\n", ethTx.Value()) + fmt.Printf("Gas limit: %d\n", ethTx.Gas()) + fmt.Printf("Gas price: %s\n", ethTx.GasPrice()) + fmt.Printf("Data:\n") + if len(ethTx.Data()) > 0 { + fmt.Printf(" %s\n", hex.EncodeToString(ethTx.Data())) + } else { + fmt.Printf(" (none)\n") + } + default: + fmt.Printf("[module-specific transaction encoding scheme: %s]\n", scheme) + } + } else { + // Regular SDK transaction. + fmt.Printf("Kind: oasis\n") - // Show result. - fmt.Printf("=== Result of transaction %d ===\n", txIndex) - switch res := tx.Result; { - case res.Failed != nil: - fmt.Printf("Status: failed\n") - fmt.Printf("Module: %s\n", res.Failed.Module) - fmt.Printf("Code: %d\n", res.Failed.Code) - fmt.Printf("Message: %s\n", res.Failed.Message) - case res.Ok != nil: - fmt.Printf("Status: ok\n") - fmt.Printf("Data:\n") - prettyPrintCBOR(" ", "result", res.Ok) - case res.Unknown != nil: - fmt.Printf("Status: unknown\n") - fmt.Printf("Data:\n") - prettyPrintCBOR(" ", "result", res.Unknown) - default: - fmt.Printf("[unsupported result kind]\n") - } - fmt.Println() + common.PrintTransactionRaw(npa, &tx.Tx) + } + fmt.Println() - // Show events. - fmt.Printf("=== Events emitted by transaction %d ===\n", txIndex) - if numEvents := len(tx.Events); numEvents > 0 { - fmt.Printf("Events: %d\n", numEvents) + // Show result. + fmt.Printf("=== Result of transaction %d ===\n", txIndex) + switch res := tx.Result; { + case res.Failed != nil: + fmt.Printf("Status: failed\n") + fmt.Printf("Module: %s\n", res.Failed.Module) + fmt.Printf("Code: %d\n", res.Failed.Code) + fmt.Printf("Message: %s\n", res.Failed.Message) + case res.Ok != nil: + fmt.Printf("Status: ok\n") + fmt.Printf("Data:\n") + prettyPrintCBOR(" ", "result", res.Ok) + case res.Unknown != nil: + fmt.Printf("Status: unknown\n") + fmt.Printf("Data:\n") + prettyPrintCBOR(" ", "result", res.Unknown) + default: + fmt.Printf("[unsupported result kind]\n") + } fmt.Println() - for evIndex, ev := range tx.Events { - prettyPrintEvent(" ", evIndex, ev, evDecoders) + // Show events. + fmt.Printf("=== Events emitted by transaction %d ===\n", txIndex) + if numEvents := len(tx.Events); numEvents > 0 { + fmt.Printf("Events: %d\n", numEvents) fmt.Println() + + for evIndex, ev := range tx.Events { + prettyPrintEvent(" ", evIndex, ev, evDecoders) + fmt.Println() + } + } else { + fmt.Println("No events emitted by this transaction.") } - } else { - fmt.Println("No events emitted by this transaction.") + } + case propertySelector: + switch v { + case selParameters: + showParameters(ctx, npa, selectedRound, rt) + return + default: + cobra.CheckErr(fmt.Errorf("selector '%s' not found", args[0])) } } + }, + } +) + +func parseBlockNum( + s string, +) (interface{}, error) { // TODO: Use `any` + if sel := selectorFromString(s); sel != selInvalid { + return sel, nil + } + + switch blkNumRaw := s; blkNumRaw { + case selRoundLatest: + // The latest block. + return client.RoundLatest, nil // TODO: Support consensus. + default: + // A specific block. + blkNum, err := strconv.ParseUint(blkNumRaw, 10, 64) + if err != nil { + return nil, fmt.Errorf("malformed block number: %w", err) } - }, + return blkNum, nil + } +} + +func selectorFromString(s string) propertySelector { + if strings.ToLower(strings.TrimSpace(s)) == "parameters" { + return selParameters + } + return selInvalid } func prettyPrintCBOR(indent string, kind string, data []byte) { @@ -385,6 +433,45 @@ func prettyPrintEvent(indent string, evIndex int, ev *types.Event, decoders []cl prettyPrintCBOR(indent+" ", "event", ev.Value) } +func showParameters(ctx context.Context, npa *common.NPASelection, round uint64, rt connection.RuntimeClient) { + checkErr := func(what string, err error) { + if err != nil { + cobra.CheckErr(fmt.Errorf("%s: %w", what, err)) + } + } + + stakeThresholds, err := rt.ROFL.StakeThresholds(ctx, round) + checkErr("ROFL StakeThresholds", err) + + doc := make(map[string]interface{}) + + doSection := func(name string, params interface{}) { + if outputFormat == formatJSON { + doc[name] = params + } else { + fmt.Printf("\n=== %s PARAMETERS ===\n", strings.ToUpper(name)) + out := common.PrettyPrint(npa, " ", params) + fmt.Printf("%s\n", out) + } + } + + doSection("rofl", stakeThresholds) + + if outputFormat == formatJSON { + pp, err := json.MarshalIndent(doc, "", " ") + cobra.CheckErr(err) + fmt.Printf("%s\n", pp) + } +} + func init() { + formatFlag := flag.NewFlagSet("", flag.ContinueOnError) + formatFlag.StringVar(&outputFormat, "format", formatText, "output format ["+strings.Join([]string{formatText, formatJSON}, ",")+"]") + + roundFlag := flag.NewFlagSet("", flag.ContinueOnError) + roundFlag.Uint64Var(&selectedRound, "round", client.RoundLatest, "explicitly set block round to use") + showCmd.Flags().AddFlagSet(common.SelectorNPFlags) + showCmd.Flags().AddFlagSet(roundFlag) + showCmd.Flags().AddFlagSet(formatFlag) } diff --git a/docs/network.md b/docs/network.md index ced835e4..21bcb217 100644 --- a/docs/network.md +++ b/docs/network.md @@ -332,24 +332,30 @@ We can see that the token's name is ROSE and that 1 token corresponds to 10^9 Next, we can observe that the **total supply** is 10 billion tokens and that about 1.3 billion tokens are in the **common pool**. -**Staking thresholds** are important for the network validators. They show -the minimum staked amount required to become an active validator for the entity -and all node kinds (validator, compute, key manager nodes). At time of writing, -this was 100 tokens. For example, if you wanted to register an entity running a -validator and a compute node, you would need to stake (i.e. *escrow*) at least -300 tokens. +The **staking thresholds** fields are the following: + +- `entity`: The amount needed to be staked when registering an entity. +- `node-validator`, `node-compute`, `node-keymanager`: The amount needed to be + staked to the corresponding entity for a node to run as a validator, a compute + node or a key manager. This is the amount that will be slashed in case of + inappropriate node behavior. +- `runtime-compute`, `runtime-keymanager`: The amount needed to be staked to an + entity for [registering a new ParaTime or a key manager]. + Keep in mind that a ParaTime cannot be unregistered and there is no way of + getting the staked assets back. + +For example, if you wanted to register an entity running a validator and a +compute node, you would need to stake (i.e. *escrow*) at least 300 tokens. :::info -Each runtime may also require a **minimum ParaTime-specific escrow** for -running a compute node. Use the [`network show id`](#show-id) -command and pass a corresponding Paratime ID to see it. +Apart from the `node-compute` threshold above, a ParaTime may require additional +**ParaTime-specific escrow** for running a compute node. Use the +[`network show id`](#show-id) command to see it. ::: -The `runtime-` fields show you the required staked amount for -[**registering a new ParaTime**](./paratime.md#register) of any kind (compute, -key manager) was 50,000 tokens at time of writing. +[registering a new ParaTime or a key manager]: ./paratime.md#register #### `gas-costs` {#show-gas-costs} @@ -386,10 +392,20 @@ The provided ID can be one of the following: ![code json](../examples/network-show/id-paratime.out.static) Network validators may be interested in the **ParaTime staking threshold** - stored inside the `thresholds` field. In the example above, the amount to run - a Sapphire compute node on the Mainnet is 5,000,000 tokens and should be - considered on top of the consensus-layer validator staking thresholds - obtained by the [`network show native-token`](#show-native-token) command. + stored inside the `thresholds` field: + + ```shell + oasis network show 000000000000000000000000000000000000000000000000f80306c9858e7279 | jq '.staking.thresholds."node-compute"' + ``` + + ``` + "5000000000000000" + ``` + + In the example above, the amount to run a Sapphire compute node on the Mainnet + is 5,000,000 tokens and should be considered on top of the consensus-layer + validator staking thresholds obtained by the + [`network show native-token`](#show-native-token) command. - If the entity ID is provided, Oasis CLI shows information on the entity and its corresponding nodes in the network registry. For example: diff --git a/docs/paratime.md b/docs/paratime.md index a7aca4bc..53684348 100644 --- a/docs/paratime.md +++ b/docs/paratime.md @@ -125,8 +125,13 @@ For example, to set the Cipher ParaTime default on the Testnet, run: ## Show {#show} -Use `paratime show ` providing the block round to print its header and -other information. +Use `paratime show` to investigate a specific ParaTime block or other +parameters. + +### `` {#show-round} + +Providing the block round or `latest` literal will print its header and other +information. ![code shell](../examples/paratime-show/show.in.static) @@ -147,6 +152,17 @@ encrypted: ![code](../examples/paratime-show/show-tx-encrypted.out.static) +### `parameters` {#show-parameters} + +This will print various ParaTime-specific parameters such as the ROFL stake +thresholds. + +![code shell](../examples/paratime-show/show-parameters.in) + +![code](../examples/paratime-show/show-parameters.out) + +By passing `--format json`, the output is formatted as JSON. + ## Set information about a denomination {#denom-set} To set information about a denomination on the specific network and paratime use diff --git a/examples/paratime-show/config/cli.toml b/examples/paratime-show/config/cli.toml new file mode 100644 index 00000000..5c7f6efe --- /dev/null +++ b/examples/paratime-show/config/cli.toml @@ -0,0 +1,119 @@ +last_migration = 1 + +[networks] +default = 'testnet' + +[networks.mainnet] +chain_context = 'bb3d748def55bdfb797a2ac53ee6ee141e54cd2ab2dc2375f4a0703a178e6e55' +description = '' +rpc = 'grpc.oasis.io:443' + +[networks.mainnet.denomination] +decimals = 9 +symbol = 'ROSE' + +[networks.mainnet.paratimes] +default = 'sapphire' + +[networks.mainnet.paratimes.cipher] +consensus_denomination = '_' +description = '' +id = '000000000000000000000000000000000000000000000000e199119c992377cb' + +[networks.mainnet.paratimes.cipher.denominations] +[networks.mainnet.paratimes.cipher.denominations._] +decimals = 9 +symbol = 'ROSE' + +[networks.mainnet.paratimes.emerald] +consensus_denomination = '_' +description = '' +id = '000000000000000000000000000000000000000000000000e2eaa99fc008f87f' + +[networks.mainnet.paratimes.emerald.denominations] +[networks.mainnet.paratimes.emerald.denominations._] +decimals = 18 +symbol = 'ROSE' + +[networks.mainnet.paratimes.sapphire] +consensus_denomination = '_' +description = '' +id = '000000000000000000000000000000000000000000000000f80306c9858e7279' + +[networks.mainnet.paratimes.sapphire.denominations] +[networks.mainnet.paratimes.sapphire.denominations._] +decimals = 18 +symbol = 'ROSE' + +[networks.testnet] +chain_context = '0b91b8e4e44b2003a7c5e23ddadb5e14ef5345c0ebcb3ddcae07fa2f244cab76' +description = '' +rpc = 'testnet.grpc.oasis.io:443' + +[networks.testnet.denomination] +decimals = 9 +symbol = 'TEST' + +[networks.testnet.paratimes] +default = 'sapphire' + +[networks.testnet.paratimes.cipher] +consensus_denomination = '_' +description = '' +id = '0000000000000000000000000000000000000000000000000000000000000000' + +[networks.testnet.paratimes.cipher.denominations] +[networks.testnet.paratimes.cipher.denominations._] +decimals = 9 +symbol = 'TEST' + +[networks.testnet.paratimes.emerald] +consensus_denomination = '_' +description = '' +id = '00000000000000000000000000000000000000000000000072c8215e60d5bca7' + +[networks.testnet.paratimes.emerald.denominations] +[networks.testnet.paratimes.emerald.denominations._] +decimals = 18 +symbol = 'TEST' + +[networks.testnet.paratimes.pontusx_dev] +consensus_denomination = 'TEST' +description = 'Pontus-X Devnet' +id = '0000000000000000000000000000000000000000000000004febe52eb412b421' + +[networks.testnet.paratimes.pontusx_dev.denominations] +[networks.testnet.paratimes.pontusx_dev.denominations._] +decimals = 18 +symbol = 'EUROe' + +[networks.testnet.paratimes.pontusx_dev.denominations.test] +decimals = 18 +symbol = 'TEST' + +[networks.testnet.paratimes.pontusx_test] +consensus_denomination = 'TEST' +description = 'Pontus-X Testnet' +id = '00000000000000000000000000000000000000000000000004a6f9071c007069' + +[networks.testnet.paratimes.pontusx_test.denominations] +[networks.testnet.paratimes.pontusx_test.denominations._] +decimals = 18 +symbol = 'EUROe' + +[networks.testnet.paratimes.pontusx_test.denominations.test] +decimals = 18 +symbol = 'TEST' + +[networks.testnet.paratimes.sapphire] +consensus_denomination = '_' +description = '' +id = '000000000000000000000000000000000000000000000000a6d1e3ebf60dff6c' + +[networks.testnet.paratimes.sapphire.denominations] +[networks.testnet.paratimes.sapphire.denominations._] +decimals = 18 +symbol = 'TEST' + +[wallets] +default = '' diff --git a/examples/paratime-show/show-parameters.in b/examples/paratime-show/show-parameters.in new file mode 100644 index 00000000..dbb190bb --- /dev/null +++ b/examples/paratime-show/show-parameters.in @@ -0,0 +1 @@ +oasis paratime show parameters diff --git a/examples/paratime-show/show-parameters.out b/examples/paratime-show/show-parameters.out new file mode 100644 index 00000000..734d2730 --- /dev/null +++ b/examples/paratime-show/show-parameters.out @@ -0,0 +1,8 @@ +Network: testnet +ParaTime: sapphire + +=== ROFL PARAMETERS === + app_create: + Amount: 10000000000000000000000 + Denomination: + diff --git a/go.mod b/go.mod index 274137c7..2d0360c0 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 github.com/oasisprotocol/metadata-registry-tools v0.0.0-20220406100644-7e9a2b991920 github.com/oasisprotocol/oasis-core/go v0.2403.0 - github.com/oasisprotocol/oasis-sdk/client-sdk/go v0.11.0 + github.com/oasisprotocol/oasis-sdk/client-sdk/go v0.11.1 github.com/olekukonko/tablewriter v0.0.5 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 diff --git a/go.sum b/go.sum index e90216f1..93b50a2d 100644 --- a/go.sum +++ b/go.sum @@ -431,8 +431,8 @@ github.com/oasisprotocol/metadata-registry-tools v0.0.0-20220406100644-7e9a2b991 github.com/oasisprotocol/metadata-registry-tools v0.0.0-20220406100644-7e9a2b991920/go.mod h1:MKr/giwakLyCCjSWh0W9Pbaf7rDD1K96Wr57OhNoUK0= github.com/oasisprotocol/oasis-core/go v0.2403.0 h1:jOs3Y0hIDWCOx1y2ZHtj2zzkh36S0nkHQaI77mx3K/o= github.com/oasisprotocol/oasis-core/go v0.2403.0/go.mod h1:lUV3T76Ng0QvAXm1loYnVTj3uzAJHwwa29o3QjM/ID8= -github.com/oasisprotocol/oasis-sdk/client-sdk/go v0.11.0 h1:+0TpEBTMRsRXc4lmLyVZrKWdldolVVpQSYh21LynkAA= -github.com/oasisprotocol/oasis-sdk/client-sdk/go v0.11.0/go.mod h1:HZcEGQc/XnfJkDTj/bnfHxch3J9AxFKrCU4iQo1w3Gw= +github.com/oasisprotocol/oasis-sdk/client-sdk/go v0.11.1 h1:CdQ3SznnCVfnSRJszoR10+Nrv3a8nKQFxodZXKpePIo= +github.com/oasisprotocol/oasis-sdk/client-sdk/go v0.11.1/go.mod h1:HZcEGQc/XnfJkDTj/bnfHxch3J9AxFKrCU4iQo1w3Gw= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=