Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: add transaction query support #776

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f213eec
refactor: current circular import
johnson2427 May 18, 2022
80d9fb0
refactor: moved BlockTransactionQuery inside of transaction
johnson2427 May 18, 2022
f0ca8a3
feat: added get_transactions_by_block iterator
johnson2427 May 23, 2022
c2f0b07
feat: added docstring to BlockTransactionQuery
johnson2427 May 23, 2022
3f28f40
fix: test_basic_query was missing new field in assert
johnson2427 May 23, 2022
72e9902
feat: perform_transaction_query is now using get_transactions_by_block
johnson2427 May 23, 2022
ad26c83
feat: added AccountTransactionQuery and BlockTransactionQuery to Quer…
johnson2427 May 24, 2022
e3b176d
fix: mypy issue
johnson2427 May 24, 2022
f8d6839
fix: returning a list of TransactionAPI from BlockAPI.transactions()
johnson2427 May 24, 2022
c5dccd9
fix: functionality of map function is the same, removed lambda
johnson2427 May 25, 2022
ff3fc7f
fix: mypy
johnson2427 May 25, 2022
5ed9de5
feat: pytest added for BlockTransactionQuery
johnson2427 Jun 1, 2022
11f5672
feat: black issue
johnson2427 Jun 1, 2022
c93b352
feat: removed unnecessary lines in test_block_transaction_query()
johnson2427 Jun 1, 2022
ecd9163
feat: added support for block_transaction_query
johnson2427 Jun 2, 2022
093fcb0
feat: allow different keys for block_number and gas_price in ecosystem
johnson2427 Jun 3, 2022
7bab5b8
refactor: changed to block_hash from block_id for transaction query
johnson2427 Jun 7, 2022
9736467
fix: test is passing, reverted back to block id
johnson2427 Jun 7, 2022
4542082
fix: mypy issue
johnson2427 Jun 7, 2022
1ff2d23
fix: mypy issue
johnson2427 Jun 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/ape/api/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

from ape.api.config import PluginConfig
from ape.api.networks import LOCAL_NETWORK_NAME, NetworkAPI
from ape.api.query import BlockTransactionQuery
from ape.api.transactions import ReceiptAPI, TransactionAPI
from ape.exceptions import (
ContractLogicError,
Expand Down Expand Up @@ -74,6 +75,7 @@ class BlockAPI(BaseInterfaceModel):

gas_data: BlockGasAPI
consensus_data: BlockConsensusAPI
num_transactions: int = 0
hash: Optional[Any] = None
number: Optional[int] = None
parent_hash: Optional[Any] = None
Expand All @@ -87,6 +89,11 @@ def validate_hexbytes(cls, value):
raise ValueError(f"Hash `{value}` is not a valid Hexbyte.")
return value

@cached_property
def transactions(self) -> List[TransactionAPI]:
query = BlockTransactionQuery(columns=["*"], block_id=self.hash)
return list(self.query_manager.query(query)) # type: ignore


class ProviderAPI(BaseInterfaceModel):
"""
Expand Down Expand Up @@ -283,6 +290,18 @@ def get_transaction(self, txn_hash: str) -> ReceiptAPI:
The receipt of the transaction with the given hash.
"""

@abstractmethod
def get_transactions_by_block(self, block_id: HexBytes) -> Iterator[TransactionAPI]:
antazoey marked this conversation as resolved.
Show resolved Hide resolved
"""
Get the information about a set of transactions from a block.

Args:
block_id (HexBytes): The hash of a block.

Returns:
Iterator[:class: `~ape.api.transactions.TransactionAPI`]
"""

@abstractmethod
def send_transaction(self, txn: TransactionAPI) -> ReceiptAPI:
"""
Expand Down Expand Up @@ -650,6 +669,17 @@ def get_transaction(self, txn_hash: str, required_confirmations: int = 0) -> Rec
)
return receipt.await_confirmations()

def get_transactions_by_block(self, block_id: BlockID) -> Iterator:
if isinstance(block_id, str):
block_id = HexStr(block_id)

if block_id.isnumeric():
block_id = add_0x_prefix(block_id)

block = self.web3.eth.get_block(block_id, full_transactions=True)
for transaction in block.get("transactions"): # type: ignore
yield self.network.ecosystem.create_transaction(**transaction) # type: ignore

def get_contract_logs(
self,
address: Union[AddressType, List[AddressType]],
Expand Down
41 changes: 29 additions & 12 deletions src/ape/api/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@
from ape.types import AddressType
from ape.utils import BaseInterfaceModel, abstractmethod

from .providers import BlockAPI
from .transactions import TransactionAPI

QueryType = Union["BlockQuery", "AccountQuery", "ContractEventQuery", "ContractMethodQuery"]
QueryType = Union[
"BlockQuery",
"BlockTransactionQuery",
"AccountTransactionQuery",
"ContractEventQuery",
"ContractMethodQuery",
]


class _BaseQuery(BaseModel):
Expand Down Expand Up @@ -64,10 +69,31 @@ class BlockQuery(_BaseBlockQuery):

@classmethod
def all_fields(cls) -> List[str]:
from .providers import BlockAPI

return list(BlockAPI.__fields__)


class _BaseAccountQuery(_BaseQuery):
class BlockTransactionQuery(_BaseQuery):
"""
A ``QueryType`` that collects properties of ``TransactionAPI`` over a range of
transactions collected inside the ``BlockAPI` object represented by ``block_id``.
"""

block_id: Any

@classmethod
def all_fields(cls) -> List[str]:
return list(TransactionAPI.__fields__)


class AccountTransactionQuery(_BaseQuery):
"""
A ``QueryType`` that collects properties of ``TransactionAPI`` over a range
of transactions made by ``account`` between ``start_nonce`` and ``stop_nonce``.
"""

account: AddressType
start_nonce: NonNegativeInt = 0
stop_nonce: NonNegativeInt

Expand All @@ -81,15 +107,6 @@ def check_start_nonce_before_stop_nonce(cls, values: Dict) -> Dict:

return values


class AccountQuery(_BaseAccountQuery):
"""
A ``QueryType`` that collects properties of ``TransactionAPI`` over a range
of transactions made by ``account`` between ``start_nonce`` and ``stop_nonce``.
"""

account: AddressType

@classmethod
def all_fields(cls) -> List[str]:
return list(TransactionAPI.__fields__)
Expand Down
11 changes: 10 additions & 1 deletion src/ape/managers/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pydantic import BaseModel

from ape.api import QueryAPI, QueryType
from ape.api.query import BlockQuery, _BaseQuery
from ape.api.query import BlockQuery, BlockTransactionQuery, _BaseQuery
from ape.exceptions import QueryEngineError
from ape.plugins import clean_plugin_name
from ape.utils import ManagerAccessMixin, cached_property, singledispatchmethod
Expand All @@ -29,6 +29,11 @@ def estimate_block_query(self, query: BlockQuery) -> Optional[int]:
# NOTE: Very loose estimate of 100ms per block
return (query.stop_block - query.start_block) * 100

@estimate_query.register
def estimate_block_transaction_query(self, query: BlockTransactionQuery) -> int:

return 100

@singledispatchmethod
def perform_query(self, query: QueryType) -> Iterator: # type: ignore
raise QueryEngineError(f"Cannot handle '{type(query)}'.")
Expand All @@ -42,6 +47,10 @@ def perform_block_query(self, query: BlockQuery) -> Iterator:
range(query.start_block, query.stop_block + 1, query.step),
)

@perform_query.register
def perform_block_transaction_query(self, query: BlockTransactionQuery) -> Iterator:
return self.provider.get_transactions_by_block(query.block_id)


class QueryManager(ManagerAccessMixin):
"""
Expand Down
4 changes: 2 additions & 2 deletions src/ape_ethereum/ecosystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,9 +272,9 @@ def decode_receipt(self, data: dict) -> ReceiptAPI:
required_confirmations=data.get("required_confirmations", 0),
txn_hash=txn_hash,
status=status,
block_number=data["blockNumber"],
block_number=data.get("block_number") or data.get("blockNumber"),
gas_used=data["gasUsed"],
gas_price=data["gasPrice"],
gas_price=data.get("gas_price") or data.get("gasPrice"),
gas_limit=data.get("gas") or data.get("gasLimit"),
logs=data.get("logs", []),
contract_address=data.get("contractAddress"),
Expand Down
33 changes: 30 additions & 3 deletions tests/functional/api/test_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from pydantic import ValidationError

from ape import chain
from ape.api.query import AccountQuery, BlockQuery
from ape.api.query import AccountTransactionQuery, BlockQuery, BlockTransactionQuery


def test_basic_query(eth_tester_provider):
Expand All @@ -15,6 +15,7 @@ def test_basic_query(eth_tester_provider):
assert columns == [
"gas_data",
"consensus_data",
"num_transactions",
"hash",
"number",
"parent_hash",
Expand All @@ -23,6 +24,32 @@ def test_basic_query(eth_tester_provider):
]


def test_block_transaction_query_api():
query = BlockTransactionQuery(columns=["*"], block_id=0)
assert query.columns == [
"chain_id",
"receiver",
"sender",
"gas_limit",
"nonce",
"value",
"data",
"type",
"max_fee",
"max_priority_fee",
"required_confirmations",
"signature",
]


def test_block_transaction_query(eth_tester_provider, sender, receiver):
sender.transfer(receiver, 100)
query = chain.blocks[-1].transactions
assert len(query) == 1
assert query[0].value == 100
assert query[0].chain_id == 61


def test_block_query(eth_tester_provider):
chain.mine(3)
with pytest.raises(ValidationError) as err:
Expand All @@ -39,8 +66,8 @@ def test_account_query(eth_tester_provider):
account="0x0000000000000000000000000000000000000000", start_nonce=0, stop_nonce=2
)
with pytest.raises(ValidationError) as err:
AccountQuery(columns=["none"], **query_kwargs)
AccountTransactionQuery(columns=["none"], **query_kwargs)
assert "Unrecognized field 'none'" in str(err.value)
with pytest.raises(ValidationError) as err:
AccountQuery(columns=["nonce", "chain_id", "nonce"], **query_kwargs)
AccountTransactionQuery(columns=["nonce", "chain_id", "nonce"], **query_kwargs)
assert "Duplicate fields in ['nonce', 'chain_id', 'nonce']" in str(err.value)