From e02ed46ae2b07dbf65ef2b1d7c7894cfbaed39e0 Mon Sep 17 00:00:00 2001 From: Andrii Date: Thu, 25 Jul 2024 15:15:06 +0300 Subject: [PATCH 1/9] Added request for new call --- .../rest/models/model_account_public_keys.go | 13 ++++++++ .../access/rest/request/get_account_keys.go | 1 + .../rest/request/get_account_keys_test.go | 1 + engine/access/rest/routes/account_keys.go | 33 ++++++++++++++++++- engine/access/rest/routes/router.go | 5 +++ 5 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 engine/access/rest/models/model_account_public_keys.go create mode 100644 engine/access/rest/request/get_account_keys.go create mode 100644 engine/access/rest/request/get_account_keys_test.go diff --git a/engine/access/rest/models/model_account_public_keys.go b/engine/access/rest/models/model_account_public_keys.go new file mode 100644 index 00000000000..81bc0adbcb6 --- /dev/null +++ b/engine/access/rest/models/model_account_public_keys.go @@ -0,0 +1,13 @@ +/* + * Access API + * + * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) + * + * API version: 1.0.0 + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package models + +type AccountPublicKeys struct { + Keys []AccountPublicKey `json:"keys"` +} diff --git a/engine/access/rest/request/get_account_keys.go b/engine/access/rest/request/get_account_keys.go new file mode 100644 index 00000000000..725b8fc210d --- /dev/null +++ b/engine/access/rest/request/get_account_keys.go @@ -0,0 +1 @@ +package request diff --git a/engine/access/rest/request/get_account_keys_test.go b/engine/access/rest/request/get_account_keys_test.go new file mode 100644 index 00000000000..725b8fc210d --- /dev/null +++ b/engine/access/rest/request/get_account_keys_test.go @@ -0,0 +1 @@ +package request diff --git a/engine/access/rest/routes/account_keys.go b/engine/access/rest/routes/account_keys.go index 55f601ec1a5..240f9f7e9bd 100644 --- a/engine/access/rest/routes/account_keys.go +++ b/engine/access/rest/routes/account_keys.go @@ -9,7 +9,7 @@ import ( ) // GetAccountKeyByIndex handler retrieves an account key by address and index and returns the response -func GetAccountKeyByIndex(r *request.Request, backend access.API, link models.LinkGenerator) (interface{}, error) { +func GetAccountKeyByIndex(r *request.Request, backend access.API, _ models.LinkGenerator) (interface{}, error) { req, err := r.GetAccountKeyRequest() if err != nil { return nil, models.NewBadRequestError(err) @@ -37,3 +37,34 @@ func GetAccountKeyByIndex(r *request.Request, backend access.API, link models.Li response.Build(*accountKey) return response, nil } + +// GetAccountKeys handler retrieves an account keys by address and returns the response +func GetAccountKeys(r *request.Request, backend access.API, _ models.LinkGenerator) (interface{}, error) { + req, err := r.GetAccountKeyRequest() + if err != nil { + return nil, models.NewBadRequestError(err) + } + + // In case we receive special height values 'final' and 'sealed', + // fetch that height and overwrite request with it. + isSealed := req.Height == request.SealedHeight + isFinalized := req.Height == request.FinalHeight + if isFinalized || isSealed { + header, _, err := backend.GetLatestBlockHeader(r.Context(), isSealed) + if err != nil { + err := fmt.Errorf("block with height: %d does not exist", req.Height) + return nil, models.NewNotFoundError(err.Error(), err) + } + req.Height = header.Height + } + + accountKeys, err := backend.GetAccountKeysAtBlockHeight(r.Context(), req.Address, req.Height) + if err != nil { + err = fmt.Errorf("failed to get account keys, reason: %w", err) + return nil, models.NewNotFoundError(err.Error(), err) + } + + var response models.AccountPublicKeys + response.Build(*accountKey) + return response, nil +} diff --git a/engine/access/rest/routes/router.go b/engine/access/rest/routes/router.go index 97e3dec07d6..7a82f34eb57 100644 --- a/engine/access/rest/routes/router.go +++ b/engine/access/rest/routes/router.go @@ -151,6 +151,11 @@ var Routes = []route{{ Pattern: "/accounts/{address}", Name: "getAccount", Handler: GetAccount, +}, { + Method: http.MethodGet, + Pattern: "/accounts/{address}/keys", + Name: "getAccountKeys", + Handler: GetAccountKeys, }, { Method: http.MethodGet, Pattern: "/accounts/{address}/keys/{index}", From 97c945dbab645f174db75d9c9b6041ed47c79962 Mon Sep 17 00:00:00 2001 From: Andrii Date: Thu, 25 Jul 2024 15:15:53 +0300 Subject: [PATCH 2/9] Added test for get account keys request --- engine/access/rest/models/account.go | 2 +- .../access/rest/request/get_account_keys.go | 44 +++++++++++++++++++ .../rest/request/get_account_keys_test.go | 43 ++++++++++++++++++ engine/access/rest/request/request.go | 6 +++ 4 files changed, 94 insertions(+), 1 deletion(-) diff --git a/engine/access/rest/models/account.go b/engine/access/rest/models/account.go index aea3601c2b1..dcf74c494a5 100644 --- a/engine/access/rest/models/account.go +++ b/engine/access/rest/models/account.go @@ -54,7 +54,7 @@ func (a *AccountPublicKey) Build(k flow.AccountPublicKey) { a.Revoked = k.Revoked } -type AccountPublicKeys []AccountPublicKey +type AccountKeys []AccountPublicKey func (a *AccountPublicKeys) Build(accountKeys []flow.AccountPublicKey) { keys := make([]AccountPublicKey, len(accountKeys)) diff --git a/engine/access/rest/request/get_account_keys.go b/engine/access/rest/request/get_account_keys.go index 725b8fc210d..6580135648f 100644 --- a/engine/access/rest/request/get_account_keys.go +++ b/engine/access/rest/request/get_account_keys.go @@ -1 +1,45 @@ package request + +import ( + "github.com/onflow/flow-go/model/flow" +) + +type GetAccountKeys struct { + Address flow.Address + Height uint64 +} + +func (g *GetAccountKeys) Build(r *Request) error { + return g.Parse( + r.GetVar(addressVar), + r.GetQueryParam(blockHeightQuery), + r.Chain, + ) +} + +func (g *GetAccountKeys) Parse( + rawAddress string, + rawHeight string, + chain flow.Chain, +) error { + address, err := ParseAddress(rawAddress, chain) + if err != nil { + return err + } + + var height Height + err = height.Parse(rawHeight) + if err != nil { + return err + } + + g.Address = address + g.Height = height.Flow() + + // default to last block + if g.Height == EmptyHeight { + g.Height = SealedHeight + } + + return nil +} diff --git a/engine/access/rest/request/get_account_keys_test.go b/engine/access/rest/request/get_account_keys_test.go index 725b8fc210d..e79b63e0f19 100644 --- a/engine/access/rest/request/get_account_keys_test.go +++ b/engine/access/rest/request/get_account_keys_test.go @@ -1 +1,44 @@ package request + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/onflow/flow-go/model/flow" +) + +func Test_GetAccountKeys_InvalidParse(t *testing.T) { + var getAccountKeys GetAccountKeys + + tests := []struct { + address string + height string + err string + }{ + {"", "", "invalid address"}, + {"f8d6e0586b0a20c7", "-1", "invalid height format"}, + } + + chain := flow.Localnet.Chain() + for i, test := range tests { + err := getAccountKeys.Parse(test.address, test.height, chain) + assert.EqualError(t, err, test.err, fmt.Sprintf("test #%d failed", i)) + } +} + +func Test_GetAccountKeys_ValidParse(t *testing.T) { + var getAccountKeys GetAccountKeys + + addr := "f8d6e0586b0a20c7" + chain := flow.Localnet.Chain() + err := getAccountKeys.Parse(addr, "", chain) + assert.NoError(t, err) + assert.Equal(t, getAccountKeys.Address.String(), addr) + assert.Equal(t, getAccountKeys.Height, SealedHeight) + + err = getAccountKeys.Parse(addr, "100", chain) + assert.NoError(t, err) + assert.Equal(t, getAccountKeys.Height, uint64(100)) +} diff --git a/engine/access/rest/request/request.go b/engine/access/rest/request/request.go index 9643fefaca4..071b063ad99 100644 --- a/engine/access/rest/request/request.go +++ b/engine/access/rest/request/request.go @@ -54,6 +54,12 @@ func (rd *Request) GetAccountRequest() (GetAccount, error) { return req, err } +func (rd *Request) GetAccountKeysRequest() (GetAccountKeys, error) { + var req GetAccountKeys + err := req.Build(rd) + return req, err +} + func (rd *Request) GetAccountKeyRequest() (GetAccountKey, error) { var req GetAccountKey err := req.Build(rd) From a0c2d707d5a2a356e585a4ed3aa719c7ee2d7cdd Mon Sep 17 00:00:00 2001 From: Andrii Date: Thu, 25 Jul 2024 16:06:18 +0300 Subject: [PATCH 3/9] Added missing implementation parts and added test for GetAccountKeys call --- .../rest/apiproxy/rest_proxy_handler.go | 33 ++++ engine/access/rest/models/account.go | 15 +- engine/access/rest/routes/account_keys.go | 4 +- .../access/rest/routes/account_keys_test.go | 173 +++++++++++++++++- 4 files changed, 220 insertions(+), 5 deletions(-) diff --git a/engine/access/rest/apiproxy/rest_proxy_handler.go b/engine/access/rest/apiproxy/rest_proxy_handler.go index ffe52c73fd9..cb3c4bc2c97 100644 --- a/engine/access/rest/apiproxy/rest_proxy_handler.go +++ b/engine/access/rest/apiproxy/rest_proxy_handler.go @@ -203,6 +203,39 @@ func (r *RestProxyHandler) GetAccountAtBlockHeight(ctx context.Context, address return convert.MessageToAccount(accountResponse.Account) } +// GetAccountKeys returns account keys by account address and block height. +func (r *RestProxyHandler) GetAccountKeys(ctx context.Context, address flow.Address, height uint64) ([]flow.AccountPublicKey, error) { + upstream, closer, err := r.FaultTolerantClient() + if err != nil { + return nil, err + } + defer closer.Close() + + getAccountKeysAtBlockHeightRequest := &accessproto.GetAccountKeysAtBlockHeightRequest{ + Address: address.Bytes(), + BlockHeight: height, + } + + accountKeyResponse, err := upstream.GetAccountKeysAtBlockHeight(ctx, getAccountKeysAtBlockHeightRequest) + r.log("upstream", "GetAccountKeysAtBlockHeight", err) + + if err != nil { + return nil, err + } + + accountKeys := make([]flow.AccountPublicKey, len(accountKeyResponse.GetAccountKeys())) + for i, key := range accountKeyResponse.GetAccountKeys() { + accountKey, err := convert.MessageToAccountKey(key) + if err != nil { + return nil, err + } + + accountKeys[i] = *accountKey + } + + return accountKeys, nil +} + // GetAccountKeyByIndex returns account key by account address, key index and block height. func (r *RestProxyHandler) GetAccountKeyByIndex(ctx context.Context, address flow.Address, keyIndex uint32, height uint64) (*flow.AccountPublicKey, error) { upstream, closer, err := r.FaultTolerantClient() diff --git a/engine/access/rest/models/account.go b/engine/access/rest/models/account.go index dcf74c494a5..375983dfaf6 100644 --- a/engine/access/rest/models/account.go +++ b/engine/access/rest/models/account.go @@ -14,7 +14,7 @@ func (a *Account) Build(flowAccount *flow.Account, link LinkGenerator, expand ma a.Expandable = &AccountExpandable{} if expand[expandableKeys] { - var keys AccountPublicKeys + var keys AccountKeys keys.Build(flowAccount.Keys) a.Keys = keys } else { @@ -56,7 +56,7 @@ func (a *AccountPublicKey) Build(k flow.AccountPublicKey) { type AccountKeys []AccountPublicKey -func (a *AccountPublicKeys) Build(accountKeys []flow.AccountPublicKey) { +func (a *AccountKeys) Build(accountKeys []flow.AccountPublicKey) { keys := make([]AccountPublicKey, len(accountKeys)) for i, k := range accountKeys { var key AccountPublicKey @@ -66,3 +66,14 @@ func (a *AccountPublicKeys) Build(accountKeys []flow.AccountPublicKey) { *a = keys } + +func (a *AccountPublicKeys) Build(accountKeys []flow.AccountPublicKey) { + keys := make([]AccountPublicKey, len(accountKeys)) + for i, k := range accountKeys { + var key AccountPublicKey + key.Build(k) + keys[i] = key + } + + a.Keys = keys +} diff --git a/engine/access/rest/routes/account_keys.go b/engine/access/rest/routes/account_keys.go index 240f9f7e9bd..c77655aba68 100644 --- a/engine/access/rest/routes/account_keys.go +++ b/engine/access/rest/routes/account_keys.go @@ -40,7 +40,7 @@ func GetAccountKeyByIndex(r *request.Request, backend access.API, _ models.LinkG // GetAccountKeys handler retrieves an account keys by address and returns the response func GetAccountKeys(r *request.Request, backend access.API, _ models.LinkGenerator) (interface{}, error) { - req, err := r.GetAccountKeyRequest() + req, err := r.GetAccountKeysRequest() if err != nil { return nil, models.NewBadRequestError(err) } @@ -65,6 +65,6 @@ func GetAccountKeys(r *request.Request, backend access.API, _ models.LinkGenerat } var response models.AccountPublicKeys - response.Build(*accountKey) + response.Build(accountKeys) return response, nil } diff --git a/engine/access/rest/routes/account_keys_test.go b/engine/access/rest/routes/account_keys_test.go index 58eb2695eca..b28429e204d 100644 --- a/engine/access/rest/routes/account_keys_test.go +++ b/engine/access/rest/routes/account_keys_test.go @@ -219,6 +219,128 @@ func TestGetAccountKeyByIndex(t *testing.T) { } } +// TestGetAccountKeys tests local getAccountKeys request. +// +// Runs the following tests: +// 1. Get keys by address at latest sealed block. +// 2. Get keys by address at latest finalized block. +// 3. Get keys by address at height. +// 4. Get key by address and index at missing block. +func TestGetAccountKeys(t *testing.T) { + backend := mock.NewAPI(t) + + t.Run("get keys by address at latest sealed block", func(t *testing.T) { + account := accountFixture(t) + var height uint64 = 100 + block := unittest.BlockHeaderFixture(unittest.WithHeaderHeight(height)) + + req := getAccountKeysRequest(t, account, sealedHeightQueryParam) + + backend.Mock. + On("GetLatestBlockHeader", mocktestify.Anything, true). + Return(block, flow.BlockStatusSealed, nil) + + backend.Mock. + On("GetAccountKeysAtBlockHeight", mocktestify.Anything, account.Address, height). + Return(account.Keys, nil) + + expected := expectedAccountKeysResponse(account) + + assertOKResponse(t, req, expected, backend) + mocktestify.AssertExpectationsForObjects(t, backend) + }) + + t.Run("get keys by address at latest finalized block", func(t *testing.T) { + account := accountFixture(t) + var height uint64 = 100 + block := unittest.BlockHeaderFixture(unittest.WithHeaderHeight(height)) + + req := getAccountKeysRequest(t, account, finalHeightQueryParam) + + backend.Mock. + On("GetLatestBlockHeader", mocktestify.Anything, false). + Return(block, flow.BlockStatusFinalized, nil) + + backend.Mock. + On("GetAccountKeysAtBlockHeight", mocktestify.Anything, account.Address, height). + Return(account.Keys, nil) + + expected := expectedAccountKeysResponse(account) + + assertOKResponse(t, req, expected, backend) + mocktestify.AssertExpectationsForObjects(t, backend) + }) + + t.Run("get keys by address at height", func(t *testing.T) { + var height uint64 = 1337 + account := accountFixture(t) + req := getAccountKeysRequest(t, account, "1337") + + backend.Mock. + On("GetAccountKeysAtBlockHeight", mocktestify.Anything, account.Address, height). + Return(account.Keys, nil) + + expected := expectedAccountKeysResponse(account) + + assertOKResponse(t, req, expected, backend) + mocktestify.AssertExpectationsForObjects(t, backend) + }) + + t.Run("get keys by address at missing block", func(t *testing.T) { + backend := mock.NewAPI(t) + account := accountFixture(t) + const finalHeight uint64 = math.MaxUint64 - 2 + + req := getAccountKeysRequest(t, account, finalHeightQueryParam) + + err := fmt.Errorf("block with height: %d does not exist", finalHeight) + backend.Mock. + On("GetLatestBlockHeader", mocktestify.Anything, false). + Return(nil, flow.BlockStatusUnknown, err) + + statusCode := 404 + expected := fmt.Sprintf(` + { + "code": %d, + "message": "block with height: %d does not exist" + } + `, statusCode, finalHeight) + + assertResponse(t, req, statusCode, expected, backend) + mocktestify.AssertExpectationsForObjects(t, backend) + }) + + tests := []struct { + name string + url string + out string + }{ + { + "get keys with invalid address", + accountKeysURL(t, "123", "100"), + `{"code":400, "message":"invalid address"}`, + }, + { + "get keys with invalid height", + accountKeysURL( + t, + unittest.AddressFixture().String(), + "-100", + ), + `{"code":400, "message":"invalid height format"}`, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + req, _ := http.NewRequest("GET", test.url, nil) + rr := executeRequest(req, backend) + assert.Equal(t, http.StatusBadRequest, rr.Code) + assert.JSONEq(t, test.out, rr.Body.String()) + }) + } +} + func accountKeyURL(t *testing.T, address string, index string, height string) string { u, err := url.ParseRequestURI( fmt.Sprintf("/v1/accounts/%s/keys/%s", address, index), @@ -234,6 +356,21 @@ func accountKeyURL(t *testing.T, address string, index string, height string) st return u.String() } +func accountKeysURL(t *testing.T, address string, height string) string { + u, err := url.ParseRequestURI( + fmt.Sprintf("/v1/accounts/%s/keys", address), + ) + require.NoError(t, err) + q := u.Query() + + if height != "" { + q.Add("block_height", height) + } + + u.RawQuery = q.Encode() + return u.String() +} + func getAccountKeyByIndexRequest( t *testing.T, account *flow.Account, @@ -250,6 +387,21 @@ func getAccountKeyByIndexRequest( return req } +func getAccountKeysRequest( + t *testing.T, + account *flow.Account, + height string, +) *http.Request { + req, err := http.NewRequest( + "GET", + accountKeysURL(t, account.Address.String(), height), + nil, + ) + require.NoError(t, err) + + return req +} + func expectedAccountKeyResponse(account *flow.Account) string { return fmt.Sprintf(` { @@ -265,9 +417,28 @@ func expectedAccountKeyResponse(account *flow.Account) string { ) } +func expectedAccountKeysResponse(account *flow.Account) string { + return fmt.Sprintf(` + { + "keys":[ + { + "index":"0", + "public_key":"%s", + "signing_algorithm":"ECDSA_P256", + "hashing_algorithm":"SHA3_256", + "sequence_number":"0", + "weight":"1000", + "revoked":false + } + ] + }`, + account.Keys[0].PublicKey.String(), + ) +} + func findAccountKeyByIndex(keys []flow.AccountPublicKey, keyIndex uint32) *flow.AccountPublicKey { for _, key := range keys { - if uint32(key.Index) == keyIndex { + if key.Index == keyIndex { return &key } } From f67a91be3d777ef63a4d4daf5ee25b32ae4dc8c3 Mon Sep 17 00:00:00 2001 From: Andrii Date: Fri, 26 Jul 2024 13:28:33 +0300 Subject: [PATCH 4/9] Added missing test cases --- engine/access/rest/request/get_account_keys_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/engine/access/rest/request/get_account_keys_test.go b/engine/access/rest/request/get_account_keys_test.go index e79b63e0f19..d37b4f82ac6 100644 --- a/engine/access/rest/request/get_account_keys_test.go +++ b/engine/access/rest/request/get_account_keys_test.go @@ -41,4 +41,12 @@ func Test_GetAccountKeys_ValidParse(t *testing.T) { err = getAccountKeys.Parse(addr, "100", chain) assert.NoError(t, err) assert.Equal(t, getAccountKeys.Height, uint64(100)) + + err = getAccountKeys.Parse(addr, sealed, chain) + assert.NoError(t, err) + assert.Equal(t, getAccountKeys.Height, SealedHeight) + + err = getAccountKeys.Parse(addr, final, chain) + assert.NoError(t, err) + assert.Equal(t, getAccountKeys.Height, FinalHeight) } From 22630f62bef4b462b9284d1efdddc7b8c2e2f1f0 Mon Sep 17 00:00:00 2001 From: Andrii Date: Fri, 26 Jul 2024 13:36:58 +0300 Subject: [PATCH 5/9] Updated normalizeUrl function --- engine/access/rest/routes/router.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/engine/access/rest/routes/router.go b/engine/access/rest/routes/router.go index 7a82f34eb57..ea698816625 100644 --- a/engine/access/rest/routes/router.go +++ b/engine/access/rest/routes/router.go @@ -236,7 +236,11 @@ func normalizeURL(url string) (string, error) { // address based resource. e.g. /v1/accounts/1234567890abcdef parts = append(parts, "{address}") if matches[0][5] == "keys" { - parts = append(parts, "keys", "{index}") + if len(matches[0][5]) != 0 { + parts = append(parts, "keys", "{index}") + } else { + parts = append(parts, "keys") + } } default: // named resource. e.g. /v1/network/parameters From fe90d8672d9df89d2ee53b31e028c726cbfcb3c9 Mon Sep 17 00:00:00 2001 From: Andrii Date: Tue, 30 Jul 2024 19:38:08 +0300 Subject: [PATCH 6/9] Fixed normilizeUrl function to habdle /keys case --- engine/access/rest/models/account.go | 1 + engine/access/rest/routes/router.go | 24 +++++++++++------------- engine/access/rest/routes/router_test.go | 10 ++++++++++ 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/engine/access/rest/models/account.go b/engine/access/rest/models/account.go index 375983dfaf6..d8bbf6b7f1e 100644 --- a/engine/access/rest/models/account.go +++ b/engine/access/rest/models/account.go @@ -67,6 +67,7 @@ func (a *AccountKeys) Build(accountKeys []flow.AccountPublicKey) { *a = keys } +// Build function use model AccountPublicKeys type for GetAccountKeys call func (a *AccountPublicKeys) Build(accountKeys []flow.AccountPublicKey) { keys := make([]AccountPublicKey, len(accountKeys)) for i, k := range accountKeys { diff --git a/engine/access/rest/routes/router.go b/engine/access/rest/routes/router.go index ea698816625..32d2d58defe 100644 --- a/engine/access/rest/routes/router.go +++ b/engine/access/rest/routes/router.go @@ -151,16 +151,16 @@ var Routes = []route{{ Pattern: "/accounts/{address}", Name: "getAccount", Handler: GetAccount, -}, { - Method: http.MethodGet, - Pattern: "/accounts/{address}/keys", - Name: "getAccountKeys", - Handler: GetAccountKeys, }, { Method: http.MethodGet, Pattern: "/accounts/{address}/keys/{index}", Name: "getAccountKeyByIndex", Handler: GetAccountKeyByIndex, +}, { + Method: http.MethodGet, + Pattern: "/accounts/{address}/keys", + Name: "getAccountKeys", + Handler: GetAccountKeys, }, { Method: http.MethodGet, Pattern: "/events", @@ -186,7 +186,7 @@ var WSRoutes = []wsroute{{ }} var routeUrlMap = map[string]string{} -var routeRE = regexp.MustCompile(`(?i)/v1/(\w+)(/(\w+)(/(\w+))?)?`) +var routeRE = regexp.MustCompile(`(?i)/v1/(\w+)(/(\w+))?(/(\w+))?(/(\w+))?`) func init() { for _, r := range Routes { @@ -212,7 +212,7 @@ func URLToRoute(url string) (string, error) { func normalizeURL(url string) (string, error) { matches := routeRE.FindAllStringSubmatch(url, -1) - if len(matches) != 1 || len(matches[0]) != 6 { + if len(matches) != 1 || len(matches[0]) != 8 { return "", fmt.Errorf("invalid url") } @@ -235,12 +235,10 @@ func normalizeURL(url string) (string, error) { case 16: // address based resource. e.g. /v1/accounts/1234567890abcdef parts = append(parts, "{address}") - if matches[0][5] == "keys" { - if len(matches[0][5]) != 0 { - parts = append(parts, "keys", "{index}") - } else { - parts = append(parts, "keys") - } + if matches[0][5] == "keys" && matches[0][7] != "" { + parts = append(parts, "keys", "{index}") + } else if matches[0][5] != "" { + parts = append(parts, matches[0][5]) } default: // named resource. e.g. /v1/network/parameters diff --git a/engine/access/rest/routes/router_test.go b/engine/access/rest/routes/router_test.go index 9112c408155..364a587cb59 100644 --- a/engine/access/rest/routes/router_test.go +++ b/engine/access/rest/routes/router_test.go @@ -74,6 +74,11 @@ func TestParseURL(t *testing.T) { url: "/v1/accounts/6a587be304c1224c/keys/0", expected: "getAccountKeyByIndex", }, + { + name: "/v1/accounts/{address}/keys", + url: "/v1/accounts/6a587be304c1224c/keys", + expected: "getAccountKeys", + }, { name: "/v1/events", url: "/v1/events", @@ -171,6 +176,11 @@ func TestBenchmarkParseURL(t *testing.T) { url: "/v1/accounts/6a587be304c1224c/keys/0", expected: "getAccountKeyByIndex", }, + { + name: "/v1/accounts/{address}/keys", + url: "/v1/accounts/6a587be304c1224c/keys", + expected: "getAccountKeys", + }, { name: "/v1/events", url: "/v1/events", From ba8c00d5f1b7ec3475e827d9739e68a8f5c63f19 Mon Sep 17 00:00:00 2001 From: Andrii Date: Tue, 30 Jul 2024 19:49:32 +0300 Subject: [PATCH 7/9] Added tests with mulpiple keys cases --- .../access/rest/routes/account_keys_test.go | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/engine/access/rest/routes/account_keys_test.go b/engine/access/rest/routes/account_keys_test.go index b28429e204d..fbc3ef3d01c 100644 --- a/engine/access/rest/routes/account_keys_test.go +++ b/engine/access/rest/routes/account_keys_test.go @@ -2,6 +2,8 @@ package routes import ( "fmt" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" "math" "net/http" "net/url" @@ -230,7 +232,7 @@ func TestGetAccountKeys(t *testing.T) { backend := mock.NewAPI(t) t.Run("get keys by address at latest sealed block", func(t *testing.T) { - account := accountFixture(t) + account := accountWithKeysFixture(t) var height uint64 = 100 block := unittest.BlockHeaderFixture(unittest.WithHeaderHeight(height)) @@ -251,7 +253,7 @@ func TestGetAccountKeys(t *testing.T) { }) t.Run("get keys by address at latest finalized block", func(t *testing.T) { - account := accountFixture(t) + account := accountWithKeysFixture(t) var height uint64 = 100 block := unittest.BlockHeaderFixture(unittest.WithHeaderHeight(height)) @@ -273,7 +275,7 @@ func TestGetAccountKeys(t *testing.T) { t.Run("get keys by address at height", func(t *testing.T) { var height uint64 = 1337 - account := accountFixture(t) + account := accountWithKeysFixture(t) req := getAccountKeysRequest(t, account, "1337") backend.Mock. @@ -288,7 +290,7 @@ func TestGetAccountKeys(t *testing.T) { t.Run("get keys by address at missing block", func(t *testing.T) { backend := mock.NewAPI(t) - account := accountFixture(t) + account := accountWithKeysFixture(t) const finalHeight uint64 = math.MaxUint64 - 2 req := getAccountKeysRequest(t, account, finalHeightQueryParam) @@ -429,10 +431,20 @@ func expectedAccountKeysResponse(account *flow.Account) string { "sequence_number":"0", "weight":"1000", "revoked":false + }, + { + "index":"0", + "public_key":"%s", + "signing_algorithm":"ECDSA_P256", + "hashing_algorithm":"SHA3_256", + "sequence_number":"0", + "weight":"500", + "revoked":false } ] }`, account.Keys[0].PublicKey.String(), + account.Keys[1].PublicKey.String(), ) } @@ -444,3 +456,15 @@ func findAccountKeyByIndex(keys []flow.AccountPublicKey, keyIndex uint32) *flow. } return &flow.AccountPublicKey{} } + +func accountWithKeysFixture(t *testing.T) *flow.Account { + account, err := unittest.AccountFixture() + + key2, err := unittest.AccountKeyFixture(128, crypto.ECDSAP256, hash.SHA3_256) + require.NoError(t, err) + + account.Keys = append(account.Keys, key2.PublicKey(500)) + + require.NoError(t, err) + return account +} From b3854ee275e1771aed0c954557e7c4f282441452 Mon Sep 17 00:00:00 2001 From: Andrii Diachuk Date: Wed, 31 Jul 2024 16:05:33 +0300 Subject: [PATCH 8/9] Update engine/access/rest/models/account.go Co-authored-by: Peter Argue <89119817+peterargue@users.noreply.github.com> --- engine/access/rest/models/account.go | 1 + 1 file changed, 1 insertion(+) diff --git a/engine/access/rest/models/account.go b/engine/access/rest/models/account.go index d8bbf6b7f1e..044c05f4982 100644 --- a/engine/access/rest/models/account.go +++ b/engine/access/rest/models/account.go @@ -68,6 +68,7 @@ func (a *AccountKeys) Build(accountKeys []flow.AccountPublicKey) { } // Build function use model AccountPublicKeys type for GetAccountKeys call +// AccountPublicKeys is an auto-generated type from the openapi spec func (a *AccountPublicKeys) Build(accountKeys []flow.AccountPublicKey) { keys := make([]AccountPublicKey, len(accountKeys)) for i, k := range accountKeys { From bc6862cfd8ba9790dfe807c3f8735374fa031a48 Mon Sep 17 00:00:00 2001 From: Andrii Date: Wed, 31 Jul 2024 16:49:54 +0300 Subject: [PATCH 9/9] Fixed remarks --- engine/access/rest/routes/account_keys_test.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/engine/access/rest/routes/account_keys_test.go b/engine/access/rest/routes/account_keys_test.go index fbc3ef3d01c..ecffbf4f3ca 100644 --- a/engine/access/rest/routes/account_keys_test.go +++ b/engine/access/rest/routes/account_keys_test.go @@ -2,8 +2,6 @@ package routes import ( "fmt" - "github.com/onflow/crypto" - "github.com/onflow/crypto/hash" "math" "net/http" "net/url" @@ -13,9 +11,11 @@ import ( mocktestify "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/access/mock" "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/utils/unittest" ) @@ -433,7 +433,7 @@ func expectedAccountKeysResponse(account *flow.Account) string { "revoked":false }, { - "index":"0", + "index":"1", "public_key":"%s", "signing_algorithm":"ECDSA_P256", "hashing_algorithm":"SHA3_256", @@ -459,12 +459,13 @@ func findAccountKeyByIndex(keys []flow.AccountPublicKey, keyIndex uint32) *flow. func accountWithKeysFixture(t *testing.T) *flow.Account { account, err := unittest.AccountFixture() + require.NoError(t, err) key2, err := unittest.AccountKeyFixture(128, crypto.ECDSAP256, hash.SHA3_256) require.NoError(t, err) account.Keys = append(account.Keys, key2.PublicKey(500)) + account.Keys[1].Index = 1 - require.NoError(t, err) return account }