Repository: ZcashFoundation/zebra
Pull request #1266 (2020-11-06 22:46:13)

Add unit tests for `NonFinalizedState`
## Motivation

Prior to this PR we'd already implemented the `NonFinalizedState` type but otherwise didn't add any new tests to ensure that it functions correctly.

## Solution

This PR adds some basic unit tests to cover basic functionality of the `NonFinalizedState` type. This PR also introduces a bug fix for `finalize` that was discovered when adding the unit tests.

The code in this pull request has:
  - [x] Documentation Comments
  - [x] Unit Tests and Property Tests

## Review

@teor2345

## Related Issues

- [Tracking Issue](https://github.com/ZcashFoundation/zebra/issues/1250)

## Follow Up Work

This PR doesn't add any property tests because much of the behaviour of this struct is influenced by the more or less random `hashes` assigned to arbitrary blocks, so we cannot consistently generate arbitrary chains where one is always "better" than the other. The alternative seems to be generating two arbitrary chains, comparing them to see which is better, and then doing the property test, but this would end up just being a reimplementation of the `NonFinalizedState`'s logic in the test itself to do the ordering check.


yaahc: blocked until https://github.com/ZcashFoundation/zebra/pull/1258 merges

Patch:
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index f31066b79..021831bd4 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -26,7 +26,7 @@ jobs:
       - name: Run cargo-tarpaulin
         uses: actions-rs/tarpaulin@v0.1
         with:
-          version: 'latest'
+          version: '0.16.0'
           timeout: 600
       - name: Upload coverage report to Codecov
         uses: codecov/codecov-action@v1.0.14
diff --git a/Cargo.lock b/Cargo.lock
index 8a23efc4b..e5c9d9706 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]]
@@ -1451,17 +1462,17 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
 
 [[package]]
 name = "libc"
-version = "0.2.79"
+version = "0.2.80"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"
+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",
 ]
 
@@ -2590,13 +2600,13 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
 
 [[package]]
 name = "sled"
-version = "0.34.4"
+version = "0.34.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f72c064e63fbca3138ad07f3588c58093f1684f3a99f60dcfa6d46b87e60fde7"
+checksum = "34a9981af2a13b88f562777db36ed6e8695eef9be8bbbfb1c8b1c83762b8c15c"
 dependencies = [
  "crc32fast",
- "crossbeam-epoch 0.8.2",
- "crossbeam-utils 0.7.2",
+ "crossbeam-epoch 0.9.0",
+ "crossbeam-utils 0.8.0",
  "fs2",
  "fxhash",
  "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 = "b78a366903f506d2ad52ca8dc552102ffdd3e937ba8a227f024dc1d1eae28575"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "238ce071d267c5710f9d31451efec16c5ee22de34df17cc05e56cbc92e967117"
+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",
@@ -3458,6 +3478,7 @@ dependencies = [
  "lazy_static",
  "metrics",
  "once_cell",
+ "primitive-types",
  "proptest",
  "serde",
  "sled",
@@ -3561,6 +3582,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/book/src/dev/rfcs/0005-state-updates.md b/book/src/dev/rfcs/0005-state-updates.md
index e2842f116..3d634ab33 100644
--- a/book/src/dev/rfcs/0005-state-updates.md
+++ b/book/src/dev/rfcs/0005-state-updates.md
@@ -124,11 +124,12 @@ pub enum Request {
 ```
 
 `zebra-state` breaks down its requests into two categories and provides
-different guarantees for each category: requests that modify the state, and requests that
-do not. Requests that update the state are guaranteed to run sequentially and
-will never race against each other. Requests that read state are done
-asynchronously and are guaranteed to read at least the state present at the
-time the request was processed by the service, or a later state present at the time the request future is executed. The state service avoids
+different guarantees for each category: requests that modify the state, and
+requests that do not. Requests that update the state are guaranteed to run
+sequentially and will never race against each other. Requests that read state
+are done asynchronously and are guaranteed to read at least the state present
+at the time the request was processed by the service, or a later state
+present at the time the request future is executed. The state service avoids
 race conditions between the read state and the written state by doing all
 contextual verification internally.
 
@@ -231,7 +232,7 @@ time the request was processed, or a later state.
 At a high level, the in-memory data structures store a collection of chains,
 each rooted at the highest finalized block. Each chain consists of a map from
 heights to blocks. Chains are stored using an ordered map from cumulative work to
-chains, so that the map ordering is the ordering of best to worst chains.
+chains, so that the map ordering is the ordering of worst to best chains.
 
 ### The `Chain` type
 [chain-type]: #chain-type
@@ -343,10 +344,10 @@ Remove the highest height block of the non-finalized portion of a chain.
 
 #### `Ord`
 
-The `Chain` type implements `Ord` for reorganizing chains. First chains
-are compared by their `partial_cumulative_work`. Ties are then broken by
-comparing `block::Hash`es of the tips of each chain. (This tie-breaker
-means that all `Chain`s in the `ChainSet` must have at least one block.)
+The `Chain` type implements `Ord` for reorganizing chains. First chains are
+compared by their `partial_cumulative_work`. Ties are then broken by
+comparing `block::Hash`es of the tips of each chain. (This tie-breaker means
+that all `Chain`s in the `NonFinalizedState` must have at least one block.)
 
 **Note**: Unlike `zcashd`, Zebra does not use block arrival times as a
 tie-breaker for the best tip. Since Zebra downloads blocks in parallel,
@@ -403,11 +404,11 @@ chain and updates all side chains to match.
 3. Remove the lowest height block from the best chain with
    `let finalized_block = best_chain.pop_root();`
 
-4. Add `best_chain` back to `self.chain_set`
+4. Add `best_chain` back to `self.chain_set` if `best_chain` is not empty
 
 5. For each remaining `chain` in `side_chains`
     - remove the lowest height block from `chain`
-    - If that block is equal to `finalized_block` add `chain` back to `self.chain_set`
+    - If that block is equal to `finalized_block` and `chain` is not empty add `chain` back to `self.chain_set`
     - Else, drop `chain`
 
 6. Return `finalized_block`
@@ -617,7 +618,7 @@ Zcash structures are encoded using `ZcashSerialize`/`ZcashDeserialize`.
 - The `hash_by_height` and `height_by_hash` trees provide a bijection between
   block heights and block hashes.  (Since the Sled state only stores finalized
   state, they are actually a bijection).
-  
+
 - The `block_by_height` tree provides a bijection between block heights and block
   data. There is no corresponding `height_by_block` tree: instead, hash the block,
   and use `height_by_hash`. (Since the Sled state only stores finalized state,
@@ -668,7 +669,7 @@ check that `block`'s parent hash is `null` (all zeroes) and its height is `0`.
 
     (Due to a [bug in zcashd](https://github.com/ZcashFoundation/zebra/issues/559),
     genesis block anchors and transactions are ignored during validation.)
-    
+
 4.  Update the `sprout_anchors` and `sapling_anchors` trees with the Sprout and
     Sapling anchors.
 
diff --git a/zebra-chain/src/amount.rs b/zebra-chain/src/amount.rs
index de644a46a..81a30b6fe 100644
--- a/zebra-chain/src/amount.rs
+++ b/zebra-chain/src/amount.rs
@@ -403,6 +403,7 @@ mod test {
     #[test]
     fn test_add_bare() -> Result<()> {
         zebra_test::init();
+
         let one: Amount = 1.try_into()?;
         let neg_one: Amount = (-1).try_into()?;
 
@@ -417,6 +418,7 @@ mod test {
     #[test]
     fn test_add_opt_lhs() -> Result<()> {
         zebra_test::init();
+
         let one: Amount = 1.try_into()?;
         let one = Ok(one);
         let neg_one: Amount = (-1).try_into()?;
@@ -432,6 +434,7 @@ mod test {
     #[test]
     fn test_add_opt_rhs() -> Result<()> {
         zebra_test::init();
+
         let one: Amount = 1.try_into()?;
         let neg_one: Amount = (-1).try_into()?;
         let neg_one = Ok(neg_one);
@@ -447,6 +450,7 @@ mod test {
     #[test]
     fn test_add_opt_both() -> Result<()> {
         zebra_test::init();
+
         let one: Amount = 1.try_into()?;
         let one = Ok(one);
         let neg_one: Amount = (-1).try_into()?;
@@ -463,6 +467,7 @@ mod test {
     #[test]
     fn test_add_assign() -> Result<()> {
         zebra_test::init();
+
         let one: Amount = 1.try_into()?;
         let neg_one: Amount = (-1).try_into()?;
         let mut neg_one = Ok(neg_one);
@@ -479,6 +484,7 @@ mod test {
     #[test]
     fn test_sub_bare() -> Result<()> {
         zebra_test::init();
+
         let one: Amount = 1.try_into()?;
         let zero: Amount = 0.try_into()?;
 
@@ -493,6 +499,7 @@ mod test {
     #[test]
     fn test_sub_opt_lhs() -> Result<()> {
         zebra_test::init();
+
         let one: Amount = 1.try_into()?;
         let one = Ok(one);
         let zero: Amount = 0.try_into()?;
@@ -508,6 +515,7 @@ mod test {
     #[test]
     fn test_sub_opt_rhs() -> Result<()> {
         zebra_test::init();
+
         let one: Amount = 1.try_into()?;
         let zero: Amount = 0.try_into()?;
         let zero = Ok(zero);
@@ -523,6 +531,7 @@ mod test {
     #[test]
     fn test_sub_assign() -> Result<()> {
         zebra_test::init();
+
         let one: Amount = 1.try_into()?;
         let zero: Amount = 0.try_into()?;
         let mut zero = Ok(zero);
@@ -538,6 +547,8 @@ mod test {
 
     #[test]
     fn add_with_diff_constraints() -> Result<()> {
+        zebra_test::init();
+
         let one = Amount::<NonNegative>::try_from(1)?;
         let zero = Amount::<NegativeAllowed>::try_from(0)?;
 
@@ -549,6 +560,8 @@ mod test {
 
     #[test]
     fn deserialize_checks_bounds() -> Result<()> {
+        zebra_test::init();
+
         let big = MAX_MONEY * 2;
         let neg = -10;
 
@@ -572,6 +585,8 @@ mod test {
 
     #[test]
     fn hash() -> Result<()> {
+        zebra_test::init();
+
         let one = Amount::<NonNegative>::try_from(1)?;
         let another_one = Amount::<NonNegative>::try_from(1)?;
         let zero = Amount::<NonNegative>::try_from(0)?;
@@ -601,6 +616,8 @@ mod test {
 
     #[test]
     fn ordering_constraints() -> Result<()> {
+        zebra_test::init();
+
         ordering::<NonNegative, NonNegative>()?;
         ordering::<NonNegative, NegativeAllowed>()?;
         ordering::<NegativeAllowed, NonNegative>()?;
diff --git a/zebra-chain/src/block/height.rs b/zebra-chain/src/block/height.rs
index a9465db5f..0c11b2d74 100644
--- a/zebra-chain/src/block/height.rs
+++ b/zebra-chain/src/block/height.rs
@@ -132,6 +132,8 @@ impl Arbitrary for Height {
 
 #[test]
 fn operator_tests() {
+    zebra_test::init();
+
     assert_eq!(Some(Height(2)), Height(1) + Height(1));
     assert_eq!(None, Height::MAX + Height(1));
     // Bad heights aren't caught at compile-time or runtime, until we add or subtract
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/sapling/address.rs b/zebra-chain/src/sapling/address.rs
index 841f97c7a..2ee9e6ae5 100644
--- a/zebra-chain/src/sapling/address.rs
+++ b/zebra-chain/src/sapling/address.rs
@@ -119,6 +119,8 @@ mod tests {
 
     #[test]
     fn from_str_display() {
+        zebra_test::init();
+
         let zs_addr: Address =
             "zs1qqqqqqqqqqqqqqqqqrjq05nyfku05msvu49mawhg6kr0wwljahypwyk2h88z6975u563j8nfaxd"
                 .parse()
@@ -132,6 +134,8 @@ mod tests {
 
     #[test]
     fn derive_keys_and_addresses() {
+        zebra_test::init();
+
         let spending_key = keys::SpendingKey::new(&mut OsRng);
 
         let spend_authorizing_key = keys::SpendAuthorizingKey::from(spending_key);
@@ -158,6 +162,7 @@ proptest! {
 
     #[test]
     fn sapling_address_roundtrip(zaddr in any::<Address>()) {
+        zebra_test::init();
 
         let string = zaddr.to_string();
 
diff --git a/zebra-chain/src/sapling/commitment.rs b/zebra-chain/src/sapling/commitment.rs
index cd702d32c..562b83b1d 100644
--- a/zebra-chain/src/sapling/commitment.rs
+++ b/zebra-chain/src/sapling/commitment.rs
@@ -288,6 +288,8 @@ mod tests {
 
     #[test]
     fn pedersen_hash_to_point_test_vectors() {
+        zebra_test::init();
+
         const D: [u8; 8] = *b"Zcash_PH";
 
         for test_vector in test_vectors::TEST_VECTORS.iter() {
@@ -302,6 +304,8 @@ mod tests {
 
     #[test]
     fn add() {
+        zebra_test::init();
+
         let identity = ValueCommitment(jubjub::AffinePoint::identity());
 
         let g = ValueCommitment(jubjub::AffinePoint::from_raw_unchecked(
@@ -324,6 +328,8 @@ mod tests {
 
     #[test]
     fn add_assign() {
+        zebra_test::init();
+
         let mut identity = ValueCommitment(jubjub::AffinePoint::identity());
 
         let g = ValueCommitment(jubjub::AffinePoint::from_raw_unchecked(
@@ -349,6 +355,8 @@ mod tests {
 
     #[test]
     fn sub() {
+        zebra_test::init();
+
         let g_point = jubjub::AffinePoint::from_raw_unchecked(
             jubjub::Fq::from_raw([
                 0xe4b3_d35d_f1a7_adfe,
@@ -373,6 +381,8 @@ mod tests {
 
     #[test]
     fn sub_assign() {
+        zebra_test::init();
+
         let g_point = jubjub::AffinePoint::from_raw_unchecked(
             jubjub::Fq::from_raw([
                 0xe4b3_d35d_f1a7_adfe,
@@ -400,6 +410,8 @@ mod tests {
 
     #[test]
     fn sum() {
+        zebra_test::init();
+
         let g_point = jubjub::AffinePoint::from_raw_unchecked(
             jubjub::Fq::from_raw([
                 0xe4b3_d35d_f1a7_adfe,
diff --git a/zebra-chain/src/sapling/note/ciphertexts.rs b/zebra-chain/src/sapling/note/ciphertexts.rs
index f5215d184..5f7b97586 100644
--- a/zebra-chain/src/sapling/note/ciphertexts.rs
+++ b/zebra-chain/src/sapling/note/ciphertexts.rs
@@ -107,6 +107,7 @@ proptest! {
 
     #[test]
     fn encrypted_ciphertext_roundtrip(ec in any::<EncryptedNote>()) {
+        zebra_test::init();
 
         let mut data = Vec::new();
 
@@ -119,6 +120,7 @@ proptest! {
 
     #[test]
     fn out_ciphertext_roundtrip(oc in any::<WrappedNoteKey>()) {
+        zebra_test::init();
 
         let mut data = Vec::new();
 
diff --git a/zebra-chain/src/sapling/tree.rs b/zebra-chain/src/sapling/tree.rs
index 15e06f543..ad45b40fa 100644
--- a/zebra-chain/src/sapling/tree.rs
+++ b/zebra-chain/src/sapling/tree.rs
@@ -161,6 +161,8 @@ mod tests {
 
     #[test]
     fn empty_roots() {
+        zebra_test::init();
+
         // From https://github.com/zcash/librustzcash/blob/master/zcash_primitives/src/merkle_tree.rs#L512
         const HEX_EMPTY_ROOTS: [&str; 33] = [
             "0100000000000000000000000000000000000000000000000000000000000000",
@@ -205,6 +207,7 @@ mod tests {
 
     #[test]
     fn incremental_roots() {
+        zebra_test::init();
         // From https://github.com/zcash/zcash/blob/master/src/test/data/merkle_commitments_sapling.json
         // Byte-reversed from those ones because the original test vectors are loaded using uint256S()
         let commitments = [
diff --git a/zebra-chain/src/serialization/sha256d.rs b/zebra-chain/src/serialization/sha256d.rs
index 4b6acbfec..b861704da 100644
--- a/zebra-chain/src/serialization/sha256d.rs
+++ b/zebra-chain/src/serialization/sha256d.rs
@@ -60,6 +60,8 @@ mod tests {
 
     #[test]
     fn sha256d_checksum() {
+        zebra_test::init();
+
         // https://en.bitcoin.it/wiki/Protocol_documentation#Hashes
         let input = b"hello";
         let checksum = Checksum::from(&input[..]);
@@ -69,6 +71,8 @@ mod tests {
 
     #[test]
     fn sha256d_checksum_debug() {
+        zebra_test::init();
+
         let input = b"hello";
         let checksum = Checksum::from(&input[..]);
 
diff --git a/zebra-chain/src/sprout/address.rs b/zebra-chain/src/sprout/address.rs
index 73c074cbb..bbe9f1c49 100644
--- a/zebra-chain/src/sprout/address.rs
+++ b/zebra-chain/src/sprout/address.rs
@@ -138,6 +138,8 @@ mod tests {
 
     #[test]
     fn from_string_debug() {
+        zebra_test::init();
+
         let string = "zcU1Cd6zYyZCd2VJF8yKgmzjxdiiU1rgTTjEwoN1CGUWCziPkUTXUjXmX7TMqdMNsTfuiGN1jQoVN4kGxUR4sAPN4XZ7pxb";
         let zc_addr = string.parse::<SproutShieldedAddress>().unwrap();
 
@@ -150,6 +152,7 @@ proptest! {
 
     #[test]
     fn zcash_de_serialize_roundtrip(zaddr in any::<SproutShieldedAddress>()) {
+        zebra_test::init();
 
         let mut data = Vec::new();
 
@@ -163,6 +166,7 @@ proptest! {
 
     #[test]
     fn zcash_base58check_roundtrip(zaddr in any::<SproutShieldedAddress>()) {
+        zebra_test::init();
 
         let string = zaddr.to_string();
 
diff --git a/zebra-chain/src/sprout/keys.rs b/zebra-chain/src/sprout/keys.rs
index e9c562a0c..59c5ed52e 100644
--- a/zebra-chain/src/sprout/keys.rs
+++ b/zebra-chain/src/sprout/keys.rs
@@ -344,6 +344,8 @@ mod tests {
     #[test]
     // TODO: test vectors, not just random data
     fn derive_keys() {
+        zebra_test::init();
+
         let spending_key = SpendingKey::new(&mut OsRng);
 
         let receiving_key = ReceivingKey::from(spending_key);
@@ -357,6 +359,7 @@ proptest! {
 
     #[test]
     fn spending_key_roundtrip(sk in any::<SpendingKey>()) {
+        zebra_test::init();
 
         let mut data = Vec::new();
 
@@ -370,6 +373,7 @@ proptest! {
 
     #[test]
     fn spending_key_string_roundtrip(sk in any::<SpendingKey>()) {
+        zebra_test::init();
 
         let string = sk.to_string();
 
@@ -381,6 +385,7 @@ proptest! {
 
     #[test]
     fn incoming_viewing_key_roundtrip(ivk in any::<IncomingViewingKey>()) {
+        zebra_test::init();
 
         let mut data = Vec::new();
 
@@ -394,6 +399,7 @@ proptest! {
 
     #[test]
     fn incoming_viewing_key_string_roundtrip(ivk in any::<IncomingViewingKey>()) {
+        zebra_test::init();
 
         let string = ivk.to_string();
 
diff --git a/zebra-chain/src/sprout/note/ciphertexts.rs b/zebra-chain/src/sprout/note/ciphertexts.rs
index 515355106..818ca0ef7 100644
--- a/zebra-chain/src/sprout/note/ciphertexts.rs
+++ b/zebra-chain/src/sprout/note/ciphertexts.rs
@@ -60,6 +60,7 @@ proptest! {
 
     #[test]
     fn encrypted_ciphertext_roundtrip(ec in any::<EncryptedNote>()) {
+        zebra_test::init();
 
         let mut data = Vec::new();
 
diff --git a/zebra-chain/src/sprout/tree.rs b/zebra-chain/src/sprout/tree.rs
index e020c2310..10f9ec859 100644
--- a/zebra-chain/src/sprout/tree.rs
+++ b/zebra-chain/src/sprout/tree.rs
@@ -176,6 +176,8 @@ mod tests {
 
     #[test]
     fn empty_roots() {
+        zebra_test::init();
+
         // From https://github.com/zcash/zcash/blob/master/src/zcash/IncrementalMerkleTree.cpp#L439
         let hex_empty_roots = [
             "0000000000000000000000000000000000000000000000000000000000000000",
@@ -217,6 +219,8 @@ mod tests {
 
     #[test]
     fn incremental_roots() {
+        zebra_test::init();
+
         // From https://github.com/zcash/zcash/blob/master/src/test/data/merkle_commitments.json
         //
         // Byte-reversed from those ones because the original test vectors are
diff --git a/zebra-chain/src/transaction/hash.rs b/zebra-chain/src/transaction/hash.rs
index 9b7511aaa..ae1e9ea8c 100644
--- a/zebra-chain/src/transaction/hash.rs
+++ b/zebra-chain/src/transaction/hash.rs
@@ -65,6 +65,8 @@ mod tests {
 
     #[test]
     fn transactionhash_from_str() {
+        zebra_test::init();
+
         let hash: Hash = "3166411bd5343e0b284a108f39a929fbbb62619784f8c6dafe520703b5b446bf"
             .parse()
             .unwrap();
diff --git a/zebra-chain/src/transaction/memo.rs b/zebra-chain/src/transaction/memo.rs
index e5d0ad52e..6b09f7cb8 100644
--- a/zebra-chain/src/transaction/memo.rs
+++ b/zebra-chain/src/transaction/memo.rs
@@ -50,6 +50,8 @@ impl fmt::Debug for Memo {
 
 #[test]
 fn memo_fmt() {
+    zebra_test::init();
+
     let memo = Memo(Box::new(
         *b"thiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis \
            iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis \
@@ -74,6 +76,8 @@ fn memo_fmt() {
 
 #[test]
 fn memo_from_string() {
+    zebra_test::init();
+
     let memo = Memo::try_from("foo bar baz".as_ref()).unwrap();
 
     let mut bytes = [0; 512];
diff --git a/zebra-chain/src/transparent/address.rs b/zebra-chain/src/transparent/address.rs
index 923007357..b52eb2686 100644
--- a/zebra-chain/src/transparent/address.rs
+++ b/zebra-chain/src/transparent/address.rs
@@ -257,6 +257,8 @@ mod tests {
 
     #[test]
     fn pubkey_mainnet() {
+        zebra_test::init();
+
         let pub_key = PublicKey::from_slice(&[
             3, 23, 183, 225, 206, 31, 159, 148, 195, 42, 67, 115, 146, 41, 248, 140, 11, 3, 51, 41,
             111, 180, 110, 143, 114, 134, 88, 73, 198, 174, 52, 184, 78,
@@ -270,6 +272,8 @@ mod tests {
 
     #[test]
     fn pubkey_testnet() {
+        zebra_test::init();
+
         let pub_key = PublicKey::from_slice(&[
             3, 23, 183, 225, 206, 31, 159, 148, 195, 42, 67, 115, 146, 41, 248, 140, 11, 3, 51, 41,
             111, 180, 110, 143, 114, 134, 88, 73, 198, 174, 52, 184, 78,
@@ -283,6 +287,8 @@ mod tests {
 
     #[test]
     fn empty_script_mainnet() {
+        zebra_test::init();
+
         let script = Script(vec![0; 20]);
 
         let t_addr = script.to_address(Network::Mainnet);
@@ -292,6 +298,8 @@ mod tests {
 
     #[test]
     fn empty_script_testnet() {
+        zebra_test::init();
+
         let script = Script(vec![0; 20]);
 
         let t_addr = script.to_address(Network::Testnet);
@@ -301,6 +309,8 @@ mod tests {
 
     #[test]
     fn from_string() {
+        zebra_test::init();
+
         let t_addr: Address = "t3Vz22vK5z2LcKEdg16Yv4FFneEL1zg9ojd".parse().unwrap();
 
         assert_eq!(format!("{}", t_addr), "t3Vz22vK5z2LcKEdg16Yv4FFneEL1zg9ojd");
@@ -308,6 +318,8 @@ mod tests {
 
     #[test]
     fn debug() {
+        zebra_test::init();
+
         let t_addr: Address = "t3Vz22vK5z2LcKEdg16Yv4FFneEL1zg9ojd".parse().unwrap();
 
         assert_eq!(
@@ -322,6 +334,7 @@ proptest! {
 
     #[test]
     fn transparent_address_roundtrip(taddr in any::<Address>()) {
+        zebra_test::init();
 
         let mut data = Vec::new();
 
diff --git a/zebra-chain/src/transparent/script.rs b/zebra-chain/src/transparent/script.rs
index 1e92bda7f..d99914acd 100644
--- a/zebra-chain/src/transparent/script.rs
+++ b/zebra-chain/src/transparent/script.rs
@@ -53,6 +53,8 @@ mod proptests {
     proptest! {
         #[test]
         fn script_roundtrip(script in any::<Script>()) {
+            zebra_test::init();
+
             let mut bytes = Cursor::new(Vec::new());
             script.zcash_serialize(&mut bytes)?;
 
diff --git a/zebra-chain/src/work/difficulty.rs b/zebra-chain/src/work/difficulty.rs
index 6443e2b27..45e654a19 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()
@@ -123,6 +123,12 @@ impl fmt::Debug for ExpandedDifficulty {
 #[derive(Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd)]
 pub struct Work(u128);
 
+impl Work {
+    pub fn as_u128(self) -> u128 {
+        self.0
+    }
+}
+
 impl fmt::Debug for Work {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         // There isn't a standard way to represent alternate formats for the
@@ -239,20 +245,33 @@ impl CompactDifficulty {
     /// [Zcash Specification]: https://zips.z.cash/protocol/protocol.pdf#workdef
     pub fn to_work(&self) -> Option<Work> {
         let expanded = self.to_expanded()?;
+        Work::try_from(expanded).ok()
+    }
+}
+
+impl TryFrom<ExpandedDifficulty> for Work {
+    type Error = ();
 
+    fn try_from(expanded: ExpandedDifficulty) -> Result<Self, Self::Error> {
         // We need to compute `2^256 / (expanded + 1)`, but we can't represent
         // 2^256, as it's too large for a u256. However, as 2^256 is at least as
         // large as `expanded + 1`, it is equal to
         // `((2^256 - expanded - 1) / (expanded + 1)) + 1`, or
         let result = (!expanded.0 / (expanded.0 + 1)) + 1;
         if result <= u128::MAX.into() {
-            Some(Work(result.as_u128()))
+            Ok(Work(result.as_u128()))
         } else {
-            None
+            Err(())
         }
     }
 }
 
+impl From<ExpandedDifficulty> for CompactDifficulty {
+    fn from(value: ExpandedDifficulty) -> Self {
+        value.to_compact()
+    }
+}
+
 impl ExpandedDifficulty {
     /// Returns the difficulty of the hash.
     ///
@@ -280,7 +299,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-chain/src/work/difficulty/arbitrary.rs b/zebra-chain/src/work/difficulty/arbitrary.rs
index 586ad83ea..1e6c93032 100644
--- a/zebra-chain/src/work/difficulty/arbitrary.rs
+++ b/zebra-chain/src/work/difficulty/arbitrary.rs
@@ -51,15 +51,7 @@ impl Arbitrary for Work {
         // In the Zcash protocol, a Work is converted from an ExpandedDifficulty.
         // But some randomised difficulties are impractically large, and will
         // never appear in any real-world block. So we just use a random Work value.
-        (any::<u128>())
-            .prop_filter_map("zero Work values are invalid", |w| {
-                if w == 0 {
-                    None
-                } else {
-                    Some(Work(w))
-                }
-            })
-            .boxed()
+        (1..std::u128::MAX).prop_map(Work).boxed()
     }
 
     type Strategy = BoxedStrategy<Self>;
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/block/subsidy/founders_reward.rs b/zebra-consensus/src/block/subsidy/founders_reward.rs
index 953968487..b4b08a3bd 100644
--- a/zebra-consensus/src/block/subsidy/founders_reward.rs
+++ b/zebra-consensus/src/block/subsidy/founders_reward.rs
@@ -33,6 +33,8 @@ mod test {
     use zebra_chain::parameters::NetworkUpgrade::*;
     #[test]
     fn test_founders_reward() -> Result<(), Report> {
+        zebra_test::init();
+
         let network = Network::Mainnet;
         let blossom_height = Blossom.activation_height(network).unwrap();
         let canopy_height = Canopy.activation_height(network).unwrap();
diff --git a/zebra-consensus/src/block/subsidy/general.rs b/zebra-consensus/src/block/subsidy/general.rs
index f8970db38..4da95a34a 100644
--- a/zebra-consensus/src/block/subsidy/general.rs
+++ b/zebra-consensus/src/block/subsidy/general.rs
@@ -109,6 +109,8 @@ mod test {
 
     #[test]
     fn halving_test() -> Result<(), Report> {
+        zebra_test::init();
+
         halving_for_network(Network::Mainnet)?;
         halving_for_network(Network::Testnet)?;
 
@@ -194,6 +196,8 @@ mod test {
 
     #[test]
     fn block_subsidy_test() -> Result<(), Report> {
+        zebra_test::init();
+
         block_subsidy_for_network(Network::Mainnet)?;
         block_subsidy_for_network(Network::Testnet)?;
 
@@ -283,6 +287,8 @@ mod test {
 
     #[test]
     fn miner_subsidy_test() -> Result<(), Report> {
+        zebra_test::init();
+
         miner_subsidy_for_network(Network::Mainnet)?;
         miner_subsidy_for_network(Network::Testnet)?;
 
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.
 ///
diff --git a/zebra-network/src/constants.rs b/zebra-network/src/constants.rs
index 37a4fcfd4..f4b8d59de 100644
--- a/zebra-network/src/constants.rs
+++ b/zebra-network/src/constants.rs
@@ -113,6 +113,8 @@ mod tests {
     /// relies on.
     #[test]
     fn ensure_live_peer_duration_value_matches_others() {
+        zebra_test::init();
+
         let constructed_live_peer_duration =
             HEARTBEAT_INTERVAL + REQUEST_TIMEOUT + REQUEST_TIMEOUT + REQUEST_TIMEOUT;
 
@@ -122,6 +124,8 @@ mod tests {
     /// Make sure that the timeout values are consistent with each other.
     #[test]
     fn ensure_timeouts_consistent() {
+        zebra_test::init();
+
         assert!(HANDSHAKE_TIMEOUT <= REQUEST_TIMEOUT,
                 "Handshakes are requests, so the handshake timeout can't be longer than the timeout for all requests.");
         // This check is particularly important on testnet, which has a small
diff --git a/zebra-network/src/meta_addr.rs b/zebra-network/src/meta_addr.rs
index 792e6d1c7..a122a10f4 100644
--- a/zebra-network/src/meta_addr.rs
+++ b/zebra-network/src/meta_addr.rs
@@ -92,6 +92,8 @@ mod tests {
     // XXX remove this test and replace it with a proptest instance.
     #[test]
     fn sanitize_truncates_timestamps() {
+        zebra_test::init();
+
         let entry = MetaAddr {
             services: PeerServices::default(),
             addr: "127.0.0.1:8233".parse().unwrap(),
diff --git a/zebra-network/src/protocol/external/codec.rs b/zebra-network/src/protocol/external/codec.rs
index baa89832e..ac3c56adc 100644
--- a/zebra-network/src/protocol/external/codec.rs
+++ b/zebra-network/src/protocol/external/codec.rs
@@ -582,6 +582,7 @@ mod tests {
 
     #[test]
     fn version_message_round_trip() {
+        zebra_test::init();
         use std::net::{IpAddr, Ipv4Addr, SocketAddr};
         let services = PeerServices::NODE_NETWORK;
         let timestamp = Utc.timestamp(1_568_000_000, 0);
@@ -631,6 +632,8 @@ mod tests {
 
     #[test]
     fn filterload_message_round_trip() {
+        zebra_test::init();
+
         let mut rt = Runtime::new().unwrap();
 
         let v = Message::FilterLoad {
@@ -665,6 +668,8 @@ mod tests {
 
     #[test]
     fn filterload_message_too_large_round_trip() {
+        zebra_test::init();
+
         let mut rt = Runtime::new().unwrap();
 
         let v = Message::FilterLoad {
diff --git a/zebra-network/src/protocol/external/types.rs b/zebra-network/src/protocol/external/types.rs
index c0d699005..a277343d0 100644
--- a/zebra-network/src/protocol/external/types.rs
+++ b/zebra-network/src/protocol/external/types.rs
@@ -122,6 +122,8 @@ mod proptest {
 
     #[test]
     fn magic_debug() {
+        zebra_test::init();
+
         assert_eq!(format!("{:?}", magics::MAINNET), "Magic(\"24e92764\")");
         assert_eq!(format!("{:?}", magics::TESTNET), "Magic(\"fa1af9bf\")");
     }
@@ -130,6 +132,8 @@ mod proptest {
 
         #[test]
         fn proptest_magic_from_array(data in any::<[u8; 4]>()) {
+            zebra_test::init();
+
             assert_eq!(format!("{:?}", Magic(data)), format!("Magic({:x?})", hex::encode(data)));
         }
     }
@@ -152,6 +156,8 @@ mod test {
     /// Test the min_for_upgrade and current_min functions for `network` with
     /// extreme values.
     fn version_extremes(network: Network) {
+        zebra_test::init();
+
         assert_eq!(
             Version::current_min(network, block::Height(0)),
             Version::min_for_upgrade(network, BeforeOverwinter),
@@ -178,6 +184,8 @@ mod test {
     /// Check that the min_for_upgrade and current_min functions
     /// are consistent for `network`.
     fn version_consistent(network: Network) {
+        zebra_test::init();
+
         let highest_network_upgrade = NetworkUpgrade::current(network, block::Height::MAX);
         assert!(highest_network_upgrade == Canopy || highest_network_upgrade == Heartwood,
                 "expected coverage of all network upgrades: add the new network upgrade to the list in this test");
diff --git a/zebra-state/Cargo.toml b/zebra-state/Cargo.toml
index 97621c506..5bd87e93e 100644
--- a/zebra-state/Cargo.toml
+++ b/zebra-state/Cargo.toml
@@ -15,7 +15,7 @@ dirs = "3.0.1"
 hex = "0.4.2"
 lazy_static = "1.4.0"
 serde = { version = "1", features = ["serde_derive"] }
-sled = "0.34.4"
+sled = "0.34.5"
 
 futures = "0.3.7"
 metrics = "0.12"
@@ -35,3 +35,4 @@ spandoc = "0.2"
 tempdir = "0.3.7"
 tokio = { version = "0.2.22", features = ["full"] }
 proptest = "0.10.1"
+primitive-types = "0.7.2"
diff --git a/zebra-state/src/service/memory_state/chain.rs b/zebra-state/src/service/memory_state/chain.rs
index 50f521a09..82b85bff6 100644
--- a/zebra-state/src/service/memory_state/chain.rs
+++ b/zebra-state/src/service/memory_state/chain.rs
@@ -117,6 +117,10 @@ impl Chain {
             .next_back()
             .expect("only called while blocks is populated")
     }
+
+    pub fn is_empty(&self) -> bool {
+        self.blocks.is_empty()
+    }
 }
 
 /// Helper trait to organize inverse operations done on the `Chain` type. Used to
@@ -414,9 +418,7 @@ impl Ord for Chain {
 
 #[cfg(test)]
 mod tests {
-    use transaction::Transaction;
-
-    use std::{env, fmt, mem};
+    use std::{env, fmt};
 
     use zebra_chain::serialization::ZcashDeserializeInto;
     use zebra_chain::{
@@ -425,6 +427,8 @@ mod tests {
     };
     use zebra_test::prelude::*;
 
+    use crate::tests::FakeChainHelper;
+
     use self::assert_eq;
     use super::*;
 
@@ -436,37 +440,6 @@ mod tests {
         }
     }
 
-    /// Helper trait for constructing "valid" looking chains of blocks
-    trait FakeChainHelper {
-        fn make_fake_child(&self) -> Arc<Block>;
-    }
-
-    impl FakeChainHelper for Block {
-        fn make_fake_child(&self) -> Arc<Block> {
-            let parent_hash = self.hash();
-            let mut child = Block::clone(self);
-            let mut transactions = mem::take(&mut child.transactions);
-            let mut tx = transactions.remove(0);
-
-            let input = match Arc::make_mut(&mut tx) {
-                Transaction::V1 { inputs, .. } => &mut inputs[0],
-                Transaction::V2 { inputs, .. } => &mut inputs[0],
-                Transaction::V3 { inputs, .. } => &mut inputs[0],
-                Transaction::V4 { inputs, .. } => &mut inputs[0],
-            };
-
-            match input {
-                transparent::Input::Coinbase { height, .. } => height.0 += 1,
-                _ => panic!("block must have a coinbase height to create a child"),
-            }
-
-            child.transactions.push(tx);
-            child.header.previous_block_hash = parent_hash;
-
-            Arc::new(child)
-        }
-    }
-
     #[test]
     fn construct_empty() {
         zebra_test::init();
@@ -511,6 +484,25 @@ mod tests {
         Ok(())
     }
 
+    #[test]
+    fn ord_matches_work() -> Result<()> {
+        zebra_test::init();
+        let less_block = zebra_test::vectors::BLOCK_MAINNET_434873_BYTES
+            .zcash_deserialize_into::<Arc<Block>>()?
+            .set_work(1);
+        let more_block = less_block.clone().set_work(10);
+
+        let mut lesser_chain = Chain::default();
+        lesser_chain.push(less_block);
+
+        let mut bigger_chain = Chain::default();
+        bigger_chain.push(more_block);
+
+        assert!(bigger_chain > lesser_chain);
+
+        Ok(())
+    }
+
     fn arbitrary_chain(tip_height: block::Height) -> BoxedStrategy<Vec<Arc<Block>>> {
         Block::partial_chain_strategy(LedgerState::new(tip_height, Network::Mainnet), 100)
     }
diff --git a/zebra-state/src/service/memory_state/non_finalized_state.rs b/zebra-state/src/service/memory_state/non_finalized_state.rs
index 5b6965f06..2384bf070 100644
--- a/zebra-state/src/service/memory_state/non_finalized_state.rs
+++ b/zebra-state/src/service/memory_state/non_finalized_state.rs
@@ -33,15 +33,18 @@ impl NonFinalizedState {
 
         // remove the lowest height block from the best_chain as finalized_block
         let finalized_block = best_chain.pop_root();
+
         // add best_chain back to `self.chain_set`
-        self.chain_set.insert(best_chain);
+        if !best_chain.is_empty() {
+            self.chain_set.insert(best_chain);
+        }
 
         // for each remaining chain in side_chains
         for mut chain in side_chains {
             // remove the first block from `chain`
             let chain_start = chain.pop_root();
             // if block equals finalized_block
-            if chain_start == finalized_block {
+            if !chain.is_empty() && chain_start == finalized_block {
                 // add the chain back to `self.chain_set`
                 self.chain_set.insert(chain);
             } else {
@@ -50,11 +53,13 @@ impl NonFinalizedState {
             }
         }
 
+        self.update_metrics_for_chains();
+
         // return the finalized block
         finalized_block
     }
 
-    /// Commit block to the non-finalize state.
+    /// Commit block to the non-finalized state.
     pub fn commit_block(&mut self, block: Arc<Block>) {
         let parent_hash = block.header.previous_block_hash;
 
@@ -68,16 +73,18 @@ impl NonFinalizedState {
             })
             .expect("commit_block is only called with blocks that are ready to be commited");
 
-        parent_chain.push(block);
+        parent_chain.push(block.clone());
         self.chain_set.insert(parent_chain);
+        self.update_metrics_for_committed_block(block);
     }
 
     /// Commit block to the non-finalized state as a new chain where its parent
     /// is the finalized tip.
     pub fn commit_new_chain(&mut self, block: Arc<Block>) {
         let mut chain = Chain::default();
-        chain.push(block);
+        chain.push(block.clone());
         self.chain_set.insert(Box::new(chain));
+        self.update_metrics_for_committed_block(block);
     }
 
     /// Returns the length of the non-finalized portion of the current best chain.
@@ -93,6 +100,7 @@ impl NonFinalizedState {
     pub fn any_chain_contains(&self, hash: &block::Hash) -> bool {
         self.chain_set
             .iter()
+            .rev()
             .any(|chain| chain.height_by_hash.contains_key(hash))
     }
 
@@ -181,4 +189,191 @@ impl NonFinalizedState {
             .next_back()
             .map(|box_chain| box_chain.deref())
     }
+
+    /// Update the metrics after `block` is committed
+    fn update_metrics_for_committed_block(&self, block: Arc<Block>) {
+        let height = block.coinbase_height().unwrap();
+
+        metrics::counter!("state.memory.committed.block.count", 1);
+        metrics::gauge!("state.memory.committed.block.height", height.0 as _);
+
+        if self
+            .best_chain()
+            .unwrap()
+            .blocks
+            .iter()
+            .next_back()
+            .unwrap()
+            .1
+            == &block
+        {
+            metrics::counter!("state.memory.best.committed.block.count", 1);
+            metrics::gauge!("state.memory.best.committed.block.height", height.0 as _);
+        }
+
+        self.update_metrics_for_chains();
+    }
+
+    /// Update the metrics after `self.chain_set` is modified
+    fn update_metrics_for_chains(&self) {
+        metrics::gauge!("state.memory.chain.count", self.chain_set.len() as _);
+        metrics::gauge!("state.memory.best.chain.length", self.best_chain_len() as _);
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use zebra_chain::serialization::ZcashDeserializeInto;
+    use zebra_test::prelude::*;
+
+    use crate::tests::FakeChainHelper;
+
+    use self::assert_eq;
+    use super::*;
+
+    #[test]
+    fn best_chain_wins() -> Result<()> {
+        zebra_test::init();
+
+        let block1: Arc<Block> =
+            zebra_test::vectors::BLOCK_MAINNET_419200_BYTES.zcash_deserialize_into()?;
+        let block2 = block1.make_fake_child().set_work(10);
+        let child = block1.make_fake_child().set_work(1);
+
+        let expected_hash = block2.hash();
+
+        let mut state = NonFinalizedState::default();
+        state.commit_new_chain(block2);
+        state.commit_new_chain(child);
+
+        let best_chain = state.best_chain().unwrap();
+        assert!(best_chain.height_by_hash.contains_key(&expected_hash));
+
+        Ok(())
+    }
+
+    #[test]
+    fn finalize_pops_from_best_chain() -> Result<()> {
+        zebra_test::init();
+
+        let block1: Arc<Block> =
+            zebra_test::vectors::BLOCK_MAINNET_419200_BYTES.zcash_deserialize_into()?;
+        let block2 = block1.make_fake_child().set_work(10);
+        let child = block1.make_fake_child().set_work(1);
+
+        let mut state = NonFinalizedState::default();
+        state.commit_new_chain(block1.clone());
+        state.commit_block(block2.clone());
+        state.commit_block(child);
+
+        let finalized = state.finalize();
+        assert_eq!(block1, finalized);
+
+        let finalized = state.finalize();
+        assert_eq!(block2, finalized);
+
+        assert!(state.best_chain().is_none());
+
+        Ok(())
+    }
+
+    #[test]
+    // This test gives full coverage for `take_chain_if`
+    fn commit_block_extending_best_chain_doesnt_drop_worst_chains() -> Result<()> {
+        zebra_test::init();
+
+        let block1: Arc<Block> =
+            zebra_test::vectors::BLOCK_MAINNET_419200_BYTES.zcash_deserialize_into()?;
+        let block2 = block1.make_fake_child().set_work(10);
+        let child1 = block1.make_fake_child().set_work(1);
+        let child2 = block2.make_fake_child().set_work(1);
+
+        let mut state = NonFinalizedState::default();
+        assert_eq!(0, state.chain_set.len());
+        state.commit_new_chain(block1);
+        assert_eq!(1, state.chain_set.len());
+        state.commit_block(block2);
+        assert_eq!(1, state.chain_set.len());
+        state.commit_block(child1);
+        assert_eq!(2, state.chain_set.len());
+        state.commit_block(child2);
+        assert_eq!(2, state.chain_set.len());
+
+        Ok(())
+    }
+
+    #[test]
+    fn shorter_chain_can_be_best_chain() -> Result<()> {
+        zebra_test::init();
+
+        let block1: Arc<Block> =
+            zebra_test::vectors::BLOCK_MAINNET_419200_BYTES.zcash_deserialize_into()?;
+
+        let long_chain_block1 = block1.make_fake_child().set_work(1);
+        let long_chain_block2 = long_chain_block1.make_fake_child().set_work(1);
+
+        let short_chain_block = block1.make_fake_child().set_work(3);
+
+        let mut state = NonFinalizedState::default();
+        state.commit_new_chain(block1);
+        state.commit_block(long_chain_block1);
+        state.commit_block(long_chain_block2);
+        state.commit_block(short_chain_block);
+        assert_eq!(2, state.chain_set.len());
+
+        assert_eq!(2, state.best_chain_len());
+
+        Ok(())
+    }
+
+    #[test]
+    fn longer_chain_with_more_work_wins() -> Result<()> {
+        zebra_test::init();
+
+        let block1: Arc<Block> =
+            zebra_test::vectors::BLOCK_MAINNET_419200_BYTES.zcash_deserialize_into()?;
+
+        let long_chain_block1 = block1.make_fake_child().set_work(1);
+        let long_chain_block2 = long_chain_block1.make_fake_child().set_work(1);
+        let long_chain_block3 = long_chain_block2.make_fake_child().set_work(1);
+        let long_chain_block4 = long_chain_block3.make_fake_child().set_work(1);
+
+        let short_chain_block = block1.make_fake_child().set_work(3);
+
+        let mut state = NonFinalizedState::default();
+        state.commit_new_chain(block1);
+        state.commit_block(long_chain_block1);
+        state.commit_block(long_chain_block2);
+        state.commit_block(long_chain_block3);
+        state.commit_block(long_chain_block4);
+        state.commit_block(short_chain_block);
+        assert_eq!(2, state.chain_set.len());
+
+        assert_eq!(5, state.best_chain_len());
+
+        Ok(())
+    }
+
+    #[test]
+    fn equal_length_goes_to_more_work() -> Result<()> {
+        zebra_test::init();
+
+        let block1: Arc<Block> =
+            zebra_test::vectors::BLOCK_MAINNET_419200_BYTES.zcash_deserialize_into()?;
+
+        let less_work_child = block1.make_fake_child().set_work(1);
+        let more_work_child = block1.make_fake_child().set_work(3);
+        let expected_hash = more_work_child.hash();
+
+        let mut state = NonFinalizedState::default();
+        state.commit_new_chain(block1);
+        state.commit_block(less_work_child);
+        state.commit_block(more_work_child);
+        assert_eq!(2, state.chain_set.len());
+
+        let tip_hash = state.tip().unwrap().1;
+        assert_eq!(expected_hash, tip_hash);
+
+        Ok(())
+    }
 }
diff --git a/zebra-state/src/service/memory_state/queued_blocks.rs b/zebra-state/src/service/memory_state/queued_blocks.rs
index d8e74839b..dc7721857 100644
--- a/zebra-state/src/service/memory_state/queued_blocks.rs
+++ b/zebra-state/src/service/memory_state/queued_blocks.rs
@@ -49,6 +49,7 @@ impl QueuedBlocks {
         assert!(inserted, "hashes must be unique");
 
         tracing::trace!(num_blocks = %self.blocks.len(), %parent_hash, ?new_height,  "Finished queueing a new block");
+        self.update_metrics();
     }
 
     /// Dequeue and return all blocks that were waiting for the arrival of
@@ -73,6 +74,7 @@ impl QueuedBlocks {
         }
 
         tracing::trace!(num_blocks = %self.blocks.len(), "Finished dequeuing blocks waiting for parent hash",);
+        self.update_metrics();
 
         queued_children
     }
@@ -99,10 +101,24 @@ impl QueuedBlocks {
                 .expect("parent is present")
                 .remove(&hash);
         }
+
+        tracing::trace!(num_blocks = %self.blocks.len(), "Finished pruning blocks at or beneath the finalized tip height",);
+        self.update_metrics();
     }
 
     /// Return the queued block if it has already been registered
     pub fn get_mut(&mut self, hash: &block::Hash) -> Option<&mut QueuedBlock> {
         self.blocks.get_mut(&hash)
     }
+
+    /// Update metrics after the queue is modified
+    fn update_metrics(&self) {
+        if let Some(max_height) = self.by_height.keys().next_back() {
+            metrics::gauge!("state.memory.queued.max.height", max_height.0 as i64);
+        } else {
+            // use -1 as a sentinel value for "None", because 0 is a valid height
+            metrics::gauge!("state.memory.queued.max.height", -1);
+        }
+        metrics::gauge!("state.memory.queued.block.count", self.blocks.len() as _);
+    }
 }
diff --git a/zebrad/src/components/sync.rs b/zebrad/src/components/sync.rs
index 02f678e8f..50aa13aca 100644
--- a/zebrad/src/components/sync.rs
+++ b/zebrad/src/components/sync.rs
@@ -588,6 +588,8 @@ mod test {
     /// Make sure the timeout values are consistent with each other.
     #[test]
     fn ensure_timeouts_consistent() {
+        zebra_test::init();
+
         let max_download_retry_time =
             BLOCK_DOWNLOAD_TIMEOUT.as_secs() * (BLOCK_DOWNLOAD_RETRY_LIMIT as u64);
         assert!(


Test patch:
diff --git a/zebra-chain/src/block/tests/prop.rs b/zebra-chain/src/block/tests/prop.rs
index f202306fd..7cfee309e 100644
--- a/zebra-chain/src/block/tests/prop.rs
+++ b/zebra-chain/src/block/tests/prop.rs
@@ -12,6 +12,8 @@ use super::super::{serialize::MAX_BLOCK_BYTES, *};
 proptest! {
     #[test]
     fn block_hash_roundtrip(hash in any::<Hash>()) {
+        zebra_test::init();
+
         let bytes = hash.zcash_serialize_to_vec()?;
         let other_hash: Hash = bytes.zcash_deserialize_into()?;
 
@@ -20,6 +22,8 @@ proptest! {
 
     #[test]
     fn block_hash_display_fromstr_roundtrip(hash in any::<Hash>()) {
+        zebra_test::init();
+
         let display = format!("{}", hash);
         let parsed = display.parse::<Hash>().expect("hash should parse");
         prop_assert_eq!(hash, parsed);
@@ -27,6 +31,8 @@ proptest! {
 
     #[test]
     fn blockheader_roundtrip(header in any::<Header>()) {
+        zebra_test::init();
+
         let bytes = header.zcash_serialize_to_vec()?;
         let other_header = bytes.zcash_deserialize_into()?;
 
@@ -39,6 +45,8 @@ proptest! {
         network in any::<Network>(),
         block_height in any::<Height>()
     ) {
+        zebra_test::init();
+
         let root_hash = RootHash::from_bytes(bytes, network, block_height);
         let other_bytes = root_hash.to_bytes();
 
@@ -56,6 +64,8 @@ proptest! {
 
     #[test]
     fn block_roundtrip(block in any::<Block>(), network in any::<Network>()) {
+        zebra_test::init();
+
         let bytes = block.zcash_serialize_to_vec()?;
         let bytes = &mut bytes.as_slice();
 
diff --git a/zebra-chain/src/block/tests/vectors.rs b/zebra-chain/src/block/tests/vectors.rs
index 1dcfd9a8a..15a47e771 100644
--- a/zebra-chain/src/block/tests/vectors.rs
+++ b/zebra-chain/src/block/tests/vectors.rs
@@ -11,6 +11,8 @@ use super::generate; // XXX this should be rewritten as strategies
 
 #[test]
 fn blockheaderhash_debug() {
+    zebra_test::init();
+
     let preimage = b"foo bar baz";
     let mut sha_writer = sha256d::Writer::default();
     let _ = sha_writer.write_all(preimage);
@@ -25,6 +27,8 @@ fn blockheaderhash_debug() {
 
 #[test]
 fn blockheaderhash_from_blockheader() {
+    zebra_test::init();
+
     let blockheader = generate::block_header();
 
     let hash = Hash::from(&blockheader);
@@ -69,6 +73,8 @@ fn deserialize_blockheader() {
 
 #[test]
 fn deserialize_block() {
+    zebra_test::init();
+
     // this one has a bad version field
     zebra_test::vectors::BLOCK_MAINNET_434873_BYTES
         .zcash_deserialize_into::<Block>()
@@ -83,6 +89,8 @@ fn deserialize_block() {
 
 #[test]
 fn block_test_vectors_unique() {
+    zebra_test::init();
+
     let block_count = zebra_test::vectors::BLOCKS.len();
     let block_hashes: HashSet<_> = zebra_test::vectors::BLOCKS
         .iter()
@@ -103,11 +111,15 @@ fn block_test_vectors_unique() {
 
 #[test]
 fn block_test_vectors_height_mainnet() {
+    zebra_test::init();
+
     block_test_vectors_height(Network::Mainnet);
 }
 
 #[test]
 fn block_test_vectors_height_testnet() {
+    zebra_test::init();
+
     block_test_vectors_height(Network::Testnet);
 }
 
@@ -131,6 +143,8 @@ fn block_test_vectors_height(network: Network) {
 
 #[test]
 fn block_limits_multi_tx() {
+    zebra_test::init();
+
     // Test multiple small transactions to fill a block max size
 
     // Create a block just below the limit
@@ -166,6 +180,8 @@ fn block_limits_multi_tx() {
 
 #[test]
 fn block_limits_single_tx() {
+    zebra_test::init();
+
     // Test block limit with a big single transaction
 
     // Create a block just below the limit
@@ -213,6 +229,8 @@ fn node_time_check(
 
 #[test]
 fn time_check_now() {
+    zebra_test::init();
+
     // These checks are deteministic, because all the times are offset
     // from the current time.
     let now = Utc::now();
@@ -273,6 +291,8 @@ static BLOCK_HEADER_INVALID_TIMESTAMPS: &[i64] = &[
 
 #[test]
 fn time_check_fixed() {
+    zebra_test::init();
+
     // These checks are non-deterministic, but the times are all in the
     // distant past or far future. So it's unlikely that the test
     // machine will have a clock that makes these tests fail.
diff --git a/zebra-chain/src/parameters/tests.rs b/zebra-chain/src/parameters/tests.rs
index ad7345d60..33e5b6831 100644
--- a/zebra-chain/src/parameters/tests.rs
+++ b/zebra-chain/src/parameters/tests.rs
@@ -12,6 +12,8 @@ use NetworkUpgrade::*;
 /// Check that the activation heights and network upgrades are unique.
 #[test]
 fn activation_bijective() {
+    zebra_test::init();
+
     let mainnet_activations = NetworkUpgrade::activation_list(Mainnet);
     let mainnet_heights: HashSet<&block::Height> = mainnet_activations.keys().collect();
     assert_eq!(MAINNET_ACTIVATION_HEIGHTS.len(), mainnet_heights.len());
@@ -29,11 +31,13 @@ fn activation_bijective() {
 
 #[test]
 fn activation_extremes_mainnet() {
+    zebra_test::init();
     activation_extremes(Mainnet)
 }
 
 #[test]
 fn activation_extremes_testnet() {
+    zebra_test::init();
     activation_extremes(Testnet)
 }
 
@@ -84,11 +88,13 @@ fn activation_extremes(network: Network) {
 
 #[test]
 fn activation_consistent_mainnet() {
+    zebra_test::init();
     activation_consistent(Mainnet)
 }
 
 #[test]
 fn activation_consistent_testnet() {
+    zebra_test::init();
     activation_consistent(Testnet)
 }
 
@@ -119,6 +125,8 @@ fn activation_consistent(network: Network) {
 /// Check that the network upgrades and branch ids are unique.
 #[test]
 fn branch_id_bijective() {
+    zebra_test::init();
+
     let branch_id_list = NetworkUpgrade::branch_id_list();
     let nus: HashSet<&NetworkUpgrade> = branch_id_list.keys().collect();
     assert_eq!(CONSENSUS_BRANCH_IDS.len(), nus.len());
@@ -129,11 +137,13 @@ fn branch_id_bijective() {
 
 #[test]
 fn branch_id_extremes_mainnet() {
+    zebra_test::init();
     branch_id_extremes(Mainnet)
 }
 
 #[test]
 fn branch_id_extremes_testnet() {
+    zebra_test::init();
     branch_id_extremes(Testnet)
 }
 
@@ -165,11 +175,13 @@ fn branch_id_extremes(network: Network) {
 
 #[test]
 fn branch_id_consistent_mainnet() {
+    zebra_test::init();
     branch_id_consistent(Mainnet)
 }
 
 #[test]
 fn branch_id_consistent_testnet() {
+    zebra_test::init();
     branch_id_consistent(Testnet)
 }
 
diff --git a/zebra-chain/src/sapling/keys/tests.rs b/zebra-chain/src/sapling/keys/tests.rs
index 252eae0df..8b8f43228 100644
--- a/zebra-chain/src/sapling/keys/tests.rs
+++ b/zebra-chain/src/sapling/keys/tests.rs
@@ -37,6 +37,8 @@ mod tests {
 
     #[test]
     fn derive_for_each_test_vector() {
+        zebra_test::init();
+
         for test_vector in test_vectors::TEST_VECTORS.iter() {
             let spending_key = SpendingKey::from(test_vector.sk);
 
@@ -76,6 +78,8 @@ proptest! {
 
     #[test]
     fn string_roundtrips(spending_key in any::<SpendingKey>()) {
+        zebra_test::init();
+
         let sk_string = spending_key.to_string();
         let spending_key_2: SpendingKey = sk_string.parse().unwrap();
         prop_assert_eq![spending_key, spending_key_2];
diff --git a/zebra-chain/src/serialization/proptests.rs b/zebra-chain/src/serialization/proptests.rs
index dda4b7637..99f2ec166 100644
--- a/zebra-chain/src/serialization/proptests.rs
+++ b/zebra-chain/src/serialization/proptests.rs
@@ -11,6 +11,8 @@ proptest! {
 
     #[test]
     fn compactsize_write_then_read_round_trip(s in 0u64..0x2_0000u64) {
+        zebra_test::init();
+
         // Maximum encoding size of a compactsize is 9 bytes.
         let mut buf = [0u8; 8+1];
         Cursor::new(&mut buf[..]).write_compactsize(s).unwrap();
@@ -20,6 +22,8 @@ proptest! {
 
     #[test]
     fn compactsize_read_then_write_round_trip(bytes in prop::array::uniform9(0u8..)) {
+        zebra_test::init();
+
         // Only do the test if the bytes were valid.
         if let Ok(s) = Cursor::new(&bytes[..]).read_compactsize() {
             // The compactsize encoding is variable-length, so we may not even
diff --git a/zebra-chain/src/transaction/tests/prop.rs b/zebra-chain/src/transaction/tests/prop.rs
index 29548bbbf..97cf6fcf3 100644
--- a/zebra-chain/src/transaction/tests/prop.rs
+++ b/zebra-chain/src/transaction/tests/prop.rs
@@ -8,6 +8,8 @@ use crate::serialization::{ZcashDeserialize, ZcashDeserializeInto, ZcashSerializ
 proptest! {
     #[test]
     fn transaction_roundtrip(tx in any::<Transaction>()) {
+        zebra_test::init();
+
         let data = tx.zcash_serialize_to_vec().expect("tx should serialize");
         let tx2 = data.zcash_deserialize_into().expect("randomized tx should deserialize");
 
@@ -16,6 +18,8 @@ proptest! {
 
     #[test]
     fn transaction_hash_display_fromstr_roundtrip(hash in any::<Hash>()) {
+        zebra_test::init();
+
         let display = format!("{}", hash);
         let parsed = display.parse::<Hash>().expect("hash should parse");
         prop_assert_eq!(hash, parsed);
@@ -23,6 +27,8 @@ proptest! {
 
     #[test]
     fn locktime_roundtrip(locktime in any::<LockTime>()) {
+        zebra_test::init();
+
         let mut bytes = Cursor::new(Vec::new());
         locktime.zcash_serialize(&mut bytes)?;
 
diff --git a/zebra-chain/src/transaction/tests/vectors.rs b/zebra-chain/src/transaction/tests/vectors.rs
index 50265362b..62c585813 100644
--- a/zebra-chain/src/transaction/tests/vectors.rs
+++ b/zebra-chain/src/transaction/tests/vectors.rs
@@ -4,6 +4,8 @@ use crate::serialization::{ZcashDeserialize, ZcashSerialize};
 
 #[test]
 fn librustzcash_tx_deserialize_and_round_trip() {
+    zebra_test::init();
+
     let tx = Transaction::zcash_deserialize(&zebra_test::vectors::GENERIC_TESTNET_TX[..])
         .expect("transaction test vector from librustzcash should deserialize");
 
@@ -15,6 +17,8 @@ fn librustzcash_tx_deserialize_and_round_trip() {
 
 #[test]
 fn librustzcash_tx_hash() {
+    zebra_test::init();
+
     let tx = Transaction::zcash_deserialize(&zebra_test::vectors::GENERIC_TESTNET_TX[..])
         .expect("transaction test vector from librustzcash should deserialize");
 
@@ -29,6 +33,8 @@ fn librustzcash_tx_hash() {
 
 #[test]
 fn zip143_deserialize_and_round_trip() {
+    zebra_test::init();
+
     let tx1 = Transaction::zcash_deserialize(&zebra_test::vectors::ZIP143_1[..])
         .expect("transaction test vector from ZIP143 should deserialize");
 
@@ -50,6 +56,8 @@ fn zip143_deserialize_and_round_trip() {
 
 #[test]
 fn zip243_deserialize_and_round_trip() {
+    zebra_test::init();
+
     let tx1 = Transaction::zcash_deserialize(&zebra_test::vectors::ZIP243_1[..])
         .expect("transaction test vector from ZIP243 should deserialize");
 
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-chain/src/work/tests/prop.rs b/zebra-chain/src/work/tests/prop.rs
index 3e285c2e3..6436b2335 100644
--- a/zebra-chain/src/work/tests/prop.rs
+++ b/zebra-chain/src/work/tests/prop.rs
@@ -9,6 +9,8 @@ use super::super::*;
 
 #[test]
 fn equihash_solution_roundtrip() {
+    zebra_test::init();
+
     proptest!(|(solution in any::<equihash::Solution>())| {
             let data = solution
                 .zcash_serialize_to_vec()
diff --git a/zebra-consensus/src/block/tests.rs b/zebra-consensus/src/block/tests.rs
index 49e5d9fad..82c8c0d14 100644
--- a/zebra-consensus/src/block/tests.rs
+++ b/zebra-consensus/src/block/tests.rs
@@ -135,6 +135,8 @@ async fn check_transcripts() -> Result<(), Report> {
 
 #[test]
 fn coinbase_is_first_for_historical_blocks() -> Result<(), Report> {
+    zebra_test::init();
+
     let block_iter = zebra_test::vectors::BLOCKS.iter();
 
     for block in block_iter {
@@ -151,6 +153,8 @@ fn coinbase_is_first_for_historical_blocks() -> Result<(), Report> {
 
 #[test]
 fn difficulty_is_valid_for_historical_blocks() -> Result<(), Report> {
+    zebra_test::init();
+
     difficulty_is_valid_for_network(Network::Mainnet)?;
     difficulty_is_valid_for_network(Network::Testnet)?;
 
@@ -177,6 +181,7 @@ fn difficulty_is_valid_for_network(network: Network) -> Result<(), Report> {
 
 #[test]
 fn difficulty_validation_failure() -> Result<(), Report> {
+    zebra_test::init();
     use crate::error::*;
 
     // Get a block in the mainnet, and mangle its difficulty field
@@ -229,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(())
@@ -237,6 +243,8 @@ fn difficulty_validation_failure() -> Result<(), Report> {
 
 #[test]
 fn equihash_is_valid_for_historical_blocks() -> Result<(), Report> {
+    zebra_test::init();
+
     let block_iter = zebra_test::vectors::BLOCKS.iter();
 
     for block in block_iter {
@@ -253,6 +261,8 @@ fn equihash_is_valid_for_historical_blocks() -> Result<(), Report> {
 
 #[test]
 fn subsidy_is_valid_for_historical_blocks() -> Result<(), Report> {
+    zebra_test::init();
+
     subsidy_is_valid_for_network(Network::Mainnet)?;
     subsidy_is_valid_for_network(Network::Testnet)?;
 
@@ -283,6 +293,7 @@ fn subsidy_is_valid_for_network(network: Network) -> Result<(), Report> {
 
 #[test]
 fn coinbase_validation_failure() -> Result<(), Report> {
+    zebra_test::init();
     use crate::error::*;
 
     let network = Network::Mainnet;
@@ -355,6 +366,7 @@ fn coinbase_validation_failure() -> Result<(), Report> {
 
 #[test]
 fn founders_reward_validation_failure() -> Result<(), Report> {
+    zebra_test::init();
     use crate::error::*;
     use zebra_chain::transaction::Transaction;
 
@@ -398,6 +410,8 @@ fn founders_reward_validation_failure() -> Result<(), Report> {
 
 #[test]
 fn time_is_valid_for_historical_blocks() -> Result<(), Report> {
+    zebra_test::init();
+
     let block_iter = zebra_test::vectors::BLOCKS.iter();
     let now = Utc::now();
 
diff --git a/zebra-consensus/src/parameters/tests.rs b/zebra-consensus/src/parameters/tests.rs
index 9c5fc1eaa..ad10ad706 100644
--- a/zebra-consensus/src/parameters/tests.rs
+++ b/zebra-consensus/src/parameters/tests.rs
@@ -19,6 +19,7 @@ fn minimum_difficulty_testnet() {
 
 /// Test MinimumDifficulty
 fn minimum_difficulty(network: Network) {
+    zebra_test::init();
     use block::Height;
     use MinimumDifficulty::*;
 
diff --git a/zebra-state/src/tests.rs b/zebra-state/src/tests.rs
index 26af6df2f..86ea33237 100644
--- a/zebra-state/src/tests.rs
+++ b/zebra-state/src/tests.rs
@@ -1,7 +1,70 @@
-use zebra_chain::block;
+use std::{convert::TryFrom, mem, sync::Arc};
+
+use primitive_types::U256;
+use zebra_chain::{
+    block::{self, Block},
+    transaction::Transaction,
+    transparent,
+    work::difficulty::ExpandedDifficulty,
+    work::difficulty::Work,
+};
 
 use super::*;
 
+/// Helper trait for constructing "valid" looking chains of blocks
+pub trait FakeChainHelper {
+    fn make_fake_child(&self) -> Arc<Block>;
+
+    fn set_work(self, work: u128) -> Arc<Block>;
+}
+
+impl FakeChainHelper for Arc<Block> {
+    fn make_fake_child(&self) -> Arc<Block> {
+        let parent_hash = self.hash();
+        let mut child = Block::clone(self);
+        let mut transactions = mem::take(&mut child.transactions);
+        let mut tx = transactions.remove(0);
+
+        let input = match Arc::make_mut(&mut tx) {
+            Transaction::V1 { inputs, .. } => &mut inputs[0],
+            Transaction::V2 { inputs, .. } => &mut inputs[0],
+            Transaction::V3 { inputs, .. } => &mut inputs[0],
+            Transaction::V4 { inputs, .. } => &mut inputs[0],
+        };
+
+        match input {
+            transparent::Input::Coinbase { height, .. } => height.0 += 1,
+            _ => panic!("block must have a coinbase height to create a child"),
+        }
+
+        child.transactions.push(tx);
+        child.header.previous_block_hash = parent_hash;
+
+        Arc::new(child)
+    }
+
+    fn set_work(mut self, work: u128) -> Arc<Block> {
+        let work: U256 = work.into();
+        let expanded = work_to_expanded(work);
+
+        let block = Arc::make_mut(&mut self);
+        block.header.difficulty_threshold = expanded.into();
+        self
+    }
+}
+
+fn work_to_expanded(work: U256) -> ExpandedDifficulty {
+    // Work is calculated from expanded difficulty with the equation `work = 2^256 / (expanded + 1)`
+    // By balancing the equation we get `expanded = (2^256 / work) - 1`
+    // `2^256` is too large to represent, so we instead use the following equivalent equations
+    // `expanded = (2^256 / work) - (work / work)`
+    // `expanded = (2^256 - work) / work`
+    // `expanded = ((2^256 - 1) - work + 1) / work`
+    // `(2^256 - 1 - work)` is equivalent to `!work` when work is a U256
+    let expanded = (!work + 1) / work;
+    ExpandedDifficulty::from(expanded)
+}
+
 /// Block heights, and the expected minimum block locator height
 static BLOCK_LOCATOR_CASES: &[(u32, u32)] = &[
     (0, 0),
@@ -15,9 +78,25 @@ static BLOCK_LOCATOR_CASES: &[(u32, u32)] = &[
     (10000, 9901),
 ];
 
+use proptest::prelude::*;
+
+#[test]
+fn round_trip_work_expanded() {
+    zebra_test::init();
+
+    proptest!(|(work_before in any::<Work>())| {
+        let work: U256 = work_before.as_u128().into();
+        let expanded = work_to_expanded(work);
+        let work_after = Work::try_from(expanded).unwrap();
+        prop_assert_eq!(work_before, work_after);
+    });
+}
+
 /// Check that the block locator heights are sensible.
 #[test]
 fn test_block_locator_heights() {
+    zebra_test::init();
+
     for (height, min_height) in BLOCK_LOCATOR_CASES.iter().cloned() {
         let locator = util::block_locator_heights(block::Height(height));
 
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 93ad816e5..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");
@@ -404,8 +440,12 @@ mod test {
 
     use std::collections::HashSet;
 
+    use crate::init;
+
     #[test]
     fn block_test_vectors_unique() {
+        init();
+
         let block_count = BLOCKS.len();
         let block_set: HashSet<_> = BLOCKS.iter().collect();
 
@@ -422,6 +462,8 @@ mod test {
     /// We're using lazy_static! and combinators, so it would be easy to make this mistake.
     #[test]
     fn block_test_vectors_count() {
+        init();
+
         assert!(
             BLOCKS.len() > 50,
             "there should be a reasonable number of block test vectors"
diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs
index 8b9d023f8..3aedb972d 100644
--- a/zebrad/tests/acceptance.rs
+++ b/zebrad/tests/acceptance.rs
@@ -134,6 +134,7 @@ where
 #[test]
 fn generate_no_args() -> Result<()> {
     zebra_test::init();
+
     let child = testdir()?
         .with_config(default_test_config()?)?
         .spawn_child(&["generate"])?;
@@ -165,6 +166,7 @@ macro_rules! assert_with_context {
 #[test]
 fn generate_args() -> Result<()> {
     zebra_test::init();
+
     let testdir = testdir()?;
     let testdir = &testdir;
 
@@ -205,6 +207,7 @@ fn generate_args() -> Result<()> {
 #[test]
 fn help_no_args() -> Result<()> {
     zebra_test::init();
+
     let testdir = testdir()?.with_config(default_test_config()?)?;
 
     let child = testdir.spawn_child(&["help"])?;
@@ -223,6 +226,7 @@ fn help_no_args() -> Result<()> {
 #[test]
 fn help_args() -> Result<()> {
     zebra_test::init();
+
     let testdir = testdir()?;
     let testdir = &testdir;
 
@@ -242,6 +246,7 @@ fn help_args() -> Result<()> {
 #[test]
 fn start_no_args() -> Result<()> {
     zebra_test::init();
+
     // start caches state, so run one of the start tests with persistent state
     let testdir = testdir()?.with_config(persistent_test_config()?)?;
 
@@ -265,6 +270,7 @@ fn start_no_args() -> Result<()> {
 #[test]
 fn start_args() -> Result<()> {
     zebra_test::init();
+
     let testdir = testdir()?.with_config(default_test_config()?)?;
     let testdir = &testdir;
 
@@ -291,6 +297,7 @@ fn start_args() -> Result<()> {
 #[test]
 fn persistent_mode() -> Result<()> {
     zebra_test::init();
+
     let testdir = testdir()?.with_config(persistent_test_config()?)?;
     let testdir = &testdir;
 
@@ -314,6 +321,7 @@ fn persistent_mode() -> Result<()> {
 #[test]
 fn ephemeral_mode() -> Result<()> {
     zebra_test::init();
+
     let testdir = testdir()?.with_config(default_test_config()?)?;
     let testdir = &testdir;
 
@@ -378,6 +386,7 @@ fn misconfigured_ephemeral_mode() -> Result<()> {
 #[test]
 fn app_no_args() -> Result<()> {
     zebra_test::init();
+
     let testdir = testdir()?.with_config(default_test_config()?)?;
 
     let child = testdir.spawn_child(&[])?;
@@ -392,6 +401,7 @@ fn app_no_args() -> Result<()> {
 #[test]
 fn version_no_args() -> Result<()> {
     zebra_test::init();
+
     let testdir = testdir()?.with_config(default_test_config()?)?;
 
     let child = testdir.spawn_child(&["version"])?;
@@ -406,6 +416,7 @@ fn version_no_args() -> Result<()> {
 #[test]
 fn version_args() -> Result<()> {
     zebra_test::init();
+
     let testdir = testdir()?.with_config(default_test_config()?)?;
     let testdir = &testdir;
 
@@ -434,6 +445,7 @@ fn valid_generated_config_test() -> Result<()> {
 
 fn valid_generated_config(command: &str, expected_output: &str) -> Result<()> {
     zebra_test::init();
+
     let testdir = testdir()?;
     let testdir = &testdir;