Skip to content
This repository has been archived by the owner on Apr 23, 2024. It is now read-only.

Use soroban smart contract to hold custody of bridge assets #102

Draft
wants to merge 14 commits into
base: sc-audit-fixes
Choose a base branch
from
Draft
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
22 changes: 22 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ jobs:
uses: actions/setup-go@v2
with:
go-version: 1.17
- name: Set up Rust
uses: stellar/actions/rust-cache@main
- name: Add wasm32 rust target
run: rustup update && rustup target add wasm32-unknown-unknown
- name: Generate
run: go generate ./...
- name: golangci-lint
uses: golangci/golangci-lint-action@5c56cd6c9dc07901af25baab6f2b0d9f3b7c3018
with:
Expand All @@ -35,6 +41,10 @@ jobs:
uses: actions/setup-go@v2
with:
go-version: 1
- name: Set up Rust
uses: stellar/actions/rust-cache@main
- name: Add wasm32 rust target
run: rustup update && rustup target add wasm32-unknown-unknown
- name: Generate
run: go generate ./...
- name: Check no diff after generating
Expand All @@ -49,6 +59,12 @@ jobs:
uses: actions/setup-go@v2
with:
go-version: 1
- name: Set up Rust
uses: stellar/actions/rust-cache@main
- name: Add wasm32 rust target
run: rustup update && rustup target add wasm32-unknown-unknown
- name: Generate
run: go generate ./...
- name: Build
run: go build ./...
- name: Build Tests
Expand All @@ -63,5 +79,11 @@ jobs:
uses: actions/setup-go@v2
with:
go-version: 1
- name: Set up Rust
uses: stellar/actions/rust-cache@main
- name: Add wasm32 rust target
run: rustup update && rustup target add wasm32-unknown-unknown
- name: Generate
run: go generate ./...
- name: Run Unit Tests
run: go test -v -race ./...
7 changes: 7 additions & 0 deletions .github/workflows/starbridge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,11 @@ jobs:
with:
go-version: ${{ matrix.go }}

- name: Set up Rust
uses: stellar/actions/rust-cache@main
- name: Add wasm32 rust target
run: rustup update && rustup target add wasm32-unknown-unknown
- name: Generate
run: go generate ./...

- run: go test -race -timeout 25m -v ./integration/...
215 changes: 74 additions & 141 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,34 @@ package app

import (
"context"
"encoding/hex"
"net/http"
"os"
"os/signal"
"sync"
"syscall"
"time"

"github.com/ethereum/go-ethereum/ethclient"
"github.com/prometheus/client_golang/prometheus"

"github.com/ethereum/go-ethereum/ethclient"

"github.com/stellar/go/clients/horizonclient"
"github.com/stellar/go/clients/stellarcore"
"github.com/stellar/go/keypair"
"github.com/stellar/go/support/db"
"github.com/stellar/go/support/log"

"github.com/stellar/starbridge/backend"
"github.com/stellar/starbridge/controllers"
"github.com/stellar/starbridge/ethereum"
"github.com/stellar/starbridge/httpx"
"github.com/stellar/starbridge/stellar/signer"
"github.com/stellar/starbridge/stellar/txbuilder"
"github.com/stellar/starbridge/stellar/txobserver"
"github.com/stellar/starbridge/store"
"github.com/stellar/starbridge/stellar"
)

type App struct {
appCtx context.Context
cancelCtx context.CancelFunc

httpServer *httpx.Server
worker *backend.Worker
session *db.Session
stellarObserver *txobserver.Observer
httpServer *httpx.Server

prometheusRegistry *prometheus.Registry
}
Expand All @@ -45,16 +40,19 @@ type Config struct {

PostgresDSN string `toml:"postgres_dsn" valid:"-"`

HorizonURL string `toml:"horizon_url" valid:"-"`
NetworkPassphrase string `toml:"network_passphrase" valid:"-"`
StellarBridgeAccount string `toml:"stellar_bridge_account" valid:"stellar_accountid"`
StellarPrivateKey string `toml:"stellar_private_key" valid:"stellar_seed"`
HorizonURL string `toml:"horizon_url" valid:"-"`
CoreURL string `toml:"core_url" valid:"-"`

NetworkPassphrase string `toml:"network_passphrase" valid:"-"`
StellarBridgeAccount string `toml:"stellar_bridge_account" valid:"stellar_accountid"`
StellarBridgeContractID string `toml:"stellar_bridge_contract_id" valid:"-"`
StellarPrivateKey string `toml:"stellar_private_key" valid:"stellar_seed"`

EthereumRPCURL string `toml:"ethereum_rpc_url" valid:"-"`
EthereumBridgeAddress string `toml:"ethereum_bridge_address" valid:"-"`
EthereumPrivateKey string `toml:"ethereum_private_key" valid:"-"`

AssetMapping []backend.AssetMappingConfigEntry `toml:"asset_mapping" valid:"-"`
AssetMapping []controllers.AssetMappingConfigEntry `toml:"asset_mapping" valid:"-"`

EthereumFinalityBuffer uint64 `toml:"-" valid:"-"`
WithdrawalWindow time.Duration `toml:"-" valid:"-"`
Expand All @@ -64,30 +62,9 @@ func NewApp(config Config) *App {
app := &App{
prometheusRegistry: prometheus.NewRegistry(),
}

client := &horizonclient.Client{
HorizonURL: config.HorizonURL,
// TODO set proper timeouts
HTTP: http.DefaultClient,
}

app.initDB(config)
app.initGracefulShutdown()
app.stellarObserver = txobserver.NewObserver(
config.StellarBridgeAccount,
client,
app.NewStore(),
)
ethRPCClient, err := ethclient.Dial(config.EthereumRPCURL)
if err != nil {
log.WithField("err", err).Fatal("could not dial ethereum node")
}
ethObserver, err := ethereum.NewObserver(ethRPCClient, config.EthereumBridgeAddress)
if err != nil {
log.WithField("err", err).Fatal("could not create ethereum observer")
}
app.initHTTP(config, client, ethObserver)
app.initWorker(config, client, ethObserver)

app.initHTTP(config)
app.initLogger()
app.initPrometheus()

Expand Down Expand Up @@ -119,32 +96,48 @@ func (a *App) initLogger() {
log.SetLevel(log.InfoLevel)
}

func (a *App) initDB(config Config) {
session, err := db.Open("postgres", config.PostgresDSN)
if err != nil {
log.Fatalf("cannot open DB: %v", err)
}

a.session = session
err = store.InitSchema(session.DB.DB)
if err != nil {
log.Fatalf("cannot init DB: %v", err)
}
}

func (a *App) initWorker(config Config, client *horizonclient.Client, ethObserver ethereum.Observer) {
func (a *App) initHTTP(config Config) {
var (
signerKey *keypair.Full
err error
)
if config.StellarPrivateKey != "" {
signerKey, err = keypair.ParseFull(config.StellarPrivateKey)
if err != nil {
log.Fatalf("cannot pase signer secret key: %v", err)
log.Fatalf("cannot parse signer secret key: %v", err)
}
}

converter, err := backend.NewAssetConverter(config.AssetMapping)
contractIDBytes, err := hex.DecodeString(config.StellarBridgeContractID)
if err != nil {
log.Fatalf("cannot parse bridge contract id: %v", err)
}
if len(contractIDBytes) != 32 {
log.Fatalf("invalid contract id: %v", config.StellarBridgeContractID)
}
var bridgeContractID [32]byte
copy(bridgeContractID[:], contractIDBytes)

stellarObserver := stellar.NewObserver(
bridgeContractID,
&horizonclient.Client{
HorizonURL: config.HorizonURL,
// TODO set proper timeouts
HTTP: http.DefaultClient,
},
&stellarcore.Client{URL: config.CoreURL, HTTP: http.DefaultClient},
)

ethRPCClient, err := ethclient.Dial(config.EthereumRPCURL)
if err != nil {
log.WithField("err", err).Fatal("could not dial ethereum node")
}
ethObserver, err := ethereum.NewObserver(ethRPCClient, config.EthereumBridgeAddress)
if err != nil {
log.WithField("err", err).Fatal("could not create ethereum observer")
}

converter, err := controllers.NewAssetConverter(config.NetworkPassphrase, config.StellarBridgeAccount, config.AssetMapping)
if err != nil {
log.Fatalf("unable to create asset converter: %v", err)
}
Expand All @@ -159,46 +152,12 @@ func (a *App) initWorker(config Config, client *horizonclient.Client, ethObserve
log.Fatalf("cannot create ethereum signer: %v", err)
}

a.worker = &backend.Worker{
Store: a.NewStore(),
StellarClient: client,
StellarBuilder: &txbuilder.Builder{
BridgeAccount: config.StellarBridgeAccount,
},
StellarSigner: &signer.Signer{
NetworkPassphrase: config.NetworkPassphrase,
Signer: signerKey,
},
StellarObserver: a.stellarObserver,
EthereumSigner: ethSigner,
StellarWithdrawalValidator: backend.StellarWithdrawalValidator{
Session: a.session.Clone(),
WithdrawalWindow: config.WithdrawalWindow,
Converter: converter,
},
StellarRefundValidator: backend.StellarRefundValidator{
Session: a.session.Clone(),
WithdrawalWindow: config.WithdrawalWindow,
Observer: ethObserver,
EthereumFinalityBuffer: config.EthereumFinalityBuffer,
},
EthereumWithdrawalValidator: backend.EthereumWithdrawalValidator{
Observer: ethObserver,
EthereumFinalityBuffer: config.EthereumFinalityBuffer,
WithdrawalWindow: config.WithdrawalWindow,
Converter: converter,
},
EthereumRefundValidator: backend.EthereumRefundValidator{
Session: a.session.Clone(),
WithdrawalWindow: config.WithdrawalWindow,
},
}
}

func (a *App) initHTTP(config Config, client *horizonclient.Client, ethObserver ethereum.Observer) {
converter, err := backend.NewAssetConverter(config.AssetMapping)
if err != nil {
log.Fatal("unable to create asset converter", err)
stellarSigner := &stellar.Signer{
BridgeAccount: config.StellarBridgeAccount,
BridgeContractID: bridgeContractID,
NetworkPassphrase: config.NetworkPassphrase,
Signer: signerKey,
CoreClient: &stellarcore.Client{URL: config.CoreURL, HTTP: http.DefaultClient},
}

httpServer, err := httpx.NewServer(httpx.ServerConfig{
Expand All @@ -207,43 +166,34 @@ func (a *App) initHTTP(config Config, client *horizonclient.Client, ethObserver
AdminPort: config.AdminPort,
PrometheusRegistry: a.prometheusRegistry,
StellarWithdrawalHandler: &controllers.StellarWithdrawalHandler{
StellarClient: client,
Observer: ethObserver,
Store: a.NewStore(),
StellarWithdrawalValidator: backend.StellarWithdrawalValidator{
Session: a.session.Clone(),
WithdrawalWindow: config.WithdrawalWindow,
Converter: converter,
},
StellarSigner: stellarSigner,
StellarObserver: stellar.Observer{},
WithdrawalWindow: config.WithdrawalWindow,
Converter: converter,
EthereumObserver: ethObserver,
EthereumFinalityBuffer: config.EthereumFinalityBuffer,
},
EthereumWithdrawalHandler: &controllers.EthereumWithdrawalHandler{
Store: a.NewStore(),
EthereumWithdrawalValidator: backend.EthereumWithdrawalValidator{
Observer: ethObserver,
EthereumFinalityBuffer: config.EthereumFinalityBuffer,
WithdrawalWindow: config.WithdrawalWindow,
Converter: converter,
},
EthereumObserver: ethObserver,
StellarObserver: stellarObserver,
EthereumSigner: ethSigner,
EthereumFinalityBuffer: config.EthereumFinalityBuffer,
WithdrawalWindow: config.WithdrawalWindow,
Converter: converter,
},
EthereumRefundHandler: &controllers.EthereumRefundHandler{
Observer: ethObserver,
Store: a.NewStore(),
EthereumRefundValidator: backend.EthereumRefundValidator{
Session: a.session.Clone(),
WithdrawalWindow: config.WithdrawalWindow,
},
EthereumObserver: ethObserver,
StellarObserver: stellarObserver,
EthereumSigner: ethSigner,
EthereumFinalityBuffer: config.EthereumFinalityBuffer,
WithdrawalWindow: config.WithdrawalWindow,
},
StellarRefundHandler: &controllers.StellarRefundHandler{
StellarClient: client,
Store: a.NewStore(),
StellarRefundValidator: backend.StellarRefundValidator{
Session: a.session.Clone(),
WithdrawalWindow: config.WithdrawalWindow,
Observer: ethObserver,
EthereumFinalityBuffer: config.EthereumFinalityBuffer,
},
StellarSigner: stellarSigner,
EthereumObserver: ethObserver,
StellarObserver: stellarObserver,
WithdrawalWindow: config.WithdrawalWindow,
EthereumFinalityBuffer: config.EthereumFinalityBuffer,
},
})
if err != nil {
Expand All @@ -252,11 +202,6 @@ func (a *App) initHTTP(config Config, client *horizonclient.Client, ethObserver
a.httpServer = httpServer
}

// NewStore returns a new instance of store.DB
func (a *App) NewStore() *store.DB {
return &store.DB{Session: a.session.Clone()}
}

// Run starts all services and block until they are gracefully shut down.
func (a *App) Run() {
var wg sync.WaitGroup
Expand All @@ -267,12 +212,6 @@ func (a *App) Run() {
wg.Done()
}()

wg.Add(1)
go func() {
a.RunBackendWorker()
wg.Done()
}()

wg.Wait()
log.Info("Bye")
}
Expand All @@ -285,12 +224,6 @@ func (a *App) RunHTTPServer() {
}
}

// RunBackendWorker starts backend worker responsible for building and signing
// transactions
func (a *App) RunBackendWorker() {
a.worker.Run(a.appCtx)
}

func (a *App) Close() {
a.cancelCtx()
}
Loading