Skip to content

Commit

Permalink
Remove the need of reflection of p2p message (de)serialization (#1195)
Browse files Browse the repository at this point in the history
* Add `PayloadFactory` to create payload instances based on the command string instead of the  using the reflection `Activator` class based on the type decorated info.

* Simplify payload deserialization

A `Message` comes always with a Payload type and never with any other type. This means that a `Message` can come with a `BlockPayload` type but never with a `Block` type. So, the `ConsensusFactory` is unnecessary.

* Use a table to get payload command name instead of using reflection

* Remove `PayloadAttribute` completely.

* Make PayloadFactory a singleton

* Use `switch` and pattern matching

* Allow override of CreatePayload

---------

Co-authored-by: nicolas.dorier <[email protected]>
  • Loading branch information
lontivero and NicolasDorier authored Jan 10, 2024
1 parent 40ca72b commit c06f086
Show file tree
Hide file tree
Showing 36 changed files with 245 additions and 257 deletions.
44 changes: 44 additions & 0 deletions NBitcoin/ConsensusFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,50 @@ public bool TryCreateNew<T>(out T result) where T : IBitcoinSerializable
return success;
}

public virtual Payload CreatePayload(string command)
{
return command switch
{
"inv" => new InvPayload(),
"tx" => new TxPayload(),
"getdata" => new GetDataPayload(),
"headers" => new HeadersPayload(),
"block" => new BlockPayload(),
#if !NOSOCKET
"addr" => new AddrPayload(),
"addrv2" => new AddrV2Payload(),
"version" => new VersionPayload(),
#endif
"ping" => new PingPayload(),
"pong" => new PongPayload(),
"getaddr" => new GetAddrPayload(),
"blocktxn" => new BlockTxnPayload(),
"cmpctblock" => new CmpctBlockPayload(),
"cfilter" => new CompactFilterPayload(),
"cfcheckpt" => new CompactFilterCheckPointPayload(),
"cfheaders" => new CompactFilterHeadersPayload(),
"feefilter" => new FeeFilterPayload(),
"filteradd" => new FilterAddPayload(),
"filterload" => new FilterLoadPayload(),
"getblocktxn" => new GetBlockTxnPayload(),
"getblocks" => new GetBlocksPayload(),
"getcfilters" => new GetCompactFiltersPayload(),
"getcfheaders" => new GetCompactFilterHeadersPayload(),
"getcfcheckpt" => new GetCompactFilterCheckPointPayload(),
"getheaders" => new GetHeadersPayload(),
"havewitness" => new HaveWitnessPayload(),
"mempool" => new MempoolPayload(),
"merkleblock" => new MerkleBlockPayload(),
"sendaddrv2" => new SendAddrV2Payload(),
"sendcmpct" => new SendCmpctPayload(),
"sendheaders" => new SendHeadersPayload(),
"utxos" => new UTxOutputPayload(),
"verack" => new VerAckPayload(),
"wtxidrelay" => new WTxIdRelayPayload(),
_ => new UnknownPayload(command)
};
}

public virtual ProtocolCapabilities GetProtocolCapabilities(uint protocolVersion)
{
return new ProtocolCapabilities()
Expand Down
2 changes: 1 addition & 1 deletion NBitcoin/Protocol/BitcoinSerializablePayload.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace NBitcoin.Protocol
{
public class BitcoinSerializablePayload<T> : Payload where T : IBitcoinSerializable, new()
public abstract class BitcoinSerializablePayload<T> : Payload where T : IBitcoinSerializable, new()
{
public BitcoinSerializablePayload()
{
Expand Down
13 changes: 3 additions & 10 deletions NBitcoin/Protocol/Message.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ public bool IfPayloadIs<TPayload>(Action<TPayload> action) where TPayload : Payl
// We use this for big blocks, because the default array pool would allocate a new array. We do not need lot's of bucket such arrays are short lived.
readonly static Lazy<ArrayPool<byte>> BigArrayPool = new Lazy<ArrayPool<byte>>(() => ArrayPool<byte>.Create(0x02000000, 5), false);
ArrayPool<byte> GetArrayPool(int size) => size < 1_048_576 ? ArrayPool<byte>.Shared : BigArrayPool.Value;

public void ReadWrite(BitcoinStream stream)
{
if (Payload == null && stream.Serializing)
Expand Down Expand Up @@ -130,18 +129,12 @@ public void ReadWrite(BitcoinStream stream)
BitcoinStream payloadStream = new BitcoinStream(new MemoryStream(payloadBytes, 0, length, false), false);
payloadStream.CopyParameters(stream);

var payloadType = PayloadAttribute.GetCommandType(Command);
var unknown = payloadType == typeof(UnknownPayload);
if (unknown)
var payload = stream.ConsensusFactory.CreatePayload(Command);
if (payload is UnknownPayload)
Logs.NodeServer.LogWarning("Unknown command received {command}", Command);

IBitcoinSerializable payload = null;
if (!stream.ConsensusFactory.TryCreateNew(payloadType, out payload))
payload = (IBitcoinSerializable)Activator.CreateInstance(payloadType);
payload.ReadWrite(payloadStream);
if (unknown)
((UnknownPayload)payload)._Command = Command;
Payload = (Payload)payload;
Payload = payload;
}
finally
{
Expand Down
14 changes: 7 additions & 7 deletions NBitcoin/Protocol/Payload.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@

namespace NBitcoin.Protocol
{
public class Payload : IBitcoinSerializable
/// <summary>
/// A P2P Bitcoin payload
/// </summary>
public abstract class Payload : IBitcoinSerializable
{
public virtual string Command
public abstract string Command
{
get
{
return PayloadAttribute.GetCommandName(this.GetType());
}
get;
}

#region IBitcoinSerializable Members
Expand All @@ -34,7 +34,7 @@ public virtual void ReadWriteCore(BitcoinStream stream)

public override string ToString()
{
return this.GetType().Name;
return Command;
}
}
}
82 changes: 0 additions & 82 deletions NBitcoin/Protocol/PayloadAttribute.cs

This file was deleted.

166 changes: 84 additions & 82 deletions NBitcoin/Protocol/Payloads/AddrPayload.cs
Original file line number Diff line number Diff line change
@@ -1,82 +1,84 @@
#if !NOSOCKET
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NBitcoin.Protocol
{
/// <summary>
/// An available peer address in the bitcoin network is announced (unsolicited or after a getaddr)
/// </summary>
[Payload("addr")]
public class AddrPayload : Payload, IBitcoinSerializable
{
NetworkAddress[] addr_list = new NetworkAddress[0];
public NetworkAddress[] Addresses
{
get
{
return addr_list;
}
}

public AddrPayload()
{

}
public AddrPayload(NetworkAddress address)
{
addr_list = new NetworkAddress[] { address };
}
public AddrPayload(NetworkAddress[] addresses)
{
addr_list = addresses.ToArray();
}

#region IBitcoinSerializable Members

public override void ReadWriteCore(BitcoinStream stream)
{
stream.ReadWrite(ref addr_list);
}

#endregion

public override string ToString()
{
return Addresses.Length + " address(es)";
}
}

/// <summary>
/// An available peer address in the bitcoin network is announced (unsolicited or after a getaddrv2)
/// </summary>
[Payload("addrv2")]
public class AddrV2Payload : AddrPayload
{
public AddrV2Payload()
: base()
{
}
public AddrV2Payload(NetworkAddress address)
: base(address)
{
}

public AddrV2Payload(NetworkAddress[] addresses)
: base(addresses)
{
}

public override void ReadWriteCore(BitcoinStream stream)
{
using (stream.ProtocolVersionScope(NetworkAddress.AddrV2Format))
{
base.ReadWriteCore(stream);
}
}
}
}
#endif
#if !NOSOCKET
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NBitcoin.Protocol
{
/// <summary>
/// An available peer address in the bitcoin network is announced (unsolicited or after a getaddr)
/// </summary>

public class AddrPayload : Payload, IBitcoinSerializable
{
public override string Command => "addr";
NetworkAddress[] addr_list = new NetworkAddress[0];
public NetworkAddress[] Addresses
{
get
{
return addr_list;
}
}

public AddrPayload()
{

}
public AddrPayload(NetworkAddress address)
{
addr_list = new NetworkAddress[] { address };
}
public AddrPayload(NetworkAddress[] addresses)
{
addr_list = addresses.ToArray();
}

#region IBitcoinSerializable Members

public override void ReadWriteCore(BitcoinStream stream)
{
stream.ReadWrite(ref addr_list);
}

#endregion

public override string ToString()
{
return Addresses.Length + " address(es)";
}
}

/// <summary>
/// An available peer address in the bitcoin network is announced (unsolicited or after a getaddrv2)
/// </summary>

public class AddrV2Payload : AddrPayload
{
public override string Command => "addrv2";
public AddrV2Payload()
: base()
{
}
public AddrV2Payload(NetworkAddress address)
: base(address)
{
}

public AddrV2Payload(NetworkAddress[] addresses)
: base(addresses)
{
}

public override void ReadWriteCore(BitcoinStream stream)
{
using (stream.ProtocolVersionScope(NetworkAddress.AddrV2Format))
{
base.ReadWriteCore(stream);
}
}
}
}
#endif
3 changes: 2 additions & 1 deletion NBitcoin/Protocol/Payloads/BlockPayload.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ namespace NBitcoin.Protocol
/// <summary>
/// A block received after being asked with a getdata message
/// </summary>
[Payload("block")]

public class BlockPayload : BitcoinSerializablePayload<Block>
{
public override string Command => "block";
public BlockPayload()
{

Expand Down
3 changes: 2 additions & 1 deletion NBitcoin/Protocol/Payloads/BlockTxnPayload.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@

namespace NBitcoin.Protocol
{
[Payload("blocktxn")]

public class BlockTxnPayload : Payload
{
public override string Command => "blocktxn";

uint256 _BlockId;
public uint256 BlockId
Expand Down
Loading

0 comments on commit c06f086

Please sign in to comment.