Guardian support for Injective (#1327)

This commit is contained in:
bruce-riley 2022-07-06 09:19:57 -05:00 committed by GitHub
parent e8620ccea9
commit d7b7cefa99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 134 additions and 84 deletions

View File

@ -117,6 +117,7 @@ func runListNodes(cmd *cobra.Command, args []string) {
networks = append(networks, network{"Ropsten", vaa.ChainIDEthereumRopsten}) networks = append(networks, network{"Ropsten", vaa.ChainIDEthereumRopsten})
networks = append(networks, network{"Moonbeam", vaa.ChainIDMoonbeam}) networks = append(networks, network{"Moonbeam", vaa.ChainIDMoonbeam})
networks = append(networks, network{"Neon", vaa.ChainIDNeon}) networks = append(networks, network{"Neon", vaa.ChainIDNeon})
networks = append(networks, network{"Injective", vaa.ChainIDInjective})
} }
if len(only) > 0 { if len(only) > 0 {

View File

@ -41,7 +41,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/certusone/wormhole/node/pkg/terra" cosmwasm "github.com/certusone/wormhole/node/pkg/terra"
"github.com/certusone/wormhole/node/pkg/algorand" "github.com/certusone/wormhole/node/pkg/algorand"
@ -114,6 +114,10 @@ var (
terra2LCD *string terra2LCD *string
terra2Contract *string terra2Contract *string
injectiveWS *string
injectiveLCD *string
injectiveContract *string
algorandIndexerRPC *string algorandIndexerRPC *string
algorandIndexerToken *string algorandIndexerToken *string
algorandAlgodRPC *string algorandAlgodRPC *string
@ -218,6 +222,10 @@ func init() {
terra2LCD = NodeCmd.Flags().String("terra2LCD", "", "Path to LCD service root for http calls") terra2LCD = NodeCmd.Flags().String("terra2LCD", "", "Path to LCD service root for http calls")
terra2Contract = NodeCmd.Flags().String("terra2Contract", "", "Wormhole contract address on Terra 2 blockchain") terra2Contract = NodeCmd.Flags().String("terra2Contract", "", "Wormhole contract address on Terra 2 blockchain")
injectiveWS = NodeCmd.Flags().String("injectiveWS", "", "Path to root for Injective websocket connection")
injectiveLCD = NodeCmd.Flags().String("injectiveLCD", "", "Path to LCD service root for Injective http calls")
injectiveContract = NodeCmd.Flags().String("injectiveContract", "", "Wormhole contract address on Injective blockchain")
algorandIndexerRPC = NodeCmd.Flags().String("algorandIndexerRPC", "", "Algorand Indexer RPC URL") algorandIndexerRPC = NodeCmd.Flags().String("algorandIndexerRPC", "", "Algorand Indexer RPC URL")
algorandIndexerToken = NodeCmd.Flags().String("algorandIndexerToken", "", "Algorand Indexer access token") algorandIndexerToken = NodeCmd.Flags().String("algorandIndexerToken", "", "Algorand Indexer access token")
algorandAlgodRPC = NodeCmd.Flags().String("algorandAlgodRPC", "", "Algorand Algod RPC URL") algorandAlgodRPC = NodeCmd.Flags().String("algorandAlgodRPC", "", "Algorand Algod RPC URL")
@ -352,6 +360,7 @@ func runNode(cmd *cobra.Command, args []string) {
readiness.RegisterComponent(common.ReadinessEthRopstenSyncing) readiness.RegisterComponent(common.ReadinessEthRopstenSyncing)
readiness.RegisterComponent(common.ReadinessMoonbeamSyncing) readiness.RegisterComponent(common.ReadinessMoonbeamSyncing)
readiness.RegisterComponent(common.ReadinessNeonSyncing) readiness.RegisterComponent(common.ReadinessNeonSyncing)
readiness.RegisterComponent(common.ReadinessInjectiveSyncing)
} }
if *statusAddr != "" { if *statusAddr != "" {
@ -499,6 +508,15 @@ func runNode(cmd *cobra.Command, args []string) {
if *neonContract == "" { if *neonContract == "" {
logger.Fatal("Please specify --neonContract") logger.Fatal("Please specify --neonContract")
} }
if *injectiveWS == "" {
logger.Fatal("Please specify --injectiveWS")
}
if *injectiveLCD == "" {
logger.Fatal("Please specify --injectiveLCD")
}
if *injectiveContract == "" {
logger.Fatal("Please specify --injectiveContract")
}
} else { } else {
if *ethRopstenRPC != "" { if *ethRopstenRPC != "" {
logger.Fatal("Please do not specify --ethRopstenRPC in non-testnet mode") logger.Fatal("Please do not specify --ethRopstenRPC in non-testnet mode")
@ -518,6 +536,15 @@ func runNode(cmd *cobra.Command, args []string) {
if *neonContract != "" && !*unsafeDevMode { if *neonContract != "" && !*unsafeDevMode {
logger.Fatal("Please do not specify --neonContract") logger.Fatal("Please do not specify --neonContract")
} }
if *injectiveWS != "" && !*unsafeDevMode {
logger.Fatal("Please do not specify --injectiveWS")
}
if *injectiveLCD != "" && !*unsafeDevMode {
logger.Fatal("Please do not specify --injectiveLCD")
}
if *injectiveContract != "" && !*unsafeDevMode {
logger.Fatal("Please do not specify --injectiveContract")
}
} }
if *nodeName == "" { if *nodeName == "" {
logger.Fatal("Please specify --nodeName") logger.Fatal("Please specify --nodeName")
@ -720,6 +747,7 @@ func runNode(cmd *cobra.Command, args []string) {
chainObsvReqC[vaa.ChainIDMoonbeam] = make(chan *gossipv1.ObservationRequest) chainObsvReqC[vaa.ChainIDMoonbeam] = make(chan *gossipv1.ObservationRequest)
chainObsvReqC[vaa.ChainIDNeon] = make(chan *gossipv1.ObservationRequest) chainObsvReqC[vaa.ChainIDNeon] = make(chan *gossipv1.ObservationRequest)
chainObsvReqC[vaa.ChainIDEthereumRopsten] = make(chan *gossipv1.ObservationRequest) chainObsvReqC[vaa.ChainIDEthereumRopsten] = make(chan *gossipv1.ObservationRequest)
chainObsvReqC[vaa.ChainIDInjective] = make(chan *gossipv1.ObservationRequest)
} }
// Multiplex observation requests to the appropriate chain // Multiplex observation requests to the appropriate chain
@ -904,16 +932,24 @@ func runNode(cmd *cobra.Command, args []string) {
logger.Info("Starting Terra watcher") logger.Info("Starting Terra watcher")
if err := supervisor.Run(ctx, "terrawatch", if err := supervisor.Run(ctx, "terrawatch",
terra.NewWatcher(*terraWS, *terraLCD, *terraContract, lockC, setC, chainObsvReqC[vaa.ChainIDTerra], common.ReadinessTerraSyncing, vaa.ChainIDTerra).Run); err != nil { cosmwasm.NewWatcher(*terraWS, *terraLCD, *terraContract, lockC, setC, chainObsvReqC[vaa.ChainIDTerra], common.ReadinessTerraSyncing, vaa.ChainIDTerra).Run); err != nil {
return err return err
} }
logger.Info("Starting Terra 2 watcher") logger.Info("Starting Terra 2 watcher")
if err := supervisor.Run(ctx, "terra2watch", if err := supervisor.Run(ctx, "terra2watch",
terra.NewWatcher(*terra2WS, *terra2LCD, *terra2Contract, lockC, setC, chainObsvReqC[vaa.ChainIDTerra2], common.ReadinessTerra2Syncing, vaa.ChainIDTerra2).Run); err != nil { cosmwasm.NewWatcher(*terra2WS, *terra2LCD, *terra2Contract, lockC, setC, chainObsvReqC[vaa.ChainIDTerra2], common.ReadinessTerra2Syncing, vaa.ChainIDTerra2).Run); err != nil {
return err return err
} }
if *testnetMode {
logger.Info("Starting Injective watcher")
if err := supervisor.Run(ctx, "injectivewatch",
cosmwasm.NewWatcher(*injectiveWS, *injectiveLCD, *injectiveContract, lockC, setC, chainObsvReqC[vaa.ChainIDInjective], common.ReadinessInjectiveSyncing, vaa.ChainIDInjective).Run); err != nil {
return err
}
}
if *testnetMode || *unsafeDevMode { if *testnetMode || *unsafeDevMode {
if err := supervisor.Run(ctx, "algorandwatch", if err := supervisor.Run(ctx, "algorandwatch",
algorand.NewWatcher(*algorandIndexerRPC, *algorandIndexerToken, *algorandAlgodRPC, *algorandAlgodToken, *algorandAppID, lockC, setC, chainObsvReqC[vaa.ChainIDAlgorand]).Run); err != nil { algorand.NewWatcher(*algorandIndexerRPC, *algorandIndexerToken, *algorandAlgodRPC, *algorandAlgodToken, *algorandAppID, lockC, setC, chainObsvReqC[vaa.ChainIDAlgorand]).Run); err != nil {

View File

@ -19,7 +19,7 @@ import (
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1" gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
nodev1 "github.com/certusone/wormhole/node/pkg/proto/node/v1" nodev1 "github.com/certusone/wormhole/node/pkg/proto/node/v1"
"github.com/certusone/wormhole/node/pkg/terra" cosmwasm "github.com/certusone/wormhole/node/pkg/terra"
"github.com/certusone/wormhole/node/pkg/vaa" "github.com/certusone/wormhole/node/pkg/vaa"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
@ -65,7 +65,7 @@ func getAdminClient(ctx context.Context, addr string) (*grpc.ClientConn, error,
return conn, err, c return conn, err, c
} }
func getSequencesForTxhash(txhash string, fcd string, contractAddressLogKey string, coreContract string, emitter Emitter) ([]uint64, error) { func getSequencesForTxhash(txhash string, fcd string, contractAddressLogKey string, coreContract string, emitter Emitter, chainID vaa.ChainID) ([]uint64, error) {
client := &http.Client{ client := &http.Client{
Timeout: time.Second * 5, Timeout: time.Second * 5,
} }
@ -86,21 +86,21 @@ func getSequencesForTxhash(txhash string, fcd string, contractAddressLogKey stri
} }
txHashRaw := gjson.Get(txJSON, "tx_response.txhash") txHashRaw := gjson.Get(txJSON, "tx_response.txhash")
if !txHashRaw.Exists() { if !txHashRaw.Exists() {
return []uint64{}, fmt.Errorf("terra tx does not have tx hash") return []uint64{}, fmt.Errorf("cosmwasm tx does not have tx hash")
} }
txHash := txHashRaw.String() txHash := txHashRaw.String()
events := gjson.Get(txJSON, "tx_response.events") events := gjson.Get(txJSON, "tx_response.events")
if !events.Exists() { if !events.Exists() {
return []uint64{}, fmt.Errorf("terra tx has no events") return []uint64{}, fmt.Errorf("cosmwasm tx has no events")
} }
msgs := EventsToMessagePublications(coreContract, txHash, events.Array(), contractAddressLogKey) msgs := EventsToMessagePublications(coreContract, txHash, events.Array(), chainID, contractAddressLogKey)
// Should only ever be 1 message. Stole the above function from watcher.go // Should only ever be 1 message. Stole the above function from watcher.go
var sequences = []uint64{} var sequences = []uint64{}
for _, msg := range msgs { for _, msg := range msgs {
tokenBridgeEmitter, err := vaa.StringToAddress(emitter.Emitter) tokenBridgeEmitter, err := vaa.StringToAddress(emitter.Emitter)
if err != nil { if err != nil {
log.Fatalf("Terra emitter address is not valid: %s", emitter.Emitter) log.Fatalf("Emitter address is not valid: %s", emitter.Emitter)
} }
if msg.EmitterAddress == tokenBridgeEmitter { if msg.EmitterAddress == tokenBridgeEmitter {
sequences = append(sequences, msg.Sequence) sequences = append(sequences, msg.Sequence)
@ -110,11 +110,11 @@ func getSequencesForTxhash(txhash string, fcd string, contractAddressLogKey stri
} }
// This was stolen from pkg/terra/watcher.go // This was stolen from pkg/terra/watcher.go
func EventsToMessagePublications(contract string, txHash string, events []gjson.Result, contractAddressLogKey string) []*common.MessagePublication { func EventsToMessagePublications(contract string, txHash string, events []gjson.Result, chainID vaa.ChainID, contractAddressLogKey string) []*common.MessagePublication {
msgs := make([]*common.MessagePublication, 0, len(events)) msgs := make([]*common.MessagePublication, 0, len(events))
for _, event := range events { for _, event := range events {
if !event.IsObject() { if !event.IsObject() {
log.Println("terra event is invalid", zap.String("tx_hash", txHash), zap.String("event", event.String())) log.Println("event is invalid", zap.String("tx_hash", txHash), zap.String("event", event.String()))
continue continue
} }
eventType := gjson.Get(event.String(), "type") eventType := gjson.Get(event.String(), "type")
@ -124,34 +124,34 @@ func EventsToMessagePublications(contract string, txHash string, events []gjson.
attributes := gjson.Get(event.String(), "attributes") attributes := gjson.Get(event.String(), "attributes")
if !attributes.Exists() { if !attributes.Exists() {
log.Println("terra message event has no attributes", zap.String("tx_hash", txHash), zap.String("event", event.String())) log.Println("message event has no attributes", zap.String("tx_hash", txHash), zap.String("event", event.String()))
continue continue
} }
mappedAttributes := map[string]string{} mappedAttributes := map[string]string{}
for _, attribute := range attributes.Array() { for _, attribute := range attributes.Array() {
if !attribute.IsObject() { if !attribute.IsObject() {
log.Println("terra event attribute is invalid", zap.String("tx_hash", txHash), zap.String("attribute", attribute.String())) log.Println("event attribute is invalid", zap.String("tx_hash", txHash), zap.String("attribute", attribute.String()))
continue continue
} }
keyBase := gjson.Get(attribute.String(), "key") keyBase := gjson.Get(attribute.String(), "key")
if !keyBase.Exists() { if !keyBase.Exists() {
log.Println("terra event attribute does not have key", zap.String("tx_hash", txHash), zap.String("attribute", attribute.String())) log.Println("event attribute does not have key", zap.String("tx_hash", txHash), zap.String("attribute", attribute.String()))
continue continue
} }
valueBase := gjson.Get(attribute.String(), "value") valueBase := gjson.Get(attribute.String(), "value")
if !valueBase.Exists() { if !valueBase.Exists() {
log.Println("terra event attribute does not have value", zap.String("tx_hash", txHash), zap.String("attribute", attribute.String())) log.Println("event attribute does not have value", zap.String("tx_hash", txHash), zap.String("attribute", attribute.String()))
continue continue
} }
key, err := base64.StdEncoding.DecodeString(keyBase.String()) key, err := base64.StdEncoding.DecodeString(keyBase.String())
if err != nil { if err != nil {
log.Println("terra event key attribute is invalid", zap.String("tx_hash", txHash), zap.String("key", keyBase.String())) log.Println("event key attribute is invalid", zap.String("tx_hash", txHash), zap.String("key", keyBase.String()))
continue continue
} }
value, err := base64.StdEncoding.DecodeString(valueBase.String()) value, err := base64.StdEncoding.DecodeString(valueBase.String())
if err != nil { if err != nil {
log.Println("terra event value attribute is invalid", zap.String("tx_hash", txHash), zap.String("key", keyBase.String()), zap.String("value", valueBase.String())) log.Println("event value attribute is invalid", zap.String("tx_hash", txHash), zap.String("key", keyBase.String()), zap.String("value", valueBase.String()))
continue continue
} }
@ -165,7 +165,7 @@ func EventsToMessagePublications(contract string, txHash string, events []gjson.
contractAddress, ok := mappedAttributes[contractAddressLogKey] contractAddress, ok := mappedAttributes[contractAddressLogKey]
if !ok { if !ok {
log.Println("terra wasm event without contract address field set", zap.String("event", event.String())) log.Println("wasm event without contract address field set", zap.String("event", event.String()))
continue continue
} }
// This is not a wormhole message // This is not a wormhole message
@ -199,12 +199,12 @@ func EventsToMessagePublications(contract string, txHash string, events []gjson.
continue continue
} }
senderAddress, err := terra.StringToAddress(sender) senderAddress, err := cosmwasm.StringToAddress(sender)
if err != nil { if err != nil {
log.Println("cannot decode emitter hex", zap.String("tx_hash", txHash), zap.String("value", sender)) log.Println("cannot decode emitter hex", zap.String("tx_hash", txHash), zap.String("value", sender))
continue continue
} }
txHashValue, err := terra.StringToHash(txHash) txHashValue, err := cosmwasm.StringToHash(txHash)
if err != nil { if err != nil {
log.Println("cannot decode tx hash hex", zap.String("tx_hash", txHash), zap.String("value", txHash)) log.Println("cannot decode tx hash hex", zap.String("tx_hash", txHash), zap.String("value", txHash))
continue continue
@ -235,7 +235,7 @@ func EventsToMessagePublications(contract string, txHash string, events []gjson.
Timestamp: time.Unix(blockTimeInt, 0), Timestamp: time.Unix(blockTimeInt, 0),
Nonce: uint32(nonceInt), Nonce: uint32(nonceInt),
Sequence: sequenceInt, Sequence: sequenceInt,
EmitterChain: vaa.ChainIDTerra, EmitterChain: chainID,
EmitterAddress: senderAddress, EmitterAddress: senderAddress,
Payload: payloadValue, Payload: payloadValue,
ConsistencyLevel: 0, // Instant finality ConsistencyLevel: 0, // Instant finality
@ -367,7 +367,7 @@ func main() {
} }
txhash := gjson.Get(tx.String(), "txhash") txhash := gjson.Get(tx.String(), "txhash")
// Get sequence number for tx // Get sequence number for tx
seqs, err := getSequencesForTxhash(txhash.String(), fcd, contractAddressLogKey, coreContract, emitter) seqs, err := getSequencesForTxhash(txhash.String(), fcd, contractAddressLogKey, coreContract, emitter, chainID)
if err != nil { if err != nil {
log.Fatalln("Failed getting sequence number", err) log.Fatalln("Failed getting sequence number", err)
continue continue
@ -382,7 +382,7 @@ func main() {
if *dryRun { if *dryRun {
log.Println("Would have sent txhash", txhash, "to the guardian to re-observe") log.Println("Would have sent txhash", txhash, "to the guardian to re-observe")
} else { } else {
txHashAsByteArray, err := terra.StringToHash(txhash.String()) txHashAsByteArray, err := cosmwasm.StringToHash(txhash.String())
if err != nil { if err != nil {
log.Fatalln("Couldn't decode the txhash", txhash) log.Fatalln("Couldn't decode the txhash", txhash)
} else { } else {

View File

@ -21,4 +21,5 @@ const (
ReadinessMoonbeamSyncing readiness.Component = "moonbeamSyncing" ReadinessMoonbeamSyncing readiness.Component = "moonbeamSyncing"
ReadinessNeonSyncing readiness.Component = "neonSyncing" ReadinessNeonSyncing readiness.Component = "neonSyncing"
ReadinessTerra2Syncing readiness.Component = "terra2Syncing" ReadinessTerra2Syncing readiness.Component = "terra2Syncing"
ReadinessInjectiveSyncing readiness.Component = "injectiveSyncing"
) )

View File

@ -1,4 +1,4 @@
package terra package cosmwasm
import ( import (
"context" "context"
@ -22,7 +22,7 @@ type submitVAAParams struct {
VAA []byte `json:"vaa"` VAA []byte `json:"vaa"`
} }
// SubmitVAA prepares transaction with signed VAA and sends it to the Terra blockchain // SubmitVAA prepares transaction with signed VAA and sends it to a cosmwasm blockchain
func SubmitVAA(ctx context.Context, urlLCD string, chainID string, contractAddress string, feePayer string, signed *vaa.VAA) (*sdk.TxResponse, error) { func SubmitVAA(ctx context.Context, urlLCD string, chainID string, contractAddress string, feePayer string, signed *vaa.VAA) (*sdk.TxResponse, error) {
// Serialize VAA // Serialize VAA

View File

@ -1,4 +1,4 @@
package terra package cosmwasm
import ( import (
"context" "context"
@ -28,7 +28,7 @@ import (
) )
type ( type (
// Watcher is responsible for looking over Terra blockchain and reporting new transactions to the contract // Watcher is responsible for looking over a cosmwasm blockchain and reporting new transactions to the contract
Watcher struct { Watcher struct {
urlWS string urlWS string
urlLCD string urlLCD string
@ -53,25 +53,25 @@ type (
) )
var ( var (
terraConnectionErrors = promauto.NewCounterVec( connectionErrors = promauto.NewCounterVec(
prometheus.CounterOpts{ prometheus.CounterOpts{
Name: "wormhole_terra_connection_errors_total", Name: "wormhole_terra_connection_errors_total",
Help: "Total number of Terra connection errors", Help: "Total number of connection errors on a cosmwasm chain",
}, []string{"terra_network", "reason"}) }, []string{"terra_network", "reason"})
terraMessagesConfirmed = promauto.NewCounterVec( messagesConfirmed = promauto.NewCounterVec(
prometheus.CounterOpts{ prometheus.CounterOpts{
Name: "wormhole_terra_messages_confirmed_total", Name: "wormhole_terra_messages_confirmed_total",
Help: "Total number of verified terra messages found", Help: "Total number of verified messages found on a cosmwasm chain",
}, []string{"terra_network"}) }, []string{"terra_network"})
currentTerraHeight = promauto.NewGaugeVec( currentSlotHeight = promauto.NewGaugeVec(
prometheus.GaugeOpts{ prometheus.GaugeOpts{
Name: "wormhole_terra_current_height", Name: "wormhole_terra_current_height",
Help: "Current terra slot height (at default commitment level, not the level used for observations)", Help: "Current slot height on a cosmwasm chain (at default commitment level, not the level used for observations)",
}, []string{"terra_network"}) }, []string{"terra_network"})
queryLatency = promauto.NewHistogramVec( queryLatency = promauto.NewHistogramVec(
prometheus.HistogramOpts{ prometheus.HistogramOpts{
Name: "wormhole_terra_query_latency", Name: "wormhole_terra_query_latency",
Help: "Latency histogram for terra RPC calls", Help: "Latency histogram for RPC calls on a cosmwasm chain",
}, []string{"terra_network", "operation"}) }, []string{"terra_network", "operation"})
) )
@ -86,7 +86,7 @@ type clientRequest struct {
ID uint64 `json:"id"` ID uint64 `json:"id"`
} }
// NewWatcher creates a new Terra contract watcher // NewWatcher creates a new cosmwasm contract watcher
func NewWatcher( func NewWatcher(
urlWS string, urlWS string,
urlLCD string, urlLCD string,
@ -119,12 +119,12 @@ func (e *Watcher) Run(ctx context.Context) error {
errC := make(chan error) errC := make(chan error)
logger := supervisor.Logger(ctx) logger := supervisor.Logger(ctx)
logger.Info("connecting to websocket", zap.String("url", e.urlWS)) logger.Info("connecting to websocket", zap.String("network", networkName), zap.String("url", e.urlWS))
c, _, err := websocket.DefaultDialer.DialContext(ctx, e.urlWS, nil) c, _, err := websocket.DefaultDialer.DialContext(ctx, e.urlWS, nil)
if err != nil { if err != nil {
p2p.DefaultRegistry.AddErrorCount(e.chainID, 1) p2p.DefaultRegistry.AddErrorCount(e.chainID, 1)
terraConnectionErrors.WithLabelValues(networkName, "websocket_dial_error").Inc() connectionErrors.WithLabelValues(networkName, "websocket_dial_error").Inc()
return fmt.Errorf("websocket dial failed: %w", err) return fmt.Errorf("websocket dial failed: %w", err)
} }
defer c.Close() defer c.Close()
@ -140,7 +140,7 @@ func (e *Watcher) Run(ctx context.Context) error {
err = c.WriteJSON(command) err = c.WriteJSON(command)
if err != nil { if err != nil {
p2p.DefaultRegistry.AddErrorCount(e.chainID, 1) p2p.DefaultRegistry.AddErrorCount(e.chainID, 1)
terraConnectionErrors.WithLabelValues(networkName, "websocket_subscription_error").Inc() connectionErrors.WithLabelValues(networkName, "websocket_subscription_error").Inc()
return fmt.Errorf("websocket subscription failed: %w", err) return fmt.Errorf("websocket subscription failed: %w", err)
} }
@ -148,10 +148,10 @@ func (e *Watcher) Run(ctx context.Context) error {
_, _, err = c.ReadMessage() _, _, err = c.ReadMessage()
if err != nil { if err != nil {
p2p.DefaultRegistry.AddErrorCount(e.chainID, 1) p2p.DefaultRegistry.AddErrorCount(e.chainID, 1)
terraConnectionErrors.WithLabelValues(networkName, "event_subscription_error").Inc() connectionErrors.WithLabelValues(networkName, "event_subscription_error").Inc()
return fmt.Errorf("event subscription failed: %w", err) return fmt.Errorf("event subscription failed: %w", err)
} }
logger.Info("subscribed to new transaction events") logger.Info("subscribed to new transaction events", zap.String("network", networkName))
readiness.SetReady(e.readiness) readiness.SetReady(e.readiness)
@ -164,15 +164,15 @@ func (e *Watcher) Run(ctx context.Context) error {
for { for {
<-t.C <-t.C
// Query and report height and set currentTerraHeight // Query and report height and set currentSlotHeight
resp, err := client.Get(fmt.Sprintf("%s/blocks/latest", e.urlLCD)) resp, err := client.Get(fmt.Sprintf("%s/blocks/latest", e.urlLCD))
if err != nil { if err != nil {
logger.Error("query latest block response error", zap.Error(err)) logger.Error("query latest block response error", zap.String("network", networkName), zap.Error(err))
continue continue
} }
blocksBody, err := ioutil.ReadAll(resp.Body) blocksBody, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
logger.Error("query latest block response read error", zap.Error(err)) logger.Error("query latest block response read error", zap.String("network", networkName), zap.Error(err))
errC <- err errC <- err
resp.Body.Close() resp.Body.Close()
continue continue
@ -181,8 +181,8 @@ func (e *Watcher) Run(ctx context.Context) error {
blockJSON := string(blocksBody) blockJSON := string(blocksBody)
latestBlock := gjson.Get(blockJSON, "block.header.height") latestBlock := gjson.Get(blockJSON, "block.header.height")
logger.Info("current Terra height", zap.Int64("block", latestBlock.Int())) logger.Info("current height", zap.String("network", networkName), zap.Int64("block", latestBlock.Int()))
currentTerraHeight.WithLabelValues(networkName).Set(float64(latestBlock.Int())) currentSlotHeight.WithLabelValues(networkName).Set(float64(latestBlock.Int()))
p2p.DefaultRegistry.SetNetworkStats(e.chainID, &gossipv1.Heartbeat_Network{ p2p.DefaultRegistry.SetNetworkStats(e.chainID, &gossipv1.Heartbeat_Network{
Height: latestBlock.Int(), Height: latestBlock.Int(),
ContractAddress: e.contract, ContractAddress: e.contract,
@ -202,8 +202,7 @@ func (e *Watcher) Run(ctx context.Context) error {
tx := hex.EncodeToString(r.TxHash) tx := hex.EncodeToString(r.TxHash)
logger.Info("received observation request for terra", logger.Info("received observation request", zap.String("network", networkName), zap.String("tx_hash", tx))
zap.String("tx_hash", tx))
client := &http.Client{ client := &http.Client{
Timeout: time.Second * 5, Timeout: time.Second * 5,
@ -212,12 +211,12 @@ func (e *Watcher) Run(ctx context.Context) error {
// Query for tx by hash // Query for tx by hash
resp, err := client.Get(fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/%s", e.urlLCD, tx)) resp, err := client.Get(fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/%s", e.urlLCD, tx))
if err != nil { if err != nil {
logger.Error("query tx response error", zap.Error(err)) logger.Error("query tx response error", zap.String("network", networkName), zap.Error(err))
continue continue
} }
txBody, err := ioutil.ReadAll(resp.Body) txBody, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
logger.Error("query tx response read error", zap.Error(err)) logger.Error("query tx response read error", zap.String("network", networkName), zap.Error(err))
resp.Body.Close() resp.Body.Close()
continue continue
} }
@ -227,21 +226,21 @@ func (e *Watcher) Run(ctx context.Context) error {
txHashRaw := gjson.Get(txJSON, "tx_response.txhash") txHashRaw := gjson.Get(txJSON, "tx_response.txhash")
if !txHashRaw.Exists() { if !txHashRaw.Exists() {
logger.Error("terra tx does not have tx hash", zap.String("payload", txJSON)) logger.Error("tx does not have tx hash", zap.String("network", networkName), zap.String("payload", txJSON))
continue continue
} }
txHash := txHashRaw.String() txHash := txHashRaw.String()
events := gjson.Get(txJSON, "tx_response.events") events := gjson.Get(txJSON, "tx_response.events")
if !events.Exists() { if !events.Exists() {
logger.Error("terra tx has no events", zap.String("payload", txJSON)) logger.Error("tx has no events", zap.String("network", networkName), zap.String("payload", txJSON))
continue continue
} }
msgs := EventsToMessagePublications(e.contract, txHash, events.Array(), logger, e.chainID, e.contractAddressLogKey) msgs := EventsToMessagePublications(e.contract, txHash, events.Array(), logger, e.chainID, e.contractAddressLogKey)
for _, msg := range msgs { for _, msg := range msgs {
e.msgChan <- msg e.msgChan <- msg
terraMessagesConfirmed.WithLabelValues(networkName).Inc() messagesConfirmed.WithLabelValues(networkName).Inc()
} }
} }
} }
@ -254,8 +253,8 @@ func (e *Watcher) Run(ctx context.Context) error {
_, message, err := c.ReadMessage() _, message, err := c.ReadMessage()
if err != nil { if err != nil {
p2p.DefaultRegistry.AddErrorCount(e.chainID, 1) p2p.DefaultRegistry.AddErrorCount(e.chainID, 1)
terraConnectionErrors.WithLabelValues(networkName, "channel_read_error").Inc() connectionErrors.WithLabelValues(networkName, "channel_read_error").Inc()
logger.Error("error reading channel", zap.Error(err)) logger.Error("error reading channel", zap.String("network", networkName), zap.Error(err))
errC <- err errC <- err
return return
} }
@ -265,21 +264,21 @@ func (e *Watcher) Run(ctx context.Context) error {
txHashRaw := gjson.Get(json, "result.events.tx\\.hash.0") txHashRaw := gjson.Get(json, "result.events.tx\\.hash.0")
if !txHashRaw.Exists() { if !txHashRaw.Exists() {
logger.Warn("terra message does not have tx hash", zap.String("payload", json)) logger.Warn("message does not have tx hash", zap.String("network", networkName), zap.String("payload", json))
continue continue
} }
txHash := txHashRaw.String() txHash := txHashRaw.String()
events := gjson.Get(json, "result.data.value.TxResult.result.events") events := gjson.Get(json, "result.data.value.TxResult.result.events")
if !events.Exists() { if !events.Exists() {
logger.Warn("terra message has no events", zap.String("payload", json)) logger.Warn("message has no events", zap.String("network", networkName), zap.String("payload", json))
continue continue
} }
msgs := EventsToMessagePublications(e.contract, txHash, events.Array(), logger, e.chainID, e.contractAddressLogKey) msgs := EventsToMessagePublications(e.contract, txHash, events.Array(), logger, e.chainID, e.contractAddressLogKey)
for _, msg := range msgs { for _, msg := range msgs {
e.msgChan <- msg e.msgChan <- msg
terraMessagesConfirmed.WithLabelValues(networkName).Inc() messagesConfirmed.WithLabelValues(networkName).Inc()
} }
client := &http.Client{ client := &http.Client{
@ -291,8 +290,8 @@ func (e *Watcher) Run(ctx context.Context) error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL, nil) req, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL, nil)
if err != nil { if err != nil {
p2p.DefaultRegistry.AddErrorCount(e.chainID, 1) p2p.DefaultRegistry.AddErrorCount(e.chainID, 1)
terraConnectionErrors.WithLabelValues(networkName, "guardian_set_req_error").Inc() connectionErrors.WithLabelValues(networkName, "guardian_set_req_error").Inc()
logger.Error("query guardian set request error", zap.Error(err)) logger.Error("query guardian set request error", zap.String("network", networkName), zap.Error(err))
errC <- err errC <- err
return return
} }
@ -301,7 +300,7 @@ func (e *Watcher) Run(ctx context.Context) error {
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
p2p.DefaultRegistry.AddErrorCount(e.chainID, 1) p2p.DefaultRegistry.AddErrorCount(e.chainID, 1)
logger.Error("query guardian set response error", zap.Error(err)) logger.Error("query guardian set response error", zap.String("network", networkName), zap.Error(err))
errC <- err errC <- err
return return
} }
@ -310,7 +309,7 @@ func (e *Watcher) Run(ctx context.Context) error {
queryLatency.WithLabelValues(networkName, "guardian_set_info").Observe(time.Since(msm).Seconds()) queryLatency.WithLabelValues(networkName, "guardian_set_info").Observe(time.Since(msm).Seconds())
if err != nil { if err != nil {
p2p.DefaultRegistry.AddErrorCount(e.chainID, 1) p2p.DefaultRegistry.AddErrorCount(e.chainID, 1)
logger.Error("query guardian set error", zap.Error(err)) logger.Error("query guardian set error", zap.String("network", networkName), zap.Error(err))
errC <- err errC <- err
resp.Body.Close() resp.Body.Close()
return return
@ -320,7 +319,8 @@ func (e *Watcher) Run(ctx context.Context) error {
guardianSetIndex := gjson.Get(json, "result.guardian_set_index") guardianSetIndex := gjson.Get(json, "result.guardian_set_index")
addresses := gjson.Get(json, "result.addresses.#.bytes") addresses := gjson.Get(json, "result.addresses.#.bytes")
logger.Debug("current guardian set on Terra", logger.Debug("current guardian set",
zap.String("network", networkName),
zap.Any("guardianSetIndex", guardianSetIndex), zap.Any("guardianSetIndex", guardianSetIndex),
zap.Any("addresses", addresses)) zap.Any("addresses", addresses))
@ -334,7 +334,7 @@ func (e *Watcher) Run(ctx context.Context) error {
case <-ctx.Done(): case <-ctx.Done():
err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
if err != nil { if err != nil {
logger.Error("error on closing socket ", zap.Error(err)) logger.Error("error on closing socket ", zap.String("network", networkName), zap.Error(err))
} }
return ctx.Err() return ctx.Err()
case err := <-errC: case err := <-errC:
@ -343,10 +343,11 @@ func (e *Watcher) Run(ctx context.Context) error {
} }
func EventsToMessagePublications(contract string, txHash string, events []gjson.Result, logger *zap.Logger, chainID vaa.ChainID, contractAddressKey string) []*common.MessagePublication { func EventsToMessagePublications(contract string, txHash string, events []gjson.Result, logger *zap.Logger, chainID vaa.ChainID, contractAddressKey string) []*common.MessagePublication {
networkName := vaa.ChainID(chainID).String()
msgs := make([]*common.MessagePublication, 0, len(events)) msgs := make([]*common.MessagePublication, 0, len(events))
for _, event := range events { for _, event := range events {
if !event.IsObject() { if !event.IsObject() {
logger.Warn("terra event is invalid", zap.String("tx_hash", txHash), zap.String("event", event.String())) logger.Warn("event is invalid", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("event", event.String()))
continue continue
} }
eventType := gjson.Get(event.String(), "type") eventType := gjson.Get(event.String(), "type")
@ -356,39 +357,39 @@ func EventsToMessagePublications(contract string, txHash string, events []gjson.
attributes := gjson.Get(event.String(), "attributes") attributes := gjson.Get(event.String(), "attributes")
if !attributes.Exists() { if !attributes.Exists() {
logger.Warn("terra message event has no attributes", zap.String("tx_hash", txHash), zap.String("event", event.String())) logger.Warn("message event has no attributes", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("event", event.String()))
continue continue
} }
mappedAttributes := map[string]string{} mappedAttributes := map[string]string{}
for _, attribute := range attributes.Array() { for _, attribute := range attributes.Array() {
if !attribute.IsObject() { if !attribute.IsObject() {
logger.Warn("terra event attribute is invalid", zap.String("tx_hash", txHash), zap.String("attribute", attribute.String())) logger.Warn("event attribute is invalid", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("attribute", attribute.String()))
continue continue
} }
keyBase := gjson.Get(attribute.String(), "key") keyBase := gjson.Get(attribute.String(), "key")
if !keyBase.Exists() { if !keyBase.Exists() {
logger.Warn("terra event attribute does not have key", zap.String("tx_hash", txHash), zap.String("attribute", attribute.String())) logger.Warn("event attribute does not have key", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("attribute", attribute.String()))
continue continue
} }
valueBase := gjson.Get(attribute.String(), "value") valueBase := gjson.Get(attribute.String(), "value")
if !valueBase.Exists() { if !valueBase.Exists() {
logger.Warn("terra event attribute does not have value", zap.String("tx_hash", txHash), zap.String("attribute", attribute.String())) logger.Warn("event attribute does not have value", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("attribute", attribute.String()))
continue continue
} }
key, err := base64.StdEncoding.DecodeString(keyBase.String()) key, err := base64.StdEncoding.DecodeString(keyBase.String())
if err != nil { if err != nil {
logger.Warn("terra event key attribute is invalid", zap.String("tx_hash", txHash), zap.String("key", keyBase.String())) logger.Warn("event key attribute is invalid", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("key", keyBase.String()))
continue continue
} }
value, err := base64.StdEncoding.DecodeString(valueBase.String()) value, err := base64.StdEncoding.DecodeString(valueBase.String())
if err != nil { if err != nil {
logger.Warn("terra event value attribute is invalid", zap.String("tx_hash", txHash), zap.String("key", keyBase.String()), zap.String("value", valueBase.String())) 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()))
continue continue
} }
if _, ok := mappedAttributes[string(key)]; ok { if _, ok := mappedAttributes[string(key)]; ok {
logger.Debug("duplicate key in events", zap.String("tx_hash", txHash), zap.String("key", keyBase.String()), zap.String("value", valueBase.String())) 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()))
continue continue
} }
@ -397,7 +398,7 @@ func EventsToMessagePublications(contract string, txHash string, events []gjson.
contractAddress, ok := mappedAttributes[contractAddressKey] contractAddress, ok := mappedAttributes[contractAddressKey]
if !ok { if !ok {
logger.Warn("terra wasm event without contract address field set", zap.String("event", event.String())) logger.Warn("wasm event without contract address field set", zap.String("network", networkName), zap.String("event", event.String()))
continue continue
} }
// This is not a wormhole message // This is not a wormhole message
@ -407,36 +408,37 @@ func EventsToMessagePublications(contract string, txHash string, events []gjson.
payload, ok := mappedAttributes["message.message"] payload, ok := mappedAttributes["message.message"]
if !ok { if !ok {
logger.Error("wormhole event does not have a message field", zap.String("tx_hash", txHash), zap.String("attributes", attributes.String())) logger.Error("wormhole event does not have a message field", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("attributes", attributes.String()))
continue continue
} }
sender, ok := mappedAttributes["message.sender"] sender, ok := mappedAttributes["message.sender"]
if !ok { if !ok {
logger.Error("wormhole event does not have a sender field", zap.String("tx_hash", txHash), zap.String("attributes", attributes.String())) logger.Error("wormhole event does not have a sender field", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("attributes", attributes.String()))
continue continue
} }
chainId, ok := mappedAttributes["message.chain_id"] chainId, ok := mappedAttributes["message.chain_id"]
if !ok { if !ok {
logger.Error("wormhole event does not have a chain_id field", zap.String("tx_hash", txHash), zap.String("attributes", attributes.String())) 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()))
continue continue
} }
nonce, ok := mappedAttributes["message.nonce"] nonce, ok := mappedAttributes["message.nonce"]
if !ok { if !ok {
logger.Error("wormhole event does not have a nonce field", zap.String("tx_hash", txHash), zap.String("attributes", attributes.String())) logger.Error("wormhole event does not have a nonce field", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("attributes", attributes.String()))
continue continue
} }
sequence, ok := mappedAttributes["message.sequence"] sequence, ok := mappedAttributes["message.sequence"]
if !ok { if !ok {
logger.Error("wormhole event does not have a sequence field", zap.String("tx_hash", txHash), zap.String("attributes", attributes.String())) logger.Error("wormhole event does not have a sequence field", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("attributes", attributes.String()))
continue continue
} }
blockTime, ok := mappedAttributes["message.block_time"] blockTime, ok := mappedAttributes["message.block_time"]
if !ok { if !ok {
logger.Error("wormhole event does not have a block_time field", zap.String("tx_hash", txHash), zap.String("attributes", attributes.String())) 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()))
continue continue
} }
logger.Info("new message detected on terra", logger.Info("new message detected on terra",
zap.String("network", networkName),
zap.String("chainId", chainId), zap.String("chainId", chainId),
zap.String("txHash", txHash), zap.String("txHash", txHash),
zap.String("sender", sender), zap.String("sender", sender),
@ -447,33 +449,33 @@ func EventsToMessagePublications(contract string, txHash string, events []gjson.
senderAddress, err := StringToAddress(sender) senderAddress, err := StringToAddress(sender)
if err != nil { if err != nil {
logger.Error("cannot decode emitter hex", zap.String("tx_hash", txHash), zap.String("value", sender)) logger.Error("cannot decode emitter hex", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("value", sender))
continue continue
} }
txHashValue, err := StringToHash(txHash) txHashValue, err := StringToHash(txHash)
if err != nil { if err != nil {
logger.Error("cannot decode tx hash hex", zap.String("tx_hash", txHash), zap.String("value", txHash)) logger.Error("cannot decode tx hash hex", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("value", txHash))
continue continue
} }
payloadValue, err := hex.DecodeString(payload) payloadValue, err := hex.DecodeString(payload)
if err != nil { if err != nil {
logger.Error("cannot decode payload", zap.String("tx_hash", txHash), zap.String("value", payload)) logger.Error("cannot decode payload", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("value", payload))
continue continue
} }
blockTimeInt, err := strconv.ParseInt(blockTime, 10, 64) blockTimeInt, err := strconv.ParseInt(blockTime, 10, 64)
if err != nil { if err != nil {
logger.Error("blocktime cannot be parsed as int", zap.String("tx_hash", txHash), zap.String("value", blockTime)) logger.Error("blocktime cannot be parsed as int", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("value", blockTime))
continue continue
} }
nonceInt, err := strconv.ParseUint(nonce, 10, 32) nonceInt, err := strconv.ParseUint(nonce, 10, 32)
if err != nil { if err != nil {
logger.Error("nonce cannot be parsed as int", zap.String("tx_hash", txHash), zap.String("value", blockTime)) logger.Error("nonce cannot be parsed as int", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("value", blockTime))
continue continue
} }
sequenceInt, err := strconv.ParseUint(sequence, 10, 64) sequenceInt, err := strconv.ParseUint(sequence, 10, 64)
if err != nil { if err != nil {
logger.Error("sequence cannot be parsed as int", zap.String("tx_hash", txHash), zap.String("value", blockTime)) logger.Error("sequence cannot be parsed as int", zap.String("network", networkName), zap.String("tx_hash", txHash), zap.String("value", blockTime))
continue continue
} }
messagePublication := &common.MessagePublication{ messagePublication := &common.MessagePublication{

View File

@ -130,6 +130,8 @@ func (c ChainID) String() string {
return "neon" return "neon"
case ChainIDTerra2: case ChainIDTerra2:
return "terra2" return "terra2"
case ChainIDInjective:
return "injective"
default: default:
return fmt.Sprintf("unknown chain ID: %d", c) return fmt.Sprintf("unknown chain ID: %d", c)
} }
@ -175,6 +177,8 @@ func ChainIDFromString(s string) (ChainID, error) {
return ChainIDNeon, nil return ChainIDNeon, nil
case "terra2": case "terra2":
return ChainIDTerra2, nil return ChainIDTerra2, nil
case "injective":
return ChainIDInjective, nil
default: default:
return ChainIDUnset, fmt.Errorf("unknown chain ID: %s", s) return ChainIDUnset, fmt.Errorf("unknown chain ID: %s", s)
} }
@ -216,6 +220,8 @@ const (
ChainIDNeon ChainID = 17 ChainIDNeon ChainID = 17
// ChainIDTerra2 is the ChainID of Terra 2 // ChainIDTerra2 is the ChainID of Terra 2
ChainIDTerra2 ChainID = 18 ChainIDTerra2 ChainID = 18
// ChainIDInjective is the ChainID of Injective
ChainIDInjective ChainID = 19
// ChainIDEthereumRopsten is the ChainID of Ethereum Ropsten // ChainIDEthereumRopsten is the ChainID of Ethereum Ropsten
ChainIDEthereumRopsten ChainID = 10001 ChainIDEthereumRopsten ChainID = 10001

View File

@ -41,6 +41,7 @@ func TestChainIDFromString(t *testing.T) {
{input: "moonbeam", output: ChainIDMoonbeam}, {input: "moonbeam", output: ChainIDMoonbeam},
{input: "neon", output: ChainIDNeon}, {input: "neon", output: ChainIDNeon},
{input: "terra2", output: ChainIDTerra2}, {input: "terra2", output: ChainIDTerra2},
{input: "injective", output: ChainIDInjective},
{input: "ethereum-ropsten", output: ChainIDEthereumRopsten}, {input: "ethereum-ropsten", output: ChainIDEthereumRopsten},
{input: "Solana", output: ChainIDSolana}, {input: "Solana", output: ChainIDSolana},
@ -60,6 +61,7 @@ func TestChainIDFromString(t *testing.T) {
{input: "Moonbeam", output: ChainIDMoonbeam}, {input: "Moonbeam", output: ChainIDMoonbeam},
{input: "Neon", output: ChainIDNeon}, {input: "Neon", output: ChainIDNeon},
{input: "Terra2", output: ChainIDTerra2}, {input: "Terra2", output: ChainIDTerra2},
{input: "Injective", output: ChainIDInjective},
{input: "Ethereum-ropsten", output: ChainIDEthereumRopsten}, {input: "Ethereum-ropsten", output: ChainIDEthereumRopsten},
} }
@ -149,6 +151,7 @@ func TestChainId_String(t *testing.T) {
{input: 16, output: "moonbeam"}, {input: 16, output: "moonbeam"},
{input: 17, output: "neon"}, {input: 17, output: "neon"},
{input: 18, output: "terra2"}, {input: 18, output: "terra2"},
{input: 19, output: "injective"},
{input: 10001, output: "ethereum-ropsten"}, {input: 10001, output: "ethereum-ropsten"},
} }

View File

@ -27,6 +27,7 @@ enum ChainID {
CHAIN_ID_MOONBEAM = 16; CHAIN_ID_MOONBEAM = 16;
CHAIN_ID_NEON = 17; CHAIN_ID_NEON = 17;
CHAIN_ID_TERRA2 = 18; CHAIN_ID_TERRA2 = 18;
CHAIN_ID_INJECTIVE = 19;
// Special case - Eth has two testnets. CHAIN_ID_ETHEREUM is Goerli, // Special case - Eth has two testnets. CHAIN_ID_ETHEREUM is Goerli,
// but we also want to connect to Ropsten, so we add a separate chain. // but we also want to connect to Ropsten, so we add a separate chain.
CHAIN_ID_ETHEREUM_ROPSTEN = 10001; CHAIN_ID_ETHEREUM_ROPSTEN = 10001;