-
Notifications
You must be signed in to change notification settings - Fork 180
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6253 from AndriiDiachuk/add-rest-endpoint-GetAcco…
…untsBalance [Access] Add REST endpoint to get an accounts flow balance
- Loading branch information
Showing
11 changed files
with
339 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/* | ||
* 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 AccountBalance struct { | ||
// Flow balance of the account. | ||
Balance string `json:"balance"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package request | ||
|
||
import ( | ||
"github.com/onflow/flow-go/model/flow" | ||
) | ||
|
||
type GetAccountBalance struct { | ||
Address flow.Address | ||
Height uint64 | ||
} | ||
|
||
func (g *GetAccountBalance) Build(r *Request) error { | ||
return g.Parse( | ||
r.GetVar(addressVar), | ||
r.GetQueryParam(blockHeightQuery), | ||
r.Chain, | ||
) | ||
} | ||
|
||
func (g *GetAccountBalance) 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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package request | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
|
||
"github.com/onflow/flow-go/model/flow" | ||
) | ||
|
||
func Test_GetAccountBalance_InvalidParse(t *testing.T) { | ||
var getAccountBalance GetAccountBalance | ||
|
||
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 := getAccountBalance.Parse(test.address, test.height, chain) | ||
assert.EqualError(t, err, test.err, fmt.Sprintf("test #%d failed", i)) | ||
} | ||
} | ||
|
||
func Test_GetAccountBalance_ValidParse(t *testing.T) { | ||
|
||
var getAccountBalance GetAccountBalance | ||
|
||
addr := "f8d6e0586b0a20c7" | ||
chain := flow.Localnet.Chain() | ||
err := getAccountBalance.Parse(addr, "", chain) | ||
assert.NoError(t, err) | ||
assert.Equal(t, getAccountBalance.Address.String(), addr) | ||
assert.Equal(t, getAccountBalance.Height, SealedHeight) | ||
|
||
err = getAccountBalance.Parse(addr, "100", chain) | ||
assert.NoError(t, err) | ||
assert.Equal(t, getAccountBalance.Height, uint64(100)) | ||
|
||
err = getAccountBalance.Parse(addr, sealed, chain) | ||
assert.NoError(t, err) | ||
assert.Equal(t, getAccountBalance.Height, SealedHeight) | ||
|
||
err = getAccountBalance.Parse(addr, final, chain) | ||
assert.NoError(t, err) | ||
assert.Equal(t, getAccountBalance.Height, FinalHeight) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package routes | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/onflow/flow-go/access" | ||
"github.com/onflow/flow-go/engine/access/rest/models" | ||
"github.com/onflow/flow-go/engine/access/rest/request" | ||
) | ||
|
||
// GetAccountBalance handler retrieves an account balance by address and block height and returns the response | ||
func GetAccountBalance(r *request.Request, backend access.API, _ models.LinkGenerator) (interface{}, error) { | ||
req, err := r.GetAccountBalanceRequest() | ||
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 | ||
isFinal := req.Height == request.FinalHeight | ||
if isFinal || 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 | ||
} | ||
|
||
balance, err := backend.GetAccountBalanceAtBlockHeight(r.Context(), req.Address, req.Height) | ||
if err != nil { | ||
err = fmt.Errorf("failed to get account balance, reason: %w", err) | ||
return nil, models.NewNotFoundError(err.Error(), err) | ||
} | ||
|
||
var response models.AccountBalance | ||
response.Build(balance) | ||
return response, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package routes | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
mocktestify "github.com/stretchr/testify/mock" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/onflow/flow-go/access/mock" | ||
"github.com/onflow/flow-go/model/flow" | ||
"github.com/onflow/flow-go/utils/unittest" | ||
) | ||
|
||
// TestGetAccountBalance tests local getAccountBalance request. | ||
// | ||
// Runs the following tests: | ||
// 1. Get account balance by address at latest sealed block. | ||
// 2. Get account balance by address at latest finalized block. | ||
// 3. Get account balance by address at height. | ||
// 4. Get invalid account balance. | ||
func TestGetAccountBalance(t *testing.T) { | ||
backend := mock.NewAPI(t) | ||
|
||
t.Run("get balance by address at latest sealed block", func(t *testing.T) { | ||
account := accountFixture(t) | ||
var height uint64 = 100 | ||
block := unittest.BlockHeaderFixture(unittest.WithHeaderHeight(height)) | ||
|
||
req := getAccountBalanceRequest(t, account, sealedHeightQueryParam) | ||
|
||
backend.Mock. | ||
On("GetLatestBlockHeader", mocktestify.Anything, true). | ||
Return(block, flow.BlockStatusSealed, nil) | ||
|
||
backend.Mock. | ||
On("GetAccountBalanceAtBlockHeight", mocktestify.Anything, account.Address, height). | ||
Return(account.Balance, nil) | ||
|
||
expected := expectedAccountBalanceResponse(account) | ||
|
||
assertOKResponse(t, req, expected, backend) | ||
mocktestify.AssertExpectationsForObjects(t, backend) | ||
}) | ||
|
||
t.Run("get balance by address at latest finalized block", func(t *testing.T) { | ||
account := accountFixture(t) | ||
var height uint64 = 100 | ||
block := unittest.BlockHeaderFixture(unittest.WithHeaderHeight(height)) | ||
|
||
req := getAccountBalanceRequest(t, account, finalHeightQueryParam) | ||
|
||
backend.Mock. | ||
On("GetLatestBlockHeader", mocktestify.Anything, false). | ||
Return(block, flow.BlockStatusFinalized, nil) | ||
|
||
backend.Mock. | ||
On("GetAccountBalanceAtBlockHeight", mocktestify.Anything, account.Address, height). | ||
Return(account.Balance, nil) | ||
|
||
expected := expectedAccountBalanceResponse(account) | ||
|
||
assertOKResponse(t, req, expected, backend) | ||
mocktestify.AssertExpectationsForObjects(t, backend) | ||
}) | ||
|
||
t.Run("get balance by address at height", func(t *testing.T) { | ||
account := accountFixture(t) | ||
var height uint64 = 1337 | ||
req := getAccountBalanceRequest(t, account, fmt.Sprintf("%d", height)) | ||
|
||
backend.Mock. | ||
On("GetAccountBalanceAtBlockHeight", mocktestify.Anything, account.Address, height). | ||
Return(account.Balance, nil) | ||
|
||
expected := expectedAccountBalanceResponse(account) | ||
|
||
assertOKResponse(t, req, expected, backend) | ||
mocktestify.AssertExpectationsForObjects(t, backend) | ||
}) | ||
|
||
t.Run("get invalid", func(t *testing.T) { | ||
tests := []struct { | ||
url string | ||
out string | ||
}{ | ||
{accountBalanceURL(t, "123", ""), `{"code":400, "message":"invalid address"}`}, | ||
{accountBalanceURL(t, unittest.AddressFixture().String(), "foo"), `{"code":400, "message":"invalid height format"}`}, | ||
} | ||
|
||
for i, test := range tests { | ||
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(), fmt.Sprintf("test #%d failed: %v", i, test)) | ||
} | ||
}) | ||
} | ||
|
||
func accountBalanceURL(t *testing.T, address string, height string) string { | ||
u, err := url.ParseRequestURI(fmt.Sprintf("/v1/accounts/%s/balance", address)) | ||
require.NoError(t, err) | ||
q := u.Query() | ||
|
||
if height != "" { | ||
q.Add("block_height", height) | ||
} | ||
|
||
u.RawQuery = q.Encode() | ||
return u.String() | ||
} | ||
|
||
func getAccountBalanceRequest(t *testing.T, account *flow.Account, height string) *http.Request { | ||
req, err := http.NewRequest( | ||
"GET", | ||
accountBalanceURL(t, account.Address.String(), height), | ||
nil, | ||
) | ||
|
||
require.NoError(t, err) | ||
return req | ||
} | ||
|
||
func expectedAccountBalanceResponse(account *flow.Account) string { | ||
return fmt.Sprintf(` | ||
{ | ||
"balance":"%d" | ||
}`, | ||
account.Balance, | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters