Block: make verify*() static helper methods

This commit is contained in:
Andreas Schildbach 2023-04-01 18:30:34 +02:00
parent cf8715eb4b
commit ac2e244bc8
4 changed files with 76 additions and 70 deletions

View file

@ -497,7 +497,7 @@ public abstract class AbstractBlockChain {
// are only lightly verified: presence in a valid connecting block is taken as proof of validity. See the
// article here for more details: https://bitcoinj.github.io/security-model
try {
block.verifyHeader();
Block.verifyHeader(block);
storedPrev = getStoredBlockInCurrentScope(block.getPrevBlockHash());
if (storedPrev != null) {
height = storedPrev.getHeight() + 1;
@ -506,7 +506,7 @@ public abstract class AbstractBlockChain {
}
flags = params.getBlockVerificationFlags(block, versionTally, height);
if (shouldVerifyTransactions())
block.verifyTransactions(height, flags);
Block.verifyTransactions(params, block, height, flags);
} catch (VerificationException e) {
log.error("Failed to verify block: ", e);
log.error(block.getHashAsString());

View file

@ -645,62 +645,6 @@ public class Block extends Message {
}
}
/**
* Checks the block data to ensure it follows the rules laid out in the network parameters. Specifically,
* throws an exception if the proof of work is invalid, or if the timestamp is too far from what it should be.
* This is <b>not</b> everything that is required for a block to be valid, only what is checkable independent
* of the chain and without a transaction index.
*
* @throws VerificationException
*/
public void verifyHeader() throws VerificationException {
// Prove that this block is OK. It might seem that we can just ignore most of these checks given that the
// network is also verifying the blocks, but we cannot as it'd open us to a variety of obscure attacks.
//
// Firstly we need to ensure this block does in fact represent real work done. If the difficulty is high
// enough, it's probably been done by the network.
checkProofOfWork(true);
checkTimestamp();
}
/**
* Checks the block contents
*
* @param height block height, if known, or -1 otherwise. If valid, used
* to validate the coinbase input script of v2 and above blocks.
* @param flags flags to indicate which tests should be applied (i.e.
* whether to test for height in the coinbase transaction).
* @throws VerificationException if there was an error verifying the block.
*/
public void verifyTransactions(final int height, final EnumSet<VerifyFlag> flags) 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-existent inputs.
if (transactions.isEmpty())
throw new VerificationException("Block had no transactions");
if (this.getMessageSize() > MAX_BLOCK_SIZE)
throw new VerificationException("Block larger than MAX_BLOCK_SIZE");
checkTransactions(height, flags);
checkMerkleRoot();
checkSigOps();
for (Transaction tx : transactions)
Transaction.verify(params, tx);
}
/**
* Verifies both the header and that the transactions hash to the merkle root.
*
* @param height block height, if known, or -1 otherwise.
* @param flags flags to indicate which tests should be applied (i.e.
* whether to test for height in the coinbase transaction).
* @throws VerificationException if there was an error verifying the block.
*/
public void verify(final int height, final EnumSet<VerifyFlag> flags) throws VerificationException {
verifyHeader();
verifyTransactions(height, flags);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@ -943,7 +887,7 @@ public class Block extends Message {
b.setTime(bitcoinTime);
b.solve();
try {
b.verifyHeader();
Block.verifyHeader(b);
} catch (VerificationException e) {
throw new RuntimeException(e); // Cannot happen.
}
@ -1026,4 +970,66 @@ public class Block extends Message {
public boolean isBIP65() {
return version >= BLOCK_VERSION_BIP65;
}
/**
* Verifies both the header and that the transactions hash to the merkle root.
*
* @param params parameters for the verification rules
* @param block block to verify
* @param height block height, if known, or -1 otherwise.
* @param flags flags to indicate which tests should be applied (i.e.
* whether to test for height in the coinbase transaction).
* @throws VerificationException if at least one of the rules is violated
*/
public static void verify(NetworkParameters params, Block block, int height, EnumSet<VerifyFlag> flags) throws VerificationException {
verifyHeader(block);
verifyTransactions(params, block, height, flags);
}
/**
* Checks the block data to ensure it follows the rules laid out in the network parameters. Specifically,
* throws an exception if the proof of work is invalid, or if the timestamp is too far from what it should be.
* This is <b>not</b> everything that is required for a block to be valid, only what is checkable independent
* of the chain and without a transaction index.
*
* @param block block to verify
* @throws VerificationException if at least one of the rules is violated
*/
public static void verifyHeader(Block block) throws VerificationException {
// Prove that this block is OK. It might seem that we can just ignore most of these checks given that the
// network is also verifying the blocks, but we cannot as it'd open us to a variety of obscure attacks.
//
// Firstly we need to ensure this block does in fact represent real work done. If the difficulty is high
// enough, it's probably been done by the network.
block.checkProofOfWork(true);
block.checkTimestamp();
}
/**
* Checks the block contents
*
* @param params parameters for the verification rules
* @param block block to verify
* @param height block height, if known, or -1 otherwise. If valid, used
* to validate the coinbase input script of v2 and above blocks.
* @param flags flags to indicate which tests should be applied (i.e.
* whether to test for height in the coinbase transaction).
* @throws VerificationException if at least one of the rules is violated
*/
public static void verifyTransactions(NetworkParameters params, Block block, int height,
EnumSet<VerifyFlag> flags) 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-existent inputs.
if (block.transactions.isEmpty())
throw new VerificationException("Block had no transactions");
if (block.getMessageSize() > MAX_BLOCK_SIZE)
throw new VerificationException("Block larger than MAX_BLOCK_SIZE");
block.checkTransactions(height, flags);
block.checkMerkleRoot();
block.checkSigOps();
for (Transaction tx : block.transactions)
Transaction.verify(params, tx);
}
}

View file

@ -402,7 +402,7 @@ public class BlockChainTest {
b2.setDifficultyTarget(0x1d00ffff);
b2.setPrevBlockHash(Sha256Hash.wrap("00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206"));
assertEquals("000000006c02c8ea6e4ff69651f7fcde348fb9d557a06e6957b65552002a7820", b2.getHashAsString());
b2.verifyHeader();
Block.verifyHeader(b2);
return b2;
}
@ -414,7 +414,7 @@ public class BlockChainTest {
b1.setDifficultyTarget(0x1d00ffff);
b1.setPrevBlockHash(Sha256Hash.wrap("000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"));
assertEquals("00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206", b1.getHashAsString());
b1.verifyHeader();
Block.verifyHeader(b1);
return b1;
}

View file

@ -81,7 +81,7 @@ public class BlockTest {
@Test
public void testBlockVerification() {
block700000.verify(Block.BLOCK_HEIGHT_GENESIS, EnumSet.noneOf(Block.VerifyFlag.class));
Block.verify(TESTNET, block700000, Block.BLOCK_HEIGHT_GENESIS, EnumSet.noneOf(Block.VerifyFlag.class));
}
@Test
@ -103,7 +103,7 @@ public class BlockTest {
Block block = TWEAK_TESTNET.getDefaultSerializer().makeBlock(ByteBuffer.wrap(block700000Bytes));
block.setNonce(12346);
try {
block.verify(Block.BLOCK_HEIGHT_GENESIS, EnumSet.noneOf(Block.VerifyFlag.class));
Block.verify(TWEAK_TESTNET, block, Block.BLOCK_HEIGHT_GENESIS, EnumSet.noneOf(Block.VerifyFlag.class));
fail();
} catch (VerificationException e) {
// Expected.
@ -112,18 +112,18 @@ public class BlockTest {
// from containing artificially weak difficulties.
block.setDifficultyTarget(Block.EASIEST_DIFFICULTY_TARGET);
// Now it should pass.
block.verify(Block.BLOCK_HEIGHT_GENESIS, EnumSet.noneOf(Block.VerifyFlag.class));
Block.verify(TWEAK_TESTNET, block, Block.BLOCK_HEIGHT_GENESIS, EnumSet.noneOf(Block.VerifyFlag.class));
// Break the nonce again at the lower difficulty level so we can try solving for it.
block.setNonce(1);
try {
block.verify(Block.BLOCK_HEIGHT_GENESIS, EnumSet.noneOf(Block.VerifyFlag.class));
Block.verify(TWEAK_TESTNET, block, Block.BLOCK_HEIGHT_GENESIS, EnumSet.noneOf(Block.VerifyFlag.class));
fail();
} catch (VerificationException e) {
// Expected to fail as the nonce is no longer correct.
}
// Should find an acceptable nonce.
block.solve();
block.verify(Block.BLOCK_HEIGHT_GENESIS, EnumSet.noneOf(Block.VerifyFlag.class));
Block.verify(TWEAK_TESTNET, block, Block.BLOCK_HEIGHT_GENESIS, EnumSet.noneOf(Block.VerifyFlag.class));
}
@Test
@ -134,7 +134,7 @@ public class BlockTest {
block700000.transactions.set(0, tx2);
block700000.transactions.set(1, tx1);
try {
block700000.verify(Block.BLOCK_HEIGHT_GENESIS, EnumSet.noneOf(Block.VerifyFlag.class));
Block.verify(TESTNET, block700000, Block.BLOCK_HEIGHT_GENESIS, EnumSet.noneOf(Block.VerifyFlag.class));
fail();
} catch (VerificationException e) {
// We should get here.
@ -168,7 +168,7 @@ public class BlockTest {
// Check block.
assertEquals("0000000004053156021d8e42459d284220a7f6e087bf78f30179c3703ca4eefa", block.getHashAsString());
block.verify(21066, EnumSet.of(Block.VerifyFlag.HEIGHT_IN_COINBASE));
Block.verify(TESTNET, block, 21066, EnumSet.of(Block.VerifyFlag.HEIGHT_IN_COINBASE));
// Testnet block 32768 (hash 000000007590ba495b58338a5806c2b6f10af921a70dbd814e0da3c6957c0c03)
// contains a coinbase transaction whose height is three bytes, but could
@ -180,7 +180,7 @@ public class BlockTest {
// Check block.
assertEquals("000000007590ba495b58338a5806c2b6f10af921a70dbd814e0da3c6957c0c03", block.getHashAsString());
block.verify(32768, EnumSet.of(Block.VerifyFlag.HEIGHT_IN_COINBASE));
Block.verify(TESTNET, block, 32768, EnumSet.of(Block.VerifyFlag.HEIGHT_IN_COINBASE));
}
@Test
@ -199,7 +199,7 @@ public class BlockTest {
// Check block.
assertNotNull(block169482);
block169482.verify(169482, EnumSet.noneOf(Block.VerifyFlag.class));
Block.verify(MAINNET, block169482, 169482, EnumSet.noneOf(Block.VerifyFlag.class));
assertEquals(BLOCK_NONCE, block169482.getNonce());
StoredBlock storedBlock = new StoredBlock(block169482, BigInteger.ONE, 169482); // Nonsense work - not used in test.