Skip to content

Commit

Permalink
go-adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
jrh3k5 committed Feb 10, 2024
1 parent eef2fa9 commit e83b59a
Show file tree
Hide file tree
Showing 6 changed files with 296 additions and 12 deletions.
44 changes: 44 additions & 0 deletions go/cmd/cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package main

import (
"context"
"flag"
"fmt"
"io"
"os"

"github.com/paradigmxyz/mesc/go/pkg/mesc"
"github.com/paradigmxyz/mesc/go/pkg/mesc/endpoint/io/serialization"
)

func main() {
ctx := context.Background()

var queryType string
flag.StringVar(&queryType, "query-type", "", "the type of query to execute")

flag.Parse()

switch queryType {
case "get_default_endpoint":
endpoint, err := mesc.GetDefaultEndpoint(ctx)
if err != nil {
printFailure(fmt.Errorf("failed to get default endpoint: %w", err))
return
}

jsonModel, err := serialization.SerializeEndpointMetadataJSON(endpoint)
if err != nil {
printFailure(fmt.Errorf("failed to serialize endpoint metadata to JSON: %w", err))
return
}

_, _ = io.Copy(os.Stdout, jsonModel)

return
}
}

func printFailure(e error) {
fmt.Printf("FAIL: %v\n", e)
}
110 changes: 100 additions & 10 deletions go/pkg/mesc/endpoint/io/serialization/json.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package serialization

import (
"bytes"
"encoding/json"
"fmt"
"io"
Expand Down Expand Up @@ -34,6 +35,54 @@ func DeserializeEndpointMetadataJSON(reader io.Reader) (map[string]model.Endpoin
return endpoints, nil
}

// SerializeJSON serializes the given RPC configuration to a JSON representation conforming to the MESC specification.
func SerializeJSON(rpcConfig *model.RPCConfig) (io.Reader, error) {
jsonProfiles := make(map[string]*jsonProfile)
for profileKey, profile := range jsonProfiles {
jsonProfiles[profileKey] = &jsonProfile{
Name: profile.Name,
DefaultEndpoint: profile.DefaultEndpoint,
NetworkDefaults: profile.NetworkDefaults,
ProfileMetadata: profile.ProfileMetadata,
UseMESC: profile.UseMESC,
}
}

jsonEndpoints := make(map[string]*jsonEndpoint)
for endpointKey, endpoint := range rpcConfig.Endpoints {
jsonEndpoints[endpointKey] = modelEndpointToJSON(&endpoint)
}

jsonRPCConfig := &jsonRPCConfig{
MESCVersion: rpcConfig.MESCVersion,
DefaultEndpoint: rpcConfig.DefaultEndpoint,
NetworkDefaults: fromChainIDMap(rpcConfig.NetworkDefaults),
NetworkNames: fromMappedChainID(rpcConfig.NetworkNames),
Endpoints: jsonEndpoints,
Profiles: jsonProfiles,
GlobalMetadata: rpcConfig.GlobalMetadata,
}

jsonBytes, err := json.Marshal(jsonRPCConfig)
if err != nil {
return nil, fmt.Errorf("failed to marshal RPC config to bytes: %w", err)
}

return bytes.NewBuffer(jsonBytes), nil
}

// SerializeEndpointMetadataJSON serializes the given endpoint model to a JSON form compliant with the MESC specification.
func SerializeEndpointMetadataJSON(endpoint *model.EndpointMetadata) (io.Reader, error) {
jsonEndpoint := modelEndpointToJSON(endpoint)

jsonBytes, err := json.Marshal(jsonEndpoint)
if err != nil {
return nil, fmt.Errorf("failed to marshal endpoint metadata to bytes: %w", err)
}

return bytes.NewBuffer(jsonBytes), nil
}

func toOptionalChainID(v *string) *model.ChainID {
if v == nil {
return nil
Expand All @@ -43,6 +92,32 @@ func toOptionalChainID(v *string) *model.ChainID {
return &asModel
}

func fromChainIDMap[V any](source map[model.ChainID]V) map[string]V {
if source == nil {
return nil
}

copy := make(map[string]V, len(source))
for chainID, value := range source {
copy[string(chainID)] = value
}

return copy
}

func fromMappedChainID[K comparable](source map[K]model.ChainID) map[K]string {
if source == nil {
return nil
}

copy := make(map[K]string, len(source))
for key, chainID := range source {
copy[key] = string(chainID)
}

return copy
}

func toChainIDMap[V any](source map[string]V) map[model.ChainID]V {
if source == nil {
return nil
Expand Down Expand Up @@ -70,13 +145,13 @@ func toMappedChainID[K comparable](source map[K]string) map[K]model.ChainID {
}

type jsonRPCConfig struct {
MESCVersion string `json:"mesc_version"`
DefaultEndpoint *string `json:"default_endpoint"`
NetworkDefaults map[string]string `json:"network_defaults"`
NetworkNames map[string]string `json:"network_names"`
Endpoints map[string]*jsonEndpoint
Profiles map[string]*jsonProfile
GlobalMetadata map[string]any `json:"global_metadata"`
MESCVersion string `json:"mesc_version"`
DefaultEndpoint *string `json:"default_endpoint"`
NetworkDefaults map[string]string `json:"network_defaults"`
NetworkNames map[string]string `json:"network_names"`
Endpoints map[string]*jsonEndpoint `json:"endpoints"`
Profiles map[string]*jsonProfile `json:"profiles"`
GlobalMetadata map[string]any `json:"global_metadata"`
}

func (j *jsonRPCConfig) toModel() *model.RPCConfig {
Expand Down Expand Up @@ -110,9 +185,24 @@ func (j *jsonRPCConfig) toModel() *model.RPCConfig {
return rpcConfig
}

func modelEndpointToJSON(endpoint *model.EndpointMetadata) *jsonEndpoint {
var chainIDPtr *string
if endpoint.ChainID != nil {
v := string(*endpoint.ChainID)
chainIDPtr = &v
}

return &jsonEndpoint{
Name: endpoint.Name,
URL: endpoint.URL,
ChainID: chainIDPtr,
EndpointMetadata: endpoint.EndpointMetadata,
}
}

type jsonEndpoint struct {
Name string
URL string
Name string `json:"name"`
URL string `json:"url"`
ChainID *string `json:"chain_id"`
EndpointMetadata map[string]any `json:"endpoint_metadata"`
}
Expand All @@ -127,7 +217,7 @@ func (j *jsonEndpoint) toModel() model.EndpointMetadata {
}

type jsonProfile struct {
Name string
Name string `json:"name"`
DefaultEndpoint *string `json:"default_endpoint"`
NetworkDefaults map[string]string `json:"network_defaults"`
ProfileMetadata map[string]any `json:"profile_metadata"`
Expand Down
20 changes: 20 additions & 0 deletions go/pkg/mesc/endpoint/resolution/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,26 @@ type EndpointResolutionConfig struct {
rpcConfig *model.RPCConfig
}

// GetProfile gets the profile, if any, that was supplied.
// It returns true for the bool if there is a profile supplied; false if not.
func (e *EndpointResolutionConfig) GetProfile() (string, bool) {
if e.profile == nil {
return "", false
}

return *e.profile, true
}

// GetRPCConfig gets the RPC configuration, if any, that was supplied.
// It returns false for the bool if there is an RPC configuration supplied; false if not.
func (e *EndpointResolutionConfig) GetRPCConfig() (model.RPCConfig, bool) {
if e.rpcConfig == nil {
return model.RPCConfig{}, false
}

return *e.rpcConfig, true
}

// EndpointResolutionOption describes a way of configuring the resolution of an endpoint
type EndpointResolutionOption func(*EndpointResolutionConfig)

Expand Down
49 changes: 47 additions & 2 deletions go/pkg/mesc/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package mesc
import (
"context"
"errors"
"fmt"

criteria "github.com/paradigmxyz/mesc/go/pkg/mesc/endpoint/criteria"
resolution "github.com/paradigmxyz/mesc/go/pkg/mesc/endpoint/resolution"
Expand All @@ -20,8 +21,43 @@ func FindEndpoints(ctx context.Context, findCriteria []criteria.FindEndpointsCri
// GetDefaultEndpoint resolves the endpoint metadata, if available, for the default endpoint.
// This will return nil if no endpoint metadata can be resolved.
func GetDefaultEndpoint(ctx context.Context, options ...resolution.EndpointResolutionOption) (*model.EndpointMetadata, error) {
// TODO: implement
return nil, errors.New("not yet implemented")
resolutionConfig := resolveEndpointResolutionConfig(options...)

rpcConfig, hasConfig := resolutionConfig.GetRPCConfig()
if !hasConfig {
resolvedRPCConfig, err := ResolveRPCConfig(ctx)
if err != nil {
return nil, fmt.Errorf("failed to resolve RPC configuration: %w", err)
}

rpcConfig = *resolvedRPCConfig
}

var defaultEndpointName string

profileName, hasProfileName := resolutionConfig.GetProfile()
if hasProfileName {
profile, hasProfile := rpcConfig.Profiles[profileName]
if hasProfile && profile.DefaultEndpoint != nil {
defaultEndpointName = *profile.DefaultEndpoint
}
}

if defaultEndpointName == "" && rpcConfig.DefaultEndpoint != nil {
defaultEndpointName = *rpcConfig.DefaultEndpoint
}

if defaultEndpointName == "" {
// unable to resolve default endpoint name to use, so nothing can be found
return nil, nil
}

endpoint, hasEndpoint := rpcConfig.Endpoints[defaultEndpointName]
if !hasEndpoint {
return nil, nil
}

return &endpoint, nil
}

// GetEndpointByNetwork resolves the endpoint metadata, if available, for the given chain ID.
Expand Down Expand Up @@ -52,3 +88,12 @@ func IsMESCEnabled(ctx context.Context) (bool, error) {
// TODO: implement
return false, errors.New("not yet implemented")
}

func resolveEndpointResolutionConfig(options ...resolution.EndpointResolutionOption) *resolution.EndpointResolutionConfig {
cfg := &resolution.EndpointResolutionConfig{}
for _, option := range options {
option(cfg)
}

return cfg
}
81 changes: 81 additions & 0 deletions tests/adapters/golang
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env python3

from __future__ import annotations

import argparse
import json
import os
import subprocess
import typing
from typing import cast

if typing.TYPE_CHECKING:
from mesc.types import MescQuery


def run_query(query: MescQuery) -> str:
if query["query_type"] == "default_endpoint":
cmd: list[str] = ["go", "run", "../go/cmd/cli.go", "--query-type", "get_default_endpoint"]
# elif query["query_type"] == "endpoint_by_name":
# name = cast(str, query["fields"]["name"]) # type: ignore
# cmd = ["mesc", "endpoint", "--json", "--name", name]
# elif query["query_type"] == "endpoint_by_network":
# chain_id = query["fields"]["chain_id"]
# if isinstance(chain_id, int):
# chain_id = str(chain_id)
# cmd = ["mesc", "endpoint", "--json", "--network", chain_id]
# elif query["query_type"] == "user_input":
# user_input = query["fields"]["user_input"] # type: ignore
# cmd = ["mesc", "endpoint", user_input, "--json"]
# elif query["query_type"] == "multi_endpoint":
# cmd = ["mesc", "ls", "--json"]
# if query["fields"].get('name_contains') is not None:
# cmd.append('--name')
# cmd.append(query['fields']['name_contains'])
# if query["fields"].get('url_contains') is not None:
# cmd.append('--url')
# cmd.append(query['fields']['url_contains'])
# if query["fields"].get('chain_id') is not None:
# cmd.append('--network')
# chain_id = query["fields"]["chain_id"]
# if isinstance(chain_id, int):
# chain_id = str(chain_id)
# cmd.append(chain_id)
# elif query['query_type'] == 'global_metadata':
# cmd = ["mesc", "metadata"]
else:
raise Exception("invalid query query_type: " + str(query["query_type"]))

if query["fields"].get("profile") is not None:
cmd.append("--profile")
cmd.append(query["fields"]["profile"]) # type: ignore

raw_output = subprocess.check_output(cmd, env=dict(os.environ), stderr=subprocess.DEVNULL)
output = raw_output.decode("utf-8").strip()
if output == "":
output = "null"

return output


if __name__ == "__main__":
# load test
parser = argparse.ArgumentParser()
parser.add_argument("test")
args = parser.parse_args()
test = json.loads(args.test)

# run test
try:
raw_output = run_query(test)
try:
result = json.loads(raw_output)
print(json.dumps(result, indent=4, sort_keys=True))
except Exception as e:
print("FAIL")
print(e)
print('RAW_OUTPUT:', raw_output)
except Exception as e:
print("FAIL")
print(e)

4 changes: 4 additions & 0 deletions tests/go.work
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
go 1.21.3

// Needed so that 'go run' works from this directory
use ../go

0 comments on commit e83b59a

Please sign in to comment.