Feature/handle governor fly event processor (#1436)
* changes in fly component to send governor status events * split processor in vaaProcessor and governor processor * add governor processsor v1 * Add endpoint in tx-tracker to calculate txHash for a vaa id * fly-event-processor integration with new tx-tracker endpoint and refactor * Add governor vaas endpoint in api * api, fix amount data type in governor vaas endpoint * fly-event-processor normalize emitter and txHash * fly-event-processor fix nodeGovernorVaa id * fly-event-processor control array not empty in insert/delete many operation * add index in nodeGovernorVaas collection by vaaId * add prometheus metrics * add tx-tracker url for fly-event-processor deployment * Add sns attributes into sns messages Co-authored-by: walker-16 <agpazos85@gmail.com> * fix governor vaa endpoint empty response --------- Co-authored-by: Fernando Torres <fert1335@gmail.com>
This commit is contained in:
parent
8628d1f915
commit
7545acb77e
|
@ -563,6 +563,29 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/governor/vaas": {
|
||||||
|
"get": {
|
||||||
|
"description": "Returns all vaas in Governor.",
|
||||||
|
"tags": [
|
||||||
|
"wormholescan"
|
||||||
|
],
|
||||||
|
"operationId": "governor-vaas",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response-array_governor_GovernorVaasResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/health": {
|
"/api/v1/health": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Health check",
|
"description": "Health check",
|
||||||
|
@ -2431,6 +2454,35 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"governor.GovernorVaasResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"amount": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"chainId": {
|
||||||
|
"$ref": "#/definitions/vaa.ChainID"
|
||||||
|
},
|
||||||
|
"emitterAddress": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"releaseTime": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"sequence": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"txHash": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"vaaId": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"governor.MaxNotionalAvailableRecord": {
|
"governor.MaxNotionalAvailableRecord": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -3156,6 +3208,20 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"response.Response-array_governor_GovernorVaasResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/governor.GovernorVaasResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pagination": {
|
||||||
|
"$ref": "#/definitions/response.ResponsePagination"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"response.Response-array_governor_NotionalAvailable": {
|
"response.Response-array_governor_NotionalAvailable": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
@ -556,6 +556,29 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/governor/vaas": {
|
||||||
|
"get": {
|
||||||
|
"description": "Returns all vaas in Governor.",
|
||||||
|
"tags": [
|
||||||
|
"wormholescan"
|
||||||
|
],
|
||||||
|
"operationId": "governor-vaas",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response-array_governor_GovernorVaasResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/health": {
|
"/api/v1/health": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Health check",
|
"description": "Health check",
|
||||||
|
@ -2424,6 +2447,35 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"governor.GovernorVaasResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"amount": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"chainId": {
|
||||||
|
"$ref": "#/definitions/vaa.ChainID"
|
||||||
|
},
|
||||||
|
"emitterAddress": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"releaseTime": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"sequence": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"txHash": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"vaaId": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"governor.MaxNotionalAvailableRecord": {
|
"governor.MaxNotionalAvailableRecord": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -3149,6 +3201,20 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"response.Response-array_governor_GovernorVaasResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/governor.GovernorVaasResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pagination": {
|
||||||
|
"$ref": "#/definitions/response.ResponsePagination"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"response.Response-array_governor_NotionalAvailable": {
|
"response.Response-array_governor_NotionalAvailable": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
@ -199,6 +199,25 @@ definitions:
|
||||||
notionalLimit:
|
notionalLimit:
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
|
governor.GovernorVaasResponse:
|
||||||
|
properties:
|
||||||
|
amount:
|
||||||
|
type: integer
|
||||||
|
chainId:
|
||||||
|
$ref: '#/definitions/vaa.ChainID'
|
||||||
|
emitterAddress:
|
||||||
|
type: string
|
||||||
|
releaseTime:
|
||||||
|
type: string
|
||||||
|
sequence:
|
||||||
|
type: string
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
txHash:
|
||||||
|
type: string
|
||||||
|
vaaId:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
governor.MaxNotionalAvailableRecord:
|
governor.MaxNotionalAvailableRecord:
|
||||||
properties:
|
properties:
|
||||||
availableNotional:
|
availableNotional:
|
||||||
|
@ -672,6 +691,15 @@ definitions:
|
||||||
pagination:
|
pagination:
|
||||||
$ref: '#/definitions/response.ResponsePagination'
|
$ref: '#/definitions/response.ResponsePagination'
|
||||||
type: object
|
type: object
|
||||||
|
response.Response-array_governor_GovernorVaasResponse:
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/governor.GovernorVaasResponse'
|
||||||
|
type: array
|
||||||
|
pagination:
|
||||||
|
$ref: '#/definitions/response.ResponsePagination'
|
||||||
|
type: object
|
||||||
response.Response-array_governor_NotionalAvailable:
|
response.Response-array_governor_NotionalAvailable:
|
||||||
properties:
|
properties:
|
||||||
data:
|
data:
|
||||||
|
@ -1546,6 +1574,21 @@ paths:
|
||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
tags:
|
tags:
|
||||||
- wormholescan
|
- wormholescan
|
||||||
|
/api/v1/governor/vaas:
|
||||||
|
get:
|
||||||
|
description: Returns all vaas in Governor.
|
||||||
|
operationId: governor-vaas
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Response-array_governor_GovernorVaasResponse'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
tags:
|
||||||
|
- wormholescan
|
||||||
/api/v1/health:
|
/api/v1/health:
|
||||||
get:
|
get:
|
||||||
description: Health check
|
description: Health check
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
errs "github.com/wormhole-foundation/wormhole-explorer/api/internal/errors"
|
errs "github.com/wormhole-foundation/wormhole-explorer/api/internal/errors"
|
||||||
|
mongoTypes "github.com/wormhole-foundation/wormhole-explorer/api/internal/mongo"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/api/internal/pagination"
|
"github.com/wormhole-foundation/wormhole-explorer/api/internal/pagination"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/common/types"
|
"github.com/wormhole-foundation/wormhole-explorer/common/types"
|
||||||
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||||
|
@ -27,6 +28,7 @@ type Repository struct {
|
||||||
collections struct {
|
collections struct {
|
||||||
governorConfig *mongo.Collection
|
governorConfig *mongo.Collection
|
||||||
governorStatus *mongo.Collection
|
governorStatus *mongo.Collection
|
||||||
|
governorVaas *mongo.Collection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,9 +39,11 @@ func NewRepository(db *mongo.Database, logger *zap.Logger) *Repository {
|
||||||
collections: struct {
|
collections: struct {
|
||||||
governorConfig *mongo.Collection
|
governorConfig *mongo.Collection
|
||||||
governorStatus *mongo.Collection
|
governorStatus *mongo.Collection
|
||||||
|
governorVaas *mongo.Collection
|
||||||
}{
|
}{
|
||||||
governorConfig: db.Collection("governorConfig"),
|
governorConfig: db.Collection("governorConfig"),
|
||||||
governorStatus: db.Collection("governorStatus"),
|
governorStatus: db.Collection("governorStatus"),
|
||||||
|
governorVaas: db.Collection("governorVaas"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1725,3 +1729,46 @@ func (r *Repository) IsVaaEnqueued(
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GovernorVaaDoc struct {
|
||||||
|
ID string `bson:"_id"`
|
||||||
|
ChainID vaa.ChainID `bson:"chainId"`
|
||||||
|
EmitterAddress string `bson:"emitterAddress"`
|
||||||
|
Sequence string `bson:"sequence"`
|
||||||
|
TxHash string `bson:"txHash"`
|
||||||
|
ReleaseTime time.Time `bson:"releaseTime"`
|
||||||
|
Amount mongoTypes.Uint64 `bson:"amount"`
|
||||||
|
Vaas []any `bson:"vaas"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) GetGovernorVaas(ctx context.Context) ([]GovernorVaaDoc, error) {
|
||||||
|
// left outer join on the `vaas` collection
|
||||||
|
pipeline := []bson.D{{{Key: "$lookup", Value: bson.D{
|
||||||
|
{Key: "from", Value: "vaas"},
|
||||||
|
{Key: "localField", Value: "_id"},
|
||||||
|
{Key: "foreignField", Value: "_id"},
|
||||||
|
{Key: "as", Value: "vaas"},
|
||||||
|
}}},
|
||||||
|
}
|
||||||
|
|
||||||
|
cur, err := r.collections.governorVaas.Aggregate(ctx, pipeline)
|
||||||
|
if err != nil {
|
||||||
|
requestID := fmt.Sprintf("%v", ctx.Value("requestid"))
|
||||||
|
r.logger.Error("failed execute aggregate command to get governor enqueded vaas",
|
||||||
|
zap.Error(err), zap.String("requestID", requestID))
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// read results from cursor
|
||||||
|
var result []GovernorVaaDoc
|
||||||
|
err = cur.All(ctx, &result)
|
||||||
|
if err != nil {
|
||||||
|
requestID := fmt.Sprintf("%v", ctx.Value("requestid"))
|
||||||
|
r.logger.Error("failed decoding cursor to []*VaaDoc",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("requestID", requestID),
|
||||||
|
)
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
|
@ -228,3 +228,13 @@ func (s *Service) IsVaaEnqueued(ctx context.Context, chainID vaa.ChainID, emitte
|
||||||
isEnqueued, err := s.repo.IsVaaEnqueued(ctx, chainID, emitter, seq)
|
isEnqueued, err := s.repo.IsVaaEnqueued(ctx, chainID, emitter, seq)
|
||||||
return isEnqueued, err
|
return isEnqueued, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetGovernorVaas get enqueued vaas.
|
||||||
|
// Guardian api migration.
|
||||||
|
func (s *Service) GetGovernorVaas(ctx context.Context) ([]GovernorVaaDoc, error) {
|
||||||
|
result, err := s.repo.GetGovernorVaas(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
|
@ -399,3 +399,38 @@ func (c *Controller) GetEnqueuedVaasByChainID(ctx *fiber.Ctx) error {
|
||||||
|
|
||||||
return ctx.JSON(enqueuedVaas)
|
return ctx.JSON(enqueuedVaas)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetGovernorVaas godoc
|
||||||
|
// @Description Returns all vaas in Governor.
|
||||||
|
// @Tags wormholescan
|
||||||
|
// @ID governor-vaas
|
||||||
|
// @Success 200 {object} response.Response[[]governor.GovernorVaasResponse]
|
||||||
|
// @Failure 400
|
||||||
|
// @Failure 500
|
||||||
|
// @Router /api/v1/governor/vaas [get]
|
||||||
|
func (c *Controller) GetGovernorVaas(ctx *fiber.Ctx) error {
|
||||||
|
enqueuedVaas, err := c.srv.GetGovernorVaas(ctx.Context())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]GovernorVaasResponse, 0)
|
||||||
|
for _, v := range enqueuedVaas {
|
||||||
|
status := "pending"
|
||||||
|
if len(v.Vaas) > 0 {
|
||||||
|
status = "issued"
|
||||||
|
}
|
||||||
|
result = append(result, GovernorVaasResponse{
|
||||||
|
VaaID: v.ID,
|
||||||
|
ChainID: v.ChainID,
|
||||||
|
EmitterAddress: v.EmitterAddress,
|
||||||
|
Sequence: v.Sequence,
|
||||||
|
TxHash: v.TxHash,
|
||||||
|
ReleaseTime: v.ReleaseTime,
|
||||||
|
Amount: uint64(v.Amount),
|
||||||
|
Status: status,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.JSON(result)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package governor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GovernorVaasResponse struct {
|
||||||
|
VaaID string `json:"vaaId"`
|
||||||
|
ChainID vaa.ChainID `json:"chainId"`
|
||||||
|
EmitterAddress string `json:"emitterAddress"`
|
||||||
|
Sequence string `json:"sequence"`
|
||||||
|
TxHash string `json:"txHash"`
|
||||||
|
ReleaseTime time.Time `json:"releaseTime"`
|
||||||
|
Amount uint64 `json:"amount"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
|
@ -144,6 +144,7 @@ func RegisterRoutes(
|
||||||
enqueueVaas := governor.Group("/enqueued_vaas")
|
enqueueVaas := governor.Group("/enqueued_vaas")
|
||||||
enqueueVaas.Get("/", governorCtrl.GetEnqueuedVaas)
|
enqueueVaas.Get("/", governorCtrl.GetEnqueuedVaas)
|
||||||
enqueueVaas.Get("/:chain", governorCtrl.GetEnqueuedVaasByChainID)
|
enqueueVaas.Get("/:chain", governorCtrl.GetEnqueuedVaasByChainID)
|
||||||
|
governor.Get("/vaas", governorCtrl.GetGovernorVaas)
|
||||||
|
|
||||||
relays := api.Group("/relays")
|
relays := api.Group("/relays")
|
||||||
relays.Get("/:chain/:emitter/:sequence", relaysCtrl.FindOne)
|
relays.Get("/:chain/:emitter/:sequence", relaysCtrl.FindOne)
|
||||||
|
|
|
@ -15,6 +15,7 @@ const DefaultTimeout = 30
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrCallEndpoint = errors.New("ERROR CALL ENPOINT")
|
ErrCallEndpoint = errors.New("ERROR CALL ENPOINT")
|
||||||
|
ErrBadRequest = errors.New("BAD REQUEST")
|
||||||
ErrInternalError = errors.New("INTERNAL ERROR")
|
ErrInternalError = errors.New("INTERNAL ERROR")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -84,3 +85,55 @@ func (c *TxTrackerAPIClient) Process(vaaID string) (*ProcessVaaResponse, error)
|
||||||
return nil, ErrInternalError
|
return nil, ErrInternalError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateTxHashFunc represent a create tx hash function.
|
||||||
|
type CreateTxHashFunc func(vaaID, txHash string) (*TxHashResponse, error)
|
||||||
|
|
||||||
|
// TxHashResponse represent a create tx hash response.
|
||||||
|
type TxHashResponse struct {
|
||||||
|
NativeTxHash string `json:"nativeTxHash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTxHash create tx hash.
|
||||||
|
func (c *TxTrackerAPIClient) CreateTxHash(vaaID, txHash string) (*TxHashResponse, error) {
|
||||||
|
endpoint := fmt.Sprintf("%s/vaa/tx-hash", c.BaseURL)
|
||||||
|
|
||||||
|
// create request body.
|
||||||
|
payload := struct {
|
||||||
|
VaaID string `json:"id"`
|
||||||
|
TxHash string `json:"txHash"`
|
||||||
|
}{
|
||||||
|
VaaID: vaaID,
|
||||||
|
TxHash: txHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
c.Logger.Error("error marshalling payload", zap.Error(err), zap.String("vaaID", vaaID), zap.String("txHash", txHash))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := c.Client.Post(endpoint, "application/json", bytes.NewBuffer(body))
|
||||||
|
if err != nil {
|
||||||
|
c.Logger.Error("error call create tx hash endpoint",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("vaaID", vaaID),
|
||||||
|
zap.String("txHash", txHash))
|
||||||
|
return nil, ErrCallEndpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
defer response.Body.Close()
|
||||||
|
switch response.StatusCode {
|
||||||
|
case http.StatusOK:
|
||||||
|
var txHashResponse TxHashResponse
|
||||||
|
json.NewDecoder(response.Body).Decode(&txHashResponse)
|
||||||
|
return &txHashResponse, nil
|
||||||
|
case http.StatusBadRequest:
|
||||||
|
return nil, ErrBadRequest
|
||||||
|
case http.StatusInternalServerError:
|
||||||
|
return nil, ErrInternalError
|
||||||
|
default:
|
||||||
|
return nil, ErrInternalError
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -6,4 +6,6 @@ const (
|
||||||
Vaas = "vaas"
|
Vaas = "vaas"
|
||||||
DuplicateVaas = "duplicateVaas"
|
DuplicateVaas = "duplicateVaas"
|
||||||
GuardianSets = "guardianSets"
|
GuardianSets = "guardianSets"
|
||||||
|
NodeGovernorVaas = "nodeGovernorVaas"
|
||||||
|
GovernorVaas = "governorVaas"
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,3 +7,4 @@ metadata:
|
||||||
data:
|
data:
|
||||||
aws-region: {{ .SQS_AWS_REGION }}
|
aws-region: {{ .SQS_AWS_REGION }}
|
||||||
duplicate-vaa-sqs-url: {{ .DUPLICATE_VAA_SQS_URL }}
|
duplicate-vaa-sqs-url: {{ .DUPLICATE_VAA_SQS_URL }}
|
||||||
|
governor-sqs-url: {{ .GOVERNOR_SQS_URL }}
|
||||||
|
|
|
@ -15,3 +15,5 @@ AWS_IAM_ROLE=
|
||||||
ALERT_ENABLED=false
|
ALERT_ENABLED=false
|
||||||
METRICS_ENABLED=true
|
METRICS_ENABLED=true
|
||||||
CONSUMER_WORKER_SIZE=1
|
CONSUMER_WORKER_SIZE=1
|
||||||
|
TX_TRACKER_URL=http://wormscan-tx-tracker.wormscan/api
|
||||||
|
TX_TRACKER_TIMEOUT=30
|
||||||
|
|
|
@ -15,3 +15,5 @@ AWS_IAM_ROLE=
|
||||||
ALERT_ENABLED=false
|
ALERT_ENABLED=false
|
||||||
METRICS_ENABLED=true
|
METRICS_ENABLED=true
|
||||||
CONSUMER_WORKER_SIZE=1
|
CONSUMER_WORKER_SIZE=1
|
||||||
|
TX_TRACKER_URL=http://wormscan-tx-tracker.wormscan-testnet/api
|
||||||
|
TX_TRACKER_TIMEOUT=30
|
||||||
|
|
|
@ -15,3 +15,5 @@ AWS_IAM_ROLE=
|
||||||
ALERT_ENABLED=false
|
ALERT_ENABLED=false
|
||||||
METRICS_ENABLED=true
|
METRICS_ENABLED=true
|
||||||
CONSUMER_WORKER_SIZE=1
|
CONSUMER_WORKER_SIZE=1
|
||||||
|
TX_TRACKER_URL=http://wormscan-tx-tracker.wormscan/api
|
||||||
|
TX_TRACKER_TIMEOUT=30
|
||||||
|
|
|
@ -15,3 +15,5 @@ AWS_IAM_ROLE=
|
||||||
ALERT_ENABLED=false
|
ALERT_ENABLED=false
|
||||||
METRICS_ENABLED=true
|
METRICS_ENABLED=true
|
||||||
CONSUMER_WORKER_SIZE=1
|
CONSUMER_WORKER_SIZE=1
|
||||||
|
TX_TRACKER_URL=http://wormscan-tx-tracker.wormscan-testnet/api
|
||||||
|
TX_TRACKER_TIMEOUT=30
|
||||||
|
|
|
@ -64,6 +64,11 @@ spec:
|
||||||
configMapKeyRef:
|
configMapKeyRef:
|
||||||
name: fly-event-processor
|
name: fly-event-processor
|
||||||
key: duplicate-vaa-sqs-url
|
key: duplicate-vaa-sqs-url
|
||||||
|
- name: GOVERNOR_SQS_URL
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: fly-event-processor
|
||||||
|
key: governor-sqs-url
|
||||||
- name: AWS_REGION
|
- name: AWS_REGION
|
||||||
valueFrom:
|
valueFrom:
|
||||||
configMapKeyRef:
|
configMapKeyRef:
|
||||||
|
@ -86,6 +91,10 @@ spec:
|
||||||
value: "{{ .CONSUMER_WORKER_SIZE }}"
|
value: "{{ .CONSUMER_WORKER_SIZE }}"
|
||||||
- name: GUARDIAN_API_PROVIDER_PATH
|
- name: GUARDIAN_API_PROVIDER_PATH
|
||||||
value: "/opt/fly-event-processor/guardian-provider.json"
|
value: "/opt/fly-event-processor/guardian-provider.json"
|
||||||
|
- name: TX_TRACKER_URL
|
||||||
|
value: "{{ .TX_TRACKER_URL }}"
|
||||||
|
- name: TX_TRACKER_TIMEOUT
|
||||||
|
value: "{{ .TX_TRACKER_TIMEOUT }}"
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
memory: {{ .RESOURCES_LIMITS_MEMORY }}
|
memory: {{ .RESOURCES_LIMITS_MEMORY }}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package service
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
@ -17,16 +18,22 @@ import (
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/common/health"
|
"github.com/wormhole-foundation/wormhole-explorer/common/health"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/common/logger"
|
"github.com/wormhole-foundation/wormhole-explorer/common/logger"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/common/pool"
|
"github.com/wormhole-foundation/wormhole-explorer/common/pool"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/http/vaa"
|
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/processor"
|
governorConsumer "github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/consumer/governor"
|
||||||
|
vaaConsumer "github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/consumer/vaa"
|
||||||
|
governorProcessor "github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/processor/governor"
|
||||||
|
vaaprocessor "github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/processor/vaa"
|
||||||
|
|
||||||
|
txTracker "github.com/wormhole-foundation/wormhole-explorer/common/client/txtracker"
|
||||||
|
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/queue"
|
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/queue"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/storage"
|
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/storage"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/config"
|
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/config"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/consumer"
|
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/http/infrastructure"
|
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/http/infrastructure"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/http/vaa"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/internal/metrics"
|
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/internal/metrics"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -61,23 +68,35 @@ func Run() {
|
||||||
// create a new repository
|
// create a new repository
|
||||||
repository := storage.NewRepository(logger, db.Database)
|
repository := storage.NewRepository(logger, db.Database)
|
||||||
|
|
||||||
|
//TxTracker createTxHash client
|
||||||
|
createTxHashFunc, err := newCreateTxHashFunc(cfg, logger)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal("failed to initialize VAA parser", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
// create a new processor
|
// create a new processor
|
||||||
processor := processor.NewProcessor(guardianApiProviderPool, repository, logger, metrics)
|
dupVaaProcessor := vaaprocessor.NewProcessor(guardianApiProviderPool, repository, logger, metrics)
|
||||||
|
governorProcessor := governorProcessor.NewProcessor(repository, createTxHashFunc, logger, metrics)
|
||||||
|
|
||||||
// start serving /health and /ready endpoints
|
// start serving /health and /ready endpoints
|
||||||
healthChecks, err := makeHealthChecks(rootCtx, cfg, db.Database)
|
healthChecks, err := makeHealthChecks(rootCtx, cfg, db.Database)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatal("Failed to create health checks", zap.Error(err))
|
logger.Fatal("Failed to create health checks", zap.Error(err))
|
||||||
}
|
}
|
||||||
vaaCtrl := vaa.NewController(processor.Process, repository, logger)
|
vaaCtrl := vaa.NewController(dupVaaProcessor.Process, repository, logger)
|
||||||
server := infrastructure.NewServer(logger, cfg.Port, vaaCtrl, cfg.PprofEnabled, healthChecks...)
|
server := infrastructure.NewServer(logger, cfg.Port, vaaCtrl, cfg.PprofEnabled, healthChecks...)
|
||||||
server.Start()
|
server.Start()
|
||||||
|
|
||||||
// create and start a duplicate VAA consumer.
|
// create and start a duplicate VAA consumer.
|
||||||
duplicateVaaConsumeFunc := newDuplicateVaaConsumeFunc(rootCtx, cfg, metrics, logger)
|
duplicateVaaConsumeFunc := newDuplicateVaaConsumeFunc(rootCtx, cfg, metrics, logger)
|
||||||
duplicateVaa := consumer.New(duplicateVaaConsumeFunc, processor.Process, logger, metrics, cfg.P2pNetwork, cfg.ConsumerWorkerSize)
|
duplicateVaa := vaaConsumer.New(duplicateVaaConsumeFunc, dupVaaProcessor.Process, logger, metrics, cfg.P2pNetwork, cfg.ConsumerWorkerSize)
|
||||||
duplicateVaa.Start(rootCtx)
|
duplicateVaa.Start(rootCtx)
|
||||||
|
|
||||||
|
// create and start a governor status consumer.
|
||||||
|
governorStatusConsumerFunc := newGovernorStatusConsumeFunc(rootCtx, cfg, metrics, logger)
|
||||||
|
governorStatus := governorConsumer.New(governorStatusConsumerFunc, governorProcessor.Process, logger, metrics, cfg.P2pNetwork, cfg.GovernorConsumerWorkerSize)
|
||||||
|
governorStatus.Start(rootCtx)
|
||||||
|
|
||||||
logger.Info("Started wormholescan-fly-event-processor")
|
logger.Info("Started wormholescan-fly-event-processor")
|
||||||
|
|
||||||
// Waiting for signal
|
// Waiting for signal
|
||||||
|
@ -203,13 +222,49 @@ func newDuplicateVaaConsumeFunc(
|
||||||
cfg *config.ServiceConfiguration,
|
cfg *config.ServiceConfiguration,
|
||||||
metrics metrics.Metrics,
|
metrics metrics.Metrics,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
) queue.ConsumeFunc {
|
) queue.ConsumeFunc[queue.EventDuplicateVaa] {
|
||||||
|
|
||||||
sqsConsumer, err := newSqsConsumer(ctx, cfg, cfg.DuplicateVaaSQSUrl)
|
sqsConsumer, err := newSqsConsumer(ctx, cfg, cfg.DuplicateVaaSQSUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatal("failed to create sqs consumer", zap.Error(err))
|
logger.Fatal("failed to create sqs consumer", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
vaaQueue := queue.NewEventSqs(sqsConsumer, metrics, logger)
|
vaaQueue := queue.NewEventSqs[queue.EventDuplicateVaa](sqsConsumer,
|
||||||
|
metrics.IncDuplicatedVaaConsumedQueue, logger)
|
||||||
return vaaQueue.Consume
|
return vaaQueue.Consume
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newGovernorStatusConsumeFunc(
|
||||||
|
ctx context.Context,
|
||||||
|
cfg *config.ServiceConfiguration,
|
||||||
|
metrics metrics.Metrics,
|
||||||
|
logger *zap.Logger,
|
||||||
|
) queue.ConsumeFunc[queue.EventGovernorStatus] {
|
||||||
|
|
||||||
|
sqsConsumer, err := newSqsConsumer(ctx, cfg, cfg.GovernorSQSUrl)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal("failed to create sqs consumer", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
governorStatusQueue := queue.NewEventSqs[queue.EventGovernorStatus](sqsConsumer,
|
||||||
|
metrics.IncGovernorStatusConsumedQueue, logger)
|
||||||
|
return governorStatusQueue.Consume
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCreateTxHashFunc(
|
||||||
|
cfg *config.ServiceConfiguration,
|
||||||
|
logger *zap.Logger,
|
||||||
|
) (txTracker.CreateTxHashFunc, error) {
|
||||||
|
if cfg.Environment == config.EnvironmentLocal {
|
||||||
|
return func(vaaID, txHash string) (*txTracker.TxHashResponse, error) {
|
||||||
|
return &txTracker.TxHashResponse{
|
||||||
|
NativeTxHash: txHash,
|
||||||
|
}, nil
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
createTxHashClient, err := txTracker.NewTxTrackerAPIClient(cfg.TxTrackerTimeout, cfg.TxTrackerUrl, logger)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to initialize TxTracker client: %w", err)
|
||||||
|
}
|
||||||
|
return createTxHashClient.CreateTxHash, nil
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,10 @@ import (
|
||||||
"github.com/sethvargo/go-envconfig"
|
"github.com/sethvargo/go-envconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EnvironmentLocal = "local"
|
||||||
|
)
|
||||||
|
|
||||||
// p2p network constants.
|
// p2p network constants.
|
||||||
const (
|
const (
|
||||||
P2pMainNet = "mainnet"
|
P2pMainNet = "mainnet"
|
||||||
|
@ -30,6 +34,8 @@ type ServiceConfiguration struct {
|
||||||
MetricsEnabled bool `env:"METRICS_ENABLED,default=false"`
|
MetricsEnabled bool `env:"METRICS_ENABLED,default=false"`
|
||||||
// Fly event consumer configuration
|
// Fly event consumer configuration
|
||||||
ConsumerWorkerSize int `env:"CONSUMER_WORKER_SIZE,default=1"`
|
ConsumerWorkerSize int `env:"CONSUMER_WORKER_SIZE,default=1"`
|
||||||
|
GovernorConsumerWorkerSize int `env:"GOVERNOR_CONSUMER_WORKER_SIZE,default=1"`
|
||||||
|
|
||||||
// Database configuration
|
// Database configuration
|
||||||
MongoURI string `env:"MONGODB_URI,required"`
|
MongoURI string `env:"MONGODB_URI,required"`
|
||||||
MongoDatabase string `env:"MONGODB_DATABASE,required"`
|
MongoDatabase string `env:"MONGODB_DATABASE,required"`
|
||||||
|
@ -39,6 +45,11 @@ type ServiceConfiguration struct {
|
||||||
AwsSecretAccessKey string `env:"AWS_SECRET_ACCESS_KEY"`
|
AwsSecretAccessKey string `env:"AWS_SECRET_ACCESS_KEY"`
|
||||||
AwsRegion string `env:"AWS_REGION"`
|
AwsRegion string `env:"AWS_REGION"`
|
||||||
DuplicateVaaSQSUrl string `env:"DUPLICATE_VAA_SQS_URL"`
|
DuplicateVaaSQSUrl string `env:"DUPLICATE_VAA_SQS_URL"`
|
||||||
|
GovernorSQSUrl string `env:"GOVERNOR_SQS_URL"`
|
||||||
|
// Tx-tracker client configuration
|
||||||
|
TxTrackerUrl string `env:"TX_TRACKER_URL,required"`
|
||||||
|
TxTrackerTimeout int64 `env:"TX_TRACKER_TIMEOUT,default=10"`
|
||||||
|
|
||||||
// Guardian api provider configuration
|
// Guardian api provider configuration
|
||||||
GuardianAPIProviderPath string `env:"GUARDIAN_API_PROVIDER_PATH,required"`
|
GuardianAPIProviderPath string `env:"GUARDIAN_API_PROVIDER_PATH,required"`
|
||||||
*GuardianAPIConfigurationJson `required:"false"`
|
*GuardianAPIConfigurationJson `required:"false"`
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
package governor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/domain"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/internal/metrics"
|
||||||
|
govprocessor "github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/processor/governor"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/queue"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Consumer consumer struct definition.
|
||||||
|
type Consumer struct {
|
||||||
|
consumeFunc queue.ConsumeFunc[queue.EventGovernorStatus]
|
||||||
|
processor govprocessor.ProcessorFunc
|
||||||
|
logger *zap.Logger
|
||||||
|
metrics metrics.Metrics
|
||||||
|
p2pNetwork string
|
||||||
|
workersSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new vaa consumer.
|
||||||
|
func New(
|
||||||
|
consumeFunc queue.ConsumeFunc[queue.EventGovernorStatus],
|
||||||
|
processor govprocessor.ProcessorFunc,
|
||||||
|
logger *zap.Logger,
|
||||||
|
metrics metrics.Metrics,
|
||||||
|
p2pNetwork string,
|
||||||
|
workersSize int,
|
||||||
|
) *Consumer {
|
||||||
|
|
||||||
|
c := Consumer{
|
||||||
|
consumeFunc: consumeFunc,
|
||||||
|
processor: processor,
|
||||||
|
logger: logger,
|
||||||
|
metrics: metrics,
|
||||||
|
p2pNetwork: p2pNetwork,
|
||||||
|
workersSize: workersSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start consumes messages from VAA queue, parse and store those messages in a repository.
|
||||||
|
func (c *Consumer) Start(ctx context.Context) {
|
||||||
|
ch := c.consumeFunc(ctx)
|
||||||
|
for i := 0; i < c.workersSize; i++ {
|
||||||
|
go c.producerLoop(ctx, ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Consumer) producerLoop(ctx context.Context, ch <-chan queue.ConsumerMessage[queue.EventGovernorStatus]) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case msg := <-ch:
|
||||||
|
c.processEvent(ctx, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Consumer) processEvent(ctx context.Context, msg queue.ConsumerMessage[queue.EventGovernorStatus]) {
|
||||||
|
event := msg.Data()
|
||||||
|
|
||||||
|
// Check if the event is a governor status event.
|
||||||
|
if event.Type != queue.GovernorStatusEventType {
|
||||||
|
msg.Done()
|
||||||
|
c.logger.Debug("event is not a governor status",
|
||||||
|
zap.Any("event", event))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := c.logger.With(
|
||||||
|
zap.String("trackId", event.TrackID),
|
||||||
|
zap.String("type", event.Type),
|
||||||
|
zap.String("node", event.Data.NodeName))
|
||||||
|
|
||||||
|
if msg.IsExpired() {
|
||||||
|
msg.Failed()
|
||||||
|
logger.Debug("event is expired")
|
||||||
|
c.metrics.IncGovernorStatusExpired(event.Data.NodeName, event.Data.NodeAddress)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
params := &govprocessor.Params{
|
||||||
|
TrackID: event.TrackID,
|
||||||
|
NodeGovernorVaa: domain.ConvertEventToGovernorVaa(&event),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.processor(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
msg.Failed()
|
||||||
|
logger.Error("failed to process governor-status event", zap.Error(err))
|
||||||
|
c.metrics.IncGovernorStatusFailed(params.NodeGovernorVaa.Name, params.NodeGovernorVaa.Address)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Done()
|
||||||
|
logger.Debug("governor-status event processed")
|
||||||
|
c.metrics.IncGovernorStatusProcessed(params.NodeGovernorVaa.Name, params.NodeGovernorVaa.Address)
|
||||||
|
}
|
|
@ -1,11 +1,10 @@
|
||||||
package consumer
|
package vaa
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/common/pool"
|
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/internal/metrics"
|
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/internal/metrics"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/processor"
|
processor "github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/processor/vaa"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/queue"
|
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/queue"
|
||||||
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
@ -13,9 +12,8 @@ import (
|
||||||
|
|
||||||
// Consumer consumer struct definition.
|
// Consumer consumer struct definition.
|
||||||
type Consumer struct {
|
type Consumer struct {
|
||||||
consumeFunc queue.ConsumeFunc
|
consumeFunc queue.ConsumeFunc[queue.EventDuplicateVaa]
|
||||||
processor processor.ProcessorFunc
|
processor processor.ProcessorFunc
|
||||||
guardianPool *pool.Pool
|
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
metrics metrics.Metrics
|
metrics metrics.Metrics
|
||||||
p2pNetwork string
|
p2pNetwork string
|
||||||
|
@ -24,7 +22,7 @@ type Consumer struct {
|
||||||
|
|
||||||
// New creates a new vaa consumer.
|
// New creates a new vaa consumer.
|
||||||
func New(
|
func New(
|
||||||
consumeFunc queue.ConsumeFunc,
|
consumeFunc queue.ConsumeFunc[queue.EventDuplicateVaa],
|
||||||
processor processor.ProcessorFunc,
|
processor processor.ProcessorFunc,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
metrics metrics.Metrics,
|
metrics metrics.Metrics,
|
||||||
|
@ -52,7 +50,7 @@ func (c *Consumer) Start(ctx context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Consumer) producerLoop(ctx context.Context, ch <-chan queue.ConsumerMessage) {
|
func (c *Consumer) producerLoop(ctx context.Context, ch <-chan queue.ConsumerMessage[queue.EventDuplicateVaa]) {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
@ -63,13 +61,23 @@ func (c *Consumer) producerLoop(ctx context.Context, ch <-chan queue.ConsumerMes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Consumer) processEvent(ctx context.Context, msg queue.ConsumerMessage) {
|
func (c *Consumer) processEvent(ctx context.Context, msg queue.ConsumerMessage[queue.EventDuplicateVaa]) {
|
||||||
event := msg.Data()
|
event := msg.Data()
|
||||||
|
|
||||||
|
// Check if the event is a duplicate VAA event.
|
||||||
|
if event.Type != queue.DeduplicateVaaEventType {
|
||||||
|
msg.Done()
|
||||||
|
c.logger.Debug("event is not a duplicate VAA",
|
||||||
|
zap.Any("event", event))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
vaaID := event.Data.VaaID
|
vaaID := event.Data.VaaID
|
||||||
chainID := sdk.ChainID(event.Data.ChainID)
|
chainID := sdk.ChainID(event.Data.ChainID)
|
||||||
|
|
||||||
logger := c.logger.With(
|
logger := c.logger.With(
|
||||||
zap.String("trackId", event.TrackID),
|
zap.String("trackId", event.TrackID),
|
||||||
|
zap.String("type", event.Type),
|
||||||
zap.String("vaaId", vaaID))
|
zap.String("vaaId", vaaID))
|
||||||
|
|
||||||
if msg.IsExpired() {
|
if msg.IsExpired() {
|
|
@ -0,0 +1,79 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/common/utils"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/queue"
|
||||||
|
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Node struct {
|
||||||
|
Name string
|
||||||
|
Address string
|
||||||
|
}
|
||||||
|
|
||||||
|
type NodeGovernorVaa struct {
|
||||||
|
Node
|
||||||
|
GovernorVaas map[string]GovernorVaa
|
||||||
|
}
|
||||||
|
|
||||||
|
type GovernorVaa struct {
|
||||||
|
ID string
|
||||||
|
ChainID sdk.ChainID
|
||||||
|
EmitterAddress string
|
||||||
|
Sequence string
|
||||||
|
TxHash string
|
||||||
|
ReleaseTime time.Time
|
||||||
|
Amount uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertEventToGovernorVaa convert a event *queue.EventGovernorStatus to a *NodeGovernorVaa.
|
||||||
|
func ConvertEventToGovernorVaa(event *queue.EventGovernorStatus) *NodeGovernorVaa {
|
||||||
|
|
||||||
|
// check if event is nil.
|
||||||
|
if event == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if chains is empty.
|
||||||
|
if len(event.Data.Chains) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
governorVaas := make(map[string]GovernorVaa)
|
||||||
|
for _, chain := range event.Data.Chains {
|
||||||
|
for _, emitter := range chain.Emitters {
|
||||||
|
for _, enqueuedVAA := range emitter.EnqueuedVaas {
|
||||||
|
|
||||||
|
normalizeEmitter := utils.NormalizeHex(emitter.EmitterAddress)
|
||||||
|
normalizeTxHash := utils.NormalizeHex(enqueuedVAA.TxHash)
|
||||||
|
vaaID := fmt.Sprintf("%d/%s/%s",
|
||||||
|
chain.ChainId,
|
||||||
|
normalizeEmitter,
|
||||||
|
enqueuedVAA.Sequence)
|
||||||
|
|
||||||
|
gs := GovernorVaa{
|
||||||
|
ID: vaaID,
|
||||||
|
ChainID: sdk.ChainID(chain.ChainId),
|
||||||
|
EmitterAddress: normalizeEmitter,
|
||||||
|
Sequence: enqueuedVAA.Sequence,
|
||||||
|
TxHash: normalizeTxHash,
|
||||||
|
ReleaseTime: time.Unix(int64(enqueuedVAA.ReleaseTime), 0),
|
||||||
|
Amount: enqueuedVAA.NotionalValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
governorVaas[vaaID] = gs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &NodeGovernorVaa{
|
||||||
|
Node: Node{
|
||||||
|
Name: event.Data.NodeName,
|
||||||
|
Address: event.Data.NodeAddress,
|
||||||
|
},
|
||||||
|
GovernorVaas: governorVaas,
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/processor"
|
processor "github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/processor/vaa"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/storage"
|
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/storage"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
|
@ -24,3 +24,21 @@ func (d *DummyMetrics) IncDuplicatedVaaExpired(chainID sdk.ChainID) {}
|
||||||
|
|
||||||
// IncDuplicatedVaaCanNotFixed dummy implementation.
|
// IncDuplicatedVaaCanNotFixed dummy implementation.
|
||||||
func (d *DummyMetrics) IncDuplicatedVaaCanNotFixed(chainID sdk.ChainID) {}
|
func (d *DummyMetrics) IncDuplicatedVaaCanNotFixed(chainID sdk.ChainID) {}
|
||||||
|
|
||||||
|
// IncGovernorStatusConsumedQueue dummy implementation.
|
||||||
|
func (d *DummyMetrics) IncGovernorStatusConsumedQueue() {}
|
||||||
|
|
||||||
|
// IncGovernorStatusProcessed dummy implementation.
|
||||||
|
func (d *DummyMetrics) IncGovernorStatusProcessed(node string, address string) {}
|
||||||
|
|
||||||
|
// IncGovernorStatusFailed dummy implementation.
|
||||||
|
func (d *DummyMetrics) IncGovernorStatusFailed(node string, address string) {}
|
||||||
|
|
||||||
|
// IncGovernorStatusExpired dummy implementation.
|
||||||
|
func (d *DummyMetrics) IncGovernorStatusExpired(node string, address string) {}
|
||||||
|
|
||||||
|
// IncGovernorVaaAdded dummy implementation.
|
||||||
|
func (d *DummyMetrics) IncGovernorVaaAdded(chainID sdk.ChainID) {}
|
||||||
|
|
||||||
|
// IndGovenorVaaDeleted dummy implementation.
|
||||||
|
func (d *DummyMetrics) IndGovenorVaaDeleted(chainID sdk.ChainID) {}
|
||||||
|
|
|
@ -10,4 +10,18 @@ type Metrics interface {
|
||||||
IncDuplicatedVaaFailed(chainID sdk.ChainID)
|
IncDuplicatedVaaFailed(chainID sdk.ChainID)
|
||||||
IncDuplicatedVaaExpired(chainID sdk.ChainID)
|
IncDuplicatedVaaExpired(chainID sdk.ChainID)
|
||||||
IncDuplicatedVaaCanNotFixed(chainID sdk.ChainID)
|
IncDuplicatedVaaCanNotFixed(chainID sdk.ChainID)
|
||||||
|
IncGovernorStatusConsumedQueue()
|
||||||
|
IncGovernorStatusProcessed(node string, address string)
|
||||||
|
IncGovernorStatusFailed(node string, address string)
|
||||||
|
IncGovernorStatusExpired(node string, address string)
|
||||||
|
IncGovernorVaaAdded(chainID sdk.ChainID)
|
||||||
|
IndGovenorVaaDeleted(chainID sdk.ChainID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IncDuplicatedVaaConsumedQueue increments the counter of consumed queue
|
||||||
|
type IncConsumedQueue func()
|
||||||
|
|
||||||
|
/*
|
||||||
|
// ProcessorFunc is a function to process a governor message.
|
||||||
|
type ProcessorFunc func(context.Context, *Params) error
|
||||||
|
*/
|
||||||
|
|
|
@ -9,6 +9,8 @@ import (
|
||||||
// PrometheusMetrics is a Prometheus implementation of Metric interface.
|
// PrometheusMetrics is a Prometheus implementation of Metric interface.
|
||||||
type PrometheusMetrics struct {
|
type PrometheusMetrics struct {
|
||||||
duplicatedVaaCount *prometheus.CounterVec
|
duplicatedVaaCount *prometheus.CounterVec
|
||||||
|
governorStatusCount *prometheus.CounterVec
|
||||||
|
governorVaaCount *prometheus.CounterVec
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPrometheusMetrics returns a new instance of PrometheusMetrics.
|
// NewPrometheusMetrics returns a new instance of PrometheusMetrics.
|
||||||
|
@ -23,29 +25,84 @@ func NewPrometheusMetrics(environment string) *PrometheusMetrics {
|
||||||
"service": serviceName,
|
"service": serviceName,
|
||||||
},
|
},
|
||||||
}, []string{"chain", "type"}),
|
}, []string{"chain", "type"}),
|
||||||
|
governorStatusCount: promauto.NewCounterVec(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Name: "wormscan_fly_event_processor_governor_status_count",
|
||||||
|
Help: "The total number of governor status processed",
|
||||||
|
ConstLabels: map[string]string{
|
||||||
|
"environment": environment,
|
||||||
|
"service": serviceName,
|
||||||
|
},
|
||||||
|
}, []string{"node", "address", "type"}),
|
||||||
|
governorVaaCount: promauto.NewCounterVec(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Name: "wormscan_fly_event_processor_governor_vaa_count",
|
||||||
|
Help: "The total number of governor VAA processed",
|
||||||
|
ConstLabels: map[string]string{
|
||||||
|
"environment": environment,
|
||||||
|
"service": serviceName,
|
||||||
|
},
|
||||||
|
}, []string{"chain", "type"}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IncDuplicatedVaaConsumedQueue increments the total number of duplicated VAA consumed queue.
|
||||||
func (m *PrometheusMetrics) IncDuplicatedVaaConsumedQueue() {
|
func (m *PrometheusMetrics) IncDuplicatedVaaConsumedQueue() {
|
||||||
m.duplicatedVaaCount.WithLabelValues("all", "consumed_queue").Inc()
|
m.duplicatedVaaCount.WithLabelValues("all", "consumed_queue").Inc()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IncDuplicatedVaaProcessed increments the total number of duplicated VAA processed.
|
||||||
func (m *PrometheusMetrics) IncDuplicatedVaaProcessed(chainID sdk.ChainID) {
|
func (m *PrometheusMetrics) IncDuplicatedVaaProcessed(chainID sdk.ChainID) {
|
||||||
chain := chainID.String()
|
chain := chainID.String()
|
||||||
m.duplicatedVaaCount.WithLabelValues(chain, "processed").Inc()
|
m.duplicatedVaaCount.WithLabelValues(chain, "processed").Inc()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IncDuplicatedVaaFailed increments the total number of duplicated VAA failed.
|
||||||
func (m *PrometheusMetrics) IncDuplicatedVaaFailed(chainID sdk.ChainID) {
|
func (m *PrometheusMetrics) IncDuplicatedVaaFailed(chainID sdk.ChainID) {
|
||||||
chain := chainID.String()
|
chain := chainID.String()
|
||||||
m.duplicatedVaaCount.WithLabelValues(chain, "failed").Inc()
|
m.duplicatedVaaCount.WithLabelValues(chain, "failed").Inc()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IncDuplicatedVaaExpired increments the total number of duplicated VAA expired.
|
||||||
func (m *PrometheusMetrics) IncDuplicatedVaaExpired(chainID sdk.ChainID) {
|
func (m *PrometheusMetrics) IncDuplicatedVaaExpired(chainID sdk.ChainID) {
|
||||||
chain := chainID.String()
|
chain := chainID.String()
|
||||||
m.duplicatedVaaCount.WithLabelValues(chain, "expired").Inc()
|
m.duplicatedVaaCount.WithLabelValues(chain, "expired").Inc()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IncDuplicatedVaaCanNotFixed increments the total number of duplicated VAA can not fixed.
|
||||||
func (m *PrometheusMetrics) IncDuplicatedVaaCanNotFixed(chainID sdk.ChainID) {
|
func (m *PrometheusMetrics) IncDuplicatedVaaCanNotFixed(chainID sdk.ChainID) {
|
||||||
chain := chainID.String()
|
chain := chainID.String()
|
||||||
m.duplicatedVaaCount.WithLabelValues(chain, "can_not_fixed").Inc()
|
m.duplicatedVaaCount.WithLabelValues(chain, "can_not_fixed").Inc()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IncGovernorStatusConsumedQueue increments the total number of governor status consumed queue.
|
||||||
|
func (m *PrometheusMetrics) IncGovernorStatusConsumedQueue() {
|
||||||
|
m.governorStatusCount.WithLabelValues("all", "", "consumed_queue").Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncGovernorStatusProcessed increments the total number of governor status processed.
|
||||||
|
func (m *PrometheusMetrics) IncGovernorStatusProcessed(node string, address string) {
|
||||||
|
m.governorStatusCount.WithLabelValues(node, address, "processed").Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncGovernorStatusFailed increments the total number of governor status failed.
|
||||||
|
func (m *PrometheusMetrics) IncGovernorStatusFailed(node string, address string) {
|
||||||
|
m.governorStatusCount.WithLabelValues(node, address, "failed").Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncGovernorStatusExpired increments the total number of governor status expired.
|
||||||
|
func (m *PrometheusMetrics) IncGovernorStatusExpired(node string, address string) {
|
||||||
|
m.governorStatusCount.WithLabelValues(node, address, "expired").Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncGovernorVaaAdded increments the total number of governor VAA added.
|
||||||
|
func (m *PrometheusMetrics) IncGovernorVaaAdded(chainID sdk.ChainID) {
|
||||||
|
chain := chainID.String()
|
||||||
|
m.governorVaaCount.WithLabelValues(chain, "added").Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IndGovenorVaaDeleted increments the total number of governor VAA deleted.
|
||||||
|
func (m *PrometheusMetrics) IndGovenorVaaDeleted(chainID sdk.ChainID) {
|
||||||
|
chain := chainID.String()
|
||||||
|
m.governorVaaCount.WithLabelValues(chain, "deleted").Inc()
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,320 @@
|
||||||
|
package governor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
txTracker "github.com/wormhole-foundation/wormhole-explorer/common/client/txtracker"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/domain"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/internal/metrics"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/storage"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Processor is a governor processor.
|
||||||
|
type Processor struct {
|
||||||
|
repository *storage.Repository
|
||||||
|
createTxHashFunc txTracker.CreateTxHashFunc
|
||||||
|
logger *zap.Logger
|
||||||
|
metrics metrics.Metrics
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProcessor creates a new governor processor.
|
||||||
|
func NewProcessor(
|
||||||
|
repository *storage.Repository,
|
||||||
|
createTxHashFunc txTracker.CreateTxHashFunc,
|
||||||
|
logger *zap.Logger,
|
||||||
|
metrics metrics.Metrics,
|
||||||
|
) *Processor {
|
||||||
|
|
||||||
|
return &Processor{
|
||||||
|
repository: repository,
|
||||||
|
createTxHashFunc: createTxHashFunc,
|
||||||
|
logger: logger,
|
||||||
|
metrics: metrics,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process processes a governor event.
|
||||||
|
func (p *Processor) Process(
|
||||||
|
ctx context.Context,
|
||||||
|
params *Params) error {
|
||||||
|
|
||||||
|
logger := p.logger.With(
|
||||||
|
zap.String("trackId", params.TrackID),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 1. Check if the event is valid.
|
||||||
|
if params.NodeGovernorVaa == nil {
|
||||||
|
logger.Info("event is nil")
|
||||||
|
return errors.New("event cannot be nil")
|
||||||
|
}
|
||||||
|
node := params.NodeGovernorVaa.Node
|
||||||
|
if node.Address == "" {
|
||||||
|
logger.Info("node is invalid")
|
||||||
|
return errors.New("node is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Get new and current governorVaa by node.
|
||||||
|
newNodeGovernorVaas := params.NodeGovernorVaa
|
||||||
|
nodeGovernorVaaIds, err := p.getNodeGovernorVaaIds(ctx, node, logger)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to get current governorVaa",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("nodeAddress", node.Address))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Get nodeGovernorVaa to add and delete.
|
||||||
|
nodeGovernorVaasToAdd := getNodeGovernorVaasToAdd(
|
||||||
|
newNodeGovernorVaas.GovernorVaas, nodeGovernorVaaIds)
|
||||||
|
nodeGovernorVaaIdsToDelete := getNodeGovernorVaasToDelete(
|
||||||
|
newNodeGovernorVaas.GovernorVaas, nodeGovernorVaaIds)
|
||||||
|
|
||||||
|
// 4. Get governorVaa to add and delete.
|
||||||
|
governorVaasToAdd, err := p.getGovernorVaaToAdd(ctx, nodeGovernorVaasToAdd, logger)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to get governorVaa to insert",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("nodeAddress", node.Address))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
governorVaaIdsToDelete, err := p.getGovernorVaaToDelete(ctx, node, nodeGovernorVaaIdsToDelete, logger)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to get governorVaa to delete",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("nodeAddress", node.Address))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Check if there are no changes in governor.
|
||||||
|
changeNodeGovernorVaas := len(nodeGovernorVaasToAdd) > 0 || len(nodeGovernorVaaIdsToDelete) > 0
|
||||||
|
changeGovernorVaas := len(governorVaasToAdd) > 0 || len(governorVaaIdsToDelete) > 0
|
||||||
|
if !changeNodeGovernorVaas && !changeGovernorVaas {
|
||||||
|
logger.Info("no changes in governor",
|
||||||
|
zap.String("nodeAddress", node.Address))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Update governor data for the node.
|
||||||
|
err = p.updateGovernor(ctx,
|
||||||
|
node,
|
||||||
|
nodeGovernorVaasToAdd,
|
||||||
|
nodeGovernorVaaIdsToDelete,
|
||||||
|
governorVaasToAdd,
|
||||||
|
governorVaaIdsToDelete)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to update governorVaa",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("nodeAddress", node.Address),
|
||||||
|
zap.String("node", node.Name))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getNodeGovernorVaaIds gets the current governor vaaIds stored in the database by node address.
|
||||||
|
func (p *Processor) getNodeGovernorVaaIds(
|
||||||
|
ctx context.Context,
|
||||||
|
node domain.Node,
|
||||||
|
logger *zap.Logger,
|
||||||
|
) (Set[string], error) {
|
||||||
|
|
||||||
|
// get current nodeGovernorVaa by nodeAddress.
|
||||||
|
nodeGovernorVaaDoc, err := p.repository.FindNodeGovernorVaaByNodeAddress(ctx, node.Address)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to find nodeGovernorVaa by nodeAddress",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("nodeAddress", node.Address))
|
||||||
|
return Set[string]{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert nodeGovernorVaaDoc to Set[string]
|
||||||
|
nodeGovernorVaaId := make(Set[string])
|
||||||
|
for _, governorVaaDoc := range nodeGovernorVaaDoc {
|
||||||
|
nodeGovernorVaaId.Add(governorVaaDoc.VaaID)
|
||||||
|
}
|
||||||
|
return nodeGovernorVaaId, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getNodeGovernorVaasToAdd gets the node governor vaas to add.
|
||||||
|
func getNodeGovernorVaasToAdd(
|
||||||
|
newNodeGovernorVaas map[string]domain.GovernorVaa,
|
||||||
|
nodeGovernorVaaIds Set[string],
|
||||||
|
) map[string]domain.GovernorVaa {
|
||||||
|
|
||||||
|
nodeGovernorVaasToAdd := make(map[string]domain.GovernorVaa)
|
||||||
|
for vaaID, governorVaa := range newNodeGovernorVaas {
|
||||||
|
if ok := nodeGovernorVaaIds.Contains(vaaID); !ok {
|
||||||
|
nodeGovernorVaasToAdd[vaaID] = governorVaa
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodeGovernorVaasToAdd
|
||||||
|
}
|
||||||
|
|
||||||
|
// getNodeGovernorVaasToDelete gets the node governor vaas to delete.
|
||||||
|
func getNodeGovernorVaasToDelete(
|
||||||
|
newNodeGovernorVaas map[string]domain.GovernorVaa,
|
||||||
|
nodeGovernorVaaIds Set[string],
|
||||||
|
) Set[string] {
|
||||||
|
|
||||||
|
nodeGovernorVaasToDelete := make(Set[string])
|
||||||
|
for vaaID := range nodeGovernorVaaIds {
|
||||||
|
if _, ok := newNodeGovernorVaas[vaaID]; !ok {
|
||||||
|
nodeGovernorVaasToDelete.Add(vaaID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodeGovernorVaasToDelete
|
||||||
|
}
|
||||||
|
|
||||||
|
// getGovernorVaaToAdd gets the governor vaas to add.
|
||||||
|
func (p *Processor) getGovernorVaaToAdd(
|
||||||
|
ctx context.Context,
|
||||||
|
nodeGovernorVaas map[string]domain.GovernorVaa,
|
||||||
|
logger *zap.Logger,
|
||||||
|
) ([]domain.GovernorVaa, error) {
|
||||||
|
|
||||||
|
// get vaaIDs from the nodeGovernorVaas.
|
||||||
|
vaaIds := make([]string, 0, len(nodeGovernorVaas))
|
||||||
|
for vaaId, _ := range nodeGovernorVaas {
|
||||||
|
vaaIds = append(vaaIds, vaaId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get governoVaas already added by vaaIDs.
|
||||||
|
governorVaas, err := p.repository.FindGovernorVaaByVaaIDs(ctx, vaaIds)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to find governor vaas by a list of vaaIDs",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.Strings("vaaIDs", vaaIds))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(vaaIds) < len(governorVaas) {
|
||||||
|
logger.Error("failed to find governorVaa by a list of vaaIDs",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.Strings("vaaIDs", vaaIds))
|
||||||
|
return nil, errors.New("failed to find governorVaa by vaaIDs")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if all the governorVaa are already added
|
||||||
|
if len(vaaIds) == len(governorVaas) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert governorVaas to a set of vaaIDs.
|
||||||
|
governorVaaIds := make(Set[string])
|
||||||
|
for _, governorVaa := range governorVaas {
|
||||||
|
governorVaaIds.Add(governorVaa.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get governorVaa to insert
|
||||||
|
var governorVaasToInsert []domain.GovernorVaa
|
||||||
|
for vaaID, governorVaa := range nodeGovernorVaas {
|
||||||
|
if ok := governorVaaIds.Contains(vaaID); !ok {
|
||||||
|
// fix governor vaa txHash
|
||||||
|
txHash, err := p.createTxHashFunc(governorVaa.ID, governorVaa.TxHash)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to create txHash",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("vaaID", governorVaa.ID),
|
||||||
|
zap.String("txHash", governorVaa.TxHash))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
governorVaa.TxHash = txHash.NativeTxHash
|
||||||
|
governorVaasToInsert = append(governorVaasToInsert, governorVaa)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return governorVaasToInsert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getGovernorVaaToDelete gets the governor vaas to delete.
|
||||||
|
func (p *Processor) getGovernorVaaToDelete(
|
||||||
|
ctx context.Context,
|
||||||
|
node domain.Node,
|
||||||
|
nodeGovernorVaaIds Set[string],
|
||||||
|
logger *zap.Logger,
|
||||||
|
) (Set[string], error) {
|
||||||
|
|
||||||
|
// get vaaIDs from the nodeGovernorVaaIds.
|
||||||
|
vaaIds := make([]string, 0, nodeGovernorVaaIds.Len())
|
||||||
|
for vaaID := range nodeGovernorVaaIds {
|
||||||
|
vaaIds = append(vaaIds, vaaID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nodeGovernorVaas contains all the node governor vaas that have the same vaaID.
|
||||||
|
nodeGovernorVaas, err := p.repository.FindNodeGovernorVaaByVaaIDs(ctx, vaaIds)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to find governorVaa by vaaIDs",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.Strings("vaaIDs", vaaIds))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// nodeAddressByVaaId contains all the node address grouped by vaaID.
|
||||||
|
nodeAddressByVaaId := make(map[string][]string)
|
||||||
|
for _, n := range nodeGovernorVaas {
|
||||||
|
if _, ok := nodeAddressByVaaId[n.VaaID]; !ok {
|
||||||
|
nodeAddressByVaaId[n.VaaID] = make([]string, 0)
|
||||||
|
}
|
||||||
|
nodeAddressByVaaId[n.VaaID] = append(nodeAddressByVaaId[n.VaaID], n.NodeAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get governorVaa to delete
|
||||||
|
governorVaaToDelete := make(Set[string])
|
||||||
|
for vaaID, nodeAddresses := range nodeAddressByVaaId {
|
||||||
|
deleteGovernorVaa := len(nodeAddresses) == 1 && node.Address == nodeAddresses[0]
|
||||||
|
if deleteGovernorVaa {
|
||||||
|
governorVaaToDelete.Add(vaaID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return governorVaaToDelete, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Processor) updateGovernor(ctx context.Context,
|
||||||
|
node domain.Node,
|
||||||
|
nodeGovernorVaasToAdd map[string]domain.GovernorVaa,
|
||||||
|
nodeGovernorVaaIdsToDelete Set[string],
|
||||||
|
governorVaasToAdd []domain.GovernorVaa,
|
||||||
|
governorVaaIdsToDelete Set[string]) error {
|
||||||
|
|
||||||
|
// convert nodeGovernorVaasToAdd to []storage.NodeGovernorVaaDoc
|
||||||
|
var nodeGovernorVaasToAddDoc []storage.NodeGovernorVaaDoc
|
||||||
|
for vaaID, _ := range nodeGovernorVaasToAdd {
|
||||||
|
nodeGovernorVaasToAddDoc = append(nodeGovernorVaasToAddDoc, storage.NodeGovernorVaaDoc{
|
||||||
|
ID: fmt.Sprintf("%s-%s", node.Address, vaaID),
|
||||||
|
NodeName: node.Name,
|
||||||
|
NodeAddress: node.Address,
|
||||||
|
VaaID: vaaID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert governorVaasToAdd to []storage.GovernorVaaDoc
|
||||||
|
var governorVaasToAddDoc []storage.GovernorVaaDoc
|
||||||
|
for _, governorVaa := range governorVaasToAdd {
|
||||||
|
governorVaasToAddDoc = append(governorVaasToAddDoc, storage.GovernorVaaDoc{
|
||||||
|
ID: governorVaa.ID,
|
||||||
|
ChainID: governorVaa.ChainID,
|
||||||
|
EmitterAddress: governorVaa.EmitterAddress,
|
||||||
|
Sequence: governorVaa.Sequence,
|
||||||
|
TxHash: governorVaa.TxHash,
|
||||||
|
ReleaseTime: governorVaa.ReleaseTime,
|
||||||
|
Amount: storage.Uint64(governorVaa.Amount),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert nodeGovernorVaas vaaIds to ids
|
||||||
|
var nodeGovVaaIdsToDelete []string
|
||||||
|
for vaaID := range nodeGovernorVaaIdsToDelete {
|
||||||
|
nodeGovVaaIdsToDelete = append(nodeGovVaaIdsToDelete, fmt.Sprintf("%s-%s", node.Address, vaaID))
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.repository.UpdateGovernor(
|
||||||
|
ctx,
|
||||||
|
nodeGovernorVaasToAddDoc,
|
||||||
|
nodeGovVaaIdsToDelete,
|
||||||
|
governorVaasToAddDoc,
|
||||||
|
governorVaaIdsToDelete.ToSlice())
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package governor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/fly-event-processor/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set generic type definition.
|
||||||
|
type Set[T comparable] map[T]struct{}
|
||||||
|
|
||||||
|
// add a value to the set
|
||||||
|
func (s Set[T]) Add(v T) {
|
||||||
|
s[v] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the set contains a value
|
||||||
|
func (s Set[T]) Contains(v T) bool {
|
||||||
|
_, ok := s[v]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// get length of the set
|
||||||
|
func (s Set[T]) Len() int {
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// to slice
|
||||||
|
func (s Set[T]) ToSlice() []T {
|
||||||
|
var slice []T
|
||||||
|
for k := range s {
|
||||||
|
slice = append(slice, k)
|
||||||
|
}
|
||||||
|
return slice
|
||||||
|
}
|
||||||
|
|
||||||
|
type Params struct {
|
||||||
|
TrackID string
|
||||||
|
NodeGovernorVaa *domain.NodeGovernorVaa
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessorFunc is a function to process a governor message.
|
||||||
|
type ProcessorFunc func(context.Context, *Params) error
|
|
@ -1,4 +1,4 @@
|
||||||
package processor
|
package vaa
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
|
@ -1,4 +1,4 @@
|
||||||
package processor
|
package vaa
|
||||||
|
|
||||||
import (
|
import (
|
||||||
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
|
@ -13,42 +13,46 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// SQSOption represents a VAA queue in SQS option function.
|
// SQSOption represents a VAA queue in SQS option function.
|
||||||
type SQSOption func(*SQS)
|
type SQSOption[T Event] func(*SQS[T])
|
||||||
|
|
||||||
// SQS represents a VAA queue in SQS.
|
// SQS represents a VAA queue in SQS.
|
||||||
type SQS struct {
|
type SQS[T Event] struct {
|
||||||
consumer *sqs_client.Consumer
|
consumer *sqs_client.Consumer
|
||||||
ch chan ConsumerMessage
|
ch chan ConsumerMessage[T]
|
||||||
chSize int
|
chSize int
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
metrics metrics.Metrics
|
incConsumedQueueFunc metrics.IncConsumedQueue
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEventSqs creates a VAA queue in SQS instances.
|
// NewEventSqs creates a VAA queue in SQS instances.
|
||||||
func NewEventSqs(consumer *sqs_client.Consumer, metrics metrics.Metrics, logger *zap.Logger, opts ...SQSOption) *SQS {
|
func NewEventSqs[T Event](
|
||||||
s := &SQS{
|
consumer *sqs_client.Consumer,
|
||||||
|
incConsumedQueueFunc metrics.IncConsumedQueue,
|
||||||
|
logger *zap.Logger,
|
||||||
|
opts ...SQSOption[T]) *SQS[T] {
|
||||||
|
s := &SQS[T]{
|
||||||
consumer: consumer,
|
consumer: consumer,
|
||||||
chSize: 10,
|
chSize: 10,
|
||||||
metrics: metrics,
|
incConsumedQueueFunc: incConsumedQueueFunc,
|
||||||
logger: logger.With(zap.String("queueUrl", consumer.GetQueueUrl())),
|
logger: logger.With(zap.String("queueUrl", consumer.GetQueueUrl())),
|
||||||
}
|
}
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(s)
|
opt(s)
|
||||||
}
|
}
|
||||||
s.ch = make(chan ConsumerMessage, s.chSize)
|
s.ch = make(chan ConsumerMessage[T], s.chSize)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithChannelSize allows to specify an channel size when setting a value.
|
// WithChannelSize allows to specify an channel size when setting a value.
|
||||||
func WithChannelSize(size int) SQSOption {
|
func WithChannelSize[T Event](size int) SQSOption[T] {
|
||||||
return func(d *SQS) {
|
return func(d *SQS[T]) {
|
||||||
d.chSize = size
|
d.chSize = size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consume returns the channel with the received messages from SQS queue.
|
// Consume returns the channel with the received messages from SQS queue.
|
||||||
func (q *SQS) Consume(ctx context.Context) <-chan ConsumerMessage {
|
func (q *SQS[T]) Consume(ctx context.Context) <-chan ConsumerMessage[T] {
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
messages, err := q.consumer.GetMessages(ctx)
|
messages, err := q.consumer.GetMessages(ctx)
|
||||||
|
@ -60,7 +64,7 @@ func (q *SQS) Consume(ctx context.Context) <-chan ConsumerMessage {
|
||||||
expiredAt := time.Now().Add(q.consumer.GetVisibilityTimeout())
|
expiredAt := time.Now().Add(q.consumer.GetVisibilityTimeout())
|
||||||
for _, msg := range messages {
|
for _, msg := range messages {
|
||||||
|
|
||||||
q.metrics.IncDuplicatedVaaConsumedQueue()
|
q.incConsumedQueueFunc()
|
||||||
// unmarshal body to sqsEvent
|
// unmarshal body to sqsEvent
|
||||||
var sqsEvent sqsEvent
|
var sqsEvent sqsEvent
|
||||||
err := json.Unmarshal([]byte(*msg.Body), &sqsEvent)
|
err := json.Unmarshal([]byte(*msg.Body), &sqsEvent)
|
||||||
|
@ -72,7 +76,7 @@ func (q *SQS) Consume(ctx context.Context) <-chan ConsumerMessage {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var event Event
|
var event T
|
||||||
err = json.Unmarshal([]byte(sqsEvent.Message), &event)
|
err = json.Unmarshal([]byte(sqsEvent.Message), &event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
q.logger.Error("Error decoding message from SQS", zap.String("body", sqsEvent.Message), zap.Error(err))
|
q.logger.Error("Error decoding message from SQS", zap.String("body", sqsEvent.Message), zap.Error(err))
|
||||||
|
@ -84,15 +88,14 @@ func (q *SQS) Consume(ctx context.Context) <-chan ConsumerMessage {
|
||||||
|
|
||||||
retry, _ := strconv.Atoi(msg.Attributes["ApproximateReceiveCount"])
|
retry, _ := strconv.Atoi(msg.Attributes["ApproximateReceiveCount"])
|
||||||
q.wg.Add(1)
|
q.wg.Add(1)
|
||||||
q.ch <- &sqsConsumerMessage{
|
q.ch <- &sqsConsumerMessage[T]{
|
||||||
id: msg.ReceiptHandle,
|
id: msg.ReceiptHandle,
|
||||||
data: &event,
|
data: event,
|
||||||
wg: &q.wg,
|
wg: &q.wg,
|
||||||
logger: q.logger,
|
logger: q.logger,
|
||||||
consumer: q.consumer,
|
consumer: q.consumer,
|
||||||
expiredAt: expiredAt,
|
expiredAt: expiredAt,
|
||||||
retry: uint8(retry),
|
retry: uint8(retry),
|
||||||
metrics: q.metrics,
|
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,30 +107,24 @@ func (q *SQS) Consume(ctx context.Context) <-chan ConsumerMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes all consumer resources.
|
// Close closes all consumer resources.
|
||||||
func (q *SQS) Close() {
|
func (q *SQS[T]) Close() {
|
||||||
close(q.ch)
|
close(q.ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
type sqsConsumerMessage struct {
|
type sqsConsumerMessage[T Event] struct {
|
||||||
data *Event
|
data T
|
||||||
consumer *sqs_client.Consumer
|
consumer *sqs_client.Consumer
|
||||||
wg *sync.WaitGroup
|
wg *sync.WaitGroup
|
||||||
id *string
|
id *string
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
expiredAt time.Time
|
expiredAt time.Time
|
||||||
retry uint8
|
retry uint8
|
||||||
metrics metrics.Metrics
|
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *sqsConsumerMessage) Data() *Event {
|
func (m *sqsConsumerMessage[T]) Done() {
|
||||||
return m.data
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *sqsConsumerMessage) Done() {
|
|
||||||
if err := m.consumer.DeleteMessage(m.ctx, m.id); err != nil {
|
if err := m.consumer.DeleteMessage(m.ctx, m.id); err != nil {
|
||||||
m.logger.Error("Error deleting message from SQS",
|
m.logger.Error("Error deleting message from SQS",
|
||||||
zap.String("vaaId", m.data.Data.VaaID),
|
|
||||||
zap.Bool("isExpired", m.IsExpired()),
|
zap.Bool("isExpired", m.IsExpired()),
|
||||||
zap.Time("expiredAt", m.expiredAt),
|
zap.Time("expiredAt", m.expiredAt),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
|
@ -136,14 +133,18 @@ func (m *sqsConsumerMessage) Done() {
|
||||||
m.wg.Done()
|
m.wg.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *sqsConsumerMessage) Failed() {
|
func (m *sqsConsumerMessage[T]) Data() T {
|
||||||
|
return m.data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *sqsConsumerMessage[T]) Failed() {
|
||||||
m.wg.Done()
|
m.wg.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *sqsConsumerMessage) IsExpired() bool {
|
func (m *sqsConsumerMessage[T]) IsExpired() bool {
|
||||||
return m.expiredAt.Before(time.Now())
|
return m.expiredAt.Before(time.Now())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *sqsConsumerMessage) Retry() uint8 {
|
func (m *sqsConsumerMessage[T]) Retry() uint8 {
|
||||||
return m.retry
|
return m.retry
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,20 +5,31 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DeduplicateVaaEventType = "duplicated-vaa"
|
||||||
|
GovernorStatusEventType = "governor-status"
|
||||||
|
)
|
||||||
|
|
||||||
// sqsEvent represents a event data from SQS.
|
// sqsEvent represents a event data from SQS.
|
||||||
type sqsEvent struct {
|
type sqsEvent struct {
|
||||||
MessageID string `json:"MessageId"`
|
MessageID string `json:"MessageId"`
|
||||||
Message string `json:"Message"`
|
Message string `json:"Message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event represents a event data to be handle.
|
// Event represents a event data.
|
||||||
type Event struct {
|
type Event interface {
|
||||||
|
EventDuplicateVaa | EventGovernorStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventDuplicateVaa defition.
|
||||||
|
type EventDuplicateVaa struct {
|
||||||
TrackID string `json:"trackId"`
|
TrackID string `json:"trackId"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Source string `json:"source"`
|
Source string `json:"source"`
|
||||||
Data DuplicateVaa `json:"data"`
|
Data DuplicateVaa `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DuplicateVaa defition.
|
||||||
type DuplicateVaa struct {
|
type DuplicateVaa struct {
|
||||||
VaaID string `json:"vaaId"`
|
VaaID string `json:"vaaId"`
|
||||||
ChainID uint16 `json:"chainId"`
|
ChainID uint16 `json:"chainId"`
|
||||||
|
@ -30,14 +41,53 @@ type DuplicateVaa struct {
|
||||||
Timestamp *time.Time `json:"timestamp"`
|
Timestamp *time.Time `json:"timestamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EventGovernorStatus defition.
|
||||||
|
type EventGovernorStatus struct {
|
||||||
|
TrackID string `json:"trackId"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Source string `json:"source"`
|
||||||
|
Data GovernorStatus `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GovernorStatus defition.
|
||||||
|
type GovernorStatus struct {
|
||||||
|
NodeAddress string `json:"nodeAddress"`
|
||||||
|
NodeName string `json:"nodeName"`
|
||||||
|
Counter int64 `json:"counter"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
Chains []*ChainStatus `json:"chains"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainStatus defition.
|
||||||
|
type ChainStatus struct {
|
||||||
|
ChainId uint32 `json:"chainId"`
|
||||||
|
RemainingAvailableNotional uint64 `json:"remainingAvailableNotional"`
|
||||||
|
Emitters []*Emitter `json:"emitters"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emitter defition.
|
||||||
|
type Emitter struct {
|
||||||
|
EmitterAddress string `bson:"emitteraddress" json:"emitterAddress"`
|
||||||
|
TotalEnqueuedVaas uint64 `bson:"totalenqueuedvaas" json:"totalEnqueuedVaas"`
|
||||||
|
EnqueuedVaas []*EnqueuedVAA `bson:"enqueuedvaas" json:"enqueuedVaas"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnqueuedVAA defition.
|
||||||
|
type EnqueuedVAA struct {
|
||||||
|
Sequence string `bson:"sequence" json:"sequence"`
|
||||||
|
ReleaseTime uint64 `bson:"releasetime" json:"releaseTime"`
|
||||||
|
NotionalValue uint64 `bson:"notionalvalue" json:"notionalValue"`
|
||||||
|
TxHash string `bson:"txhash" json:"txHash"`
|
||||||
|
}
|
||||||
|
|
||||||
// ConsumerMessage defition.
|
// ConsumerMessage defition.
|
||||||
type ConsumerMessage interface {
|
type ConsumerMessage[T any] interface {
|
||||||
Retry() uint8
|
Retry() uint8
|
||||||
Data() *Event
|
Data() T
|
||||||
Done()
|
Done()
|
||||||
Failed()
|
Failed()
|
||||||
IsExpired() bool
|
IsExpired() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConsumeFunc is a function to consume Event.
|
// ConsumeFunc is a function to consume Event.
|
||||||
type ConsumeFunc func(context.Context) <-chan ConsumerMessage
|
type ConsumeFunc[T any] func(context.Context) <-chan ConsumerMessage[T]
|
||||||
|
|
|
@ -14,6 +14,8 @@ type Repository struct {
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
vaas *mongo.Collection
|
vaas *mongo.Collection
|
||||||
duplicateVaas *mongo.Collection
|
duplicateVaas *mongo.Collection
|
||||||
|
nodeGovernorVaas *mongo.Collection
|
||||||
|
governorVaas *mongo.Collection
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new repository.
|
// New creates a new repository.
|
||||||
|
@ -22,6 +24,8 @@ func NewRepository(logger *zap.Logger, db *mongo.Database) *Repository {
|
||||||
logger: logger,
|
logger: logger,
|
||||||
vaas: db.Collection(commonRepo.Vaas),
|
vaas: db.Collection(commonRepo.Vaas),
|
||||||
duplicateVaas: db.Collection(commonRepo.DuplicateVaas),
|
duplicateVaas: db.Collection(commonRepo.DuplicateVaas),
|
||||||
|
nodeGovernorVaas: db.Collection(commonRepo.NodeGovernorVaas),
|
||||||
|
governorVaas: db.Collection(commonRepo.GovernorVaas),
|
||||||
}
|
}
|
||||||
return &r
|
return &r
|
||||||
}
|
}
|
||||||
|
@ -125,3 +129,127 @@ func (r *Repository) FixVAA(ctx context.Context, vaaID, duplicateID string) erro
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindNodeGovernorVaaByNodeAddress find governor vaas by node address.
|
||||||
|
func (r *Repository) FindNodeGovernorVaaByNodeAddress(ctx context.Context, nodeAddress string) ([]NodeGovernorVaaDoc, error) {
|
||||||
|
var nodeGovernorVaa []NodeGovernorVaaDoc
|
||||||
|
cursor, err := r.nodeGovernorVaas.Find(ctx, bson.M{"nodeAddress": nodeAddress})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = cursor.All(ctx, &nodeGovernorVaa); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nodeGovernorVaa, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindNodeGovernorVaaByVaaID find governor vaas by vaa id.
|
||||||
|
func (r *Repository) FindNodeGovernorVaaByVaaID(ctx context.Context, vaaID string) ([]NodeGovernorVaaDoc, error) {
|
||||||
|
var nodeGovernorVaa []NodeGovernorVaaDoc
|
||||||
|
cursor, err := r.nodeGovernorVaas.Find(ctx, bson.M{"vaaId": vaaID})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = cursor.All(ctx, &nodeGovernorVaa); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nodeGovernorVaa, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindNodeGovernorVaaByVaaIDs find governor vaas by vaa ids.
|
||||||
|
func (r *Repository) FindNodeGovernorVaaByVaaIDs(ctx context.Context, vaaID []string) ([]NodeGovernorVaaDoc, error) {
|
||||||
|
var nodeGovernorVaa []NodeGovernorVaaDoc
|
||||||
|
cursor, err := r.nodeGovernorVaas.Find(ctx, bson.M{"vaaId": bson.M{"$in": vaaID}})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = cursor.All(ctx, &nodeGovernorVaa); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nodeGovernorVaa, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindGovernorVaaByVaaID find governor vaas by a list of vaaIds
|
||||||
|
func (r *Repository) FindGovernorVaaByVaaIDs(ctx context.Context, vaaID []string) ([]GovernorVaaDoc, error) {
|
||||||
|
var governorVaa []GovernorVaaDoc
|
||||||
|
cursor, err := r.governorVaas.Find(ctx, bson.M{"_id": bson.M{"$in": vaaID}})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = cursor.All(ctx, &governorVaa); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return governorVaa, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) UpdateGovernor(
|
||||||
|
ctx context.Context,
|
||||||
|
nodeGovernorVaaDocToInsert []NodeGovernorVaaDoc,
|
||||||
|
nodeGovernorVaaDocToDelete []string,
|
||||||
|
governorVaasToInsert []GovernorVaaDoc,
|
||||||
|
governorVaaIdsToDelete []string) error {
|
||||||
|
|
||||||
|
// 1. start mongo transaction
|
||||||
|
session, err := r.vaas.Database().Client().StartSession()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = session.StartTransaction()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. insert node governor vaas.
|
||||||
|
if len(nodeGovernorVaaDocToInsert) > 0 {
|
||||||
|
var nodeGovVaadocs []interface{}
|
||||||
|
for _, doc := range nodeGovernorVaaDocToInsert {
|
||||||
|
nodeGovVaadocs = append(nodeGovVaadocs, doc)
|
||||||
|
}
|
||||||
|
_, err = r.nodeGovernorVaas.InsertMany(ctx, nodeGovVaadocs)
|
||||||
|
if err != nil {
|
||||||
|
session.AbortTransaction(ctx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. delete node governor vaas.
|
||||||
|
if len(nodeGovernorVaaDocToDelete) > 0 {
|
||||||
|
_, err = r.nodeGovernorVaas.DeleteMany(ctx, bson.M{"_id": bson.M{"$in": nodeGovernorVaaDocToDelete}})
|
||||||
|
if err != nil {
|
||||||
|
session.AbortTransaction(ctx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. insert governor vaas.
|
||||||
|
if len(governorVaasToInsert) > 0 {
|
||||||
|
var govVaaDocs []interface{}
|
||||||
|
for _, doc := range governorVaasToInsert {
|
||||||
|
govVaaDocs = append(govVaaDocs, doc)
|
||||||
|
}
|
||||||
|
_, err = r.governorVaas.InsertMany(ctx, govVaaDocs)
|
||||||
|
if err != nil {
|
||||||
|
session.AbortTransaction(ctx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. delete governor vaas.
|
||||||
|
if len(governorVaaIdsToDelete) > 0 {
|
||||||
|
_, err = r.governorVaas.DeleteMany(ctx, bson.M{"_id": bson.M{"$in": governorVaaIdsToDelete}})
|
||||||
|
if err != nil {
|
||||||
|
session.AbortTransaction(ctx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. commit transaction
|
||||||
|
err = session.CommitTransaction(ctx)
|
||||||
|
if err != nil {
|
||||||
|
session.AbortTransaction(ctx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -3,9 +3,15 @@ package storage
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
||||||
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
|
||||||
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/bson/bsontype"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
|
||||||
)
|
)
|
||||||
|
|
||||||
// VaaDoc represents a VAA document.
|
// VaaDoc represents a VAA document.
|
||||||
|
@ -43,6 +49,23 @@ type DuplicateVaaDoc struct {
|
||||||
UpdatedAt *time.Time `bson:"updatedAt"`
|
UpdatedAt *time.Time `bson:"updatedAt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NodeGovernorVaaDoc struct {
|
||||||
|
ID string `bson:"_id"`
|
||||||
|
NodeName string `bson:"nodeName"`
|
||||||
|
NodeAddress string `bson:"nodeAddress"`
|
||||||
|
VaaID string `bson:"vaaId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GovernorVaaDoc struct {
|
||||||
|
ID string `bson:"_id"`
|
||||||
|
ChainID sdk.ChainID `bson:"chainId"`
|
||||||
|
EmitterAddress string `bson:"emitterAddress"`
|
||||||
|
Sequence string `bson:"sequence"`
|
||||||
|
TxHash string `bson:"txHash"`
|
||||||
|
ReleaseTime time.Time `bson:"releaseTime"`
|
||||||
|
Amount Uint64 `bson:"amount"`
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DuplicateVaaDoc) ToVaaDoc(duplicatedFixed bool) *VaaDoc {
|
func (d *DuplicateVaaDoc) ToVaaDoc(duplicatedFixed bool) *VaaDoc {
|
||||||
return &VaaDoc{
|
return &VaaDoc{
|
||||||
ID: d.VaaID,
|
ID: d.VaaID,
|
||||||
|
@ -63,7 +86,7 @@ func (d *DuplicateVaaDoc) ToVaaDoc(duplicatedFixed bool) *VaaDoc {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VaaDoc) ToDuplicateVaaDoc() (*DuplicateVaaDoc, error) {
|
func (v *VaaDoc) ToDuplicateVaaDoc() (*DuplicateVaaDoc, error) {
|
||||||
vaa, err := vaa.Unmarshal(v.Vaa)
|
vaa, err := sdk.Unmarshal(v.Vaa)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -85,3 +108,26 @@ func (v *VaaDoc) ToDuplicateVaaDoc() (*DuplicateVaaDoc, error) {
|
||||||
UpdatedAt: v.UpdatedAt,
|
UpdatedAt: v.UpdatedAt,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Uint64 uint64
|
||||||
|
|
||||||
|
func (u Uint64) MarshalBSONValue() (bsontype.Type, []byte, error) {
|
||||||
|
ui64Str := strconv.FormatUint(uint64(u), 10)
|
||||||
|
d128, err := primitive.ParseDecimal128(ui64Str)
|
||||||
|
return bsontype.Decimal128, bsoncore.AppendDecimal128(nil, d128), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Uint64) UnmarshalBSONValue(t bsontype.Type, b []byte) error {
|
||||||
|
d128, _, ok := bsoncore.ReadDecimal128(b)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("Uint64 UnmarshalBSONValue error")
|
||||||
|
}
|
||||||
|
|
||||||
|
ui64, err := strconv.ParseUint(d128.String(), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*u = Uint64(ui64)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -11,3 +11,7 @@ func NewNoopEventDispatcher() *NoopEventDispatcher {
|
||||||
func (n *NoopEventDispatcher) NewDuplicateVaa(context.Context, DuplicateVaa) error {
|
func (n *NoopEventDispatcher) NewDuplicateVaa(context.Context, DuplicateVaa) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *NoopEventDispatcher) NewGovernorStatus(context.Context, GovernorStatus) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
aws_sns "github.com/aws/aws-sdk-go-v2/service/sns"
|
aws_sns "github.com/aws/aws-sdk-go-v2/service/sns"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/sns/types"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/fly/internal/track"
|
"github.com/wormhole-foundation/wormhole-explorer/fly/internal/track"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,6 +27,12 @@ func NewSnsEventDispatcher(awsConfig aws.Config, url string) (*SnsEventDispatche
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SnsEventDispatcher) NewDuplicateVaa(ctx context.Context, e DuplicateVaa) error {
|
func (s *SnsEventDispatcher) NewDuplicateVaa(ctx context.Context, e DuplicateVaa) error {
|
||||||
|
attrs := map[string]types.MessageAttributeValue{
|
||||||
|
"messageType": {
|
||||||
|
DataType: aws.String("String"),
|
||||||
|
StringValue: aws.String("duplicated-vaa"),
|
||||||
|
},
|
||||||
|
}
|
||||||
body, err := json.Marshal(event{
|
body, err := json.Marshal(event{
|
||||||
TrackID: track.GetTrackIDForDuplicatedVAA(e.VaaID),
|
TrackID: track.GetTrackIDForDuplicatedVAA(e.VaaID),
|
||||||
Type: "duplicated-vaa",
|
Type: "duplicated-vaa",
|
||||||
|
@ -42,6 +49,7 @@ func (s *SnsEventDispatcher) NewDuplicateVaa(ctx context.Context, e DuplicateVaa
|
||||||
MessageDeduplicationId: aws.String(groupID),
|
MessageDeduplicationId: aws.String(groupID),
|
||||||
Message: aws.String(string(body)),
|
Message: aws.String(string(body)),
|
||||||
TopicArn: aws.String(s.url),
|
TopicArn: aws.String(s.url),
|
||||||
|
MessageAttributes: attrs,
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -56,3 +64,31 @@ func createDeduplicationIDForDuplicateVaa(e DuplicateVaa) string {
|
||||||
}
|
}
|
||||||
return deduplicationID
|
return deduplicationID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SnsEventDispatcher) NewGovernorStatus(ctx context.Context, e GovernorStatus) error {
|
||||||
|
attrs := map[string]types.MessageAttributeValue{
|
||||||
|
"messageType": {
|
||||||
|
DataType: aws.String("String"),
|
||||||
|
StringValue: aws.String("governor"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
body, err := json.Marshal(event{
|
||||||
|
TrackID: track.GetTrackIDForGovernorStatus(e.NodeName, e.Timestamp),
|
||||||
|
Type: "governor-status",
|
||||||
|
Source: "fly",
|
||||||
|
Data: e,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
groupID := fmt.Sprintf("%s-%v", e.NodeAddress, e.Timestamp)
|
||||||
|
_, err = s.api.Publish(ctx,
|
||||||
|
&aws_sns.PublishInput{
|
||||||
|
MessageGroupId: aws.String(groupID),
|
||||||
|
MessageDeduplicationId: aws.String(groupID),
|
||||||
|
Message: aws.String(string(body)),
|
||||||
|
TopicArn: aws.String(s.url),
|
||||||
|
MessageAttributes: attrs,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,14 @@ type DuplicateVaa struct {
|
||||||
Timestamp *time.Time `json:"timestamp"`
|
Timestamp *time.Time `json:"timestamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GovernorStatus struct {
|
||||||
|
NodeAddress string `json:"nodeAddress"`
|
||||||
|
NodeName string `json:"nodeName"`
|
||||||
|
Counter int64 `json:"counter"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
Chains any `json:"chains"`
|
||||||
|
}
|
||||||
|
|
||||||
type event struct {
|
type event struct {
|
||||||
TrackID string `json:"trackId"`
|
TrackID string `json:"trackId"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
@ -25,4 +33,5 @@ type event struct {
|
||||||
|
|
||||||
type EventDispatcher interface {
|
type EventDispatcher interface {
|
||||||
NewDuplicateVaa(ctx context.Context, e DuplicateVaa) error
|
NewDuplicateVaa(ctx context.Context, e DuplicateVaa) error
|
||||||
|
NewGovernorStatus(ctx context.Context, e GovernorStatus) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,3 +16,8 @@ func GetTrackIDForDuplicatedVAA(vaaID string) string {
|
||||||
uuid := uuid.New()
|
uuid := uuid.New()
|
||||||
return fmt.Sprintf("fly-duplicated-vaa-%s-%s", vaaID, uuid.String())
|
return fmt.Sprintf("fly-duplicated-vaa-%s-%s", vaaID, uuid.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetTrackIDForGovernorStatus(nodeName string, timestamp int64) string {
|
||||||
|
uuid := uuid.New()
|
||||||
|
return fmt.Sprintf("fly-governor-status-%s-%v-%s", nodeName, timestamp, uuid.String())
|
||||||
|
}
|
||||||
|
|
|
@ -260,6 +260,14 @@ func Run(db *mongo.Database) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create index in nodeGovernorVaas collection by vaaId.
|
||||||
|
indexNodeGovernorVaasByVaaId := mongo.IndexModel{
|
||||||
|
Keys: bson.D{{Key: "vaaId", Value: 1}}}
|
||||||
|
_, err = db.Collection("nodeGovernorVaas").Indexes().CreateOne(context.TODO(), indexNodeGovernorVaasByVaaId)
|
||||||
|
if err != nil && isNotAlreadyExistsError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -131,22 +131,22 @@ type GovernorStatusUpdate struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChainGovernorStatusChain struct {
|
type ChainGovernorStatusChain struct {
|
||||||
ChainId uint32 `bson:"chainid"`
|
ChainId uint32 `bson:"chainid" json:"chainId"`
|
||||||
RemainingAvailableNotional Uint64 `bson:"remainingavailablenotional"`
|
RemainingAvailableNotional Uint64 `bson:"remainingavailablenotional" json:"remainingAvailableNotional"`
|
||||||
Emitters []*ChainGovernorStatusEmitter `bson:"emitters"`
|
Emitters []*ChainGovernorStatusEmitter `bson:"emitters" json:"emitters"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChainGovernorStatusEmitter struct {
|
type ChainGovernorStatusEmitter struct {
|
||||||
EmitterAddress string `bson:"emitteraddress"`
|
EmitterAddress string `bson:"emitteraddress" json:"emitterAddress"`
|
||||||
TotalEnqueuedVaas Uint64 `bson:"totalenqueuedvaas"`
|
TotalEnqueuedVaas Uint64 `bson:"totalenqueuedvaas" json:"totalEnqueuedVaas"`
|
||||||
EnqueuedVaas []*ChainGovernorStatusEnqueuedVAA `bson:"enqueuedvaas"`
|
EnqueuedVaas []*ChainGovernorStatusEnqueuedVAA `bson:"enqueuedvaas" json:"enqueuedVaas"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChainGovernorStatusEnqueuedVAA struct {
|
type ChainGovernorStatusEnqueuedVAA struct {
|
||||||
Sequence string `bson:"sequence"`
|
Sequence string `bson:"sequence" json:"sequence"`
|
||||||
ReleaseTime uint32 `bson:"releasetime"`
|
ReleaseTime uint32 `bson:"releasetime" json:"releaseTime"`
|
||||||
NotionalValue Uint64 `bson:"notionalvalue"`
|
NotionalValue Uint64 `bson:"notionalvalue" json:"notionalValue"`
|
||||||
TxHash string `bson:"txhash"`
|
TxHash string `bson:"txhash" json:"txHash"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChainGovernorConfigUpdate struct {
|
type ChainGovernorConfigUpdate struct {
|
||||||
|
|
|
@ -335,8 +335,24 @@ func (s *Repository) UpsertGovernorStatus(govS *gossipv1.SignedChainGovernorStat
|
||||||
Error: err2,
|
Error: err2,
|
||||||
}
|
}
|
||||||
s.alertClient.CreateAndSend(context.TODO(), flyAlert.ErrorSaveGovernorStatus, alertContext)
|
s.alertClient.CreateAndSend(context.TODO(), flyAlert.ErrorSaveGovernorStatus, alertContext)
|
||||||
}
|
|
||||||
return err2
|
return err2
|
||||||
|
}
|
||||||
|
|
||||||
|
// send governor status to topic.
|
||||||
|
err3 := s.eventDispatcher.NewGovernorStatus(context.TODO(), event.GovernorStatus{
|
||||||
|
NodeAddress: id,
|
||||||
|
NodeName: status.NodeName,
|
||||||
|
Counter: status.Counter,
|
||||||
|
Timestamp: status.Timestamp,
|
||||||
|
Chains: status.Chains,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err3 != nil {
|
||||||
|
s.log.Error("Error sending governor status to topic",
|
||||||
|
zap.String("guardian", status.NodeName),
|
||||||
|
zap.Error(err3))
|
||||||
|
}
|
||||||
|
return err3
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Repository) updateVAACount(chainID vaa.ChainID) {
|
func (s *Repository) updateVAACount(chainID vaa.ChainID) {
|
||||||
|
|
|
@ -82,6 +82,7 @@ func createChangesDoc(source, _type string, timestamp *time.Time) bson.D {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpsertOriginTx upserts a source transaction document.
|
||||||
func (r *Repository) UpsertOriginTx(ctx context.Context, params *UpsertOriginTxParams) error {
|
func (r *Repository) UpsertOriginTx(ctx context.Context, params *UpsertOriginTxParams) error {
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
@ -364,3 +365,25 @@ func (r *Repository) GetDocumentsByVaas(
|
||||||
|
|
||||||
return globalTransactions, nil
|
return globalTransactions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SourceTxDoc represents a source transaction document.
|
||||||
|
type SourceTxDoc struct {
|
||||||
|
ID string `bson:"_id"`
|
||||||
|
OriginTx *struct {
|
||||||
|
ChainID int `bson:"chainId"`
|
||||||
|
Status string `bson:"status"`
|
||||||
|
Processed bool `bson:"processed"`
|
||||||
|
NativeTxHash string `bson:"nativeTxHash"`
|
||||||
|
From string `bson:"from"`
|
||||||
|
} `bson:"originTx"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindSourceTxById returns the source transaction document with the given ID.
|
||||||
|
func (r *Repository) FindSourceTxById(ctx context.Context, id string) (*SourceTxDoc, error) {
|
||||||
|
var sourceTxDoc SourceTxDoc
|
||||||
|
err := r.globalTransactions.FindOne(ctx, bson.M{"_id": id}).Decode(&sourceTxDoc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &sourceTxDoc, err
|
||||||
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ func NewServer(logger *zap.Logger, port string, pprofEnabled bool, vaaController
|
||||||
api.Get("/ready", ctrl.ReadyCheck)
|
api.Get("/ready", ctrl.ReadyCheck)
|
||||||
|
|
||||||
api.Post("/vaa/process", vaaController.Process)
|
api.Post("/vaa/process", vaaController.Process)
|
||||||
|
api.Post("/vaa/tx-hash", vaaController.CreateTxHash)
|
||||||
|
|
||||||
return &Server{
|
return &Server{
|
||||||
app: app,
|
app: app,
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
package vaa
|
package vaa
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/hex"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/common/pool"
|
"github.com/wormhole-foundation/wormhole-explorer/common/pool"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/common/utils"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/txtracker/consumer"
|
"github.com/wormhole-foundation/wormhole-explorer/txtracker/consumer"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/txtracker/internal/metrics"
|
"github.com/wormhole-foundation/wormhole-explorer/txtracker/internal/metrics"
|
||||||
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||||
|
@ -35,9 +39,7 @@ func NewController(rpcPool map[sdk.ChainID]*pool.Pool, wormchainRpcPool map[sdk.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) Process(ctx *fiber.Ctx) error {
|
func (c *Controller) Process(ctx *fiber.Ctx) error {
|
||||||
payload := struct {
|
var payload ProcessVaaRequest
|
||||||
ID string `json:"id"`
|
|
||||||
}{}
|
|
||||||
|
|
||||||
if err := ctx.BodyParser(&payload); err != nil {
|
if err := ctx.BodyParser(&payload); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -79,3 +81,72 @@ func (c *Controller) Process(ctx *fiber.Ctx) error {
|
||||||
Result any `json:"result"`
|
Result any `json:"result"`
|
||||||
}{Result: result})
|
}{Result: result})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Controller) CreateTxHash(ctx *fiber.Ctx) error {
|
||||||
|
|
||||||
|
var payload TxHashRequest
|
||||||
|
|
||||||
|
if err := ctx.BodyParser(&payload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
txHash, err := hex.DecodeString(utils.Remove0x(payload.TxHash))
|
||||||
|
if err != nil {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid tx hash", "details": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
c.logger.Info("Processing txHash from endpoint", zap.String("id", payload.ID))
|
||||||
|
|
||||||
|
vaaID := strings.Split(payload.ID, "/")
|
||||||
|
if len(vaaID) != 3 {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid vaa id"})
|
||||||
|
}
|
||||||
|
|
||||||
|
chainIDStr, emitter, sequenceStr := vaaID[0], vaaID[1], vaaID[2]
|
||||||
|
chainIDUint, err := strconv.ParseUint(chainIDStr, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "chain id is not a number", "details": err.Error()})
|
||||||
|
}
|
||||||
|
chainID := sdk.ChainID(chainIDUint)
|
||||||
|
if !domain.ChainIdIsValid(chainID) {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid chain id"})
|
||||||
|
}
|
||||||
|
|
||||||
|
encodedTxHash, err := domain.EncodeTrxHashByChainID(chainID, txHash)
|
||||||
|
if err != nil {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid tx hash", "details": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
if chainID != sdk.ChainIDSolana && chainID != sdk.ChainIDAptos && chainID != sdk.ChainIDWormchain {
|
||||||
|
return ctx.JSON(TxHashResponse{NativeTxHash: encodedTxHash})
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceTx, err := c.repository.FindSourceTxById(ctx.Context(), payload.ID)
|
||||||
|
if err == nil && sourceTx != nil {
|
||||||
|
if sourceTx.OriginTx != nil && sourceTx.OriginTx.NativeTxHash != "" {
|
||||||
|
return ctx.JSON(TxHashResponse{NativeTxHash: sourceTx.OriginTx.NativeTxHash})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &consumer.ProcessSourceTxParams{
|
||||||
|
TrackID: "controller-tx-hash",
|
||||||
|
Source: "controller",
|
||||||
|
Timestamp: nil,
|
||||||
|
VaaId: payload.ID,
|
||||||
|
ChainId: chainID,
|
||||||
|
Emitter: emitter,
|
||||||
|
Sequence: sequenceStr,
|
||||||
|
TxHash: encodedTxHash,
|
||||||
|
IsVaaSigned: false,
|
||||||
|
Metrics: c.metrics,
|
||||||
|
Overwrite: true,
|
||||||
|
DisableDBUpsert: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := consumer.ProcessSourceTx(ctx.Context(), c.logger, c.rpcPool, c.wormchainRpcPool, c.repository, p, c.p2pNetwork)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.JSON(TxHashResponse{NativeTxHash: result.NativeTxHash})
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ type Repository struct {
|
||||||
db *mongo.Database
|
db *mongo.Database
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
vaas *mongo.Collection
|
vaas *mongo.Collection
|
||||||
|
globalTransactions *mongo.Collection
|
||||||
}
|
}
|
||||||
|
|
||||||
type VaaDoc struct {
|
type VaaDoc struct {
|
||||||
|
@ -25,6 +26,7 @@ func NewRepository(db *mongo.Database, logger *zap.Logger) *Repository {
|
||||||
return &Repository{db: db,
|
return &Repository{db: db,
|
||||||
logger: logger.With(zap.String("module", "VaaRepository")),
|
logger: logger.With(zap.String("module", "VaaRepository")),
|
||||||
vaas: db.Collection("vaas"),
|
vaas: db.Collection("vaas"),
|
||||||
|
globalTransactions: db.Collection("globalTransactions"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package vaa
|
||||||
|
|
||||||
|
// ProcessVaaRequest request a vaa to process.
|
||||||
|
type ProcessVaaRequest struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxHashRequest request a tx hash.
|
||||||
|
type TxHashRequest struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
TxHash string `json:"txHash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessVaaResponse response from processing a vaa.
|
||||||
|
type TxHashResponse struct {
|
||||||
|
NativeTxHash string `json:"nativeTxHash"`
|
||||||
|
}
|
Loading…
Reference in New Issue