Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: upload btc execution metadata to ipfs #339

Merged
merged 20 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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")
tcar121293 marked this conversation as resolved.
Show resolved Hide resolved
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)
tcar121293 marked this conversation as resolved.
Show resolved Hide resolved
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
Loading