-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Made Validation Middleware Pluggable And Added A Check For POA extraData #756
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,44 +1,71 @@ | ||
from cytoolz import ( | ||
curry, | ||
merge, | ||
) | ||
from cytoolz.dicttoolz import ( | ||
assoc, | ||
) | ||
|
||
|
||
def construct_formatting_middleware(request_formatters=None, | ||
result_formatters=None, | ||
error_formatters=None): | ||
if request_formatters is None: | ||
request_formatters = {} | ||
if result_formatters is None: | ||
result_formatters = {} | ||
if error_formatters is None: | ||
error_formatters = {} | ||
|
||
def formatter_middleware(make_request, web3): | ||
def middleware(method, params): | ||
if method in request_formatters: | ||
formatter = request_formatters[method] | ||
formatted_params = formatter(params) | ||
response = make_request(method, formatted_params) | ||
else: | ||
response = make_request(method, params) | ||
|
||
if 'result' in response and method in result_formatters: | ||
formatter = result_formatters[method] | ||
formatted_response = assoc( | ||
response, | ||
'result', | ||
formatter(response['result']), | ||
) | ||
return formatted_response | ||
elif 'error' in response and method in error_formatters: | ||
formatter = error_formatters[method] | ||
formatted_response = assoc( | ||
response, | ||
'error', | ||
formatter(response['error']), | ||
) | ||
return formatted_response | ||
else: | ||
return response | ||
return middleware | ||
def construct_formatting_middleware( | ||
request_formatters=None, | ||
result_formatters=None, | ||
error_formatters=None): | ||
def ignore_web3_in_standard_formatters(w3): | ||
return dict( | ||
request_formatters=request_formatters or {}, | ||
result_formatters=result_formatters or {}, | ||
error_formatters=error_formatters or {}, | ||
) | ||
|
||
return construct_web3_formatting_middleware(ignore_web3_in_standard_formatters) | ||
|
||
|
||
def construct_web3_formatting_middleware(web3_formatters_builder): | ||
def formatter_middleware(make_request, w3): | ||
formatters = merge( | ||
{ | ||
'request_formatters': {}, | ||
'result_formatters': {}, | ||
'error_formatters': {}, | ||
}, | ||
web3_formatters_builder(w3), | ||
) | ||
return apply_formatters(make_request=make_request, **formatters) | ||
|
||
return formatter_middleware | ||
|
||
|
||
@curry | ||
def apply_formatters( | ||
method, | ||
params, | ||
make_request, | ||
request_formatters, | ||
result_formatters, | ||
error_formatters): | ||
if method in request_formatters: | ||
formatter = request_formatters[method] | ||
formatted_params = formatter(params) | ||
response = make_request(method, formatted_params) | ||
else: | ||
response = make_request(method, params) | ||
|
||
if 'result' in response and method in result_formatters: | ||
formatter = result_formatters[method] | ||
formatted_response = assoc( | ||
response, | ||
'result', | ||
formatter(response['result']), | ||
) | ||
return formatted_response | ||
elif 'error' in response and method in error_formatters: | ||
formatter = error_formatters[method] | ||
formatted_response = assoc( | ||
response, | ||
'error', | ||
formatter(response['error']), | ||
) | ||
return formatted_response | ||
else: | ||
return response |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,30 @@ | ||
from cytoolz import ( | ||
complement, | ||
compose, | ||
curry, | ||
dissoc, | ||
) | ||
from eth_utils.curried import ( | ||
apply_formatter_at_index, | ||
apply_formatter_if, | ||
apply_formatters_to_dict, | ||
is_null, | ||
) | ||
from hexbytes import ( | ||
HexBytes, | ||
) | ||
|
||
from web3.exceptions import ( | ||
ValidationError, | ||
) | ||
from web3.middleware.formatting import ( | ||
construct_web3_formatting_middleware, | ||
) | ||
|
||
MAX_EXTRADATA_LENGTH = 32 | ||
|
||
|
||
is_not_null = complement(is_null) | ||
|
||
|
||
@curry | ||
|
@@ -27,20 +41,72 @@ def validate_chain_id(web3, chain_id): | |
) | ||
|
||
|
||
def check_extradata_length(val): | ||
if not isinstance(val, (str, int, bytes)): | ||
return val | ||
result = HexBytes(val) | ||
if len(result) > MAX_EXTRADATA_LENGTH: | ||
raise ValidationError( | ||
"The field extraData is %d bytes, but should be %d. " | ||
"It is quite likely that you are connected to a POA chain. " | ||
"Refer " | ||
"http://web3py.readthedocs.io/en/stable/middleware.html#geth-style-proof-of-authority " | ||
"for more details. The full extraData is: %r" % ( | ||
len(result), MAX_EXTRADATA_LENGTH, result | ||
) | ||
) | ||
return val | ||
|
||
|
||
def transaction_normalizer(transaction): | ||
return dissoc(transaction, 'chainId') | ||
|
||
|
||
def validation_middleware(make_request, web3): | ||
transaction_validator = apply_formatters_to_dict({ | ||
}) | ||
def transaction_param_validator(web3): | ||
transactions_params_validators = { | ||
'chainId': apply_formatter_if( | ||
# Bypass `validate_chain_id` if chainId can't be determined | ||
lambda _: is_not_null(web3.net.chainId), | ||
validate_chain_id(web3) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cool, this looks like a good solution that can stay in place for the long term 👍 |
||
), | ||
} | ||
return apply_formatter_at_index( | ||
apply_formatters_to_dict(transactions_params_validators), | ||
0 | ||
) | ||
|
||
|
||
BLOCK_VALIDATORS = { | ||
'extraData': check_extradata_length, | ||
} | ||
|
||
|
||
block_validator = apply_formatter_if( | ||
is_not_null, | ||
apply_formatters_to_dict(BLOCK_VALIDATORS) | ||
) | ||
|
||
|
||
@curry | ||
def chain_id_validator(web3): | ||
return compose( | ||
apply_formatter_at_index(transaction_normalizer, 0), | ||
transaction_param_validator(web3) | ||
) | ||
|
||
|
||
def build_validators_with_web3(w3): | ||
return dict( | ||
request_formatters={ | ||
'eth_sendTransaction': chain_id_validator(w3), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After having a look at https://github.com/ethereum/web3.py/pull/777/files#diff-1e8b0fe1b6f6e15899cb2f41637945bbL36, I'm not sure if I should remove these 3 lines below or If I should comment out line 67. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One approach would be to leave the validator in, but short-circuit it if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
'eth_estimateGas': chain_id_validator(w3), | ||
'eth_call': chain_id_validator(w3), | ||
}, | ||
result_formatters={ | ||
'eth_getBlockByHash': block_validator, | ||
'eth_getBlockByNumber': block_validator, | ||
}, | ||
) | ||
|
||
transaction_sanitizer = compose(transaction_normalizer, transaction_validator) | ||
|
||
def middleware(method, params): | ||
if method in {'eth_sendTransaction', 'eth_estimateGas', 'eth_call'}: | ||
post_validated_params = apply_formatter_at_index(transaction_sanitizer, 0, params) | ||
return make_request(method, post_validated_params) | ||
else: | ||
return make_request(method, params) | ||
return middleware | ||
validation_middleware = construct_web3_formatting_middleware(build_validators_with_web3) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the style-fix!