From 9869bd5dbda101a50e9fa3db6901a40f7f28cd4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDiga=20Kokelj?= Date: Thu, 12 Dec 2024 11:26:11 +0100 Subject: [PATCH 1/4] remove getStorageAt method for returning userID and corresponding test --- go/common/custom_query_types.go | 1 - integration/tengateway/tengateway_test.go | 49 ------------------- .../walletextension/rpcapi/blockchain_api.go | 2 - 3 files changed, 52 deletions(-) diff --git a/go/common/custom_query_types.go b/go/common/custom_query_types.go index 662c71d6c9..58805b92b2 100644 --- a/go/common/custom_query_types.go +++ b/go/common/custom_query_types.go @@ -19,7 +19,6 @@ import "github.com/ethereum/go-ethereum/common" // CustomQuery methods const ( - UserIDRequestCQMethod = "0x0000000000000000000000000000000000000001" ListPrivateTransactionsCQMethod = "0x0000000000000000000000000000000000000002" CreateSessionKeyCQMethod = "0x0000000000000000000000000000000000000003" ActivateSessionKeyCQMethod = "0x0000000000000000000000000000000000000004" diff --git a/integration/tengateway/tengateway_test.go b/integration/tengateway/tengateway_test.go index 322bc1b4c7..ed995779fe 100644 --- a/integration/tengateway/tengateway_test.go +++ b/integration/tengateway/tengateway_test.go @@ -116,7 +116,6 @@ func TestTenGateway(t *testing.T) { "testSubscriptionTopics": testSubscriptionTopics, "testDifferentMessagesOnRegister": testDifferentMessagesOnRegister, "testInvokeNonSensitiveMethod": testInvokeNonSensitiveMethod, - "testGetStorageAtForReturningUserID": testGetStorageAtForReturningUserID, // "testRateLimiter": testRateLimiter, "testSessionKeys": testSessionKeys, } { @@ -899,54 +898,6 @@ func testInvokeNonSensitiveMethod(t *testing.T, _ int, httpURL, wsURL string, w } } -func testGetStorageAtForReturningUserID(t *testing.T, _ int, httpURL, wsURL string, w wallet.Wallet) { - user, err := NewGatewayUser([]wallet.Wallet{w}, httpURL, wsURL) - require.NoError(t, err) - - type JSONResponse struct { - Result string `json:"result"` - } - var response JSONResponse - - // make a request to GetStorageAt with correct parameters to get userID that exists in the database - respBody := makeHTTPEthJSONReq(httpURL, "eth_getStorageAt", user.tgClient.UserID(), []interface{}{common.UserIDRequestCQMethod, "0", nil}) - if err = json.Unmarshal(respBody, &response); err != nil { - t.Error("Unable to unmarshal response") - } - if !bytes.Equal(gethcommon.FromHex(response.Result), user.tgClient.UserIDBytes()) { - t.Errorf("Wrong ID 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, "eth_getStorageAt", "0x0000000000000000000000000000000000000001", []interface{}{common.UserIDRequestCQMethod, "0", nil}) - if !strings.Contains(string(respBody2), "not found") { - t.Error("eth_getStorageAt did not respond with not found error") - } - - err = user.RegisterAccounts() - if err != nil { - t.Errorf("Failed to register accounts: %s", err) - return - } - - // make a request to GetStorageAt with wrong parameters to get userID, but correct userID - respBody3 := makeHTTPEthJSONReq(httpURL, "eth_getStorageAt", user.tgClient.UserID(), []interface{}{"0x0000000000000000000000000000000000000007", "0", nil}) - expectedErr := "not supported" - if !strings.Contains(string(respBody3), expectedErr) { - t.Errorf("eth_getStorageAt did not respond with error: %s, it was: %s", expectedErr, string(respBody3)) - } - - privateTxs, _ := json.Marshal(common.ListPrivateTransactionsQueryParams{ - Address: gethcommon.HexToAddress("0xA58C60cc047592DE97BF1E8d2f225Fc5D959De77"), - Pagination: common.QueryPagination{Size: 10}, - }) - - respBody4 := makeHTTPEthJSONReq(httpURL, "eth_getStorageAt", user.tgClient.UserID(), []interface{}{common.ListPrivateTransactionsCQMethod, string(privateTxs), nil}) - if err = json.Unmarshal(respBody4, &response); err != nil { - t.Error("Unable to unmarshal response") - } -} - func makeRequestHTTP(url string, body []byte) []byte { generateViewingKeyBody := bytes.NewBuffer(body) resp, err := http.Post(url, "application/json", generateViewingKeyBody) //nolint:noctx,gosec diff --git a/tools/walletextension/rpcapi/blockchain_api.go b/tools/walletextension/rpcapi/blockchain_api.go index dbb085c650..9ae0a86e8e 100644 --- a/tools/walletextension/rpcapi/blockchain_api.go +++ b/tools/walletextension/rpcapi/blockchain_api.go @@ -190,8 +190,6 @@ func (api *BlockChainAPI) GetStorageAt(ctx context.Context, address gethcommon.A } switch address.Hex() { - case common.UserIDRequestCQMethod: // todo - review whether we need this endpoint - return user.ID, nil case common.ListPrivateTransactionsCQMethod: // sensitive CustomQuery methods use the convention of having "address" at the top level of the params json userAddr, err := extractCustomQueryAddress(params) From 24ea3ea286616bc35a0c5b1295ac1cd205399715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDiga=20Kokelj?= Date: Thu, 12 Dec 2024 11:58:59 +0100 Subject: [PATCH 2/4] fix sqlite local database --- .../storage/database/sqlite/sqlite.go | 130 +++++++++--------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/tools/walletextension/storage/database/sqlite/sqlite.go b/tools/walletextension/storage/database/sqlite/sqlite.go index 832fc1117c..32f47e784d 100644 --- a/tools/walletextension/storage/database/sqlite/sqlite.go +++ b/tools/walletextension/storage/database/sqlite/sqlite.go @@ -1,11 +1,19 @@ package sqlite /* - SQLite database implementation of the Storage interface + SQLite database implementation of the Storage interface. - SQLite is used for local deployments and testing without the need for a cloud database. - To make sure to see similar behaviour as in production using CosmosDB we use SQLite database in a similar way as comosDB (as key-value database). + This implementation mimics the CosmosDB approach where we store the entire user record (including accounts and session keys) + in a single JSON object within the 'users' table. There are no separate tables for accounts or session keys. + + Each user record: + { + "user_data": + } + + This simplifies the schema and keeps it similar to the CosmosDB container-based storage. */ + import ( "database/sql" "encoding/json" @@ -15,15 +23,14 @@ import ( "path/filepath" "github.com/ethereum/go-ethereum/crypto" + _ "github.com/mattn/go-sqlite3" // sqlite driver for sql.Open() dbcommon "github.com/ten-protocol/go-ten/tools/walletextension/storage/database/common" - "github.com/ten-protocol/go-ten/go/common/viewingkey" - "github.com/ten-protocol/go-ten/tools/walletextension/common" - - _ "github.com/mattn/go-sqlite3" // sqlite driver for sql.Open() obscurocommon "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/viewingkey" + "github.com/ten-protocol/go-ten/tools/walletextension/common" ) type SqliteDB struct { @@ -33,7 +40,7 @@ type SqliteDB struct { const sqliteCfg = "_foreign_keys=on&_journal_mode=wal&_txlock=immediate&_synchronous=normal" func NewSqliteDatabase(dbPath string) (*SqliteDB, error) { - // load the db file + // load or create the db file dbFilePath, err := createOrLoad(dbPath) if err != nil { return nil, err @@ -43,17 +50,16 @@ func NewSqliteDatabase(dbPath string) (*SqliteDB, error) { path := fmt.Sprintf("file:%s?%s", dbFilePath, sqliteCfg) db, err := sql.Open("sqlite3", path) if err != nil { - fmt.Println("Error opening database: ", err) - return nil, err + return nil, fmt.Errorf("error opening database: %w", err) } - // enable foreign keys in sqlite + // Enable foreign keys in SQLite (harmless, even though we don't use them now) _, err = db.Exec("PRAGMA foreign_keys = ON;") if err != nil { return nil, err } - // Modify the users table to store the entire GWUserDB as JSON + // Create the users table if it doesn't exist. We store entire user as JSON. _, err = db.Exec(`CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, user_data TEXT @@ -62,7 +68,9 @@ func NewSqliteDatabase(dbPath string) (*SqliteDB, error) { return nil, err } - // Remove the accounts table as it will be stored within the user_data JSON + // If there was an old 'accounts' table from a previous implementation, drop it. + // This ensures no leftover foreign key constraints cause issues. + _, _ = db.Exec("DROP TABLE IF EXISTS accounts;") return &SqliteDB{db: db}, nil } @@ -73,23 +81,23 @@ func (s *SqliteDB) AddUser(userID []byte, privateKey []byte) error { PrivateKey: privateKey, Accounts: []dbcommon.GWAccountDB{}, } + userJSON, err := json.Marshal(user) if err != nil { - return err + return fmt.Errorf("failed to marshal user data: %w", err) } return s.withTx(func(dbTx *sql.Tx) error { stmt, err := dbTx.Prepare("INSERT OR REPLACE INTO users(id, user_data) VALUES (?, ?)") if err != nil { - return err + return fmt.Errorf("failed to prepare insert statement: %w", err) } defer stmt.Close() _, err = stmt.Exec(string(user.UserId), string(userJSON)) if err != nil { - return err + return fmt.Errorf("failed to insert user: %w", err) } - return nil }) } @@ -98,7 +106,7 @@ func (s *SqliteDB) DeleteUser(userID []byte) error { return s.withTx(func(dbTx *sql.Tx) error { stmt, err := dbTx.Prepare("DELETE FROM users WHERE id = ?") if err != nil { - return err + return fmt.Errorf("failed to prepare delete statement: %w", err) } defer stmt.Close() @@ -106,18 +114,24 @@ func (s *SqliteDB) DeleteUser(userID []byte) error { if err != nil { return fmt.Errorf("failed to delete user: %w", err) } - return nil }) } -func (s *SqliteDB) ActivateSessionKey(userID []byte, active bool) error { +func (s *SqliteDB) AddAccount(userID []byte, accountAddress []byte, signature []byte, signatureType viewingkey.SignatureType) error { return s.withTx(func(dbTx *sql.Tx) error { user, err := s.readUser(dbTx, userID) if err != nil { return err } - user.ActiveSK = active + + newAccount := dbcommon.GWAccountDB{ + AccountAddress: accountAddress, + Signature: signature, + SignatureType: int(signatureType), + } + + user.Accounts = append(user.Accounts, newAccount) return s.updateUser(dbTx, user) }) } @@ -140,32 +154,24 @@ func (s *SqliteDB) AddSessionKey(userID []byte, key common.GWSessionKey) error { }) } -func (s *SqliteDB) RemoveSessionKey(userID []byte) error { +func (s *SqliteDB) ActivateSessionKey(userID []byte, active bool) error { return s.withTx(func(dbTx *sql.Tx) error { user, err := s.readUser(dbTx, userID) if err != nil { return err } - user.SessionKey = nil + user.ActiveSK = active return s.updateUser(dbTx, user) }) } -func (s *SqliteDB) AddAccount(userID []byte, accountAddress []byte, signature []byte, signatureType viewingkey.SignatureType) error { +func (s *SqliteDB) RemoveSessionKey(userID []byte) error { return s.withTx(func(dbTx *sql.Tx) error { user, err := s.readUser(dbTx, userID) if err != nil { return err } - - newAccount := dbcommon.GWAccountDB{ - AccountAddress: accountAddress, - Signature: signature, - SignatureType: int(signatureType), - } - - user.Accounts = append(user.Accounts, newAccount) - + user.SessionKey = nil return s.updateUser(dbTx, user) }) } @@ -175,10 +181,7 @@ func (s *SqliteDB) GetUser(userID []byte) (*common.GWUser, error) { var err error err = s.withTx(func(dbTx *sql.Tx) error { user, err = s.readUser(dbTx, userID) - if err != nil { - return err - } - return nil + return err }) if err != nil { return nil, err @@ -212,41 +215,19 @@ func (s *SqliteDB) updateUser(dbTx *sql.Tx, user dbcommon.GWUserDB) error { stmt, err := dbTx.Prepare("UPDATE users SET user_data = ? WHERE id = ?") if err != nil { - return err + return fmt.Errorf("failed to prepare update statement: %w", err) } defer stmt.Close() _, err = stmt.Exec(string(updatedUserJSON), string(user.UserId)) if err != nil { - return fmt.Errorf("failed to update user with new account: %w", err) + return fmt.Errorf("failed to update user: %w", err) } return nil } -func createOrLoad(dbPath string) (string, error) { - // If path is empty we create a random throwaway temp file, otherwise we use the path to the database - if dbPath == "" { - tempDir := filepath.Join("/tmp", "obscuro_gateway", obscurocommon.RandomStr(8)) - err := os.MkdirAll(tempDir, os.ModePerm) - if err != nil { - fmt.Println("Error creating directory: ", tempDir, err) - return "", err - } - dbPath = filepath.Join(tempDir, "gateway_databse.db") - } else { - dir := filepath.Dir(dbPath) - err := os.MkdirAll(dir, 0o755) - if err != nil { - fmt.Println("Error creating directories:", err) - return "", err - } - } - - return dbPath, nil -} - -// GetEncryptionKey returns nil for SQLite as it doesn't use encryption +// GetEncryptionKey returns nil for SQLite as it doesn't use encryption directly in this implementation. func (s *SqliteDB) GetEncryptionKey() []byte { return nil } @@ -254,14 +235,33 @@ func (s *SqliteDB) GetEncryptionKey() []byte { func (s *SqliteDB) withTx(fn func(*sql.Tx) error) error { tx, err := s.db.Begin() if err != nil { - return err + return fmt.Errorf("failed to begin transaction: %w", err) } defer tx.Rollback() - err = fn(tx) - if err != nil { + if err := fn(tx); err != nil { return err } return tx.Commit() } + +func createOrLoad(dbPath string) (string, error) { + // If path is empty we create a random temporary file, otherwise we use the provided path + if dbPath == "" { + tempDir := filepath.Join("/tmp", "obscuro_gateway", obscurocommon.RandomStr(8)) + err := os.MkdirAll(tempDir, os.ModePerm) + if err != nil { + return "", fmt.Errorf("error creating directory %s: %w", tempDir, err) + } + dbPath = filepath.Join(tempDir, "gateway_database.db") + } else { + dir := filepath.Dir(dbPath) + err := os.MkdirAll(dir, 0o755) + if err != nil { + return "", fmt.Errorf("error creating directories: %w", err) + } + } + + return dbPath, nil +} From fe053bad4f2d5026218fb9b531411303584fc609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDiga=20Kokelj?= Date: Thu, 12 Dec 2024 14:23:57 +0100 Subject: [PATCH 3/4] fix frontend to use local storage instead of getStorageAt --- packages/ui/routes/index.ts | 1 - .../frontend/src/api/ethRequests.ts | 23 +++++++------------ .../components/providers/wallet-provider.tsx | 4 ++-- .../frontend/src/services/ethService.ts | 2 +- .../src/services/useGatewayService.ts | 6 ++++- 5 files changed, 16 insertions(+), 20 deletions(-) diff --git a/packages/ui/routes/index.ts b/packages/ui/routes/index.ts index ddb30d872c..a2d5d960ad 100644 --- a/packages/ui/routes/index.ts +++ b/packages/ui/routes/index.ts @@ -2,7 +2,6 @@ export const requestMethods = { requestAccounts: "eth_requestAccounts", switchNetwork: "wallet_switchEthereumChain", addNetwork: "wallet_addEthereumChain", - getStorageAt: "eth_getStorageAt", signTypedData: "eth_signTypedData_v4", getChainId: "eth_chainId", }; diff --git a/tools/walletextension/frontend/src/api/ethRequests.ts b/tools/walletextension/frontend/src/api/ethRequests.ts index ed8583be6e..695e50346c 100644 --- a/tools/walletextension/frontend/src/api/ethRequests.ts +++ b/tools/walletextension/frontend/src/api/ethRequests.ts @@ -82,27 +82,20 @@ export const getSignature = async (account: string, data: any) => { } }; -export const getToken = async (provider: ethers.providers.Web3Provider) => { - if (!provider.send) { - return null; - } +export const getToken = async () => { try { - if (await isTenChain()) { - const token = await provider.send(requestMethods.getStorageAt, [ - userStorageAddress, - getRandomIntAsString(0, 1000), - null, - ]); - return token; - } else { - return null; - } + const token = localStorage.getItem('ten_token') || ''; + return token; } catch (e: any) { console.error(e); throw e; } }; +export const clearToken = () => { + localStorage.removeItem('ten_token'); +}; + export async function addNetworkToMetaMask(rpcUrls: string[]) { if (!ethereum) { throw "No ethereum object found"; @@ -150,7 +143,7 @@ export async function authenticateAccountWithTenGatewayEIP712( ...typedData, message: { ...typedData.message, - "Encryption Token": token, + "Encryption Token": token, }, }; const signature = await getSignature(account, data); diff --git a/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx b/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx index 11c77be0af..0f587e5bcc 100644 --- a/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx +++ b/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx @@ -56,7 +56,7 @@ export const WalletConnectionProvider = ({ try { await ethService.checkIfMetamaskIsLoaded(providerInstance); - const fetchedToken = await getToken(providerInstance); + const fetchedToken = await getToken(); setToken(fetchedToken); const status = await ethService.isUserConnectedToTenChain(fetchedToken); @@ -135,7 +135,7 @@ export const WalletConnectionProvider = ({ ); return; } - const token = await getToken(provider); + const token = await getToken(); if (!isValidTokenFormat(token)) { showToast( diff --git a/tools/walletextension/frontend/src/services/ethService.ts b/tools/walletextension/frontend/src/services/ethService.ts index 92d749b881..5b5f39feef 100644 --- a/tools/walletextension/frontend/src/services/ethService.ts +++ b/tools/walletextension/frontend/src/services/ethService.ts @@ -127,7 +127,7 @@ const ethService = { return; } - const token = await getToken(provider); + const token = await getToken(); if (!token || !isValidTokenFormat(token)) { return; diff --git a/tools/walletextension/frontend/src/services/useGatewayService.ts b/tools/walletextension/frontend/src/services/useGatewayService.ts index 5e25ea27ea..23a792b03f 100644 --- a/tools/walletextension/frontend/src/services/useGatewayService.ts +++ b/tools/walletextension/frontend/src/services/useGatewayService.ts @@ -52,10 +52,14 @@ const useGatewayService = () => { // SWITCHED_CODE=4902; error 4902 means that the chain does not exist if ( switched === SWITCHED_CODE || - !isValidTokenFormat(await getToken(provider)) + !isValidTokenFormat(await getToken()) ) { showToast(ToastType.INFO, "Adding TEN Testnet..."); const user = await joinTestnet(); + + // Store the token in localStorage + localStorage.setItem("ten_token", "0x" + user); + const rpcUrls = [ `${tenGatewayAddress}/${tenGatewayVersion}/?token=${user}`, ]; From 9572d887acd09146f38dc2fb9dd7eff3812ed5dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDiga=20Kokelj?= Date: Fri, 13 Dec 2024 08:42:17 +0100 Subject: [PATCH 4/4] clear local storage when revoking --- .../frontend/src/components/providers/wallet-provider.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx b/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx index 0f587e5bcc..24e7d6b04f 100644 --- a/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx +++ b/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx @@ -15,6 +15,7 @@ import { ToastType } from "@/types/interfaces"; import { authenticateAccountWithTenGatewayEIP712, getToken, + clearToken } from "@/api/ethRequests"; import { ethers } from "ethers"; import ethService from "@/services/ethService"; @@ -124,6 +125,7 @@ export const WalletConnectionProvider = ({ setAccounts(null); setWalletConnected(false); setToken(""); + clearToken(); } }; @@ -194,6 +196,7 @@ export const WalletConnectionProvider = ({ setAccounts(null); setWalletConnected(false); setToken(""); + clearToken(); } else { window.location.reload(); }