watcher.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. package cosmwasm
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "encoding/hex"
  6. "fmt"
  7. "math"
  8. "net/http"
  9. "strconv"
  10. "time"
  11. "github.com/certusone/wormhole/node/pkg/p2p"
  12. gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
  13. "github.com/certusone/wormhole/node/pkg/watchers"
  14. "github.com/prometheus/client_golang/prometheus/promauto"
  15. "github.com/prometheus/client_golang/prometheus"
  16. eth_common "github.com/ethereum/go-ethereum/common"
  17. "github.com/certusone/wormhole/node/pkg/common"
  18. "github.com/certusone/wormhole/node/pkg/readiness"
  19. "github.com/certusone/wormhole/node/pkg/supervisor"
  20. "github.com/tidwall/gjson"
  21. "github.com/wormhole-foundation/wormhole/sdk/vaa"
  22. "go.uber.org/zap"
  23. "nhooyr.io/websocket"
  24. "nhooyr.io/websocket/wsjson"
  25. )
  26. // ReadLimitSize can be used to increase the read limit size on the listening connection. The default read limit size is not large enough,
  27. // causing "failed to read: read limited at 32769 bytes" errors during testing. Increasing this limit effects an internal buffer that
  28. // is used to as part of the zero alloc/copy design.
  29. const ReadLimitSize = 524288
  30. type (
  31. // Watcher is responsible for looking over a cosmwasm blockchain and reporting new transactions to the contract
  32. Watcher struct {
  33. urlWS string
  34. urlLCD string
  35. contract string
  36. msgC chan<- *common.MessagePublication
  37. // Incoming re-observation requests from the network. Pre-filtered to only
  38. // include requests for our chainID.
  39. obsvReqC <-chan *gossipv1.ObservationRequest
  40. // Readiness component
  41. readinessSync readiness.Component
  42. // VAA ChainID of the network we're connecting to.
  43. chainID vaa.ChainID
  44. // Key for contract address in the wasm logs
  45. contractAddressFilterKey string
  46. // Key for contract address in the wasm logs
  47. contractAddressLogKey string
  48. // URL to get the latest block info from
  49. latestBlockURL string
  50. // b64Encoded indicates if transactions are base 64 encoded.
  51. b64Encoded bool
  52. // Human readable chain name
  53. networkName string
  54. }
  55. )
  56. var (
  57. connectionErrors = promauto.NewCounterVec(
  58. prometheus.CounterOpts{
  59. Name: "wormhole_terra_connection_errors_total",
  60. Help: "Total number of connection errors on a cosmwasm chain",
  61. }, []string{"terra_network", "reason"})
  62. messagesConfirmed = promauto.NewCounterVec(
  63. prometheus.CounterOpts{
  64. Name: "wormhole_terra_messages_confirmed_total",
  65. Help: "Total number of verified messages found on a cosmwasm chain",
  66. }, []string{"terra_network"})
  67. currentSlotHeight = promauto.NewGaugeVec(
  68. prometheus.GaugeOpts{
  69. Name: "wormhole_terra_current_height",
  70. Help: "Current slot height on a cosmwasm chain (at default commitment level, not the level used for observations)",
  71. }, []string{"terra_network"})
  72. queryLatency = promauto.NewHistogramVec(
  73. prometheus.HistogramOpts{
  74. Name: "wormhole_terra_query_latency",
  75. Help: "Latency histogram for RPC calls on a cosmwasm chain",
  76. }, []string{"terra_network", "operation"})
  77. )
  78. type clientRequest struct {
  79. JSONRPC string `json:"jsonrpc"`
  80. // A String containing the name of the method to be invoked.
  81. Method string `json:"method"`
  82. // Object to pass as request parameter to the method.
  83. Params [1]string `json:"params"`
  84. // The request id. This can be of any type. It is used to match the
  85. // response with the request that it is replying to.
  86. ID uint64 `json:"id"`
  87. }
  88. // NewWatcher creates a new cosmwasm contract watcher
  89. func NewWatcher(
  90. urlWS string,
  91. urlLCD string,
  92. contract string,
  93. msgC chan<- *common.MessagePublication,
  94. obsvReqC <-chan *gossipv1.ObservationRequest,
  95. chainID vaa.ChainID,
  96. env common.Environment,
  97. ) *Watcher {
  98. // CosmWasm 1.0.0
  99. contractAddressFilterKey := "execute._contract_address"
  100. contractAddressLogKey := "_contract_address"
  101. // Do not add a leading slash
  102. latestBlockURL := "cosmos/base/tendermint/v1beta1/blocks/latest"
  103. // Injective does not base64 encode parameters (as of release v1.11.2).
  104. // Terra does not base64 encode parameters (as of v3.0.1 software upgrade)
  105. // Terra2 no longer base64 encodes parameters.
  106. b64Encoded := env == common.UnsafeDevNet || (chainID != vaa.ChainIDInjective && chainID != vaa.ChainIDTerra2 && chainID != vaa.ChainIDTerra)
  107. // Human readable network name
  108. networkName := vaa.ChainID(chainID).String()
  109. return &Watcher{
  110. urlWS: urlWS,
  111. urlLCD: urlLCD,
  112. contract: contract,
  113. msgC: msgC,
  114. obsvReqC: obsvReqC,
  115. readinessSync: common.MustConvertChainIdToReadinessSyncing(chainID),
  116. chainID: chainID,
  117. contractAddressFilterKey: contractAddressFilterKey,
  118. contractAddressLogKey: contractAddressLogKey,
  119. latestBlockURL: latestBlockURL,
  120. b64Encoded: b64Encoded,
  121. networkName: networkName,
  122. }
  123. }
  124. func (e *Watcher) Run(ctx context.Context) error {
  125. p2p.DefaultRegistry.SetNetworkStats(e.chainID, &gossipv1.Heartbeat_Network{
  126. ContractAddress: e.contract,
  127. })
  128. errC := make(chan error)
  129. logger := supervisor.Logger(ctx)
  130. logger.Info("Starting watcher",
  131. zap.String("watcher_name", "cosmwasm"),
  132. zap.String("urlWS", e.urlWS),
  133. zap.String("urlLCD", e.urlLCD),
  134. zap.String("contract", e.contract),
  135. zap.String("chainID", e.chainID.String()),
  136. )
  137. logger.Info("connecting to websocket", zap.String("network", e.networkName), zap.String("url", e.urlWS))
  138. //nolint:bodyclose // The close is down below. The linter misses it.
  139. c, _, err := websocket.Dial(ctx, e.urlWS, nil)
  140. if err != nil {
  141. p2p.DefaultRegistry.AddErrorCount(e.chainID, 1)
  142. connectionErrors.WithLabelValues(e.networkName, "websocket_dial_error").Inc()
  143. return fmt.Errorf("websocket dial failed: %w", err)
  144. }
  145. defer c.Close(websocket.StatusNormalClosure, "")
  146. e.logVersion(ctx, logger, c)
  147. c.SetReadLimit(ReadLimitSize)
  148. // Subscribe to smart contract transactions
  149. params := [...]string{fmt.Sprintf("tm.event='Tx' AND %s='%s'", e.contractAddressFilterKey, e.contract)}
  150. command := &clientRequest{
  151. JSONRPC: "2.0",
  152. Method: "subscribe",
  153. Params: params,
  154. ID: 1,
  155. }
  156. err = wsjson.Write(ctx, c, command)
  157. if err != nil {
  158. p2p.DefaultRegistry.AddErrorCount(e.chainID, 1)
  159. connectionErrors.WithLabelValues(e.networkName, "websocket_subscription_error").Inc()
  160. return fmt.Errorf("websocket subscription failed: %w", err)
  161. }
  162. // Wait for the success response
  163. _, _, err = c.Read(ctx)
  164. if err != nil {
  165. p2p.DefaultRegistry.AddErrorCount(e.chainID, 1)
  166. connectionErrors.WithLabelValues(e.networkName, "event_subscription_error").Inc()
  167. return fmt.Errorf("event subscription failed: %w", err)
  168. }
  169. logger.Info("subscribed to new transaction events", zap.String("network", e.networkName))
  170. readiness.SetReady(e.readinessSync)
  171. common.RunWithScissors(ctx, errC, "cosmwasm_block_height", func(ctx context.Context) error {
  172. t := time.NewTicker(5 * time.Second)
  173. client := &http.Client{
  174. Timeout: time.Second * 5,
  175. }
  176. for {
  177. select {
  178. case <-ctx.Done():
  179. return nil
  180. case <-t.C:
  181. msm := time.Now()
  182. // Query and report height and set currentSlotHeight
  183. resp, err := client.Get(fmt.Sprintf("%s/%s", e.urlLCD, e.latestBlockURL)) //nolint:noctx // TODO FIXME we should propagate context with Deadline here.
  184. if err != nil {
  185. logger.Error("query latest block response error", zap.String("network", e.networkName), zap.Error(err))
  186. continue
  187. }
  188. blocksBody, err := common.SafeRead(resp.Body)
  189. if err != nil {
  190. logger.Error("query latest block response read error", zap.String("network", e.networkName), zap.Error(err))
  191. errC <- err //nolint:channelcheck // The watcher will exit anyway
  192. resp.Body.Close()
  193. continue
  194. }
  195. resp.Body.Close()
  196. // Update the prom metrics with how long the http request took to the rpc
  197. queryLatency.WithLabelValues(e.networkName, "block_latest").Observe(time.Since(msm).Seconds())
  198. blockJSON := string(blocksBody)
  199. latestBlock := gjson.Get(blockJSON, "block.header.height")
  200. logger.Debug("current height", zap.String("network", e.networkName), zap.Int64("block", latestBlock.Int()))
  201. currentSlotHeight.WithLabelValues(e.networkName).Set(float64(latestBlock.Int()))
  202. p2p.DefaultRegistry.SetNetworkStats(e.chainID, &gossipv1.Heartbeat_Network{
  203. Height: latestBlock.Int(),
  204. ContractAddress: e.contract,
  205. })
  206. readiness.SetReady(e.readinessSync)
  207. }
  208. }
  209. })
  210. common.RunWithScissors(ctx, errC, "cosmwasm_objs_req", func(ctx context.Context) error {
  211. for {
  212. select {
  213. case <-ctx.Done():
  214. return nil
  215. case r := <-e.obsvReqC:
  216. // node/pkg/node/reobserve.go already enforces the chain id is a valid uint16
  217. // and only writes to the channel for this chain id.
  218. // If either of the below cases are true, something has gone wrong
  219. if r.ChainId > math.MaxUint16 || vaa.ChainID(r.ChainId) != e.chainID {
  220. panic("invalid chain ID")
  221. }
  222. tx := hex.EncodeToString(r.TxHash)
  223. logger.Info("received observation request", zap.String("network", e.networkName), zap.String("tx_hash", tx))
  224. client := &http.Client{
  225. Timeout: time.Second * 5,
  226. }
  227. // Query for tx by hash
  228. resp, err := client.Get(fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/%s", e.urlLCD, tx)) //nolint:noctx // TODO FIXME we should propagate context with Deadline here.
  229. if err != nil {
  230. logger.Error("query tx response error", zap.String("network", e.networkName), zap.Error(err))
  231. continue
  232. }
  233. txBody, err := common.SafeRead(resp.Body)
  234. if err != nil {
  235. logger.Error("query tx response read error", zap.String("network", e.networkName), zap.Error(err))
  236. resp.Body.Close()
  237. continue
  238. }
  239. resp.Body.Close()
  240. txJSON := string(txBody)
  241. txHashRaw := gjson.Get(txJSON, "tx_response.txhash")
  242. if !txHashRaw.Exists() {
  243. logger.Error("tx does not have tx hash", zap.String("network", e.networkName), zap.String("payload", txJSON))
  244. continue
  245. }
  246. txHash := txHashRaw.String()
  247. events := gjson.Get(txJSON, "tx_response.events")
  248. if !events.Exists() {
  249. logger.Error("tx has no events", zap.String("network", e.networkName), zap.String("payload", txJSON))
  250. continue
  251. }
  252. contractAddressLogKey := e.contractAddressLogKey
  253. if e.chainID == vaa.ChainIDTerra {
  254. // Terra Classic upgraded WASM versions starting at block 13215800. If this transaction is from before that, we need to use the old contract address format.
  255. blockHeightStr := gjson.Get(txJSON, "tx_response.height")
  256. if !blockHeightStr.Exists() {
  257. logger.Error("failed to look up block height on old reobserved tx", zap.String("network", e.networkName), zap.String("txHash", txHash), zap.String("payload", txJSON))
  258. continue
  259. }
  260. blockHeight := blockHeightStr.Int()
  261. if blockHeight < 13215800 {
  262. logger.Info("doing look up of old tx", zap.String("network", e.networkName), zap.String("txHash", txHash), zap.Int64("blockHeight", blockHeight))
  263. contractAddressLogKey = "contract_address"
  264. }
  265. }
  266. msgs := EventsToMessagePublications(e.contract, txHash, events.Array(), logger, e.chainID, contractAddressLogKey, e.b64Encoded)
  267. for _, msg := range msgs {
  268. msg.IsReobservation = true
  269. e.msgC <- msg //nolint:channelcheck // The channel to the processor is buffered and shared across chains, if it backs up we should stop processing new observations
  270. messagesConfirmed.WithLabelValues(e.networkName).Inc()
  271. watchers.ReobservationsByChain.WithLabelValues(e.networkName, "std").Inc()
  272. }
  273. }
  274. }
  275. })
  276. common.RunWithScissors(ctx, errC, "cosmwasm_data_pump", func(ctx context.Context) error {
  277. for {
  278. select {
  279. case <-ctx.Done():
  280. return nil
  281. default:
  282. _, message, err := c.Read(ctx)
  283. if err != nil {
  284. p2p.DefaultRegistry.AddErrorCount(e.chainID, 1)
  285. connectionErrors.WithLabelValues(e.networkName, "channel_read_error").Inc()
  286. logger.Error("error reading channel", zap.String("network", e.networkName), zap.Error(err))
  287. errC <- err //nolint:channelcheck // The watcher will exit anyway
  288. return nil
  289. }
  290. // Received a message from the blockchain
  291. json := string(message)
  292. txHashRaw := gjson.Get(json, "result.events.tx\\.hash.0")
  293. if !txHashRaw.Exists() {
  294. logger.Warn("message does not have tx hash", zap.String("network", e.networkName), zap.String("payload", json))
  295. continue
  296. }
  297. txHash := txHashRaw.String()
  298. events := gjson.Get(json, "result.data.value.TxResult.result.events")
  299. if !events.Exists() {
  300. logger.Warn("message has no events", zap.String("network", e.networkName), zap.String("payload", json))
  301. continue
  302. }
  303. msgs := EventsToMessagePublications(e.contract, txHash, events.Array(), logger, e.chainID, e.contractAddressLogKey, e.b64Encoded)
  304. for _, msg := range msgs {
  305. e.msgC <- msg //nolint:channelcheck // The channel to the processor is buffered and shared across chains, if it backs up we should stop processing new observations
  306. messagesConfirmed.WithLabelValues(e.networkName).Inc()
  307. }
  308. // We do not send guardian changes to the processor - ETH guardians are the source of truth.
  309. }
  310. }
  311. })
  312. select {
  313. case <-ctx.Done():
  314. return ctx.Err()
  315. case err := <-errC:
  316. return err
  317. }
  318. }
  319. // logVersion uses the abci_info rpc to log node version information.
  320. func (e *Watcher) logVersion(ctx context.Context, logger *zap.Logger, c *websocket.Conn) {
  321. // NOTE: This function is ugly because this watcher doesn't use a
  322. // client library. It can be rewritten in a followup change.
  323. //
  324. // Get information about the application (the /status endpoint returns the
  325. // version of the tendermint or cometbft library, no the actual application
  326. // version.
  327. //
  328. // From:
  329. // https://docs.cometbft.com/v0.34/rpc/#/ABCI/abci_info
  330. // https://docs.tendermint.com/v0.34/rpc/#/ABCI/abci_info
  331. command := map[string]interface{}{
  332. "jsonrpc": "2.0",
  333. "method": "abci_info",
  334. "params": []interface{}{},
  335. "id": 1,
  336. }
  337. err := wsjson.Write(ctx, c, command)
  338. if err != nil {
  339. logger.Error("problem retrieving node version when building request",
  340. zap.String("network", e.networkName),
  341. zap.Error(err),
  342. )
  343. return
  344. }
  345. // Wait for the success response
  346. _, data, err := c.Read(ctx)
  347. if err != nil {
  348. logger.Error("problem retrieving node version",
  349. zap.String("network", e.networkName),
  350. zap.Error(err),
  351. )
  352. return
  353. }
  354. version := gjson.GetBytes(data, "result.response.version").String()
  355. if version == "" {
  356. logger.Error("problem retrieving node version due to an empty response version ",
  357. zap.String("network", e.networkName),
  358. zap.String("response", string(data)),
  359. )
  360. return
  361. }
  362. logger.Info("node version",
  363. zap.String("network", e.networkName),
  364. zap.String("version", version),
  365. )
  366. }
  367. func EventsToMessagePublications(contract string, txHash string, events []gjson.Result, logger *zap.Logger, chainID vaa.ChainID, contractAddressKey string, b64Encoded bool) []*common.MessagePublication {
  368. networkName := chainID.String()
  369. msgs := make([]*common.MessagePublication, 0, len(events))
  370. for _, event := range events {
  371. if !event.IsObject() {
  372. logger.Warn("event is invalid", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("event", event.String()))
  373. continue
  374. }
  375. eventType := gjson.Get(event.String(), "type")
  376. if eventType.String() == "recv_packet" && chainID != vaa.ChainIDWormchain {
  377. logger.Warn("processing ibc-related events is disabled", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("event", event.String()))
  378. return []*common.MessagePublication{}
  379. }
  380. if eventType.String() != "wasm" {
  381. continue
  382. }
  383. attributes := gjson.Get(event.String(), "attributes")
  384. if !attributes.Exists() {
  385. logger.Warn("message event has no attributes", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("event", event.String()))
  386. continue
  387. }
  388. mappedAttributes := map[string]string{}
  389. for _, attribute := range attributes.Array() {
  390. if !attribute.IsObject() {
  391. logger.Warn("event attribute is invalid", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("attribute", attribute.String()))
  392. continue
  393. }
  394. keyBase := gjson.Get(attribute.String(), "key")
  395. if !keyBase.Exists() {
  396. logger.Warn("event attribute does not have key", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("attribute", attribute.String()))
  397. continue
  398. }
  399. valueBase := gjson.Get(attribute.String(), "value")
  400. if !valueBase.Exists() {
  401. logger.Warn("event attribute does not have value", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("attribute", attribute.String()))
  402. continue
  403. }
  404. var key, value []byte
  405. if b64Encoded {
  406. var err error
  407. key, err = base64.StdEncoding.DecodeString(keyBase.String())
  408. if err != nil {
  409. logger.Warn("event key attribute is invalid", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("key", keyBase.String()))
  410. continue
  411. }
  412. value, err = base64.StdEncoding.DecodeString(valueBase.String())
  413. if err != nil {
  414. logger.Warn("event value attribute is invalid", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("key", keyBase.String()), zap.String("value", valueBase.String()))
  415. continue
  416. }
  417. } else {
  418. key = []byte(keyBase.String())
  419. value = []byte(valueBase.String())
  420. }
  421. if _, ok := mappedAttributes[string(key)]; ok {
  422. logger.Debug("duplicate key in events", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("key", keyBase.String()), zap.String("value", valueBase.String()))
  423. continue
  424. }
  425. logger.Debug("msg attribute",
  426. zap.String("network", networkName),
  427. zap.String("tx_hash", txHash), zap.String("key", string(key)), zap.String("value", string(value)))
  428. mappedAttributes[string(key)] = string(value)
  429. }
  430. contractAddress, ok := mappedAttributes[contractAddressKey]
  431. if !ok {
  432. logger.Warn("wasm event without contract address field set", zap.String("network", networkName), zap.String("event", event.String()))
  433. continue
  434. }
  435. // This is not a wormhole message
  436. if contractAddress != contract {
  437. continue
  438. }
  439. payload, ok := mappedAttributes["message.message"]
  440. if !ok {
  441. logger.Error("wormhole event does not have a message field", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("attributes", attributes.String()))
  442. continue
  443. }
  444. sender, ok := mappedAttributes["message.sender"]
  445. if !ok {
  446. logger.Error("wormhole event does not have a sender field", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("attributes", attributes.String()))
  447. continue
  448. }
  449. chainId, ok := mappedAttributes["message.chain_id"]
  450. if !ok {
  451. logger.Error("wormhole event does not have a chain_id field", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("attributes", attributes.String()))
  452. continue
  453. }
  454. nonce, ok := mappedAttributes["message.nonce"]
  455. if !ok {
  456. logger.Error("wormhole event does not have a nonce field", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("attributes", attributes.String()))
  457. continue
  458. }
  459. sequence, ok := mappedAttributes["message.sequence"]
  460. if !ok {
  461. logger.Error("wormhole event does not have a sequence field", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("attributes", attributes.String()))
  462. continue
  463. }
  464. blockTime, ok := mappedAttributes["message.block_time"]
  465. if !ok {
  466. logger.Error("wormhole event does not have a block_time field", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("attributes", attributes.String()))
  467. continue
  468. }
  469. logger.Info("new message detected on cosmwasm",
  470. zap.String("network", networkName),
  471. zap.String("chainId", chainId),
  472. zap.String("txHash", txHash),
  473. zap.String("sender", sender),
  474. zap.String("nonce", nonce),
  475. zap.String("sequence", sequence),
  476. zap.String("blockTime", blockTime),
  477. )
  478. senderAddress, err := StringToAddress(sender)
  479. if err != nil {
  480. logger.Error("cannot decode emitter hex", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("value", sender))
  481. continue
  482. }
  483. txHashValue, err := StringToHash(txHash)
  484. if err != nil {
  485. logger.Error("cannot decode tx hash hex", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("value", txHash))
  486. continue
  487. }
  488. payloadValue, err := hex.DecodeString(payload)
  489. if err != nil {
  490. logger.Error("cannot decode payload", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("value", payload))
  491. continue
  492. }
  493. blockTimeInt, err := strconv.ParseInt(blockTime, 10, 64)
  494. if err != nil {
  495. logger.Error("blocktime cannot be parsed as int", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("value", blockTime))
  496. continue
  497. }
  498. nonceInt, err := strconv.ParseUint(nonce, 10, 32)
  499. if err != nil {
  500. logger.Error("nonce cannot be parsed as int", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("value", blockTime))
  501. continue
  502. }
  503. sequenceInt, err := strconv.ParseUint(sequence, 10, 64)
  504. if err != nil {
  505. logger.Error("sequence cannot be parsed as int", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("value", blockTime))
  506. continue
  507. }
  508. messagePublication := &common.MessagePublication{
  509. TxID: txHashValue.Bytes(),
  510. Timestamp: time.Unix(blockTimeInt, 0),
  511. Nonce: uint32(nonceInt),
  512. Sequence: sequenceInt,
  513. EmitterChain: chainID,
  514. EmitterAddress: senderAddress,
  515. Payload: payloadValue,
  516. ConsistencyLevel: 0, // Instant finality
  517. }
  518. msgs = append(msgs, messagePublication)
  519. }
  520. return msgs
  521. }
  522. // StringToAddress convert string into address
  523. func StringToAddress(value string) (vaa.Address, error) {
  524. var address vaa.Address
  525. res, err := hex.DecodeString(value)
  526. if err != nil {
  527. return address, err
  528. }
  529. copy(address[:], res)
  530. return address, nil
  531. }
  532. // StringToHash convert string into transaction hash
  533. func StringToHash(value string) (eth_common.Hash, error) {
  534. var hash eth_common.Hash
  535. res, err := hex.DecodeString(value)
  536. if err != nil {
  537. return hash, err
  538. }
  539. copy(hash[:], res)
  540. return hash, nil
  541. }