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

feat!: support for metaprotocol types #2960

Merged
merged 13 commits into from
Feb 27, 2024
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
build
.github
.vscode
7 changes: 7 additions & 0 deletions app/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ import (

gaiaappparams "github.com/cosmos/gaia/v15/app/params"
"github.com/cosmos/gaia/v15/x/globalfee"
"github.com/cosmos/gaia/v15/x/metaprotocols"
metaprotocolstypes "github.com/cosmos/gaia/v15/x/metaprotocols/types"
)

var maccPerms = map[string][]string{
Expand Down Expand Up @@ -111,6 +113,7 @@ var ModuleBasics = module.NewBasicManager(
globalfee.AppModule{},
icsprovider.AppModuleBasic{},
consensus.AppModuleBasic{},
metaprotocols.AppModuleBasic{},
)

func appModules(
Expand Down Expand Up @@ -149,6 +152,7 @@ func appModules(
app.ICAModule,
app.PFMRouterModule,
app.ProviderModule,
metaprotocols.NewAppModule(),
}
}

Expand Down Expand Up @@ -219,6 +223,7 @@ func orderBeginBlockers() []string {
globalfee.ModuleName,
providertypes.ModuleName,
consensusparamtypes.ModuleName,
metaprotocolstypes.ModuleName,
}
}

Expand Down Expand Up @@ -255,6 +260,7 @@ func orderEndBlockers() []string {
globalfee.ModuleName,
providertypes.ModuleName,
consensusparamtypes.ModuleName,
metaprotocolstypes.ModuleName,
}
}

Expand Down Expand Up @@ -299,5 +305,6 @@ func orderInitBlockers() []string {
globalfee.ModuleName,
providertypes.ModuleName,
consensusparamtypes.ModuleName,
metaprotocolstypes.ModuleName,
}
}
71 changes: 71 additions & 0 deletions docs/docs/metaprotocols/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
title: Metaprotocol Support
order: false
parent:
order: 2
---

The `x/metaprotocol` module adds support for encoding and decoding additional fields attached to transactions.

`extension_options` and `non_critical_extension_options` are optional fields that can be used to attach data to valid transactions. The fields are validated by the blockchain, but they are not used in any way. The fields pass validation if they are provided as empty lists (`[ ]`) or they use a list of `ExtensionData` types.

The application does not use the attached data but it does ensure that the correct type is provided and that it can be successfully unmarshalled. The attached data will be part of a block.

:::tip
Txs where `extension_options` or `non_critical_extension_options` are populated with a type other than `/gaia.metaprotocols.ExtensionData` are considered invalid and will be rejected.
:::

Here is an example of a correctly formed `non_critical_extension_options` field:

```json
{
"@type": "/gaia.metaprotocols.ExtensionData", // must be this exact string
"protocol_id": "some-protocol",
"protocol_version": "1",
"data": "<base64 encoded bytes>"
}
```

Here is an example of a correctly populated `non_critical_extension_options` on a `bank.MsgSend` transaction:

```json
{
"body": {
"messages": [
{
"@type": "/cosmos.bank.v1beta1.MsgSend",
"from_address": "cosmos1ehpqg9sj09037uhe56sqktk30asn47asthyr22",
"to_address": "cosmos1ehpqg9sj09037uhe56sqktk30asn47asthyr22",
"amount": [
{
"denom": "uatom",
"amount": "100"
}
]
}
],
"memo": "memo_smaller_than_512_bytes",
"timeout_height": "0",
"extension_options": [],
"non_critical_extension_options": [
{
"@type": "/gaia.metaprotocols.ExtensionData",
"protocol_id": "some-protocol",
"protocol_version": "1",
"data": "<base64 encoded bytes>"
}
]
},
"auth_info": {
"signer_infos": [],
"fee": {
"amount": [],
"gas_limit": "200000",
"payer": "",
"granter": ""
},
"tip": null
},
"signatures": []
}
```
5 changes: 5 additions & 0 deletions docs/docs/metaprotocols/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"label": "Metaprotocol support",
"position": 15,
"link": { "type": "doc", "id": "metaprotocols/README" }
}
19 changes: 19 additions & 0 deletions proto/gaia/metaprotocols/extensions.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
syntax = "proto3";
package gaia.metaprotocols;

option go_package = "github.com/cosmos/gaia/x/metaprotocols/types";

// ExtensionData is a data structure that can be used in transaction extensions.
message ExtensionData {
// protocol_id is the identifier of the protocol
// the field is not used internally but it is validated for correctness
string protocol_id = 1;

// protocol_version is the identifier of the protocol version
// the field is not used internally but it is validated for correctness
string protocol_version = 2;

// arbitrary bytes data that can be used to store any data
// the field is not used internally but it is validated and must be provided
bytes data = 3;
}
4 changes: 4 additions & 0 deletions tests/e2e/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
authvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
distribtypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types"
govv1types "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
Expand All @@ -23,6 +24,7 @@ import (
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"

gaiaparams "github.com/cosmos/gaia/v15/app/params"
metaprotocoltypes "github.com/cosmos/gaia/v15/x/metaprotocols/types"
)

const (
Expand All @@ -38,6 +40,7 @@ var (

func init() {
encodingConfig = gaiaparams.MakeEncodingConfig()
banktypes.RegisterInterfaces(encodingConfig.InterfaceRegistry)
authtypes.RegisterInterfaces(encodingConfig.InterfaceRegistry)
authvesting.RegisterInterfaces(encodingConfig.InterfaceRegistry)
stakingtypes.RegisterInterfaces(encodingConfig.InterfaceRegistry)
Expand All @@ -51,6 +54,7 @@ func init() {
upgradetypes.RegisterInterfaces(encodingConfig.InterfaceRegistry)
distribtypes.RegisterInterfaces(encodingConfig.InterfaceRegistry)
providertypes.RegisterInterfaces(encodingConfig.InterfaceRegistry)
metaprotocoltypes.RegisterInterfaces(encodingConfig.InterfaceRegistry)

cdc = encodingConfig.Marshaler
txConfig = encodingConfig.TxConfig
Expand Down
122 changes: 121 additions & 1 deletion tests/e2e/e2e_bank_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ package e2e

import (
"fmt"
"path/filepath"
"time"

codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
authTx "github.com/cosmos/cosmos-sdk/x/auth/tx"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"

extensiontypes "github.com/cosmos/gaia/v15/x/metaprotocols/types"
)

func (s *IntegrationTestSuite) testBankTokenTransfer() {
s.Run("send_photon_between_accounts", func() {
s.Run("send_tokens_between_accounts", func() {
var (
err error
valIdx = 0
Expand Down Expand Up @@ -96,3 +102,117 @@ func (s *IntegrationTestSuite) testBankTokenTransfer() {
)
})
}

// tests the bank send command with populated non_critical_extension_options field
// the Tx should succeed if the data can be properly encoded and decoded
// the tx is signed and broadcast using gaiad tx sign and broadcast commands
func (s *IntegrationTestSuite) bankSendWithNonCriticalExtensionOptions() {
s.Run("transfer_with_non_critical_extension_options", func() {
c := s.chainA

submitterAccount := c.genesisAccounts[1]
submitterAddress, err := submitterAccount.keyInfo.GetAddress()
s.Require().NoError(err)
sendMsg := banktypes.NewMsgSend(submitterAddress, submitterAddress, sdk.NewCoins(sdk.NewCoin(uatomDenom, sdk.NewInt(100))))

// valid non-critical extension options
ext := &extensiontypes.ExtensionData{
ProtocolId: "test-protocol",
ProtocolVersion: "1",
Data: []byte("Hello Cosmos"),
}

extAny, err := codectypes.NewAnyWithValue(ext)
s.Require().NoError(err)
s.Require().NotNil(extAny)

txBuilder := encodingConfig.TxConfig.NewTxBuilder()

s.Require().NoError(txBuilder.SetMsgs(sendMsg))

txBuilder.SetMemo("non-critical-ext-message-test")
txBuilder.SetFeeAmount(sdk.NewCoins(standardFees))
txBuilder.SetGasLimit(200000)

// add extension options
tx := txBuilder.GetTx()
if etx, ok := tx.(authTx.ExtensionOptionsTxBuilder); ok {
etx.SetNonCriticalExtensionOptions(extAny)
}

bz, err := encodingConfig.TxConfig.TxEncoder()(tx)
s.Require().NoError(err)
s.Require().NotNil(bz)

txWithExt, err := decodeTx(bz)
s.Require().NoError(err)
s.Require().NotNil(txWithExt)

rawTx, err := cdc.MarshalJSON(txWithExt)
s.Require().NoError(err)
s.Require().NotNil(rawTx)

unsignedFname := "unsigned_non_critical_extension_option_tx.json"
unsignedJSONFile := filepath.Join(c.validators[0].configDir(), unsignedFname)
err = writeFile(unsignedJSONFile, rawTx)
s.Require().NoError(err)

signedTx, err := s.signTxFileOnline(c, 0, submitterAddress.String(), unsignedFname)
s.Require().NoError(err)
s.Require().NotNil(signedTx)

signedFname := "signed_non_critical_extension_option_tx.json"
signedJSONFile := filepath.Join(c.validators[0].configDir(), signedFname)
err = writeFile(signedJSONFile, signedTx)
s.Require().NoError(err)

// if there's no errors the non_critical_extension_options field was properly encoded and decoded
out, err := s.broadcastTxFile(c, 0, submitterAddress.String(), signedFname)
s.Require().NoError(err)
s.Require().NotNil(out)
})
}

// tests the bank send command with invalid non_critical_extension_options field
// the tx should always fail to decode the extension options since no concrete type is registered for the provided extension field
func (s *IntegrationTestSuite) failedBankSendWithNonCriticalExtensionOptions() {
s.Run("fail_encoding_invalid_non_critical_extension_options", func() {
c := s.chainA

submitterAccount := c.genesisAccounts[1]
submitterAddress, err := submitterAccount.keyInfo.GetAddress()
s.Require().NoError(err)
sendMsg := banktypes.NewMsgSend(submitterAddress, submitterAddress, sdk.NewCoins(sdk.NewCoin(uatomDenom, sdk.NewInt(100))))

// the message does not matter, as long as it is in the interface registry
ext := &banktypes.MsgMultiSend{}

extAny, err := codectypes.NewAnyWithValue(ext)
s.Require().NoError(err)
s.Require().NotNil(extAny)

txBuilder := encodingConfig.TxConfig.NewTxBuilder()

s.Require().NoError(txBuilder.SetMsgs(sendMsg))

txBuilder.SetMemo("fail-non-critical-ext-message")
txBuilder.SetFeeAmount(sdk.NewCoins(standardFees))
txBuilder.SetGasLimit(200000)

// add extension options
tx := txBuilder.GetTx()
if etx, ok := tx.(authTx.ExtensionOptionsTxBuilder); ok {
etx.SetNonCriticalExtensionOptions(extAny)
}

bz, err := encodingConfig.TxConfig.TxEncoder()(tx)
s.Require().NoError(err)
s.Require().NotNil(bz)

// decode fails because the provided extension option does not implement the correct TxExtensionOptionI interface
txWithExt, err := decodeTx(bz)
s.Require().Error(err)
s.Require().ErrorContains(err, "failed to decode tx: no concrete type registered for type URL /cosmos.bank.v1beta1.MsgMultiSend against interface *tx.TxExtensionOptionI")
s.Require().Nil(txWithExt)
})
}
Loading
Loading