Skip to content

Commit

Permalink
feat(HIP-367) Mirror Node Queries (#930)
Browse files Browse the repository at this point in the history
* feat: Modified TokenRelationship and added fromJSON method

Signed-off-by: gsstoykov <georgi.stoykov@limechain.tech>

* feat: Added Mirror Node Gateway and modified AccountBalanceQuery to fetch from it

Signed-off-by: gsstoykov <georgi.stoykov@limechain.tech>

* chore: Added some comments and removed TokenRel deprecation from ContractInfo

Signed-off-by: gsstoykov <georgi.stoykov@limechain.tech>

* chore: Skipped mocking AccountBalance unit tests as they require data from Mirror Node

Signed-off-by: gsstoykov <georgi.stoykov@limechain.tech>

* feat: WIP for AccountInfoQuery

Signed-off-by: gsstoykov <georgi.stoykov@limechain.tech>

* feat: Added token relationship queries for AccountInfo, ContractInfo

Signed-off-by: gsstoykov <georgi.stoykov@limechain.tech>

* chore: Clean up redundant code and typos added test suite

Signed-off-by: gsstoykov <georgi.stoykov@limechain.tech>

* feat: Introduce sleep to tests which rely on mirror node updates

Signed-off-by: gsstoykov <georgi.stoykov@limechain.tech>

* feat: Refactor mirror_node_gateway. Removed redundant tests

Signed-off-by: gsstoykov <georgi.stoykov@limechain.tech>

* feat: Refactor AccountBalance

Signed-off-by: gsstoykov <georgi.stoykov@limechain.tech>

* feat: Added TokenRelationship unit tests

Signed-off-by: gsstoykov <georgi.stoykov@limechain.tech>

* feat: Added more ContractInfo tests

Signed-off-by: gsstoykov <georgi.stoykov@limechain.tech>

* chore: address PR comments

Signed-off-by: Ivan Ivanov <ivanivanov.ii726@gmail.com>

* chore: apply linter

Signed-off-by: Ivan Ivanov <ivanivanov.ii726@gmail.com>

* chore: address PR comments

Signed-off-by: Ivan Ivanov <ivanivanov.ii726@gmail.com>

* chore: address PR comments

Signed-off-by: Ivan Ivanov <ivanivanov.ii726@gmail.com>

* chore: update local node version

Signed-off-by: Ivan Ivanov <ivanivanov.ii726@gmail.com>

---------

Signed-off-by: gsstoykov <georgi.stoykov@limechain.tech>
Signed-off-by: Ivan Ivanov <ivanivanov.ii726@gmail.com>
Co-authored-by: Ivan Ivanov <ivanivanov.ii726@gmail.com>
  • Loading branch information
gsstoykov and 0xivanov authored May 28, 2024
1 parent a3f6b87 commit ada9950
Show file tree
Hide file tree
Showing 22 changed files with 758 additions and 99 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ jobs:

- name: Start the local node
if: success()
run: npx @hashgraph/hedera-local start -d --network local
run: npx @hashgraph/hedera-local start -d --network-tag=0.50.0-alpha.2

- name: Tests Unit
if: success()
Expand Down
8 changes: 2 additions & 6 deletions account_balance.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,9 @@ import (

type AccountBalance struct {
Hbars Hbar

// Deprecated: Use `AccountBalance.Tokens` instead
Token map[TokenID]uint64

// Deprecated
Tokens TokenBalanceMap
// Deprecated
Token map[TokenID]uint64
Tokens TokenBalanceMap
TokenDecimals TokenDecimalMap
}

Expand Down
44 changes: 43 additions & 1 deletion account_balance_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,49 @@ func (q *AccountBalanceQuery) Execute(client *Client) (AccountBalance, error) {
return AccountBalance{}, err
}

return _AccountBalanceFromProtobuf(resp.(*services.Response).GetCryptogetAccountBalance()), nil
balance := _AccountBalanceFromProtobuf(resp.(*services.Response).GetCryptogetAccountBalance())

// accountId value could be either in q.accountID or q.contractID
accountId := q.accountID.String()
if accountId == "" {
accountId = q.contractID.String()
}

err = fetchTokenBalances(fetchMirrorNodeUrlFromClient(client), accountId, &balance)
if err != nil {
return balance, err
}

return balance, nil
}

/*
Helper function, which queries the mirror node and if the balance query has tokens, it iterates over the tokens and
populates them in the appropriate AccountBalance tokens field.
IMPORTANT: This function will fetch the state of the data in the Mirror Node at the moment of its execution. It
is important to note that the Mirror Node currently needs 2-3 seconds to be updated with the latest data from the
consensus nodes. So if data related to token relationships is changed and a proper timeout is not introduced the
user would not get the up to date state of token relationships. This note is ONLY for token relationship data as it
is queried from the MirrorNode. Other query information arrives at the time of consensus response.
*/
func fetchTokenBalances(network string, id string, balance *AccountBalance) error {
response, err := accountTokenBalanceMirrorNodeQuery(network, id)
if err != nil {
return err
}

balance.Tokens.balances = make(map[string]uint64)

if tokens, ok := response["tokens"].([]interface{}); ok {
for _, token := range tokens {
if tokenJSON, ok := token.(map[string]interface{}); ok {
balance.Tokens.balances[tokenJSON["token_id"].(string)] = uint64(tokenJSON["balance"].(float64))
balance.TokenDecimals.decimals[tokenJSON["token_id"].(string)] = uint64(tokenJSON["decimals"].(float64))
}
}
}
return nil
}

// SetMaxQueryPayment sets the maximum payment allowed for this query.
Expand Down
9 changes: 6 additions & 3 deletions account_balance_query_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ package hedera

import (
"testing"
"time"

"github.com/stretchr/testify/assert"

Expand Down Expand Up @@ -77,15 +78,17 @@ func TestIntegrationAccountBalanceQueryCanGetTokenBalance(t *testing.T) {

tokenID := receipt.TokenID

// sleep in order for mirror node information to update
time.Sleep(3 * time.Second)

balance, err := NewAccountBalanceQuery().
SetNodeAccountIDs(env.NodeAccountIDs).
SetAccountID(env.Client.GetOperatorAccountID()).
Execute(env.Client)
require.NoError(t, err)

assert.Equal(t, balance, balance)
// TODO: assert.Equal(t, uint64(1000000), balance.Tokens.Get(*tokenID))
// TODO: assert.Equal(t, uint64(3), balance.TokenDecimals.Get(*tokenID))
assert.Equal(t, uint64(1000000), balance.Tokens.Get(*tokenID))
assert.Equal(t, uint64(3), balance.TokenDecimals.Get(*tokenID))
err = CloseIntegrationTestEnv(env, tokenID)
require.NoError(t, err)
}
Expand Down
2 changes: 2 additions & 0 deletions account_balance_query_unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ func TestUnitAccountBalanceQueryCoverage(t *testing.T) {
}

func TestUnitAccountBalanceQueryMock(t *testing.T) {
// TODO: Skip this test as mocking a query which fetches data from the Mirror Node has to be further discussed.
t.Skip()
t.Parallel()

responses := [][]interface{}{
Expand Down
13 changes: 13 additions & 0 deletions account_create_transaction_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ package hedera
import (
"fmt"
"testing"
"time"

"github.com/stretchr/testify/assert"

Expand Down Expand Up @@ -321,6 +322,9 @@ func TestIntegrationAccountCreateTransactionWithAliasFromAdminKey(t *testing.T)

accountID := *receipt.AccountID

// sleep in order for mirror node information to update
time.Sleep(3 * time.Second)

info, err := NewAccountInfoQuery().
SetAccountID(accountID).
Execute(env.Client)
Expand Down Expand Up @@ -364,6 +368,9 @@ func TestIntegrationAccountCreateTransactionWithAliasFromAdminKeyWithReceiverSig

accountID := *receipt.AccountID

// sleep in order for mirror node information to update
time.Sleep(3 * time.Second)

info, err := NewAccountInfoQuery().
SetAccountID(accountID).
Execute(env.Client)
Expand Down Expand Up @@ -441,6 +448,9 @@ func TestIntegrationAccountCreateTransactionWithAlias(t *testing.T) {

accountID := *receipt.AccountID

// sleep in order for mirror node information to update
time.Sleep(3 * time.Second)

info, err := NewAccountInfoQuery().
SetAccountID(accountID).
Execute(env.Client)
Expand Down Expand Up @@ -518,6 +528,9 @@ func TestIntegrationAccountCreateTransactionWithAliasWithReceiverSigRequired(t *

accountID := *receipt.AccountID

// sleep in order for mirror node information to update
time.Sleep(3 * time.Second)

info, err := NewAccountInfoQuery().
SetAccountID(accountID).
Execute(env.Client)
Expand Down
13 changes: 6 additions & 7 deletions account_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,12 @@ type AccountInfo struct {
ExpirationTime time.Time
AutoRenewPeriod time.Duration
LiveHashes []*LiveHash
// Deprecated
TokenRelationships []*TokenRelationship
AccountMemo string
OwnedNfts int64
MaxAutomaticTokenAssociations uint32
AliasKey *PublicKey
LedgerID LedgerID
TokenRelationships []*TokenRelationship
AccountMemo string
OwnedNfts int64
MaxAutomaticTokenAssociations uint32
AliasKey *PublicKey
LedgerID LedgerID
// Deprecated
HbarAllowances []HbarAllowance
// Deprecated
Expand Down
42 changes: 41 additions & 1 deletion account_info_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,47 @@ func (q *AccountInfoQuery) Execute(client *Client) (AccountInfo, error) {
return AccountInfo{}, err
}

return _AccountInfoFromProtobuf(resp.GetCryptoGetInfo().AccountInfo)
info, err := _AccountInfoFromProtobuf(resp.GetCryptoGetInfo().AccountInfo)
if err != nil {
return AccountInfo{}, err
}

err = fetchAccountInfoTokenRelationships(fetchMirrorNodeUrlFromClient(client), q.accountID.String(), &info)
if err != nil {
return info, err
}

return info, nil
}

/*
Helper function, which queries the mirror node and if the query result has token relations, it iterates over the token
relationships and populates the appropriate field in AccountInfo object
IMPORTANT: This function will fetch the state of the data in the Mirror Node at the moment of its execution. It
is important to note that the Mirror Node currently needs 2-3 seconds to be updated with the latest data from the
consensus nodes. So if data related to token relationships is changed and a proper timeout is not introduced the
user would not get the up to date state of token relationships. This note is ONLY for token relationship data as it
is queried from the MirrorNode. Other query information arrives at the time of consensus response.
*/
func fetchAccountInfoTokenRelationships(network string, id string, info *AccountInfo) error {
response, err := tokenRelationshipMirrorNodeQuery(network, id)
if err != nil {
return err
}

info.TokenRelationships = make([]*TokenRelationship, 0)

if tokens, ok := response["tokens"].([]interface{}); ok {
for _, token := range tokens {
tr, err := TokenRelationshipFromJson(token)
if err != nil {
return err
}
info.TokenRelationships = append(info.TokenRelationships, tr)
}
}
return nil
}

// SetGrpcDeadline When execution is attempted, a single attempt will timeout when this deadline is reached. (The SDK may subsequently retry the execution.)
Expand Down
Loading

0 comments on commit ada9950

Please sign in to comment.