Skip to content

Commit

Permalink
Merge 88f9165 into 4a6bab0
Browse files Browse the repository at this point in the history
  • Loading branch information
golangisfun123 authored Oct 28, 2024
2 parents 4a6bab0 + 88f9165 commit 8ddb7de
Show file tree
Hide file tree
Showing 43 changed files with 507 additions and 318 deletions.
11 changes: 7 additions & 4 deletions contrib/opbot/botmd/botmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/slack-io/slacker"
"github.com/synapsecns/sanguine/contrib/opbot/config"
"github.com/synapsecns/sanguine/contrib/opbot/internal"
"github.com/synapsecns/sanguine/contrib/opbot/signoz"
screenerClient "github.com/synapsecns/sanguine/contrib/screener-api/client"
"github.com/synapsecns/sanguine/core/dbcommon"
Expand All @@ -29,6 +30,7 @@ type Bot struct {
signozClient *signoz.Client
signozEnabled bool
rpcClient omnirpcClient.RPCClient
rfqClient internal.RFQClient
signer signer.Signer
submitter submitter.TransactionSubmitter
screener screenerClient.ScreenerClient
Expand All @@ -42,10 +44,11 @@ func NewBot(handler metrics.Handler, cfg config.Config) *Bot {
sugaredLogger := otelzap.New(experimentalLogger.MakeZapLogger()).Sugar()

bot := Bot{
handler: handler,
cfg: cfg,
server: server,
logger: experimentalLogger.MakeWrappedSugaredLogger(sugaredLogger),
handler: handler,
cfg: cfg,
server: server,
logger: experimentalLogger.MakeWrappedSugaredLogger(sugaredLogger),
rfqClient: internal.NewRFQClient(handler, cfg.RFQIndexerAPIURL, cfg.RelayerURLS),
}

// you should be able to run opbot even without signoz.
Expand Down
213 changes: 65 additions & 148 deletions contrib/opbot/botmd/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"regexp"
"sort"
"strings"
"sync"
"time"

"github.com/dustin/go-humanize"
Expand All @@ -21,14 +20,13 @@ import (
"github.com/hako/durafmt"
"github.com/slack-go/slack"
"github.com/slack-io/slacker"
"github.com/synapsecns/sanguine/contrib/opbot/internal"
"github.com/synapsecns/sanguine/contrib/opbot/signoz"
"github.com/synapsecns/sanguine/core/retry"
"github.com/synapsecns/sanguine/ethergo/chaindata"
"github.com/synapsecns/sanguine/ethergo/client"
"github.com/synapsecns/sanguine/ethergo/submitter"
rfqClient "github.com/synapsecns/sanguine/services/rfq/api/client"
"github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge"
"github.com/synapsecns/sanguine/services/rfq/relayer/relapi"
)

func (b *Bot) requiresSignoz(definition *slacker.CommandDefinition) *slacker.CommandDefinition {
Expand Down Expand Up @@ -159,62 +157,17 @@ func (b *Bot) traceCommand() *slacker.CommandDefinition {
func (b *Bot) rfqLookupCommand() *slacker.CommandDefinition {
return &slacker.CommandDefinition{
Command: "rfq <tx>",
Description: "find a rfq transaction by either tx hash or txid on all configured relayers",
Description: "find a rfq transaction by either tx hash or txid from the rfq-indexer api",
Examples: []string{
"rfq 0x30f96b45ba689c809f7e936c140609eb31c99b182bef54fccf49778716a7e1ca",
},
Handler: func(ctx *slacker.CommandContext) {
type Status struct {
relayer string
*relapi.GetQuoteRequestResponse
}

var statuses []Status
var sliceMux sync.Mutex

if len(b.cfg.RelayerURLS) == 0 {
_, err := ctx.Response().Reply("no relayer urls configured")
if err != nil {
log.Println(err)
}
return
}

tx := stripLinks(ctx.Request().Param("tx"))

var wg sync.WaitGroup
// 2 routines per relayer, one for tx hashh one for tx id
wg.Add(len(b.cfg.RelayerURLS) * 2)
for _, relayer := range b.cfg.RelayerURLS {
client := relapi.NewRelayerClient(b.handler, relayer)
go func() {
defer wg.Done()
res, err := client.GetQuoteRequestByTxHash(ctx.Context(), tx)
if err != nil {
log.Printf("error fetching quote request status by tx hash: %v\n", err)
return
}
sliceMux.Lock()
defer sliceMux.Unlock()
statuses = append(statuses, Status{relayer: relayer, GetQuoteRequestResponse: res})
}()

go func() {
defer wg.Done()
res, err := client.GetQuoteRequestByTXID(ctx.Context(), tx)
if err != nil {
log.Printf("error fetching quote request status by tx id: %v\n", err)
return
}
sliceMux.Lock()
defer sliceMux.Unlock()
statuses = append(statuses, Status{relayer: relayer, GetQuoteRequestResponse: res})
}()
}
wg.Wait()

if len(statuses) == 0 {
_, err := ctx.Response().Reply("no quote request found")
res, status, err := b.rfqClient.GetRFQ(ctx.Context(), tx)
if err != nil {
b.logger.Errorf(ctx.Context(), "error fetching quote request: %v", err)
_, err := ctx.Response().Reply(fmt.Sprintf("error fetching quote request %s", err.Error()))
if err != nil {
log.Println(err)
}
Expand All @@ -223,51 +176,46 @@ func (b *Bot) rfqLookupCommand() *slacker.CommandDefinition {

var slackBlocks []slack.Block

for _, status := range statuses {
client, err := b.rpcClient.GetChainClient(ctx.Context(), int(status.OriginChainID))
if err != nil {
log.Printf("error getting chain client: %v\n", err)
}

objects := []*slack.TextBlockObject{
{
Type: slack.MarkdownType,
Text: fmt.Sprintf("*Relayer*: %s", status.relayer),
},
{
Type: slack.MarkdownType,
Text: fmt.Sprintf("*Status*: %s", status.Status),
},
{
Type: slack.MarkdownType,
Text: fmt.Sprintf("*TxID*: %s", toExplorerSlackLink(status.TxID)),
},
{
Type: slack.MarkdownType,
Text: fmt.Sprintf("*OriginTxHash*: %s", toTXSlackLink(status.OriginTxHash, status.OriginChainID)),
},
{
Type: slack.MarkdownType,
Text: fmt.Sprintf("*Estimated Tx Age*: %s", getTxAge(ctx.Context(), client, status.GetQuoteRequestResponse)),
},
}

if status.DestTxHash == (common.Hash{}).String() {
objects = append(objects, &slack.TextBlockObject{
Type: slack.MarkdownType,
Text: "*DestTxHash*: not available",
})
} else {
objects = append(objects, &slack.TextBlockObject{
Type: slack.MarkdownType,
Text: fmt.Sprintf("*DestTxHash*: %s", toTXSlackLink(status.DestTxHash, status.DestChainID)),
})
}
objects := []*slack.TextBlockObject{
{
Type: slack.MarkdownType,
Text: fmt.Sprintf("*Relayer*: %s", res.BridgeRelay.Relayer),
},
{
Type: slack.MarkdownType,
Text: fmt.Sprintf("*Status*: %s", status),
},
{
Type: slack.MarkdownType,
Text: fmt.Sprintf("*TxID*: %s", toExplorerSlackLink(res.Bridge.TransactionID)),
},
{
Type: slack.MarkdownType,
//nolint: gosec
Text: fmt.Sprintf("*OriginTxHash*: %s", toTXSlackLink(res.BridgeRequest.TransactionHash, uint32(res.Bridge.OriginChainID))),
},
{
Type: slack.MarkdownType,
Text: fmt.Sprintf("*Estimated Tx Age*: %s", humanize.Time(time.Unix(res.BridgeRelay.BlockTimestamp, 0))),
},
}

slackBlocks = append(slackBlocks, slack.NewSectionBlock(nil, objects, nil))
if status == "Requested" {
objects = append(objects, &slack.TextBlockObject{
Type: slack.MarkdownType,
Text: "*DestTxHash*: not available",
})
} else {
//nolint: gosec
objects = append(objects, &slack.TextBlockObject{
Type: slack.MarkdownType,
Text: fmt.Sprintf("*DestTxHash*: %s", toTXSlackLink(res.BridgeRelay.TransactionHash, uint32(res.Bridge.DestChainID))),
})
}

_, err := ctx.Response().ReplyBlocks(slackBlocks, slacker.WithUnfurlLinks(false))
slackBlocks = append(slackBlocks, slack.NewSectionBlock(nil, objects, nil))

_, err = ctx.Response().ReplyBlocks(slackBlocks, slacker.WithUnfurlLinks(false))
if err != nil {
log.Println(err)
}
Expand All @@ -292,16 +240,7 @@ func (b *Bot) rfqRefund() *slacker.CommandDefinition {
return
}

var rawRequest *relapi.GetQuoteRequestResponse
var err error
var relClient relapi.RelayerClient
for _, relayer := range b.cfg.RelayerURLS {
relClient = relapi.NewRelayerClient(b.handler, relayer)
rawRequest, err = getQuoteRequest(ctx.Context(), relClient, tx)
if err == nil {
break
}
}
rawRequest, _, err := b.rfqClient.GetRFQ(ctx.Context(), tx)
if err != nil {
b.logger.Errorf(ctx.Context(), "error fetching quote request: %v", err)
_, err := ctx.Response().Reply("error fetching quote request")
Expand All @@ -320,7 +259,7 @@ func (b *Bot) rfqRefund() *slacker.CommandDefinition {
return
}

isScreened, err := b.screener.ScreenAddress(ctx.Context(), rawRequest.Sender)
isScreened, err := b.screener.ScreenAddress(ctx.Context(), rawRequest.Bridge.Sender)
if err != nil {
_, err := ctx.Response().Reply("error screening address")
if err != nil {
Expand All @@ -336,13 +275,16 @@ func (b *Bot) rfqRefund() *slacker.CommandDefinition {
return
}

nonce, err := b.submitter.SubmitTransaction(ctx.Context(), big.NewInt(int64(rawRequest.OriginChainID)), func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) {
tx, err = fastBridgeContract.Refund(transactor, common.Hex2Bytes(rawRequest.QuoteRequestRaw))
if err != nil {
return nil, fmt.Errorf("error submitting refund: %w", err)
}
return tx, nil
})
nonce, err := b.submitter.SubmitTransaction(
ctx.Context(),
big.NewInt(int64(rawRequest.Bridge.OriginChainID)),
func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) {
tx, err = fastBridgeContract.Refund(transactor, common.Hex2Bytes(rawRequest.Bridge.Request))
if err != nil {
return nil, fmt.Errorf("error submitting refund: %w", err)
}
return tx, nil
})
if err != nil {
log.Printf("error submitting refund: %v\n", err)
return
Expand All @@ -352,7 +294,7 @@ func (b *Bot) rfqRefund() *slacker.CommandDefinition {
err = retry.WithBackoff(
ctx.Context(),
func(ctx context.Context) error {
status, err = b.submitter.GetSubmissionStatus(ctx, big.NewInt(int64(rawRequest.OriginChainID)), nonce)
status, err = b.submitter.GetSubmissionStatus(ctx, big.NewInt(int64(rawRequest.Bridge.OriginChainID)), nonce)
if err != nil || !status.HasTx() {
b.logger.Errorf(ctx, "error fetching quote request: %v", err)
return fmt.Errorf("error fetching quote request: %w", err)
Expand All @@ -364,20 +306,22 @@ func (b *Bot) rfqRefund() *slacker.CommandDefinition {
)
if err != nil {
b.logger.Errorf(ctx.Context(), "error fetching quote request: %v", err)
_, err := ctx.Response().Reply(fmt.Sprintf("error fetching explorer link to refund, but nonce is %d", nonce))
log.Printf("error fetching quote request: %v\n", err)
_, err := ctx.Response().Reply(fmt.Sprintf("refund submitted with nonce %d", nonce))
if err != nil {
log.Println(err)
}
return
}

_, err = ctx.Response().Reply(fmt.Sprintf("refund submitted: %s", toExplorerSlackLink(status.TxHash().String())))
_, err = ctx.Response().Reply(fmt.Sprintf("refund submitted: %s", toTXSlackLink(status.TxHash().String(), uint32(rawRequest.Bridge.OriginChainID))))
if err != nil {
log.Println(err)
}
},
}
}

func (b *Bot) makeFastBridge(ctx context.Context, req *relapi.GetQuoteRequestResponse) (*fastbridge.FastBridge, error) {
func (b *Bot) makeFastBridge(ctx context.Context, req *internal.GetRFQByTxIDResponse) (*fastbridge.FastBridge, error) {
client, err := rfqClient.NewUnauthenticatedClient(b.handler, b.cfg.RFQApiURL)
if err != nil {
return nil, fmt.Errorf("error creating rfq client: %w", err)
Expand All @@ -388,12 +332,12 @@ func (b *Bot) makeFastBridge(ctx context.Context, req *relapi.GetQuoteRequestRes
return nil, fmt.Errorf("error fetching rfq contracts: %w", err)
}

chainClient, err := b.rpcClient.GetChainClient(ctx, int(req.OriginChainID))
chainClient, err := b.rpcClient.GetChainClient(ctx, int(req.Bridge.OriginChainID))
if err != nil {
return nil, fmt.Errorf("error getting chain client: %w", err)
}

contractAddress, ok := contracts.Contracts[req.OriginChainID]
contractAddress, ok := contracts.Contracts[uint32(req.Bridge.OriginChainID)]
if !ok {
return nil, errors.New("contract address not found")
}
Expand All @@ -405,24 +349,10 @@ func (b *Bot) makeFastBridge(ctx context.Context, req *relapi.GetQuoteRequestRes
return fastBridgeHandle, nil
}

func getTxAge(ctx context.Context, client client.EVM, res *relapi.GetQuoteRequestResponse) string {
// TODO: add CreatedAt field to GetQuoteRequestStatusResponse so we don't need to make network calls?
receipt, err := client.TransactionReceipt(ctx, common.HexToHash(res.OriginTxHash))
if err != nil {
return "unknown time ago"
}
txBlock, err := client.HeaderByHash(ctx, receipt.BlockHash)
if err != nil {
return "unknown time ago"
}

return humanize.Time(time.Unix(int64(txBlock.Time), 0))
}

func toExplorerSlackLink(ogHash string) string {
rfqHash := strings.ToUpper(ogHash)
// cut off 0x
if strings.HasPrefix(rfqHash, "0x") {
if strings.HasPrefix(rfqHash, "0X") {
rfqHash = strings.ToLower(rfqHash[2:])
}

Expand All @@ -444,16 +374,3 @@ func stripLinks(input string) string {
linkRegex := regexp.MustCompile(`<https?://[^|>]+\|([^>]+)>`)
return linkRegex.ReplaceAllString(input, "$1")
}

func getQuoteRequest(ctx context.Context, client relapi.RelayerClient, tx string) (qr *relapi.GetQuoteRequestResponse, err error) {
if qr, err = client.GetQuoteRequestByTxHash(ctx, tx); err == nil {
return qr, nil
}

// look up quote request
if qr, err = client.GetQuoteRequestByTXID(ctx, tx); err == nil {
return qr, nil
}

return nil, fmt.Errorf("error fetching quote request: %w", err)
}
25 changes: 0 additions & 25 deletions contrib/opbot/botmd/commands_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
package botmd_test

import (
"context"
"testing"

"github.com/synapsecns/sanguine/contrib/opbot/botmd"
"github.com/synapsecns/sanguine/core/metrics"
omnirpcClient "github.com/synapsecns/sanguine/services/omnirpc/client"
"github.com/synapsecns/sanguine/services/rfq/relayer/relapi"
)

func TestStripLinks(t *testing.T) {
Expand All @@ -18,24 +14,3 @@ func TestStripLinks(t *testing.T) {
t.Errorf("StripLinks(%s) = %s; want %s", testLink, got, expected)
}
}

func TestTxAge(t *testing.T) {
notExpected := "unknown time ago" // should be a definite time

status := &relapi.GetQuoteRequestResponse{
OriginTxHash: "0x954264d120f5f3cf50edc39ebaf88ea9dc647d9d6843b7a120ed3677e23d7890",
OriginChainID: 421611,
}

ctx := context.Background()

client := omnirpcClient.NewOmnirpcClient("https://arb1.arbitrum.io/rpc", metrics.Get())
cc, err := client.GetChainClient(ctx, int(status.OriginChainID))
if err != nil {
t.Fatalf("GetChainClient() failed: %v", err)
}

if got := botmd.GetTxAge(context.Background(), cc, status); got == notExpected {
t.Errorf("TxAge(%s) = %s; want not %s", status.OriginTxHash, got, notExpected)
}
}
Loading

0 comments on commit 8ddb7de

Please sign in to comment.