feat: suggested derivation path endpoint added

This commit is contained in:
Sale Djenic 2024-03-27 14:58:51 +01:00 committed by saledjenic
parent e9a2f19c17
commit caf3de1190
3 changed files with 290 additions and 0 deletions

View File

@ -24,6 +24,10 @@ const (
zeroAddress = "0x0000000000000000000000000000000000000000" zeroAddress = "0x0000000000000000000000000000000000000000"
SyncedFromBackup = "backup" // means a keypair is coming from backed up data SyncedFromBackup = "backup" // means a keypair is coming from backed up data
ThirtyDaysInMilliseconds = 30 * 24 * 60 * 60 * 1000 ThirtyDaysInMilliseconds = 30 * 24 * 60 * 60 * 1000
maxNumOfGeneratedAddresses = uint64(100)
numOfGeneratedAddressesRegularKeypair = maxNumOfGeneratedAddresses
numOfGeneratedAddressesKeycardKeypair = uint64(10)
) )
var ( var (
@ -1671,3 +1675,77 @@ func (db *Database) AddressWasShown(address types.Address) error {
_, err = tx.Exec(`UPDATE keypairs_accounts SET address_was_not_shown = 0 WHERE address = ?`, address) _, err = tx.Exec(`UPDATE keypairs_accounts SET address_was_not_shown = 0 WHERE address = ?`, address)
return err return err
} }
func (db *Database) resolveNumOfAddressesToGenerate(keypair *Keypair) uint64 {
if keypair == nil {
return maxNumOfGeneratedAddresses
}
if !keypair.MigratedToKeycard() {
return numOfGeneratedAddressesRegularKeypair
}
final := numOfGeneratedAddressesKeycardKeypair + keypair.LastUsedDerivationIndex
if final < maxNumOfGeneratedAddresses {
return final
}
return maxNumOfGeneratedAddresses
}
func (db *Database) GetNumOfAddressesToGenerateForKeypair(keyUID string) (uint64, error) {
kp, err := db.GetKeypairByKeyUID(keyUID)
if err != nil {
if err == ErrDbKeypairNotFound {
return maxNumOfGeneratedAddresses, nil
}
return 0, err
}
return db.resolveNumOfAddressesToGenerate(kp), nil
}
func (db *Database) ResolveSuggestedPathForKeypair(keyUID string) (suggestedPath string, err error) {
var tx *sql.Tx
tx, err = db.db.Begin()
if err != nil {
return "", err
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
_ = tx.Rollback()
}()
var kp *Keypair
kp, err = db.getKeypairByKeyUID(tx, keyUID, true)
if err != nil {
if err == ErrDbKeypairNotFound {
return fmt.Sprintf("%s0", statusWalletRootPath), nil
}
return "", err
}
numOfAddressesToGenerater := db.resolveNumOfAddressesToGenerate(kp)
nextIndex := kp.LastUsedDerivationIndex + 1
for i := nextIndex; i < numOfAddressesToGenerater; i++ {
suggestedPath := fmt.Sprintf("%s%d", statusWalletRootPath, i)
found := false
for _, acc := range kp.Accounts {
if acc.Path != suggestedPath {
continue
}
found = true
break
}
if !found {
return suggestedPath, nil
}
}
return "", errors.New("couldn't find available path for new account")
}

View File

@ -3,6 +3,7 @@ package accounts
import ( import (
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"fmt"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -459,3 +460,206 @@ func TestKeypairs(t *testing.T) {
}) })
} }
} }
func TestResolvingSuggestedDerivationPath(t *testing.T) {
kp := GetProfileKeypairForTest(true, true, true)
totalNumOfAccounts := len(kp.Accounts)
db, stop := setupTestDB(t)
defer stop()
// check the db
dbKp, err := db.GetKeypairByKeyUID(kp.KeyUID)
require.Error(t, err)
require.True(t, err == ErrDbKeypairNotFound)
require.Nil(t, dbKp)
expectedLastUsedDerivationIndex := uint64(2)
// save keypair
err = db.SaveOrUpdateKeypair(kp)
require.NoError(t, err)
dbKp, err = db.GetKeypairByKeyUID(kp.KeyUID)
require.NoError(t, err)
require.Equal(t, totalNumOfAccounts, len(dbKp.Accounts))
require.Equal(t, expectedLastUsedDerivationIndex, dbKp.LastUsedDerivationIndex)
// check number of addresses to generate
numOfAddresses, err := db.GetNumOfAddressesToGenerateForKeypair(kp.KeyUID)
require.NoError(t, err)
require.Equal(t, maxNumOfGeneratedAddresses, numOfAddresses)
// check suggested path
suggestedPath, err := db.ResolveSuggestedPathForKeypair(kp.KeyUID)
require.NoError(t, err)
require.Equal(t, fmt.Sprintf("%s%d", statusWalletRootPath, expectedLastUsedDerivationIndex+1), suggestedPath)
// prepare new account with the next suggested path
generatedWalletAccountThatWillBeRemovedLater := &Account{
Address: types.Address{0x05},
KeyUID: kp.KeyUID,
Wallet: false,
Chat: false,
Type: AccountTypeGenerated,
Path: suggestedPath,
PublicKey: types.Hex2Bytes("0x000000005"),
Name: "Generated Acc 4",
Emoji: "emoji-4",
ColorID: common.CustomizationColorPrimary,
Hidden: false,
Clock: 0,
Removed: false,
Operable: AccountFullyOperable,
ProdPreferredChainIDs: "1",
TestPreferredChainIDs: "5",
}
// add new account with the next suggested path
err = db.SaveOrUpdateAccounts([]*Account{generatedWalletAccountThatWillBeRemovedLater}, false)
require.NoError(t, err)
totalNumOfAccounts++
expectedLastUsedDerivationIndex++
dbKp, err = db.GetKeypairByKeyUID(kp.KeyUID)
require.NoError(t, err)
require.Equal(t, totalNumOfAccounts, len(dbKp.Accounts))
require.Equal(t, expectedLastUsedDerivationIndex, dbKp.LastUsedDerivationIndex)
// check suggested path
suggestedPath, err = db.ResolveSuggestedPathForKeypair(kp.KeyUID)
require.NoError(t, err)
require.Equal(t, fmt.Sprintf("%s%d", statusWalletRootPath, expectedLastUsedDerivationIndex+1), suggestedPath)
customSuggestedPath := fmt.Sprintf("%s%d", statusWalletRootPath, expectedLastUsedDerivationIndex+1+1)
// prepare new account with the custom suggested path
generatedWalletAccount := &Account{
Address: types.Address{0x07},
KeyUID: kp.KeyUID,
Wallet: false,
Chat: false,
Type: AccountTypeGenerated,
Path: customSuggestedPath,
PublicKey: types.Hex2Bytes("0x000000007"),
Name: "Generated Acc 6",
Emoji: "emoji-6",
ColorID: common.CustomizationColorPrimary,
Hidden: false,
Clock: 0,
Removed: false,
Operable: AccountFullyOperable,
ProdPreferredChainIDs: "1",
TestPreferredChainIDs: "5",
}
// add new account with the next suggested path
err = db.SaveOrUpdateAccounts([]*Account{generatedWalletAccount}, false)
require.NoError(t, err)
totalNumOfAccounts++
dbKp, err = db.GetKeypairByKeyUID(kp.KeyUID)
require.NoError(t, err)
require.Equal(t, totalNumOfAccounts, len(dbKp.Accounts))
require.Equal(t, expectedLastUsedDerivationIndex, dbKp.LastUsedDerivationIndex)
// check suggested path
suggestedPath, err = db.ResolveSuggestedPathForKeypair(kp.KeyUID)
require.NoError(t, err)
require.Equal(t, fmt.Sprintf("%s%d", statusWalletRootPath, expectedLastUsedDerivationIndex+1), suggestedPath)
// prepare new account with the next suggested path
generatedWalletAccount = &Account{
Address: types.Address{0x06},
KeyUID: kp.KeyUID,
Wallet: false,
Chat: false,
Type: AccountTypeGenerated,
Path: suggestedPath,
PublicKey: types.Hex2Bytes("0x000000006"),
Name: "Generated Acc 5",
Emoji: "emoji-5",
ColorID: common.CustomizationColorPrimary,
Hidden: false,
Clock: 0,
Removed: false,
Operable: AccountFullyOperable,
ProdPreferredChainIDs: "1",
TestPreferredChainIDs: "5",
}
// add new account with the next suggested path
err = db.SaveOrUpdateAccounts([]*Account{generatedWalletAccount}, false)
require.NoError(t, err)
totalNumOfAccounts++
expectedLastUsedDerivationIndex++
dbKp, err = db.GetKeypairByKeyUID(kp.KeyUID)
require.NoError(t, err)
require.Equal(t, totalNumOfAccounts, len(dbKp.Accounts))
require.Equal(t, expectedLastUsedDerivationIndex, dbKp.LastUsedDerivationIndex)
// check suggested path
suggestedPath, err = db.ResolveSuggestedPathForKeypair(kp.KeyUID)
require.NoError(t, err)
require.Equal(t, fmt.Sprintf("%s%d", statusWalletRootPath, expectedLastUsedDerivationIndex+2), suggestedPath)
// prepare new account with the next suggested path
generatedWalletAccount = &Account{
Address: types.Address{0x08},
KeyUID: kp.KeyUID,
Wallet: false,
Chat: false,
Type: AccountTypeGenerated,
Path: suggestedPath,
PublicKey: types.Hex2Bytes("0x000000008"),
Name: "Generated Acc 7",
Emoji: "emoji-7",
ColorID: common.CustomizationColorPrimary,
Hidden: false,
Clock: 0,
Removed: false,
Operable: AccountFullyOperable,
ProdPreferredChainIDs: "1",
TestPreferredChainIDs: "5",
}
// add new account with the next suggested path
err = db.SaveOrUpdateAccounts([]*Account{generatedWalletAccount}, false)
require.NoError(t, err)
totalNumOfAccounts++
expectedLastUsedDerivationIndex += 2
dbKp, err = db.GetKeypairByKeyUID(kp.KeyUID)
require.NoError(t, err)
require.Equal(t, totalNumOfAccounts, len(dbKp.Accounts))
require.Equal(t, expectedLastUsedDerivationIndex, dbKp.LastUsedDerivationIndex)
// check suggested path
suggestedPath, err = db.ResolveSuggestedPathForKeypair(kp.KeyUID)
require.NoError(t, err)
require.Equal(t, fmt.Sprintf("%s%d", statusWalletRootPath, expectedLastUsedDerivationIndex+1), suggestedPath)
// remove account
err = db.RemoveAccount(generatedWalletAccountThatWillBeRemovedLater.Address, 0)
require.NoError(t, err)
totalNumOfAccounts--
dbKp, err = db.GetKeypairByKeyUID(kp.KeyUID)
require.NoError(t, err)
require.Equal(t, totalNumOfAccounts, len(dbKp.Accounts))
require.Equal(t, expectedLastUsedDerivationIndex, dbKp.LastUsedDerivationIndex)
_, err = db.GetAccountByAddress(generatedWalletAccountThatWillBeRemovedLater.Address)
require.Error(t, err)
require.True(t, err == ErrDbAccountNotFound)
// check suggested path after removing account
suggestedPath, err = db.ResolveSuggestedPathForKeypair(kp.KeyUID)
require.NoError(t, err)
require.Equal(t, fmt.Sprintf("%s%d", statusWalletRootPath, expectedLastUsedDerivationIndex+1), suggestedPath)
}

View File

@ -612,3 +612,11 @@ func (api *API) UpdateKeycardUID(ctx context.Context, oldKeycardUID string, newK
func (api *API) AddressWasShown(address types.Address) error { func (api *API) AddressWasShown(address types.Address) error {
return api.db.AddressWasShown(address) return api.db.AddressWasShown(address)
} }
func (api *API) GetNumOfAddressesToGenerateForKeypair(keyUID string) (uint64, error) {
return api.db.GetNumOfAddressesToGenerateForKeypair(keyUID)
}
func (api *API) ResolveSuggestedPathForKeypair(keyUID string) (string, error) {
return api.db.ResolveSuggestedPathForKeypair(keyUID)
}