Skip to content

Commit

Permalink
Squashed commit of the following: (#1139)
Browse files Browse the repository at this point in the history
commit 9f8ff45
Author: Eric <[email protected]>
Date:   Mon Mar 11 12:46:53 2024 -0700

    Update sample pregenesis

commit 8573ddf
Merge: eea7890 340801a
Author: Eric <[email protected]>
Date:   Mon Mar 11 12:41:09 2024 -0700

    Merge remote-tracking branch 'upstream' into eric/ve-logic

commit eea7890
Author: Eric <[email protected]>
Date:   Mon Mar 11 12:41:06 2024 -0700

    Update go version in Dockerfile

commit ba27204
Author: Eric <[email protected]>
Date:   Fri Mar 8 09:44:04 2024 -0800

    Add slinky utils, use that to convert between market and currency pair

commit 667a804
Author: Eric <[email protected]>
Date:   Wed Mar 6 20:43:40 2024 -0800

    Update error messages

commit d53292c
Author: Eric <[email protected]>
Date:   Wed Mar 6 20:16:01 2024 -0800

    Update docstrings, rename OracleClient

commit daad125
Author: Eric <[email protected]>
Date:   Mon Mar 4 10:51:23 2024 -0800

    VoteExtension slinky logic
  • Loading branch information
Eric-Warehime authored Mar 14, 2024
1 parent 20ffef4 commit c0cd790
Show file tree
Hide file tree
Showing 22 changed files with 1,131 additions and 203 deletions.
28 changes: 28 additions & 0 deletions protocol/app/process/market_price_decoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package process

import (
sdk "github.com/cosmos/cosmos-sdk/types"
pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types"
)

// MarketPriceDecoder is an interface for decoding market price transactions, This interface is responsible
// for distinguishing between logic for unmarshalling MarketPriceUpdates, between MarketPriceUpdates
// determined by the proposer's price-cache, and from VoteExtensions.
type UpdateMarketPriceTxDecoder interface {
// DecodeUpdateMarketPricesTx decodes the tx-bytes from the RequestProcessProposal and returns a MarketPriceUpdateTx.
DecodeUpdateMarketPricesTx(ctx sdk.Context, txs [][]byte) (*UpdateMarketPricesTx, error)

// GetTxOffset returns the offset that other injected txs should be placed with respect to their normally
// expected indices. This method is used to account for injected vote-extensions, or any other injected
// txs from dependencies.
GetTxOffset(ctx sdk.Context) int
}

func NewUpdateMarketPricesTx(
ctx sdk.Context, pk ProcessPricesKeeper, msg *pricestypes.MsgUpdateMarketPrices) *UpdateMarketPricesTx {
return &UpdateMarketPricesTx{
ctx: ctx,
pricesKeeper: pk,
msg: msg,
}
}
18 changes: 18 additions & 0 deletions protocol/app/vote_extensions/expected_keepers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package vote_extensions

import (
sdk "github.com/cosmos/cosmos-sdk/types"
oracletypes "github.com/skip-mev/slinky/pkg/types"

pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types"
)

// PricesKeeper is the expected interface for the x/price keeper used by the vote extension handlers
type PricesKeeper interface {
GetCurrencyPairFromID(ctx sdk.Context, id uint64) (cp oracletypes.CurrencyPair, found bool)
GetValidMarketPriceUpdates(ctx sdk.Context) *pricestypes.MsgUpdateMarketPrices
UpdateMarketPrices(
ctx sdk.Context,
updates []*pricestypes.MsgUpdateMarketPrices_MarketPrice,
) (err error)
}
62 changes: 62 additions & 0 deletions protocol/app/vote_extensions/extend_vote_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package vote_extensions

import (
"fmt"

cometabci "github.com/cometbft/cometbft/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/dydxprotocol/v4-chain/protocol/app/process"
prices "github.com/dydxprotocol/v4-chain/protocol/x/prices/types"
)

// ExtendVoteHandler is a wrapper around the Slinky ExtendVoteHandler. This wrapper is responsible for
// applying the newest MarketPriceUpdates in a block so that the prices to be submitted in a vote extension are
// determined on the latest available information.
type ExtendVoteHandler struct {
SlinkyExtendVoteHandler sdk.ExtendVoteHandler
PricesTxDecoder process.UpdateMarketPriceTxDecoder
PricesKeeper PricesKeeper
}

// ExtendVoteHandler returns a sdk.ExtendVoteHandler, responsible for the following:
// 1. Decoding the x/prices MsgUpdateMarketPrices in the current block - fail on errors
// 2. Validating the proposed MsgUpdateMarketPrices in accordance with the ProcessProposal check
// 3. Updating the market prices in the PricesKeeper so that the GetValidMarketPriceUpdates function returns the
// latest available market prices
// 4. Calling the Slinky ExtendVoteHandler to handle the rest of ExtendVote
//
// See https://github.com/skip-mev/slinky/blob/a5b1d3d3a2723e4746b5d588c512d7cc052dc0ff/abci/ve/vote_extension.go#L77
// for the Slinky ExtendVoteHandler logic.
func (e *ExtendVoteHandler) ExtendVoteHandler() sdk.ExtendVoteHandler {
return func(ctx sdk.Context, req *cometabci.RequestExtendVote) (resp *cometabci.ResponseExtendVote, err error) {
// Decode the x/prices txn in the current block
updatePricesTx, err := e.PricesTxDecoder.DecodeUpdateMarketPricesTx(ctx, req.Txs)
if err != nil {
return nil, fmt.Errorf("DecodeMarketPricesTx failure %w", err)
}

// ensure that the proposed MsgUpdateMarketPrices is valid in accordance w/ stateful information
// this check is equivalent to the check in ProcessProposal (indexPriceCache has not been updated)
err = updatePricesTx.Validate()
if err != nil {
return nil, fmt.Errorf("updatePricesTx.Validate failure %w", err)
}

// Update the market prices in the PricesKeeper, so that the GetValidMarketPriceUpdates
// function returns the latest available market prices.
updateMarketPricesMsg, ok := updatePricesTx.GetMsg().(*prices.MsgUpdateMarketPrices)
if !ok {
return nil, fmt.Errorf("expected %s, got %T", "MsgUpdateMarketPrices", updateMarketPricesMsg)
}

// Update the market prices in the PricesKeeper
err = e.PricesKeeper.UpdateMarketPrices(ctx, updateMarketPricesMsg.MarketPriceUpdates)
if err != nil {
return nil, fmt.Errorf("failed to update market prices in extend vote handler pre-slinky invocation %w", err)
}

// Call the Slinky ExtendVoteHandler
return e.SlinkyExtendVoteHandler(ctx, req)
}
}
133 changes: 133 additions & 0 deletions protocol/app/vote_extensions/extend_vote_handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package vote_extensions

import (
"fmt"
"testing"

cometabci "github.com/cometbft/cometbft/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"

"github.com/dydxprotocol/v4-chain/protocol/app/process"
"github.com/dydxprotocol/v4-chain/protocol/mocks"
"github.com/dydxprotocol/v4-chain/protocol/testutil/constants"
)

func TestExtendVoteHandlerDeecodeMarketPricesFailure(t *testing.T) {
slinkyEvh := mocks.NewExtendVoteHandler(t)
pricesTxDecoder := mocks.NewUpdateMarketPriceTxDecoder(t)
pricesKeeper := mocks.NewPricesKeeper(t)
evh := ExtendVoteHandler{
SlinkyExtendVoteHandler: slinkyEvh.Execute,
PricesTxDecoder: pricesTxDecoder,
PricesKeeper: pricesKeeper,
}

pricesTxDecoder.On("DecodeUpdateMarketPricesTx", mock.Anything, mock.Anything).Return(
nil, fmt.Errorf("foobar"))
_, err := evh.ExtendVoteHandler()(sdk.Context{}, &cometabci.RequestExtendVote{Txs: make([][]byte, 0)})

require.ErrorContains(t, err, "DecodeMarketPricesTx failure foobar")
pricesTxDecoder.AssertExpectations(t)
pricesKeeper.AssertExpectations(t)
slinkyEvh.AssertExpectations(t)
}

func TestExtendVoteHandlerUpdatePricesTxValidateFailure(t *testing.T) {
slinkyEvh := mocks.NewExtendVoteHandler(t)
pricesTxDecoder := mocks.NewUpdateMarketPriceTxDecoder(t)
pricesKeeper := mocks.NewPricesKeeper(t)
evh := ExtendVoteHandler{
SlinkyExtendVoteHandler: slinkyEvh.Execute,
PricesTxDecoder: pricesTxDecoder,
PricesKeeper: pricesKeeper,
}

pricesTxDecoder.On("DecodeUpdateMarketPricesTx", mock.Anything, mock.Anything).Return(
process.NewUpdateMarketPricesTx(sdk.Context{}, pricesKeeper, constants.InvalidMsgUpdateMarketPricesStateless),
nil)
_, err := evh.ExtendVoteHandler()(sdk.Context{}, &cometabci.RequestExtendVote{Txs: make([][]byte, 0)})

require.ErrorContains(t, err, "updatePricesTx.Validate failure")
pricesTxDecoder.AssertExpectations(t)
pricesKeeper.AssertExpectations(t)
slinkyEvh.AssertExpectations(t)
}

func TestExtendVoteHandlerUpdateMarketPricesError(t *testing.T) {
slinkyEvh := mocks.NewExtendVoteHandler(t)
pricesTxDecoder := mocks.NewUpdateMarketPriceTxDecoder(t)
pricesKeeper := mocks.NewPricesKeeper(t)
evh := ExtendVoteHandler{
SlinkyExtendVoteHandler: slinkyEvh.Execute,
PricesTxDecoder: pricesTxDecoder,
PricesKeeper: pricesKeeper,
}

pricesTxDecoder.On("DecodeUpdateMarketPricesTx", mock.Anything, mock.Anything).Return(
process.NewUpdateMarketPricesTx(sdk.Context{}, pricesKeeper, constants.EmptyMsgUpdateMarketPrices),
nil)
pricesKeeper.On("PerformStatefulPriceUpdateValidation", mock.Anything, mock.Anything, mock.Anything).
Return(nil)
pricesKeeper.On("UpdateMarketPrices", mock.Anything, mock.Anything).
Return(fmt.Errorf(""))
_, err := evh.ExtendVoteHandler()(sdk.Context{}, &cometabci.RequestExtendVote{Txs: make([][]byte, 0)})

require.ErrorContains(t, err, "failed to update market prices in extend vote handler pre-slinky invocation")
pricesTxDecoder.AssertExpectations(t)
pricesKeeper.AssertExpectations(t)
slinkyEvh.AssertExpectations(t)
}

func TestExtendVoteHandlerSlinkyFailure(t *testing.T) {
slinkyEvh := mocks.NewExtendVoteHandler(t)
pricesTxDecoder := mocks.NewUpdateMarketPriceTxDecoder(t)
pricesKeeper := mocks.NewPricesKeeper(t)
evh := ExtendVoteHandler{
SlinkyExtendVoteHandler: slinkyEvh.Execute,
PricesTxDecoder: pricesTxDecoder,
PricesKeeper: pricesKeeper,
}

pricesTxDecoder.On("DecodeUpdateMarketPricesTx", mock.Anything, mock.Anything).Return(
process.NewUpdateMarketPricesTx(sdk.Context{}, pricesKeeper, constants.EmptyMsgUpdateMarketPrices),
nil)
pricesKeeper.On("PerformStatefulPriceUpdateValidation", mock.Anything, mock.Anything, mock.Anything).
Return(nil)
pricesKeeper.On("UpdateMarketPrices", mock.Anything, mock.Anything).Return(nil)
slinkyEvh.On("Execute", mock.Anything, mock.Anything).
Return(&cometabci.ResponseExtendVote{}, fmt.Errorf("slinky failure"))
_, err := evh.ExtendVoteHandler()(sdk.Context{}, &cometabci.RequestExtendVote{Txs: make([][]byte, 0)})

require.ErrorContains(t, err, "slinky failure")
pricesTxDecoder.AssertExpectations(t)
pricesKeeper.AssertExpectations(t)
slinkyEvh.AssertExpectations(t)
}

func TestExtendVoteHandlerSlinkySuccess(t *testing.T) {
slinkyEvh := mocks.NewExtendVoteHandler(t)
pricesTxDecoder := mocks.NewUpdateMarketPriceTxDecoder(t)
pricesKeeper := mocks.NewPricesKeeper(t)
evh := ExtendVoteHandler{
SlinkyExtendVoteHandler: slinkyEvh.Execute,
PricesTxDecoder: pricesTxDecoder,
PricesKeeper: pricesKeeper,
}

pricesTxDecoder.On("DecodeUpdateMarketPricesTx", mock.Anything, mock.Anything).Return(
process.NewUpdateMarketPricesTx(sdk.Context{}, pricesKeeper, constants.EmptyMsgUpdateMarketPrices),
nil)
pricesKeeper.On("PerformStatefulPriceUpdateValidation", mock.Anything, mock.Anything, mock.Anything).
Return(nil)
pricesKeeper.On("UpdateMarketPrices", mock.Anything, mock.Anything).Return(nil)
slinkyEvh.On("Execute", mock.Anything, mock.Anything).
Return(&cometabci.ResponseExtendVote{}, nil)
_, err := evh.ExtendVoteHandler()(sdk.Context{}, &cometabci.RequestExtendVote{Txs: make([][]byte, 0)})

require.NoError(t, err)
pricesTxDecoder.AssertExpectations(t)
pricesKeeper.AssertExpectations(t)
slinkyEvh.AssertExpectations(t)
}
77 changes: 77 additions & 0 deletions protocol/app/vote_extensions/oracle_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package vote_extensions

import (
"context"
"fmt"
"strconv"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
oracleservicetypes "github.com/skip-mev/slinky/service/servers/oracle/types"
"google.golang.org/grpc"
)

// OraclePrices is an implementation of the Slinky OracleClient interface.
// This object is responsible for requesting prices from the x/prices module, and injecting those prices into the
// vote-extension.
// The
type OraclePrices struct {
PricesKeeper PricesKeeper
}

// NewOraclePrices returns a new OracleClient object.
func NewOraclePrices(pricesKeeper PricesKeeper) *OraclePrices {
return &OraclePrices{
PricesKeeper: pricesKeeper,
}
}

// Start is a no-op.
func (o *OraclePrices) Start(_ context.Context) error {
return nil
}

// Stop is a no-op.
func (o *OraclePrices) Stop() error {
return nil
}

// Prices is called in ExtendVoteHandler to determine which Prices are put into the extended commit.
// This method is responsible for doing the following:
// 1. Get the latest prices from the x/prices module's indexPriceCache via GetValidMarketPriceUpdates
// 2. Translate the response from x/prices into a QueryPricesResponse, and return it.
//
// This method fails if:
// - The passed in context is not an sdk.Context
func (o *OraclePrices) Prices(ctx context.Context,
_ *oracleservicetypes.QueryPricesRequest,
_ ...grpc.CallOption) (*oracleservicetypes.QueryPricesResponse, error) {
sdkCtx, ok := ctx.(sdk.Context)
if !ok {
return nil, fmt.Errorf("OraclePrices was passed on non-sdk context object")
}

// get the final prices to include in the vote-extension from the x/prices module
validUpdates := o.PricesKeeper.GetValidMarketPriceUpdates(sdkCtx)
if validUpdates == nil {
sdkCtx.Logger().Info("prices keeper returned no valid market price updates")
return nil, nil
}
sdkCtx.Logger().Info("prices keeper returned valid updates", "length", len(validUpdates.MarketPriceUpdates))

// translate price updates into oracle response
var outputResponse = &oracleservicetypes.QueryPricesResponse{
Prices: make(map[string]string),
Timestamp: time.Now(),
}
for _, update := range validUpdates.MarketPriceUpdates {
mappedPair, found := o.PricesKeeper.GetCurrencyPairFromID(sdkCtx, uint64(update.GetMarketId()))
if found {
sdkCtx.Logger().Info("added currency pair", "pair", mappedPair.String())
outputResponse.Prices[mappedPair.String()] = strconv.FormatUint(update.Price, 10)
} else {
sdkCtx.Logger().Info("failed to add currency pair", "pair", mappedPair.String())
}
}
return outputResponse, nil
}
Loading

0 comments on commit c0cd790

Please sign in to comment.