Contract ABI handling refactor
This commit is contained in:
parent
5871194bd1
commit
989e270d86
|
@ -4,7 +4,7 @@ WORKDIR /app
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN go build
|
RUN mkdir out && go build -o ./out ./cmd/...
|
||||||
|
|
||||||
FROM ubuntu:20.04
|
FROM ubuntu:20.04
|
||||||
|
|
||||||
|
@ -13,8 +13,8 @@ WORKDIR /app
|
||||||
RUN apt-get update && apt-get install -y ca-certificates && update-ca-certificates
|
RUN apt-get update && apt-get install -y ca-certificates && update-ca-certificates
|
||||||
|
|
||||||
COPY db/migrations ./db/migrations/
|
COPY db/migrations ./db/migrations/
|
||||||
COPY --from=build /app/tokenbridge-monitor ./
|
COPY --from=build /app/out/monitor ./
|
||||||
|
|
||||||
EXPOSE 3333
|
EXPOSE 3333
|
||||||
|
|
||||||
ENTRYPOINT ./tokenbridge-monitor
|
ENTRYPOINT ./monitor
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"tokenbridge-monitor/config"
|
"tokenbridge-monitor/config"
|
||||||
"tokenbridge-monitor/db"
|
"tokenbridge-monitor/db"
|
||||||
|
"tokenbridge-monitor/ethclient"
|
||||||
"tokenbridge-monitor/logging"
|
"tokenbridge-monitor/logging"
|
||||||
"tokenbridge-monitor/monitor"
|
"tokenbridge-monitor/monitor"
|
||||||
"tokenbridge-monitor/presenter"
|
"tokenbridge-monitor/presenter"
|
||||||
|
@ -67,7 +68,15 @@ func main() {
|
||||||
}
|
}
|
||||||
for _, bridgeCfg := range cfg.Bridges {
|
for _, bridgeCfg := range cfg.Bridges {
|
||||||
bridgeLogger := logger.WithField("bridge_id", bridgeCfg.ID)
|
bridgeLogger := logger.WithField("bridge_id", bridgeCfg.ID)
|
||||||
m, err2 := monitor.NewMonitor(ctx, bridgeLogger, dbConn, repo, bridgeCfg)
|
homeClient, err2 := ethclient.NewClient(bridgeCfg.Home.Chain.RPC.Host, bridgeCfg.Home.Chain.RPC.Timeout, bridgeCfg.Home.Chain.ChainID)
|
||||||
|
if err2 != nil {
|
||||||
|
bridgeLogger.WithError(err2).Fatal("can't dial home rpc client")
|
||||||
|
}
|
||||||
|
foreignClient, err2 := ethclient.NewClient(bridgeCfg.Foreign.Chain.RPC.Host, bridgeCfg.Foreign.Chain.RPC.Timeout, bridgeCfg.Foreign.Chain.ChainID)
|
||||||
|
if err2 != nil {
|
||||||
|
bridgeLogger.WithError(err2).Fatal("can't dial foreign rpc client")
|
||||||
|
}
|
||||||
|
m, err2 := monitor.NewMonitor(ctx, bridgeLogger, dbConn, repo, bridgeCfg, homeClient, foreignClient)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
bridgeLogger.WithError(err2).Fatal("can't initialize bridge monitor")
|
bridgeLogger.WithError(err2).Fatal("can't initialize bridge monitor")
|
||||||
}
|
}
|
|
@ -58,8 +58,10 @@
|
||||||
"additionalProperties": {
|
"additionalProperties": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"is_erc_to_native": {
|
"bridge_mode": {
|
||||||
"type": "boolean"
|
"type": "string",
|
||||||
|
"enum": ["AMB", "ERC_TO_NATIVE"],
|
||||||
|
"default": "AMB"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"$ref": "#/$defs/side_config"
|
"$ref": "#/$defs/side_config"
|
||||||
|
|
|
@ -59,7 +59,7 @@ chains:
|
||||||
block_index_interval: 60s
|
block_index_interval: 60s
|
||||||
bridges:
|
bridges:
|
||||||
xdai:
|
xdai:
|
||||||
is_erc_to_native: true
|
bridge_mode: ERC_TO_NATIVE
|
||||||
home:
|
home:
|
||||||
chain: xdai
|
chain: xdai
|
||||||
address: 0x7301CFA0e1756B71869E93d4e4Dca5c7d0eb0AA6
|
address: 0x7301CFA0e1756B71869E93d4e4Dca5c7d0eb0AA6
|
||||||
|
@ -89,6 +89,7 @@ bridges:
|
||||||
stuck_erc_to_native_message_confirmation:
|
stuck_erc_to_native_message_confirmation:
|
||||||
last_validator_activity:
|
last_validator_activity:
|
||||||
xdai-amb:
|
xdai-amb:
|
||||||
|
bridge_mode: AMB
|
||||||
home:
|
home:
|
||||||
chain: xdai
|
chain: xdai
|
||||||
address: 0x75Df5AF045d91108662D8080fD1FEFAd6aA0bb59
|
address: 0x75Df5AF045d91108662D8080fD1FEFAd6aA0bb59
|
||||||
|
@ -120,6 +121,7 @@ bridges:
|
||||||
different_information_signatures:
|
different_information_signatures:
|
||||||
last_validator_activity:
|
last_validator_activity:
|
||||||
test-amb:
|
test-amb:
|
||||||
|
bridge_mode: AMB
|
||||||
home:
|
home:
|
||||||
chain: sokol
|
chain: sokol
|
||||||
address: 0xFe446bEF1DbF7AFE24E81e05BC8B271C1BA9a560
|
address: 0xFe446bEF1DbF7AFE24E81e05BC8B271C1BA9a560
|
||||||
|
@ -149,6 +151,7 @@ bridges:
|
||||||
home_start_block: 21822099
|
home_start_block: 21822099
|
||||||
different_information_signatures:
|
different_information_signatures:
|
||||||
bsc-xdai-amb:
|
bsc-xdai-amb:
|
||||||
|
bridge_mode: AMB
|
||||||
home:
|
home:
|
||||||
chain: xdai
|
chain: xdai
|
||||||
address: 0x162E898bD0aacB578C8D5F8d6ca588c13d2A383F
|
address: 0x162E898bD0aacB578C8D5F8d6ca588c13d2A383F
|
||||||
|
@ -176,6 +179,7 @@ bridges:
|
||||||
failed_information_request:
|
failed_information_request:
|
||||||
different_information_signatures:
|
different_information_signatures:
|
||||||
rinkeby-xdai-amb:
|
rinkeby-xdai-amb:
|
||||||
|
bridge_mode: AMB
|
||||||
home:
|
home:
|
||||||
chain: xdai
|
chain: xdai
|
||||||
address: 0xc38D4991c951fE8BCE1a12bEef2046eF36b0FA4A
|
address: 0xc38D4991c951fE8BCE1a12bEef2046eF36b0FA4A
|
||||||
|
@ -201,6 +205,7 @@ bridges:
|
||||||
failed_information_request:
|
failed_information_request:
|
||||||
different_information_signatures:
|
different_information_signatures:
|
||||||
poa-xdai-amb:
|
poa-xdai-amb:
|
||||||
|
bridge_mode: AMB
|
||||||
home:
|
home:
|
||||||
chain: xdai
|
chain: xdai
|
||||||
address: 0xc2d77d118326c33BBe36EbeAbf4F7ED6BC2dda5c
|
address: 0xc2d77d118326c33BBe36EbeAbf4F7ED6BC2dda5c
|
||||||
|
@ -226,6 +231,7 @@ bridges:
|
||||||
failed_information_request:
|
failed_information_request:
|
||||||
different_information_signatures:
|
different_information_signatures:
|
||||||
eth-bsc-amb:
|
eth-bsc-amb:
|
||||||
|
bridge_mode: AMB
|
||||||
home:
|
home:
|
||||||
chain: bsc
|
chain: bsc
|
||||||
address: 0x6943A218d58135793F1FE619414eD476C37ad65a
|
address: 0x6943A218d58135793F1FE619414eD476C37ad65a
|
||||||
|
|
|
@ -56,12 +56,19 @@ type BridgeAlertConfig struct {
|
||||||
ForeignStartBlock uint `yaml:"foreign_start_block"`
|
ForeignStartBlock uint `yaml:"foreign_start_block"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BridgeMode string
|
||||||
|
|
||||||
|
const (
|
||||||
|
BridgeModeArbitraryMessage BridgeMode = "AMB"
|
||||||
|
BridgeModeErcToNative BridgeMode = "ERC_TO_NATIVE"
|
||||||
|
)
|
||||||
|
|
||||||
type BridgeConfig struct {
|
type BridgeConfig struct {
|
||||||
ID string `yaml:"-"`
|
ID string `yaml:"-"`
|
||||||
IsErcToNative bool `yaml:"is_erc_to_native"`
|
BridgeMode BridgeMode `yaml:"bridge_mode"`
|
||||||
Home *BridgeSideConfig `yaml:"home"`
|
Home *BridgeSideConfig `yaml:"home"`
|
||||||
Foreign *BridgeSideConfig `yaml:"foreign"`
|
Foreign *BridgeSideConfig `yaml:"foreign"`
|
||||||
Alerts map[string]*BridgeAlertConfig `yaml:"alerts"`
|
Alerts map[string]*BridgeAlertConfig `yaml:"alerts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DBConfig struct {
|
type DBConfig struct {
|
||||||
|
@ -111,6 +118,21 @@ func (cfg *Config) init() error {
|
||||||
if bridge.Foreign.MaxBlockRangeSize <= 0 {
|
if bridge.Foreign.MaxBlockRangeSize <= 0 {
|
||||||
bridge.Foreign.MaxBlockRangeSize = 1000
|
bridge.Foreign.MaxBlockRangeSize = 1000
|
||||||
}
|
}
|
||||||
|
if len(bridge.Home.ErcToNativeTokens) > 0 {
|
||||||
|
return fmt.Errorf("non-empty home token address list")
|
||||||
|
}
|
||||||
|
switch bridge.BridgeMode {
|
||||||
|
case BridgeModeErcToNative:
|
||||||
|
if len(bridge.Foreign.ErcToNativeTokens) == 0 {
|
||||||
|
return fmt.Errorf("empty foreign token address list in ERC_TO_NATIVE mode")
|
||||||
|
}
|
||||||
|
case BridgeModeArbitraryMessage:
|
||||||
|
default:
|
||||||
|
bridge.BridgeMode = BridgeModeArbitraryMessage
|
||||||
|
if len(bridge.Foreign.ErcToNativeTokens) > 0 {
|
||||||
|
return fmt.Errorf("non-empty foreign token address list in AMB mode")
|
||||||
|
}
|
||||||
|
}
|
||||||
for _, side := range [2]*BridgeSideConfig{bridge.Home, bridge.Foreign} {
|
for _, side := range [2]*BridgeSideConfig{bridge.Home, bridge.Foreign} {
|
||||||
chainName := side.ChainName
|
chainName := side.ChainName
|
||||||
var ok bool
|
var ok bool
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
package contract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"tokenbridge-monitor/config"
|
||||||
|
"tokenbridge-monitor/contract/abi"
|
||||||
|
"tokenbridge-monitor/ethclient"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BridgeContract struct {
|
||||||
|
*Contract
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBridgeContract(client ethclient.Client, addr common.Address, mode config.BridgeMode) *BridgeContract {
|
||||||
|
var contract *Contract
|
||||||
|
switch mode {
|
||||||
|
case config.BridgeModeArbitraryMessage:
|
||||||
|
contract = NewContract(client, addr, abi.AMB)
|
||||||
|
case config.BridgeModeErcToNative:
|
||||||
|
contract = NewContract(client, addr, abi.ERC_TO_NATIVE)
|
||||||
|
default:
|
||||||
|
contract = NewContract(client, addr, abi.AMB)
|
||||||
|
}
|
||||||
|
return &BridgeContract{contract}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *BridgeContract) ValidatorContractAddress(ctx context.Context) (common.Address, error) {
|
||||||
|
res, err := c.Call(ctx, "validatorContract")
|
||||||
|
if err != nil {
|
||||||
|
return common.Address{}, fmt.Errorf("cannot obtain validator contract address: %w", err)
|
||||||
|
}
|
||||||
|
return common.BytesToAddress(res), nil
|
||||||
|
}
|
|
@ -12,12 +12,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Contract struct {
|
type Contract struct {
|
||||||
Address common.Address
|
address common.Address
|
||||||
client *ethclient.Client
|
client ethclient.Client
|
||||||
abi abi.ABI
|
abi abi.ABI
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewContract(client *ethclient.Client, addr common.Address, abi abi.ABI) *Contract {
|
func NewContract(client ethclient.Client, addr common.Address, abi abi.ABI) *Contract {
|
||||||
return &Contract{addr, client, abi}
|
return &Contract{addr, client, abi}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,67 +29,21 @@ func (c *Contract) AllEvents() map[string]bool {
|
||||||
return events
|
return events
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Contract) ValidatorContractAddress(ctx context.Context) (common.Address, error) {
|
func (c *Contract) Call(ctx context.Context, method string, args ...interface{}) ([]byte, error) {
|
||||||
data, err := c.abi.Pack("validatorContract")
|
data, err := c.abi.Pack(method, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Address{}, fmt.Errorf("cannot encode abi calldata: %w", err)
|
return nil, fmt.Errorf("cannot encode abi calldata: %w", err)
|
||||||
}
|
}
|
||||||
res, err := c.client.CallContract(ctx, ethereum.CallMsg{
|
res, err := c.client.CallContract(ctx, ethereum.CallMsg{
|
||||||
To: &c.Address,
|
To: &c.address,
|
||||||
Data: data,
|
Data: data,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Address{}, fmt.Errorf("cannot call validatorContract(): %w", err)
|
return nil, fmt.Errorf("cannot call %s(...): %w", method, err)
|
||||||
}
|
}
|
||||||
return common.BytesToAddress(res), nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Contract) ParseLog(log *entity.Log) (string, map[string]interface{}, error) {
|
func (c *Contract) ParseLog(log *entity.Log) (string, map[string]interface{}, error) {
|
||||||
if log.Topic0 == nil {
|
return ParseLog(c.abi, log)
|
||||||
return "", nil, fmt.Errorf("cannot process event without topics")
|
|
||||||
}
|
|
||||||
topics := make([]common.Hash, 0, 3)
|
|
||||||
if log.Topic1 != nil {
|
|
||||||
topics = append(topics, *log.Topic1)
|
|
||||||
if log.Topic2 != nil {
|
|
||||||
topics = append(topics, *log.Topic2)
|
|
||||||
if log.Topic3 != nil {
|
|
||||||
topics = append(topics, *log.Topic3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var event *abi.Event
|
|
||||||
var indexed abi.Arguments
|
|
||||||
for _, e := range c.abi.Events {
|
|
||||||
if e.ID == *log.Topic0 {
|
|
||||||
indexed = Indexed(e.Inputs)
|
|
||||||
if len(indexed) == len(topics) {
|
|
||||||
event = &e
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if event == nil {
|
|
||||||
return "", nil, nil
|
|
||||||
}
|
|
||||||
m := make(map[string]interface{})
|
|
||||||
if len(indexed) < len(event.Inputs) {
|
|
||||||
if err := event.Inputs.UnpackIntoMap(m, log.Data); err != nil {
|
|
||||||
return "", nil, fmt.Errorf("can't unpack data: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := abi.ParseTopicsIntoMap(m, indexed, topics); err != nil {
|
|
||||||
return "", nil, fmt.Errorf("can't unpack topics: %w", err)
|
|
||||||
}
|
|
||||||
return event.String(), m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Indexed(args abi.Arguments) abi.Arguments {
|
|
||||||
var indexed abi.Arguments
|
|
||||||
for _, arg := range args {
|
|
||||||
if arg.Indexed {
|
|
||||||
indexed = append(indexed, arg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return indexed
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
package contract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"tokenbridge-monitor/entity"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Indexed(args abi.Arguments) abi.Arguments {
|
||||||
|
var indexed abi.Arguments
|
||||||
|
for _, arg := range args {
|
||||||
|
if arg.Indexed {
|
||||||
|
indexed = append(indexed, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return indexed
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindMatchingEventABI(contractABI abi.ABI, topics []common.Hash) *abi.Event {
|
||||||
|
for _, e := range contractABI.Events {
|
||||||
|
if e.ID == topics[0] {
|
||||||
|
indexed := Indexed(e.Inputs)
|
||||||
|
if len(indexed) == len(topics)-1 {
|
||||||
|
return &e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeEventLog(event *abi.Event, topics []common.Hash, data []byte) (map[string]interface{}, error) {
|
||||||
|
indexed := Indexed(event.Inputs)
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
if len(indexed) < len(event.Inputs) {
|
||||||
|
if err := event.Inputs.UnpackIntoMap(m, data); err != nil {
|
||||||
|
return nil, fmt.Errorf("can't unpack data: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := abi.ParseTopicsIntoMap(m, indexed, topics[1:]); err != nil {
|
||||||
|
return nil, fmt.Errorf("can't unpack topics: %w", err)
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseLog(contractABI abi.ABI, log *entity.Log) (string, map[string]interface{}, error) {
|
||||||
|
topics := log.Topics()
|
||||||
|
if len(topics) == 0 {
|
||||||
|
return "", nil, fmt.Errorf("cannot process event without topics")
|
||||||
|
}
|
||||||
|
event := FindMatchingEventABI(contractABI, topics)
|
||||||
|
if event == nil {
|
||||||
|
return "", nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := DecodeEventLog(event, topics, log.Data)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, fmt.Errorf("can't decode event log: %w", err)
|
||||||
|
}
|
||||||
|
return event.String(), res, nil
|
||||||
|
}
|
|
@ -55,3 +55,20 @@ func NewLog(chainID string, log types.Log) *Log {
|
||||||
}
|
}
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *Log) Topics() []common.Hash {
|
||||||
|
topics := make([]common.Hash, 0, 4)
|
||||||
|
if l.Topic0 != nil {
|
||||||
|
topics = append(topics, *l.Topic0)
|
||||||
|
if l.Topic1 != nil {
|
||||||
|
topics = append(topics, *l.Topic1)
|
||||||
|
if l.Topic2 != nil {
|
||||||
|
topics = append(topics, *l.Topic2)
|
||||||
|
if l.Topic3 != nil {
|
||||||
|
topics = append(topics, *l.Topic3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return topics
|
||||||
|
}
|
||||||
|
|
|
@ -14,8 +14,19 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client interface {
|
||||||
ChainID string
|
BlockNumber(ctx context.Context) (uint, error)
|
||||||
|
HeaderByNumber(ctx context.Context, n uint) (*types.Header, error)
|
||||||
|
FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error)
|
||||||
|
FilterLogsSafe(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error)
|
||||||
|
TransactionByHash(ctx context.Context, hash common.Hash) (*types.Transaction, error)
|
||||||
|
TransactionReceiptByHash(ctx context.Context, hash common.Hash) (*types.Receipt, error)
|
||||||
|
CallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error)
|
||||||
|
TransactionSender(tx *types.Transaction) (common.Address, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type rpcClient struct {
|
||||||
|
chainID string
|
||||||
url string
|
url string
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
rawClient *rpc.Client
|
rawClient *rpc.Client
|
||||||
|
@ -23,7 +34,7 @@ type Client struct {
|
||||||
signer types.Signer
|
signer types.Signer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(url string, timeout time.Duration, chainID string) (*Client, error) {
|
func NewClient(url string, timeout time.Duration, chainID string) (Client, error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
@ -31,8 +42,8 @@ func NewClient(url string, timeout time.Duration, chainID string) (*Client, erro
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't dial JSON rpc url: %w", err)
|
return nil, fmt.Errorf("can't dial JSON rpc url: %w", err)
|
||||||
}
|
}
|
||||||
client := &Client{
|
client := &rpcClient{
|
||||||
ChainID: chainID,
|
chainID: chainID,
|
||||||
url: url,
|
url: url,
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
rawClient: rawClient,
|
rawClient: rawClient,
|
||||||
|
@ -51,46 +62,46 @@ func NewClient(url string, timeout time.Duration, chainID string) (*Client, erro
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) BlockNumber(ctx context.Context) (uint, error) {
|
func (c *rpcClient) BlockNumber(ctx context.Context) (uint, error) {
|
||||||
defer ObserveDuration(c.ChainID, c.url, "eth_blockNumber")()
|
defer ObserveDuration(c.chainID, c.url, "eth_blockNumber")()
|
||||||
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
n, err := c.client.BlockNumber(ctx)
|
n, err := c.client.BlockNumber(ctx)
|
||||||
ObserveError(c.ChainID, c.url, "eth_blockNumber", err)
|
ObserveError(c.chainID, c.url, "eth_blockNumber", err)
|
||||||
return uint(n), err
|
return uint(n), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) HeaderByNumber(ctx context.Context, n uint) (*types.Header, error) {
|
func (c *rpcClient) HeaderByNumber(ctx context.Context, n uint) (*types.Header, error) {
|
||||||
defer ObserveDuration(c.ChainID, c.url, "eth_getBlockByNumber")()
|
defer ObserveDuration(c.chainID, c.url, "eth_getBlockByNumber")()
|
||||||
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
header, err := c.client.HeaderByNumber(ctx, big.NewInt(int64(n)))
|
header, err := c.client.HeaderByNumber(ctx, big.NewInt(int64(n)))
|
||||||
ObserveError(c.ChainID, c.url, "eth_getBlockByNumber", err)
|
ObserveError(c.chainID, c.url, "eth_getBlockByNumber", err)
|
||||||
return header, err
|
return header, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) {
|
func (c *rpcClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) {
|
||||||
defer ObserveDuration(c.ChainID, c.url, "eth_getLogs")()
|
defer ObserveDuration(c.chainID, c.url, "eth_getLogs")()
|
||||||
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
logs, err := c.client.FilterLogs(ctx, q)
|
logs, err := c.client.FilterLogs(ctx, q)
|
||||||
ObserveError(c.ChainID, c.url, "eth_getLogs", err)
|
ObserveError(c.chainID, c.url, "eth_getLogs", err)
|
||||||
return logs, err
|
return logs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilterLogsSafe is the same as FilterLogs, but makes an additional eth_blockNumber
|
// FilterLogsSafe is the same as FilterLogs, but makes an additional eth_blockNumber
|
||||||
// request to ensure that the node behind RPC is synced to the needed point.
|
// request to ensure that the node behind RPC is synced to the needed point.
|
||||||
func (c *Client) FilterLogsSafe(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) {
|
func (c *rpcClient) FilterLogsSafe(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) {
|
||||||
defer ObserveDuration(c.ChainID, c.url, "eth_getLogsSafe")()
|
defer ObserveDuration(c.chainID, c.url, "eth_getLogsSafe")()
|
||||||
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
defer func() {
|
defer func() {
|
||||||
ObserveError(c.ChainID, c.url, "eth_getLogsSafe", err)
|
ObserveError(c.chainID, c.url, "eth_getLogsSafe", err)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var arg interface{}
|
var arg interface{}
|
||||||
|
@ -127,37 +138,37 @@ func (c *Client) FilterLogsSafe(ctx context.Context, q ethereum.FilterQuery) ([]
|
||||||
return logs, nil
|
return logs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, error) {
|
func (c *rpcClient) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, error) {
|
||||||
defer ObserveDuration(c.ChainID, c.url, "eth_getTransactionByHash")()
|
defer ObserveDuration(c.chainID, c.url, "eth_getTransactionByHash")()
|
||||||
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
tx, _, err := c.client.TransactionByHash(ctx, txHash)
|
tx, _, err := c.client.TransactionByHash(ctx, txHash)
|
||||||
ObserveError(c.ChainID, c.url, "eth_getTransactionByHash", err)
|
ObserveError(c.chainID, c.url, "eth_getTransactionByHash", err)
|
||||||
return tx, err
|
return tx, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) TransactionReceiptByHash(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
|
func (c *rpcClient) TransactionReceiptByHash(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
|
||||||
defer ObserveDuration(c.ChainID, c.url, "eth_getTransactionReceipt")()
|
defer ObserveDuration(c.chainID, c.url, "eth_getTransactionReceipt")()
|
||||||
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
receipt, err := c.client.TransactionReceipt(ctx, txHash)
|
receipt, err := c.client.TransactionReceipt(ctx, txHash)
|
||||||
ObserveError(c.ChainID, c.url, "eth_getTransactionReceipt", err)
|
ObserveError(c.chainID, c.url, "eth_getTransactionReceipt", err)
|
||||||
return receipt, err
|
return receipt, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) CallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) {
|
func (c *rpcClient) CallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) {
|
||||||
defer ObserveDuration(c.ChainID, c.url, "eth_call")()
|
defer ObserveDuration(c.chainID, c.url, "eth_call")()
|
||||||
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
res, err := c.client.CallContract(ctx, msg, nil)
|
res, err := c.client.CallContract(ctx, msg, nil)
|
||||||
ObserveError(c.ChainID, c.url, "eth_call", err)
|
ObserveError(c.chainID, c.url, "eth_call", err)
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) TransactionSender(tx *types.Transaction) (common.Address, error) {
|
func (c *rpcClient) TransactionSender(tx *types.Transaction) (common.Address, error) {
|
||||||
return c.signer.Sender(tx)
|
return c.signer.Sender(tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
"tokenbridge-monitor/config"
|
"tokenbridge-monitor/config"
|
||||||
"tokenbridge-monitor/contract"
|
"tokenbridge-monitor/contract"
|
||||||
"tokenbridge-monitor/contract/abi"
|
|
||||||
"tokenbridge-monitor/entity"
|
"tokenbridge-monitor/entity"
|
||||||
"tokenbridge-monitor/ethclient"
|
"tokenbridge-monitor/ethclient"
|
||||||
"tokenbridge-monitor/logging"
|
"tokenbridge-monitor/logging"
|
||||||
|
@ -37,11 +36,11 @@ type ContractMonitor struct {
|
||||||
cfg *config.BridgeSideConfig
|
cfg *config.BridgeSideConfig
|
||||||
logger logging.Logger
|
logger logging.Logger
|
||||||
repo *repository.Repo
|
repo *repository.Repo
|
||||||
client *ethclient.Client
|
client ethclient.Client
|
||||||
logsCursor *entity.LogsCursor
|
logsCursor *entity.LogsCursor
|
||||||
blocksRangeChan chan *BlocksRange
|
blocksRangeChan chan *BlocksRange
|
||||||
logsChan chan *LogsBatch
|
logsChan chan *LogsBatch
|
||||||
contract *contract.Contract
|
contract *contract.BridgeContract
|
||||||
eventHandlers map[string]EventHandler
|
eventHandlers map[string]EventHandler
|
||||||
headBlock uint
|
headBlock uint
|
||||||
isSynced bool
|
isSynced bool
|
||||||
|
@ -51,38 +50,31 @@ type ContractMonitor struct {
|
||||||
processedBlockMetric prometheus.Gauge
|
processedBlockMetric prometheus.Gauge
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewContractMonitor(ctx context.Context, logger logging.Logger, repo *repository.Repo, bridgeCfg *config.BridgeConfig, cfg *config.BridgeSideConfig) (*ContractMonitor, error) {
|
func NewContractMonitor(ctx context.Context, logger logging.Logger, repo *repository.Repo, bridgeCfg *config.BridgeConfig, cfg *config.BridgeSideConfig, client ethclient.Client) (*ContractMonitor, error) {
|
||||||
client, err := ethclient.NewClient(cfg.Chain.RPC.Host, cfg.Chain.RPC.Timeout, cfg.Chain.ChainID)
|
bridgeContract := contract.NewBridgeContract(client, cfg.Address, bridgeCfg.BridgeMode)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to start eth client: %w", err)
|
|
||||||
}
|
|
||||||
contractAbi := abi.AMB
|
|
||||||
if bridgeCfg.IsErcToNative {
|
|
||||||
contractAbi = abi.ERC_TO_NATIVE
|
|
||||||
}
|
|
||||||
bridgeContract := contract.NewContract(client, cfg.Address, contractAbi)
|
|
||||||
if cfg.ValidatorContractAddress == (common.Address{}) {
|
if cfg.ValidatorContractAddress == (common.Address{}) {
|
||||||
cfg.ValidatorContractAddress, err = bridgeContract.ValidatorContractAddress(ctx)
|
addr, err := bridgeContract.ValidatorContractAddress(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot get validator contract address: %w", err)
|
return nil, fmt.Errorf("cannot get validator contract address: %w", err)
|
||||||
}
|
}
|
||||||
logger.WithFields(logrus.Fields{
|
logger.WithFields(logrus.Fields{
|
||||||
"chain_id": client.ChainID,
|
"chain_id": cfg.Chain.ChainID,
|
||||||
"bridge_address": cfg.Address,
|
"bridge_address": cfg.Address,
|
||||||
"validator_contract_address": cfg.ValidatorContractAddress,
|
"validator_contract_address": cfg.ValidatorContractAddress,
|
||||||
"start_block": cfg.StartBlock,
|
"start_block": cfg.StartBlock,
|
||||||
}).Info("obtained validator contract address")
|
}).Info("obtained validator contract address")
|
||||||
|
cfg.ValidatorContractAddress = addr
|
||||||
}
|
}
|
||||||
logsCursor, err := repo.LogsCursors.GetByChainIDAndAddress(ctx, client.ChainID, cfg.Address)
|
logsCursor, err := repo.LogsCursors.GetByChainIDAndAddress(ctx, cfg.Chain.ChainID, cfg.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
logger.WithFields(logrus.Fields{
|
logger.WithFields(logrus.Fields{
|
||||||
"chain_id": client.ChainID,
|
"chain_id": cfg.Chain.ChainID,
|
||||||
"address": cfg.Address,
|
"address": cfg.Address,
|
||||||
"start_block": cfg.StartBlock,
|
"start_block": cfg.StartBlock,
|
||||||
}).Warn("contract cursor is not present, staring indexing from scratch")
|
}).Warn("contract cursor is not present, staring indexing from scratch")
|
||||||
logsCursor = &entity.LogsCursor{
|
logsCursor = &entity.LogsCursor{
|
||||||
ChainID: client.ChainID,
|
ChainID: cfg.Chain.ChainID,
|
||||||
Address: cfg.Address,
|
Address: cfg.Address,
|
||||||
LastFetchedBlock: cfg.StartBlock - 1,
|
LastFetchedBlock: cfg.StartBlock - 1,
|
||||||
LastProcessedBlock: cfg.StartBlock - 1,
|
LastProcessedBlock: cfg.StartBlock - 1,
|
||||||
|
@ -93,7 +85,7 @@ func NewContractMonitor(ctx context.Context, logger logging.Logger, repo *reposi
|
||||||
}
|
}
|
||||||
commonLabels := prometheus.Labels{
|
commonLabels := prometheus.Labels{
|
||||||
"bridge_id": bridgeCfg.ID,
|
"bridge_id": bridgeCfg.ID,
|
||||||
"chain_id": client.ChainID,
|
"chain_id": cfg.Chain.ChainID,
|
||||||
"address": cfg.Address.String(),
|
"address": cfg.Address.String(),
|
||||||
}
|
}
|
||||||
return &ContractMonitor{
|
return &ContractMonitor{
|
||||||
|
@ -135,6 +127,8 @@ func (m *ContractMonitor) VerifyEventHandlersABI() error {
|
||||||
func (m *ContractMonitor) Start(ctx context.Context) {
|
func (m *ContractMonitor) Start(ctx context.Context) {
|
||||||
lastProcessedBlock := m.logsCursor.LastProcessedBlock
|
lastProcessedBlock := m.logsCursor.LastProcessedBlock
|
||||||
lastFetchedBlock := m.logsCursor.LastFetchedBlock
|
lastFetchedBlock := m.logsCursor.LastFetchedBlock
|
||||||
|
m.processedBlockMetric.Set(float64(lastProcessedBlock))
|
||||||
|
m.fetchedBlockMetric.Set(float64(lastFetchedBlock))
|
||||||
go m.StartBlockFetcher(ctx, lastFetchedBlock+1)
|
go m.StartBlockFetcher(ctx, lastFetchedBlock+1)
|
||||||
go m.StartLogsProcessor(ctx)
|
go m.StartLogsProcessor(ctx)
|
||||||
m.LoadUnprocessedLogs(ctx, lastProcessedBlock+1, lastFetchedBlock)
|
m.LoadUnprocessedLogs(ctx, lastProcessedBlock+1, lastFetchedBlock)
|
||||||
|
@ -149,7 +143,7 @@ func (m *ContractMonitor) LoadUnprocessedLogs(ctx context.Context, fromBlock, to
|
||||||
|
|
||||||
addresses := m.cfg.ContractAddresses(fromBlock, toBlock)
|
addresses := m.cfg.ContractAddresses(fromBlock, toBlock)
|
||||||
for {
|
for {
|
||||||
logs, err := m.repo.Logs.FindByBlockRange(ctx, m.client.ChainID, addresses, fromBlock, toBlock)
|
logs, err := m.repo.Logs.FindByBlockRange(ctx, m.cfg.Chain.ChainID, addresses, fromBlock, toBlock)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.logger.WithError(err).Error("can't find unprocessed logs in block range")
|
m.logger.WithError(err).Error("can't find unprocessed logs in block range")
|
||||||
} else {
|
} else {
|
||||||
|
@ -272,7 +266,7 @@ func (m *ContractMonitor) buildFilterQueries(blocksRange *BlocksRange) []ethereu
|
||||||
q.Topics = [][]common.Hash{{*blocksRange.Topic}}
|
q.Topics = [][]common.Hash{{*blocksRange.Topic}}
|
||||||
}
|
}
|
||||||
qs = append(qs, q)
|
qs = append(qs, q)
|
||||||
if m.bridgeCfg.IsErcToNative {
|
if m.bridgeCfg.BridgeMode == config.BridgeModeErcToNative {
|
||||||
for _, token := range m.cfg.ErcToNativeTokens {
|
for _, token := range m.cfg.ErcToNativeTokens {
|
||||||
if token.StartBlock > 0 && blocksRange.To < token.StartBlock {
|
if token.StartBlock > 0 && blocksRange.To < token.StartBlock {
|
||||||
continue
|
continue
|
||||||
|
@ -437,7 +431,7 @@ func (m *ContractMonitor) StartLogsProcessor(ctx context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ContractMonitor) tryToGetBlockTimestamp(ctx context.Context, blockNumber uint) error {
|
func (m *ContractMonitor) tryToGetBlockTimestamp(ctx context.Context, blockNumber uint) error {
|
||||||
ts, err := m.repo.BlockTimestamps.GetByBlockNumber(ctx, m.client.ChainID, blockNumber)
|
ts, err := m.repo.BlockTimestamps.GetByBlockNumber(ctx, m.cfg.Chain.ChainID, blockNumber)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't get block timestamp from db: %w", err)
|
return fmt.Errorf("can't get block timestamp from db: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -451,7 +445,7 @@ func (m *ContractMonitor) tryToGetBlockTimestamp(ctx context.Context, blockNumbe
|
||||||
return fmt.Errorf("can't request block header: %w", err)
|
return fmt.Errorf("can't request block header: %w", err)
|
||||||
}
|
}
|
||||||
return m.repo.BlockTimestamps.Ensure(ctx, &entity.BlockTimestamp{
|
return m.repo.BlockTimestamps.Ensure(ctx, &entity.BlockTimestamp{
|
||||||
ChainID: m.client.ChainID,
|
ChainID: m.cfg.Chain.ChainID,
|
||||||
BlockNumber: blockNumber,
|
BlockNumber: blockNumber,
|
||||||
Timestamp: time.Unix(int64(header.Time), 0),
|
Timestamp: time.Unix(int64(header.Time), 0),
|
||||||
})
|
})
|
||||||
|
|
|
@ -19,11 +19,11 @@ type EventHandler func(ctx context.Context, log *entity.Log, data map[string]int
|
||||||
type BridgeEventHandler struct {
|
type BridgeEventHandler struct {
|
||||||
repo *repository.Repo
|
repo *repository.Repo
|
||||||
bridgeID string
|
bridgeID string
|
||||||
homeClient *ethclient.Client
|
homeClient ethclient.Client
|
||||||
cfg *config.BridgeConfig
|
cfg *config.BridgeConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBridgeEventHandler(repo *repository.Repo, cfg *config.BridgeConfig, homeClient *ethclient.Client) *BridgeEventHandler {
|
func NewBridgeEventHandler(repo *repository.Repo, cfg *config.BridgeConfig, homeClient ethclient.Client) *BridgeEventHandler {
|
||||||
return &BridgeEventHandler{
|
return &BridgeEventHandler{
|
||||||
repo: repo,
|
repo: repo,
|
||||||
bridgeID: cfg.ID,
|
bridgeID: cfg.ID,
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"tokenbridge-monitor/config"
|
"tokenbridge-monitor/config"
|
||||||
"tokenbridge-monitor/contract/abi"
|
"tokenbridge-monitor/contract/abi"
|
||||||
"tokenbridge-monitor/db"
|
"tokenbridge-monitor/db"
|
||||||
|
"tokenbridge-monitor/ethclient"
|
||||||
"tokenbridge-monitor/logging"
|
"tokenbridge-monitor/logging"
|
||||||
"tokenbridge-monitor/monitor/alerts"
|
"tokenbridge-monitor/monitor/alerts"
|
||||||
"tokenbridge-monitor/repository"
|
"tokenbridge-monitor/repository"
|
||||||
|
@ -20,13 +21,13 @@ type Monitor struct {
|
||||||
alertManager *alerts.AlertManager
|
alertManager *alerts.AlertManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMonitor(ctx context.Context, logger logging.Logger, dbConn *db.DB, repo *repository.Repo, cfg *config.BridgeConfig) (*Monitor, error) {
|
func NewMonitor(ctx context.Context, logger logging.Logger, dbConn *db.DB, repo *repository.Repo, cfg *config.BridgeConfig, homeClient, foreignClient ethclient.Client) (*Monitor, error) {
|
||||||
logger.Info("initializing bridge monitor")
|
logger.Info("initializing bridge monitor")
|
||||||
homeMonitor, err := NewContractMonitor(ctx, logger.WithField("contract", "home"), repo, cfg, cfg.Home)
|
homeMonitor, err := NewContractMonitor(ctx, logger.WithField("contract", "home"), repo, cfg, cfg.Home, homeClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to initialize home side monitor: %w", err)
|
return nil, fmt.Errorf("failed to initialize home side monitor: %w", err)
|
||||||
}
|
}
|
||||||
foreignMonitor, err := NewContractMonitor(ctx, logger.WithField("contract", "foreign"), repo, cfg, cfg.Foreign)
|
foreignMonitor, err := NewContractMonitor(ctx, logger.WithField("contract", "foreign"), repo, cfg, cfg.Foreign, foreignClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to initialize foreign side monitor: %w", err)
|
return nil, fmt.Errorf("failed to initialize foreign side monitor: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -42,9 +43,10 @@ func NewMonitor(ctx context.Context, logger logging.Logger, dbConn *db.DB, repo
|
||||||
foreignMonitor: foreignMonitor,
|
foreignMonitor: foreignMonitor,
|
||||||
alertManager: alertManager,
|
alertManager: alertManager,
|
||||||
}
|
}
|
||||||
if cfg.IsErcToNative {
|
switch cfg.BridgeMode {
|
||||||
|
case config.BridgeModeErcToNative:
|
||||||
monitor.RegisterErcToNativeEventHandlers()
|
monitor.RegisterErcToNativeEventHandlers()
|
||||||
} else {
|
case config.BridgeModeArbitraryMessage:
|
||||||
monitor.RegisterAMBEventHandlers()
|
monitor.RegisterAMBEventHandlers()
|
||||||
}
|
}
|
||||||
err = monitor.homeMonitor.VerifyEventHandlersABI()
|
err = monitor.homeMonitor.VerifyEventHandlersABI()
|
||||||
|
|
Loading…
Reference in New Issue