Skip to content

Commit

Permalink
add unit tests for shard routing proxies
Browse files Browse the repository at this point in the history
  • Loading branch information
pirtleshell committed Mar 4, 2024
1 parent 3c53de9 commit 1afa8c8
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 6 deletions.
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ TEST_DATABASE_ENDPOINT_URL=localhost:5432
TEST_PROXY_BACKEND_HOST_URL_MAP=localhost:7777>http://kava-validator:8545,localhost:7778>http://kava-pruning:8545
TEST_PROXY_HEIGHT_BASED_ROUTING_ENABLED=true
TEST_PROXY_PRUNING_BACKEND_HOST_URL_MAP=localhost:7777>http://kava-pruning:8545,localhost:7778>http://kava-pruning:8545
TEST_PROXY_SHARD_BACKEND_HOST_URL_MAP=localhost:7777>10|http://kava-shard-10:8545|20|http://kava-shard-20:8545
# What level of logging to use for service objects constructed during
# unit tests
TEST_SERVICE_LOG_LEVEL=ERROR
Expand Down
22 changes: 17 additions & 5 deletions service/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"github.com/stretchr/testify/require"
)

func newConfig(t *testing.T, defaultHostMap string, pruningHostMap string) config.Config {
func newConfig(t *testing.T, defaultHostMap string, pruningHostMap string, shardHostMap string) config.Config {
parsed, err := config.ParseRawProxyBackendHostURLMap(defaultHostMap)
require.NoError(t, err)
result := config.Config{
Expand All @@ -27,27 +27,39 @@ func newConfig(t *testing.T, defaultHostMap string, pruningHostMap string) confi
result.ProxyPruningBackendHostURLMap, err = config.ParseRawProxyBackendHostURLMap(pruningHostMap)
require.NoError(t, err)
}
if shardHostMap != "" {
result.EnableShardedRouting = true
result.ProxyShardBackendHostURLMapRaw = shardHostMap
result.ProxyShardBackendHostURLMap, err = config.ParseRawShardRoutingBackendHostURLMap(shardHostMap)
require.NoError(t, err)
}
return result
}

func TestUnitTest_NewProxies(t *testing.T) {
t.Run("returns a HostProxies when sharding disabled", func(t *testing.T) {
config := newConfig(t, dummyConfig.ProxyBackendHostURLMapRaw, "")
config := newConfig(t, dummyConfig.ProxyBackendHostURLMapRaw, "", "")
proxies := service.NewProxies(config, dummyLogger)
require.IsType(t, service.HostProxies{}, proxies)
})

t.Run("returns a PruningOrDefaultProxies when sharding enabled", func(t *testing.T) {
config := newConfig(t, dummyConfig.ProxyBackendHostURLMapRaw, dummyConfig.ProxyPruningBackendHostURLMapRaw)
t.Run("returns a PruningOrDefaultProxies when height-based routing enabled", func(t *testing.T) {
config := newConfig(t, dummyConfig.ProxyBackendHostURLMapRaw, dummyConfig.ProxyPruningBackendHostURLMapRaw, "")
proxies := service.NewProxies(config, dummyLogger)
require.IsType(t, service.PruningOrDefaultProxies{}, proxies)
})

t.Run("returns a ShardProxies when sharding enabled", func(t *testing.T) {
config := newConfig(t, dummyConfig.ProxyBackendHostURLMapRaw, "", dummyConfig.ProxyShardBackendHostURLMapRaw)
proxies := service.NewProxies(config, dummyLogger)
require.IsType(t, service.ShardProxies{}, proxies)
})
}

func TestUnitTest_HostProxies(t *testing.T) {
config := newConfig(t,
"magic.kava.io>magicalbackend.kava.io,archive.kava.io>archivenode.kava.io,pruning.kava.io>pruningnode.kava.io",
"",
"", "",
)
proxies := service.NewProxies(config, dummyLogger)

Expand Down
8 changes: 7 additions & 1 deletion service/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var (
testDefaultContext = context.TODO()
proxyServiceDefaultURLMapRaw = os.Getenv("TEST_PROXY_BACKEND_HOST_URL_MAP")
proxyServicePruningURLMapRaw = os.Getenv("TEST_PROXY_PRUNING_BACKEND_HOST_URL_MAP")
proxyServiceShardURLMapRaw = os.Getenv("TEST_PROXY_SHARD_BACKEND_HOST_URL_MAP")
databaseName = os.Getenv("DATABASE_NAME")
databaseUsername = os.Getenv("DATABASE_USERNAME")
databasePassword = os.Getenv("DATABASE_PASSWORD")
Expand All @@ -23,7 +24,6 @@ var (
evmQueryServiceURL = os.Getenv("TEST_EVM_QUERY_SERVICE_URL")

dummyConfig = func() config.Config {

proxyBackendHostURLMapParsed, err := config.ParseRawProxyBackendHostURLMap(proxyServiceDefaultURLMapRaw)
if err != nil {
panic(err)
Expand All @@ -32,12 +32,18 @@ var (
if err != nil {
panic(err)
}
proxyShardBackendHostURLMapParsed, err := config.ParseRawShardRoutingBackendHostURLMap(proxyServiceShardURLMapRaw)
if err != nil {
panic(err)
}

conf := config.Config{
ProxyBackendHostURLMapRaw: proxyServiceDefaultURLMapRaw,
ProxyBackendHostURLMapParsed: proxyBackendHostURLMapParsed,
ProxyPruningBackendHostURLMapRaw: proxyServicePruningURLMapRaw,
ProxyPruningBackendHostURLMap: proxyPruningBackendHostURLMapParsed,
ProxyShardBackendHostURLMapRaw: proxyServiceShardURLMapRaw,
ProxyShardBackendHostURLMap: proxyShardBackendHostURLMapParsed,

DatabaseName: databaseName,
DatabaseUserName: databaseUsername,
Expand Down
185 changes: 185 additions & 0 deletions service/shard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ func TestUnitTest_PruningOrDefaultProxies(t *testing.T) {
config := newConfig(t,
fmt.Sprintf("archive.kava.io>%s,pruning.kava.io>%s", archiveBackend, pruningBackend),
fmt.Sprintf("archive.kava.io>%s", pruningBackend),
"",
)
proxies := service.NewProxies(config, dummyLogger)
require.IsType(t, service.PruningOrDefaultProxies{}, proxies)

testCases := []struct {
name string
Expand Down Expand Up @@ -160,3 +162,186 @@ func TestUnitTest_PruningOrDefaultProxies(t *testing.T) {
})
}
}

// shard proxies with a pruning underlying proxy expects the same as above
// except that requests for specific heights that fall within a shard route to that shard.
func TestUnitTest_ShardProxies(t *testing.T) {
archiveBackend := "archivenode.kava.io/"
pruningBackend := "pruningnode.kava.io/"
shard1Backend := "shard-1.kava.io/"
shard2Backend := "shard-2.kava.io/"
config := newConfig(t,
fmt.Sprintf("archive.kava.io>%s,pruning.kava.io>%s", archiveBackend, pruningBackend),
fmt.Sprintf("archive.kava.io>%s", pruningBackend),
fmt.Sprintf("archive.kava.io>10|%s|20|%s", shard1Backend, shard2Backend),
)
proxies := service.NewProxies(config, dummyLogger)
require.IsType(t, service.ShardProxies{}, proxies)

testCases := []struct {
name string
url string
req *decode.EVMRPCRequestEnvelope
expectFound bool
expectBackend string
expectRoute string
}{
// DEFAULT ROUTE CASES
{
name: "routes to default when not in pruning or shard map",
url: "//pruning.kava.io",
req: &decode.EVMRPCRequestEnvelope{},
expectFound: true,
expectBackend: service.ResponseBackendDefault,
expectRoute: pruningBackend,
},
{
name: "routes to default for specific height beyond latest shard",
url: "//archive.kava.io",
req: &decode.EVMRPCRequestEnvelope{
Method: "eth_getBlockByNumber",
Params: []interface{}{"0xbaddad", false},
},
expectFound: true,
expectBackend: service.ResponseBackendDefault,
expectRoute: archiveBackend,
},
{
name: "routes to default for methods that don't have block number",
url: "//archive.kava.io",
req: &decode.EVMRPCRequestEnvelope{
Method: "eth_getBlockByHash",
Params: []interface{}{"0xe9bd10bc1d62b4406dd1fb3dbf3adb54f640bdb9ebbe3dd6dfc6bcc059275e54", false},
},
expectFound: true,
expectBackend: service.ResponseBackendDefault,
expectRoute: archiveBackend,
},
{
name: "routes to default if it fails to decode req",
url: "//archive.kava.io",
req: nil,
expectFound: true,
expectBackend: service.ResponseBackendDefault,
expectRoute: archiveBackend,
},
{
name: "routes to default if it fails to parse block number",
url: "//archive.kava.io",
req: &decode.EVMRPCRequestEnvelope{
Method: "eth_getBlockByNumber",
Params: []interface{}{"not-a-block-tag", false},
},
expectFound: true,
expectBackend: service.ResponseBackendDefault,
expectRoute: archiveBackend,
},
{
// TODO: should it do this? if shards exist, route to first shard?
name: "routes to default for 'earliest' block",
url: "//archive.kava.io",
req: &decode.EVMRPCRequestEnvelope{
Method: "eth_getBlockByNumber",
Params: []interface{}{"earliest", false},
},
expectFound: true,
expectBackend: service.ResponseBackendDefault,
expectRoute: archiveBackend,
},

// PRUNING ROUTE CASES
{
name: "routes to pruning for 'latest' block",
url: "//archive.kava.io",
req: &decode.EVMRPCRequestEnvelope{
Method: "eth_getBlockByNumber",
Params: []interface{}{"latest", false},
},
expectFound: true,
expectBackend: service.ResponseBackendPruning,
expectRoute: pruningBackend,
},
{
name: "routes to pruning when block number empty",
url: "//archive.kava.io",
req: &decode.EVMRPCRequestEnvelope{
Method: "eth_getBlockByNumber",
Params: []interface{}{nil, false},
},
expectFound: true,
expectBackend: service.ResponseBackendPruning,
expectRoute: pruningBackend,
},
{
name: "routes to pruning for no-history methods",
url: "//archive.kava.io",
req: &decode.EVMRPCRequestEnvelope{
Method: "eth_chainId",
},
expectFound: true,
expectBackend: service.ResponseBackendPruning,
expectRoute: pruningBackend,
},
{
// this is just another example of the above, but worth pointing out!
name: "routes to pruning when sending txs",
url: "//archive.kava.io",
req: &decode.EVMRPCRequestEnvelope{
Method: "eth_sendTransaction",
Params: []interface{}{
map[string]string{
"from": "0xdeadbeef00000000000000000000000000000123",
"to": "0xbaddad0000000000000000000000000000000123",
"value": "0x1",
"gas": "0xeeee",
"gasPrice": "0x12345678900",
"nonce": "0x0",
},
},
},
expectFound: true,
expectBackend: service.ResponseBackendPruning,
expectRoute: pruningBackend,
},

// SHARD ROUTE CASES
{
name: "routes to shard 1 for specific height in shard 1",
url: "//archive.kava.io",
req: &decode.EVMRPCRequestEnvelope{
Method: "eth_getBlockByNumber",
Params: []interface{}{"0x5", false}, // block 5
},
expectFound: true,
expectBackend: service.ResponseBackendShard,
expectRoute: shard1Backend,
},
{
name: "routes to shard 2 for specific height in shard 2",
url: "//archive.kava.io",
req: &decode.EVMRPCRequestEnvelope{
Method: "eth_getBlockByNumber",
Params: []interface{}{"0xF", false}, // block 15
},
expectFound: true,
expectBackend: service.ResponseBackendShard,
expectRoute: shard2Backend,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
req := mockJsonRpcReqToUrl(tc.url, tc.req)
proxy, metadata, found := proxies.ProxyForRequest(req)
if !tc.expectFound {
require.False(t, found, "expected proxy not to be found")
return
}
require.True(t, found, "expected proxy to be found")
require.NotNil(t, proxy)
require.Equal(t, metadata.BackendName, tc.expectBackend)
require.Equal(t, metadata.BackendRoute.String(), tc.expectRoute)
requireProxyRoutesToUrl(t, proxy, req, tc.expectRoute)
})
}
}

0 comments on commit 1afa8c8

Please sign in to comment.