Repository: ZcashFoundation/zebra
Pull request #1256 (2020-11-05 04:24:48)

Fix minimum difficulty bugs
## Motivation

There are a bunch of bugs in Zebra's difficulty implementation, and the Zcash difficulty specifications also have some bugs.

## Solution

New changes:
- [x] Revise the testnet minimum difficulty code based on `zcashd`'s implementation zcash/zips#416
  - ZIPs 205 and 208 are wrong 
  - the min-difficulty rules change difficulty adjustment, not the difficulty filter
  - all blocks pass the default difficulty filter
- [x] Turn `expect("minimum difficulty activation is checked earlier in the test")` into an `Err` if the rule is not active (and so the function returns `None`)

Tests for spec errors:
  - [x] Test that testnet min-difficulty rules follow `zcashd` https://github.com/zcash/zips/issues/416
  - [x] Test implicit conversion of the minimum difficulty threshold to the compact representation #1277
    - covered by the genesis difficulty equality test

Reviewed changes:
- Refactor block target spacing into NetworkUpgrade methods
- Make ExpandedDifficulty Debug byte order match Hash
  - Bug fix on PR #1171
  - Without this change, test failures are really confusing
- Round-trip the PoWLimit through the compact representation
  - the Zcash specification is ambiguous, see #1277
- Add minimum difficulty testnet block vectors
- Add tests for testnet minimum difficulty blocks
  - the Zcash specification is ambiguous, see #1276
  - find a pre-Blossom and post-Blossom minimum difficulty block, and add them to `zebra_test::vectors::block`, along with their previous blocks
  - make sure the `BlockVerifier` difficulty tests pass on these new blocks

The code in this pull request has:
  - [x] Documentation Comments
  - [x] Unit Tests and Property Tests

## Review

@yaahc has reviewed the Difficulty Contextual Validation RFC, which describes these checks.

## Related Issues

Difficulty Contextual Validation RFC PR #1246 
Difficulty tracking issue #802

Fixes bugs in:
- Previous difficulty check PR #809 
- Reverse the displayed endianness of hashes #1171
- Off-by-one error in testnet minimum difficulty block gap specification #1276 (Zcash specification)
- Implicit conversion of the minimum difficulty threshold to the compact representation #1277 (Zcash specification)
- Testnet min-difficulty rules are incorrectly specified https://github.com/zcash/zips/issues/416

## Follow Up Work

- ~Move the difficulty filter check into the state service~ - obsoleted by zcash/zips#416
- allow minimum difficulty testnet blocks
- Change the difficulty threshold check into a method on `Block` or `ExpandedDifficulty`
  - it's easy to get `<=` confused with `<`

This work is listed in the difficulty tracking issue #802



teor2345: Blocked by the `Duplicate`/`AlreadyVerified` bug #1259.
teor2345: Unblocked by Daira, who said that blocks 299188, 299189, and 299190 are minimum-difficulty blocks.
That's enough info to move forward with this PR, and get it out of a draft state.

I also un-assigned yaahc as the reviewer, because this PR is not ready for review yet. I'm not sure if it actually fixes the bug yet.
teor2345: @yaahc after you approve this PR, I'd like to squash and rebase it myself, so we can keep some of the changes to different modules in different commits.

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 208833304..741d50f2c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -38,15 +38,15 @@ dependencies = [
  "ident_case",
  "proc-macro2 1.0.24",
  "quote 1.0.7",
- "syn 1.0.46",
+ "syn 1.0.48",
  "synstructure",
 ]
 
 [[package]]
 name = "addr2line"
-version = "0.13.0"
+version = "0.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072"
+checksum = "7c0929d69e78dd9bf5408269919fcbcaeb2e35e5d43e5815517cdc6a8e11a423"
 dependencies = [
  "gimli",
 ]
@@ -68,9 +68,9 @@ dependencies = [
 
 [[package]]
 name = "aho-corasick"
-version = "0.7.14"
+version = "0.7.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b476ce7103678b0c6d3d395dbbae31d48ff910bd28be979ba5d48c6351131d0d"
+checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
 dependencies = [
  "memchr",
 ]
@@ -116,9 +116,9 @@ dependencies = [
 
 [[package]]
 name = "arrayvec"
-version = "0.5.1"
+version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
+checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
 
 [[package]]
 name = "atomic-shim"
@@ -148,9 +148,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
 
 [[package]]
 name = "backtrace"
-version = "0.3.53"
+version = "0.3.54"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "707b586e0e2f247cbde68cdd2c3ce69ea7b7be43e1c5b426e37c9319c4b9838e"
+checksum = "2baad346b2d4e94a24347adeee9c7a93f412ee94b9cc26e5b59dea23848e9f28"
 dependencies = [
  "addr2line",
  "cfg-if 1.0.0",
@@ -273,7 +273,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
 dependencies = [
  "arrayref",
- "arrayvec 0.5.1",
+ "arrayvec 0.5.2",
  "constant_time_eq",
 ]
 
@@ -284,7 +284,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9e461a7034e85b211a4acb57ee2e6730b32912b06c08cc242243c39fc21ae6a2"
 dependencies = [
  "arrayref",
- "arrayvec 0.5.1",
+ "arrayvec 0.5.2",
  "constant_time_eq",
 ]
 
@@ -532,9 +532,9 @@ dependencies = [
 
 [[package]]
 name = "const_fn"
-version = "0.4.2"
+version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce90df4c658c62f12d78f7508cf92f9173e5184a539c10bfe54a3107b3ffd0f2"
+checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab"
 
 [[package]]
 name = "constant_time_eq"
@@ -708,7 +708,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7fbaabec2c953050352311293be5c6aba8e141ba19d6811862b232d6fd020484"
 dependencies = [
  "quote 1.0.7",
- "syn 1.0.46",
+ "syn 1.0.48",
 ]
 
 [[package]]
@@ -746,7 +746,7 @@ dependencies = [
  "proc-macro2 1.0.24",
  "quote 1.0.7",
  "strsim 0.9.3",
- "syn 1.0.46",
+ "syn 1.0.48",
 ]
 
 [[package]]
@@ -757,7 +757,7 @@ checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
 dependencies = [
  "darling_core",
  "quote 1.0.7",
- "syn 1.0.46",
+ "syn 1.0.48",
 ]
 
 [[package]]
@@ -812,7 +812,7 @@ checksum = "adc2ab4d5a16117f9029e9a6b5e4e79f4c67f6519bc134210d4d4a04ba31f41b"
 dependencies = [
  "proc-macro2 1.0.24",
  "quote 1.0.7",
- "syn 1.0.46",
+ "syn 1.0.48",
 ]
 
 [[package]]
@@ -880,9 +880,9 @@ dependencies = [
 
 [[package]]
 name = "eyre"
-version = "0.6.1"
+version = "0.6.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4c5cb4dc433c59f09df4b4450f649cbfed61e8a3505abe32e4154066439157e"
+checksum = "5f29abf4740a4778632fe27a4f681ef5b7a6f659aeba3330ac66f48e20cfa3b7"
 dependencies = [
  "indenter",
  "once_cell",
@@ -913,11 +913,11 @@ dependencies = [
 
 [[package]]
 name = "flate2"
-version = "1.0.18"
+version = "1.0.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da80be589a72651dcda34d8b35bcdc9b7254ad06325611074d9cc0fbb19f60ee"
+checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129"
 dependencies = [
- "cfg-if 0.1.10",
+ "cfg-if 1.0.0",
  "crc32fast",
  "libc",
  "miniz_oxide",
@@ -929,6 +929,16 @@ version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
 
+[[package]]
+name = "form_urlencoded"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00"
+dependencies = [
+ "matches",
+ "percent-encoding",
+]
+
 [[package]]
 name = "fs2"
 version = "0.4.3"
@@ -969,9 +979,9 @@ checksum = "0ba62103ce691c2fd80fbae2213dfdda9ce60804973ac6b6e97de818ea7f52c8"
 
 [[package]]
 name = "futures"
-version = "0.3.7"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95314d38584ffbfda215621d723e0a3906f032e03ae5551e650058dac83d4797"
+checksum = "9b3b0c040a1fe6529d30b3c5944b280c7f0dcb2930d2c3062bca967b602583d0"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -984,9 +994,9 @@ dependencies = [
 
 [[package]]
 name = "futures-channel"
-version = "0.3.7"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0448174b01148032eed37ac4aed28963aaaa8cfa93569a08e5b479bbc6c2c151"
+checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64"
 dependencies = [
  "futures-core",
  "futures-sink",
@@ -994,15 +1004,15 @@ dependencies = [
 
 [[package]]
 name = "futures-core"
-version = "0.3.7"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18eaa56102984bed2c88ea39026cff3ce3b4c7f508ca970cedf2450ea10d4e46"
+checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748"
 
 [[package]]
 name = "futures-executor"
-version = "0.3.7"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f5f8e0c9258abaea85e78ebdda17ef9666d390e987f006be6080dfe354b708cb"
+checksum = "4caa2b2b68b880003057c1dd49f1ed937e38f22fcf6c212188a121f08cf40a65"
 dependencies = [
  "futures-core",
  "futures-task",
@@ -1011,42 +1021,42 @@ dependencies = [
 
 [[package]]
 name = "futures-io"
-version = "0.3.7"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e1798854a4727ff944a7b12aa999f58ce7aa81db80d2dfaaf2ba06f065ddd2b"
+checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb"
 
 [[package]]
 name = "futures-macro"
-version = "0.3.7"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e36fccf3fc58563b4a14d265027c627c3b665d7fed489427e88e7cc929559efe"
+checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556"
 dependencies = [
  "proc-macro-hack",
  "proc-macro2 1.0.24",
  "quote 1.0.7",
- "syn 1.0.46",
+ "syn 1.0.48",
 ]
 
 [[package]]
 name = "futures-sink"
-version = "0.3.7"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e3ca3f17d6e8804ae5d3df7a7d35b2b3a6fe89dac84b31872720fc3060a0b11"
+checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d"
 
 [[package]]
 name = "futures-task"
-version = "0.3.7"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96d502af37186c4fef99453df03e374683f8a1eec9dcc1e66b3b82dc8278ce3c"
+checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d"
 dependencies = [
  "once_cell",
 ]
 
 [[package]]
 name = "futures-util"
-version = "0.3.7"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abcb44342f62e6f3e8ac427b8aa815f724fd705dfad060b18ac7866c15bb8e34"
+checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -1136,9 +1146,9 @@ dependencies = [
 
 [[package]]
 name = "gimli"
-version = "0.22.0"
+version = "0.23.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724"
+checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce"
 
 [[package]]
 name = "glob"
@@ -1175,14 +1185,14 @@ checksum = "90454ce4de40b7ca6a8968b5ef367bdab48413962588d0d2b1638d60090c35d7"
 dependencies = [
  "proc-macro2 1.0.24",
  "quote 1.0.7",
- "syn 1.0.46",
+ "syn 1.0.48",
 ]
 
 [[package]]
 name = "h2"
-version = "0.2.6"
+version = "0.2.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "993f9e0baeed60001cf565546b0d3dbe6a6ad23f2bd31644a133c641eccf6d53"
+checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535"
 dependencies = [
  "bytes",
  "fnv",
@@ -1195,6 +1205,7 @@ dependencies = [
  "tokio",
  "tokio-util 0.3.1",
  "tracing",
+ "tracing-futures",
 ]
 
 [[package]]
@@ -1381,11 +1392,11 @@ dependencies = [
 
 [[package]]
 name = "instant"
-version = "0.1.7"
+version = "0.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "63312a18f7ea8760cdd0a7c5aac1a619752a246b833545e3e36d1f81f7cd9e66"
+checksum = "cb1fc4429a33e1f80d41dc9fea4d108a88bec1de8053878898ae448a0b52f613"
 dependencies = [
- "cfg-if 0.1.10",
+ "cfg-if 1.0.0",
 ]
 
 [[package]]
@@ -1457,11 +1468,11 @@ checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
 
 [[package]]
 name = "libloading"
-version = "0.6.4"
+version = "0.6.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3557c9384f7f757f6d139cd3a4c62ef4e850696c16bf27924a5538c8a09717a1"
+checksum = "1090080fe06ec2648d0da3881d9453d97e71a45f00eb179af7fdd7e3f686fdb0"
 dependencies = [
- "cfg-if 0.1.10",
+ "cfg-if 1.0.0",
  "winapi 0.3.9",
 ]
 
@@ -1534,9 +1545,9 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
 
 [[package]]
 name = "memchr"
-version = "2.3.3"
+version = "2.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
+checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
 
 [[package]]
 name = "memoffset"
@@ -1774,9 +1785,9 @@ dependencies = [
 
 [[package]]
 name = "num-integer"
-version = "0.1.43"
+version = "0.1.44"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b"
+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
 dependencies = [
  "autocfg",
  "num-traits",
@@ -1784,9 +1795,9 @@ dependencies = [
 
 [[package]]
 name = "num-traits"
-version = "0.2.12"
+version = "0.2.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
 dependencies = [
  "autocfg",
 ]
@@ -1803,15 +1814,15 @@ dependencies = [
 
 [[package]]
 name = "object"
-version = "0.21.1"
+version = "0.22.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37fd5004feb2ce328a52b0b3d01dbf4ffff72583493900ed15f22d4111c51693"
+checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397"
 
 [[package]]
 name = "once_cell"
-version = "1.4.1"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad"
+checksum = "f53cef67919d7d247eb9a2f128ca9e522789967ef1eb4ccd8c71a95a8aedf596"
 
 [[package]]
 name = "opaque-debug"
@@ -1855,7 +1866,7 @@ version = "1.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7c740e5fbcb6847058b40ac7e5574766c6388f585e184d769910fe0d3a2ca861"
 dependencies = [
- "arrayvec 0.5.1",
+ "arrayvec 0.5.2",
  "bitvec 0.17.4",
  "byte-slice-cast",
  "serde",
@@ -1949,7 +1960,7 @@ checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895"
 dependencies = [
  "proc-macro2 1.0.24",
  "quote 1.0.7",
- "syn 1.0.46",
+ "syn 1.0.48",
 ]
 
 [[package]]
@@ -1960,7 +1971,7 @@ checksum = "81a4ffa594b66bff340084d4081df649a7dc049ac8d7fc458d8e628bfbbb2f86"
 dependencies = [
  "proc-macro2 1.0.24",
  "quote 1.0.7",
- "syn 1.0.46",
+ "syn 1.0.48",
 ]
 
 [[package]]
@@ -1977,9 +1988,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
 
 [[package]]
 name = "ppv-lite86"
-version = "0.2.9"
+version = "0.2.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20"
+checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
 
 [[package]]
 name = "pretty_assertions"
@@ -2013,7 +2024,7 @@ dependencies = [
  "proc-macro-error-attr",
  "proc-macro2 1.0.24",
  "quote 1.0.7",
- "syn 1.0.46",
+ "syn 1.0.48",
  "version_check 0.9.2",
 ]
 
@@ -2030,9 +2041,9 @@ dependencies = [
 
 [[package]]
 name = "proc-macro-hack"
-version = "0.5.18"
+version = "0.5.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598"
+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
 
 [[package]]
 name = "proc-macro-nested"
@@ -2497,7 +2508,7 @@ checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
 dependencies = [
  "proc-macro2 1.0.24",
  "quote 1.0.7",
- "syn 1.0.46",
+ "syn 1.0.48",
 ]
 
 [[package]]
@@ -2513,9 +2524,9 @@ dependencies = [
 
 [[package]]
 name = "serde_yaml"
-version = "0.8.13"
+version = "0.8.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae3e2dd40a7cdc18ca80db804b7f461a39bb721160a85c9a1fa30134bf3c02a5"
+checksum = "f7baae0a99f1a324984bcdc5f0718384c1f69775f1c7eec8b859b71b443e3fd7"
 dependencies = [
  "dtoa",
  "linked-hash-map",
@@ -2564,11 +2575,10 @@ dependencies = [
 
 [[package]]
 name = "signal-hook-registry"
-version = "1.2.1"
+version = "1.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3e12110bc539e657a646068aaf5eb5b63af9d0c1f7b29c97113fad80e15f035"
+checksum = "ce32ea0c6c56d5eacaeb814fbed9960547021d3edd010ded1425f180536b20ab"
 dependencies = [
- "arc-swap",
  "libc",
 ]
 
@@ -2621,9 +2631,9 @@ checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252"
 
 [[package]]
 name = "socket2"
-version = "0.3.15"
+version = "0.3.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44"
+checksum = "7fd8b795c389288baa5f355489c65e71fd48a02104600d15c4cfbc561e9e429d"
 dependencies = [
  "cfg-if 0.1.10",
  "libc",
@@ -2633,9 +2643,9 @@ dependencies = [
 
 [[package]]
 name = "spandoc"
-version = "0.2.0"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c287699b5fa467d5286a2be14bff499f684133c223cf79c46c6251d5920badb"
+checksum = "8c026ab1a725f8e3ee7ca4480ce5c88a67778815e0cdf5d190021565a2426b5b"
 dependencies = [
  "spandoc-attribute",
  "tracing",
@@ -2644,13 +2654,13 @@ dependencies = [
 
 [[package]]
 name = "spandoc-attribute"
-version = "0.1.0"
+version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5254766110c377a921c002ca0775d4e384ba69af951fc4329d9dd77af2c25763"
+checksum = "5bdfb59103e43a0f99a346b57860d50f2138a7008d08acd964e9ac0fef3ae9a5"
 dependencies = [
  "proc-macro2 1.0.24",
  "quote 1.0.7",
- "syn 1.0.46",
+ "syn 1.0.48",
 ]
 
 [[package]]
@@ -2704,7 +2714,7 @@ dependencies = [
  "proc-macro-error",
  "proc-macro2 1.0.24",
  "quote 1.0.7",
- "syn 1.0.46",
+ "syn 1.0.48",
 ]
 
 [[package]]
@@ -2726,9 +2736,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "1.0.46"
+version = "1.0.48"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ad5de3220ea04da322618ded2c42233d02baca219d6f160a3e9c87cda16c942"
+checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac"
 dependencies = [
  "proc-macro2 1.0.24",
  "quote 1.0.7",
@@ -2743,7 +2753,7 @@ checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
 dependencies = [
  "proc-macro2 1.0.24",
  "quote 1.0.7",
- "syn 1.0.46",
+ "syn 1.0.48",
  "unicode-xid 0.2.1",
 ]
 
@@ -2806,7 +2816,7 @@ checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56"
 dependencies = [
  "proc-macro2 1.0.24",
  "quote 1.0.7",
- "syn 1.0.46",
+ "syn 1.0.48",
 ]
 
 [[package]]
@@ -2831,9 +2841,18 @@ dependencies = [
 
 [[package]]
 name = "tinyvec"
-version = "0.3.4"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "238ce071d267c5710f9d31451efec16c5ee22de34df17cc05e56cbc92e967117"
+checksum = "b78a366903f506d2ad52ca8dc552102ffdd3e937ba8a227f024dc1d1eae28575"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
 
 [[package]]
 name = "tokio"
@@ -2862,13 +2881,13 @@ dependencies = [
 
 [[package]]
 name = "tokio-macros"
-version = "0.2.5"
+version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389"
+checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a"
 dependencies = [
  "proc-macro2 1.0.24",
  "quote 1.0.7",
- "syn 1.0.46",
+ "syn 1.0.48",
 ]
 
 [[package]]
@@ -2997,7 +3016,7 @@ checksum = "80e0ccfc3378da0cce270c946b676a376943f5cd16aeba64568e7939806f4ada"
 dependencies = [
  "proc-macro2 1.0.24",
  "quote 1.0.7",
- "syn 1.0.46",
+ "syn 1.0.48",
 ]
 
 [[package]]
@@ -3135,9 +3154,9 @@ dependencies = [
 
 [[package]]
 name = "unicode-normalization"
-version = "0.1.13"
+version = "0.1.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977"
+checksum = "b7f98e67a4d84f730d343392f9bfff7d21e3fca562b9cb7a43b768350beeddc6"
 dependencies = [
  "tinyvec",
 ]
@@ -3168,10 +3187,11 @@ checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
 
 [[package]]
 name = "url"
-version = "2.1.1"
+version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
+checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e"
 dependencies = [
+ "form_urlencoded",
  "idna",
  "matches",
  "percent-encoding",
@@ -3561,6 +3581,6 @@ checksum = "c3f369ddb18862aba61aa49bf31e74d29f0f162dec753063200e1dc084345d16"
 dependencies = [
  "proc-macro2 1.0.24",
  "quote 1.0.7",
- "syn 1.0.46",
+ "syn 1.0.48",
  "synstructure",
 ]
diff --git a/zebra-chain/src/parameters/network_upgrade.rs b/zebra-chain/src/parameters/network_upgrade.rs
index 3fc8e0916..d3db8f740 100644
--- a/zebra-chain/src/parameters/network_upgrade.rs
+++ b/zebra-chain/src/parameters/network_upgrade.rs
@@ -8,6 +8,8 @@ use crate::parameters::{Network, Network::*};
 use std::collections::{BTreeMap, HashMap};
 use std::ops::Bound::*;
 
+use chrono::Duration;
+
 /// A Zcash network upgrade.
 ///
 /// Network upgrades can change the Zcash network protocol or consensus rules in
@@ -96,6 +98,23 @@ pub(crate) const CONSENSUS_BRANCH_IDS: &[(NetworkUpgrade, ConsensusBranchId)] =
     (Canopy, ConsensusBranchId(0xe9ff75a6)),
 ];
 
+/// The target block spacing before Blossom.
+const PRE_BLOSSOM_POW_TARGET_SPACING: i64 = 150;
+
+/// The target block spacing after Blossom activation.
+const POST_BLOSSOM_POW_TARGET_SPACING: i64 = 75;
+
+/// The multiplier used to derive the testnet minimum difficulty block time gap
+/// threshold.
+///
+/// Based on https://zips.z.cash/zip-0208#minimum-difficulty-blocks-on-the-test-network
+const TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER: i32 = 6;
+
+/// The start height for the testnet minimum difficulty consensus rule.
+///
+/// Based on https://zips.z.cash/zip-0208#minimum-difficulty-blocks-on-the-test-network
+const TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT: block::Height = block::Height(299_188);
+
 impl NetworkUpgrade {
     /// Returns a BTreeMap of activation heights and network upgrades for
     /// `network`.
@@ -163,6 +182,47 @@ impl NetworkUpgrade {
     pub fn branch_id(&self) -> Option<ConsensusBranchId> {
         NetworkUpgrade::branch_id_list().get(&self).cloned()
     }
+
+    /// Returns the target block spacing for the network upgrade.
+    ///
+    /// Based on `PRE_BLOSSOM_POW_TARGET_SPACING` and
+    /// `POST_BLOSSOM_POW_TARGET_SPACING` from the Zcash specification.
+    pub fn target_spacing(&self) -> Duration {
+        let spacing_seconds = match self {
+            Genesis | BeforeOverwinter | Overwinter | Sapling => PRE_BLOSSOM_POW_TARGET_SPACING,
+            Blossom | Heartwood | Canopy => POST_BLOSSOM_POW_TARGET_SPACING,
+        };
+
+        Duration::seconds(spacing_seconds)
+    }
+
+    /// Returns the target block spacing for `network` and `height`.
+    ///
+    /// See `target_spacing` for details.
+    pub fn target_spacing_for_height(network: Network, height: block::Height) -> Duration {
+        NetworkUpgrade::current(network, height).target_spacing()
+    }
+
+    /// Returns the minimum difficulty block spacing for `network` and `height`.
+    /// Returns `None` if the testnet minimum difficulty consensus rule is not active.
+    ///
+    /// Based on https://zips.z.cash/zip-0208#minimum-difficulty-blocks-on-the-test-network
+    ///
+    /// `zcashd` requires a gap that's strictly greater than 6 times the target
+    /// threshold, but ZIP-205 and ZIP-208 are ambiguous. See bug #1276.
+    pub fn minimum_difficulty_spacing_for_height(
+        network: Network,
+        height: block::Height,
+    ) -> Option<Duration> {
+        match (network, height) {
+            (Network::Testnet, height) if height < TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT => None,
+            (Network::Mainnet, _) => None,
+            (Network::Testnet, _) => {
+                let network_upgrade = NetworkUpgrade::current(network, height);
+                Some(network_upgrade.target_spacing() * TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER)
+            }
+        }
+    }
 }
 
 impl ConsensusBranchId {
diff --git a/zebra-chain/src/work/difficulty.rs b/zebra-chain/src/work/difficulty.rs
index 6443e2b27..119a97c3c 100644
--- a/zebra-chain/src/work/difficulty.rs
+++ b/zebra-chain/src/work/difficulty.rs
@@ -98,7 +98,7 @@ impl fmt::Debug for ExpandedDifficulty {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         let mut buf = [0; 32];
         // Use the same byte order as block::Hash
-        self.0.to_little_endian(&mut buf);
+        self.0.to_big_endian(&mut buf);
         f.debug_tuple("ExpandedDifficulty")
             .field(&hex::encode(&buf))
             .finish()
@@ -280,7 +280,16 @@ impl ExpandedDifficulty {
             Network::Testnet => (U256::one() << 251) - 1,
         };
 
-        limit.into()
+        // `zcashd` converts the PoWLimit into a compact representation before
+        // using it to perform difficulty filter checks.
+        //
+        // The Zcash specification converts to compact for the default difficulty
+        // filter, but not for testnet minimum difficulty blocks. (ZIP 205 and
+        // ZIP 208 don't specify this conversion either.) See #1277 for details.
+        ExpandedDifficulty(limit)
+            .to_compact()
+            .to_expanded()
+            .expect("difficulty limits are valid expanded values")
     }
 
     /// Calculate the CompactDifficulty for an expanded difficulty.
diff --git a/zebra-consensus/src/block/check.rs b/zebra-consensus/src/block/check.rs
index eb1ef1bb3..b522d0f36 100644
--- a/zebra-consensus/src/block/check.rs
+++ b/zebra-consensus/src/block/check.rs
@@ -70,12 +70,19 @@ pub fn difficulty_is_valid(
         ))?;
     }
 
-    // Difficulty filter
+    // The difficulty filter is also context-free.
+    //
+    // ZIP 205 and ZIP 208 incorrectly describe testnet minimum difficulty blocks
+    // as a change to the difficulty filter. But in `zcashd`, it is implemented
+    // as a change to the difficulty adjustment algorithm. So we don't need to
+    // do anything special for testnet here.
+    // For details, see https://github.com/zcash/zips/issues/416
     if hash > &difficulty_threshold {
         Err(BlockError::DifficultyFilter(
             *height,
             *hash,
             difficulty_threshold,
+            network,
         ))?;
     }
 
diff --git a/zebra-consensus/src/error.rs b/zebra-consensus/src/error.rs
index bee2f1878..70c002c32 100644
--- a/zebra-consensus/src/error.rs
+++ b/zebra-consensus/src/error.rs
@@ -108,10 +108,13 @@ pub enum BlockError {
         zebra_chain::work::difficulty::ExpandedDifficulty,
     ),
 
-    #[error("block {0:?} has a hash {1:?} that is easier than the difficulty threshold {2:?}")]
+    #[error(
+        "block {0:?} on {3:?} has a hash {1:?} that is easier than its difficulty threshold {2:?}"
+    )]
     DifficultyFilter(
         zebra_chain::block::Height,
         zebra_chain::block::Hash,
         zebra_chain::work::difficulty::ExpandedDifficulty,
+        zebra_chain::parameters::Network,
     ),
 }
diff --git a/zebra-consensus/src/parameters/subsidy.rs b/zebra-consensus/src/parameters/subsidy.rs
index 03894b311..fc5d00a01 100644
--- a/zebra-consensus/src/parameters/subsidy.rs
+++ b/zebra-consensus/src/parameters/subsidy.rs
@@ -1,7 +1,5 @@
 //! Constants for Block Subsidy, Funding Streams, and Founders’ Reward
 
-use std::time::Duration;
-
 use zebra_chain::{amount::COIN, block::Height};
 
 /// An initial period from Genesis to this Height where the block subsidy is gradually incremented. [What is slow-start mining][slow-mining]
@@ -22,15 +20,11 @@ pub const SLOW_START_SHIFT: Height = Height(SLOW_START_INTERVAL.0 / 2);
 /// This calculation is exact, because COIN is divisible by 2, and the division is done last.
 pub const MAX_BLOCK_SUBSIDY: u64 = ((25 * COIN) / 2) as u64;
 
-/// The blocktime before Blossom, used to calculate ratio.
-pub const PRE_BLOSSOM_POW_TARGET_SPACING: Duration = Duration::from_secs(150);
-
-/// The blocktime after Blossom, used to calculate ratio.
-pub const POST_BLOSSOM_POW_TARGET_SPACING: Duration = Duration::from_secs(75);
-
 /// Used as a multiplier to get the new halving interval after Blossom.
-pub const BLOSSOM_POW_TARGET_SPACING_RATIO: u64 =
-    PRE_BLOSSOM_POW_TARGET_SPACING.as_secs() / POST_BLOSSOM_POW_TARGET_SPACING.as_secs();
+///
+/// Calculated as `PRE_BLOSSOM_POW_TARGET_SPACING / POST_BLOSSOM_POW_TARGET_SPACING`
+/// in the Zcash specification.
+pub const BLOSSOM_POW_TARGET_SPACING_RATIO: u64 = 2;
 
 /// Halving is at about every 4 years, before Blossom block time is 150 seconds.
 ///


Test patch:
diff --git a/zebra-chain/src/work/difficulty/tests/vectors.rs b/zebra-chain/src/work/difficulty/tests/vectors.rs
index a906b8e33..b1637f261 100644
--- a/zebra-chain/src/work/difficulty/tests/vectors.rs
+++ b/zebra-chain/src/work/difficulty/tests/vectors.rs
@@ -1,8 +1,8 @@
+use color_eyre::eyre::eyre;
 use color_eyre::eyre::Report;
-use std::sync::Arc;
 
-use crate::block::Block;
 use crate::serialization::ZcashDeserialize;
+use crate::{block::Block, parameters::NetworkUpgrade};
 
 use super::super::*;
 
@@ -36,7 +36,7 @@ fn debug_format() {
     );
     assert_eq!(
         format!("{:?}", ExpandedDifficulty(U256::one())),
-        "ExpandedDifficulty(\"0100000000000000000000000000000000000000000000000000000000000000\")"
+        "ExpandedDifficulty(\"0000000000000000000000000000000000000000000000000000000000000001\")"
     );
     assert_eq!(
         format!("{:?}", ExpandedDifficulty(U256::MAX)),
@@ -44,6 +44,7 @@ fn debug_format() {
     );
 
     assert_eq!(format!("{:?}", Work(0)), "Work(0x0, 0, -inf)");
+    assert_eq!(format!("{:?}", Work(1)), "Work(0x1, 1, 0.00000)");
     assert_eq!(
         format!("{:?}", Work(u8::MAX as u128)),
         "Work(0xff, 255, 7.99435)"
@@ -247,16 +248,21 @@ fn compact_bitcoin_test_vectors() {
 
 /// Test blocks using CompactDifficulty.
 #[test]
-#[spandoc::spandoc]
 fn block_difficulty() -> Result<(), Report> {
+    block_difficulty_for_network(Network::Mainnet)?;
+    block_difficulty_for_network(Network::Testnet)?;
+
+    Ok(())
+}
+
+#[spandoc::spandoc]
+fn block_difficulty_for_network(network: Network) -> Result<(), Report> {
     zebra_test::init();
 
-    let mut blockchain = Vec::new();
-    for b in zebra_test::vectors::BLOCKS.iter() {
-        let block = Arc::<Block>::zcash_deserialize(*b)?;
-        let hash = block.hash();
-        blockchain.push((block.clone(), block.coinbase_height().unwrap(), hash));
-    }
+    let block_iter = match network {
+        Network::Mainnet => zebra_test::vectors::MAINNET_BLOCKS.iter(),
+        Network::Testnet => zebra_test::vectors::TESTNET_BLOCKS.iter(),
+    };
 
     let diff_zero = ExpandedDifficulty(U256::zero());
     let diff_one = ExpandedDifficulty(U256::one());
@@ -267,15 +273,20 @@ fn block_difficulty() -> Result<(), Report> {
 
     let mut cumulative_work = PartialCumulativeWork::default();
     let mut previous_cumulative_work = PartialCumulativeWork::default();
-    for (block, height, hash) in blockchain {
-        /// SPANDOC: Calculate the threshold for block {?height}
+
+    for (&height, block) in block_iter {
+        let block =
+            Block::zcash_deserialize(&block[..]).expect("block test vector should deserialize");
+        let hash = block.hash();
+
+        /// SPANDOC: Calculate the threshold for block {?height, ?network}
         let threshold = block
             .header
             .difficulty_threshold
             .to_expanded()
             .expect("Chain blocks have valid difficulty thresholds.");
 
-        /// SPANDOC: Check the difficulty for block {?height, ?threshold, ?hash}
+        /// SPANDOC: Check the difficulty for block {?height, ?network, ?threshold, ?hash}
         {
             assert!(hash <= threshold);
             // also check the comparison operators work
@@ -284,7 +295,15 @@ fn block_difficulty() -> Result<(), Report> {
             assert!(hash < diff_max);
         }
 
-        /// SPANDOC: Check compact round-trip for block {?height}
+        /// SPANDOC: Check the PoWLimit for block {?height, ?network, ?threshold, ?hash}
+        {
+            // the consensus rule
+            assert!(threshold <= ExpandedDifficulty::target_difficulty_limit(network));
+            // check that ordering is transitive, we checked `hash <= threshold` above
+            assert!(hash <= ExpandedDifficulty::target_difficulty_limit(network));
+        }
+
+        /// SPANDOC: Check compact round-trip for block {?height, ?network}
         {
             let canonical_compact = threshold.to_compact();
 
@@ -292,7 +311,7 @@ fn block_difficulty() -> Result<(), Report> {
                        canonical_compact);
         }
 
-        /// SPANDOC: Check the work for block {?height}
+        /// SPANDOC: Check the work for block {?height, ?network}
         {
             let work = block
                 .header
@@ -317,6 +336,148 @@ fn block_difficulty() -> Result<(), Report> {
     Ok(())
 }
 
+/// Test that the genesis block threshold is PowLimit
+#[test]
+fn genesis_block_difficulty() -> Result<(), Report> {
+    genesis_block_difficulty_for_network(Network::Mainnet)?;
+    genesis_block_difficulty_for_network(Network::Testnet)?;
+
+    Ok(())
+}
+
+#[spandoc::spandoc]
+fn genesis_block_difficulty_for_network(network: Network) -> Result<(), Report> {
+    zebra_test::init();
+
+    let block = match network {
+        Network::Mainnet => zebra_test::vectors::MAINNET_BLOCKS.get(&0),
+        Network::Testnet => zebra_test::vectors::TESTNET_BLOCKS.get(&0),
+    };
+
+    let block = block.expect("test vectors contain the genesis block");
+    let block = Block::zcash_deserialize(&block[..]).expect("block test vector should deserialize");
+    let hash = block.hash();
+
+    /// SPANDOC: Calculate the threshold for the genesis block {?network}
+    let threshold = block
+        .header
+        .difficulty_threshold
+        .to_expanded()
+        .expect("Chain blocks have valid difficulty thresholds.");
+
+    /// SPANDOC: Check the genesis PoWLimit {?network, ?threshold, ?hash}
+    {
+        assert_eq!(threshold, ExpandedDifficulty::target_difficulty_limit(network),
+                   "genesis block difficulty thresholds must be equal to the PoWLimit");
+    }
+
+    Ok(())
+}
+
+/// Test that testnet minimum-difficulty blocks are valid
+#[test]
+#[spandoc::spandoc]
+fn testnet_minimum_difficulty() -> Result<(), Report> {
+    const MINIMUM_DIFFICULTY_HEIGHTS: &[block::Height] = &[
+        // block time gaps greater than 15 minutes (pre-Blossom)
+        block::Height(299_188),
+        block::Height(299_189),
+        block::Height(299_202),
+        // block time gaps greater than 7.5 minutes (Blossom and later)
+        block::Height(584_000),
+        // these 3 blocks have gaps greater than 7.5 minutes and less than 15 minutes
+        block::Height(903_800),
+        block::Height(903_801),
+        block::Height(1_028_500),
+    ];
+
+    for (&height, _block) in zebra_test::vectors::TESTNET_BLOCKS.iter() {
+        let height = block::Height(height);
+
+        /// SPANDOC: Do minimum difficulty checks for testnet block {?height}
+        if MINIMUM_DIFFICULTY_HEIGHTS.contains(&height) {
+            check_testnet_minimum_difficulty_block(height)?;
+        } else {
+            assert!(check_testnet_minimum_difficulty_block(height).is_err(),
+                   "all testnet minimum difficulty block test vectors must be tested by the unit tests. Hint: add the failing block to MINIMUM_DIFFICULTY_HEIGHTS");
+        }
+    }
+
+    Ok(())
+}
+
+/// Check that the testnet block at `height` is a testnet minimum difficulty
+/// block.
+#[spandoc::spandoc]
+fn check_testnet_minimum_difficulty_block(height: block::Height) -> Result<(), Report> {
+    let block = zebra_test::vectors::TESTNET_BLOCKS
+        .get(&height.0)
+        .expect("test vectors contain the specified minimum difficulty block height");
+    let block = Block::zcash_deserialize(&block[..]).expect("block test vector should deserialize");
+    let hash = block.hash();
+
+    /// SPANDOC: Check the testnet minimum difficulty start height {?height, ?hash}
+    if height < block::Height(299_188) {
+        Err(eyre!(
+            "the testnet minimum difficulty rule starts at block 299188"
+        ))?;
+    }
+
+    /// SPANDOC: Make sure testnet minimum difficulty blocks have large time gaps {?height, ?hash}
+    {
+        let previous_block = zebra_test::vectors::TESTNET_BLOCKS.get(&(height.0 - 1));
+        if previous_block.is_none() {
+            Err(eyre!(
+                "test vectors should contain the previous block for each minimum difficulty block"
+            ))?;
+        }
+
+        let previous_block = previous_block.unwrap();
+        let previous_block = Block::zcash_deserialize(&previous_block[..])
+            .expect("block test vector should deserialize");
+        let time_gap = block
+            .header
+            .time
+            .signed_duration_since(previous_block.header.time);
+
+        // zcashd requires a gap that's strictly greater than 6 times the target
+        // threshold, but ZIP-205 and ZIP-208 are ambiguous. See bug #1276.
+        match NetworkUpgrade::minimum_difficulty_spacing_for_height(Network::Testnet, height) {
+            None => Err(eyre!("the minimum difficulty rule is not active"))?,
+            Some(spacing) if (time_gap <= spacing) => Err(eyre!(
+                "minimum difficulty block times must be more than 6 target spacing intervals apart"
+            ))?,
+            _ => {}
+        };
+    }
+
+    // At this point, the current block has passed all the consensus rules that allow
+    // minimum-difficulty blocks. So it is *allowed* to be a minimum-difficulty block, but not
+    // *required* to be one. But at the moment, all test vectors with large gaps are minimum-difficulty
+    // blocks.
+
+    /// SPANDOC: Calculate the threshold for testnet block {?height, ?hash}
+    let threshold = block
+        .header
+        .difficulty_threshold
+        .to_expanded()
+        .expect("Chain blocks have valid difficulty thresholds.");
+
+    /// SPANDOC: Check that the testnet minimum difficulty is the PoWLimit {?height, ?threshold, ?hash}
+    {
+        assert_eq!(threshold, ExpandedDifficulty::target_difficulty_limit(Network::Testnet),
+                   "testnet minimum difficulty thresholds should be equal to the PoWLimit. Hint: Blocks with large gaps are allowed to have the minimum difficulty, but it's not required.");
+        // all blocks pass the minimum difficulty threshold, even if they aren't minimum
+        // difficulty blocks, because it's the lowest permitted difficulty
+        assert!(
+            hash <= ExpandedDifficulty::target_difficulty_limit(Network::Testnet),
+            "testnet minimum difficulty hashes must be less than the PoWLimit"
+        );
+    }
+
+    Ok(())
+}
+
 /// Test ExpandedDifficulty ordering
 #[test]
 #[spandoc::spandoc]
diff --git a/zebra-consensus/src/block/tests.rs b/zebra-consensus/src/block/tests.rs
index 174d73190..82c8c0d14 100644
--- a/zebra-consensus/src/block/tests.rs
+++ b/zebra-consensus/src/block/tests.rs
@@ -234,7 +234,8 @@ fn difficulty_validation_failure() -> Result<(), Report> {
     // Validate the block
     let result = check::difficulty_is_valid(&block.header, Network::Mainnet, &height, &bad_hash)
         .unwrap_err();
-    let expected = BlockError::DifficultyFilter(height, bad_hash, difficulty_threshold);
+    let expected =
+        BlockError::DifficultyFilter(height, bad_hash, difficulty_threshold, Network::Mainnet);
     assert_eq!(expected, result);
 
     Ok(())
diff --git a/zebra-test/src/lib.rs b/zebra-test/src/lib.rs
index 276369f20..46919611a 100644
--- a/zebra-test/src/lib.rs
+++ b/zebra-test/src/lib.rs
@@ -1,4 +1,8 @@
 //! Miscellaneous test code for Zebra.
+
+// Each lazy_static variable uses additional recursion
+#![recursion_limit = "256"]
+
 use color_eyre::section::PanicMessage;
 use owo_colors::OwoColorize;
 use tracing_error::ErrorLayer;
diff --git a/zebra-test/src/vectors/block-test-0-299-187.txt b/zebra-test/src/vectors/block-test-0-299-187.txt
new file mode 100644
index 000000000..4441bb5e3
--- /dev/null
+++ b/zebra-test/src/vectors/block-test-0-299-187.txt
@@ -0,0 +1,1 @@
+040000001e8745663d7183b438bd0e37a9d350f2a5d586e8f3dc2fbe98315f1fd8010000198843867743542ebd4d76f413b706d110c4d8945646ad53d1b9fe114f6c33b62155aa92b3ea1ce41bc92a1c10a7db4893ff92e9a4b3a08694ecf7f999df1568c100b65bd5b4021edf027a1d050f0000000000000000000000000000000000000000000000000003fd4005019a418d043a8857e2cf73f71c364f7e7778fcbd4a088a6c7eca146a06b77c713f4f26fbcd876af67529057c04756ddf1f5b7a11453a033d17decd181eeb911dcecf423665667bcfe5766aa041c379a36bdad14608cb1529cbc6708c5899535335ec29ceaf8b5da1ea2bde01630ef23d23bf479398a0f4674a2252f43dc0148a2b028a93d008f828244eada4fd65afd7f73f27368b2f145093c459048f0cb470ee120781b13f5c8301e55ca0bdd20935032342c7ee50d59f85987ded5f0ca644f46d96b5abd49ce4f4165d4a1a6d3b3a324004b3125f4f36f64ff6c8f2f6fdd2780947531348bf0e33aab7e3679463da53832735377f88ff5e743c4c10719b0e5fd6e3d37073ea10a5fe0da3aaf1de843a74d81c780aa2c58fc98319620f6ce8b679f974167510aae242339137a4c4df13f907c39691065d11ec8f68dcb56e3d1e913dee9196c872c4faf344a0befd29025e653b10af53eb8900bb7c196581ef4c4ede8bea1fbc27d0836679fdb20bd43e4d50b93128174aaaf204c5fe8b176a262b62af57f4a8e62cb5ff90d36b370880c15e1ddfa629370ee1324bb097785c5a3286b10dd884b3204bd6ef13036276aff1fdc4f03a9b12d0278015348df0c5b99235b2d4cd3898163adc3a314a20b7c56dd612ccc1b805b92cd4db6def54a75ee6552caeae4690d724e5e3ab5355983e64a6313b5bff39042412fdb6e3b6a7394f466f566e8fa2dedc1c3fdd1d0153653c2a7bf7a17dc333cab478116f7d58f18909dc420981cfc829ee80a3bb4bcaa04b420fdb86fd1d014e57cd9731a595ed28e2a9ec8f225fe7fef12b0df9bfaa614d2e5fd08066c5e5e873a67bd8bca26255e74ac00d562d833f9cf6aca8c999163ee5df347a132d964f0916b7853019421b139339e58653d25cf82b1e9c94bc749813f063546553a900c27530d661ec019e005f5928a147bd6ae9a279d554626c667951701926da3262743b29e186031baaa3ab5a0ea514c801044cc25bf851a2e4c5d8c0a3d35aeafd52ccb2a4351308f33a224ae532873173fc15c7a8765f6bde0c4701c4d484edc8e2cf36a3567ab2c4554da69a8f87ab0fa12f6f2bce791edbb86162db8d3006fc921854270ee3f945560e6fcd47ad22fa89631131831ed5fd2a14618c380448ea138aa0f5260e3dbb176a563bbe330452a3a8b523dc5da993c502236d5506c9e457ecb1070261b72e2704d9820b10b2cb463a771886bf5c171bd186c79d19eb0919ed95d1d069b066e29d1b554e2a90e34d948bfd6b68d526453869484a4ebcd3f2de0499d7db67028f75546e76d22dcacb32992a3f134f0a5e4badb61174075a0b30f4f13a2c94b806cfca790c6000e90221a68b60b6e26698a224ef70377ed76519097dc8fa661e959f6155384363f66dc0d71a7c0101b265e75f45fd8c92a7809ef4d98d1ca2fb6c68740a6783d1a40558d65ad615aa70d8bc122799ff3b721e668424308e21edbaa962640b77532db840b5e16d33622299fce38a6fc90a7420e7c1c2717f67f76aef068c2c12f4656deb3769122103bf509ddd835ef6d42b98d6e433188cad06dda82ede47a022c468ddf6680c35aca592448a4927c4b340bea53bcd1307ab3f9b54fbfe11ad72e409d614d6d5174adb51c221d6dc8f048c6540214ce6aaef45d414c6596cc58032afaf5357862c18c4dfeb0b6f90982f3dc6e85254e557bd1e0bb8fc0d8f97c9fbee80f1e3a5c4ee44db6e8faa79129228a2748bc713f6b9e6981ee9930ec687176c36068829715cf14ecda56f0265b63efce5575faf037723955bb039a632b3e8f796826cbfcaa2b99618e9fc2bb1c1818fe1b24f20ec35b0e27bb6e9828e6f649649c1242a969f6a3dfa314556bdf4ee9963f6ee1b33010400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0503b3900400ffffffff0200ca9a3b000000001976a9144bb9f3f3de8da40922a1cdb0775a2956289e285188ac80b2e60e0000000017a914fd79e0c84acac223142cf503dc6afde24acac50e8700000000000000000000000000000000000000
diff --git a/zebra-test/src/vectors/block-test-0-299-188.txt b/zebra-test/src/vectors/block-test-0-299-188.txt
new file mode 100644
index 000000000..251b7db91
--- /dev/null
+++ b/zebra-test/src/vectors/block-test-0-299-188.txt
@@ -0,0 +1,1 @@
+040000004ff18b3a2ea1ba2caa9c6b310ec8bfa1e7106aca4a0a176878f4618d190100007404a23449657413177db10158ff2fbeb9b31c5134cdf12f63b47bcf551b297c2155aa92b3ea1ce41bc92a1c10a7db4893ff92e9a4b3a08694ecf7f999df1568d80db65bffff0720020095e608a506f5b7f58f4c990cd8a86a806df95237b950e2a58917033e0000fd400500416392a1daa1676380a275031c2221e11731877913037f57cb8d2af9cc5cb57d4870764580093a5489071e8418e5c22ed7a626a1066310087e7a36979d500fb4f7325dcaeba2960a4627d4d764ae3d5494235301b58f1a8ad6a5c4d8c143d8db5113ab01e45d8bf212dca36bf461f8312b4429bdaff5605ee74cbbd8a2037075bde0a5142942af2708e8dcdd1247db17c01a206251b22c1bbfd1ea50b8d2805d87f325e7dfb6600366a0512092615cb429b57974e174b5ef1254da590cafba687f0796fe7c1771d88c1d38208e86ca19f709afb3729528de41dc1ae63942b9d47240ee3e19ef26bad54223d881152e4c04e4bc3a9ead5fb32ec895065d906759d2b343b93a1a45296299971c1ffb4620198e4de52148f24a5c791b2b7b65a2f749b0fcc7e80819ab0ce2ce9af3e7728148ed1063c10ccbab82ee417c54ba6451f4f4e7dbe975ad5216179c031d0b6003de9c34c38242929d5eb16cf81f27ecc0ae8caeba1fc61a230a0e7a70ab394203c05be239acdf3c9def0b7fc7cb450d8228f5b19242bea4370da46e7f70d71216dd297dd9d8cbec7ed20a6fb2c8b1b5c493c54b06777ad327cf6ce746866173d75db6f47b6bd3d5610be0e947ebca12709791612242b37028734a18a85214f18e7b0c0a9f4e8625a24439b642bcf95937aa481ebc9100e7b11ff9f83aa3a52db30c7603685f9ff905826f1e09c250789a43911ac0cd358ad9bddd682222de5c8a35244201445082ab4e414baa22b97824170f24cc255f116c30997fd1520ec2fb4a357bb7f0c92f99c9a3bb2d233b7c82dc2a216885439049bcd3aa08c861357686be94a93a130b4cfb8dc928ba1401960b4b2b5a48777173d765c3fb9dcd2465fa043525c31b3b316e5e87847b7fab2a3a667d63029461b6f68923d37edd481d7ceff4b454e5eb383c8dc348f8690c008355bc60cde307a9936097deeea7bdf975f3c41e4cafb27a289cc3b4fc13c83653f80d8afb1bdb5b540d05569363d19c10ab3d021c07ef6b757bdb37d9d4284cdc35f1e9b79fdf2e52cf30d37d41571515d5b20fcff97f24449d1e49ec72712b7d9f14d41d517b9120e20419ed912c00f9feb52d8a2fe3cabda2bf17111136e221e856866f0cba71f0e97caf41c1049e366024e543d74a619561759447e5e6799da6f71b5c56810978925aae5b12acdf9612e308272bae838f55864610ae1c716e4e53dd191f83f3752c4e9d758959a1eb0a2fc71f66a0bf65dc78c4423dbd477650cf7a50c626682ace0c9c84498586a30db6a466ce553315a31b09cd27a64f107d25a2a866526b5924f6207f765c730c1ecb3bfa2c288976f022259b241166698495d071312beda08ccdc39b68eb2471d171c5721a4ffa040a3f811fc9856ddc9d7ca4d4a6b3dffe393a80f81879023bf3b3450a96dac495e0d881a2042dd0d1bc84df1041b661918df82a9f05a24fa26fc1dd58aa1246e711bfa0c7302c360d913844bd005979ea0f77bad27e1ac8ccf86c2035af12054355a9bd5a9dcb029d503e0cc68f9047a29025ea16f6e37374e39e25d37763ba1ab4b4af73c99f92b108d46ad37cbe15f76ddae8b10fa49c7f56a2959bd607d3bcaefce5ed0f779651784d8175c74ffc569bfeab77de7fc6f70356173bd0f1027405231f24695d9c8e9605c74683daf2483ab2b4369cc2f4284f09b6f799875d9ccf6f333a1fde9b5906659f1a4bc586beffbaf1863dbf5976774ddcc0d20f0fb29ff36719bbbc9c96f57c75e94ece305c10f1047ad4232f6612a1345e4125c69bffa689a7570b03244024efa79fd4bda566936db67d7471028bcc01f718374260441123bbc010d185daed23190da9aadbde1e4ce5b2532f8c15b568043aaff82665afc21a6b7e010400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603b490040101ffffffff0200ca9a3b000000001976a9144fcef3cd1d27436e43c2ca7e6ab3317e08a57f9c88ac80b2e60e0000000017a914fd79e0c84acac223142cf503dc6afde24acac50e8700000000000000000000000000000000000000
diff --git a/zebra-test/src/vectors/block-test-0-299-189.txt b/zebra-test/src/vectors/block-test-0-299-189.txt
new file mode 100644
index 000000000..1e1f53652
--- /dev/null
+++ b/zebra-test/src/vectors/block-test-0-299-189.txt
@@ -0,0 +1,1 @@
+04000000c338ba3aaea3919c3f1077aa4d636553ac6fee63f6e212e083a043af8c87ed003972d7e8c6d0a7ca7999bcc9851fc88bef0ade38849d62553224699d9f9b09502155aa92b3ea1ce41bc92a1c10a7db4893ff92e9a4b3a08694ecf7f999df15682812b65bffff072001003d67059d9f40d0786370f13b4be590f7fd908f26e496b9f79fc1c0d20000fd4005002965c0648a4c808db486b8be3aa075c6e1d347ed3f749bfb83357687f29a94bbb1c6c969b1e3191b630b13531f9af7a7d1e352820705cd08fd92767570372449073899980fefd91ad4cf193ccae57179eddb1001087356cb4f2a7351f30434227a412b260ddec5c6280cd61d4b8d6a72f175955a3671cc4605ddf592320e53c6e459a461efadc6041367ad539e0a4315e0a01f3a2761500998db93200590bbe2ea5d828a51b9e405225cdb188f216ece785061d14aea958a6f9564251a56710b0669ef25c8c5b887d85dd08b0dee7e9f94055ded81120866dd5a96b2b226cfa44a942595bf2c2577c9d40bf75753da5e55b82855d8cf02fff90d5a13ba6eaff21a5dc340c4133c4bb0f0c598183a7b366dd0950408ac5a71afa6cb79cbfd0dbae80abf6b65243a8eb2bba10b2d950572a259f6ffa58dd31c4d1b3730255c86d826636a22247e846a58aa8b0e7fd2e702e654e733816b9d0484b0def90e65f22122db66cb2ebb5fd7f6fdfbe9fc8bf94d7bd97c26c6253c0bd516da337a7598e41fdec6b4fc58f1e16a312656389b1b2279c4f06210bf8af104ff974afecaeb01ba765304c9def7610e2e619c09a3b8154214c124aa7f3b742795164f4069359f5aeb66a41c48ed41f193b766140d318b3d99d51e6759882559a858b3ad5a9fb7f1480da13313845f3edba6c2b3c9f371bc39d5c0b89bcc07a8a1e0c3d536db745055d1e5476bf9d1eebdc8d40aa4a3bb2e8c0c3282f41408a9c1a20e323c7a574f0a177bdf2e57d019d44b12063fac806671a67a9f3724854f5b950a990483d2e4effbb4ba7d467a50ae710c86117ec21bb1694b15f27ccc5d7ce9721bf485601c7211d9b6ded419de28a470673be607045e3f289028aa61bf554af8635cdd929c1ef333c692c31e8cf03026b76984e68acf8c38e49a495f2b85418cdb3ad400faf27e6f32d2fbeb402501d3caf0b14c594edd4a0132bad7dc5c979962b832c6313c6bf54b3a9293cb13390abc08114c2b6e0e630064c49831a9437ccc7728b08ceabf4ded5fb290b4b388c8e04ed5a0fefad907a4cac56cc5dddf98780746a85cf0da74a9d9567f1964730cba8f2c8d1a9227a2dac080369b2e1fe5f50de477b21cd5315938e7e1c84d2a812a7075168cee1bab20e099e361f7b4fea6306bfa85fe178c592efe0131ed941bdfc0b13ada93514ee1e6d10e019925b92723cc0c100e24e1d0299444d34a72213dcc3ba91b02da7aec4556df235e14d2cb4c6f11294eb51e9bd61ff03f25321f703f3118c84ae4d198fa667355544a079415de89145de90ae7f4459b45e90259131321d20ce386e35d52ae2d1daaa388f24b10226e20ff14af09cd64a07de13aad45a2f1f0b6b82c7a574fb4e4a628dbed9431afebef9fc184598a597d52806b14b6570114dc994a848b7a8f2a1291b974f0bcdb4c913b213205caca902e4477ff59f41672eca90da671386d6d0e662f0c8b8a28e30f0fb3a2af5258a5ebb29b6be03bbf17cb68e7cb217d2d18a18df91b8e57575d4c9302c4d2e76151c441ae66c37e852a84266ba1da594b65beacdc1721cf37f8fd866c26704246587d3880570382a8978d0b20dd493d10e5dad9b9634b761f5b7112a4b622214c4b5b6b27b20076dca1e6252e17f77702cac763e011507ed51a12779feb5b5b0909ff80990e3ad42d8badcdef7a5e46f9287b786e0fe31dff61181855a7f40cee81189db2702a53d9a0bcb50bcf4225af3fd8b0cc1460ebe4c6609646227dbda5784d050b14759d44f5636fbe61a446b5ca0cd5c2605dfe4c20fedf440169b29996a9b4189fdc116937dc73f9a315b3153d0c17bfa9ea0b2293cb9c29655c986e19fd1f61a4ddde31ebb3c6778270a4aca1617476ad7f3b010400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603b590040101ffffffff0200ca9a3b000000001976a9149b4e02e6cd875f8adfa2167018531c4b4e0e0a6188ac80b2e60e0000000017a914fd79e0c84acac223142cf503dc6afde24acac50e8700000000000000000000000000000000000000
diff --git a/zebra-test/src/vectors/block-test-0-299-201.txt b/zebra-test/src/vectors/block-test-0-299-201.txt
new file mode 100644
index 000000000..20ff934e2
--- /dev/null
+++ b/zebra-test/src/vectors/block-test-0-299-201.txt
@@ -0,0 +1,1 @@
+04000000489c76c5bef8b4c9cefd35e4af11438d5dca5c10ed5a7a44bfbc343e164b600094f9fd3a15efdedad8664f543ccb1641205d457ad43573221792870c6c6beba82155aa92b3ea1ce41bc92a1c10a7db4893ff92e9a4b3a08694ecf7f999df15683014b65bfdd30220160033627b85fd00348671bea53e5f13279e9461e077f3b0510e737397b60000fd4005018a2752c205a73b16b0a513e5f8b8ca0d48d40c4638ae02c97f386111f854d57bb1640336c948578324120fe60fd7b3de49e29b2afe86eb6b07ed6a3fd7ca30773491880c268ce3996493987ef9da588a3e477d16c67da902976ca12909c350f19bc089c3db5ca4921e779f41a4c85e7ba459a560a4364c4adbe97cdf0b178a61c7df511053cce26610826da5860841956ff057a77324cbd9f46b3e97169a7ffb1f465a5dff635b0b212515280812235e98a495497b5a25ca57d76c812718f9d0725ae331fccb95efa35fe7c21e7ede78af1a4502dbeba20adfff76066eb4d4724f32867998352235a9c60f1c7fb7f60612d03e54f8c0b53fa94eb318bfbd548a064f154237477849d9866e6d8b73928f5ecaf7d466e820f7e377298d26daeb4e8da2950e7b2c39e9aee14f2f7de92b74c3917e522d9c6033e90a3230541ee59163689ba5755d6cc6674b002a78064002b7a3b92daa687993d2f11aaa3162af25051dc0af228e7f94d057b0f9af6ec66d52d9b4f9f3a736125d100825d30a1fe65dca0bf6d4c4708f6ec80d99f12d10e702e5651db309c30e555a023c88b63dafb5c505064cf9b7e6f2cd31a6602348543253f95a1f969fb2146a7fb55ad5252f117721f2f5dd3ce8c3a0db38e112885b3b8c8bc815a642828dfd7829f9869bd2ff6c227b97e5bddc7975c8c5780b6dc5fb82d16e9ece0809deb50f84465cb04337d5bd5374ea3276971f217231ba92e0eb6b6b8568f0054296d72d16b5687929e31be15d4af80c38b5552b4214aea2167d3393b37bb03e6133ceb760c2abc4f243f71c554d753ccbae1aa10a0f77fb9a446481c42634ecb4796ea33c24bb2b8f0c7ef9a25fa014911b03618ea36d623eadcadf6ff81b0e7b9195543889a881924921217064c56f4c39ec28ca43d2194baff4ce8e639673237552dff8fa538d02194dddd5dc9903e5e0923678976e7167cf9c73d41fc9137fba88f02af9457292cc9be3c51382f35a7905838c149ea897fbefbb787340d9ecaf4d87bbe2920853a59f418a23b3759a66c7a6fa7cd5cf7571cfe80843c0549f4ba10cd32b823870fe4f1d064551b11224c2bd5669d36127eee1ebfb0d7f501b24139cd9fc15399992bb08303a581577f7736cb9ee8dd11b0bce6e228724951dea0f2388c7529febed3e3c10b56c2305c23e9dedd01041b12fc69aba3783f23d57dfcd1c1beb346f9d12d55bcf9b4377f4d454d2a16ed88cb51d5e5bf01b72a4e7a975d1f535a05a12902e9ff03253389aa8fd28c839af85656bd1b464ce61d5f434420c9e4876df237dbf6962a24f14b7bedb084ed9ee8b1ed07751fd8b05d45d70e24da15b792d65083d8ded1136e74ec5059ab96e86d17949abc925a4d7790ae03ac74e8a605a4287feee861bb0caf073288efa58b40648c2fb46981505f2b42319b6ad6352bccbb69aa85a7cd6d6db338ecbb8741716ac7cf692737d3c39cc118e0da3066982d3ae83470329cc18b5e79e50d2d65e341f5b42aa2b99fe1027085cc2152de9abba09f60b2a4d4e76a1485ded4a466d8cc6d391c218bf6d240babb0d6a38417bbbb1b9270a76c893edd509b67511ff855e331dc8713b18aa3f2fe5f242f9ef23ec59332696fe7f1ef410deb89da244af5231ea22ddd5fe10aefdc356cefe1ffb8b9d476d149cd7ac2ffdbe32410916fea9a0588899cefe69a1d6d6206aaea9906c10c01f983d6f3d19fd886d51c10cd17ce59f99fb49a1137f2ab3eaf48b5b3f2e27cad53a40cbd408cccbb0e614dfa37b9838fcf7d814492946370684066bbd93181bc7dcf213da56ab233d34875e83d1a37762a9325ff33eaf55de4c4f722d483bc52db1e7a3835ceda49cbefd2c8349d6fc475c7a1495a6766212ad3d928010400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603c190040101ffffffff0200ca9a3b000000001976a91478f228ff2278032ad67a2a91d03494dd724c8e0088ac80b2e60e0000000017a914fd79e0c84acac223142cf503dc6afde24acac50e8700000000000000000000000000000000000000
diff --git a/zebra-test/src/vectors/block-test-0-299-202.txt b/zebra-test/src/vectors/block-test-0-299-202.txt
new file mode 100644
index 000000000..bbc5556f1
--- /dev/null
+++ b/zebra-test/src/vectors/block-test-0-299-202.txt
@@ -0,0 +1,1 @@
+040000002bc8fe5fbcc33f28637c187713cc66a9d0282e9523b961e6bd98a6584dc2aa00543d074920d46dccfdab357e2f8dfbcb3b5278473bfba0f48b7eea3209fcd5882155aa92b3ea1ce41bc92a1c10a7db4893ff92e9a4b3a08694ecf7f999df1568254db65bffff072000006350564dd6bde56965e94caa700a314a94c0bc822b25ec27989dd8fe0000fd400500ab13ad00b4716bc82cd604203d1445883235463c1742f1bfbb8666e26ac8c497a5657aaddd0a0eeaa0059333c4fbc4976e267f53c97d56cab1dfa2b9e48012d36de271d141919db1d1ee00cce85f285f1fa6ff014318d96993b98902fba19d98cde590af609be18814fcf3a80d87f493b02ffb2ce8f0c552fb30ba62031cd3d1e703de45eb3576a25291d94559c3d3f370872db996db1faa53bf6e7915b1ab53414dab53d583080449a263a4157e1984cf02c308684d11889cda33700d7e064932d4c6f74debc4be9c76d957bcff5e75cd0669f766b56ed90583e3e20ad0c7c844b4e346a0eb30972f831f8eafc2f1752994d7767e731336ffc85e0af12413c88a35b36cbbd2af9724271a6e08155d497bbe2f47a5ed86eb78bf17f5ffe0a97e4d76f58bf50dbb1164654b736b32efa0ebc3355aa4538afd0bea1ab2c366b8cb37945d6361afe0364f75120b92c3f7017eef42278b9a91910646413750caae18821c38ed1db98be17c0babb3dbce92b3502fe5a58f316d5ff711e3fa6b31c4b4d8d341e80cd75323aa403e576e9b4f5a0da135abbc15dd3677b1e1f7f2f29abc198b7e063b7ee2540ba2c4c0d7f2740cc29950eb63ec58be0647b863f101bcbde694a46f6f32766de2303307732faac22cfc659081d9bf74d130b4547a854d3dc805370f93088a4ea67db73d8a2a0fe618d3abcbfee12e04c621723821357f11e2440558f66445772fed92de33f8c5a48621563b2e6c55891ec065e295a0b80eca092830b81c4d9ff3e4e514b9fe2e43f715107dca05155544945fc91f03ace833cacfc341f746517eae0a05ed066b568a8f3258b1d12ba4b2c4909ad8347969264b8bc83f122604917ad7f9136390c64efc74b6d8153d2fe95947dfd0b7bd01f130101112b02b18b14e30006b210ed19b4ec8541395edb9f21a7827573d6c0112b61c57265477f1402213149fbad8ab150d12b320380f4fa80e1261ba17e3075fdadcc18be16de31f1f7ca4e72bd9342b1a1307ecee55558f093edbe5c62d3ada0baa35ef95b10e84d15ec3f131a7f55ba47b0b3897f29e55c078f733e372127aa3ee24f997d6672057bea944775991cb9ea3e10cd0629d77114cffb819f5bc46c616d97b051cb56547d2cca29cdcde1b60311e1eb24e5e4795fe80856d2db25e8a43c8bf0fd7059b49d760d0e495469ed0e28ec4c738e54af38ae9077eedb617905a70fd5ce3fd79583dea8a8f5dac56266107d95cdd6939ea7337283677009723b63c1c4b5992fcabd22c96837c008788cc52bb2e925e75af29085842a6aa0da0138af0417ca53191f91a655c344e094021e64787dfd84142a856227a47a62095dc3e8f0c56e14e7de66437c78cf344e0b082c9f86418e3931288f59c7e3a9d35de5c23ec0c27a9625c875fa3fa03f4d0ad0ac633bedc0f327d4515dc5da4451d6dcb08979e31368d7a6b2c5ea1145675e5d996083afec704bb42e0821f3dfdb0ed63ac6c67e1013b449121141e372ef7a84e46eb021483347425f5370e7afbad11081977575eed9b89a6a6a403c55d9f49f5b339028d1515ae3b46177ac8ff2f31688d4c2b6d97f153431811747a9a15dfd301e216993ff7d7673ec5687c2f671840c4353cdf074169eeb24172b0ab6dddc30f5c7504c75ecf9ba88c3f4f1e51871c8de6e068b3f50b9e1fa0c1d33b5de68d1ca4047fb05c6947277ff9d31316965c5f0ede9b0107a742b4b663b19a3e733e50a919d932d1b26b92a36151368d03c0f415f6acd11f8209ef3ecab9874b6c729960a7409009a031c2d50bee33ade42534d9069712e744a09c6a53919d11daaf3f3390ab3504b30757f1d8273882427c43a3441d286e563f1bdf935625bf33665a3b18610532ae345ffc66010400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603c290040101ffffffff0200ca9a3b000000001976a914dd2f390663eefced093d78dcfce557f6858a657a88ac80b2e60e0000000017a914fd79e0c84acac223142cf503dc6afde24acac50e8700000000000000000000000000000000000000
diff --git a/zebra-test/src/vectors/block.rs b/zebra-test/src/vectors/block.rs
index bfa075b71..a95cdee9c 100644
--- a/zebra-test/src/vectors/block.rs
+++ b/zebra-test/src/vectors/block.rs
@@ -46,6 +46,7 @@ lazy_static! {
             // Sapling
             (419_200, BLOCK_MAINNET_419200_BYTES.as_ref()),
             (419_201, BLOCK_MAINNET_419201_BYTES.as_ref()),
+            // A bad version field
             (434_873, BLOCK_MAINNET_434873_BYTES.as_ref()),
             (653_599, BLOCK_MAINNET_653599_BYTES.as_ref()),
             // Blossom
@@ -55,6 +56,7 @@ lazy_static! {
             // Heartwood
             (903_000, BLOCK_MAINNET_903000_BYTES.as_ref()),
             (903_001, BLOCK_MAINNET_903001_BYTES.as_ref()),
+            // Shielded coinbase x3
             (949_496, BLOCK_MAINNET_949496_BYTES.as_ref()),
             (975_066, BLOCK_MAINNET_975066_BYTES.as_ref()),
             (982_681, BLOCK_MAINNET_982681_BYTES.as_ref()),
@@ -80,6 +82,7 @@ lazy_static! {
             (8, BLOCK_TESTNET_8_BYTES.as_ref()),
             (9, BLOCK_TESTNET_9_BYTES.as_ref()),
             (10, BLOCK_TESTNET_10_BYTES.as_ref()),
+            // A large block
             (141_042, BLOCK_TESTNET_141042_BYTES.as_ref()),
             (207_499, BLOCK_TESTNET_207499_BYTES.as_ref()),
             // Overwinter
@@ -89,6 +92,14 @@ lazy_static! {
             // Sapling
             (280_000, BLOCK_TESTNET_280000_BYTES.as_ref()),
             (280_001, BLOCK_TESTNET_280001_BYTES.as_ref()),
+            (299_187, BLOCK_TESTNET_299187_BYTES.as_ref()),
+            // Minimum-difficulty blocks x2
+            // See zebra_chain's MINIMUM_DIFFICULTY_HEIGHTS for a full list
+            (299_188, BLOCK_TESTNET_299188_BYTES.as_ref()),
+            (299_189, BLOCK_TESTNET_299189_BYTES.as_ref()),
+            (299_201, BLOCK_TESTNET_299201_BYTES.as_ref()),
+            // Minimum-difficulty block
+            (299_202, BLOCK_TESTNET_299202_BYTES.as_ref()),
             (583_999, BLOCK_TESTNET_583999_BYTES.as_ref()),
             // Blossom
             (584_000, BLOCK_TESTNET_584000_BYTES.as_ref()),
@@ -97,6 +108,7 @@ lazy_static! {
             // Heartwood
             (903_800, BLOCK_TESTNET_903800_BYTES.as_ref()),
             (903_801, BLOCK_TESTNET_903801_BYTES.as_ref()),
+            // Shielded coinbase x2
             (914_678, BLOCK_TESTNET_914678_BYTES.as_ref()),
             (925_483, BLOCK_TESTNET_925483_BYTES.as_ref()),
             (1_028_499, BLOCK_TESTNET_1028499_BYTES.as_ref()),
@@ -104,6 +116,7 @@ lazy_static! {
             (1_028_500, BLOCK_TESTNET_1028500_BYTES.as_ref()),
             (1_028_501, BLOCK_TESTNET_1028501_BYTES.as_ref()),
             (1_095_000, BLOCK_TESTNET_1095000_BYTES.as_ref()),
+            // Shielded coinbase
             (1_101_629, BLOCK_TESTNET_1101629_BYTES.as_ref()),
             // TODO: First Halving, see #1104
         ].iter().cloned()
@@ -327,6 +340,28 @@ lazy_static! {
         <Vec<u8>>::from_hex(include_str!("block-test-0-280-001.txt").trim())
         .expect("Block bytes are in valid hex representation");
 
+    // The first minimum difficulty blocks 299188, 299189, 299202 and their previous blocks for context
+    // (pre-Blossom minimum difficulty)
+    //
+    // for i in 299187 299188 299189 299201 299202; do
+    //     zcash-cli -testnet getblock $i 0 > block-test-$[i/1000000]-$[i/1000%1000]-$[i%1000].txt
+    // done
+    pub static ref BLOCK_TESTNET_299187_BYTES: Vec<u8> =
+        <Vec<u8>>::from_hex(include_str!("block-test-0-299-187.txt").trim())
+        .expect("Block bytes are in valid hex representation");
+    pub static ref BLOCK_TESTNET_299188_BYTES: Vec<u8> =
+        <Vec<u8>>::from_hex(include_str!("block-test-0-299-188.txt").trim())
+        .expect("Block bytes are in valid hex representation");
+    pub static ref BLOCK_TESTNET_299189_BYTES: Vec<u8> =
+        <Vec<u8>>::from_hex(include_str!("block-test-0-299-189.txt").trim())
+        .expect("Block bytes are in valid hex representation");
+    pub static ref BLOCK_TESTNET_299201_BYTES: Vec<u8> =
+        <Vec<u8>>::from_hex(include_str!("block-test-0-299-201.txt").trim())
+        .expect("Block bytes are in valid hex representation");
+    pub static ref BLOCK_TESTNET_299202_BYTES: Vec<u8> =
+        <Vec<u8>>::from_hex(include_str!("block-test-0-299-202.txt").trim())
+        .expect("Block bytes are in valid hex representation");
+
     // Blossom transition
     // i=583999
     // zcash-cli -testnet getblock $i 0 > block-test-$[i/1000000]-$[i/1000%1000]-$[i%1000].txt
@@ -336,6 +371,7 @@ lazy_static! {
     pub static ref BLOCK_TESTNET_583999_BYTES: Vec<u8> =
         <Vec<u8>>::from_hex(include_str!("block-test-0-583-999.txt").trim())
         .expect("Block bytes are in valid hex representation");
+    // The first post-Blossom minimum difficulty block is the activation block
     pub static ref BLOCK_TESTNET_584000_BYTES: Vec<u8> =
         <Vec<u8>>::from_hex(include_str!("block-test-0-584-000.txt").trim())
         .expect("Block bytes are in valid hex representation");