Skip to content

Commit 6428baa

Browse files
Merge pull request #47 from CorruptedAesthetic/update-local-changes-20250710-172406
Update local changes 20250710 172406
2 parents cf6d4a2 + 983c7ac commit 6428baa

13 files changed

Lines changed: 871 additions & 21 deletions

File tree

.envrc

Whitespace-only changes.

.github/workflows/publish.yml

Lines changed: 320 additions & 14 deletions
Large diffs are not rendered by default.

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

chainspecs/production/README.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Fennel Production Chainspecs
2+
3+
This directory contains the production chainspecs for the Fennel blockchain network.
4+
5+
## 📋 Files
6+
7+
- `production-chainspec.json` - Human-readable production chain specification
8+
- `production-raw.json` - SCALE-encoded raw production chainspec for node deployment
9+
10+
## 🏭 Production Configuration
11+
12+
### Chain Details
13+
- **Chain ID**: `fennel_production`
14+
- **Chain Type**: `Live` (Production)
15+
- **Network Name**: Fennel Production Network
16+
17+
### Consensus
18+
- **Block Production**: AURA (Authority Round)
19+
- **Finality**: GRANDPA (GHOST-based Recursive Ancestor Deriving Prefix Agreement)
20+
21+
### Authority Configuration
22+
The production chainspec contains **placeholder validator keys** that must be replaced during deployment:
23+
24+
⚠️ **Important Security Notes:**
25+
- Placeholder keys are used for chainspec generation
26+
- Real validator and bootnode keys are managed via HashiCorp Vault
27+
- Production keys should be generated offline using air-gapped systems
28+
- See `DOCUMENTATION/UPGRADETOPRODUCTION/ndoesessionkeygenerations/` for secure key generation procedures
29+
30+
## 🌐 Bootnode Architecture
31+
32+
Uses **external-only bootnode architecture** following Parity's best practices:
33+
- Both internal and external validators connect to the same public bootnode endpoints
34+
- Bootnode peer IDs are dynamically derived from GitHub secrets during CI
35+
- DNS-based addressing: `bootnode1.fennel.network` and `bootnode2.fennel.network`
36+
37+
## 🔐 Production Deployment Requirements
38+
39+
### Prerequisites
40+
1. **HashiCorp Vault Setup**: Secure key management system deployed
41+
2. **Real Validator Keys**: Generated offline and stored in Vault
42+
3. **Production Infrastructure**: Azure AKS cluster with proper security policies
43+
4. **DNS Configuration**: Bootnode domains pointing to production load balancers
44+
45+
### Key Management
46+
- **Validator Session Keys**: AURA (Sr25519) + GRANDPA (Ed25519) keys per validator
47+
- **Bootnode Node Keys**: Ed25519 keys for libp2p networking
48+
- **Storage**: All keys stored in Vault with KV v2 engine
49+
- **Access Control**: Kubernetes service account-based authentication
50+
51+
## 🚀 Usage
52+
53+
### For Production Deployment
54+
```bash
55+
# The production chainspec is automatically referenced by fennel-prod Helm charts
56+
# Example production values.yaml:
57+
node:
58+
customChainspecUrl: "https://raw.githubusercontent.com/CorruptedAesthetic/fennel-solonet/main/chainspecs/production/production-raw.json"
59+
chain: "production"
60+
```
61+
62+
### For External Validators
63+
```bash
64+
# Download and use the production chainspec
65+
curl -O https://raw.githubusercontent.com/CorruptedAesthetic/fennel-solonet/main/chainspecs/production/production-raw.json
66+
67+
# Start validator with production chainspec
68+
./fennel-node \
69+
--chain production-raw.json \
70+
--validator \
71+
--name "my-production-validator"
72+
```
73+
74+
## 🔄 Generation Process
75+
76+
Production chainspecs are automatically generated during CI/CD:
77+
78+
1. **Trigger**: Only generated for release tags (`fennel-node-*`)
79+
2. **Runtime**: Built deterministically with srtool
80+
3. **Preset**: Uses `production` genesis preset from runtime
81+
4. **Bootnodes**: Dynamically injected from GitHub secrets
82+
5. **Validation**: SHA-256 computed for integrity verification
83+
84+
## 📦 Distribution
85+
86+
- **GitHub Releases**: Attached to release assets
87+
- **GitHub Pages**: Available via raw URL
88+
- **CI Artifacts**: Available in GitHub Actions artifacts
89+
90+
## 🔗 Related Documentation
91+
92+
- **Production Setup**: `/DOCUMENTATION/UPGRADETOPRODUCTION/`
93+
- **Vault Integration**: `/DOCUMENTATION/UPGRADETOPRODUCTION/upgradenotes/creationofvaultkubernetes.md`
94+
- **Key Generation**: `/DOCUMENTATION/UPGRADETOPRODUCTION/ndoesessionkeygenerations/`
95+
- **Staging Environment**: `../staging/README.md`
96+
97+
---
98+
99+
**Generated**: Automatically during release builds
100+
**Last Updated**: See git commit history
101+
**Security Review**: Required before production deployment

derive-public-keys.sh

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#!/bin/bash
2+
3+
# Script to derive public keys from private keys to verify our offline keys are corrupted
4+
5+
echo "=== Deriving public keys from Vault private keys ==="
6+
7+
# Validator 1 keys from Vault
8+
echo "Validator 1:"
9+
echo "Aura seed: 0x721aaee6982d5272bc85a3e8e50e47b5f108fc3a0326a3cc9550d682a28e579d"
10+
echo "Grandpa seed: 0x78608f3e2a3c545960b472de3b9fb82630864d13d7a8e26ab9e897931defe99c"
11+
12+
# Derive Aura public key (sr25519)
13+
aura1_public=$(./target/release/fennel-node key inspect --scheme sr25519 "0x721aaee6982d5272bc85a3e8e50e47b5f108fc3a0326a3cc9550d682a28e579d" | grep "Public key" | cut -d':' -f2 | tr -d ' ')
14+
echo "Derived Aura public: $aura1_public"
15+
16+
# Derive Grandpa public key (ed25519)
17+
grandpa1_public=$(./target/release/fennel-node key inspect --scheme ed25519 "0x78608f3e2a3c545960b472de3b9fb82630864d13d7a8e26ab9e897931defe99c" | grep "Public key" | cut -d':' -f2 | tr -d ' ')
18+
echo "Derived Grandpa public: $grandpa1_public"
19+
20+
echo ""
21+
echo "Validator 2:"
22+
echo "Aura seed: 0xea9bcfd9d6d7eb3711b38d293490f8f846480a192e247c0619c822e70a523ab0"
23+
echo "Grandpa seed: 0x101cbaf82b3b4f1dde746b26999b29c4044ce15e57de317efc69f7963827d141"
24+
25+
# Derive Aura public key (sr25519)
26+
aura2_public=$(./target/release/fennel-node key inspect --scheme sr25519 "0xea9bcfd9d6d7eb3711b38d293490f8f846480a192e247c0619c822e70a523ab0" | grep "Public key" | cut -d':' -f2 | tr -d ' ')
27+
echo "Derived Aura public: $aura2_public"
28+
29+
# Derive Grandpa public key (ed25519)
30+
grandpa2_public=$(./target/release/fennel-node key inspect --scheme ed25519 "0x101cbaf82b3b4f1dde746b26999b29c4044ce15e57de317efc69f7963827d141" | grep "Public key" | cut -d':' -f2 | tr -d ' ')
31+
echo "Derived Grandpa public: $grandpa2_public"
32+
33+
echo ""
34+
echo "=== Comparison with stored offline keys ==="
35+
echo "Offline Aura public (both validators): 0x46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a"
36+
echo "Offline Grandpa public (both validators): 0x345071da55e5dccefaaa440339415ef9f2663338a38f7da0df21be5ab4e055ef"
37+
38+
echo ""
39+
echo "=== Verification ==="
40+
if [ "$aura1_public" != "0x46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a" ]; then
41+
echo "❌ Validator 1 Aura public key mismatch - offline keys are corrupted!"
42+
else
43+
echo "✅ Validator 1 Aura public key matches"
44+
fi
45+
46+
if [ "$aura2_public" != "0x46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a" ]; then
47+
echo "❌ Validator 2 Aura public key mismatch - offline keys are corrupted!"
48+
else
49+
echo "✅ Validator 2 Aura public key matches"
50+
fi
51+
52+
if [ "$aura1_public" = "$aura2_public" ]; then
53+
echo "❌ Both validators have the same Aura public key - this is a critical error!"
54+
else
55+
echo "✅ Validators have different Aura public keys (correct)"
56+
fi
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
```mermaid
2+
graph TB
3+
%% Input triggers and verification
4+
T[Signed Git Tag<br/>fennel-node-X.Y.Z] --> GV{GPG Signature<br/>Verification}
5+
GV -->|✅ Valid| CI[CI/CD Pipeline]
6+
GV -->|❌ Invalid| FAIL[Build Fails]
7+
8+
%% Version extraction and propagation
9+
CI --> VE[Version Extraction<br/>X.Y.Z from tag]
10+
VE --> RT[Runtime Build<br/>srtool]
11+
VE --> CS[Chainspec Generation]
12+
VE --> DI[Docker Image Build]
13+
VE --> HC[Helm Chart Update]
14+
15+
%% Runtime and chainspec processing
16+
RT --> WH[Wasm Hash<br/>sha256]
17+
CS --> CSH[Chainspec SHA-256<br/>dev_sha + staging_sha]
18+
19+
%% Docker image processing
20+
DI --> IT[Image Tag<br/>fennel-node-X.Y.Z]
21+
DI --> ID[Image Digest<br/>sha256:abc123...]
22+
23+
%% Helm chart version unity
24+
HC --> CV[Chart Version<br/>X.Y.Z]
25+
HC --> CAV[Chart AppVersion<br/>X.Y.Z]
26+
27+
%% Cryptographic linking in Helm values
28+
IT --> HV[Helm Values Update]
29+
ID --> HV
30+
CSH --> HV
31+
VE --> HV
32+
33+
HV --> BV[Base values.yaml<br/>image.tag: X.Y.Z<br/>image.digest: sha256:...]
34+
HV --> SV[Staging values.yaml<br/>image.tag: X.Y.Z<br/>image.digest: sha256:...<br/>customChainspecSha256: abc123<br/>releaseTag: fennel-node-X.Y.Z]
35+
36+
%% Template rendering with digest-aware image helper
37+
BV --> TH[{{fennel-node.image}} Helper]
38+
SV --> TH
39+
TH --> IR[Image Reference<br/>repo@sha256:digest OR repo:tag]
40+
41+
%% Release artifact creation
42+
IT --> RA[Release Artifacts]
43+
ID --> RA
44+
WH --> RA
45+
CSH --> RA
46+
CV --> RA
47+
48+
RA --> GR[GitHub Release<br/>• fennel-node-X.Y.Z.tgz<br/>• development.json + raw<br/>• staging-chainspec.json + raw<br/>• image-info.txt]
49+
RA --> HR[Helm Repository<br/>Chart Releaser]
50+
51+
%% Deployment verification
52+
GR --> DV[Deployment Verification]
53+
HR --> DV
54+
DV --> VER[Runtime Verification<br/>• Image digest match<br/>• Chainspec SHA-256 match<br/>• Release tag consistency]
55+
56+
%% Security guarantees
57+
VER --> SG[Security Guarantees<br/>✅ Cryptographic version unity<br/>✅ Immutable artifact references<br/>✅ Tamper detection<br/>✅ Reproducible deployments]
58+
59+
%% Styling
60+
classDef input fill:#e1f5fe,stroke:#01579b,stroke-width:2px
61+
classDef process fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
62+
classDef hash fill:#fff3e0,stroke:#e65100,stroke-width:2px
63+
classDef artifact fill:#e8f5e8,stroke:#1b5e20,stroke-width:2px
64+
classDef security fill:#ffebee,stroke:#b71c1c,stroke-width:2px
65+
classDef fail fill:#ffcdd2,stroke:#d32f2f,stroke-width:3px
66+
67+
class T,GV input
68+
class CI,VE,RT,CS,DI,HC,HV,TH,DV process
69+
class WH,CSH,IT,ID,CV,CAV hash
70+
class BV,SV,IR,RA,GR,HR artifact
71+
class VER,SG security
72+
class FAIL fail
73+
```
74+
75+
## Cryptographic Version Unity Architecture
76+
77+
This diagram illustrates how the fennel-solonet CI/CD pipeline enforces cryptographic version unity across all release artifacts. The system ensures that every component in a deployment can be traced back to a single, verified Git tag through an unbroken chain of cryptographic hashes and version references.
78+
79+
### Key Security Features
80+
81+
1. **Single Source of Truth**: The signed Git tag `fennel-node-X.Y.Z` is the authoritative version source
82+
2. **Cryptographic Verification**: GPG signature verification ensures tag authenticity
83+
3. **Hash Propagation**: SHA-256 hashes link runtime, chainspecs, and Docker images
84+
4. **Digest-Based References**: Helm charts use immutable Docker image digests
85+
5. **Comprehensive Verification**: Deployment verifies all hashes match expected values
86+
87+
### Version Unity Enforcement
88+
89+
- **Chart Version** = **App Version** = **Release Tag Version** = `X.Y.Z`
90+
- **Docker Image Tag** = `fennel-node-X.Y.Z`
91+
- **Docker Image Digest** = immutable `sha256:...` reference
92+
- **Chainspec SHA-256** = runtime verification hash
93+
- **Release Tag** = Git tag embedded in Helm values
94+
95+
This architecture prevents version drift, ensures reproducible deployments, and provides cryptographic proof that all artifacts belong to the same verified release.

node/src/chain_spec.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,21 @@ pub fn staging_chain_spec() -> Result<ChainSpec, String> {
4545
])
4646
.build())
4747
}
48+
49+
pub fn production_chain_spec() -> Result<ChainSpec, String> {
50+
Ok(ChainSpec::builder(
51+
WASM_BINARY.ok_or_else(|| "Production wasm not available".to_string())?,
52+
None,
53+
)
54+
.with_name("Fennel Production")
55+
.with_id("fennel_production")
56+
.with_chain_type(ChainType::Live)
57+
.with_genesis_config_preset_name("production")
58+
.with_boot_nodes(vec![
59+
// Production bootnodes - will be populated dynamically in CI
60+
// These placeholder addresses will be replaced with derived peer IDs
61+
"/dns4/bootnode1.fennel.network/tcp/30333/p2p/12D3KooWS84f71ufMQRsm9YWynfK5Zxa6iSooStJECnAT3RBVVxz".parse().unwrap(),
62+
"/dns4/bootnode2.fennel.network/tcp/30333/p2p/12D3KooWLWzcGVuLycfL1W83yc9S4UmVJ8qBd4Rk5mS6RJ4Bh7Su".parse().unwrap(),
63+
])
64+
.build())
65+
}

node/src/command.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ impl SubstrateCli for Cli {
4040
"dev" => Box::new(chain_spec::development_chain_spec()?),
4141
"" | "local" => Box::new(chain_spec::local_chain_spec()?),
4242
"staging" => Box::new(chain_spec::staging_chain_spec()?),
43+
"production" => Box::new(chain_spec::production_chain_spec()?),
4344
path =>
4445
Box::new(chain_spec::ChainSpec::from_json_file(std::path::PathBuf::from(path))?),
4546
})

runtime/fennel/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ sp-consensus-aura = { features = ["serde"], workspace = true }
4848
sp-consensus-grandpa = { features = ["serde"], workspace = true }
4949
sp-core = { features = ["serde"], workspace = true }
5050
sp-genesis-builder.workspace = true
51+
hex = { version = "0.4", default-features = false, features = ["alloc"] }
5152
sp-inherents.workspace = true
5253
sp-io = { version = "40.0.0", default-features = false }
5354
sp-keyring.workspace = true

runtime/fennel/build.rs

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,89 @@
11
#[cfg(all(feature = "std", feature = "metadata-hash"))]
22
fn main() {
3+
// Pass production genesis environment variables as compile-time constants
4+
pass_genesis_env_vars();
5+
36
substrate_wasm_builder::WasmBuilder::init_with_defaults()
47
.enable_metadata_hash("UNIT", 12)
5-
.build();
6-
}
8+
.build();
9+
}
710

811
#[cfg(all(feature = "std", not(feature = "metadata-hash")))]
912
fn main() {
13+
// Pass production genesis environment variables as compile-time constants
14+
pass_genesis_env_vars();
15+
1016
substrate_wasm_builder::WasmBuilder::build_using_defaults();
1117
}
1218

1319
/// The wasm builder is deactivated when compiling
1420
/// this crate for wasm to speed up the compilation.
1521
#[cfg(not(feature = "std"))]
1622
fn main() {}
23+
24+
/// Pass production genesis environment variables as rustc-env for compile-time access
25+
/// Variables are MANDATORY for production builds but optional for development builds
26+
fn pass_genesis_env_vars() {
27+
// All required environment variables for production builds
28+
// These MUST be set for production builds to prevent accidental use of test keys
29+
const REQUIRED_VARS: &[&str] = &[
30+
"SUDO_SS58", // Production sudo account
31+
"VAL1_AURA_PUB", // Validator 1 AURA public key
32+
"VAL1_GRANDPA_PUB", // Validator 1 GRANDPA public key
33+
"VAL1_STASH_SS58", // Validator 1 stash account
34+
"VAL2_AURA_PUB", // Validator 2 AURA public key
35+
"VAL2_GRANDPA_PUB", // Validator 2 GRANDPA public key
36+
"VAL2_STASH_SS58", // Validator 2 stash account
37+
];
38+
39+
let mut missing_vars = Vec::new();
40+
let mut found_vars = Vec::new();
41+
42+
// Check all required variables first
43+
for &var_name in REQUIRED_VARS {
44+
match std::env::var(var_name) {
45+
Ok(value) => {
46+
// Forward to the compiler so `env!(var_name)` works in the runtime
47+
println!("cargo:rustc-env={}={}", var_name, value);
48+
println!("cargo:rerun-if-env-changed={}", var_name);
49+
found_vars.push(var_name);
50+
}
51+
Err(_) => {
52+
missing_vars.push(var_name);
53+
}
54+
}
55+
}
56+
57+
// Determine build mode based on environment variables presence
58+
if found_vars.is_empty() {
59+
// Development/staging build - no production variables set
60+
println!("🧪 Development/staging build detected (no production env vars)");
61+
println!("📋 Development and staging presets will use Alice/Bob hardcoded keys");
62+
println!("⚠️ Production preset will NOT be available for this build");
63+
} else if missing_vars.is_empty() {
64+
// Production build - all variables set
65+
println!("✅ Production build: All {} required environment variables are set", REQUIRED_VARS.len());
66+
println!("🏭 Production preset will use Vault-sourced public keys");
67+
} else {
68+
// Partial production build - some variables set, some missing (ERROR)
69+
eprintln!("🚨 PRODUCTION BUILD ERROR: Partial environment variable configuration detected!");
70+
eprintln!(" Found {} variables, missing {} variables", found_vars.len(), missing_vars.len());
71+
eprintln!();
72+
eprintln!(" ✅ Found:");
73+
for var in &found_vars {
74+
eprintln!(" • {}", var);
75+
}
76+
eprintln!();
77+
eprintln!(" ❌ Missing:");
78+
for var in &missing_vars {
79+
eprintln!(" • {}", var);
80+
}
81+
eprintln!();
82+
eprintln!("💡 To fix this:");
83+
eprintln!(" • For production: Set ALL 7 variables");
84+
eprintln!(" • For development: Set NONE of them (use Alice/Bob presets)");
85+
eprintln!(" • This prevents accidental mixing of production and test keys");
86+
eprintln!();
87+
panic!("Build halted due to partial production environment variable configuration");
88+
}
89+
}

0 commit comments

Comments
 (0)