forked from Qortal/qortal
New network handshaking. NOT backwards compatible!
Old Qora v1 message types removed. Message type values changed. Network handshaking reworked to fix multiple-connections issue. Instead of using some random peerID, we now use proper keypairs and a challenge-response handshake to prevent doppelgangers/ID-theft. This results in simpler handshaking code as we don't have to perform some arcane doppelganger resolution. Handshaking still uses proof-of-work for challenge-response, but switched to newer MemoryPoW. API call GET /peers no longer has 'buildTimestamp' field, but does now have 'nodeId' field. Network no longer has a whole raft of getXXXpeers() due to simplified handshaking. Quite a few method calls changed to simply Network.getHandshakedPeers(), which is also faster.
This commit is contained in:
parent
bd543a526b
commit
0c32afa07f
@ -25,7 +25,8 @@ public class ConnectedPeer {
|
|||||||
|
|
||||||
public String address;
|
public String address;
|
||||||
public String version;
|
public String version;
|
||||||
public Long buildTimestamp;
|
|
||||||
|
public String nodeId;
|
||||||
|
|
||||||
public Integer lastHeight;
|
public Integer lastHeight;
|
||||||
@Schema(example = "base58")
|
@Schema(example = "base58")
|
||||||
@ -45,10 +46,9 @@ public class ConnectedPeer {
|
|||||||
this.peersConnectedWhen = peer.getPeersConnectionTimestamp();
|
this.peersConnectedWhen = peer.getPeersConnectionTimestamp();
|
||||||
|
|
||||||
this.address = peerData.getAddress().toString();
|
this.address = peerData.getAddress().toString();
|
||||||
if (peer.getVersionMessage() != null) {
|
|
||||||
this.version = peer.getVersionMessage().getVersionString();
|
this.version = peer.getPeersVersionString();
|
||||||
this.buildTimestamp = peer.getVersionMessage().getBuildTimestamp();
|
this.nodeId = peer.getPeersNodeId();
|
||||||
}
|
|
||||||
|
|
||||||
PeerChainTipData peerChainTipData = peer.getChainTipData();
|
PeerChainTipData peerChainTipData = peer.getChainTipData();
|
||||||
if (peerChainTipData != null) {
|
if (peerChainTipData != null) {
|
||||||
|
@ -10,6 +10,7 @@ public class NodeInfo {
|
|||||||
public long uptime;
|
public long uptime;
|
||||||
public String buildVersion;
|
public String buildVersion;
|
||||||
public long buildTimestamp;
|
public long buildTimestamp;
|
||||||
|
public String nodeId;
|
||||||
|
|
||||||
public NodeInfo() {
|
public NodeInfo() {
|
||||||
}
|
}
|
||||||
|
@ -117,6 +117,7 @@ public class AdminResource {
|
|||||||
nodeInfo.uptime = System.currentTimeMillis() - Controller.startTime;
|
nodeInfo.uptime = System.currentTimeMillis() - Controller.startTime;
|
||||||
nodeInfo.buildVersion = Controller.getInstance().getVersionString();
|
nodeInfo.buildVersion = Controller.getInstance().getVersionString();
|
||||||
nodeInfo.buildTimestamp = Controller.getInstance().getBuildTimestamp();
|
nodeInfo.buildTimestamp = Controller.getInstance().getBuildTimestamp();
|
||||||
|
nodeInfo.nodeId = Network.getInstance().getOurNodeId();
|
||||||
|
|
||||||
return nodeInfo;
|
return nodeInfo;
|
||||||
}
|
}
|
||||||
|
@ -128,7 +128,7 @@ public class BlockMinter extends Thread {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Peer> peers = Network.getInstance().getUniqueHandshakedPeers();
|
List<Peer> peers = Network.getInstance().getHandshakedPeers();
|
||||||
BlockData lastBlockData = blockRepository.getLastBlock();
|
BlockData lastBlockData = blockRepository.getLastBlock();
|
||||||
|
|
||||||
// Disregard peers that have "misbehaved" recently
|
// Disregard peers that have "misbehaved" recently
|
||||||
|
@ -60,11 +60,9 @@ import org.qortal.network.message.GetBlockMessage;
|
|||||||
import org.qortal.network.message.GetBlockSummariesMessage;
|
import org.qortal.network.message.GetBlockSummariesMessage;
|
||||||
import org.qortal.network.message.GetOnlineAccountsMessage;
|
import org.qortal.network.message.GetOnlineAccountsMessage;
|
||||||
import org.qortal.network.message.GetPeersMessage;
|
import org.qortal.network.message.GetPeersMessage;
|
||||||
import org.qortal.network.message.GetSignaturesMessage;
|
|
||||||
import org.qortal.network.message.GetSignaturesV2Message;
|
import org.qortal.network.message.GetSignaturesV2Message;
|
||||||
import org.qortal.network.message.GetTransactionMessage;
|
import org.qortal.network.message.GetTransactionMessage;
|
||||||
import org.qortal.network.message.GetUnconfirmedTransactionsMessage;
|
import org.qortal.network.message.GetUnconfirmedTransactionsMessage;
|
||||||
import org.qortal.network.message.HeightMessage;
|
|
||||||
import org.qortal.network.message.HeightV2Message;
|
import org.qortal.network.message.HeightV2Message;
|
||||||
import org.qortal.network.message.Message;
|
import org.qortal.network.message.Message;
|
||||||
import org.qortal.network.message.OnlineAccountsMessage;
|
import org.qortal.network.message.OnlineAccountsMessage;
|
||||||
@ -501,7 +499,7 @@ public class Controller extends Thread {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private void potentiallySynchronize() throws InterruptedException {
|
private void potentiallySynchronize() throws InterruptedException {
|
||||||
List<Peer> peers = Network.getInstance().getUniqueHandshakedPeers();
|
List<Peer> peers = Network.getInstance().getHandshakedPeers();
|
||||||
|
|
||||||
// Disregard peers that have "misbehaved" recently
|
// Disregard peers that have "misbehaved" recently
|
||||||
peers.removeIf(hasMisbehaved);
|
peers.removeIf(hasMisbehaved);
|
||||||
@ -626,7 +624,7 @@ public class Controller extends Thread {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int numberOfPeers = Network.getInstance().getUniqueHandshakedPeers().size();
|
final int numberOfPeers = Network.getInstance().getHandshakedPeers().size();
|
||||||
|
|
||||||
final int height = getChainHeight();
|
final int height = getChainHeight();
|
||||||
|
|
||||||
@ -782,32 +780,11 @@ public class Controller extends Thread {
|
|||||||
public void onPeerHandshakeCompleted(Peer peer) {
|
public void onPeerHandshakeCompleted(Peer peer) {
|
||||||
// Only send if outbound
|
// Only send if outbound
|
||||||
if (peer.isOutbound()) {
|
if (peer.isOutbound()) {
|
||||||
if (peer.getVersion() < 2) {
|
// Request peer's unconfirmed transactions
|
||||||
// Legacy mode
|
Message message = new GetUnconfirmedTransactionsMessage();
|
||||||
|
if (!peer.sendMessage(message)) {
|
||||||
// Send our unconfirmed transactions
|
peer.disconnect("failed to send request for unconfirmed transactions");
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
return;
|
||||||
List<TransactionData> transactions = repository.getTransactionRepository().getUnconfirmedTransactions();
|
|
||||||
|
|
||||||
for (TransactionData transactionData : transactions) {
|
|
||||||
Message transactionMessage = new TransactionMessage(transactionData);
|
|
||||||
if (!peer.sendMessage(transactionMessage)) {
|
|
||||||
peer.disconnect("failed to send unconfirmed transaction");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (DataException e) {
|
|
||||||
LOGGER.error("Repository issue while sending unconfirmed transactions", e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// V2 protocol
|
|
||||||
|
|
||||||
// Request peer's unconfirmed transactions
|
|
||||||
Message message = new GetUnconfirmedTransactionsMessage();
|
|
||||||
if (!peer.sendMessage(message)) {
|
|
||||||
peer.disconnect("failed to send request for unconfirmed transactions");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -823,22 +800,10 @@ public class Controller extends Thread {
|
|||||||
|
|
||||||
// Ordered by message type value
|
// Ordered by message type value
|
||||||
switch (message.getType()) {
|
switch (message.getType()) {
|
||||||
case HEIGHT:
|
|
||||||
onNetworkHeightMessage(peer, message);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GET_SIGNATURES:
|
|
||||||
onNetworkGetSignaturesMessage(peer, message);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GET_BLOCK:
|
case GET_BLOCK:
|
||||||
onNetworkGetBlockMessage(peer, message);
|
onNetworkGetBlockMessage(peer, message);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BLOCK:
|
|
||||||
onNetworkBlockMessage(peer, message);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TRANSACTION:
|
case TRANSACTION:
|
||||||
onNetworkTransactionMessage(peer, message);
|
onNetworkTransactionMessage(peer, message);
|
||||||
break;
|
break;
|
||||||
@ -884,56 +849,11 @@ public class Controller extends Thread {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
LOGGER.debug(String.format("Unhandled %s message [ID %d] from peer %s", message.getType().name(), message.getId(), peer));
|
LOGGER.debug(() -> String.format("Unhandled %s message [ID %d] from peer %s", message.getType().name(), message.getId(), peer));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onNetworkHeightMessage(Peer peer, Message message) {
|
|
||||||
HeightMessage heightMessage = (HeightMessage) message;
|
|
||||||
|
|
||||||
// Update all peers with same ID
|
|
||||||
|
|
||||||
List<Peer> connectedPeers = Network.getInstance().getHandshakedPeers();
|
|
||||||
for (Peer connectedPeer : connectedPeers) {
|
|
||||||
if (connectedPeer.getPeerId() == null || !Arrays.equals(connectedPeer.getPeerId(), peer.getPeerId()))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Update peer chain tip data
|
|
||||||
PeerChainTipData newChainTipData = new PeerChainTipData(heightMessage.getHeight(), null, null, null);
|
|
||||||
connectedPeer.setChainTipData(newChainTipData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Potentially synchronize
|
|
||||||
requestSync = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onNetworkGetSignaturesMessage(Peer peer, Message message) {
|
|
||||||
GetSignaturesMessage getSignaturesMessage = (GetSignaturesMessage) message;
|
|
||||||
byte[] parentSignature = getSignaturesMessage.getParentSignature();
|
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
List<byte[]> signatures = new ArrayList<>();
|
|
||||||
|
|
||||||
do {
|
|
||||||
BlockData blockData = repository.getBlockRepository().fromReference(parentSignature);
|
|
||||||
|
|
||||||
if (blockData == null)
|
|
||||||
break;
|
|
||||||
|
|
||||||
parentSignature = blockData.getSignature();
|
|
||||||
signatures.add(parentSignature);
|
|
||||||
} while (signatures.size() < Network.MAX_SIGNATURES_PER_REPLY);
|
|
||||||
|
|
||||||
Message signaturesMessage = new SignaturesMessage(signatures);
|
|
||||||
signaturesMessage.setId(message.getId());
|
|
||||||
if (!peer.sendMessage(signaturesMessage))
|
|
||||||
peer.disconnect("failed to send signatures");
|
|
||||||
} catch (DataException e) {
|
|
||||||
LOGGER.error(String.format("Repository issue while sending signatures after %s to peer %s", Base58.encode(parentSignature), peer), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onNetworkGetBlockMessage(Peer peer, Message message) {
|
private void onNetworkGetBlockMessage(Peer peer, Message message) {
|
||||||
GetBlockMessage getBlockMessage = (GetBlockMessage) message;
|
GetBlockMessage getBlockMessage = (GetBlockMessage) message;
|
||||||
byte[] signature = getBlockMessage.getSignature();
|
byte[] signature = getBlockMessage.getSignature();
|
||||||
@ -957,40 +877,6 @@ public class Controller extends Thread {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onNetworkBlockMessage(Peer peer, Message message) {
|
|
||||||
// From a v1 peer, with no message ID, this is a broadcast of peer's latest block
|
|
||||||
// v2 peers announce new blocks using HEIGHT_V2
|
|
||||||
|
|
||||||
// Not version 1?
|
|
||||||
if (peer.getVersion() == null || peer.getVersion() > 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Message ID present?
|
|
||||||
// XXX Why is this test here? If BLOCK had an ID then surely it would be a response to GET_BLOCK
|
|
||||||
// and hence captured by Peer's reply queue?
|
|
||||||
if (message.hasId())
|
|
||||||
return;
|
|
||||||
|
|
||||||
BlockMessage blockMessage = (BlockMessage) message;
|
|
||||||
BlockData blockData = blockMessage.getBlockData();
|
|
||||||
|
|
||||||
// Update all peers with same ID
|
|
||||||
|
|
||||||
List<Peer> connectedPeers = Network.getInstance().getHandshakedPeers();
|
|
||||||
for (Peer connectedPeer : connectedPeers) {
|
|
||||||
// Skip connectedPeer if they have no ID or their ID doesn't match sender's ID
|
|
||||||
if (connectedPeer.getPeerId() == null || !Arrays.equals(connectedPeer.getPeerId(), peer.getPeerId()))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Update peer chain tip data
|
|
||||||
PeerChainTipData newChainTipData = new PeerChainTipData(blockData.getHeight(), blockData.getSignature(), blockData.getTimestamp(), blockData.getMinterPublicKey());
|
|
||||||
connectedPeer.setChainTipData(newChainTipData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Potentially synchronize
|
|
||||||
requestSync = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onNetworkTransactionMessage(Peer peer, Message message) {
|
private void onNetworkTransactionMessage(Peer peer, Message message) {
|
||||||
TransactionMessage transactionMessage = (TransactionMessage) message;
|
TransactionMessage transactionMessage = (TransactionMessage) message;
|
||||||
TransactionData transactionData = transactionMessage.getTransactionData();
|
TransactionData transactionData = transactionMessage.getTransactionData();
|
||||||
@ -1096,18 +982,9 @@ public class Controller extends Thread {
|
|||||||
if (!peer.isOutbound() && (peer.getChainTipData() == null || peer.getChainTipData().getLastHeight() == null))
|
if (!peer.isOutbound() && (peer.getChainTipData() == null || peer.getChainTipData().getLastHeight() == null))
|
||||||
peer.sendMessage(Network.getInstance().buildHeightMessage(peer, getChainTip()));
|
peer.sendMessage(Network.getInstance().buildHeightMessage(peer, getChainTip()));
|
||||||
|
|
||||||
// Update all peers with same ID
|
// Update peer chain tip data
|
||||||
|
PeerChainTipData newChainTipData = new PeerChainTipData(heightV2Message.getHeight(), heightV2Message.getSignature(), heightV2Message.getTimestamp(), heightV2Message.getMinterPublicKey());
|
||||||
List<Peer> connectedPeers = Network.getInstance().getHandshakedPeers();
|
peer.setChainTipData(newChainTipData);
|
||||||
for (Peer connectedPeer : connectedPeers) {
|
|
||||||
// Skip connectedPeer if they have no ID or their ID doesn't match sender's ID
|
|
||||||
if (connectedPeer.getPeerId() == null || !Arrays.equals(connectedPeer.getPeerId(), peer.getPeerId()))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Update peer chain tip data
|
|
||||||
PeerChainTipData newChainTipData = new PeerChainTipData(heightV2Message.getHeight(), heightV2Message.getSignature(), heightV2Message.getTimestamp(), heightV2Message.getMinterPublicKey());
|
|
||||||
connectedPeer.setChainTipData(newChainTipData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Potentially synchronize
|
// Potentially synchronize
|
||||||
requestSync = true;
|
requestSync = true;
|
||||||
@ -1561,7 +1438,7 @@ public class Controller extends Thread {
|
|||||||
getArbitraryDataMessage.setId(id);
|
getArbitraryDataMessage.setId(id);
|
||||||
|
|
||||||
// Broadcast request
|
// Broadcast request
|
||||||
Network.getInstance().broadcast(peer -> peer.getVersion() < 2 ? null : getArbitraryDataMessage);
|
Network.getInstance().broadcast(peer -> getArbitraryDataMessage);
|
||||||
|
|
||||||
// Poll to see if data has arrived
|
// Poll to see if data has arrived
|
||||||
final long singleWait = 100;
|
final long singleWait = 100;
|
||||||
@ -1593,7 +1470,7 @@ public class Controller extends Thread {
|
|||||||
if (minLatestBlockTimestamp == null)
|
if (minLatestBlockTimestamp == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
List<Peer> peers = Network.getInstance().getUniqueHandshakedPeers();
|
List<Peer> peers = Network.getInstance().getHandshakedPeers();
|
||||||
|
|
||||||
// Filter out unsuitable peers
|
// Filter out unsuitable peers
|
||||||
Iterator<Peer> iterator = peers.iterator();
|
Iterator<Peer> iterator = peers.iterator();
|
||||||
@ -1639,7 +1516,7 @@ public class Controller extends Thread {
|
|||||||
if (latestBlockData == null || latestBlockData.getTimestamp() < minLatestBlockTimestamp)
|
if (latestBlockData == null || latestBlockData.getTimestamp() < minLatestBlockTimestamp)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
List<Peer> peers = Network.getInstance().getUniqueHandshakedPeers();
|
List<Peer> peers = Network.getInstance().getHandshakedPeers();
|
||||||
if (peers == null)
|
if (peers == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ import org.qortal.network.message.BlockMessage;
|
|||||||
import org.qortal.network.message.BlockSummariesMessage;
|
import org.qortal.network.message.BlockSummariesMessage;
|
||||||
import org.qortal.network.message.GetBlockMessage;
|
import org.qortal.network.message.GetBlockMessage;
|
||||||
import org.qortal.network.message.GetBlockSummariesMessage;
|
import org.qortal.network.message.GetBlockSummariesMessage;
|
||||||
import org.qortal.network.message.GetSignaturesMessage;
|
|
||||||
import org.qortal.network.message.GetSignaturesV2Message;
|
import org.qortal.network.message.GetSignaturesV2Message;
|
||||||
import org.qortal.network.message.Message;
|
import org.qortal.network.message.Message;
|
||||||
import org.qortal.network.message.SignaturesMessage;
|
import org.qortal.network.message.SignaturesMessage;
|
||||||
@ -372,12 +371,7 @@ public class Synchronizer {
|
|||||||
return SynchronizationResult.TOO_DIVERGENT;
|
return SynchronizationResult.TOO_DIVERGENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (peer.getVersion() >= 2) {
|
step <<= 1;
|
||||||
step <<= 1;
|
|
||||||
} else {
|
|
||||||
// Old v1 peers are hard-coded to return 500 signatures so we might as well go backward by 500 too
|
|
||||||
step = 500;
|
|
||||||
}
|
|
||||||
step = Math.min(step, MAXIMUM_BLOCK_STEP);
|
step = Math.min(step, MAXIMUM_BLOCK_STEP);
|
||||||
|
|
||||||
testHeight = Math.max(testHeight - step, 1);
|
testHeight = Math.max(testHeight - step, 1);
|
||||||
@ -415,8 +409,7 @@ public class Synchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<byte[]> getBlockSignatures(Peer peer, byte[] parentSignature, int numberRequested) throws InterruptedException {
|
private List<byte[]> getBlockSignatures(Peer peer, byte[] parentSignature, int numberRequested) throws InterruptedException {
|
||||||
// numberRequested is v2+ feature
|
Message getSignaturesMessage = new GetSignaturesV2Message(parentSignature, numberRequested);
|
||||||
Message getSignaturesMessage = peer.getVersion() >= 2 ? new GetSignaturesV2Message(parentSignature, numberRequested) : new GetSignaturesMessage(parentSignature);
|
|
||||||
|
|
||||||
Message message = peer.getResponse(getSignaturesMessage);
|
Message message = peer.getResponse(getSignaturesMessage);
|
||||||
if (message == null || message.getType() != MessageType.SIGNATURES)
|
if (message == null || message.getType() != MessageType.SIGNATURES)
|
||||||
|
@ -1,158 +1,193 @@
|
|||||||
package org.qortal.network;
|
package org.qortal.network;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.qortal.controller.Controller;
|
import org.qortal.controller.Controller;
|
||||||
|
import org.qortal.crypto.Crypto;
|
||||||
|
import org.qortal.crypto.MemoryPoW;
|
||||||
|
import org.qortal.network.message.ChallengeMessage;
|
||||||
|
import org.qortal.network.message.HelloMessage;
|
||||||
import org.qortal.network.message.Message;
|
import org.qortal.network.message.Message;
|
||||||
import org.qortal.network.message.PeerIdMessage;
|
|
||||||
import org.qortal.network.message.PeerVerifyMessage;
|
|
||||||
import org.qortal.network.message.ProofMessage;
|
|
||||||
import org.qortal.network.message.VerificationCodesMessage;
|
|
||||||
import org.qortal.network.message.VersionMessage;
|
|
||||||
import org.qortal.network.message.Message.MessageType;
|
import org.qortal.network.message.Message.MessageType;
|
||||||
|
import org.qortal.network.message.ResponseMessage;
|
||||||
|
import org.qortal.utils.NTP;
|
||||||
|
|
||||||
|
import com.google.common.primitives.Bytes;
|
||||||
|
|
||||||
public enum Handshake {
|
public enum Handshake {
|
||||||
STARTED(null) {
|
STARTED(null) {
|
||||||
@Override
|
@Override
|
||||||
public Handshake onMessage(Peer peer, Message message) {
|
public Handshake onMessage(Peer peer, Message message) {
|
||||||
return SELF_CHECK;
|
return HELLO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void action(Peer peer) {
|
public void action(Peer peer) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
SELF_CHECK(MessageType.PEER_ID) {
|
HELLO(MessageType.HELLO) {
|
||||||
@Override
|
@Override
|
||||||
public Handshake onMessage(Peer peer, Message message) {
|
public Handshake onMessage(Peer peer, Message message) {
|
||||||
PeerIdMessage peerIdMessage = (PeerIdMessage) message;
|
HelloMessage helloMessage = (HelloMessage) message;
|
||||||
byte[] peerId = peerIdMessage.getPeerId();
|
|
||||||
|
|
||||||
if (Arrays.equals(peerId, Network.ZERO_PEER_ID)) {
|
long peersConnectionTimestamp = helloMessage.getTimestamp();
|
||||||
if (peer.isOutbound()) {
|
long now = NTP.getTime();
|
||||||
// Peer has indicated they already have an outbound connection to us
|
|
||||||
LOGGER.trace(String.format("Peer %s already connected to us - discarding this connection", peer));
|
|
||||||
} else {
|
|
||||||
// Not sure this should occur so log it
|
|
||||||
LOGGER.info(String.format("Inbound peer %s claims we also have outbound connection to them?", peer));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
long timestampDelta = Math.abs(peersConnectionTimestamp - now);
|
||||||
|
if (timestampDelta > MAX_TIMESTAMP_DELTA) {
|
||||||
|
LOGGER.debug(() -> String.format("Peer %s HELLO timestamp %d too divergent (± %d > %d) from ours %d",
|
||||||
|
peer, peersConnectionTimestamp, timestampDelta, MAX_TIMESTAMP_DELTA, now));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Arrays.equals(peerId, Network.getInstance().getOurPeerId())) {
|
String versionString = helloMessage.getVersionString();
|
||||||
// Connected to self!
|
|
||||||
|
Matcher matcher = VERSION_PATTERN.matcher(versionString);
|
||||||
|
if (!matcher.lookingAt()) {
|
||||||
|
LOGGER.debug(() -> String.format("Peer %s sent invalid HELLO version string '%s'", peer, versionString));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're expecting 3 positive shorts, so we can convert 1.2.3 into 0x0100020003
|
||||||
|
long version = 0;
|
||||||
|
for (int g = 1; g <= 3; ++g) {
|
||||||
|
long value = Long.parseLong(matcher.group(g));
|
||||||
|
|
||||||
|
if (value < 0 || value > Short.MAX_VALUE)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
version <<= 16;
|
||||||
|
version |= value;
|
||||||
|
}
|
||||||
|
|
||||||
|
peer.setPeersConnectionTimestamp(peersConnectionTimestamp);
|
||||||
|
peer.setPeersVersion(versionString, version);
|
||||||
|
|
||||||
|
return CHALLENGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void action(Peer peer) {
|
||||||
|
String versionString = Controller.getInstance().getVersionString();
|
||||||
|
long timestamp = NTP.getTime();
|
||||||
|
|
||||||
|
Message helloMessage = new HelloMessage(timestamp, versionString);
|
||||||
|
if (!peer.sendMessage(helloMessage))
|
||||||
|
peer.disconnect("failed to send HELLO");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CHALLENGE(MessageType.CHALLENGE) {
|
||||||
|
@Override
|
||||||
|
public Handshake onMessage(Peer peer, Message message) {
|
||||||
|
ChallengeMessage challengeMessage = (ChallengeMessage) message;
|
||||||
|
|
||||||
|
byte[] peersPublicKey = challengeMessage.getPublicKey();
|
||||||
|
byte[] peersChallenge = challengeMessage.getChallenge();
|
||||||
|
|
||||||
|
// If public key matches our public key then we've connected to self
|
||||||
|
byte[] ourPublicKey = Network.getInstance().getOurPublicKey();
|
||||||
|
if (Arrays.equals(ourPublicKey, peersPublicKey)) {
|
||||||
// If outgoing connection then record destination as self so we don't try again
|
// If outgoing connection then record destination as self so we don't try again
|
||||||
if (peer.isOutbound()) {
|
if (peer.isOutbound()) {
|
||||||
Network.getInstance().noteToSelf(peer);
|
Network.getInstance().noteToSelf(peer);
|
||||||
// Handshake failure - caller will deal with disconnect
|
// Handshake failure, caller will handle disconnect
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
// We still need to send our ID so our outbound connection can mark their address as 'self'
|
// We still need to send our ID so our outbound connection can mark their address as 'self'
|
||||||
sendMyId(peer);
|
challengeMessage = new ChallengeMessage(ourPublicKey, ZERO_CHALLENGE);
|
||||||
// We return SELF_CHECK here to prevent us from closing connection, which currently preempts
|
if (!peer.sendMessage(challengeMessage))
|
||||||
// remote end from reading any pending messages, specifically the PEER_ID message we just sent above.
|
peer.disconnect("failed to send CHALLENGE to self");
|
||||||
// When our 'remote' outbound counterpart reads our message, they will close both connections.
|
|
||||||
// Failing that, our connection will timeout or a future handshake error will occur.
|
/*
|
||||||
return SELF_CHECK;
|
* We return CHALLENGE here to prevent us from closing connection. Closing
|
||||||
|
* connection currently preempts remote end from reading any pending messages,
|
||||||
|
* specifically the CHALLENGE message we just sent above. When our 'remote'
|
||||||
|
* outbound counterpart reads our message, they will close both connections.
|
||||||
|
* Failing that, our connection will timeout or a future handshake error will
|
||||||
|
* occur.
|
||||||
|
*/
|
||||||
|
return CHALLENGE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is this ID already connected inbound or outbound?
|
// Are we already connected to this peer?
|
||||||
Peer otherInboundPeer = Network.getInstance().getInboundPeerWithId(peerId);
|
Peer existingPeer = Network.getInstance().getHandshakedPeerWithPublicKey(peersPublicKey);
|
||||||
Peer otherOutboundPeer = Network.getInstance().getOutboundHandshakedPeerWithId(peerId);
|
if (existingPeer != null) {
|
||||||
|
LOGGER.info(() -> String.format("We already have a connection with peer %s - discarding", peer));
|
||||||
// Extra checks on inbound peers with known IDs, to prevent ID stealing
|
|
||||||
if (!peer.isOutbound() && otherInboundPeer != null) {
|
|
||||||
if (otherOutboundPeer == null) {
|
|
||||||
// We already have an inbound peer with this ID, but no outgoing peer with which to request verification
|
|
||||||
LOGGER.trace(String.format("Discarding inbound peer %s with existing ID", peer));
|
|
||||||
|
|
||||||
// Let peer know by sending special zero peer ID. This avoids peer keeping connection open until timeout.
|
|
||||||
peerIdMessage = new PeerIdMessage(Network.ZERO_PEER_ID);
|
|
||||||
peer.sendMessage(peerIdMessage);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
// Use corresponding outbound peer to verify inbound
|
|
||||||
LOGGER.trace(String.format("We will be using outbound peer %s to verify inbound peer %s with same ID", otherOutboundPeer, peer));
|
|
||||||
|
|
||||||
// Discard peer's ID
|
|
||||||
// peer.setPeerId(peerId);
|
|
||||||
|
|
||||||
// Generate verification codes for later
|
|
||||||
peer.generateVerificationCodes();
|
|
||||||
}
|
|
||||||
} else if (peer.isOutbound() && otherOutboundPeer != null) {
|
|
||||||
// We already have an outbound connection to this peer?
|
|
||||||
LOGGER.info(String.format("We already have another outbound connection to peer %s - discarding", peer));
|
|
||||||
// Handshake failure - caller will deal with disconnect
|
// Handshake failure - caller will deal with disconnect
|
||||||
return null;
|
return null;
|
||||||
} else {
|
|
||||||
// Set peer's ID
|
|
||||||
peer.setPeerId(peerId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return VERSION;
|
peer.setPeersPublicKey(peersPublicKey);
|
||||||
|
peer.setPeersChallenge(peersChallenge);
|
||||||
|
|
||||||
|
return RESPONSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void action(Peer peer) {
|
public void action(Peer peer) {
|
||||||
sendMyId(peer);
|
// Send challenge
|
||||||
|
byte[] publicKey = Network.getInstance().getOurPublicKey();
|
||||||
|
byte[] challenge = peer.getOurChallenge();
|
||||||
|
|
||||||
|
Message challengeMessage = new ChallengeMessage(publicKey, challenge);
|
||||||
|
if (!peer.sendMessage(challengeMessage))
|
||||||
|
peer.disconnect("failed to send CHALLENGE");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
VERSION(MessageType.VERSION) {
|
RESPONSE(MessageType.RESPONSE) {
|
||||||
@Override
|
@Override
|
||||||
public Handshake onMessage(Peer peer, Message message) {
|
public Handshake onMessage(Peer peer, Message message) {
|
||||||
peer.setVersionMessage((VersionMessage) message);
|
ResponseMessage responseMessage = (ResponseMessage) message;
|
||||||
|
|
||||||
// If we're both version 2 peers then next stage is proof
|
byte[] peersPublicKey = peer.getPeersPublicKey();
|
||||||
if (peer.getVersion() >= 2)
|
byte[] ourChallenge = peer.getOurChallenge();
|
||||||
return PROOF;
|
|
||||||
|
byte[] sharedSecret = Network.getInstance().getSharedSecret(peersPublicKey);
|
||||||
|
final byte[] expectedData = Crypto.digest(Bytes.concat(sharedSecret, ourChallenge));
|
||||||
|
|
||||||
|
byte[] data = responseMessage.getData();
|
||||||
|
if (!Arrays.equals(expectedData, data)) {
|
||||||
|
LOGGER.debug(() -> String.format("Peer %s sent incorrect RESPONSE data", peer));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int nonce = responseMessage.getNonce();
|
||||||
|
if (!MemoryPoW.verify2(data, POW_BUFFER_SIZE, POW_DIFFICULTY, nonce)) {
|
||||||
|
LOGGER.debug(() -> String.format("Peer %s sent incorrect RESPONSE nonce", peer));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
peer.setPeersNodeId(Crypto.toNodeAddress(peersPublicKey));
|
||||||
|
|
||||||
// Fall-back for older clients (for now)
|
|
||||||
return COMPLETED;
|
return COMPLETED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void action(Peer peer) {
|
public void action(Peer peer) {
|
||||||
sendVersion(peer);
|
// Send response
|
||||||
}
|
|
||||||
},
|
|
||||||
PROOF(MessageType.PROOF) {
|
|
||||||
@Override
|
|
||||||
public Handshake onMessage(Peer peer, Message message) {
|
|
||||||
ProofMessage proofMessage = (ProofMessage) message;
|
|
||||||
|
|
||||||
// Check peer's timestamp is within acceptable bounds
|
byte[] peersPublicKey = peer.getPeersPublicKey();
|
||||||
if (Math.abs(proofMessage.getTimestamp() - peer.getConnectionTimestamp()) > MAX_TIMESTAMP_DELTA) {
|
byte[] peersChallenge = peer.getPeersChallenge();
|
||||||
LOGGER.debug(String.format("Rejecting PROOF from %s as timestamp delta %d greater than max %d", peer, Math.abs(proofMessage.getTimestamp() - peer.getConnectionTimestamp()), MAX_TIMESTAMP_DELTA));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save peer's value for connectionTimestamp
|
byte[] sharedSecret = Network.getInstance().getSharedSecret(peersPublicKey);
|
||||||
peer.setPeersConnectionTimestamp(proofMessage.getTimestamp());
|
final byte[] data = Crypto.digest(Bytes.concat(sharedSecret, peersChallenge));
|
||||||
|
|
||||||
// If we connected outbound to peer, then this is a faked confirmation response, so we're good
|
// We do this in a new thread as it can take a while...
|
||||||
if (peer.isOutbound())
|
Thread responseThread = new Thread(() -> {
|
||||||
return COMPLETED;
|
Integer nonce = MemoryPoW.compute2(data, POW_BUFFER_SIZE, POW_DIFFICULTY);
|
||||||
|
|
||||||
// Check salt hasn't been seen before - this stops multiple peers reusing same nonce in a Sybil-like attack
|
Message responseMessage = new ResponseMessage(nonce, data);
|
||||||
if (Proof.seenSalt(proofMessage.getSalt()))
|
if (!peer.sendMessage(responseMessage))
|
||||||
return null;
|
peer.disconnect("failed to send RESPONSE");
|
||||||
|
});
|
||||||
|
|
||||||
if (!Proof.check(proofMessage.getTimestamp(), proofMessage.getSalt(), proofMessage.getNonce()))
|
responseThread.setDaemon(true);
|
||||||
return null;
|
responseThread.start();
|
||||||
|
|
||||||
// Proof valid
|
|
||||||
return COMPLETED;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void action(Peer peer) {
|
|
||||||
sendProof(peer);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
COMPLETED(null) {
|
COMPLETED(null) {
|
||||||
@ -166,40 +201,6 @@ public enum Handshake {
|
|||||||
public void action(Peer peer) {
|
public void action(Peer peer) {
|
||||||
// Note: this is only called when we've made outbound connection
|
// Note: this is only called when we've made outbound connection
|
||||||
}
|
}
|
||||||
},
|
|
||||||
PEER_VERIFY(null) {
|
|
||||||
@Override
|
|
||||||
public Handshake onMessage(Peer peer, Message message) {
|
|
||||||
// We only accept PEER_VERIFY messages
|
|
||||||
if (message.getType() != Message.MessageType.PEER_VERIFY)
|
|
||||||
return PEER_VERIFY;
|
|
||||||
|
|
||||||
// Check returned code against expected
|
|
||||||
PeerVerifyMessage peerVerifyMessage = (PeerVerifyMessage) message;
|
|
||||||
|
|
||||||
if (!Arrays.equals(peerVerifyMessage.getVerificationCode(), peer.getVerificationCodeExpected()))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// Drop other inbound peers with the same ID
|
|
||||||
for (Peer otherPeer : Network.getInstance().getConnectedPeers())
|
|
||||||
if (!otherPeer.isOutbound() && otherPeer.getPeerId() != null && Arrays.equals(otherPeer.getPeerId(), peer.getPendingPeerId()))
|
|
||||||
otherPeer.disconnect("doppelganger");
|
|
||||||
|
|
||||||
// Tidy up
|
|
||||||
peer.setVerificationCodes(null, null);
|
|
||||||
peer.setPeerId(peer.getPendingPeerId());
|
|
||||||
peer.setPendingPeerId(null);
|
|
||||||
|
|
||||||
// Completed for real this time
|
|
||||||
return COMPLETED;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void action(Peer peer) {
|
|
||||||
// Send VERIFICATION_CODE to other peer (that we connected to)
|
|
||||||
// Send PEER_VERIFY to peer
|
|
||||||
sendVerificationCodes(peer);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger(Handshake.class);
|
private static final Logger LOGGER = LogManager.getLogger(Handshake.class);
|
||||||
@ -207,6 +208,13 @@ public enum Handshake {
|
|||||||
/** Maximum allowed difference between peer's reported timestamp and when they connected, in milliseconds. */
|
/** Maximum allowed difference between peer's reported timestamp and when they connected, in milliseconds. */
|
||||||
private static final long MAX_TIMESTAMP_DELTA = 30 * 1000L; // ms
|
private static final long MAX_TIMESTAMP_DELTA = 30 * 1000L; // ms
|
||||||
|
|
||||||
|
private static final Pattern VERSION_PATTERN = Pattern.compile(Controller.VERSION_PREFIX + "(\\d{1,3})\\.(\\d{1,5})\\.(\\d{1,5})");
|
||||||
|
|
||||||
|
private static final int POW_BUFFER_SIZE = 8 * 1024 * 1024; // bytes
|
||||||
|
private static final int POW_DIFFICULTY = 12; // leading zero bits
|
||||||
|
|
||||||
|
private static final byte[] ZERO_CHALLENGE = new byte[ChallengeMessage.CHALLENGE_LENGTH];
|
||||||
|
|
||||||
public final MessageType expectedMessageType;
|
public final MessageType expectedMessageType;
|
||||||
|
|
||||||
private Handshake(MessageType expectedMessageType) {
|
private Handshake(MessageType expectedMessageType) {
|
||||||
@ -217,47 +225,4 @@ public enum Handshake {
|
|||||||
|
|
||||||
public abstract void action(Peer peer);
|
public abstract void action(Peer peer);
|
||||||
|
|
||||||
private static void sendVersion(Peer peer) {
|
|
||||||
long buildTimestamp = Controller.getInstance().getBuildTimestamp();
|
|
||||||
String versionString = Controller.getInstance().getVersionString();
|
|
||||||
|
|
||||||
Message versionMessage = new VersionMessage(buildTimestamp, versionString);
|
|
||||||
if (!peer.sendMessage(versionMessage))
|
|
||||||
peer.disconnect("failed to send version");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void sendMyId(Peer peer) {
|
|
||||||
Message peerIdMessage = new PeerIdMessage(Network.getInstance().getOurPeerId());
|
|
||||||
if (!peer.sendMessage(peerIdMessage))
|
|
||||||
peer.disconnect("failed to send peer ID");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void sendProof(Peer peer) {
|
|
||||||
if (peer.isOutbound()) {
|
|
||||||
// For outbound connections we need to generate real proof
|
|
||||||
new Proof(peer).start(); // Calculate & send in a new thread to free up networking processing
|
|
||||||
} else {
|
|
||||||
// For incoming connections we only need to send a fake proof message as confirmation
|
|
||||||
Message proofMessage = new ProofMessage(peer.getConnectionTimestamp(), 0, 0);
|
|
||||||
if (!peer.sendMessage(proofMessage))
|
|
||||||
peer.disconnect("failed to send proof");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void sendVerificationCodes(Peer peer) {
|
|
||||||
Peer otherOutboundPeer = Network.getInstance().getOutboundHandshakedPeerWithId(peer.getPendingPeerId());
|
|
||||||
|
|
||||||
// Send VERIFICATION_CODES to peer
|
|
||||||
Message verificationCodesMessage = new VerificationCodesMessage(peer.getVerificationCodeSent(), peer.getVerificationCodeExpected());
|
|
||||||
if (!otherOutboundPeer.sendMessage(verificationCodesMessage)) {
|
|
||||||
peer.disconnect("failed to send verification codes"); // give up with this peer instead
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send PEER_VERIFY to peer
|
|
||||||
Message peerVerifyMessage = new PeerVerifyMessage(peer.getVerificationCodeSent());
|
|
||||||
if (!peer.sendMessage(peerVerifyMessage))
|
|
||||||
peer.disconnect("failed to send verification code");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package org.qortal.network;
|
package org.qortal.network;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.Inet6Address;
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.StandardSocketOptions;
|
import java.net.StandardSocketOptions;
|
||||||
@ -32,28 +31,26 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
|
||||||
|
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
|
||||||
import org.qortal.block.BlockChain;
|
import org.qortal.block.BlockChain;
|
||||||
import org.qortal.controller.Controller;
|
import org.qortal.controller.Controller;
|
||||||
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.data.block.BlockData;
|
import org.qortal.data.block.BlockData;
|
||||||
import org.qortal.data.network.PeerData;
|
import org.qortal.data.network.PeerData;
|
||||||
import org.qortal.data.transaction.TransactionData;
|
import org.qortal.data.transaction.TransactionData;
|
||||||
import org.qortal.network.message.GetPeersMessage;
|
import org.qortal.network.message.GetPeersMessage;
|
||||||
import org.qortal.network.message.GetUnconfirmedTransactionsMessage;
|
import org.qortal.network.message.GetUnconfirmedTransactionsMessage;
|
||||||
import org.qortal.network.message.HeightMessage;
|
|
||||||
import org.qortal.network.message.HeightV2Message;
|
import org.qortal.network.message.HeightV2Message;
|
||||||
import org.qortal.network.message.Message;
|
import org.qortal.network.message.Message;
|
||||||
import org.qortal.network.message.PeerVerifyMessage;
|
|
||||||
import org.qortal.network.message.PeersMessage;
|
|
||||||
import org.qortal.network.message.PeersV2Message;
|
import org.qortal.network.message.PeersV2Message;
|
||||||
import org.qortal.network.message.PingMessage;
|
import org.qortal.network.message.PingMessage;
|
||||||
import org.qortal.network.message.TransactionMessage;
|
|
||||||
import org.qortal.network.message.TransactionSignaturesMessage;
|
import org.qortal.network.message.TransactionSignaturesMessage;
|
||||||
import org.qortal.network.message.VerificationCodesMessage;
|
|
||||||
import org.qortal.network.message.Message.MessageType;
|
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
import org.qortal.repository.RepositoryManager;
|
import org.qortal.repository.RepositoryManager;
|
||||||
import org.qortal.settings.Settings;
|
import org.qortal.settings.Settings;
|
||||||
|
import org.qortal.transform.Transformer;
|
||||||
import org.qortal.utils.ExecuteProduceConsume;
|
import org.qortal.utils.ExecuteProduceConsume;
|
||||||
// import org.qortal.utils.ExecutorDumper;
|
// import org.qortal.utils.ExecutorDumper;
|
||||||
import org.qortal.utils.ExecuteProduceConsume.StatsSnapshot;
|
import org.qortal.utils.ExecuteProduceConsume.StatsSnapshot;
|
||||||
@ -99,10 +96,11 @@ public class Network {
|
|||||||
|
|
||||||
public static final int MAX_SIGNATURES_PER_REPLY = 500;
|
public static final int MAX_SIGNATURES_PER_REPLY = 500;
|
||||||
public static final int MAX_BLOCK_SUMMARIES_PER_REPLY = 500;
|
public static final int MAX_BLOCK_SUMMARIES_PER_REPLY = 500;
|
||||||
public static final int PEER_ID_LENGTH = 128;
|
|
||||||
public static final byte[] ZERO_PEER_ID = new byte[PEER_ID_LENGTH];
|
|
||||||
|
|
||||||
private final byte[] ourPeerId;
|
private final Ed25519PrivateKeyParameters edPrivateKeyParams;
|
||||||
|
private final Ed25519PublicKeyParameters edPublicKeyParams;
|
||||||
|
private final String ourNodeId;
|
||||||
|
|
||||||
private final int maxMessageSize;
|
private final int maxMessageSize;
|
||||||
|
|
||||||
private List<PeerData> allKnownPeers;
|
private List<PeerData> allKnownPeers;
|
||||||
@ -129,10 +127,13 @@ public class Network {
|
|||||||
connectedPeers = new ArrayList<>();
|
connectedPeers = new ArrayList<>();
|
||||||
selfPeers = new ArrayList<>();
|
selfPeers = new ArrayList<>();
|
||||||
|
|
||||||
ourPeerId = new byte[PEER_ID_LENGTH];
|
// Generate our ID
|
||||||
new SecureRandom().nextBytes(ourPeerId);
|
byte[] seed = new byte[Transformer.PRIVATE_KEY_LENGTH];
|
||||||
// Set bit to make sure our peer ID is not 0
|
new SecureRandom().nextBytes(seed);
|
||||||
ourPeerId[ourPeerId.length - 1] |= 0x01;
|
|
||||||
|
edPrivateKeyParams = new Ed25519PrivateKeyParameters(seed, 0);
|
||||||
|
edPublicKeyParams = edPrivateKeyParams.generatePublicKey();
|
||||||
|
ourNodeId = Crypto.toNodeAddress(edPublicKeyParams.getEncoded());
|
||||||
|
|
||||||
maxMessageSize = 4 + 1 + 4 + BlockChain.getInstance().getMaxBlockSize();
|
maxMessageSize = 4 + 1 + 4 + BlockChain.getInstance().getMaxBlockSize();
|
||||||
|
|
||||||
@ -201,8 +202,12 @@ public class Network {
|
|||||||
return Settings.getInstance().isTestNet() ? TESTNET_MESSAGE_MAGIC : MAINNET_MESSAGE_MAGIC;
|
return Settings.getInstance().isTestNet() ? TESTNET_MESSAGE_MAGIC : MAINNET_MESSAGE_MAGIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getOurPeerId() {
|
public String getOurNodeId() {
|
||||||
return this.ourPeerId;
|
return this.ourNodeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*package*/ byte[] getOurPublicKey() {
|
||||||
|
return this.edPublicKeyParams.getEncoded();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Maximum message size (bytes). Needs to be at least maximum block size + MAGIC + message type, etc. */
|
/** Maximum message size (bytes). Needs to be at least maximum block size + MAGIC + message type, etc. */
|
||||||
@ -241,25 +246,6 @@ public class Network {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns list of connected peers that have completed handshaking, with inbound duplicates removed. */
|
|
||||||
public List<Peer> getUniqueHandshakedPeers() {
|
|
||||||
List<Peer> peers = getHandshakedPeers();
|
|
||||||
|
|
||||||
// Returns true if this peer is inbound and has corresponding outbound peer with same ID
|
|
||||||
Predicate<Peer> hasOutboundWithSameId = peer -> {
|
|
||||||
// Peer is outbound so return fast
|
|
||||||
if (peer.isOutbound())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return peers.stream().anyMatch(otherPeer -> otherPeer.isOutbound() && Arrays.equals(otherPeer.getPeerId(), peer.getPeerId()));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Filter out inbound peers that have corresponding outbound peer with the same ID
|
|
||||||
peers.removeIf(hasOutboundWithSameId);
|
|
||||||
|
|
||||||
return peers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns list of peers we connected to that have completed handshaking. */
|
/** Returns list of peers we connected to that have completed handshaking. */
|
||||||
public List<Peer> getOutboundHandshakedPeers() {
|
public List<Peer> getOutboundHandshakedPeers() {
|
||||||
synchronized (this.connectedPeers) {
|
synchronized (this.connectedPeers) {
|
||||||
@ -267,21 +253,13 @@ public class Network {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns Peer with inbound connection and matching ID, or null if none found. */
|
/** Returns first peer that has completed handshaking and has matching public key. */
|
||||||
public Peer getInboundPeerWithId(byte[] peerId) {
|
public Peer getHandshakedPeerWithPublicKey(byte[] publicKey) {
|
||||||
synchronized (this.connectedPeers) {
|
synchronized (this.connectedPeers) {
|
||||||
return this.connectedPeers.stream().filter(peer -> !peer.isOutbound() && peer.getPeerId() != null && Arrays.equals(peer.getPeerId(), peerId)).findAny().orElse(null);
|
return this.connectedPeers.stream().filter(peer -> peer.getHandshakeStatus() == Handshake.COMPLETED && Arrays.equals(peer.getPeersPublicKey(), publicKey)).findFirst().orElse(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns handshake-completed Peer with outbound connection and matching ID, or null if none found. */
|
|
||||||
public Peer getOutboundHandshakedPeerWithId(byte[] peerId) {
|
|
||||||
synchronized (this.connectedPeers) {
|
|
||||||
return this.connectedPeers.stream().filter(peer -> peer.isOutbound() && peer.getHandshakeStatus() == Handshake.COMPLETED && peer.getPeerId() != null && Arrays.equals(peer.getPeerId(), peerId)).findAny().orElse(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Peer list filters
|
// Peer list filters
|
||||||
|
|
||||||
/** Must be inside <tt>synchronized (this.selfPeers) {...}</tt> */
|
/** Must be inside <tt>synchronized (this.selfPeers) {...}</tt> */
|
||||||
@ -639,10 +617,22 @@ public class Network {
|
|||||||
|
|
||||||
// Peer callbacks
|
// Peer callbacks
|
||||||
|
|
||||||
/* package */ void wakeupChannelSelector() {
|
/*package*/ void wakeupChannelSelector() {
|
||||||
this.channelSelector.wakeup();
|
this.channelSelector.wakeup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*package*/ boolean verify(byte[] signature, byte[] message) {
|
||||||
|
return Crypto.verify(this.edPublicKeyParams.getEncoded(), signature, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*package*/ byte[] sign(byte[] message) {
|
||||||
|
return Crypto.sign(this.edPrivateKeyParams, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*package*/ byte[] getSharedSecret(byte[] publicKey) {
|
||||||
|
return Crypto.getSharedSecret(this.edPrivateKeyParams.getEncoded(), publicKey);
|
||||||
|
}
|
||||||
|
|
||||||
/** Called when Peer's thread has setup and is ready to process messages */
|
/** Called when Peer's thread has setup and is ready to process messages */
|
||||||
public void onPeerReady(Peer peer) {
|
public void onPeerReady(Peer peer) {
|
||||||
this.onMessage(peer, null);
|
this.onMessage(peer, null);
|
||||||
@ -692,17 +682,13 @@ public class Network {
|
|||||||
onGetPeersMessage(peer, message);
|
onGetPeersMessage(peer, message);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PEERS:
|
|
||||||
onPeersMessage(peer, message);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PING:
|
case PING:
|
||||||
onPingMessage(peer, message);
|
onPingMessage(peer, message);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case VERSION:
|
case HELLO:
|
||||||
case PEER_ID:
|
case CHALLENGE:
|
||||||
case PROOF:
|
case RESPONSE:
|
||||||
LOGGER.debug(() -> String.format("Unexpected handshaking message %s from peer %s", message.getType().name(), peer));
|
LOGGER.debug(() -> String.format("Unexpected handshaking message %s from peer %s", message.getType().name(), peer));
|
||||||
peer.disconnect("unexpected handshaking message");
|
peer.disconnect("unexpected handshaking message");
|
||||||
return;
|
return;
|
||||||
@ -711,14 +697,6 @@ public class Network {
|
|||||||
onPeersV2Message(peer, message);
|
onPeersV2Message(peer, message);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PEER_VERIFY:
|
|
||||||
onPeerVerifyMessage(peer, message);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case VERIFICATION_CODES:
|
|
||||||
onVerificationCodesMessage(peer, message);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Bump up to controller for possible action
|
// Bump up to controller for possible action
|
||||||
Controller.getInstance().onNetworkMessage(peer, message);
|
Controller.getInstance().onNetworkMessage(peer, message);
|
||||||
@ -731,12 +709,6 @@ public class Network {
|
|||||||
// Still handshaking
|
// Still handshaking
|
||||||
LOGGER.trace(() -> String.format("Handshake status %s, message %s from peer %s", handshakeStatus.name(), (message != null ? message.getType().name() : "null"), peer));
|
LOGGER.trace(() -> String.format("Handshake status %s, message %s from peer %s", handshakeStatus.name(), (message != null ? message.getType().name() : "null"), peer));
|
||||||
|
|
||||||
// v1 nodes are keen on sending PINGs early. Send to back of queue so we'll process right after handshake
|
|
||||||
if (message != null && message.getType() == MessageType.PING) {
|
|
||||||
peer.queueMessage(message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check message type is as expected
|
// Check message type is as expected
|
||||||
if (handshakeStatus.expectedMessageType != null && message.getType() != handshakeStatus.expectedMessageType) {
|
if (handshakeStatus.expectedMessageType != null && message.getType() != handshakeStatus.expectedMessageType) {
|
||||||
LOGGER.debug(() -> String.format("Unexpected %s message from %s, expected %s", message.getType().name(), peer, handshakeStatus.expectedMessageType));
|
LOGGER.debug(() -> String.format("Unexpected %s message from %s, expected %s", message.getType().name(), peer, handshakeStatus.expectedMessageType));
|
||||||
@ -775,22 +747,6 @@ public class Network {
|
|||||||
peer.disconnect("failed to send peers list");
|
peer.disconnect("failed to send peers list");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onPeersMessage(Peer peer, Message message) {
|
|
||||||
PeersMessage peersMessage = (PeersMessage) message;
|
|
||||||
|
|
||||||
List<PeerAddress> peerAddresses = new ArrayList<>();
|
|
||||||
|
|
||||||
// v1 PEERS message doesn't support port numbers so we have to add default port
|
|
||||||
for (InetAddress peerAddress : peersMessage.getPeerAddresses())
|
|
||||||
// This is always IPv4 so we don't have to worry about bracketing IPv6.
|
|
||||||
peerAddresses.add(PeerAddress.fromString(peerAddress.getHostAddress()));
|
|
||||||
|
|
||||||
// Also add peer's details
|
|
||||||
peerAddresses.add(PeerAddress.fromString(peer.getPeerData().getAddress().getHost()));
|
|
||||||
|
|
||||||
opportunisticMergePeers(peer.toString(), peerAddresses);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onPingMessage(Peer peer, Message message) {
|
private void onPingMessage(Peer peer, Message message) {
|
||||||
PingMessage pingMessage = (PingMessage) message;
|
PingMessage pingMessage = (PingMessage) message;
|
||||||
|
|
||||||
@ -821,45 +777,7 @@ public class Network {
|
|||||||
opportunisticMergePeers(peer.toString(), peerV2Addresses);
|
opportunisticMergePeers(peer.toString(), peerV2Addresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onPeerVerifyMessage(Peer peer, Message message) {
|
|
||||||
// Remote peer wants extra verification
|
|
||||||
possibleVerificationResponse(peer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onVerificationCodesMessage(Peer peer, Message message) {
|
|
||||||
VerificationCodesMessage verificationCodesMessage = (VerificationCodesMessage) message;
|
|
||||||
|
|
||||||
// Remote peer is sending the code it wants to receive back via our outbound connection to it
|
|
||||||
Peer ourUnverifiedPeer = Network.getInstance().getInboundPeerWithId(Network.getInstance().getOurPeerId());
|
|
||||||
ourUnverifiedPeer.setVerificationCodes(verificationCodesMessage.getVerificationCodeSent(), verificationCodesMessage.getVerificationCodeExpected());
|
|
||||||
|
|
||||||
possibleVerificationResponse(ourUnverifiedPeer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void possibleVerificationResponse(Peer peer) {
|
|
||||||
// Can't respond if we don't have the codes (yet?)
|
|
||||||
if (peer.getVerificationCodeExpected() == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
PeerVerifyMessage peerVerifyMessage = new PeerVerifyMessage(peer.getVerificationCodeExpected());
|
|
||||||
if (!peer.sendMessage(peerVerifyMessage)) {
|
|
||||||
peer.disconnect("failed to send verification code");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
peer.setVerificationCodes(null, null);
|
|
||||||
peer.setHandshakeStatus(Handshake.COMPLETED);
|
|
||||||
this.onHandshakeCompleted(peer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onHandshakeCompleted(Peer peer) {
|
private void onHandshakeCompleted(Peer peer) {
|
||||||
// Do we need extra handshaking because of peer doppelgangers?
|
|
||||||
if (peer.getPendingPeerId() != null) {
|
|
||||||
peer.setHandshakeStatus(Handshake.PEER_VERIFY);
|
|
||||||
peer.getHandshakeStatus().action(peer);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER.debug(String.format("Handshake completed with peer %s", peer));
|
LOGGER.debug(String.format("Handshake completed with peer %s", peer));
|
||||||
|
|
||||||
// Make a note that we've successfully completed handshake (and when)
|
// Make a note that we've successfully completed handshake (and when)
|
||||||
@ -930,86 +848,44 @@ public class Network {
|
|||||||
};
|
};
|
||||||
knownPeers.removeIf(notRecentlyConnected);
|
knownPeers.removeIf(notRecentlyConnected);
|
||||||
|
|
||||||
if (peer.getVersion() >= 2) {
|
List<PeerAddress> peerAddresses = new ArrayList<>();
|
||||||
List<PeerAddress> peerAddresses = new ArrayList<>();
|
|
||||||
|
|
||||||
for (PeerData peerData : knownPeers) {
|
for (PeerData peerData : knownPeers) {
|
||||||
try {
|
try {
|
||||||
InetAddress address = InetAddress.getByName(peerData.getAddress().getHost());
|
InetAddress address = InetAddress.getByName(peerData.getAddress().getHost());
|
||||||
|
|
||||||
// Don't send 'local' addresses if peer is not 'local'. e.g. don't send localhost:9084 to node4.qortal.org
|
// Don't send 'local' addresses if peer is not 'local'. e.g. don't send localhost:9084 to node4.qortal.org
|
||||||
if (!peer.getIsLocal() && Peer.isAddressLocal(address))
|
if (!peer.isLocal() && Peer.isAddressLocal(address))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
peerAddresses.add(peerData.getAddress());
|
peerAddresses.add(peerData.getAddress());
|
||||||
} catch (UnknownHostException e) {
|
} catch (UnknownHostException e) {
|
||||||
// Couldn't resolve hostname to IP address so discard
|
// Couldn't resolve hostname to IP address so discard
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New format PEERS_V2 message that supports hostnames, IPv6 and ports
|
|
||||||
return new PeersV2Message(peerAddresses);
|
|
||||||
} else {
|
|
||||||
// Map to socket addresses
|
|
||||||
List<InetAddress> peerAddresses = new ArrayList<>();
|
|
||||||
|
|
||||||
for (PeerData peerData : knownPeers) {
|
|
||||||
try {
|
|
||||||
// We have to resolve to literal IP address to check for IPv4-ness.
|
|
||||||
// This isn't great if hostnames have both IPv6 and IPv4 DNS entries.
|
|
||||||
InetAddress address = InetAddress.getByName(peerData.getAddress().getHost());
|
|
||||||
|
|
||||||
// Legacy PEERS message doesn't support IPv6
|
|
||||||
if (address instanceof Inet6Address)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Don't send 'local' addresses if peer is not 'local'. e.g. don't send localhost:9084 to node4.qortal.org
|
|
||||||
if (!peer.getIsLocal() && !Peer.isAddressLocal(address))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
peerAddresses.add(address);
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
// Couldn't resolve hostname to IP address so discard
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Legacy PEERS message that only sends IPv4 addresses
|
|
||||||
return new PeersMessage(peerAddresses);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New format PEERS_V2 message that supports hostnames, IPv6 and ports
|
||||||
|
return new PeersV2Message(peerAddresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Message buildHeightMessage(Peer peer, BlockData blockData) {
|
public Message buildHeightMessage(Peer peer, BlockData blockData) {
|
||||||
if (peer.getVersion() < 2) {
|
|
||||||
// Legacy height message
|
|
||||||
return new HeightMessage(blockData.getHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
// HEIGHT_V2 contains way more useful info
|
// HEIGHT_V2 contains way more useful info
|
||||||
return new HeightV2Message(blockData.getHeight(), blockData.getSignature(), blockData.getTimestamp(), blockData.getMinterPublicKey());
|
return new HeightV2Message(blockData.getHeight(), blockData.getSignature(), blockData.getTimestamp(), blockData.getMinterPublicKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Message buildNewTransactionMessage(Peer peer, TransactionData transactionData) {
|
public Message buildNewTransactionMessage(Peer peer, TransactionData transactionData) {
|
||||||
if (peer.getVersion() < 2) {
|
|
||||||
// Legacy TRANSACTION message
|
|
||||||
return new TransactionMessage(transactionData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// In V2 we send out transaction signature only and peers can decide whether to request the full transaction
|
// In V2 we send out transaction signature only and peers can decide whether to request the full transaction
|
||||||
return new TransactionSignaturesMessage(Collections.singletonList(transactionData.getSignature()));
|
return new TransactionSignaturesMessage(Collections.singletonList(transactionData.getSignature()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Message buildGetUnconfirmedTransactionsMessage(Peer peer) {
|
public Message buildGetUnconfirmedTransactionsMessage(Peer peer) {
|
||||||
// V2 only
|
|
||||||
if (peer.getVersion() < 2)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return new GetUnconfirmedTransactionsMessage();
|
return new GetUnconfirmedTransactionsMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peer-management calls
|
// Peer-management calls
|
||||||
|
|
||||||
public void noteToSelf(Peer peer) {
|
public void noteToSelf(Peer peer) {
|
||||||
LOGGER.info(String.format("No longer considering peer address %s as it connects to self", peer));
|
LOGGER.info(() -> String.format("No longer considering peer address %s as it connects to self", peer));
|
||||||
|
|
||||||
synchronized (this.selfPeers) {
|
synchronized (this.selfPeers) {
|
||||||
this.selfPeers.add(peer.getPeerData().getAddress());
|
this.selfPeers.add(peer.getPeerData().getAddress());
|
||||||
@ -1230,7 +1106,7 @@ public class Network {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
broadcastExecutor.execute(new Broadcaster(this.getUniqueHandshakedPeers(), peerMessageBuilder));
|
broadcastExecutor.execute(new Broadcaster(this.getHandshakedPeers(), peerMessageBuilder));
|
||||||
} catch (RejectedExecutionException e) {
|
} catch (RejectedExecutionException e) {
|
||||||
// Can't execute - probably because we're shutting down, so ignore
|
// Can't execute - probably because we're shutting down, so ignore
|
||||||
}
|
}
|
||||||
|
@ -24,9 +24,9 @@ import org.apache.logging.log4j.LogManager;
|
|||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.qortal.data.network.PeerChainTipData;
|
import org.qortal.data.network.PeerChainTipData;
|
||||||
import org.qortal.data.network.PeerData;
|
import org.qortal.data.network.PeerData;
|
||||||
|
import org.qortal.network.message.ChallengeMessage;
|
||||||
import org.qortal.network.message.Message;
|
import org.qortal.network.message.Message;
|
||||||
import org.qortal.network.message.PingMessage;
|
import org.qortal.network.message.PingMessage;
|
||||||
import org.qortal.network.message.VersionMessage;
|
|
||||||
import org.qortal.network.message.Message.MessageException;
|
import org.qortal.network.message.Message.MessageException;
|
||||||
import org.qortal.network.message.Message.MessageType;
|
import org.qortal.network.message.Message.MessageType;
|
||||||
import org.qortal.settings.Settings;
|
import org.qortal.settings.Settings;
|
||||||
@ -42,7 +42,7 @@ public class Peer {
|
|||||||
private static final Logger LOGGER = LogManager.getLogger(Peer.class);
|
private static final Logger LOGGER = LogManager.getLogger(Peer.class);
|
||||||
|
|
||||||
/** Maximum time to allow <tt>connect()</tt> to remote peer to complete. (ms) */
|
/** Maximum time to allow <tt>connect()</tt> to remote peer to complete. (ms) */
|
||||||
private static final int CONNECT_TIMEOUT = 1000; // ms
|
private static final int CONNECT_TIMEOUT = 2000; // ms
|
||||||
|
|
||||||
/** Maximum time to wait for a message reply to arrive from peer. (ms) */
|
/** Maximum time to wait for a message reply to arrive from peer. (ms) */
|
||||||
private static final int RESPONSE_TIMEOUT = 2000; // ms
|
private static final int RESPONSE_TIMEOUT = 2000; // ms
|
||||||
@ -54,9 +54,6 @@ public class Peer {
|
|||||||
*/
|
*/
|
||||||
private static final int PING_INTERVAL = 20_000; // ms
|
private static final int PING_INTERVAL = 20_000; // ms
|
||||||
|
|
||||||
/** Threshold for buildTimestamp in VERSION messages where we consider peer to be using v2 protocol. */
|
|
||||||
private static final long V2_PROTOCOL_TIMESTAMP_THRESHOLD = 1546300800L; // midnight starting 1st Jan 2019
|
|
||||||
|
|
||||||
private volatile boolean isStopping = false;
|
private volatile boolean isStopping = false;
|
||||||
|
|
||||||
private SocketChannel socketChannel = null;
|
private SocketChannel socketChannel = null;
|
||||||
@ -65,41 +62,48 @@ public class Peer {
|
|||||||
private boolean isLocal;
|
private boolean isLocal;
|
||||||
|
|
||||||
private final Object byteBufferLock = new Object();
|
private final Object byteBufferLock = new Object();
|
||||||
private volatile ByteBuffer byteBuffer;
|
private ByteBuffer byteBuffer;
|
||||||
|
|
||||||
private Map<Integer, BlockingQueue<Message>> replyQueues;
|
private Map<Integer, BlockingQueue<Message>> replyQueues;
|
||||||
private LinkedBlockingQueue<Message> pendingMessages;
|
private LinkedBlockingQueue<Message> pendingMessages;
|
||||||
|
|
||||||
/** True if we created connection to peer, false if we accepted incoming connection from peer. */
|
/** True if we created connection to peer, false if we accepted incoming connection from peer. */
|
||||||
private final boolean isOutbound;
|
private final boolean isOutbound;
|
||||||
/** Numeric protocol version, typically 1 or 2. */
|
|
||||||
private volatile Integer version;
|
|
||||||
private volatile byte[] peerId;
|
|
||||||
|
|
||||||
private volatile Handshake handshakeStatus = Handshake.STARTED;
|
private final Object handshakingLock = new Object();
|
||||||
|
private Handshake handshakeStatus = Handshake.STARTED;
|
||||||
private volatile boolean handshakeMessagePending = false;
|
private volatile boolean handshakeMessagePending = false;
|
||||||
|
|
||||||
private volatile byte[] pendingPeerId;
|
|
||||||
private volatile byte[] verificationCodeSent;
|
|
||||||
private volatile byte[] verificationCodeExpected;
|
|
||||||
|
|
||||||
private volatile PeerData peerData = null;
|
|
||||||
|
|
||||||
/** Timestamp of when socket was accepted, or connected. */
|
/** Timestamp of when socket was accepted, or connected. */
|
||||||
private volatile Long connectionTimestamp = null;
|
private Long connectionTimestamp = null;
|
||||||
|
|
||||||
/** Peer's value of connectionTimestamp. */
|
|
||||||
private volatile Long peersConnectionTimestamp = null;
|
|
||||||
|
|
||||||
/** Version info as reported by peer. */
|
|
||||||
private volatile VersionMessage versionMessage = null;
|
|
||||||
|
|
||||||
/** Last PING message round-trip time (ms). */
|
/** Last PING message round-trip time (ms). */
|
||||||
private volatile Long lastPing = null;
|
private Long lastPing = null;
|
||||||
/** When last PING message was sent, or null if pings not started yet. */
|
/** When last PING message was sent, or null if pings not started yet. */
|
||||||
private volatile Long lastPingSent;
|
private Long lastPingSent;
|
||||||
|
|
||||||
|
byte[] ourChallenge;
|
||||||
|
|
||||||
|
// Peer info
|
||||||
|
|
||||||
|
private final Object peerInfoLock = new Object();
|
||||||
|
|
||||||
|
private String peersNodeId;
|
||||||
|
private byte[] peersPublicKey;
|
||||||
|
private byte[] peersChallenge;
|
||||||
|
|
||||||
|
private PeerData peerData = null;
|
||||||
|
|
||||||
|
/** Peer's value of connectionTimestamp. */
|
||||||
|
private Long peersConnectionTimestamp = null;
|
||||||
|
|
||||||
|
/** Version string as reported by peer. */
|
||||||
|
private String peersVersionString = null;
|
||||||
|
/** Numeric version of peer. */
|
||||||
|
private Long peersVersion = null;
|
||||||
|
|
||||||
/** Latest block info as reported by peer. */
|
/** Latest block info as reported by peer. */
|
||||||
private volatile PeerChainTipData chainTipData;
|
private PeerChainTipData peersChainTipData;
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
@ -124,16 +128,20 @@ public class Peer {
|
|||||||
|
|
||||||
// Getters / setters
|
// Getters / setters
|
||||||
|
|
||||||
public SocketChannel getSocketChannel() {
|
|
||||||
return this.socketChannel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isStopping() {
|
public boolean isStopping() {
|
||||||
return this.isStopping;
|
return this.isStopping;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PeerData getPeerData() {
|
public SocketChannel getSocketChannel() {
|
||||||
return this.peerData;
|
return this.socketChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InetSocketAddress getResolvedAddress() {
|
||||||
|
return this.resolvedAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLocal() {
|
||||||
|
return this.isLocal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOutbound() {
|
public boolean isOutbound() {
|
||||||
@ -141,103 +149,131 @@ public class Peer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Handshake getHandshakeStatus() {
|
public Handshake getHandshakeStatus() {
|
||||||
return this.handshakeStatus;
|
synchronized (this.handshakingLock) {
|
||||||
}
|
return this.handshakeStatus;
|
||||||
|
|
||||||
public void setHandshakeStatus(Handshake handshakeStatus) {
|
|
||||||
this.handshakeStatus = handshakeStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetHandshakeMessagePending() {
|
|
||||||
this.handshakeMessagePending = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public VersionMessage getVersionMessage() {
|
|
||||||
return this.versionMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVersionMessage(VersionMessage versionMessage) {
|
|
||||||
this.versionMessage = versionMessage;
|
|
||||||
|
|
||||||
if (this.versionMessage.getBuildTimestamp() >= V2_PROTOCOL_TIMESTAMP_THRESHOLD) {
|
|
||||||
this.version = 2; // enhanced protocol
|
|
||||||
} else {
|
|
||||||
this.version = 1; // legacy protocol
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getVersion() {
|
/*package*/ void setHandshakeStatus(Handshake handshakeStatus) {
|
||||||
return this.version;
|
synchronized (this.handshakingLock) {
|
||||||
|
this.handshakeStatus = handshakeStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*package*/ void resetHandshakeMessagePending() {
|
||||||
|
this.handshakeMessagePending = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PeerData getPeerData() {
|
||||||
|
synchronized (this.peerInfoLock) {
|
||||||
|
return this.peerData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getConnectionTimestamp() {
|
public Long getConnectionTimestamp() {
|
||||||
return this.connectionTimestamp;
|
synchronized (this.peerInfoLock) {
|
||||||
|
return this.connectionTimestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPeersVersionString() {
|
||||||
|
synchronized (this.peerInfoLock) {
|
||||||
|
return this.peersVersionString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getPeersVersion() {
|
||||||
|
synchronized (this.peerInfoLock) {
|
||||||
|
return this.peersVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*package*/ void setPeersVersion(String versionString, long version) {
|
||||||
|
synchronized (this.peerInfoLock) {
|
||||||
|
this.peersVersionString = versionString;
|
||||||
|
this.peersVersion = version;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getPeersConnectionTimestamp() {
|
public Long getPeersConnectionTimestamp() {
|
||||||
return this.peersConnectionTimestamp;
|
synchronized (this.peerInfoLock) {
|
||||||
|
return this.peersConnectionTimestamp;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ void setPeersConnectionTimestamp(Long peersConnectionTimestamp) {
|
/*package*/ void setPeersConnectionTimestamp(Long peersConnectionTimestamp) {
|
||||||
this.peersConnectionTimestamp = peersConnectionTimestamp;
|
synchronized (this.peerInfoLock) {
|
||||||
|
this.peersConnectionTimestamp = peersConnectionTimestamp;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getLastPing() {
|
public Long getLastPing() {
|
||||||
return this.lastPing;
|
synchronized (this.peerInfoLock) {
|
||||||
|
return this.lastPing;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLastPing(long lastPing) {
|
/*package*/ void setLastPing(long lastPing) {
|
||||||
this.lastPing = lastPing;
|
synchronized (this.peerInfoLock) {
|
||||||
|
this.lastPing = lastPing;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public InetSocketAddress getResolvedAddress() {
|
/*package*/ byte[] getOurChallenge() {
|
||||||
return this.resolvedAddress;
|
return this.ourChallenge;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getIsLocal() {
|
public String getPeersNodeId() {
|
||||||
return this.isLocal;
|
synchronized (this.peerInfoLock) {
|
||||||
|
return this.peersNodeId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getPeerId() {
|
/*package*/ void setPeersNodeId(String peersNodeId) {
|
||||||
return this.peerId;
|
synchronized (this.peerInfoLock) {
|
||||||
|
this.peersNodeId = peersNodeId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPeerId(byte[] peerId) {
|
public byte[] getPeersPublicKey() {
|
||||||
this.peerId = peerId;
|
synchronized (this.peerInfoLock) {
|
||||||
|
return this.peersPublicKey;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getPendingPeerId() {
|
/*package*/ void setPeersPublicKey(byte[] peerPublicKey) {
|
||||||
return this.pendingPeerId;
|
synchronized (this.peerInfoLock) {
|
||||||
|
this.peersPublicKey = peerPublicKey;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPendingPeerId(byte[] peerId) {
|
public byte[] getPeersChallenge() {
|
||||||
this.pendingPeerId = peerId;
|
synchronized (this.peerInfoLock) {
|
||||||
|
return this.peersChallenge;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getVerificationCodeSent() {
|
/*package*/ void setPeersChallenge(byte[] peersChallenge) {
|
||||||
return this.verificationCodeSent;
|
synchronized (this.peerInfoLock) {
|
||||||
}
|
this.peersChallenge = peersChallenge;
|
||||||
|
}
|
||||||
public byte[] getVerificationCodeExpected() {
|
|
||||||
return this.verificationCodeExpected;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVerificationCodes(byte[] sent, byte[] expected) {
|
|
||||||
this.verificationCodeSent = sent;
|
|
||||||
this.verificationCodeExpected = expected;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PeerChainTipData getChainTipData() {
|
public PeerChainTipData getChainTipData() {
|
||||||
return this.chainTipData;
|
synchronized (this.peerInfoLock) {
|
||||||
|
return this.peersChainTipData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setChainTipData(PeerChainTipData chainTipData) {
|
public void setChainTipData(PeerChainTipData chainTipData) {
|
||||||
this.chainTipData = chainTipData;
|
synchronized (this.peerInfoLock) {
|
||||||
|
this.peersChainTipData = chainTipData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ void queueMessage(Message message) {
|
/*package*/ void queueMessage(Message message) {
|
||||||
if (!this.pendingMessages.offer(message))
|
if (!this.pendingMessages.offer(message))
|
||||||
LOGGER.info(String.format("No room to queue message from peer %s - discarding", this));
|
LOGGER.info(() -> String.format("No room to queue message from peer %s - discarding", this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -248,14 +284,6 @@ public class Peer {
|
|||||||
|
|
||||||
// Processing
|
// Processing
|
||||||
|
|
||||||
public void generateVerificationCodes() {
|
|
||||||
verificationCodeSent = new byte[Network.PEER_ID_LENGTH];
|
|
||||||
new SecureRandom().nextBytes(verificationCodeSent);
|
|
||||||
|
|
||||||
verificationCodeExpected = new byte[Network.PEER_ID_LENGTH];
|
|
||||||
new SecureRandom().nextBytes(verificationCodeExpected);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sharedSetup(Selector channelSelector) throws IOException {
|
private void sharedSetup(Selector channelSelector) throws IOException {
|
||||||
this.connectionTimestamp = NTP.getTime();
|
this.connectionTimestamp = NTP.getTime();
|
||||||
this.socketChannel.setOption(StandardSocketOptions.TCP_NODELAY, true);
|
this.socketChannel.setOption(StandardSocketOptions.TCP_NODELAY, true);
|
||||||
@ -264,6 +292,10 @@ public class Peer {
|
|||||||
this.byteBuffer = null; // Defer allocation to when we need it, to save memory. Sorry GC!
|
this.byteBuffer = null; // Defer allocation to when we need it, to save memory. Sorry GC!
|
||||||
this.replyQueues = Collections.synchronizedMap(new HashMap<Integer, BlockingQueue<Message>>());
|
this.replyQueues = Collections.synchronizedMap(new HashMap<Integer, BlockingQueue<Message>>());
|
||||||
this.pendingMessages = new LinkedBlockingQueue<>();
|
this.pendingMessages = new LinkedBlockingQueue<>();
|
||||||
|
|
||||||
|
Random random = new SecureRandom();
|
||||||
|
this.ourChallenge = new byte[ChallengeMessage.CHALLENGE_LENGTH];
|
||||||
|
random.nextBytes(this.ourChallenge);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SocketChannel connect(Selector channelSelector) {
|
public SocketChannel connect(Selector channelSelector) {
|
||||||
@ -378,9 +410,11 @@ public class Peer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* package */ ExecuteProduceConsume.Task getMessageTask() {
|
/* package */ ExecuteProduceConsume.Task getMessageTask() {
|
||||||
// If we are still handshaking and there is a message yet to be processed
|
/*
|
||||||
// then don't produce another message task.
|
* If we are still handshaking and there is a message yet to be processed then
|
||||||
// This allows us to process handshake messages sequentially.
|
* don't produce another message task. This allows us to process handshake
|
||||||
|
* messages sequentially.
|
||||||
|
*/
|
||||||
if (this.handshakeMessagePending)
|
if (this.handshakeMessagePending)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@ -454,9 +488,10 @@ public class Peer {
|
|||||||
BlockingQueue<Message> blockingQueue = new ArrayBlockingQueue<>(1);
|
BlockingQueue<Message> blockingQueue = new ArrayBlockingQueue<>(1);
|
||||||
|
|
||||||
// Assign random ID to this message
|
// Assign random ID to this message
|
||||||
|
Random random = new Random();
|
||||||
int id;
|
int id;
|
||||||
do {
|
do {
|
||||||
id = new Random().nextInt(Integer.MAX_VALUE - 1) + 1;
|
id = random.nextInt(Integer.MAX_VALUE - 1) + 1;
|
||||||
|
|
||||||
// Put queue into map (keyed by message ID) so we can poll for a response
|
// Put queue into map (keyed by message ID) so we can poll for a response
|
||||||
// If putIfAbsent() doesn't return null, then this ID is already taken
|
// If putIfAbsent() doesn't return null, then this ID is already taken
|
||||||
|
@ -1,126 +0,0 @@
|
|||||||
package org.qortal.network;
|
|
||||||
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.HashSet;
|
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import org.qortal.network.message.ProofMessage;
|
|
||||||
|
|
||||||
import com.google.common.primitives.Longs;
|
|
||||||
|
|
||||||
public class Proof extends Thread {
|
|
||||||
|
|
||||||
private static final int MIN_PROOF_ZEROS = 2;
|
|
||||||
private static final HashSet<Long> seenSalts = new HashSet<>();
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger(Proof.class);
|
|
||||||
|
|
||||||
private Peer peer;
|
|
||||||
|
|
||||||
public Proof(Peer peer) {
|
|
||||||
this.peer = peer;
|
|
||||||
setDaemon(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean seenSalt(long salt) {
|
|
||||||
synchronized (seenSalts) {
|
|
||||||
return seenSalts.contains(salt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void addSalt(long salt) {
|
|
||||||
synchronized (seenSalts) {
|
|
||||||
seenSalts.add(salt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
setName("Proof for peer " + this.peer);
|
|
||||||
|
|
||||||
// Do proof-of-work calculation to gain acceptance with remote end
|
|
||||||
final long startTime = LOGGER.isTraceEnabled() ? System.currentTimeMillis() : 0;
|
|
||||||
|
|
||||||
// Remote end knows this (approximately)
|
|
||||||
long timestamp = this.peer.getConnectionTimestamp();
|
|
||||||
|
|
||||||
// Needs to be unique on the remote end
|
|
||||||
long salt = new SecureRandom().nextLong();
|
|
||||||
|
|
||||||
byte[] message = new byte[8 + 8 + 8]; // nonce + salt + timestamp
|
|
||||||
|
|
||||||
byte[] saltBytes = Longs.toByteArray(salt);
|
|
||||||
System.arraycopy(saltBytes, 0, message, 8, saltBytes.length);
|
|
||||||
|
|
||||||
byte[] timestampBytes = Longs.toByteArray(timestamp);
|
|
||||||
System.arraycopy(timestampBytes, 0, message, 8 + 8, timestampBytes.length);
|
|
||||||
|
|
||||||
MessageDigest sha256;
|
|
||||||
try {
|
|
||||||
sha256 = MessageDigest.getInstance("SHA256");
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
// Can't progress
|
|
||||||
throw new RuntimeException("Message digest SHA256 not available");
|
|
||||||
}
|
|
||||||
|
|
||||||
long nonce;
|
|
||||||
for (nonce = 0; nonce < Long.MAX_VALUE; ++nonce) {
|
|
||||||
// Check whether we're shutting down every so often
|
|
||||||
if ((nonce & 0xff) == 0 && (this.peer.isStopping() || Thread.currentThread().isInterrupted()))
|
|
||||||
// throw new InterruptedException("Interrupted during peer proof calculation");
|
|
||||||
return;
|
|
||||||
|
|
||||||
byte[] nonceBytes = Longs.toByteArray(nonce);
|
|
||||||
System.arraycopy(nonceBytes, 0, message, 0, nonceBytes.length);
|
|
||||||
|
|
||||||
byte[] digest = sha256.digest(message);
|
|
||||||
|
|
||||||
if (check(digest))
|
|
||||||
break;
|
|
||||||
|
|
||||||
sha256.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER.trace(() -> String.format("Proof for peer %s took %dms", this.peer, System.currentTimeMillis() - startTime));
|
|
||||||
|
|
||||||
ProofMessage proofMessage = new ProofMessage(timestamp, salt, nonce);
|
|
||||||
peer.sendMessage(proofMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean check(byte[] digest) {
|
|
||||||
int idx;
|
|
||||||
for (idx = 0; idx < MIN_PROOF_ZEROS; ++idx)
|
|
||||||
if (digest[idx] != 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
return idx == MIN_PROOF_ZEROS;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean check(long timestamp, long salt, long nonce) {
|
|
||||||
byte[] message = new byte[8 + 8 + 8];
|
|
||||||
|
|
||||||
byte[] saltBytes = Longs.toByteArray(salt);
|
|
||||||
System.arraycopy(saltBytes, 0, message, 8, saltBytes.length);
|
|
||||||
|
|
||||||
byte[] timestampBytes = Longs.toByteArray(timestamp);
|
|
||||||
System.arraycopy(timestampBytes, 0, message, 8 + 8, timestampBytes.length);
|
|
||||||
|
|
||||||
byte[] nonceBytes = Longs.toByteArray(nonce);
|
|
||||||
System.arraycopy(nonceBytes, 0, message, 0, nonceBytes.length);
|
|
||||||
|
|
||||||
MessageDigest sha256;
|
|
||||||
try {
|
|
||||||
sha256 = MessageDigest.getInstance("SHA256");
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
// Can't progress
|
|
||||||
throw new RuntimeException("Message digest SHA256 not available");
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] digest = sha256.digest(message);
|
|
||||||
|
|
||||||
return check(digest);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,56 @@
|
|||||||
|
package org.qortal.network.message;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import org.qortal.transform.Transformer;
|
||||||
|
|
||||||
|
public class ChallengeMessage extends Message {
|
||||||
|
|
||||||
|
public static final int CHALLENGE_LENGTH = 32;
|
||||||
|
|
||||||
|
private final byte[] publicKey;
|
||||||
|
private final byte[] challenge;
|
||||||
|
|
||||||
|
private ChallengeMessage(int id, byte[] publicKey, byte[] challenge) {
|
||||||
|
super(id, MessageType.CHALLENGE);
|
||||||
|
|
||||||
|
this.publicKey = publicKey;
|
||||||
|
this.challenge = challenge;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChallengeMessage(byte[] publicKey, byte[] challenge) {
|
||||||
|
this(-1, publicKey, challenge);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getPublicKey() {
|
||||||
|
return this.publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getChallenge() {
|
||||||
|
return this.challenge;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) {
|
||||||
|
byte[] publicKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
||||||
|
byteBuffer.get(publicKey);
|
||||||
|
|
||||||
|
byte[] challenge = new byte[CHALLENGE_LENGTH];
|
||||||
|
byteBuffer.get(challenge);
|
||||||
|
|
||||||
|
return new ChallengeMessage(id, publicKey, challenge);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected byte[] toData() throws IOException {
|
||||||
|
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
bytes.write(this.publicKey);
|
||||||
|
|
||||||
|
bytes.write(this.challenge);
|
||||||
|
|
||||||
|
return bytes.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,54 +0,0 @@
|
|||||||
package org.qortal.network.message;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import org.qortal.transform.block.BlockTransformer;
|
|
||||||
|
|
||||||
public class GetSignaturesMessage extends Message {
|
|
||||||
|
|
||||||
private static final int BLOCK_SIGNATURE_LENGTH = BlockTransformer.BLOCK_SIGNATURE_LENGTH;
|
|
||||||
|
|
||||||
private byte[] parentSignature;
|
|
||||||
|
|
||||||
public GetSignaturesMessage(byte[] parentSignature) {
|
|
||||||
this(-1, parentSignature);
|
|
||||||
}
|
|
||||||
|
|
||||||
private GetSignaturesMessage(int id, byte[] parentSignature) {
|
|
||||||
super(id, MessageType.GET_SIGNATURES);
|
|
||||||
|
|
||||||
this.parentSignature = parentSignature;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getParentSignature() {
|
|
||||||
return this.parentSignature;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
|
||||||
if (bytes.remaining() != BLOCK_SIGNATURE_LENGTH)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
byte[] parentSignature = new byte[BLOCK_SIGNATURE_LENGTH];
|
|
||||||
|
|
||||||
bytes.get(parentSignature);
|
|
||||||
|
|
||||||
return new GetSignaturesMessage(id, parentSignature);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected byte[] toData() {
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
|
||||||
|
|
||||||
bytes.write(this.parentSignature);
|
|
||||||
|
|
||||||
return bytes.toByteArray();
|
|
||||||
} catch (IOException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
65
src/main/java/org/qortal/network/message/GoodbyeMessage.java
Normal file
65
src/main/java/org/qortal/network/message/GoodbyeMessage.java
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package org.qortal.network.message;
|
||||||
|
|
||||||
|
import static java.util.Arrays.stream;
|
||||||
|
import static java.util.stream.Collectors.toMap;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.google.common.primitives.Ints;
|
||||||
|
|
||||||
|
public class GoodbyeMessage extends Message {
|
||||||
|
|
||||||
|
public enum Reason {
|
||||||
|
NO_HELLO(1),
|
||||||
|
BAD_HELLO(2),
|
||||||
|
BAD_HELLO_VERSION(3),
|
||||||
|
BAD_HELLO_TIMESTAMP(4);
|
||||||
|
|
||||||
|
public final int value;
|
||||||
|
|
||||||
|
private static final Map<Integer, Reason> map = stream(Reason.values())
|
||||||
|
.collect(toMap(reason -> reason.value, reason -> reason));
|
||||||
|
|
||||||
|
private Reason(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Reason valueOf(int value) {
|
||||||
|
return map.get(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Reason reason;
|
||||||
|
|
||||||
|
private GoodbyeMessage(int id, Reason reason) {
|
||||||
|
super(id, MessageType.GOODBYE);
|
||||||
|
|
||||||
|
this.reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GoodbyeMessage(Reason reason) {
|
||||||
|
this(-1, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Reason getReason() {
|
||||||
|
return this.reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) {
|
||||||
|
int reasonValue = byteBuffer.getInt();
|
||||||
|
|
||||||
|
Reason reason = Reason.valueOf(reasonValue);
|
||||||
|
if (reason == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new GoodbyeMessage(id, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected byte[] toData() throws IOException {
|
||||||
|
return Ints.toByteArray(this.reason.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,47 +0,0 @@
|
|||||||
package org.qortal.network.message;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import com.google.common.primitives.Ints;
|
|
||||||
|
|
||||||
public class HeightMessage extends Message {
|
|
||||||
|
|
||||||
private int height;
|
|
||||||
|
|
||||||
public HeightMessage(int height) {
|
|
||||||
this(-1, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
private HeightMessage(int id, int height) {
|
|
||||||
super(id, MessageType.HEIGHT);
|
|
||||||
|
|
||||||
this.height = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getHeight() {
|
|
||||||
return this.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
|
||||||
int height = bytes.getInt();
|
|
||||||
|
|
||||||
return new HeightMessage(id, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected byte[] toData() {
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
|
||||||
|
|
||||||
bytes.write(Ints.toByteArray(this.height));
|
|
||||||
|
|
||||||
return bytes.toByteArray();
|
|
||||||
} catch (IOException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
55
src/main/java/org/qortal/network/message/HelloMessage.java
Normal file
55
src/main/java/org/qortal/network/message/HelloMessage.java
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package org.qortal.network.message;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import org.qortal.transform.TransformationException;
|
||||||
|
import org.qortal.utils.Serialization;
|
||||||
|
|
||||||
|
import com.google.common.primitives.Longs;
|
||||||
|
|
||||||
|
public class HelloMessage extends Message {
|
||||||
|
|
||||||
|
private final long timestamp;
|
||||||
|
private final String versionString;
|
||||||
|
|
||||||
|
private HelloMessage(int id, long timestamp, String versionString) {
|
||||||
|
super(id, MessageType.HELLO);
|
||||||
|
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.versionString = versionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HelloMessage(long timestamp, String versionString) {
|
||||||
|
this(-1, timestamp, versionString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimestamp() {
|
||||||
|
return this.timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVersionString() {
|
||||||
|
return this.versionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws TransformationException {
|
||||||
|
long timestamp = byteBuffer.getLong();
|
||||||
|
|
||||||
|
String versionString = Serialization.deserializeSizedString(byteBuffer, 255);
|
||||||
|
|
||||||
|
return new HelloMessage(id, timestamp, versionString);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected byte[] toData() throws IOException {
|
||||||
|
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
bytes.write(Longs.toByteArray(this.timestamp));
|
||||||
|
|
||||||
|
Serialization.serializeSizedString(bytes, this.versionString);
|
||||||
|
|
||||||
|
return bytes.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -4,6 +4,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.network.Network;
|
import org.qortal.network.Network;
|
||||||
|
import org.qortal.transform.TransformationException;
|
||||||
|
|
||||||
import com.google.common.primitives.Ints;
|
import com.google.common.primitives.Ints;
|
||||||
|
|
||||||
@ -45,32 +46,41 @@ public abstract class Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum MessageType {
|
public enum MessageType {
|
||||||
GET_PEERS(1),
|
// Handshaking
|
||||||
PEERS(2),
|
HELLO(0),
|
||||||
HEIGHT(3),
|
GOODBYE(1),
|
||||||
GET_SIGNATURES(4),
|
CHALLENGE(2),
|
||||||
SIGNATURES(5),
|
RESPONSE(3),
|
||||||
GET_BLOCK(6),
|
|
||||||
BLOCK(7),
|
// Status / notifications
|
||||||
TRANSACTION(8),
|
HEIGHT_V2(10),
|
||||||
PING(9),
|
PING(11),
|
||||||
VERSION(10),
|
PONG(12),
|
||||||
PEER_ID(11),
|
|
||||||
PROOF(12),
|
// Requesting data
|
||||||
PEERS_V2(13),
|
PEERS_V2(20),
|
||||||
GET_BLOCK_SUMMARIES(14),
|
GET_PEERS(21),
|
||||||
BLOCK_SUMMARIES(15),
|
|
||||||
GET_SIGNATURES_V2(16),
|
TRANSACTION(30),
|
||||||
PEER_VERIFY(17),
|
GET_TRANSACTION(31),
|
||||||
VERIFICATION_CODES(18),
|
|
||||||
HEIGHT_V2(19),
|
TRANSACTION_SIGNATURES(40),
|
||||||
GET_TRANSACTION(20),
|
GET_UNCONFIRMED_TRANSACTIONS(41),
|
||||||
GET_UNCONFIRMED_TRANSACTIONS(21),
|
|
||||||
TRANSACTION_SIGNATURES(22),
|
BLOCK(50),
|
||||||
GET_ARBITRARY_DATA(23),
|
GET_BLOCK(51),
|
||||||
ARBITRARY_DATA(24),
|
|
||||||
GET_ONLINE_ACCOUNTS(25),
|
SIGNATURES(60),
|
||||||
ONLINE_ACCOUNTS(26);
|
GET_SIGNATURES_V2(61),
|
||||||
|
|
||||||
|
BLOCK_SUMMARIES(70),
|
||||||
|
GET_BLOCK_SUMMARIES(71),
|
||||||
|
|
||||||
|
ONLINE_ACCOUNTS(80),
|
||||||
|
GET_ONLINE_ACCOUNTS(81),
|
||||||
|
|
||||||
|
ARBITRARY_DATA(90),
|
||||||
|
GET_ARBITRARY_DATA(91);
|
||||||
|
|
||||||
public final int value;
|
public final int value;
|
||||||
public final Method fromByteBufferMethod;
|
public final Method fromByteBufferMethod;
|
||||||
@ -263,11 +273,11 @@ public abstract class Message {
|
|||||||
throw new MessageException(String.format("About to send message with length %d larger than allowed %d", bytes.size(), MAX_DATA_SIZE));
|
throw new MessageException(String.format("About to send message with length %d larger than allowed %d", bytes.size(), MAX_DATA_SIZE));
|
||||||
|
|
||||||
return bytes.toByteArray();
|
return bytes.toByteArray();
|
||||||
} catch (IOException e) {
|
} catch (IOException | TransformationException e) {
|
||||||
throw new MessageException("Failed to serialize message", e);
|
throw new MessageException("Failed to serialize message", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract byte[] toData();
|
protected abstract byte[] toData() throws IOException, TransformationException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
package org.qortal.network.message;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import org.qortal.network.Network;
|
|
||||||
|
|
||||||
public class PeerIdMessage extends Message {
|
|
||||||
|
|
||||||
private byte[] peerId;
|
|
||||||
|
|
||||||
public PeerIdMessage(byte[] peerId) {
|
|
||||||
this(-1, peerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private PeerIdMessage(int id, byte[] peerId) {
|
|
||||||
super(id, MessageType.PEER_ID);
|
|
||||||
|
|
||||||
this.peerId = peerId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getPeerId() {
|
|
||||||
return this.peerId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
|
||||||
if (bytes.remaining() != Network.PEER_ID_LENGTH)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
byte[] peerId = new byte[Network.PEER_ID_LENGTH];
|
|
||||||
|
|
||||||
bytes.get(peerId);
|
|
||||||
|
|
||||||
return new PeerIdMessage(id, peerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected byte[] toData() {
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
|
||||||
|
|
||||||
bytes.write(this.peerId);
|
|
||||||
|
|
||||||
return bytes.toByteArray();
|
|
||||||
} catch (IOException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
package org.qortal.network.message;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import org.qortal.network.Network;
|
|
||||||
|
|
||||||
public class PeerVerifyMessage extends Message {
|
|
||||||
|
|
||||||
private byte[] verificationCode;
|
|
||||||
|
|
||||||
public PeerVerifyMessage(byte[] verificationCode) {
|
|
||||||
this(-1, verificationCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
private PeerVerifyMessage(int id, byte[] verificationCode) {
|
|
||||||
super(id, MessageType.PEER_VERIFY);
|
|
||||||
|
|
||||||
this.verificationCode = verificationCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getVerificationCode() {
|
|
||||||
return this.verificationCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
|
||||||
if (bytes.remaining() != Network.PEER_ID_LENGTH)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
byte[] verificationCode = new byte[Network.PEER_ID_LENGTH];
|
|
||||||
bytes.get(verificationCode);
|
|
||||||
|
|
||||||
return new PeerVerifyMessage(id, verificationCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected byte[] toData() {
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
|
||||||
|
|
||||||
bytes.write(this.verificationCode);
|
|
||||||
|
|
||||||
return bytes.toByteArray();
|
|
||||||
} catch (IOException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
package org.qortal.network.message;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.net.Inet6Address;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.google.common.primitives.Ints;
|
|
||||||
|
|
||||||
// NOTE: this legacy message only supports 4-byte IPv4 addresses and doesn't send port number either
|
|
||||||
public class PeersMessage extends Message {
|
|
||||||
|
|
||||||
private static final int ADDRESS_LENGTH = 4;
|
|
||||||
|
|
||||||
private List<InetAddress> peerAddresses;
|
|
||||||
|
|
||||||
public PeersMessage(List<InetAddress> peerAddresses) {
|
|
||||||
super(MessageType.PEERS);
|
|
||||||
|
|
||||||
this.peerAddresses = new ArrayList<>(peerAddresses);
|
|
||||||
|
|
||||||
// Legacy PEERS message doesn't support IPv6
|
|
||||||
this.peerAddresses.removeIf(address -> address instanceof Inet6Address);
|
|
||||||
}
|
|
||||||
|
|
||||||
private PeersMessage(int id, List<InetAddress> peerAddresses) {
|
|
||||||
super(id, MessageType.PEERS);
|
|
||||||
|
|
||||||
this.peerAddresses = peerAddresses;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<InetAddress> getPeerAddresses() {
|
|
||||||
return this.peerAddresses;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
|
||||||
int count = bytes.getInt();
|
|
||||||
|
|
||||||
if (bytes.remaining() != count * ADDRESS_LENGTH)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
List<InetAddress> peerAddresses = new ArrayList<>();
|
|
||||||
|
|
||||||
byte[] addressBytes = new byte[ADDRESS_LENGTH];
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (int i = 0; i < count; ++i) {
|
|
||||||
bytes.get(addressBytes);
|
|
||||||
peerAddresses.add(InetAddress.getByAddress(addressBytes));
|
|
||||||
}
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new PeersMessage(id, peerAddresses);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected byte[] toData() {
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
|
||||||
|
|
||||||
bytes.write(Ints.toByteArray(this.peerAddresses.size()));
|
|
||||||
|
|
||||||
for (InetAddress peerAddress : this.peerAddresses)
|
|
||||||
bytes.write(peerAddress.getAddress());
|
|
||||||
|
|
||||||
return bytes.toByteArray();
|
|
||||||
} catch (IOException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
package org.qortal.network.message;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import com.google.common.primitives.Longs;
|
|
||||||
|
|
||||||
public class ProofMessage extends Message {
|
|
||||||
|
|
||||||
private long timestamp;
|
|
||||||
private long salt;
|
|
||||||
private long nonce;
|
|
||||||
|
|
||||||
public ProofMessage(long timestamp, long salt, long nonce) {
|
|
||||||
this(-1, timestamp, salt, nonce);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ProofMessage(int id, long timestamp, long salt, long nonce) {
|
|
||||||
super(id, MessageType.PROOF);
|
|
||||||
|
|
||||||
this.timestamp = timestamp;
|
|
||||||
this.salt = salt;
|
|
||||||
this.nonce = nonce;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTimestamp() {
|
|
||||||
return this.timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getSalt() {
|
|
||||||
return this.salt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getNonce() {
|
|
||||||
return this.nonce;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
|
||||||
long timestamp = bytes.getLong();
|
|
||||||
long salt = bytes.getLong();
|
|
||||||
long nonce = bytes.getLong();
|
|
||||||
|
|
||||||
return new ProofMessage(id, timestamp, salt, nonce);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected byte[] toData() {
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
|
||||||
|
|
||||||
bytes.write(Longs.toByteArray(this.timestamp));
|
|
||||||
bytes.write(Longs.toByteArray(this.salt));
|
|
||||||
bytes.write(Longs.toByteArray(this.nonce));
|
|
||||||
|
|
||||||
return bytes.toByteArray();
|
|
||||||
} catch (IOException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,55 @@
|
|||||||
|
package org.qortal.network.message;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import com.google.common.primitives.Ints;
|
||||||
|
|
||||||
|
public class ResponseMessage extends Message {
|
||||||
|
|
||||||
|
public static final int DATA_LENGTH = 32;
|
||||||
|
|
||||||
|
private final int nonce;
|
||||||
|
private final byte[] data;
|
||||||
|
|
||||||
|
private ResponseMessage(int id, int nonce, byte[] data) {
|
||||||
|
super(id, MessageType.RESPONSE);
|
||||||
|
|
||||||
|
this.nonce = nonce;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponseMessage(int nonce, byte[] data) {
|
||||||
|
this(-1, nonce, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNonce() {
|
||||||
|
return this.nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getData() {
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) {
|
||||||
|
int nonce = byteBuffer.getInt();
|
||||||
|
|
||||||
|
byte[] data = new byte[DATA_LENGTH];
|
||||||
|
byteBuffer.get(data);
|
||||||
|
|
||||||
|
return new ResponseMessage(id, nonce, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected byte[] toData() throws IOException {
|
||||||
|
ByteArrayOutputStream bytes = new ByteArrayOutputStream(4 + DATA_LENGTH);
|
||||||
|
|
||||||
|
bytes.write(Ints.toByteArray(this.nonce));
|
||||||
|
|
||||||
|
bytes.write(data);
|
||||||
|
|
||||||
|
return bytes.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,64 +0,0 @@
|
|||||||
package org.qortal.network.message;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import org.qortal.network.Network;
|
|
||||||
|
|
||||||
public class VerificationCodesMessage extends Message {
|
|
||||||
|
|
||||||
private static final int TOTAL_LENGTH = Network.PEER_ID_LENGTH + Network.PEER_ID_LENGTH;
|
|
||||||
|
|
||||||
private byte[] verificationCodeSent;
|
|
||||||
private byte[] verificationCodeExpected;
|
|
||||||
|
|
||||||
public VerificationCodesMessage(byte[] verificationCodeSent, byte[] verificationCodeExpected) {
|
|
||||||
this(-1, verificationCodeSent, verificationCodeExpected);
|
|
||||||
}
|
|
||||||
|
|
||||||
private VerificationCodesMessage(int id, byte[] verificationCodeSent, byte[] verificationCodeExpected) {
|
|
||||||
super(id, MessageType.VERIFICATION_CODES);
|
|
||||||
|
|
||||||
this.verificationCodeSent = verificationCodeSent;
|
|
||||||
this.verificationCodeExpected = verificationCodeExpected;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getVerificationCodeSent() {
|
|
||||||
return this.verificationCodeSent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getVerificationCodeExpected() {
|
|
||||||
return this.verificationCodeExpected;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
|
||||||
if (bytes.remaining() != TOTAL_LENGTH)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
byte[] verificationCodeSent = new byte[Network.PEER_ID_LENGTH];
|
|
||||||
bytes.get(verificationCodeSent);
|
|
||||||
|
|
||||||
byte[] verificationCodeExpected = new byte[Network.PEER_ID_LENGTH];
|
|
||||||
bytes.get(verificationCodeExpected);
|
|
||||||
|
|
||||||
return new VerificationCodesMessage(id, verificationCodeSent, verificationCodeExpected);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected byte[] toData() {
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
|
||||||
|
|
||||||
bytes.write(this.verificationCodeSent);
|
|
||||||
|
|
||||||
bytes.write(this.verificationCodeExpected);
|
|
||||||
|
|
||||||
return bytes.toByteArray();
|
|
||||||
} catch (IOException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
package org.qortal.network.message;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
import org.qortal.utils.Serialization;
|
|
||||||
|
|
||||||
import com.google.common.primitives.Longs;
|
|
||||||
|
|
||||||
public class VersionMessage extends Message {
|
|
||||||
|
|
||||||
private long buildTimestamp;
|
|
||||||
private String versionString;
|
|
||||||
|
|
||||||
public VersionMessage(long buildTimestamp, String versionString) {
|
|
||||||
this(-1, buildTimestamp, versionString);
|
|
||||||
}
|
|
||||||
|
|
||||||
private VersionMessage(int id, long buildTimestamp, String versionString) {
|
|
||||||
super(id, MessageType.VERSION);
|
|
||||||
|
|
||||||
this.buildTimestamp = buildTimestamp;
|
|
||||||
this.versionString = versionString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getBuildTimestamp() {
|
|
||||||
return this.buildTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getVersionString() {
|
|
||||||
return this.versionString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
|
||||||
long buildTimestamp = bytes.getLong();
|
|
||||||
|
|
||||||
int versionStringLength = bytes.getInt();
|
|
||||||
|
|
||||||
if (versionStringLength != bytes.remaining())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
byte[] versionBytes = new byte[versionStringLength];
|
|
||||||
bytes.get(versionBytes);
|
|
||||||
|
|
||||||
String versionString = new String(versionBytes, StandardCharsets.UTF_8);
|
|
||||||
|
|
||||||
return new VersionMessage(id, buildTimestamp, versionString);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected byte[] toData() {
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
|
||||||
|
|
||||||
bytes.write(Longs.toByteArray(this.buildTimestamp));
|
|
||||||
|
|
||||||
Serialization.serializeSizedString(bytes, this.versionString);
|
|
||||||
|
|
||||||
return bytes.toByteArray();
|
|
||||||
} catch (IOException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user