diff --git a/core/src/main/java/org/bitcoinj/core/AddressMessage.java b/core/src/main/java/org/bitcoinj/core/AddressMessage.java index 9d05ee29692..1de5947a9da 100644 --- a/core/src/main/java/org/bitcoinj/core/AddressMessage.java +++ b/core/src/main/java/org/bitcoinj/core/AddressMessage.java @@ -1,6 +1,5 @@ /* - * Copyright 2011 Google Inc. - * Copyright 2014 Andreas Schildbach + * Copyright by the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,74 +18,18 @@ import java.io.IOException; import java.io.OutputStream; -import java.util.ArrayList; import java.util.Collections; import java.util.List; -/** - *
Represents an "addr" message on the P2P network, which contains broadcast IP addresses of other peers. This is - * one of the ways peers can find each other without using the DNS or IRC discovery mechanisms. However storing and - * using addr messages is not presently implemented.
- * - *Instances of this class are not safe for use by multiple threads.
- */ -public class AddressMessage extends Message { +public abstract class AddressMessage extends Message { - private static final long MAX_ADDRESSES = 1000; - private ListRepresents an "addr" message on the P2P network, which contains broadcast IP addresses of other peers. This is + * one of the ways peers can find each other without using the DNS or IRC discovery mechanisms. However storing and + * using addr messages is not presently implemented.
+ * + *Instances of this class are not safe for use by multiple threads.
+ */ +public class AddressV1Message extends AddressMessage { + + /** + * Construct a new 'addr' message. + * @param params NetworkParameters object. + * @param offset The location of the first payload byte within the array. + * @param serializer the serializer to use for this block. + * @param length The length of message if known. Usually this is provided when deserializing of the wire + * as the length will be provided as part of the header. If unknown then set to Message.UNKNOWN_LENGTH + * @throws ProtocolException + */ + AddressV1Message(NetworkParameters params, byte[] payload, int offset, MessageSerializer serializer, int length) throws ProtocolException { + super(params, payload, offset, serializer, length); + } + + /** + * Construct a new 'addr' message. + * @param params NetworkParameters object. + * @param serializer the serializer to use for this block. + * @param length The length of message if known. Usually this is provided when deserializing of the wire + * as the length will be provided as part of the header. If unknown then set to Message.UNKNOWN_LENGTH + * @throws ProtocolException + */ + AddressV1Message(NetworkParameters params, byte[] payload, MessageSerializer serializer, int length) throws ProtocolException { + super(params, payload, 0, serializer, length); + } + + AddressV1Message(NetworkParameters params, byte[] payload, int offset) throws ProtocolException { + super(params, payload, offset, params.getDefaultSerializer(), UNKNOWN_LENGTH); + } + + AddressV1Message(NetworkParameters params, byte[] payload) throws ProtocolException { + super(params, payload, 0, params.getDefaultSerializer(), UNKNOWN_LENGTH); + } + + @Override + protected void parse() throws ProtocolException { + final VarInt numAddressesVarInt = readVarInt(); + int numAddresses = numAddressesVarInt.intValue(); + // Guard against ultra large messages that will crash us. + if (numAddresses > MAX_ADDRESSES) + throw new ProtocolException("Address message too large."); + addresses = new ArrayList<>(numAddresses); + MessageSerializer serializer = this.serializer.withProtocolVersion(1); + length = numAddressesVarInt.getSizeInBytes(); + for (int i = 0; i < numAddresses; i++) { + PeerAddress addr = new PeerAddress(params, payload, cursor, this, serializer); + addresses.add(addr); + cursor += addr.getMessageSize(); + length += addr.getMessageSize(); + } + } + + public void addAddress(PeerAddress address) { + int protocolVersion = address.serializer.getProtocolVersion(); + if (protocolVersion != 1) + throw new IllegalStateException("invalid protocolVersion: " + protocolVersion); + + unCache(); + address.setParent(this); + addresses.add(address); + length = UNKNOWN_LENGTH; + } + + @Override + public String toString() { + return "addr: " + Utils.SPACE_JOINER.join(addresses); + } +} diff --git a/core/src/main/java/org/bitcoinj/core/AddressV2Message.java b/core/src/main/java/org/bitcoinj/core/AddressV2Message.java new file mode 100644 index 00000000000..7b214287be9 --- /dev/null +++ b/core/src/main/java/org/bitcoinj/core/AddressV2Message.java @@ -0,0 +1,94 @@ +/* + * Copyright by the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bitcoinj.core; + +import java.util.ArrayList; + +/** + *Represents an "addrv2" message on the P2P network, which contains broadcast IP addresses of other peers. This is + * one of the ways peers can find each other without using the DNS or IRC discovery mechanisms. However storing and + * using addrv2 messages is not presently implemented.
+ * + *See BIP155 for details.
+ * + *Instances of this class are not safe for use by multiple threads.
+ */ +public class AddressV2Message extends AddressMessage { + + /** + * Construct a new 'addrv2' message. + * @param params NetworkParameters object. + * @param offset The location of the first payload byte within the array. + * @param serializer the serializer to use for this block. + * @param length The length of message if known. Usually this is provided when deserializing of the wire + * as the length will be provided as part of the header. If unknown then set to Message.UNKNOWN_LENGTH + * @throws ProtocolException + */ + AddressV2Message(NetworkParameters params, byte[] payload, int offset, MessageSerializer serializer, int length) throws ProtocolException { + super(params, payload, offset, serializer, length); + } + + /** + * Construct a new 'addrv2' message. + * @param params NetworkParameters object. + * @param serializer the serializer to use for this block. + * @param length The length of message if known. Usually this is provided when deserializing of the wire + * as the length will be provided as part of the header. If unknown then set to Message.UNKNOWN_LENGTH + * @throws ProtocolException + */ + AddressV2Message(NetworkParameters params, byte[] payload, MessageSerializer serializer, int length) throws ProtocolException { + super(params, payload, 0, serializer, length); + } + + AddressV2Message(NetworkParameters params, byte[] payload) throws ProtocolException { + super(params, payload, 0, params.getDefaultSerializer(), UNKNOWN_LENGTH); + } + + @Override + protected void parse() throws ProtocolException { + final VarInt numAddressesVarInt = readVarInt(); + int numAddresses = numAddressesVarInt.intValue(); + // Guard against ultra large messages that will crash us. + if (numAddresses > MAX_ADDRESSES) + throw new ProtocolException("Address message too large."); + addresses = new ArrayList<>(numAddresses); + MessageSerializer serializer = this.serializer.withProtocolVersion(2); + length = numAddressesVarInt.getSizeInBytes(); + for (int i = 0; i < numAddresses; i++) { + PeerAddress addr = new PeerAddress(params, payload, cursor, this, serializer); + addresses.add(addr); + cursor += addr.getMessageSize(); + length += addr.getMessageSize(); + } + } + + public void addAddress(PeerAddress address) { + int protocolVersion = address.serializer.getProtocolVersion(); + if (protocolVersion != 2) + throw new IllegalStateException("invalid protocolVersion: " + protocolVersion); + + unCache(); + address.setParent(this); + addresses.add(address); + length = UNKNOWN_LENGTH; + } + + @Override + public String toString() { + return "addrv2: " + Utils.SPACE_JOINER.join(addresses); + } +} diff --git a/core/src/main/java/org/bitcoinj/core/BitcoinSerializer.java b/core/src/main/java/org/bitcoinj/core/BitcoinSerializer.java index dfac351d550..3ddc3084817 100644 --- a/core/src/main/java/org/bitcoinj/core/BitcoinSerializer.java +++ b/core/src/main/java/org/bitcoinj/core/BitcoinSerializer.java @@ -59,13 +59,15 @@ public class BitcoinSerializer extends MessageSerializer { names.put(Block.class, "block"); names.put(GetDataMessage.class, "getdata"); names.put(Transaction.class, "tx"); - names.put(AddressMessage.class, "addr"); + names.put(AddressV1Message.class, "addr"); + names.put(AddressV2Message.class, "addrv2"); names.put(Ping.class, "ping"); names.put(Pong.class, "pong"); names.put(VersionAck.class, "verack"); names.put(GetBlocksMessage.class, "getblocks"); names.put(GetHeadersMessage.class, "getheaders"); names.put(GetAddrMessage.class, "getaddr"); + names.put(SendAddrV2Message.class, "sendaddrv2"); names.put(HeadersMessage.class, "headers"); names.put(BloomFilter.class, "filterload"); names.put(FilteredBlock.class, "merkleblock"); @@ -231,8 +233,12 @@ private Message makeMessage(String command, int length, byte[] payloadBytes, byt return new GetHeadersMessage(params, payloadBytes); } else if (command.equals("tx")) { return makeTransaction(payloadBytes, 0, length, hash); + } else if (command.equals("sendaddrv2")) { + return new SendAddrV2Message(params); } else if (command.equals("addr")) { - return makeAddressMessage(payloadBytes, length); + return makeAddressV1Message(payloadBytes, length); + } else if (command.equals("addrv2")) { + return makeAddressV2Message(payloadBytes, length); } else if (command.equals("ping")) { return new Ping(params, payloadBytes); } else if (command.equals("pong")) { @@ -272,8 +278,17 @@ public NetworkParameters getParameters() { * serialization format support. */ @Override - public AddressMessage makeAddressMessage(byte[] payloadBytes, int length) throws ProtocolException { - return new AddressMessage(params, payloadBytes, this, length); + public AddressV1Message makeAddressV1Message(byte[] payloadBytes, int length) throws ProtocolException { + return new AddressV1Message(params, payloadBytes, this, length); + } + + /** + * Make an address message from the payload. Extension point for alternative + * serialization format support. + */ + @Override + public AddressV2Message makeAddressV2Message(byte[] payloadBytes, int length) throws ProtocolException { + return new AddressV2Message(params, payloadBytes, this, length); } /** diff --git a/core/src/main/java/org/bitcoinj/core/DummySerializer.java b/core/src/main/java/org/bitcoinj/core/DummySerializer.java index 053642b4548..c3ab72aa945 100644 --- a/core/src/main/java/org/bitcoinj/core/DummySerializer.java +++ b/core/src/main/java/org/bitcoinj/core/DummySerializer.java @@ -64,7 +64,12 @@ public boolean isParseRetainMode() { } @Override - public AddressMessage makeAddressMessage(byte[] payloadBytes, int length) throws UnsupportedOperationException { + public AddressV1Message makeAddressV1Message(byte[] payloadBytes, int length) throws UnsupportedOperationException { + throw new UnsupportedOperationException(DEFAULT_EXCEPTION_MESSAGE); + } + + @Override + public AddressV2Message makeAddressV2Message(byte[] payloadBytes, int length) throws UnsupportedOperationException { throw new UnsupportedOperationException(DEFAULT_EXCEPTION_MESSAGE); } diff --git a/core/src/main/java/org/bitcoinj/core/Message.java b/core/src/main/java/org/bitcoinj/core/Message.java index e1640902f9e..9fc0a137cc4 100644 --- a/core/src/main/java/org/bitcoinj/core/Message.java +++ b/core/src/main/java/org/bitcoinj/core/Message.java @@ -305,6 +305,12 @@ protected VarInt readVarInt(int offset) throws ProtocolException { } } + private void checkReadLength(int length) throws ProtocolException { + if ((length > MAX_SIZE) || (cursor + length > payload.length)) { + throw new ProtocolException("Claimed value length too large: " + length); + } + } + protected byte[] readBytes(int length) throws ProtocolException { if ((length > MAX_SIZE) || (cursor + length > payload.length)) { throw new ProtocolException("Claimed value length too large: " + length); @@ -319,6 +325,11 @@ protected byte[] readBytes(int length) throws ProtocolException { } } + protected byte readByte() throws ProtocolException { + checkReadLength(1); + return payload[cursor++]; + } + protected byte[] readByteArray() throws ProtocolException { final int length = readVarInt().intValue(); return readBytes(length); diff --git a/core/src/main/java/org/bitcoinj/core/MessageSerializer.java b/core/src/main/java/org/bitcoinj/core/MessageSerializer.java index bf5ab9d3475..e4f142b1e8f 100644 --- a/core/src/main/java/org/bitcoinj/core/MessageSerializer.java +++ b/core/src/main/java/org/bitcoinj/core/MessageSerializer.java @@ -65,7 +65,13 @@ public abstract class MessageSerializer { * Make an address message from the payload. Extension point for alternative * serialization format support. */ - public abstract AddressMessage makeAddressMessage(byte[] payloadBytes, int length) throws ProtocolException, UnsupportedOperationException; + public abstract AddressV1Message makeAddressV1Message(byte[] payloadBytes, int length) throws ProtocolException, UnsupportedOperationException; + + /** + * Make an address message from the payload. Extension point for alternative + * serialization format support. + */ + public abstract AddressV2Message makeAddressV2Message(byte[] payloadBytes, int length) throws ProtocolException, UnsupportedOperationException; /** * Make a block from the payload, using an offset of zero and the payload diff --git a/core/src/main/java/org/bitcoinj/core/Peer.java b/core/src/main/java/org/bitcoinj/core/Peer.java index 2cccf1fdbd6..7f9306019fe 100644 --- a/core/src/main/java/org/bitcoinj/core/Peer.java +++ b/core/src/main/java/org/bitcoinj/core/Peer.java @@ -455,7 +455,7 @@ protected void processMessage(Message m) throws Exception { } // No further communication is possible until version handshake is complete. - if (!(m instanceof VersionMessage || m instanceof VersionAck + if (!(m instanceof VersionMessage || m instanceof VersionAck || m instanceof SendAddrV2Message || (versionHandshakeFuture.isDone() && !versionHandshakeFuture.isCancelled()))) throw new ProtocolException( "Received " + m.getClass().getSimpleName() + " before version handshake is complete."); @@ -557,6 +557,8 @@ private void processVersionMessage(VersionMessage peerVersionMessage) throws Pro // In this case, it's a protocol violation. throw new ProtocolException("Peer reports invalid best height: " + peerVersionMessage.bestHeight); // Now it's our turn ... + // Send a sendaddrv2 message, indicating that we prefer to receive addrv2 messages. + sendMessage(new SendAddrV2Message(params)); // Send an ACK message stating we accept the peers protocol version. sendMessage(new VersionAck()); if (log.isDebugEnabled()) diff --git a/core/src/main/java/org/bitcoinj/core/PeerAddress.java b/core/src/main/java/org/bitcoinj/core/PeerAddress.java index a6fba2a0a0a..e909f166eeb 100644 --- a/core/src/main/java/org/bitcoinj/core/PeerAddress.java +++ b/core/src/main/java/org/bitcoinj/core/PeerAddress.java @@ -17,44 +17,42 @@ package org.bitcoinj.core; -import org.bitcoinj.net.OnionCatAddressChecker; -import org.bitcoinj.net.OnionCatConverter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.io.OutputStream; import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; +import java.util.Objects; import static com.google.common.base.Preconditions.checkNotNull; /** *A PeerAddress holds an IP address and port number representing the network location of * a peer in the Bitcoin P2P network. It exists primarily for serialization purposes.
- * + * + *This class abuses the protocol version contained in its serializer. It can only contain 0 (format within + * {@link VersionMessage}), 1 ({@link AddressV1Message}) or 2 ({@link AddressV2Message}).
+ * *Instances of this class are not safe for use by multiple threads.
*/ public class PeerAddress extends ChildMessage { - private static final Logger log = LoggerFactory.getLogger(PeerAddress.class); - - static final int MESSAGE_SIZE = 30; //addr and hostname are alternatives and should not be used together. private InetAddress addr; - private String hostname; - + private String hostname; // Used for .onion addresses private int port; private BigInteger services; private long time; /** * Construct a peer address from a serialized payload. - * @param params NetworkParameters object. - * @param payload Bitcoin protocol formatted byte array containing message content. - * @param offset The location of the first payload byte within the array. + * + * @param params NetworkParameters object. + * @param payload Bitcoin protocol formatted byte array containing message content. + * @param offset The location of the first payload byte within the array. * @param serializer the serializer to use for this message. * @throws ProtocolException */ @@ -71,14 +69,21 @@ public PeerAddress(NetworkParameters params, InetAddress addr, int port, BigInte this.port = port; setSerializer(serializer); this.services = services; - length = isSerializeTime() ? MESSAGE_SIZE : MESSAGE_SIZE - 4; + this.time = Utils.currentTimeSeconds(); + } + + /** + * Constructs a peer address from the given IP address, port and services. Version number is default for the given parameters. + */ + public PeerAddress(NetworkParameters params, InetAddress addr, int port, BigInteger services) { + this(params, addr, port, services, params.getDefaultSerializer().withProtocolVersion(0)); } /** * Constructs a peer address from the given IP address and port. Version number is default for the given parameters. */ public PeerAddress(NetworkParameters params, InetAddress addr, int port) { - this(params, addr, port, BigInteger.ZERO, params.getDefaultSerializer()); + this(params, addr, port, BigInteger.ZERO); } /** @@ -98,25 +103,22 @@ public PeerAddress(NetworkParameters params, InetSocketAddress addr) { } /** - * Constructs a peer address from a stringified hostname+port. + * Constructs a peer address from a stringified hostname+port+services. */ - public PeerAddress(NetworkParameters params, String hostname, int port) { - super(params); - this.hostname = hostname; - this.port = port; - this.services = BigInteger.ZERO; + public PeerAddress(NetworkParameters params, String hostname, int port, BigInteger services) { + this(params, hostname, port); + this.services = services; } /** - * Construct a peer address from a memorized or hardcoded hostname. + * Constructs a peer address from a stringified hostname+port. */ - public PeerAddress(NetworkParameters params, String hostname, int port, int protocolVersion, BigInteger services) { + public PeerAddress(NetworkParameters params, String hostname, int port) { super(params); this.hostname = hostname; this.port = port; - setSerializer(serializer.withProtocolVersion(protocolVersion)); - this.services = services; - length = isSerializeTime() ? MESSAGE_SIZE : MESSAGE_SIZE - 4; + this.services = BigInteger.ZERO; + this.time = Utils.currentTimeSeconds(); } public static PeerAddress localhost(NetworkParameters params) { @@ -125,72 +127,110 @@ public static PeerAddress localhost(NetworkParameters params) { @Override protected void bitcoinSerializeToStream(OutputStream stream) throws IOException { - if (isSerializeTime()) { - //TODO this appears to be dynamic because the client only ever sends out it's own address - //so assumes itself to be up. For a fuller implementation this needs to be dynamic only if - //the address refers to this client. - int secs = (int) (Utils.currentTimeSeconds()); - Utils.uint32ToByteStreamLE(secs, stream); - } - Utils.uint64ToByteStreamLE(services, stream); // nServices. + int protocolVersion = serializer.getProtocolVersion(); + if (protocolVersion < 0 || protocolVersion > 2) + throw new IllegalStateException("invalid protocolVersion: " + protocolVersion); - byte[] ipBytes; - if (hostname!=null) { - if (hostname.endsWith(".onion")) { - ipBytes = OnionCatConverter.onionHostToIPV6Bytes(hostname); + if (protocolVersion >= 1) { + Utils.uint32ToByteStreamLE(time, stream); + } + if (protocolVersion == 2) { + stream.write(new VarInt(services.longValue()).encode()); + if (addr != null) { + if (addr instanceof Inet4Address) { + stream.write(0x01); + stream.write(new VarInt(4).encode()); + stream.write(addr.getAddress()); + } else if (addr instanceof Inet6Address) { + stream.write(0x02); + stream.write(new VarInt(16).encode()); + stream.write(addr.getAddress()); + } else { + throw new IllegalStateException(); + } } else { - ipBytes = new byte[16]; - } - } else if( addr != null ) { - // Java does not provide any utility to map an IPv4 address into IPv6 space, so we have to do it by hand. - ipBytes = addr.getAddress(); - if (ipBytes.length == 4) { - byte[] v6addr = new byte[16]; - System.arraycopy(ipBytes, 0, v6addr, 12, 4); - v6addr[10] = (byte) 0xFF; - v6addr[11] = (byte) 0xFF; - ipBytes = v6addr; + throw new IllegalStateException(); } } else { - throw new IllegalStateException("Either hostname or addr should be not null"); + Utils.uint64ToByteStreamLE(services, stream); // nServices. + if (addr != null) { + // Java does not provide any utility to map an IPv4 address into IPv6 space, so we have to do it by + // hand. + byte[] ipBytes = addr.getAddress(); + if (ipBytes.length == 4) { + byte[] v6addr = new byte[16]; + System.arraycopy(ipBytes, 0, v6addr, 12, 4); + v6addr[10] = (byte) 0xFF; + v6addr[11] = (byte) 0xFF; + ipBytes = v6addr; + } + stream.write(ipBytes); + } else { + throw new IllegalStateException(); + } } - stream.write(ipBytes); // And write out the port. Unlike the rest of the protocol, address and port is in big endian byte order. Utils.uint16ToByteStreamBE(port, stream); } - private boolean isSerializeTime() { - return serializer.getProtocolVersion() >= 31402 && !(parent instanceof VersionMessage); - } - @Override protected void parse() throws ProtocolException { - // Format of a serialized address: - // uint32 timestamp - // uint64 services (flags determining what the node can do) - // 16 bytes ip address - // 2 bytes port num - if (isSerializeTime()) + int protocolVersion = serializer.getProtocolVersion(); + if (protocolVersion < 0 || protocolVersion > 2) + throw new IllegalStateException("invalid protocolVersion: " + protocolVersion); + + length = 0; + if (protocolVersion >= 1) { time = readUint32(); - else + length += 4; + } else { time = -1; - services = readUint64(); - byte[] addrBytes = readBytes(16); - InetAddress inetAddress; - try { - inetAddress = InetAddress.getByAddress(addrBytes); - } catch (UnknownHostException e) { - throw new RuntimeException(e); // Cannot happen. } - if(OnionCatAddressChecker.isOnionCatTor(inetAddress)) { - hostname = OnionCatConverter.IPV6BytesToOnionHost(inetAddress.getAddress()); + if (protocolVersion == 2) { + VarInt servicesVarInt = readVarInt(); + length += servicesVarInt.getSizeInBytes(); + services = BigInteger.valueOf(servicesVarInt.longValue()); + int networkId = readByte(); + length += 1; + byte[] addrBytes = readByteArray(); + int addrLen = addrBytes.length; + length += VarInt.sizeOf(addrLen) + addrLen; + if (networkId == 0x01) { + // IPv4 + if (addrLen != 4) + throw new ProtocolException("invalid length of IPv4 address: " + addrLen); + addr = getByAddress(addrBytes); + hostname = null; + } else if (networkId == 0x02) { + // IPv6 + if (addrLen != 16) + throw new ProtocolException("invalid length of IPv6 address: " + addrLen); + addr = getByAddress(addrBytes); + hostname = null; + } else { + // ignore unknown network IDs + addr = null; + hostname = null; + } } else { - addr = inetAddress; + services = readUint64(); + length += 8; + byte[] addrBytes = readBytes(16); + length += 16; + addr = getByAddress(addrBytes); + hostname = null; } port = Utils.readUint16BE(payload, cursor); cursor += 2; - // The 4 byte difference is the uint32 timestamp that was introduced in version 31402 - length = isSerializeTime() ? MESSAGE_SIZE : MESSAGE_SIZE - 4; + length += 2; + } + + private static InetAddress getByAddress(byte[] addrBytes) { + try { + return InetAddress.getByAddress(addrBytes); + } catch (UnknownHostException e) { + throw new RuntimeException(e); // Cannot happen. + } } public String getHostname() { @@ -222,7 +262,7 @@ public String toString() { if (hostname != null) { return "[" + hostname + "]:" + port; } - if(addr != null ) { + if (addr != null) { return "[" + addr.getHostAddress() + "]:" + port; } return "[]"; @@ -250,11 +290,10 @@ public boolean equalsIgnoringMetadata(Object o) { if (port != that.port) return false; // Don't compare the time field // if (time != that.time) return false; - if (addr != null ? !addr.equals(that.addr) : that.addr != null) return false; - if (hostname != null ? !hostname.equals(that.hostname) : that.hostname != null) return false; + if (!Objects.equals(addr, that.addr)) return false; + return Objects.equals(hostname, that.hostname); // Don't compare the services field // return !(services != null ? !services.equals(that.services) : that.services != null); - return true; } @Override diff --git a/core/src/main/java/org/bitcoinj/core/SendAddrV2Message.java b/core/src/main/java/org/bitcoinj/core/SendAddrV2Message.java new file mode 100644 index 00000000000..9e45ffd2d49 --- /dev/null +++ b/core/src/main/java/org/bitcoinj/core/SendAddrV2Message.java @@ -0,0 +1,31 @@ +/* + * Copyright by the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bitcoinj.core; + +/** + *Represents the {@code sendaddrv2} P2P protocol message, which indicates that a node can understand and prefers + * to receive {@code addrv2] messages instead of {@code addr} messages.
+ * + *See BIP155 for details.
+ * + *Instances of this class are not safe for use by multiple threads.
+ */ +public class SendAddrV2Message extends EmptyMessage { + public SendAddrV2Message(NetworkParameters params) { + super(params); + } +} diff --git a/core/src/main/java/org/bitcoinj/core/VersionMessage.java b/core/src/main/java/org/bitcoinj/core/VersionMessage.java index b19de5581a9..74758e5ac97 100644 --- a/core/src/main/java/org/bitcoinj/core/VersionMessage.java +++ b/core/src/main/java/org/bitcoinj/core/VersionMessage.java @@ -112,6 +112,7 @@ public VersionMessage(NetworkParameters params, int newBestHeight) { // Note that the Bitcoin Core doesn't do anything with these, and finding out your own external IP address // is kind of tricky anyway, so we just put nonsense here for now. InetAddress localhost = InetAddresses.forString("127.0.0.1"); + MessageSerializer serializer = this.serializer.withProtocolVersion(0); receivingAddr = new PeerAddress(params, localhost, params.getPort(), BigInteger.ZERO, serializer); receivingAddr.setParent(this); fromAddr = new PeerAddress(params, localhost, params.getPort(), BigInteger.ZERO, serializer); @@ -119,9 +120,6 @@ public VersionMessage(NetworkParameters params, int newBestHeight) { subVer = LIBRARY_SUBVER; bestHeight = newBestHeight; relayTxesBeforeFilter = true; - - length = 4 + 8 + 8 + receivingAddr.getMessageSize() + fromAddr.getMessageSize() + 8 - + VarInt.sizeOf(subVer.length()) + subVer.length() + 4 + 1; } @Override diff --git a/core/src/main/java/org/bitcoinj/wallet/WalletProtobufSerializer.java b/core/src/main/java/org/bitcoinj/wallet/WalletProtobufSerializer.java index 48711438382..dd0142158c9 100644 --- a/core/src/main/java/org/bitcoinj/wallet/WalletProtobufSerializer.java +++ b/core/src/main/java/org/bitcoinj/wallet/WalletProtobufSerializer.java @@ -449,7 +449,7 @@ public Wallet readWallet(InputStream input, boolean forceReset, @Nullable Wallet if (params == null) throw new UnreadableWalletException("Unknown network parameters ID " + paramsID); return readWallet(params, extensions, walletProto, forceReset); - } catch (IOException | IllegalStateException | IllegalArgumentException e) { + } catch (IOException | IllegalArgumentException | IllegalStateException e) { throw new UnreadableWalletException("Could not parse input stream to protobuf", e); } } @@ -822,7 +822,7 @@ private void readConfidence(final NetworkParameters params, final Transaction tx if (ip != null) { address = new PeerAddress(params, ip, port, services, params.getDefaultSerializer()); } else { - address = new PeerAddress(params, hostname, port, params.getDefaultSerializer().getProtocolVersion(), services); + address = new PeerAddress(params, hostname, port, services); } confidence.markBroadcastBy(address); } diff --git a/core/src/test/java/org/bitcoinj/core/AddressV1MessageTest.java b/core/src/test/java/org/bitcoinj/core/AddressV1MessageTest.java new file mode 100644 index 00000000000..07db7e48528 --- /dev/null +++ b/core/src/test/java/org/bitcoinj/core/AddressV1MessageTest.java @@ -0,0 +1,84 @@ +/* + * Copyright by the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bitcoinj.core; + +import org.bitcoinj.params.UnitTestParams; +import org.junit.Test; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.util.List; + +import static org.bitcoinj.core.Utils.HEX; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class AddressV1MessageTest { + + private static final NetworkParameters UNITTEST = UnitTestParams.get(); + // mostly copied from src/test/netbase_tests.cpp#stream_addrv1_hex + private static final String MESSAGE_HEX = + "03" // number of entries + + + "61bc6649" // time, Fri Jan 9 02:54:25 UTC 2009 + + "0000000000000000" // service flags, NODE_NONE + + "00000000000000000000ffff00000001" // address, fixed 16 bytes (IPv4 embedded in IPv6) + + "0000" // port + + + "79627683" // time, Tue Nov 22 11:22:33 UTC 2039 + + "0100000000000000" // service flags, NODE_NETWORK + + "00000000000000000000000000000001" // address, fixed 16 bytes (IPv6) + + "00f1" // port + + + "ffffffff" // time, Sun Feb 7 06:28:15 UTC 2106 + + "4804000000000000" // service flags, NODE_WITNESS | NODE_COMPACT_FILTERS | NODE_NETWORK_LIMITED + + "00000000000000000000000000000001" // address, fixed 16 bytes (IPv6) + + "f1f2"; // port + + @Test + public void roundtrip() { + AddressMessage message = new AddressV1Message(UNITTEST, HEX.decode(MESSAGE_HEX)); + + List