Skip to content
This repository was archived by the owner on Nov 6, 2020. It is now read-only.

Macros to define subprotocol packet ids in chain sync #10341

Closed
elferdo wants to merge 16 commits into
masterfrom
fhc-syncpacket-macros
Closed

Macros to define subprotocol packet ids in chain sync #10341
elferdo wants to merge 16 commits into
masterfrom
fhc-syncpacket-macros

Conversation

@elferdo
Copy link
Copy Markdown
Contributor

@elferdo elferdo commented Feb 13, 2019

This PR elaborates on comments by @tomusdrw in #10315

Synchronization of a node's blockchain with other nodes happens through an exchange of packets over the network. In parity we support a number of different synchronization protocols that are transmitted and received as devp2p subprotocols.

In devp2p, subprotocol and the particular message within it are identified with a single number instead of two, one for protocol and one for message. Care must be taken to ensure that each type of package within each subprotocol gets a globally unique id that does not collide with other subprotocols.

This PR is a first proposal of a derive-macro that can be applied to an enum and attributes for the enum variants. Code would possibly look like the following:

#[derive(SyncPackets)]
enum Packet {
   #[protocol(ETH_PROTOCOL)] StatusMessage = 0x01,
...
   #[protocol(WARP_SYNC_PROTOCOL)] GetSnapshotManifestPacket = 0x11,
}

Benefits of the approach:

  • The enum takes care itself that ids are globally unique
  • The derive-macro implements conversion method from_u8 so that ids read from the network can be directly converted to the appropriate identifier.
  • The provided from_u8 method returns an Option<Packet>
  • The derive-macro automatically provides functionality to query id and protocol from an instance of Packet
  • Protocol and Id are specified in one single compact line
  • Further attributes may be easily added, for example a version number.
  • Syntax errors are reported as compile errors.

Drawbacks:

  • Each message needs to be individually tagged with its protocol attribute. Whole blocks would be more ergonomic.

@elferdo elferdo requested review from ngotchac and tomusdrw February 13, 2019 10:13
@parity-cla-bot
Copy link
Copy Markdown

It looks like @elferdo signed our Contributor License Agreement. 👍

Many thanks,

Parity Technologies CLA Bot

@5chdn 5chdn added this to the 2.4 milestone Feb 13, 2019
@5chdn 5chdn added A0-pleasereview 🤓 Pull request needs code review. M4-core ⛓ Core client code / Rust. labels Feb 13, 2019
@ngotchac
Copy link
Copy Markdown
Contributor

Couldn't we have something like:

#[derive(EnumMap)]
enum Packet {
   #[packet = ETH_PACKET]
  StatusMessage = 0x01,
...
  #[packet = WARP_SYNC_PACKET] 
  GetSnapshotManifestPacket = 0x11,
}

and Packet::StatusMessage.packet() would return ETH_PACKET?

@elferdo
Copy link
Copy Markdown
Contributor Author

elferdo commented Feb 13, 2019

You mean that the attribute packet itself defines a new method? So for instance #[version = v63] would automatically define Packet::StatusMessage.version()?

@ngotchac
Copy link
Copy Markdown
Contributor

That would be the idea yeah, if possible

@elferdo
Copy link
Copy Markdown
Contributor Author

elferdo commented Feb 13, 2019

I think the syntax would need to look somewhat different, because attributes for derive-macros must be predefined, but I think we can achieve something along those lines. I'll look into it.

For some reason I prefer closed solutions to open-ended ones, or at least try not to generalize too early, but I can't see any big downside to your idea right now. I would see, for example, that instead of #[eth] and #[par] we would provide a fixed set of attributes (protocol and version) for example: #[protocol WARP_SYNC] or #[version v63]. This would at least decouple the macro from the definition of the protocols.

Comment thread util/syncpacketid_derive/Cargo.toml Outdated
Comment thread util/syncpacketid_derive/src/lib.rs Outdated
Comment thread util/syncpacketid_derive/src/lib.rs Outdated
Comment thread util/syncpacketid_derive/src/lib.rs Outdated
niklasad1 and others added 2 commits February 13, 2019 16:26
Co-Authored-By: elferdo <elferdo@gmail.com>
Co-Authored-By: elferdo <elferdo@gmail.com>
Comment thread util/syncpacketid_derive/src/lib.rs Outdated
@ngotchac
Copy link
Copy Markdown
Contributor

@elferdo Can you update the PR description to reflect your recent changes?

Comment thread ethcore/sync/src/chain/sync_packet.rs Outdated
@elferdo
Copy link
Copy Markdown
Contributor Author

elferdo commented Feb 19, 2019

@ngotchac Oh, sure, sorry, I will update it right now.

@5chdn 5chdn modified the milestones: 2.4, 2.5, 2.6 Feb 21, 2019
@soc1c soc1c modified the milestones: 2.5, 2.6 Apr 2, 2019
Copy link
Copy Markdown
Collaborator

@dvdplm dvdplm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks pretty great! :)

One question though. If I try to use #[derive(SyncPackets)] on two enums I get this error:

24 | #[derive(SyncPackets, Clone, Copy, Debug, PartialEq)]
   |          ----------- previous import of the type `PacketId` here
...
30 | #[derive(SyncPackets, Clone, Copy, Debug, PartialEq)]
   |          ^^^^^^^^^^^
   |          |
   |          `PacketId` reimported here
   |          help: remove unnecessary import

Seems like a bug yeah?

Code:

#[derive(SyncPackets, Clone, Copy, Debug, PartialEq)]
enum Packets {
	#[protocol(MY_PROTOCOL0)] Packet0 = 0x00,
	#[protocol(MY_PROTOCOL1)] Packet1 = 0x01,
}

#[derive(SyncPackets, Clone, Copy, Debug, PartialEq)]
enum Bovine {
	#[protocol(BEEF)] Bull = 0x00,
	#[protocol(BEEF)] Cow = 0x01,
}

@dvdplm
Copy link
Copy Markdown
Collaborator

dvdplm commented Jun 4, 2019

@elferdo It'd be good to have some module level docs here with an example or two. Pretty please?

@elferdo
Copy link
Copy Markdown
Contributor Author

elferdo commented Jun 4, 2019

@elferdo It'd be good to have some module level docs here with an example or two. Pretty please?

Sure! I'll address your two issues asap.

@dvdplm dvdplm added the A5-grumble 🔥 Pull request has minor issues that must be addressed before merging. label Jun 4, 2019
@elferdo
Copy link
Copy Markdown
Contributor Author

elferdo commented Jun 5, 2019

Seems like a bug yeah?

The macro was designed with one specific use case in mind, namely that of putting all the p2p protocol IDs into one place. One of the restrictions these IDs must meet is that they are unique, and by allowing only one enum the language itself will take care of that.

Code:

#[derive(SyncPackets, Clone, Copy, Debug, PartialEq)]
enum Packets {
	#[protocol(MY_PROTOCOL0)] Packet0 = 0x00,
	#[protocol(MY_PROTOCOL1)] Packet1 = 0x01,
}

#[derive(SyncPackets, Clone, Copy, Debug, PartialEq)]
enum Bovine {
	#[protocol(BEEF)] Bull = 0x00,
	#[protocol(BEEF)] Cow = 0x01,
}

I guess you don't want:

#[derive(SyncPackets, Clone, Copy, Debug, PartialEq)]
enum Packets {
	#[protocol(MY_PROTOCOL0)] Packet0 = 0x00,
	#[protocol(MY_PROTOCOL1)] Packet1 = 0x01,
	#[protocol(BEEF)] Bull = 0x02,
	#[protocol(BEEF)] Cow = 0x03,
}

right?

Where is the other enum used?

The solution to the problem is quite straightforward, I'm just wondering what the use case is.

@dvdplm
Copy link
Copy Markdown
Collaborator

dvdplm commented Jun 5, 2019

Where is the other enum used?

The solution to the problem is quite straightforward, I'm just wondering what the use case is.

There isn't one, just me playing around with the code while reviewing it and noticing a surprising error message: I think the expectation is that a macro should work even when applied several times?

If it becomes terribly complicated to fix, I'm fine with some good docs explaining how to go about applying the macro to several enums in a project.

@dvdplm
Copy link
Copy Markdown
Collaborator

dvdplm commented Jun 5, 2019

Just to be clear: I think this is a terrific improvement and I'm prepared to merge it even if imperfect, just as long as it's properly doc'd. :)

@elferdo
Copy link
Copy Markdown
Contributor Author

elferdo commented Jun 5, 2019

Where is the other enum used?
The solution to the problem is quite straightforward, I'm just wondering what the use case is.

There isn't one, just me playing around with the code while reviewing it and noticing a surprising error message: I think the expectation is that a macro should work even when applied several times?

If it becomes terribly complicated to fix, I'm fine with some good docs explaining how to go about applying the macro to several enums in a project.

No, not really, I already have it in place. My worries are that it gets abused and then all of a sudden we end up having, again, uncontrolled IDs. Like it should be obvious how to properly update IDs, or add a new one, and it should be difficult to make it wrong.

Let's make it reusable, just in case. The code as it is should be obviously easy to maintain, and if someone wants it s/he can have it.

@dvdplm
Copy link
Copy Markdown
Collaborator

dvdplm commented Jun 5, 2019

namely that of putting all the p2p protocol IDs into one place

So this is used in libp2p? Should we perhaps extract it to parity-common then so we can share the code?

@dvdplm dvdplm added A3-inprogress ⏳ Pull request is in progress. No review needed at this stage. and removed A0-pleasereview 🤓 Pull request needs code review. labels Jun 10, 2019
@dvdplm
Copy link
Copy Markdown
Collaborator

dvdplm commented Jun 17, 2019

ping @elferdo

Copy link
Copy Markdown
Collaborator

@dvdplm dvdplm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a second look I think this is fine as-is. My code example is clearly a misuse and while it'd be nice to get a good compiler error I also think that with good module-level docs and tests for bad usage this is good to go.

Comment thread util/syncpacket/src/lib.rs
// Arguments to invocation attributes are delivered as a list
match argument {
Meta::Word(_) => Err(syn::Error::new_spanned(input, "protocol attribute without argument")),
Meta::List(args) => parse_protocol_arguments(&args).map(|ok| ok.clone()),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put the happy path first?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this check that there's a single argument in the list as well?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is checked in parse_protocol_arguments, do you think here would be a better place?

@dvdplm
Copy link
Copy Markdown
Collaborator

dvdplm commented Jul 3, 2019

@elferdo ping

@ordian ordian modified the milestones: 2.6, 2.7 Jul 12, 2019
@debris
Copy link
Copy Markdown
Collaborator

debris commented Jul 29, 2019

Sure! I'll address your two issues asap.

@elferdo please address the issues and reopen the pr :)

@debris debris closed this Jul 29, 2019
@denisgranha denisgranha deleted the fhc-syncpacket-macros branch March 17, 2020 11:38
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

A3-inprogress ⏳ Pull request is in progress. No review needed at this stage. A5-grumble 🔥 Pull request has minor issues that must be addressed before merging. M4-core ⛓ Core client code / Rust.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants