diff --git a/core/src/main/java/org/bitcoinj/core/Block.java b/core/src/main/java/org/bitcoinj/core/Block.java index 54bc937f339..6d94e4b6166 100644 --- a/core/src/main/java/org/bitcoinj/core/Block.java +++ b/core/src/main/java/org/bitcoinj/core/Block.java @@ -28,6 +28,7 @@ import org.bitcoinj.base.internal.InternalUtils; import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptBuilder; +import org.jspecify.annotations.NonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -120,9 +121,12 @@ public enum VerifyFlag { // Fields defined as part of the protocol format. private final long version; + @NonNull private final Sha256Hash prevHash; // previous block private Sha256Hash merkleRoot, witnessRoot; + @NonNull private Instant time; + @NonNull private Difficulty difficultyTarget; // "nBits" private long nonce; @@ -151,13 +155,9 @@ public static Block read(ByteBuffer payload) throws BufferUnderflowException, Pr long nonce = ByteUtils.readUint32(payload); payload.reset(); // read again from the mark for the hash Sha256Hash hash = Sha256Hash.wrapReversed(Sha256Hash.hashTwice(Buffers.readBytes(payload, HEADER_SIZE))); - // transactions - List transactions = payload.hasRemaining() ? // otherwise this message is just a header - readTransactions(payload) : - null; - Block block = new Block(version, prevHash, merkleRoot, time, difficultyTarget, nonce, transactions); - block.hash = hash; - return block; + return payload.hasRemaining() + ? new Block(version, prevHash, merkleRoot, time, difficultyTarget, nonce, readTransactions(payload), hash) // full block + : new Block(version, prevHash, merkleRoot, time, difficultyTarget, nonce, hash); // header } /** @@ -209,6 +209,7 @@ private static List readTransactions(ByteBuffer payload) throws Buf this.difficultyTarget = difficultyTarget; this.nonce = nonce; this.prevHash = Sha256Hash.ZERO_HASH; + this.merkleRoot = calculateMerkleRoot(transactions); this.transactions = new ArrayList<>(Objects.requireNonNull(transactions)); } @@ -224,7 +225,6 @@ private static List readTransactions(ByteBuffer payload) throws Buf */ public Block(long version, Sha256Hash prevHash, Sha256Hash merkleRoot, Instant time, Difficulty difficultyTarget, long nonce, @Nullable List transactions) { - super(); this.version = version; this.prevHash = prevHash; this.merkleRoot = merkleRoot; @@ -236,6 +236,32 @@ public Block(long version, Sha256Hash prevHash, Sha256Hash merkleRoot, Instant t null; } + // block header constructor that takes pre-calculated hash - this block header could be treated as immutable + private Block(long version, Sha256Hash prevHash, Sha256Hash merkleRoot, Instant time, + Difficulty difficultyTarget, long nonce, Sha256Hash hash) { + this.version = version; + this.prevHash = prevHash; + this.merkleRoot = merkleRoot; + this.time = time; + this.difficultyTarget = difficultyTarget; + this.nonce = nonce; + this.transactions = null; + this.hash = hash; + } + + // full block constructor that takes pre-calculated hash - this block could be treated as immutable + private Block(long version, Sha256Hash prevHash, Sha256Hash merkleRoot, Instant time, + Difficulty difficultyTarget, long nonce, @NonNull List transactions, Sha256Hash hash) { + this.version = version; + this.prevHash = prevHash; + this.merkleRoot = merkleRoot; + this.time = time; + this.difficultyTarget = difficultyTarget; + this.nonce = nonce; + this.transactions = new ArrayList<>(Objects.requireNonNull(transactions)); + this.hash = hash; + } + /** @deprecated use {@link #Block(long, Sha256Hash, Sha256Hash, Instant, Difficulty, long, List)} */ @Deprecated public Block(long version, Sha256Hash prevHash, Sha256Hash merkleRoot, Instant time, @@ -266,7 +292,11 @@ public static Block createGenesis(Instant time, long difficultyTarget, long nonc private static List genesisTransactions() { byte[] messageBytes = GENESIS_MESSAGE.getBytes(StandardCharsets.US_ASCII); Script scriptSig = // TODO find out what the pushdata(4) is supposed to mean - new ScriptBuilder().bigNum(STANDARD_MAX_DIFFICULTY_TARGET).bigNum(4).data(messageBytes).build(); + new ScriptBuilder() + .bigNum(Difficulty.STANDARD_MAX_DIFFICULTY_TARGET.compact()) + .bigNum(4) + .data(messageBytes) + .build(); Transaction tx = Transaction.coinbase(scriptSig.program()); tx.addOutput(new TransactionOutput( tx, FIFTY_COINS, ScriptBuilder.createP2PKOutputScript(GENESIS_OUTPUT_PUBKEY).program())); @@ -348,6 +378,7 @@ private Sha256Hash calculateHash() { * the block explorer. If you call this on block 1 in the mainnet chain * you will get "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048". */ + @NonNull public String getHashAsString() { return getHash().toString(); } @@ -356,6 +387,7 @@ public String getHashAsString() { * Returns the hash of the block (which for a valid, solved block should be * below the target). Big endian. */ + @NonNull public Sha256Hash getHash() { if (hash == null) hash = calculateHash(); @@ -366,7 +398,7 @@ public Sha256Hash getHash() { * The number that is one greater than the largest representable SHA-256 * hash. */ - private static BigInteger LARGEST_HASH = BigInteger.ONE.shiftLeft(256); + private static final BigInteger LARGEST_HASH = BigInteger.ONE.shiftLeft(256); /** * Returns the work represented by this block.

@@ -376,6 +408,7 @@ public Sha256Hash getHash() { * hash values. Then the work of the block will be 20. As the target gets * lower, the amount of work goes up. */ + @NonNull public BigInteger getWork() throws VerificationException { BigInteger target = difficultyTarget.asInteger(); return LARGEST_HASH.divide(target.add(BigInteger.ONE)); @@ -386,10 +419,9 @@ public BigInteger getWork() throws VerificationException { * * @return new, header-only {@code Block} */ + @NonNull public Block asHeader() { - Block block = new Block(version, prevHash, getMerkleRoot(), time, difficultyTarget, nonce, null); - block.hash = getHash(); - return block; + return new Block(version, prevHash, getMerkleRoot(), time, difficultyTarget, nonce, getHash()); } /** @deprecated use {@link #asHeader()} */ @@ -485,7 +517,7 @@ private void checkSigOps() throws VerificationException { } private void checkMerkleRoot() throws VerificationException { - Sha256Hash calculatedRoot = calculateMerkleRoot(); + Sha256Hash calculatedRoot = calculateMerkleRoot(transactions); if (!calculatedRoot.equals(merkleRoot)) { log.error("Merkle tree did not verify"); throw new VerificationException("Merkle hashes do not match: " + calculatedRoot + " vs " + merkleRoot); @@ -518,17 +550,17 @@ void checkWitnessRoot() throws VerificationException { } } - private Sha256Hash calculateMerkleRoot() { - List tree = buildMerkleTree(false); + private static Sha256Hash calculateMerkleRoot(List transactions) { + List tree = buildMerkleTree(transactions, false); return tree.get(tree.size() - 1); } - private Sha256Hash calculateWitnessRoot() { - List tree = buildMerkleTree(true); + private static Sha256Hash calculateWitnessRoot(List transactions) { + List tree = buildMerkleTree(transactions, true); return tree.get(tree.size() - 1); } - private List buildMerkleTree(boolean useWTxId) { + private static List buildMerkleTree(List transactions, boolean useWTxId) { // The Merkle root is based on a tree of hashes calculated from the transactions: // // root @@ -626,11 +658,12 @@ public int hashCode() { /** * Returns the merkle root in big endian form, calculating it from transactions if necessary. */ + @NonNull public Sha256Hash getMerkleRoot() { if (merkleRoot == null) { //TODO check if this is really necessary. unCacheHeader(); - merkleRoot = calculateMerkleRoot(); + merkleRoot = calculateMerkleRoot(transactions); } return merkleRoot; } @@ -646,9 +679,10 @@ void setMerkleRoot(Sha256Hash value) { /** * Returns the witness root in big endian form, calculating it from transactions if necessary. */ + @NonNull public Sha256Hash getWitnessRoot() { if (witnessRoot == null) - witnessRoot = calculateWitnessRoot(); + witnessRoot = calculateWitnessRoot(transactions); return witnessRoot; } @@ -694,6 +728,7 @@ public long getVersion() { * * @return hash of the previous block */ + @NonNull public Sha256Hash prevHash() { return prevHash; } @@ -707,6 +742,7 @@ public Sha256Hash getPrevBlockHash() { /** * Returns the time at which the block was solved and broadcast, according to the clock of the solving node. */ + @NonNull public Instant time() { return time; } @@ -737,6 +773,7 @@ void setTime(Instant time) { * That number is the result of applying a formula to the underlying difficulty to normalize the minimum to 1. * Calculating the difficulty that way is currently unsupported. */ + @NonNull public Difficulty difficultyTarget() { return difficultyTarget; } @@ -749,7 +786,7 @@ public long getDifficultyTarget() { /** Sets the difficulty target. */ // For testing only - void setDifficultyTarget(Difficulty difficultyTarget) { + void setDifficultyTarget(@NonNull Difficulty difficultyTarget) { unCacheHeader(); this.difficultyTarget = difficultyTarget; this.hash = null;