Exemple #1
0
 @Test
 public void testProofOfWork() throws Exception {
   // This params accepts any difficulty target.
   NetworkParameters params = UnitTestParams.get();
   Block block = params.getDefaultSerializer().makeBlock(blockBytes);
   block.setNonce(12346);
   try {
     block.verify(Block.BLOCK_HEIGHT_GENESIS, EnumSet.noneOf(Block.VerifyFlag.class));
     fail();
   } catch (VerificationException e) {
     // Expected.
   }
   // Blocks contain their own difficulty target. The BlockChain verification mechanism is what
   // stops real blocks
   // from containing artificially weak difficulties.
   block.setDifficultyTarget(Block.EASIEST_DIFFICULTY_TARGET);
   // Now it should pass.
   block.verify(Block.BLOCK_HEIGHT_GENESIS, EnumSet.noneOf(Block.VerifyFlag.class));
   // Break the nonce again at the lower difficulty level so we can try solving for it.
   block.setNonce(1);
   try {
     block.verify(Block.BLOCK_HEIGHT_GENESIS, EnumSet.noneOf(Block.VerifyFlag.class));
     fail();
   } catch (VerificationException e) {
     // Expected to fail as the nonce is no longer correct.
   }
   // Should find an acceptable nonce.
   block.solve();
   block.verify(Block.BLOCK_HEIGHT_GENESIS, EnumSet.noneOf(Block.VerifyFlag.class));
   assertEquals(block.getNonce(), 2);
 }
  @Test
  public void derive() throws Exception {
    ECKey key1 = chain.getKey(SimpleHDKeyChain.KeyPurpose.RECEIVE_FUNDS);
    ECKey key2 = chain.getKey(SimpleHDKeyChain.KeyPurpose.RECEIVE_FUNDS);

    final Address address = new Address(UnitTestParams.get(), "n1bQNoEx8uhmCzzA5JPG6sFdtsUQhwiQJV");
    assertEquals(address, key1.toAddress(UnitTestParams.get()));
    assertEquals(
        "mnHUcqUVvrfi5kAaXJDQzBb9HsWs78b42R", key2.toAddress(UnitTestParams.get()).toString());
    assertEquals(key1, chain.findKeyFromPubHash(address.getHash160()));
    assertEquals(key2, chain.findKeyFromPubKey(key2.getPubKey()));

    key1.sign(Sha256Hash.ZERO_HASH);

    ECKey key3 = chain.getKey(SimpleHDKeyChain.KeyPurpose.CHANGE);
    assertEquals(
        "mqumHgVDqNzuXNrszBmi7A2UpmwaPMx4HQ", key3.toAddress(UnitTestParams.get()).toString());
    key3.sign(Sha256Hash.ZERO_HASH);
  }
Exemple #3
0
 @Test
 public void testUpdateLength() {
   NetworkParameters params = UnitTestParams.get();
   Block block =
       params
           .getGenesisBlock()
           .createNextBlockWithCoinbase(
               Block.BLOCK_VERSION_GENESIS, new ECKey().getPubKey(), Block.BLOCK_HEIGHT_GENESIS);
   assertEquals(block.bitcoinSerialize().length, block.length);
   final int origBlockLen = block.length;
   Transaction tx = new Transaction(params);
   // this is broken until the transaction has > 1 input + output (which is required anyway...)
   // assertTrue(tx.length == tx.bitcoinSerialize().length && tx.length == 8);
   byte[] outputScript = new byte[10];
   Arrays.fill(outputScript, (byte) ScriptOpCodes.OP_FALSE);
   tx.addOutput(new TransactionOutput(params, null, Coin.SATOSHI, outputScript));
   tx.addInput(
       new TransactionInput(
           params,
           null,
           new byte[] {(byte) ScriptOpCodes.OP_FALSE},
           new TransactionOutPoint(params, 0, Sha256Hash.of(new byte[] {1}))));
   int origTxLength = 8 + 2 + 8 + 1 + 10 + 40 + 1 + 1;
   assertEquals(tx.bitcoinSerialize().length, tx.length);
   assertEquals(origTxLength, tx.length);
   block.addTransaction(tx);
   assertEquals(block.bitcoinSerialize().length, block.length);
   assertEquals(origBlockLen + tx.length, block.length);
   block
       .getTransactions()
       .get(1)
       .getInputs()
       .get(0)
       .setScriptBytes(new byte[] {(byte) ScriptOpCodes.OP_FALSE, (byte) ScriptOpCodes.OP_FALSE});
   assertEquals(block.length, origBlockLen + tx.length);
   assertEquals(tx.length, origTxLength + 1);
   block.getTransactions().get(1).getInputs().get(0).setScriptBytes(new byte[] {});
   assertEquals(block.length, block.bitcoinSerialize().length);
   assertEquals(block.length, origBlockLen + tx.length);
   assertEquals(tx.length, origTxLength - 1);
   block
       .getTransactions()
       .get(1)
       .addInput(
           new TransactionInput(
               params,
               null,
               new byte[] {(byte) ScriptOpCodes.OP_FALSE},
               new TransactionOutPoint(params, 0, Sha256Hash.of(new byte[] {1}))));
   assertEquals(block.length, origBlockLen + tx.length);
   assertEquals(tx.length, origTxLength + 41); // - 1 + 40 + 1 + 1
 }
 /** Returns the network parameters for the given string ID or NULL if not recognized. */
 @Nullable
 public static NetworkParameters fromID(String id) {
   if (id.equals(ID_MAINNET)) {
     return MainNetParams.get();
   } else if (id.equals(ID_TESTNET)) {
     return TestNet3Params.get();
   } else if (id.equals(ID_UNITTESTNET)) {
     return UnitTestParams.get();
   } else if (id.equals(ID_REGTEST)) {
     return RegTestParams.get();
   } else {
     return null;
   }
 }
 /** Returns a testnet params modified to allow any difficulty target. */
 @Deprecated
 public static NetworkParameters unitTests() {
   return UnitTestParams.get();
 }
@RunWith(value = Parameterized.class)
public class TransactionBroadcastTest extends TestWithPeerGroup {
  static final NetworkParameters params = UnitTestParams.get();

  @Parameterized.Parameters
  public static Collection<ClientType[]> parameters() {
    return Arrays.asList(
        new ClientType[] {ClientType.NIO_CLIENT_MANAGER},
        new ClientType[] {ClientType.BLOCKING_CLIENT_MANAGER});
  }

  public TransactionBroadcastTest(ClientType clientType) {
    super(clientType);
  }

  @Override
  @Before
  public void setUp() throws Exception {
    Utils.setMockClock(); // Use mock clock
    super.setUp();
    // Fix the random permutation that TransactionBroadcast uses to shuffle the peers.
    TransactionBroadcast.random = new Random(0);
    peerGroup.setMinBroadcastConnections(2);
    peerGroup.startAsync();
    peerGroup.awaitRunning();
  }

  @Override
  @After
  public void tearDown() {
    super.tearDown();
  }

  @Test
  public void fourPeers() throws Exception {
    InboundMessageQueuer[] channels = {
      connectPeer(1), connectPeer(2), connectPeer(3), connectPeer(4)
    };
    Transaction tx = new Transaction(params);
    TransactionBroadcast broadcast = new TransactionBroadcast(peerGroup, tx);
    ListenableFuture<Transaction> future = broadcast.broadcast();
    assertFalse(future.isDone());
    // We expect two peers to receive a tx message, and at least one of the others must announce for
    // the future to
    // complete successfully.
    Message[] messages = {
      (Message) outbound(channels[0]),
      (Message) outbound(channels[1]),
      (Message) outbound(channels[2]),
      (Message) outbound(channels[3])
    };
    // 0 and 3 are randomly selected to receive the broadcast.
    assertEquals(tx, messages[0]);
    assertEquals(tx, messages[3]);
    assertNull(messages[1]);
    assertNull(messages[2]);
    Threading.waitForUserCode();
    assertFalse(future.isDone());
    inbound(channels[1], InventoryMessage.with(tx));
    pingAndWait(channels[1]);
    Threading.waitForUserCode();
    assertTrue(future.isDone());
  }

  @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 = FakeTxBuilder.makeSolvedTestBlock(blockStore, address);
    inbound(p1, b1);
    assertNull(outbound(p1));
    assertEquals(FIFTY_COINS, 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, COIN);
    assertFalse(sendResult.broadcastComplete.isDone());
    Transaction t1;
    {
      Message m;
      while (!((m = outbound(p1)) instanceof Transaction)) ;
      t1 = (Transaction) m;
    }
    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);
  }

  @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 = FakeTxBuilder.makeSolvedTestBlock(blockStore, address);
    inbound(p1, b1);
    pingAndWait(p1);
    assertNull(outbound(p1));
    assertEquals(FIFTY_COINS, 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, COIN);
    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;
    {
      peerGroup.waitForJobQueue();
      Message m = outbound(p1);
      // Hack: bloom filters are recalculated asynchronously to sending transactions to avoid lock
      // inversion, so we might or might not get the filter/mempool message first or second.
      while (!(m instanceof Transaction)) m = outbound(p1);
      t1 = (Transaction) m;
    }
    assertNotNull(t1);
    // 49 BTC in change.
    assertEquals(valueOf(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 = FakeTxBuilder.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, valueOf(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. No extra Bloom filter because no change address was
    // needed.
    assertEquals(t3.getHash(), ((Transaction) outbound(p1)).getHash());
  }
}
public class WalletProtobufSerializerTest {
  static final NetworkParameters params = UnitTestParams.get();
  private ECKey myKey;
  private ECKey myWatchedKey;
  private Address myAddress;
  private Wallet myWallet;

  public static String WALLET_DESCRIPTION =
      "The quick brown fox lives in \u4f26\u6566"; // Beijing in Chinese
  private long mScriptCreationTime;

  @Before
  public void setUp() throws Exception {
    BriefLogFormatter.initVerbose();
    Context ctx = new Context(params);
    myWatchedKey = new ECKey();
    myWallet = new Wallet(params);
    myKey = new ECKey();
    myKey.setCreationTimeSeconds(123456789L);
    myWallet.importKey(myKey);
    myAddress = myKey.toAddress(params);
    myWallet = new Wallet(params);
    myWallet.importKey(myKey);
    mScriptCreationTime = new Date().getTime() / 1000 - 1234;
    myWallet.addWatchedAddress(myWatchedKey.toAddress(params), mScriptCreationTime);
    myWallet.setDescription(WALLET_DESCRIPTION);
  }

  @Test
  public void empty() throws Exception {
    // Check the base case of a wallet with one key and no transactions.
    Wallet wallet1 = roundTrip(myWallet);
    assertEquals(0, wallet1.getTransactions(true).size());
    assertEquals(Coin.ZERO, wallet1.getBalance());
    assertArrayEquals(
        myKey.getPubKey(), wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPubKey());
    assertArrayEquals(
        myKey.getPrivKeyBytes(),
        wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPrivKeyBytes());
    assertEquals(
        myKey.getCreationTimeSeconds(),
        wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getCreationTimeSeconds());
    assertEquals(mScriptCreationTime, wallet1.getWatchedScripts().get(0).getCreationTimeSeconds());
    assertEquals(1, wallet1.getWatchedScripts().size());
    assertEquals(
        ScriptBuilder.createOutputScript(myWatchedKey.toAddress(params)),
        wallet1.getWatchedScripts().get(0));
    assertEquals(WALLET_DESCRIPTION, wallet1.getDescription());
  }

  @Test
  public void oneTx() throws Exception {
    // Check basic tx serialization.
    Coin v1 = COIN;
    Transaction t1 = createFakeTx(params, v1, myAddress);
    t1.getConfidence().markBroadcastBy(new PeerAddress(InetAddress.getByName("1.2.3.4")));
    t1.getConfidence().markBroadcastBy(new PeerAddress(InetAddress.getByName("5.6.7.8")));
    t1.getConfidence().setSource(TransactionConfidence.Source.NETWORK);
    myWallet.receivePending(t1, null);
    Wallet wallet1 = roundTrip(myWallet);
    assertEquals(1, wallet1.getTransactions(true).size());
    assertEquals(v1, wallet1.getBalance(Wallet.BalanceType.ESTIMATED));
    Transaction t1copy = wallet1.getTransaction(t1.getHash());
    assertArrayEquals(t1.bitcoinSerialize(), t1copy.bitcoinSerialize());
    assertEquals(2, t1copy.getConfidence().numBroadcastPeers());
    assertEquals(TransactionConfidence.Source.NETWORK, t1copy.getConfidence().getSource());

    Protos.Wallet walletProto = new WalletProtobufSerializer().walletToProto(myWallet);
    assertEquals(Protos.Key.Type.ORIGINAL, walletProto.getKey(0).getType());
    assertEquals(0, walletProto.getExtensionCount());
    assertEquals(1, walletProto.getTransactionCount());
    assertEquals(6, walletProto.getKeyCount());

    Protos.Transaction t1p = walletProto.getTransaction(0);
    assertEquals(0, t1p.getBlockHashCount());
    assertArrayEquals(t1.getHash().getBytes(), t1p.getHash().toByteArray());
    assertEquals(Protos.Transaction.Pool.PENDING, t1p.getPool());
    assertFalse(t1p.hasLockTime());
    assertFalse(t1p.getTransactionInput(0).hasSequence());
    assertArrayEquals(
        t1.getInputs().get(0).getOutpoint().getHash().getBytes(),
        t1p.getTransactionInput(0).getTransactionOutPointHash().toByteArray());
    assertEquals(0, t1p.getTransactionInput(0).getTransactionOutPointIndex());
    assertEquals(t1p.getTransactionOutput(0).getValue(), v1.value);
  }

  @Test
  public void raiseFeeTx() throws Exception {
    // Check basic tx serialization.
    Coin v1 = COIN;
    Transaction t1 = createFakeTx(params, v1, myAddress);
    t1.setPurpose(Purpose.RAISE_FEE);
    myWallet.receivePending(t1, null);
    Wallet wallet1 = roundTrip(myWallet);
    Transaction t1copy = wallet1.getTransaction(t1.getHash());
    assertEquals(Purpose.RAISE_FEE, t1copy.getPurpose());
  }

  @Test
  public void doubleSpend() throws Exception {
    // Check that we can serialize double spends correctly, as this is a slightly tricky case.
    FakeTxBuilder.DoubleSpends doubleSpends =
        FakeTxBuilder.createFakeDoubleSpendTxns(params, myAddress);
    // t1 spends to our wallet.
    myWallet.receivePending(doubleSpends.t1, null);
    // t2 rolls back t1 and spends somewhere else.
    myWallet.receiveFromBlock(doubleSpends.t2, null, BlockChain.NewBlockType.BEST_CHAIN, 0);
    Wallet wallet1 = roundTrip(myWallet);
    assertEquals(1, wallet1.getTransactions(true).size());
    Transaction t1 = wallet1.getTransaction(doubleSpends.t1.getHash());
    assertEquals(ConfidenceType.DEAD, t1.getConfidence().getConfidenceType());
    assertEquals(Coin.ZERO, wallet1.getBalance());

    // TODO: Wallet should store overriding transactions even if they are not wallet-relevant.
    // assertEquals(doubleSpends.t2, t1.getConfidence().getOverridingTransaction());
  }

  @Test
  public void testKeys() throws Exception {
    for (int i = 0; i < 20; i++) {
      myKey = new ECKey();
      myAddress = myKey.toAddress(params);
      myWallet = new Wallet(params);
      myWallet.importKey(myKey);
      Wallet wallet1 = roundTrip(myWallet);
      assertArrayEquals(
          myKey.getPubKey(), wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPubKey());
      assertArrayEquals(
          myKey.getPrivKeyBytes(),
          wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPrivKeyBytes());
    }
  }

  @Test
  public void testLastBlockSeenHash() throws Exception {
    // Test the lastBlockSeenHash field works.

    // LastBlockSeenHash should be empty if never set.
    Wallet wallet = new Wallet(params);
    Protos.Wallet walletProto = new WalletProtobufSerializer().walletToProto(wallet);
    ByteString lastSeenBlockHash = walletProto.getLastSeenBlockHash();
    assertTrue(lastSeenBlockHash.isEmpty());

    // Create a block.
    Block block = params.getDefaultSerializer().makeBlock(BlockTest.blockBytes);
    Sha256Hash blockHash = block.getHash();
    wallet.setLastBlockSeenHash(blockHash);
    wallet.setLastBlockSeenHeight(1);

    // Roundtrip the wallet and check it has stored the blockHash.
    Wallet wallet1 = roundTrip(wallet);
    assertEquals(blockHash, wallet1.getLastBlockSeenHash());
    assertEquals(1, wallet1.getLastBlockSeenHeight());

    // Test the Satoshi genesis block (hash of all zeroes) is roundtripped ok.
    Block genesisBlock = MainNetParams.get().getGenesisBlock();
    wallet.setLastBlockSeenHash(genesisBlock.getHash());
    Wallet wallet2 = roundTrip(wallet);
    assertEquals(genesisBlock.getHash(), wallet2.getLastBlockSeenHash());
  }

  @Test
  public void testAppearedAtChainHeightDepthAndWorkDone() throws Exception {
    // Test the TransactionConfidence appearedAtChainHeight, depth and workDone field are stored.

    BlockChain chain = new BlockChain(params, myWallet, new MemoryBlockStore(params));

    final ArrayList<Transaction> txns = new ArrayList<Transaction>(2);
    myWallet.addEventListener(
        new AbstractWalletEventListener() {
          @Override
          public void onCoinsReceived(
              Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
            txns.add(tx);
          }
        });

    // Start by building two blocks on top of the genesis block.
    Block b1 = params.getGenesisBlock().createNextBlock(myAddress);
    BigInteger work1 = b1.getWork();
    assertTrue(work1.signum() > 0);

    Block b2 = b1.createNextBlock(myAddress);
    BigInteger work2 = b2.getWork();
    assertTrue(work2.signum() > 0);

    assertTrue(chain.add(b1));
    assertTrue(chain.add(b2));

    // We now have the following chain:
    //     genesis -> b1 -> b2

    // Check the transaction confidence levels are correct before wallet roundtrip.
    Threading.waitForUserCode();
    assertEquals(2, txns.size());

    TransactionConfidence confidence0 = txns.get(0).getConfidence();
    TransactionConfidence confidence1 = txns.get(1).getConfidence();

    assertEquals(1, confidence0.getAppearedAtChainHeight());
    assertEquals(2, confidence1.getAppearedAtChainHeight());

    assertEquals(2, confidence0.getDepthInBlocks());
    assertEquals(1, confidence1.getDepthInBlocks());

    // Roundtrip the wallet and check it has stored the depth and workDone.
    Wallet rebornWallet = roundTrip(myWallet);

    Set<Transaction> rebornTxns = rebornWallet.getTransactions(false);
    assertEquals(2, rebornTxns.size());

    // The transactions are not guaranteed to be in the same order so sort them to be in chain
    // height order if required.
    Iterator<Transaction> it = rebornTxns.iterator();
    Transaction txA = it.next();
    Transaction txB = it.next();

    Transaction rebornTx0, rebornTx1;
    if (txA.getConfidence().getAppearedAtChainHeight() == 1) {
      rebornTx0 = txA;
      rebornTx1 = txB;
    } else {
      rebornTx0 = txB;
      rebornTx1 = txA;
    }

    TransactionConfidence rebornConfidence0 = rebornTx0.getConfidence();
    TransactionConfidence rebornConfidence1 = rebornTx1.getConfidence();

    assertEquals(1, rebornConfidence0.getAppearedAtChainHeight());
    assertEquals(2, rebornConfidence1.getAppearedAtChainHeight());

    assertEquals(2, rebornConfidence0.getDepthInBlocks());
    assertEquals(1, rebornConfidence1.getDepthInBlocks());
  }

  private static Wallet roundTrip(Wallet wallet) throws Exception {
    ByteArrayOutputStream output = new ByteArrayOutputStream();
    new WalletProtobufSerializer().writeWallet(wallet, output);
    ByteArrayInputStream test = new ByteArrayInputStream(output.toByteArray());
    assertTrue(WalletProtobufSerializer.isWallet(test));
    ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray());
    return new WalletProtobufSerializer().readWallet(input);
  }

  @Test
  public void testRoundTripNormalWallet() throws Exception {
    Wallet wallet1 = roundTrip(myWallet);
    assertEquals(0, wallet1.getTransactions(true).size());
    assertEquals(Coin.ZERO, wallet1.getBalance());
    assertArrayEquals(
        myKey.getPubKey(), wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPubKey());
    assertArrayEquals(
        myKey.getPrivKeyBytes(),
        wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPrivKeyBytes());
    assertEquals(
        myKey.getCreationTimeSeconds(),
        wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getCreationTimeSeconds());
  }

  @Test
  public void testRoundTripMarriedWallet() throws Exception {
    // create 2-of-2 married wallet
    myWallet = new Wallet(params);
    final DeterministicKeyChain partnerChain = new DeterministicKeyChain(new SecureRandom());
    DeterministicKey partnerKey =
        DeterministicKey.deserializeB58(
            null, partnerChain.getWatchingKey().serializePubB58(params), params);
    MarriedKeyChain chain =
        MarriedKeyChain.builder()
            .random(new SecureRandom())
            .followingKeys(partnerKey)
            .threshold(2)
            .build();
    myWallet.addAndActivateHDChain(chain);

    myAddress = myWallet.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);

    Wallet wallet1 = roundTrip(myWallet);
    assertEquals(0, wallet1.getTransactions(true).size());
    assertEquals(Coin.ZERO, wallet1.getBalance());
    assertEquals(2, wallet1.getActiveKeyChain().getSigsRequiredToSpend());
    assertEquals(myAddress, wallet1.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS));
  }

  @Test
  public void coinbaseTxns() throws Exception {
    // Covers issue 420 where the outpoint index of a coinbase tx input was being mis-serialized.
    Block b =
        params
            .getGenesisBlock()
            .createNextBlockWithCoinbase(
                Block.BLOCK_VERSION_GENESIS,
                myKey.getPubKey(),
                FIFTY_COINS,
                Block.BLOCK_HEIGHT_GENESIS);
    Transaction coinbase = b.getTransactions().get(0);
    assertTrue(coinbase.isCoinBase());
    BlockChain chain = new BlockChain(params, myWallet, new MemoryBlockStore(params));
    assertTrue(chain.add(b));
    // Wallet now has a coinbase tx in it.
    assertEquals(1, myWallet.getTransactions(true).size());
    assertTrue(myWallet.getTransaction(coinbase.getHash()).isCoinBase());
    Wallet wallet2 = roundTrip(myWallet);
    assertEquals(1, wallet2.getTransactions(true).size());
    assertTrue(wallet2.getTransaction(coinbase.getHash()).isCoinBase());
  }

  @Test
  public void tags() throws Exception {
    myWallet.setTag("foo", ByteString.copyFromUtf8("bar"));
    assertEquals("bar", myWallet.getTag("foo").toStringUtf8());
    myWallet = roundTrip(myWallet);
    assertEquals("bar", myWallet.getTag("foo").toStringUtf8());
  }

  @Test
  public void extensions() throws Exception {
    myWallet.addExtension(new FooWalletExtension("com.whatever.required", true));
    Protos.Wallet proto = new WalletProtobufSerializer().walletToProto(myWallet);
    // Initial extension is mandatory: try to read it back into a wallet that doesn't know about it.
    try {
      new WalletProtobufSerializer().readWallet(params, null, proto);
      fail();
    } catch (UnreadableWalletException e) {
      assertTrue(e.getMessage().contains("mandatory"));
    }
    Wallet wallet =
        new WalletProtobufSerializer()
            .readWallet(
                params,
                new WalletExtension[] {new FooWalletExtension("com.whatever.required", true)},
                proto);
    assertTrue(wallet.getExtensions().containsKey("com.whatever.required"));

    // Non-mandatory extensions are ignored if the wallet doesn't know how to read them.
    Wallet wallet2 = new Wallet(params);
    wallet2.addExtension(new FooWalletExtension("com.whatever.optional", false));
    Protos.Wallet proto2 = new WalletProtobufSerializer().walletToProto(wallet2);
    Wallet wallet5 = new WalletProtobufSerializer().readWallet(params, null, proto2);
    assertEquals(0, wallet5.getExtensions().size());
  }

  @Test
  public void extensionsWithError() throws Exception {
    WalletExtension extension =
        new WalletExtension() {
          @Override
          public String getWalletExtensionID() {
            return "test";
          }

          @Override
          public boolean isWalletExtensionMandatory() {
            return false;
          }

          @Override
          public byte[] serializeWalletExtension() {
            return new byte[0];
          }

          @Override
          public void deserializeWalletExtension(Wallet containingWallet, byte[] data)
              throws Exception {
            throw new NullPointerException(); // Something went wrong!
          }
        };
    myWallet.addExtension(extension);
    Protos.Wallet proto = new WalletProtobufSerializer().walletToProto(myWallet);
    Wallet wallet =
        new WalletProtobufSerializer().readWallet(params, new WalletExtension[] {extension}, proto);
    assertEquals(0, wallet.getExtensions().size());
  }

  @Test(expected = UnreadableWalletException.FutureVersion.class)
  public void versions() throws Exception {
    Protos.Wallet.Builder proto =
        Protos.Wallet.newBuilder(new WalletProtobufSerializer().walletToProto(myWallet));
    proto.setVersion(2);
    new WalletProtobufSerializer().readWallet(params, null, proto.build());
  }
}