private static void sendTransactionsToListener( StoredBlock block, NewBlockType blockType, BlockChainListener listener, int relativityOffset, List<Transaction> transactions, boolean clone, Set<Sha256Hash> falsePositives) throws VerificationException { for (Transaction tx : transactions) { try { if (listener.isTransactionRelevant(tx)) { falsePositives.remove(tx.getHash()); if (clone) tx = new Transaction(tx.params, tx.bitcoinSerialize()); listener.receiveFromBlock(tx, block, blockType, relativityOffset++); } } catch (ScriptException e) { // We don't want scripts we don't understand to break the block chain so just note that this // tx was // not scanned here and continue. log.warn("Failed to parse a script: " + e.toString()); } catch (ProtocolException e) { // Failed to duplicate tx, should never happen. throw new RuntimeException(e); } } }
/** * Updates the outputs on the payment contract transaction and re-signs it. The state must be * READY in order to call this method. The signature that is returned should be sent to the server * so it has the ability to broadcast the best seen payment when the channel closes or times out. * * <p>The returned signature is over the payment transaction, which we never have a valid copy of * and thus there is no accessor for it on this object. * * <p>To spend the whole channel increment by {@link PaymentChannelClientState#getTotalValue()} - * {@link PaymentChannelClientState#getValueRefunded()} * * @param size How many satoshis to increment the payment by (note: not the new total). * @throws ValueOutOfRangeException If size is negative or the channel does not have sufficient * money in it to complete this payment. */ public synchronized IncrementedPayment incrementPaymentBy(Coin size) throws ValueOutOfRangeException { checkState(state == State.READY); checkNotExpired(); checkNotNull(size); // Validity of size will be checked by makeUnsignedChannelContract. if (size.signum() < 0) throw new ValueOutOfRangeException("Tried to decrement payment"); Coin newValueToMe = valueToMe.subtract(size); if (newValueToMe.compareTo(Transaction.MIN_NONDUST_OUTPUT) < 0 && newValueToMe.signum() > 0) { log.info( "New value being sent back as change was smaller than minimum nondust output, sending all"); size = valueToMe; newValueToMe = Coin.ZERO; } if (newValueToMe.signum() < 0) throw new ValueOutOfRangeException( "Channel has too little money to pay " + size + " satoshis"); Transaction tx = makeUnsignedChannelContract(newValueToMe); log.info("Signing new payment tx {}", tx); Transaction.SigHash mode; // If we spent all the money we put into this channel, we (by definition) don't care what the // outputs are, so // we sign with SIGHASH_NONE to let the server do what it wants. if (newValueToMe.equals(Coin.ZERO)) mode = Transaction.SigHash.NONE; else mode = Transaction.SigHash.SINGLE; TransactionSignature sig = tx.calculateSignature(0, myKey, multisigScript, mode, true); valueToMe = newValueToMe; updateChannelInWallet(); IncrementedPayment payment = new IncrementedPayment(); payment.signature = sig; payment.amount = size; return payment; }
/** * When the servers signature for the refund transaction is received, call this to verify it and * sign the complete refund ourselves. * * <p>If this does not throw an exception, we are secure against the loss of funds and can safely * provide the server with the multi-sig contract to lock in the agreement. In this case, both the * multisig contract and the refund transaction are automatically committed to wallet so that it * can handle broadcasting the refund transaction at the appropriate time if necessary. */ public synchronized void provideRefundSignature(byte[] theirSignature) throws VerificationException { checkNotNull(theirSignature); checkState(state == State.WAITING_FOR_SIGNED_REFUND); TransactionSignature theirSig = TransactionSignature.decodeFromBitcoin(theirSignature, true); if (theirSig.sigHashMode() != Transaction.SigHash.NONE || !theirSig.anyoneCanPay()) throw new VerificationException("Refund signature was not SIGHASH_NONE|SIGHASH_ANYONECANPAY"); // Sign the refund transaction ourselves. final TransactionOutput multisigContractOutput = multisigContract.getOutput(0); try { multisigScript = multisigContractOutput.getScriptPubKey(); } catch (ScriptException e) { throw new RuntimeException(e); // Cannot happen: we built this ourselves. } TransactionSignature ourSignature = refundTx.calculateSignature(0, myKey, multisigScript, Transaction.SigHash.ALL, false); // Insert the signatures. Script scriptSig = ScriptBuilder.createMultiSigInputScript(ourSignature, theirSig); log.info("Refund scriptSig: {}", scriptSig); log.info("Multi-sig contract scriptPubKey: {}", multisigScript); TransactionInput refundInput = refundTx.getInput(0); refundInput.setScriptSig(scriptSig); refundInput.verify(multisigContractOutput); state = State.SAVE_STATE_IN_WALLET; }
/** * Called when the client provides the multi-sig contract. Checks that the previously-provided * refund transaction spends this transaction (because we will use it as a base to create payment * transactions) as well as output value and form (ie it is a 2-of-2 multisig to the correct * keys). * * @param multisigContract The provided multisig contract. Do not mutate this object after this * call. * @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 VerificationException If the provided multisig contract is not well-formed or does not * meet previously-specified parameters */ public synchronized ListenableFuture<PaymentChannelServerState> provideMultiSigContract( final Transaction multisigContract) throws VerificationException { checkNotNull(multisigContract); checkState(state == State.WAITING_FOR_MULTISIG_CONTRACT); try { multisigContract.verify(); this.multisigContract = multisigContract; this.multisigScript = multisigContract.getOutput(0).getScriptPubKey(); // Check that multisigContract's first output is a 2-of-2 multisig to the correct pubkeys in // the correct order final Script expectedScript = ScriptBuilder.createMultiSigOutputScript(2, Lists.newArrayList(clientKey, serverKey)); if (!Arrays.equals(multisigScript.getProgram(), expectedScript.getProgram())) throw new VerificationException( "Multisig contract's first output was not a standard 2-of-2 multisig to client and server in that order."); this.totalValue = multisigContract.getOutput(0).getValue(); if (this.totalValue.signum() <= 0) throw new VerificationException( "Not accepting an attempt to open a contract with zero value."); } catch (VerificationException e) { // We couldn't parse the multisig transaction or its output. log.error("Provided multisig contract did not verify: {}", multisigContract.toString()); throw e; } log.info("Broadcasting multisig contract: {}", multisigContract); state = State.WAITING_FOR_MULTISIG_ACCEPTANCE; final SettableFuture<PaymentChannelServerState> future = SettableFuture.create(); Futures.addCallback( broadcaster.broadcastTransaction(multisigContract).future(), new FutureCallback<Transaction>() { @Override public void onSuccess(Transaction transaction) { log.info( "Successfully broadcast multisig contract {}. Channel now open.", transaction.getHashAsString()); try { // Manually add the multisigContract to the wallet, overriding the isRelevant checks // so we can track // it and check for double-spends later wallet.receivePending(multisigContract, null, true); } catch (VerificationException e) { throw new RuntimeException( e); // Cannot happen, we already called multisigContract.verify() } state = State.READY; future.set(PaymentChannelServerState.this); } @Override public void onFailure(Throwable throwable) { // Couldn't broadcast the transaction for some reason. log.error("Broadcast multisig contract failed", throwable); state = State.ERROR; future.setException(throwable); } }); return future; }
int getIndex() { checkNotNull(parentTransaction, "This output is not attached to a parent transaction."); for (int i = 0; i < parentTransaction.getOutputs().size(); i++) { if (parentTransaction.getOutputs().get(i) == this) return i; } // Should never happen. throw new RuntimeException("Output linked to wrong parent transaction?"); }
// 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); }
// Signs the first input of the transaction which must spend the multisig contract. private void signMultisigInput( Transaction tx, Transaction.SigHash hashType, boolean anyoneCanPay) { TransactionSignature signature = tx.calculateSignature(0, serverKey, multisigScript, hashType, anyoneCanPay); byte[] mySig = signature.encodeToBitcoin(); Script scriptSig = ScriptBuilder.createMultiSigInputScriptBytes(ImmutableList.of(bestValueSignature, mySig)); tx.getInput(0).setScriptSig(scriptSig); }
/** Returns true if the tx is a valid settlement transaction. */ public synchronized boolean isSettlementTransaction(Transaction tx) { try { tx.verify(); tx.getInput(0).verify(multisigContract.getOutput(0)); return true; } catch (VerificationException e) { return false; } }
private synchronized Transaction makeUnsignedChannelContract(Coin valueToMe) throws ValueOutOfRangeException { Transaction tx = new Transaction(wallet.getParams()); tx.addInput(multisigContract.getOutput(0)); // Our output always comes first. // TODO: We should drop myKey in favor of output key + multisig key separation // (as its always obvious who the client is based on T2 output order) tx.addOutput(valueToMe, myKey.toAddress(wallet.getParams())); return tx; }
/** * Creates the initial multisig contract and incomplete refund transaction which can be requested * at the appropriate time using {@link PaymentChannelClientState#getIncompleteRefundTransaction} * and {@link PaymentChannelClientState#getMultisigContract()}. The way the contract is crafted * can be adjusted by overriding {@link * PaymentChannelClientState#editContractSendRequest(com.google.bitcoin.core.Wallet.SendRequest)}. * By default unconfirmed coins are allowed to be used, as for micropayments the risk should be * relatively low. * * @throws ValueOutOfRangeException if the value being used is too small to be accepted by the * network * @throws InsufficientMoneyException if the wallet doesn't contain enough balance to initiate */ public synchronized void initiate() throws ValueOutOfRangeException, InsufficientMoneyException { final NetworkParameters params = wallet.getParams(); Transaction template = new Transaction(params); // We always place the client key before the server key because, if either side wants some // privacy, they can // use a fresh key for the the multisig contract and nowhere else List<ECKey> keys = Lists.newArrayList(myKey, serverMultisigKey); // There is also probably a change output, but we don't bother shuffling them as it's obvious // from the // format which one is the change. If we start obfuscating the change output better in future // this may // be worth revisiting. TransactionOutput multisigOutput = template.addOutput(totalValue, ScriptBuilder.createMultiSigOutputScript(2, keys)); if (multisigOutput.getMinNonDustValue().compareTo(totalValue) > 0) throw new ValueOutOfRangeException("totalValue too small to use"); Wallet.SendRequest req = Wallet.SendRequest.forTx(template); req.coinSelector = AllowUnconfirmedCoinSelector.get(); editContractSendRequest(req); req.shuffleOutputs = false; // TODO: Fix things so shuffling is usable. wallet.completeTx(req); Coin multisigFee = req.tx.getFee(); multisigContract = req.tx; // Build a refund transaction that protects us in the case of a bad server that's just trying to // cause havoc // by locking up peoples money (perhaps as a precursor to a ransom attempt). We time lock it so // the server // has an assurance that we cannot take back our money by claiming a refund before the channel // closes - this // relies on the fact that since Bitcoin 0.8 time locked transactions are non-final. This will // need to change // in future as it breaks the intended design of timelocking/tx replacement, but for now it // simplifies this // specific protocol somewhat. refundTx = new Transaction(params); refundTx .addInput(multisigOutput) .setSequenceNumber(0); // Allow replacement when it's eventually reactivated. refundTx.setLockTime(expiryTime); if (totalValue.compareTo(Coin.CENT) < 0) { // Must pay min fee. final Coin valueAfterFee = totalValue.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE); if (Transaction.MIN_NONDUST_OUTPUT.compareTo(valueAfterFee) > 0) throw new ValueOutOfRangeException("totalValue too small to use"); refundTx.addOutput(valueAfterFee, myKey.toAddress(params)); refundFees = multisigFee.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE); } else { refundTx.addOutput(totalValue, myKey.toAddress(params)); refundFees = multisigFee; } refundTx.getConfidence().setSource(TransactionConfidence.Source.SELF); log.info( "initiated channel with multi-sig contract {}, refund {}", multisigContract.getHashAsString(), refundTx.getHashAsString()); state = State.INITIATED; // Client should now call getIncompleteRefundTransaction() and send it to the server. }
PaymentChannelClientState(StoredClientChannel storedClientChannel, Wallet wallet) throws VerificationException { // The PaymentChannelClientConnection handles storedClientChannel.active and ensures we aren't // resuming channels this.wallet = checkNotNull(wallet); this.multisigContract = checkNotNull(storedClientChannel.contract); this.multisigScript = multisigContract.getOutput(0).getScriptPubKey(); this.refundTx = checkNotNull(storedClientChannel.refund); this.refundFees = checkNotNull(storedClientChannel.refundFees); this.expiryTime = refundTx.getLockTime(); this.myKey = checkNotNull(storedClientChannel.myKey); this.serverMultisigKey = null; this.totalValue = multisigContract.getOutput(0).getValue(); this.valueToMe = checkNotNull(storedClientChannel.valueToMe); this.storedChannel = storedClientChannel; this.state = State.READY; initWalletListeners(); }
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; } }
@VisibleForTesting synchronized void doStoreChannelInWallet(Sha256Hash id) { StoredPaymentChannelClientStates channels = (StoredPaymentChannelClientStates) wallet.getExtensions().get(StoredPaymentChannelClientStates.EXTENSION_ID); checkNotNull( channels, "You have not added the StoredPaymentChannelClientStates extension to the wallet."); checkState(channels.getChannel(id, multisigContract.getHash()) == null); storedChannel = new StoredClientChannel(id, multisigContract, refundTx, myKey, valueToMe, refundFees, true); channels.putChannel(storedChannel); wallet.addOrUpdateExtension(channels); }
/** * Called when the client provides the refund transaction. The refund transaction must have one * input from the multisig contract (that we don't have yet) and one output that the client * creates to themselves. This object will later be modified when we start getting paid. * * @param refundTx The refund transaction, this object will be mutated when payment is * incremented. * @param clientMultiSigPubKey The client's pubkey which is required for the multisig output * @return Our signature that makes the refund transaction valid * @throws VerificationException If the transaction isnt valid or did not meet the requirements of * a refund transaction. */ public synchronized byte[] provideRefundTransaction( Transaction refundTx, byte[] clientMultiSigPubKey) throws VerificationException { checkNotNull(refundTx); checkNotNull(clientMultiSigPubKey); checkState(state == State.WAITING_FOR_REFUND_TRANSACTION); log.info("Provided with refund transaction: {}", refundTx); // Do a few very basic syntax sanity checks. refundTx.verify(); // Verify that the refund transaction has a single input (that we can fill to sign the multisig // output). if (refundTx.getInputs().size() != 1) throw new VerificationException("Refund transaction does not have exactly one input"); // Verify that the refund transaction has a time lock on it and a sequence number of zero. if (refundTx.getInput(0).getSequenceNumber() != 0) throw new VerificationException("Refund transaction's input's sequence number is non-0"); if (refundTx.getLockTime() < minExpireTime) throw new VerificationException("Refund transaction has a lock time too soon"); // Verify the transaction has one output (we don't care about its contents, its up to the // client) // Note that because we sign with SIGHASH_NONE|SIGHASH_ANYOENCANPAY the client can later add // more outputs and // inputs, but we will need only one output later to create the paying transactions if (refundTx.getOutputs().size() != 1) throw new VerificationException("Refund transaction does not have exactly one output"); refundTransactionUnlockTimeSecs = refundTx.getLockTime(); // Sign the refund tx with the scriptPubKey and return the signature. We don't have the spending // transaction // so do the steps individually. clientKey = ECKey.fromPublicOnly(clientMultiSigPubKey); Script multisigPubKey = ScriptBuilder.createMultiSigOutputScript(2, ImmutableList.of(clientKey, serverKey)); // We are really only signing the fact that the transaction has a proper lock time and don't // care about anything // else, so we sign SIGHASH_NONE and SIGHASH_ANYONECANPAY. TransactionSignature sig = refundTx.calculateSignature(0, serverKey, multisigPubKey, Transaction.SigHash.NONE, true); log.info("Signed refund transaction."); this.clientOutput = refundTx.getOutput(0); state = State.WAITING_FOR_MULTISIG_CONTRACT; return sig.encodeToBitcoin(); }
/** * 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); }
/** * 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; }
// expensiveChecks enables checks that require looking at blocks further back in the chain // than the previous one when connecting (eg median timestamp check) // It could be exposed, but for now we just set it to shouldVerifyTransactions() private void connectBlock( final Block block, StoredBlock storedPrev, boolean expensiveChecks, @Nullable final List<Sha256Hash> filteredTxHashList, @Nullable final Map<Sha256Hash, Transaction> filteredTxn) throws BlockStoreException, VerificationException, PrunedException { checkState(lock.isHeldByCurrentThread()); boolean filtered = filteredTxHashList != null && filteredTxn != null; // Check that we aren't connecting a block that fails a checkpoint check if (!params.passesCheckpoint(storedPrev.getHeight() + 1, block.getHash())) throw new VerificationException( "Block failed checkpoint lockin at " + (storedPrev.getHeight() + 1)); if (shouldVerifyTransactions()) { checkNotNull(block.transactions); for (Transaction tx : block.transactions) if (!tx.isFinal(storedPrev.getHeight() + 1, block.getTimeSeconds())) throw new VerificationException("Block contains non-final transaction"); } StoredBlock head = getChainHead(); if (storedPrev.equals(head)) { if (filtered && filteredTxn.size() > 0) { log.debug( "Block {} connects to top of best chain with {} transaction(s) of which we were sent {}", block.getHashAsString(), filteredTxHashList.size(), filteredTxn.size()); for (Sha256Hash hash : filteredTxHashList) log.debug(" matched tx {}", hash); } if (expensiveChecks && block.getTimeSeconds() <= getMedianTimestampOfRecentBlocks(head, blockStore)) throw new VerificationException("Block's timestamp is too early"); // This block connects to the best known block, it is a normal continuation of the system. TransactionOutputChanges txOutChanges = null; if (shouldVerifyTransactions()) txOutChanges = connectTransactions(storedPrev.getHeight() + 1, block); StoredBlock newStoredBlock = addToBlockStore( storedPrev, block.transactions == null ? block : block.cloneAsHeader(), txOutChanges); setChainHead(newStoredBlock); log.debug("Chain is now {} blocks high, running listeners", newStoredBlock.getHeight()); informListenersForNewBlock( block, NewBlockType.BEST_CHAIN, filteredTxHashList, filteredTxn, newStoredBlock); } else { // This block connects to somewhere other than the top of the best known chain. We treat these // differently. // // Note that we send the transactions to the wallet FIRST, even if we're about to re-organize // this block // to become the new best chain head. This simplifies handling of the re-org in the Wallet // class. StoredBlock newBlock = storedPrev.build(block); boolean haveNewBestChain = newBlock.moreWorkThan(head); if (haveNewBestChain) { log.info("Block is causing a re-organize"); } else { StoredBlock splitPoint = findSplit(newBlock, head, blockStore); if (splitPoint != null && splitPoint.equals(newBlock)) { // newStoredBlock is a part of the same chain, there's no fork. This happens when we // receive a block // that we already saw and linked into the chain previously, which isn't the chain head. // Re-processing it is confusing for the wallet so just skip. log.warn( "Saw duplicated block in main chain at height {}: {}", newBlock.getHeight(), newBlock.getHeader().getHash()); return; } if (splitPoint == null) { // This should absolutely never happen // (lets not write the full block to disk to keep any bugs which allow this to happen // from writing unreasonable amounts of data to disk) throw new VerificationException("Block forks the chain but splitPoint is null"); } else { // We aren't actually spending any transactions (yet) because we are on a fork addToBlockStore(storedPrev, block); int splitPointHeight = splitPoint.getHeight(); String splitPointHash = splitPoint.getHeader().getHashAsString(); log.info( "Block forks the chain at height {}/block {}, but it did not cause a reorganize:\n{}", splitPointHeight, splitPointHash, newBlock.getHeader().getHashAsString()); } } // We may not have any transactions if we received only a header, which can happen during fast // catchup. // If we do, send them to the wallet but state that they are on a side chain so it knows not // to try and // spend them until they become activated. if (block.transactions != null || filtered) { informListenersForNewBlock( block, NewBlockType.SIDE_CHAIN, filteredTxHashList, filteredTxn, newBlock); } if (haveNewBestChain) handleNewBestChain(storedPrev, newBlock, block, expensiveChecks); } }
/** * 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; }