/**
  * 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;
 }
Example #2
0
  public void putInternal(Transaction tx, Sha256Hash block_hash, StatusContext ctx) {

    if (block_hash == null) {
      ctx.setStatus("TX_SERIALIZE");
      SerializedTransaction s_tx = new SerializedTransaction(tx);
      ctx.setStatus("TX_PUT");
      // System.out.println("Transaction " + tx.getHash() + " " + Util.measureSerialization(s_tx));
      file_db.getTransactionMap().put(tx.getHash(), s_tx);
    }

    // putTxOutSpents(tx);
    boolean confirmed = (block_hash != null);

    ctx.setStatus("TX_GET_ADDR");
    Collection<String> addrs = getAllAddresses(tx, confirmed);

    Random rnd = new Random();

    ctx.setStatus("TX_SAVE_ADDRESS");
    file_db.addAddressesToTxMap(addrs, tx.getHash());

    imported_transactions.incrementAndGet();
    int h = -1;
    if (block_hash != null) {
      ctx.setStatus("TX_GET_HEIGHT");
      h = block_store.getHeight(block_hash);
    }

    ctx.setStatus("TX_NOTIFY");
    jelly.getElectrumNotifier().notifyNewTransaction(tx, addrs, h);
    ctx.setStatus("TX_DONE");
  }
 /**
  * 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;
 }
 @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 =
           new Transaction(params, storedState.getRefundTransaction().toByteArray());
       refundTransaction.getConfidence().setSource(TransactionConfidence.Source.SELF);
       StoredClientChannel channel =
           new StoredClientChannel(
               new Sha256Hash(storedState.getId().toByteArray()),
               new Transaction(params, storedState.getContractTransaction().toByteArray()),
               refundTransaction,
               new ECKey(new BigInteger(1, storedState.getMyKey().toByteArray()), null, true),
               BigInteger.valueOf(storedState.getValueToMe()),
               BigInteger.valueOf(storedState.getRefundFees()),
               false);
       if (storedState.hasCloseTransactionHash())
         channel.close =
             containingWallet.getTransaction(new Sha256Hash(storedState.toByteArray()));
       putChannel(channel, false);
     }
   } finally {
     lock.unlock();
   }
 }
Example #5
0
  public Address getAddressForInput(TransactionInput in, boolean confirmed) {
    if (in.isCoinBase()) return null;

    try {
      Address a = in.getFromAddress();
      return a;
    } catch (ScriptException e) {
      // Lets try this the other way

      try {

        TransactionOutPoint out_p = in.getOutpoint();

        Transaction src_tx = null;
        while (src_tx == null) {
          src_tx = getTransaction(out_p.getHash());
          if (src_tx == null) {
            if (!confirmed) {
              return null;
            }
            System.out.println("Unable to get source transaction: " + out_p.getHash());
            try {
              Thread.sleep(500);
            } catch (Exception e7) {
            }
          }
        }
        TransactionOutput out = src_tx.getOutput((int) out_p.getIndex());
        Address a = getAddressForOutput(out);
        return a;
      } catch (ScriptException e2) {
        return null;
      }
    }
  }
    @Override
    public View getView(final int position, final View convertView, final ViewGroup parent) {
      final ViewGroup row;
      if (convertView == null)
        row = (ViewGroup) getLayoutInflater(null).inflate(R.layout.block_row, null);
      else row = (ViewGroup) convertView;

      final StoredBlock storedBlock = getItem(position);
      final Block header = storedBlock.getHeader();

      final TextView rowHeight = (TextView) row.findViewById(R.id.block_list_row_height);
      final int height = storedBlock.getHeight();
      rowHeight.setText(Integer.toString(height));

      final TextView rowTime = (TextView) row.findViewById(R.id.block_list_row_time);
      final long timeMs = header.getTimeSeconds() * DateUtils.SECOND_IN_MILLIS;
      rowTime.setText(
          DateUtils.getRelativeDateTimeString(
              activity, timeMs, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, 0));

      final TextView rowHash = (TextView) row.findViewById(R.id.block_list_row_hash);
      rowHash.setText(WalletUtils.formatHash(null, header.getHashAsString(), 8, 0, ' '));

      final int transactionChildCount = row.getChildCount() - ROW_BASE_CHILD_COUNT;
      int iTransactionView = 0;

      if (transactions != null) {
        final String precision =
            prefs.getString(
                Constants.PREFS_KEY_BTC_PRECISION, Constants.PREFS_DEFAULT_BTC_PRECISION);
        final int btcPrecision = precision.charAt(0) - '0';
        final int btcShift = precision.length() == 3 ? precision.charAt(2) - '0' : 0;

        transactionsAdapter.setPrecision(btcPrecision, btcShift);

        for (final Transaction tx : transactions) {
          if (tx.getAppearsInHashes().containsKey(header.getHash())) {
            final View view;
            if (iTransactionView < transactionChildCount) {
              view = row.getChildAt(ROW_INSERT_INDEX + iTransactionView);
            } else {
              view = getLayoutInflater(null).inflate(R.layout.transaction_row_oneline, null);
              row.addView(view, ROW_INSERT_INDEX + iTransactionView);
            }

            transactionsAdapter.bindView(view, tx);

            iTransactionView++;
          }
        }
      }

      final int leftoverTransactionViews = transactionChildCount - iTransactionView;
      if (leftoverTransactionViews > 0)
        row.removeViews(ROW_INSERT_INDEX + iTransactionView, leftoverTransactionViews);

      return row;
    }
 /** 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;
 }
 /** 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,
             BigInteger.valueOf(output.getAmount()),
             output.getScript().toByteArray()));
   return Wallet.SendRequest.forTx(tx);
 }
Example #10
0
  public void sweepKey(ECKey key, long fee, int accountId, JSONArray outputs) {
    mLogger.info("sweepKey starting");

    mLogger.info("key addr " + key.toAddress(mParams).toString());

    Transaction tx = new Transaction(mParams);

    long balance = 0;
    ArrayList<Script> scripts = new ArrayList<Script>();
    try {
      for (int ii = 0; ii < outputs.length(); ++ii) {
        JSONObject output;
        output = outputs.getJSONObject(ii);

        String tx_hash = output.getString("tx_hash");
        int tx_output_n = output.getInt("tx_output_n");
        String script = output.getString("script");

        // Reverse byte order, create hash.
        Sha256Hash hash = new Sha256Hash(WalletUtil.msgHexToBytes(tx_hash));

        tx.addInput(
            new TransactionInput(
                mParams, tx, new byte[] {}, new TransactionOutPoint(mParams, tx_output_n, hash)));

        scripts.add(new Script(Hex.decode(script)));

        balance += output.getLong("value");
      }
    } catch (JSONException e) {
      e.printStackTrace();
      throw new RuntimeException("trouble parsing unspent outputs");
    }

    // Compute balance - fee.
    long amount = balance - fee;
    mLogger.info(String.format("sweeping %d", amount));

    // Figure out the destination address.
    Address to = mHDWallet.nextReceiveAddress(accountId);
    mLogger.info("sweeping to " + to.toString());

    // Add output.
    tx.addOutput(BigInteger.valueOf(amount), to);

    WalletUtil.signTransactionInputs(tx, Transaction.SigHash.ALL, key, scripts);

    mLogger.info("tx bytes: " + new String(Hex.encode(tx.bitcoinSerialize())));
    // mKit.peerGroup().broadcastTransaction(tx);
    broadcastTransaction(mKit.peerGroup(), tx);

    mLogger.info("sweepKey finished");
  }
 /**
  * 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.
 }
    @Override
    public Set<Transaction> loadInBackground() {
      final Set<Transaction> transactions = wallet.getTransactions(true);

      final Set<Transaction> filteredTransactions = new HashSet<Transaction>(transactions.size());
      for (final Transaction tx : transactions) {
        final Map<Sha256Hash, Integer> appearsIn = tx.getAppearsInHashes();
        if (appearsIn != null && !appearsIn.isEmpty()) // TODO filter by updateTime
        filteredTransactions.add(tx);
      }

      return filteredTransactions;
    }
Example #13
0
  public Collection<String> getAllAddresses(Transaction tx, boolean confirmed) {
    HashSet<String> lst = new HashSet<String>();

    for (TransactionInput in : tx.getInputs()) {
      Address a = getAddressForInput(in, confirmed);
      if (a != null) lst.add(a.toString());
    }

    for (TransactionOutput out : tx.getOutputs()) {
      Address a = getAddressForOutput(out);
      if (a != null) lst.add(a.toString());
    }

    return lst;
  }
 public static void signTransaction(Transaction tx, String qrCodeContent) throws ScriptException {
   String[] stringArray = qrCodeContent.split(StringUtil.QR_CODE_SPLIT);
   List<String> hashList = new ArrayList<String>();
   for (String str : stringArray) {
     if (!StringUtil.isEmpty(str)) {
       hashList.add(str);
       LogUtil.d("sign", str);
     }
   }
   for (int i = 0; i < tx.getInputs().size(); i++) {
     TransactionInput input = tx.getInputs().get(i);
     String str = hashList.get(i);
     input.setScriptSig(new Script(StringUtil.hexStringToByteArray(str)));
     input
         .getScriptSig()
         .correctlySpends(tx, i, input.getOutpoint().getConnectedOutput().getScriptPubKey(), true);
   }
 }
 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();
 }
Example #16
0
 public void broadcastTransaction(@Nonnull final Transaction tx) {
   final Intent intent =
       new Intent(
           BlockchainService.ACTION_BROADCAST_TRANSACTION,
           null,
           this,
           BlockchainServiceImpl.class);
   intent.putExtra(BlockchainService.ACTION_BROADCAST_TRANSACTION_HASH, tx.getHash().getBytes());
   startService(intent);
 }
Example #17
0
  public void putTxOutSpents(Transaction tx) {
    LinkedList<String> tx_outs = new LinkedList<String>();

    for (TransactionInput in : tx.getInputs()) {
      if (!in.isCoinBase()) {
        TransactionOutPoint out = in.getOutpoint();
        String key = out.getHash().toString() + ":" + out.getIndex();
        // file_db.addTxOutSpentByMap(key, tx.getHash());
        tx_outs.add(key);
      }
    }
  }
 @Override
 public String toString() {
   final String newline = String.format("%n");
   final String closeStr =
       close == null ? "still open" : close.toString().replaceAll(newline, newline + "   ");
   return String.format(
       "Stored client channel for server ID %s (%s)%n"
           + "    Key:         %s%n"
           + "    Value left:  %d%n"
           + "    Refund fees: %d%n"
           + "    Contract:  %s"
           + "Refund:    %s"
           + "Close:     %s",
       id,
       active ? "active" : "inactive",
       myKey,
       valueToMe,
       refundFees,
       contract.toString().replaceAll(newline, newline + "    "),
       refund.toString().replaceAll(newline, newline + "    "),
       closeStr);
 }
Example #19
0
  @Test
  public void testCreateMultiSigInputScript() throws AddressFormatException {
    // Setup transaction and signatures
    ECKey key1 =
        new DumpedPrivateKey(params, "cVLwRLTvz3BxDAWkvS3yzT9pUcTCup7kQnfT2smRjvmmm1wAP6QT")
            .getKey();
    ECKey key2 =
        new DumpedPrivateKey(params, "cTine92s8GLpVqvebi8rYce3FrUYq78ZGQffBYCS1HmDPJdSTxUo")
            .getKey();
    ECKey key3 =
        new DumpedPrivateKey(params, "cVHwXSPRZmL9adctwBwmn4oTZdZMbaCsR5XF6VznqMgcvt1FDDxg")
            .getKey();
    Script multisigScript =
        ScriptBuilder.createMultiSigOutputScript(2, Arrays.asList(key1, key2, key3));
    byte[] bytes =
        Hex.decode(
            "01000000013df681ff83b43b6585fa32dd0e12b0b502e6481e04ee52ff0fdaf55a16a4ef61000000006b483045022100a84acca7906c13c5895a1314c165d33621cdcf8696145080895cbf301119b7cf0220730ff511106aa0e0a8570ff00ee57d7a6f24e30f592a10cae1deffac9e13b990012102b8d567bcd6328fd48a429f9cf4b315b859a58fd28c5088ef3cb1d98125fc4e8dffffffff02364f1c00000000001976a91439a02793b418de8ec748dd75382656453dc99bcb88ac40420f000000000017a9145780b80be32e117f675d6e0ada13ba799bf248e98700000000");
    Transaction transaction = new Transaction(params, bytes);
    TransactionOutput output = transaction.getOutput(1);
    Transaction spendTx = new Transaction(params);
    Address address = new Address(params, "n3CFiCmBXVt5d3HXKQ15EFZyhPz4yj5F3H");
    Script outputScript = ScriptBuilder.createOutputScript(address);
    spendTx.addOutput(output.getValue(), outputScript);
    spendTx.addInput(output);
    Sha256Hash sighash = spendTx.hashForSignature(0, multisigScript, SigHash.ALL, false);
    ECKey.ECDSASignature party1Signature = key1.sign(sighash);
    ECKey.ECDSASignature party2Signature = key2.sign(sighash);
    TransactionSignature party1TransactionSignature =
        new TransactionSignature(party1Signature, SigHash.ALL, false);
    TransactionSignature party2TransactionSignature =
        new TransactionSignature(party2Signature, SigHash.ALL, false);

    // Create p2sh multisig input script
    Script inputScript =
        ScriptBuilder.createP2SHMultiSigInputScript(
            ImmutableList.of(party1TransactionSignature, party2TransactionSignature),
            multisigScript.getProgram());

    // Assert that the input script contains 4 chunks
    assertTrue(inputScript.getChunks().size() == 4);

    // Assert that the input script created contains the original multisig
    // script as the last chunk
    ScriptChunk scriptChunk = inputScript.getChunks().get(inputScript.getChunks().size() - 1);
    Assert.assertArrayEquals(scriptChunk.data, multisigScript.getProgram());

    // Create regular multisig input script
    inputScript =
        ScriptBuilder.createMultiSigInputScript(
            ImmutableList.of(party1TransactionSignature, party2TransactionSignature));

    // Assert that the input script only contains 3 chunks
    assertTrue(inputScript.getChunks().size() == 3);

    // Assert that the input script created does not end with the original
    // multisig script
    scriptChunk = inputScript.getChunks().get(inputScript.getChunks().size() - 1);
    Assert.assertThat(scriptChunk.data, IsNot.not(IsEqual.equalTo(multisigScript.getProgram())));
  }
  private void readTransaction(Protos.Transaction txProto, NetworkParameters params) {
    Transaction tx = new Transaction(params);
    if (txProto.hasUpdatedAt()) {
      tx.setUpdateTime(new Date(txProto.getUpdatedAt()));
    }

    for (Protos.TransactionOutput outputProto : txProto.getTransactionOutputList()) {
      BigInteger value = BigInteger.valueOf(outputProto.getValue());
      byte[] scriptBytes = outputProto.getScriptBytes().toByteArray();
      TransactionOutput output = new TransactionOutput(params, tx, value, scriptBytes);
      tx.addOutput(output);
    }

    for (Protos.TransactionInput transactionInput : txProto.getTransactionInputList()) {
      byte[] scriptBytes = transactionInput.getScriptBytes().toByteArray();
      TransactionOutPoint outpoint =
          new TransactionOutPoint(
              params,
              transactionInput.getTransactionOutPointIndex(),
              byteStringToHash(transactionInput.getTransactionOutPointHash()));
      TransactionInput input = new TransactionInput(params, tx, scriptBytes, outpoint);
      if (transactionInput.hasSequence()) {
        input.setSequenceNumber(transactionInput.getSequence());
      }
      tx.addInput(input);
    }

    for (ByteString blockHash : txProto.getBlockHashList()) {
      tx.addBlockAppearance(byteStringToHash(blockHash));
    }

    if (txProto.hasLockTime()) {
      tx.setLockTime(0xffffffffL & txProto.getLockTime());
    }

    // Transaction should now be complete.
    Sha256Hash protoHash = byteStringToHash(txProto.getHash());
    Preconditions.checkState(
        tx.getHash().equals(protoHash),
        "Transaction did not deserialize completely: %s vs %s",
        tx.getHash(),
        protoHash);
    Preconditions.checkState(
        !txMap.containsKey(txProto.getHash()),
        "Wallet contained duplicate transaction %s",
        byteStringToHash(txProto.getHash()));
    txMap.put(txProto.getHash(), tx);
  }
 @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);
 }
 /**
  * Generates a Payment message based on the information in the PaymentRequest. Provide
  * transactions built by the wallet. If the PaymentRequest did not specify a payment_url, returns
  * null.
  *
  * @param txns list of transactions to be included with the Payment message.
  * @param refundAddr will be used by the merchant to send money back if there was a problem.
  * @param memo is a message to include in the payment message sent to the merchant.
  */
 public @Nullable Protos.Payment getPayment(
     List<Transaction> txns, @Nullable Address refundAddr, @Nullable String memo)
     throws IOException {
   if (!paymentDetails.hasPaymentUrl()) return null;
   Protos.Payment.Builder payment = Protos.Payment.newBuilder();
   if (paymentDetails.hasMerchantData()) payment.setMerchantData(paymentDetails.getMerchantData());
   if (refundAddr != null) {
     Protos.Output.Builder refundOutput = Protos.Output.newBuilder();
     refundOutput.setAmount(totalValue.longValue());
     refundOutput.setScript(
         ByteString.copyFrom(ScriptBuilder.createOutputScript(refundAddr).getProgram()));
     payment.addRefundTo(refundOutput);
   }
   if (memo != null) {
     payment.setMemo(memo);
   }
   for (Transaction txn : txns) {
     txn.verify();
     ByteArrayOutputStream o = new ByteArrayOutputStream();
     txn.bitcoinSerialize(o);
     payment.addTransactions(ByteString.copyFrom(o.toByteArray()));
   }
   return payment.build();
 }
  private WalletTransaction connectTransactionOutputs(
      org.bitcoinj.wallet.Protos.Transaction txProto) {
    Transaction tx = txMap.get(txProto.getHash());
    WalletTransaction.Pool pool = WalletTransaction.Pool.valueOf(txProto.getPool().getNumber());
    if (pool == WalletTransaction.Pool.INACTIVE
        || pool == WalletTransaction.Pool.PENDING_INACTIVE) {
      // Upgrade old wallets: inactive pool has been merged with the pending pool.
      // Remove this some time after 0.9 is old and everyone has upgraded.
      // There should not be any spent outputs in this tx as old wallets would not allow them to be
      // spent
      // in this state.
      pool = WalletTransaction.Pool.PENDING;
    }
    for (int i = 0; i < tx.getOutputs().size(); i++) {
      TransactionOutput output = tx.getOutputs().get(i);
      final Protos.TransactionOutput transactionOutput = txProto.getTransactionOutput(i);
      if (transactionOutput.hasSpentByTransactionHash()) {
        final ByteString spentByTransactionHash = transactionOutput.getSpentByTransactionHash();
        Transaction spendingTx = txMap.get(spentByTransactionHash);
        if (spendingTx == null)
          throw new IllegalArgumentException(
              String.format(
                  "Could not connect %s to %s",
                  tx.getHashAsString(), byteStringToHash(spentByTransactionHash)));
        final int spendingIndex = transactionOutput.getSpentByTransactionIndex();
        TransactionInput input = checkNotNull(spendingTx.getInput(spendingIndex));
        input.connect(output);
      }
    }

    if (txProto.hasConfidence()) {
      Protos.TransactionConfidence confidenceProto = txProto.getConfidence();
      TransactionConfidence confidence = tx.getConfidence();
      readConfidence(tx, confidenceProto, confidence);
    }

    return new WalletTransaction(pool, tx);
  }
Example #24
0
  @Test
  public void dataDrivenInvalidTransactions() throws Exception {
    BufferedReader in =
        new BufferedReader(
            new InputStreamReader(
                getClass().getResourceAsStream("tx_invalid.json"), Charset.forName("UTF-8")));

    NetworkParameters params = TestNet3Params.get();

    // Poor man's (aka. really, really poor) JSON parser (because pulling in a lib for this is
    // probably overkill)
    List<JSONObject> tx = new ArrayList<JSONObject>(1);
    in.read(); // remove first [
    StringBuffer buffer = new StringBuffer(1000);
    while (in.ready()) {
      String line = in.readLine();
      if (line == null || line.equals("")) continue;
      buffer.append(line);
      if (line.equals("]") && buffer.toString().equals("]") && !in.ready()) break; // ignore last ]
      boolean isFinished = appendToList(tx, buffer);
      while (tx.size() > 0
          && tx.get(0).isList()
          && tx.get(0).list.size() == 1
          && tx.get(0).list.get(0).isString()) tx.remove(0);
      if (isFinished && tx.size() == 1 && tx.get(0).list.size() == 3) {
        HashMap<TransactionOutPoint, Script> scriptPubKeys =
            new HashMap<TransactionOutPoint, Script>();
        for (JSONObject input : tx.get(0).list.get(0).list) {
          String hash = input.list.get(0).string;
          int index = input.list.get(1).integer;
          String script = input.list.get(2).string;
          Sha256Hash sha256Hash =
              new Sha256Hash(Hex.decode(hash.getBytes(Charset.forName("UTF-8"))));
          scriptPubKeys.put(
              new TransactionOutPoint(params, index, sha256Hash), parseScriptString(script));
        }

        byte[] bytes = tx.get(0).list.get(1).string.getBytes(Charset.forName("UTF-8"));
        Transaction transaction = new Transaction(params, Hex.decode(bytes));
        boolean enforceP2SH = tx.get(0).list.get(2).booleanValue;
        assertTrue(tx.get(0).list.get(2).isBoolean());

        boolean valid = true;
        try {
          transaction.verify();
        } catch (VerificationException e) {
          valid = false;
        }

        // The reference client checks this case in CheckTransaction, but we leave it to
        // later where we will see an attempt to double-spend, so we explicitly check here
        HashSet<TransactionOutPoint> set = new HashSet<TransactionOutPoint>();
        for (TransactionInput input : transaction.getInputs()) {
          if (set.contains(input.getOutpoint())) valid = false;
          set.add(input.getOutpoint());
        }

        for (int i = 0; i < transaction.getInputs().size() && valid; i++) {
          TransactionInput input = transaction.getInputs().get(i);
          assertTrue(scriptPubKeys.containsKey(input.getOutpoint()));
          try {
            input
                .getScriptSig()
                .correctlySpends(
                    transaction, i, scriptPubKeys.get(input.getOutpoint()), enforceP2SH);
          } catch (VerificationException e) {
            valid = false;
          }
        }

        if (valid) fail();

        tx.clear();
      }
    }
    in.close();
  }
 private void readConfidence(
     Transaction tx,
     Protos.TransactionConfidence confidenceProto,
     TransactionConfidence confidence) {
   // We are lenient here because tx confidence is not an essential part of the wallet.
   // If the tx has an unknown type of confidence, ignore.
   if (!confidenceProto.hasType()) {
     log.warn("Unknown confidence type for tx {}", tx.getHashAsString());
     return;
   }
   ConfidenceType confidenceType;
   switch (confidenceProto.getType()) {
     case BUILDING:
       confidenceType = ConfidenceType.BUILDING;
       break;
     case DEAD:
       confidenceType = ConfidenceType.DEAD;
       break;
       // These two are equivalent (must be able to read old wallets).
     case NOT_IN_BEST_CHAIN:
       confidenceType = ConfidenceType.PENDING;
       break;
     case NOT_SEEN_IN_CHAIN:
       confidenceType = ConfidenceType.PENDING;
       break;
     case UNKNOWN:
       // Fall through.
     default:
       confidenceType = ConfidenceType.UNKNOWN;
       break;
   }
   confidence.setConfidenceType(confidenceType);
   if (confidenceProto.hasAppearedAtHeight()) {
     if (confidence.getConfidenceType() != ConfidenceType.BUILDING) {
       log.warn("Have appearedAtHeight but not BUILDING for tx {}", tx.getHashAsString());
       return;
     }
     confidence.setAppearedAtChainHeight(confidenceProto.getAppearedAtHeight());
   }
   if (confidenceProto.hasDepth()) {
     if (confidence.getConfidenceType() != ConfidenceType.BUILDING) {
       log.warn("Have depth but not BUILDING for tx {}", tx.getHashAsString());
       return;
     }
     confidence.setDepthInBlocks(confidenceProto.getDepth());
   }
   if (confidenceProto.hasWorkDone()) {
     if (confidence.getConfidenceType() != ConfidenceType.BUILDING) {
       log.warn("Have workDone but not BUILDING for tx {}", tx.getHashAsString());
       return;
     }
     confidence.setWorkDone(BigInteger.valueOf(confidenceProto.getWorkDone()));
   }
   if (confidenceProto.hasOverridingTransaction()) {
     if (confidence.getConfidenceType() != ConfidenceType.DEAD) {
       log.warn("Have overridingTransaction but not OVERRIDDEN for tx {}", tx.getHashAsString());
       return;
     }
     Transaction overridingTransaction = txMap.get(confidenceProto.getOverridingTransaction());
     if (overridingTransaction == null) {
       log.warn(
           "Have overridingTransaction that is not in wallet for tx {}", tx.getHashAsString());
       return;
     }
     confidence.setOverridingTransaction(overridingTransaction);
   }
   for (Protos.PeerAddress proto : confidenceProto.getBroadcastByList()) {
     InetAddress ip;
     try {
       ip = InetAddress.getByAddress(proto.getIpAddress().toByteArray());
     } catch (UnknownHostException e) {
       throw new RuntimeException(e); // IP address is of invalid length.
     }
     int port = proto.getPort();
     PeerAddress address = new PeerAddress(ip, port);
     address.setServices(BigInteger.valueOf(proto.getServices()));
     confidence.markBroadcastBy(address);
   }
   switch (confidenceProto.getSource()) {
     case SOURCE_SELF:
       confidence.setSource(TransactionConfidence.Source.SELF);
       break;
     case SOURCE_NETWORK:
       confidence.setSource(TransactionConfidence.Source.NETWORK);
       break;
     case SOURCE_UNKNOWN:
       // Fall through.
     default:
       confidence.setSource(TransactionConfidence.Source.UNKNOWN);
       break;
   }
 }
 @Override
 public int compare(Transaction lhs, Transaction rhs) {
   return lhs.getUpdateTime().compareTo(rhs.getUpdateTime());
 }
  public static List<Transaction> getTransactionsFromBither(
      JSONObject jsonObject, int storeBlockHeight)
      throws JSONException, WrongNetworkException, AddressFormatException, VerificationException,
          ParseException, NoSuchFieldException, IllegalAccessException, IllegalArgumentException {
    List<Transaction> transactions = new ArrayList<Transaction>();

    if (!jsonObject.isNull(TXS)) {
      JSONArray txArray = jsonObject.getJSONArray(TXS);
      double count = 0;
      double size = txArray.length();

      for (int j = 0; j < txArray.length(); j++) {
        JSONObject tranJsonObject = txArray.getJSONObject(j);
        String blockHash = tranJsonObject.getString(BITHER_BLOCK_HASH);
        String txHash = tranJsonObject.getString(TX_HASH);
        int height = tranJsonObject.getInt(BITHER_BLOCK_NO);
        if (height > storeBlockHeight && storeBlockHeight > 0) {
          continue;
        }
        int version = 1;
        Date updateTime = new Date();
        if (!tranJsonObject.isNull(EXPLORER_TIME)) {
          updateTime = DateTimeUtil.getDateTimeForTimeZone(tranJsonObject.getString(EXPLORER_TIME));
        }
        if (!tranJsonObject.isNull(EXPLORER_VERSION)) {
          version = tranJsonObject.getInt(EXPLORER_VERSION);
        }
        Transaction transaction =
            new Transaction(BitherSetting.NETWORK_PARAMETERS, version, new Sha256Hash(txHash));
        transaction.addBlockAppearance(new Sha256Hash(blockHash), height);
        if (!tranJsonObject.isNull(EXPLORER_OUT)) {
          JSONArray tranOutArray = tranJsonObject.getJSONArray(EXPLORER_OUT);
          for (int i = 0; i < tranOutArray.length(); i++) {
            JSONObject tranOutJson = tranOutArray.getJSONObject(i);
            BigInteger value = BigInteger.valueOf(tranOutJson.getLong(BITHER_VALUE));
            if (!tranOutJson.isNull(SCRIPT_PUB_KEY)) {
              String str = tranOutJson.getString(SCRIPT_PUB_KEY);
              // Script script = new Script(
              // );
              // byte[] bytes1 = ScriptBuilder.createOutputScript(
              // address).getProgram();
              // byte[] bytes2 = StringUtil
              // .hexStringToByteArray(str);
              // LogUtil.d("tx", Arrays.equals(bytes1, bytes2) +
              // ";");
              TransactionOutput transactionOutput =
                  new TransactionOutput(
                      BitherSetting.NETWORK_PARAMETERS,
                      transaction,
                      value,
                      StringUtil.hexStringToByteArray(str));
              transaction.addOutput(transactionOutput);
            }
          }
        }

        if (!tranJsonObject.isNull(EXPLORER_IN)) {
          JSONArray tranInArray = tranJsonObject.getJSONArray(EXPLORER_IN);
          for (int i = 0; i < tranInArray.length(); i++) {
            JSONObject tranInJson = tranInArray.getJSONObject(i);
            TransactionOutPoint transactionOutPoint = null;
            if (!tranInJson.isNull(EXPLORER_COINBASE)) {
              long index = 0;
              if (!tranInJson.isNull(EXPLORER_SEQUENCE)) {
                index = tranInJson.getLong(EXPLORER_SEQUENCE);
              }
              transactionOutPoint =
                  new TransactionOutPoint(
                      BitherSetting.NETWORK_PARAMETERS, index, Sha256Hash.ZERO_HASH);

            } else {

              String prevOutHash = tranInJson.getString(PREV_TX_HASH);
              long n = 0;
              if (!tranInJson.isNull(PREV_OUTPUT_SN)) {
                n = tranInJson.getLong(PREV_OUTPUT_SN);
              }
              transactionOutPoint =
                  new TransactionOutPoint(
                      BitherSetting.NETWORK_PARAMETERS, n, new Sha256Hash(prevOutHash));
            }
            // Log.d("transaction", transaction.toString());
            if (transactionOutPoint != null) {
              TransactionInput transactionInput =
                  new TransactionInput(
                      BitherSetting.NETWORK_PARAMETERS,
                      transaction,
                      Script.createInputScript(EMPTY_BYTES, EMPTY_BYTES),
                      transactionOutPoint);

              transaction.addInput(transactionInput);
            }
          }
        }
        transaction.getConfidence().setAppearedAtChainHeight(height);
        transaction.getConfidence().setConfidenceType(ConfidenceType.BUILDING);
        transaction.getConfidence().setDepthInBlocks(storeBlockHeight - height + 1);
        transaction.setUpdateTime(updateTime);
        // Log.d("transaction", "transaction.num:" + transaction);
        Field txField = Transaction.class.getDeclaredField("hash");
        txField.setAccessible(true);
        txField.set(transaction, new Sha256Hash(txHash));
        transactions.add(transaction);
        count++;
        double progress =
            BitherSetting.SYNC_TX_PROGRESS_BLOCK_HEIGHT
                + BitherSetting.SYNC_TX_PROGRESS_STEP1
                + BitherSetting.SYNC_TX_PROGRESS_STEP2 * (count / size);
        BroadcastUtil.sendBroadcastProgressState(progress);
      }
    }

    LogUtil.d("transaction", "transactions.num:" + transactions.size());
    return transactions;
  }
Example #28
0
  @Test
  public void dataDrivenValidTransactions() throws Exception {
    BufferedReader in =
        new BufferedReader(
            new InputStreamReader(
                getClass().getResourceAsStream("tx_valid.json"), Charset.forName("UTF-8")));

    NetworkParameters params = TestNet3Params.get();

    // Poor man's (aka. really, really poor) JSON parser (because pulling in a lib for this is
    // probably not overkill)
    int lineNum = -1;
    List<JSONObject> tx = new ArrayList<JSONObject>(3);
    in.read(); // remove first [
    StringBuffer buffer = new StringBuffer(1000);
    while (in.ready()) {
      lineNum++;
      String line = in.readLine();
      if (line == null || line.equals("")) continue;
      buffer.append(line);
      if (line.equals("]") && buffer.toString().equals("]") && !in.ready()) break;
      boolean isFinished = appendToList(tx, buffer);
      while (tx.size() > 0
          && tx.get(0).isList()
          && tx.get(0).list.size() == 1
          && tx.get(0).list.get(0).isString()) tx.remove(0); // ignore last ]
      if (isFinished && tx.size() == 1 && tx.get(0).list.size() == 3) {
        Transaction transaction = null;
        try {
          HashMap<TransactionOutPoint, Script> scriptPubKeys =
              new HashMap<TransactionOutPoint, Script>();
          for (JSONObject input : tx.get(0).list.get(0).list) {
            String hash = input.list.get(0).string;
            int index = input.list.get(1).integer;
            String script = input.list.get(2).string;
            Sha256Hash sha256Hash =
                new Sha256Hash(Hex.decode(hash.getBytes(Charset.forName("UTF-8"))));
            scriptPubKeys.put(
                new TransactionOutPoint(params, index, sha256Hash), parseScriptString(script));
          }

          byte[] bytes = tx.get(0).list.get(1).string.getBytes(Charset.forName("UTF-8"));
          transaction = new Transaction(params, Hex.decode(bytes));
          boolean enforceP2SH = tx.get(0).list.get(2).booleanValue;
          assertTrue(tx.get(0).list.get(2).isBoolean());

          transaction.verify();

          for (int i = 0; i < transaction.getInputs().size(); i++) {
            TransactionInput input = transaction.getInputs().get(i);
            if (input.getOutpoint().getIndex() == 0xffffffffL) input.getOutpoint().setIndex(-1);
            assertTrue(scriptPubKeys.containsKey(input.getOutpoint()));
            input
                .getScriptSig()
                .correctlySpends(
                    transaction, i, scriptPubKeys.get(input.getOutpoint()), enforceP2SH);
          }
          tx.clear();
        } catch (Exception e) {
          System.err.println("Exception processing line " + lineNum + ": " + line);
          if (transaction != null) System.err.println(transaction);
          throw e;
        }
      }
    }
    in.close();
  }
Example #29
0
        @Override
        public void onCoinsSent(
            Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) {
          BigInteger amt = prevBalance.subtract(newBalance);
          final long amount = amt.longValue();

          WalletApplication app = (WalletApplication) getApplicationContext();
          final BTCFmt btcfmt = app.getBTCFmt();

          // We allocate a new notification id for each receive.
          // We use it on both the receive and confirm so it
          // will replace the receive note with the confirm ...
          final int noteId = ++mNoteId;

          mLogger.info(String.format("showing notification send %d", amount));

          showEventNotification(
              noteId,
              R.drawable.ic_note_bc_red_lt,
              mRes.getString(R.string.wallet_service_note_sent_title, btcfmt.unitStr()),
              mRes.getString(
                  R.string.wallet_service_note_sent_msg, btcfmt.format(amount), btcfmt.unitStr()));

          final TransactionConfidence txconf = tx.getConfidence();

          final TransactionConfidence.Listener listener =
              new TransactionConfidence.Listener() {
                @Override
                public void onConfidenceChanged(Transaction tx, ChangeReason reason) {
                  // Wait until it's not pending anymore.
                  if (tx.isPending()) return;

                  ConfidenceType ct = tx.getConfidence().getConfidenceType();

                  if (ct == ConfidenceType.BUILDING) {
                    mLogger.info(String.format("send %d confirm", amount));

                    // Show no longer pending.
                    showEventNotification(
                        noteId,
                        R.drawable.ic_note_bc_red,
                        mRes.getString(R.string.wallet_service_note_scnf_title, btcfmt.unitStr()),
                        mRes.getString(
                            R.string.wallet_service_note_scnf_msg,
                            btcfmt.format(amount),
                            btcfmt.unitStr()));
                  } else if (ct == ConfidenceType.DEAD) {
                    mLogger.info(String.format("send %d dead", amount));
                    // Notify dead.
                    showEventNotification(
                        noteId,
                        R.drawable.ic_note_bc_gray,
                        mRes.getString(R.string.wallet_service_note_sdead_title, btcfmt.unitStr()),
                        mRes.getString(
                            R.string.wallet_service_note_sdead_msg,
                            btcfmt.format(amount),
                            btcfmt.unitStr()));

                  } else {
                    mLogger.info(String.format("send %d unknown", amount));
                  }

                  // We're all done listening ...
                  txconf.removeEventListener(this);
                }
              };

          txconf.addEventListener(listener);
        }
Example #30
0
  private void putInternal(Block block, StatusContext ctx) {
    long t1 = System.currentTimeMillis();
    Sha256Hash hash = block.getHash();

    ctx.setStatus("BLOCK_CHECK_EXIST");
    if (file_db.getBlockMap().containsKey(hash)) {
      imported_blocks.incrementAndGet();
      return;
    }
    // Mark block as in progress

    Semaphore block_wait_sem;
    synchronized (in_progress) {
      block_wait_sem = in_progress.get(hash);
      if (block_wait_sem == null) {
        block_wait_sem = new Semaphore(0);
        in_progress.put(hash, block_wait_sem);
      }
    }

    // Kick off threaded storage of transactions
    int size = 0;

    ctx.setStatus("BLOCK_TX_CACHE_INSERT");
    synchronized (transaction_cache) {
      for (Transaction tx : block.getTransactions()) {
        transaction_cache.put(tx.getHash(), SerializedTransaction.scrubTransaction(params, tx));
      }
    }

    ctx.setStatus("BLOCK_TX_ENQUE");
    LinkedList<Sha256Hash> tx_list = new LinkedList<Sha256Hash>();

    Collection<Map.Entry<String, Sha256Hash>> addrTxLst =
        new LinkedList<Map.Entry<String, Sha256Hash>>();
    Map<Sha256Hash, SerializedTransaction> txs_map =
        new HashMap<Sha256Hash, SerializedTransaction>();

    for (Transaction tx : block.getTransactions()) {
      imported_transactions.incrementAndGet();
      Collection<String> addrs = getAllAddresses(tx, true);

      for (String addr : addrs) {
        addrTxLst.add(
            new java.util.AbstractMap.SimpleEntry<String, Sha256Hash>(addr, tx.getHash()));
      }

      txs_map.put(tx.getHash(), new SerializedTransaction(tx));

      tx_list.add(tx.getHash());
      size++;
    }

    ctx.setStatus("TX_SAVEALL");
    file_db.getTransactionMap().putAll(txs_map);

    ctx.setStatus("BLOCK_TX_MAP_ADD");
    file_db.addTxsToBlockMap(tx_list, hash);

    ctx.setStatus("ADDR_SAVEALL");
    file_db.addAddressesToTxMap(addrTxLst);

    int h = block_store.getHeight(hash);

    for (Transaction tx : block.getTransactions()) {
      Collection<String> addrs = getAllAddresses(tx, true);
      ctx.setStatus("TX_NOTIFY");
      jelly.getElectrumNotifier().notifyNewTransaction(tx, addrs, h);
      ctx.setStatus("TX_DONE");
    }

    // Once all transactions are in, check for prev block in this store

    ctx.setStatus("BLOCK_WAIT_PREV");
    Sha256Hash prev_hash = block.getPrevBlockHash();

    waitForBlockStored(prev_hash);

    // System.out.println("Block " + hash + " " + Util.measureSerialization(new
    // SerializedBlock(block)));

    ctx.setStatus("BLOCK_SAVE");
    file_db.getBlockMap().put(hash, new SerializedBlock(block));

    block_wait_sem.release(1024);
    boolean wait_for_utxo = false;
    if (jelly.isUpToDate() && jelly.getUtxoTrieMgr().isUpToDate()) {
      wait_for_utxo = true;
    }

    jelly.getUtxoTrieMgr().notifyBlock(wait_for_utxo);
    if (wait_for_utxo) {
      jelly.getEventLog().alarm("UTXO root hash: " + jelly.getUtxoTrieMgr().getRootHash());
    }
    jelly.getElectrumNotifier().notifyNewBlock(block);

    long t2 = System.currentTimeMillis();
    DecimalFormat df = new DecimalFormat("0.000");
    double sec = (t2 - t1) / 1000.0;

    if (h % block_print_every == 0) {
      jelly
          .getEventLog()
          .alarm(
              "Saved block: "
                  + hash
                  + " - "
                  + h
                  + " - "
                  + size
                  + " ("
                  + df.format(sec)
                  + " seconds)");
    }
    jelly
        .getEventLog()
        .log(
            "Saved block: "
                + hash
                + " - "
                + h
                + " - "
                + size
                + " ("
                + df.format(sec)
                + " seconds)");

    imported_blocks.incrementAndGet();
  }