forked from Qortal/qortal
Proxy private keys are now SHA256(shared secret only) instead of SHA256(shared secret + public keys).
HTML/JS in src/test/resources/proxy-key-example.html updated accordingly. Add handshake status to output of API call GET /peers Add/correct @ApiErrors annotations on some API calls. Add API call POST /admin/orphan (target height as body) to force blockchain orphaning for when node is wildly out of sync. Added support for above to BlockChain class. BlockGenerator now requires a minimum number of peers before it will generate any new blocks. See "minBlockchainPeers" in settings. Controller now requires a minimum number of peers before it will consider synchronizing. See "minBlockchainPeers" in settings. Old "minPeers" entry in settings.json no longer valid! Networking now allows both an outbound and inbound connection to a peer although will use the outbound connection in preference. Networking checks peer ID of inbound connections to detect, and resolve, peer ID clashes/theft.
This commit is contained in:
parent
40d6190265
commit
8f7c954f5a
@ -9,8 +9,6 @@ import org.qora.crypto.BouncyCastle25519;
|
||||
import org.qora.crypto.Crypto;
|
||||
import org.qora.repository.Repository;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
|
||||
public class PrivateKeyAccount extends PublicKeyAccount {
|
||||
|
||||
private static final int SIGNATURE_LENGTH = 64;
|
||||
@ -70,8 +68,7 @@ public class PrivateKeyAccount extends PublicKeyAccount {
|
||||
public byte[] getProxyPrivateKey(byte[] publicKey) {
|
||||
byte[] sharedSecret = this.getSharedSecret(publicKey);
|
||||
|
||||
byte[] proxyHashData = Bytes.concat(sharedSecret, this.getPublicKey(), publicKey);
|
||||
return Crypto.digest(proxyHashData);
|
||||
return Crypto.digest(sharedSecret);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ public enum ApiError {
|
||||
INVALID_REFERENCE(126, 400),
|
||||
TRANSFORMATION_ERROR(127, 400),
|
||||
INVALID_PRIVATE_KEY(128, 400),
|
||||
INVALID_HEIGHT(129, 400),
|
||||
|
||||
// WALLET
|
||||
WALLET_NO_EXISTS(201, 404),
|
||||
|
@ -3,6 +3,7 @@ package org.qora.api.model;
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
|
||||
import org.qora.network.Handshake;
|
||||
import org.qora.network.Peer;
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@ -16,9 +17,10 @@ public class ConnectedPeer {
|
||||
INBOUND,
|
||||
OUTBOUND;
|
||||
}
|
||||
|
||||
public Direction direction;
|
||||
|
||||
public Handshake handshakeStatus;
|
||||
|
||||
protected ConnectedPeer() {
|
||||
}
|
||||
|
||||
@ -27,6 +29,7 @@ public class ConnectedPeer {
|
||||
this.lastPing = peer.getLastPing();
|
||||
this.direction = peer.isOutbound() ? Direction.OUTBOUND : Direction.INBOUND;
|
||||
this.lastHeight = peer.getPeerData() == null ? null : peer.getPeerData().getLastHeight();
|
||||
this.handshakeStatus = peer.getHandshakeStatus();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -36,10 +36,12 @@ import org.qora.account.Forging;
|
||||
import org.qora.account.PrivateKeyAccount;
|
||||
import org.qora.api.ApiError;
|
||||
import org.qora.api.ApiErrors;
|
||||
import org.qora.api.ApiException;
|
||||
import org.qora.api.ApiExceptionFactory;
|
||||
import org.qora.api.Security;
|
||||
import org.qora.api.model.ActivitySummary;
|
||||
import org.qora.api.model.NodeInfo;
|
||||
import org.qora.block.BlockChain;
|
||||
import org.qora.controller.Controller;
|
||||
import org.qora.repository.DataException;
|
||||
import org.qora.repository.Repository;
|
||||
@ -235,7 +237,7 @@ public class AdminResource {
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
||||
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.REPOSITORY_ISSUE})
|
||||
public String addForgingAccount(String seed58) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
byte[] seed = Base58.decode(seed58.trim());
|
||||
@ -279,6 +281,7 @@ public class AdminResource {
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.REPOSITORY_ISSUE})
|
||||
public String deleteForgingAccount(String seed58) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
byte[] seed = Base58.decode(seed58.trim());
|
||||
@ -355,4 +358,47 @@ public class AdminResource {
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/orphan")
|
||||
@Operation(
|
||||
summary = "Discard blocks back to given height.",
|
||||
requestBody = @RequestBody(
|
||||
required = true,
|
||||
content = @Content(
|
||||
mediaType = MediaType.TEXT_PLAIN,
|
||||
schema = @Schema(
|
||||
type = "string", example = "0"
|
||||
)
|
||||
)
|
||||
),
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "\"true\"",
|
||||
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string"))
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_HEIGHT, ApiError.REPOSITORY_ISSUE})
|
||||
public String orphan(String targetHeightString) {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
try {
|
||||
int targetHeight = Integer.parseUnsignedInt(targetHeightString);
|
||||
|
||||
if (targetHeight <= 0 || targetHeight > Controller.getInstance().getChainHeight())
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_HEIGHT);
|
||||
|
||||
if (BlockChain.orphan(targetHeight))
|
||||
return "true";
|
||||
else
|
||||
return "false";
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
} catch (NumberFormatException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_HEIGHT);
|
||||
} catch (ApiException e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ import java.math.MathContext;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
@ -24,6 +26,8 @@ import org.apache.logging.log4j.Logger;
|
||||
import org.eclipse.persistence.exceptions.XMLMarshalException;
|
||||
import org.eclipse.persistence.jaxb.JAXBContextFactory;
|
||||
import org.eclipse.persistence.jaxb.UnmarshallerProperties;
|
||||
import org.qora.controller.Controller;
|
||||
import org.qora.crypto.Crypto;
|
||||
import org.qora.data.block.BlockData;
|
||||
import org.qora.data.network.BlockSummaryData;
|
||||
import org.qora.network.Network;
|
||||
@ -347,4 +351,27 @@ public class BlockChain {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean orphan(int targetHeight) throws DataException {
|
||||
ReentrantLock blockchainLock = Controller.getInstance().getBlockchainLock();
|
||||
if (blockchainLock.tryLock())
|
||||
try {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
for (int height = repository.getBlockRepository().getBlockchainHeight(); height > targetHeight; --height) {
|
||||
LOGGER.info(String.format("Forcably orphaning block %d", height));
|
||||
|
||||
BlockData blockData = repository.getBlockRepository().fromHeight(height);
|
||||
Block block = new Block(repository, blockData);
|
||||
block.orphan();
|
||||
repository.saveChanges();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} finally {
|
||||
blockchainLock.unlock();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import org.qora.controller.Controller;
|
||||
import org.qora.data.account.ForgingAccountData;
|
||||
import org.qora.data.block.BlockData;
|
||||
import org.qora.data.transaction.TransactionData;
|
||||
import org.qora.network.Network;
|
||||
import org.qora.repository.BlockRepository;
|
||||
import org.qora.repository.DataException;
|
||||
import org.qora.repository.Repository;
|
||||
@ -82,6 +83,10 @@ public class BlockGenerator extends Thread {
|
||||
newBlocks.clear();
|
||||
}
|
||||
|
||||
// Don't generate if we don't have enough connected peers as where would the transactions/consensus come from?
|
||||
if (Network.getInstance().getUniqueHandshakedPeers().size() < Settings.getInstance().getMinBlockchainPeers())
|
||||
continue;
|
||||
|
||||
// Do we need to build any potential new blocks?
|
||||
List<ForgingAccountData> forgingAccountsData = repository.getAccountRepository().getForgingAccounts();
|
||||
List<PrivateKeyAccount> forgingAccounts = forgingAccountsData.stream().map(accountData -> new PrivateKeyAccount(repository, accountData.getSeed())).collect(Collectors.toList());
|
||||
|
@ -240,8 +240,8 @@ public class Controller extends Thread {
|
||||
return;
|
||||
|
||||
// If we have enough peers, potentially synchronize
|
||||
List<Peer> peers = Network.getInstance().getHandshakeCompletedPeers();
|
||||
if (peers.size() < Settings.getInstance().getMinPeers())
|
||||
List<Peer> peers = Network.getInstance().getUniqueHandshakedPeers();
|
||||
if (peers.size() < Settings.getInstance().getMinBlockchainPeers())
|
||||
return;
|
||||
|
||||
for(Peer peer : peers)
|
||||
|
@ -105,8 +105,10 @@ public class Synchronizer {
|
||||
signatures.remove(0);
|
||||
|
||||
// If common block is peer's latest block then we simply have a longer chain to peer, so exit now
|
||||
if (commonBlockHeight == peerHeight)
|
||||
if (commonBlockHeight == peerHeight) {
|
||||
LOGGER.info(String.format("We have the same blockchain as peer %s, but longer", peer));
|
||||
return SynchronizationResult.OK;
|
||||
}
|
||||
|
||||
// If common block is too far behind us then we're on massively different forks so give up.
|
||||
int minCommonHeight = ourHeight - MAXIMUM_COMMON_DELTA;
|
||||
|
@ -8,7 +8,9 @@ import org.qora.controller.Controller;
|
||||
import org.qora.network.message.Message;
|
||||
import org.qora.network.message.Message.MessageType;
|
||||
import org.qora.network.message.PeerIdMessage;
|
||||
import org.qora.network.message.PeerVerifyMessage;
|
||||
import org.qora.network.message.ProofMessage;
|
||||
import org.qora.network.message.VerificationCodesMessage;
|
||||
import org.qora.network.message.VersionMessage;
|
||||
|
||||
public enum Handshake {
|
||||
@ -41,14 +43,30 @@ public enum Handshake {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Set peer's ID
|
||||
peer.setPeerId(peerId);
|
||||
// Is this ID already connected inbound or outbound?
|
||||
Peer otherInboundPeer = Network.getInstance().getInboundPeerWithId(peerId);
|
||||
|
||||
// Is this ID already connected? We don't want both inbound and outbound so discard inbound if possible
|
||||
Peer similarPeer = Network.getInstance().getOutboundPeerWithId(peerId);
|
||||
if (similarPeer != null && similarPeer != peer) {
|
||||
LOGGER.trace(String.format("Discarding inbound peer %s with existing ID", peer));
|
||||
return null;
|
||||
// Extra checks on inbound peers with known IDs, to prevent ID stealing
|
||||
if (!peer.isOutbound() && otherInboundPeer != null) {
|
||||
Peer otherOutboundPeer = Network.getInstance().getOutboundHandshakedPeerWithId(peerId);
|
||||
|
||||
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));
|
||||
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 {
|
||||
// Set peer's ID
|
||||
peer.setPeerId(peerId);
|
||||
}
|
||||
|
||||
return VERSION;
|
||||
@ -117,6 +135,40 @@ public enum Handshake {
|
||||
public void action(Peer peer) {
|
||||
// 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();
|
||||
|
||||
// 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);
|
||||
@ -160,4 +212,20 @@ public enum Handshake {
|
||||
}
|
||||
}
|
||||
|
||||
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(); // give up with this peer instead
|
||||
return;
|
||||
}
|
||||
|
||||
// Send PEER_VERIFY to peer
|
||||
Message peerVerifyMessage = new PeerVerifyMessage(peer.getVerificationCodeSent());
|
||||
if (!peer.sendMessage(peerVerifyMessage))
|
||||
peer.disconnect();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,10 +31,12 @@ import org.qora.network.message.GetPeersMessage;
|
||||
import org.qora.network.message.HeightMessage;
|
||||
import org.qora.network.message.Message;
|
||||
import org.qora.network.message.Message.MessageType;
|
||||
import org.qora.network.message.PeerVerifyMessage;
|
||||
import org.qora.network.message.PeersMessage;
|
||||
import org.qora.network.message.PeersV2Message;
|
||||
import org.qora.network.message.PingMessage;
|
||||
import org.qora.network.message.TransactionMessage;
|
||||
import org.qora.network.message.VerificationCodesMessage;
|
||||
import org.qora.repository.DataException;
|
||||
import org.qora.repository.Repository;
|
||||
import org.qora.repository.RepositoryManager;
|
||||
@ -69,7 +71,7 @@ public class Network extends Thread {
|
||||
private List<Peer> connectedPeers;
|
||||
private List<PeerAddress> selfPeers;
|
||||
private ServerSocket listenSocket;
|
||||
private int minPeers;
|
||||
private int minOutboundPeers;
|
||||
private int maxPeers;
|
||||
private ExecutorService peerExecutor;
|
||||
private ExecutorService mergePeersExecutor;
|
||||
@ -106,7 +108,7 @@ public class Network extends Thread {
|
||||
ourPeerId = new byte[PEER_ID_LENGTH];
|
||||
new SecureRandom().nextBytes(ourPeerId);
|
||||
|
||||
minPeers = Settings.getInstance().getMinPeers();
|
||||
minOutboundPeers = Settings.getInstance().getMinOutboundPeers();
|
||||
maxPeers = Settings.getInstance().getMaxPeers();
|
||||
|
||||
peerExecutor = Executors.newCachedThreadPool();
|
||||
@ -269,7 +271,7 @@ public class Network extends Thread {
|
||||
}
|
||||
|
||||
private void createConnection() throws InterruptedException, DataException {
|
||||
if (this.getOutboundHandshakeCompletedPeers().size() >= minPeers)
|
||||
if (this.getOutboundHandshakedPeers().size() >= minOutboundPeers)
|
||||
return;
|
||||
|
||||
Peer newPeer;
|
||||
@ -389,6 +391,21 @@ public class Network extends Thread {
|
||||
// Should be non-handshaking messages from now on
|
||||
|
||||
switch (message.getType()) {
|
||||
case PEER_VERIFY:
|
||||
// Remote peer wants extra verification
|
||||
possibleVerificationResponse(peer);
|
||||
break;
|
||||
|
||||
case VERIFICATION_CODES:
|
||||
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);
|
||||
break;
|
||||
|
||||
case VERSION:
|
||||
case PEER_ID:
|
||||
case PROOF:
|
||||
@ -447,7 +464,30 @@ public class Network extends Thread {
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
return;
|
||||
}
|
||||
|
||||
peer.setVerificationCodes(null, null);
|
||||
peer.setHandshakeStatus(Handshake.COMPLETED);
|
||||
this.onHandshakeCompleted(peer);
|
||||
}
|
||||
|
||||
private void onHandshakeCompleted(Peer peer) {
|
||||
// Do we need extra handshaking because of peer dopplegangers?
|
||||
if (peer.getPendingPeerId() != null) {
|
||||
peer.setHandshakeStatus(Handshake.PEER_VERIFY);
|
||||
peer.getHandshakeStatus().action(peer);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make a note that we've successfully completed handshake (and when)
|
||||
peer.getPeerData().setLastConnected(NTP.getTime());
|
||||
|
||||
@ -551,7 +591,7 @@ public class Network extends Thread {
|
||||
// Network-wide calls
|
||||
|
||||
/** Returns list of connected peers that have completed handshaking. */
|
||||
public List<Peer> getHandshakeCompletedPeers() {
|
||||
public List<Peer> getHandshakedPeers() {
|
||||
List<Peer> peers = new ArrayList<>();
|
||||
|
||||
synchronized (this.connectedPeers) {
|
||||
@ -561,8 +601,31 @@ public class Network extends Thread {
|
||||
return peers;
|
||||
}
|
||||
|
||||
/** Returns list of connected peers that have completed handshaking, with unbound duplicates removed. */
|
||||
public List<Peer> getUniqueHandshakedPeers() {
|
||||
final List<Peer> peers;
|
||||
|
||||
synchronized (this.connectedPeers) {
|
||||
peers = this.connectedPeers.stream().filter(peer -> peer.getHandshakeStatus() == Handshake.COMPLETED).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// Returns true if this [inbound] peer 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. */
|
||||
public List<Peer> getOutboundHandshakeCompletedPeers() {
|
||||
public List<Peer> getOutboundHandshakedPeers() {
|
||||
List<Peer> peers = new ArrayList<>();
|
||||
|
||||
synchronized (this.connectedPeers) {
|
||||
@ -573,10 +636,17 @@ public class Network extends Thread {
|
||||
return peers;
|
||||
}
|
||||
|
||||
/** Returns Peer with outbound connection and passed ID, or null if none found. */
|
||||
public Peer getOutboundPeerWithId(byte[] peerId) {
|
||||
/** Returns Peer with inbound connection and matching ID, or null if none found. */
|
||||
public Peer getInboundPeerWithId(byte[] peerId) {
|
||||
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.isOutbound() && peer.getPeerId() != null && Arrays.equals(peer.getPeerId(), peerId)).findAny().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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -647,7 +717,7 @@ public class Network extends Thread {
|
||||
}
|
||||
|
||||
try {
|
||||
peerExecutor.execute(new Broadcaster(this.getHandshakeCompletedPeers(), peerMessage));
|
||||
peerExecutor.execute(new Broadcaster(this.getUniqueHandshakedPeers(), peerMessage));
|
||||
} catch (RejectedExecutionException e) {
|
||||
// Can't execute - probably because we're shutting down, so ignore
|
||||
}
|
||||
|
@ -56,6 +56,10 @@ public class Peer implements Runnable {
|
||||
private boolean isLocal;
|
||||
private byte[] peerId;
|
||||
|
||||
private byte[] pendingPeerId;
|
||||
private byte[] verificationCodeSent;
|
||||
private byte[] verificationCodeExpected;
|
||||
|
||||
/** Construct unconnected outbound Peer using socket address in peer data */
|
||||
public Peer(PeerData peerData) {
|
||||
this.isOutbound = true;
|
||||
@ -133,6 +137,27 @@ public class Peer implements Runnable {
|
||||
this.peerId = peerId;
|
||||
}
|
||||
|
||||
public byte[] getPendingPeerId() {
|
||||
return this.pendingPeerId;
|
||||
}
|
||||
|
||||
public void setPendingPeerId(byte[] peerId) {
|
||||
this.pendingPeerId = peerId;
|
||||
}
|
||||
|
||||
public byte[] getVerificationCodeSent() {
|
||||
return this.verificationCodeSent;
|
||||
}
|
||||
|
||||
public byte[] getVerificationCodeExpected() {
|
||||
return this.verificationCodeExpected;
|
||||
}
|
||||
|
||||
public void setVerificationCodes(byte[] sent, byte[] expected) {
|
||||
this.verificationCodeSent = sent;
|
||||
this.verificationCodeExpected = expected;
|
||||
}
|
||||
|
||||
// Easier, and nicer output, than peer.getRemoteSocketAddress()
|
||||
|
||||
@Override
|
||||
@ -142,6 +167,14 @@ public class Peer implements Runnable {
|
||||
|
||||
// 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 setup() throws IOException {
|
||||
this.socket.setSoTimeout(INACTIVITY_TIMEOUT);
|
||||
this.out = this.socket.getOutputStream();
|
||||
|
@ -43,7 +43,9 @@ public abstract class Message {
|
||||
PEERS_V2(13),
|
||||
GET_BLOCK_SUMMARIES(14),
|
||||
BLOCK_SUMMARIES(15),
|
||||
GET_SIGNATURES_V2(16);
|
||||
GET_SIGNATURES_V2(16),
|
||||
PEER_VERIFY(17),
|
||||
VERIFICATION_CODES(18);
|
||||
|
||||
public final int value;
|
||||
public final Method fromByteBuffer;
|
||||
|
@ -0,0 +1,51 @@
|
||||
package org.qora.network.message;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.qora.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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package org.qora.network.message;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.qora.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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -2,12 +2,9 @@ package org.qora;
|
||||
import java.security.Security;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.qora.block.Block;
|
||||
import org.qora.block.BlockChain;
|
||||
import org.qora.controller.Controller;
|
||||
import org.qora.data.block.BlockData;
|
||||
import org.qora.repository.DataException;
|
||||
import org.qora.repository.Repository;
|
||||
import org.qora.repository.RepositoryFactory;
|
||||
import org.qora.repository.RepositoryManager;
|
||||
import org.qora.repository.hsqldb.HSQLDBRepositoryFactory;
|
||||
@ -43,15 +40,8 @@ public class orphan {
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
for (int height = repository.getBlockRepository().getBlockchainHeight(); height > targetHeight; --height) {
|
||||
System.out.println("Orphaning block " + height);
|
||||
|
||||
BlockData blockData = repository.getBlockRepository().fromHeight(height);
|
||||
Block block = new Block(repository, blockData);
|
||||
block.orphan();
|
||||
repository.saveChanges();
|
||||
}
|
||||
try {
|
||||
BlockChain.orphan(targetHeight);
|
||||
} catch (DataException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
@ -57,7 +57,11 @@ public class Settings {
|
||||
// Peer-to-peer related
|
||||
private int listenPort = DEFAULT_LISTEN_PORT;
|
||||
private String bindAddress = null; // listen on all local addresses
|
||||
private int minPeers = 3;
|
||||
/** Minimum number of peers to allow block generation / synchronization. */
|
||||
private int minBlockchainPeers = 3;
|
||||
/** Target number of outbound connections to peers we should make. */
|
||||
private int minOutboundPeers = 3;
|
||||
/** Maximum number of peer connections we allow. */
|
||||
private int maxPeers = 10;
|
||||
|
||||
// Which blockchains this node is running
|
||||
@ -223,8 +227,12 @@ public class Settings {
|
||||
return this.bindAddress;
|
||||
}
|
||||
|
||||
public int getMinPeers() {
|
||||
return this.minPeers;
|
||||
public int getMinBlockchainPeers() {
|
||||
return this.minBlockchainPeers;
|
||||
}
|
||||
|
||||
public int getMinOutboundPeers() {
|
||||
return this.minOutboundPeers;
|
||||
}
|
||||
|
||||
public int getMaxPeers() {
|
||||
|
@ -36,6 +36,7 @@ ADDRESS_NO_EXISTS=account address does not exist
|
||||
INVALID_CRITERIA=invalid search criteria
|
||||
INVALID_REFERENCE=invalid reference
|
||||
INVALID_PRIVATE_KEY=invalid private key
|
||||
INVALID_HEIGHT=invalid block height
|
||||
|
||||
# Wallet
|
||||
WALLET_NO_EXISTS=wallet does not exist
|
||||
|
17
src/test/java/org/qora/test/GuiTests.java
Normal file
17
src/test/java/org/qora/test/GuiTests.java
Normal file
@ -0,0 +1,17 @@
|
||||
package org.qora.test;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.qora.gui.SplashFrame;
|
||||
|
||||
public class GuiTests {
|
||||
|
||||
@Test
|
||||
public void testSplashFrame() throws InterruptedException {
|
||||
SplashFrame splashFrame = SplashFrame.getInstance();
|
||||
|
||||
Thread.sleep(2000L);
|
||||
|
||||
splashFrame.dispose();
|
||||
}
|
||||
|
||||
}
|
@ -25,18 +25,8 @@
|
||||
var sharedSecret = nacl.crypto_scalarmult(mintingX25519Prk, recipientAccountX25519Puk);
|
||||
console.log("shared secret (for debugging): " + Base58.encode(sharedSecret));
|
||||
|
||||
// Data to be hashed: shared secret (32 bytes) + minting public key (32 bytes) + recipient public key (32 bytes)
|
||||
// or, in general terms: shared secret (32 bytes) + public key from private key (32 bytes) + other party's public key (32 bytes)
|
||||
var proxyHashData = new Uint8Array(sharedSecret.length + mintingAccountPuk.length + recipientAccountPuk.length);
|
||||
// copy shared secret into array, starting at index 0
|
||||
proxyHashData.set(sharedSecret);
|
||||
// copy minting account public key into array, starting at index 32
|
||||
proxyHashData.set(mintingAccountPuk, sharedSecret.length);
|
||||
// copy recipient account public key into array, starting at index 64 (32 + 32)
|
||||
proxyHashData.set(recipientAccountPuk, sharedSecret.length + mintingAccountPuk.length);
|
||||
|
||||
// Proxy PRIVATE key is SHA256 of data above
|
||||
var proxyPrivateKey = nacl.crypto_hash_sha256(proxyHashData)
|
||||
// Proxy PRIVATE key is SHA256 of shared secret
|
||||
var proxyPrivateKey = nacl.crypto_hash_sha256(sharedSecret)
|
||||
console.log("proxy private key: " + Base58.encode(proxyPrivateKey));
|
||||
|
||||
var proxyKeyPair = nacl.crypto_sign_seed_keypair(proxyPrivateKey);
|
||||
|
Loading…
x
Reference in New Issue
Block a user