private void serDeser(
      BitcoinSerializer bs,
      Message message,
      byte[] sourceBytes,
      byte[] containedBytes,
      byte[] containingBytes)
      throws Exception {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    bs.serialize(message, bos);
    byte[] b1 = bos.toByteArray();

    Message m2 = bs.deserialize(new ByteArrayInputStream(b1));

    assertEquals(message, m2);

    bos.reset();
    bs.serialize(m2, bos);
    byte[] b2 = bos.toByteArray();
    assertTrue(Arrays.equals(b1, b2));

    if (sourceBytes != null) {
      assertTrue(arrayContains(sourceBytes, b1));

      assertTrue(arrayContains(sourceBytes, b2));
    }

    if (containedBytes != null) {
      assertTrue(arrayContains(b1, containedBytes));
    }
    if (containingBytes != null) {
      assertTrue(arrayContains(containingBytes, b1));
    }
  }
  @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();
  }
  public void testBlock(byte[] blockBytes, boolean isChild, boolean lazy, boolean retain)
      throws Exception {
    // reference serializer to produce comparison serialization output after changes to
    // message structure.
    BitcoinSerializer bsRef = new BitcoinSerializer(unitTestParams, false, false);
    ByteArrayOutputStream bos = new ByteArrayOutputStream();

    BitcoinSerializer bs = new BitcoinSerializer(unitTestParams, lazy, retain);
    Block b1;
    Block bRef;
    b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes));
    bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes));

    // verify our reference BitcoinSerializer produces matching byte array.
    bos.reset();
    bsRef.serialize(bRef, bos);
    assertTrue(Arrays.equals(bos.toByteArray(), blockBytes));

    // check lazy and retain status survive both before and after a serialization
    assertEquals(!lazy, b1.isParsedTransactions());
    assertEquals(!lazy, b1.isParsedHeader());
    if (b1.isParsedHeader()) assertEquals(retain, b1.isHeaderBytesValid());
    if (b1.isParsedTransactions()) assertEquals(retain, b1.isTransactionBytesValid());

    serDeser(bs, b1, blockBytes, null, null);

    assertEquals(!lazy, b1.isParsedTransactions());
    assertEquals(!lazy, b1.isParsedHeader());
    if (b1.isParsedHeader()) assertEquals(retain, b1.isHeaderBytesValid());
    if (b1.isParsedTransactions()) assertEquals(retain, b1.isTransactionBytesValid());

    // compare to ref block
    bos.reset();
    bsRef.serialize(bRef, bos);
    serDeser(bs, b1, bos.toByteArray(), null, null);

    // retrieve a value from a child
    b1.getTransactions();
    assertTrue(b1.isParsedTransactions());
    if (b1.getTransactions().size() > 0) {
      assertTrue(b1.isParsedTransactions());
      Transaction tx1 = b1.getTransactions().get(0);

      // this will always be true for all children of a block once they are retrieved.
      // the tx child inputs/outputs may not be parsed however.

      // no longer forced to parse if length not provided.
      // assertEquals(true, tx1.isParsed());
      if (tx1.isParsed()) assertEquals(retain, tx1.isCached());
      else assertTrue(tx1.isCached());

      // does it still match ref block?
      serDeser(bs, b1, bos.toByteArray(), null, null);
    }

    // refresh block
    b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes));
    bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes));

    // retrieve a value from header
    b1.getDifficultyTarget();
    assertTrue(b1.isParsedHeader());
    assertEquals(lazy, !b1.isParsedTransactions());

    // does it still match ref block?
    serDeser(bs, b1, bos.toByteArray(), null, null);

    // refresh block
    b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes));
    bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes));

    // retrieve a value from a child and header
    b1.getDifficultyTarget();
    assertTrue(b1.isParsedHeader());
    assertEquals(lazy, !b1.isParsedTransactions());

    b1.getTransactions();
    assertTrue(b1.isParsedTransactions());
    if (b1.getTransactions().size() > 0) {
      assertTrue(b1.isParsedTransactions());
      Transaction tx1 = b1.getTransactions().get(0);

      // no longer forced to parse if length not provided.
      // assertEquals(true, tx1.isParsed());

      if (tx1.isParsed()) assertEquals(retain, tx1.isCached());
      else assertTrue(tx1.isCached());
    }
    // does it still match ref block?
    serDeser(bs, b1, bos.toByteArray(), null, null);

    // refresh block
    b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes));
    bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes));

    // change a value in header
    b1.setNonce(23);
    bRef.setNonce(23);
    assertTrue(b1.isParsedHeader());
    assertEquals(lazy, !b1.isParsedTransactions());
    assertFalse(b1.isHeaderBytesValid());
    if (b1.isParsedTransactions()) assertEquals(retain, b1.isTransactionBytesValid());
    else assertEquals(true, b1.isTransactionBytesValid());
    // does it still match ref block?
    bos.reset();
    bsRef.serialize(bRef, bos);
    serDeser(bs, b1, bos.toByteArray(), null, null);

    // refresh block
    b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes));
    bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes));

    // retrieve a value from a child of a child
    b1.getTransactions();
    if (b1.getTransactions().size() > 0) {
      Transaction tx1 = b1.getTransactions().get(0);

      TransactionInput tin = tx1.getInputs().get(0);

      assertTrue(tx1.isParsed());
      assertTrue(b1.isParsedTransactions());
      assertEquals(!lazy, b1.isParsedHeader());

      assertEquals(!lazy, tin.isParsed());
      assertEquals(tin.isParsed() ? retain : true, tin.isCached());

      // does it still match ref tx?
      bos.reset();
      bsRef.serialize(bRef, bos);
      serDeser(bs, b1, bos.toByteArray(), null, null);
    }

    // refresh block
    b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes));
    bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes));

    // add an input
    b1.getTransactions();
    if (b1.getTransactions().size() > 0) {
      Transaction tx1 = b1.getTransactions().get(0);

      if (tx1.getInputs().size() > 0) {
        tx1.addInput(tx1.getInputs().get(0));
        // replicate on reference tx
        bRef.getTransactions().get(0).addInput(bRef.getTransactions().get(0).getInputs().get(0));

        assertFalse(tx1.isCached());
        assertTrue(tx1.isParsed());
        assertFalse(b1.isTransactionBytesValid());
        assertTrue(b1.isParsedHeader());

        // confirm sibling cache status was unaffected
        if (tx1.getInputs().size() > 1) {
          boolean parsed = tx1.getInputs().get(1).isParsed();
          assertEquals(parsed ? retain : true, tx1.getInputs().get(1).isCached());
          assertEquals(!lazy, parsed);
        }

        // this has to be false. Altering a tx invalidates the merkle root.
        // when we have seperate merkle caching then the entire header won't need to be
        // invalidated.
        assertFalse(b1.isHeaderBytesValid());

        bos.reset();
        bsRef.serialize(bRef, bos);
        byte[] source = bos.toByteArray();
        // confirm we still match the reference tx.
        serDeser(bs, b1, source, null, null);
      }

      // does it still match ref tx?
      bos.reset();
      bsRef.serialize(bRef, bos);
      serDeser(bs, b1, bos.toByteArray(), null, null);
    }

    // refresh block
    b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes));
    Block b2 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes));
    bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes));
    Block bRef2 = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes));

    // reparent an input
    b1.getTransactions();
    if (b1.getTransactions().size() > 0) {
      Transaction tx1 = b1.getTransactions().get(0);
      Transaction tx2 = b2.getTransactions().get(0);

      if (tx1.getInputs().size() > 0) {
        TransactionInput fromTx1 = tx1.getInputs().get(0);
        tx2.addInput(fromTx1);

        // replicate on reference tx
        TransactionInput fromTxRef = bRef.getTransactions().get(0).getInputs().get(0);
        bRef2.getTransactions().get(0).addInput(fromTxRef);

        // b1 hasn't changed but it's no longer in the parent
        // chain of fromTx1 so has to have been uncached since it won't be
        // notified of changes throught the parent chain anymore.
        assertFalse(b1.isTransactionBytesValid());

        // b2 should have it's cache invalidated because it has changed.
        assertFalse(b2.isTransactionBytesValid());

        bos.reset();
        bsRef.serialize(bRef2, bos);
        byte[] source = bos.toByteArray();
        // confirm altered block matches altered ref block.
        serDeser(bs, b2, source, null, null);
      }

      // does unaltered block still match ref block?
      bos.reset();
      bsRef.serialize(bRef, bos);
      serDeser(bs, b1, bos.toByteArray(), null, null);

      // how about if we refresh it?
      bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes));
      bos.reset();
      bsRef.serialize(bRef, bos);
      serDeser(bs, b1, bos.toByteArray(), null, null);
    }
  }
  public void testTransaction(
      NetworkParameters params, byte[] txBytes, boolean isChild, boolean lazy, boolean retain)
      throws Exception {

    // reference serializer to produce comparison serialization output after changes to
    // message structure.
    BitcoinSerializer bsRef = new BitcoinSerializer(params, false, false);
    ByteArrayOutputStream bos = new ByteArrayOutputStream();

    BitcoinSerializer bs = new BitcoinSerializer(params, lazy, retain);
    Transaction t1;
    Transaction tRef;
    t1 = (Transaction) bs.deserialize(new ByteArrayInputStream(txBytes));
    tRef = (Transaction) bsRef.deserialize(new ByteArrayInputStream(txBytes));

    // verify our reference BitcoinSerializer produces matching byte array.
    bos.reset();
    bsRef.serialize(tRef, bos);
    assertTrue(Arrays.equals(bos.toByteArray(), txBytes));

    // check lazy and retain status survive both before and after a serialization
    assertEquals(!lazy, t1.isParsed());
    if (t1.isParsed()) assertEquals(retain, t1.isCached());

    serDeser(bs, t1, txBytes, null, null);

    assertEquals(lazy, !t1.isParsed());
    if (t1.isParsed()) assertEquals(retain, t1.isCached());

    // compare to ref tx
    bos.reset();
    bsRef.serialize(tRef, bos);
    serDeser(bs, t1, bos.toByteArray(), null, null);

    // retrieve a value from a child
    t1.getInputs();
    assertTrue(t1.isParsed());
    if (t1.getInputs().size() > 0) {
      assertTrue(t1.isParsed());
      TransactionInput tin = t1.getInputs().get(0);
      assertEquals(!lazy, tin.isParsed());
      if (tin.isParsed()) assertEquals(retain, tin.isCached());

      // does it still match ref tx?
      serDeser(bs, t1, bos.toByteArray(), null, null);
    }

    // refresh tx
    t1 = (Transaction) bs.deserialize(new ByteArrayInputStream(txBytes));
    tRef = (Transaction) bsRef.deserialize(new ByteArrayInputStream(txBytes));

    // add an input
    if (t1.getInputs().size() > 0) {

      t1.addInput(t1.getInputs().get(0));

      // replicate on reference tx
      tRef.addInput(tRef.getInputs().get(0));

      assertFalse(t1.isCached());
      assertTrue(t1.isParsed());

      bos.reset();
      bsRef.serialize(tRef, bos);
      byte[] source = bos.toByteArray();
      // confirm we still match the reference tx.
      serDeser(bs, t1, source, null, null);
    }
  }
예제 #5
0
  @Test
  public void badMessage() throws Exception {
    // Bring up an actual network connection and feed it bogus data.
    final SettableFuture<Void> result = SettableFuture.create();
    Threading.uncaughtExceptionHandler =
        new Thread.UncaughtExceptionHandler() {
          @Override
          public void uncaughtException(Thread thread, Throwable throwable) {
            result.setException(throwable);
          }
        };
    connect(); // Writes out a verack+version.
    final SettableFuture<Void> peerDisconnected = SettableFuture.create();
    writeTarget.peer.addEventListener(
        new AbstractPeerEventListener() {
          @Override
          public void onPeerDisconnected(Peer p, int peerCount) {
            peerDisconnected.set(null);
          }
        });
    final NetworkParameters params = TestNet3Params.get();
    BitcoinSerializer serializer = new BitcoinSerializer(params);
    // Now write some bogus truncated message.
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    serializer.serialize(
        "inv",
        new InventoryMessage(params) {
          @Override
          public void bitcoinSerializeToStream(OutputStream stream) throws IOException {
            // Add some hashes.
            addItem(
                new InventoryItem(
                    InventoryItem.Type.Transaction, Sha256Hash.create(new byte[] {1})));
            addItem(
                new InventoryItem(
                    InventoryItem.Type.Transaction, Sha256Hash.create(new byte[] {2})));
            addItem(
                new InventoryItem(
                    InventoryItem.Type.Transaction, Sha256Hash.create(new byte[] {3})));

            // Write out a copy that's truncated in the middle.
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            super.bitcoinSerializeToStream(bos);
            byte[] bits = bos.toByteArray();
            bits = Arrays.copyOf(bits, bits.length / 2);
            stream.write(bits);
          }
        }.bitcoinSerialize(),
        out);
    writeTarget.writeTarget.writeBytes(out.toByteArray());
    try {
      result.get();
      fail();
    } catch (ExecutionException e) {
      assertTrue(e.getCause() instanceof ProtocolException);
    }
    peerDisconnected.get();
    try {
      peer.writeTarget.writeBytes(new byte[1]);
      fail();
    } catch (IOException e) {
      assertTrue(
          (e.getCause() != null && e.getCause() instanceof CancelledKeyException)
              || (e instanceof SocketException && e.getMessage().equals("Socket is closed")));
    }
  }