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 }