@Test public void retryFailedBroadcast() throws Exception { // If we create a spend, it's sent to a peer that swallows it, and the peergroup is // removed/re-added then // the tx should be broadcast again. InboundMessageQueuer p1 = connectPeer(1); connectPeer(2); // Send ourselves a bit of money. Block b1 = TestUtils.makeSolvedTestBlock(blockStore, address); inbound(p1, b1); assertNull(outbound(p1)); assertEquals(Utils.toNanoCoins(50, 0), wallet.getBalance()); // Now create a spend, and expect the announcement on p1. Address dest = new ECKey().toAddress(params); Wallet.SendResult sendResult = wallet.sendCoins(peerGroup, dest, Utils.toNanoCoins(1, 0)); assertFalse(sendResult.broadcastComplete.isDone()); Transaction t1 = (Transaction) outbound(p1); assertFalse(sendResult.broadcastComplete.isDone()); // p1 eats it :( A bit later the PeerGroup is taken down. peerGroup.removeWallet(wallet); peerGroup.addWallet(wallet); // We want to hear about it again. Now, because we've disabled the randomness for the unit tests // it will // re-appear on p1 again. Of course in the real world it would end up with a different set of // peers and // select randomly so we get a second chance. Transaction t2 = (Transaction) outbound(p1); assertEquals(t1, t2); }
public void timeLockedTransaction(boolean useNotFound) throws Exception { connectWithVersion(useNotFound ? 70001 : 60001); // Test that if we receive a relevant transaction that has a lock time, it doesn't result in a // notification // until we explicitly opt in to seeing those. ECKey key = new ECKey(); Wallet wallet = new Wallet(unitTestParams); wallet.addKey(key); peer.addWallet(wallet); final Transaction[] vtx = new Transaction[1]; wallet.addEventListener( new AbstractWalletEventListener() { @Override public void onCoinsReceived( Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) { vtx[0] = tx; } }); // Send a normal relevant transaction, it's received correctly. Transaction t1 = TestUtils.createFakeTx(unitTestParams, Utils.toNanoCoins(1, 0), key); inbound(writeTarget, t1); GetDataMessage getdata = (GetDataMessage) outbound(writeTarget); if (useNotFound) { inbound(writeTarget, new NotFoundMessage(unitTestParams, getdata.getItems())); } else { bouncePing(); } pingAndWait(writeTarget); Threading.waitForUserCode(); assertNotNull(vtx[0]); vtx[0] = null; // Send a timelocked transaction, nothing happens. Transaction t2 = TestUtils.createFakeTx(unitTestParams, Utils.toNanoCoins(2, 0), key); t2.setLockTime(999999); inbound(writeTarget, t2); Threading.waitForUserCode(); assertNull(vtx[0]); // Now we want to hear about them. Send another, we are told about it. wallet.setAcceptRiskyTransactions(true); inbound(writeTarget, t2); getdata = (GetDataMessage) outbound(writeTarget); if (useNotFound) { inbound(writeTarget, new NotFoundMessage(unitTestParams, getdata.getItems())); } else { bouncePing(); } pingAndWait(writeTarget); Threading.waitForUserCode(); assertEquals(t2, vtx[0]); }
@Before public void setUp() throws Exception { unitTestParams = UnitTestParams.get(); wallet = new Wallet(unitTestParams); wallet.addKey(new ECKey()); resetBlockStore(); Transaction tx1 = createFakeTx( unitTestParams, Utils.toNanoCoins(2, 0), wallet.getKeys().get(0).toAddress(unitTestParams)); // add a second input so can test granularity of byte cache. Transaction prevTx = new Transaction(unitTestParams); TransactionOutput prevOut = new TransactionOutput( unitTestParams, prevTx, Utils.toNanoCoins(1, 0), wallet.getKeys().get(0).toAddress(unitTestParams)); prevTx.addOutput(prevOut); // Connect it. tx1.addInput(prevOut); Transaction tx2 = createFakeTx( unitTestParams, Utils.toNanoCoins(1, 0), new ECKey().toAddress(unitTestParams)); Block b1 = createFakeBlock(blockStore, tx1, tx2).block; BitcoinSerializer bs = new BitcoinSerializer(unitTestParams); ByteArrayOutputStream bos = new ByteArrayOutputStream(); bs.serialize(tx1, bos); tx1BytesWithHeader = bos.toByteArray(); tx1Bytes = tx1.bitcoinSerialize(); bos.reset(); bs.serialize(tx2, bos); tx2BytesWithHeader = bos.toByteArray(); tx2Bytes = tx2.bitcoinSerialize(); bos.reset(); bs.serialize(b1, bos); b1BytesWithHeader = bos.toByteArray(); b1Bytes = b1.bitcoinSerialize(); }
@Test public void exceptionListener() throws Exception { wallet.addEventListener( new AbstractWalletEventListener() { @Override public void onCoinsReceived( Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) { throw new NullPointerException("boo!"); } }); final Throwable[] throwables = new Throwable[1]; Threading.uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread thread, Throwable throwable) { throwables[0] = throwable; } }; // In real usage we're not really meant to adjust the uncaught exception handler after stuff // started happening // but in the unit test environment other tests have just run so the thread is probably still // kicking around. // Force it to crash so it'll be recreated with our new handler. Threading.USER_THREAD.execute( new Runnable() { @Override public void run() { throw new RuntimeException(); } }); connect(); Transaction t1 = new Transaction(unitTestParams); t1.addInput(new TransactionInput(unitTestParams, t1, new byte[] {})); t1.addOutput(Utils.toNanoCoins(1, 0), new ECKey().toAddress(unitTestParams)); Transaction t2 = new Transaction(unitTestParams); t2.addInput(t1.getOutput(0)); t2.addOutput(Utils.toNanoCoins(1, 0), wallet.getChangeAddress()); inbound(writeTarget, t2); final InventoryItem inventoryItem = new InventoryItem(InventoryItem.Type.Transaction, t2.getInput(0).getOutpoint().getHash()); final NotFoundMessage nfm = new NotFoundMessage(unitTestParams, Lists.newArrayList(inventoryItem)); inbound(writeTarget, nfm); pingAndWait(writeTarget); Threading.waitForUserCode(); assertTrue(throwables[0] instanceof NullPointerException); Threading.uncaughtExceptionHandler = null; }
@Test public void invDownloadTxMultiPeer() throws Exception { // Check co-ordination of which peer to download via the memory pool. VersionMessage ver = new VersionMessage(unitTestParams, 100); InetSocketAddress address = new InetSocketAddress("127.0.0.1", 4242); Peer peer2 = new Peer(unitTestParams, ver, new PeerAddress(address), blockChain, memoryPool); peer2.addWallet(wallet); VersionMessage peerVersion = new VersionMessage(unitTestParams, OTHER_PEER_CHAIN_HEIGHT); peerVersion.clientVersion = 70001; peerVersion.localServices = VersionMessage.NODE_NETWORK; connect(); InboundMessageQueuer writeTarget2 = connect(peer2, peerVersion); // Make a tx and advertise it to one of the peers. BigInteger value = Utils.toNanoCoins(1, 0); Transaction tx = createFakeTx(unitTestParams, value, this.address); InventoryMessage inv = new InventoryMessage(unitTestParams); InventoryItem item = new InventoryItem(InventoryItem.Type.Transaction, tx.getHash()); inv.addItem(item); inbound(writeTarget, inv); // We got a getdata message. GetDataMessage message = (GetDataMessage) outbound(writeTarget); assertEquals(1, message.getItems().size()); assertEquals(tx.getHash(), message.getItems().get(0).hash); assertTrue(memoryPool.maybeWasSeen(tx.getHash())); // Advertising to peer2 results in no getdata message. inbound(writeTarget2, inv); pingAndWait(writeTarget2); assertNull(outbound(writeTarget2)); }
@Test public void invDownloadTx() throws Exception { connect(); peer.setDownloadData(true); // Make a transaction and tell the peer we have it. BigInteger value = Utils.toNanoCoins(1, 0); Transaction tx = createFakeTx(unitTestParams, value, address); InventoryMessage inv = new InventoryMessage(unitTestParams); InventoryItem item = new InventoryItem(InventoryItem.Type.Transaction, tx.getHash()); inv.addItem(item); inbound(writeTarget, inv); // Peer hasn't seen it before, so will ask for it. GetDataMessage getdata = (GetDataMessage) outbound(writeTarget); assertEquals(1, getdata.getItems().size()); assertEquals(tx.getHash(), getdata.getItems().get(0).hash); inbound(writeTarget, tx); // Ask for the dependency, it's not in the mempool (in chain). getdata = (GetDataMessage) outbound(writeTarget); inbound(writeTarget, new NotFoundMessage(unitTestParams, getdata.getItems())); pingAndWait(writeTarget); assertEquals(value, wallet.getBalance(Wallet.BalanceType.ESTIMATED)); }
@Test public void peerGroupWalletIntegration() throws Exception { // Make sure we can create spends, and that they are announced. Then do the same with offline // mode. // Set up connections and block chain. VersionMessage ver = new VersionMessage(params, 2); ver.localServices = VersionMessage.NODE_NETWORK; InboundMessageQueuer p1 = connectPeer(1, ver); InboundMessageQueuer p2 = connectPeer(2); // Send ourselves a bit of money. Block b1 = TestUtils.makeSolvedTestBlock(blockStore, address); inbound(p1, b1); pingAndWait(p1); assertNull(outbound(p1)); assertEquals(Utils.toNanoCoins(50, 0), wallet.getBalance()); // Check that the wallet informs us of changes in confidence as the transaction ripples across // the network. final Transaction[] transactions = new Transaction[1]; wallet.addEventListener( new AbstractWalletEventListener() { @Override public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) { transactions[0] = tx; } }); // Now create a spend, and expect the announcement on p1. Address dest = new ECKey().toAddress(params); Wallet.SendResult sendResult = wallet.sendCoins(peerGroup, dest, Utils.toNanoCoins(1, 0)); assertNotNull(sendResult.tx); Threading.waitForUserCode(); assertFalse(sendResult.broadcastComplete.isDone()); assertEquals(transactions[0], sendResult.tx); assertEquals(0, transactions[0].getConfidence().numBroadcastPeers()); transactions[0] = null; Transaction t1 = (Transaction) outbound(p1); assertNotNull(t1); // 49 BTC in change. assertEquals(Utils.toNanoCoins(49, 0), t1.getValueSentToMe(wallet)); // The future won't complete until it's heard back from the network on p2. InventoryMessage inv = new InventoryMessage(params); inv.addTransaction(t1); inbound(p2, inv); pingAndWait(p2); Threading.waitForUserCode(); assertTrue(sendResult.broadcastComplete.isDone()); assertEquals(transactions[0], sendResult.tx); assertEquals(1, transactions[0].getConfidence().numBroadcastPeers()); // Confirm it. Block b2 = TestUtils.createFakeBlock(blockStore, t1).block; inbound(p1, b2); pingAndWait(p1); assertNull(outbound(p1)); // Do the same thing with an offline transaction. peerGroup.removeWallet(wallet); Wallet.SendRequest req = Wallet.SendRequest.to(dest, Utils.toNanoCoins(2, 0)); req.ensureMinRequiredFee = false; Transaction t3 = checkNotNull(wallet.sendCoinsOffline(req)); assertNull(outbound(p1)); // Nothing sent. // Add the wallet to the peer group (simulate initialization). Transactions should be announced. peerGroup.addWallet(wallet); // Transaction announced to the first peer. assertEquals(t3.getHash(), ((Transaction) outbound(p1)).getHash()); }
private void checkTimeLockedDependency(boolean shouldAccept, boolean useNotFound) throws Exception { // Initial setup. connectWithVersion(useNotFound ? 70001 : 60001); ECKey key = new ECKey(); Wallet wallet = new Wallet(unitTestParams); wallet.addKey(key); wallet.setAcceptRiskyTransactions(shouldAccept); peer.addWallet(wallet); final Transaction[] vtx = new Transaction[1]; wallet.addEventListener( new AbstractWalletEventListener() { @Override public void onCoinsReceived( Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) { vtx[0] = tx; } }); // t1 -> t2 [locked] -> t3 (not available) Transaction t2 = new Transaction(unitTestParams); t2.setLockTime(999999); // Add a fake input to t3 that goes nowhere. Sha256Hash t3 = Sha256Hash.create("abc".getBytes(Charset.forName("UTF-8"))); t2.addInput( new TransactionInput( unitTestParams, t2, new byte[] {}, new TransactionOutPoint(unitTestParams, 0, t3))); t2.getInput(0).setSequenceNumber(0xDEADBEEF); t2.addOutput(Utils.toNanoCoins(1, 0), new ECKey()); Transaction t1 = new Transaction(unitTestParams); t1.addInput(t2.getOutput(0)); t1.addOutput(Utils.toNanoCoins(1, 0), key); // Make it relevant. // Announce t1. InventoryMessage inv = new InventoryMessage(unitTestParams); inv.addTransaction(t1); inbound(writeTarget, inv); // Send it. GetDataMessage getdata = (GetDataMessage) outbound(writeTarget); assertEquals(t1.getHash(), getdata.getItems().get(0).hash); inbound(writeTarget, t1); // Nothing arrived at our event listener yet. assertNull(vtx[0]); // We request t2. getdata = (GetDataMessage) outbound(writeTarget); assertEquals(t2.getHash(), getdata.getItems().get(0).hash); inbound(writeTarget, t2); if (!useNotFound) bouncePing(); // We request t3. getdata = (GetDataMessage) outbound(writeTarget); assertEquals(t3, getdata.getItems().get(0).hash); // Can't find it: bottom of tree. if (useNotFound) { NotFoundMessage notFound = new NotFoundMessage(unitTestParams); notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, t3)); inbound(writeTarget, notFound); } else { bouncePing(); } pingAndWait(writeTarget); Threading.waitForUserCode(); // We're done but still not notified because it was timelocked. if (shouldAccept) assertNotNull(vtx[0]); else assertNull(vtx[0]); }
public void recursiveDownload(boolean useNotFound) throws Exception { // Using ping or notfound? connectWithVersion(useNotFound ? 70001 : 60001); // Check that we can download all dependencies of an unconfirmed relevant transaction from the // mempool. ECKey to = new ECKey(); final Transaction[] onTx = new Transaction[1]; peer.addEventListener( new AbstractPeerEventListener() { @Override public void onTransaction(Peer peer1, Transaction t) { onTx[0] = t; } }, Threading.SAME_THREAD); // Make the some fake transactions in the following graph: // t1 -> t2 -> [t5] // -> t3 -> t4 -> [t6] // -> [t7] // -> [t8] // The ones in brackets are assumed to be in the chain and are represented only by hashes. Transaction t2 = TestUtils.createFakeTx(unitTestParams, Utils.toNanoCoins(1, 0), to); Sha256Hash t5 = t2.getInput(0).getOutpoint().getHash(); Transaction t4 = TestUtils.createFakeTx(unitTestParams, Utils.toNanoCoins(1, 0), new ECKey()); Sha256Hash t6 = t4.getInput(0).getOutpoint().getHash(); t4.addOutput(Utils.toNanoCoins(1, 0), new ECKey()); Transaction t3 = new Transaction(unitTestParams); t3.addInput(t4.getOutput(0)); t3.addOutput(Utils.toNanoCoins(1, 0), new ECKey()); Transaction t1 = new Transaction(unitTestParams); t1.addInput(t2.getOutput(0)); t1.addInput(t3.getOutput(0)); Sha256Hash someHash = new Sha256Hash("2b801dd82f01d17bbde881687bf72bc62e2faa8ab8133d36fcb8c3abe7459da6"); t1.addInput( new TransactionInput( unitTestParams, t1, new byte[] {}, new TransactionOutPoint(unitTestParams, 0, someHash))); Sha256Hash anotherHash = new Sha256Hash("3b801dd82f01d17bbde881687bf72bc62e2faa8ab8133d36fcb8c3abe7459da6"); t1.addInput( new TransactionInput( unitTestParams, t1, new byte[] {}, new TransactionOutPoint(unitTestParams, 1, anotherHash))); t1.addOutput(Utils.toNanoCoins(1, 0), to); t1 = TestUtils.roundTripTransaction(unitTestParams, t1); t2 = TestUtils.roundTripTransaction(unitTestParams, t2); t3 = TestUtils.roundTripTransaction(unitTestParams, t3); t4 = TestUtils.roundTripTransaction(unitTestParams, t4); // Announce the first one. Wait for it to be downloaded. InventoryMessage inv = new InventoryMessage(unitTestParams); inv.addTransaction(t1); inbound(writeTarget, inv); GetDataMessage getdata = (GetDataMessage) outbound(writeTarget); Threading.waitForUserCode(); assertEquals(t1.getHash(), getdata.getItems().get(0).hash); inbound(writeTarget, t1); pingAndWait(writeTarget); assertEquals(t1, onTx[0]); // We want its dependencies so ask for them. ListenableFuture<List<Transaction>> futures = peer.downloadDependencies(t1); assertFalse(futures.isDone()); // It will recursively ask for the dependencies of t1: t2, t3, someHash and anotherHash. getdata = (GetDataMessage) outbound(writeTarget); assertEquals(4, getdata.getItems().size()); assertEquals(t2.getHash(), getdata.getItems().get(0).hash); assertEquals(t3.getHash(), getdata.getItems().get(1).hash); assertEquals(someHash, getdata.getItems().get(2).hash); assertEquals(anotherHash, getdata.getItems().get(3).hash); long nonce = -1; if (!useNotFound) nonce = ((Ping) outbound(writeTarget)).getNonce(); // For some random reason, t4 is delivered at this point before it's needed - perhaps it was a // Bloom filter // false positive. We do this to check that the mempool is being checked for seen transactions // before // requesting them. inbound(writeTarget, t4); // Deliver the requested transactions. inbound(writeTarget, t2); inbound(writeTarget, t3); if (useNotFound) { NotFoundMessage notFound = new NotFoundMessage(unitTestParams); notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, someHash)); notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, anotherHash)); inbound(writeTarget, notFound); } else { inbound(writeTarget, new Pong(nonce)); } assertFalse(futures.isDone()); // It will recursively ask for the dependencies of t2: t5 and t4, but not t3 because it already // found t4. getdata = (GetDataMessage) outbound(writeTarget); assertEquals(getdata.getItems().get(0).hash, t2.getInput(0).getOutpoint().getHash()); // t5 isn't found and t4 is. if (useNotFound) { NotFoundMessage notFound = new NotFoundMessage(unitTestParams); notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, t5)); inbound(writeTarget, notFound); } else { bouncePing(); } assertFalse(futures.isDone()); // Continue to explore the t4 branch and ask for t6, which is in the chain. getdata = (GetDataMessage) outbound(writeTarget); assertEquals(t6, getdata.getItems().get(0).hash); if (useNotFound) { NotFoundMessage notFound = new NotFoundMessage(unitTestParams); notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, t6)); inbound(writeTarget, notFound); } else { bouncePing(); } pingAndWait(writeTarget); // That's it, we explored the entire tree. assertTrue(futures.isDone()); List<Transaction> results = futures.get(); assertTrue(results.contains(t2)); assertTrue(results.contains(t3)); assertTrue(results.contains(t4)); }
public class PaymentSessionTest { private static final NetworkParameters params = TestNet3Params.get(); private static final String simplePaymentUrl = "http://a.simple.url.com/"; private static final String paymentRequestMemo = "send coinz noa plz kthx"; private static final String paymentMemo = "take ze coinz"; private static final ByteString merchantData = ByteString.copyFromUtf8("merchant data"); private static final long time = System.currentTimeMillis() / 1000L; private ECKey serverKey; private Transaction tx; private TransactionOutput outputToMe; BigInteger nanoCoins = Utils.toNanoCoins(1, 0); @Before public void setUp() throws Exception { serverKey = new ECKey(); tx = new Transaction(params); outputToMe = new TransactionOutput(params, tx, nanoCoins, serverKey); tx.addOutput(outputToMe); } @Test public void testSimplePayment() throws Exception { // Create a PaymentRequest and make sure the correct values are parsed by the PaymentSession. MockPaymentSession paymentSession = new MockPaymentSession(newSimplePaymentRequest()); assertEquals(paymentRequestMemo, paymentSession.getMemo()); assertEquals(nanoCoins, paymentSession.getValue()); assertEquals(simplePaymentUrl, paymentSession.getPaymentUrl()); assertTrue(new Date(time * 1000L).equals(paymentSession.getDate())); assertTrue(paymentSession.getSendRequest().tx.equals(tx)); assertFalse(paymentSession.isExpired()); // Send the payment and verify that the correct information is sent. // Add a dummy input to tx so it is considered valid. tx.addInput(new TransactionInput(params, tx, outputToMe.getScriptBytes())); ArrayList<Transaction> txns = new ArrayList<Transaction>(); txns.add(tx); Address refundAddr = new Address(params, serverKey.getPubKeyHash()); paymentSession.sendPayment(txns, refundAddr, paymentMemo); assertEquals(1, paymentSession.getPaymentLog().size()); assertEquals(simplePaymentUrl, paymentSession.getPaymentLog().get(0).getUrl().toString()); Protos.Payment payment = paymentSession.getPaymentLog().get(0).getPayment(); assertEquals(paymentMemo, payment.getMemo()); assertEquals(merchantData, payment.getMerchantData()); assertEquals(1, payment.getRefundToCount()); assertEquals(nanoCoins.longValue(), payment.getRefundTo(0).getAmount()); TransactionOutput refundOutput = new TransactionOutput(params, null, nanoCoins, refundAddr); ByteString refundScript = ByteString.copyFrom(refundOutput.getScriptBytes()); assertTrue(refundScript.equals(payment.getRefundTo(0).getScript())); } @Test public void testDefaults() throws Exception { Protos.Output.Builder outputBuilder = Protos.Output.newBuilder().setScript(ByteString.copyFrom(outputToMe.getScriptBytes())); Protos.PaymentDetails paymentDetails = Protos.PaymentDetails.newBuilder().setTime(time).addOutputs(outputBuilder).build(); Protos.PaymentRequest paymentRequest = Protos.PaymentRequest.newBuilder() .setSerializedPaymentDetails(paymentDetails.toByteString()) .build(); MockPaymentSession paymentSession = new MockPaymentSession(paymentRequest); assertEquals(BigInteger.ZERO, paymentSession.getValue()); assertNull(paymentSession.getPaymentUrl()); assertNull(paymentSession.getMemo()); } @Test public void testExpiredPaymentRequest() throws Exception { MockPaymentSession paymentSession = new MockPaymentSession(newExpiredPaymentRequest()); assertTrue(paymentSession.isExpired()); // Send the payment and verify that an exception is thrown. // Add a dummy input to tx so it is considered valid. tx.addInput(new TransactionInput(params, tx, outputToMe.getScriptBytes())); ArrayList<Transaction> txns = new ArrayList<Transaction>(); txns.add(tx); try { paymentSession.sendPayment(txns, null, null); } catch (PaymentRequestException.Expired e) { assertEquals(0, paymentSession.getPaymentLog().size()); assertEquals(e.getMessage(), "PaymentRequest is expired"); return; } fail("Expected exception due to expired PaymentRequest"); } @Test public void testPkiVerification() throws Exception { InputStream in = getClass().getResourceAsStream("pki_test.bitcoinpaymentrequest"); Protos.PaymentRequest paymentRequest = Protos.PaymentRequest.newBuilder().mergeFrom(in).build(); MockPaymentSession paymentSession = new MockPaymentSession(paymentRequest); PaymentSession.PkiVerificationData pkiData = paymentSession.verifyPki(); assertEquals("www.bitcoincore.org", pkiData.name); assertEquals("The USERTRUST Network, Salt Lake City, US", pkiData.rootAuthorityName); } private Protos.PaymentRequest newSimplePaymentRequest() { Protos.Output.Builder outputBuilder = Protos.Output.newBuilder() .setAmount(nanoCoins.longValue()) .setScript(ByteString.copyFrom(outputToMe.getScriptBytes())); Protos.PaymentDetails paymentDetails = Protos.PaymentDetails.newBuilder() .setNetwork("test") .setTime(time) .setPaymentUrl(simplePaymentUrl) .addOutputs(outputBuilder) .setMemo(paymentRequestMemo) .setMerchantData(merchantData) .build(); Protos.PaymentRequest paymentRequest = Protos.PaymentRequest.newBuilder() .setPaymentDetailsVersion(1) .setPkiType("none") .setSerializedPaymentDetails(paymentDetails.toByteString()) .build(); return paymentRequest; } private Protos.PaymentRequest newExpiredPaymentRequest() { Protos.Output.Builder outputBuilder = Protos.Output.newBuilder() .setAmount(nanoCoins.longValue()) .setScript(ByteString.copyFrom(outputToMe.getScriptBytes())); Protos.PaymentDetails paymentDetails = Protos.PaymentDetails.newBuilder() .setNetwork("test") .setTime(time - 10) .setExpires(time - 1) .setPaymentUrl(simplePaymentUrl) .addOutputs(outputBuilder) .setMemo(paymentRequestMemo) .setMerchantData(merchantData) .build(); Protos.PaymentRequest paymentRequest = Protos.PaymentRequest.newBuilder() .setPaymentDetailsVersion(1) .setPkiType("none") .setSerializedPaymentDetails(paymentDetails.toByteString()) .build(); return paymentRequest; } private class MockPaymentSession extends PaymentSession { private ArrayList<PaymentLogItem> paymentLog = new ArrayList<PaymentLogItem>(); public MockPaymentSession(Protos.PaymentRequest request) throws PaymentRequestException { super(request); } public ArrayList<PaymentLogItem> getPaymentLog() { return paymentLog; } protected ListenableFuture<Ack> sendPayment(final URL url, final Protos.Payment payment) { paymentLog.add(new PaymentLogItem(url, payment)); return null; } public class PaymentLogItem { private final URL url; private final Protos.Payment payment; PaymentLogItem(final URL url, final Protos.Payment payment) { this.url = url; this.payment = payment; } public URL getUrl() { return url; } public Protos.Payment getPayment() { return payment; } } } }