Skip to content

Commit

Permalink
Fix regression on query creation
Browse files Browse the repository at this point in the history
  • Loading branch information
Maxwell Dulin committed Aug 22, 2024
2 parents e847d92 + f24499f commit 5d7e33d
Show file tree
Hide file tree
Showing 41 changed files with 4,796 additions and 3,999 deletions.
4,221 changes: 1,969 additions & 2,252 deletions clients/js/package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions clients/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
],
"dependencies": {
"@celo-tools/celo-ethers-wrapper": "^0.1.0",
"@certusone/wormhole-sdk": "^0.10.15",
"@certusone/wormhole-sdk": "^0.10.18",
"@cosmjs/encoding": "^0.26.2",
"@improbable-eng/grpc-web-node-http-transport": "^0.15.0",
"@injectivelabs/networks": "^1.10.7",
Expand All @@ -42,7 +42,7 @@
"@solana/web3.js": "^1.22.0",
"@terra-money/terra.js": "^3.1.9",
"@types/config": "^3.3.0",
"@wormhole-foundation/sdk": "^0.7.0-beta.5",
"@wormhole-foundation/sdk": "^0.9.0",
"@xpla/xpla.js": "^0.2.1",
"algosdk": "^2.4.0",
"aptos": "^1.3.16",
Expand Down
1 change: 1 addition & 0 deletions clients/js/src/chains/generic/getOriginalAsset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export const getOriginalAsset = async (
case "Xlayer":
case "Linea":
case "Berachain":
case "Snaxchain":
case "Seievm":
case "Sepolia":
case "ArbitrumSepolia":
Expand Down
1 change: 1 addition & 0 deletions clients/js/src/chains/generic/getWrappedAssetAddress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export const getWrappedAssetAddress = async (
case "Xlayer":
case "Linea":
case "Berachain":
case "Snaxchain":
case "Seievm":
case "Sepolia":
case "ArbitrumSepolia":
Expand Down
1 change: 1 addition & 0 deletions clients/js/src/chains/generic/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export const getProviderForChain = <T extends Chain>(
case "Xlayer":
case "Linea":
case "Berachain":
case "Snaxchain":
case "Seievm":
case "Sepolia":
case "ArbitrumSepolia":
Expand Down
17 changes: 16 additions & 1 deletion clients/js/src/consts/networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,11 @@ const Mainnet = {
key: undefined,
chain_id: undefined,
},
Snaxchain: {
rpc: undefined,
key: undefined,
chain_id: undefined,
},
Seievm: {
rpc: undefined,
key: undefined,
Expand Down Expand Up @@ -336,7 +341,7 @@ const Testnet = {
Acala: {
rpc: "https://eth-rpc-acala-testnet.aca-staging.network",
key: getEnvVar("ETH_KEY_TESTNET"),
chain_id: 595,
chain_id: 597,
},
Klaytn: {
rpc: "https://api.baobab.klaytn.net:8651",
Expand Down Expand Up @@ -414,6 +419,11 @@ const Testnet = {
key: getEnvVar("ETH_KEY_TESTNET"),
chain_id: 80084,
},
Snaxchain: {
rpc: "https://rpc-snaxchain-s50q0kjngn.t.conduit.xyz/",
key: getEnvVar("ETH_KEY_TESTNET"),
chain_id: 2192,
},
Seievm: {
rpc: "https://evm-rpc-arctic-1.sei-apis.com/",
key: getEnvVar("ETH_KEY_TESTNET"),
Expand Down Expand Up @@ -662,6 +672,11 @@ const Devnet = {
key: undefined,
chain_id: undefined,
},
Snaxchain: {
rpc: undefined,
key: undefined,
chain_id: undefined,
},
Seievm: {
rpc: undefined,
key: undefined,
Expand Down
25 changes: 25 additions & 0 deletions docs/query_proxy.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ The following are the Solana call types. Both require the `chain` parameter plus

The Solana account and and program address can be expressed as either a 32 byte hex string starting with "0x" or as a base 58 value.

#### Wild Card Contract Addresses

For the eth calls, the `contractAddress` field may be set to `"*"` which means the specified call type and call may be made to any
contract address on the specified chain.

#### Creating New API Keys

Each user must have an API key. These keys only have meaning to the proxy server. They are not passed to the guardians.
Expand Down Expand Up @@ -174,6 +179,26 @@ If this flag is specified, then `allowedCalls` must not be specified.
}
```

### Validating Permissions File Changes

The query server automatically detects changes to the permissions file and attempts to reload them. If there are errors in the updated
file, the server rejects the update and continues running on the old version. However, if the file is not fixed, those errors will prevent
the server from coming up on the next restart. You can avoid this problem by verifying any file updates before attempting to reload.

To do this, you can copy the permissions file to some other file, make your changes to the copy, and then do the following:

```sh
$ guardiand query-server --verifyPermissions --permFile new.permissions.file.json --allowAnything
```

where `new.permissions.file.json` is the path to the updated file. Additionally, if your permission file includes the `allowAnything`
flag for any of the users, you must specify that flag on the command line when doing the verify.

If the updated file is good, the program will exit immediately with no output and an exit code of zero. If the file contains
errors, the first error will be printed, and the exit code will be one.

Once you are satisfied with your updates, you can copy the updated file to the official location.

## Telemetry

The proxy server provides two types of telemetry data, logs and metrics.
Expand Down
2 changes: 1 addition & 1 deletion node/cmd/ccq/devnet.permissions.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"ethCall": {
"note:": "Name of WETH on Goerli",
"chain": 2,
"contractAddress": "B4FBF271143F4FBf7B91A5ded31805e42b2208d6",
"contractAddress": "*",
"call": "0x06fdde03"
}
},
Expand Down
9 changes: 8 additions & 1 deletion node/cmd/ccq/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,14 @@ func (s *httpServer) handleQuery(w http.ResponseWriter, r *http.Request) {
// Wait for the response or timeout
select {
case <-time.After(query.RequestTimeout + 5*time.Second):
s.logger.Info("publishing time out to client", zap.String("userId", permEntry.userName), zap.String("requestId", requestId))
maxMatchingResponses, outstandingResponses, quorum := pendingResponse.getStats()
s.logger.Info("publishing time out to client",
zap.String("userId", permEntry.userName),
zap.String("requestId", requestId),
zap.Int("maxMatchingResponses", maxMatchingResponses),
zap.Int("outstandingResponses", outstandingResponses),
zap.Int("quorum", quorum),
)
http.Error(w, "Timed out waiting for response", http.StatusGatewayTimeout)
queryTimeoutsByUser.WithLabelValues(permEntry.userName).Inc()
failedQueriesByUser.WithLabelValues(permEntry.userName).Inc()
Expand Down
1 change: 1 addition & 0 deletions node/cmd/ccq/p2p.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ func runP2P(
}
}
outstandingResponses := len(guardianSet.Keys) - totalSigners
pendingResponse.updateStats(maxMatchingResponses, outstandingResponses, quorum)
if maxMatchingResponses+outstandingResponses < quorum {
quorumNotMetByUser.WithLabelValues(pendingResponse.userName).Inc()
failedQueriesByUser.WithLabelValues(pendingResponse.userName).Inc()
Expand Down
157 changes: 157 additions & 0 deletions node/cmd/ccq/parse_config_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package ccq

import (
"encoding/hex"
"strings"
"testing"

"github.com/certusone/wormhole/node/pkg/query"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/wormhole-foundation/wormhole/sdk/vaa"
"go.uber.org/zap"
)

func TestParseConfigFileDoesntExist(t *testing.T) {
Expand Down Expand Up @@ -461,3 +466,155 @@ func TestParseConfigAllowAnythingSuccess(t *testing.T) {
require.True(t, ok)
assert.True(t, perm.allowAnything)
}

func TestParseConfigContractWildcard(t *testing.T) {
str := `
{
"permissions": [
{
"userName": "Test User",
"apiKey": "my_secret_key",
"allowedCalls": [
{
"ethCall": {
"note:": "Name of anything on Goerli",
"chain": 2,
"contractAddress": "*",
"call": "0x06fdde03"
}
},
{
"ethCallByTimestamp": {
"note:": "Total supply of WETH on Goerli",
"chain": 2,
"contractAddress": "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6",
"call": "0x18160ddd"
}
}
]
}
]
}`

perms, err := parseConfig([]byte(str), true)
require.NoError(t, err)
assert.Equal(t, 1, len(perms))

permsForUser, ok := perms["my_secret_key"]
require.True(t, ok)
assert.Equal(t, 2, len(permsForUser.allowedCalls))

logger := zap.NewNop()

type testCase struct {
label string
callType string
chainID vaa.ChainID
contractAddress string
data string
errText string // empty string means success
}

var testCases = []testCase{
{
label: "Wild card, success",
callType: "ethCall",
chainID: vaa.ChainIDEthereum,
contractAddress: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6",
data: "0x06fdde03",
errText: "",
},
{
label: "Wild card, success, different address",
callType: "ethCall",
chainID: vaa.ChainIDEthereum,
contractAddress: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d7",
data: "0x06fdde03",
errText: "",
},
{
label: "Wild card, wrong call type",
callType: "ethCallByTimestamp",
chainID: vaa.ChainIDEthereum,
contractAddress: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6",
data: "0x06fdde03",
errText: "not authorized",
},
{
label: "Wild card, wrong chain",
callType: "ethCall",
chainID: vaa.ChainIDBase,
contractAddress: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6",
data: "0x06fdde03",
errText: "not authorized",
},
{
label: "Specific, success",
callType: "ethCallByTimestamp",
chainID: vaa.ChainIDEthereum,
contractAddress: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6",
data: "0x18160ddd",
errText: "",
},
{
label: "Specific, wrong call type",
callType: "ethCall",
chainID: vaa.ChainIDEthereum,
contractAddress: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6",
data: "0x18160ddd",
errText: "not authorized",
},
{
label: "Specific, wrong chain",
callType: "ethCallByTimestamp",
chainID: vaa.ChainIDBase,
contractAddress: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6",
data: "0x18160ddd",
errText: "not authorized",
},
{
label: "Specific, wrong address",
callType: "ethCallByTimestamp",
chainID: vaa.ChainIDEthereum,
contractAddress: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d7",
data: "0x18160ddd",
errText: "not authorized",
},
{
label: "Specific, wrong data",
callType: "ethCallByTimestamp",
chainID: vaa.ChainIDEthereum,
contractAddress: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6",
data: "0x18160dde",
errText: "not authorized",
},
}

for _, tst := range testCases {
t.Run(tst.label, func(t *testing.T) {
status, err := validateCallData(logger, permsForUser, tst.callType, tst.chainID, createCallData(t, tst.contractAddress, tst.data))
if tst.errText == "" {
require.NoError(t, err)
assert.Equal(t, 200, status)
} else {
require.ErrorContains(t, err, tst.errText)
}
})
}
}

func createCallData(t *testing.T, toStr string, dataStr string) []*query.EthCallData {
t.Helper()
to, err := vaa.StringToAddress(strings.TrimPrefix(toStr, "0x"))
require.NoError(t, err)

data, err := hex.DecodeString(strings.TrimPrefix(dataStr, "0x"))
require.NoError(t, err)

return []*query.EthCallData{
{
To: to.Bytes(),
Data: data,
},
}
}
20 changes: 20 additions & 0 deletions node/cmd/ccq/pending_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ type PendingResponse struct {
queryRequest *query.QueryRequest
ch chan *SignedResponse
errCh chan *ErrorEntry

// statsLock protects the data items below.
statsLock sync.RWMutex
maxMatchingResponses int
outstandingResponses int
quorum int
}

type ErrorEntry struct {
Expand Down Expand Up @@ -111,3 +117,17 @@ func (p *PendingResponses) updateMetricsAlreadyLocked(reqRemoved *PendingRespons
}
}
}

func (p *PendingResponse) updateStats(maxMatchingResponses int, outstandingResponses int, quorum int) {
p.statsLock.Lock()
defer p.statsLock.Unlock()
p.maxMatchingResponses = maxMatchingResponses
p.outstandingResponses = outstandingResponses
p.quorum = quorum
}

func (p *PendingResponse) getStats() (int, int, int) {
p.statsLock.Lock()
defer p.statsLock.Unlock()
return p.maxMatchingResponses, p.outstandingResponses, p.quorum
}
10 changes: 7 additions & 3 deletions node/cmd/ccq/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,9 +280,13 @@ func parseConfig(byteValue []byte, allowAnything bool) (PermissionsMap, error) {

if callKey == "" {
// Convert the contract address into a standard format like "000000000000000000000000b4fbf271143f4fbf7b91a5ded31805e42b2208d6".
contractAddress, err := vaa.StringToAddress(contractAddressStr)
if err != nil {
return nil, fmt.Errorf(`invalid contract address "%s" for user "%s"`, contractAddressStr, user.UserName)
contractAddress := contractAddressStr
if contractAddressStr != "*" {
contractAddr, err := vaa.StringToAddress(contractAddressStr)
if err != nil {
return nil, fmt.Errorf(`invalid contract address "%s" for user "%s"`, contractAddressStr, user.UserName)
}
contractAddress = contractAddr.String()
}

// The call should be the ABI four byte hex hash of the function signature. Parse it into a standard form of "06fdde03".
Expand Down
Loading

0 comments on commit 5d7e33d

Please sign in to comment.