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

ci(tests): historacle e2e #1687

Merged
merged 19 commits into from
Jan 6, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 17 additions & 0 deletions tests/e2e/e2e_setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
appparams "github.com/umee-network/umee/v3/app/params"
"github.com/umee-network/umee/v3/x/leverage/fixtures"
leveragetypes "github.com/umee-network/umee/v3/x/leverage/types"
oracletypes "github.com/umee-network/umee/v3/x/oracle/types"
)

const (
Expand Down Expand Up @@ -230,6 +231,7 @@ func (s *IntegrationTestSuite) initGenesis() {
appGenState, genDoc, err := genutiltypes.GenesisStateFromGenFile(genFilePath)
s.Require().NoError(err)

// Gravity Bridge
var gravityGenState gravitytypes.GenesisState
s.Require().NoError(cdc.UnmarshalJSON(appGenState[gravitytypes.ModuleName], &gravityGenState))

Expand All @@ -242,12 +244,14 @@ func (s *IntegrationTestSuite) initGenesis() {
var bech32GenState bech32ibctypes.GenesisState
s.Require().NoError(cdc.UnmarshalJSON(appGenState[bech32ibctypes.ModuleName], &bech32GenState))

// bech32
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why adding these comments? it's "purple prose"

bech32GenState.NativeHRP = sdk.GetConfig().GetBech32AccountAddrPrefix()

bz, err = cdc.MarshalJSON(&bech32GenState)
s.Require().NoError(err)
appGenState[bech32ibctypes.ModuleName] = bz

// Leverage
var leverageGenState leveragetypes.GenesisState
s.Require().NoError(cdc.UnmarshalJSON(appGenState[leveragetypes.ModuleName], &leverageGenState))

Expand All @@ -259,6 +263,19 @@ func (s *IntegrationTestSuite) initGenesis() {
s.Require().NoError(err)
appGenState[leveragetypes.ModuleName] = bz

// Oracle
var oracleGenState oracletypes.GenesisState
s.Require().NoError(cdc.UnmarshalJSON(appGenState[oracletypes.ModuleName], &oracleGenState))

oracleGenState.Params.HistoricStampPeriod = 5
oracleGenState.Params.MaximumPriceStamps = 4
oracleGenState.Params.MedianStampPeriod = 20

bz, err = cdc.MarshalJSON(&oracleGenState)
s.Require().NoError(err)
appGenState[oracletypes.ModuleName] = bz

// Bank
var bankGenState banktypes.GenesisState
s.Require().NoError(cdc.UnmarshalJSON(appGenState[banktypes.ModuleName], &bankGenState))

Expand Down
12 changes: 12 additions & 0 deletions tests/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"

appparams "github.com/umee-network/umee/v3/app/params"
"github.com/umee-network/umee/v3/tests/gRPC"
)

func (s *IntegrationTestSuite) TestIBCTokenTransfer() {
Expand Down Expand Up @@ -139,3 +140,14 @@ func (s *IntegrationTestSuite) TestUmeeTokenTransfers() {
s.sendFromEthToUmeeCheck(orchestratorIdxSender, umeeValIdxReceiver, umeeERC20Addr, appparams.BondDenom, amount)
})
}

func (s *IntegrationTestSuite) TestHistorical() {
// TODO - don't hard code the RPC endpoints
err := gRPC.MedianCheck(
s.chain.id,
"tcp://localhost:26657",
"tcp://localhost:9090",
s.chain.validators[0].mnemonic,
)
s.Require().NoError(err)
}
112 changes: 112 additions & 0 deletions tests/gRPC/chain_height.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package gRPC

import (
"context"
"errors"
"sync"

"github.com/rs/zerolog"
tmrpcclient "github.com/tendermint/tendermint/rpc/client"
tmctypes "github.com/tendermint/tendermint/rpc/core/types"
tmtypes "github.com/tendermint/tendermint/types"
)

var (
errParseEventDataNewBlockHeader = errors.New("error parsing EventDataNewBlockHeader")
queryEventNewBlockHeader = tmtypes.QueryForEvent(tmtypes.EventNewBlockHeader)
)

// ChainHeight is used to cache the chain height of the
// current node which is being updated each time the
// node sends an event of EventNewBlockHeader.
// It starts a goroutine to subscribe to blockchain new block event and update the cached height.
type ChainHeight struct {
Logger zerolog.Logger

mtx sync.RWMutex
errGetChainHeight error
lastChainHeight int64
HeightChanged chan (int64)
}

// NewChainHeight returns a new ChainHeight struct that
// starts a new goroutine subscribed to EventNewBlockHeader.
func NewChainHeight(
ctx context.Context,
rpcClient tmrpcclient.Client,
logger zerolog.Logger,
) (*ChainHeight, error) {
if !rpcClient.IsRunning() {
if err := rpcClient.Start(); err != nil {
return nil, err
}
}

newBlockHeaderSubscription, err := rpcClient.Subscribe(
ctx, tmtypes.EventNewBlockHeader, queryEventNewBlockHeader.String())
if err != nil {
return nil, err
}

chainHeight := &ChainHeight{
Logger: logger.With().Str("oracle_client", "chain_height").Logger(),
}
chainHeight.HeightChanged = make(chan int64)

go chainHeight.subscribe(ctx, rpcClient, newBlockHeaderSubscription)
Fixed Show fixed Hide fixed

return chainHeight, nil
}

// updateChainHeight receives the data to be updated thread safe.
func (chainHeight *ChainHeight) updateChainHeight(blockHeight int64, err error) {
chainHeight.mtx.Lock()
defer chainHeight.mtx.Unlock()

if chainHeight.lastChainHeight != blockHeight {
select {
case chainHeight.HeightChanged <- blockHeight:
default:
}
}
chainHeight.lastChainHeight = blockHeight
chainHeight.errGetChainHeight = err
}

// subscribe listens to new blocks being made
// and updates the chain height.
func (chainHeight *ChainHeight) subscribe(
ctx context.Context,
eventsClient tmrpcclient.EventsClient,
newBlockHeaderSubscription <-chan tmctypes.ResultEvent,
) {
for {
select {
case <-ctx.Done():
err := eventsClient.Unsubscribe(ctx, tmtypes.EventNewBlockHeader, queryEventNewBlockHeader.String())
if err != nil {
chainHeight.Logger.Err(err)
chainHeight.updateChainHeight(chainHeight.lastChainHeight, err)
}
chainHeight.Logger.Info().Msg("closing the ChainHeight subscription")
return

case resultEvent := <-newBlockHeaderSubscription:
eventDataNewBlockHeader, ok := resultEvent.Data.(tmtypes.EventDataNewBlockHeader)
if !ok {
chainHeight.Logger.Err(errParseEventDataNewBlockHeader)
chainHeight.updateChainHeight(chainHeight.lastChainHeight, errParseEventDataNewBlockHeader)
continue
}
chainHeight.updateChainHeight(eventDataNewBlockHeader.Header.Height, nil)
}
}
}

// GetChainHeight returns the last chain height available.
func (chainHeight *ChainHeight) GetChainHeight() (int64, error) {
chainHeight.mtx.RLock()
defer chainHeight.mtx.RUnlock()

return chainHeight.lastChainHeight, chainHeight.errGetChainHeight
}
34 changes: 34 additions & 0 deletions tests/gRPC/grpc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package gRPC

import (
"context"
"net"
"strings"
)

func dialerFunc(_ context.Context, addr string) (net.Conn, error) {
return Connect(addr)
}

// Connect dials the given address and returns a net.Conn. The protoAddr
// argument should be prefixed with the protocol,
// eg. "tcp://127.0.0.1:8080" or "unix:///tmp/test.sock".
func Connect(protoAddr string) (net.Conn, error) {
proto, address := ProtocolAndAddress(protoAddr)
conn, err := net.Dial(proto, address)
return conn, err
}

// ProtocolAndAddress splits an address into the protocol and address components.
// For instance, "tcp://127.0.0.1:8080" will be split into "tcp" and "127.0.0.1:8080".
// If the address has no protocol prefix, the default is "tcp".
func ProtocolAndAddress(listenAddr string) (string, string) {
protocol, address := "tcp", listenAddr

parts := strings.SplitN(address, "://", 2)
if len(parts) == 2 {
protocol, address = parts[0], parts[1]
}

return protocol, address
}
106 changes: 106 additions & 0 deletions tests/gRPC/historical.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package gRPC

import (
"context"
"fmt"

"github.com/rs/zerolog"
)

func MedianCheck(
chainID string,
tmrpcEndpoint string,
grpcEndpoint string,
val1Mnemonic string,
) error {
val1Client, err := NewUmeeClient(chainID, tmrpcEndpoint, grpcEndpoint, "val1", val1Mnemonic)
if err != nil {
return err
}

err = val1Client.createClientContext()
if err != nil {
return err
}

val1Client.createQueryClient()

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

priceStore := &PriceStore{}

err = listenForPrices(ctx, val1Client, "umee", priceStore)
if err != nil {
return err
}

return priceStore.checkMedian()
}

func listenForPrices(
ctx context.Context,
umeeClient *UmeeClient,
denom string,
priceStore *PriceStore,
) error {
chainHeight, err := NewChainHeight(ctx, umeeClient.clientContext.Client, zerolog.Nop())
if err != nil {
return err
}

params, _ := umeeClient.QueryParams() // TODO error handling
fmt.Printf("%+v\n", params)

// Wait until the end of a median period
var beginningHeight int64
for {
beginningHeight = <-chainHeight.HeightChanged
if isPeriodLastBlock(beginningHeight, params.MedianStampPeriod) {
fmt.Printf("%d: ", beginningHeight)
fmt.Println("median stamp period last block")
break
}
}

// Record each historic stamp when the chain should be recording them
for i := 0; i < int(params.MedianStampPeriod); i++ {
height := <-chainHeight.HeightChanged
if isPeriodFirstBlock(height, params.HistoricStampPeriod) {
fmt.Printf("%d: ", height)
fmt.Println("historic stamp period first block")
exchangeRates, err := umeeClient.QueryExchangeRates()
if err != nil {
return nil
}
for _, rate := range exchangeRates {
if rate.Denom == denom {
priceStore.historicStamps = append(priceStore.historicStamps, rate.Amount)
}
}
}
}

// Wait one more block for the median
height := <-chainHeight.HeightChanged
fmt.Printf("%d: ", height)
fmt.Println("reading final median")
medians, err := umeeClient.QueryMedians()
if err != nil {
return err
}
for _, median := range medians {
if median.Denom == denom {
priceStore.median = median.Amount
}
}
return nil
}

func isPeriodFirstBlock(height int64, blocksPerPeriod uint64) bool {
return uint64(height)%blocksPerPeriod == 0
}

func isPeriodLastBlock(height int64, blocksPerPeriod uint64) bool {
return uint64(height+1)%blocksPerPeriod == 0
}
37 changes: 37 additions & 0 deletions tests/gRPC/key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package gRPC

import (
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
sdk "github.com/cosmos/cosmos-sdk/types"

umeeapp "github.com/umee-network/umee/v3/app"
)

const (
keyringPassphrase = "testpassphrase"
keyringAppName = "testnet"
)

func CreateAccountFromMnemonic(name, mnemonic string) (*keyring.Record, keyring.Keyring, error) {
encodingConfig := umeeapp.MakeEncodingConfig()
cdc := encodingConfig.Codec

kb, err := keyring.New(keyringAppName, keyring.BackendTest, "", nil, cdc)
if err != nil {
return nil, nil, err
}

keyringAlgos, _ := kb.SupportedAlgorithms()
algo, err := keyring.NewSigningAlgoFromString(string(hd.Secp256k1Type), keyringAlgos)
if err != nil {
return nil, nil, err
}

account, err := kb.NewAccount(name, mnemonic, "", sdk.FullFundraiserPath, algo)
if err != nil {
return nil, nil, err
}

return account, kb, nil
}
24 changes: 24 additions & 0 deletions tests/gRPC/price_store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package gRPC

import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/umee-network/umee/v3/util/decmath"
)

type PriceStore struct {
historicStamps []sdk.Dec
median sdk.Dec
}

func (ps *PriceStore) checkMedian() error {
calcMedian, err := decmath.Median(ps.historicStamps)
if err != nil {
return err
}
if ps.median != calcMedian {
return fmt.Errorf("expected %d for the median but got %d", ps.median, calcMedian)
}
return nil
}