@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);
  }
Exemple #2
0
 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();
  }
Exemple #4
0
 @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;
 }
Exemple #5
0
  @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));
  }
Exemple #6
0
  @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());
  }
Exemple #8
0
 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]);
 }
Exemple #9
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;
      }
    }
  }
}