From 0e75423fc936de6047596a62b692c9f6dc7af1cc Mon Sep 17 00:00:00 2001 From: linchizhen Date: Fri, 31 May 2024 21:21:39 +0800 Subject: [PATCH 1/3] chore: fix some function names (#2297) Signed-off-by: linchizhen --- x/crosschain/keeper/refund.go | 2 +- x/lightclient/types/block_header_verification.go | 2 +- zetaclient/chains/bitcoin/observer/observer.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x/crosschain/keeper/refund.go b/x/crosschain/keeper/refund.go index e24a3f0158..19afd1be1f 100644 --- a/x/crosschain/keeper/refund.go +++ b/x/crosschain/keeper/refund.go @@ -60,7 +60,7 @@ func (k Keeper) RefundAmountOnZetaChainGas( return nil } -// RefundAmountOnZetaChainGas refunds the amount of the cctx on ZetaChain in case of aborted cctx with cointype zeta +// RefundAmountOnZetaChainZeta refunds the amount of the cctx on ZetaChain in case of aborted cctx with cointype zeta func (k Keeper) RefundAmountOnZetaChainZeta( ctx sdk.Context, cctx types.CrossChainTx, diff --git a/x/lightclient/types/block_header_verification.go b/x/lightclient/types/block_header_verification.go index 351a971d1b..20ae380006 100644 --- a/x/lightclient/types/block_header_verification.go +++ b/x/lightclient/types/block_header_verification.go @@ -61,7 +61,7 @@ func (b *BlockHeaderVerification) IsChainEnabled(chainID int64) bool { return false } -// GetHeaderSupportedChainIDList returns a list of chain IDs that have block header verification enabled +// GetHeaderEnabledChainIDs returns a list of chain IDs that have block header verification enabled func (b *BlockHeaderVerification) GetHeaderEnabledChainIDs() []int64 { var enabledChains []int64 for _, v := range b.HeaderSupportedChains { diff --git a/zetaclient/chains/bitcoin/observer/observer.go b/zetaclient/chains/bitcoin/observer/observer.go index 0a4a062aef..91f0d695f4 100644 --- a/zetaclient/chains/bitcoin/observer/observer.go +++ b/zetaclient/chains/bitcoin/observer/observer.go @@ -634,7 +634,7 @@ func GetTxResultByHash( return hash, txResult, nil } -// GetBlockHeightByTxHash gets the block height by block hash +// GetBlockHeightByHash gets the block height by block hash func GetBlockHeightByHash( rpcClient interfaces.BTCRPCClient, hash string, From 54de7616e76287a40c49f2895cd7c67f05838438 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Fri, 31 May 2024 12:27:12 -0400 Subject: [PATCH 2/3] feat: add authorization list for policy transactions (#2289) --- app/setup_handlers.go | 2 +- changelog.md | 1 + .../zetacore/authority/authorization.proto | 21 + .../zetacore/authority/genesis.proto | 4 +- proto/zetachain/zetacore/authority/tx.proto | 1 + testutil/sample/authority.go | 28 + .../zetacore/authority/authorization_pb.d.ts | 71 +++ .../zetacore/authority/genesis_pb.d.ts | 8 +- .../zetachain/zetacore/authority/index.d.ts | 1 + x/authority/genesis.go | 5 + x/authority/genesis_test.go | 62 +- x/authority/keeper/authorization_list.go | 26 + x/authority/keeper/authorization_list_test.go | 49 ++ x/authority/types/authorization.pb.go | 545 ++++++++++++++++++ x/authority/types/authorizations.go | 131 +++++ x/authority/types/authorizations_test.go | 439 ++++++++++++++ x/authority/types/errors.go | 6 +- x/authority/types/genesis.pb.go | 85 ++- x/authority/types/keys.go | 3 +- x/authority/types/tx.pb.go | 35 +- 20 files changed, 1459 insertions(+), 64 deletions(-) create mode 100644 proto/zetachain/zetacore/authority/authorization.proto create mode 100644 typescript/zetachain/zetacore/authority/authorization_pb.d.ts create mode 100644 x/authority/keeper/authorization_list.go create mode 100644 x/authority/keeper/authorization_list_test.go create mode 100644 x/authority/types/authorization.pb.go create mode 100644 x/authority/types/authorizations.go create mode 100644 x/authority/types/authorizations_test.go diff --git a/app/setup_handlers.go b/app/setup_handlers.go index 2ca71ca0ae..7fe7bc1f8f 100644 --- a/app/setup_handlers.go +++ b/app/setup_handlers.go @@ -129,7 +129,7 @@ func SetupHandlers(app *App) { app.UpgradeKeeper.SetUpgradeHandler( constant.Version, - func(ctx sdk.Context, plan types.Plan, vm module.VersionMap) (module.VersionMap, error) { + func(ctx sdk.Context, _ types.Plan, vm module.VersionMap) (module.VersionMap, error) { app.Logger().Info("Running upgrade handler for " + constant.Version) var err error diff --git a/changelog.md b/changelog.md index 442fcb68f8..4dda5f671e 100644 --- a/changelog.md +++ b/changelog.md @@ -19,6 +19,7 @@ * [2287](https://github.com/zeta-chain/node/pull/2287) - implement `MsgUpdateChainInfo` message * [2279](https://github.com/zeta-chain/node/pull/2279) - add a CCTXGateway field to chain static data * [2275](https://github.com/zeta-chain/node/pull/2275) - add ChainInfo singleton state variable in authority +* [2289](https://github.com/zeta-chain/node/pull/2289) - add an authorization list to keep track of all authorizations on the chain ### Refactor diff --git a/proto/zetachain/zetacore/authority/authorization.proto b/proto/zetachain/zetacore/authority/authorization.proto new file mode 100644 index 0000000000..914b6b7528 --- /dev/null +++ b/proto/zetachain/zetacore/authority/authorization.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; +package zetachain.zetacore.authority; + +import "gogoproto/gogo.proto"; +import "zetachain/zetacore/authority/policies.proto"; + +option go_package = "github.com/zeta-chain/zetacore/x/authority/types"; + +// Authorization defines the authorization required to access use a message +// which needs special permissions +message Authorization { + // The URL of the message that needs to be authorized + string msg_url = 1; + // The policy that is authorized to access the message + PolicyType authorized_policy = 2; +} + +// AuthorizationList holds the list of authorizations on zetachain +message AuthorizationList { + repeated Authorization authorizations = 1 [ (gogoproto.nullable) = false ]; +} \ No newline at end of file diff --git a/proto/zetachain/zetacore/authority/genesis.proto b/proto/zetachain/zetacore/authority/genesis.proto index a23416161f..4f7b238aca 100644 --- a/proto/zetachain/zetacore/authority/genesis.proto +++ b/proto/zetachain/zetacore/authority/genesis.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package zetachain.zetacore.authority; import "zetachain/zetacore/authority/policies.proto"; +import "zetachain/zetacore/authority/authorization.proto"; import "zetachain/zetacore/authority/chain_info.proto"; import "gogoproto/gogo.proto"; @@ -10,5 +11,6 @@ option go_package = "github.com/zeta-chain/zetacore/x/authority/types"; // GenesisState defines the authority module's genesis state. message GenesisState { Policies policies = 1 [ (gogoproto.nullable) = false ]; - ChainInfo chain_info = 2 [ (gogoproto.nullable) = false ]; + AuthorizationList authorization_list = 2 [ (gogoproto.nullable) = false ]; + ChainInfo chain_info = 3 [ (gogoproto.nullable) = false ]; } diff --git a/proto/zetachain/zetacore/authority/tx.proto b/proto/zetachain/zetacore/authority/tx.proto index cc4d22a6e6..55509e3172 100644 --- a/proto/zetachain/zetacore/authority/tx.proto +++ b/proto/zetachain/zetacore/authority/tx.proto @@ -3,6 +3,7 @@ package zetachain.zetacore.authority; import "zetachain/zetacore/authority/policies.proto"; import "zetachain/zetacore/authority/chain_info.proto"; +import "zetachain/zetacore/authority/authorization.proto"; import "gogoproto/gogo.proto"; option go_package = "github.com/zeta-chain/zetacore/x/authority/types"; diff --git a/testutil/sample/authority.go b/testutil/sample/authority.go index f24542c7fc..c7e3b7e6b8 100644 --- a/testutil/sample/authority.go +++ b/testutil/sample/authority.go @@ -1,6 +1,8 @@ package sample import ( + "fmt" + "github.com/zeta-chain/zetacore/pkg/chains" authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" ) @@ -37,3 +39,29 @@ func ChainInfo(startChainID int64) authoritytypes.ChainInfo { }, } } + +func AuthorizationList(val string) authoritytypes.AuthorizationList { + return authoritytypes.AuthorizationList{ + Authorizations: []authoritytypes.Authorization{ + { + MsgUrl: fmt.Sprintf("/zetachain/%d%s", 0, val), + AuthorizedPolicy: authoritytypes.PolicyType_groupEmergency, + }, + { + MsgUrl: fmt.Sprintf("/zetachain/%d%s", 1, val), + AuthorizedPolicy: authoritytypes.PolicyType_groupAdmin, + }, + { + MsgUrl: fmt.Sprintf("/zetachain/%d%s", 2, val), + AuthorizedPolicy: authoritytypes.PolicyType_groupOperational, + }, + }, + } +} + +func Authorization() authoritytypes.Authorization { + return authoritytypes.Authorization{ + MsgUrl: "ABC", + AuthorizedPolicy: authoritytypes.PolicyType_groupOperational, + } +} diff --git a/typescript/zetachain/zetacore/authority/authorization_pb.d.ts b/typescript/zetachain/zetacore/authority/authorization_pb.d.ts new file mode 100644 index 0000000000..96178864ea --- /dev/null +++ b/typescript/zetachain/zetacore/authority/authorization_pb.d.ts @@ -0,0 +1,71 @@ +// @generated by protoc-gen-es v1.3.0 with parameter "target=dts" +// @generated from file zetachain/zetacore/authority/authorization.proto (package zetachain.zetacore.authority, syntax proto3) +/* eslint-disable */ +// @ts-nocheck + +import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf"; +import { Message, proto3 } from "@bufbuild/protobuf"; +import type { PolicyType } from "./policies_pb.js"; + +/** + * Authorization defines the authorization required to access use a message + * which needs special permissions + * + * @generated from message zetachain.zetacore.authority.Authorization + */ +export declare class Authorization extends Message { + /** + * The URL of the message that needs to be authorized + * + * @generated from field: string msg_url = 1; + */ + msgUrl: string; + + /** + * The policy that is authorized to access the message + * + * @generated from field: zetachain.zetacore.authority.PolicyType authorized_policy = 2; + */ + authorizedPolicy: PolicyType; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto3; + static readonly typeName = "zetachain.zetacore.authority.Authorization"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): Authorization; + + static fromJson(jsonValue: JsonValue, options?: Partial): Authorization; + + static fromJsonString(jsonString: string, options?: Partial): Authorization; + + static equals(a: Authorization | PlainMessage | undefined, b: Authorization | PlainMessage | undefined): boolean; +} + +/** + * AuthorizationList holds the list of authorizations on zetachain + * + * @generated from message zetachain.zetacore.authority.AuthorizationList + */ +export declare class AuthorizationList extends Message { + /** + * @generated from field: repeated zetachain.zetacore.authority.Authorization authorizations = 1; + */ + authorizations: Authorization[]; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto3; + static readonly typeName = "zetachain.zetacore.authority.AuthorizationList"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): AuthorizationList; + + static fromJson(jsonValue: JsonValue, options?: Partial): AuthorizationList; + + static fromJsonString(jsonString: string, options?: Partial): AuthorizationList; + + static equals(a: AuthorizationList | PlainMessage | undefined, b: AuthorizationList | PlainMessage | undefined): boolean; +} + diff --git a/typescript/zetachain/zetacore/authority/genesis_pb.d.ts b/typescript/zetachain/zetacore/authority/genesis_pb.d.ts index 381048e226..1bd7878edb 100644 --- a/typescript/zetachain/zetacore/authority/genesis_pb.d.ts +++ b/typescript/zetachain/zetacore/authority/genesis_pb.d.ts @@ -6,6 +6,7 @@ import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf"; import { Message, proto3 } from "@bufbuild/protobuf"; import type { Policies } from "./policies_pb.js"; +import type { AuthorizationList } from "./authorization_pb.js"; import type { ChainInfo } from "./chain_info_pb.js"; /** @@ -20,7 +21,12 @@ export declare class GenesisState extends Message { policies?: Policies; /** - * @generated from field: zetachain.zetacore.authority.ChainInfo chain_info = 2; + * @generated from field: zetachain.zetacore.authority.AuthorizationList authorization_list = 2; + */ + authorizationList?: AuthorizationList; + + /** + * @generated from field: zetachain.zetacore.authority.ChainInfo chain_info = 3; */ chainInfo?: ChainInfo; diff --git a/typescript/zetachain/zetacore/authority/index.d.ts b/typescript/zetachain/zetacore/authority/index.d.ts index e27cc8abc4..6978a3b26c 100644 --- a/typescript/zetachain/zetacore/authority/index.d.ts +++ b/typescript/zetachain/zetacore/authority/index.d.ts @@ -1,3 +1,4 @@ +export * from "./authorization_pb"; export * from "./chain_info_pb"; export * from "./genesis_pb"; export * from "./policies_pb"; diff --git a/x/authority/genesis.go b/x/authority/genesis.go index 56528a5935..06dbaa7e57 100644 --- a/x/authority/genesis.go +++ b/x/authority/genesis.go @@ -11,6 +11,7 @@ import ( func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { k.SetPolicies(ctx, genState.Policies) k.SetChainInfo(ctx, genState.ChainInfo) + k.SetAuthorizationList(ctx, genState.AuthorizationList) } // ExportGenesis returns the authority module's exported genesis. @@ -21,6 +22,10 @@ func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { if found { genesis.Policies = policies } + authorizationList, found := k.GetAuthorizationList(ctx) + if found { + genesis.AuthorizationList = authorizationList + } chainInfo, found := k.GetChainInfo(ctx) if found { diff --git a/x/authority/genesis_test.go b/x/authority/genesis_test.go index f14f4f37c6..59176ae91e 100644 --- a/x/authority/genesis_test.go +++ b/x/authority/genesis_test.go @@ -13,31 +13,39 @@ import ( ) func TestGenesis(t *testing.T) { - genesisState := types.GenesisState{ - Policies: sample.Policies(), - ChainInfo: sample.ChainInfo(42), - } - - // Init - k, ctx := keepertest.AuthorityKeeper(t) - authority.InitGenesis(ctx, *k, genesisState) - - // Check policy is set - policies, found := k.GetPolicies(ctx) - require.True(t, found) - require.Equal(t, genesisState.Policies, policies) - - // Check chain info is set - chainInfo, found := k.GetChainInfo(ctx) - require.True(t, found) - require.Equal(t, genesisState.ChainInfo, chainInfo) - - // Export - got := authority.ExportGenesis(ctx, *k) - require.NotNil(t, got) - - // Compare genesis after init and export - nullify.Fill(&genesisState) - nullify.Fill(got) - require.Equal(t, genesisState, *got) + t.Run("valid genesis", func(t *testing.T) { + genesisState := types.GenesisState{ + Policies: sample.Policies(), + AuthorizationList: sample.AuthorizationList("sample"), + ChainInfo: sample.ChainInfo(42), + } + + // Init + k, ctx := keepertest.AuthorityKeeper(t) + authority.InitGenesis(ctx, *k, genesisState) + + // Check policy is set + policies, found := k.GetPolicies(ctx) + require.True(t, found) + require.Equal(t, genesisState.Policies, policies) + + // Check authorization list is set + authorizationList, found := k.GetAuthorizationList(ctx) + require.True(t, found) + require.Equal(t, genesisState.AuthorizationList, authorizationList) + + // Check chain info is set + chainInfo, found := k.GetChainInfo(ctx) + require.True(t, found) + require.Equal(t, genesisState.ChainInfo, chainInfo) + + // Export + got := authority.ExportGenesis(ctx, *k) + require.NotNil(t, got) + + // Compare genesis after init and export + nullify.Fill(&genesisState) + nullify.Fill(got) + require.Equal(t, genesisState, *got) + }) } diff --git a/x/authority/keeper/authorization_list.go b/x/authority/keeper/authorization_list.go new file mode 100644 index 0000000000..0e7fba9163 --- /dev/null +++ b/x/authority/keeper/authorization_list.go @@ -0,0 +1,26 @@ +package keeper + +import ( + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/zeta-chain/zetacore/x/authority/types" +) + +// SetAuthorizationList sets the authorization list to the store.It returns an error if the list is invalid. +func (k Keeper) SetAuthorizationList(ctx sdk.Context, list types.AuthorizationList) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.AuthorizationListKey)) + b := k.cdc.MustMarshal(&list) + store.Set([]byte{0}, b) +} + +// GetAuthorizationList returns the authorization list from the store +func (k Keeper) GetAuthorizationList(ctx sdk.Context) (val types.AuthorizationList, found bool) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.AuthorizationListKey)) + b := store.Get([]byte{0}) + if b == nil { + return val, false + } + k.cdc.MustUnmarshal(b, &val) + return val, true +} diff --git a/x/authority/keeper/authorization_list_test.go b/x/authority/keeper/authorization_list_test.go new file mode 100644 index 0000000000..008be61f25 --- /dev/null +++ b/x/authority/keeper/authorization_list_test.go @@ -0,0 +1,49 @@ +package keeper_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + keepertest "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/x/authority/types" +) + +func TestKeeper_GetAuthorizationList(t *testing.T) { + t.Run("successfully get authorizations list", func(t *testing.T) { + k, ctx := keepertest.AuthorityKeeper(t) + authorizationList := sample.AuthorizationList("sample") + k.SetAuthorizationList(ctx, authorizationList) + list, found := k.GetAuthorizationList(ctx) + require.True(t, found) + require.Equal(t, authorizationList, list) + }) + + t.Run("get authorizations list not found", func(t *testing.T) { + k, ctx := keepertest.AuthorityKeeper(t) + list, found := k.GetAuthorizationList(ctx) + require.False(t, found) + require.Equal(t, types.AuthorizationList{}, list) + }) +} + +func TestKeeper_SetAuthorizationList(t *testing.T) { + t.Run("successfully set authorizations list when a list already exists", func(t *testing.T) { + k, ctx := keepertest.AuthorityKeeper(t) + authorizationList := sample.AuthorizationList("sample") + k.SetAuthorizationList(ctx, authorizationList) + + list, found := k.GetAuthorizationList(ctx) + require.True(t, found) + require.Equal(t, authorizationList, list) + + newAuthorizationList := sample.AuthorizationList("sample2") + require.NotEqual(t, authorizationList, newAuthorizationList) + k.SetAuthorizationList(ctx, newAuthorizationList) + + list, found = k.GetAuthorizationList(ctx) + require.True(t, found) + require.Equal(t, newAuthorizationList, list) + }) +} diff --git a/x/authority/types/authorization.pb.go b/x/authority/types/authorization.pb.go new file mode 100644 index 0000000000..d8c5fa9292 --- /dev/null +++ b/x/authority/types/authorization.pb.go @@ -0,0 +1,545 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: zetachain/zetacore/authority/authorization.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Authorization defines the authorization required to access use a message +// which needs special permissions +type Authorization struct { + // The URL of the message that needs to be authorized + MsgUrl string `protobuf:"bytes,1,opt,name=msg_url,json=msgUrl,proto3" json:"msg_url,omitempty"` + // The policy that is authorized to access the message + AuthorizedPolicy PolicyType `protobuf:"varint,2,opt,name=authorized_policy,json=authorizedPolicy,proto3,enum=zetachain.zetacore.authority.PolicyType" json:"authorized_policy,omitempty"` +} + +func (m *Authorization) Reset() { *m = Authorization{} } +func (m *Authorization) String() string { return proto.CompactTextString(m) } +func (*Authorization) ProtoMessage() {} +func (*Authorization) Descriptor() ([]byte, []int) { + return fileDescriptor_b7303e09de7c755a, []int{0} +} +func (m *Authorization) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Authorization) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Authorization.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Authorization) XXX_Merge(src proto.Message) { + xxx_messageInfo_Authorization.Merge(m, src) +} +func (m *Authorization) XXX_Size() int { + return m.Size() +} +func (m *Authorization) XXX_DiscardUnknown() { + xxx_messageInfo_Authorization.DiscardUnknown(m) +} + +var xxx_messageInfo_Authorization proto.InternalMessageInfo + +func (m *Authorization) GetMsgUrl() string { + if m != nil { + return m.MsgUrl + } + return "" +} + +func (m *Authorization) GetAuthorizedPolicy() PolicyType { + if m != nil { + return m.AuthorizedPolicy + } + return PolicyType_groupEmergency +} + +// AuthorizationList holds the list of authorizations on zetachain +type AuthorizationList struct { + Authorizations []Authorization `protobuf:"bytes,1,rep,name=authorizations,proto3" json:"authorizations"` +} + +func (m *AuthorizationList) Reset() { *m = AuthorizationList{} } +func (m *AuthorizationList) String() string { return proto.CompactTextString(m) } +func (*AuthorizationList) ProtoMessage() {} +func (*AuthorizationList) Descriptor() ([]byte, []int) { + return fileDescriptor_b7303e09de7c755a, []int{1} +} +func (m *AuthorizationList) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *AuthorizationList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_AuthorizationList.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *AuthorizationList) XXX_Merge(src proto.Message) { + xxx_messageInfo_AuthorizationList.Merge(m, src) +} +func (m *AuthorizationList) XXX_Size() int { + return m.Size() +} +func (m *AuthorizationList) XXX_DiscardUnknown() { + xxx_messageInfo_AuthorizationList.DiscardUnknown(m) +} + +var xxx_messageInfo_AuthorizationList proto.InternalMessageInfo + +func (m *AuthorizationList) GetAuthorizations() []Authorization { + if m != nil { + return m.Authorizations + } + return nil +} + +func init() { + proto.RegisterType((*Authorization)(nil), "zetachain.zetacore.authority.Authorization") + proto.RegisterType((*AuthorizationList)(nil), "zetachain.zetacore.authority.AuthorizationList") +} + +func init() { + proto.RegisterFile("zetachain/zetacore/authority/authorization.proto", fileDescriptor_b7303e09de7c755a) +} + +var fileDescriptor_b7303e09de7c755a = []byte{ + // 271 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x32, 0xa8, 0x4a, 0x2d, 0x49, + 0x4c, 0xce, 0x48, 0xcc, 0xcc, 0xd3, 0x07, 0xb3, 0xf2, 0x8b, 0x52, 0xf5, 0x13, 0x4b, 0x4b, 0x32, + 0xf2, 0x8b, 0x32, 0x4b, 0x2a, 0x61, 0xac, 0xaa, 0xc4, 0x92, 0xcc, 0xfc, 0x3c, 0xbd, 0x82, 0xa2, + 0xfc, 0x92, 0x7c, 0x21, 0x19, 0xb8, 0x0e, 0x3d, 0x98, 0x0e, 0x3d, 0xb8, 0x0e, 0x29, 0x91, 0xf4, + 0xfc, 0xf4, 0x7c, 0xb0, 0x42, 0x7d, 0x10, 0x0b, 0xa2, 0x47, 0x4a, 0x1b, 0xaf, 0x2d, 0x05, 0xf9, + 0x39, 0x99, 0xc9, 0x99, 0xa9, 0xc5, 0x10, 0xc5, 0x4a, 0xf5, 0x5c, 0xbc, 0x8e, 0xc8, 0xf6, 0x0a, + 0x89, 0x73, 0xb1, 0xe7, 0x16, 0xa7, 0xc7, 0x97, 0x16, 0xe5, 0x48, 0x30, 0x2a, 0x30, 0x6a, 0x70, + 0x06, 0xb1, 0xe5, 0x16, 0xa7, 0x87, 0x16, 0xe5, 0x08, 0x85, 0x72, 0x09, 0xc2, 0x5c, 0x98, 0x9a, + 0x12, 0x0f, 0x36, 0xa6, 0x52, 0x82, 0x49, 0x81, 0x51, 0x83, 0xcf, 0x48, 0x43, 0x0f, 0x9f, 0x33, + 0xf5, 0x02, 0xc0, 0x6a, 0x43, 0x2a, 0x0b, 0x52, 0x83, 0x04, 0x10, 0x46, 0x40, 0x44, 0x95, 0xf2, + 0xb8, 0x04, 0x51, 0x1c, 0xe0, 0x93, 0x59, 0x5c, 0x22, 0x14, 0xc9, 0xc5, 0x87, 0x12, 0x1a, 0xc5, + 0x12, 0x8c, 0x0a, 0xcc, 0x1a, 0xdc, 0x46, 0xda, 0xf8, 0x2d, 0x42, 0x31, 0xc8, 0x89, 0xe5, 0xc4, + 0x3d, 0x79, 0x86, 0x20, 0x34, 0x83, 0x9c, 0xbc, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, + 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, + 0x8e, 0x21, 0xca, 0x20, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x17, 0x1c, 0x70, + 0xba, 0x68, 0x61, 0x58, 0x81, 0x14, 0x8a, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x49, 0x6c, 0xe0, 0x30, + 0x34, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x88, 0x76, 0x6b, 0x6c, 0xd8, 0x01, 0x00, 0x00, +} + +func (m *Authorization) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Authorization) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Authorization) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.AuthorizedPolicy != 0 { + i = encodeVarintAuthorization(dAtA, i, uint64(m.AuthorizedPolicy)) + i-- + dAtA[i] = 0x10 + } + if len(m.MsgUrl) > 0 { + i -= len(m.MsgUrl) + copy(dAtA[i:], m.MsgUrl) + i = encodeVarintAuthorization(dAtA, i, uint64(len(m.MsgUrl))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *AuthorizationList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AuthorizationList) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AuthorizationList) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Authorizations) > 0 { + for iNdEx := len(m.Authorizations) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Authorizations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintAuthorization(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintAuthorization(dAtA []byte, offset int, v uint64) int { + offset -= sovAuthorization(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Authorization) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.MsgUrl) + if l > 0 { + n += 1 + l + sovAuthorization(uint64(l)) + } + if m.AuthorizedPolicy != 0 { + n += 1 + sovAuthorization(uint64(m.AuthorizedPolicy)) + } + return n +} + +func (m *AuthorizationList) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Authorizations) > 0 { + for _, e := range m.Authorizations { + l = e.Size() + n += 1 + l + sovAuthorization(uint64(l)) + } + } + return n +} + +func sovAuthorization(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozAuthorization(x uint64) (n int) { + return sovAuthorization(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Authorization) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Authorization: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Authorization: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MsgUrl", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAuthorization + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAuthorization + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.MsgUrl = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AuthorizedPolicy", wireType) + } + m.AuthorizedPolicy = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.AuthorizedPolicy |= PolicyType(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipAuthorization(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAuthorization + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AuthorizationList) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AuthorizationList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AuthorizationList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Authorizations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAuthorization + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthAuthorization + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Authorizations = append(m.Authorizations, Authorization{}) + if err := m.Authorizations[len(m.Authorizations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipAuthorization(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAuthorization + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipAuthorization(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAuthorization + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAuthorization + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAuthorization + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthAuthorization + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupAuthorization + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthAuthorization + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthAuthorization = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowAuthorization = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupAuthorization = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/authority/types/authorizations.go b/x/authority/types/authorizations.go new file mode 100644 index 0000000000..0de98a768e --- /dev/null +++ b/x/authority/types/authorizations.go @@ -0,0 +1,131 @@ +package types + +import ( + "fmt" + + "cosmossdk.io/errors" +) + +var ( + // OperationPolicyMessages keeps track of the message URLs that can, by default, only be executed by operational policy address + OperationPolicyMessages = []string{ + "/zetachain.zetacore.crosschain.MsgRefundAbortedCCTX", + "/zetachain.zetacore.crosschain.MsgAbortStuckCCTX", + "/zetachain.zetacore.crosschain.MsgUpdateRateLimiterFlags", + "/zetachain.zetacore.crosschain.MsgWhitelistERC20", + "/zetachain.zetacore.fungible.MsgDeployFungibleCoinZRC20", + "/zetachain.zetacore.fungible.MsgDeploySystemContracts", + "/zetachain.zetacore.fungible.MsgRemoveForeignCoin", + "/zetachain.zetacore.fungible.MsgUpdateZRC20LiquidityCap", + "/zetachain.zetacore.fungible.MsgUpdateZRC20WithdrawFee", + "/zetachain.zetacore.fungible.MsgUnpauseZRC20", + "/zetachain.zetacore.observer.MsgAddObserver", + "/zetachain.zetacore.observer.MsgRemoveChainParams", + "/zetachain.zetacore.observer.MsgResetChainNonces", + "/zetachain.zetacore.observer.MsgUpdateChainParams", + "/zetachain.zetacore.observer.MsgEnableCCTX", + "/zetachain.zetacore.observer.MsgUpdateGasPriceIncreaseFlags", + "/zetachain.zetacore.lightclient.MsgEnableHeaderVerification", + } + // AdminPolicyMessages keeps track of the message URLs that can, by default, only be executed by admin policy address + AdminPolicyMessages = []string{ + "/zetachain.zetacore.crosschain.MsgMigrateTssFunds", + "/zetachain.zetacore.crosschain.MsgUpdateTssAddress", + "/zetachain.zetacore.fungible.MsgUpdateContractBytecode", + "/zetachain.zetacore.fungible.MsgUpdateSystemContract", + "/zetachain.zetacore.observer.MsgUpdateObserver", + } + // EmergencyPolicyMessages keeps track of the message URLs that can, by default, only be executed by emergency policy address + EmergencyPolicyMessages = []string{ + "/zetachain.zetacore.crosschain.MsgAddInboundTracker", + "/zetachain.zetacore.crosschain.MsgAddOutboundTracker", + "/zetachain.zetacore.crosschain.MsgRemoveOutboundTracker", + "/zetachain.zetacore.fungible.MsgPauseZRC20", + "/zetachain.zetacore.observer.MsgUpdateKeygen", + "/zetachain.zetacore.observer.MsgDisableCCTX", + "/zetachain.zetacore.lightclient.MsgDisableHeaderVerification", + } +) + +// DefaultAuthorizationsList list is the list of authorizations that presently exist in the system. +// This is the minimum set of authorizations that are required to be set when the authorization table is deployed +func DefaultAuthorizationsList() AuthorizationList { + authorizations := make( + []Authorization, + len(OperationPolicyMessages)+len(AdminPolicyMessages)+len(EmergencyPolicyMessages), + ) + index := 0 + for _, msgURL := range OperationPolicyMessages { + authorizations[index] = Authorization{ + MsgUrl: msgURL, + AuthorizedPolicy: PolicyType_groupOperational, + } + index++ + } + for _, msgURL := range AdminPolicyMessages { + authorizations[index] = Authorization{ + MsgUrl: msgURL, + AuthorizedPolicy: PolicyType_groupAdmin, + } + index++ + } + for _, msgURL := range EmergencyPolicyMessages { + authorizations[index] = Authorization{ + MsgUrl: msgURL, + AuthorizedPolicy: PolicyType_groupEmergency, + } + index++ + } + + return AuthorizationList{ + Authorizations: authorizations, + } +} + +// SetAuthorization adds the authorization to the list. If the authorization already exists, it updates the policy. +func (a *AuthorizationList) SetAuthorization(authorization Authorization) { + for i, auth := range a.Authorizations { + if auth.MsgUrl == authorization.MsgUrl { + a.Authorizations[i].AuthorizedPolicy = authorization.AuthorizedPolicy + return + } + } + a.Authorizations = append(a.Authorizations, authorization) +} + +// RemoveAuthorization removes the authorization from the list. It does not check if the authorization exists or not. +func (a *AuthorizationList) RemoveAuthorization(authorization Authorization) { + for i, auth := range a.Authorizations { + if auth.MsgUrl == authorization.MsgUrl { + a.Authorizations = append(a.Authorizations[:i], a.Authorizations[i+1:]...) + } + } +} + +// GetAuthorizedPolicy returns the policy for the given message url. If the message url is not found, + +func (a *AuthorizationList) GetAuthorizedPolicy(msgURL string) (PolicyType, error) { + for _, auth := range a.Authorizations { + if auth.MsgUrl == msgURL { + return auth.AuthorizedPolicy, nil + } + } + // Returning first value of enum, can consider adding a default value of `EmptyPolicy` in the enum. + return PolicyType(0), ErrAuthorizationNotFound +} + +// Validate checks if the authorization list is valid. It returns an error if the message url is duplicated with different policies. +// It does not check if the list is empty or not, as an empty list is also considered valid. +func (a *AuthorizationList) Validate() error { + checkMsgUrls := make(map[string]bool) + for _, authorization := range a.Authorizations { + if checkMsgUrls[authorization.MsgUrl] { + return errors.Wrap( + ErrInvalidAuthorizationList, + fmt.Sprintf("duplicate message url: %s", authorization.MsgUrl), + ) + } + checkMsgUrls[authorization.MsgUrl] = true + } + return nil +} diff --git a/x/authority/types/authorizations_test.go b/x/authority/types/authorizations_test.go new file mode 100644 index 0000000000..8bb7ed805a --- /dev/null +++ b/x/authority/types/authorizations_test.go @@ -0,0 +1,439 @@ +package types_test + +import ( + "fmt" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/pkg/errors" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/zetacore/x/authority/types" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" + fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" + lightclienttypes "github.com/zeta-chain/zetacore/x/lightclient/types" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" +) + +func TestAuthorizationList_SetAuthorizations(t *testing.T) { + tt := []struct { + name string + oldList types.AuthorizationList + addAuthorization types.Authorization + expectedList types.AuthorizationList + }{ + { + name: "set new authorization successfully", + oldList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + addAuthorization: types.Authorization{ + MsgUrl: "XYZ", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + expectedList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + { + MsgUrl: "XYZ", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + }, + { + name: "set new authorization successfully with empty list", + oldList: types.AuthorizationList{Authorizations: []types.Authorization{}}, + addAuthorization: types.Authorization{ + MsgUrl: "XYZ", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + expectedList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "XYZ", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + }, + { + name: "update existing authorization successfully", + oldList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + addAuthorization: types.Authorization{ + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupEmergency, + }, + expectedList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupEmergency, + }, + }}, + }, + { + name: "update existing authorization successfully in the middle of the list", + oldList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + { + MsgUrl: "XYZ", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + { + MsgUrl: "DEF", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + addAuthorization: types.Authorization{ + MsgUrl: "XYZ", + AuthorizedPolicy: types.PolicyType_groupEmergency, + }, + expectedList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + { + MsgUrl: "XYZ", + AuthorizedPolicy: types.PolicyType_groupEmergency, + }, + { + MsgUrl: "DEF", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + tc.oldList.SetAuthorization(tc.addAuthorization) + require.Equal(t, tc.expectedList, tc.oldList) + }) + } +} + +func TestAuthorizationList_GetAuthorizations(t *testing.T) { + tt := []struct { + name string + authorizations types.AuthorizationList + getPolicyMsgUrl string + expectedPolicy types.PolicyType + error error + }{ + { + name: "get authorizations successfully", + authorizations: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + getPolicyMsgUrl: "ABC", + expectedPolicy: types.PolicyType_groupOperational, + error: nil, + }, + { + name: "get authorizations successfully for admin policy", + authorizations: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupAdmin, + }, + }}, + getPolicyMsgUrl: "ABC", + expectedPolicy: types.PolicyType_groupAdmin, + error: nil, + }, + { + name: "get authorizations successfully for emergency policy", + authorizations: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupEmergency, + }, + }}, + getPolicyMsgUrl: "ABC", + expectedPolicy: types.PolicyType_groupEmergency, + error: nil, + }, + { + name: "get authorizations fails when msg not found in list", + authorizations: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + getPolicyMsgUrl: "XYZ", + expectedPolicy: types.PolicyType(0), + error: types.ErrAuthorizationNotFound, + }, + { + name: "get authorizations fails when msg not found in list", + authorizations: types.AuthorizationList{Authorizations: []types.Authorization{}}, + getPolicyMsgUrl: "ABC", + expectedPolicy: types.PolicyType(0), + error: types.ErrAuthorizationNotFound, + }, + { + name: "get authorizations fails when when queried for empty string", + authorizations: types.AuthorizationList{Authorizations: []types.Authorization{}}, + getPolicyMsgUrl: "", + expectedPolicy: types.PolicyType(0), + error: types.ErrAuthorizationNotFound, + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + policy, err := tc.authorizations.GetAuthorizedPolicy(tc.getPolicyMsgUrl) + require.ErrorIs(t, err, tc.error) + require.Equal(t, tc.expectedPolicy, policy) + }) + } +} + +func TestAuthorizationList_Validate(t *testing.T) { + tt := []struct { + name string + authorizations types.AuthorizationList + expectedError error + }{ + { + name: "validate successfully", + authorizations: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + { + MsgUrl: "XYZ", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + expectedError: nil, + }, + { + name: "validate successfully with empty list", + authorizations: types.AuthorizationList{Authorizations: []types.Authorization{}}, + expectedError: nil, + }, + { + name: "validate successfully for default list", + authorizations: types.DefaultAuthorizationsList(), + expectedError: nil, + }, + { + name: "validate failed with duplicate msg url with different policies", + authorizations: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupEmergency, + }, + }}, + expectedError: errors.Wrap( + types.ErrInvalidAuthorizationList, + fmt.Sprintf("duplicate message url: %s", "ABC")), + }, + { + name: "validate failed with duplicate msg url with same policies", + authorizations: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + expectedError: errors.Wrap( + types.ErrInvalidAuthorizationList, + fmt.Sprintf("duplicate message url: %s", "ABC")), + }} + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + err := tc.authorizations.Validate() + require.ErrorIs(t, err, tc.expectedError) + }) + } +} + +func TestAuthorizationList_RemoveAuthorizations(t *testing.T) { + tt := []struct { + name string + oldList types.AuthorizationList + removeAuthorization types.Authorization + expectedList types.AuthorizationList + }{ + { + name: "remove authorization successfully", + oldList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + { + MsgUrl: "XYZ", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + removeAuthorization: types.Authorization{ + MsgUrl: "ABC", + }, + expectedList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "XYZ", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + }, + { + name: "remove authorization successfully in the middle of the list", + oldList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + { + MsgUrl: "XYZ", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + { + MsgUrl: "DEF", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + removeAuthorization: types.Authorization{ + MsgUrl: "XYZ", + }, + expectedList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + { + MsgUrl: "DEF", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + }, + { + name: "do not remove anything when trying to remove from an empty list", + oldList: types.AuthorizationList{Authorizations: []types.Authorization{}}, + removeAuthorization: types.Authorization{ + MsgUrl: "XYZ", + }, + expectedList: types.AuthorizationList{Authorizations: []types.Authorization{}}, + }, + { + name: "do not remove anything if authorization not found", + oldList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + removeAuthorization: types.Authorization{ + MsgUrl: "XYZ", + }, + expectedList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + tc.oldList.RemoveAuthorization(tc.removeAuthorization) + require.Equal(t, tc.expectedList, tc.oldList) + }) + } +} + +func TestDefaultAuthorizationsList(t *testing.T) { + t.Run("default authorizations list", func(t *testing.T) { + var OperationalPolicyMessageList = []string{ + sdk.MsgTypeURL(&crosschaintypes.MsgRefundAbortedCCTX{}), + sdk.MsgTypeURL(&crosschaintypes.MsgAbortStuckCCTX{}), + sdk.MsgTypeURL(&crosschaintypes.MsgUpdateRateLimiterFlags{}), + sdk.MsgTypeURL(&crosschaintypes.MsgWhitelistERC20{}), + sdk.MsgTypeURL(&fungibletypes.MsgDeployFungibleCoinZRC20{}), + sdk.MsgTypeURL(&fungibletypes.MsgDeploySystemContracts{}), + sdk.MsgTypeURL(&fungibletypes.MsgRemoveForeignCoin{}), + sdk.MsgTypeURL(&fungibletypes.MsgUpdateZRC20LiquidityCap{}), + sdk.MsgTypeURL(&fungibletypes.MsgUpdateZRC20WithdrawFee{}), + sdk.MsgTypeURL(&fungibletypes.MsgUnpauseZRC20{}), + sdk.MsgTypeURL(&observertypes.MsgAddObserver{}), + sdk.MsgTypeURL(&observertypes.MsgRemoveChainParams{}), + sdk.MsgTypeURL(&observertypes.MsgResetChainNonces{}), + sdk.MsgTypeURL(&observertypes.MsgUpdateChainParams{}), + sdk.MsgTypeURL(&observertypes.MsgEnableCCTX{}), + sdk.MsgTypeURL(&observertypes.MsgUpdateGasPriceIncreaseFlags{}), + sdk.MsgTypeURL(&lightclienttypes.MsgEnableHeaderVerification{}), + } + + // EmergencyPolicyMessageList is a list of messages that can be authorized by the emergency policy + var EmergencyPolicyMessageList = []string{ + sdk.MsgTypeURL(&crosschaintypes.MsgAddInboundTracker{}), + sdk.MsgTypeURL(&crosschaintypes.MsgAddOutboundTracker{}), + sdk.MsgTypeURL(&crosschaintypes.MsgRemoveOutboundTracker{}), + sdk.MsgTypeURL(&fungibletypes.MsgPauseZRC20{}), + sdk.MsgTypeURL(&observertypes.MsgUpdateKeygen{}), + sdk.MsgTypeURL(&observertypes.MsgDisableCCTX{}), + sdk.MsgTypeURL(&lightclienttypes.MsgDisableHeaderVerification{}), + } + + // AdminPolicyMessageList is a list of messages that can be authorized by the admin policy + var AdminPolicyMessageList = []string{ + sdk.MsgTypeURL(&crosschaintypes.MsgMigrateTssFunds{}), + sdk.MsgTypeURL(&crosschaintypes.MsgUpdateTssAddress{}), + sdk.MsgTypeURL(&fungibletypes.MsgUpdateContractBytecode{}), + sdk.MsgTypeURL(&fungibletypes.MsgUpdateSystemContract{}), + sdk.MsgTypeURL(&observertypes.MsgUpdateObserver{}), + } + defaultList := types.DefaultAuthorizationsList() + for _, msgUrl := range OperationalPolicyMessageList { + _, err := defaultList.GetAuthorizedPolicy(msgUrl) + require.NoError(t, err) + policy, err := defaultList.GetAuthorizedPolicy(msgUrl) + require.NoError(t, err) + require.Equal(t, types.PolicyType_groupOperational, policy) + } + for _, msgUrl := range EmergencyPolicyMessageList { + _, err := defaultList.GetAuthorizedPolicy(msgUrl) + require.NoError(t, err) + policy, err := defaultList.GetAuthorizedPolicy(msgUrl) + require.NoError(t, err) + require.Equal(t, types.PolicyType_groupEmergency, policy) + } + for _, msgUrl := range AdminPolicyMessageList { + _, err := defaultList.GetAuthorizedPolicy(msgUrl) + require.NoError(t, err) + policy, err := defaultList.GetAuthorizedPolicy(msgUrl) + require.NoError(t, err) + require.Equal(t, types.PolicyType_groupAdmin, policy) + } + require.Len( + t, + defaultList.Authorizations, + len(OperationalPolicyMessageList)+len(EmergencyPolicyMessageList)+len(AdminPolicyMessageList), + ) + }) +} diff --git a/x/authority/types/errors.go b/x/authority/types/errors.go index 6df6aa26a5..88b44b9887 100644 --- a/x/authority/types/errors.go +++ b/x/authority/types/errors.go @@ -2,4 +2,8 @@ package types import errorsmod "cosmossdk.io/errors" -var ErrUnauthorized = errorsmod.Register(ModuleName, 1102, "sender not authorized") +var ( + ErrUnauthorized = errorsmod.Register(ModuleName, 1102, "sender not authorized") + ErrInvalidAuthorizationList = errorsmod.Register(ModuleName, 1103, "invalid authorization list") + ErrAuthorizationNotFound = errorsmod.Register(ModuleName, 1104, "authorization not found") +) diff --git a/x/authority/types/genesis.pb.go b/x/authority/types/genesis.pb.go index aca80bfed4..c4a9f015bc 100644 --- a/x/authority/types/genesis.pb.go +++ b/x/authority/types/genesis.pb.go @@ -25,8 +25,9 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // GenesisState defines the authority module's genesis state. type GenesisState struct { - Policies Policies `protobuf:"bytes,1,opt,name=policies,proto3" json:"policies"` - ChainInfo ChainInfo `protobuf:"bytes,2,opt,name=chain_info,json=chainInfo,proto3" json:"chain_info"` + Policies Policies `protobuf:"bytes,1,opt,name=policies,proto3" json:"policies"` + AuthorizationList AuthorizationList `protobuf:"bytes,2,opt,name=authorization_list,json=authorizationList,proto3" json:"authorization_list"` + ChainInfo ChainInfo `protobuf:"bytes,3,opt,name=chain_info,json=chainInfo,proto3" json:"chain_info"` } func (m *GenesisState) Reset() { *m = GenesisState{} } @@ -69,6 +70,13 @@ func (m *GenesisState) GetPolicies() Policies { return Policies{} } +func (m *GenesisState) GetAuthorizationList() AuthorizationList { + if m != nil { + return m.AuthorizationList + } + return AuthorizationList{} +} + func (m *GenesisState) GetChainInfo() ChainInfo { if m != nil { return m.ChainInfo @@ -85,23 +93,25 @@ func init() { } var fileDescriptor_633475075491b169 = []byte{ - // 242 bytes of a gzipped FileDescriptorProto + // 287 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0xaa, 0x4a, 0x2d, 0x49, 0x4c, 0xce, 0x48, 0xcc, 0xcc, 0xd3, 0x07, 0xb3, 0xf2, 0x8b, 0x52, 0xf5, 0x13, 0x4b, 0x4b, 0x32, 0xf2, 0x8b, 0x32, 0x4b, 0x2a, 0xf5, 0xd3, 0x53, 0xf3, 0x52, 0x8b, 0x33, 0x8b, 0xf5, 0x0a, 0x8a, 0xf2, 0x4b, 0xf2, 0x85, 0x64, 0xe0, 0x6a, 0xf5, 0x60, 0x6a, 0xf5, 0xe0, 0x6a, 0xa5, 0xb4, 0xf1, - 0x9a, 0x54, 0x90, 0x9f, 0x93, 0x99, 0x9c, 0x99, 0x0a, 0x35, 0x4a, 0x4a, 0x17, 0xaf, 0x62, 0xb0, - 0x44, 0x7c, 0x66, 0x5e, 0x5a, 0x3e, 0x54, 0xb9, 0x48, 0x7a, 0x7e, 0x7a, 0x3e, 0x98, 0xa9, 0x0f, - 0x62, 0x41, 0x44, 0x95, 0x96, 0x31, 0x72, 0xf1, 0xb8, 0x43, 0x5c, 0x18, 0x5c, 0x92, 0x58, 0x92, - 0x2a, 0xe4, 0xc1, 0xc5, 0x01, 0xb3, 0x47, 0x82, 0x51, 0x81, 0x51, 0x83, 0xdb, 0x48, 0x4d, 0x0f, - 0x9f, 0x9b, 0xf5, 0x02, 0xa0, 0xaa, 0x9d, 0x58, 0x4e, 0xdc, 0x93, 0x67, 0x08, 0x82, 0xeb, 0x16, - 0xf2, 0xe1, 0xe2, 0x42, 0x38, 0x42, 0x82, 0x09, 0x6c, 0x96, 0x3a, 0x7e, 0xb3, 0x9c, 0x41, 0x12, - 0x9e, 0x79, 0x69, 0xf9, 0x50, 0xc3, 0x38, 0x93, 0xe1, 0x02, 0x5e, 0x27, 0x1e, 0xc9, 0x31, 0x5e, - 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, - 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0x65, 0x90, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, - 0x0b, 0x0e, 0x08, 0x5d, 0xb4, 0x30, 0xa9, 0x40, 0x0a, 0x95, 0x92, 0xca, 0x82, 0xd4, 0xe2, 0x24, - 0x36, 0xb0, 0xdf, 0x8d, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0xaf, 0x5f, 0x4d, 0x13, 0xb9, 0x01, - 0x00, 0x00, + 0x9a, 0x54, 0x90, 0x9f, 0x93, 0x99, 0x9c, 0x99, 0x0a, 0x35, 0x4a, 0xca, 0x00, 0xaf, 0x62, 0x28, + 0xab, 0x2a, 0xb1, 0x24, 0x33, 0x3f, 0x0f, 0xaa, 0x43, 0x17, 0xaf, 0x0e, 0xb0, 0x44, 0x7c, 0x66, + 0x5e, 0x5a, 0x3e, 0x54, 0xb9, 0x48, 0x7a, 0x7e, 0x7a, 0x3e, 0x98, 0xa9, 0x0f, 0x62, 0x41, 0x44, + 0x95, 0x7a, 0x98, 0xb8, 0x78, 0xdc, 0x21, 0x7e, 0x0a, 0x2e, 0x49, 0x2c, 0x49, 0x15, 0xf2, 0xe0, + 0xe2, 0x80, 0xb9, 0x4c, 0x82, 0x51, 0x81, 0x51, 0x83, 0xdb, 0x48, 0x4d, 0x0f, 0x9f, 0x2f, 0xf5, + 0x02, 0xa0, 0xaa, 0x9d, 0x58, 0x4e, 0xdc, 0x93, 0x67, 0x08, 0x82, 0xeb, 0x16, 0x4a, 0xe1, 0x12, + 0x42, 0x71, 0x76, 0x7c, 0x4e, 0x66, 0x71, 0x89, 0x04, 0x13, 0xd8, 0x4c, 0x7d, 0xfc, 0x66, 0x3a, + 0x22, 0xeb, 0xf3, 0xc9, 0x2c, 0x2e, 0x81, 0x1a, 0x2e, 0x98, 0x88, 0x2e, 0x21, 0xe4, 0xc3, 0xc5, + 0x85, 0xf0, 0xaa, 0x04, 0x33, 0xd8, 0x74, 0x75, 0xfc, 0xa6, 0x3b, 0x83, 0x24, 0x3c, 0xf3, 0xd2, + 0xf2, 0xa1, 0xa6, 0x72, 0x26, 0xc3, 0x05, 0xbc, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, + 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, + 0x8e, 0x21, 0xca, 0x20, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x17, 0x1c, 0xdc, + 0xba, 0x68, 0x21, 0x5f, 0x81, 0x14, 0xf6, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x49, 0x6c, 0xe0, 0x10, + 0x36, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x7e, 0x66, 0xd6, 0x94, 0x51, 0x02, 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -133,6 +143,16 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintGenesis(dAtA, i, uint64(size)) } i-- + dAtA[i] = 0x1a + { + size, err := m.AuthorizationList.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- dAtA[i] = 0x12 { size, err := m.Policies.MarshalToSizedBuffer(dAtA[:i]) @@ -166,6 +186,8 @@ func (m *GenesisState) Size() (n int) { _ = l l = m.Policies.Size() n += 1 + l + sovGenesis(uint64(l)) + l = m.AuthorizationList.Size() + n += 1 + l + sovGenesis(uint64(l)) l = m.ChainInfo.Size() n += 1 + l + sovGenesis(uint64(l)) return n @@ -240,6 +262,39 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error { } iNdEx = postIndex case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AuthorizationList", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.AuthorizationList.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ChainInfo", wireType) } diff --git a/x/authority/types/keys.go b/x/authority/types/keys.go index 7593feed3a..13365abf77 100644 --- a/x/authority/types/keys.go +++ b/x/authority/types/keys.go @@ -25,7 +25,8 @@ func KeyPrefix(p string) []byte { const ( // PoliciesKey is the key for the policies store PoliciesKey = "Policies-value-" - + // AuthorizationListKey is the key for the authorization list store + AuthorizationListKey = "AuthorizationList-value-" // ChainInfoKey is the key for the chain info store ChainInfoKey = "ChainInfo-value-" ) diff --git a/x/authority/types/tx.pb.go b/x/authority/types/tx.pb.go index 4dd5469876..ac5c04416b 100644 --- a/x/authority/types/tx.pb.go +++ b/x/authority/types/tx.pb.go @@ -220,28 +220,29 @@ func init() { } var fileDescriptor_42e081863c477116 = []byte{ - // 336 bytes of a gzipped FileDescriptorProto + // 350 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xad, 0x4a, 0x2d, 0x49, 0x4c, 0xce, 0x48, 0xcc, 0xcc, 0xd3, 0x07, 0xb3, 0xf2, 0x8b, 0x52, 0xf5, 0x13, 0x4b, 0x4b, 0x32, 0xf2, 0x8b, 0x32, 0x4b, 0x2a, 0xf5, 0x4b, 0x2a, 0xf4, 0x0a, 0x8a, 0xf2, 0x4b, 0xf2, 0x85, 0x64, 0xe0, 0xca, 0xf4, 0x60, 0xca, 0xf4, 0xe0, 0xca, 0xa4, 0xb4, 0xf1, 0x1a, 0x52, 0x90, 0x9f, 0x93, 0x99, 0x9c, 0x99, 0x5a, 0x0c, 0x31, 0x4a, 0x4a, 0x17, 0xaf, 0x62, 0xb0, 0x44, 0x7c, 0x66, 0x5e, - 0x5a, 0x3e, 0x54, 0xb9, 0x48, 0x7a, 0x7e, 0x7a, 0x3e, 0x98, 0xa9, 0x0f, 0x62, 0x41, 0x44, 0x95, - 0xca, 0xb9, 0x04, 0x7d, 0x8b, 0xd3, 0x43, 0x0b, 0x52, 0x12, 0x4b, 0x52, 0x03, 0xa0, 0xe6, 0x0b, - 0x49, 0x70, 0xb1, 0x27, 0x17, 0xa5, 0x26, 0x96, 0xe4, 0x17, 0x49, 0x30, 0x2a, 0x30, 0x6a, 0x70, - 0x06, 0xc1, 0xb8, 0x42, 0x1e, 0x5c, 0x1c, 0x30, 0x57, 0x48, 0x30, 0x29, 0x30, 0x6a, 0x70, 0x1b, - 0xa9, 0xe9, 0xe1, 0xf3, 0x91, 0x1e, 0xcc, 0x4c, 0x27, 0x96, 0x13, 0xf7, 0xe4, 0x19, 0x82, 0xe0, - 0xba, 0x95, 0xa4, 0xb9, 0x24, 0x31, 0x2c, 0x0e, 0x4a, 0x2d, 0x2e, 0xc8, 0xcf, 0x2b, 0x4e, 0x55, - 0xaa, 0xe1, 0x12, 0x82, 0x4b, 0x3a, 0x83, 0x8c, 0xf6, 0xcc, 0x4b, 0xcb, 0xc7, 0xe3, 0x2c, 0x1f, - 0x2e, 0x2e, 0x84, 0x7f, 0xa1, 0x0e, 0x53, 0xc7, 0xef, 0x30, 0xb8, 0xb1, 0x50, 0x97, 0x71, 0x26, - 0xc3, 0x04, 0x94, 0x64, 0xb8, 0xa4, 0x30, 0x6d, 0x87, 0xb9, 0xcd, 0xa8, 0x81, 0x89, 0x8b, 0xd9, - 0xb7, 0x38, 0x5d, 0xa8, 0x8a, 0x8b, 0x0f, 0x2d, 0xd8, 0xf4, 0xf1, 0xdb, 0x88, 0xe1, 0x5d, 0x29, - 0x73, 0x12, 0x35, 0xc0, 0xdc, 0x20, 0x54, 0xcb, 0xc5, 0x8f, 0x1e, 0x38, 0x06, 0x44, 0x9a, 0x05, - 0xd7, 0x21, 0x65, 0x41, 0xaa, 0x0e, 0x98, 0xf5, 0x4e, 0x5e, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, - 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, 0xdc, - 0x78, 0x2c, 0xc7, 0x10, 0x65, 0x90, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0x0b, - 0x4e, 0x94, 0xba, 0x68, 0xe9, 0xb3, 0x02, 0x39, 0x4f, 0x54, 0x16, 0xa4, 0x16, 0x27, 0xb1, 0x81, - 0xd3, 0xa1, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x42, 0xb9, 0x95, 0xf6, 0x40, 0x03, 0x00, 0x00, + 0x5a, 0x3e, 0x54, 0xb9, 0x01, 0x5e, 0xe5, 0x50, 0x56, 0x55, 0x62, 0x49, 0x66, 0x7e, 0x1e, 0x54, + 0x87, 0x48, 0x7a, 0x7e, 0x7a, 0x3e, 0x98, 0xa9, 0x0f, 0x62, 0x41, 0x44, 0x95, 0xca, 0xb9, 0x04, + 0x7d, 0x8b, 0xd3, 0x43, 0x0b, 0x52, 0x12, 0x4b, 0x52, 0x03, 0xa0, 0x2e, 0x12, 0x92, 0xe0, 0x62, + 0x4f, 0x2e, 0x4a, 0x4d, 0x2c, 0xc9, 0x2f, 0x92, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x82, 0x71, + 0x85, 0x3c, 0xb8, 0x38, 0x60, 0xee, 0x96, 0x60, 0x52, 0x60, 0xd4, 0xe0, 0x36, 0x52, 0xd3, 0xc3, + 0x17, 0x06, 0x7a, 0x30, 0x33, 0x9d, 0x58, 0x4e, 0xdc, 0x93, 0x67, 0x08, 0x82, 0xeb, 0x56, 0x92, + 0xe6, 0x92, 0xc4, 0xb0, 0x38, 0x28, 0xb5, 0xb8, 0x20, 0x3f, 0xaf, 0x38, 0x55, 0xa9, 0x86, 0x4b, + 0x08, 0x2e, 0xe9, 0x0c, 0x32, 0xda, 0x33, 0x2f, 0x2d, 0x1f, 0x8f, 0xb3, 0x7c, 0xb8, 0xb8, 0x10, + 0x21, 0x04, 0x75, 0x98, 0x3a, 0x7e, 0x87, 0xc1, 0x8d, 0x85, 0xba, 0x8c, 0x33, 0x19, 0x26, 0xa0, + 0x24, 0xc3, 0x25, 0x85, 0x69, 0x3b, 0xcc, 0x6d, 0x46, 0x0d, 0x4c, 0x5c, 0xcc, 0xbe, 0xc5, 0xe9, + 0x42, 0x55, 0x5c, 0x7c, 0x68, 0xc1, 0xa6, 0x8f, 0xdf, 0x46, 0x0c, 0xef, 0x4a, 0x99, 0x93, 0xa8, + 0x01, 0xe6, 0x06, 0xa1, 0x5a, 0x2e, 0x7e, 0xf4, 0xc0, 0x31, 0x20, 0xd2, 0x2c, 0xb8, 0x0e, 0x29, + 0x0b, 0x52, 0x75, 0xc0, 0xac, 0x77, 0xf2, 0x3a, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, + 0x07, 0x8f, 0xe4, 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, + 0x86, 0x28, 0x83, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0x70, 0xba, 0xd4, + 0x45, 0x4b, 0xa2, 0x15, 0xc8, 0xb9, 0xa8, 0xb2, 0x20, 0xb5, 0x38, 0x89, 0x0d, 0x9c, 0x0e, 0x8d, + 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0xe8, 0xeb, 0xa6, 0x40, 0x72, 0x03, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. From 0a3b8dc7235fc5fd962aacca43324e499c3b41bd Mon Sep 17 00:00:00 2001 From: skosito Date: Fri, 31 May 2024 19:37:05 +0100 Subject: [PATCH 3/3] feat: modify rpc methods to include synthetic txs (#2282) --- changelog.md | 1 + go.mod | 13 ++-- go.sum | 5 +- rpc/backend/blocks.go | 90 +++++++++++++++++++++------ rpc/backend/tracing.go | 89 +++++---------------------- rpc/backend/tx_info.go | 129 +++++++++++++++++++-------------------- rpc/types/events.go | 88 ++++++++++++++++++++++---- rpc/types/types.go | 2 + rpc/types/utils.go | 6 +- x/fungible/keeper/evm.go | 8 ++- 10 files changed, 243 insertions(+), 188 deletions(-) diff --git a/changelog.md b/changelog.md index 4dda5f671e..d4595d2891 100644 --- a/changelog.md +++ b/changelog.md @@ -15,6 +15,7 @@ * [2152](https://github.com/zeta-chain/node/pull/2152) - custom priority nonce mempool * [2113](https://github.com/zeta-chain/node/pull/2113) - add zetaclientd-supervisor process * [2154](https://github.com/zeta-chain/node/pull/2154) - add `ibccrosschain` module +* [2282](https://github.com/zeta-chain/node/pull/2282) - modify rpc methods to support synthetic txs * [2258](https://github.com/zeta-chain/node/pull/2258) - add Optimism and Base in static chain information * [2287](https://github.com/zeta-chain/node/pull/2287) - implement `MsgUpdateChainInfo` message * [2279](https://github.com/zeta-chain/node/pull/2279) - add a CCTXGateway field to chain static data diff --git a/go.mod b/go.mod index 5cba25e884..709983e8cc 100644 --- a/go.mod +++ b/go.mod @@ -59,11 +59,13 @@ require ( cosmossdk.io/tools/rosetta v0.2.1 github.com/binance-chain/tss-lib v0.0.0-20201118045712-70b2cb4bf916 github.com/btcsuite/btcd/btcutil v1.1.3 + github.com/cockroachdb/errors v1.10.0 github.com/cometbft/cometbft v0.37.4 github.com/cometbft/cometbft-db v0.8.0 + github.com/golang/mock v1.6.0 + github.com/huandu/skiplist v1.2.0 github.com/nanmu42/etherscan-api v1.10.0 github.com/onrik/ethrpc v1.2.0 - github.com/tendermint/tendermint v0.34.12 go.nhat.io/grpcmock v0.25.0 ) @@ -77,7 +79,6 @@ require ( github.com/agl/ed25519 v0.0.0-20200225211852-fd4d107ace12 // indirect github.com/bool64/shared v0.1.5 // indirect github.com/cespare/xxhash v1.1.0 // indirect - github.com/cockroachdb/errors v1.10.0 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect github.com/cockroachdb/pebble v0.0.0-20220817183557-09c6e030a677 // indirect github.com/cockroachdb/redact v1.1.5 // indirect @@ -95,10 +96,8 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/golang/glog v1.1.2 // indirect - github.com/golang/mock v1.6.0 // indirect github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect github.com/google/s2a-go v0.1.7 // indirect - github.com/huandu/skiplist v1.2.0 // indirect github.com/iancoleman/orderedmap v0.3.0 // indirect github.com/ipfs/boxo v0.10.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect @@ -216,7 +215,7 @@ require ( github.com/gtank/ristretto255 v0.1.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-getter v1.7.4 // indirect + github.com/hashicorp/go-getter v1.7.4 github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/go-version v1.6.0 // indirect @@ -321,7 +320,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/crypto v0.17.0 - golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb // indirect + golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb golang.org/x/mod v0.11.0 // indirect golang.org/x/net v0.19.0 golang.org/x/oauth2 v0.15.0 // indirect @@ -354,4 +353,4 @@ replace ( replace github.com/cometbft/cometbft-db => github.com/notional-labs/cometbft-db v0.0.0-20230321185329-6dc7c0ca6345 -replace github.com/evmos/ethermint => github.com/zeta-chain/ethermint v0.0.0-20240429123701-35f3f79bf83f +replace github.com/evmos/ethermint => github.com/zeta-chain/ethermint v0.0.0-20240531172701-61d040058c94 diff --git a/go.sum b/go.sum index 21f0ee7415..ac3eb6cc0b 100644 --- a/go.sum +++ b/go.sum @@ -1639,7 +1639,6 @@ github.com/tendermint/tendermint v0.34.0-rc6/go.mod h1:ugzyZO5foutZImv0Iyx/gOFCX github.com/tendermint/tendermint v0.34.0/go.mod h1:Aj3PIipBFSNO21r+Lq3TtzQ+uKESxkbA3yo/INM4QwQ= github.com/tendermint/tendermint v0.34.10/go.mod h1:aeHL7alPh4uTBIJQ8mgFEE8VwJLXI1VD3rVOmH2Mcy0= github.com/tendermint/tendermint v0.34.11/go.mod h1:aeHL7alPh4uTBIJQ8mgFEE8VwJLXI1VD3rVOmH2Mcy0= -github.com/tendermint/tendermint v0.34.12 h1:m+kUYNhONedhJfHmHG8lqsdZvbR5t6vmhaok1yXjpKg= github.com/tendermint/tendermint v0.34.12/go.mod h1:aeHL7alPh4uTBIJQ8mgFEE8VwJLXI1VD3rVOmH2Mcy0= github.com/tendermint/tm-db v0.6.2/go.mod h1:GYtQ67SUvATOcoY8/+x6ylk8Qo02BQyLrAs+yAcLvGI= github.com/tendermint/tm-db v0.6.3/go.mod h1:lfA1dL9/Y/Y8wwyPp2NMLyn5P5Ptr/gvDFNWtrCWSf8= @@ -1733,8 +1732,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zeta-chain/ethermint v0.0.0-20240429123701-35f3f79bf83f h1:joafCsPgohPEn93VCbNXi9IAl6kNvKy8u+kv5amEvUk= -github.com/zeta-chain/ethermint v0.0.0-20240429123701-35f3f79bf83f/go.mod h1:s1zA6OpXv3Tb5I0M6M6j5fo/AssaZL/pgkc7G0W2kN8= +github.com/zeta-chain/ethermint v0.0.0-20240531172701-61d040058c94 h1:M54ljayJvy+WlEVdUmX8pgo1nA+XguB3mLhm3wi2z9o= +github.com/zeta-chain/ethermint v0.0.0-20240531172701-61d040058c94/go.mod h1:s1zA6OpXv3Tb5I0M6M6j5fo/AssaZL/pgkc7G0W2kN8= github.com/zeta-chain/go-tss v0.1.1-0.20240430111318-1785e48eb127 h1:AGQepvsMIVHAHPlplzNcSCyMoGBY1DfO4WHG/QHUSIU= github.com/zeta-chain/go-tss v0.1.1-0.20240430111318-1785e48eb127/go.mod h1:bVpAoSlRYYCY/R34horVU3cheeHqhSVxygelc++emIU= github.com/zeta-chain/keystone/keys v0.0.0-20231105174229-903bc9405da2 h1:gd2uE0X+ZbdFJ8DubxNqLbOVlCB12EgWdzSNRAR82tM= diff --git a/rpc/backend/blocks.go b/rpc/backend/blocks.go index 361c53e318..d28b8345da 100644 --- a/rpc/backend/blocks.go +++ b/rpc/backend/blocks.go @@ -22,8 +22,10 @@ import ( "math/big" "strconv" + abci "github.com/cometbft/cometbft/abci/types" tmrpcclient "github.com/cometbft/cometbft/rpc/client" tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" + tmtypes "github.com/cometbft/cometbft/types" sdk "github.com/cosmos/cosmos-sdk/types" grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" "github.com/ethereum/go-ethereum/common" @@ -272,14 +274,14 @@ func (b *Backend) BlockNumberFromTendermintByHash(blockHash common.Hash) (*big.I return big.NewInt(resBlock.Block.Height), nil } -// EthMsgsFromTendermintBlock returns all real MsgEthereumTxs from a +// EthMsgsFromTendermintBlock returns all real and synthetic MsgEthereumTxs from a // Tendermint block. It also ensures consistency over the correct txs indexes // across RPC endpoints func (b *Backend) EthMsgsFromTendermintBlock( resBlock *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults, ) ([]*evmtypes.MsgEthereumTx, []*rpctypes.TxResultAdditionalFields) { - var result []*evmtypes.MsgEthereumTx + var ethMsgs []*evmtypes.MsgEthereumTx var txsAdditional []*rpctypes.TxResultAdditionalFields block := resBlock.Block @@ -294,30 +296,78 @@ func (b *Backend) EthMsgsFromTendermintBlock( } tx, err := b.clientCtx.TxConfig.TxDecoder()(tx) - if err != nil { + // assumption is that if regular ethermint msg is found in tx + // there should not be synthetic one as well + shouldCheckForSyntheticTx := true + // if tx can be decoded, try to find MsgEthereumTx inside + if err == nil { + for _, msg := range tx.GetMsgs() { + ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) + if ok { + shouldCheckForSyntheticTx = false + ethMsg.Hash = ethMsg.AsTransaction().Hash().Hex() + ethMsgs = append(ethMsgs, ethMsg) + txsAdditional = append(txsAdditional, nil) + } + } + } else { b.logger.Debug("failed to decode transaction in block", "height", block.Height, "error", err.Error()) - continue } - for _, msg := range tx.GetMsgs() { - ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) - if !ok { - res, additional, err := rpctypes.ParseTxBlockResult(txResults[i], tx, i, block.Height) - if err != nil || additional == nil || res == nil { - continue - } - ethMsg = &evmtypes.MsgEthereumTx{ - From: additional.Sender.Hex(), - Hash: additional.Hash.Hex(), - } + + // if tx can not be decoded or MsgEthereumTx was not found, try to parse it from block results + if shouldCheckForSyntheticTx { + ethMsg, additional := b.parseSyntheticTxFromBlockResults(txResults, i, tx, block) + if ethMsg != nil { + ethMsgs = append(ethMsgs, ethMsg) txsAdditional = append(txsAdditional, additional) - } else { - ethMsg.Hash = ethMsg.AsTransaction().Hash().Hex() - txsAdditional = append(txsAdditional, nil) } - result = append(result, ethMsg) } } - return result, txsAdditional + return ethMsgs, txsAdditional +} + +func (b *Backend) parseSyntheticTxFromBlockResults( + txResults []*abci.ResponseDeliverTx, + i int, + tx sdk.Tx, + block *tmtypes.Block, +) (*evmtypes.MsgEthereumTx, *rpctypes.TxResultAdditionalFields) { + res, additional, err := rpctypes.ParseTxBlockResult(txResults[i], tx, i, block.Height) + // just skip tx if it can not be parsed, so remaining txs from the block are parsed + if err != nil { + b.logger.Error(err.Error()) + return nil, nil + } + if additional == nil || res == nil { + return nil, nil + } + return b.parseSyntethicTxFromAdditionalFields(additional), additional +} + +func (b *Backend) parseSyntethicTxFromAdditionalFields( + additional *rpctypes.TxResultAdditionalFields, +) *evmtypes.MsgEthereumTx { + recipient := additional.Recipient + t := ethtypes.NewTx(ðtypes.LegacyTx{ + Nonce: additional.Nonce, + Data: additional.Data, + Gas: additional.GasUsed, + To: &recipient, + GasPrice: nil, + Value: additional.Value, + V: big.NewInt(0), + R: big.NewInt(0), + S: big.NewInt(0), + }) + ethMsg := &evmtypes.MsgEthereumTx{} + err := ethMsg.FromEthereumTx(t) + if err != nil { + b.logger.Error("can not create eth msg", err.Error()) + return nil + } + ethMsg.Hash = additional.Hash.Hex() + ethMsg.From = additional.Sender.Hex() + return ethMsg } // HeaderByNumber returns the block header identified by height. diff --git a/rpc/backend/tracing.go b/rpc/backend/tracing.go index e86d2ed8a4..84a78dc6c7 100644 --- a/rpc/backend/tracing.go +++ b/rpc/backend/tracing.go @@ -22,7 +22,6 @@ import ( tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" evmtypes "github.com/evmos/ethermint/x/evm/types" "github.com/pkg/errors" @@ -50,62 +49,28 @@ func (b *Backend) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfi return nil, err } - // check tx index is not out of bound - // #nosec G701 txs number in block is always less than MaxUint32 - if uint32(len(blk.Block.Txs)) < transaction.TxIndex { - b.logger.Debug( - "tx index out of bounds", - "index", - transaction.TxIndex, - "hash", - hash.String(), - "height", - blk.Block.Height, - ) - return nil, fmt.Errorf("transaction not included in block %v", blk.Block.Height) - } - - var predecessors []*evmtypes.MsgEthereumTx - for _, txBz := range blk.Block.Txs[:transaction.TxIndex] { - tx, err := b.clientCtx.TxConfig.TxDecoder()(txBz) - if err != nil { - b.logger.Debug("failed to decode transaction in block", "height", blk.Block.Height, "error", err.Error()) - continue - } - for _, msg := range tx.GetMsgs() { - ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) - if !ok { - continue - } - - predecessors = append(predecessors, ethMsg) - } - } - - tx, err := b.clientCtx.TxConfig.TxDecoder()(blk.Block.Txs[transaction.TxIndex]) + blockResult, err := b.TendermintBlockResultByNumber(&blk.Block.Height) if err != nil { - b.logger.Debug("tx not found", "hash", hash) - return nil, err + return nil, fmt.Errorf("block result not found for height %d", blk.Block.Height) } - // add predecessor messages in current cosmos tx - // #nosec G701 always in range - for i := 0; i < int(transaction.MsgIndex); i++ { - ethMsg, ok := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx) - if !ok { - continue + predecessors := []*evmtypes.MsgEthereumTx{} + msgs, _ := b.EthMsgsFromTendermintBlock(blk, blockResult) + var ethMsg *evmtypes.MsgEthereumTx + for _, m := range msgs { + if m.Hash == hash.Hex() { + ethMsg = m + break } - predecessors = append(predecessors, ethMsg) + predecessors = append(predecessors, m) } - ethMessage, ok := tx.GetMsgs()[transaction.MsgIndex].(*evmtypes.MsgEthereumTx) - if !ok { - b.logger.Debug("invalid transaction type", "type", fmt.Sprintf("%T", tx)) - return nil, fmt.Errorf("invalid transaction type %T", tx) + if ethMsg == nil { + return nil, fmt.Errorf("tx not found in block %d", blk.Block.Height) } traceTxRequest := evmtypes.QueryTraceTxRequest{ - Msg: ethMessage, + Msg: ethMsg, Predecessors: predecessors, BlockNumber: blk.Block.Height, BlockTime: blk.Block.Time, @@ -160,31 +125,7 @@ func (b *Backend) TraceBlock(height rpctypes.BlockNumber, b.logger.Debug("block result not found", "height", block.Block.Height, "error", err.Error()) return nil, nil } - - txDecoder := b.clientCtx.TxConfig.TxDecoder() - - var txsMessages []*evmtypes.MsgEthereumTx - for i, tx := range txs { - if !rpctypes.TxSuccessOrExceedsBlockGasLimit(blockRes.TxsResults[i]) { - b.logger.Debug("invalid tx result code", "cosmos-hash", hexutil.Encode(tx.Hash())) - continue - } - - decodedTx, err := txDecoder(tx) - if err != nil { - b.logger.Error("failed to decode transaction", "hash", txs[i].Hash(), "error", err.Error()) - continue - } - - for _, msg := range decodedTx.GetMsgs() { - ethMessage, ok := msg.(*evmtypes.MsgEthereumTx) - if !ok { - // Just considers Ethereum transactions - continue - } - txsMessages = append(txsMessages, ethMessage) - } - } + msgs, _ := b.EthMsgsFromTendermintBlock(block, blockRes) // minus one to get the context at the beginning of the block contextHeight := height - 1 @@ -195,7 +136,7 @@ func (b *Backend) TraceBlock(height rpctypes.BlockNumber, ctxWithHeight := rpctypes.ContextWithHeight(int64(contextHeight)) traceBlockRequest := &evmtypes.QueryTraceBlockRequest{ - Txs: txsMessages, + Txs: msgs, TraceConfig: config, BlockNumber: block.Block.Height, BlockTime: block.Block.Time, diff --git a/rpc/backend/tx_info.go b/rpc/backend/tx_info.go index 612097b0a1..e08f36db42 100644 --- a/rpc/backend/tx_info.go +++ b/rpc/backend/tx_info.go @@ -41,39 +41,48 @@ func (b *Backend) GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransac return b.getTransactionByHashPending(txHash) } - block, err := b.TendermintBlockByNumber(rpctypes.BlockNumber(res.Height)) - if err != nil { - return nil, err - } - - tx, err := b.clientCtx.TxConfig.TxDecoder()(block.Block.Txs[res.TxIndex]) + resBlock, err := b.TendermintBlockByNumber(rpctypes.BlockNumber(res.Height)) if err != nil { - return nil, err + b.logger.Debug("block not found", "height", res.Height, "error", err.Error()) + return nil, nil } - blockRes, err := b.TendermintBlockResultByNumber(&block.Block.Height) + blockRes, err := b.TendermintBlockResultByNumber(&res.Height) if err != nil { - b.logger.Debug("block result not found", "height", block.Block.Height, "error", err.Error()) + b.logger.Debug("failed to retrieve block results", "height", res.Height, "error", err.Error()) return nil, nil } - // the `res.MsgIndex` is inferred from tx index, should be within the bound. - msg, ok := tx.GetMsgs()[res.MsgIndex].(*evmtypes.MsgEthereumTx) - if !ok { - if additional == nil { - return nil, err + var ethMsg *evmtypes.MsgEthereumTx + // if additional fields are empty we can try to get MsgEthereumTx from sdk.Msg array + if additional == nil { + // #nosec G701 always in range + if int(res.TxIndex) >= len(resBlock.Block.Txs) { + b.logger.Error("tx out of bounds") + return nil, fmt.Errorf("tx out of bounds") } - msg = &evmtypes.MsgEthereumTx{ - Hash: hexTx, - From: additional.Sender.Hex(), + tx, err := b.clientCtx.TxConfig.TxDecoder()(resBlock.Block.Txs[res.TxIndex]) + if err != nil { + b.logger.Debug("decoding failed", "error", err.Error()) + return nil, fmt.Errorf("failed to decode tx: %w", err) + } + ethMsg = tx.GetMsgs()[res.MsgIndex].(*evmtypes.MsgEthereumTx) + if ethMsg == nil { + b.logger.Error("failed to get eth msg from sdk.Msgs") + return nil, fmt.Errorf("failed to get eth msg from sdk.Msgs") } } else { - additional = nil + // if additional fields are not empty try to parse synthetic tx from them + ethMsg = b.parseSyntethicTxFromAdditionalFields(additional) + if ethMsg == nil { + b.logger.Error("failed to get synthetic eth msg from additional fields") + return nil, fmt.Errorf("failed to get synthetic eth msg from additional fields") + } } if res.EthTxIndex == -1 { // Fallback to find tx index by iterating all valid eth transactions - msgs, _ := b.EthMsgsFromTendermintBlock(block, blockRes) + msgs, _ := b.EthMsgsFromTendermintBlock(resBlock, blockRes) for i := range msgs { if msgs[i].Hash == hexTx { // #nosec G701 always in range @@ -82,6 +91,7 @@ func (b *Backend) GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransac } } } + // if we still unable to find the eth tx index, return error, shouldn't happen. if res.EthTxIndex == -1 && additional == nil { return nil, errors.New("can't find index of ethereum tx") @@ -103,8 +113,8 @@ func (b *Backend) GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransac } return rpctypes.NewTransactionFromMsg( - msg, - common.BytesToHash(block.BlockID.Hash.Bytes()), + ethMsg, + common.BytesToHash(resBlock.BlockID.Hash.Bytes()), // #nosec G701 always positive uint64(res.Height), // #nosec G701 always positive @@ -158,7 +168,6 @@ func (b *Backend) getTransactionByHashPending(txHash common.Hash) (*rpctypes.RPC func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) { hexTx := hash.Hex() b.logger.Debug("eth_getTransactionReceipt", "hash", hexTx) - res, additional, err := b.GetTxByEthHash(hash) if err != nil { b.logger.Debug("tx not found", "hash", hexTx, "error", err.Error()) @@ -170,16 +179,26 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{ b.logger.Debug("block not found", "height", res.Height, "error", err.Error()) return nil, nil } - tx, err := b.clientCtx.TxConfig.TxDecoder()(resBlock.Block.Txs[res.TxIndex]) - if err != nil { - b.logger.Debug("decoding failed", "error", err.Error()) - return nil, fmt.Errorf("failed to decode tx: %w", err) - } var txData evmtypes.TxData var ethMsg *evmtypes.MsgEthereumTx + // if additional fields are empty we can try to get MsgEthereumTx from sdk.Msg array if additional == nil { + // #nosec G701 always in range + if int(res.TxIndex) >= len(resBlock.Block.Txs) { + b.logger.Error("tx out of bounds") + return nil, fmt.Errorf("tx out of bounds") + } + tx, err := b.clientCtx.TxConfig.TxDecoder()(resBlock.Block.Txs[res.TxIndex]) + if err != nil { + b.logger.Debug("decoding failed", "error", err.Error()) + return nil, fmt.Errorf("failed to decode tx: %w", err) + } ethMsg = tx.GetMsgs()[res.MsgIndex].(*evmtypes.MsgEthereumTx) + if ethMsg == nil { + b.logger.Error("failed to get eth msg") + return nil, fmt.Errorf("failed to get eth msg") + } txData, err = evmtypes.UnpackTxData(ethMsg.Data) if err != nil { @@ -187,9 +206,11 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{ return nil, err } } else { - ethMsg = &evmtypes.MsgEthereumTx{ - From: additional.Sender.Hex(), - Hash: additional.Hash.Hex(), + // if additional fields are not empty try to parse synthetic tx from them + ethMsg = b.parseSyntethicTxFromAdditionalFields(additional) + if ethMsg == nil { + b.logger.Error("failed to parse tx") + return nil, fmt.Errorf("failed to parse tx") } } @@ -218,13 +239,13 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{ } var from common.Address - if ethMsg.Data != nil { + if additional != nil { + from = common.HexToAddress(ethMsg.From) + } else if ethMsg.Data != nil { from, err = ethMsg.GetSender(chainID.ToInt()) if err != nil { return nil, err } - } else if additional != nil { - from = common.HexToAddress(ethMsg.From) } else { return nil, errors.New("failed to parse receipt") } @@ -247,6 +268,7 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{ } } } + // return error if still unable to find the eth tx index if res.EthTxIndex == -1 { if additional != nil { @@ -321,7 +343,6 @@ func (b *Backend) GetTransactionByBlockHashAndIndex( idx hexutil.Uint, ) (*rpctypes.RPCTransaction, error) { b.logger.Debug("eth_getTransactionByBlockHashAndIndex", "hash", hash.Hex(), "index", idx) - sc, ok := b.clientCtx.Client.(tmrpcclient.SignClient) if !ok { return nil, errors.New("invalid rpc client") @@ -450,42 +471,16 @@ func (b *Backend) GetTransactionByBlockAndIndex( return nil, nil } - var msg *evmtypes.MsgEthereumTx - // find in tx indexer // #nosec G701 always in range - res, additional, err := b.GetTxByTxIndex(block.Block.Height, uint(idx)) - if err == nil { - tx, err := b.clientCtx.TxConfig.TxDecoder()(block.Block.Txs[res.TxIndex]) - if err != nil { - b.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx) - return nil, nil - } - - var ok bool - // msgIndex is inferred from tx events, should be within bound. - msg, ok = tx.GetMsgs()[res.MsgIndex].(*evmtypes.MsgEthereumTx) - if !ok { - if additional == nil { - b.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx) - return nil, nil - } - msg = &evmtypes.MsgEthereumTx{ - Hash: additional.Hash.Hex(), - From: additional.Sender.Hex(), - } - } - } else { - // #nosec G701 always in range - i := int(idx) - ethMsgs, _ := b.EthMsgsFromTendermintBlock(block, blockRes) - if i >= len(ethMsgs) { - b.logger.Debug("block txs index out of bound", "index", i) - return nil, nil - } - - msg = ethMsgs[i] + i := int(idx) + ethMsgs, additionals := b.EthMsgsFromTendermintBlock(block, blockRes) + if i >= len(ethMsgs) { + b.logger.Debug("block txs index out of bound", "index", i) + return nil, nil } + msg := ethMsgs[i] + additional := additionals[i] baseFee, err := b.BaseFee(blockRes) if err != nil { // handle the error for pruned node. diff --git a/rpc/types/events.go b/rpc/types/events.go index e6e9a3c994..3839fb84df 100644 --- a/rpc/types/events.go +++ b/rpc/types/events.go @@ -16,14 +16,18 @@ package types import ( + "encoding/base64" "fmt" + "math" "math/big" "strconv" + "strings" abci "github.com/cometbft/cometbft/abci/types" tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" ethermint "github.com/evmos/ethermint/types" evmtypes "github.com/evmos/ethermint/x/evm/types" ) @@ -68,6 +72,7 @@ const ( // ParsedTx is the tx infos parsed from events. type ParsedTx struct { + // max uint32 means there is no sdk.Msg corresponding to eth tx MsgIndex int // the following fields are parsed from events @@ -83,6 +88,8 @@ type ParsedTx struct { Amount *big.Int Recipient common.Address Sender common.Address + Nonce uint64 + Data []byte } // NewParsedTx initialize a ParsedTx @@ -177,6 +184,18 @@ func ParseTxResult(result *abci.ResponseDeliverTx, tx sdk.Tx) (*ParsedTxs, error p.Txs[i].GasUsed = gasLimit } } + + // fix msg indexes, because some eth txs indexed here don't have corresponding sdk.Msg + currMsgIndex := 0 + for _, tx := range p.Txs { + if tx.Type == CosmosEVMTxType { + tx.MsgIndex = math.MaxUint32 + // todo: fix mapping as well + } else { + tx.MsgIndex = currMsgIndex + currMsgIndex++ + } + } return p, nil } @@ -204,7 +223,7 @@ func ParseTxIndexerResult( txResult.Index, ) } - if parsedTx.Type == 88 { + if parsedTx.Type == CosmosEVMTxType { return ðermint.TxResult{ Height: txResult.Height, TxIndex: txResult.Index, @@ -251,7 +270,7 @@ func ParseTxBlockResult( return nil, nil, fmt.Errorf("ethereum tx not found in msgs: block %d, index %d", height, txIndex) } parsedTx := txs.Txs[0] - if parsedTx.Type == 88 { + if parsedTx.Type == CosmosEVMTxType { return ðermint.TxResult{ Height: height, // #nosec G701 always in range @@ -270,6 +289,8 @@ func ParseTxBlockResult( Recipient: parsedTx.Recipient, Sender: parsedTx.Sender, GasUsed: parsedTx.GasUsed, + Data: parsedTx.Data, + Nonce: parsedTx.Nonce, }, nil } return ðermint.TxResult{ @@ -353,19 +374,19 @@ func (p *ParsedTxs) AccumulativeGasUsed(msgIndex int) (result uint64) { // fillTxAttribute parse attributes by name, less efficient than hardcode the index, but more stable against event // format changes. -func fillTxAttribute(tx *ParsedTx, key []byte, value []byte) error { - switch string(key) { +func fillTxAttribute(tx *ParsedTx, key, value string) error { + switch key { case evmtypes.AttributeKeyEthereumTxHash: - tx.Hash = common.HexToHash(string(value)) + tx.Hash = common.HexToHash(value) case evmtypes.AttributeKeyTxIndex: - txIndex, err := strconv.ParseUint(string(value), 10, 31) + txIndex, err := strconv.ParseUint(value, 10, 31) if err != nil { return err } // #nosec G701 always in range tx.EthTxIndex = int32(txIndex) case evmtypes.AttributeKeyTxGasUsed: - gasUsed, err := strconv.ParseUint(string(value), 10, 64) + gasUsed, err := strconv.ParseUint(value, 10, 64) if err != nil { return err } @@ -373,32 +394,73 @@ func fillTxAttribute(tx *ParsedTx, key []byte, value []byte) error { case evmtypes.AttributeKeyEthereumTxFailed: tx.Failed = len(value) > 0 case SenderType: - tx.Sender = common.HexToAddress(string(value)) + tx.Sender = common.HexToAddress(value) case evmtypes.AttributeKeyRecipient: - tx.Recipient = common.HexToAddress(string(value)) + tx.Recipient = common.HexToAddress(value) case evmtypes.AttributeKeyTxHash: - tx.TxHash = string(value) + tx.TxHash = value case evmtypes.AttributeKeyTxType: - txType, err := strconv.ParseUint(string(value), 10, 31) + txType, err := strconv.ParseUint(value, 10, 31) if err != nil { return err } tx.Type = txType case AmountType: var success bool - tx.Amount, success = big.NewInt(0).SetString(string(value), 10) + tx.Amount, success = big.NewInt(0).SetString(value, 10) if !success { return nil } + case evmtypes.AttributeKeyTxNonce: + nonce, err := strconv.ParseUint(value, 10, 64) + if err != nil { + return err + } + tx.Nonce = nonce + + case evmtypes.AttributeKeyTxData: + hexBytes, err := hexutil.Decode(value) + if err != nil { + return err + } + tx.Data = hexBytes } return nil } func fillTxAttributes(tx *ParsedTx, attrs []abci.EventAttribute) error { + // before cosmos upgrade to 0.47, attributes are base64 encoded + // purpose of this is to support older txs as well + isLegacyAttrs := isLegacyAttrEncoding(attrs) for _, attr := range attrs { - if err := fillTxAttribute(tx, []byte(attr.Key), []byte(attr.Value)); err != nil { + if isLegacyAttrs { + // only decode if value can be decoded + // (error should not happen because at this point it is determined it is legacy attr) + decKey, err := base64.StdEncoding.DecodeString(attr.Key) + if err != nil { + return err + } + attr.Key = string(decKey) + decValue, err := base64.StdEncoding.DecodeString(attr.Value) + if err != nil { + return err + } + attr.Value = string(decValue) + } + + if err := fillTxAttribute(tx, attr.Key, attr.Value); err != nil { return err } } return nil } + +func isLegacyAttrEncoding(attrs []abci.EventAttribute) bool { + for _, attr := range attrs { + if strings.Contains(attr.Key, "==") || strings.Contains(attr.Value, "==") { + return true + } + } + + return false +} diff --git a/rpc/types/types.go b/rpc/types/types.go index 62d4b343a9..907eff4740 100644 --- a/rpc/types/types.go +++ b/rpc/types/types.go @@ -53,6 +53,8 @@ type TxResultAdditionalFields struct { Recipient common.Address `json:"recipient"` Sender common.Address `json:"sender"` GasUsed uint64 `json:"gasUsed"` + Nonce uint64 `json:"nonce"` + Data []byte `json:"data"` } // RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction diff --git a/rpc/types/utils.go b/rpc/types/utils.go index 650a0d52ff..dfda689557 100644 --- a/rpc/types/utils.go +++ b/rpc/types/utils.go @@ -167,10 +167,10 @@ func NewTransactionFromMsg( chainID *big.Int, txAdditional *TxResultAdditionalFields, ) (*RPCTransaction, error) { - tx := msg.AsTransaction() - if tx == nil { + if txAdditional != nil { return NewRPCTransactionFromIncompleteMsg(msg, blockHash, blockNumber, index, baseFee, chainID, txAdditional) } + tx := msg.AsTransaction() return NewRPCTransaction(tx, blockHash, blockNumber, index, baseFee, chainID) } @@ -253,7 +253,7 @@ func NewRPCTransactionFromIncompleteMsg( GasPrice: (*hexutil.Big)(baseFee), Hash: common.HexToHash(msg.Hash), Input: []byte{}, - Nonce: 0, // TODO: get nonce for "from" from ethermint + Nonce: hexutil.Uint64(txAdditional.Nonce), // TODO: get nonce for "from" from ethermint To: to, Value: (*hexutil.Big)(txAdditional.Value), V: nil, diff --git a/x/fungible/keeper/evm.go b/x/fungible/keeper/evm.go index bb7a037c07..9a7ed00d5b 100644 --- a/x/fungible/keeper/evm.go +++ b/x/fungible/keeper/evm.go @@ -680,6 +680,9 @@ func (k Keeper) CallEVM( // value is the amount of wei to send; gaslimit is the custom gas limit, if nil EstimateGas is used // to bisect the correct gas limit (this may sometimes result in insufficient gas limit; not sure why) // +// noEthereumTxEvent flag is used to control if ethereum_tx eventsshould emitted +// which will mean these txs are indexed and available in rpc methods +// // returns (msg,err) the EVM execution result if there is any, even if error is non-nil due to contract reverts // Furthermore, err!=nil && msg!=nil && msg.Failed() means the contract call reverted; in which case // msg.Ret gives the RET code if contract revert with REVERT opcode with parameters. @@ -721,7 +724,6 @@ func (k Keeper) CallEVMWithData( if gasLimit != nil { gasCap = gasLimit.Uint64() } - msg := ethtypes.NewMessage( from, contract, @@ -789,6 +791,10 @@ func (k Keeper) CallEVMWithData( } if !noEthereumTxEvent { + // adding txData for more info in rpc methods in order to parse synthetic txs + attrs = append(attrs, sdk.NewAttribute(evmtypes.AttributeKeyTxData, hexutil.Encode(msg.Data()))) + // adding nonce for more info in rpc methods in order to parse synthetic txs + attrs = append(attrs, sdk.NewAttribute(evmtypes.AttributeKeyTxNonce, fmt.Sprint(nonce))) ctx.EventManager().EmitEvents(sdk.Events{ sdk.NewEvent( evmtypes.EventTypeEthereumTx,