diff --git a/core/src/main/java/org/bitcoinj/core/Block.java b/core/src/main/java/org/bitcoinj/core/Block.java index f1662dab..942d1efd 100644 --- a/core/src/main/java/org/bitcoinj/core/Block.java +++ b/core/src/main/java/org/bitcoinj/core/Block.java @@ -92,7 +92,7 @@ public class Block extends Message { // Fields defined as part of the protocol format. private long version; private Sha256Hash prevBlockHash; - private Sha256Hash merkleRoot; + private Sha256Hash merkleRoot, witnessRoot; private long time; private long difficultyTarget; // "nBits" private long nonce; @@ -484,11 +484,12 @@ public class Block extends Message { s.append(" (").append(bips).append(')'); s.append('\n'); s.append(" previous block: ").append(getPrevBlockHash()).append("\n"); - s.append(" merkle root: ").append(getMerkleRoot()).append("\n"); s.append(" time: ").append(time).append(" (").append(Utils.dateTimeFormat(time * 1000)).append(")\n"); s.append(" difficulty target (nBits): ").append(difficultyTarget).append("\n"); s.append(" nonce: ").append(nonce).append("\n"); if (transactions != null && transactions.size() > 0) { + s.append(" merkle root: ").append(getMerkleRoot()).append("\n"); + s.append(" witness root: ").append(getWitnessRoot()).append("\n"); s.append(" with ").append(transactions.size()).append(" transaction(s):\n"); for (Transaction tx : transactions) { s.append(tx).append('\n'); @@ -582,11 +583,16 @@ public class Block extends Message { } private Sha256Hash calculateMerkleRoot() { - List tree = buildMerkleTree(); + List tree = buildMerkleTree(false); return Sha256Hash.wrap(tree.get(tree.size() - 1)); } - private List buildMerkleTree() { + private Sha256Hash calculateWitnessRoot() { + List tree = buildMerkleTree(true); + return Sha256Hash.wrap(tree.get(tree.size() - 1)); + } + + private List buildMerkleTree(boolean useWTxId) { // The Merkle root is based on a tree of hashes calculated from the transactions: // // root @@ -617,10 +623,15 @@ public class Block extends Message { // 2 3 4 4 // / \ / \ / \ // t1 t2 t3 t4 t5 t5 - ArrayList tree = new ArrayList<>(); + ArrayList tree = new ArrayList<>(transactions.size()); // Start by adding all the hashes of the transactions as leaves of the tree. - for (Transaction t : transactions) { - tree.add(t.getTxId().getBytes()); + for (Transaction tx : transactions) { + final Sha256Hash id; + if (useWTxId && tx.isCoinBase()) + id = Sha256Hash.ZERO_HASH; + else + id = useWTxId ? tx.getWTxId() : tx.getTxId(); + tree.add(id.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). @@ -749,6 +760,15 @@ public class Block extends Message { hash = null; } + /** + * Returns the witness root in big endian form, calculating it from transactions if necessary. + */ + public Sha256Hash getWitnessRoot() { + if (witnessRoot == null) + witnessRoot = calculateWitnessRoot(); + return witnessRoot; + } + /** Adds a transaction to this block. The nonce and merkle root are invalid after this. */ public void addTransaction(Transaction t) { addTransaction(t, true); diff --git a/core/src/test/java/org/bitcoinj/core/BlockTest.java b/core/src/test/java/org/bitcoinj/core/BlockTest.java index 3a2b0a32..9196647c 100644 --- a/core/src/test/java/org/bitcoinj/core/BlockTest.java +++ b/core/src/test/java/org/bitcoinj/core/BlockTest.java @@ -240,6 +240,14 @@ public class BlockTest { Block block481815 = MAINNET.getDefaultSerializer() .makeBlock(ByteStreams.toByteArray(getClass().getResourceAsStream("block481815.dat"))); assertEquals(2097, block481815.getTransactions().size()); + assertEquals("f115afa8134171a0a686bfbe9667b60ae6fb5f6a439e0265789babc315333262", + block481815.getMerkleRoot().toString()); + + // This block has no witnesses. + for (Transaction tx : block481815.getTransactions()) + assertFalse(tx.hasWitnesses()); + + // Nevertheless, there is a witness commitment (but no witness reserved). Transaction coinbase = block481815.getTransactions().get(0); assertEquals("919a0df2253172a55bebcb9002dbe775b8511f84955b282ca6dae826fdd94f90", coinbase.getTxId().toString()); assertEquals("919a0df2253172a55bebcb9002dbe775b8511f84955b282ca6dae826fdd94f90", @@ -253,6 +261,11 @@ public class BlockTest { Block block481829 = MAINNET.getDefaultSerializer() .makeBlock(ByteStreams.toByteArray(getClass().getResourceAsStream("block481829.dat"))); assertEquals(2020, block481829.getTransactions().size()); + assertEquals("f06f697be2cac7af7ed8cd0b0b81eaa1a39e444c6ebd3697e35ab34461b6c58d", + block481829.getMerkleRoot().toString()); + assertEquals("0a02ddb2f86a14051294f8d98dd6959dd12bf3d016ca816c3db9b32d3e24fc2d", + block481829.getWitnessRoot().toString()); + Transaction coinbase = block481829.getTransactions().get(0); assertEquals("9c1ab453283035800c43eb6461eb46682b81be110a0cb89ee923882a5fd9daa4", coinbase.getTxId().toString()); assertEquals("2bbda73aa4e561e7f849703994cc5e563e4bcf103fb0f6fef5ae44c95c7b83a6",