Node/CCQ: Allow anything in testnet (#3966)

This commit is contained in:
bruce-riley 2024-06-07 08:13:07 -05:00 committed by GitHub
parent 215c60aee9
commit 624df192ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 379 additions and 45 deletions

View File

@ -60,6 +60,7 @@ spec:
- --logLevel=info
- --shutdownDelay1
- "0"
- --allowAnything
ports:
- containerPort: 6069
name: rest

View File

@ -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.

View File

@ -178,6 +178,12 @@
}
}
]
},
{
"userName": "Unlimited User",
"apiKey": "my_secret_key_3",
"allowUnsigned": true,
"allowAnything": true
}
]
}

View File

@ -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)
}

View File

@ -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,
}

View File

@ -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))
}

View File

@ -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

View File

@ -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"
);
});
});

View File

@ -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"
);
});
});