This PR factors out transport packaging logic from CConnman::PushMessage().
It's similar to #16202 (where we refactor deserialization).
This allows implementing a new message transport protocol like BIP324.
This PR factors out transport packaging logic from CConnman::PushMessage().
It's similar to #16202 (where we refactor deserialization).
This allows implementing a new message transport protocol like BIP324.
<!--e57a25ab6845829454e8d69fc972939a-->
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
<!--174a7506f384e20aa4161008e828411d-->
No conflicts as of last run.
613 | @@ -614,12 +614,27 @@ class CNetMessage { 614 | int readData(const char *pch, unsigned int nBytes); 615 | }; 616 | 617 | +/** The TransportSerializer prepares messages for the network transport 618 | + */ 619 | +class TransportSerializer { 620 | +public: 621 | + // prepare message for transport (insert header, etc.) 622 | + virtual bool prepareForTransport(CSerializedNetMsg& msg) = 0;
Return value is currently ignored, either make it void or assert(result)?
This is more a concept comment than code review. Looks like this approach causes reallocations? If the goal is to factor out from CConnman::PushMessage have you considered something like class MessageWriterV1 : public MessageWriter? The message writer V1 would still push 2 buffers to vSendMsg (vSendMsg should be accessible to the message writer). In other words, CConnman wouldn't be responsible to push to vSendMsg).
@promag: yes. You'r right. The insert/reallocation was stupid. Slighly changed the approach. It is now also avoiding the behavior change.
Also removed the unused return value (will eventually come back with the v2 implementation).
618 | + */ 619 | +class TransportSerializer { 620 | +public: 621 | + // prepare message for transport (insert header, etc.) 622 | + virtual void prepareForTransport(CSerializedNetMsg& msg, std::vector<unsigned char>& header) = 0; 623 | + virtual ~TransportSerializer() {}
Do you expect serializer to keep state between call and so need to clean it with a destructor? (or just a good practice)
Since we keep the instances in an std::unique_ptr, we need to have a virtual destructor.
631 | /** Information about a peer */
632 | class CNode
633 | {
634 | friend class CConnman;
635 | public:
636 | + std::unique_ptr<TransportSerializer> m_serializer;
This change would mean we initialize serializer at ConnectNode/AcceptConnection, if we make an outbound connection, thanks to NODE_P2P_V2 we can be sure to have right serializer, but in case of inbound we may need to update it. Maybe comment this logic here?
Thanks for the comment. Your concerns are valid but not part of this PR. For a BIP324 (NODE_P2P_V2) inbound connection (also outbound) we always start with a V1 serializer/deserializer to perform the handshake. I think the handshake/a.k.a. protocol upgrade belongs to the V1 protocol.
Once the handshake has been completed, we exchange the node objects serializer/deserializer with a V2 instance.
You mean the handshake belongs to the V1 protocol because we are using V1 messages to exchange 32-byte public keys between initiator/responder as defined in V2 BIP ? That makes sense and it's already implicit in the BIP, "After a successful handshake, messages MUST use the "v2 messages structure", but maybe put a word about this at the beginning of handshake section, just to be really obvious.
613 | @@ -614,12 +614,27 @@ class CNetMessage { 614 | int readData(const char *pch, unsigned int nBytes); 615 | }; 616 | 617 | +/** The TransportSerializer prepares messages for the network transport 618 | + */ 619 | +class TransportSerializer { 620 | +public: 621 | + // prepare message for transport (insert header, etc.)
nit: Comments could be better for public interfaces, parent class could list the abstract operations a transport protocol may realize like header construction, error-correction computation, payload encryption and then for transport subclass would list the concrete sequence of operations implemented?
Slightly improved...
ACK b840e42, code reviewed, tests ok. Feel free to add comments if you rebase.
689 | @@ -690,6 +690,19 @@ const uint256& CNetMessage::GetMessageHash() const 690 | return data_hash; 691 | } 692 | 693 | +void V1TransportSerializer::prepareForTransport(CSerializedNetMsg& msg, std::vector<unsigned char>& header) {
Should be override?
Thanks. Fixed.
Rebased this PR for you as well: https://github.com/bitcoin/bitcoin/compare/master...dongcarl:2020-01-net_refactor_2-rebased
Thanks @dongcarl. Pushed your rebased version.
semiACK 16d6113f4faa901e248adb693d4768a9e5019a16 ran functional+unit tests.
While reading the code I found one observable difference which is the replacement of HEADER_SIZE with serializedHeader.size(). I'm not exactly sure which should be used here(they should be the same but maybe there's some DOS vector i'm missing)
other than that the pointer is allocated in the constructor so there shouldn't be a way to call it when null.
ACK 16d6113f4faa901e248adb693d4768a9e5019a16 FWIW
Possible followup:
Add properly formatted Doxygen comments (something like what's already in the abstract class) for, and/or rename TransportSerializer::prepareForTransport as the name can be misinterpreted as: "do everything that we need to do before we send it to transport layer."
Code review ACK 16d6113
I'm not exactly sure which should be used here(they should be the same but maybe there's some DOS vector i'm missing) @elichai I don't think there is a risk of DoS here,
serializerHeaderas allCMessageHeadercomponents have a fixed-length. Bandwidth-DoS is already left unbounded (see default of-maxuploadtarget)
2715 | @@ -2703,6 +2716,7 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn 2716 | } 2717 | 2718 | m_deserializer = MakeUnique<V1TransportDeserializer>(V1TransportDeserializer(Params().MessageStart(), SER_NETWORK, INIT_PROTO_VERSION)); 2719 | + m_serializer = MakeUnique<V1TransportSerializer>(V1TransportSerializer());
Any reason this needs to call the copy(or move) constructor?
m_serializer = MakeUnique<V1TransportSerializer>();
ACK 16d6113f4faa901e248adb693d4768a9e5019a16 🙎
<details><summary>Show signature and timestamp</summary>
Signature:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
ACK 16d6113f4faa901e248adb693d4768a9e5019a16 🙎
-----BEGIN PGP SIGNATURE-----
iQGzBAEBCgAdFiEE+rVPoUahrI9sLGYTzit1aX5ppUgFAlwqrYAACgkQzit1aX5p
pUhcGwv+Igl6tmkV78gSSi8EZt3582CuubMrSdBrB8UJ62ukEZVAmotmNR3ShTjM
rAmpiY7bxU0/2hW6Nr1YFo7t3FZ15dNPX2fTClctXXeLVFJdc1fSUwUg/oeRoJtm
T2CftFTfDrbU2o7HeX7rZvYSuieIl/SFBmNQ+w4jG8AFh4bCHH3pQVC7ueJhIHIx
qxKx9GNzwGOn3/rKCXj9vkEKRxHs/vGFDh4WYRezhEWkVvpe6996IiUMYVsIgyqm
npuGyOYoBmaSxj4z9PFHrL58AnnuRlUKy/1B0MyHz5v789b2swFn1iw5L9JTzOvh
dI9W2w7vkUnIaudUn1Z5dNPmVG8k7tmMAYvxE7TrhD1UgBRe2QYyNMt8CEJerxOd
50xlIKikLZxzL1ASwxatzPSKMl2ITprvIMrtoI0RrS1tPtfcXLnx10yr7EJ0dAxB
ZLs3GWZah5d/XLMDcyBSGWtiuWS+nkws4dAmZIlwBI/Qcm55dXAuW+Wocj59mtDE
QdnFfvp/
=gFtP
-----END PGP SIGNATURE-----
Timestamp of file with hash 2ae5b6b8bcff0b2640a243d5665c57ba947e1d6ef581e8eb4aa04cfd1b160d31 -
</details>
Any chance we can move forward with the V1TransportSerializer fuzzer in #17771 to get additional robustness testing of this code before proceeding with the V1TransportSerializer refactoring(s)?
It should hopefully be trivial to review :)
@practicalswift: are you working on fuzz testing the serializing part?
@practicalswift: are you working on fuzz testing the serializing part? @jonasschnelli: I've attempt to extend the test to serialization in #22029. I've also discovered that the deserialization does not proceed 60-70% of the time due to a failed checksum test. Will try to address that and increase coverage in another commit this week.