From 7e1686dcbb0798ddb661763f23f533e8ddd68730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDiga=20Kokelj?= Date: Wed, 13 Mar 2024 14:49:37 +0100 Subject: [PATCH 01/65] Add new endpoint for generating messages and refactor message generation --- go/common/viewingkey/viewing_key.go | 144 +++++++++++++--------- go/enclave/vkhandler/vk_handler_test.go | 21 ++-- tools/walletextension/api/routes.go | 60 +++++++++ tools/walletextension/common/common.go | 11 ++ tools/walletextension/common/constants.go | 33 ++--- tools/walletextension/lib/client_lib.go | 14 +-- tools/walletextension/wallet_extension.go | 16 +++ 7 files changed, 210 insertions(+), 89 deletions(-) diff --git a/go/common/viewingkey/viewing_key.go b/go/common/viewingkey/viewing_key.go index c46c285585..4ff6642997 100644 --- a/go/common/viewingkey/viewing_key.go +++ b/go/common/viewingkey/viewing_key.go @@ -3,6 +3,7 @@ package viewingkey import ( "crypto/ecdsa" "encoding/hex" + "encoding/json" "errors" "fmt" "math/big" @@ -100,8 +101,10 @@ func (psc PersonalSignChecker) CheckSignature(encryptionToken string, signature // create all possible hashes (for all the supported versions) of the message (needed for signature verification) for _, version := range PersonalSignMessageSupportedVersions { - message := GeneratePersonalSignMessage(encryptionToken, chainID, version) - messageHash := accounts.TextHash([]byte(message)) + messageHash, err := GenerateMessage(encryptionToken, chainID, version, PersonalSign, true) + if err != nil { + return nil, fmt.Errorf("cannot generate message. Cause %w", err) + } // current signature is valid - return account address address, err := CheckSignatureAndReturnAccountAddress(messageHash, signature) @@ -118,9 +121,9 @@ func (e EIP712Checker) CheckSignature(encryptionToken string, signature []byte, return nil, fmt.Errorf("invalid signaure length: %d", len(signature)) } - rawDataOptions, err := GenerateAuthenticationEIP712RawDataOptions(encryptionToken, chainID) + messageHash, err := GenerateMessage(encryptionToken, chainID, 1, EIP712Signature, true) if err != nil { - return nil, fmt.Errorf("cannot generate eip712 message. Cause %w", err) + return nil, fmt.Errorf("cannot generate message. Cause %w", err) } // We transform the V from 27/28 to 0/1. This same change is made in Geth internals, for legacy reasons to be able @@ -129,16 +132,12 @@ func (e EIP712Checker) CheckSignature(encryptionToken string, signature []byte, signature[64] -= 27 } - for _, rawData := range rawDataOptions { - // create a hash of structured message (needed for signature verification) - hashBytes := crypto.Keccak256(rawData) - - // current signature is valid - return account address - address, err := CheckSignatureAndReturnAccountAddress(hashBytes, signature) - if err == nil { - return address, nil - } + // current signature is valid - return account address + address, err := CheckSignatureAndReturnAccountAddress(messageHash, signature) + if err == nil { + return address, nil } + return nil, errors.New("EIP 712 signature verification failed") } @@ -249,10 +248,6 @@ func GenerateSignMessage(vkPubKey []byte) string { return SignedMsgPrefix + hex.EncodeToString(vkPubKey) } -func GeneratePersonalSignMessage(encryptionToken string, chainID int64, version int) string { - return fmt.Sprintf(PersonalSignMessageFormat, encryptionToken, chainID, version) -} - // getBytesFromTypedData creates EIP-712 compliant hash from typedData. // It involves hashing the message with its structure, hashing domain separator, // and then encoding both hashes with specific EIP-712 bytes to construct the final message format. @@ -271,14 +266,71 @@ func getBytesFromTypedData(typedData apitypes.TypedData) ([]byte, error) { return rawData, nil } -// GenerateAuthenticationEIP712RawDataOptions generates all the options or raw data messages (bytes) -// for an EIP-712 message used to authenticate an address with user -// (currently only one option is supported, but function leaves room for future expansion of options) -func GenerateAuthenticationEIP712RawDataOptions(userID string, chainID int64) ([][]byte, error) { - if len(userID) != UserIDHexLength { - return nil, fmt.Errorf("userID hex length must be %d, received %d", UserIDHexLength, len(userID)) +// CalculateUserIDHex CalculateUserID calculates userID from a public key +// (we truncate it, because we want it to have length 20) and encode to hex strings +func CalculateUserIDHex(publicKeyBytes []byte) string { + return hex.EncodeToString(CalculateUserID(publicKeyBytes)) +} + +// CalculateUserID calculates userID from a public key (we truncate it, because we want it to have length 20) +func CalculateUserID(publicKeyBytes []byte) []byte { + return crypto.Keccak256Hash(publicKeyBytes).Bytes()[:20] +} + +// CheckSignatureAndReturnAccountAddress checks if the signature is valid for hash of the message and checks if +// signer is an address provided to the function. +// It returns an address if the signature is valid and nil otherwise +func CheckSignatureAndReturnAccountAddress(hashBytes []byte, signature []byte) (*gethcommon.Address, error) { + pubKeyBytes, err := crypto.Ecrecover(hashBytes, signature) + if err != nil { + return nil, err + } + + pubKey, err := crypto.UnmarshalPubkey(pubKeyBytes) + if err != nil { + return nil, err + } + + r := new(big.Int).SetBytes(signature[:32]) + s := new(big.Int).SetBytes(signature[32:64]) + + // Verify the signature and return the result (all the checks above passed) + isSigValid := ecdsa.Verify(pubKey, hashBytes, r, s) + if isSigValid { + address := crypto.PubkeyToAddress(*pubKey) + return &address, nil } - encryptionToken := "0x" + userID + return nil, fmt.Errorf("invalid signature") +} + +type MessageGenerator interface { + generateMessage(encryptionToken string, chainID int64, version int, hash bool) ([]byte, error) +} + +type ( + PersonalMessageGenerator struct{} + EIP712MessageGenerator struct{} +) + +var messageGenerators = map[SignatureType]MessageGenerator{ + PersonalSign: PersonalMessageGenerator{}, + EIP712Signature: EIP712MessageGenerator{}, +} + +// GenerateMessage generates a message for the given encryptionToken, chainID, version and signatureType +func (p PersonalMessageGenerator) generateMessage(encryptionToken string, chainID int64, version int, hash bool) ([]byte, error) { + textMessage := fmt.Sprintf(PersonalSignMessageFormat, encryptionToken, chainID, version) + if hash { + return accounts.TextHash([]byte(textMessage)), nil + } + return []byte(textMessage), nil +} + +func (e EIP712MessageGenerator) generateMessage(encryptionToken string, chainID int64, _ int, hash bool) ([]byte, error) { + if len(encryptionToken) != UserIDHexLength { + return nil, fmt.Errorf("userID hex length must be %d, received %d", UserIDHexLength, len(encryptionToken)) + } + encryptionToken = "0x" + encryptionToken domain := apitypes.TypedDataDomain{ Name: EIP712DomainNameValue, @@ -308,49 +360,27 @@ func GenerateAuthenticationEIP712RawDataOptions(userID string, chainID int64) ([ Message: message, } - rawDataOptions := make([][]byte, 0) rawData, err := getBytesFromTypedData(newTypeElement) if err != nil { return nil, err } - rawDataOptions = append(rawDataOptions, rawData) - - return rawDataOptions, nil -} - -// CalculateUserIDHex CalculateUserID calculates userID from a public key -// (we truncate it, because we want it to have length 20) and encode to hex strings -func CalculateUserIDHex(publicKeyBytes []byte) string { - return hex.EncodeToString(CalculateUserID(publicKeyBytes)) -} - -// CalculateUserID calculates userID from a public key (we truncate it, because we want it to have length 20) -func CalculateUserID(publicKeyBytes []byte) []byte { - return crypto.Keccak256Hash(publicKeyBytes).Bytes()[:20] -} -// CheckSignatureAndReturnAccountAddress checks if the signature is valid for hash of the message and checks if -// signer is an address provided to the function. -// It returns an address if the signature is valid and nil otherwise -func CheckSignatureAndReturnAccountAddress(hashBytes []byte, signature []byte) (*gethcommon.Address, error) { - pubKeyBytes, err := crypto.Ecrecover(hashBytes, signature) + // add the JSON message to the list of messages + jsonData, err := json.Marshal(newTypeElement) if err != nil { return nil, err } - pubKey, err := crypto.UnmarshalPubkey(pubKeyBytes) - if err != nil { - return nil, err + if hash { + return crypto.Keccak256(rawData), nil } + return jsonData, nil +} - r := new(big.Int).SetBytes(signature[:32]) - s := new(big.Int).SetBytes(signature[32:64]) - - // Verify the signature and return the result (all the checks above passed) - isSigValid := ecdsa.Verify(pubKey, hashBytes, r, s) - if isSigValid { - address := crypto.PubkeyToAddress(*pubKey) - return &address, nil +func GenerateMessage(encryptionToken string, chainID int64, version int, signatureType SignatureType, hash bool) ([]byte, error) { + generator, exists := messageGenerators[signatureType] + if !exists { + return nil, fmt.Errorf("unsupported signature type") } - return nil, fmt.Errorf("invalid signature") + return generator.generateMessage(encryptionToken, chainID, version, hash) } diff --git a/go/enclave/vkhandler/vk_handler_test.go b/go/enclave/vkhandler/vk_handler_test.go index 7d66185c86..61368b7b1e 100644 --- a/go/enclave/vkhandler/vk_handler_test.go +++ b/go/enclave/vkhandler/vk_handler_test.go @@ -7,7 +7,6 @@ import ( gethcommon "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/ecies" "github.com/stretchr/testify/assert" @@ -49,13 +48,15 @@ func TestCheckSignature(t *testing.T) { userPrivKey, _, userID, userAddress := generateRandomUserKeys() // Generate all message types and create map with the corresponding signature type - // Test EIP712 message format - EIP712MessageDataOptions, err := viewingkey.GenerateAuthenticationEIP712RawDataOptions(userID, chainID) + message, err := viewingkey.GenerateMessage(userID, chainID, 0, viewingkey.EIP712Signature, true) + if err != nil { + t.Fatalf(err.Error()) + } + EIP712MessageHash := crypto.Keccak256(message) + PersonalSignMessageHash, err := viewingkey.GenerateMessage(userID, chainID, viewingkey.PersonalSignMessageSupportedVersions[0], viewingkey.PersonalSign, true) if err != nil { t.Fatalf(err.Error()) } - EIP712MessageHash := crypto.Keccak256(EIP712MessageDataOptions[0]) - PersonalSignMessageHash := accounts.TextHash([]byte(viewingkey.GeneratePersonalSignMessage(userID, chainID, viewingkey.PersonalSignMessageSupportedVersions[0]))) messages := map[string]MessageWithSignatureType{ "EIP712MessageHash": { @@ -86,12 +87,16 @@ func TestVerifyViewingKey(t *testing.T) { userPrivKey, vkPrivKey, userID, userAddress := generateRandomUserKeys() // Generate all message types and create map with the corresponding signature type // Test EIP712 message format - EIP712MessageDataOptions, err := viewingkey.GenerateAuthenticationEIP712RawDataOptions(userID, chainID) + + message, err := viewingkey.GenerateMessage(userID, chainID, viewingkey.PersonalSignMessageSupportedVersions[0], viewingkey.EIP712Signature, true) + if err != nil { + t.Fatalf(err.Error()) + } + EIP712MessageHash := crypto.Keccak256(message) + PersonalSignMessageHash, err := viewingkey.GenerateMessage(userID, chainID, viewingkey.PersonalSignMessageSupportedVersions[0], viewingkey.PersonalSign, true) if err != nil { t.Fatalf(err.Error()) } - EIP712MessageHash := crypto.Keccak256(EIP712MessageDataOptions[0]) - PersonalSignMessageHash := accounts.TextHash([]byte(viewingkey.GeneratePersonalSignMessage(userID, chainID, viewingkey.PersonalSignMessageSupportedVersions[0]))) messages := map[string]MessageWithSignatureType{ "EIP712MessageHash": { diff --git a/tools/walletextension/api/routes.go b/tools/walletextension/api/routes.go index 625cd9ad7a..fde9eb798a 100644 --- a/tools/walletextension/api/routes.go +++ b/tools/walletextension/api/routes.go @@ -46,6 +46,10 @@ func NewHTTPRoutes(walletExt *walletextension.WalletExtension) []Route { Name: common.APIVersion1 + common.PathJoin, Func: httpHandler(walletExt, joinRequestHandler), }, + { + Name: common.APIVersion1 + common.PathGetMessage, + Func: httpHandler(walletExt, getMessageRequestHandler), + }, { Name: common.APIVersion1 + common.PathAuthenticate, Func: httpHandler(walletExt, authenticateRequestHandler), @@ -490,3 +494,59 @@ func versionRequestHandler(walletExt *walletextension.WalletExtension, userConn walletExt.Logger().Error("error writing success response", log.ErrKey, err) } } + +// ethRequestHandler parses the user eth request, passes it on to the WE to proxy it and processes the response +func getMessageRequestHandler(walletExt *walletextension.WalletExtension, conn userconn.UserConn) { + // read the request + body, err := conn.ReadRequest() + if err != nil { + handleError(conn, walletExt.Logger(), fmt.Errorf("error reading request: %w", err)) + return + } + + // read body of the request + var reqJSONMap map[string]interface{} + err = json.Unmarshal(body, &reqJSONMap) + if err != nil { + handleError(conn, walletExt.Logger(), fmt.Errorf("could not unmarshal address request - %w", err)) + return + } + + // get address from the request + encryptionToken, ok := reqJSONMap[common.JSONKeyEncryptionToken] + if !ok || len(encryptionToken.(string)) != common.MessageUserIDLen { + handleError(conn, walletExt.Logger(), fmt.Errorf("unable to read encryptionToken field from the request or it is not of correct length")) + return + } + + // get formats from the request, if present + var formatsSlice []string + if formatsInterface, ok := reqJSONMap[common.JSONKeyFormats]; ok { + formats, ok := formatsInterface.([]interface{}) + if !ok { + handleError(conn, walletExt.Logger(), fmt.Errorf("formats field is not an array")) + return + } + + for _, f := range formats { + formatStr, ok := f.(string) + if !ok { + handleError(conn, walletExt.Logger(), fmt.Errorf("format value is not a string")) + return + } + formatsSlice = append(formatsSlice, formatStr) + } + } + + message, err := walletExt.GetMessage(encryptionToken.(string), formatsSlice) + if err != nil { + handleError(conn, walletExt.Logger(), fmt.Errorf("internal error")) + walletExt.Logger().Error("error getting message", log.ErrKey, err) + return + } + + err = conn.WriteResponse([]byte(message)) + if err != nil { + walletExt.Logger().Error("error writing success response", log.ErrKey, err) + } +} diff --git a/tools/walletextension/common/common.go b/tools/walletextension/common/common.go index 8a13de5a3d..db29535621 100644 --- a/tools/walletextension/common/common.go +++ b/tools/walletextension/common/common.go @@ -99,3 +99,14 @@ func NewFileLogger() gethlog.Logger { return logger } + +// GetBestFormat returns the best format for a message based on available formats that are supported by the user +func GetBestFormat(formatsSlice []string) viewingkey.SignatureType { + // If "Personal" is the only format available, choose it + if len(formatsSlice) == 1 && formatsSlice[0] == "Personal" { + return viewingkey.PersonalSign + } + + // otherwise, choose EIP712 + return viewingkey.EIP712Signature +} diff --git a/tools/walletextension/common/constants.go b/tools/walletextension/common/constants.go index 8e66ea55dd..073f6f5045 100644 --- a/tools/walletextension/common/constants.go +++ b/tools/walletextension/common/constants.go @@ -9,21 +9,23 @@ import ( const ( Localhost = "127.0.0.1" - JSONKeyAddress = "address" - JSONKeyData = "data" - JSONKeyErr = "error" - JSONKeyFrom = "from" - JSONKeyID = "id" - JSONKeyMethod = "method" - JSONKeyParams = "params" - JSONKeyResult = "result" - JSONKeyRoot = "root" - JSONKeyRPCVersion = "jsonrpc" - JSONKeySignature = "signature" - JSONKeySubscription = "subscription" - JSONKeyCode = "code" - JSONKeyMessage = "message" - JSONKeyType = "type" + JSONKeyAddress = "address" + JSONKeyData = "data" + JSONKeyErr = "error" + JSONKeyFrom = "from" + JSONKeyID = "id" + JSONKeyMethod = "method" + JSONKeyParams = "params" + JSONKeyResult = "result" + JSONKeyRoot = "root" + JSONKeyRPCVersion = "jsonrpc" + JSONKeySignature = "signature" + JSONKeySubscription = "subscription" + JSONKeyCode = "code" + JSONKeyMessage = "message" + JSONKeyType = "type" + JSONKeyEncryptionToken = "encryptionToken" + JSONKeyFormats = "formats" ) const ( @@ -33,6 +35,7 @@ const ( PathGenerateViewingKey = "/generateviewingkey/" PathSubmitViewingKey = "/submitviewingkey/" PathJoin = "/join/" + PathGetMessage = "/getmessage/" PathAuthenticate = "/authenticate/" PathQuery = "/query/" PathRevoke = "/revoke/" diff --git a/tools/walletextension/lib/client_lib.go b/tools/walletextension/lib/client_lib.go index 04accae2e4..30b5a4d47b 100644 --- a/tools/walletextension/lib/client_lib.go +++ b/tools/walletextension/lib/client_lib.go @@ -9,8 +9,6 @@ import ( "net/http" "strings" - "github.com/ethereum/go-ethereum/accounts" - "github.com/ten-protocol/go-ten/integration" gethcommon "github.com/ethereum/go-ethereum/common" @@ -48,15 +46,11 @@ func (o *TGLib) Join() error { func (o *TGLib) RegisterAccount(pk *ecdsa.PrivateKey, addr gethcommon.Address) error { // create the registration message - rawMessageOptions, err := viewingkey.GenerateAuthenticationEIP712RawDataOptions(string(o.userID), integration.TenChainID) + messageHash, err := viewingkey.GenerateMessage(string(o.userID), integration.TenChainID, 1, viewingkey.EIP712Signature, true) if err != nil { return err } - if len(rawMessageOptions) == 0 { - return fmt.Errorf("GenerateAuthenticationEIP712RawDataOptions returned 0 options") - } - messageHash := crypto.Keccak256(rawMessageOptions[0]) sig, err := crypto.Sign(messageHash, pk) if err != nil { return fmt.Errorf("failed to sign message: %w", err) @@ -97,8 +91,10 @@ func (o *TGLib) RegisterAccount(pk *ecdsa.PrivateKey, addr gethcommon.Address) e func (o *TGLib) RegisterAccountPersonalSign(pk *ecdsa.PrivateKey, addr gethcommon.Address) error { // create the registration message - personalSignMessage := viewingkey.GeneratePersonalSignMessage(string(o.userID), integration.TenChainID, 1) - messageHash := accounts.TextHash([]byte(personalSignMessage)) + messageHash, err := viewingkey.GenerateMessage(string(o.userID), integration.TenChainID, viewingkey.PersonalSignMessageSupportedVersions[0], viewingkey.PersonalSign, true) + if err != nil { + return err + } sig, err := crypto.Sign(messageHash, pk) if err != nil { diff --git a/tools/walletextension/wallet_extension.go b/tools/walletextension/wallet_extension.go index 5fcc3d6b32..da0ccf8268 100644 --- a/tools/walletextension/wallet_extension.go +++ b/tools/walletextension/wallet_extension.go @@ -478,3 +478,19 @@ func (w *WalletExtension) Version() string { func (w *WalletExtension) GetTenNodeHealthStatus() (bool, error) { return w.tenClient.Health() } + +func (w *WalletExtension) GetMessage(encryptionToken string, formatsSlice []string) (string, error) { + // Check if the formats are valid + for _, format := range formatsSlice { + if _, exists := common.SignatureTypeMap[format]; !exists { + return "", fmt.Errorf("invalid format: %s", format) + } + } + + messageFormat := common.GetBestFormat(formatsSlice) + message, err := viewingkey.GenerateMessage(encryptionToken, int64(w.config.TenChainID), viewingkey.PersonalSignMessageSupportedVersions[0], messageFormat, false) + if err != nil { + return "", fmt.Errorf("error generating message: %w", err) + } + return string(message), nil +} From 744404b05f0f4f651efd86a22422835841cda368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDiga=20Kokelj?= Date: Mon, 18 Mar 2024 15:30:53 +0100 Subject: [PATCH 02/65] add message type in get messages --- tools/walletextension/api/routes.go | 27 +++++++++++++++++++++++++- tools/walletextension/common/common.go | 9 +++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/tools/walletextension/api/routes.go b/tools/walletextension/api/routes.go index fde9eb798a..24287f8d37 100644 --- a/tools/walletextension/api/routes.go +++ b/tools/walletextension/api/routes.go @@ -545,7 +545,32 @@ func getMessageRequestHandler(walletExt *walletextension.WalletExtension, conn u return } - err = conn.WriteResponse([]byte(message)) + // create the response structure + type JsonResponse struct { + Message string `json:"message"` + Type string `json:"type"` + } + + // get string representation of the message format + messageFormat := common.GetBestFormat(formatsSlice) + messageFormatString, err := common.GetSignatureTypeString(messageFormat) + if err != nil { + handleError(conn, walletExt.Logger(), fmt.Errorf("internal error")) + return + } + + response := JsonResponse{ + Message: message, + Type: messageFormatString, + } + + responseBytes, err := json.Marshal(response) + if err != nil { + walletExt.Logger().Error("error marshaling JSON response", log.ErrKey, err) + return + } + + err = conn.WriteResponse(responseBytes) if err != nil { walletExt.Logger().Error("error writing success response", log.ErrKey, err) } diff --git a/tools/walletextension/common/common.go b/tools/walletextension/common/common.go index db29535621..7f844d8ee7 100644 --- a/tools/walletextension/common/common.go +++ b/tools/walletextension/common/common.go @@ -110,3 +110,12 @@ func GetBestFormat(formatsSlice []string) viewingkey.SignatureType { // otherwise, choose EIP712 return viewingkey.EIP712Signature } + +func GetSignatureTypeString(expectedSignatureType viewingkey.SignatureType) (string, error) { + for key, value := range SignatureTypeMap { + if value == expectedSignatureType { + return key, nil + } + } + return "", fmt.Errorf("unable to find signature type") +} From 6aa6cb3410e8540e49b14ec475c697b0b95d901a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDiga=20Kokelj?= Date: Mon, 18 Mar 2024 15:32:28 +0100 Subject: [PATCH 03/65] fix test --- go/enclave/vkhandler/vk_handler_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/go/enclave/vkhandler/vk_handler_test.go b/go/enclave/vkhandler/vk_handler_test.go index 61368b7b1e..67f40f1672 100644 --- a/go/enclave/vkhandler/vk_handler_test.go +++ b/go/enclave/vkhandler/vk_handler_test.go @@ -48,11 +48,10 @@ func TestCheckSignature(t *testing.T) { userPrivKey, _, userID, userAddress := generateRandomUserKeys() // Generate all message types and create map with the corresponding signature type - message, err := viewingkey.GenerateMessage(userID, chainID, 0, viewingkey.EIP712Signature, true) + EIP712MessageHash, err := viewingkey.GenerateMessage(userID, chainID, 0, viewingkey.EIP712Signature, true) if err != nil { t.Fatalf(err.Error()) } - EIP712MessageHash := crypto.Keccak256(message) PersonalSignMessageHash, err := viewingkey.GenerateMessage(userID, chainID, viewingkey.PersonalSignMessageSupportedVersions[0], viewingkey.PersonalSign, true) if err != nil { t.Fatalf(err.Error()) From ab224225f7feac807f299c1e3e76d9903fb9ce62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDiga=20Kokelj?= Date: Mon, 18 Mar 2024 15:35:02 +0100 Subject: [PATCH 04/65] lint --- tools/walletextension/api/routes.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/walletextension/api/routes.go b/tools/walletextension/api/routes.go index 24287f8d37..1cad37dc97 100644 --- a/tools/walletextension/api/routes.go +++ b/tools/walletextension/api/routes.go @@ -546,7 +546,7 @@ func getMessageRequestHandler(walletExt *walletextension.WalletExtension, conn u } // create the response structure - type JsonResponse struct { + type JSONResponse struct { Message string `json:"message"` Type string `json:"type"` } @@ -559,7 +559,7 @@ func getMessageRequestHandler(walletExt *walletextension.WalletExtension, conn u return } - response := JsonResponse{ + response := JSONResponse{ Message: message, Type: messageFormatString, } From 4c1509178d1bd4f09e3ea947cb0014a5cd1c2432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDiga=20Kokelj?= Date: Mon, 18 Mar 2024 16:16:25 +0100 Subject: [PATCH 05/65] fix test --- go/enclave/vkhandler/vk_handler_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/go/enclave/vkhandler/vk_handler_test.go b/go/enclave/vkhandler/vk_handler_test.go index 67f40f1672..ef91a7bf12 100644 --- a/go/enclave/vkhandler/vk_handler_test.go +++ b/go/enclave/vkhandler/vk_handler_test.go @@ -87,11 +87,10 @@ func TestVerifyViewingKey(t *testing.T) { // Generate all message types and create map with the corresponding signature type // Test EIP712 message format - message, err := viewingkey.GenerateMessage(userID, chainID, viewingkey.PersonalSignMessageSupportedVersions[0], viewingkey.EIP712Signature, true) + EIP712MessageHash, err := viewingkey.GenerateMessage(userID, chainID, viewingkey.PersonalSignMessageSupportedVersions[0], viewingkey.EIP712Signature, true) if err != nil { t.Fatalf(err.Error()) } - EIP712MessageHash := crypto.Keccak256(message) PersonalSignMessageHash, err := viewingkey.GenerateMessage(userID, chainID, viewingkey.PersonalSignMessageSupportedVersions[0], viewingkey.PersonalSign, true) if err != nil { t.Fatalf(err.Error()) From c36ee46f7b9b96bdde817eacb5f3030e8a791927 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Tue, 19 Mar 2024 13:40:58 +0000 Subject: [PATCH 06/65] - return nil instead of custom error - align some methods with the geth rpc types --- go/common/gethencoding/geth_encoding.go | 60 ++++++-- go/enclave/l2chain/interfaces.go | 1 + go/enclave/l2chain/l1_blockchain.go | 7 +- go/enclave/rpc/EstimateGas.go | 3 +- go/enclave/rpc/GetBalance.go | 6 +- go/enclave/rpc/GetTransaction.go | 2 +- go/enclave/rpc/GetTransactionCount.go | 3 +- go/enclave/rpc/GetTransactionReceipt.go | 57 +++++++- go/enclave/rpc/TenEthCall.go | 11 +- go/enclave/storage/enclavedb/batch.go | 3 +- go/obsclient/authclient.go | 137 ++++++++++++------ go/rpc/client.go | 7 +- go/rpc/encrypted_client.go | 44 ++---- integration/common/utils.go | 6 +- .../networktest/userwallet/authclient.go | 4 +- integration/networktest/userwallet/gateway.go | 3 +- integration/tenscan/tenscan_test.go | 6 - 17 files changed, 226 insertions(+), 134 deletions(-) diff --git a/go/common/gethencoding/geth_encoding.go b/go/common/gethencoding/geth_encoding.go index be0d26d165..28f8937f7f 100644 --- a/go/common/gethencoding/geth_encoding.go +++ b/go/common/gethencoding/geth_encoding.go @@ -23,7 +23,6 @@ import ( "github.com/ten-protocol/go-ten/go/enclave/crypto" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ten-protocol/go-ten/go/common/errutil" "github.com/ten-protocol/go-ten/go/common/gethapi" gethcommon "github.com/ethereum/go-ethereum/common" @@ -145,33 +144,68 @@ func ExtractAddress(param interface{}) (*gethcommon.Address, error) { } // ExtractOptionalBlockNumber defaults nil or empty block number params to latest block number -func ExtractOptionalBlockNumber(params []interface{}, idx int) (*gethrpc.BlockNumber, error) { +func ExtractOptionalBlockNumber(params []interface{}, idx int) (*gethrpc.BlockNumberOrHash, error) { + latest := gethrpc.BlockNumberOrHashWithNumber(gethrpc.LatestBlockNumber) if len(params) <= idx { - return ExtractBlockNumber("latest") + return &latest, nil } if params[idx] == nil { - return ExtractBlockNumber("latest") + return &latest, nil } if emptyStr, ok := params[idx].(string); ok && len(strings.TrimSpace(emptyStr)) == 0 { - return ExtractBlockNumber("latest") + return &latest, nil } return ExtractBlockNumber(params[idx]) } -// ExtractBlockNumber returns a gethrpc.BlockNumber given an interface{}, errors if unexpected values are used -func ExtractBlockNumber(param interface{}) (*gethrpc.BlockNumber, error) { +func ExtractBlockNumber(param interface{}) (*gethrpc.BlockNumberOrHash, error) { if param == nil { - return nil, errutil.ErrNotFound + latest := gethrpc.BlockNumberOrHashWithNumber(gethrpc.LatestBlockNumber) + return &latest, nil } - blockNumber := gethrpc.BlockNumber(0) - err := blockNumber.UnmarshalJSON([]byte(param.(string))) - if err != nil { - return nil, fmt.Errorf("could not parse requested rollup number %s - %w", param.(string), err) + // when the param is a single string we try to convert it to a block number + blockString, ok := param.(string) + if ok { + blockNumber := gethrpc.BlockNumber(0) + err := blockNumber.UnmarshalJSON([]byte(blockString)) + if err != nil { + return nil, fmt.Errorf("invalid block number %s - %w", blockString, err) + } + return &gethrpc.BlockNumberOrHash{BlockNumber: &blockNumber}, nil + } + + var blockNo *gethrpc.BlockNumber + var blockHa *gethcommon.Hash + var reqCanon bool + + blockAndHash, ok := param.(map[string]any) + if !ok { + return nil, fmt.Errorf("invalid block or hash parameter %s", param.(string)) + } + if blockAndHash["blockNumber"] != nil { + b := blockAndHash["blockNumber"].(string) + blockNumber := gethrpc.BlockNumber(0) + err := blockNumber.UnmarshalJSON([]byte(b)) + if err != nil { + return nil, fmt.Errorf("invalid block number %s - %w", b, err) + } + blockNo = &blockNumber + } + if blockAndHash["blockHash"] != nil { + bh := blockAndHash["blockHash"].(gethcommon.Hash) + blockHa = &bh + } + if blockAndHash["RequireCanonical"] != nil { + reqCanon = blockAndHash["RequireCanonical"].(bool) } - return &blockNumber, err + return &gethrpc.BlockNumberOrHash{ + BlockNumber: blockNo, + BlockHash: blockHa, + RequireCanonical: reqCanon, + }, nil } // ExtractEthCall extracts the eth_call gethapi.TransactionArgs from an interface{} diff --git a/go/enclave/l2chain/interfaces.go b/go/enclave/l2chain/interfaces.go index 4e8e3e2d5d..9fb2bd2e4a 100644 --- a/go/enclave/l2chain/interfaces.go +++ b/go/enclave/l2chain/interfaces.go @@ -18,6 +18,7 @@ type ObscuroChain interface { // For EOA - the actual address. // For Contracts - the address of the deployer. // Note - this might be subject to change if we implement a more flexible mechanism + // todo - support BlockNumberOrHash AccountOwner(address gethcommon.Address, blockNumber *gethrpc.BlockNumber) (*gethcommon.Address, error) // GetBalanceAtBlock - will return the balance of a specific address at the specific given block number (batch number). diff --git a/go/enclave/l2chain/l1_blockchain.go b/go/enclave/l2chain/l1_blockchain.go index 99a917843f..5f2838030e 100644 --- a/go/enclave/l2chain/l1_blockchain.go +++ b/go/enclave/l2chain/l1_blockchain.go @@ -23,11 +23,8 @@ import ( ) const ( - gethDir = "geth" - chainDataDir = "chaindata" - chainDataAncientDir = "chaindata/ancient" - trieCacheDir = "triecache" - ethashDir = "ethash" + gethDir = "geth" + chainDataDir = "chaindata" // todo (#1471) - use a constant that makes sense outside of the simulation. dataDirRoot = "../.build/simulations/gethDataDir" ) diff --git a/go/enclave/rpc/EstimateGas.go b/go/enclave/rpc/EstimateGas.go index 2b6489d383..5686bb7e48 100644 --- a/go/enclave/rpc/EstimateGas.go +++ b/go/enclave/rpc/EstimateGas.go @@ -47,7 +47,8 @@ func EstimateGasValidate(reqParams []any, builder *CallBuilder[CallParamsWithBlo } builder.From = callMsg.From - builder.Param = &CallParamsWithBlock{callMsg, blockNumber} + // todo + builder.Param = &CallParamsWithBlock{callMsg, blockNumber.BlockNumber} return nil } diff --git a/go/enclave/rpc/GetBalance.go b/go/enclave/rpc/GetBalance.go index dc0ece0bd9..e1af2db73f 100644 --- a/go/enclave/rpc/GetBalance.go +++ b/go/enclave/rpc/GetBalance.go @@ -13,7 +13,7 @@ import ( type BalanceReq struct { Addr *common.Address - Block *rpc.BlockNumber + Block *rpc.BlockNumberOrHash } func GetBalanceValidate(reqParams []any, builder *CallBuilder[BalanceReq, hexutil.Big], _ *EncryptionManager) error { @@ -41,7 +41,7 @@ func GetBalanceValidate(reqParams []any, builder *CallBuilder[BalanceReq, hexuti } func GetBalanceExecute(builder *CallBuilder[BalanceReq, hexutil.Big], rpc *EncryptionManager) error { - acctOwner, err := rpc.chain.AccountOwner(*builder.Param.Addr, builder.Param.Block) + acctOwner, err := rpc.chain.AccountOwner(*builder.Param.Addr, builder.Param.Block.BlockNumber) if err != nil { return err } @@ -53,7 +53,7 @@ func GetBalanceExecute(builder *CallBuilder[BalanceReq, hexutil.Big], rpc *Encry return nil } - balance, err := rpc.chain.GetBalanceAtBlock(*builder.Param.Addr, builder.Param.Block) + balance, err := rpc.chain.GetBalanceAtBlock(*builder.Param.Addr, builder.Param.Block.BlockNumber) if err != nil { return fmt.Errorf("unable to get balance - %w", err) } diff --git a/go/enclave/rpc/GetTransaction.go b/go/enclave/rpc/GetTransaction.go index e859b24456..a06080985f 100644 --- a/go/enclave/rpc/GetTransaction.go +++ b/go/enclave/rpc/GetTransaction.go @@ -56,7 +56,7 @@ func GetTransactionExecute(builder *CallBuilder[gethcommon.Hash, RpcTransaction] // Unlike in the Geth impl, we hardcode the use of a London signer. // todo (#1553) - once the enclave's genesis.json is set, retrieve the signer type using `types.MakeSigner` signer := types.NewLondonSigner(tx.ChainId()) - rpcTx := newRPCTransaction(tx, blockHash, blockNumber, index, gethcommon.Big0, signer) + rpcTx := newRPCTransaction(tx, blockHash, blockNumber, index, rpc.config.BaseFee, signer) builder.ReturnValue = rpcTx return nil } diff --git a/go/enclave/rpc/GetTransactionCount.go b/go/enclave/rpc/GetTransactionCount.go index 1787ad3c66..80c57e4824 100644 --- a/go/enclave/rpc/GetTransactionCount.go +++ b/go/enclave/rpc/GetTransactionCount.go @@ -30,7 +30,8 @@ func GetTransactionCountValidate(reqParams []any, builder *CallBuilder[uint64, s return nil } - b, err := rpc.registry.GetBatchAtHeight(*tag) + // todo - support BlockNumberOrHash + b, err := rpc.registry.GetBatchAtHeight(*tag.BlockNumber) if err != nil { builder.Err = fmt.Errorf("cant retrieve batch for tag. Cause: %w", err) return nil diff --git a/go/enclave/rpc/GetTransactionReceipt.go b/go/enclave/rpc/GetTransactionReceipt.go index f4f48ebbcf..8a2342fcfc 100644 --- a/go/enclave/rpc/GetTransactionReceipt.go +++ b/go/enclave/rpc/GetTransactionReceipt.go @@ -3,6 +3,10 @@ package rpc import ( "errors" "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ten-protocol/go-ten/go/enclave/evm/ethchainadapter" "github.com/ten-protocol/go-ten/go/enclave/core" @@ -13,7 +17,7 @@ import ( "github.com/ten-protocol/go-ten/go/enclave/events" ) -func GetTransactionReceiptValidate(reqParams []any, builder *CallBuilder[gethcommon.Hash, types.Receipt], _ *EncryptionManager) error { +func GetTransactionReceiptValidate(reqParams []any, builder *CallBuilder[gethcommon.Hash, map[string]interface{}], _ *EncryptionManager) error { // Parameters are [Hash] if len(reqParams) < 1 { builder.Err = fmt.Errorf("unexpected number of parameters") @@ -30,12 +34,12 @@ func GetTransactionReceiptValidate(reqParams []any, builder *CallBuilder[gethcom return nil } -func GetTransactionReceiptExecute(builder *CallBuilder[gethcommon.Hash, types.Receipt], rpc *EncryptionManager) error { +func GetTransactionReceiptExecute(builder *CallBuilder[gethcommon.Hash, map[string]interface{}], rpc *EncryptionManager) error { txHash := *builder.Param // todo - optimise these calls. This can be done with a single sql rpc.logger.Trace("Get receipt for ", log.TxKey, txHash) // We retrieve the transaction. - tx, _, _, _, err := rpc.storage.GetTransaction(txHash) //nolint:dogsled + tx, blockHash, number, txIndex, err := rpc.storage.GetTransaction(txHash) //nolint:dogsled if err != nil { rpc.logger.Trace("error getting tx ", log.TxKey, txHash, log.ErrKey, err) if errors.Is(err, errutil.ErrNotFound) { @@ -78,6 +82,51 @@ func GetTransactionReceiptExecute(builder *CallBuilder[gethcommon.Hash, types.Re } rpc.logger.Trace("Successfully retrieved receipt for ", log.TxKey, txHash, "rec", txReceipt) - builder.ReturnValue = txReceipt + signer := types.MakeSigner(ethchainadapter.ChainParams(big.NewInt(rpc.config.ObscuroChainID)), big.NewInt(int64(number)), 0) + r := marshalReceipt(txReceipt, blockHash, number, signer, tx, int(txIndex)) + builder.ReturnValue = &r return nil } + +// marshalReceipt marshals a transaction receipt into a JSON object. +// taken from geth +func marshalReceipt(receipt *types.Receipt, blockHash gethcommon.Hash, blockNumber uint64, signer types.Signer, tx *types.Transaction, txIndex int) map[string]interface{} { + from, _ := types.Sender(signer, tx) + + fields := map[string]interface{}{ + "blockHash": blockHash, + "blockNumber": hexutil.Uint64(blockNumber), + "transactionHash": tx.Hash(), + "transactionIndex": hexutil.Uint64(txIndex), + "from": from, + "to": tx.To(), + "gasUsed": hexutil.Uint64(receipt.GasUsed), + "cumulativeGasUsed": hexutil.Uint64(receipt.CumulativeGasUsed), + "contractAddress": nil, + "logs": receipt.Logs, + "logsBloom": receipt.Bloom, + "type": hexutil.Uint(tx.Type()), + "effectiveGasPrice": (*hexutil.Big)(receipt.EffectiveGasPrice), + } + + // Assign receipt status or post state. + if len(receipt.PostState) > 0 { + fields["root"] = hexutil.Bytes(receipt.PostState) + } else { + fields["status"] = hexutil.Uint(receipt.Status) + } + if receipt.Logs == nil { + fields["logs"] = []*types.Log{} + } + + if tx.Type() == types.BlobTxType { + fields["blobGasUsed"] = hexutil.Uint64(receipt.BlobGasUsed) + fields["blobGasPrice"] = (*hexutil.Big)(receipt.BlobGasPrice) + } + + // If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation + if receipt.ContractAddress != (gethcommon.Address{}) { + fields["contractAddress"] = receipt.ContractAddress + } + return fields +} diff --git a/go/enclave/rpc/TenEthCall.go b/go/enclave/rpc/TenEthCall.go index 9e1565a139..8f96e7a878 100644 --- a/go/enclave/rpc/TenEthCall.go +++ b/go/enclave/rpc/TenEthCall.go @@ -13,8 +13,8 @@ import ( ) func TenCallValidate(reqParams []any, builder *CallBuilder[CallParamsWithBlock, string], _ *EncryptionManager) error { - // Parameters are [TransactionArgs, BlockNumber] - if len(reqParams) != 2 { + // Parameters are [TransactionArgs, BlockNumber, 2 more which we don't support yet] + if len(reqParams) < 2 && len(reqParams) > 4 { builder.Err = fmt.Errorf("unexpected number of parameters") return nil } @@ -36,7 +36,8 @@ func TenCallValidate(reqParams []any, builder *CallBuilder[CallParamsWithBlock, } builder.From = apiArgs.From - builder.Param = &CallParamsWithBlock{apiArgs, blkNumber} + // todo - support BlockNumberOrHash + builder.Param = &CallParamsWithBlock{apiArgs, blkNumber.BlockNumber} return nil } @@ -71,8 +72,10 @@ func TenCallExecute(builder *CallBuilder[CallParamsWithBlock, string], rpc *Encr var encodedResult string if len(execResult.ReturnData) != 0 { encodedResult = hexutil.Encode(execResult.ReturnData) + builder.ReturnValue = &encodedResult + } else { + builder.ReturnValue = nil } - builder.ReturnValue = &encodedResult return nil } diff --git a/go/enclave/storage/enclavedb/batch.go b/go/enclave/storage/enclavedb/batch.go index 20e565124d..e0ce4cac1b 100644 --- a/go/enclave/storage/enclavedb/batch.go +++ b/go/enclave/storage/enclavedb/batch.go @@ -395,7 +395,8 @@ func ReadReceipt(db *sql.DB, hash common.L2TxHash, config *params.ChainConfig) ( batchhash := common.L2BatchHash{} batchhash.SetBytes(batchHash) - if err = receipts.DeriveFields(config, batchhash, height, 0, big.NewInt(0), big.NewInt(0), transactions); err != nil { + // todo base fee + if err = receipts.DeriveFields(config, batchhash, height, 0, big.NewInt(1), big.NewInt(0), transactions); err != nil { return nil, fmt.Errorf("failed to derive block receipts fields. hash = %s; number = %d; err = %w", hash, height, err) } return receipts[0], nil diff --git a/go/obsclient/authclient.go b/go/obsclient/authclient.go index 2db0151f61..f417e3443f 100644 --- a/go/obsclient/authclient.go +++ b/go/obsclient/authclient.go @@ -2,6 +2,8 @@ package obsclient import ( "context" + "encoding/json" + "errors" "math/big" "github.com/ethereum/go-ethereum" @@ -19,14 +21,6 @@ import ( gethlog "github.com/ethereum/go-ethereum/log" ) -const ( - filterKeyBlockHash = "blockHash" - filterKeyFromBlock = "fromBlock" - filterKeyToBlock = "toBlock" - filterKeyAddress = "address" - filterKeyTopics = "topics" -) - // AuthObsClient extends the functionality of the ObsClient for all methods that require encryption when communicating with the enclave // It is created with an EncRPCClient rather than basic RPC client so encryption/decryption is supported // @@ -50,8 +44,7 @@ func NewAuthObsClient(client *rpc.EncRPCClient) *AuthObsClient { } // DialWithAuth will generate and sign a viewing key for given wallet, then initiate a connection with the RPC node and -// -// register the viewing key +// register the viewing key func DialWithAuth(rpcurl string, wal wallet.Wallet, logger gethlog.Logger) (*AuthObsClient, error) { viewingKey, err := viewingkey.GenerateViewingKeyForWallet(wal) if err != nil { @@ -66,15 +59,78 @@ func DialWithAuth(rpcurl string, wal wallet.Wallet, logger gethlog.Logger) (*Aut return authClient, nil } +type rpcTransaction struct { + tx *types.Transaction + txExtraInfo +} + +type txExtraInfo struct { + BlockNumber *string `json:"blockNumber,omitempty"` + BlockHash *gethcommon.Hash `json:"blockHash,omitempty"` + From *gethcommon.Address `json:"from,omitempty"` +} + +func (tx *rpcTransaction) UnmarshalJSON(msg []byte) error { + if err := json.Unmarshal(msg, &tx.tx); err != nil { + return err + } + return json.Unmarshal(msg, &tx.txExtraInfo) +} + // TransactionByHash returns transaction (if found), isPending (always false currently as we don't search the mempool), error -func (ac *AuthObsClient) TransactionByHash(ctx context.Context, hash gethcommon.Hash) (*types.Transaction, bool, error) { - var tx responses.TxType - err := ac.rpcClient.CallContext(ctx, &tx, rpc.GetTransactionByHash, hash.Hex()) +func (ac *AuthObsClient) TransactionByHash(ctx context.Context, hash gethcommon.Hash) (tx *types.Transaction, isPending bool, err error) { + var result *rpcTransaction + err = ac.rpcClient.CallContext(ctx, &result, "eth_getTransactionByHash", hash) if err != nil { return nil, false, err + } else if result == nil { + return nil, false, ethereum.NotFound + } else if _, r, _ := result.tx.RawSignatureValues(); r == nil { + return nil, false, errors.New("server returned transaction without signature") + } + if result.From != nil && result.BlockHash != nil { + setSenderFromServer(result.tx, *result.From, *result.BlockHash) } - // todo (#1491) - revisit isPending result value, included for ethclient equivalence but hardcoded currently - return &tx, false, nil + return result.tx, result.BlockNumber == nil, nil +} + +// senderFromServer is a types.Signer that remembers the sender address returned by the RPC +// server. It is stored in the transaction's sender address cache to avoid an additional +// request in TransactionSender. +type senderFromServer struct { + addr gethcommon.Address + blockhash gethcommon.Hash +} + +var errNotCached = errors.New("sender not cached") + +func setSenderFromServer(tx *types.Transaction, addr gethcommon.Address, block gethcommon.Hash) { + // Use types.Sender for side-effect to store our signer into the cache. + _, _ = types.Sender(&senderFromServer{addr, block}, tx) +} + +func (s *senderFromServer) Equal(other types.Signer) bool { + os, ok := other.(*senderFromServer) + return ok && os.blockhash == s.blockhash +} + +func (s *senderFromServer) Sender(_ *types.Transaction) (gethcommon.Address, error) { + if s.addr == (gethcommon.Address{}) { + return gethcommon.Address{}, errNotCached + } + return s.addr, nil +} + +func (s *senderFromServer) ChainID() *big.Int { + panic("can't sign with senderFromServer") +} + +func (s *senderFromServer) Hash(_ *types.Transaction) gethcommon.Hash { + panic("can't sign with senderFromServer") +} + +func (s *senderFromServer) SignatureValues(_ *types.Transaction, _ []byte) (R, S, V *big.Int, err error) { + panic("can't sign with senderFromServer") } func (ac *AuthObsClient) GasPrice(ctx context.Context) (*big.Int, error) { @@ -88,13 +144,14 @@ func (ac *AuthObsClient) GasPrice(ctx context.Context) (*big.Int, error) { } func (ac *AuthObsClient) TransactionReceipt(ctx context.Context, txHash gethcommon.Hash) (*types.Receipt, error) { - var result responses.ReceiptType - err := ac.rpcClient.CallContext(ctx, &result, rpc.GetTransactionReceipt, txHash) - if err != nil { - return nil, err + var r *types.Receipt + err := ac.rpcClient.CallContext(ctx, &r, "eth_getTransactionReceipt", txHash) + if err == nil { + if r == nil { + return nil, ethereum.NotFound + } } - - return &result, nil + return r, err } // NonceAt retrieves the nonce for the account registered on this client (due to obscuro privacy restrictions, @@ -110,13 +167,12 @@ func (ac *AuthObsClient) NonceAt(ctx context.Context, blockNumber *big.Int) (uin } func (ac *AuthObsClient) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { - var result responses.CallType - err := ac.rpcClient.CallContext(ctx, &result, rpc.Call, ToCallArg(msg), toBlockNumArg(blockNumber)) + var hex hexutil.Bytes + err := ac.rpcClient.CallContext(ctx, &hex, "eth_call", ToCallArg(msg), toBlockNumArg(blockNumber)) if err != nil { return nil, err } - - return []byte(result), nil + return hex, nil } func (ac *AuthObsClient) SendTransaction(ctx context.Context, signedTx *types.Transaction) error { @@ -125,31 +181,19 @@ func (ac *AuthObsClient) SendTransaction(ctx context.Context, signedTx *types.Tr if err != nil { return err } - return nil } // BalanceAt retrieves the native balance for the account registered on this client (due to obscuro privacy restrictions, // balance cannot be requested for other accounts) func (ac *AuthObsClient) BalanceAt(ctx context.Context, blockNumber *big.Int) (*big.Int, error) { - var result responses.BalanceType - err := ac.rpcClient.CallContext(ctx, &result, rpc.GetBalance, ac.account, toBlockNumArg(blockNumber)) - if err != nil { - return big.NewInt(0), err - } - - return result.ToInt(), nil + var result hexutil.Big + err := ac.rpcClient.CallContext(ctx, &result, "eth_getBalance", ac.account, toBlockNumArg(blockNumber)) + return (*big.Int)(&result), err } func (ac *AuthObsClient) SubscribeFilterLogs(ctx context.Context, filterCriteria filters.FilterCriteria, ch chan common.IDAndLog) (ethereum.Subscription, error) { - filterCriteriaMap := map[string]interface{}{ - filterKeyBlockHash: filterCriteria.BlockHash, - filterKeyFromBlock: filterCriteria.FromBlock, - filterKeyToBlock: filterCriteria.ToBlock, - filterKeyAddress: filterCriteria.Addresses, - filterKeyTopics: filterCriteria.Topics, - } - return ac.rpcClient.Subscribe(ctx, nil, rpc.SubscribeNamespace, ch, rpc.SubscriptionTypeLogs, filterCriteriaMap) + return ac.rpcClient.Subscribe(ctx, nil, rpc.SubscribeNamespace, ch, rpc.SubscriptionTypeLogs, filterCriteria) } func (ac *AuthObsClient) GetLogs(ctx context.Context, filterCriteria common.FilterCriteriaJSON) ([]*types.Log, error) { @@ -166,20 +210,19 @@ func (ac *AuthObsClient) Address() gethcommon.Address { return ac.account } -func (ac *AuthObsClient) EstimateGas(ctx context.Context, msg *ethereum.CallMsg) (uint64, error) { - var result responses.GasType - err := ac.rpcClient.CallContext(ctx, &result, rpc.EstimateGas, ToCallArg(*msg)) +func (ac *AuthObsClient) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) { + var hex hexutil.Uint64 + err := ac.rpcClient.CallContext(ctx, &hex, "eth_estimateGas", ToCallArg(msg)) if err != nil { return 0, err } - - return hexutil.DecodeUint64(result.String()) + return uint64(hex), nil } func (ac *AuthObsClient) EstimateGasAndGasPrice(txData types.TxData) types.TxData { unEstimatedTx := types.NewTx(txData) - gasLimit, err := ac.EstimateGas(context.Background(), ðereum.CallMsg{ + gasLimit, err := ac.EstimateGas(context.Background(), ethereum.CallMsg{ From: ac.Address(), To: unEstimatedTx.To(), Value: unEstimatedTx.Value(), diff --git a/go/rpc/client.go b/go/rpc/client.go index 81bea5c0bb..2021597d44 100644 --- a/go/rpc/client.go +++ b/go/rpc/client.go @@ -2,7 +2,6 @@ package rpc import ( "context" - "errors" "github.com/ethereum/go-ethereum/rpc" ) @@ -51,11 +50,9 @@ const ( GetFullBatchByHash = "scan_getBatchByHash" ) -var ErrNilResponse = errors.New("nil response received from Obscuro node") - -// Client is used by client applications to interact with the Obscuro node +// Client is used by client applications to interact with the Ten node type Client interface { - // Call executes the named method via RPC. (Returns `ErrNilResponse` on nil response from Node, this is used as "not found" for some method calls) + // Call executes the named method via RPC. Call(result interface{}, method string, args ...interface{}) error // CallContext If the context is canceled before the call has successfully returned, CallContext returns immediately. CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error diff --git a/go/rpc/encrypted_client.go b/go/rpc/encrypted_client.go index 2412d8af50..8c74149076 100644 --- a/go/rpc/encrypted_client.go +++ b/go/rpc/encrypted_client.go @@ -28,7 +28,6 @@ import ( const ( // todo: this is a convenience for testnet testing and will eventually be retrieved from the L1 enclavePublicKeyHex = "034d3b7e63a8bcd532ee3d1d6ecad9d67fca7821981a044551f0f0cbec74d0bc5e" - emptyFilterCriteria = "[]" // This is the value that gets passed for an empty filter criteria. ) // SensitiveMethods for which the RPC requests and responses should be encrypted @@ -81,7 +80,11 @@ func (c *EncRPCClient) Call(result interface{}, method string, args ...interface // - result must be a pointer so that package json can unmarshal into it. You can also pass nil, in which case the result is ignored. // - callExec handles the delegated call, allows EncClient to use the same code for calling with or without a context func (c *EncRPCClient) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { - assertResultIsPointer(result) + // borrowed from geth + if result != nil && reflect.TypeOf(result).Kind() != reflect.Ptr { + return fmt.Errorf("call result parameter must be pointer or nil interface: %v", result) + } + if !IsSensitiveMethod(method) { // for non-sensitive methods or when viewing keys are disabled we just delegate directly to the geth RPC client return c.executeRPCCall(ctx, result, method, args...) @@ -182,23 +185,10 @@ func (c *EncRPCClient) createAuthenticatedLogSubscription(args []interface{}) (* return logSubscription, nil } - // TODO - Consider switching to using the common.FilterCriteriaJSON type. Should allow us to avoid RLP serialisation. - // We marshal the filter criteria from a map to JSON, then back from JSON into a FilterCriteria. This is - // because the filter criteria arrives as a map, and there is no way to convert it from a map directly into a - // FilterCriteria. - filterCriteriaJSON, err := json.Marshal(args[1]) - if err != nil { - return nil, fmt.Errorf("could not marshal filter criteria to JSON. Cause: %w", err) - } - - filterCriteria := filters.FilterCriteria{} - if string(filterCriteriaJSON) != emptyFilterCriteria { - err = filterCriteria.UnmarshalJSON(filterCriteriaJSON) - if err != nil { - return nil, fmt.Errorf("could not unmarshal filter criteria from the following JSON: `%s`. Cause: %w", string(filterCriteriaJSON), err) - } + filterCriteria, ok := args[1].(filters.FilterCriteria) + if !ok { + return nil, fmt.Errorf("invalid subscription") } - // If we do not override a nil block hash to an empty one, RLP decoding will fail on the enclave side. if filterCriteria.BlockHash == nil { filterCriteria.BlockHash = &gethcommon.Hash{} @@ -235,7 +225,7 @@ func (c *EncRPCClient) executeSensitiveCall(ctx context.Context, result interfac // If there is no encrypted response then this is equivalent to nil response if rawResult.EncUserResponse == nil || len(rawResult.EncUserResponse) == 0 { - return ErrNilResponse + return nil } // We decrypt the user response from the enclave response. @@ -344,19 +334,3 @@ func IsSensitiveMethod(method string) bool { } return false } - -func assertResultIsPointer(result interface{}) { - // result MUST be an initialized pointer else call won't be able to return it - if result != nil { - // todo: replace these panics with an error for invalid usage (same behaviour as json.Unmarshal()) - if reflect.ValueOf(result).Kind() != reflect.Ptr { - // we panic if result is not a pointer, this is a coding mistake and we want to fail fast during development - panic("result MUST be a pointer else Call cannot populate it") - } - if reflect.ValueOf(result).IsNil() { - // we panic if result is a nil pointer, cannot unmarshal json to it. Pointer must be initialized. - // if you see this then the calling code probably used: `var resObj *ResType` instead of: `var resObj ResType` - panic("result pointer must be initialized else Call cannot populate it") - } - } -} diff --git a/integration/common/utils.go b/integration/common/utils.go index f0ff1839ad..cc530c65ec 100644 --- a/integration/common/utils.go +++ b/integration/common/utils.go @@ -9,6 +9,8 @@ import ( "sync" "time" + "github.com/ethereum/go-ethereum" + "github.com/ten-protocol/go-ten/integration/common/testlog" "github.com/ethereum/go-ethereum/accounts/abi" @@ -23,7 +25,6 @@ import ( gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "github.com/ten-protocol/go-ten/go/rpc" ) var _awaitReceiptPollingInterval = 200 * time.Millisecond @@ -49,8 +50,7 @@ func AwaitReceipt(ctx context.Context, client *obsclient.AuthObsClient, txHash g var err error err = retry.Do(func() error { receipt, err = client.TransactionReceipt(ctx, txHash) - if err != nil && !errors.Is(err, rpc.ErrNilResponse) { - // we only retry for a nil "not found" response. This is a different error, so we bail out of the retry loop + if err != nil && !errors.Is(err, ethereum.NotFound) { return retry.FailFast(err) } return err diff --git a/integration/networktest/userwallet/authclient.go b/integration/networktest/userwallet/authclient.go index 3a22bfa763..ace6e89ec0 100644 --- a/integration/networktest/userwallet/authclient.go +++ b/integration/networktest/userwallet/authclient.go @@ -12,7 +12,6 @@ import ( gethlog "github.com/ethereum/go-ethereum/log" "github.com/ten-protocol/go-ten/go/common/retry" "github.com/ten-protocol/go-ten/go/obsclient" - "github.com/ten-protocol/go-ten/go/rpc" "github.com/ten-protocol/go-ten/go/wallet" ) @@ -82,8 +81,7 @@ func (s *AuthClientUser) AwaitReceipt(ctx context.Context, txHash *gethcommon.Ha var err error err = retry.Do(func() error { receipt, err = s.client.TransactionReceipt(ctx, *txHash) - if !errors.Is(err, rpc.ErrNilResponse) { - // nil response means not found. Any other error is unexpected, so we stop polling and fail immediately + if err != nil && !errors.Is(err, ethereum.NotFound) { return retry.FailFast(err) } return err diff --git a/integration/networktest/userwallet/gateway.go b/integration/networktest/userwallet/gateway.go index a6ca793191..0853595489 100644 --- a/integration/networktest/userwallet/gateway.go +++ b/integration/networktest/userwallet/gateway.go @@ -13,7 +13,6 @@ import ( "github.com/ethereum/go-ethereum/ethclient" gethlog "github.com/ethereum/go-ethereum/log" "github.com/ten-protocol/go-ten/go/common/retry" - "github.com/ten-protocol/go-ten/go/rpc" "github.com/ten-protocol/go-ten/go/wallet" "github.com/ten-protocol/go-ten/tools/walletextension/lib" ) @@ -95,7 +94,7 @@ func (g *GatewayUser) AwaitReceipt(ctx context.Context, txHash *gethcommon.Hash) var err error err = retry.Do(func() error { receipt, err = g.client.TransactionReceipt(ctx, *txHash) - if !errors.Is(err, rpc.ErrNilResponse) { + if err != nil && !errors.Is(err, ethereum.NotFound) { return retry.FailFast(err) } return err diff --git a/integration/tenscan/tenscan_test.go b/integration/tenscan/tenscan_test.go index b9b82e1c29..5a9030403c 100644 --- a/integration/tenscan/tenscan_test.go +++ b/integration/tenscan/tenscan_test.go @@ -279,12 +279,6 @@ func issueTransactions(t *testing.T, hostWSAddr string, issuerWallet wallet.Wall if err == nil { break } - // - // Currently when a receipt is not available the obscuro node is returning nil instead of err ethereum.NotFound - // once that's fixed this commented block should be removed - //if !errors.Is(err, ethereum.NotFound) { - // t.Fatal(err) - //} if receipt != nil && receipt.Status == 1 { break } From 90b58cccedbae0e9e0a55efe9e5756e59de37185 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Tue, 19 Mar 2024 13:59:30 +0000 Subject: [PATCH 07/65] fix --- tools/faucet/faucet/faucet.go | 4 +--- tools/walletextension/accountmanager/account_manager.go | 3 +-- tools/walletextension/wallet_extension.go | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/tools/faucet/faucet/faucet.go b/tools/faucet/faucet/faucet.go index 4efb7c6132..32508550fa 100644 --- a/tools/faucet/faucet/faucet.go +++ b/tools/faucet/faucet/faucet.go @@ -3,7 +3,6 @@ package faucet import ( "context" "encoding/json" - "errors" "fmt" "math/big" "os" @@ -15,7 +14,6 @@ import ( "github.com/ethereum/go-ethereum/log" tenlog "github.com/ten-protocol/go-ten/go/common/log" "github.com/ten-protocol/go-ten/go/obsclient" - "github.com/ten-protocol/go-ten/go/rpc" "github.com/ten-protocol/go-ten/go/wallet" ) @@ -85,7 +83,7 @@ func (f *Faucet) validateTx(tx *types.Transaction) error { for now := time.Now(); time.Since(now) < _timeout; time.Sleep(time.Second) { receipt, err := f.client.TransactionReceipt(context.Background(), tx.Hash()) if err != nil { - if errors.Is(err, rpc.ErrNilResponse) { + if receipt == nil { // tx receipt is not available yet continue } diff --git a/tools/walletextension/accountmanager/account_manager.go b/tools/walletextension/accountmanager/account_manager.go index 126adc134e..67aefc9931 100644 --- a/tools/walletextension/accountmanager/account_manager.go +++ b/tools/walletextension/accountmanager/account_manager.go @@ -3,7 +3,6 @@ package accountmanager import ( "bytes" "encoding/json" - "errors" "fmt" "strings" "sync" @@ -226,7 +225,7 @@ func (m *AccountManager) executeCall(rpcReq *wecommon.RPCRequest, rpcResp *inter var err error for _, client := range m.accountClientsHTTP { err = submitCall(client, rpcReq, rpcResp) - if err == nil || errors.Is(err, rpc.ErrNilResponse) { + if err == nil { // request didn't fail, we don't need to continue trying the other clients return nil } diff --git a/tools/walletextension/wallet_extension.go b/tools/walletextension/wallet_extension.go index 5fcc3d6b32..73e8375f1c 100644 --- a/tools/walletextension/wallet_extension.go +++ b/tools/walletextension/wallet_extension.go @@ -165,7 +165,7 @@ func (w *WalletExtension) ProxyEthRequest(request *common.RPCRequest, conn userc err = selectedAccountManager.ProxyRequest(request, &rpcResp, conn) if err != nil { - if errors.Is(err, rpc.ErrNilResponse) { + if rpcResp == nil { // if err was for a nil response then we will return an RPC result of null to the caller (this is a valid "not-found" response for some methods) response[common.JSONKeyResult] = nil requestEndTime := time.Now() From bbc441dca98a8de9d0801233c26e22b271850e2e Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Tue, 19 Mar 2024 14:03:22 +0000 Subject: [PATCH 08/65] fix --- integration/networktest/userwallet/authclient.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/integration/networktest/userwallet/authclient.go b/integration/networktest/userwallet/authclient.go index ace6e89ec0..66d82307e5 100644 --- a/integration/networktest/userwallet/authclient.go +++ b/integration/networktest/userwallet/authclient.go @@ -1,9 +1,10 @@ -package userwallet +package userwallet //nolint:typecheck import ( "context" "errors" "fmt" + "github.com/ethereum/go-ethereum" "math/big" "time" From 50d6d0b6684ec6848d5472bb63668b0676bfae50 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Tue, 19 Mar 2024 14:05:11 +0000 Subject: [PATCH 09/65] lint --- integration/networktest/userwallet/authclient.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/integration/networktest/userwallet/authclient.go b/integration/networktest/userwallet/authclient.go index 66d82307e5..fba2bd7e88 100644 --- a/integration/networktest/userwallet/authclient.go +++ b/integration/networktest/userwallet/authclient.go @@ -4,10 +4,11 @@ import ( "context" "errors" "fmt" - "github.com/ethereum/go-ethereum" "math/big" "time" + "github.com/ethereum/go-ethereum" + gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" gethlog "github.com/ethereum/go-ethereum/log" From 3fa13068fa2c5bf31aab1a9edef1972bfac06346 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Tue, 19 Mar 2024 16:05:45 +0000 Subject: [PATCH 10/65] fix --- .../accountmanager/account_manager.go | 11 ++++++++++- .../subscriptions/subscriptions.go | 14 ++++++-------- tools/walletextension/wallet_extension.go | 17 ++++++----------- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/tools/walletextension/accountmanager/account_manager.go b/tools/walletextension/accountmanager/account_manager.go index 67aefc9931..a4bad822cf 100644 --- a/tools/walletextension/accountmanager/account_manager.go +++ b/tools/walletextension/accountmanager/account_manager.go @@ -89,7 +89,16 @@ func (m *AccountManager) ProxyRequest(rpcReq *wecommon.RPCRequest, rpcResp *inte if err != nil { return err } - err = m.subscriptionsManager.HandleNewSubscriptions(clients, rpcReq, rpcResp, userConn) + critBytes, err := json.Marshal(rpcReq.Params[1]) + if err != nil { + return err + } + criteria := new(filters.FilterCriteria) + err = criteria.UnmarshalJSON(critBytes) + if err != nil { + return err + } + err = m.subscriptionsManager.HandleNewSubscriptions(clients, *criteria, rpcResp, userConn) if err != nil { m.logger.Error("Error subscribing to multiple clients") return err diff --git a/tools/walletextension/subscriptions/subscriptions.go b/tools/walletextension/subscriptions/subscriptions.go index 7d3fe3f7a4..9405a524ce 100644 --- a/tools/walletextension/subscriptions/subscriptions.go +++ b/tools/walletextension/subscriptions/subscriptions.go @@ -7,6 +7,8 @@ import ( "sync" "time" + "github.com/ethereum/go-ethereum/eth/filters" + "github.com/go-kit/kit/transport/http/jsonrpc" gethlog "github.com/ethereum/go-ethereum/log" @@ -33,12 +35,8 @@ func New(logger gethlog.Logger) *SubscriptionManager { // HandleNewSubscriptions subscribes to an event with all the clients provided. // Doing this is necessary because we have relevancy rule, and we want to subscribe sometimes with all clients to get all the events -func (sm *SubscriptionManager) HandleNewSubscriptions(clients []rpc.Client, req *wecommon.RPCRequest, resp *interface{}, userConn userconn.UserConn) error { - if len(req.Params) == 0 { - return fmt.Errorf("could not subscribe as no subscription namespace was provided") - } - - sm.logger.Info(fmt.Sprintf("Subscribing to event %s with %d clients", req.Params, len(clients))) +func (sm *SubscriptionManager) HandleNewSubscriptions(clients []rpc.Client, criteria filters.FilterCriteria, resp *interface{}, userConn userconn.UserConn) error { + sm.logger.Info(fmt.Sprintf("Subscribing to event %s with %d clients", criteria, len(clients))) // create subscriptionID which will enable user to unsubscribe from all subscriptions userSubscriptionID := gethrpc.NewID() @@ -51,9 +49,9 @@ func (sm *SubscriptionManager) HandleNewSubscriptions(clients []rpc.Client, req // iterate over all clients and subscribe for each of them for _, client := range clients { - subscription, err := client.Subscribe(context.Background(), resp, rpc.SubscribeNamespace, funnelMultipleAccountsChan, req.Params...) + subscription, err := client.Subscribe(context.Background(), resp, rpc.SubscribeNamespace, funnelMultipleAccountsChan, rpc.SubscriptionTypeLogs, criteria) if err != nil { - return fmt.Errorf("could not call %s with params %v. Cause: %w", req.Method, req.Params, err) + return fmt.Errorf("could not subscrbie for logs with params %v. Cause: %w", criteria, err) } sm.UpdateSubscriptionMapping(string(userSubscriptionID), subscription) diff --git a/tools/walletextension/wallet_extension.go b/tools/walletextension/wallet_extension.go index 73e8375f1c..cf0ee4da57 100644 --- a/tools/walletextension/wallet_extension.go +++ b/tools/walletextension/wallet_extension.go @@ -165,22 +165,17 @@ func (w *WalletExtension) ProxyEthRequest(request *common.RPCRequest, conn userc err = selectedAccountManager.ProxyRequest(request, &rpcResp, conn) if err != nil { - if rpcResp == nil { - // if err was for a nil response then we will return an RPC result of null to the caller (this is a valid "not-found" response for some methods) - response[common.JSONKeyResult] = nil - requestEndTime := time.Now() - duration := requestEndTime.Sub(requestStartTime) - w.fileLogger.Info(fmt.Sprintf("Request method: %s, request params: %s, encryptionToken of sender: %s, response: %s, duration: %d ", request.Method, request.Params, hexUserID, response, duration.Milliseconds())) - return response, nil - } return nil, err } response[common.JSONKeyResult] = rpcResp - // todo (@ziga) - fix this upstream on the decode - // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-658.md - adjustStateRoot(rpcResp, response) + if rpcResp != nil { + // todo (@ziga) - fix this upstream on the decode + // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-658.md + adjustStateRoot(rpcResp, response) + } + requestEndTime := time.Now() duration := requestEndTime.Sub(requestStartTime) w.fileLogger.Info(fmt.Sprintf("Request method: %s, request params: %s, encryptionToken of sender: %s, response: %s, duration: %d ", request.Method, request.Params, hexUserID, response, duration.Milliseconds())) From 324b4e0e95454263ddb3036f33e9d03616f07cac Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Tue, 19 Mar 2024 17:13:26 +0000 Subject: [PATCH 11/65] add geth RPC --- .../obscurogateway}/json.go | 2 +- integration/obscurogateway/tengateway_test.go | 75 ++- .../accountmanager/account_manager.go | 442 ---------------- .../accountmanager/account_manager_test.go | 67 --- tools/walletextension/api/server.go | 86 ---- tools/walletextension/api/static/favicon.ico | Bin 1260 -> 0 bytes tools/walletextension/cache/RistrettoCache.go | 38 +- tools/walletextension/cache/cache.go | 99 +--- tools/walletextension/cache/cache_test.go | 249 --------- .../{config => common}/config.go | 2 +- tools/walletextension/common/constants.go | 29 +- .../common/{types.go => db_types.go} | 0 tools/walletextension/common/responses.go | 29 -- .../container/walletextension_container.go | 209 -------- .../components/providers/wallet-provider.tsx | 6 +- .../frontend/src/lib/constants.ts | 2 +- .../frontend/src/services/ethService.ts | 17 - tools/walletextension/httpapi/README.MD | 1 + .../{api => httpapi}/routes.go | 142 +----- tools/walletextension/httpapi/user_conn.go | 72 +++ .../walletextension/{api => httpapi}/utils.go | 86 +--- tools/walletextension/main/cli.go | 6 +- tools/walletextension/main/main.go | 5 +- .../walletextension/rpcapi/blockchain_api.go | 265 ++++++++++ tools/walletextension/rpcapi/debug_api.go | 62 +++ .../deduplication_circular_buffer.go | 2 +- tools/walletextension/rpcapi/ethereum_api.go | 60 +++ tools/walletextension/rpcapi/filter_api.go | 249 +++++++++ tools/walletextension/rpcapi/from_tx_args.go | 75 +++ tools/walletextension/rpcapi/gw_user.go | 65 +++ tools/walletextension/rpcapi/net_api.go | 25 + .../walletextension/rpcapi/transaction_api.go | 152 ++++++ tools/walletextension/rpcapi/txpool_api.go | 45 ++ tools/walletextension/rpcapi/utils.go | 228 +++++++++ .../rpcapi/wallet_extension.go | 303 +++++++++++ .../subscriptions/subscriptions.go | 202 -------- tools/walletextension/test/apis.go | 5 +- tools/walletextension/test/utils.go | 17 +- .../test/wallet_extension_test.go | 316 ------------ .../user_account_manager.go | 135 ----- .../user_account_manager_test.go | 76 --- tools/walletextension/userconn/user_conn.go | 147 ------ tools/walletextension/wallet_extension.go | 480 ------------------ .../walletextension_container.go | 128 +++++ 44 files changed, 1850 insertions(+), 2851 deletions(-) rename {tools/walletextension/common => integration/obscurogateway}/json.go (97%) delete mode 100644 tools/walletextension/accountmanager/account_manager.go delete mode 100644 tools/walletextension/accountmanager/account_manager_test.go delete mode 100644 tools/walletextension/api/server.go delete mode 100644 tools/walletextension/api/static/favicon.ico delete mode 100644 tools/walletextension/cache/cache_test.go rename tools/walletextension/{config => common}/config.go (97%) rename tools/walletextension/common/{types.go => db_types.go} (100%) delete mode 100644 tools/walletextension/common/responses.go delete mode 100644 tools/walletextension/container/walletextension_container.go create mode 100644 tools/walletextension/httpapi/README.MD rename tools/walletextension/{api => httpapi}/routes.go (68%) create mode 100644 tools/walletextension/httpapi/user_conn.go rename tools/walletextension/{api => httpapi}/utils.go (52%) create mode 100644 tools/walletextension/rpcapi/blockchain_api.go create mode 100644 tools/walletextension/rpcapi/debug_api.go rename tools/walletextension/{subscriptions => rpcapi}/deduplication_circular_buffer.go (98%) create mode 100644 tools/walletextension/rpcapi/ethereum_api.go create mode 100644 tools/walletextension/rpcapi/filter_api.go create mode 100644 tools/walletextension/rpcapi/from_tx_args.go create mode 100644 tools/walletextension/rpcapi/gw_user.go create mode 100644 tools/walletextension/rpcapi/net_api.go create mode 100644 tools/walletextension/rpcapi/transaction_api.go create mode 100644 tools/walletextension/rpcapi/txpool_api.go create mode 100644 tools/walletextension/rpcapi/utils.go create mode 100644 tools/walletextension/rpcapi/wallet_extension.go delete mode 100644 tools/walletextension/subscriptions/subscriptions.go delete mode 100644 tools/walletextension/test/wallet_extension_test.go delete mode 100644 tools/walletextension/useraccountmanager/user_account_manager.go delete mode 100644 tools/walletextension/useraccountmanager/user_account_manager_test.go delete mode 100644 tools/walletextension/userconn/user_conn.go delete mode 100644 tools/walletextension/wallet_extension.go create mode 100644 tools/walletextension/walletextension_container.go diff --git a/tools/walletextension/common/json.go b/integration/obscurogateway/json.go similarity index 97% rename from tools/walletextension/common/json.go rename to integration/obscurogateway/json.go index 2079aef3bc..3bccea5a6b 100644 --- a/tools/walletextension/common/json.go +++ b/integration/obscurogateway/json.go @@ -1,4 +1,4 @@ -package common +package faucet import ( "encoding/json" diff --git a/integration/obscurogateway/tengateway_test.go b/integration/obscurogateway/tengateway_test.go index 774a2a4d19..1046588a19 100644 --- a/integration/obscurogateway/tengateway_test.go +++ b/integration/obscurogateway/tengateway_test.go @@ -11,6 +11,8 @@ import ( "testing" "time" + "github.com/ten-protocol/go-ten/tools/walletextension" + log2 "github.com/ten-protocol/go-ten/go/common/log" "github.com/ethereum/go-ethereum" @@ -34,8 +36,6 @@ import ( "github.com/ten-protocol/go-ten/integration/ethereummock" "github.com/ten-protocol/go-ten/integration/simulation/network" "github.com/ten-protocol/go-ten/integration/simulation/params" - "github.com/ten-protocol/go-ten/tools/walletextension/config" - "github.com/ten-protocol/go-ten/tools/walletextension/container" "github.com/ten-protocol/go-ten/tools/walletextension/lib" "github.com/valyala/fasthttp" ) @@ -57,7 +57,7 @@ func TestTenGateway(t *testing.T) { startPort := integration.StartPortTenGatewayUnitTest createTenNetwork(t, startPort) - tenGatewayConf := config.Config{ + tenGatewayConf := wecommon.Config{ WalletExtensionHost: "127.0.0.1", WalletExtensionPortHTTP: startPort + integration.DefaultTenGatewayHTTPPortOffset, WalletExtensionPortWS: startPort + integration.DefaultTenGatewayWSPortOffset, @@ -70,7 +70,7 @@ func TestTenGateway(t *testing.T) { StoreIncomingTxs: true, } - tenGwContainer := container.NewWalletExtensionContainerFromConfig(tenGatewayConf, testlog.Logger()) + tenGwContainer := walletextension.NewContainerFromConfig(tenGatewayConf, testlog.Logger()) go func() { err := tenGwContainer.Start() if err != nil { @@ -120,6 +120,9 @@ func testMultipleAccountsSubscription(t *testing.T, httpURL, wsURL string, w wal require.NoError(t, err) testlog.Logger().Info("Created user with encryption token", "t", user0.tgClient.UserID()) + _, err = user0.HTTPClient.ChainID(context.Background()) + require.NoError(t, err) + user1, err := NewUser([]wallet.Wallet{datagenerator.RandomWallet(integration.TenChainID), datagenerator.RandomWallet(integration.TenChainID)}, httpURL, wsURL) require.NoError(t, err) testlog.Logger().Info("Created user with encryption token", "t", user1.tgClient.UserID()) @@ -204,9 +207,12 @@ func testMultipleAccountsSubscription(t *testing.T, httpURL, wsURL string, w wal var user0logs []types.Log var user1logs []types.Log var user2logs []types.Log - subscribeToEvents([]gethcommon.Address{contractReceipt.ContractAddress}, nil, user0.WSClient, &user0logs) - subscribeToEvents([]gethcommon.Address{contractReceipt.ContractAddress}, nil, user1.WSClient, &user1logs) - subscribeToEvents([]gethcommon.Address{contractReceipt.ContractAddress}, nil, user2.WSClient, &user2logs) + _, err = subscribeToEvents([]gethcommon.Address{contractReceipt.ContractAddress}, nil, user0.WSClient, &user0logs) + require.NoError(t, err) + _, err = subscribeToEvents([]gethcommon.Address{contractReceipt.ContractAddress}, nil, user1.WSClient, &user1logs) + require.NoError(t, err) + _, err = subscribeToEvents([]gethcommon.Address{contractReceipt.ContractAddress}, nil, user2.WSClient, &user2logs) + require.NoError(t, err) // user1 calls setMessage and setMessage2 on deployed smart contract with the account // that was registered as the first in TG @@ -313,6 +319,12 @@ func testSubscriptionTopics(t *testing.T, httpURL, wsURL string, w wallet.Wallet contractReceipt, err := integrationCommon.AwaitReceiptEth(context.Background(), user0.HTTPClient, signedTx.Hash(), time.Minute) require.NoError(t, err) + tx, _, err := user0.HTTPClient.TransactionByHash(context.Background(), signedTx.Hash()) + if err != nil { + return + } + require.Equal(t, signedTx.Hash(), tx.Hash()) + // user0 subscribes to all events from that smart contract, user1 only an event with a topic of his first account var user0logs []types.Log var user1logs []types.Log @@ -320,8 +332,10 @@ func testSubscriptionTopics(t *testing.T, httpURL, wsURL string, w wallet.Wallet t1 := gethcommon.BytesToHash(user1.Wallets[1].Address().Bytes()) topics = append(topics, nil) topics = append(topics, []gethcommon.Hash{t1}) - subscribeToEvents([]gethcommon.Address{contractReceipt.ContractAddress}, nil, user0.WSClient, &user0logs) - subscribeToEvents([]gethcommon.Address{contractReceipt.ContractAddress}, topics, user1.WSClient, &user1logs) + _, err = subscribeToEvents([]gethcommon.Address{contractReceipt.ContractAddress}, nil, user0.WSClient, &user0logs) + require.NoError(t, err) + _, err = subscribeToEvents([]gethcommon.Address{contractReceipt.ContractAddress}, topics, user1.WSClient, &user1logs) + require.NoError(t, err) // user0 calls setMessage on deployed smart contract with the account twice and expects two events _, err = integrationCommon.InteractWithSmartContract(user0.HTTPClient, user0.Wallets[0], eventsContractABI, "setMessage", "user0Event1", contractReceipt.ContractAddress) @@ -388,28 +402,32 @@ func testErrorHandling(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { // make requests to geth for comparison for _, req := range []string{ + `{"jsonrpc":"2.0","method":"eth_gasPrice","params": [],"id":1}`, + `{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params": ["latest", false],"id":1}`, + `{"jsonrpc":"2.0","method":"eth_feeHistory","params":[1, "latest", [50]],"id":1}`, `{"jsonrpc":"2.0","method":"eth_getBalance","params":["0xA58C60cc047592DE97BF1E8d2f225Fc5D959De77", "latest"],"id":1}`, `{"jsonrpc":"2.0","method":"eth_getBalance","params":[],"id":1}`, - `{"jsonrpc":"2.0","method":"eth_getgetget","params":["0xA58C60cc047592DE97BF1E8d2f225Fc5D959De77", "latest"],"id":1}`, + //`{"jsonrpc":"2.0","method":"eth_getgetget","params":["0xA58C60cc047592DE97BF1E8d2f225Fc5D959De77", "latest"],"id":1}`, `{"method":"eth_getBalance","params":["0xA58C60cc047592DE97BF1E8d2f225Fc5D959De77", "latest"],"id":1}`, `{"jsonrpc":"2.0","method":"eth_getBalance","params":["0xA58C60cc047592DE97BF1E8d2f225Fc5D959De77", "latest"],"id":1,"extra":"extra_field"}`, `{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[["0xA58C60cc047592DE97BF1E8d2f225Fc5D959De77", "0x1234"]],"id":1}`, + `{"jsonrpc":"2.0","method":"eth_getTransactionByHash","params":["0x0000000000000000000000000000000000000000000000000000000000000000"],"id":1}`, } { // ensure the geth request is issued correctly (should return 200 ok with jsonRPCError) _, response, err := httputil.PostDataJSON(ogClient.HTTP(), []byte(req)) require.NoError(t, err) // unmarshall the response to JSONRPCMessage - jsonRPCError := wecommon.JSONRPCMessage{} + jsonRPCError := JSONRPCMessage{} err = json.Unmarshal(response, &jsonRPCError) - require.NoError(t, err) + require.NoError(t, err, req, response) // repeat the process for the gateway _, response, err = httputil.PostDataJSON(fmt.Sprintf("http://localhost:%d", integration.StartPortTenGatewayUnitTest), []byte(req)) require.NoError(t, err) // we only care about format - jsonRPCError = wecommon.JSONRPCMessage{} + jsonRPCError = JSONRPCMessage{} err = json.Unmarshal(response, &jsonRPCError) require.NoError(t, err) } @@ -467,7 +485,7 @@ func testErrorsRevertedArePassed(t *testing.T, httpURL, wsURL string, w wallet.W // convert error to WE error errBytes, err := json.Marshal(err) require.NoError(t, err) - weError := wecommon.JSONError{} + weError := JSONError{} err = json.Unmarshal(errBytes, &weError) require.NoError(t, err) require.Equal(t, "execution reverted: Forced require", weError.Message) @@ -498,7 +516,10 @@ func testUnsubscribe(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { // create a user with multiple accounts user, err := NewUser([]wallet.Wallet{w, datagenerator.RandomWallet(integration.TenChainID)}, httpURL, wsURL) require.NoError(t, err) - testlog.Logger().Info("Created user with encryption token: %s\n", user.tgClient.UserID()) + testlog.Logger().Info("Created user with encryption token", "t", user.tgClient.UserID()) + + _, err = user.HTTPClient.ChainID(context.Background()) + require.NoError(t, err) // register all the accounts for the user err = user.RegisterAccounts() @@ -523,11 +544,12 @@ func testUnsubscribe(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { contractReceipt, err := integrationCommon.AwaitReceiptEth(context.Background(), user.HTTPClient, signedTx.Hash(), time.Minute) require.NoError(t, err) - testlog.Logger().Info("Deployed contract address: ", contractReceipt.ContractAddress) + testlog.Logger().Info("Deployed contract address: ", "addr", contractReceipt.ContractAddress) // subscribe to an event var userLogs []types.Log - subscription := subscribeToEvents([]gethcommon.Address{contractReceipt.ContractAddress}, nil, user.WSClient, &userLogs) + subscription, err := subscribeToEvents([]gethcommon.Address{contractReceipt.ContractAddress}, nil, user.WSClient, &userLogs) + require.NoError(t, err) // make an action that will trigger events _, err = integrationCommon.InteractWithSmartContract(user.HTTPClient, user.Wallets[0], eventsContractABI, "setMessage", "foo", contractReceipt.ContractAddress) @@ -550,7 +572,10 @@ func testClosingConnectionWhileSubscribed(t *testing.T, httpURL, wsURL string, w // create a user with multiple accounts user, err := NewUser([]wallet.Wallet{w, datagenerator.RandomWallet(integration.TenChainID)}, httpURL, wsURL) require.NoError(t, err) - testlog.Logger().Info("Created user with encryption token: %s\n", user.tgClient.UserID()) + testlog.Logger().Info("Created user with encryption token", "t", user.tgClient.UserID()) + + _, err = user.HTTPClient.ChainID(context.Background()) + require.NoError(t, err) // register all the accounts for the user err = user.RegisterAccounts() @@ -575,11 +600,12 @@ func testClosingConnectionWhileSubscribed(t *testing.T, httpURL, wsURL string, w contractReceipt, err := integrationCommon.AwaitReceiptEth(context.Background(), user.HTTPClient, signedTx.Hash(), time.Minute) require.NoError(t, err) - testlog.Logger().Info("Deployed contract address: ", contractReceipt.ContractAddress) + testlog.Logger().Info("Deployed contract address: ", "addr", contractReceipt.ContractAddress) // subscribe to an event var userLogs []types.Log - subscription := subscribeToEvents([]gethcommon.Address{contractReceipt.ContractAddress}, nil, user.WSClient, &userLogs) + subscription, err := subscribeToEvents([]gethcommon.Address{contractReceipt.ContractAddress}, nil, user.WSClient, &userLogs) + require.NoError(t, err) // Close the websocket connection and make sure nothing breaks, but user does not receive events user.WSClient.Close() @@ -742,7 +768,7 @@ func transferETHToAddress(client *ethclient.Client, wallet wallet.Wallet, toAddr return integrationCommon.AwaitReceiptEth(context.Background(), client, signedTx.Hash(), 30*time.Second) } -func subscribeToEvents(addresses []gethcommon.Address, topics [][]gethcommon.Hash, client *ethclient.Client, logs *[]types.Log) ethereum.Subscription { +func subscribeToEvents(addresses []gethcommon.Address, topics [][]gethcommon.Hash, client *ethclient.Client, logs *[]types.Log) (ethereum.Subscription, error) { // Make a subscription filterQuery := ethereum.FilterQuery{ Addresses: addresses, @@ -754,7 +780,8 @@ func subscribeToEvents(addresses []gethcommon.Address, topics [][]gethcommon.Has subscription, err := client.SubscribeFilterLogs(context.Background(), filterQuery, logsCh) if err != nil { - testlog.Logger().Info("Failed to subscribe to filter logs: %v", log2.ErrKey, err) + testlog.Logger().Info("Failed to subscribe to filter logs", log2.ErrKey, err) + return nil, err } // Listen for logs in a goroutine @@ -762,7 +789,7 @@ func subscribeToEvents(addresses []gethcommon.Address, topics [][]gethcommon.Has for { select { case err := <-subscription.Err(): - testlog.Logger().Info("Error from logs subscription: %v", log2.ErrKey, err) + testlog.Logger().Info("Error from logs subscription", log2.ErrKey, err) return case log := <-logsCh: // append logs to be visible from the main thread @@ -771,5 +798,5 @@ func subscribeToEvents(addresses []gethcommon.Address, topics [][]gethcommon.Has } }() - return subscription + return subscription, nil } diff --git a/tools/walletextension/accountmanager/account_manager.go b/tools/walletextension/accountmanager/account_manager.go deleted file mode 100644 index 126adc134e..0000000000 --- a/tools/walletextension/accountmanager/account_manager.go +++ /dev/null @@ -1,442 +0,0 @@ -package accountmanager - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "strings" - "sync" - - "github.com/ten-protocol/go-ten/go/common/viewingkey" - - "github.com/ethereum/go-ethereum/eth/filters" - "github.com/ten-protocol/go-ten/go/common" - - "github.com/ten-protocol/go-ten/tools/walletextension/storage" - - "github.com/ten-protocol/go-ten/tools/walletextension/subscriptions" - - "github.com/ten-protocol/go-ten/go/common/gethencoding" - - gethlog "github.com/ethereum/go-ethereum/log" - - wecommon "github.com/ten-protocol/go-ten/tools/walletextension/common" - - "github.com/ten-protocol/go-ten/go/rpc" - "github.com/ten-protocol/go-ten/tools/walletextension/userconn" - - gethcommon "github.com/ethereum/go-ethereum/common" -) - -const ( - ethCallPaddedArgLen = 64 - ethCallAddrPadding = "000000000000000000000000" - - ErrNoViewingKey = "method %s cannot be called with an unauthorised client - no signed viewing keys found" -) - -// AccountManager provides a single location for code that helps the gateway in determining the appropriate -// account to use to send a request for selected user when multiple accounts are registered -type AccountManager struct { - userID string - unauthedClient rpc.Client - accountsMutex sync.RWMutex - accountClientsHTTP map[gethcommon.Address]*rpc.EncRPCClient // An encrypted RPC http client per registered account - hostRPCBindAddrWS string - subscriptionsManager *subscriptions.SubscriptionManager - storage storage.Storage - logger gethlog.Logger -} - -func NewAccountManager(userID string, unauthedClient rpc.Client, hostRPCBindAddressWS string, storage storage.Storage, logger gethlog.Logger) *AccountManager { - return &AccountManager{ - userID: userID, - unauthedClient: unauthedClient, - accountClientsHTTP: make(map[gethcommon.Address]*rpc.EncRPCClient), - hostRPCBindAddrWS: hostRPCBindAddressWS, - subscriptionsManager: subscriptions.New(logger), - storage: storage, - logger: logger, - } -} - -// GetAllAddressesWithClients returns a list of addresses which already have clients (are in accountClients map) -func (m *AccountManager) GetAllAddressesWithClients() []string { - m.accountsMutex.RLock() - defer m.accountsMutex.RUnlock() - - addresses := make([]string, 0, len(m.accountClientsHTTP)) - for address := range m.accountClientsHTTP { - addresses = append(addresses, address.Hex()) - } - return addresses -} - -// AddClient adds a client to the list of clients, keyed by account address. -func (m *AccountManager) AddClient(address gethcommon.Address, client *rpc.EncRPCClient) { - m.accountsMutex.Lock() - defer m.accountsMutex.Unlock() - m.accountClientsHTTP[address] = client -} - -// ProxyRequest tries to identify the correct EncRPCClient to proxy the request to the Ten node, or it will attempt -// the request with all clients until it succeeds -func (m *AccountManager) ProxyRequest(rpcReq *wecommon.RPCRequest, rpcResp *interface{}, userConn userconn.UserConn) error { - // We need to handle a special case for subscribing and unsubscribing from events, - // because we need to handle multiple accounts with a single user request - if rpcReq.Method == rpc.Subscribe { - clients, err := m.suggestSubscriptionClient(rpcReq) - if err != nil { - return err - } - err = m.subscriptionsManager.HandleNewSubscriptions(clients, rpcReq, rpcResp, userConn) - if err != nil { - m.logger.Error("Error subscribing to multiple clients") - return err - } - return nil - } - if rpcReq.Method == rpc.Unsubscribe { - if len(rpcReq.Params) != 1 { - return fmt.Errorf("one parameter (subscriptionID) expected, %d parameters received", len(rpcReq.Params)) - } - subscriptionID, ok := rpcReq.Params[0].(string) - if !ok { - return fmt.Errorf("subscriptionID needs to be a string. Got: %v", rpcReq.Params[0]) - } - m.subscriptionsManager.HandleUnsubscribe(subscriptionID, rpcResp) - return nil - } - return m.executeCall(rpcReq, rpcResp) -} - -const emptyFilterCriteria = "[]" // This is the value that gets passed for an empty filter criteria. - -// suggestSubscriptionClient returns clients that should be used for the subscription request. -// For other requests we use http clients, but for subscriptions ws clients are required, that is the reason for -// creating ws clients here. -// We only want to have the connections open for the duration of the subscription, so we create the clients here and -// don't store them in the accountClients map. -func (m *AccountManager) suggestSubscriptionClient(rpcReq *wecommon.RPCRequest) ([]rpc.Client, error) { - m.accountsMutex.RLock() - defer m.accountsMutex.RUnlock() - - userIDBytes, err := wecommon.GetUserIDbyte(m.userID) - if err != nil { - return nil, fmt.Errorf("error decoding string (%s), %w", m.userID, err) - } - - accounts, err := m.storage.GetAccounts(userIDBytes) - if err != nil { - return nil, fmt.Errorf("error getting accounts for user: %s, %w", m.userID, err) - } - - userPrivateKey, err := m.storage.GetUserPrivateKey(userIDBytes) - if err != nil { - return nil, fmt.Errorf("error getting private key for user: %s, %w", m.userID, err) - } - - if len(rpcReq.Params) > 1 { - filteredAccounts, err := m.filterAccounts(rpcReq, accounts) - if err != nil { - return nil, err - } - // return filtered clients if we found any - if len(filteredAccounts) > 0 { - accounts = filteredAccounts - } - } - // create clients for all accounts if we didn't find any clients that match the filter or if no topics were provided - return m.createClientsForAccounts(accounts, userPrivateKey) -} - -// filterClients checks if any of the accounts match the filter criteria and returns those accounts -func (m *AccountManager) filterAccounts(rpcReq *wecommon.RPCRequest, accounts []wecommon.AccountDB) ([]wecommon.AccountDB, error) { - var filteredAccounts []wecommon.AccountDB - filterCriteriaJSON, err := json.Marshal(rpcReq.Params[1]) - if err != nil { - return nil, fmt.Errorf("could not marshal filter criteria to JSON. Cause: %w", err) - } - filterCriteria := filters.FilterCriteria{} - if string(filterCriteriaJSON) != emptyFilterCriteria { - err = filterCriteria.UnmarshalJSON(filterCriteriaJSON) - if err != nil { - return nil, fmt.Errorf("could not unmarshal filter criteria from the following JSON: `%s`. Cause: %w", string(filterCriteriaJSON), err) - } - } - - for _, topicCondition := range filterCriteria.Topics { - for _, topic := range topicCondition { - potentialAddr := common.ExtractPotentialAddress(topic) - m.logger.Info(fmt.Sprintf("Potential address (%s) found for the request %s", potentialAddr, rpcReq)) - if potentialAddr != nil { - for _, account := range accounts { - // if we find a match, we append the account to the list of filtered accounts - if bytes.Equal(account.AccountAddress, potentialAddr.Bytes()) { - filteredAccounts = append(filteredAccounts, account) - } - } - } - } - } - - return filteredAccounts, nil -} - -// createClientsForAllAccounts creates ws clients for all accounts for given user and returns them -func (m *AccountManager) createClientsForAccounts(accounts []wecommon.AccountDB, userPrivateKey []byte) ([]rpc.Client, error) { - clients := make([]rpc.Client, 0, len(accounts)) - for _, account := range accounts { - encClient, err := wecommon.CreateEncClient(m.hostRPCBindAddrWS, account.AccountAddress, userPrivateKey, account.Signature, viewingkey.SignatureType(account.SignatureType), m.logger) - if err != nil { - m.logger.Error(fmt.Errorf("error creating new client, %w", err).Error()) - continue - } - clients = append(clients, encClient) - } - return clients, nil -} - -// todo - better way -const notAuthorised = "not authorised" - -var platformAuthorisedCalls = map[string]bool{ - rpc.GetBalance: true, - // rpc.GetCode, //todo - rpc.GetTransactionCount: true, - rpc.GetTransactionReceipt: true, - rpc.GetLogs: true, -} - -func (m *AccountManager) executeCall(rpcReq *wecommon.RPCRequest, rpcResp *interface{}) error { - m.accountsMutex.RLock() - defer m.accountsMutex.RUnlock() - // for Ten RPC requests, it is important we know the sender account for the viewing key encryption/decryption - suggestedClient := m.suggestAccountClient(rpcReq, m.accountClientsHTTP) - - switch { - case suggestedClient != nil: // use the suggested client if there is one - // todo (@ziga) - if we have a suggested client, should we still loop through the other clients if it fails? - // The call data guessing won't often be wrong but there could be edge-cases there - return submitCall(suggestedClient, rpcReq, rpcResp) - - case len(m.accountClientsHTTP) > 0: // try registered clients until there's a successful execution - m.logger.Info(fmt.Sprintf("appropriate client not found, attempting request with up to %d clients", len(m.accountClientsHTTP))) - var err error - for _, client := range m.accountClientsHTTP { - err = submitCall(client, rpcReq, rpcResp) - if err == nil || errors.Is(err, rpc.ErrNilResponse) { - // request didn't fail, we don't need to continue trying the other clients - return nil - } - // platform calls return a standard error for calls that are not authorised. - // any other error can be returned early - if platformAuthorisedCalls[rpcReq.Method] && err.Error() != notAuthorised { - return err - } - } - // every attempt errored - return err - - default: // no clients registered, use the unauthenticated one - if rpc.IsSensitiveMethod(rpcReq.Method) { - return fmt.Errorf(ErrNoViewingKey, rpcReq.Method) - } - return m.unauthedClient.Call(rpcResp, rpcReq.Method, rpcReq.Params...) - } -} - -// suggestAccountClient works through various methods to try and guess which available client to use for a request, returns nil if none found -func (m *AccountManager) suggestAccountClient(req *wecommon.RPCRequest, accClients map[gethcommon.Address]*rpc.EncRPCClient) *rpc.EncRPCClient { - if len(accClients) == 1 { - for _, client := range accClients { - // return the first (and only) client - return client - } - } - switch req.Method { - case rpc.Call, rpc.EstimateGas: - return m.handleEthCall(req, accClients) - case rpc.GetBalance: - return extractAddress(0, req.Params, accClients) - case rpc.GetLogs: - return extractAddress(1, req.Params, accClients) - case rpc.GetTransactionCount: - return extractAddress(0, req.Params, accClients) - default: - return nil - } -} - -func extractAddress(pos int, params []interface{}, accClients map[gethcommon.Address]*rpc.EncRPCClient) *rpc.EncRPCClient { - if len(params) < pos+1 { - return nil - } - requestedAddress, err := gethencoding.ExtractAddress(params[pos]) - if err == nil { - return accClients[*requestedAddress] - } - return nil -} - -func (m *AccountManager) handleEthCall(req *wecommon.RPCRequest, accClients map[gethcommon.Address]*rpc.EncRPCClient) *rpc.EncRPCClient { - paramsMap, err := parseParams(req.Params) - if err != nil { - // no further info to deduce calling client - return nil - } - // check if request params had a "from" address and if we had a client for that address - fromClient, found := checkForFromField(paramsMap, accClients) - if found { - return fromClient - } - - // Otherwise, we search the `data` field for an address matching a registered viewing key. - addr, err := searchDataFieldForAccount(paramsMap, accClients) - if err == nil { - return accClients[*addr] - } - return nil -} - -// Many eth RPC requests provide params as first argument in a json map with similar fields (e.g. a `from` field) -func parseParams(args []interface{}) (map[string]interface{}, error) { - if len(args) == 0 { - return nil, fmt.Errorf("no params found to unmarshal") - } - - // only interested in trying first arg - params, ok := args[0].(map[string]interface{}) - if !ok { - callParamsJSON, ok := args[0].([]byte) - if !ok { - return nil, fmt.Errorf("first arg was not a byte array") - } - - err := json.Unmarshal(callParamsJSON, ¶ms) - if err != nil { - return nil, fmt.Errorf("first arg couldn't be unmarshaled into a params map") - } - } - - return params, nil -} - -func checkForFromField(paramsMap map[string]interface{}, accClients map[gethcommon.Address]*rpc.EncRPCClient) (*rpc.EncRPCClient, bool) { - fromVal, found := paramsMap[wecommon.JSONKeyFrom] - if !found { - return nil, false - } - - fromStr, ok := fromVal.(string) - if !ok { - return nil, false - } - - fromAddr := gethcommon.HexToAddress(fromStr) - client, found := accClients[fromAddr] - return client, found -} - -// Extracts the arguments from the request's `data` field. If any of them, after removing padding, match the viewing -// key address, we return that address. Otherwise, we return nil. -func searchDataFieldForAccount(callParams map[string]interface{}, accClients map[gethcommon.Address]*rpc.EncRPCClient) (*gethcommon.Address, error) { - // We ensure that the `data` field is present. - data := callParams[wecommon.JSONKeyData] - if data == nil { - return nil, fmt.Errorf("eth_call request did not have its `data` field set") - } - dataString, ok := data.(string) - if !ok { - return nil, fmt.Errorf("eth_call request's `data` field was not of the expected type `string`") - } - - // We check that the data field is long enough before removing the leading "0x" (1 bytes/2 chars) and the method ID - // (4 bytes/8 chars). - if len(dataString) < 10 { - return nil, fmt.Errorf("data field is not long enough - no known account found in data bytes") - } - dataString = dataString[10:] - - // We split up the arguments in the `data` field. - var dataArgs []string - for i := 0; i < len(dataString); i += ethCallPaddedArgLen { - if i+ethCallPaddedArgLen > len(dataString) { - break - } - dataArgs = append(dataArgs, dataString[i:i+ethCallPaddedArgLen]) - } - - // We iterate over the arguments, looking for an argument that matches a viewing key address - for _, dataArg := range dataArgs { - // If the argument doesn't have the correct padding, it's not an address. - if !strings.HasPrefix(dataArg, ethCallAddrPadding) { - continue - } - - maybeAddress := gethcommon.HexToAddress(dataArg[len(ethCallAddrPadding):]) - if _, ok := accClients[maybeAddress]; ok { - return &maybeAddress, nil - } - } - - return nil, fmt.Errorf("no known account found in data bytes") -} - -func submitCall(client *rpc.EncRPCClient, req *wecommon.RPCRequest, resp *interface{}) error { - if req.Method == rpc.Call || req.Method == rpc.EstimateGas { - // Never modify the original request, as it might be reused. - req = req.Clone() - - // Any method using an ethereum.CallMsg is a sensitive method that requires a viewing key lookup but the 'from' field is not mandatory - // and is often not included from metamask etc. So we ensure it is populated here. - account := client.Account() - var err error - req.Params, err = setFromFieldIfMissing(req.Params, *account) - if err != nil { - return err - } - } - - if req.Method == rpc.GetLogs { - // Never modify the original request, as it might be reused. - req = req.Clone() - - // We add the account to the list of arguments, so we know which account to use to filter the logs and encrypt - // the result. - req.Params = append(req.Params, client.Account().Hex()) - } - - return client.Call(resp, req.Method, req.Params...) -} - -// The enclave requires the `from` field to be set so that it can encrypt the response, but sources like MetaMask often -// don't set it. So we check whether it's present; if absent, we walk through the arguments in the request's `data` -// field, and if any of the arguments match our viewing key address, we set the `from` field to that address. -func setFromFieldIfMissing(args []interface{}, account gethcommon.Address) ([]interface{}, error) { - if len(args) == 0 { - return nil, fmt.Errorf("no params found to unmarshal") - } - - callMsg, err := gethencoding.ExtractEthCallMapString(args[0]) - if err != nil { - return nil, fmt.Errorf("unable to marshal callMsg - %w", err) - } - - // We only modify `eth_call` requests where the `from` field is not set. - if callMsg[gethencoding.CallFieldFrom] != gethcommon.HexToAddress("0x0").Hex() { - return args, nil - } - - // override the existing args - callMsg[gethencoding.CallFieldFrom] = account.Hex() - - // do not modify other existing arguments - request := []interface{}{callMsg} - for i := 1; i < len(args); i++ { - request = append(request, args[i]) - } - - return request, nil -} diff --git a/tools/walletextension/accountmanager/account_manager_test.go b/tools/walletextension/accountmanager/account_manager_test.go deleted file mode 100644 index 23cd3737cd..0000000000 --- a/tools/walletextension/accountmanager/account_manager_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package accountmanager - -import ( - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ten-protocol/go-ten/go/rpc" -) - -const ( - dataFieldPrefix = "0xmethodID" - padding = "000000000000000000000000" - viewingKeyAddressHex = "71C7656EC7ab88b098defB751B7401B5f6d8976F" - viewingKeyAddressHexPadded = padding + viewingKeyAddressHex - otherAddressHexPadded = padding + "71C7656EC7ab88b098defB751B7401B5f6d8976E" // Differs only in the final byte. -) - -var ( - viewingKeyAddressOne = common.HexToAddress("0x" + viewingKeyAddressHex) - viewingKeyAddressTwo = common.HexToAddress("0x71C7656EC7ab88b098defB751B7401B5f6d8976D") // Not in the data field. - accClients = map[common.Address]*rpc.EncRPCClient{ - viewingKeyAddressOne: nil, - viewingKeyAddressTwo: nil, - } -) - -func TestCanSearchDataFieldForFrom(t *testing.T) { - callParams := map[string]interface{}{"data": dataFieldPrefix + otherAddressHexPadded + viewingKeyAddressHexPadded} - address, err := searchDataFieldForAccount(callParams, accClients) - if err != nil { - t.Fatalf("did not expect an error but got %s", err) - } - if *address != viewingKeyAddressOne { - t.Fatal("did not find correct viewing key address in `data` field") - } -} - -func TestCanSearchDataFieldWhenHasUnexpectedLength(t *testing.T) { - incorrectLengthArg := "arg2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" // Only 31 bytes. - callParams := map[string]interface{}{"data": dataFieldPrefix + otherAddressHexPadded + viewingKeyAddressHexPadded + incorrectLengthArg} - address, err := searchDataFieldForAccount(callParams, accClients) - if err != nil { - t.Fatalf("did not expect an error but got %s", err) - } - if *address != viewingKeyAddressOne { - t.Fatal("did not find correct viewing key address in `data` field") - } -} - -func TestErrorsWhenDataFieldIsMissing(t *testing.T) { - _, err := searchDataFieldForAccount(make(map[string]interface{}), accClients) - - if err == nil { - t.Fatal("`data` field was missing but not error was thrown") - } -} - -func TestDataFieldTooShort(t *testing.T) { - callParams := map[string]interface{}{"data": "tooshort"} - address, err := searchDataFieldForAccount(callParams, accClients) - if err == nil { - t.Fatal("expected an error but got none") - } - if address != nil { - t.Fatal("`data` field was too short but address was found anyway") - } -} diff --git a/tools/walletextension/api/server.go b/tools/walletextension/api/server.go deleted file mode 100644 index ae1fac8252..0000000000 --- a/tools/walletextension/api/server.go +++ /dev/null @@ -1,86 +0,0 @@ -package api - -import ( - "context" - "embed" - "fmt" - "io/fs" - "net/http" - "time" - - "github.com/ten-protocol/go-ten/lib/gethfork/node" - - "github.com/ten-protocol/go-ten/tools/walletextension/common" -) - -//go:embed all:static -var staticFiles embed.FS - -const ( - staticDir = "static" -) - -// Server is a wrapper for the http server -type Server struct { - server *http.Server -} - -// Start starts the server in its own goroutine and returns an error chan where errors can be monitored -func (s *Server) Start() chan error { - errChan := make(chan error) - go func() { - // start the server and serve any errors over the channel - errChan <- s.server.ListenAndServe() - }() - return errChan -} - -// Stop synchronously stops the server -func (s *Server) Stop() error { - return s.server.Shutdown(context.Background()) -} - -// NewHTTPServer returns the HTTP server for the WE -func NewHTTPServer(address string, routes []node.Route) *Server { - return &Server{ - server: createHTTPServer(address, routes), - } -} - -// NewWSServer returns the WS server for the WE -func NewWSServer(address string, routes []node.Route) *Server { - return &Server{ - server: createWSServer(address, routes), - } -} - -func createHTTPServer(address string, routes []node.Route) *http.Server { - serveMux := http.NewServeMux() - - // Handles Ethereum JSON-RPC requests received over HTTP. - for _, route := range routes { - serveMux.HandleFunc(route.Name, route.Func) - } - - // Serves the web assets for the management of viewing keys. - noPrefixStaticFiles, err := fs.Sub(staticFiles, staticDir) - if err != nil { - panic(fmt.Sprintf("could not serve static files. Cause: %s", err)) - } - serveMux.Handle(common.PathObscuroGateway, http.StripPrefix(common.PathObscuroGateway, http.FileServer(http.FS(noPrefixStaticFiles)))) - - // Creates the actual http server with a ReadHeaderTimeout to avoid Potential Slowloris Attack - server := &http.Server{Addr: address, Handler: serveMux, ReadHeaderTimeout: common.ReaderHeadTimeout} - return server -} - -func createWSServer(address string, routes []node.Route) *http.Server { - serveMux := http.NewServeMux() - - // Handles Ethereum JSON-RPC requests received over HTTP. - for _, route := range routes { - serveMux.HandleFunc(route.Name, route.Func) - } - - return &http.Server{Addr: address, Handler: serveMux, ReadHeaderTimeout: 10 * time.Second} -} diff --git a/tools/walletextension/api/static/favicon.ico b/tools/walletextension/api/static/favicon.ico deleted file mode 100644 index 0e8dea2f715d4fdc30aa83101021fe05fb3e2c21..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1260 zcmVg-p z$Qi$Yq(b+^#>V2{-~fAj zdr*R;7$7`6JXjpa4Q5?#Z*NgtT#T`?F~~)-3!R*tFc+fLQ;L<96&xKMp#;e;MBLom zU}|a#+uPf?xVT{1#o5^zCMM$7gYGFWFGo*L50oI;g}%bY#RYM3afpbBKtMnMixmo3 zLy+=yb#-WIX@OEyGe92;3kxJCCnGvK8VLyr@bvW5UaPIGRYiH?4~)dq(-S5qC)q(+ zXK85(K|w)K8(A?RmDkr-wt>aR$7@~f?d=Wg>+4X8A{VNzu10!#`j^+k!^83X{H#5< zv9ZD2+#HJ$qM@MyO-)Tug5nq$8XEetG@ulzYXD(uYm4dWX|@RxN{vt@h_kb^S+%MU zrATrgeVm+}FgQ4fn3x!LuEYpcf_Qj%SdzS@r6pWlUA5U1^G8E_w3kzBqLq}Cr6{Mhw3z70Q zH8rd>CPqg`QCL{`L59-j%*>47ZhU=x&01Fa6@%g|q_2E`e?Lk}N@b&d& zg&gf2hRSieo|BWqo<;SNDCiByJ?!l4xW2wV?j8EiEBisl&CQM5-`{7~`SO9QtgQU@ znVp@T+{42I$2U%*bWl_awYRsUprC;L@9phHX=y22Vf~f&^YdfpKq}r(T7}!xV*d+lvQ;jqgOsMGNMhD$hQc_YN1>IQ>&5}rY-M&}|cXxLb z6%`4}2^x@*k@0C)l?o{?MR_R*8bB@J^71m2@YQ%l4FnCKnj|kT4{K{{_>G7PVlskK z<8S1DKk(DYHh`}%VQe-xHw9&68<3coh>eX67z^q#1_lNM=2)z|lvK^Ye2k!6=`Y#15sDDo^~AAf~3sSQzaipDr&iFYKeAG58Dg W(m0zVdEl%70000 { try { if (ethereum) { - - // There are some wallets that are conflicting with MetaMask - we want to check that and throw an error if they are connected - const conflictingWalletMap = { - 'Exodus Wallet': ethereum.isExodus, - 'Nest Wallet': ethereum.isNestWallet, - // Add other wallets here as needed - }; - - // Iterate over the wallet map and handle conflicts - for (const [walletName, isWalletConnected] of Object.entries(conflictingWalletMap)) { - if (isWalletConnected) { - const message = `${walletName} is connected and is conflicting with MetaMask. Please disable ${walletName} and try again.`; - showToast(ToastType.DESTRUCTIVE, message); - throw new Error(message); - } - } - return await ethService.handleEthereum(provider); } else { showToast(ToastType.INFO, "Connecting to MetaMask..."); diff --git a/tools/walletextension/httpapi/README.MD b/tools/walletextension/httpapi/README.MD new file mode 100644 index 0000000000..90a8e7e823 --- /dev/null +++ b/tools/walletextension/httpapi/README.MD @@ -0,0 +1 @@ +todo - the content of this package should be moved to rpcapi to avoid implementing the low-level http logic \ No newline at end of file diff --git a/tools/walletextension/api/routes.go b/tools/walletextension/httpapi/routes.go similarity index 68% rename from tools/walletextension/api/routes.go rename to tools/walletextension/httpapi/routes.go index 829dc69a38..e489f98c4e 100644 --- a/tools/walletextension/api/routes.go +++ b/tools/walletextension/httpapi/routes.go @@ -1,4 +1,4 @@ -package api +package httpapi import ( "encoding/hex" @@ -7,25 +7,20 @@ import ( "net/http" "github.com/ten-protocol/go-ten/lib/gethfork/node" + "github.com/ten-protocol/go-ten/tools/walletextension/rpcapi" "github.com/ten-protocol/go-ten/go/common/log" "github.com/ten-protocol/go-ten/go/common/httputil" - "github.com/ten-protocol/go-ten/go/rpc" - "github.com/ten-protocol/go-ten/tools/walletextension" "github.com/ten-protocol/go-ten/tools/walletextension/common" - "github.com/ten-protocol/go-ten/tools/walletextension/userconn" gethcommon "github.com/ethereum/go-ethereum/common" ) // NewHTTPRoutes returns the http specific routes -func NewHTTPRoutes(walletExt *walletextension.WalletExtension) []node.Route { +// todo - move these to the rpc framework. +func NewHTTPRoutes(walletExt *rpcapi.Services) []node.Route { return []node.Route{ - { - Name: common.APIVersion1 + common.PathRoot, - Func: httpHandler(walletExt, ethRequestHandler), - }, { Name: common.PathReady, Func: httpHandler(walletExt, readyRequestHandler), @@ -70,8 +65,8 @@ func NewHTTPRoutes(walletExt *walletextension.WalletExtension) []node.Route { } func httpHandler( - walletExt *walletextension.WalletExtension, - fun func(walletExt *walletextension.WalletExtension, conn userconn.UserConn), + walletExt *rpcapi.Services, + fun func(walletExt *rpcapi.Services, conn UserConn), ) func(resp http.ResponseWriter, req *http.Request) { return func(resp http.ResponseWriter, req *http.Request) { httpRequestHandler(walletExt, resp, req, fun) @@ -79,122 +74,22 @@ func httpHandler( } // Overall request handler for http requests -func httpRequestHandler(walletExt *walletextension.WalletExtension, resp http.ResponseWriter, req *http.Request, fun func(walletExt *walletextension.WalletExtension, conn userconn.UserConn)) { +func httpRequestHandler(walletExt *rpcapi.Services, resp http.ResponseWriter, req *http.Request, fun func(walletExt *rpcapi.Services, conn UserConn)) { if walletExt.IsStopping() { return } if httputil.EnableCORS(resp, req) { return } - userConn := userconn.NewUserConnHTTP(resp, req, walletExt.Logger()) + userConn := NewUserConnHTTP(resp, req, walletExt.Logger()) fun(walletExt, userConn) } -// NewWSRoutes returns the WS specific routes -func NewWSRoutes(walletExt *walletextension.WalletExtension) []node.Route { - return []node.Route{ - { - Name: common.PathRoot, - Func: wsHandler(walletExt, ethRequestHandler), - }, - { - Name: common.PathReady, - Func: wsHandler(walletExt, readyRequestHandler), - }, - { - Name: common.PathGenerateViewingKey, - Func: wsHandler(walletExt, generateViewingKeyRequestHandler), - }, - - { - Name: common.PathSubmitViewingKey, - Func: wsHandler(walletExt, submitViewingKeyRequestHandler), - }, - } -} - -func wsHandler( - walletExt *walletextension.WalletExtension, - fun func(walletExt *walletextension.WalletExtension, conn userconn.UserConn), -) func(resp http.ResponseWriter, req *http.Request) { - return func(resp http.ResponseWriter, req *http.Request) { - wsRequestHandler(walletExt, resp, req, fun) - } -} - -// Overall request handler for WS requests -func wsRequestHandler(walletExt *walletextension.WalletExtension, resp http.ResponseWriter, req *http.Request, fun func(walletExt *walletextension.WalletExtension, conn userconn.UserConn)) { - if walletExt.IsStopping() { - return - } - - userConn, err := userconn.NewUserConnWS(resp, req, walletExt.Logger()) - if err != nil { - return - } - // We handle requests in a loop until the connection is closed on the client side. - for !userConn.IsClosed() { - fun(walletExt, userConn) - } -} - -// ethRequestHandler parses the user eth request, passes it on to the WE to proxy it and processes the response -func ethRequestHandler(walletExt *walletextension.WalletExtension, conn userconn.UserConn) { - body, err := conn.ReadRequest() - if err != nil { - handleEthError(nil, conn, walletExt.Logger(), fmt.Errorf("error reading request - %w", err)) - return - } - - request, err := parseRequest(body) - if err != nil { - handleError(conn, walletExt.Logger(), err) - return - } - walletExt.Logger().Debug("REQUEST", "method", request.Method, "body", string(body)) - - if request.Method == rpc.Subscribe && !conn.SupportsSubscriptions() { - handleError(conn, walletExt.Logger(), fmt.Errorf("received an %s request but the connection does not support subscriptions", rpc.Subscribe)) - return - } - - // Get userID - // TODO: @ziga - after removing old wallet extension endpoints we should prevent users doing anything without valid encryption token - hexUserID, err := getUserID(conn, 1) - if err != nil || !walletExt.UserExists(hexUserID) { - walletExt.Logger().Info("user not found in the query params: %w. Using the default user", log.ErrKey, err) - hexUserID = hex.EncodeToString([]byte(common.DefaultUser)) // todo (@ziga) - this can be removed once old WE endpoints are removed - } - - if len(hexUserID) < 3 { - handleError(conn, walletExt.Logger(), fmt.Errorf("encryption token length is incorrect")) - return - } - - // todo (@pedro) remove this conn dependency - response, err := walletExt.ProxyEthRequest(request, conn, hexUserID) - if err != nil { - handleEthError(request, conn, walletExt.Logger(), err) - return - } - - rpcResponse, err := json.Marshal(response) - if err != nil { - handleEthError(request, conn, walletExt.Logger(), err) - return - } - - walletExt.Logger().Info(fmt.Sprintf("Forwarding %s response from Obscuro node: %s", request.Method, rpcResponse)) - if err = conn.WriteResponse(rpcResponse); err != nil { - walletExt.Logger().Error("error writing success response", log.ErrKey, err) - } -} - // readyRequestHandler is used to check whether the server is ready -func readyRequestHandler(_ *walletextension.WalletExtension, _ userconn.UserConn) {} +func readyRequestHandler(_ *rpcapi.Services, _ UserConn) {} // generateViewingKeyRequestHandler parses the gen vk request -func generateViewingKeyRequestHandler(walletExt *walletextension.WalletExtension, conn userconn.UserConn) { +func generateViewingKeyRequestHandler(walletExt *rpcapi.Services, conn UserConn) { body, err := conn.ReadRequest() if err != nil { handleError(conn, walletExt.Logger(), fmt.Errorf("error reading request: %w", err)) @@ -223,7 +118,7 @@ func generateViewingKeyRequestHandler(walletExt *walletextension.WalletExtension } // submitViewingKeyRequestHandler submits the viewing key and signed bytes to the WE -func submitViewingKeyRequestHandler(walletExt *walletextension.WalletExtension, conn userconn.UserConn) { +func submitViewingKeyRequestHandler(walletExt *rpcapi.Services, conn UserConn) { body, err := conn.ReadRequest() if err != nil { handleError(conn, walletExt.Logger(), fmt.Errorf("error reading request: %w", err)) @@ -258,7 +153,8 @@ func submitViewingKeyRequestHandler(walletExt *walletextension.WalletExtension, } // This function handles request to /join endpoint. It is responsible to create new user (new key-pair) and store it to the db -func joinRequestHandler(walletExt *walletextension.WalletExtension, conn userconn.UserConn) { +func joinRequestHandler(walletExt *rpcapi.Services, conn UserConn) { + // audit() // todo (@ziga) add protection against DDOS attacks _, err := conn.ReadRequest() if err != nil { @@ -283,7 +179,7 @@ func joinRequestHandler(walletExt *walletextension.WalletExtension, conn usercon // This function handles request to /authenticate endpoint. // In the request we receive message, signature and address in JSON as request body and userID and address as query parameters // We then check if message is in correct format and if signature is valid. If all checks pass we save address and signature against userID -func authenticateRequestHandler(walletExt *walletextension.WalletExtension, conn userconn.UserConn) { +func authenticateRequestHandler(walletExt *rpcapi.Services, conn UserConn) { // read the request body, err := conn.ReadRequest() if err != nil { @@ -348,7 +244,7 @@ func authenticateRequestHandler(walletExt *walletextension.WalletExtension, conn // This function handles request to /query endpoint. // In the query parameters address and userID are required. We check if provided address is registered for given userID // and return true/false in json response -func queryRequestHandler(walletExt *walletextension.WalletExtension, conn userconn.UserConn) { +func queryRequestHandler(walletExt *rpcapi.Services, conn UserConn) { // read the request _, err := conn.ReadRequest() if err != nil { @@ -400,7 +296,7 @@ func queryRequestHandler(walletExt *walletextension.WalletExtension, conn userco // This function handles request to /revoke endpoint. // It requires userID as query parameter and deletes given user and all associated viewing keys -func revokeRequestHandler(walletExt *walletextension.WalletExtension, conn userconn.UserConn) { +func revokeRequestHandler(walletExt *rpcapi.Services, conn UserConn) { // read the request _, err := conn.ReadRequest() if err != nil { @@ -430,7 +326,7 @@ func revokeRequestHandler(walletExt *walletextension.WalletExtension, conn userc } // Handles request to /health endpoint. -func healthRequestHandler(walletExt *walletextension.WalletExtension, conn userconn.UserConn) { +func healthRequestHandler(walletExt *rpcapi.Services, conn UserConn) { // read the request _, err := conn.ReadRequest() if err != nil { @@ -446,7 +342,7 @@ func healthRequestHandler(walletExt *walletextension.WalletExtension, conn userc } // Handles request to /network-health endpoint. -func networkHealthRequestHandler(walletExt *walletextension.WalletExtension, userConn userconn.UserConn) { +func networkHealthRequestHandler(walletExt *rpcapi.Services, userConn UserConn) { // read the request _, err := userConn.ReadRequest() if err != nil { @@ -472,7 +368,7 @@ func networkHealthRequestHandler(walletExt *walletextension.WalletExtension, use } // Handles request to /version endpoint. -func versionRequestHandler(walletExt *walletextension.WalletExtension, userConn userconn.UserConn) { +func versionRequestHandler(walletExt *rpcapi.Services, userConn UserConn) { // read the request _, err := userConn.ReadRequest() if err != nil { diff --git a/tools/walletextension/httpapi/user_conn.go b/tools/walletextension/httpapi/user_conn.go new file mode 100644 index 0000000000..2244adcfbd --- /dev/null +++ b/tools/walletextension/httpapi/user_conn.go @@ -0,0 +1,72 @@ +package httpapi + +import ( + "fmt" + "io" + "net/http" + "net/url" + + gethlog "github.com/ethereum/go-ethereum/log" +) + +// UserConn represents a connection to a user. +type UserConn interface { + ReadRequest() ([]byte, error) + ReadRequestParams() map[string]string + WriteResponse(msg []byte) error + SupportsSubscriptions() bool + IsClosed() bool + GetHTTPRequest() *http.Request +} + +// Represents a user's connection over HTTP. +type userConnHTTP struct { + resp http.ResponseWriter + req *http.Request + logger gethlog.Logger +} + +func NewUserConnHTTP(resp http.ResponseWriter, req *http.Request, logger gethlog.Logger) UserConn { + return &userConnHTTP{resp: resp, req: req, logger: logger} +} + +func (h *userConnHTTP) ReadRequest() ([]byte, error) { + body, err := io.ReadAll(h.req.Body) + if err != nil { + return nil, fmt.Errorf("could not read request body: %w", err) + } + return body, nil +} + +func (h *userConnHTTP) WriteResponse(msg []byte) error { + _, err := h.resp.Write(msg) + if err != nil { + return fmt.Errorf("could not write response: %w", err) + } + return nil +} + +func (h *userConnHTTP) SupportsSubscriptions() bool { + return false +} + +func (h *userConnHTTP) IsClosed() bool { + return false +} + +func (h *userConnHTTP) ReadRequestParams() map[string]string { + return getQueryParams(h.req.URL.Query()) +} + +func (h *userConnHTTP) GetHTTPRequest() *http.Request { + return h.req +} + +func getQueryParams(query url.Values) map[string]string { + params := make(map[string]string) + queryParams := query + for key, value := range queryParams { + params[key] = value[0] + } + return params +} diff --git a/tools/walletextension/api/utils.go b/tools/walletextension/httpapi/utils.go similarity index 52% rename from tools/walletextension/api/utils.go rename to tools/walletextension/httpapi/utils.go index 4ca99ac44d..821abaa89d 100644 --- a/tools/walletextension/api/utils.go +++ b/tools/walletextension/httpapi/utils.go @@ -1,53 +1,14 @@ -package api +package httpapi import ( - "encoding/json" "fmt" "strings" gethlog "github.com/ethereum/go-ethereum/log" - "github.com/ten-protocol/go-ten/go/common/errutil" "github.com/ten-protocol/go-ten/go/common/log" "github.com/ten-protocol/go-ten/tools/walletextension/common" - "github.com/ten-protocol/go-ten/tools/walletextension/userconn" ) -func parseRequest(body []byte) (*common.RPCRequest, error) { - // We unmarshal the JSON request - var reqJSONMap map[string]json.RawMessage - err := json.Unmarshal(body, &reqJSONMap) - if err != nil { - return nil, fmt.Errorf("could not unmarshal JSON-RPC request body to JSON: %s. "+ - "If you're trying to generate a viewing key, visit %s", err, common.PathViewingKeys) - } - - reqID := reqJSONMap[common.JSONKeyID] - var method string - err = json.Unmarshal(reqJSONMap[common.JSONKeyMethod], &method) - if err != nil { - return nil, fmt.Errorf("could not unmarshal method string from JSON-RPC request body: %s ; %w", string(body), err) - } - - // we extract the params into a JSON list - var params []interface{} - // params key is optional in JSON-RPC request - _, exists := reqJSONMap[common.JSONKeyParams] - if exists { - err = json.Unmarshal(reqJSONMap[common.JSONKeyParams], ¶ms) - if err != nil { - return nil, fmt.Errorf("could not unmarshal params list from JSON-RPC request body: %s ; %w", string(body), err) - } - } else { - params = []interface{}{} - } - - return &common.RPCRequest{ - ID: reqID, - Method: method, - Params: params, - }, nil -} - func getQueryParameter(params map[string]string, selectedParameter string) (string, error) { value, exists := params[selectedParameter] if !exists { @@ -60,7 +21,7 @@ func getQueryParameter(params map[string]string, selectedParameter string) (stri // getUserID returns userID from query params / url of the URL // it always first tries to get userID from a query parameter `u` or `token` (`u` parameter will become deprecated) // if it fails to get userID from a query parameter it tries to get it from the URL and it needs position as the second parameter -func getUserID(conn userconn.UserConn, userIDPosition int) (string, error) { +func getUserID(conn UserConn, userIDPosition int) (string, error) { // try getting userID (`token`) from query parameters and return it if successful userID, err := getQueryParameter(conn.ReadRequestParams(), common.EncryptedTokenQueryParameter) if err == nil { @@ -102,48 +63,7 @@ func getUserID(conn userconn.UserConn, userIDPosition int) (string, error) { return userID, nil } -func handleEthError(req *common.RPCRequest, conn userconn.UserConn, logger gethlog.Logger, err error) { - var method string - id := json.RawMessage("1") - if req != nil { - method = req.Method - id = req.ID - } - - errjson := &common.JSONError{ - Code: 0, - Message: err.Error(), - Data: nil, - } - - jsonRPRCError := common.JSONRPCMessage{ - Version: "2.0", - ID: id, - Method: method, - Params: nil, - Error: errjson, - Result: nil, - } - - if evmError, ok := err.(errutil.EVMSerialisableError); ok { //nolint: errorlint - jsonRPRCError.Error.Data = evmError.Reason - jsonRPRCError.Error.Code = evmError.ErrorCode() - } - - errBytes, err := json.Marshal(jsonRPRCError) - if err != nil { - logger.Error("unable to marshal error - %w", log.ErrKey, err) - return - } - - logger.Info(fmt.Sprintf("Forwarding %s error response from Obscuro node: %s", method, errBytes)) - - if err = conn.WriteResponse(errBytes); err != nil { - logger.Error("unable to write response back", log.ErrKey, err) - } -} - -func handleError(conn userconn.UserConn, logger gethlog.Logger, err error) { +func handleError(conn UserConn, logger gethlog.Logger, err error) { logger.Warn("error processing request - Forwarding response to user", log.ErrKey, err) if err = conn.WriteResponse([]byte(err.Error())); err != nil { diff --git a/tools/walletextension/main/cli.go b/tools/walletextension/main/cli.go index b84b854d90..dbbe72a9a0 100644 --- a/tools/walletextension/main/cli.go +++ b/tools/walletextension/main/cli.go @@ -4,7 +4,7 @@ import ( "flag" "fmt" - "github.com/ten-protocol/go-ten/tools/walletextension/config" + wecommon "github.com/ten-protocol/go-ten/tools/walletextension/common" ) const ( @@ -61,7 +61,7 @@ const ( storeIncomingTxsUsage = "Flag to enable storing incoming transactions in the database for debugging purposes. Default: true" ) -func parseCLIArgs() config.Config { +func parseCLIArgs() wecommon.Config { walletExtensionHost := flag.String(walletExtensionHostName, walletExtensionHostDefault, walletExtensionHostUsage) walletExtensionPort := flag.Int(walletExtensionPortName, walletExtensionPortDefault, walletExtensionPortUsage) walletExtensionPortWS := flag.Int(walletExtensionPortWSName, walletExtensionPortWSDefault, walletExtensionPortWSUsage) @@ -77,7 +77,7 @@ func parseCLIArgs() config.Config { storeIncomingTransactions := flag.Bool(storeIncomingTxs, storeIncomingTxsDefault, storeIncomingTxsUsage) flag.Parse() - return config.Config{ + return wecommon.Config{ WalletExtensionHost: *walletExtensionHost, WalletExtensionPortHTTP: *walletExtensionPort, WalletExtensionPortWS: *walletExtensionPortWS, diff --git a/tools/walletextension/main/main.go b/tools/walletextension/main/main.go index b909f43368..4b200968c0 100644 --- a/tools/walletextension/main/main.go +++ b/tools/walletextension/main/main.go @@ -7,9 +7,10 @@ import ( "os" "time" + "github.com/ten-protocol/go-ten/tools/walletextension" + "github.com/ten-protocol/go-ten/go/common/log" "github.com/ten-protocol/go-ten/tools/walletextension/common" - "github.com/ten-protocol/go-ten/tools/walletextension/container" gethlog "github.com/ethereum/go-ethereum/log" ) @@ -58,7 +59,7 @@ func main() { } logger := log.New(log.WalletExtCmp, int(logLvl), config.LogPath) - walletExtContainer := container.NewWalletExtensionContainerFromConfig(config, logger) + walletExtContainer := walletextension.NewContainerFromConfig(config, logger) // Start the wallet extension. err := walletExtContainer.Start() diff --git a/tools/walletextension/rpcapi/blockchain_api.go b/tools/walletextension/rpcapi/blockchain_api.go new file mode 100644 index 0000000000..3c67716f3e --- /dev/null +++ b/tools/walletextension/rpcapi/blockchain_api.go @@ -0,0 +1,265 @@ +package rpcapi + +//goland:noinspection ALL +import ( + "context" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ten-protocol/go-ten/go/common/gethapi" + "github.com/ten-protocol/go-ten/lib/gethfork/rpc" + wecommon "github.com/ten-protocol/go-ten/tools/walletextension/common" +) + +type BlockChainAPI struct { + we *Services +} + +func NewBlockChainAPI(we *Services) *BlockChainAPI { + return &BlockChainAPI{we} +} + +func (api *BlockChainAPI) ChainId() *hexutil.Big { //nolint:stylecheck + // chainid, _ := UnauthenticatedTenRPCCall[hexutil.Big](nil, api.we, &CacheCfg{TTL: longCacheTTL}, "eth_chainId") + // return chainid + chainID := big.NewInt(int64(api.we.Config.TenChainID)) + return (*hexutil.Big)(chainID) +} + +func (api *BlockChainAPI) BlockNumber() hexutil.Uint64 { + nr, err := UnauthenticatedTenRPCCall[hexutil.Uint64](nil, api.we, &CacheCfg{TTL: shortCacheTTL}, "eth_blockNumber") + if err != nil { + return hexutil.Uint64(0) + } + return *nr +} + +func (api *BlockChainAPI) GetBalance(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Big, error) { + // todo - how do you handle getBalance for contracts + return ExecAuthRPC[hexutil.Big]( + ctx, + api.we, + &ExecCfg{ + cacheCfg: &CacheCfg{ + TTLCallback: func() time.Duration { + if blockNrOrHash.BlockNumber != nil && blockNrOrHash.BlockNumber.Int64() <= 0 { + return shortCacheTTL + } + return longCacheTTL + }, + }, + tryUntilAuthorised: true, // the user can request the balance of a contract account + }, + "eth_getBalance", + address, + blockNrOrHash, + ) +} + +// Result structs for GetProof +type AccountResult struct { + Address common.Address `json:"address"` + AccountProof []string `json:"accountProof"` + Balance *hexutil.Big `json:"balance"` + CodeHash common.Hash `json:"codeHash"` + Nonce hexutil.Uint64 `json:"nonce"` + StorageHash common.Hash `json:"storageHash"` + StorageProof []StorageResult `json:"storageProof"` +} + +type StorageResult struct { + Key string `json:"key"` + Value *hexutil.Big `json:"value"` + Proof []string `json:"proof"` +} + +/* + func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNrOrHash rpc.BlockNumberOrHash) (*AccountResult, error) { + // not implemented + return nil, nil + } +*/ +func (api *BlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (map[string]interface{}, error) { + resp, err := UnauthenticatedTenRPCCall[map[string]interface{}](ctx, api.we, &CacheCfg{TTLCallback: func() time.Duration { + if number > 0 { + return longCacheTTL + } + return shortCacheTTL + }}, "eth_getHeaderByNumber", number) + if resp == nil { + return nil, err + } + return *resp, err +} + +func (api *BlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.Hash) map[string]interface{} { + resp, _ := UnauthenticatedTenRPCCall[map[string]interface{}](ctx, api.we, &CacheCfg{TTL: longCacheTTL}, "eth_getHeaderByHash", hash) + if resp == nil { + return nil + } + return *resp +} + +func (api *BlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { + resp, err := UnauthenticatedTenRPCCall[map[string]interface{}]( + ctx, + api.we, + &CacheCfg{ + TTLCallback: func() time.Duration { + if number > 0 { + return longCacheTTL + } + return shortCacheTTL + }, + }, "eth_getBlockByNumber", number, fullTx) + if resp == nil { + return nil, err + } + return *resp, err +} + +func (api *BlockChainAPI) GetBlockByHash(ctx context.Context, hash common.Hash, fullTx bool) (map[string]interface{}, error) { + resp, err := UnauthenticatedTenRPCCall[map[string]interface{}](ctx, api.we, &CacheCfg{TTL: longCacheTTL}, "eth_getBlockByHash", hash, fullTx) + if resp == nil { + return nil, err + } + return *resp, err +} + +func (api *BlockChainAPI) GetCode(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) { + resp, err := ExecAuthRPC[hexutil.Bytes]( + ctx, + api.we, + &ExecCfg{ + cacheCfg: &CacheCfg{ + TTLCallback: func() time.Duration { + if blockNrOrHash.BlockNumber != nil && blockNrOrHash.BlockNumber.Int64() <= 0 { + return shortCacheTTL + } + return longCacheTTL + }, + }, + account: &address, + }, + "eth_getCode", + address, + blockNrOrHash, + ) + if resp == nil { + return nil, err + } + return *resp, err +} + +func (api *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Address, hexKey string, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) { + // GetStorageAt is repurposed to return the userID + if address.Hex() == wecommon.GetStorageAtUserIDRequestMethodName { + userID, err := extractUserID(ctx, api.we) + if err != nil { + return nil, err + } + + _, err = getUser(userID, api.we.Storage) + if err != nil { + return nil, err + } + return userID, nil + } + + resp, err := ExecAuthRPC[hexutil.Bytes](ctx, api.we, &ExecCfg{account: &address}, "eth_getStorageAt", address, hexKey, blockNrOrHash) + if resp == nil { + return nil, err + } + return *resp, err +} + +/* + func (s *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) { + // not implemented + return nil, nil + } +*/ +type OverrideAccount struct { + Nonce *hexutil.Uint64 `json:"nonce"` + Code *hexutil.Bytes `json:"code"` + Balance **hexutil.Big `json:"balance"` + State *map[common.Hash]common.Hash `json:"state"` + StateDiff *map[common.Hash]common.Hash `json:"stateDiff"` +} +type ( + StateOverride map[common.Address]OverrideAccount + BlockOverrides struct { + Number *hexutil.Big + Difficulty *hexutil.Big + Time *hexutil.Uint64 + GasLimit *hexutil.Uint64 + Coinbase *common.Address + Random *common.Hash + BaseFee *hexutil.Big + } +) + +func (api *BlockChainAPI) Call(ctx context.Context, args gethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, blockOverrides *BlockOverrides) (hexutil.Bytes, error) { + resp, err := ExecAuthRPC[hexutil.Bytes](ctx, api.we, &ExecCfg{ + cacheCfg: &CacheCfg{ + TTLCallback: func() time.Duration { + if blockNrOrHash.BlockNumber != nil && blockNrOrHash.BlockNumber.Int64() <= 0 { + return shortCacheTTL + } + return longCacheTTL + }, + }, + computeFromCallback: func(user *GWUser) *common.Address { + return searchFromAndData(user.GetAllAddresses(), args) + }, + adjustArgs: func(acct *GWAccount) []any { + // set the from + if args.From == nil { + args.From = acct.address + } + return []any{args, blockNrOrHash, overrides, blockOverrides} + }, + useDefaultUser: true, + }, "eth_call", args, blockNrOrHash, overrides, blockOverrides) + if resp == nil { + return nil, err + } + return *resp, err +} + +func (api *BlockChainAPI) EstimateGas(ctx context.Context, args gethapi.TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Uint64, error) { + resp, err := ExecAuthRPC[hexutil.Uint64](ctx, api.we, &ExecCfg{ + cacheCfg: &CacheCfg{ + TTLCallback: func() time.Duration { + if blockNrOrHash != nil && blockNrOrHash.BlockNumber != nil && blockNrOrHash.BlockNumber.Int64() <= 0 { + return shortCacheTTL + } + return longCacheTTL + }, + }, + computeFromCallback: func(user *GWUser) *common.Address { + return searchFromAndData(user.GetAllAddresses(), args) + }, + // is this a security risk? + useDefaultUser: true, + }, "eth_estimateGas", args, blockNrOrHash, overrides) + if resp == nil { + return 0, err + } + return *resp, err +} + +/* +type accessListResult struct { + Accesslist *types.AccessList `json:"accessList"` + Error string `json:"error,omitempty"` + GasUsed hexutil.Uint64 `json:"gasUsed"` +} + +func (s *BlockChainAPI) CreateAccessList(ctx context.Context, args gethapi.TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash) (*accessListResult, error) { + // not implemented + return nil, nil +} +*/ diff --git a/tools/walletextension/rpcapi/debug_api.go b/tools/walletextension/rpcapi/debug_api.go new file mode 100644 index 0000000000..3dcc2d0dcd --- /dev/null +++ b/tools/walletextension/rpcapi/debug_api.go @@ -0,0 +1,62 @@ +package rpcapi + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/common" +) + +type DebugAPI struct { + we *Services +} + +func NewDebugAPI(we *Services) *DebugAPI { + return &DebugAPI{we} +} + +/*func (api *DebugAPI) GetRawHeader(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) { + // not implemented + return nil, nil +} + +func (api *DebugAPI) GetRawBlock(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) { + // not implemented + return nil, nil +} + +func (api *DebugAPI) GetRawReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]hexutil.Bytes, error) { + // not implemented + return nil, nil +} + +func (s *DebugAPI) GetRawTransaction(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) { + // not implemented + return nil, nil +} + +func (api *DebugAPI) PrintBlock(ctx context.Context, number uint64) (string, error) { + // not implemented + return "", nil +} + +func (api *DebugAPI) ChaindbProperty(property string) (string, error) { + // not implemented + return "", nil +} + +func (api *DebugAPI) ChaindbCompact() error { + // not implemented + return nil +} + +func (api *DebugAPI) SetHead(number hexutil.Uint64) { + // not implemented +} +*/ + +// EventLogRelevancy - specific to Ten - todo +func (api *DebugAPI) EventLogRelevancy(_ context.Context, _ common.Hash) (interface{}, error) { + // todo + return nil, fmt.Errorf("not implemented") +} diff --git a/tools/walletextension/subscriptions/deduplication_circular_buffer.go b/tools/walletextension/rpcapi/deduplication_circular_buffer.go similarity index 98% rename from tools/walletextension/subscriptions/deduplication_circular_buffer.go rename to tools/walletextension/rpcapi/deduplication_circular_buffer.go index 52a2e23cfe..89b2f2614c 100644 --- a/tools/walletextension/subscriptions/deduplication_circular_buffer.go +++ b/tools/walletextension/rpcapi/deduplication_circular_buffer.go @@ -1,4 +1,4 @@ -package subscriptions +package rpcapi import "github.com/ethereum/go-ethereum/common" diff --git a/tools/walletextension/rpcapi/ethereum_api.go b/tools/walletextension/rpcapi/ethereum_api.go new file mode 100644 index 0000000000..724c7a6354 --- /dev/null +++ b/tools/walletextension/rpcapi/ethereum_api.go @@ -0,0 +1,60 @@ +package rpcapi + +import ( + "context" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ten-protocol/go-ten/lib/gethfork/rpc" +) + +type EthereumAPI struct { + we *Services +} + +func NewEthereumAPI(we *Services, +) *EthereumAPI { + return &EthereumAPI{we} +} + +func (api *EthereumAPI) GasPrice(ctx context.Context) (*hexutil.Big, error) { + return (*hexutil.Big)(big.NewInt(int64(0x3b9aca00))), nil + // return UnauthenticatedTenRPCCall[hexutil.Big](ctx, api.we, &CacheCfg{TTL: shortCacheTTL}, "eth_gasPrice") +} + +func (api *EthereumAPI) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, error) { + // todo + return UnauthenticatedTenRPCCall[hexutil.Big](ctx, api.we, nil, "eth_maxPriorityFeePerGas") +} + +type FeeHistoryResult struct { + OldestBlock *hexutil.Big `json:"oldestBlock"` + Reward [][]*hexutil.Big `json:"reward,omitempty"` + BaseFee []*hexutil.Big `json:"baseFeePerGas,omitempty"` + GasUsedRatio []float64 `json:"gasUsedRatio"` +} + +func (api *EthereumAPI) FeeHistory(ctx context.Context, blockCount math.HexOrDecimal64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*FeeHistoryResult, error) { + return UnauthenticatedTenRPCCall[FeeHistoryResult]( + ctx, + api.we, + &CacheCfg{TTLCallback: func() time.Duration { + if lastBlock > 0 { + return longCacheTTL + } + return shortCacheTTL + }}, + "eth_feeHistory", + blockCount, + lastBlock, + rewardPercentiles, + ) +} + +/*func (api *EthereumAPI) Syncing() (interface{}, error) { + // todo + return nil, nil +} +*/ diff --git a/tools/walletextension/rpcapi/filter_api.go b/tools/walletextension/rpcapi/filter_api.go new file mode 100644 index 0000000000..d4feeb0140 --- /dev/null +++ b/tools/walletextension/rpcapi/filter_api.go @@ -0,0 +1,249 @@ +package rpcapi + +import ( + "context" + "fmt" + "reflect" + "sync/atomic" + "time" + + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ten-protocol/go-ten/go/common" + + wecommon "github.com/ten-protocol/go-ten/tools/walletextension/common" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/filters" + "github.com/ten-protocol/go-ten/lib/gethfork/rpc" +) + +type FilterAPI struct { + we *Services +} + +func NewFilterAPI(we *Services) *FilterAPI { + return &FilterAPI{we: we} +} + +func (api *FilterAPI) NewPendingTransactionFilter(_ *bool) rpc.ID { + return "not supported" +} + +/*func (api *FilterAPI) NewPendingTransactions(ctx context.Context, fullTx *bool) (*rpc.Subscription, error) { + // not supported + return nil, nil +} +*/ + +func (api *FilterAPI) NewBlockFilter() rpc.ID { + // not implemented + return "" +} + +/* + func (api *FilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, error) { + // not implemented + return nil, nil + } +*/ +func (api *FilterAPI) Logs(ctx context.Context, crit filters.FilterCriteria) (*rpc.Subscription, error) { + subNotifier, user, err := getUserAndNotifier(ctx, api) + if err != nil { + return nil, err + } + + // determine the accounts to use for the backend subscriptions + candidateAddresses := user.GetAllAddresses() + if len(candidateAddresses) > 1 { + candidateAddresses = searchForAddressInFilterCriteria(crit, user.GetAllAddresses()) + // when we can't determine which addresses to use based on the criteria, use all of them + if len(candidateAddresses) == 0 { + candidateAddresses = user.GetAllAddresses() + } + } + + inputChannels := make([]chan common.IDAndLog, 0) + backendSubscriptions := make([]*rpc.ClientSubscription, 0) + for _, address := range candidateAddresses { + rpcWSClient, err := user.accounts[*address].connect(api.we.HostAddrWS, api.we.Logger()) + if err != nil { + return nil, err + } + + inCh := make(chan common.IDAndLog) + backendSubscription, err := rpcWSClient.Subscribe(ctx, nil, "eth", inCh, "logs", crit) + if err != nil { + return nil, err + } + + inputChannels = append(inputChannels, inCh) + backendSubscriptions = append(backendSubscriptions, backendSubscription) + } + + dedupeBuffer := NewCircularBuffer(wecommon.DeduplicationBufferSize) + subscription := subNotifier.CreateSubscription() + + unsubscribed := atomic.Bool{} + go forwardAndDedupe(inputChannels, backendSubscriptions, subscription, subNotifier, &unsubscribed, func(data common.IDAndLog) *types.Log { + uniqueLogKey := LogKey{ + BlockHash: data.Log.BlockHash, + TxHash: data.Log.TxHash, + Index: data.Log.Index, + } + + if !dedupeBuffer.Contains(uniqueLogKey) { + dedupeBuffer.Push(uniqueLogKey) + return data.Log + } + return nil + }) + + go handleUnsubscribe(subscription, backendSubscriptions, &unsubscribed) + + return subscription, err +} + +func getUserAndNotifier(ctx context.Context, api *FilterAPI) (*rpc.Notifier, *GWUser, error) { + subNotifier, supported := rpc.NotifierFromContext(ctx) + if !supported { + return nil, nil, fmt.Errorf("creation of subscriptions is not supported") + } + + // todo - we might want to allow access to public logs + if len(subNotifier.UserID) == 0 { + return nil, nil, fmt.Errorf("illegal access") + } + + uid, err := wecommon.GetUserIDbyte(subNotifier.UserID) + if err != nil { + return nil, nil, fmt.Errorf("invald token: %s, %w", subNotifier.UserID, err) + } + + user, err := getUser(uid, api.we.Storage) + if err != nil { + return nil, nil, fmt.Errorf("illegal access: %s, %w", subNotifier.UserID, err) + } + return subNotifier, user, nil +} + +func searchForAddressInFilterCriteria(filterCriteria filters.FilterCriteria, possibleAddresses []*gethcommon.Address) []*gethcommon.Address { + result := make([]*gethcommon.Address, 0) + addrMap := toMap(possibleAddresses) + for _, topicCondition := range filterCriteria.Topics { + for _, topic := range topicCondition { + potentialAddr := common.ExtractPotentialAddress(topic) + if potentialAddr != nil && addrMap[*potentialAddr] != nil { + result = append(result, potentialAddr) + } + } + } + return result +} + +// forwardAndDedupe - reads messages from the input channel, and forwards them to the notifier only if they are new +func forwardAndDedupe[R any, T any](inputChannels []chan R, _ []*rpc.ClientSubscription, outSub *rpc.Subscription, notifier *rpc.Notifier, unsubscribed *atomic.Bool, toForward func(elem R) *T) { + inputCases := make([]reflect.SelectCase, len(inputChannels)+1) + + // create a ticker to handle cleanup + inputCases[0] = reflect.SelectCase{ + Dir: reflect.SelectRecv, + Chan: reflect.ValueOf(time.NewTicker(10 * time.Second).C), + } + + // create a select "case" for each input channel + for i, ch := range inputChannels { + inputCases[i+1] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ch)} + } + + unclosedInputChannels := len(inputCases) + for unclosedInputChannels > 0 { + chosen, value, ok := reflect.Select(inputCases) + if !ok { + // The chosen channel has been closed, so zero out the channel to disable the case + inputCases[chosen].Chan = reflect.ValueOf(nil) + unclosedInputChannels-- + continue + } + + switch v := value.Interface().(type) { + case time.Time: + // exit the loop + if unsubscribed.Load() { + return + } + case R: + valueToSubmit := toForward(v) + if valueToSubmit != nil { + err := notifier.Notify(outSub.ID, *valueToSubmit) + if err != nil { + return + } + } + default: + // unexpected element received + continue + } + } +} + +func handleUnsubscribe(connectionSub *rpc.Subscription, backendSubscriptions []*rpc.ClientSubscription, unsubscribed *atomic.Bool) { + <-connectionSub.Err() + for _, backendSub := range backendSubscriptions { + backendSub.Unsubscribe() + unsubscribed.Store(true) + } +} + +/* + func (api *FilterAPI) NewFilter(crit filters.FilterCriteria) (rpc.ID, error) { + // not implemented + return "", nil + } +*/ +func (api *FilterAPI) GetLogs(ctx context.Context, crit filters.FilterCriteria) ([]*types.Log, error) { + // todo + logs, err := ExecAuthRPC[[]*types.Log]( + ctx, + api.we, + &ExecCfg{ + cacheCfg: &CacheCfg{ + TTLCallback: func() time.Duration { + // when the toBlock is not specified, the request is open-ended + if crit.ToBlock != nil { + return longCacheTTL + } + return shortCacheTTL + }, + }, + tryUntilAuthorised: true, + }, + "eth_getLogs", + crit, + ) + if logs != nil { + return *logs, err + } + return nil, err +} + +/*func (api *FilterAPI) UninstallFilter(id rpc.ID) bool { + // not implemented + return false +} + +func (api *FilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Log, error) { + //txRec, err := ExecAuthRPC[[]*types.Log](ctx, api.we, "GetFilterLogs", ExecCfg{account: args.From}, id) + //if txRec != nil { + // return *txRec, err + //} + //return common.Hash{}, err + + // not implemented + return nil, nil +} + +func (api *FilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { + // not implemented + return nil, nil +} +*/ diff --git a/tools/walletextension/rpcapi/from_tx_args.go b/tools/walletextension/rpcapi/from_tx_args.go new file mode 100644 index 0000000000..553a7bfffe --- /dev/null +++ b/tools/walletextension/rpcapi/from_tx_args.go @@ -0,0 +1,75 @@ +package rpcapi + +import ( + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/status-im/keycard-go/hexutils" + "github.com/ten-protocol/go-ten/go/common/gethapi" +) + +func searchFromAndData(possibleAddresses []*common.Address, args gethapi.TransactionArgs) *common.Address { + addressesMap := toMap(possibleAddresses) + + // todo - is this correct + //if args.From != nil && addressesMap[*args.From] != nil { + // return args.From + //} + if args.From != nil { + return args.From + } + + if args.Data == nil { + return nil + } + + // the "from" field is not mandatory, so we try to find the address in the data field + if args.From == nil { + return searchDataFieldForAccount(addressesMap, *args.Data) + } + + return nil +} + +func searchDataFieldForAccount(addressesMap map[common.Address]*common.Address, data []byte) *common.Address { + hexEncodedData := hexutils.BytesToHex(data) + + // We check that the data field is long enough before removing the leading "0x" (1 bytes/2 chars) and the method ID + // (4 bytes/8 chars). + if len(hexEncodedData) < 10 { + return nil + } + hexEncodedData = hexEncodedData[10:] + + // We split up the arguments in the `data` field. + var dataArgs []string + for i := 0; i < len(hexEncodedData); i += ethCallPaddedArgLen { + if i+ethCallPaddedArgLen > len(hexEncodedData) { + break + } + dataArgs = append(dataArgs, hexEncodedData[i:i+ethCallPaddedArgLen]) + } + + // We iterate over the arguments, looking for an argument that matches a viewing key address + for _, dataArg := range dataArgs { + // If the argument doesn't have the correct padding, it's not an address. + if !strings.HasPrefix(dataArg, ethCallAddrPadding) { + continue + } + + maybeAddress := common.HexToAddress(dataArg[len(ethCallAddrPadding):]) + if _, ok := addressesMap[maybeAddress]; ok { + return &maybeAddress + } + } + + return nil +} + +func toMap(possibleAddresses []*common.Address) map[common.Address]*common.Address { + addresses := map[common.Address]*common.Address{} + for i := range possibleAddresses { + addresses[*possibleAddresses[i]] = possibleAddresses[i] + } + return addresses +} diff --git a/tools/walletextension/rpcapi/gw_user.go b/tools/walletextension/rpcapi/gw_user.go new file mode 100644 index 0000000000..bc3fabb7b2 --- /dev/null +++ b/tools/walletextension/rpcapi/gw_user.go @@ -0,0 +1,65 @@ +package rpcapi + +import ( + "fmt" + + "github.com/ten-protocol/go-ten/go/common/viewingkey" + + "github.com/status-im/keycard-go/hexutils" + + "github.com/ethereum/go-ethereum/common" + gethlog "github.com/ethereum/go-ethereum/log" + "github.com/ten-protocol/go-ten/go/rpc" + wecommon "github.com/ten-protocol/go-ten/tools/walletextension/common" + "github.com/ten-protocol/go-ten/tools/walletextension/storage" +) + +type GWAccount struct { + user *GWUser + address *common.Address + signature []byte + signatureType viewingkey.SignatureType +} + +type GWUser struct { + userID []byte + accounts map[common.Address]*GWAccount + userKey []byte +} + +func (u GWUser) GetAllAddresses() []*common.Address { + accts := make([]*common.Address, 0) + for _, acc := range u.accounts { + accts = append(accts, acc.address) + } + return accts +} + +func getUser(userID []byte, s storage.Storage) (*GWUser, error) { + result := GWUser{userID: userID, accounts: map[common.Address]*GWAccount{}} + userPrivateKey, err := s.GetUserPrivateKey(userID) + if err != nil { + return nil, fmt.Errorf("user %s not found. %w", hexutils.BytesToHex(userID), err) + } + result.userKey = userPrivateKey + allAccounts, err := s.GetAccounts(userID) + if err != nil { + return nil, err + } + + for _, account := range allAccounts { + address := common.BytesToAddress(account.AccountAddress) + result.accounts[address] = &GWAccount{user: &result, address: &address, signature: account.Signature, signatureType: viewingkey.SignatureType(uint8(account.SignatureType))} + } + return &result, nil +} + +func (account *GWAccount) connect(url string, logger gethlog.Logger) (*rpc.EncRPCClient, error) { + // create a new client + // todo - close and cache + encClient, err := wecommon.CreateEncClient(url, account.address.Bytes(), account.user.userKey, account.signature, account.signatureType, logger) + if err != nil { + return nil, fmt.Errorf("error creating new client, %w", err) + } + return encClient, nil +} diff --git a/tools/walletextension/rpcapi/net_api.go b/tools/walletextension/rpcapi/net_api.go new file mode 100644 index 0000000000..cb3fa5a69e --- /dev/null +++ b/tools/walletextension/rpcapi/net_api.go @@ -0,0 +1,25 @@ +package rpcapi + +/* +type NetAPI struct{} + +// NewNetAPI creates a new net API instance. +func NewNetAPI() *NetAPI { + return &NetAPI{} +} + +// Listening returns an indication if the node is listening for network connections. +func (s *NetAPI) Listening() bool { + return true // always listening +} + +// PeerCount returns the number of connected peers +func (s *NetAPI) PeerCount() hexutil.Uint { + return 0 +} + +// Version returns the current ethereum protocol version. +func (s *NetAPI) Version() string { + return "1" +} +*/ diff --git a/tools/walletextension/rpcapi/transaction_api.go b/tools/walletextension/rpcapi/transaction_api.go new file mode 100644 index 0000000000..d9f3078388 --- /dev/null +++ b/tools/walletextension/rpcapi/transaction_api.go @@ -0,0 +1,152 @@ +package rpcapi + +//goland:noinspection ALL +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ten-protocol/go-ten/go/common/gethapi" + "github.com/ten-protocol/go-ten/go/enclave/rpc" + gethrpc "github.com/ten-protocol/go-ten/lib/gethfork/rpc" +) + +type TransactionAPI struct { + we *Services +} + +func NewTransactionAPI(we *Services) *TransactionAPI { + return &TransactionAPI{we} +} + +func (s *TransactionAPI) GetBlockTransactionCountByNumber(ctx context.Context, blockNr gethrpc.BlockNumber) *hexutil.Uint { + count, err := UnauthenticatedTenRPCCall[hexutil.Uint](ctx, s.we, &CacheCfg{TTLCallback: func() time.Duration { + if blockNr > 0 { + return longCacheTTL + } + return shortCacheTTL + }}, "eth_getBlockTransactionCountByNumber", blockNr) + if err != nil { + return nil + } + return count +} + +func (s *TransactionAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint { + count, err := UnauthenticatedTenRPCCall[hexutil.Uint](ctx, s.we, &CacheCfg{TTL: longCacheTTL}, "eth_getBlockTransactionCountByHash", blockHash) + if err != nil { + return nil + } + return count +} + +/*func (s *TransactionAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNr gethrpc.BlockNumber, index hexutil.Uint) *rpc.RpcTransaction { + // not implemented + return nil +} + +func (s *TransactionAPI) GetTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) *rpc.RpcTransaction { + // not implemented + return nil +} + +func (s *TransactionAPI) GetRawTransactionByBlockNumberAndIndex(ctx context.Context, blockNr gethrpc.BlockNumber, index hexutil.Uint) hexutil.Bytes { + // not implemented + return nil +} + +func (s *TransactionAPI) GetRawTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) hexutil.Bytes { + // not implemented + return nil +}*/ + +func (s *TransactionAPI) GetTransactionCount(ctx context.Context, address common.Address, blockNrOrHash gethrpc.BlockNumberOrHash) (*hexutil.Uint64, error) { + return ExecAuthRPC[hexutil.Uint64](ctx, s.we, &ExecCfg{account: &address, useDefaultUser: true}, "eth_getTransactionCount", address, blockNrOrHash) +} + +func (s *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) (*rpc.RpcTransaction, error) { + return ExecAuthRPC[rpc.RpcTransaction](ctx, s.we, &ExecCfg{tryAll: true, useDefaultUser: true}, "eth_getTransactionByHash", hash) +} + +func (s *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) { + tx, err := ExecAuthRPC[hexutil.Bytes](ctx, s.we, &ExecCfg{tryAll: true}, "eth_getRawTransactionByHash", hash) + if tx != nil { + return *tx, err + } + return nil, err +} + +func (s *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) { + txRec, err := ExecAuthRPC[map[string]interface{}](ctx, s.we, &ExecCfg{tryUntilAuthorised: true, useDefaultUser: true}, "eth_getTransactionReceipt", hash) + if err != nil { + return nil, err + } + if txRec == nil { + return nil, err + } + return *txRec, err +} + +func (s *TransactionAPI) SendTransaction(ctx context.Context, args gethapi.TransactionArgs) (common.Hash, error) { + txRec, err := ExecAuthRPC[common.Hash](ctx, s.we, &ExecCfg{account: args.From, useDefaultUser: true}, "eth_sendTransaction", args) + if err != nil { + return common.Hash{}, err + } + userIDBytes, _ := extractUserID(ctx, s.we) + if s.we.Config.StoreIncomingTxs && len(userIDBytes) > 10 { + tx, err := json.Marshal(args) + if err != nil { + s.we.Logger().Error("error marshalling transaction: %s", err) + return *txRec, nil + } + err = s.we.Storage.StoreTransaction(string(tx), userIDBytes) + if err != nil { + s.we.Logger().Error("error storing transaction in the database: %s", err) + return *txRec, nil + } + } + return *txRec, err +} + +type SignTransactionResult struct { + Raw hexutil.Bytes `json:"raw"` + Tx *types.Transaction `json:"tx"` +} + +/*func (s *TransactionAPI) FillTransaction(ctx context.Context, args gethapi.TransactionArgs) (*SignTransactionResult, error) { + // not implemented + return nil, nil +}*/ + +func (s *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) { + txRec, err := ExecAuthRPC[common.Hash](ctx, s.we, &ExecCfg{tryAll: true, useDefaultUser: true}, "eth_sendRawTransaction", input) + if err != nil { + return common.Hash{}, err + } + userIDBytes, err := extractUserID(ctx, s.we) + if s.we.Config.StoreIncomingTxs && len(userIDBytes) > 10 { + err = s.we.Storage.StoreTransaction(input.String(), userIDBytes) + if err != nil { + s.we.Logger().Error(fmt.Errorf("error storing transaction in the database: %w", err).Error()) + } + } + return *txRec, err +} + +/* + func (s *TransactionAPI) PendingTransactions() ([]*rpc.RpcTransaction, error) { + // not implemented + return nil, nil + } +*/ +func (s *TransactionAPI) Resend(ctx context.Context, sendArgs gethapi.TransactionArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error) { + txRec, err := ExecAuthRPC[common.Hash](ctx, s.we, &ExecCfg{account: sendArgs.From}, "eth_resend", sendArgs, gasPrice, gasLimit) + if txRec != nil { + return *txRec, err + } + return common.Hash{}, err +} diff --git a/tools/walletextension/rpcapi/txpool_api.go b/tools/walletextension/rpcapi/txpool_api.go new file mode 100644 index 0000000000..191b1c9aab --- /dev/null +++ b/tools/walletextension/rpcapi/txpool_api.go @@ -0,0 +1,45 @@ +package rpcapi + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + rpc2 "github.com/ten-protocol/go-ten/go/enclave/rpc" +) + +// todo +type TxPoolAPI struct { + we *Services +} + +func NewTxPoolAPI(we *Services) *TxPoolAPI { + return &TxPoolAPI{we} +} + +func (s *TxPoolAPI) Content() map[string]map[string]map[string]*rpc2.RpcTransaction { + content := map[string]map[string]map[string]*rpc2.RpcTransaction{ + "pending": make(map[string]map[string]*rpc2.RpcTransaction), + "queued": make(map[string]map[string]*rpc2.RpcTransaction), + } + return content +} + +func (s *TxPoolAPI) ContentFrom(_ common.Address) map[string]map[string]*rpc2.RpcTransaction { + content := make(map[string]map[string]*rpc2.RpcTransaction, 2) + return content +} + +func (s *TxPoolAPI) Status() map[string]hexutil.Uint { + pending, queue := 0, 0 + return map[string]hexutil.Uint{ + "pending": hexutil.Uint(pending), + "queued": hexutil.Uint(queue), + } +} + +func (s *TxPoolAPI) Inspect() map[string]map[string]map[string]string { + content := map[string]map[string]map[string]string{ + "pending": make(map[string]map[string]string), + "queued": make(map[string]map[string]string), + } + return content +} diff --git a/tools/walletextension/rpcapi/utils.go b/tools/walletextension/rpcapi/utils.go new file mode 100644 index 0000000000..33533a455e --- /dev/null +++ b/tools/walletextension/rpcapi/utils.go @@ -0,0 +1,228 @@ +package rpcapi + +import ( + "context" + "crypto/sha256" + "encoding/json" + "fmt" + "time" + + "github.com/ten-protocol/go-ten/lib/gethfork/rpc" + + "github.com/status-im/keycard-go/hexutils" + + "github.com/ten-protocol/go-ten/tools/walletextension/cache" + + "github.com/ethereum/go-ethereum/common" + wecommon "github.com/ten-protocol/go-ten/tools/walletextension/common" +) + +const ( + ethCallPaddedArgLen = 64 + ethCallAddrPadding = "000000000000000000000000" + + notAuthorised = "not authorised" + + longCacheTTL = 5 * time.Hour + shortCacheTTL = 1 * time.Second +) + +type ExecCfg struct { + account *common.Address + computeFromCallback func(user *GWUser) *common.Address + tryAll bool + tryUntilAuthorised bool + adjustArgs func(acct *GWAccount) []any + cacheCfg *CacheCfg + useDefaultUser bool +} + +type CacheCfg struct { + // ResetWhenNewBlock bool todo + TTL time.Duration + // logic based on block + // todo - handle block in the future + TTLCallback func() time.Duration +} + +func UnauthenticatedTenRPCCall[R any](ctx context.Context, w *Services, cfg *CacheCfg, method string, args ...any) (*R, error) { + audit(w, "RPC start method=%s args=%v", method, args) + requestStartTime := time.Now() + res, err := withCache(w.Cache, cfg, args, func() (*R, error) { + var resp *R + unauthedRPC, err := w.UnauthenticatedClient() + if err != nil { + return nil, err + } + if ctx == nil { + err = unauthedRPC.Call(&resp, method, args...) + return resp, err + } + err = unauthedRPC.CallContext(ctx, &resp, method, args...) + return resp, err + }) + defer audit(w, "RPC call. method=%s args=%v result=%s error=%s time=%d", method, args, res, err, time.Since(requestStartTime).Milliseconds()) + return res, err +} + +func ExecAuthRPC[R any](ctx context.Context, w *Services, cfg *ExecCfg, method string, args ...any) (*R, error) { + audit(w, "RPC start method=%s args=%v", method, args) + requestStartTime := time.Now() + userID, err := extractUserID(ctx, w) + if err != nil { + return nil, err + } + + user, err := getUser(userID, w.Storage) + if err != nil { + return nil, err + } + + cacheKey := make([]any, 0) + cacheKey = append(cacheKey, userID) + cacheKey = append(cacheKey, method) + cacheKey = append(cacheKey, args...) + + res, err := withCache(w.Cache, cfg.cacheCfg, cacheKey, func() (*R, error) { + // determine candidate "from" + candidateAccts, err := getCandidateAccounts(user, w, cfg) + if err != nil { + return nil, err + } + if len(candidateAccts) == 0 { + return nil, fmt.Errorf("illegal access") + } + + var rpcErr error + for _, acct := range candidateAccts { + var result *R + rpcClient, err := acct.connect(w.HostAddrHTTP, w.Logger()) + if err != nil { + rpcErr = err + continue + } + adjustedArgs := args + if cfg.adjustArgs != nil { + adjustedArgs = cfg.adjustArgs(acct) + } + err = rpcClient.CallContext(ctx, &result, method, adjustedArgs...) + if err != nil { + // todo - is this correct? + if cfg.tryUntilAuthorised && err.Error() != notAuthorised { + return nil, err + } + rpcErr = err + continue + } + return result, nil + } + return nil, rpcErr + }) + defer audit(w, "RPC call. uid=%s, method=%s args=%v result=%s error=%s time=%d", hexutils.BytesToHex(userID), method, args, res, err, time.Since(requestStartTime).Milliseconds()) + return res, err +} + +func getCandidateAccounts(user *GWUser, w *Services, cfg *ExecCfg) ([]*GWAccount, error) { + candidateAccts := make([]*GWAccount, 0) + // for users with multiple accounts determine a candidate account + switch { + case cfg.account != nil: + acc := user.accounts[*cfg.account] + if acc != nil { + candidateAccts = append(candidateAccts, acc) + } + + case cfg.computeFromCallback != nil: + addr := cfg.computeFromCallback(user) + if addr == nil { + return nil, fmt.Errorf("invalid request") + } + acc := user.accounts[*addr] + if acc != nil { + candidateAccts = append(candidateAccts, acc) + } + + case cfg.tryAll, cfg.tryUntilAuthorised: + for _, acc := range user.accounts { + candidateAccts = append(candidateAccts, acc) + } + + default: + return nil, fmt.Errorf("programming error. invalid owner detection strategy") + } + + // when there is no matching address, some calls, like submitting a transactions are allowed to go through + // todo - remove + //if len(candidateAccts) == 0 && cfg.useDefaultUser { + // defaultUser, err := getUser(w.DefaultUser, w.Storage) + // if err != nil { + // panic(err) + // } + // defaultAcct := defaultUser.GetAllAddresses()[0] + // candidateAccts = append(candidateAccts, defaultUser.accounts[*defaultAcct]) + //} + return candidateAccts, nil +} + +func extractUserID(ctx context.Context, w *Services) ([]byte, error) { + userID, ok := ctx.Value(rpc.GWTokenKey{}).(string) + if !ok { + return w.DefaultUser, nil + // return nil, fmt.Errorf("invalid encryption token %s", userID) + } + return wecommon.GetUserIDbyte(userID) +} + +// generateCacheKey generates a cache key for the given method, encryptionToken and parameters +// encryptionToken is used to generate a unique cache key for each user and empty string should be used for public data +func generateCacheKey(params []any) []byte { + // Serialize parameters + rawKey, err := json.Marshal(params) + if err != nil { + return nil + } + + // Optional: Apply hashing + hasher := sha256.New() + hasher.Write(rawKey) + + return hasher.Sum(nil) +} + +func withCache[R any](cache cache.Cache, cfg *CacheCfg, args []any, onCacheMiss func() (*R, error)) (*R, error) { + if cfg == nil { + return onCacheMiss() + } + + cacheTTL := cfg.TTL + if cfg.TTLCallback != nil { + cacheTTL = cfg.TTLCallback() + } + isCacheable := cacheTTL > 0 + + var cacheKey []byte + if isCacheable { + cacheKey = generateCacheKey(args) + if cachedValue, ok := cache.Get(cacheKey); ok { + // cloning? + returnValue := cachedValue.(*R) + return returnValue, nil + } + } + + result, err := onCacheMiss() + + // cache only non-nil values + if isCacheable && err == nil && result != nil { + cache.Set(cacheKey, result, cacheTTL) + } + + return result, err +} + +func audit(services *Services, msg string, params ...any) { + println(fmt.Sprintf(msg, params...)) + if services.Config.VerboseFlag { + services.FileLogger.Info(fmt.Sprintf(msg, params...)) + } +} diff --git a/tools/walletextension/rpcapi/wallet_extension.go b/tools/walletextension/rpcapi/wallet_extension.go new file mode 100644 index 0000000000..f6bd0ae12c --- /dev/null +++ b/tools/walletextension/rpcapi/wallet_extension.go @@ -0,0 +1,303 @@ +package rpcapi + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + "math/big" + "time" + + "github.com/ten-protocol/go-ten/go/wallet" + + "github.com/ten-protocol/go-ten/tools/walletextension/cache" + + "github.com/ten-protocol/go-ten/go/obsclient" + + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/ecies" + gethlog "github.com/ethereum/go-ethereum/log" + "github.com/ten-protocol/go-ten/go/common/stopcontrol" + "github.com/ten-protocol/go-ten/go/common/viewingkey" + "github.com/ten-protocol/go-ten/go/rpc" + "github.com/ten-protocol/go-ten/tools/walletextension/common" + "github.com/ten-protocol/go-ten/tools/walletextension/storage" +) + +// Services handles the various business logic for the api endpoints +type Services struct { + HostAddrHTTP string // The HTTP address on which the Ten host can be reached + HostAddrWS string // The WS address on which the Ten host can be reached + unsignedVKs map[gethcommon.Address]*viewingkey.ViewingKey // Map temporarily holding VKs that have been generated but not yet signed + Storage storage.Storage + logger gethlog.Logger + FileLogger gethlog.Logger + stopControl *stopcontrol.StopControl + version string + tenClient *obsclient.ObsClient + Cache cache.Cache + Config *common.Config + DefaultUser []byte +} + +func NewServices(hostAddrHTTP string, hostAddrWS string, storage storage.Storage, stopControl *stopcontrol.StopControl, version string, logger gethlog.Logger, config *common.Config) *Services { + rpcClient, err := rpc.NewNetworkClient(hostAddrHTTP) + if err != nil { + logger.Error(fmt.Errorf("could not create RPC client on %s. Cause: %w", hostAddrHTTP, err).Error()) + panic(err) + } + newTenClient := obsclient.NewObsClient(rpcClient) + newFileLogger := common.NewFileLogger() + newGatewayCache, err := cache.NewCache(logger) + if err != nil { + logger.Error(fmt.Errorf("could not create cache. Cause: %w", err).Error()) + panic(err) + } + + userID := addDefaultUser(config, logger, storage) + + return &Services{ + HostAddrHTTP: hostAddrHTTP, + HostAddrWS: hostAddrWS, + unsignedVKs: map[gethcommon.Address]*viewingkey.ViewingKey{}, + Storage: storage, + logger: logger, + FileLogger: newFileLogger, + stopControl: stopControl, + version: version, + tenClient: newTenClient, + Cache: newGatewayCache, + Config: config, + DefaultUser: userID, + } +} + +func addDefaultUser(config *common.Config, logger gethlog.Logger, storage storage.Storage) []byte { + userAccountKey, err := crypto.GenerateKey() + if err != nil { + panic(fmt.Errorf("error generating default user key")) + } + + wallet := wallet.NewInMemoryWalletFromPK(big.NewInt(int64(config.TenChainID)), userAccountKey, logger) + vk, err := viewingkey.GenerateViewingKeyForWallet(wallet) + if err != nil { + panic(err) + } + + // create UserID and store it in the database with the private key + userID := viewingkey.CalculateUserID(common.PrivateKeyToCompressedPubKey(vk.PrivateKey)) + + err = storage.AddUser(userID, crypto.FromECDSA(vk.PrivateKey.ExportECDSA())) + if err != nil { + panic(fmt.Errorf("error saving default user")) + } + + err = storage.AddAccount(userID, vk.Account.Bytes(), vk.SignatureWithAccountKey, viewingkey.Legacy) + if err != nil { + panic(fmt.Errorf("error saving account %s for default user %s", vk.Account.Hex(), userID)) + } + return userID +} + +// IsStopping returns whether the WE is stopping +func (w *Services) IsStopping() bool { + return w.stopControl.IsStopping() +} + +// Logger returns the WE set logger +func (w *Services) Logger() gethlog.Logger { + return w.logger +} + +// todo - once the logic in routes has been moved to RPC functions, these methods can be moved there + +// GenerateViewingKey generates the user viewing key and waits for signature +func (w *Services) GenerateViewingKey(addr gethcommon.Address) (string, error) { + // Requested to generate viewing key for address(old way): %s", addr.Hex())) + viewingKeyPrivate, err := crypto.GenerateKey() + if err != nil { + return "", fmt.Errorf("unable to generate a new keypair - %w", err) + } + + viewingPublicKeyBytes := crypto.CompressPubkey(&viewingKeyPrivate.PublicKey) + viewingPrivateKeyEcies := ecies.ImportECDSA(viewingKeyPrivate) + + w.unsignedVKs[addr] = &viewingkey.ViewingKey{ + Account: &addr, + PrivateKey: viewingPrivateKeyEcies, + PublicKey: viewingPublicKeyBytes, + SignatureWithAccountKey: nil, // we await a signature from the user before we can set up the EncRPCClient + } + + // compress the viewing key and convert it to hex string ( this is what Metamask signs) + viewingKeyBytes := crypto.CompressPubkey(&viewingKeyPrivate.PublicKey) + return hex.EncodeToString(viewingKeyBytes), nil +} + +// SubmitViewingKey checks the signed viewing key and stores it +func (w *Services) SubmitViewingKey(address gethcommon.Address, signature []byte) error { + audit(w, "Requested to submit a viewing key (old way): %s", address.Hex()) + vk, found := w.unsignedVKs[address] + if !found { + return fmt.Errorf(fmt.Sprintf("no viewing key found to sign for acc=%s, please call %s to generate key before sending signature", address, common.PathGenerateViewingKey)) + } + + // We transform the V from 27/28 to 0/1. This same change is made in Geth internals, for legacy reasons to be able + // to recover the address: https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L452-L459 + signature[64] -= 27 + + vk.SignatureWithAccountKey = signature + + //err := w.Storage.AddUser(defaultUserId, crypto.FromECDSA(vk.PrivateKey.ExportECDSA())) + //if err != nil { + // return fmt.Errorf("error saving user: %s", common.DefaultUser) + //} + // + //err = w.Storage.AddAccount(defaultUserId, vk.Account.Bytes(), vk.SignatureWithAccountKey) + //if err != nil { + // return fmt.Errorf("error saving account %s for user %s", vk.Account.Hex(), common.DefaultUser) + //} + + // finally we remove the VK from the pending 'unsigned VKs' map now the client has been created + delete(w.unsignedVKs, address) + + return nil +} + +// GenerateAndStoreNewUser generates new key-pair and userID, stores it in the database and returns hex encoded userID and error +func (w *Services) GenerateAndStoreNewUser() (string, error) { + requestStartTime := time.Now() + // generate new key-pair + viewingKeyPrivate, err := crypto.GenerateKey() + viewingPrivateKeyEcies := ecies.ImportECDSA(viewingKeyPrivate) + if err != nil { + w.Logger().Error(fmt.Sprintf("could not generate new keypair: %s", err)) + return "", err + } + + // create UserID and store it in the database with the private key + userID := viewingkey.CalculateUserID(common.PrivateKeyToCompressedPubKey(viewingPrivateKeyEcies)) + err = w.Storage.AddUser(userID, crypto.FromECDSA(viewingPrivateKeyEcies.ExportECDSA())) + if err != nil { + w.Logger().Error(fmt.Sprintf("failed to save user to the database: %s", err)) + return "", err + } + + hexUserID := hex.EncodeToString(userID) + + requestEndTime := time.Now() + duration := requestEndTime.Sub(requestStartTime) + audit(w, "Storing new userID: %s, duration: %d ", hexUserID, duration.Milliseconds()) + return hexUserID, nil +} + +// AddAddressToUser checks if a message is in correct format and if signature is valid. If all checks pass we save address and signature against userID +func (w *Services) AddAddressToUser(hexUserID string, address string, signature []byte, signatureType viewingkey.SignatureType) error { + requestStartTime := time.Now() + addressFromMessage := gethcommon.HexToAddress(address) + // check if a message was signed by the correct address and if the signature is valid + _, err := viewingkey.CheckSignature(hexUserID, signature, int64(w.Config.TenChainID), signatureType) + if err != nil { + return fmt.Errorf("signature is not valid: %w", err) + } + + // register the account for that viewing key + userIDBytes, err := common.GetUserIDbyte(hexUserID) + if err != nil { + w.Logger().Error(fmt.Errorf("error decoding string (%s), %w", hexUserID[2:], err).Error()) + return errors.New("error decoding userID. It should be in hex format") + } + err = w.Storage.AddAccount(userIDBytes, addressFromMessage.Bytes(), signature, signatureType) + if err != nil { + w.Logger().Error(fmt.Errorf("error while storing account (%s) for user (%s): %w", addressFromMessage.Hex(), hexUserID, err).Error()) + return err + } + + requestEndTime := time.Now() + duration := requestEndTime.Sub(requestStartTime) + audit(w, "Storing new address for user: %s, address: %s, duration: %d ", hexUserID, address, duration.Milliseconds()) + return nil +} + +// UserHasAccount checks if provided account exist in the database for given userID +func (w *Services) UserHasAccount(hexUserID string, address string) (bool, error) { + audit(w, "Checkinf if user has account: %s, address: %s", hexUserID, address) + userIDBytes, err := common.GetUserIDbyte(hexUserID) + if err != nil { + w.Logger().Error(fmt.Errorf("error decoding string (%s), %w", hexUserID[2:], err).Error()) + return false, err + } + + addressBytes, err := hex.DecodeString(address[2:]) // remove 0x prefix from address + if err != nil { + w.Logger().Error(fmt.Errorf("error decoding string (%s), %w", address[2:], err).Error()) + return false, err + } + + // todo - this can be optimised and done in the database if we will have users with large number of accounts + // get all the accounts for the selected user + accounts, err := w.Storage.GetAccounts(userIDBytes) + if err != nil { + w.Logger().Error(fmt.Errorf("error getting accounts for user (%s), %w", hexUserID, err).Error()) + return false, err + } + + // check if any of the account matches given account + found := false + for _, account := range accounts { + if bytes.Equal(account.AccountAddress, addressBytes) { + found = true + } + } + return found, nil +} + +// DeleteUser deletes user and accounts associated with user from the database for given userID +func (w *Services) DeleteUser(hexUserID string) error { + audit(w, "Deleting user: %s", hexUserID) + userIDBytes, err := common.GetUserIDbyte(hexUserID) + if err != nil { + w.Logger().Error(fmt.Errorf("error decoding string (%s), %w", hexUserID, err).Error()) + return err + } + + err = w.Storage.DeleteUser(userIDBytes) + if err != nil { + w.Logger().Error(fmt.Errorf("error deleting user (%s), %w", hexUserID, err).Error()) + return err + } + + return nil +} + +func (w *Services) UserExists(hexUserID string) bool { + audit(w, "Checking if user exists: %s", hexUserID) + userIDBytes, err := common.GetUserIDbyte(hexUserID) + if err != nil { + w.Logger().Error(fmt.Errorf("error decoding string (%s), %w", hexUserID, err).Error()) + return false + } + + // Check if user exists and don't log error if user doesn't exist, because we expect this to happen in case of + // user revoking encryption token or using different testnet. + // todo add a counter here in the future + key, err := w.Storage.GetUserPrivateKey(userIDBytes) + if err != nil { + return false + } + + return len(key) > 0 +} + +func (w *Services) Version() string { + return w.version +} + +func (w *Services) GetTenNodeHealthStatus() (bool, error) { + return w.tenClient.Health() +} + +func (w *Services) UnauthenticatedClient() (rpc.Client, error) { + return rpc.NewNetworkClient(w.HostAddrHTTP) +} diff --git a/tools/walletextension/subscriptions/subscriptions.go b/tools/walletextension/subscriptions/subscriptions.go deleted file mode 100644 index 7d3fe3f7a4..0000000000 --- a/tools/walletextension/subscriptions/subscriptions.go +++ /dev/null @@ -1,202 +0,0 @@ -package subscriptions - -import ( - "context" - "encoding/json" - "fmt" - "sync" - "time" - - "github.com/go-kit/kit/transport/http/jsonrpc" - - gethlog "github.com/ethereum/go-ethereum/log" - "github.com/ten-protocol/go-ten/go/common" - "github.com/ten-protocol/go-ten/go/common/log" - "github.com/ten-protocol/go-ten/go/rpc" - gethrpc "github.com/ten-protocol/go-ten/lib/gethfork/rpc" - wecommon "github.com/ten-protocol/go-ten/tools/walletextension/common" - "github.com/ten-protocol/go-ten/tools/walletextension/userconn" -) - -type SubscriptionManager struct { - subscriptionMappings map[string][]*gethrpc.ClientSubscription - logger gethlog.Logger - mu sync.Mutex -} - -func New(logger gethlog.Logger) *SubscriptionManager { - return &SubscriptionManager{ - subscriptionMappings: make(map[string][]*gethrpc.ClientSubscription), - logger: logger, - } -} - -// HandleNewSubscriptions subscribes to an event with all the clients provided. -// Doing this is necessary because we have relevancy rule, and we want to subscribe sometimes with all clients to get all the events -func (sm *SubscriptionManager) HandleNewSubscriptions(clients []rpc.Client, req *wecommon.RPCRequest, resp *interface{}, userConn userconn.UserConn) error { - if len(req.Params) == 0 { - return fmt.Errorf("could not subscribe as no subscription namespace was provided") - } - - sm.logger.Info(fmt.Sprintf("Subscribing to event %s with %d clients", req.Params, len(clients))) - - // create subscriptionID which will enable user to unsubscribe from all subscriptions - userSubscriptionID := gethrpc.NewID() - - // create a common channel for subscriptions from all accounts - funnelMultipleAccountsChan := make(chan common.IDAndLog) - - // read from a multiple accounts channel and write results to userConn - go readFromChannelAndWriteToUserConn(funnelMultipleAccountsChan, userConn, userSubscriptionID, sm.logger) - - // iterate over all clients and subscribe for each of them - for _, client := range clients { - subscription, err := client.Subscribe(context.Background(), resp, rpc.SubscribeNamespace, funnelMultipleAccountsChan, req.Params...) - if err != nil { - return fmt.Errorf("could not call %s with params %v. Cause: %w", req.Method, req.Params, err) - } - sm.UpdateSubscriptionMapping(string(userSubscriptionID), subscription) - - // We periodically check if the websocket is closed, and terminate the subscription. - // TODO: Check if it will be much more efficient to create just one go routine for all clients together - go sm.checkIfUserConnIsClosedAndUnsubscribe(userConn, subscription, string(userSubscriptionID)) - } - - // We return subscriptionID with resp interface. We want to use userSubscriptionID to allow unsubscribing - *resp = userSubscriptionID - return nil -} - -func readFromChannelAndWriteToUserConn(channel chan common.IDAndLog, userConn userconn.UserConn, userSubscriptionID gethrpc.ID, logger gethlog.Logger) { - buffer := NewCircularBuffer(wecommon.DeduplicationBufferSize) - for data := range channel { - // create unique identifier for current log - uniqueLogKey := LogKey{ - BlockHash: data.Log.BlockHash, - TxHash: data.Log.TxHash, - Index: data.Log.Index, - } - - // check if the current event is a duplicate (and skip it if it is) - if buffer.Contains(uniqueLogKey) { - continue - } - - jsonResponse, err := prepareLogResponse(data, userSubscriptionID) - if err != nil { - logger.Error("could not marshal log response to JSON on subscription.", log.SubIDKey, data.SubID, log.ErrKey, err) - continue - } - - // the current log is unique, and we want to add it to our buffer and proceed with forwarding to the user - buffer.Push(uniqueLogKey) - - logger.Trace(fmt.Sprintf("Forwarding log from Obscuro node: %s", jsonResponse), log.SubIDKey, data.SubID) - err = userConn.WriteResponse(jsonResponse) - if err != nil { - logger.Error("could not write the JSON log to the websocket on subscription %", log.SubIDKey, data.SubID, log.ErrKey, err) - continue - } - } -} - -func (sm *SubscriptionManager) unsubscribeAndRemove(userSubscriptionID string, subscription *gethrpc.ClientSubscription) { - sm.mu.Lock() - defer sm.mu.Unlock() - - subscription.Unsubscribe() - - subscriptions, exists := sm.subscriptionMappings[userSubscriptionID] - if !exists { - sm.logger.Error("subscription that needs to be removed is not present in subscriptionMappings for userSubscriptionID: %s", userSubscriptionID) - return - } - - for i, s := range subscriptions { - if s != subscription { - continue - } - - // Remove the subscription from the slice - lastIndex := len(subscriptions) - 1 - subscriptions[i] = subscriptions[lastIndex] - subscriptions = subscriptions[:lastIndex] - - // If the slice is empty, delete the key from the map - if len(subscriptions) == 0 { - delete(sm.subscriptionMappings, userSubscriptionID) - } else { - sm.subscriptionMappings[userSubscriptionID] = subscriptions - } - break - } -} - -func (sm *SubscriptionManager) checkIfUserConnIsClosedAndUnsubscribe(userConn userconn.UserConn, subscription *gethrpc.ClientSubscription, userSubscriptionID string) { - for !userConn.IsClosed() { - time.Sleep(100 * time.Millisecond) - } - - sm.unsubscribeAndRemove(userSubscriptionID, subscription) -} - -func (sm *SubscriptionManager) UpdateSubscriptionMapping(userSubscriptionID string, subscription *gethrpc.ClientSubscription) { - // Ensure there is no concurrent map writes - sm.mu.Lock() - defer sm.mu.Unlock() - - // Check if the userSubscriptionID already exists in the map - subscriptions, exists := sm.subscriptionMappings[userSubscriptionID] - - // If it doesn't exist, create a new slice for it - if !exists { - subscriptions = []*gethrpc.ClientSubscription{} - } - - // Check if the subscription is already in the slice, if not, add it - subscriptionExists := false - for _, sub := range subscriptions { - if sub == subscription { - subscriptionExists = true - break - } - } - - if !subscriptionExists { - sm.subscriptionMappings[userSubscriptionID] = append(subscriptions, subscription) - } -} - -// Formats the log to be sent as an Eth JSON-RPC response. -func prepareLogResponse(idAndLog common.IDAndLog, userSubscriptionID gethrpc.ID) ([]byte, error) { - paramsMap := make(map[string]interface{}) - paramsMap[wecommon.JSONKeySubscription] = userSubscriptionID - paramsMap[wecommon.JSONKeyResult] = idAndLog.Log - - respMap := make(map[string]interface{}) - respMap[wecommon.JSONKeyRPCVersion] = jsonrpc.Version - respMap[wecommon.JSONKeyMethod] = wecommon.MethodEthSubscription - respMap[wecommon.JSONKeyParams] = paramsMap - - jsonResponse, err := json.Marshal(respMap) - if err != nil { - return nil, fmt.Errorf("could not marshal log response to JSON. Cause: %w", err) - } - return jsonResponse, nil -} - -func (sm *SubscriptionManager) HandleUnsubscribe(userSubscriptionID string, rpcResp *interface{}) { - subscriptions, exists := sm.subscriptionMappings[userSubscriptionID] - if !exists { - *rpcResp = false - return - } - - sm.mu.Lock() - defer sm.mu.Unlock() - for _, sub := range subscriptions { - sub.Unsubscribe() - } - delete(sm.subscriptionMappings, userSubscriptionID) - *rpcResp = true -} diff --git a/tools/walletextension/test/apis.go b/tools/walletextension/test/apis.go index b21fa499a9..6dc42a28cc 100644 --- a/tools/walletextension/test/apis.go +++ b/tools/walletextension/test/apis.go @@ -1,4 +1,4 @@ -package test //nolint:typecheck +package test import ( "context" @@ -8,6 +8,8 @@ import ( "math/big" "time" + "github.com/ten-protocol/go-ten/lib/gethfork/rpc" + "github.com/ten-protocol/go-ten/go/common/viewingkey" "github.com/ethereum/go-ethereum/common/hexutil" @@ -18,7 +20,6 @@ import ( "github.com/ten-protocol/go-ten/go/common" "github.com/ten-protocol/go-ten/go/enclave/vkhandler" "github.com/ten-protocol/go-ten/go/responses" - "github.com/ten-protocol/go-ten/lib/gethfork/rpc" gethcommon "github.com/ethereum/go-ethereum/common" ) diff --git a/tools/walletextension/test/utils.go b/tools/walletextension/test/utils.go index 8e0f3001c6..ad3481c7d1 100644 --- a/tools/walletextension/test/utils.go +++ b/tools/walletextension/test/utils.go @@ -12,6 +12,8 @@ import ( "testing" "time" + "github.com/ten-protocol/go-ten/tools/walletextension" + "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/accounts" @@ -21,8 +23,6 @@ import ( "github.com/ten-protocol/go-ten/go/common/log" "github.com/ten-protocol/go-ten/go/common/viewingkey" "github.com/ten-protocol/go-ten/tools/walletextension/common" - "github.com/ten-protocol/go-ten/tools/walletextension/config" - "github.com/ten-protocol/go-ten/tools/walletextension/container" gethcommon "github.com/ethereum/go-ethereum/common" gethlog "github.com/ethereum/go-ethereum/log" @@ -32,12 +32,13 @@ import ( const jsonID = "1" -func createWalExtCfg(connectPort, wallHTTPPort, wallWSPort int) *config.Config { //nolint: unparam +func createWalExtCfg(connectPort, wallHTTPPort, wallWSPort int) *common.Config { //nolint: unparam testDBPath, err := os.CreateTemp("", "") if err != nil { panic("could not create persistence file for wallet extension tests") } - return &config.Config{ + return &common.Config{ + WalletExtensionHost: "127.0.0.1", NodeRPCWebsocketAddress: fmt.Sprintf("localhost:%d", connectPort), DBPathOverride: testDBPath.Name(), WalletExtensionPortHTTP: wallHTTPPort, @@ -46,11 +47,11 @@ func createWalExtCfg(connectPort, wallHTTPPort, wallWSPort int) *config.Config { } } -func createWalExt(t *testing.T, walExtCfg *config.Config) func() error { +func createWalExt(t *testing.T, walExtCfg *common.Config) func() error { // todo (@ziga) - log somewhere else? logger := log.New(log.WalletExtCmp, int(gethlog.LvlInfo), log.SysOut) - wallExtContainer := container.NewWalletExtensionContainerFromConfig(*walExtCfg, logger) + wallExtContainer := walletextension.NewContainerFromConfig(*walExtCfg, logger) go wallExtContainer.Start() //nolint: errcheck err := waitForEndpoint(fmt.Sprintf("http://%s:%d%s", walExtCfg.WalletExtensionHost, walExtCfg.WalletExtensionPortHTTP, common.PathReady)) @@ -73,15 +74,11 @@ func createDummyHost(t *testing.T, wsRPCPort int) (*DummyAPI, func() error) { // rpcServerNode.RegisterAPIs([]rpc.API{ { Namespace: hostcontainer.APINamespaceObscuro, - Version: hostcontainer.APIVersion1, Service: dummyAPI, - Public: true, }, { Namespace: hostcontainer.APINamespaceEth, - Version: hostcontainer.APIVersion1, Service: dummyAPI, - Public: true, }, }) if err != nil { diff --git a/tools/walletextension/test/wallet_extension_test.go b/tools/walletextension/test/wallet_extension_test.go deleted file mode 100644 index 1591c7fb31..0000000000 --- a/tools/walletextension/test/wallet_extension_test.go +++ /dev/null @@ -1,316 +0,0 @@ -package test - -import ( - "fmt" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/ten-protocol/go-ten/go/rpc" - "github.com/ten-protocol/go-ten/integration" - "github.com/ten-protocol/go-ten/tools/walletextension/accountmanager" - - gethcommon "github.com/ethereum/go-ethereum/common" -) - -const ( - errFailedDecrypt = "could not decrypt bytes with viewing key" - dummyParams = "dummyParams" - _hostWSPort = integration.StartPortWalletExtensionUnitTest -) - -type testHelper struct { - hostPort int - walletHTTPPort int - walletWSPort int - hostAPI *DummyAPI -} - -func TestWalletExtension(t *testing.T) { - t.Skip("Skipping because it is too flaky") - i := 0 - for name, test := range map[string]func(t *testing.T, testHelper *testHelper){ - "canInvokeSensitiveMethodsWithViewingKey": canInvokeSensitiveMethodsWithViewingKey, - "canInvokeNonSensitiveMethodsWithoutViewingKey": canInvokeNonSensitiveMethodsWithoutViewingKey, - "cannotInvokeSensitiveMethodsWithViewingKeyForAnotherAccount": cannotInvokeSensitiveMethodsWithViewingKeyForAnotherAccount, - "canInvokeSensitiveMethodsAfterSubmittingMultipleViewingKeys": canInvokeSensitiveMethodsAfterSubmittingMultipleViewingKeys, - "cannotSubscribeOverHTTP": cannotSubscribeOverHTTP, - "canRegisterViewingKeyAndMakeRequestsOverWebsockets": canRegisterViewingKeyAndMakeRequestsOverWebsockets, - } { - t.Run(name, func(t *testing.T) { - dummyAPI, shutDownHost := createDummyHost(t, _hostWSPort) - shutdownWallet := createWalExt(t, createWalExtCfg(_hostWSPort, _hostWSPort+1, _hostWSPort+2)) - - h := &testHelper{ - hostPort: _hostWSPort, - walletHTTPPort: _hostWSPort + 1, - walletWSPort: _hostWSPort + 2, - hostAPI: dummyAPI, - } - - test(t, h) - - assert.NoError(t, shutdownWallet()) - assert.NoError(t, shutDownHost()) - }) - i++ - } -} - -func canInvokeNonSensitiveMethodsWithoutViewingKey(t *testing.T, testHelper *testHelper) { - respBody, wsConnWE := makeWSEthJSONReq(testHelper.hostPort, rpc.ChainID, []interface{}{}) - defer wsConnWE.Close() - - validateJSONResponse(t, respBody) - - if !strings.Contains(string(respBody), l2ChainIDHex) { - t.Fatalf("expected response containing '%s', got '%s'", l2ChainIDHex, string(respBody)) - } -} - -func canInvokeSensitiveMethodsWithViewingKey(t *testing.T, testHelper *testHelper) { - address, vkPubKeyBytes, signature := simulateViewingKeyRegister(t, testHelper.walletHTTPPort, testHelper.walletWSPort, false) - testHelper.hostAPI.setViewingKey(address, vkPubKeyBytes, signature) - - for _, method := range rpc.SensitiveMethods { - // Subscriptions have to be tested separately, as they return results differently. - if method == rpc.Subscribe { - continue - } - - respBody := makeHTTPEthJSONReq(testHelper.walletHTTPPort, method, []interface{}{map[string]interface{}{"params": dummyParams}}) - validateJSONResponse(t, respBody) - - if !strings.Contains(string(respBody), dummyParams) { - t.Fatalf("expected response containing '%s', got '%s'", dummyParams, string(respBody)) - } - } -} - -var ErrInvalidAddressSignature = fmt.Errorf("invalid viewing key signature for requested address") - -func cannotInvokeSensitiveMethodsWithViewingKeyForAnotherAccount(t *testing.T, testHelper *testHelper) { - addr1, _, _ := simulateViewingKeyRegister(t, testHelper.walletHTTPPort, testHelper.walletWSPort, false) - - _, hexVKPubKeyBytes2, signature2 := simulateViewingKeyRegister(t, testHelper.walletHTTPPort, testHelper.walletWSPort, false) - - // We set the API to decrypt with a key different to the viewing key we just submitted. - testHelper.hostAPI.setViewingKey(addr1, hexVKPubKeyBytes2, signature2) - - for _, method := range rpc.SensitiveMethods { - // Subscriptions have to be tested separately, as they return results differently. - if method == rpc.Subscribe { - continue - } - - respBody := makeHTTPEthJSONReq(testHelper.walletHTTPPort, method, []interface{}{map[string]interface{}{}}) - if !strings.Contains(string(respBody), ErrInvalidAddressSignature.Error()) { - t.Fatalf("expected response containing '%s', got '%s'", errFailedDecrypt, string(respBody)) - } - } -} - -func canInvokeSensitiveMethodsAfterSubmittingMultipleViewingKeys(t *testing.T, testHelper *testHelper) { - type tempVKHolder struct { - address *gethcommon.Address - hexVKPubKey []byte - signature []byte - } - // We submit viewing keys for ten arbitrary accounts. - var viewingKeys []tempVKHolder - - for i := 0; i < 10; i++ { - address, hexVKPubKeyBytes, signature := simulateViewingKeyRegister(t, testHelper.walletHTTPPort, testHelper.walletWSPort, false) - viewingKeys = append(viewingKeys, tempVKHolder{ - address: address, - hexVKPubKey: hexVKPubKeyBytes, - signature: signature, - }) - } - - // We set the API to decrypt with an arbitrary key from the list we just generated. - arbitraryViewingKey := viewingKeys[len(viewingKeys)/2] - testHelper.hostAPI.setViewingKey(arbitraryViewingKey.address, arbitraryViewingKey.hexVKPubKey, arbitraryViewingKey.signature) - - respBody := makeHTTPEthJSONReq(testHelper.walletHTTPPort, rpc.GetBalance, []interface{}{map[string]interface{}{"params": dummyParams}}) - validateJSONResponse(t, respBody) - - if !strings.Contains(string(respBody), dummyParams) { - t.Fatalf("expected response containing '%s', got '%s'", dummyParams, string(respBody)) - } -} - -func cannotSubscribeOverHTTP(t *testing.T, testHelper *testHelper) { - respBody := makeHTTPEthJSONReq(testHelper.walletHTTPPort, rpc.Subscribe, []interface{}{rpc.SubscriptionTypeLogs}) - - if string(respBody) != "received an eth_subscribe request but the connection does not support subscriptions" { - t.Fatalf("unexpected response %s", string(respBody)) - } -} - -func canRegisterViewingKeyAndMakeRequestsOverWebsockets(t *testing.T, testHelper *testHelper) { - address, hexVKPubKeyBytes, signature := simulateViewingKeyRegister(t, testHelper.walletHTTPPort, testHelper.walletWSPort, true) - testHelper.hostAPI.setViewingKey(address, hexVKPubKeyBytes, signature) - - conn, err := openWSConn(testHelper.walletWSPort) - if err != nil { - t.Fatal(err) - } - - respBody := makeWSEthJSONReqWithConn(conn, rpc.GetTransactionReceipt, []interface{}{map[string]interface{}{"params": dummyParams}}) - validateJSONResponse(t, respBody) - - if !strings.Contains(string(respBody), dummyParams) { - t.Fatalf("expected response containing '%s', got '%s'", dummyParams, string(respBody)) - } - - err = conn.Close() - if err != nil { - t.Fatal(err) - } -} - -func TestCannotInvokeSensitiveMethodsWithoutViewingKey(t *testing.T) { - walletHTTPPort := _hostWSPort + 1 - walletWSPort := _hostWSPort + 2 - - _, shutdownHost := createDummyHost(t, _hostWSPort) - defer shutdownHost() //nolint: errcheck - - shutdownWallet := createWalExt(t, createWalExtCfg(_hostWSPort, walletHTTPPort, walletWSPort)) - defer shutdownWallet() //nolint: errcheck - - conn, err := openWSConn(walletWSPort) - if err != nil { - t.Fatal(err) - } - - for _, method := range rpc.SensitiveMethods { - // We use a websocket request because one of the sensitive methods, eth_subscribe, requires it. - respBody := makeWSEthJSONReqWithConn(conn, method, []interface{}{}) - if !strings.Contains(string(respBody), fmt.Sprintf(accountmanager.ErrNoViewingKey, method)) { - t.Fatalf("expected response containing '%s', got '%s'", fmt.Sprintf(accountmanager.ErrNoViewingKey, method), string(respBody)) - } - } - err = conn.Close() - if err != nil { - t.Fatal(err) - } -} - -func TestKeysAreReloadedWhenWalletExtensionRestarts(t *testing.T) { - walletHTTPPort := _hostWSPort + 1 - walletWSPort := _hostWSPort + 2 - - dummyAPI, shutdownHost := createDummyHost(t, _hostWSPort) - defer shutdownHost() //nolint: errcheck - walExtCfg := createWalExtCfg(_hostWSPort, walletHTTPPort, walletWSPort) - shutdownWallet := createWalExt(t, walExtCfg) - - addr, viewingKeyBytes, signature := simulateViewingKeyRegister(t, walletHTTPPort, walletWSPort, false) - dummyAPI.setViewingKey(addr, viewingKeyBytes, signature) - - // We shut down the wallet extension and restart it with the same config, forcing the viewing keys to be reloaded. - err := shutdownWallet() - assert.NoError(t, err) - - shutdownWallet = createWalExt(t, walExtCfg) - defer shutdownWallet() //nolint: errcheck - - respBody := makeHTTPEthJSONReq(walletHTTPPort, rpc.GetBalance, []interface{}{map[string]interface{}{"params": dummyParams}}) - validateJSONResponse(t, respBody) - - if !strings.Contains(string(respBody), dummyParams) { - t.Fatalf("expected response containing '%s', got '%s'", dummyParams, string(respBody)) - } -} - -// TODO (@ziga) - move those tests to integration Obscuro Gateway tests -// currently this test if failing, because we need proper registration in the test -//func TestCanSubscribeForLogsOverWebsockets(t *testing.T) { -// hostPort := _hostWSPort + _testOffset*9 -// walletHTTPPort := hostPort + 1 -// walletWSPort := hostPort + 2 -// -// dummyHash := gethcommon.BigToHash(big.NewInt(1234)) -// -// dummyAPI, shutdownHost := createDummyHost(t, hostPort) -// defer shutdownHost() //nolint: errcheck -// shutdownWallet := createWalExt(t, createWalExtCfg(hostPort, walletHTTPPort, walletWSPort)) -// defer shutdownWallet() //nolint: errcheck -// -// dummyAPI.setViewingKey(simulateViewingKeyRegister(t, walletHTTPPort, walletWSPort, false)) -// -// filter := common.FilterCriteriaJSON{Topics: []interface{}{dummyHash}} -// resp, conn := makeWSEthJSONReq(walletWSPort, rpc.Subscribe, []interface{}{rpc.SubscriptionTypeLogs, filter}) -// validateSubscriptionResponse(t, resp) -// -// logsJSON := readMessagesForDuration(t, conn, time.Second) -// -// // We check we received enough logs. -// if len(logsJSON) < 50 { -// t.Errorf("expected to receive at least 50 logs, only received %d", len(logsJSON)) -// } -// -// // We check that none of the logs were duplicates (i.e. were sent twice). -// assertNoDupeLogs(t, logsJSON) -// -// // We validate that each log contains the correct topic. -// for _, logJSON := range logsJSON { -// var logResp map[string]interface{} -// err := json.Unmarshal(logJSON, &logResp) -// if err != nil { -// t.Fatalf("could not unmarshal received log from JSON") -// } -// -// // We extract the topic from the received logs. The API should have set this based on the filter we passed when subscribing. -// logMap := logResp[wecommon.JSONKeyParams].(map[string]interface{})[wecommon.JSONKeyResult].(map[string]interface{}) -// firstLogTopic := logMap[jsonKeyTopics].([]interface{})[0].(string) -// -// if firstLogTopic != dummyHash.Hex() { -// t.Errorf("expected first topic to be '%s', got '%s'", dummyHash.Hex(), firstLogTopic) -// } -// } -//} - -func TestGetStorageAtForReturningUserID(t *testing.T) { - walletHTTPPort := _hostWSPort + 1 - walletWSPort := _hostWSPort + 2 - - createDummyHost(t, _hostWSPort) - walExtCfg := createWalExtCfg(_hostWSPort, walletHTTPPort, walletWSPort) - createWalExtCfg(_hostWSPort, walletHTTPPort, walletWSPort) - createWalExt(t, walExtCfg) - - // create userID - respJoin := makeHTTPEthJSONReqWithPath(walletHTTPPort, "v1/join") - userID := string(respJoin) - - // make a request to GetStorageAt with correct parameters to get userID that exists in the database - respBody := makeHTTPEthJSONReqWithUserID(walletHTTPPort, rpc.GetStorageAt, []interface{}{"getUserID", "0", nil}, userID) - validateJSONResponse(t, respBody) - - if !strings.Contains(string(respBody), userID) { - t.Fatalf("expected response containing '%s', got '%s'", userID, string(respBody)) - } - - // make a request to GetStorageAt with correct parameters, but userID that is not present in the database - invalidUserID := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - respBody2 := makeHTTPEthJSONReqWithUserID(walletHTTPPort, rpc.GetStorageAt, []interface{}{"getUserID", "0", nil}, invalidUserID) - - if !strings.Contains(string(respBody2), "method eth_getStorageAt cannot be called with an unauthorised client - no signed viewing keys found") { - t.Fatalf("expected method eth_getStorageAt cannot be called with an unauthorised client - no signed viewing keys found, got '%s'", string(respBody2)) - } - - // make a request to GetStorageAt with userID that is in the database, but wrong parameters - respBody3 := makeHTTPEthJSONReqWithUserID(walletHTTPPort, rpc.GetStorageAt, []interface{}{"abc", "0", nil}, userID) - if strings.Contains(string(respBody3), userID) { - t.Fatalf("expected response not containing userID as the parameters are wrong ") - } - - // make a request with wrong rpcMethod - respBody4 := makeHTTPEthJSONReqWithUserID(walletHTTPPort, rpc.GetBalance, []interface{}{"getUserID", "0", nil}, userID) - if strings.Contains(string(respBody4), userID) { - t.Fatalf("expected response not containing userID as the parameters are wrong ") - } -} diff --git a/tools/walletextension/useraccountmanager/user_account_manager.go b/tools/walletextension/useraccountmanager/user_account_manager.go deleted file mode 100644 index 664c2cd994..0000000000 --- a/tools/walletextension/useraccountmanager/user_account_manager.go +++ /dev/null @@ -1,135 +0,0 @@ -package useraccountmanager - -import ( - "encoding/hex" - "fmt" - "sync" - - "github.com/ten-protocol/go-ten/go/common/viewingkey" - - "github.com/ethereum/go-ethereum/common" - gethlog "github.com/ethereum/go-ethereum/log" - "github.com/ten-protocol/go-ten/go/rpc" - "github.com/ten-protocol/go-ten/tools/walletextension/accountmanager" - wecommon "github.com/ten-protocol/go-ten/tools/walletextension/common" - "github.com/ten-protocol/go-ten/tools/walletextension/storage" -) - -// UserAccountManager is a struct that stores one account manager per user and other required data -type UserAccountManager struct { - userAccountManager map[string]*accountmanager.AccountManager - unauthenticatedClient rpc.Client - storage storage.Storage - hostRPCBinAddrHTTP string - hostRPCBinAddrWS string - logger gethlog.Logger - mu sync.Mutex -} - -func NewUserAccountManager(unauthenticatedClient rpc.Client, logger gethlog.Logger, storage storage.Storage, hostRPCBindAddrHTTP string, hostRPCBindAddrWS string) UserAccountManager { - return UserAccountManager{ - userAccountManager: make(map[string]*accountmanager.AccountManager), - unauthenticatedClient: unauthenticatedClient, - storage: storage, - hostRPCBinAddrHTTP: hostRPCBindAddrHTTP, - hostRPCBinAddrWS: hostRPCBindAddrWS, - logger: logger, - } -} - -// AddAndReturnAccountManager adds new UserAccountManager if it doesn't exist and returns it, if UserAccountManager already exists for that user just return it -func (m *UserAccountManager) AddAndReturnAccountManager(userID string) *accountmanager.AccountManager { - m.mu.Lock() - defer m.mu.Unlock() - existingUserAccountManager, exists := m.userAccountManager[userID] - if exists { - return existingUserAccountManager - } - newAccountManager := accountmanager.NewAccountManager(userID, m.unauthenticatedClient, m.hostRPCBinAddrWS, m.storage, m.logger) - m.userAccountManager[userID] = newAccountManager - return newAccountManager -} - -// GetUserAccountManager retrieves the UserAccountManager associated with the given userID. -// it returns the UserAccountManager and nil error if one exists. -// before returning it checks the database and creates all missing clients for that userID -// (we are not loading all of them at startup to limit the number of established connections) -// If a UserAccountManager does not exist for the userID, it returns nil and an error. -func (m *UserAccountManager) GetUserAccountManager(userID string) (*accountmanager.AccountManager, error) { - m.mu.Lock() - defer m.mu.Unlock() - userAccManager, exists := m.userAccountManager[userID] - if !exists { - return nil, fmt.Errorf("UserAccountManager doesn't exist for user: %s", userID) - } - - // we have userAccountManager as expected. - // now we need to create all clients that don't exist there yet - addressesWithClients := userAccManager.GetAllAddressesWithClients() - - // get all addresses for current userID - userIDbytes, err := hex.DecodeString(userID) - if err != nil { - return nil, err - } - - // log that we don't have a storage, but still return existing userAccountManager - // this should never happen, but is useful for tests - if m.storage == nil { - m.logger.Error("storage is nil in UserAccountManager") - return userAccManager, nil - } - - databaseAccounts, err := m.storage.GetAccounts(userIDbytes) - if err != nil { - return nil, err - } - - userPrivateKey, err := m.storage.GetUserPrivateKey(userIDbytes) - if err != nil { - return nil, err - } - - for _, account := range databaseAccounts { - addressHexString := common.BytesToAddress(account.AccountAddress).Hex() - // check if a client for the current address already exists (and skip it if it does) - if addressAlreadyExists(addressHexString, addressesWithClients) { - continue - } - - // create a new client - encClient, err := wecommon.CreateEncClient(m.hostRPCBinAddrWS, account.AccountAddress, userPrivateKey, account.Signature, viewingkey.SignatureType(account.SignatureType), m.logger) - if err != nil { - m.logger.Error(fmt.Errorf("error creating new client, %w", err).Error()) - } - - // add a client to requested userAccountManager - userAccManager.AddClient(common.BytesToAddress(account.AccountAddress), encClient) - addressesWithClients = append(addressesWithClients, addressHexString) - } - - return userAccManager, nil -} - -// DeleteUserAccountManager removes the UserAccountManager associated with the given userID. -// It returns an error if no UserAccountManager exists for that userID. -func (m *UserAccountManager) DeleteUserAccountManager(userID string) error { - m.mu.Lock() - defer m.mu.Unlock() - _, exists := m.userAccountManager[userID] - if !exists { - return fmt.Errorf("no UserAccountManager exists for userID %s", userID) - } - delete(m.userAccountManager, userID) - return nil -} - -// addressAlreadyExists is a helper function to check if an address is already present in a list of existing addresses -func addressAlreadyExists(str string, list []string) bool { - for _, v := range list { - if v == str { - return true - } - } - return false -} diff --git a/tools/walletextension/useraccountmanager/user_account_manager_test.go b/tools/walletextension/useraccountmanager/user_account_manager_test.go deleted file mode 100644 index 38c94ed85d..0000000000 --- a/tools/walletextension/useraccountmanager/user_account_manager_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package useraccountmanager - -import ( - "testing" - - "github.com/ethereum/go-ethereum/log" - "github.com/ten-protocol/go-ten/go/rpc" -) - -func TestAddingAndGettingUserAccountManagers(t *testing.T) { - unauthedClient, _ := rpc.NewNetworkClient("ws://test") - userAccountManager := NewUserAccountManager(unauthedClient, log.New(), nil, "http://test", "ws://test") - userID1 := "4A6F686E20446F65" - userID2 := "7A65746F65A2676F" - - // Test adding and getting account manager for userID1 - userAccountManager.AddAndReturnAccountManager(userID1) - accManager1, err := userAccountManager.GetUserAccountManager(userID1) - if err != nil { - t.Fatal(err) - } - // We should get error if we try to get Account manager for User2 - _, err = userAccountManager.GetUserAccountManager(userID2) - if err == nil { - t.Fatal("expecting error when trying to get AccountManager for user that doesn't exist.") - } - - // After trying to add new AccountManager for the same user we should get the same instance (not overriding old one) - userAccountManager.AddAndReturnAccountManager(userID1) - accManager1New, err := userAccountManager.GetUserAccountManager(userID1) - if err != nil { - t.Fatal(err) - } - - if accManager1 != accManager1New { - t.Fatal("AccountManagers are not the same after adding new account manager for the same userID") - } - - // We get a new instance of AccountManager when we add it for a new user - userAccountManager.AddAndReturnAccountManager(userID2) - accManager2, err := userAccountManager.GetUserAccountManager(userID2) - if err != nil { - t.Fatal(err) - } - - if accManager1 == accManager2 { - t.Fatal("AccountManagers are the same for two different users") - } -} - -func TestDeletingUserAccountManagers(t *testing.T) { - unauthedClient, _ := rpc.NewNetworkClient("ws://test") - userAccountManager := NewUserAccountManager(unauthedClient, log.New(), nil, "", "") - userID := "user1" - - // Add an account manager for the user - userAccountManager.AddAndReturnAccountManager(userID) - - // Test deleting user account manager - err := userAccountManager.DeleteUserAccountManager(userID) - if err != nil { - t.Fatal(err) - } - - // After deleting, we should get an error if we try to get the user's account manager - _, err = userAccountManager.GetUserAccountManager(userID) - if err == nil { - t.Fatal("expected an error after trying to get a deleted account manager") - } - - // Trying to delete an account manager that doesn't exist should return an error - err = userAccountManager.DeleteUserAccountManager("nonexistentUser") - if err == nil { - t.Fatal("expected an error after trying to delete an account manager that doesn't exist") - } -} diff --git a/tools/walletextension/userconn/user_conn.go b/tools/walletextension/userconn/user_conn.go deleted file mode 100644 index 4acb9ebf2f..0000000000 --- a/tools/walletextension/userconn/user_conn.go +++ /dev/null @@ -1,147 +0,0 @@ -package userconn - -import ( - "fmt" - "io" - "net/http" - "net/url" - "strings" - "sync" - - "github.com/ten-protocol/go-ten/go/common/log" - - gethlog "github.com/ethereum/go-ethereum/log" - - "github.com/gorilla/websocket" -) - -var upgrader = websocket.Upgrader{} // Used to upgrade connections to websocket connections. - -// UserConn represents a connection to a user. -type UserConn interface { - ReadRequest() ([]byte, error) - ReadRequestParams() map[string]string - WriteResponse(msg []byte) error - SupportsSubscriptions() bool - IsClosed() bool - GetHTTPRequest() *http.Request -} - -// Represents a user's connection over HTTP. -type userConnHTTP struct { - resp http.ResponseWriter - req *http.Request - logger gethlog.Logger -} - -// Represents a user's connection websockets. -type userConnWS struct { - conn *websocket.Conn - isClosed bool - logger gethlog.Logger - req *http.Request - mu sync.Mutex -} - -func NewUserConnHTTP(resp http.ResponseWriter, req *http.Request, logger gethlog.Logger) UserConn { - return &userConnHTTP{resp: resp, req: req, logger: logger} -} - -func NewUserConnWS(resp http.ResponseWriter, req *http.Request, logger gethlog.Logger) (UserConn, error) { - // We search all the request's headers. If there's a websocket upgrade header, we upgrade to a websocket connection. - conn, err := upgrader.Upgrade(resp, req, nil) - if err != nil { - err = fmt.Errorf("unable to upgrade to websocket connection - %w", err) - _, _ = resp.Write([]byte(err.Error())) - logger.Error("unable to upgrade to websocket connection", log.ErrKey, err) - return nil, err - } - - return &userConnWS{ - conn: conn, - logger: logger, - req: req, - }, nil -} - -func (h *userConnHTTP) ReadRequest() ([]byte, error) { - body, err := io.ReadAll(h.req.Body) - if err != nil { - return nil, fmt.Errorf("could not read request body: %w", err) - } - return body, nil -} - -func (h *userConnHTTP) WriteResponse(msg []byte) error { - _, err := h.resp.Write(msg) - if err != nil { - return fmt.Errorf("could not write response: %w", err) - } - return nil -} - -func (h *userConnHTTP) SupportsSubscriptions() bool { - return false -} - -func (h *userConnHTTP) IsClosed() bool { - return false -} - -func (h *userConnHTTP) ReadRequestParams() map[string]string { - return getQueryParams(h.req.URL.Query()) -} - -func (h *userConnHTTP) GetHTTPRequest() *http.Request { - return h.req -} - -func (w *userConnWS) ReadRequest() ([]byte, error) { - _, msg, err := w.conn.ReadMessage() - if err != nil { - if websocket.IsCloseError(err) { - w.isClosed = true - } - return nil, fmt.Errorf("could not read request: %w", err) - } - return msg, nil -} - -func (w *userConnWS) WriteResponse(msg []byte) error { - w.mu.Lock() - defer w.mu.Unlock() - - err := w.conn.WriteMessage(websocket.TextMessage, msg) - if err != nil { - if websocket.IsCloseError(err) || strings.Contains(string(msg), "EOF") { - w.isClosed = true - } - return fmt.Errorf("could not write response: %w", err) - } - return nil -} - -func (w *userConnWS) SupportsSubscriptions() bool { - return true -} - -func (w *userConnWS) IsClosed() bool { - return w.isClosed -} - -func (w *userConnWS) ReadRequestParams() map[string]string { - return getQueryParams(w.req.URL.Query()) -} - -func (w *userConnWS) GetHTTPRequest() *http.Request { - return w.req -} - -func getQueryParams(query url.Values) map[string]string { - params := make(map[string]string) - queryParams := query - for key, value := range queryParams { - params[key] = value[0] - } - return params -} diff --git a/tools/walletextension/wallet_extension.go b/tools/walletextension/wallet_extension.go deleted file mode 100644 index 5fcc3d6b32..0000000000 --- a/tools/walletextension/wallet_extension.go +++ /dev/null @@ -1,480 +0,0 @@ -package walletextension - -import ( - "bytes" - "encoding/hex" - "errors" - "fmt" - "time" - - "github.com/ten-protocol/go-ten/tools/walletextension/cache" - - "github.com/ten-protocol/go-ten/tools/walletextension/accountmanager" - - "github.com/ten-protocol/go-ten/tools/walletextension/config" - - "github.com/ten-protocol/go-ten/go/common/log" - "github.com/ten-protocol/go-ten/go/obsclient" - - "github.com/ten-protocol/go-ten/tools/walletextension/useraccountmanager" - - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/crypto/ecies" - "github.com/go-kit/kit/transport/http/jsonrpc" - "github.com/ten-protocol/go-ten/go/common/stopcontrol" - "github.com/ten-protocol/go-ten/go/common/viewingkey" - "github.com/ten-protocol/go-ten/go/rpc" - "github.com/ten-protocol/go-ten/tools/walletextension/common" - "github.com/ten-protocol/go-ten/tools/walletextension/storage" - "github.com/ten-protocol/go-ten/tools/walletextension/userconn" - - gethcommon "github.com/ethereum/go-ethereum/common" - gethlog "github.com/ethereum/go-ethereum/log" -) - -// WalletExtension handles the management of viewing keys and the forwarding of Ethereum JSON-RPC requests. -type WalletExtension struct { - hostAddrHTTP string // The HTTP address on which the Ten host can be reached - hostAddrWS string // The WS address on which the Ten host can be reached - userAccountManager *useraccountmanager.UserAccountManager - unsignedVKs map[gethcommon.Address]*viewingkey.ViewingKey // Map temporarily holding VKs that have been generated but not yet signed - storage storage.Storage - logger gethlog.Logger - fileLogger gethlog.Logger - stopControl *stopcontrol.StopControl - version string - config *config.Config - tenClient *obsclient.ObsClient - cache cache.Cache -} - -func New( - hostAddrHTTP string, - hostAddrWS string, - userAccountManager *useraccountmanager.UserAccountManager, - storage storage.Storage, - stopControl *stopcontrol.StopControl, - version string, - logger gethlog.Logger, - config *config.Config, -) *WalletExtension { - rpcClient, err := rpc.NewNetworkClient(hostAddrHTTP) - if err != nil { - logger.Error(fmt.Errorf("could not create RPC client on %s. Cause: %w", hostAddrHTTP, err).Error()) - panic(err) - } - newTenClient := obsclient.NewObsClient(rpcClient) - newFileLogger := common.NewFileLogger() - newGatewayCache, err := cache.NewCache(logger) - if err != nil { - logger.Error(fmt.Errorf("could not create cache. Cause: %w", err).Error()) - panic(err) - } - - return &WalletExtension{ - hostAddrHTTP: hostAddrHTTP, - hostAddrWS: hostAddrWS, - userAccountManager: userAccountManager, - unsignedVKs: map[gethcommon.Address]*viewingkey.ViewingKey{}, - storage: storage, - logger: logger, - fileLogger: newFileLogger, - stopControl: stopControl, - version: version, - config: config, - tenClient: newTenClient, - cache: newGatewayCache, - } -} - -// IsStopping returns whether the WE is stopping -func (w *WalletExtension) IsStopping() bool { - return w.stopControl.IsStopping() -} - -// Logger returns the WE set logger -func (w *WalletExtension) Logger() gethlog.Logger { - return w.logger -} - -// ProxyEthRequest proxys an incoming user request to the enclave -func (w *WalletExtension) ProxyEthRequest(request *common.RPCRequest, conn userconn.UserConn, hexUserID string) (map[string]interface{}, error) { - response := map[string]interface{}{} - // all responses must contain the request id. Both successful and unsuccessful. - response[common.JSONKeyRPCVersion] = jsonrpc.Version - response[common.JSONKeyID] = request.ID - - // start measuring time for request - requestStartTime := time.Now() - - // Check if the request is in the cache - isCacheable, key, ttl := cache.IsCacheable(request, hexUserID) - - // in case of cache hit return the response from the cache - if isCacheable { - if value, ok := w.cache.Get(key); ok { - // do a shallow copy of the map to avoid concurrent map iteration and map write - returnValue := make(map[string]interface{}) - for k, v := range value { - returnValue[k] = v - } - - requestEndTime := time.Now() - duration := requestEndTime.Sub(requestStartTime) - // adjust requestID - returnValue[common.JSONKeyID] = request.ID - w.fileLogger.Info(fmt.Sprintf("Request method: %s, request params: %s, encryptionToken of sender: %s, response: %s, duration: %d ", request.Method, request.Params, hexUserID, returnValue, duration.Milliseconds())) - return returnValue, nil - } - } - - // proxyRequest will find the correct client to proxy the request (or try them all if appropriate) - var rpcResp interface{} - - // wallet extension can override the GetStorageAt to retrieve the current userID - if request.Method == rpc.GetStorageAt { - if interceptedResponse := w.getStorageAtInterceptor(request, hexUserID); interceptedResponse != nil { - w.logger.Info("interception successful for getStorageAt, returning userID response") - requestEndTime := time.Now() - duration := requestEndTime.Sub(requestStartTime) - w.fileLogger.Info(fmt.Sprintf("Request method: %s, request params: %s, encryptionToken of sender: %s, response: %s, duration: %d ", request.Method, request.Params, hexUserID, interceptedResponse, duration.Milliseconds())) - return interceptedResponse, nil - } - } - - // check if user is sending a new transaction and if we should store it in the database for debugging purposes - if request.Method == rpc.SendRawTransaction && w.config.StoreIncomingTxs { - userIDBytes, err := common.GetUserIDbyte(hexUserID) - if err != nil { - w.Logger().Error(fmt.Errorf("error decoding string (%s), %w", hexUserID[2:], err).Error()) - return nil, errors.New("error decoding userID. It should be in hex format") - } - err = w.storage.StoreTransaction(request.Params[0].(string), userIDBytes) - if err != nil { - w.Logger().Error(fmt.Errorf("error storing transaction in the database: %w", err).Error()) - return nil, err - } - } - - // get account manager for current user (if there is no users in the query parameters - use defaultUser for WE endpoints) - selectedAccountManager, err := w.userAccountManager.GetUserAccountManager(hexUserID) - if err != nil { - w.Logger().Error(fmt.Errorf("error getting accountManager for user (%s), %w", hexUserID, err).Error()) - return nil, err - } - - err = selectedAccountManager.ProxyRequest(request, &rpcResp, conn) - if err != nil { - if errors.Is(err, rpc.ErrNilResponse) { - // if err was for a nil response then we will return an RPC result of null to the caller (this is a valid "not-found" response for some methods) - response[common.JSONKeyResult] = nil - requestEndTime := time.Now() - duration := requestEndTime.Sub(requestStartTime) - w.fileLogger.Info(fmt.Sprintf("Request method: %s, request params: %s, encryptionToken of sender: %s, response: %s, duration: %d ", request.Method, request.Params, hexUserID, response, duration.Milliseconds())) - return response, nil - } - return nil, err - } - - response[common.JSONKeyResult] = rpcResp - - // todo (@ziga) - fix this upstream on the decode - // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-658.md - adjustStateRoot(rpcResp, response) - requestEndTime := time.Now() - duration := requestEndTime.Sub(requestStartTime) - w.fileLogger.Info(fmt.Sprintf("Request method: %s, request params: %s, encryptionToken of sender: %s, response: %s, duration: %d ", request.Method, request.Params, hexUserID, response, duration.Milliseconds())) - - // if the request is cacheable, store the response in the cache - if isCacheable { - w.cache.Set(key, response, ttl) - } - - return response, nil -} - -// GenerateViewingKey generates the user viewing key and waits for signature -func (w *WalletExtension) GenerateViewingKey(addr gethcommon.Address) (string, error) { - w.fileLogger.Info(fmt.Sprintf("Requested to generate viewing key for address(old way): %s", addr.Hex())) - viewingKeyPrivate, err := crypto.GenerateKey() - if err != nil { - return "", fmt.Errorf("unable to generate a new keypair - %w", err) - } - - viewingPublicKeyBytes := crypto.CompressPubkey(&viewingKeyPrivate.PublicKey) - viewingPrivateKeyEcies := ecies.ImportECDSA(viewingKeyPrivate) - - w.unsignedVKs[addr] = &viewingkey.ViewingKey{ - Account: &addr, - PrivateKey: viewingPrivateKeyEcies, - PublicKey: viewingPublicKeyBytes, - SignatureWithAccountKey: nil, // we await a signature from the user before we can set up the EncRPCClient - SignatureType: viewingkey.Legacy, - } - - // compress the viewing key and convert it to hex string ( this is what Metamask signs) - viewingKeyBytes := crypto.CompressPubkey(&viewingKeyPrivate.PublicKey) - return hex.EncodeToString(viewingKeyBytes), nil -} - -// SubmitViewingKey checks the signed viewing key and stores it -func (w *WalletExtension) SubmitViewingKey(address gethcommon.Address, signature []byte) error { - w.fileLogger.Info(fmt.Sprintf("Requested to submit a viewing key (old way): %s", address.Hex())) - vk, found := w.unsignedVKs[address] - if !found { - return fmt.Errorf(fmt.Sprintf("no viewing key found to sign for acc=%s, please call %s to generate key before sending signature", address, common.PathGenerateViewingKey)) - } - - // We transform the V from 27/28 to 0/1. This same change is made in Geth internals, for legacy reasons to be able - // to recover the address: https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L452-L459 - signature[64] -= 27 - - vk.SignatureWithAccountKey = signature - - err := w.storage.AddUser([]byte(common.DefaultUser), crypto.FromECDSA(vk.PrivateKey.ExportECDSA())) - if err != nil { - return fmt.Errorf("error saving user: %s", common.DefaultUser) - } - // create an encrypted RPC client with the signed VK and register it with the enclave - // todo (@ziga) - Create the clients lazily, to reduce connections to the host. - client, err := rpc.NewEncNetworkClient(w.hostAddrHTTP, vk, w.logger) - if err != nil { - return fmt.Errorf("failed to create encrypted RPC client for account %s - %w", address, err) - } - defaultAccountManager, err := w.userAccountManager.GetUserAccountManager(hex.EncodeToString([]byte(common.DefaultUser))) - if err != nil { - return fmt.Errorf(fmt.Sprintf("error getting default user account manager: %s", err)) - } - - defaultAccountManager.AddClient(address, client) - - err = w.storage.AddAccount([]byte(common.DefaultUser), vk.Account.Bytes(), vk.SignatureWithAccountKey, viewingkey.Legacy) - if err != nil { - return fmt.Errorf("error saving account %s for user %s", vk.Account.Hex(), common.DefaultUser) - } - - if err != nil { - return fmt.Errorf("error saving viewing key to the database: %w", err) - } - - // finally we remove the VK from the pending 'unsigned VKs' map now the client has been created - delete(w.unsignedVKs, address) - - return nil -} - -// GenerateAndStoreNewUser generates new key-pair and userID, stores it in the database and returns hex encoded userID and error -func (w *WalletExtension) GenerateAndStoreNewUser() (string, error) { - requestStartTime := time.Now() - // generate new key-pair - viewingKeyPrivate, err := crypto.GenerateKey() - viewingPrivateKeyEcies := ecies.ImportECDSA(viewingKeyPrivate) - if err != nil { - w.Logger().Error(fmt.Sprintf("could not generate new keypair: %s", err)) - return "", err - } - - // create UserID and store it in the database with the private key - userID := viewingkey.CalculateUserID(common.PrivateKeyToCompressedPubKey(viewingPrivateKeyEcies)) - err = w.storage.AddUser(userID, crypto.FromECDSA(viewingPrivateKeyEcies.ExportECDSA())) - if err != nil { - w.Logger().Error(fmt.Sprintf("failed to save user to the database: %s", err)) - return "", err - } - - hexUserID := hex.EncodeToString(userID) - - w.userAccountManager.AddAndReturnAccountManager(hexUserID) - requestEndTime := time.Now() - duration := requestEndTime.Sub(requestStartTime) - w.fileLogger.Info(fmt.Sprintf("Storing new userID: %s, duration: %d ", hexUserID, duration.Milliseconds())) - return hexUserID, nil -} - -// AddAddressToUser checks if a message is in correct format and if signature is valid. If all checks pass we save address and signature against userID -func (w *WalletExtension) AddAddressToUser(hexUserID string, address string, signature []byte, signatureType viewingkey.SignatureType) error { - requestStartTime := time.Now() - addressFromMessage := gethcommon.HexToAddress(address) - // check if a message was signed by the correct address and if the signature is valid - _, err := viewingkey.CheckSignature(hexUserID, signature, int64(w.config.TenChainID), signatureType) - if err != nil { - return fmt.Errorf("signature is not valid: %w", err) - } - - // register the account for that viewing key - userIDBytes, err := common.GetUserIDbyte(hexUserID) - if err != nil { - w.Logger().Error(fmt.Errorf("error decoding string (%s), %w", hexUserID[2:], err).Error()) - return errors.New("error decoding userID. It should be in hex format") - } - err = w.storage.AddAccount(userIDBytes, addressFromMessage.Bytes(), signature, signatureType) - if err != nil { - w.Logger().Error(fmt.Errorf("error while storing account (%s) for user (%s): %w", addressFromMessage.Hex(), hexUserID, err).Error()) - return err - } - - // Get account manager for current userID (and create it if it doesn't exist) - privateKeyBytes, err := w.storage.GetUserPrivateKey(userIDBytes) - if err != nil { - w.Logger().Error(fmt.Errorf("error getting private key for user: (%s), %w", hexUserID, err).Error()) - } - - accManager := w.userAccountManager.AddAndReturnAccountManager(hexUserID) - - encClient, err := common.CreateEncClient(w.hostAddrHTTP, addressFromMessage.Bytes(), privateKeyBytes, signature, signatureType, w.Logger()) - if err != nil { - w.Logger().Error(fmt.Errorf("error creating encrypted client for user: (%s), %w", hexUserID, err).Error()) - return fmt.Errorf("error creating encrypted client for user: (%s), %w", hexUserID, err) - } - - accManager.AddClient(addressFromMessage, encClient) - requestEndTime := time.Now() - duration := requestEndTime.Sub(requestStartTime) - w.fileLogger.Info(fmt.Sprintf("Storing new address for user: %s, address: %s, duration: %d ", hexUserID, address, duration.Milliseconds())) - return nil -} - -// UserHasAccount checks if provided account exist in the database for given userID -func (w *WalletExtension) UserHasAccount(hexUserID string, address string) (bool, error) { - w.fileLogger.Info(fmt.Sprintf("Checkinf if user has account: %s, address: %s", hexUserID, address)) - userIDBytes, err := common.GetUserIDbyte(hexUserID) - if err != nil { - w.Logger().Error(fmt.Errorf("error decoding string (%s), %w", hexUserID[2:], err).Error()) - return false, err - } - - addressBytes, err := hex.DecodeString(address[2:]) // remove 0x prefix from address - if err != nil { - w.Logger().Error(fmt.Errorf("error decoding string (%s), %w", address[2:], err).Error()) - return false, err - } - - // todo - this can be optimised and done in the database if we will have users with large number of accounts - // get all the accounts for the selected user - accounts, err := w.storage.GetAccounts(userIDBytes) - if err != nil { - w.Logger().Error(fmt.Errorf("error getting accounts for user (%s), %w", hexUserID, err).Error()) - return false, err - } - - // check if any of the account matches given account - found := false - for _, account := range accounts { - if bytes.Equal(account.AccountAddress, addressBytes) { - found = true - } - } - return found, nil -} - -// DeleteUser deletes user and accounts associated with user from the database for given userID -func (w *WalletExtension) DeleteUser(hexUserID string) error { - w.fileLogger.Info(fmt.Sprintf("Deleting user: %s", hexUserID)) - userIDBytes, err := common.GetUserIDbyte(hexUserID) - if err != nil { - w.Logger().Error(fmt.Errorf("error decoding string (%s), %w", hexUserID, err).Error()) - return err - } - - err = w.storage.DeleteUser(userIDBytes) - if err != nil { - w.Logger().Error(fmt.Errorf("error deleting user (%s), %w", hexUserID, err).Error()) - return err - } - - // Delete UserAccountManager for user that revoked userID - err = w.userAccountManager.DeleteUserAccountManager(hexUserID) - if err != nil { - w.Logger().Error(fmt.Errorf("error deleting UserAccointManager for user (%s), %w", hexUserID, err).Error()) - } - - return nil -} - -func (w *WalletExtension) UserExists(hexUserID string) bool { - w.fileLogger.Info(fmt.Sprintf("Checking if user exists: %s", hexUserID)) - userIDBytes, err := common.GetUserIDbyte(hexUserID) - if err != nil { - w.Logger().Error(fmt.Errorf("error decoding string (%s), %w", hexUserID, err).Error()) - return false - } - - // Check if user exists and don't log error if user doesn't exist, because we expect this to happen in case of - // user revoking encryption token or using different testnet. - // todo add a counter here in the future - key, err := w.storage.GetUserPrivateKey(userIDBytes) - if err != nil { - return false - } - - return len(key) > 0 -} - -func adjustStateRoot(rpcResp interface{}, respMap map[string]interface{}) { - if resultMap, ok := rpcResp.(map[string]interface{}); ok { - if val, foundRoot := resultMap[common.JSONKeyRoot]; foundRoot { - if val == "0x" { - respMap[common.JSONKeyResult].(map[string]interface{})[common.JSONKeyRoot] = nil - } - } - } -} - -// getStorageAtInterceptor checks if the parameters for getStorageAt are set to values that require interception -// and return response or nil if the gateway should forward the request to the node. -func (w *WalletExtension) getStorageAtInterceptor(request *common.RPCRequest, hexUserID string) map[string]interface{} { - // check if parameters are correct, and we can intercept a request, otherwise return nil - if w.checkParametersForInterceptedGetStorageAt(request.Params) { - // check if userID in the parameters is also in our database - userID, err := common.GetUserIDbyte(hexUserID) - if err != nil { - w.logger.Warn("GetStorageAt called with appropriate parameters to return userID, but not found in the database: ", "userId", hexUserID) - return nil - } - - // check if we have default user (we don't want to send userID of it out) - if hexUserID == hex.EncodeToString([]byte(common.DefaultUser)) { - response := map[string]interface{}{} - response[common.JSONKeyRPCVersion] = jsonrpc.Version - response[common.JSONKeyID] = request.ID - response[common.JSONKeyResult] = fmt.Sprintf(accountmanager.ErrNoViewingKey, "eth_getStorageAt") - return response - } - - _, err = w.storage.GetUserPrivateKey(userID) - if err != nil { - w.logger.Info("Trying to get userID, but it is not present in our database: ", log.ErrKey, err) - return nil - } - response := map[string]interface{}{} - response[common.JSONKeyRPCVersion] = jsonrpc.Version - response[common.JSONKeyID] = request.ID - response[common.JSONKeyResult] = hexUserID - return response - } - w.logger.Info(fmt.Sprintf("parameters used in the request do not match requited parameters for interception: %s", request.Params)) - - return nil -} - -// checkParametersForInterceptedGetStorageAt checks -// if parameters for getStorageAt are in the correct format to intercept the function -func (w *WalletExtension) checkParametersForInterceptedGetStorageAt(params []interface{}) bool { - if len(params) != 3 { - w.logger.Info(fmt.Sprintf("getStorageAt expects 3 parameters, but %d received", len(params))) - return false - } - - if methodName, ok := params[0].(string); ok { - return methodName == common.GetStorageAtUserIDRequestMethodName - } - return false -} - -func (w *WalletExtension) Version() string { - return w.version -} - -func (w *WalletExtension) GetTenNodeHealthStatus() (bool, error) { - return w.tenClient.Health() -} diff --git a/tools/walletextension/walletextension_container.go b/tools/walletextension/walletextension_container.go new file mode 100644 index 0000000000..d510cbb71e --- /dev/null +++ b/tools/walletextension/walletextension_container.go @@ -0,0 +1,128 @@ +package walletextension + +import ( + "os" + "time" + + "github.com/ten-protocol/go-ten/tools/walletextension/httpapi" + + "github.com/ten-protocol/go-ten/tools/walletextension/rpcapi" + + "github.com/ten-protocol/go-ten/lib/gethfork/node" + + gethlog "github.com/ethereum/go-ethereum/log" + "github.com/ten-protocol/go-ten/go/common/log" + "github.com/ten-protocol/go-ten/go/common/stopcontrol" + gethrpc "github.com/ten-protocol/go-ten/lib/gethfork/rpc" + wecommon "github.com/ten-protocol/go-ten/tools/walletextension/common" + "github.com/ten-protocol/go-ten/tools/walletextension/storage" +) + +type Container struct { + stopControl *stopcontrol.StopControl + logger gethlog.Logger + rpcServer node.Server +} + +func NewContainerFromConfig(config wecommon.Config, logger gethlog.Logger) *Container { + // create the account manager with a single unauthenticated connection + hostRPCBindAddrWS := wecommon.WSProtocol + config.NodeRPCWebsocketAddress + hostRPCBindAddrHTTP := wecommon.HTTPProtocol + config.NodeRPCHTTPAddress + // start the database + databaseStorage, err := storage.New(config.DBType, config.DBConnectionURL, config.DBPathOverride) + if err != nil { + logger.Crit("unable to create database to store viewing keys ", log.ErrKey, err) + os.Exit(1) + } + + // captures version in the env vars + version := os.Getenv("OBSCURO_GATEWAY_VERSION") + if version == "" { + version = "dev" + } + + stopControl := stopcontrol.New() + walletExt := rpcapi.NewServices(hostRPCBindAddrHTTP, hostRPCBindAddrWS, databaseStorage, stopControl, version, logger, &config) + cfg := &node.RPCConfig{ + EnableHTTP: true, + HTTPPort: config.WalletExtensionPortHTTP, + EnableWs: true, + WsPort: config.WalletExtensionPortWS, + WsPath: "/v1/", + HTTPPath: "/v1/", + Host: config.WalletExtensionHost, + } + rpcServer := node.NewServer(cfg, logger) + + rpcServer.RegisterRoutes(httpapi.NewHTTPRoutes(walletExt)) + + // register all RPC endpoints exposed by a typical Geth node + // todo - discover what else we need to register here + rpcServer.RegisterAPIs([]gethrpc.API{ + { + Namespace: "eth", + Service: rpcapi.NewEthereumAPI(walletExt), + }, { + Namespace: "eth", + Service: rpcapi.NewBlockChainAPI(walletExt), + }, { + Namespace: "eth", + Service: rpcapi.NewTransactionAPI(walletExt), + }, { + Namespace: "txpool", + Service: rpcapi.NewTxPoolAPI(walletExt), + }, { + Namespace: "debug", + Service: rpcapi.NewDebugAPI(walletExt), + }, { + Namespace: "eth", + Service: rpcapi.NewFilterAPI(walletExt), + }, + }) + + // rpcServer. + return NewWalletExtensionContainer( + // hostRPCBindAddrWS, + // walletExt, + // databaseStorage, + stopControl, + rpcServer, + logger, + ) +} + +func NewWalletExtensionContainer( + stopControl *stopcontrol.StopControl, + rpcServer node.Server, + logger gethlog.Logger, +) *Container { + return &Container{ + stopControl: stopControl, + rpcServer: rpcServer, + logger: logger, + } +} + +// Start starts the wallet extension container +func (w *Container) Start() error { + err := w.rpcServer.Start() + if err != nil { + return err + } + return nil +} + +func (w *Container) Stop() error { + w.stopControl.Stop() + + if w.rpcServer != nil { + // rpc server cannot be stopped synchronously as it will kill current request + go func() { + // make sure it's not killing the connection before returning the response + time.Sleep(time.Second) // todo review this sleep + w.rpcServer.Stop() + }() + } + + return nil +} From 979ddc1f4e8c27321406edb58fd19732f8f59e3b Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Tue, 19 Mar 2024 19:32:46 +0000 Subject: [PATCH 12/65] fix --- .../accountmanager/account_manager.go | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/tools/walletextension/accountmanager/account_manager.go b/tools/walletextension/accountmanager/account_manager.go index a4bad822cf..d284f0662c 100644 --- a/tools/walletextension/accountmanager/account_manager.go +++ b/tools/walletextension/accountmanager/account_manager.go @@ -89,16 +89,22 @@ func (m *AccountManager) ProxyRequest(rpcReq *wecommon.RPCRequest, rpcResp *inte if err != nil { return err } - critBytes, err := json.Marshal(rpcReq.Params[1]) - if err != nil { - return err - } - criteria := new(filters.FilterCriteria) - err = criteria.UnmarshalJSON(critBytes) - if err != nil { - return err + criteria := filters.FilterCriteria{} + + // this stuff will be removed when the rpc is added + crit, ok := rpcReq.Params[1].(map[string]any) + if ok { + critBytes, err := json.Marshal(crit) + if err != nil { + return err + } + err = criteria.UnmarshalJSON(critBytes) + if err != nil { + return err + } } - err = m.subscriptionsManager.HandleNewSubscriptions(clients, *criteria, rpcResp, userConn) + + err = m.subscriptionsManager.HandleNewSubscriptions(clients, criteria, rpcResp, userConn) if err != nil { m.logger.Error("Error subscribing to multiple clients") return err From c6a8a981fe37ff09d815f952cfe8f0499d3e6626 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDiga=20Kokelj?= Date: Wed, 20 Mar 2024 10:40:31 +0100 Subject: [PATCH 13/65] code review fixes --- go/common/viewingkey/viewing_key.go | 362 +----------------- go/common/viewingkey/viewing_key_messages.go | 258 +++++++++++++ go/common/viewingkey/viewing_key_signature.go | 132 +++++++ go/enclave/vkhandler/vk_handler_test.go | 6 +- tools/walletextension/api/routes.go | 22 +- tools/walletextension/common/common.go | 20 - tools/walletextension/common/constants.go | 7 - tools/walletextension/lib/client_lib.go | 2 +- tools/walletextension/wallet_extension.go | 8 +- 9 files changed, 409 insertions(+), 408 deletions(-) create mode 100644 go/common/viewingkey/viewing_key_messages.go create mode 100644 go/common/viewingkey/viewing_key_signature.go diff --git a/go/common/viewingkey/viewing_key.go b/go/common/viewingkey/viewing_key.go index 4ff6642997..058893bfe9 100644 --- a/go/common/viewingkey/viewing_key.go +++ b/go/common/viewingkey/viewing_key.go @@ -1,61 +1,10 @@ package viewingkey import ( - "crypto/ecdsa" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/crypto/ecies" - "github.com/ethereum/go-ethereum/signer/core/apitypes" - "github.com/ten-protocol/go-ten/go/wallet" - gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto/ecies" ) -// SignedMsgPrefix is the prefix added when signing the viewing key in MetaMask using the personal_sign -// API. Why is this needed? MetaMask has a security feature whereby if you ask it to sign something that looks like -// a transaction using the personal_sign API, it modifies the data being signed. The goal is to prevent hackers -// from asking a visitor to their website to personal_sign something that is actually a malicious transaction (e.g. -// theft of funds). By adding a prefix, the viewing key bytes no longer looks like a transaction hash, and thus get -// signed as-is. -const SignedMsgPrefix = "vk" - -const ( - EIP712Domain = "EIP712Domain" - EIP712Type = "Authentication" - EIP712DomainName = "name" - EIP712DomainVersion = "version" - EIP712DomainChainID = "chainId" - EIP712EncryptionToken = "Encryption Token" - EIP712DomainNameValue = "Ten" - EIP712DomainVersionValue = "1.0" - UserIDHexLength = 40 - PersonalSignMessageFormat = "Token: %s on chain: %d version:%d" -) - -const ( - EIP712Signature SignatureType = 0 - PersonalSign SignatureType = 1 - Legacy SignatureType = 2 -) - -// EIP712EncryptionTokens is a list of all possible options for Encryption token name -var EIP712EncryptionTokens = [...]string{ - EIP712EncryptionToken, -} - -// PersonalSignMessageSupportedVersions is a list of supported versions for the personal sign message -var PersonalSignMessageSupportedVersions = []int{1} - -// SignatureType is used to differentiate between different signature types (string is used, because int is not RLP-serializable) -type SignatureType uint8 - // ViewingKey encapsulates the signed viewing key for an account for use in encrypted communication with an enclave. // It is the client-side perspective of the viewing key used for decrypting incoming traffic. type ViewingKey struct { @@ -75,312 +24,3 @@ type RPCSignedViewingKey struct { SignatureWithAccountKey []byte SignatureType SignatureType } - -// SignatureChecker is an interface for checking -// if signature is valid for provided encryptionToken and chainID and return singing address or nil if not valid -type SignatureChecker interface { - CheckSignature(encryptionToken string, signature []byte, chainID int64) (*gethcommon.Address, error) -} - -type ( - PersonalSignChecker struct{} - EIP712Checker struct{} - LegacyChecker struct{} -) - -// CheckSignature checks if signature is valid for provided encryptionToken and chainID and return address or nil if not valid -func (psc PersonalSignChecker) CheckSignature(encryptionToken string, signature []byte, chainID int64) (*gethcommon.Address, error) { - if len(signature) != 65 { - return nil, fmt.Errorf("invalid signaure length: %d", len(signature)) - } - // We transform the V from 27/28 to 0/1. This same change is made in Geth internals, for legacy reasons to be able - // to recover the address: https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L452-L459 - if signature[64] == 27 || signature[64] == 28 { - signature[64] -= 27 - } - - // create all possible hashes (for all the supported versions) of the message (needed for signature verification) - for _, version := range PersonalSignMessageSupportedVersions { - messageHash, err := GenerateMessage(encryptionToken, chainID, version, PersonalSign, true) - if err != nil { - return nil, fmt.Errorf("cannot generate message. Cause %w", err) - } - - // current signature is valid - return account address - address, err := CheckSignatureAndReturnAccountAddress(messageHash, signature) - if err == nil { - return address, nil - } - } - - return nil, fmt.Errorf("signature verification failed") -} - -func (e EIP712Checker) CheckSignature(encryptionToken string, signature []byte, chainID int64) (*gethcommon.Address, error) { - if len(signature) != 65 { - return nil, fmt.Errorf("invalid signaure length: %d", len(signature)) - } - - messageHash, err := GenerateMessage(encryptionToken, chainID, 1, EIP712Signature, true) - if err != nil { - return nil, fmt.Errorf("cannot generate message. Cause %w", err) - } - - // We transform the V from 27/28 to 0/1. This same change is made in Geth internals, for legacy reasons to be able - // to recover the address: https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L452-L459 - if signature[64] == 27 || signature[64] == 28 { - signature[64] -= 27 - } - - // current signature is valid - return account address - address, err := CheckSignatureAndReturnAccountAddress(messageHash, signature) - if err == nil { - return address, nil - } - - return nil, errors.New("EIP 712 signature verification failed") -} - -// CheckSignature checks if signature is valid for provided encryptionToken and chainID and return address or nil if not valid -// todo (@ziga) Remove this method once old WE endpoints are removed -// encryptionToken is expected to be a public key and not encrypted token as with other signature types -// (since this is only temporary fix and legacy format will be removed soon) -func (lsc LegacyChecker) CheckSignature(encryptionToken string, signature []byte, _ int64) (*gethcommon.Address, error) { - publicKey := []byte(encryptionToken) - msgToSignLegacy := GenerateSignMessage(publicKey) - - recoveredAccountPublicKeyLegacy, err := crypto.SigToPub(accounts.TextHash([]byte(msgToSignLegacy)), signature) - if err != nil { - return nil, fmt.Errorf("failed to recover account public key from legacy signature: %w", err) - } - recoveredAccountAddressLegacy := crypto.PubkeyToAddress(*recoveredAccountPublicKeyLegacy) - return &recoveredAccountAddressLegacy, nil -} - -// SignatureChecker is a map of SignatureType to SignatureChecker -var signatureCheckers = map[SignatureType]SignatureChecker{ - PersonalSign: PersonalSignChecker{}, - EIP712Signature: EIP712Checker{}, - Legacy: LegacyChecker{}, -} - -// CheckSignature checks if signature is valid for provided encryptionToken and chainID and return address or nil if not valid -func CheckSignature(encryptionToken string, signature []byte, chainID int64, signatureType SignatureType) (*gethcommon.Address, error) { - checker, exists := signatureCheckers[signatureType] - if !exists { - return nil, fmt.Errorf("unsupported signature type") - } - return checker.CheckSignature(encryptionToken, signature, chainID) -} - -// GenerateViewingKeyForWallet takes an account wallet, generates a viewing key and signs the key with the acc's private key -// uses the same method of signature handling as Metamask/geth -// TODO @Ziga - update this method to use the new EIP-712 signature format / personal sign after the removal of the legacy format -func GenerateViewingKeyForWallet(wal wallet.Wallet) (*ViewingKey, error) { - // generate an ECDSA key pair to encrypt sensitive communications with the obscuro enclave - vk, err := crypto.GenerateKey() - if err != nil { - return nil, fmt.Errorf("failed to generate viewing key for RPC client: %w", err) - } - - // get key in ECIES format - viewingPrivateKeyECIES := ecies.ImportECDSA(vk) - - // encode public key as bytes - viewingPubKeyBytes := crypto.CompressPubkey(&vk.PublicKey) - - // sign public key bytes with the wallet's private key - signature, err := mmSignViewingKey(viewingPubKeyBytes, wal.PrivateKey()) - if err != nil { - return nil, err - } - - accAddress := wal.Address() - return &ViewingKey{ - Account: &accAddress, - PrivateKey: viewingPrivateKeyECIES, - PublicKey: viewingPubKeyBytes, - SignatureWithAccountKey: signature, - SignatureType: Legacy, - }, nil -} - -// mmSignViewingKey takes a public key bytes as hex and the private key for a wallet, it simulates the back-and-forth to -// MetaMask and returns the signature bytes to register with the enclave -func mmSignViewingKey(viewingPubKeyBytes []byte, signerKey *ecdsa.PrivateKey) ([]byte, error) { - signature, err := Sign(signerKey, viewingPubKeyBytes) - if err != nil { - return nil, fmt.Errorf("failed to sign viewing key: %w", err) - } - - // We have to transform the V from 0/1 to 27/28, and add the leading "0". - signature[64] += 27 - signatureWithLeadBytes := append([]byte("0"), signature...) - - // this string encoded signature is what the wallet extension would receive after it is signed by metamask - sigStr := hex.EncodeToString(signatureWithLeadBytes) - // and then we extract the signature bytes in the same way as the wallet extension - outputSig, err := hex.DecodeString(sigStr[2:]) - if err != nil { - return nil, fmt.Errorf("failed to decode signature string: %w", err) - } - // This same change is made in geth internals, for legacy reasons to be able to recover the address: - // https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L452-L459 - outputSig[64] -= 27 - - return outputSig, nil -} - -// Sign takes a users Private key and signs the public viewingKey hex -func Sign(userPrivKey *ecdsa.PrivateKey, vkPubKey []byte) ([]byte, error) { - msgToSign := GenerateSignMessage(vkPubKey) - signature, err := crypto.Sign(accounts.TextHash([]byte(msgToSign)), userPrivKey) - if err != nil { - return nil, fmt.Errorf("unable to sign messages - %w", err) - } - return signature, nil -} - -// GenerateSignMessage creates the message to be signed -// vkPubKey is expected to be a []byte("0x....") to create the signing message -// todo (@ziga) Remove this method once old WE endpoints are removed -func GenerateSignMessage(vkPubKey []byte) string { - return SignedMsgPrefix + hex.EncodeToString(vkPubKey) -} - -// getBytesFromTypedData creates EIP-712 compliant hash from typedData. -// It involves hashing the message with its structure, hashing domain separator, -// and then encoding both hashes with specific EIP-712 bytes to construct the final message format. -func getBytesFromTypedData(typedData apitypes.TypedData) ([]byte, error) { - typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) - if err != nil { - return nil, err - } - // Create the domain separator hash for EIP-712 message context - domainSeparator, err := typedData.HashStruct(EIP712Domain, typedData.Domain.Map()) - if err != nil { - return nil, err - } - // Prefix domain and message hashes with EIP-712 version and encoding bytes - rawData := append([]byte("\x19\x01"), append(domainSeparator, typedDataHash...)...) - return rawData, nil -} - -// CalculateUserIDHex CalculateUserID calculates userID from a public key -// (we truncate it, because we want it to have length 20) and encode to hex strings -func CalculateUserIDHex(publicKeyBytes []byte) string { - return hex.EncodeToString(CalculateUserID(publicKeyBytes)) -} - -// CalculateUserID calculates userID from a public key (we truncate it, because we want it to have length 20) -func CalculateUserID(publicKeyBytes []byte) []byte { - return crypto.Keccak256Hash(publicKeyBytes).Bytes()[:20] -} - -// CheckSignatureAndReturnAccountAddress checks if the signature is valid for hash of the message and checks if -// signer is an address provided to the function. -// It returns an address if the signature is valid and nil otherwise -func CheckSignatureAndReturnAccountAddress(hashBytes []byte, signature []byte) (*gethcommon.Address, error) { - pubKeyBytes, err := crypto.Ecrecover(hashBytes, signature) - if err != nil { - return nil, err - } - - pubKey, err := crypto.UnmarshalPubkey(pubKeyBytes) - if err != nil { - return nil, err - } - - r := new(big.Int).SetBytes(signature[:32]) - s := new(big.Int).SetBytes(signature[32:64]) - - // Verify the signature and return the result (all the checks above passed) - isSigValid := ecdsa.Verify(pubKey, hashBytes, r, s) - if isSigValid { - address := crypto.PubkeyToAddress(*pubKey) - return &address, nil - } - return nil, fmt.Errorf("invalid signature") -} - -type MessageGenerator interface { - generateMessage(encryptionToken string, chainID int64, version int, hash bool) ([]byte, error) -} - -type ( - PersonalMessageGenerator struct{} - EIP712MessageGenerator struct{} -) - -var messageGenerators = map[SignatureType]MessageGenerator{ - PersonalSign: PersonalMessageGenerator{}, - EIP712Signature: EIP712MessageGenerator{}, -} - -// GenerateMessage generates a message for the given encryptionToken, chainID, version and signatureType -func (p PersonalMessageGenerator) generateMessage(encryptionToken string, chainID int64, version int, hash bool) ([]byte, error) { - textMessage := fmt.Sprintf(PersonalSignMessageFormat, encryptionToken, chainID, version) - if hash { - return accounts.TextHash([]byte(textMessage)), nil - } - return []byte(textMessage), nil -} - -func (e EIP712MessageGenerator) generateMessage(encryptionToken string, chainID int64, _ int, hash bool) ([]byte, error) { - if len(encryptionToken) != UserIDHexLength { - return nil, fmt.Errorf("userID hex length must be %d, received %d", UserIDHexLength, len(encryptionToken)) - } - encryptionToken = "0x" + encryptionToken - - domain := apitypes.TypedDataDomain{ - Name: EIP712DomainNameValue, - Version: EIP712DomainVersionValue, - ChainId: (*math.HexOrDecimal256)(big.NewInt(chainID)), - } - - message := map[string]interface{}{ - EIP712EncryptionToken: encryptionToken, - } - - types := apitypes.Types{ - EIP712Domain: { - {Name: EIP712DomainName, Type: "string"}, - {Name: EIP712DomainVersion, Type: "string"}, - {Name: EIP712DomainChainID, Type: "uint256"}, - }, - EIP712Type: { - {Name: EIP712EncryptionToken, Type: "address"}, - }, - } - - newTypeElement := apitypes.TypedData{ - Types: types, - PrimaryType: EIP712Type, - Domain: domain, - Message: message, - } - - rawData, err := getBytesFromTypedData(newTypeElement) - if err != nil { - return nil, err - } - - // add the JSON message to the list of messages - jsonData, err := json.Marshal(newTypeElement) - if err != nil { - return nil, err - } - - if hash { - return crypto.Keccak256(rawData), nil - } - return jsonData, nil -} - -func GenerateMessage(encryptionToken string, chainID int64, version int, signatureType SignatureType, hash bool) ([]byte, error) { - generator, exists := messageGenerators[signatureType] - if !exists { - return nil, fmt.Errorf("unsupported signature type") - } - return generator.generateMessage(encryptionToken, chainID, version, hash) -} diff --git a/go/common/viewingkey/viewing_key_messages.go b/go/common/viewingkey/viewing_key_messages.go new file mode 100644 index 0000000000..522690d713 --- /dev/null +++ b/go/common/viewingkey/viewing_key_messages.go @@ -0,0 +1,258 @@ +package viewingkey + +import ( + "crypto/ecdsa" + "encoding/hex" + "encoding/json" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/ecies" + "github.com/ethereum/go-ethereum/signer/core/apitypes" + "github.com/ten-protocol/go-ten/go/wallet" +) + +const ( + EIP712Signature SignatureType = 0 + PersonalSign SignatureType = 1 + Legacy SignatureType = 2 +) + +// SignatureType is used to differentiate between different signature types (string is used, because int is not RLP-serializable) +type SignatureType uint8 + +const ( + EIP712Domain = "EIP712Domain" + EIP712Type = "Authentication" + EIP712DomainName = "name" + EIP712DomainVersion = "version" + EIP712DomainChainID = "chainId" + EIP712EncryptionToken = "Encryption Token" + EIP712DomainNameValue = "Ten" + EIP712DomainVersionValue = "1.0" + UserIDHexLength = 40 + PersonalSignMessageFormat = "Token: %s on chain: %d version: %d" + SignedMsgPrefix = "vk" // prefix for legacy signed messages (remove when legacy signature type is removed) + PersonalSignVersion = 1 +) + +// EIP712EncryptionTokens is a list of all possible options for Encryption token name +var EIP712EncryptionTokens = [...]string{ + EIP712EncryptionToken, +} + +type MessageGenerator interface { + generateMessage(encryptionToken string, chainID int64, version int, hash bool) ([]byte, error) +} + +type ( + PersonalMessageGenerator struct{} + EIP712MessageGenerator struct{} +) + +var messageGenerators = map[SignatureType]MessageGenerator{ + PersonalSign: PersonalMessageGenerator{}, + EIP712Signature: EIP712MessageGenerator{}, +} + +// GenerateMessage generates a message for the given encryptionToken, chainID, version and signatureType +func (p PersonalMessageGenerator) generateMessage(encryptionToken string, chainID int64, version int, hash bool) ([]byte, error) { + textMessage := fmt.Sprintf(PersonalSignMessageFormat, encryptionToken, chainID, version) + if hash { + return accounts.TextHash([]byte(textMessage)), nil + } + return []byte(textMessage), nil +} + +func (e EIP712MessageGenerator) generateMessage(encryptionToken string, chainID int64, _ int, hash bool) ([]byte, error) { + if len(encryptionToken) != UserIDHexLength { + return nil, fmt.Errorf("userID hex length must be %d, received %d", UserIDHexLength, len(encryptionToken)) + } + encryptionToken = "0x" + encryptionToken + + domain := apitypes.TypedDataDomain{ + Name: EIP712DomainNameValue, + Version: EIP712DomainVersionValue, + ChainId: (*math.HexOrDecimal256)(big.NewInt(chainID)), + } + + message := map[string]interface{}{ + EIP712EncryptionToken: encryptionToken, + } + + types := apitypes.Types{ + EIP712Domain: { + {Name: EIP712DomainName, Type: "string"}, + {Name: EIP712DomainVersion, Type: "string"}, + {Name: EIP712DomainChainID, Type: "uint256"}, + }, + EIP712Type: { + {Name: EIP712EncryptionToken, Type: "address"}, + }, + } + + newTypeElement := apitypes.TypedData{ + Types: types, + PrimaryType: EIP712Type, + Domain: domain, + Message: message, + } + + rawData, err := getBytesFromTypedData(newTypeElement) + if err != nil { + return nil, err + } + + // add the JSON message to the list of messages + jsonData, err := json.Marshal(newTypeElement) + if err != nil { + return nil, err + } + + if hash { + return crypto.Keccak256(rawData), nil + } + return jsonData, nil +} + +// GenerateMessage generates a message for the given encryptionToken, chainID, version and signatureType +// hashed parameter is used to determine if the returned message should be hashed or returned in plain text +func GenerateMessage(encryptionToken string, chainID int64, version int, signatureType SignatureType, hashed bool) ([]byte, error) { + generator, exists := messageGenerators[signatureType] + if !exists { + return nil, fmt.Errorf("unsupported signature type") + } + return generator.generateMessage(encryptionToken, chainID, version, hashed) +} + +// GenerateSignMessage creates the message to be signed +// vkPubKey is expected to be a []byte("0x....") to create the signing message +// todo (@ziga) Remove this method once old WE endpoints are removed +func GenerateSignMessage(vkPubKey []byte) string { + return SignedMsgPrefix + hex.EncodeToString(vkPubKey) +} + +// getBytesFromTypedData creates EIP-712 compliant hash from typedData. +// It involves hashing the message with its structure, hashing domain separator, +// and then encoding both hashes with specific EIP-712 bytes to construct the final message format. +func getBytesFromTypedData(typedData apitypes.TypedData) ([]byte, error) { + typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) + if err != nil { + return nil, err + } + // Create the domain separator hash for EIP-712 message context + domainSeparator, err := typedData.HashStruct(EIP712Domain, typedData.Domain.Map()) + if err != nil { + return nil, err + } + // Prefix domain and message hashes with EIP-712 version and encoding bytes + rawData := append([]byte("\x19\x01"), append(domainSeparator, typedDataHash...)...) + return rawData, nil +} + +// GenerateViewingKeyForWallet takes an account wallet, generates a viewing key and signs the key with the acc's private key +// uses the same method of signature handling as Metamask/geth +// TODO @Ziga - update this method to use the new EIP-712 signature format / personal sign after the removal of the legacy format +func GenerateViewingKeyForWallet(wal wallet.Wallet) (*ViewingKey, error) { + // generate an ECDSA key pair to encrypt sensitive communications with the obscuro enclave + vk, err := crypto.GenerateKey() + if err != nil { + return nil, fmt.Errorf("failed to generate viewing key for RPC client: %w", err) + } + + // get key in ECIES format + viewingPrivateKeyECIES := ecies.ImportECDSA(vk) + + // encode public key as bytes + viewingPubKeyBytes := crypto.CompressPubkey(&vk.PublicKey) + + // sign public key bytes with the wallet's private key + signature, err := mmSignViewingKey(viewingPubKeyBytes, wal.PrivateKey()) + if err != nil { + return nil, err + } + + accAddress := wal.Address() + return &ViewingKey{ + Account: &accAddress, + PrivateKey: viewingPrivateKeyECIES, + PublicKey: viewingPubKeyBytes, + SignatureWithAccountKey: signature, + SignatureType: Legacy, + }, nil +} + +// mmSignViewingKey takes a public key bytes as hex and the private key for a wallet, it simulates the back-and-forth to +// MetaMask and returns the signature bytes to register with the enclave +func mmSignViewingKey(viewingPubKeyBytes []byte, signerKey *ecdsa.PrivateKey) ([]byte, error) { + signature, err := Sign(signerKey, viewingPubKeyBytes) + if err != nil { + return nil, fmt.Errorf("failed to sign viewing key: %w", err) + } + + // We have to transform the V from 0/1 to 27/28, and add the leading "0". + signature[64] += 27 + signatureWithLeadBytes := append([]byte("0"), signature...) + + // this string encoded signature is what the wallet extension would receive after it is signed by metamask + sigStr := hex.EncodeToString(signatureWithLeadBytes) + // and then we extract the signature bytes in the same way as the wallet extension + outputSig, err := hex.DecodeString(sigStr[2:]) + if err != nil { + return nil, fmt.Errorf("failed to decode signature string: %w", err) + } + // This same change is made in geth internals, for legacy reasons to be able to recover the address: + // https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L452-L459 + outputSig[64] -= 27 + + return outputSig, nil +} + +// Sign takes a users Private key and signs the public viewingKey hex +func Sign(userPrivKey *ecdsa.PrivateKey, vkPubKey []byte) ([]byte, error) { + msgToSign := GenerateSignMessage(vkPubKey) + signature, err := crypto.Sign(accounts.TextHash([]byte(msgToSign)), userPrivKey) + if err != nil { + return nil, fmt.Errorf("unable to sign messages - %w", err) + } + return signature, nil +} + +// CalculateUserIDHex CalculateUserID calculates userID from a public key +// (we truncate it, because we want it to have length 20) and encode to hex strings +func CalculateUserIDHex(publicKeyBytes []byte) string { + return hex.EncodeToString(CalculateUserID(publicKeyBytes)) +} + +// CalculateUserID calculates userID from a public key (we truncate it, because we want it to have length 20) +func CalculateUserID(publicKeyBytes []byte) []byte { + return crypto.Keccak256Hash(publicKeyBytes).Bytes()[:20] +} + +// GetBestFormat returns the best format for a message based on available formats that are supported by the user +func GetBestFormat(formatsSlice []string) SignatureType { + // If "Personal" is the only format available, choose it + if len(formatsSlice) == 1 && formatsSlice[0] == "Personal" { + return PersonalSign + } + + // otherwise, choose EIP712 + return EIP712Signature +} + +func GetSignatureTypeString(expectedSignatureType SignatureType) string { + for key, value := range SignatureTypeMap { + if value == expectedSignatureType { + return key + } + } + return "" +} + +var SignatureTypeMap = map[string]SignatureType{ + "EIP712": EIP712Signature, + "Personal": PersonalSign, +} diff --git a/go/common/viewingkey/viewing_key_signature.go b/go/common/viewingkey/viewing_key_signature.go new file mode 100644 index 0000000000..41c0bd3dc1 --- /dev/null +++ b/go/common/viewingkey/viewing_key_signature.go @@ -0,0 +1,132 @@ +package viewingkey + +import ( + "crypto/ecdsa" + "errors" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/accounts" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +// SignatureChecker is an interface for checking +// if signature is valid for provided encryptionToken and chainID and return singing address or nil if not valid +type SignatureChecker interface { + CheckSignature(encryptionToken string, signature []byte, chainID int64) (*gethcommon.Address, error) +} + +type ( + PersonalSignChecker struct{} + EIP712Checker struct{} + LegacyChecker struct{} +) + +// CheckSignature checks if signature is valid for provided encryptionToken and chainID and return address or nil if not valid +func (psc PersonalSignChecker) CheckSignature(encryptionToken string, signature []byte, chainID int64) (*gethcommon.Address, error) { + if len(signature) != 65 { + return nil, fmt.Errorf("invalid signaure length: %d", len(signature)) + } + // We transform the V from 27/28 to 0/1. This same change is made in Geth internals, for legacy reasons to be able + // to recover the address: https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L452-L459 + if signature[64] == 27 || signature[64] == 28 { + signature[64] -= 27 + } + + messageHash, err := GenerateMessage(encryptionToken, chainID, PersonalSignVersion, PersonalSign, true) + if err != nil { + return nil, fmt.Errorf("cannot generate message. Cause %w", err) + } + + // signature is valid - return account address + address, err := CheckSignatureAndReturnAccountAddress(messageHash, signature) + if err == nil { + return address, nil + } + + return nil, fmt.Errorf("signature verification failed") +} + +func (e EIP712Checker) CheckSignature(encryptionToken string, signature []byte, chainID int64) (*gethcommon.Address, error) { + if len(signature) != 65 { + return nil, fmt.Errorf("invalid signaure length: %d", len(signature)) + } + + messageHash, err := GenerateMessage(encryptionToken, chainID, 1, EIP712Signature, true) + if err != nil { + return nil, fmt.Errorf("cannot generate message. Cause %w", err) + } + + // We transform the V from 27/28 to 0/1. This same change is made in Geth internals, for legacy reasons to be able + // to recover the address: https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L452-L459 + if signature[64] == 27 || signature[64] == 28 { + signature[64] -= 27 + } + + // current signature is valid - return account address + address, err := CheckSignatureAndReturnAccountAddress(messageHash, signature) + if err == nil { + return address, nil + } + + return nil, errors.New("EIP 712 signature verification failed") +} + +// CheckSignature checks if signature is valid for provided encryptionToken and chainID and return address or nil if not valid +// todo (@ziga) Remove this method once old WE endpoints are removed +// encryptionToken is expected to be a public key and not encrypted token as with other signature types +// (since this is only temporary fix and legacy format will be removed soon) +func (lsc LegacyChecker) CheckSignature(encryptionToken string, signature []byte, _ int64) (*gethcommon.Address, error) { + publicKey := []byte(encryptionToken) + msgToSignLegacy := GenerateSignMessage(publicKey) + + recoveredAccountPublicKeyLegacy, err := crypto.SigToPub(accounts.TextHash([]byte(msgToSignLegacy)), signature) + if err != nil { + return nil, fmt.Errorf("failed to recover account public key from legacy signature: %w", err) + } + recoveredAccountAddressLegacy := crypto.PubkeyToAddress(*recoveredAccountPublicKeyLegacy) + return &recoveredAccountAddressLegacy, nil +} + +// SignatureChecker is a map of SignatureType to SignatureChecker +var signatureCheckers = map[SignatureType]SignatureChecker{ + PersonalSign: PersonalSignChecker{}, + EIP712Signature: EIP712Checker{}, + Legacy: LegacyChecker{}, +} + +// CheckSignature checks if signature is valid for provided encryptionToken and chainID and return address or nil if not valid +func CheckSignature(encryptionToken string, signature []byte, chainID int64, signatureType SignatureType) (*gethcommon.Address, error) { + checker, exists := signatureCheckers[signatureType] + if !exists { + return nil, fmt.Errorf("unsupported signature type") + } + return checker.CheckSignature(encryptionToken, signature, chainID) +} + +// CheckSignatureAndReturnAccountAddress checks if the signature is valid for hash of the message and checks if +// signer is an address provided to the function. +// It returns an address if the signature is valid and nil otherwise +func CheckSignatureAndReturnAccountAddress(hashBytes []byte, signature []byte) (*gethcommon.Address, error) { + pubKeyBytes, err := crypto.Ecrecover(hashBytes, signature) + if err != nil { + return nil, err + } + + pubKey, err := crypto.UnmarshalPubkey(pubKeyBytes) + if err != nil { + return nil, err + } + + r := new(big.Int).SetBytes(signature[:32]) + s := new(big.Int).SetBytes(signature[32:64]) + + // Verify the signature and return the result (all the checks above passed) + isSigValid := ecdsa.Verify(pubKey, hashBytes, r, s) + if isSigValid { + address := crypto.PubkeyToAddress(*pubKey) + return &address, nil + } + return nil, fmt.Errorf("invalid signature") +} diff --git a/go/enclave/vkhandler/vk_handler_test.go b/go/enclave/vkhandler/vk_handler_test.go index ef91a7bf12..b511a094a4 100644 --- a/go/enclave/vkhandler/vk_handler_test.go +++ b/go/enclave/vkhandler/vk_handler_test.go @@ -52,7 +52,7 @@ func TestCheckSignature(t *testing.T) { if err != nil { t.Fatalf(err.Error()) } - PersonalSignMessageHash, err := viewingkey.GenerateMessage(userID, chainID, viewingkey.PersonalSignMessageSupportedVersions[0], viewingkey.PersonalSign, true) + PersonalSignMessageHash, err := viewingkey.GenerateMessage(userID, chainID, viewingkey.PersonalSignVersion, viewingkey.PersonalSign, true) if err != nil { t.Fatalf(err.Error()) } @@ -87,11 +87,11 @@ func TestVerifyViewingKey(t *testing.T) { // Generate all message types and create map with the corresponding signature type // Test EIP712 message format - EIP712MessageHash, err := viewingkey.GenerateMessage(userID, chainID, viewingkey.PersonalSignMessageSupportedVersions[0], viewingkey.EIP712Signature, true) + EIP712MessageHash, err := viewingkey.GenerateMessage(userID, chainID, viewingkey.PersonalSignVersion, viewingkey.EIP712Signature, true) if err != nil { t.Fatalf(err.Error()) } - PersonalSignMessageHash, err := viewingkey.GenerateMessage(userID, chainID, viewingkey.PersonalSignMessageSupportedVersions[0], viewingkey.PersonalSign, true) + PersonalSignMessageHash, err := viewingkey.GenerateMessage(userID, chainID, viewingkey.PersonalSignVersion, viewingkey.PersonalSign, true) if err != nil { t.Fatalf(err.Error()) } diff --git a/tools/walletextension/api/routes.go b/tools/walletextension/api/routes.go index 1cad37dc97..dadd47577a 100644 --- a/tools/walletextension/api/routes.go +++ b/tools/walletextension/api/routes.go @@ -6,6 +6,8 @@ import ( "fmt" "net/http" + "github.com/ten-protocol/go-ten/go/common/viewingkey" + "github.com/ten-protocol/go-ten/go/common/log" "github.com/ten-protocol/go-ten/go/common/httputil" @@ -303,7 +305,7 @@ func authenticateRequestHandler(walletExt *walletextension.WalletExtension, conn var reqJSONMap map[string]string err = json.Unmarshal(body, &reqJSONMap) if err != nil { - handleError(conn, walletExt.Logger(), fmt.Errorf("could not unmarshal address request - %w", err)) + handleError(conn, walletExt.Logger(), fmt.Errorf("could not unmarshal request body - %w", err)) return } @@ -321,14 +323,14 @@ func authenticateRequestHandler(walletExt *walletextension.WalletExtension, conn return } - // get optional type of the message that was signed + // get an optional type of the message that was signed messageTypeValue := common.DefaultGatewayAuthMessageType if typeFromRequest, ok := reqJSONMap[common.JSONKeyType]; ok && typeFromRequest != "" { messageTypeValue = typeFromRequest } - // check if message type is valid - messageType, ok := common.SignatureTypeMap[messageTypeValue] + // check if a message type is valid + messageType, ok := viewingkey.SignatureTypeMap[messageTypeValue] if !ok { handleError(conn, walletExt.Logger(), fmt.Errorf("invalid message type: %s", messageTypeValue)) } @@ -495,7 +497,7 @@ func versionRequestHandler(walletExt *walletextension.WalletExtension, userConn } } -// ethRequestHandler parses the user eth request, passes it on to the WE to proxy it and processes the response +// getMessageRequestHandler handles request to /get-message endpoint. func getMessageRequestHandler(walletExt *walletextension.WalletExtension, conn userconn.UserConn) { // read the request body, err := conn.ReadRequest() @@ -538,7 +540,7 @@ func getMessageRequestHandler(walletExt *walletextension.WalletExtension, conn u } } - message, err := walletExt.GetMessage(encryptionToken.(string), formatsSlice) + message, err := walletExt.GenerateUserMessageToSign(encryptionToken.(string), formatsSlice) if err != nil { handleError(conn, walletExt.Logger(), fmt.Errorf("internal error")) walletExt.Logger().Error("error getting message", log.ErrKey, err) @@ -552,12 +554,8 @@ func getMessageRequestHandler(walletExt *walletextension.WalletExtension, conn u } // get string representation of the message format - messageFormat := common.GetBestFormat(formatsSlice) - messageFormatString, err := common.GetSignatureTypeString(messageFormat) - if err != nil { - handleError(conn, walletExt.Logger(), fmt.Errorf("internal error")) - return - } + messageFormat := viewingkey.GetBestFormat(formatsSlice) + messageFormatString := viewingkey.GetSignatureTypeString(messageFormat) response := JSONResponse{ Message: message, diff --git a/tools/walletextension/common/common.go b/tools/walletextension/common/common.go index 7f844d8ee7..8a13de5a3d 100644 --- a/tools/walletextension/common/common.go +++ b/tools/walletextension/common/common.go @@ -99,23 +99,3 @@ func NewFileLogger() gethlog.Logger { return logger } - -// GetBestFormat returns the best format for a message based on available formats that are supported by the user -func GetBestFormat(formatsSlice []string) viewingkey.SignatureType { - // If "Personal" is the only format available, choose it - if len(formatsSlice) == 1 && formatsSlice[0] == "Personal" { - return viewingkey.PersonalSign - } - - // otherwise, choose EIP712 - return viewingkey.EIP712Signature -} - -func GetSignatureTypeString(expectedSignatureType viewingkey.SignatureType) (string, error) { - for key, value := range SignatureTypeMap { - if value == expectedSignatureType { - return key, nil - } - } - return "", fmt.Errorf("unable to find signature type") -} diff --git a/tools/walletextension/common/constants.go b/tools/walletextension/common/constants.go index 073f6f5045..6adbde8737 100644 --- a/tools/walletextension/common/constants.go +++ b/tools/walletextension/common/constants.go @@ -2,8 +2,6 @@ package common import ( "time" - - "github.com/ten-protocol/go-ten/go/common/viewingkey" ) const ( @@ -60,8 +58,3 @@ const ( ) var ReaderHeadTimeout = 10 * time.Second - -var SignatureTypeMap = map[string]viewingkey.SignatureType{ - "EIP712": viewingkey.EIP712Signature, - "Personal": viewingkey.PersonalSign, -} diff --git a/tools/walletextension/lib/client_lib.go b/tools/walletextension/lib/client_lib.go index 30b5a4d47b..483840426c 100644 --- a/tools/walletextension/lib/client_lib.go +++ b/tools/walletextension/lib/client_lib.go @@ -91,7 +91,7 @@ func (o *TGLib) RegisterAccount(pk *ecdsa.PrivateKey, addr gethcommon.Address) e func (o *TGLib) RegisterAccountPersonalSign(pk *ecdsa.PrivateKey, addr gethcommon.Address) error { // create the registration message - messageHash, err := viewingkey.GenerateMessage(string(o.userID), integration.TenChainID, viewingkey.PersonalSignMessageSupportedVersions[0], viewingkey.PersonalSign, true) + messageHash, err := viewingkey.GenerateMessage(string(o.userID), integration.TenChainID, viewingkey.PersonalSignVersion, viewingkey.PersonalSign, true) if err != nil { return err } diff --git a/tools/walletextension/wallet_extension.go b/tools/walletextension/wallet_extension.go index da0ccf8268..1984cece1e 100644 --- a/tools/walletextension/wallet_extension.go +++ b/tools/walletextension/wallet_extension.go @@ -479,16 +479,16 @@ func (w *WalletExtension) GetTenNodeHealthStatus() (bool, error) { return w.tenClient.Health() } -func (w *WalletExtension) GetMessage(encryptionToken string, formatsSlice []string) (string, error) { +func (w *WalletExtension) GenerateUserMessageToSign(encryptionToken string, formatsSlice []string) (string, error) { // Check if the formats are valid for _, format := range formatsSlice { - if _, exists := common.SignatureTypeMap[format]; !exists { + if _, exists := viewingkey.SignatureTypeMap[format]; !exists { return "", fmt.Errorf("invalid format: %s", format) } } - messageFormat := common.GetBestFormat(formatsSlice) - message, err := viewingkey.GenerateMessage(encryptionToken, int64(w.config.TenChainID), viewingkey.PersonalSignMessageSupportedVersions[0], messageFormat, false) + messageFormat := viewingkey.GetBestFormat(formatsSlice) + message, err := viewingkey.GenerateMessage(encryptionToken, int64(w.config.TenChainID), viewingkey.PersonalSignVersion, messageFormat, false) if err != nil { return "", fmt.Errorf("error generating message: %w", err) } From 65026caf484dfaa824d380590e70e7063287b79e Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Wed, 20 Mar 2024 09:42:31 +0000 Subject: [PATCH 14/65] fix --- .../walletextension/frontend/src/components/head-seo.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/walletextension/frontend/src/components/head-seo.tsx b/tools/walletextension/frontend/src/components/head-seo.tsx index fa3af3f4ea..2ee7d0689a 100644 --- a/tools/walletextension/frontend/src/components/head-seo.tsx +++ b/tools/walletextension/frontend/src/components/head-seo.tsx @@ -19,10 +19,10 @@ const HeadSeo = ({ {/* Beagle Security */} - + {/**/} {/* twitter metadata */} From e1a3077ec7abf7a42dcf380de522b7de81cc7717 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Wed, 20 Mar 2024 10:27:26 +0000 Subject: [PATCH 15/65] fix --- lib/gethfork/rpc/handler.go | 2 ++ .../frontend/src/services/ethService.ts | 17 +++++++++++++++++ .../walletextension/rpcapi/wallet_extension.go | 2 +- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/gethfork/rpc/handler.go b/lib/gethfork/rpc/handler.go index 856ef8b2c4..343e09a558 100644 --- a/lib/gethfork/rpc/handler.go +++ b/lib/gethfork/rpc/handler.go @@ -19,6 +19,7 @@ package rpc import ( "context" "encoding/json" + "fmt" "reflect" "strconv" "strings" @@ -487,6 +488,7 @@ func (h *handler) handleCallMsg(ctx *callProc, msg *jsonrpcMessage) *jsonrpcMess return msg.errorResponse(&invalidRequestError{"invalid request"}) default: + fmt.Printf("Invalid request %v\n", jsonrpcMessage{}) return errorMessage(&invalidRequestError{"invalid request"}) } } diff --git a/tools/walletextension/frontend/src/services/ethService.ts b/tools/walletextension/frontend/src/services/ethService.ts index e5a8bcebe6..48993e9237 100644 --- a/tools/walletextension/frontend/src/services/ethService.ts +++ b/tools/walletextension/frontend/src/services/ethService.ts @@ -14,6 +14,23 @@ const ethService = { checkIfMetamaskIsLoaded: async (provider: ethers.providers.Web3Provider) => { try { if (ethereum) { + + // There are some wallets that are conflicting with MetaMask - we want to check that and throw an error if they are connected + const conflictingWalletMap = { + 'Exodus Wallet': ethereum.isExodus, + 'Nest Wallet': ethereum.isNestWallet, + // Add other wallets here as needed + }; + + // Iterate over the wallet map and handle conflicts + for (const [walletName, isWalletConnected] of Object.entries(conflictingWalletMap)) { + if (isWalletConnected) { + const message = `${walletName} is connected and is conflicting with MetaMask. Please disable ${walletName} and try again.`; + showToast(ToastType.DESTRUCTIVE, message); + throw new Error(message); + } + } + return await ethService.handleEthereum(provider); } else { showToast(ToastType.INFO, "Connecting to MetaMask..."); diff --git a/tools/walletextension/rpcapi/wallet_extension.go b/tools/walletextension/rpcapi/wallet_extension.go index f6bd0ae12c..34d771deb8 100644 --- a/tools/walletextension/rpcapi/wallet_extension.go +++ b/tools/walletextension/rpcapi/wallet_extension.go @@ -222,7 +222,7 @@ func (w *Services) AddAddressToUser(hexUserID string, address string, signature // UserHasAccount checks if provided account exist in the database for given userID func (w *Services) UserHasAccount(hexUserID string, address string) (bool, error) { - audit(w, "Checkinf if user has account: %s, address: %s", hexUserID, address) + audit(w, "Checking if user has account: %s, address: %s", hexUserID, address) userIDBytes, err := common.GetUserIDbyte(hexUserID) if err != nil { w.Logger().Error(fmt.Errorf("error decoding string (%s), %w", hexUserID[2:], err).Error()) From 64050e84586812430317dde4a3f9629be53c01a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDiga=20Kokelj?= Date: Wed, 20 Mar 2024 12:05:37 +0100 Subject: [PATCH 16/65] refactor and add separate hashing functions --- go/common/viewingkey/viewing_key.go | 74 +++++++++ go/common/viewingkey/viewing_key_messages.go | 164 ++++++++----------- 2 files changed, 145 insertions(+), 93 deletions(-) diff --git a/go/common/viewingkey/viewing_key.go b/go/common/viewingkey/viewing_key.go index 058893bfe9..0406bda79e 100644 --- a/go/common/viewingkey/viewing_key.go +++ b/go/common/viewingkey/viewing_key.go @@ -1,8 +1,14 @@ package viewingkey import ( + "crypto/ecdsa" + "encoding/hex" + "fmt" + "github.com/ethereum/go-ethereum/accounts" gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/ecies" + "github.com/ten-protocol/go-ten/go/wallet" ) // ViewingKey encapsulates the signed viewing key for an account for use in encrypted communication with an enclave. @@ -24,3 +30,71 @@ type RPCSignedViewingKey struct { SignatureWithAccountKey []byte SignatureType SignatureType } + +// GenerateViewingKeyForWallet takes an account wallet, generates a viewing key and signs the key with the acc's private key +// uses the same method of signature handling as Metamask/geth +// TODO @Ziga - update this method to use the new EIP-712 signature format / personal sign after the removal of the legacy format +func GenerateViewingKeyForWallet(wal wallet.Wallet) (*ViewingKey, error) { + // generate an ECDSA key pair to encrypt sensitive communications with the obscuro enclave + vk, err := crypto.GenerateKey() + if err != nil { + return nil, fmt.Errorf("failed to generate viewing key for RPC client: %w", err) + } + + // get key in ECIES format + viewingPrivateKeyECIES := ecies.ImportECDSA(vk) + + // encode public key as bytes + viewingPubKeyBytes := crypto.CompressPubkey(&vk.PublicKey) + + // sign public key bytes with the wallet's private key + signature, err := mmSignViewingKey(viewingPubKeyBytes, wal.PrivateKey()) + if err != nil { + return nil, err + } + + accAddress := wal.Address() + return &ViewingKey{ + Account: &accAddress, + PrivateKey: viewingPrivateKeyECIES, + PublicKey: viewingPubKeyBytes, + SignatureWithAccountKey: signature, + SignatureType: Legacy, + }, nil +} + +// mmSignViewingKey takes a public key bytes as hex and the private key for a wallet, it simulates the back-and-forth to +// MetaMask and returns the signature bytes to register with the enclave +func mmSignViewingKey(viewingPubKeyBytes []byte, signerKey *ecdsa.PrivateKey) ([]byte, error) { + signature, err := Sign(signerKey, viewingPubKeyBytes) + if err != nil { + return nil, fmt.Errorf("failed to sign viewing key: %w", err) + } + + // We have to transform the V from 0/1 to 27/28, and add the leading "0". + signature[64] += 27 + signatureWithLeadBytes := append([]byte("0"), signature...) + + // this string encoded signature is what the wallet extension would receive after it is signed by metamask + sigStr := hex.EncodeToString(signatureWithLeadBytes) + // and then we extract the signature bytes in the same way as the wallet extension + outputSig, err := hex.DecodeString(sigStr[2:]) + if err != nil { + return nil, fmt.Errorf("failed to decode signature string: %w", err) + } + // This same change is made in geth internals, for legacy reasons to be able to recover the address: + // https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L452-L459 + outputSig[64] -= 27 + + return outputSig, nil +} + +// Sign takes a users Private key and signs the public viewingKey hex +func Sign(userPrivKey *ecdsa.PrivateKey, vkPubKey []byte) ([]byte, error) { + msgToSign := GenerateSignMessage(vkPubKey) + signature, err := crypto.Sign(accounts.TextHash([]byte(msgToSign)), userPrivKey) + if err != nil { + return nil, fmt.Errorf("unable to sign messages - %w", err) + } + return signature, nil +} diff --git a/go/common/viewingkey/viewing_key_messages.go b/go/common/viewingkey/viewing_key_messages.go index 522690d713..afe87c1684 100644 --- a/go/common/viewingkey/viewing_key_messages.go +++ b/go/common/viewingkey/viewing_key_messages.go @@ -1,7 +1,6 @@ package viewingkey import ( - "crypto/ecdsa" "encoding/hex" "encoding/json" "fmt" @@ -10,9 +9,7 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/crypto/ecies" "github.com/ethereum/go-ethereum/signer/core/apitypes" - "github.com/ten-protocol/go-ten/go/wallet" ) const ( @@ -71,43 +68,15 @@ func (e EIP712MessageGenerator) generateMessage(encryptionToken string, chainID if len(encryptionToken) != UserIDHexLength { return nil, fmt.Errorf("userID hex length must be %d, received %d", UserIDHexLength, len(encryptionToken)) } - encryptionToken = "0x" + encryptionToken - - domain := apitypes.TypedDataDomain{ - Name: EIP712DomainNameValue, - Version: EIP712DomainVersionValue, - ChainId: (*math.HexOrDecimal256)(big.NewInt(chainID)), - } - - message := map[string]interface{}{ - EIP712EncryptionToken: encryptionToken, - } - - types := apitypes.Types{ - EIP712Domain: { - {Name: EIP712DomainName, Type: "string"}, - {Name: EIP712DomainVersion, Type: "string"}, - {Name: EIP712DomainChainID, Type: "uint256"}, - }, - EIP712Type: { - {Name: EIP712EncryptionToken, Type: "address"}, - }, - } + EIP712TypedData := createTypedDataForEIP712Message(encryptionToken, chainID) - newTypeElement := apitypes.TypedData{ - Types: types, - PrimaryType: EIP712Type, - Domain: domain, - Message: message, - } - - rawData, err := getBytesFromTypedData(newTypeElement) + rawData, err := getBytesFromTypedData(EIP712TypedData) if err != nil { return nil, err } // add the JSON message to the list of messages - jsonData, err := json.Marshal(newTypeElement) + jsonData, err := json.Marshal(EIP712TypedData) if err != nil { return nil, err } @@ -119,7 +88,6 @@ func (e EIP712MessageGenerator) generateMessage(encryptionToken string, chainID } // GenerateMessage generates a message for the given encryptionToken, chainID, version and signatureType -// hashed parameter is used to determine if the returned message should be hashed or returned in plain text func GenerateMessage(encryptionToken string, chainID int64, version int, signatureType SignatureType, hashed bool) ([]byte, error) { generator, exists := messageGenerators[signatureType] if !exists { @@ -128,6 +96,50 @@ func GenerateMessage(encryptionToken string, chainID int64, version int, signatu return generator.generateMessage(encryptionToken, chainID, version, hashed) } +// MessageHash is an interface for getting the hash of the message +type MessageHash interface { + getMessageHash(message []byte) []byte +} + +type ( + PersonalMessageHash struct{} + EIP712MessageHash struct{} +) + +var messageHash = map[SignatureType]MessageHash{ + PersonalSign: PersonalMessageHash{}, + EIP712Signature: EIP712MessageHash{}, +} + +// getMessageHash returns the hash for the personal message +func (p PersonalMessageHash) getMessageHash(message []byte) []byte { + return accounts.TextHash(message) +} + +// getMessageHash returns the hash for the EIP712 message +func (E EIP712MessageHash) getMessageHash(message []byte) []byte { + var EIP712TypedData apitypes.TypedData + err := json.Unmarshal(message, &EIP712TypedData) + if err != nil { + return nil + } + + rawData, err := getBytesFromTypedData(EIP712TypedData) + if err != nil { + return nil + } + return crypto.Keccak256(rawData) +} + +// GetMessageHash returns the hash of the message based on the signature type +func GetMessageHash(message []byte, signatureType SignatureType) ([]byte, error) { + hashFunction, exists := messageHash[signatureType] + if !exists { + return nil, fmt.Errorf("unsupported signature type") + } + return hashFunction.getMessageHash(message), nil +} + // GenerateSignMessage creates the message to be signed // vkPubKey is expected to be a []byte("0x....") to create the signing message // todo (@ziga) Remove this method once old WE endpoints are removed @@ -153,72 +165,38 @@ func getBytesFromTypedData(typedData apitypes.TypedData) ([]byte, error) { return rawData, nil } -// GenerateViewingKeyForWallet takes an account wallet, generates a viewing key and signs the key with the acc's private key -// uses the same method of signature handling as Metamask/geth -// TODO @Ziga - update this method to use the new EIP-712 signature format / personal sign after the removal of the legacy format -func GenerateViewingKeyForWallet(wal wallet.Wallet) (*ViewingKey, error) { - // generate an ECDSA key pair to encrypt sensitive communications with the obscuro enclave - vk, err := crypto.GenerateKey() - if err != nil { - return nil, fmt.Errorf("failed to generate viewing key for RPC client: %w", err) - } - - // get key in ECIES format - viewingPrivateKeyECIES := ecies.ImportECDSA(vk) - - // encode public key as bytes - viewingPubKeyBytes := crypto.CompressPubkey(&vk.PublicKey) +// createTypedDataForEIP712Message creates typed data for EIP712 message +func createTypedDataForEIP712Message(encryptionToken string, chainID int64) apitypes.TypedData { + encryptionToken = "0x" + encryptionToken - // sign public key bytes with the wallet's private key - signature, err := mmSignViewingKey(viewingPubKeyBytes, wal.PrivateKey()) - if err != nil { - return nil, err + domain := apitypes.TypedDataDomain{ + Name: EIP712DomainNameValue, + Version: EIP712DomainVersionValue, + ChainId: (*math.HexOrDecimal256)(big.NewInt(chainID)), } - accAddress := wal.Address() - return &ViewingKey{ - Account: &accAddress, - PrivateKey: viewingPrivateKeyECIES, - PublicKey: viewingPubKeyBytes, - SignatureWithAccountKey: signature, - SignatureType: Legacy, - }, nil -} - -// mmSignViewingKey takes a public key bytes as hex and the private key for a wallet, it simulates the back-and-forth to -// MetaMask and returns the signature bytes to register with the enclave -func mmSignViewingKey(viewingPubKeyBytes []byte, signerKey *ecdsa.PrivateKey) ([]byte, error) { - signature, err := Sign(signerKey, viewingPubKeyBytes) - if err != nil { - return nil, fmt.Errorf("failed to sign viewing key: %w", err) + message := map[string]interface{}{ + EIP712EncryptionToken: encryptionToken, } - // We have to transform the V from 0/1 to 27/28, and add the leading "0". - signature[64] += 27 - signatureWithLeadBytes := append([]byte("0"), signature...) - - // this string encoded signature is what the wallet extension would receive after it is signed by metamask - sigStr := hex.EncodeToString(signatureWithLeadBytes) - // and then we extract the signature bytes in the same way as the wallet extension - outputSig, err := hex.DecodeString(sigStr[2:]) - if err != nil { - return nil, fmt.Errorf("failed to decode signature string: %w", err) + types := apitypes.Types{ + EIP712Domain: { + {Name: EIP712DomainName, Type: "string"}, + {Name: EIP712DomainVersion, Type: "string"}, + {Name: EIP712DomainChainID, Type: "uint256"}, + }, + EIP712Type: { + {Name: EIP712EncryptionToken, Type: "address"}, + }, } - // This same change is made in geth internals, for legacy reasons to be able to recover the address: - // https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L452-L459 - outputSig[64] -= 27 - - return outputSig, nil -} -// Sign takes a users Private key and signs the public viewingKey hex -func Sign(userPrivKey *ecdsa.PrivateKey, vkPubKey []byte) ([]byte, error) { - msgToSign := GenerateSignMessage(vkPubKey) - signature, err := crypto.Sign(accounts.TextHash([]byte(msgToSign)), userPrivKey) - if err != nil { - return nil, fmt.Errorf("unable to sign messages - %w", err) + typedData := apitypes.TypedData{ + Types: types, + PrimaryType: EIP712Type, + Domain: domain, + Message: message, } - return signature, nil + return typedData } // CalculateUserIDHex CalculateUserID calculates userID from a public key From 871a4f0d43700e892ae17625591b1b6640f4c20b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDiga=20Kokelj?= Date: Wed, 20 Mar 2024 12:19:30 +0100 Subject: [PATCH 17/65] GenerateMessage single responsibility + separate hashing --- go/common/viewingkey/viewing_key.go | 1 + go/common/viewingkey/viewing_key_messages.go | 25 +++++------------ go/common/viewingkey/viewing_key_signature.go | 18 ++++++++++--- go/enclave/vkhandler/vk_handler_test.go | 27 ++++++++++++++++--- tools/walletextension/lib/client_lib.go | 14 ++++++++-- tools/walletextension/wallet_extension.go | 2 +- 6 files changed, 57 insertions(+), 30 deletions(-) diff --git a/go/common/viewingkey/viewing_key.go b/go/common/viewingkey/viewing_key.go index 0406bda79e..464b29ca81 100644 --- a/go/common/viewingkey/viewing_key.go +++ b/go/common/viewingkey/viewing_key.go @@ -4,6 +4,7 @@ import ( "crypto/ecdsa" "encoding/hex" "fmt" + "github.com/ethereum/go-ethereum/accounts" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" diff --git a/go/common/viewingkey/viewing_key_messages.go b/go/common/viewingkey/viewing_key_messages.go index afe87c1684..036a82a99b 100644 --- a/go/common/viewingkey/viewing_key_messages.go +++ b/go/common/viewingkey/viewing_key_messages.go @@ -42,7 +42,7 @@ var EIP712EncryptionTokens = [...]string{ } type MessageGenerator interface { - generateMessage(encryptionToken string, chainID int64, version int, hash bool) ([]byte, error) + generateMessage(encryptionToken string, chainID int64, version int) ([]byte, error) } type ( @@ -56,44 +56,31 @@ var messageGenerators = map[SignatureType]MessageGenerator{ } // GenerateMessage generates a message for the given encryptionToken, chainID, version and signatureType -func (p PersonalMessageGenerator) generateMessage(encryptionToken string, chainID int64, version int, hash bool) ([]byte, error) { - textMessage := fmt.Sprintf(PersonalSignMessageFormat, encryptionToken, chainID, version) - if hash { - return accounts.TextHash([]byte(textMessage)), nil - } - return []byte(textMessage), nil +func (p PersonalMessageGenerator) generateMessage(encryptionToken string, chainID int64, version int) ([]byte, error) { + return []byte(fmt.Sprintf(PersonalSignMessageFormat, encryptionToken, chainID, version)), nil } -func (e EIP712MessageGenerator) generateMessage(encryptionToken string, chainID int64, _ int, hash bool) ([]byte, error) { +func (e EIP712MessageGenerator) generateMessage(encryptionToken string, chainID int64, _ int) ([]byte, error) { if len(encryptionToken) != UserIDHexLength { return nil, fmt.Errorf("userID hex length must be %d, received %d", UserIDHexLength, len(encryptionToken)) } EIP712TypedData := createTypedDataForEIP712Message(encryptionToken, chainID) - rawData, err := getBytesFromTypedData(EIP712TypedData) - if err != nil { - return nil, err - } - // add the JSON message to the list of messages jsonData, err := json.Marshal(EIP712TypedData) if err != nil { return nil, err } - - if hash { - return crypto.Keccak256(rawData), nil - } return jsonData, nil } // GenerateMessage generates a message for the given encryptionToken, chainID, version and signatureType -func GenerateMessage(encryptionToken string, chainID int64, version int, signatureType SignatureType, hashed bool) ([]byte, error) { +func GenerateMessage(encryptionToken string, chainID int64, version int, signatureType SignatureType) ([]byte, error) { generator, exists := messageGenerators[signatureType] if !exists { return nil, fmt.Errorf("unsupported signature type") } - return generator.generateMessage(encryptionToken, chainID, version, hashed) + return generator.generateMessage(encryptionToken, chainID, version) } // MessageHash is an interface for getting the hash of the message diff --git a/go/common/viewingkey/viewing_key_signature.go b/go/common/viewingkey/viewing_key_signature.go index 41c0bd3dc1..9c48f0b3cc 100644 --- a/go/common/viewingkey/viewing_key_signature.go +++ b/go/common/viewingkey/viewing_key_signature.go @@ -34,13 +34,18 @@ func (psc PersonalSignChecker) CheckSignature(encryptionToken string, signature signature[64] -= 27 } - messageHash, err := GenerateMessage(encryptionToken, chainID, PersonalSignVersion, PersonalSign, true) + msg, err := GenerateMessage(encryptionToken, chainID, PersonalSignVersion, PersonalSign) if err != nil { return nil, fmt.Errorf("cannot generate message. Cause %w", err) } + msgHash, err := GetMessageHash(msg, PersonalSign) + if err != nil { + return nil, fmt.Errorf("cannot generate message hash. Cause %w", err) + } + // signature is valid - return account address - address, err := CheckSignatureAndReturnAccountAddress(messageHash, signature) + address, err := CheckSignatureAndReturnAccountAddress(msgHash, signature) if err == nil { return address, nil } @@ -53,11 +58,16 @@ func (e EIP712Checker) CheckSignature(encryptionToken string, signature []byte, return nil, fmt.Errorf("invalid signaure length: %d", len(signature)) } - messageHash, err := GenerateMessage(encryptionToken, chainID, 1, EIP712Signature, true) + msg, err := GenerateMessage(encryptionToken, chainID, 1, EIP712Signature) if err != nil { return nil, fmt.Errorf("cannot generate message. Cause %w", err) } + msgHash, err := GetMessageHash(msg, EIP712Signature) + if err != nil { + return nil, fmt.Errorf("cannot generate message hash. Cause %w", err) + } + // We transform the V from 27/28 to 0/1. This same change is made in Geth internals, for legacy reasons to be able // to recover the address: https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L452-L459 if signature[64] == 27 || signature[64] == 28 { @@ -65,7 +75,7 @@ func (e EIP712Checker) CheckSignature(encryptionToken string, signature []byte, } // current signature is valid - return account address - address, err := CheckSignatureAndReturnAccountAddress(messageHash, signature) + address, err := CheckSignatureAndReturnAccountAddress(msgHash, signature) if err == nil { return address, nil } diff --git a/go/enclave/vkhandler/vk_handler_test.go b/go/enclave/vkhandler/vk_handler_test.go index b511a094a4..e8065ef2e8 100644 --- a/go/enclave/vkhandler/vk_handler_test.go +++ b/go/enclave/vkhandler/vk_handler_test.go @@ -48,11 +48,21 @@ func TestCheckSignature(t *testing.T) { userPrivKey, _, userID, userAddress := generateRandomUserKeys() // Generate all message types and create map with the corresponding signature type - EIP712MessageHash, err := viewingkey.GenerateMessage(userID, chainID, 0, viewingkey.EIP712Signature, true) + EIP712Message, err := viewingkey.GenerateMessage(userID, chainID, 0, viewingkey.EIP712Signature) if err != nil { t.Fatalf(err.Error()) } - PersonalSignMessageHash, err := viewingkey.GenerateMessage(userID, chainID, viewingkey.PersonalSignVersion, viewingkey.PersonalSign, true) + EIP712MessageHash, err := viewingkey.GetMessageHash(EIP712Message, viewingkey.EIP712Signature) + if err != nil { + t.Fatalf(err.Error()) + } + + PersonalSignMessage, err := viewingkey.GenerateMessage(userID, chainID, viewingkey.PersonalSignVersion, viewingkey.PersonalSign) + if err != nil { + t.Fatalf(err.Error()) + } + + PersonalSignMessageHash, err := viewingkey.GetMessageHash(PersonalSignMessage, viewingkey.PersonalSign) if err != nil { t.Fatalf(err.Error()) } @@ -87,11 +97,20 @@ func TestVerifyViewingKey(t *testing.T) { // Generate all message types and create map with the corresponding signature type // Test EIP712 message format - EIP712MessageHash, err := viewingkey.GenerateMessage(userID, chainID, viewingkey.PersonalSignVersion, viewingkey.EIP712Signature, true) + EIP712Message, err := viewingkey.GenerateMessage(userID, chainID, viewingkey.PersonalSignVersion, viewingkey.EIP712Signature) + if err != nil { + t.Fatalf(err.Error()) + } + EIP712MessageHash, err := viewingkey.GetMessageHash(EIP712Message, viewingkey.EIP712Signature) + if err != nil { + t.Fatalf(err.Error()) + } + + PersonalSignMessage, err := viewingkey.GenerateMessage(userID, chainID, viewingkey.PersonalSignVersion, viewingkey.PersonalSign) if err != nil { t.Fatalf(err.Error()) } - PersonalSignMessageHash, err := viewingkey.GenerateMessage(userID, chainID, viewingkey.PersonalSignVersion, viewingkey.PersonalSign, true) + PersonalSignMessageHash, err := viewingkey.GetMessageHash(PersonalSignMessage, viewingkey.PersonalSign) if err != nil { t.Fatalf(err.Error()) } diff --git a/tools/walletextension/lib/client_lib.go b/tools/walletextension/lib/client_lib.go index 483840426c..42be5c4eb0 100644 --- a/tools/walletextension/lib/client_lib.go +++ b/tools/walletextension/lib/client_lib.go @@ -46,11 +46,16 @@ func (o *TGLib) Join() error { func (o *TGLib) RegisterAccount(pk *ecdsa.PrivateKey, addr gethcommon.Address) error { // create the registration message - messageHash, err := viewingkey.GenerateMessage(string(o.userID), integration.TenChainID, 1, viewingkey.EIP712Signature, true) + message, err := viewingkey.GenerateMessage(string(o.userID), integration.TenChainID, 1, viewingkey.EIP712Signature) if err != nil { return err } + messageHash, err := viewingkey.GetMessageHash(message, viewingkey.EIP712Signature) + if err != nil { + return fmt.Errorf("failed to get message hash: %w", err) + } + sig, err := crypto.Sign(messageHash, pk) if err != nil { return fmt.Errorf("failed to sign message: %w", err) @@ -91,11 +96,16 @@ func (o *TGLib) RegisterAccount(pk *ecdsa.PrivateKey, addr gethcommon.Address) e func (o *TGLib) RegisterAccountPersonalSign(pk *ecdsa.PrivateKey, addr gethcommon.Address) error { // create the registration message - messageHash, err := viewingkey.GenerateMessage(string(o.userID), integration.TenChainID, viewingkey.PersonalSignVersion, viewingkey.PersonalSign, true) + message, err := viewingkey.GenerateMessage(string(o.userID), integration.TenChainID, viewingkey.PersonalSignVersion, viewingkey.PersonalSign) if err != nil { return err } + messageHash, err := viewingkey.GetMessageHash(message, viewingkey.PersonalSign) + if err != nil { + return fmt.Errorf("failed to get message hash: %w", err) + } + sig, err := crypto.Sign(messageHash, pk) if err != nil { return fmt.Errorf("failed to sign message: %w", err) diff --git a/tools/walletextension/wallet_extension.go b/tools/walletextension/wallet_extension.go index 1984cece1e..c07d1fb048 100644 --- a/tools/walletextension/wallet_extension.go +++ b/tools/walletextension/wallet_extension.go @@ -488,7 +488,7 @@ func (w *WalletExtension) GenerateUserMessageToSign(encryptionToken string, form } messageFormat := viewingkey.GetBestFormat(formatsSlice) - message, err := viewingkey.GenerateMessage(encryptionToken, int64(w.config.TenChainID), viewingkey.PersonalSignVersion, messageFormat, false) + message, err := viewingkey.GenerateMessage(encryptionToken, int64(w.config.TenChainID), viewingkey.PersonalSignVersion, messageFormat) if err != nil { return "", fmt.Errorf("error generating message: %w", err) } From ab3a456db4e787dff9d1758743e2ac2ab1c58642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDiga=20Kokelj?= Date: Wed, 20 Mar 2024 13:13:45 +0100 Subject: [PATCH 18/65] lint --- tools/walletextension/api/routes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/walletextension/api/routes.go b/tools/walletextension/api/routes.go index c7024f418a..6d38b70feb 100644 --- a/tools/walletextension/api/routes.go +++ b/tools/walletextension/api/routes.go @@ -5,7 +5,7 @@ import ( "encoding/json" "fmt" "net/http" - + "github.com/ten-protocol/go-ten/go/common/viewingkey" "github.com/ten-protocol/go-ten/lib/gethfork/node" From c9d1e6ec7399433cf454b27ab04c021ed2fc8295 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Wed, 20 Mar 2024 12:37:27 +0000 Subject: [PATCH 19/65] fix --- tools/faucet/faucet/faucet.go | 1 + tools/walletextension/walletextension_container.go | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/tools/faucet/faucet/faucet.go b/tools/faucet/faucet/faucet.go index 7b6912f073..c45dd4727f 100644 --- a/tools/faucet/faucet/faucet.go +++ b/tools/faucet/faucet/faucet.go @@ -3,6 +3,7 @@ package faucet import ( "context" "encoding/json" + "errors" "fmt" "math/big" "os" diff --git a/tools/walletextension/walletextension_container.go b/tools/walletextension/walletextension_container.go index d510cbb71e..fadb634d18 100644 --- a/tools/walletextension/walletextension_container.go +++ b/tools/walletextension/walletextension_container.go @@ -80,11 +80,7 @@ func NewContainerFromConfig(config wecommon.Config, logger gethlog.Logger) *Cont }, }) - // rpcServer. return NewWalletExtensionContainer( - // hostRPCBindAddrWS, - // walletExt, - // databaseStorage, stopControl, rpcServer, logger, From ba5f06023c1f9a72b48be34b98b93693b8dad671 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Wed, 20 Mar 2024 13:28:30 +0000 Subject: [PATCH 20/65] fix --- integration/networktest/env/network_setup.go | 10 +++++----- integration/simulation/devnetwork/dev_network.go | 11 ++++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/integration/networktest/env/network_setup.go b/integration/networktest/env/network_setup.go index 8f0b4df2ca..86d45bb8e7 100644 --- a/integration/networktest/env/network_setup.go +++ b/integration/networktest/env/network_setup.go @@ -8,8 +8,8 @@ import ( "github.com/ten-protocol/go-ten/integration" "github.com/ten-protocol/go-ten/integration/common/testlog" "github.com/ten-protocol/go-ten/integration/networktest" - gatewaycfg "github.com/ten-protocol/go-ten/tools/walletextension/config" - "github.com/ten-protocol/go-ten/tools/walletextension/container" + "github.com/ten-protocol/go-ten/tools/walletextension" + wecommon "github.com/ten-protocol/go-ten/tools/walletextension/common" ) const ( @@ -68,7 +68,7 @@ type TestnetEnvOption func(env *testnetEnv) type testnetEnv struct { testnetConnector *testnetConnector localTenGateway bool - tenGatewayContainer *container.WalletExtensionContainer + tenGatewayContainer *walletextension.Container logger gethlog.Logger } @@ -99,7 +99,7 @@ func (t *testnetEnv) startTenGateway() { validatorHTTP := validator[len("http://"):] // replace the last character with a 1 (expect it to be zero), this is good enough for these tests validatorWS := validatorHTTP[:len(validatorHTTP)-1] + "1" - cfg := gatewaycfg.Config{ + cfg := wecommon.Config{ WalletExtensionHost: "127.0.0.1", WalletExtensionPortHTTP: _gwHTTPPort, WalletExtensionPortWS: _gwWSPort, @@ -110,7 +110,7 @@ func (t *testnetEnv) startTenGateway() { DBType: "sqlite", TenChainID: integration.TenChainID, } - tenGWContainer := container.NewWalletExtensionContainerFromConfig(cfg, t.logger) + tenGWContainer := walletextension.NewContainerFromConfig(cfg, t.logger) go func() { fmt.Println("Starting Ten Gateway, HTTP Port:", _gwHTTPPort, "WS Port:", _gwWSPort) err := tenGWContainer.Start() diff --git a/integration/simulation/devnetwork/dev_network.go b/integration/simulation/devnetwork/dev_network.go index 2d1bc96884..5b4bb7b3f4 100644 --- a/integration/simulation/devnetwork/dev_network.go +++ b/integration/simulation/devnetwork/dev_network.go @@ -7,10 +7,11 @@ import ( "sync" "time" + "github.com/ten-protocol/go-ten/tools/walletextension" + wecommon "github.com/ten-protocol/go-ten/tools/walletextension/common" + "github.com/ten-protocol/go-ten/integration/common/testlog" "github.com/ten-protocol/go-ten/integration/simulation/network" - gatewaycfg "github.com/ten-protocol/go-ten/tools/walletextension/config" - "github.com/ten-protocol/go-ten/tools/walletextension/container" "github.com/ten-protocol/go-ten/go/ethadapter" @@ -57,7 +58,7 @@ type InMemDevNetwork struct { obscuroConfig ObscuroConfig obscuroSequencer *InMemNodeOperator obscuroValidators []*InMemNodeOperator - tenGatewayContainer *container.WalletExtensionContainer + tenGatewayContainer *walletextension.Container tenGatewayEnabled bool @@ -194,7 +195,7 @@ func (s *InMemDevNetwork) startTenGateway() { validatorWS := validator.HostRPCWSAddress() // remove ws:// prefix for the gateway config validatorWS = validatorWS[len("ws://"):] - cfg := gatewaycfg.Config{ + cfg := wecommon.Config{ WalletExtensionHost: "127.0.0.1", WalletExtensionPortHTTP: _gwHTTPPort, WalletExtensionPortWS: _gwWSPort, @@ -205,7 +206,7 @@ func (s *InMemDevNetwork) startTenGateway() { DBType: "sqlite", TenChainID: integration.TenChainID, } - tenGWContainer := container.NewWalletExtensionContainerFromConfig(cfg, s.logger) + tenGWContainer := walletextension.NewContainerFromConfig(cfg, s.logger) go func() { fmt.Println("Starting Ten Gateway, HTTP Port:", _gwHTTPPort, "WS Port:", _gwWSPort) err := tenGWContainer.Start() From d9d9e96c344e15a9c0ddd6dd8b773d0b349f4ac4 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Wed, 20 Mar 2024 14:51:17 +0000 Subject: [PATCH 21/65] lint --- testnet/launcher/eth2network/docker.go | 2 +- tools/walletextension/test/apis.go | 8 - tools/walletextension/test/utils.go | 376 ------------------------- 3 files changed, 1 insertion(+), 385 deletions(-) delete mode 100644 tools/walletextension/test/utils.go diff --git a/testnet/launcher/eth2network/docker.go b/testnet/launcher/eth2network/docker.go index 65685833f5..0fc73f23d4 100644 --- a/testnet/launcher/eth2network/docker.go +++ b/testnet/launcher/eth2network/docker.go @@ -50,7 +50,7 @@ func (n *Eth2Network) Start() error { } func (n *Eth2Network) IsReady() error { - timeout := 10 * time.Minute + timeout := 20 * time.Minute interval := 2 * time.Second var dial *ethclient.Client var err error diff --git a/tools/walletextension/test/apis.go b/tools/walletextension/test/apis.go index 6dc42a28cc..d690231710 100644 --- a/tools/walletextension/test/apis.go +++ b/tools/walletextension/test/apis.go @@ -37,7 +37,6 @@ type DummyAPI struct { enclavePrivateKey *ecies.PrivateKey viewingKey []byte signature []byte - address *gethcommon.Address } func NewDummyAPI() *DummyAPI { @@ -51,13 +50,6 @@ func NewDummyAPI() *DummyAPI { } } -// Determines which key the API will encrypt responses with. -func (api *DummyAPI) setViewingKey(address *gethcommon.Address, compressedVKKeyHexBytes, signature []byte) { - api.viewingKey = compressedVKKeyHexBytes - api.address = address - api.signature = signature -} - func (api *DummyAPI) ChainId() (*hexutil.Big, error) { //nolint:stylecheck,revive chainID, err := hexutil.DecodeBig(l2ChainIDHex) return (*hexutil.Big)(chainID), err diff --git a/tools/walletextension/test/utils.go b/tools/walletextension/test/utils.go deleted file mode 100644 index ad3481c7d1..0000000000 --- a/tools/walletextension/test/utils.go +++ /dev/null @@ -1,376 +0,0 @@ -package test - -import ( - "bytes" - "crypto/ecdsa" - "encoding/hex" - "encoding/json" - "fmt" - "io" - "net/http" - "os" - "testing" - "time" - - "github.com/ten-protocol/go-ten/tools/walletextension" - - "github.com/ethereum/go-ethereum/rpc" - - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/crypto" - "github.com/go-kit/kit/transport/http/jsonrpc" - "github.com/gorilla/websocket" - "github.com/ten-protocol/go-ten/go/common/log" - "github.com/ten-protocol/go-ten/go/common/viewingkey" - "github.com/ten-protocol/go-ten/tools/walletextension/common" - - gethcommon "github.com/ethereum/go-ethereum/common" - gethlog "github.com/ethereum/go-ethereum/log" - gethnode "github.com/ethereum/go-ethereum/node" - hostcontainer "github.com/ten-protocol/go-ten/go/host/container" -) - -const jsonID = "1" - -func createWalExtCfg(connectPort, wallHTTPPort, wallWSPort int) *common.Config { //nolint: unparam - testDBPath, err := os.CreateTemp("", "") - if err != nil { - panic("could not create persistence file for wallet extension tests") - } - return &common.Config{ - WalletExtensionHost: "127.0.0.1", - NodeRPCWebsocketAddress: fmt.Sprintf("localhost:%d", connectPort), - DBPathOverride: testDBPath.Name(), - WalletExtensionPortHTTP: wallHTTPPort, - WalletExtensionPortWS: wallWSPort, - DBType: "sqlite", - } -} - -func createWalExt(t *testing.T, walExtCfg *common.Config) func() error { - // todo (@ziga) - log somewhere else? - logger := log.New(log.WalletExtCmp, int(gethlog.LvlInfo), log.SysOut) - - wallExtContainer := walletextension.NewContainerFromConfig(*walExtCfg, logger) - go wallExtContainer.Start() //nolint: errcheck - - err := waitForEndpoint(fmt.Sprintf("http://%s:%d%s", walExtCfg.WalletExtensionHost, walExtCfg.WalletExtensionPortHTTP, common.PathReady)) - if err != nil { - t.Fatalf(err.Error()) - } - - return wallExtContainer.Stop -} - -// Creates an RPC layer that the wallet extension can connect to. Returns a handle to shut down the host. -func createDummyHost(t *testing.T, wsRPCPort int) (*DummyAPI, func() error) { //nolint: unparam - dummyAPI := NewDummyAPI() - cfg := gethnode.Config{ - WSHost: common.Localhost, - WSPort: wsRPCPort, - WSOrigins: []string{"*"}, - } - rpcServerNode, err := gethnode.New(&cfg) - rpcServerNode.RegisterAPIs([]rpc.API{ - { - Namespace: hostcontainer.APINamespaceObscuro, - Service: dummyAPI, - }, - { - Namespace: hostcontainer.APINamespaceEth, - Service: dummyAPI, - }, - }) - if err != nil { - t.Fatalf(fmt.Sprintf("could not create new client server. Cause: %s", err)) - } - t.Cleanup(func() { rpcServerNode.Close() }) - - err = rpcServerNode.Start() - if err != nil { - t.Fatalf(fmt.Sprintf("could not create new client server. Cause: %s", err)) - } - return dummyAPI, rpcServerNode.Close -} - -// Waits for the endpoint to be available. Times out after three seconds. -func waitForEndpoint(addr string) error { - retries := 30 - for i := 0; i < retries; i++ { - resp, err := http.Get(addr) //nolint:noctx,gosec - if resp != nil && resp.Body != nil { - resp.Body.Close() - } - if err == nil { - return nil - } - time.Sleep(300 * time.Millisecond) - } - return fmt.Errorf("could not establish connection to wallet extension") -} - -// Makes an Ethereum JSON RPC request over HTTP and returns the response body. -func makeHTTPEthJSONReq(port int, method string, params interface{}) []byte { - reqBody := prepareRequestBody(method, params) - return makeRequestHTTP(fmt.Sprintf("http://%s:%d/v1/", common.Localhost, port), reqBody) -} - -// Makes an Ethereum JSON RPC request over HTTP to specific endpoint and returns the response body. -func makeHTTPEthJSONReqWithPath(port int, path string) []byte { - reqBody := prepareRequestBody("", "") - return makeRequestHTTP(fmt.Sprintf("http://%s:%d/%s", common.Localhost, port, path), reqBody) -} - -// Makes an Ethereum JSON RPC request over HTTP and returns the response body with userID query paremeter. -func makeHTTPEthJSONReqWithUserID(port int, method string, params interface{}, userID string) []byte { //nolint: unparam - reqBody := prepareRequestBody(method, params) - return makeRequestHTTP(fmt.Sprintf("http://%s:%d/v1/?token=%s", common.Localhost, port, userID), reqBody) -} - -// Makes an Ethereum JSON RPC request over websockets and returns the response body. -func makeWSEthJSONReq(port int, method string, params interface{}) ([]byte, *websocket.Conn) { - reqBody := prepareRequestBody(method, params) - return makeRequestWS(fmt.Sprintf("ws://%s:%d", common.Localhost, port), reqBody) -} - -func makeWSEthJSONReqWithConn(conn *websocket.Conn, method string, params interface{}) []byte { - reqBody := prepareRequestBody(method, params) - return issueRequestWS(conn, reqBody) -} - -func openWSConn(port int) (*websocket.Conn, error) { - conn, dialResp, err := websocket.DefaultDialer.Dial(fmt.Sprintf("ws://%s:%d", common.Localhost, port), nil) - if dialResp != nil && dialResp.Body != nil { - defer dialResp.Body.Close() - } - if err != nil { - if conn != nil { - conn.Close() - } - panic(fmt.Errorf("received error response from wallet extension: %w", err)) - } - return conn, err -} - -// Formats a method and its parameters as a Ethereum JSON RPC request. -func prepareRequestBody(method string, params interface{}) []byte { - reqBodyBytes, err := json.Marshal(map[string]interface{}{ - common.JSONKeyRPCVersion: jsonrpc.Version, - common.JSONKeyMethod: method, - common.JSONKeyParams: params, - common.JSONKeyID: jsonID, - }) - if err != nil { - panic(fmt.Errorf("failed to prepare request body. Cause: %w", err)) - } - return reqBodyBytes -} - -// Generates a new account and registers it with the node. -func simulateViewingKeyRegister(t *testing.T, walletHTTPPort, walletWSPort int, useWS bool) (*gethcommon.Address, []byte, []byte) { - accountPrivateKey, err := crypto.GenerateKey() - if err != nil { - t.Fatalf(err.Error()) - } - accountAddr := crypto.PubkeyToAddress(accountPrivateKey.PublicKey) - - compressedHexVKBytes := generateViewingKey(walletHTTPPort, walletWSPort, accountAddr.String(), useWS) - mmSignature := signViewingKey(accountPrivateKey, compressedHexVKBytes) - submitViewingKey(accountAddr.String(), walletHTTPPort, walletWSPort, mmSignature, useWS) - - // transform the metamask signature to the geth compatible one - sigStr := hex.EncodeToString(mmSignature) - // and then we extract the signature bytes in the same way as the wallet extension - outputSig, err := hex.DecodeString(sigStr[2:]) - if err != nil { - panic(fmt.Errorf("failed to decode signature string: %w", err)) - } - // This same change is made in geth internals, for legacy reasons to be able to recover the address: - // https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L452-L459 - outputSig[64] -= 27 - - // keys are expected to be a []byte of hex string - vkPubKeyBytes, err := hex.DecodeString(string(compressedHexVKBytes)) - if err != nil { - panic(fmt.Errorf("unexpected hex string")) - } - - return &accountAddr, vkPubKeyBytes, outputSig -} - -// Generates a viewing key. -func generateViewingKey(wallHTTPPort, wallWSPort int, accountAddress string, useWS bool) []byte { - generateViewingKeyBodyBytes, err := json.Marshal(map[string]interface{}{ - common.JSONKeyAddress: accountAddress, - }) - if err != nil { - panic(err) - } - - if useWS { - viewingKeyBytes, _ := makeRequestWS(fmt.Sprintf("ws://%s:%d%s", common.Localhost, wallWSPort, common.PathGenerateViewingKey), generateViewingKeyBodyBytes) - return viewingKeyBytes - } - return makeRequestHTTP(fmt.Sprintf("http://%s:%d%s", common.Localhost, wallHTTPPort, common.PathGenerateViewingKey), generateViewingKeyBodyBytes) -} - -// Signs a viewing key like metamask -func signViewingKey(privateKey *ecdsa.PrivateKey, compressedHexVKBytes []byte) []byte { - // compressedHexVKBytes already has the key in the hex format - // it should be decoded back into raw bytes - viewingKey, err := hex.DecodeString(string(compressedHexVKBytes)) - if err != nil { - panic(err) - } - msgToSign := viewingkey.GenerateSignMessage(viewingKey) - signature, err := crypto.Sign(accounts.TextHash([]byte(msgToSign)), privateKey) - if err != nil { - panic(err) - } - - // We have to transform the V from 0/1 to 27/28, and add the leading "0". - signature[64] += 27 - signatureWithLeadBytes := append([]byte("0"), signature...) - - return signatureWithLeadBytes -} - -// Submits a viewing key. -func submitViewingKey(accountAddr string, wallHTTPPort, wallWSPort int, signature []byte, useWS bool) { - submitViewingKeyBodyBytes, err := json.Marshal(map[string]interface{}{ - common.JSONKeySignature: hex.EncodeToString(signature), - common.JSONKeyAddress: accountAddr, - }) - if err != nil { - panic(err) - } - - if useWS { - makeRequestWS(fmt.Sprintf("ws://%s:%d%s", common.Localhost, wallWSPort, common.PathSubmitViewingKey), submitViewingKeyBodyBytes) - } else { - makeRequestHTTP(fmt.Sprintf("http://%s:%d%s", common.Localhost, wallHTTPPort, common.PathSubmitViewingKey), submitViewingKeyBodyBytes) - } -} - -// Sends the body to the URL over HTTP, and returns the result. -func makeRequestHTTP(url string, body []byte) []byte { - generateViewingKeyBody := bytes.NewBuffer(body) - resp, err := http.Post(url, "application/json", generateViewingKeyBody) //nolint:noctx,gosec - if resp != nil && resp.Body != nil { - defer resp.Body.Close() - } - if err != nil { - panic(err) - } - viewingKey, err := io.ReadAll(resp.Body) - if err != nil { - panic(err) - } - return viewingKey -} - -// Sends the body to the URL over a websocket connection, and returns the result. -func makeRequestWS(url string, body []byte) ([]byte, *websocket.Conn) { - conn, dialResp, err := websocket.DefaultDialer.Dial(url, nil) - if dialResp != nil && dialResp.Body != nil { - defer dialResp.Body.Close() - } - if err != nil { - if conn != nil { - conn.Close() - } - panic(fmt.Errorf("received error response from wallet extension: %w", err)) - } - - return issueRequestWS(conn, body), conn -} - -// issues request on an existing ws connection -func issueRequestWS(conn *websocket.Conn, body []byte) []byte { - err := conn.WriteMessage(websocket.TextMessage, body) - if err != nil { - panic(err) - } - - _, reqResp, err := conn.ReadMessage() - if err != nil { - panic(err) - } - return reqResp -} - -// Reads messages from the connection for the provided duration, and returns the read messages. -//func readMessagesForDuration(t *testing.T, conn *websocket.Conn, duration time.Duration) [][]byte { -// // We set a timeout to kill the test, in case we never receive a log. -// timeout := time.AfterFunc(duration*3, func() { -// t.Fatalf("timed out waiting to receive a log via the subscription") -// }) -// defer timeout.Stop() -// -// var msgs [][]byte -// endTime := time.Now().Add(duration) -// for { -// _, msg, err := conn.ReadMessage() -// if err != nil { -// t.Fatalf("could not read message from websocket. Cause: %s", err) -// } -// msgs = append(msgs, msg) -// if time.Now().After(endTime) { -// return msgs -// } -// } -//} - -// Asserts that there are no duplicate logs in the provided list. -//func assertNoDupeLogs(t *testing.T, logsJSON [][]byte) { -// logCount := make(map[string]int) -// -// for _, logJSON := range logsJSON { -// // Check if the log is already in the logCount map. -// _, exist := logCount[string(logJSON)] -// if exist { -// logCount[string(logJSON)]++ // If it is, increase the count for that log by one. -// } else { -// logCount[string(logJSON)] = 1 // Otherwise, start a count for that log starting at one. -// } -// } -// -// for logJSON, count := range logCount { -// if count > 1 { -// t.Errorf("received duplicate log with body %s", logJSON) -// } -// } -//} - -// Checks that the response to a request is correctly formatted, and returns the result field. -func validateJSONResponse(t *testing.T, resp []byte) { - var respJSON map[string]interface{} - err := json.Unmarshal(resp, &respJSON) - if err != nil { - t.Fatalf("could not unmarshal response to JSON") - } - - id := respJSON[common.JSONKeyID] - jsonRPCVersion := respJSON[common.JSONKeyRPCVersion] - result := respJSON[common.JSONKeyResult] - - if id != jsonID { - t.Fatalf("response did not contain expected ID. Expected 1, got %s", id) - } - if jsonRPCVersion != jsonrpc.Version { - t.Fatalf("response did not contain expected RPC version. Expected 2.0, got %s", jsonRPCVersion) - } - if result == nil { - t.Fatalf("response did not contain `result` field") - } -} - -// Checks that the response to a subscription request is correctly formatted. -//func validateSubscriptionResponse(t *testing.T, resp []byte) { -// result := validateJSONResponse(t, resp) -// pattern := "0x.*" -// resultString, ok := result.(string) -// if !ok || !regexp.MustCompile(pattern).MatchString(resultString) { -// t.Fatalf("subscription response did not contain expected result. Expected pattern matching %s, got %s", pattern, resultString) -// } -//} From 5d5b8444afb6337a12ccd0200491affd458ef962 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Wed, 20 Mar 2024 14:55:28 +0000 Subject: [PATCH 22/65] lint --- tools/walletextension/rpcapi/blockchain_api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/walletextension/rpcapi/blockchain_api.go b/tools/walletextension/rpcapi/blockchain_api.go index 3c67716f3e..1b1f60832c 100644 --- a/tools/walletextension/rpcapi/blockchain_api.go +++ b/tools/walletextension/rpcapi/blockchain_api.go @@ -29,7 +29,7 @@ func (api *BlockChainAPI) ChainId() *hexutil.Big { //nolint:stylecheck } func (api *BlockChainAPI) BlockNumber() hexutil.Uint64 { - nr, err := UnauthenticatedTenRPCCall[hexutil.Uint64](nil, api.we, &CacheCfg{TTL: shortCacheTTL}, "eth_blockNumber") + nr, err := UnauthenticatedTenRPCCall[hexutil.Uint64](context.Background(), api.we, &CacheCfg{TTL: shortCacheTTL}, "eth_blockNumber") if err != nil { return hexutil.Uint64(0) } From aa0daa7680a21f3012c909d3ccae2267336b4e79 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Wed, 20 Mar 2024 17:36:17 +0000 Subject: [PATCH 23/65] fixes --- go/common/viewingkey/viewing_key.go | 4 +- go/common/viewingkey/viewing_key_messages.go | 35 ++++---- go/common/viewingkey/viewing_key_signature.go | 14 +-- go/enclave/vkhandler/vk_handler.go | 6 +- lib/gethfork/rpc/client.go | 2 +- lib/gethfork/rpc/client_opt.go | 2 +- lib/gethfork/rpc/handler.go | 4 +- lib/gethfork/rpc/inproc.go | 2 +- lib/gethfork/rpc/ipc.go | 2 +- lib/gethfork/rpc/server.go | 4 +- lib/gethfork/rpc/subscription.go | 2 +- lib/gethfork/rpc/websocket.go | 13 ++- tools/walletextension/common/common.go | 6 -- tools/walletextension/common/constants.go | 19 ++--- tools/walletextension/httpapi/routes.go | 29 ++++--- tools/walletextension/httpapi/utils.go | 39 ++------- tools/walletextension/lib/client_lib.go | 16 ++-- .../walletextension/rpcapi/blockchain_api.go | 1 + tools/walletextension/rpcapi/filter_api.go | 7 +- tools/walletextension/rpcapi/utils.go | 18 ++-- .../rpcapi/wallet_extension.go | 85 +++++++++---------- 21 files changed, 138 insertions(+), 172 deletions(-) diff --git a/go/common/viewingkey/viewing_key.go b/go/common/viewingkey/viewing_key.go index 464b29ca81..fd58d60a5f 100644 --- a/go/common/viewingkey/viewing_key.go +++ b/go/common/viewingkey/viewing_key.go @@ -92,8 +92,8 @@ func mmSignViewingKey(viewingPubKeyBytes []byte, signerKey *ecdsa.PrivateKey) ([ // Sign takes a users Private key and signs the public viewingKey hex func Sign(userPrivKey *ecdsa.PrivateKey, vkPubKey []byte) ([]byte, error) { - msgToSign := GenerateSignMessage(vkPubKey) - signature, err := crypto.Sign(accounts.TextHash([]byte(msgToSign)), userPrivKey) + msgToSign := GenerateLegacySignMessage(vkPubKey) + signature, err := crypto.Sign(accounts.TextHash(msgToSign), userPrivKey) if err != nil { return nil, fmt.Errorf("unable to sign messages - %w", err) } diff --git a/go/common/viewingkey/viewing_key_messages.go b/go/common/viewingkey/viewing_key_messages.go index 036a82a99b..c523f8b6a2 100644 --- a/go/common/viewingkey/viewing_key_messages.go +++ b/go/common/viewingkey/viewing_key_messages.go @@ -6,6 +6,8 @@ import ( "fmt" "math/big" + "github.com/status-im/keycard-go/hexutils" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" @@ -30,19 +32,14 @@ const ( EIP712EncryptionToken = "Encryption Token" EIP712DomainNameValue = "Ten" EIP712DomainVersionValue = "1.0" - UserIDHexLength = 40 + UserIDLength = 20 PersonalSignMessageFormat = "Token: %s on chain: %d version: %d" SignedMsgPrefix = "vk" // prefix for legacy signed messages (remove when legacy signature type is removed) PersonalSignVersion = 1 ) -// EIP712EncryptionTokens is a list of all possible options for Encryption token name -var EIP712EncryptionTokens = [...]string{ - EIP712EncryptionToken, -} - type MessageGenerator interface { - generateMessage(encryptionToken string, chainID int64, version int) ([]byte, error) + generateMessage(encryptionToken []byte, chainID int64, version int) ([]byte, error) } type ( @@ -56,13 +53,13 @@ var messageGenerators = map[SignatureType]MessageGenerator{ } // GenerateMessage generates a message for the given encryptionToken, chainID, version and signatureType -func (p PersonalMessageGenerator) generateMessage(encryptionToken string, chainID int64, version int) ([]byte, error) { - return []byte(fmt.Sprintf(PersonalSignMessageFormat, encryptionToken, chainID, version)), nil +func (p PersonalMessageGenerator) generateMessage(encryptionToken []byte, chainID int64, version int) ([]byte, error) { + return []byte(fmt.Sprintf(PersonalSignMessageFormat, hexutils.BytesToHex(encryptionToken), chainID, version)), nil } -func (e EIP712MessageGenerator) generateMessage(encryptionToken string, chainID int64, _ int) ([]byte, error) { - if len(encryptionToken) != UserIDHexLength { - return nil, fmt.Errorf("userID hex length must be %d, received %d", UserIDHexLength, len(encryptionToken)) +func (e EIP712MessageGenerator) generateMessage(encryptionToken []byte, chainID int64, _ int) ([]byte, error) { + if len(encryptionToken) != UserIDLength { + return nil, fmt.Errorf("userID must be %d bytes, received %d", UserIDLength, len(encryptionToken)) } EIP712TypedData := createTypedDataForEIP712Message(encryptionToken, chainID) @@ -75,7 +72,7 @@ func (e EIP712MessageGenerator) generateMessage(encryptionToken string, chainID } // GenerateMessage generates a message for the given encryptionToken, chainID, version and signatureType -func GenerateMessage(encryptionToken string, chainID int64, version int, signatureType SignatureType) ([]byte, error) { +func GenerateMessage(encryptionToken []byte, chainID int64, version int, signatureType SignatureType) ([]byte, error) { generator, exists := messageGenerators[signatureType] if !exists { return nil, fmt.Errorf("unsupported signature type") @@ -127,11 +124,11 @@ func GetMessageHash(message []byte, signatureType SignatureType) ([]byte, error) return hashFunction.getMessageHash(message), nil } -// GenerateSignMessage creates the message to be signed +// GenerateLegacySignMessage creates the message to be signed // vkPubKey is expected to be a []byte("0x....") to create the signing message // todo (@ziga) Remove this method once old WE endpoints are removed -func GenerateSignMessage(vkPubKey []byte) string { - return SignedMsgPrefix + hex.EncodeToString(vkPubKey) +func GenerateLegacySignMessage(vkPubKey []byte) []byte { + return []byte(SignedMsgPrefix + hex.EncodeToString(vkPubKey)) } // getBytesFromTypedData creates EIP-712 compliant hash from typedData. @@ -153,8 +150,8 @@ func getBytesFromTypedData(typedData apitypes.TypedData) ([]byte, error) { } // createTypedDataForEIP712Message creates typed data for EIP712 message -func createTypedDataForEIP712Message(encryptionToken string, chainID int64) apitypes.TypedData { - encryptionToken = "0x" + encryptionToken +func createTypedDataForEIP712Message(encryptionToken []byte, chainID int64) apitypes.TypedData { + hexToken := hexutils.BytesToHex(encryptionToken) domain := apitypes.TypedDataDomain{ Name: EIP712DomainNameValue, @@ -163,7 +160,7 @@ func createTypedDataForEIP712Message(encryptionToken string, chainID int64) apit } message := map[string]interface{}{ - EIP712EncryptionToken: encryptionToken, + EIP712EncryptionToken: hexToken, } types := apitypes.Types{ diff --git a/go/common/viewingkey/viewing_key_signature.go b/go/common/viewingkey/viewing_key_signature.go index 9c48f0b3cc..54c3f51248 100644 --- a/go/common/viewingkey/viewing_key_signature.go +++ b/go/common/viewingkey/viewing_key_signature.go @@ -14,7 +14,7 @@ import ( // SignatureChecker is an interface for checking // if signature is valid for provided encryptionToken and chainID and return singing address or nil if not valid type SignatureChecker interface { - CheckSignature(encryptionToken string, signature []byte, chainID int64) (*gethcommon.Address, error) + CheckSignature(encryptionToken []byte, signature []byte, chainID int64) (*gethcommon.Address, error) } type ( @@ -24,7 +24,7 @@ type ( ) // CheckSignature checks if signature is valid for provided encryptionToken and chainID and return address or nil if not valid -func (psc PersonalSignChecker) CheckSignature(encryptionToken string, signature []byte, chainID int64) (*gethcommon.Address, error) { +func (psc PersonalSignChecker) CheckSignature(encryptionToken []byte, signature []byte, chainID int64) (*gethcommon.Address, error) { if len(signature) != 65 { return nil, fmt.Errorf("invalid signaure length: %d", len(signature)) } @@ -53,7 +53,7 @@ func (psc PersonalSignChecker) CheckSignature(encryptionToken string, signature return nil, fmt.Errorf("signature verification failed") } -func (e EIP712Checker) CheckSignature(encryptionToken string, signature []byte, chainID int64) (*gethcommon.Address, error) { +func (e EIP712Checker) CheckSignature(encryptionToken []byte, signature []byte, chainID int64) (*gethcommon.Address, error) { if len(signature) != 65 { return nil, fmt.Errorf("invalid signaure length: %d", len(signature)) } @@ -87,11 +87,11 @@ func (e EIP712Checker) CheckSignature(encryptionToken string, signature []byte, // todo (@ziga) Remove this method once old WE endpoints are removed // encryptionToken is expected to be a public key and not encrypted token as with other signature types // (since this is only temporary fix and legacy format will be removed soon) -func (lsc LegacyChecker) CheckSignature(encryptionToken string, signature []byte, _ int64) (*gethcommon.Address, error) { +func (lsc LegacyChecker) CheckSignature(encryptionToken []byte, signature []byte, _ int64) (*gethcommon.Address, error) { publicKey := []byte(encryptionToken) - msgToSignLegacy := GenerateSignMessage(publicKey) + msgToSignLegacy := GenerateLegacySignMessage(publicKey) - recoveredAccountPublicKeyLegacy, err := crypto.SigToPub(accounts.TextHash([]byte(msgToSignLegacy)), signature) + recoveredAccountPublicKeyLegacy, err := crypto.SigToPub(accounts.TextHash(msgToSignLegacy), signature) if err != nil { return nil, fmt.Errorf("failed to recover account public key from legacy signature: %w", err) } @@ -107,7 +107,7 @@ var signatureCheckers = map[SignatureType]SignatureChecker{ } // CheckSignature checks if signature is valid for provided encryptionToken and chainID and return address or nil if not valid -func CheckSignature(encryptionToken string, signature []byte, chainID int64, signatureType SignatureType) (*gethcommon.Address, error) { +func CheckSignature(encryptionToken []byte, signature []byte, chainID int64, signatureType SignatureType) (*gethcommon.Address, error) { checker, exists := signatureCheckers[signatureType] if !exists { return nil, fmt.Errorf("unsupported signature type") diff --git a/go/enclave/vkhandler/vk_handler.go b/go/enclave/vkhandler/vk_handler.go index b9245f3ad6..54799bd777 100644 --- a/go/enclave/vkhandler/vk_handler.go +++ b/go/enclave/vkhandler/vk_handler.go @@ -21,7 +21,7 @@ type AuthenticatedViewingKey struct { rpcVK *viewingkey.RPCSignedViewingKey AccountAddress *gethcommon.Address ecdsaKey *ecies.PublicKey - UserID string + UserID []byte } func VerifyViewingKey(rpcVK *viewingkey.RPCSignedViewingKey, chainID int64) (*AuthenticatedViewingKey, error) { @@ -48,13 +48,13 @@ func VerifyViewingKey(rpcVK *viewingkey.RPCSignedViewingKey, chainID int64) (*Au // checkViewingKeyAndRecoverAddress checks the signature and recovers the address from the viewing key func checkViewingKeyAndRecoverAddress(vk *AuthenticatedViewingKey, chainID int64) (*gethcommon.Address, error) { // get userID from viewingKey public key - userID := viewingkey.CalculateUserIDHex(vk.rpcVK.PublicKey) + userID := viewingkey.CalculateUserID(vk.rpcVK.PublicKey) vk.UserID = userID // todo - remove this when the legacy format is no longer supported // this is a temporary fix to support the legacy format which will be removed soon if vk.rpcVK.SignatureType == viewingkey.Legacy { - userID = string(vk.rpcVK.PublicKey) // for legacy format, the userID is the public key + userID = vk.rpcVK.PublicKey // for legacy format, the userID is the public key } // check the signature and recover the address assuming the message was signed with EIP712 diff --git a/lib/gethfork/rpc/client.go b/lib/gethfork/rpc/client.go index de7fd5396a..805f375441 100644 --- a/lib/gethfork/rpc/client.go +++ b/lib/gethfork/rpc/client.go @@ -76,7 +76,7 @@ type BatchElem struct { // Client represents a connection to an RPC server. type Client struct { - UserID string + UserID []byte idgen func() ID // for subscriptions isHTTP bool // connection type: http, ws or ipc services *serviceRegistry diff --git a/lib/gethfork/rpc/client_opt.go b/lib/gethfork/rpc/client_opt.go index 0eae2e6134..32435e8efb 100644 --- a/lib/gethfork/rpc/client_opt.go +++ b/lib/gethfork/rpc/client_opt.go @@ -28,7 +28,7 @@ type ClientOption interface { } type clientConfig struct { - UserID string + UserID []byte // HTTP settings httpClient *http.Client httpHeaders http.Header diff --git a/lib/gethfork/rpc/handler.go b/lib/gethfork/rpc/handler.go index 343e09a558..df8b56ee10 100644 --- a/lib/gethfork/rpc/handler.go +++ b/lib/gethfork/rpc/handler.go @@ -66,7 +66,7 @@ type handler struct { subLock sync.Mutex serverSubs map[ID]*Subscription - UserID string + UserID []byte } type callProc struct { @@ -74,7 +74,7 @@ type callProc struct { notifiers []*Notifier } -func newHandler(connCtx context.Context, conn jsonWriter, idgen func() ID, reg *serviceRegistry, batchRequestLimit, batchResponseMaxSize int, userID string) *handler { +func newHandler(connCtx context.Context, conn jsonWriter, idgen func() ID, reg *serviceRegistry, batchRequestLimit, batchResponseMaxSize int, userID []byte) *handler { rootCtx, cancelRoot := context.WithCancel(connCtx) h := &handler{ reg: reg, diff --git a/lib/gethfork/rpc/inproc.go b/lib/gethfork/rpc/inproc.go index 2a5d400b19..835825334b 100644 --- a/lib/gethfork/rpc/inproc.go +++ b/lib/gethfork/rpc/inproc.go @@ -27,7 +27,7 @@ func DialInProc(handler *Server) *Client { cfg := new(clientConfig) c, _ := newClient(initctx, cfg, func(context.Context) (ServerCodec, error) { p1, p2 := net.Pipe() - go handler.ServeCodec(NewCodec(p1), 0, "") + go handler.ServeCodec(NewCodec(p1), 0, nil) return NewCodec(p2), nil }) return c diff --git a/lib/gethfork/rpc/ipc.go b/lib/gethfork/rpc/ipc.go index 9db95dc467..5f45a4cb07 100644 --- a/lib/gethfork/rpc/ipc.go +++ b/lib/gethfork/rpc/ipc.go @@ -35,7 +35,7 @@ func (s *Server) ServeListener(l net.Listener) error { return err } log.Trace("Accepted RPC connection", "conn", conn.RemoteAddr()) - go s.ServeCodec(NewCodec(conn), 0, "") + go s.ServeCodec(NewCodec(conn), 0, nil) } } diff --git a/lib/gethfork/rpc/server.go b/lib/gethfork/rpc/server.go index e0b96ad53f..59af5e072e 100644 --- a/lib/gethfork/rpc/server.go +++ b/lib/gethfork/rpc/server.go @@ -103,7 +103,7 @@ func (s *Server) RegisterName(name string, receiver interface{}) error { // server is stopped. In either case the codec is closed. // // Note that codec options are no longer supported. -func (s *Server) ServeCodec(codec ServerCodec, _ CodecOption, userID string) { +func (s *Server) ServeCodec(codec ServerCodec, _ CodecOption, userID []byte) { defer codec.close() if !s.trackCodec(codec) { @@ -149,7 +149,7 @@ func (s *Server) serveSingleRequest(ctx context.Context, codec ServerCodec) { return } - h := newHandler(ctx, codec, s.idgen, &s.services, s.batchItemLimit, s.batchResponseLimit, "") + h := newHandler(ctx, codec, s.idgen, &s.services, s.batchItemLimit, s.batchResponseLimit, nil) h.allowSubscribe = false defer h.close(io.EOF, nil) diff --git a/lib/gethfork/rpc/subscription.go b/lib/gethfork/rpc/subscription.go index c7fbc6c8c2..ca432190fb 100644 --- a/lib/gethfork/rpc/subscription.go +++ b/lib/gethfork/rpc/subscription.go @@ -101,7 +101,7 @@ func NotifierFromContext(ctx context.Context) (*Notifier, bool) { // Server callbacks use the notifier to send notifications. type Notifier struct { h *handler - UserID string // added by TEN + UserID []byte // added by TEN namespace string mu sync.Mutex diff --git a/lib/gethfork/rpc/websocket.go b/lib/gethfork/rpc/websocket.go index 605931c47d..7bd09d589a 100644 --- a/lib/gethfork/rpc/websocket.go +++ b/lib/gethfork/rpc/websocket.go @@ -27,6 +27,9 @@ import ( "sync" "time" + "github.com/status-im/keycard-go/hexutils" + "github.com/ten-protocol/go-ten/go/common/viewingkey" + mapset "github.com/deckarep/golang-set/v2" "github.com/ethereum/go-ethereum/log" "github.com/gorilla/websocket" @@ -65,12 +68,16 @@ func (s *Server) WebsocketHandler(allowedOrigins []string) http.Handler { }) } -func extractUserID(ctx context.Context) string { +func extractUserID(ctx context.Context) []byte { token, ok := ctx.Value(GWTokenKey{}).(string) if !ok { - return "" + return nil + } + userID := hexutils.HexToBytes(token) + if len(userID) != viewingkey.UserIDLength { + return nil } - return token + return userID } // wsHandshakeValidator returns a handler that verifies the origin during the diff --git a/tools/walletextension/common/common.go b/tools/walletextension/common/common.go index 8a13de5a3d..fd8a2025fe 100644 --- a/tools/walletextension/common/common.go +++ b/tools/walletextension/common/common.go @@ -1,7 +1,6 @@ package common import ( - "encoding/hex" "encoding/json" "fmt" "os" @@ -34,11 +33,6 @@ func BytesToPrivateKey(keyBytes []byte) (*ecies.PrivateKey, error) { return eciesPrivateKey, nil } -// GetUserIDbyte converts userID from string to correct byte format -func GetUserIDbyte(userID string) ([]byte, error) { - return hex.DecodeString(userID) -} - func CreateEncClient( hostRPCBindAddr string, addressBytes []byte, diff --git a/tools/walletextension/common/constants.go b/tools/walletextension/common/constants.go index fdd74a8ad6..ec13209999 100644 --- a/tools/walletextension/common/constants.go +++ b/tools/walletextension/common/constants.go @@ -1,20 +1,13 @@ package common -import ( - "time" -) - const ( Localhost = "127.0.0.1" - JSONKeyAddress = "address" - JSONKeyID = "id" - JSONKeyMethod = "method" - JSONKeyParams = "params" - JSONKeyResult = "result" - JSONKeyRPCVersion = "jsonrpc" - JSONKeySignature = "signature" - JSONKeyType = "type" + JSONKeyAddress = "address" + JSONKeySignature = "signature" + JSONKeyType = "type" + JSONKeyEncryptionToken = "encryptionToken" + JSONKeyFormats = "formats" ) const ( @@ -43,5 +36,3 @@ const ( DeduplicationBufferSize = 20 DefaultGatewayAuthMessageType = "EIP712" ) - -var ReaderHeadTimeout = 10 * time.Second diff --git a/tools/walletextension/httpapi/routes.go b/tools/walletextension/httpapi/routes.go index 3172c992bc..6644954d82 100644 --- a/tools/walletextension/httpapi/routes.go +++ b/tools/walletextension/httpapi/routes.go @@ -6,6 +6,8 @@ import ( "fmt" "net/http" + "github.com/status-im/keycard-go/hexutils" + "github.com/ten-protocol/go-ten/go/common/viewingkey" "github.com/ten-protocol/go-ten/lib/gethfork/node" "github.com/ten-protocol/go-ten/tools/walletextension/rpcapi" @@ -227,17 +229,17 @@ func authenticateRequestHandler(walletExt *rpcapi.Services, conn UserConn) { } // read userID from query params - hexUserID, err := getUserID(conn, 2) + userID, err := getUserID(conn) if err != nil { handleError(conn, walletExt.Logger(), fmt.Errorf("malformed query: 'u' required - representing encryption token - %w", err)) return } // check signature and add address and signature for that user - err = walletExt.AddAddressToUser(hexUserID, address, signature, messageType) + err = walletExt.AddAddressToUser(userID, address, signature, messageType) if err != nil { handleError(conn, walletExt.Logger(), fmt.Errorf("internal error")) - walletExt.Logger().Error(fmt.Sprintf("error adding address: %s to user: %s with signature: %s", address, hexUserID, signature)) + walletExt.Logger().Error(fmt.Sprintf("error adding address: %s to user: %s with signature: %s", address, userID, signature)) return } err = conn.WriteResponse([]byte(common.SuccessMsg)) @@ -257,7 +259,7 @@ func queryRequestHandler(walletExt *rpcapi.Services, conn UserConn) { return } - hexUserID, err := getUserID(conn, 2) + userID, err := getUserID(conn) if err != nil { handleError(conn, walletExt.Logger(), fmt.Errorf("user ('u') not found in query parameters")) walletExt.Logger().Info("user not found in the query params", log.ErrKey, err) @@ -276,10 +278,10 @@ func queryRequestHandler(walletExt *rpcapi.Services, conn UserConn) { } // check if this account is registered with given user - found, err := walletExt.UserHasAccount(hexUserID, address) + found, err := walletExt.UserHasAccount(userID, address) if err != nil { handleError(conn, walletExt.Logger(), fmt.Errorf("internal error")) - walletExt.Logger().Error("error during checking if account exists for user", "hexUserID", hexUserID, log.ErrKey, err) + walletExt.Logger().Error("error during checking if account exists for user", "userID", userID, log.ErrKey, err) } // create and write the response @@ -309,7 +311,7 @@ func revokeRequestHandler(walletExt *rpcapi.Services, conn UserConn) { return } - hexUserID, err := getUserID(conn, 2) + userID, err := getUserID(conn) if err != nil { handleError(conn, walletExt.Logger(), fmt.Errorf("user ('u') not found in query parameters")) walletExt.Logger().Info("user not found in the query params", log.ErrKey, err) @@ -317,10 +319,10 @@ func revokeRequestHandler(walletExt *rpcapi.Services, conn UserConn) { } // delete user and accounts associated with it from the database - err = walletExt.DeleteUser(hexUserID) + err = walletExt.DeleteUser(userID) if err != nil { handleError(conn, walletExt.Logger(), fmt.Errorf("internal error")) - walletExt.Logger().Error("unable to delete user", "hexUserID", hexUserID, log.ErrKey, err) + walletExt.Logger().Error("unable to delete user", "userID", userID, log.ErrKey, err) return } @@ -388,7 +390,7 @@ func versionRequestHandler(walletExt *rpcapi.Services, userConn UserConn) { } // getMessageRequestHandler handles request to /get-message endpoint. -func getMessageRequestHandler(walletExt *walletextension.WalletExtension, conn userconn.UserConn) { +func getMessageRequestHandler(walletExt *rpcapi.Services, conn UserConn) { // read the request body, err := conn.ReadRequest() if err != nil { @@ -430,7 +432,12 @@ func getMessageRequestHandler(walletExt *walletextension.WalletExtension, conn u } } - message, err := walletExt.GenerateUserMessageToSign(encryptionToken.(string), formatsSlice) + userID := hexutils.HexToBytes(encryptionToken.(string)) + if len(userID) != viewingkey.UserIDLength { + return + } + + message, err := walletExt.GenerateUserMessageToSign(userID, formatsSlice) if err != nil { handleError(conn, walletExt.Logger(), fmt.Errorf("internal error")) walletExt.Logger().Error("error getting message", log.ErrKey, err) diff --git a/tools/walletextension/httpapi/utils.go b/tools/walletextension/httpapi/utils.go index 821abaa89d..cc7c50fe1a 100644 --- a/tools/walletextension/httpapi/utils.go +++ b/tools/walletextension/httpapi/utils.go @@ -2,9 +2,9 @@ package httpapi import ( "fmt" - "strings" gethlog "github.com/ethereum/go-ethereum/log" + "github.com/status-im/keycard-go/hexutils" "github.com/ten-protocol/go-ten/go/common/log" "github.com/ten-protocol/go-ten/tools/walletextension/common" ) @@ -21,46 +21,17 @@ func getQueryParameter(params map[string]string, selectedParameter string) (stri // getUserID returns userID from query params / url of the URL // it always first tries to get userID from a query parameter `u` or `token` (`u` parameter will become deprecated) // if it fails to get userID from a query parameter it tries to get it from the URL and it needs position as the second parameter -func getUserID(conn UserConn, userIDPosition int) (string, error) { +func getUserID(conn UserConn) ([]byte, error) { // try getting userID (`token`) from query parameters and return it if successful userID, err := getQueryParameter(conn.ReadRequestParams(), common.EncryptedTokenQueryParameter) if err == nil { if len(userID) != common.MessageUserIDLen { - return "", fmt.Errorf(fmt.Sprintf("wrong length of userID from URL. Got: %d, Expected: %d", len(userID), common.MessageUserIDLen)) + return nil, fmt.Errorf(fmt.Sprintf("wrong length of userID from URL. Got: %d, Expected: %d", len(userID), common.MessageUserIDLen)) } - return userID, err + return hexutils.HexToBytes(userID), err } - // try getting userID(`u`) from query parameters and return it if successful - userID, err = getQueryParameter(conn.ReadRequestParams(), common.UserQueryParameter) - if err == nil { - if len(userID) != common.MessageUserIDLen { - return "", fmt.Errorf(fmt.Sprintf("wrong length of userID from URL. Got: %d, Expected: %d", len(userID), common.MessageUserIDLen)) - } - return userID, err - } - - // Alternatively, try to get it from URL path - // This is a temporary hack to work around hardhat bug which causes hardhat to ignore query parameters. - // It is unsafe because https encrypts query parameters, - // but not URL itself and will be removed once hardhat bug is resolved. - path := conn.GetHTTPRequest().URL.Path - path = strings.Trim(path, "/") - parts := strings.Split(path, "/") - - // our URLs, which require userID, have following pattern: // - // userID can be only on second or third position - if len(parts) != userIDPosition+1 { - return "", fmt.Errorf("URL structure of the request looks wrong") - } - userID = parts[userIDPosition] - - // Check if userID has the correct length - if len(userID) != common.MessageUserIDLen { - return "", fmt.Errorf(fmt.Sprintf("wrong length of userID from URL. Got: %d, Expected: %d", len(userID), common.MessageUserIDLen)) - } - - return userID, nil + return nil, fmt.Errorf("missing token field") } func handleError(conn UserConn, logger gethlog.Logger, err error) { diff --git a/tools/walletextension/lib/client_lib.go b/tools/walletextension/lib/client_lib.go index 42be5c4eb0..99f1086316 100644 --- a/tools/walletextension/lib/client_lib.go +++ b/tools/walletextension/lib/client_lib.go @@ -9,6 +9,8 @@ import ( "net/http" "strings" + "github.com/status-im/keycard-go/hexutils" + "github.com/ten-protocol/go-ten/integration" gethcommon "github.com/ethereum/go-ethereum/common" @@ -31,7 +33,7 @@ func NewTenGatewayLibrary(httpURL, wsURL string) *TGLib { } func (o *TGLib) UserID() string { - return string(o.userID) + return hexutils.BytesToHex(o.userID) } func (o *TGLib) Join() error { @@ -46,7 +48,7 @@ func (o *TGLib) Join() error { func (o *TGLib) RegisterAccount(pk *ecdsa.PrivateKey, addr gethcommon.Address) error { // create the registration message - message, err := viewingkey.GenerateMessage(string(o.userID), integration.TenChainID, 1, viewingkey.EIP712Signature) + message, err := viewingkey.GenerateMessage(o.userID, integration.TenChainID, 1, viewingkey.EIP712Signature) if err != nil { return err } @@ -68,7 +70,7 @@ func (o *TGLib) RegisterAccount(pk *ecdsa.PrivateKey, addr gethcommon.Address) e req, err := http.NewRequestWithContext( context.Background(), http.MethodPost, - o.httpURL+"/v1/authenticate/?token="+string(o.userID), + o.httpURL+"/v1/authenticate/?token="+hexutils.BytesToHex(o.userID), strings.NewReader(payload), ) if err != nil { @@ -96,7 +98,7 @@ func (o *TGLib) RegisterAccount(pk *ecdsa.PrivateKey, addr gethcommon.Address) e func (o *TGLib) RegisterAccountPersonalSign(pk *ecdsa.PrivateKey, addr gethcommon.Address) error { // create the registration message - message, err := viewingkey.GenerateMessage(string(o.userID), integration.TenChainID, viewingkey.PersonalSignVersion, viewingkey.PersonalSign) + message, err := viewingkey.GenerateMessage(o.userID, integration.TenChainID, viewingkey.PersonalSignVersion, viewingkey.PersonalSign) if err != nil { return err } @@ -118,7 +120,7 @@ func (o *TGLib) RegisterAccountPersonalSign(pk *ecdsa.PrivateKey, addr gethcommo req, err := http.NewRequestWithContext( context.Background(), http.MethodPost, - o.httpURL+"/v1/authenticate/?token="+string(o.userID), + o.httpURL+"/v1/authenticate/?token="+hexutils.BytesToHex(o.userID), strings.NewReader(payload), ) if err != nil { @@ -145,9 +147,9 @@ func (o *TGLib) RegisterAccountPersonalSign(pk *ecdsa.PrivateKey, addr gethcommo } func (o *TGLib) HTTP() string { - return fmt.Sprintf("%s/v1/?token=%s", o.httpURL, o.userID) + return fmt.Sprintf("%s/v1/?token=%s", o.httpURL, hexutils.BytesToHex(o.userID)) } func (o *TGLib) WS() string { - return fmt.Sprintf("%s/v1/?token=%s", o.wsURL, o.userID) + return fmt.Sprintf("%s/v1/?token=%s", o.wsURL, hexutils.BytesToHex(o.userID)) } diff --git a/tools/walletextension/rpcapi/blockchain_api.go b/tools/walletextension/rpcapi/blockchain_api.go index 1b1f60832c..17034c7640 100644 --- a/tools/walletextension/rpcapi/blockchain_api.go +++ b/tools/walletextension/rpcapi/blockchain_api.go @@ -244,6 +244,7 @@ func (api *BlockChainAPI) EstimateGas(ctx context.Context, args gethapi.Transact }, // is this a security risk? useDefaultUser: true, + tryAll: true, }, "eth_estimateGas", args, blockNrOrHash, overrides) if resp == nil { return 0, err diff --git a/tools/walletextension/rpcapi/filter_api.go b/tools/walletextension/rpcapi/filter_api.go index d4feeb0140..25d9d0c05e 100644 --- a/tools/walletextension/rpcapi/filter_api.go +++ b/tools/walletextension/rpcapi/filter_api.go @@ -114,12 +114,7 @@ func getUserAndNotifier(ctx context.Context, api *FilterAPI) (*rpc.Notifier, *GW return nil, nil, fmt.Errorf("illegal access") } - uid, err := wecommon.GetUserIDbyte(subNotifier.UserID) - if err != nil { - return nil, nil, fmt.Errorf("invald token: %s, %w", subNotifier.UserID, err) - } - - user, err := getUser(uid, api.we.Storage) + user, err := getUser(subNotifier.UserID, api.we.Storage) if err != nil { return nil, nil, fmt.Errorf("illegal access: %s, %w", subNotifier.UserID, err) } diff --git a/tools/walletextension/rpcapi/utils.go b/tools/walletextension/rpcapi/utils.go index 33533a455e..ea40371d8b 100644 --- a/tools/walletextension/rpcapi/utils.go +++ b/tools/walletextension/rpcapi/utils.go @@ -7,6 +7,8 @@ import ( "fmt" "time" + "github.com/ten-protocol/go-ten/go/common/viewingkey" + "github.com/ten-protocol/go-ten/lib/gethfork/rpc" "github.com/status-im/keycard-go/hexutils" @@ -14,7 +16,6 @@ import ( "github.com/ten-protocol/go-ten/tools/walletextension/cache" "github.com/ethereum/go-ethereum/common" - wecommon "github.com/ten-protocol/go-ten/tools/walletextension/common" ) const ( @@ -140,6 +141,10 @@ func getCandidateAccounts(user *GWUser, w *Services, cfg *ExecCfg) ([]*GWAccount acc := user.accounts[*addr] if acc != nil { candidateAccts = append(candidateAccts, acc) + } else if cfg.tryAll { + for _, acc := range user.accounts { + candidateAccts = append(candidateAccts, acc) + } } case cfg.tryAll, cfg.tryUntilAuthorised: @@ -165,12 +170,15 @@ func getCandidateAccounts(user *GWUser, w *Services, cfg *ExecCfg) ([]*GWAccount } func extractUserID(ctx context.Context, w *Services) ([]byte, error) { - userID, ok := ctx.Value(rpc.GWTokenKey{}).(string) + token, ok := ctx.Value(rpc.GWTokenKey{}).(string) if !ok { - return w.DefaultUser, nil - // return nil, fmt.Errorf("invalid encryption token %s", userID) + return nil, fmt.Errorf("invalid userid") + } + userID := hexutils.HexToBytes(token) + if len(userID) != viewingkey.UserIDLength { + return nil, fmt.Errorf("invalid userid") } - return wecommon.GetUserIDbyte(userID) + return userID, nil } // generateCacheKey generates a cache key for the given method, encryptionToken and parameters diff --git a/tools/walletextension/rpcapi/wallet_extension.go b/tools/walletextension/rpcapi/wallet_extension.go index 34d771deb8..b569755b6a 100644 --- a/tools/walletextension/rpcapi/wallet_extension.go +++ b/tools/walletextension/rpcapi/wallet_extension.go @@ -3,11 +3,12 @@ package rpcapi import ( "bytes" "encoding/hex" - "errors" "fmt" "math/big" "time" + "github.com/status-im/keycard-go/hexutils" + "github.com/ten-protocol/go-ten/go/wallet" "github.com/ten-protocol/go-ten/tools/walletextension/cache" @@ -166,14 +167,14 @@ func (w *Services) SubmitViewingKey(address gethcommon.Address, signature []byte } // GenerateAndStoreNewUser generates new key-pair and userID, stores it in the database and returns hex encoded userID and error -func (w *Services) GenerateAndStoreNewUser() (string, error) { +func (w *Services) GenerateAndStoreNewUser() ([]byte, error) { requestStartTime := time.Now() // generate new key-pair viewingKeyPrivate, err := crypto.GenerateKey() viewingPrivateKeyEcies := ecies.ImportECDSA(viewingKeyPrivate) if err != nil { w.Logger().Error(fmt.Sprintf("could not generate new keypair: %s", err)) - return "", err + return nil, err } // create UserID and store it in the database with the private key @@ -181,54 +182,41 @@ func (w *Services) GenerateAndStoreNewUser() (string, error) { err = w.Storage.AddUser(userID, crypto.FromECDSA(viewingPrivateKeyEcies.ExportECDSA())) if err != nil { w.Logger().Error(fmt.Sprintf("failed to save user to the database: %s", err)) - return "", err + return nil, err } - hexUserID := hex.EncodeToString(userID) - requestEndTime := time.Now() duration := requestEndTime.Sub(requestStartTime) - audit(w, "Storing new userID: %s, duration: %d ", hexUserID, duration.Milliseconds()) - return hexUserID, nil + audit(w, "Storing new userID: %s, duration: %d ", hexutils.BytesToHex(userID), duration.Milliseconds()) + return userID, nil } // AddAddressToUser checks if a message is in correct format and if signature is valid. If all checks pass we save address and signature against userID -func (w *Services) AddAddressToUser(hexUserID string, address string, signature []byte, signatureType viewingkey.SignatureType) error { +func (w *Services) AddAddressToUser(userID []byte, address string, signature []byte, signatureType viewingkey.SignatureType) error { requestStartTime := time.Now() addressFromMessage := gethcommon.HexToAddress(address) // check if a message was signed by the correct address and if the signature is valid - _, err := viewingkey.CheckSignature(hexUserID, signature, int64(w.Config.TenChainID), signatureType) + _, err := viewingkey.CheckSignature(userID, signature, int64(w.Config.TenChainID), signatureType) if err != nil { return fmt.Errorf("signature is not valid: %w", err) } // register the account for that viewing key - userIDBytes, err := common.GetUserIDbyte(hexUserID) - if err != nil { - w.Logger().Error(fmt.Errorf("error decoding string (%s), %w", hexUserID[2:], err).Error()) - return errors.New("error decoding userID. It should be in hex format") - } - err = w.Storage.AddAccount(userIDBytes, addressFromMessage.Bytes(), signature, signatureType) + err = w.Storage.AddAccount(userID, addressFromMessage.Bytes(), signature, signatureType) if err != nil { - w.Logger().Error(fmt.Errorf("error while storing account (%s) for user (%s): %w", addressFromMessage.Hex(), hexUserID, err).Error()) + w.Logger().Error(fmt.Errorf("error while storing account (%s) for user (%s): %w", addressFromMessage.Hex(), userID, err).Error()) return err } requestEndTime := time.Now() duration := requestEndTime.Sub(requestStartTime) - audit(w, "Storing new address for user: %s, address: %s, duration: %d ", hexUserID, address, duration.Milliseconds()) + audit(w, "Storing new address for user: %s, address: %s, duration: %d ", hexutils.BytesToHex(userID), address, duration.Milliseconds()) return nil } // UserHasAccount checks if provided account exist in the database for given userID -func (w *Services) UserHasAccount(hexUserID string, address string) (bool, error) { - audit(w, "Checking if user has account: %s, address: %s", hexUserID, address) - userIDBytes, err := common.GetUserIDbyte(hexUserID) - if err != nil { - w.Logger().Error(fmt.Errorf("error decoding string (%s), %w", hexUserID[2:], err).Error()) - return false, err - } - +func (w *Services) UserHasAccount(userID []byte, address string) (bool, error) { + audit(w, "Checking if user has account: %s, address: %s", hexutils.BytesToHex(userID), address) addressBytes, err := hex.DecodeString(address[2:]) // remove 0x prefix from address if err != nil { w.Logger().Error(fmt.Errorf("error decoding string (%s), %w", address[2:], err).Error()) @@ -237,9 +225,9 @@ func (w *Services) UserHasAccount(hexUserID string, address string) (bool, error // todo - this can be optimised and done in the database if we will have users with large number of accounts // get all the accounts for the selected user - accounts, err := w.Storage.GetAccounts(userIDBytes) + accounts, err := w.Storage.GetAccounts(userID) if err != nil { - w.Logger().Error(fmt.Errorf("error getting accounts for user (%s), %w", hexUserID, err).Error()) + w.Logger().Error(fmt.Errorf("error getting accounts for user (%s), %w", userID, err).Error()) return false, err } @@ -254,35 +242,24 @@ func (w *Services) UserHasAccount(hexUserID string, address string) (bool, error } // DeleteUser deletes user and accounts associated with user from the database for given userID -func (w *Services) DeleteUser(hexUserID string) error { - audit(w, "Deleting user: %s", hexUserID) - userIDBytes, err := common.GetUserIDbyte(hexUserID) - if err != nil { - w.Logger().Error(fmt.Errorf("error decoding string (%s), %w", hexUserID, err).Error()) - return err - } +func (w *Services) DeleteUser(userID []byte) error { + audit(w, "Deleting user: %s", hexutils.BytesToHex(userID)) - err = w.Storage.DeleteUser(userIDBytes) + err := w.Storage.DeleteUser(userID) if err != nil { - w.Logger().Error(fmt.Errorf("error deleting user (%s), %w", hexUserID, err).Error()) + w.Logger().Error(fmt.Errorf("error deleting user (%s), %w", userID, err).Error()) return err } return nil } -func (w *Services) UserExists(hexUserID string) bool { - audit(w, "Checking if user exists: %s", hexUserID) - userIDBytes, err := common.GetUserIDbyte(hexUserID) - if err != nil { - w.Logger().Error(fmt.Errorf("error decoding string (%s), %w", hexUserID, err).Error()) - return false - } - +func (w *Services) UserExists(userID []byte) bool { + audit(w, "Checking if user exists: %s", userID) // Check if user exists and don't log error if user doesn't exist, because we expect this to happen in case of // user revoking encryption token or using different testnet. // todo add a counter here in the future - key, err := w.Storage.GetUserPrivateKey(userIDBytes) + key, err := w.Storage.GetUserPrivateKey(userID) if err != nil { return false } @@ -301,3 +278,19 @@ func (w *Services) GetTenNodeHealthStatus() (bool, error) { func (w *Services) UnauthenticatedClient() (rpc.Client, error) { return rpc.NewNetworkClient(w.HostAddrHTTP) } + +func (w *Services) GenerateUserMessageToSign(encryptionToken []byte, formatsSlice []string) (string, error) { + // Check if the formats are valid + for _, format := range formatsSlice { + if _, exists := viewingkey.SignatureTypeMap[format]; !exists { + return "", fmt.Errorf("invalid format: %s", format) + } + } + + messageFormat := viewingkey.GetBestFormat(formatsSlice) + message, err := viewingkey.GenerateMessage(encryptionToken, int64(w.Config.TenChainID), viewingkey.PersonalSignVersion, messageFormat) + if err != nil { + return "", fmt.Errorf("error generating message: %w", err) + } + return string(message), nil +} From a9888756e95b38f3ef9aa9c15a2df4a6aaf64afe Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Wed, 20 Mar 2024 17:57:14 +0000 Subject: [PATCH 24/65] fix --- tools/walletextension/httpapi/routes.go | 4 ++-- tools/walletextension/lib/client_lib.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/walletextension/httpapi/routes.go b/tools/walletextension/httpapi/routes.go index 6644954d82..a1ba5900eb 100644 --- a/tools/walletextension/httpapi/routes.go +++ b/tools/walletextension/httpapi/routes.go @@ -170,14 +170,14 @@ func joinRequestHandler(walletExt *rpcapi.Services, conn UserConn) { } // generate new key-pair and store it in the database - hexUserID, err := walletExt.GenerateAndStoreNewUser() + userID, err := walletExt.GenerateAndStoreNewUser() if err != nil { handleError(conn, walletExt.Logger(), fmt.Errorf("internal Error")) walletExt.Logger().Error("error creating new user", log.ErrKey, err) } // write hex encoded userID in the response - err = conn.WriteResponse([]byte(hexUserID)) + err = conn.WriteResponse([]byte(hexutils.BytesToHex(userID))) if err != nil { walletExt.Logger().Error("error writing success response", log.ErrKey, err) } diff --git a/tools/walletextension/lib/client_lib.go b/tools/walletextension/lib/client_lib.go index 99f1086316..c4a0e3f11e 100644 --- a/tools/walletextension/lib/client_lib.go +++ b/tools/walletextension/lib/client_lib.go @@ -42,7 +42,7 @@ func (o *TGLib) Join() error { if err != nil || statusCode != 200 { return fmt.Errorf(fmt.Sprintf("Failed to get userID. Status code: %d, err: %s", statusCode, err)) } - o.userID = userID + o.userID = hexutils.HexToBytes(string(userID)) return nil } From a3462e5de932e8c06b2c1a3de0b5610d90874e29 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Wed, 20 Mar 2024 18:26:46 +0000 Subject: [PATCH 25/65] fix --- go/enclave/rpc/GetBalance.go | 4 +++- tools/walletextension/rpcapi/utils.go | 23 +++++++++-------------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/go/enclave/rpc/GetBalance.go b/go/enclave/rpc/GetBalance.go index de9b6a4f42..8bdc26fc14 100644 --- a/go/enclave/rpc/GetBalance.go +++ b/go/enclave/rpc/GetBalance.go @@ -3,6 +3,8 @@ package rpc import ( "fmt" + "github.com/status-im/keycard-go/hexutils" + "github.com/ethereum/go-ethereum/common" "github.com/ten-protocol/go-ten/lib/gethfork/rpc" @@ -48,7 +50,7 @@ func GetBalanceExecute(builder *CallBuilder[BalanceReq, hexutil.Big], rpc *Encry // authorise the call if acctOwner.Hex() != builder.VK.AccountAddress.Hex() { - rpc.logger.Debug("Unauthorised call", "address", acctOwner, "vk", builder.VK.AccountAddress, "userId", builder.VK.UserID) + rpc.logger.Debug("Unauthorised call", "address", acctOwner, "vk", builder.VK.AccountAddress, "userId", hexutils.BytesToHex(builder.VK.UserID)) builder.Status = NotAuthorised return nil } diff --git a/tools/walletextension/rpcapi/utils.go b/tools/walletextension/rpcapi/utils.go index ea40371d8b..f35a358611 100644 --- a/tools/walletextension/rpcapi/utils.go +++ b/tools/walletextension/rpcapi/utils.go @@ -123,7 +123,7 @@ func ExecAuthRPC[R any](ctx context.Context, w *Services, cfg *ExecCfg, method s return res, err } -func getCandidateAccounts(user *GWUser, w *Services, cfg *ExecCfg) ([]*GWAccount, error) { +func getCandidateAccounts(user *GWUser, _ *Services, cfg *ExecCfg) ([]*GWAccount, error) { candidateAccts := make([]*GWAccount, 0) // for users with multiple accounts determine a candidate account switch { @@ -131,29 +131,24 @@ func getCandidateAccounts(user *GWUser, w *Services, cfg *ExecCfg) ([]*GWAccount acc := user.accounts[*cfg.account] if acc != nil { candidateAccts = append(candidateAccts, acc) + return candidateAccts, nil } case cfg.computeFromCallback != nil: - addr := cfg.computeFromCallback(user) - if addr == nil { - return nil, fmt.Errorf("invalid request") - } - acc := user.accounts[*addr] - if acc != nil { - candidateAccts = append(candidateAccts, acc) - } else if cfg.tryAll { - for _, acc := range user.accounts { + suggestedAddress := cfg.computeFromCallback(user) + if suggestedAddress != nil { + acc := user.accounts[*suggestedAddress] + if acc != nil { candidateAccts = append(candidateAccts, acc) } + return candidateAccts, nil } + } - case cfg.tryAll, cfg.tryUntilAuthorised: + if cfg.tryAll || cfg.tryUntilAuthorised { for _, acc := range user.accounts { candidateAccts = append(candidateAccts, acc) } - - default: - return nil, fmt.Errorf("programming error. invalid owner detection strategy") } // when there is no matching address, some calls, like submitting a transactions are allowed to go through From 982476b09f77761025cfc98f38a2c113022821e4 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Wed, 20 Mar 2024 19:50:42 +0000 Subject: [PATCH 26/65] fix --- tools/walletextension/main/main.go | 3 +++ tools/walletextension/rpcapi/wallet_extension.go | 4 +--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/walletextension/main/main.go b/tools/walletextension/main/main.go index 4b200968c0..0c848bed4f 100644 --- a/tools/walletextension/main/main.go +++ b/tools/walletextension/main/main.go @@ -45,6 +45,9 @@ func main() { time.Sleep(time.Second) } + // todo - temporary + config.LogPath = log.SysOut + // Sets up the log file. if config.LogPath != log.SysOut { _, err := os.Create(config.LogPath) diff --git a/tools/walletextension/rpcapi/wallet_extension.go b/tools/walletextension/rpcapi/wallet_extension.go index b569755b6a..80aa7d94a1 100644 --- a/tools/walletextension/rpcapi/wallet_extension.go +++ b/tools/walletextension/rpcapi/wallet_extension.go @@ -208,9 +208,7 @@ func (w *Services) AddAddressToUser(userID []byte, address string, signature []b return err } - requestEndTime := time.Now() - duration := requestEndTime.Sub(requestStartTime) - audit(w, "Storing new address for user: %s, address: %s, duration: %d ", hexutils.BytesToHex(userID), address, duration.Milliseconds()) + audit(w, "Storing new address for user: %s, address: %s, duration: %d ", hexutils.BytesToHex(userID), address, time.Now().Sub(requestStartTime).Milliseconds()) return nil } From 77ddb5e45dc1d39c2640483af85f89557b717e7b Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Thu, 21 Mar 2024 10:43:57 +0000 Subject: [PATCH 27/65] fix --- go/enclave/rpc/EstimateGas.go | 3 +++ tools/walletextension/container_run.sh | 2 +- tools/walletextension/main/cli.go | 2 +- tools/walletextension/main/main.go | 5 +---- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go/enclave/rpc/EstimateGas.go b/go/enclave/rpc/EstimateGas.go index f553742eb8..1573a16bd1 100644 --- a/go/enclave/rpc/EstimateGas.go +++ b/go/enclave/rpc/EstimateGas.go @@ -3,6 +3,7 @@ package rpc import ( "errors" "fmt" + "github.com/ten-protocol/go-ten/go/common/log" "math/big" "github.com/ethereum/go-ethereum/accounts/abi" @@ -107,10 +108,12 @@ func EstimateGasExecute(builder *CallBuilder[CallParamsWithBlock, hexutil.Uint64 err = fmt.Errorf(string(evmErr)) } builder.Err = err + rpc.logger.Info("Estimate gas error", log.ErrKey, err) return nil } totalGasEstimate := hexutil.Uint64(publishingGas.Uint64() + uint64(executionGasEstimate)) + rpc.logger.Info("Estimate gas:", "value", totalGasEstimate) builder.ReturnValue = &totalGasEstimate return nil } diff --git a/tools/walletextension/container_run.sh b/tools/walletextension/container_run.sh index 8329fce4d1..162cff74bb 100755 --- a/tools/walletextension/container_run.sh +++ b/tools/walletextension/container_run.sh @@ -14,7 +14,7 @@ host="0.0.0.0" nodeHost="erpc.sepolia-testnet.ten.xyz" nodePortHTTP=80 nodePortWS=81 -logPath="wallet_extension_logs.txt" +logPath="sys_out" databasePath=".obscuro/gateway_database.db" image="obscuronet/obscuro_gateway_sepolia_testnet:latest" diff --git a/tools/walletextension/main/cli.go b/tools/walletextension/main/cli.go index dbbe72a9a0..0e7f564c4c 100644 --- a/tools/walletextension/main/cli.go +++ b/tools/walletextension/main/cli.go @@ -33,7 +33,7 @@ const ( nodeWebsocketPortUsage = "The port on which to connect to the Obscuro node via RPC over websockets. Default: 81." logPathName = "logPath" - logPathDefault = "wallet_extension_logs.txt" + logPathDefault = "sys_out" logPathUsage = "The path to use for the wallet extension's log file" databasePathName = "databasePath" diff --git a/tools/walletextension/main/main.go b/tools/walletextension/main/main.go index 0c848bed4f..f07ee53da2 100644 --- a/tools/walletextension/main/main.go +++ b/tools/walletextension/main/main.go @@ -45,9 +45,6 @@ func main() { time.Sleep(time.Second) } - // todo - temporary - config.LogPath = log.SysOut - // Sets up the log file. if config.LogPath != log.SysOut { _, err := os.Create(config.LogPath) @@ -56,7 +53,7 @@ func main() { } } - logLvl := gethlog.LvlError + logLvl := gethlog.LvlDebug if config.VerboseFlag { logLvl = gethlog.LvlDebug } From 776b06a32026ea15107d21c26d1274ae51ff626c Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Thu, 21 Mar 2024 12:37:15 +0000 Subject: [PATCH 28/65] fix --- go/rpc/encrypted_client.go | 8 ++++---- lib/gethfork/rpc/handler.go | 2 -- tools/walletextension/rpcapi/utils.go | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/go/rpc/encrypted_client.go b/go/rpc/encrypted_client.go index 24050814f2..c82af3cf3f 100644 --- a/go/rpc/encrypted_client.go +++ b/go/rpc/encrypted_client.go @@ -243,13 +243,13 @@ func (c *EncRPCClient) executeSensitiveCall(ctx context.Context, result interfac // EstimateGas and Call methods return EVM Errors that are json objects // and contain multiple keys that normally do not get serialized if method == EstimateGas || method == Call { - var result errutil.EVMSerialisableError - err = json.Unmarshal([]byte(decodedError.Error()), &result) + var evmErr errutil.EVMSerialisableError + err = json.Unmarshal([]byte(decodedError.Error()), &evmErr) if err != nil { - return err + return decodedError } // Return the evm user error. - return result + return evmErr } // Return the user error. diff --git a/lib/gethfork/rpc/handler.go b/lib/gethfork/rpc/handler.go index df8b56ee10..6bab07b45f 100644 --- a/lib/gethfork/rpc/handler.go +++ b/lib/gethfork/rpc/handler.go @@ -19,7 +19,6 @@ package rpc import ( "context" "encoding/json" - "fmt" "reflect" "strconv" "strings" @@ -488,7 +487,6 @@ func (h *handler) handleCallMsg(ctx *callProc, msg *jsonrpcMessage) *jsonrpcMess return msg.errorResponse(&invalidRequestError{"invalid request"}) default: - fmt.Printf("Invalid request %v\n", jsonrpcMessage{}) return errorMessage(&invalidRequestError{"invalid request"}) } } diff --git a/tools/walletextension/rpcapi/utils.go b/tools/walletextension/rpcapi/utils.go index f35a358611..b18c28777e 100644 --- a/tools/walletextension/rpcapi/utils.go +++ b/tools/walletextension/rpcapi/utils.go @@ -119,7 +119,7 @@ func ExecAuthRPC[R any](ctx context.Context, w *Services, cfg *ExecCfg, method s } return nil, rpcErr }) - defer audit(w, "RPC call. uid=%s, method=%s args=%v result=%s error=%s time=%d", hexutils.BytesToHex(userID), method, args, res, err, time.Since(requestStartTime).Milliseconds()) + audit(w, "RPC call. uid=%s, method=%s args=%v result=%s error=%s time=%d", hexutils.BytesToHex(userID), method, args, res, err, time.Since(requestStartTime).Milliseconds()) return res, err } From 34868cd9d7fc19a1927cf5ca8685bc7e63bbb115 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Thu, 21 Mar 2024 12:37:24 +0000 Subject: [PATCH 29/65] fix --- go/enclave/rpc/EstimateGas.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/go/enclave/rpc/EstimateGas.go b/go/enclave/rpc/EstimateGas.go index 1573a16bd1..b05396afbe 100644 --- a/go/enclave/rpc/EstimateGas.go +++ b/go/enclave/rpc/EstimateGas.go @@ -3,9 +3,10 @@ package rpc import ( "errors" "fmt" - "github.com/ten-protocol/go-ten/go/common/log" "math/big" + "github.com/ten-protocol/go-ten/go/common/log" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" From afc5d3185b6ba2847fb0a9ab086bad28fcb0e281 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Thu, 21 Mar 2024 12:53:55 +0000 Subject: [PATCH 30/65] fix --- tools/walletextension/rpcapi/blockchain_api.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/walletextension/rpcapi/blockchain_api.go b/tools/walletextension/rpcapi/blockchain_api.go index 17034c7640..6c951bbe03 100644 --- a/tools/walletextension/rpcapi/blockchain_api.go +++ b/tools/walletextension/rpcapi/blockchain_api.go @@ -242,6 +242,13 @@ func (api *BlockChainAPI) EstimateGas(ctx context.Context, args gethapi.Transact computeFromCallback: func(user *GWUser) *common.Address { return searchFromAndData(user.GetAllAddresses(), args) }, + adjustArgs: func(acct *GWAccount) []any { + // set the from + if args.From == nil { + args.From = acct.address + } + return []any{args, blockNrOrHash, overrides} + }, // is this a security risk? useDefaultUser: true, tryAll: true, From e2943cb054274d6ff3a63c50cfde1f0b0bddec10 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Thu, 21 Mar 2024 13:25:17 +0000 Subject: [PATCH 31/65] fix --- go/enclave/rpc/EstimateGas.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/go/enclave/rpc/EstimateGas.go b/go/enclave/rpc/EstimateGas.go index b05396afbe..952b7c548e 100644 --- a/go/enclave/rpc/EstimateGas.go +++ b/go/enclave/rpc/EstimateGas.go @@ -55,11 +55,12 @@ func EstimateGasValidate(reqParams []any, builder *CallBuilder[CallParamsWithBlo } func EstimateGasExecute(builder *CallBuilder[CallParamsWithBlock, hexutil.Uint64], rpc *EncryptionManager) error { - err := authenticateFrom(builder.VK, builder.From) - if err != nil { - builder.Err = err - return nil //nolint:nilerr - } + // allow unauthenticated gas estimation + //err := authenticateFrom(builder.VK, builder.From) + //if err != nil { + // builder.Err = err + // return nil //nolint:nilerr + //} txArgs := builder.Param.callParams blockNumber := builder.Param.block From 889f7675f1957f0a5db4f2a95384b9aabeca7bdf Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Thu, 21 Mar 2024 14:48:25 +0000 Subject: [PATCH 32/65] fix --- go/enclave/rpc/EstimateGas.go | 75 ++++++++++--------- .../walletextension/rpcapi/blockchain_api.go | 14 ++-- 2 files changed, 45 insertions(+), 44 deletions(-) diff --git a/go/enclave/rpc/EstimateGas.go b/go/enclave/rpc/EstimateGas.go index 952b7c548e..7ac87b855b 100644 --- a/go/enclave/rpc/EstimateGas.go +++ b/go/enclave/rpc/EstimateGas.go @@ -36,10 +36,10 @@ func EstimateGasValidate(reqParams []any, builder *CallBuilder[CallParamsWithBlo return nil } - if callMsg.From == nil { - builder.Err = fmt.Errorf("no from Addr provided") - return nil - } + //if callMsg.From == nil { + // builder.Err = fmt.Errorf("no from Addr provided") + // return nil + //} // extract optional Block number - defaults to the latest Block if not avail blockNumber, err := gethencoding.ExtractOptionalBlockNumber(reqParams, 1) @@ -153,44 +153,45 @@ func (rpc *EncryptionManager) doEstimateGas(args *gethapi.TransactionArgs, blkNu */ hi = rpc.config.GasLocalExecutionCapFlag } - // Normalize the max fee per gas the call is willing to spend. - var feeCap *big.Int - if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { - return 0, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") - } else if args.GasPrice != nil { - feeCap = args.GasPrice.ToInt() - } else if args.MaxFeePerGas != nil { - feeCap = args.MaxFeePerGas.ToInt() - } else { - feeCap = gethcommon.Big0 - } - // Recap the highest gas limit with account's available balance. - if feeCap.BitLen() != 0 { //nolint:nestif - balance, err := rpc.chain.GetBalanceAtBlock(*args.From, blkNumber) - if err != nil { - return 0, fmt.Errorf("unable to fetch account balance - %w", err) + /* + // Normalize the max fee per gas the call is willing to spend. + var feeCap *big.Int + if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { + return 0, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") + } else if args.GasPrice != nil { + feeCap = args.GasPrice.ToInt() + } else if args.MaxFeePerGas != nil { + feeCap = args.MaxFeePerGas.ToInt() + } else { + feeCap = gethcommon.Big0 } + // Recap the highest gas limit with account's available balance. + if feeCap.BitLen() != 0 { //nolint:nestif + balance, err := rpc.chain.GetBalanceAtBlock(*args.From, blkNumber) + if err != nil { + return 0, fmt.Errorf("unable to fetch account balance - %w", err) + } - available := new(big.Int).Set(balance.ToInt()) - if args.Value != nil { - if args.Value.ToInt().Cmp(available) >= 0 { - return 0, errors.New("insufficient funds for transfer") + available := new(big.Int).Set(balance.ToInt()) + if args.Value != nil { + if args.Value.ToInt().Cmp(available) >= 0 { + return 0, errors.New("insufficient funds for transfer") + } + available.Sub(available, args.Value.ToInt()) } - available.Sub(available, args.Value.ToInt()) - } - allowance := new(big.Int).Div(available, feeCap) + allowance := new(big.Int).Div(available, feeCap) - // If the allowance is larger than maximum uint64, skip checking - if allowance.IsUint64() && hi > allowance.Uint64() { - transfer := args.Value - if transfer == nil { - transfer = new(hexutil.Big) + // If the allowance is larger than maximum uint64, skip checking + if allowance.IsUint64() && hi > allowance.Uint64() { + transfer := args.Value + if transfer == nil { + transfer = new(hexutil.Big) + } + rpc.logger.Debug("Gas estimation capped by limited funds", "original", hi, "balance", balance, + "sent", transfer.ToInt(), "maxFeePerGas", feeCap, "fundable", allowance) + hi = allowance.Uint64() } - rpc.logger.Debug("Gas estimation capped by limited funds", "original", hi, "balance", balance, - "sent", transfer.ToInt(), "maxFeePerGas", feeCap, "fundable", allowance) - hi = allowance.Uint64() - } - } + }*/ // Recap the highest gas allowance with specified gascap. if gasCap != 0 && hi > gasCap { rpc.logger.Debug("Caller gas above allowance, capping", "requested", hi, "cap", gasCap) diff --git a/tools/walletextension/rpcapi/blockchain_api.go b/tools/walletextension/rpcapi/blockchain_api.go index 6c951bbe03..eeb52b8018 100644 --- a/tools/walletextension/rpcapi/blockchain_api.go +++ b/tools/walletextension/rpcapi/blockchain_api.go @@ -214,13 +214,13 @@ func (api *BlockChainAPI) Call(ctx context.Context, args gethapi.TransactionArgs computeFromCallback: func(user *GWUser) *common.Address { return searchFromAndData(user.GetAllAddresses(), args) }, - adjustArgs: func(acct *GWAccount) []any { - // set the from - if args.From == nil { - args.From = acct.address - } - return []any{args, blockNrOrHash, overrides, blockOverrides} - }, + //adjustArgs: func(acct *GWAccount) []any { + // // set the from + // if args.From == nil { + // args.From = acct.address + // } + // return []any{args, blockNrOrHash, overrides, blockOverrides} + //}, useDefaultUser: true, }, "eth_call", args, blockNrOrHash, overrides, blockOverrides) if resp == nil { From 85b8ccb0ce7dcfcf585be4cb612bc3996ca6ea01 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Thu, 21 Mar 2024 16:42:58 +0000 Subject: [PATCH 33/65] fix --- go/enclave/rpc/EstimateGas.go | 10 +++++----- tools/walletextension/rpcapi/blockchain_api.go | 15 ++++++++------- tools/walletextension/rpcapi/utils.go | 6 +++--- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/go/enclave/rpc/EstimateGas.go b/go/enclave/rpc/EstimateGas.go index 7ac87b855b..074c2022cf 100644 --- a/go/enclave/rpc/EstimateGas.go +++ b/go/enclave/rpc/EstimateGas.go @@ -56,11 +56,11 @@ func EstimateGasValidate(reqParams []any, builder *CallBuilder[CallParamsWithBlo func EstimateGasExecute(builder *CallBuilder[CallParamsWithBlock, hexutil.Uint64], rpc *EncryptionManager) error { // allow unauthenticated gas estimation - //err := authenticateFrom(builder.VK, builder.From) - //if err != nil { - // builder.Err = err - // return nil //nolint:nilerr - //} + err := authenticateFrom(builder.VK, builder.From) + if err != nil { + builder.Err = err + return nil //nolint:nilerr + } txArgs := builder.Param.callParams blockNumber := builder.Param.block diff --git a/tools/walletextension/rpcapi/blockchain_api.go b/tools/walletextension/rpcapi/blockchain_api.go index eeb52b8018..b5ab673f0c 100644 --- a/tools/walletextension/rpcapi/blockchain_api.go +++ b/tools/walletextension/rpcapi/blockchain_api.go @@ -214,14 +214,15 @@ func (api *BlockChainAPI) Call(ctx context.Context, args gethapi.TransactionArgs computeFromCallback: func(user *GWUser) *common.Address { return searchFromAndData(user.GetAllAddresses(), args) }, - //adjustArgs: func(acct *GWAccount) []any { - // // set the from - // if args.From == nil { - // args.From = acct.address - // } - // return []any{args, blockNrOrHash, overrides, blockOverrides} - //}, + adjustArgs: func(acct *GWAccount) []any { + // set the from + if args.From == nil { + args.From = acct.address + } + return []any{args, blockNrOrHash, overrides, blockOverrides} + }, useDefaultUser: true, + tryAll: true, }, "eth_call", args, blockNrOrHash, overrides, blockOverrides) if resp == nil { return nil, err diff --git a/tools/walletextension/rpcapi/utils.go b/tools/walletextension/rpcapi/utils.go index b18c28777e..d370c2d040 100644 --- a/tools/walletextension/rpcapi/utils.go +++ b/tools/walletextension/rpcapi/utils.go @@ -108,7 +108,7 @@ func ExecAuthRPC[R any](ctx context.Context, w *Services, cfg *ExecCfg, method s } err = rpcClient.CallContext(ctx, &result, method, adjustedArgs...) if err != nil { - // todo - is this correct? + // for calls where we know the expected error we can return early if cfg.tryUntilAuthorised && err.Error() != notAuthorised { return nil, err } @@ -125,7 +125,7 @@ func ExecAuthRPC[R any](ctx context.Context, w *Services, cfg *ExecCfg, method s func getCandidateAccounts(user *GWUser, _ *Services, cfg *ExecCfg) ([]*GWAccount, error) { candidateAccts := make([]*GWAccount, 0) - // for users with multiple accounts determine a candidate account + // for users with multiple accounts try to determine a candidate account based on the available information switch { case cfg.account != nil: acc := user.accounts[*cfg.account] @@ -140,8 +140,8 @@ func getCandidateAccounts(user *GWUser, _ *Services, cfg *ExecCfg) ([]*GWAccount acc := user.accounts[*suggestedAddress] if acc != nil { candidateAccts = append(candidateAccts, acc) + return candidateAccts, nil } - return candidateAccts, nil } } From d0831bf4ce60e8a950eb8828ee89738244b949bd Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Thu, 21 Mar 2024 18:05:14 +0000 Subject: [PATCH 34/65] fix --- go/enclave/rpc/EstimateGas.go | 8 +++--- integration/obscurogateway/errors_contract.go | 2 +- integration/obscurogateway/events_contract.go | 2 +- integration/obscurogateway/gateway_user.go | 4 +-- integration/obscurogateway/json.go | 2 +- integration/obscurogateway/tengateway_test.go | 27 ++++++++----------- .../walletextension/rpcapi/blockchain_api.go | 19 ++++++++----- .../rpcapi/wallet_extension.go | 6 ++++- 8 files changed, 38 insertions(+), 32 deletions(-) diff --git a/go/enclave/rpc/EstimateGas.go b/go/enclave/rpc/EstimateGas.go index 074c2022cf..2b7140147d 100644 --- a/go/enclave/rpc/EstimateGas.go +++ b/go/enclave/rpc/EstimateGas.go @@ -36,10 +36,10 @@ func EstimateGasValidate(reqParams []any, builder *CallBuilder[CallParamsWithBlo return nil } - //if callMsg.From == nil { - // builder.Err = fmt.Errorf("no from Addr provided") - // return nil - //} + if callMsg.From == nil { + builder.Err = fmt.Errorf("no from Addr provided") + return nil + } // extract optional Block number - defaults to the latest Block if not avail blockNumber, err := gethencoding.ExtractOptionalBlockNumber(reqParams, 1) diff --git a/integration/obscurogateway/errors_contract.go b/integration/obscurogateway/errors_contract.go index 105640d9ae..67e2c076ad 100644 --- a/integration/obscurogateway/errors_contract.go +++ b/integration/obscurogateway/errors_contract.go @@ -1,4 +1,4 @@ -package faucet +package obscurogateway import ( "strings" diff --git a/integration/obscurogateway/events_contract.go b/integration/obscurogateway/events_contract.go index 859472ca6d..2815f56f2b 100644 --- a/integration/obscurogateway/events_contract.go +++ b/integration/obscurogateway/events_contract.go @@ -1,4 +1,4 @@ -package faucet +package obscurogateway import ( "strings" diff --git a/integration/obscurogateway/gateway_user.go b/integration/obscurogateway/gateway_user.go index 3f425d351c..b37445c504 100644 --- a/integration/obscurogateway/gateway_user.go +++ b/integration/obscurogateway/gateway_user.go @@ -1,4 +1,4 @@ -package faucet +package obscurogateway import ( "context" @@ -22,7 +22,7 @@ type GatewayUser struct { tgClient *lib.TGLib } -func NewUser(wallets []wallet.Wallet, serverAddressHTTP string, serverAddressWS string) (*GatewayUser, error) { +func NewGatewayUser(wallets []wallet.Wallet, serverAddressHTTP string, serverAddressWS string) (*GatewayUser, error) { ogClient := lib.NewTenGatewayLibrary(serverAddressHTTP, serverAddressWS) // automatically join diff --git a/integration/obscurogateway/json.go b/integration/obscurogateway/json.go index 3bccea5a6b..41b8629f0c 100644 --- a/integration/obscurogateway/json.go +++ b/integration/obscurogateway/json.go @@ -1,4 +1,4 @@ -package faucet +package obscurogateway import ( "encoding/json" diff --git a/integration/obscurogateway/tengateway_test.go b/integration/obscurogateway/tengateway_test.go index 1046588a19..b64402b5e9 100644 --- a/integration/obscurogateway/tengateway_test.go +++ b/integration/obscurogateway/tengateway_test.go @@ -1,4 +1,4 @@ -package faucet +package obscurogateway import ( "bytes" @@ -116,18 +116,18 @@ func TestTenGateway(t *testing.T) { } func testMultipleAccountsSubscription(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { - user0, err := NewUser([]wallet.Wallet{w}, httpURL, wsURL) + user0, err := NewGatewayUser([]wallet.Wallet{w, datagenerator.RandomWallet(integration.TenChainID)}, httpURL, wsURL) require.NoError(t, err) testlog.Logger().Info("Created user with encryption token", "t", user0.tgClient.UserID()) _, err = user0.HTTPClient.ChainID(context.Background()) require.NoError(t, err) - user1, err := NewUser([]wallet.Wallet{datagenerator.RandomWallet(integration.TenChainID), datagenerator.RandomWallet(integration.TenChainID)}, httpURL, wsURL) + user1, err := NewGatewayUser([]wallet.Wallet{datagenerator.RandomWallet(integration.TenChainID), datagenerator.RandomWallet(integration.TenChainID)}, httpURL, wsURL) require.NoError(t, err) testlog.Logger().Info("Created user with encryption token", "t", user1.tgClient.UserID()) - user2, err := NewUser([]wallet.Wallet{datagenerator.RandomWallet(integration.TenChainID), datagenerator.RandomWallet(integration.TenChainID)}, httpURL, wsURL) + user2, err := NewGatewayUser([]wallet.Wallet{datagenerator.RandomWallet(integration.TenChainID), datagenerator.RandomWallet(integration.TenChainID)}, httpURL, wsURL) require.NoError(t, err) testlog.Logger().Info("Created user with encryption token", "t", user2.tgClient.UserID()) @@ -151,12 +151,7 @@ func testMultipleAccountsSubscription(t *testing.T, httpURL, wsURL string, w wal require.NoError(t, err) // Print balances of all registered accounts to check if all accounts have funds - balances, err := user0.GetUserAccountsBalances() - require.NoError(t, err) - for _, balance := range balances { - require.NotZero(t, balance.Uint64()) - } - balances, err = user1.GetUserAccountsBalances() + balances, err := user1.GetUserAccountsBalances() require.NoError(t, err) for _, balance := range balances { require.NotZero(t, balance.Uint64()) @@ -268,10 +263,10 @@ func testMultipleAccountsSubscription(t *testing.T, httpURL, wsURL string, w wal } func testSubscriptionTopics(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { - user0, err := NewUser([]wallet.Wallet{w}, httpURL, wsURL) + user0, err := NewGatewayUser([]wallet.Wallet{w}, httpURL, wsURL) require.NoError(t, err) - user1, err := NewUser([]wallet.Wallet{datagenerator.RandomWallet(integration.TenChainID), datagenerator.RandomWallet(integration.TenChainID)}, httpURL, wsURL) + user1, err := NewGatewayUser([]wallet.Wallet{datagenerator.RandomWallet(integration.TenChainID), datagenerator.RandomWallet(integration.TenChainID)}, httpURL, wsURL) require.NoError(t, err) // register all the accounts for that user @@ -514,7 +509,7 @@ func testErrorsRevertedArePassed(t *testing.T, httpURL, wsURL string, w wallet.W func testUnsubscribe(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { // create a user with multiple accounts - user, err := NewUser([]wallet.Wallet{w, datagenerator.RandomWallet(integration.TenChainID)}, httpURL, wsURL) + user, err := NewGatewayUser([]wallet.Wallet{w, datagenerator.RandomWallet(integration.TenChainID)}, httpURL, wsURL) require.NoError(t, err) testlog.Logger().Info("Created user with encryption token", "t", user.tgClient.UserID()) @@ -570,7 +565,7 @@ func testUnsubscribe(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { func testClosingConnectionWhileSubscribed(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { // create a user with multiple accounts - user, err := NewUser([]wallet.Wallet{w, datagenerator.RandomWallet(integration.TenChainID)}, httpURL, wsURL) + user, err := NewGatewayUser([]wallet.Wallet{w, datagenerator.RandomWallet(integration.TenChainID)}, httpURL, wsURL) require.NoError(t, err) testlog.Logger().Info("Created user with encryption token", "t", user.tgClient.UserID()) @@ -633,7 +628,7 @@ func testClosingConnectionWhileSubscribed(t *testing.T, httpURL, wsURL string, w } func testDifferentMessagesOnRegister(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { - user, err := NewUser([]wallet.Wallet{w, datagenerator.RandomWallet(integration.TenChainID)}, httpURL, wsURL) + user, err := NewGatewayUser([]wallet.Wallet{w, datagenerator.RandomWallet(integration.TenChainID)}, httpURL, wsURL) require.NoError(t, err) testlog.Logger().Info("Created user with encryption token: %s\n", user.tgClient.UserID()) @@ -727,7 +722,7 @@ func getFeeAndGas(client *ethclient.Client, wallet wallet.Wallet, legacyTx *type } estimate, err := client.EstimateGas(context.Background(), ethereum.CallMsg{ - From: wallet.Address(), + // From: wallet.Address(), To: tx.To(), Value: tx.Value(), Data: tx.Data(), diff --git a/tools/walletextension/rpcapi/blockchain_api.go b/tools/walletextension/rpcapi/blockchain_api.go index b5ab673f0c..93daf5ef9a 100644 --- a/tools/walletextension/rpcapi/blockchain_api.go +++ b/tools/walletextension/rpcapi/blockchain_api.go @@ -3,6 +3,7 @@ package rpcapi //goland:noinspection ALL import ( "context" + "encoding/json" "math/big" "time" @@ -215,11 +216,14 @@ func (api *BlockChainAPI) Call(ctx context.Context, args gethapi.TransactionArgs return searchFromAndData(user.GetAllAddresses(), args) }, adjustArgs: func(acct *GWAccount) []any { + serialised, _ := json.Marshal(args) + var argsClone gethapi.TransactionArgs + json.Unmarshal(serialised, &argsClone) // set the from - if args.From == nil { - args.From = acct.address + if args.From == nil || args.From.Hex() == (common.Address{}).Hex() { + argsClone.From = acct.address } - return []any{args, blockNrOrHash, overrides, blockOverrides} + return []any{argsClone, blockNrOrHash, overrides} }, useDefaultUser: true, tryAll: true, @@ -244,11 +248,14 @@ func (api *BlockChainAPI) EstimateGas(ctx context.Context, args gethapi.Transact return searchFromAndData(user.GetAllAddresses(), args) }, adjustArgs: func(acct *GWAccount) []any { + serialised, _ := json.Marshal(args) + var argsClone gethapi.TransactionArgs + json.Unmarshal(serialised, &argsClone) // set the from - if args.From == nil { - args.From = acct.address + if args.From == nil || args.From.Hex() == (common.Address{}).Hex() { + argsClone.From = acct.address } - return []any{args, blockNrOrHash, overrides} + return []any{argsClone, blockNrOrHash, overrides} }, // is this a security risk? useDefaultUser: true, diff --git a/tools/walletextension/rpcapi/wallet_extension.go b/tools/walletextension/rpcapi/wallet_extension.go index 80aa7d94a1..a7e5055f35 100644 --- a/tools/walletextension/rpcapi/wallet_extension.go +++ b/tools/walletextension/rpcapi/wallet_extension.go @@ -196,11 +196,15 @@ func (w *Services) AddAddressToUser(userID []byte, address string, signature []b requestStartTime := time.Now() addressFromMessage := gethcommon.HexToAddress(address) // check if a message was signed by the correct address and if the signature is valid - _, err := viewingkey.CheckSignature(userID, signature, int64(w.Config.TenChainID), signatureType) + recoveredAddress, err := viewingkey.CheckSignature(userID, signature, int64(w.Config.TenChainID), signatureType) if err != nil { return fmt.Errorf("signature is not valid: %w", err) } + if recoveredAddress.Hex() != addressFromMessage.Hex() { + return fmt.Errorf("invalid request. Signature doesn't match address") + } + // register the account for that viewing key err = w.Storage.AddAccount(userID, addressFromMessage.Bytes(), signature, signatureType) if err != nil { From 3f927764ded3c2a5da0609313d668d0ee023cef9 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Fri, 22 Mar 2024 12:11:08 +0000 Subject: [PATCH 35/65] fix --- go/common/log_events.go | 47 ++++++++++++++- go/enclave/rpc/GetLogs.go | 58 ++++++------------- integration/obscurogateway/tengateway_test.go | 13 +++++ .../walletextension/rpcapi/blockchain_api.go | 18 +++--- tools/walletextension/rpcapi/filter_api.go | 5 +- tools/walletextension/rpcapi/utils.go | 16 +---- 6 files changed, 90 insertions(+), 67 deletions(-) diff --git a/go/common/log_events.go b/go/common/log_events.go index fd01b0357c..7f29d0c401 100644 --- a/go/common/log_events.go +++ b/go/common/log_events.go @@ -1,6 +1,8 @@ package common import ( + "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/filters" @@ -36,6 +38,47 @@ type FilterCriteriaJSON struct { BlockHash *common.Hash `json:"blockHash"` FromBlock *rpc.BlockNumber `json:"fromBlock"` ToBlock *rpc.BlockNumber `json:"toBlock"` - Addresses interface{} `json:"address"` - Topics []interface{} `json:"topics"` + Addresses []common.Address `json:"addresses"` + Topics [][]common.Hash `json:"topics"` +} + +func FromCriteria(crit filters.FilterCriteria) FilterCriteriaJSON { + var from *rpc.BlockNumber + if crit.FromBlock != nil { + f := (rpc.BlockNumber)(crit.FromBlock.Int64()) + from = &f + } + + var to *rpc.BlockNumber + if crit.ToBlock != nil { + t := (rpc.BlockNumber)(crit.ToBlock.Int64()) + to = &t + } + + return FilterCriteriaJSON{ + BlockHash: crit.BlockHash, + FromBlock: from, + ToBlock: to, + Addresses: crit.Addresses, + Topics: crit.Topics, + } +} + +func ToCriteria(jsonCriteria FilterCriteriaJSON) filters.FilterCriteria { + var from *big.Int + if jsonCriteria.FromBlock != nil { + from = big.NewInt(jsonCriteria.FromBlock.Int64()) + } + var to *big.Int + if jsonCriteria.ToBlock != nil { + to = big.NewInt(jsonCriteria.ToBlock.Int64()) + } + + return filters.FilterCriteria{ + BlockHash: jsonCriteria.BlockHash, + FromBlock: from, + ToBlock: to, + Addresses: jsonCriteria.Addresses, + Topics: jsonCriteria.Topics, + } } diff --git a/go/enclave/rpc/GetLogs.go b/go/enclave/rpc/GetLogs.go index 9ac1d2acbc..b4798600e2 100644 --- a/go/enclave/rpc/GetLogs.go +++ b/go/enclave/rpc/GetLogs.go @@ -5,39 +5,40 @@ import ( "errors" "fmt" + "github.com/ten-protocol/go-ten/go/common" "github.com/ten-protocol/go-ten/go/common/errutil" "github.com/ethereum/go-ethereum/core/types" - gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ten-protocol/go-ten/go/common/syserr" ) func GetLogsValidate(reqParams []any, builder *CallBuilder[filters.FilterCriteria, []*types.Log], _ *EncryptionManager) error { - // Parameters are [Filter, Address] - if len(reqParams) != 2 { + // Parameters are [Filter] + if len(reqParams) != 1 { builder.Err = fmt.Errorf("unexpected number of parameters") return nil } - // We extract the arguments from the param bytes. - filter, forAddress, err := extractGetLogsParams(reqParams) + + serialised, err := json.Marshal(reqParams[0]) if err != nil { - builder.Err = err - return nil //nolint:nilerr + builder.Err = fmt.Errorf("invalid parameter %w", err) + return nil + } + var crit common.FilterCriteriaJSON + err = json.Unmarshal(serialised, &crit) + if err != nil { + builder.Err = fmt.Errorf("invalid parameter %w", err) + return nil } - builder.From = forAddress - builder.Param = filter + filter := common.ToCriteria(crit) + + builder.Param = &filter return nil } func GetLogsExecute(builder *CallBuilder[filters.FilterCriteria, []*types.Log], rpc *EncryptionManager) error { //nolint:gocognit - err := authenticateFrom(builder.VK, builder.From) - if err != nil { - builder.Err = err - return nil //nolint:nilerr - } - filter := builder.Param // todo logic to check that the filter is valid // can't have both from and blockhash @@ -83,7 +84,7 @@ func GetLogsExecute(builder *CallBuilder[filters.FilterCriteria, []*types.Log], } // We retrieve the relevant logs that match the filter. - filteredLogs, err := rpc.storage.FilterLogs(builder.From, from, to, nil, filter.Addresses, filter.Topics) + filteredLogs, err := rpc.storage.FilterLogs(builder.VK.AccountAddress, from, to, nil, filter.Addresses, filter.Topics) if err != nil { if errors.Is(err, syserr.InternalError{}) { return err @@ -95,28 +96,3 @@ func GetLogsExecute(builder *CallBuilder[filters.FilterCriteria, []*types.Log], builder.ReturnValue = &filteredLogs return nil } - -// Returns the params extracted from an eth_getLogs request. -func extractGetLogsParams(paramList []interface{}) (*filters.FilterCriteria, *gethcommon.Address, error) { - // We extract the first param, the filter for the logs. - // We marshal the filter criteria from a map to JSON, then back from JSON into a FilterCriteria. This is - // because the filter criteria arrives as a map, and there is no way to convert it to a map directly into a - // FilterCriteria. - filterJSON, err := json.Marshal(paramList[0]) - if err != nil { - return nil, nil, fmt.Errorf("could not marshal filter criteria to JSON. Cause: %w", err) - } - filter := filters.FilterCriteria{} - err = filter.UnmarshalJSON(filterJSON) - if err != nil { - return nil, nil, fmt.Errorf("could not unmarshal filter criteria from JSON. Cause: %w", err) - } - - // We extract the second param, the address the logs are for. - forAddressHex, ok := paramList[1].(string) - if !ok { - return nil, nil, fmt.Errorf("expected second argument in GetLogs request to be of type string, but got %T", paramList[0]) - } - forAddress := gethcommon.HexToAddress(forAddressHex) - return &filter, &forAddress, nil -} diff --git a/integration/obscurogateway/tengateway_test.go b/integration/obscurogateway/tengateway_test.go index b64402b5e9..d94ad928da 100644 --- a/integration/obscurogateway/tengateway_test.go +++ b/integration/obscurogateway/tengateway_test.go @@ -11,6 +11,8 @@ import ( "testing" "time" + "github.com/ten-protocol/go-ten/lib/gethfork/rpc" + "github.com/ten-protocol/go-ten/tools/walletextension" log2 "github.com/ten-protocol/go-ten/go/common/log" @@ -182,6 +184,9 @@ func testMultipleAccountsSubscription(t *testing.T, httpURL, wsURL string, w wal contractReceipt, err := integrationCommon.AwaitReceiptEth(context.Background(), user0.HTTPClient, signedTx.Hash(), time.Minute) require.NoError(t, err) + _, err = user0.HTTPClient.CodeAt(context.Background(), contractReceipt.ContractAddress, big.NewInt(int64(rpc.LatestBlockNumber))) + require.NoError(t, err) + // check if value was changed in the smart contract with the interactions above pack, _ := eventsContractABI.Pack("message2") result, err := user1.HTTPClient.CallContract(context.Background(), ethereum.CallMsg{ @@ -260,6 +265,14 @@ func testMultipleAccountsSubscription(t *testing.T, httpURL, wsURL string, w wal assert.Equal(t, 3, len(user1logs)) // user2 should see three events (two lifecycle events - same as user0) and event with his interaction with setMessage assert.Equal(t, 3, len(user2logs)) + + _, err = user0.HTTPClient.FilterLogs(context.TODO(), ethereum.FilterQuery{ + Addresses: []gethcommon.Address{contractReceipt.ContractAddress}, + FromBlock: big.NewInt(0), + ToBlock: big.NewInt(10000), + Topics: nil, + }) + require.NoError(t, err) } func testSubscriptionTopics(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { diff --git a/tools/walletextension/rpcapi/blockchain_api.go b/tools/walletextension/rpcapi/blockchain_api.go index 93daf5ef9a..ffa016e380 100644 --- a/tools/walletextension/rpcapi/blockchain_api.go +++ b/tools/walletextension/rpcapi/blockchain_api.go @@ -130,19 +130,17 @@ func (api *BlockChainAPI) GetBlockByHash(ctx context.Context, hash common.Hash, } func (api *BlockChainAPI) GetCode(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) { - resp, err := ExecAuthRPC[hexutil.Bytes]( + // todo - must be authenticated + resp, err := UnauthenticatedTenRPCCall[hexutil.Bytes]( ctx, api.we, - &ExecCfg{ - cacheCfg: &CacheCfg{ - TTLCallback: func() time.Duration { - if blockNrOrHash.BlockNumber != nil && blockNrOrHash.BlockNumber.Int64() <= 0 { - return shortCacheTTL - } - return longCacheTTL - }, + &CacheCfg{ + TTLCallback: func() time.Duration { + if blockNrOrHash.BlockNumber != nil && blockNrOrHash.BlockNumber.Int64() <= 0 { + return shortCacheTTL + } + return longCacheTTL }, - account: &address, }, "eth_getCode", address, diff --git a/tools/walletextension/rpcapi/filter_api.go b/tools/walletextension/rpcapi/filter_api.go index 25d9d0c05e..218c9059d4 100644 --- a/tools/walletextension/rpcapi/filter_api.go +++ b/tools/walletextension/rpcapi/filter_api.go @@ -196,7 +196,6 @@ func handleUnsubscribe(connectionSub *rpc.Subscription, backendSubscriptions []* } */ func (api *FilterAPI) GetLogs(ctx context.Context, crit filters.FilterCriteria) ([]*types.Log, error) { - // todo logs, err := ExecAuthRPC[[]*types.Log]( ctx, api.we, @@ -211,6 +210,10 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit filters.FilterCriteria) }, }, tryUntilAuthorised: true, + adjustArgs: func(acct *GWAccount) []any { + // convert to something serializable + return []any{common.FromCriteria(crit)} + }, }, "eth_getLogs", crit, diff --git a/tools/walletextension/rpcapi/utils.go b/tools/walletextension/rpcapi/utils.go index d370c2d040..3ea327f48a 100644 --- a/tools/walletextension/rpcapi/utils.go +++ b/tools/walletextension/rpcapi/utils.go @@ -151,27 +151,17 @@ func getCandidateAccounts(user *GWUser, _ *Services, cfg *ExecCfg) ([]*GWAccount } } - // when there is no matching address, some calls, like submitting a transactions are allowed to go through - // todo - remove - //if len(candidateAccts) == 0 && cfg.useDefaultUser { - // defaultUser, err := getUser(w.DefaultUser, w.Storage) - // if err != nil { - // panic(err) - // } - // defaultAcct := defaultUser.GetAllAddresses()[0] - // candidateAccts = append(candidateAccts, defaultUser.accounts[*defaultAcct]) - //} return candidateAccts, nil } -func extractUserID(ctx context.Context, w *Services) ([]byte, error) { +func extractUserID(ctx context.Context, _ *Services) ([]byte, error) { token, ok := ctx.Value(rpc.GWTokenKey{}).(string) if !ok { - return nil, fmt.Errorf("invalid userid") + return nil, fmt.Errorf("invalid userid: %s", ctx.Value(rpc.GWTokenKey{})) } userID := hexutils.HexToBytes(token) if len(userID) != viewingkey.UserIDLength { - return nil, fmt.Errorf("invalid userid") + return nil, fmt.Errorf("invalid userid: %s", token) } return userID, nil } From d036a6a40a3c3e4eba2f83fbc5a32699e9c043ac Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Fri, 22 Mar 2024 13:56:04 +0000 Subject: [PATCH 36/65] fix --- integration/obscurogateway/tengateway_test.go | 3 +++ lib/gethfork/rpc/handler.go | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/integration/obscurogateway/tengateway_test.go b/integration/obscurogateway/tengateway_test.go index d94ad928da..dab772f299 100644 --- a/integration/obscurogateway/tengateway_test.go +++ b/integration/obscurogateway/tengateway_test.go @@ -125,6 +125,9 @@ func testMultipleAccountsSubscription(t *testing.T, httpURL, wsURL string, w wal _, err = user0.HTTPClient.ChainID(context.Background()) require.NoError(t, err) + //_, err = user0.WSClient.BalanceAt(context.TODO(), user0.Wallets[0].Address(), nil) + //require.NoError(t, err) + user1, err := NewGatewayUser([]wallet.Wallet{datagenerator.RandomWallet(integration.TenChainID), datagenerator.RandomWallet(integration.TenChainID)}, httpURL, wsURL) require.NoError(t, err) testlog.Logger().Info("Created user with encryption token", "t", user1.tgClient.UserID()) diff --git a/lib/gethfork/rpc/handler.go b/lib/gethfork/rpc/handler.go index 6bab07b45f..73e0443dab 100644 --- a/lib/gethfork/rpc/handler.go +++ b/lib/gethfork/rpc/handler.go @@ -25,6 +25,8 @@ import ( "sync" "time" + "github.com/status-im/keycard-go/hexutils" + "github.com/ethereum/go-ethereum/log" ) @@ -386,6 +388,9 @@ func (h *handler) startCallProc(fn func(*callProc)) { ctx, cancel := context.WithCancel(h.rootCtx) defer h.callWG.Done() defer cancel() + if ctx.Value(GWTokenKey{}) == nil { + ctx = context.WithValue(ctx, GWTokenKey{}, hexutils.BytesToHex(h.UserID)) + } fn(&callProc{ctx: ctx}) }() } From 886235650f348c38d1631bf313973449f9917031 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Fri, 22 Mar 2024 14:30:48 +0000 Subject: [PATCH 37/65] cleanup --- .../walletextension/rpcapi/blockchain_api.go | 76 +++++++++---------- tools/walletextension/rpcapi/ethereum_api.go | 5 +- .../walletextension/rpcapi/transaction_api.go | 15 ++-- tools/walletextension/rpcapi/utils.go | 15 +++- 4 files changed, 55 insertions(+), 56 deletions(-) diff --git a/tools/walletextension/rpcapi/blockchain_api.go b/tools/walletextension/rpcapi/blockchain_api.go index ffa016e380..19f3353b76 100644 --- a/tools/walletextension/rpcapi/blockchain_api.go +++ b/tools/walletextension/rpcapi/blockchain_api.go @@ -45,12 +45,10 @@ func (api *BlockChainAPI) GetBalance(ctx context.Context, address common.Address &ExecCfg{ cacheCfg: &CacheCfg{ TTLCallback: func() time.Duration { - if blockNrOrHash.BlockNumber != nil && blockNrOrHash.BlockNumber.Int64() <= 0 { - return shortCacheTTL - } - return longCacheTTL + return cacheTTLBlockNumberOrHash(blockNrOrHash) }, }, + account: &address, tryUntilAuthorised: true, // the user can request the balance of a contract account }, "eth_getBalance", @@ -84,10 +82,7 @@ type StorageResult struct { */ func (api *BlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (map[string]interface{}, error) { resp, err := UnauthenticatedTenRPCCall[map[string]interface{}](ctx, api.we, &CacheCfg{TTLCallback: func() time.Duration { - if number > 0 { - return longCacheTTL - } - return shortCacheTTL + return cacheTTLBlockNumber(number) }}, "eth_getHeaderByNumber", number) if resp == nil { return nil, err @@ -109,10 +104,7 @@ func (api *BlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.Block api.we, &CacheCfg{ TTLCallback: func() time.Duration { - if number > 0 { - return longCacheTTL - } - return shortCacheTTL + return cacheTTLBlockNumber(number) }, }, "eth_getBlockByNumber", number, fullTx) if resp == nil { @@ -136,10 +128,7 @@ func (api *BlockChainAPI) GetCode(ctx context.Context, address common.Address, b api.we, &CacheCfg{ TTLCallback: func() time.Duration { - if blockNrOrHash.BlockNumber != nil && blockNrOrHash.BlockNumber.Int64() <= 0 { - return shortCacheTTL - } - return longCacheTTL + return cacheTTLBlockNumberOrHash(blockNrOrHash) }, }, "eth_getCode", @@ -204,27 +193,17 @@ func (api *BlockChainAPI) Call(ctx context.Context, args gethapi.TransactionArgs resp, err := ExecAuthRPC[hexutil.Bytes](ctx, api.we, &ExecCfg{ cacheCfg: &CacheCfg{ TTLCallback: func() time.Duration { - if blockNrOrHash.BlockNumber != nil && blockNrOrHash.BlockNumber.Int64() <= 0 { - return shortCacheTTL - } - return longCacheTTL + return cacheTTLBlockNumberOrHash(blockNrOrHash) }, }, computeFromCallback: func(user *GWUser) *common.Address { return searchFromAndData(user.GetAllAddresses(), args) }, adjustArgs: func(acct *GWAccount) []any { - serialised, _ := json.Marshal(args) - var argsClone gethapi.TransactionArgs - json.Unmarshal(serialised, &argsClone) - // set the from - if args.From == nil || args.From.Hex() == (common.Address{}).Hex() { - argsClone.From = acct.address - } - return []any{argsClone, blockNrOrHash, overrides} + argsClone := populateFrom(acct, args) + return []any{argsClone, blockNrOrHash, overrides, blockOverrides} }, - useDefaultUser: true, - tryAll: true, + tryAll: true, }, "eth_call", args, blockNrOrHash, overrides, blockOverrides) if resp == nil { return nil, err @@ -236,28 +215,21 @@ func (api *BlockChainAPI) EstimateGas(ctx context.Context, args gethapi.Transact resp, err := ExecAuthRPC[hexutil.Uint64](ctx, api.we, &ExecCfg{ cacheCfg: &CacheCfg{ TTLCallback: func() time.Duration { - if blockNrOrHash != nil && blockNrOrHash.BlockNumber != nil && blockNrOrHash.BlockNumber.Int64() <= 0 { - return shortCacheTTL + if blockNrOrHash != nil { + return cacheTTLBlockNumberOrHash(*blockNrOrHash) } - return longCacheTTL + return shortCacheTTL }, }, computeFromCallback: func(user *GWUser) *common.Address { return searchFromAndData(user.GetAllAddresses(), args) }, adjustArgs: func(acct *GWAccount) []any { - serialised, _ := json.Marshal(args) - var argsClone gethapi.TransactionArgs - json.Unmarshal(serialised, &argsClone) - // set the from - if args.From == nil || args.From.Hex() == (common.Address{}).Hex() { - argsClone.From = acct.address - } + argsClone := populateFrom(acct, args) return []any{argsClone, blockNrOrHash, overrides} }, // is this a security risk? - useDefaultUser: true, - tryAll: true, + tryAll: true, }, "eth_estimateGas", args, blockNrOrHash, overrides) if resp == nil { return 0, err @@ -265,6 +237,26 @@ func (api *BlockChainAPI) EstimateGas(ctx context.Context, args gethapi.Transact return *resp, err } +func populateFrom(acct *GWAccount, args gethapi.TransactionArgs) gethapi.TransactionArgs { + // clone the args + argsClone := cloneArgs(args) + // set the from + if args.From == nil || args.From.Hex() == (common.Address{}).Hex() { + argsClone.From = acct.address + } + return argsClone +} + +func cloneArgs(args gethapi.TransactionArgs) gethapi.TransactionArgs { + serialised, _ := json.Marshal(args) + var argsClone gethapi.TransactionArgs + err := json.Unmarshal(serialised, &argsClone) + if err != nil { + return gethapi.TransactionArgs{} + } + return argsClone +} + /* type accessListResult struct { Accesslist *types.AccessList `json:"accessList"` diff --git a/tools/walletextension/rpcapi/ethereum_api.go b/tools/walletextension/rpcapi/ethereum_api.go index 724c7a6354..bdc74f5b8e 100644 --- a/tools/walletextension/rpcapi/ethereum_api.go +++ b/tools/walletextension/rpcapi/ethereum_api.go @@ -41,10 +41,7 @@ func (api *EthereumAPI) FeeHistory(ctx context.Context, blockCount math.HexOrDec ctx, api.we, &CacheCfg{TTLCallback: func() time.Duration { - if lastBlock > 0 { - return longCacheTTL - } - return shortCacheTTL + return cacheTTLBlockNumber(lastBlock) }}, "eth_feeHistory", blockCount, diff --git a/tools/walletextension/rpcapi/transaction_api.go b/tools/walletextension/rpcapi/transaction_api.go index d9f3078388..dcfd3512fc 100644 --- a/tools/walletextension/rpcapi/transaction_api.go +++ b/tools/walletextension/rpcapi/transaction_api.go @@ -25,10 +25,7 @@ func NewTransactionAPI(we *Services) *TransactionAPI { func (s *TransactionAPI) GetBlockTransactionCountByNumber(ctx context.Context, blockNr gethrpc.BlockNumber) *hexutil.Uint { count, err := UnauthenticatedTenRPCCall[hexutil.Uint](ctx, s.we, &CacheCfg{TTLCallback: func() time.Duration { - if blockNr > 0 { - return longCacheTTL - } - return shortCacheTTL + return cacheTTLBlockNumber(blockNr) }}, "eth_getBlockTransactionCountByNumber", blockNr) if err != nil { return nil @@ -65,11 +62,11 @@ func (s *TransactionAPI) GetRawTransactionByBlockHashAndIndex(ctx context.Contex }*/ func (s *TransactionAPI) GetTransactionCount(ctx context.Context, address common.Address, blockNrOrHash gethrpc.BlockNumberOrHash) (*hexutil.Uint64, error) { - return ExecAuthRPC[hexutil.Uint64](ctx, s.we, &ExecCfg{account: &address, useDefaultUser: true}, "eth_getTransactionCount", address, blockNrOrHash) + return ExecAuthRPC[hexutil.Uint64](ctx, s.we, &ExecCfg{account: &address}, "eth_getTransactionCount", address, blockNrOrHash) } func (s *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) (*rpc.RpcTransaction, error) { - return ExecAuthRPC[rpc.RpcTransaction](ctx, s.we, &ExecCfg{tryAll: true, useDefaultUser: true}, "eth_getTransactionByHash", hash) + return ExecAuthRPC[rpc.RpcTransaction](ctx, s.we, &ExecCfg{tryAll: true}, "eth_getTransactionByHash", hash) } func (s *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) { @@ -81,7 +78,7 @@ func (s *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash commo } func (s *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) { - txRec, err := ExecAuthRPC[map[string]interface{}](ctx, s.we, &ExecCfg{tryUntilAuthorised: true, useDefaultUser: true}, "eth_getTransactionReceipt", hash) + txRec, err := ExecAuthRPC[map[string]interface{}](ctx, s.we, &ExecCfg{tryUntilAuthorised: true}, "eth_getTransactionReceipt", hash) if err != nil { return nil, err } @@ -92,7 +89,7 @@ func (s *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common. } func (s *TransactionAPI) SendTransaction(ctx context.Context, args gethapi.TransactionArgs) (common.Hash, error) { - txRec, err := ExecAuthRPC[common.Hash](ctx, s.we, &ExecCfg{account: args.From, useDefaultUser: true}, "eth_sendTransaction", args) + txRec, err := ExecAuthRPC[common.Hash](ctx, s.we, &ExecCfg{account: args.From}, "eth_sendTransaction", args) if err != nil { return common.Hash{}, err } @@ -123,7 +120,7 @@ type SignTransactionResult struct { }*/ func (s *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) { - txRec, err := ExecAuthRPC[common.Hash](ctx, s.we, &ExecCfg{tryAll: true, useDefaultUser: true}, "eth_sendRawTransaction", input) + txRec, err := ExecAuthRPC[common.Hash](ctx, s.we, &ExecCfg{tryAll: true}, "eth_sendRawTransaction", input) if err != nil { return common.Hash{}, err } diff --git a/tools/walletextension/rpcapi/utils.go b/tools/walletextension/rpcapi/utils.go index 3ea327f48a..c9ca272c1c 100644 --- a/tools/walletextension/rpcapi/utils.go +++ b/tools/walletextension/rpcapi/utils.go @@ -35,7 +35,6 @@ type ExecCfg struct { tryUntilAuthorised bool adjustArgs func(acct *GWAccount) []any cacheCfg *CacheCfg - useDefaultUser bool } type CacheCfg struct { @@ -219,3 +218,17 @@ func audit(services *Services, msg string, params ...any) { services.FileLogger.Info(fmt.Sprintf(msg, params...)) } } + +func cacheTTLBlockNumberOrHash(blockNrOrHash rpc.BlockNumberOrHash) time.Duration { + if blockNrOrHash.BlockNumber != nil && blockNrOrHash.BlockNumber.Int64() <= 0 { + return shortCacheTTL + } + return longCacheTTL +} + +func cacheTTLBlockNumber(lastBlock rpc.BlockNumber) time.Duration { + if lastBlock > 0 { + return longCacheTTL + } + return shortCacheTTL +} From b26ab557fe0c4b60ea6e13414367a6d5c1cc36bf Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Fri, 22 Mar 2024 14:47:06 +0000 Subject: [PATCH 38/65] cleanup --- go/common/viewingkey/viewing_key_signature.go | 2 +- go/enclave/vkhandler/vk_handler.go | 2 +- go/enclave/vkhandler/vk_handler_test.go | 8 ++--- lib/gethfork/rpc/types.go | 14 ++++---- tools/walletextension/rpcapi/utils.go | 2 +- .../rpcapi/wallet_extension.go | 36 +------------------ 6 files changed, 15 insertions(+), 49 deletions(-) diff --git a/go/common/viewingkey/viewing_key_signature.go b/go/common/viewingkey/viewing_key_signature.go index 54c3f51248..5733dda64c 100644 --- a/go/common/viewingkey/viewing_key_signature.go +++ b/go/common/viewingkey/viewing_key_signature.go @@ -88,7 +88,7 @@ func (e EIP712Checker) CheckSignature(encryptionToken []byte, signature []byte, // encryptionToken is expected to be a public key and not encrypted token as with other signature types // (since this is only temporary fix and legacy format will be removed soon) func (lsc LegacyChecker) CheckSignature(encryptionToken []byte, signature []byte, _ int64) (*gethcommon.Address, error) { - publicKey := []byte(encryptionToken) + publicKey := encryptionToken msgToSignLegacy := GenerateLegacySignMessage(publicKey) recoveredAccountPublicKeyLegacy, err := crypto.SigToPub(accounts.TextHash(msgToSignLegacy), signature) diff --git a/go/enclave/vkhandler/vk_handler.go b/go/enclave/vkhandler/vk_handler.go index 54799bd777..67fa6d22b0 100644 --- a/go/enclave/vkhandler/vk_handler.go +++ b/go/enclave/vkhandler/vk_handler.go @@ -1,4 +1,4 @@ -package vkhandler +package vkhandler //nolint:typecheck import ( "crypto/rand" diff --git a/go/enclave/vkhandler/vk_handler_test.go b/go/enclave/vkhandler/vk_handler_test.go index e8065ef2e8..1e4704a0f9 100644 --- a/go/enclave/vkhandler/vk_handler_test.go +++ b/go/enclave/vkhandler/vk_handler_test.go @@ -18,14 +18,14 @@ const chainID = 443 // generateRandomUserKeys - // generates a random user private key and a random viewing key private key and returns the user private key, // the viewing key private key, the userID and the user address -func generateRandomUserKeys() (*ecdsa.PrivateKey, *ecdsa.PrivateKey, string, gethcommon.Address) { +func generateRandomUserKeys() (*ecdsa.PrivateKey, *ecdsa.PrivateKey, []byte, gethcommon.Address) { userPrivKey, err := crypto.GenerateKey() // user private key if err != nil { - return nil, nil, "", gethcommon.Address{} + return nil, nil, nil, gethcommon.Address{} } vkPrivKey, _ := crypto.GenerateKey() // viewingkey generated in the gateway if err != nil { - return nil, nil, "", gethcommon.Address{} + return nil, nil, nil, gethcommon.Address{} } // get the address from userPrivKey @@ -33,7 +33,7 @@ func generateRandomUserKeys() (*ecdsa.PrivateKey, *ecdsa.PrivateKey, string, get // get userID from viewingKey public key vkPubKeyBytes := crypto.CompressPubkey(ecies.ImportECDSAPublic(&vkPrivKey.PublicKey).ExportECDSA()) - userID := viewingkey.CalculateUserIDHex(vkPubKeyBytes) + userID := viewingkey.CalculateUserID(vkPubKeyBytes) return userPrivKey, vkPrivKey, userID, userAddress } diff --git a/lib/gethfork/rpc/types.go b/lib/gethfork/rpc/types.go index 34a1451dea..4491c8a4c8 100644 --- a/lib/gethfork/rpc/types.go +++ b/lib/gethfork/rpc/types.go @@ -112,19 +112,19 @@ func (bn *BlockNumber) UnmarshalJSON(data []byte) error { } // Int64 returns the block number as int64. -func (bn BlockNumber) Int64() int64 { - return (int64)(bn) +func (bn *BlockNumber) Int64() int64 { + return (int64)(*bn) } // MarshalText implements encoding.TextMarshaler. It marshals: // - "safe", "finalized", "latest", "earliest" or "pending" as strings // - other numbers as hex -func (bn BlockNumber) MarshalText() ([]byte, error) { +func (bn *BlockNumber) MarshalText() ([]byte, error) { return []byte(bn.String()), nil } -func (bn BlockNumber) String() string { - switch bn { +func (bn *BlockNumber) String() string { + switch *bn { case EarliestBlockNumber: return "earliest" case LatestBlockNumber: @@ -136,10 +136,10 @@ func (bn BlockNumber) String() string { case SafeBlockNumber: return "safe" default: - if bn < 0 { + if *bn < 0 { return fmt.Sprintf("", bn) } - return hexutil.Uint64(bn).String() + return hexutil.Uint64(*bn).String() } } diff --git a/tools/walletextension/rpcapi/utils.go b/tools/walletextension/rpcapi/utils.go index c9ca272c1c..a70dead912 100644 --- a/tools/walletextension/rpcapi/utils.go +++ b/tools/walletextension/rpcapi/utils.go @@ -61,7 +61,7 @@ func UnauthenticatedTenRPCCall[R any](ctx context.Context, w *Services, cfg *Cac err = unauthedRPC.CallContext(ctx, &resp, method, args...) return resp, err }) - defer audit(w, "RPC call. method=%s args=%v result=%s error=%s time=%d", method, args, res, err, time.Since(requestStartTime).Milliseconds()) + audit(w, "RPC call. method=%s args=%v result=%s error=%s time=%d", method, args, res, err, time.Since(requestStartTime).Milliseconds()) return res, err } diff --git a/tools/walletextension/rpcapi/wallet_extension.go b/tools/walletextension/rpcapi/wallet_extension.go index a7e5055f35..520a97c9b7 100644 --- a/tools/walletextension/rpcapi/wallet_extension.go +++ b/tools/walletextension/rpcapi/wallet_extension.go @@ -4,13 +4,10 @@ import ( "bytes" "encoding/hex" "fmt" - "math/big" "time" "github.com/status-im/keycard-go/hexutils" - "github.com/ten-protocol/go-ten/go/wallet" - "github.com/ten-protocol/go-ten/tools/walletextension/cache" "github.com/ten-protocol/go-ten/go/obsclient" @@ -39,7 +36,6 @@ type Services struct { tenClient *obsclient.ObsClient Cache cache.Cache Config *common.Config - DefaultUser []byte } func NewServices(hostAddrHTTP string, hostAddrWS string, storage storage.Storage, stopControl *stopcontrol.StopControl, version string, logger gethlog.Logger, config *common.Config) *Services { @@ -56,8 +52,6 @@ func NewServices(hostAddrHTTP string, hostAddrWS string, storage storage.Storage panic(err) } - userID := addDefaultUser(config, logger, storage) - return &Services{ HostAddrHTTP: hostAddrHTTP, HostAddrWS: hostAddrWS, @@ -70,35 +64,7 @@ func NewServices(hostAddrHTTP string, hostAddrWS string, storage storage.Storage tenClient: newTenClient, Cache: newGatewayCache, Config: config, - DefaultUser: userID, - } -} - -func addDefaultUser(config *common.Config, logger gethlog.Logger, storage storage.Storage) []byte { - userAccountKey, err := crypto.GenerateKey() - if err != nil { - panic(fmt.Errorf("error generating default user key")) - } - - wallet := wallet.NewInMemoryWalletFromPK(big.NewInt(int64(config.TenChainID)), userAccountKey, logger) - vk, err := viewingkey.GenerateViewingKeyForWallet(wallet) - if err != nil { - panic(err) - } - - // create UserID and store it in the database with the private key - userID := viewingkey.CalculateUserID(common.PrivateKeyToCompressedPubKey(vk.PrivateKey)) - - err = storage.AddUser(userID, crypto.FromECDSA(vk.PrivateKey.ExportECDSA())) - if err != nil { - panic(fmt.Errorf("error saving default user")) - } - - err = storage.AddAccount(userID, vk.Account.Bytes(), vk.SignatureWithAccountKey, viewingkey.Legacy) - if err != nil { - panic(fmt.Errorf("error saving account %s for default user %s", vk.Account.Hex(), userID)) } - return userID } // IsStopping returns whether the WE is stopping @@ -212,7 +178,7 @@ func (w *Services) AddAddressToUser(userID []byte, address string, signature []b return err } - audit(w, "Storing new address for user: %s, address: %s, duration: %d ", hexutils.BytesToHex(userID), address, time.Now().Sub(requestStartTime).Milliseconds()) + audit(w, "Storing new address for user: %s, address: %s, duration: %d ", hexutils.BytesToHex(userID), address, time.Since(requestStartTime).Milliseconds()) return nil } From 63d300ad75266443c0e4f76c5db4d54e066887ea Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Fri, 22 Mar 2024 16:29:45 +0000 Subject: [PATCH 39/65] cleanup --- go/enclave/rpc/EstimateGas.go | 72 +++++++++---------- integration/obscurogateway/tengateway_test.go | 2 +- .../walletextension/rpcapi/blockchain_api.go | 9 +-- tools/walletextension/rpcapi/ethereum_api.go | 4 +- .../walletextension/rpcapi/transaction_api.go | 1 - 5 files changed, 37 insertions(+), 51 deletions(-) diff --git a/go/enclave/rpc/EstimateGas.go b/go/enclave/rpc/EstimateGas.go index 2b7140147d..f553742eb8 100644 --- a/go/enclave/rpc/EstimateGas.go +++ b/go/enclave/rpc/EstimateGas.go @@ -5,8 +5,6 @@ import ( "fmt" "math/big" - "github.com/ten-protocol/go-ten/go/common/log" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" @@ -55,7 +53,6 @@ func EstimateGasValidate(reqParams []any, builder *CallBuilder[CallParamsWithBlo } func EstimateGasExecute(builder *CallBuilder[CallParamsWithBlock, hexutil.Uint64], rpc *EncryptionManager) error { - // allow unauthenticated gas estimation err := authenticateFrom(builder.VK, builder.From) if err != nil { builder.Err = err @@ -110,12 +107,10 @@ func EstimateGasExecute(builder *CallBuilder[CallParamsWithBlock, hexutil.Uint64 err = fmt.Errorf(string(evmErr)) } builder.Err = err - rpc.logger.Info("Estimate gas error", log.ErrKey, err) return nil } totalGasEstimate := hexutil.Uint64(publishingGas.Uint64() + uint64(executionGasEstimate)) - rpc.logger.Info("Estimate gas:", "value", totalGasEstimate) builder.ReturnValue = &totalGasEstimate return nil } @@ -153,45 +148,44 @@ func (rpc *EncryptionManager) doEstimateGas(args *gethapi.TransactionArgs, blkNu */ hi = rpc.config.GasLocalExecutionCapFlag } - /* - // Normalize the max fee per gas the call is willing to spend. - var feeCap *big.Int - if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { - return 0, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") - } else if args.GasPrice != nil { - feeCap = args.GasPrice.ToInt() - } else if args.MaxFeePerGas != nil { - feeCap = args.MaxFeePerGas.ToInt() - } else { - feeCap = gethcommon.Big0 + // Normalize the max fee per gas the call is willing to spend. + var feeCap *big.Int + if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { + return 0, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") + } else if args.GasPrice != nil { + feeCap = args.GasPrice.ToInt() + } else if args.MaxFeePerGas != nil { + feeCap = args.MaxFeePerGas.ToInt() + } else { + feeCap = gethcommon.Big0 + } + // Recap the highest gas limit with account's available balance. + if feeCap.BitLen() != 0 { //nolint:nestif + balance, err := rpc.chain.GetBalanceAtBlock(*args.From, blkNumber) + if err != nil { + return 0, fmt.Errorf("unable to fetch account balance - %w", err) } - // Recap the highest gas limit with account's available balance. - if feeCap.BitLen() != 0 { //nolint:nestif - balance, err := rpc.chain.GetBalanceAtBlock(*args.From, blkNumber) - if err != nil { - return 0, fmt.Errorf("unable to fetch account balance - %w", err) - } - available := new(big.Int).Set(balance.ToInt()) - if args.Value != nil { - if args.Value.ToInt().Cmp(available) >= 0 { - return 0, errors.New("insufficient funds for transfer") - } - available.Sub(available, args.Value.ToInt()) + available := new(big.Int).Set(balance.ToInt()) + if args.Value != nil { + if args.Value.ToInt().Cmp(available) >= 0 { + return 0, errors.New("insufficient funds for transfer") } - allowance := new(big.Int).Div(available, feeCap) + available.Sub(available, args.Value.ToInt()) + } + allowance := new(big.Int).Div(available, feeCap) - // If the allowance is larger than maximum uint64, skip checking - if allowance.IsUint64() && hi > allowance.Uint64() { - transfer := args.Value - if transfer == nil { - transfer = new(hexutil.Big) - } - rpc.logger.Debug("Gas estimation capped by limited funds", "original", hi, "balance", balance, - "sent", transfer.ToInt(), "maxFeePerGas", feeCap, "fundable", allowance) - hi = allowance.Uint64() + // If the allowance is larger than maximum uint64, skip checking + if allowance.IsUint64() && hi > allowance.Uint64() { + transfer := args.Value + if transfer == nil { + transfer = new(hexutil.Big) } - }*/ + rpc.logger.Debug("Gas estimation capped by limited funds", "original", hi, "balance", balance, + "sent", transfer.ToInt(), "maxFeePerGas", feeCap, "fundable", allowance) + hi = allowance.Uint64() + } + } // Recap the highest gas allowance with specified gascap. if gasCap != 0 && hi > gasCap { rpc.logger.Debug("Caller gas above allowance, capping", "requested", hi, "cap", gasCap) diff --git a/integration/obscurogateway/tengateway_test.go b/integration/obscurogateway/tengateway_test.go index dab772f299..02f901ccf2 100644 --- a/integration/obscurogateway/tengateway_test.go +++ b/integration/obscurogateway/tengateway_test.go @@ -732,7 +732,7 @@ func waitServerIsReady(serverAddr string) error { func getFeeAndGas(client *ethclient.Client, wallet wallet.Wallet, legacyTx *types.LegacyTx) error { tx := types.NewTx(legacyTx) - history, err := client.FeeHistory(context.Background(), 1, nil, []float64{}) + history, err := client.FeeHistory(context.Background(), 1, nil, nil) if err != nil || len(history.BaseFee) == 0 { return err } diff --git a/tools/walletextension/rpcapi/blockchain_api.go b/tools/walletextension/rpcapi/blockchain_api.go index 19f3353b76..0037dac96c 100644 --- a/tools/walletextension/rpcapi/blockchain_api.go +++ b/tools/walletextension/rpcapi/blockchain_api.go @@ -1,10 +1,8 @@ package rpcapi -//goland:noinspection ALL import ( "context" "encoding/json" - "math/big" "time" "github.com/ethereum/go-ethereum/common" @@ -23,10 +21,8 @@ func NewBlockChainAPI(we *Services) *BlockChainAPI { } func (api *BlockChainAPI) ChainId() *hexutil.Big { //nolint:stylecheck - // chainid, _ := UnauthenticatedTenRPCCall[hexutil.Big](nil, api.we, &CacheCfg{TTL: longCacheTTL}, "eth_chainId") - // return chainid - chainID := big.NewInt(int64(api.we.Config.TenChainID)) - return (*hexutil.Big)(chainID) + chainID, _ := UnauthenticatedTenRPCCall[hexutil.Big](context.Background(), api.we, &CacheCfg{TTL: longCacheTTL}, "eth_chainId") + return chainID } func (api *BlockChainAPI) BlockNumber() hexutil.Uint64 { @@ -38,7 +34,6 @@ func (api *BlockChainAPI) BlockNumber() hexutil.Uint64 { } func (api *BlockChainAPI) GetBalance(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Big, error) { - // todo - how do you handle getBalance for contracts return ExecAuthRPC[hexutil.Big]( ctx, api.we, diff --git a/tools/walletextension/rpcapi/ethereum_api.go b/tools/walletextension/rpcapi/ethereum_api.go index bdc74f5b8e..65fecbac0e 100644 --- a/tools/walletextension/rpcapi/ethereum_api.go +++ b/tools/walletextension/rpcapi/ethereum_api.go @@ -2,7 +2,6 @@ package rpcapi import ( "context" - "math/big" "time" "github.com/ethereum/go-ethereum/common/hexutil" @@ -20,8 +19,7 @@ func NewEthereumAPI(we *Services, } func (api *EthereumAPI) GasPrice(ctx context.Context) (*hexutil.Big, error) { - return (*hexutil.Big)(big.NewInt(int64(0x3b9aca00))), nil - // return UnauthenticatedTenRPCCall[hexutil.Big](ctx, api.we, &CacheCfg{TTL: shortCacheTTL}, "eth_gasPrice") + return UnauthenticatedTenRPCCall[hexutil.Big](ctx, api.we, &CacheCfg{TTL: shortCacheTTL}, "eth_gasPrice") } func (api *EthereumAPI) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, error) { diff --git a/tools/walletextension/rpcapi/transaction_api.go b/tools/walletextension/rpcapi/transaction_api.go index dcfd3512fc..95f206cc68 100644 --- a/tools/walletextension/rpcapi/transaction_api.go +++ b/tools/walletextension/rpcapi/transaction_api.go @@ -1,6 +1,5 @@ package rpcapi -//goland:noinspection ALL import ( "context" "encoding/json" From 50d694486b6cfa5bd83f5f715d3bdc8e1b93c8f7 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Fri, 22 Mar 2024 16:45:51 +0000 Subject: [PATCH 40/65] fix --- lib/gethfork/rpc/types.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/gethfork/rpc/types.go b/lib/gethfork/rpc/types.go index 4491c8a4c8..34a1451dea 100644 --- a/lib/gethfork/rpc/types.go +++ b/lib/gethfork/rpc/types.go @@ -112,19 +112,19 @@ func (bn *BlockNumber) UnmarshalJSON(data []byte) error { } // Int64 returns the block number as int64. -func (bn *BlockNumber) Int64() int64 { - return (int64)(*bn) +func (bn BlockNumber) Int64() int64 { + return (int64)(bn) } // MarshalText implements encoding.TextMarshaler. It marshals: // - "safe", "finalized", "latest", "earliest" or "pending" as strings // - other numbers as hex -func (bn *BlockNumber) MarshalText() ([]byte, error) { +func (bn BlockNumber) MarshalText() ([]byte, error) { return []byte(bn.String()), nil } -func (bn *BlockNumber) String() string { - switch *bn { +func (bn BlockNumber) String() string { + switch bn { case EarliestBlockNumber: return "earliest" case LatestBlockNumber: @@ -136,10 +136,10 @@ func (bn *BlockNumber) String() string { case SafeBlockNumber: return "safe" default: - if *bn < 0 { + if bn < 0 { return fmt.Sprintf("", bn) } - return hexutil.Uint64(*bn).String() + return hexutil.Uint64(bn).String() } } From 69d1506ebb9710cea379f5cfc31e18d91a7139b7 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Fri, 22 Mar 2024 17:00:36 +0000 Subject: [PATCH 41/65] fix --- go/obsclient/authclient.go | 4 ++-- integration/simulation/validate_chain.go | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/go/obsclient/authclient.go b/go/obsclient/authclient.go index f417e3443f..e484d5a3b8 100644 --- a/go/obsclient/authclient.go +++ b/go/obsclient/authclient.go @@ -196,9 +196,9 @@ func (ac *AuthObsClient) SubscribeFilterLogs(ctx context.Context, filterCriteria return ac.rpcClient.Subscribe(ctx, nil, rpc.SubscribeNamespace, ch, rpc.SubscriptionTypeLogs, filterCriteria) } -func (ac *AuthObsClient) GetLogs(ctx context.Context, filterCriteria common.FilterCriteriaJSON) ([]*types.Log, error) { +func (ac *AuthObsClient) GetLogs(ctx context.Context, filterCriteria filters.FilterCriteria) ([]*types.Log, error) { var result responses.LogsType - err := ac.rpcClient.CallContext(ctx, &result, rpc.GetLogs, filterCriteria, ac.account) + err := ac.rpcClient.CallContext(ctx, &result, rpc.GetLogs, filterCriteria) if err != nil { return nil, err } diff --git a/integration/simulation/validate_chain.go b/integration/simulation/validate_chain.go index c7010f1747..e4e93ccfb0 100644 --- a/integration/simulation/validate_chain.go +++ b/integration/simulation/validate_chain.go @@ -10,6 +10,8 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/eth/filters" + "github.com/ten-protocol/go-ten/contracts/generated/MessageBus" testcommon "github.com/ten-protocol/go-ten/integration/common" @@ -627,7 +629,7 @@ func checkSubscribedLogs(t *testing.T, owner string, channel chan common.IDAndLo func checkSnapshotLogs(t *testing.T, client *obsclient.AuthObsClient) int { // To exercise the filtering mechanism, we get a snapshot for HOC events only, ignoring POC events. - hocFilter := common.FilterCriteriaJSON{ + hocFilter := filters.FilterCriteria{ Addresses: []gethcommon.Address{gethcommon.HexToAddress("0x" + testcommon.HOCAddr)}, } logs, err := client.GetLogs(context.Background(), hocFilter) From 8d9a2143ebe903f80555ab59af70431ddfc2d73c Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Fri, 22 Mar 2024 17:38:23 +0000 Subject: [PATCH 42/65] fix --- .../walletextension/rpcapi/blockchain_api.go | 27 +++++++--------- tools/walletextension/rpcapi/debug_api.go | 29 +++++++---------- tools/walletextension/rpcapi/ethereum_api.go | 9 ++---- tools/walletextension/rpcapi/filter_api.go | 32 +++++++------------ tools/walletextension/rpcapi/from_tx_args.go | 15 ++------- tools/walletextension/rpcapi/net_api.go | 25 --------------- .../walletextension/rpcapi/transaction_api.go | 21 ++++++------ tools/walletextension/rpcapi/txpool_api.go | 26 +++++---------- tools/walletextension/rpcapi/utils.go | 2 ++ .../walletextension_container.go | 1 - 10 files changed, 59 insertions(+), 128 deletions(-) delete mode 100644 tools/walletextension/rpcapi/net_api.go diff --git a/tools/walletextension/rpcapi/blockchain_api.go b/tools/walletextension/rpcapi/blockchain_api.go index 0037dac96c..4c016e97e2 100644 --- a/tools/walletextension/rpcapi/blockchain_api.go +++ b/tools/walletextension/rpcapi/blockchain_api.go @@ -5,6 +5,8 @@ import ( "encoding/json" "time" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ten-protocol/go-ten/go/common/gethapi" @@ -69,12 +71,10 @@ type StorageResult struct { Proof []string `json:"proof"` } -/* - func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNrOrHash rpc.BlockNumberOrHash) (*AccountResult, error) { - // not implemented - return nil, nil - } -*/ +func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNrOrHash rpc.BlockNumberOrHash) (*AccountResult, error) { + return nil, rpcNotImplemented +} + func (api *BlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (map[string]interface{}, error) { resp, err := UnauthenticatedTenRPCCall[map[string]interface{}](ctx, api.we, &CacheCfg{TTLCallback: func() time.Duration { return cacheTTLBlockNumber(number) @@ -158,12 +158,10 @@ func (api *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Addre return *resp, err } -/* - func (s *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) { - // not implemented - return nil, nil - } -*/ +func (s *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) { + return nil, rpcNotImplemented +} + type OverrideAccount struct { Nonce *hexutil.Uint64 `json:"nonce"` Code *hexutil.Bytes `json:"code"` @@ -252,7 +250,6 @@ func cloneArgs(args gethapi.TransactionArgs) gethapi.TransactionArgs { return argsClone } -/* type accessListResult struct { Accesslist *types.AccessList `json:"accessList"` Error string `json:"error,omitempty"` @@ -260,7 +257,5 @@ type accessListResult struct { } func (s *BlockChainAPI) CreateAccessList(ctx context.Context, args gethapi.TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash) (*accessListResult, error) { - // not implemented - return nil, nil + return nil, rpcNotImplemented } -*/ diff --git a/tools/walletextension/rpcapi/debug_api.go b/tools/walletextension/rpcapi/debug_api.go index 3dcc2d0dcd..fc0452611d 100644 --- a/tools/walletextension/rpcapi/debug_api.go +++ b/tools/walletextension/rpcapi/debug_api.go @@ -2,9 +2,10 @@ package rpcapi import ( "context" - "fmt" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ten-protocol/go-ten/lib/gethfork/rpc" ) type DebugAPI struct { @@ -15,48 +16,40 @@ func NewDebugAPI(we *Services) *DebugAPI { return &DebugAPI{we} } -/*func (api *DebugAPI) GetRawHeader(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) { - // not implemented - return nil, nil +func (api *DebugAPI) GetRawHeader(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) { + return nil, rpcNotImplemented } func (api *DebugAPI) GetRawBlock(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) { - // not implemented - return nil, nil + return nil, rpcNotImplemented } func (api *DebugAPI) GetRawReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]hexutil.Bytes, error) { - // not implemented - return nil, nil + return nil, rpcNotImplemented } func (s *DebugAPI) GetRawTransaction(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) { - // not implemented - return nil, nil + return nil, rpcNotImplemented } func (api *DebugAPI) PrintBlock(ctx context.Context, number uint64) (string, error) { - // not implemented - return "", nil + return "", rpcNotImplemented } func (api *DebugAPI) ChaindbProperty(property string) (string, error) { - // not implemented - return "", nil + return "", rpcNotImplemented } func (api *DebugAPI) ChaindbCompact() error { - // not implemented - return nil + return rpcNotImplemented } func (api *DebugAPI) SetHead(number hexutil.Uint64) { // not implemented } -*/ // EventLogRelevancy - specific to Ten - todo func (api *DebugAPI) EventLogRelevancy(_ context.Context, _ common.Hash) (interface{}, error) { // todo - return nil, fmt.Errorf("not implemented") + return nil, rpcNotImplemented } diff --git a/tools/walletextension/rpcapi/ethereum_api.go b/tools/walletextension/rpcapi/ethereum_api.go index 65fecbac0e..742c5c71c9 100644 --- a/tools/walletextension/rpcapi/ethereum_api.go +++ b/tools/walletextension/rpcapi/ethereum_api.go @@ -23,8 +23,7 @@ func (api *EthereumAPI) GasPrice(ctx context.Context) (*hexutil.Big, error) { } func (api *EthereumAPI) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, error) { - // todo - return UnauthenticatedTenRPCCall[hexutil.Big](ctx, api.we, nil, "eth_maxPriorityFeePerGas") + return UnauthenticatedTenRPCCall[hexutil.Big](ctx, api.we, &CacheCfg{TTL: shortCacheTTL}, "eth_maxPriorityFeePerGas") } type FeeHistoryResult struct { @@ -48,8 +47,6 @@ func (api *EthereumAPI) FeeHistory(ctx context.Context, blockCount math.HexOrDec ) } -/*func (api *EthereumAPI) Syncing() (interface{}, error) { - // todo - return nil, nil +func (api *EthereumAPI) Syncing() (interface{}, error) { + return nil, rpcNotImplemented } -*/ diff --git a/tools/walletextension/rpcapi/filter_api.go b/tools/walletextension/rpcapi/filter_api.go index 218c9059d4..195e10f3ce 100644 --- a/tools/walletextension/rpcapi/filter_api.go +++ b/tools/walletextension/rpcapi/filter_api.go @@ -29,23 +29,19 @@ func (api *FilterAPI) NewPendingTransactionFilter(_ *bool) rpc.ID { return "not supported" } -/*func (api *FilterAPI) NewPendingTransactions(ctx context.Context, fullTx *bool) (*rpc.Subscription, error) { - // not supported - return nil, nil +func (api *FilterAPI) NewPendingTransactions(ctx context.Context, fullTx *bool) (*rpc.Subscription, error) { + return nil, fmt.Errorf("not supported") } -*/ func (api *FilterAPI) NewBlockFilter() rpc.ID { // not implemented return "" } -/* - func (api *FilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, error) { - // not implemented - return nil, nil - } -*/ +func (api *FilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, error) { + return nil, rpcNotImplemented +} + func (api *FilterAPI) Logs(ctx context.Context, crit filters.FilterCriteria) (*rpc.Subscription, error) { subNotifier, user, err := getUserAndNotifier(ctx, api) if err != nil { @@ -189,12 +185,10 @@ func handleUnsubscribe(connectionSub *rpc.Subscription, backendSubscriptions []* } } -/* - func (api *FilterAPI) NewFilter(crit filters.FilterCriteria) (rpc.ID, error) { - // not implemented - return "", nil - } -*/ +func (api *FilterAPI) NewFilter(crit filters.FilterCriteria) (rpc.ID, error) { + return rpc.NewID(), rpcNotImplemented +} + func (api *FilterAPI) GetLogs(ctx context.Context, crit filters.FilterCriteria) ([]*types.Log, error) { logs, err := ExecAuthRPC[[]*types.Log]( ctx, @@ -224,7 +218,7 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit filters.FilterCriteria) return nil, err } -/*func (api *FilterAPI) UninstallFilter(id rpc.ID) bool { +func (api *FilterAPI) UninstallFilter(id rpc.ID) bool { // not implemented return false } @@ -241,7 +235,5 @@ func (api *FilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Lo } func (api *FilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { - // not implemented - return nil, nil + return nil, rpcNotImplemented } -*/ diff --git a/tools/walletextension/rpcapi/from_tx_args.go b/tools/walletextension/rpcapi/from_tx_args.go index 553a7bfffe..996e0b667d 100644 --- a/tools/walletextension/rpcapi/from_tx_args.go +++ b/tools/walletextension/rpcapi/from_tx_args.go @@ -9,12 +9,6 @@ import ( ) func searchFromAndData(possibleAddresses []*common.Address, args gethapi.TransactionArgs) *common.Address { - addressesMap := toMap(possibleAddresses) - - // todo - is this correct - //if args.From != nil && addressesMap[*args.From] != nil { - // return args.From - //} if args.From != nil { return args.From } @@ -23,12 +17,9 @@ func searchFromAndData(possibleAddresses []*common.Address, args gethapi.Transac return nil } - // the "from" field is not mandatory, so we try to find the address in the data field - if args.From == nil { - return searchDataFieldForAccount(addressesMap, *args.Data) - } - - return nil + // since the "from" field is not mandatory, we try to find a matching address in the data field + addressesMap := toMap(possibleAddresses) + return searchDataFieldForAccount(addressesMap, *args.Data) } func searchDataFieldForAccount(addressesMap map[common.Address]*common.Address, data []byte) *common.Address { diff --git a/tools/walletextension/rpcapi/net_api.go b/tools/walletextension/rpcapi/net_api.go deleted file mode 100644 index cb3fa5a69e..0000000000 --- a/tools/walletextension/rpcapi/net_api.go +++ /dev/null @@ -1,25 +0,0 @@ -package rpcapi - -/* -type NetAPI struct{} - -// NewNetAPI creates a new net API instance. -func NewNetAPI() *NetAPI { - return &NetAPI{} -} - -// Listening returns an indication if the node is listening for network connections. -func (s *NetAPI) Listening() bool { - return true // always listening -} - -// PeerCount returns the number of connected peers -func (s *NetAPI) PeerCount() hexutil.Uint { - return 0 -} - -// Version returns the current ethereum protocol version. -func (s *NetAPI) Version() string { - return "1" -} -*/ diff --git a/tools/walletextension/rpcapi/transaction_api.go b/tools/walletextension/rpcapi/transaction_api.go index 95f206cc68..537bdbc8f4 100644 --- a/tools/walletextension/rpcapi/transaction_api.go +++ b/tools/walletextension/rpcapi/transaction_api.go @@ -40,7 +40,7 @@ func (s *TransactionAPI) GetBlockTransactionCountByHash(ctx context.Context, blo return count } -/*func (s *TransactionAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNr gethrpc.BlockNumber, index hexutil.Uint) *rpc.RpcTransaction { +func (s *TransactionAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNr gethrpc.BlockNumber, index hexutil.Uint) *rpc.RpcTransaction { // not implemented return nil } @@ -58,7 +58,7 @@ func (s *TransactionAPI) GetRawTransactionByBlockNumberAndIndex(ctx context.Cont func (s *TransactionAPI) GetRawTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) hexutil.Bytes { // not implemented return nil -}*/ +} func (s *TransactionAPI) GetTransactionCount(ctx context.Context, address common.Address, blockNrOrHash gethrpc.BlockNumberOrHash) (*hexutil.Uint64, error) { return ExecAuthRPC[hexutil.Uint64](ctx, s.we, &ExecCfg{account: &address}, "eth_getTransactionCount", address, blockNrOrHash) @@ -113,10 +113,9 @@ type SignTransactionResult struct { Tx *types.Transaction `json:"tx"` } -/*func (s *TransactionAPI) FillTransaction(ctx context.Context, args gethapi.TransactionArgs) (*SignTransactionResult, error) { - // not implemented - return nil, nil -}*/ +func (s *TransactionAPI) FillTransaction(ctx context.Context, args gethapi.TransactionArgs) (*SignTransactionResult, error) { + return nil, rpcNotImplemented +} func (s *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) { txRec, err := ExecAuthRPC[common.Hash](ctx, s.we, &ExecCfg{tryAll: true}, "eth_sendRawTransaction", input) @@ -133,12 +132,10 @@ func (s *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.B return *txRec, err } -/* - func (s *TransactionAPI) PendingTransactions() ([]*rpc.RpcTransaction, error) { - // not implemented - return nil, nil - } -*/ +func (s *TransactionAPI) PendingTransactions() ([]*rpc.RpcTransaction, error) { + return nil, rpcNotImplemented +} + func (s *TransactionAPI) Resend(ctx context.Context, sendArgs gethapi.TransactionArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error) { txRec, err := ExecAuthRPC[common.Hash](ctx, s.we, &ExecCfg{account: sendArgs.From}, "eth_resend", sendArgs, gasPrice, gasLimit) if txRec != nil { diff --git a/tools/walletextension/rpcapi/txpool_api.go b/tools/walletextension/rpcapi/txpool_api.go index 191b1c9aab..16ec3f3b76 100644 --- a/tools/walletextension/rpcapi/txpool_api.go +++ b/tools/walletextension/rpcapi/txpool_api.go @@ -6,7 +6,6 @@ import ( rpc2 "github.com/ten-protocol/go-ten/go/enclave/rpc" ) -// todo type TxPoolAPI struct { we *Services } @@ -16,30 +15,21 @@ func NewTxPoolAPI(we *Services) *TxPoolAPI { } func (s *TxPoolAPI) Content() map[string]map[string]map[string]*rpc2.RpcTransaction { - content := map[string]map[string]map[string]*rpc2.RpcTransaction{ - "pending": make(map[string]map[string]*rpc2.RpcTransaction), - "queued": make(map[string]map[string]*rpc2.RpcTransaction), - } - return content + // not implemented + return nil } func (s *TxPoolAPI) ContentFrom(_ common.Address) map[string]map[string]*rpc2.RpcTransaction { - content := make(map[string]map[string]*rpc2.RpcTransaction, 2) - return content + // not implemented + return nil } func (s *TxPoolAPI) Status() map[string]hexutil.Uint { - pending, queue := 0, 0 - return map[string]hexutil.Uint{ - "pending": hexutil.Uint(pending), - "queued": hexutil.Uint(queue), - } + // not implemented + return nil } func (s *TxPoolAPI) Inspect() map[string]map[string]map[string]string { - content := map[string]map[string]map[string]string{ - "pending": make(map[string]map[string]string), - "queued": make(map[string]map[string]string), - } - return content + // not implemented + return nil } diff --git a/tools/walletextension/rpcapi/utils.go b/tools/walletextension/rpcapi/utils.go index a70dead912..c6273489c7 100644 --- a/tools/walletextension/rpcapi/utils.go +++ b/tools/walletextension/rpcapi/utils.go @@ -28,6 +28,8 @@ const ( shortCacheTTL = 1 * time.Second ) +var rpcNotImplemented = fmt.Errorf("rpc endpoint not implemented") + type ExecCfg struct { account *common.Address computeFromCallback func(user *GWUser) *common.Address diff --git a/tools/walletextension/walletextension_container.go b/tools/walletextension/walletextension_container.go index fadb634d18..0692c521ea 100644 --- a/tools/walletextension/walletextension_container.go +++ b/tools/walletextension/walletextension_container.go @@ -57,7 +57,6 @@ func NewContainerFromConfig(config wecommon.Config, logger gethlog.Logger) *Cont rpcServer.RegisterRoutes(httpapi.NewHTTPRoutes(walletExt)) // register all RPC endpoints exposed by a typical Geth node - // todo - discover what else we need to register here rpcServer.RegisterAPIs([]gethrpc.API{ { Namespace: "eth", From 1cc865fad96fa63ca93ab37794b3683350154724 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Fri, 22 Mar 2024 19:19:30 +0000 Subject: [PATCH 43/65] fix --- integration/obscurogateway/tengateway_test.go | 1 + lib/gethfork/rpc/service.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/integration/obscurogateway/tengateway_test.go b/integration/obscurogateway/tengateway_test.go index 02f901ccf2..609e3d6d6b 100644 --- a/integration/obscurogateway/tengateway_test.go +++ b/integration/obscurogateway/tengateway_test.go @@ -413,6 +413,7 @@ func testErrorHandling(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { // make requests to geth for comparison for _, req := range []string{ + `{"jsonrpc":"2.0","method":"eth_blockNumber","params": [],"id":1}`, `{"jsonrpc":"2.0","method":"eth_gasPrice","params": [],"id":1}`, `{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params": ["latest", false],"id":1}`, `{"jsonrpc":"2.0","method":"eth_feeHistory","params":[1, "latest", [50]],"id":1}`, diff --git a/lib/gethfork/rpc/service.go b/lib/gethfork/rpc/service.go index 38f16bb26f..f8e90064fd 100644 --- a/lib/gethfork/rpc/service.go +++ b/lib/gethfork/rpc/service.go @@ -198,7 +198,7 @@ func (c *callback) call(ctx context.Context, method string, args []reflect.Value buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] log.Error("RPC method " + method + " crashed: " + fmt.Sprintf("%v\n%s", err, buf)) - errRes = &internalServerError{errcodePanic, "method handler crashed"} + errRes = &internalServerError{errcodePanic, fmt.Sprintf("method handler crashed:%v\n%s ", err, buf)} } }() // Run the callback. From 3cb73ec915197118bcf32f6bde4ba1a6a322e1f0 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Fri, 22 Mar 2024 19:57:20 +0000 Subject: [PATCH 44/65] fix --- integration/obscurogateway/tengateway_test.go | 2 ++ tools/walletextension/rpcapi/utils.go | 19 +++++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/integration/obscurogateway/tengateway_test.go b/integration/obscurogateway/tengateway_test.go index 609e3d6d6b..04a23f8707 100644 --- a/integration/obscurogateway/tengateway_test.go +++ b/integration/obscurogateway/tengateway_test.go @@ -414,7 +414,9 @@ func testErrorHandling(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { for _, req := range []string{ `{"jsonrpc":"2.0","method":"eth_blockNumber","params": [],"id":1}`, + `{"jsonrpc":"2.0","method":"eth_blockNumber","params": [],"id":1}`, // test caching `{"jsonrpc":"2.0","method":"eth_gasPrice","params": [],"id":1}`, + `{"jsonrpc":"2.0","method":"eth_gasPrice","params": [],"id":1}`, // test caching `{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params": ["latest", false],"id":1}`, `{"jsonrpc":"2.0","method":"eth_feeHistory","params":[1, "latest", [50]],"id":1}`, `{"jsonrpc":"2.0","method":"eth_getBalance","params":["0xA58C60cc047592DE97BF1E8d2f225Fc5D959De77", "latest"],"id":1}`, diff --git a/tools/walletextension/rpcapi/utils.go b/tools/walletextension/rpcapi/utils.go index c6273489c7..eb8d6aea22 100644 --- a/tools/walletextension/rpcapi/utils.go +++ b/tools/walletextension/rpcapi/utils.go @@ -50,7 +50,11 @@ type CacheCfg struct { func UnauthenticatedTenRPCCall[R any](ctx context.Context, w *Services, cfg *CacheCfg, method string, args ...any) (*R, error) { audit(w, "RPC start method=%s args=%v", method, args) requestStartTime := time.Now() - res, err := withCache(w.Cache, cfg, args, func() (*R, error) { + cacheKey := make([]any, 0) + cacheKey = append(cacheKey, method) + cacheKey = append(cacheKey, args...) + + res, err := withCache(w.Cache, cfg, cacheKey, func() (*R, error) { var resp *R unauthedRPC, err := w.UnauthenticatedClient() if err != nil { @@ -58,9 +62,9 @@ func UnauthenticatedTenRPCCall[R any](ctx context.Context, w *Services, cfg *Cac } if ctx == nil { err = unauthedRPC.Call(&resp, method, args...) - return resp, err + } else { + err = unauthedRPC.CallContext(ctx, &resp, method, args...) } - err = unauthedRPC.CallContext(ctx, &resp, method, args...) return resp, err }) audit(w, "RPC call. method=%s args=%v result=%s error=%s time=%d", method, args, res, err, time.Since(requestStartTime).Milliseconds()) @@ -183,7 +187,7 @@ func generateCacheKey(params []any) []byte { return hasher.Sum(nil) } -func withCache[R any](cache cache.Cache, cfg *CacheCfg, args []any, onCacheMiss func() (*R, error)) (*R, error) { +func withCache[R any](cache cache.Cache, cfg *CacheCfg, cacheArgs []any, onCacheMiss func() (*R, error)) (*R, error) { if cfg == nil { return onCacheMiss() } @@ -196,10 +200,13 @@ func withCache[R any](cache cache.Cache, cfg *CacheCfg, args []any, onCacheMiss var cacheKey []byte if isCacheable { - cacheKey = generateCacheKey(args) + cacheKey = generateCacheKey(cacheArgs) if cachedValue, ok := cache.Get(cacheKey); ok { // cloning? - returnValue := cachedValue.(*R) + returnValue, ok := cachedValue.(*R) + if !ok { + return nil, fmt.Errorf("unexpected error. Invalid format cached. %v", cachedValue) + } return returnValue, nil } } From 7001ef707e177d49cf72174ee6afbfed2c5c3c0b Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Sat, 23 Mar 2024 10:45:12 +0000 Subject: [PATCH 45/65] fix --- go/common/log_events.go | 2 +- go/enclave/events/subscription_manager.go | 10 ++++------ go/rpc/encrypted_client.go | 14 ++++---------- integration/obscurogateway/tengateway_test.go | 6 +++--- 4 files changed, 12 insertions(+), 20 deletions(-) diff --git a/go/common/log_events.go b/go/common/log_events.go index 7f29d0c401..9082f18880 100644 --- a/go/common/log_events.go +++ b/go/common/log_events.go @@ -16,7 +16,7 @@ type LogSubscription struct { ViewingKey *viewingkey.RPCSignedViewingKey // A subscriber-defined filter to apply to the stream of logs. - Filter *filters.FilterCriteria + Filter *FilterCriteriaJSON } // IDAndEncLog pairs an encrypted log with the ID of the subscription that generated it. diff --git a/go/enclave/events/subscription_manager.go b/go/enclave/events/subscription_manager.go index fcaeaaeb2e..b49ff1f84e 100644 --- a/go/enclave/events/subscription_manager.go +++ b/go/enclave/events/subscription_manager.go @@ -3,7 +3,6 @@ package events import ( "encoding/json" "fmt" - "math/big" "sync" "github.com/ten-protocol/go-ten/go/enclave/vkhandler" @@ -19,7 +18,6 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" gethlog "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" "github.com/ten-protocol/go-ten/go/common" ) @@ -61,7 +59,7 @@ func NewSubscriptionManager(storage storage.Storage, chainID int64, logger gethl // correctly. If there is an existing subscription with the given ID, it is overwritten. func (s *SubscriptionManager) AddSubscription(id gethrpc.ID, encodedSubscription []byte) error { subscription := &common.LogSubscription{} - if err := rlp.DecodeBytes(encodedSubscription, subscription); err != nil { + if err := json.Unmarshal(encodedSubscription, subscription); err != nil { return fmt.Errorf("could not decocde log subscription from RLP. Cause: %w", err) } @@ -238,15 +236,15 @@ func getUserAddrsFromLogTopics(log *types.Log, db *state.StateDB) []*gethcommon. // Lifted from eth/filters/filter.go in the go-ethereum repository. // filterLogs creates a slice of logs matching the given criteria. -func filterLogs(logs []*types.Log, fromBlock, toBlock *big.Int, addresses []gethcommon.Address, topics [][]gethcommon.Hash, logger gethlog.Logger) []*types.Log { //nolint:gocognit +func filterLogs(logs []*types.Log, fromBlock, toBlock *gethrpc.BlockNumber, addresses []gethcommon.Address, topics [][]gethcommon.Hash, logger gethlog.Logger) []*types.Log { //nolint:gocognit var ret []*types.Log Logs: for _, logItem := range logs { - if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > logItem.BlockNumber { + if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Int64() > int64(logItem.BlockNumber) { logger.Debug("Skipping log ", "log", logItem, "reason", "In the past. The starting block num for filter is bigger than log") continue } - if toBlock != nil && toBlock.Int64() > 0 && toBlock.Uint64() < logItem.BlockNumber { + if toBlock != nil && toBlock.Int64() > 0 && toBlock.Int64() < int64(logItem.BlockNumber) { logger.Debug("Skipping log ", "log", logItem, "reason", "In the future. The ending block num for filter is smaller than log") continue } diff --git a/go/rpc/encrypted_client.go b/go/rpc/encrypted_client.go index c82af3cf3f..6dbde5faec 100644 --- a/go/rpc/encrypted_client.go +++ b/go/rpc/encrypted_client.go @@ -13,7 +13,6 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/ecies" "github.com/ethereum/go-ethereum/eth/filters" - "github.com/ethereum/go-ethereum/rlp" "github.com/ten-protocol/go-ten/go/common" "github.com/ten-protocol/go-ten/go/common/errutil" "github.com/ten-protocol/go-ten/go/common/log" @@ -108,8 +107,7 @@ func (c *EncRPCClient) Subscribe(ctx context.Context, _ interface{}, namespace s return nil, err } - // We use RLP instead of JSON marshaling here, as for some reason the filter criteria doesn't unmarshal correctly from JSON. - encodedLogSubscription, err := rlp.EncodeToBytes(logSubscription) + encodedLogSubscription, err := json.Marshal(logSubscription) if err != nil { return nil, err } @@ -181,7 +179,7 @@ func (c *EncRPCClient) createAuthenticatedLogSubscription(args []interface{}) (* // If there are less than two arguments, it means no filter criteria was passed. if len(args) < 2 { - logSubscription.Filter = &filters.FilterCriteria{} + logSubscription.Filter = &common.FilterCriteriaJSON{} return logSubscription, nil } @@ -189,12 +187,8 @@ func (c *EncRPCClient) createAuthenticatedLogSubscription(args []interface{}) (* if !ok { return nil, fmt.Errorf("invalid subscription") } - // If we do not override a nil block hash to an empty one, RLP decoding will fail on the enclave side. - if filterCriteria.BlockHash == nil { - filterCriteria.BlockHash = &gethcommon.Hash{} - } - - logSubscription.Filter = &filterCriteria + fc := common.FromCriteria(filterCriteria) + logSubscription.Filter = &fc return logSubscription, nil } diff --git a/integration/obscurogateway/tengateway_test.go b/integration/obscurogateway/tengateway_test.go index 04a23f8707..09b3167e89 100644 --- a/integration/obscurogateway/tengateway_test.go +++ b/integration/obscurogateway/tengateway_test.go @@ -786,9 +786,9 @@ func subscribeToEvents(addresses []gethcommon.Address, topics [][]gethcommon.Has // Make a subscription filterQuery := ethereum.FilterQuery{ Addresses: addresses, - FromBlock: big.NewInt(0), // todo (@ziga) - without those we get errors - fix that and make them configurable - ToBlock: big.NewInt(10000), - Topics: topics, + // FromBlock: big.NewInt(0), // todo (@ziga) - without those we get errors - fix that and make them configurable + // ToBlock: big.NewInt(10000), + Topics: topics, } logsCh := make(chan types.Log) From a5a8b81ab26133a49e4a823a2e7a638ea4fdce9b Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Sat, 23 Mar 2024 10:55:24 +0000 Subject: [PATCH 46/65] fix --- integration/constants.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration/constants.go b/integration/constants.go index 48b3bcfa29..e6c73d8224 100644 --- a/integration/constants.go +++ b/integration/constants.go @@ -12,11 +12,11 @@ const ( StartPortNetworkTests = 17000 StartPortSmartContractTests = 18000 StartPortContractDeployerTest1 = 19000 - StartPortContractDeployerTest2 = 20000 - StartPortWalletExtensionUnitTest = 21000 + StartPortContractDeployerTest2 = 21000 StartPortFaucetUnitTest = 22000 StartPortFaucetHTTPUnitTest = 23000 StartPortTenGatewayUnitTest = 24000 + StartPortWalletExtensionUnitTest = 25000 DefaultGethWSPortOffset = 100 DefaultGethAUTHPortOffset = 200 From fddedebcc971f557a9f20255e7d179fad6b54dd4 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Sun, 24 Mar 2024 13:55:30 +0000 Subject: [PATCH 47/65] fix --- lib/gethfork/rpc/handler.go | 2 ++ tools/walletextension/rpcapi/filter_api.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/lib/gethfork/rpc/handler.go b/lib/gethfork/rpc/handler.go index 73e0443dab..7755b776eb 100644 --- a/lib/gethfork/rpc/handler.go +++ b/lib/gethfork/rpc/handler.go @@ -19,6 +19,7 @@ package rpc import ( "context" "encoding/json" + "fmt" "reflect" "strconv" "strings" @@ -554,6 +555,7 @@ func (h *handler) handleSubscribe(cp *callProc, msg *jsonrpcMessage) *jsonrpcMes // Parse subscription name arg too, but remove it before calling the callback. argTypes := append([]reflect.Type{stringType}, callb.argTypes...) args, err := parsePositionalArguments(msg.Params, argTypes) + fmt.Printf("Subscribe %s\n", string(msg.Params)) if err != nil { return msg.errorResponse(&invalidParamsError{err.Error()}) } diff --git a/tools/walletextension/rpcapi/filter_api.go b/tools/walletextension/rpcapi/filter_api.go index 195e10f3ce..c746770f2c 100644 --- a/tools/walletextension/rpcapi/filter_api.go +++ b/tools/walletextension/rpcapi/filter_api.go @@ -43,6 +43,7 @@ func (api *FilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, error) { } func (api *FilterAPI) Logs(ctx context.Context, crit filters.FilterCriteria) (*rpc.Subscription, error) { + audit(api.we, "start Logs subscription %v", crit) subNotifier, user, err := getUserAndNotifier(ctx, api) if err != nil { return nil, err @@ -69,6 +70,7 @@ func (api *FilterAPI) Logs(ctx context.Context, crit filters.FilterCriteria) (*r inCh := make(chan common.IDAndLog) backendSubscription, err := rpcWSClient.Subscribe(ctx, nil, "eth", inCh, "logs", crit) if err != nil { + fmt.Printf("could not connect to backend %s", err) return nil, err } From 9eb55eb994a9ecbabb18f323d2f29003dd535232 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Mon, 25 Mar 2024 15:21:23 +0000 Subject: [PATCH 48/65] merge main --- go/rpc/client.go | 2 - integration/obscurogateway/tengateway_test.go | 36 +++++----- lib/gethfork/rpc/websocket.go | 5 +- tools/walletextension/common/constants.go | 8 +-- tools/walletextension/httpapi/routes.go | 67 ------------------- tools/walletextension/lib/client_lib.go | 4 ++ tools/walletextension/rpcapi/utils.go | 2 +- .../rpcapi/wallet_extension.go | 61 +---------------- 8 files changed, 34 insertions(+), 151 deletions(-) diff --git a/go/rpc/client.go b/go/rpc/client.go index cb2297ac20..78e52fb28a 100644 --- a/go/rpc/client.go +++ b/go/rpc/client.go @@ -33,8 +33,6 @@ const ( GetTotalTxs = "tenscan_getTotalTransactions" Attestation = "tenscan_attestation" StopHost = "test_stopHost" - Subscribe = "eth_subscribe" - Unsubscribe = "eth_unsubscribe" SubscribeNamespace = "eth" SubscriptionTypeLogs = "logs" diff --git a/integration/obscurogateway/tengateway_test.go b/integration/obscurogateway/tengateway_test.go index ada5d06348..cdc96d58da 100644 --- a/integration/obscurogateway/tengateway_test.go +++ b/integration/obscurogateway/tengateway_test.go @@ -17,7 +17,7 @@ import ( "github.com/ten-protocol/go-ten/tools/walletextension" "github.com/go-kit/kit/transport/http/jsonrpc" - "github.com/ten-protocol/go-ten/go/rpc" + tenrpc "github.com/ten-protocol/go-ten/go/rpc" log2 "github.com/ten-protocol/go-ten/go/common/log" @@ -419,7 +419,11 @@ func testErrorHandling(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { // make requests to geth for comparison for _, req := range []string{ - `{"jsonrpc":"2.0","method":"eth_blockNumber","params": [],"id":1}`, + //`{"jsonrpc":"2.0","method":"eth_getLogs","params":[],"id":1}`, + //`{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"topics":[]}],"id":1}`, + //`{"jsonrpc":"2.0","method":"eth_subscribe","params":["logs"],"id":1}`, + //`{"jsonrpc":"2.0","method":"eth_subscribe","params":["logs",{"topics":[]}],"id":1}`, + `{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}`, `{"jsonrpc":"2.0","method":"eth_blockNumber","params": [],"id":1}`, // test caching `{"jsonrpc":"2.0","method":"eth_gasPrice","params": [],"id":1}`, `{"jsonrpc":"2.0","method":"eth_gasPrice","params": [],"id":1}`, // test caching @@ -667,19 +671,19 @@ func testDifferentMessagesOnRegister(t *testing.T, httpURL, wsURL string, w wall } func testInvokeNonSensitiveMethod(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { - user, err := NewUser([]wallet.Wallet{w}, httpURL, wsURL) + user, err := NewGatewayUser([]wallet.Wallet{w}, httpURL, wsURL) require.NoError(t, err) // call one of the non-sensitive methods with unauthenticated user // and make sure gateway is not complaining about not having viewing keys - respBody := makeHTTPEthJSONReq(httpURL, rpc.ChainID, user.tgClient.UserID(), nil) - if strings.Contains(string(respBody), fmt.Sprintf("method %s cannot be called with an unauthorised client - no signed viewing keys found", rpc.ChainID)) { - t.Errorf("sensitive method called without authenticating viewingkeys and did fail because of it: %s", rpc.ChainID) + respBody := makeHTTPEthJSONReq(httpURL, tenrpc.ChainID, user.tgClient.UserID(), nil) + if strings.Contains(string(respBody), fmt.Sprintf("method %s cannot be called with an unauthorised client - no signed viewing keys found", tenrpc.ChainID)) { + t.Errorf("sensitive method called without authenticating viewingkeys and did fail because of it: %s", tenrpc.ChainID) } } func testGetStorageAtForReturningUserID(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { - user, err := NewUser([]wallet.Wallet{w}, httpURL, wsURL) + user, err := NewGatewayUser([]wallet.Wallet{w}, httpURL, wsURL) require.NoError(t, err) type JSONResponse struct { @@ -688,24 +692,24 @@ func testGetStorageAtForReturningUserID(t *testing.T, httpURL, wsURL string, w w var response JSONResponse // make a request to GetStorageAt with correct parameters to get userID that exists in the database - respBody := makeHTTPEthJSONReq(httpURL, rpc.GetStorageAt, user.tgClient.UserID(), []interface{}{"getUserID", "0", nil}) + respBody := makeHTTPEthJSONReq(httpURL, tenrpc.GetStorageAt, user.tgClient.UserID(), []interface{}{wecommon.GetStorageAtUserIDRequestMethodName, "0", nil}) if err = json.Unmarshal(respBody, &response); err != nil { t.Error("Unable to unmarshal response") } - if response.Result != user.tgClient.UserID() { + if !bytes.Equal(gethcommon.FromHex(response.Result), user.tgClient.UserIDBytes()) { t.Errorf("Wrong UserID returned. Expected: %s, received: %s", user.tgClient.UserID(), response.Result) } // make a request to GetStorageAt with correct parameters to get userID, but with wrong userID - respBody2 := makeHTTPEthJSONReq(httpURL, rpc.GetStorageAt, "invalid_user_id", []interface{}{"getUserID", "0", nil}) - if !strings.Contains(string(respBody2), "method eth_getStorageAt cannot be called with an unauthorised client - no signed viewing keys found") { - t.Error("eth_getStorageAt did not respond with error: method eth_getStorageAt cannot be called with an unauthorised client - no signed viewing keys found") + respBody2 := makeHTTPEthJSONReq(httpURL, tenrpc.GetStorageAt, "0x0000000000000000000000000000000000000001", []interface{}{wecommon.GetStorageAtUserIDRequestMethodName, "0", nil}) + if !strings.Contains(string(respBody2), "not found") { + t.Error("eth_getStorageAt did not respond with not found error") } // make a request to GetStorageAt with wrong parameters to get userID, but correct userID - respBody3 := makeHTTPEthJSONReq(httpURL, rpc.GetStorageAt, user.tgClient.UserID(), []interface{}{"abc", "0", nil}) - if !strings.Contains(string(respBody3), "method eth_getStorageAt cannot be called with an unauthorised client - no signed viewing keys found") { - t.Error("eth_getStorageAt did not respond with error: no signed viewing keys found") + respBody3 := makeHTTPEthJSONReq(httpURL, tenrpc.GetStorageAt, user.tgClient.UserID(), []interface{}{"0x0000000000000000000000000000000000000001", "0", nil}) + if !strings.Contains(string(respBody3), "illegal access") { + t.Error("eth_getStorageAt did not respond with error: illegal access") } } @@ -869,7 +873,7 @@ func subscribeToEvents(addresses []gethcommon.Address, topics [][]gethcommon.Has // Make a subscription filterQuery := ethereum.FilterQuery{ Addresses: addresses, - // FromBlock: big.NewInt(0), // todo (@ziga) - without those we get errors - fix that and make them configurable + FromBlock: big.NewInt(2), // todo (@ziga) - without those we get errors - fix that and make them configurable // ToBlock: big.NewInt(10000), Topics: topics, } diff --git a/lib/gethfork/rpc/websocket.go b/lib/gethfork/rpc/websocket.go index 7bd09d589a..9db7e19e40 100644 --- a/lib/gethfork/rpc/websocket.go +++ b/lib/gethfork/rpc/websocket.go @@ -27,7 +27,8 @@ import ( "sync" "time" - "github.com/status-im/keycard-go/hexutils" + "github.com/ethereum/go-ethereum/common" + "github.com/ten-protocol/go-ten/go/common/viewingkey" mapset "github.com/deckarep/golang-set/v2" @@ -73,7 +74,7 @@ func extractUserID(ctx context.Context) []byte { if !ok { return nil } - userID := hexutils.HexToBytes(token) + userID := common.FromHex(token) if len(userID) != viewingkey.UserIDLength { return nil } diff --git a/tools/walletextension/common/constants.go b/tools/walletextension/common/constants.go index ec13209999..b8ecbf2438 100644 --- a/tools/walletextension/common/constants.go +++ b/tools/walletextension/common/constants.go @@ -4,6 +4,10 @@ const ( Localhost = "127.0.0.1" JSONKeyAddress = "address" + JSONKeyID = "id" + JSONKeyMethod = "method" + JSONKeyParams = "params" + JSONKeyRPCVersion = "jsonrpc" JSONKeySignature = "signature" JSONKeyType = "type" JSONKeyEncryptionToken = "encryptionToken" @@ -12,9 +16,6 @@ const ( const ( PathReady = "/ready/" - PathViewingKeys = "/viewingkeys/" - PathGenerateViewingKey = "/generateviewingkey/" - PathSubmitViewingKey = "/submitviewingkey/" PathJoin = "/join/" PathGetMessage = "/getmessage/" PathAuthenticate = "/authenticate/" @@ -24,7 +25,6 @@ const ( PathNetworkHealth = "/network-health/" WSProtocol = "ws://" HTTPProtocol = "http://" - UserQueryParameter = "u" EncryptedTokenQueryParameter = "token" AddressQueryParameter = "a" MessageUserIDLen = 40 diff --git a/tools/walletextension/httpapi/routes.go b/tools/walletextension/httpapi/routes.go index 7615b6c152..d0901ebc1a 100644 --- a/tools/walletextension/httpapi/routes.go +++ b/tools/walletextension/httpapi/routes.go @@ -16,9 +16,6 @@ import ( "github.com/ten-protocol/go-ten/go/common/httputil" "github.com/ten-protocol/go-ten/tools/walletextension/common" - - gethcommon "github.com/ethereum/go-ethereum/common" - "github.com/ten-protocol/go-ten/tools/walletextension/userconn" ) // NewHTTPRoutes returns the http specific routes @@ -88,70 +85,6 @@ func httpRequestHandler(walletExt *rpcapi.Services, resp http.ResponseWriter, re // readyRequestHandler is used to check whether the server is ready func readyRequestHandler(_ *rpcapi.Services, _ UserConn) {} -// generateViewingKeyRequestHandler parses the gen vk request -func generateViewingKeyRequestHandler(walletExt *rpcapi.Services, conn UserConn) { - body, err := conn.ReadRequest() - if err != nil { - handleError(conn, walletExt.Logger(), fmt.Errorf("error reading request: %w", err)) - return - } - - var reqJSONMap map[string]string - err = json.Unmarshal(body, &reqJSONMap) - if err != nil { - handleError(conn, walletExt.Logger(), fmt.Errorf("could not unmarshal address request - %w", err)) - return - } - - address := gethcommon.HexToAddress(reqJSONMap[common.JSONKeyAddress]) - - pubViewingKey, err := walletExt.GenerateViewingKey(address) - if err != nil { - handleError(conn, walletExt.Logger(), fmt.Errorf("unable to generate vieweing key - %w", err)) - return - } - - err = conn.WriteResponse([]byte(pubViewingKey)) - if err != nil { - walletExt.Logger().Error("error writing success response", log.ErrKey, err) - } -} - -// submitViewingKeyRequestHandler submits the viewing key and signed bytes to the WE -func submitViewingKeyRequestHandler(walletExt *rpcapi.Services, conn UserConn) { - body, err := conn.ReadRequest() - if err != nil { - handleError(conn, walletExt.Logger(), fmt.Errorf("error reading request: %w", err)) - return - } - - var reqJSONMap map[string]string - err = json.Unmarshal(body, &reqJSONMap) - if err != nil { - handleError(conn, walletExt.Logger(), fmt.Errorf("could not unmarshal address request - %w", err)) - return - } - accAddress := gethcommon.HexToAddress(reqJSONMap[common.JSONKeyAddress]) - - signature, err := hex.DecodeString(reqJSONMap[common.JSONKeySignature][2:]) - if err != nil { - handleError(conn, walletExt.Logger(), fmt.Errorf("could not decode signature from client to hex - %w", err)) - return - } - - err = walletExt.SubmitViewingKey(accAddress, signature) - if err != nil { - handleError(conn, walletExt.Logger(), fmt.Errorf("could not submit viewing key - %w", err)) - return - } - - err = conn.WriteResponse([]byte(common.SuccessMsg)) - if err != nil { - walletExt.Logger().Error("error writing success response", log.ErrKey, err) - return - } -} - // This function handles request to /join endpoint. It is responsible to create new user (new key-pair) and store it to the db func joinRequestHandler(walletExt *rpcapi.Services, conn UserConn) { // audit() diff --git a/tools/walletextension/lib/client_lib.go b/tools/walletextension/lib/client_lib.go index c4a0e3f11e..3e63a1a22e 100644 --- a/tools/walletextension/lib/client_lib.go +++ b/tools/walletextension/lib/client_lib.go @@ -36,6 +36,10 @@ func (o *TGLib) UserID() string { return hexutils.BytesToHex(o.userID) } +func (o *TGLib) UserIDBytes() []byte { + return o.userID +} + func (o *TGLib) Join() error { // todo move this to stdlib statusCode, userID, err := fasthttp.Get(nil, fmt.Sprintf("%s/v1/join/", o.httpURL)) diff --git a/tools/walletextension/rpcapi/utils.go b/tools/walletextension/rpcapi/utils.go index eb8d6aea22..b050fd3dff 100644 --- a/tools/walletextension/rpcapi/utils.go +++ b/tools/walletextension/rpcapi/utils.go @@ -164,7 +164,7 @@ func extractUserID(ctx context.Context, _ *Services) ([]byte, error) { if !ok { return nil, fmt.Errorf("invalid userid: %s", ctx.Value(rpc.GWTokenKey{})) } - userID := hexutils.HexToBytes(token) + userID := common.FromHex(token) if len(userID) != viewingkey.UserIDLength { return nil, fmt.Errorf("invalid userid: %s", token) } diff --git a/tools/walletextension/rpcapi/wallet_extension.go b/tools/walletextension/rpcapi/wallet_extension.go index 520a97c9b7..c7b2e1c062 100644 --- a/tools/walletextension/rpcapi/wallet_extension.go +++ b/tools/walletextension/rpcapi/wallet_extension.go @@ -25,9 +25,8 @@ import ( // Services handles the various business logic for the api endpoints type Services struct { - HostAddrHTTP string // The HTTP address on which the Ten host can be reached - HostAddrWS string // The WS address on which the Ten host can be reached - unsignedVKs map[gethcommon.Address]*viewingkey.ViewingKey // Map temporarily holding VKs that have been generated but not yet signed + HostAddrHTTP string // The HTTP address on which the Ten host can be reached + HostAddrWS string // The WS address on which the Ten host can be reached Storage storage.Storage logger gethlog.Logger FileLogger gethlog.Logger @@ -55,7 +54,6 @@ func NewServices(hostAddrHTTP string, hostAddrWS string, storage storage.Storage return &Services{ HostAddrHTTP: hostAddrHTTP, HostAddrWS: hostAddrWS, - unsignedVKs: map[gethcommon.Address]*viewingkey.ViewingKey{}, Storage: storage, logger: logger, FileLogger: newFileLogger, @@ -77,61 +75,6 @@ func (w *Services) Logger() gethlog.Logger { return w.logger } -// todo - once the logic in routes has been moved to RPC functions, these methods can be moved there - -// GenerateViewingKey generates the user viewing key and waits for signature -func (w *Services) GenerateViewingKey(addr gethcommon.Address) (string, error) { - // Requested to generate viewing key for address(old way): %s", addr.Hex())) - viewingKeyPrivate, err := crypto.GenerateKey() - if err != nil { - return "", fmt.Errorf("unable to generate a new keypair - %w", err) - } - - viewingPublicKeyBytes := crypto.CompressPubkey(&viewingKeyPrivate.PublicKey) - viewingPrivateKeyEcies := ecies.ImportECDSA(viewingKeyPrivate) - - w.unsignedVKs[addr] = &viewingkey.ViewingKey{ - Account: &addr, - PrivateKey: viewingPrivateKeyEcies, - PublicKey: viewingPublicKeyBytes, - SignatureWithAccountKey: nil, // we await a signature from the user before we can set up the EncRPCClient - } - - // compress the viewing key and convert it to hex string ( this is what Metamask signs) - viewingKeyBytes := crypto.CompressPubkey(&viewingKeyPrivate.PublicKey) - return hex.EncodeToString(viewingKeyBytes), nil -} - -// SubmitViewingKey checks the signed viewing key and stores it -func (w *Services) SubmitViewingKey(address gethcommon.Address, signature []byte) error { - audit(w, "Requested to submit a viewing key (old way): %s", address.Hex()) - vk, found := w.unsignedVKs[address] - if !found { - return fmt.Errorf(fmt.Sprintf("no viewing key found to sign for acc=%s, please call %s to generate key before sending signature", address, common.PathGenerateViewingKey)) - } - - // We transform the V from 27/28 to 0/1. This same change is made in Geth internals, for legacy reasons to be able - // to recover the address: https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L452-L459 - signature[64] -= 27 - - vk.SignatureWithAccountKey = signature - - //err := w.Storage.AddUser(defaultUserId, crypto.FromECDSA(vk.PrivateKey.ExportECDSA())) - //if err != nil { - // return fmt.Errorf("error saving user: %s", common.DefaultUser) - //} - // - //err = w.Storage.AddAccount(defaultUserId, vk.Account.Bytes(), vk.SignatureWithAccountKey) - //if err != nil { - // return fmt.Errorf("error saving account %s for user %s", vk.Account.Hex(), common.DefaultUser) - //} - - // finally we remove the VK from the pending 'unsigned VKs' map now the client has been created - delete(w.unsignedVKs, address) - - return nil -} - // GenerateAndStoreNewUser generates new key-pair and userID, stores it in the database and returns hex encoded userID and error func (w *Services) GenerateAndStoreNewUser() ([]byte, error) { requestStartTime := time.Now() From 70674897f86267d9b1247cbd9934bbac5e21100c Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Mon, 25 Mar 2024 16:07:23 +0000 Subject: [PATCH 49/65] fix --- go/common/log_events.go | 139 +++++++++++++++++- go/rpc/encrypted_client.go | 3 +- integration/obscurogateway/tengateway_test.go | 6 +- lib/gethfork/rpc/json.go | 1 + tools/walletextension/rpcapi/filter_api.go | 9 +- 5 files changed, 148 insertions(+), 10 deletions(-) diff --git a/go/common/log_events.go b/go/common/log_events.go index 9082f18880..e99d757457 100644 --- a/go/common/log_events.go +++ b/go/common/log_events.go @@ -1,9 +1,15 @@ package common import ( + "encoding/json" + "errors" + "fmt" "math/big" + "strings" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ten-protocol/go-ten/go/common/viewingkey" @@ -42,7 +48,7 @@ type FilterCriteriaJSON struct { Topics [][]common.Hash `json:"topics"` } -func FromCriteria(crit filters.FilterCriteria) FilterCriteriaJSON { +func FromCriteria(crit FilterCriteria) FilterCriteriaJSON { var from *rpc.BlockNumber if crit.FromBlock != nil { f := (rpc.BlockNumber)(crit.FromBlock.Int64()) @@ -82,3 +88,134 @@ func ToCriteria(jsonCriteria FilterCriteriaJSON) filters.FilterCriteria { Topics: jsonCriteria.Topics, } } + +// duplicated from geth +// FilterCriteria represents a request to create a new filter. +// Same as ethereum.FilterQuery but with UnmarshalJSON() method. +type FilterCriteria ethereum.FilterQuery + +var errInvalidTopic = errors.New("invalid topic(s)") + +// UnmarshalJSON sets *args fields with given data. +func (args *FilterCriteria) UnmarshalJSON(data []byte) error { + type input struct { + BlockHash *common.Hash `json:"blockHash"` + FromBlock *rpc.BlockNumber `json:"fromBlock"` + ToBlock *rpc.BlockNumber `json:"toBlock"` + Addresses interface{} `json:"address"` + Topics []interface{} `json:"topics"` + } + + fmt.Printf("FilterCriteria UnmarshalJSON %s", data) + + var raw input + if err := json.Unmarshal(data, &raw); err != nil { + if strings.Contains(err.Error(), "cannot unmarshal array") { + return nil + } + return err + } + + if raw.BlockHash != nil { + if raw.FromBlock != nil || raw.ToBlock != nil { + // BlockHash is mutually exclusive with FromBlock/ToBlock criteria + return errors.New("cannot specify both BlockHash and FromBlock/ToBlock, choose one or the other") + } + args.BlockHash = raw.BlockHash + } else { + if raw.FromBlock != nil { + args.FromBlock = big.NewInt(raw.FromBlock.Int64()) + } + + if raw.ToBlock != nil { + args.ToBlock = big.NewInt(raw.ToBlock.Int64()) + } + } + + args.Addresses = []common.Address{} + + if raw.Addresses != nil { + // raw.Address can contain a single address or an array of addresses + switch rawAddr := raw.Addresses.(type) { + case []interface{}: + for i, addr := range rawAddr { + if strAddr, ok := addr.(string); ok { + addr, err := decodeAddress(strAddr) + if err != nil { + return fmt.Errorf("invalid address at index %d: %v", i, err) + } + args.Addresses = append(args.Addresses, addr) + } else { + return fmt.Errorf("non-string address at index %d", i) + } + } + case string: + addr, err := decodeAddress(rawAddr) + if err != nil { + return fmt.Errorf("invalid address: %v", err) + } + args.Addresses = []common.Address{addr} + default: + return errors.New("invalid addresses in query") + } + } + + // topics is an array consisting of strings and/or arrays of strings. + // JSON null values are converted to common.Hash{} and ignored by the filter manager. + if len(raw.Topics) > 0 { + args.Topics = make([][]common.Hash, len(raw.Topics)) + for i, t := range raw.Topics { + switch topic := t.(type) { + case nil: + // ignore topic when matching logs + + case string: + // match specific topic + top, err := decodeTopic(topic) + if err != nil { + return err + } + args.Topics[i] = []common.Hash{top} + + case []interface{}: + // or case e.g. [null, "topic0", "topic1"] + for _, rawTopic := range topic { + if rawTopic == nil { + // null component, match all + args.Topics[i] = nil + break + } + if topic, ok := rawTopic.(string); ok { + parsed, err := decodeTopic(topic) + if err != nil { + return err + } + args.Topics[i] = append(args.Topics[i], parsed) + } else { + return errInvalidTopic + } + } + default: + return errInvalidTopic + } + } + } + + return nil +} + +func decodeAddress(s string) (common.Address, error) { + b, err := hexutil.Decode(s) + if err == nil && len(b) != common.AddressLength { + err = fmt.Errorf("hex has invalid length %d after decoding; expected %d for address", len(b), common.AddressLength) + } + return common.BytesToAddress(b), err +} + +func decodeTopic(s string) (common.Hash, error) { + b, err := hexutil.Decode(s) + if err == nil && len(b) != common.HashLength { + err = fmt.Errorf("hex has invalid length %d after decoding; expected %d for topic", len(b), common.HashLength) + } + return common.BytesToHash(b), err +} diff --git a/go/rpc/encrypted_client.go b/go/rpc/encrypted_client.go index 6dbde5faec..a2122cac70 100644 --- a/go/rpc/encrypted_client.go +++ b/go/rpc/encrypted_client.go @@ -12,7 +12,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/ecies" - "github.com/ethereum/go-ethereum/eth/filters" "github.com/ten-protocol/go-ten/go/common" "github.com/ten-protocol/go-ten/go/common/errutil" "github.com/ten-protocol/go-ten/go/common/log" @@ -183,7 +182,7 @@ func (c *EncRPCClient) createAuthenticatedLogSubscription(args []interface{}) (* return logSubscription, nil } - filterCriteria, ok := args[1].(filters.FilterCriteria) + filterCriteria, ok := args[1].(common.FilterCriteria) if !ok { return nil, fmt.Errorf("invalid subscription") } diff --git a/integration/obscurogateway/tengateway_test.go b/integration/obscurogateway/tengateway_test.go index cdc96d58da..31f474a689 100644 --- a/integration/obscurogateway/tengateway_test.go +++ b/integration/obscurogateway/tengateway_test.go @@ -419,8 +419,9 @@ func testErrorHandling(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { // make requests to geth for comparison for _, req := range []string{ - //`{"jsonrpc":"2.0","method":"eth_getLogs","params":[],"id":1}`, - //`{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"topics":[]}],"id":1}`, + `{"jsonrpc":"2.0","method":"eth_getLogs","params":[[]],"id":1}`, + `{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"topics":[]}],"id":1}`, + `{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":"0x387","topics":["0xc6d8c0af6d21f291e7c359603aa97e0ed500f04db6e983b9fce75a91c6b8da6b"]}],"id":1}`, //`{"jsonrpc":"2.0","method":"eth_subscribe","params":["logs"],"id":1}`, //`{"jsonrpc":"2.0","method":"eth_subscribe","params":["logs",{"topics":[]}],"id":1}`, `{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}`, @@ -440,6 +441,7 @@ func testErrorHandling(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { // ensure the geth request is issued correctly (should return 200 ok with jsonRPCError) _, response, err := httputil.PostDataJSON(ogClient.HTTP(), []byte(req)) require.NoError(t, err) + fmt.Printf("Resp: %s", response) // unmarshall the response to JSONRPCMessage jsonRPCError := JSONRPCMessage{} diff --git a/lib/gethfork/rpc/json.go b/lib/gethfork/rpc/json.go index f815adc313..51ae122ea5 100644 --- a/lib/gethfork/rpc/json.go +++ b/lib/gethfork/rpc/json.go @@ -297,6 +297,7 @@ func isBatch(raw json.RawMessage) bool { // given types. It returns the parsed values or an error when the args could not be // parsed. Missing optional arguments are returned as reflect.Zero values. func parsePositionalArguments(rawArgs json.RawMessage, types []reflect.Type) ([]reflect.Value, error) { + fmt.Printf("Parse: %s", rawArgs) dec := json.NewDecoder(bytes.NewReader(rawArgs)) var args []reflect.Value tok, err := dec.Token() diff --git a/tools/walletextension/rpcapi/filter_api.go b/tools/walletextension/rpcapi/filter_api.go index c746770f2c..ad53048837 100644 --- a/tools/walletextension/rpcapi/filter_api.go +++ b/tools/walletextension/rpcapi/filter_api.go @@ -13,7 +13,6 @@ import ( wecommon "github.com/ten-protocol/go-ten/tools/walletextension/common" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth/filters" "github.com/ten-protocol/go-ten/lib/gethfork/rpc" ) @@ -42,7 +41,7 @@ func (api *FilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, error) { return nil, rpcNotImplemented } -func (api *FilterAPI) Logs(ctx context.Context, crit filters.FilterCriteria) (*rpc.Subscription, error) { +func (api *FilterAPI) Logs(ctx context.Context, crit common.FilterCriteria) (*rpc.Subscription, error) { audit(api.we, "start Logs subscription %v", crit) subNotifier, user, err := getUserAndNotifier(ctx, api) if err != nil { @@ -119,7 +118,7 @@ func getUserAndNotifier(ctx context.Context, api *FilterAPI) (*rpc.Notifier, *GW return subNotifier, user, nil } -func searchForAddressInFilterCriteria(filterCriteria filters.FilterCriteria, possibleAddresses []*gethcommon.Address) []*gethcommon.Address { +func searchForAddressInFilterCriteria(filterCriteria common.FilterCriteria, possibleAddresses []*gethcommon.Address) []*gethcommon.Address { result := make([]*gethcommon.Address, 0) addrMap := toMap(possibleAddresses) for _, topicCondition := range filterCriteria.Topics { @@ -187,11 +186,11 @@ func handleUnsubscribe(connectionSub *rpc.Subscription, backendSubscriptions []* } } -func (api *FilterAPI) NewFilter(crit filters.FilterCriteria) (rpc.ID, error) { +func (api *FilterAPI) NewFilter(crit common.FilterCriteria) (rpc.ID, error) { return rpc.NewID(), rpcNotImplemented } -func (api *FilterAPI) GetLogs(ctx context.Context, crit filters.FilterCriteria) ([]*types.Log, error) { +func (api *FilterAPI) GetLogs(ctx context.Context, crit common.FilterCriteria) ([]*types.Log, error) { logs, err := ExecAuthRPC[[]*types.Log]( ctx, api.we, From 7db9f64a7f2ef48b0adc86166513c7625446f94c Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Mon, 25 Mar 2024 16:27:06 +0000 Subject: [PATCH 50/65] fix --- go/common/viewingkey/viewing_key.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/common/viewingkey/viewing_key.go b/go/common/viewingkey/viewing_key.go index 6d70a59977..93ad049999 100644 --- a/go/common/viewingkey/viewing_key.go +++ b/go/common/viewingkey/viewing_key.go @@ -42,7 +42,7 @@ func GenerateViewingKeyForWallet(wal wallet.Wallet) (*ViewingKey, error) { if err != nil { return nil, err } - encryptionToken := CalculateUserIDHex(crypto.CompressPubkey(viewingPrivateKeyECIES.PublicKey.ExportECDSA())) + encryptionToken := CalculateUserID(crypto.CompressPubkey(viewingPrivateKeyECIES.PublicKey.ExportECDSA())) messageToSign, err := GenerateMessage(encryptionToken, chainID, PersonalSignVersion, messageType) if err != nil { return nil, fmt.Errorf("failed to generate message for viewing key: %w", err) From dcb3f12a96f39fa62cd42d543ce9c46b5dd086ff Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Tue, 26 Mar 2024 09:35:08 +0000 Subject: [PATCH 51/65] add cache for users --- go/common/log_events.go | 2 +- lib/gethfork/rpc/json.go | 2 +- tools/walletextension/cache/RistrettoCache.go | 4 ++ tools/walletextension/cache/cache.go | 1 + .../walletextension/rpcapi/blockchain_api.go | 2 +- tools/walletextension/rpcapi/filter_api.go | 2 +- tools/walletextension/rpcapi/gw_user.go | 44 ++++++++++++------- tools/walletextension/rpcapi/utils.go | 21 ++++----- .../rpcapi/wallet_extension.go | 3 +- 9 files changed, 46 insertions(+), 35 deletions(-) diff --git a/go/common/log_events.go b/go/common/log_events.go index e99d757457..6230eb16de 100644 --- a/go/common/log_events.go +++ b/go/common/log_events.go @@ -106,7 +106,7 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error { Topics []interface{} `json:"topics"` } - fmt.Printf("FilterCriteria UnmarshalJSON %s", data) + fmt.Printf("FilterCriteria UnmarshalJSON %s\n", data) var raw input if err := json.Unmarshal(data, &raw); err != nil { diff --git a/lib/gethfork/rpc/json.go b/lib/gethfork/rpc/json.go index 51ae122ea5..0aaf4bbbc6 100644 --- a/lib/gethfork/rpc/json.go +++ b/lib/gethfork/rpc/json.go @@ -297,7 +297,7 @@ func isBatch(raw json.RawMessage) bool { // given types. It returns the parsed values or an error when the args could not be // parsed. Missing optional arguments are returned as reflect.Zero values. func parsePositionalArguments(rawArgs json.RawMessage, types []reflect.Type) ([]reflect.Value, error) { - fmt.Printf("Parse: %s", rawArgs) + fmt.Printf("Parse: %s\n", rawArgs) dec := json.NewDecoder(bytes.NewReader(rawArgs)) var args []reflect.Value tok, err := dec.Token() diff --git a/tools/walletextension/cache/RistrettoCache.go b/tools/walletextension/cache/RistrettoCache.go index d71cabcb50..a72ca5f606 100644 --- a/tools/walletextension/cache/RistrettoCache.go +++ b/tools/walletextension/cache/RistrettoCache.go @@ -53,6 +53,10 @@ func (c *ristrettoCache) Get(key []byte) (value any, ok bool) { return c.cache.Get(key) } +func (c *ristrettoCache) Remove(key []byte) { + c.cache.Del(key) +} + // startMetricsLogging starts logging cache metrics every hour. func (c *ristrettoCache) startMetricsLogging(logger log.Logger) { ticker := time.NewTicker(1 * time.Hour) diff --git a/tools/walletextension/cache/cache.go b/tools/walletextension/cache/cache.go index 88aed05c48..c080779305 100644 --- a/tools/walletextension/cache/cache.go +++ b/tools/walletextension/cache/cache.go @@ -9,6 +9,7 @@ import ( type Cache interface { Set(key []byte, value any, ttl time.Duration) bool Get(key []byte) (value any, ok bool) + Remove(key []byte) } func NewCache(logger log.Logger) (Cache, error) { diff --git a/tools/walletextension/rpcapi/blockchain_api.go b/tools/walletextension/rpcapi/blockchain_api.go index 4c016e97e2..e9e1d66e3f 100644 --- a/tools/walletextension/rpcapi/blockchain_api.go +++ b/tools/walletextension/rpcapi/blockchain_api.go @@ -144,7 +144,7 @@ func (api *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Addre return nil, err } - _, err = getUser(userID, api.we.Storage) + _, err = getUser(userID, api.we) if err != nil { return nil, err } diff --git a/tools/walletextension/rpcapi/filter_api.go b/tools/walletextension/rpcapi/filter_api.go index ad53048837..54248e24be 100644 --- a/tools/walletextension/rpcapi/filter_api.go +++ b/tools/walletextension/rpcapi/filter_api.go @@ -111,7 +111,7 @@ func getUserAndNotifier(ctx context.Context, api *FilterAPI) (*rpc.Notifier, *GW return nil, nil, fmt.Errorf("illegal access") } - user, err := getUser(subNotifier.UserID, api.we.Storage) + user, err := getUser(subNotifier.UserID, api.we) if err != nil { return nil, nil, fmt.Errorf("illegal access: %s, %w", subNotifier.UserID, err) } diff --git a/tools/walletextension/rpcapi/gw_user.go b/tools/walletextension/rpcapi/gw_user.go index bc3fabb7b2..efdb698e67 100644 --- a/tools/walletextension/rpcapi/gw_user.go +++ b/tools/walletextension/rpcapi/gw_user.go @@ -11,9 +11,10 @@ import ( gethlog "github.com/ethereum/go-ethereum/log" "github.com/ten-protocol/go-ten/go/rpc" wecommon "github.com/ten-protocol/go-ten/tools/walletextension/common" - "github.com/ten-protocol/go-ten/tools/walletextension/storage" ) +var userCacheKeyPrefix = []byte{0x0, 0x1, 0x2, 0x3} + type GWAccount struct { user *GWUser address *common.Address @@ -35,23 +36,32 @@ func (u GWUser) GetAllAddresses() []*common.Address { return accts } -func getUser(userID []byte, s storage.Storage) (*GWUser, error) { - result := GWUser{userID: userID, accounts: map[common.Address]*GWAccount{}} - userPrivateKey, err := s.GetUserPrivateKey(userID) - if err != nil { - return nil, fmt.Errorf("user %s not found. %w", hexutils.BytesToHex(userID), err) - } - result.userKey = userPrivateKey - allAccounts, err := s.GetAccounts(userID) - if err != nil { - return nil, err - } +func userCacheKey(userID []byte) []byte { + var key []byte + key = append(key, userCacheKeyPrefix...) + key = append(key, userID...) + return key +} - for _, account := range allAccounts { - address := common.BytesToAddress(account.AccountAddress) - result.accounts[address] = &GWAccount{user: &result, address: &address, signature: account.Signature, signatureType: viewingkey.SignatureType(uint8(account.SignatureType))} - } - return &result, nil +func getUser(userID []byte, s *Services) (*GWUser, error) { + return withCache(s.Cache, &CacheCfg{TTL: longCacheTTL}, userCacheKey(userID), func() (*GWUser, error) { + result := GWUser{userID: userID, accounts: map[common.Address]*GWAccount{}} + userPrivateKey, err := s.Storage.GetUserPrivateKey(userID) + if err != nil { + return nil, fmt.Errorf("user %s not found. %w", hexutils.BytesToHex(userID), err) + } + result.userKey = userPrivateKey + allAccounts, err := s.Storage.GetAccounts(userID) + if err != nil { + return nil, err + } + + for _, account := range allAccounts { + address := common.BytesToAddress(account.AccountAddress) + result.accounts[address] = &GWAccount{user: &result, address: &address, signature: account.Signature, signatureType: viewingkey.SignatureType(uint8(account.SignatureType))} + } + return &result, nil + }) } func (account *GWAccount) connect(url string, logger gethlog.Logger) (*rpc.EncRPCClient, error) { diff --git a/tools/walletextension/rpcapi/utils.go b/tools/walletextension/rpcapi/utils.go index b050fd3dff..da9b608026 100644 --- a/tools/walletextension/rpcapi/utils.go +++ b/tools/walletextension/rpcapi/utils.go @@ -50,11 +50,10 @@ type CacheCfg struct { func UnauthenticatedTenRPCCall[R any](ctx context.Context, w *Services, cfg *CacheCfg, method string, args ...any) (*R, error) { audit(w, "RPC start method=%s args=%v", method, args) requestStartTime := time.Now() - cacheKey := make([]any, 0) - cacheKey = append(cacheKey, method) - cacheKey = append(cacheKey, args...) + cacheArgs := []any{method} + cacheArgs = append(cacheArgs, args...) - res, err := withCache(w.Cache, cfg, cacheKey, func() (*R, error) { + res, err := withCache(w.Cache, cfg, generateCacheKey(cacheArgs), func() (*R, error) { var resp *R unauthedRPC, err := w.UnauthenticatedClient() if err != nil { @@ -79,17 +78,15 @@ func ExecAuthRPC[R any](ctx context.Context, w *Services, cfg *ExecCfg, method s return nil, err } - user, err := getUser(userID, w.Storage) + user, err := getUser(userID, w) if err != nil { return nil, err } - cacheKey := make([]any, 0) - cacheKey = append(cacheKey, userID) - cacheKey = append(cacheKey, method) - cacheKey = append(cacheKey, args...) + cacheArgs := []any{userID, method} + cacheArgs = append(cacheArgs, args...) - res, err := withCache(w.Cache, cfg.cacheCfg, cacheKey, func() (*R, error) { + res, err := withCache(w.Cache, cfg.cacheCfg, generateCacheKey(cacheArgs), func() (*R, error) { // determine candidate "from" candidateAccts, err := getCandidateAccounts(user, w, cfg) if err != nil { @@ -187,7 +184,7 @@ func generateCacheKey(params []any) []byte { return hasher.Sum(nil) } -func withCache[R any](cache cache.Cache, cfg *CacheCfg, cacheArgs []any, onCacheMiss func() (*R, error)) (*R, error) { +func withCache[R any](cache cache.Cache, cfg *CacheCfg, cacheKey []byte, onCacheMiss func() (*R, error)) (*R, error) { if cfg == nil { return onCacheMiss() } @@ -198,9 +195,7 @@ func withCache[R any](cache cache.Cache, cfg *CacheCfg, cacheArgs []any, onCache } isCacheable := cacheTTL > 0 - var cacheKey []byte if isCacheable { - cacheKey = generateCacheKey(cacheArgs) if cachedValue, ok := cache.Get(cacheKey); ok { // cloning? returnValue, ok := cachedValue.(*R) diff --git a/tools/walletextension/rpcapi/wallet_extension.go b/tools/walletextension/rpcapi/wallet_extension.go index c7b2e1c062..28ea48472d 100644 --- a/tools/walletextension/rpcapi/wallet_extension.go +++ b/tools/walletextension/rpcapi/wallet_extension.go @@ -121,6 +121,7 @@ func (w *Services) AddAddressToUser(userID []byte, address string, signature []b return err } + w.Cache.Remove(userCacheKey(userID)) audit(w, "Storing new address for user: %s, address: %s, duration: %d ", hexutils.BytesToHex(userID), address, time.Since(requestStartTime).Milliseconds()) return nil } @@ -161,7 +162,7 @@ func (w *Services) DeleteUser(userID []byte) error { w.Logger().Error(fmt.Errorf("error deleting user (%s), %w", userID, err).Error()) return err } - + w.Cache.Remove(userCacheKey(userID)) return nil } From 514f10bb5dc5f55ce5067b8736f39b7ba1a30ba0 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Tue, 26 Mar 2024 10:29:11 +0000 Subject: [PATCH 52/65] add conn pool for rpc backend --- go.mod | 15 ++--- go.sum | 8 ++- go/rpc/encrypted_client.go | 4 ++ go/rpc/network_client.go | 35 +++++++---- tools/walletextension/common/common.go | 6 +- tools/walletextension/rpcapi/filter_api.go | 14 ++++- tools/walletextension/rpcapi/gw_user.go | 19 +----- tools/walletextension/rpcapi/utils.go | 35 ++++++++++- .../rpcapi/wallet_extension.go | 60 +++++++++++++++---- 9 files changed, 138 insertions(+), 58 deletions(-) diff --git a/go.mod b/go.mod index e1be7176b9..06516ba0ce 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,9 @@ go 1.21 replace github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220224222438-c78f6963a1c0+incompatible require ( + github.com/Microsoft/go-winio v0.6.1 github.com/andybalholm/brotli v1.1.0 + github.com/deckarep/golang-set/v2 v2.6.0 github.com/dgraph-io/ristretto v0.1.1 github.com/docker/docker v25.0.4+incompatible github.com/docker/go-connections v0.5.0 @@ -17,14 +19,18 @@ require ( github.com/gin-gonic/gin v1.9.1 github.com/go-kit/kit v0.13.0 github.com/go-sql-driver/mysql v1.8.0 + github.com/gofrs/flock v0.8.1 github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/golang-jwt/jwt/v4 v4.5.0 github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.1 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/holiman/uint256 v1.2.4 + github.com/jolestar/go-commons-pool/v2 v2.1.2 github.com/mattn/go-sqlite3 v1.14.22 github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 github.com/pkg/errors v0.9.1 + github.com/rs/cors v1.10.1 github.com/sanity-io/litter v1.5.5 github.com/status-im/keycard-go v0.3.2 github.com/stretchr/testify v1.9.0 @@ -43,7 +49,6 @@ require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/DataDog/zstd v1.5.5 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect github.com/VictoriaMetrics/fastcache v1.12.2 // indirect github.com/allegro/bigcache v1.2.1 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -65,7 +70,6 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/deckarep/golang-set/v2 v2.6.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/distribution/reference v0.5.0 // indirect github.com/docker/distribution v2.8.3+incompatible // indirect @@ -86,9 +90,7 @@ require ( github.com/go-playground/validator/v10 v10.19.0 // indirect github.com/go-stack/stack v1.8.1 // indirect github.com/goccy/go-json v0.10.2 // indirect - github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/glog v1.2.0 // indirect github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.4 // indirect @@ -105,11 +107,8 @@ require ( github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/mitchellh/pointerstructure v1.2.1 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -127,8 +126,6 @@ require ( github.com/prometheus/procfs v0.13.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect - github.com/rs/cors v1.10.1 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/stretchr/objx v0.5.2 // indirect diff --git a/go.sum b/go.sum index 4f122dede7..f1251d5bde 100644 --- a/go.sum +++ b/go.sum @@ -128,6 +128,8 @@ github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA= github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= @@ -260,6 +262,8 @@ github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0Gqw github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jolestar/go-commons-pool/v2 v2.1.2 h1:E+XGo58F23t7HtZiC/W6jzO2Ux2IccSH/yx4nD+J1CM= +github.com/jolestar/go-commons-pool/v2 v2.1.2/go.mod h1:r4NYccrkS5UqP1YQI1COyTZ9UjPJAAGTUxzcsK1kqhY= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -309,7 +313,6 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= @@ -322,7 +325,6 @@ github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQhoHZAz4x7TIw= @@ -398,6 +400,7 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99 github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -577,7 +580,6 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/go/rpc/encrypted_client.go b/go/rpc/encrypted_client.go index a2122cac70..8b0d8635e8 100644 --- a/go/rpc/encrypted_client.go +++ b/go/rpc/encrypted_client.go @@ -68,6 +68,10 @@ func NewEncRPCClient(client Client, viewingKey *viewingkey.ViewingKey, logger ge return encClient, nil } +func (c *EncRPCClient) Client() Client { + return c.obscuroClient +} + // Call handles JSON rpc requests without a context - see CallContext for details func (c *EncRPCClient) Call(result interface{}, method string, args ...interface{}) error { return c.CallContext(nil, result, method, args...) //nolint:staticcheck diff --git a/go/rpc/network_client.go b/go/rpc/network_client.go index 22c9c4e64d..01f0284590 100644 --- a/go/rpc/network_client.go +++ b/go/rpc/network_client.go @@ -7,6 +7,7 @@ import ( "github.com/ten-protocol/go-ten/go/common/viewingkey" "github.com/ten-protocol/go-ten/lib/gethfork/rpc" + gethrpc "github.com/ten-protocol/go-ten/lib/gethfork/rpc" gethlog "github.com/ethereum/go-ethereum/log" ) @@ -16,9 +17,9 @@ const ( http = "http://" ) -// networkClient is a Client implementation that wraps Geth's rpc.Client to make calls to the obscuro node -type networkClient struct { - rpcClient *rpc.Client +// NetworkClient is a Client implementation that wraps Geth's rpc.Client to make calls to the obscuro node +type NetworkClient struct { + RpcClient *rpc.Client } // NewEncNetworkClient returns a network RPC client with Viewing Key encryption/decryption @@ -34,6 +35,14 @@ func NewEncNetworkClient(rpcAddress string, viewingKey *viewingkey.ViewingKey, l return encClient, nil } +func NewEncNetworkClientFromConn(connection *gethrpc.Client, viewingKey *viewingkey.ViewingKey, logger gethlog.Logger) (*EncRPCClient, error) { + encClient, err := NewEncRPCClient(&NetworkClient{RpcClient: connection}, viewingKey, logger) + if err != nil { + return nil, err + } + return encClient, nil +} + // NewNetworkClient returns a client that can make RPC calls to an Obscuro node func NewNetworkClient(address string) (Client, error) { if !strings.HasPrefix(address, http) && !strings.HasPrefix(address, ws) { @@ -45,25 +54,25 @@ func NewNetworkClient(address string) (Client, error) { return nil, fmt.Errorf("could not create RPC client on %s. Cause: %w", address, err) } - return &networkClient{ - rpcClient: rpcClient, + return &NetworkClient{ + RpcClient: rpcClient, }, nil } // Call handles JSON rpc requests, delegating to the geth RPC client // The result must be a pointer so that package json can unmarshal into it. You can also pass nil, in which case the result is ignored. -func (c *networkClient) Call(result interface{}, method string, args ...interface{}) error { - return c.rpcClient.Call(result, method, args...) +func (c *NetworkClient) Call(result interface{}, method string, args ...interface{}) error { + return c.RpcClient.Call(result, method, args...) } -func (c *networkClient) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { - return c.rpcClient.CallContext(ctx, result, method, args...) +func (c *NetworkClient) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { + return c.RpcClient.CallContext(ctx, result, method, args...) } -func (c *networkClient) Subscribe(ctx context.Context, _ interface{}, namespace string, channel interface{}, args ...interface{}) (*rpc.ClientSubscription, error) { - return c.rpcClient.Subscribe(ctx, namespace, channel, args...) +func (c *NetworkClient) Subscribe(ctx context.Context, _ interface{}, namespace string, channel interface{}, args ...interface{}) (*rpc.ClientSubscription, error) { + return c.RpcClient.Subscribe(ctx, namespace, channel, args...) } -func (c *networkClient) Stop() { - c.rpcClient.Close() +func (c *NetworkClient) Stop() { + c.RpcClient.Close() } diff --git a/tools/walletextension/common/common.go b/tools/walletextension/common/common.go index fd8a2025fe..59cb4b8a4b 100644 --- a/tools/walletextension/common/common.go +++ b/tools/walletextension/common/common.go @@ -5,6 +5,8 @@ import ( "fmt" "os" + gethrpc "github.com/ten-protocol/go-ten/lib/gethfork/rpc" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/ecies" "github.com/ten-protocol/go-ten/go/common/log" @@ -34,7 +36,7 @@ func BytesToPrivateKey(keyBytes []byte) (*ecies.PrivateKey, error) { } func CreateEncClient( - hostRPCBindAddr string, + conn *gethrpc.Client, addressBytes []byte, privateKeyBytes []byte, signature []byte, @@ -55,7 +57,7 @@ func CreateEncClient( SignatureWithAccountKey: signature, SignatureType: signatureType, } - encClient, err := rpc.NewEncNetworkClient(hostRPCBindAddr, vk, logger) + encClient, err := rpc.NewEncNetworkClientFromConn(conn, vk, logger) if err != nil { return nil, fmt.Errorf("unable to create EncRPCClient: %w", err) } diff --git a/tools/walletextension/rpcapi/filter_api.go b/tools/walletextension/rpcapi/filter_api.go index 54248e24be..a27f3247f3 100644 --- a/tools/walletextension/rpcapi/filter_api.go +++ b/tools/walletextension/rpcapi/filter_api.go @@ -7,6 +7,9 @@ import ( "sync/atomic" "time" + pool "github.com/jolestar/go-commons-pool/v2" + tenrpc "github.com/ten-protocol/go-ten/go/rpc" + gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ten-protocol/go-ten/go/common" @@ -60,11 +63,13 @@ func (api *FilterAPI) Logs(ctx context.Context, crit common.FilterCriteria) (*rp inputChannels := make([]chan common.IDAndLog, 0) backendSubscriptions := make([]*rpc.ClientSubscription, 0) + connections := make([]*tenrpc.EncRPCClient, 0) for _, address := range candidateAddresses { - rpcWSClient, err := user.accounts[*address].connect(api.we.HostAddrWS, api.we.Logger()) + rpcWSClient, err := connectWS(user.accounts[*address], api.we.Logger()) if err != nil { return nil, err } + connections = append(connections, rpcWSClient) inCh := make(chan common.IDAndLog) backendSubscription, err := rpcWSClient.Subscribe(ctx, nil, "eth", inCh, "logs", crit) @@ -95,7 +100,7 @@ func (api *FilterAPI) Logs(ctx context.Context, crit common.FilterCriteria) (*rp return nil }) - go handleUnsubscribe(subscription, backendSubscriptions, &unsubscribed) + go handleUnsubscribe(subscription, backendSubscriptions, connections, api.we.rpcWSConnPool, &unsubscribed) return subscription, err } @@ -178,12 +183,15 @@ func forwardAndDedupe[R any, T any](inputChannels []chan R, _ []*rpc.ClientSubsc } } -func handleUnsubscribe(connectionSub *rpc.Subscription, backendSubscriptions []*rpc.ClientSubscription, unsubscribed *atomic.Bool) { +func handleUnsubscribe(connectionSub *rpc.Subscription, backendSubscriptions []*rpc.ClientSubscription, connections []*tenrpc.EncRPCClient, p *pool.ObjectPool, unsubscribed *atomic.Bool) { <-connectionSub.Err() for _, backendSub := range backendSubscriptions { backendSub.Unsubscribe() unsubscribed.Store(true) } + for _, connection := range connections { + _ = returnConn(p, connection) + } } func (api *FilterAPI) NewFilter(crit common.FilterCriteria) (rpc.ID, error) { diff --git a/tools/walletextension/rpcapi/gw_user.go b/tools/walletextension/rpcapi/gw_user.go index efdb698e67..abd65df0fa 100644 --- a/tools/walletextension/rpcapi/gw_user.go +++ b/tools/walletextension/rpcapi/gw_user.go @@ -3,14 +3,10 @@ package rpcapi import ( "fmt" - "github.com/ten-protocol/go-ten/go/common/viewingkey" - "github.com/status-im/keycard-go/hexutils" + "github.com/ten-protocol/go-ten/go/common/viewingkey" "github.com/ethereum/go-ethereum/common" - gethlog "github.com/ethereum/go-ethereum/log" - "github.com/ten-protocol/go-ten/go/rpc" - wecommon "github.com/ten-protocol/go-ten/tools/walletextension/common" ) var userCacheKeyPrefix = []byte{0x0, 0x1, 0x2, 0x3} @@ -24,6 +20,7 @@ type GWAccount struct { type GWUser struct { userID []byte + services *Services accounts map[common.Address]*GWAccount userKey []byte } @@ -45,7 +42,7 @@ func userCacheKey(userID []byte) []byte { func getUser(userID []byte, s *Services) (*GWUser, error) { return withCache(s.Cache, &CacheCfg{TTL: longCacheTTL}, userCacheKey(userID), func() (*GWUser, error) { - result := GWUser{userID: userID, accounts: map[common.Address]*GWAccount{}} + result := GWUser{userID: userID, services: s, accounts: map[common.Address]*GWAccount{}} userPrivateKey, err := s.Storage.GetUserPrivateKey(userID) if err != nil { return nil, fmt.Errorf("user %s not found. %w", hexutils.BytesToHex(userID), err) @@ -63,13 +60,3 @@ func getUser(userID []byte, s *Services) (*GWUser, error) { return &result, nil }) } - -func (account *GWAccount) connect(url string, logger gethlog.Logger) (*rpc.EncRPCClient, error) { - // create a new client - // todo - close and cache - encClient, err := wecommon.CreateEncClient(url, account.address.Bytes(), account.user.userKey, account.signature, account.signatureType, logger) - if err != nil { - return nil, fmt.Errorf("error creating new client, %w", err) - } - return encClient, nil -} diff --git a/tools/walletextension/rpcapi/utils.go b/tools/walletextension/rpcapi/utils.go index da9b608026..12bbdc562f 100644 --- a/tools/walletextension/rpcapi/utils.go +++ b/tools/walletextension/rpcapi/utils.go @@ -7,6 +7,11 @@ import ( "fmt" "time" + gethlog "github.com/ethereum/go-ethereum/log" + pool "github.com/jolestar/go-commons-pool/v2" + tenrpc "github.com/ten-protocol/go-ten/go/rpc" + wecommon "github.com/ten-protocol/go-ten/tools/walletextension/common" + "github.com/ten-protocol/go-ten/go/common/viewingkey" "github.com/ten-protocol/go-ten/lib/gethfork/rpc" @@ -99,7 +104,7 @@ func ExecAuthRPC[R any](ctx context.Context, w *Services, cfg *ExecCfg, method s var rpcErr error for _, acct := range candidateAccts { var result *R - rpcClient, err := acct.connect(w.HostAddrHTTP, w.Logger()) + rpcClient, err := connectHTTP(acct, w.Logger()) if err != nil { rpcErr = err continue @@ -115,8 +120,10 @@ func ExecAuthRPC[R any](ctx context.Context, w *Services, cfg *ExecCfg, method s return nil, err } rpcErr = err + _ = returnConn(w.rpcHTTPConnPool, rpcClient) continue } + _ = returnConn(w.rpcHTTPConnPool, rpcClient) return result, nil } return nil, rpcErr @@ -236,3 +243,29 @@ func cacheTTLBlockNumber(lastBlock rpc.BlockNumber) time.Duration { } return shortCacheTTL } + +func connectHTTP(account *GWAccount, logger gethlog.Logger) (*tenrpc.EncRPCClient, error) { + return conn(account.user.services.rpcHTTPConnPool, account, logger) +} + +func connectWS(account *GWAccount, logger gethlog.Logger) (*tenrpc.EncRPCClient, error) { + return conn(account.user.services.rpcWSConnPool, account, logger) +} + +func conn(p *pool.ObjectPool, account *GWAccount, logger gethlog.Logger) (*tenrpc.EncRPCClient, error) { + connectionObj, err := p.BorrowObject(context.Background()) + if err != nil { + return nil, fmt.Errorf("cannot fetch rpc connection to backend node %w", err) + } + conn := connectionObj.(*rpc.Client) + encClient, err := wecommon.CreateEncClient(conn, account.address.Bytes(), account.user.userKey, account.signature, account.signatureType, logger) + if err != nil { + return nil, fmt.Errorf("error creating new client, %w", err) + } + return encClient, nil +} + +func returnConn(p *pool.ObjectPool, conn *tenrpc.EncRPCClient) error { + c := conn.Client().(*tenrpc.NetworkClient).RpcClient + return p.ReturnObject(context.Background(), c) +} diff --git a/tools/walletextension/rpcapi/wallet_extension.go b/tools/walletextension/rpcapi/wallet_extension.go index 28ea48472d..8d292df5f4 100644 --- a/tools/walletextension/rpcapi/wallet_extension.go +++ b/tools/walletextension/rpcapi/wallet_extension.go @@ -2,10 +2,14 @@ package rpcapi import ( "bytes" + "context" "encoding/hex" "fmt" "time" + pool "github.com/jolestar/go-commons-pool/v2" + gethrpc "github.com/ten-protocol/go-ten/lib/gethfork/rpc" + "github.com/status-im/keycard-go/hexutils" "github.com/ten-protocol/go-ten/tools/walletextension/cache" @@ -34,7 +38,10 @@ type Services struct { version string tenClient *obsclient.ObsClient Cache cache.Cache - Config *common.Config + // the OG maintains a connection pool of rpc connections to underlying nodes + rpcHTTPConnPool *pool.ObjectPool + rpcWSConnPool *pool.ObjectPool + Config *common.Config } func NewServices(hostAddrHTTP string, hostAddrWS string, storage storage.Storage, stopControl *stopcontrol.StopControl, version string, logger gethlog.Logger, config *common.Config) *Services { @@ -51,17 +58,48 @@ func NewServices(hostAddrHTTP string, hostAddrWS string, storage storage.Storage panic(err) } + factoryHTTP := pool.NewPooledObjectFactory( + func(context.Context) (interface{}, error) { + rpcClient, err := gethrpc.Dial(hostAddrHTTP) + if err != nil { + return nil, fmt.Errorf("could not create RPC client on %s. Cause: %w", hostAddrHTTP, err) + } + return rpcClient, nil + }, func(ctx context.Context, object *pool.PooledObject) error { + client := object.Object.(*gethrpc.Client) + client.Close() + return nil + }, nil, nil, nil) + + factoryWS := pool.NewPooledObjectFactory( + func(context.Context) (interface{}, error) { + rpcClient, err := gethrpc.Dial(hostAddrWS) + if err != nil { + return nil, fmt.Errorf("could not create RPC client on %s. Cause: %w", hostAddrWS, err) + } + return rpcClient, nil + }, func(ctx context.Context, object *pool.PooledObject) error { + client := object.Object.(*gethrpc.Client) + client.Close() + return nil + }, nil, nil, nil) + + cfg := pool.NewDefaultPoolConfig() + cfg.MaxTotal = 50 + return &Services{ - HostAddrHTTP: hostAddrHTTP, - HostAddrWS: hostAddrWS, - Storage: storage, - logger: logger, - FileLogger: newFileLogger, - stopControl: stopControl, - version: version, - tenClient: newTenClient, - Cache: newGatewayCache, - Config: config, + HostAddrHTTP: hostAddrHTTP, + HostAddrWS: hostAddrWS, + Storage: storage, + logger: logger, + FileLogger: newFileLogger, + stopControl: stopControl, + version: version, + tenClient: newTenClient, + Cache: newGatewayCache, + rpcHTTPConnPool: pool.NewObjectPool(context.Background(), factoryHTTP, cfg), + rpcWSConnPool: pool.NewObjectPool(context.Background(), factoryWS, cfg), + Config: config, } } From 0ddcdcb8bf54ff6ad4f537ebbe4dc53de619ee68 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Tue, 26 Mar 2024 11:58:31 +0000 Subject: [PATCH 53/65] disable cache --- go/common/log_events.go | 7 ++++--- go/rpc/network_client.go | 2 +- lib/gethfork/rpc/json.go | 1 - tools/walletextension/rpcapi/utils.go | 3 ++- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/go/common/log_events.go b/go/common/log_events.go index 6230eb16de..5d7f7e9461 100644 --- a/go/common/log_events.go +++ b/go/common/log_events.go @@ -89,13 +89,13 @@ func ToCriteria(jsonCriteria FilterCriteriaJSON) filters.FilterCriteria { } } -// duplicated from geth +var errInvalidTopic = errors.New("invalid topic(s)") + // FilterCriteria represents a request to create a new filter. // Same as ethereum.FilterQuery but with UnmarshalJSON() method. +// duplicated from geth to tweak the unmarshalling type FilterCriteria ethereum.FilterQuery -var errInvalidTopic = errors.New("invalid topic(s)") - // UnmarshalJSON sets *args fields with given data. func (args *FilterCriteria) UnmarshalJSON(data []byte) error { type input struct { @@ -110,6 +110,7 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error { var raw input if err := json.Unmarshal(data, &raw); err != nil { + // tweak to handle the case when an empty array is passed in by javascript libraries if strings.Contains(err.Error(), "cannot unmarshal array") { return nil } diff --git a/go/rpc/network_client.go b/go/rpc/network_client.go index 01f0284590..aa08c4274c 100644 --- a/go/rpc/network_client.go +++ b/go/rpc/network_client.go @@ -69,7 +69,7 @@ func (c *NetworkClient) CallContext(ctx context.Context, result interface{}, met return c.RpcClient.CallContext(ctx, result, method, args...) } -func (c *NetworkClient) Subscribe(ctx context.Context, _ interface{}, namespace string, channel interface{}, args ...interface{}) (*rpc.ClientSubscription, error) { +func (c *NetworkClient) Subscribe(ctx context.Context, _ interface{}, namespace string, channel interface{}, args ...interface{}) (*gethrpc.ClientSubscription, error) { return c.RpcClient.Subscribe(ctx, namespace, channel, args...) } diff --git a/lib/gethfork/rpc/json.go b/lib/gethfork/rpc/json.go index 0aaf4bbbc6..f815adc313 100644 --- a/lib/gethfork/rpc/json.go +++ b/lib/gethfork/rpc/json.go @@ -297,7 +297,6 @@ func isBatch(raw json.RawMessage) bool { // given types. It returns the parsed values or an error when the args could not be // parsed. Missing optional arguments are returned as reflect.Zero values. func parsePositionalArguments(rawArgs json.RawMessage, types []reflect.Type) ([]reflect.Value, error) { - fmt.Printf("Parse: %s\n", rawArgs) dec := json.NewDecoder(bytes.NewReader(rawArgs)) var args []reflect.Value tok, err := dec.Token() diff --git a/tools/walletextension/rpcapi/utils.go b/tools/walletextension/rpcapi/utils.go index 12bbdc562f..71693668aa 100644 --- a/tools/walletextension/rpcapi/utils.go +++ b/tools/walletextension/rpcapi/utils.go @@ -200,7 +200,8 @@ func withCache[R any](cache cache.Cache, cfg *CacheCfg, cacheKey []byte, onCache if cfg.TTLCallback != nil { cacheTTL = cfg.TTLCallback() } - isCacheable := cacheTTL > 0 + // isCacheable := cacheTTL > 0 + isCacheable := false if isCacheable { if cachedValue, ok := cache.Get(cacheKey); ok { From e2852c8fe50622112abc358e2c5c1f63bcb854b1 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Tue, 26 Mar 2024 12:28:17 +0000 Subject: [PATCH 54/65] reenable cache --- tools/walletextension/rpcapi/utils.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tools/walletextension/rpcapi/utils.go b/tools/walletextension/rpcapi/utils.go index 71693668aa..65177d7a39 100644 --- a/tools/walletextension/rpcapi/utils.go +++ b/tools/walletextension/rpcapi/utils.go @@ -30,7 +30,7 @@ const ( notAuthorised = "not authorised" longCacheTTL = 5 * time.Hour - shortCacheTTL = 1 * time.Second + shortCacheTTL = 100 * time.Millisecond ) var rpcNotImplemented = fmt.Errorf("rpc endpoint not implemented") @@ -200,8 +200,7 @@ func withCache[R any](cache cache.Cache, cfg *CacheCfg, cacheKey []byte, onCache if cfg.TTLCallback != nil { cacheTTL = cfg.TTLCallback() } - // isCacheable := cacheTTL > 0 - isCacheable := false + isCacheable := cacheTTL > 0 if isCacheable { if cachedValue, ok := cache.Get(cacheKey); ok { From 80c8f17e44332736983f2e0e3647cbe05547ae53 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Tue, 26 Mar 2024 12:42:24 +0000 Subject: [PATCH 55/65] cleanup --- integration/obscurogateway/tengateway_test.go | 5 +---- lib/gethfork/rpc/handler.go | 3 +-- lib/gethfork/rpc/server.go | 2 -- lib/gethfork/rpc/service.go | 2 +- testnet/launcher/eth2network/docker.go | 2 +- tools/walletextension/container_run.sh | 2 +- .../frontend/src/components/providers/wallet-provider.tsx | 6 +++--- tools/walletextension/main/cli.go | 2 +- tools/walletextension/main/main.go | 2 +- tools/walletextension/rpcapi/utils.go | 1 - 10 files changed, 10 insertions(+), 17 deletions(-) diff --git a/integration/obscurogateway/tengateway_test.go b/integration/obscurogateway/tengateway_test.go index 31f474a689..42f612d6bf 100644 --- a/integration/obscurogateway/tengateway_test.go +++ b/integration/obscurogateway/tengateway_test.go @@ -131,9 +131,6 @@ func testMultipleAccountsSubscription(t *testing.T, httpURL, wsURL string, w wal _, err = user0.HTTPClient.ChainID(context.Background()) require.NoError(t, err) - //_, err = user0.WSClient.BalanceAt(context.TODO(), user0.Wallets[0].Address(), nil) - //require.NoError(t, err) - user1, err := NewGatewayUser([]wallet.Wallet{datagenerator.RandomWallet(integration.TenChainID), datagenerator.RandomWallet(integration.TenChainID)}, httpURL, wsURL) require.NoError(t, err) testlog.Logger().Info("Created user with encryption token", "t", user1.tgClient.UserID()) @@ -875,7 +872,7 @@ func subscribeToEvents(addresses []gethcommon.Address, topics [][]gethcommon.Has // Make a subscription filterQuery := ethereum.FilterQuery{ Addresses: addresses, - FromBlock: big.NewInt(2), // todo (@ziga) - without those we get errors - fix that and make them configurable + FromBlock: big.NewInt(2), // ToBlock: big.NewInt(10000), Topics: topics, } diff --git a/lib/gethfork/rpc/handler.go b/lib/gethfork/rpc/handler.go index 7755b776eb..8836f3c6b5 100644 --- a/lib/gethfork/rpc/handler.go +++ b/lib/gethfork/rpc/handler.go @@ -19,7 +19,6 @@ package rpc import ( "context" "encoding/json" - "fmt" "reflect" "strconv" "strings" @@ -389,6 +388,7 @@ func (h *handler) startCallProc(fn func(*callProc)) { ctx, cancel := context.WithCancel(h.rootCtx) defer h.callWG.Done() defer cancel() + // handle the case when normal rpc calls are made over a ws connection if ctx.Value(GWTokenKey{}) == nil { ctx = context.WithValue(ctx, GWTokenKey{}, hexutils.BytesToHex(h.UserID)) } @@ -555,7 +555,6 @@ func (h *handler) handleSubscribe(cp *callProc, msg *jsonrpcMessage) *jsonrpcMes // Parse subscription name arg too, but remove it before calling the callback. argTypes := append([]reflect.Type{stringType}, callb.argTypes...) args, err := parsePositionalArguments(msg.Params, argTypes) - fmt.Printf("Subscribe %s\n", string(msg.Params)) if err != nil { return msg.errorResponse(&invalidParamsError{err.Error()}) } diff --git a/lib/gethfork/rpc/server.go b/lib/gethfork/rpc/server.go index 59af5e072e..686afe5a02 100644 --- a/lib/gethfork/rpc/server.go +++ b/lib/gethfork/rpc/server.go @@ -18,7 +18,6 @@ package rpc import ( "context" - "fmt" "io" "sync" "sync/atomic" @@ -157,7 +156,6 @@ func (s *Server) serveSingleRequest(ctx context.Context, codec ServerCodec) { if err != nil { if err != io.EOF { resp := errorMessage(&invalidMessageError{"parse error"}) - fmt.Printf(">> Parse error %s. requests: %v\n", err, reqs) codec.writeJSON(ctx, resp, true) } return diff --git a/lib/gethfork/rpc/service.go b/lib/gethfork/rpc/service.go index f8e90064fd..38f16bb26f 100644 --- a/lib/gethfork/rpc/service.go +++ b/lib/gethfork/rpc/service.go @@ -198,7 +198,7 @@ func (c *callback) call(ctx context.Context, method string, args []reflect.Value buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] log.Error("RPC method " + method + " crashed: " + fmt.Sprintf("%v\n%s", err, buf)) - errRes = &internalServerError{errcodePanic, fmt.Sprintf("method handler crashed:%v\n%s ", err, buf)} + errRes = &internalServerError{errcodePanic, "method handler crashed"} } }() // Run the callback. diff --git a/testnet/launcher/eth2network/docker.go b/testnet/launcher/eth2network/docker.go index 0fc73f23d4..144e13dbf4 100644 --- a/testnet/launcher/eth2network/docker.go +++ b/testnet/launcher/eth2network/docker.go @@ -50,7 +50,7 @@ func (n *Eth2Network) Start() error { } func (n *Eth2Network) IsReady() error { - timeout := 20 * time.Minute + timeout := 20 * time.Minute // this can be reduced when we no longer download the ethereum binaries interval := 2 * time.Second var dial *ethclient.Client var err error diff --git a/tools/walletextension/container_run.sh b/tools/walletextension/container_run.sh index 162cff74bb..8329fce4d1 100755 --- a/tools/walletextension/container_run.sh +++ b/tools/walletextension/container_run.sh @@ -14,7 +14,7 @@ host="0.0.0.0" nodeHost="erpc.sepolia-testnet.ten.xyz" nodePortHTTP=80 nodePortWS=81 -logPath="sys_out" +logPath="wallet_extension_logs.txt" databasePath=".obscuro/gateway_database.db" image="obscuronet/obscuro_gateway_sepolia_testnet:latest" diff --git a/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx b/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx index 22da64e2ab..81ab3481ba 100644 --- a/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx +++ b/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx @@ -67,9 +67,9 @@ export const WalletConnectionProvider = ({ setVersion(await fetchVersion()); } catch (error) { showToast( - ToastType.DESTRUCTIVE, - "Error initializing wallet connection. Please refresh the page." - ); + ToastType.DESTRUCTIVE, + error instanceof Error ? error.message : "Error initializing wallet connection. Please refresh the page." + ); } finally { setLoading(false); } diff --git a/tools/walletextension/main/cli.go b/tools/walletextension/main/cli.go index 0e7f564c4c..dbbe72a9a0 100644 --- a/tools/walletextension/main/cli.go +++ b/tools/walletextension/main/cli.go @@ -33,7 +33,7 @@ const ( nodeWebsocketPortUsage = "The port on which to connect to the Obscuro node via RPC over websockets. Default: 81." logPathName = "logPath" - logPathDefault = "sys_out" + logPathDefault = "wallet_extension_logs.txt" logPathUsage = "The path to use for the wallet extension's log file" databasePathName = "databasePath" diff --git a/tools/walletextension/main/main.go b/tools/walletextension/main/main.go index f07ee53da2..4b200968c0 100644 --- a/tools/walletextension/main/main.go +++ b/tools/walletextension/main/main.go @@ -53,7 +53,7 @@ func main() { } } - logLvl := gethlog.LvlDebug + logLvl := gethlog.LvlError if config.VerboseFlag { logLvl = gethlog.LvlDebug } diff --git a/tools/walletextension/rpcapi/utils.go b/tools/walletextension/rpcapi/utils.go index 65177d7a39..dda9589459 100644 --- a/tools/walletextension/rpcapi/utils.go +++ b/tools/walletextension/rpcapi/utils.go @@ -224,7 +224,6 @@ func withCache[R any](cache cache.Cache, cfg *CacheCfg, cacheKey []byte, onCache } func audit(services *Services, msg string, params ...any) { - println(fmt.Sprintf(msg, params...)) if services.Config.VerboseFlag { services.FileLogger.Info(fmt.Sprintf(msg, params...)) } From 5bf7c87946fba020c30640e15942229949fffc83 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Tue, 26 Mar 2024 12:57:38 +0000 Subject: [PATCH 56/65] fix --- go/obsclient/authclient.go | 5 ++--- integration/simulation/simulation.go | 3 +-- integration/simulation/validate_chain.go | 4 +--- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/go/obsclient/authclient.go b/go/obsclient/authclient.go index e484d5a3b8..6f4eec0495 100644 --- a/go/obsclient/authclient.go +++ b/go/obsclient/authclient.go @@ -9,7 +9,6 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/params" "github.com/ten-protocol/go-ten/go/common" "github.com/ten-protocol/go-ten/go/common/viewingkey" @@ -192,11 +191,11 @@ func (ac *AuthObsClient) BalanceAt(ctx context.Context, blockNumber *big.Int) (* return (*big.Int)(&result), err } -func (ac *AuthObsClient) SubscribeFilterLogs(ctx context.Context, filterCriteria filters.FilterCriteria, ch chan common.IDAndLog) (ethereum.Subscription, error) { +func (ac *AuthObsClient) SubscribeFilterLogs(ctx context.Context, filterCriteria common.FilterCriteria, ch chan common.IDAndLog) (ethereum.Subscription, error) { return ac.rpcClient.Subscribe(ctx, nil, rpc.SubscribeNamespace, ch, rpc.SubscriptionTypeLogs, filterCriteria) } -func (ac *AuthObsClient) GetLogs(ctx context.Context, filterCriteria filters.FilterCriteria) ([]*types.Log, error) { +func (ac *AuthObsClient) GetLogs(ctx context.Context, filterCriteria common.FilterCriteria) ([]*types.Log, error) { var result responses.LogsType err := ac.rpcClient.CallContext(ctx, &result, rpc.GetLogs, filterCriteria) if err != nil { diff --git a/integration/simulation/simulation.go b/integration/simulation/simulation.go index d2ab17b953..68dc37e895 100644 --- a/integration/simulation/simulation.go +++ b/integration/simulation/simulation.go @@ -11,7 +11,6 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth/filters" gethparams "github.com/ethereum/go-ethereum/params" "github.com/ten-protocol/go-ten/contracts/generated/MessageBus" "github.com/ten-protocol/go-ten/go/common" @@ -192,7 +191,7 @@ func (s *Simulation) trackLogs() { channel := make(chan common.IDAndLog, 1000) // To exercise the filtering mechanism, we subscribe for HOC events only, ignoring POC events. - hocFilter := filters.FilterCriteria{ + hocFilter := common.FilterCriteria{ Addresses: []gethcommon.Address{gethcommon.HexToAddress("0x" + testcommon.HOCAddr)}, } sub, err := client.SubscribeFilterLogs(context.Background(), hocFilter, channel) diff --git a/integration/simulation/validate_chain.go b/integration/simulation/validate_chain.go index e4e93ccfb0..5fa4809065 100644 --- a/integration/simulation/validate_chain.go +++ b/integration/simulation/validate_chain.go @@ -10,8 +10,6 @@ import ( "testing" "time" - "github.com/ethereum/go-ethereum/eth/filters" - "github.com/ten-protocol/go-ten/contracts/generated/MessageBus" testcommon "github.com/ten-protocol/go-ten/integration/common" @@ -629,7 +627,7 @@ func checkSubscribedLogs(t *testing.T, owner string, channel chan common.IDAndLo func checkSnapshotLogs(t *testing.T, client *obsclient.AuthObsClient) int { // To exercise the filtering mechanism, we get a snapshot for HOC events only, ignoring POC events. - hocFilter := filters.FilterCriteria{ + hocFilter := common.FilterCriteria{ Addresses: []gethcommon.Address{gethcommon.HexToAddress("0x" + testcommon.HOCAddr)}, } logs, err := client.GetLogs(context.Background(), hocFilter) From 030ed3f6081ab78d12b2f6ffbe6d0bf55b297df3 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Tue, 26 Mar 2024 15:05:10 +0000 Subject: [PATCH 57/65] fix --- tools/walletextension/api/server.go | 24 +++++++++++++++++++ tools/walletextension/rpcapi/filter_api.go | 6 ++--- .../walletextension_container.go | 12 ++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 tools/walletextension/api/server.go diff --git a/tools/walletextension/api/server.go b/tools/walletextension/api/server.go new file mode 100644 index 0000000000..6636e8381b --- /dev/null +++ b/tools/walletextension/api/server.go @@ -0,0 +1,24 @@ +package api + +import ( + "embed" + "fmt" + "io/fs" + "net/http" +) + +//go:embed all:static +var staticFiles embed.FS + +const ( + staticDir = "static" +) + +func StaticFilesHandler() http.Handler { + // Serves the web assets for the management of viewing keys. + noPrefixStaticFiles, err := fs.Sub(staticFiles, staticDir) + if err != nil { + panic(fmt.Sprintf("could not serve static files. Cause: %s", err)) + } + return http.FileServer(http.FS(noPrefixStaticFiles)) +} diff --git a/tools/walletextension/rpcapi/filter_api.go b/tools/walletextension/rpcapi/filter_api.go index a27f3247f3..3ad4e6b302 100644 --- a/tools/walletextension/rpcapi/filter_api.go +++ b/tools/walletextension/rpcapi/filter_api.go @@ -137,7 +137,7 @@ func searchForAddressInFilterCriteria(filterCriteria common.FilterCriteria, poss return result } -// forwardAndDedupe - reads messages from the input channel, and forwards them to the notifier only if they are new +// forwardAndDedupe - reads messages from the input channels, and forwards them to the notifier only if they are new func forwardAndDedupe[R any, T any](inputChannels []chan R, _ []*rpc.ClientSubscription, outSub *rpc.Subscription, notifier *rpc.Notifier, unsubscribed *atomic.Bool, toForward func(elem R) *T) { inputCases := make([]reflect.SelectCase, len(inputChannels)+1) @@ -164,7 +164,7 @@ func forwardAndDedupe[R any, T any](inputChannels []chan R, _ []*rpc.ClientSubsc switch v := value.Interface().(type) { case time.Time: - // exit the loop + // exit the loop to avoid a goroutine loop if unsubscribed.Load() { return } @@ -185,9 +185,9 @@ func forwardAndDedupe[R any, T any](inputChannels []chan R, _ []*rpc.ClientSubsc func handleUnsubscribe(connectionSub *rpc.Subscription, backendSubscriptions []*rpc.ClientSubscription, connections []*tenrpc.EncRPCClient, p *pool.ObjectPool, unsubscribed *atomic.Bool) { <-connectionSub.Err() + unsubscribed.Store(true) for _, backendSub := range backendSubscriptions { backendSub.Unsubscribe() - unsubscribed.Store(true) } for _, connection := range connections { _ = returnConn(p, connection) diff --git a/tools/walletextension/walletextension_container.go b/tools/walletextension/walletextension_container.go index 0692c521ea..b72c8c98e9 100644 --- a/tools/walletextension/walletextension_container.go +++ b/tools/walletextension/walletextension_container.go @@ -1,9 +1,12 @@ package walletextension import ( + "net/http" "os" "time" + "github.com/ten-protocol/go-ten/tools/walletextension/api" + "github.com/ten-protocol/go-ten/tools/walletextension/httpapi" "github.com/ten-protocol/go-ten/tools/walletextension/rpcapi" @@ -56,6 +59,15 @@ func NewContainerFromConfig(config wecommon.Config, logger gethlog.Logger) *Cont rpcServer.RegisterRoutes(httpapi.NewHTTPRoutes(walletExt)) + // register the static files + staticHandler := api.StaticFilesHandler() + rpcServer.RegisterRoutes([]node.Route{{ + Name: "/", + Func: func(resp http.ResponseWriter, req *http.Request) { + staticHandler.ServeHTTP(resp, req) + }, + }}) + // register all RPC endpoints exposed by a typical Geth node rpcServer.RegisterAPIs([]gethrpc.API{ { From e4d9e2d7e35786588ec6dbd12951055e4c49961c Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Tue, 26 Mar 2024 15:29:55 +0000 Subject: [PATCH 58/65] bump pool --- tools/walletextension/rpcapi/wallet_extension.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/walletextension/rpcapi/wallet_extension.go b/tools/walletextension/rpcapi/wallet_extension.go index 8d292df5f4..3b27bbe750 100644 --- a/tools/walletextension/rpcapi/wallet_extension.go +++ b/tools/walletextension/rpcapi/wallet_extension.go @@ -85,6 +85,7 @@ func NewServices(hostAddrHTTP string, hostAddrWS string, storage storage.Storage }, nil, nil, nil) cfg := pool.NewDefaultPoolConfig() + cfg.MaxTotal = 100 cfg.MaxTotal = 50 return &Services{ From 3b5189e234e351bd40a07e65badf00dbda802e99 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Tue, 26 Mar 2024 15:40:13 +0000 Subject: [PATCH 59/65] fix --- tools/walletextension/rpcapi/filter_api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/walletextension/rpcapi/filter_api.go b/tools/walletextension/rpcapi/filter_api.go index 3ad4e6b302..247d235b05 100644 --- a/tools/walletextension/rpcapi/filter_api.go +++ b/tools/walletextension/rpcapi/filter_api.go @@ -206,7 +206,7 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit common.FilterCriteria) ( cacheCfg: &CacheCfg{ TTLCallback: func() time.Duration { // when the toBlock is not specified, the request is open-ended - if crit.ToBlock != nil { + if crit.ToBlock != nil && crit.ToBlock.Int64() > 0 { return longCacheTTL } return shortCacheTTL From f5ee59d66d99da1de6f88f955b395664944225db Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Tue, 26 Mar 2024 15:45:45 +0000 Subject: [PATCH 60/65] fix --- tools/walletextension/api/static/nothing | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tools/walletextension/api/static/nothing diff --git a/tools/walletextension/api/static/nothing b/tools/walletextension/api/static/nothing new file mode 100644 index 0000000000..e69de29bb2 From bf2287e3b1353249eb45fb9a30c48b1a238cfb15 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Tue, 26 Mar 2024 16:26:55 +0000 Subject: [PATCH 61/65] comment static --- tools/walletextension/api/static/favicon.ico | Bin 0 -> 1260 bytes tools/walletextension/api/static/nothing | 0 .../walletextension_container.go | 17 +++++++---------- 3 files changed, 7 insertions(+), 10 deletions(-) create mode 100644 tools/walletextension/api/static/favicon.ico delete mode 100644 tools/walletextension/api/static/nothing diff --git a/tools/walletextension/api/static/favicon.ico b/tools/walletextension/api/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..0e8dea2f715d4fdc30aa83101021fe05fb3e2c21 GIT binary patch literal 1260 zcmVg-p z$Qi$Yq(b+^#>V2{-~fAj zdr*R;7$7`6JXjpa4Q5?#Z*NgtT#T`?F~~)-3!R*tFc+fLQ;L<96&xKMp#;e;MBLom zU}|a#+uPf?xVT{1#o5^zCMM$7gYGFWFGo*L50oI;g}%bY#RYM3afpbBKtMnMixmo3 zLy+=yb#-WIX@OEyGe92;3kxJCCnGvK8VLyr@bvW5UaPIGRYiH?4~)dq(-S5qC)q(+ zXK85(K|w)K8(A?RmDkr-wt>aR$7@~f?d=Wg>+4X8A{VNzu10!#`j^+k!^83X{H#5< zv9ZD2+#HJ$qM@MyO-)Tug5nq$8XEetG@ulzYXD(uYm4dWX|@RxN{vt@h_kb^S+%MU zrATrgeVm+}FgQ4fn3x!LuEYpcf_Qj%SdzS@r6pWlUA5U1^G8E_w3kzBqLq}Cr6{Mhw3z70Q zH8rd>CPqg`QCL{`L59-j%*>47ZhU=x&01Fa6@%g|q_2E`e?Lk}N@b&d& zg&gf2hRSieo|BWqo<;SNDCiByJ?!l4xW2wV?j8EiEBisl&CQM5-`{7~`SO9QtgQU@ znVp@T+{42I$2U%*bWl_awYRsUprC;L@9phHX=y22Vf~f&^YdfpKq}r(T7}!xV*d+lvQ;jqgOsMGNMhD$hQc_YN1>IQ>&5}rY-M&}|cXxLb z6%`4}2^x@*k@0C)l?o{?MR_R*8bB@J^71m2@YQ%l4FnCKnj|kT4{K{{_>G7PVlskK z<8S1DKk(DYHh`}%VQe-xHw9&68<3coh>eX67z^q#1_lNM=2)z|lvK^Ye2k!6=`Y#15sDDo^~AAf~3sSQzaipDr&iFYKeAG58Dg W(m0zVdEl%70000 Date: Wed, 27 Mar 2024 11:08:42 +0000 Subject: [PATCH 62/65] add static files --- go/common/log_events.go | 2 -- integration/obscurogateway/tengateway_test.go | 2 +- integration/tenscan/tenscan_test.go | 2 +- testnet/launcher/gateway/docker.go | 2 +- tools/walletextension/api/server.go | 6 ++--- tools/walletextension/common/constants.go | 2 +- .../frontend/src/routes/index.ts | 2 +- tools/walletextension/httpapi/routes.go | 8 +++--- .../walletextension_container.go | 26 +++++++++++-------- 9 files changed, 27 insertions(+), 25 deletions(-) diff --git a/go/common/log_events.go b/go/common/log_events.go index 5d7f7e9461..962762dbcf 100644 --- a/go/common/log_events.go +++ b/go/common/log_events.go @@ -106,8 +106,6 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error { Topics []interface{} `json:"topics"` } - fmt.Printf("FilterCriteria UnmarshalJSON %s\n", data) - var raw input if err := json.Unmarshal(data, &raw); err != nil { // tweak to handle the case when an empty array is passed in by javascript libraries diff --git a/integration/obscurogateway/tengateway_test.go b/integration/obscurogateway/tengateway_test.go index 42f612d6bf..c8846dedfc 100644 --- a/integration/obscurogateway/tengateway_test.go +++ b/integration/obscurogateway/tengateway_test.go @@ -802,7 +802,7 @@ func createTenNetwork(t *testing.T, startPort int) { func waitServerIsReady(serverAddr string) error { for now := time.Now(); time.Since(now) < 30*time.Second; time.Sleep(500 * time.Millisecond) { - statusCode, _, err := fasthttp.Get(nil, fmt.Sprintf("%s/health/", serverAddr)) + statusCode, _, err := fasthttp.Get(nil, fmt.Sprintf("%s/v1/health/", serverAddr)) if err != nil { // give it time to boot up if strings.Contains(err.Error(), "connection") { diff --git a/integration/tenscan/tenscan_test.go b/integration/tenscan/tenscan_test.go index 5a9030403c..7ff83fac14 100644 --- a/integration/tenscan/tenscan_test.go +++ b/integration/tenscan/tenscan_test.go @@ -189,7 +189,7 @@ func TestTenscan(t *testing.T) { func waitServerIsReady(serverAddr string) error { for now := time.Now(); time.Since(now) < 30*time.Second; time.Sleep(500 * time.Millisecond) { - statusCode, _, err := fasthttp.Get(nil, fmt.Sprintf("%s/health/", serverAddr)) + statusCode, _, err := fasthttp.Get(nil, fmt.Sprintf("%s/v1/health/", serverAddr)) if err != nil { // give it time to boot up if strings.Contains(err.Error(), "connection") { diff --git a/testnet/launcher/gateway/docker.go b/testnet/launcher/gateway/docker.go index 1827516cf9..3bde82b592 100644 --- a/testnet/launcher/gateway/docker.go +++ b/testnet/launcher/gateway/docker.go @@ -43,7 +43,7 @@ func (n *DockerGateway) IsReady() error { interval := time.Second return retry.Do(func() error { - statusCode, _, err := fasthttp.Get(nil, fmt.Sprintf("http://127.0.0.1:%d/health/", n.cfg.gatewayHTTPPort)) + statusCode, _, err := fasthttp.Get(nil, fmt.Sprintf("http://127.0.0.1:%d/v1/health/", n.cfg.gatewayHTTPPort)) if err != nil { return err } diff --git a/tools/walletextension/api/server.go b/tools/walletextension/api/server.go index 6636e8381b..3552888ae0 100644 --- a/tools/walletextension/api/server.go +++ b/tools/walletextension/api/server.go @@ -14,11 +14,11 @@ const ( staticDir = "static" ) -func StaticFilesHandler() http.Handler { +func StaticFilesHandler(prefix string) http.Handler { // Serves the web assets for the management of viewing keys. - noPrefixStaticFiles, err := fs.Sub(staticFiles, staticDir) + fileSystem, err := fs.Sub(staticFiles, staticDir) if err != nil { panic(fmt.Sprintf("could not serve static files. Cause: %s", err)) } - return http.FileServer(http.FS(noPrefixStaticFiles)) + return http.StripPrefix(prefix, http.FileServer(http.FS(fileSystem))) } diff --git a/tools/walletextension/common/constants.go b/tools/walletextension/common/constants.go index 4fd05e6e4d..aa81df8b64 100644 --- a/tools/walletextension/common/constants.go +++ b/tools/walletextension/common/constants.go @@ -15,8 +15,8 @@ const ( ) const ( + PathStatic = "/static/" PathReady = "/ready/" - PathViewingKeys = "/viewingkeys/" PathJoin = "/join/" PathGetMessage = "/getmessage/" PathAuthenticate = "/authenticate/" diff --git a/tools/walletextension/frontend/src/routes/index.ts b/tools/walletextension/frontend/src/routes/index.ts index f3ac4438f7..c53c3bc501 100644 --- a/tools/walletextension/frontend/src/routes/index.ts +++ b/tools/walletextension/frontend/src/routes/index.ts @@ -8,7 +8,7 @@ export const apiRoutes = { authenticate: `/${tenGatewayVersion}/authenticate/`, queryAccountToken: `/${tenGatewayVersion}/query/`, revoke: `/${tenGatewayVersion}/revoke/`, - version: `/version/`, + version: `/${tenGatewayVersion}/version/`, // **** INFO **** getHealthStatus: `/${tenGatewayVersion}/network-health/`, diff --git a/tools/walletextension/httpapi/routes.go b/tools/walletextension/httpapi/routes.go index d0901ebc1a..4f05e5b62f 100644 --- a/tools/walletextension/httpapi/routes.go +++ b/tools/walletextension/httpapi/routes.go @@ -23,7 +23,7 @@ import ( func NewHTTPRoutes(walletExt *rpcapi.Services) []node.Route { return []node.Route{ { - Name: common.PathReady, + Name: common.APIVersion1 + common.PathReady, Func: httpHandler(walletExt, readyRequestHandler), }, { @@ -47,15 +47,15 @@ func NewHTTPRoutes(walletExt *rpcapi.Services) []node.Route { Func: httpHandler(walletExt, revokeRequestHandler), }, { - Name: common.PathHealth, + Name: common.APIVersion1 + common.PathHealth, Func: httpHandler(walletExt, healthRequestHandler), }, { - Name: common.PathNetworkHealth, + Name: common.APIVersion1 + common.PathNetworkHealth, Func: httpHandler(walletExt, networkHealthRequestHandler), }, { - Name: common.PathVersion, + Name: common.APIVersion1 + common.PathVersion, Func: httpHandler(walletExt, versionRequestHandler), }, } diff --git a/tools/walletextension/walletextension_container.go b/tools/walletextension/walletextension_container.go index ec24e87c32..505832a98e 100644 --- a/tools/walletextension/walletextension_container.go +++ b/tools/walletextension/walletextension_container.go @@ -1,9 +1,12 @@ package walletextension import ( + "net/http" "os" "time" + "github.com/ten-protocol/go-ten/tools/walletextension/api" + "github.com/ten-protocol/go-ten/tools/walletextension/httpapi" "github.com/ten-protocol/go-ten/tools/walletextension/rpcapi" @@ -48,23 +51,14 @@ func NewContainerFromConfig(config wecommon.Config, logger gethlog.Logger) *Cont HTTPPort: config.WalletExtensionPortHTTP, EnableWs: true, WsPort: config.WalletExtensionPortWS, - WsPath: "/v1/", - HTTPPath: "/v1/", + WsPath: wecommon.APIVersion1 + "/", + HTTPPath: wecommon.APIVersion1 + "/", Host: config.WalletExtensionHost, } rpcServer := node.NewServer(cfg, logger) rpcServer.RegisterRoutes(httpapi.NewHTTPRoutes(walletExt)) - // register the static files - //staticHandler := api.StaticFilesHandler() - //rpcServer.RegisterRoutes([]node.Route{{ - // Name: "/", - // Func: func(resp http.ResponseWriter, req *http.Request) { - // staticHandler.ServeHTTP(resp, req) - // }, - //}}) - // register all RPC endpoints exposed by a typical Geth node rpcServer.RegisterAPIs([]gethrpc.API{ { @@ -88,6 +82,16 @@ func NewContainerFromConfig(config wecommon.Config, logger gethlog.Logger) *Cont }, }) + // register the static files + // todo - remove this when the frontend is no longer served from the enclave + staticHandler := api.StaticFilesHandler(wecommon.PathStatic) + rpcServer.RegisterRoutes([]node.Route{{ + Name: wecommon.PathStatic, + Func: func(resp http.ResponseWriter, req *http.Request) { + staticHandler.ServeHTTP(resp, req) + }, + }}) + return NewWalletExtensionContainer( stopControl, rpcServer, From 9964a613b370ed9217e9e81c95a6714510be3f41 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Wed, 27 Mar 2024 11:23:32 +0000 Subject: [PATCH 63/65] fix --- integration/tenscan/tenscan_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/tenscan/tenscan_test.go b/integration/tenscan/tenscan_test.go index 7ff83fac14..5a9030403c 100644 --- a/integration/tenscan/tenscan_test.go +++ b/integration/tenscan/tenscan_test.go @@ -189,7 +189,7 @@ func TestTenscan(t *testing.T) { func waitServerIsReady(serverAddr string) error { for now := time.Now(); time.Since(now) < 30*time.Second; time.Sleep(500 * time.Millisecond) { - statusCode, _, err := fasthttp.Get(nil, fmt.Sprintf("%s/v1/health/", serverAddr)) + statusCode, _, err := fasthttp.Get(nil, fmt.Sprintf("%s/health/", serverAddr)) if err != nil { // give it time to boot up if strings.Contains(err.Error(), "connection") { From 51f81b9f466f98faa4aeea1d901ad831c566f129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDiga=20Kokelj?= Date: Wed, 27 Mar 2024 12:43:28 +0100 Subject: [PATCH 64/65] serve static files with /static path --- tools/tenscan/frontend/pages/_app.tsx | 2 +- tools/tenscan/frontend/src/components/layouts/header.tsx | 2 +- tools/walletextension/frontend/next.config.js | 2 ++ .../walletextension/frontend/src/components/layouts/header.tsx | 2 +- tools/walletextension/frontend/src/pages/_app.tsx | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tools/tenscan/frontend/pages/_app.tsx b/tools/tenscan/frontend/pages/_app.tsx index 287ecf806d..e8c8d12077 100644 --- a/tools/tenscan/frontend/pages/_app.tsx +++ b/tools/tenscan/frontend/pages/_app.tsx @@ -69,7 +69,7 @@ export default function App({ Component, pageProps }: AppProps) { ogTwitterImage={siteMetadata.siteLogo} ogType={"website"} > - + diff --git a/tools/tenscan/frontend/src/components/layouts/header.tsx b/tools/tenscan/frontend/src/components/layouts/header.tsx index a04de0fea8..3a2e59b40a 100644 --- a/tools/tenscan/frontend/src/components/layouts/header.tsx +++ b/tools/tenscan/frontend/src/components/layouts/header.tsx @@ -14,7 +14,7 @@ export default function Header() {
Logo Logo - + From bbc9320de75df55e69d9bbb33d32edd5fcf26a68 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Wed, 27 Mar 2024 11:49:43 +0000 Subject: [PATCH 65/65] clean up conn pool --- tools/walletextension/rpcapi/utils.go | 39 ++++++++++++++------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/tools/walletextension/rpcapi/utils.go b/tools/walletextension/rpcapi/utils.go index dda9589459..a0f24c1c74 100644 --- a/tools/walletextension/rpcapi/utils.go +++ b/tools/walletextension/rpcapi/utils.go @@ -102,32 +102,30 @@ func ExecAuthRPC[R any](ctx context.Context, w *Services, cfg *ExecCfg, method s } var rpcErr error - for _, acct := range candidateAccts { - var result *R - rpcClient, err := connectHTTP(acct, w.Logger()) - if err != nil { - rpcErr = err - continue - } - adjustedArgs := args - if cfg.adjustArgs != nil { - adjustedArgs = cfg.adjustArgs(acct) - } - err = rpcClient.CallContext(ctx, &result, method, adjustedArgs...) + for i := range candidateAccts { + acct := candidateAccts[i] + result, err := withHTTPRPCConnection(w, acct, func(rpcClient *tenrpc.EncRPCClient) (*R, error) { + var result *R + adjustedArgs := args + if cfg.adjustArgs != nil { + adjustedArgs = cfg.adjustArgs(acct) + } + err := rpcClient.CallContext(ctx, &result, method, adjustedArgs...) + return result, err + }) if err != nil { // for calls where we know the expected error we can return early if cfg.tryUntilAuthorised && err.Error() != notAuthorised { return nil, err } rpcErr = err - _ = returnConn(w.rpcHTTPConnPool, rpcClient) continue } - _ = returnConn(w.rpcHTTPConnPool, rpcClient) return result, nil } return nil, rpcErr }) + audit(w, "RPC call. uid=%s, method=%s args=%v result=%s error=%s time=%d", hexutils.BytesToHex(userID), method, args, res, err, time.Since(requestStartTime).Milliseconds()) return res, err } @@ -243,10 +241,6 @@ func cacheTTLBlockNumber(lastBlock rpc.BlockNumber) time.Duration { return shortCacheTTL } -func connectHTTP(account *GWAccount, logger gethlog.Logger) (*tenrpc.EncRPCClient, error) { - return conn(account.user.services.rpcHTTPConnPool, account, logger) -} - func connectWS(account *GWAccount, logger gethlog.Logger) (*tenrpc.EncRPCClient, error) { return conn(account.user.services.rpcWSConnPool, account, logger) } @@ -268,3 +262,12 @@ func returnConn(p *pool.ObjectPool, conn *tenrpc.EncRPCClient) error { c := conn.Client().(*tenrpc.NetworkClient).RpcClient return p.ReturnObject(context.Background(), c) } + +func withHTTPRPCConnection[R any](w *Services, acct *GWAccount, execute func(*tenrpc.EncRPCClient) (*R, error)) (*R, error) { + rpcClient, err := conn(acct.user.services.rpcHTTPConnPool, acct, w.logger) + if err != nil { + return nil, fmt.Errorf("could not connect to backed. Cause: %w", err) + } + defer returnConn(w.rpcHTTPConnPool, rpcClient) + return execute(rpcClient) +}