@Override public CoinSelection select(Coin target, List<TransactionOutput> candidates) { ArrayList<TransactionOutput> selected = new ArrayList<TransactionOutput>(); // Sort the inputs by age*value so we get the highest "coindays" spent. // TODO: Consider changing the wallets internal format to track just outputs and keep them // ordered. ArrayList<TransactionOutput> sortedOutputs = new ArrayList<TransactionOutput>(candidates); // When calculating the wallet balance, we may be asked to select all possible coins, if so, // avoid sorting // them in order to improve performance. // TODO: Take in network parameters when instanatiated, and then test against the current // network. Or just have a boolean parameter for "give me everything" if (!target.equals(NetworkParameters.MAX_MONEY)) { sortOutputs(sortedOutputs); } // Now iterate over the sorted outputs until we have got as close to the target as possible or a // little // bit over (excessive value will be change). long total = 0; for (TransactionOutput output : sortedOutputs) { if (total >= target.value) break; // Only pick chain-included transactions, or transactions that are ours and pending. if (!shouldSelect(output.getParentTransaction())) continue; selected.add(output); total += output.getValue().value; } // Total may be lower than target here, if the given candidates were insufficient to create to // requested // transaction. return new CoinSelection(Coin.valueOf(total), selected); }
private void parsePaymentRequest(Protos.PaymentRequest request) throws PaymentProtocolException { try { if (request == null) throw new PaymentProtocolException("request cannot be null"); if (request.getPaymentDetailsVersion() != 1) throw new PaymentProtocolException.InvalidVersion( "Version 1 required. Received version " + request.getPaymentDetailsVersion()); paymentRequest = request; if (!request.hasSerializedPaymentDetails()) throw new PaymentProtocolException("No PaymentDetails"); paymentDetails = Protos.PaymentDetails.newBuilder() .mergeFrom(request.getSerializedPaymentDetails()) .build(); if (paymentDetails == null) throw new PaymentProtocolException("Invalid PaymentDetails"); if (!paymentDetails.hasNetwork()) params = MainNetParams.get(); else params = NetworkParameters.fromPmtProtocolID(paymentDetails.getNetwork()); if (params == null) throw new PaymentProtocolException.InvalidNetwork( "Invalid network " + paymentDetails.getNetwork()); if (paymentDetails.getOutputsCount() < 1) throw new PaymentProtocolException.InvalidOutputs("No outputs"); for (Protos.Output output : paymentDetails.getOutputsList()) { if (output.hasAmount()) totalValue = totalValue.add(Coin.valueOf(output.getAmount())); } // This won't ever happen in practice. It would only happen if the user provided outputs // that are obviously invalid. Still, we don't want to silently overflow. if (params.hasMaxMoney() && totalValue.compareTo(params.getMaxMoney()) > 0) throw new PaymentProtocolException.InvalidOutputs("The outputs are way too big."); } catch (InvalidProtocolBufferException e) { throw new PaymentProtocolException(e); } }
private void addContent() { addMultilineLabel( gridPane, ++rowIndex, "Please use that only in emergency case if you cannot access your fund from the UI.\n" + "Before you use this tool, you should backup your data directory. After you have successfully transferred your wallet balance, remove" + " the db directory inside the data directory to start with a newly created and consistent data structure.\n" + "Please make a bug report on Github so that we can investigate what was causing the problem.", 10); Coin totalBalance = walletService.getAvailableBalance(); boolean isBalanceSufficient = totalBalance.compareTo(FeePolicy.TX_FEE) >= 0; addressTextField = addLabelTextField( gridPane, ++rowIndex, "Your available wallet balance:", formatter.formatCoinWithCode(totalBalance), 10) .second; Tuple2<Label, InputTextField> tuple = addLabelInputTextField(gridPane, ++rowIndex, "Your destination address:"); addressInputTextField = tuple.second; emptyWalletButton = new Button("Empty wallet"); emptyWalletButton.setDefaultButton(isBalanceSufficient); emptyWalletButton.setDisable( !isBalanceSufficient && addressInputTextField.getText().length() > 0); emptyWalletButton.setOnAction( e -> { if (addressInputTextField.getText().length() > 0 && isBalanceSufficient) { if (walletService.getWallet().isEncrypted()) { walletPasswordPopup .onClose(() -> blurAgain()) .onAesKey(aesKey -> doEmptyWallet(aesKey)) .show(); } else { doEmptyWallet(null); } } }); closeButton = new Button("Cancel"); closeButton.setOnAction( e -> { hide(); closeHandlerOptional.ifPresent(closeHandler -> closeHandler.run()); }); HBox hBox = new HBox(); hBox.setSpacing(10); GridPane.setRowIndex(hBox, ++rowIndex); GridPane.setColumnIndex(hBox, 1); hBox.getChildren().addAll(emptyWalletButton, closeButton); gridPane.getChildren().add(hBox); GridPane.setMargin(hBox, new Insets(10, 0, 0, 0)); }
// 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); }
@Test public void testGetOpenTransactionOutputs() throws Exception { final int UNDOABLE_BLOCKS_STORED = 10; store = createStore(params, UNDOABLE_BLOCKS_STORED); chain = new FullPrunedBlockChain(params, store); // Check that we aren't accidentally leaving any references // to the full StoredUndoableBlock's lying around (ie memory leaks) ECKey outKey = new ECKey(); int height = 1; // Build some blocks on genesis block to create a spendable output Block rollingBlock = params .getGenesisBlock() .createNextBlockWithCoinbase(Block.BLOCK_VERSION_GENESIS, outKey.getPubKey(), height++); chain.add(rollingBlock); Transaction transaction = rollingBlock.getTransactions().get(0); TransactionOutPoint spendableOutput = new TransactionOutPoint(params, 0, transaction.getHash()); byte[] spendableOutputScriptPubKey = transaction.getOutputs().get(0).getScriptBytes(); for (int i = 1; i < params.getSpendableCoinbaseDepth(); i++) { rollingBlock = rollingBlock.createNextBlockWithCoinbase( Block.BLOCK_VERSION_GENESIS, outKey.getPubKey(), height++); chain.add(rollingBlock); } rollingBlock = rollingBlock.createNextBlock(null); // Create bitcoin spend of 1 BTC. ECKey toKey = new ECKey(); Coin amount = Coin.valueOf(100000000); Address address = new Address(params, toKey.getPubKeyHash()); Coin totalAmount = Coin.ZERO; Transaction t = new Transaction(params); t.addOutput(new TransactionOutput(params, t, amount, toKey)); t.addSignedInput(spendableOutput, new Script(spendableOutputScriptPubKey), outKey); rollingBlock.addTransaction(t); rollingBlock.solve(); chain.add(rollingBlock); totalAmount = totalAmount.add(amount); List<UTXO> outputs = store.getOpenTransactionOutputs(Lists.newArrayList(address)); assertNotNull(outputs); assertEquals("Wrong Number of Outputs", 1, outputs.size()); UTXO output = outputs.get(0); assertEquals("The address is not equal", address.toString(), output.getAddress()); assertEquals("The amount is not equal", totalAmount, output.getValue()); outputs = null; output = null; try { store.close(); } catch (Exception e) { } }
/** * @param params The network parameters or null * @param nameValuePairTokens The tokens representing the name value pairs (assumed to be * separated by '=' e.g. 'amount=0.2') */ private void parseParameters( @Nullable NetworkParameters params, String addressToken, String[] nameValuePairTokens) throws BitcoinURIParseException { // Attempt to decode the rest of the tokens into a parameter map. for (String nameValuePairToken : nameValuePairTokens) { final int sepIndex = nameValuePairToken.indexOf('='); if (sepIndex == -1) throw new BitcoinURIParseException( "Malformed Bitcoin URI - no separator in '" + nameValuePairToken + "'"); if (sepIndex == 0) throw new BitcoinURIParseException( "Malformed Bitcoin URI - empty name '" + nameValuePairToken + "'"); final String nameToken = nameValuePairToken.substring(0, sepIndex).toLowerCase(Locale.ENGLISH); final String valueToken = nameValuePairToken.substring(sepIndex + 1); // Parse the amount. if (FIELD_AMOUNT.equals(nameToken)) { // Decode the amount (contains an optional decimal component to 8dp). try { Coin amount = Coin.parseCoin(valueToken); if (params != null && amount.isGreaterThan(params.getMaxMoney())) throw new BitcoinURIParseException("Max number of coins exceeded"); if (amount.signum() < 0) throw new ArithmeticException("Negative coins specified"); putWithValidation(FIELD_AMOUNT, amount); } catch (IllegalArgumentException e) { throw new OptionalFieldValidationException( String.format(Locale.US, "'%s' is not a valid amount", valueToken), e); } catch (ArithmeticException e) { throw new OptionalFieldValidationException( String.format(Locale.US, "'%s' has too many decimal places", valueToken), e); } } else { if (nameToken.startsWith("req-")) { // A required parameter that we do not know about. throw new RequiredFieldValidationException( "'" + nameToken + "' is required but not known, this URI is not valid"); } else { // Known fields and unknown parameters that are optional. try { if (valueToken.length() > 0) putWithValidation(nameToken, URLDecoder.decode(valueToken, "UTF-8")); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); // can't happen } } } } // Note to the future: when you want to implement 'req-expires' have a look at commit // 410a53791841 // which had it in. }
/** * Returns the outstanding amount of money sent back to us for all channels to this server added * together. */ public Coin getBalanceForServer(Sha256Hash id) { Coin balance = Coin.ZERO; lock.lock(); try { Set<StoredClientChannel> setChannels = mapChannels.get(id); for (StoredClientChannel channel : setChannels) { synchronized (channel) { if (channel.close != null) continue; balance = balance.add(channel.valueToMe); } } return balance; } finally { lock.unlock(); } }
/** Returns a {@link Wallet.SendRequest} suitable for broadcasting to the network. */ public Wallet.SendRequest getSendRequest() { Transaction tx = new Transaction(params); for (Protos.Output output : paymentDetails.getOutputsList()) tx.addOutput( new TransactionOutput( params, tx, Coin.valueOf(output.getAmount()), output.getScript().toByteArray())); return Wallet.SendRequest.forTx(tx).fromPaymentDetails(paymentDetails); }
/** * Simple Bitcoin URI builder using known good fields. * * @param params The network parameters that determine which network the URI is for. * @param address The Bitcoin address * @param amount The amount * @param label A label * @param message A message * @return A String containing the Bitcoin URI */ public static String convertToBitcoinURI( NetworkParameters params, String address, @Nullable Coin amount, @Nullable String label, @Nullable String message) { checkNotNull(params); checkNotNull(address); if (amount != null && amount.signum() < 0) { throw new IllegalArgumentException("Coin must be positive"); } StringBuilder builder = new StringBuilder(); String scheme = params.getUriScheme(); builder.append(scheme).append(":").append(address); boolean questionMarkHasBeenOutput = false; if (amount != null) { builder.append(QUESTION_MARK_SEPARATOR).append(FIELD_AMOUNT).append("="); builder.append(amount.toPlainString()); questionMarkHasBeenOutput = true; } if (label != null && !"".equals(label)) { if (questionMarkHasBeenOutput) { builder.append(AMPERSAND_SEPARATOR); } else { builder.append(QUESTION_MARK_SEPARATOR); questionMarkHasBeenOutput = true; } builder.append(FIELD_LABEL).append("=").append(encodeURLString(label)); } if (message != null && !"".equals(message)) { if (questionMarkHasBeenOutput) { builder.append(AMPERSAND_SEPARATOR); } else { builder.append(QUESTION_MARK_SEPARATOR); } builder.append(FIELD_MESSAGE).append("=").append(encodeURLString(message)); } return builder.toString(); }
/** Returns the outputs of the payment request. */ public List<PaymentProtocol.Output> getOutputs() { List<PaymentProtocol.Output> outputs = new ArrayList<PaymentProtocol.Output>(paymentDetails.getOutputsCount()); for (Protos.Output output : paymentDetails.getOutputsList()) { Coin amount = output.hasAmount() ? Coin.valueOf(output.getAmount()) : null; outputs.add(new PaymentProtocol.Output(amount, output.getScript().toByteArray())); } return outputs; }
private void tryOpenDispute(boolean isSupportTicket) { if (trade != null) { Transaction depositTx = trade.getDepositTx(); if (depositTx != null) { doOpenDispute(isSupportTicket, trade.getDepositTx()); } else { log.warn("Trade.depositTx is null. We try to find the tx in our wallet."); List<Transaction> candidates = new ArrayList<>(); List<Transaction> transactions = walletService.getWallet().getRecentTransactions(100, true); transactions .stream() .forEach( transaction -> { Coin valueSentFromMe = transaction.getValueSentFromMe(walletService.getWallet()); if (!valueSentFromMe.isZero()) { // spending tx for (TransactionOutput transactionOutput : transaction.getOutputs()) { if (!transactionOutput.isMine(walletService.getWallet())) { if (transactionOutput.getScriptPubKey().isPayToScriptHash()) { // MS tx candidates.add(transaction); } } } } }); if (candidates.size() == 1) doOpenDispute(isSupportTicket, candidates.get(0)); else if (candidates.size() > 1) new SelectDepositTxPopup() .transactions(candidates) .onSelect( transaction -> { doOpenDispute(isSupportTicket, transaction); }) .closeButtonText("Cancel") .show(); else log.error("Trade.depositTx is null and we did not find any MultiSig transaction."); } } else { log.error("Trade is null"); } }
@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(); } }
@Override protected void run() { try { runInterceptHook(); checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null"); Coin sellerPayoutAmount = FeePolicy.SECURITY_DEPOSIT; Coin buyerPayoutAmount = sellerPayoutAmount.add(trade.getTradeAmount()); // We use the sellers LastBlockSeenHeight, which might be different to the buyers one. // If lock time is 0 we set lockTimeAsBlockHeight to 0 to mark it as "not set". // In the tradewallet we apply the locktime only if it is set, otherwise we use the default // values for // transaction locktime and sequence number long lockTime = trade.getOffer().getPaymentMethod().getLockTime(); long lockTimeAsBlockHeight = 0; if (lockTime > 0) lockTimeAsBlockHeight = processModel.getTradeWalletService().getLastBlockSeenHeight() + lockTime; trade.setLockTimeAsBlockHeight(lockTimeAsBlockHeight); byte[] payoutTxSignature = processModel .getTradeWalletService() .sellerSignsPayoutTx( trade.getDepositTx(), buyerPayoutAmount, sellerPayoutAmount, processModel.tradingPeer.getPayoutAddressString(), processModel.getAddressEntry(), lockTimeAsBlockHeight, processModel.tradingPeer.getTradeWalletPubKey(), processModel.getTradeWalletPubKey(), processModel.getArbitratorPubKey(trade.getArbitratorAddress())); processModel.setPayoutTxSignature(payoutTxSignature); complete(); } catch (Throwable t) { failed(t); } }
/** * Create a standard pay to address output for usage in {@link #createPaymentRequest} and {@link * #createPaymentMessage}. * * @param amount amount to pay, or null * @param address address to pay to * @return output */ public static Protos.Output createPayToAddressOutput(@Nullable Coin amount, Address address) { Protos.Output.Builder output = Protos.Output.newBuilder(); if (amount != null) { final NetworkParameters params = address.getParameters(); if (params.hasMaxMoney() && amount.compareTo(params.getMaxMoney()) > 0) throw new IllegalArgumentException("Amount too big: " + amount); output.setAmount(amount.value); } else { output.setAmount(0); } output.setScript(ByteString.copyFrom(ScriptBuilder.createOutputScript(address).getProgram())); return output.build(); }
public Coin getRecommendedFee() { String url = "https://bitcoinfees.21.co/api/v1/fees/recommended"; URL obj; try { obj = new URL(url); JSONTokener tokener; HttpURLConnection con = (HttpURLConnection) obj.openConnection(); con.setRequestMethod("GET"); // set user header to prevent 403 con.setRequestProperty("User-Agent", "Chrome/5.0"); tokener = new JSONTokener(con.getInputStream()); JSONObject root = new JSONObject(tokener); return Coin.valueOf(Long.valueOf(root.get("fastestFee").toString())); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e); } }
public Transaction send(String destinationAddress, long amountSatoshis) throws InsufficientMoneyException { Address addressj; try { addressj = new Address(params, destinationAddress); } catch (AddressFormatException e) { e.printStackTrace(); throw new RuntimeException(e); } Coin amount = Coin.valueOf(amountSatoshis); // create a tx to destinationAddress of amount and broadcast by wallets peergroup Wallet.SendResult sendResult = getKit().wallet().sendCoins(kit.peerGroup(), addressj, amount); try { return sendResult.broadcastComplete.get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); throw new RuntimeException(e); } }
public String sendOffline(String destinationAddress, long amountSatoshis) throws InsufficientMoneyException { Address addressj; try { addressj = new Address(params, destinationAddress); } catch (AddressFormatException e) { e.printStackTrace(); throw new RuntimeException(e); } Coin amount = Coin.valueOf(amountSatoshis); // create a SendRequest of amount to destinationAddress Wallet.SendRequest sendRequest = Wallet.SendRequest.to(addressj, amount); // set dynamic fee sendRequest.feePerKb = getRecommendedFee(); // complete & sign tx kit.wallet().completeTx(sendRequest); kit.wallet().signTransaction(sendRequest); // return tx bytes as hex encoded String return Hex.encodeHexString(sendRequest.tx.bitcoinSerialize()); }
PaymentChannelServerState( StoredServerChannel storedServerChannel, Wallet wallet, TransactionBroadcaster broadcaster) throws VerificationException { synchronized (storedServerChannel) { this.wallet = checkNotNull(wallet); this.broadcaster = checkNotNull(broadcaster); this.multisigContract = checkNotNull(storedServerChannel.contract); this.multisigScript = multisigContract.getOutput(0).getScriptPubKey(); this.clientKey = ECKey.fromPublicOnly(multisigScript.getChunks().get(1).data); this.clientOutput = checkNotNull(storedServerChannel.clientOutput); this.refundTransactionUnlockTimeSecs = storedServerChannel.refundTransactionUnlockTimeSecs; this.serverKey = checkNotNull(storedServerChannel.myKey); this.totalValue = multisigContract.getOutput(0).getValue(); this.bestValueToMe = checkNotNull(storedServerChannel.bestValueToMe); this.bestValueSignature = storedServerChannel.bestValueSignature; checkArgument(bestValueToMe.equals(Coin.ZERO) || bestValueSignature != null); this.storedServerChannel = storedServerChannel; storedServerChannel.state = this; this.state = State.READY; } }
@Test public void testUTXOProviderWithWallet() throws Exception { final int UNDOABLE_BLOCKS_STORED = 10; store = createStore(params, UNDOABLE_BLOCKS_STORED); chain = new FullPrunedBlockChain(params, store); // Check that we aren't accidentally leaving any references // to the full StoredUndoableBlock's lying around (ie memory leaks) ECKey outKey = new ECKey(); int height = 1; // Build some blocks on genesis block to create a spendable output. Block rollingBlock = params .getGenesisBlock() .createNextBlockWithCoinbase(Block.BLOCK_VERSION_GENESIS, outKey.getPubKey(), height++); chain.add(rollingBlock); Transaction transaction = rollingBlock.getTransactions().get(0); TransactionOutPoint spendableOutput = new TransactionOutPoint(params, 0, transaction.getHash()); byte[] spendableOutputScriptPubKey = transaction.getOutputs().get(0).getScriptBytes(); for (int i = 1; i < params.getSpendableCoinbaseDepth(); i++) { rollingBlock = rollingBlock.createNextBlockWithCoinbase( Block.BLOCK_VERSION_GENESIS, outKey.getPubKey(), height++); chain.add(rollingBlock); } rollingBlock = rollingBlock.createNextBlock(null); // Create 1 BTC spend to a key in this wallet (to ourselves). Wallet wallet = new Wallet(params); assertEquals( "Available balance is incorrect", Coin.ZERO, wallet.getBalance(Wallet.BalanceType.AVAILABLE)); assertEquals( "Estimated balance is incorrect", Coin.ZERO, wallet.getBalance(Wallet.BalanceType.ESTIMATED)); wallet.setUTXOProvider(store); ECKey toKey = wallet.freshReceiveKey(); Coin amount = Coin.valueOf(100000000); Transaction t = new Transaction(params); t.addOutput(new TransactionOutput(params, t, amount, toKey)); t.addSignedInput(spendableOutput, new Script(spendableOutputScriptPubKey), outKey); rollingBlock.addTransaction(t); rollingBlock.solve(); chain.add(rollingBlock); // Create another spend of 1/2 the value of BTC we have available using the wallet (store coin // selector). ECKey toKey2 = new ECKey(); Coin amount2 = amount.divide(2); Address address2 = new Address(params, toKey2.getPubKeyHash()); Wallet.SendRequest req = Wallet.SendRequest.to(address2, amount2); wallet.completeTx(req); wallet.commitTx(req.tx); Coin fee = req.fee; // There should be one pending tx (our spend). assertEquals( "Wrong number of PENDING.4", 1, wallet.getPoolSize(WalletTransaction.Pool.PENDING)); Coin totalPendingTxAmount = Coin.ZERO; for (Transaction tx : wallet.getPendingTransactions()) { totalPendingTxAmount = totalPendingTxAmount.add(tx.getValueSentToMe(wallet)); } // The availbale balance should be the 0 (as we spent the 1 BTC that's pending) and estimated // should be 1/2 - fee BTC assertEquals( "Available balance is incorrect", Coin.ZERO, wallet.getBalance(Wallet.BalanceType.AVAILABLE)); assertEquals( "Estimated balance is incorrect", amount2.subtract(fee), wallet.getBalance(Wallet.BalanceType.ESTIMATED)); assertEquals("Pending tx amount is incorrect", amount2.subtract(fee), totalPendingTxAmount); try { store.close(); } catch (Exception e) { } }
/** * 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); } }
/** * 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; }
/** * 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; }
private void readTransaction(Protos.Transaction txProto, NetworkParameters params) throws UnreadableWalletException { Transaction tx = new Transaction(params); if (txProto.hasUpdatedAt()) { tx.setUpdateTime(new Date(txProto.getUpdatedAt())); } for (Protos.TransactionOutput outputProto : txProto.getTransactionOutputList()) { Coin value = Coin.valueOf(outputProto.getValue()); byte[] scriptBytes = outputProto.getScriptBytes().toByteArray(); TransactionOutput output = new TransactionOutput(params, tx, value, scriptBytes); tx.addOutput(output); } for (Protos.TransactionInput inputProto : txProto.getTransactionInputList()) { byte[] scriptBytes = inputProto.getScriptBytes().toByteArray(); TransactionOutPoint outpoint = new TransactionOutPoint( params, inputProto.getTransactionOutPointIndex() & 0xFFFFFFFFL, byteStringToHash(inputProto.getTransactionOutPointHash())); Coin value = inputProto.hasValue() ? Coin.valueOf(inputProto.getValue()) : null; TransactionInput input = new TransactionInput(params, tx, scriptBytes, outpoint, value); if (inputProto.hasSequence()) { input.setSequenceNumber(inputProto.getSequence()); } tx.addInput(input); } for (int i = 0; i < txProto.getBlockHashCount(); i++) { ByteString blockHash = txProto.getBlockHash(i); int relativityOffset = 0; if (txProto.getBlockRelativityOffsetsCount() > 0) relativityOffset = txProto.getBlockRelativityOffsets(i); tx.addBlockAppearance(byteStringToHash(blockHash), relativityOffset); } if (txProto.hasLockTime()) { tx.setLockTime(0xffffffffL & txProto.getLockTime()); } if (txProto.hasPurpose()) { switch (txProto.getPurpose()) { case UNKNOWN: tx.setPurpose(Transaction.Purpose.UNKNOWN); break; case USER_PAYMENT: tx.setPurpose(Transaction.Purpose.USER_PAYMENT); break; case KEY_ROTATION: tx.setPurpose(Transaction.Purpose.KEY_ROTATION); break; case ASSURANCE_CONTRACT_CLAIM: tx.setPurpose(Transaction.Purpose.ASSURANCE_CONTRACT_CLAIM); break; case ASSURANCE_CONTRACT_PLEDGE: tx.setPurpose(Transaction.Purpose.ASSURANCE_CONTRACT_PLEDGE); break; case ASSURANCE_CONTRACT_STUB: tx.setPurpose(Transaction.Purpose.ASSURANCE_CONTRACT_STUB); break; default: throw new RuntimeException("New purpose serialization not implemented"); } } else { // Old wallet: assume a user payment as that's the only reason a new tx would have been // created back then. tx.setPurpose(Transaction.Purpose.USER_PAYMENT); } if (txProto.hasExchangeRate()) { Protos.ExchangeRate exchangeRateProto = txProto.getExchangeRate(); tx.setExchangeRate( new ExchangeRate( Coin.valueOf(exchangeRateProto.getCoinValue()), Fiat.valueOf( exchangeRateProto.getFiatCurrencyCode(), exchangeRateProto.getFiatValue()))); } if (txProto.hasMemo()) tx.setMemo(txProto.getMemo()); // Transaction should now be complete. Sha256Hash protoHash = byteStringToHash(txProto.getHash()); if (!tx.getHash().equals(protoHash)) throw new UnreadableWalletException( String.format( "Transaction did not deserialize completely: %s vs %s", tx.getHash(), protoHash)); if (txMap.containsKey(txProto.getHash())) throw new UnreadableWalletException( "Wallet contained duplicate transaction " + byteStringToHash(txProto.getHash())); txMap.put(txProto.getHash(), tx); }