-
Notifications
You must be signed in to change notification settings - Fork 116
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Squashed commit of the following: (#1139)
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
1 parent
20ffef4
commit c0cd790
Showing
22 changed files
with
1,131 additions
and
203 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
133
protocol/app/vote_extensions/extend_vote_handler_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.