public int getOptimalEncodingMessageSize() { if (optimalEncodingMessageSize != 0) return optimalEncodingMessageSize; maybeParseTransactions(); if (optimalEncodingMessageSize != 0) return optimalEncodingMessageSize; optimalEncodingMessageSize = getMessageSize(); return optimalEncodingMessageSize; }
/** * In lazy parsing mode access to getters and setters may throw an unchecked LazyParseException. * If guaranteed safe access is required this method will force parsing to occur immediately thus * ensuring LazyParseExeption will never be thrown from this Message. If the Message contains * child messages (e.g. a Block containing Transaction messages) this will not force child * messages to parse. * * <p>This method ensures parsing of transactions only. * * @throws ProtocolException */ public void ensureParsedTransactions() throws ProtocolException { try { maybeParseTransactions(); } catch (LazyParseException e) { if (e.getCause() instanceof ProtocolException) throw (ProtocolException) e.getCause(); throw new ProtocolException(e); } }
private void unCacheTransactions() { maybeParseTransactions(); transactionBytesValid = false; if (!headerBytesValid) bytes = null; // Current implementation has to uncache headers as well as any change to a tx will alter the // merkle root. In // future we can go more granular and cache merkle root separately so rest of the header does // not need to be // rewritten. unCacheHeader(); // Clear merkleRoot last as it may end up being parsed during unCacheHeader(). merkleRoot = null; }
/** * Checks the block contents * * @throws VerificationException */ public void verifyTransactions() throws VerificationException { // Now we need to check that the body of the block actually matches the headers. The network // won't generate // an invalid block, but if we didn't validate this then an untrusted man-in-the-middle could // obtain the next // valid block from the network and simply replace the transactions in it with their own // fictional // transactions that reference spent or non-existant inputs. if (transactions.isEmpty()) throw new VerificationException("Block had no transactions"); maybeParseTransactions(); if (this.getOptimalEncodingMessageSize() > MAX_BLOCK_SIZE) throw new VerificationException("Block larger than MAX_BLOCK_SIZE"); checkTransactions(); checkMerkleRoot(); checkSigOps(); for (Transaction transaction : transactions) transaction.verify(); }
public List<Transaction> getTransactions() { maybeParseTransactions(); return Collections.unmodifiableList(transactions); }
private List<byte[]> buildMerkleTree() { // The Merkle root is based on a tree of hashes calculated from the transactions: // // root // / \ // A B // / \ / \ // t1 t2 t3 t4 // // The tree is represented as a list: t1,t2,t3,t4,A,B,root where each // entry is a hash. // // The hashing algorithm is double SHA-256. The leaves are a hash of the serialized contents of // the transaction. // The interior nodes are hashes of the concenation of the two child hashes. // // This structure allows the creation of proof that a transaction was included into a block // without having to // provide the full block contents. Instead, you can provide only a Merkle branch. For example // to prove tx2 was // in a block you can just provide tx2, the hash(tx1) and B. Now the other party has everything // they need to // derive the root, which can be checked against the block header. These proofs aren't used // right now but // will be helpful later when we want to download partial block contents. // // Note that if the number of transactions is not even the last tx is repeated to make it so // (see // tx3 above). A tree with 5 transactions would look like this: // // root // / \ // 1 5 // / \ / \ // 2 3 4 4 // / \ / \ / \ // t1 t2 t3 t4 t5 t5 maybeParseTransactions(); ArrayList<byte[]> tree = new ArrayList<byte[]>(); // Start by adding all the hashes of the transactions as leaves of the tree. for (Transaction t : transactions) { tree.add(t.getHash().getBytes()); } int levelOffset = 0; // Offset in the list where the currently processed level starts. // Step through each level, stopping when we reach the root (levelSize == 1). for (int levelSize = transactions.size(); levelSize > 1; levelSize = (levelSize + 1) / 2) { // For each pair of nodes on that level: for (int left = 0; left < levelSize; left += 2) { // The right hand node can be the same as the left hand, in the case where we don't have // enough // transactions. int right = Math.min(left + 1, levelSize - 1); byte[] leftBytes = Utils.reverseBytes(tree.get(levelOffset + left)); byte[] rightBytes = Utils.reverseBytes(tree.get(levelOffset + right)); tree.add(Utils.reverseBytes(doubleDigestTwoBuffers(leftBytes, 0, 32, rightBytes, 0, 32))); } // Move to the next level. levelOffset += levelSize; } return tree; }