Skip to content

Commit fb1bfa3

Browse files
authored
Live test fixes (#260)
* Add missing gas price parameter * Make loglevel configurable via environment * Config file is optional, because the application can be configured via Environment as well. * Improve parameter validation & implicitly typecast int and float values. Invalid values will throw a ValueError anyway. * Make RPC timeouts configurable * Sort config keys * Update Example Config with Ropsten Test Values * Formatting * Fix tests * Fix float check & change poll interval to int * Make event fetcher a bit more verbose on info level * Update Test-Token Address * Use Checksummed address * Add missing library versions to constraints.txt * Rename token_contract_address to foreign_chain_token_contract_address * Fix Nonce Calculation, GasPrice & ChainId * Cleanup pre-commit, so that just python3 is required * Fix tests, but this will likely not work in production (chainId) * Update example to reflect new config parameter name for token bridge * Formatting * Upgrade web3 from 5.0.0b2 to 5.0.0b4 (#263) ethpm has been integrated into web3. That means we can get rid of the workaround for missing dependencies. Before upgrading to this version, the standalone ethpm package has to be manually uninstalled. The following PR has been merged into 5.0.0b4, which is the primary reason for the upgrade: ethereum/web3.py#1378
1 parent 1c0a1ef commit fb1bfa3

File tree

10 files changed

+97
-50
lines changed

10 files changed

+97
-50
lines changed

.pre-commit-config.yaml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
default_language_version:
2+
python: python3
3+
14
repos:
25
- repo: https://github.com/trustlines-protocol/more-pre-commit-hooks.git
36
rev: 1edc6d3ab9380e17eb01a601551283cf7bc23f2b
@@ -17,25 +20,21 @@ repos:
1720
rev: 19.3b0
1821
hooks:
1922
- id: black
20-
language_version: python3.6
2123

2224
- repo: git://github.com/pre-commit/pre-commit-hooks
2325
rev: v2.2.3
2426
hooks:
2527
- id: check-added-large-files
2628
- id: check-ast
27-
language_version: python3.6
2829
- id: check-byte-order-marker
2930
- id: check-case-conflict
3031
- id: check-json
3132
- id: check-merge-conflict
3233
- id: check-yaml
3334
- id: debug-statements
34-
language_version: python3.6
3535
- id: end-of-file-fixer
3636
exclude: ^contracts/contracts/lib/.*\.sol|contracts/contracts/token/TrustlinesNetworkToken.sol$
3737
- id: flake8
38-
language_version: python3.6
3938
additional_dependencies: ["flake8-string-format", "flake8-per-file-ignores"]
4039
- id: trailing-whitespace
4140
exclude: ^contracts/contracts/lib/.*\.sol$

constraints.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Click==7.0
1414
contract-deploy-tools==0.4.3
1515
cryptography==2.7
1616
cytoolz==0.9.0.1
17+
decorator==4.4.0
1718
entrypoints==0.3
1819
eth-abi==2.0.0b9
1920
eth-account==0.4.0
@@ -25,7 +26,6 @@ eth-rlp==0.1.2
2526
eth-tester==0.1.0b39
2627
eth-typing==2.1.0
2728
eth-utils==1.6.0
28-
ethpm==0.1.4a19
2929
flake8==3.7.7
3030
flake8-polyfill==1.0.2
3131
gevent==1.4.0
@@ -57,7 +57,6 @@ protobuf==3.8.0
5757
py==1.8.0
5858
py-ecc==1.7.0
5959
py-evm==0.2.0a42
60-
py-geth==2.1.0
6160
py-solc==3.2.0
6261
pycodestyle==2.5.0
6362
pycparser==2.19
@@ -67,8 +66,8 @@ pyflakes==2.1.1
6766
pyparsing==2.4.0
6867
pysha3==1.0.2
6968
pytest==4.6.2
70-
pytest-ethereum==0.1.3a6
7169
python-dateutil==2.8.0
70+
python-dotenv==0.10.3
7271
pytzdata==2019.1
7372
PyYAML==5.1
7473
requests==2.22.0
@@ -82,10 +81,11 @@ toolz==0.9.0
8281
trie==1.4.0
8382
typed-ast==1.3.5
8483
urllib3==1.25.3
84+
validators==0.13.0
8585
varint==1.0.2
8686
virtualenv==16.6.0
8787
wcwidth==0.1.7
88-
web3==5.0.0b2
88+
web3==5.0.0b4
8989
websockets==7.0
9090
wheel==0.33.4
9191
zipp==0.5.1

requirements-dev.txt

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,3 @@ pep8-naming
55
pre-commit
66
pytest
77
setuptools_scm
8-
9-
# The following three dependencies are missing because ethpm has a
10-
# dependency on web3[tester]. This is a hacky workaround. For details
11-
# visit:
12-
# https://github.com/trustlines-protocol/contract-deploy-tools/pull/38#issuecomment-499412276
13-
14-
py-geth
15-
eth-tester[py-evm]
16-
pytest-ethereum

tools/bridge/.env.example

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
# The Home RPC Address. Should point to a Trustlines RPC Node
2-
HOME_RPC_URL=http://node_home:8545
2+
HOME_RPC_URL=http://localhost:8545
33

44
# The Foreign RPC Address. Should point to a Mainnet / Ropsten RPC Node
5-
FOREIGN_RPC_URL=http://node_foreign:8545
5+
FOREIGN_RPC_URL=http://parity.ropsten.ethnodes.brainbot.com:8545
66

77
# The Address of the Token Contract on the Foreign Network
8-
TOKEN_CONTRACT_ADDRESS=0x20fE562d797A42Dcb3399062AE9546cd06f63280
8+
FOREIGN_CHAIN_TOKEN_CONTRACT_ADDRESS=0xCd7464985f3b5dbD96e12da9b018BA45a64256E6
99

1010
# The Address of the Bridge Contract on the Foreign Network
11-
FOREIGN_BRIDGE_CONTRACT_ADDRESS=0x83a30dD54a744c60190548af8d983719116eff4a
11+
FOREIGN_BRIDGE_CONTRACT_ADDRESS=0x8d25a6C7685ca80fF110b2B3CEDbcd520FdE8Dd3
12+
13+
# The Deployment Block Number of the Bridge Contract on the Foreign Network
14+
FOREIGN_CHAIN_EVENT_FETCH_START_BLOCK_NUMBER=6057718
15+
16+
# The Address of the Bridge Contract on the Home Network
17+
HOME_BRIDGE_CONTRACT_ADDRESS=0x321b427210E706747129cfF9BC6A33607Ed86dff
18+
19+
# The Deployment Block Number of the Bridge Contract on the Home Network
20+
HOME_CHAIN_EVENT_FETCH_START_BLOCK_NUMBER=3395992
21+
22+
VALIDATOR_PRIVATE_KEY=

tools/bridge/bridge/config.py

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
)
1414
from eth_utils.toolz import merge
1515

16+
import validators
17+
1618
from eth_keys.constants import SECPK1_N
1719

1820
from dotenv import load_dotenv
@@ -21,25 +23,29 @@
2123

2224

2325
def validate_rpc_url(url: Any) -> str:
24-
if not isinstance(url, str):
26+
if not validators.url(url):
2527
raise ValueError(f"{url} is not a valid RPC url")
2628
return url
2729

2830

29-
def validate_non_negative_integer(number: Any) -> float:
31+
def validate_non_negative_integer(number: Any) -> int:
32+
if str(number) != str(int(number)):
33+
raise ValueError(f"{number} is not a valid integer")
3034
if not isinstance(number, int):
31-
raise ValueError(f"{number} is not an integer")
35+
number = int(number)
3236
if number < 0:
3337
raise ValueError(f"{number} must be greater than or equal zero")
34-
return int(number)
38+
return number
3539

3640

3741
def validate_positive_float(number: Any) -> float:
38-
if not isinstance(number, (int, float)):
39-
raise ValueError(f"{number} is neither integer nor float")
42+
if str(number) != str(float(number)) and str(number) != str(int(number)):
43+
raise ValueError(f"{number} is not a valid float")
44+
if not isinstance(number, float):
45+
number = float(number)
4046
if number <= 0:
4147
raise ValueError(f"{number} must be positive")
42-
return float(number)
48+
return number
4349

4450

4551
def validate_checksum_address(address: Any) -> bytes:
@@ -67,32 +73,38 @@ def validate_private_key(private_key: Any) -> bytes:
6773

6874
REQUIRED_CONFIG_ENTRIES = [
6975
"home_rpc_url",
70-
"foreign_rpc_url",
71-
"token_contract_address",
7276
"home_bridge_contract_address",
77+
"foreign_rpc_url",
78+
"foreign_chain_token_contract_address",
7379
"foreign_bridge_contract_address",
7480
"validator_private_key",
7581
]
7682

7783
OPTIONAL_CONFIG_ENTRIES_WITH_DEFAULTS: Dict[str, Any] = {
84+
"home_rpc_timeout": 180,
85+
"home_chain_gas_price": 10 * 1000000000, # Gas price is in GWei
7886
"home_chain_max_reorg_depth": 1,
87+
"home_chain_event_fetch_start_block_number": 0,
88+
"foreign_rpc_timeout": 180,
7989
"foreign_chain_max_reorg_depth": 10,
8090
"foreign_chain_event_poll_interval": 5,
8191
"foreign_chain_event_fetch_start_block_number": 0,
82-
"home_chain_event_fetch_start_block_number": 0,
8392
}
8493

8594
CONFIG_ENTRY_VALIDATORS = {
8695
"home_rpc_url": validate_rpc_url,
87-
"foreign_rpc_url": validate_rpc_url,
88-
"token_contract_address": validate_checksum_address,
89-
"home_bridge_contract_address": validate_checksum_address,
90-
"foreign_bridge_contract_address": validate_checksum_address,
96+
"home_rpc_timeout": validate_non_negative_integer,
97+
"home_chain_gas_price": validate_non_negative_integer,
9198
"home_chain_max_reorg_depth": validate_non_negative_integer,
99+
"home_bridge_contract_address": validate_checksum_address,
100+
"home_chain_event_fetch_start_block_number": validate_non_negative_integer,
101+
"foreign_rpc_url": validate_rpc_url,
102+
"foreign_rpc_timeout": validate_non_negative_integer,
92103
"foreign_chain_max_reorg_depth": validate_non_negative_integer,
93-
"foreign_chain_event_poll_interval": validate_positive_float,
104+
"foreign_chain_event_poll_interval": validate_non_negative_integer,
105+
"foreign_chain_token_contract_address": validate_checksum_address,
106+
"foreign_bridge_contract_address": validate_checksum_address,
94107
"foreign_chain_event_fetch_start_block_number": validate_non_negative_integer,
95-
"home_chain_event_fetch_start_block_number": validate_non_negative_integer,
96108
"validator_private_key": validate_private_key,
97109
}
98110

tools/bridge/bridge/confirmation_sender.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def __init__(
3636
self.pending_transaction_queue: Queue[Dict[str, Any]] = Queue()
3737

3838
def get_next_nonce(self):
39-
return self.w3.eth.getTransactionCount(self.address)
39+
return self.w3.eth.getTransactionCount(self.address, "pending")
4040

4141
def run(self):
4242
self.logger.info("Starting")
@@ -58,12 +58,24 @@ def send_confirmation_transactions(self):
5858

5959
def prepare_confirmation_transaction(self, transfer_event):
6060
nonce = self.get_next_nonce()
61+
self.logger.debug(
62+
f"Preparing confirmation transaction for address "
63+
f"{transfer_event.args['from']} for {transfer_event.args.value} "
64+
f"coins (nonce {nonce}, chain {self.w3.eth.chainId})"
65+
)
6166
transaction = self.home_bridge_contract.functions.confirmTransfer(
6267
self.compute_transfer_hash(transfer_event),
6368
transfer_event.transactionHash,
6469
transfer_event.args.value,
6570
transfer_event.args["from"],
66-
).buildTransaction({"gasPrice": self.gas_price, "nonce": nonce})
71+
).buildTransaction(
72+
{
73+
"gasPrice": self.gas_price,
74+
"nonce": nonce,
75+
# "gas": 1000000,
76+
# "chainId": self.w3.eth.chainId, # TODO: This should be obsolete with web3 5.0.0b4
77+
}
78+
)
6779
signed_transaction = self.w3.eth.account.sign_transaction(
6880
transaction, self.private_key
6981
)
@@ -77,9 +89,10 @@ def compute_transfer_hash(self, transfer):
7789
)
7890

7991
def send_confirmation_transaction(self, transaction):
80-
self.logger.info(f"Sending confirmation transaction {transaction}")
8192
self.pending_transaction_queue.put(transaction)
82-
self.w3.eth.sendRawTransaction(transaction.rawTransaction)
93+
tx_hash = self.w3.eth.sendRawTransaction(transaction.rawTransaction)
94+
self.logger.info(f"Sent confirmation transaction {tx_hash.hex()}")
95+
return tx_hash
8396

8497
def watch_pending_transactions(self):
8598
while True:
@@ -95,7 +108,7 @@ def clear_confirmed_transactions(self):
95108
receipt = self.w3.eth.getTransactionReceipt(oldest_pending_transaction.hash)
96109
if receipt and receipt.blockNumber <= confirmation_threshold:
97110
self.logger.info(
98-
f"Transaction has been confirmed: {oldest_pending_transaction}"
111+
f"Transaction has been confirmed: {oldest_pending_transaction.hash.hex()}"
99112
)
100113
confirmed_transaction = (
101114
self.pending_transaction_queue.get()

tools/bridge/bridge/event_fetcher.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,10 @@ def fetch_events_in_range(
8484
argument_filters=self.event_argument_filter,
8585
)
8686

87-
self.logger.debug(f"Found {len(events)} events.")
87+
if len(events) > 0:
88+
self.logger.info(f"Found {len(events)} events.")
89+
else:
90+
self.logger.debug(f"Found {len(events)} events.")
8891

8992
for event in events:
9093
self.event_queue.put(event)

tools/bridge/bridge/main.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
monkey.patch_all() # noqa: E402
44

55
import logging
6+
import os
67

78
import gevent
89
from gevent import Greenlet
@@ -25,7 +26,7 @@
2526
"--config",
2627
"config_path",
2728
type=click.Path(exists=True),
28-
required=True,
29+
required=False,
2930
help="Path to a config file",
3031
)
3132
def main(config_path: str) -> None:
@@ -37,7 +38,9 @@ def main(config_path: str) -> None:
3738
See .env.example and config.py for valid configuration options and defaults.
3839
"""
3940

40-
logging.basicConfig(level=logging.INFO)
41+
logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO").upper())
42+
43+
logging.info("Starting Trustlines Bridge Validation Server")
4144

4245
try:
4346
config = load_config(config_path)
@@ -46,8 +49,18 @@ def main(config_path: str) -> None:
4649
except ValueError as value_error:
4750
raise click.UsageError(f"Invalid config file: {value_error}") from value_error
4851

49-
w3_foreign = Web3(HTTPProvider(config["foreign_rpc_url"]))
50-
w3_home = Web3(HTTPProvider(config["home_rpc_url"]))
52+
w3_foreign = Web3(
53+
HTTPProvider(
54+
config["foreign_rpc_url"],
55+
request_kwargs={"timeout": config["foreign_rpc_timeout"]},
56+
)
57+
)
58+
w3_home = Web3(
59+
HTTPProvider(
60+
config["home_rpc_url"],
61+
request_kwargs={"timeout": config["home_rpc_timeout"]},
62+
)
63+
)
5164

5265
home_bridge_contract = w3_home.eth.contract(
5366
address=config["home_bridge_contract_address"], abi=HOME_BRIDGE_ABI
@@ -57,7 +70,7 @@ def main(config_path: str) -> None:
5770

5871
transfer_event_fetcher = EventFetcher(
5972
web3=w3_foreign,
60-
contract_address=config["token_contract_address"],
73+
contract_address=config["foreign_chain_token_contract_address"],
6174
contract_abi=MINIMAL_ERC20_TOKEN_ABI,
6275
event_name="Transfer",
6376
event_argument_filter={"to": config["foreign_bridge_contract_address"]},

tools/bridge/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ toml
55
web3
66
contract-deploy-tools
77
python-dotenv
8+
validators
89
-r ../../requirements-dev.txt

tools/bridge/tests/test_config.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def test_validate_rpc_url():
3838

3939

4040
def test_validate_invalid_rpc_url():
41-
with pytest.raises(ValueError):
41+
with pytest.raises(TypeError):
4242
validate_rpc_url(1)
4343

4444

@@ -60,9 +60,13 @@ def test_validate_positive_float():
6060
validate_positive_float(1.1)
6161

6262

63+
def test_validate_positive_float_int():
64+
validate_positive_float(5)
65+
66+
6367
def test_validate_positive_float_false_type():
6468
with pytest.raises(ValueError):
65-
validate_positive_float("1.1")
69+
validate_positive_float("foo")
6670

6771

6872
def test_validate_positive_float_negative():

0 commit comments

Comments
 (0)