From 4a0530eba4ea7b0e307ace93c5b77de1a13f273a Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Sun, 2 Jul 2023 07:04:36 -0400 Subject: [PATCH 01/20] e2e v0 for trustless relay --- app/client/cli/servicer.go | 15 +++--- build/localnet/manifests/configs.yaml | 2 +- e2e/tests/steps_init_test.go | 75 +++++++++++++++++++++++++++ e2e/tests/trustless_relays.feature | 11 ++++ rpc/handlers.go | 3 +- rpc/v1/openapi.yaml | 5 +- shared/core/types/proto/relay.proto | 2 +- shared/core/types/relay.go | 4 +- shared/k8s/debug.go | 2 + utility/servicer/module.go | 21 ++++++-- 10 files changed, 123 insertions(+), 17 deletions(-) create mode 100644 e2e/tests/trustless_relays.feature diff --git a/app/client/cli/servicer.go b/app/client/cli/servicer.go index 5787d2d7e..adb2a48a0 100644 --- a/app/client/cli/servicer.go +++ b/app/client/cli/servicer.go @@ -78,7 +78,7 @@ Will prompt the user for the *application* account passphrase`, return fmt.Errorf("error getting servicer for the relay: %w", err) } - relay, err := buildRelay(relayPayload, pk, session, servicer) + relay, err := buildRelay(relayPayload, pk, session, servicer, applicationAddr) if err != nil { return fmt.Errorf("error building relay from payload: %w", err) } @@ -183,13 +183,13 @@ func sendTrustlessRelay(ctx context.Context, servicerUrl string, relay *rpc.Rela return client.PostV1ClientRelayWithResponse(ctx, *relay) } -func buildRelay(payload string, appPrivateKey crypto.PrivateKey, session *rpc.Session, servicer *rpc.ProtocolActor) (*rpc.RelayRequest, error) { +func buildRelay(payload string, appPrivateKey crypto.PrivateKey, session *rpc.Session, servicer *rpc.ProtocolActor, appAddr string) (*rpc.RelayRequest, error) { // TECHDEBT: This is mostly COPIED from pocket-go: we should refactor pocket-go code and import this functionality from there instead. - relayPayload := rpc.Payload{ - // INCOMPLETE(#803): need to unmarshal into JSONRPC and other supported relay formats once proto-generated custom types are added. - Jsonrpc: "2.0", - Method: payload, - // INCOMPLETE: set Headers for HTTP relays + var relayPayload rpc.Payload + // INCOMPLETE(#803): need to unmarshal into JSONRPC and other supported relay formats once proto-generated custom types are added. + // INCOMPLETE: set Headers for HTTP relays + if err := json.Unmarshal([]byte(payload), &relayPayload); err != nil { + return nil, fmt.Errorf("error unmarshalling relay payload %s: %w", payload, err) } relayMeta := rpc.RelayRequestMeta{ @@ -200,6 +200,7 @@ func buildRelay(payload string, appPrivateKey crypto.PrivateKey, session *rpc.Se }, ServicerPubKey: servicer.PublicKey, // TODO(#697): Geozone + ApplicationAddress: appAddr, } relay := &rpc.RelayRequest{ diff --git a/build/localnet/manifests/configs.yaml b/build/localnet/manifests/configs.yaml index 29bc520df..3a3574e47 100644 --- a/build/localnet/manifests/configs.yaml +++ b/build/localnet/manifests/configs.yaml @@ -1699,7 +1699,7 @@ data: "address": "001022b138896c4c5466ac86b24a9bbe249905c2", "public_key": "56915c1270bc8d9280a633e0be51647f62388a851318381614877ef2ed84a495", "chains": ["0001"], - "service_url": "servicer-001-pocket:42069", + "service_url": "http://servicer-001-pocket:50832", "staked_amount": "1000000000000", "paused_height": -1, "unstaking_height": -1, diff --git a/e2e/tests/steps_init_test.go b/e2e/tests/steps_init_test.go index f981f5793..46b0c9868 100644 --- a/e2e/tests/steps_init_test.go +++ b/e2e/tests/steps_init_test.go @@ -32,6 +32,10 @@ const ( // validatorB maps to suffix ID 002 and receives in the Send test. validatorB = "002" chainId = "0001" + + servicerA = "001" + appA = "000" + serviceA = "0001" ) type rootSuite struct { @@ -45,6 +49,14 @@ type rootSuite struct { // validator holds command results between runs and reports errors to the test suite validator *validatorPod // validatorA maps to suffix ID 001 of the kube pod that we use as our control agent + + // servicerKeys is hydrated by the clientset with credentials for all servicers. + // servicerKeys maps servicer IDs to their private key as a hex string. + servicerKeys map[string]string + + // appKeys is hydrated by the clientset with credentials for all apps. + // appKeys maps app IDs to their private key as a hex string. + appKeys map[string]string } func (s *rootSuite) Before() { @@ -59,6 +71,18 @@ func (s *rootSuite) Before() { s.validator = new(validatorPod) s.clientset = clientSet s.validatorKeys = vkmap + + // ADDPR: use pocketk8s to populate + s.servicerKeys = map[string]string{ + // 000 servicer NOT in session + "000": "acbca21f295caefdfe480ceba85f3fed31a50915162f94867f9c23d8f474f4c6d1130c5eb920af8edd5b6bfa39d33aa787f421c8ba0786de4ca4e7703553bb97", + // 001 servicer in session + "001": "eec4072b095acf60be9d6be4093b14a24e2ddb6e9d385d980a635815961d025856915c1270bc8d9280a633e0be51647f62388a851318381614877ef2ed84a495", + } + + s.appKeys = map[string]string{ + "000": "468cc03083d72f2440d3d08d12143b9b74cca9460690becaa2499a4f04fddaa805a25e527bf6f51676f61f2f1a96efaa748218ac82f54d3cdc55a4881389eb60", + } } // TestFeatures runs the e2e tests specified in any .features files in this directory @@ -158,6 +182,57 @@ func (s *rootSuite) getPrivateKey( return privateKey } +func (s *rootSuite) TheUserSendsARelayToAServicer() { + // ADDPR: Verify the response: correct id, correct jsonrpc, and result should be greater than a known block number + relayContents := `{"jsonrpc": "2.0", "method": "eth_blockNumber"}` + servicerPrivateKey := s.getServicerPrivateKey(servicerA) + appPrivateKey := s.getAppPrivateKey(appA) + + s.sendTrustlessRelay(relayContents, servicerPrivateKey.Address().String(), appPrivateKey.Address().String()) +} + +func (s *rootSuite) sendTrustlessRelay(relayPayload string, servicerAddr, appAddr string) { + args := []string{ + "Servicer", + "Relay", + appAddr, + servicerAddr, + // IMPROVE: add ETH_Goerli as a chain/service to genesis + serviceA, + relayPayload, + } + + // DISCUSS: does this need to be run from a client, i.e. not a validator, pod? + res, err := s.validator.RunCommand(args...) + + fmt.Printf("Result: %s\n%s\n", res.Stdout, res.Stderr) + require.NoError(s, err) + + s.validator.result = res +} + +// getAppPrivateKey generates a new keypair from the application private hex key that we get from the clientset +func (s *rootSuite) getAppPrivateKey( + appId string, +) cryptoPocket.PrivateKey { + privHexString := s.appKeys[appId] + privateKey, err := cryptoPocket.NewPrivateKey(privHexString) + require.NoErrorf(s, err, "failed to extract privkey") + + return privateKey +} + +// getServicerPrivateKey generates a new keypair from the servicer private hex key that we get from the clientset +func (s *rootSuite) getServicerPrivateKey( + servicerId string, +) cryptoPocket.PrivateKey { + privHexString := s.servicerKeys[servicerId] + privateKey, err := cryptoPocket.NewPrivateKey(privHexString) + require.NoErrorf(s, err, "failed to extract privkey") + + return privateKey +} + // getClientset uses the default path `$HOME/.kube/config` to build a kubeconfig // and then connects to that cluster and returns a *Clientset or an error func getClientset(t gocuke.TestingT) (*kubernetes.Clientset, error) { diff --git a/e2e/tests/trustless_relays.feature b/e2e/tests/trustless_relays.feature new file mode 100644 index 000000000..440d241d7 --- /dev/null +++ b/e2e/tests/trustless_relays.feature @@ -0,0 +1,11 @@ +Feature: Trustless Relays + + Scenario: User Wants Help Using The Servicer Command + When the user runs the command "Servicer help" + Then the user should be able to see standard output containing "Available Commands" + And the validator should have exited without error + + Scenario: User can send a trustless relay + When the user sends a relay to a servicer + Then the user should be able to see standard output containing "result" + And the validator should have exited without error diff --git a/rpc/handlers.go b/rpc/handlers.go index e222b3e66..9df7af7a6 100644 --- a/rpc/handlers.go +++ b/rpc/handlers.go @@ -110,6 +110,7 @@ func (s *rpcServer) PostV1ClientRelay(ctx echo.Context) error { RelayChain: chain, GeoZone: geozone, Signature: body.Meta.Signature, + ApplicationAddress: body.Meta.ApplicationAddress, } relayRequest := buildJsonRPCRelayPayload(&body) @@ -220,7 +221,7 @@ func (s *rpcServer) GetV1P2pStakedActorsAddressBook(ctx echo.Context, params Get func buildJsonRPCRelayPayload(body *RelayRequest) *coreTypes.Relay { payload := &coreTypes.Relay_JsonRpcPayload{ JsonRpcPayload: &coreTypes.JSONRPCPayload{ - JsonRpc: body.Payload.Jsonrpc, + Jsonrpc: body.Payload.Jsonrpc, Method: body.Payload.Method, }, } diff --git a/rpc/v1/openapi.yaml b/rpc/v1/openapi.yaml index eb36f0c76..20f31aa0a 100644 --- a/rpc/v1/openapi.yaml +++ b/rpc/v1/openapi.yaml @@ -1699,9 +1699,9 @@ components: - jsonrpc - method properties: + # INCOMPLETE: need to support string, number, and NULL values, according to JSONRPC spec id: type: string - format: byte jsonrpc: type: string method: @@ -1762,6 +1762,7 @@ components: - geozone - token - signature + - application_address properties: block_height: type: integer @@ -1776,6 +1777,8 @@ components: $ref: "#/components/schemas/AAT" signature: type: string + application_address: + type: string Signature: type: object required: diff --git a/shared/core/types/proto/relay.proto b/shared/core/types/proto/relay.proto index 6061a3a62..89238ae47 100644 --- a/shared/core/types/proto/relay.proto +++ b/shared/core/types/proto/relay.proto @@ -39,7 +39,7 @@ message JSONRPCPayload { // JSONRPC version 2 expects a field named "jsonrpc" with a value of "2.0". // See the JSONRPC spec in the following link for more details: // https://www.jsonrpc.org/specification#request_object - string json_rpc = 2; + string jsonrpc = 2; string method = 3; // The parameters field can be empty, an array or a structure. It is on the server to decide which one // has been sent to it and whether the supplied value is valid. diff --git a/shared/core/types/relay.go b/shared/core/types/relay.go index 5d9f5b65f..91e5424ff 100644 --- a/shared/core/types/relay.go +++ b/shared/core/types/relay.go @@ -34,8 +34,8 @@ func (r *Relay) Validate() error { // 1. The JSONRPC field is set to "2.0" as per the JSONRPC spec requirement, and // 2. The Method field is not empty func (p *JSONRPCPayload) Validate() error { - if p.JsonRpc != jsonRpcVersion { - return fmt.Errorf("%w: %s", errInvalidJSONRPC, p.JsonRpc) + if p.Jsonrpc != jsonRpcVersion { + return fmt.Errorf("%w: %s", errInvalidJSONRPC, p.Jsonrpc) } // DISCUSS: do we need/want chain-specific validation? Potential for reusing the existing logic of Portal V2/pocket-go diff --git a/shared/k8s/debug.go b/shared/k8s/debug.go index bfb966e04..5e2130ea5 100644 --- a/shared/k8s/debug.go +++ b/shared/k8s/debug.go @@ -50,6 +50,8 @@ func FetchValidatorPrivateKeys(clientset *kubernetes.Clientset) (map[string]stri return validatorKeysMap, nil } +// ADDPR: add the following functions in a separate PR: FetchServicerPrivateKeys and FetchAppPrivateKeys + func getNamespace() (string, error) { _, err := os.Stat(kubernetesServiceAccountNamespaceFile) if err == nil { diff --git a/utility/servicer/module.go b/utility/servicer/module.go index eb4ec9c26..f06a73f3d 100644 --- a/utility/servicer/module.go +++ b/utility/servicer/module.go @@ -2,7 +2,9 @@ package servicer import ( "bytes" + "crypto/tls" "encoding/hex" + "encoding/json" "errors" "fmt" "io" @@ -228,8 +230,16 @@ func (s *servicer) validateRelayChainSupport(relayChain *coreTypes.Identifiable, } defer readCtx.Release() //nolint:errcheck // We only need to make sure the readCtx is released - // DISCUSS: should we update the GetServicer signature to take a string instead? - servicer, err := readCtx.GetServicer([]byte(s.config.Address), currentHeight) + + // The servicer address needs to be passed to persistence module, which uses hex.EncodeToString to convert the byte array to string. + // Therefore, the address needs to be decoded as a byte array before passing it to the persistence module. + servicerAddrBz, err := hex.DecodeString(s.config.Address) + if err != nil { + return fmt.Errorf("error decoding servicer address %s: %w", s.config.Address, err) + } + + // DISCUSS: should we update the GetServicer signature to take a string instead? alternatively, we could use []byte as type of servicer address + servicer, err := readCtx.GetServicer(servicerAddrBz, currentHeight) if err != nil { return fmt.Errorf("error reading servicer from persistence: %w", err) } @@ -424,7 +434,8 @@ func (s *servicer) executeJsonRPCRelay(meta *coreTypes.RelayMeta, payload *coreT return nil, fmt.Errorf("Chain %s not found in servicer configuration: %w", meta.RelayChain.Id, errValidateRelayMeta) } - relayBytes, err := codec.GetCodec().Marshal(payload) + // JSONRPC endpoints expect json-encoded payload, so codec package would not work here as it uses proto serialization + relayBytes, err := json.Marshal(payload) if err != nil { return nil, fmt.Errorf("Error marshalling payload %s: %w", payload.String(), err) } @@ -466,7 +477,9 @@ func (s *servicer) executeHTTPRelay(serviceConfig *configs.ServiceConfig, payloa } // INCOMPLETE(#837): Optimize usage of HTTP client, e.g. connection reuse, depending on the volume of relays a servicer is expected to handle - resp, err := (&http.Client{Timeout: time.Duration(serviceConfig.TimeoutMsec) * time.Millisecond}).Do(req) + // ADDPR: allow configuration of TLS Settings for HTTPS services + tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} + resp, err := (&http.Client{Transport: tr, Timeout: time.Duration(serviceConfig.TimeoutMsec) * time.Millisecond}).Do(req) if err != nil { return nil, fmt.Errorf("Error performing the HTTP request for relay: %w", err) } From 9bbc8423046da098b22853354276957a61b707ea Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Mon, 3 Jul 2023 08:13:00 -0400 Subject: [PATCH 02/20] e2e test passing for ETH relaychain account balance --- e2e/tests/steps_init_test.go | 27 +++++++++++++++++++++------ e2e/tests/trustless_relays.feature | 18 +++++++++++++++--- rpc/handlers.go | 15 ++++++++------- rpc/v1/openapi.yaml | 8 +++++--- shared/core/types/proto/relay.proto | 3 ++- 5 files changed, 51 insertions(+), 20 deletions(-) diff --git a/e2e/tests/steps_init_test.go b/e2e/tests/steps_init_test.go index 46b0c9868..ba6947e5b 100644 --- a/e2e/tests/steps_init_test.go +++ b/e2e/tests/steps_init_test.go @@ -88,7 +88,10 @@ func (s *rootSuite) Before() { // TestFeatures runs the e2e tests specified in any .features files in this directory // * This test suite assumes that a LocalNet is running that can be accessed by `kubectl` func TestFeatures(t *testing.T) { - gocuke.NewRunner(t, &rootSuite{}).Path("*.feature").Run() + runner := gocuke.NewRunner(t, &rootSuite{}).Path("*.feature") + // DISCUSS: is there a better way to make gocuke pickup the balance, i.e. a hexadecimal, as a string in function argument? + runner.Step(`^the\srelay\sresponse\scontains\s([[:alnum:]]+)$`, (*rootSuite).TheRelayResponseContains) + runner.Run() } // InitializeScenario registers step regexes to function handlers @@ -182,13 +185,26 @@ func (s *rootSuite) getPrivateKey( return privateKey } -func (s *rootSuite) TheUserSendsARelayToAServicer() { - // ADDPR: Verify the response: correct id, correct jsonrpc, and result should be greater than a known block number - relayContents := `{"jsonrpc": "2.0", "method": "eth_blockNumber"}` +// An Application requests the account balance of a specific address at a specific height +func (s *rootSuite) TheApplicationSendsARelayToAServicer() { + // ADDPR: Add a servicer staked for the Ethereum RelayChain + // ADDPR: Verify the response: correct id, correct jsonrpc, and the returned balance + // ADDPR: move the method and account to the feature file + + // ETH + // Account: 0x8315177aB297bA92A06054cE80a67Ed4DBd7ed3a (Arbitrum Bridge) + // Balance: 1,160,126.46817237178258965 ETH = 0xf5aa94f49d4fd1f8dcd2 + // BlockNumber: 17605670 = 0x10CA426 + checkBalanceRelay := `{"method": "eth_getBalance", "params": ["0x8315177aB297bA92A06054cE80a67Ed4DBd7ed3a", "0x10CA426"], "id": "1", "jsonrpc": "2.0"}` + servicerPrivateKey := s.getServicerPrivateKey(servicerA) appPrivateKey := s.getAppPrivateKey(appA) - s.sendTrustlessRelay(relayContents, servicerPrivateKey.Address().String(), appPrivateKey.Address().String()) + s.sendTrustlessRelay(checkBalanceRelay, servicerPrivateKey.Address().String(), appPrivateKey.Address().String()) +} + +func (s *rootSuite) TheRelayResponseContains(arg1 string) { + require.Contains(s, s.validator.result.Stdout, arg1) } func (s *rootSuite) sendTrustlessRelay(relayPayload string, servicerAddr, appAddr string) { @@ -205,7 +221,6 @@ func (s *rootSuite) sendTrustlessRelay(relayPayload string, servicerAddr, appAdd // DISCUSS: does this need to be run from a client, i.e. not a validator, pod? res, err := s.validator.RunCommand(args...) - fmt.Printf("Result: %s\n%s\n", res.Stdout, res.Stderr) require.NoError(s, err) s.validator.result = res diff --git a/e2e/tests/trustless_relays.feature b/e2e/tests/trustless_relays.feature index 440d241d7..8026c8d66 100644 --- a/e2e/tests/trustless_relays.feature +++ b/e2e/tests/trustless_relays.feature @@ -5,7 +5,19 @@ Feature: Trustless Relays Then the user should be able to see standard output containing "Available Commands" And the validator should have exited without error - Scenario: User can send a trustless relay - When the user sends a relay to a servicer - Then the user should be able to see standard output containing "result" + + # Happy test cases + # An Application requests the account balance of a specific address at a specific height from a Servicer staked for the Ethereum RelayChain, and receives a successful response. + + Scenario: Application can send a trustless relay to a relaychain to get an account's balance at a specific height + # Given the application has a valid ethereum relaychain account + # Given the application has a valid ethereum relaychain height + # Given the application has a valid servicer for the session + # INCOMPLETE: GeoZone + # ADDPR: specify the servicer + # ADDPR: specify the relay method and params + When the application sends a relay to a servicer + Then the relay response contains 0xf5aa94f49d4fd1f8dcd2 And the validator should have exited without error + + # ADDPR: Sad test case: An Application requests the account balance of a specific address at a specific height from a Servicer staked for the Ethereum RelayChain in the same GeoZone, and the request times out without a response. diff --git a/rpc/handlers.go b/rpc/handlers.go index 9df7af7a6..e28216e91 100644 --- a/rpc/handlers.go +++ b/rpc/handlers.go @@ -105,11 +105,11 @@ func (s *rpcServer) PostV1ClientRelay(ctx echo.Context) error { Name: body.Meta.Geozone.Name, } relayMeta := &coreTypes.RelayMeta{ - BlockHeight: body.Meta.BlockHeight, - ServicerPublicKey: body.Meta.ServicerPubKey, - RelayChain: chain, - GeoZone: geozone, - Signature: body.Meta.Signature, + BlockHeight: body.Meta.BlockHeight, + ServicerPublicKey: body.Meta.ServicerPubKey, + RelayChain: chain, + GeoZone: geozone, + Signature: body.Meta.Signature, ApplicationAddress: body.Meta.ApplicationAddress, } @@ -230,8 +230,9 @@ func buildJsonRPCRelayPayload(body *RelayRequest) *coreTypes.Relay { payload.JsonRpcPayload.Id = []byte(*body.Payload.Id) } - if body.Payload.Parameters != nil { - payload.JsonRpcPayload.Parameters = *body.Payload.Parameters + if body.Payload.Params != nil { + // ADDPR: Need a decision and implementation on Params field and conversion from rpc to proto + payload.JsonRpcPayload.Params = *body.Payload.Params } if body.Payload.Headers != nil { diff --git a/rpc/v1/openapi.yaml b/rpc/v1/openapi.yaml index 20f31aa0a..5f2765f3c 100644 --- a/rpc/v1/openapi.yaml +++ b/rpc/v1/openapi.yaml @@ -1706,9 +1706,11 @@ components: type: string method: type: string - parameters: - type: string - format: byte + # ADDPR: DISCUSS: params can be anything: should we use AnyValue for it? + params: + type: array + items: + type: string headers: $ref: "#/components/schemas/Headers" Pool: diff --git a/shared/core/types/proto/relay.proto b/shared/core/types/proto/relay.proto index 89238ae47..2679d687c 100644 --- a/shared/core/types/proto/relay.proto +++ b/shared/core/types/proto/relay.proto @@ -45,7 +45,8 @@ message JSONRPCPayload { // has been sent to it and whether the supplied value is valid. // See the JSONRPC spec in the following link for more details: // https://www.jsonrpc.org/specification#parameter_structures - bytes parameters = 4; + // INCOMPLETE: decide the type for params field, considering the above description and interaction with OpenAPI + repeated string params = 4; map headers = 5; } From 8704b695e2dede1ed09118c02fcf0e545b280420 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Mon, 3 Jul 2023 09:47:47 -0400 Subject: [PATCH 03/20] Parameterized e2e happy case for trustless relays --- e2e/tests/steps_init_test.go | 55 +++++++++++++++++++++++++----- e2e/tests/trustless_relays.feature | 15 ++++---- 2 files changed, 53 insertions(+), 17 deletions(-) diff --git a/e2e/tests/steps_init_test.go b/e2e/tests/steps_init_test.go index ba6947e5b..fbbdd516b 100644 --- a/e2e/tests/steps_init_test.go +++ b/e2e/tests/steps_init_test.go @@ -36,6 +36,8 @@ const ( servicerA = "001" appA = "000" serviceA = "0001" + + relaychainEth = "RelayChainETH" // used to refer to Ethereum chain when retrieving relaychain settings ) type rootSuite struct { @@ -57,6 +59,19 @@ type rootSuite struct { // appKeys is hydrated by the clientset with credentials for all apps. // appKeys maps app IDs to their private key as a hex string. appKeys map[string]string + + // relaychains holds settings for all relaychains used in the tests + // the map key is a constant selected as the identifier for the relaychain, e.g. "RelayChainETH" for Ethereum + relaychains map[string]*relaychainSettings + + // servicer holds the key for the servicer that should received the relay + servicer string +} + +// relaychainSettings holds the settings for a specific relaychain +type relaychainSettings struct { + Account string + Height string } func (s *rootSuite) Before() { @@ -83,6 +98,10 @@ func (s *rootSuite) Before() { s.appKeys = map[string]string{ "000": "468cc03083d72f2440d3d08d12143b9b74cca9460690becaa2499a4f04fddaa805a25e527bf6f51676f61f2f1a96efaa748218ac82f54d3cdc55a4881389eb60", } + + s.relaychains = map[string]*relaychainSettings{ + relaychainEth: {}, + } } // TestFeatures runs the e2e tests specified in any .features files in this directory @@ -185,19 +204,37 @@ func (s *rootSuite) getPrivateKey( return privateKey } +// TheApplicationHasAValidEthereumRelaychainAccount fullfils the following condition from feature file: +// +// "Given the application has a valid ethereum relaychain account" +func (s *rootSuite) TheApplicationHasAValidEthereumRelaychainAccount() { + // Account: 0x8315177aB297bA92A06054cE80a67Ed4DBd7ed3a (Arbitrum Bridge) + s.relaychains[relaychainEth].Account = "0x8315177aB297bA92A06054cE80a67Ed4DBd7ed3a" +} + +// TheApplicationHasAValidEthereumRelaychaindHeight fullfils the following condition from feature file: +// +// "Given the application has a valid ethereum relaychain height" +func (s *rootSuite) TheApplicationHasAValidEthereumRelaychainHeight() { + // Ethereum relaychain BlockNumber: 17605670 = 0x10CA426 + s.relaychains[relaychainEth].Height = "0x10CA426" +} + +// TheApplicationHasAValidServicer fullfils the following condition from feature file: +// +// "Given the application has a valid servicer" +func (s *rootSuite) TheApplicationHasAValidServicer() { + s.servicer = servicerA +} + // An Application requests the account balance of a specific address at a specific height func (s *rootSuite) TheApplicationSendsARelayToAServicer() { // ADDPR: Add a servicer staked for the Ethereum RelayChain - // ADDPR: Verify the response: correct id, correct jsonrpc, and the returned balance - // ADDPR: move the method and account to the feature file - - // ETH - // Account: 0x8315177aB297bA92A06054cE80a67Ed4DBd7ed3a (Arbitrum Bridge) - // Balance: 1,160,126.46817237178258965 ETH = 0xf5aa94f49d4fd1f8dcd2 - // BlockNumber: 17605670 = 0x10CA426 - checkBalanceRelay := `{"method": "eth_getBalance", "params": ["0x8315177aB297bA92A06054cE80a67Ed4DBd7ed3a", "0x10CA426"], "id": "1", "jsonrpc": "2.0"}` + // ADDPR: Verify the response: correct id and correct jsonrpc + params := fmt.Sprintf("%q: [%q, %q]", "params", s.relaychains[relaychainEth].Account, s.relaychains[relaychainEth].Height) + checkBalanceRelay := fmt.Sprintf("{%s, %s}", `"method": "eth_getBalance", "id": "1", "jsonrpc": "2.0"`, params) - servicerPrivateKey := s.getServicerPrivateKey(servicerA) + servicerPrivateKey := s.getServicerPrivateKey(s.servicer) appPrivateKey := s.getAppPrivateKey(appA) s.sendTrustlessRelay(checkBalanceRelay, servicerPrivateKey.Address().String(), appPrivateKey.Address().String()) diff --git a/e2e/tests/trustless_relays.feature b/e2e/tests/trustless_relays.feature index 8026c8d66..0220f1bd3 100644 --- a/e2e/tests/trustless_relays.feature +++ b/e2e/tests/trustless_relays.feature @@ -6,17 +6,16 @@ Feature: Trustless Relays And the validator should have exited without error - # Happy test cases - # An Application requests the account balance of a specific address at a specific height from a Servicer staked for the Ethereum RelayChain, and receives a successful response. - + # Happy test case: An Application requests the account balance of a specific address at a specific height from a Servicer staked for the Ethereum RelayChain, and receives a successful response. + + # ADDPR: Add a servicer staked for the Ethereum relaychain to the genesis file Scenario: Application can send a trustless relay to a relaychain to get an account's balance at a specific height - # Given the application has a valid ethereum relaychain account - # Given the application has a valid ethereum relaychain height - # Given the application has a valid servicer for the session + Given the application has a valid ethereum relaychain account + Given the application has a valid ethereum relaychain height + Given the application has a valid servicer # INCOMPLETE: GeoZone - # ADDPR: specify the servicer - # ADDPR: specify the relay method and params When the application sends a relay to a servicer + # Balance: 1,160,126.46817237178258965 ETH = 0xf5aa94f49d4fd1f8dcd2 Then the relay response contains 0xf5aa94f49d4fd1f8dcd2 And the validator should have exited without error From ef8eda411724325ae835f024ece2f2a4461eca21 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Mon, 3 Jul 2023 12:28:24 -0400 Subject: [PATCH 04/20] Add a servicer config to LocalNet --- charts/pocket/pocket-servicer-overrides.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 charts/pocket/pocket-servicer-overrides.yaml diff --git a/charts/pocket/pocket-servicer-overrides.yaml b/charts/pocket/pocket-servicer-overrides.yaml new file mode 100644 index 000000000..dcabfa46a --- /dev/null +++ b/charts/pocket/pocket-servicer-overrides.yaml @@ -0,0 +1,8 @@ +config: + servicer: + enabled: true + address: "001022b138896c4c5466ac86b24a9bbe249905c2" + public_key: "56915c1270bc8d9280a633e0be51647f62388a851318381614877ef2ed84a495" + services: + "0001": + url: "https://eth-mainnet.gateway.pokt.network" From 1f2e14345084069023dfec71c2577ace33c998cc Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Mon, 3 Jul 2023 17:48:24 -0400 Subject: [PATCH 05/20] Address review comment --- charts/pocket/pocket-servicer-overrides.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/charts/pocket/pocket-servicer-overrides.yaml b/charts/pocket/pocket-servicer-overrides.yaml index dcabfa46a..81dc916e6 100644 --- a/charts/pocket/pocket-servicer-overrides.yaml +++ b/charts/pocket/pocket-servicer-overrides.yaml @@ -1,6 +1,8 @@ config: servicer: enabled: true + # The address and public_key fields are taken from the genesis section of the config file: + # https://github.com/pokt-network/pocket/blob/40f325305c1756bbfd069bf139fa67545419981c/build/localnet/manifests/configs.yaml#L1699 address: "001022b138896c4c5466ac86b24a9bbe249905c2" public_key: "56915c1270bc8d9280a633e0be51647f62388a851318381614877ef2ed84a495" services: From aee600948ff74d436870d89ebeedaa709481da04 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 4 Jul 2023 08:48:22 -0400 Subject: [PATCH 06/20] Address review comment --- e2e/tests/steps_init_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/e2e/tests/steps_init_test.go b/e2e/tests/steps_init_test.go index fbbdd516b..6357c1b7a 100644 --- a/e2e/tests/steps_init_test.go +++ b/e2e/tests/steps_init_test.go @@ -70,8 +70,8 @@ type rootSuite struct { // relaychainSettings holds the settings for a specific relaychain type relaychainSettings struct { - Account string - Height string + account string + height string } func (s *rootSuite) Before() { @@ -209,7 +209,7 @@ func (s *rootSuite) getPrivateKey( // "Given the application has a valid ethereum relaychain account" func (s *rootSuite) TheApplicationHasAValidEthereumRelaychainAccount() { // Account: 0x8315177aB297bA92A06054cE80a67Ed4DBd7ed3a (Arbitrum Bridge) - s.relaychains[relaychainEth].Account = "0x8315177aB297bA92A06054cE80a67Ed4DBd7ed3a" + s.relaychains[relaychainEth].account = "0x8315177aB297bA92A06054cE80a67Ed4DBd7ed3a" } // TheApplicationHasAValidEthereumRelaychaindHeight fullfils the following condition from feature file: @@ -217,7 +217,7 @@ func (s *rootSuite) TheApplicationHasAValidEthereumRelaychainAccount() { // "Given the application has a valid ethereum relaychain height" func (s *rootSuite) TheApplicationHasAValidEthereumRelaychainHeight() { // Ethereum relaychain BlockNumber: 17605670 = 0x10CA426 - s.relaychains[relaychainEth].Height = "0x10CA426" + s.relaychains[relaychainEth].height = "0x10CA426" } // TheApplicationHasAValidServicer fullfils the following condition from feature file: @@ -231,7 +231,7 @@ func (s *rootSuite) TheApplicationHasAValidServicer() { func (s *rootSuite) TheApplicationSendsARelayToAServicer() { // ADDPR: Add a servicer staked for the Ethereum RelayChain // ADDPR: Verify the response: correct id and correct jsonrpc - params := fmt.Sprintf("%q: [%q, %q]", "params", s.relaychains[relaychainEth].Account, s.relaychains[relaychainEth].Height) + params := fmt.Sprintf("%q: [%q, %q]", "params", s.relaychains[relaychainEth].account, s.relaychains[relaychainEth].height) checkBalanceRelay := fmt.Sprintf("{%s, %s}", `"method": "eth_getBalance", "id": "1", "jsonrpc": "2.0"`, params) servicerPrivateKey := s.getServicerPrivateKey(s.servicer) From 40d99649858bc654dbe4531b9a9ccb662b72dbd3 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 4 Jul 2023 08:57:57 -0400 Subject: [PATCH 07/20] Address review comment --- e2e/tests/steps_init_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/tests/steps_init_test.go b/e2e/tests/steps_init_test.go index 6357c1b7a..f4a2cedab 100644 --- a/e2e/tests/steps_init_test.go +++ b/e2e/tests/steps_init_test.go @@ -90,6 +90,7 @@ func (s *rootSuite) Before() { // ADDPR: use pocketk8s to populate s.servicerKeys = map[string]string{ // 000 servicer NOT in session + // The list of servicers in the session is decided by the 'servicers' section of the genesis, from 'build/localnet/manifest/configs.yaml' file "000": "acbca21f295caefdfe480ceba85f3fed31a50915162f94867f9c23d8f474f4c6d1130c5eb920af8edd5b6bfa39d33aa787f421c8ba0786de4ca4e7703553bb97", // 001 servicer in session "001": "eec4072b095acf60be9d6be4093b14a24e2ddb6e9d385d980a635815961d025856915c1270bc8d9280a633e0be51647f62388a851318381614877ef2ed84a495", From 6df0ef29fbb1a3ad3233149e8f76e9284547beab Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 4 Jul 2023 09:03:53 -0400 Subject: [PATCH 08/20] Address review comment --- rpc/v1/openapi.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/rpc/v1/openapi.yaml b/rpc/v1/openapi.yaml index 5f2765f3c..7b697cce5 100644 --- a/rpc/v1/openapi.yaml +++ b/rpc/v1/openapi.yaml @@ -1706,7 +1706,6 @@ components: type: string method: type: string - # ADDPR: DISCUSS: params can be anything: should we use AnyValue for it? params: type: array items: From e88fb2eef9e2bd95a3c1d6df40f759eaeeb4bc14 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 4 Jul 2023 09:05:46 -0400 Subject: [PATCH 09/20] Address review comment --- utility/servicer/module.go | 1 - 1 file changed, 1 deletion(-) diff --git a/utility/servicer/module.go b/utility/servicer/module.go index f06a73f3d..d2c4da1f5 100644 --- a/utility/servicer/module.go +++ b/utility/servicer/module.go @@ -238,7 +238,6 @@ func (s *servicer) validateRelayChainSupport(relayChain *coreTypes.Identifiable, return fmt.Errorf("error decoding servicer address %s: %w", s.config.Address, err) } - // DISCUSS: should we update the GetServicer signature to take a string instead? alternatively, we could use []byte as type of servicer address servicer, err := readCtx.GetServicer(servicerAddrBz, currentHeight) if err != nil { return fmt.Errorf("error reading servicer from persistence: %w", err) From 027a69cd8cb3643329501ff44b57d748b48d3e56 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 4 Jul 2023 09:34:19 -0400 Subject: [PATCH 10/20] Address review comment --- Makefile | 5 +++++ e2e/tests/steps_init_test.go | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/Makefile b/Makefile index 43eaf02e5..9510de673 100644 --- a/Makefile +++ b/Makefile @@ -347,6 +347,11 @@ test_e2e: kubectl_check ## Run all E2E tests echo "IMPROVE(#759): Make sure you ran 'make localnet_up' in case this fails with infrastructure related errors." go test ${VERBOSE_TEST} -count=1 -tags=test,e2e ./e2e/tests/... +.PHONY: test_e2e_relay +test_e2e_relay: kubectl_check + echo "IMPROVE(#759): Make sure you ran 'make localnet_up' in case this fails with infrastructure related errors." + go test ${VERBOSE_TEST} -count=1 -tags=test,e2e -run TestRelay ./e2e/tests/... + .PHONY: test_all_with_json_coverage test_all_with_json_coverage: generate_rpc_openapi ## Run all go unit tests, output results & coverage into json & coverage files go test -p=1 -count=1 -tags=test -json ./... -covermode=count -coverprofile=coverage.out | tee test_results.json | jq diff --git a/e2e/tests/steps_init_test.go b/e2e/tests/steps_init_test.go index f4a2cedab..4a0a66ac1 100644 --- a/e2e/tests/steps_init_test.go +++ b/e2e/tests/steps_init_test.go @@ -109,6 +109,17 @@ func (s *rootSuite) Before() { // * This test suite assumes that a LocalNet is running that can be accessed by `kubectl` func TestFeatures(t *testing.T) { runner := gocuke.NewRunner(t, &rootSuite{}).Path("*.feature") + runTests(runner) +} + +// TestRelay builds a test runner which only includes relay tests +func TestRelay(t *testing.T) { + runner := gocuke.NewRunner(t, &rootSuite{}).Path("*_relays.feature") + runTests(runner) +} + +// runTests adds steps that need to be registered manually and runs the tests +func runTests(runner *gocuke.Runner) { // DISCUSS: is there a better way to make gocuke pickup the balance, i.e. a hexadecimal, as a string in function argument? runner.Step(`^the\srelay\sresponse\scontains\s([[:alnum:]]+)$`, (*rootSuite).TheRelayResponseContains) runner.Run() From 00990a66db9dc122250b7c29585eb7c5b36b3684 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 4 Jul 2023 09:36:25 -0400 Subject: [PATCH 11/20] Address review comment --- e2e/tests/trustless_relays.feature | 7 ------- 1 file changed, 7 deletions(-) diff --git a/e2e/tests/trustless_relays.feature b/e2e/tests/trustless_relays.feature index 0220f1bd3..a67383891 100644 --- a/e2e/tests/trustless_relays.feature +++ b/e2e/tests/trustless_relays.feature @@ -1,11 +1,4 @@ Feature: Trustless Relays - - Scenario: User Wants Help Using The Servicer Command - When the user runs the command "Servicer help" - Then the user should be able to see standard output containing "Available Commands" - And the validator should have exited without error - - # Happy test case: An Application requests the account balance of a specific address at a specific height from a Servicer staked for the Ethereum RelayChain, and receives a successful response. # ADDPR: Add a servicer staked for the Ethereum relaychain to the genesis file From 14fd3f23507073246189f0d5f9ec3d87e5aa006f Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 4 Jul 2023 09:44:54 -0400 Subject: [PATCH 12/20] Address review comment --- e2e/tests/steps_init_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/tests/steps_init_test.go b/e2e/tests/steps_init_test.go index 4a0a66ac1..67cbb0544 100644 --- a/e2e/tests/steps_init_test.go +++ b/e2e/tests/steps_init_test.go @@ -40,6 +40,7 @@ const ( relaychainEth = "RelayChainETH" // used to refer to Ethereum chain when retrieving relaychain settings ) +// TODO(#874, olshansky): Populate the app & servicer keys with the full set type rootSuite struct { gocuke.TestingT From 6d6a383ffdc955427bc1e985361f3d25d8a978de Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Thu, 6 Jul 2023 08:25:35 -0400 Subject: [PATCH 13/20] Address review comments --- Makefile | 1 + build/localnet/manifests/configs.yaml | 1 + e2e/tests/steps_init_test.go | 22 +++++++++++----------- e2e/tests/trustless_relays.feature | 9 ++++++--- rpc/handlers.go | 2 +- shared/k8s/debug.go | 2 +- utility/servicer/module.go | 2 +- 7 files changed, 22 insertions(+), 17 deletions(-) diff --git a/Makefile b/Makefile index 9510de673..eae3f5c64 100644 --- a/Makefile +++ b/Makefile @@ -471,6 +471,7 @@ benchmark_p2p_peerstore: ## Run P2P peerstore benchmarks # BUG - There is a known existing bug in this code # DISCUSS_IN_THIS_COMMIT - SHOULD NEVER BE COMMITTED TO MASTER. It is a way for the reviewer of a PR to start / reply to a discussion. # TODO_IN_THIS_COMMIT - SHOULD NEVER BE COMMITTED TO MASTER. It is a way to start the review process while non-critical changes are still in progress +# ADD_IN_THIS_PR - SHOULD NEVER BE COMMITTED TO MASTER. It is a way to mark functionality that should be added, possibly in a different PR, to allow this comment to be removed. TODO_KEYWORDS = -e "TODO" -e "DECIDE" -e "TECHDEBT" -e "IMPROVE" -e "OPTIMIZE" -e "DISCUSS" -e "INCOMPLETE" -e "INVESTIGATE" -e "CLEANUP" -e "HACK" -e "REFACTOR" -e "CONSIDERATION" -e "TODO_IN_THIS_COMMIT" -e "DISCUSS_IN_THIS_COMMIT" -e "CONSOLIDATE" -e "DEPRECATE" -e "ADDTEST" -e "RESEARCH" -e "BUG" # How do I use TODOs? diff --git a/build/localnet/manifests/configs.yaml b/build/localnet/manifests/configs.yaml index 3a3574e47..7136be142 100644 --- a/build/localnet/manifests/configs.yaml +++ b/build/localnet/manifests/configs.yaml @@ -1699,6 +1699,7 @@ data: "address": "001022b138896c4c5466ac86b24a9bbe249905c2", "public_key": "56915c1270bc8d9280a633e0be51647f62388a851318381614877ef2ed84a495", "chains": ["0001"], + # ADD_IN_THIS_PR: update all RPC ports and add the protocol, i.e. HTTPS "service_url": "http://servicer-001-pocket:50832", "staked_amount": "1000000000000", "paused_height": -1, diff --git a/e2e/tests/steps_init_test.go b/e2e/tests/steps_init_test.go index 67cbb0544..4e4707bf4 100644 --- a/e2e/tests/steps_init_test.go +++ b/e2e/tests/steps_init_test.go @@ -62,11 +62,11 @@ type rootSuite struct { appKeys map[string]string // relaychains holds settings for all relaychains used in the tests - // the map key is a constant selected as the identifier for the relaychain, e.g. "RelayChainETH" for Ethereum + // the map key is a constant selected as the identifier for the relaychain, e.g. "RelayChainETH" represented as "0001" in other parts of the codebase for Ethereum relaychains map[string]*relaychainSettings // servicer holds the key for the servicer that should received the relay - servicer string + servicerKey string } // relaychainSettings holds the settings for a specific relaychain @@ -88,7 +88,7 @@ func (s *rootSuite) Before() { s.clientset = clientSet s.validatorKeys = vkmap - // ADDPR: use pocketk8s to populate + // ADD_IN_THIS_PR: use pocketk8s to populate s.servicerKeys = map[string]string{ // 000 servicer NOT in session // The list of servicers in the session is decided by the 'servicers' section of the genesis, from 'build/localnet/manifest/configs.yaml' file @@ -241,9 +241,9 @@ func (s *rootSuite) TheApplicationHasAValidServicer() { } // An Application requests the account balance of a specific address at a specific height -func (s *rootSuite) TheApplicationSendsARelayToAServicer() { - // ADDPR: Add a servicer staked for the Ethereum RelayChain - // ADDPR: Verify the response: correct id and correct jsonrpc + func (s *rootSuite) TheApplicationSendsAGetBalanceRelayAtASpecificHeightToAnEthereumServicer() { + // ADD_IN_THIS_PR: Add a servicer staked for the Ethereum RelayChain + // ADD_IN_THIS_PR: Verify the response: correct id and correct jsonrpc params := fmt.Sprintf("%q: [%q, %q]", "params", s.relaychains[relaychainEth].account, s.relaychains[relaychainEth].height) checkBalanceRelay := fmt.Sprintf("{%s, %s}", `"method": "eth_getBalance", "id": "1", "jsonrpc": "2.0"`, params) @@ -253,8 +253,8 @@ func (s *rootSuite) TheApplicationSendsARelayToAServicer() { s.sendTrustlessRelay(checkBalanceRelay, servicerPrivateKey.Address().String(), appPrivateKey.Address().String()) } -func (s *rootSuite) TheRelayResponseContains(arg1 string) { - require.Contains(s, s.validator.result.Stdout, arg1) +func (s *rootSuite) TheRelayResponseContains(relayResponse string) { + require.Contains(s, s.validator.result.Stdout, relayResponse) } func (s *rootSuite) sendTrustlessRelay(relayPayload string, servicerAddr, appAddr string) { @@ -268,7 +268,7 @@ func (s *rootSuite) sendTrustlessRelay(relayPayload string, servicerAddr, appAdd relayPayload, } - // DISCUSS: does this need to be run from a client, i.e. not a validator, pod? + // TECHDEBT: run the command from a client, i.e. not a validator, pod. res, err := s.validator.RunCommand(args...) require.NoError(s, err) @@ -282,7 +282,7 @@ func (s *rootSuite) getAppPrivateKey( ) cryptoPocket.PrivateKey { privHexString := s.appKeys[appId] privateKey, err := cryptoPocket.NewPrivateKey(privHexString) - require.NoErrorf(s, err, "failed to extract privkey") + require.NoErrorf(s, err, "failed to extract privkey for app with id %s", appId) return privateKey } @@ -293,7 +293,7 @@ func (s *rootSuite) getServicerPrivateKey( ) cryptoPocket.PrivateKey { privHexString := s.servicerKeys[servicerId] privateKey, err := cryptoPocket.NewPrivateKey(privHexString) - require.NoErrorf(s, err, "failed to extract privkey") + require.NoErrorf(s, err, "failed to extract privkey for servicer with id %s", servicerId) return privateKey } diff --git a/e2e/tests/trustless_relays.feature b/e2e/tests/trustless_relays.feature index a67383891..a230a2a15 100644 --- a/e2e/tests/trustless_relays.feature +++ b/e2e/tests/trustless_relays.feature @@ -1,15 +1,18 @@ Feature: Trustless Relays # Happy test case: An Application requests the account balance of a specific address at a specific height from a Servicer staked for the Ethereum RelayChain, and receives a successful response. - # ADDPR: Add a servicer staked for the Ethereum relaychain to the genesis file + # ADD_IN_THIS_PR: Add a servicer staked for the Ethereum relaychain to the genesis file Scenario: Application can send a trustless relay to a relaychain to get an account's balance at a specific height Given the application has a valid ethereum relaychain account Given the application has a valid ethereum relaychain height Given the application has a valid servicer # INCOMPLETE: GeoZone - When the application sends a relay to a servicer + When the application sends a get balance relay at a specific height to an Ethereum Servicer # Balance: 1,160,126.46817237178258965 ETH = 0xf5aa94f49d4fd1f8dcd2 Then the relay response contains 0xf5aa94f49d4fd1f8dcd2 + # TECHDEBT: replace validator with client And the validator should have exited without error - # ADDPR: Sad test case: An Application requests the account balance of a specific address at a specific height from a Servicer staked for the Ethereum RelayChain in the same GeoZone, and the request times out without a response. + # ADD_IN_THIS_PR: Sad test case: An Application requests the account balance of a specific address at a specific height from a Servicer staked for the Ethereum RelayChain in the same GeoZone, and the request times out without a response. + + # TODO: add an E2E test for a trustless relay, where the application retrieves the session first, using a new fetch session command diff --git a/rpc/handlers.go b/rpc/handlers.go index e28216e91..75c6d095c 100644 --- a/rpc/handlers.go +++ b/rpc/handlers.go @@ -231,7 +231,7 @@ func buildJsonRPCRelayPayload(body *RelayRequest) *coreTypes.Relay { } if body.Payload.Params != nil { - // ADDPR: Need a decision and implementation on Params field and conversion from rpc to proto + // ADD_IN_THIS_PR: Need a decision and implementation on Params field and conversion from rpc to proto payload.JsonRpcPayload.Params = *body.Payload.Params } diff --git a/shared/k8s/debug.go b/shared/k8s/debug.go index 5e2130ea5..f7cfa8e0c 100644 --- a/shared/k8s/debug.go +++ b/shared/k8s/debug.go @@ -50,7 +50,7 @@ func FetchValidatorPrivateKeys(clientset *kubernetes.Clientset) (map[string]stri return validatorKeysMap, nil } -// ADDPR: add the following functions in a separate PR: FetchServicerPrivateKeys and FetchAppPrivateKeys +// ADD_IN_THIS_PR: add the following functions in a separate PR: FetchServicerPrivateKeys and FetchAppPrivateKeys func getNamespace() (string, error) { _, err := os.Stat(kubernetesServiceAccountNamespaceFile) diff --git a/utility/servicer/module.go b/utility/servicer/module.go index d2c4da1f5..a29189a90 100644 --- a/utility/servicer/module.go +++ b/utility/servicer/module.go @@ -476,7 +476,7 @@ func (s *servicer) executeHTTPRelay(serviceConfig *configs.ServiceConfig, payloa } // INCOMPLETE(#837): Optimize usage of HTTP client, e.g. connection reuse, depending on the volume of relays a servicer is expected to handle - // ADDPR: allow configuration of TLS Settings for HTTPS services + // INCOMPLETE(#887): allow configuration of TLS Settings for HTTPS services tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} resp, err := (&http.Client{Transport: tr, Timeout: time.Duration(serviceConfig.TimeoutMsec) * time.Millisecond}).Do(req) if err != nil { From bfe7ac5fa39ff14dc659072945504be5454110e2 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Thu, 6 Jul 2023 08:28:16 -0400 Subject: [PATCH 14/20] Address review comment --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index eae3f5c64..25000cb96 100644 --- a/Makefile +++ b/Makefile @@ -472,7 +472,7 @@ benchmark_p2p_peerstore: ## Run P2P peerstore benchmarks # DISCUSS_IN_THIS_COMMIT - SHOULD NEVER BE COMMITTED TO MASTER. It is a way for the reviewer of a PR to start / reply to a discussion. # TODO_IN_THIS_COMMIT - SHOULD NEVER BE COMMITTED TO MASTER. It is a way to start the review process while non-critical changes are still in progress # ADD_IN_THIS_PR - SHOULD NEVER BE COMMITTED TO MASTER. It is a way to mark functionality that should be added, possibly in a different PR, to allow this comment to be removed. -TODO_KEYWORDS = -e "TODO" -e "DECIDE" -e "TECHDEBT" -e "IMPROVE" -e "OPTIMIZE" -e "DISCUSS" -e "INCOMPLETE" -e "INVESTIGATE" -e "CLEANUP" -e "HACK" -e "REFACTOR" -e "CONSIDERATION" -e "TODO_IN_THIS_COMMIT" -e "DISCUSS_IN_THIS_COMMIT" -e "CONSOLIDATE" -e "DEPRECATE" -e "ADDTEST" -e "RESEARCH" -e "BUG" +TODO_KEYWORDS = -e "TODO" -e "DECIDE" -e "TECHDEBT" -e "IMPROVE" -e "OPTIMIZE" -e "DISCUSS" -e "INCOMPLETE" -e "INVESTIGATE" -e "CLEANUP" -e "HACK" -e "REFACTOR" -e "CONSIDERATION" -e "TODO_IN_THIS_COMMIT" -e "DISCUSS_IN_THIS_COMMIT" -e "ADD_IN_THIS_PR" -e "CONSOLIDATE" -e "DEPRECATE" -e "ADDTEST" -e "RESEARCH" -e "BUG" # How do I use TODOs? # 1. : ; From f41039b42ce628f73afe27b7f7b6111cca085cf0 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Thu, 6 Jul 2023 20:25:43 -0400 Subject: [PATCH 15/20] Address review comments --- charts/pocket/pocket-servicer-overrides.yaml | 4 ++++ rpc/handlers.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/charts/pocket/pocket-servicer-overrides.yaml b/charts/pocket/pocket-servicer-overrides.yaml index 81dc916e6..008f20191 100644 --- a/charts/pocket/pocket-servicer-overrides.yaml +++ b/charts/pocket/pocket-servicer-overrides.yaml @@ -1,3 +1,7 @@ +# - This is an override of the shared config: +# - This is a reference of how we can utilize Helm to configure the service +# - Configs can also be overrident without an explicit config file: https://github.com/pokt-network/pocket/blob/main/build/localnet/Tiltfile#L216 +# - Settings specific to a single instance of the servicer, e.g. public key and address, are a good fit for this file config: servicer: enabled: true diff --git a/rpc/handlers.go b/rpc/handlers.go index 75c6d095c..5f2a0b2e3 100644 --- a/rpc/handlers.go +++ b/rpc/handlers.go @@ -231,7 +231,7 @@ func buildJsonRPCRelayPayload(body *RelayRequest) *coreTypes.Relay { } if body.Payload.Params != nil { - // ADD_IN_THIS_PR: Need a decision and implementation on Params field and conversion from rpc to proto + // DISCUSS: Need a decision and implementation on Params field and conversion from rpc to proto payload.JsonRpcPayload.Params = *body.Payload.Params } From ed740dbad4eb3f69d4223d1687cf8548636f55c1 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 18 Jul 2023 13:09:29 -0400 Subject: [PATCH 16/20] Fix syntax error --- e2e/tests/steps_init_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/tests/steps_init_test.go b/e2e/tests/steps_init_test.go index 4e4707bf4..f3da50b91 100644 --- a/e2e/tests/steps_init_test.go +++ b/e2e/tests/steps_init_test.go @@ -237,7 +237,7 @@ func (s *rootSuite) TheApplicationHasAValidEthereumRelaychainHeight() { // // "Given the application has a valid servicer" func (s *rootSuite) TheApplicationHasAValidServicer() { - s.servicer = servicerA + s.servicerKey = servicerA } // An Application requests the account balance of a specific address at a specific height @@ -247,7 +247,7 @@ func (s *rootSuite) TheApplicationHasAValidServicer() { params := fmt.Sprintf("%q: [%q, %q]", "params", s.relaychains[relaychainEth].account, s.relaychains[relaychainEth].height) checkBalanceRelay := fmt.Sprintf("{%s, %s}", `"method": "eth_getBalance", "id": "1", "jsonrpc": "2.0"`, params) - servicerPrivateKey := s.getServicerPrivateKey(s.servicer) + servicerPrivateKey := s.getServicerPrivateKey(s.servicerKey) appPrivateKey := s.getAppPrivateKey(appA) s.sendTrustlessRelay(checkBalanceRelay, servicerPrivateKey.Address().String(), appPrivateKey.Address().String()) From fbb4c8fd1ff3a6ed069b79732773d34c64c855a0 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 25 Jul 2023 17:40:12 -0400 Subject: [PATCH 17/20] remove ADD_IN_THIS_PR for updating all RPC ports --- build/localnet/manifests/configs.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/build/localnet/manifests/configs.yaml b/build/localnet/manifests/configs.yaml index 7136be142..3a3574e47 100644 --- a/build/localnet/manifests/configs.yaml +++ b/build/localnet/manifests/configs.yaml @@ -1699,7 +1699,6 @@ data: "address": "001022b138896c4c5466ac86b24a9bbe249905c2", "public_key": "56915c1270bc8d9280a633e0be51647f62388a851318381614877ef2ed84a495", "chains": ["0001"], - # ADD_IN_THIS_PR: update all RPC ports and add the protocol, i.e. HTTPS "service_url": "http://servicer-001-pocket:50832", "staked_amount": "1000000000000", "paused_height": -1, From a48dc0d821af81a4c8ea4d047532d6962033a7e3 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Thu, 27 Jul 2023 10:21:53 -0400 Subject: [PATCH 18/20] Address IN_THIS_PR --- e2e/tests/steps_init_test.go | 19 ++++++++++++++++--- shared/k8s/debug.go | 2 -- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/e2e/tests/steps_init_test.go b/e2e/tests/steps_init_test.go index b8759c859..7e2c70a69 100644 --- a/e2e/tests/steps_init_test.go +++ b/e2e/tests/steps_init_test.go @@ -33,6 +33,8 @@ const ( validatorB = "002" chainId = "0001" + // 001 servicer is in session 0 for applicatio 000 + // The list of servicers in the session is decided by the 'servicers' section of the genesis, from 'build/localnet/manifest/configs.yaml' file servicerA = "001" appA = "000" serviceA = "0001" @@ -85,22 +87,33 @@ func (s *rootSuite) Before() { e2eLogger.Fatal().Err(err).Msg("failed to get validator key map") } + skmap, err := pocketk8s.FetchServicerPrivateKeys(clientSet) + if err != nil { + e2eLogger.Fatal().Err(err).Msg("failed to get validator key map") + } + + akmap, err := pocketk8s.FetchApplicationPrivateKeys(clientSet) + if err != nil { + e2eLogger.Fatal().Err(err).Msg("failed to get validator key map") + } + s.validator = new(validatorPod) s.clientset = clientSet s.validatorKeys = vkmap + s.servicerKeys = skmap + s.appKeys = akmap - // ADD_IN_THIS_PR: use pocketk8s to populate + /* s.servicerKeys = map[string]string{ // 000 servicer NOT in session - // The list of servicers in the session is decided by the 'servicers' section of the genesis, from 'build/localnet/manifest/configs.yaml' file "000": "acbca21f295caefdfe480ceba85f3fed31a50915162f94867f9c23d8f474f4c6d1130c5eb920af8edd5b6bfa39d33aa787f421c8ba0786de4ca4e7703553bb97", - // 001 servicer in session "001": "eec4072b095acf60be9d6be4093b14a24e2ddb6e9d385d980a635815961d025856915c1270bc8d9280a633e0be51647f62388a851318381614877ef2ed84a495", } s.appKeys = map[string]string{ "000": "468cc03083d72f2440d3d08d12143b9b74cca9460690becaa2499a4f04fddaa805a25e527bf6f51676f61f2f1a96efaa748218ac82f54d3cdc55a4881389eb60", } + */ s.relaychains = map[string]*relaychainSettings{ relaychainEth: {}, diff --git a/shared/k8s/debug.go b/shared/k8s/debug.go index 157d0110e..a340fcfda 100644 --- a/shared/k8s/debug.go +++ b/shared/k8s/debug.go @@ -77,8 +77,6 @@ func fetchPrivateKeys(clientset *kubernetes.Clientset, resourceName string) (map return privateKeysMap, nil } -// ADD_IN_THIS_PR: add the following functions in a separate PR: FetchServicerPrivateKeys and FetchAppPrivateKeys - func getNamespace() (string, error) { _, err := os.Stat(kubernetesServiceAccountNamespaceFile) if err == nil { From 3c46849c3946635c4125e4f77078fc34a9733288 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Sat, 29 Jul 2023 07:28:36 -0400 Subject: [PATCH 19/20] Verify id and jsonrpc fields in e2e response --- e2e/tests/steps_init_test.go | 11 +++++++++-- e2e/tests/trustless_relays.feature | 2 ++ rpc/handlers.go | 2 +- shared/core/types/proto/relay.proto | 6 +++++- shared/core/types/relay.go | 7 +++++++ shared/core/types/relay_test.go | 26 ++++++++++++++++++++------ 6 files changed, 44 insertions(+), 10 deletions(-) diff --git a/e2e/tests/steps_init_test.go b/e2e/tests/steps_init_test.go index 7e2c70a69..992beb2d4 100644 --- a/e2e/tests/steps_init_test.go +++ b/e2e/tests/steps_init_test.go @@ -253,9 +253,8 @@ func (s *rootSuite) TheApplicationHasAValidServicer() { } // An Application requests the account balance of a specific address at a specific height - func (s *rootSuite) TheApplicationSendsAGetBalanceRelayAtASpecificHeightToAnEthereumServicer() { +func (s *rootSuite) TheApplicationSendsAGetBalanceRelayAtASpecificHeightToAnEthereumServicer() { // ADD_IN_THIS_PR: Add a servicer staked for the Ethereum RelayChain - // ADD_IN_THIS_PR: Verify the response: correct id and correct jsonrpc params := fmt.Sprintf("%q: [%q, %q]", "params", s.relaychains[relaychainEth].account, s.relaychains[relaychainEth].height) checkBalanceRelay := fmt.Sprintf("{%s, %s}", `"method": "eth_getBalance", "id": "1", "jsonrpc": "2.0"`, params) @@ -269,6 +268,14 @@ func (s *rootSuite) TheRelayResponseContains(relayResponse string) { require.Contains(s, s.validator.result.Stdout, relayResponse) } +func (s *rootSuite) TheRelayResponseIsValidJsonRpc() { + require.Contains(s, s.validator.result.Stdout, `"jsonrpc":"2.0"`) +} + +func (s *rootSuite) TheRelayResponseHasValidId() { + require.Contains(s, s.validator.result.Stdout, `"id":1`) +} + func (s *rootSuite) sendTrustlessRelay(relayPayload string, servicerAddr, appAddr string) { args := []string{ "Servicer", diff --git a/e2e/tests/trustless_relays.feature b/e2e/tests/trustless_relays.feature index a230a2a15..80ff910f9 100644 --- a/e2e/tests/trustless_relays.feature +++ b/e2e/tests/trustless_relays.feature @@ -10,6 +10,8 @@ Feature: Trustless Relays When the application sends a get balance relay at a specific height to an Ethereum Servicer # Balance: 1,160,126.46817237178258965 ETH = 0xf5aa94f49d4fd1f8dcd2 Then the relay response contains 0xf5aa94f49d4fd1f8dcd2 + And the relay response is valid json rpc + And the relay response has valid id # TECHDEBT: replace validator with client And the validator should have exited without error diff --git a/rpc/handlers.go b/rpc/handlers.go index 5f2a0b2e3..facfa994f 100644 --- a/rpc/handlers.go +++ b/rpc/handlers.go @@ -227,7 +227,7 @@ func buildJsonRPCRelayPayload(body *RelayRequest) *coreTypes.Relay { } if body.Payload.Id != nil { - payload.JsonRpcPayload.Id = []byte(*body.Payload.Id) + payload.JsonRpcPayload.Id = &coreTypes.JsonRpcId{Id: []byte(*body.Payload.Id)} } if body.Payload.Params != nil { diff --git a/shared/core/types/proto/relay.proto b/shared/core/types/proto/relay.proto index 2679d687c..ff9f12c4e 100644 --- a/shared/core/types/proto/relay.proto +++ b/shared/core/types/proto/relay.proto @@ -31,11 +31,15 @@ enum RESTRequestType { RESTRequestTypeDELETE = 3; } +message JSONRPCId { + bytes id = 1; +} + message JSONRPCPayload { // JSONRPC version 2 expected a field named "id". // See the JSONRPC spec in the following link for more details: // https://www.jsonrpc.org/specification#request_object - bytes id = 1; + JSONRPCId id = 1; // JSONRPC version 2 expects a field named "jsonrpc" with a value of "2.0". // See the JSONRPC spec in the following link for more details: // https://www.jsonrpc.org/specification#request_object diff --git a/shared/core/types/relay.go b/shared/core/types/relay.go index 91e5424ff..c9d7d7273 100644 --- a/shared/core/types/relay.go +++ b/shared/core/types/relay.go @@ -54,3 +54,10 @@ func (p *RESTPayload) Validate() error { } return nil } + +// MarshalJSON is a custom marshaller for JSONRPCId type to return the byte array as-is. +// +// This is to ensure the specified ID gets sent correctly when serializing the relay that contains the ID. +func (i *JSONRPCId) MarshalJSON() ([]byte, error) { + return i.Id, nil +} diff --git a/shared/core/types/relay_test.go b/shared/core/types/relay_test.go index db34d8dec..a0c1bb47c 100644 --- a/shared/core/types/relay_test.go +++ b/shared/core/types/relay_test.go @@ -1,6 +1,7 @@ package types import ( + "encoding/json" "testing" "github.com/stretchr/testify/require" @@ -16,7 +17,7 @@ func TestRelay_Validate(t *testing.T) { name: "valid Relay: JSONRPC", relay: &Relay{ RelayPayload: &Relay_JsonRpcPayload{ - JsonRpcPayload: &JSONRPCPayload{JsonRpc: "2.0", Method: "eth_blockNumber"}, + JsonRpcPayload: &JSONRPCPayload{Jsonrpc: "2.0", Method: "eth_blockNumber"}, }, }, }, @@ -37,7 +38,7 @@ func TestRelay_Validate(t *testing.T) { name: "invalid Relay: invalid JSONRPC Payload", relay: &Relay{ RelayPayload: &Relay_JsonRpcPayload{ - JsonRpcPayload: &JSONRPCPayload{JsonRpc: "foo"}, + JsonRpcPayload: &JSONRPCPayload{Jsonrpc: "foo"}, }, }, expected: errInvalidJSONRPC, @@ -69,16 +70,16 @@ func TestRelay_ValidateJsonRpc(t *testing.T) { }{ { name: "valid JSONRPC", - payload: &JSONRPCPayload{JsonRpc: "2.0", Method: "eth_blockNumber"}, + payload: &JSONRPCPayload{Jsonrpc: "2.0", Method: "eth_blockNumber"}, }, { - name: "invalid JSONRPC: invalid JsonRpc field value", - payload: &JSONRPCPayload{JsonRpc: "foo", Method: "eth_blockNumber"}, + name: "invalid JSONRPC: invalid Jsonrpc field value", + payload: &JSONRPCPayload{Jsonrpc: "foo", Method: "eth_blockNumber"}, expected: errInvalidJSONRPC, }, { name: "invalid JSONRPC: Method field not set", - payload: &JSONRPCPayload{JsonRpc: "2.0"}, + payload: &JSONRPCPayload{Jsonrpc: "2.0"}, expected: errInvalidJSONRPCMissingMethod, }, } @@ -115,3 +116,16 @@ func TestRelay_ValidateREST(t *testing.T) { }) } } + +func TestRelay_MarshalJSONRPC(t *testing.T) { + payload := JSONRPCPayload{ + Id: &JSONRPCId{Id: []byte(`"1"`)}, + Jsonrpc: "2.0", + Method: "eth_blockNumber", + } + expected := []byte(`{"id":"1","jsonrpc":"2.0","method":"eth_blockNumber"}`) + + bz, err := json.Marshal(payload) + require.NoError(t, err) + require.Equal(t, bz, expected) +} From 0822a082933dc1f43c2b5323c03adec65924e1f1 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Sun, 30 Jul 2023 17:38:08 -0400 Subject: [PATCH 20/20] Add sad e2e test case: relay timeout --- build/localnet/manifests/configs.yaml | 4 +- charts/pocket/pocket-servicer-overrides.yaml | 5 ++ e2e/tests/steps_init_test.go | 69 ++++++++++++++------ e2e/tests/trustless_relays.feature | 7 ++ rpc/handlers.go | 2 +- 5 files changed, 65 insertions(+), 22 deletions(-) diff --git a/build/localnet/manifests/configs.yaml b/build/localnet/manifests/configs.yaml index da8a091e7..18a648865 100644 --- a/build/localnet/manifests/configs.yaml +++ b/build/localnet/manifests/configs.yaml @@ -1665,7 +1665,7 @@ data: { "address": "00001fff518b1cdddd74c197d76ba5b5dedc0301", "public_key": "05a25e527bf6f51676f61f2f1a96efaa748218ac82f54d3cdc55a4881389eb60", - "chains": ["0001"], + "chains": ["0001", "9999"], "staked_amount": "1000000000000", "paused_height": -1, "unstaking_height": -1, @@ -1698,7 +1698,7 @@ data: { "address": "001022b138896c4c5466ac86b24a9bbe249905c2", "public_key": "56915c1270bc8d9280a633e0be51647f62388a851318381614877ef2ed84a495", - "chains": ["0001"], + "chains": ["0001", "9999"], "service_url": "http://servicer-001-pocket:50832", "staked_amount": "1000000000000", "paused_height": -1, diff --git a/charts/pocket/pocket-servicer-overrides.yaml b/charts/pocket/pocket-servicer-overrides.yaml index 008f20191..63b545086 100644 --- a/charts/pocket/pocket-servicer-overrides.yaml +++ b/charts/pocket/pocket-servicer-overrides.yaml @@ -12,3 +12,8 @@ config: services: "0001": url: "https://eth-mainnet.gateway.pokt.network" + # 9999 is chosen as the ID for a service that always times out when responding to relays + "9999": + # Port 22222 is used by the mock service node to support timeout test scenarios + url: "http://localhost:22222/timing_out_service" + TimeoutMsec: 50 diff --git a/e2e/tests/steps_init_test.go b/e2e/tests/steps_init_test.go index 992beb2d4..f3ec575f5 100644 --- a/e2e/tests/steps_init_test.go +++ b/e2e/tests/steps_init_test.go @@ -4,10 +4,12 @@ package e2e import ( "fmt" + "net/http" "os" "path/filepath" "strings" "testing" + "time" pocketLogger "github.com/pokt-network/pocket/logger" "github.com/pokt-network/pocket/runtime/defaults" @@ -38,6 +40,7 @@ const ( servicerA = "001" appA = "000" serviceA = "0001" + timeoutService = "9999" relaychainEth = "RelayChainETH" // used to refer to Ethereum chain when retrieving relaychain settings ) @@ -96,33 +99,26 @@ func (s *rootSuite) Before() { if err != nil { e2eLogger.Fatal().Err(err).Msg("failed to get validator key map") } - + s.validator = new(validatorPod) s.clientset = clientSet s.validatorKeys = vkmap s.servicerKeys = skmap s.appKeys = akmap - - /* - s.servicerKeys = map[string]string{ - // 000 servicer NOT in session - "000": "acbca21f295caefdfe480ceba85f3fed31a50915162f94867f9c23d8f474f4c6d1130c5eb920af8edd5b6bfa39d33aa787f421c8ba0786de4ca4e7703553bb97", - "001": "eec4072b095acf60be9d6be4093b14a24e2ddb6e9d385d980a635815961d025856915c1270bc8d9280a633e0be51647f62388a851318381614877ef2ed84a495", - } - - s.appKeys = map[string]string{ - "000": "468cc03083d72f2440d3d08d12143b9b74cca9460690becaa2499a4f04fddaa805a25e527bf6f51676f61f2f1a96efaa748218ac82f54d3cdc55a4881389eb60", - } - */ - s.relaychains = map[string]*relaychainSettings{ relaychainEth: {}, } -} + } // TestFeatures runs the e2e tests specified in any .features files in this directory // * This test suite assumes that a LocalNet is running that can be accessed by `kubectl` func TestFeatures(t *testing.T) { + // setup a mock service node that causes a timeout by sleeping for the specified duration + // 22222 is the port used for service ID "0004" in charts/pocket/pocket-servicer-overrides.yaml + // 100 is the delay in milliseconds, selected to be more than the timeout value for service "0004" in charts/pocket/pocket-servicer-overrides.yaml + // This setup is done here to ensure the http path is registered exactly once. + setupMockServiceNodeWithTimeOut(22222, 100) + runner := gocuke.NewRunner(t, &rootSuite{}).Path("*.feature") runTests(runner) } @@ -261,7 +257,23 @@ func (s *rootSuite) TheApplicationSendsAGetBalanceRelayAtASpecificHeightToAnEthe servicerPrivateKey := s.getServicerPrivateKey(s.servicerKey) appPrivateKey := s.getAppPrivateKey(appA) - s.sendTrustlessRelay(checkBalanceRelay, servicerPrivateKey.Address().String(), appPrivateKey.Address().String()) + s.sendTrustlessRelay(checkBalanceRelay, servicerPrivateKey.Address().String(), appPrivateKey.Address().String(), serviceA, true) +} + +// An Application requests the account balance of a specific address at a specific height on "ServiceWithTimeout", i.e. timing out, service +func (s *rootSuite) TheApplicationSendsAGetBalanceRelayAtASpecificHeightToTheServicewithtimeoutService() { + params := fmt.Sprintf("%q: [%q, %q]", "params", s.relaychains[relaychainEth].account, s.relaychains[relaychainEth].height) + checkBalanceRelay := fmt.Sprintf("{%s, %s}", `"method": "eth_getBalance", "id": "1", "jsonrpc": "2.0"`, params) + + servicerPrivateKey := s.getServicerPrivateKey(s.servicerKey) + appPrivateKey := s.getAppPrivateKey(appA) + + s.sendTrustlessRelay(checkBalanceRelay, servicerPrivateKey.Address().String(), appPrivateKey.Address().String(), timeoutService, false) +} + +// Then the request times out without a response +func (s *rootSuite) TheRequestTimesOutWithoutAResponse() { + require.Contains(s, s.validator.result.Stdout, "HTTP status code: 500") } func (s *rootSuite) TheRelayResponseContains(relayResponse string) { @@ -276,21 +288,23 @@ func (s *rootSuite) TheRelayResponseHasValidId() { require.Contains(s, s.validator.result.Stdout, `"id":1`) } -func (s *rootSuite) sendTrustlessRelay(relayPayload string, servicerAddr, appAddr string) { +func (s *rootSuite) sendTrustlessRelay(relayPayload string, servicerAddr, appAddr, serviceId string, shouldSucceed bool) { args := []string{ "Servicer", "Relay", appAddr, servicerAddr, // IMPROVE: add ETH_Goerli as a chain/service to genesis - serviceA, + serviceId, relayPayload, } // TECHDEBT: run the command from a client, i.e. not a validator, pod. res, err := s.validator.RunCommand(args...) - require.NoError(s, err) + if shouldSucceed { + require.NoError(s, err) + } s.validator.result = res } @@ -350,3 +364,20 @@ func inClusterConfig(t gocuke.TestingT) *rest.Config { return config } + +// setupMockServiceNodeWithTimeout sets up an http server on localhost that causes a timeout by delaying the response +// +// delay is the desired delay in milliseconds +func setupMockServiceNodeWithTimeOut(port int, delay int64) { + http.HandleFunc("/timing_out_service", func(http.ResponseWriter, *http.Request) { + time.Sleep(time.Millisecond * time.Duration(delay)) + return + }) + + go func() { + err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil) + if err != nil { + e2eLogger.Fatal().Err(err).Msg("unexpected error in mock service") + } + }() +} diff --git a/e2e/tests/trustless_relays.feature b/e2e/tests/trustless_relays.feature index 80ff910f9..0fcf5f818 100644 --- a/e2e/tests/trustless_relays.feature +++ b/e2e/tests/trustless_relays.feature @@ -15,6 +15,13 @@ Feature: Trustless Relays # TECHDEBT: replace validator with client And the validator should have exited without error + # ADD_IN_THIS_PR: Sad test case: An Application requests the account balance of a specific address at a specific height from a Servicer staked for the Ethereum RelayChain in the same GeoZone, and the request times out without a response. + # Note: to test the timeout scenario, a local http server is setup which simply sleeps on receiving a request to trigger a timeout + Scenario: An Application requests the account balance of a specific address at a specific height from a Servicer staked for the "TimeoutService" RelayChain in the same GeoZone, and the request times out without a response. + Given the application has a valid servicer + When the application sends a get balance relay at a specific height to the ServiceWithTimeout Service + Then the request times out without a response # TODO: add an E2E test for a trustless relay, where the application retrieves the session first, using a new fetch session command + # TODO: add an E2E test for a trustless relay, where the application is not staked for a service but requests a relay for it, and gets rejected diff --git a/rpc/handlers.go b/rpc/handlers.go index facfa994f..18bea8caf 100644 --- a/rpc/handlers.go +++ b/rpc/handlers.go @@ -227,7 +227,7 @@ func buildJsonRPCRelayPayload(body *RelayRequest) *coreTypes.Relay { } if body.Payload.Id != nil { - payload.JsonRpcPayload.Id = &coreTypes.JsonRpcId{Id: []byte(*body.Payload.Id)} + payload.JsonRpcPayload.Id = &coreTypes.JSONRPCId{Id: []byte(*body.Payload.Id)} } if body.Payload.Params != nil {