Skip to content

Commit

Permalink
Node/CCQ/Solana: Add sol_pda query
Browse files Browse the repository at this point in the history
  • Loading branch information
bruce-riley committed Feb 17, 2024
1 parent 00f504e commit 94e0645
Show file tree
Hide file tree
Showing 23 changed files with 2,182 additions and 97 deletions.
7 changes: 7 additions & 0 deletions node/cmd/ccq/devnet.permissions.json
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,13 @@
"chain": 1,
"account": "BVxyYhm498L79r4HMQ9sxZ5bi41DmJmeWZ7SCS7Cyvna"
}
},
{
"solPDA": {
"note:": "Core Bridge on Devnet",
"chain": 1,
"programAddress": "Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"
}
}
]
},
Expand Down
37 changes: 35 additions & 2 deletions node/cmd/ccq/parse_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ func TestParseConfigUnsupportedCallType(t *testing.T) {

_, err := parseConfig([]byte(str))
require.Error(t, err)
assert.Equal(t, `unsupported call type for user "Test User", must be "ethCall", "ethCallByTimestamp", "ethCallWithFinality" or "solAccount"`, err.Error())
assert.Equal(t, `unsupported call type for user "Test User", must be "ethCall", "ethCallByTimestamp", "ethCallWithFinality", "solAccount" or "solPDA"`, err.Error())
}

func TestParseConfigInvalidContractAddress(t *testing.T) {
Expand Down Expand Up @@ -295,7 +295,29 @@ func TestParseConfigSuccess(t *testing.T) {
"contractAddress": "B4FBF271143F4FBf7B91A5ded31805e42b2208d7",
"call": "0x06fdde03"
}
}
},
{
"ethCallWithFinality": {
"note:": "Decimals of WETH on Devnet",
"chain": 2,
"contractAddress": "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E",
"call": "0x313ce567"
}
},
{
"solAccount": {
"note:": "Example NFT on Devnet",
"chain": 1,
"account": "BVxyYhm498L79r4HMQ9sxZ5bi41DmJmeWZ7SCS7Cyvna"
}
},
{
"solPDA": {
"note:": "Core Bridge on Devnet",
"chain": 1,
"programAddress": "Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"
}
}
]
}
]
Expand All @@ -308,9 +330,20 @@ func TestParseConfigSuccess(t *testing.T) {
perm, exists := perms["my_secret_key"]
require.True(t, exists)

assert.Equal(t, 5, len(perm.allowedCalls))

_, exists = perm.allowedCalls["ethCall:2:000000000000000000000000b4fbf271143f4fbf7b91a5ded31805e42b2208d6:06fdde03"]
assert.True(t, exists)

_, exists = perm.allowedCalls["ethCallByTimestamp:2:000000000000000000000000b4fbf271143f4fbf7b91a5ded31805e42b2208d7:06fdde03"]
assert.True(t, exists)

_, exists = perm.allowedCalls["ethCallWithFinality:2:000000000000000000000000ddb64fe46a91d46ee29420539fc25fd07c5fea3e:313ce567"]
assert.True(t, exists)

_, exists = perm.allowedCalls["solAccount:1:BVxyYhm498L79r4HMQ9sxZ5bi41DmJmeWZ7SCS7Cyvna"]
assert.True(t, exists)

_, exists = perm.allowedCalls["solPDA:1:Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"]
assert.True(t, exists)
}
29 changes: 28 additions & 1 deletion node/cmd/ccq/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type (
EthCallByTimestamp *EthCallByTimestamp `json:"ethCallByTimestamp"`
EthCallWithFinality *EthCallWithFinality `json:"ethCallWithFinality"`
SolanaAccount *SolanaAccount `json:"solAccount"`
SolanaPda *SolanaPda `json:"solPDA"`
}

EthCall struct {
Expand All @@ -62,6 +63,12 @@ type (
Account string `json:"account"`
}

SolanaPda struct {
Chain int `json:"chain"`
ProgramAddress string `json:"programAddress"`
// TODO: Should we make them specify seeds as well?
}

PermissionsMap map[string]*permissionEntry

permissionEntry struct {
Expand Down Expand Up @@ -234,8 +241,28 @@ func parseConfig(byteValue []byte) (PermissionsMap, error) {
}
}
callKey = fmt.Sprintf("solAccount:%d:%s", ac.SolanaAccount.Chain, account)
} else if ac.SolanaPda != nil {
// We assume the account is base58, but if it starts with "0x" it should be 32 bytes of hex.
pa := ac.SolanaPda.ProgramAddress
if strings.HasPrefix(pa, "0x") {
buf, err := hex.DecodeString(pa[2:])
if err != nil {
return nil, fmt.Errorf(`invalid solana program address hex string "%s" for user "%s": %w`, pa, user.UserName, err)
}
if len(buf) != query.SolanaPublicKeyLength {
return nil, fmt.Errorf(`invalid solana program address hex string "%s" for user "%s, must be %d bytes`, pa, user.UserName, query.SolanaPublicKeyLength)
}
pa = solana.PublicKey(buf).String()
} else {
// Make sure it is valid base58.
_, err := solana.PublicKeyFromBase58(pa)
if err != nil {
return nil, fmt.Errorf(`solana program address string "%s" for user "%s" is not valid base58: %w`, pa, user.UserName, err)
}
}
callKey = fmt.Sprintf("solPDA:%d:%s", ac.SolanaPda.Chain, pa)
} else {
return nil, fmt.Errorf(`unsupported call type for user "%s", must be "ethCall", "ethCallByTimestamp", "ethCallWithFinality" or "solAccount"`, user.UserName)
return nil, fmt.Errorf(`unsupported call type for user "%s", must be "ethCall", "ethCallByTimestamp", "ethCallWithFinality", "solAccount" or "solPDA"`, user.UserName)
}

if callKey == "" {
Expand Down
18 changes: 18 additions & 0 deletions node/cmd/ccq/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ func validateRequest(logger *zap.Logger, env common.Environment, perms *Permissi
status, err = validateCallData(logger, permsForUser, "ethCallWithFinality", pcq.ChainId, q.CallData)
case *query.SolanaAccountQueryRequest:
status, err = validateSolanaAccountQuery(logger, permsForUser, "solAccount", pcq.ChainId, q)
case *query.SolanaPdaQueryRequest:
status, err = validateSolanaPdaQuery(logger, permsForUser, "solPDA", pcq.ChainId, q)
default:
logger.Debug("unsupported query type", zap.String("userName", permsForUser.userName), zap.Any("type", pcq.Query))
invalidQueryRequestReceived.WithLabelValues("unsupported_query_type").Inc()
Expand Down Expand Up @@ -171,3 +173,19 @@ func validateSolanaAccountQuery(logger *zap.Logger, permsForUser *permissionEntr

return http.StatusOK, nil
}

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

totalRequestedCallsByChain.WithLabelValues(chainId.String()).Inc()
}

return http.StatusOK, nil
}
61 changes: 45 additions & 16 deletions node/hack/query/send_req.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
"github.com/certusone/wormhole/node/pkg/query"
"github.com/ethereum/go-ethereum/accounts/abi"
ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethCrypto "github.com/ethereum/go-ethereum/crypto"
pubsub "github.com/libp2p/go-libp2p-pubsub"
Expand Down Expand Up @@ -124,7 +125,7 @@ func main() {
//

{
logger.Info("Running Solana tests")
logger.Info("Running Solana account test")

// Start of query creation...
account1, err := solana.PublicKeyFromBase58("Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o")
Expand All @@ -142,11 +143,50 @@ func main() {
Accounts: [][query.SolanaPublicKeyLength]byte{account1, account2},
}

queryRequest := createSolanaQueryRequest(callRequest)
queryRequest := &query.QueryRequest{
Nonce: rand.Uint32(),
PerChainQueries: []*query.PerChainQueryRequest{
{
ChainId: 1,
Query: callRequest,
},
},
}
sendSolanaQueryAndGetRsp(queryRequest, sk, th_req, ctx, logger, sub)
}

{
logger.Info("Running Solana PDA test")

// Start of query creation...
callRequest := &query.SolanaPdaQueryRequest{
Commitment: "finalized",
DataSliceOffset: 0,
DataSliceLength: 100,
PDAs: []query.SolanaPDAEntry{
query.SolanaPDAEntry{
ProgramAddress: ethCommon.HexToHash("0x02c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa"), // Devnet core bridge
Seeds: [][]byte{
[]byte("GuardianSet"),
make([]byte, 4),
},
},
},
}

logger.Info("Solana tests complete!")
queryRequest := &query.QueryRequest{
Nonce: rand.Uint32(),
PerChainQueries: []*query.PerChainQueryRequest{
{
ChainId: 1,
Query: callRequest,
},
},
}
sendSolanaQueryAndGetRsp(queryRequest, sk, th_req, ctx, logger, sub)
}

logger.Info("Solana tests complete!")
// return

//
Expand Down Expand Up @@ -392,19 +432,6 @@ func sendQueryAndGetRsp(queryRequest *query.QueryRequest, sk *ecdsa.PrivateKey,
}
}

func createSolanaQueryRequest(callRequest *query.SolanaAccountQueryRequest) *query.QueryRequest {
queryRequest := &query.QueryRequest{
Nonce: rand.Uint32(),
PerChainQueries: []*query.PerChainQueryRequest{
{
ChainId: 1,
Query: callRequest,
},
},
}
return queryRequest
}

func sendSolanaQueryAndGetRsp(queryRequest *query.QueryRequest, sk *ecdsa.PrivateKey, th *pubsub.Topic, ctx context.Context, logger *zap.Logger, sub *pubsub.Subscription) {
queryRequestBytes, err := queryRequest.Marshal()
if err != nil {
Expand Down Expand Up @@ -483,6 +510,8 @@ func sendSolanaQueryAndGetRsp(queryRequest *query.QueryRequest, sk *ecdsa.Privat
switch r := response.PerChainResponses[index].Response.(type) {
case *query.SolanaAccountQueryResponse:
logger.Info("solana query per chain response", zap.Int("index", index), zap.Any("pcr", r))
case *query.SolanaPdaQueryResponse:
logger.Info("solana query per chain response", zap.Int("index", index), zap.Any("pcr", r))
default:
panic(fmt.Sprintf("unsupported query type, should be solana, index: %d", index))
}
Expand Down
2 changes: 1 addition & 1 deletion node/pkg/query/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,11 +433,11 @@ func (pcq *perChainQuery) ccqForwardToWatcher(qLogger *zap.Logger, receiveTime t
case pcq.channel <- pcq.req:
qLogger.Debug("forwarded query request to watcher", zap.String("requestID", pcq.req.RequestID), zap.Stringer("chainID", pcq.req.Request.ChainId))
totalRequestsByChain.WithLabelValues(pcq.req.Request.ChainId.String()).Inc()
pcq.lastUpdateTime = receiveTime
default:
// By leaving lastUpdateTime unset, we will retry next interval.
qLogger.Warn("failed to send query request to watcher, will retry next interval", zap.String("requestID", pcq.req.RequestID), zap.Stringer("chain_id", pcq.req.Request.ChainId))
}
pcq.lastUpdateTime = receiveTime
}

// numPendingRequests returns the number of per chain queries in a request that are still awaiting responses. Zero means the request can now be published.
Expand Down
Loading

0 comments on commit 94e0645

Please sign in to comment.