Skip to content

Commit

Permalink
Merge branch 'tilen/audit_fixes' into 'main'
Browse files Browse the repository at this point in the history
Audit fixes

See merge request flarenetwork/fast-updates!25
  • Loading branch information
tilenflare committed May 24, 2024
2 parents a153050 + 170e769 commit bc029ed
Show file tree
Hide file tree
Showing 32 changed files with 858 additions and 310 deletions.
44 changes: 23 additions & 21 deletions go-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,23 @@ FTSOv2 Top Level system (see [link](https://github.com/flare-foundation/ftso-v2-
where it registers its **sortition private kay** (see
below on how to generate the sortition key).

Set private information in environment variables

```bash
# voters private key registered in the VoterRegistry, aka signingPolicy private key
SIGNING_PRIVATE_KEY="0xd49743deccbccc5dc7baa8e69e5be03298da8688a15dd202e20f15d5e0e9a9fb"
# voters sortition key registered in the VoterRegistry contract that enables generating verifiable
# randomness to determine the order of clients submitting the fast updates
SORTITION_PRIVATE_KEY="0xd49743deccbccc5dc7baa8e69e5be03298da8688a15dd202e20f15d5e0e9a9fb"
# private keys of accounts from which the fast updates will be
# submitted - the client needs multiple addresses to not miss the
# submission window in case multiple fast updates can be submitted
# for blocks in a short interval
ACCOUNTS="0xd49743deccbccc5dc7baa8e69e5be03298da8688a15dd202e20f15d5e0e9a9fb,0x23c601ae397441f3ef6f1075dcb0031ff17fb079837beadaf3c84d96c6f3e569,0xee9d129c1997549ee09c0757af5939b2483d80ad649a0eda68e8b0357ad11131"
```

```toml
[client]
# private key corresponding to voter's "signingPolicyAddress"
# can also be set up with environment variable PRIVATE_KEY
signing_private_key = "0xd49743deccbccc5dc7baa8e69e5be03298da8688a15dd202e20f15d5e0e9a9fb"
# voters sortition key registered in the VoterRegistry contract that enables to generate verifiable
# randomness to determine the order of clients submitting the fast updates
# can also be set up with environment variable SORTITION_PRIVATE_KEY
sortition_private_key = "0xd49743deccbccc5dc7baa8e69e5be03298da8688a15dd202e20f15d5e0e9a9fb"
# address of the FastUpdater contract
fast_updater_address = "0xbe65A1F9a31D5E81d5e2B863AEf15bF9b3d92891"
# address of the Submission contract to which the updates are sent
Expand All @@ -44,16 +52,6 @@ incentive_manager_address = "0x919b4b4B561C72c990DC868F751328eF127c45F4"
submission_window = 10

[transactions]
# private keys of accounts from which the fast updates will be
# submitted - the client needs multiple addresses to not miss the
# submission window in case multiple fast updates can be submitted
# for blocks in a short interval
# can also be set up with environment variable ACCOUNTS
accounts = [
"0xd49743deccbccc5dc7baa8e69e5be03298da8688a15dd202e20f15d5e0e9a9fb",
"0x23c601ae397441f3ef6f1075dcb0031ff17fb079837beadaf3c84d96c6f3e569",
"0xee9d129c1997549ee09c0757af5939b2483d80ad649a0eda68e8b0357ad11131",
]
gas_limit = 8000000
value = 0
gas_price_multiplier = 1.2
Expand All @@ -63,6 +61,9 @@ gas_price_multiplier = 1.2
level = "INFO"
file = "./logger/logs/fast_updates_client.log"
console = true
# when the balance (in WEI) of the provided accounts falls bellow this value
# the logger will show warnings
min_balance = 10000000000000000000

[chain]
node_url = "https://coston2-api.flare.network/ext/C/rpc"
Expand All @@ -71,8 +72,9 @@ api_key = ""
chain_id = 114
```

It is advised that the private key, sortition private key, and accounts private keys
are set using environment variables to avoid accidentally exposing them.
The private key, sortition private key, and accounts private keys can also be set in
the configuration file, but we strongly suggest to set them using environment variables
to avoid accidentally exposing them.

## Price Updates Provider

Expand All @@ -94,8 +96,8 @@ one can define in `main.go` which price provider will be used.

## Running the FTSO Fast Updates Client

Assuming that the configuration file was set and the provider is
registered, simply run
Assuming that the configuration file and configuration environment variables ware set and
the provider is registered, simply run

```bash
go run main.go --config config.toml
Expand Down
23 changes: 20 additions & 3 deletions go-client/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,19 @@ type FastUpdatesClient struct {
transactionQueue *TransactionQueue
allFeeds []provider.FeedId
loggingParams config.LoggerConfig
Stats UpdatesStats
}

type Account struct {
Address common.Address
PrivateKey *ecdsa.PrivateKey
}

type UpdatesStats struct {
NumUpdates uint64
NumSuccessfulUpdates uint64
}

const (
refreshFeedsBlockInterval = 100
)
Expand Down Expand Up @@ -238,9 +244,9 @@ func (client *FastUpdatesClient) Run(startBlock, endBlock uint64) error {
}
logger.Info("new epoch, my weight weight %d, current block %d", weight, blockNum)
}
cutoff, err := client.GetCurrentScoreCutoff()
cutoff, err := client.GetBlockScoreCutoff(big.NewInt(int64(blockNum))) // todo
if err != nil {
return fmt.Errorf("Run: GetCurrentScoreCutoff: %w", err)
return fmt.Errorf("Run: GetCurrentScoreCutoff for block %d: %w", blockNum, err)
}

updateProofs, err := sortition.FindUpdateProofs(client.key, seed, cutoff, big.NewInt(int64(blockNum)), weight)
Expand All @@ -250,6 +256,17 @@ func (client *FastUpdatesClient) Run(startBlock, endBlock uint64) error {
for _, updateProof := range updateProofs {
logger.Info("scheduling update for block %d replicate %d", updateProof.BlockNumber, updateProof.Replicate)
client.SubmitUpdates(updateProof)
client.Stats.NumUpdates++
}

if len(updateProofs) > 0 {
balances, err := client.GetBalances()
if err != nil {
logger.Error("could not obtain balances: %s", err)
}
if !CheckBalances(balances, client.loggingParams.MinBalance) {
logger.Warn("account balance low: %s", balances)
}
}

if client.loggingParams.FeedValuesLog != 0 && blockNum%uint64(client.loggingParams.FeedValuesLog) == 0 {
Expand All @@ -263,7 +280,7 @@ func (client *FastUpdatesClient) Run(startBlock, endBlock uint64) error {
}

// do not calculate in advance more than specified
err = WaitForBlock(client.transactionQueue, blockNum-uint64(client.params.AdvanceBlocks))
err = WaitForBlock(client.transactionQueue, blockNum)
if err != nil {
return fmt.Errorf("Run: WaitForBlock: %w", err)
}
Expand Down
50 changes: 45 additions & 5 deletions go-client/client/client_requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ func (client *FastUpdatesClient) GetCurrentScoreCutoff() (*big.Int, error) {
return score, err
}

func (client *FastUpdatesClient) GetBlockScoreCutoff(blockNum *big.Int) (*big.Int, error) {
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Duration(config.CallTimeoutMillisDefault)*time.Millisecond)
ops := &bind.CallOpts{Context: ctx}
score, err := client.fastUpdater.BlockScoreCutoff(ops, blockNum)
cancelFunc()

return score, err
}

func (client *FastUpdatesClient) GetSeed(rewardEpochId int64) (*big.Int, error) {
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Duration(config.CallTimeoutMillisDefault)*time.Millisecond)
ops := &bind.CallOpts{Context: ctx}
Expand All @@ -54,6 +63,15 @@ func (client *FastUpdatesClient) GetScale() (*big.Int, error) {
return scale, err
}

func (client *FastUpdatesClient) GetExpectedSampleSize() (*big.Int, error) {
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Duration(config.CallTimeoutMillisDefault)*time.Millisecond)
ops := &bind.CallOpts{Context: ctx}
sampleSize, err := client.IncentiveManager.GetExpectedSampleSize(ops)
cancelFunc()

return sampleSize, err
}

func (client *FastUpdatesClient) GetCurrentRewardEpochId() (int64, error) {
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Duration(config.CallTimeoutMillisDefault)*time.Millisecond)
ops := &bind.CallOpts{Context: ctx}
Expand All @@ -78,7 +96,7 @@ func (client *FastUpdatesClient) GetMyWeight() (uint64, error) {
return weight.Uint64(), nil
}

func (client *FastUpdatesClient) GetPrices(feedIndexes []int) ([]float64, error) {
func (client *FastUpdatesClient) GetFeeds(feedIndexes []int) ([]float64, uint64, error) {
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Duration(config.CallTimeoutMillisDefault)*time.Millisecond)
ops := &bind.CallOpts{Context: ctx}

Expand All @@ -91,7 +109,7 @@ func (client *FastUpdatesClient) GetPrices(feedIndexes []int) ([]float64, error)
cancelFunc()

floatValues := RawChainValuesToFloats(feedValues)
return floatValues, err
return floatValues, feedValues.Timestamp, err
}

func (client *FastUpdatesClient) GetCurrentFeedIds() ([]provider.FeedId, error) {
Expand Down Expand Up @@ -119,6 +137,21 @@ func (client *FastUpdatesClient) Register(epoch int64) {
client.transactionQueue.InputChan <- compReq
}

func (client *FastUpdatesClient) GetBalances() ([]*big.Int, error) {
balances := make([]*big.Int, len(client.transactionAccounts))
for i, account := range client.transactionAccounts {
balance, err := client.chainClient.BalanceAt(context.Background(),
account.Address, nil)
if err != nil {
return nil, err
}

balances[i] = balance
}

return balances, nil
}

// only on mocked
func (client *FastUpdatesClient) register(epoch int64, txOpts *bind.TransactOpts) error {
policy := mock.FlareSystemMockPolicy{Pk1: client.key.Pk.X.Bytes(), Pk2: client.key.Pk.Y.Bytes(), Weight: uint16(1000)}
Expand Down Expand Up @@ -176,7 +209,7 @@ func (client *FastUpdatesClient) getOnlineOfflineValues() ([]int, []float64, []f
}

// get current prices from on-chain
chainValues, err := client.GetPrices(supportedFeedIndexes)
chainValues, _, err := client.GetFeeds(supportedFeedIndexes)
if err != nil {
return nil, nil, nil, err
}
Expand All @@ -191,6 +224,12 @@ func (client *FastUpdatesClient) submitUpdates(updateProof *sortition.UpdateProo
return err
}

// get current expectedSampleSize
sampleSize, err := client.GetExpectedSampleSize()
if err != nil {
return err
}

logger.Info("chain feeds values in block %d (before update): %v", client.transactionQueue.CurrentBlockNum, chainValues)
logger.Info("provider feeds values: %v", providerValues)

Expand All @@ -201,7 +240,7 @@ func (client *FastUpdatesClient) submitUpdates(updateProof *sortition.UpdateProo
}

// calculate deltas for provider and on-chain prices
deltas, deltasString, err := provider.GetDeltas(chainValues, providerValues, supportedFeedIndexes, scale)
deltas, deltasString, err := provider.GetDeltas(chainValues, providerValues, supportedFeedIndexes, scale, sampleSize)
if err != nil {
return err
}
Expand Down Expand Up @@ -248,9 +287,10 @@ func (client *FastUpdatesClient) submitUpdates(updateProof *sortition.UpdateProo
return fmt.Errorf("transaction failed")
}
logger.Info("successful update for block %d replicate %d in block %d", updateProof.BlockNumber, updateProof.Replicate, receipt.BlockNumber.Int64())
client.Stats.NumSuccessfulUpdates++

// get current prices from on-chain
chainValues, err = client.GetPrices(supportedFeedIndexes)
chainValues, _, err = client.GetFeeds(supportedFeedIndexes)
if err != nil {
return err
}
Expand Down
85 changes: 55 additions & 30 deletions go-client/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fast-updates-client/logger"
"fast-updates-client/provider"
"fast-updates-client/tests/test_utils"
"math"
"os"
"os/exec"
"testing"
Expand All @@ -23,18 +24,16 @@ func TestClient(t *testing.T) {
chainAddress = "http://ganache:8545"
valueProviderBaseUrl = "http://value-provider:3101"
} else {
// running a ganache node
logger.Info("starting a ganache chain node")
// cmd := exec.Command("bash", "-c", "docker run --publish 8544:8545 trufflesuite/ganache:latest --chain.hardfork=\"london\" --miner.blockTime=5 --wallet.accounts \"0xc5e8f61d1ab959b397eecc0a37a6517b8e67a0e7cf1f4bce5591f3ed80199122, 10000000000000000000000\" \"0xd49743deccbccc5dc7baa8e69e5be03298da8688a15dd202e20f15d5e0e9a9fb, 10000000000000000000000\" \"0x23c601ae397441f3ef6f1075dcb0031ff17fb079837beadaf3c84d96c6f3e569, 10000000000000000000000\" \"0xee9d129c1997549ee09c0757af5939b2483d80ad649a0eda68e8b0357ad11131, 10000000000000000000000\"")
cmd := exec.Command("bash", "-c", "docker compose up ganache")
// running a ganache node and an external provider that returns fixed values for testing
logger.Info("starting a ganache chain node and data provider")
// Can set VALUE_PROVIDER_IMPL to "fixed" or "random" to return 0.01 or random values for all feeds.
cmd := exec.Command("bash", "-c", "docker compose up ganache --detach && VALUE_PROVIDER_IMPL=random docker compose up value-provider --detach")
cmd.Dir = "../tests"
// cmd.Stdout = os.Stdout
// cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
go cmd.Run() //nolint:errcheck
chainAddress = "http://127.0.0.1:8545"

// runs an external provider that returns fixed values for testing
runValueProvider()
chainAddress = "http://127.0.0.1:8545"
valueProviderBaseUrl = "http://localhost:3101"
}

Expand All @@ -57,14 +56,14 @@ func TestClient(t *testing.T) {
cfgClient := config.FastUpdateClientConfig{
SigningPrivateKey: "0xd49743deccbccc5dc7baa8e69e5be03298da8688a15dd202e20f15d5e0e9a9fb",
SortitionPrivateKey: "0xd49743deccbccc5dc7baa8e69e5be03298da8688a15dd202e20f15d5e0e9a9fb",
SubmissionWindow: 5,
SubmissionWindow: 4,
MaxWeight: 1024,
}
cfgTransactions := config.TransactionsConfig{
Accounts: []string{"0xd49743deccbccc5dc7baa8e69e5be03298da8688a15dd202e20f15d5e0e9a9fb",
"0x23c601ae397441f3ef6f1075dcb0031ff17fb079837beadaf3c84d96c6f3e569",
"0xee9d129c1997549ee09c0757af5939b2483d80ad649a0eda68e8b0357ad11131"},
GasLimit: 8000000,
GasLimit: 80000000,
GasPriceMultiplier: 1.2,
}
cfgLog := config.LoggerConfig{Level: "DEBUG", Console: true, File: "../logger/logs/flare-ftso-indexer_test.log"}
Expand Down Expand Up @@ -100,36 +99,62 @@ func TestClient(t *testing.T) {
t.Fatal(err)
}

feedIds, err := client.GetCurrentFeedIds()
if err != nil {
t.Fatal(err)
}
indexes := make([]int, len(feedIds))
for i := range indexes {
indexes[i] = i
}
startingFeeds, _, err := client.GetFeeds(indexes)
if err != nil {
t.Fatal(err)
}

err = client.Run(blockNum, blockNum+10)
if err != nil {
t.Fatal(err)
}
client.Stop()

if chainNode != "docker_ganache" {
time.Sleep(time.Second)
// stopping a ganache node
cmd := exec.Command("bash", "-c", "docker compose stop ganache")
cmd.Dir = "../tests"
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run() //nolint:errcheck
feeds, _, err := client.GetFeeds(indexes)
if err != nil {
t.Fatal(err)
}
scaleBig, err := client.GetScale()
if err != nil {
t.Fatal(err)
}
scaleFloat, _ := scaleBig.Float64()
scale := scaleFloat / math.Pow(2, 127)

downDockerContainers()
if client.Stats.NumUpdates == 0 {
if err != nil {
t.Fatal(err)
t.Fatal("no updates submitted")
}
stopValueProvider()
}
}

// Can set VALUE_PROVIDER_IMPL to "fixed" or "random" to return 0.01 or random values for all feeds.
func runValueProvider() {
cmd := exec.Command("bash", "-c", "VALUE_PROVIDER_IMPL=random docker compose up value-provider")
cmd.Dir = "../tests"
go cmd.Run() //nolint:errcheck
if client.Stats.NumSuccessfulUpdates == 0 {
if err != nil {
t.Fatal("no successful update")
}
}

for i, val := range feeds {
// all updates are expected to be negative
expectedVal := startingFeeds[i] * math.Pow(scale, -float64(client.Stats.NumSuccessfulUpdates))
if expectedVal*0.999 > val && expectedVal*1.001 < val {
if err != nil {
t.Fatal("final feed values not correct:", expectedVal, val)
}
}
}
}

func stopValueProvider() {
cmd := exec.Command("bash", "-c", "docker compose stop value-provider")
func downDockerContainers() {
cmd := exec.Command("bash", "-c", "docker compose down ganache value-provider")
cmd.Dir = "../tests"
go cmd.Run() //nolint:errcheck
cmd.Run() //nolint:errcheck
}
Loading

0 comments on commit bc029ed

Please sign in to comment.