Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Utility] trustless relay e2e happy case (POC PR) #869

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions app/client/cli/servicer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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{
Expand All @@ -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{
Expand Down
2 changes: 1 addition & 1 deletion build/localnet/manifests/configs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did we update the port?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Port 50832 is the configured RPC port, so 42069 seems incorrect (encountered this while running the E2E test introduced in this PR)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should update all the RPC ports then to avoid a discrepancy:

Screenshot 2023-07-05 at 4 07 04 PM

If you want to do it in a small separate PR (while this one is being reviewed), I support that!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the intention to do it in this PR (given the comment)?

I still think a different small "micro PR" could be simpler & faster

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think a different small "micro PR" could be simpler & faster

Yes, I will open a separate, small PR for this shortly.

"staked_amount": "1000000000000",
"paused_height": -1,
"unstaking_height": -1,
Expand Down
8 changes: 8 additions & 0 deletions charts/pocket/pocket-servicer-overrides.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
config:
adshmh marked this conversation as resolved.
Show resolved Hide resolved
servicer:
enabled: true
address: "001022b138896c4c5466ac86b24a9bbe249905c2"
public_key: "56915c1270bc8d9280a633e0be51647f62388a851318381614877ef2ed84a495"
adshmh marked this conversation as resolved.
Show resolved Hide resolved
services:
"0001":
url: "https://eth-mainnet.gateway.pokt.network"
adshmh marked this conversation as resolved.
Show resolved Hide resolved
129 changes: 128 additions & 1 deletion e2e/tests/steps_init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ const (
// validatorB maps to suffix ID 002 and receives in the Send test.
validatorB = "002"
chainId = "0001"

servicerA = "001"
appA = "000"
serviceA = "0001"

relaychainEth = "RelayChainETH" // used to refer to Ethereum chain when retrieving relaychain settings
)

type rootSuite struct {
adshmh marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -45,6 +51,27 @@ 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

// 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
Olshansk marked this conversation as resolved.
Show resolved Hide resolved
relaychains map[string]*relaychainSettings

// servicer holds the key for the servicer that should received the relay
servicer string
Olshansk marked this conversation as resolved.
Show resolved Hide resolved
}

// relaychainSettings holds the settings for a specific relaychain
type relaychainSettings struct {
Account string
Height string
adshmh marked this conversation as resolved.
Show resolved Hide resolved
}

func (s *rootSuite) Before() {
Expand All @@ -59,12 +86,31 @@ func (s *rootSuite) Before() {
s.validator = new(validatorPod)
s.clientset = clientSet
s.validatorKeys = vkmap

// ADDPR: use pocketk8s to populate
adshmh marked this conversation as resolved.
Show resolved Hide resolved
s.servicerKeys = map[string]string{
// 000 servicer NOT in session
adshmh marked this conversation as resolved.
Show resolved Hide resolved
"000": "acbca21f295caefdfe480ceba85f3fed31a50915162f94867f9c23d8f474f4c6d1130c5eb920af8edd5b6bfa39d33aa787f421c8ba0786de4ca4e7703553bb97",
// 001 servicer in session
"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) {
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
Expand Down Expand Up @@ -158,6 +204,87 @@ 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)
Olshansk marked this conversation as resolved.
Show resolved Hide resolved
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() {
adshmh marked this conversation as resolved.
Show resolved Hide resolved
// ADDPR: Add a servicer staked for the Ethereum RelayChain
adshmh marked this conversation as resolved.
Show resolved Hide resolved
// 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(s.servicer)
appPrivateKey := s.getAppPrivateKey(appA)

s.sendTrustlessRelay(checkBalanceRelay, servicerPrivateKey.Address().String(), appPrivateKey.Address().String())
}

func (s *rootSuite) TheRelayResponseContains(arg1 string) {
adshmh marked this conversation as resolved.
Show resolved Hide resolved
require.Contains(s, s.validator.result.Stdout, arg1)
}

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?
adshmh marked this conversation as resolved.
Show resolved Hide resolved
res, err := s.validator.RunCommand(args...)

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")
adshmh marked this conversation as resolved.
Show resolved Hide resolved

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")
adshmh marked this conversation as resolved.
Show resolved Hide resolved

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) {
Expand Down
22 changes: 22 additions & 0 deletions e2e/tests/trustless_relays.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Feature: Trustless Relays
Olshansk marked this conversation as resolved.
Show resolved Hide resolved

Scenario: User Wants Help Using The Servicer Command
adshmh marked this conversation as resolved.
Show resolved Hide resolved
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
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
adshmh marked this conversation as resolved.
Show resolved Hide resolved
# INCOMPLETE: GeoZone
When the application sends a relay to a servicer
adshmh marked this conversation as resolved.
Show resolved Hide resolved
# Balance: 1,160,126.46817237178258965 ETH = 0xf5aa94f49d4fd1f8dcd2
Then the relay response contains 0xf5aa94f49d4fd1f8dcd2
And the validator should have exited without error
adshmh marked this conversation as resolved.
Show resolved Hide resolved

# 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.
18 changes: 10 additions & 8 deletions rpc/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,12 @@ 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,
}

relayRequest := buildJsonRPCRelayPayload(&body)
Expand Down Expand Up @@ -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,
},
}
Expand All @@ -229,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
adshmh marked this conversation as resolved.
Show resolved Hide resolved
payload.JsonRpcPayload.Params = *body.Payload.Params
}

if body.Payload.Headers != nil {
Expand Down
13 changes: 9 additions & 4 deletions rpc/v1/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1699,16 +1699,18 @@ 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:
type: string
parameters:
type: string
format: byte
# ADDPR: DISCUSS: params can be anything: should we use AnyValue for it?
adshmh marked this conversation as resolved.
Show resolved Hide resolved
params:
type: array
items:
type: string
headers:
$ref: "#/components/schemas/Headers"
Pool:
Expand Down Expand Up @@ -1762,6 +1764,7 @@ components:
- geozone
- token
- signature
- application_address
properties:
block_height:
type: integer
Expand All @@ -1776,6 +1779,8 @@ components:
$ref: "#/components/schemas/AAT"
signature:
type: string
application_address:
type: string
Signature:
type: object
required:
Expand Down
5 changes: 3 additions & 2 deletions shared/core/types/proto/relay.proto
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,14 @@ 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.
// 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<string, string> headers = 5;
}

Expand Down
4 changes: 2 additions & 2 deletions shared/core/types/relay.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions shared/k8s/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Olshansk marked this conversation as resolved.
Show resolved Hide resolved

func getNamespace() (string, error) {
_, err := os.Stat(kubernetesServiceAccountNamespaceFile)
if err == nil {
Expand Down
Loading