Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 59 additions & 22 deletions core/src/main/java/org/bitcoinj/core/Block.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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<Transaction> 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
}

/**
Expand Down Expand Up @@ -209,6 +209,7 @@ private static List<Transaction> 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));
}

Expand All @@ -224,7 +225,6 @@ private static List<Transaction> readTransactions(ByteBuffer payload) throws Buf
*/
public Block(long version, Sha256Hash prevHash, Sha256Hash merkleRoot, Instant time,
Difficulty difficultyTarget, long nonce, @Nullable List<Transaction> transactions) {
super();
this.version = version;
this.prevHash = prevHash;
this.merkleRoot = merkleRoot;
Expand All @@ -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<Transaction> 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,
Expand Down Expand Up @@ -266,7 +292,11 @@ public static Block createGenesis(Instant time, long difficultyTarget, long nonc
private static List<Transaction> 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()));
Expand Down Expand Up @@ -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();
}
Expand All @@ -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();
Expand All @@ -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.<p>
Expand All @@ -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));
Expand All @@ -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()} */
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -518,17 +550,17 @@ void checkWitnessRoot() throws VerificationException {
}
}

private Sha256Hash calculateMerkleRoot() {
List<Sha256Hash> tree = buildMerkleTree(false);
private static Sha256Hash calculateMerkleRoot(List<Transaction> transactions) {
List<Sha256Hash> tree = buildMerkleTree(transactions, false);
return tree.get(tree.size() - 1);
}

private Sha256Hash calculateWitnessRoot() {
List<Sha256Hash> tree = buildMerkleTree(true);
private static Sha256Hash calculateWitnessRoot(List<Transaction> transactions) {
List<Sha256Hash> tree = buildMerkleTree(transactions, true);
return tree.get(tree.size() - 1);
}

private List<Sha256Hash> buildMerkleTree(boolean useWTxId) {
private static List<Sha256Hash> buildMerkleTree(List<Transaction> transactions, boolean useWTxId) {
// The Merkle root is based on a tree of hashes calculated from the transactions:
//
// root
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}

Expand Down Expand Up @@ -694,6 +728,7 @@ public long getVersion() {
*
* @return hash of the previous block
*/
@NonNull
public Sha256Hash prevHash() {
return prevHash;
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
Expand Down
Loading