code.vegaprotocol.io/vega@v0.79.0/core/blockchain/abci/client.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package abci
    17  
    18  import (
    19  	"context"
    20  	"encoding/base64"
    21  	"errors"
    22  	"os"
    23  	"sync"
    24  	"time"
    25  
    26  	cmtjson "github.com/cometbft/cometbft/libs/json"
    27  	tmlog "github.com/cometbft/cometbft/libs/log"
    28  	tmquery "github.com/cometbft/cometbft/libs/pubsub/query"
    29  	tmclihttp "github.com/cometbft/cometbft/rpc/client/http"
    30  	tmctypes "github.com/cometbft/cometbft/rpc/core/types"
    31  	"github.com/cometbft/cometbft/types"
    32  	tmtypes "github.com/cometbft/cometbft/types"
    33  )
    34  
    35  var ErrEmptyClientAddr = errors.New("abci client addr is empty in config")
    36  
    37  type Client struct {
    38  	tmclt      *tmclihttp.HTTP
    39  	genesisDoc *cachedGenesisDoc
    40  }
    41  
    42  func NewClient(addr string) (*Client, error) {
    43  	if len(addr) <= 0 {
    44  		return nil, ErrEmptyClientAddr
    45  	}
    46  
    47  	clt, err := tmclihttp.New(addr, "/websocket")
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	// log errors only
    53  	clt.Logger = tmlog.NewFilter(
    54  		tmlog.NewTMLogger(os.Stdout),
    55  		tmlog.AllowError(),
    56  	)
    57  
    58  	return &Client{
    59  		tmclt:      clt,
    60  		genesisDoc: newCachedGenesisDoc(),
    61  	}, nil
    62  }
    63  
    64  func (c *Client) SendTransactionAsync(ctx context.Context, bytes []byte) (*tmctypes.ResultBroadcastTx, error) {
    65  	// Fire off the transaction for consensus
    66  	return c.tmclt.BroadcastTxAsync(ctx, bytes)
    67  }
    68  
    69  func (c *Client) CheckTransaction(ctx context.Context, bytes []byte) (*tmctypes.ResultCheckTx, error) {
    70  	return c.tmclt.CheckTx(ctx, bytes)
    71  }
    72  
    73  func (c *Client) SendTransactionSync(ctx context.Context, bytes []byte) (*tmctypes.ResultBroadcastTx, error) {
    74  	// Fire off the transaction for consensus
    75  	return c.tmclt.BroadcastTxSync(ctx, bytes)
    76  }
    77  
    78  func (c *Client) SendTransactionCommit(ctx context.Context, bytes []byte) (*tmctypes.ResultBroadcastTxCommit, error) {
    79  	// Fire off the transaction for consensus
    80  	return c.tmclt.BroadcastTxCommit(ctx, bytes)
    81  }
    82  
    83  // GetGenesisTime retrieves the genesis time from the blockchain.
    84  func (c *Client) GetGenesisTime(ctx context.Context) (genesisTime time.Time, err error) {
    85  	genDoc, err := c.genesisDoc.Get(ctx, c.tmclt)
    86  	if err != nil {
    87  		return time.Time{}, err
    88  	}
    89  	return genDoc.GenesisTime.UTC(), nil
    90  }
    91  
    92  // GetChainID retrieves the chainID from the blockchain.
    93  func (c *Client) GetChainID(ctx context.Context) (chainID string, err error) {
    94  	genDoc, err := c.genesisDoc.Get(ctx, c.tmclt)
    95  	if err != nil {
    96  		return "", err
    97  	}
    98  	return genDoc.ChainID, nil
    99  }
   100  
   101  // GetStatus returns the current status of the chain.
   102  func (c *Client) GetStatus(ctx context.Context) (status *tmctypes.ResultStatus, err error) {
   103  	return c.tmclt.Status(ctx)
   104  }
   105  
   106  // GetNetworkInfo return information of the current network.
   107  func (c *Client) GetNetworkInfo(ctx context.Context) (netInfo *tmctypes.ResultNetInfo, err error) {
   108  	return c.tmclt.NetInfo(ctx)
   109  }
   110  
   111  // GetUnconfirmedTxCount return the current count of unconfirmed transactions.
   112  func (c *Client) GetUnconfirmedTxCount(ctx context.Context) (count int, err error) {
   113  	res, err := c.tmclt.NumUnconfirmedTxs(ctx)
   114  	if err != nil {
   115  		return 0, err
   116  	}
   117  	return res.Count, err
   118  }
   119  
   120  // Health returns the result of the health endpoint of the chain.
   121  func (c *Client) Health(ctx context.Context) (*tmctypes.ResultHealth, error) {
   122  	return c.tmclt.Health(ctx)
   123  }
   124  
   125  func (c *Client) Validators(ctx context.Context, height *int64) ([]*tmtypes.Validator, error) {
   126  	res, err := c.tmclt.Validators(ctx, height, nil, nil)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  	return res.Validators, nil
   131  }
   132  
   133  func (c *Client) Genesis(ctx context.Context) (*tmtypes.GenesisDoc, error) {
   134  	genDoc, err := c.genesisDoc.Get(ctx, c.tmclt)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  	return genDoc, nil
   139  }
   140  
   141  func (c *Client) GenesisValidators(ctx context.Context) ([]*tmtypes.Validator, error) {
   142  	gen, err := c.genesisDoc.Get(ctx, c.tmclt)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  
   147  	validators := make([]*tmtypes.Validator, 0, len(gen.Validators))
   148  	for _, v := range gen.Validators {
   149  		validators = append(validators, &tmtypes.Validator{
   150  			Address:     v.Address,
   151  			PubKey:      v.PubKey,
   152  			VotingPower: v.Power,
   153  		})
   154  	}
   155  
   156  	return validators, nil
   157  }
   158  
   159  // Subscribe subscribes to any event matching query (https://godoc.org/github.com/cometbft/cometbft/types#pkg-constants).
   160  // Subscribe will call fn each time it receives an event from the node.
   161  // The function returns nil when the context is canceled or when fn returns an error.
   162  func (c *Client) Subscribe(ctx context.Context, fn func(tmctypes.ResultEvent) error, queries ...string) error {
   163  	if err := c.tmclt.Start(); err != nil {
   164  		return err
   165  	}
   166  	defer c.tmclt.Stop()
   167  
   168  	errCh := make(chan error)
   169  
   170  	for _, query := range queries {
   171  		q, err := tmquery.New(query)
   172  		if err != nil {
   173  			return err
   174  		}
   175  
   176  		// For subscription we use "vega" as the client name but it's ignored by the implementation.
   177  		// 10 is the channel capacity which is absolutely arbitraty.
   178  		out, err := c.tmclt.Subscribe(ctx, "vega", q.String(), 10)
   179  		if err != nil {
   180  			return err
   181  		}
   182  
   183  		go func() {
   184  			for res := range out {
   185  				if err := fn(res); err != nil {
   186  					errCh <- err
   187  					return
   188  				}
   189  			}
   190  		}()
   191  	}
   192  	defer c.tmclt.UnsubscribeAll(context.Background(), "vega")
   193  
   194  	return <-errCh
   195  }
   196  
   197  func (c *Client) Start() error {
   198  	return nil // Nothing to do for this client type.
   199  }
   200  
   201  type cachedGenesisDoc struct {
   202  	mu           sync.Mutex
   203  	genesisCache *tmtypes.GenesisDoc
   204  }
   205  
   206  func newCachedGenesisDoc() *cachedGenesisDoc {
   207  	return &cachedGenesisDoc{}
   208  }
   209  
   210  func (c *cachedGenesisDoc) Get(
   211  	ctx context.Context,
   212  	clt interface {
   213  		GenesisChunked(context.Context, uint) (*tmctypes.ResultGenesisChunk, error)
   214  	},
   215  ) (*tmtypes.GenesisDoc, error) {
   216  	c.mu.Lock()
   217  	defer c.mu.Unlock()
   218  	if c.genesisCache == nil {
   219  		var err error
   220  		if c.genesisCache, err = c.cacheGenesis(ctx, clt); err != nil {
   221  			return nil, err
   222  		}
   223  	}
   224  
   225  	return c.genesisCache, nil
   226  }
   227  
   228  func (c *cachedGenesisDoc) cacheGenesis(
   229  	ctx context.Context,
   230  	clt interface {
   231  		GenesisChunked(context.Context, uint) (*tmctypes.ResultGenesisChunk, error)
   232  	},
   233  ) (*tmtypes.GenesisDoc, error) {
   234  	var (
   235  		res = &tmctypes.ResultGenesisChunk{
   236  			TotalChunks: 1, // just default to startup our for loop
   237  		}
   238  		buf []byte
   239  		err error
   240  	)
   241  
   242  	for i := 0; i < res.TotalChunks; i++ {
   243  		res, err = clt.GenesisChunked(ctx, uint(i))
   244  		if err != nil {
   245  			return nil, err
   246  		}
   247  
   248  		decoded, err := base64.StdEncoding.DecodeString(res.Data)
   249  		if err != nil {
   250  			return nil, err
   251  		}
   252  
   253  		buf = append(buf, decoded...)
   254  	}
   255  
   256  	genDoc := types.GenesisDoc{}
   257  	err = cmtjson.Unmarshal(buf, &genDoc)
   258  	if err != nil {
   259  		return nil, err
   260  	}
   261  
   262  	return &genDoc, nil
   263  }