Skip to content

Commit

Permalink
tests: almanac registrations
Browse files Browse the repository at this point in the history
  • Loading branch information
bryanchriswhite committed Dec 1, 2022
1 parent 2046443 commit 6379b3e
Show file tree
Hide file tree
Showing 4 changed files with 258 additions and 1 deletion.
30 changes: 30 additions & 0 deletions src/genesis/helpers/field_enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,3 +325,33 @@ class AuthzExecMessageFields(NamedFields):
@property
def table(self):
return "authz_exec_messages"


class AlmanacRegistrations(NamedFields):
id = 0
expiry_height = 1
account_id = 2
record_id = 3
transaction_id = 4
block_id = 5
# event_id = 6
# record_id = 7

@classmethod
@property
def table(self):
return "almanac_registrations"



class AlmanacRecords(NamedFields):
id = 0
service = 1
transaction_id = 2
block_id = 3
# event_id = 4

@classmethod
@property
def table(self):
return "almanac_records"
189 changes: 189 additions & 0 deletions tests/e2e/entities/test_almanac.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import json
import sys
import time
import unittest
from dataclasses import dataclass
from pathlib import Path
from typing import List, Dict

import graphql
from cosmpy.aerial.tx_helpers import SubmittedTx
from gql import gql

repo_root_path = Path(__file__).parent.parent.parent.parent.absolute()
sys.path.insert(0, str(repo_root_path))

from src.genesis.helpers.field_enums import AlmanacRegistrations, AlmanacRecords
from tests.helpers.contracts import AlmanacContract, DefaultAlmanacContractConfig
from tests.helpers.entity_test import EntityTest
from tests.helpers.graphql import test_filtered_query
from tests.helpers.regexes import msg_id_regex, tx_id_regex, block_id_regex


def by_expiry_height(registration_node: Dict):
return int(registration_node["expiryHeight"])


@dataclass
class Scenario:
name: str
query: graphql.DocumentNode
expected: any


class TestAlmanac(EntityTest):
test_registrations_endpoints = [
"127.0.0.1:9999",
"127.0.0.1:8888",
"127.0.0.1:7777",
"127.0.0.1:6666"
]
submitted_txs: List[SubmittedTx] = []
expected_registrations: List[Dict] = []
expected_records: List[Dict] = [
{
"service": {
"protocols": ["grpc"],
"endpoints": [{
"url": endpoint,
# NB: not "proper" usage of weight; for testing only
"weight": i
}]
}
} for (i, endpoint) in enumerate(test_registrations_endpoints)
]

@classmethod
def setUpClass(cls):
super().setUpClass()
cls.clean_db({"almanac_registrations", "almanac_resolutions"})
cls._contract = AlmanacContract(cls.ledger_client, cls.validator_wallet)

# NB: broadcast multiple registrations
for (i, expected_record) in enumerate(cls.expected_records):
tx = cls._contract.execute({
"register": {
"record": expected_record,
# "sequence": ,
# "signature": ,
}
}, cls.validator_wallet, funds=DefaultAlmanacContractConfig.register_stake_funds)
tx.wait_to_complete()
cls.submitted_txs.append(tx)
cls.expected_registrations.append({
"accountId": cls.validator_address,
"expiryHeight": tx.response.height + DefaultAlmanacContractConfig.expiry_height,
"record": expected_record
})
# NB: wait for the indexer
time.sleep(7)

def test_registrations_sql(self):
registrations = self.db_cursor.execute(AlmanacRegistrations.select_query()).fetchall()
actual_reg_length = len(registrations)

expected_registrations_count = len(self.expected_registrations)
self.assertEqual(expected_registrations_count,
actual_reg_length,
f"expected {expected_registrations_count} registrations; got {actual_reg_length}")
for (i, registration) in enumerate(registrations):
self.assertEqual(self.validator_address, registration[AlmanacRegistrations.account_id.value])
self.assertLess(self.submitted_txs[i].response.height,
registration[AlmanacRegistrations.expiry_height.value])
self.assertRegex(registration[AlmanacRegistrations.id.value], msg_id_regex)
self.assertRegex(registration[AlmanacRegistrations.transaction_id.value], tx_id_regex)
self.assertRegex(registration[AlmanacRegistrations.block_id.value], block_id_regex)

def matches_expected_record(_record: Dict) -> bool:
return _record["service"]["endpoints"][0]["weight"] == i

# Lookup related record
record = self.db_cursor.execute(
AlmanacRecords.select_where(
f"almanac_records.id = '{registration[AlmanacRegistrations.record_id.value]}'",
[AlmanacRecords.table, AlmanacRegistrations.table])).fetchone()
expected_record = next(r for r in self.expected_records if matches_expected_record(r))
self.assertIsNotNone(record)
self.assertIsNotNone(expected_record)
self.assertDictEqual(expected_record["service"], record[AlmanacRecords.service.value])
self.assertRegex(record[AlmanacRecords.id.value], msg_id_regex)
self.assertRegex(record[AlmanacRecords.transaction_id.value], tx_id_regex)
self.assertRegex(record[AlmanacRecords.block_id.value], block_id_regex)

def test_registrations_gql(self):
registrations_nodes = """
{
id
expiryHeight
accountId
record {
id
service
# registrationId
# eventId
transactionId
blockId
}
transactionId
blockId
}
"""

last_tx_height = self.submitted_txs[-1].response.height
expired_registrations_query = test_filtered_query("almanacRegistrations", {
"expiryHeight": {
"lessThanOrEqualTo": str(last_tx_height)
}
}, registrations_nodes)

active_registrations_query = test_filtered_query("almanacRegistrations", {
"expiryHeight": {
"greaterThan": str(last_tx_height)
}
}, registrations_nodes)

all_registrations_query = gql("query {almanacRegistrations {nodes " + registrations_nodes + "}}")

last_expired_height = last_tx_height - DefaultAlmanacContractConfig.expiry_height
last_expired = next(r for r in self.submitted_txs if r.response.height == last_expired_height)
last_expired_index = self.submitted_txs.index(last_expired)
scenarios = [
Scenario(
name="expired registrations",
query=expired_registrations_query,
expected=self.expected_registrations[0: last_expired_index + 1]
),
Scenario(
name="active registrations",
query=active_registrations_query,
expected=self.expected_registrations[last_expired_index + 1:]
),
Scenario(
name="all registrations",
query=all_registrations_query,
expected=self.expected_registrations
),
]

for scenario in scenarios:
with self.subTest(scenario.name):
gql_result = self.gql_client.execute(scenario.query)
registrations = gql_result["almanacRegistrations"]["nodes"]
self.assertEqual(len(scenario.expected), len(registrations))

# TODO: use respective gql order by when available
# NB: sort by expiry height so that indexes match
# their respective scenario.expected index
list.sort(registrations, key=by_expiry_height)
self.assertEqual(len(scenario.expected), len(registrations))

for (i, registration) in enumerate(registrations):
self.assertRegex(registration["id"], msg_id_regex)
self.assertEqual(self.validator_address, registration["accountId"], )
self.assertEqual(str(scenario.expected[i]["expiryHeight"]), registration["expiryHeight"])
self.assertRegex(registration["transactionId"], tx_id_regex)
self.assertRegex(registration["blockId"], block_id_regex)
# TODO: assert record equality

if __name__ == "__main__":
unittest.main()
37 changes: 37 additions & 0 deletions tests/helpers/contracts.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from dataclasses import dataclass

import os
from typing import Optional, Union

import requests
from cosmpy.aerial.client import LedgerClient
from cosmpy.aerial.contract import LedgerContract
Expand All @@ -22,6 +24,21 @@ class BridgeContractConfig:
next_swap_id: int


@dataclass_json
@dataclass
class AlmanacContractConfig:
stake_denom: str
expiry_height: Optional[int]
register_stake_amount: Optional[str]
admin: Optional[str]

@property
def register_stake_funds(self) -> Union[None, str]:
if self.register_stake_amount == "0":
return None
return self.register_stake_amount + self.stake_denom


DefaultBridgeContractConfig = BridgeContractConfig(
cap="250000000000000000000000000",
reverse_aggregated_allowance="3000000000000000000000000",
Expand All @@ -34,6 +51,13 @@ class BridgeContractConfig:
next_swap_id=0
)

DefaultAlmanacContractConfig = AlmanacContractConfig(
stake_denom="atestfet",
expiry_height=2,
register_stake_amount="0",
admin=None
)


def ensure_contract(name: str, url: str) -> str:
contract_path = f".contract/{name}.wasm"
Expand Down Expand Up @@ -112,3 +136,16 @@ def __init__(self, client: LedgerClient, admin: Wallet, cfg: BridgeContractConfi
admin,
store_gas_limit=3000000
)


class AlmanacContract(LedgerContract):
def __init__(self, client: LedgerClient, admin: Wallet, cfg: AlmanacContractConfig = DefaultAlmanacContractConfig):
url = "https://github.com/fetchai/contract-agent-almanac/releases/download/v0.1.1/contract_agent_almanac.wasm"
contract_path = ensure_contract("almanac", url)
super().__init__(contract_path, client)

self.deploy(
cfg.to_dict(),
admin,
store_gas_limit=3000000
)
3 changes: 2 additions & 1 deletion tests/helpers/graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import re
from typing import Dict

import graphql
from gql import gql

json_keys_regex = re.compile('"(\w+)":')
Expand All @@ -12,7 +13,7 @@ def to_gql(obj: Dict):
return json_keys_regex.sub("\g<1>:", json.dumps(obj))


def test_filtered_query(root_entity: str, _filter: Dict, nodes_string: str):
def test_filtered_query(root_entity: str, _filter: Dict, nodes_string: str) -> graphql.DocumentNode:
filter_string = to_gql(_filter)

return gql("""
Expand Down

0 comments on commit 6379b3e

Please sign in to comment.