From 1dd022f6f8c6158e9924cbb839d7d8c5792ac725 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Mon, 6 Nov 2023 12:56:53 -0800 Subject: [PATCH 01/27] Beginning to rename ServiceId to Service --- go.mod | 4 +- pkg/relayer/proxy/interface.go | 2 +- proto/pocket/application/application.proto | 2 +- proto/pocket/session/query.proto | 2 +- proto/pocket/session/session.proto | 2 +- proto/pocket/shared/service.proto | 8 +- testutil/keeper/session.go | 12 +- testutil/network/network.go | 4 +- x/application/genesis_test.go | 4 +- x/application/keeper/application_test.go | 2 +- .../msg_server_delegate_to_gateway_test.go | 8 +- .../msg_server_stake_application_test.go | 24 +-- ...msg_server_undelegate_from_gateway_test.go | 6 +- .../msg_server_unstake_application_test.go | 2 +- x/application/types/genesis_test.go | 10 +- .../types/message_stake_application.go | 2 +- .../types/message_stake_application_test.go | 24 +-- x/session/keeper/query_get_session.go | 2 +- x/session/keeper/query_get_session_test.go | 4 +- x/session/keeper/session_hydrator.go | 6 +- x/session/keeper/session_hydrator_test.go | 4 +- x/shared/helpers/service.go | 7 + x/shared/helpers/service_configs.go | 29 +--- x/shared/helpers/service_test.go | 145 +++++++++++++++--- x/supplier/client/cli/tx_stake_supplier.go | 2 +- x/supplier/genesis_test.go | 4 +- .../keeper/msg_server_stake_supplier_test.go | 22 +-- .../msg_server_unstake_supplier_test.go | 2 +- x/supplier/keeper/supplier_test.go | 2 +- x/supplier/types/errors.go | 12 +- x/supplier/types/genesis_test.go | 8 +- .../types/message_stake_supplier_test.go | 18 +-- 32 files changed, 240 insertions(+), 145 deletions(-) diff --git a/go.mod b/go.mod index 95c298124..4b5bdd434 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( cosmossdk.io/math v1.0.1 github.com/cometbft/cometbft v0.37.2 github.com/cometbft/cometbft-db v0.8.0 - github.com/cosmos/cosmos-proto v1.0.0-beta.2 github.com/cosmos/cosmos-sdk v0.47.3 github.com/cosmos/gogoproto v1.4.10 github.com/cosmos/ibc-go/v7 v7.1.0 @@ -27,7 +26,6 @@ require ( go.uber.org/multierr v1.11.0 golang.org/x/crypto v0.12.0 golang.org/x/sync v0.3.0 - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 google.golang.org/grpc v1.56.1 gopkg.in/yaml.v2 v2.4.0 ) @@ -71,6 +69,7 @@ require ( github.com/containerd/cgroups v1.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect + github.com/cosmos/cosmos-proto v1.0.0-beta.2 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect github.com/cosmos/iavl v0.20.0 // indirect @@ -266,6 +265,7 @@ require ( gonum.org/v1/gonum v0.11.0 // indirect google.golang.org/api v0.122.0 // indirect google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/pkg/relayer/proxy/interface.go b/pkg/relayer/proxy/interface.go index 27ee83e72..e9057f663 100644 --- a/pkg/relayer/proxy/interface.go +++ b/pkg/relayer/proxy/interface.go @@ -42,5 +42,5 @@ type RelayServer interface { Stop(ctx context.Context) error // ServiceId returns the serviceId of the service. - ServiceId() *sharedtypes.ServiceId + Service() *sharedtypes.Service } diff --git a/proto/pocket/application/application.proto b/proto/pocket/application/application.proto index e5763d697..f4d3610ca 100644 --- a/proto/pocket/application/application.proto +++ b/proto/pocket/application/application.proto @@ -13,6 +13,6 @@ import "pocket/shared/service.proto"; message Application { string address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // The Bech32 address of the application using cosmos' ScalarDescriptor to ensure deterministic encoding cosmos.base.v1beta1.Coin stake = 2; // The total amount of uPOKT the application has staked - repeated shared.ApplicationServiceConfig service_configs = 3; // The ID of the service this session is servicing + repeated shared.ApplicationServiceConfig service_configs = 3; // The list of services this appliccation is configured to request service for repeated string delegatee_gateway_addresses = 4 [(cosmos_proto.scalar) = "cosmos.AddressString", (gogoproto.nullable) = false]; // The Bech32 encoded addresses for all delegatee Gateways, in a non-nullable slice } diff --git a/proto/pocket/session/query.proto b/proto/pocket/session/query.proto index f8b1c7187..08de287eb 100644 --- a/proto/pocket/session/query.proto +++ b/proto/pocket/session/query.proto @@ -38,7 +38,7 @@ message QueryParamsResponse { message QueryGetSessionRequest { string application_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // The Bech32 address of the application using cosmos' ScalarDescriptor to ensure deterministic encoding - shared.ServiceId service_id = 2; // The service id to query the session for + shared.Service service = 2; // The service id to query the session for int64 block_height = 3; // The block height to query the session for } diff --git a/proto/pocket/session/session.proto b/proto/pocket/session/session.proto index e8f14b35e..3008e749f 100644 --- a/proto/pocket/session/session.proto +++ b/proto/pocket/session/session.proto @@ -14,7 +14,7 @@ import "pocket/shared/supplier.proto"; // It is the minimal amount of data required to hydrate & retrieve all data relevant to the session. message SessionHeader { string application_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // The Bech32 address of the application using cosmos' ScalarDescriptor to ensure deterministic encoding - shared.ServiceId service_id = 2; // The ID of the service this session is servicing + shared.Service service = 2; // The service this session is for int64 session_start_block_height = 3; // The height at which this session started // NOTE: session_id can be derived from the above values using on-chain but is included in the header for convenience string session_id = 4; // A unique pseudoranom ID for this session diff --git a/proto/pocket/shared/service.proto b/proto/pocket/shared/service.proto index c911bad1f..8ae83ad4d 100644 --- a/proto/pocket/shared/service.proto +++ b/proto/pocket/shared/service.proto @@ -8,8 +8,8 @@ option go_package = "github.com/pokt-network/poktroll/x/shared/types"; // TODO_CLEANUP(@Olshansk): Add native optional identifiers once its supported; https://github.com/ignite/cli/issues/3698 -// ServiceId message to encapsulate unique and semantic identifiers for a service on the network -message ServiceId { +// Service message to encapsulate unique and semantic identifiers for a service on the network +message Service { // NOTE: `ServiceId.Id` may seem redundant but was desigtned created to enable more complex service identification // For example, what if we want to request a session for a certain service but with some additional configs that identify it? string id = 1; // Unique identifier for the service @@ -23,7 +23,7 @@ message ServiceId { // ApplicationServiceConfig holds the service configuration the application stakes for message ApplicationServiceConfig { - ServiceId service_id = 1; // Unique and semantic identifier for the service + Service service = 1; // Unique and semantic identifier for the service // TODO_RESEARCH: There is an opportunity for applications to advertise the max // they're willing to pay for a certain configuration/price, but this is outside of scope. @@ -32,7 +32,7 @@ message ApplicationServiceConfig { // SupplierServiceConfig holds the service configuration the supplier stakes for message SupplierServiceConfig { - ServiceId service_id = 1; // Unique and semantic identifier for the service + Service service = 1; // Unique and semantic identifier for the service repeated SupplierEndpoint endpoints = 2; // List of endpoints for the service // TODO_RESEARCH: There is an opportunity for supplier to advertise the min // they're willing to earn for a certain configuration/price, but this is outside of scope. diff --git a/testutil/keeper/session.go b/testutil/keeper/session.go index e4be2537f..ab0eb277c 100644 --- a/testutil/keeper/session.go +++ b/testutil/keeper/session.go @@ -36,10 +36,10 @@ var ( Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, ServiceConfigs: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: TestServiceId1}, + Service: &sharedtypes.Service{Id: TestServiceId1}, }, { - ServiceId: &sharedtypes.ServiceId{Id: TestServiceId2}, + Service: &sharedtypes.Service{Id: TestServiceId2}, }, }, } @@ -50,10 +50,10 @@ var ( Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, ServiceConfigs: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: TestServiceId1}, + Service: &sharedtypes.Service{Id: TestServiceId1}, }, { - ServiceId: &sharedtypes.ServiceId{Id: TestServiceId2}, + Service: &sharedtypes.Service{Id: TestServiceId2}, }, }, } @@ -65,7 +65,7 @@ var ( Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: TestServiceId1}, + Service: &sharedtypes.Service{Id: TestServiceId1}, Endpoints: []*sharedtypes.SupplierEndpoint{ { Url: TestSupplierUrl, @@ -75,7 +75,7 @@ var ( }, }, { - ServiceId: &sharedtypes.ServiceId{Id: TestServiceId2}, + Service: &sharedtypes.Service{Id: TestServiceId2}, Endpoints: []*sharedtypes.SupplierEndpoint{ { Url: TestSupplierUrl, diff --git a/testutil/network/network.go b/testutil/network/network.go index c8ab3efcb..c39983ca6 100644 --- a/testutil/network/network.go +++ b/testutil/network/network.go @@ -111,7 +111,7 @@ func DefaultApplicationModuleGenesisState(t *testing.T, n int) *apptypes.Genesis Stake: &stake, ServiceConfigs: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: fmt.Sprintf("svc%d", i)}, + Service: &sharedtypes.Service{Id: fmt.Sprintf("svc%d", i)}, }, }, } @@ -152,7 +152,7 @@ func DefaultSupplierModuleGenesisState(t *testing.T, n int) *suppliertypes.Genes Stake: &stake, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: fmt.Sprintf("svc%d", i)}, + Service: &sharedtypes.Service{Id: fmt.Sprintf("svc%d", i)}, Endpoints: []*sharedtypes.SupplierEndpoint{ { Url: fmt.Sprintf("http://localhost:%d", i), diff --git a/x/application/genesis_test.go b/x/application/genesis_test.go index 51d1f7ec9..74ac91f37 100644 --- a/x/application/genesis_test.go +++ b/x/application/genesis_test.go @@ -24,7 +24,7 @@ func TestGenesis(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, ServiceConfigs: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, }, @@ -33,7 +33,7 @@ func TestGenesis(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, ServiceConfigs: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc2"}, + Service: &sharedtypes.Service{Id: "svc2"}, }, }, }, diff --git a/x/application/keeper/application_test.go b/x/application/keeper/application_test.go index 0fd7d7ea1..6af5e3d26 100644 --- a/x/application/keeper/application_test.go +++ b/x/application/keeper/application_test.go @@ -33,7 +33,7 @@ func createNApplication(keeper *keeper.Keeper, ctx sdk.Context, n int) []types.A app.Stake = &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(int64(i))} app.ServiceConfigs = []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: fmt.Sprintf("svc%d", i)}, + Service: &sharedtypes.Service{Id: fmt.Sprintf("svc%d", i)}, }, } keeper.SetApplication(ctx, *app) diff --git a/x/application/keeper/msg_server_delegate_to_gateway_test.go b/x/application/keeper/msg_server_delegate_to_gateway_test.go index 2e48edf5f..80e0b586a 100644 --- a/x/application/keeper/msg_server_delegate_to_gateway_test.go +++ b/x/application/keeper/msg_server_delegate_to_gateway_test.go @@ -36,7 +36,7 @@ func TestMsgServer_DelegateToGateway_SuccessfullyDelegate(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, } @@ -100,7 +100,7 @@ func TestMsgServer_DelegateToGateway_FailDuplicate(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, } @@ -158,7 +158,7 @@ func TestMsgServer_DelegateToGateway_FailGatewayNotStaked(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, } @@ -203,7 +203,7 @@ func TestMsgServer_DelegateToGateway_FailMaxReached(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, } diff --git a/x/application/keeper/msg_server_stake_application_test.go b/x/application/keeper/msg_server_stake_application_test.go index 3b67cd20c..d3e858987 100644 --- a/x/application/keeper/msg_server_stake_application_test.go +++ b/x/application/keeper/msg_server_stake_application_test.go @@ -31,7 +31,7 @@ func TestMsgServer_StakeApplication_SuccessfulCreateAndUpdate(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, } @@ -46,7 +46,7 @@ func TestMsgServer_StakeApplication_SuccessfulCreateAndUpdate(t *testing.T) { require.Equal(t, addr, appFound.Address) require.Equal(t, int64(100), appFound.Stake.Amount.Int64()) require.Len(t, appFound.ServiceConfigs, 1) - require.Equal(t, "svc1", appFound.ServiceConfigs[0].ServiceId.Id) + require.Equal(t, "svc1", appFound.ServiceConfigs[0].Service.Id) // Prepare an updated application with a higher stake and another service updateStakeMsg := &types.MsgStakeApplication{ @@ -54,10 +54,10 @@ func TestMsgServer_StakeApplication_SuccessfulCreateAndUpdate(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(200)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, { - ServiceId: &sharedtypes.ServiceId{Id: "svc2"}, + Service: &sharedtypes.Service{Id: "svc2"}, }, }, } @@ -69,8 +69,8 @@ func TestMsgServer_StakeApplication_SuccessfulCreateAndUpdate(t *testing.T) { require.True(t, isAppFound) require.Equal(t, int64(200), appFound.Stake.Amount.Int64()) require.Len(t, appFound.ServiceConfigs, 2) - require.Equal(t, "svc1", appFound.ServiceConfigs[0].ServiceId.Id) - require.Equal(t, "svc2", appFound.ServiceConfigs[1].ServiceId.Id) + require.Equal(t, "svc1", appFound.ServiceConfigs[0].Service.Id) + require.Equal(t, "svc2", appFound.ServiceConfigs[1].Service.Id) } func TestMsgServer_StakeApplication_FailRestakingDueToInvalidServices(t *testing.T) { @@ -86,7 +86,7 @@ func TestMsgServer_StakeApplication_FailRestakingDueToInvalidServices(t *testing Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, } @@ -111,7 +111,7 @@ func TestMsgServer_StakeApplication_FailRestakingDueToInvalidServices(t *testing require.True(t, isAppFound) require.Equal(t, appAddr, app.Address) require.Len(t, app.ServiceConfigs, 1) - require.Equal(t, "svc1", app.ServiceConfigs[0].ServiceId.Id) + require.Equal(t, "svc1", app.ServiceConfigs[0].Service.Id) // Prepare the application stake message with an invalid service ID updateStakeMsg = &types.MsgStakeApplication{ @@ -119,7 +119,7 @@ func TestMsgServer_StakeApplication_FailRestakingDueToInvalidServices(t *testing Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1 INVALID ! & *"}, + Service: &sharedtypes.Service{Id: "svc1 INVALID ! & *"}, }, }, } @@ -133,7 +133,7 @@ func TestMsgServer_StakeApplication_FailRestakingDueToInvalidServices(t *testing require.True(t, isAppFound) require.Equal(t, appAddr, app.Address) require.Len(t, app.ServiceConfigs, 1) - require.Equal(t, "svc1", app.ServiceConfigs[0].ServiceId.Id) + require.Equal(t, "svc1", app.ServiceConfigs[0].Service.Id) } func TestMsgServer_StakeApplication_FailLoweringStake(t *testing.T) { @@ -148,7 +148,7 @@ func TestMsgServer_StakeApplication_FailLoweringStake(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, } @@ -165,7 +165,7 @@ func TestMsgServer_StakeApplication_FailLoweringStake(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(50)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, } diff --git a/x/application/keeper/msg_server_undelegate_from_gateway_test.go b/x/application/keeper/msg_server_undelegate_from_gateway_test.go index 30824a6d2..7a3b8283d 100644 --- a/x/application/keeper/msg_server_undelegate_from_gateway_test.go +++ b/x/application/keeper/msg_server_undelegate_from_gateway_test.go @@ -39,7 +39,7 @@ func TestMsgServer_UndelegateFromGateway_SuccessfullyUndelegate(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, } @@ -113,7 +113,7 @@ func TestMsgServer_UndelegateFromGateway_FailNotDelegated(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, } @@ -174,7 +174,7 @@ func TestMsgServer_UndelegateFromGateway_SuccessfullyUndelegateFromUnstakedGatew Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, } diff --git a/x/application/keeper/msg_server_unstake_application_test.go b/x/application/keeper/msg_server_unstake_application_test.go index fece27bd0..4d23ff0a0 100644 --- a/x/application/keeper/msg_server_unstake_application_test.go +++ b/x/application/keeper/msg_server_unstake_application_test.go @@ -32,7 +32,7 @@ func TestMsgServer_UnstakeApplication_Success(t *testing.T) { Stake: &initialStake, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, } diff --git a/x/application/types/genesis_test.go b/x/application/types/genesis_test.go index 01dd9c174..3e7ce0361 100644 --- a/x/application/types/genesis_test.go +++ b/x/application/types/genesis_test.go @@ -15,13 +15,13 @@ func TestGenesisState_Validate(t *testing.T) { addr1 := sample.AccAddress() stake1 := sdk.NewCoin("upokt", sdk.NewInt(100)) svc1AppConfig := &sharedtypes.ApplicationServiceConfig{ - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, } addr2 := sample.AccAddress() stake2 := sdk.NewCoin("upokt", sdk.NewInt(100)) svc2AppConfig := &sharedtypes.ApplicationServiceConfig{ - ServiceId: &sharedtypes.ServiceId{Id: "svc2"}, + Service: &sharedtypes.Service{Id: "svc2"}, } emptyDelegatees := make([]string, 0) @@ -314,7 +314,7 @@ func TestGenesisState_Validate(t *testing.T) { Address: addr1, Stake: &stake1, ServiceConfigs: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{Id: "12345678901"}}, + {Service: &sharedtypes.Service{Id: "12345678901"}}, }, DelegateeGatewayAddresses: emptyDelegatees, }, @@ -333,7 +333,7 @@ func TestGenesisState_Validate(t *testing.T) { Address: addr1, Stake: &stake1, ServiceConfigs: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{ + {Service: &sharedtypes.Service{ Id: "123", Name: "abcdefghijklmnopqrstuvwxyzab-abcdefghijklmnopqrstuvwxyzab", }}, @@ -355,7 +355,7 @@ func TestGenesisState_Validate(t *testing.T) { Address: addr1, Stake: &stake1, ServiceConfigs: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{Id: "12 45 !"}}, + {Service: &sharedtypes.Service{Id: "12 45 !"}}, }, DelegateeGatewayAddresses: emptyDelegatees, }, diff --git a/x/application/types/message_stake_application.go b/x/application/types/message_stake_application.go index 9dba9eebf..a70a54815 100644 --- a/x/application/types/message_stake_application.go +++ b/x/application/types/message_stake_application.go @@ -23,7 +23,7 @@ func NewMsgStakeApplication( appServiceConfigs := make([]*sharedtypes.ApplicationServiceConfig, len(serviceIds)) for idx, serviceId := range serviceIds { appServiceConfigs[idx] = &sharedtypes.ApplicationServiceConfig{ - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: serviceId, }, } diff --git a/x/application/types/message_stake_application_test.go b/x/application/types/message_stake_application_test.go index c14119898..0d155f50c 100644 --- a/x/application/types/message_stake_application_test.go +++ b/x/application/types/message_stake_application_test.go @@ -23,7 +23,7 @@ func TestMsgStakeApplication_ValidateBasic(t *testing.T) { Address: "invalid_address", // Stake explicitly nil Services: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{Id: "svc1"}}, + {Service: &sharedtypes.Service{Id: "svc1"}}, }, }, err: ErrAppInvalidAddress, @@ -36,7 +36,7 @@ func TestMsgStakeApplication_ValidateBasic(t *testing.T) { Address: sample.AccAddress(), // Stake explicitly nil Services: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{Id: "svc1"}}, + {Service: &sharedtypes.Service{Id: "svc1"}}, }, }, err: ErrAppInvalidStake, @@ -46,7 +46,7 @@ func TestMsgStakeApplication_ValidateBasic(t *testing.T) { Address: sample.AccAddress(), Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{Id: "svc1"}}, + {Service: &sharedtypes.Service{Id: "svc1"}}, }, }, }, { @@ -55,7 +55,7 @@ func TestMsgStakeApplication_ValidateBasic(t *testing.T) { Address: sample.AccAddress(), Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(0)}, Services: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{Id: "svc1"}}, + {Service: &sharedtypes.Service{Id: "svc1"}}, }, }, err: ErrAppInvalidStake, @@ -65,7 +65,7 @@ func TestMsgStakeApplication_ValidateBasic(t *testing.T) { Address: sample.AccAddress(), Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(-100)}, Services: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{Id: "svc1"}}, + {Service: &sharedtypes.Service{Id: "svc1"}}, }, }, err: ErrAppInvalidStake, @@ -75,7 +75,7 @@ func TestMsgStakeApplication_ValidateBasic(t *testing.T) { Address: sample.AccAddress(), Stake: &sdk.Coin{Denom: "invalid", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{Id: "svc1"}}, + {Service: &sharedtypes.Service{Id: "svc1"}}, }, }, err: ErrAppInvalidStake, @@ -85,7 +85,7 @@ func TestMsgStakeApplication_ValidateBasic(t *testing.T) { Address: sample.AccAddress(), Stake: &sdk.Coin{Denom: "", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{Id: "svc1"}}, + {Service: &sharedtypes.Service{Id: "svc1"}}, }, }, err: ErrAppInvalidStake, @@ -98,8 +98,8 @@ func TestMsgStakeApplication_ValidateBasic(t *testing.T) { Address: sample.AccAddress(), Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{Id: "svc1"}}, - {ServiceId: &sharedtypes.ServiceId{Id: "svc2"}}, + {Service: &sharedtypes.Service{Id: "svc1"}}, + {Service: &sharedtypes.Service{Id: "svc2"}}, }, }, }, @@ -127,7 +127,7 @@ func TestMsgStakeApplication_ValidateBasic(t *testing.T) { Address: sample.AccAddress(), Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{Id: "123456790"}}, + {Service: &sharedtypes.Service{Id: "123456790"}}, }, }, err: ErrAppInvalidServiceConfigs, @@ -138,7 +138,7 @@ func TestMsgStakeApplication_ValidateBasic(t *testing.T) { Address: sample.AccAddress(), Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{ + {Service: &sharedtypes.Service{ Id: "123", Name: "abcdefghijklmnopqrstuvwxyzab-abcdefghijklmnopqrstuvwxyzab", }}, @@ -152,7 +152,7 @@ func TestMsgStakeApplication_ValidateBasic(t *testing.T) { Address: sample.AccAddress(), Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{Id: "12 45 !"}}, + {Service: &sharedtypes.Service{Id: "12 45 !"}}, }, }, err: ErrAppInvalidServiceConfigs, diff --git a/x/session/keeper/query_get_session.go b/x/session/keeper/query_get_session.go index d9fd3deaf..7a69e5a8c 100644 --- a/x/session/keeper/query_get_session.go +++ b/x/session/keeper/query_get_session.go @@ -17,7 +17,7 @@ func (k Keeper) GetSession(goCtx context.Context, req *types.QueryGetSessionRequ ctx := sdk.UnwrapSDKContext(goCtx) - sessionHydrator := NewSessionHydrator(req.ApplicationAddress, req.ServiceId.Id, req.BlockHeight) + sessionHydrator := NewSessionHydrator(req.ApplicationAddress, req.Service.Id, req.BlockHeight) session, err := k.HydrateSession(ctx, sessionHydrator) if err != nil { return nil, err diff --git a/x/session/keeper/query_get_session_test.go b/x/session/keeper/query_get_session_test.go index 5f15a94e9..ebf80b9cf 100644 --- a/x/session/keeper/query_get_session_test.go +++ b/x/session/keeper/query_get_session_test.go @@ -56,7 +56,7 @@ func TestSession_GetSession_Success(t *testing.T) { req := &types.QueryGetSessionRequest{ ApplicationAddress: tt.appAddr, - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: tt.serviceId, }, BlockHeight: 1, @@ -115,7 +115,7 @@ func TestSession_GetSession_Failure(t *testing.T) { req := &types.QueryGetSessionRequest{ ApplicationAddress: tt.appAddr, - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: tt.serviceId, }, BlockHeight: 1, diff --git a/x/session/keeper/session_hydrator.go b/x/session/keeper/session_hydrator.go index b6e78e2c8..310bc8c2c 100644 --- a/x/session/keeper/session_hydrator.go +++ b/x/session/keeper/session_hydrator.go @@ -107,8 +107,8 @@ func (k Keeper) hydrateSessionID(ctx sdk.Context, sh *sessionHydrator) error { // TODO_TECHDEBT: In the future, we will need to valid that the ServiceId is a valid service depending on whether // or not its permissioned or permissionless - // TODO(@Olshansk): Add a check to make sure `IsValidServiceName(ServiceId.Id)` returns True - serviceIdBz := []byte(sh.sessionHeader.ServiceId.Id) + // TODO(@Olshansk): Add a check to make sure `IsValidServiceName(Service.Id)` returns True + serviceIdBz := []byte(sh.sessionHeader.Service.Id) sessionHeightBz := make([]byte, 8) binary.LittleEndian.PutUint64(sessionHeightBz, uint64(sh.sessionHeader.SessionStartBlockHeight)) @@ -144,7 +144,7 @@ func (k Keeper) hydrateSessionSuppliers(ctx sdk.Context, sh *sessionHydrator) er for _, supplier := range suppliers { // TODO_OPTIMIZE: If `supplier.Services` was a map[string]struct{}, we could eliminate `slices.Contains()`'s loop for _, supplierServiceConfig := range supplier.Services { - if supplierServiceConfig.ServiceId.Id == sh.sessionHeader.ServiceId.Id { + if supplierServiceConfig.Service.Id == sh.sessionHeader.Service.Id { candidateSuppliers = append(candidateSuppliers, &supplier) break } diff --git a/x/session/keeper/session_hydrator_test.go b/x/session/keeper/session_hydrator_test.go index c50d653ab..505f56fad 100644 --- a/x/session/keeper/session_hydrator_test.go +++ b/x/session/keeper/session_hydrator_test.go @@ -22,8 +22,8 @@ func TestSession_HydrateSession_Success_BaseCase(t *testing.T) { // Check the header sessionHeader := session.Header require.Equal(t, keepertest.TestApp1Address, sessionHeader.ApplicationAddress) - require.Equal(t, keepertest.TestServiceId1, sessionHeader.ServiceId.Id) - require.Equal(t, "", sessionHeader.ServiceId.Name) + require.Equal(t, keepertest.TestServiceId1, sessionHeader.Service.Id) + require.Equal(t, "", sessionHeader.Service.Name) require.Equal(t, int64(8), sessionHeader.SessionStartBlockHeight) require.Equal(t, "23f037a10f9d51d020d27763c42dd391d7e71765016d95d0d61f36c4a122efd0", sessionHeader.SessionId) diff --git a/x/shared/helpers/service.go b/x/shared/helpers/service.go index 3a4e56e5f..760ee0162 100644 --- a/x/shared/helpers/service.go +++ b/x/shared/helpers/service.go @@ -3,6 +3,8 @@ package helpers import ( "net/url" "regexp" + + sharedtypes "github.com/pokt-network/poktroll/x/shared/types" ) const ( @@ -25,6 +27,11 @@ func init() { } +// IsValidService checks if the input service is valid +func IsValidService(service *sharedtypes.Service) bool { + return service != nil && IsValidServiceId(service.Id) && IsValidServiceName(service.Name) +} + // IsValidServiceId checks if the input string is a valid serviceId func IsValidServiceId(serviceId string) bool { // ServiceId CANNOT be empty diff --git a/x/shared/helpers/service_configs.go b/x/shared/helpers/service_configs.go index 6884da7ae..535f634be 100644 --- a/x/shared/helpers/service_configs.go +++ b/x/shared/helpers/service_configs.go @@ -15,17 +15,9 @@ func ValidateAppServiceConfigs(services []*sharedtypes.ApplicationServiceConfig) if serviceConfig == nil { return fmt.Errorf("serviceConfig cannot be nil: %v", services) } - if serviceConfig.ServiceId == nil { - return fmt.Errorf("serviceId cannot be nil: %v", serviceConfig) - } - if serviceConfig.ServiceId.Id == "" { - return fmt.Errorf("serviceId.Id cannot be empty: %v", serviceConfig) - } - if !IsValidServiceId(serviceConfig.ServiceId.Id) { - return fmt.Errorf("invalid serviceId.Id: %v", serviceConfig) - } - if !IsValidServiceName(serviceConfig.ServiceId.Name) { - return fmt.Errorf("invalid serviceId.Name: %v", serviceConfig) + // Check the Service + if IsValidService(serviceConfig.Service) { + return fmt.Errorf("invalid service: %v", serviceConfig.Service) } } return nil @@ -41,18 +33,9 @@ func ValidateSupplierServiceConfigs(services []*sharedtypes.SupplierServiceConfi return fmt.Errorf("serviceConfig cannot be nil: %v", services) } - // Check the ServiceId - if serviceConfig.ServiceId == nil { - return fmt.Errorf("serviceId cannot be nil: %v", serviceConfig) - } - if serviceConfig.ServiceId.Id == "" { - return fmt.Errorf("serviceId.Id cannot be empty: %v", serviceConfig) - } - if !IsValidServiceId(serviceConfig.ServiceId.Id) { - return fmt.Errorf("invalid serviceId.Id: %v", serviceConfig) - } - if !IsValidServiceName(serviceConfig.ServiceId.Name) { - return fmt.Errorf("invalid serviceId.Name: %v", serviceConfig) + // Check the Service + if IsValidService(serviceConfig.Service) { + return fmt.Errorf("invalid service: %v", serviceConfig.Service) } // Check the Endpoints diff --git a/x/shared/helpers/service_test.go b/x/shared/helpers/service_test.go index 335cbcd51..6cb7ff0c8 100644 --- a/x/shared/helpers/service_test.go +++ b/x/shared/helpers/service_test.go @@ -1,28 +1,130 @@ package helpers -import "testing" +import ( + "testing" + + sharedtypes "github.com/pokt-network/poktroll/x/shared/types" +) + +func TestIsValidService(t *testing.T) { + tests := []struct { + testCase string + id string + name string + expected bool + }{ + { + testCase: "Valid ID and Name", + id: "Service1", + name: "Valid Service Name", + expected: true, + }, + { + testCase: "Valid ID and empty Name", + id: "Srv", + name: "", // Valid because the service name can be empty + expected: true, + }, + { + testCase: "ID exceeds max length", + id: "TooLongId123", // Exceeds maxServiceIdLength + name: "Valid Name", + expected: false, + }, + { + testCase: "Name exceeds max length", + id: "ValidID", + name: "This service name is way too long to be considered valid since it exceeds the max length", + expected: false, + }, + { + testCase: "Empty ID", + id: "", // Invalid because the service ID cannot be empty + name: "Valid Name", + expected: false, + }, + { + testCase: "Invalid characters in ID", + id: "ID@Invalid", // Invalid character '@' + name: "Valid Name", + expected: false, + }, + } + + for _, test := range tests { + t.Run(test.testCase, func(t *testing.T) { + service := &sharedtypes.Service{ + Id: test.id, + Name: test.name, + } + result := IsValidService(service) + if result != test.expected { + t.Errorf("Test Case '%s' - IsValidService() with Id: '%s', Name: '%s', expected %v, got %v", + test.testCase, test.id, test.name, test.expected, result) + } + }) + } +} func TestIsValidServiceId(t *testing.T) { tests := []struct { + testCase string input string expected bool }{ - {"Hello-1", true}, - {"Hello_2", true}, - {"hello-world", false}, // exceeds maxServiceIdLength - {"Hello@", false}, // contains invalid character '@' - {"HELLO", true}, - {"12345678", true}, // exactly maxServiceIdLength - {"123456789", false}, // exceeds maxServiceIdLength - {"Hello.World", false}, // contains invalid character '.' - {"", false}, // empty string + { + testCase: "Valid alphanumeric with hyphen", + input: "Hello-1", + expected: true, + }, + { + testCase: "Valid alphanumeric with underscore", + input: "Hello_2", + expected: true, + }, + { + testCase: "Exceeds maximum length", + input: "hello-world", + expected: false, // exceeds maxServiceIdLength + }, + { + testCase: "Contains invalid character '@'", + input: "Hello@", + expected: false, // contains invalid character '@' + }, + { + testCase: "All uppercase", + input: "HELLO", + expected: true, + }, + { + testCase: "Maximum length boundary", + input: "12345678", + expected: true, // exactly maxServiceIdLength + }, + { + testCase: "Above maximum length boundary", + input: "123456789", + expected: false, // exceeds maxServiceIdLength + }, + { + testCase: "Contains invalid character '.'", + input: "Hello.World", + expected: false, // contains invalid character '.' + }, + { + testCase: "Empty string", + input: "", + expected: false, // empty string + }, } for _, test := range tests { - t.Run(test.input, func(t *testing.T) { + t.Run(test.testCase, func(t *testing.T) { result := IsValidServiceId(test.input) if result != test.expected { - t.Errorf("For input %s, expected %v but got %v", test.input, test.expected, result) + t.Errorf("Test Case '%s' - IsValidServiceId(%q) = %v, want %v", + test.testCase, test.input, result, test.expected) } }) } @@ -30,49 +132,50 @@ func TestIsValidServiceId(t *testing.T) { func TestIsValidEndpointUrl(t *testing.T) { tests := []struct { - name string + testCase string + input string expected bool }{ { - name: "valid http URL", + testCase: "valid http URL", input: "http://example.com", expected: true, }, { - name: "valid https URL", + testCase: "valid https URL", input: "https://example.com/path?query=value#fragment", expected: true, }, { - name: "valid localhost URL with scheme", + testCase: "valid localhost URL with scheme", input: "https://localhost:8081", expected: true, }, { - name: "valid loopback URL with scheme", + testCase: "valid loopback URL with scheme", input: "http://127.0.0.1:8081", expected: true, }, { - name: "invalid scheme", + testCase: "invalid scheme", input: "ftp://example.com", expected: false, }, { - name: "missing scheme", + testCase: "missing scheme", input: "example.com", expected: false, }, { - name: "invalid URL", + testCase: "invalid URL", input: "not-a-valid-url", expected: false, }, } for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + t.Run(tt.testCase, func(t *testing.T) { got := IsValidEndpointUrl(tt.input) if got != tt.expected { t.Errorf("IsValidEndpointUrl(%q) = %v, want %v", tt.input, got, tt.expected) diff --git a/x/supplier/client/cli/tx_stake_supplier.go b/x/supplier/client/cli/tx_stake_supplier.go index f223799cf..0d65045c8 100644 --- a/x/supplier/client/cli/tx_stake_supplier.go +++ b/x/supplier/client/cli/tx_stake_supplier.go @@ -84,7 +84,7 @@ func hackStringToServices(servicesArg string) ([]*sharedtypes.SupplierServiceCon return nil, fmt.Errorf("invalid service string: %s. Expected it to be of the form 'service;url'", serviceString) } service := &sharedtypes.SupplierServiceConfig{ - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: serviceParts[0], }, Endpoints: []*sharedtypes.SupplierEndpoint{ diff --git a/x/supplier/genesis_test.go b/x/supplier/genesis_test.go index b6af0545c..ba9b521f8 100644 --- a/x/supplier/genesis_test.go +++ b/x/supplier/genesis_test.go @@ -24,7 +24,7 @@ func TestGenesis(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId1", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -42,7 +42,7 @@ func TestGenesis(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId2", }, Endpoints: []*sharedtypes.SupplierEndpoint{ diff --git a/x/supplier/keeper/msg_server_stake_supplier_test.go b/x/supplier/keeper/msg_server_stake_supplier_test.go index 7ac0ee031..cd6158a81 100644 --- a/x/supplier/keeper/msg_server_stake_supplier_test.go +++ b/x/supplier/keeper/msg_server_stake_supplier_test.go @@ -31,7 +31,7 @@ func TestMsgServer_StakeSupplier_SuccessfulCreateAndUpdate(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -55,7 +55,7 @@ func TestMsgServer_StakeSupplier_SuccessfulCreateAndUpdate(t *testing.T) { require.Equal(t, addr, supplierFound.Address) require.Equal(t, int64(100), supplierFound.Stake.Amount.Int64()) require.Len(t, supplierFound.Services, 1) - require.Equal(t, "svcId", supplierFound.Services[0].ServiceId.Id) + require.Equal(t, "svcId", supplierFound.Services[0].Service.Id) require.Len(t, supplierFound.Services[0].Endpoints, 1) require.Equal(t, "http://localhost:8080", supplierFound.Services[0].Endpoints[0].Url) @@ -65,7 +65,7 @@ func TestMsgServer_StakeSupplier_SuccessfulCreateAndUpdate(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(200)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId2", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -86,7 +86,7 @@ func TestMsgServer_StakeSupplier_SuccessfulCreateAndUpdate(t *testing.T) { require.True(t, isSupplierFound) require.Equal(t, int64(200), supplierFound.Stake.Amount.Int64()) require.Len(t, supplierFound.Services, 1) - require.Equal(t, "svcId2", supplierFound.Services[0].ServiceId.Id) + require.Equal(t, "svcId2", supplierFound.Services[0].Service.Id) require.Len(t, supplierFound.Services[0].Endpoints, 1) require.Equal(t, "http://localhost:8082", supplierFound.Services[0].Endpoints[0].Url) } @@ -104,7 +104,7 @@ func TestMsgServer_StakeSupplier_FailRestakingDueToInvalidServices(t *testing.T) Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -128,7 +128,7 @@ func TestMsgServer_StakeSupplier_FailRestakingDueToInvalidServices(t *testing.T) Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svcId"}, + Service: &sharedtypes.Service{Id: "svcId"}, Endpoints: []*sharedtypes.SupplierEndpoint{}, }, }, @@ -143,7 +143,7 @@ func TestMsgServer_StakeSupplier_FailRestakingDueToInvalidServices(t *testing.T) require.True(t, isSupplierFound) require.Equal(t, supplierAddr, supplierFound.Address) require.Len(t, supplierFound.Services, 1) - require.Equal(t, "svcId", supplierFound.Services[0].ServiceId.Id) + require.Equal(t, "svcId", supplierFound.Services[0].Service.Id) require.Len(t, supplierFound.Services[0].Endpoints, 1) require.Equal(t, "http://localhost:8080", supplierFound.Services[0].Endpoints[0].Url) @@ -153,7 +153,7 @@ func TestMsgServer_StakeSupplier_FailRestakingDueToInvalidServices(t *testing.T) Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1 INVALID ! & *"}, + Service: &sharedtypes.Service{Id: "svc1 INVALID ! & *"}, }, }, } @@ -167,7 +167,7 @@ func TestMsgServer_StakeSupplier_FailRestakingDueToInvalidServices(t *testing.T) require.True(t, isSupplierFound) require.Equal(t, supplierAddr, supplierFound.Address) require.Len(t, supplierFound.Services, 1) - require.Equal(t, "svcId", supplierFound.Services[0].ServiceId.Id) + require.Equal(t, "svcId", supplierFound.Services[0].Service.Id) require.Len(t, supplierFound.Services[0].Endpoints, 1) require.Equal(t, "http://localhost:8080", supplierFound.Services[0].Endpoints[0].Url) } @@ -184,7 +184,7 @@ func TestMsgServer_StakeSupplier_FailLoweringStake(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -210,7 +210,7 @@ func TestMsgServer_StakeSupplier_FailLoweringStake(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(50)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId", }, Endpoints: []*sharedtypes.SupplierEndpoint{ diff --git a/x/supplier/keeper/msg_server_unstake_supplier_test.go b/x/supplier/keeper/msg_server_unstake_supplier_test.go index 163cbe5ae..993201971 100644 --- a/x/supplier/keeper/msg_server_unstake_supplier_test.go +++ b/x/supplier/keeper/msg_server_unstake_supplier_test.go @@ -32,7 +32,7 @@ func TestMsgServer_UnstakeSupplier_Success(t *testing.T) { Stake: &initialStake, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId", }, Endpoints: []*sharedtypes.SupplierEndpoint{ diff --git a/x/supplier/keeper/supplier_test.go b/x/supplier/keeper/supplier_test.go index c02c5758e..e7b901489 100644 --- a/x/supplier/keeper/supplier_test.go +++ b/x/supplier/keeper/supplier_test.go @@ -33,7 +33,7 @@ func createNSupplier(keeper *keeper.Keeper, ctx sdk.Context, n int) []sharedtype supplier.Stake = &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(int64(i))} supplier.Services = []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: fmt.Sprintf("svc%d", i)}, + Service: &sharedtypes.Service{Id: fmt.Sprintf("svc%d", i)}, Endpoints: []*sharedtypes.SupplierEndpoint{ { Url: fmt.Sprintf("http://localhost:%d", i), diff --git a/x/supplier/types/errors.go b/x/supplier/types/errors.go index 02a76a573..9403950e1 100644 --- a/x/supplier/types/errors.go +++ b/x/supplier/types/errors.go @@ -8,9 +8,11 @@ import ( // x/supplier module sentinel errors var ( - ErrSupplierInvalidStake = sdkerrors.Register(ModuleName, 1, "invalid supplier stake") - ErrSupplierInvalidAddress = sdkerrors.Register(ModuleName, 2, "invalid supplier address") - ErrSupplierUnauthorized = sdkerrors.Register(ModuleName, 3, "unauthorized supplier signer") - ErrSupplierNotFound = sdkerrors.Register(ModuleName, 4, "supplier not found") - ErrSupplierInvalidServiceConfig = sdkerrors.Register(ModuleName, 5, "invalid service config") + ErrSupplierInvalidStake = sdkerrors.Register(ModuleName, 1, "invalid supplier stake") + ErrSupplierInvalidAddress = sdkerrors.Register(ModuleName, 2, "invalid supplier address") + ErrSupplierUnauthorized = sdkerrors.Register(ModuleName, 3, "unauthorized supplier signer") + ErrSupplierNotFound = sdkerrors.Register(ModuleName, 4, "supplier not found") + ErrSupplierInvalidServiceConfig = sdkerrors.Register(ModuleName, 5, "invalid service config") + ErrSupplierInvalidSessionStartHeight = sdkerrors.Register(ModuleName, 6, "invalid session start height") + ErrSupplierInvalidSessionId = sdkerrors.Register(ModuleName, 7, "invalid session ID") ) diff --git a/x/supplier/types/genesis_test.go b/x/supplier/types/genesis_test.go index ba806c98b..5625fa435 100644 --- a/x/supplier/types/genesis_test.go +++ b/x/supplier/types/genesis_test.go @@ -15,7 +15,7 @@ func TestGenesisState_Validate(t *testing.T) { addr1 := sample.AccAddress() stake1 := sdk.NewCoin("upokt", sdk.NewInt(100)) serviceConfig1 := &sharedtypes.SupplierServiceConfig{ - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId1", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -31,7 +31,7 @@ func TestGenesisState_Validate(t *testing.T) { addr2 := sample.AccAddress() stake2 := sdk.NewCoin("upokt", sdk.NewInt(100)) serviceConfig2 := &sharedtypes.SupplierServiceConfig{ - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId2", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -250,7 +250,7 @@ func TestGenesisState_Validate(t *testing.T) { Stake: &stake2, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId1", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -281,7 +281,7 @@ func TestGenesisState_Validate(t *testing.T) { Stake: &stake2, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId1", }, Endpoints: []*sharedtypes.SupplierEndpoint{ diff --git a/x/supplier/types/message_stake_supplier_test.go b/x/supplier/types/message_stake_supplier_test.go index 57ce3e4fd..158fd8ff3 100644 --- a/x/supplier/types/message_stake_supplier_test.go +++ b/x/supplier/types/message_stake_supplier_test.go @@ -17,7 +17,7 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { defaultServicesList := []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId1", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -103,7 +103,7 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId1", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -115,7 +115,7 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { }, }, { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId2", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -154,7 +154,7 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "123456790", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -176,7 +176,7 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "123", Name: "abcdefghijklmnopqrstuvwxyzab-abcdefghijklmnopqrstuvwxyzab", }, @@ -199,7 +199,7 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "12 45 !", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -221,7 +221,7 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId", Name: "name", }, @@ -244,7 +244,7 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId", Name: "name", }, @@ -267,7 +267,7 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId", Name: "name", }, From 60f0fcd8af78421737d526bbfb793807655571e0 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Mon, 6 Nov 2023 14:27:24 -0800 Subject: [PATCH 02/27] Fix tests --- x/session/keeper/session_hydrator.go | 4 ++-- x/shared/helpers/service_configs.go | 4 ++-- x/supplier/types/genesis.go | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/x/session/keeper/session_hydrator.go b/x/session/keeper/session_hydrator.go index 310bc8c2c..630be57ac 100644 --- a/x/session/keeper/session_hydrator.go +++ b/x/session/keeper/session_hydrator.go @@ -45,7 +45,7 @@ func NewSessionHydrator( ) *sessionHydrator { sessionHeader := &types.SessionHeader{ ApplicationAddress: appAddress, - ServiceId: &sharedtypes.ServiceId{Id: serviceId}, + Service: &sharedtypes.Service{Id: serviceId}, } return &sessionHydrator{ sessionHeader: sessionHeader, @@ -153,7 +153,7 @@ func (k Keeper) hydrateSessionSuppliers(ctx sdk.Context, sh *sessionHydrator) er if len(candidateSuppliers) == 0 { logger.Error("[ERROR] no suppliers found for session") - return sdkerrors.Wrapf(types.ErrSuppliersNotFound, "could not find suppliers for service %s at height %d", sh.sessionHeader.ServiceId, sh.sessionHeader.SessionStartBlockHeight) + return sdkerrors.Wrapf(types.ErrSuppliersNotFound, "could not find suppliers for service %s at height %d", sh.sessionHeader.Service, sh.sessionHeader.SessionStartBlockHeight) } if len(candidateSuppliers) < NumSupplierPerSession { diff --git a/x/shared/helpers/service_configs.go b/x/shared/helpers/service_configs.go index 535f634be..95335d058 100644 --- a/x/shared/helpers/service_configs.go +++ b/x/shared/helpers/service_configs.go @@ -16,7 +16,7 @@ func ValidateAppServiceConfigs(services []*sharedtypes.ApplicationServiceConfig) return fmt.Errorf("serviceConfig cannot be nil: %v", services) } // Check the Service - if IsValidService(serviceConfig.Service) { + if !IsValidService(serviceConfig.Service) { return fmt.Errorf("invalid service: %v", serviceConfig.Service) } } @@ -34,7 +34,7 @@ func ValidateSupplierServiceConfigs(services []*sharedtypes.SupplierServiceConfi } // Check the Service - if IsValidService(serviceConfig.Service) { + if !IsValidService(serviceConfig.Service) { return fmt.Errorf("invalid service: %v", serviceConfig.Service) } diff --git a/x/supplier/types/genesis.go b/x/supplier/types/genesis.go index 2f4ff1872..b68751dcd 100644 --- a/x/supplier/types/genesis.go +++ b/x/supplier/types/genesis.go @@ -56,7 +56,6 @@ func (gs GenesisState) Validate() error { return sdkerrors.Wrapf(ErrSupplierInvalidStake, "invalid stake amount denom for supplier %v", supplier.Stake) } - // Valid the application service configs // Validate the application service configs if err := servicehelpers.ValidateSupplierServiceConfigs(supplier.Services); err != nil { return sdkerrors.Wrapf(ErrSupplierInvalidServiceConfig, err.Error()) From 2b95bc48e7eb8651701937a2fbf4231762e8e96b Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Mon, 6 Nov 2023 14:31:29 -0800 Subject: [PATCH 03/27] Rename renaming components --- docs/static/openapi.yml | 166 +++++++++++++++++++++------ go.mod | 4 +- pkg/relayer/proxy/interface.go | 2 +- pkg/relayer/proxy/jsonrpc.go | 14 +-- pkg/relayer/proxy/server_builder.go | 8 +- proto/pocket/shared/service.proto | 7 +- x/session/keeper/session_hydrator.go | 2 +- x/supplier/keeper/supplier.go | 2 +- 8 files changed, 149 insertions(+), 56 deletions(-) diff --git a/docs/static/openapi.yml b/docs/static/openapi.yml index 687106365..8013d6531 100644 --- a/docs/static/openapi.yml +++ b/docs/static/openapi.yml @@ -46477,7 +46477,7 @@ paths: items: type: object properties: - service_id: + service: title: Unique and semantic identifier for the service type: object properties: @@ -46504,7 +46504,9 @@ paths: title: >- ApplicationServiceConfig holds the service configuration the application stakes for - title: The ID of the service this session is servicing + title: >- + The list of services this appliccation is configured to + request service for delegatee_gateway_addresses: type: array items: @@ -46659,7 +46661,7 @@ paths: items: type: object properties: - service_id: + service: title: Unique and semantic identifier for the service type: object properties: @@ -46686,7 +46688,9 @@ paths: title: >- ApplicationServiceConfig holds the service configuration the application stakes for - title: The ID of the service this session is servicing + title: >- + The list of services this appliccation is configured to + request service for delegatee_gateway_addresses: type: array items: @@ -47090,8 +47094,8 @@ paths: title: >- The Bech32 address of the application using cosmos' ScalarDescriptor to ensure deterministic encoding - service_id: - title: The ID of the service this session is servicing + service: + title: The service this session is for type: object properties: id: @@ -47173,7 +47177,7 @@ paths: items: type: object properties: - service_id: + service: title: Unique and semantic identifier for the service type: object properties: @@ -47200,7 +47204,9 @@ paths: title: >- ApplicationServiceConfig holds the service configuration the application stakes for - title: The ID of the service this session is servicing + title: >- + The list of services this appliccation is configured + to request service for delegatee_gateway_addresses: type: array items: @@ -47240,7 +47246,7 @@ paths: items: type: object properties: - service_id: + service: title: Unique and semantic identifier for the service type: object properties: @@ -47363,7 +47369,7 @@ paths: in: query required: false type: string - - name: service_id.id + - name: service.id description: >- NOTE: `ServiceId.Id` may seem redundant but was desigtned created to enable more complex service identification @@ -47376,7 +47382,7 @@ paths: in: query required: false type: string - - name: service_id.name + - name: service.name description: >- TODO_TECHDEBT: Name is currently unused but acts as a reminder than an optional onchain representation of the service is necessary @@ -76616,7 +76622,7 @@ definitions: items: type: object properties: - service_id: + service: title: Unique and semantic identifier for the service type: object properties: @@ -76640,7 +76646,9 @@ definitions: title: >- ApplicationServiceConfig holds the service configuration the application stakes for - title: The ID of the service this session is servicing + title: >- + The list of services this appliccation is configured to request + service for delegatee_gateway_addresses: type: array items: @@ -76701,7 +76709,7 @@ definitions: items: type: object properties: - service_id: + service: title: Unique and semantic identifier for the service type: object properties: @@ -76728,7 +76736,9 @@ definitions: title: >- ApplicationServiceConfig holds the service configuration the application stakes for - title: The ID of the service this session is servicing + title: >- + The list of services this appliccation is configured to request + service for delegatee_gateway_addresses: type: array items: @@ -76797,7 +76807,7 @@ definitions: items: type: object properties: - service_id: + service: title: Unique and semantic identifier for the service type: object properties: @@ -76822,7 +76832,9 @@ definitions: title: >- ApplicationServiceConfig holds the service configuration the application stakes for - title: The ID of the service this session is servicing + title: >- + The list of services this appliccation is configured to request + service for delegatee_gateway_addresses: type: array items: @@ -76850,7 +76862,7 @@ definitions: pocket.shared.ApplicationServiceConfig: type: object properties: - service_id: + service: title: Unique and semantic identifier for the service type: object properties: @@ -76873,7 +76885,7 @@ definitions: title: >- ApplicationServiceConfig holds the service configuration the application stakes for - pocket.shared.ServiceId: + pocket.shared.Service: type: object properties: id: @@ -76892,7 +76904,7 @@ definitions: TODO_TECHDEBT: Name is currently unused but acts as a reminder than an optional onchain representation of the service is necessary title: >- - ServiceId message to encapsulate unique and semantic identifiers for a + Service message to encapsulate unique and semantic identifiers for a service on the network pocket.gateway.Gateway: type: object @@ -77043,8 +77055,8 @@ definitions: title: >- The Bech32 address of the application using cosmos' ScalarDescriptor to ensure deterministic encoding - service_id: - title: The ID of the service this session is servicing + service: + title: The service this session is for type: object properties: id: @@ -77121,7 +77133,7 @@ definitions: items: type: object properties: - service_id: + service: title: Unique and semantic identifier for the service type: object properties: @@ -77148,7 +77160,9 @@ definitions: title: >- ApplicationServiceConfig holds the service configuration the application stakes for - title: The ID of the service this session is servicing + title: >- + The list of services this appliccation is configured to + request service for delegatee_gateway_addresses: type: array items: @@ -77187,7 +77201,7 @@ definitions: items: type: object properties: - service_id: + service: title: Unique and semantic identifier for the service type: object properties: @@ -77300,8 +77314,8 @@ definitions: title: >- The Bech32 address of the application using cosmos' ScalarDescriptor to ensure deterministic encoding - service_id: - title: The ID of the service this session is servicing + service: + title: The service this session is for type: object properties: id: @@ -77378,7 +77392,7 @@ definitions: items: type: object properties: - service_id: + service: title: Unique and semantic identifier for the service type: object properties: @@ -77403,7 +77417,9 @@ definitions: title: >- ApplicationServiceConfig holds the service configuration the application stakes for - title: The ID of the service this session is servicing + title: >- + The list of services this appliccation is configured to request + service for delegatee_gateway_addresses: type: array items: @@ -77442,7 +77458,7 @@ definitions: items: type: object properties: - service_id: + service: title: Unique and semantic identifier for the service type: object properties: @@ -77542,8 +77558,8 @@ definitions: title: >- The Bech32 address of the application using cosmos' ScalarDescriptor to ensure deterministic encoding - service_id: - title: The ID of the service this session is servicing + service: + title: The service this session is for type: object properties: id: @@ -77572,6 +77588,26 @@ definitions: title: >- NOTE: session_id can be derived from the above values using on-chain but is included in the header for convenience + service_id: + title: The ID of the service this session is servicing + type: object + properties: + id: + type: string + description: Unique identifier for the service + title: >- + NOTE: `ServiceId.Id` may seem redundant but was desigtned created + to enable more complex service identification + + For example, what if we want to request a session for a certain + service but with some additional configs that identify it? + name: + type: string + description: (Optional) Semantic human readable name for the service + title: >- + TODO_TECHDEBT: Name is currently unused but acts as a reminder + than an optional onchain representation of the service is + necessary description: >- SessionHeader is a lightweight header for a session that can be passed around. @@ -77657,7 +77693,7 @@ definitions: items: type: object properties: - service_id: + service: title: Unique and semantic identifier for the service type: object properties: @@ -77730,6 +77766,27 @@ definitions: title: Additional configuration options for the endpoint title: SupplierEndpoint message to hold service configuration details title: List of endpoints for the service + service_id: + title: Unique and semantic identifier for the service + type: object + properties: + id: + type: string + description: Unique identifier for the service + title: >- + NOTE: `ServiceId.Id` may seem redundant but was desigtned + created to enable more complex service identification + + For example, what if we want to request a session for a + certain service but with some additional configs that + identify it? + name: + type: string + description: (Optional) Semantic human readable name for the service + title: >- + TODO_TECHDEBT: Name is currently unused but acts as a + reminder than an optional onchain representation of the + service is necessary title: >- SupplierServiceConfig holds the service configuration the supplier stakes for @@ -77789,7 +77846,7 @@ definitions: pocket.shared.SupplierServiceConfig: type: object properties: - service_id: + service: title: Unique and semantic identifier for the service type: object properties: @@ -77861,9 +77918,50 @@ definitions: title: Additional configuration options for the endpoint title: SupplierEndpoint message to hold service configuration details title: List of endpoints for the service + service_id: + title: Unique and semantic identifier for the service + type: object + properties: + id: + type: string + description: Unique identifier for the service + title: >- + NOTE: `ServiceId.Id` may seem redundant but was desigtned created + to enable more complex service identification + + For example, what if we want to request a session for a certain + service but with some additional configs that identify it? + name: + type: string + description: (Optional) Semantic human readable name for the service + title: >- + TODO_TECHDEBT: Name is currently unused but acts as a reminder + than an optional onchain representation of the service is + necessary title: >- SupplierServiceConfig holds the service configuration the supplier stakes for + pocket.shared.ServiceId: + type: object + properties: + id: + type: string + description: Unique identifier for the service + title: >- + NOTE: `ServiceId.Id` may seem redundant but was desigtned created to + enable more complex service identification + + For example, what if we want to request a session for a certain + service but with some additional configs that identify it? + name: + type: string + description: (Optional) Semantic human readable name for the service + title: >- + TODO_TECHDEBT: Name is currently unused but acts as a reminder than an + optional onchain representation of the service is necessary + title: >- + ServiceId message to encapsulate unique and semantic identifiers for a + service on the network pocket.supplier.MsgCreateClaimResponse: type: object pocket.supplier.MsgStakeSupplierResponse: diff --git a/go.mod b/go.mod index 4b5bdd434..95c298124 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( cosmossdk.io/math v1.0.1 github.com/cometbft/cometbft v0.37.2 github.com/cometbft/cometbft-db v0.8.0 + github.com/cosmos/cosmos-proto v1.0.0-beta.2 github.com/cosmos/cosmos-sdk v0.47.3 github.com/cosmos/gogoproto v1.4.10 github.com/cosmos/ibc-go/v7 v7.1.0 @@ -26,6 +27,7 @@ require ( go.uber.org/multierr v1.11.0 golang.org/x/crypto v0.12.0 golang.org/x/sync v0.3.0 + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 google.golang.org/grpc v1.56.1 gopkg.in/yaml.v2 v2.4.0 ) @@ -69,7 +71,6 @@ require ( github.com/containerd/cgroups v1.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect - github.com/cosmos/cosmos-proto v1.0.0-beta.2 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect github.com/cosmos/iavl v0.20.0 // indirect @@ -265,7 +266,6 @@ require ( gonum.org/v1/gonum v0.11.0 // indirect google.golang.org/api v0.122.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/pkg/relayer/proxy/interface.go b/pkg/relayer/proxy/interface.go index e9057f663..69994e015 100644 --- a/pkg/relayer/proxy/interface.go +++ b/pkg/relayer/proxy/interface.go @@ -41,6 +41,6 @@ type RelayServer interface { // Stop terminates the service server and returns an error if it fails. Stop(ctx context.Context) error - // ServiceId returns the serviceId of the service. + // Service returns the serviceId of the service. Service() *sharedtypes.Service } diff --git a/pkg/relayer/proxy/jsonrpc.go b/pkg/relayer/proxy/jsonrpc.go index ac9295c6b..bbfce740e 100644 --- a/pkg/relayer/proxy/jsonrpc.go +++ b/pkg/relayer/proxy/jsonrpc.go @@ -12,8 +12,8 @@ import ( var _ RelayServer = (*jsonRPCServer)(nil) type jsonRPCServer struct { - // serviceId is the id of the service that the server is responsible for. - serviceId *sharedtypes.ServiceId + // service is the id of the service that the server is responsible for. + service *sharedtypes.Service // serverEndpoint is the advertised endpoint configuration that the server uses to // listen for incoming relay requests. @@ -38,14 +38,14 @@ type jsonRPCServer struct { // It takes the serviceId, endpointUrl, and the main RelayerProxy as arguments and returns // a RelayServer that listens to incoming RelayRequests func NewJSONRPCServer( - serviceId *sharedtypes.ServiceId, + service *sharedtypes.Service, supplierEndpoint *sharedtypes.SupplierEndpoint, proxiedServiceEndpoint url.URL, servedRelaysProducer chan<- *types.Relay, proxy RelayerProxy, ) RelayServer { return &jsonRPCServer{ - serviceId: serviceId, + service: service, serverEndpoint: supplierEndpoint, server: &http.Server{Addr: supplierEndpoint.Url}, relayerProxy: proxy, @@ -71,9 +71,9 @@ func (j *jsonRPCServer) Stop(ctx context.Context) error { return j.server.Shutdown(ctx) } -// ServiceId returns the serviceId of the JSON-RPC service. -func (j *jsonRPCServer) ServiceId() *sharedtypes.ServiceId { - return j.serviceId +// Service returns the serviceId of the JSON-RPC service. +func (j *jsonRPCServer) Service() *sharedtypes.Service { + return j.service } // ServeHTTP listens for incoming relay requests. It implements the respective diff --git a/pkg/relayer/proxy/server_builder.go b/pkg/relayer/proxy/server_builder.go index eb21cc1f2..53e0c4d14 100644 --- a/pkg/relayer/proxy/server_builder.go +++ b/pkg/relayer/proxy/server_builder.go @@ -29,8 +29,8 @@ func (rp *relayerProxy) BuildProvidedServices(ctx context.Context) error { // Build the advertised relay servers map. For each service's endpoint, create the appropriate RelayServer. providedServices := make(relayServersMap) for _, serviceConfig := range services { - serviceId := serviceConfig.ServiceId - proxiedServicesEndpoints := rp.proxiedServicesEndpoints[serviceId.Id] + service := serviceConfig.Service + proxiedServicesEndpoints := rp.proxiedServicesEndpoints[service.Id] serviceEndpoints := make([]RelayServer, len(serviceConfig.Endpoints)) for _, endpoint := range serviceConfig.Endpoints { @@ -40,7 +40,7 @@ func (rp *relayerProxy) BuildProvidedServices(ctx context.Context) error { switch endpoint.RpcType { case sharedtypes.RPCType_JSON_RPC: server = NewJSONRPCServer( - serviceId, + service, endpoint, proxiedServicesEndpoints, rp.servedRelaysProducer, @@ -53,7 +53,7 @@ func (rp *relayerProxy) BuildProvidedServices(ctx context.Context) error { serviceEndpoints = append(serviceEndpoints, server) } - providedServices[serviceId.Id] = serviceEndpoints + providedServices[service.Id] = serviceEndpoints } rp.advertisedRelayServers = providedServices diff --git a/proto/pocket/shared/service.proto b/proto/pocket/shared/service.proto index 8ae83ad4d..7036e3114 100644 --- a/proto/pocket/shared/service.proto +++ b/proto/pocket/shared/service.proto @@ -6,19 +6,14 @@ package pocket.shared; option go_package = "github.com/pokt-network/poktroll/x/shared/types"; -// TODO_CLEANUP(@Olshansk): Add native optional identifiers once its supported; https://github.com/ignite/cli/issues/3698 - // Service message to encapsulate unique and semantic identifiers for a service on the network message Service { - // NOTE: `ServiceId.Id` may seem redundant but was desigtned created to enable more complex service identification + // NOTE: `Service.Id` may seem redundant but was desigtned created to enable more complex service identification // For example, what if we want to request a session for a certain service but with some additional configs that identify it? string id = 1; // Unique identifier for the service // TODO_TECHDEBT: Name is currently unused but acts as a reminder than an optional onchain representation of the service is necessary string name = 2; // (Optional) Semantic human readable name for the service - - // NOTE: `ServiceId.Id` may seem redundant but was designed to enable more complex service identification. - // For example, what if we want to request a session for a certain service but with some additional configs that identify it? } // ApplicationServiceConfig holds the service configuration the application stakes for diff --git a/x/session/keeper/session_hydrator.go b/x/session/keeper/session_hydrator.go index 630be57ac..ed312f066 100644 --- a/x/session/keeper/session_hydrator.go +++ b/x/session/keeper/session_hydrator.go @@ -105,7 +105,7 @@ func (k Keeper) hydrateSessionID(ctx sdk.Context, sh *sessionHydrator) error { prevHashBz := []byte("TODO_BLOCKER: See the comment above") appPubKeyBz := []byte(sh.sessionHeader.ApplicationAddress) - // TODO_TECHDEBT: In the future, we will need to valid that the ServiceId is a valid service depending on whether + // TODO_TECHDEBT: In the future, we will need to valid that the Service is a valid service depending on whether // or not its permissioned or permissionless // TODO(@Olshansk): Add a check to make sure `IsValidServiceName(Service.Id)` returns True serviceIdBz := []byte(sh.sessionHeader.Service.Id) diff --git a/x/supplier/keeper/supplier.go b/x/supplier/keeper/supplier.go index 0c31db2be..ba5d8f4fe 100644 --- a/x/supplier/keeper/supplier.go +++ b/x/supplier/keeper/supplier.go @@ -64,5 +64,5 @@ func (k Keeper) GetAllSupplier(ctx sdk.Context) (suppliers []sharedtypes.Supplie return } -// TODO_OPTIMIZE: Index suppliers by serviceId so we can easily query `k.GetAllSupplier(ctx, ServiceId)` +// TODO_OPTIMIZE: Index suppliers by serviceId so we can easily query `k.GetAllSupplier(ctx, Service)` // func (k Keeper) GetAllSupplier(ctx, sdkContext, serviceId string) (suppliers []sharedtypes.Supplier) {} From e8a50ad008341bf2924b043bf8249eb324b046bc Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Mon, 6 Nov 2023 14:50:59 -0800 Subject: [PATCH 04/27] Added unit tests for the claim --- x/supplier/types/errors.go | 2 + x/supplier/types/message_create_claim.go | 21 +++++ x/supplier/types/message_create_claim_test.go | 89 ++++++++++++++++--- 3 files changed, 101 insertions(+), 11 deletions(-) diff --git a/x/supplier/types/errors.go b/x/supplier/types/errors.go index 9403950e1..ceda3f96d 100644 --- a/x/supplier/types/errors.go +++ b/x/supplier/types/errors.go @@ -15,4 +15,6 @@ var ( ErrSupplierInvalidServiceConfig = sdkerrors.Register(ModuleName, 5, "invalid service config") ErrSupplierInvalidSessionStartHeight = sdkerrors.Register(ModuleName, 6, "invalid session start height") ErrSupplierInvalidSessionId = sdkerrors.Register(ModuleName, 7, "invalid session ID") + ErrSupplierInvalidService = sdkerrors.Register(ModuleName, 8, "invalid service") + ErrSupplierInvalidRootHash = sdkerrors.Register(ModuleName, 9, "invalid root hash") ) diff --git a/x/supplier/types/message_create_claim.go b/x/supplier/types/message_create_claim.go index 7d68c6a94..8a41e2ce7 100644 --- a/x/supplier/types/message_create_claim.go +++ b/x/supplier/types/message_create_claim.go @@ -5,6 +5,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sessiontypes "github.com/pokt-network/poktroll/x/session/types" + sharedhelpers "github.com/pokt-network/poktroll/x/shared/helpers" ) const TypeMsgCreateClaim = "create_claim" @@ -41,9 +42,29 @@ func (msg *MsgCreateClaim) GetSignBytes() []byte { } func (msg *MsgCreateClaim) ValidateBasic() error { + // Validate the supplier address _, err := sdk.AccAddressFromBech32(msg.SupplierAddress) if err != nil { return sdkerrors.Wrapf(ErrSupplierInvalidAddress, "invalid supplierAddress address (%s)", err) } + + // Validate the session header + sessionHeader := msg.SessionHeader + if sessionHeader.SessionStartBlockHeight < 1 { + return sdkerrors.Wrapf(ErrSupplierInvalidSessionStartHeight, "invalid session start block height (%d)", sessionHeader.SessionStartBlockHeight) + } + if len(sessionHeader.SessionId) == 0 { + return sdkerrors.Wrapf(ErrSupplierInvalidSessionId, "invalid session ID (%v)", sessionHeader.SessionId) + } + if !sharedhelpers.IsValidService(sessionHeader.Service) { + return sdkerrors.Wrapf(ErrSupplierInvalidService, "invalid service (%v)", sessionHeader.Service) + } + + // Validate the root hash + // TODO_IMPROVE: Only checking to make sure a non-nil hash was provided for now, but we can validate the length as well. + if len(msg.RootHash) == 0 { + return sdkerrors.Wrapf(ErrSupplierInvalidRootHash, "invalid root hash (%v)", msg.RootHash) + } + return nil } diff --git a/x/supplier/types/message_create_claim_test.go b/x/supplier/types/message_create_claim_test.go index 8401c0d3d..a78918a3c 100644 --- a/x/supplier/types/message_create_claim_test.go +++ b/x/supplier/types/message_create_claim_test.go @@ -6,37 +6,104 @@ import ( "github.com/stretchr/testify/require" "github.com/pokt-network/poktroll/testutil/sample" + sessiontypes "github.com/pokt-network/poktroll/x/session/types" + sharedtypes "github.com/pokt-network/poktroll/x/shared/types" ) -// TODO(@bryanchriswhite): Add unit tests for message validation when adding the business logic. - func TestMsgCreateClaim_ValidateBasic(t *testing.T) { tests := []struct { - name string - msg MsgCreateClaim - err error + testCase string + + msg MsgCreateClaim + err error }{ { - name: "invalid address", + testCase: "invalid address", + msg: MsgCreateClaim{ SupplierAddress: "invalid_address", }, err: ErrSupplierInvalidAddress, - }, { - name: "valid address", + }, + { + testCase: "valid address but invalid session start height", + + msg: MsgCreateClaim{ + SupplierAddress: sample.AccAddress(), + SessionHeader: &sessiontypes.SessionHeader{ + SessionStartBlockHeight: 0, // Invalid start height + }, + }, + err: ErrSupplierInvalidSessionStartHeight, + }, + { + testCase: "valid address and session start height but invalid session ID", + + msg: MsgCreateClaim{ + SupplierAddress: sample.AccAddress(), + SessionHeader: &sessiontypes.SessionHeader{ + SessionStartBlockHeight: 100, + SessionId: "", // Invalid session ID + }, + }, + err: ErrSupplierInvalidSessionId, + }, + { + testCase: "valid address, session start height, session ID but invalid service", + + msg: MsgCreateClaim{ + SupplierAddress: sample.AccAddress(), + SessionHeader: &sessiontypes.SessionHeader{ + SessionStartBlockHeight: 100, + SessionId: "valid_session_id", + Service: &sharedtypes.Service{ + Id: "invalid_service_id", // Assuming this ID is invalid + }, // Should trigger error + }, + }, + err: ErrSupplierInvalidService, + }, + { + testCase: "valid address, session start height, session ID, service but invalid root hash", + + msg: MsgCreateClaim{ + SupplierAddress: sample.AccAddress(), + SessionHeader: &sessiontypes.SessionHeader{ + SessionStartBlockHeight: 100, + SessionId: "valid_session_id", + Service: &sharedtypes.Service{ + Id: "svcId", // Assuming this ID is valid + }, + }, + RootHash: []byte(""), // Invalid root hash + }, + err: ErrSupplierInvalidRootHash, + }, + { + testCase: "all valid inputs", + msg: MsgCreateClaim{ SupplierAddress: sample.AccAddress(), + SessionHeader: &sessiontypes.SessionHeader{ + SessionStartBlockHeight: 100, + SessionId: "valid_session_id", + Service: &sharedtypes.Service{ + Id: "svcId", // Assuming this ID is valid + }, + }, + RootHash: []byte("valid_root_hash"), // Assuming this is valid }, + err: nil, }, } for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + t.Run(tt.testCase, func(t *testing.T) { err := tt.msg.ValidateBasic() if tt.err != nil { require.ErrorIs(t, err, tt.err) - return + } else { + require.NoError(t, err) } - require.NoError(t, err) }) } } From a54c90eab5d182b14ed9f4bd92699288cc94f3e4 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Tue, 7 Nov 2023 11:24:55 -0800 Subject: [PATCH 05/27] Update pkg/relayer/proxy/interface.go Co-authored-by: Bryan White --- pkg/relayer/proxy/interface.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/relayer/proxy/interface.go b/pkg/relayer/proxy/interface.go index 69994e015..df4232588 100644 --- a/pkg/relayer/proxy/interface.go +++ b/pkg/relayer/proxy/interface.go @@ -41,6 +41,6 @@ type RelayServer interface { // Stop terminates the service server and returns an error if it fails. Stop(ctx context.Context) error - // Service returns the serviceId of the service. + // Service returns the service to which the RelayServer relays. Service() *sharedtypes.Service } From cbd39ab174a4772d7f656131e5e6f6dbc5503dc7 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Tue, 7 Nov 2023 11:25:39 -0800 Subject: [PATCH 06/27] Update pkg/relayer/proxy/jsonrpc.go Co-authored-by: Bryan White --- pkg/relayer/proxy/jsonrpc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/relayer/proxy/jsonrpc.go b/pkg/relayer/proxy/jsonrpc.go index bbfce740e..874af3b64 100644 --- a/pkg/relayer/proxy/jsonrpc.go +++ b/pkg/relayer/proxy/jsonrpc.go @@ -71,7 +71,7 @@ func (j *jsonRPCServer) Stop(ctx context.Context) error { return j.server.Shutdown(ctx) } -// Service returns the serviceId of the JSON-RPC service. +// Service returns the JSON-RPC service. func (j *jsonRPCServer) Service() *sharedtypes.Service { return j.service } From a5e798656a4f92072ed98392f3dc46e6be4e25ef Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Tue, 7 Nov 2023 11:25:49 -0800 Subject: [PATCH 07/27] Update proto/pocket/shared/service.proto Co-authored-by: Bryan White --- proto/pocket/shared/service.proto | 1 - 1 file changed, 1 deletion(-) diff --git a/proto/pocket/shared/service.proto b/proto/pocket/shared/service.proto index 7036e3114..b7185188e 100644 --- a/proto/pocket/shared/service.proto +++ b/proto/pocket/shared/service.proto @@ -8,7 +8,6 @@ option go_package = "github.com/pokt-network/poktroll/x/shared/types"; // Service message to encapsulate unique and semantic identifiers for a service on the network message Service { - // NOTE: `Service.Id` may seem redundant but was desigtned created to enable more complex service identification // For example, what if we want to request a session for a certain service but with some additional configs that identify it? string id = 1; // Unique identifier for the service From 5ddb28e515b4b0ecacc31f8fbbcec82c8959c20d Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Tue, 7 Nov 2023 11:27:12 -0800 Subject: [PATCH 08/27] Update x/shared/helpers/service_test.go Co-authored-by: Bryan White --- x/shared/helpers/service_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/shared/helpers/service_test.go b/x/shared/helpers/service_test.go index 6cb7ff0c8..2a63dbb95 100644 --- a/x/shared/helpers/service_test.go +++ b/x/shared/helpers/service_test.go @@ -38,7 +38,7 @@ func TestIsValidService(t *testing.T) { expected: false, }, { - testCase: "Empty ID", + testCase: "Empty ID is invalid", id: "", // Invalid because the service ID cannot be empty name: "Valid Name", expected: false, From a33b42e687f1fd3503d2598652f10e69917d7b03 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Tue, 7 Nov 2023 11:27:26 -0800 Subject: [PATCH 09/27] Update x/supplier/keeper/supplier.go Co-authored-by: Bryan White --- x/supplier/keeper/supplier.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/supplier/keeper/supplier.go b/x/supplier/keeper/supplier.go index ba5d8f4fe..559ca4a97 100644 --- a/x/supplier/keeper/supplier.go +++ b/x/supplier/keeper/supplier.go @@ -64,5 +64,5 @@ func (k Keeper) GetAllSupplier(ctx sdk.Context) (suppliers []sharedtypes.Supplie return } -// TODO_OPTIMIZE: Index suppliers by serviceId so we can easily query `k.GetAllSupplier(ctx, Service)` +// TODO_OPTIMIZE: Index suppliers by service so we can easily query `k.GetAllSupplier(ctx, Service)` // func (k Keeper) GetAllSupplier(ctx, sdkContext, serviceId string) (suppliers []sharedtypes.Supplier) {} From 16c81452887b890e8e0e3136d903e980f6bf58ae Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Tue, 7 Nov 2023 11:30:56 -0800 Subject: [PATCH 10/27] Update pkg/relayer/proxy/jsonrpc.go Co-authored-by: Bryan White --- pkg/relayer/proxy/jsonrpc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/relayer/proxy/jsonrpc.go b/pkg/relayer/proxy/jsonrpc.go index 874af3b64..f6cc9cb22 100644 --- a/pkg/relayer/proxy/jsonrpc.go +++ b/pkg/relayer/proxy/jsonrpc.go @@ -12,7 +12,7 @@ import ( var _ RelayServer = (*jsonRPCServer)(nil) type jsonRPCServer struct { - // service is the id of the service that the server is responsible for. + // service is the service that the server is responsible for. service *sharedtypes.Service // serverEndpoint is the advertised endpoint configuration that the server uses to From d204d14d4a9679d476609b9281fd712f4c2e6a11 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Mon, 6 Nov 2023 21:27:23 +0100 Subject: [PATCH 11/27] fix: flaky block client test (#132) * fix: flakey block cliekt test * chore: simplify & react to review feedback * chore: add godoc comment * chore: simplify --- internal/testclient/testeventsquery/client.go | 43 ++++++ pkg/client/block/client.go | 2 +- pkg/client/block/client_test.go | 129 +++++++++--------- 3 files changed, 105 insertions(+), 69 deletions(-) diff --git a/internal/testclient/testeventsquery/client.go b/internal/testclient/testeventsquery/client.go index d55a765ab..0aa618fe9 100644 --- a/internal/testclient/testeventsquery/client.go +++ b/internal/testclient/testeventsquery/client.go @@ -1,11 +1,18 @@ package testeventsquery import ( + "context" "testing" + "time" + "github.com/golang/mock/gomock" + + "github.com/pokt-network/poktroll/internal/mocks/mockclient" "github.com/pokt-network/poktroll/internal/testclient" "github.com/pokt-network/poktroll/pkg/client" eventsquery "github.com/pokt-network/poktroll/pkg/client/events_query" + "github.com/pokt-network/poktroll/pkg/either" + "github.com/pokt-network/poktroll/pkg/observable/channel" ) // NewLocalnetClient returns a new events query client which is configured to @@ -15,3 +22,39 @@ func NewLocalnetClient(t *testing.T, opts ...client.EventsQueryClientOption) cli return eventsquery.NewEventsQueryClient(testclient.CometLocalWebsocketURL, opts...) } + +// NewAnyTimesEventsBytesEventsQueryClient returns a new events query client which +// is configured to return the expected event bytes when queried with the expected +// query, any number of times. The returned client also expects to be closed once. +func NewAnyTimesEventsBytesEventsQueryClient( + ctx context.Context, + t *testing.T, + expectedQuery string, + expectedEventBytes []byte, +) client.EventsQueryClient { + t.Helper() + + ctrl := gomock.NewController(t) + eventsQueryClient := mockclient.NewMockEventsQueryClient(ctrl) + eventsQueryClient.EXPECT().Close().Times(1) + eventsQueryClient.EXPECT(). + EventsBytes(gomock.AssignableToTypeOf(ctx), gomock.Eq(expectedQuery)). + DoAndReturn( + func(ctx context.Context, query string) (client.EventsBytesObservable, error) { + bytesObsvbl, bytesPublishCh := channel.NewReplayObservable[either.Bytes](ctx, 1) + + // Now that the observable is set up, publish the expected event bytes. + // Only need to send once because it's a ReplayObservable. + bytesPublishCh <- either.Success(expectedEventBytes) + + // Wait a tick for the observables to be set up. This isn't strictly + // necessary but is done to mitigate test flakiness. + time.Sleep(10 * time.Millisecond) + + return bytesObsvbl, nil + }, + ). + AnyTimes() + + return eventsQueryClient +} diff --git a/pkg/client/block/client.go b/pkg/client/block/client.go index 54569e60d..18526508d 100644 --- a/pkg/client/block/client.go +++ b/pkg/client/block/client.go @@ -155,7 +155,7 @@ func (bClient *blockClient) retryPublishBlocksFactory(ctx context.Context) func( } // NB: must cast back to generic observable type to use with Map. - // client.BlocksObservable is only used to workaround gomock's lack of + // client.BlocksObservable cannot be an alias due to gomock's lack of // support for generic types. eventsBz := observable.Observable[either.Either[[]byte]](eventsBzObsvbl) blockEventFromEventBz := newEventsBytesToBlockMapFn(errCh) diff --git a/pkg/client/block/client_test.go b/pkg/client/block/client_test.go index b983ff274..b2a5515b3 100644 --- a/pkg/client/block/client_test.go +++ b/pkg/client/block/client_test.go @@ -8,17 +8,20 @@ import ( "cosmossdk.io/depinject" comettypes "github.com/cometbft/cometbft/types" - "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" "github.com/pokt-network/poktroll/internal/testclient" "github.com/pokt-network/poktroll/internal/testclient/testeventsquery" "github.com/pokt-network/poktroll/pkg/client" "github.com/pokt-network/poktroll/pkg/client/block" - eventsquery "github.com/pokt-network/poktroll/pkg/client/events_query" ) -const blockAssertionLoopTimeout = 500 * time.Millisecond +const ( + testTimeoutDuration = 100 * time.Millisecond + + // duplicates pkg/client/block/client.go's committedBlocksQuery for testing purposes + committedBlocksQuery = "tm.event='NewBlock'" +) func TestBlockClient(t *testing.T) { var ( @@ -38,19 +41,15 @@ func TestBlockClient(t *testing.T) { ctx = context.Background() ) - // Set up a mock connection and dialer which are expected to be used once. - connMock, dialerMock := testeventsquery.NewOneTimeMockConnAndDialer(t) - connMock.EXPECT().Send(gomock.Any()).Return(nil).Times(1) - // Mock the Receive method to return the expected block event. - connMock.EXPECT().Receive().DoAndReturn(func() ([]byte, error) { - blockEventJson, err := json.Marshal(expectedBlockEvent) - require.NoError(t, err) - return blockEventJson, nil - }).AnyTimes() - - // Set up events query client dependency. - dialerOpt := eventsquery.WithDialer(dialerMock) - eventsQueryClient := testeventsquery.NewLocalnetClient(t, dialerOpt) + expectedEventBz, err := json.Marshal(expectedBlockEvent) + require.NoError(t, err) + + eventsQueryClient := testeventsquery.NewAnyTimesEventsBytesEventsQueryClient( + ctx, t, + committedBlocksQuery, + expectedEventBz, + ) + deps := depinject.Supply(eventsQueryClient) // Set up block client. @@ -58,60 +57,54 @@ func TestBlockClient(t *testing.T) { require.NoError(t, err) require.NotNil(t, blockClient) - // Run LatestBlock and CommittedBlockSequence concurrently because they can - // block, leading to an unresponsive test. This function sends multiple values - // on the actualBlockCh which are all asserted against in blockAssertionLoop. - // If any of the methods under test hang, the test will time out. - var ( - actualBlockCh = make(chan client.Block, 1) - done = make(chan struct{}, 1) - ) - go func() { - // Test LatestBlock method. - actualBlock := blockClient.LatestBlock(ctx) - require.Equal(t, expectedHeight, actualBlock.Height()) - require.Equal(t, expectedHash, actualBlock.Hash()) - - // Test CommittedBlockSequence method. - blockObservable := blockClient.CommittedBlocksSequence(ctx) - require.NotNil(t, blockObservable) - - // Ensure that the observable is replayable via Last. - actualBlockCh <- blockObservable.Last(ctx, 1)[0] - - // Ensure that the observable is replayable via Subscribe. - blockObserver := blockObservable.Subscribe(ctx) - for block := range blockObserver.Ch() { - actualBlockCh <- block - break - } - - // Signal test completion - done <- struct{}{} - }() - - // blockAssertionLoop ensures that the blocks retrieved from both LatestBlock - // method and CommittedBlocksSequence method match the expected block height - // and hash. This loop waits for blocks to be sent on the actualBlockCh channel - // by the methods being tested. Once the methods are done, they send a signal on - // the "done" channel. If the blockAssertionLoop doesn't receive any block or - // the done signal within a specific timeout, it assumes something has gone wrong - // and fails the test. -blockAssertionLoop: - for { - select { - case actualBlock := <-actualBlockCh: - require.Equal(t, expectedHeight, actualBlock.Height()) - require.Equal(t, expectedHash, actualBlock.Hash()) - case <-done: - break blockAssertionLoop - case <-time.After(blockAssertionLoopTimeout): - t.Fatal("timed out waiting for block event") - } + tests := []struct { + name string + fn func() client.Block + }{ + { + name: "LatestBlock successfully returns latest block", + fn: func() client.Block { + lastBlock := blockClient.LatestBlock(ctx) + return lastBlock + }, + }, + { + name: "CommittedBlocksSequence successfully returns latest block", + fn: func() client.Block { + blockObservable := blockClient.CommittedBlocksSequence(ctx) + require.NotNil(t, blockObservable) + + // Ensure that the observable is replayable via Last. + lastBlock := blockObservable.Last(ctx, 1)[0] + require.Equal(t, expectedHeight, lastBlock.Height()) + require.Equal(t, expectedHash, lastBlock.Hash()) + + return lastBlock + }, + }, } - // Wait a tick for the observables to be set up. - time.Sleep(time.Millisecond) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var actualBlockCh = make(chan client.Block, 10) + + // Run test functions asynchronously because they can block, leading + // to an unresponsive test. If any of the methods under test hang, + // the test will time out in the select statement that follows. + go func(fn func() client.Block) { + actualBlockCh <- fn() + close(actualBlockCh) + }(tt.fn) + + select { + case actualBlock := <-actualBlockCh: + require.Equal(t, expectedHeight, actualBlock.Height()) + require.Equal(t, expectedHash, actualBlock.Hash()) + case <-time.After(testTimeoutDuration): + t.Fatal("timed out waiting for block event") + } + }) + } blockClient.Close() } From 21edfe7058837d509be16509893346ba040c15cd Mon Sep 17 00:00:00 2001 From: Dima Kniazev Date: Mon, 6 Nov 2023 15:38:41 -0800 Subject: [PATCH 12/27] [CI] Build container images (#107) * wip - need info from GitHub CI * build image as a part of main ci * troublshoot w/o test * should be a cp here * wip * more label control * install directly from github * use wget * rerun ci * troubleshoot * more information * it was git context * kill previous run if a new commit is pushed * this should work * remove buildlog * resolve conflicts * perform the tests as well * we will be allright withoug keeping the bin dir * bring back ignite version * also build on mai * refine label actions * Update .github/workflows/go.yml Co-authored-by: Daniel Olshansky * add requested changes * pocketd has been replaced with poktrolld * only change the binary name for now, take care of other pocketd instances later * Update .github/label-actions.yml Co-authored-by: Daniel Olshansky * rename pocketd with poktrolld where necessary * typofix * also use poktrolld for e2e tests --------- Co-authored-by: Daniel Olshansky --- .github/label-actions.yml | 35 ++++++++++++ .github/workflows/go.yml | 54 ++++++++++++++++++- .github/workflows/label-actions.yml | 21 ++++++++ .gitignore | 6 ++- Dockerfile.dev | 23 ++++++++ Makefile | 37 ++++++++----- Tiltfile | 6 +-- e2e/tests/node.go | 2 +- .../client/cli/tx_delegate_to_gateway.go | 2 +- .../client/cli/tx_stake_application.go | 2 +- .../client/cli/tx_undelegate_from_gateway.go | 2 +- .../client/cli/tx_unstake_application.go | 2 +- x/gateway/client/cli/tx_stake_gateway.go | 2 +- x/gateway/client/cli/tx_unstake_gateway.go | 2 +- x/supplier/client/cli/tx_stake_supplier.go | 2 +- x/supplier/client/cli/tx_unstake_supplier.go | 2 +- 16 files changed, 171 insertions(+), 29 deletions(-) create mode 100644 .github/label-actions.yml create mode 100644 .github/workflows/label-actions.yml create mode 100644 Dockerfile.dev diff --git a/.github/label-actions.yml b/.github/label-actions.yml new file mode 100644 index 000000000..b4dc1814d --- /dev/null +++ b/.github/label-actions.yml @@ -0,0 +1,35 @@ +# When `devnet-e2e-test` is added, also assign `devnet` to the PR. +devnet-e2e-test: + prs: + comment: The CI will now also run the e2e tests on devnet, which increases the time it takes to complete all CI checks. + label: + - devnet + +# When `devnet-e2e-test` is removed, also delete `devnet` from the PR. +-devnet-e2e-test: + prs: + unlabel: + - devnet + +# When `devnet` is added, also assign `push-image` to the PR. +devnet: + prs: + label: + - push-image + +# When `devnet` is removed, also delete `devnet-e2e-test` from the PR. +-devnet: + prs: + unlabel: + - devnet-e2e-test + +# Let the developer know that they need to push another commit after attaching the label to PR. +push-image: + prs: + comment: The image is going to be pushed after the next commit. If you want to run an e2e test, it is necessary to push another commit. You can use `make trigger_ci` to push an empty commit. + +# When `push-image` is removed, also delete `devnet` from the PR. +-push-image: + prs: + unlabel: + - devnet diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 1b60ddc4c..6f76334c1 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -8,11 +8,16 @@ on: branches: ["main"] pull_request: +concurrency: + group: ${{ github.head_ref || github.ref_name }} + cancel-in-progress: true + jobs: build: runs-on: ubuntu-latest steps: - name: install ignite + # If this step fails due to ignite.com failing, see #116 for a temporary workaround run: | curl https://get.ignite.com/cli! | bash ignite version @@ -39,7 +44,54 @@ jobs: run: make go_lint - name: Build - run: ignite chain build --debug --skip-proto + run: ignite chain build -v --debug --skip-proto - name: Test run: make go_test + + - name: Set up Docker Buildx + if: (github.ref == 'refs/heads/main') || (contains(github.event.pull_request.labels.*.name, 'push-image')) + uses: docker/setup-buildx-action@v3 + + - name: Docker Metadata action + if: (github.ref == 'refs/heads/main') || (contains(github.event.pull_request.labels.*.name, 'push-image')) + id: meta + uses: docker/metadata-action@v5 + env: + DOCKER_METADATA_PR_HEAD_SHA: "true" + with: + images: | + ghcr.io/pokt-network/pocketd + tags: | + type=ref,event=branch + type=ref,event=pr + type=sha + type=sha,format=long + + - name: Login to GitHub Container Registry + if: (github.ref == 'refs/heads/main') || (contains(github.event.pull_request.labels.*.name, 'push-image')) + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Copy binary to inside of the Docker context + if: (github.ref == 'refs/heads/main') || (contains(github.event.pull_request.labels.*.name, 'push-image')) + run: | + mkdir -p ./bin # Make sure the bin directory exists + cp $(go env GOPATH)/bin/poktrolld ./bin # Copy the binary to the repo's bin directory + + - name: Build and push Docker image + if: (github.ref == 'refs/heads/main') || (contains(github.event.pull_request.labels.*.name, 'push-image')) + uses: docker/build-push-action@v5 + with: + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + # NB: Uncomment below if arm64 build is needed; arm64 builds are off by default because build times are significant. + platforms: linux/amd64 #,linux/arm64 + file: Dockerfile.dev + cache-from: type=gha + cache-to: type=gha,mode=max + context: . diff --git a/.github/workflows/label-actions.yml b/.github/workflows/label-actions.yml new file mode 100644 index 000000000..caf1a31cc --- /dev/null +++ b/.github/workflows/label-actions.yml @@ -0,0 +1,21 @@ +name: 'Label Actions' + +on: + issues: + types: [labeled, unlabeled] + pull_request_target: + types: [labeled, unlabeled] + discussion: + types: [labeled, unlabeled] + +permissions: + contents: read + issues: write + pull-requests: write + discussions: write + +jobs: + action: + runs-on: ubuntu-latest + steps: + - uses: dessant/label-actions@v3 diff --git a/.gitignore b/.gitignore index aa7066b08..5dc239e58 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,6 @@ go.work # Don't commit binaries bin -!bin/.keep # Before we provision the localnet, `ignite` creates the accounts, genesis, etc. for us # As many of the files are dynamic, we only preserve the config files in git history. @@ -57,4 +56,7 @@ ts-client/ **/*_mock.go # Localnet config -localnet_config.yaml \ No newline at end of file +localnet_config.yaml + +# Relase artifacts produced by `ignite chain build --release` +release diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 000000000..2d10955e0 --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,23 @@ +# This Dockerfile is used to build container image for development purposes. +# It intentionally contains no security features, ships with code and troubleshooting tools. + +FROM golang:1.20 as base + +RUN apt update && \ + apt-get install -y \ + ca-certificates \ + curl jq make + +# enable faster module downloading. +ENV GOPROXY https://proxy.golang.org + +COPY . /poktroll + +WORKDIR /poktroll + +RUN mv /poktroll/bin/poktrolld /usr/bin/poktrolld + +EXPOSE 8545 +EXPOSE 8546 + +ENTRYPOINT ["ignite"] diff --git a/Makefile b/Makefile index d6d3257dc..19c8fe59e 100644 --- a/Makefile +++ b/Makefile @@ -231,11 +231,11 @@ todo_this_commit: ## List all the TODOs needed to be done in this commit .PHONY: gateway_list gateway_list: ## List all the staked gateways - pocketd --home=$(POCKETD_HOME) q gateway list-gateway --node $(POCKET_NODE) + poktrolld --home=$(POCKETD_HOME) q gateway list-gateway --node $(POCKET_NODE) .PHONY: gateway_stake gateway_stake: ## Stake tokens for the gateway specified (must specify the gateway env var) - pocketd --home=$(POCKETD_HOME) tx gateway stake-gateway 1000upokt --keyring-backend test --from $(GATEWAY) --node $(POCKET_NODE) + poktrolld --home=$(POCKETD_HOME) tx gateway stake-gateway 1000upokt --keyring-backend test --from $(GATEWAY) --node $(POCKET_NODE) .PHONY: gateway1_stake gateway1_stake: ## Stake gateway1 @@ -251,7 +251,7 @@ gateway3_stake: ## Stake gateway3 .PHONY: gateway_unstake gateway_unstake: ## Unstake an gateway (must specify the GATEWAY env var) - pocketd --home=$(POCKETD_HOME) tx gateway unstake-gateway --keyring-backend test --from $(GATEWAY) --node $(POCKET_NODE) + poktrolld --home=$(POCKETD_HOME) tx gateway unstake-gateway --keyring-backend test --from $(GATEWAY) --node $(POCKET_NODE) .PHONY: gateway1_unstake gateway1_unstake: ## Unstake gateway1 @@ -271,11 +271,11 @@ gateway3_unstake: ## Unstake gateway3 .PHONY: app_list app_list: ## List all the staked applications - pocketd --home=$(POCKETD_HOME) q application list-application --node $(POCKET_NODE) + poktrolld --home=$(POCKETD_HOME) q application list-application --node $(POCKET_NODE) .PHONY: app_stake app_stake: ## Stake tokens for the application specified (must specify the APP and SERVICES env vars) - pocketd --home=$(POCKETD_HOME) tx application stake-application 1000upokt $(SERVICES) --keyring-backend test --from $(APP) --node $(POCKET_NODE) + poktrolld --home=$(POCKETD_HOME) tx application stake-application 1000upokt $(SERVICES) --keyring-backend test --from $(APP) --node $(POCKET_NODE) .PHONY: app1_stake app1_stake: ## Stake app1 @@ -291,7 +291,7 @@ app3_stake: ## Stake app3 .PHONY: app_unstake app_unstake: ## Unstake an application (must specify the APP env var) - pocketd --home=$(POCKETD_HOME) tx application unstake-application --keyring-backend test --from $(APP) --node $(POCKET_NODE) + poktrolld --home=$(POCKETD_HOME) tx application unstake-application --keyring-backend test --from $(APP) --node $(POCKET_NODE) .PHONY: app1_unstake app1_unstake: ## Unstake app1 @@ -307,7 +307,7 @@ app3_unstake: ## Unstake app3 .PHONY: app_delegate app_delegate: ## Delegate trust to a gateway (must specify the APP and GATEWAY_ADDR env vars). Requires the app to be staked - pocketd --home=$(POCKETD_HOME) tx application delegate-to-gateway $(GATEWAY_ADDR) --keyring-backend test --from $(APP) --node $(POCKET_NODE) + poktrolld --home=$(POCKETD_HOME) tx application delegate-to-gateway $(GATEWAY_ADDR) --keyring-backend test --from $(APP) --node $(POCKET_NODE) .PHONY: app1_delegate_gateway1 app1_delegate_gateway1: ## Delegate trust to gateway1 @@ -323,7 +323,7 @@ app3_delegate_gateway3: ## Delegate trust to gateway3 .PHONY: app_undelegate app_undelegate: ## Undelegate trust to a gateway (must specify the APP and GATEWAY_ADDR env vars). Requires the app to be staked - pocketd --home=$(POCKETD_HOME) tx application undelegate-from-gateway $(GATEWAY_ADDR) --keyring-backend test --from $(APP) --node $(POCKET_NODE) + poktrolld --home=$(POCKETD_HOME) tx application undelegate-from-gateway $(GATEWAY_ADDR) --keyring-backend test --from $(APP) --node $(POCKET_NODE) .PHONY: app1_undelegate_gateway1 app1_undelegate_gateway1: ## Undelegate trust to gateway1 @@ -343,13 +343,13 @@ app3_undelegate_gateway3: ## Undelegate trust to gateway3 .PHONY: supplier_list supplier_list: ## List all the staked supplier - pocketd --home=$(POCKETD_HOME) q supplier list-supplier --node $(POCKET_NODE) + poktrolld --home=$(POCKETD_HOME) q supplier list-supplier --node $(POCKET_NODE) # TODO(@Olshansk, @okdas): Add more services (in addition to anvil) for apps and suppliers to stake for. # TODO_TECHDEBT: svc1, svc2 and svc3 below are only in place to make GetSession testable .PHONY: supplier_stake supplier_stake: ## Stake tokens for the supplier specified (must specify the APP env var) - pocketd --home=$(POCKETD_HOME) tx supplier stake-supplier 1000upokt "$(SERVICES)" --keyring-backend test --from $(SUPPLIER) --node $(POCKET_NODE) + poktrolld --home=$(POCKETD_HOME) tx supplier stake-supplier 1000upokt "$(SERVICES)" --keyring-backend test --from $(SUPPLIER) --node $(POCKET_NODE) .PHONY: supplier1_stake supplier1_stake: ## Stake supplier1 @@ -365,7 +365,7 @@ supplier3_stake: ## Stake supplier3 .PHONY: supplier_unstake supplier_unstake: ## Unstake an supplier (must specify the SUPPLIER env var) - pocketd --home=$(POCKETD_HOME) tx supplier unstake-supplier --keyring-backend test --from $(SUPPLIER) --node $(POCKET_NODE) + poktrolld --home=$(POCKETD_HOME) tx supplier unstake-supplier --keyring-backend test --from $(SUPPLIER) --node $(POCKET_NODE) .PHONY: supplier1_unstake supplier1_unstake: ## Unstake supplier1 @@ -386,10 +386,10 @@ supplier3_unstake: ## Unstake supplier3 .PHONY: acc_balance_query acc_balance_query: ## Query the balance of the account specified (make acc_balance_query ACC=pokt...) @echo "~~~ Balances ~~~" - pocketd --home=$(POCKETD_HOME) q bank balances $(ACC) --node $(POCKET_NODE) + poktrolld --home=$(POCKETD_HOME) q bank balances $(ACC) --node $(POCKET_NODE) @echo "~~~ Spendable Balances ~~~" @echo "Querying spendable balance for $(ACC)" - pocketd --home=$(POCKETD_HOME) q bank spendable-balances $(ACC) --node $(POCKET_NODE) + poktrolld --home=$(POCKETD_HOME) q bank spendable-balances $(ACC) --node $(POCKET_NODE) .PHONY: acc_balance_query_module_app acc_balance_query_module_app: ## Query the balance of the network level "application" module @@ -405,7 +405,7 @@ acc_balance_query_app1: ## Query the balance of app1 .PHONY: acc_balance_total_supply acc_balance_total_supply: ## Query the total supply of the network - pocketd --home=$(POCKETD_HOME) q bank total --node $(POCKET_NODE) + poktrolld --home=$(POCKETD_HOME) q bank total --node $(POCKET_NODE) ###################### ### Ignite Helpers ### @@ -415,6 +415,15 @@ acc_balance_total_supply: ## Query the total supply of the network ignite_acc_list: ## List all the accounts in LocalNet ignite account list --keyring-dir=$(POCKETD_HOME) --keyring-backend test --address-prefix $(POCKET_ADDR_PREFIX) +################## +### CI Helpers ### +################## + +.PHONY: trigger_ci +trigger_ci: ## Trigger the CI pipeline by submitting an empty commit; See https://github.com/pokt-network/pocket/issues/900 for details + git commit --allow-empty -m "Empty commit" + git push + ##################### ### Documentation ### ##################### diff --git a/Tiltfile b/Tiltfile index 1fd0dd779..17521b14b 100644 --- a/Tiltfile +++ b/Tiltfile @@ -101,12 +101,12 @@ docker_build_with_restart( dockerfile_contents="""FROM golang:1.20.8 RUN apt-get -q update && apt-get install -qyy curl jq RUN go install github.com/go-delve/delve/cmd/dlv@latest -COPY bin/pocketd /usr/local/bin/pocketd +COPY bin/poktrolld /usr/local/bin/pocketd WORKDIR / """, - only=["./bin/pocketd"], + only=["./bin/poktrolld"], entrypoint=["/bin/sh", "/scripts/pocket.sh"], - live_update=[sync("bin/pocketd", "/usr/local/bin/pocketd")], + live_update=[sync("bin/poktrolld", "/usr/local/bin/pocketd")], ) # Run celestia and anvil nodes diff --git a/e2e/tests/node.go b/e2e/tests/node.go index 4e34fa827..e46ad2889 100644 --- a/e2e/tests/node.go +++ b/e2e/tests/node.go @@ -67,7 +67,7 @@ func (p *pocketdBin) RunCommandOnHost(rpcUrl string, args ...string) (*commandRe func (p *pocketdBin) runCmd(args ...string) (*commandResult, error) { base := []string{"--home", defaultHome} args = append(base, args...) - cmd := exec.Command("pocketd", args...) + cmd := exec.Command("poktrolld", args...) r := &commandResult{} out, err := cmd.Output() if err != nil { diff --git a/x/application/client/cli/tx_delegate_to_gateway.go b/x/application/client/cli/tx_delegate_to_gateway.go index 324f88622..ea251e6cd 100644 --- a/x/application/client/cli/tx_delegate_to_gateway.go +++ b/x/application/client/cli/tx_delegate_to_gateway.go @@ -22,7 +22,7 @@ that delegates authority to the gateway specified to sign relays requests for th act on the behalf of the application during a session. Example: -$ pocketd --home=$(POCKETD_HOME) tx application delegate-to-gateway $(GATEWAY_ADDR) --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, +$ poktrolld --home=$(POCKETD_HOME) tx application delegate-to-gateway $(GATEWAY_ADDR) --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) (err error) { gatewayAddress := args[0] diff --git a/x/application/client/cli/tx_stake_application.go b/x/application/client/cli/tx_stake_application.go index 4b077e6c2..510cfd648 100644 --- a/x/application/client/cli/tx_stake_application.go +++ b/x/application/client/cli/tx_stake_application.go @@ -27,7 +27,7 @@ func CmdStakeApplication() *cobra.Command { will stake the tokens and serviceIds and associate them with the application specified by the 'from' address. Example: -$ pocketd --home=$(POCKETD_HOME) tx application stake-application 1000upokt svc1,svc2,svc3 --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, +$ poktrolld --home=$(POCKETD_HOME) tx application stake-application 1000upokt svc1,svc2,svc3 --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) (err error) { stakeString := args[0] diff --git a/x/application/client/cli/tx_undelegate_from_gateway.go b/x/application/client/cli/tx_undelegate_from_gateway.go index 95a770baa..308a5d8a0 100644 --- a/x/application/client/cli/tx_undelegate_from_gateway.go +++ b/x/application/client/cli/tx_undelegate_from_gateway.go @@ -22,7 +22,7 @@ that removes the authority from the gateway specified to sign relays requests fo act on the behalf of the application during a session. Example: -$ pocketd --home=$(POCKETD_HOME) tx application undelegate-from-gateway $(GATEWAY_ADDR) --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, +$ poktrolld --home=$(POCKETD_HOME) tx application undelegate-from-gateway $(GATEWAY_ADDR) --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) (err error) { gatewayAddress := args[0] diff --git a/x/application/client/cli/tx_unstake_application.go b/x/application/client/cli/tx_unstake_application.go index ebf720a82..bfbf10e32 100644 --- a/x/application/client/cli/tx_unstake_application.go +++ b/x/application/client/cli/tx_unstake_application.go @@ -22,7 +22,7 @@ func CmdUnstakeApplication() *cobra.Command { the application specified by the 'from' address. Example: -$ pocketd --home=$(POCKETD_HOME) tx application unstake-application --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, +$ poktrolld --home=$(POCKETD_HOME) tx application unstake-application --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) (err error) { diff --git a/x/gateway/client/cli/tx_stake_gateway.go b/x/gateway/client/cli/tx_stake_gateway.go index 2104b2523..2c363b43b 100644 --- a/x/gateway/client/cli/tx_stake_gateway.go +++ b/x/gateway/client/cli/tx_stake_gateway.go @@ -21,7 +21,7 @@ func CmdStakeGateway() *cobra.Command { Long: `Stake a gateway with the provided parameters. This is a broadcast operation that will stake the tokens and associate them with the gateway specified by the 'from' address. Example: -$ pocketd --home=$(POCKETD_HOME) tx gateway stake-gateway 1000upokt --keyring-backend test --from $(GATEWAY) --node $(POCKET_NODE)`, +$ poktrolld --home=$(POCKETD_HOME) tx gateway stake-gateway 1000upokt --keyring-backend test --from $(GATEWAY) --node $(POCKET_NODE)`, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) (err error) { clientCtx, err := client.GetClientTxContext(cmd) diff --git a/x/gateway/client/cli/tx_unstake_gateway.go b/x/gateway/client/cli/tx_unstake_gateway.go index b57fd9eb7..e417b7540 100644 --- a/x/gateway/client/cli/tx_unstake_gateway.go +++ b/x/gateway/client/cli/tx_unstake_gateway.go @@ -21,7 +21,7 @@ func CmdUnstakeGateway() *cobra.Command { Long: `Unstake a gateway. This is a broadcast operation that will unstake the gateway specified by the 'from' address. Example: -$ pocketd --home=$(POCKETD_HOME) tx gateway unstake-gateway --keyring-backend test --from $(GATEWAY) --node $(POCKET_NODE)`, +$ poktrolld --home=$(POCKETD_HOME) tx gateway unstake-gateway --keyring-backend test --from $(GATEWAY) --node $(POCKET_NODE)`, Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, _ []string) (err error) { clientCtx, err := client.GetClientTxContext(cmd) diff --git a/x/supplier/client/cli/tx_stake_supplier.go b/x/supplier/client/cli/tx_stake_supplier.go index 0d65045c8..1d721eded 100644 --- a/x/supplier/client/cli/tx_stake_supplier.go +++ b/x/supplier/client/cli/tx_stake_supplier.go @@ -32,7 +32,7 @@ of comma separated values of the form 'service;url' where 'service' is the servi For example, an application that stakes for 'anvil' could be matched with a supplier staking for 'anvil;http://anvil:8547'. Example: -$ pocketd --home=$(POCKETD_HOME) tx supplier stake-supplier 1000upokt anvil;http://anvil:8547 --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, +$ poktrolld --home=$(POCKETD_HOME) tx supplier stake-supplier 1000upokt anvil;http://anvil:8547 --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) (err error) { stakeString := args[0] diff --git a/x/supplier/client/cli/tx_unstake_supplier.go b/x/supplier/client/cli/tx_unstake_supplier.go index 40ac4a83f..2daf7c00a 100644 --- a/x/supplier/client/cli/tx_unstake_supplier.go +++ b/x/supplier/client/cli/tx_unstake_supplier.go @@ -17,7 +17,7 @@ func CmdUnstakeSupplier() *cobra.Command { Long: `Unstake an supplier with the provided parameters. This is a broadcast operation that will unstake the supplier specified by the 'from' address. Example: -$ pocketd --home=$(POCKETD_HOME) tx supplier unstake-supplier --keyring-backend test --from $(SUPPLIER) --node $(POCKET_NODE)`, +$ poktrolld --home=$(POCKETD_HOME) tx supplier unstake-supplier --keyring-backend test --from $(SUPPLIER) --node $(POCKET_NODE)`, Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) (err error) { From 51ae8022ccec47499377ba4ef95163bb85e6b6cd Mon Sep 17 00:00:00 2001 From: Bryan White Date: Tue, 7 Nov 2023 09:25:20 +0100 Subject: [PATCH 13/27] [Miner] feat: add `TxClient` (#94) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: add `TxClient` interface * chore: add option support to `ReplayObservable` * feat: add `txClient` implementation * test: `txClient` * test: tx client integration * chore: s/tx/transaction/g * chore: update pkg README.md template * wip: client pkg README * docs: fix client pkg godoc comment * fix: flakey test * chore: dial back godoc comments 😅 * chore: revise (and move to godoc.go) `testblock` & `testeventsquery` pkg godoc comment * chore: update go.mod * chore: refactor & condense godoc comments * chore: fix import paths post-update * chore: review feedback improvements * docs: update client README.md * docs: add `tx query` usage association between `txContext` & `Blockchain` * docs: add TOC * chore: review feedback improvements Co-authored-by: Daniel Olshansky * docs: improve godoc comments & client README.md --------- Co-authored-by: Daniel Olshansky --- docs/pkg/client/README.md | 147 +++++ docs/template/pkg/README.md | 21 +- go.mod | 5 +- go.sum | 3 +- internal/testclient/testblock/client.go | 73 +++ internal/testclient/testblock/godoc.go | 4 + internal/testclient/testeventsquery/client.go | 61 +- internal/testclient/testeventsquery/godoc.go | 5 + internal/testclient/testtx/context.go | 113 +--- pkg/client/godoc.go | 12 + pkg/client/interface.go | 56 +- pkg/client/services.go | 19 + pkg/client/tx/client.go | 567 ++++++++++++++++++ pkg/client/tx/client_integration_test.go | 65 ++ pkg/client/tx/client_test.go | 413 +++++++++++++ pkg/client/tx/context.go | 13 +- pkg/client/tx/encoding.go | 18 + pkg/client/tx/errors.go | 53 ++ pkg/client/tx/options.go | 22 + pkg/observable/channel/replay.go | 3 +- 20 files changed, 1517 insertions(+), 156 deletions(-) create mode 100644 docs/pkg/client/README.md create mode 100644 internal/testclient/testblock/godoc.go create mode 100644 internal/testclient/testeventsquery/godoc.go create mode 100644 pkg/client/godoc.go create mode 100644 pkg/client/services.go create mode 100644 pkg/client/tx/client.go create mode 100644 pkg/client/tx/client_integration_test.go create mode 100644 pkg/client/tx/client_test.go create mode 100644 pkg/client/tx/encoding.go create mode 100644 pkg/client/tx/errors.go create mode 100644 pkg/client/tx/options.go diff --git a/docs/pkg/client/README.md b/docs/pkg/client/README.md new file mode 100644 index 000000000..6f4032800 --- /dev/null +++ b/docs/pkg/client/README.md @@ -0,0 +1,147 @@ +# Package `client` + +## Table of Contents + +- [Overview](#overview) +- [Features](#features) +- [Architecture Overview](#architecture-overview) + - [Component Diagram Legend](#component-diagram-legend) + - [Clients Dependency Tree](#clients-dependency-tree) + - [Network Interaction](#network-interaction) +- [Installation](#installation) +- [Usage](#usage) + - [Basic Example](#basic-example) + - [Advanced Usage](#advanced-usage) + - [Configuration](#configuration) +- [API Reference](#api-reference) +- [Best Practices](#best-practices) +- [FAQ](#faq) + + +## Overview + +The `client` package exposes go APIs to facilitate interactions with the Pocket network. +It includes lower-level interfaces for working with transactions and subscribing to events generally, as well as higher-level interfaces for tracking blocks and broadcasting protocol-specific transactions. + +## Features + +| Interface | Description | +|-------------------------|----------------------------------------------------------------------------------------------------| +| **`SupplierClient`** | A high-level client for use by the "supplier" actor. | +| **`TxClient`** | A high-level client used to build, sign, and broadcast transaction from cosmos-sdk messages. | +| **`TxContext`** | Abstracts and encapsulates the transaction building, signing, encoding, and broadcasting concerns. | +| **`BlockClient`** | Exposes methods for receiving notifications about newly committed blocks. | +| **`EventsQueryClient`** | Encapsulates blockchain event subscriptions. | +| **`Connection`** | A transport agnostic communication channel for sending and receiving messages. | +| **`Dialer`** | Abstracts the establishment of connections. | + +## Architecture Overview + +```mermaid +--- +title: Component Diagram Legend +--- +flowchart + +c[Component] +d[Dependency Component] +s[[Subcomponent]] +r[Remote Component] + +c --"direct usage via #DependencyMethod()"--> d +c -."usage via network I/O".-> r +c --> s +``` + +> **Figure 1**: A legend for the component diagrams in this document. + +```mermaid +--- +title: Clients Dependency Tree +--- +flowchart + +sup[SupplierClient] +tx[TxClient] +txctx[[TxContext]] +bl[BlockClient] +evt[EventsQueryClient] +conn[[Connection]] +dial[[Dialer]] + +sup --"#SignAndBroadcast()"--> tx + +tx --"#CommittedBlocksSequence()"--> bl +tx --"#BroadcastTx"--> txctx +tx --"#EventsBytes()"--> evt +bl --"#EventsBytes()"--> evt +evt --> conn +evt --"#DialContext()"--> dial +dial --"(returns)"--> conn +``` + +> **Figure 2**: An overview which articulates the dependency relationships between the various client interfaces and their subcompnents. + +```mermaid +--- +title: Network Interaction +--- +flowchart + +txctx[[TxContext]] +conn[[Connection]] +dial[[Dialer]] + +chain[Blockchain] + +conn <-."subscribed events".-> chain +dial -."RPC subscribe".-> chain +txctx -."tx broadcast".-> chain +txctx -."tx query".-> chain +``` + +> **Figure 3**: An overview of how client subcomponents interact with the network. + +## Installation + +```bash +go get github.com/pokt-network/poktroll/pkg/client +``` + +## Usage + +### Basic Example + +```go +// TODO: Code example showcasing the use of TxClient or any other primary interface. +``` + +### Advanced Usage + +```go +// TODO: Example illustrating advanced features or edge cases of the package. +``` + +### Configuration + +- **TxClientOption**: Function type that modifies the `TxClient` allowing for flexible and optional configurations. +- **EventsQueryClientOption**: Modifies the `EventsQueryClient` to apply custom behaviors or configurations. + +## API Reference + +For the complete API details, see the [godoc](https://pkg.go.dev/github.com/pokt-network/poktroll/pkg/client). + +## Best Practices + +- **Use Abstractions**: Instead of directly communicating with blockchain platforms, leverage the provided interfaces for consistent and error-free interactions. +- **Stay Updated**: With evolving blockchain technologies, ensure to keep the package updated for any new features or security patches. + +## FAQ + +#### How does the `TxClient` interface differ from `TxContext`? + +While `TxClient` is centered around signing and broadcasting transactions, `TxContext` consolidates operational dependencies for the transaction lifecycle, like building, encoding, and querying. + +#### Can I extend or customize the provided interfaces? + +Yes, the package is designed with modularity in mind. You can either implement the interfaces based on your requirements or extend them for additional functionalities. \ No newline at end of file diff --git a/docs/template/pkg/README.md b/docs/template/pkg/README.md index 44f41885a..10fdc2755 100644 --- a/docs/template/pkg/README.md +++ b/docs/template/pkg/README.md @@ -70,12 +70,7 @@ If the package can be configured in some way, describe it here: ## API Reference -While `godoc` will provide the detailed API reference, you can highlight or briefly describe key functions, types, or methods here. - -- `FunctionOrType1()`: A short description of its purpose. -- `FunctionOrType2(param Type)`: Another brief description. - -For the complete API details, see the [godoc](https://pkg.go.dev/github.com/yourusername/yourproject/[PackageName]). +For the complete API details, see the [godoc](https://pkg.go.dev/github.com/pokt-network/poktroll/[PackageName]). ## Best Practices @@ -90,16 +85,4 @@ Answer for question 1. #### Question 2? -Answer for question 2. - -## Contributing - -Briefly describe how others can contribute to this package. Link to the main contributing guide if you have one. - -## Changelog - -For detailed release notes, see the [CHANGELOG](../CHANGELOG.md) at the root level or link to a separate CHANGELOG specific to this package. - -## License - -This package is released under the XYZ License. For more information, see the [LICENSE](../LICENSE) file at the root level. \ No newline at end of file +Answer for question 2. \ No newline at end of file diff --git a/go.mod b/go.mod index 95c298124..2d699d1cf 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( cosmossdk.io/math v1.0.1 github.com/cometbft/cometbft v0.37.2 github.com/cometbft/cometbft-db v0.8.0 - github.com/cosmos/cosmos-proto v1.0.0-beta.2 github.com/cosmos/cosmos-sdk v0.47.3 github.com/cosmos/gogoproto v1.4.10 github.com/cosmos/ibc-go/v7 v7.1.0 @@ -27,7 +26,6 @@ require ( go.uber.org/multierr v1.11.0 golang.org/x/crypto v0.12.0 golang.org/x/sync v0.3.0 - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 google.golang.org/grpc v1.56.1 gopkg.in/yaml.v2 v2.4.0 ) @@ -71,6 +69,7 @@ require ( github.com/containerd/cgroups v1.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect + github.com/cosmos/cosmos-proto v1.0.0-beta.2 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect github.com/cosmos/iavl v0.20.0 // indirect @@ -135,7 +134,6 @@ require ( github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect - github.com/hashicorp/go-uuid v1.0.2 // indirect github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect github.com/hashicorp/golang-lru/v2 v2.0.2 // indirect @@ -266,6 +264,7 @@ require ( gonum.org/v1/gonum v0.11.0 // indirect google.golang.org/api v0.122.0 // indirect google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 48ddd50c7..ef5829bd5 100644 --- a/go.sum +++ b/go.sum @@ -933,9 +933,8 @@ github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoD github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= diff --git a/internal/testclient/testblock/client.go b/internal/testclient/testblock/client.go index 0918ee64f..ebd2ebcd7 100644 --- a/internal/testclient/testblock/client.go +++ b/internal/testclient/testblock/client.go @@ -5,14 +5,19 @@ import ( "testing" "cosmossdk.io/depinject" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" + "github.com/pokt-network/poktroll/internal/mocks/mockclient" "github.com/pokt-network/poktroll/internal/testclient" "github.com/pokt-network/poktroll/internal/testclient/testeventsquery" "github.com/pokt-network/poktroll/pkg/client" "github.com/pokt-network/poktroll/pkg/client/block" + "github.com/pokt-network/poktroll/pkg/observable/channel" ) +// NewLocalnetClient creates and returns a new BlockClient that's configured for +// use with the localnet sequencer. func NewLocalnetClient(ctx context.Context, t *testing.T) client.BlockClient { t.Helper() @@ -25,3 +30,71 @@ func NewLocalnetClient(ctx context.Context, t *testing.T) client.BlockClient { return bClient } + +// NewOneTimeCommittedBlocksSequenceBlockClient creates a new mock BlockClient. +// This mock BlockClient will expect a call to CommittedBlocksSequence, and +// when that call is made, it returns a new BlocksObservable that is notified of +// blocks sent on the given blocksPublishCh. +// blocksPublishCh is the channel the caller can use to publish blocks the observable. +func NewOneTimeCommittedBlocksSequenceBlockClient( + t *testing.T, + blocksPublishCh chan client.Block, +) *mockclient.MockBlockClient { + t.Helper() + + // Create a mock for the block client which expects the LatestBlock method to be called any number of times. + blockClientMock := NewAnyTimeLatestBlockBlockClient(t, nil, 0) + + // Set up the mock expectation for the CommittedBlocksSequence method. When + // the method is called, it returns a new replay observable that publishes + // blocks sent on the given blocksPublishCh. + blockClientMock.EXPECT().CommittedBlocksSequence( + gomock.AssignableToTypeOf(context.Background()), + ).DoAndReturn(func(ctx context.Context) client.BlocksObservable { + // Create a new replay observable with a replay buffer size of 1. Blocks + // are published to this observable via the provided blocksPublishCh. + withPublisherOpt := channel.WithPublisher(blocksPublishCh) + obs, _ := channel.NewReplayObservable[client.Block]( + ctx, 1, withPublisherOpt, + ) + return obs + }) + + return blockClientMock +} + +// NewAnyTimeLatestBlockBlockClient creates a mock BlockClient that expects +// calls to the LatestBlock method any number of times. When the LatestBlock +// method is called, it returns a mock Block with the provided hash and height. +func NewAnyTimeLatestBlockBlockClient( + t *testing.T, + hash []byte, + height int64, +) *mockclient.MockBlockClient { + t.Helper() + ctrl := gomock.NewController(t) + + // Create a mock block that returns the provided hash and height. + blockMock := NewAnyTimesBlock(t, hash, height) + // Create a mock block client that expects calls to LatestBlock method and + // returns the mock block. + blockClientMock := mockclient.NewMockBlockClient(ctrl) + blockClientMock.EXPECT().LatestBlock(gomock.Any()).Return(blockMock).AnyTimes() + + return blockClientMock +} + +// NewAnyTimesBlock creates a mock Block that expects calls to Height and Hash +// methods any number of times. When the methods are called, they return the +// provided height and hash respectively. +func NewAnyTimesBlock(t *testing.T, hash []byte, height int64) *mockclient.MockBlock { + t.Helper() + ctrl := gomock.NewController(t) + + // Create a mock block that returns the provided hash and height AnyTimes. + blockMock := mockclient.NewMockBlock(ctrl) + blockMock.EXPECT().Height().Return(height).AnyTimes() + blockMock.EXPECT().Hash().Return(hash).AnyTimes() + + return blockMock +} diff --git a/internal/testclient/testblock/godoc.go b/internal/testclient/testblock/godoc.go new file mode 100644 index 000000000..866bb4f70 --- /dev/null +++ b/internal/testclient/testblock/godoc.go @@ -0,0 +1,4 @@ +// Package testblock provides helper functions for constructing real (e.g. localnet) +// and mock BlockClient objects with pre-configured and/or parameterized call +// arguments, return value(s), and/or expectations thereof. Intended for use in tests. +package testblock diff --git a/internal/testclient/testeventsquery/client.go b/internal/testclient/testeventsquery/client.go index 0aa618fe9..2c68606ce 100644 --- a/internal/testclient/testeventsquery/client.go +++ b/internal/testclient/testeventsquery/client.go @@ -2,10 +2,13 @@ package testeventsquery import ( "context" + "fmt" "testing" "time" + cosmoskeyring "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" "github.com/pokt-network/poktroll/internal/mocks/mockclient" "github.com/pokt-network/poktroll/internal/testclient" @@ -15,14 +18,68 @@ import ( "github.com/pokt-network/poktroll/pkg/observable/channel" ) -// NewLocalnetClient returns a new events query client which is configured to -// connect to the localnet sequencer. +// NewLocalnetClient creates and returns a new events query client that's configured +// for use with the localnet sequencer. Any options provided are applied to the client. func NewLocalnetClient(t *testing.T, opts ...client.EventsQueryClientOption) client.EventsQueryClient { t.Helper() return eventsquery.NewEventsQueryClient(testclient.CometLocalWebsocketURL, opts...) } +// NewOneTimeEventsQuery creates a mock of the EventsQueryClient which expects +// a single call to the EventsBytes method. query is the query string which is +// expected to be received by that call. +// It returns a mock client whose event bytes method constructs a new observable. +// The caller can simulate blockchain events by sending on the value publishCh +// points to, which is set by this helper function. +func NewOneTimeEventsQuery( + ctx context.Context, + t *testing.T, + query string, + publishCh *chan<- either.Bytes, +) *mockclient.MockEventsQueryClient { + t.Helper() + ctrl := gomock.NewController(t) + + eventsQueryClient := mockclient.NewMockEventsQueryClient(ctrl) + eventsQueryClient.EXPECT().EventsBytes(gomock.Eq(ctx), gomock.Eq(query)). + DoAndReturn(func( + ctx context.Context, + query string, + ) (eventsBzObservable client.EventsBytesObservable, err error) { + eventsBzObservable, *publishCh = channel.NewObservable[either.Bytes]() + return eventsBzObservable, nil + }).Times(1) + return eventsQueryClient +} + +// NewOneTimeTxEventsQueryClient creates a mock of the Events that expects +// a single call to the EventsBytes method where the query is for transaction +// events for sender address matching that of the given key. +// The caller can simulate blockchain events by sending on the value publishCh +// points to, which is set by this helper function. +func NewOneTimeTxEventsQueryClient( + ctx context.Context, + t *testing.T, + key *cosmoskeyring.Record, + publishCh *chan<- either.Bytes, +) *mockclient.MockEventsQueryClient { + t.Helper() + + signingAddr, err := key.GetAddress() + require.NoError(t, err) + + expectedEventsQuery := fmt.Sprintf( + "tm.event='Tx' AND message.sender='%s'", + signingAddr, + ) + return NewOneTimeEventsQuery( + ctx, t, + expectedEventsQuery, + publishCh, + ) +} + // NewAnyTimesEventsBytesEventsQueryClient returns a new events query client which // is configured to return the expected event bytes when queried with the expected // query, any number of times. The returned client also expects to be closed once. diff --git a/internal/testclient/testeventsquery/godoc.go b/internal/testclient/testeventsquery/godoc.go new file mode 100644 index 000000000..0caa02997 --- /dev/null +++ b/internal/testclient/testeventsquery/godoc.go @@ -0,0 +1,5 @@ +// Package testeventsquery provides helper functions for constructing real +// (e.g. localnet) and mock EventsQueryClient objects with pre-configured and/or +// parameterized call arguments, return value(s), and/or expectations thereof. +// Intended for use in tests. +package testeventsquery diff --git a/internal/testclient/testtx/context.go b/internal/testclient/testtx/context.go index e7d1f8446..fa25494e7 100644 --- a/internal/testclient/testtx/context.go +++ b/internal/testclient/testtx/context.go @@ -30,25 +30,10 @@ import ( // correlations between these "times" values and the contexts in which the expected // methods may be called. -// NewOneTimeErrTxTimeoutTxContext creates a mock transaction context designed to simulate a specific -// timeout error scenario during transaction broadcasting. -// -// Parameters: -// - t: The testing.T instance for the current test. -// - keyring: The Cosmos SDK keyring containing the signer's cryptographic keys. -// - signingKeyName: The name of the key within the keyring to use for signing. -// - expectedTx: A pointer whose value will be set to the expected transaction -// bytes (in hexadecimal format). -// - expectedErrMsg: A pointer whose value will be set to the expected error -// message string. -// -// The function performs the following actions: -// 1. It retrieves the signer's cryptographic key from the provided keyring using the signingKeyName. -// 2. It computes the corresponding address of the signer's key. -// 3. It then formats an error message indicating that the fee payer's address does not exist. -// 4. It creates a base mock transaction context using NewBaseTxContext. -// 5. It sets up the mock behavior for the BroadcastTxSync method to return a specific preset response. -// 6. It also sets up the mock behavior for the QueryTx method to return a specific error response. +// NewOneTimeErrTxTimeoutTxContext creates a mock transaction context designed to +// simulate a specific timeout error scenario during transaction broadcasting. +// expectedErrMsg is populated with the same error message which is presented in +// the result from the QueryTx method so that it can be asserted against. func NewOneTimeErrTxTimeoutTxContext( t *testing.T, keyring cosmoskeyring.Keyring, @@ -116,24 +101,8 @@ func NewOneTimeErrTxTimeoutTxContext( // NewOneTimeErrCheckTxTxContext creates a mock transaction context to simulate // a specific error scenario during the ABCI check-tx phase (i.e., during initial // validation before the transaction is included in the block). -// -// Parameters: -// - t: The testing.T instance for the current test. -// - keyring: The Cosmos SDK keyring containing the signer's cryptographic keys. -// - signingKeyName: The name of the key within the keyring to be used for signing. -// - expectedTx: A pointer whose value will be set to the expected transaction -// bytes (in hexadecimal format). -// - expectedErrMsg: A pointer whose value will be set to the expected error -// message string. -// -// The function operates as follows: -// 1. Retrieves the signer's cryptographic key from the provided keyring based on -// the signingKeyName. -// 2. Determines the corresponding address of the signer's key. -// 3. Composes an error message suggesting that the fee payer's address is unrecognized. -// 4. Creates a base mock transaction context using the NewBaseTxContext function. -// 5. Sets up the mock behavior for the BroadcastTxSync method to return a specific -// error response related to the check phase of the transaction. +// expectedErrMsg is populated with the same error message which is presented in +// the result from the QueryTx method so that it can be asserted against. func NewOneTimeErrCheckTxTxContext( t *testing.T, keyring cosmoskeyring.Keyring, @@ -179,22 +148,7 @@ func NewOneTimeErrCheckTxTxContext( } // NewOneTimeTxTxContext creates a mock transaction context primed to respond with -// a single successful transaction response. This function facilitates testing by -// ensuring that the BroadcastTxSync method will return a specific, controlled response -// without actually broadcasting the transaction to the network. -// -// Parameters: -// - t: The testing.T instance used for the current test, typically passed from -// the calling test function. -// - keyring: The Cosmos SDK keyring containing the available cryptographic keys. -// - signingKeyName: The name of the key within the keyring used for transaction signing. -// - expectedTx: A pointer whose value will be set to the expected transaction -// bytes (in hexadecimal format). -// -// The function operates as follows: -// 1. Constructs a base mock transaction context using the NewBaseTxContext function. -// 2. Configures the mock behavior for the BroadcastTxSync method to return a pre-defined -// successful transaction response, ensuring that this behavior will only be triggered once. +// a single successful transaction response. func NewOneTimeTxTxContext( t *testing.T, keyring cosmoskeyring.Keyring, @@ -224,30 +178,11 @@ func NewOneTimeTxTxContext( return txCtxMock } -// NewBaseTxContext establishes a foundational mock transaction context with -// predefined behaviors suitable for a broad range of testing scenarios. It ensures -// that when interactions like transaction building, signing, and encoding occur -// in the test environment, they produce predictable and controlled outcomes. -// -// Parameters: -// - t: The testing.T instance used for the current test, typically passed from -// the calling test function. -// - signingKeyName: The name of the key within the keyring to be used for -// transaction signing. -// - keyring: The Cosmos SDK keyring containing the available cryptographic keys. -// - expectedTx: A pointer whose value will be set to the expected transaction -// bytes (in hexadecimal format). -// - expectedErrMsg: A pointer whose value will be set to the expected error -// message string. -// -// The function works as follows: -// 1. Invokes the NewAnyTimesTxTxContext to create a base mock transaction context. -// 2. Sets the expectation that NewTxBuilder method will be called exactly once. -// 3. Configures the mock behavior for the SignTx method to utilize the context's -// signing logic. -// 4. Overrides the EncodeTx method's behavior to intercept the encoding operation, -// capture the encoded transaction bytes, compute the transaction hash, and populate -// the expectedTx and expectedTxHash parameters accordingly. +// NewBaseTxContext creates a mock transaction context that's configured to expect +// calls to NewTxBuilder, SignTx, and EncodeTx methods, any number of times. +// EncodeTx is used to intercept the encoded transaction bytes and store them in +// the expectedTx output parameter. Each of these methods proxies to the corresponding +// method on a real transaction context. func NewBaseTxContext( t *testing.T, signingKeyName string, @@ -281,30 +216,6 @@ func NewBaseTxContext( // NewAnyTimesTxTxContext initializes a mock transaction context that's configured to allow // arbitrary calls to certain predefined interactions, primarily concerning the retrieval // of account numbers and sequences. -// -// Parameters: -// - t: The testing.T instance used for the current test, typically passed from the calling test function. -// - keyring: The Cosmos SDK keyring containing the available cryptographic keys. -// -// The function operates in the following manner: -// 1. Establishes a new gomock controller for setting up mock expectations and behaviors. -// 2. Prepares a set of flags suitable for localnet testing environments. -// 3. Sets up a mock behavior to intercept the GetAccountNumberSequence method calls, -// ensuring that whenever this method is invoked, it consistently returns an account number -// and sequence of 1, without making real queries to the underlying infrastructure. -// 4. Constructs a client context tailored for localnet testing with the provided keyring -// and the mocked account retriever. -// 5. Initializes a transaction factory from the client context and validates its integrity. -// 6. Injects the transaction factory and client context dependencies to create a new transaction context. -// 7. Creates a mock transaction context that always returns the provided keyring when the GetKeyring method is called. -// -// This setup aids tests by facilitating the creation of mock transaction contexts that have predictable -// and controlled outcomes for account number and sequence retrieval operations. -// -// Returns: -// - A mock transaction context suitable for setting additional expectations in tests. -// - A real transaction context initialized with the supplied dependencies. - func NewAnyTimesTxTxContext( t *testing.T, keyring cosmoskeyring.Keyring, diff --git a/pkg/client/godoc.go b/pkg/client/godoc.go new file mode 100644 index 000000000..66da550dd --- /dev/null +++ b/pkg/client/godoc.go @@ -0,0 +1,12 @@ +// Package client defines interfaces and types that facilitate interactions +// with blockchain functionalities, both transactional and observational. It is +// built to provide an abstraction layer for sending, receiving, and querying +// blockchain data, thereby offering a standardized way of integrating with +// various blockchain platforms. +// +// The client package leverages external libraries like cosmos-sdk and cometbft, +// but there is a preference to minimize direct dependencies on these external +// libraries, when defining interfaces, aiming for a cleaner decoupling. +// It seeks to provide a flexible and comprehensive interface layer, adaptable to +// different blockchain configurations and requirements. +package client diff --git a/pkg/client/interface.go b/pkg/client/interface.go index 2d67c90c0..32dab250c 100644 --- a/pkg/client/interface.go +++ b/pkg/client/interface.go @@ -1,4 +1,5 @@ //go:generate mockgen -destination=../../internal/mocks/mockclient/events_query_client_mock.go -package=mockclient . Dialer,Connection,EventsQueryClient +//go:generate mockgen -destination=../../internal/mocks/mockclient/block_client_mock.go -package=mockclient . Block,BlockClient //go:generate mockgen -destination=../../internal/mocks/mockclient/tx_client_mock.go -package=mockclient . TxContext //go:generate mockgen -destination=../../internal/mocks/mockclient/cosmos_tx_builder_mock.go -package=mockclient github.com/cosmos/cosmos-sdk/client TxBuilder //go:generate mockgen -destination=../../internal/mocks/mockclient/cosmos_keyring_mock.go -package=mockclient github.com/cosmos/cosmos-sdk/crypto/keyring Keyring @@ -18,9 +19,18 @@ import ( "github.com/pokt-network/poktroll/pkg/observable" ) +// TxClient provides a synchronous interface initiating and waiting for transactions +// derived from cosmos-sdk messages, in a cosmos-sdk based blockchain network. +type TxClient interface { + SignAndBroadcast( + ctx context.Context, + msgs ...cosmostypes.Msg, + ) either.AsyncError +} + // TxContext provides an interface which consolidates the operational dependencies -// required to facilitate the sender side of the tx lifecycle: build, sign, encode, -// broadcast, query (optional). +// required to facilitate the sender side of the transaction lifecycle: build, sign, +// encode, broadcast, and query (optional). // // TODO_IMPROVE: Avoid depending on cosmos-sdk structs or interfaces; add Pocket // interface types to substitute: @@ -29,13 +39,13 @@ import ( // - Keyring // - TxBuilder type TxContext interface { - // GetKeyring returns the associated key management mechanism for the tx context. + // GetKeyring returns the associated key management mechanism for the transaction context. GetKeyring() cosmoskeyring.Keyring - // NewTxBuilder creates and returns a new tx builder instance. + // NewTxBuilder creates and returns a new transaction builder instance. NewTxBuilder() cosmosclient.TxBuilder - // SignTx signs a tx using the specified key name. It can operate in offline mode, + // SignTx signs a transaction using the specified key name. It can operate in offline mode, // and can overwrite any existing signatures based on the provided flags. SignTx( keyName string, @@ -43,14 +53,14 @@ type TxContext interface { offline, overwriteSig bool, ) error - // EncodeTx takes a tx builder and encodes it, returning its byte representation. + // EncodeTx takes a transaction builder and encodes it, returning its byte representation. EncodeTx(txBuilder cosmosclient.TxBuilder) ([]byte, error) - // BroadcastTx broadcasts the given tx to the network. + // BroadcastTx broadcasts the given transaction to the network. BroadcastTx(txBytes []byte) (*cosmostypes.TxResponse, error) - // QueryTx retrieves a tx status based on its hash and optionally provides - // proof of the tx. + // QueryTx retrieves a transaction status based on its hash and optionally provides + // proof of the transaction. QueryTx( ctx context.Context, txHash []byte, @@ -60,6 +70,7 @@ type TxContext interface { // BlocksObservable is an observable which is notified with an either // value which contains either an error or the event message bytes. +// // TODO_HACK: The purpose of this type is to work around gomock's lack of // support for generic types. For the same reason, this type cannot be an // alias (i.e. EventsBytesObservable = observable.Observable[either.Either[[]byte]]). @@ -84,23 +95,24 @@ type Block interface { Hash() []byte } -// TODO_CONSIDERATION: the cosmos-sdk CLI code seems to use a cometbft RPC client -// which includes a `#Subscribe()` method for a similar purpose. Perhaps we could -// replace this custom websocket client with that. -// (see: https://github.com/cometbft/cometbft/blob/main/rpc/client/http/http.go#L110) -// (see: https://github.com/cosmos/cosmos-sdk/blob/main/client/rpc/tx.go#L114) -// -// NOTE: a branch which attempts this is available at: -// https://github.com/pokt-network/poktroll/pull/74 - // EventsBytesObservable is an observable which is notified with an either // value which contains either an error or the event message bytes. +// // TODO_HACK: The purpose of this type is to work around gomock's lack of // support for generic types. For the same reason, this type cannot be an // alias (i.e. EventsBytesObservable = observable.Observable[either.Bytes]). type EventsBytesObservable observable.Observable[either.Bytes] // EventsQueryClient is used to subscribe to chain event messages matching the given query, +// +// TODO_CONSIDERATION: the cosmos-sdk CLI code seems to use a cometbft RPC client +// which includes a `#Subscribe()` method for a similar purpose. Perhaps we could +// replace our custom implementation with one which wraps that. +// (see: https://github.com/cometbft/cometbft/blob/main/rpc/client/http/http.go#L110) +// (see: https://github.com/cosmos/cosmos-sdk/blob/main/client/rpc/tx.go#L114) +// +// NOTE: a branch which attempts this is available at: +// https://github.com/pokt-network/poktroll/pull/74 type EventsQueryClient interface { // EventsBytes returns an observable which is notified about chain event messages // matching the given query. It receives an either value which contains either an @@ -131,8 +143,8 @@ type Dialer interface { DialContext(ctx context.Context, urlStr string) (Connection, error) } -// EventsQueryClientOption is an interface-wide type which can be implemented to use or modify the -// query client during construction. This would likely be done in an -// implementation-specific way; e.g. using a type assertion to assign to an -// implementation struct field(s). +// EventsQueryClientOption defines a function type that modifies the EventsQueryClient. type EventsQueryClientOption func(EventsQueryClient) + +// TxClientOption defines a function type that modifies the TxClient. +type TxClientOption func(TxClient) diff --git a/pkg/client/services.go b/pkg/client/services.go new file mode 100644 index 000000000..0d2ca060d --- /dev/null +++ b/pkg/client/services.go @@ -0,0 +1,19 @@ +package client + +import ( + "fmt" + + sharedtypes "github.com/pokt-network/poktroll/x/shared/types" +) + +// NewTestApplicationServiceConfig returns a slice of application service configs for testing. +func NewTestApplicationServiceConfig(prefix string, count int) []*sharedtypes.ApplicationServiceConfig { + appSvcCfg := make([]*sharedtypes.ApplicationServiceConfig, count) + for i, _ := range appSvcCfg { + serviceId := fmt.Sprintf("%s%d", prefix, i) + appSvcCfg[i] = &sharedtypes.ApplicationServiceConfig{ + ServiceId: &sharedtypes.ServiceId{Id: serviceId}, + } + } + return appSvcCfg +} diff --git a/pkg/client/tx/client.go b/pkg/client/tx/client.go new file mode 100644 index 000000000..805443eb4 --- /dev/null +++ b/pkg/client/tx/client.go @@ -0,0 +1,567 @@ +package tx + +import ( + "bytes" + "context" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "sync" + + "cosmossdk.io/depinject" + abciTypes "github.com/cometbft/cometbft/abci/types" + comettypes "github.com/cometbft/cometbft/types" + cosmostypes "github.com/cosmos/cosmos-sdk/types" + "go.uber.org/multierr" + + "github.com/pokt-network/poktroll/pkg/client" + "github.com/pokt-network/poktroll/pkg/either" + "github.com/pokt-network/poktroll/pkg/observable" + "github.com/pokt-network/poktroll/pkg/observable/channel" +) + +const ( + // DefaultCommitTimeoutHeightOffset is the default number of blocks after the + // latest block (when broadcasting) that a transactions should be considered + // errored if it has not been committed. + DefaultCommitTimeoutHeightOffset = 5 + // txWithSenderAddrQueryFmt is the query used to subscribe to cometbft transactions + // events where the sender address matches the interpolated address. + // (see: https://docs.cosmos.network/v0.47/core/events#subscribing-to-events) + txWithSenderAddrQueryFmt = "tm.event='Tx' AND message.sender='%s'" +) + +var _ client.TxClient = (*txClient)(nil) + +// txClient orchestrates building, signing, broadcasting, and querying of +// transactions. It maintains a single events query subscription to its own +// transactions (via the EventsQueryClient) in order to receive notifications +// regarding their status. +// It also depends on the BlockClient as a timer, synchronized to block height, +// to facilitate transaction timeout logic. If a transaction doesn't appear to +// have been committed by commitTimeoutHeightOffset number of blocks have elapsed, +// it is considered as timed out. Upon timeout, the client queries the network for +// the last status of the transaction, which is used to derive the asynchronous +// error that's populated in the either.AsyncError. +type txClient struct { + // TODO_TECHDEBT: this should be configurable & integrated w/ viper, flags, etc. + // commitTimeoutHeightOffset is the number of blocks after the latest block + // that a transactions should be considered errored if it has not been committed. + commitTimeoutHeightOffset int64 + // signingKeyName is the name of the key in the keyring to use for signing + // transactions. + signingKeyName string + // signingAddr is the address of the signing key referenced by signingKeyName. + // It is hydrated from the keyring by calling Keyring#Key() with signingKeyName. + signingAddr cosmostypes.AccAddress + // txCtx is the transactions context which encapsulates transactions building, signing, + // broadcasting, and querying, as well as keyring access. + txCtx client.TxContext + // eventsQueryClient is the client used to subscribe to transactions events from this + // sender. It is used to receive notifications about transactions events corresponding + // to transactions which it has constructed, signed, and broadcast. + eventsQueryClient client.EventsQueryClient + // blockClient is the client used to query for the latest block height. + // It is used to implement timout logic for transactions which weren't committed. + blockClient client.BlockClient + + // txsMutex protects txErrorChans and txTimeoutPool maps. + txsMutex sync.Mutex + // txErrorChans maps tx_hash->channel which will receive an error or nil, + // and close, when the transactions with the given hash is committed. + txErrorChans txErrorChansByHash + // txTimeoutPool maps timeout_block_height->map_of_txsByHash. It + // is used to ensure that transactions error channels receive and close in the event + // that they have not already by the given timeout height. + txTimeoutPool txTimeoutPool +} + +type ( + txTimeoutPool map[height]txErrorChansByHash + txErrorChansByHash map[txHash]chan error + height = int64 + txHash = string +) + +// TxEvent is used to deserialize incoming websocket messages from +// the transactions subscription. +type TxEvent struct { + // Tx is the binary representation of the tx hash. + Tx []byte `json:"tx"` + Events []abciTypes.Event `json:"events"` +} + +// NewTxClient attempts to construct a new TxClient using the given dependencies +// and options. +// +// It performs the following steps: +// 1. Initializes a default txClient with the default commit timeout height +// offset, an empty error channel map, and an empty transaction timeout pool. +// 2. Injects the necessary dependencies using depinject. +// 3. Applies any provided options to customize the client. +// 4. Validates and sets any missing default configurations using the +// validateConfigAndSetDefaults method. +// 5. Subscribes the client to its own transactions. This step might be +// reconsidered for relocation to a potential Start() method in the future. +func NewTxClient( + ctx context.Context, + deps depinject.Config, + opts ...client.TxClientOption, +) (client.TxClient, error) { + tClient := &txClient{ + commitTimeoutHeightOffset: DefaultCommitTimeoutHeightOffset, + txErrorChans: make(txErrorChansByHash), + txTimeoutPool: make(txTimeoutPool), + } + + if err := depinject.Inject( + deps, + &tClient.txCtx, + &tClient.eventsQueryClient, + &tClient.blockClient, + ); err != nil { + return nil, err + } + + for _, opt := range opts { + opt(tClient) + } + + if err := tClient.validateConfigAndSetDefaults(); err != nil { + return nil, err + } + + // Start an events query subscription for transactions originating from this + // client's signing address. + // TODO_CONSIDERATION: move this into a #Start() method + if err := tClient.subscribeToOwnTxs(ctx); err != nil { + return nil, err + } + + // Launch a separate goroutine to handle transaction timeouts. + // TODO_CONSIDERATION: move this into a #Start() method + go tClient.goTimeoutPendingTransactions(ctx) + + return tClient, nil +} + +// SignAndBroadcast signs a set of Cosmos SDK messages, constructs a transaction, +// and broadcasts it to the network. The function performs several steps to +// ensure the messages and the resultant transaction are valid: +// +// 1. Validates each message in the provided set. +// 2. Constructs the transaction using the Cosmos SDK's transaction builder. +// 3. Calculates and sets the transaction's timeout height. +// 4. Sets a default gas limit (note: this will be made configurable in the future). +// 5. Signs the transaction. +// 6. Validates the constructed transaction. +// 7. Serializes and broadcasts the transaction. +// 8. Checks the broadcast response for errors. +// 9. If all the above steps are successful, the function registers the +// transaction as pending. +// +// If any step encounters an error, it returns an either.AsyncError populated with +// the synchronous error. If the function completes successfully, it returns an +// either.AsyncError populated with the error channel which will receive if the +// transaction results in an asynchronous error or times out. +func (tClient *txClient) SignAndBroadcast( + ctx context.Context, + msgs ...cosmostypes.Msg, +) either.AsyncError { + var validationErrs error + for i, msg := range msgs { + if err := msg.ValidateBasic(); err != nil { + validationErr := ErrInvalidMsg.Wrapf("in msg with index %d: %s", i, err) + validationErrs = multierr.Append(validationErrs, validationErr) + } + } + if validationErrs != nil { + return either.SyncErr(validationErrs) + } + + // Construct the transactions using cosmos' transactions builder. + txBuilder := tClient.txCtx.NewTxBuilder() + if err := txBuilder.SetMsgs(msgs...); err != nil { + // return synchronous error + return either.SyncErr(err) + } + + // Calculate timeout height + timeoutHeight := tClient.blockClient.LatestBlock(ctx). + Height() + tClient.commitTimeoutHeightOffset + + // TODO_TECHDEBT: this should be configurable + txBuilder.SetGasLimit(200000) + txBuilder.SetTimeoutHeight(uint64(timeoutHeight)) + + // sign transactions + err := tClient.txCtx.SignTx( + tClient.signingKeyName, + txBuilder, + false, false, + ) + if err != nil { + return either.SyncErr(err) + } + + // ensure transactions is valid + // NOTE: this makes the transactions valid; i.e. it is *REQUIRED* + if err := txBuilder.GetTx().ValidateBasic(); err != nil { + return either.SyncErr(err) + } + + // serialize transactions + txBz, err := tClient.txCtx.EncodeTx(txBuilder) + if err != nil { + return either.SyncErr(err) + } + + txResponse, err := tClient.txCtx.BroadcastTx(txBz) + if err != nil { + return either.SyncErr(err) + } + + if txResponse.Code != 0 { + return either.SyncErr(ErrCheckTx.Wrapf(txResponse.RawLog)) + } + + return tClient.addPendingTransactions(normalizeTxHashHex(txResponse.TxHash), timeoutHeight) +} + +// validateConfigAndSetDefaults ensures that the necessary configurations for the +// txClient are set, and populates any missing defaults. +// +// 1. It checks if the signing key name is set and returns an error if it's empty. +// 2. It then retrieves the key record from the keyring using the signing key name +// and checks its existence. +// 3. The address of the signing key is computed and assigned to txClient#signgingAddr. +// 4. Lastly, it ensures that commitTimeoutHeightOffset has a valid value, setting +// it to DefaultCommitTimeoutHeightOffset if it's zero or negative. +// +// Returns: +// - ErrEmptySigningKeyName if the signing key name is not provided. +// - ErrNoSuchSigningKey if the signing key is not found in the keyring. +// - ErrSigningKeyAddr if there's an issue retrieving the address for the signing key. +// - nil if validation is successful and defaults are set appropriately. +func (tClient *txClient) validateConfigAndSetDefaults() error { + if tClient.signingKeyName == "" { + return ErrEmptySigningKeyName + } + + keyRecord, err := tClient.txCtx.GetKeyring().Key(tClient.signingKeyName) + if err != nil { + return ErrNoSuchSigningKey.Wrapf("name %q: %s", tClient.signingKeyName, err) + } + signingAddr, err := keyRecord.GetAddress() + if err != nil { + return ErrSigningKeyAddr.Wrapf("name %q: %s", tClient.signingKeyName, err) + } + tClient.signingAddr = signingAddr + + if tClient.commitTimeoutHeightOffset <= 0 { + tClient.commitTimeoutHeightOffset = DefaultCommitTimeoutHeightOffset + } + return nil +} + +// addPendingTransactions registers a new pending transaction for monitoring and +// notification of asynchronous errors. It accomplishes the following: +// +// 1. Creates an error notification channel (if one doesn't already exist) and associates +// it with the provided transaction hash in the txErrorChans map. +// +// 2. Ensures that there's an initialized map of transactions by hash for the +// given timeout height in the txTimeoutPool. The same error notification channel +// is also associated with the transaction hash in this map. +// +// Both txErrorChans and txTimeoutPool store references to the same error notification +// channel for a given transaction hash. This ensures idempotency of error handling +// for any given transaction between asynchronous, transaction-specific errors and +// transaction timeout logic. +// +// Note: The error channels are buffered to prevent blocking on send operations and +// are intended to convey a single error event. +// +// Returns: +// - An either.AsyncError populated with the error notification channel for the +// provided transaction hash. +func (tClient *txClient) addPendingTransactions( + txHash string, + timeoutHeight int64, +) either.AsyncError { + tClient.txsMutex.Lock() + defer tClient.txsMutex.Unlock() + + // Initialize txTimeoutPool map if necessary. + txsByHash, ok := tClient.txTimeoutPool[timeoutHeight] + if !ok { + txsByHash = make(map[string]chan error) + tClient.txTimeoutPool[timeoutHeight] = txsByHash + } + + // Initialize txErrorChans map in txTimeoutPool map if necessary. + errCh, ok := txsByHash[txHash] + if !ok { + // NB: intentionally buffered to avoid blocking on send. Only intended + // to send/receive a single error. + errCh = make(chan error, 1) + txsByHash[txHash] = errCh + } + + // Initialize txErrorChans map if necessary. + if _, ok := tClient.txErrorChans[txHash]; !ok { + // NB: both maps hold a reference to the same channel so that we can check + // if the channel has already been closed when timing out. + tClient.txErrorChans[txHash] = errCh + } + + return either.AsyncErr(errCh) +} + +// subscribeToOwnTxs establishes an event query subscription to monitor transactions +// originating from this client's signing address. +// +// It performs the following steps: +// +// 1. Forms a query to fetch transaction events specific to the client's signing address. +// 2. Maps raw event bytes observable notifications to a new transaction event objects observable. +// 3. Handle each transaction event. +// +// Important considerations: +// There's uncertainty surrounding the potential for asynchronous errors post transaction broadcast. +// Current implementation and observations suggest that errors might be returned synchronously, +// even when using Cosmos' BroadcastTxAsync method. Further investigation is required. +// +// This function also spawns a goroutine to handle transaction timeouts via goTimeoutPendingTransactions. +// +// Parameters: +// - ctx: Context for managing the function's lifecycle and child operations. +// +// Returns: +// - An error if there's a failure during the event query or subscription process. +func (tClient *txClient) subscribeToOwnTxs(ctx context.Context) error { + // Form a query based on the client's signing address. + query := fmt.Sprintf(txWithSenderAddrQueryFmt, tClient.signingAddr) + + // Fetch transaction events matching the query. + eventsBz, err := tClient.eventsQueryClient.EventsBytes(ctx, query) + if err != nil { + return err + } + + // Convert raw event data into a stream of transaction events. + txEventsObservable := channel.Map[ + either.Bytes, either.Either[*TxEvent], + ](ctx, eventsBz, tClient.txEventFromEventBz) + txEventsObserver := txEventsObservable.Subscribe(ctx) + + // Handle transaction events asynchronously. + go tClient.goHandleTxEvents(txEventsObserver) + + return nil +} + +// goHandleTxEvents ranges over the transaction events observable, performing +// the following steps on each: +// +// 1. Normalize hexadeimal transaction hash. +// 2. Retrieves the transaction's error channel from txErrorChans. +// 3. Closes and removes it from txErrorChans. +// 4. Removes the transaction error channel from txTimeoutPool. +// +// It is intended to be called in a goroutine. +func (tClient *txClient) goHandleTxEvents( + txEventsObserver observable.Observer[either.Either[*TxEvent]], +) { + for eitherTxEvent := range txEventsObserver.Ch() { + txEvent, err := eitherTxEvent.ValueOrError() + if err != nil { + return + } + + // Convert transaction hash into its normalized hex form. + txHashHex := txHashBytesToNormalizedHex(comettypes.Tx(txEvent.Tx).Hash()) + + tClient.txsMutex.Lock() + + // Check for a corresponding error channel in the map. + txErrCh, ok := tClient.txErrorChans[txHashHex] + if !ok { + panic("Received tx event without an associated error channel.") + } + + // TODO_INVESTIGATE: it seems like it may not be possible for the + // txEvent to represent an error. Cosmos' #BroadcastTxSync() is being + // called internally, which will return an error if the transaction + // is not accepted by the mempool. + // + // It's unclear if a cosmos chain is capable of returning an async + // error for a transaction at this point; even when substituting + // #BroadcastTxAsync(), the error is returned synchronously: + // + // > error in json rpc client, with http response metadata: (Status: + // > 200 OK, Protocol HTTP/1.1). RPC error -32000 - tx added to local + // > mempool but failed to gossip: validation failed + // + // Potential parse and send transaction error on txErrCh here. + + // Close and remove from txErrChans + close(txErrCh) + delete(tClient.txErrorChans, txHashHex) + + // Remove from the txTimeoutPool. + for timeoutHeight, txErrorChans := range tClient.txTimeoutPool { + // Handled transaction isn't in this timeout height. + if _, ok := txErrorChans[txHashHex]; !ok { + continue + } + + delete(txErrorChans, txHashHex) + if len(txErrorChans) == 0 { + delete(tClient.txTimeoutPool, timeoutHeight) + } + } + + tClient.txsMutex.Unlock() + } +} + +// goTimeoutPendingTransactions monitors blocks and handles transaction timeouts. +// For each block observed, it checks if there are transactions associated with that +// block's height in the txTimeoutPool. If transactions are found, the function +// evaluates whether they have already been processed by the transaction events +// query subscription logic. If not, a timeout error is generated and sent on the +// transaction's error channel. Finally, the error channel is closed and removed +// from the txTimeoutPool. +func (tClient *txClient) goTimeoutPendingTransactions(ctx context.Context) { + // Subscribe to a sequence of committed blocks. + blockCh := tClient.blockClient.CommittedBlocksSequence(ctx).Subscribe(ctx).Ch() + + // Iterate over each incoming block. + for block := range blockCh { + select { + case <-ctx.Done(): + // Exit if the context signals done. + return + default: + } + + tClient.txsMutex.Lock() + + // Retrieve transactions associated with the current block's height. + txsByHash, ok := tClient.txTimeoutPool[block.Height()] + if !ok { + // If no transactions are found for the current block height, continue. + tClient.txsMutex.Unlock() + continue + } + + // Process each transaction for the current block height. + for txHash, txErrCh := range txsByHash { + select { + // Check if the transaction was processed by its subscription. + case err, ok := <-txErrCh: + if ok { + // Unexpected state: error channel should be closed after processing. + panic(fmt.Errorf("Expected txErrCh to be closed; received err: %w", err)) + } + // Remove the processed transaction. + delete(txsByHash, txHash) + tClient.txsMutex.Unlock() + continue + default: + } + + // Transaction was not processed by its subscription: handle timeout. + txErrCh <- tClient.getTxTimeoutError(ctx, txHash) // Send a timeout error. + close(txErrCh) // Close the error channel. + delete(txsByHash, txHash) // Remove the transaction. + } + + // Clean up the txTimeoutPool for the current block height. + delete(tClient.txTimeoutPool, block.Height()) + tClient.txsMutex.Unlock() + } +} + +// txEventFromEventBz deserializes a binary representation of a transaction event +// into a TxEvent structure. +// +// Parameters: +// - eitherEventBz: Binary data of the event, potentially encapsulating an error. +// +// Returns: +// - eitherTxEvent: The TxEvent or an encapsulated error, facilitating clear +// error management in the caller's context. +// - skip: A flag denoting if the event should be bypassed. A value of true +// suggests the event be disregarded, progressing to the succeeding message. +func (tClient *txClient) txEventFromEventBz( + eitherEventBz either.Bytes, +) (eitherTxEvent either.Either[*TxEvent], skip bool) { + + // Extract byte data from the given event. In case of failure, wrap the error + // and denote the event for skipping. + eventBz, err := eitherEventBz.ValueOrError() + if err != nil { + return either.Error[*TxEvent](err), true + } + + // Unmarshal byte data into a TxEvent object. + txEvt, err := tClient.unmarshalTxEvent(eventBz) + switch { + // If the error indicates a non-transactional event, return the TxEvent and + // signal for skipping. + case errors.Is(err, ErrNonTxEventBytes): + return either.Success(txEvt), true + // For other errors, wrap them and flag the event to be skipped. + case err != nil: + return either.Error[*TxEvent](ErrUnmarshalTx.Wrapf("%s", err)), true + } + + // For successful unmarshalling, return the TxEvent. + return either.Success(txEvt), false +} + +// unmarshalTxEvent attempts to deserialize a slice of bytes into a TxEvent. +// It checks if the given bytes correspond to a valid transaction event. +// If the resulting TxEvent has empty transaction bytes, it assumes that +// the message was not a transaction event and returns an ErrNonTxEventBytes error. +func (tClient *txClient) unmarshalTxEvent(eventBz []byte) (*TxEvent, error) { + txEvent := new(TxEvent) + + // Try to deserialize the provided bytes into a TxEvent. + if err := json.Unmarshal(eventBz, txEvent); err != nil { + return nil, err + } + + // Check if the TxEvent has empty transaction bytes, which indicates + // the message might not be a valid transaction event. + if bytes.Equal(txEvent.Tx, []byte{}) { + return nil, ErrNonTxEventBytes.Wrapf("%s", string(eventBz)) + } + + return txEvent, nil +} + +// getTxTimeoutError checks if a transaction with the specified hash has timed out. +// The function decodes the provided hexadecimal hash into bytes and queries the +// transaction using the byte hash. If any error occurs during this process, +// appropriate wrapped errors are returned for easier debugging. +func (tClient *txClient) getTxTimeoutError(ctx context.Context, txHashHex string) error { + + // Decode the provided hex hash into bytes. + txHash, err := hex.DecodeString(txHashHex) + if err != nil { + return ErrInvalidTxHash.Wrapf("%s", txHashHex) + } + + // Query the transaction using the decoded byte hash. + txResponse, err := tClient.txCtx.QueryTx(ctx, txHash, false) + if err != nil { + return ErrQueryTx.Wrapf("with hash: %s: %s", txHashHex, err) + } + + // Return a timeout error with details about the transaction. + return ErrTxTimeout.Wrapf("with hash %s: %s", txHashHex, txResponse.TxResult.Log) +} diff --git a/pkg/client/tx/client_integration_test.go b/pkg/client/tx/client_integration_test.go new file mode 100644 index 000000000..c2d5db6e5 --- /dev/null +++ b/pkg/client/tx/client_integration_test.go @@ -0,0 +1,65 @@ +//go:build integration + +package tx_test + +import ( + "context" + "testing" + + "cosmossdk.io/depinject" + "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "github.com/pokt-network/poktroll/pkg/client/tx" + + "github.com/pokt-network/poktroll/internal/testclient/testblock" + "github.com/pokt-network/poktroll/internal/testclient/testeventsquery" + "github.com/pokt-network/poktroll/internal/testclient/testtx" + "github.com/pokt-network/poktroll/pkg/client" + apptypes "github.com/pokt-network/poktroll/x/application/types" +) + +func TestTxClient_SignAndBroadcast_Integration(t *testing.T) { + t.Skip("TODO_TECHDEBT: this test depends on some setup which is currently not implemented in this test: staked application and servicer with matching services") + + var ctx = context.Background() + + keyring, signingKey := newTestKeyringWithKey(t) + + eventsQueryClient := testeventsquery.NewLocalnetClient(t) + + _, txCtx := testtx.NewAnyTimesTxTxContext(t, keyring) + + // Construct a new mock block client because it is a required dependency. Since + // we're not exercising transactions timeouts in this test, we don't need to set any + // particular expectations on it, nor do we care about the value of blockHash + // argument. + blockClientMock := testblock.NewLocalnetClient(ctx, t) + + // Construct a new depinject config with the mocks we created above. + txClientDeps := depinject.Supply( + eventsQueryClient, + txCtx, + blockClientMock, + ) + + // Construct the transaction client. + txClient, err := tx.NewTxClient(ctx, txClientDeps, tx.WithSigningKeyName(testSigningKeyName)) + require.NoError(t, err) + + signingKeyAddr, err := signingKey.GetAddress() + require.NoError(t, err) + + // Construct a valid (arbitrary) message to sign, encode, and broadcast. + appStake := types.NewCoin("upokt", types.NewInt(1000000)) + appStakeMsg := &apptypes.MsgStakeApplication{ + Address: signingKeyAddr.String(), + Stake: &appStake, + Services: client.NewTestApplicationServiceConfig(testServiceIdPrefix, 2), + } + + // Sign and broadcast the message. + eitherErr := txClient.SignAndBroadcast(ctx, appStakeMsg) + err, _ = eitherErr.SyncOrAsyncError() + require.NoError(t, err) +} diff --git a/pkg/client/tx/client_test.go b/pkg/client/tx/client_test.go new file mode 100644 index 000000000..6cb5d33da --- /dev/null +++ b/pkg/client/tx/client_test.go @@ -0,0 +1,413 @@ +package tx_test + +import ( + "context" + "encoding/json" + "testing" + "time" + + "cosmossdk.io/depinject" + cometbytes "github.com/cometbft/cometbft/libs/bytes" + cosmoskeyring "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/types" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + + "github.com/pokt-network/poktroll/internal/mocks/mockclient" + "github.com/pokt-network/poktroll/internal/testclient" + "github.com/pokt-network/poktroll/internal/testclient/testblock" + "github.com/pokt-network/poktroll/internal/testclient/testeventsquery" + "github.com/pokt-network/poktroll/internal/testclient/testtx" + "github.com/pokt-network/poktroll/pkg/client" + "github.com/pokt-network/poktroll/pkg/client/tx" + "github.com/pokt-network/poktroll/pkg/either" + apptypes "github.com/pokt-network/poktroll/x/application/types" +) + +const ( + testSigningKeyName = "test_signer" + // NB: testServiceIdPrefix must not be longer than 7 characters due to + // maxServiceIdLen. + testServiceIdPrefix = "testsvc" + txCommitTimeout = 10 * time.Millisecond +) + +// TODO_TECHDEBT: add coverage for the transactions client handling an events bytes error either. + +func TestTxClient_SignAndBroadcast_Succeeds(t *testing.T) { + var ( + // expectedTx is the expected transactions bytes that will be signed and broadcast + // by the transaction client. It is computed and assigned in the + // testtx.NewOneTimeTxTxContext helper function. The same reference needs + // to be used across the expectations that are set on the transactions context mock. + expectedTx cometbytes.HexBytes + // eventsBzPublishCh is the channel that the mock events query client + // will use to publish the transactions event bytes. It is used near the end of + // the test to mock the network signaling that the transactions was committed. + eventsBzPublishCh chan<- either.Bytes + blocksPublishCh chan client.Block + ctx = context.Background() + ) + + keyring, signingKey := newTestKeyringWithKey(t) + + eventsQueryClient := testeventsquery.NewOneTimeTxEventsQueryClient( + ctx, t, signingKey, &eventsBzPublishCh, + ) + + txCtxMock := testtx.NewOneTimeTxTxContext( + t, keyring, + testSigningKeyName, + &expectedTx, + ) + + // Construct a new mock block client because it is a required dependency. + // Since we're not exercising transactions timeouts in this test, we don't need to + // set any particular expectations on it, nor do we care about the contents + // of the latest block. + blockClientMock := testblock.NewOneTimeCommittedBlocksSequenceBlockClient( + t, blocksPublishCh, + ) + + // Construct a new depinject config with the mocks we created above. + txClientDeps := depinject.Supply( + eventsQueryClient, + txCtxMock, + blockClientMock, + ) + + // Construct the transaction client. + txClient, err := tx.NewTxClient( + ctx, txClientDeps, tx.WithSigningKeyName(testSigningKeyName), + ) + require.NoError(t, err) + + signingKeyAddr, err := signingKey.GetAddress() + require.NoError(t, err) + + // Construct a valid (arbitrary) message to sign, encode, and broadcast. + appStake := types.NewCoin("upokt", types.NewInt(1000000)) + appStakeMsg := &apptypes.MsgStakeApplication{ + Address: signingKeyAddr.String(), + Stake: &appStake, + Services: client.NewTestApplicationServiceConfig(testServiceIdPrefix, 2), + } + + // Sign and broadcast the message. + eitherErr := txClient.SignAndBroadcast(ctx, appStakeMsg) + err, errCh := eitherErr.SyncOrAsyncError() + require.NoError(t, err) + + // Construct the expected transaction event bytes from the expected transaction bytes. + txEventBz, err := json.Marshal(&tx.TxEvent{Tx: expectedTx}) + require.NoError(t, err) + + // Publish the transaction event bytes to the events query client so that the transaction client + // registers the transactions as committed (i.e. removes it from the timeout pool). + eventsBzPublishCh <- either.Success[[]byte](txEventBz) + + // Assert that the error channel was closed without receiving. + select { + case err, ok := <-errCh: + require.NoError(t, err) + require.Falsef(t, ok, "expected errCh to be closed") + case <-time.After(txCommitTimeout): + t.Fatal("test timed out waiting for errCh to receive") + } +} + +func TestTxClient_NewTxClient_Error(t *testing.T) { + // Construct an empty in-memory keyring. + keyring := cosmoskeyring.NewInMemory(testclient.EncodingConfig.Marshaler) + + tests := []struct { + name string + signingKeyName string + expectedErr error + }{ + { + name: "empty signing key name", + signingKeyName: "", + expectedErr: tx.ErrEmptySigningKeyName, + }, + { + name: "signing key does not exist", + signingKeyName: "nonexistent", + expectedErr: tx.ErrNoSuchSigningKey, + }, + // TODO_TECHDEBT: add coverage for this error case + // { + // name: "failed to get address", + // testSigningKeyName: "incompatible", + // expectedErr: tx.ErrSigningKeyAddr, + // }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var ( + ctrl = gomock.NewController(t) + ctx = context.Background() + ) + + // Construct a new mock events query client. Since we expect the + // NewTxClient call to fail, we don't need to set any expectations + // on this mock. + eventsQueryClient := mockclient.NewMockEventsQueryClient(ctrl) + + // Construct a new mock transactions context. + txCtxMock, _ := testtx.NewAnyTimesTxTxContext(t, keyring) + + // Construct a new mock block client. Since we expect the NewTxClient + // call to fail, we don't need to set any expectations on this mock. + blockClientMock := mockclient.NewMockBlockClient(ctrl) + + // Construct a new depinject config with the mocks we created above. + txClientDeps := depinject.Supply( + eventsQueryClient, + txCtxMock, + blockClientMock, + ) + + // Construct a signing key option using the test signing key name. + signingKeyOpt := tx.WithSigningKeyName(tt.signingKeyName) + + // Attempt to create the transactions client. + txClient, err := tx.NewTxClient(ctx, txClientDeps, signingKeyOpt) + require.ErrorIs(t, err, tt.expectedErr) + require.Nil(t, txClient) + }) + } +} + +func TestTxClient_SignAndBroadcast_SyncError(t *testing.T) { + var ( + // eventsBzPublishCh is the channel that the mock events query client + // will use to publish the transactions event bytes. It is not used in + // this test but is required to use the NewOneTimeTxEventsQueryClient + // helper. + eventsBzPublishCh chan<- either.Bytes + // blocksPublishCh is the channel that the mock block client will use + // to publish the latest block. It is not used in this test but is + // required to use the NewOneTimeCommittedBlocksSequenceBlockClient + // helper. + blocksPublishCh chan client.Block + ctx = context.Background() + ) + + keyring, signingKey := newTestKeyringWithKey(t) + + // Construct a new mock events query client. Since we expect the + // NewTxClient call to fail, we don't need to set any expectations + // on this mock. + eventsQueryClient := testeventsquery.NewOneTimeTxEventsQueryClient( + ctx, t, signingKey, &eventsBzPublishCh, + ) + + // Construct a new mock transaction context. + txCtxMock, _ := testtx.NewAnyTimesTxTxContext(t, keyring) + + // Construct a new mock block client because it is a required dependency. + // Since we're not exercising transactions timeouts in this test, we don't need to + // set any particular expectations on it, nor do we care about the contents + // of the latest block. + blockClientMock := testblock.NewOneTimeCommittedBlocksSequenceBlockClient( + t, blocksPublishCh, + ) + + // Construct a new depinject config with the mocks we created above. + txClientDeps := depinject.Supply( + eventsQueryClient, + txCtxMock, + blockClientMock, + ) + + // Construct the transaction client. + txClient, err := tx.NewTxClient( + ctx, txClientDeps, tx.WithSigningKeyName(testSigningKeyName), + ) + require.NoError(t, err) + + // Construct an invalid (arbitrary) message to sign, encode, and broadcast. + signingAddr, err := signingKey.GetAddress() + require.NoError(t, err) + appStakeMsg := &apptypes.MsgStakeApplication{ + // Providing address to avoid panic from #GetSigners(). + Address: signingAddr.String(), + Stake: nil, + // NB: explicitly omitting required fields + } + + eitherErr := txClient.SignAndBroadcast(ctx, appStakeMsg) + err, _ = eitherErr.SyncOrAsyncError() + require.ErrorIs(t, err, tx.ErrInvalidMsg) + + time.Sleep(10 * time.Millisecond) +} + +// TODO_INCOMPLETE: add coverage for async error; i.e. insufficient gas or on-chain error +func TestTxClient_SignAndBroadcast_CheckTxError(t *testing.T) { + var ( + // expectedErrMsg is the expected error message that will be returned + // by the transaction client. It is computed and assigned in the + // testtx.NewOneTimeErrCheckTxTxContext helper function. + expectedErrMsg string + // eventsBzPublishCh is the channel that the mock events query client + // will use to publish the transactions event bytes. It is used near the end of + // the test to mock the network signaling that the transactions was committed. + eventsBzPublishCh chan<- either.Bytes + blocksPublishCh chan client.Block + ctx = context.Background() + ) + + keyring, signingKey := newTestKeyringWithKey(t) + + eventsQueryClient := testeventsquery.NewOneTimeTxEventsQueryClient( + ctx, t, signingKey, &eventsBzPublishCh, + ) + + txCtxMock := testtx.NewOneTimeErrCheckTxTxContext( + t, keyring, + testSigningKeyName, + &expectedErrMsg, + ) + + // Construct a new mock block client because it is a required dependency. + // Since we're not exercising transactions timeouts in this test, we don't need to + // set any particular expectations on it, nor do we care about the contents + // of the latest block. + blockClientMock := testblock.NewOneTimeCommittedBlocksSequenceBlockClient( + t, blocksPublishCh, + ) + + // Construct a new depinject config with the mocks we created above. + txClientDeps := depinject.Supply( + eventsQueryClient, + txCtxMock, + blockClientMock, + ) + + // Construct the transaction client. + txClient, err := tx.NewTxClient(ctx, txClientDeps, tx.WithSigningKeyName(testSigningKeyName)) + require.NoError(t, err) + + signingKeyAddr, err := signingKey.GetAddress() + require.NoError(t, err) + + // Construct a valid (arbitrary) message to sign, encode, and broadcast. + appStake := types.NewCoin("upokt", types.NewInt(1000000)) + appStakeMsg := &apptypes.MsgStakeApplication{ + Address: signingKeyAddr.String(), + Stake: &appStake, + Services: client.NewTestApplicationServiceConfig(testServiceIdPrefix, 2), + } + + // Sign and broadcast the message. + eitherErr := txClient.SignAndBroadcast(ctx, appStakeMsg) + err, _ = eitherErr.SyncOrAsyncError() + require.ErrorIs(t, err, tx.ErrCheckTx) + require.ErrorContains(t, err, expectedErrMsg) +} + +func TestTxClient_SignAndBroadcast_Timeout(t *testing.T) { + var ( + // expectedErrMsg is the expected error message that will be returned + // by the transaction client. It is computed and assigned in the + // testtx.NewOneTimeErrCheckTxTxContext helper function. + expectedErrMsg string + // eventsBzPublishCh is the channel that the mock events query client + // will use to publish the transaction event bytes. It is used near the end of + // the test to mock the network signaling that the transaction was committed. + eventsBzPublishCh chan<- either.Bytes + blocksPublishCh = make(chan client.Block, tx.DefaultCommitTimeoutHeightOffset) + ctx = context.Background() + ) + + keyring, signingKey := newTestKeyringWithKey(t) + + eventsQueryClient := testeventsquery.NewOneTimeTxEventsQueryClient( + ctx, t, signingKey, &eventsBzPublishCh, + ) + + txCtxMock := testtx.NewOneTimeErrTxTimeoutTxContext( + t, keyring, + testSigningKeyName, + &expectedErrMsg, + ) + + // Construct a new mock block client because it is a required dependency. + // Since we're not exercising transaction timeouts in this test, we don't need to + // set any particular expectations on it, nor do we care about the contents + // of the latest block. + blockClientMock := testblock.NewOneTimeCommittedBlocksSequenceBlockClient( + t, blocksPublishCh, + ) + + // Construct a new depinject config with the mocks we created above. + txClientDeps := depinject.Supply( + eventsQueryClient, + txCtxMock, + blockClientMock, + ) + + // Construct the transaction client. + txClient, err := tx.NewTxClient( + ctx, txClientDeps, tx.WithSigningKeyName(testSigningKeyName), + ) + require.NoError(t, err) + + signingKeyAddr, err := signingKey.GetAddress() + require.NoError(t, err) + + // Construct a valid (arbitrary) message to sign, encode, and broadcast. + appStake := types.NewCoin("upokt", types.NewInt(1000000)) + appStakeMsg := &apptypes.MsgStakeApplication{ + Address: signingKeyAddr.String(), + Stake: &appStake, + Services: client.NewTestApplicationServiceConfig(testServiceIdPrefix, 2), + } + + // Sign and broadcast the message in a transaction. + eitherErr := txClient.SignAndBroadcast(ctx, appStakeMsg) + err, errCh := eitherErr.SyncOrAsyncError() + require.NoError(t, err) + + for i := 0; i < tx.DefaultCommitTimeoutHeightOffset; i++ { + blocksPublishCh <- testblock.NewAnyTimesBlock(t, []byte{}, int64(i+1)) + } + + // Assert that we receive the expected error type & message. + select { + case err := <-errCh: + require.ErrorIs(t, err, tx.ErrTxTimeout) + require.ErrorContains(t, err, expectedErrMsg) + // NB: wait 110% of txCommitTimeout; a bit longer than strictly necessary in + // order to mitigate flakiness. + case <-time.After(txCommitTimeout * 110 / 100): + t.Fatal("test timed out waiting for errCh to receive") + } + + // Assert that the error channel was closed. + select { + case err, ok := <-errCh: + require.Falsef(t, ok, "expected errCh to be closed") + require.NoError(t, err) + // NB: Give the error channel some time to be ready to receive in order to + // mitigate flakiness. + case <-time.After(50 * time.Millisecond): + t.Fatal("expected errCh to be closed") + } +} + +// TODO_TECHDEBT: add coverage for sending multiple messages simultaneously +func TestTxClient_SignAndBroadcast_MultipleMsgs(t *testing.T) { + t.SkipNow() +} + +// newTestKeyringWithKey creates a new in-memory keyring with a test key +// with testSigningKeyName as its name. +func newTestKeyringWithKey(t *testing.T) (cosmoskeyring.Keyring, *cosmoskeyring.Record) { + keyring := cosmoskeyring.NewInMemory(testclient.EncodingConfig.Marshaler) + key, _ := testclient.NewKey(t, testSigningKeyName, keyring) + return keyring, key +} diff --git a/pkg/client/tx/context.go b/pkg/client/tx/context.go index 5865ae526..eca32f943 100644 --- a/pkg/client/tx/context.go +++ b/pkg/client/tx/context.go @@ -22,7 +22,7 @@ type cosmosTxContext struct { // Holds cosmos-sdk client context. // (see: https://pkg.go.dev/github.com/cosmos/cosmos-sdk@v0.47.5/client#Context) clientCtx cosmosclient.Context - // Holds the cosmos-sdk tx factory. + // Holds the cosmos-sdk transaction factory. // (see: https://pkg.go.dev/github.com/cosmos/cosmos-sdk@v0.47.5/client/tx#Factory) txFactory cosmostx.Factory } @@ -67,7 +67,7 @@ func (txCtx cosmosTxContext) SignTx( ) } -// NewTxBuilder returns a new tx builder instance using the cosmos-sdk client tx config. +// NewTxBuilder returns a new transaction builder instance using the cosmos-sdk client transaction config. func (txCtx cosmosTxContext) NewTxBuilder() cosmosclient.TxBuilder { return txCtx.clientCtx.TxConfig.NewTxBuilder() } @@ -77,14 +77,15 @@ func (txCtx cosmosTxContext) EncodeTx(txBuilder cosmosclient.TxBuilder) ([]byte, return txCtx.clientCtx.TxConfig.TxEncoder()(txBuilder.GetTx()) } -// BroadcastTx broadcasts the given tx to the network, blocking until the check-tx -// ABCI operation completes and returns a TxResponse of the tx status at that point in time. +// BroadcastTx broadcasts the given transaction to the network, blocking until the check-tx +// ABCI operation completes and returns a TxResponse of the transaction status at that point in time. func (txCtx cosmosTxContext) BroadcastTx(txBytes []byte) (*cosmostypes.TxResponse, error) { - return txCtx.clientCtx.BroadcastTxSync(txBytes) + return txCtx.clientCtx.BroadcastTxAsync(txBytes) + //return txCtx.clientCtx.BroadcastTxSync(txBytes) } // QueryTx queries the transaction based on its hash and optionally provides proof -// of the transaction. It returns the tx query result. +// of the transaction. It returns the transaction query result. func (txCtx cosmosTxContext) QueryTx( ctx context.Context, txHash []byte, diff --git a/pkg/client/tx/encoding.go b/pkg/client/tx/encoding.go new file mode 100644 index 000000000..78612e7b7 --- /dev/null +++ b/pkg/client/tx/encoding.go @@ -0,0 +1,18 @@ +package tx + +import ( + "fmt" + "strings" +) + +// normalizeTxHashHex defines canonical and unambiguous representation for a +// transaction hash hexadecimal string; lower-case. +func normalizeTxHashHex(txHash string) string { + return strings.ToLower(txHash) +} + +// txHashBytesToNormalizedHex converts a transaction hash bytes to a normalized +// hexadecimal string representation. +func txHashBytesToNormalizedHex(txHash []byte) string { + return normalizeTxHashHex(fmt.Sprintf("%x", txHash)) +} diff --git a/pkg/client/tx/errors.go b/pkg/client/tx/errors.go new file mode 100644 index 000000000..474f2ac19 --- /dev/null +++ b/pkg/client/tx/errors.go @@ -0,0 +1,53 @@ +package tx + +import errorsmod "cosmossdk.io/errors" + +var ( + // ErrEmptySigningKeyName represents an error which indicates that the + // provided signing key name is empty or unspecified. + ErrEmptySigningKeyName = errorsmod.Register(codespace, 1, "empty signing key name") + + // ErrNoSuchSigningKey represents an error signifying that the requested + // signing key does not exist or could not be located. + ErrNoSuchSigningKey = errorsmod.Register(codespace, 2, "signing key does not exist") + + // ErrSigningKeyAddr is raised when there's a failure in retrieving the + // associated address for the provided signing key. + ErrSigningKeyAddr = errorsmod.Register(codespace, 3, "failed to get address for signing key") + + // ErrInvalidMsg signifies that there was an issue in validating the + // transaction message. This could be due to format, content, or other + // constraints imposed on the message. + ErrInvalidMsg = errorsmod.Register(codespace, 4, "failed to validate tx message") + + // ErrCheckTx indicates an error occurred during the ABCI check transaction + // process, which verifies the transaction's integrity before it is added + // to the mempool. + ErrCheckTx = errorsmod.Register(codespace, 5, "error during ABCI check tx") + + // ErrTxTimeout is raised when a transaction has taken too long to + // complete, surpassing a predefined threshold. + ErrTxTimeout = errorsmod.Register(codespace, 6, "tx timed out") + + // ErrQueryTx indicates an error occurred while trying to query for the status + // of a specific transaction, likely due to issues with the query parameters + // or the state of the blockchain network. + ErrQueryTx = errorsmod.Register(codespace, 7, "error encountered while querying for tx") + + // ErrInvalidTxHash represents an error which is triggered when the + // transaction hash provided does not adhere to the expected format or + // constraints, implying it may be corrupted or tampered with. + ErrInvalidTxHash = errorsmod.Register(codespace, 8, "invalid tx hash") + + // ErrNonTxEventBytes indicates an attempt to deserialize bytes that do not + // correspond to a transaction event. This error is triggered when the provided + // byte data isn't recognized as a valid transaction event representation. + ErrNonTxEventBytes = errorsmod.Register(codespace, 9, "attempted to deserialize non-tx event bytes") + + // ErrUnmarshalTx signals a failure in the unmarshalling process of a transaction. + // This error is triggered when the system encounters issues translating a set of + // bytes into the corresponding Tx structure or object. + ErrUnmarshalTx = errorsmod.Register(codespace, 10, "failed to unmarshal tx") + + codespace = "tx_client" +) diff --git a/pkg/client/tx/options.go b/pkg/client/tx/options.go new file mode 100644 index 000000000..34e782b6d --- /dev/null +++ b/pkg/client/tx/options.go @@ -0,0 +1,22 @@ +package tx + +import ( + "github.com/pokt-network/poktroll/pkg/client" +) + +// WithCommitTimeoutBlocks sets the timeout duration in terms of number of blocks +// for the client to wait for broadcast transactions to be committed before +// returning a timeout error. +func WithCommitTimeoutBlocks(timeout int64) client.TxClientOption { + return func(client client.TxClient) { + client.(*txClient).commitTimeoutHeightOffset = timeout + } +} + +// WithSigningKeyName sets the name of the key which should be retrieved from the +// keyring and used for signing transactions. +func WithSigningKeyName(keyName string) client.TxClientOption { + return func(client client.TxClient) { + client.(*txClient).signingKeyName = keyName + } +} diff --git a/pkg/observable/channel/replay.go b/pkg/observable/channel/replay.go index 583edb4e5..a3935543d 100644 --- a/pkg/observable/channel/replay.go +++ b/pkg/observable/channel/replay.go @@ -38,8 +38,9 @@ type replayObservable[V any] struct { func NewReplayObservable[V any]( ctx context.Context, replayBufferSize int, + opts ...option[V], ) (observable.ReplayObservable[V], chan<- V) { - obsvbl, publishCh := NewObservable[V]() + obsvbl, publishCh := NewObservable[V](opts...) return ToReplayObservable[V](ctx, replayBufferSize, obsvbl), publishCh } From 36b6de292383e83b0dafb94458de54dd8d237c24 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Tue, 7 Nov 2023 09:44:14 +0100 Subject: [PATCH 14/27] [Off-chain] refactor: keyring errors & helpers (#131) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: add `TxClient` interface * chore: add option support to `ReplayObservable` * feat: add `txClient` implementation * test: `txClient` * test: tx client integration * chore: s/tx/transaction/g * chore: update pkg README.md template * wip: client pkg README * docs: fix client pkg godoc comment * refactor: consolidate keyring errors & helpers * refactor: keyring test helpers * fix: flakey test * chore: dial back godoc comments 😅 * chore: revise (and move to godoc.go) `testblock` & `testeventsquery` pkg godoc comment * chore: update go.mod * chore: refactor & condense godoc comments * chore: fix import paths post-update --- internal/testclient/testeventsquery/client.go | 18 +++++++----- internal/testclient/testkeyring/keyring.go | 17 +++++++++++ pkg/client/keyring/errors.go | 19 ++++++++++++ pkg/client/keyring/keyring.go | 29 +++++++++++++++++++ pkg/client/tx/client.go | 17 +++++------ pkg/client/tx/client_integration_test.go | 3 +- pkg/client/tx/client_test.go | 26 +++++++---------- pkg/client/tx/errors.go | 12 -------- 8 files changed, 94 insertions(+), 47 deletions(-) create mode 100644 internal/testclient/testkeyring/keyring.go create mode 100644 pkg/client/keyring/errors.go create mode 100644 pkg/client/keyring/keyring.go diff --git a/internal/testclient/testeventsquery/client.go b/internal/testclient/testeventsquery/client.go index 2c68606ce..fbf7daeb1 100644 --- a/internal/testclient/testeventsquery/client.go +++ b/internal/testclient/testeventsquery/client.go @@ -27,11 +27,12 @@ func NewLocalnetClient(t *testing.T, opts ...client.EventsQueryClientOption) cli } // NewOneTimeEventsQuery creates a mock of the EventsQueryClient which expects -// a single call to the EventsBytes method. query is the query string which is -// expected to be received by that call. -// It returns a mock client whose event bytes method constructs a new observable. -// The caller can simulate blockchain events by sending on the value publishCh -// points to, which is set by this helper function. +// a single call to the EventsBytes method. It returns a mock client whose event +// bytes method always constructs a new observable. query is the query string +// for which event bytes subscription is expected to be for. +// The caller can simulate blockchain events by sending on publishCh, the value +// of which is set to the publish channel of the events bytes observable publish +// channel. func NewOneTimeEventsQuery( ctx context.Context, t *testing.T, @@ -53,11 +54,12 @@ func NewOneTimeEventsQuery( return eventsQueryClient } -// NewOneTimeTxEventsQueryClient creates a mock of the Events that expects +// NewOneTimeTxEventsQueryClient creates a mock of the Events that expects to to // a single call to the EventsBytes method where the query is for transaction // events for sender address matching that of the given key. -// The caller can simulate blockchain events by sending on the value publishCh -// points to, which is set by this helper function. +// The caller can simulate blockchain events by sending on publishCh, the value +// of which is set to the publish channel of the events bytes observable publish +// channel. func NewOneTimeTxEventsQueryClient( ctx context.Context, t *testing.T, diff --git a/internal/testclient/testkeyring/keyring.go b/internal/testclient/testkeyring/keyring.go new file mode 100644 index 000000000..40fbc64c8 --- /dev/null +++ b/internal/testclient/testkeyring/keyring.go @@ -0,0 +1,17 @@ +package testkeyring + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/crypto/keyring" + + "github.com/pokt-network/poktroll/internal/testclient" +) + +// NewTestKeyringWithKey creates a new in-memory keyring with a test key +// with testSigningKeyName as its name. +func NewTestKeyringWithKey(t *testing.T, keyName string) (keyring.Keyring, *keyring.Record) { + keyring := keyring.NewInMemory(testclient.EncodingConfig.Marshaler) + key, _ := testclient.NewKey(t, keyName, keyring) + return keyring, key +} diff --git a/pkg/client/keyring/errors.go b/pkg/client/keyring/errors.go new file mode 100644 index 000000000..7be8a677a --- /dev/null +++ b/pkg/client/keyring/errors.go @@ -0,0 +1,19 @@ +package keyring + +import "cosmossdk.io/errors" + +var ( + // ErrEmptySigningKeyName represents an error which indicates that the + // provided signing key name is empty or unspecified. + ErrEmptySigningKeyName = errors.Register(codespace, 1, "empty signing key name") + + // ErrNoSuchSigningKey represents an error signifying that the requested + // signing key does not exist or could not be located. + ErrNoSuchSigningKey = errors.Register(codespace, 2, "signing key does not exist") + + // ErrSigningKeyAddr is raised when there's a failure in retrieving the + // associated address for the provided signing key. + ErrSigningKeyAddr = errors.Register(codespace, 3, "failed to get address for signing key") + + codespace = "keyring" +) diff --git a/pkg/client/keyring/keyring.go b/pkg/client/keyring/keyring.go new file mode 100644 index 000000000..a77d35b6e --- /dev/null +++ b/pkg/client/keyring/keyring.go @@ -0,0 +1,29 @@ +package keyring + +import ( + cosmoskeyring "github.com/cosmos/cosmos-sdk/crypto/keyring" + cosmostypes "github.com/cosmos/cosmos-sdk/types" +) + +// KeyNameToAddr attempts to retrieve the key with the given name from the +// given keyring and compute its address. +func KeyNameToAddr( + keyName string, + keyring cosmoskeyring.Keyring, +) (cosmostypes.AccAddress, error) { + if keyName == "" { + return nil, ErrEmptySigningKeyName + } + + keyRecord, err := keyring.Key(keyName) + if err != nil { + return nil, ErrNoSuchSigningKey.Wrapf("name %q: %s", keyName, err) + } + + signingAddr, err := keyRecord.GetAddress() + if err != nil { + return nil, ErrSigningKeyAddr.Wrapf("name %q: %s", keyName, err) + } + + return signingAddr, nil +} diff --git a/pkg/client/tx/client.go b/pkg/client/tx/client.go index 805443eb4..1c083559b 100644 --- a/pkg/client/tx/client.go +++ b/pkg/client/tx/client.go @@ -16,6 +16,7 @@ import ( "go.uber.org/multierr" "github.com/pokt-network/poktroll/pkg/client" + "github.com/pokt-network/poktroll/pkg/client/keyring" "github.com/pokt-network/poktroll/pkg/either" "github.com/pokt-network/poktroll/pkg/observable" "github.com/pokt-network/poktroll/pkg/observable/channel" @@ -245,18 +246,14 @@ func (tClient *txClient) SignAndBroadcast( // - ErrSigningKeyAddr if there's an issue retrieving the address for the signing key. // - nil if validation is successful and defaults are set appropriately. func (tClient *txClient) validateConfigAndSetDefaults() error { - if tClient.signingKeyName == "" { - return ErrEmptySigningKeyName - } - - keyRecord, err := tClient.txCtx.GetKeyring().Key(tClient.signingKeyName) - if err != nil { - return ErrNoSuchSigningKey.Wrapf("name %q: %s", tClient.signingKeyName, err) - } - signingAddr, err := keyRecord.GetAddress() + signingAddr, err := keyring.KeyNameToAddr( + tClient.signingKeyName, + tClient.txCtx.GetKeyring(), + ) if err != nil { - return ErrSigningKeyAddr.Wrapf("name %q: %s", tClient.signingKeyName, err) + return err } + tClient.signingAddr = signingAddr if tClient.commitTimeoutHeightOffset <= 0 { diff --git a/pkg/client/tx/client_integration_test.go b/pkg/client/tx/client_integration_test.go index c2d5db6e5..737c8a628 100644 --- a/pkg/client/tx/client_integration_test.go +++ b/pkg/client/tx/client_integration_test.go @@ -10,6 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" + "github.com/pokt-network/poktroll/internal/testclient/testkeyring" "github.com/pokt-network/poktroll/pkg/client/tx" "github.com/pokt-network/poktroll/internal/testclient/testblock" @@ -24,7 +25,7 @@ func TestTxClient_SignAndBroadcast_Integration(t *testing.T) { var ctx = context.Background() - keyring, signingKey := newTestKeyringWithKey(t) + keyring, signingKey := testkeyring.NewTestKeyringWithKey(t, testSigningKeyName) eventsQueryClient := testeventsquery.NewLocalnetClient(t) diff --git a/pkg/client/tx/client_test.go b/pkg/client/tx/client_test.go index 6cb5d33da..f6f1d08a9 100644 --- a/pkg/client/tx/client_test.go +++ b/pkg/client/tx/client_test.go @@ -17,8 +17,10 @@ import ( "github.com/pokt-network/poktroll/internal/testclient" "github.com/pokt-network/poktroll/internal/testclient/testblock" "github.com/pokt-network/poktroll/internal/testclient/testeventsquery" + "github.com/pokt-network/poktroll/internal/testclient/testkeyring" "github.com/pokt-network/poktroll/internal/testclient/testtx" "github.com/pokt-network/poktroll/pkg/client" + "github.com/pokt-network/poktroll/pkg/client/keyring" "github.com/pokt-network/poktroll/pkg/client/tx" "github.com/pokt-network/poktroll/pkg/either" apptypes "github.com/pokt-network/poktroll/x/application/types" @@ -49,7 +51,7 @@ func TestTxClient_SignAndBroadcast_Succeeds(t *testing.T) { ctx = context.Background() ) - keyring, signingKey := newTestKeyringWithKey(t) + keyring, signingKey := testkeyring.NewTestKeyringWithKey(t, testSigningKeyName) eventsQueryClient := testeventsquery.NewOneTimeTxEventsQueryClient( ctx, t, signingKey, &eventsBzPublishCh, @@ -118,7 +120,7 @@ func TestTxClient_SignAndBroadcast_Succeeds(t *testing.T) { func TestTxClient_NewTxClient_Error(t *testing.T) { // Construct an empty in-memory keyring. - keyring := cosmoskeyring.NewInMemory(testclient.EncodingConfig.Marshaler) + memKeyring := cosmoskeyring.NewInMemory(testclient.EncodingConfig.Marshaler) tests := []struct { name string @@ -128,12 +130,12 @@ func TestTxClient_NewTxClient_Error(t *testing.T) { { name: "empty signing key name", signingKeyName: "", - expectedErr: tx.ErrEmptySigningKeyName, + expectedErr: keyring.ErrEmptySigningKeyName, }, { name: "signing key does not exist", signingKeyName: "nonexistent", - expectedErr: tx.ErrNoSuchSigningKey, + expectedErr: keyring.ErrNoSuchSigningKey, }, // TODO_TECHDEBT: add coverage for this error case // { @@ -156,7 +158,7 @@ func TestTxClient_NewTxClient_Error(t *testing.T) { eventsQueryClient := mockclient.NewMockEventsQueryClient(ctrl) // Construct a new mock transactions context. - txCtxMock, _ := testtx.NewAnyTimesTxTxContext(t, keyring) + txCtxMock, _ := testtx.NewAnyTimesTxTxContext(t, memKeyring) // Construct a new mock block client. Since we expect the NewTxClient // call to fail, we don't need to set any expectations on this mock. @@ -195,7 +197,7 @@ func TestTxClient_SignAndBroadcast_SyncError(t *testing.T) { ctx = context.Background() ) - keyring, signingKey := newTestKeyringWithKey(t) + keyring, signingKey := testkeyring.NewTestKeyringWithKey(t, testSigningKeyName) // Construct a new mock events query client. Since we expect the // NewTxClient call to fail, we don't need to set any expectations @@ -260,7 +262,7 @@ func TestTxClient_SignAndBroadcast_CheckTxError(t *testing.T) { ctx = context.Background() ) - keyring, signingKey := newTestKeyringWithKey(t) + keyring, signingKey := testkeyring.NewTestKeyringWithKey(t, testSigningKeyName) eventsQueryClient := testeventsquery.NewOneTimeTxEventsQueryClient( ctx, t, signingKey, &eventsBzPublishCh, @@ -323,7 +325,7 @@ func TestTxClient_SignAndBroadcast_Timeout(t *testing.T) { ctx = context.Background() ) - keyring, signingKey := newTestKeyringWithKey(t) + keyring, signingKey := testkeyring.NewTestKeyringWithKey(t, testSigningKeyName) eventsQueryClient := testeventsquery.NewOneTimeTxEventsQueryClient( ctx, t, signingKey, &eventsBzPublishCh, @@ -403,11 +405,3 @@ func TestTxClient_SignAndBroadcast_Timeout(t *testing.T) { func TestTxClient_SignAndBroadcast_MultipleMsgs(t *testing.T) { t.SkipNow() } - -// newTestKeyringWithKey creates a new in-memory keyring with a test key -// with testSigningKeyName as its name. -func newTestKeyringWithKey(t *testing.T) (cosmoskeyring.Keyring, *cosmoskeyring.Record) { - keyring := cosmoskeyring.NewInMemory(testclient.EncodingConfig.Marshaler) - key, _ := testclient.NewKey(t, testSigningKeyName, keyring) - return keyring, key -} diff --git a/pkg/client/tx/errors.go b/pkg/client/tx/errors.go index 474f2ac19..1e43f1d05 100644 --- a/pkg/client/tx/errors.go +++ b/pkg/client/tx/errors.go @@ -3,18 +3,6 @@ package tx import errorsmod "cosmossdk.io/errors" var ( - // ErrEmptySigningKeyName represents an error which indicates that the - // provided signing key name is empty or unspecified. - ErrEmptySigningKeyName = errorsmod.Register(codespace, 1, "empty signing key name") - - // ErrNoSuchSigningKey represents an error signifying that the requested - // signing key does not exist or could not be located. - ErrNoSuchSigningKey = errorsmod.Register(codespace, 2, "signing key does not exist") - - // ErrSigningKeyAddr is raised when there's a failure in retrieving the - // associated address for the provided signing key. - ErrSigningKeyAddr = errorsmod.Register(codespace, 3, "failed to get address for signing key") - // ErrInvalidMsg signifies that there was an issue in validating the // transaction message. This could be due to format, content, or other // constraints imposed on the message. From 5496e429befcaf684d639c44336250ba3d2842f6 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Tue, 7 Nov 2023 09:55:19 +0100 Subject: [PATCH 15/27] [Miner] feat: add supplier client (#42) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: add `TxClient` interface * chore: add option support to `ReplayObservable` * feat: add `txClient` implementation * test: `txClient` * test: tx client integration * chore: s/tx/transaction/g * chore: update pkg README.md template * wip: client pkg README * docs: fix client pkg godoc comment * refactor: consolidate keyring errors & helpers * refactor: keyring test helpers * fix: flakey test * chore: dial back godoc comments 😅 * chore: add `SupplierClient` interface * feat: add supplier client implementation * test: supplier test helpers * test: supplier client tests * test: supplier client integration test * chore: update go.mod * trigger CI * chore: revise (and move to godoc.go) `testblock` & `testeventsquery` pkg godoc comment * chore: update go.mod * chore: refactor & condense godoc comments * chore: fix import paths post-update * chore: add godoc comment --- go.mod | 2 + go.sum | 4 + internal/testclient/keyring.go | 2 + internal/testclient/testsupplier/client.go | 37 ++++ internal/testclient/testtx/client.go | 93 +++++++++ internal/testclient/testtx/context.go | 22 ++ pkg/client/interface.go | 32 ++- pkg/client/supplier/client.go | 127 ++++++++++++ .../supplier/client_integration_test.go | 36 ++++ pkg/client/supplier/client_test.go | 190 ++++++++++++++++++ pkg/client/supplier/options.go | 14 ++ 11 files changed, 557 insertions(+), 2 deletions(-) create mode 100644 internal/testclient/testsupplier/client.go create mode 100644 internal/testclient/testtx/client.go create mode 100644 pkg/client/supplier/client.go create mode 100644 pkg/client/supplier/client_integration_test.go create mode 100644 pkg/client/supplier/client_test.go create mode 100644 pkg/client/supplier/options.go diff --git a/go.mod b/go.mod index 2d699d1cf..a8ef04265 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/gorilla/websocket v1.5.0 github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 + github.com/pokt-network/smt v0.7.1 github.com/regen-network/gocuke v0.6.2 github.com/spf13/cast v1.5.1 github.com/spf13/cobra v1.7.0 @@ -86,6 +87,7 @@ require ( github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/dgraph-io/badger/v2 v2.2007.4 // indirect github.com/dgraph-io/badger/v3 v3.2103.5 // indirect + github.com/dgraph-io/badger/v4 v4.2.0 // indirect github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect github.com/docker/go-units v0.5.0 // indirect diff --git a/go.sum b/go.sum index ef5829bd5..64f7566f0 100644 --- a/go.sum +++ b/go.sum @@ -520,6 +520,8 @@ github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdw github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg= github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw= +github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8BzuWsEs= +github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak= github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= @@ -1582,6 +1584,8 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pokt-network/smt v0.7.1 h1:WHcZeMLe+9U1/kCAhdbssdyTYzYxxb74sf8MCvG34M8= +github.com/pokt-network/smt v0.7.1/go.mod h1:K7BLEOWoZGZmY5USQuYvTkZ3qXjE6m39BMufBvVo3U8= github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4= github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= github.com/polyfloyd/go-errorlint v1.0.0/go.mod h1:KZy4xxPJyy88/gldCe5OdW6OQRtNO3EZE7hXzmnebgA= diff --git a/internal/testclient/keyring.go b/internal/testclient/keyring.go index a3c8dc14c..59d54f908 100644 --- a/internal/testclient/keyring.go +++ b/internal/testclient/keyring.go @@ -8,6 +8,8 @@ import ( "github.com/stretchr/testify/require" ) +// NewKey creates a new Secp256k1 key and mnemonic for the given name within +// the provided keyring. func NewKey( t *testing.T, name string, diff --git a/internal/testclient/testsupplier/client.go b/internal/testclient/testsupplier/client.go new file mode 100644 index 000000000..be5df6507 --- /dev/null +++ b/internal/testclient/testsupplier/client.go @@ -0,0 +1,37 @@ +package testsupplier + +import ( + "testing" + + "cosmossdk.io/depinject" + "github.com/stretchr/testify/require" + + "github.com/pokt-network/poktroll/internal/testclient/testtx" + "github.com/pokt-network/poktroll/pkg/client" + "github.com/pokt-network/poktroll/pkg/client/supplier" + "github.com/pokt-network/poktroll/pkg/client/tx" +) + +// NewLocalnetClient creates and returns a new supplier client that connects to +// the localnet sequencer. +func NewLocalnetClient( + t *testing.T, + signingKeyName string, +) client.SupplierClient { + t.Helper() + + txClientOpt := tx.WithSigningKeyName(signingKeyName) + supplierClientOpt := supplier.WithSigningKeyName(signingKeyName) + + txCtx := testtx.NewLocalnetContext(t) + txClient := testtx.NewLocalnetClient(t, txClientOpt) + + deps := depinject.Supply( + txCtx, + txClient, + ) + + supplierClient, err := supplier.NewSupplierClient(deps, supplierClientOpt) + require.NoError(t, err) + return supplierClient +} diff --git a/internal/testclient/testtx/client.go b/internal/testclient/testtx/client.go new file mode 100644 index 000000000..3e843dd91 --- /dev/null +++ b/internal/testclient/testtx/client.go @@ -0,0 +1,93 @@ +package testtx + +import ( + "context" + "testing" + "time" + + "cosmossdk.io/depinject" + cosmostypes "github.com/cosmos/cosmos-sdk/types" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + + "github.com/pokt-network/poktroll/internal/mocks/mockclient" + "github.com/pokt-network/poktroll/internal/testclient/testblock" + "github.com/pokt-network/poktroll/internal/testclient/testeventsquery" + "github.com/pokt-network/poktroll/pkg/client" + "github.com/pokt-network/poktroll/pkg/client/tx" + "github.com/pokt-network/poktroll/pkg/either" +) + +type signAndBroadcastFn func(context.Context, cosmostypes.Msg) either.AsyncError + +// TODO_CONSIDERATION: functions like these (NewLocalnetXXX) could probably accept +// and return depinject.Config arguments to support shared dependencies. + +// NewLocalnetClient creates and returns a new client for use with the localnet +// sequencer. +func NewLocalnetClient(t *testing.T, opts ...client.TxClientOption) client.TxClient { + t.Helper() + + ctx := context.Background() + txCtx := NewLocalnetContext(t) + eventsQueryClient := testeventsquery.NewLocalnetClient(t) + blockClient := testblock.NewLocalnetClient(ctx, t) + + deps := depinject.Supply( + txCtx, + eventsQueryClient, + blockClient, + ) + + txClient, err := tx.NewTxClient(ctx, deps, opts...) + require.NoError(t, err) + + return txClient +} + +// NewOneTimeDelayedSignAndBroadcastTxClient constructs a mock TxClient with the +// expectation to perform a SignAndBroadcast operation with a specified delay. +func NewOneTimeDelayedSignAndBroadcastTxClient( + t *testing.T, + delay time.Duration, +) *mockclient.MockTxClient { + t.Helper() + + signAndBroadcast := newSignAndBroadcastSucceedsDelayed(delay) + return NewOneTimeSignAndBroadcastTxClient(t, signAndBroadcast) +} + +// NewOneTimeSignAndBroadcastTxClient constructs a mock TxClient with the +// expectation to perform a SignAndBroadcast operation, which will call and receive +// the return from the given signAndBroadcast function. +func NewOneTimeSignAndBroadcastTxClient( + t *testing.T, + signAndBroadcast signAndBroadcastFn, +) *mockclient.MockTxClient { + t.Helper() + + var ctrl = gomock.NewController(t) + + txClient := mockclient.NewMockTxClient(ctrl) + txClient.EXPECT().SignAndBroadcast( + gomock.AssignableToTypeOf(context.Background()), + gomock.Any(), + ).DoAndReturn(signAndBroadcast).Times(1) + + return txClient +} + +// newSignAndBroadcastSucceedsDelayed returns a signAndBroadcastFn that succeeds +// after the given delay. +func newSignAndBroadcastSucceedsDelayed(delay time.Duration) signAndBroadcastFn { + return func(ctx context.Context, msg cosmostypes.Msg) either.AsyncError { + errCh := make(chan error) + + go func() { + time.Sleep(delay) + close(errCh) + }() + + return either.AsyncErr(errCh) + } +} diff --git a/internal/testclient/testtx/context.go b/internal/testclient/testtx/context.go index fa25494e7..134d7b2c4 100644 --- a/internal/testclient/testtx/context.go +++ b/internal/testclient/testtx/context.go @@ -23,6 +23,28 @@ import ( "github.com/pokt-network/poktroll/pkg/client/tx" ) +// NewLocalnetContext creates and returns a new transaction context configured +// for use with the localnet sequencer. +func NewLocalnetContext(t *testing.T) client.TxContext { + t.Helper() + + flagSet := testclient.NewLocalnetFlagSet(t) + clientCtx := testclient.NewLocalnetClientCtx(t, flagSet) + txFactory, err := cosmostx.NewFactoryCLI(*clientCtx, flagSet) + require.NoError(t, err) + require.NotEmpty(t, txFactory) + + deps := depinject.Supply( + *clientCtx, + txFactory, + ) + + txCtx, err := tx.NewTxContext(deps) + require.NoError(t, err) + + return txCtx +} + // TODO_IMPROVE: these mock constructor helpers could include parameters for the // "times" (e.g. exact, min, max) values which are passed to their respective // gomock.EXPECT() method calls (i.e. Times(), MinTimes(), MaxTimes()). diff --git a/pkg/client/interface.go b/pkg/client/interface.go index 32dab250c..c088df35c 100644 --- a/pkg/client/interface.go +++ b/pkg/client/interface.go @@ -1,6 +1,6 @@ //go:generate mockgen -destination=../../internal/mocks/mockclient/events_query_client_mock.go -package=mockclient . Dialer,Connection,EventsQueryClient //go:generate mockgen -destination=../../internal/mocks/mockclient/block_client_mock.go -package=mockclient . Block,BlockClient -//go:generate mockgen -destination=../../internal/mocks/mockclient/tx_client_mock.go -package=mockclient . TxContext +//go:generate mockgen -destination=../../internal/mocks/mockclient/tx_client_mock.go -package=mockclient . TxContext,TxClient //go:generate mockgen -destination=../../internal/mocks/mockclient/cosmos_tx_builder_mock.go -package=mockclient github.com/cosmos/cosmos-sdk/client TxBuilder //go:generate mockgen -destination=../../internal/mocks/mockclient/cosmos_keyring_mock.go -package=mockclient github.com/cosmos/cosmos-sdk/crypto/keyring Keyring //go:generate mockgen -destination=../../internal/mocks/mockclient/cosmos_client_mock.go -package=mockclient github.com/cosmos/cosmos-sdk/client AccountRetriever @@ -14,11 +14,36 @@ import ( cosmosclient "github.com/cosmos/cosmos-sdk/client" cosmoskeyring "github.com/cosmos/cosmos-sdk/crypto/keyring" cosmostypes "github.com/cosmos/cosmos-sdk/types" + "github.com/pokt-network/smt" "github.com/pokt-network/poktroll/pkg/either" "github.com/pokt-network/poktroll/pkg/observable" + sessiontypes "github.com/pokt-network/poktroll/x/session/types" ) +// SupplierClient is an interface for sufficient for a supplier operator to be +// able to construct blockchain transactions from pocket protocol-specific messages +// related to its role. +type SupplierClient interface { + // CreateClaim sends a claim message which creates an on-chain commitment by + // calling supplier to the given smt.SparseMerkleSumTree root hash of the given + // session's mined relays. + CreateClaim( + ctx context.Context, + sessionHeader sessiontypes.SessionHeader, + rootHash []byte, + ) error + // SubmitProof sends a proof message which contains the + // smt.SparseMerkleClosestProof, corresponding to some previously created claim + // for the same session. The proof is validated on-chain as part of the pocket + // protocol. + SubmitProof( + ctx context.Context, + sessionHeader sessiontypes.SessionHeader, + proof *smt.SparseMerkleClosestProof, + ) error +} + // TxClient provides a synchronous interface initiating and waiting for transactions // derived from cosmos-sdk messages, in a cosmos-sdk based blockchain network. type TxClient interface { @@ -79,7 +104,7 @@ type BlocksObservable observable.ReplayObservable[Block] // BlockClient is an interface which provides notifications about newly committed // blocks as well as direct access to the latest block via some blockchain API. type BlockClient interface { - // Blocks returns an observable which emits newly committed blocks. + // CommittedBlocksSequence returns an observable which emits newly committed blocks. CommittedBlocksSequence(context.Context) BlocksObservable // LatestBlock returns the latest block that has been committed. LatestBlock(context.Context) Block @@ -148,3 +173,6 @@ type EventsQueryClientOption func(EventsQueryClient) // TxClientOption defines a function type that modifies the TxClient. type TxClientOption func(TxClient) + +// SupplierClientOption defines a function type that modifies the SupplierClient. +type SupplierClientOption func(SupplierClient) diff --git a/pkg/client/supplier/client.go b/pkg/client/supplier/client.go new file mode 100644 index 000000000..a0172c3c5 --- /dev/null +++ b/pkg/client/supplier/client.go @@ -0,0 +1,127 @@ +package supplier + +import ( + "context" + + "cosmossdk.io/depinject" + cosmostypes "github.com/cosmos/cosmos-sdk/types" + "github.com/pokt-network/smt" + + "github.com/pokt-network/poktroll/pkg/client" + "github.com/pokt-network/poktroll/pkg/client/keyring" + sessiontypes "github.com/pokt-network/poktroll/x/session/types" + suppliertypes "github.com/pokt-network/poktroll/x/supplier/types" +) + +var _ client.SupplierClient = (*supplierClient)(nil) + +// supplierClient +type supplierClient struct { + signingKeyName string + signingKeyAddr cosmostypes.AccAddress + + txClient client.TxClient + txCtx client.TxContext +} + +// NewSupplierClient constructs a new SupplierClient with the given dependencies +// and options. If a signingKeyName is not configured, an error will be returned. +// +// Required dependencies: +// - client.TxClient +// - client.TxContext +// +// Available options: +// - WithSigningKeyName +func NewSupplierClient( + deps depinject.Config, + opts ...client.SupplierClientOption, +) (*supplierClient, error) { + sClient := &supplierClient{} + + if err := depinject.Inject( + deps, + &sClient.txClient, + &sClient.txCtx, + ); err != nil { + return nil, err + } + + for _, opt := range opts { + opt(sClient) + } + + if err := sClient.validateConfigAndSetDefaults(); err != nil { + return nil, err + } + + return sClient, nil +} + +// SubmitProof constructs a submit proof message then signs and broadcasts it +// to the network via #txClient. It blocks until the transaction is included in +// a block or times out. +func (sClient *supplierClient) SubmitProof( + ctx context.Context, + sessionHeader sessiontypes.SessionHeader, + proof *smt.SparseMerkleClosestProof, +) error { + proofBz, err := proof.Marshal() + if err != nil { + return err + } + + msg := &suppliertypes.MsgSubmitProof{ + SupplierAddress: sClient.signingKeyAddr.String(), + SessionHeader: &sessionHeader, + Proof: proofBz, + } + eitherErr := sClient.txClient.SignAndBroadcast(ctx, msg) + err, errCh := eitherErr.SyncOrAsyncError() + if err != nil { + return err + } + + return <-errCh +} + +// CreateClaim constructs a creates claim message then signs and broadcasts it +// to the network via #txClient. It blocks until the transaction is included in +// a block or times out. +func (sClient *supplierClient) CreateClaim( + ctx context.Context, + sessionHeader sessiontypes.SessionHeader, + rootHash []byte, +) error { + msg := &suppliertypes.MsgCreateClaim{ + SupplierAddress: sClient.signingKeyAddr.String(), + SessionHeader: &sessionHeader, + RootHash: rootHash, + } + eitherErr := sClient.txClient.SignAndBroadcast(ctx, msg) + err, errCh := eitherErr.SyncOrAsyncError() + if err != nil { + return err + } + + err = <-errCh + return err +} + +// validateConfigAndSetDefaults attempts to get the address from the keyring +// corresponding to the key whose name matches the configured signingKeyName. +// If signingKeyName is empty or the keyring does not contain the corresponding +// key, an error is returned. +func (sClient *supplierClient) validateConfigAndSetDefaults() error { + signingAddr, err := keyring.KeyNameToAddr( + sClient.signingKeyName, + sClient.txCtx.GetKeyring(), + ) + if err != nil { + return err + } + + sClient.signingKeyAddr = signingAddr + + return nil +} diff --git a/pkg/client/supplier/client_integration_test.go b/pkg/client/supplier/client_integration_test.go new file mode 100644 index 000000000..8af759186 --- /dev/null +++ b/pkg/client/supplier/client_integration_test.go @@ -0,0 +1,36 @@ +//go:build integration + +package supplier_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/pokt-network/poktroll/internal/testclient/testsupplier" + sessiontypes "github.com/pokt-network/poktroll/x/session/types" +) + +func TestNewSupplierClient_Localnet(t *testing.T) { + t.Skip("TODO_TECHDEBT: this test depends on some setup which is currently not implemented in this test: staked application and servicer with matching services") + + var ( + signingKeyName = "app1" + ctx = context.Background() + ) + + supplierClient := testsupplier.NewLocalnetClient(t, signingKeyName) + require.NotNil(t, supplierClient) + + var rootHash []byte + sessionHeader := sessiontypes.SessionHeader{ + ApplicationAddress: "", + SessionStartBlockHeight: 0, + SessionId: "", + } + err := supplierClient.CreateClaim(ctx, sessionHeader, rootHash) + require.NoError(t, err) + + require.True(t, false) +} diff --git a/pkg/client/supplier/client_test.go b/pkg/client/supplier/client_test.go new file mode 100644 index 000000000..9cb6e85c6 --- /dev/null +++ b/pkg/client/supplier/client_test.go @@ -0,0 +1,190 @@ +package supplier_test + +import ( + "context" + "crypto/sha256" + "testing" + "time" + + "cosmossdk.io/depinject" + "github.com/golang/mock/gomock" + "github.com/pokt-network/smt" + "github.com/stretchr/testify/require" + + "github.com/pokt-network/poktroll/internal/mocks/mockclient" + "github.com/pokt-network/poktroll/internal/testclient/testkeyring" + "github.com/pokt-network/poktroll/internal/testclient/testtx" + "github.com/pokt-network/poktroll/pkg/client/keyring" + "github.com/pokt-network/poktroll/pkg/client/supplier" + sessiontypes "github.com/pokt-network/poktroll/x/session/types" +) + +var testSigningKeyName = "test_signer" + +func TestNewSupplierClient(t *testing.T) { + ctrl := gomock.NewController(t) + + memKeyring, _ := testkeyring.NewTestKeyringWithKey(t, testSigningKeyName) + txCtxMock, _ := testtx.NewAnyTimesTxTxContext(t, memKeyring) + txClientMock := mockclient.NewMockTxClient(ctrl) + + deps := depinject.Supply( + txCtxMock, + txClientMock, + ) + + tests := []struct { + name string + signingKeyName string + expectedErr error + }{ + { + name: "valid signing key name", + signingKeyName: testSigningKeyName, + expectedErr: nil, + }, + { + name: "empty signing key name", + signingKeyName: "", + expectedErr: keyring.ErrEmptySigningKeyName, + }, + { + name: "no such signing key name", + signingKeyName: "nonexistent", + expectedErr: keyring.ErrNoSuchSigningKey, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + signingKeyOpt := supplier.WithSigningKeyName(tt.signingKeyName) + + supplierClient, err := supplier.NewSupplierClient(deps, signingKeyOpt) + if tt.expectedErr != nil { + require.ErrorIs(t, err, tt.expectedErr) + require.Nil(t, supplierClient) + } else { + require.NoError(t, err) + require.NotNil(t, supplierClient) + } + }) + } +} + +func TestSupplierClient_CreateClaim(t *testing.T) { + var ( + signAndBroadcastDelay = 50 * time.Millisecond + doneCh = make(chan struct{}, 1) + ctx = context.Background() + ) + + keyring, testAppKey := testkeyring.NewTestKeyringWithKey(t, testSigningKeyName) + + testAppAddr, err := testAppKey.GetAddress() + require.NoError(t, err) + + txCtxMock, _ := testtx.NewAnyTimesTxTxContext(t, keyring) + txClientMock := testtx.NewOneTimeDelayedSignAndBroadcastTxClient(t, signAndBroadcastDelay) + + signingKeyOpt := supplier.WithSigningKeyName(testAppKey.Name) + deps := depinject.Supply( + txCtxMock, + txClientMock, + ) + + supplierClient, err := supplier.NewSupplierClient(deps, signingKeyOpt) + require.NoError(t, err) + require.NotNil(t, supplierClient) + + var rootHash []byte + sessionHeader := sessiontypes.SessionHeader{ + ApplicationAddress: testAppAddr.String(), + SessionStartBlockHeight: 0, + SessionId: "", + } + + go func() { + err = supplierClient.CreateClaim(ctx, sessionHeader, rootHash) + require.NoError(t, err) + close(doneCh) + }() + + // TODO_IMPROVE: this could be rewritten to record the times at which + // things happen and then compare them to the expected times. + + select { + case <-doneCh: + t.Fatal("expected CreateClaim to block for signAndBroadcastDelay") + case <-time.After(signAndBroadcastDelay * 95 / 100): + t.Log("OK: CreateClaim blocked for at least 95% of signAndBroadcastDelay") + } + + select { + case <-time.After(signAndBroadcastDelay): + t.Fatal("expected CreateClaim to unblock after signAndBroadcastDelay") + case <-doneCh: + t.Log("OK: CreateClaim unblocked after signAndBroadcastDelay") + } +} + +func TestSupplierClient_SubmitProof(t *testing.T) { + var ( + signAndBroadcastDelay = 50 * time.Millisecond + doneCh = make(chan struct{}, 1) + ctx = context.Background() + ) + + keyring, testAppKey := testkeyring.NewTestKeyringWithKey(t, testSigningKeyName) + + testAppAddr, err := testAppKey.GetAddress() + require.NoError(t, err) + + txCtxMock, _ := testtx.NewAnyTimesTxTxContext(t, keyring) + txClientMock := testtx.NewOneTimeDelayedSignAndBroadcastTxClient(t, signAndBroadcastDelay) + + signingKeyOpt := supplier.WithSigningKeyName(testAppKey.Name) + deps := depinject.Supply( + txCtxMock, + txClientMock, + ) + + supplierClient, err := supplier.NewSupplierClient(deps, signingKeyOpt) + require.NoError(t, err) + require.NotNil(t, supplierClient) + + sessionHeader := sessiontypes.SessionHeader{ + ApplicationAddress: testAppAddr.String(), + SessionStartBlockHeight: 0, + SessionId: "", + } + + kvStore, err := smt.NewKVStore("") + require.NoError(t, err) + + tree := smt.NewSparseMerkleSumTree(kvStore, sha256.New()) + proof, err := tree.ProveClosest([]byte{1}) + require.NoError(t, err) + + go func() { + err = supplierClient.SubmitProof(ctx, sessionHeader, proof) + require.NoError(t, err) + close(doneCh) + }() + + // TODO_IMPROVE: this could be rewritten to record the times at which + // things happen and then compare them to the expected times. + + select { + case <-doneCh: + t.Fatal("expected SubmitProof to block for signAndBroadcastDelay") + case <-time.After(signAndBroadcastDelay * 95 / 100): + t.Log("OK: SubmitProof blocked for at least 95% of signAndBroadcastDelay") + } + + select { + case <-time.After(signAndBroadcastDelay): + t.Fatal("expected SubmitProof to unblock after signAndBroadcastDelay") + case <-doneCh: + t.Log("OK: SubmitProof unblocked after signAndBroadcastDelay") + } +} diff --git a/pkg/client/supplier/options.go b/pkg/client/supplier/options.go new file mode 100644 index 000000000..f4460c8c9 --- /dev/null +++ b/pkg/client/supplier/options.go @@ -0,0 +1,14 @@ +package supplier + +import ( + "github.com/pokt-network/poktroll/pkg/client" +) + +// WithSigningKeyName sets the name of the key which the supplier client should +// retrieve from the keyring to use for authoring and signing CreateClaim and +// SubmitProof messages. +func WithSigningKeyName(keyName string) client.SupplierClientOption { + return func(sClient client.SupplierClient) { + sClient.(*supplierClient).signingKeyName = keyName + } +} From 4d49a1ad718f82ea61330d1b5bab7f6e8c8f01b9 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Tue, 7 Nov 2023 11:31:13 -0800 Subject: [PATCH 16/27] Fixed some tests based on bryan's comments --- proto/pocket/shared/service.proto | 6 +- x/shared/helpers/service_test.go | 95 ++++++++++++++++++------------- 2 files changed, 60 insertions(+), 41 deletions(-) diff --git a/proto/pocket/shared/service.proto b/proto/pocket/shared/service.proto index b7185188e..8d6927515 100644 --- a/proto/pocket/shared/service.proto +++ b/proto/pocket/shared/service.proto @@ -6,6 +6,8 @@ package pocket.shared; option go_package = "github.com/pokt-network/poktroll/x/shared/types"; +// TODO_CLEANUP(@Olshansk): Add native optional identifiers once its supported; https://github.com/ignite/cli/issues/3698 + // Service message to encapsulate unique and semantic identifiers for a service on the network message Service { // For example, what if we want to request a session for a certain service but with some additional configs that identify it? @@ -17,7 +19,7 @@ message Service { // ApplicationServiceConfig holds the service configuration the application stakes for message ApplicationServiceConfig { - Service service = 1; // Unique and semantic identifier for the service + Service service = 1; // The Service for which the application is configured for // TODO_RESEARCH: There is an opportunity for applications to advertise the max // they're willing to pay for a certain configuration/price, but this is outside of scope. @@ -26,7 +28,7 @@ message ApplicationServiceConfig { // SupplierServiceConfig holds the service configuration the supplier stakes for message SupplierServiceConfig { - Service service = 1; // Unique and semantic identifier for the service + Service service = 1; // The Service for which the supplier is configured for repeated SupplierEndpoint endpoints = 2; // List of endpoints for the service // TODO_RESEARCH: There is an opportunity for supplier to advertise the min // they're willing to earn for a certain configuration/price, but this is outside of scope. diff --git a/x/shared/helpers/service_test.go b/x/shared/helpers/service_test.go index 2a63dbb95..63009cf28 100644 --- a/x/shared/helpers/service_test.go +++ b/x/shared/helpers/service_test.go @@ -3,48 +3,56 @@ package helpers import ( "testing" + "github.com/stretchr/testify/require" + sharedtypes "github.com/pokt-network/poktroll/x/shared/types" ) func TestIsValidService(t *testing.T) { tests := []struct { - testCase string + desc string + id string name string expected bool }{ { - testCase: "Valid ID and Name", + desc: "Valid ID and Name", + id: "Service1", name: "Valid Service Name", expected: true, }, { - testCase: "Valid ID and empty Name", + desc: "Valid ID and empty Name", + id: "Srv", name: "", // Valid because the service name can be empty expected: true, }, { - testCase: "ID exceeds max length", + desc: "ID exceeds max length", + id: "TooLongId123", // Exceeds maxServiceIdLength name: "Valid Name", expected: false, }, { - testCase: "Name exceeds max length", + desc: "Name exceeds max length", id: "ValidID", name: "This service name is way too long to be considered valid since it exceeds the max length", expected: false, }, { - testCase: "Empty ID is invalid", + desc: "Empty ID is invalid", + id: "", // Invalid because the service ID cannot be empty name: "Valid Name", expected: false, }, { - testCase: "Invalid characters in ID", + desc: "Invalid characters in ID", + id: "ID@Invalid", // Invalid character '@' name: "Valid Name", expected: false, @@ -52,134 +60,143 @@ func TestIsValidService(t *testing.T) { } for _, test := range tests { - t.Run(test.testCase, func(t *testing.T) { + t.Run(test.desc, func(t *testing.T) { service := &sharedtypes.Service{ Id: test.id, Name: test.name, } result := IsValidService(service) - if result != test.expected { - t.Errorf("Test Case '%s' - IsValidService() with Id: '%s', Name: '%s', expected %v, got %v", - test.testCase, test.id, test.name, test.expected, result) - } + require.Equal(t, test.expected, result) }) } } func TestIsValidServiceId(t *testing.T) { tests := []struct { - testCase string + desc string + input string expected bool }{ { - testCase: "Valid alphanumeric with hyphen", + desc: "Valid alphanumeric with hyphen", + input: "Hello-1", expected: true, }, { - testCase: "Valid alphanumeric with underscore", + desc: "Valid alphanumeric with underscore", + input: "Hello_2", expected: true, }, { - testCase: "Exceeds maximum length", + desc: "Exceeds maximum length", + input: "hello-world", expected: false, // exceeds maxServiceIdLength }, { - testCase: "Contains invalid character '@'", + desc: "Contains invalid character '@'", + input: "Hello@", expected: false, // contains invalid character '@' }, { - testCase: "All uppercase", + desc: "All uppercase", + input: "HELLO", expected: true, }, { - testCase: "Maximum length boundary", + desc: "Maximum length boundary", + input: "12345678", expected: true, // exactly maxServiceIdLength }, { - testCase: "Above maximum length boundary", + desc: "Above maximum length boundary", + input: "123456789", expected: false, // exceeds maxServiceIdLength }, { - testCase: "Contains invalid character '.'", + desc: "Contains invalid character '.'", + input: "Hello.World", expected: false, // contains invalid character '.' }, { - testCase: "Empty string", + desc: "Empty string", + input: "", expected: false, // empty string }, } for _, test := range tests { - t.Run(test.testCase, func(t *testing.T) { + t.Run(test.desc, func(t *testing.T) { result := IsValidServiceId(test.input) - if result != test.expected { - t.Errorf("Test Case '%s' - IsValidServiceId(%q) = %v, want %v", - test.testCase, test.input, result, test.expected) - } + require.Equal(t, test.expected, result) }) } } func TestIsValidEndpointUrl(t *testing.T) { tests := []struct { - testCase string + desc string input string expected bool }{ { - testCase: "valid http URL", + desc: "valid http URL", + input: "http://example.com", expected: true, }, { - testCase: "valid https URL", + desc: "valid https URL", + input: "https://example.com/path?query=value#fragment", expected: true, }, { - testCase: "valid localhost URL with scheme", + desc: "valid localhost URL with scheme", + input: "https://localhost:8081", expected: true, }, { - testCase: "valid loopback URL with scheme", + desc: "valid loopback URL with scheme", + input: "http://127.0.0.1:8081", expected: true, }, { - testCase: "invalid scheme", + desc: "invalid scheme", + input: "ftp://example.com", expected: false, }, { - testCase: "missing scheme", + desc: "missing scheme", + input: "example.com", expected: false, }, { - testCase: "invalid URL", + desc: "invalid URL", + input: "not-a-valid-url", expected: false, }, } for _, tt := range tests { - t.Run(tt.testCase, func(t *testing.T) { + t.Run(tt.desc, func(t *testing.T) { got := IsValidEndpointUrl(tt.input) - if got != tt.expected { - t.Errorf("IsValidEndpointUrl(%q) = %v, want %v", tt.input, got, tt.expected) - } + require.Equal(t, tt.expected, got) }) } } From ee3d8cba7c7fedbdc2d49b7cd98f3ac67ccb1e01 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Tue, 7 Nov 2023 11:41:48 -0800 Subject: [PATCH 17/27] Fix lingering serviceId rename --- go.mod | 4 ++-- pkg/client/services.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a8ef04265..63e3b8cd4 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( cosmossdk.io/math v1.0.1 github.com/cometbft/cometbft v0.37.2 github.com/cometbft/cometbft-db v0.8.0 + github.com/cosmos/cosmos-proto v1.0.0-beta.2 github.com/cosmos/cosmos-sdk v0.47.3 github.com/cosmos/gogoproto v1.4.10 github.com/cosmos/ibc-go/v7 v7.1.0 @@ -27,6 +28,7 @@ require ( go.uber.org/multierr v1.11.0 golang.org/x/crypto v0.12.0 golang.org/x/sync v0.3.0 + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 google.golang.org/grpc v1.56.1 gopkg.in/yaml.v2 v2.4.0 ) @@ -70,7 +72,6 @@ require ( github.com/containerd/cgroups v1.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect - github.com/cosmos/cosmos-proto v1.0.0-beta.2 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect github.com/cosmos/iavl v0.20.0 // indirect @@ -266,7 +267,6 @@ require ( gonum.org/v1/gonum v0.11.0 // indirect google.golang.org/api v0.122.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/pkg/client/services.go b/pkg/client/services.go index 0d2ca060d..1e2667cf9 100644 --- a/pkg/client/services.go +++ b/pkg/client/services.go @@ -12,7 +12,7 @@ func NewTestApplicationServiceConfig(prefix string, count int) []*sharedtypes.Ap for i, _ := range appSvcCfg { serviceId := fmt.Sprintf("%s%d", prefix, i) appSvcCfg[i] = &sharedtypes.ApplicationServiceConfig{ - ServiceId: &sharedtypes.ServiceId{Id: serviceId}, + Service: &sharedtypes.Service{Id: serviceId}, } } return appSvcCfg From f4b3e7e32ab6aa9b6f1dee8d57dd89f74c18e189 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Tue, 7 Nov 2023 12:06:56 -0800 Subject: [PATCH 18/27] Reply to some review comments --- x/supplier/types/errors.go | 2 +- x/supplier/types/message_create_claim.go | 2 +- x/supplier/types/message_create_claim_test.go | 18 +++++++++--------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/x/supplier/types/errors.go b/x/supplier/types/errors.go index ceda3f96d..9cdc8d15b 100644 --- a/x/supplier/types/errors.go +++ b/x/supplier/types/errors.go @@ -16,5 +16,5 @@ var ( ErrSupplierInvalidSessionStartHeight = sdkerrors.Register(ModuleName, 6, "invalid session start height") ErrSupplierInvalidSessionId = sdkerrors.Register(ModuleName, 7, "invalid session ID") ErrSupplierInvalidService = sdkerrors.Register(ModuleName, 8, "invalid service") - ErrSupplierInvalidRootHash = sdkerrors.Register(ModuleName, 9, "invalid root hash") + ErrSupplierInvalidClaimRootHash = sdkerrors.Register(ModuleName, 9, "invalid root hash") ) diff --git a/x/supplier/types/message_create_claim.go b/x/supplier/types/message_create_claim.go index 8a41e2ce7..0e702c956 100644 --- a/x/supplier/types/message_create_claim.go +++ b/x/supplier/types/message_create_claim.go @@ -63,7 +63,7 @@ func (msg *MsgCreateClaim) ValidateBasic() error { // Validate the root hash // TODO_IMPROVE: Only checking to make sure a non-nil hash was provided for now, but we can validate the length as well. if len(msg.RootHash) == 0 { - return sdkerrors.Wrapf(ErrSupplierInvalidRootHash, "invalid root hash (%v)", msg.RootHash) + return sdkerrors.Wrapf(ErrSupplierInvalidClaimRootHash, "invalid root hash (%v)", msg.RootHash) } return nil diff --git a/x/supplier/types/message_create_claim_test.go b/x/supplier/types/message_create_claim_test.go index a78918a3c..c46647ebe 100644 --- a/x/supplier/types/message_create_claim_test.go +++ b/x/supplier/types/message_create_claim_test.go @@ -12,13 +12,13 @@ import ( func TestMsgCreateClaim_ValidateBasic(t *testing.T) { tests := []struct { - testCase string + desc string msg MsgCreateClaim err error }{ { - testCase: "invalid address", + desc: "invalid address", msg: MsgCreateClaim{ SupplierAddress: "invalid_address", @@ -26,7 +26,7 @@ func TestMsgCreateClaim_ValidateBasic(t *testing.T) { err: ErrSupplierInvalidAddress, }, { - testCase: "valid address but invalid session start height", + desc: "valid address but invalid session start height", msg: MsgCreateClaim{ SupplierAddress: sample.AccAddress(), @@ -37,7 +37,7 @@ func TestMsgCreateClaim_ValidateBasic(t *testing.T) { err: ErrSupplierInvalidSessionStartHeight, }, { - testCase: "valid address and session start height but invalid session ID", + desc: "valid address and session start height but invalid session ID", msg: MsgCreateClaim{ SupplierAddress: sample.AccAddress(), @@ -49,7 +49,7 @@ func TestMsgCreateClaim_ValidateBasic(t *testing.T) { err: ErrSupplierInvalidSessionId, }, { - testCase: "valid address, session start height, session ID but invalid service", + desc: "valid address, session start height, session ID but invalid service", msg: MsgCreateClaim{ SupplierAddress: sample.AccAddress(), @@ -64,7 +64,7 @@ func TestMsgCreateClaim_ValidateBasic(t *testing.T) { err: ErrSupplierInvalidService, }, { - testCase: "valid address, session start height, session ID, service but invalid root hash", + desc: "valid address, session start height, session ID, service but invalid root hash", msg: MsgCreateClaim{ SupplierAddress: sample.AccAddress(), @@ -77,10 +77,10 @@ func TestMsgCreateClaim_ValidateBasic(t *testing.T) { }, RootHash: []byte(""), // Invalid root hash }, - err: ErrSupplierInvalidRootHash, + err: ErrSupplierInvalidClaimRootHash, }, { - testCase: "all valid inputs", + desc: "all valid inputs", msg: MsgCreateClaim{ SupplierAddress: sample.AccAddress(), @@ -97,7 +97,7 @@ func TestMsgCreateClaim_ValidateBasic(t *testing.T) { }, } for _, tt := range tests { - t.Run(tt.testCase, func(t *testing.T) { + t.Run(tt.desc, func(t *testing.T) { err := tt.msg.ValidateBasic() if tt.err != nil { require.ErrorIs(t, err, tt.err) From 334f9b416b052d8d2ffdf2da43a4edae0003242a Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Wed, 8 Nov 2023 12:10:51 -0800 Subject: [PATCH 19/27] Update x/supplier/types/errors.go Co-authored-by: Bryan White --- x/supplier/types/errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/supplier/types/errors.go b/x/supplier/types/errors.go index 9cdc8d15b..bc5a32be9 100644 --- a/x/supplier/types/errors.go +++ b/x/supplier/types/errors.go @@ -15,6 +15,6 @@ var ( ErrSupplierInvalidServiceConfig = sdkerrors.Register(ModuleName, 5, "invalid service config") ErrSupplierInvalidSessionStartHeight = sdkerrors.Register(ModuleName, 6, "invalid session start height") ErrSupplierInvalidSessionId = sdkerrors.Register(ModuleName, 7, "invalid session ID") - ErrSupplierInvalidService = sdkerrors.Register(ModuleName, 8, "invalid service") + ErrSupplierInvalidService = sdkerrors.Register(ModuleName, 8, "invalid service in supplier") ErrSupplierInvalidClaimRootHash = sdkerrors.Register(ModuleName, 9, "invalid root hash") ) From ad081eed714217f9678dad715677da71b6647a75 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Tue, 7 Nov 2023 21:13:53 +0100 Subject: [PATCH 20/27] [Testing, Docs] refactor: move /internal/... to /testutil/... for godocs (#153) * refactor: move /internal pkgs to /testutil * chore: cleanup --- internal/mocks/mocks.go | 10 ---------- pkg/client/block/client_integration_test.go | 2 +- pkg/client/block/client_test.go | 4 ++-- pkg/client/events_query/client_integration_test.go | 2 +- pkg/client/events_query/client_test.go | 9 +++++---- pkg/client/interface.go | 12 ++++++------ pkg/client/supplier/client_integration_test.go | 2 +- pkg/client/supplier/client_test.go | 7 ++++--- pkg/client/tx/client_integration_test.go | 8 ++++---- pkg/client/tx/client_test.go | 13 +++++++------ pkg/observable/channel/observable_test.go | 4 ++-- pkg/observable/channel/replay_test.go | 2 +- {internal/mocks => testutil}/mockclient/mocks.go | 0 {internal => testutil}/testchannel/drain.go | 0 {internal => testutil}/testclient/keyring.go | 0 {internal => testutil}/testclient/localnet.go | 0 .../testclient/testblock/client.go | 7 ++++--- .../testclient/testblock/godoc.go | 0 .../testclient/testeventsquery/client.go | 5 +++-- .../testclient/testeventsquery/connection.go | 2 +- .../testclient/testeventsquery/godoc.go | 0 .../testclient/testkeyring/keyring.go | 2 +- .../testclient/testsupplier/client.go | 2 +- {internal => testutil}/testclient/testtx/client.go | 7 ++++--- {internal => testutil}/testclient/testtx/context.go | 5 +++-- {internal => testutil}/testerrors/require.go | 0 26 files changed, 51 insertions(+), 54 deletions(-) delete mode 100644 internal/mocks/mocks.go rename {internal/mocks => testutil}/mockclient/mocks.go (100%) rename {internal => testutil}/testchannel/drain.go (100%) rename {internal => testutil}/testclient/keyring.go (100%) rename {internal => testutil}/testclient/localnet.go (100%) rename {internal => testutil}/testclient/testblock/client.go (95%) rename {internal => testutil}/testclient/testblock/godoc.go (100%) rename {internal => testutil}/testclient/testeventsquery/client.go (97%) rename {internal => testutil}/testclient/testeventsquery/connection.go (95%) rename {internal => testutil}/testclient/testeventsquery/godoc.go (100%) rename {internal => testutil}/testclient/testkeyring/keyring.go (88%) rename {internal => testutil}/testclient/testsupplier/client.go (93%) rename {internal => testutil}/testclient/testtx/client.go (93%) rename {internal => testutil}/testclient/testtx/context.go (98%) rename {internal => testutil}/testerrors/require.go (100%) diff --git a/internal/mocks/mocks.go b/internal/mocks/mocks.go deleted file mode 100644 index 423f63d3e..000000000 --- a/internal/mocks/mocks.go +++ /dev/null @@ -1,10 +0,0 @@ -package mocks - -// This file is in place to declare the package for dynamically generated structs. -// -// Note that this does not follow the Cosmos SDK pattern of committing Mocks to main. -// For example, they commit auto-generate code to main: https://github.com/cosmos/cosmos-sdk/blob/main/x/gov/testutil/expected_keepers_mocks.go -// Documentation on how Cosmos uses mockgen can be found here: https://docs.cosmos.network/main/build/building-modules/testing#unit-tests -// -// IMPORTANT: We have attempted to use `.gitkeep` files instead, but it causes a circular dependency issue with protobuf and mock generation -// since we are leveraging `ignite` to compile `.proto` files which requires `.go` files to compile. diff --git a/pkg/client/block/client_integration_test.go b/pkg/client/block/client_integration_test.go index fd7e633ab..c242d7474 100644 --- a/pkg/client/block/client_integration_test.go +++ b/pkg/client/block/client_integration_test.go @@ -12,8 +12,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/pokt-network/poktroll/internal/testclient/testblock" "github.com/pokt-network/poktroll/pkg/client" + "github.com/pokt-network/poktroll/testutil/testclient/testblock" ) const blockIntegrationSubTimeout = 5 * time.Second diff --git a/pkg/client/block/client_test.go b/pkg/client/block/client_test.go index b2a5515b3..768fa59f9 100644 --- a/pkg/client/block/client_test.go +++ b/pkg/client/block/client_test.go @@ -10,10 +10,10 @@ import ( comettypes "github.com/cometbft/cometbft/types" "github.com/stretchr/testify/require" - "github.com/pokt-network/poktroll/internal/testclient" - "github.com/pokt-network/poktroll/internal/testclient/testeventsquery" "github.com/pokt-network/poktroll/pkg/client" "github.com/pokt-network/poktroll/pkg/client/block" + "github.com/pokt-network/poktroll/testutil/testclient" + "github.com/pokt-network/poktroll/testutil/testclient/testeventsquery" ) const ( diff --git a/pkg/client/events_query/client_integration_test.go b/pkg/client/events_query/client_integration_test.go index 05bf09c1a..a6a0e3bcd 100644 --- a/pkg/client/events_query/client_integration_test.go +++ b/pkg/client/events_query/client_integration_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/pokt-network/poktroll/internal/testclient/testeventsquery" + "github.com/pokt-network/poktroll/testutil/testclient/testeventsquery" ) // The query use to subscribe for new block events on the websocket endpoint exposed by CometBFT nodes diff --git a/pkg/client/events_query/client_test.go b/pkg/client/events_query/client_test.go index 0ba52ec88..aae5f72d2 100644 --- a/pkg/client/events_query/client_test.go +++ b/pkg/client/events_query/client_test.go @@ -13,14 +13,15 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/pokt-network/poktroll/internal/mocks/mockclient" - "github.com/pokt-network/poktroll/internal/testchannel" - "github.com/pokt-network/poktroll/internal/testclient/testeventsquery" - "github.com/pokt-network/poktroll/internal/testerrors" + "github.com/pokt-network/poktroll/testutil/mockclient" + eventsquery "github.com/pokt-network/poktroll/pkg/client/events_query" "github.com/pokt-network/poktroll/pkg/client/events_query/websocket" "github.com/pokt-network/poktroll/pkg/either" "github.com/pokt-network/poktroll/pkg/observable" + "github.com/pokt-network/poktroll/testutil/testchannel" + "github.com/pokt-network/poktroll/testutil/testclient/testeventsquery" + "github.com/pokt-network/poktroll/testutil/testerrors" ) func TestEventsQueryClient_Subscribe_Succeeds(t *testing.T) { diff --git a/pkg/client/interface.go b/pkg/client/interface.go index c088df35c..1a9bcb500 100644 --- a/pkg/client/interface.go +++ b/pkg/client/interface.go @@ -1,9 +1,9 @@ -//go:generate mockgen -destination=../../internal/mocks/mockclient/events_query_client_mock.go -package=mockclient . Dialer,Connection,EventsQueryClient -//go:generate mockgen -destination=../../internal/mocks/mockclient/block_client_mock.go -package=mockclient . Block,BlockClient -//go:generate mockgen -destination=../../internal/mocks/mockclient/tx_client_mock.go -package=mockclient . TxContext,TxClient -//go:generate mockgen -destination=../../internal/mocks/mockclient/cosmos_tx_builder_mock.go -package=mockclient github.com/cosmos/cosmos-sdk/client TxBuilder -//go:generate mockgen -destination=../../internal/mocks/mockclient/cosmos_keyring_mock.go -package=mockclient github.com/cosmos/cosmos-sdk/crypto/keyring Keyring -//go:generate mockgen -destination=../../internal/mocks/mockclient/cosmos_client_mock.go -package=mockclient github.com/cosmos/cosmos-sdk/client AccountRetriever +//go:generate mockgen -destination=../../testutil/mockclient/events_query_client_mock.go -package=mockclient . Dialer,Connection,EventsQueryClient +//go:generate mockgen -destination=../../testutil/mockclient/block_client_mock.go -package=mockclient . Block,BlockClient +//go:generate mockgen -destination=../../testutil/mockclient/tx_client_mock.go -package=mockclient . TxContext,TxClient +//go:generate mockgen -destination=../../testutil/mockclient/cosmos_tx_builder_mock.go -package=mockclient github.com/cosmos/cosmos-sdk/client TxBuilder +//go:generate mockgen -destination=../../testutil/mockclient/cosmos_keyring_mock.go -package=mockclient github.com/cosmos/cosmos-sdk/crypto/keyring Keyring +//go:generate mockgen -destination=../../testutil/mockclient/cosmos_client_mock.go -package=mockclient github.com/cosmos/cosmos-sdk/client AccountRetriever package client diff --git a/pkg/client/supplier/client_integration_test.go b/pkg/client/supplier/client_integration_test.go index 8af759186..f7ea11f56 100644 --- a/pkg/client/supplier/client_integration_test.go +++ b/pkg/client/supplier/client_integration_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/pokt-network/poktroll/internal/testclient/testsupplier" + "github.com/pokt-network/poktroll/testutil/testclient/testsupplier" sessiontypes "github.com/pokt-network/poktroll/x/session/types" ) diff --git a/pkg/client/supplier/client_test.go b/pkg/client/supplier/client_test.go index 9cb6e85c6..830426946 100644 --- a/pkg/client/supplier/client_test.go +++ b/pkg/client/supplier/client_test.go @@ -11,11 +11,12 @@ import ( "github.com/pokt-network/smt" "github.com/stretchr/testify/require" - "github.com/pokt-network/poktroll/internal/mocks/mockclient" - "github.com/pokt-network/poktroll/internal/testclient/testkeyring" - "github.com/pokt-network/poktroll/internal/testclient/testtx" + "github.com/pokt-network/poktroll/testutil/mockclient" + "github.com/pokt-network/poktroll/pkg/client/keyring" "github.com/pokt-network/poktroll/pkg/client/supplier" + "github.com/pokt-network/poktroll/testutil/testclient/testkeyring" + "github.com/pokt-network/poktroll/testutil/testclient/testtx" sessiontypes "github.com/pokt-network/poktroll/x/session/types" ) diff --git a/pkg/client/tx/client_integration_test.go b/pkg/client/tx/client_integration_test.go index 737c8a628..2716e9f64 100644 --- a/pkg/client/tx/client_integration_test.go +++ b/pkg/client/tx/client_integration_test.go @@ -10,12 +10,12 @@ import ( "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" - "github.com/pokt-network/poktroll/internal/testclient/testkeyring" "github.com/pokt-network/poktroll/pkg/client/tx" + "github.com/pokt-network/poktroll/testutil/testclient/testblock" + "github.com/pokt-network/poktroll/testutil/testclient/testeventsquery" + "github.com/pokt-network/poktroll/testutil/testclient/testkeyring" + "github.com/pokt-network/poktroll/testutil/testclient/testtx" - "github.com/pokt-network/poktroll/internal/testclient/testblock" - "github.com/pokt-network/poktroll/internal/testclient/testeventsquery" - "github.com/pokt-network/poktroll/internal/testclient/testtx" "github.com/pokt-network/poktroll/pkg/client" apptypes "github.com/pokt-network/poktroll/x/application/types" ) diff --git a/pkg/client/tx/client_test.go b/pkg/client/tx/client_test.go index f6f1d08a9..bb4f78f6b 100644 --- a/pkg/client/tx/client_test.go +++ b/pkg/client/tx/client_test.go @@ -13,16 +13,17 @@ import ( "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" - "github.com/pokt-network/poktroll/internal/mocks/mockclient" - "github.com/pokt-network/poktroll/internal/testclient" - "github.com/pokt-network/poktroll/internal/testclient/testblock" - "github.com/pokt-network/poktroll/internal/testclient/testeventsquery" - "github.com/pokt-network/poktroll/internal/testclient/testkeyring" - "github.com/pokt-network/poktroll/internal/testclient/testtx" + "github.com/pokt-network/poktroll/testutil/mockclient" + "github.com/pokt-network/poktroll/pkg/client" "github.com/pokt-network/poktroll/pkg/client/keyring" "github.com/pokt-network/poktroll/pkg/client/tx" "github.com/pokt-network/poktroll/pkg/either" + "github.com/pokt-network/poktroll/testutil/testclient" + "github.com/pokt-network/poktroll/testutil/testclient/testblock" + "github.com/pokt-network/poktroll/testutil/testclient/testeventsquery" + "github.com/pokt-network/poktroll/testutil/testclient/testkeyring" + "github.com/pokt-network/poktroll/testutil/testclient/testtx" apptypes "github.com/pokt-network/poktroll/x/application/types" ) diff --git a/pkg/observable/channel/observable_test.go b/pkg/observable/channel/observable_test.go index cb89c79d8..b278546fc 100644 --- a/pkg/observable/channel/observable_test.go +++ b/pkg/observable/channel/observable_test.go @@ -10,10 +10,10 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" - "github.com/pokt-network/poktroll/internal/testchannel" - "github.com/pokt-network/poktroll/internal/testerrors" "github.com/pokt-network/poktroll/pkg/observable" "github.com/pokt-network/poktroll/pkg/observable/channel" + "github.com/pokt-network/poktroll/testutil/testchannel" + "github.com/pokt-network/poktroll/testutil/testerrors" ) const ( diff --git a/pkg/observable/channel/replay_test.go b/pkg/observable/channel/replay_test.go index 0f9b3e9ac..6e01f123e 100644 --- a/pkg/observable/channel/replay_test.go +++ b/pkg/observable/channel/replay_test.go @@ -8,8 +8,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/pokt-network/poktroll/internal/testerrors" "github.com/pokt-network/poktroll/pkg/observable/channel" + "github.com/pokt-network/poktroll/testutil/testerrors" ) func TestReplayObservable(t *testing.T) { diff --git a/internal/mocks/mockclient/mocks.go b/testutil/mockclient/mocks.go similarity index 100% rename from internal/mocks/mockclient/mocks.go rename to testutil/mockclient/mocks.go diff --git a/internal/testchannel/drain.go b/testutil/testchannel/drain.go similarity index 100% rename from internal/testchannel/drain.go rename to testutil/testchannel/drain.go diff --git a/internal/testclient/keyring.go b/testutil/testclient/keyring.go similarity index 100% rename from internal/testclient/keyring.go rename to testutil/testclient/keyring.go diff --git a/internal/testclient/localnet.go b/testutil/testclient/localnet.go similarity index 100% rename from internal/testclient/localnet.go rename to testutil/testclient/localnet.go diff --git a/internal/testclient/testblock/client.go b/testutil/testclient/testblock/client.go similarity index 95% rename from internal/testclient/testblock/client.go rename to testutil/testclient/testblock/client.go index ebd2ebcd7..7fc1a0341 100644 --- a/internal/testclient/testblock/client.go +++ b/testutil/testclient/testblock/client.go @@ -8,12 +8,13 @@ import ( "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" - "github.com/pokt-network/poktroll/internal/mocks/mockclient" - "github.com/pokt-network/poktroll/internal/testclient" - "github.com/pokt-network/poktroll/internal/testclient/testeventsquery" + "github.com/pokt-network/poktroll/testutil/mockclient" + "github.com/pokt-network/poktroll/pkg/client" "github.com/pokt-network/poktroll/pkg/client/block" "github.com/pokt-network/poktroll/pkg/observable/channel" + "github.com/pokt-network/poktroll/testutil/testclient" + "github.com/pokt-network/poktroll/testutil/testclient/testeventsquery" ) // NewLocalnetClient creates and returns a new BlockClient that's configured for diff --git a/internal/testclient/testblock/godoc.go b/testutil/testclient/testblock/godoc.go similarity index 100% rename from internal/testclient/testblock/godoc.go rename to testutil/testclient/testblock/godoc.go diff --git a/internal/testclient/testeventsquery/client.go b/testutil/testclient/testeventsquery/client.go similarity index 97% rename from internal/testclient/testeventsquery/client.go rename to testutil/testclient/testeventsquery/client.go index fbf7daeb1..3ce867fde 100644 --- a/internal/testclient/testeventsquery/client.go +++ b/testutil/testclient/testeventsquery/client.go @@ -10,12 +10,13 @@ import ( "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" - "github.com/pokt-network/poktroll/internal/mocks/mockclient" - "github.com/pokt-network/poktroll/internal/testclient" + "github.com/pokt-network/poktroll/testutil/mockclient" + "github.com/pokt-network/poktroll/pkg/client" eventsquery "github.com/pokt-network/poktroll/pkg/client/events_query" "github.com/pokt-network/poktroll/pkg/either" "github.com/pokt-network/poktroll/pkg/observable/channel" + "github.com/pokt-network/poktroll/testutil/testclient" ) // NewLocalnetClient creates and returns a new events query client that's configured diff --git a/internal/testclient/testeventsquery/connection.go b/testutil/testclient/testeventsquery/connection.go similarity index 95% rename from internal/testclient/testeventsquery/connection.go rename to testutil/testclient/testeventsquery/connection.go index 27a38f2ae..c8af4e6ad 100644 --- a/internal/testclient/testeventsquery/connection.go +++ b/testutil/testclient/testeventsquery/connection.go @@ -5,8 +5,8 @@ import ( "github.com/golang/mock/gomock" - "github.com/pokt-network/poktroll/internal/mocks/mockclient" "github.com/pokt-network/poktroll/pkg/either" + "github.com/pokt-network/poktroll/testutil/mockclient" ) // NewOneTimeMockConnAndDialer returns a new mock connection and mock dialer that diff --git a/internal/testclient/testeventsquery/godoc.go b/testutil/testclient/testeventsquery/godoc.go similarity index 100% rename from internal/testclient/testeventsquery/godoc.go rename to testutil/testclient/testeventsquery/godoc.go diff --git a/internal/testclient/testkeyring/keyring.go b/testutil/testclient/testkeyring/keyring.go similarity index 88% rename from internal/testclient/testkeyring/keyring.go rename to testutil/testclient/testkeyring/keyring.go index 40fbc64c8..bb83baf88 100644 --- a/internal/testclient/testkeyring/keyring.go +++ b/testutil/testclient/testkeyring/keyring.go @@ -5,7 +5,7 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keyring" - "github.com/pokt-network/poktroll/internal/testclient" + "github.com/pokt-network/poktroll/testutil/testclient" ) // NewTestKeyringWithKey creates a new in-memory keyring with a test key diff --git a/internal/testclient/testsupplier/client.go b/testutil/testclient/testsupplier/client.go similarity index 93% rename from internal/testclient/testsupplier/client.go rename to testutil/testclient/testsupplier/client.go index be5df6507..f5ee73969 100644 --- a/internal/testclient/testsupplier/client.go +++ b/testutil/testclient/testsupplier/client.go @@ -6,10 +6,10 @@ import ( "cosmossdk.io/depinject" "github.com/stretchr/testify/require" - "github.com/pokt-network/poktroll/internal/testclient/testtx" "github.com/pokt-network/poktroll/pkg/client" "github.com/pokt-network/poktroll/pkg/client/supplier" "github.com/pokt-network/poktroll/pkg/client/tx" + "github.com/pokt-network/poktroll/testutil/testclient/testtx" ) // NewLocalnetClient creates and returns a new supplier client that connects to diff --git a/internal/testclient/testtx/client.go b/testutil/testclient/testtx/client.go similarity index 93% rename from internal/testclient/testtx/client.go rename to testutil/testclient/testtx/client.go index 3e843dd91..496c99023 100644 --- a/internal/testclient/testtx/client.go +++ b/testutil/testclient/testtx/client.go @@ -10,12 +10,13 @@ import ( "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" - "github.com/pokt-network/poktroll/internal/mocks/mockclient" - "github.com/pokt-network/poktroll/internal/testclient/testblock" - "github.com/pokt-network/poktroll/internal/testclient/testeventsquery" + "github.com/pokt-network/poktroll/testutil/mockclient" + "github.com/pokt-network/poktroll/pkg/client" "github.com/pokt-network/poktroll/pkg/client/tx" "github.com/pokt-network/poktroll/pkg/either" + "github.com/pokt-network/poktroll/testutil/testclient/testblock" + "github.com/pokt-network/poktroll/testutil/testclient/testeventsquery" ) type signAndBroadcastFn func(context.Context, cosmostypes.Msg) either.AsyncError diff --git a/internal/testclient/testtx/context.go b/testutil/testclient/testtx/context.go similarity index 98% rename from internal/testclient/testtx/context.go rename to testutil/testclient/testtx/context.go index 134d7b2c4..35b3dfb71 100644 --- a/internal/testclient/testtx/context.go +++ b/testutil/testclient/testtx/context.go @@ -17,10 +17,11 @@ import ( "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" - "github.com/pokt-network/poktroll/internal/mocks/mockclient" - "github.com/pokt-network/poktroll/internal/testclient" + "github.com/pokt-network/poktroll/testutil/mockclient" + "github.com/pokt-network/poktroll/pkg/client" "github.com/pokt-network/poktroll/pkg/client/tx" + "github.com/pokt-network/poktroll/testutil/testclient" ) // NewLocalnetContext creates and returns a new transaction context configured diff --git a/internal/testerrors/require.go b/testutil/testerrors/require.go similarity index 100% rename from internal/testerrors/require.go rename to testutil/testerrors/require.go From 9dea6fc50b2b551bbe8973e93a9a0a6fe9cda66e Mon Sep 17 00:00:00 2001 From: Dima Kniazev Date: Tue, 7 Nov 2023 17:08:53 -0800 Subject: [PATCH 21/27] [Code health] rename pocketd to poktroll (#157) * add more debug output * Empty commit * symlink to pocketd for now * rename pocketd * --wip-- [skip ci] * removing pocketd completely * also remove comment * --wip-- [skip ci] * add files * change git rules --- .github/label-actions.yml | 2 + .gitignore | 13 +++--- .tool-versions | 3 ++ Makefile | 41 ++++++++++--------- Tiltfile | 28 ++++++------- e2e/tests/node.go | 36 +++++++++++----- go.mod | 2 +- .../{pocketd => poktrolld}/config/app.toml | 0 .../{pocketd => poktrolld}/config/client.toml | 0 .../{pocketd => poktrolld}/config/config.toml | 0 .../client/cli/tx_delegate_to_gateway.go | 2 +- .../client/cli/tx_stake_application.go | 2 +- .../client/cli/tx_undelegate_from_gateway.go | 2 +- .../client/cli/tx_unstake_application.go | 2 +- x/gateway/client/cli/tx_stake_gateway.go | 2 +- x/gateway/client/cli/tx_unstake_gateway.go | 2 +- x/supplier/client/cli/tx_stake_supplier.go | 2 +- x/supplier/client/cli/tx_unstake_supplier.go | 2 +- 18 files changed, 82 insertions(+), 59 deletions(-) create mode 100644 .tool-versions rename localnet/{pocketd => poktrolld}/config/app.toml (100%) rename localnet/{pocketd => poktrolld}/config/client.toml (100%) rename localnet/{pocketd => poktrolld}/config/config.toml (100%) diff --git a/.github/label-actions.yml b/.github/label-actions.yml index b4dc1814d..0ab29008d 100644 --- a/.github/label-actions.yml +++ b/.github/label-actions.yml @@ -4,6 +4,7 @@ devnet-e2e-test: comment: The CI will now also run the e2e tests on devnet, which increases the time it takes to complete all CI checks. label: - devnet + - push-image # When `devnet-e2e-test` is removed, also delete `devnet` from the PR. -devnet-e2e-test: @@ -33,3 +34,4 @@ push-image: prs: unlabel: - devnet + - devnet-e2e-test diff --git a/.gitignore b/.gitignore index 5dc239e58..c893568d5 100644 --- a/.gitignore +++ b/.gitignore @@ -25,9 +25,13 @@ bin # Before we provision the localnet, `ignite` creates the accounts, genesis, etc. for us # As many of the files are dynamic, we only preserve the config files in git history. -localnet/pocketd/* -localnet/*/config/ -!localnet/*/config/{app.toml,client.toml,config.toml} +localnet/poktrolld/* +localnet/*/config/*.json +!localnet/poktrolld/config/ +!localnet/poktrolld/config/app.toml +!localnet/poktrolld/config/client.toml +!localnet/poktrolld/config/config.toml + # Macos .DS_Store @@ -45,9 +49,6 @@ localnet/*/config/ # Frontend utils ts-client/ -# asdf -.tool-versions - # Proto artifacts **/*.pb.go **/*.pb.gw.go diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 000000000..cfaba1c4f --- /dev/null +++ b/.tool-versions @@ -0,0 +1,3 @@ +# Run `asdf plugin add golang` and `asdf install` to install the dependencies, +# and `asdf current` to switch to the versions of dependencies listed below +golang 1.20.10 diff --git a/Makefile b/Makefile index 19c8fe59e..de0c67cd2 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .SILENT: -POCKETD_HOME := ./localnet/pocketd +POKTROLLD_HOME := ./localnet/poktrolld POCKET_NODE = tcp://127.0.0.1:36657 # The pocket rollup node (full node and sequencer in the localnet context) POCKET_ADDR_PREFIX = pokt @@ -113,9 +113,10 @@ localnet_down: ## Delete resources created by localnet localnet_regenesis: ## Regenerate the localnet genesis file # NOTE: intentionally not using --home flag to avoid overwriting the test keyring ignite chain init - cp -r ${HOME}/.pocket/keyring-test $(POCKETD_HOME) - cp ${HOME}/.pocket/config/*_key.json $(POCKETD_HOME)/config/ - cp ${HOME}/.pocket/config/genesis.json $(POCKETD_HOME)/config/ + mkdir -p $(POKTROLLD_HOME)/config/ + cp -r ${HOME}/.pocket/keyring-test $(POKTROLLD_HOME) + cp ${HOME}/.pocket/config/*_key.json $(POKTROLLD_HOME)/config/ + cp ${HOME}/.pocket/config/genesis.json $(POKTROLLD_HOME)/config/ ############### ### Linting ### @@ -134,7 +135,7 @@ go_imports: check_go_version ## Run goimports on all go files .PHONY: test_e2e test_e2e: ## Run all E2E tests - export POCKET_NODE=$(POCKET_NODE) POCKETD_HOME=../../$(POCKETD_HOME) && go test -v ./e2e/tests/... -tags=e2e + export POCKET_NODE=$(POCKET_NODE) POKTROLLD_HOME=../../$(POKTROLLD_HOME) && go test -v ./e2e/tests/... -tags=e2e .PHONY: go_test go_test: check_go_version ## Run all go tests @@ -231,11 +232,11 @@ todo_this_commit: ## List all the TODOs needed to be done in this commit .PHONY: gateway_list gateway_list: ## List all the staked gateways - poktrolld --home=$(POCKETD_HOME) q gateway list-gateway --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) q gateway list-gateway --node $(POCKET_NODE) .PHONY: gateway_stake gateway_stake: ## Stake tokens for the gateway specified (must specify the gateway env var) - poktrolld --home=$(POCKETD_HOME) tx gateway stake-gateway 1000upokt --keyring-backend test --from $(GATEWAY) --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) tx gateway stake-gateway 1000upokt --keyring-backend test --from $(GATEWAY) --node $(POCKET_NODE) .PHONY: gateway1_stake gateway1_stake: ## Stake gateway1 @@ -251,7 +252,7 @@ gateway3_stake: ## Stake gateway3 .PHONY: gateway_unstake gateway_unstake: ## Unstake an gateway (must specify the GATEWAY env var) - poktrolld --home=$(POCKETD_HOME) tx gateway unstake-gateway --keyring-backend test --from $(GATEWAY) --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) tx gateway unstake-gateway --keyring-backend test --from $(GATEWAY) --node $(POCKET_NODE) .PHONY: gateway1_unstake gateway1_unstake: ## Unstake gateway1 @@ -271,11 +272,11 @@ gateway3_unstake: ## Unstake gateway3 .PHONY: app_list app_list: ## List all the staked applications - poktrolld --home=$(POCKETD_HOME) q application list-application --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) q application list-application --node $(POCKET_NODE) .PHONY: app_stake app_stake: ## Stake tokens for the application specified (must specify the APP and SERVICES env vars) - poktrolld --home=$(POCKETD_HOME) tx application stake-application 1000upokt $(SERVICES) --keyring-backend test --from $(APP) --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) tx application stake-application 1000upokt $(SERVICES) --keyring-backend test --from $(APP) --node $(POCKET_NODE) .PHONY: app1_stake app1_stake: ## Stake app1 @@ -291,7 +292,7 @@ app3_stake: ## Stake app3 .PHONY: app_unstake app_unstake: ## Unstake an application (must specify the APP env var) - poktrolld --home=$(POCKETD_HOME) tx application unstake-application --keyring-backend test --from $(APP) --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) tx application unstake-application --keyring-backend test --from $(APP) --node $(POCKET_NODE) .PHONY: app1_unstake app1_unstake: ## Unstake app1 @@ -307,7 +308,7 @@ app3_unstake: ## Unstake app3 .PHONY: app_delegate app_delegate: ## Delegate trust to a gateway (must specify the APP and GATEWAY_ADDR env vars). Requires the app to be staked - poktrolld --home=$(POCKETD_HOME) tx application delegate-to-gateway $(GATEWAY_ADDR) --keyring-backend test --from $(APP) --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) tx application delegate-to-gateway $(GATEWAY_ADDR) --keyring-backend test --from $(APP) --node $(POCKET_NODE) .PHONY: app1_delegate_gateway1 app1_delegate_gateway1: ## Delegate trust to gateway1 @@ -323,7 +324,7 @@ app3_delegate_gateway3: ## Delegate trust to gateway3 .PHONY: app_undelegate app_undelegate: ## Undelegate trust to a gateway (must specify the APP and GATEWAY_ADDR env vars). Requires the app to be staked - poktrolld --home=$(POCKETD_HOME) tx application undelegate-from-gateway $(GATEWAY_ADDR) --keyring-backend test --from $(APP) --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) tx application undelegate-from-gateway $(GATEWAY_ADDR) --keyring-backend test --from $(APP) --node $(POCKET_NODE) .PHONY: app1_undelegate_gateway1 app1_undelegate_gateway1: ## Undelegate trust to gateway1 @@ -343,13 +344,13 @@ app3_undelegate_gateway3: ## Undelegate trust to gateway3 .PHONY: supplier_list supplier_list: ## List all the staked supplier - poktrolld --home=$(POCKETD_HOME) q supplier list-supplier --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) q supplier list-supplier --node $(POCKET_NODE) # TODO(@Olshansk, @okdas): Add more services (in addition to anvil) for apps and suppliers to stake for. # TODO_TECHDEBT: svc1, svc2 and svc3 below are only in place to make GetSession testable .PHONY: supplier_stake supplier_stake: ## Stake tokens for the supplier specified (must specify the APP env var) - poktrolld --home=$(POCKETD_HOME) tx supplier stake-supplier 1000upokt "$(SERVICES)" --keyring-backend test --from $(SUPPLIER) --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) tx supplier stake-supplier 1000upokt "$(SERVICES)" --keyring-backend test --from $(SUPPLIER) --node $(POCKET_NODE) .PHONY: supplier1_stake supplier1_stake: ## Stake supplier1 @@ -365,7 +366,7 @@ supplier3_stake: ## Stake supplier3 .PHONY: supplier_unstake supplier_unstake: ## Unstake an supplier (must specify the SUPPLIER env var) - poktrolld --home=$(POCKETD_HOME) tx supplier unstake-supplier --keyring-backend test --from $(SUPPLIER) --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) tx supplier unstake-supplier --keyring-backend test --from $(SUPPLIER) --node $(POCKET_NODE) .PHONY: supplier1_unstake supplier1_unstake: ## Unstake supplier1 @@ -386,10 +387,10 @@ supplier3_unstake: ## Unstake supplier3 .PHONY: acc_balance_query acc_balance_query: ## Query the balance of the account specified (make acc_balance_query ACC=pokt...) @echo "~~~ Balances ~~~" - poktrolld --home=$(POCKETD_HOME) q bank balances $(ACC) --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) q bank balances $(ACC) --node $(POCKET_NODE) @echo "~~~ Spendable Balances ~~~" @echo "Querying spendable balance for $(ACC)" - poktrolld --home=$(POCKETD_HOME) q bank spendable-balances $(ACC) --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) q bank spendable-balances $(ACC) --node $(POCKET_NODE) .PHONY: acc_balance_query_module_app acc_balance_query_module_app: ## Query the balance of the network level "application" module @@ -405,7 +406,7 @@ acc_balance_query_app1: ## Query the balance of app1 .PHONY: acc_balance_total_supply acc_balance_total_supply: ## Query the total supply of the network - poktrolld --home=$(POCKETD_HOME) q bank total --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) q bank total --node $(POCKET_NODE) ###################### ### Ignite Helpers ### @@ -413,7 +414,7 @@ acc_balance_total_supply: ## Query the total supply of the network .PHONY: ignite_acc_list ignite_acc_list: ## List all the accounts in LocalNet - ignite account list --keyring-dir=$(POCKETD_HOME) --keyring-backend test --address-prefix $(POCKET_ADDR_PREFIX) + ignite account list --keyring-dir=$(POKTROLLD_HOME) --keyring-backend test --address-prefix $(POCKET_ADDR_PREFIX) ################## ### CI Helpers ### diff --git a/Tiltfile b/Tiltfile index 17521b14b..1333a406e 100644 --- a/Tiltfile +++ b/Tiltfile @@ -60,15 +60,15 @@ def generate_config_map_yaml(name, data): # Import keyring/keybase files into Kubernetes ConfigMap k8s_yaml( generate_config_map_yaml( - "pocketd-keys", read_files_from_directory("localnet/pocketd/keyring-test/") + "poktrolld-keys", read_files_from_directory("localnet/poktrolld/keyring-test/") ) -) # pocketd/keys +) # poktrolld/keys # Import configuration files into Kubernetes ConfigMap k8s_yaml( generate_config_map_yaml( - "pocketd-configs", read_files_from_directory("localnet/pocketd/config/") + "poktrolld-configs", read_files_from_directory("localnet/poktrolld/config/") ) -) # pocketd/configs +) # poktrolld/configs # Hot reload protobuf changes local_resource( @@ -77,36 +77,36 @@ local_resource( deps=["proto"], labels=["hot-reloading"], ) -# Hot reload the pocketd binary used by the k8s cluster +# Hot reload the poktrolld binary used by the k8s cluster local_resource( - "hot-reload: pocketd", + "hot-reload: poktrolld", "GOOS=linux ignite chain build --skip-proto --output=./bin --debug -v", deps=hot_reload_dirs, labels=["hot-reloading"], resource_deps=["hot-reload: generate protobufs"], ) -# Hot reload the local pocketd binary used by the CLI +# Hot reload the local poktrolld binary used by the CLI local_resource( - "hot-reload: pocketd - local cli", + "hot-reload: poktrolld - local cli", "ignite chain build --skip-proto --debug -v -o $(go env GOPATH)/bin", deps=hot_reload_dirs, labels=["hot-reloading"], resource_deps=["hot-reload: generate protobufs"], ) -# Build an image with a pocketd binary +# Build an image with a poktrolld binary docker_build_with_restart( - "pocketd", + "poktrolld", ".", dockerfile_contents="""FROM golang:1.20.8 RUN apt-get -q update && apt-get install -qyy curl jq RUN go install github.com/go-delve/delve/cmd/dlv@latest -COPY bin/poktrolld /usr/local/bin/pocketd +COPY bin/poktrolld /usr/local/bin/poktrolld WORKDIR / """, only=["./bin/poktrolld"], entrypoint=["/bin/sh", "/scripts/pocket.sh"], - live_update=[sync("bin/poktrolld", "/usr/local/bin/pocketd")], + live_update=[sync("bin/poktrolld", "/usr/local/bin/poktrolld")], ) # Run celestia and anvil nodes @@ -119,7 +119,7 @@ helm_resource( "sequencer", sequencer_chart, flags=["--values=./localnet/kubernetes/values-common.yaml"], - image_deps=["pocketd"], + image_deps=["poktrolld"], image_keys=[("image.repository", "image.tag")], ) helm_resource( @@ -129,7 +129,7 @@ helm_resource( "--values=./localnet/kubernetes/values-common.yaml", "--set=replicaCount=" + str(localnet_config["relayers"]["count"]), ], - image_deps=["pocketd"], + image_deps=["poktrolld"], image_keys=[("image.repository", "image.tag")], ) diff --git a/e2e/tests/node.go b/e2e/tests/node.go index e46ad2889..2a8c81a73 100644 --- a/e2e/tests/node.go +++ b/e2e/tests/node.go @@ -3,9 +3,11 @@ package e2e import ( + "bytes" "fmt" "os" "os/exec" + "strings" ) var ( @@ -16,7 +18,7 @@ var ( // defaultRPCHost is the default RPC host that pocketd listens on defaultRPCHost = "127.0.0.1" // defaultHome is the default home directory for pocketd - defaultHome = os.Getenv("POCKETD_HOME") + defaultHome = os.Getenv("POKTROLLD_HOME") ) func init() { @@ -30,9 +32,10 @@ func init() { // commandResult combines the stdout, stderr, and err of an operation type commandResult struct { - Stdout string - Stderr string - Err error + Command string // the command that was executed + Stdout string // standard output + Stderr string // standard error + Err error // execution error, if any } // PocketClient is a single function interface for interacting with a node @@ -67,13 +70,26 @@ func (p *pocketdBin) RunCommandOnHost(rpcUrl string, args ...string) (*commandRe func (p *pocketdBin) runCmd(args ...string) (*commandResult, error) { base := []string{"--home", defaultHome} args = append(base, args...) + commandStr := "poktrolld " + strings.Join(args, " ") // Create a string representation of the command cmd := exec.Command("poktrolld", args...) - r := &commandResult{} - out, err := cmd.Output() - if err != nil { - return nil, err + + var stdoutBuf, stderrBuf bytes.Buffer + cmd.Stdout = &stdoutBuf + cmd.Stderr = &stderrBuf + + err := cmd.Run() + r := &commandResult{ + Command: commandStr, // Set the command string + Stdout: stdoutBuf.String(), + Stderr: stderrBuf.String(), + Err: err, } - r.Stdout = string(out) p.result = r - return r, nil + + if err != nil { + // Include the command executed in the error message for context + err = fmt.Errorf("error running command [%s]: %v, stderr: %s", commandStr, err, stderrBuf.String()) + } + + return r, err } diff --git a/go.mod b/go.mod index a8ef04265..70dafa1e4 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/pokt-network/poktroll -go 1.19 +go 1.20 require ( cosmossdk.io/api v0.3.1 diff --git a/localnet/pocketd/config/app.toml b/localnet/poktrolld/config/app.toml similarity index 100% rename from localnet/pocketd/config/app.toml rename to localnet/poktrolld/config/app.toml diff --git a/localnet/pocketd/config/client.toml b/localnet/poktrolld/config/client.toml similarity index 100% rename from localnet/pocketd/config/client.toml rename to localnet/poktrolld/config/client.toml diff --git a/localnet/pocketd/config/config.toml b/localnet/poktrolld/config/config.toml similarity index 100% rename from localnet/pocketd/config/config.toml rename to localnet/poktrolld/config/config.toml diff --git a/x/application/client/cli/tx_delegate_to_gateway.go b/x/application/client/cli/tx_delegate_to_gateway.go index ea251e6cd..b27562215 100644 --- a/x/application/client/cli/tx_delegate_to_gateway.go +++ b/x/application/client/cli/tx_delegate_to_gateway.go @@ -22,7 +22,7 @@ that delegates authority to the gateway specified to sign relays requests for th act on the behalf of the application during a session. Example: -$ poktrolld --home=$(POCKETD_HOME) tx application delegate-to-gateway $(GATEWAY_ADDR) --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, +$ poktrolld --home=$(POKTROLLD_HOME) tx application delegate-to-gateway $(GATEWAY_ADDR) --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) (err error) { gatewayAddress := args[0] diff --git a/x/application/client/cli/tx_stake_application.go b/x/application/client/cli/tx_stake_application.go index 510cfd648..86ba62893 100644 --- a/x/application/client/cli/tx_stake_application.go +++ b/x/application/client/cli/tx_stake_application.go @@ -27,7 +27,7 @@ func CmdStakeApplication() *cobra.Command { will stake the tokens and serviceIds and associate them with the application specified by the 'from' address. Example: -$ poktrolld --home=$(POCKETD_HOME) tx application stake-application 1000upokt svc1,svc2,svc3 --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, +$ poktrolld --home=$(POKTROLLD_HOME) tx application stake-application 1000upokt svc1,svc2,svc3 --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) (err error) { stakeString := args[0] diff --git a/x/application/client/cli/tx_undelegate_from_gateway.go b/x/application/client/cli/tx_undelegate_from_gateway.go index 308a5d8a0..50a755d8f 100644 --- a/x/application/client/cli/tx_undelegate_from_gateway.go +++ b/x/application/client/cli/tx_undelegate_from_gateway.go @@ -22,7 +22,7 @@ that removes the authority from the gateway specified to sign relays requests fo act on the behalf of the application during a session. Example: -$ poktrolld --home=$(POCKETD_HOME) tx application undelegate-from-gateway $(GATEWAY_ADDR) --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, +$ poktrolld --home=$(POKTROLLD_HOME) tx application undelegate-from-gateway $(GATEWAY_ADDR) --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) (err error) { gatewayAddress := args[0] diff --git a/x/application/client/cli/tx_unstake_application.go b/x/application/client/cli/tx_unstake_application.go index bfbf10e32..81011d0e0 100644 --- a/x/application/client/cli/tx_unstake_application.go +++ b/x/application/client/cli/tx_unstake_application.go @@ -22,7 +22,7 @@ func CmdUnstakeApplication() *cobra.Command { the application specified by the 'from' address. Example: -$ poktrolld --home=$(POCKETD_HOME) tx application unstake-application --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, +$ poktrolld --home=$(POKTROLLD_HOME) tx application unstake-application --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) (err error) { diff --git a/x/gateway/client/cli/tx_stake_gateway.go b/x/gateway/client/cli/tx_stake_gateway.go index 2c363b43b..97e93c812 100644 --- a/x/gateway/client/cli/tx_stake_gateway.go +++ b/x/gateway/client/cli/tx_stake_gateway.go @@ -21,7 +21,7 @@ func CmdStakeGateway() *cobra.Command { Long: `Stake a gateway with the provided parameters. This is a broadcast operation that will stake the tokens and associate them with the gateway specified by the 'from' address. Example: -$ poktrolld --home=$(POCKETD_HOME) tx gateway stake-gateway 1000upokt --keyring-backend test --from $(GATEWAY) --node $(POCKET_NODE)`, +$ poktrolld --home=$(POKTROLLD_HOME) tx gateway stake-gateway 1000upokt --keyring-backend test --from $(GATEWAY) --node $(POCKET_NODE)`, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) (err error) { clientCtx, err := client.GetClientTxContext(cmd) diff --git a/x/gateway/client/cli/tx_unstake_gateway.go b/x/gateway/client/cli/tx_unstake_gateway.go index e417b7540..bd2d38ebe 100644 --- a/x/gateway/client/cli/tx_unstake_gateway.go +++ b/x/gateway/client/cli/tx_unstake_gateway.go @@ -21,7 +21,7 @@ func CmdUnstakeGateway() *cobra.Command { Long: `Unstake a gateway. This is a broadcast operation that will unstake the gateway specified by the 'from' address. Example: -$ poktrolld --home=$(POCKETD_HOME) tx gateway unstake-gateway --keyring-backend test --from $(GATEWAY) --node $(POCKET_NODE)`, +$ poktrolld --home=$(POKTROLLD_HOME) tx gateway unstake-gateway --keyring-backend test --from $(GATEWAY) --node $(POCKET_NODE)`, Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, _ []string) (err error) { clientCtx, err := client.GetClientTxContext(cmd) diff --git a/x/supplier/client/cli/tx_stake_supplier.go b/x/supplier/client/cli/tx_stake_supplier.go index 1d721eded..f74a874fa 100644 --- a/x/supplier/client/cli/tx_stake_supplier.go +++ b/x/supplier/client/cli/tx_stake_supplier.go @@ -32,7 +32,7 @@ of comma separated values of the form 'service;url' where 'service' is the servi For example, an application that stakes for 'anvil' could be matched with a supplier staking for 'anvil;http://anvil:8547'. Example: -$ poktrolld --home=$(POCKETD_HOME) tx supplier stake-supplier 1000upokt anvil;http://anvil:8547 --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, +$ poktrolld --home=$(POKTROLLD_HOME) tx supplier stake-supplier 1000upokt anvil;http://anvil:8547 --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) (err error) { stakeString := args[0] diff --git a/x/supplier/client/cli/tx_unstake_supplier.go b/x/supplier/client/cli/tx_unstake_supplier.go index 2daf7c00a..434eceb24 100644 --- a/x/supplier/client/cli/tx_unstake_supplier.go +++ b/x/supplier/client/cli/tx_unstake_supplier.go @@ -17,7 +17,7 @@ func CmdUnstakeSupplier() *cobra.Command { Long: `Unstake an supplier with the provided parameters. This is a broadcast operation that will unstake the supplier specified by the 'from' address. Example: -$ poktrolld --home=$(POCKETD_HOME) tx supplier unstake-supplier --keyring-backend test --from $(SUPPLIER) --node $(POCKET_NODE)`, +$ poktrolld --home=$(POKTROLLD_HOME) tx supplier unstake-supplier --keyring-backend test --from $(SUPPLIER) --node $(POCKET_NODE)`, Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) (err error) { From 3fb0e6ea1a0e4ac610efe767aeef0abcf9f3a31f Mon Sep 17 00:00:00 2001 From: Redouane Lakrache Date: Wed, 8 Nov 2023 11:40:46 +0100 Subject: [PATCH 22/27] feat: implement relayerSessions and sessionTree (#105) * Implement MsgStakeSupplier * Unit tests pass * Rename some test vars * Fix typo * Unstake - WIP * Update makefile * Finished implementing unstake * Self review * [WIP] Session Hydrator * snapshot commit * Working on the tests * One HydrateSessionTest ready * Self review - before finishing tests * Prepared templates for all of the unit tests * feat: add the map channel observable operator (cherry picked from commit 22371aa550eb0060b528f4573ba6908bbdfa0c1c) * Implemented TestSession_HydrateSession_Metadata * feat: add replay observable (cherry picked from commit ab21790164ab544ae5f1508d3237a3faab33e71e) * Implemented TestSession_HydrateSession_SessionId * chore: add query client interface * chore: add query client errors * Implemented TestSession_HydrateSession_Success_BaseCase * Implemented TestSession_HydrateSession_Application * Option attempt for tests * Implemented TestSession_HydrateSession_Suppliers * test: fix false positive, prevent regression, & add comments * chore: add godoc comment * feat: add query client implementation * chore: add connection & dialer wrapper implementations * test: query client & add testquery helper pkg * chore: add go_test_integration make target * chore: add internal mocks pkg * test: query client integration test * docs: add event query client docs * chore: update go.mod * chore: re-order `eventsQueryClient` methods to improve readability * chore: add godoc comments to testclient helpers * fix: comment formatting * chore: improve comment & naming in evt query client test * test: tune events query client parameters * chore: improve godoc comments * chore: review improvements * refactor: `replayObservable` as its own interface type * refactor: `replayObservable#Next() V` to `ReplayObservable#Last(ctx, n) []V` * chore: add constructor func for `ReplayObservable` * test: reorder to improve readibility * refactor: rename and add godoc comments * chore: improve naming & comments * chore: add warning log and improve comments * test: improve and add tests * fix: interface assertion * fix: comment typo * chore: review improvements * fix: race * chore: add block client interface * chore: add `MapReplay` operator * feat: add block client * test: block client integration * test: block client * docs: fix install instructions * fix: race on eventsBytesAndConns map * fix: interface assertions Co-authored-by: Redouane Lakrache * fix: race * Apply suggestions from code review Co-authored-by: Bryan White Co-authored-by: Redouane Lakrache * [RelayerProxy] feat: implement relayerProxy struct (#82) * feat: add notifiable observable Co-authored-by: red-0ne * fixup: observable (cherry picked from commit bcf700405b5e4bd71bf9bb650c988526fa16c728) * refactor/fix: notifiable observable improvements * chore: more review improvements * refactor: renaming - `notifiable` pkg to `channel` - `notifiableObservable` struct to `channelObservable` - `observer` struct to `channelObserver` - `notifier` vars to `producer` - `notifee` vars to `observable` (or similar) * chore: update comments * refactor: simplify drainCh test helper * test: fix timeout * test: rename observable test functions * test: add test TODOs * chore: update comments * refactor: simplify observable & observer * test: fix & add observable tests * test: cleanup & comment observable tests * fixup: observable (cherry picked from commit 33f3196535b7dae154e01f93aab36f70cda8fc4f) * fixup: observable test (cherry picked from commit 9c206da115dc35843d588313c2215a0e649c6df6) * refactor: simplify & cleanup * chore: cleanup logs & comments * chore: improve comments * refactor: DrainChannel test helper * shore: cleanup & simplify * test: comment out flaky test cases * fixup: drain channel helper * chore: improve var name * fixup: drain channel helper * test: shorten timeout * chore: cleanup * chore: cleanup, simplification, review improvements (cherry picked from commit 92a547da29ec526d415f6967ccfa5988c3f5ca1d) * chore: improve comments Co-authored-by: Daniel Olshansky * chore: improve comments Co-authored-by: Daniel Olshansky * refactor: rename `Observable#Close()` to `#UnsubscribeAll()` * chore: improve comments * chore: misc. review feedback improvements * chore: improve comment * chore: review improvements * chore: last minute improvements * feat: add RelayerProxy interface * Fix grammar in comments Co-authored-by: Daniel Olshansky * chore: rename package to relayerproxy * feat: implement relayerProxy struct and its constructor args * fix: change directory structure * fix: change directory structure * chore: address change requests * chore: comment unavailable interface and its usage --------- Co-authored-by: Bryan White Co-authored-by: Daniel Olshansky * fix: RelayerProxy interface mismatch (#91) * [Observable] chore: observable touchup (#83) * chore: add `Observer#IsClosed()` to prevent redundant unsubscription (cherry picked from commit 78a9946b3f14353e79b123919416903d4622da4d) * chore: simplify channel observable (cherry picked from commit a2629c8bc3decfb5a787e453af67aa78fc8ca1ea) * test: add case for publisher w/ large buffer size, comment, & cleanup (cherry picked from commit e97b691e39af8fa1654b8d697a3b34095b32ed82) * docs: update observable pkg README.md (cherry picked from commit d5442c7062630d847e048850fa71806086f84172) * doc: fix pkg README template * chore: add `Observable#Next()` (cherry picked from commit cb4142f673fee37ead8520394e314f1fcb9d0dc9) * chore: update godoc comments * feat: seperate tests from go_develop (#89) * [E2E] Add Regression Testing for Send E2E Feature Test (#84) * chore: enforce go standard interface implementation registration (#87) * [Miner] feat: add the map channel observable operator (#92) * feat: add the map channel observable operator (cherry picked from commit 22371aa550eb0060b528f4573ba6908bbdfa0c1c) * test: fix false positive, prevent regression, & add comments * chore: add godoc comment * chore: review improvements * Reply to Red0ne's comments * Update small comment * Small self review * Updated TestSession_GetSession * Fixed last failing test * feat: add the interfaces for the RelayerSessions and SessionTree * chore: address change requests * chore: move-up comment * feat: add ExpiringSessions to RelaySessions interface * fix: use appropriate function name in comment Co-authored-by: Daniel Olshansky * feat: implement relayerSessions and sessionTree * chore: improve comments * chore: address change requests * chore: remove alias types for sessionId and block height * chore: remove previous merge changes * chore: remove added gitkeep * chore: go mod tidy * fix: use conventional module paths * chore: address change request from PRs 104 & 105 * chore: wrap long comments * chore: address change requests --------- Co-authored-by: Daniel Olshansky Co-authored-by: Bryan White Co-authored-by: harry <53987565+h5law@users.noreply.github.com> --- go.mod | 4 +- pkg/relayer/interface.go | 53 ++++++ pkg/relayer/session/errors.go | 11 ++ pkg/relayer/session/session.go | 128 +++++++++++++++ pkg/relayer/session/session_test.go | 3 + pkg/relayer/session/sessiontree.go | 210 ++++++++++++++++++++++++ pkg/relayer/session/sessiontree_test.go | 3 + 7 files changed, 410 insertions(+), 2 deletions(-) create mode 100644 pkg/relayer/interface.go create mode 100644 pkg/relayer/session/errors.go create mode 100644 pkg/relayer/session/session.go create mode 100644 pkg/relayer/session/session_test.go create mode 100644 pkg/relayer/session/sessiontree.go create mode 100644 pkg/relayer/session/sessiontree_test.go diff --git a/go.mod b/go.mod index 70dafa1e4..b4658dfb1 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( cosmossdk.io/math v1.0.1 github.com/cometbft/cometbft v0.37.2 github.com/cometbft/cometbft-db v0.8.0 + github.com/cosmos/cosmos-proto v1.0.0-beta.2 github.com/cosmos/cosmos-sdk v0.47.3 github.com/cosmos/gogoproto v1.4.10 github.com/cosmos/ibc-go/v7 v7.1.0 @@ -27,6 +28,7 @@ require ( go.uber.org/multierr v1.11.0 golang.org/x/crypto v0.12.0 golang.org/x/sync v0.3.0 + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 google.golang.org/grpc v1.56.1 gopkg.in/yaml.v2 v2.4.0 ) @@ -70,7 +72,6 @@ require ( github.com/containerd/cgroups v1.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect - github.com/cosmos/cosmos-proto v1.0.0-beta.2 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect github.com/cosmos/iavl v0.20.0 // indirect @@ -266,7 +267,6 @@ require ( gonum.org/v1/gonum v0.11.0 // indirect google.golang.org/api v0.122.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/pkg/relayer/interface.go b/pkg/relayer/interface.go new file mode 100644 index 000000000..68714efd7 --- /dev/null +++ b/pkg/relayer/interface.go @@ -0,0 +1,53 @@ +package relayer + +import ( + "github.com/pokt-network/smt" + + "github.com/pokt-network/poktroll/pkg/observable" + sessiontypes "github.com/pokt-network/poktroll/x/session/types" +) + +// RelayerSessionsManager is an interface for managing the relayer's sessions and Sparse +// Merkle Sum Trees (SMSTs). It provides notifications about closing sessions that are +// ready to be claimed, and handles the creation and retrieval of SMSTs for a given session. +// It also handles the creation and retrieval of SMSTs for a given session. +type RelayerSessionsManager interface { + // SessionsToClaim returns an observable that notifies of sessions ready to be claimed. + SessionsToClaim() observable.Observable[SessionTree] + + // EnsureSessionTree returns the SMST (Sparse Merkle State Tree) for a given session. + // It is used to retrieve the SMST and update it when a Relay has been successfully served. + // If the session is seen for the first time, it creates a new SMST for it before returning it. + // An error is returned if the corresponding KVStore for SMST fails to be created. + EnsureSessionTree(session *sessiontypes.Session) (SessionTree, error) +} + +// SessionTree is an interface that wraps an SMST (Sparse Merkle State Tree) and its corresponding session. +type SessionTree interface { + // GetSession returns the session corresponding to the SMST. + GetSession() *sessiontypes.Session + + // Update is a wrapper for the SMST's Update function. It updates the SMST with + // the given key, value, and weight. + // This function should be called when a Relay has been successfully served. + Update(key, value []byte, weight uint64) error + + // ProveClosest is a wrapper for the SMST's ProveClosest function. It returns the + // proof for the given path. + // This function should be called several blocks after a session has been claimed and needs to be proven. + ProveClosest(path []byte) (proof *smt.SparseMerkleClosestProof, err error) + + // Flush gets the root hash of the SMST needed for submitting the claim; + // then commits the entire tree to disk and stops the KVStore. + // It should be called before submitting the claim on-chain. This function frees up + // the in-memory resources used by the SMST that are no longer needed while waiting + // for the proof submission window to open. + Flush() (SMSTRoot []byte, err error) + + // TODO_DISCUSS: This function should not be part of the interface as it is an optimization + // aiming to free up KVStore resources after the proof is no longer needed. + // Delete deletes the SMST from the KVStore. + // WARNING: This function should be called only after the proof has been successfully + // submitted on-chain and the servicer has confirmed that it has been rewarded. + Delete() error +} diff --git a/pkg/relayer/session/errors.go b/pkg/relayer/session/errors.go new file mode 100644 index 000000000..adf5a403b --- /dev/null +++ b/pkg/relayer/session/errors.go @@ -0,0 +1,11 @@ +package session + +import sdkerrors "cosmossdk.io/errors" + +var ( + codespace = "relayer/session" + ErrSessionTreeClosed = sdkerrors.Register(codespace, 1, "session tree already closed") + ErrSessionTreeNotClosed = sdkerrors.Register(codespace, 2, "session tree not closed") + ErrSessionStorePathExists = sdkerrors.Register(codespace, 3, "session store path already exists") + ErrSessionTreeProofPathMismatch = sdkerrors.Register(codespace, 4, "session tree proof path mismatch") +) diff --git a/pkg/relayer/session/session.go b/pkg/relayer/session/session.go new file mode 100644 index 000000000..17b1282c0 --- /dev/null +++ b/pkg/relayer/session/session.go @@ -0,0 +1,128 @@ +package session + +import ( + "context" + "log" + "sync" + + blockclient "github.com/pokt-network/poktroll/pkg/client" + "github.com/pokt-network/poktroll/pkg/observable" + "github.com/pokt-network/poktroll/pkg/observable/channel" + "github.com/pokt-network/poktroll/pkg/relayer" + sessiontypes "github.com/pokt-network/poktroll/x/session/types" +) + +var _ relayer.RelayerSessionsManager = (*relayerSessionsManager)(nil) + +type sessionsTreesMap = map[int64]map[string]relayer.SessionTree + +// relayerSessionsManager is an implementation of the RelayerSessions interface. +// TODO_TEST: Add tests to the relayerSessionsManager. +type relayerSessionsManager struct { + // sessionsToClaim notifies about sessions that are ready to be claimed. + sessionsToClaim observable.Observable[relayer.SessionTree] + + // sessionsToClaimPublisher is the channel used to publish sessions to claim. + sessionsToClaimPublisher chan<- relayer.SessionTree + + // sessionTrees is a map of block heights pointing to a map of SessionTrees + // indexed by their sessionId. + // The block height index is used to know when the sessions contained in the entry should be closed, + // this helps to avoid iterating over all sessionsTrees to check if they are ready to be closed. + sessionsTrees sessionsTreesMap + sessionsTreesMu *sync.Mutex + + // blockClient is used to get the notifications of committed blocks. + blockClient blockclient.BlockClient + + // storesDirectory points to a path on disk where KVStore data files are created. + storesDirectory string +} + +// NewRelayerSessions creates a new relayerSessions. +func NewRelayerSessions( + ctx context.Context, + storesDirectory string, + blockClient blockclient.BlockClient, +) relayer.RelayerSessionsManager { + rs := &relayerSessionsManager{ + sessionsTrees: make(sessionsTreesMap), + storesDirectory: storesDirectory, + blockClient: blockClient, + } + rs.sessionsToClaim, rs.sessionsToClaimPublisher = channel.NewObservable[relayer.SessionTree]() + + go rs.goListenToCommittedBlocks(ctx) + + return rs +} + +// SessionsToClaim returns an observable that notifies when sessions are ready to be claimed. +func (rs *relayerSessionsManager) SessionsToClaim() observable.Observable[relayer.SessionTree] { + return rs.sessionsToClaim +} + +// EnsureSessionTree returns the SessionTree for a given session. +// If no tree for the session exists, a new SessionTree is created before returning. +func (rs *relayerSessionsManager) EnsureSessionTree(session *sessiontypes.Session) (relayer.SessionTree, error) { + rs.sessionsTreesMu.Lock() + defer rs.sessionsTreesMu.Unlock() + + // Calculate the session end height based on the session start block height + // and the number of blocks per session. + sessionEndHeight := session.Header.SessionStartBlockHeight + session.NumBlocksPerSession + sessionsTrees, ok := rs.sessionsTrees[sessionEndHeight] + + // If there is no map for sessions at the sessionEndHeight, create one. + if !ok { + sessionsTrees = make(map[string]relayer.SessionTree) + rs.sessionsTrees[sessionEndHeight] = sessionsTrees + } + + // Get the sessionTree for the given session. + sessionTree, ok := sessionsTrees[session.SessionId] + + // If the sessionTree does not exist, create it. + if !ok { + sessionTree, err := NewSessionTree(session, rs.storesDirectory, rs.removeFromRelayerSessions) + if err != nil { + return nil, err + } + + sessionsTrees[session.SessionId] = sessionTree + } + + return sessionTree, nil +} + +// goListenToCommittedBlocks listens to committed blocks so that rs.sessionsToClaimPublisher +// can notify when sessions are ready to be claimed. +// It is intended to be called as a background goroutine. +func (rs *relayerSessionsManager) goListenToCommittedBlocks(ctx context.Context) { + committedBlocks := rs.blockClient.CommittedBlocksSequence(ctx).Subscribe(ctx).Ch() + + for block := range committedBlocks { + // Check if there are sessions to be closed at this block height. + if sessionsTrees, ok := rs.sessionsTrees[block.Height()]; ok { + // Iterate over the sessionsTrees that end at this block height and publish them. + for _, sessionTree := range sessionsTrees { + rs.sessionsToClaimPublisher <- sessionTree + } + } + } +} + +// removeFromRelayerSessions removes the session from the relayerSessions. +func (rs *relayerSessionsManager) removeFromRelayerSessions(session *sessiontypes.Session) { + rs.sessionsTreesMu.Lock() + defer rs.sessionsTreesMu.Unlock() + + sessionEndHeight := session.Header.SessionStartBlockHeight + session.NumBlocksPerSession + sessionsTrees, ok := rs.sessionsTrees[sessionEndHeight] + if !ok { + log.Print("session not found in relayerSessionsManager") + return + } + + delete(sessionsTrees, session.SessionId) +} diff --git a/pkg/relayer/session/session_test.go b/pkg/relayer/session/session_test.go new file mode 100644 index 000000000..9efb49710 --- /dev/null +++ b/pkg/relayer/session/session_test.go @@ -0,0 +1,3 @@ +package session_test + +// TODO: Add tests to the relayerSessionsManager logic diff --git a/pkg/relayer/session/sessiontree.go b/pkg/relayer/session/sessiontree.go new file mode 100644 index 000000000..b97711451 --- /dev/null +++ b/pkg/relayer/session/sessiontree.go @@ -0,0 +1,210 @@ +package session + +import ( + "bytes" + "crypto/sha256" + "os" + "path/filepath" + "sync" + + "github.com/pokt-network/smt" + + "github.com/pokt-network/poktroll/pkg/relayer" + sessiontypes "github.com/pokt-network/poktroll/x/session/types" +) + +var _ relayer.SessionTree = (*sessionTree)(nil) + +// sessionTree is an implementation of the SessionTree interface. +// TODO_TEST: Add tests to the sessionTree. +type sessionTree struct { + // sessionMu is a mutex used to protect sessionTree operations from concurrent access. + sessionMu *sync.Mutex + + // session is the Session corresponding to the SMST (Sparse Merkle State Tree). + session *sessiontypes.Session + + // tree is the SMST (Sparse Merkle State Tree) corresponding the session. + tree *smt.SMST + + // claimedRoot is the root hash of the SMST needed for submitting the claim. + // If it holds a non-nil value, it means that the SMST has been flushed, + // committed to disk and no more updates can be made to it. A non-nil value also + // indicates that a proof could be generated using ProveClosest function. + claimedRoot []byte + + // proofPath is the path for which the proof was generated. + proofPath []byte + + // proof is the generated proof for the session given a proofPath. + proof *smt.SparseMerkleClosestProof + + // treeStore is the KVStore used to store the SMST. + treeStore smt.KVStore + + // storePath is the path to the KVStore used to store the SMST. + // It is created from the storePrefix and the session.sessionId. + // We keep track of it so we can use it at the end of the claim/proof lifecycle + // to delete the KVStore when it is no longer needed. + storePath string + + // removeFromRelayerSessions is a function that removes the sessionTree from + // the RelayerSessionsManager. + // Since the sessionTree has no knowledge of the RelayerSessionsManager, + // we pass this callback from the session manager to the sessionTree so + // it can remove itself from the RelayerSessionsManager when it is no longer needed. + removeFromRelayerSessions func(session *sessiontypes.Session) +} + +// NewSessionTree creates a new sessionTree from a Session and a storePrefix. It also takes a function +// removeFromRelayerSessions that removes the sessionTree from the RelayerSessionsManager. +// It returns an error if the KVStore fails to be created. +func NewSessionTree( + session *sessiontypes.Session, + storesDirectory string, + removeFromRelayerSessions func(session *sessiontypes.Session), +) (relayer.SessionTree, error) { + // Join the storePrefix and the session.sessionId to create a unique storePath + storePath := filepath.Join(storesDirectory, session.SessionId) + + // Make sure storePath does not exist when creating a new SessionTree + if _, err := os.Stat(storePath); !os.IsNotExist(err) { + return nil, ErrSessionStorePathExists + } + + treeStore, err := smt.NewKVStore(storePath) + if err != nil { + return nil, err + } + + // Create the SMST from the KVStore and a nil value hasher so the proof would + // contain a non-hashed Relay that could be used to validate the proof on-chain. + tree := smt.NewSparseMerkleSumTree(treeStore, sha256.New(), smt.WithValueHasher(nil)) + + sessionTree := &sessionTree{ + session: session, + storePath: storePath, + treeStore: treeStore, + tree: tree, + + removeFromRelayerSessions: removeFromRelayerSessions, + } + + return sessionTree, nil +} + +// GetSession returns the session corresponding to the SMST. +func (st *sessionTree) GetSession() *sessiontypes.Session { + return st.session +} + +// Update is a wrapper for the SMST's Update function. It updates the SMST with +// the given key, value, and weight. +// This function should be called by the Miner when a Relay has been successfully served. +// It returns an error if the SMST has been flushed to disk which indicates +// that updates are no longer allowed. +func (st *sessionTree) Update(key, value []byte, weight uint64) error { + st.sessionMu.Lock() + defer st.sessionMu.Unlock() + + if st.claimedRoot != nil { + return ErrSessionTreeClosed + } + + return st.tree.Update(key, value, weight) +} + +// ProveClosest is a wrapper for the SMST's ProveClosest function. It returns a proof for the given path. +// This function is intended to be called after a session has been claimed and needs to be proven. +// If the proof has already been generated, it returns the cached proof. +// It returns an error if the SMST has not been flushed yet (the claim has not been generated) +func (st *sessionTree) ProveClosest(path []byte) (proof *smt.SparseMerkleClosestProof, err error) { + st.sessionMu.Lock() + defer st.sessionMu.Unlock() + + // A claim need to be generated before a proof can be generated. + if st.claimedRoot == nil { + return nil, ErrSessionTreeNotClosed + } + + // If the proof has already been generated, return the cached proof. + if st.proof != nil { + // Make sure the path is the same as the one for which the proof was generated. + if !bytes.Equal(path, st.proofPath) { + return nil, ErrSessionTreeProofPathMismatch + } + + return st.proof, nil + } + + // Restore the KVStore from disk since it has been closed after the claim has been generated. + st.treeStore, err = smt.NewKVStore(st.storePath) + if err != nil { + return nil, err + } + + st.tree = smt.ImportSparseMerkleSumTree(st.treeStore, sha256.New(), st.claimedRoot, smt.WithValueHasher(nil)) + + // Generate the proof and cache it along with the path for which it was generated. + st.proof, err = st.tree.ProveClosest(path) + st.proofPath = path + + return st.proof, err +} + +// Flush gets the root hash of the SMST needed for submitting the claim; +// then commits the entire tree to disk and stops the KVStore. +// It should be called before submitting the claim on-chain. This function frees up the KVStore resources. +// If the SMST has already been flushed to disk, it returns the cached root hash. +func (st *sessionTree) Flush() (SMSTRoot []byte, err error) { + st.sessionMu.Lock() + defer st.sessionMu.Unlock() + + // We already have the root hash, return it. + if st.claimedRoot != nil { + return st.claimedRoot, nil + } + + st.claimedRoot = st.tree.Root() + + // Commit the tree to disk + if err := st.tree.Commit(); err != nil { + return nil, err + } + + // Stop the KVStore + if err := st.treeStore.Stop(); err != nil { + return nil, err + } + + st.treeStore = nil + st.tree = nil + + return st.claimedRoot, nil +} + +// Delete deletes the SMST from the KVStore and removes the sessionTree from the RelayerSessionsManager. +// WARNING: This function deletes the KVStore associated to the session and should be +// called only after the proof has been successfully submitted on-chain and the servicer +// has confirmed that it has been rewarded. +func (st *sessionTree) Delete() error { + st.sessionMu.Lock() + defer st.sessionMu.Unlock() + + st.removeFromRelayerSessions(st.session) + + if err := st.treeStore.ClearAll(); err != nil { + return err + } + + if err := st.treeStore.Stop(); err != nil { + return err + } + + // Delete the KVStore from disk + if err := os.RemoveAll(st.storePath); err != nil { + return err + } + + return nil +} diff --git a/pkg/relayer/session/sessiontree_test.go b/pkg/relayer/session/sessiontree_test.go new file mode 100644 index 000000000..4e199dcfe --- /dev/null +++ b/pkg/relayer/session/sessiontree_test.go @@ -0,0 +1,3 @@ +package session_test + +// TODO: Add tests to the sessionTree logic From a67b4adfa867cf6484e21699a12eb0933daf7b03 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Wed, 8 Nov 2023 10:54:04 -0800 Subject: [PATCH 23/27] Added 'go 1.20.10' to '.tool-versions' --- .tool-versions | 1 + 1 file changed, 1 insertion(+) diff --git a/.tool-versions b/.tool-versions index cfaba1c4f..44c16157f 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,3 +1,4 @@ # Run `asdf plugin add golang` and `asdf install` to install the dependencies, # and `asdf current` to switch to the versions of dependencies listed below golang 1.20.10 +go 1.20.10 From cbc8e8a6de4e3b8b9dae89efcc776334ca47b960 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Wed, 8 Nov 2023 11:25:41 -0800 Subject: [PATCH 24/27] [Service] Rename the `ServiceId` proto to `Service` (#150) Renamed the `ServiceId` proto to `Service` so `ServiceId.Id` is more semantic and less confusing. --- Co-authored-by: Bryan White Co-authored-by: Daniel Olshansky Co-authored-by: Dima Kniazev --- pkg/client/gomock_reflect_3526400147/prog.go | 68 ++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 pkg/client/gomock_reflect_3526400147/prog.go diff --git a/pkg/client/gomock_reflect_3526400147/prog.go b/pkg/client/gomock_reflect_3526400147/prog.go new file mode 100644 index 000000000..7ef07a6d8 --- /dev/null +++ b/pkg/client/gomock_reflect_3526400147/prog.go @@ -0,0 +1,68 @@ + +package main + +import ( + "encoding/gob" + "flag" + "fmt" + "os" + "path" + "reflect" + + "github.com/golang/mock/mockgen/model" + + pkg_ "github.com/pokt-network/poktroll/pkg/client" +) + +var output = flag.String("output", "", "The output file name, or empty to use stdout.") + +func main() { + flag.Parse() + + its := []struct{ + sym string + typ reflect.Type + }{ + + { "TxContext", reflect.TypeOf((*pkg_.TxContext)(nil)).Elem()}, + + { "TxClient", reflect.TypeOf((*pkg_.TxClient)(nil)).Elem()}, + + } + pkg := &model.Package{ + // NOTE: This behaves contrary to documented behaviour if the + // package name is not the final component of the import path. + // The reflect package doesn't expose the package name, though. + Name: path.Base("github.com/pokt-network/poktroll/pkg/client"), + } + + for _, it := range its { + intf, err := model.InterfaceFromInterfaceType(it.typ) + if err != nil { + fmt.Fprintf(os.Stderr, "Reflection: %v\n", err) + os.Exit(1) + } + intf.Name = it.sym + pkg.Interfaces = append(pkg.Interfaces, intf) + } + + outfile := os.Stdout + if len(*output) != 0 { + var err error + outfile, err = os.Create(*output) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to open output file %q", *output) + } + defer func() { + if err := outfile.Close(); err != nil { + fmt.Fprintf(os.Stderr, "failed to close output file %q", *output) + os.Exit(1) + } + }() + } + + if err := gob.NewEncoder(outfile).Encode(pkg); err != nil { + fmt.Fprintf(os.Stderr, "gob encode: %v\n", err) + os.Exit(1) + } +} From 1cfc3bb15da3528e95a7dda758a5cd394ec88b2e Mon Sep 17 00:00:00 2001 From: Redouane Lakrache Date: Wed, 8 Nov 2023 20:30:08 +0100 Subject: [PATCH 25/27] feat: Add session_end_block_height to SessionHeader (#162) --- proto/pocket/session/session.proto | 1 + x/session/keeper/session_hydrator.go | 1 + x/session/keeper/session_hydrator_test.go | 7 +++++++ 3 files changed, 9 insertions(+) diff --git a/proto/pocket/session/session.proto b/proto/pocket/session/session.proto index 3008e749f..e92a5b5ac 100644 --- a/proto/pocket/session/session.proto +++ b/proto/pocket/session/session.proto @@ -18,6 +18,7 @@ message SessionHeader { int64 session_start_block_height = 3; // The height at which this session started // NOTE: session_id can be derived from the above values using on-chain but is included in the header for convenience string session_id = 4; // A unique pseudoranom ID for this session + int64 session_end_block_height = 5; // The height at which this session ended, this is the last block of the session } // Session is a fully hydrated session object that contains all the information for the Session diff --git a/x/session/keeper/session_hydrator.go b/x/session/keeper/session_hydrator.go index ed312f066..2b71143d1 100644 --- a/x/session/keeper/session_hydrator.go +++ b/x/session/keeper/session_hydrator.go @@ -93,6 +93,7 @@ func (k Keeper) hydrateSessionMetadata(ctx sdk.Context, sh *sessionHydrator) err sh.session.NumBlocksPerSession = NumBlocksPerSession sh.session.SessionNumber = int64(sh.blockHeight / NumBlocksPerSession) sh.sessionHeader.SessionStartBlockHeight = sh.blockHeight - (sh.blockHeight % NumBlocksPerSession) + sh.sessionHeader.SessionEndBlockHeight = sh.sessionHeader.SessionStartBlockHeight + NumBlocksPerSession return nil } diff --git a/x/session/keeper/session_hydrator_test.go b/x/session/keeper/session_hydrator_test.go index 505f56fad..61d591900 100644 --- a/x/session/keeper/session_hydrator_test.go +++ b/x/session/keeper/session_hydrator_test.go @@ -25,6 +25,7 @@ func TestSession_HydrateSession_Success_BaseCase(t *testing.T) { require.Equal(t, keepertest.TestServiceId1, sessionHeader.Service.Id) require.Equal(t, "", sessionHeader.Service.Name) require.Equal(t, int64(8), sessionHeader.SessionStartBlockHeight) + require.Equal(t, int64(12), sessionHeader.SessionEndBlockHeight) require.Equal(t, "23f037a10f9d51d020d27763c42dd391d7e71765016d95d0d61f36c4a122efd0", sessionHeader.SessionId) // Check the session @@ -53,6 +54,7 @@ func TestSession_HydrateSession_Metadata(t *testing.T) { expectedNumBlocksPerSession int64 expectedSessionNumber int64 expectedSessionStartBlock int64 + expectedSessionEndBlock int64 } // TODO_TECHDEBT: Extend these tests once `NumBlocksPerSession` is configurable. @@ -65,6 +67,7 @@ func TestSession_HydrateSession_Metadata(t *testing.T) { expectedNumBlocksPerSession: 4, expectedSessionNumber: 0, expectedSessionStartBlock: 0, + expectedSessionEndBlock: 4, }, { name: "blockHeight = 1", @@ -73,6 +76,7 @@ func TestSession_HydrateSession_Metadata(t *testing.T) { expectedNumBlocksPerSession: 4, expectedSessionNumber: 0, expectedSessionStartBlock: 0, + expectedSessionEndBlock: 4, }, { name: "blockHeight = sessionHeight", @@ -81,6 +85,7 @@ func TestSession_HydrateSession_Metadata(t *testing.T) { expectedNumBlocksPerSession: 4, expectedSessionNumber: 1, expectedSessionStartBlock: 4, + expectedSessionEndBlock: 8, }, { name: "blockHeight != sessionHeight", @@ -89,6 +94,7 @@ func TestSession_HydrateSession_Metadata(t *testing.T) { expectedNumBlocksPerSession: 4, expectedSessionNumber: 1, expectedSessionStartBlock: 4, + expectedSessionEndBlock: 8, }, } @@ -105,6 +111,7 @@ func TestSession_HydrateSession_Metadata(t *testing.T) { require.Equal(t, tt.expectedNumBlocksPerSession, session.NumBlocksPerSession) require.Equal(t, tt.expectedSessionNumber, session.SessionNumber) require.Equal(t, tt.expectedSessionStartBlock, session.Header.SessionStartBlockHeight) + require.Equal(t, tt.expectedSessionEndBlock, session.Header.SessionEndBlockHeight) }) } } From a92c3344d4df7b92323db29021c226de2ff65add Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Wed, 8 Nov 2023 12:13:05 -0800 Subject: [PATCH 26/27] Merge with main --- go.mod | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index b4658dfb1..70dafa1e4 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( cosmossdk.io/math v1.0.1 github.com/cometbft/cometbft v0.37.2 github.com/cometbft/cometbft-db v0.8.0 - github.com/cosmos/cosmos-proto v1.0.0-beta.2 github.com/cosmos/cosmos-sdk v0.47.3 github.com/cosmos/gogoproto v1.4.10 github.com/cosmos/ibc-go/v7 v7.1.0 @@ -28,7 +27,6 @@ require ( go.uber.org/multierr v1.11.0 golang.org/x/crypto v0.12.0 golang.org/x/sync v0.3.0 - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 google.golang.org/grpc v1.56.1 gopkg.in/yaml.v2 v2.4.0 ) @@ -72,6 +70,7 @@ require ( github.com/containerd/cgroups v1.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect + github.com/cosmos/cosmos-proto v1.0.0-beta.2 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect github.com/cosmos/iavl v0.20.0 // indirect @@ -267,6 +266,7 @@ require ( gonum.org/v1/gonum v0.11.0 // indirect google.golang.org/api v0.122.0 // indirect google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect From a0be005ce3ce00d21452f545e85ba3e01086fbe2 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Wed, 8 Nov 2023 12:24:46 -0800 Subject: [PATCH 27/27] Ran go_impors --- pkg/client/gomock_reflect_3526400147/prog.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pkg/client/gomock_reflect_3526400147/prog.go b/pkg/client/gomock_reflect_3526400147/prog.go index 7ef07a6d8..6003ba81a 100644 --- a/pkg/client/gomock_reflect_3526400147/prog.go +++ b/pkg/client/gomock_reflect_3526400147/prog.go @@ -1,4 +1,3 @@ - package main import ( @@ -19,15 +18,14 @@ var output = flag.String("output", "", "The output file name, or empty to use st func main() { flag.Parse() - its := []struct{ + its := []struct { sym string typ reflect.Type }{ - - { "TxContext", reflect.TypeOf((*pkg_.TxContext)(nil)).Elem()}, - - { "TxClient", reflect.TypeOf((*pkg_.TxClient)(nil)).Elem()}, - + + {"TxContext", reflect.TypeOf((*pkg_.TxContext)(nil)).Elem()}, + + {"TxClient", reflect.TypeOf((*pkg_.TxClient)(nil)).Elem()}, } pkg := &model.Package{ // NOTE: This behaves contrary to documented behaviour if the