Rework presenter internals
This commit is contained in:
parent
ca028c32bc
commit
4c29ff7789
|
@ -1,11 +1,17 @@
|
||||||
package logging
|
package logging
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ctxKey int
|
||||||
|
|
||||||
|
const loggerCtxKey ctxKey = iota
|
||||||
|
|
||||||
type Logger logrus.FieldLogger
|
type Logger logrus.FieldLogger
|
||||||
|
|
||||||
func New() *logrus.Logger {
|
func New() *logrus.Logger {
|
||||||
|
@ -17,3 +23,21 @@ func New() *logrus.Logger {
|
||||||
})
|
})
|
||||||
return logger
|
return logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithLogger(ctx context.Context, logger Logger) context.Context {
|
||||||
|
return context.WithValue(ctx, loggerCtxKey, logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NullLogger() Logger {
|
||||||
|
log := logrus.New()
|
||||||
|
log.SetOutput(io.Discard)
|
||||||
|
return log
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoggerFromContext(ctx context.Context) Logger {
|
||||||
|
logger, ok := ctx.Value(loggerCtxKey).(Logger)
|
||||||
|
if ok {
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
return NullLogger()
|
||||||
|
}
|
||||||
|
|
|
@ -262,13 +262,15 @@ func (m *ContractMonitor) buildFilterQueries(blocksRange *BlocksRange) []ethereu
|
||||||
if token.EndBlock > 0 && blocksRange.From > token.EndBlock {
|
if token.EndBlock > 0 && blocksRange.From > token.EndBlock {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
q = ethereum.FilterQuery{}
|
q = ethereum.FilterQuery{
|
||||||
if blocksRange.Topic != nil {
|
FromBlock: big.NewInt(int64(blocksRange.From)),
|
||||||
q.Topics = [][]common.Hash{{*blocksRange.Topic}, {}, {m.cfg.Address.Hash()}}
|
ToBlock: big.NewInt(int64(blocksRange.To)),
|
||||||
} else {
|
Addresses: []common.Address{token.Address},
|
||||||
q.Topics = [][]common.Hash{{}, {}, {m.cfg.Address.Hash()}}
|
Topics: [][]common.Hash{{}, {}, {m.cfg.Address.Hash()}},
|
||||||
|
}
|
||||||
|
if blocksRange.Topic != nil {
|
||||||
|
q.Topics[0] = []common.Hash{*blocksRange.Topic}
|
||||||
}
|
}
|
||||||
q.Addresses = []common.Address{token.Address}
|
|
||||||
if token.StartBlock > 0 && token.StartBlock > blocksRange.From {
|
if token.StartBlock > 0 && token.StartBlock > blocksRange.From {
|
||||||
q.FromBlock = big.NewInt(int64(token.StartBlock))
|
q.FromBlock = big.NewInt(int64(token.StartBlock))
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
|
||||||
|
"github.com/poanetwork/tokenbridge-monitor/config"
|
||||||
|
"github.com/poanetwork/tokenbridge-monitor/presenter/http/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ctxKey int
|
||||||
|
|
||||||
|
const (
|
||||||
|
bridgeCfgCtxKey ctxKey = iota
|
||||||
|
chainCfgCtxKey
|
||||||
|
blockNumberCtxKey
|
||||||
|
txHashCtxKey
|
||||||
|
filterCtxKey
|
||||||
|
)
|
||||||
|
|
||||||
|
type FilterContext struct {
|
||||||
|
ChainID *string
|
||||||
|
FromBlock *uint
|
||||||
|
ToBlock *uint
|
||||||
|
TxHash *common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBridgeConfigMiddleware(cfg *config.Config) func(http.Handler) http.Handler {
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
bridgeID := chi.URLParam(r, "bridgeID")
|
||||||
|
|
||||||
|
bridgeCfg, ok := cfg.Bridges[bridgeID]
|
||||||
|
if !ok || bridgeCfg == nil {
|
||||||
|
render.JSON(w, r, http.StatusNotFound, fmt.Sprintf("bridge with id %s not found", bridgeID))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.WithValue(r.Context(), bridgeCfgCtxKey, bridgeCfg)
|
||||||
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BridgeConfig(ctx context.Context) *config.BridgeConfig {
|
||||||
|
if cfg, ok := ctx.Value(bridgeCfgCtxKey).(*config.BridgeConfig); ok {
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
return new(config.BridgeConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetChainConfigMiddleware(cfg *config.Config) func(http.Handler) http.Handler {
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
chainID := chi.URLParam(r, "chainID")
|
||||||
|
|
||||||
|
var chainCfg *config.ChainConfig
|
||||||
|
for _, c := range cfg.Chains {
|
||||||
|
if c.ChainID == chainID {
|
||||||
|
chainCfg = c
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if chainCfg == nil {
|
||||||
|
render.JSON(w, r, http.StatusNotFound, fmt.Sprintf("chain with id %s not found", chainID))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.WithValue(r.Context(), chainCfgCtxKey, chainCfg)
|
||||||
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBlockNumberMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
blockNumber, err := strconv.ParseUint(chi.URLParam(r, "blockNumber"), 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(w, r, fmt.Errorf("failed to parse blockNumber: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.WithValue(r.Context(), blockNumberCtxKey, uint(blockNumber))
|
||||||
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTxHashMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
txHash := chi.URLParam(r, "txHash")
|
||||||
|
|
||||||
|
ctx := context.WithValue(r.Context(), txHashCtxKey, common.HexToHash(txHash))
|
||||||
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFilterMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
filter := &FilterContext{}
|
||||||
|
|
||||||
|
if cfg, ok := ctx.Value(chainCfgCtxKey).(*config.ChainConfig); ok {
|
||||||
|
filter.ChainID = &cfg.ChainID
|
||||||
|
}
|
||||||
|
if blockNumber, ok := ctx.Value(blockNumberCtxKey).(uint); ok {
|
||||||
|
filter.FromBlock = &blockNumber
|
||||||
|
filter.ToBlock = &blockNumber
|
||||||
|
}
|
||||||
|
if txHash, ok := ctx.Value(txHashCtxKey).(common.Hash); ok {
|
||||||
|
filter.TxHash = &txHash
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = context.WithValue(ctx, filterCtxKey, filter)
|
||||||
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFilterContext(ctx context.Context) *FilterContext {
|
||||||
|
if cfg, ok := ctx.Value(filterCtxKey).(*FilterContext); ok {
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
return new(FilterContext)
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/poanetwork/tokenbridge-monitor/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewLoggerMiddleware(logger logging.Logger) func(next http.Handler) http.Handler {
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
logger = logger.WithFields(logrus.Fields{
|
||||||
|
"request_id": middleware.GetReqID(ctx),
|
||||||
|
"http_method": r.Method,
|
||||||
|
"http_path": r.RequestURI,
|
||||||
|
})
|
||||||
|
ctx = logging.WithLogger(ctx, logger)
|
||||||
|
|
||||||
|
logger.Info("handling http request")
|
||||||
|
ts := time.Now()
|
||||||
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
logger.WithField("duration", time.Since(ts)).Info("http request completed")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/poanetwork/tokenbridge-monitor/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Recoverer(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
logger := logging.LoggerFromContext(r.Context())
|
||||||
|
if err2, ok := err.(error); ok {
|
||||||
|
logger = logger.WithError(err2)
|
||||||
|
} else {
|
||||||
|
logger = logger.WithField("recovered", err)
|
||||||
|
}
|
||||||
|
logger.Error("recovered error from the http handler")
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
|
||||||
|
"github.com/poanetwork/tokenbridge-monitor/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
func JSON(w http.ResponseWriter, r *http.Request, status int, res interface{}) {
|
||||||
|
enc := json.NewEncoder(w)
|
||||||
|
|
||||||
|
if pretty, _ := strconv.ParseBool(chi.URLParam(r, "pretty")); pretty {
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(status)
|
||||||
|
if err := enc.Encode(res); err != nil {
|
||||||
|
Error(w, r, fmt.Errorf("failed to marshal JSON result: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Error(w http.ResponseWriter, r *http.Request, err error) {
|
||||||
|
logger := logging.LoggerFromContext(r.Context())
|
||||||
|
logger.WithError(err).Error("request handling failed")
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
|
@ -1,67 +0,0 @@
|
||||||
package presenter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/poanetwork/tokenbridge-monitor/logging"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewRequestLogger(logger logging.Logger) func(next http.Handler) http.Handler {
|
|
||||||
return middleware.RequestLogger(&StructuredLogger{logger})
|
|
||||||
}
|
|
||||||
|
|
||||||
type StructuredLogger struct {
|
|
||||||
Logger logging.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *StructuredLogger) NewLogEntry(r *http.Request) middleware.LogEntry {
|
|
||||||
entry := &StructuredLoggerEntry{Logger: l.Logger}
|
|
||||||
logFields := logrus.Fields{}
|
|
||||||
|
|
||||||
if reqID := middleware.GetReqID(r.Context()); reqID != "" {
|
|
||||||
logFields["req_id"] = reqID
|
|
||||||
}
|
|
||||||
|
|
||||||
scheme := "http"
|
|
||||||
if r.TLS != nil {
|
|
||||||
scheme = "https"
|
|
||||||
}
|
|
||||||
logFields["http_scheme"] = scheme
|
|
||||||
logFields["http_proto"] = r.Proto
|
|
||||||
logFields["http_method"] = r.Method
|
|
||||||
logFields["remote_addr"] = r.RemoteAddr
|
|
||||||
|
|
||||||
logFields["uri"] = fmt.Sprintf("%s://%s%s", scheme, r.Host, r.RequestURI)
|
|
||||||
|
|
||||||
entry.Logger = entry.Logger.WithFields(logFields)
|
|
||||||
|
|
||||||
entry.Logger.Infoln("request started")
|
|
||||||
|
|
||||||
return entry
|
|
||||||
}
|
|
||||||
|
|
||||||
type StructuredLoggerEntry struct {
|
|
||||||
Logger logrus.FieldLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *StructuredLoggerEntry) Write(status, bytes int, header http.Header, elapsed time.Duration, extra interface{}) {
|
|
||||||
l.Logger = l.Logger.WithFields(logrus.Fields{
|
|
||||||
"resp_status": status,
|
|
||||||
"resp_bytes_length": bytes,
|
|
||||||
"resp_elapsed_ms": elapsed,
|
|
||||||
})
|
|
||||||
|
|
||||||
l.Logger.Infoln("request completed")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *StructuredLoggerEntry) Panic(v interface{}, stack []byte) {
|
|
||||||
l.Logger = l.Logger.WithFields(logrus.Fields{
|
|
||||||
"stack": string(stack),
|
|
||||||
"panic": fmt.Sprintf("%+v", v),
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -2,34 +2,24 @@ package presenter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
chimiddleware "github.com/go-chi/chi/v5/middleware"
|
||||||
|
|
||||||
"github.com/poanetwork/tokenbridge-monitor/config"
|
"github.com/poanetwork/tokenbridge-monitor/config"
|
||||||
"github.com/poanetwork/tokenbridge-monitor/db"
|
"github.com/poanetwork/tokenbridge-monitor/db"
|
||||||
"github.com/poanetwork/tokenbridge-monitor/entity"
|
"github.com/poanetwork/tokenbridge-monitor/entity"
|
||||||
"github.com/poanetwork/tokenbridge-monitor/logging"
|
"github.com/poanetwork/tokenbridge-monitor/logging"
|
||||||
|
"github.com/poanetwork/tokenbridge-monitor/presenter/http/middleware"
|
||||||
|
"github.com/poanetwork/tokenbridge-monitor/presenter/http/render"
|
||||||
"github.com/poanetwork/tokenbridge-monitor/repository"
|
"github.com/poanetwork/tokenbridge-monitor/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ctxKey int
|
|
||||||
|
|
||||||
const (
|
|
||||||
BridgeCfgCtxKey ctxKey = iota
|
|
||||||
ChainCfgCtxKey
|
|
||||||
BlockNumberCtxKey
|
|
||||||
TxHashCtxKey
|
|
||||||
FilterCtxKey
|
|
||||||
)
|
|
||||||
|
|
||||||
type Presenter struct {
|
type Presenter struct {
|
||||||
logger logging.Logger
|
logger logging.Logger
|
||||||
repo *repository.Repo
|
repo *repository.Repo
|
||||||
|
@ -48,145 +38,41 @@ func NewPresenter(logger logging.Logger, repo *repository.Repo, cfg *config.Conf
|
||||||
|
|
||||||
func (p *Presenter) Serve(addr string) error {
|
func (p *Presenter) Serve(addr string) error {
|
||||||
p.logger.WithField("addr", addr).Info("starting presenter service")
|
p.logger.WithField("addr", addr).Info("starting presenter service")
|
||||||
p.root.Use(middleware.Throttle(5))
|
p.root.Use(chimiddleware.Throttle(5))
|
||||||
p.root.Use(middleware.RequestID)
|
p.root.Use(chimiddleware.RequestID)
|
||||||
p.root.Use(NewRequestLogger(p.logger))
|
p.root.Use(middleware.NewLoggerMiddleware(p.logger))
|
||||||
|
p.root.Use(middleware.Recoverer)
|
||||||
registerSearchRoutes := func(r chi.Router) {
|
registerSearchRoutes := func(r chi.Router) {
|
||||||
r.Use(p.GetFilterMiddleware)
|
r.Use(middleware.GetFilterMiddleware)
|
||||||
r.Get("/", p.GetMessages)
|
r.Get("/", p.GetMessages)
|
||||||
r.Get("/logs", p.GetLogs)
|
r.Get("/logs", p.GetLogs)
|
||||||
r.Get("/messages", p.GetMessages)
|
r.Get("/messages", p.GetMessages)
|
||||||
}
|
}
|
||||||
p.root.Route("/bridge/{bridgeID:[0-9a-zA-Z_\\-]+}", func(r chi.Router) {
|
p.root.Route("/bridge/{bridgeID:[0-9a-zA-Z_\\-]+}", func(r chi.Router) {
|
||||||
r.Use(p.GetBridgeConfigMiddleware)
|
r.Use(middleware.GetBridgeConfigMiddleware(p.cfg))
|
||||||
r.Get("/", p.GetBridgeInfo)
|
r.Get("/", p.GetBridgeInfo)
|
||||||
r.Get("/info", p.GetBridgeInfo)
|
r.Get("/info", p.GetBridgeInfo)
|
||||||
r.Get("/config", p.GetBridgeConfig)
|
r.Get("/config", p.GetBridgeConfig)
|
||||||
r.Get("/validators", p.GetBridgeValidators)
|
r.Get("/validators", p.GetBridgeValidators)
|
||||||
})
|
})
|
||||||
p.root.Route("/chain/{chainID:[0-9]+}", func(r chi.Router) {
|
p.root.Route("/chain/{chainID:[0-9]+}", func(r chi.Router) {
|
||||||
r.Use(p.GetChainConfigMiddleware)
|
r.Use(middleware.GetChainConfigMiddleware(p.cfg))
|
||||||
r.Route("/block/{blockNumber:[0-9]+}", func(r2 chi.Router) {
|
r.Route("/block/{blockNumber:[0-9]+}", func(r2 chi.Router) {
|
||||||
r2.Use(p.GetBlockNumberMiddleware)
|
r2.Use(middleware.GetBlockNumberMiddleware)
|
||||||
r2.Group(registerSearchRoutes)
|
r2.Group(registerSearchRoutes)
|
||||||
})
|
})
|
||||||
r.Route("/tx/{txHash:0x[0-9a-fA-F]{64}}", func(r2 chi.Router) {
|
r.Route("/tx/{txHash:0x[0-9a-fA-F]{64}}", func(r2 chi.Router) {
|
||||||
r2.Use(p.GetTxHashMiddleware)
|
r2.Use(middleware.GetTxHashMiddleware)
|
||||||
r2.Group(registerSearchRoutes)
|
r2.Group(registerSearchRoutes)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
p.root.Route("/tx/{txHash:0x[0-9a-fA-F]{64}}", func(r chi.Router) {
|
p.root.Route("/tx/{txHash:0x[0-9a-fA-F]{64}}", func(r chi.Router) {
|
||||||
r.Use(p.GetTxHashMiddleware)
|
r.Use(middleware.GetTxHashMiddleware)
|
||||||
r.Group(registerSearchRoutes)
|
r.Group(registerSearchRoutes)
|
||||||
})
|
})
|
||||||
return http.ListenAndServe(addr, p.root)
|
return http.ListenAndServe(addr, p.root)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Presenter) JSON(w http.ResponseWriter, r *http.Request, status int, res interface{}) {
|
|
||||||
enc := json.NewEncoder(w)
|
|
||||||
|
|
||||||
if pretty, _ := strconv.ParseBool(chi.URLParam(r, "pretty")); pretty {
|
|
||||||
enc.SetIndent("", " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(status)
|
|
||||||
if err := enc.Encode(res); err != nil {
|
|
||||||
p.Error(w, r, fmt.Errorf("failed to marshal JSON result: %w", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Presenter) Error(w http.ResponseWriter, r *http.Request, err error) {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
_, err = w.Write([]byte(err.Error()))
|
|
||||||
p.logger.WithError(err).Error("request handling failed")
|
|
||||||
if err != nil {
|
|
||||||
p.logger.WithError(err).Error("can't write error response")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Presenter) GetBridgeConfigMiddleware(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
bridgeID := chi.URLParam(r, "bridgeID")
|
|
||||||
|
|
||||||
cfg, ok := p.cfg.Bridges[bridgeID]
|
|
||||||
if !ok || cfg == nil {
|
|
||||||
p.JSON(w, r, http.StatusNotFound, fmt.Sprintf("bridge with id %s not found", bridgeID))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.WithValue(r.Context(), BridgeCfgCtxKey, cfg)
|
|
||||||
next.ServeHTTP(w, r.WithContext(ctx))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Presenter) GetChainConfigMiddleware(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
chainID := chi.URLParam(r, "chainID")
|
|
||||||
|
|
||||||
var cfg *config.ChainConfig
|
|
||||||
for _, chainCfg := range p.cfg.Chains {
|
|
||||||
if chainCfg.ChainID == chainID {
|
|
||||||
cfg = chainCfg
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if cfg == nil {
|
|
||||||
p.JSON(w, r, http.StatusNotFound, fmt.Sprintf("chain with id %s not found", chainID))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.WithValue(r.Context(), ChainCfgCtxKey, cfg)
|
|
||||||
next.ServeHTTP(w, r.WithContext(ctx))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Presenter) GetBlockNumberMiddleware(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
blockNumber, err := strconv.ParseUint(chi.URLParam(r, "blockNumber"), 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
p.Error(w, r, fmt.Errorf("failed to parse blockNumber: %w", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.WithValue(r.Context(), BlockNumberCtxKey, uint(blockNumber))
|
|
||||||
next.ServeHTTP(w, r.WithContext(ctx))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Presenter) GetTxHashMiddleware(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
txHash := chi.URLParam(r, "txHash")
|
|
||||||
|
|
||||||
ctx := context.WithValue(r.Context(), TxHashCtxKey, common.HexToHash(txHash))
|
|
||||||
next.ServeHTTP(w, r.WithContext(ctx))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Presenter) GetFilterMiddleware(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := r.Context()
|
|
||||||
|
|
||||||
filter := &FilterContext{}
|
|
||||||
|
|
||||||
if cfg, ok := ctx.Value(ChainCfgCtxKey).(*config.ChainConfig); ok {
|
|
||||||
filter.ChainID = &cfg.ChainID
|
|
||||||
}
|
|
||||||
if blockNumber, ok := ctx.Value(BlockNumberCtxKey).(uint); ok {
|
|
||||||
filter.FromBlock = &blockNumber
|
|
||||||
filter.ToBlock = &blockNumber
|
|
||||||
}
|
|
||||||
if txHash, ok := ctx.Value(TxHashCtxKey).(common.Hash); ok {
|
|
||||||
filter.TxHash = &txHash
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, FilterCtxKey, filter)
|
|
||||||
next.ServeHTTP(w, r.WithContext(ctx))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Presenter) getBridgeSideInfo(ctx context.Context, bridgeID string, cfg *config.BridgeSideConfig) (*BridgeSideInfo, error) {
|
func (p *Presenter) getBridgeSideInfo(ctx context.Context, bridgeID string, cfg *config.BridgeSideConfig) (*BridgeSideInfo, error) {
|
||||||
cursor, err := p.repo.LogsCursors.GetByChainIDAndAddress(ctx, cfg.Chain.ChainID, cfg.Address)
|
cursor, err := p.repo.LogsCursors.GetByChainIDAndAddress(ctx, cfg.Chain.ChainID, cfg.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -234,28 +120,27 @@ func (p *Presenter) getBridgeSideInfo(ctx context.Context, bridgeID string, cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Presenter) GetBridgeConfig(w http.ResponseWriter, r *http.Request) {
|
func (p *Presenter) GetBridgeConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
cfg := middleware.BridgeConfig(r.Context())
|
||||||
cfg, _ := ctx.Value(BridgeCfgCtxKey).(*config.BridgeConfig)
|
|
||||||
|
|
||||||
p.JSON(w, r, http.StatusOK, cfg)
|
render.JSON(w, r, http.StatusOK, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Presenter) GetBridgeInfo(w http.ResponseWriter, r *http.Request) {
|
func (p *Presenter) GetBridgeInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
cfg, _ := ctx.Value(BridgeCfgCtxKey).(*config.BridgeConfig)
|
cfg := middleware.BridgeConfig(ctx)
|
||||||
|
|
||||||
homeInfo, err := p.getBridgeSideInfo(ctx, cfg.ID, cfg.Home)
|
homeInfo, err := p.getBridgeSideInfo(ctx, cfg.ID, cfg.Home)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Error(w, r, fmt.Errorf("failed to get home bridge info: %w", err))
|
render.Error(w, r, fmt.Errorf("failed to get home bridge info: %w", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
foreignInfo, err := p.getBridgeSideInfo(ctx, cfg.ID, cfg.Foreign)
|
foreignInfo, err := p.getBridgeSideInfo(ctx, cfg.ID, cfg.Foreign)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Error(w, r, fmt.Errorf("failed to get foreign bridge info: %w", err))
|
render.Error(w, r, fmt.Errorf("failed to get foreign bridge info: %w", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
p.JSON(w, r, http.StatusOK, &BridgeInfo{
|
render.JSON(w, r, http.StatusOK, &BridgeInfo{
|
||||||
BridgeID: cfg.ID,
|
BridgeID: cfg.ID,
|
||||||
Mode: cfg.BridgeMode,
|
Mode: cfg.BridgeMode,
|
||||||
Home: homeInfo,
|
Home: homeInfo,
|
||||||
|
@ -265,17 +150,17 @@ func (p *Presenter) GetBridgeInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
func (p *Presenter) GetBridgeValidators(w http.ResponseWriter, r *http.Request) {
|
func (p *Presenter) GetBridgeValidators(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
cfg, _ := ctx.Value(BridgeCfgCtxKey).(*config.BridgeConfig)
|
cfg := middleware.BridgeConfig(ctx)
|
||||||
|
|
||||||
homeValidators, err := p.repo.BridgeValidators.FindActiveValidators(ctx, cfg.ID, cfg.Home.Chain.ChainID)
|
homeValidators, err := p.repo.BridgeValidators.FindActiveValidators(ctx, cfg.ID, cfg.Home.Chain.ChainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Error(w, r, fmt.Errorf("failed to find home validators: %w", err))
|
render.Error(w, r, fmt.Errorf("failed to find home validators: %w", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
foreignValidators, err := p.repo.BridgeValidators.FindActiveValidators(ctx, cfg.ID, cfg.Home.Chain.ChainID)
|
foreignValidators, err := p.repo.BridgeValidators.FindActiveValidators(ctx, cfg.ID, cfg.Home.Chain.ChainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Error(w, r, fmt.Errorf("failed to find home validators: %w", err))
|
render.Error(w, r, fmt.Errorf("failed to find home validators: %w", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,24 +184,24 @@ func (p *Presenter) GetBridgeValidators(w http.ResponseWriter, r *http.Request)
|
||||||
confirmation, err2 := p.repo.SignedMessages.FindLatest(ctx, cfg.ID, cfg.Home.Chain.ChainID, val.Address)
|
confirmation, err2 := p.repo.SignedMessages.FindLatest(ctx, cfg.ID, cfg.Home.Chain.ChainID, val.Address)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
if !errors.Is(err, db.ErrNotFound) {
|
if !errors.Is(err, db.ErrNotFound) {
|
||||||
p.Error(w, r, fmt.Errorf("failed to find latest validator confirmation: %w", err))
|
render.Error(w, r, fmt.Errorf("failed to find latest validator confirmation: %w", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
valInfo.LastConfirmation, err = p.getTxInfo(ctx, confirmation.LogID)
|
valInfo.LastConfirmation, err = p.getTxInfo(ctx, confirmation.LogID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Error(w, r, fmt.Errorf("failed to get tx info: %w", err))
|
render.Error(w, r, fmt.Errorf("failed to get tx info: %w", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.Validators = append(res.Validators, valInfo)
|
res.Validators = append(res.Validators, valInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
p.JSON(w, r, http.StatusOK, res)
|
render.JSON(w, r, http.StatusOK, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Presenter) getFilteredLogs(ctx context.Context) ([]*entity.Log, error) {
|
func (p *Presenter) getFilteredLogs(ctx context.Context) ([]*entity.Log, error) {
|
||||||
filter, _ := ctx.Value(FilterCtxKey).(*FilterContext)
|
filter := middleware.GetFilterContext(ctx)
|
||||||
|
|
||||||
if filter.TxHash != nil {
|
if filter.TxHash != nil {
|
||||||
logs, err := p.repo.Logs.FindByTxHash(ctx, *filter.TxHash)
|
logs, err := p.repo.Logs.FindByTxHash(ctx, *filter.TxHash)
|
||||||
|
@ -347,18 +232,18 @@ func (p *Presenter) getFilteredLogs(ctx context.Context) ([]*entity.Log, error)
|
||||||
func (p *Presenter) GetMessages(w http.ResponseWriter, r *http.Request) {
|
func (p *Presenter) GetMessages(w http.ResponseWriter, r *http.Request) {
|
||||||
logs, err := p.getFilteredLogs(r.Context())
|
logs, err := p.getFilteredLogs(r.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Error(w, r, fmt.Errorf("can't filter logs: %w", err))
|
render.Error(w, r, fmt.Errorf("can't filter logs: %w", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
res := p.searchForMessagesInLogs(r.Context(), logs)
|
res := p.searchForMessagesInLogs(r.Context(), logs)
|
||||||
p.JSON(w, r, http.StatusOK, res)
|
render.JSON(w, r, http.StatusOK, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Presenter) GetLogs(w http.ResponseWriter, r *http.Request) {
|
func (p *Presenter) GetLogs(w http.ResponseWriter, r *http.Request) {
|
||||||
logs, err := p.getFilteredLogs(r.Context())
|
logs, err := p.getFilteredLogs(r.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Error(w, r, fmt.Errorf("can't filter logs: %w", err))
|
render.Error(w, r, fmt.Errorf("can't filter logs: %w", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,7 +263,7 @@ func (p *Presenter) GetLogs(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p.JSON(w, r, http.StatusOK, res)
|
render.JSON(w, r, http.StatusOK, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Presenter) searchForMessagesInLogs(ctx context.Context, logs []*entity.Log) []*SearchResult {
|
func (p *Presenter) searchForMessagesInLogs(ctx context.Context, logs []*entity.Log) []*SearchResult {
|
||||||
|
@ -426,7 +311,7 @@ func (p *Presenter) searchSentMessage(ctx context.Context, log *entity.Log) (*Se
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
return nil, err2
|
return nil, err2
|
||||||
}
|
}
|
||||||
messageInfo = ercToNativeMessageToInfo(ercToNativeMsg)
|
messageInfo = NewErcToNativeMessageInfo(ercToNativeMsg)
|
||||||
events, err = p.buildMessageEvents(ctx, ercToNativeMsg.BridgeID, ercToNativeMsg.MsgHash, ercToNativeMsg.MsgHash)
|
events, err = p.buildMessageEvents(ctx, ercToNativeMsg.BridgeID, ercToNativeMsg.MsgHash, ercToNativeMsg.MsgHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -434,7 +319,7 @@ func (p *Presenter) searchSentMessage(ctx context.Context, log *entity.Log) (*Se
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
messageInfo = messageToInfo(msg)
|
messageInfo = NewMessageInfo(msg)
|
||||||
events, err = p.buildMessageEvents(ctx, msg.BridgeID, msg.MsgHash, msg.MessageID)
|
events, err = p.buildMessageEvents(ctx, msg.BridgeID, msg.MsgHash, msg.MessageID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -461,7 +346,7 @@ func (p *Presenter) searchSignedMessage(ctx context.Context, log *entity.Log) (*
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
return nil, err2
|
return nil, err2
|
||||||
}
|
}
|
||||||
messageInfo = ercToNativeMessageToInfo(ercToNativeMsg)
|
messageInfo = NewErcToNativeMessageInfo(ercToNativeMsg)
|
||||||
events, err = p.buildMessageEvents(ctx, ercToNativeMsg.BridgeID, ercToNativeMsg.MsgHash, ercToNativeMsg.MsgHash)
|
events, err = p.buildMessageEvents(ctx, ercToNativeMsg.BridgeID, ercToNativeMsg.MsgHash, ercToNativeMsg.MsgHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -469,7 +354,7 @@ func (p *Presenter) searchSignedMessage(ctx context.Context, log *entity.Log) (*
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
messageInfo = messageToInfo(msg)
|
messageInfo = NewMessageInfo(msg)
|
||||||
events, err = p.buildMessageEvents(ctx, msg.BridgeID, msg.MsgHash, msg.MessageID)
|
events, err = p.buildMessageEvents(ctx, msg.BridgeID, msg.MsgHash, msg.MessageID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -495,7 +380,7 @@ func (p *Presenter) searchExecutedMessage(ctx context.Context, log *entity.Log)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
return nil, err2
|
return nil, err2
|
||||||
}
|
}
|
||||||
messageInfo = ercToNativeMessageToInfo(ercToNativeMsg)
|
messageInfo = NewErcToNativeMessageInfo(ercToNativeMsg)
|
||||||
events, err = p.buildMessageEvents(ctx, ercToNativeMsg.BridgeID, ercToNativeMsg.MsgHash, ercToNativeMsg.MsgHash)
|
events, err = p.buildMessageEvents(ctx, ercToNativeMsg.BridgeID, ercToNativeMsg.MsgHash, ercToNativeMsg.MsgHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -503,7 +388,7 @@ func (p *Presenter) searchExecutedMessage(ctx context.Context, log *entity.Log)
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
messageInfo = messageToInfo(msg)
|
messageInfo = NewMessageInfo(msg)
|
||||||
events, err = p.buildMessageEvents(ctx, msg.BridgeID, msg.MsgHash, msg.MessageID)
|
events, err = p.buildMessageEvents(ctx, msg.BridgeID, msg.MsgHash, msg.MessageID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -530,7 +415,7 @@ func (p *Presenter) searchSentInformationRequest(ctx context.Context, log *entit
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &SearchResult{
|
return &SearchResult{
|
||||||
Message: informationRequestToInfo(req),
|
Message: NewInformationRequestInfo(req),
|
||||||
RelatedEvents: events,
|
RelatedEvents: events,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -550,7 +435,7 @@ func (p *Presenter) searchSignedInformationRequest(ctx context.Context, log *ent
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &SearchResult{
|
return &SearchResult{
|
||||||
Message: informationRequestToInfo(req),
|
Message: NewInformationRequestInfo(req),
|
||||||
RelatedEvents: events,
|
RelatedEvents: events,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -570,7 +455,7 @@ func (p *Presenter) searchExecutedInformationRequest(ctx context.Context, log *e
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &SearchResult{
|
return &SearchResult{
|
||||||
Message: informationRequestToInfo(req),
|
Message: NewInformationRequestInfo(req),
|
||||||
RelatedEvents: events,
|
RelatedEvents: events,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -687,6 +572,6 @@ func (p *Presenter) getTxInfo(ctx context.Context, logID uint) (*TxInfo, error)
|
||||||
return &TxInfo{
|
return &TxInfo{
|
||||||
BlockNumber: log.BlockNumber,
|
BlockNumber: log.BlockNumber,
|
||||||
Timestamp: bt.Timestamp,
|
Timestamp: bt.Timestamp,
|
||||||
Link: logToTxLink(log),
|
Link: FormatLogTxLinkURL(log),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,10 +101,3 @@ type TxInfo struct {
|
||||||
Timestamp time.Time
|
Timestamp time.Time
|
||||||
Link string
|
Link string
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilterContext struct {
|
|
||||||
ChainID *string
|
|
||||||
FromBlock *uint
|
|
||||||
ToBlock *uint
|
|
||||||
TxHash *common.Hash
|
|
||||||
}
|
|
||||||
|
|
|
@ -16,14 +16,14 @@ var formats = map[string]string{
|
||||||
"100": "https://blockscout.com/xdai/mainnet/tx/%s",
|
"100": "https://blockscout.com/xdai/mainnet/tx/%s",
|
||||||
}
|
}
|
||||||
|
|
||||||
func logToTxLink(log *entity.Log) string {
|
func FormatLogTxLinkURL(log *entity.Log) string {
|
||||||
if format, ok := formats[log.ChainID]; ok {
|
if format, ok := formats[log.ChainID]; ok {
|
||||||
return fmt.Sprintf(format, log.TransactionHash)
|
return fmt.Sprintf(format, log.TransactionHash)
|
||||||
}
|
}
|
||||||
return log.TransactionHash.String()
|
return log.TransactionHash.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func messageToInfo(msg *entity.Message) *MessageInfo {
|
func NewMessageInfo(msg *entity.Message) *MessageInfo {
|
||||||
return &MessageInfo{
|
return &MessageInfo{
|
||||||
BridgeID: msg.BridgeID,
|
BridgeID: msg.BridgeID,
|
||||||
MsgHash: msg.MsgHash,
|
MsgHash: msg.MsgHash,
|
||||||
|
@ -35,7 +35,7 @@ func messageToInfo(msg *entity.Message) *MessageInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func informationRequestToInfo(req *entity.InformationRequest) *InformationRequestInfo {
|
func NewInformationRequestInfo(req *entity.InformationRequest) *InformationRequestInfo {
|
||||||
return &InformationRequestInfo{
|
return &InformationRequestInfo{
|
||||||
BridgeID: req.BridgeID,
|
BridgeID: req.BridgeID,
|
||||||
MessageID: req.MessageID,
|
MessageID: req.MessageID,
|
||||||
|
@ -45,7 +45,7 @@ func informationRequestToInfo(req *entity.InformationRequest) *InformationReques
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ercToNativeMessageToInfo(req *entity.ErcToNativeMessage) *ErcToNativeMessageInfo {
|
func NewErcToNativeMessageInfo(req *entity.ErcToNativeMessage) *ErcToNativeMessageInfo {
|
||||||
return &ErcToNativeMessageInfo{
|
return &ErcToNativeMessageInfo{
|
||||||
BridgeID: req.BridgeID,
|
BridgeID: req.BridgeID,
|
||||||
MsgHash: req.MsgHash,
|
MsgHash: req.MsgHash,
|
||||||
|
|
Loading…
Reference in New Issue