package network.thunder.core.communication.nio; import network.thunder.core.communication.objects.messages.impl.LNEventHelperImpl; import network.thunder.core.communication.objects.messages.impl.factories.ContextFactoryImpl; import network.thunder.core.communication.objects.messages.impl.message.gossip.objects.PubkeyIPObject; import network.thunder.core.communication.objects.messages.impl.results.*; import network.thunder.core.communication.objects.messages.interfaces.factories.ContextFactory; import network.thunder.core.communication.objects.messages.interfaces.helper.LNEventHelper; import network.thunder.core.communication.objects.messages.interfaces.helper.etc.ResultCommand; import network.thunder.core.communication.processor.ChannelIntent; import network.thunder.core.communication.processor.implementations.sync.SynchronizationHelper; import network.thunder.core.database.DBHandler; import network.thunder.core.etc.Tools; import network.thunder.core.mesh.NodeClient; import network.thunder.core.mesh.NodeServer; import org.bitcoinj.core.ECKey; import org.bitcoinj.core.Wallet; import java.util.ArrayList; import java.util.List; import static network.thunder.core.communication.processor.ChannelIntent.*; /** * Created by matsjerratsch on 22/01/2016. */ public class ConnectionManagerImpl implements ConnectionManager { public final static int NODES_TO_SYNC = 5; public final static int CHANNELS_TO_OPEN = 5; public final static int MINIMUM_AMOUNT_OF_IPS = 10; NodeServer node; ContextFactory contextFactory; DBHandler dbHandler; LNEventHelper eventHelper; P2PServer server; public ConnectionManagerImpl (NodeServer node, Wallet wallet, DBHandler dbHandler) { this.dbHandler = dbHandler; this.node = node; eventHelper = new LNEventHelperImpl(); contextFactory = new ContextFactoryImpl(node, dbHandler, wallet, eventHelper); } public ConnectionManagerImpl (NodeServer node, ContextFactory contextFactory, DBHandler dbHandler, LNEventHelper eventHelper) { this.node = node; this.contextFactory = contextFactory; this.dbHandler = dbHandler; this.eventHelper = eventHelper; } @Override public void startUp (ResultCommand callback) { new Thread(new Runnable() { @Override public void run () { try { startUpBlocking(callback); } catch (Exception e) { e.printStackTrace(); } } }).start(); } public void startUpBlocking (ResultCommand callback) throws Exception { startListening(callback); connectOpenChannels(); fetchNetworkIPs(callback); startBuildingRandomChannel(callback); } public void startListening (ResultCommand callback) { System.out.println("startListening " + this.node.portServer); server = new P2PServer(contextFactory); server.startServer(this.node.portServer); callback.execute(new SuccessResult()); } private void connectOpenChannels () { //TODO } @Override public void fetchNetworkIPs (ResultCommand callback) { new Thread(() -> { fetchNetworkIPsBlocking(callback); }).start(); } private void fetchNetworkIPsBlocking (ResultCommand callback) { List<PubkeyIPObject> ipList = dbHandler.getIPObjects(); List<PubkeyIPObject> alreadyFetched = new ArrayList<>(); List<PubkeyIPObject> seedNodes = SeedNodes.getSeedNodes(); ipList.addAll(seedNodes); while (ipList.size() < MINIMUM_AMOUNT_OF_IPS) { try { ipList = PubkeyIPObject.removeFromListByPubkey(ipList, alreadyFetched); ipList = PubkeyIPObject.removeFromListByPubkey(ipList, node.pubKeyServer.getPubKey()); if (ipList.size() == 0) { break; } PubkeyIPObject randomNode = Tools.getRandomItemFromList(ipList); NodeClient client = ipObjectToNode(randomNode, GET_IPS); client.resultCallback = new NullResultCommand(); connectBlocking(client); alreadyFetched.add(randomNode); ipList = dbHandler.getIPObjects(); } catch (Exception e) { e.printStackTrace(); } } ipList = dbHandler.getIPObjects(); if (ipList.size() > 0) { callback.execute(new SuccessResult()); } else { callback.execute(new FailureResult()); } } @Override public void startBuildingRandomChannel (ResultCommand callback) { try { List<PubkeyIPObject> ipList = dbHandler.getIPObjects(); List<PubkeyIPObject> alreadyConnected = dbHandler.getIPObjectsWithActiveChannel(); List<PubkeyIPObject> alreadyTried = new ArrayList<>(); while (alreadyConnected.size() < CHANNELS_TO_OPEN) { //TODO Here we want some algorithm to determine who we want to connect to initially.. ipList = dbHandler.getIPObjects(); ipList = PubkeyIPObject.removeFromListByPubkey(ipList, node.pubKeyServer.getPubKey()); ipList = PubkeyIPObject.removeFromListByPubkey(ipList, alreadyConnected); ipList = PubkeyIPObject.removeFromListByPubkey(ipList, alreadyTried); if (ipList.size() == 0) { Thread.sleep(5000); continue; } PubkeyIPObject randomNode = Tools.getRandomItemFromList(ipList); NodeClient node = ipObjectToNode(randomNode, OPEN_CHANNEL); buildChannel(node.pubKeyClient.getPubKey(), callback); alreadyTried.add(randomNode); alreadyConnected = dbHandler.getIPObjectsWithActiveChannel(); Thread.sleep(5000); } } catch (Exception e) { throw new RuntimeException(e); } } //TODO be able to tear down a channel completely again //TODO reset sleepIntervall if @Override public void buildChannel (byte[] nodeKey, ResultCommand callback) { new Thread(() -> { final long[] sleepIntervall = {1000}; final boolean[] reconnectAutomatically = {true}; while (reconnectAutomatically[0]) { PubkeyIPObject ipObject = dbHandler.getIPObject(nodeKey); if (ipObject != null) { NodeClient node1 = ipObjectToNode(ipObject, OPEN_CHANNEL); node1.resultCallback = result -> { if (result.shouldTryToReconnect()) { reconnectAutomatically[0] = true; } if (result.wasSuccessful()) { sleepIntervall[0] = 1000; } callback.execute(result); }; connectBlocking(node1); } sleepIntervall[0] *= 1.2; try { sleepIntervall[0] = Math.min(sleepIntervall[0], 5 * 60 * 1000); Thread.sleep(sleepIntervall[0]); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } @Override public void startSyncing (ResultCommand callback) { new Thread(() -> { startSyncingBlocking(callback); }).start(); } private void startSyncingBlocking (ResultCommand callback) { SynchronizationHelper synchronizationHelper = contextFactory.getSyncHelper(); List<PubkeyIPObject> ipList = dbHandler.getIPObjects(); List<PubkeyIPObject> alreadyFetched = new ArrayList<>(); List<PubkeyIPObject> seedNodes = SeedNodes.getSeedNodes(); ipList.addAll(seedNodes); while (!synchronizationHelper.fullySynchronized()) { try { ipList = PubkeyIPObject.removeFromListByPubkey(ipList, alreadyFetched); ipList = PubkeyIPObject.removeFromListByPubkey(ipList, node.pubKeyServer.getPubKey()); if (ipList.size() == 0) { callback.execute(new NoSyncResult()); return; } PubkeyIPObject randomNode = Tools.getRandomItemFromList(ipList); NodeClient client = ipObjectToNode(randomNode, GET_SYNC_DATA); connectBlocking(client); alreadyFetched.add(randomNode); ipList = dbHandler.getIPObjects(); } catch (Exception e) { e.printStackTrace(); } } callback.execute(new SyncSuccessResult()); } private void connect (NodeClient node) { P2PClient client = new P2PClient(contextFactory); client.connectTo(node); } private void connectBlocking (NodeClient node) { try { P2PClient client = new P2PClient(contextFactory); client.connectBlocking(node); } catch (Exception e) { e.printStackTrace(); } } private NodeClient ipObjectToNode (PubkeyIPObject ipObject, ChannelIntent intent) { NodeClient node = new NodeClient(); node.isServer = false; node.intent = intent; node.pubKeyClient = ECKey.fromPublicOnly(ipObject.pubkey); node.host = ipObject.IP; node.port = ipObject.port; return node; } }