Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: upgrademonitor #2922

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
98fdddb
feat: boilerplate for upgrademonitor
rootulp Dec 11, 2023
2a0239a
feat: make grpc-endpoint required arg
rootulp Dec 11, 2023
cad89ce
docs: improve README
rootulp Dec 11, 2023
11c7915
feat: query version tally
rootulp Dec 11, 2023
26b5eb3
feat: accept version flag
rootulp Dec 11, 2023
57a1444
feat: move GRPC endpoint to optional flag
rootulp Dec 11, 2023
97035a7
feat: poll frequency
rootulp Dec 11, 2023
3034773
chore: improve print statement
rootulp Dec 11, 2023
b31244e
feat: auto-try
rootulp Dec 11, 2023
c49f4d7
refactor: extract flags
rootulp Dec 11, 2023
fca19d9
chore: remove redundant newlines
rootulp Dec 11, 2023
338cc72
docs: improve usage
rootulp Dec 11, 2023
57774e1
feat: signer
rootulp Dec 11, 2023
5b9cf51
refactor: extract validateSigner
rootulp Dec 11, 2023
bb1d998
feat: stop polling after upgrading
rootulp Dec 11, 2023
8795a2d
submit upgrade
rootulp Dec 11, 2023
0a5a20e
fix: devnet is running v1 so upgrade msg is to v2
rootulp Dec 11, 2023
de4dd31
docs: describe steps to generate signed try upgrade tx
rootulp Dec 12, 2023
fbe254f
docs: prefer --output-document
rootulp Dec 12, 2023
1c9635e
feat: auto-publish transaction
rootulp Dec 12, 2023
dc50ab6
refactor: extract
rootulp Dec 12, 2023
c4ad39f
refactor
rootulp Dec 12, 2023
ab70eff
refactor: cleanup
rootulp Dec 12, 2023
4a76d26
refactor: delete unused code
rootulp Dec 12, 2023
d0e973a
docs: improve docs
rootulp Dec 12, 2023
eff04b2
Merge branch 'main' into rp/upgrade-monitor
rootulp Dec 12, 2023
766516a
chore: resolve TODOs
rootulp Dec 12, 2023
4c612f1
Update tools/upgrademonitor/internal/grpc.go
rootulp Dec 13, 2023
0ec3969
chore: print tx log if tx failed
rootulp Dec 13, 2023
7271be8
feat: validate transaction on start up
rootulp Dec 13, 2023
50fcc9a
feat: query current version
rootulp Dec 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ square/testdata
**/*.html
.release-env
**/*.DS_Store
vendor
vendor
tools/upgrademonitor/upgrademonitor
rootulp marked this conversation as resolved.
Show resolved Hide resolved
tools/upgrademonitor/*.json
3 changes: 2 additions & 1 deletion go.work
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
go 1.21.1
go 1.21.4

use (
.
./test/testground
./tools/upgrademonitor
)
68 changes: 68 additions & 0 deletions tools/upgrademonitor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Upgrade Monitor

Upgrade monitor is a stand-alone binary that monitors that status of upgrades on a Celestia network.

## Build from source

```shell
go build .
```

## Prerequisites

1. Determine which network you would like to monitor upgrades on.
1. Get a GRPC endpoint for a consensus node on that network.

**NOTE** This tool only works for consensus nodes that are running app version >= 2. At the time of writing, Celestia mainnet and testnets are on app version 1 so upgrademonitor can only be used on a local devnet.

## Usage

1. In one terminal tab, start a local devnet

```shell
cd celestia-app

# Upgrade monitor can only be run on app version >= 2 so checkout main and install celestia-appd.
git checkout main
make install

# This will start a GRPC server at 0.0.0.0:9000
./scripts/single-node.sh
```

1. In a new terminal tab, prepare the try upgrade transaction

```shell
# Export environment variables. Modify these based on your needs.
export CHAIN_ID="private"
export KEY_NAME="validator"
export KEYRING_BACKEND="test"
export FROM=$(celestia-appd keys show $KEY_NAME --address --keyring-backend $KEYRING_BACKEND)

# Prepare the unsigned transaction
celestia-appd tx upgrade try-upgrade --from $FROM --keyring-backend $KEYRING_BACKEND --fees 420utia --generate-only > unsigned_tx.json

# Verify the unsigned transaction
cat unsigned_tx.json

# Sign the unsigned transaction.
celestia-appd tx sign unsigned_tx.json --from $FROM --keyring-backend $KEYRING_BACKEND --chain-id $CHAIN_ID --output-document signed_tx.json

# Verify the signed transaction
cat signed_tx.json
```

1. Run the upgrademonitor.

```shell
cd celestia-app/tools/upgrademonitor

# Build the binary
go build .

# Run the binary without auto publishing
./upgrademonitor

# Run the binary with auto publishing
./upgrademonitor --auto-publish signed_tx.json
```
30 changes: 30 additions & 0 deletions tools/upgrademonitor/cmd/flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package cmd

// Flags
var (
// version is the version number that should be monitored.
version uint64
// grpcEndpoint is the endpoint of a consensus full node.
grpcEndpoint string
// pollFrequency is the frequency in seconds that upgrade monitor polls the
// GRPC endpoint.
pollFrequency int64
// pathToTransaction is the file path to a signed transaction that will be
// auto-published when the network is upgradeable.
pathToTransaction string
)

// Defaults
var (
// defaultVersion is the value used if the version flag isn't provided. Since
// v2 is coordinated via an upgrade-height, v3 is the first version that this
// tool supports.
defaultVersion = uint64(3)
// defaultGrpcEndpoint is the value used if the grpc-endpoint flag isn't provided.
// This endpoint is the one enabled by default when you run ./scripts/single-node.sh
defaultGrpcEndpoint = "0.0.0.0:9090"
// defaultPollFrequency is the value used if the poll-frequency flag isn't provided.
defaultPollFrequency = int64(10) // 10 seconds
// defaultPathToTransaction is the value used if the auto-publish flag isn't provided.
defaultPathToTransaction = ""
)
64 changes: 64 additions & 0 deletions tools/upgrademonitor/cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package cmd

import (
"fmt"
"os"
"time"

"github.com/celestiaorg/celestia-app/tools/upgrademonitor/internal"
"github.com/spf13/cobra"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)

var rootCmd = &cobra.Command{
Use: "upgrademonitor",
Short: "upgrademonitor monitors that status of upgrades on a Celestia network.",
RunE: func(cmd *cobra.Command, args []string) error {
conn, err := grpc.Dial(grpcEndpoint, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return fmt.Errorf("could not connect: %v", err)
}
defer conn.Close()

ticker := time.NewTicker(time.Duration(pollFrequency) * time.Second)
defer ticker.Stop()

for {
select {
case <-ticker.C:
resp, err := internal.QueryVersionTally(conn, version)
if err != nil {
return err
}
fmt.Printf("version: %v, voting: %v, threshold: %v, total: %v\n", version, resp.GetVotingPower(), resp.GetThresholdPower(), resp.GetTotalVotingPower())

if internal.IsUpgradeable(resp) {
fmt.Printf("the network is upgradeable so publishing %v\n", pathToTransaction)
resp, err := internal.Publish(conn, pathToTransaction)
if err != nil {
return err
}
fmt.Printf("published transaction: %v\n", resp.TxHash)
rootulp marked this conversation as resolved.
Show resolved Hide resolved
return nil // stop the upgrademonitor
}
}
}
},
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cobra.Command setup looks correct, and the RunE function is well-structured to establish a gRPC connection, create a ticker for polling, and handle the upgrade monitoring logic. However, there are a few points to consider:

  1. The use of insecure.NewCredentials() in line 18 is potentially concerning from a security perspective. It's important to ensure that this is acceptable for the intended use case and that any risks are mitigated.

  2. The RunE function will exit after publishing a transaction when the network is upgradeable (line 43). This behavior should be clearly documented, and it should be confirmed that this is the desired behavior, as it means the tool will not continue to monitor after an upgrade is detected and handled.

  3. The logging in lines 34 and 42 is directly to stdout. Consider using a logging framework that can be configured for different environments and verbosity levels.

  4. Error handling in lines 19 and 39 is straightforward, but it might be beneficial to log additional context or perform cleanup before returning the error.

  5. The infinite loop starting at line 27 will run indefinitely unless an upgrade is detected. Ensure that there is a way to gracefully shut down the application if needed, such as by handling a termination signal.


func Execute() {
// Bind the version variable to the --version flag
rootCmd.Flags().Uint64Var(&version, "version", defaultVersion, "version to monitor")
rootulp marked this conversation as resolved.
Show resolved Hide resolved
// Bind the grpcEndpoint variable to the --grpc-endpoint flag
rootCmd.Flags().StringVar(&grpcEndpoint, "grpc-endpoint", defaultGrpcEndpoint, "GRPC endpoint of a consensus node")
// Bind the pollFrequency variable to the --poll-frequency flag
rootCmd.Flags().Int64Var(&pollFrequency, "poll-frequency", defaultPollFrequency, "poll frequency in seconds")
// Bind the pathToTransaction variable to the value provided for the --auto-publish flag
rootCmd.Flags().StringVar(&pathToTransaction, "auto-publish", defaultPathToTransaction, "auto publish a signed transaction when the network is upgradeable")
rootulp marked this conversation as resolved.
Show resolved Hide resolved

if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
rootulp marked this conversation as resolved.
Show resolved Hide resolved
}
13 changes: 13 additions & 0 deletions tools/upgrademonitor/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module github.com/celestiaorg/celestia-app/tools/upgrademonitor
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was the rationale for this having it's own go.mod?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm upgrademonitor is distinct from celestia-app in that the binary will be used independently of celestia-app. A distinct go.mod allows upgrademonitor to manage its own dependencies.

Your question made me realize that we could even extract upgrademonitor entirely to its own repo.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want it in a stand alone repo: celestiaorg/upgrade-monitor#1


go 1.21.4

require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/cobra v1.8.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
)

replace (
github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.20.0-sdk-v0.46.16
)
10 changes: 10 additions & 0 deletions tools/upgrademonitor/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
63 changes: 63 additions & 0 deletions tools/upgrademonitor/internal/grpc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package internal

import (
"context"
"fmt"
"os"
"time"

"github.com/celestiaorg/celestia-app/app"
"github.com/celestiaorg/celestia-app/app/encoding"
upgradetypes "github.com/celestiaorg/celestia-app/x/upgrade/types"

"github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/tx"
"google.golang.org/grpc"
)

func QueryVersionTally(conn *grpc.ClientConn, version uint64) (*upgradetypes.QueryVersionTallyResponse, error) {
client := upgradetypes.NewQueryClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

resp, err := client.VersionTally(ctx, &upgradetypes.QueryVersionTallyRequest{Version: version})
if err != nil {
return nil, fmt.Errorf("could not query version tally: %v", err)
}
return resp, nil
rootulp marked this conversation as resolved.
Show resolved Hide resolved
}

func Publish(conn *grpc.ClientConn, pathToTransaction string) (*types.TxResponse, error) {
signedTx, err := os.ReadFile(pathToTransaction)
if err != nil {
return nil, fmt.Errorf("failed to read file %v. %v", pathToTransaction, err)
}

encCfg := encoding.MakeConfig(app.ModuleEncodingRegisters...)
decoded, err := encCfg.TxConfig.TxJSONDecoder()(signedTx)
if err != nil {
return nil, fmt.Errorf("failed to decode transaction: %v", err)
}

txBytes, err := encCfg.TxConfig.TxEncoder()(decoded)
if err != nil {
return nil, fmt.Errorf("failed to encode transaction: %v", err)
}

client := tx.NewServiceClient(conn)
res, err := client.BroadcastTx(context.Background(), &tx.BroadcastTxRequest{
Mode: tx.BroadcastMode_BROADCAST_MODE_BLOCK,
TxBytes: txBytes,
})
if err != nil {
return nil, fmt.Errorf("failed to broadcast transaction: %v", err)
}
return res.GetTxResponse(), nil
rootulp marked this conversation as resolved.
Show resolved Hide resolved
}

func IsUpgradeable(response *upgradetypes.QueryVersionTallyResponse) bool {
if response == nil {
return false
}
return response.GetVotingPower() > response.ThresholdPower
}
9 changes: 9 additions & 0 deletions tools/upgrademonitor/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

import (
"github.com/celestiaorg/celestia-app/tools/upgrademonitor/cmd"
)

func main() {
cmd.Execute()
}
Loading