Skip to content

Commit

Permalink
Merge pull request #313 from stellar/backend-account-configuration
Browse files Browse the repository at this point in the history
Bifrost 0.0.2
  • Loading branch information
bartekn authored Apr 20, 2018
2 parents f4b371f + d6107b1 commit 9eb9682
Show file tree
Hide file tree
Showing 20 changed files with 464 additions and 149 deletions.
4 changes: 2 additions & 2 deletions build/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,8 @@ type Thresholds struct {
}

type Timebounds struct {
MinTime uint64
MaxTime uint64
MinTime uint64
MaxTime uint64
}

// Trustor is a mutator capable of setting the trustor on
Expand Down
4 changes: 2 additions & 2 deletions build/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,8 @@ func (m MemoText) MutateTransaction(o *TransactionBuilder) (err error) {
}

func (m Timebounds) MutateTransaction(o *TransactionBuilder) error {
o.TX.TimeBounds = &xdr.TimeBounds{MinTime: xdr.Uint64(m.MinTime), MaxTime: xdr.Uint64(m.MaxTime)}
return nil
o.TX.TimeBounds = &xdr.TimeBounds{MinTime: xdr.Uint64(m.MinTime), MaxTime: xdr.Uint64(m.MaxTime)}
return nil
}

// MutateTransaction for Network sets the Network ID to use when signing this transaction
Expand Down
2 changes: 2 additions & 0 deletions clients/horizon/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/stellar/go/build"
"github.com/stellar/go/support/errors"
"github.com/stellar/go/xdr"
)

// DefaultTestNetClient is a default client to connect to test network
Expand Down Expand Up @@ -83,6 +84,7 @@ type ClientInterface interface {
LoadTradeAggregations(selling Asset, buying Asset, resolution int64, params ...interface{}) (tradeAggrs TradeAggregationsPage, err error)
LoadMemo(p *Payment) error
LoadOrderBook(selling Asset, buying Asset, params ...interface{}) (orderBook OrderBookSummary, err error)
SequenceForAccount(accountID string) (xdr.SequenceNumber, error)
StreamLedgers(ctx context.Context, cursor *Cursor, handler LedgerHandler) error
StreamPayments(ctx context.Context, accountID string, cursor *Cursor, handler PaymentHandler) error
StreamTransactions(ctx context.Context, accountID string, cursor *Cursor, handler TransactionHandler) error
Expand Down
7 changes: 7 additions & 0 deletions clients/horizon/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package horizon
import (
"context"

"github.com/stellar/go/xdr"
"github.com/stretchr/testify/mock"
)

Expand Down Expand Up @@ -74,6 +75,12 @@ func (m *MockClient) LoadOrderBook(
return a.Get(0).(OrderBookSummary), a.Error(1)
}

// SequenceForAccount is a mocking a method
func (m *MockClient) SequenceForAccount(accountID string) (xdr.SequenceNumber, error) {
a := m.Called(accountID)
return a.Get(0).(xdr.SequenceNumber), a.Error(1)
}

// StreamLedgers is a mocking a method
func (m *MockClient) StreamLedgers(
ctx context.Context,
Expand Down
41 changes: 33 additions & 8 deletions services/bifrost/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,9 @@ Download the binary from [the release page](https://github.com/stellar/go/releas
1. User selects what cryptocurrency she wants to move to Stellar network.
1. A receiving Bitcoin/Ethereum address is generated.
1. User sends funds in Bitcoin/Ethereum network.
1. Bifrost listens to Bitcoin and Ethereum network events. When payment arrives it creates a Stellar [account](https://www.stellar.org/developers/guides/concepts/accounts.html) for the user.
1. User creates a [trust line](https://www.stellar.org/developers/guides/concepts/assets.html) to BTC/ETH issued by Bifrost account.
1. Bifrost sends corresponding amount of BTC/ETH to user's Stellar account.
1. As an optional step, web application can also exchange BTC/ETH to other token in Stellar network at a given rate.
1. Bifrost listens to Bitcoin and Ethereum network events. When payment arrives it creates a Stellar [account](https://www.stellar.org/developers/guides/concepts/accounts.html) for the user who later adds a temporary signer on the account.
1. Using a temporary signer Bifrost creates necessary [trust lines](https://www.stellar.org/developers/guides/concepts/assets.html) and sends corresponding amount of BTC/ETH to user's Stellar account. It also exchanges BTC/ETH to the final token at a given rate.
1. Finally a temporary signer is removed.

## Demo

Expand All @@ -51,25 +50,30 @@ https://bifrost.stellar.org/

* `port` - bifrost server listening port
* `using_proxy` (default `false`) - set to `true` if bifrost lives behind a proxy or load balancer
* `access_control_allow_origin_header` (default `*`) - value of `Access-Control-Allow-Origin` headers sent by Bifrost
* `bitcoin`
* `master_public_key` - master public key for bitcoin keys derivation (read more in [BIP-0032](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki))
* `rpc_server` - URL of [bitcoin-core](https://github.com/bitcoin/bitcoin) >= 0.15.0 RPC server
* `rpc_user` (default empty) - username for RPC server (if any)
* `rpc_pass` (default empty) - password for RPC server (if any)
* `testnet` (default `false`) - set to `true` if you're testing bifrost in ethereum
* `minimum_value_btc` - minimum transaction value in BTC that will be accepted by Bifrost, everything below will be ignored.
* `token_price` - a price of one BTC in terms of final token (`stellar.token_asset_code`)
* `ethereum`
* `master_public_key` - master public key for bitcoin keys derivation (read more in [BIP-0032](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki))
* `rpc_server` - URL of [geth](https://github.com/ethereum/go-ethereum) >= 1.7.1 RPC server
* `network_id` - network ID (`3` - Ropsten testnet, `1` - live Ethereum network)
* `minimum_value_eth` - minimum transaction value in ETH that will be accepted by Bifrost, everything below will be ignored.
* `token_price` - a price of one ETH in terms of final token (`stellar.token_asset_code`)
* `stellar`
* `token_asset_code` - asset code for the token that will be distributed
* `issuer_public_key` - public key of the assets issuer or hot wallet,
* `signer_secret_key` - issuer's secret key if only one instance of Bifrost is deployed OR [channel](https://www.stellar.org/developers/guides/channels.html)'s secret key if more than one instance of Bifrost is deployed. Signer's sequence number will be consumed in transaction's sequence number.
* `issuer_public_key` - public key of the assets issuer (see "Account configuration" section),
* `distribution_public_key` - public key of the distribution account, it can be the same as issuer but it's recommended to use a separate account, it's also used to fund new accounts (see "Account configuration" section)
* `signer_secret_key` - distribuions accounts's secret key if only one instance of Bifrost is deployed OR [channel](https://www.stellar.org/developers/guides/channels.html)'s secret key if more than one instance of Bifrost is deployed, signer is also used as a temporary signer in new accounts (see "Account configuration" section)
* `horizon` - URL to [horizon](https://github.com/stellar/go/tree/master/services/horizon) server
* `network_passphrase` - Stellar network passphrase (`Public Global Stellar Network ; September 2015` for production network, `Test SDF Network ; September 2015` for test network)
* `starting_balance` - Stellar XLM amount issued to created account (41 by default)
* `lock_unix_timestamp` - Unix timestamp of a date until funds will be locked in a new account (helpful if you want to disallow trading during token sale)
* `database`
* `type` - currently the only supported database type is: `postgres`
* `dsn` - data source name for postgres connection (`postgres://user:password@host/dbname?sslmode=sslmode` - [more info](https://godoc.org/github.com/lib/pq#hdr-Connection_String_Parameters))
Expand All @@ -88,12 +92,32 @@ Here's the proposed architecture diagram of high-availability deployment:

![Architecture](./images/architecture.png)

## Account configuration

Issuing, Distribution and Signer Accounts can be configured in many different ways, it all depends on your needs.

### Issuing Account Only

This is the most basic configuration but it is not recommended. In this configuration only one account is responsible for everything: issuing and distributing assets and creating new accounts. In this configuration `stellar.issuer_public_key`, `stellar.distribution_public_key` and `stellar.signer_secret_key` represent a single account.

### Issuing and Distribution Accounts

Sometimes you want to have a limited supply of your token so you create a distribution account, send all the tokens to distribution (and other accounts, ex. vesting) and lock the issuing account so no new tokens can be created. In this configuration `stellar.distribution_public_key` and `stellar.signer_secret_key` represent a separate account used to distribute funds in a token sale. This account will also be used to create new accounts.

### Distribution Account with multiple signers

If you expect really high load you should deploy multiple Bifrost servers. To prevent Bifrost instances from consuming the same sequence numbers of the Distribution account you should use [channels](https://www.stellar.org/developers/guides/channels.html). Each Bifrost server will contain a secret key of one of the signers of Distribution account and will use Signer account as a [transaction source](https://www.stellar.org/developers/guides/concepts/transactions.html#source-account) when creating new accounts.

In this configuration all servers have the same `stellar.issuer_public_key` and `stellar.distribution_public_key` values but each server has a separate `stellar.signer_secret_key` which is a signer of `stellar.distribution_public_key` account (and needs to exist in Stellar ledger).

![Accounts](./images/accounts.png)

## Going to production

* Remember that everyone with master public key and **any** child private key can recover your **master** private key. Do not share your master public key and obviously any private keys. Treat your master public key as if it was a private key. Read more in BIP-0032 [Security](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#security) section.
* Make sure "Sell [your token] for BTC" and/or "Sell [your token] for ETH" exist in Stellar production network. If not, you can create an offer by sending a transaction with `manage_offer` operation.
* Make sure you don't use account from `stellar.issuer_secret_key` anywhere else than bifrost. Otherwise, sequence numbers will go out of sync and bifrost will stop working. It's good idea to create a new signer on issuing account.
* Check if public master key correct. Use CLI tool (`bifrost check-keys`) to generate a few addresses and ensure you have corresponding private keys! You should probably send test transactions to some of these addresses and check if you can withdraw funds.
* Check if public master key is correct. Use CLI tool (`bifrost check-keys`) to generate a few addresses and ensure you have corresponding private keys! You should probably send test transactions to some of these addresses and check if you can withdraw funds.
* Make sure `using_proxy` variable is set to correct value. Otherwise you will see your proxy IP instead of users' IPs in logs.
* Make sure you're not connecting to testnets.
* Deploy at least 2 bifrost, bitcoin-core, geth, stellar-core and horizon servers. Use multi-AZ database.
Expand All @@ -104,4 +128,5 @@ Here's the proposed architecture diagram of high-availability deployment:
* Make sure you are using geth >= 1.7.1 and bitcoin-core >= 0.15.0.
* Increase horizon rate limiting to handle expected load.
* Make sure you configured minimum accepted value for Bitcoin and Ethereum transactions to the value you really want.
* Make sure you start from a fresh Bifrost DB in production. If Bifrost was running, you stopped it and then started it again then all the Bitcoin and Ethereum blocks mined during that period will be processed which can take a lot of time.
* Make sure you start from a fresh Bifrost DB in production. If Bifrost was running, you stopped it and then started it again, all the Bitcoin and Ethereum blocks mined during that period will be processed which can take a lot of time.
* Make sure that `lock_unix_timestamp` is correct and test it. If the value is too far in the future users will be locked out of their funds.
7 changes: 5 additions & 2 deletions services/bifrost/bifrost.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
port = 8000
using_proxy = false
access-control-allow-origin-header = "*"
access_control_allow_origin_header = "*"

[bitcoin]
master_public_key = "xpub6DxSCdWu6jKqr4isjo7bsPeDD6s3J4YVQV1JSHZg12Eagdqnf7XX4fxqyW2sLhUoFWutL7tAELU2LiGZrEXtjVbvYptvTX5Eoa4Mamdjm9u"
Expand All @@ -9,21 +9,24 @@ rpc_user = "user"
rpc_pass = "password"
testnet = true
minimum_value_btc = "0.0001"
token_price = "1"

[ethereum]
master_public_key = "xpub6DxSCdWu6jKqr4isjo7bsPeDD6s3J4YVQV1JSHZg12Eagdqnf7XX4fxqyW2sLhUoFWutL7tAELU2LiGZrEXtjVbvYptvTX5Eoa4Mamdjm9u"
rpc_server = "localhost:8545"
network_id = "3"
minimum_value_eth = "0.00001"
token_price = "1"

[stellar]
issuer_public_key = "GDGVTKSEXWB4VFTBDWCBJVJZLIY6R3766EHBZFIGK2N7EQHVV5UTA63C"
distribution_public_key = "GDGVTKSEXWB4VFTBDWCBJVJZLIY6R3766EHBZFIGK2N7EQHVV5UTA63C"
signer_secret_key = "SAGC33ER53WGBISR5LQ4RJIBFG5UHXWNGTLG4KJRC737VYXNDGWLO54B"
token_asset_code = "TOKE"
needs_authorize = true
horizon = "https://horizon-testnet.stellar.org"
network_passphrase = "Test SDF Network ; September 2015"
starting_balance = "41"
starting_balance = "4"

[database]
type="postgres"
Expand Down
34 changes: 27 additions & 7 deletions services/bifrost/config/main.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package config

import (
"github.com/stellar/go/keypair"
)

type Config struct {
Port int `valid:"required"`
UsingProxy bool `valid:"optional" toml:"using_proxy"`
Bitcoin *bitcoinConfig `valid:"optional" toml:"bitcoin"`
Ethereum *ethereumConfig `valid:"optional" toml:"ethereum"`
AccessControlAllowOriginHeader string `valid:"optional" toml:"access-control-allow-origin-header"`
AccessControlAllowOriginHeader string `valid:"optional" toml:"access_control_allow_origin_header"`

Stellar struct {
Horizon string `valid:"required" toml:"horizon"`
Expand All @@ -14,17 +18,24 @@ type Config struct {
TokenAssetCode string `valid:"required" toml:"token_asset_code"`
// NeedsAuthorize should be set to true if issuers's authorization required flag is set.
NeedsAuthorize bool `valid:"optional" toml:"needs_authorize"`
// IssuerPublicKey is public key of the assets issuer or hot wallet.
IssuerPublicKey string `valid:"required" toml:"issuer_public_key"`
// IssuerPublicKey is public key of the assets issuer.
IssuerPublicKey string `valid:"required,stellar_accountid" toml:"issuer_public_key"`
// DistributionPublicKey is public key of the distribution account.
// Distribution account can be the same account as issuer account however it's recommended
// to use a separate account.
// Distribution account is also used to fund new accounts.
DistributionPublicKey string `valid:"required,stellar_accountid" toml:"distribution_public_key"`
// SignerSecretKey is:
// * Issuer's secret key if only one instance of Bifrost is deployed.
// * Channel's secret key if more than one instance of Bifrost is deployed.
// * Distribution's secret key if only one instance of Bifrost is deployed.
// * Channel's secret key of Distribution account if more than one instance of Bifrost is deployed.
// https://www.stellar.org/developers/guides/channels.html
// Signer's sequence number will be consumed in transaction's sequence number.
SignerSecretKey string `valid:"required" toml:"signer_secret_key"`
SignerSecretKey string `valid:"required,stellar_seed" toml:"signer_secret_key"`
// StartingBalance is the starting amount of XLM for newly created accounts.
// Default value is 41. Increase it if you need Data records / other custom entities on new account.
StartingBalance string `valid:"optional,numeric" toml:"starting_balance"`
StartingBalance string `valid:"optional,stellar_amount" toml:"starting_balance"`
// LockUnixTimestamp defines unix timestamp when user account will be unlocked.
LockUnixTimestamp uint64 `valid:"optional" toml:"lock_unix_timestamp"`
} `valid:"required" toml:"stellar"`
Database struct {
Type string `valid:"matches(^postgres$)"`
Expand All @@ -37,6 +48,8 @@ type bitcoinConfig struct {
// Minimum value of transaction accepted by Bifrost in BTC.
// Everything below will be ignored.
MinimumValueBtc string `valid:"required" toml:"minimum_value_btc"`
// TokenPrice is a price of one token in BTC
TokenPrice string `valid:"required" toml:"token_price"`
// Host only
RpcServer string `valid:"required" toml:"rpc_server"`
RpcUser string `valid:"optional" toml:"rpc_user"`
Expand All @@ -50,6 +63,13 @@ type ethereumConfig struct {
// Minimum value of transaction accepted by Bifrost in ETH.
// Everything below will be ignored.
MinimumValueEth string `valid:"required" toml:"minimum_value_eth"`
// TokenPrice is a price of one token in ETH
TokenPrice string `valid:"required" toml:"token_price"`
// Host only
RpcServer string `valid:"required" toml:"rpc_server"`
}

func (c Config) SignerPublicKey() string {
kp := keypair.MustParse(c.Stellar.SignerSecretKey)
return kp.Address()
}
2 changes: 2 additions & 0 deletions services/bifrost/database/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ func (s *Chain) Scan(src interface{}) error {
}

const (
SchemaVersion uint32 = 2

ChainBitcoin Chain = "bitcoin"
ChainEthereum Chain = "ethereum"
)
Expand Down
36 changes: 36 additions & 0 deletions services/bifrost/database/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ import (
"strings"
"time"

"github.com/lib/pq"
"github.com/stellar/go/services/bifrost/queue"
"github.com/stellar/go/services/bifrost/sse"
"github.com/stellar/go/support/db"
"github.com/stellar/go/support/errors"
)

const (
schemaVersionKey = "schema_version"

ethereumAddressIndexKey = "ethereum_address_index"
ethereumLastBlockKey = "ethereum_last_block"

Expand Down Expand Up @@ -98,6 +101,39 @@ func (d *PostgresDatabase) Open(dsn string) error {
return nil
}

// Import imports DB schema
func (d *PostgresDatabase) Import() error {
return d.session.ExecAll(schema)
}

// GetSchemaVersion returns schema version of Bifrost DB. Returns 0.
func (d *PostgresDatabase) GetSchemaVersion() (uint32, error) {
keyValueStore := d.getTable(keyValueStoreTableName, nil)
row := keyValueStoreRow{}

err := keyValueStore.Get(&row, map[string]interface{}{"key": schemaVersionKey}).Exec()
if err != nil {
if pqErr, ok := errors.Cause(err).(*pq.Error); ok && pqErr.Code == "42P01" {
// Relation does not exist - DB is probably clean. If not, Import will return error.
return 0, nil
}

if errors.Cause(err) == sql.ErrNoRows {
// schemaVersionKey key not found - schema is old (return 1 because schemaVersionKey was set to 2 when created)
return 1, nil
}

return 0, errors.Wrap(err, "Error getting `"+schemaVersionKey+"` from DB")
}

version, err := strconv.ParseUint(row.Value, 10, 32)
if err != nil {
return 0, errors.Wrap(err, "Error converting `"+schemaVersionKey+"` value to uint32")
}

return uint32(version), nil
}

func (d *PostgresDatabase) getTable(name string, session *db.Session) *db.Table {
if session == nil {
session = d.session
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
CREATE TYPE chain AS ENUM ('bitcoin', 'ethereum');
package database

import (
"fmt"
)

var schema = fmt.Sprintf(`CREATE TYPE chain AS ENUM ('bitcoin', 'ethereum');
CREATE TABLE address_association (
chain chain NOT NULL,
Expand All @@ -18,6 +24,8 @@ CREATE TABLE key_value_store (
PRIMARY KEY (key)
);
INSERT INTO key_value_store (key, value) VALUES ('schema_version', '%d');
INSERT INTO key_value_store (key, value) VALUES ('ethereum_address_index', '0');
INSERT INTO key_value_store (key, value) VALUES ('ethereum_last_block', '0');
Expand Down Expand Up @@ -53,15 +61,15 @@ CREATE TABLE transactions_queue (
CONSTRAINT valid_stellar_public_key CHECK (char_length(stellar_public_key) = 56)
);
CREATE TYPE event AS ENUM ('transaction_received', 'account_created', 'account_credited');
CREATE TYPE event AS ENUM ('transaction_received', 'account_created', 'exchanged', 'exchanged_timelocked');
CREATE TABLE broadcasted_event (
id bigserial,
/* bitcoin 34 characters */
/* ethereum 42 characters */
address varchar(42) NOT NULL,
event event NOT NULL,
data varchar(255) NOT NULL,
data text NOT NULL,
PRIMARY KEY (id),
UNIQUE (address, event)
);
Expand All @@ -71,4 +79,4 @@ CREATE TABLE recovery_transaction (
envelope_xdr text NOT NULL
);
CREATE INDEX source_index ON recovery_transaction (source);
CREATE INDEX source_index ON recovery_transaction (source);`, SchemaVersion)
Binary file added services/bifrost/images/accounts.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 9eb9682

Please sign in to comment.