Node/CCQ: Allow anything in testnet (#3966)
This commit is contained in:
parent
215c60aee9
commit
624df192ad
|
@ -60,6 +60,7 @@ spec:
|
|||
- --logLevel=info
|
||||
- --shutdownDelay1
|
||||
- "0"
|
||||
- --allowAnything
|
||||
ports:
|
||||
- containerPort: 6069
|
||||
name: rest
|
||||
|
|
|
@ -62,6 +62,7 @@ Optional Parameters
|
|||
|
||||
- The `gossipAdvertiseAddress` argument allows you to specify an external IP to advertize on P2P (use if behind a NAT or running in k8s).
|
||||
- The `monitorPeers` flag will cause the proxy server to periodically check its connectivity to the P2P bootstrap peers, and attempt to reconnect if necessary.
|
||||
- The `allowAnything` flag enables defining users with the `allowAnything` flag set to true. This is only allowed in testnet and devnet.
|
||||
|
||||
#### Creating the Signing Key File
|
||||
|
||||
|
@ -111,6 +112,7 @@ The simplest file would look something like this
|
|||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -153,6 +155,25 @@ The proxy server monitors the permissions file for changes. Whenever a change is
|
|||
it passes validation, switches to the new version. Care should be taken when editing the file while the proxy server is running, because
|
||||
as soon as you save the file, the changes will be picked up (whether they are logically complete or not).
|
||||
|
||||
#### The `allowAnything` flag
|
||||
|
||||
If this flag is specified for a user, then that user may make any call on any supported chain, without restriction.
|
||||
This flag is only allowed if the `allowAnything` command line argument is specified.
|
||||
If this flag is specified, then `allowedCalls` must not be specified.
|
||||
|
||||
```json
|
||||
{
|
||||
"permissions": [
|
||||
{
|
||||
"userName": "Monitor",
|
||||
"apiKey": "insert_generated_api_key_here",
|
||||
"allowUnsigned": true,
|
||||
"allowedAnything": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Telemetry
|
||||
|
||||
The proxy server provides two types of telemetry data, logs and metrics.
|
||||
|
|
|
@ -178,6 +178,12 @@
|
|||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"userName": "Unlimited User",
|
||||
"apiKey": "my_secret_key_3",
|
||||
"allowUnsigned": true,
|
||||
"allowAnything": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
)
|
||||
|
||||
func TestParseConfigFileDoesntExist(t *testing.T) {
|
||||
_, err := parseConfigFile("missingFile.json")
|
||||
_, err := parseConfigFile("missingFile.json", false)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, `failed to open permissions file "missingFile.json": open missingFile.json: no such file or directory`, err.Error())
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ func TestParseConfigBadJson(t *testing.T) {
|
|||
]
|
||||
}`
|
||||
|
||||
_, err := parseConfig([]byte(str))
|
||||
_, err := parseConfig([]byte(str), false)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, `failed to unmarshal json: unexpected end of JSON input`, err.Error())
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ func TestParseConfigDuplicateUser(t *testing.T) {
|
|||
]
|
||||
}`
|
||||
|
||||
_, err := parseConfig([]byte(str))
|
||||
_, err := parseConfig([]byte(str), false)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, `UserName "Test User" is a duplicate`, err.Error())
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ func TestParseConfigDuplicateApiKey(t *testing.T) {
|
|||
]
|
||||
}`
|
||||
|
||||
_, err := parseConfig([]byte(str))
|
||||
_, err := parseConfig([]byte(str), false)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, `API key "my_secret_key" is a duplicate`, err.Error())
|
||||
}
|
||||
|
@ -155,7 +155,7 @@ func TestParseConfigUnsupportedCallType(t *testing.T) {
|
|||
]
|
||||
}`
|
||||
|
||||
_, err := parseConfig([]byte(str))
|
||||
_, err := parseConfig([]byte(str), false)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, `unsupported call type for user "Test User", must be "ethCall", "ethCallByTimestamp", "ethCallWithFinality", "solAccount" or "solPDA"`, err.Error())
|
||||
}
|
||||
|
@ -181,7 +181,7 @@ func TestParseConfigInvalidContractAddress(t *testing.T) {
|
|||
]
|
||||
}`
|
||||
|
||||
_, err := parseConfig([]byte(str))
|
||||
_, err := parseConfig([]byte(str), false)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, `invalid contract address "HelloWorld" for user "Test User"`, err.Error())
|
||||
}
|
||||
|
@ -207,7 +207,7 @@ func TestParseConfigInvalidEthCall(t *testing.T) {
|
|||
]
|
||||
}`
|
||||
|
||||
_, err := parseConfig([]byte(str))
|
||||
_, err := parseConfig([]byte(str), false)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, `invalid eth call "HelloWorld" for user "Test User"`, err.Error())
|
||||
}
|
||||
|
@ -233,7 +233,7 @@ func TestParseConfigInvalidEthCallLength(t *testing.T) {
|
|||
]
|
||||
}`
|
||||
|
||||
_, err := parseConfig([]byte(str))
|
||||
_, err := parseConfig([]byte(str), false)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, `eth call "0x06fd" for user "Test User" has an invalid length, must be 4 bytes`, err.Error())
|
||||
}
|
||||
|
@ -267,7 +267,7 @@ func TestParseConfigDuplicateAllowedCallForUser(t *testing.T) {
|
|||
]
|
||||
}`
|
||||
|
||||
_, err := parseConfig([]byte(str))
|
||||
_, err := parseConfig([]byte(str), false)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, `"ethCall:2:000000000000000000000000b4fbf271143f4fbf7b91a5ded31805e42b2208d6:06fdde03" is a duplicate allowed call for user "Test User"`, err.Error())
|
||||
}
|
||||
|
@ -323,7 +323,7 @@ func TestParseConfigSuccess(t *testing.T) {
|
|||
]
|
||||
}`
|
||||
|
||||
perms, err := parseConfig([]byte(str))
|
||||
perms, err := parseConfig([]byte(str), false)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, len(perms))
|
||||
|
||||
|
@ -347,3 +347,117 @@ func TestParseConfigSuccess(t *testing.T) {
|
|||
_, exists = perm.allowedCalls["solPDA:1:Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"]
|
||||
assert.True(t, exists)
|
||||
}
|
||||
|
||||
func TestParseConfigAllowAnythingWhenNotEnabled(t *testing.T) {
|
||||
str := `
|
||||
{
|
||||
"permissions": [
|
||||
{
|
||||
"userName": "Test User",
|
||||
"apiKey": "my_secret_key",
|
||||
"allowedCalls": [
|
||||
{
|
||||
"ethCall": {
|
||||
"note:": "Name of WETH on Goerli",
|
||||
"chain": 2,
|
||||
"contractAddress": "B4FBF271143F4FBf7B91A5ded31805e42b2208d6",
|
||||
"call": "0x06fdde03"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"userName": "Test User2",
|
||||
"apiKey": "my_secret_key_2",
|
||||
"allowUnsigned": true,
|
||||
"allowAnything": true
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
_, err := parseConfig([]byte(str), false)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, `UserName "Test User2" has "allowAnything" specified when the feature is not enabled`, err.Error())
|
||||
}
|
||||
|
||||
func TestParseConfigAllowAnythingWithAllowedCallsIsInvalid(t *testing.T) {
|
||||
str := `
|
||||
{
|
||||
"permissions": [
|
||||
{
|
||||
"userName": "Test User",
|
||||
"apiKey": "my_secret_key",
|
||||
"allowedCalls": [
|
||||
{
|
||||
"ethCall": {
|
||||
"note:": "Name of WETH on Goerli",
|
||||
"chain": 2,
|
||||
"contractAddress": "B4FBF271143F4FBf7B91A5ded31805e42b2208d6",
|
||||
"call": "0x06fdde03"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"userName": "Test User2",
|
||||
"apiKey": "my_secret_key_2",
|
||||
"allowUnsigned": true,
|
||||
"allowAnything": true,
|
||||
"allowedCalls": [
|
||||
{
|
||||
"ethCall": {
|
||||
"note:": "Name of WETH on Goerli",
|
||||
"chain": 2,
|
||||
"contractAddress": "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6",
|
||||
"call": "0x06fdde03"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
_, err := parseConfig([]byte(str), true)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, `UserName "Test User2" has "allowedCalls" specified with "allowAnything", which is not allowed`, err.Error())
|
||||
}
|
||||
|
||||
func TestParseConfigAllowAnythingSuccess(t *testing.T) {
|
||||
str := `
|
||||
{
|
||||
"permissions": [
|
||||
{
|
||||
"userName": "Test User",
|
||||
"apiKey": "my_secret_key",
|
||||
"allowedCalls": [
|
||||
{
|
||||
"ethCall": {
|
||||
"note:": "Name of WETH on Goerli",
|
||||
"chain": 2,
|
||||
"contractAddress": "B4FBF271143F4FBf7B91A5ded31805e42b2208d6",
|
||||
"call": "0x06fdde03"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"userName": "Test User2",
|
||||
"apiKey": "my_secret_key_2",
|
||||
"allowUnsigned": true,
|
||||
"allowAnything": true
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
perms, err := parseConfig([]byte(str), true)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 2, len(perms))
|
||||
|
||||
perm, ok := perms["my_secret_key"]
|
||||
require.True(t, ok)
|
||||
assert.False(t, perm.allowAnything)
|
||||
|
||||
perm, ok = perms["my_secret_key_2"]
|
||||
require.True(t, ok)
|
||||
assert.True(t, perm.allowAnything)
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ type (
|
|||
UserName string `json:"userName"`
|
||||
ApiKey string `json:"apiKey"`
|
||||
AllowUnsigned bool `json:"allowUnsigned"`
|
||||
AllowAnything bool `json:"allowAnything"`
|
||||
LogResponses bool `json:"logResponses"`
|
||||
AllowedCalls []AllowedCall `json:"allowedCalls"`
|
||||
}
|
||||
|
@ -75,6 +76,7 @@ type (
|
|||
userName string
|
||||
apiKey string
|
||||
allowUnsigned bool
|
||||
allowAnything bool
|
||||
logResponses bool
|
||||
allowedCalls allowedCallsForUser // Key is something like "ethCall:2:000000000000000000000000b4fbf271143f4fbf7b91a5ded31805e42b2208d6:06fdde03"
|
||||
}
|
||||
|
@ -82,23 +84,25 @@ type (
|
|||
allowedCallsForUser map[string]struct{}
|
||||
|
||||
Permissions struct {
|
||||
lock sync.Mutex
|
||||
permMap PermissionsMap
|
||||
fileName string
|
||||
watcher *fswatch.Watcher
|
||||
lock sync.Mutex
|
||||
permMap PermissionsMap
|
||||
fileName string
|
||||
allowAnything bool
|
||||
watcher *fswatch.Watcher
|
||||
}
|
||||
)
|
||||
|
||||
// NewPermissions creates a Permissions object which contains the per-user permissions.
|
||||
func NewPermissions(fileName string) (*Permissions, error) {
|
||||
permMap, err := parseConfigFile(fileName)
|
||||
func NewPermissions(fileName string, allowAnything bool) (*Permissions, error) {
|
||||
permMap, err := parseConfigFile(fileName, allowAnything)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Permissions{
|
||||
permMap: permMap,
|
||||
fileName: fileName,
|
||||
permMap: permMap,
|
||||
fileName: fileName,
|
||||
allowAnything: allowAnything,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -127,7 +131,7 @@ func (perms *Permissions) StartWatcher(ctx context.Context, logger *zap.Logger,
|
|||
|
||||
// Reload reloads the permissions file.
|
||||
func (perms *Permissions) Reload(logger *zap.Logger) {
|
||||
permMap, err := parseConfigFile(perms.fileName)
|
||||
permMap, err := parseConfigFile(perms.fileName, perms.allowAnything)
|
||||
if err != nil {
|
||||
logger.Error("failed to reload the permissions file, sticking with the old one", zap.String("fileName", perms.fileName), zap.Error(err))
|
||||
permissionFileReloadsFailure.Inc()
|
||||
|
@ -159,7 +163,7 @@ func (perms *Permissions) GetUserEntry(apiKey string) (*permissionEntry, bool) {
|
|||
const ETH_CALL_SIG_LENGTH = 4
|
||||
|
||||
// parseConfigFile parses the permissions config file into a map keyed by API key.
|
||||
func parseConfigFile(fileName string) (PermissionsMap, error) {
|
||||
func parseConfigFile(fileName string, allowAnything bool) (PermissionsMap, error) {
|
||||
jsonFile, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to open permissions file "%s": %w`, fileName, err)
|
||||
|
@ -171,7 +175,7 @@ func parseConfigFile(fileName string) (PermissionsMap, error) {
|
|||
return nil, fmt.Errorf(`failed to read permissions file "%s": %w`, fileName, err)
|
||||
}
|
||||
|
||||
retVal, err := parseConfig(byteValue)
|
||||
retVal, err := parseConfig(byteValue, allowAnything)
|
||||
if err != nil {
|
||||
return retVal, fmt.Errorf(`failed to parse permissions file "%s": %w`, fileName, err)
|
||||
}
|
||||
|
@ -180,7 +184,7 @@ func parseConfigFile(fileName string) (PermissionsMap, error) {
|
|||
}
|
||||
|
||||
// parseConfig parses the permissions config from a buffer into a map keyed by API key.
|
||||
func parseConfig(byteValue []byte) (PermissionsMap, error) {
|
||||
func parseConfig(byteValue []byte, allowAnything bool) (PermissionsMap, error) {
|
||||
var config Config
|
||||
if err := json.Unmarshal(byteValue, &config); err != nil {
|
||||
return nil, fmt.Errorf(`failed to unmarshal json: %w`, err)
|
||||
|
@ -200,6 +204,15 @@ func parseConfig(byteValue []byte) (PermissionsMap, error) {
|
|||
return nil, fmt.Errorf(`API key "%s" is a duplicate`, apiKey)
|
||||
}
|
||||
|
||||
if user.AllowAnything {
|
||||
if !allowAnything {
|
||||
return nil, fmt.Errorf(`UserName "%s" has "allowAnything" specified when the feature is not enabled`, user.UserName)
|
||||
}
|
||||
if len(user.AllowedCalls) != 0 {
|
||||
return nil, fmt.Errorf(`UserName "%s" has "allowedCalls" specified with "allowAnything", which is not allowed`, user.UserName)
|
||||
}
|
||||
}
|
||||
|
||||
// Build the list of allowed calls for this API key.
|
||||
allowedCalls := make(allowedCallsForUser)
|
||||
for _, ac := range user.AllowedCalls {
|
||||
|
@ -296,6 +309,7 @@ func parseConfig(byteValue []byte) (PermissionsMap, error) {
|
|||
userName: user.UserName,
|
||||
apiKey: apiKey,
|
||||
allowUnsigned: user.AllowUnsigned,
|
||||
allowAnything: user.AllowAnything,
|
||||
logResponses: user.LogResponses,
|
||||
allowedCalls: allowedCalls,
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ var (
|
|||
shutdownDelay2 *uint
|
||||
monitorPeers *bool
|
||||
gossipAdvertiseAddress *string
|
||||
allowAnything *bool
|
||||
)
|
||||
|
||||
const DEV_NETWORK_ID = "/wormhole/dev"
|
||||
|
@ -69,6 +70,7 @@ func init() {
|
|||
promRemoteURL = QueryServerCmd.Flags().String("promRemoteURL", "", "Prometheus remote write URL (Grafana)")
|
||||
monitorPeers = QueryServerCmd.Flags().Bool("monitorPeers", false, "Should monitor bootstrap peers and attempt to reconnect")
|
||||
gossipAdvertiseAddress = QueryServerCmd.Flags().String("gossipAdvertiseAddress", "", "External IP to advertize on P2P (use if behind a NAT or running in k8s)")
|
||||
allowAnything = QueryServerCmd.Flags().Bool("allowAnything", false, `Should allow API keys with the "allowAnything" flag (only allowed in testnet and devnet)`)
|
||||
|
||||
// The default health check monitoring is every five seconds, with a five second timeout, and you have to miss two, for 20 seconds total.
|
||||
shutdownDelay1 = QueryServerCmd.Flags().Uint("shutdownDelay1", 25, "Seconds to delay after disabling health check on shutdown")
|
||||
|
@ -162,7 +164,14 @@ func runQueryServer(cmd *cobra.Command, args []string) {
|
|||
logger.Fatal("Please specify --ethContract")
|
||||
}
|
||||
|
||||
permissions, err := NewPermissions(*permFile)
|
||||
if *allowAnything {
|
||||
if env != common.TestNet && env != common.UnsafeDevNet {
|
||||
logger.Fatal(`The "--allowAnything" flag is only supported in testnet and devnet`)
|
||||
}
|
||||
logger.Info("will allow anything for users for which it is enabled")
|
||||
}
|
||||
|
||||
permissions, err := NewPermissions(*permFile, *allowAnything)
|
||||
if err != nil {
|
||||
logger.Fatal("Failed to load permissions file", zap.String("permFile", *permFile), zap.Error(err))
|
||||
}
|
||||
|
|
|
@ -144,12 +144,14 @@ func validateCallData(logger *zap.Logger, permsForUser *permissionEntry, callTag
|
|||
invalidQueryRequestReceived.WithLabelValues("bad_call_data").Inc()
|
||||
return http.StatusBadRequest, fmt.Errorf("eth call data must be at least four bytes")
|
||||
}
|
||||
call := hex.EncodeToString(cd.Data[0:ETH_CALL_SIG_LENGTH])
|
||||
callKey := fmt.Sprintf("%s:%d:%s:%s", callTag, chainId, contractAddress, call)
|
||||
if _, exists := permsForUser.allowedCalls[callKey]; !exists {
|
||||
logger.Debug("requested call not authorized", zap.String("userName", permsForUser.userName), zap.String("callKey", callKey))
|
||||
invalidQueryRequestReceived.WithLabelValues("call_not_authorized").Inc()
|
||||
return http.StatusBadRequest, fmt.Errorf(`call "%s" not authorized`, callKey)
|
||||
if !permsForUser.allowAnything {
|
||||
call := hex.EncodeToString(cd.Data[0:ETH_CALL_SIG_LENGTH])
|
||||
callKey := fmt.Sprintf("%s:%d:%s:%s", callTag, chainId, contractAddress, call)
|
||||
if _, exists := permsForUser.allowedCalls[callKey]; !exists {
|
||||
logger.Debug("requested call not authorized", zap.String("userName", permsForUser.userName), zap.String("callKey", callKey))
|
||||
invalidQueryRequestReceived.WithLabelValues("call_not_authorized").Inc()
|
||||
return http.StatusBadRequest, fmt.Errorf(`call "%s" not authorized`, callKey)
|
||||
}
|
||||
}
|
||||
|
||||
totalRequestedCallsByChain.WithLabelValues(chainId.String()).Inc()
|
||||
|
@ -160,15 +162,17 @@ func validateCallData(logger *zap.Logger, permsForUser *permissionEntry, callTag
|
|||
|
||||
// validateSolanaAccountQuery performs verification on a Solana sol_account query.
|
||||
func validateSolanaAccountQuery(logger *zap.Logger, permsForUser *permissionEntry, callTag string, chainId vaa.ChainID, q *query.SolanaAccountQueryRequest) (int, error) {
|
||||
for _, acct := range q.Accounts {
|
||||
callKey := fmt.Sprintf("%s:%d:%s", callTag, chainId, solana.PublicKey(acct).String())
|
||||
if _, exists := permsForUser.allowedCalls[callKey]; !exists {
|
||||
logger.Debug("requested call not authorized", zap.String("userName", permsForUser.userName), zap.String("callKey", callKey))
|
||||
invalidQueryRequestReceived.WithLabelValues("call_not_authorized").Inc()
|
||||
return http.StatusForbidden, fmt.Errorf(`call "%s" not authorized`, callKey)
|
||||
}
|
||||
if !permsForUser.allowAnything {
|
||||
for _, acct := range q.Accounts {
|
||||
callKey := fmt.Sprintf("%s:%d:%s", callTag, chainId, solana.PublicKey(acct).String())
|
||||
if _, exists := permsForUser.allowedCalls[callKey]; !exists {
|
||||
logger.Debug("requested call not authorized", zap.String("userName", permsForUser.userName), zap.String("callKey", callKey))
|
||||
invalidQueryRequestReceived.WithLabelValues("call_not_authorized").Inc()
|
||||
return http.StatusForbidden, fmt.Errorf(`call "%s" not authorized`, callKey)
|
||||
}
|
||||
|
||||
totalRequestedCallsByChain.WithLabelValues(chainId.String()).Inc()
|
||||
totalRequestedCallsByChain.WithLabelValues(chainId.String()).Inc()
|
||||
}
|
||||
}
|
||||
|
||||
return http.StatusOK, nil
|
||||
|
@ -176,15 +180,17 @@ func validateSolanaAccountQuery(logger *zap.Logger, permsForUser *permissionEntr
|
|||
|
||||
// validateSolanaPdaQuery performs verification on a Solana sol_account query.
|
||||
func validateSolanaPdaQuery(logger *zap.Logger, permsForUser *permissionEntry, callTag string, chainId vaa.ChainID, q *query.SolanaPdaQueryRequest) (int, error) {
|
||||
for _, acct := range q.PDAs {
|
||||
callKey := fmt.Sprintf("%s:%d:%s", callTag, chainId, solana.PublicKey(acct.ProgramAddress).String())
|
||||
if _, exists := permsForUser.allowedCalls[callKey]; !exists {
|
||||
logger.Debug("requested call not authorized", zap.String("userName", permsForUser.userName), zap.String("callKey", callKey))
|
||||
invalidQueryRequestReceived.WithLabelValues("call_not_authorized").Inc()
|
||||
return http.StatusForbidden, fmt.Errorf(`call "%s" not authorized`, callKey)
|
||||
}
|
||||
if !permsForUser.allowAnything {
|
||||
for _, acct := range q.PDAs {
|
||||
callKey := fmt.Sprintf("%s:%d:%s", callTag, chainId, solana.PublicKey(acct.ProgramAddress).String())
|
||||
if _, exists := permsForUser.allowedCalls[callKey]; !exists {
|
||||
logger.Debug("requested call not authorized", zap.String("userName", permsForUser.userName), zap.String("callKey", callKey))
|
||||
invalidQueryRequestReceived.WithLabelValues("call_not_authorized").Inc()
|
||||
return http.StatusForbidden, fmt.Errorf(`call "%s" not authorized`, callKey)
|
||||
}
|
||||
|
||||
totalRequestedCallsByChain.WithLabelValues(chainId.String()).Inc()
|
||||
totalRequestedCallsByChain.WithLabelValues(chainId.String()).Inc()
|
||||
}
|
||||
}
|
||||
|
||||
return http.StatusOK, nil
|
||||
|
|
|
@ -893,4 +893,58 @@ describe("eth call", () => {
|
|||
);
|
||||
}
|
||||
});
|
||||
test("allow anything", async () => {
|
||||
const nameCallData = createTestEthCallData(WETH_ADDRESS, "name", "string");
|
||||
const decimalsCallData = createTestEthCallData(
|
||||
WETH_ADDRESS,
|
||||
"decimals",
|
||||
"uint8"
|
||||
);
|
||||
const blockNumber = await web3.eth.getBlockNumber(ETH_DATA_FORMAT);
|
||||
const ethCall = new EthCallQueryRequest(blockNumber, [
|
||||
nameCallData,
|
||||
decimalsCallData,
|
||||
]);
|
||||
const chainId = 2;
|
||||
const ethQuery = new PerChainQueryRequest(chainId, ethCall);
|
||||
const nonce = 1;
|
||||
const request = new QueryRequest(nonce, [ethQuery]);
|
||||
const serialized = request.serialize();
|
||||
const digest = QueryRequest.digest(ENV, serialized);
|
||||
const signature = sign(PRIVATE_KEY, digest);
|
||||
const response = await axios.put(
|
||||
QUERY_URL,
|
||||
{
|
||||
signature,
|
||||
bytes: Buffer.from(serialized).toString("hex"),
|
||||
},
|
||||
{ headers: { "X-API-Key": "my_secret_key_3" } }
|
||||
);
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const queryResponse = QueryResponse.from(response.data.bytes);
|
||||
expect(queryResponse.version).toEqual(1);
|
||||
expect(queryResponse.requestChainId).toEqual(0);
|
||||
expect(queryResponse.request.version).toEqual(1);
|
||||
expect(queryResponse.request.requests.length).toEqual(1);
|
||||
expect(queryResponse.request.requests[0].chainId).toEqual(2);
|
||||
expect(queryResponse.request.requests[0].query.type()).toEqual(
|
||||
ChainQueryType.EthCall
|
||||
);
|
||||
|
||||
const ecr = queryResponse.responses[0].response as EthCallQueryResponse;
|
||||
expect(ecr.blockNumber.toString()).toEqual(BigInt(blockNumber).toString());
|
||||
expect(ecr.blockHash).toEqual(
|
||||
(await web3.eth.getBlock(BigInt(blockNumber))).hash
|
||||
);
|
||||
expect(ecr.results.length).toEqual(2);
|
||||
expect(ecr.results[0]).toEqual(
|
||||
// Name
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000"
|
||||
);
|
||||
expect(ecr.results[1]).toEqual(
|
||||
// Decimals
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000012"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -505,4 +505,113 @@ describe("solana", () => {
|
|||
);
|
||||
}
|
||||
});
|
||||
test("sol_account query with allow anything", async () => {
|
||||
const solAccountReq = new SolanaAccountQueryRequest("finalized", ACCOUNTS);
|
||||
const nonce = 42;
|
||||
const query = new PerChainQueryRequest(1, solAccountReq);
|
||||
const request = new QueryRequest(nonce, [query]);
|
||||
const serialized = request.serialize();
|
||||
const digest = QueryRequest.digest(ENV, serialized);
|
||||
const signature = sign(PRIVATE_KEY, digest);
|
||||
const response = await axios.put(
|
||||
QUERY_URL,
|
||||
{
|
||||
signature,
|
||||
bytes: Buffer.from(serialized).toString("hex"),
|
||||
},
|
||||
{ headers: { "X-API-Key": "my_secret_key_3" } }
|
||||
);
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const queryResponse = QueryResponse.from(response.data.bytes);
|
||||
expect(queryResponse.version).toEqual(1);
|
||||
expect(queryResponse.requestChainId).toEqual(0);
|
||||
expect(queryResponse.request.version).toEqual(1);
|
||||
expect(queryResponse.request.requests.length).toEqual(1);
|
||||
expect(queryResponse.request.requests[0].chainId).toEqual(1);
|
||||
expect(queryResponse.request.requests[0].query.type()).toEqual(
|
||||
ChainQueryType.SolanaAccount
|
||||
);
|
||||
|
||||
const sar = queryResponse.responses[0]
|
||||
.response as SolanaAccountQueryResponse;
|
||||
expect(Number(sar.slotNumber)).not.toEqual(0);
|
||||
expect(Number(sar.blockTime)).not.toEqual(0);
|
||||
expect(sar.results.length).toEqual(2);
|
||||
|
||||
expect(Number(sar.results[0].lamports)).toEqual(1461600);
|
||||
expect(Number(sar.results[0].rentEpoch)).toEqual(0);
|
||||
expect(sar.results[0].executable).toEqual(false);
|
||||
expect(base58.encode(Buffer.from(sar.results[0].owner))).toEqual(
|
||||
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
|
||||
);
|
||||
expect(Buffer.from(sar.results[0].data).toString("hex")).toEqual(
|
||||
"01000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d0000e8890423c78a0901000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
);
|
||||
|
||||
expect(Number(sar.results[1].lamports)).toEqual(1461600);
|
||||
expect(Number(sar.results[1].rentEpoch)).toEqual(0);
|
||||
expect(sar.results[1].executable).toEqual(false);
|
||||
expect(base58.encode(Buffer.from(sar.results[1].owner))).toEqual(
|
||||
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
|
||||
);
|
||||
expect(Buffer.from(sar.results[1].data).toString("hex")).toEqual(
|
||||
"01000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d01000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
);
|
||||
});
|
||||
test("sol_pda query with allow anything", async () => {
|
||||
const solPdaReq = new SolanaPdaQueryRequest(
|
||||
"finalized",
|
||||
PDAS,
|
||||
BigInt(0),
|
||||
BigInt(12),
|
||||
BigInt(16) // After this, things can change.
|
||||
);
|
||||
const nonce = 43;
|
||||
const query = new PerChainQueryRequest(1, solPdaReq);
|
||||
const request = new QueryRequest(nonce, [query]);
|
||||
const serialized = request.serialize();
|
||||
const digest = QueryRequest.digest(ENV, serialized);
|
||||
const signature = sign(PRIVATE_KEY, digest);
|
||||
const response = await axios.put(
|
||||
QUERY_URL,
|
||||
{
|
||||
signature,
|
||||
bytes: Buffer.from(serialized).toString("hex"),
|
||||
},
|
||||
{ headers: { "X-API-Key": "my_secret_key_3" } }
|
||||
);
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const queryResponse = QueryResponse.from(response.data.bytes);
|
||||
expect(queryResponse.version).toEqual(1);
|
||||
expect(queryResponse.requestChainId).toEqual(0);
|
||||
expect(queryResponse.request.version).toEqual(1);
|
||||
expect(queryResponse.request.requests.length).toEqual(1);
|
||||
expect(queryResponse.request.requests[0].chainId).toEqual(1);
|
||||
expect(queryResponse.request.requests[0].query.type()).toEqual(
|
||||
ChainQueryType.SolanaPda
|
||||
);
|
||||
|
||||
const sar = queryResponse.responses[0].response as SolanaPdaQueryResponse;
|
||||
|
||||
expect(Number(sar.slotNumber)).not.toEqual(0);
|
||||
expect(Number(sar.blockTime)).not.toEqual(0);
|
||||
expect(sar.results.length).toEqual(1);
|
||||
|
||||
expect(Buffer.from(sar.results[0].account).toString("hex")).toEqual(
|
||||
"4fa9188b339cfd573a0778c5deaeeee94d4bcfb12b345bf8e417e5119dae773e"
|
||||
);
|
||||
expect(sar.results[0].bump).toEqual(253);
|
||||
expect(Number(sar.results[0].lamports)).not.toEqual(0);
|
||||
|
||||
expect(Number(sar.results[0].rentEpoch)).toEqual(0);
|
||||
expect(sar.results[0].executable).toEqual(false);
|
||||
expect(Buffer.from(sar.results[0].owner).toString("hex")).toEqual(
|
||||
"02c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa"
|
||||
);
|
||||
expect(Buffer.from(sar.results[0].data).toString("hex")).toEqual(
|
||||
"57cd18b7f8a4d91a2da9ab4af05d0fbe"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue