diff --git a/cmd/qgb/query/cmd.go b/cmd/qgb/query/cmd.go new file mode 100644 index 00000000..48293540 --- /dev/null +++ b/cmd/qgb/query/cmd.go @@ -0,0 +1,229 @@ +package query + +import ( + "context" + "fmt" + "math/big" + "os" + "strconv" + "time" + + celestiatypes "github.com/celestiaorg/celestia-app/x/qgb/types" + "github.com/celestiaorg/orchestrator-relayer/cmd/qgb/common" + "github.com/celestiaorg/orchestrator-relayer/p2p" + "github.com/celestiaorg/orchestrator-relayer/rpc" + "github.com/celestiaorg/orchestrator-relayer/types" + ds "github.com/ipfs/go-datastore" + dssync "github.com/ipfs/go-datastore/sync" + "github.com/libp2p/go-libp2p" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/pkg/errors" + "github.com/spf13/cobra" + tmlog "github.com/tendermint/tendermint/libs/log" +) + +func Command() *cobra.Command { + queryCmd := &cobra.Command{ + Use: "query", + Aliases: []string{"q"}, + Short: "Query relevant information from a running QGB", + SilenceUsage: true, + } + + queryCmd.AddCommand( + Signers(), + ) + + queryCmd.SetHelpCommand(&cobra.Command{}) + + return queryCmd +} + +func Signers() *cobra.Command { + command := &cobra.Command{ + Use: "signers ", + Args: cobra.ExactArgs(1), + Short: "Queries the QGB for attestations signers", + Long: "Queries the QGB for attestations signers. The nonce is the attestation nonce that the command" + + " will query signatures for. It should be either a specific nonce starting from 2 and on." + + " Or, use 'latest' as argument to check the latest attestation nonce", + RunE: func(cmd *cobra.Command, args []string) error { + config, err := parseFlags(cmd) + if err != nil { + return err + } + + // creating the logger + logger := tmlog.NewTMLogger(os.Stdout) + logger.Debug("initializing queriers") + + ctx, cancel := context.WithCancel(cmd.Context()) + defer cancel() + + stopFuncs := make([]func() error, 0) + defer func() { + for _, f := range stopFuncs { + err := f() + if err != nil { + logger.Error(err.Error()) + } + } + }() + + // create tm querier and app querier + tmQuerier, appQuerier, stops, err := common.NewTmAndAppQuerier(logger, config.tendermintRPC, config.celesGRPC) + stopFuncs = append(stopFuncs, stops...) + if err != nil { + return err + } + + // creating the host + h, err := libp2p.New() + if err != nil { + return err + } + addrInfo, err := peer.AddrInfoFromString(config.targetNode) + if err != nil { + return err + } + for i := 0; i < 5; i++ { + logger.Debug("connecting to target node...") + err := h.Connect(ctx, *addrInfo) + if err != nil { + logger.Error("couldn't connect to target node", "err", err.Error()) + } + if err == nil { + logger.Debug("connected to target node") + break + } + time.Sleep(5 * time.Second) + } + + // creating the data store + dataStore := dssync.MutexWrap(ds.NewMapDatastore()) + + // creating the dht + dht, err := p2p.NewQgbDHT(cmd.Context(), h, dataStore, []peer.AddrInfo{}, logger) + if err != nil { + return err + } + + // creating the p2p querier + p2pQuerier := p2p.NewQuerier(dht, logger) + + nonce, err := parseNonce(ctx, appQuerier, args[0]) + if err != nil { + return err + } + if nonce == 1 { + return fmt.Errorf("nonce 1 doesn't need to be signed. signatures start from nonce 2") + } + + err = getSignaturesAndPrintThem(ctx, logger, appQuerier, tmQuerier, p2pQuerier, nonce) + if err != nil { + return err + } + return nil + }, + } + return addFlags(command) +} + +func getSignaturesAndPrintThem(ctx context.Context, logger tmlog.Logger, appQuerier *rpc.AppQuerier, tmQuerier *rpc.TmQuerier, p2pQuerier *p2p.Querier, nonce uint64) error { + logger.Info("getting signatures for nonce", "nonce", nonce) + + lastValset, err := appQuerier.QueryLastValsetBeforeNonce(ctx, nonce) + if err != nil { + return err + } + + att, err := appQuerier.QueryAttestationByNonce(ctx, nonce) + if err != nil { + return err + } + if att == nil { + return celestiatypes.ErrAttestationNotFound + } + + switch att.Type() { + case celestiatypes.ValsetRequestType: + vs, ok := att.(*celestiatypes.Valset) + if !ok { + return errors.Wrap(celestiatypes.ErrAttestationNotValsetRequest, strconv.FormatUint(nonce, 10)) + } + signBytes, err := vs.SignBytes() + if err != nil { + return err + } + confirms, err := p2pQuerier.QueryValsetConfirms(ctx, nonce, *lastValset, signBytes.Hex()) + if err != nil { + return err + } + confirmsMap := make(map[string]string) + for _, confirm := range confirms { + confirmsMap[confirm.EthAddress] = confirm.Signature + } + printConfirms(logger, confirmsMap, lastValset) + case celestiatypes.DataCommitmentRequestType: + dc, ok := att.(*celestiatypes.DataCommitment) + if !ok { + return errors.Wrap(types.ErrAttestationNotDataCommitmentRequest, strconv.FormatUint(nonce, 10)) + } + commitment, err := tmQuerier.QueryCommitment( + ctx, + dc.BeginBlock, + dc.EndBlock, + ) + if err != nil { + return err + } + dataRootHash := types.DataCommitmentTupleRootSignBytes(big.NewInt(int64(dc.Nonce)), commitment) + confirms, err := p2pQuerier.QueryDataCommitmentConfirms(ctx, *lastValset, nonce, dataRootHash.Hex()) + if err != nil { + return err + } + confirmsMap := make(map[string]string) + for _, confirm := range confirms { + confirmsMap[confirm.EthAddress] = confirm.Signature + } + printConfirms(logger, confirmsMap, lastValset) + default: + return errors.Wrap(types.ErrUnknownAttestationType, strconv.FormatUint(nonce, 10)) + } + return nil +} + +func parseNonce(ctx context.Context, querier *rpc.AppQuerier, nonce string) (uint64, error) { + switch nonce { + case "latest": + return querier.QueryLatestAttestationNonce(ctx) + default: + return strconv.ParseUint(nonce, 10, 0) + } +} + +func printConfirms(logger tmlog.Logger, confirmsMap map[string]string, valset *celestiatypes.Valset) { + signers := make(map[string]string) + missingSigners := make([]string, 0) + + for _, validator := range valset.Members { + val, ok := confirmsMap[validator.EvmAddress] + if ok { + signers[validator.EvmAddress] = val + continue + } + missingSigners = append(missingSigners, validator.EvmAddress) + } + + logger.Info("orchestrators that signed the attestation", "count", len(signers)) + i := 0 + for addr, sig := range signers { + logger.Info(addr, "number", i, "signature", sig) + i++ + } + + logger.Info("orchestrators that missed signing the attestation", "count", len(missingSigners)) + for i, addr := range missingSigners { + logger.Info(addr, "number", i) + } +} diff --git a/cmd/qgb/query/config.go b/cmd/qgb/query/config.go new file mode 100644 index 00000000..0ab369a9 --- /dev/null +++ b/cmd/qgb/query/config.go @@ -0,0 +1,42 @@ +package query + +import ( + "github.com/celestiaorg/orchestrator-relayer/cmd/qgb/relayer" + "github.com/spf13/cobra" +) + +const FlagP2PNode = "p2p-node" + +func addFlags(cmd *cobra.Command) *cobra.Command { + cmd.Flags().StringP(relayer.FlagCelesGRPC, "c", "localhost:9090", "Specify the grpc address") + cmd.Flags().StringP(relayer.FlagTendermintRPC, "t", "http://localhost:26657", "Specify the rest rpc address") + cmd.Flags().StringP(FlagP2PNode, "n", "", "P2P target node multiaddress (eg. /ip4/127.0.0.1/tcp/30000/p2p/12D3KooWBSMasWzRSRKXREhediFUwABNZwzJbkZcYz5rYr9Zdmfn)") + + return cmd +} + +type Config struct { + celesGRPC, tendermintRPC string + targetNode string +} + +func parseFlags(cmd *cobra.Command) (Config, error) { + tendermintRPC, err := cmd.Flags().GetString(relayer.FlagTendermintRPC) + if err != nil { + return Config{}, err + } + celesGRPC, err := cmd.Flags().GetString(relayer.FlagCelesGRPC) + if err != nil { + return Config{}, err + } + targetNode, err := cmd.Flags().GetString(FlagP2PNode) + if err != nil { + return Config{}, err + } + + return Config{ + celesGRPC: celesGRPC, + tendermintRPC: tendermintRPC, + targetNode: targetNode, + }, nil +} diff --git a/cmd/qgb/relayer/cmd.go b/cmd/qgb/relayer/cmd.go index 337f3af8..fb1e5c58 100644 --- a/cmd/qgb/relayer/cmd.go +++ b/cmd/qgb/relayer/cmd.go @@ -18,22 +18,22 @@ import ( ) func Command() *cobra.Command { - orchCmd := &cobra.Command{ + relCmd := &cobra.Command{ Use: "relayer", Aliases: []string{"rel"}, Short: "QGB relayer that relays signatures to the target EVM chain", SilenceUsage: true, } - orchCmd.AddCommand( + relCmd.AddCommand( Start(), Init(), keys.Command("relayer"), ) - orchCmd.SetHelpCommand(&cobra.Command{}) + relCmd.SetHelpCommand(&cobra.Command{}) - return orchCmd + return relCmd } // Init initializes the orchestrator store and creates necessary files. diff --git a/cmd/qgb/root/cmd.go b/cmd/qgb/root/cmd.go index 328e604e..50e989e0 100644 --- a/cmd/qgb/root/cmd.go +++ b/cmd/qgb/root/cmd.go @@ -2,6 +2,7 @@ package root import ( "github.com/celestiaorg/orchestrator-relayer/cmd/qgb/generate" + "github.com/celestiaorg/orchestrator-relayer/cmd/qgb/query" "github.com/celestiaorg/celestia-app/x/qgb/client" "github.com/celestiaorg/orchestrator-relayer/cmd/qgb/deploy" @@ -26,6 +27,7 @@ func Cmd() *cobra.Command { deploy.Command(), client.VerifyCmd(), generate.Command(), + query.Command(), ) rootCmd.SetHelpCommand(&cobra.Command{})