diff --git a/.gitignore b/.gitignore index daffad5cd9..8d9730d527 100644 --- a/.gitignore +++ b/.gitignore @@ -91,6 +91,19 @@ coverage.txt harmony_db_* explorer_storage_* +# committed recovery-preflight fixtures are deliberately tracked +# (see testdata/recovery/preflight/README.md) +!testdata/recovery/preflight/*/harmony_db_0/ + +# committed metadata fixture kit is deliberately tracked +# (see testdata/recovery/metadata/README.md). Re-include the LevelDB +# directories first, then the file types the global rules would otherwise +# swallow (*.log LevelDB write-ahead logs, *.hex fixture BLS secrets). +!testdata/recovery/metadata/kit/harmony_db_0/ +!testdata/recovery/metadata/kit/clean/harmony_db_0/ +!testdata/recovery/metadata/kit/**/*.log +!testdata/recovery/metadata/kit/**/*.hex + # local blskeys for testing .hmy/blskeys diff --git a/cmd/harmony-recovery/deps_guard_test.go b/cmd/harmony-recovery/deps_guard_test.go new file mode 100644 index 0000000000..ae5aceeae7 --- /dev/null +++ b/cmd/harmony-recovery/deps_guard_test.go @@ -0,0 +1,88 @@ +package main + +import ( + "os/exec" + "strings" + "testing" +) + +// forbiddenDeps is the static dependency guard: a compile-time code-hygiene +// boundary (distinct from any approved-build allowlist). The preflight +// binary must never link networking, RPC, consensus-service wiring or BLS +// keystore loading - it reads a database and verifies signatures, nothing +// else. Exact package or prefix matches against `go list -deps`. +var forbiddenDeps = []string{ + // harmony p2p service / host wiring (the libp2p transport stack hangs + // off this package; note the type-only go-libp2p/core/{crypto,peer} + // packages leak in via internal/utils logging helpers and open no + // sockets - the boundary is harmony's own p2p package) + "github.com/harmony-one/harmony/p2p", + // RPC and API services (incl. sync services) + "github.com/harmony-one/harmony/rpc", + "github.com/harmony-one/harmony/api/service", + // node service wiring + "github.com/harmony-one/harmony/node", + // consensus service (the engine's verification-only subpackages + // consensus/engine, consensus/quorum, consensus/signature, + // consensus/votepower are allowed; the service package itself is not) + "github.com/harmony-one/harmony/consensus\x00exact", + // BLS keystore loading (validators' signing keys must never be touched; + // multibls is a pure key-slice type and is allowed) + "github.com/harmony-one/harmony/internal/blsgen", + // libp2p host construction (transport/muxer/swarm - the actual network + // stack, as opposed to the type-only core packages) + "github.com/libp2p/go-libp2p\x00exact", + "github.com/libp2p/go-libp2p/p2p", +} + +// exemptDeps are exact-match exemptions consulted before recording a +// violation. The metadata audit-branch engine links package core (the +// masked-overlay re-execution needs the production BlockChain), and core's +// blockchain_pruner_metric.go imports api/service/prometheus purely for +// metric REGISTRATION - nothing in this binary constructs or starts the +// prometheus service (no listener; the process-isolation test enforces +// that). The api/service prefix ban and every other rule stay intact. +var exemptDeps = map[string]bool{ + "github.com/harmony-one/harmony/api/service/prometheus": true, +} + +// TestDependencyGuard runs `go list -deps ./cmd/harmony-recovery` and fails +// on forbidden imports. +func TestDependencyGuard(t *testing.T) { + out, err := exec.Command("go", "list", "-deps", ".").CombinedOutput() + if err != nil { + t.Fatalf("go list -deps failed: %v\n%s", err, out) + } + deps := strings.Split(strings.TrimSpace(string(out)), "\n") + depSet := make(map[string]bool, len(deps)) + for _, d := range deps { + depSet[strings.TrimSpace(d)] = true + } + var violations []string + for _, rule := range forbiddenDeps { + if exact, ok := strings.CutSuffix(rule, "\x00exact"); ok { + if depSet[exact] && !exemptDeps[exact] { + violations = append(violations, exact) + } + continue + } + for dep := range depSet { + if (dep == rule || strings.HasPrefix(dep, rule+"/")) && !exemptDeps[dep] { + violations = append(violations, dep) + } + } + } + if len(violations) > 0 { + t.Fatalf("forbidden dependencies linked into harmony-recovery:\n %s", + strings.Join(violations, "\n ")) + } + // Sanity: the audited verification packages ARE expected. + for _, want := range []string{ + "github.com/harmony-one/harmony/internal/chain", + "github.com/harmony-one/harmony/consensus/quorum", + } { + if !depSet[want] { + t.Fatalf("expected dependency %s missing; the dependency guard may be checking the wrong package", want) + } + } +} diff --git a/cmd/harmony-recovery/golden_test.go b/cmd/harmony-recovery/golden_test.go new file mode 100644 index 0000000000..94f43b6a32 --- /dev/null +++ b/cmd/harmony-recovery/golden_test.go @@ -0,0 +1,180 @@ +package main + +import ( + "bytes" + "encoding/json" + "os" + "path/filepath" + "regexp" + "strings" + "testing" + + "github.com/spf13/pflag" + + "github.com/harmony-one/harmony/internal/recovery/inplace/fixture" + "github.com/harmony-one/harmony/internal/recovery/inplace/report" +) + +const goldenDir = "../../testdata/recovery/preflight/golden" + +// normalizeReceipt zeroes the volatile fields (host identity, timing, build +// stamp, machine paths); everything else in the fixture receipts is +// deterministic, including the state digest. +func normalizeReceipt(rec report.Receipt) report.Receipt { + rec.Hostname = "" + rec.DBPath = "" + rec.StartedAt = "