2023-10-12 12:29:21 -07:00
package ccq
import (
"context"
"crypto/ecdsa"
"encoding/hex"
"fmt"
"net/http"
"time"
"github.com/certusone/wormhole/node/pkg/common"
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
"github.com/certusone/wormhole/node/pkg/query"
"github.com/wormhole-foundation/wormhole/sdk/vaa"
"go.uber.org/zap"
ethAbi "github.com/certusone/wormhole/node/pkg/watchers/evm/connectors/ethabi"
ethBind "github.com/ethereum/go-ethereum/accounts/abi/bind"
eth_common "github.com/ethereum/go-ethereum/common"
ethCrypto "github.com/ethereum/go-ethereum/crypto"
ethClient "github.com/ethereum/go-ethereum/ethclient"
ethRpc "github.com/ethereum/go-ethereum/rpc"
)
func FetchCurrentGuardianSet ( rpcUrl , coreAddr string ) ( * common . GuardianSet , error ) {
ctx , cancel := context . WithTimeout ( context . Background ( ) , time . Second * 5 )
defer cancel ( )
ethContract := eth_common . HexToAddress ( coreAddr )
rawClient , err := ethRpc . DialContext ( ctx , rpcUrl )
if err != nil {
return nil , fmt . Errorf ( "failed to connect to ethereum" )
}
client := ethClient . NewClient ( rawClient )
caller , err := ethAbi . NewAbiCaller ( ethContract , client )
if err != nil {
return nil , fmt . Errorf ( "failed to create caller" )
}
currentIndex , err := caller . GetCurrentGuardianSetIndex ( & ethBind . CallOpts { Context : ctx } )
if err != nil {
return nil , fmt . Errorf ( "error requesting current guardian set index: %w" , err )
}
gs , err := caller . GetGuardianSet ( & ethBind . CallOpts { Context : ctx } , currentIndex )
if err != nil {
return nil , fmt . Errorf ( "error requesting current guardian set value: %w" , err )
}
return & common . GuardianSet {
Keys : gs . Keys ,
Index : currentIndex ,
} , nil
}
// validateRequest verifies that this API key is allowed to do all of the calls in this request. In the case of an error, it returns the HTTP status.
2023-12-11 12:44:48 -08:00
func validateRequest ( logger * zap . Logger , env common . Environment , perms * Permissions , signerKey * ecdsa . PrivateKey , apiKey string , qr * gossipv1 . SignedQueryRequest ) ( int , error ) {
permsForUser , exists := perms . GetUserEntry ( apiKey )
2023-10-12 12:29:21 -07:00
if ! exists {
logger . Debug ( "invalid api key" , zap . String ( "apiKey" , apiKey ) )
2023-11-08 09:22:34 -08:00
invalidQueryRequestReceived . WithLabelValues ( "invalid_api_key" ) . Inc ( )
2023-10-12 12:29:21 -07:00
return http . StatusForbidden , fmt . Errorf ( "invalid api key" )
}
// TODO: Should we verify the signatures?
if len ( qr . Signature ) == 0 {
if ! permsForUser . allowUnsigned || signerKey == nil {
logger . Debug ( "request not signed and unsigned requests not supported for this user" ,
zap . String ( "userName" , permsForUser . userName ) ,
zap . Bool ( "allowUnsigned" , permsForUser . allowUnsigned ) ,
zap . Bool ( "signerKeyConfigured" , signerKey != nil ) ,
)
2023-11-08 09:22:34 -08:00
invalidQueryRequestReceived . WithLabelValues ( "request_not_signed" ) . Inc ( )
2023-10-12 12:29:21 -07:00
return http . StatusBadRequest , fmt . Errorf ( "request not signed" )
}
// Sign the request using our key.
var err error
digest := query . QueryRequestDigest ( env , qr . QueryRequest )
qr . Signature , err = ethCrypto . Sign ( digest . Bytes ( ) , signerKey )
if err != nil {
logger . Debug ( "failed to sign request" , zap . String ( "userName" , permsForUser . userName ) , zap . Error ( err ) )
2023-11-08 09:22:34 -08:00
invalidQueryRequestReceived . WithLabelValues ( "failed_to_sign_request" ) . Inc ( )
2023-10-12 12:29:21 -07:00
return http . StatusInternalServerError , fmt . Errorf ( "failed to sign request: %w" , err )
}
}
var queryRequest query . QueryRequest
err := queryRequest . Unmarshal ( qr . QueryRequest )
if err != nil {
logger . Debug ( "failed to unmarshal request" , zap . String ( "userName" , permsForUser . userName ) , zap . Error ( err ) )
2023-11-08 09:22:34 -08:00
invalidQueryRequestReceived . WithLabelValues ( "failed_to_unmarshal_request" ) . Inc ( )
2023-10-12 12:29:21 -07:00
return http . StatusInternalServerError , fmt . Errorf ( "failed to unmarshal request: %w" , err )
}
// Make sure the overall query request is sane.
if err := queryRequest . Validate ( ) ; err != nil {
logger . Debug ( "failed to validate request" , zap . String ( "userName" , permsForUser . userName ) , zap . Error ( err ) )
2023-11-08 09:22:34 -08:00
invalidQueryRequestReceived . WithLabelValues ( "failed_to_validate_request" ) . Inc ( )
2023-10-12 12:29:21 -07:00
return http . StatusBadRequest , fmt . Errorf ( "failed to validate request: %w" , err )
}
// Make sure they are allowed to make all of the calls that they are asking for.
for _ , pcq := range queryRequest . PerChainQueries {
2023-11-06 06:56:34 -08:00
var status int
var err error
2023-10-12 12:29:21 -07:00
switch q := pcq . Query . ( type ) {
case * query . EthCallQueryRequest :
2023-11-06 06:56:34 -08:00
status , err = validateCallData ( logger , permsForUser , "ethCall" , pcq . ChainId , q . CallData )
2023-10-19 08:32:23 -07:00
case * query . EthCallByTimestampQueryRequest :
2023-11-06 06:56:34 -08:00
status , err = validateCallData ( logger , permsForUser , "ethCallByTimestamp" , pcq . ChainId , q . CallData )
2023-10-30 09:13:03 -07:00
case * query . EthCallWithFinalityQueryRequest :
2023-11-06 06:56:34 -08:00
status , err = validateCallData ( logger , permsForUser , "ethCallWithFinality" , pcq . ChainId , q . CallData )
2023-10-12 12:29:21 -07:00
default :
logger . Debug ( "unsupported query type" , zap . String ( "userName" , permsForUser . userName ) , zap . Any ( "type" , pcq . Query ) )
2023-11-08 09:22:34 -08:00
invalidQueryRequestReceived . WithLabelValues ( "unsupported_query_type" ) . Inc ( )
2023-10-12 12:29:21 -07:00
return http . StatusBadRequest , fmt . Errorf ( "unsupported query type" )
}
2023-11-06 06:56:34 -08:00
if err != nil {
2023-11-08 09:22:34 -08:00
// Metric is pegged below.
2023-11-06 06:56:34 -08:00
return status , err
}
2023-10-12 12:29:21 -07:00
}
logger . Debug ( "submitting query request" , zap . String ( "userName" , permsForUser . userName ) )
return http . StatusOK , nil
}
2023-11-06 06:56:34 -08:00
// validateCallData performs verification on all of the call data objects in a query.
func validateCallData ( logger * zap . Logger , permsForUser * permissionEntry , callTag string , chainId vaa . ChainID , callData [ ] * query . EthCallData ) ( int , error ) {
for _ , cd := range callData {
contractAddress , err := vaa . BytesToAddress ( cd . To )
if err != nil {
logger . Debug ( "failed to parse contract address" , zap . String ( "userName" , permsForUser . userName ) , zap . String ( "contract" , hex . EncodeToString ( cd . To ) ) , zap . Error ( err ) )
2023-11-08 09:22:34 -08:00
invalidQueryRequestReceived . WithLabelValues ( "invalid_contract_address" ) . Inc ( )
2023-11-06 06:56:34 -08:00
return http . StatusBadRequest , fmt . Errorf ( "failed to parse contract address: %w" , err )
}
if len ( cd . Data ) < ETH_CALL_SIG_LENGTH {
logger . Debug ( "eth call data must be at least four bytes" , zap . String ( "userName" , permsForUser . userName ) , zap . String ( "data" , hex . EncodeToString ( cd . Data ) ) )
2023-11-08 09:22:34 -08:00
invalidQueryRequestReceived . WithLabelValues ( "bad_call_data" ) . Inc ( )
2023-11-06 06:56:34 -08:00
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 ) )
2023-11-08 09:22:34 -08:00
invalidQueryRequestReceived . WithLabelValues ( "call_not_authorized" ) . Inc ( )
2023-11-06 06:56:34 -08:00
return http . StatusBadRequest , fmt . Errorf ( ` call "%s" not authorized ` , callKey )
}
2023-11-08 09:22:34 -08:00
totalRequestedCallsByChain . WithLabelValues ( chainId . String ( ) ) . Inc ( )
2023-11-06 06:56:34 -08:00
}
return http . StatusOK , nil
}