wormhole/node/pkg/watchers/evm/connectors/celo.go

254 lines
8.0 KiB
Go

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(&ethLog))
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 &ethAbi.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 &ethTypes.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 &ethTypes.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,
}
}