Skip to content

Commit

Permalink
feat: upload btc execution metadata to ipfs (#339)
Browse files Browse the repository at this point in the history
  • Loading branch information
tcar121293 authored Aug 14, 2024
1 parent 7b35e11 commit ff55000
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 6 deletions.
6 changes: 5 additions & 1 deletion app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/ChainSafe/sygma-relayer/chains"
"github.com/ChainSafe/sygma-relayer/chains/btc"
"github.com/ChainSafe/sygma-relayer/chains/btc/mempool"
"github.com/ChainSafe/sygma-relayer/chains/btc/uploader"
"github.com/ChainSafe/sygma-relayer/chains/evm"
"github.com/ChainSafe/sygma-relayer/chains/evm/calls/contracts/bridge"
"github.com/ChainSafe/sygma-relayer/chains/evm/calls/events"
Expand Down Expand Up @@ -331,6 +332,8 @@ func Run() error {

mempool := mempool.NewMempoolAPI(config.MempoolUrl)
mh := &btcExecutor.BtcMessageHandler{}
uploader := uploader.NewIPFSUploader(configuration.RelayerConfig.UploaderConfig)

executor := btcExecutor.NewExecutor(
propStore,
host,
Expand All @@ -341,7 +344,8 @@ func Run() error {
mempool,
resources,
config.Network,
exitLock)
exitLock,
uploader)

btcChain := btc.NewBtcChain(listener, executor, mh, *config.GeneralChainConfig.Id)
domains[*config.GeneralChainConfig.Id] = btcChain
Expand Down
29 changes: 29 additions & 0 deletions chains/btc/executor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/ChainSafe/sygma-relayer/chains/btc/config"
"github.com/ChainSafe/sygma-relayer/chains/btc/connection"
"github.com/ChainSafe/sygma-relayer/chains/btc/mempool"
"github.com/ChainSafe/sygma-relayer/chains/btc/uploader"
"github.com/ChainSafe/sygma-relayer/comm"
"github.com/ChainSafe/sygma-relayer/store"
"github.com/ChainSafe/sygma-relayer/tss"
Expand Down Expand Up @@ -60,6 +61,7 @@ type Executor struct {
propMutex sync.Mutex

exitLock *sync.RWMutex
uploader uploader.Uploader
}

func NewExecutor(
Expand All @@ -73,6 +75,7 @@ func NewExecutor(
resources map[[32]byte]config.Resource,
chainCfg chaincfg.Params,
exitLock *sync.RWMutex,
uploader uploader.Uploader,
) *Executor {
return &Executor{
propStorer: propStorer,
Expand All @@ -85,6 +88,7 @@ func NewExecutor(
resources: resources,
mempool: mempool,
chainCfg: chainCfg,
uploader: uploader,
}
}

Expand Down Expand Up @@ -273,6 +277,8 @@ func (e *Executor) rawTx(proposals []*BtcTransferProposal, resource config.Resou

func (e *Executor) outputs(tx *wire.MsgTx, proposals []*BtcTransferProposal) (uint64, error) {
outputAmount := uint64(0)
var dataToUpload []map[string]interface{}

for _, prop := range proposals {
addr, err := btcutil.DecodeAddress(prop.Data.Recipient, &e.chainCfg)
if err != nil {
Expand All @@ -282,10 +288,33 @@ func (e *Executor) outputs(tx *wire.MsgTx, proposals []*BtcTransferProposal) (ui
if err != nil {
return 0, err
}

dataToUpload = append(dataToUpload, map[string]interface{}{
"sourceDomain": prop.Source,
"depositNonce": prop.Data.DepositNonce,
})

txOut := wire.NewTxOut(int64(prop.Data.Amount), destinationAddrByte)
tx.AddTxOut(txOut)

outputAmount += prop.Data.Amount
}

// Upload to IPFS
cid, err := e.uploader.Upload(dataToUpload)
if err != nil {
log.Error().Err(err).Msg("Error occured while uploading metadata to ipfs")
}

// Store the CID in OP_RETURN
opReturnData := []byte("syg_" + cid)
opReturnScript, err := txscript.NullDataScript(opReturnData)
if err != nil {
log.Error().Err(err).Msg("Error occured while constructiong OP_RETURN data")
}

opReturnOut := wire.NewTxOut(0, opReturnScript)
tx.AddTxOut(opReturnOut)
return outputAmount, nil
}

Expand Down
103 changes: 103 additions & 0 deletions chains/btc/uploader/ipfs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package uploader

import (
"bytes"
"encoding/json"
"errors"
"io"
"mime/multipart"
"net/http"
"time"

"github.com/rs/zerolog/log"

"github.com/ChainSafe/sygma-relayer/config/relayer"
"github.com/cenkalti/backoff/v4"
)

type Uploader interface {
Upload(proposals []map[string]interface{}) (string, error)
}

type IPFSUploader struct {
config relayer.UploaderConfig
}

func NewIPFSUploader(config relayer.UploaderConfig) *IPFSUploader {
return &IPFSUploader{config: config}
}

type IPFSResponse struct {
IpfsHash string `json:"IpfsHash"`
}

func (i *IPFSUploader) Upload(dataToUpload []map[string]interface{}) (string, error) {
jsonData, err := json.Marshal(dataToUpload)
if err != nil {
return "", err
}

body := new(bytes.Buffer)
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("file", "metadata.json")
if err != nil {
return "", err
}
_, err = part.Write(jsonData)
if err != nil {
return "", err
}
writer.Close()

req, err := http.NewRequest("POST", i.config.URL, body)
if err != nil {
return "", err
}

req.Header.Add("Authorization", "Bearer "+i.config.AuthToken)
req.Header.Add("Content-Type", writer.FormDataContentType())

var ipfsResponse IPFSResponse

// Define the operation to be retried
operation := func() error {
return i.performRequest(req, &ipfsResponse)
}
expBackoff := backoff.NewExponentialBackOff()
expBackoff.MaxElapsedTime = i.config.MaxElapsedTime

notify := func(err error, duration time.Duration) {
log.Warn().Err(err).Msg("Unable to upload metadata to ipfs")
}

err = backoff.RetryNotify(operation, backoff.WithMaxRetries(expBackoff, i.config.MaxRetries), notify)
if err != nil {
return "", err
}

return ipfsResponse.IpfsHash, nil
}

func (i *IPFSUploader) performRequest(req *http.Request, ipfsResponse *IPFSResponse) error {
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

if resp.StatusCode != 200 {
return errors.New("received non-200 status code")
}

respBody, err := io.ReadAll(resp.Body)
if err != nil {
return err
}

if err := json.Unmarshal(respBody, &ipfsResponse); err != nil {
return err
}

return nil
}
57 changes: 57 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ func (s *GetConfigTestSuite) Test_GetConfigFromENV() {
ElectionWaitTime: 2 * time.Second,
BullyWaitTime: 3 * time.Minute,
},
UploaderConfig: relayer.UploaderConfig{
MaxRetries: 5,
MaxElapsedTime: 300000,
},
},
ChainConfigs: []map[string]interface{}{
{
Expand Down Expand Up @@ -212,6 +216,10 @@ func (s *GetConfigTestSuite) Test_SharedConfigLengthMismatch() {
ElectionWaitTime: 2 * time.Second,
BullyWaitTime: 3 * time.Minute,
},
UploaderConfig: relayer.UploaderConfig{
MaxRetries: 5,
MaxElapsedTime: 300000,
},
},
ChainConfigs: []map[string]interface{}{
{
Expand Down Expand Up @@ -279,6 +287,12 @@ func (s *GetConfigTestSuite) Test_GetConfigFromFile() {
PingInterval: "1s",
ElectionWaitTime: "1s",
},
UploaderConfig: relayer.UploaderConfig{
URL: "https://testIPFSProvider.com",
AuthToken: "testToken",
MaxRetries: 5,
MaxElapsedTime: 5 * time.Minute,
},
},
},
shouldFail: true,
Expand All @@ -297,6 +311,12 @@ func (s *GetConfigTestSuite) Test_GetConfigFromFile() {
Path: "path",
},
},
UploaderConfig: relayer.UploaderConfig{
URL: "https://testIPFSProvider.com",
AuthToken: "testToken",
MaxRetries: 5,
MaxElapsedTime: 5 * time.Minute,
},
},
ChainConfigs: []map[string]interface{}{{
"type": "evm",
Expand Down Expand Up @@ -326,6 +346,12 @@ func (s *GetConfigTestSuite) Test_GetConfigFromFile() {
PingInterval: "",
ElectionWaitTime: "",
},
UploaderConfig: relayer.UploaderConfig{
URL: "https://testIPFSProvider.com",
AuthToken: "testToken",
MaxRetries: 5,
MaxElapsedTime: 5 * time.Minute,
},
},
ChainConfigs: []map[string]interface{}{{
"type": "evm",
Expand All @@ -345,7 +371,14 @@ func (s *GetConfigTestSuite) Test_GetConfigFromFile() {
TopologyConfiguration: relayer.TopologyConfiguration{},
Port: "2020",
},
UploaderConfig: relayer.UploaderConfig{
URL: "https://testIPFSProvider.com",
AuthToken: "testToken",
MaxRetries: 5,
MaxElapsedTime: 5 * time.Minute,
},
},

ChainConfigs: []map[string]interface{}{{
"type": "evm",
"name": "chain1",
Expand All @@ -369,6 +402,12 @@ func (s *GetConfigTestSuite) Test_GetConfigFromFile() {
},
// Port: use default value,
},
UploaderConfig: relayer.UploaderConfig{
URL: "https://testIPFSProvider.com",
AuthToken: "testToken",
MaxRetries: 5,
MaxElapsedTime: 5 * time.Minute,
},
},
ChainConfigs: []map[string]interface{}{{
"type": "evm",
Expand Down Expand Up @@ -399,6 +438,12 @@ func (s *GetConfigTestSuite) Test_GetConfigFromFile() {
ElectionWaitTime: 2 * time.Second,
BullyWaitTime: 3 * time.Minute,
},
UploaderConfig: relayer.UploaderConfig{
URL: "https://testIPFSProvider.com",
AuthToken: "testToken",
MaxRetries: 5,
MaxElapsedTime: 5 * time.Minute,
},
},
ChainConfigs: []map[string]interface{}{{
"type": "evm",
Expand Down Expand Up @@ -431,6 +476,12 @@ func (s *GetConfigTestSuite) Test_GetConfigFromFile() {
ElectionWaitTime: "1s",
BullyWaitTime: "1s",
},
UploaderConfig: relayer.UploaderConfig{
URL: "https://testIPFSProvider.com",
AuthToken: "testToken",
MaxRetries: 5,
MaxElapsedTime: 5 * time.Minute,
},
},
ChainConfigs: []map[string]interface{}{{
"type": "evm",
Expand Down Expand Up @@ -463,6 +514,12 @@ func (s *GetConfigTestSuite) Test_GetConfigFromFile() {
ElectionWaitTime: time.Second,
BullyWaitTime: time.Second,
},
UploaderConfig: relayer.UploaderConfig{
URL: "https://testIPFSProvider.com",
AuthToken: "testToken",
MaxRetries: 5,
MaxElapsedTime: 5 * time.Minute,
},
},
ChainConfigs: []map[string]interface{}{{
"type": "evm",
Expand Down
9 changes: 9 additions & 0 deletions config/relayer/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type RelayerConfig struct {
Id string
MpcConfig MpcRelayerConfig
BullyConfig BullyConfig
UploaderConfig UploaderConfig
}

type MpcRelayerConfig struct {
Expand All @@ -46,6 +47,12 @@ type TopologyConfiguration struct {
Path string `mapstructure:"Path" json:"path"`
}

type UploaderConfig struct {
URL string `mapstructure:"url"`
AuthToken string `mapstructure:"authToken"`
MaxRetries uint64 `mapstructure:"MaxRetries" json:"maxRetries" default:"5"`
MaxElapsedTime time.Duration `mapstructure:"MaxElapsedTime" json:"maxElapsedTime" default:"300000"` // 5 min
}
type RawRelayerConfig struct {
OpenTelemetryCollectorURL string `mapstructure:"OpenTelemetryCollectorURL" json:"opentelemetryCollectorURL"`
LogLevel string `mapstructure:"LogLevel" json:"logLevel" default:"info"`
Expand All @@ -55,6 +62,7 @@ type RawRelayerConfig struct {
Id string `mapstructure:"Id" json:"id"`
MpcConfig RawMpcRelayerConfig `mapstructure:"MpcConfig" json:"mpcConfig"`
BullyConfig RawBullyConfig `mapstructure:"BullyConfig" json:"bullyConfig"`
UploaderConfig UploaderConfig `mapstructure:"uploaderConfig"`
}

type RawMpcRelayerConfig struct {
Expand Down Expand Up @@ -123,6 +131,7 @@ func NewRelayerConfig(rawConfig RawRelayerConfig) (RelayerConfig, error) {
config.BullyConfig = bullyConfig
config.Env = rawConfig.Env
config.Id = rawConfig.Id
config.UploaderConfig = rawConfig.UploaderConfig
return config, nil
}

Expand Down
Loading

0 comments on commit ff55000

Please sign in to comment.