-
Notifications
You must be signed in to change notification settings - Fork 146
Conversation
Updated to use the new eth-keys API introduced in ethereum/eth-keys#56 and ethereum/eth-keys#58. I still have to add the new version in I also restructured the code a little in order to have two separate classes for signed and unsigned ENRs (instead of an |
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.
Solid work! 👍
Need to fix linter.
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.
Can you open (or link me to an existing) larger meta-tracking issue to tie these various discv5 PR's together into a larger epoch so that it's easier to understand the big picture as I review these smaller components?
Opened a meta issue here (I guess I should have started with that): #668 |
I've addressed the feedback and now I'm waiting for ethereum/eth-keys#58 and a subsequent eth-keys release. Then I'll have a look at the outstanding CI errors. |
That suggests the verify_signature api should be changed if possible to either convert the exception case to return false. I'm on mobile so haven't really done my homework on feasibility or why the api was designed that way.
Sent from ProtonMail mobile
…-------- Original Message --------
On May 26, 2019, 4:07 PM, jannikluhn wrote:
@jannikluhn commented on this pull request.
---------------------------------------------------------------
In [p2p/discv5/identity_schemes.py](#586 (comment)):
> + signature = private_key.sign_msg_non_recoverable(message)
+ return bytes(signature)
+
+ @classmethod
+ def validate_signature(cls, enr: "ENR") -> None:
+ public_key = PublicKey.from_compressed_bytes(enr[cls.public_key_enr_key])
+ signature = NonRecoverableSignature(enr.signature)
+ message = enr.get_signing_message()
+
+ try:
+ is_valid = signature.verify_msg(message, public_key)
+ except BadSignature:
+ is_valid = False
+
+ if not is_valid:
+ raise ValidationError("Invalid signature")
verify_signature can either return False or raise a BadSignature, depending on the way the signature is invalid. So there isn't away to avoid the variable without copying the raise ValidationError line which I'd like to avoid.
—
You are receiving this because you commented.
Reply to this email directly, [view it on GitHub](#586?email_source=notifications&email_token=AAGJHAVZOCEIGL3VUZMLFCDPXKKR3A5CNFSM4HK56BGKYY3PNVWWK3TUL52HS4DFWFIHK3DMKJSXC5LFON2FEZLWNFSXPKTDN5WW2ZLOORPWSZGOBZWUFBA#discussion_r287599483), or [mute the thread](https://github.com/notifications/unsubscribe-auth/AAGJHARB5O6C3IK65PIMSULPXKKR3ANCNFSM4HK56BGA).
|
Very nice to see Trinity will have ENR support :). Note that EIP-778 was changed one more time to add "tcp6", "udp6" keys. They allow relaying IPv6-specific port numbers. We have also decided to use base64url encoding (without padding) instead of standard base64. |
f64e91a
to
2dd80e8
Compare
Unfortunately, now everything breaks due to a version conflict. Pretty much all |
Yeah, sorry, this is fixed in #699 and was released so it doesn't affect fresh installs. But it hasn't been merged in yet. I'll ping you when it gets merged in, and the rebase should be painless. |
@jannikluhn Okay, a rebase should fix the eth-keys issue now. |
@carver that didn't do the trick. The problem is that this PR needs |
Ah, right. @pipermerriam instead of propagating a chain of eth-keys releases up to trinity (I think at least these would need new releases: eth-account, web3, py-evm), maybe we can just do a patch release that includes everything that's in 0.3.0? |
|
Hm, interesting, I think this is a legit issue. The new
|
yeah, that's my fault, this PR should fix it: ethereum/eth-keys#62 |
Finally everything is green 🎉 I think I've addressed all comments so far, but since the last review I've restructured and extended the tests a bit. Anyone wants to have another look? Otherwise this is ready from my side. |
p2p/discv5/enr.py
Outdated
def serialize(cls, enr: "BaseENR") -> Tuple[bytes, ...]: | ||
serialized_sequence_number = big_endian_int.serialize(enr.sequence_number) | ||
|
||
sorted_key_value_pairs = sorted(enr.items(), key=lambda key_value: key_value[0]) |
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.
I believe historically we've measured operator.itemgetter(0)
to be faster and slightly more preferred.
p2p/discv5/enr.py
Outdated
serialized_enr: Sequence[bytes]) -> None: | ||
duplicates = set( | ||
key for index, key in enumerate(keys) | ||
if key in keys[index + 1:] |
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.
I think this has some unfortunate time complexity. An itertools.Counter
does a pretty nice job of both detecting duplicates and allowing easy reporting of which ones are duplicate.
duplicates = {key for key, num in itertools.Counter(keys).items() if num > 1}
That one-liner does the trick pretty concisely.
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.
neat! changed it
) | ||
|
||
|
||
class BaseENR(Mapping[bytes, Any], ABC): |
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.
Are the value types truly Any
or can that be constrained more?
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.
For now it's just int
and bytes
, but in theory it can be anything -- we might want to add special types for, e.g., libp2p multiaddrs or public key objects in the future. So to keep it easily extendable I'd prefer to keep it Any
.
p2p/discv5/enr.py
Outdated
|
||
def get_identity_scheme(self) -> Type[IdentityScheme]: | ||
try: | ||
identity_scheme_id = self[b"id"] |
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.
Maybe instead of having to do this try/except
checking at runtime it should happen during initialization and be memoized somewhere private since it's probably very natural to do that at whatever place the b'id'
key is validated to be present.
p2p/discv5/identity_schemes.py
Outdated
) | ||
|
||
|
||
identity_scheme_registry: Dict[bytes, Type["IdentityScheme"]] = {} |
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.
We probably still use this pattern in a number of places but I've grown to think that we should be using a registry pattern closer to something like this.
class Registry(collections.UserDict):
def register_thing(self, thing) -> None:
... # business logic here
self[thing_key] = thing
default_registry = Registry()
register_thing = default_registry.register_thing
Note that collections.UserDict
gives us an easy mechanism for simple registries like this.
https://docs.python.org/3/library/collections.html#collections.UserDict
Note that this requires ALL apis that make use of the registry to have some mechanism for passing in a custom registry. This pattern should result in easier isolation during testing as well as more flexibility when a library needs to override the base registry. I also think this approach has minimal extra overhead with respect to implementation and user facing API since the actualy underlying Registry
API can be maintained as private.
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.
Sounds reasonable. I've implemented something like this, but I'm not sure if I fully understood. Please have a look if that's how you imagined it.
tests/p2p/discv5/test_enr.py
Outdated
b"value1", | ||
b"id", | ||
]), | ||
) |
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.
This should be parametrized using pytest.mark.parametrize
so it runs as 4 different tests.
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.
This looks great. 👍
pass | ||
|
||
|
||
@default_identity_scheme_registry.register |
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.
You might expose the register
function as a top level module item that users can import to register a new identity scheme but that's only relevant if you want to expose this as a public API. For now I think it's fine to keep it private.
tests/p2p/discv5/test_enr.py
Outdated
|
||
|
||
@pytest.fixture | ||
def test_identity_scheme_registry(mock_identity_scheme): |
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.
because pytest is greedy I've tended to avoid naming anything in a module that pytest scrapes with test_
(or classes that start with Test
) because it sometimes tries to run them as tests. Naming schemes to get around this that seem appropriate are just to call this identity_scheme_registry
or identity_scheme_registry_for_testing
Closes #583. Still WIP: A couple of additional tests are needed and we have to use compressed public keys (working on ethereum/eth-keys#55 now).
How was it fixed?
None
it is considered unsigned. A signed copy can be created withto_signed_enr
.ENRSedes
andENRContentSedes
to serialize/deserialize an ENR with or without the signature. Values are (de)serialized as binaries, unless there's something better inENR_KEY_SEDES_MAPPING
.ENRIdentityScheme
which define a method to check if they can be used for a given ENR or not (usually by checking if the ENR contains a public key in the right format). The ENR will use the first matching identity scheme it can find (there will probably only be a few).Cute Animal Picture