├── tests ├── __init__.py ├── p2p │ ├── __init__.py │ ├── fixtures │ │ ├── __init__.py │ │ ├── README │ │ └── sample_1000_headers_rlp │ ├── test_kademlia_node_picklable.py │ ├── conftest.py │ ├── stats-utils │ │ ├── test_percentile.py │ │ └── test_standard_deviation.py │ ├── test_duplicates_util.py │ ├── test_peer_pair_factory.py │ ├── test_memory_transport.py │ ├── test_get_cmd_id_offsets.py │ ├── test_peer_pool_event_server.py │ ├── test_utils.py │ ├── test_metrics.py │ └── test_resource_lock.py ├── integration │ ├── __init__.py │ ├── churn_state │ │ ├── __init__.py │ │ ├── Nymph.sol │ │ ├── FillStorage.sol │ │ ├── Nymph2.sol │ │ └── Nymph1.sol │ ├── fixtures │ │ ├── geth_lightchain_datadir │ │ │ └── geth │ │ │ │ ├── LOCK │ │ │ │ ├── chaindata │ │ │ │ ├── LOCK │ │ │ │ ├── CURRENT │ │ │ │ ├── 000014.ldb │ │ │ │ ├── 000015.ldb │ │ │ │ ├── 000018.ldb │ │ │ │ ├── 000021.ldb │ │ │ │ └── MANIFEST-000023 │ │ │ │ ├── transactions.rlp │ │ │ │ └── nodekey │ │ ├── cold_state.ldb.zip │ │ ├── 20pow_headers.ldb.zip │ │ ├── churn_state.ldb.zip │ │ ├── 1000pow_headers.ldb.zip │ │ ├── mainnet_blocks │ │ │ ├── block_1.rlp │ │ │ ├── block_2.rlp │ │ │ └── block_1_2.rlp │ │ ├── 1000pow_uncle_chain.ldb.zip │ │ └── trinity_headerchain_datadir.zip │ ├── test_component_discovery.py │ ├── test_fixture_builders.py │ └── helpers.py ├── core │ ├── _utils │ │ ├── test_version.py │ │ └── test_priority.py │ ├── peer_helpers.py │ ├── tools │ │ └── test_async_runner.py │ ├── chains │ │ └── test_light_peer_chain.py │ ├── initialization │ │ ├── test_initialize_database.py │ │ └── test_is_database_initialized.py │ ├── test_logging.py │ ├── filesystem-utils │ │ └── test_is_under_path.py │ ├── utils │ │ ├── test_humanize_integer_sequence.py │ │ ├── test_rolling_bloom_filter.py │ │ ├── test_async_iter.py │ │ └── test_headers.py │ ├── cli │ │ ├── test_trinity_repl.py │ │ └── test_log_level_configuration.py │ ├── p2p-proto │ │ ├── test_peer_discovery.py │ │ └── test_wit_api.py │ ├── database │ │ ├── test_db_client.py │ │ └── test_db_manager.py │ ├── eip1085-utils │ │ └── test_extract_genesis_state.py │ ├── header-utils │ │ └── test_block_number_sequence_builder.py │ ├── test_ipc_log_listener.py │ └── configuration │ │ └── test_eth1_app_config_object.py └── trinity_long_run │ └── test_trinity_long_run.py ├── p2p ├── stats │ ├── __init__.py │ ├── stddev.py │ └── ema.py ├── tools │ ├── __init__.py │ ├── factories │ │ ├── socket.py │ │ ├── session.py │ │ ├── p2p_proto.py │ │ ├── keys.py │ │ ├── __init__.py │ │ └── kademlia.py │ ├── paragon │ │ ├── payloads.py │ │ ├── proto.py │ │ ├── __init__.py │ │ ├── api.py │ │ └── commands.py │ ├── handshake.py │ └── connection.py ├── tracking │ └── __init__.py ├── __init__.py ├── exchange │ ├── validator.py │ ├── typing.py │ ├── __init__.py │ ├── constants.py │ ├── logic.py │ └── normalizers.py ├── logging.py ├── typing.py ├── receipt.py ├── disconnect.py ├── subscription.py ├── session.py ├── message.py ├── events.py ├── validation.py └── resource_lock.py ├── scripts ├── __init__.py ├── mine_devnet.py ├── upnp.py └── release │ └── test_package.py ├── trinity ├── db │ ├── __init__.py │ ├── eth1 │ │ └── __init__.py │ └── network.py ├── rlp │ ├── __init__.py │ └── block_body.py ├── _utils │ ├── __init__.py │ ├── les.py │ ├── errors.py │ ├── assertions.py │ ├── log_messages.py │ ├── shellart.py │ ├── trio_utils.py │ ├── timer.py │ ├── version.py │ ├── address.py │ ├── os.py │ ├── profiling.py │ ├── trie.py │ ├── xdg.py │ ├── async_dispatch.py │ ├── async_iter.py │ ├── services.py │ ├── pauser.py │ ├── connect.py │ ├── humanize.py │ ├── bloom.py │ ├── validation.py │ ├── queues.py │ └── decorators.py ├── chains │ ├── __init__.py │ ├── full.py │ ├── header.py │ └── coro.py ├── http │ ├── __init__.py │ ├── handlers │ │ ├── __init__.py │ │ └── base.py │ ├── exceptions.py │ └── main.py ├── nodes │ ├── __init__.py │ └── events.py ├── protocol │ ├── __init__.py │ ├── eth │ │ ├── __init__.py │ │ ├── monitors.py │ │ ├── constants.py │ │ ├── sync.py │ │ ├── payloads.py │ │ └── normalizers.py │ ├── les │ │ ├── __init__.py │ │ ├── monitors.py │ │ ├── validators.py │ │ ├── sync.py │ │ ├── constants.py │ │ ├── trackers.py │ │ └── proto.py │ ├── wit │ │ ├── __init__.py │ │ ├── proto.py │ │ ├── handshaker.py │ │ ├── commands.py │ │ └── events.py │ └── common │ │ ├── __init__.py │ │ ├── constants.py │ │ ├── payloads.py │ │ ├── abc.py │ │ ├── context.py │ │ └── typing.py ├── sync │ ├── __init__.py │ ├── beam │ │ └── __init__.py │ ├── common │ │ ├── __init__.py │ │ ├── types.py │ │ └── checkpoint.py │ ├── full │ │ ├── __init__.py │ │ └── service.py │ ├── header │ │ └── __init__.py │ └── light │ │ ├── __init__.py │ │ └── chain.py ├── tools │ ├── __init__.py │ ├── factories │ │ ├── eth │ │ │ └── __init__.py │ │ ├── common.py │ │ ├── address.py │ │ ├── block_body.py │ │ ├── block_hash.py │ │ ├── les │ │ │ └── __init__.py │ │ ├── chain_context.py │ │ ├── __init__.py │ │ ├── events.py │ │ ├── headers.py │ │ └── receipts.py │ ├── async_method.py │ ├── chain.py │ └── event_bus.py ├── components │ ├── __init__.py │ └── builtin │ │ ├── __init__.py │ │ ├── upnp │ │ ├── __init__.py │ │ ├── events.py │ │ └── component.py │ │ ├── attach │ │ └── __init__.py │ │ ├── beam_exec │ │ └── __init__.py │ │ ├── ethstats │ │ └── __init__.py │ │ ├── json_rpc │ │ └── __init__.py │ │ ├── metrics │ │ ├── __init__.py │ │ ├── service │ │ │ ├── __init__.py │ │ │ ├── asyncio.py │ │ │ ├── trio.py │ │ │ └── noop.py │ │ ├── abc.py │ │ └── sync_metrics_registry.py │ │ ├── new_block │ │ └── __init__.py │ │ ├── syncer │ │ └── __init__.py │ │ ├── tx_pool │ │ └── __init__.py │ │ ├── beam_preview │ │ └── __init__.py │ │ ├── import_export │ │ └── __init__.py │ │ ├── network_db │ │ ├── __init__.py │ │ ├── connection │ │ │ ├── __init__.py │ │ │ └── events.py │ │ ├── eth1_peer_db │ │ │ ├── __init__.py │ │ │ └── events.py │ │ └── cli.py │ │ ├── peer_discovery │ │ └── __init__.py │ │ ├── preferred_node │ │ └── __init__.py │ │ ├── request_server │ │ └── __init__.py │ │ └── fix_unclean_shutdown │ │ └── __init__.py ├── rpc │ ├── __init__.py │ └── modules │ │ ├── web3.py │ │ ├── __init__.py │ │ ├── net.py │ │ ├── _util.py │ │ ├── main.py │ │ └── evm.py ├── boot_info.py ├── extensibility │ └── __init__.py ├── assets │ └── eip1085 │ │ └── devnet.json ├── __init__.py ├── events.py └── main.py ├── dappnode ├── .gitignore ├── trinity_logo.png ├── docker-compose.yml ├── build │ └── Dockerfile └── dappnode_package.json ├── docs ├── quickstart.rst ├── fragments │ ├── unstable_api.rst │ └── virtualenv_explainer.rst ├── images │ ├── trinity_layers.png │ ├── dappnode_syncing.png │ ├── trinity_processes.png │ ├── trinity_dappnode_install.gif │ └── configuring_trinity_on_dappnode.gif ├── api │ ├── protocol │ │ ├── eth │ │ │ └── index.rst │ │ ├── les │ │ │ ├── index.rst │ │ │ └── api.events.rst │ │ ├── common │ │ │ ├── index.rst │ │ │ └── api.events.rst │ │ └── index.rst │ ├── extensibility │ │ ├── index.rst │ │ └── api.extensibility.component.rst │ ├── index.rst │ └── api.config.rst ├── guides │ └── index.rst ├── index.rst ├── _static │ └── js │ │ └── matomo.js └── Makefile ├── stubs └── cached_property.pyi ├── .gitmodules ├── newsfragments ├── 2127.internal.rst ├── README.md └── validate_files.py ├── trinity-external-components ├── examples │ └── peer_count_reporter │ │ ├── peer_count_reporter_component │ │ └── __init__.py │ │ └── setup.py └── README.md ├── README.md ├── MANIFEST.in ├── .readthedocs.yml ├── pytest.ini ├── mypy.ini ├── docker ├── Dockerfile └── beacon.Dockerfile ├── .circleci ├── build_influxdb.sh ├── merge_pr.sh └── build_geth.sh ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .bumpversion.cfg ├── DEVELOPMENT.md ├── .gitignore ├── LICENSE ├── pyproject.toml └── tests-trio └── conftest.py /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /p2p/stats/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /p2p/tools/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/mine_devnet.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/p2p/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/db/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/rlp/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /p2p/tracking/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/_utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/chains/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/db/eth1/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/http/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/nodes/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/protocol/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/sync/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/tools/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dappnode/.gitignore: -------------------------------------------------------------------------------- 1 | build_* 2 | -------------------------------------------------------------------------------- /tests/integration/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/p2p/fixtures/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/components/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/http/handlers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/protocol/eth/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/protocol/les/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/protocol/wit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/sync/beam/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/sync/common/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/sync/full/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/sync/header/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/sync/light/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/components/builtin/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/protocol/common/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/churn_state/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/components/builtin/upnp/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/components/builtin/attach/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/components/builtin/beam_exec/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/components/builtin/ethstats/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/components/builtin/json_rpc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/components/builtin/metrics/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/components/builtin/new_block/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/components/builtin/syncer/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/components/builtin/tx_pool/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/quickstart.rst: -------------------------------------------------------------------------------- 1 | .. include:: guides/quickstart.rst -------------------------------------------------------------------------------- /trinity/components/builtin/beam_preview/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/components/builtin/import_export/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/components/builtin/network_db/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/components/builtin/peer_discovery/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/components/builtin/preferred_node/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/components/builtin/request_server/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stubs/cached_property.pyi: -------------------------------------------------------------------------------- 1 | cached_property = property 2 | -------------------------------------------------------------------------------- /trinity/components/builtin/metrics/service/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/fixtures/geth_lightchain_datadir/geth/LOCK: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/components/builtin/fix_unclean_shutdown/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/components/builtin/network_db/connection/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/components/builtin/network_db/eth1_peer_db/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/fixtures/geth_lightchain_datadir/geth/chaindata/LOCK: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/fixtures/geth_lightchain_datadir/geth/transactions.rlp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trinity/rpc/__init__.py: -------------------------------------------------------------------------------- 1 | from .main import RPCServer # noqa: F401 2 | -------------------------------------------------------------------------------- /tests/integration/fixtures/geth_lightchain_datadir/geth/chaindata/CURRENT: -------------------------------------------------------------------------------- 1 | MANIFEST-000023 2 | -------------------------------------------------------------------------------- /dappnode/trinity_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/trinity/HEAD/dappnode/trinity_logo.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "fixtures"] 2 | path = fixtures 3 | url = https://github.com/ethereum/tests.git 4 | -------------------------------------------------------------------------------- /docs/fragments/unstable_api.rst: -------------------------------------------------------------------------------- 1 | .. warning:: 2 | 3 | This API isn't stable yet. Expect breaking changes. 4 | -------------------------------------------------------------------------------- /docs/images/trinity_layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/trinity/HEAD/docs/images/trinity_layers.png -------------------------------------------------------------------------------- /docs/images/dappnode_syncing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/trinity/HEAD/docs/images/dappnode_syncing.png -------------------------------------------------------------------------------- /docs/images/trinity_processes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/trinity/HEAD/docs/images/trinity_processes.png -------------------------------------------------------------------------------- /newsfragments/2127.internal.rst: -------------------------------------------------------------------------------- 1 | Update ethereum/tests to v8.0.2, mark some of the new tests as too slow for CI. 2 | -------------------------------------------------------------------------------- /tests/integration/fixtures/geth_lightchain_datadir/geth/nodekey: -------------------------------------------------------------------------------- 1 | 6cb1a5fc574fd8e1353d2f90aec20695db7d7917d8e6ade739bc36872c88eb0f -------------------------------------------------------------------------------- /tests/p2p/fixtures/README: -------------------------------------------------------------------------------- 1 | sample_1000_headers_rlp: First 1000 headers from mainnet, stored as CountableList(BlockHeader) 2 | -------------------------------------------------------------------------------- /docs/images/trinity_dappnode_install.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/trinity/HEAD/docs/images/trinity_dappnode_install.gif -------------------------------------------------------------------------------- /p2p/__init__.py: -------------------------------------------------------------------------------- 1 | # This is to ensure we call setup_extended_logging() before anything else. 2 | import eth as _eth_module # noqa: F401 3 | -------------------------------------------------------------------------------- /tests/p2p/fixtures/sample_1000_headers_rlp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/trinity/HEAD/tests/p2p/fixtures/sample_1000_headers_rlp -------------------------------------------------------------------------------- /tests/integration/fixtures/cold_state.ldb.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/trinity/HEAD/tests/integration/fixtures/cold_state.ldb.zip -------------------------------------------------------------------------------- /docs/images/configuring_trinity_on_dappnode.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/trinity/HEAD/docs/images/configuring_trinity_on_dappnode.gif -------------------------------------------------------------------------------- /p2p/exchange/validator.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | 4 | def noop_payload_validator(request: Any, response: Any) -> None: 5 | pass 6 | -------------------------------------------------------------------------------- /tests/integration/fixtures/20pow_headers.ldb.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/trinity/HEAD/tests/integration/fixtures/20pow_headers.ldb.zip -------------------------------------------------------------------------------- /tests/integration/fixtures/churn_state.ldb.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/trinity/HEAD/tests/integration/fixtures/churn_state.ldb.zip -------------------------------------------------------------------------------- /tests/integration/fixtures/1000pow_headers.ldb.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/trinity/HEAD/tests/integration/fixtures/1000pow_headers.ldb.zip -------------------------------------------------------------------------------- /tests/integration/fixtures/mainnet_blocks/block_1.rlp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/trinity/HEAD/tests/integration/fixtures/mainnet_blocks/block_1.rlp -------------------------------------------------------------------------------- /tests/integration/fixtures/mainnet_blocks/block_2.rlp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/trinity/HEAD/tests/integration/fixtures/mainnet_blocks/block_2.rlp -------------------------------------------------------------------------------- /tests/integration/fixtures/1000pow_uncle_chain.ldb.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/trinity/HEAD/tests/integration/fixtures/1000pow_uncle_chain.ldb.zip -------------------------------------------------------------------------------- /tests/integration/fixtures/mainnet_blocks/block_1_2.rlp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/trinity/HEAD/tests/integration/fixtures/mainnet_blocks/block_1_2.rlp -------------------------------------------------------------------------------- /tests/integration/fixtures/trinity_headerchain_datadir.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/trinity/HEAD/tests/integration/fixtures/trinity_headerchain_datadir.zip -------------------------------------------------------------------------------- /trinity-external-components/examples/peer_count_reporter/peer_count_reporter_component/__init__.py: -------------------------------------------------------------------------------- 1 | from .component import PeerCountReporterComponent # noqa: F401 2 | -------------------------------------------------------------------------------- /trinity/_utils/les.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from eth_utils import big_endian_to_int 4 | 5 | 6 | def gen_request_id() -> int: 7 | return big_endian_to_int(os.urandom(8)) 8 | -------------------------------------------------------------------------------- /trinity/protocol/common/constants.py: -------------------------------------------------------------------------------- 1 | # Timeout used when performing the check to ensure peers are on the same side of chain splits as 2 | # us. 3 | CHAIN_SPLIT_CHECK_TIMEOUT = 15 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🔥 NO LONGER MAINTAINED OR DEVELOPED 🔥 2 | 3 | The Trinity Ethereum client is no longer being developed or maintained. This repository will remain here in archive mode. 4 | -------------------------------------------------------------------------------- /trinity/chains/full.py: -------------------------------------------------------------------------------- 1 | from eth.chains.base import Chain 2 | 3 | from trinity.chains.coro import AsyncChainMixin 4 | 5 | 6 | class FullChain(AsyncChainMixin, Chain): 7 | pass 8 | -------------------------------------------------------------------------------- /trinity/components/builtin/upnp/events.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from lahja import BaseEvent 4 | 5 | 6 | @dataclass 7 | class UPnPMapping(BaseEvent): 8 | ip: str 9 | -------------------------------------------------------------------------------- /dappnode/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | services: 3 | trinity.public.dappnode.eth: 4 | image: 'trinity.public.dappnode.eth:0.1.3' 5 | build: ./build 6 | volumes: [] 7 | ports: [] 8 | -------------------------------------------------------------------------------- /tests/integration/fixtures/geth_lightchain_datadir/geth/chaindata/000014.ldb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/trinity/HEAD/tests/integration/fixtures/geth_lightchain_datadir/geth/chaindata/000014.ldb -------------------------------------------------------------------------------- /tests/integration/fixtures/geth_lightchain_datadir/geth/chaindata/000015.ldb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/trinity/HEAD/tests/integration/fixtures/geth_lightchain_datadir/geth/chaindata/000015.ldb -------------------------------------------------------------------------------- /tests/integration/fixtures/geth_lightchain_datadir/geth/chaindata/000018.ldb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/trinity/HEAD/tests/integration/fixtures/geth_lightchain_datadir/geth/chaindata/000018.ldb -------------------------------------------------------------------------------- /tests/integration/fixtures/geth_lightchain_datadir/geth/chaindata/000021.ldb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/trinity/HEAD/tests/integration/fixtures/geth_lightchain_datadir/geth/chaindata/000021.ldb -------------------------------------------------------------------------------- /tests/integration/fixtures/geth_lightchain_datadir/geth/chaindata/MANIFEST-000023: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/trinity/HEAD/tests/integration/fixtures/geth_lightchain_datadir/geth/chaindata/MANIFEST-000023 -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include VERSION 3 | include README.md 4 | include requirements.txt 5 | 6 | recursive-exclude * __pycache__ 7 | recursive-exclude * *.py[co] 8 | 9 | recursive-include trinity/assets * 10 | -------------------------------------------------------------------------------- /tests/integration/churn_state/Nymph.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.7; 2 | 3 | // Nymph exists purely to be deleted later 4 | 5 | contract Nymph { 6 | function poof() public { 7 | selfdestruct(msg.sender); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/core/_utils/test_version.py: -------------------------------------------------------------------------------- 1 | from trinity._utils.version import construct_trinity_client_identifier 2 | 3 | 4 | def test_construct_trinity_client_identifier(): 5 | assert construct_trinity_client_identifier().startswith('Trinity/') 6 | -------------------------------------------------------------------------------- /docs/api/protocol/eth/index.rst: -------------------------------------------------------------------------------- 1 | ETH 2 | === 3 | 4 | .. include:: /fragments/unstable_api.rst 5 | 6 | .. toctree:: 7 | :maxdepth: 4 8 | :name: toc-trinity-api-protocol-eth 9 | :caption: ETH Protocol Events 10 | 11 | api.events.rst -------------------------------------------------------------------------------- /docs/api/protocol/les/index.rst: -------------------------------------------------------------------------------- 1 | LES 2 | === 3 | 4 | .. include:: /fragments/unstable_api.rst 5 | 6 | .. toctree:: 7 | :maxdepth: 4 8 | :name: toc-trinity-api-protocol-les 9 | :caption: LES Protocol Events 10 | 11 | api.events.rst -------------------------------------------------------------------------------- /docs/api/extensibility/index.rst: -------------------------------------------------------------------------------- 1 | Extensibility 2 | ============= 3 | 4 | .. include:: /fragments/unstable_api.rst 5 | 6 | .. toctree:: 7 | :maxdepth: 4 8 | :name: toc-eth-api-extensibility 9 | 10 | api.extensibility.component.rst 11 | -------------------------------------------------------------------------------- /docs/api/protocol/common/index.rst: -------------------------------------------------------------------------------- 1 | Common 2 | ====== 3 | 4 | .. include:: /fragments/unstable_api.rst 5 | 6 | .. toctree:: 7 | :maxdepth: 4 8 | :name: toc-trinity-api-protocol-common 9 | :caption: Common Protocol Events 10 | 11 | api.events.rst -------------------------------------------------------------------------------- /p2p/tools/factories/socket.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | 4 | def get_open_port() -> int: 5 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 6 | s.bind(("", 0)) 7 | s.listen(1) 8 | port = s.getsockname()[1] 9 | s.close() 10 | return port 11 | -------------------------------------------------------------------------------- /trinity/protocol/les/monitors.py: -------------------------------------------------------------------------------- 1 | from trinity.protocol.common.monitors import BaseChainTipMonitor 2 | from trinity.protocol.les import commands 3 | 4 | 5 | class LightChainTipMonitor(BaseChainTipMonitor): 6 | subscription_msg_types = frozenset({commands.Announce}) 7 | -------------------------------------------------------------------------------- /p2p/exchange/typing.py: -------------------------------------------------------------------------------- 1 | from typing import Any, TypeVar 2 | 3 | from p2p.abc import CommandAPI 4 | 5 | 6 | TRequestCommand = TypeVar('TRequestCommand', bound=CommandAPI[Any]) 7 | TResponseCommand = TypeVar('TResponseCommand', bound=CommandAPI[Any]) 8 | TResult = TypeVar('TResult') 9 | -------------------------------------------------------------------------------- /p2p/tools/paragon/payloads.py: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple 2 | 3 | 4 | class BroadcastDataPayload(NamedTuple): 5 | data: bytes 6 | 7 | 8 | class GetSumPayload(NamedTuple): 9 | a: int 10 | b: int 11 | 12 | 13 | class SumPayload(NamedTuple): 14 | c: int 15 | -------------------------------------------------------------------------------- /tests/p2p/test_kademlia_node_picklable.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | 3 | from p2p.tools.factories import NodeFactory 4 | 5 | 6 | def test_kademlia_node_is_pickleable(): 7 | node = NodeFactory() 8 | result = pickle.loads(pickle.dumps(node)) 9 | assert result == node 10 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # https://docs.readthedocs.io/en/latest/yaml-config.html 2 | version: 2 3 | build: 4 | image: latest 5 | python: 6 | version: 3.7 7 | install: 8 | - method: pip 9 | path: . 10 | extra_requirements: 11 | - trinity 12 | - doc 13 | -------------------------------------------------------------------------------- /trinity/protocol/les/validators.py: -------------------------------------------------------------------------------- 1 | from trinity.protocol.common.validators import ( 2 | BaseBlockHeadersValidator, 3 | ) 4 | from . import constants 5 | 6 | 7 | class GetBlockHeadersValidator(BaseBlockHeadersValidator): 8 | protocol_max_request_size = constants.MAX_HEADERS_FETCH 9 | -------------------------------------------------------------------------------- /p2p/logging.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | Any, 3 | ) 4 | 5 | from p2p._utils import trim_middle 6 | from p2p.constants import LONGEST_ALLOWED_LOG_STRING 7 | 8 | 9 | def loggable(log_object: Any) -> str: 10 | return trim_middle(str(log_object), LONGEST_ALLOWED_LOG_STRING) 11 | -------------------------------------------------------------------------------- /trinity/protocol/eth/monitors.py: -------------------------------------------------------------------------------- 1 | from trinity.protocol.common.monitors import BaseChainTipMonitor 2 | from trinity.protocol.eth import commands 3 | 4 | 5 | class ETHChainTipMonitor(BaseChainTipMonitor): 6 | subscription_msg_types = frozenset({commands.NewBlock, commands.NewBlockHashes}) 7 | -------------------------------------------------------------------------------- /trinity/protocol/common/payloads.py: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple, Union 2 | 3 | from eth_typing import BlockNumber, Hash32 4 | 5 | 6 | class BlockHeadersQuery(NamedTuple): 7 | block_number_or_hash: Union[BlockNumber, Hash32] 8 | max_headers: int 9 | skip: int 10 | reverse: bool 11 | -------------------------------------------------------------------------------- /trinity/protocol/eth/constants.py: -------------------------------------------------------------------------------- 1 | # Max number of items we can ask for in ETH requests. These are the values used 2 | # in geth and if we ask for more than this the peers will disconnect from us. 3 | MAX_STATE_FETCH = 384 4 | MAX_BODIES_FETCH = 128 5 | MAX_RECEIPTS_FETCH = 256 6 | MAX_HEADERS_FETCH = 192 7 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts= --showlocals --durations 50 -r fEX 3 | python_paths= . 4 | xfail_strict=true 5 | log_format = %(levelname)8s %(asctime)s %(filename)20s %(message)s 6 | log_date_format = %m-%d %H:%M:%S 7 | markers = 8 | slow: test is known to be excessively slow... 9 | timeout = 300 10 | -------------------------------------------------------------------------------- /p2p/tools/factories/session.py: -------------------------------------------------------------------------------- 1 | import factory 2 | 3 | from p2p.session import Session 4 | 5 | from .kademlia import NodeFactory 6 | 7 | 8 | class SessionFactory(factory.Factory): 9 | class Meta: 10 | model = Session 11 | 12 | remote = factory.SubFactory(NodeFactory) 13 | session_id = None 14 | -------------------------------------------------------------------------------- /tests/p2p/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture(scope='session', autouse=True) 5 | def ensure_pytest_asyncio(): 6 | try: 7 | import pytest_asyncio # noqa: F401 8 | except ModuleNotFoundError: 9 | raise AssertionError("Missing pytest-asyncio, cannot run asyncio tests") 10 | -------------------------------------------------------------------------------- /trinity/tools/factories/eth/__init__.py: -------------------------------------------------------------------------------- 1 | from .payloads import ( # noqa: F401 2 | StatusV63PayloadFactory, 3 | StatusPayloadFactory, 4 | NewBlockHashFactory, 5 | NewBlockPayloadFactory, 6 | ) 7 | from .proto import ( # noqa: F401 8 | ETHHandshakerFactory, 9 | ETHV65PeerPairFactory, 10 | ) 11 | -------------------------------------------------------------------------------- /trinity/protocol/eth/sync.py: -------------------------------------------------------------------------------- 1 | from trinity.protocol.eth.monitors import ETHChainTipMonitor 2 | from trinity.protocol.eth.peer import ETHPeer 3 | from trinity.sync.common.headers import BaseHeaderChainSyncer 4 | 5 | 6 | class ETHHeaderChainSyncer(BaseHeaderChainSyncer[ETHPeer]): 7 | tip_monitor_class = ETHChainTipMonitor 8 | -------------------------------------------------------------------------------- /trinity/protocol/les/sync.py: -------------------------------------------------------------------------------- 1 | from trinity.protocol.les.monitors import LightChainTipMonitor 2 | from trinity.protocol.les.peer import LESPeer 3 | from trinity.sync.common.headers import BaseHeaderChainSyncer 4 | 5 | 6 | class LightHeaderChainSyncer(BaseHeaderChainSyncer[LESPeer]): 7 | tip_monitor_class = LightChainTipMonitor 8 | -------------------------------------------------------------------------------- /trinity/boot_info.py: -------------------------------------------------------------------------------- 1 | from argparse import Namespace 2 | from typing import Dict, NamedTuple 3 | 4 | from trinity.config import TrinityConfig 5 | 6 | 7 | class BootInfo(NamedTuple): 8 | args: Namespace 9 | trinity_config: TrinityConfig 10 | profile: bool 11 | min_log_level: int 12 | logger_levels: Dict[str, int] 13 | -------------------------------------------------------------------------------- /p2p/exchange/__init__.py: -------------------------------------------------------------------------------- 1 | from .abc import ExchangeAPI, PerformanceAPI, ValidatorAPI # noqa: F401 2 | from .exchange import BaseExchange # noqa: F401 3 | from .logic import ExchangeLogic # noqa: F401 4 | from .normalizers import BaseNormalizer # noqa: F401 5 | from .tracker import BasePerformanceTracker # noqa: F401 6 | from .validator import noop_payload_validator # noqa: F401 7 | -------------------------------------------------------------------------------- /trinity-external-components/README.md: -------------------------------------------------------------------------------- 1 | # External Components 2 | 3 | This directory is for components that do not come bundled with Trinity itself and hence need to be installed through pip. 4 | 5 | ## Installing components from PyPi 6 | 7 | Run: `pip install 8 | 9 | ## Installing components from local directory 10 | 11 | Run `pip install /path/to/component` -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | warn_unused_ignores = True 3 | ignore_missing_imports = True 4 | strict_optional = False 5 | check_untyped_defs = True 6 | disallow_incomplete_defs = True 7 | disallow_untyped_defs = True 8 | disallow_any_generics = True 9 | disallow_untyped_calls = True 10 | warn_redundant_casts = True 11 | warn_unused_configs = True 12 | strict_equality = True 13 | plugins = sqlmypy 14 | -------------------------------------------------------------------------------- /trinity/db/network.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from trinity.config import ( 4 | TrinityConfig, 5 | ) 6 | 7 | from .orm import SCHEMA_VERSION 8 | 9 | 10 | def get_networkdb_path(config: TrinityConfig) -> Path: 11 | base_db_path = config.with_app_suffix( 12 | config.data_dir / "networkdb" 13 | ) 14 | return base_db_path.with_name(base_db_path.name + f".v{SCHEMA_VERSION}.sqlite3") 15 | -------------------------------------------------------------------------------- /trinity/_utils/errors.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | TypeVar 3 | ) 4 | from typing_extensions import Protocol 5 | 6 | 7 | class SupportsError(Protocol): 8 | error: Exception 9 | 10 | 11 | TSupportsError = TypeVar('TSupportsError', bound=SupportsError) 12 | 13 | 14 | def pass_or_raise(value: TSupportsError) -> TSupportsError: 15 | if value.error is not None: 16 | raise value.error 17 | 18 | return value 19 | -------------------------------------------------------------------------------- /trinity/protocol/wit/proto.py: -------------------------------------------------------------------------------- 1 | from p2p.protocol import BaseProtocol 2 | 3 | from .commands import BlockWitnessHashes, GetBlockWitnessHashes 4 | 5 | 6 | class WitnessProtocol(BaseProtocol): 7 | name = 'wit' 8 | version = 0 9 | commands = ( 10 | GetBlockWitnessHashes, 11 | BlockWitnessHashes, 12 | ) 13 | command_length = 24 # twelve more identified possibilities for new sync, plus wiggle room 14 | -------------------------------------------------------------------------------- /docs/api/protocol/index.rst: -------------------------------------------------------------------------------- 1 | Protocol 2 | ======== 3 | 4 | The protocol APIs form the basis for all communication with peers. They provide building blocks, 5 | e.g. to create chain synchronizations, but do not implement them themselves. 6 | 7 | .. include:: /fragments/unstable_api.rst 8 | 9 | .. toctree:: 10 | :maxdepth: 4 11 | :name: toc-trinity-api-protocol 12 | 13 | common/index 14 | eth/index 15 | les/index 16 | 17 | -------------------------------------------------------------------------------- /docs/api/protocol/les/api.events.rst: -------------------------------------------------------------------------------- 1 | Events 2 | ====== 3 | 4 | LES Protocol Events 5 | ------------------- 6 | 7 | Events used for the LES peer protocols. These events derive from :class:`~lahja.BaseEvent` and 8 | can thus be consumed through the event bus. 9 | 10 | .. autoclass:: trinity.protocol.les.events.GetBlockHeadersEvent 11 | :members: 12 | 13 | .. autoclass:: trinity.protocol.les.events.SendBlockHeadersEvent 14 | :members: 15 | -------------------------------------------------------------------------------- /trinity/_utils/assertions.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from eth_utils import is_list_like 4 | 5 | 6 | def assert_type_equality(left: Any, right: Any) -> None: 7 | assert type(left) is type(right) 8 | if is_list_like(left): 9 | assert is_list_like(right) 10 | assert len(left) == len(right) 11 | for left_item, right_item in zip(left, right): 12 | assert_type_equality(left_item, right_item) 13 | -------------------------------------------------------------------------------- /trinity/_utils/log_messages.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | 4 | def create_missing_ipc_error_message(ipc_path: Path) -> str: 5 | log_message = ( 6 | f"The IPC path at {str(ipc_path)} is not found. \n" 7 | "Please run " 8 | "'trinity --data-dir attach' " 9 | "or 'trinity attach '" 10 | "to specify the IPC path." 11 | ) 12 | return log_message 13 | -------------------------------------------------------------------------------- /trinity/tools/async_method.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from typing import Callable 3 | 4 | 5 | async def wait_until_true( 6 | predicate: Callable[[], bool], timeout: float = 1.0 7 | ) -> bool: 8 | async def _check() -> bool: 9 | while True: 10 | if predicate(): 11 | return True 12 | else: 13 | await asyncio.sleep(0) 14 | 15 | return await asyncio.wait_for(_check(), timeout=timeout) 16 | -------------------------------------------------------------------------------- /trinity/protocol/les/constants.py: -------------------------------------------------------------------------------- 1 | # Max number of items we can ask for in LES requests. These are the values used in geth and if we 2 | # ask for more than this the peers will disconnect from us. 3 | MAX_HEADERS_FETCH = 192 4 | MAX_BODIES_FETCH = 32 5 | MAX_RECEIPTS_FETCH = 128 6 | MAX_CODE_FETCH = 64 7 | MAX_PROOFS_FETCH = 64 8 | MAX_HEADER_PROOFS_FETCH = 64 9 | 10 | # Types of LES Announce messages 11 | LES_ANNOUNCE_SIMPLE = 1 12 | LES_ANNOUNCE_SIGNED = 2 13 | -------------------------------------------------------------------------------- /docs/guides/index.rst: -------------------------------------------------------------------------------- 1 | Guides 2 | ====== 3 | 4 | This section aims to provide hands-on guides to demonstrate how to use Trinity. If you are looking for detailed API descriptions check out the :doc:`API section `. 5 | 6 | .. toctree:: 7 | :maxdepth: 3 8 | :name: toc-trinity-guides 9 | 10 | quickstart 11 | running_trinity_on_dappnode 12 | architecture 13 | creating_a_custom_testnet 14 | writing_components 15 | setting_up_local_monitoring -------------------------------------------------------------------------------- /trinity-external-components/examples/peer_count_reporter/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from setuptools import setup 4 | 5 | entry_point = 'peer_count_reporter_component=peer_count_reporter_component:PeerCountReporterComponent' 6 | 7 | setup( 8 | name='trinity-peer-count-reporter-component', 9 | py_modules=['peer_count_reporter_component'], 10 | entry_points={ 11 | 'trinity.components': entry_point, 12 | }, 13 | ) 14 | -------------------------------------------------------------------------------- /p2p/tools/paragon/proto.py: -------------------------------------------------------------------------------- 1 | from p2p.protocol import BaseProtocol 2 | from p2p._utils import get_logger 3 | 4 | from .commands import ( 5 | BroadcastData, 6 | GetSum, 7 | Sum, 8 | ) 9 | 10 | 11 | class ParagonProtocol(BaseProtocol): 12 | name = 'paragon' 13 | version = 1 14 | commands = ( 15 | BroadcastData, 16 | GetSum, Sum, 17 | ) 18 | command_length = 3 19 | logger = get_logger("p2p.tools.paragon.proto.ParagonProtocol") 20 | -------------------------------------------------------------------------------- /trinity/_utils/shellart.py: -------------------------------------------------------------------------------- 1 | from termcolor import ( 2 | colored 3 | ) 4 | 5 | 6 | def bold_green(txt: str) -> str: 7 | return colored(txt, 'green', attrs=['bold']) 8 | 9 | 10 | def bold_red(txt: str) -> str: 11 | return colored(txt, 'red', attrs=['bold']) 12 | 13 | 14 | def bold_yellow(txt: str) -> str: 15 | return colored(txt, 'yellow', attrs=['bold']) 16 | 17 | 18 | def bold_white(txt: str) -> str: 19 | return colored(txt, 'white', attrs=['bold']) 20 | -------------------------------------------------------------------------------- /trinity/sync/common/types.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | NamedTuple, 3 | ) 4 | 5 | from eth_typing import ( 6 | BlockNumber, 7 | ) 8 | 9 | 10 | class SyncProgress(NamedTuple): 11 | starting_block: BlockNumber 12 | current_block: BlockNumber 13 | highest_block: BlockNumber 14 | 15 | def update_current_block(self, new_current_block: BlockNumber) -> 'SyncProgress': 16 | return SyncProgress(self.starting_block, new_current_block, self.highest_block) 17 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7 2 | # Set up code directory 3 | RUN mkdir -p /usr/src/app 4 | WORKDIR /usr/src/app 5 | 6 | COPY . /usr/src/app 7 | 8 | # Install deps 9 | RUN apt-get update 10 | RUN apt-get -y install libsnappy-dev 11 | 12 | RUN pip install -e .[dev] --no-cache-dir 13 | RUN pip install -U trinity --no-cache-dir 14 | 15 | RUN echo "Type \`trinity\` to boot or \`trinity --help\` for an overview of commands" 16 | 17 | EXPOSE 30303 30303/udp 18 | ENTRYPOINT ["trinity"] 19 | -------------------------------------------------------------------------------- /docs/api/index.rst: -------------------------------------------------------------------------------- 1 | API 2 | === 3 | 4 | This section aims to provide a detailed description of all APIs. If you are looking for something more hands-on or higher-level check out the existing :doc:`guides `. 5 | 6 | .. warning:: 7 | 8 | We expect each alpha release to have breaking changes to the API. 9 | 10 | .. toctree:: 11 | :maxdepth: 4 12 | :name: toc-api-trinity 13 | 14 | api.cli 15 | api.config 16 | extensibility/index 17 | protocol/index 18 | 19 | 20 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. include:: introduction.rst 2 | 3 | 4 | Table of contents 5 | ============================== 6 | 7 | .. toctree:: 8 | :maxdepth: 1 9 | :caption: General 10 | 11 | introduction 12 | quickstart 13 | release_notes 14 | 15 | .. toctree:: 16 | :maxdepth: 1 17 | :caption: Fundamentals 18 | 19 | api/index 20 | cookbook 21 | guides/index 22 | 23 | .. toctree:: 24 | :maxdepth: 1 25 | :caption: Community 26 | 27 | contributing 28 | code_of_conduct 29 | -------------------------------------------------------------------------------- /tests/core/peer_helpers.py: -------------------------------------------------------------------------------- 1 | from trinity.protocol.eth.peer import ETHPeerPool 2 | 3 | 4 | class MockPeerPoolWithConnectedPeers(ETHPeerPool): 5 | def __init__(self, peers, event_bus=None) -> None: 6 | super().__init__(privkey=None, context=None, event_bus=event_bus) 7 | for peer in peers: 8 | self.connected_nodes[peer.session] = peer 9 | 10 | async def run(self) -> None: 11 | raise NotImplementedError("This is a mock PeerPool implementation, you must not _run() it") 12 | -------------------------------------------------------------------------------- /trinity/http/exceptions.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Type 2 | 3 | 4 | class APIServerError(Exception): 5 | ... 6 | 7 | 8 | class InvalidRequestSyntaxError_400(APIServerError): 9 | ... 10 | 11 | 12 | class NotFoundError_404(APIServerError): 13 | ... 14 | 15 | 16 | class InternalError_500(APIServerError): 17 | ... 18 | 19 | 20 | EXCEPTION_TO_STATUS: Dict[Type[Exception], int] = { 21 | InvalidRequestSyntaxError_400: 400, 22 | NotFoundError_404: 404, 23 | InternalError_500: 500, 24 | } 25 | -------------------------------------------------------------------------------- /trinity/nodes/events.py: -------------------------------------------------------------------------------- 1 | from dataclasses import ( 2 | dataclass, 3 | ) 4 | from typing import ( 5 | Type, 6 | ) 7 | 8 | from lahja import ( 9 | BaseEvent, 10 | BaseRequestResponseEvent, 11 | ) 12 | 13 | 14 | @dataclass 15 | class NetworkIdResponse(BaseEvent): 16 | 17 | network_id: int 18 | 19 | 20 | class NetworkIdRequest(BaseRequestResponseEvent[NetworkIdResponse]): 21 | 22 | @staticmethod 23 | def expected_response_type() -> Type[NetworkIdResponse]: 24 | return NetworkIdResponse 25 | -------------------------------------------------------------------------------- /tests/trinity_long_run/test_trinity_long_run.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from tests.integration.helpers import ( 4 | run_command_and_detect_errors, 5 | ) 6 | 7 | 8 | @pytest.mark.parametrize( 9 | 'command', 10 | ( 11 | # ropsten 12 | ('trinity', '--ropsten',), 13 | ) 14 | ) 15 | @pytest.mark.asyncio 16 | async def test_does_not_throw_errors_on_long_run(command): 17 | # Ensure that no errors are thrown when trinity is run for 90 seconds 18 | await run_command_and_detect_errors(command, 90) 19 | -------------------------------------------------------------------------------- /trinity/extensibility/__init__.py: -------------------------------------------------------------------------------- 1 | from trinity.extensibility.asyncio import ( # noqa: F401 2 | AsyncioIsolatedComponent, 3 | ) 4 | from trinity.extensibility.component import ( # noqa: F401 5 | Application, 6 | BaseComponentAPI, 7 | BaseIsolatedComponent, 8 | ComponentAPI, 9 | ) 10 | from trinity.extensibility.component_manager import ( # noqa: F401 11 | ComponentManager, 12 | ) 13 | from trinity.extensibility.trio import ( # noqa: F401 14 | TrioComponent, 15 | TrioIsolatedComponent, 16 | ) 17 | -------------------------------------------------------------------------------- /trinity/tools/factories/common.py: -------------------------------------------------------------------------------- 1 | 2 | try: 3 | import factory 4 | except ImportError: 5 | raise ImportError( 6 | "The p2p.tools.factories module requires the `factory_boy` and `faker` libraries." 7 | ) 8 | 9 | 10 | from trinity.protocol.common.payloads import BlockHeadersQuery 11 | 12 | 13 | class BlockHeadersQueryFactory(factory.Factory): 14 | class Meta: 15 | model = BlockHeadersQuery 16 | 17 | block_number_or_hash = 0 18 | max_headers = 1 19 | skip = 0 20 | reverse = False 21 | -------------------------------------------------------------------------------- /.circleci/build_influxdb.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sudo apt-get install influxdb; 4 | sudo apt-get install influxdb-client; 5 | (sudo influxd -config /etc/influxdb/influxdb.conf) & 6 | # wait until influxdb service is actually available on port 8086 7 | (while netstat -lnt | awk '$4 ~ /:8086$/ {exit 1}'; do sleep 1; done; 8 | sudo influx --execute "CREATE DATABASE trinity"; 9 | sudo influx --execute "USE trinity"; 10 | sudo influx --execute "CREATE USER \"trinity\" WITH PASSWORD 'trinity' WITH ALL PRIVILEGES") & 11 | (sleep infinity;) 12 | -------------------------------------------------------------------------------- /docker/beacon.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7 2 | # Set up code directory 3 | RUN mkdir -p /usr/src/app 4 | WORKDIR /usr/src/app 5 | 6 | # Install deps 7 | RUN apt-get update 8 | RUN apt-get -y install libsnappy-dev gcc g++ cmake 9 | 10 | RUN git clone https://github.com/ethereum/trinity.git . 11 | RUN pip install -e .[eth2-dev] --no-cache-dir --use-feature=2020-resolver 12 | 13 | RUN echo "Type \`trinity-beacon\` to boot or \`trinity-beacon --help\` for an overview of commands" 14 | 15 | EXPOSE 30303 30303/udp 16 | ENTRYPOINT ["trinity-beacon"] 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * Trinity version: x.x.x 2 | * OS: osx/linux/win 3 | * Python version (output of `python --version`): 4 | * Environment (output of `pip freeze`): 5 | 6 | ### What is wrong? 7 | 8 | Please include information like: 9 | 10 | * full output of the error you received 11 | * what command you ran 12 | * the code that caused the failure (see [this link](https://help.github.com/articles/basic-writing-and-formatting-syntax/) for help with formatting code) 13 | 14 | 15 | ### How can it be fixed 16 | 17 | Fill this in if you know how to fix it. 18 | -------------------------------------------------------------------------------- /trinity/protocol/common/abc.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from eth_typing import BlockNumber, Hash32 4 | 5 | 6 | class ChainInfoAPI(ABC): 7 | network_id: int 8 | genesis_hash: Hash32 9 | 10 | 11 | class HeadInfoAPI(ABC): 12 | 13 | @property 14 | @abstractmethod 15 | def head_td(self) -> int: 16 | ... 17 | 18 | @property 19 | @abstractmethod 20 | def head_hash(self) -> Hash32: 21 | ... 22 | 23 | @property 24 | @abstractmethod 25 | def head_number(self) -> BlockNumber: 26 | ... 27 | -------------------------------------------------------------------------------- /p2p/typing.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Tuple, TypeVar, Union, NewType, NamedTuple 2 | 3 | TCommandPayload = TypeVar('TCommandPayload') 4 | 5 | 6 | Structure = Union[ 7 | Tuple[Tuple[str, Any], ...], 8 | ] 9 | 10 | 11 | Capability = Tuple[str, int] 12 | Capabilities = Tuple[Capability, ...] 13 | AES128Key = NewType("AES128Key", bytes) 14 | Nonce = NewType("Nonce", bytes) 15 | IDNonce = NewType("IDNonce", bytes) 16 | 17 | 18 | class SessionKeys(NamedTuple): 19 | encryption_key: AES128Key 20 | decryption_key: AES128Key 21 | auth_response_key: AES128Key 22 | -------------------------------------------------------------------------------- /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.1.0-alpha.37 3 | commit = True 4 | tag = True 5 | parse = (?P\d+)\.(?P\d+)\.(?P\d+)(-(?P[^.]*)\.(?P\d+))? 6 | serialize = 7 | {major}.{minor}.{patch}-{stage}.{devnum} 8 | {major}.{minor}.{patch} 9 | 10 | [bumpversion:part:stage] 11 | optional_value = stable 12 | first_value = stable 13 | values = 14 | alpha 15 | beta 16 | stable 17 | 18 | [bumpversion:part:devnum] 19 | 20 | [bumpversion:file:setup.py] 21 | search = version='{current_version}', 22 | replace = version='{new_version}', 23 | -------------------------------------------------------------------------------- /p2p/receipt.py: -------------------------------------------------------------------------------- 1 | from typing import Type 2 | 3 | from p2p.abc import ( 4 | HandshakeCheckAPI, 5 | HandshakeReceiptAPI, 6 | ProtocolAPI, 7 | ) 8 | 9 | 10 | class HandshakeReceipt(HandshakeReceiptAPI): 11 | """ 12 | Data storage object for ephemeral data exchanged during protocol 13 | handshakes. 14 | """ 15 | protocol: ProtocolAPI 16 | 17 | def __init__(self, protocol: ProtocolAPI) -> None: 18 | self.protocol = protocol 19 | 20 | def was_check_performed(self, check_type: Type[HandshakeCheckAPI]) -> bool: 21 | return False 22 | -------------------------------------------------------------------------------- /docs/_static/js/matomo.js: -------------------------------------------------------------------------------- 1 | var _paq = window._paq || []; 2 | /* tracker methods like "setCustomDimension" should be called before "trackPageView" */ 3 | _paq.push(['trackPageView']); 4 | _paq.push(['enableLinkTracking']); 5 | (function() { 6 | var u="https://matomo.ethereum.org/piwik/"; 7 | _paq.push(['setTrackerUrl', u+'matomo.php']); 8 | _paq.push(['setSiteId', '16']); 9 | var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; 10 | g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s); 11 | })(); 12 | -------------------------------------------------------------------------------- /tests/core/tools/test_async_runner.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from trinity._utils.async_iter import contains_all 4 | from trinity.tools.async_process_runner import AsyncProcessRunner 5 | 6 | 7 | @pytest.mark.parametrize("command", (("yes",),)) 8 | @pytest.mark.asyncio 9 | async def test_async_process_runner(command): 10 | async with AsyncProcessRunner.run(command, timeout_sec=1) as runner: 11 | assert not await contains_all(runner.stderr, {"Inexistent keyword"}) 12 | return 13 | raise AssertionError("Unreachable: AsyncProcessRunner skipped the return statement") 14 | -------------------------------------------------------------------------------- /dappnode/build/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7 2 | 3 | WORKDIR /usr/src/app 4 | 5 | # Install deps 6 | RUN apt-get update 7 | RUN apt-get -y install libsnappy-dev gcc g++ cmake 8 | 9 | ARG GIT_REPOSITORY=ethereum/trinity 10 | ARG GITREF=v0.1.0-alpha.36 11 | 12 | RUN git clone https://github.com/$GIT_REPOSITORY.git 13 | RUN cd trinity && git checkout $GITREF && pip install -e .[dev] --no-cache-dir 14 | 15 | EXPOSE 30303 30303/udp 16 | # Trinity shutdowns aren't yet solid enough to avoid the fix-unclean-shutdown 17 | ENTRYPOINT trinity $EXTRA_OPTS fix-unclean-shutdown && trinity $EXTRA_OPTS 18 | -------------------------------------------------------------------------------- /p2p/tools/handshake.py: -------------------------------------------------------------------------------- 1 | from typing import Type 2 | 3 | from p2p.abc import MultiplexerAPI, ProtocolAPI 4 | from p2p.handshake import Handshaker 5 | from p2p.receipt import HandshakeReceipt 6 | 7 | 8 | class NoopHandshaker(Handshaker[ProtocolAPI]): 9 | def __init__(self, protocol_class: Type[ProtocolAPI]) -> None: 10 | self.protocol_class = protocol_class 11 | 12 | async def do_handshake(self, 13 | multiplexer: MultiplexerAPI, 14 | protocol: ProtocolAPI) -> HandshakeReceipt: 15 | return HandshakeReceipt(protocol) 16 | -------------------------------------------------------------------------------- /p2p/disconnect.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | 4 | @enum.unique 5 | class DisconnectReason(enum.Enum): 6 | """More details at https://github.com/ethereum/wiki/wiki/%C3%90%CE%9EVp2p-Wire-Protocol#p2p""" 7 | DISCONNECT_REQUESTED = 0 8 | TCP_SUB_SYSTEM_ERROR = 1 9 | BAD_PROTOCOL = 2 10 | USELESS_PEER = 3 11 | TOO_MANY_PEERS = 4 12 | ALREADY_CONNECTED = 5 13 | INCOMPATIBLE_P2P_VERSION = 6 14 | NULL_NODE_IDENTITY_RECEIVED = 7 15 | CLIENT_QUITTING = 8 16 | UNEXPECTED_IDENTITY = 9 17 | CONNECTED_TO_SELF = 10 18 | TIMEOUT = 11 19 | SUBPROTOCOL_ERROR = 16 20 | -------------------------------------------------------------------------------- /tests/p2p/stats-utils/test_percentile.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from p2p.stats.percentile import Percentile 4 | 5 | 6 | @pytest.mark.parametrize( 7 | 'data,percentile,window_size,expected', 8 | ( 9 | (range(6), 0.2, 6, 1), 10 | (range(11), 0.4, 11, 4), 11 | (range(11), 0.2, 6, 6), 12 | ), 13 | ) 14 | def test_percentile_class(data, percentile, window_size, expected): 15 | percentile = Percentile(percentile=percentile, window_size=window_size) 16 | for value in data: 17 | percentile.update(value) 18 | 19 | assert percentile.value == expected 20 | -------------------------------------------------------------------------------- /trinity/sync/common/checkpoint.py: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple 2 | 3 | from eth_typing import ( 4 | Hash32, 5 | ) 6 | from eth_utils import ( 7 | humanize_hash 8 | ) 9 | 10 | 11 | class Checkpoint(NamedTuple): 12 | """ 13 | Represent a checkpoint from where the syncing process can start off 14 | when not starting from genesis. 15 | """ 16 | block_hash: Hash32 17 | score: int 18 | 19 | def __str__(self) -> str: 20 | return ( 21 | f"" 23 | ) 24 | -------------------------------------------------------------------------------- /p2p/tools/paragon/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import ( # noqa: F401 2 | ParagonAPI, 3 | ) 4 | from .commands import ( # noqa: F401 5 | BroadcastData, 6 | GetSum, 7 | Sum, 8 | ) 9 | from .payloads import ( # noqa: F401 10 | BroadcastDataPayload, 11 | GetSumPayload, 12 | SumPayload, 13 | ) 14 | from .peer import ( # noqa: F401 15 | ParagonHandshaker, 16 | ParagonContext, 17 | ParagonMockPeerPoolWithConnectedPeers, 18 | ParagonPeer, 19 | ParagonPeerFactory, 20 | ParagonPeerPool, 21 | ) 22 | from .proto import ( # noqa: F401 23 | ParagonProtocol, 24 | ) 25 | -------------------------------------------------------------------------------- /tests/p2p/test_duplicates_util.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from p2p._utils import duplicates 4 | 5 | 6 | @pytest.mark.parametrize( 7 | 'elements,expected', 8 | ( 9 | ((), ()), 10 | ((1,), ()), 11 | ((1, 2), ()), 12 | ((2, 1, 2), (2,)), 13 | ((2, 1, 2, 4, 3, 4), (2, 4)), 14 | ([], ()), 15 | ([1], ()), 16 | ([1, 2], ()), 17 | ([2, 1, 2], (2,)), 18 | ([2, 1, 2, 3, 4, 3], (2, 3)), 19 | ), 20 | ) 21 | def test_duplicates_with_identity_fn(elements, expected): 22 | dups = duplicates(elements) 23 | assert dups == expected 24 | -------------------------------------------------------------------------------- /trinity/tools/factories/address.py: -------------------------------------------------------------------------------- 1 | 2 | try: 3 | import factory 4 | except ImportError: 5 | raise ImportError("The p2p.tools.factories module requires the `factory_boy` library.") 6 | 7 | import secrets 8 | from typing import Any, Type 9 | 10 | from eth_typing import Address 11 | 12 | 13 | class AddressFactory(factory.Factory): 14 | class Meta: 15 | model = bytes 16 | 17 | @classmethod 18 | def _create(cls, 19 | model_class: Type[bytes], 20 | *args: Any, 21 | **kwargs: Any) -> Address: 22 | return Address(model_class(secrets.token_bytes(20))) 23 | -------------------------------------------------------------------------------- /trinity/_utils/trio_utils.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | Any, 3 | Awaitable, 4 | Callable, 5 | Sequence, 6 | ) 7 | 8 | import trio 9 | 10 | 11 | async def wait_first(callables: Sequence[Callable[[], Awaitable[Any]]]) -> None: 12 | """ 13 | Run any number of tasks but cancel out any outstanding tasks as soon as the first one finishes. 14 | """ 15 | async with trio.open_nursery() as nursery: 16 | for task in callables: 17 | async def _run_then_cancel() -> None: 18 | await task() 19 | nursery.cancel_scope.cancel() 20 | nursery.start_soon(_run_then_cancel) 21 | -------------------------------------------------------------------------------- /trinity/tools/factories/block_body.py: -------------------------------------------------------------------------------- 1 | try: 2 | import factory 3 | except ImportError: 4 | raise ImportError("The p2p.tools.factories module requires the `factory_boy` library.") 5 | 6 | from trinity.rlp.block_body import BlockBody 7 | 8 | from .headers import BlockHeaderFactory 9 | from .transactions import UninterpretedTransactionFactory 10 | 11 | 12 | class BlockBodyFactory(factory.Factory): 13 | class Meta: 14 | model = BlockBody 15 | 16 | transactions = factory.LazyFunction(lambda: UninterpretedTransactionFactory.create_batch(5)) 17 | uncles = factory.LazyFunction(lambda: BlockHeaderFactory.create_batch(2)) 18 | -------------------------------------------------------------------------------- /trinity/components/builtin/network_db/eth1_peer_db/events.py: -------------------------------------------------------------------------------- 1 | from dataclasses import ( 2 | dataclass, 3 | ) 4 | import datetime 5 | from typing import Optional 6 | 7 | from lahja import ( 8 | BaseEvent, 9 | ) 10 | 11 | from eth_typing import Hash32 12 | 13 | from p2p.abc import NodeAPI 14 | 15 | 16 | class BasePeerDBEvent(BaseEvent): 17 | pass 18 | 19 | 20 | @dataclass 21 | class TrackPeerEvent(BasePeerDBEvent): 22 | 23 | remote: NodeAPI 24 | is_outbound: bool 25 | last_connected_at: Optional[datetime.datetime] 26 | genesis_hash: Hash32 27 | protocol: str 28 | protocol_version: int 29 | network_id: int 30 | -------------------------------------------------------------------------------- /.circleci/merge_pr.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ -n "${CIRCLE_PR_NUMBER}" ]]; then 4 | PR_INFO_URL=https://api.github.com/repos/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/pulls/$CIRCLE_PR_NUMBER 5 | PR_BASE_BRANCH=$(curl -L "$PR_INFO_URL" | python -c 'import json, sys; obj = json.load(sys.stdin); sys.stdout.write(obj["base"]["ref"])') 6 | git fetch origin +"$PR_BASE_BRANCH":circleci/pr-base 7 | # We need these config values or git complains when creating the 8 | # merge commit 9 | git config --global user.name "Circle CI" 10 | git config --global user.email "circleci@example.com" 11 | git merge --no-edit circleci/pr-base 12 | fi 13 | -------------------------------------------------------------------------------- /tests/p2p/stats-utils/test_standard_deviation.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from p2p.stats.stddev import StandardDeviation 4 | 5 | 6 | @pytest.mark.parametrize( 7 | "data,expected", 8 | ( 9 | ((4, 2, 5, 8, 6), 2.23606), 10 | ((1.5, 1.8, 7, 1.2, 1.35), 2.4863), 11 | ((2, 2, 2, 2, 2), 0), 12 | ((1, 3, 5, 7, 9), 3.1622), 13 | ((100, 200, 300, 400, 500, 1, 3, 5, 7, 9), 3.1622), 14 | ), 15 | ) 16 | def test_standard_deviation(data, expected): 17 | stddev = StandardDeviation(window_size=5) 18 | 19 | for value in data: 20 | stddev.update(value) 21 | 22 | assert abs(stddev.value - expected) < 0.01 23 | -------------------------------------------------------------------------------- /docs/api/extensibility/api.extensibility.component.rst: -------------------------------------------------------------------------------- 1 | Component 2 | ========= 3 | 4 | 5 | BaseComponentAPI 6 | ---------------- 7 | 8 | .. autoclass:: trinity.extensibility.component.BaseComponentAPI 9 | :members: 10 | 11 | 12 | Application 13 | ----------- 14 | 15 | .. autoclass:: trinity.extensibility.component.Application 16 | :members: 17 | 18 | 19 | ComponentAPI 20 | ------------------------ 21 | 22 | .. autoclass:: trinity.extensibility.component.ComponentAPI 23 | :members: 24 | 25 | 26 | AsyncioIsolatedComponent 27 | ------------------------ 28 | 29 | .. autoclass:: trinity.extensibility.asyncio.AsyncioIsolatedComponent 30 | :members: 31 | -------------------------------------------------------------------------------- /trinity/_utils/timer.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | 4 | class Timer: 5 | _start: float = None 6 | 7 | def __init__(self, auto_start: bool = True) -> None: 8 | if auto_start: 9 | self.start() 10 | 11 | def start(self) -> None: 12 | self._start = time.perf_counter() 13 | 14 | def pop_elapsed(self) -> float: 15 | """Return time elapsed since last start, and start the timer over""" 16 | now = time.perf_counter() 17 | elapsed = now - self._start 18 | self._start = now 19 | return elapsed 20 | 21 | @property 22 | def elapsed(self) -> float: 23 | return time.perf_counter() - self._start 24 | -------------------------------------------------------------------------------- /trinity/tools/factories/block_hash.py: -------------------------------------------------------------------------------- 1 | try: 2 | import factory 3 | except ImportError: 4 | raise ImportError("The p2p.tools.factories module requires the `factory_boy` library.") 5 | 6 | import secrets 7 | from typing import Any, Type 8 | 9 | from eth_typing import Hash32 10 | 11 | 12 | class Hash32Factory(factory.Factory): 13 | class Meta: 14 | model = bytes 15 | 16 | @classmethod 17 | def _create(cls, 18 | model_class: Type[bytes], 19 | *args: Any, 20 | **kwargs: Any) -> Hash32: 21 | return Hash32(model_class(secrets.token_bytes(32))) 22 | 23 | 24 | BlockHashFactory = Hash32Factory 25 | -------------------------------------------------------------------------------- /tests/core/chains/test_light_peer_chain.py: -------------------------------------------------------------------------------- 1 | from trinity.sync.light.service import ( 2 | LightPeerChain 3 | ) 4 | from trinity.chains.light_eventbus import ( 5 | EventBusLightPeerChain 6 | ) 7 | 8 | 9 | # These tests may seem obvious but they safe us from runtime errors where 10 | # changes are made to the `BaseLightPeerChain` that are then forgotton to 11 | # implement on both derived chains. 12 | 13 | def test_can_instantiate_eventbus_light_peer_chain(): 14 | chain = EventBusLightPeerChain(None) 15 | assert chain is not None 16 | 17 | 18 | def test_can_instantiate_light_peer_chain(): 19 | chain = LightPeerChain(None, None) 20 | assert chain is not None 21 | -------------------------------------------------------------------------------- /trinity/rpc/modules/web3.py: -------------------------------------------------------------------------------- 1 | from eth_hash.auto import keccak 2 | from eth_utils import decode_hex, encode_hex 3 | 4 | from trinity._utils.version import construct_trinity_client_identifier 5 | 6 | from trinity.rpc.modules import ( 7 | BaseRPCModule 8 | ) 9 | 10 | 11 | class Web3(BaseRPCModule): 12 | 13 | async def clientVersion(self) -> str: 14 | """ 15 | Returns the current client version. 16 | """ 17 | return construct_trinity_client_identifier() 18 | 19 | async def sha3(self, data: str) -> str: 20 | """ 21 | Returns Keccak-256 of the given data. 22 | """ 23 | return encode_hex(keccak(decode_hex(data))) 24 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = py-evm 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /p2p/subscription.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | Any, 3 | Callable, 4 | Type, 5 | ) 6 | from types import TracebackType 7 | 8 | from p2p.abc import SubscriptionAPI 9 | 10 | 11 | class Subscription(SubscriptionAPI): 12 | def __init__(self, cancel_fn: Callable[[], Any]) -> None: 13 | self._cancel_fn = cancel_fn 14 | 15 | def cancel(self) -> None: 16 | self._cancel_fn() 17 | 18 | def __enter__(self) -> SubscriptionAPI: 19 | return self 20 | 21 | def __exit__(self, 22 | exc_type: Type[BaseException], 23 | exc_value: BaseException, 24 | exc_tb: TracebackType) -> None: 25 | self._cancel_fn() 26 | -------------------------------------------------------------------------------- /trinity/assets/eip1085/devnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "accounts":{ 3 | "0x00000000000000000000000000000000deadbeef":{ 4 | "balance":"0x0ad78ebc5ac6200000" 5 | }, 6 | "0x00000000000000000000000000000000deadcafe":{ 7 | "balance":"0x0ad78ebc5ac6200000" 8 | } 9 | }, 10 | "genesis":{ 11 | "author":"0x0000000000000000000000000000000000000000", 12 | "difficulty":"0x1", 13 | "extraData":"0xdeadbeef", 14 | "gasLimit":"0x900000", 15 | "nonce":"0x00000000deadbeef", 16 | "timestamp":"0x0" 17 | }, 18 | "params":{ 19 | "berlinForkBlock":"0x0", 20 | "chainId":"0xdeadbeef", 21 | "miningMethod":"ethash" 22 | }, 23 | "version":"1" 24 | } 25 | -------------------------------------------------------------------------------- /docs/fragments/virtualenv_explainer.rst: -------------------------------------------------------------------------------- 1 | **Optional:** Often, the best way to guarantee a clean Python 3 environment is with 2 | `virtualenv `_. If we don't have ``virtualenv`` installed 3 | already, we first need to install it via pip. 4 | 5 | .. code:: sh 6 | 7 | pip install virtualenv 8 | 9 | Then, we can initialize a new virtual environment ``venv``, like: 10 | 11 | .. code:: sh 12 | 13 | virtualenv -p python3 venv 14 | 15 | This creates a new directory ``venv`` where packages are installed isolated from any other global 16 | packages. 17 | 18 | To activate the virtual directory we have to *source* it 19 | 20 | .. code:: sh 21 | 22 | . venv/bin/activate -------------------------------------------------------------------------------- /tests/integration/test_component_discovery.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from trinity.components.registry import ( 3 | discover_components 4 | ) 5 | 6 | 7 | def test_component_discovery(request): 8 | if not request.config.getoption("--integration"): 9 | pytest.skip("Not asked to run integration tests") 10 | 11 | # This component is external to this code base and installed by tox 12 | # In order to install it locally run: 13 | # pip install -e trinity-external-components/examples/peer_count_reporter 14 | from peer_count_reporter_component import PeerCountReporterComponent 15 | 16 | components = discover_components() 17 | assert PeerCountReporterComponent in components 18 | -------------------------------------------------------------------------------- /.circleci/build_geth.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mkdir -p $HOME/.ethash 4 | pip install --user py-geth>=2.1.0 5 | export GOROOT=/usr/local/go 6 | export GETH_BINARY="$HOME/.py-geth/geth-$GETH_VERSION/bin/geth" 7 | if [ ! -e "$GETH_BINARY" ]; then 8 | export GO_VERSION="1.13" 9 | wget "https://dl.google.com/go/go$GO_VERSION.linux-amd64.tar.gz" 10 | tar zxvf go$GO_VERSION.linux-amd64.tar.gz 11 | sudo chown -R root:root ./go 12 | sudo mv go /usr/local 13 | sudo ln -s /usr/local/go/bin/go /usr/local/bin/go 14 | sudo apt-get update; 15 | sudo apt-get install -y build-essential; 16 | python -m geth.install $GETH_VERSION; 17 | fi 18 | sudo ln -s $GETH_BINARY /usr/local/bin/geth 19 | geth version 20 | -------------------------------------------------------------------------------- /p2p/exchange/constants.py: -------------------------------------------------------------------------------- 1 | # The default timeout for a round trip API request and response from a peer. 2 | # 3 | # > NOTE: This value **MUST** be less than `p2p.constants.CONN_IDLE_TIMEOUT` for 4 | # it to be meaningful. Otherwise, the actual reading of the p2p message from 5 | # the network will timeout before this timeout is ever hit. 6 | ROUND_TRIP_TIMEOUT = 20.0 7 | 8 | 9 | # We send requests to peers one at a time, but might initiate a few locally before 10 | # they are sent. This is an estimate of how many get queued locally. The reason we 11 | # estimate the queue length is to determine how long a timeout to use when 12 | # waiting for the lock to send the next queued peer request. 13 | NUM_QUEUED_REQUESTS = 4 14 | -------------------------------------------------------------------------------- /docs/api/api.config.rst: -------------------------------------------------------------------------------- 1 | Config 2 | ====== 3 | 4 | This section covers Trinity's configuration APIs that are being used within many internal 5 | components and are also exposed to the :doc:`extensibility API `. 6 | 7 | This is **not** about Trinity's command line configuration, which is covered in the 8 | :doc:`CLI section `. 9 | 10 | 11 | Trinity Config 12 | -------------- 13 | 14 | .. autoclass:: trinity.config.TrinityConfig 15 | :members: 16 | 17 | Base App Config 18 | --------------- 19 | 20 | .. autoclass:: trinity.config.BaseAppConfig 21 | :members: 22 | 23 | Eth1 App Config 24 | --------------- 25 | 26 | .. autoclass:: trinity.config.Eth1AppConfig 27 | :members: 28 | -------------------------------------------------------------------------------- /trinity/protocol/wit/handshaker.py: -------------------------------------------------------------------------------- 1 | from p2p.abc import MultiplexerAPI 2 | from p2p.handshake import Handshaker 3 | from p2p.receipt import HandshakeReceipt 4 | 5 | from .proto import WitnessProtocol 6 | 7 | 8 | class WitnessHandshakeReceipt(HandshakeReceipt): 9 | pass 10 | 11 | 12 | class WitnessHandshaker(Handshaker[WitnessProtocol]): 13 | protocol_class = WitnessProtocol 14 | 15 | async def do_handshake(self, 16 | multiplexer: MultiplexerAPI, 17 | protocol: WitnessProtocol) -> WitnessHandshakeReceipt: 18 | self.logger.debug("Performing %s handshake with %s", protocol, multiplexer.remote) 19 | return WitnessHandshakeReceipt(protocol) 20 | -------------------------------------------------------------------------------- /tests/integration/churn_state/FillStorage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.7; 2 | 3 | // FillStorage churns storage on each shuffle() call 4 | 5 | contract FillStorage { 6 | mapping(uint => uint) public balances; 7 | 8 | constructor () public { 9 | balances[0] = 1; 10 | } 11 | 12 | function shuffle(uint shuffle_width) public { 13 | for(uint i = 0; i web.Response: 15 | data = {'error': message} 16 | if exception is not None: 17 | status = EXCEPTION_TO_STATUS[exception.__class__] 18 | return web.json_response(data, status=status, reason=str(exception)) 19 | else: 20 | return web.json_response(data) 21 | 22 | 23 | class BaseHTTPHandler(ABC): 24 | 25 | @staticmethod 26 | @abstractmethod 27 | def handle(*arg: Any) -> web.Response: 28 | ... 29 | -------------------------------------------------------------------------------- /p2p/tools/factories/keys.py: -------------------------------------------------------------------------------- 1 | import secrets 2 | 3 | import factory 4 | 5 | from eth_utils import ( 6 | int_to_big_endian, 7 | ) 8 | 9 | from eth_keys import keys 10 | 11 | 12 | def _mk_private_key_bytes() -> bytes: 13 | return int_to_big_endian(secrets.randbits(256)).rjust(32, b'\x00') 14 | 15 | 16 | class PrivateKeyFactory(factory.Factory): 17 | class Meta: 18 | model = keys.PrivateKey 19 | 20 | private_key_bytes = factory.LazyFunction(_mk_private_key_bytes) 21 | 22 | 23 | def _mk_public_key_bytes() -> bytes: 24 | return PrivateKeyFactory().public_key.to_bytes() 25 | 26 | 27 | class PublicKeyFactory(factory.Factory): 28 | class Meta: 29 | model = keys.PublicKey 30 | 31 | public_key_bytes = factory.LazyFunction(_mk_public_key_bytes) 32 | -------------------------------------------------------------------------------- /trinity/components/builtin/metrics/service/asyncio.py: -------------------------------------------------------------------------------- 1 | import aiohttp 2 | import asyncio 3 | 4 | from trinity.components.builtin.metrics.service.base import BaseMetricsService 5 | 6 | 7 | class AsyncioMetricsService(BaseMetricsService): 8 | async def async_post(self, data: str) -> None: 9 | # use asyncio-compatible aiohttp for async http calls 10 | url = self.reporter._get_post_url() 11 | auth_header = self.reporter._generate_auth_header() 12 | async with aiohttp.ClientSession() as session: 13 | await session.post(url, data=data, headers=auth_header) 14 | 15 | async def continuously_report(self) -> None: 16 | while self.manager.is_running: 17 | await self.report_now() 18 | await asyncio.sleep(self._reporting_frequency) 19 | -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | # Block Workflow 2 | 3 | The following workflows are supported for blocks. 4 | 5 | ## 1. Block Building 6 | 7 | Incremental creation 8 | 9 | 1. Initialize Block - `Header.from_parent(...)`: 10 | - `coinbase` 11 | - `parent_hash` 12 | - `difficulty` 13 | - `block_number` 14 | - `gas_limit` 15 | - `timestamp` 16 | 2. Apply Transaction(s) - `Block.apply_transaction(...)`: 17 | 3. Mine Block - `Block.mine(...)`: 18 | - `uncles_hash` 19 | - `state_root` 20 | - `transaction_root` 21 | - `receipts_root` 22 | - `bloom` 23 | - `gas_used` 24 | - `extra_data` 25 | - `mix_hash` 26 | - `nonce` 27 | 28 | 29 | ## 2. Block Ingestion 30 | 31 | > (This is actually just a special case of use case #1.) 32 | 33 | Full ingestion of a complete block. 34 | -------------------------------------------------------------------------------- /p2p/session.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | import uuid 3 | 4 | from p2p.abc import NodeAPI, SessionAPI 5 | 6 | 7 | class Session(SessionAPI): 8 | def __init__(self, remote: NodeAPI, session_id: Optional[uuid.UUID] = None) -> None: 9 | if session_id is None: 10 | session_id = uuid.uuid4() 11 | self.id = session_id 12 | self.remote = remote 13 | 14 | def __str__(self) -> str: 15 | return f"" 16 | 17 | def __repr__(self) -> str: 18 | return f"Session({self.remote!r} {self.id!r})" 19 | 20 | def __hash__(self) -> int: 21 | return hash(self.id) 22 | 23 | def __eq__(self, other: Any) -> bool: 24 | if not type(self) is type(other): 25 | return False 26 | return self.id == other.id 27 | -------------------------------------------------------------------------------- /p2p/message.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | import rlp 4 | 5 | from p2p.abc import MessageAPI 6 | 7 | 8 | class Message(MessageAPI): 9 | def __init__(self, header: bytes, body: bytes): 10 | self.header = header 11 | self.body = body 12 | self.command_id = rlp.decode(self.body[:1], sedes=rlp.sedes.big_endian_int) 13 | self.encoded_payload = self.body[1:] 14 | 15 | def __eq__(self, other: Any) -> bool: 16 | if type(other) is not type(self): 17 | return False 18 | return self.header == other.header and self.body == other.body 19 | 20 | def __str__(self) -> str: 21 | return f"Message(header={self.header.hex()}, body={self.body.hex()})" 22 | 23 | def __repr__(self) -> str: 24 | return f"Message(header={self.header!r}, body={self.body!r})" 25 | -------------------------------------------------------------------------------- /trinity/components/builtin/metrics/abc.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod 2 | 3 | from async_service import ServiceAPI 4 | from pyformance import MetricsRegistry 5 | 6 | 7 | class MetricsServiceAPI(ServiceAPI): 8 | 9 | @abstractmethod 10 | def __init__(self, 11 | influx_server: str, 12 | influx_user: str, 13 | influx_password: str, 14 | influx_database: str, 15 | host: str, 16 | port: int, 17 | protocol: str, 18 | reporting_frequency: int) -> None: 19 | ... 20 | 21 | @property 22 | @abstractmethod 23 | def registry(self) -> MetricsRegistry: 24 | ... 25 | 26 | @abstractmethod 27 | async def send_annotation(self, annotation_data: str) -> None: 28 | ... 29 | -------------------------------------------------------------------------------- /tests/core/initialization/test_is_database_initialized.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | # TODO: use a custom chain class only for testing. 4 | from eth.chains.ropsten import ROPSTEN_GENESIS_HEADER 5 | from eth.db.backends.level import LevelDB 6 | from eth.db.chain import ChainDB 7 | 8 | from trinity.initialization import ( 9 | is_database_initialized, 10 | ) 11 | 12 | 13 | @pytest.fixture 14 | def chaindb(eth1_app_config): 15 | return ChainDB(LevelDB(db_path=eth1_app_config.database_dir)) 16 | 17 | 18 | def test_database_dir_not_initialized_without_canonical_head_block(chaindb): 19 | assert not is_database_initialized(chaindb) 20 | 21 | 22 | def test_fully_initialized_database_dir(chaindb): 23 | assert not is_database_initialized(chaindb) 24 | chaindb.persist_header(ROPSTEN_GENESIS_HEADER) 25 | assert is_database_initialized(chaindb) 26 | -------------------------------------------------------------------------------- /tests/core/filesystem-utils/test_is_under_path.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from trinity._utils.filesystem import ( 4 | is_under_path, 5 | ) 6 | 7 | 8 | @pytest.mark.parametrize( 9 | 'base_path,path,expected', 10 | ( 11 | # Same Path 12 | ('foo', 'foo', False), 13 | ('foo', 'foo/bar/..', False), 14 | # up a directory (or two) 15 | ('foo', '..', False), 16 | ('foo', 'foo/bar/../../', False), 17 | # relative and abs 18 | ('foo', '/foo/bar', False), 19 | ('foo', '/foo', False), 20 | # actually nested 21 | ('foo', 'foo/bar.sol', True), 22 | ('foo', 'foo/bar', True), 23 | ('foo', 'foo/bar/../../foo/baz', True), 24 | ), 25 | ) 26 | def test_is_under_path(base_path, path, expected): 27 | actual = is_under_path(base_path, path) 28 | assert actual is expected 29 | -------------------------------------------------------------------------------- /tests/core/utils/test_humanize_integer_sequence.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from trinity._utils.humanize import humanize_integer_sequence 4 | 5 | 6 | @pytest.mark.parametrize( 7 | 'seq,expected', 8 | ( 9 | ((), '(empty)'), 10 | ((1,), '1'), 11 | ((2,), '2'), 12 | ((10,), '10'), 13 | ((1, 2, 3), '1-3'), 14 | (range(6), '0-5'), 15 | ((1, 2, 3, 7, 8, 9), '1-3|7-9'), 16 | ((1, 2, 3, 5, 7, 8, 9), '1-3|5|7-9'), 17 | ((1, 3, 4, 5, 7, 8, 9), '1|3-5|7-9'), 18 | ((1, 3, 4, 5, 9), '1|3-5|9'), 19 | # should accept a generator 20 | ((_ for _ in range(0)), '(empty)'), 21 | ((i for i in (1, 2, 3, 7, 8, 10)), '1-3|7-8|10'), 22 | ), 23 | ) 24 | def test_humanize_integer_sequence(seq, expected): 25 | actual = humanize_integer_sequence(seq) 26 | assert actual == expected 27 | -------------------------------------------------------------------------------- /trinity/_utils/version.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import pkg_resources 4 | 5 | from trinity import __version__ 6 | 7 | 8 | def construct_trinity_client_identifier() -> str: 9 | """ 10 | Constructs the client identifier string 11 | 12 | e.g. 'Trinity/v1.2.3/darwin-amd64/python3.7.3' 13 | """ 14 | return ( 15 | "Trinity/" 16 | f"v{__version__}/" 17 | f"{sys.platform}/" 18 | f"{sys.implementation.name}" 19 | f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" 20 | ) 21 | 22 | 23 | def is_prerelease() -> bool: 24 | try: 25 | distro = pkg_resources.get_distribution("trinity") 26 | # mypy thinks that parsed_version is a tuple. Ignored... 27 | return distro.parsed_version.is_prerelease # type: ignore 28 | except pkg_resources.DistributionNotFound: 29 | return True 30 | -------------------------------------------------------------------------------- /trinity/tools/factories/chain_context.py: -------------------------------------------------------------------------------- 1 | try: 2 | import factory 3 | except ImportError: 4 | raise ImportError("The p2p.tools.factories module requires the `factory_boy` library.") 5 | 6 | from eth_utils import to_bytes 7 | 8 | from eth.chains.mainnet import MAINNET_VM_CONFIGURATION 9 | 10 | from trinity.protocol.common.context import ChainContext 11 | 12 | from .db import AsyncHeaderDBFactory 13 | 14 | MAINNET_GENESIS_HASH = to_bytes(hexstr='0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3') # noqa: E501 15 | 16 | 17 | class ChainContextFactory(factory.Factory): 18 | class Meta: 19 | model = ChainContext 20 | 21 | network_id = 1 22 | client_version_string = 'test' 23 | headerdb = factory.SubFactory(AsyncHeaderDBFactory) 24 | vm_configuration = ((0, MAINNET_VM_CONFIGURATION[-1][1]),) 25 | listen_port = 30303 26 | p2p_version = 5 27 | -------------------------------------------------------------------------------- /trinity/_utils/address.py: -------------------------------------------------------------------------------- 1 | import rlp 2 | 3 | from eth_hash.auto import keccak 4 | from eth_typing import Address 5 | 6 | 7 | def force_bytes_to_address(value: bytes) -> Address: 8 | """ 9 | Take any byte value and force it into becoming a valid address by padding it to 20 bytes 10 | or returning the first 20 bytes in case the provided value is longer than 20 bytes. 11 | """ 12 | trimmed_value = value[-20:] 13 | padded_value = trimmed_value.rjust(20, b'\x00') 14 | return Address(padded_value) 15 | 16 | 17 | def generate_contract_address(sender: Address, nonce: int) -> Address: 18 | """ 19 | Take the given ``sender```and ``nonce`` and generate an address in the same way 20 | as a *Contract Creation Transaction* creates a contract address when a contract 21 | is deployed. 22 | """ 23 | return force_bytes_to_address(keccak(rlp.encode([sender, nonce]))) 24 | -------------------------------------------------------------------------------- /trinity/_utils/os.py: -------------------------------------------------------------------------------- 1 | import re 2 | import resource 3 | import unicodedata 4 | 5 | 6 | def get_open_fd_limit() -> int: 7 | """ 8 | Return the OS soft limit of open file descriptors per process. 9 | """ 10 | soft_limit, hard_limit = resource.getrlimit(resource.RLIMIT_NOFILE) 11 | return soft_limit 12 | 13 | 14 | def friendly_filename_or_url(value: str) -> str: 15 | """ 16 | Normalize any string to be file name and URL friendly. 17 | Convert to lowercase, remove non-alpha characters, 18 | and convert spaces to hyphens. 19 | """ 20 | # Taken from: 21 | # https://stackoverflow.com/questions/295135/turn-a-string-into-a-valid-filename/295466#295466 22 | value = str(unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')) 23 | value = str(re.sub(r'[^\w\s-]', '', value).strip().lower()) 24 | value = str(re.sub(r'[-\s]+', '-', value)) 25 | return value 26 | -------------------------------------------------------------------------------- /tests/core/cli/test_trinity_repl.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from trinity.components.builtin.attach.console import console 4 | from pathlib import Path 5 | from trinity._utils.log_messages import ( 6 | create_missing_ipc_error_message, 7 | ) 8 | 9 | 10 | def test_console(caplog, jsonrpc_ipc_pipe_path): 11 | # if ipc_path is not found, raise an exception with a useful message 12 | with pytest.raises(FileNotFoundError): 13 | console(Path(jsonrpc_ipc_pipe_path)) 14 | assert create_missing_ipc_error_message(jsonrpc_ipc_pipe_path) in caplog.text 15 | 16 | 17 | def test_python_console(caplog, jsonrpc_ipc_pipe_path): 18 | # if ipc_path is not found, raise an exception with a useful message 19 | with pytest.raises(FileNotFoundError): 20 | console(Path(jsonrpc_ipc_pipe_path), use_ipython=False) 21 | assert create_missing_ipc_error_message(jsonrpc_ipc_pipe_path) in caplog.text 22 | -------------------------------------------------------------------------------- /trinity/protocol/common/context.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | Tuple, 3 | Type, 4 | ) 5 | 6 | from eth.abc import ( 7 | BlockNumber, 8 | VirtualMachineAPI, 9 | ) 10 | 11 | from p2p.peer import BasePeerContext 12 | 13 | from trinity.db.eth1.header import BaseAsyncHeaderDB 14 | 15 | 16 | class ChainContext(BasePeerContext): 17 | def __init__(self, 18 | headerdb: BaseAsyncHeaderDB, 19 | network_id: int, 20 | vm_configuration: Tuple[Tuple[BlockNumber, Type[VirtualMachineAPI]], ...], 21 | client_version_string: str, 22 | listen_port: int, 23 | p2p_version: int, 24 | ) -> None: 25 | super().__init__(client_version_string, listen_port, p2p_version) 26 | self.headerdb = headerdb 27 | self.network_id = network_id 28 | self.vm_configuration = vm_configuration 29 | -------------------------------------------------------------------------------- /tests/p2p/test_peer_pair_factory.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import pytest 4 | 5 | from p2p.tools.factories import ParagonPeerPairFactory 6 | from p2p.p2p_proto import Ping, Pong 7 | 8 | 9 | @pytest.mark.asyncio 10 | async def test_connection_factory_with_ParagonPeer(): 11 | async with ParagonPeerPairFactory() as (alice, bob): 12 | got_ping = asyncio.Event() 13 | got_pong = asyncio.Event() 14 | 15 | async def handle_ping(conn, msg): 16 | got_ping.set() 17 | bob._p2p_api.send_pong() 18 | 19 | async def handle_pong(conn, msg): 20 | got_pong.set() 21 | 22 | alice.connection.add_command_handler(Pong, handle_pong) 23 | bob.connection.add_command_handler(Ping, handle_ping) 24 | 25 | alice._p2p_api.send_ping() 26 | 27 | await asyncio.wait_for(got_ping.wait(), timeout=1) 28 | await asyncio.wait_for(got_pong.wait(), timeout=1) 29 | -------------------------------------------------------------------------------- /p2p/tools/connection.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from typing import Any 3 | 4 | from p2p.abc import ConnectionAPI 5 | from p2p.p2p_proto import Ping, Pong 6 | 7 | 8 | async def do_ping_pong_test(alice_connection: ConnectionAPI, bob_connection: ConnectionAPI) -> None: 9 | got_ping = asyncio.Event() 10 | got_pong = asyncio.Event() 11 | 12 | async def _handle_ping(connection: ConnectionAPI, msg: Any) -> None: 13 | got_ping.set() 14 | bob_connection.get_base_protocol().send(Pong(None)) 15 | 16 | async def _handle_pong(connection: ConnectionAPI, msg: Any) -> None: 17 | got_pong.set() 18 | 19 | alice_connection.add_command_handler(Pong, _handle_pong) 20 | bob_connection.add_command_handler(Ping, _handle_ping) 21 | 22 | alice_connection.get_base_protocol().send(Ping(None)) 23 | 24 | await asyncio.wait_for(got_ping.wait(), timeout=1) 25 | await asyncio.wait_for(got_pong.wait(), timeout=1) 26 | -------------------------------------------------------------------------------- /trinity/components/builtin/network_db/connection/events.py: -------------------------------------------------------------------------------- 1 | from dataclasses import ( 2 | dataclass, 3 | ) 4 | from typing import Tuple, Type 5 | 6 | from eth_typing import NodeID 7 | from lahja import ( 8 | BaseEvent, 9 | BaseRequestResponseEvent, 10 | ) 11 | 12 | from p2p.abc import NodeAPI 13 | 14 | 15 | class BaseConnectionTrackerEvent(BaseEvent): 16 | pass 17 | 18 | 19 | @dataclass 20 | class BlacklistEvent(BaseConnectionTrackerEvent): 21 | 22 | remote: NodeAPI 23 | timeout_seconds: int 24 | reason: str 25 | 26 | 27 | @dataclass 28 | class GetBlacklistedPeersResponse(BaseConnectionTrackerEvent): 29 | 30 | peers: Tuple[NodeID, ...] 31 | 32 | 33 | @dataclass 34 | class GetBlacklistedPeersRequest(BaseRequestResponseEvent[GetBlacklistedPeersResponse]): 35 | 36 | @staticmethod 37 | def expected_response_type() -> Type[GetBlacklistedPeersResponse]: 38 | return GetBlacklistedPeersResponse 39 | -------------------------------------------------------------------------------- /trinity/chains/header.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod 2 | from typing import Tuple 3 | 4 | from eth.abc import ( 5 | BlockHeaderAPI, 6 | HeaderChainAPI, 7 | ) 8 | from eth.chains.header import ( 9 | HeaderChain, 10 | ) 11 | 12 | 13 | class BaseAsyncHeaderChain(HeaderChainAPI): 14 | @abstractmethod 15 | async def coro_get_canonical_head(self) -> BlockHeaderAPI: 16 | ... 17 | 18 | @abstractmethod 19 | async def coro_import_header(self, header: BlockHeaderAPI) -> Tuple[BlockHeaderAPI, ...]: 20 | ... 21 | 22 | 23 | class AsyncHeaderChain(HeaderChain, BaseAsyncHeaderChain): 24 | 25 | async def coro_get_canonical_head(self) -> BlockHeaderAPI: 26 | raise NotImplementedError("Chain classes must implement this method") 27 | 28 | async def coro_import_header(self, header: BlockHeaderAPI) -> Tuple[BlockHeaderAPI, ...]: 29 | raise NotImplementedError("Chain classes must implement this method") 30 | -------------------------------------------------------------------------------- /p2p/tools/factories/__init__.py: -------------------------------------------------------------------------------- 1 | try: 2 | import factory # noqa: F401 3 | except ImportError: 4 | raise ImportError("The `p2p.tools.factories` module requires the `factory-boy` library") 5 | from .connection import ConnectionPairFactory # noqa: F401 6 | from .kademlia import AddressFactory, IPAddressFactory, NodeFactory # noqa: F401 7 | from .keys import ( # noqa: F401 8 | PrivateKeyFactory, 9 | PublicKeyFactory, 10 | ) 11 | from .multiplexer import MultiplexerPairFactory # noqa: F401 12 | from .p2p_proto import DevP2PHandshakeParamsFactory, HelloPayloadFactory # noqa: F401 13 | from .peer import PeerPairFactory, ParagonPeerPairFactory # noqa: F401 14 | from .protocol import CommandFactory, ProtocolFactory # noqa: F401 15 | from .session import SessionFactory # noqa: F401 16 | from .socket import get_open_port # noqa: F401 17 | from .transport import ( # noqa: F401 18 | MemoryTransportPairFactory, 19 | TransportPairFactory, 20 | ) 21 | -------------------------------------------------------------------------------- /trinity/_utils/profiling.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import cProfile 3 | import functools 4 | from typing import ( 5 | Any, 6 | Callable, 7 | Iterator, 8 | ) 9 | 10 | 11 | @contextlib.contextmanager 12 | def profiler(filename: str) -> Iterator[None]: 13 | pr = cProfile.Profile() 14 | pr.enable() 15 | try: 16 | yield 17 | finally: 18 | pr.disable() 19 | pr.dump_stats(filename) 20 | 21 | 22 | def setup_cprofiler(filename: str) -> Callable[..., Any]: 23 | def outer(fn: Callable[..., Any]) -> Callable[..., Any]: 24 | @functools.wraps(fn) 25 | def inner(*args: Any, **kwargs: Any) -> None: 26 | should_profile = kwargs.pop('profile', False) 27 | if should_profile: 28 | with profiler(filename): 29 | return fn(*args, **kwargs) 30 | else: 31 | return fn(*args, **kwargs) 32 | return inner 33 | return outer 34 | -------------------------------------------------------------------------------- /trinity/rlp/block_body.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | Iterable, 3 | Union, 4 | ) 5 | 6 | from eth.abc import ( 7 | BlockHeaderAPI, 8 | SignedTransactionAPI, 9 | ) 10 | from eth.rlp.headers import BlockHeader 11 | import rlp 12 | from rlp import sedes 13 | 14 | from .sedes import ( 15 | UninterpretedTransaction, 16 | UninterpretedTransactionRLP, 17 | ) 18 | 19 | 20 | class BlockBody(rlp.Serializable): 21 | fields = [ 22 | ('transactions', sedes.CountableList(UninterpretedTransactionRLP)), 23 | ('uncles', sedes.CountableList(BlockHeader)) 24 | ] 25 | 26 | def __init__( 27 | self, 28 | transactions: Iterable[Union[UninterpretedTransaction, SignedTransactionAPI]], 29 | uncles: Iterable[BlockHeaderAPI]) -> None: 30 | if not isinstance(transactions, (list, bytes)): 31 | transactions = rlp.decode(rlp.encode(transactions)) 32 | super().__init__(transactions, uncles) 33 | -------------------------------------------------------------------------------- /tests/core/p2p-proto/test_peer_discovery.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from eth_enr.tools.factories import ENRFactory 4 | 5 | from eth_utils import to_bytes 6 | 7 | from eth.chains.ropsten import ROPSTEN_GENESIS_HEADER, ROPSTEN_VM_CONFIGURATION 8 | from eth.db.atomic import AtomicDB 9 | from eth.db.chain import ChainDB 10 | 11 | from trinity.components.builtin.peer_discovery.component import generate_eth_cap_enr_field 12 | from trinity.db.eth1.header import AsyncHeaderDB 13 | from trinity.protocol.eth.forkid import extract_forkid, ForkID 14 | 15 | 16 | @pytest.mark.asyncio 17 | async def test_generate_eth_cap_enr_field(): 18 | base_db = AtomicDB() 19 | ChainDB(base_db).persist_header(ROPSTEN_GENESIS_HEADER) 20 | 21 | enr_field = await generate_eth_cap_enr_field(ROPSTEN_VM_CONFIGURATION, AsyncHeaderDB(base_db)) 22 | 23 | enr = ENRFactory(custom_kv_pairs={enr_field[0]: enr_field[1]}) 24 | assert extract_forkid(enr) == ForkID(hash=to_bytes(hexstr='0x30c7ddbc'), next=10) 25 | -------------------------------------------------------------------------------- /tests/p2p/test_memory_transport.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import pytest 3 | 4 | from rlp import sedes 5 | 6 | from p2p.tools.factories import ( 7 | MemoryTransportPairFactory, 8 | ) 9 | from p2p.commands import BaseCommand, RLPCodec 10 | 11 | 12 | class CommandForTest(BaseCommand): 13 | protocol_command_id = 0 14 | serialization_codec = RLPCodec(sedes=sedes.binary) 15 | 16 | 17 | @pytest.mark.parametrize('snappy_support', (True, False)) 18 | @pytest.mark.asyncio 19 | async def test_memory_transport_tool(snappy_support): 20 | alice_transport, bob_transport = MemoryTransportPairFactory() 21 | 22 | command = CommandForTest(b'test-payload') 23 | message = command.encode(0, snappy_support) 24 | alice_transport.send(message) 25 | 26 | result = await asyncio.wait_for(bob_transport.recv(), timeout=1) 27 | assert result == message 28 | result_command = CommandForTest.decode(result, snappy_support) 29 | 30 | assert result_command.payload == b'test-payload' 31 | -------------------------------------------------------------------------------- /trinity/tools/factories/__init__.py: -------------------------------------------------------------------------------- 1 | from .block_body import BlockBodyFactory # noqa: F401 2 | from .block_hash import BlockHashFactory, Hash32Factory # noqa: F401 3 | from .chain_context import ChainContextFactory # noqa: F401 4 | from .db import ( # noqa: F401 5 | MemoryDBFactory, 6 | AtomicDBFactory, 7 | HeaderDBFactory, 8 | AsyncHeaderDBFactory, 9 | ) 10 | from .les.proto import ( # noqa: F401 11 | LESV1HandshakerFactory, 12 | LESV2HandshakerFactory, 13 | LESV1PeerPairFactory, 14 | LESV2PeerPairFactory, 15 | ) 16 | from .eth.proto import ( # noqa: F401 17 | ETHHandshakerFactory, 18 | ETHV65PeerPairFactory, 19 | ETHV63PeerPairFactory, 20 | ETHV64PeerPairFactory, 21 | LatestETHPeerPairFactory, 22 | ALL_PEER_PAIR_FACTORIES, 23 | ) 24 | from .headers import BlockHeaderFactory # noqa: F401 25 | from .receipts import UninterpretedReceiptFactory # noqa: F401 26 | from .transactions import ( # noqa: F401 27 | UninterpretedTransactionFactory, 28 | ) 29 | -------------------------------------------------------------------------------- /tests/p2p/test_get_cmd_id_offsets.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from p2p.constants import P2P_PROTOCOL_COMMAND_LENGTH 4 | from p2p.protocol import BaseProtocol, get_cmd_offsets 5 | 6 | 7 | class With2(BaseProtocol): 8 | command_length = 2 9 | 10 | 11 | class With5(BaseProtocol): 12 | command_length = 5 13 | 14 | 15 | class With7(BaseProtocol): 16 | command_length = 7 17 | 18 | 19 | BASE_OFFSET = P2P_PROTOCOL_COMMAND_LENGTH 20 | 21 | 22 | @pytest.mark.parametrize( 23 | 'protocols,offsets', 24 | ( 25 | ((), ()), 26 | ((With2,), (BASE_OFFSET,)), 27 | ((With5,), (BASE_OFFSET,)), 28 | ((With7,), (BASE_OFFSET,)), 29 | ((With2, With5), (BASE_OFFSET, BASE_OFFSET + 2)), 30 | ((With5, With2), (BASE_OFFSET, BASE_OFFSET + 5)), 31 | ((With7, With2, With5), (BASE_OFFSET, BASE_OFFSET + 7, BASE_OFFSET + 7 + 2)), 32 | ), 33 | ) 34 | def test_get_cmd_offsets(protocols, offsets): 35 | actual = get_cmd_offsets(protocols) 36 | assert actual == offsets 37 | -------------------------------------------------------------------------------- /p2p/tools/paragon/api.py: -------------------------------------------------------------------------------- 1 | from p2p.logic import Application 2 | from p2p.qualifiers import HasProtocol 3 | 4 | from cached_property import cached_property 5 | 6 | from .commands import ( 7 | BroadcastData, 8 | GetSum, 9 | Sum, 10 | ) 11 | from .payloads import ( 12 | BroadcastDataPayload, 13 | GetSumPayload, 14 | SumPayload, 15 | ) 16 | from .proto import ParagonProtocol 17 | 18 | 19 | class ParagonAPI(Application): 20 | name = 'paragon' 21 | qualifier = HasProtocol(ParagonProtocol) 22 | 23 | @cached_property 24 | def protocol(self) -> ParagonProtocol: 25 | return self.connection.get_protocol_by_type(ParagonProtocol) 26 | 27 | def send_broadcast_data(self, data: bytes) -> None: 28 | self.protocol.send(BroadcastData(BroadcastDataPayload(data))) 29 | 30 | def send_get_sum(self, a: int, b: int) -> None: 31 | self.protocol.send(GetSum(GetSumPayload(a, b))) 32 | 33 | def send_sum(self, c: int) -> None: 34 | self.protocol.send(Sum(SumPayload(c))) 35 | -------------------------------------------------------------------------------- /trinity/__init__.py: -------------------------------------------------------------------------------- 1 | import pkg_resources 2 | import sys 3 | 4 | # TODO: update this to use the `trinity` version once extracted from py-evm 5 | __version__: str 6 | try: 7 | __version__ = pkg_resources.get_distribution("trinity").version 8 | except pkg_resources.DistributionNotFound: 9 | __version__ = f"eth-{pkg_resources.get_distribution('py-evm').version}" 10 | 11 | 12 | # Setup the `DEBUG2` logging level 13 | from eth_utils import setup_DEBUG2_logging # noqa: E402 14 | setup_DEBUG2_logging() 15 | 16 | 17 | def is_uvloop_supported() -> bool: 18 | return sys.platform in {'darwin', 'linux'} or sys.platform.startswith('freebsd') 19 | 20 | 21 | if is_uvloop_supported(): 22 | # Set `uvloop` as the default event loop 23 | import asyncio 24 | 25 | from eth._warnings import catch_and_ignore_import_warning 26 | with catch_and_ignore_import_warning(): 27 | import uvloop 28 | 29 | asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) 30 | 31 | from .main import ( # noqa: F401 32 | main, 33 | ) 34 | -------------------------------------------------------------------------------- /scripts/upnp.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import uuid 3 | 4 | import trio 5 | 6 | from lahja import ConnectionConfig, TrioEndpoint 7 | 8 | from trinity.components.builtin.upnp.events import UPnPMapping 9 | from trinity.constants import UPNP_EVENTBUS_ENDPOINT 10 | 11 | 12 | async def main() -> None: 13 | parser = argparse.ArgumentParser() 14 | parser.add_argument('--ipc', type=str, help="The path to UPnPService's IPC file") 15 | args = parser.parse_args() 16 | 17 | connection_config = ConnectionConfig(UPNP_EVENTBUS_ENDPOINT, args.ipc) 18 | async with TrioEndpoint(f"upnp-watcher-{uuid.uuid4()}").run() as client: 19 | with trio.fail_after(1): 20 | await client.connect_to_endpoints(connection_config) 21 | 22 | async for event in client.stream(UPnPMapping): 23 | external_ip = event.ip 24 | print("Got new UPnP mapping:", external_ip) 25 | 26 | 27 | if __name__ == "__main__": 28 | # Connect to a running UPnPService and prints any UPnPMapping it broadcasts. 29 | trio.run(main) 30 | -------------------------------------------------------------------------------- /trinity/_utils/trie.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | Iterable, 3 | ) 4 | 5 | from eth.db.trie import _make_trie_root_and_nodes, TrieRootAndData 6 | import rlp 7 | 8 | from trinity.rlp.sedes import DecodedZeroOrOneLayerRLP 9 | 10 | 11 | def make_trie_root_and_nodes(uninterpreted: Iterable[DecodedZeroOrOneLayerRLP]) -> TrieRootAndData: 12 | """ 13 | Make a trie root, and get the trie nodes, for loose data. 14 | 15 | This is currently used for transactions or receipts. It could be used for 16 | any object that is either bytes or a list of bytes, which should be 17 | rlp-encoded before insertion to a trie. 18 | """ 19 | def encode_serialized(serialized_transaction: DecodedZeroOrOneLayerRLP) -> bytes: 20 | if isinstance(serialized_transaction, bytes): 21 | return serialized_transaction 22 | else: 23 | return rlp.encode(serialized_transaction) 24 | 25 | encoded_items = tuple(encode_serialized(item) for item in uninterpreted) 26 | return _make_trie_root_and_nodes(encoded_items) 27 | -------------------------------------------------------------------------------- /trinity/rpc/modules/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | Iterable, 3 | ) 4 | 5 | from lahja import EndpointAPI 6 | 7 | from eth_utils import ( 8 | to_tuple, 9 | ) 10 | 11 | from trinity.chains.base import AsyncChainAPI 12 | from trinity.config import TrinityConfig 13 | 14 | from .main import ( # noqa: F401 15 | BaseRPCModule, 16 | ChainReplacementEvent, 17 | Eth1ChainRPCModule, 18 | ChainBasedRPCModule, 19 | ) 20 | 21 | from .admin import Admin 22 | from .eth import Eth # noqa: F401 23 | from .evm import EVM # noqa: F401 24 | from .net import Net # noqa: F401 25 | from .web3 import Web3 # noqa: F401 26 | 27 | 28 | @to_tuple 29 | def initialize_eth1_modules(chain: AsyncChainAPI, 30 | event_bus: EndpointAPI, 31 | trinity_config: TrinityConfig) -> Iterable[BaseRPCModule]: 32 | yield Eth(chain, event_bus, trinity_config) 33 | yield EVM(chain, event_bus) 34 | yield Net(event_bus) 35 | yield Web3() 36 | yield Admin(chain, event_bus, trinity_config) 37 | -------------------------------------------------------------------------------- /trinity/protocol/eth/payloads.py: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple, Tuple 2 | 3 | from eth_typing import BlockNumber, Hash32 4 | 5 | from eth.abc import BlockHeaderAPI 6 | 7 | from trinity.protocol.eth.forkid import ForkID 8 | from trinity.rlp.sedes import UninterpretedTransaction 9 | 10 | 11 | class StatusV63Payload(NamedTuple): 12 | version: int 13 | network_id: int 14 | total_difficulty: int 15 | head_hash: Hash32 16 | genesis_hash: Hash32 17 | 18 | 19 | class StatusPayload(NamedTuple): 20 | version: int 21 | network_id: int 22 | total_difficulty: int 23 | head_hash: Hash32 24 | genesis_hash: Hash32 25 | fork_id: ForkID 26 | 27 | 28 | class NewBlockHash(NamedTuple): 29 | hash: Hash32 30 | number: BlockNumber 31 | 32 | 33 | class BlockFields(NamedTuple): 34 | header: BlockHeaderAPI 35 | transactions: Tuple[UninterpretedTransaction, ...] 36 | uncles: Tuple[BlockHeaderAPI, ...] 37 | 38 | 39 | class NewBlockPayload(NamedTuple): 40 | block: BlockFields 41 | total_difficulty: int 42 | -------------------------------------------------------------------------------- /trinity/tools/factories/events.py: -------------------------------------------------------------------------------- 1 | from typing import Sequence, Any 2 | 3 | from p2p.abc import SessionAPI 4 | 5 | try: 6 | import factory 7 | except ImportError: 8 | raise ImportError( 9 | "The p2p.tools.factories module requires the `factory_boy` and `faker` libraries." 10 | ) 11 | 12 | 13 | from trinity.protocol.common.events import GetConnectedPeersResponse, PeerInfo 14 | 15 | 16 | class GetConnectedPeersResponseFactory(factory.Factory): 17 | class Meta: 18 | model = GetConnectedPeersResponse 19 | 20 | @classmethod 21 | def from_sessions(cls, 22 | sessions: Sequence[SessionAPI], 23 | *args: Any, 24 | **kwargs: Any) -> GetConnectedPeersResponse: 25 | return GetConnectedPeersResponse(tuple( 26 | PeerInfo( 27 | session=session, 28 | capabilities=(), 29 | client_version_string='unknown', 30 | inbound=False 31 | ) for session in sessions 32 | )) 33 | -------------------------------------------------------------------------------- /tests/core/_utils/test_priority.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import pytest 4 | 5 | from trinity._utils.priority import SilenceObserver 6 | 7 | 8 | @pytest.mark.asyncio 9 | async def test_silence_observer_waits_until_silence(): 10 | observer = SilenceObserver(minimum_silence_duration=0.03) 11 | 12 | async def noise_maker(): 13 | async with observer.make_noise(): 14 | await asyncio.sleep(0.02) 15 | 16 | asyncio.ensure_future(noise_maker()) 17 | asyncio.ensure_future(noise_maker()) 18 | asyncio.ensure_future(noise_maker()) 19 | 20 | # Allow the noise makers to start 21 | await asyncio.sleep(0) 22 | 23 | # Noise makers might have stopped, but minimum_silence_duration hasn't ended 24 | with pytest.raises(asyncio.TimeoutError): 25 | await asyncio.wait_for(observer.until_silence(), timeout=0.03) 26 | 27 | # Already waited 0.03, so only need another 0.02, but leave a little extra for CI 28 | await asyncio.wait_for( 29 | observer.until_silence(), 30 | timeout=0.05, 31 | ) 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | var 14 | sdist 15 | develop-eggs 16 | .installed.cfg 17 | lib 18 | lib64 19 | pip-wheel-metadata 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | 37 | # Complexity 38 | output/*.html 39 | output/*/index.html 40 | 41 | # Sphinx 42 | docs/_build 43 | docs/modules.rst 44 | 45 | # pytest 46 | .cache/ 47 | .pytest_cache/ 48 | 49 | # fixtures 50 | ./fixtures/** 51 | 52 | # profiling 53 | prof/** 54 | 55 | # hypothesis 56 | .hypothesis/ 57 | 58 | # mypy 59 | .mypy_cache/ 60 | *.swp 61 | docs/evm.*rst 62 | venv* 63 | .eggs/ 64 | 65 | # vscode 66 | .vscode/** 67 | 68 | # monkeytype 69 | monkeytype.sqlite3 70 | 71 | # pyenv 72 | .python-version 73 | 74 | # idea 75 | .idea/** 76 | 77 | !tests/integration/fixtures/*.ldb.zip 78 | -------------------------------------------------------------------------------- /tests/p2p/test_peer_pool_event_server.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from async_service import background_asyncio_service 4 | 5 | from trinity.protocol.common.events import PeerCountRequest 6 | from trinity.protocol.common.peer_pool_event_bus import ( 7 | DefaultPeerPoolEventServer, 8 | ) 9 | 10 | from p2p.tools.paragon import ( 11 | ParagonMockPeerPoolWithConnectedPeers, 12 | ) 13 | from p2p.tools.factories import ( 14 | ParagonPeerPairFactory, 15 | ) 16 | 17 | 18 | @pytest.mark.asyncio 19 | async def test_event_bus_requests_against_peer_pool(request, event_loop, event_bus): 20 | async with ParagonPeerPairFactory() as (alice, bob): 21 | peer_pool = ParagonMockPeerPoolWithConnectedPeers([alice, bob]) 22 | event_server = DefaultPeerPoolEventServer(event_bus, peer_pool) 23 | async with background_asyncio_service(event_server): 24 | await event_bus.wait_until_any_endpoint_subscribed_to(PeerCountRequest) 25 | 26 | res = await event_bus.request(PeerCountRequest()) 27 | 28 | assert res.peer_count == 2 29 | -------------------------------------------------------------------------------- /docs/api/protocol/common/api.events.rst: -------------------------------------------------------------------------------- 1 | Events 2 | ====== 3 | 4 | Common Protocol Events 5 | ---------------------- 6 | 7 | Common events used for all peer protocols. These events derive from :class:`~lahja.BaseEvent` and 8 | can thus be consumed through the event bus. 9 | 10 | .. autoclass:: trinity.protocol.common.events.ConnectToNodeCommand 11 | :members: 12 | 13 | .. autoclass:: trinity.protocol.common.events.PeerCountRequest 14 | :members: 15 | 16 | .. autoclass:: trinity.protocol.common.events.PeerCountResponse 17 | :members: 18 | 19 | .. autoclass:: trinity.protocol.common.events.DisconnectPeerEvent 20 | :members: 21 | 22 | .. autoclass:: trinity.protocol.common.events.PeerJoinedEvent 23 | :members: 24 | 25 | .. autoclass:: trinity.protocol.common.events.PeerLeftEvent 26 | :members: 27 | 28 | .. autoclass:: trinity.protocol.common.events.GetConnectedPeersRequest 29 | :members: 30 | 31 | .. autoclass:: trinity.protocol.common.events.GetConnectedPeersResponse 32 | :members: 33 | 34 | .. autoclass:: trinity.protocol.common.events.PeerPoolMessageEvent 35 | :members: 36 | -------------------------------------------------------------------------------- /trinity/tools/chain.py: -------------------------------------------------------------------------------- 1 | from eth import MainnetChain, RopstenChain 2 | from eth.chains.base import ( 3 | MiningChain, 4 | ) 5 | from eth.constants import GENESIS_BLOCK_NUMBER 6 | from eth.vm.forks.byzantium import ByzantiumVM 7 | from eth.vm.forks.muir_glacier import MuirGlacierVM 8 | 9 | from trinity.chains.coro import AsyncChainMixin 10 | from trinity.chains.full import FullChain 11 | 12 | 13 | class AsyncRopstenChain(AsyncChainMixin, RopstenChain): 14 | pass 15 | 16 | 17 | class AsyncMainnetChain(AsyncChainMixin, MainnetChain): 18 | pass 19 | 20 | 21 | class AsyncMiningChain(AsyncChainMixin, MiningChain): 22 | pass 23 | 24 | 25 | class LatestTestChain(FullChain): 26 | """ 27 | A test chain that uses the most recent mainnet VM from block 0. 28 | That means the VM will explicitly change when a new network upgrade is locked in. 29 | """ 30 | vm_configuration = ((GENESIS_BLOCK_NUMBER, MuirGlacierVM),) 31 | network_id = 999 32 | 33 | 34 | class ByzantiumTestChain(FullChain): 35 | vm_configuration = ((GENESIS_BLOCK_NUMBER, ByzantiumVM),) 36 | network_id = 999 37 | -------------------------------------------------------------------------------- /tests/core/utils/test_rolling_bloom_filter.py: -------------------------------------------------------------------------------- 1 | from trinity._utils.bloom import RollingBloom 2 | 3 | 4 | def test_rolling_bloom_maintains_history(): 5 | # only one historical filter 6 | bloom = RollingBloom(generation_size=10, max_generations=2) 7 | 8 | bloom_values = tuple(bytes((i,)) for i in range(20)) 9 | 10 | # fill up the main filter and the history 11 | for value in bloom_values: 12 | bloom.add(value) 13 | assert value in bloom 14 | 15 | # since the filter discards old history, we loop back over the values 16 | for value in bloom_values: 17 | assert value in bloom 18 | 19 | # this should eject all of the 0-9 values 20 | assert b'\xff' not in bloom_values 21 | bloom.add(b'\xff') 22 | 23 | # this must be done probabalistically since bloom filters have false 24 | # positives. At least one of the 0-9 values should be gone. 25 | assert any( 26 | value not in bloom 27 | for value in bloom_values[:10] 28 | ) 29 | 30 | for value in bloom_values[10:]: 31 | assert value in bloom 32 | assert b'\xff' in bloom 33 | -------------------------------------------------------------------------------- /trinity/components/builtin/network_db/cli.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import enum 3 | from typing import Any 4 | 5 | 6 | @enum.unique 7 | class TrackingBackend(enum.Enum): 8 | SQLITE3 = 'sqlite3' 9 | MEMORY = 'memory' 10 | DO_NOT_TRACK = 'do-not-track' 11 | 12 | 13 | class NormalizeTrackingBackend(argparse.Action): 14 | """ 15 | Normalized the --network-tracking-backend CLI arg into the proper Enum type. 16 | """ 17 | def __call__(self, 18 | parser: argparse.ArgumentParser, 19 | namespace: argparse.Namespace, 20 | value: Any, 21 | option_string: str = None) -> None: 22 | try: 23 | tracking_backend = TrackingBackend(value) 24 | except TypeError: 25 | raise argparse.ArgumentError( 26 | self, 27 | ( 28 | "Unknown option for --network-tracking-backend. Must be one of " 29 | "`sqlite3/memory/disabled`. Got '{value}'" 30 | ), 31 | ) 32 | 33 | setattr(namespace, self.dest, tracking_backend) 34 | -------------------------------------------------------------------------------- /newsfragments/README.md: -------------------------------------------------------------------------------- 1 | This directory collects "newsfragments": short files that each contain 2 | a snippet of ReST-formatted text that will be added to the next 3 | release notes. This should be a description of aspects of the change 4 | (if any) that are relevant to users. (This contrasts with the 5 | commit message and PR description, which are a description of the change as 6 | relevant to people working on the code itself.) 7 | 8 | Each file should be named like `..rst`, where 9 | `` is an issue numbers, and `` is one of: 10 | 11 | * `feature` 12 | * `bugfix` 13 | * `performance` 14 | * `doc` 15 | * `internal` 16 | * `removal` 17 | * `misc` 18 | 19 | So for example: `123.feature.rst`, `456.bugfix.rst` 20 | 21 | If the PR fixes an issue, use that number here. If there is no issue, 22 | then open up the PR first and use the PR number for the newsfragment. 23 | 24 | Note that the `towncrier` tool will automatically 25 | reflow your text, so don't try to do any fancy formatting. Run 26 | `towncrier --draft` to get a preview of what the release notes entry 27 | will look like in the final release notes. 28 | -------------------------------------------------------------------------------- /p2p/events.py: -------------------------------------------------------------------------------- 1 | from dataclasses import ( 2 | dataclass, 3 | ) 4 | from typing import ( 5 | Callable, 6 | Tuple, 7 | Type, 8 | ) 9 | 10 | from lahja import ( 11 | BaseEvent, 12 | BaseRequestResponseEvent, 13 | ) 14 | 15 | from p2p.abc import NodeAPI 16 | 17 | 18 | class BaseDiscoveryServiceResponse(BaseEvent): 19 | pass 20 | 21 | 22 | @dataclass 23 | class PeerCandidatesResponse(BaseDiscoveryServiceResponse): 24 | 25 | candidates: Tuple[NodeAPI, ...] 26 | 27 | 28 | class PeerCandidatesRequest(BaseRequestResponseEvent[PeerCandidatesResponse]): 29 | 30 | def __init__(self, max_candidates: int, should_skip_fn: Callable[[NodeAPI], bool]) -> None: 31 | self.max_candidates = max_candidates 32 | self.should_skip_fn = should_skip_fn 33 | 34 | @staticmethod 35 | def expected_response_type() -> Type[PeerCandidatesResponse]: 36 | return PeerCandidatesResponse 37 | 38 | 39 | class RandomBootnodeRequest(BaseRequestResponseEvent[PeerCandidatesResponse]): 40 | 41 | @staticmethod 42 | def expected_response_type() -> Type[PeerCandidatesResponse]: 43 | return PeerCandidatesResponse 44 | -------------------------------------------------------------------------------- /p2p/stats/stddev.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import math 3 | from typing import Union, Deque 4 | 5 | 6 | class StandardDeviation: 7 | """ 8 | https://stackoverflow.com/questions/5543651/computing-standard-deviation-in-a-stream 9 | 10 | Tracks standard deviation on a stream of data. 11 | """ 12 | def __init__(self, window_size: int) -> None: 13 | self.window: Deque[Union[int, float]] = collections.deque() 14 | self.window_size = window_size 15 | 16 | def update(self, value: Union[int, float]) -> None: 17 | self.window.append(value) 18 | 19 | while len(self.window) > self.window_size: 20 | self.window.popleft() 21 | 22 | @property 23 | def value(self) -> float: 24 | num_values = len(self.window) 25 | 26 | if num_values < 2: 27 | raise ValueError("No data") 28 | 29 | sum_of_values = sum(self.window) 30 | sum_of_squared_values = sum(item * item for item in self.window) 31 | 32 | return math.sqrt( 33 | (num_values * sum_of_squared_values - sum_of_values ** 2) / 34 | (num_values * (num_values - 1)) 35 | ) 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2017-2019 Ethereum Foundation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.towncrier] 2 | # Read https://github.com/ethereum/trinity/newsfragments/README.md for instructions 3 | package = "trinity" 4 | filename = "docs/release_notes.rst" 5 | directory = "newsfragments" 6 | underlines = ["-", "~", "^"] 7 | issue_format = "`#{issue} `__" 8 | 9 | [[tool.towncrier.type]] 10 | directory = "feature" 11 | name = "Features" 12 | showcontent = true 13 | 14 | [[tool.towncrier.type]] 15 | directory = "bugfix" 16 | name = "Bugfixes" 17 | showcontent = true 18 | 19 | [[tool.towncrier.type]] 20 | directory = "performance" 21 | name = "Performance improvements" 22 | showcontent = true 23 | 24 | [[tool.towncrier.type]] 25 | directory = "doc" 26 | name = "Improved Documentation" 27 | showcontent = true 28 | 29 | [[tool.towncrier.type]] 30 | directory = "removal" 31 | name = "Deprecations and Removals" 32 | showcontent = true 33 | 34 | [[tool.towncrier.type]] 35 | directory = "internal" 36 | name = "Internal Changes - for Trinity Contributors" 37 | showcontent = true 38 | 39 | [[tool.towncrier.type]] 40 | directory = "misc" 41 | name = "Miscellaneous changes" 42 | showcontent = false 43 | -------------------------------------------------------------------------------- /trinity/protocol/common/typing.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | Dict, 3 | Tuple, 4 | TypeVar, 5 | ) 6 | 7 | from eth.abc import BlockAPI, ReceiptAPI 8 | from eth_typing import ( 9 | Hash32, 10 | ) 11 | 12 | from p2p.peer import BasePeer 13 | 14 | from trinity.rlp.sedes import ( 15 | UninterpretedReceipt, 16 | ) 17 | 18 | TPeer = TypeVar('TPeer', bound=BasePeer) 19 | 20 | # ( 21 | # (node_hash, node), 22 | # ... 23 | # ) 24 | NodeDataBundles = Tuple[Tuple[Hash32, bytes], ...] 25 | 26 | # (receipts_in_block_a, receipts_in_block_b, ...) 27 | ReceiptsByBlock = Tuple[Tuple[ReceiptAPI, ...], ...] 28 | 29 | # ( 30 | # (receipts_in_block_a, (receipts_root_hash, receipts_trie_nodes), 31 | # (receipts_in_block_b, (receipts_root_hash, receipts_trie_nodes), 32 | # ... 33 | # ( 34 | ReceiptsBundles = Tuple[ 35 | Tuple[Tuple[UninterpretedReceipt, ...], Tuple[Hash32, Dict[Hash32, bytes]]], 36 | ... 37 | ] 38 | 39 | # (BlockBody, (txn_root, txn_trie_data), uncles_hash) 40 | 41 | BlockBodyBundle = Tuple[ 42 | BlockAPI, 43 | Tuple[Hash32, Dict[Hash32, bytes]], 44 | Hash32, 45 | ] 46 | 47 | BlockBodyBundles = Tuple[BlockBodyBundle, ...] 48 | -------------------------------------------------------------------------------- /trinity/protocol/wit/commands.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | NamedTuple, 3 | Tuple, 4 | ) 5 | 6 | from eth_typing import Hash32 7 | 8 | from rlp import sedes 9 | 10 | from p2p.commands import BaseCommand, RLPCodec 11 | 12 | from trinity.rlp.sedes import hash_sedes 13 | 14 | 15 | class GetBlockWitnessHashesPayload(NamedTuple): 16 | request_id: int 17 | block_hash: Hash32 18 | 19 | 20 | class GetBlockWitnessHashes(BaseCommand[GetBlockWitnessHashesPayload]): 21 | protocol_command_id = 1 22 | serialization_codec = RLPCodec( 23 | sedes=sedes.List((sedes.big_endian_int, hash_sedes)), 24 | process_inbound_payload_fn=lambda args: GetBlockWitnessHashesPayload(*args), 25 | ) 26 | 27 | 28 | class BlockWitnessHashesPayload(NamedTuple): 29 | request_id: int 30 | node_hashes: Tuple[Hash32, ...] 31 | 32 | 33 | class BlockWitnessHashes(BaseCommand[BlockWitnessHashesPayload]): 34 | protocol_command_id = 2 35 | serialization_codec = RLPCodec( 36 | sedes=sedes.List((sedes.big_endian_int, sedes.CountableList(hash_sedes))), 37 | process_inbound_payload_fn=lambda args: BlockWitnessHashesPayload(*args), 38 | ) 39 | -------------------------------------------------------------------------------- /tests/p2p/test_utils.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from hypothesis import ( 4 | given, 5 | strategies as st, 6 | ) 7 | 8 | from p2p._utils import trim_middle 9 | 10 | 11 | @pytest.mark.parametrize( 12 | "in_str, max_length, expected", 13 | ( 14 | ("notcut", 6, "notcut"), 15 | ("tobecut", 6, "to✂✂✂t"), 16 | ("tobecut", 5, "t✂✂✂t"), 17 | ("0000", 3, "✂✂✂"), 18 | ("really long thing with a bunch of garbage", 20, "really lo✂✂✂ garbage"), 19 | ) 20 | ) 21 | def test_trim_middle(in_str, max_length, expected): 22 | actual = trim_middle(in_str, max_length) 23 | assert actual == expected 24 | 25 | 26 | @given(st.text(), st.integers(min_value=3)) 27 | def test_trim_middle_length(in_str, max_length): 28 | result = trim_middle(in_str, max_length) 29 | 30 | # should never be longer than max length 31 | assert len(result) <= max_length 32 | 33 | if len(in_str) <= max_length: 34 | # should never be modified if the input is within max length 35 | assert in_str == result 36 | else: 37 | # should always have the trim marker if the input was too long 38 | assert "✂✂✂" in result 39 | -------------------------------------------------------------------------------- /trinity/_utils/xdg.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from pathlib import Path 4 | 5 | from trinity.exceptions import ( 6 | AmbigiousFileSystem 7 | ) 8 | 9 | 10 | def get_home() -> Path: 11 | try: 12 | return Path(os.environ['HOME']) 13 | except KeyError: 14 | raise AmbigiousFileSystem('$HOME environment variable not set') 15 | 16 | 17 | def get_xdg_cache_home() -> Path: 18 | try: 19 | return Path(os.environ['XDG_CACHE_HOME']) 20 | except KeyError: 21 | return get_home() / '.cache' 22 | 23 | 24 | def get_xdg_config_home() -> Path: 25 | try: 26 | return Path(os.environ['XDG_CONFIG_HOME']) 27 | except KeyError: 28 | return get_home() / '.config' 29 | 30 | 31 | def get_xdg_data_home() -> Path: 32 | try: 33 | return Path(os.environ['XDG_DATA_HOME']) 34 | except KeyError: 35 | return get_home() / '.local' / 'share' 36 | 37 | 38 | def get_xdg_trinity_root() -> Path: 39 | """ 40 | Returns the base directory under which trinity will store all data. 41 | """ 42 | try: 43 | return Path(os.environ['XDG_TRINITY_ROOT']) 44 | except KeyError: 45 | return get_xdg_data_home() / 'trinity' 46 | -------------------------------------------------------------------------------- /p2p/tools/factories/kademlia.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | import factory 4 | 5 | from eth_utils import int_to_big_endian 6 | 7 | from p2p.abc import AddressAPI, NodeAPI 8 | from p2p.kademlia import Node, Address 9 | 10 | from .keys import PublicKeyFactory 11 | from .socket import get_open_port 12 | 13 | 14 | IPAddressFactory = factory.Faker("ipv4") 15 | 16 | 17 | class AddressFactory(factory.Factory): 18 | class Meta: 19 | model = Address 20 | 21 | ip = IPAddressFactory 22 | udp_port = tcp_port = factory.LazyFunction(get_open_port) 23 | 24 | @classmethod 25 | def localhost(cls, *args: Any, **kwargs: Any) -> AddressAPI: 26 | return cls(*args, ip='127.0.0.1', **kwargs) 27 | 28 | 29 | class NodeFactory(factory.Factory): 30 | class Meta: 31 | model = Node.from_pubkey_and_addr 32 | 33 | pubkey = factory.LazyFunction(PublicKeyFactory) 34 | address = factory.SubFactory(AddressFactory) 35 | 36 | @classmethod 37 | def with_nodeid(cls, nodeid: int, *args: Any, **kwargs: Any) -> NodeAPI: 38 | node = cls(*args, **kwargs) 39 | node._id_int = nodeid 40 | node._id = int_to_big_endian(nodeid) 41 | return node 42 | -------------------------------------------------------------------------------- /trinity/components/builtin/metrics/sync_metrics_registry.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from eth_typing import BlockNumber 4 | 5 | from trinity.components.builtin.metrics.abc import MetricsServiceAPI 6 | 7 | 8 | class SyncMetricsRegistry: 9 | """ 10 | Registry to track weighted moving average of pivot events, and report to InfluxDB. 11 | """ 12 | def __init__(self, metrics_service: MetricsServiceAPI) -> None: 13 | self.metrics_service = metrics_service 14 | self.pivot_meter = metrics_service.registry.meter('trinity.p2p/sync/pivot_rate.meter') 15 | 16 | def record_lag(self, lag: int) -> None: 17 | self.metrics_service.registry.gauge('trinity.sync/chain_head_lag').set_value(lag) 18 | 19 | async def record_pivot(self, block_number: BlockNumber) -> None: 20 | # record pivot and send event annotation to influxdb 21 | self.pivot_meter.mark() 22 | pivot_time = int(time.time()) 23 | post_data = ( 24 | f'events title="beam pivot @ block {block_number}",' 25 | f'text="pivot event",tags="{self.metrics_service.registry.host}" {pivot_time}' 26 | ) 27 | await self.metrics_service.send_annotation(post_data) 28 | -------------------------------------------------------------------------------- /trinity/tools/event_bus.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import contextlib 3 | from typing import AsyncIterator, Type 4 | 5 | from lahja import BaseEvent, EndpointAPI 6 | 7 | 8 | @contextlib.asynccontextmanager 9 | async def mock_request_response(request_type: Type[BaseEvent], 10 | response: BaseEvent, 11 | event_bus: EndpointAPI) -> AsyncIterator[None]: 12 | ready = asyncio.Event() 13 | task = asyncio.ensure_future(_do_mock_response(request_type, response, event_bus, ready)) 14 | await ready.wait() 15 | try: 16 | yield 17 | finally: 18 | if not task.done(): 19 | task.cancel() 20 | 21 | try: 22 | await task 23 | except asyncio.CancelledError: 24 | pass 25 | 26 | 27 | async def _do_mock_response(request_type: Type[BaseEvent], 28 | response: BaseEvent, 29 | event_bus: EndpointAPI, 30 | ready: asyncio.Event) -> None: 31 | ready.set() 32 | async for req in event_bus.stream(request_type): 33 | await event_bus.broadcast(response, req.broadcast_config()) 34 | break 35 | -------------------------------------------------------------------------------- /trinity/_utils/async_dispatch.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import functools 3 | from typing import ( 4 | Any, 5 | Coroutine, 6 | Callable, 7 | TypeVar, 8 | ) 9 | 10 | import trio 11 | 12 | TReturn = TypeVar('TReturn') 13 | 14 | 15 | def async_method(method: Callable[..., TReturn], 16 | ) -> Callable[..., Coroutine[Any, Any, TReturn]]: 17 | @functools.wraps(method) 18 | async def wrapper(cls_or_self: Any, *args: Any, **kwargs: Any) -> TReturn: 19 | cls_method = getattr(cls_or_self, method.__name__) 20 | loop = asyncio.get_event_loop() 21 | 22 | return await loop.run_in_executor( 23 | None, 24 | functools.partial(cls_method, **kwargs), 25 | *args 26 | ) 27 | return wrapper 28 | 29 | 30 | def trio_method(method: Callable[..., TReturn], 31 | ) -> Callable[..., Coroutine[Any, Any, TReturn]]: 32 | @functools.wraps(method) 33 | async def wrapper(cls_or_self: Any, *args: Any, **kwargs: Any) -> TReturn: 34 | cls_method = getattr(cls_or_self, method.__name__) 35 | return await trio.to_thread.run_sync(functools.partial(cls_method, **kwargs), *args) 36 | return wrapper 37 | -------------------------------------------------------------------------------- /trinity/tools/factories/headers.py: -------------------------------------------------------------------------------- 1 | try: 2 | import factory 3 | except ImportError: 4 | raise ImportError("The p2p.tools.factories module requires the `factory_boy` library.") 5 | 6 | import time 7 | 8 | from eth.constants import ( 9 | BLANK_ROOT_HASH, 10 | EMPTY_UNCLE_HASH, 11 | GENESIS_BLOCK_NUMBER, 12 | GENESIS_COINBASE, 13 | GENESIS_DIFFICULTY, 14 | GENESIS_EXTRA_DATA, 15 | GENESIS_MIX_HASH, 16 | GENESIS_NONCE, 17 | GENESIS_PARENT_HASH, 18 | ) 19 | from eth.rlp.headers import BlockHeader 20 | 21 | 22 | class BlockHeaderFactory(factory.Factory): 23 | class Meta: 24 | model = BlockHeader 25 | 26 | parent_hash = GENESIS_PARENT_HASH 27 | uncles_hash = EMPTY_UNCLE_HASH 28 | coinbase = GENESIS_COINBASE 29 | state_root = BLANK_ROOT_HASH 30 | transaction_root = BLANK_ROOT_HASH 31 | receipt_root = BLANK_ROOT_HASH 32 | bloom = 0 33 | difficulty = GENESIS_DIFFICULTY 34 | block_number = GENESIS_BLOCK_NUMBER 35 | gas_limit = 0 36 | gas_used = 0 37 | timestamp = factory.LazyFunction(lambda: int(time.time())) 38 | extra_data = GENESIS_EXTRA_DATA 39 | mix_hash = GENESIS_MIX_HASH 40 | nonce = GENESIS_NONCE 41 | -------------------------------------------------------------------------------- /p2p/stats/ema.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | from eth_utils import ValidationError 4 | 5 | 6 | class EMA: 7 | """ 8 | Represents an exponential moving average. 9 | https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average 10 | 11 | Smoothing factor, or "alpha" of the exponential moving average. 12 | 13 | - Closer to 0 gives you smoother, slower-to-update, data 14 | - Closer to 1 gives you choppier, quicker-to-update, data 15 | 16 | .. note:: 17 | 18 | A smoothing factor of 1 would completely ignore history whereas 0 would 19 | completely ignore new data 20 | 21 | 22 | The initial value is the starting value for the EMA 23 | """ 24 | def __init__(self, initial_value: float, smoothing_factor: float) -> None: 25 | self._value = initial_value 26 | if 0 < smoothing_factor < 1: 27 | self._alpha = smoothing_factor 28 | else: 29 | raise ValidationError("Smoothing factor of EMA must be between 0 and 1") 30 | 31 | def update(self, scalar: Union[int, float]) -> None: 32 | self._value = (self._value * (1 - self._alpha)) + (scalar * self._alpha) 33 | 34 | @property 35 | def value(self) -> float: 36 | return self._value 37 | -------------------------------------------------------------------------------- /trinity/protocol/les/trackers.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | Optional, 3 | Tuple, 4 | ) 5 | 6 | from eth.abc import BlockHeaderAPI 7 | 8 | from p2p.exchange import BasePerformanceTracker 9 | 10 | from trinity._utils.headers import sequence_builder 11 | 12 | from .commands import ( 13 | GetBlockHeaders, 14 | ) 15 | 16 | 17 | BaseGetBlockHeadersTracker = BasePerformanceTracker[ 18 | GetBlockHeaders, 19 | Tuple[BlockHeaderAPI, ...], 20 | ] 21 | 22 | 23 | class GetBlockHeadersTracker(BaseGetBlockHeadersTracker): 24 | def _get_request_size(self, request: GetBlockHeaders) -> Optional[int]: 25 | payload = request.payload.query 26 | if isinstance(payload.block_number_or_hash, int): 27 | return len(sequence_builder( 28 | start_number=payload.block_number_or_hash, 29 | max_length=payload.max_headers, 30 | skip=payload.skip, 31 | reverse=payload.reverse, 32 | )) 33 | else: 34 | return None 35 | 36 | def _get_result_size(self, result: Tuple[BlockHeaderAPI, ...]) -> int: 37 | return len(result) 38 | 39 | def _get_result_item_count(self, result: Tuple[BlockHeaderAPI, ...]) -> int: 40 | return len(result) 41 | -------------------------------------------------------------------------------- /p2p/tools/paragon/commands.py: -------------------------------------------------------------------------------- 1 | from rlp import sedes 2 | 3 | from p2p.commands import BaseCommand, RLPCodec 4 | 5 | from .payloads import ( 6 | BroadcastDataPayload, 7 | GetSumPayload, 8 | SumPayload, 9 | ) 10 | 11 | 12 | BROADCAST_DATA_STRUCTURE = sedes.List(( 13 | sedes.binary, 14 | )) 15 | 16 | 17 | class BroadcastData(BaseCommand[BroadcastDataPayload]): 18 | protocol_command_id = 0 19 | serialization_codec = RLPCodec( 20 | sedes=BROADCAST_DATA_STRUCTURE, 21 | process_inbound_payload_fn=lambda args: BroadcastDataPayload(*args), 22 | ) 23 | 24 | 25 | GET_SUM_STRUCTURE = sedes.List(( 26 | sedes.big_endian_int, 27 | sedes.big_endian_int, 28 | )) 29 | 30 | 31 | class GetSum(BaseCommand[GetSumPayload]): 32 | protocol_command_id = 2 33 | serialization_codec = RLPCodec( 34 | sedes=GET_SUM_STRUCTURE, 35 | process_inbound_payload_fn=lambda args: GetSumPayload(*args), 36 | ) 37 | 38 | 39 | SUM_STRUCTURE = sedes.List(( 40 | sedes.big_endian_int, 41 | )) 42 | 43 | 44 | class Sum(BaseCommand[SumPayload]): 45 | protocol_command_id = 3 46 | serialization_codec = RLPCodec( 47 | sedes=SUM_STRUCTURE, 48 | process_inbound_payload_fn=lambda args: SumPayload(*args), 49 | ) 50 | -------------------------------------------------------------------------------- /trinity/components/builtin/metrics/service/trio.py: -------------------------------------------------------------------------------- 1 | import functools 2 | from typing import ( 3 | Callable, 4 | TypeVar, 5 | ) 6 | 7 | from asks import Session 8 | 9 | from p2p import trio_utils 10 | 11 | from trinity.components.builtin.metrics.service.base import BaseMetricsService 12 | 13 | 14 | T = TypeVar('T') 15 | 16 | 17 | # temporary workaround to support decorator typing until we can use 18 | # @functools.cached_property with python version >= 3.8 19 | # https://github.com/python/mypy/issues/5858 20 | def cache(func: Callable[..., T]) -> T: 21 | return functools.lru_cache()(func) # type: ignore 22 | 23 | 24 | class TrioMetricsService(BaseMetricsService): 25 | @property # type: ignore 26 | @cache 27 | def session(self) -> Session: 28 | url = self.reporter._get_post_url() 29 | auth_header = self.reporter._generate_auth_header() 30 | return Session(url, headers=auth_header) 31 | 32 | async def async_post(self, data: str) -> None: 33 | # use trio-compatible asks library for async http calls 34 | await self.session.post(data=data) 35 | 36 | async def continuously_report(self) -> None: 37 | async for _ in trio_utils.every(self._reporting_frequency): 38 | await self.report_now() 39 | -------------------------------------------------------------------------------- /tests/core/utils/test_async_iter.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from trinity._utils.async_iter import async_take 4 | 5 | 6 | async def empty_iterator(): 7 | # trick to convince python that this is an iterator, despite yielding nothing 8 | if False: 9 | yield "NEVER" 10 | raise AssertionError("This should never have been yielded in the first place") 11 | 12 | 13 | async def single_val_iterator(): 14 | yield "just_once" 15 | 16 | 17 | async def canned_iterator(): 18 | while True: 19 | yield "spam" 20 | 21 | 22 | @pytest.mark.parametrize( 23 | "take_count, iterator, expected_list", 24 | ( 25 | (0, empty_iterator(), []), 26 | (1, empty_iterator(), []), 27 | (2, empty_iterator(), []), 28 | (0, single_val_iterator(), []), 29 | (1, single_val_iterator(), ["just_once"]), 30 | (2, single_val_iterator(), ["just_once"]), 31 | (0, canned_iterator(), []), 32 | (1, canned_iterator(), ["spam"]), 33 | (2, canned_iterator(), ["spam", "spam"]), 34 | ), 35 | ) 36 | @pytest.mark.asyncio 37 | async def test_async_take(take_count, iterator, expected_list): 38 | actual_result = [val async for val in async_take(take_count, iterator)] 39 | assert actual_result == expected_list 40 | -------------------------------------------------------------------------------- /trinity/tools/factories/receipts.py: -------------------------------------------------------------------------------- 1 | try: 2 | import factory 3 | from faker import Faker 4 | except ImportError: 5 | raise ImportError("The p2p.tools.factories module requires the `factory_boy` library.") 6 | 7 | from typing import ( 8 | Any, 9 | List, 10 | Type, 11 | ) 12 | 13 | from eth.constants import BLANK_ROOT_HASH 14 | from eth.rlp.receipts import Receipt 15 | import rlp 16 | 17 | from trinity.rlp.sedes import ( 18 | UninterpretedReceipt, 19 | strip_interpretation, 20 | ) 21 | 22 | 23 | class UninterpretedReceiptFactory(factory.Factory): 24 | class Meta: 25 | model = list 26 | 27 | __faker = Faker() 28 | @classmethod 29 | def _create(cls, 30 | model_class: Type[List[bytes]], 31 | *args: Any, 32 | **kwargs: Any) -> UninterpretedReceipt: 33 | 34 | if cls.__faker.boolean(): 35 | return b'\x01' + rlp.encode(LegacyReceiptFactory(*args, **kwargs)) 36 | else: 37 | return strip_interpretation(LegacyReceiptFactory(*args, **kwargs)) 38 | 39 | 40 | class LegacyReceiptFactory(factory.Factory): 41 | class Meta: 42 | model = Receipt 43 | 44 | state_root = BLANK_ROOT_HASH 45 | gas_used = 0 46 | bloom = 0 47 | logs = () 48 | -------------------------------------------------------------------------------- /newsfragments/validate_files.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Towncrier silently ignores files that do not match the expected ending. 4 | # We use this script to ensure we catch these as errors in CI. 5 | 6 | import os 7 | import pathlib 8 | import sys 9 | 10 | ALLOWED_EXTENSIONS = { 11 | '.bugfix.rst', 12 | '.doc.rst', 13 | '.feature.rst', 14 | '.internal.rst', 15 | '.misc.rst', 16 | '.performance.rst', 17 | '.removal.rst', 18 | } 19 | 20 | ALLOWED_FILES = { 21 | 'validate_files.py', 22 | 'README.md', 23 | } 24 | 25 | THIS_DIR = pathlib.Path(__file__).parent 26 | 27 | num_args = len(sys.argv) - 1 28 | assert num_args in {0, 1} 29 | if num_args == 1: 30 | assert sys.argv[1] in ('is-empty', ) 31 | 32 | for fragment_file in THIS_DIR.iterdir(): 33 | 34 | if fragment_file.name in ALLOWED_FILES: 35 | continue 36 | elif num_args == 0: 37 | full_extension = "".join(fragment_file.suffixes) 38 | if full_extension not in ALLOWED_EXTENSIONS: 39 | raise Exception(f"Unexpected file: {fragment_file}") 40 | elif sys.argv[1] == 'is-empty': 41 | raise Exception(f"Unexpected file: {fragment_file}") 42 | else: 43 | raise RuntimeError("Strange: arguments {sys.argv} were validated, but not found") 44 | -------------------------------------------------------------------------------- /trinity/rpc/modules/net.py: -------------------------------------------------------------------------------- 1 | from lahja import EndpointAPI 2 | 3 | from trinity.constants import TO_NETWORKING_BROADCAST_CONFIG 4 | from trinity.nodes.events import NetworkIdRequest 5 | from trinity.protocol.common.events import PeerCountRequest 6 | from trinity.rpc.modules import BaseRPCModule 7 | 8 | 9 | class Net(BaseRPCModule): 10 | 11 | def __init__(self, event_bus: EndpointAPI): 12 | self.event_bus = event_bus 13 | 14 | async def version(self) -> str: 15 | """ 16 | Returns the current network ID. 17 | """ 18 | response = await self.event_bus.request( 19 | NetworkIdRequest(), 20 | TO_NETWORKING_BROADCAST_CONFIG 21 | ) 22 | return str(response.network_id) 23 | 24 | async def peerCount(self) -> str: 25 | """ 26 | Return the number of peers that are currently connected to the node 27 | """ 28 | response = await self.event_bus.request( 29 | PeerCountRequest(), 30 | TO_NETWORKING_BROADCAST_CONFIG 31 | ) 32 | return hex(response.peer_count) 33 | 34 | async def listening(self) -> bool: 35 | """ 36 | Return `True` if the client is actively listening for network connections 37 | """ 38 | return True 39 | -------------------------------------------------------------------------------- /trinity/rpc/modules/_util.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | Union, 3 | ) 4 | 5 | from eth.abc import BlockHeaderAPI 6 | from eth_typing import ( 7 | BlockNumber, 8 | ) 9 | from eth_utils import ( 10 | is_integer, 11 | ) 12 | 13 | 14 | from trinity.chains.base import AsyncChainAPI 15 | 16 | 17 | async def get_header(chain: AsyncChainAPI, at_block: Union[str, int]) -> BlockHeaderAPI: 18 | if at_block == 'pending': 19 | raise NotImplementedError("RPC interface does not support the 'pending' block at this time") 20 | elif at_block == 'latest': 21 | at_header = chain.get_canonical_head() 22 | elif at_block == 'earliest': 23 | # TODO find if genesis block can be non-zero. Why does 'earliest' option even exist? 24 | block = await chain.coro_get_canonical_block_by_number(BlockNumber(0)) 25 | at_header = block.header 26 | # mypy doesn't have user defined type guards yet 27 | # https://github.com/python/mypy/issues/5206 28 | elif is_integer(at_block) and at_block >= 0: # type: ignore 29 | block = await chain.coro_get_canonical_block_by_number(BlockNumber(int(at_block))) 30 | at_header = block.header 31 | else: 32 | raise TypeError("Unrecognized block reference: %r" % at_block) 33 | 34 | return at_header 35 | -------------------------------------------------------------------------------- /trinity/events.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import ( 3 | Tuple, 4 | ) 5 | 6 | from lahja import ( 7 | BaseEvent, 8 | ConnectionConfig, 9 | ) 10 | 11 | 12 | @dataclass 13 | class EventBusConnected(BaseEvent): 14 | """ 15 | Broadcasted when a new :class:`~lahja.endpoint.Endpoint` connects to the ``main`` 16 | :class:`~lahja.endpoint.Endpoint`. The :class:`~lahja.endpoint.Endpoint` that connects to the 17 | the ``main`` :class:`~lahja.endpoint.Endpoint` should send 18 | :class:`~trinity.events.EventBusConnected` to ``main`` which will then cause ``main`` to send 19 | a :class:`~trinity.events.AvailableEndpointsUpdated` event to every connected 20 | :class:`~lahja.endpoint.Endpoint`, making them aware of other endpoints they can connect to. 21 | """ 22 | 23 | connection_config: ConnectionConfig 24 | 25 | 26 | @dataclass 27 | class AvailableEndpointsUpdated(BaseEvent): 28 | """ 29 | Broadcasted by the ``main`` :class:`~lahja.endpoint.Endpoint` after it has received a 30 | :class:`~trinity.events.EventBusConnected` event. The ``available_endpoints`` property 31 | lists all available endpoints that are known at the time when the event is raised. 32 | """ 33 | 34 | available_endpoints: Tuple[ConnectionConfig, ...] 35 | -------------------------------------------------------------------------------- /tests/core/database/test_db_client.py: -------------------------------------------------------------------------------- 1 | from eth.db.atomic import AtomicDB 2 | 3 | import pathlib 4 | import pytest 5 | import tempfile 6 | 7 | from eth.tools.db.atomic import AtomicDatabaseBatchAPITestSuite 8 | from eth.tools.db.base import DatabaseAPITestSuite 9 | 10 | from trinity.db.manager import ( 11 | DBManager, 12 | DBClient, 13 | ) 14 | 15 | 16 | @pytest.fixture 17 | def ipc_path(): 18 | with tempfile.TemporaryDirectory() as dir: 19 | ipc_path = pathlib.Path(dir) / "db_manager.ipc" 20 | yield ipc_path 21 | 22 | 23 | @pytest.fixture 24 | def base_db(): 25 | return AtomicDB() 26 | 27 | 28 | @pytest.fixture 29 | def db_manager(base_db, ipc_path): 30 | with DBManager(base_db).run(ipc_path) as manager: 31 | yield manager 32 | 33 | 34 | @pytest.fixture 35 | def db_client(ipc_path, db_manager): 36 | client = DBClient.connect(ipc_path) 37 | try: 38 | yield client 39 | finally: 40 | client.close() 41 | 42 | 43 | @pytest.fixture 44 | def db(db_client): 45 | return db_client 46 | 47 | 48 | @pytest.fixture 49 | def atomic_db(db): 50 | return db 51 | 52 | 53 | class TestDBClientDatabaseAPI(DatabaseAPITestSuite): 54 | pass 55 | 56 | 57 | class TestDBClientAtomicBatchAPI(AtomicDatabaseBatchAPITestSuite): 58 | pass 59 | -------------------------------------------------------------------------------- /trinity/rpc/modules/main.py: -------------------------------------------------------------------------------- 1 | from abc import ( 2 | ABC, 3 | ) 4 | from typing import ( 5 | Generic, 6 | TypeVar, 7 | ) 8 | 9 | from lahja import ( 10 | BaseEvent, 11 | EndpointAPI 12 | ) 13 | 14 | from trinity.chains.base import AsyncChainAPI 15 | 16 | 17 | TChain = TypeVar('TChain') 18 | 19 | 20 | class ChainReplacementEvent(BaseEvent, Generic[TChain]): 21 | 22 | def __init__(self, chain: TChain): 23 | self.chain = chain 24 | 25 | 26 | class BaseRPCModule(ABC): 27 | 28 | @classmethod 29 | def get_name(cls) -> str: 30 | # By default the name is the lower-case class name. 31 | # This encourages a standard name of the module, but can 32 | # be overridden if necessary. 33 | return cls.__name__.lower() 34 | 35 | 36 | class ChainBasedRPCModule(BaseRPCModule, Generic[TChain]): 37 | 38 | def __init__(self, chain: TChain, event_bus: EndpointAPI) -> None: 39 | self.chain = chain 40 | self.event_bus = event_bus 41 | 42 | self.event_bus.subscribe( 43 | ChainReplacementEvent, 44 | lambda ev: self.on_chain_replacement(ev.chain) 45 | ) 46 | 47 | def on_chain_replacement(self, chain: TChain) -> None: 48 | self.chain = chain 49 | 50 | 51 | Eth1ChainRPCModule = ChainBasedRPCModule[AsyncChainAPI] 52 | -------------------------------------------------------------------------------- /p2p/exchange/logic.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | import asyncio 3 | import contextlib 4 | from typing import Any, AsyncIterator 5 | 6 | from p2p.abc import ConnectionAPI, LogicAPI 7 | from p2p.exceptions import UnknownProtocol 8 | from p2p.logic import BaseLogic 9 | 10 | from .abc import ExchangeAPI 11 | 12 | 13 | class ExchangeLogic(BaseLogic): 14 | """ 15 | A thin wrapper around an exchange which handles running the services and 16 | checking whether it's applicable to the connection 17 | """ 18 | exchange: ExchangeAPI[Any, Any, Any] 19 | 20 | def __init__(self, exchange: ExchangeAPI[Any, Any, Any]) -> None: 21 | self.exchange = exchange 22 | 23 | def qualifier(self, connection: ConnectionAPI, logic: LogicAPI) -> bool: 24 | try: 25 | protocol = connection.get_protocol_for_command_type( 26 | self.exchange.get_request_cmd_type() 27 | ) 28 | except UnknownProtocol: 29 | return False 30 | else: 31 | return protocol.supports_command(self.exchange.get_response_cmd_type()) 32 | 33 | @contextlib.asynccontextmanager 34 | async def apply(self, connection: ConnectionAPI) -> AsyncIterator[asyncio.Task[Any]]: 35 | async with self.exchange.run_exchange(connection) as future: 36 | yield future 37 | -------------------------------------------------------------------------------- /tests/core/p2p-proto/test_wit_api.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from p2p.tools.factories.peer import PeerPairFactory 4 | 5 | from trinity.protocol.eth.peer import ETHPeerFactory 6 | from trinity.protocol.wit.api import WitnessAPI 7 | from trinity.tools.factories import ( 8 | ChainContextFactory, 9 | LatestETHPeerPairFactory, 10 | ) 11 | 12 | 13 | @pytest.mark.asyncio 14 | async def test_wit_api_property(): 15 | async with LatestETHPeerPairFactory() as (alice, bob): 16 | assert alice.connection.has_logic(WitnessAPI.name) 17 | wit_api = alice.connection.get_logic(WitnessAPI.name, WitnessAPI) 18 | 19 | assert wit_api is alice.wit_api 20 | 21 | 22 | @pytest.mark.asyncio 23 | async def test_no_wit_api_property_when_witness_not_supported(): 24 | class ETHPeerFactoryWithoutWitness(ETHPeerFactory): 25 | def _get_wit_handshakers(self): 26 | return tuple() 27 | 28 | peer_context = ChainContextFactory() 29 | peer_pair_factory = PeerPairFactory( 30 | alice_peer_context=peer_context, 31 | alice_peer_factory_class=ETHPeerFactory, 32 | bob_peer_context=peer_context, 33 | bob_peer_factory_class=ETHPeerFactoryWithoutWitness, 34 | ) 35 | async with peer_pair_factory as (alice, bob): 36 | assert not hasattr(bob, 'wit_api') 37 | assert not hasattr(alice, 'wit_api') 38 | -------------------------------------------------------------------------------- /p2p/validation.py: -------------------------------------------------------------------------------- 1 | import re 2 | import ipaddress 3 | 4 | from urllib import parse as urlparse 5 | 6 | from eth_utils import ( 7 | decode_hex, 8 | ValidationError, 9 | ) 10 | 11 | from eth_keys import ( 12 | keys, 13 | ) 14 | 15 | 16 | def validate_enode_uri(enode: str, require_ip: bool = False) -> None: 17 | try: 18 | parsed = urlparse.urlparse(enode) 19 | except ValueError as e: 20 | raise ValidationError(str(e)) 21 | 22 | if parsed.scheme != 'enode' or not parsed.username: 23 | raise ValidationError('enode string must be of the form "enode://public-key@ip:port"') 24 | 25 | if not re.match('^[0-9a-fA-F]{128}$', parsed.username): 26 | raise ValidationError('public key must be a 128-character hex string') 27 | 28 | decoded_username = decode_hex(parsed.username) 29 | 30 | try: 31 | ip = ipaddress.ip_address(parsed.hostname) 32 | except ValueError as e: 33 | raise ValidationError(str(e)) 34 | 35 | if require_ip and ip in (ipaddress.ip_address('0.0.0.0'), ipaddress.ip_address('::')): 36 | raise ValidationError('A concrete IP address must be specified') 37 | 38 | keys.PublicKey(decoded_username) 39 | 40 | try: 41 | # this property performs a check that the port is in range 42 | parsed.port 43 | except ValueError as e: 44 | raise ValidationError(str(e)) 45 | -------------------------------------------------------------------------------- /trinity/protocol/wit/events.py: -------------------------------------------------------------------------------- 1 | from dataclasses import ( 2 | dataclass, 3 | ) 4 | from typing import ( 5 | Sequence, 6 | Type, 7 | ) 8 | 9 | from lahja import ( 10 | BaseEvent, 11 | BaseRequestResponseEvent, 12 | ) 13 | 14 | from eth_typing import ( 15 | Hash32, 16 | ) 17 | 18 | from p2p.abc import SessionAPI 19 | 20 | from trinity.protocol.common.events import PeerPoolMessageEvent 21 | from trinity.protocol.wit.commands import ( 22 | BlockWitnessHashes, 23 | GetBlockWitnessHashes, 24 | ) 25 | 26 | 27 | class BlockWitnessHashesEvent(PeerPoolMessageEvent): 28 | command: BlockWitnessHashes 29 | 30 | 31 | class GetBlockWitnessHashesEvent(PeerPoolMessageEvent): 32 | command: GetBlockWitnessHashes 33 | 34 | 35 | @dataclass 36 | class SendBlockWitnessHashesEvent(PeerPoolMessageEvent): 37 | command: BlockWitnessHashes 38 | block_hash: Hash32 39 | 40 | 41 | @dataclass 42 | class GetBlockWitnessHashesResponse(BaseEvent): 43 | node_hashes: Sequence[Hash32] 44 | error: Exception = None 45 | 46 | 47 | @dataclass 48 | class GetBlockWitnessHashesRequest(BaseRequestResponseEvent[GetBlockWitnessHashesResponse]): 49 | session: SessionAPI 50 | block_hash: Hash32 51 | timeout: float 52 | 53 | @staticmethod 54 | def expected_response_type() -> Type[GetBlockWitnessHashesResponse]: 55 | return GetBlockWitnessHashesResponse 56 | -------------------------------------------------------------------------------- /trinity/_utils/async_iter.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | AsyncIterable, 3 | Set, 4 | TypeVar, 5 | ) 6 | 7 | from eth_utils import ValidationError 8 | 9 | 10 | async def contains_all(async_gen: AsyncIterable[str], keywords: Set[str]) -> bool: 11 | """ 12 | Check wether an ``AsyncIterable[str]`` contains all of the given keywords. The keywords 13 | can be embedded in some larger string. Return ``True`` as soon as all keywords were matched. 14 | If not all keywords were matched by the time that the async iterable is done, return ``False``. 15 | """ 16 | seen_keywords: Set[str] = set() 17 | async for line in async_gen: 18 | for check in keywords - seen_keywords: 19 | if check in line: 20 | seen_keywords.add(check) 21 | if seen_keywords == keywords: 22 | return True 23 | return False 24 | 25 | 26 | TYield = TypeVar('TYield') 27 | 28 | 29 | async def async_take(take_count: int, iterator: AsyncIterable[TYield]) -> AsyncIterable[TYield]: 30 | 31 | if take_count < 0: 32 | raise ValidationError(f"Cannot take a negative number of items: tried to take {take_count}") 33 | elif take_count == 0: 34 | return 35 | else: 36 | taken = 0 37 | async for val in iterator: 38 | taken += 1 39 | yield val 40 | if taken == take_count: 41 | break 42 | -------------------------------------------------------------------------------- /tests/integration/churn_state/Nymph2.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | // SPDX-License-Identifier: MIT 3 | 4 | // Nymph exists purely to be deleted later 5 | 6 | contract Nymph2 { 7 | function poof2() public { 8 | selfdestruct(msg.sender); 9 | } 10 | } 11 | 12 | /* 13 | Compiled bytecode: 14 | { 15 | "linkReferences": {}, 16 | "object": "6080604052348015600f57600080fd5b50606e80601d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063052950fc14602d575b600080fd5b60336035565b005b33fffea26469706673582212201ded73ef5821dbba54ce9bf1768654ba3ee022844594138501f73c9b5432163264736f6c634300060a0033", 17 | "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x6E DUP1 PUSH1 0x1D PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH1 0x28 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0x52950FC EQ PUSH1 0x2D JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x33 PUSH1 0x35 JUMP JUMPDEST STOP JUMPDEST CALLER SELFDESTRUCT INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 SAR 0xED PUSH20 0xEF5821DBBA54CE9BF1768654BA3EE02284459413 DUP6 ADD 0xF7 EXTCODECOPY SWAP12 SLOAD ORIGIN AND ORIGIN PUSH5 0x736F6C6343 STOP MOD EXP STOP CALLER ", 18 | "sourceMap": "275:481:0:-:0;;;;;;;;;;;;;;;;;;;" 19 | } 20 | */ 21 | -------------------------------------------------------------------------------- /tests-trio/conftest.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import tempfile 3 | import uuid 4 | 5 | from lahja import ConnectionConfig 6 | from lahja.trio.endpoint import TrioEndpoint 7 | import pytest 8 | import trio 9 | 10 | # Fixtures below are copied from https://github.com/ethereum/lahja/blob/f0b7ead13298de82c02ed92cfb2d32a8bc00b42a/tests/core/trio/conftest.py # noqa: E501 11 | 12 | 13 | @pytest.fixture 14 | def ipc_base_path(): 15 | with tempfile.TemporaryDirectory() as temp_dir: 16 | yield Path(temp_dir) 17 | 18 | 19 | def generate_unique_name() -> str: 20 | # We use unique names to avoid clashing of IPC pipes 21 | return str(uuid.uuid4()) 22 | 23 | 24 | @pytest.fixture 25 | def endpoint_server_config(ipc_base_path): 26 | config = ConnectionConfig.from_name(generate_unique_name(), base_path=ipc_base_path) 27 | return config 28 | 29 | 30 | @pytest.fixture 31 | async def endpoint_server(endpoint_server_config): 32 | async with TrioEndpoint.serve(endpoint_server_config) as endpoint: 33 | yield endpoint 34 | 35 | 36 | @pytest.fixture 37 | async def endpoint_client(endpoint_server_config, endpoint_server): 38 | async with TrioEndpoint("client-for-testing").run() as client: 39 | await client.connect_to_endpoints(endpoint_server_config) 40 | while not endpoint_server.is_connected_to("client-for-testing"): 41 | await trio.sleep(0) 42 | yield client 43 | -------------------------------------------------------------------------------- /p2p/exchange/normalizers.py: -------------------------------------------------------------------------------- 1 | from typing import Type, Callable 2 | 3 | from .abc import NormalizerAPI 4 | from .typing import TResponseCommand, TResult 5 | 6 | 7 | class BaseNormalizer(NormalizerAPI[TResponseCommand, TResult]): 8 | is_normalization_slow = False 9 | 10 | 11 | def _pick_payload(cmd: TResponseCommand) -> TResult: 12 | return cmd.payload 13 | 14 | 15 | class DefaultNormalizer(NormalizerAPI[TResponseCommand, TResult]): 16 | """ 17 | A normalizer that directly delegates to the payload of the response command. 18 | """ 19 | 20 | def __init__(self, 21 | command_type: Type[TResponseCommand], 22 | payload_type: Type[TResult], 23 | is_normalization_slow: bool = False, 24 | normalize_fn: Callable[[TResponseCommand], TResult] = _pick_payload): 25 | self.is_normalization_slow = is_normalization_slow 26 | self.normalize_fn = normalize_fn 27 | 28 | # The constructor allows mypy to defer typehints automatically from something like: 29 | # normalizer = DefaultNormalizer(BlockHeadersV66, Tuple[BlockHeaderAPI, ...]) 30 | # instead of the slightly longer form: 31 | # normalizer: DefaultNormalizer[BlockHeadersV66, Tuple[BlockHeaderAPI, ...]] = DefaultNormalizer() # noqa: E501 32 | 33 | def normalize_result(self, cmd: TResponseCommand) -> TResult: 34 | return self.normalize_fn(cmd) 35 | -------------------------------------------------------------------------------- /tests/integration/churn_state/Nymph1.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | // SPDX-License-Identifier: MIT 3 | 4 | // Nymph exists purely to be deleted later 5 | 6 | contract Nymph1 { 7 | function poof1() public { 8 | selfdestruct(msg.sender); 9 | } 10 | } 11 | 12 | /* 13 | Compiled bytecode: 14 | { 15 | "linkReferences": {}, 16 | "object": "6080604052348015600f57600080fd5b50606e80601d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063efef643214602d575b600080fd5b60336035565b005b33fffea2646970667358221220b4ea18f564027780dd6914a115a492a1dbf78dc68f64b867e51db5a6c71238c964736f6c634300060a0033", 17 | "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x6E DUP1 PUSH1 0x1D PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH1 0x28 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0xEFEF6432 EQ PUSH1 0x2D JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x33 PUSH1 0x35 JUMP JUMPDEST STOP JUMPDEST CALLER SELFDESTRUCT INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 0xB4 0xEA XOR CREATE2 PUSH5 0x27780DD69 EQ LOG1 ISZERO LOG4 SWAP3 LOG1 0xDB 0xF7 DUP14 0xC6 DUP16 PUSH5 0xB867E51DB5 0xA6 0xC7 SLT CODESIZE 0xC9 PUSH5 0x736F6C6343 STOP MOD EXP STOP CALLER ", 18 | "sourceMap": "275:481:0:-:0;;;;;;;;;;;;;;;;;;;" 19 | } 20 | */ 21 | -------------------------------------------------------------------------------- /p2p/resource_lock.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from collections import defaultdict 3 | from collections.abc import Hashable 4 | import contextlib 5 | from typing import AsyncIterator, DefaultDict, Dict, Generic, TypeVar 6 | 7 | 8 | TResource = TypeVar('TResource', bound=Hashable) 9 | 10 | 11 | class ResourceLock(Generic[TResource]): 12 | """ 13 | Manage a set of locks for some set of hashable resources. 14 | """ 15 | _locks: Dict[TResource, asyncio.Lock] 16 | _reference_counts: DefaultDict[TResource, int] 17 | 18 | def __init__(self) -> None: 19 | self._locks = {} 20 | self._reference_counts = defaultdict(int) 21 | 22 | @contextlib.asynccontextmanager 23 | async def lock(self, resource: TResource) -> AsyncIterator[None]: 24 | if resource not in self._locks: 25 | self._locks[resource] = asyncio.Lock() 26 | 27 | try: 28 | self._reference_counts[resource] += 1 29 | async with self._locks[resource]: 30 | yield 31 | finally: 32 | self._reference_counts[resource] -= 1 33 | if self._reference_counts[resource] <= 0: 34 | del self._reference_counts[resource] 35 | del self._locks[resource] 36 | 37 | def is_locked(self, resource: TResource) -> bool: 38 | if resource not in self._locks: 39 | return False 40 | else: 41 | return self._locks[resource].locked() 42 | -------------------------------------------------------------------------------- /trinity/_utils/services.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import contextlib 3 | from typing import Sequence 4 | 5 | from async_service import ( 6 | background_asyncio_service, 7 | background_trio_service, 8 | ServiceAPI, 9 | ) 10 | 11 | from p2p.asyncio_utils import wait_first as wait_first_asyncio 12 | from trinity._utils.trio_utils import wait_first as wait_first_trio 13 | 14 | 15 | async def run_background_asyncio_services(services: Sequence[ServiceAPI]) -> None: 16 | async with contextlib.AsyncExitStack() as stack: 17 | managers = tuple([ 18 | await stack.enter_async_context(background_asyncio_service(service)) 19 | for service in services 20 | ]) 21 | # If any of the services terminate, we do so as well. 22 | await wait_first_asyncio( 23 | [asyncio.create_task(manager.wait_finished()) for manager in managers], 24 | max_wait_after_cancellation=2 25 | ) 26 | 27 | 28 | async def run_background_trio_services(services: Sequence[ServiceAPI]) -> None: 29 | async with contextlib.AsyncExitStack() as stack: 30 | managers = tuple([ 31 | await stack.enter_async_context(background_trio_service(service)) 32 | for service in services 33 | ]) 34 | # If any of the services terminate, we do so as well. 35 | await wait_first_trio([ 36 | manager.wait_finished 37 | for manager in managers 38 | ]) 39 | -------------------------------------------------------------------------------- /tests/core/database/test_db_manager.py: -------------------------------------------------------------------------------- 1 | from eth.db.atomic import AtomicDB 2 | 3 | import pathlib 4 | import pytest 5 | import tempfile 6 | from trinity.db.manager import ( 7 | DBManager, 8 | DBClient, 9 | ) 10 | 11 | 12 | @pytest.fixture 13 | def ipc_path(): 14 | with tempfile.TemporaryDirectory() as dir: 15 | ipc_path = pathlib.Path(dir) / "db_manager.ipc" 16 | yield ipc_path 17 | 18 | 19 | @pytest.fixture 20 | def db(): 21 | return AtomicDB() 22 | 23 | 24 | def test_db_manager_lifecycle(db, ipc_path): 25 | manager = DBManager(db) 26 | 27 | assert not manager.is_running 28 | assert not manager.is_stopped 29 | 30 | with manager.run(ipc_path): 31 | assert manager.is_running 32 | assert not manager.is_stopped 33 | 34 | assert not manager.is_running 35 | assert manager.is_stopped 36 | 37 | 38 | def test_db_manager_lifecycle_with_connections(db, ipc_path): 39 | manager = DBManager(db) 40 | 41 | assert not manager.is_running 42 | assert not manager.is_stopped 43 | 44 | with manager.run(ipc_path): 45 | assert manager.is_running 46 | assert not manager.is_stopped 47 | 48 | client_a = DBClient.connect(ipc_path) 49 | client_b = DBClient.connect(ipc_path) 50 | 51 | assert manager.is_running 52 | assert not manager.is_stopped 53 | 54 | client_a.close() 55 | client_b.close() 56 | 57 | assert not manager.is_running 58 | assert manager.is_stopped 59 | -------------------------------------------------------------------------------- /trinity/_utils/pauser.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | 4 | class Pauser: 5 | """ 6 | A helper class to provide pause / resume functionality to other classes. 7 | """ 8 | 9 | def __init__(self) -> None: 10 | self._paused = False 11 | self._resumed = asyncio.Event() 12 | 13 | @property 14 | def is_paused(self) -> bool: 15 | """ 16 | Return ``True`` if the state of managed operation is paused, otherwise ``False``. 17 | """ 18 | return self._paused 19 | 20 | def pause(self) -> None: 21 | """ 22 | Pause the managed operation. 23 | """ 24 | if self._paused: 25 | raise RuntimeError( 26 | "Invalid action. Can not pause an operation that is already paused." 27 | ) 28 | 29 | self._paused = True 30 | 31 | def resume(self) -> None: 32 | """ 33 | Resume the operation. 34 | """ 35 | if not self._paused: 36 | raise RuntimeError("Invalid action. Can not resume operation that isn't paused.") 37 | 38 | self._paused = False 39 | self._resumed.set() 40 | 41 | async def await_resume(self) -> None: 42 | """ 43 | Await until ``resume()`` is called. Throw if called when the operation is not paused. 44 | """ 45 | if not self._paused: 46 | raise RuntimeError("Can not await resume on operation that isn't paused.") 47 | 48 | await self._resumed.wait() 49 | self._resumed.clear() 50 | -------------------------------------------------------------------------------- /trinity/http/main.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import ( 3 | Any, 4 | Callable, 5 | ) 6 | 7 | from aiohttp import web 8 | 9 | from async_service import Service 10 | 11 | from eth_utils import DEBUG2_LEVEL_NUM 12 | 13 | from trinity._utils.logging import get_logger 14 | 15 | 16 | class HTTPServer(Service): 17 | server = None 18 | host = None 19 | port = None 20 | 21 | def __init__( 22 | self, handler: Callable[..., Any], host: str = '127.0.0.1', port: int = 8545) -> None: 23 | self.host = host 24 | self.port = port 25 | self.server = web.Server(handler) 26 | self.logger = get_logger('trinity.http.HTTPServer') 27 | 28 | # aiohttp logs every HTTP request as INFO so we want to reduce the general log level for 29 | # this particular logger to WARNING except if the Trinity is configured to write DEBUG2 logs 30 | if logging.getLogger().level != DEBUG2_LEVEL_NUM: 31 | logging.getLogger('aiohttp.access').setLevel(logging.WARNING) 32 | 33 | async def run(self) -> None: 34 | runner = web.ServerRunner(self.server) 35 | await runner.setup() 36 | site = web.TCPSite(runner, self.host, self.port) 37 | self.logger.info("Running HTTP Server %s:%d", self.host, self.port) 38 | await site.start() 39 | 40 | try: 41 | await self.manager.wait_finished() 42 | finally: 43 | self.logger.info("Closing HTTPServer...") 44 | await self.server.shutdown() 45 | -------------------------------------------------------------------------------- /tests/core/utils/test_headers.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from trinity._utils.headers import ( 4 | skip_complete_headers, 5 | ) 6 | 7 | 8 | async def return_true(header): 9 | return True 10 | 11 | 12 | async def return_false(header): 13 | return False 14 | 15 | 16 | async def is_odd(header): 17 | return header % 2 18 | 19 | 20 | @pytest.mark.asyncio 21 | @pytest.mark.parametrize( 22 | "headers, check, expected_completed, expected_remaining", 23 | ( 24 | ((), return_true, (), ()), 25 | ((), return_false, (), ()), 26 | ((1,), return_true, (1,), ()), 27 | ((1,), return_false, (), (1,)), 28 | ((2, 3), return_true, (2, 3), ()), 29 | ((2, 3), return_false, (), (2, 3)), 30 | ((5, 6), is_odd, (5,), (6,)), 31 | # should accept a generator 32 | ((_ for _ in range(0)), return_false, (), ()), 33 | ((_ for _ in range(0)), return_true, (), ()), 34 | ((i for i in range(3)), return_true, (0, 1, 2), ()), 35 | ((i for i in range(3)), return_false, (), (0, 1, 2)), 36 | ((i for i in range(1, 4)), is_odd, (1,), (2, 3)), 37 | ), 38 | ) 39 | async def test_skip_complete_headers(headers, check, expected_completed, expected_remaining): 40 | # Nothing about skip_complete_headers actually depends on having a header, so 41 | # we use integers for easy testing. 42 | completed, remaining = await skip_complete_headers(headers, check) 43 | assert completed == expected_completed 44 | assert remaining == expected_remaining 45 | -------------------------------------------------------------------------------- /tests/p2p/test_metrics.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pyformance import MetricsRegistry 4 | 5 | from p2p.metrics import PeerReporterRegistry 6 | from trinity.protocol.eth.peer import ETHPeer 7 | from trinity.tools.factories import LatestETHPeerPairFactory 8 | 9 | 10 | @pytest.mark.asyncio 11 | async def test_peer_reporter_registry(): 12 | async with LatestETHPeerPairFactory() as (alice, bob): 13 | assert isinstance(alice, ETHPeer) 14 | assert isinstance(bob, ETHPeer) 15 | 16 | metrics_registry = MetricsRegistry() 17 | peer_reporter_registry = PeerReporterRegistry(metrics_registry) 18 | assert len(peer_reporter_registry._peer_reporters.keys()) == 0 19 | 20 | peer_reporter_registry.assign_peer_reporter(alice) 21 | assert len(peer_reporter_registry._peer_reporters.keys()) == 1 22 | assert peer_reporter_registry._peer_reporters[alice] == 0 23 | 24 | peer_reporter_registry.assign_peer_reporter(bob) 25 | assert len(peer_reporter_registry._peer_reporters.keys()) == 2 26 | assert peer_reporter_registry._peer_reporters[bob] == 1 27 | 28 | peer_reporter_registry.unassign_peer_reporter(alice) 29 | assert alice not in peer_reporter_registry._peer_reporters.keys() 30 | assert len(peer_reporter_registry._peer_reporters.keys()) == 1 31 | 32 | peer_reporter_registry.unassign_peer_reporter(bob) 33 | assert bob not in peer_reporter_registry._peer_reporters.keys() 34 | assert len(peer_reporter_registry._peer_reporters.keys()) == 0 35 | -------------------------------------------------------------------------------- /dappnode/dappnode_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "trinity.public.dappnode.eth", 3 | "version": "0.1.3", 4 | "shortDescription": "Ethereum Python Client (experimental)", 5 | "description": "Written in Python with great extensibility, the Trinity client is especially interesting for developers. Its novel beam sync strategy makes it possible to have a fully functional client within minutes after launch.", 6 | "avatar": "/ipfs/QmRyJJwuCEYHPf7xkRcoPJqiHQgAN3ay2voSCTrAY1nMet", 7 | "type": "library", 8 | "chain": "ethereum", 9 | "image": { 10 | "path": "trinity.public.dappnode.eth_0.1.3.tar.xz", 11 | "hash": "/ipfs/QmZfnAJNHoGESss7gQAjfiyV9RP3Rt1bEh4Btcz2LNFAVh", 12 | "size": 296347736, 13 | "ports": [ 14 | "57313:30303", 15 | "57313:30303/udp", 16 | "57314:30304" 17 | ], 18 | "volumes": [ 19 | "trinity:/trinity", 20 | "/etc/timezone:/etc/timezone:ro", 21 | "/etc/localtime:/etc/localtime:ro" 22 | ], 23 | "environment": [ 24 | "EXTRA_OPTS=--trinity-root-dir /trinity --enable-http-apis=net,eth --http-listen-address 0.0.0.0" 25 | ], 26 | "restart": "always" 27 | }, 28 | "dependencies": {}, 29 | "author": "Ethereum Foundation", 30 | "categories": [ 31 | "Ethereum Node", 32 | "Developer Tools" 33 | ], 34 | "links": { 35 | "homepage": "http://trinity.ethereum.org", 36 | "api": "http://trinity.public.dappnode:8545" 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "https://github.com/ethereum/trinity.git" 41 | }, 42 | "bugs": { 43 | "url": "https://github.com/ethereum/trinity/issues" 44 | }, 45 | "license": "MIT" 46 | } -------------------------------------------------------------------------------- /trinity/components/builtin/upnp/component.py: -------------------------------------------------------------------------------- 1 | from argparse import ( 2 | ArgumentParser, 3 | _SubParsersAction, 4 | ) 5 | 6 | from async_service import background_trio_service 7 | from lahja import EndpointAPI 8 | 9 | from trinity.extensibility import ( 10 | TrioIsolatedComponent, 11 | ) 12 | from trinity.components.builtin.upnp.nat import UPnPService 13 | from trinity.constants import UPNP_EVENTBUS_ENDPOINT 14 | 15 | 16 | class UpnpComponent(TrioIsolatedComponent): 17 | """ 18 | Continously try to map external to internal ip address/port using the 19 | Universal Plug 'n' Play (upnp) standard. 20 | """ 21 | name = "Upnp" 22 | endpoint_name = UPNP_EVENTBUS_ENDPOINT 23 | 24 | @property 25 | def is_enabled(self) -> bool: 26 | return not bool(self._boot_info.args.disable_upnp) 27 | 28 | @classmethod 29 | def configure_parser(cls, 30 | arg_parser: ArgumentParser, 31 | subparser: _SubParsersAction) -> None: 32 | arg_parser.add_argument( 33 | "--disable-upnp", 34 | action="store_true", 35 | help="Disable upnp mapping", 36 | ) 37 | 38 | async def do_run(self, event_bus: EndpointAPI) -> None: 39 | port = self._boot_info.trinity_config.port 40 | upnp_service = UPnPService(port, event_bus) 41 | 42 | async with background_trio_service(upnp_service) as manager: 43 | await manager.wait_finished() 44 | 45 | 46 | if __name__ == "__main__": 47 | from trinity.extensibility.component import run_trio_eth1_component 48 | run_trio_eth1_component(UpnpComponent) 49 | -------------------------------------------------------------------------------- /trinity/protocol/les/proto.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | Type, 3 | Union, 4 | ) 5 | 6 | from p2p.protocol import BaseProtocol 7 | 8 | from .commands import ( 9 | Announce, 10 | BlockBodies, 11 | BlockHeaders, 12 | ContractCodes, 13 | GetBlockBodies, 14 | GetBlockHeaders, 15 | GetContractCodes, 16 | GetProofsV1, 17 | GetProofsV2, 18 | GetReceipts, 19 | ProofsV1, 20 | ProofsV2, 21 | Receipts, 22 | StatusV1, 23 | StatusV2, 24 | ) 25 | 26 | 27 | class BaseLESProtocol(BaseProtocol): 28 | name = 'les' 29 | status_command_type: Union[Type[StatusV1], Type[StatusV2]] 30 | get_proofs_command_type: Union[Type[GetProofsV1], Type[GetProofsV2]] 31 | 32 | 33 | class LESProtocolV1(BaseLESProtocol): 34 | version = 1 35 | commands = ( 36 | StatusV1, 37 | Announce, 38 | BlockHeaders, GetBlockHeaders, 39 | BlockBodies, GetBlockBodies, 40 | Receipts, GetReceipts, 41 | ProofsV1, GetProofsV1, 42 | ContractCodes, GetContractCodes, 43 | ) 44 | command_length = 15 45 | 46 | status_command_type = StatusV1 47 | get_proofs_command_type = GetProofsV1 48 | 49 | 50 | class LESProtocolV2(BaseLESProtocol): 51 | version = 2 52 | commands = ( 53 | StatusV2, 54 | Announce, 55 | BlockHeaders, GetBlockHeaders, 56 | BlockBodies, GetBlockBodies, 57 | Receipts, GetReceipts, 58 | ProofsV2, GetProofsV2, 59 | ContractCodes, GetContractCodes, 60 | ) 61 | command_length = 21 62 | 63 | status_command_type = StatusV2 64 | get_proofs_command_type = GetProofsV2 65 | -------------------------------------------------------------------------------- /scripts/release/test_package.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import subprocess 3 | from tempfile import TemporaryDirectory 4 | import venv 5 | 6 | 7 | def create_venv(parent_path): 8 | venv_path = parent_path / 'package-smoke-test' 9 | venv.create(venv_path, with_pip=True) 10 | subprocess.run([venv_path / 'bin' / 'pip', 'install', '-U', 'pip', 'setuptools'], check=True) 11 | return venv_path 12 | 13 | 14 | def find_wheel(project_path): 15 | wheels = list(project_path.glob('dist/*.whl')) 16 | 17 | if len(wheels) != 1: 18 | raise Exception( 19 | f"Expected one wheel. Instead found: {wheels} in project {project_path.absolute()}" 20 | ) 21 | 22 | return wheels[0] 23 | 24 | 25 | def install_wheel(venv_path, wheel_path, extras=()): 26 | if extras: 27 | extra_suffix = f"[{','.join(extras)}]" 28 | else: 29 | extra_suffix = "" 30 | 31 | subprocess.run( 32 | [ 33 | venv_path / 'bin' / 'pip', 34 | 'install', 35 | f"{wheel_path}{extra_suffix}" 36 | ], 37 | check=True, 38 | ) 39 | 40 | 41 | def test_install_local_wheel(): 42 | with TemporaryDirectory() as tmpdir: 43 | venv_path = create_venv(Path(tmpdir)) 44 | wheel_path = find_wheel(Path('.')) 45 | install_wheel(venv_path, wheel_path, extras=['p2p', 'trinity']) 46 | print("Installed", wheel_path.absolute(), "to", venv_path) 47 | print(f"Activate with `source {venv_path}/bin/activate`") 48 | input("Press enter when the test has completed. The directory will be deleted.") 49 | 50 | 51 | if __name__ == '__main__': 52 | test_install_local_wheel() 53 | -------------------------------------------------------------------------------- /trinity/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from eth.db.backends.level import LevelDB 4 | from eth.db.chain import ChainDB 5 | 6 | from trinity.boot_info import BootInfo 7 | from trinity.bootstrap import ( 8 | main_entry, 9 | ) 10 | from trinity.config import ( 11 | Eth1AppConfig, 12 | ) 13 | from trinity.constants import ( 14 | APP_IDENTIFIER_ETH1, 15 | ) 16 | from trinity.components.registry import ( 17 | get_components_for_eth1_client, 18 | ) 19 | from trinity.initialization import ( 20 | ensure_eth1_dirs, 21 | initialize_database, 22 | is_database_initialized, 23 | ) 24 | 25 | 26 | def main() -> None: 27 | # Need a pretty long timeout because we fire all components at the same time so unless there 28 | # are at least a dozen idle cores, some of them will take a while to actually start running. 29 | os.environ['ASYNCIO_RUN_IN_PROCESS_STARTUP_TIMEOUT'] = '30' 30 | main_entry( 31 | trinity_boot, 32 | get_base_db, 33 | APP_IDENTIFIER_ETH1, 34 | get_components_for_eth1_client(), 35 | (Eth1AppConfig,) 36 | ) 37 | 38 | 39 | def get_base_db(boot_info: BootInfo) -> LevelDB: 40 | app_config = boot_info.trinity_config.get_app_config(Eth1AppConfig) 41 | base_db = LevelDB(db_path=app_config.database_dir) 42 | chaindb = ChainDB(base_db) 43 | if not is_database_initialized(chaindb): 44 | chain_config = app_config.get_chain_config() 45 | initialize_database(chain_config, chaindb, base_db) 46 | return base_db 47 | 48 | 49 | def trinity_boot(boot_info: BootInfo) -> None: 50 | trinity_config = boot_info.trinity_config 51 | ensure_eth1_dirs(trinity_config.get_app_config(Eth1AppConfig)) 52 | -------------------------------------------------------------------------------- /trinity/_utils/connect.py: -------------------------------------------------------------------------------- 1 | from typing import Iterator 2 | import contextlib 3 | 4 | from eth.abc import ChainAPI 5 | from eth.db.chain import ChainDB 6 | 7 | from lahja import EndpointAPI 8 | from trinity.boot_info import BootInfo 9 | from trinity.db.eth1.header import AsyncHeaderDB 10 | from trinity.chains.light_eventbus import ( 11 | EventBusLightPeerChain, 12 | ) 13 | from trinity.config import ( 14 | Eth1AppConfig, 15 | ) 16 | from trinity.constants import ( 17 | SYNC_LIGHT, 18 | ) 19 | from trinity.db.manager import DBClient 20 | 21 | 22 | @contextlib.contextmanager 23 | def get_eth1_chain_with_remote_db(boot_info: BootInfo, 24 | event_bus: EndpointAPI) -> Iterator[ChainAPI]: 25 | app_config = boot_info.trinity_config.get_app_config(Eth1AppConfig) 26 | chain_config = app_config.get_chain_config() 27 | 28 | chain: ChainAPI 29 | base_db = DBClient.connect(boot_info.trinity_config.database_ipc_path) 30 | with base_db: 31 | if boot_info.args.sync_mode == SYNC_LIGHT: 32 | header_db = AsyncHeaderDB(base_db) 33 | chain = chain_config.light_chain_class( 34 | header_db, 35 | peer_chain=EventBusLightPeerChain(event_bus) 36 | ) 37 | else: 38 | chain = chain_config.full_chain_class(base_db) 39 | 40 | yield chain 41 | 42 | 43 | @contextlib.contextmanager 44 | def get_eth1_chain_db_with_remote_db(boot_info: BootInfo, 45 | event_bus: EndpointAPI) -> Iterator[ChainDB]: 46 | base_db = DBClient.connect(boot_info.trinity_config.database_ipc_path) 47 | with base_db: 48 | yield ChainDB(base_db) 49 | -------------------------------------------------------------------------------- /tests/p2p/test_resource_lock.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import random 3 | 4 | from hypothesis import ( 5 | given, 6 | strategies as st, 7 | ) 8 | import pytest 9 | 10 | from p2p.resource_lock import ResourceLock 11 | 12 | 13 | async def _yield_to_loop_some(): 14 | for _ in range(random.randint(0, 5)): 15 | await asyncio.sleep(0) 16 | 17 | 18 | class Resource: 19 | is_available = True 20 | 21 | async def consume(self): 22 | assert self.is_available 23 | self.is_available = False 24 | try: 25 | await _yield_to_loop_some() 26 | finally: 27 | self.is_available = True 28 | 29 | 30 | @given( 31 | num_consumers=st.integers(min_value=0, max_value=100), 32 | ) 33 | @pytest.mark.asyncio 34 | async def test_resource_lock(num_consumers): 35 | """ 36 | The lock is tested by fuzzing it against multiple consumers that all try to 37 | acquire the same resource. Each consumer randomly yields to the event loop 38 | a few times between each context switch to better guarantee that different 39 | race conditions are covered. 40 | """ 41 | resource_lock = ResourceLock() 42 | resource = Resource() 43 | 44 | async def consume_resource(): 45 | await _yield_to_loop_some() 46 | 47 | async with resource_lock.lock(resource): 48 | await _yield_to_loop_some() 49 | await resource.consume() 50 | await _yield_to_loop_some() 51 | 52 | await asyncio.gather(*( 53 | consume_resource() 54 | for i in range(num_consumers) 55 | )) 56 | 57 | assert resource not in resource_lock._locks 58 | assert resource not in resource_lock._reference_counts 59 | -------------------------------------------------------------------------------- /trinity/chains/coro.py: -------------------------------------------------------------------------------- 1 | from typing import Type 2 | 3 | from eth.abc import ChainDatabaseAPI 4 | from eth.chains import Chain 5 | 6 | from trinity._utils.async_dispatch import async_method 7 | from trinity.db.eth1.chain import AsyncChainDB 8 | 9 | from .base import AsyncChainAPI 10 | 11 | 12 | class AsyncChainMixin(AsyncChainAPI): 13 | chaindb_class: Type[ChainDatabaseAPI] = AsyncChainDB 14 | 15 | coro_get_ancestors = async_method(Chain.get_ancestors) 16 | coro_get_block_by_hash = async_method(Chain.get_block_by_hash) 17 | coro_get_block_by_header = async_method(Chain.get_block_by_header) 18 | coro_get_block_header_by_hash = async_method(Chain.get_block_header_by_hash) 19 | coro_get_canonical_block_by_number = async_method(Chain.get_canonical_block_by_number) 20 | coro_get_canonical_head = async_method(Chain.get_canonical_head) 21 | coro_get_canonical_block_header_by_number = async_method( 22 | Chain.get_canonical_block_header_by_number 23 | ) 24 | coro_get_canonical_transaction_index = async_method(Chain.get_canonical_transaction_index) 25 | coro_get_canonical_transaction = async_method(Chain.get_canonical_transaction) 26 | coro_get_canonical_transaction_by_index = async_method(Chain.get_canonical_transaction_by_index) 27 | coro_get_score = async_method(Chain.get_score) 28 | coro_get_transaction_receipt = async_method(Chain.get_transaction_receipt) 29 | coro_get_transaction_receipt_by_index = async_method(Chain.get_transaction_receipt_by_index) 30 | coro_import_block = async_method(Chain.import_block) 31 | coro_validate_chain = async_method(Chain.validate_chain) 32 | coro_validate_receipt = async_method(Chain.validate_receipt) 33 | -------------------------------------------------------------------------------- /trinity/sync/full/service.py: -------------------------------------------------------------------------------- 1 | from async_service import Service, background_asyncio_service 2 | 3 | from eth.abc import AtomicDatabaseAPI 4 | from eth.constants import BLANK_ROOT_HASH 5 | 6 | from trinity.chains.base import AsyncChainAPI 7 | from trinity.db.eth1.chain import BaseAsyncChainDB 8 | from trinity.protocol.eth.peer import ETHPeerPool 9 | from trinity._utils.logging import get_logger 10 | 11 | from .chain import RegularChainSyncer 12 | 13 | 14 | class FullChainSyncer(Service): 15 | 16 | def __init__(self, 17 | chain: AsyncChainAPI, 18 | chaindb: BaseAsyncChainDB, 19 | base_db: AtomicDatabaseAPI, 20 | peer_pool: ETHPeerPool) -> None: 21 | self.logger = get_logger('trinity.sync.full.FullChainSyncer') 22 | self.chain = chain 23 | self.chaindb = chaindb 24 | self.base_db = base_db 25 | self.peer_pool = peer_pool 26 | 27 | async def run(self) -> None: 28 | head = await self.chaindb.coro_get_canonical_head() 29 | 30 | # Ensure we have the state for our current head. 31 | if head.state_root != BLANK_ROOT_HASH and head.state_root not in self.base_db: 32 | self.logger.error( 33 | "Missing state for current head %s, run beam sync instead", head) 34 | return 35 | 36 | # Now, loop forever, fetching missing blocks and applying them. 37 | self.logger.info("Starting regular sync; current head: %s", head) 38 | regular_syncer = RegularChainSyncer(self.chain, self.chaindb, self.peer_pool) 39 | async with background_asyncio_service(regular_syncer) as manager: 40 | await manager.wait_finished() 41 | -------------------------------------------------------------------------------- /tests/core/eip1085-utils/test_extract_genesis_state.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from eth_utils import ( 4 | encode_hex, 5 | ) 6 | 7 | from trinity._utils.eip1085 import ( 8 | Account, 9 | extract_genesis_state, 10 | ) 11 | 12 | 13 | ADDRESS = b'01' * 10 14 | ADDRESS_HEX = encode_hex(ADDRESS) 15 | 16 | ACCOUNT_EMPTY = { 17 | 'balance': '0x00', 18 | 'nonce': '0x00', 19 | 'code': '', 20 | 'storage': {}, 21 | } 22 | N_ACCOUNT_EMPTY = Account(**{ 23 | 'balance': 0, 24 | 'nonce': 0, 25 | 'code': b'', 26 | 'storage': {}, 27 | }) 28 | 29 | ACCOUNT_SIMPLE = { 30 | 'balance': '0x10', 31 | 'nonce': '0x20', 32 | 'code': '0x74657374', 33 | 'storage': {}, 34 | } 35 | N_ACCOUNT_SIMPLE = Account(**{ 36 | 'balance': 16, 37 | 'nonce': 32, 38 | 'code': b'test', 39 | 'storage': {}, 40 | }) 41 | 42 | ACCOUNT_STORAGE = { 43 | 'balance': '0x10', 44 | 'nonce': '0x20', 45 | 'code': '', 46 | 'storage': {'0x01': '0x74657374'}, 47 | } 48 | N_ACCOUNT_STORAGE = Account(**{ 49 | 'balance': 16, 50 | 'nonce': 32, 51 | 'code': b'', 52 | 'storage': {1: 1952805748}, 53 | }) 54 | 55 | 56 | @pytest.mark.parametrize( 57 | 'genesis_config,expected', 58 | ( 59 | ({}, {}), 60 | ({'accounts': {}}, {}), 61 | ({'accounts': {ADDRESS_HEX: ACCOUNT_EMPTY}}, {ADDRESS: N_ACCOUNT_EMPTY}), 62 | ({'accounts': {ADDRESS_HEX: ACCOUNT_SIMPLE}}, {ADDRESS: N_ACCOUNT_SIMPLE}), 63 | ({'accounts': {ADDRESS_HEX: ACCOUNT_STORAGE}}, {ADDRESS: N_ACCOUNT_STORAGE}), 64 | ), 65 | ) 66 | def test_eip1085_extract_genesis_state(genesis_config, expected): 67 | actual = extract_genesis_state(genesis_config) 68 | assert actual == expected 69 | -------------------------------------------------------------------------------- /trinity/_utils/humanize.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Tuple 2 | 3 | from eth_utils.toolz import sliding_window 4 | 5 | 6 | def _find_breakpoints(*values: int) -> Iterable[int]: 7 | yield 0 8 | for index, (left, right) in enumerate(sliding_window(2, values), 1): 9 | if left + 1 == right: 10 | continue 11 | else: 12 | yield index 13 | yield len(values) 14 | 15 | 16 | def _extract_integer_ranges(*values: int) -> Iterable[Tuple[int, int]]: 17 | """ 18 | Take a sequence of integers which is expected to be ordered and return the 19 | most concise definition of the sequence in terms of integer ranges. 20 | 21 | - fn(1, 2, 3) -> ((1, 3),) 22 | - fn(1, 2, 3, 7, 8, 9) -> ((1, 3), (7, 9)) 23 | - fn(1, 7, 8, 9) -> ((1, 1), (7, 9)) 24 | """ 25 | for left, right in sliding_window(2, _find_breakpoints(*values)): 26 | chunk = values[left:right] 27 | yield chunk[0], chunk[-1] 28 | 29 | 30 | def _humanize_range(bounds: Tuple[int, int]) -> str: 31 | left, right = bounds 32 | if left == right: 33 | return str(left) 34 | else: 35 | return f'{left}-{right}' 36 | 37 | 38 | def humanize_integer_sequence(values_iter: Iterable[int]) -> str: 39 | """ 40 | Return a human readable string that attempts to concisely define a sequence 41 | of integers. 42 | 43 | - fn((1, 2, 3)) -> '1-3' 44 | - fn((1, 2, 3, 7, 8, 9)) -> '1-3|7-9' 45 | - fn((1, 2, 3, 5, 7, 8, 9)) -> '1-3|5|7-9' 46 | - fn((1, 7, 8, 9)) -> '1|7-9' 47 | """ 48 | values = tuple(values_iter) 49 | if not values: 50 | return "(empty)" 51 | else: 52 | return '|'.join(map(_humanize_range, _extract_integer_ranges(*values))) 53 | -------------------------------------------------------------------------------- /trinity/_utils/bloom.py: -------------------------------------------------------------------------------- 1 | import collections 2 | from typing import Deque 3 | 4 | from bloom_filter import ( 5 | BloomFilter 6 | ) 7 | 8 | 9 | class RollingBloom: 10 | def __init__(self, generation_size: int, max_generations: int) -> None: 11 | if generation_size < 1: 12 | raise ValueError(f"generation_size must be a positive integer: got {generation_size}") 13 | if max_generations < 2: 14 | raise ValueError(f"max_generations must be 2 or more: got {max_generations}") 15 | self._max_bloom_elements = generation_size 16 | self._max_history = max_generations - 1 17 | self._history: Deque[BloomFilter] = collections.deque() 18 | self._active = BloomFilter(max_elements=self._max_bloom_elements) 19 | self._items_in_active = 0 20 | 21 | def add(self, key: bytes) -> None: 22 | # before adding the value check if the active bloom filter is full. If 23 | # so, push it into the history and make a new one. 24 | if self._items_in_active >= self._max_bloom_elements: 25 | self._history.appendleft(self._active) 26 | self._active = BloomFilter(max_elements=self._max_bloom_elements) 27 | self._items_in_active = 0 28 | 29 | # discard any old history that is older than the number of 30 | # generations we should be retaining. 31 | while len(self._history) > self._max_history: 32 | self._history.pop() 33 | 34 | self._active.add(key) 35 | self._items_in_active += 1 36 | 37 | def __contains__(self, key: bytes) -> bool: 38 | if key in self._active: 39 | return True 40 | return any(key in bloom for bloom in self._history) 41 | -------------------------------------------------------------------------------- /trinity/_utils/validation.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | from typing import ( 3 | Any, 4 | Dict, 5 | ) 6 | 7 | from eth_utils import ( 8 | is_address, 9 | ) 10 | 11 | from eth.abc import ( 12 | VirtualMachineAPI, 13 | ) 14 | 15 | 16 | FORBIDDEN_KEYS = {'v', 'r', 's', 'nonce'} 17 | DERIVED_KEYS = {'from'} 18 | RENAMED_KEYS = {'gas_price': 'gasPrice'} 19 | 20 | 21 | def validate_transaction_gas_estimation_dict(transaction_dict: Dict[str, Any], 22 | vm: VirtualMachineAPI) -> None: 23 | """Validate a transaction dictionary supplied for an RPC method call""" 24 | transaction_signature = inspect.signature(vm.get_transaction_builder().new_transaction) 25 | 26 | all_keys = set(transaction_signature.parameters.keys()) 27 | allowed_keys = all_keys.difference(FORBIDDEN_KEYS).union(DERIVED_KEYS) 28 | spec_keys = set(RENAMED_KEYS.get(field_name, field_name) for field_name in allowed_keys) 29 | 30 | superfluous_keys = set(transaction_dict).difference(spec_keys) 31 | 32 | if superfluous_keys: 33 | raise ValueError( 34 | "The following invalid fields were given in a transaction: %r. Only %r are allowed" % ( 35 | list(sorted(superfluous_keys)), 36 | list(sorted(spec_keys)), 37 | ) 38 | ) 39 | 40 | 41 | def validate_transaction_call_dict(transaction_dict: Dict[str, Any], vm: VirtualMachineAPI) -> None: 42 | validate_transaction_gas_estimation_dict(transaction_dict, vm) 43 | 44 | # 'to' is required in a call, but not a gas estimation 45 | if not is_address(transaction_dict.get('to', None)): 46 | raise ValueError("The 'to' field must be supplied when getting the result of a transaction") 47 | -------------------------------------------------------------------------------- /tests/core/cli/test_log_level_configuration.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from trinity.cli_parser import ( 4 | parser, 5 | LOG_LEVEL_CHOICES, 6 | ) 7 | 8 | 9 | def test_cli_log_level_not_specified(): 10 | ns = parser.parse_args([]) 11 | assert ns.log_levels is None 12 | 13 | 14 | @pytest.mark.parametrize( 15 | 'level,expected', 16 | LOG_LEVEL_CHOICES.items(), 17 | ) 18 | def test_cli_log_level_global_values(level, expected): 19 | ns = parser.parse_args(['--log-level', level]) 20 | assert ns.log_levels == {None: expected} 21 | 22 | 23 | @pytest.mark.parametrize( 24 | 'level,expected', 25 | LOG_LEVEL_CHOICES.items(), 26 | ) 27 | def test_cli_log_level_module_value(level, expected): 28 | ns = parser.parse_args(['--log-level', "module={0}".format(level)]) 29 | assert ns.log_levels == {'module': expected} 30 | 31 | 32 | def test_cli_log_level_error_for_multiple_globals(capsys): 33 | with pytest.raises(SystemExit): 34 | parser.parse_args([ 35 | '--log-level', 'DEBUG', 36 | '--log-level', 'modue=DEBUG', 37 | '--log-level', 'ERROR', 38 | ]) 39 | # this prevents the messaging that this error prints to stdout from 40 | # escaping the test run. 41 | capsys.readouterr() 42 | 43 | 44 | def test_cli_log_level_error_for_repeated_name(capsys): 45 | with pytest.raises(SystemExit): 46 | parser.parse_args([ 47 | '--log-level', 'DEBUG', 48 | '--log-level', 'modue_a=DEBUG', 49 | '--log-level', 'modue_b=DEBUG', 50 | '--log-level', 'modue_a=DEBUG', 51 | ]) 52 | # this prevents the messaging that this error prints to stdout from 53 | # escaping the test run. 54 | capsys.readouterr() 55 | -------------------------------------------------------------------------------- /trinity/rpc/modules/evm.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | Any 3 | ) 4 | 5 | from eth_utils import ( 6 | encode_hex, 7 | ) 8 | from lahja import ( 9 | BroadcastConfig, 10 | ) 11 | 12 | from eth.abc import ChainAPI 13 | from eth.tools._utils.normalization import ( 14 | normalize_block, 15 | normalize_blockchain_fixtures, 16 | ) 17 | from eth.tools.fixtures import ( 18 | apply_fixture_block_to_chain, 19 | new_chain_from_fixture, 20 | ) 21 | 22 | from trinity.rpc.format import ( 23 | format_params, 24 | ) 25 | from trinity.rpc.modules import ( 26 | ChainReplacementEvent, 27 | Eth1ChainRPCModule, 28 | ) 29 | 30 | 31 | class EVM(Eth1ChainRPCModule): 32 | 33 | @format_params(normalize_blockchain_fixtures) 34 | async def resetToGenesisFixture(self, chain_info: Any) -> ChainAPI: 35 | """ 36 | This method is a special case. It returns a new chain object 37 | which is then replaced inside :class:`~trinity.rpc.main.RPCServer` 38 | for all future calls. 39 | """ 40 | chain = new_chain_from_fixture(chain_info, type(self.chain)) 41 | 42 | await self.event_bus.broadcast( 43 | ChainReplacementEvent(chain), 44 | BroadcastConfig(internal=True) 45 | ) 46 | 47 | return chain 48 | 49 | @format_params(normalize_block) 50 | async def applyBlockFixture(self, block_info: Any) -> str: 51 | """ 52 | This method is a special case. It returns a new chain object 53 | which is then replaced inside :class:`~trinity.rpc.main.RPCServer` 54 | for all future calls. 55 | """ 56 | _, _, rlp_encoded = apply_fixture_block_to_chain(block_info, self.chain) 57 | return encode_hex(rlp_encoded) 58 | -------------------------------------------------------------------------------- /trinity/protocol/eth/normalizers.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | Iterable, 3 | ) 4 | 5 | from eth_utils import ( 6 | to_tuple, 7 | ) 8 | 9 | from eth_hash.auto import keccak 10 | import rlp 11 | 12 | from p2p.exchange import BaseNormalizer 13 | 14 | from trinity._utils.trie import make_trie_root_and_nodes 15 | from trinity.protocol.common.typing import ( 16 | BlockBodyBundle, 17 | BlockBodyBundles, 18 | NodeDataBundles, 19 | ReceiptsBundles, 20 | ) 21 | 22 | from .commands import ( 23 | BlockBodiesV65, 24 | NodeDataV65, 25 | ReceiptsV65, 26 | ) 27 | 28 | 29 | class GetNodeDataNormalizer(BaseNormalizer[NodeDataV65, NodeDataBundles]): 30 | is_normalization_slow = True 31 | 32 | def normalize_result(self, cmd: NodeDataV65) -> NodeDataBundles: 33 | node_keys = map(keccak, cmd.payload) 34 | result = tuple(zip(node_keys, cmd.payload)) 35 | return result 36 | 37 | 38 | class ReceiptsNormalizer(BaseNormalizer[ReceiptsV65, ReceiptsBundles]): 39 | is_normalization_slow = True 40 | 41 | def normalize_result(self, cmd: ReceiptsV65) -> ReceiptsBundles: 42 | trie_roots_and_data = map(make_trie_root_and_nodes, cmd.payload) 43 | return tuple(zip(cmd.payload, trie_roots_and_data)) 44 | 45 | 46 | class GetBlockBodiesNormalizer(BaseNormalizer[BlockBodiesV65, BlockBodyBundles]): 47 | is_normalization_slow = True 48 | 49 | @to_tuple 50 | def normalize_result(self, cmd: BlockBodiesV65) -> Iterable[BlockBodyBundle]: 51 | for body in cmd.payload: 52 | uncle_hashes = keccak(rlp.encode(body.uncles)) 53 | transaction_root_and_nodes = make_trie_root_and_nodes(body.transactions) 54 | yield body, transaction_root_and_nodes, uncle_hashes 55 | -------------------------------------------------------------------------------- /tests/core/header-utils/test_block_number_sequence_builder.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from eth.constants import UINT_256_MAX 4 | 5 | from trinity.exceptions import OversizeObject 6 | from trinity._utils.headers import sequence_builder 7 | 8 | 9 | @pytest.mark.parametrize( 10 | 'start_num, max_length, skip, reverse, expected', 11 | ( 12 | (0, 0, 0, False, ()), 13 | (0, 0, 0, True, ()), 14 | (0, 0, 1, False, ()), 15 | (0, 0, 1, True, ()), 16 | (0, 1, 0, False, (0, )), 17 | (0, 1, 0, True, (0, )), 18 | (0, 1, 1, False, (0, )), 19 | (0, 1, 1, True, (0, )), 20 | (9, 1, 0, False, (9, )), 21 | (9, 1, 0, True, (9, )), 22 | (1, 3, 0, False, (1, 2, 3)), 23 | (0, 5, 1, False, (0, 2, 4, 6, 8)), 24 | (9, 5, 1, True, (9, 7, 5, 3, 1)), 25 | (1, 9, 0, True, (1, 0)), 26 | (UINT_256_MAX - 1, 4, 0, False, (UINT_256_MAX - 1, UINT_256_MAX, )), 27 | # can handle mildly large numbers 28 | (400000000, 1000000, 0, False, tuple(range(400000000, 401000000))), 29 | ), 30 | ) 31 | def test_sequence(start_num, max_length, skip, reverse, expected): 32 | assert sequence_builder(start_num, max_length, skip, reverse) == expected 33 | 34 | 35 | TOO_LONG = 2000000 36 | 37 | 38 | @pytest.mark.parametrize('reverse', (True, False)) 39 | @pytest.mark.parametrize('start_num', (0, 400000000)) 40 | @pytest.mark.parametrize('skip', (0, 10000)) 41 | def test_oversize_sequence(start_num, skip, reverse): 42 | # Instead of using the specific constant, just use a rough TOO_LONG number 43 | # We don't need to worry about edge cases for this gut check 44 | with pytest.raises(OversizeObject): 45 | sequence_builder(start_num, TOO_LONG, skip, reverse) 46 | -------------------------------------------------------------------------------- /trinity/sync/light/chain.py: -------------------------------------------------------------------------------- 1 | from async_service import Service 2 | 3 | from trinity.chains.base import AsyncChainAPI 4 | from trinity.db.eth1.header import BaseAsyncHeaderDB 5 | from trinity.protocol.les.peer import LESPeerPool 6 | from trinity.protocol.les.sync import LightHeaderChainSyncer 7 | from trinity._utils.logging import get_logger 8 | from trinity.sync.common.headers import persist_headers 9 | 10 | 11 | class LightChainSyncer(Service): 12 | def __init__(self, chain: AsyncChainAPI, db: BaseAsyncHeaderDB, peer_pool: LESPeerPool) -> None: 13 | self.logger = get_logger('trinity.sync.light.chain.LightChainSyncer') 14 | self._db = db 15 | self._header_syncer = LightHeaderChainSyncer(chain, db, peer_pool) 16 | 17 | async def run(self) -> None: 18 | head = await self._db.coro_get_canonical_head() 19 | self.logger.info("Starting light sync; current head: %s", head) 20 | 21 | self.manager.run_daemon_child_service(self._header_syncer) 22 | self.manager.run_daemon_task(self._persist_headers) 23 | # run sync until cancelled 24 | await self.manager.wait_finished() 25 | 26 | async def _persist_headers(self) -> None: 27 | 28 | async for persist_info in persist_headers(self.logger, self._db, self._header_syncer): 29 | 30 | if len(persist_info.new_canon_headers): 31 | head = persist_info.new_canon_headers[-1] 32 | else: 33 | head = await self._db.coro_get_canonical_head() 34 | self.logger.info( 35 | "Imported %d headers in %0.2f seconds, new head: %s", 36 | len(persist_info.imported_headers), 37 | persist_info.elapsed_time, 38 | head, 39 | ) 40 | -------------------------------------------------------------------------------- /trinity/_utils/queues.py: -------------------------------------------------------------------------------- 1 | from asyncio import ( # noqa: F401 2 | Queue, 3 | ) 4 | from typing import ( 5 | Tuple, 6 | TypeVar, 7 | ) 8 | 9 | from eth_utils import ( 10 | ValidationError, 11 | ) 12 | 13 | TQueueItem = TypeVar('TQueueItem') 14 | 15 | 16 | async def queue_get_batch( 17 | queue: 'Queue[TQueueItem]', 18 | max_results: int = None) -> Tuple[TQueueItem, ...]: 19 | """ 20 | Wait until at least one result is available, and return it and any 21 | other results that are immediately available, up to max_results. 22 | """ 23 | if max_results is not None and max_results < 1: 24 | raise ValidationError("Must request at least one item from a queue, not {max_results!r}") 25 | 26 | # if the queue is empty, wait until at least one item is available 27 | if queue.empty(): 28 | first_item = await queue.get() 29 | else: 30 | first_item = queue.get_nowait() 31 | 32 | # In order to return from queue_get_batch() as soon as possible, never await again. 33 | # Instead, take only the items that are already available. 34 | if max_results is None: 35 | remaining_count = None 36 | else: 37 | remaining_count = max_results - 1 38 | remaining_items = queue_get_nowait(queue, remaining_count) 39 | 40 | # Combine the first and remaining items 41 | return (first_item, ) + remaining_items 42 | 43 | 44 | def queue_get_nowait(queue: 'Queue[TQueueItem]', max_results: int = None) -> Tuple[TQueueItem, ...]: 45 | # How many results do we want? 46 | available = queue.qsize() 47 | if max_results is None: 48 | num_items = available 49 | else: 50 | num_items = min((available, max_results)) 51 | 52 | return tuple(queue.get_nowait() for _ in range(num_items)) 53 | -------------------------------------------------------------------------------- /tests/integration/helpers.py: -------------------------------------------------------------------------------- 1 | from trinity.tools.async_process_runner import AsyncProcessRunner 2 | 3 | 4 | async def run_command_and_detect_errors(command, time): 5 | """ 6 | Run the given ``command`` on the given ``async_process_runner`` for ``time`` seconds and 7 | throw an Exception in case any unresolved Exceptions are detected in the output of the command. 8 | """ 9 | async with AsyncProcessRunner.run(command, timeout_sec=time) as runner: 10 | await scan_for_errors(runner.stderr) 11 | 12 | 13 | async def scan_for_errors(async_iterable): 14 | """ 15 | Consume the output produced by the async iterable and throw if it contains hints of an 16 | uncaught exception. 17 | """ 18 | 19 | error_trigger = ( 20 | "Exception ignored", 21 | "exception was never retrieved", 22 | "finished unexpectedly", 23 | "ResourceWarning", 24 | "Task was destroyed but it is pending", 25 | "Traceback (most recent call last)", 26 | ) 27 | 28 | lines_since_error = 0 29 | last_error = None 30 | async for line in async_iterable: 31 | 32 | # We detect errors by some string at the beginning of the Traceback and keep 33 | # counting lines from there to be able to read and report more valuable info 34 | if any(trigger in line for trigger in error_trigger) and lines_since_error == 0: 35 | lines_since_error = 1 36 | last_error = line 37 | elif lines_since_error > 0: 38 | lines_since_error += 1 39 | 40 | # Keep on listening for output for a maxmimum of 100 lines after the error 41 | if lines_since_error >= 100: 42 | break 43 | 44 | if last_error is not None: 45 | raise Exception(f"Exception during Trinity boot detected: {last_error}") 46 | -------------------------------------------------------------------------------- /trinity/_utils/decorators.py: -------------------------------------------------------------------------------- 1 | import functools 2 | from typing import ( 3 | Any, 4 | Awaitable, 5 | Callable, 6 | Type, 7 | TypeVar, 8 | ) 9 | 10 | 11 | TAsyncFn = TypeVar("TAsyncFn", bound=Callable[..., Awaitable[None]]) 12 | 13 | # It's only permitted to ignore an exception and return None if 14 | # the wrapped function always returns None 15 | TFn = TypeVar("TFn", bound=Callable[..., None]) 16 | 17 | 18 | def async_suppress_exceptions( 19 | *exception_types: Type[BaseException]) -> Callable[[TAsyncFn], TAsyncFn]: 20 | 21 | def _suppress_decorator(func: TAsyncFn) -> TAsyncFn: 22 | async def _suppressed_func(*args: Any, **kwargs: Any) -> None: 23 | try: 24 | await func(*args, **kwargs) 25 | except exception_types: 26 | # these exceptions are expected, and require no handling 27 | pass 28 | 29 | return _suppressed_func # type: ignore 30 | 31 | return _suppress_decorator 32 | 33 | 34 | def suppress_exceptions(*exception_types: Type[BaseException]) -> Callable[[TFn], TFn]: 35 | """ 36 | Sometimes, rarely, it's okay for a function to just stop running, 37 | in the case of certain exceptions. Use this decorator, defining the 38 | exceptions that are allowed to simply exit. 39 | """ 40 | def _suppress_decorator(func: TFn) -> TFn: 41 | 42 | @functools.wraps(func) 43 | def _suppressed_func(*args: Any, **kwargs: Any) -> None: 44 | try: 45 | func(*args, **kwargs) 46 | except exception_types: 47 | # these exceptions are expected, and require no handling 48 | pass 49 | 50 | return None 51 | 52 | return _suppressed_func # type: ignore 53 | 54 | return _suppress_decorator 55 | -------------------------------------------------------------------------------- /trinity/components/builtin/metrics/service/noop.py: -------------------------------------------------------------------------------- 1 | from async_service import Service 2 | 3 | from trinity.components.builtin.metrics.abc import MetricsServiceAPI 4 | from trinity.components.builtin.metrics.registry import NoopMetricsRegistry 5 | from trinity._utils.logging import get_logger 6 | 7 | 8 | class NoopMetricsService(Service, MetricsServiceAPI): 9 | """ 10 | A ``MetricsServiceAPI`` implementation that provides a stub registry where every action results 11 | in a noop. Intended to be used when collecting of metrics is disabled. Every collected metric 12 | will only incur the cost of a noop. 13 | """ 14 | 15 | logger = get_logger('trinity.components.builtin.metrics.NoopMetricsService') 16 | 17 | def __init__(self, 18 | influx_server: str = '', 19 | influx_user: str = '', 20 | influx_password: str = '', 21 | influx_database: str = '', 22 | host: str = '', 23 | reporting_frequency: int = 10): 24 | 25 | self._registry = NoopMetricsRegistry() 26 | 27 | @property 28 | def registry(self) -> NoopMetricsRegistry: 29 | """ 30 | Return the :class:`trinity.components.builtin.metrics.registry.NoopMetricsRegistry` at which 31 | metrics instruments can be registered and retrieved. 32 | """ 33 | return self._registry 34 | 35 | async def run(self) -> None: 36 | self.logger.info("Running NoopMetricsService") 37 | await self.manager.wait_finished() 38 | 39 | async def continuously_report(self) -> None: 40 | pass 41 | 42 | async def send_annotation(self, annotation_data: str) -> None: 43 | pass 44 | 45 | 46 | NOOP_METRICS_SERVICE = NoopMetricsService() 47 | NOOP_METRICS_REGISTRY = NoopMetricsRegistry() 48 | -------------------------------------------------------------------------------- /tests/core/test_ipc_log_listener.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | import logging 3 | from multiprocessing import Process 4 | from pathlib import Path 5 | import uuid 6 | 7 | import pytest 8 | 9 | from trinity._utils.logging import IPCListener, IPCHandler 10 | 11 | 12 | @pytest.fixture 13 | def ipc_path(): 14 | with tempfile.TemporaryDirectory() as dir: 15 | yield Path(dir) / "logging.ipc" 16 | 17 | 18 | def test_queued_logging(ipc_path): 19 | class HandlerForTest(logging.Handler): 20 | def __init__(self): 21 | self.logs = [] 22 | super().__init__() 23 | 24 | def handle(self, record): 25 | self.logs.append(record) 26 | 27 | def do_other_process_logging(ipc_path): 28 | queue_handler = IPCHandler.connect(ipc_path) 29 | queue_handler.setLevel(logging.DEBUG) 30 | logger = logging.getLogger(str(uuid.uuid4())) 31 | logger.addHandler(queue_handler) 32 | logger.setLevel(logging.DEBUG) 33 | 34 | logger.error('error log') 35 | logger.info('info log') 36 | logger.debug('debug log') 37 | 38 | queue_handler.close() 39 | 40 | proc = Process(target=do_other_process_logging, args=(ipc_path,)) 41 | 42 | logger = logging.getLogger(str(uuid.uuid4())) 43 | 44 | handler = HandlerForTest() 45 | handler.setLevel(logging.DEBUG) 46 | logger.addHandler(handler) 47 | logger.setLevel(logging.DEBUG) 48 | 49 | queue_listener = IPCListener(handler) 50 | 51 | with queue_listener.run(ipc_path): 52 | assert len(handler.logs) == 0 53 | proc.start() 54 | proc.join() 55 | 56 | assert len(handler.logs) == 3 57 | 58 | error_log, info_log, debug_log = handler.logs 59 | 60 | assert 'error log' in error_log.message 61 | assert 'info log' in info_log.message 62 | assert 'debug log' in debug_log.message 63 | -------------------------------------------------------------------------------- /tests/core/configuration/test_eth1_app_config_object.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from trinity.config import ( 4 | Eth1AppConfig, 5 | Eth1DbMode, 6 | TrinityConfig, 7 | ) 8 | from trinity.nodes.full import ( 9 | FullNode, 10 | ) 11 | from trinity.nodes.light import ( 12 | LightNode, 13 | ) 14 | 15 | 16 | @pytest.mark.parametrize( 17 | "app_identifier, sync_mode, expected_db_path, expected_db_name", 18 | ( 19 | ("eth1", "light", "chain-eth1", "light"), 20 | ("eth1", "fast", "chain-eth1", "full"), 21 | ("", "light", "chain", "light"), 22 | ("", "fast", "chain", "full"), 23 | ), 24 | ) 25 | def test_computed_database_dir(app_identifier, sync_mode, expected_db_path, expected_db_name): 26 | trinity_config = TrinityConfig(network_id=1, app_identifier=app_identifier) 27 | eth1_app_config = Eth1AppConfig(trinity_config, sync_mode) 28 | 29 | assert eth1_app_config.database_dir == trinity_config.data_dir / expected_db_path / expected_db_name # noqa: E501 30 | 31 | 32 | @pytest.mark.parametrize( 33 | "sync_mode, expected_full_db, expected_node_class", 34 | ( 35 | ("light", False, LightNode), 36 | ("fast", True, FullNode), 37 | ("full", True, FullNode), 38 | ("warp", True, FullNode), 39 | ), 40 | ) 41 | def test_sync_mode_effect_on_db_and_node_type(sync_mode, 42 | expected_full_db, 43 | expected_node_class): 44 | 45 | trinity_config = TrinityConfig(network_id=1) 46 | eth1_app_config = Eth1AppConfig(trinity_config, sync_mode) 47 | assert eth1_app_config.sync_mode == sync_mode 48 | assert eth1_app_config.node_class == expected_node_class 49 | if expected_full_db: 50 | assert eth1_app_config.database_mode is Eth1DbMode.FULL 51 | else: 52 | assert eth1_app_config.database_mode is Eth1DbMode.LIGHT 53 | --------------------------------------------------------------------------------