package network.thunder.core.communication.processor.implementations.lnpayment; import network.thunder.core.communication.Message; import network.thunder.core.communication.objects.lightning.subobjects.ChannelStatus; import network.thunder.core.communication.objects.lightning.subobjects.PaymentData; import network.thunder.core.communication.objects.messages.MessageExecutor; import network.thunder.core.communication.objects.messages.impl.message.lnpayment.LNPaymentAMessage; import network.thunder.core.communication.objects.messages.impl.message.lnpayment.LNPaymentBMessage; import network.thunder.core.communication.objects.messages.impl.message.lnpayment.LNPaymentCMessage; import network.thunder.core.communication.objects.messages.impl.message.lnpayment.LNPaymentDMessage; import network.thunder.core.communication.objects.messages.interfaces.factories.ContextFactory; import network.thunder.core.communication.objects.messages.interfaces.factories.LNPaymentMessageFactory; import network.thunder.core.communication.objects.messages.interfaces.helper.LNEventHelper; import network.thunder.core.communication.objects.messages.interfaces.helper.LNPaymentHelper; import network.thunder.core.communication.objects.messages.interfaces.message.lnpayment.LNPayment; import network.thunder.core.communication.objects.subobjects.PaymentSecret; import network.thunder.core.communication.processor.exceptions.LNPaymentException; import network.thunder.core.communication.processor.implementations.lnpayment.helper.*; import network.thunder.core.communication.processor.interfaces.lnpayment.LNPaymentLogic; import network.thunder.core.communication.processor.interfaces.lnpayment.LNPaymentProcessor; import network.thunder.core.database.DBHandler; import network.thunder.core.database.objects.Channel; import network.thunder.core.database.objects.PaymentWrapper; import network.thunder.core.mesh.NodeClient; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; import static network.thunder.core.communication.processor.implementations.lnpayment.LNPaymentProcessorImpl.Status.*; import static network.thunder.core.database.objects.PaymentStatus.EMBEDDED; import static network.thunder.core.database.objects.PaymentStatus.REFUNDED; /** * Created by matsjerratsch on 04/01/2016. */ public class LNPaymentProcessorImpl extends LNPaymentProcessor { LNPaymentMessageFactory messageFactory; LNPaymentLogic paymentLogic; DBHandler dbHandler; LNPaymentHelper paymentHelper; LNEventHelper eventHelper; NodeClient node; MessageExecutor messageExecutor; Channel channel; Status status = IDLE; LinkedBlockingDeque<QueueElement> queueList = new LinkedBlockingDeque<>(1000); List<QueueElement> currentQueueElement = new ArrayList<>(); ChannelStatus statusTemp; boolean aborted = false; boolean finished = false; boolean weStartedExchange = false; CountDownLatch countDownLatch = new CountDownLatch(1); long currentTaskStarted; int latestDice = 0; public LNPaymentProcessorImpl (ContextFactory contextFactory, DBHandler dbHandler, NodeClient node) { this.messageFactory = contextFactory.getLNPaymentMessageFactory(); this.paymentLogic = contextFactory.getLNPaymentLogic(); this.dbHandler = dbHandler; this.paymentHelper = contextFactory.getPaymentHelper(); this.eventHelper = contextFactory.getEventHelper(); this.node = node; } private void startQueueListener () { new Thread(() -> { while (true) { try { checkQueue(); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } private void checkQueue () throws InterruptedException { if (status == IDLE) { QueueElement element = queueList.poll(100, TimeUnit.MILLISECONDS); if (element != null) { if (status == IDLE) { currentQueueElement.add(element); while (queueList.size() > 0) { currentQueueElement.add(queueList.poll()); } buildChannelStatus(); if (!checkForUpdates()) { return; } sendMessageA(); restartCountDown(TIMEOUT_NEGOTIATION); if (weStartedExchange && status != IDLE) { //Time is over - we try it again after some random delay? putQueueElementsBackInQueue(); setStatus(IDLE); } } else { queueList.addFirst(element); } } } else { //When we get here and Status is not IDLE, the other party started the exchange.. long timeToFinish = TIMEOUT_NEGOTIATION - (System.currentTimeMillis() - currentTaskStarted); restartCountDown(timeToFinish); if (status != IDLE) { //Other party started the exchange, but we hit the timeout for negotiation. Just abort it on our side.. statusTemp = channel.channelStatus; setStatus(IDLE); } } } private void buildChannelStatus () { ChannelStatus temp = channel.channelStatus.getClone(); System.out.println(node.name + " Old: " + temp); for (QueueElement queueElement : currentQueueElement) { temp = queueElement.produceNewChannelStatus(temp, paymentHelper); } System.out.println(node.name + " New: " + temp); this.statusTemp = temp; } private boolean checkForUpdates () { int totalChanges = statusTemp.newPayments.size() + statusTemp.redeemedPayments.size() + statusTemp.refundedPayments.size(); return totalChanges > 0; } private void putQueueElementsBackInQueue () { for (int i = currentQueueElement.size() - 1; i >= 0; --i) { queueList.addFirst(currentQueueElement.get(i)); } currentQueueElement.clear(); } public void restartCountDown (long sleepTime) throws InterruptedException { System.out.println(node.name + " START TIME " + sleepTime); countDownLatch = new CountDownLatch(1); countDownLatch.await(sleepTime, TimeUnit.MILLISECONDS); System.out.println(node.name + " STOP TIME"); } public void abortCountDown () { countDownLatch.countDown(); } @Override public boolean makePayment (PaymentData paymentData) { QueueElement payment = new QueueElementPayment(paymentData); queueList.add(payment); return true; } @Override public boolean redeemPayment (PaymentSecret paymentSecret) { QueueElementRedeem payment = new QueueElementRedeem(paymentSecret); queueList.add(payment); return true; } @Override public boolean refundPayment (PaymentData paymentData) { QueueElementRefund payment = new QueueElementRefund(paymentData.secret); queueList.add(payment); return true; } public void abortCurrentExchange () { System.out.println(node.name + " ABORT"); abortCountDown(); } private void sendMessageA () { testStatus(IDLE); weStartedExchange = true; LNPaymentAMessage message = paymentLogic.getAMessage(statusTemp); latestDice = message.dice; sendMessage(message); setStatus(SENT_A); } private void sendMessageB () { testStatus(RECEIVED_A); LNPaymentBMessage message = paymentLogic.getBMessage(); sendMessage(message); setStatus(SENT_B); } private void sendMessageC () { if (weStartedExchange) { testStatus(RECEIVED_B); } else { testStatus(RECEIVED_C); } LNPayment message = paymentLogic.getCMessage(); sendMessage(message); setStatus(SENT_C); } private void sendMessageD () { if (weStartedExchange) { testStatus(RECEIVED_C); } else { testStatus(RECEIVED_D); } //TODO sending and constructing message.. LNPayment message = paymentLogic.getDMessage(); sendMessage(message); setStatus(SENT_D); if (!weStartedExchange) { successCurrentTask(); } } private void readMessageA (LNPaymentAMessage message) { if (status == SENT_A) { if (message.dice > latestDice) { System.out.println(node.name + " DICE HIGHER..."); weStartedExchange = false; currentTaskStarted = System.currentTimeMillis(); abortCurrentTask(); } else { System.out.println(node.name + " Ignoring because we had higher dice.."); return; } } else if (status != IDLE) { System.out.println(node.name + " WE ABORT BECAUSE OTHER PARTY SENT US A NEW REQUEST.."); abortCurrentTask(); } weStartedExchange = false; currentTaskStarted = System.currentTimeMillis(); paymentLogic.checkMessageIncoming(message); setStatus(RECEIVED_A); sendMessageB(); } private void readMessageB (LNPaymentBMessage message) { testStatus(SENT_A); paymentLogic.checkMessageIncoming(message); setStatus(RECEIVED_B); sendMessageC(); } private void readMessageC (LNPaymentCMessage message) { if ((weStartedExchange && status != SENT_C) || (!weStartedExchange && status != SENT_B)) { //TODO ERROR System.out.println(node.name + " ERROR READ MESSAGE C"); } else { paymentLogic.checkMessageIncoming(message); setStatus(RECEIVED_C); if (weStartedExchange) { sendMessageD(); } else { sendMessageC(); } } } private void readMessageD (LNPaymentDMessage message) { if ((weStartedExchange && status != SENT_D) || (!weStartedExchange && status != SENT_C)) { //TODO ERROR System.out.println(node.name + " ERROR READ MESSAGE D"); return; } else { paymentLogic.checkMessageIncoming(message); setStatus(RECEIVED_D); } if (weStartedExchange) { successCurrentTask(); } else { sendMessageD(); } } public void testStatus (Status expected) { if (status != expected) { throw new RuntimeException("Expected " + expected + ". Was: " + status); } } private void abortCurrentTask () { aborted = true; putQueueElementsBackInQueue(); //TODO weStartedExchange = false; currentQueueElement.add(new QueueElementUpdate()); System.out.println(node.name + " abortCurrentTask"); abortCountDown(); } private void successCurrentTask () { currentQueueElement.clear(); updatePaymentsDatabase(); evaluateUpdates(); channel = paymentLogic.updateChannel(channel); dbHandler.updateChannel(channel); eventHelper.onPaymentExchangeDone(); finished = true; aborted = false; setStatus(IDLE); System.out.println(node.name + " successCurrentTask"); abortCountDown(); } private void updatePaymentsDatabase () { ChannelStatus status = paymentLogic.getTemporaryChannelStatus(); for (PaymentData payment : status.newPayments) { if (weStartedExchange) { PaymentWrapper wrapper = dbHandler.getPayment(payment.secret); if (wrapper == null) { wrapper = new PaymentWrapper(new byte[0], payment); wrapper.statusReceiver = EMBEDDED; dbHandler.addPayment(wrapper); } else { wrapper.statusReceiver = EMBEDDED; dbHandler.updatePaymentReceiver(wrapper); } } else { PaymentWrapper wrapper = new PaymentWrapper(node.pubKeyClient.getPubKey(), payment); System.out.println(node.name + " addPayment: " + payment); dbHandler.addPayment(wrapper); } } for (PaymentData payment : status.refundedPayments) { PaymentWrapper wrapper = dbHandler.getPayment(payment.secret); if (weStartedExchange) { wrapper.statusReceiver = REFUNDED; dbHandler.updatePaymentReceiver(wrapper); } else { wrapper.statusSender = REFUNDED; dbHandler.updatePaymentSender(wrapper); } } for (PaymentData payment : status.redeemedPayments) { PaymentWrapper wrapper = dbHandler.getPayment(payment.secret); if (weStartedExchange) { wrapper.statusReceiver = REFUNDED; dbHandler.updatePaymentReceiver(wrapper); } else { wrapper.statusSender = REFUNDED; dbHandler.updatePaymentSender(wrapper); } } } private void evaluateUpdates () { statusTemp = paymentLogic.getTemporaryChannelStatus(); System.out.println(node.name + " " + statusTemp); ChannelStatus statusToBeProcessed = statusTemp.getClone(); statusTemp.remainingPayments.addAll(statusTemp.newPayments); statusTemp.newPayments.clear(); statusTemp.refundedPayments.clear(); statusTemp.redeemedPayments.clear(); channel.channelStatus = statusTemp; if (!weStartedExchange) { for (PaymentData newPayment : statusToBeProcessed.newPayments) { paymentHelper.relayPayment(this, newPayment); } for (PaymentData redeemedPayment : statusToBeProcessed.redeemedPayments) { paymentHelper.paymentRedeemed(redeemedPayment.secret); } for (PaymentData refundedPayment : statusToBeProcessed.refundedPayments) { paymentHelper.paymentRefunded(refundedPayment); } } } private void sendMessage (Message message) { messageExecutor.sendMessageUpwards(message); } private void setStatus (Status status) { this.status = status; } @Override public void onInboundMessage (Message message) { try { if (message instanceof LNPayment) { if (message instanceof LNPaymentAMessage) { readMessageA((LNPaymentAMessage) message); } else if (message instanceof LNPaymentBMessage) { readMessageB((LNPaymentBMessage) message); } else if (message instanceof LNPaymentCMessage) { readMessageC((LNPaymentCMessage) message); } else if (message instanceof LNPaymentDMessage) { readMessageD((LNPaymentDMessage) message); } } } catch (LNPaymentException e) { sendMessage(messageFactory.getFailureMessage(e.getMessage())); } } @Override public void onLayerActive (MessageExecutor messageExecutor) { channel = dbHandler.getChannel(node.pubKeyClient.getPubKey()); paymentLogic.initialise(channel); this.messageExecutor = messageExecutor; this.paymentHelper.addProcessor(this); startQueueListener(); } @Override public void onLayerClose () { this.paymentHelper.removeProcessor(this); } @Override public boolean consumesInboundMessage (Object object) { return (object instanceof LNPayment); } @Override public boolean consumesOutboundMessage (Object object) { return false; } public ChannelStatus getStatusTemp () { if (statusTemp == null) { statusTemp = channel.channelStatus; } return statusTemp; } public Channel getChannel () { return channel; } @Override public boolean connectsToNodeId (byte[] nodeId) { return Arrays.equals(nodeId, node.pubKeyClient.getPubKey()); } @Override public byte[] connectsTo () { return node.pubKeyClient.getPubKey(); } public enum Status { IDLE, SENT_A, RECEIVED_A, SENT_B, RECEIVED_B, SENT_C, RECEIVED_C, SENT_D, RECEIVED_D } }