diff --git a/Qora/src/api/PeersResource.java b/Qora/src/api/PeersResource.java index 1b2dbe60..53b025c7 100644 --- a/Qora/src/api/PeersResource.java +++ b/Qora/src/api/PeersResource.java @@ -211,6 +211,9 @@ else if(DBSet.getInstance().getPeerMap().contains(peer.getAddress().getAddress() o.put("lastGray", "never"); } o.put("whitePingCounter", peerInfo.getWhitePingCouner()); + + if (peerInfo.isBlacklisted()) + o.put("isBlacklisted", "true"); } if(o.size() == 0){ @@ -276,4 +279,29 @@ public String clearPeers() return "OK"; } + + @POST + @Path("/blacklist") + public String blacklistPeer(String address) { + APIUtils.askAPICallAllowed("POST peers/blacklist " + address, request); + + // CHECK WALLET UNLOCKED + if (Controller.getInstance().doesWalletExists() && !Controller.getInstance().isWalletUnlocked()) { + throw ApiErrorFactory.getInstance().createError( + ApiErrorFactory.ERROR_WALLET_LOCKED); + } + + Peer peer; + try { + peer = new Peer(InetAddress.getByName(address)); + } catch (UnknownHostException e) { + throw ApiErrorFactory.getInstance().createError( + ApiErrorFactory.ERROR_INVALID_NETWORK_ADDRESS); + } + + PeerManager.getInstance().blacklistPeer(peer); + + return "OK"; + } + } diff --git a/Qora/src/controller/Controller.java b/Qora/src/controller/Controller.java index 2104a56a..de95c2ae 100644 --- a/Qora/src/controller/Controller.java +++ b/Qora/src/controller/Controller.java @@ -91,11 +91,11 @@ public class Controller extends Observable { private static final Logger LOGGER = LogManager.getLogger(Controller.class); - private String version = "0.26.7"; - private String buildTime = "2018-03-09 08:56:00 UTC"; + private String version = "0.26.9"; + private String buildTime = "2018-04-18 16:58:00 UTC"; private long buildTimestamp; - public static final String releaseVersion = "0.26.7"; + public static final String releaseVersion = "0.26.9"; // TODO ENUM would be better here public static final int STATUS_NO_CONNECTIONS = 0; @@ -158,16 +158,17 @@ public long getBuildTimestamp() { if (this.buildTimestamp == 0) { Date date = new Date(); URL resource = getClass().getResource(getClass().getSimpleName() + ".class"); - if (resource != null) { - if (!resource.getProtocol().equals("file")) { - DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); - try { - date = (Date) formatter.parse(this.buildTime); - } catch (ParseException e) { - LOGGER.error(e.getMessage(), e); - } + + if (resource == null || !resource.getProtocol().equals("file")) { + // Use compiled buildTime + DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); + try { + date = (Date) formatter.parse(this.buildTime); + } catch (ParseException e) { + LOGGER.error(e.getMessage(), e); } } + this.buildTimestamp = date.getTime(); } return this.buildTimestamp; @@ -681,7 +682,8 @@ public void onConnect(Peer peer) { peer.sendMessage(MessageFactory.getInstance().createVersionMessage(Controller.getInstance().getVersion(), this.getBuildTimestamp())); } - // SEND HEIGTH MESSAGE + // SEND HEIGHT MESSAGE + LOGGER.trace("Sending our height " + height + " to peer " + peer.getAddress()); peer.sendMessage(MessageFactory.getInstance().createHeightMessage(height)); if (this.status == STATUS_NO_CONNECTIONS) { @@ -753,7 +755,7 @@ public void onError(Peer peer) { this.onDisconnect(peer); } - // SYNCHRONIZED DO NOT PROCESSS MESSAGES SIMULTANEOUSLY + // SYNCHRONIZED DO NOT PROCESS MESSAGES SIMULTANEOUSLY public void onMessage(Message message) { Message response; Block block; @@ -776,6 +778,7 @@ public void onMessage(Message message) { case Message.HEIGHT_TYPE: HeightMessage heightMessage = (HeightMessage) message; + LOGGER.trace("Received height " + heightMessage.getHeight() + " from peer " + heightMessage.getSender().getAddress()); // ADD TO LIST synchronized (this.peerHeight) { @@ -786,10 +789,15 @@ public void onMessage(Message message) { case Message.GET_SIGNATURES_TYPE: + // Don't send if we're synchronizing + if (this.status == STATUS_SYNCHRONIZING) + break; + GetSignaturesMessage getHeadersMessage = (GetSignaturesMessage) message; // ASK SIGNATURES FROM BLOCKCHAIN List headers = this.blockChain.getSignatures(getHeadersMessage.getParent()); + LOGGER.trace("Found " + headers.size() + " block signatures to send to " + message.getSender().getAddress()); // CREATE RESPONSE WITH SAME ID response = MessageFactory.getInstance().createHeadersMessage(headers); @@ -802,6 +810,10 @@ public void onMessage(Message message) { case Message.GET_BLOCK_TYPE: + // Don't send if we're synchronizing + if (this.status == STATUS_SYNCHRONIZING) + break; + GetBlockMessage getBlockMessage = (GetBlockMessage) message; // ASK BLOCK FROM BLOCKCHAIN @@ -836,7 +848,7 @@ public void onMessage(Message message) { // CHECK IF VALID if (isNewBlockValid && this.synchronizer.process(block)) { - LOGGER.info(Lang.getInstance().translate("received new valid block")); + LOGGER.info(Lang.getInstance().translate("received new valid block") + " " + block.getHeight()); // PROCESS // this.synchronizer.process(block); @@ -972,11 +984,16 @@ public void update() { if (peer != null) { // SYNCHRONIZE FROM PEER + LOGGER.info("Synchronizing using peer " + peer.getAddress().getHostAddress() + " with height " + peerHeight.get(peer) + " - ping " + peer.getPing() + "ms"); this.synchronizer.synchronize(peer); } + + Thread.sleep(5 * 1000); } + } catch (InterruptedException e) { + return; } catch (Exception e) { - LOGGER.error(e.getMessage(), e); + LOGGER.debug(e.getMessage()); if (peer != null) { // DISHONEST PEER @@ -1005,7 +1022,8 @@ public void update() { private Peer getMaxHeightPeer() { Peer highestPeer = null; - int bestHeight = this.blockChain.getHeight(); + // needs to be better than our height + int bestHeight = this.blockChain.getHeight() + 1; long bestPing = Long.MAX_VALUE; try { diff --git a/Qora/src/database/PeerMap.java b/Qora/src/database/PeerMap.java index 3c281201..de676722 100644 --- a/Qora/src/database/PeerMap.java +++ b/Qora/src/database/PeerMap.java @@ -28,146 +28,130 @@ import lang.Lang; -public class PeerMap extends DBMap -{ - private static final byte[] BYTE_WHITELISTED = new byte[]{0, 0}; - private static final byte[] BYTE_BLACKLISTED = new byte[]{1, 1}; - private static final byte[] BYTE_NOTFOUND = new byte[]{2, 2}; - +public class PeerMap extends DBMap { + private static final byte[] BYTE_WHITELISTED = new byte[] { 0, 0 }; + private static final byte[] BYTE_BLACKLISTED = new byte[] { 1, 1 }; + private static final byte[] BYTE_NOTFOUND = new byte[] { 2, 2 }; + private static final Logger LOGGER = LogManager.getLogger(PeerMap.class); private Map observableData = new HashMap(); - - public PeerMap(DBSet databaseSet, DB database) - { + + public PeerMap(DBSet databaseSet, DB database) { super(databaseSet, database); } - public PeerMap(PeerMap parent) - { + public PeerMap(PeerMap parent) { super(parent); } - - protected void createIndexes(DB database){} + + protected void createIndexes(DB database) { + } @Override - protected Map getMap(DB database) - { - //OPEN MAP - return database.createTreeMap("peers") - .keySerializer(BTreeKeySerializer.BASIC) - .comparator(UnsignedBytes.lexicographicalComparator()) - .makeOrGet(); + protected Map getMap(DB database) { + // OPEN MAP + return database.createTreeMap("peers").keySerializer(BTreeKeySerializer.BASIC).comparator(UnsignedBytes.lexicographicalComparator()).makeOrGet(); } @Override - protected Map getMemoryMap() - { + protected Map getMemoryMap() { return new TreeMap(UnsignedBytes.lexicographicalComparator()); } @Override - protected byte[] getDefaultValue() - { + protected byte[] getDefaultValue() { return null; } - + @Override - protected Map getObservableData() - { + protected Map getObservableData() { return this.observableData; } - - public List getKnownPeers(int amount) - { - try - { - //GET ITERATOR + + public List getKnownPeers(int amount) { + try { + // GET ITERATOR Iterator iterator = this.getKeys().iterator(); - - //PEERS + + // PEERS List peers = new ArrayList(); - - //ITERATE AS LONG AS: + + // ITERATE AS LONG AS: // 1. we have not reached the amount of peers // 2. we have read all records - while(iterator.hasNext() && peers.size() < amount) - { - //GET ADDRESS + while (iterator.hasNext() && peers.size() < amount) { + // GET ADDRESS byte[] addressBI = iterator.next(); - - //CHECK IF ADDRESS IS WHITELISTED - if(Arrays.equals(Arrays.copyOfRange(this.get(addressBI), 0, 2), BYTE_WHITELISTED)) - { - InetAddress address = InetAddress.getByAddress(addressBI); - - //CHECK IF SOCKET IS NOT LOCALHOST - if(!Settings.getInstance().isLocalAddress(address)) - { - //CREATE PEER - Peer peer = new Peer(address); - - //ADD TO LIST - peers.add(peer); - } - } + + // CHECK IF ADDRESS IS WHITELISTED + if (!Arrays.equals(Arrays.copyOfRange(this.get(addressBI), 0, 2), BYTE_WHITELISTED)) + continue; + + InetAddress address = InetAddress.getByAddress(addressBI); + + // CHECK IF SOCKET IS NOT LOCALHOST + if (Settings.getInstance().isLocalAddress(address)) + continue; + + // CREATE PEER + Peer peer = new Peer(address); + + // ADD TO LIST + peers.add(peer); } - - //RETURN + + // RETURN return peers; - } - catch(Exception e) - { - LOGGER.error(e.getMessage(),e); - + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + return new ArrayList(); } } - - + public class PeerInfo { - static final int TIMESTAMP_LENGTH = 8; - static final int STATUS_LENGTH = 2; - + static final int TIMESTAMP_LENGTH = 8; + static final int STATUS_LENGTH = 2; + private byte[] address; private byte[] status; private long findingTime; private long whiteConnectTime; private long grayConnectTime; private long whitePingCouner; - - public byte[] getAddress(){ + + public byte[] getAddress() { return address; } - - public byte[] getStatus(){ + + public byte[] getStatus() { return status; } - public long getFindingTime(){ + public long getFindingTime() { return findingTime; } - - public long getWhiteConnectTime(){ + + public long getWhiteConnectTime() { return whiteConnectTime; } - - public long getGrayConnectTime(){ + + public long getGrayConnectTime() { return grayConnectTime; } - - public long getWhitePingCouner(){ + + public long getWhitePingCouner() { return whitePingCouner; } - public PeerInfo(byte[] address, byte[] data){ - if(data != null && data.length == 2 + TIMESTAMP_LENGTH * 4) - { + public PeerInfo(byte[] address, byte[] data) { + if (data != null && data.length == 2 + TIMESTAMP_LENGTH * 4) { int position = 0; - + byte[] statusBytes = Arrays.copyOfRange(data, position, position + STATUS_LENGTH); position += STATUS_LENGTH; - + byte[] findTimeBytes = Arrays.copyOfRange(data, position, position + TIMESTAMP_LENGTH); long longFindTime = Longs.fromByteArray(findTimeBytes); position += TIMESTAMP_LENGTH; @@ -175,14 +159,14 @@ public PeerInfo(byte[] address, byte[] data){ byte[] whiteConnectTimeBytes = Arrays.copyOfRange(data, position, position + TIMESTAMP_LENGTH); long longWhiteConnectTime = Longs.fromByteArray(whiteConnectTimeBytes); position += TIMESTAMP_LENGTH; - + byte[] grayConnectTimeBytes = Arrays.copyOfRange(data, position, position + TIMESTAMP_LENGTH); long longGrayConnectTime = Longs.fromByteArray(grayConnectTimeBytes); position += TIMESTAMP_LENGTH; - + byte[] whitePingCounerBytes = Arrays.copyOfRange(data, position, position + TIMESTAMP_LENGTH); long longWhitePingCouner = Longs.fromByteArray(whitePingCounerBytes); - + this.address = address; this.status = statusBytes; this.findingTime = longFindTime; @@ -196,323 +180,289 @@ public PeerInfo(byte[] address, byte[] data){ this.whiteConnectTime = 0; this.grayConnectTime = 0; this.whitePingCouner = 0; - + this.updateFindingTime(); - } else { + } else { this.address = address; this.status = BYTE_WHITELISTED; this.findingTime = 0; this.whiteConnectTime = 0; this.grayConnectTime = 0; this.whitePingCouner = 0; - + this.updateFindingTime(); } - } - - public void addWhitePingCouner(int n){ + } + + public void addWhitePingCouner(int n) { this.whitePingCouner += n; } - - public void updateWhiteConnectTime(){ + + public void updateWhiteConnectTime() { this.whiteConnectTime = NTP.getTime(); } - - public void updateGrayConnectTime(){ + + public void updateGrayConnectTime() { this.grayConnectTime = NTP.getTime(); } - - public void updateFindingTime(){ + + public void updateFindingTime() { this.findingTime = NTP.getTime(); } + + public boolean isBlacklisted() { + return Arrays.equals(this.status, BYTE_BLACKLISTED); + } - public byte[] toBytes(){ + public void setBlacklisted(boolean makeBlacklisted) { + this.status = makeBlacklisted ? BYTE_BLACKLISTED : BYTE_WHITELISTED; + } + public byte[] toBytes() { byte[] findTimeBytes = Longs.toByteArray(this.findingTime); findTimeBytes = Bytes.ensureCapacity(findTimeBytes, TIMESTAMP_LENGTH, 0); - + byte[] whiteConnectTimeBytes = Longs.toByteArray(this.whiteConnectTime); whiteConnectTimeBytes = Bytes.ensureCapacity(whiteConnectTimeBytes, TIMESTAMP_LENGTH, 0); - + byte[] grayConnectTimeBytes = Longs.toByteArray(this.grayConnectTime); grayConnectTimeBytes = Bytes.ensureCapacity(grayConnectTimeBytes, TIMESTAMP_LENGTH, 0); - + byte[] whitePingCounerBytes = Longs.toByteArray(this.whitePingCouner); whitePingCounerBytes = Bytes.ensureCapacity(whitePingCounerBytes, TIMESTAMP_LENGTH, 0); - - return Bytes.concat(BYTE_WHITELISTED, findTimeBytes, whiteConnectTimeBytes, grayConnectTimeBytes, whitePingCounerBytes); + + return Bytes.concat(this.status, findTimeBytes, whiteConnectTimeBytes, grayConnectTimeBytes, whitePingCounerBytes); } + } - public List getBestPeers(int amount, boolean allFromSettings) - { - try - { - //PEERS + public List getBestPeers(int amount, boolean allFromSettings) { + try { + // PEERS List peers = new ArrayList(); List listPeerInfo = new ArrayList(); - - try - { - //GET ITERATOR + + try { + // GET ITERATOR Iterator iterator = this.getKeys().iterator(); - - //ITERATE AS LONG AS: + + // ITERATE AS LONG AS: // 1. we have not reached the amount of peers // 2. we have read all records - while(iterator.hasNext() && peers.size() < amount) - { - //GET ADDRESS + while (iterator.hasNext() && peers.size() < amount) { + // GET ADDRESS byte[] addressBI = iterator.next(); - - //CHECK IF ADDRESS IS WHITELISTED - + + // CHECK IF ADDRESS IS WHITELISTED byte[] data = this.get(addressBI); - - try - { + + try { PeerInfo peerInfo = new PeerInfo(addressBI, data); - - if(Arrays.equals(peerInfo.getStatus(), BYTE_WHITELISTED)) - { + + if (Arrays.equals(peerInfo.getStatus(), BYTE_WHITELISTED)) { listPeerInfo.add(peerInfo); } } catch (Exception e) { - LOGGER.error(e.getMessage(),e); + LOGGER.error(e.getMessage(), e); } } - Collections.sort(listPeerInfo, new ReverseComparator(new PeerInfoComparator())); - + Collections.sort(listPeerInfo, new ReverseComparator(new PeerInfoComparator())); + } catch (Exception e) { - LOGGER.error(e.getMessage(),e); + LOGGER.error(e.getMessage(), e); } - + for (PeerInfo peer : listPeerInfo) { InetAddress address = InetAddress.getByAddress(peer.getAddress()); - //CHECK IF SOCKET IS NOT LOCALHOST - if(!Settings.getInstance().isLocalAddress(address)) - { - if(peers.size() >= amount) - { - if(allFromSettings) + // CHECK IF SOCKET IS NOT LOCALHOST + if (!Settings.getInstance().isLocalAddress(address)) { + if (peers.size() >= amount) { + if (allFromSettings) break; else return peers; } - - //ADD TO LIST + + // ADD TO LIST peers.add(new Peer(address)); - } + } } - - if(allFromSettings) { + + if (allFromSettings) { LOGGER.info(Lang.getInstance().translate("Peers loaded from database : %peers%").replace("%peers%", String.valueOf(peers.size()))); } List knownPeers = Settings.getInstance().getKnownPeers(); - - if(allFromSettings) { + + if (allFromSettings) { LOGGER.info(Lang.getInstance().translate("Peers loaded from settings : %peers%").replace("%peers%", String.valueOf(knownPeers.size()))); } - + for (Peer knownPeer : knownPeers) { - try - { - if(!allFromSettings && peers.size() >= amount) + try { + if (!allFromSettings && peers.size() >= amount) break; - + boolean found = false; + for (Peer peer : peers) { - if(peer.getAddress().equals(knownPeer.getAddress())) - { + if (peer.getAddress().equals(knownPeer.getAddress())) { found = true; break; } } - - if (!found){ - //ADD TO LIST - peers.add(knownPeer); - } + + if (found) + continue; + + // ADD TO LIST + peers.add(knownPeer); } catch (Exception e) { - LOGGER.error(e.getMessage(),e); + LOGGER.error(e.getMessage(), e); } - + } - - //RETURN + + // RETURN return peers; - } - catch(Exception e) - { - LOGGER.error(e.getMessage(),e); - + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + return new ArrayList(); - } + } } public List getAllPeersAddresses(int amount) { - try - { + try { List addresses = new ArrayList(); Iterator iterator = this.getKeys().iterator(); - while(iterator.hasNext() && (amount == -1 || addresses.size() < amount)) - { + + while (iterator.hasNext() && (amount == -1 || addresses.size() < amount)) { byte[] addressBI = iterator.next(); addresses.add(InetAddress.getByAddress(addressBI).getHostAddress()); } + return addresses; - } - catch(Exception e) - { - LOGGER.error(e.getMessage(),e); - + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + return new ArrayList(); } } - - public List getAllPeers(int amount) - { - try - { - //GET ITERATOR + + public List getAllPeers(int amount) { + try { + // GET ITERATOR Iterator iterator = this.getKeys().iterator(); - - //PEERS + + // PEERS List peers = new ArrayList(); - - //ITERATE AS LONG AS: + + // ITERATE AS LONG AS: // 1. we have not reached the amount of peers // 2. we have read all records - while(iterator.hasNext() && peers.size() < amount) - { - //GET ADDRESS + while (iterator.hasNext() && peers.size() < amount) { + // GET ADDRESS byte[] addressBI = iterator.next(); - byte [] data = this.get(addressBI); - + byte[] data = this.get(addressBI); + peers.add(new PeerInfo(addressBI, data)); } - - //SORT - Collections.sort(peers, new ReverseComparator(new PeerInfoComparator())); - - //RETURN + + // SORT + Collections.sort(peers, new ReverseComparator(new PeerInfoComparator())); + + // RETURN return peers; - } - catch(Exception e) - { - LOGGER.error(e.getMessage(),e); - + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + return new ArrayList(); } } - - - public void addPeer(Peer peer) - { - if(this.map == null){ + + public void addPeer(Peer peer) { + if (this.map == null) return; - } - + PeerInfo peerInfo; byte[] address = peer.getAddress().getAddress(); - - if(this.map.containsKey(address)) - { + + if (this.map.containsKey(address)) { byte[] data = this.map.get(address); - + peerInfo = new PeerInfo(address, data); - } - else - { + } else { peerInfo = new PeerInfo(address, null); } - - if(peer.getPingCounter() > 1) - { - if(peer.isWhite()) - { + + if (peer.getPingCounter() > 1) { + if (peer.isWhite()) { peerInfo.addWhitePingCouner(1); peerInfo.updateWhiteConnectTime(); - } - else - { + } else { peerInfo.updateGrayConnectTime(); } } - - //ADD PEER INTO DB + + // ADD PEER INTO DB this.map.put(address, peerInfo.toBytes()); } - - public void blacklistPeer(Peer peer) - { - //TODO DISABLED WHILE UNSTABLE - return; - - /*try - { - //ADD PEER INTO DB - this.peersMap.put(peer.getAddress().getAddress(), BYTE_BLACKLISTED); - - //COMMIT - if(this.databaseSet != null) - { - this.databaseSet.commit(); - } - } - catch(Exception e) - { - } */ - } - - public PeerInfo getInfo(InetAddress address) - { - byte[] addressByte = address.getAddress(); - if(this.map == null){ - return new PeerInfo(addressByte, BYTE_NOTFOUND); - } - - if(this.map.containsKey(addressByte)) - { - byte[] data = this.map.get(addressByte); - - return new PeerInfo(addressByte, data); - } - return new PeerInfo(addressByte, BYTE_NOTFOUND); + public PeerInfo getInfo(InetAddress address) { + byte[] addressBytes = address.getAddress(); + + if (this.map == null) + return new PeerInfo(addressBytes, BYTE_NOTFOUND); + + if (!this.map.containsKey(addressBytes)) + return new PeerInfo(addressBytes, BYTE_NOTFOUND); + + byte[] data = this.map.get(addressBytes); + return new PeerInfo(addressBytes, data); } - - public boolean isBlacklisted(InetAddress address) - { - //CHECK IF PEER IS BLACKLISTED - if(this.contains(address.getAddress())) - { - return Arrays.equals(this.get(address.getAddress()), BYTE_BLACKLISTED); - } - + + public boolean isBlacklisted(InetAddress address) { + PeerInfo peerInfo = getInfo(address); + + if (!peerInfo.isBlacklisted()) + return false; + + // Maybe time to un-blacklist peer? + boolean blacklistingExpired = (NTP.getTime() - peerInfo.getGrayConnectTime() > 24 * 60 * 60 * 1000); + + if (!blacklistingExpired) + return true; + + // Un-blacklist + peerInfo.setBlacklisted(false); + this.map.put(address.getAddress(), peerInfo.toBytes()); + return false; } - - public boolean isBad(InetAddress address) - { + + public void blacklistPeer(Peer peer) { + // Update peer in DB + PeerInfo peerInfo = getInfo(peer.getAddress()); + peerInfo.setBlacklisted(true); + peerInfo.updateGrayConnectTime(); + this.map.put(peer.getAddress().getAddress(), peerInfo.toBytes()); + } + + public boolean isBad(InetAddress address) { byte[] addressByte = address.getAddress(); - //CHECK IF PEER IS BAD - if(this.contains(addressByte)) - { - byte[] data = this.map.get(addressByte); - - PeerInfo peerInfo = new PeerInfo(addressByte, data); - - boolean findMoreWeekAgo = (NTP.getTime() - peerInfo.getFindingTime() > 7*24*60*60*1000); - - boolean neverWhite = peerInfo.getWhitePingCouner() == 0; - - return findMoreWeekAgo && neverWhite; - } - - return false; + // CHECK IF PEER IS BAD + if (!this.contains(addressByte)) + return false; + + byte[] data = this.map.get(addressByte); + PeerInfo peerInfo = new PeerInfo(addressByte, data); + + boolean findMoreWeekAgo = (NTP.getTime() - peerInfo.getFindingTime() > 7 * 24 * 60 * 60 * 1000); + boolean neverWhite = peerInfo.getWhitePingCouner() == 0; + + return findMoreWeekAgo && neverWhite; } } diff --git a/Qora/src/network/ConnectionAcceptor.java b/Qora/src/network/ConnectionAcceptor.java index 59cf6218..7b6268dd 100644 --- a/Qora/src/network/ConnectionAcceptor.java +++ b/Qora/src/network/ConnectionAcceptor.java @@ -1,120 +1,96 @@ package network; +import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import lang.Lang; -import ntp.NTP; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import qora.transaction.Transaction; import settings.Settings; import controller.Controller; -public class ConnectionAcceptor extends Thread{ +public class ConnectionAcceptor extends Thread { private ConnectionCallback callback; - - private static final Logger LOGGER = LogManager - .getLogger(ConnectionAcceptor.class); + + private static final Logger LOGGER = LogManager.getLogger(ConnectionAcceptor.class); private ServerSocket socket; - + private boolean isRun; - - public ConnectionAcceptor(ConnectionCallback callback) - { + + public ConnectionAcceptor(ConnectionCallback callback) { this.callback = callback; } - - public void run() - { + + public void run() { this.isRun = true; - while(isRun) - { - try - { - if(socket == null) - { - //START LISTENING - socket = new ServerSocket(Controller.getInstance().getNetworkPort()); - } - - - //CHECK IF WE HAVE MAX CONNECTIONS CONNECTIONS - if(Settings.getInstance().getMaxConnections() <= callback.getActiveConnections().size()) - { - //IF SOCKET IS OPEN CLOSE IT - if(!socket.isClosed()) - { + + while (isRun) { + try { + // START LISTENING + if (socket == null) + socket = new ServerSocket(Controller.getInstance().getNetworkPort()); + + // CHECK IF WE HAVE MAX CONNECTIONS CONNECTIONS + if (callback.getActiveConnections().size() >= Settings.getInstance().getMaxConnections()) { + // IF SOCKET IS OPEN CLOSE IT + if (!socket.isClosed()) socket.close(); - } - - Thread.sleep(100); - } - else - { - //REOPEN SOCKET - if(socket.isClosed()) - { - socket = new ServerSocket(Controller.getInstance().getNetworkPort()); - } - - //ACCEPT CONNECTION + + Thread.sleep(5 * 1000); + } else { + // REOPEN SOCKET + if (socket.isClosed()) + socket = new ServerSocket(Controller.getInstance().getNetworkPort()); + + // ACCEPT CONNECTION Socket connectionSocket = socket.accept(); - - //CHECK IF SOCKET IS NOT LOCALHOST || WE ARE ALREADY CONNECTED TO THAT SOCKET || BLACKLISTED - if( - /*connectionSocket.getInetAddress().isSiteLocalAddress() - * || connectionSocket.getInetAddress().isAnyLocalAddress() - * || connectionSocket.getInetAddress().isLoopbackAddress() - * */ - ( - (NTP.getTime() < Transaction.getPOWFIX_RELEASE() ) - && - callback.isConnectedTo(connectionSocket.getInetAddress()) - ) - || - PeerManager.getInstance().isBlacklisted(connectionSocket.getInetAddress())) - { - //DO NOT CONNECT TO OURSELF/EXISTING CONNECTION + InetAddress connectionAddress = connectionSocket.getInetAddress(); + + // CHECK IF SOCKET IS NOT LOCALHOST || WE ARE ALREADY CONNECTED TO THAT SOCKET || BLACKLISTED + if (Network.isHostLocalAddress(connectionAddress)) { + // DO NOT CONNECT TO OURSELF/EXISTING CONNECTION + LOGGER.debug("Connection rejected from local " + connectionAddress); connectionSocket.close(); + continue; } - else - { - //CREATE PEER - new Peer(callback, connectionSocket); + + if (PeerManager.getInstance().isBlacklisted(connectionAddress)) { + // DO NOT CONNECT TO BLACKLISTED PEER + LOGGER.debug("Connection rejected from blacklisted " + connectionAddress); + connectionSocket.close(); + continue; } + + // CREATE PEER + LOGGER.debug("Connection accepted from " + connectionAddress); + new Peer(callback, connectionSocket); } - } - catch(SocketException e) - { - if (this.isRun) - { - LOGGER.error(e.getMessage(),e); - LOGGER.warn(Lang.getInstance().translate("Error accepting new connection")); - } - } - catch(Exception e) - { - LOGGER.error(e.getMessage(),e); - LOGGER.warn(Lang.getInstance().translate("Error accepting new connection")); + } catch (SocketException e) { + if (this.isRun) { + LOGGER.error(e.getMessage(), e); + LOGGER.warn(Lang.getInstance().translate("Error accepting new connection")); + } + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + LOGGER.warn(Lang.getInstance().translate("Error accepting new connection")); } } } - - public void halt() - { + + public void halt() { this.isRun = false; if (socket != null && !socket.isClosed()) { - try { - socket.close(); - } catch (Exception e) { - LOGGER.error(e.getMessage(), e); - } + try { + socket.close(); + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + } } } } diff --git a/Qora/src/network/ConnectionCreator.java b/Qora/src/network/ConnectionCreator.java index a35eb6c9..3e279177 100644 --- a/Qora/src/network/ConnectionCreator.java +++ b/Qora/src/network/ConnectionCreator.java @@ -16,143 +16,132 @@ public class ConnectionCreator extends Thread { private ConnectionCallback callback; private boolean isRun; - - private static final Logger LOGGER = LogManager - .getLogger(ConnectionCreator.class); - public ConnectionCreator(ConnectionCallback callback) - { + + private static final Logger LOGGER = LogManager.getLogger(ConnectionCreator.class); + + public ConnectionCreator(ConnectionCallback callback) { this.callback = callback; } - - public void run() - { + + public void run() { this.isRun = true; - while(isRun) - { - try - { + while (isRun) { + try { int maxReceivePeers = Settings.getInstance().getMaxReceivePeers(); - - //CHECK IF WE NEED NEW CONNECTIONS - if(this.isRun && Settings.getInstance().getMinConnections() >= callback.getActiveConnections().size()) - { - //GET LIST OF KNOWN PEERS + + // CHECK IF WE NEED NEW CONNECTIONS + if (this.isRun && Settings.getInstance().getMinConnections() >= callback.getActiveConnections().size()) { + // GET LIST OF KNOWN PEERS List knownPeers = PeerManager.getInstance().getKnownPeers(); - + int knownPeersCounter = 0; - - //ITERATE knownPeers - for(Peer peer: knownPeers) - { - knownPeersCounter ++; - - //CHECK IF WE ALREADY HAVE MAX CONNECTIONS - if(this.isRun && Settings.getInstance().getMaxConnections() > callback.getActiveConnections().size()) - { - //CHECK IF ALREADY CONNECTED TO PEER - if(!callback.isConnectedTo(peer.getAddress())) - { - //CHECK IF SOCKET IS NOT LOCALHOST - if(true) - //if(!peer.getAddress().isSiteLocalAddress() && !peer.getAddress().isLoopbackAddress() && !peer.getAddress().isAnyLocalAddress()) - { - //CONNECT - LOGGER.info( - Lang.getInstance().translate("Connecting to known peer %peer% :: %knownPeersCounter% / %allKnownPeers% :: Connections: %activeConnections%") - .replace("%peer%", peer.getAddress().getHostAddress()) - .replace("%knownPeersCounter%", String.valueOf(knownPeersCounter)) - .replace("%allKnownPeers%", String.valueOf(knownPeers.size())) - .replace("%activeConnections%", String.valueOf(callback.getActiveConnections().size())) - ); - peer.connect(callback); - } - } - } + + // ITERATE knownPeers + for (Peer peer : knownPeers) { + knownPeersCounter++; + + // CHECK IF WE ALREADY HAVE MAX CONNECTIONS + if (this.isRun && callback.getActiveConnections().size() >= Settings.getInstance().getMaxConnections()) + break; + + // CHECK IF THAT PEER IS NOT BLACKLISTED + if (PeerManager.getInstance().isBlacklisted(peer)) + continue; + + // CHECK IF ALREADY CONNECTED TO PEER + if (callback.isConnectedTo(peer.getAddress())) + continue; + + // Check peer's address is not loopback, localhost, one of ours, etc. + if (Network.isHostLocalAddress(peer.getAddress())) + continue; + + // CONNECT + LOGGER.info(Lang.getInstance() + .translate("Connecting to known peer %peer% :: %knownPeersCounter% / %allKnownPeers% :: Connections: %activeConnections%") + .replace("%peer%", peer.getAddress().getHostAddress()) + .replace("%knownPeersCounter%", String.valueOf(knownPeersCounter)) + .replace("%allKnownPeers%", String.valueOf(knownPeers.size())) + .replace("%activeConnections%", String.valueOf(callback.getActiveConnections().size()))); + peer.connect(callback); } } - - //CHECK IF WE STILL NEED NEW CONNECTIONS - if(this.isRun && Settings.getInstance().getMinConnections() >= callback.getActiveConnections().size()) - { - //OLD SCHOOL ITERATE activeConnections - //avoids Exception when adding new elements - for(int i=0; i= callback.getActiveConnections().size()) { + // OLD SCHOOL ITERATE activeConnections + // avoids Exception when adding new elements + for (int i = 0; i < callback.getActiveConnections().size(); i++) { Peer peer = callback.getActiveConnections().get(i); - - //CHECK IF WE ALREADY HAVE MAX CONNECTIONS - if(this.isRun && Settings.getInstance().getMaxConnections() > callback.getActiveConnections().size()) - { - //ASK PEER FOR PEERS - Message getPeersMessage = MessageFactory.getInstance().createGetPeersMessage(); - PeersMessage peersMessage = (PeersMessage) peer.getResponse(getPeersMessage); - if(peersMessage != null) - { - int foreignPeersCounter = 0; - //FOR ALL THE RECEIVED PEERS - - for(Peer newPeer: peersMessage.getPeers()) - { - //CHECK IF WE ALREADY HAVE MAX CONNECTIONS - if(this.isRun && Settings.getInstance().getMaxConnections() > callback.getActiveConnections().size()) - { - if(foreignPeersCounter >= maxReceivePeers) { - break; - } - - foreignPeersCounter ++; - - //CHECK IF THAT PEER IS NOT BLACKLISTED - if(!PeerManager.getInstance().isBlacklisted(newPeer)) - { - //CHECK IF CONNECTED - if(!callback.isConnectedTo(newPeer)) - { - //CHECK IF SOCKET IS NOT LOCALHOST - if(!newPeer.getAddress().isSiteLocalAddress() && !newPeer.getAddress().isLoopbackAddress() && !newPeer.getAddress().isAnyLocalAddress()) - { - if(Settings.getInstance().isTryingConnectToBadPeers() || !newPeer.isBad()) - { - int maxReceivePeersForPrint = (maxReceivePeers > peersMessage.getPeers().size()) ? peersMessage.getPeers().size() : maxReceivePeers; - - LOGGER.info( - Lang.getInstance().translate("Connecting to peer %newpeer% proposed by %peer% :: %foreignPeersCounter% / %maxReceivePeersForPrint% / %allReceivePeers% :: Connections: %activeConnections%") - .replace("%newpeer%", newPeer.getAddress().getHostAddress()) - .replace("%peer%", peer.getAddress().getHostAddress()) - .replace("%foreignPeersCounter%", String.valueOf(foreignPeersCounter)) - .replace("%maxReceivePeersForPrint%", String.valueOf(maxReceivePeersForPrint)) - .replace("%allReceivePeers%", String.valueOf(peersMessage.getPeers().size())) - .replace("%activeConnections%", String.valueOf(callback.getActiveConnections().size())) - ); - //CONNECT - newPeer.connect(callback); - } - } - } - } - } - } - } - + + // CHECK IF WE ALREADY HAVE MAX CONNECTIONS + if (this.isRun && callback.getActiveConnections().size() >= Settings.getInstance().getMaxConnections()) + break; + + // ASK PEER FOR PEERS + Message getPeersMessage = MessageFactory.getInstance().createGetPeersMessage(); + PeersMessage peersMessage = (PeersMessage) peer.getResponse(getPeersMessage); + if (peersMessage == null) + continue; + + int foreignPeersCounter = 0; + // FOR ALL THE RECEIVED PEERS + for (Peer newPeer : peersMessage.getPeers()) { + // CHECK IF WE ALREADY HAVE MAX CONNECTIONS + if (this.isRun && callback.getActiveConnections().size() >= Settings.getInstance().getMaxConnections()) + break; + + // We only process a maximum number of proposed peers + if (foreignPeersCounter >= maxReceivePeers) + break; + + foreignPeersCounter++; + + // CHECK IF THAT PEER IS NOT BLACKLISTED + if (PeerManager.getInstance().isBlacklisted(newPeer)) + continue; + + // CHECK IF CONNECTED + if (callback.isConnectedTo(newPeer)) + continue; + + // Check peer's address is not loopback, localhost, one of ours, etc. + if (Network.isHostLocalAddress(newPeer.getAddress())) + continue; + + // Don't connect to "bad" peers (unless settings say otherwise) + if (!Settings.getInstance().isTryingConnectToBadPeers() && newPeer.isBad()) + continue; + + int maxReceivePeersForPrint = (maxReceivePeers > peersMessage.getPeers().size()) ? peersMessage.getPeers().size() : maxReceivePeers; + + LOGGER.info(Lang.getInstance() + .translate("Connecting to peer %newpeer% proposed by %peer% :: %foreignPeersCounter% / %maxReceivePeersForPrint% / %allReceivePeers% :: Connections: %activeConnections%") + .replace("%newpeer%", newPeer.getAddress().getHostAddress()) + .replace("%peer%", peer.getAddress().getHostAddress()) + .replace("%foreignPeersCounter%", String.valueOf(foreignPeersCounter)) + .replace("%maxReceivePeersForPrint%", String.valueOf(maxReceivePeersForPrint)) + .replace("%allReceivePeers%", String.valueOf(peersMessage.getPeers().size())) + .replace("%activeConnections%", String.valueOf(callback.getActiveConnections().size()))); + // CONNECT + newPeer.connect(callback); } } - } - //SLEEP - Thread.sleep(60 * 1000); - + } + + // SLEEP + Thread.sleep(60 * 1000); + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + + LOGGER.info(Lang.getInstance().translate("Error creating new connection")); } - catch(Exception e) - { - LOGGER.error(e.getMessage(),e); - - LOGGER.info(Lang.getInstance().translate("Error creating new connection")); - } } } - - public void halt() - { + + public void halt() { this.isRun = false; } + } diff --git a/Qora/src/network/Network.java b/Qora/src/network/Network.java index c555548d..f5ff6199 100644 --- a/Qora/src/network/Network.java +++ b/Qora/src/network/Network.java @@ -1,6 +1,7 @@ package network; import java.net.InetAddress; +import java.net.NetworkInterface; import java.net.ServerSocket; import java.util.ArrayList; import java.util.Arrays; @@ -60,7 +61,7 @@ private void start() { @Override public void onConnect(Peer peer) { - LOGGER.info(Lang.getInstance().translate("Connection successful: ") + peer.getAddress()); + LOGGER.debug(Lang.getInstance().translate("Connection successful: ") + peer.getAddress()); // ADD TO CONNECTED PEERS synchronized (this.connectedPeers) { @@ -232,7 +233,7 @@ public void onMessage(Message message) { } public void broadcast(Message message, List exclude) { - LOGGER.info(Lang.getInstance().translate("Broadcasting")); + LOGGER.debug(Lang.getInstance().translate("Broadcasting")); try { synchronized (this.connectedPeers) { @@ -248,7 +249,7 @@ public void broadcast(Message message, List exclude) { // Iterator fast-fail due to change in connectedPeers } - LOGGER.info(Lang.getInstance().translate("Broadcasting end")); + LOGGER.debug(Lang.getInstance().translate("Broadcasting end")); } @Override @@ -283,4 +284,20 @@ public void stop() { } } } + + public static boolean isHostLocalAddress(InetAddress address) { + // easy address checks first + if (address.isSiteLocalAddress() || address.isLoopbackAddress() || address.isAnyLocalAddress()) + return true; + + try { + // Is address bound to one of our interfaces? + NetworkInterface netIf = NetworkInterface.getByInetAddress(address); + return netIf != null; + } catch (Exception e) { + // Couldn't check - play safe and say it's local + return true; + } + } + } diff --git a/Qora/src/network/Peer.java b/Qora/src/network/Peer.java index 7810d02c..c30904ad 100644 --- a/Qora/src/network/Peer.java +++ b/Qora/src/network/Peer.java @@ -293,7 +293,7 @@ public void run() { if (socket == null || socket.isClosed()) { LOGGER.debug(Lang.getInstance().translate("Socket already closed") + " " + address); } else { - LOGGER.info(Lang.getInstance().translate("Socket issue with peer") + " " + address, e); + LOGGER.info(Lang.getInstance().translate("Socket issue with peer") + " " + address + e.getMessage()); } // Disconnect peer @@ -377,6 +377,8 @@ public Message getResponse(Message message) { BlockingQueue blockingQueue = new ArrayBlockingQueue(1); this.messages.put(id, blockingQueue); + // LOGGER.trace("Sending type " + message.getType() + " message " + id + " to peer " + address); + // Try to send message if (!this.sendMessage(message)) { this.messages.remove(id); @@ -387,13 +389,15 @@ public Message getResponse(Message message) { Message response = blockingQueue.poll(Settings.getInstance().getConnectionTimeout(), TimeUnit.MILLISECONDS); this.messages.remove(id); - if (response == null && this.socket.isConnected()) + if (response == null && this.socket.isConnected()) { + // LOGGER.trace("Timed out while waiting for type " + message.getType() + " response " + id + " from peer " + address); LOGGER.info("Timed out while waiting for response from peer " + address); + } return response; } catch (InterruptedException e) { // Our thread was interrupted. Probably in shutdown scenario. - LOGGER.info("Interrupted while waiting for response from peer " + address); + LOGGER.debug("Interrupted while waiting for response from peer " + address); this.messages.remove(id); return null; } @@ -421,13 +425,17 @@ public boolean isBad() { public void close() { LOGGER.debug("Closing socket connection to peer " + address); - // Stop Pinger if applicable - if (this.pinger != null) - this.pinger.stopPing(); + // Ignore any pending messages + this.messages.clear(); + // Stop processing messages if (this.isAlive()) this.interrupt(); + // Stop Pinger if applicable + if (this.pinger != null) + this.pinger.stopPing(); + try { // Close socket if applicable if (socket != null && socket.isConnected()) diff --git a/Qora/src/network/message/BlockMessage.java b/Qora/src/network/message/BlockMessage.java index 35b9edfb..bd83d9a4 100644 --- a/Qora/src/network/message/BlockMessage.java +++ b/Qora/src/network/message/BlockMessage.java @@ -1,6 +1,5 @@ package network.message; - import java.util.Arrays; import qora.block.Block; @@ -8,66 +7,68 @@ import com.google.common.primitives.Bytes; import com.google.common.primitives.Ints; -public class BlockMessage extends Message{ +public class BlockMessage extends Message { private static final int HEIGHT_LENGTH = 4; - + private Block block; private int height; - - public BlockMessage(Block block) - { - super(BLOCK_TYPE); - + + public BlockMessage(Block block) { + super(BLOCK_TYPE); + this.block = block; } - - public Block getBlock() - { + + public BlockMessage(Block block, int height) { + super(BLOCK_TYPE); + + this.block = block; + this.height = height; + } + + public Block getBlock() { return this.block; } - - public int getHeight() - { + + public int getHeight() { return this.height; } - - public static BlockMessage parse(byte[] data) throws Exception - { - //PARSE HEIGHT - byte[] heightBytes = Arrays.copyOfRange(data, 0, HEIGHT_LENGTH); + + public static BlockMessage parse(byte[] data) throws Exception { + // PARSE HEIGHT + byte[] heightBytes = Arrays.copyOfRange(data, 0, HEIGHT_LENGTH); int height = Ints.fromByteArray(heightBytes); - - //PARSE BLOCK + + // PARSE BLOCK Block block = Block.parse(Arrays.copyOfRange(data, HEIGHT_LENGTH, data.length + 1)); - //CREATE MESSAGE + // CREATE MESSAGE BlockMessage message = new BlockMessage(block); message.height = height; + return message; } - - public byte[] toBytes() - { + + public byte[] toBytes() { byte[] data = new byte[0]; - - //WRITE BLOCK HEIGHT + + // WRITE BLOCK HEIGHT byte[] heightBytes = Ints.toByteArray(this.block.getHeight()); data = Bytes.concat(data, heightBytes); - - //WRITE BLOCK + + // WRITE BLOCK byte[] blockBytes = this.block.toBytes(); data = Bytes.concat(data, blockBytes); - - //ADD CHECKSUM + + // ADD CHECKSUM data = Bytes.concat(super.toBytes(), this.generateChecksum(data), data); - + return data; - } - - protected int getDataLength() - { + } + + protected int getDataLength() { return HEIGHT_LENGTH + this.block.getDataLength(); } - + } diff --git a/Qora/src/network/message/VersionMessage.java b/Qora/src/network/message/VersionMessage.java index d944c003..17572394 100644 --- a/Qora/src/network/message/VersionMessage.java +++ b/Qora/src/network/message/VersionMessage.java @@ -38,7 +38,7 @@ public static VersionMessage parse(byte[] data) throws Exception int position = 0; //READ LENGTH byte[] buildDateTimeBytes = Arrays.copyOfRange(data, position, position + TIMESTAMP_LENGTH); - long buildDateTime = Ints.fromByteArray(buildDateTimeBytes); + long buildDateTime = Longs.fromByteArray(buildDateTimeBytes); position += TIMESTAMP_LENGTH; diff --git a/Qora/src/qora/Synchronizer.java b/Qora/src/qora/Synchronizer.java index 7bebdfec..cc000daa 100644 --- a/Qora/src/qora/Synchronizer.java +++ b/Qora/src/qora/Synchronizer.java @@ -56,7 +56,7 @@ private Block findLastCommonBlock(Peer peer) throws Exception { // We didn't even manage to get any response from peer! if (headers == null) - return null; + throw new Exception("No block signatures from peer"); // NB: empty headers means peer is unaware of the block signature we sent while (headers.size() == 0 && block.getHeight() > 1) { @@ -92,11 +92,11 @@ private Block findLastCommonBlock(Peer peer) throws Exception { * signature * @param {Peer} * peer - * @return {Block} Block from peer. + * @return {BlockMessage} Block and height from peer. * @throws {Exception} * Thrown if peer doesn't respond OR send invalid block (based on received signature). */ - private Block getBlock(byte[] signature, Peer peer) throws Exception { + private BlockMessage getBlock(byte[] signature, Peer peer) throws Exception { // Create message Message message = MessageFactory.getInstance().createGetBlockMessage(signature); @@ -114,7 +114,7 @@ private Block getBlock(byte[] signature, Peer peer) throws Exception { throw new Exception("Invalid block"); // Block has valid signature - return it - return block; + return response; } /** @@ -124,19 +124,23 @@ private Block getBlock(byte[] signature, Peer peer) throws Exception { * signatures * @param {Peer} * peer - * @return {List} Blocks from peer. + * @return {List} Blocks and heights from peer. * @throws {Exception} * Thrown if peer doesn't respond. */ - private List getBlocks(List signatures, Peer peer) throws Exception { - List blocks = new ArrayList(); + private List getBlocks(List signatures, Peer peer) throws Exception { + List blockMessages = new ArrayList(); for (byte[] signature : signatures) { // Request block and add to list - blocks.add(this.getBlock(signature, peer)); + BlockMessage blockMessage = this.getBlock(signature, peer); + if (blockMessage == null) + break; + + blockMessages.add(blockMessage); } - return blocks; + return blockMessages; } /** @@ -160,12 +164,14 @@ private List getBlocks(List signatures, Peer peer) throws Excepti private List getBlockSignatures(Block start, int minimumAmount, Peer peer) throws Exception { // NB: "headers" refers to block signatures + LOGGER.trace("Requesting " + minimumAmount + " block signatures after height " + start.getHeight()); + // Request chunk of next block signatures after "start" block from peer List headers = this.getBlockSignatures(start.getSignature(), peer); // We didn't even manage to get any response from peer! if (headers == null) - return null; + throw new Exception("No block signatures from peer"); // No new block signatures? Give up now if (headers.size() == 0) @@ -177,6 +183,9 @@ private List getBlockSignatures(Block start, int minimumAmount, Peer pee byte[] lastSignature = headers.get(headers.size() - 1); List nextHeaders = this.getBlockSignatures(lastSignature, peer); + if (nextHeaders == null) + throw new Exception("No next block signatures from peer"); + // There aren't any more - return what we have if (nextHeaders.size() == 0) break; @@ -209,10 +218,8 @@ private List getBlockSignatures(byte[] header, Peer peer) throws Excepti // Send message to peer and await response SignaturesMessage response = (SignaturesMessage) peer.getResponse(message); - if (response == null) { - LOGGER.info("Peer didn't respond with block signatures"); - return null; - } + if (response == null) + throw new Exception("Peer didn't respond with block signatures"); return response.getSignatures(); } @@ -342,25 +349,28 @@ public void stop() { * @throws {Exception} * newBlocks from peer must validate. */ - public List synchronize(DBSet db, Block lastCommonBlock, List newBlocks) throws Exception { - List orphanedTransactions = new ArrayList(); - + public List synchronize(DBSet db, Block lastCommonBlock, List newBlockMessages) throws Exception { // Test-verify new blocks, starting from common block, before applying new blocks. - DBSet fork = db.fork(); // Switch AT platform to fork AT_API_Platform_Impl.getInstance().setDBSet(fork); // Use forked DB to orphan blocks back to common block. // Note that a few extra blocks past common block might be orphaned due to how ATs work. - // We keep these extras for validation/reapplying. They are prepended to newBlocks. - this.orphanBackToCommonBlock(fork, lastCommonBlock, newBlocks, null); + // We keep these extras for validation/reapplying. + List orphanedBlocks = new ArrayList(); + this.orphanBackToCommonBlock(fork, lastCommonBlock, orphanedBlocks, null); - // Validate new blocks - for (Block block : newBlocks) { + LOGGER.debug("Orphaned back to block " + fork.getBlockMap().getLastBlock().getHeight(fork)); + + // Revalidate orphaned blocks + for (Block block : orphanedBlocks) { // Early bail-out if shutting down - if (!this.run) - return orphanedTransactions; + if (!this.run) { + // Switch AT platform back to main DB + AT_API_Platform_Impl.getInstance().setDBSet(db); + return null; + } // Check block is valid if (!block.isValid(fork)) { @@ -375,16 +385,55 @@ public List synchronize(DBSet db, Block lastCommonBlock, List orphanedTransactions = new ArrayList(); + orphanedBlocks.clear(); + this.orphanBackToCommonBlock(db, lastCommonBlock, orphanedBlocks, orphanedTransactions); - // Apply new blocks - for (Block block : newBlocks) { + // Reapply orphaned blocks + for (Block block : orphanedBlocks) { // Early bail-out if shutting down if (!this.run) return orphanedTransactions; @@ -392,6 +441,15 @@ public List synchronize(DBSet db, Block lastCommonBlock, List synchronize(DBSet db, Block lastCommonBlock, List signatures = this.getBlockSignatures(lastCommonBlock, BlockChain.MAX_SIGNATURES, peer); @@ -435,26 +491,27 @@ public void synchronize(Peer peer) throws Exception { if (signatures == null) return; + if (signatures.size() == 0) + throw new Exception("Received no block signatures from peer"); + // Create block buffer to request blocks from peer BlockBuffer blockBuffer = new BlockBuffer(signatures, peer); + int expectedBlockHeight = lastBlock.getHeight() + 1; + // Process block-by-block as they arrive into block buffer for (byte[] signature : signatures) { // Wait for block to arrive from peer into block buffer - Block block; - - try { - block = blockBuffer.getBlock(signature); - } catch (Exception e) { - // We failed to receive a block or a received block had an invalid signature - LOGGER.info("Peer didn't send block or sent a block with invalid signature"); - break; - } + Block block = blockBuffer.getBlock(signature); - if (block == null) { - LOGGER.info("Timed out receiving block from peer"); - break; - } + if (block == null) + throw new Exception("Timed out receiving block from peer"); + + // Check received block height matches our expectations + // A height of -1 means it's a new block (which is okay) + int height = block.getHeight(); + if (height != -1 && height != expectedBlockHeight) + throw new Exception("Peer sent out-of-order block " + block.getHeight() + ", we expected block " + expectedBlockHeight); // Process block from peer if (!this.process(block)) { @@ -465,12 +522,15 @@ public void synchronize(Peer peer) throws Exception { // Peer sent us an invalid block throw new Exception("Peer sent invalid block"); } + + expectedBlockHeight++; } // Block buffer no longer needed: we've finished processing or we're shutting down blockBuffer.stopThread(); } else { - LOGGER.info("Synchronizing: " + peer.getAddress().getHostAddress() + " last common block height: " + lastCommonBlock.getHeight()); + LOGGER.info("Synchronizing using peer " + peer.getAddress().getHostAddress() + " from last common block height " + lastCommonBlock.getHeight() + + ", our height was " + lastBlock.getHeight()); // Request signatures from peer covering from last common block height to our blockchain tip height int amount = lastBlock.getHeight() - lastCommonBlock.getHeight(); @@ -480,11 +540,19 @@ public void synchronize(Peer peer) throws Exception { List signatures = this.getBlockSignatures(lastCommonBlock, amount, peer); + if (signatures == null) + return; + + if (signatures.size() == 0) + throw new Exception("Received no block signatures from peer"); + // Request all the blocks using received signatures. - List blocks = this.getBlocks(signatures, peer); + List blockMessages = this.getBlocks(signatures, peer); // Synchronize our blockchain using received blocks starting from lastCommonBlock - List orphanedTransactions = this.synchronize(DBSet.getInstance(), lastCommonBlock, blocks); + List orphanedTransactions = this.synchronize(DBSet.getInstance(), lastCommonBlock, blockMessages); + if (orphanedTransactions == null) + return; // Notify peer of any orphaned transactions in case we had some they don't know about for (Transaction transaction : orphanedTransactions) { diff --git a/Qora/src/qora/block/Block.java b/Qora/src/qora/block/Block.java index ed58716f..953934d5 100644 --- a/Qora/src/qora/block/Block.java +++ b/Qora/src/qora/block/Block.java @@ -780,7 +780,8 @@ public void process(DBSet db) //UPDATE LAST BLOCK db.getBlockMap().setLastBlock(this); - LOGGER.info("Processed block " + height); + if (db == DBSet.getInstance()) + LOGGER.debug("Processed block " + height); } public void orphan() diff --git a/Qora/src/test/SynchronizerTests.java b/Qora/src/test/SynchronizerTests.java index aae32131..ea31be73 100644 --- a/Qora/src/test/SynchronizerTests.java +++ b/Qora/src/test/SynchronizerTests.java @@ -11,6 +11,7 @@ import ntp.NTP; import database.DBSet; +import network.message.BlockMessage; import qora.BlockGenerator; import qora.Synchronizer; import qora.account.PrivateKeyAccount; @@ -79,7 +80,7 @@ public void synchronizeNoCommonBlock() { DBSet fork = databaseSet.fork(); // Generate next 5 blocks (on fork) - List newBlocks = new ArrayList(); + List newBlockMessages = new ArrayList(); for (int i = 0; i < 5; ++i) { // Generate next block Block newBlock = blockGenerator.generateNextBlock(fork, accountB, lastBlock); @@ -96,7 +97,7 @@ public void synchronizeNoCommonBlock() { newBlock.process(fork); // Add to list of new blocks - newBlocks.add(newBlock); + newBlockMessages.add(new BlockMessage(newBlock, newBlock.getHeight(fork))); // Last block is the new block lastBlock = newBlock; @@ -112,15 +113,15 @@ public void synchronizeNoCommonBlock() { final Block lastCommonBlock = null; // first newBlock's block's reference should be last firstBlock's block's signature - assertTrue("first newBlock's reference should be last firstBlock's block signature", Arrays.equals(newBlocks.get(0).getReference(), firstBlocks.get(4).getSignature())); + assertTrue("first newBlock's reference should be last firstBlock's block signature", Arrays.equals(newBlockMessages.get(0).getBlock().getReference(), firstBlocks.get(4).getSignature())); - synchronizer.synchronize(databaseSet, lastCommonBlock, newBlocks); + synchronizer.synchronize(databaseSet, lastCommonBlock, newBlockMessages); // Check last 5 blocks are the ones from newBlocks simply appended to blockchain lastBlock = databaseSet.getBlockMap().getLastBlock(); for (int i = 4; i >= 0; --i) { // Check last block is the correct block from newBlocks - assertTrue("lastBlock's signature should be newBlocks[" + i + "]'s signature", Arrays.equals(newBlocks.get(i).getSignature(), lastBlock.getSignature())); + assertTrue("lastBlock's signature should be newBlockMessages[" + i + "]'s block's signature", Arrays.equals(newBlockMessages.get(i).getBlock().getSignature(), lastBlock.getSignature())); lastBlock = lastBlock.getParent(databaseSet); } @@ -198,7 +199,7 @@ public void synchronizeCommonBlock() { // GENERATE FIRST 10 BLOCKS ON CHAIN 2 USING ACCOUNT B lastBlock = genesisBlock; - List newBlocks = new ArrayList(); + List newBlockMessages = new ArrayList(); for (int i = 0; i < 10; ++i) { // Generate next block Block newBlock = blockGenerator.generateNextBlock(databaseSet2, accountB, lastBlock); @@ -215,7 +216,7 @@ public void synchronizeCommonBlock() { newBlock.process(databaseSet2); // Add to list of new blocks - newBlocks.add(newBlock); + newBlockMessages.add(new BlockMessage(newBlock, newBlock.getHeight(databaseSet2))); // Last block is the new block lastBlock = newBlock; @@ -229,13 +230,13 @@ public void synchronizeCommonBlock() { // NB: Just to be explicit about last common block being genesis block final Block lastCommonBlock = genesisBlock; - synchronizer.synchronize(databaseSet1, lastCommonBlock, newBlocks); + synchronizer.synchronize(databaseSet1, lastCommonBlock, newBlockMessages); // Check last 10 blocks on chain 1 are from chain 2 attached after genesis block lastBlock = databaseSet1.getBlockMap().getLastBlock(); for (int i = 9; i >= 0; --i) { // Check last block is the correct block from newBlocks - assertTrue("lastBlock's signature should be newBlocks[" + i + "]'s signature", Arrays.equals(newBlocks.get(i).getSignature(), lastBlock.getSignature())); + assertTrue("lastBlock's signature should be newBlocks[" + i + "]'s block's signature", Arrays.equals(newBlockMessages.get(i).getBlock().getSignature(), lastBlock.getSignature())); lastBlock = lastBlock.getParent(databaseSet1); }