Skip to content

Commit 09f39b8

Browse files
committed
added a check for POA extraData
1 parent 4817ad2 commit 09f39b8

File tree

2 files changed

+123
-14
lines changed

2 files changed

+123
-14
lines changed

tests/core/eth-module/test_poa.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import pytest
22

3+
from web3.exceptions import (
4+
ValidationError,
5+
)
36
from web3.middleware import (
47
construct_fixture_middleware,
58
geth_poa_middleware,
@@ -12,7 +15,7 @@ def test_long_extra_data(web3):
1215
'eth_getBlockByNumber': {'extraData': '0x' + 'ff' * 33},
1316
})
1417
web3.middleware_stack.inject(return_block_with_long_extra_data, layer=0)
15-
with pytest.raises(ValueError):
18+
with pytest.raises(ValidationError):
1619
web3.eth.getBlock('latest')
1720

1821

web3/middleware/validation.py

Lines changed: 119 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,91 @@
11
from cytoolz import (
2+
assoc,
3+
complement,
24
compose,
35
curry,
46
dissoc,
57
)
68
from eth_utils.curried import (
7-
apply_formatter_at_index,
9+
apply_formatter_if,
810
apply_formatters_to_dict,
11+
is_null,
12+
)
13+
from eth_utils.decorators import (
14+
return_arg_type,
15+
)
16+
from eth_utils.functional import (
17+
to_dict,
18+
)
19+
from hexbytes import (
20+
HexBytes,
921
)
1022

1123
from web3.exceptions import (
1224
ValidationError,
1325
)
1426

27+
is_not_null = complement(is_null)
28+
1529

30+
# TODO: move to utils
31+
def construct_validation_middleware(request_validators=None, result_validators=None):
32+
if request_validators is None:
33+
request_validators = {}
34+
if result_validators is None:
35+
result_validators = {}
36+
37+
def formatter_middleware(make_request, web3):
38+
def middleware(method, params):
39+
if method in request_validators:
40+
formatter = request_validators[method]
41+
formatted_params = formatter(web3, params)
42+
response = make_request(method, formatted_params)
43+
else:
44+
response = make_request(method, params)
45+
if 'result' in response and method in result_validators:
46+
formatter = result_validators[method]
47+
formatted_response = assoc(
48+
response,
49+
'result',
50+
formatter(response['result']),
51+
)
52+
return formatted_response
53+
else:
54+
return response
55+
return middleware
56+
return formatter_middleware
57+
58+
59+
# TODO: move to utils
1660
@curry
61+
@return_arg_type(3)
62+
def apply_validator_at_index(validator, at_index, web3, value):
63+
if at_index + 1 > len(value):
64+
raise IndexError(
65+
"Not enough values in iterable to apply formatter. Got: {0}. "
66+
"Need: {1}".format(len(value), at_index + 1)
67+
)
68+
for index, item in enumerate(value):
69+
if index == at_index:
70+
yield validator(web3, item)
71+
else:
72+
yield item
73+
74+
75+
# TODO: move to utils
76+
@curry
77+
@to_dict
78+
def apply_validator_to_dict(validators, web3, value):
79+
for key, item in value.items():
80+
if key in validators:
81+
try:
82+
yield key, validators[key](web3, item)
83+
except (TypeError, ValueError) as exc:
84+
raise type(exc)("Could not format value %r as field %r" % (item, key)) from exc
85+
else:
86+
yield key, item
87+
88+
1789
def validate_chain_id(web3, chain_id):
1890
if chain_id == web3.version.network:
1991
return None
@@ -27,21 +99,55 @@ def validate_chain_id(web3, chain_id):
2799
)
28100

29101

102+
@curry
103+
def check_extradata_length(val, length):
104+
if not isinstance(val, (str, int, bytes)):
105+
return val
106+
result = HexBytes(val)
107+
if len(result) > length:
108+
raise ValidationError(
109+
"The field extraData is %d bytes, but should be %d. "
110+
"It is quite likely that you are connected to a POA chain. "
111+
"Refer "
112+
"http://web3py.readthedocs.io/en/latest/middleware.html#geth-style-proof-of-authority "
113+
"for more details. The full extraData is: %r" % (
114+
len(result), length, result
115+
)
116+
)
117+
return val
118+
119+
30120
def transaction_normalizer(transaction):
31121
return dissoc(transaction, 'chainId')
32122

33123

34-
def validation_middleware(make_request, web3):
35-
transaction_validator = apply_formatters_to_dict({
36-
'chainId': validate_chain_id(web3),
37-
})
124+
BLOCK_VALIDATORS = {
125+
'extraData': check_extradata_length(length=32),
126+
}
38127

39-
transaction_sanitizer = compose(transaction_normalizer, transaction_validator)
40128

41-
def middleware(method, params):
42-
if method in {'eth_sendTransaction', 'eth_estimateGas', 'eth_call'}:
43-
post_validated_params = apply_formatter_at_index(transaction_sanitizer, 0, params)
44-
return make_request(method, post_validated_params)
45-
else:
46-
return make_request(method, params)
47-
return middleware
129+
block_validator = apply_formatters_to_dict(BLOCK_VALIDATORS)
130+
131+
132+
TRANSACTION_PARAMS_VALIDATORS = apply_validator_to_dict({
133+
'chainId': validate_chain_id,
134+
})
135+
136+
137+
transaction_params_validator = apply_validator_at_index(
138+
compose(transaction_normalizer, TRANSACTION_PARAMS_VALIDATORS),
139+
0,
140+
)
141+
142+
143+
validation_middleware = construct_validation_middleware(
144+
request_validators={
145+
'eth_sendTransaction': transaction_params_validator,
146+
'eth_estimateGas': transaction_params_validator,
147+
'eth_call': transaction_params_validator,
148+
},
149+
result_validators={
150+
'eth_getBlockByHash': apply_formatter_if(is_not_null, block_validator),
151+
'eth_getBlockByNumber': apply_formatter_if(is_not_null, block_validator),
152+
},
153+
)

0 commit comments

Comments
 (0)