Add aptos support to contract watcher (#255)
This commit is contained in:
parent
f8b631c71f
commit
4e0c077a63
|
@ -7,7 +7,7 @@ COPY contract-watcher contract-watcher
|
|||
COPY common common
|
||||
|
||||
# Build the Go app
|
||||
RUN cd contract-watcher && CGO_ENABLED=0 GOOS=linux go build -o "./contract-watcher" cmd/main.go
|
||||
RUN cd contract-watcher && CGO_ENABLED=0 GOOS=linux go build -o "./contract-watcher" cmd/*.go
|
||||
|
||||
############################
|
||||
# STEP 2 build a small image
|
||||
|
|
|
@ -8,7 +8,7 @@ help:
|
|||
@sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' | sed -e 's/^/ /'
|
||||
|
||||
build:
|
||||
go build -o contract-watcher cmd/main.go
|
||||
go build -o contract-watcher cmd/*.go
|
||||
|
||||
test:
|
||||
go test -v -cover ./...
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/config"
|
||||
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/http/infrastructure"
|
||||
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/internal/ankr"
|
||||
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/internal/aptos"
|
||||
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/internal/db"
|
||||
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/internal/solana"
|
||||
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/internal/terra"
|
||||
|
@ -118,6 +119,7 @@ type watchersConfig struct {
|
|||
evms []watcherBlockchain
|
||||
solana *watcherBlockchain
|
||||
terra *watcherBlockchain
|
||||
aptos *watcherBlockchain
|
||||
rateLimit rateLimitConfig
|
||||
}
|
||||
|
||||
|
@ -125,6 +127,7 @@ type rateLimitConfig struct {
|
|||
evm int
|
||||
solana int
|
||||
terra int
|
||||
aptos int
|
||||
}
|
||||
|
||||
func newWatchers(config *config.Configuration, repo *storage.Repository, logger *zap.Logger) []watcher.ContractWatcher {
|
||||
|
@ -138,7 +141,6 @@ func newWatchers(config *config.Configuration, repo *storage.Repository, logger
|
|||
watchers = &watchersConfig{}
|
||||
}
|
||||
|
||||
// add evm watchers
|
||||
result := make([]watcher.ContractWatcher, 0)
|
||||
|
||||
// add evm watchers
|
||||
|
@ -172,24 +174,39 @@ func newWatchers(config *config.Configuration, repo *storage.Repository, logger
|
|||
result = append(result, watcher.NewTerraWatcher(terraClient, params, repo, logger))
|
||||
}
|
||||
|
||||
// add aptos watcher
|
||||
if watchers.aptos != nil {
|
||||
aptosLimiter := ratelimit.New(watchers.rateLimit.aptos, ratelimit.Per(time.Second))
|
||||
aptosClient := aptos.NewAptosSDK(config.AptosUrl, aptosLimiter)
|
||||
params := watcher.AptosParams{
|
||||
Blockchain: watchers.aptos.name,
|
||||
ContractAddress: watchers.aptos.address,
|
||||
SizeBlocks: watchers.aptos.sizeBlocks,
|
||||
WaitSeconds: watchers.aptos.waitSeconds,
|
||||
InitialBlock: watchers.aptos.initialBlock}
|
||||
result = append(result, watcher.NewAptosWatcher(aptosClient, params, repo, logger))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func newEVMWatchersForMainnet() *watchersConfig {
|
||||
return &watchersConfig{
|
||||
evms: []watcherBlockchain{
|
||||
{vaa.ChainIDEthereum, "eth", "0x3ee18B2214AFF97000D974cf647E7C347E8fa585", 100, 10, 16820790},
|
||||
{vaa.ChainIDPolygon, "polygon", "0x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE", 100, 10, 40307020},
|
||||
{vaa.ChainIDBSC, "bsc", "0xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7", 100, 10, 26436320},
|
||||
{vaa.ChainIDFantom, "fantom", "0x7C9Fc5741288cDFdD83CeB07f3ea7e22618D79D2", 100, 10, 57525624},
|
||||
{vaa.ChainIDAvalanche, "avalanche", "0x0e082F06FF657D94310cB8cE8B0D9a04541d8052", 100, 10, 8237181},
|
||||
ETHEREUM_MAINNET,
|
||||
POLYGON_MAINNET,
|
||||
BSC_MAINNET,
|
||||
FANTOM_MAINNET,
|
||||
AVALANCHE_MAINNET,
|
||||
},
|
||||
solana: &watcherBlockchain{vaa.ChainIDSolana, "solana", "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb", 100, 10, 183675278},
|
||||
terra: &watcherBlockchain{vaa.ChainIDTerra, "terra", "terra10nmmwe8r3g99a9newtqa7a75xfgs2e8z87r2sf", 0, 10, 3911168},
|
||||
solana: &SOLANA_MAINNET,
|
||||
terra: &TERRA_MAINNET,
|
||||
aptos: &APTOS_MAINNET,
|
||||
rateLimit: rateLimitConfig{
|
||||
evm: 1000,
|
||||
solana: 3,
|
||||
terra: 10,
|
||||
aptos: 3,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -197,17 +214,19 @@ func newEVMWatchersForMainnet() *watchersConfig {
|
|||
func newEVMWatchersForTestnet() *watchersConfig {
|
||||
return &watchersConfig{
|
||||
evms: []watcherBlockchain{
|
||||
{vaa.ChainIDEthereum, "eth_goerli", "0xF890982f9310df57d00f659cf4fd87e65adEd8d7", 100, 10, 8660321},
|
||||
{vaa.ChainIDPolygon, "polygon_mumbai", "0x377D55a7928c046E18eEbb61977e714d2a76472a", 100, 10, 33151522},
|
||||
{vaa.ChainIDBSC, "bsc_testnet_chapel", "0x9dcF9D205C9De35334D646BeE44b2D2859712A09", 100, 10, 28071327},
|
||||
{vaa.ChainIDFantom, "fantom_testnet", "0x599CEa2204B4FaECd584Ab1F2b6aCA137a0afbE8", 100, 10, 14524466},
|
||||
{vaa.ChainIDAvalanche, "avalanche_fuji", "0x61E44E506Ca5659E6c0bba9b678586fA2d729756", 100, 10, 11014526},
|
||||
ETHEREUM_TESTNET,
|
||||
POLYGON_TESTNET,
|
||||
BSC_TESTNET,
|
||||
FANTOM_TESTNET,
|
||||
AVALANCHE_TESTNET,
|
||||
},
|
||||
solana: &watcherBlockchain{vaa.ChainIDSolana, "solana", "DZnkkTmCiFWfYTfT41X3Rd1kDgozqzxWaHqsw6W4x2oe", 10, 10, 16820790},
|
||||
solana: &SOLANA_TESTNET,
|
||||
aptos: &APTOS_TESTNET,
|
||||
rateLimit: rateLimitConfig{
|
||||
evm: 10,
|
||||
solana: 2,
|
||||
terra: 5,
|
||||
aptos: 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
package main
|
||||
|
||||
import "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||
|
||||
var ETHEREUM_MAINNET = watcherBlockchain{
|
||||
chainID: vaa.ChainIDEthereum,
|
||||
name: "eth",
|
||||
address: "0x3ee18B2214AFF97000D974cf647E7C347E8fa585",
|
||||
sizeBlocks: 100,
|
||||
waitSeconds: 10,
|
||||
initialBlock: 16820790,
|
||||
}
|
||||
|
||||
var POLYGON_MAINNET = watcherBlockchain{
|
||||
chainID: vaa.ChainIDPolygon,
|
||||
name: "polygon",
|
||||
address: "0x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE",
|
||||
sizeBlocks: 100,
|
||||
waitSeconds: 10,
|
||||
initialBlock: 40307020,
|
||||
}
|
||||
|
||||
var BSC_MAINNET = watcherBlockchain{
|
||||
chainID: vaa.ChainIDBSC,
|
||||
name: "bsc",
|
||||
address: "0xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7",
|
||||
sizeBlocks: 100,
|
||||
waitSeconds: 10,
|
||||
initialBlock: 26436320,
|
||||
}
|
||||
|
||||
var FANTOM_MAINNET = watcherBlockchain{
|
||||
chainID: vaa.ChainIDFantom,
|
||||
name: "fantom",
|
||||
address: "0x7C9Fc5741288cDFdD83CeB07f3ea7e22618D79D2",
|
||||
sizeBlocks: 100,
|
||||
waitSeconds: 10,
|
||||
initialBlock: 57525624,
|
||||
}
|
||||
|
||||
var SOLANA_MAINNET = watcherBlockchain{
|
||||
chainID: vaa.ChainIDSolana,
|
||||
name: "solana",
|
||||
address: "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb",
|
||||
sizeBlocks: 100,
|
||||
waitSeconds: 10,
|
||||
initialBlock: 183675278,
|
||||
}
|
||||
|
||||
var TERRA_MAINNET = watcherBlockchain{
|
||||
chainID: vaa.ChainIDTerra,
|
||||
name: "terra",
|
||||
address: "terra10nmmwe8r3g99a9newtqa7a75xfgs2e8z87r2sf",
|
||||
sizeBlocks: 0,
|
||||
waitSeconds: 10,
|
||||
initialBlock: 3911168,
|
||||
}
|
||||
|
||||
var AVALANCHE_MAINNET = watcherBlockchain{
|
||||
chainID: vaa.ChainIDAvalanche,
|
||||
name: "avalanche",
|
||||
address: "0x0e082F06FF657D94310cB8cE8B0D9a04541d8052",
|
||||
sizeBlocks: 100,
|
||||
waitSeconds: 10,
|
||||
initialBlock: 8237181,
|
||||
}
|
||||
|
||||
var APTOS_MAINNET = watcherBlockchain{
|
||||
chainID: vaa.ChainIDAptos,
|
||||
name: "aptos",
|
||||
address: "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f",
|
||||
sizeBlocks: 50,
|
||||
waitSeconds: 10,
|
||||
initialBlock: 1094430,
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package main
|
||||
|
||||
import "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||
|
||||
var ETHEREUM_TESTNET = watcherBlockchain{
|
||||
chainID: vaa.ChainIDEthereum,
|
||||
name: "eth_goerli",
|
||||
address: "0xF890982f9310df57d00f659cf4fd87e65adEd8d7",
|
||||
sizeBlocks: 100,
|
||||
waitSeconds: 10,
|
||||
initialBlock: 8660321,
|
||||
}
|
||||
|
||||
var POLYGON_TESTNET = watcherBlockchain{
|
||||
chainID: vaa.ChainIDPolygon,
|
||||
name: "polygon_mumbai",
|
||||
address: "0x377D55a7928c046E18eEbb61977e714d2a76472a",
|
||||
sizeBlocks: 100,
|
||||
waitSeconds: 10,
|
||||
initialBlock: 33151522,
|
||||
}
|
||||
|
||||
var BSC_TESTNET = watcherBlockchain{
|
||||
chainID: vaa.ChainIDBSC,
|
||||
name: "bsc_testnet_chapel",
|
||||
address: "0x9dcF9D205C9De35334D646BeE44b2D2859712A09",
|
||||
sizeBlocks: 100,
|
||||
waitSeconds: 10,
|
||||
initialBlock: 28071327,
|
||||
}
|
||||
|
||||
var FANTOM_TESTNET = watcherBlockchain{
|
||||
chainID: vaa.ChainIDFantom,
|
||||
name: "fantom_testnet",
|
||||
address: "0x599CEa2204B4FaECd584Ab1F2b6aCA137a0afbE8",
|
||||
sizeBlocks: 100,
|
||||
waitSeconds: 10,
|
||||
initialBlock: 14524466,
|
||||
}
|
||||
|
||||
var SOLANA_TESTNET = watcherBlockchain{
|
||||
chainID: vaa.ChainIDSolana,
|
||||
name: "solana",
|
||||
address: "DZnkkTmCiFWfYTfT41X3Rd1kDgozqzxWaHqsw6W4x2oe",
|
||||
sizeBlocks: 10,
|
||||
waitSeconds: 10,
|
||||
initialBlock: 16820790,
|
||||
}
|
||||
|
||||
var AVALANCHE_TESTNET = watcherBlockchain{
|
||||
chainID: vaa.ChainIDAvalanche,
|
||||
name: "avalanche_fuji",
|
||||
address: "0x61E44E506Ca5659E6c0bba9b678586fA2d729756",
|
||||
sizeBlocks: 100,
|
||||
waitSeconds: 10,
|
||||
initialBlock: 11014526,
|
||||
}
|
||||
|
||||
var APTOS_TESTNET = watcherBlockchain{
|
||||
chainID: vaa.ChainIDAptos,
|
||||
name: "aptos",
|
||||
address: "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f",
|
||||
sizeBlocks: 50,
|
||||
waitSeconds: 10,
|
||||
initialBlock: 21522262,
|
||||
}
|
|
@ -17,6 +17,7 @@ type Configuration struct {
|
|||
AnkrUrl string `env:"ANKR_URL,required"`
|
||||
SolanaUrl string `env:"SOLANA_URL,required"`
|
||||
TerraUrl string `env:"TERRA_URL,required"`
|
||||
AptosUrl string `env:"APTOS_URL,required"`
|
||||
PprofEnabled bool `env:"PPROF_ENABLED,default=false"`
|
||||
P2pNetwork string `env:"P2P_NETWORK,required"`
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ go 1.19
|
|||
require (
|
||||
github.com/avast/retry-go v3.0.0+incompatible
|
||||
github.com/gagliardetto/solana-go v1.8.2
|
||||
github.com/go-resty/resty/v2 v2.7.0
|
||||
github.com/gofiber/fiber/v2 v2.42.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/near/borsh-go v0.3.1
|
||||
|
|
|
@ -130,6 +130,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2
|
|||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
|
||||
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gofiber/fiber/v2 v2.42.0 h1:Fnp7ybWvS+sjNQsFvkhf4G8OhXswvB6Vee8hM/LyS+8=
|
||||
github.com/gofiber/fiber/v2 v2.42.0/go.mod h1:3+SGNjqMh5VQH5Vz2Wdi43zTIV16ktlFd3x3R6O1Zlc=
|
||||
|
@ -519,6 +521,7 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
|
|||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
package aptos
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"go.uber.org/ratelimit"
|
||||
)
|
||||
|
||||
var ErrTooManyRequests = fmt.Errorf("too many requests")
|
||||
|
||||
// AptosSDK is a client for the Aptos API.
|
||||
type AptosSDK struct {
|
||||
client *resty.Client
|
||||
rl ratelimit.Limiter
|
||||
}
|
||||
|
||||
type GetLatestBlock struct {
|
||||
BlockHeight string `json:"block_height"`
|
||||
}
|
||||
|
||||
type Payload struct {
|
||||
Function string `json:"function"`
|
||||
TypeArguments []string `json:"type_arguments"`
|
||||
Arguments []any `json:"arguments"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type Transaction struct {
|
||||
Version string `json:"version"`
|
||||
Hash string `json:"hash"`
|
||||
Payload Payload `json:"payload,omitempty"`
|
||||
}
|
||||
|
||||
type GetBlockResult struct {
|
||||
BlockHeight string `json:"block_height"`
|
||||
BlockHash string `json:"block_hash"`
|
||||
BlockTimestamp string `json:"block_timestamp"`
|
||||
Transactions []Transaction `json:"transactions"`
|
||||
}
|
||||
|
||||
func (r *GetBlockResult) GetBlockTime() (*time.Time, error) {
|
||||
t, err := strconv.ParseUint(r.BlockTimestamp, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tm := time.UnixMicro(int64(t))
|
||||
return &tm, nil
|
||||
}
|
||||
|
||||
// NewAptosSDK creates a new AptosSDK.
|
||||
func NewAptosSDK(url string, rl ratelimit.Limiter) *AptosSDK {
|
||||
return &AptosSDK{
|
||||
rl: rl,
|
||||
client: resty.New().SetBaseURL(url),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AptosSDK) GetLatestBlock(ctx context.Context) (uint64, error) {
|
||||
s.rl.Take()
|
||||
resp, err := s.client.R().
|
||||
SetContext(ctx).
|
||||
SetResult(&GetLatestBlock{}).
|
||||
Get("v1")
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if resp.IsError() {
|
||||
return 0, fmt.Errorf("status code: %s. %s", resp.Status(), string(resp.Body()))
|
||||
}
|
||||
|
||||
result := resp.Result().(*GetLatestBlock)
|
||||
if result == nil {
|
||||
return 0, fmt.Errorf("empty response")
|
||||
}
|
||||
if result.BlockHeight == "" {
|
||||
return 0, fmt.Errorf("empty block height")
|
||||
}
|
||||
return strconv.ParseUint(result.BlockHeight, 10, 64)
|
||||
}
|
||||
|
||||
func (s *AptosSDK) GetBlock(ctx context.Context, block uint64) (*GetBlockResult, error) {
|
||||
s.rl.Take()
|
||||
resp, err := s.client.R().
|
||||
SetContext(ctx).
|
||||
SetResult(&GetBlockResult{}).
|
||||
SetQueryParam("with_transactions", "true").
|
||||
Get(fmt.Sprintf("v1/blocks/by_height/%d", block))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.IsError() {
|
||||
if resp.StatusCode() == http.StatusTooManyRequests {
|
||||
return nil, ErrTooManyRequests
|
||||
}
|
||||
return nil, fmt.Errorf("status code: %s. %s", resp.Status(), string(resp.Body()))
|
||||
}
|
||||
|
||||
return resp.Result().(*GetBlockResult), nil
|
||||
}
|
|
@ -0,0 +1,234 @@
|
|||
package watcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/avast/retry-go"
|
||||
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/internal/aptos"
|
||||
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/storage"
|
||||
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const CompleteTransferMethod = "complete_transfer::submit_vaa_and_register_entry"
|
||||
|
||||
const aptosMaxRetries = 10
|
||||
const aptosRetryDelay = 5 * time.Second
|
||||
|
||||
type AptosParams struct {
|
||||
Blockchain string
|
||||
ContractAddress string
|
||||
SizeBlocks uint8
|
||||
WaitSeconds uint16
|
||||
InitialBlock int64
|
||||
}
|
||||
|
||||
type AptosWatcher struct {
|
||||
client *aptos.AptosSDK
|
||||
chainID vaa.ChainID
|
||||
blockchain string
|
||||
contractAddress string
|
||||
sizeBlocks uint8
|
||||
waitSeconds uint16
|
||||
initialBlock int64
|
||||
repository *storage.Repository
|
||||
logger *zap.Logger
|
||||
close chan bool
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
func NewAptosWatcher(client *aptos.AptosSDK, params AptosParams, repo *storage.Repository, logger *zap.Logger) *AptosWatcher {
|
||||
chainID := vaa.ChainIDAptos
|
||||
return &AptosWatcher{
|
||||
client: client,
|
||||
chainID: chainID,
|
||||
blockchain: params.Blockchain,
|
||||
contractAddress: params.ContractAddress,
|
||||
sizeBlocks: params.SizeBlocks,
|
||||
waitSeconds: params.WaitSeconds,
|
||||
initialBlock: params.InitialBlock,
|
||||
repository: repo,
|
||||
logger: logger.With(zap.String("blockchain", params.Blockchain), zap.Uint16("chainId", uint16(chainID))),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *AptosWatcher) Start(ctx context.Context) error {
|
||||
// get the current block for the chain.
|
||||
cBlock, err := w.repository.GetCurrentBlock(ctx, w.blockchain, w.initialBlock)
|
||||
if err != nil {
|
||||
w.logger.Error("cannot get current block", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
currentBlock := uint64(cBlock)
|
||||
w.wg.Add(1)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
w.logger.Info("clossing watcher by context")
|
||||
w.wg.Done()
|
||||
return nil
|
||||
case <-w.close:
|
||||
w.logger.Info("clossing watcher")
|
||||
w.wg.Done()
|
||||
return nil
|
||||
default:
|
||||
// get the latest block for the chain.
|
||||
lastBlock, err := w.client.GetLatestBlock(ctx)
|
||||
if err != nil {
|
||||
w.logger.Error("cannot get latest block", zap.Error(err))
|
||||
}
|
||||
maxBlocks := uint64(w.sizeBlocks)
|
||||
w.logger.Info("current block", zap.Uint64("current", currentBlock), zap.Uint64("last", lastBlock))
|
||||
if currentBlock < lastBlock {
|
||||
totalBlocks := (lastBlock-currentBlock)/maxBlocks + 1
|
||||
for i := 0; i < int(totalBlocks); i++ {
|
||||
fromBlock := currentBlock + uint64(i)*maxBlocks
|
||||
toBlock := fromBlock + maxBlocks - 1
|
||||
if toBlock > lastBlock {
|
||||
toBlock = lastBlock
|
||||
}
|
||||
w.logger.Info("processing blocks", zap.Uint64("from", fromBlock), zap.Uint64("to", toBlock))
|
||||
w.processBlock(ctx, fromBlock, toBlock)
|
||||
w.logger.Info("blocks processed", zap.Uint64("from", fromBlock), zap.Uint64("to", toBlock))
|
||||
}
|
||||
// process all the blocks between current and last block.
|
||||
} else {
|
||||
w.logger.Info("waiting for new blocks")
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
w.wg.Done()
|
||||
return nil
|
||||
case <-time.After(time.Duration(w.waitSeconds) * time.Second):
|
||||
}
|
||||
}
|
||||
currentBlock = lastBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *AptosWatcher) Close() {
|
||||
close(w.close)
|
||||
w.wg.Wait()
|
||||
}
|
||||
|
||||
func (w *AptosWatcher) processBlock(ctx context.Context, fromBlock uint64, toBlock uint64) {
|
||||
|
||||
for block := fromBlock; block <= toBlock; block++ {
|
||||
w.logger.Debug("processing block", zap.Uint64("block", block))
|
||||
retry.Do(
|
||||
func() error {
|
||||
// get the transactions for the block.
|
||||
result, err := w.client.GetBlock(ctx, block)
|
||||
if err != nil {
|
||||
w.logger.Error("cannot get block", zap.Uint64("block", block), zap.Error(err))
|
||||
if err == aptos.ErrTooManyRequests {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
blockTime, err := result.GetBlockTime()
|
||||
if err != nil {
|
||||
w.logger.Warn("cannot get block time", zap.Uint64("block", block), zap.Error(err))
|
||||
}
|
||||
|
||||
for _, tx := range result.Transactions {
|
||||
w.processTransaction(ctx, tx, block, blockTime)
|
||||
}
|
||||
// update the last block number processed in the database.
|
||||
watcherBlock := storage.WatcherBlock{
|
||||
ID: w.blockchain,
|
||||
BlockNumber: int64(block),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
return w.repository.UpdateWatcherBlock(ctx, watcherBlock)
|
||||
},
|
||||
retry.Attempts(aptosMaxRetries),
|
||||
retry.Delay(aptosRetryDelay),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *AptosWatcher) processTransaction(ctx context.Context, tx aptos.Transaction, block uint64, blockTime *time.Time) {
|
||||
|
||||
found, method := w.isTokenBridgeFunction(tx.Payload.Function)
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
log := w.logger.With(
|
||||
zap.String("txHash", tx.Hash),
|
||||
zap.String("txVersion", tx.Version),
|
||||
zap.String("function", tx.Payload.Function),
|
||||
zap.Uint64("block", block))
|
||||
|
||||
if method != CompleteTransferMethod {
|
||||
log.Warn("unkown method", zap.String("method", method))
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug("found Wormhole transaction")
|
||||
|
||||
if len(tx.Payload.Arguments) != 1 {
|
||||
log.Error("invalid number of arguments",
|
||||
zap.Int("arguments", len(tx.Payload.Arguments)))
|
||||
return
|
||||
}
|
||||
|
||||
switch tx.Payload.Arguments[0].(type) {
|
||||
case string:
|
||||
default:
|
||||
log.Error("invalid type of argument")
|
||||
return
|
||||
}
|
||||
|
||||
vaaArg := tx.Payload.Arguments[0].(string)
|
||||
data, err := hex.DecodeString(strings.TrimPrefix(vaaArg, "0x"))
|
||||
if err != nil {
|
||||
log.Error("invalid vaa argument",
|
||||
zap.String("argument", vaaArg),
|
||||
zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
result, err := vaa.Unmarshal(data)
|
||||
if err != nil {
|
||||
log.Error("invalid vaa",
|
||||
zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
updatedAt := time.Now()
|
||||
globalTx := storage.TransactionUpdate{
|
||||
ID: result.MessageID(),
|
||||
Destination: storage.DestinationTx{
|
||||
ChainID: w.chainID,
|
||||
Status: TxStatusConfirmed,
|
||||
Method: method,
|
||||
TxHash: tx.Hash,
|
||||
BlockNumber: strconv.FormatUint(block, 10),
|
||||
Timestamp: blockTime,
|
||||
UpdatedAt: &updatedAt,
|
||||
},
|
||||
}
|
||||
err = w.repository.UpsertGlobalTransaction(ctx, globalTx)
|
||||
if err != nil {
|
||||
log.Error("cannot save redeemed tx", zap.Error(err))
|
||||
} else {
|
||||
log.Info("saved redeemed tx", zap.String("vaa", result.MessageID()))
|
||||
}
|
||||
}
|
||||
|
||||
func (w *AptosWatcher) isTokenBridgeFunction(fn string) (bool, string) {
|
||||
prefixFunction := fmt.Sprintf("%s::", w.contractAddress)
|
||||
if !strings.HasPrefix(fn, prefixFunction) {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
return true, strings.TrimPrefix(fn, prefixFunction)
|
||||
}
|
|
@ -72,6 +72,11 @@ spec:
|
|||
secretKeyRef:
|
||||
name: blockchain
|
||||
key: terra-url
|
||||
- name: APTOS_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: blockchain
|
||||
key: aptos-url
|
||||
resources:
|
||||
limits:
|
||||
memory: {{ .RESOURCES_LIMITS_MEMORY }}
|
||||
|
|
|
@ -12,4 +12,5 @@ PPROF_ENABLED=false
|
|||
ANKR_URL=
|
||||
SOLANA_URL=
|
||||
TERRA_URL=
|
||||
APTOS_URL=
|
||||
|
||||
|
|
|
@ -12,3 +12,4 @@ PPROF_ENABLED=true
|
|||
ANKR_URL=
|
||||
SOLANA_URL=
|
||||
TERRA_URL=
|
||||
APTOS_URL=
|
|
@ -12,3 +12,4 @@ PPROF_ENABLED=false
|
|||
ANKR_URL=
|
||||
SOLANA_URL=
|
||||
TERRA_URL=
|
||||
APTOS_URL=
|
|
@ -8,4 +8,5 @@ data:
|
|||
ankr-url: {{ .ANKR_URL | b64enc }}
|
||||
solana-url: {{ .SOLANA_URL | b64enc }}
|
||||
terra-url: {{ .TERRA_URL | b64enc }}
|
||||
aptos-url: {{ .APTOS_URL | b64enc }}
|
||||
type: Opaque
|
||||
|
|
Loading…
Reference in New Issue