From 8cdddfecde22dda85e3bdfae6b658c1acf90b142 Mon Sep 17 00:00:00 2001 From: Ben Carman Date: Sun, 6 Dec 2020 08:07:57 -0600 Subject: [PATCH] Implement BIP 155 addrv2 messages (#2321) * Implement BIP 155 addrv2 messages * Add unit tests --- .../RawAddrV2MessageSerializerTest.scala | 203 +++++++++++++++++ .../bitcoins/core/p2p/NetworkPayload.scala | 215 +++++++++++++++++- .../networking/peer/PeerMessageReceiver.scala | 5 + .../networking/peer/PeerMessageSender.scala | 4 + 4 files changed, 425 insertions(+), 2 deletions(-) create mode 100644 core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawAddrV2MessageSerializerTest.scala diff --git a/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawAddrV2MessageSerializerTest.scala b/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawAddrV2MessageSerializerTest.scala new file mode 100644 index 0000000000..9e4a75d464 --- /dev/null +++ b/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawAddrV2MessageSerializerTest.scala @@ -0,0 +1,203 @@ +package org.bitcoins.core.serializers.p2p.messages + +import org.bitcoins.core.number.{UInt16, UInt32, UInt64} +import org.bitcoins.core.p2p._ +import org.bitcoins.core.protocol.CompactSizeUInt +import org.bitcoins.testkit.core.gen.NumberGenerator +import org.bitcoins.testkit.util.BitcoinSUnitTest +import org.scalacheck.Gen +import scodec.bits.HexStringSyntax + +import java.net.InetAddress + +class RawAddrV2MessageSerializerTest extends BitcoinSUnitTest { + + def ipv4AddrV2MessageGen: Gen[IPv4AddrV2Message] = { + for { + time <- NumberGenerator.uInt32s + services <- NumberGenerator.compactSizeUInts + addrBytes <- NumberGenerator.bytevector(AddrV2Message.IPV4_ADDR_LENGTH) + port <- NumberGenerator.uInt16 + } yield IPv4AddrV2Message(time, + services, + InetAddress.getByAddress(addrBytes.toArray), + port) + } + + def ipv6AddrV2MessageGen: Gen[IPv6AddrV2Message] = { + for { + time <- NumberGenerator.uInt32s + services <- NumberGenerator.compactSizeUInts + addrBytes <- NumberGenerator.bytevector(AddrV2Message.IPV6_ADDR_LENGTH) + port <- NumberGenerator.uInt16 + } yield IPv6AddrV2Message(time, + services, + InetAddress.getByAddress(addrBytes.toArray), + port) + } + + def torV2AddrV2MessageGen: Gen[TorV2AddrV2Message] = { + for { + time <- NumberGenerator.uInt32s + services <- NumberGenerator.compactSizeUInts + addrBytes <- NumberGenerator.bytevector(AddrV2Message.TOR_V2_ADDR_LENGTH) + port <- NumberGenerator.uInt16 + } yield TorV2AddrV2Message(time, services, addrBytes, port) + } + + def torV3AddrV2MessageGen: Gen[TorV3AddrV2Message] = { + for { + time <- NumberGenerator.uInt32s + services <- NumberGenerator.compactSizeUInts + addrBytes <- NumberGenerator.bytevector(AddrV2Message.TOR_V3_ADDR_LENGTH) + port <- NumberGenerator.uInt16 + } yield TorV3AddrV2Message(time, services, addrBytes, port) + } + + def i2pAddrV2MessageGen: Gen[I2PAddrV2Message] = { + for { + time <- NumberGenerator.uInt32s + services <- NumberGenerator.compactSizeUInts + addrBytes <- NumberGenerator.bytevector(AddrV2Message.I2P_ADDR_LENGTH) + port <- NumberGenerator.uInt16 + } yield I2PAddrV2Message(time, services, addrBytes, port) + } + + def cjdnsAddrV2MessageGen: Gen[CJDNSAddrV2Message] = { + for { + time <- NumberGenerator.uInt32s + services <- NumberGenerator.compactSizeUInts + addrBytes <- NumberGenerator.bytevector(AddrV2Message.CJDNS_ADDR_LENGTH) + port <- NumberGenerator.uInt16 + } yield CJDNSAddrV2Message(time, services, addrBytes, port) + } + + def unknownAddrV2MessageGen: Gen[UnknownNetworkAddrV2Message] = { + for { + time <- NumberGenerator.uInt32s + services <- NumberGenerator.compactSizeUInts + networkId <- NumberGenerator.byte.suchThat(byte => + !AddrV2Message.knownNetworkBytes.contains(byte)) + addrBytes <- NumberGenerator.bytevector + port <- NumberGenerator.uInt16 + } yield UnknownNetworkAddrV2Message(time, + services, + networkId, + addrBytes, + port) + } + + "IPv4AddrV2Message" must "have serialization symmetry" in { + forAll(ipv4AddrV2MessageGen) { msg => + assert(msg.bytes == AddrV2Message(msg.bytes).bytes) + } + } + + it must "parse an IPv4AddrV2Message" in { + val msg = IPv4AddrV2Message(UInt32(4523), + CompactSizeUInt(UInt64(53453453L)), + InetAddress.getByAddress(hex"00000000".toArray), + UInt16(8333)) + + assert("000011abfe8da22f030100000000208d" == msg.hex) + } + + "IPv6AddrV2Message" must "have serialization symmetry" in { + forAll(ipv6AddrV2MessageGen) { msg => + assert(msg.bytes == AddrV2Message(msg.bytes).bytes) + } + } + + it must "parse an IPv6AddrV2Message" in { + val msg = IPv6AddrV2Message( + UInt32(4523), + CompactSizeUInt(UInt64(53453453L)), + InetAddress.getByAddress(hex"00000000000000000000000000000000".toArray), + UInt16(8333)) + + assert( + "000011abfe8da22f030200000000000000000000000000000000208d" == msg.hex) + } + + "TorV2AddrV2Message" must "have serialization symmetry" in { + forAll(torV2AddrV2MessageGen) { msg => + assert(msg.bytes == AddrV2Message(msg.bytes).bytes) + } + } + + it must "parse a TorV2AddrV2Message" in { + val msg = TorV2AddrV2Message(UInt32(4523), + CompactSizeUInt(UInt64(53453453L)), + hex"00000000000000000000", + UInt16(8333)) + + assert("000011abfe8da22f030300000000000000000000208d" == msg.hex) + } + + "TorV3AddrV2Message" must "have serialization symmetry" in { + forAll(torV3AddrV2MessageGen) { msg => + assert(msg.bytes == AddrV2Message(msg.bytes).bytes) + } + } + + it must "parse a TorV3AddrV2Message" in { + val msg = TorV3AddrV2Message( + UInt32(4523), + CompactSizeUInt(UInt64(53453453L)), + hex"0000000000000000000000000000000000000000000000000000000000000000", + UInt16(8333)) + + assert( + "000011abfe8da22f03040000000000000000000000000000000000000000000000000000000000000000208d" == msg.hex) + } + + "I2PAddrV2Message" must "have serialization symmetry" in { + forAll(i2pAddrV2MessageGen) { msg => + assert(msg.bytes == AddrV2Message(msg.bytes).bytes) + } + } + + it must "parse an I2PAddrV2Message" in { + val msg = I2PAddrV2Message( + UInt32(4523), + CompactSizeUInt(UInt64(53453453L)), + hex"0000000000000000000000000000000000000000000000000000000000000000", + UInt16(8333)) + + assert( + "000011abfe8da22f03050000000000000000000000000000000000000000000000000000000000000000208d" == msg.hex) + } + + "CJDNSAddrV2Message" must "have serialization symmetry" in { + forAll(cjdnsAddrV2MessageGen) { msg => + assert(msg.bytes == AddrV2Message(msg.bytes).bytes) + } + } + + it must "parse a CJDNSAddrV2Message" in { + val msg = CJDNSAddrV2Message(UInt32(4523), + CompactSizeUInt(UInt64(53453453L)), + hex"00000000000000000000000000000000", + UInt16(8333)) + + assert( + "000011abfe8da22f030600000000000000000000000000000000208d" == msg.hex) + } + + "UnknownNetworkAddrV2Message" must "have serialization symmetry" in { + forAll(unknownAddrV2MessageGen) { msg => + assert(msg.bytes == AddrV2Message(msg.bytes).bytes) + } + } + + it must "parse a UnknownNetworkAddrV2Message" in { + val msg = UnknownNetworkAddrV2Message(UInt32(4523), + CompactSizeUInt(UInt64(53453453L)), + 0x07, + hex"00000000000000000000000000000000", + UInt16(8333)) + + assert( + "000011abfe8da22f030700000000000000000000000000000000208d" == msg.hex) + } +} diff --git a/core/src/main/scala/org/bitcoins/core/p2p/NetworkPayload.scala b/core/src/main/scala/org/bitcoins/core/p2p/NetworkPayload.scala index 17fb434f12..318a2bb596 100644 --- a/core/src/main/scala/org/bitcoins/core/p2p/NetworkPayload.scala +++ b/core/src/main/scala/org/bitcoins/core/p2p/NetworkPayload.scala @@ -1,11 +1,10 @@ package org.bitcoins.core.p2p import java.net.{InetAddress, InetSocketAddress} - import org.bitcoins.core.bloom.{BloomFilter, BloomFlag} import org.bitcoins.core.config.NetworkParameters import org.bitcoins.core.gcs.{FilterHeader, FilterType, GolombFilter} -import org.bitcoins.core.number.{Int32, Int64, UInt32, UInt64} +import org.bitcoins.core.number.{Int32, Int64, UInt16, UInt32, UInt64} import org.bitcoins.core.protocol.CompactSizeUInt import org.bitcoins.core.protocol.blockchain.{Block, BlockHeader, MerkleBlock} import org.bitcoins.core.protocol.transaction.Transaction @@ -528,6 +527,212 @@ object AddrMessage extends Factory[AddrMessage] { } +/** addrV2 relays information about a peer. It supports many different + * address types and networks. + * + * @see https://github.com/bitcoin/bips/blob/master/bip-0155.mediawiki + */ +sealed trait AddrV2Message extends ControlPayload { + def time: UInt32 + def services: CompactSizeUInt + def networkId: Byte + def addrBytes: ByteVector + def port: UInt16 + + override def commandName: String = NetworkPayload.addrV2CommandName + + override def bytes: ByteVector = + time.bytes ++ services.bytes ++ ByteVector.fromByte( + networkId) ++ addrBytes ++ port.bytes +} + +/** addrv2 message that contains an IPv4 address */ +case class IPv4AddrV2Message( + time: UInt32, + services: CompactSizeUInt, + addr: InetAddress, + port: UInt16) + extends AddrV2Message { + override val networkId: Byte = AddrV2Message.IPV4_NETWORK_BYTE + + override val addrBytes: ByteVector = ByteVector(addr.getAddress) + + require(addrBytes.size == AddrV2Message.IPV4_ADDR_LENGTH, + "Incorrect size of IPv4 message, consider using IPv6AddrV2Message") +} + +/** addrv2 message that contains an IPv6 address */ +case class IPv6AddrV2Message( + time: UInt32, + services: CompactSizeUInt, + addr: InetAddress, + port: UInt16) + extends AddrV2Message { + override val networkId: Byte = AddrV2Message.IPV6_NETWORK_BYTE + + override val addrBytes: ByteVector = ByteVector(addr.getAddress) + + require(addrBytes.size == AddrV2Message.IPV6_ADDR_LENGTH, + "Incorrect size of IPv6 message, consider using IPv4AddrV2Message") +} + +/** addrv2 message that contains a TorV2 address */ +case class TorV2AddrV2Message( + time: UInt32, + services: CompactSizeUInt, + addrBytes: ByteVector, + port: UInt16) + extends AddrV2Message { + require( + addrBytes.size == AddrV2Message.TOR_V2_ADDR_LENGTH, + s"TorV2 addresses are ${AddrV2Message.TOR_V2_ADDR_LENGTH} bytes, got ${addrBytes.size}") + override val networkId: Byte = AddrV2Message.TOR_V2_NETWORK_BYTE +} + +/** addrv2 message that contains a TorV3 address */ +case class TorV3AddrV2Message( + time: UInt32, + services: CompactSizeUInt, + addrBytes: ByteVector, + port: UInt16) + extends AddrV2Message { + require( + addrBytes.size == AddrV2Message.TOR_V3_ADDR_LENGTH, + s"TorV3 addresses are ${AddrV2Message.TOR_V3_ADDR_LENGTH} bytes, got ${addrBytes.size}") + override val networkId: Byte = AddrV2Message.TOR_V3_NETWORK_BYTE +} + +/** addrv2 message that contains an I2P address */ +case class I2PAddrV2Message( + time: UInt32, + services: CompactSizeUInt, + addrBytes: ByteVector, + port: UInt16) + extends AddrV2Message { + require( + addrBytes.size == AddrV2Message.I2P_ADDR_LENGTH, + s"I2P addresses are ${AddrV2Message.I2P_ADDR_LENGTH} bytes, got ${addrBytes.size}") + override val networkId: Byte = AddrV2Message.I2P_NETWORK_BYTE +} + +/** addrv2 message that contains an CJDNS address */ +case class CJDNSAddrV2Message( + time: UInt32, + services: CompactSizeUInt, + addrBytes: ByteVector, + port: UInt16) + extends AddrV2Message { + require( + addrBytes.size == AddrV2Message.CJDNS_ADDR_LENGTH, + s"CJDNS addresses are ${AddrV2Message.CJDNS_ADDR_LENGTH} bytes, got ${addrBytes.size}") + override val networkId: Byte = AddrV2Message.CJDNS_NETWORK_BYTE +} + +/** addrv2 message that contains an address from a network we do not understand */ +case class UnknownNetworkAddrV2Message( + time: UInt32, + services: CompactSizeUInt, + networkId: Byte, + addrBytes: ByteVector, + port: UInt16) + extends AddrV2Message { + require(!AddrV2Message.knownNetworkBytes.contains(networkId), + s"$networkId is a known network byte") +} + +/** + * The companion object for an AddrV2Message + * @see https://developer.bitcoin.org/reference/p2p_networking.html#addrv2 + * @see https://github.com/bitcoin/bips/blob/master/bip-0155.mediawiki + */ +object AddrV2Message extends Factory[AddrV2Message] { + + final val IPV4_NETWORK_BYTE: Byte = 0x01 + final val IPV4_ADDR_LENGTH: Int = 4 + + final val IPV6_NETWORK_BYTE: Byte = 0x02 + final val IPV6_ADDR_LENGTH: Int = 16 + + final val TOR_V2_NETWORK_BYTE: Byte = 0x03 + final val TOR_V2_ADDR_LENGTH: Int = 10 + + final val TOR_V3_NETWORK_BYTE: Byte = 0x04 + final val TOR_V3_ADDR_LENGTH: Int = 32 + + final val I2P_NETWORK_BYTE: Byte = 0x05 + final val I2P_ADDR_LENGTH: Int = 32 + + final val CJDNS_NETWORK_BYTE: Byte = 0x06 + final val CJDNS_ADDR_LENGTH: Int = 16 + + val knownNetworkBytes: Vector[Byte] = Vector(IPV4_NETWORK_BYTE, + IPV6_NETWORK_BYTE, + TOR_V2_NETWORK_BYTE, + TOR_V3_NETWORK_BYTE, + I2P_NETWORK_BYTE, + CJDNS_NETWORK_BYTE) + + override def fromBytes(bytes: ByteVector): AddrV2Message = { + val timeBytes = bytes.take(4) + val time = UInt32(timeBytes) + + val services = CompactSizeUInt(bytes.drop(4)) + + val remainingBytes = bytes.drop(4 + services.bytes.size) + + val networkId = remainingBytes.head + + val addrPortBytes = remainingBytes.tail + + networkId match { + case IPV4_NETWORK_BYTE => + val addrBytes = addrPortBytes.take(IPV4_ADDR_LENGTH) + val address = InetAddress.getByAddress(addrBytes.toArray) + val port = UInt16(addrPortBytes.drop(IPV4_ADDR_LENGTH).take(2)) + IPv4AddrV2Message(time, services, address, port) + case IPV6_NETWORK_BYTE => + val addrBytes = addrPortBytes.take(IPV6_ADDR_LENGTH) + val address = InetAddress.getByAddress(addrBytes.toArray) + val port = UInt16(addrPortBytes.drop(IPV6_ADDR_LENGTH).take(2)) + IPv6AddrV2Message(time, services, address, port) + case TOR_V2_NETWORK_BYTE => + val addrBytes = addrPortBytes.take(TOR_V2_ADDR_LENGTH) + val port = UInt16(addrPortBytes.drop(TOR_V2_ADDR_LENGTH).take(2)) + TorV2AddrV2Message(time, services, addrBytes, port) + case TOR_V3_NETWORK_BYTE => + val addrBytes = addrPortBytes.take(TOR_V3_ADDR_LENGTH) + val port = UInt16(addrPortBytes.drop(TOR_V3_ADDR_LENGTH).take(2)) + TorV3AddrV2Message(time, services, addrBytes, port) + case I2P_NETWORK_BYTE => + val addrBytes = addrPortBytes.take(I2P_ADDR_LENGTH) + val port = UInt16(addrPortBytes.drop(I2P_ADDR_LENGTH).take(2)) + I2PAddrV2Message(time, services, addrBytes, port) + case CJDNS_NETWORK_BYTE => + val addrBytes = addrPortBytes.take(CJDNS_ADDR_LENGTH) + val port = UInt16(addrPortBytes.drop(CJDNS_ADDR_LENGTH).take(2)) + CJDNSAddrV2Message(time, services, addrBytes, port) + case unknown: Byte => + val addrBytes = addrPortBytes.dropRight(2) + val port = UInt16(addrPortBytes.drop(addrBytes.size)) + UnknownNetworkAddrV2Message(time, services, unknown, addrBytes, port) + + } + } +} + +/** + * Sending such a message indicates that a node can understand and + * prefers to receive addrv2 messages instead of addr messages. + * I.e. "Send me addrv2".sendaddrv2 SHOULD be sent after receiving the verack message from the peer. + * For older peers, that did not emit sendaddrv2, keep sending the legacy + * addr message, ignoring addresses with the newly introduced address types. + * @see [[https://github.com/bitcoin/bips/blob/master/bip-0155.mediawiki#signaling-support-and-compatibility]] + */ +case object SendAddrV2Message extends ControlPayload { + override val commandName: String = NetworkPayload.sendAddrV2CommandName + override val bytes: ByteVector = ByteVector.empty +} + /** * The feefilter message is a request to the receiving peer to not relay any transaction inv messages * to the sending peer where the fee rate for the transaction is below the fee rate specified in the @@ -1329,6 +1534,8 @@ object NetworkPayload { private[core] val notFoundCommandName = "notfound" private[core] val transactionCommandName = "tx" private[core] val addrCommandName = "addr" + private[core] val addrV2CommandName = "addrv2" + private[core] val sendAddrV2CommandName = "sendaddrv2" private[core] val feeFilterCommandName = "feefilter" private[core] val filterAddCommandName = "filteradd" private[core] val filterClearCommandName = "filterclear" @@ -1368,6 +1575,10 @@ object NetworkPayload { notFoundCommandName -> RawNotFoundMessageSerializer.read, transactionCommandName -> RawTransactionMessageSerializer.read, addrCommandName -> RawAddrMessageSerializer.read, + addrV2CommandName -> AddrV2Message.fromBytes, + sendAddrV2CommandName -> { _: ByteVector => + SendAddrV2Message + }, feeFilterCommandName -> RawFeeFilterMessageSerializer.read, filterAddCommandName -> RawFilterAddMessageSerializer.read, filterClearCommandName -> { _: ByteVector => diff --git a/node/src/main/scala/org/bitcoins/node/networking/peer/PeerMessageReceiver.scala b/node/src/main/scala/org/bitcoins/node/networking/peer/PeerMessageReceiver.scala index 84f94face4..c581e159ef 100644 --- a/node/src/main/scala/org/bitcoins/node/networking/peer/PeerMessageReceiver.scala +++ b/node/src/main/scala/org/bitcoins/node/networking/peer/PeerMessageReceiver.scala @@ -219,6 +219,11 @@ class PeerMessageReceiver( Future.successful(this) case _: AddrMessage => Future.successful(this) + case _: AddrV2Message => + sender.sendSendAddrV2Message() + Future.successful(this) + case SendAddrV2Message => + Future.successful(this) case _ @(_: FilterAddMessage | _: FilterLoadMessage | FilterClearMessage) => Future.successful(this) diff --git a/node/src/main/scala/org/bitcoins/node/networking/peer/PeerMessageSender.scala b/node/src/main/scala/org/bitcoins/node/networking/peer/PeerMessageSender.scala index b78c57b8b7..a8162757f7 100644 --- a/node/src/main/scala/org/bitcoins/node/networking/peer/PeerMessageSender.scala +++ b/node/src/main/scala/org/bitcoins/node/networking/peer/PeerMessageSender.scala @@ -90,6 +90,10 @@ case class PeerMessageSender(client: P2PClient)(implicit conf: NodeAppConfig) sendMsg(verackMsg) } + def sendSendAddrV2Message(): Future[Unit] = { + sendMsg(SendAddrV2Message) + } + /** Responds to a ping message */ def sendPong(ping: PingMessage): Future[Unit] = { val pong = PongMessage(ping.nonce)