package network.thunder.core.communication.nio.handler.mid;
import io.netty.channel.embedded.EmbeddedChannel;
import network.thunder.core.communication.Message;
import network.thunder.core.communication.nio.handler.ProcessorHandler;
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.impl.message.lnpayment.OnionObject;
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.LNOnionHelper;
import network.thunder.core.communication.objects.subobjects.PaymentSecret;
import network.thunder.core.communication.processor.implementations.lnpayment.LNPaymentProcessorImpl;
import network.thunder.core.communication.processor.interfaces.lnpayment.LNPaymentLogic;
import network.thunder.core.database.DBHandler;
import network.thunder.core.database.objects.PaymentWrapper;
import network.thunder.core.etc.*;
import network.thunder.core.mesh.LNConfiguration;
import network.thunder.core.mesh.NodeClient;
import network.thunder.core.mesh.NodeServer;
import org.bitcoinj.core.ECKey;
import org.junit.Before;
import org.junit.Test;
import java.beans.PropertyVetoException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import static junit.framework.TestCase.assertEquals;
import static org.junit.Assert.assertNull;
/**
* Created by matsjerratsch on 02/11/2015.
*/
public class LNPaymentRoutingTest {
EmbeddedChannel channel12;
EmbeddedChannel channel21;
EmbeddedChannel channel23;
EmbeddedChannel channel32;
NodeServer node1 = new NodeServer();
NodeServer node2 = new NodeServer();
NodeServer node3 = new NodeServer();
NodeClient node12 = new NodeClient(node1);
NodeClient node21 = new NodeClient(node2);
NodeClient node23 = new NodeClient(node2);
NodeClient node32 = new NodeClient(node3);
LNPaymentDBHandlerMock dbHandler1 = new LNPaymentDBHandlerMock();
LNPaymentDBHandlerMock dbHandler2 = new LNPaymentDBHandlerMock();
LNPaymentDBHandlerMock dbHandler3 = new LNPaymentDBHandlerMock();
ContextFactory contextFactory1 = new MockLNPaymentContextFactory(node1, dbHandler1);
ContextFactory contextFactory2 = new MockLNPaymentContextFactory(node2, dbHandler2);
ContextFactory contextFactory3 = new MockLNPaymentContextFactory(node3, dbHandler3);
LNPaymentProcessorImpl processor12;
LNPaymentProcessorImpl processor21;
LNPaymentProcessorImpl processor23;
LNPaymentProcessorImpl processor32;
LNConfiguration configuration = new LNConfiguration();
@Before
public void prepare () throws PropertyVetoException, SQLException {
node12.name = "LNPayment12";
node21.name = "LNPayment21";
node23.name = "LNPayment23";
node32.name = "LNPayment32";
node12.pubKeyClient = node2.pubKeyServer;
node21.pubKeyClient = node1.pubKeyServer;
node23.pubKeyClient = node3.pubKeyServer;
node32.pubKeyClient = node2.pubKeyServer;
processor12 = new LNPaymentProcessorImpl(contextFactory1, dbHandler1, node12);
processor21 = new LNPaymentProcessorImpl(contextFactory2, dbHandler2, node21);
processor23 = new LNPaymentProcessorImpl(contextFactory2, dbHandler2, node23);
processor32 = new LNPaymentProcessorImpl(contextFactory3, dbHandler3, node32);
channel12 = new EmbeddedChannel(new ProcessorHandler(processor12, "LNPayment12"));
channel21 = new EmbeddedChannel(new ProcessorHandler(processor21, "LNPayment21"));
channel23 = new EmbeddedChannel(new ProcessorHandler(processor23, "LNPayment23"));
channel32 = new EmbeddedChannel(new ProcessorHandler(processor32, "LNPayment32"));
Message m = (Message) channel21.readOutbound();
assertNull(m);
}
public void after () {
channel12.checkException();
channel21.checkException();
channel21.checkException();
channel23.checkException();
channel32.checkException();
}
@Test
public void exchangePaymentWithRouting () throws InterruptedException {
OnionObject onionObject = getOnionObject(contextFactory1.getOnionHelper());
PaymentData paymentData = getMockPaymentData();
PaymentWrapper wrapper = new PaymentWrapper(new byte[0], paymentData);
dbHandler1.addPayment(wrapper);
dbHandler3.addPaymentSecret(paymentData.secret);
paymentData.secret.secret = null;
paymentData.onionObject = onionObject;
processor12.makePayment(paymentData);
connectChannel(channel12, channel21);
connectChannel(channel23, channel32);
Thread.sleep(3000);
ChannelStatus status12 = processor12.getStatusTemp();
ChannelStatus status21 = processor21.getStatusTemp();
ChannelStatus status23 = processor23.getStatusTemp();
ChannelStatus status32 = processor21.getStatusTemp();
assertEquals(status12.amountClient, LNPaymentDBHandlerMock.INITIAL_AMOUNT_CHANNEL + paymentData.amount);
assertEquals(status12.amountServer, LNPaymentDBHandlerMock.INITIAL_AMOUNT_CHANNEL - paymentData.amount);
assertEquals(status21.amountClient, LNPaymentDBHandlerMock.INITIAL_AMOUNT_CHANNEL - paymentData.amount);
assertEquals(status21.amountServer, LNPaymentDBHandlerMock.INITIAL_AMOUNT_CHANNEL + paymentData.amount);
assertEquals(status23.amountClient, LNPaymentDBHandlerMock.INITIAL_AMOUNT_CHANNEL + paymentData.amount);
assertEquals(status23.amountServer, LNPaymentDBHandlerMock.INITIAL_AMOUNT_CHANNEL - paymentData.amount);
assertEquals(status32.amountClient, LNPaymentDBHandlerMock.INITIAL_AMOUNT_CHANNEL - paymentData.amount);
assertEquals(status32.amountServer, LNPaymentDBHandlerMock.INITIAL_AMOUNT_CHANNEL + paymentData.amount);
after();
}
@Test
public void exchangePaymentWithRoutingRefund () throws InterruptedException {
OnionObject onionObject = getOnionObject(contextFactory1.getOnionHelper());
PaymentData paymentData = getMockPaymentData();
PaymentWrapper wrapper = new PaymentWrapper(new byte[0], paymentData);
dbHandler1.addPayment(wrapper);
paymentData.secret.secret = null;
paymentData.onionObject = onionObject;
processor12.makePayment(paymentData);
connectChannel(channel12, channel21);
connectChannel(channel23, channel32);
Thread.sleep(3000);
testUnchangedChannelAmounts();
after();
}
@Test
public void exchangePaymentWithRoutingCorruptedOnionObject () throws InterruptedException {
OnionObject onionObject = getOnionObject(contextFactory1.getOnionHelper());
Tools.copyRandomByteInByteArray(onionObject.data, 100, 2);
PaymentData paymentData = getMockPaymentData();
PaymentWrapper wrapper = new PaymentWrapper(new byte[0], paymentData);
dbHandler1.addPayment(wrapper);
paymentData.secret.secret = null;
paymentData.onionObject = onionObject;
processor12.makePayment(paymentData);
connectChannel(channel12, channel21);
connectChannel(channel23, channel32);
Thread.sleep(3000);
testUnchangedChannelAmounts();
after();
}
@Test
public void exchangePaymentWithRoutingToUnknownNode () throws InterruptedException {
OnionObject onionObject = getOnionObject(contextFactory1.getOnionHelper(), node12.pubKeyClient.getPubKey(), new ECKey().getPubKey());
PaymentData paymentData = getMockPaymentData();
PaymentWrapper wrapper = new PaymentWrapper(new byte[0], paymentData);
dbHandler1.addPayment(wrapper);
paymentData.secret.secret = null;
paymentData.onionObject = onionObject;
processor12.makePayment(paymentData);
connectChannel(channel12, channel21);
connectChannel(channel23, channel32);
Thread.sleep(3000);
testUnchangedChannelAmounts();
after();
}
@Test
public void exchangePaymentWithTooHighPayment () throws InterruptedException {
long normalAmount = LNPaymentDBHandlerMock.INITIAL_AMOUNT_CHANNEL;
long doubleAmount = normalAmount * 2;
ChannelStatus status12 = processor12.getChannel().channelStatus;
status12.amountClient *= 2;
status12.amountServer *= 2;
ChannelStatus status21 = processor21.getChannel().channelStatus;
status21.amountClient *= 2;
status21.amountServer *= 2;
OnionObject onionObject = getOnionObject(contextFactory1.getOnionHelper());
PaymentData paymentData = getMockPaymentData();
paymentData.amount = LNPaymentDBHandlerMock.INITIAL_AMOUNT_CHANNEL + 1;
PaymentWrapper wrapper = new PaymentWrapper(new byte[0], paymentData);
dbHandler1.addPayment(wrapper);
paymentData.secret.secret = null;
paymentData.onionObject = onionObject;
processor12.makePayment(paymentData);
connectChannel(channel12, channel21);
connectChannel(channel23, channel32);
Thread.sleep(3000);
testUnchangedChannelAmounts(doubleAmount, doubleAmount, normalAmount, normalAmount);
after();
}
public void testUnchangedChannelAmounts () {
testUnchangedChannelAmounts(LNPaymentDBHandlerMock.INITIAL_AMOUNT_CHANNEL, LNPaymentDBHandlerMock.INITIAL_AMOUNT_CHANNEL,
LNPaymentDBHandlerMock.INITIAL_AMOUNT_CHANNEL, LNPaymentDBHandlerMock.INITIAL_AMOUNT_CHANNEL);
}
public void testUnchangedChannelAmounts (long amount12, long amount21, long amount23, long amount32) {
ChannelStatus status12 = processor12.getStatusTemp();
ChannelStatus status21 = processor21.getStatusTemp();
ChannelStatus status23 = processor23.getStatusTemp();
ChannelStatus status32 = processor32.getStatusTemp();
assertEquals(status12.amountClient, amount12);
assertEquals(status12.amountServer, amount12);
assertEquals(status21.amountClient, amount21);
assertEquals(status21.amountServer, amount21);
assertEquals(status23.amountClient, amount23);
assertEquals(status23.amountServer, amount23);
assertEquals(status32.amountClient, amount32);
assertEquals(status32.amountServer, amount32);
}
public static void exchangeMessages (EmbeddedChannel from, EmbeddedChannel to) {
Object message = from.readOutbound();
if (message != null) {
to.writeInbound(message);
}
}
public static void exchangeMessagesDuplex (EmbeddedChannel from, EmbeddedChannel to) {
exchangeMessages(from, to);
exchangeMessages(to, from);
}
public OnionObject getOnionObject (LNOnionHelper onionHelper) {
return getOnionObject(onionHelper, node12.pubKeyClient.getPubKey(), node23.pubKeyClient.getPubKey());
}
public OnionObject getOnionObject (LNOnionHelper onionHelper, byte[] node2, byte[] node3) {
List<byte[]> route = new ArrayList<>();
route.add(node2);
route.add(node3);
return onionHelper.createOnionObject(route, null);
}
public PaymentData getMockPaymentData () {
PaymentData paymentData = new PaymentData();
paymentData.sending = true;
paymentData.amount = 10000;
paymentData.secret = new PaymentSecret(Tools.getRandomByte(20));
paymentData.timestampOpen = Tools.currentTime();
paymentData.timestampRefund = Tools.currentTime() + 3
* configuration.MAX_REFUND_DELAY * configuration.MAX_OVERLAY_REFUND;
paymentData.csvDelay = configuration.DEFAULT_REVOCATION_DELAY;
return paymentData;
}
public void connectChannel (EmbeddedChannel from, EmbeddedChannel to) {
new Thread(() -> {
while (true) {
exchangeMessagesDuplex(from, to);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
class MockLNPaymentContextFactory extends MockContextFactory {
public MockLNPaymentContextFactory (NodeServer node, DBHandler dbHandler) {
super(node, dbHandler);
}
@Override
public LNPaymentLogic getLNPaymentLogic () {
return new MockLNPaymentLogic(getLNPaymentMessageFactory());
}
@Override
public LNPaymentMessageFactory getLNPaymentMessageFactory () {
return new LNPaymentMessageFactoryMock();
}
}
}