Skip to content

Commit

Permalink
More proper way of reading byte arrays, improved handling of incorrec…
Browse files Browse the repository at this point in the history
…t magic bytes. Fixed testnet magic.
  • Loading branch information
satsen committed Aug 16, 2023
1 parent 353ca50 commit 65de518
Show file tree
Hide file tree
Showing 15 changed files with 97 additions and 66 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ while (true) {
### Example: Transactions
Listen for transactions sent to the network: [TransactionLoggerExample.java](/examples/TransactionLoggerExample.java).

### Testnet
If you want to connect to a testnet node instead, you need to provide the magic bytes for that like this:
```java
ErgoSocket ergoSocket = new ErgoSocket(address, self, ErgoSocket.TESTNET_MAGIC);
```

## Notes

Please understand that ergonnection is only a networking library.
Expand Down
42 changes: 26 additions & 16 deletions src/main/java/com/satergo/ergonnection/ErgoSocket.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,17 @@
import com.satergo.ergonnection.records.Peer;
import org.bouncycastle.jcajce.provider.digest.Blake2b;

import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class ErgoSocket extends Socket {

public static final byte[] MAINNET_MAGIC = { 1, 0, 2, 4 }, TESTNET_MAGIC = { 2, 0, 0, 1 };
public static final byte[] MAINNET_MAGIC = { 1, 0, 2, 4 }, TESTNET_MAGIC = { 2, 0, 2, 3 };

/**
* A basic feature set specifying that the state is "utxo", the client is verifying transactions, has no PoPoW suffix, and 1 block is stored.
Expand All @@ -34,10 +32,10 @@ public class ErgoSocket extends Socket {
private final DataInputStream in;

/**
* Creates a new socket for the mainnet
* Creates a new socket
*
* @param address Address to connect to
* @param self A Peer object that represent this client itself (this is the data that will be sent to the target peer)
* @param self A Peer object that represents this client itself (this is the data that will be sent to the target peer)
* @param networkMagic The magic bytes of this network, see {@link #MAINNET_MAGIC} and {@link #TESTNET_MAGIC}
* @throws IOException socket exception
*/
Expand All @@ -52,7 +50,7 @@ public ErgoSocket(InetSocketAddress address, Peer self, byte[] networkMagic) thr
* Creates a new socket for the mainnet
*
* @param address Address to connect to
* @param self A Peer object that represent this client itself (this is the data that will be sent to the target peer)
* @param self A Peer object that represents this client itself (this is the data that will be sent to the target peer)
* @throws IOException socket exception
*/
public ErgoSocket(InetSocketAddress address, Peer self) throws IOException {
Expand Down Expand Up @@ -90,15 +88,27 @@ public void send(ProtocolMessage message) throws IOException {
}
}

/**
* @throws SocketException if the socket is closed, or it was closed while the message was being read
*/
public ProtocolMessage acceptMessage() throws IOException {
byte[] magic = in.readNBytes(4);
if (!Arrays.equals(networkMagic, magic))
throw new IllegalArgumentException("incorrect magic " + Arrays.toString(magic) + " (must be " + Arrays.toString(networkMagic) + ")");
int code = in.readUnsignedByte();
int length = in.readInt();
in.skipNBytes(4); // checksum
byte[] bytes = in.readNBytes(length);
return Protocol.deserializeMessage(code, bytes);
try {
byte[] magic = InternalStreamUtils.readNFully(in, 4);
if (!Arrays.equals(networkMagic, magic)) {
close();
throw new IllegalArgumentException("Incorrect magic " + Arrays.toString(magic) + " received (must be " + Arrays.toString(networkMagic) + ")");
}
int code = in.readUnsignedByte();
int length = in.readInt();
in.skipNBytes(4); // checksum
byte[] bytes = InternalStreamUtils.readNFully(in, length);
return Protocol.deserializeMessage(code, bytes);
} catch (EOFException e) {
// Receiving this exception is due to the socket being closed, but it will not have
// been marked as such so this method is called
close();
throw new SocketException("Socket is closed");
}
}

public void sendHandshake() throws IOException {
Expand Down
31 changes: 31 additions & 0 deletions src/main/java/com/satergo/ergonnection/InternalStreamUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.satergo.ergonnection;

import java.io.DataInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class InternalStreamUtils {
private InternalStreamUtils() {}

public static void writeUTF8ByteLen(VLQOutputStream out, String s) throws IOException {
if (s.length() > 255) throw new IllegalArgumentException("too long");
byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
out.write(bytes.length);
out.write(bytes);
}

public static String readUTF8ByteLen(VLQInputStream in) throws IOException {
int len = in.read();
byte[] bytes = in.readNFully(len);
return new String(bytes, StandardCharsets.UTF_8);
}

/**
* @throws java.io.EOFException if this input stream reaches the end before reading all the bytes.
*/
public static byte[] readNFully(DataInputStream in, int length) throws IOException {
byte[] bytes = new byte[length];
in.readFully(bytes);
return bytes;
}
}
23 changes: 0 additions & 23 deletions src/main/java/com/satergo/ergonnection/StreamUTF8.java

This file was deleted.

6 changes: 6 additions & 0 deletions src/main/java/com/satergo/ergonnection/VLQInputStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ public void readFully(byte[] b, int off, int len) throws IOException {
}
}

public byte[] readNFully(int length) throws IOException {
byte[] bytes = new byte[length];
readFully(bytes);
return bytes;
}

public int skipBytes(int n) throws IOException {
int total = 0;
int cur;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/satergo/ergonnection/messages/Inv.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public static Inv deserialize(VLQInputStream in) throws IOException {
int count = (int) in.readUnsignedInt();
ArrayList<byte[]> elements = new ArrayList<>();
for (int i = 0; i < count; i++) {
elements.add(in.readNBytes(32));
elements.add(in.readNFully(32));
}
return new Inv(typeId, Collections.unmodifiableList(elements));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public static ModifierRequest deserialize(VLQInputStream in) throws IOException
int count = (int) in.readUnsignedInt();
ArrayList<byte[]> elements = new ArrayList<>();
for (int i = 0; i < count; i++) {
elements.add(in.readNBytes(32));
elements.add(in.readNFully(32));
}
return new ModifierRequest(typeId, Collections.unmodifiableList(elements));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public static SyncInfoOld deserialize(VLQInputStream in) throws IOException {
int count = in.readUnsignedShort();
ArrayList<byte[]> lastHeaderIDs = new ArrayList<>();
for (int i = 0; i < count; i++) {
lastHeaderIDs.add(in.readNBytes(32));
lastHeaderIDs.add(in.readNFully(32));
}
return new SyncInfoOld(Collections.unmodifiableList(lastHeaderIDs));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ public static ErgoTransaction deserialize(byte[] id, byte[] data) throws IOExcep
int dataInputCount = in.readUnsignedShort();
ArrayList<DataInput> dataInputs = new ArrayList<>();
for (int i = 0; i < dataInputCount; i++) {
dataInputs.add(new DataInput(in.readNBytes(32)));
dataInputs.add(new DataInput(in.readNFully(32)));
}

// parse distinct ids of tokens in transaction outputs
int tokensCount = (int) in.readUnsignedInt();
ArrayList<TokenId> tokens = new ArrayList<>();
for (int i = 0; i < tokensCount; i++) {
tokens.add(new TokenId(in.readNBytes(32)));
tokens.add(new TokenId(in.readNFully(32)));
}

int outputCandidatesCount = in.readUnsignedShort();
Expand Down
14 changes: 7 additions & 7 deletions src/main/java/com/satergo/ergonnection/modifiers/Header.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@ public record Header(byte[] id, byte version, byte[] parentId, byte[] adProofsRo
public static Header deserialize(byte[] id, byte[] data) throws IOException {
VLQInputStream in = new VLQInputStream(new ByteArrayInputStream(data));
byte version = in.readByte();
byte[] parentId = in.readNBytes(32);
byte[] adProofsRoot = in.readNBytes(32);
byte[] transactionsRoot = in.readNBytes(32);
byte[] stateRoot = in.readNBytes(33);
byte[] parentId = in.readNFully(32);
byte[] adProofsRoot = in.readNFully(32);
byte[] transactionsRoot = in.readNFully(32);
byte[] stateRoot = in.readNFully(33);
long timestamp = in.readUnsignedLong();
byte[] extensionHash = in.readNBytes(32);
byte[] nBitsBytes = in.readNBytes(4);
byte[] extensionHash = in.readNFully(32);
byte[] nBitsBytes = in.readNFully(4);
long nBits = ((nBitsBytes[0] & 0xFFL) << 24) | ((nBitsBytes[1] & 0xFFL) << 16) | ((nBitsBytes[2] & 0xFFL) << 8) | (nBitsBytes[3] & 0xFFL);
int height = Math.toIntExact(in.readUnsignedInt());
byte[] votes = in.readNBytes(3);
byte[] votes = in.readNFully(3);
if (version > INITIAL_VERSION) {
int newFieldsSize = in.readUnsignedByte();
in.skipNBytes(newFieldsSize);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ public record AutolykosSolution(Platform.Ecp minerPubKey, Platform.Ecp oneTimePu

public static AutolykosSolution deserialize(VLQInputStream in, byte version) throws IOException {
if (version == 1) {
Platform.Ecp minerPublicKey = groupElemFromBytes(in.readNBytes(PUBLIC_KEY_LENGTH));
Platform.Ecp w = groupElemFromBytes(in.readNBytes(PUBLIC_KEY_LENGTH));
byte[] nonce = in.readNBytes(8);
Platform.Ecp minerPublicKey = groupElemFromBytes(in.readNFully(PUBLIC_KEY_LENGTH));
Platform.Ecp w = groupElemFromBytes(in.readNFully(PUBLIC_KEY_LENGTH));
byte[] nonce = in.readNFully(8);
int dBytesLength = in.readUnsignedByte();
BigInteger d = BigIntegers.fromUnsignedByteArray(in.readNBytes(dBytesLength));
BigInteger d = BigIntegers.fromUnsignedByteArray(in.readNFully(dBytesLength));
return new AutolykosSolution(minerPublicKey, w, nonce, d);
} else {
Platform.Ecp minerPublicKey = groupElemFromBytes(in.readNBytes(PUBLIC_KEY_LENGTH));
byte[] nonce = in.readNBytes(8);
Platform.Ecp minerPublicKey = groupElemFromBytes(in.readNFully(PUBLIC_KEY_LENGTH));
byte[] nonce = in.readNFully(8);
return new AutolykosSolution(minerPublicKey, CryptoConstants.dlogGroup().generator(), nonce, BigInteger.ZERO);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
public record Feature(int id, byte[] data) implements ProtocolRecord {

public static Feature deserialize(VLQInputStream in) throws IOException {
return new Feature(in.readUnsignedByte(), in.readNBytes(in.readUnsignedShort()));
return new Feature(in.readUnsignedByte(), in.readNFully(in.readUnsignedShort()));
}

@Override
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/satergo/ergonnection/records/Header.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
public record Header(byte[] bytes) implements ProtocolRecord {

public static Header deserialize(VLQInputStream in) throws IOException {
return new Header(in.readNBytes(in.readUnsignedShort()));
return new Header(in.readNFully(in.readUnsignedShort()));
}

@Override
Expand Down
12 changes: 6 additions & 6 deletions src/main/java/com/satergo/ergonnection/records/Peer.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.satergo.ergonnection.records;

import com.satergo.ergonnection.StreamUTF8;
import com.satergo.ergonnection.InternalStreamUtils;
import com.satergo.ergonnection.VLQInputStream;
import com.satergo.ergonnection.VLQOutputStream;
import com.satergo.ergonnection.Version;
Expand Down Expand Up @@ -33,16 +33,16 @@ public boolean hasPublicAddress() {
}

public static Peer deserialize(VLQInputStream in) throws IOException {
String agentName = StreamUTF8.readByteLen(in);
String agentName = InternalStreamUtils.readUTF8ByteLen(in);
Version version = Version.parse(in.readByte() + "." + in.readByte() + "." + in.readByte());
String peerName = StreamUTF8.readByteLen(in);
String peerName = InternalStreamUtils.readUTF8ByteLen(in);
boolean hasPublicAddress = in.readBoolean();
InetSocketAddress publicAddress = null;
if (hasPublicAddress) {
// Protocol for some reason encodes it as length + 4
int publicAddressLength = in.readUnsignedByte() - 4;
publicAddress = new InetSocketAddress(
InetAddress.getByAddress(in.readNBytes(publicAddressLength)),
InetAddress.getByAddress(in.readNFully(publicAddressLength)),
// For some reason, it uses u-int instead of u-short
(int) in.readUnsignedInt()
);
Expand All @@ -57,11 +57,11 @@ public static Peer deserialize(VLQInputStream in) throws IOException {

@Override
public void serialize(VLQOutputStream out) throws IOException {
StreamUTF8.writeByteLen(out, agentName);
InternalStreamUtils.writeUTF8ByteLen(out, agentName);
out.write(version().major());
out.write(version().minor());
out.write(version().patch());
StreamUTF8.writeByteLen(out, peerName);
InternalStreamUtils.writeUTF8ByteLen(out, peerName);
out.writeBoolean(hasPublicAddress());
if (hasPublicAddress()) {
InetAddress address = publicAddress.getAddress();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.satergo.ergonnection.records;

import com.satergo.ergonnection.InternalStreamUtils;
import com.satergo.ergonnection.VLQInputStream;
import com.satergo.ergonnection.VLQOutputStream;
import com.satergo.ergonnection.protocol.ProtocolRecord;
Expand All @@ -13,7 +14,7 @@ public record RawModifier(int typeId, byte[] id, byte[] object) implements Proto
}

public static RawModifier deserialize(int typeId, VLQInputStream in) throws IOException {
return new RawModifier(typeId, in.readNBytes(32), in.readNBytes((int) in.readUnsignedInt()));
return new RawModifier(typeId, in.readNFully(32), in.readNFully((int) in.readUnsignedInt()));
}

@Override
Expand Down

0 comments on commit 65de518

Please sign in to comment.