package connectors import ( "context" "time" celoAbi "github.com/certusone/wormhole/node/pkg/watchers/evm/connectors/celoabi" ethAbi "github.com/certusone/wormhole/node/pkg/watchers/evm/connectors/ethabi" celoBind "github.com/celo-org/celo-blockchain/accounts/abi/bind" celoCommon "github.com/celo-org/celo-blockchain/common" celoTypes "github.com/celo-org/celo-blockchain/core/types" celoClient "github.com/celo-org/celo-blockchain/ethclient" celoRpc "github.com/celo-org/celo-blockchain/rpc" ethereum "github.com/ethereum/go-ethereum" ethCommon "github.com/ethereum/go-ethereum/common" ethTypes "github.com/ethereum/go-ethereum/core/types" ethEvent "github.com/ethereum/go-ethereum/event" "github.com/certusone/wormhole/node/pkg/common" "go.uber.org/zap" ) // CeloConnector implements EVM network query capabilities for the Celo network. It's almost identical to // EthereumConnector except it's using the Celo fork and provides shims between their respective types. type CeloConnector struct { networkName string address ethCommon.Address logger *zap.Logger client *celoClient.Client rawClient *celoRpc.Client filterer *celoAbi.AbiFilterer caller *celoAbi.AbiCaller } func NewCeloConnector(ctx context.Context, networkName, rawUrl string, address ethCommon.Address, logger *zap.Logger) (*CeloConnector, error) { rawClient, err := celoRpc.DialContext(ctx, rawUrl) if err != nil { return nil, err } client := celoClient.NewClient(rawClient) filterer, err := celoAbi.NewAbiFilterer(celoCommon.BytesToAddress(address.Bytes()), client) if err != nil { panic(err) } caller, err := celoAbi.NewAbiCaller(celoCommon.BytesToAddress(address.Bytes()), client) if err != nil { panic(err) } return &CeloConnector{ networkName: networkName, address: address, logger: logger.With(zap.String("eth_network", networkName)), client: client, rawClient: rawClient, filterer: filterer, caller: caller, }, nil } func (c *CeloConnector) NetworkName() string { return c.networkName } func (c *CeloConnector) ContractAddress() ethCommon.Address { return c.address } func (c *CeloConnector) GetCurrentGuardianSetIndex(ctx context.Context) (uint32, error) { opts := &celoBind.CallOpts{Context: ctx} return c.caller.GetCurrentGuardianSetIndex(opts) } func (c *CeloConnector) GetGuardianSet(ctx context.Context, index uint32) (ethAbi.StructsGuardianSet, error) { opts := &celoBind.CallOpts{Context: ctx} celoGs, err := c.caller.GetGuardianSet(opts, index) if err != nil { return ethAbi.StructsGuardianSet{}, err } ethKeys := make([]ethCommon.Address, len(celoGs.Keys)) for n, k := range celoGs.Keys { ethKeys[n] = ethCommon.BytesToAddress(k.Bytes()) } return ethAbi.StructsGuardianSet{ Keys: ethKeys, ExpirationTime: celoGs.ExpirationTime, }, err } func (c *CeloConnector) WatchLogMessagePublished(ctx context.Context, errC chan error, sink chan<- *ethAbi.AbiLogMessagePublished) (ethEvent.Subscription, error) { timeout, cancel := context.WithTimeout(ctx, 15*time.Second) defer cancel() messageC := make(chan *celoAbi.AbiLogMessagePublished, 2) messageSub, err := c.filterer.WatchLogMessagePublished(&celoBind.WatchOpts{Context: timeout}, messageC, nil) if err != nil { return messageSub, err } // The purpose of this is to map events from the Celo log message channel to the Eth log message channel. common.RunWithScissors(ctx, errC, "celo_connector_watch_log", func(ctx context.Context) error { for { select { // This will return when the subscription is unsubscribed as the error channel gets closed case <-messageSub.Err(): return nil case celoEvent := <-messageC: sink <- convertCeloEventToEth(celoEvent) } } }) return messageSub, err } func (c *CeloConnector) TransactionReceipt(ctx context.Context, txHash ethCommon.Hash) (*ethTypes.Receipt, error) { celoReceipt, err := c.client.TransactionReceipt(ctx, celoCommon.BytesToHash(txHash.Bytes())) if err != nil { return nil, err } return convertCeloReceiptToEth(celoReceipt), err } func (c *CeloConnector) TimeOfBlockByHash(ctx context.Context, hash ethCommon.Hash) (uint64, error) { block, err := c.client.BlockByHash(ctx, celoCommon.BytesToHash(hash.Bytes())) if err != nil { return 0, err } return block.Time(), err } func (c *CeloConnector) ParseLogMessagePublished(ethLog ethTypes.Log) (*ethAbi.AbiLogMessagePublished, error) { celoEvent, err := c.filterer.ParseLogMessagePublished(*convertCeloLogFromEth(ðLog)) if err != nil { return nil, err } return convertCeloEventToEth(celoEvent), err } func (c *CeloConnector) SubscribeForBlocks(ctx context.Context, errC chan error, sink chan<- *NewBlock) (ethereum.Subscription, error) { headSink := make(chan *celoTypes.Header, 2) headerSubscription, err := c.client.SubscribeNewHead(ctx, headSink) if err != nil { return headerSubscription, err } // The purpose of this is to map events from the Celo event channel to the new block event channel. common.RunWithScissors(ctx, errC, "celo_connector_subscribe_for_block", func(ctx context.Context) error { for { select { case <-ctx.Done(): return nil case ev := <-headSink: if ev == nil { c.logger.Error("new header event is nil") continue } if ev.Number == nil { c.logger.Error("new header block number is nil") continue } sink <- &NewBlock{ Number: ev.Number, Hash: ethCommon.BytesToHash(ev.Hash().Bytes()), } } } }) return headerSubscription, err } func (c *CeloConnector) RawCallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { return c.rawClient.CallContext(ctx, result, method, args...) } func convertCeloEventToEth(ev *celoAbi.AbiLogMessagePublished) *ethAbi.AbiLogMessagePublished { return ðAbi.AbiLogMessagePublished{ Sender: ethCommon.BytesToAddress(ev.Sender.Bytes()), Sequence: ev.Sequence, Nonce: ev.Nonce, Payload: ev.Payload, ConsistencyLevel: ev.ConsistencyLevel, Raw: *convertCeloLogToEth(&ev.Raw), } } func convertCeloLogToEth(l *celoTypes.Log) *ethTypes.Log { topics := make([]ethCommon.Hash, len(l.Topics)) for n, t := range l.Topics { topics[n] = ethCommon.BytesToHash(t.Bytes()) } return ðTypes.Log{ Address: ethCommon.BytesToAddress(l.Address.Bytes()), Topics: topics, Data: l.Data, BlockNumber: l.BlockNumber, TxHash: ethCommon.BytesToHash(l.TxHash.Bytes()), TxIndex: l.TxIndex, BlockHash: ethCommon.BytesToHash(l.BlockHash.Bytes()), Index: l.Index, Removed: l.Removed, } } func convertCeloReceiptToEth(celoReceipt *celoTypes.Receipt) *ethTypes.Receipt { ethLogs := make([]*ethTypes.Log, len(celoReceipt.Logs)) for n, l := range celoReceipt.Logs { ethLogs[n] = convertCeloLogToEth(l) } return ðTypes.Receipt{ Type: celoReceipt.Type, PostState: celoReceipt.PostState, Status: celoReceipt.Status, CumulativeGasUsed: celoReceipt.CumulativeGasUsed, Bloom: ethTypes.BytesToBloom(celoReceipt.Bloom.Bytes()), Logs: ethLogs, TxHash: ethCommon.BytesToHash(celoReceipt.TxHash.Bytes()), ContractAddress: ethCommon.BytesToAddress(celoReceipt.ContractAddress.Bytes()), GasUsed: celoReceipt.GasUsed, BlockHash: ethCommon.BytesToHash(celoReceipt.BlockHash.Bytes()), BlockNumber: celoReceipt.BlockNumber, TransactionIndex: celoReceipt.TransactionIndex, } } func convertCeloLogFromEth(l *ethTypes.Log) *celoTypes.Log { topics := make([]celoCommon.Hash, len(l.Topics)) for n, t := range l.Topics { topics[n] = celoCommon.BytesToHash(t.Bytes()) } return &celoTypes.Log{ Address: celoCommon.BytesToAddress(l.Address.Bytes()), Topics: topics, Data: l.Data, BlockNumber: l.BlockNumber, TxHash: celoCommon.BytesToHash(l.TxHash.Bytes()), TxIndex: l.TxIndex, BlockHash: celoCommon.BytesToHash(l.BlockHash.Bytes()), Index: l.Index, Removed: l.Removed, } }