@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 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 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()); }
private void loadExtensions( Wallet wallet, WalletExtension[] extensionsList, Protos.Wallet walletProto) throws UnreadableWalletException { final Map<String, WalletExtension> extensions = new HashMap<String, WalletExtension>(); for (WalletExtension e : extensionsList) extensions.put(e.getWalletExtensionID(), e); // The Wallet object, if subclassed, might have added some extensions to itself already. In that // case, don't // expect them to be passed in, just fetch them here and don't re-add. extensions.putAll(wallet.getExtensions()); for (Protos.Extension extProto : walletProto.getExtensionList()) { String id = extProto.getId(); WalletExtension extension = extensions.get(id); if (extension == null) { if (extProto.getMandatory()) { if (requireMandatoryExtensions) throw new UnreadableWalletException("Unknown mandatory extension in wallet: " + id); else log.error("Unknown extension in wallet {}, ignoring", id); } } else { log.info("Loading wallet extension {}", id); try { extension.deserializeWalletExtension(wallet, extProto.getData().toByteArray()); wallet.addOrGetExistingExtension(extension); } catch (Exception e) { if (extProto.getMandatory() && requireMandatoryExtensions) throw new UnreadableWalletException( "Could not parse mandatory extension in wallet: " + id); else log.error("Error whilst reading extension {}, ignoring", id, e); } } } }
private boolean handleTx(final Transaction tx) { log.info("tx " + tx.getHashAsString() + " arrived via blueooth"); try { if (wallet.isTransactionRelevant(tx)) { wallet.receivePending(tx, null); handler.post( new Runnable() { @Override public void run() { application.broadcastTransaction(tx); } }); } else { log.info("tx " + tx.getHashAsString() + " irrelevant"); } return true; } catch (final VerificationException x) { log.info("cannot verify tx " + tx.getHashAsString() + " received via bluetooth", x); } return false; }
@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 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 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()); } }
@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 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()); }
private static void populateExtensions(Wallet wallet, Protos.Wallet.Builder walletBuilder) { for (WalletExtension extension : wallet.getExtensions().values()) { Protos.Extension.Builder proto = Protos.Extension.newBuilder(); proto.setId(extension.getWalletExtensionID()); proto.setMandatory(extension.isWalletExtensionMandatory()); proto.setData(ByteString.copyFrom(extension.serializeWalletExtension())); walletBuilder.addExtension(proto); } }
// Create a payment transaction with valueToMe going back to us private synchronized Wallet.SendRequest makeUnsignedChannelContract(Coin valueToMe) { Transaction tx = new Transaction(wallet.getParams()); if (!totalValue.subtract(valueToMe).equals(Coin.ZERO)) { clientOutput.setValue(totalValue.subtract(valueToMe)); tx.addOutput(clientOutput); } tx.addInput(multisigContract.getOutput(0)); return Wallet.SendRequest.forTx(tx); }
private synchronized void updateChannelInWallet() { if (storedServerChannel != null) { storedServerChannel.updateValueToMe(bestValueToMe, bestValueSignature); StoredPaymentChannelServerStates channels = (StoredPaymentChannelServerStates) wallet.getExtensions().get(StoredPaymentChannelServerStates.EXTENSION_ID); channels.updatedChannel(storedServerChannel); } }
@Override public void deserializeWalletExtension(Wallet containingWallet, byte[] data) throws Exception { lock.lock(); try { checkState(this.containingWallet == null || this.containingWallet == containingWallet); this.containingWallet = containingWallet; NetworkParameters params = containingWallet.getParams(); ClientState.StoredClientPaymentChannels states = ClientState.StoredClientPaymentChannels.parseFrom(data); for (ClientState.StoredClientPaymentChannel storedState : states.getChannelsList()) { Transaction refundTransaction = params .getDefaultSerializer() .makeTransaction(storedState.getRefundTransaction().toByteArray()); refundTransaction.getConfidence().setSource(TransactionConfidence.Source.SELF); ECKey myKey = (storedState.getMyKey().isEmpty()) ? containingWallet.findKeyFromPubKey(storedState.getMyPublicKey().toByteArray()) : ECKey.fromPrivate(storedState.getMyKey().toByteArray()); StoredClientChannel channel = new StoredClientChannel( Sha256Hash.wrap(storedState.getId().toByteArray()), params .getDefaultSerializer() .makeTransaction(storedState.getContractTransaction().toByteArray()), refundTransaction, myKey, Coin.valueOf(storedState.getValueToMe()), Coin.valueOf(storedState.getRefundFees()), false); if (storedState.hasCloseTransactionHash()) { Sha256Hash closeTxHash = Sha256Hash.wrap(storedState.getCloseTransactionHash().toByteArray()); channel.close = containingWallet.getTransaction(closeTxHash); } putChannel(channel, false); } } finally { lock.unlock(); } }
@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 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()); }
private void handlePasteClipboard() { final Address address = getAddressFromPrimaryClip(); if (address == null) { final DialogBuilder dialog = new DialogBuilder(activity); dialog.setTitle(R.string.address_book_options_paste_from_clipboard_title); dialog.setMessage(R.string.address_book_options_paste_from_clipboard_invalid); dialog.singleDismissButton(null); dialog.show(); } else if (!wallet.isPubKeyHashMine(address.getHash160())) { EditAddressBookEntryFragment.edit(getFragmentManager(), address); } else { final DialogBuilder dialog = new DialogBuilder(activity); dialog.setTitle(R.string.address_book_options_paste_from_clipboard_title); dialog.setMessage(R.string.address_book_options_paste_from_clipboard_own_address); dialog.singleDismissButton(null); dialog.show(); } }
@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()); }
/** * Stores this channel's state in the wallet as a part of a {@link * StoredPaymentChannelServerStates} wallet extension and keeps it up-to-date each time payment is * incremented. This will be automatically removed when a call to {@link * PaymentChannelServerState#close()} completes successfully. A channel may only be stored after * it has fully opened (ie state == State.READY). * * @param connectedHandler Optional {@link PaymentChannelServer} object that manages this object. * This will set the appropriate pointer in the newly created {@link StoredServerChannel} * before it is committed to wallet. If set, closing the state object will propagate the close * to the handler which can then do a TCP disconnect. */ public synchronized void storeChannelInWallet(@Nullable PaymentChannelServer connectedHandler) { checkState(state == State.READY); if (storedServerChannel != null) return; log.info("Storing state with contract hash {}.", multisigContract.getHash()); StoredPaymentChannelServerStates channels = (StoredPaymentChannelServerStates) wallet.addOrGetExistingExtension( new StoredPaymentChannelServerStates(wallet, broadcaster)); storedServerChannel = new StoredServerChannel( this, multisigContract, clientOutput, refundTransactionUnlockTimeSecs, serverKey, bestValueToMe, bestValueSignature); if (connectedHandler != null) checkState( storedServerChannel.setConnectedHandler(connectedHandler, false) == connectedHandler); channels.putChannel(storedServerChannel); }
@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 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)); }
/** * Notifies the set of stored states that a channel has been updated. Use to notify the wallet of * an update to this wallet extension. */ void updatedChannel(final StoredClientChannel channel) { log.info("Stored client channel {} was updated", channel.hashCode()); containingWallet.addOrUpdateExtension(this); }
/** Returns this extension from the given wallet, or null if no such extension was added. */ @Nullable public static StoredPaymentChannelClientStates getFromWallet(Wallet wallet) { return (StoredPaymentChannelClientStates) wallet.getExtensions().get(EXTENSION_ID); }
/** * Closes this channel and broadcasts the highest value payment transaction on the network. * * <p>This will set the state to {@link State#CLOSED} if the transaction is successfully broadcast * on the network. If we fail to broadcast for some reason, the state is set to {@link * State#ERROR}. * * <p>If the current state is before {@link State#READY} (ie we have not finished initializing the * channel), we simply set the state to {@link State#CLOSED} and let the client handle getting its * refund transaction confirmed. * * @return a future which completes when the provided multisig contract successfully broadcasts, * or throws if the broadcast fails for some reason. Note that if the network simply rejects * the transaction, this future will never complete, a timeout should be used. * @throws InsufficientMoneyException If the payment tx would have cost more in fees to spend than * it is worth. */ public synchronized ListenableFuture<Transaction> close() throws InsufficientMoneyException { if (storedServerChannel != null) { StoredServerChannel temp = storedServerChannel; storedServerChannel = null; StoredPaymentChannelServerStates channels = (StoredPaymentChannelServerStates) wallet.getExtensions().get(StoredPaymentChannelServerStates.EXTENSION_ID); channels.closeChannel( temp); // May call this method again for us (if it wasn't the original caller) if (state.compareTo(State.CLOSING) >= 0) return closedFuture; } if (state.ordinal() < State.READY.ordinal()) { log.error("Attempt to settle channel in state " + state); state = State.CLOSED; closedFuture.set(null); return closedFuture; } if (state != State.READY) { // TODO: What is this codepath for? log.warn("Failed attempt to settle a channel in state " + state); return closedFuture; } Transaction tx = null; try { Wallet.SendRequest req = makeUnsignedChannelContract(bestValueToMe); tx = req.tx; // Provide a throwaway signature so that completeTx won't complain out about unsigned inputs // it doesn't // know how to sign. Note that this signature does actually have to be valid, so we can't use // a dummy // signature to save time, because otherwise completeTx will try to re-sign it to make it // valid and then // die. We could probably add features to the SendRequest API to make this a bit more // efficient. signMultisigInput(tx, Transaction.SigHash.NONE, true); // Let wallet handle adding additional inputs/fee as necessary. req.shuffleOutputs = false; req.missingSigsMode = Wallet.MissingSigsMode.USE_DUMMY_SIG; wallet.completeTx(req); // TODO: Fix things so shuffling is usable. feePaidForPayment = req.tx.getFee(); log.info("Calculated fee is {}", feePaidForPayment); if (feePaidForPayment.compareTo(bestValueToMe) > 0) { final String msg = String.format( Locale.US, "Had to pay more in fees (%s) than the channel was worth (%s)", feePaidForPayment, bestValueToMe); throw new InsufficientMoneyException(feePaidForPayment.subtract(bestValueToMe), msg); } // Now really sign the multisig input. signMultisigInput(tx, Transaction.SigHash.ALL, false); // Some checks that shouldn't be necessary but it can't hurt to check. tx.verify(); // Sanity check syntax. for (TransactionInput input : tx.getInputs()) input.verify(); // Run scripts and ensure it is valid. } catch (InsufficientMoneyException e) { throw e; // Don't fall through. } catch (Exception e) { log.error( "Could not verify self-built tx\nMULTISIG {}\nCLOSE {}", multisigContract, tx != null ? tx : ""); throw new RuntimeException(e); // Should never happen. } state = State.CLOSING; log.info("Closing channel, broadcasting tx {}", tx); // The act of broadcasting the transaction will add it to the wallet. ListenableFuture<Transaction> future = broadcaster.broadcastTransaction(tx).future(); Futures.addCallback( future, new FutureCallback<Transaction>() { @Override public void onSuccess(Transaction transaction) { log.info("TX {} propagated, channel successfully closed.", transaction.getHash()); state = State.CLOSED; closedFuture.set(transaction); } @Override public void onFailure(Throwable throwable) { log.error("Failed to settle channel, could not broadcast", throwable); state = State.ERROR; closedFuture.setException(throwable); } }); return closedFuture; }
/** * Called when the client provides us with a new signature and wishes to increment total payment * by size. Verifies the provided signature and only updates values if everything checks out. If * the new refundSize is not the lowest we have seen, it is simply ignored. * * @param refundSize How many satoshis of the original contract are refunded to the client (the * rest are ours) * @param signatureBytes The new signature spending the multi-sig contract to a new payment * transaction * @throws VerificationException If the signature does not verify or size is out of range (incl * being rejected by the network as dust). * @return true if there is more value left on the channel, false if it is now fully used up. */ public synchronized boolean incrementPayment(Coin refundSize, byte[] signatureBytes) throws VerificationException, ValueOutOfRangeException, InsufficientMoneyException { checkState(state == State.READY); checkNotNull(refundSize); checkNotNull(signatureBytes); TransactionSignature signature = TransactionSignature.decodeFromBitcoin(signatureBytes, true); // We allow snapping to zero for the payment amount because it's treated specially later, but // not less than // the dust level because that would prevent the transaction from being relayed/mined. final boolean fullyUsedUp = refundSize.equals(Coin.ZERO); if (refundSize.compareTo(clientOutput.getMinNonDustValue()) < 0 && !fullyUsedUp) throw new ValueOutOfRangeException( "Attempt to refund negative value or value too small to be accepted by the network"); Coin newValueToMe = totalValue.subtract(refundSize); if (newValueToMe.signum() < 0) throw new ValueOutOfRangeException("Attempt to refund more than the contract allows."); if (newValueToMe.compareTo(bestValueToMe) < 0) throw new ValueOutOfRangeException("Attempt to roll back payment on the channel."); // Get the wallet's copy of the multisigContract (ie with confidence information), if this is // null, the wallet // was not connected to the peergroup when the contract was broadcast (which may cause issues // down the road, and // disables our double-spend check next) Transaction walletContract = wallet.getTransaction(multisigContract.getHash()); checkNotNull( walletContract, "Wallet did not contain multisig contract {} after state was marked READY", multisigContract.getHash()); // Note that we check for DEAD state here, but this test is essentially useless in production // because we will // miss most double-spends due to bloom filtering right now anyway. This will eventually fixed // by network-wide // double-spend notifications, so we just wait instead of attempting to add all dependant // outpoints to our bloom // filters (and probably missing lots of edge-cases). if (walletContract.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.DEAD) { close(); throw new VerificationException("Multisig contract was double-spent"); } Transaction.SigHash mode; // If the client doesn't want anything back, they shouldn't sign any outputs at all. if (fullyUsedUp) mode = Transaction.SigHash.NONE; else mode = Transaction.SigHash.SINGLE; if (signature.sigHashMode() != mode || !signature.anyoneCanPay()) throw new VerificationException( "New payment signature was not signed with the right SIGHASH flags."); Wallet.SendRequest req = makeUnsignedChannelContract(newValueToMe); // Now check the signature is correct. // Note that the client must sign with SIGHASH_{SINGLE/NONE} | SIGHASH_ANYONECANPAY to allow us // to add additional // inputs (in case we need to add significant fee, or something...) and any outputs we want to // pay to. Sha256Hash sighash = req.tx.hashForSignature(0, multisigScript, mode, true); if (!clientKey.verify(sighash, signature)) throw new VerificationException("Signature does not verify on tx\n" + req.tx); bestValueToMe = newValueToMe; bestValueSignature = signatureBytes; updateChannelInWallet(); return !fullyUsedUp; }
@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()); }
/** * function that updates debit/credit/balance/price in fiat and Transaction History table * * @param services */ private void updateWalletsSummary(List<WalletService> services) { Coin totalDebit = Coin.ZERO; Coin totalCredit = Coin.ZERO; Coin totalBalance = Coin.ZERO; double priceInFiat = 0.00d; String confidence = ""; // update debit/credit/balance and price in fiat List<TransactionWrapper> transactions = new ArrayList<>(); for (WalletService service : services) { try { Wallet wallet = service.getWallet(); totalBalance = totalBalance.add(wallet.getBalance()); for (Transaction trx : wallet.getTransactionsByTime()) { if (trx.getConfidence().equals(TransactionConfidence.ConfidenceType.DEAD)) continue; Coin amount = trx.getValue(wallet); if (amount.isPositive()) { totalCredit = totalCredit.add(amount); } else { totalDebit = totalDebit.add(amount); } transactions.add(new TransactionWrapper(trx, wallet, amount)); } } catch (Exception e) { logger.error("Unable to update wallet details"); } } pnlDashboardStats.setTotalBalance(MonetaryFormat.BTC.noCode().format(totalBalance).toString()); // pnlDashboardStats.setTotalDebit(MonetaryFormat.BTC.noCode().format(totalDebit).toString()); // pnlDashboardStats.setTotalCredit(MonetaryFormat.BTC.noCode().format(totalCredit).toString()); priceInFiat = Double.valueOf(MonetaryFormat.BTC.noCode().format(totalBalance).toString()); priceInFiat *= BitcoinCurrencyRateApi.get().getCurrentRateValue(); pnlDashboardStats.setPriceInFiat( String.format("%.2f", priceInFiat), "", ConfigManager.config().getSelectedCurrency()); pnlDashboardStats.setExchangeRate( ConfigManager.config().getSelectedCurrency(), String.format("%.2f", BitcoinCurrencyRateApi.get().getCurrentRateValue())); Collections.sort( transactions, new Comparator<TransactionWrapper>() { @Override public int compare(TransactionWrapper o1, TransactionWrapper o2) { return o2.getTransaction() .getUpdateTime() .compareTo(o1.getTransaction().getUpdateTime()); } }); // update Transaction History table DefaultTableModel model = (DefaultTableModel) tblTransactions.getModel(); model.setRowCount(0); for (TransactionWrapper wrapper : transactions) { Transaction transaction = wrapper.getTransaction(); if (transaction.getConfidence().getDepthInBlocks() > 6) confidence = "<html>6<sup>+</sup></html>"; else confidence = transaction.getConfidence().getDepthInBlocks() + ""; Coin amount = wrapper.getAmount(); Coin fee = transaction.getFee(); String amountString = MonetaryFormat.BTC.noCode().format(amount).toString(); String feeString = fee != null ? MonetaryFormat.BTC.noCode().format(fee).toString() : "0.00"; Address from = transaction.getInput(0).getFromAddress(); Address to = transaction .getOutput(0) .getAddressFromP2PKHScript(wrapper.getWallet().getNetworkParameters()); boolean credit = amount.isPositive(); model.addRow( new Object[] { Utils.formatTransactionDate(transaction.getUpdateTime()), from, to, credit ? "Credit" : "Debit", amountString, feeString, "", confidence }); } Coin balanceAfter = Coin.ZERO; for (int index = transactions.size() - 1; index >= 0; index--) { balanceAfter = balanceAfter.add(Coin.parseCoin((String) model.getValueAt(index, 4))); model.setValueAt(MonetaryFormat.BTC.noCode().format(balanceAfter).toString(), index, 6); } }
/** * Loads wallet data from the given protocol buffer and inserts it into the given Wallet object. * This is primarily useful when you wish to pre-register extension objects. Note that if loading * fails the provided Wallet object may be in an indeterminate state and should be thrown away. * * <p>A wallet can be unreadable for various reasons, such as inability to open the file, corrupt * data, internally inconsistent data, a wallet extension marked as mandatory that cannot be * handled and so on. You should always handle {@link UnreadableWalletException} and communicate * failure to the user in an appropriate manner. * * @throws UnreadableWalletException thrown in various error conditions (see description). */ public Wallet readWallet( NetworkParameters params, @Nullable WalletExtension[] extensions, Protos.Wallet walletProto) throws UnreadableWalletException { if (walletProto.getVersion() > 1) throw new UnreadableWalletException.FutureVersion(); if (!walletProto.getNetworkIdentifier().equals(params.getId())) throw new UnreadableWalletException.WrongNetwork(); int sigsRequiredToSpend = walletProto.getSigsRequiredToSpend(); // Read the scrypt parameters that specify how encryption and decryption is performed. KeyChainGroup chain; if (walletProto.hasEncryptionParameters()) { Protos.ScryptParameters encryptionParameters = walletProto.getEncryptionParameters(); final KeyCrypterScrypt keyCrypter = new KeyCrypterScrypt(encryptionParameters); chain = KeyChainGroup.fromProtobufEncrypted( params, walletProto.getKeyList(), sigsRequiredToSpend, keyCrypter); } else { chain = KeyChainGroup.fromProtobufUnencrypted( params, walletProto.getKeyList(), sigsRequiredToSpend); } Wallet wallet = factory.create(params, chain); List<Script> scripts = Lists.newArrayList(); for (Protos.Script protoScript : walletProto.getWatchedScriptList()) { try { Script script = new Script( protoScript.getProgram().toByteArray(), protoScript.getCreationTimestamp() / 1000); scripts.add(script); } catch (ScriptException e) { throw new UnreadableWalletException("Unparseable script in wallet"); } } wallet.addWatchedScripts(scripts); if (walletProto.hasDescription()) { wallet.setDescription(walletProto.getDescription()); } // Read all transactions and insert into the txMap. for (Protos.Transaction txProto : walletProto.getTransactionList()) { readTransaction(txProto, wallet.getParams()); } // Update transaction outputs to point to inputs that spend them for (Protos.Transaction txProto : walletProto.getTransactionList()) { WalletTransaction wtx = connectTransactionOutputs(txProto); wallet.addWalletTransaction(wtx); } // Update the lastBlockSeenHash. if (!walletProto.hasLastSeenBlockHash()) { wallet.setLastBlockSeenHash(null); } else { wallet.setLastBlockSeenHash(byteStringToHash(walletProto.getLastSeenBlockHash())); } if (!walletProto.hasLastSeenBlockHeight()) { wallet.setLastBlockSeenHeight(-1); } else { wallet.setLastBlockSeenHeight(walletProto.getLastSeenBlockHeight()); } // Will default to zero if not present. wallet.setLastBlockSeenTimeSecs(walletProto.getLastSeenBlockTimeSecs()); if (walletProto.hasKeyRotationTime()) { wallet.setKeyRotationTime(new Date(walletProto.getKeyRotationTime() * 1000)); } loadExtensions(wallet, extensions != null ? extensions : new WalletExtension[0], walletProto); for (Protos.Tag tag : walletProto.getTagsList()) { wallet.setTag(tag.getTag(), tag.getData()); } for (Protos.TransactionSigner signerProto : walletProto.getTransactionSignersList()) { try { Class signerClass = Class.forName(signerProto.getClassName()); TransactionSigner signer = (TransactionSigner) signerClass.newInstance(); signer.deserialize(signerProto.getData().toByteArray()); wallet.addTransactionSigner(signer); } catch (Exception e) { throw new UnreadableWalletException( "Unable to deserialize TransactionSigner instance: " + signerProto.getClassName(), e); } } if (walletProto.hasVersion()) { wallet.setVersion(walletProto.getVersion()); } // Make sure the object can be re-used to read another wallet without corruption. txMap.clear(); return wallet; }
public static void main(String[] args) throws Exception { NetworkParameters params = TestNet3Params.get(); // Bitcoinj supports hierarchical deterministic wallets (or "HD Wallets"): // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki // HD wallets allow you to restore your wallet simply from a root seed. This seed can be // represented using a short mnemonic sentence as described in BIP 39: // https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki // Here we restore our wallet from a seed with no passphrase. Also have a look at the // BackupToMnemonicSeed.java example that shows how to backup a wallet by creating a mnemonic // sentence. String seedCode = "yard impulse luxury drive today throw farm pepper survey wreck glass federal"; String passphrase = ""; Long creationtime = 1409478661L; DeterministicSeed seed = new DeterministicSeed(seedCode, null, passphrase, creationtime); // The wallet class provides a easy fromSeed() function that loads a new wallet from a given // seed. Wallet wallet = Wallet.fromSeed(params, seed); // Because we are importing an existing wallet which might already have transactions we must // re-download the blockchain to make the wallet picks up these transactions // You can find some information about this in the guides: // https://bitcoinj.github.io/working-with-the-wallet#setup // To do this we clear the transactions of the wallet and delete a possible existing blockchain // file before we download the blockchain again further down. System.out.println(wallet.toString()); wallet.clearTransactions(0); File chainFile = new File("restore-from-seed.spvchain"); if (chainFile.exists()) { chainFile.delete(); } // Setting up the BlochChain, the BlocksStore and connecting to the network. SPVBlockStore chainStore = new SPVBlockStore(params, chainFile); BlockChain chain = new BlockChain(params, chainStore); PeerGroup peers = new PeerGroup(params, chain); peers.addPeerDiscovery(new DnsDiscovery(params)); // Now we need to hook the wallet up to the blockchain and the peers. This registers event // listeners that notify our wallet about new transactions. chain.addWallet(wallet); peers.addWallet(wallet); DownloadProgressTracker bListener = new DownloadProgressTracker() { @Override public void doneDownload() { System.out.println("blockchain downloaded"); } }; // Now we re-download the blockchain. This replays the chain into the wallet. Once this is // completed our wallet should know of all its transactions and print the correct balance. peers.start(); peers.startBlockChainDownload(bListener); bListener.await(); // Print a debug message with the details about the wallet. The correct balance should now be // displayed. System.out.println(wallet.toString()); // shutting down again peers.stop(); }
/** * Converts the given wallet to the object representation of the protocol buffers. This can be * modified, or additional data fields set, before serialization takes place. */ public Protos.Wallet walletToProto(Wallet wallet) { Protos.Wallet.Builder walletBuilder = Protos.Wallet.newBuilder(); walletBuilder.setNetworkIdentifier(wallet.getNetworkParameters().getId()); if (wallet.getDescription() != null) { walletBuilder.setDescription(wallet.getDescription()); } for (WalletTransaction wtx : wallet.getWalletTransactions()) { Protos.Transaction txProto = makeTxProto(wtx); walletBuilder.addTransaction(txProto); } walletBuilder.addAllKey(wallet.serializeKeychainToProtobuf()); for (Script script : wallet.getWatchedScripts()) { Protos.Script protoScript = Protos.Script.newBuilder() .setProgram(ByteString.copyFrom(script.getProgram())) .setCreationTimestamp(script.getCreationTimeSeconds() * 1000) .build(); walletBuilder.addWatchedScript(protoScript); } // Populate the lastSeenBlockHash field. Sha256Hash lastSeenBlockHash = wallet.getLastBlockSeenHash(); if (lastSeenBlockHash != null) { walletBuilder.setLastSeenBlockHash(hashToByteString(lastSeenBlockHash)); walletBuilder.setLastSeenBlockHeight(wallet.getLastBlockSeenHeight()); } if (wallet.getLastBlockSeenTimeSecs() > 0) walletBuilder.setLastSeenBlockTimeSecs(wallet.getLastBlockSeenTimeSecs()); // Populate the scrypt parameters. KeyCrypter keyCrypter = wallet.getKeyCrypter(); if (keyCrypter == null) { // The wallet is unencrypted. walletBuilder.setEncryptionType(EncryptionType.UNENCRYPTED); } else { // The wallet is encrypted. walletBuilder.setEncryptionType(keyCrypter.getUnderstoodEncryptionType()); if (keyCrypter instanceof KeyCrypterScrypt) { KeyCrypterScrypt keyCrypterScrypt = (KeyCrypterScrypt) keyCrypter; walletBuilder.setEncryptionParameters(keyCrypterScrypt.getScryptParameters()); } else { // Some other form of encryption has been specified that we do not know how to persist. throw new RuntimeException( "The wallet has encryption of type '" + keyCrypter.getUnderstoodEncryptionType() + "' but this WalletProtobufSerializer does not know how to persist this."); } } if (wallet.getKeyRotationTime() != null) { long timeSecs = wallet.getKeyRotationTime().getTime() / 1000; walletBuilder.setKeyRotationTime(timeSecs); } populateExtensions(wallet, walletBuilder); for (Map.Entry<String, ByteString> entry : wallet.getTags().entrySet()) { Protos.Tag.Builder tag = Protos.Tag.newBuilder().setTag(entry.getKey()).setData(entry.getValue()); walletBuilder.addTags(tag); } for (TransactionSigner signer : wallet.getTransactionSigners()) { // do not serialize LocalTransactionSigner as it's being added implicitly if (signer instanceof LocalTransactionSigner) continue; Protos.TransactionSigner.Builder protoSigner = Protos.TransactionSigner.newBuilder(); protoSigner.setClassName(signer.getClass().getName()); protoSigner.setData(ByteString.copyFrom(signer.serialize())); walletBuilder.addTransactionSigners(protoSigner); } walletBuilder.setSigsRequiredToSpend(wallet.getSigsRequiredToSpend()); // Populate the wallet version. walletBuilder.setVersion(wallet.getVersion()); return walletBuilder.build(); }