From 88f6dd9de3f9c0da754f0a6321b06807389f40cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaus=20Dahl=C3=A9n?= Date: Fri, 10 Dec 2021 23:41:43 +0200 Subject: [PATCH 1/3] Add multiple keys for new custodial accounts --- README.md | 4 ++ accounts/service.go | 25 +++++++++-- api-test-scripts/scripts.http | 12 ++++++ configs/configs.go | 2 + main_test.go | 81 +++++++++++++++++++++++++++++++++++ 5 files changed, 121 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 035a6cff..d6988cd0 100644 --- a/README.md +++ b/README.md @@ -289,6 +289,10 @@ Valid log leves are (case-insensitive): debug trace +### Multiple keys for custodial accounts + +To enable multiple keys for custodial accounts you'll need to set `FLOW_WALLET_DEFAULT_ACCOUNT_KEY_COUNT` to the number of keys each account should have. When a new account is created the auto-generated account key is cloned so that the total number of keys matches the configured value. + ### All possible configuration variables Refer to [configs/configs.go](configs/configs.go) for details and documentation. diff --git a/accounts/service.go b/accounts/service.go index 0bfc549b..52afba2c 100644 --- a/accounts/service.go +++ b/accounts/service.go @@ -251,8 +251,19 @@ func (s *Service) createAccount(ctx context.Context) (*Account, string, error) { return nil, "", err } + // Public keys for creating the account + publicKeys := []*flow.AccountKey{} + + // Create copies based on the configured key count, changing just the index + for i := 0; i < int(s.cfg.DefaultAccountKeyCount); i++ { + clonedAccountKey := *accountKey + clonedAccountKey.Index = i + + publicKeys = append(publicKeys, &clonedAccountKey) + } + flowTx := flow_templates.CreateAccount( - []*flow.AccountKey{accountKey}, + publicKeys, nil, payer.Address, ) @@ -315,8 +326,16 @@ func (s *Service) createAccount(ctx context.Context) (*Account, string, error) { } encryptedAccountKey.PublicKey = accountKey.PublicKey.String() - // Store account and key - account.Keys = []keys.Storable{encryptedAccountKey} + // Store account and key(s) + // Looping through accountKeys to get the correct Index values + storableKeys := []keys.Storable{} + for _, pbk := range publicKeys { + clonedEncryptedAccountKey := encryptedAccountKey + clonedEncryptedAccountKey.Index = pbk.Index + storableKeys = append(storableKeys, clonedEncryptedAccountKey) + } + + account.Keys = storableKeys if err := s.store.InsertAccount(account); err != nil { return nil, "", err } diff --git a/api-test-scripts/scripts.http b/api-test-scripts/scripts.http index c924b220..34c67555 100644 --- a/api-test-scripts/scripts.http +++ b/api-test-scripts/scripts.http @@ -1,3 +1,5 @@ +@emulatorCustodyAccount = 0x0000000000000000 + ### Really basic test POST http://localhost:3000/v1/scripts HTTP/1.1 content-type: application/json @@ -26,6 +28,16 @@ content-type: application/json "arguments":[{"type":"Address","value":"{{$dotenv FLOW_WALLET_ADMIN_ADDRESS}}"}] } + +### Get FlowToken balance of custody account (flow-emulator) +POST http://localhost:3000/v1/scripts HTTP/1.1 +content-type: application/json + +{ + "code":"import FungibleToken from 0xee82856bf20e2aa6\nimport FlowToken from 0x0ae53cb6e3f42a79\npub fun main(account: Address): UFix64 {\nlet vaultRef = getAccount(account)\n.getCapability(/public/flowTokenBalance)\n.borrow<&FlowToken.Vault{FungibleToken.Balance}>()\n?? panic(\"Could not borrow Balance reference to the Vault\")\nreturn vaultRef.balance\n}", + "arguments":[{"type":"Address","value":"{{emulatorCustodyAccount}}"}] +} + ### Get ExampleNFT balance of admin account (flow-emulator) POST http://localhost:3000/v1/scripts HTTP/1.1 content-type: application/json diff --git a/configs/configs.go b/configs/configs.go index 7f773f6a..1c596ec2 100644 --- a/configs/configs.go +++ b/configs/configs.go @@ -71,6 +71,8 @@ type Config struct { EncryptionKey string `env:"FLOW_WALLET_ENCRYPTION_KEY,notEmpty"` // Encryption key type, one of: local, aws_kms, google_kms EncryptionKeyType string `env:"FLOW_WALLET_ENCRYPTION_KEY_TYPE,notEmpty" envDefault:"local"` + // DefaultAccountKeyCount specifies how many times the account key will be duplicated upon account creation + DefaultAccountKeyCount uint `env:"FLOW_WALLET_DEFAULT_ACCOUNT_KEY_COUNT" envDefault:"1"` // -- Database -- diff --git a/main_test.go b/main_test.go index e3ee1176..c63af328 100644 --- a/main_test.go +++ b/main_test.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "io/ioutil" + "sort" "net/http" "net/http/httptest" @@ -321,6 +322,86 @@ func TestAccountServices(t *testing.T) { t.Fatalf(`expected error to contain "%s" got: "%s"`, expected, job.Error) } }) + + t.Run("sync create with multiple keys", func(t *testing.T) { + cfg2 := getTestConfig(t) + cfg2.DefaultAccountKeyCount = 3 + + app2 := getTestApp(t, cfg2, true) + + _, acc, err := app2.AccountService.Create(context.Background(), true) + fatal(t, err) + + if len(acc.Keys) != int(cfg2.DefaultAccountKeyCount) { + t.Fatalf("incorrect number of keys for new account, expected %d, got %d", len(acc.Keys), cfg2.DefaultAccountKeyCount) + } + + // Keys should be clones w/ a running Index + indexes := []int{} + for _, key := range acc.Keys { + indexes = append(indexes, key.Index) + + // Check that all public keys are identical + if key.PublicKey != acc.Keys[0].PublicKey { + t.Fatalf("expected public keys to be identical") + } + } + + sort.Ints(indexes) + + expectedIndexes := []int{0, 1, 2} + if !reflect.DeepEqual(expectedIndexes, indexes) { + t.Fatalf("incorrect key indexes, expected %v, got %v", expectedIndexes, indexes) + } + }) + + t.Run("async create with multiple keys", func(t *testing.T) { + cfg2 := getTestConfig(t) + cfg2.DefaultAccountKeyCount = 3 + app2 := getTestApp(t, cfg2, true) + + job, _, err := app2.AccountService.Create(context.Background(), false) + fatal(t, err) + + if job.State != jobs.Init && job.State != jobs.Accepted && job.State != jobs.Complete { + t.Errorf("expected job status to be %s or %s or %s but got %s", + jobs.Init, jobs.Accepted, jobs.Complete, job.State) + } + + for job.State == jobs.Init || job.State == jobs.Accepted { + time.Sleep(10 * time.Millisecond) + } + + if job.State != jobs.Complete { + t.Errorf("expected job status to be %s got %s; job.Error: %s", jobs.Complete, job.State, job.Error) + } + + acc, err := app2.AccountService.Details(job.Result) + fatal(t, err) + + if len(acc.Keys) != int(cfg2.DefaultAccountKeyCount) { + t.Fatalf("incorrect number of keys for new account, expected %d, got %d", len(acc.Keys), cfg2.DefaultAccountKeyCount) + } + + // Keys should be clones w/ a running Index + indexes := []int{} + for _, key := range acc.Keys { + indexes = append(indexes, key.Index) + + // Check that all public keys are identical + if key.PublicKey != acc.Keys[0].PublicKey { + t.Fatalf("expected public keys to be identical") + } + } + + sort.Ints(indexes) + + expectedIndexes := []int{0, 1, 2} + if !reflect.DeepEqual(expectedIndexes, indexes) { + t.Fatalf("incorrect key indexes, expected %v, got %v", expectedIndexes, indexes) + } + + }) } func TestAccountHandlers(t *testing.T) { From 35ac6066d79f424e00cae7ccc774655c2a104f28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaus=20Dahl=C3=A9n?= Date: Wed, 15 Dec 2021 15:19:01 +0200 Subject: [PATCH 2/3] Update unknown address value --- main_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main_test.go b/main_test.go index c63af328..cf17c5d8 100644 --- a/main_test.go +++ b/main_test.go @@ -461,7 +461,7 @@ func TestAccountHandlers(t *testing.T) { { name: "details unknown address", method: http.MethodGet, - url: "/0f7025fa05b578e3", + url: "/e34ea67a850e1585", expected: "record not found", status: http.StatusNotFound, }, From ec33b8cb03d67c332da94b92e35155f262f36bec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaus=20Dahl=C3=A9n?= Date: Mon, 20 Dec 2021 09:28:07 +0200 Subject: [PATCH 3/3] Update DefaultAccountKeyCount documentation --- README.md | 2 ++ configs/configs.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d6988cd0..16edb1de 100644 --- a/README.md +++ b/README.md @@ -293,6 +293,8 @@ Valid log leves are (case-insensitive): To enable multiple keys for custodial accounts you'll need to set `FLOW_WALLET_DEFAULT_ACCOUNT_KEY_COUNT` to the number of keys each account should have. When a new account is created the auto-generated account key is cloned so that the total number of keys matches the configured value. +NOTE: Changing `FLOW_WALLET_DEFAULT_ACCOUNT_KEY_COUNT` does not affect _existing_ accounts. + ### All possible configuration variables Refer to [configs/configs.go](configs/configs.go) for details and documentation. diff --git a/configs/configs.go b/configs/configs.go index 1c596ec2..8ac64439 100644 --- a/configs/configs.go +++ b/configs/configs.go @@ -71,7 +71,7 @@ type Config struct { EncryptionKey string `env:"FLOW_WALLET_ENCRYPTION_KEY,notEmpty"` // Encryption key type, one of: local, aws_kms, google_kms EncryptionKeyType string `env:"FLOW_WALLET_ENCRYPTION_KEY_TYPE,notEmpty" envDefault:"local"` - // DefaultAccountKeyCount specifies how many times the account key will be duplicated upon account creation + // DefaultAccountKeyCount specifies how many times the account key will be duplicated upon account creation, does not affect existing accounts DefaultAccountKeyCount uint `env:"FLOW_WALLET_DEFAULT_ACCOUNT_KEY_COUNT" envDefault:"1"` // -- Database --