Skip to content

Commit

Permalink
Merge pull request #235 from flow-hydraulics/feature/195-multiple-key…
Browse files Browse the repository at this point in the history
…s-for-custodial-accounts

Add multiple keys for new custodial accounts
  • Loading branch information
nanuuki authored Dec 20, 2021
2 parents 4c78f3e + ec33b8c commit 7f076bc
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 4 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,12 @@ 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.

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.
Expand Down
25 changes: 22 additions & 3 deletions accounts/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down Expand Up @@ -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
}
Expand Down
12 changes: 12 additions & 0 deletions api-test-scripts/scripts.http
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@emulatorCustodyAccount = 0x0000000000000000

### Really basic test
POST http://localhost:3000/v1/scripts HTTP/1.1
content-type: application/json
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions configs/configs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, does not affect existing accounts
DefaultAccountKeyCount uint `env:"FLOW_WALLET_DEFAULT_ACCOUNT_KEY_COUNT" envDefault:"1"`

// -- Database --

Expand Down
83 changes: 82 additions & 1 deletion main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"io/ioutil"
"sort"

"net/http"
"net/http/httptest"
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -380,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,
},
Expand Down

0 comments on commit 7f076bc

Please sign in to comment.