Miner dev fee: app switch and UI line, HiveOS package, Linux worker cross-build, docs and public text
Decision of 4 October 2026 (evening): the Igneum Miner software takes a visible, switchable 1% dev fee, the norm for GPU miners; the protocol stays fee-free. The miner side (--dev-fee, the 1-in-100 template counter, the audit command) is on branch dev-fee of the node fork. - app: settings.dev_fee (default on) passes --dev-fee 0 to igneum-miner when off; Settings shows the miner's own "dev fee 1% (1 block in 100) to 0x..." line next to the rewards address with a switch; the engine parses the miner's start line and its dev-fee block lines (fee_session, lifetime fee_total, an event per fee block) - packaging/hive: h-manifest.conf, h-config.sh, h-run.sh, h-stats.sh, make-hive-package.sh (igneum-hive-<v>.tar.gz with the Linux igneumd, igneum-miner and both GPU workers), README with the Flight Sheet, selftest.sh (bash -n, stub binaries, the three hooks the way Hive runs them, the stats JSON parsed). Hive itself is untested - infra/cross/build-workers-linux.sh: the NVRTC and OpenCL workers cross-compiled for Linux with zig; proto-opencl/cl_dynamic.h gains the Linux dlopen branch (libOpenCL.so.1) - tools/dev-fee/run.mjs: the fee-block test network (two nodes on 29900+, three CPU miners, payouts audit) - docs/design/miner-dev-fee.md (mechanism, flag, lines, the DEV_FEE_ADDRESS placeholder and the devnet address), docs/fud-ledger.md E18 and the E5/L9 status line, litepaper "What a miner's hour looks like" paragraph, homepage miner section note Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
33c430d93c
commit
6b4ec59648
22 changed files with 710 additions and 11 deletions
|
|
@ -55,6 +55,13 @@ pub struct Settings {
|
|||
/// Prove the shards assigned to this machine's keys (src/prover.rs). Default off until the GPU numbers exist.
|
||||
#[serde(default)]
|
||||
pub prove: bool,
|
||||
/// The miner software's dev fee (1 block template in 100 to the dev address; decision of 4 October 2026). Default
|
||||
/// on; off passes `--dev-fee 0` to igneum-miner. The protocol itself carries no fee.
|
||||
#[serde(default = "yes")]
|
||||
pub dev_fee: bool,
|
||||
/// Lifetime dev-fee blocks this machine found (the miner's `dev-fee block` lines), carried across runs.
|
||||
#[serde(default)]
|
||||
pub fee_total: u64,
|
||||
}
|
||||
|
||||
fn one() -> u32 {
|
||||
|
|
@ -66,7 +73,7 @@ fn yes() -> bool {
|
|||
|
||||
impl Default for Settings {
|
||||
fn default() -> Settings {
|
||||
Settings { setup_done: false, address: String::new(), address_source: String::new(), key_saved: false, identities: 1, cards: HashMap::new(), display_name: String::new(), vote: true, paused: false, accepted_total: 0, auto_update: true, remote_jobs: true, prove: false }
|
||||
Settings { setup_done: false, address: String::new(), address_source: String::new(), key_saved: false, identities: 1, cards: HashMap::new(), display_name: String::new(), vote: true, paused: false, accepted_total: 0, auto_update: true, remote_jobs: true, prove: false, dev_fee: true, fee_total: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -96,8 +96,10 @@ impl Shared {
|
|||
st.mining.state = "idle".into();
|
||||
st.mining.paused = settings.paused;
|
||||
st.mining.accepted_total = settings.accepted_total;
|
||||
st.mining.fee_total = settings.fee_total;
|
||||
st.address = address_state(&settings, &wallet_path);
|
||||
st.settings = crate::state::SettingsState { identities: settings.identities, vote: settings.vote, start_at_login: crate::platform::start_at_login_is_on(), auto_update: settings.auto_update, remote_jobs: settings.remote_jobs, prove: settings.prove };
|
||||
st.settings = crate::state::SettingsState { identities: settings.identities, vote: settings.vote, start_at_login: crate::platform::start_at_login_is_on(), auto_update: settings.auto_update, remote_jobs: settings.remote_jobs, prove: settings.prove, dev_fee: settings.dev_fee };
|
||||
st.dev_fee = crate::state::DevFeeState { on: settings.dev_fee, percent: if settings.dev_fee { 1 } else { 0 }, address: String::new(), line: String::new() };
|
||||
st.live_page = packaged.live_page.clone();
|
||||
st.finality.message = "waiting for the miner".into();
|
||||
st.clock.hint = crate::platform::clock_hint().into();
|
||||
|
|
@ -251,7 +253,7 @@ impl Shared {
|
|||
Ok(json!({ "ok": true }))
|
||||
}
|
||||
|
||||
pub fn apply_settings(&self, identities: Option<u32>, vote: Option<bool>, login: Option<bool>, address: Option<&str>, display_name: Option<&str>) -> Result<Value, String> {
|
||||
pub fn apply_settings(&self, identities: Option<u32>, vote: Option<bool>, login: Option<bool>, address: Option<&str>, display_name: Option<&str>, dev_fee: Option<bool>) -> Result<Value, String> {
|
||||
let mut restart = Vec::new();
|
||||
{
|
||||
let mut s = self.settings.lock().unwrap();
|
||||
|
|
@ -272,6 +274,12 @@ impl Shared {
|
|||
restart.push(if v { "voting on".into() } else { "voting off".into() });
|
||||
}
|
||||
}
|
||||
if let Some(v) = dev_fee {
|
||||
if v != s.dev_fee {
|
||||
s.dev_fee = v;
|
||||
restart.push(if v { "dev fee on (1 block in 100)".into() } else { "dev fee off".into() });
|
||||
}
|
||||
}
|
||||
if let Some(a) = address {
|
||||
let a = a.trim().to_ascii_lowercase();
|
||||
if !a.is_empty() && a != s.address {
|
||||
|
|
@ -289,6 +297,9 @@ impl Shared {
|
|||
let mut st = self.state.lock().unwrap();
|
||||
st.settings.identities = s.identities;
|
||||
st.settings.vote = s.vote;
|
||||
st.settings.dev_fee = s.dev_fee;
|
||||
st.dev_fee.on = s.dev_fee;
|
||||
st.dev_fee.percent = if s.dev_fee { 1 } else { 0 };
|
||||
st.address = address_state(&s, &self.wallet_path);
|
||||
}
|
||||
if let Some(on) = login {
|
||||
|
|
@ -992,6 +1003,11 @@ impl Engine {
|
|||
if !s.vote {
|
||||
a.push("--no-vote".into());
|
||||
}
|
||||
if !s.dev_fee {
|
||||
// the miner software's dev fee (default 1% of templates); the switch in Settings
|
||||
a.push("--dev-fee".into());
|
||||
a.push("0".into());
|
||||
}
|
||||
a.push("--payout-label".into());
|
||||
a.push(slot.label.clone());
|
||||
if r.network != "devnet" {
|
||||
|
|
@ -2009,6 +2025,26 @@ impl Engine {
|
|||
}
|
||||
drop(st);
|
||||
self.shared.event("block", &format!("block accepted by the node ({card_name})"));
|
||||
} else if text.contains(" dev-fee block ") {
|
||||
// the software dev fee: one template in 100 paid the dev address (igneum-miner, "Software dev fee")
|
||||
let total = {
|
||||
let mut s = self.shared.settings.lock().unwrap();
|
||||
s.fee_total += 1;
|
||||
s.fee_total
|
||||
};
|
||||
let mut st = self.st();
|
||||
st.mining.fee_session += 1;
|
||||
st.mining.fee_total = total;
|
||||
drop(st);
|
||||
self.shared.event("block", &format!("that block was the dev fee's (1 in 100, {card_name})"));
|
||||
} else if let Some(rest) = text.split(" dev fee ").nth(1) {
|
||||
// the miner's start line: "dev fee 1% (1 block in 100) to 0x...; --dev-fee 0 turns it off", or
|
||||
// "dev fee off (--dev-fee 0); the default is 1% (1 block in 100) to 0x...", or "dev fee off: <why>"
|
||||
let on = !rest.starts_with("off");
|
||||
let percent = if on { rest.split('%').next().and_then(|p| p.trim().parse::<u32>().ok()).unwrap_or(1) } else { 0 };
|
||||
let address = rest.find("0x").map(|k| rest[k..].chars().take(42).collect::<String>()).filter(|a| a.len() == 42 && keys::valid_address(a)).unwrap_or_default();
|
||||
let mut st = self.st();
|
||||
st.dev_fee = crate::state::DevFeeState { on, percent, address, line: rest.to_string() };
|
||||
} else if text.contains(" STATUS '") {
|
||||
self.miners[i].last_status = Some(Instant::now());
|
||||
let avg = kv_f64(text, "hash").unwrap_or(0.0);
|
||||
|
|
@ -2107,7 +2143,7 @@ impl Engine {
|
|||
let miner_txt = if st.mining.cards.is_empty() {
|
||||
"no GPU miner".to_string()
|
||||
} else {
|
||||
format!("accepted {} blocks ({} this run), {:.2} MH/s, {}", st.mining.accepted_total, st.mining.accepted_session, st.mining.hash_total, st.mining.state)
|
||||
format!("accepted {} blocks ({} this run, dev fee {}), {:.2} MH/s, {}", st.mining.accepted_total, st.mining.accepted_session, st.mining.fee_session, st.mining.hash_total, st.mining.state)
|
||||
};
|
||||
let up = fmt_uptime(self.shared.started.elapsed().as_secs());
|
||||
drop(st);
|
||||
|
|
|
|||
|
|
@ -257,7 +257,8 @@ fn api_post(shared: &Arc<Shared>, path: &str, body: Value) -> Result<Value, Stri
|
|||
let login = body.get("start_at_login").and_then(|v| v.as_bool());
|
||||
let address = s("address");
|
||||
let display_name = s("display_name");
|
||||
shared.apply_settings(identities, vote, login, address.as_deref(), display_name.as_deref())
|
||||
let dev_fee = body.get("dev_fee").and_then(|v| v.as_bool());
|
||||
shared.apply_settings(identities, vote, login, address.as_deref(), display_name.as_deref(), dev_fee)
|
||||
}
|
||||
"/api/prove" => shared.set_prove(body.get("on").and_then(|v| v.as_bool()).unwrap_or(false)),
|
||||
"/api/prove/setup" => crate::prover::setup(shared),
|
||||
|
|
|
|||
|
|
@ -82,6 +82,18 @@ pub struct MiningState {
|
|||
pub accepted_session: u64,
|
||||
pub rejected_session: u64,
|
||||
pub found: Vec<f64>, // unix times of the accepted blocks, last hour
|
||||
/// dev-fee blocks (the miner's `dev-fee block` lines): this run, and lifetime
|
||||
pub fee_session: u64,
|
||||
pub fee_total: u64,
|
||||
}
|
||||
|
||||
/// The miner software's dev fee as the miner reports it at start (`dev fee 1% (1 block in 100) to 0x...`).
|
||||
#[derive(Clone, Serialize, Default)]
|
||||
pub struct DevFeeState {
|
||||
pub on: bool,
|
||||
pub percent: u32,
|
||||
pub address: String, // 0x + 40 hex once a miner has printed it
|
||||
pub line: String, // the miner's own words
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Default)]
|
||||
|
|
@ -158,6 +170,8 @@ pub struct SettingsState {
|
|||
pub remote_jobs: bool,
|
||||
/// the prover service (src/prover.rs)
|
||||
pub prove: bool,
|
||||
/// the miner software's dev fee switch (settings; `--dev-fee 0` when off)
|
||||
pub dev_fee: bool,
|
||||
}
|
||||
|
||||
/// One remote job this machine ran (the ledger entry), for the Settings history and the last-job strip.
|
||||
|
|
@ -267,6 +281,7 @@ pub struct State {
|
|||
pub clock: ClockState,
|
||||
pub address: AddressState,
|
||||
pub settings: SettingsState,
|
||||
pub dev_fee: DevFeeState,
|
||||
pub update: UpdateState,
|
||||
pub jobs: JobsState,
|
||||
pub events: Vec<Event>,
|
||||
|
|
|
|||
|
|
@ -110,6 +110,8 @@
|
|||
$('s-name').value = state.display_name || '';
|
||||
$('s-mid').textContent = (state.machine_id || '').slice(0, 8);
|
||||
$('s-vote').checked = state.settings.vote;
|
||||
$('s-devfee').checked = state.settings.dev_fee;
|
||||
$('s-devfee-text').textContent = devFeeText(state);
|
||||
$('s-login').checked = state.settings.start_at_login;
|
||||
$('s-version').textContent = 'Igneum Miner ' + state.version;
|
||||
$('s-node-dir').textContent = 'chain data: ' + state.node_dir;
|
||||
|
|
@ -138,6 +140,15 @@
|
|||
api('api/cards', { cards: choices }).then(function (r) { if (r.ok) toast('Applied; only the changed workers restart'); });
|
||||
});
|
||||
$('s-vote').addEventListener('change', function () { api('api/settings', { vote: this.checked }).then(function (r) { if (r.ok) toast(r.restart ? 'Applied; the miner restarts' : 'Applied'); }); });
|
||||
$('s-devfee').addEventListener('change', function () { api('api/settings', { dev_fee: this.checked }).then(function (r) { if (r.ok) toast(r.restart ? 'Applied; the miner restarts' : 'Applied'); }); });
|
||||
// the miner's own start line, next to the rewards address: "dev fee 1% (1 block in 100) to 0x...; --dev-fee 0 turns it off"
|
||||
function devFeeText(s) {
|
||||
var d = s.dev_fee || {};
|
||||
var addr = d.address ? ' to ' + d.address.slice(0, 8) + '\u2026' + d.address.slice(-4) : '';
|
||||
if (!s.settings.dev_fee) return 'Dev fee off (--dev-fee 0); the default is 1% (1 block in 100)' + addr;
|
||||
if (d.line && !d.on && !d.address) return 'Dev fee: ' + d.line;
|
||||
return 'Dev fee ' + (d.percent || 1) + '% (1 block in 100)' + addr + (d.address ? '' : '; the miner names the address when it starts');
|
||||
}
|
||||
$('s-login').addEventListener('change', function () { var on = this.checked; api('api/settings', { start_at_login: on }).then(function (r) { if (!r.ok) { toast(r.error || 'could not change'); $('s-login').checked = !on; } }); });
|
||||
$('s-update').addEventListener('click', function () { api('api/update/check', {}); $('s-update-note').textContent = 'Checking.'; setTimeout(fillSettings, 4000); });
|
||||
$('s-install').addEventListener('click', function () { api('api/update/install', {}); toast('Installing at once'); });
|
||||
|
|
@ -333,6 +344,8 @@
|
|||
var nowU = s.now;
|
||||
$('d-found-10').textContent = m.found.filter(function (t) { return nowU - t <= 600; }).length;
|
||||
$('d-found-60').textContent = m.found.length;
|
||||
$('d-fee').textContent = withCommas(m.fee_total || 0);
|
||||
$('d-fee').title = (m.fee_session || 0) + ' this run; 1 block in 100 pays the miner software\'s dev fee';
|
||||
$('d-chain-blocks').textContent = compact(n.blocks);
|
||||
// per-card tiles (enabled cards), the rest in one muted line
|
||||
var on = m.cards.filter(function (c) { return c.enabled; }), off = m.cards.filter(function (c) { return !c.enabled; });
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@
|
|||
<div class="step">
|
||||
<div class="eyebrow">step 2 of 2</div>
|
||||
<h2>Where should rewards go?</h2>
|
||||
<p class="sub">Every block this machine finds pays one EVM address. Pick one way.</p>
|
||||
<p class="sub">Every block this machine finds pays one EVM address. Pick one way. One block in 100 pays the miner software's dev fee; Settings turns it off.</p>
|
||||
<div class="options">
|
||||
<label class="option on" id="opt-generate">
|
||||
<input type="radio" name="mode" value="generate" checked>
|
||||
|
|
@ -163,7 +163,7 @@
|
|||
<div class="card">
|
||||
<div class="card-head">
|
||||
<div class="eyebrow"><span class="dot small"></span>your blocks</div>
|
||||
<div class="stats mono"><span>last 10 min <b id="d-found-10">0</b></span><span>last hour <b id="d-found-60">0</b></span><span>chain <b id="d-chain-blocks">0</b></span></div>
|
||||
<div class="stats mono"><span>last 10 min <b id="d-found-10">0</b></span><span>last hour <b id="d-found-60">0</b></span><span>dev fee <b id="d-fee">0</b></span><span>chain <b id="d-chain-blocks">0</b></span></div>
|
||||
</div>
|
||||
<canvas id="dag" aria-hidden="true"></canvas>
|
||||
<div class="legend"><span><i class="sw ember"></i>block this machine found</span><span><i class="sw glow"></i>just accepted</span><span><i class="sw line"></i>one minute</span></div>
|
||||
|
|
@ -259,6 +259,8 @@
|
|||
<div class="err" id="s-address-err" hidden></div>
|
||||
<button class="btn small ghost" id="s-reveal" hidden>Show my key</button>
|
||||
<div class="box mono key" id="s-key-box" hidden><span id="s-key"></span><button class="btn tiny" data-copy="s-key">Copy</button></div>
|
||||
<label class="switch"><input type="checkbox" id="s-devfee"><span class="track"></span><span id="s-devfee-text">Dev fee 1% (1 block in 100)</span></label>
|
||||
<p class="note" id="s-devfee-note">The miner software's fee, the same way every GPU miner takes one. The protocol takes none. This switch turns it off.</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="k">this machine</div>
|
||||
|
|
|
|||
71
docs/design/miner-dev-fee.md
Normal file
71
docs/design/miner-dev-fee.md
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# The miner software's dev fee
|
||||
|
||||
Decision (Josh, 4 October 2026, evening): the Igneum miner software takes a visible, switchable 1% dev fee, the norm
|
||||
for GPU miners (lolMiner, T-Rex). The protocol stays fee-free: no dev fund, no fee to any team, foundation or fund
|
||||
(CLAUDE.md, litepaper). This fee is the software's, like every third-party miner's, and it is documented as such.
|
||||
|
||||
## Mechanism
|
||||
|
||||
Solo mining, no pool: the miner asks its node for block templates with a payout address. One template in 100 is
|
||||
requested with the dev payout address instead of the user's. The choice is a template counter, never a random draw,
|
||||
so it is exactly 1 in 100 and anyone can audit it from the source: template `n` (counting from 0) is a fee template
|
||||
when `floor((n + 1) p / 100) > floor(n p / 100)`, which at `p = 1` is the templates 99, 199, 299, ... Every template
|
||||
is mined for the same time, so fee blocks are `p` in 100 on average.
|
||||
|
||||
What changes on a fee template, and what does not:
|
||||
|
||||
| Field | User template | Fee template |
|
||||
|---|---|---|
|
||||
| Coinbase extra data `IGNA` address (the execution-layer payout the chain pays) | the user's `--evm-address` | the dev address |
|
||||
| Vote key hash (finality weight) | the user's | the user's, unchanged |
|
||||
| UTXO-side coinbase script (`--address`, a label address with no key in the app) | the user's | the user's, unchanged |
|
||||
|
||||
So a fee block still adds to the user's finality weight, and the only thing that moves is who the execution layer
|
||||
pays for that block.
|
||||
|
||||
Source: `vendor/igneum-node-v4` branch `dev-fee`, `igneum/miner/src/main.rs`, section "Software dev fee"
|
||||
(`DevFee`, `fee_slot`, `Identity::with_dev_fee_address`, `template(.., fee)`, `submit(.., fee)`, `payouts`).
|
||||
|
||||
## The flag, the lines, the counter
|
||||
|
||||
| Where | What |
|
||||
|---|---|
|
||||
| `igneum-miner mine ... --dev-fee <percent>` | whole percent of templates; default 1; `--dev-fee 0` turns it off |
|
||||
| Start line, on | `dev fee 1% (1 block in 100) to 0x<address>; --dev-fee 0 turns it off` |
|
||||
| Start line, off | `dev fee off (--dev-fee 0); the default is 1% (1 block in 100) to 0x<address>` |
|
||||
| Start line, no release address | `dev fee off: no release address is set (DEV_FEE_ADDRESS placeholder in igneum/miner/src/main.rs)` |
|
||||
| Per fee block accepted | `dev-fee block <hash>` |
|
||||
| Status line (CPU and worker modes) and `MINER SUMMARY` | `fee=N` |
|
||||
| `igneum-miner payouts <grpc url>` | blocks per `IGNA` payout address over every block the node holds, with the share; the dev address is tagged `(dev fee)` |
|
||||
|
||||
The app (Igneum Miner): Settings shows the same line next to the rewards address with a switch, stored in
|
||||
`settings.json` as `dev_fee` (default on); off passes `--dev-fee 0`. The dashboard's "your blocks" row shows the
|
||||
lifetime dev-fee block count; each fee block is also an event. The HiveOS package: `DEV_FEE=0` in the Flight Sheet's
|
||||
extra config.
|
||||
|
||||
## The addresses
|
||||
|
||||
| Constant (`igneum/miner/src/main.rs`) | Value | Network |
|
||||
|---|---|---|
|
||||
| `DEV_FEE_ADDRESS` | the placeholder string `DEV_FEE_ADDRESS`; **Josh fills it before any public release**. While it is not 40 hex the fee is off outside the devnet and the miner says so at start | mainnet, testnet |
|
||||
| `DEV_FEE_ADDRESS_DEVNET` | `0xdfaea67368f3e3753397d878f97efe6aa8020c2e` | devnet and simnet only |
|
||||
|
||||
The devnet address was generated on 4 October 2026 with the app's own key derivation (secp256k1, keccak of the
|
||||
uncompressed public key, `app/igneum-app/src/keys.rs`), checked against the known vector for key 0x01. The private key
|
||||
is held outside the repository (the maintainers' local secret store, mode 0600) and never in any commit. Devnet coins
|
||||
have no value and the devnet may be reset; this address is never a release address.
|
||||
|
||||
## Tests and measurement
|
||||
|
||||
- Unit tests (`cargo test --release -p igneum-miner dev_fee_tests`): exactly 100 fee templates in 10,000 at 1%, at
|
||||
positions 99, 199, ...; 0 at `--dev-fee 0`; 2, 3, 5, 10, 50 and 100% exact over 10,000; a fee template carries the
|
||||
dev `IGNA` address and the user's unchanged key reveal; the release placeholder keeps the fee off outside the devnet.
|
||||
- Test network: `node tools/dev-fee/run.mjs` (two nodes on 29900+, fast-time profile, proof of work skipped and the
|
||||
genesis target at the floor, three CPU stub miners: two at the default fee and one control at `--dev-fee 0`), then
|
||||
`igneum-miner payouts` on the peer node. The numbers are in `docs/bench-log.md` under "the software dev fee measured".
|
||||
|
||||
## Public text
|
||||
|
||||
- Litepaper, "For miners", "What a miner's hour looks like": the fee paragraph.
|
||||
- Homepage, the miner section's download note.
|
||||
- `docs/fud-ledger.md`: entry E18 (software-level, switchable, the audit).
|
||||
|
|
@ -1808,6 +1808,15 @@ Answer: Correct on each point; `docs/review/round-4-2026-10-04.md` section 7 hol
|
|||
|
||||
Evidence: the files above. Experiment: the three draw lines.
|
||||
|
||||
### E18. The dev fee is a protocol fee with better PR
|
||||
"A 1% dev fee in the official miner is 1% of the chain's hashrate paid to one company for as long as miners run it. Call it what it is: a founder allocation, hidden in the client, and nobody can tell how often it really fires."
|
||||
|
||||
Status: Answered by design and with evidence (4 October 2026, evening; Josh's decision of that evening, branch `dev-fee` in both repositories).
|
||||
|
||||
Answer: The fee exists and is disclosed; the rest is wrong in three places. (1) It is software-level, not protocol-level: no consensus rule, coinbase rule or emission rule knows of it. The chain pays whatever payout address a block template names, and the miner software names the dev address for one template in 100. A different client, or the same client with `--dev-fee 0` (a switch in the app's Settings, `DEV_FEE=0` on HiveOS), pays nothing and the chain treats its blocks exactly the same. That is the norm for GPU miners (lolMiner, T-Rex, approximate as to their percentages) and the litepaper says so beside the words "the protocol carries no fee". (2) It is auditable, not hidden: the choice is a template counter, not a random draw. Template n (from 0) is a fee template when floor((n + 1) p / 100) > floor(n p / 100), which at p = 1 is exactly the templates 99, 199, 299, ...; the source is one function (`fee_slot`, `igneum/miner/src/main.rs`, section "Software dev fee"), the miner prints `dev fee 1% (1 block in 100) to 0x<address>; --dev-fee 0 turns it off` at start, logs `dev-fee block <hash>` for each one and counts `fee=N` in its status line, and `igneum-miner payouts <node>` reads the chain back and lists blocks per payout address with the dev address tagged. (3) It takes nothing from anyone else's security: a fee block keeps the user's vote key, so the user's finality weight is unchanged; only the execution-layer payout of that block moves.
|
||||
|
||||
Evidence: `docs/design/miner-dev-fee.md`; the unit tests in `igneum/miner/src/main.rs` (`dev_fee_tests`: 100 fee templates in 10,000 at 1%, 0 at 0, exact at 2 to 100%); the test-network run in `docs/bench-log.md` ("the software dev fee measured") with the dev address's block count against the miners' own counters and the 1-in-100 expectation. Open: the release address is a placeholder (`DEV_FEE_ADDRESS`) until Josh fills it before any public release; the devnet address is labelled as such.
|
||||
|
||||
### X29. Host and file hygiene, minor
|
||||
"The Mac's live node binds its gRPC to every interface. Four secrets or pointers in `~/.config/igneum` are world-readable, one token is a filename, and the intake key rides on `curl`'s command line. The manifest answers CORS `*` and the clock source is a cacheable page's Date header."
|
||||
|
||||
|
|
@ -1832,4 +1841,5 @@ Evidence: the commits above. Experiment: `curl https://igneum.network/api/live`
|
|||
- **M20** (pruning proofs under the stub). Extended: `validate_trusted_header` (`processor.rs:330-337`) skips `pre_pow_validation`, so trusted and pruning-proof headers are never checked for bits, DAA score or the v2 activation. Review id R4.1.11.
|
||||
- **M13** (Macs mine too). Extended: the ratio is measured, 26.7 / 124, about a fifth, and the litepaper (`:420`) still carries no ratio; the app shows no projected earnings, which for a devnet is the right outcome. Review id R4.7.6.
|
||||
- **E5** (the client dev fee). Still open on the homepage; see L9.
|
||||
- **E5, L9** (the client dev fee), evening: the fee is built, switchable and audited (E18). The homepage's miner section now carries the litepaper's sentence (`site/index.html`, under the download buttons: "The protocol carries no fee. The Igneum Miner software takes an optional 1% dev fee, like other GPU miners, off with one flag"), and the litepaper's "What a miner's hour looks like" states the mechanism, the flag and the audit. The "no value" and "may be reset" devnet wording of L9 is still open.
|
||||
- **D2** (the app share). The "Why build here" text is applied at `site/litepaper.html:355` and states the number; two gaps noted under E17.
|
||||
|
|
|
|||
2
infra/cross/.gitignore
vendored
2
infra/cross/.gitignore
vendored
|
|
@ -1 +1,3 @@
|
|||
out/
|
||||
out-devfee/
|
||||
out-workers/
|
||||
|
|
|
|||
39
infra/cross/build-workers-linux.sh
Executable file
39
infra/cross/build-workers-linux.sh
Executable file
|
|
@ -0,0 +1,39 @@
|
|||
#!/usr/bin/env bash
|
||||
# Cross-compile the two GPU workers for x86_64 Linux on the Mac with zig as the C and C++ toolchain (the same zig that
|
||||
# cargo-zigbuild uses for igneumd and igneum-miner in build-linux.sh). Both workers open their GPU library at run time
|
||||
# (dlopen, no import library): libcuda.so.1 and libnvrtc.so.12 for the NVRTC worker, libOpenCL.so.1 for the OpenCL
|
||||
# worker (proto-opencl/cl_dynamic.h, Linux branch of 4 October 2026). For the HiveOS package (packaging/hive).
|
||||
#
|
||||
# infra/cross/build-workers-linux.sh -> infra/cross/out-workers/{igneum-worker-cuda,igneum-worker-opencl}
|
||||
# OUT_DIR=... GLIBC=2.36 infra/cross/build-workers-linux.sh
|
||||
#
|
||||
# Untested on a Linux GPU host as of 4 October 2026: the binaries link and load (checked with file and the ELF
|
||||
# interpreter); a HiveOS rig is the first real run.
|
||||
set -euo pipefail
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO="$(cd "$HERE/../.." && pwd)"
|
||||
OUT_DIR="${OUT_DIR:-$HERE/out-workers}"
|
||||
GLIBC="${GLIBC:-2.36}"
|
||||
TARGET="x86_64-linux-gnu"
|
||||
RED="${RED:-$REPO/proto-cuda/nvrtc/redist}" # the fetched headers (fetch-redist.sh; untracked, so a worktree may point RED at the main checkout's copy)
|
||||
PLACEHOLDER="$REPO/proto-cuda/packs/igneum-devnet-v4-epoch0"
|
||||
log() { printf '%s %s\n' "$(date -u +%H:%M:%S)" "$*"; }
|
||||
die() { log "ERROR: $*" >&2; exit 1; }
|
||||
command -v zig >/dev/null 2>&1 || die "zig is not installed (brew install zig)"
|
||||
[ -f "$RED/include/cuda.h" ] && [ -f "$RED/include/nvrtc.h" ] && [ -f "$RED/include/CL/cl.h" ] || die "run proto-cuda/nvrtc/fetch-redist.sh first (cuda.h, nvrtc.h, CL/cl.h)"
|
||||
[ -d "$PLACEHOLDER" ] || die "no placeholder pack at $PLACEHOLDER"
|
||||
mkdir -p "$OUT_DIR"
|
||||
start=$(date +%s)
|
||||
log "igneum-worker-cuda for $TARGET.$GLIBC (zig $(zig version))"
|
||||
nice -n 19 zig c++ -target "$TARGET.$GLIBC" -std=c++17 -O2 -Wall -Wextra -I "$REPO/proto-cuda/nvrtc" -I "$RED/include" \
|
||||
-o "$OUT_DIR/igneum-worker-cuda" "$REPO/proto-cuda/nvrtc/worker.cpp" -ldl -lpthread
|
||||
log "igneum-worker-opencl for $TARGET.$GLIBC"
|
||||
nice -n 19 zig cc -target "$TARGET.$GLIBC" -std=gnu99 -D_GNU_SOURCE -O2 -Wall -Wextra -Wno-stringop-truncation -Wno-format-truncation \
|
||||
-DIGNEUM_CL_DYNAMIC -DCL_TARGET_OPENCL_VERSION=120 -I "$RED/include" -I "$PLACEHOLDER" -DIGNEUM_KERNEL_PATH='"kernel_bound.cl"' \
|
||||
-o "$OUT_DIR/igneum-worker-opencl" "$REPO/proto-opencl/host.c" -ldl -lm -lpthread
|
||||
chmod +x "$OUT_DIR/igneum-worker-cuda" "$OUT_DIR/igneum-worker-opencl"
|
||||
{
|
||||
printf 'igneum workers, linux x86_64, glibc %s, cross-compiled with zig %s on %s from %s\n' "$GLIBC" "$(zig version)" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$(git -C "$REPO" rev-parse --short HEAD 2>/dev/null || echo unknown)"
|
||||
} > "$OUT_DIR/version.txt"
|
||||
file "$OUT_DIR/igneum-worker-cuda" "$OUT_DIR/igneum-worker-opencl" | sed 's/^/ /'
|
||||
log "done in $(( $(date +%s) - start )) s; binaries in $OUT_DIR"
|
||||
2
packaging/hive/.gitignore
vendored
Normal file
2
packaging/hive/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# make-hive-package.sh output (the archive is published on the download host, never committed)
|
||||
build/
|
||||
69
packaging/hive/README.md
Normal file
69
packaging/hive/README.md
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# Igneum Miner for HiveOS
|
||||
|
||||
The Igneum miner as a HiveOS custom miner: `igneum-hive-<version>.tar.gz` holds the Linux `igneum-miner`, `igneumd`,
|
||||
the two GPU workers (`igneum-worker-cuda` for NVIDIA, `igneum-worker-opencl` for AMD) and the four hooks Hive runs
|
||||
(`h-manifest.conf`, `h-config.sh`, `h-run.sh`, `h-stats.sh`). Built on 4 October 2026. **Hive itself is untested**: the
|
||||
scripts were self-tested with stub binaries (`selftest.sh`) and the binaries were cross-compiled on a Mac; the first real
|
||||
run on a Hive rig is still to come. Report what breaks.
|
||||
|
||||
## Flight Sheet
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Miner | Custom |
|
||||
| Installation URL | `https://<host>/igneum-hive-<version>.tar.gz` (the archive from `make-hive-package.sh`, published on the download host) |
|
||||
| Miner name | `igneum` |
|
||||
| Wallet and worker template | `0x<40 hex>.%WORKER_NAME%`: the payout address is an EVM address you hold the key for; the part after the dot labels this rig's keys |
|
||||
| Pool URL | `grpc://<your node>:26610` (your own igneumd, solo mining), or `local` to run the bundled node on the rig |
|
||||
| Pass | empty |
|
||||
| Extra config arguments | `DEV_FEE=1 IDENTITIES=8 WORKER=auto VOTE=1` (one per line also works); `PEERS=a:26611,b:26611` for the bundled node; `EXTRA="..."` for more miner flags |
|
||||
|
||||
Solo mining: there is no pool. Each card mines block templates from the node and the block reward pays the wallet in
|
||||
the template (80% of each block to its finder, 20% to the proving pool; the protocol takes no fee for anyone).
|
||||
|
||||
## The dev fee
|
||||
|
||||
The Igneum miner software takes a 1% dev fee the way every GPU miner does (lolMiner, T-Rex): one block template in
|
||||
100 is requested with the dev payout address instead of yours. It is a counter, not a random draw, so it is exactly
|
||||
1 in 100 and anyone can audit it from the source (`igneum/miner/src/main.rs`, "Software dev fee") or from the chain
|
||||
(`igneum-miner payouts grpc://<node>:26610` lists blocks per payout address). `DEV_FEE=0` in the extra config turns it
|
||||
off. The miner prints its own line at start, in each GPU's log:
|
||||
|
||||
dev fee 1% (1 block in 100) to 0x<address>; --dev-fee 0 turns it off
|
||||
|
||||
The protocol carries no fee: this is the software's, and any other miner client is welcome.
|
||||
|
||||
## What the hooks do
|
||||
|
||||
| Hook | What |
|
||||
|---|---|
|
||||
| `h-config.sh` | writes `igneum.conf` from the Flight Sheet (node URL, wallet, label, DEV_FEE, IDENTITIES, WORKER, VOTE, PEERS, EXTRA); refuses a wallet that is not 0x + 40 hex |
|
||||
| `h-run.sh` | starts the bundled node when the URL is `local`, waits for the node, exports the hourly program pack (`igneum-miner export-pack`), then one `igneum-miner` per GPU with its worker; restarts a miner that exits (exit 42 = program change without prepare support: the pack is re-exported first); per-GPU logs `<log>.gpu<N>.log`, merged into the main log |
|
||||
| `h-stats.sh` | per-GPU hash rate from each miner's last `STATUS` line (`now=<MH/s>`), accepted and rejected totals, dev-fee block count, temperatures and fans from Hive's `gpu-stats` (else `nvidia-smi`), uptime, version |
|
||||
|
||||
Stats JSON (what Hive reads from `$stats`): `hs` (kH/s per GPU), `hs_units` (`khs`), `temp`, `fan`, `uptime` (s),
|
||||
`ver`, `ar` (`[accepted, rejected]`), `algo` (`igneum`), `bus_numbers`, plus `dev_fee_blocks` (ours). `$khs` is the total.
|
||||
|
||||
## Requirements on the rig
|
||||
|
||||
- NVIDIA: the driver (`libcuda.so.1`) and NVRTC (`libnvrtc.so.12`, from a CUDA 12 toolkit or the NVRTC redistributable
|
||||
on the library path). The worker compiles the hourly program at run time with NVRTC; without the library it says so
|
||||
and the miner retries. Hive images ship the driver; whether `libnvrtc.so.12` is present depends on the image, untested.
|
||||
- AMD: an OpenCL ICD (`libOpenCL.so.1` from ROCm or amdgpu-pro). The OpenCL worker compiles the program through the ICD.
|
||||
- The bundled node (`local`) keeps its chain data under the miner folder (`data/`); a devnet chain is small today.
|
||||
- Ports: the bundled node listens on 26611 (p2p) and answers RPC on 127.0.0.1:26610 only.
|
||||
|
||||
## Building the package
|
||||
|
||||
infra/cross/build-linux.sh # igneumd and igneum-miner for Linux (cargo-zigbuild), into infra/cross/out
|
||||
infra/cross/build-workers-linux.sh # the two workers (zig c++ and zig cc), into infra/cross/out-workers
|
||||
packaging/hive/make-hive-package.sh # the archive, sha256 and the Flight Sheet lines; NODE_OUT=infra/cross/out-v2 to pick a build
|
||||
packaging/hive/selftest.sh # bash -n, a stub package, the three hooks the way Hive runs them
|
||||
|
||||
## Known gaps (4 October 2026)
|
||||
|
||||
- Never run on Hive. The hook contract follows Hive's custom-miner README (archive `name-version.tar.gz` with the
|
||||
directory `name/`; `h-config.sh` and `h-stats.sh` are sourced; `$khs` and `$stats`), approximate until a rig confirms it.
|
||||
- GPU order: the CUDA device index is assumed to follow `nvidia-smi` order and Hive's `gpu-stats` arrays (NVIDIA first);
|
||||
a mixed NVIDIA and AMD rig may show temperatures against the wrong card.
|
||||
- Each card runs its own `igneum-miner` and node connection; the node's template RPC serves them all.
|
||||
61
packaging/hive/h-config.sh
Executable file
61
packaging/hive/h-config.sh
Executable file
|
|
@ -0,0 +1,61 @@
|
|||
#!/usr/bin/env bash
|
||||
# Igneum Miner for HiveOS: writes igneum.conf from the Flight Sheet. Sourced by Hive (so: return, never exit).
|
||||
# CUSTOM_URL the node the miner mines against: grpc://host:26610 (your own igneumd), or "local" to run the
|
||||
# bundled node on this rig (data under the miner folder, peers from PEERS or the public seed)
|
||||
# CUSTOM_TEMPLATE the payout address: 0x followed by 40 hex (an EVM address you hold the key for), optionally
|
||||
# followed by .workername (the label of this rig's keys; defaults to the Hive worker name)
|
||||
# CUSTOM_PASS unused
|
||||
# CUSTOM_USER_CONFIG extra lines, KEY=VALUE, one per line or separated by spaces:
|
||||
# DEV_FEE=1 the miner software's dev fee in whole percent (1 block template in 100 to the dev
|
||||
# address); DEV_FEE=0 turns it off. The protocol itself takes no fee.
|
||||
# IDENTITIES=8 vote keys per card (8 for a big card, 2 for a small one)
|
||||
# WORKER=auto auto | cuda | opencl (the GPU worker; auto = cuda on NVIDIA, opencl on AMD)
|
||||
# VOTE=1 sign finality checkpoints (0 = mine without voting)
|
||||
# PEERS=a:26611,b:26611 peers for the bundled node when CUSTOM_URL=local
|
||||
# EXTRA="..." appended to every igneum-miner command line
|
||||
# Hive sources h-manifest.conf before this hook; the fallback is for a run outside Hive (selftest.sh)
|
||||
[[ -z "$CUSTOM_CONFIG_FILENAME" ]] && . "$(dirname "${BASH_SOURCE[0]}")/h-manifest.conf"
|
||||
|
||||
[[ -z "$CUSTOM_TEMPLATE" ]] && echo -e "${YELLOW:-}Igneum: the wallet (CUSTOM_TEMPLATE) is empty; it must be 0x followed by 40 hex${NOCOLOR:-}" && return 1
|
||||
[[ -z "$CUSTOM_URL" ]] && echo -e "${YELLOW:-}Igneum: the pool URL (CUSTOM_URL) is empty; grpc://host:26610 of your node, or local${NOCOLOR:-}" && return 1
|
||||
|
||||
wallet="${CUSTOM_TEMPLATE%%.*}"
|
||||
label="${CUSTOM_TEMPLATE#*.}"
|
||||
[[ "$label" == "$CUSTOM_TEMPLATE" || -z "$label" ]] && label="${WORKER_NAME:-rig}"
|
||||
label="$(printf '%s' "$label" | tr -c 'A-Za-z0-9_-' '-' | cut -c1-32)"
|
||||
if ! [[ "$wallet" =~ ^0x[0-9a-fA-F]{40}$ ]]; then
|
||||
echo -e "${YELLOW:-}Igneum: the wallet must be 0x followed by 40 hex characters, got '$wallet'${NOCOLOR:-}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# defaults, then the user's KEY=VALUE lines
|
||||
DEV_FEE=1; IDENTITIES=8; WORKER=auto; VOTE=1; PEERS=""; EXTRA=""
|
||||
while read -r kv; do
|
||||
[[ -z "$kv" || "$kv" == \#* ]] && continue
|
||||
key="${kv%%=*}"; val="${kv#*=}"
|
||||
case "$key" in
|
||||
DEV_FEE|IDENTITIES|WORKER|VOTE|PEERS|EXTRA) printf -v "$key" '%s' "$val" ;;
|
||||
*) echo "Igneum: unknown setting '$key' ignored" ;;
|
||||
esac
|
||||
done < <(printf '%s\n' "$CUSTOM_USER_CONFIG" | tr ' ' '\n' | sed 's/^"//; s/"$//')
|
||||
[[ "$DEV_FEE" =~ ^[0-9]+$ ]] || DEV_FEE=1
|
||||
[[ "$IDENTITIES" =~ ^[0-9]+$ ]] || IDENTITIES=8
|
||||
|
||||
url="$CUSTOM_URL"
|
||||
[[ "$url" == "local" ]] && url="local"
|
||||
[[ "$url" != "local" && "$url" != grpc://* ]] && url="grpc://$url"
|
||||
|
||||
mkdir -p "$(dirname "$CUSTOM_CONFIG_FILENAME")"
|
||||
cat > "$CUSTOM_CONFIG_FILENAME" <<CONF
|
||||
# written by h-config.sh from the Flight Sheet; edit the Flight Sheet, not this file
|
||||
NODE_URL=$url
|
||||
WALLET=$(printf '%s' "$wallet" | tr 'A-F' 'a-f')
|
||||
LABEL=$label
|
||||
DEV_FEE=$DEV_FEE
|
||||
IDENTITIES=$IDENTITIES
|
||||
WORKER=$WORKER
|
||||
VOTE=$VOTE
|
||||
PEERS=$PEERS
|
||||
EXTRA=$EXTRA
|
||||
CONF
|
||||
echo "Igneum: config written to $CUSTOM_CONFIG_FILENAME (node $url, wallet ${wallet:0:8}..., dev fee ${DEV_FEE}%, $IDENTITIES identities per card)"
|
||||
6
packaging/hive/h-manifest.conf
Normal file
6
packaging/hive/h-manifest.conf
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Igneum Miner for HiveOS: the custom-miner manifest Hive reads (packaging/hive, 4 October 2026).
|
||||
# CUSTOM_VERSION is stamped by make-hive-package.sh from the bundled binaries' version.
|
||||
CUSTOM_NAME=igneum
|
||||
CUSTOM_VERSION=0.0.0
|
||||
CUSTOM_CONFIG_FILENAME=/hive/miners/custom/$CUSTOM_NAME/$CUSTOM_NAME.conf
|
||||
CUSTOM_LOG_BASENAME=/var/log/miner/custom/$CUSTOM_NAME/$CUSTOM_NAME
|
||||
86
packaging/hive/h-run.sh
Executable file
86
packaging/hive/h-run.sh
Executable file
|
|
@ -0,0 +1,86 @@
|
|||
#!/usr/bin/env bash
|
||||
# Igneum Miner for HiveOS: starts the bundled node when asked, exports the hourly program pack, then one igneum-miner
|
||||
# per GPU with the matching worker (igneum-worker-cuda on NVIDIA, igneum-worker-opencl on AMD). Each miner mines
|
||||
# against the node for its own payout address; the software dev fee (1 template in 100) is the miner's default and
|
||||
# DEV_FEE=0 in the Flight Sheet's extra config turns it off. Per-GPU logs: $CUSTOM_LOG_BASENAME.gpu<N>.log; the main
|
||||
# log is their merged tail. Run by Hive with the working directory at the miner folder.
|
||||
cd "$(dirname "$0")" || exit 1
|
||||
HERE="$(pwd)"
|
||||
[[ -z "$CUSTOM_LOG_BASENAME" || -z "$CUSTOM_CONFIG_FILENAME" ]] && . "$HERE/h-manifest.conf"
|
||||
[[ -z "$CUSTOM_LOG_BASENAME" ]] && echo "No CUSTOM_LOG_BASENAME is set" && exit 1
|
||||
[[ -f "$CUSTOM_CONFIG_FILENAME" ]] || { echo "No config at $CUSTOM_CONFIG_FILENAME (apply the Flight Sheet first)"; exit 1; }
|
||||
. "$CUSTOM_CONFIG_FILENAME"
|
||||
BIN="${IGNEUM_BIN:-$HERE/bin}"
|
||||
mkdir -p "$(dirname "$CUSTOM_LOG_BASENAME")" "$HERE/packs" "$HERE/data"
|
||||
date +%s > "$CUSTOM_LOG_BASENAME.started"
|
||||
MAIN="$CUSTOM_LOG_BASENAME.log"
|
||||
: > "$MAIN"
|
||||
say() { printf '%s %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$*" | tee -a "$MAIN"; }
|
||||
echo $$ > "$CUSTOM_LOG_BASENAME.pid"
|
||||
pids=()
|
||||
cleanup() { say "stopping"; for p in "${pids[@]}"; do kill "$p" 2>/dev/null; done; pkill -TERM -P $$ 2>/dev/null; sleep 2; for p in "${pids[@]}"; do kill -9 "$p" 2>/dev/null; done; pkill -KILL -P $$ 2>/dev/null; exit 0; }
|
||||
trap cleanup INT TERM
|
||||
|
||||
# 1. the node
|
||||
if [[ "$NODE_URL" == "local" ]]; then
|
||||
NODE_URL="grpc://127.0.0.1:26610"
|
||||
peers=()
|
||||
if [[ -n "$PEERS" ]]; then IFS=',' read -r -a plist <<< "$PEERS"; for p in "${plist[@]}"; do peers+=("--addpeer=$p"); done
|
||||
else peers+=("--addpeer=188.245.5.161:26611"); fi # the public devnet seed (app/igneum-app/src/config.rs)
|
||||
say "starting the bundled node (devnet v4, data $HERE/data, peers ${peers[*]#--addpeer=})"
|
||||
"$BIN/igneumd" --devnet "--appdir=$HERE/data" --rpclisten=127.0.0.1:26610 --listen=0.0.0.0:26611 "${peers[@]}" \
|
||||
--nodnsseed --disable-upnp --nologfiles --yes >> "$CUSTOM_LOG_BASENAME.node.log" 2>&1 &
|
||||
pids+=($!)
|
||||
fi
|
||||
say "node $NODE_URL; waiting for it to answer"
|
||||
for _ in $(seq 1 60); do
|
||||
"$BIN/igneum-miner" watch 1 "$NODE_URL" >/dev/null 2>&1 && break
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# 2. the GPUs: Hive's gpu-detect when present, else the driver tools
|
||||
nv=0; amd=0
|
||||
if command -v gpu-detect >/dev/null 2>&1; then nv=$(gpu-detect NVIDIA 2>/dev/null || echo 0); amd=$(gpu-detect AMD 2>/dev/null || echo 0)
|
||||
else
|
||||
command -v nvidia-smi >/dev/null 2>&1 && nv=$(nvidia-smi -L 2>/dev/null | grep -c '^GPU')
|
||||
[[ -d /sys/bus/pci/drivers/amdgpu ]] && amd=$(ls /sys/bus/pci/drivers/amdgpu 2>/dev/null | grep -c '^0000:')
|
||||
fi
|
||||
nv=${nv:-0}; amd=${amd:-0}
|
||||
say "GPUs: $nv NVIDIA, $amd AMD (worker setting: $WORKER)"
|
||||
[[ "$nv" == "0" && "$amd" == "0" ]] && say "no GPU found; nothing to mine with" && sleep 30 && exit 1
|
||||
|
||||
# 3. the hourly program pack from the node (the workers read it with --pack; the miner writes the next one to packs/prepare)
|
||||
export_pack() { rm -rf "$HERE/packs/devnet"; "$BIN/igneum-miner" export-pack "$NODE_URL" "$HERE/packs/devnet" >> "$MAIN" 2>&1; }
|
||||
export_pack || say "pack export failed; the miners retry"
|
||||
|
||||
# 4. one miner per GPU, restarted on exit (exit 42 = the program changed and the worker cannot prepare: re-export the pack)
|
||||
run_gpu() {
|
||||
local idx="$1" worker="$2" dev="$3" tag="$4"
|
||||
local log="$CUSTOM_LOG_BASENAME.gpu$idx.log"
|
||||
local args=(mine "$NODE_URL" 1 100000000 "$LABEL-gpu$idx" --worker "$BIN/$worker" --worker-args "--device $dev --pack packs/devnet"
|
||||
--prepare-packs packs/prepare --exit-on-seed-change --evm-address "$WALLET" --payout-label "$LABEL-gpu$idx" --status-secs 30)
|
||||
[[ "$IDENTITIES" -gt 1 ]] && args+=(--identities "$IDENTITIES")
|
||||
[[ "$VOTE" == "0" ]] && args+=(--no-vote)
|
||||
[[ "$DEV_FEE" != "1" ]] && args+=(--dev-fee "$DEV_FEE")
|
||||
[[ -n "$EXTRA" ]] && args+=($EXTRA)
|
||||
while :; do
|
||||
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) gpu$idx ($tag $dev): igneum-miner ${args[*]}" >> "$log"
|
||||
"$BIN/igneum-miner" "${args[@]}" >> "$log" 2>&1
|
||||
rc=$?
|
||||
if [[ $rc == 42 ]]; then export_pack; else sleep 5; fi
|
||||
done
|
||||
}
|
||||
idx=0
|
||||
if [[ "$nv" -gt 0 && "$WORKER" != "opencl" ]]; then
|
||||
for ((d = 0; d < nv; d++)); do run_gpu "$idx" igneum-worker-cuda "$d" nvidia & pids+=($!); idx=$((idx + 1)); done
|
||||
fi
|
||||
if [[ "$amd" -gt 0 && "$WORKER" != "cuda" ]]; then
|
||||
for ((d = 0; d < amd; d++)); do run_gpu "$idx" igneum-worker-opencl "$d" amd & pids+=($!); idx=$((idx + 1)); done
|
||||
fi
|
||||
say "$idx miner(s) started; dev fee ${DEV_FEE}% (the miner prints its own dev fee line below)"
|
||||
|
||||
# 5. the main log: every GPU's lines, merged
|
||||
sleep 1
|
||||
tail -q -n +1 -F "$CUSTOM_LOG_BASENAME".gpu*.log 2>/dev/null | tee -a "$MAIN" &
|
||||
pids+=($!)
|
||||
wait
|
||||
39
packaging/hive/h-stats.sh
Executable file
39
packaging/hive/h-stats.sh
Executable file
|
|
@ -0,0 +1,39 @@
|
|||
#!/usr/bin/env bash
|
||||
# Igneum Miner for HiveOS: the stats Hive polls. Sourced by Hive (so: no exit); sets $khs (total kH/s) and $stats
|
||||
# (JSON: hs per GPU in kH/s, temp, fan, uptime, ver, ar = [accepted, rejected], algo, bus_numbers). The per-GPU numbers
|
||||
# come from the miners' own STATUS lines ($CUSTOM_LOG_BASENAME.gpu<N>.log, "now=<MH/s> MH/s"); accepted, rejected and
|
||||
# dev-fee blocks are the cumulative counters of the same line. Temperatures and fans come from Hive's gpu-stats when it
|
||||
# exists (its arrays follow Hive's GPU order; NVIDIA first here as on Hive), else nvidia-smi, else empty.
|
||||
[[ -z "$CUSTOM_LOG_BASENAME" ]] && . "$(dirname "${BASH_SOURCE[0]}")/h-manifest.conf"
|
||||
_hdir="$(dirname "${BASH_SOURCE[0]}")"
|
||||
_ver="$(head -1 "$_hdir/version.txt" 2>/dev/null | awk '{print $2}')"; _ver="${_ver:-$CUSTOM_VERSION}"
|
||||
_hs=(); _acc=0; _rej=0; _fee=0; _total_khs=0
|
||||
for _f in $(ls "$CUSTOM_LOG_BASENAME".gpu*.log 2>/dev/null | sort -t u -k 3 -n); do
|
||||
_line="$(grep ' STATUS ' "$_f" 2>/dev/null | tail -1)"
|
||||
_mh="$(printf '%s' "$_line" | sed -n 's/.* now=\([0-9.]*\) MH\/s.*/\1/p')"
|
||||
[[ -z "$_mh" ]] && _mh="$(printf '%s' "$_line" | sed -n 's/.* hash=\([0-9.]*\) MH\/s.*/\1/p')"
|
||||
[[ -z "$_mh" ]] && _mh=0
|
||||
_k="$(awk -v m="$_mh" 'BEGIN{printf "%.1f", m*1000}')"
|
||||
_hs+=("$_k")
|
||||
_total_khs="$(awk -v a="$_total_khs" -v b="$_k" 'BEGIN{printf "%.1f", a+b}')"
|
||||
_a="$(printf '%s' "$_line" | sed -n 's/.* accepted=\([0-9]*\).*/\1/p')"; _acc=$((_acc + ${_a:-0}))
|
||||
_r="$(printf '%s' "$_line" | sed -n 's/.* rejected=\([0-9]*\).*/\1/p')"; _rej=$((_rej + ${_r:-0}))
|
||||
_d="$(printf '%s' "$_line" | sed -n 's/.* fee=\([0-9]*\).*/\1/p')"; _fee=$((_fee + ${_d:-0}))
|
||||
done
|
||||
_n=${#_hs[@]}
|
||||
_temp="[]"; _fan="[]"; _bus="[]"
|
||||
if command -v gpu-stats >/dev/null 2>&1 && command -v jq >/dev/null 2>&1; then
|
||||
_gs="$(gpu-stats 2>/dev/null)"
|
||||
_temp="$(jq -c ".temp[0:$_n] // []" <<< "$_gs" 2>/dev/null || echo '[]')"
|
||||
_fan="$(jq -c ".fan[0:$_n] // []" <<< "$_gs" 2>/dev/null || echo '[]')"
|
||||
_bus="[$(jq -r ".busids[0:$_n][]? | split(\":\")[0]" <<< "$_gs" 2>/dev/null | while read -r _b; do printf '%d\n' "0x$_b" 2>/dev/null; done | paste -sd, -)]"
|
||||
elif command -v nvidia-smi >/dev/null 2>&1; then
|
||||
_temp="[$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits 2>/dev/null | head -n "$_n" | paste -sd, -)]"
|
||||
_fan="[$(nvidia-smi --query-gpu=fan.speed --format=csv,noheader,nounits 2>/dev/null | head -n "$_n" | sed 's/\[N\/A\]/0/' | paste -sd, -)]"
|
||||
_bus="[$(nvidia-smi --query-gpu=pci.bus_id --format=csv,noheader 2>/dev/null | head -n "$_n" | awk -F: '{printf "%d\n", strtonum("0x"$2)}' | paste -sd, -)]"
|
||||
fi
|
||||
_started="$(cat "$CUSTOM_LOG_BASENAME.started" 2>/dev/null)"; _up=0
|
||||
[[ -n "$_started" ]] && _up=$(( $(date +%s) - _started ))
|
||||
_hs_json="[$(IFS=,; printf '%s' "${_hs[*]}")]"
|
||||
khs="$_total_khs"
|
||||
stats="{\"hs\":$_hs_json,\"hs_units\":\"khs\",\"temp\":$_temp,\"fan\":$_fan,\"uptime\":$_up,\"ver\":\"$_ver\",\"ar\":[$_acc,$_rej],\"algo\":\"igneum\",\"bus_numbers\":$_bus,\"dev_fee_blocks\":$_fee}"
|
||||
54
packaging/hive/make-hive-package.sh
Executable file
54
packaging/hive/make-hive-package.sh
Executable file
|
|
@ -0,0 +1,54 @@
|
|||
#!/usr/bin/env bash
|
||||
# Builds the HiveOS custom-miner archive igneum-hive-<version>.tar.gz from the Linux binaries and the h-* scripts here.
|
||||
# packaging/hive/make-hive-package.sh binaries from infra/cross/out-v2 (igneumd, igneum-miner, version.txt)
|
||||
# and infra/cross/out-workers (the two GPU workers, build-workers-linux.sh)
|
||||
# NODE_OUT=... WORKERS_OUT=... VERSION=... OUT=... other inputs; VERSION defaults to the igneumd version in version.txt
|
||||
# --fake stub binaries instead (the self-test; never ship it)
|
||||
# Output: packaging/hive/build/igneum-hive-<version>.tar.gz with the directory igneum/ inside (what Hive expects:
|
||||
# the archive name carries the version, the directory does not), plus its sha256 and the Flight Sheet lines.
|
||||
set -euo pipefail
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO="$(cd "$HERE/../.." && pwd)"
|
||||
NODE_OUT="${NODE_OUT:-$REPO/infra/cross/out-v2}"
|
||||
WORKERS_OUT="${WORKERS_OUT:-$REPO/infra/cross/out-workers}"
|
||||
OUT="${OUT:-$HERE/build}"
|
||||
FAKE=0; [[ "${1:-}" == "--fake" ]] && FAKE=1
|
||||
log() { printf '%s %s\n' "$(date -u +%H:%M:%S)" "$*"; }
|
||||
die() { log "ERROR: $*" >&2; exit 1; }
|
||||
stage="$OUT/igneum"; rm -rf "$stage"; mkdir -p "$stage/bin"
|
||||
if [[ $FAKE == 1 ]]; then
|
||||
VERSION="${VERSION:-0.0.0-fake}"
|
||||
for b in igneumd igneum-miner igneum-worker-cuda igneum-worker-opencl; do printf '#!/bin/sh\necho fake %s "$@"\n' "$b" > "$stage/bin/$b"; chmod +x "$stage/bin/$b"; done
|
||||
printf 'igneumd %s\nfake binaries for the self-test\n' "$VERSION" > "$stage/version.txt"
|
||||
else
|
||||
[[ -x "$NODE_OUT/igneumd" && -x "$NODE_OUT/igneum-miner" ]] || die "no Linux igneumd and igneum-miner in $NODE_OUT (infra/cross/build-linux.sh)"
|
||||
for b in igneumd igneum-miner; do file "$NODE_OUT/$b" | grep -q 'ELF 64-bit.*x86-64' || die "$NODE_OUT/$b is not a Linux x86_64 binary"; done
|
||||
cp "$NODE_OUT/igneumd" "$NODE_OUT/igneum-miner" "$stage/bin/"
|
||||
cp "$NODE_OUT/version.txt" "$stage/version.txt"
|
||||
missing=()
|
||||
for w in igneum-worker-cuda igneum-worker-opencl; do
|
||||
if [[ -x "$WORKERS_OUT/$w" ]]; then cp "$WORKERS_OUT/$w" "$stage/bin/"; else missing+=("$w"); fi
|
||||
done
|
||||
[[ ${#missing[@]} -gt 0 ]] && log "WARNING: no Linux ${missing[*]} in $WORKERS_OUT (infra/cross/build-workers-linux.sh); the package mines nothing on that vendor"
|
||||
[[ -f "$WORKERS_OUT/version.txt" ]] && cat "$WORKERS_OUT/version.txt" >> "$stage/version.txt"
|
||||
VERSION="${VERSION:-$(head -1 "$NODE_OUT/version.txt" | awk '{print $2}')}"
|
||||
fi
|
||||
[[ -n "$VERSION" ]] || die "no version (VERSION=... or a version.txt with 'igneumd <version>')"
|
||||
cp "$HERE/h-config.sh" "$HERE/h-run.sh" "$HERE/h-stats.sh" "$HERE/README.md" "$stage/"
|
||||
sed "s/^CUSTOM_VERSION=.*/CUSTOM_VERSION=$VERSION/" "$HERE/h-manifest.conf" > "$stage/h-manifest.conf"
|
||||
chmod +x "$stage"/h-*.sh "$stage"/bin/*
|
||||
for f in "$stage"/h-*.sh; do bash -n "$f"; done
|
||||
tgz="$OUT/igneum-hive-$VERSION.tar.gz"
|
||||
tar -C "$OUT" -czf "$tgz" igneum
|
||||
rm -rf "$stage"
|
||||
sum="$(shasum -a 256 "$tgz" 2>/dev/null | awk '{print $1}' || sha256sum "$tgz" | awk '{print $1}')"
|
||||
log "wrote $tgz ($(du -h "$tgz" | cut -f1), sha256 $sum)"
|
||||
cat <<TXT
|
||||
Flight Sheet (HiveOS): Miner = Custom, then Setup Miner Config:
|
||||
Installation URL https://<your host>/igneum-hive-$VERSION.tar.gz (publish the archive on the download host)
|
||||
Miner name igneum
|
||||
Wallet and worker 0x<your 40-hex payout address>.%WORKER_NAME%
|
||||
Pool URL grpc://<your node>:26610 or local (the bundled node on the rig)
|
||||
Pass (empty)
|
||||
Extra config DEV_FEE=1 IDENTITIES=8 WORKER=auto VOTE=1 (one setting per line also works)
|
||||
TXT
|
||||
64
packaging/hive/selftest.sh
Executable file
64
packaging/hive/selftest.sh
Executable file
|
|
@ -0,0 +1,64 @@
|
|||
#!/usr/bin/env bash
|
||||
# Self-test of the HiveOS package on a machine without Hive (the Mac): bash -n on every script, a package built from
|
||||
# stub binaries, then the three hooks run the way Hive runs them (h-config.sh sourced with the Flight Sheet variables,
|
||||
# h-run.sh with fake GPUs and a fake miner that prints the real miner's lines, h-stats.sh sourced and its JSON parsed).
|
||||
# This proves the scripts' logic and the stats shape, not Hive itself: nothing here talks to a GPU or to Hive's agent.
|
||||
set -euo pipefail
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
T="$(mktemp -d)"
|
||||
cleanup() {
|
||||
_pid="$(cat "$T/log/igneum.pid" 2>/dev/null)"; [[ -n "$_pid" ]] && kill -TERM "$_pid" 2>/dev/null; sleep 2
|
||||
pkill -KILL -f "$T/" 2>/dev/null || true
|
||||
[[ -n "${KEEP:-}" ]] && echo "kept $T" || rm -rf "$T"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
echo "== bash -n"
|
||||
for f in "$HERE"/h-*.sh "$HERE"/make-hive-package.sh; do bash -n "$f" && echo " $(basename "$f") ok"; done
|
||||
echo "== package from stub binaries"
|
||||
OUT="$T/build" VERSION=0.0.0-test "$HERE/make-hive-package.sh" --fake >/dev/null
|
||||
tar -tzf "$T/build/igneum-hive-0.0.0-test.tar.gz" | sed 's/^/ /'
|
||||
mkdir -p "$T/hive/miners/custom" "$T/log"
|
||||
tar -C "$T/hive/miners/custom" -xzf "$T/build/igneum-hive-0.0.0-test.tar.gz"
|
||||
M="$T/hive/miners/custom/igneum"
|
||||
# the fake miner: records its argv, prints the real miner's start and STATUS lines, exits 42 once (the pack path)
|
||||
cat > "$M/bin/igneum-miner" <<'FAKE'
|
||||
#!/usr/bin/env bash
|
||||
case "$1" in
|
||||
watch) exit 0 ;;
|
||||
export-pack) mkdir -p "$3"; echo "fake pack in $3"; exit 0 ;;
|
||||
mine)
|
||||
echo "$@" >> "$(dirname "$0")/../argv.log"
|
||||
fee=1; for ((i=1;i<=$#;i++)); do [[ "${!i}" == "--dev-fee" ]] && j=$((i+1)) && fee="${!j}"; done
|
||||
if [[ "$fee" == "0" ]]; then echo "t dev fee off (--dev-fee 0); the default is 1% (1 block in 100) to 0x1111111111111111111111111111111111111111"
|
||||
else echo "t dev fee ${fee}% (1 block in 100) to 0x1111111111111111111111111111111111111111; --dev-fee 0 turns it off"; fi
|
||||
n=0; while :; do n=$((n+1)); echo "t STATUS 'x' [worker]: ${n}0s jobs=$n accepted=$((n*2)) rejected=0 fee=$((n/50)) mismatched=0 extra=0 rate=0.10 blocks/s hash=120.00 MH/s wall (121.00 MH/s inside jobs) now=118.5 MH/s wall (119.0 MH/s inside jobs, 10 jobs, seed walk 0 calls) template_age=0.50s synced=true idle=1.0% (last 10s: 1.0%) queued=2 restarts=0 faults=0 identities=8 accepted_by_identity=1/1"; sleep 1; [[ $n == 2 && ! -f "$(dirname "$0")/../exited42" ]] && touch "$(dirname "$0")/../exited42" && exit 42; done ;;
|
||||
esac
|
||||
FAKE
|
||||
chmod +x "$M/bin/igneum-miner"
|
||||
mkdir -p "$T/bin"
|
||||
printf '#!/bin/sh\n[ "$1" = NVIDIA ] && echo 2 || echo 0\n' > "$T/bin/gpu-detect"
|
||||
cat > "$T/bin/gpu-stats" <<'GS'
|
||||
#!/bin/sh
|
||||
echo '{"temp":[61,58],"fan":[55,50],"busids":["01:00.0","02:00.0"]}'
|
||||
GS
|
||||
chmod +x "$T/bin/gpu-detect" "$T/bin/gpu-stats"
|
||||
export PATH="$T/bin:$PATH"
|
||||
echo "== h-config.sh (sourced, Flight Sheet variables)"
|
||||
export CUSTOM_URL="local" CUSTOM_TEMPLATE="0xAbCd000000000000000000000000000000000001.rig7" CUSTOM_PASS="" CUSTOM_USER_CONFIG=$'DEV_FEE=0\nIDENTITIES=4' WORKER_NAME=rig7
|
||||
export CUSTOM_CONFIG_FILENAME="$M/igneum.conf" CUSTOM_LOG_BASENAME="$T/log/igneum"
|
||||
( . "$M/h-config.sh" ) || { echo "h-config.sh failed"; exit 1; }
|
||||
grep -q '^WALLET=0xabcd000000000000000000000000000000000001$' "$M/igneum.conf" && grep -q '^DEV_FEE=0$' "$M/igneum.conf" && grep -q '^LABEL=rig7$' "$M/igneum.conf" && grep -q '^IDENTITIES=4$' "$M/igneum.conf" && echo " conf ok: $(tr '\n' ' ' < "$M/igneum.conf" | cut -c1-160)"
|
||||
( CUSTOM_TEMPLATE="notanaddress" . "$M/h-config.sh" >/dev/null 2>&1 ) && { echo "h-config.sh accepted a bad wallet"; exit 1; } || echo " bad wallet refused ok"
|
||||
echo "== h-run.sh (fake GPUs: 2 NVIDIA, fake node, fake miner)"
|
||||
( cd "$M" && CUSTOM_CONFIG_FILENAME="$M/igneum.conf" CUSTOM_LOG_BASENAME="$T/log/igneum" ./h-run.sh > "$T/log/run.out" 2>&1 ) &
|
||||
disown
|
||||
sleep 8
|
||||
ls "$T/log" | sed 's/^/ /'
|
||||
_lines=$(grep -c "dev fee off (--dev-fee 0)" "$T/log/igneum.log" || true); echo " dev fee lines in the main log: $_lines"; [[ "$_lines" -ge 2 ]] || { echo "FAIL: expected the two miners' dev fee lines in the main log"; cat "$T/log/run.out"; exit 1; }
|
||||
grep -q -- "--dev-fee 0" "$M/argv.log" && echo " DEV_FEE=0 reached the miner as --dev-fee 0 ok" || { echo "FAIL: --dev-fee 0 missing"; exit 1; }
|
||||
grep -q -- "--identities 4" "$M/argv.log" && echo " IDENTITIES=4 reached the miner ok" || { echo "FAIL: --identities 4 missing"; exit 1; }
|
||||
grep -q "igneum-worker-cuda" "$M/argv.log" && echo " NVIDIA cards got the cuda worker ok" || { echo "FAIL: cuda worker missing"; exit 1; }
|
||||
[[ -f "$M/exited42" && $(grep -c "^mine" "$M/argv.log") -ge 3 ]] && echo " exit 42 restarted the miner and re-exported the pack ok" || { echo "FAIL: no restart after exit 42"; exit 1; }
|
||||
echo "== h-stats.sh (sourced)"
|
||||
( . "$M/h-stats.sh"; echo " khs=$khs"; echo " stats=$stats"; python3 -c "import json,sys; s=json.loads(sys.argv[1]); assert s['hs_units']=='khs' and len(s['hs'])==2 and s['ar'][0]>0 and s['algo']=='igneum' and len(s['temp'])==2, s; print(' stats JSON ok: hs', s['hs'], 'temp', s['temp'], 'ar', s['ar'], 'bus', s['bus_numbers'])" "$stats" )
|
||||
echo "== self-test passed (scripts and stats shape; Hive itself is untested)"
|
||||
|
|
@ -11,6 +11,8 @@
|
|||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
|
||||
|
|
@ -42,8 +44,18 @@ static int ig_cl_load(void) {
|
|||
#undef IG_CL_LOAD
|
||||
return 1;
|
||||
#else
|
||||
snprintf(ig_cl_error, sizeof(ig_cl_error), "IGNEUM_CL_DYNAMIC is for the Windows build");
|
||||
return 0;
|
||||
/* Linux (the HiveOS package, 4 October 2026): the ICD loader the GPU driver installs (ROCm, amdgpu-pro, NVIDIA,
|
||||
* Intel all register with it), opened at run time so the worker cross-compiles on the Mac with no OpenCL library. */
|
||||
void* h = dlopen("libOpenCL.so.1", RTLD_NOW);
|
||||
if (!h) h = dlopen("libOpenCL.so", RTLD_NOW);
|
||||
if (!h) {
|
||||
snprintf(ig_cl_error, sizeof(ig_cl_error), "libOpenCL.so.1 is not installed (the GPU driver provides it: ROCm or amdgpu-pro for AMD, the NVIDIA or Intel driver); nothing else is needed");
|
||||
return 0;
|
||||
}
|
||||
#define IG_CL_LOAD(f) ig_##f = (__typeof__(f)*)dlsym(h, #f); if (!ig_##f) { snprintf(ig_cl_error, sizeof(ig_cl_error), "libOpenCL.so.1 lacks %s", #f); return 0; }
|
||||
IG_CL_FUNCS(IG_CL_LOAD)
|
||||
#undef IG_CL_LOAD
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -413,7 +413,7 @@ footer .wrap{padding-block:48px 32px}
|
|||
<a href="#journey" class="btn dark"><svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" aria-hidden="true"><path d="M16.4 12.6c0-2.3 1.9-3.4 2-3.5-1.1-1.6-2.8-1.8-3.4-1.8-1.4-.1-2.8.8-3.5.8-.7 0-1.8-.8-3-.8-1.5 0-3 .9-3.8 2.3-1.6 2.8-.4 7 1.2 9.3.8 1.1 1.7 2.4 2.9 2.3 1.2 0 1.6-.7 3-.7s1.8.7 3 .7c1.3 0 2-1.1 2.8-2.3.9-1.3 1.2-2.6 1.3-2.6-.1 0-2.5-.9-2.5-3.7zM14.1 5.8c.6-.8 1.1-1.9.9-3-.9 0-2 .6-2.7 1.4-.6.7-1.1 1.8-1 2.9 1.1.1 2.1-.5 2.8-1.3z"/></svg>macOS</a>
|
||||
<a href="#journey" class="btn dark"><svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" aria-hidden="true"><path d="M12 2c-2.4 0-4 1.9-4 4.6 0 1.2.2 2 0 2.8-.6 1.4-2.1 2.9-2.6 4.9-.3 1.1-.1 2 .3 2.6-.6.4-1.3 1-1.1 1.7.3 1 2.1 1.2 3.2 1.8.7.4 1.5.6 2.1.1.6.2 1.3.3 2.1.3s1.5-.1 2.1-.3c.6.5 1.4.3 2.1-.1 1.1-.6 2.9-.8 3.2-1.8.2-.7-.5-1.3-1.1-1.7.4-.6.6-1.5.3-2.6-.5-2-2-3.5-2.6-4.9-.2-.8 0-1.6 0-2.8C16 3.9 14.4 2 12 2zm-1.4 4.2c.5 0 .8.5.8 1.2s-.3 1.2-.8 1.2-.8-.5-.8-1.2.3-1.2.8-1.2zm2.8 0c.5 0 .8.5.8 1.2s-.3 1.2-.8 1.2-.8-.5-.8-1.2.3-1.2.8-1.2zM12 9.3c.9 0 1.9.5 1.9 1s-1 1.2-1.9 1.2-1.9-.7-1.9-1.2 1-1 1.9-1zm0 3.4c2.2 0 3.6 2.6 3.6 4.4 0 1.5-1.6 2.3-3.6 2.3s-3.6-.8-3.6-2.3c0-1.8 1.4-4.4 3.6-4.4z"/></svg>Linux</a>
|
||||
<a href="#journey" class="btn" style="color:#0C0C0E;border-color:#0C0C0E"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round" aria-hidden="true"><path d="M12 2.5l8.2 4.75v9.5L12 21.5l-8.2-4.75v-9.5z"/><path d="M12 7.5l4.3 2.5v5L12 17.5l-4.3-2.5v-5z"/></svg>HiveOS</a>
|
||||
<p style="font-size:14px;color:#55534F">One click: install, press start, the card mines and proves to a wallet it makes for you. Public testnet first.<br>Download only from this domain. Nobody from Igneum will ask for your seed.</p>
|
||||
<p style="font-size:14px;color:#55534F">One click: install, press start, the card mines and proves to a wallet it makes for you. Public testnet first.<br>The protocol carries no fee. The Igneum Miner software takes an optional 1% dev fee, like other GPU miners, off with one flag.<br>Download only from this domain. Nobody from Igneum will ask for your seed.</p>
|
||||
</div>
|
||||
<a href="/litepaper#miners" class="lp reveal">Read more in the litepaper</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -419,7 +419,8 @@ body.all .pager{display:none}
|
|||
<h3>Hardware</h3>
|
||||
<p>The dataset starts at 2 GB and grows by half a gigabyte a year, so a 4 GB card mines for about four years and an 8 GB card for more than a decade, approximate. 12 GB or more proves full shards. NVIDIA and AMD both work, because the mining program is generated for the architecture both share and the proof system is hash-based. Apple's chips are GPUs with unified memory, so Macs mine too. There is no CPU mining lane, on purpose, because CPU mining is what botnets farm. Nodes, wallets and exchanges need no GPU at all.</p>
|
||||
<h3>What a miner's hour looks like</h3>
|
||||
<p>The card hashes the lottery continuously. When the client sees a shard or an external job it can win, it switches the card to proving for a few seconds, posts the proof, and goes back to hashing. The client does the switching and the miner sees one balance. The official client is open source and charges a 1% dev fee, like every miner you already run, and any other client is welcome.</p>
|
||||
<p>The card hashes the lottery continuously. When the client sees a shard or an external job it can win, it switches the card to proving for a few seconds, posts the proof, and goes back to hashing. The client does the switching and the miner sees one balance.</p>
|
||||
<p>The protocol carries no fee: no dev fund, no cut to any team. The Igneum Miner software takes an optional 1% dev fee, the way other GPU miners do. One block template in 100 is requested with the dev address instead of yours, by a counter, not a random draw, so it is exactly 1 in 100 and anyone can check it from the source or from the chain. One flag turns it off (<code>--dev-fee 0</code>, a switch in the app, a line in the HiveOS config). The miner prints the fee and the address when it starts. Any other client is welcome.</p>
|
||||
<h3>One click, for everyone else</h3>
|
||||
<p>Farm operators get HiveOS support on day one. Everyone else gets the Igneum app: install it on Windows, macOS or Linux, press one button, and the card is mining and proving to a wallet the app made for you, with earnings shown in IGN and in your currency, and mining paused while you game. It is the same client with a face on it. The app shows you the seed phrase and has you confirm it before mining starts, offers a hardware wallet for your earnings, updates only when you accept a release signed by the key published in the genesis block, and is downloaded only from this domain or the repository with its hash shown beside the button. Nobody from Igneum will ever ask for your seed. Mining never runs in a browser, because browser compute is slow and browser mining has meant malware since Coinhive. The browser is for the dashboard, and for verifying the chain.</p>
|
||||
<h3>Fair launch, announced</h3>
|
||||
|
|
|
|||
109
tools/dev-fee/run.mjs
Executable file
109
tools/dev-fee/run.mjs
Executable file
|
|
@ -0,0 +1,109 @@
|
|||
#!/usr/bin/env node
|
||||
// The software dev fee measured on a private test network (4 October 2026): two nodes on 127.0.0.1 ports 29900 and
|
||||
// up (network id igneum-devnet-957, data under /tmp/igneum-dev-fee), the fast-time profile with proof of work skipped
|
||||
// and the genesis difficulty at the floor, so a CPU stub miner solves every template it holds. Three CPU miners pace
|
||||
// themselves with --hold-ms: two at the default --dev-fee 1 and one control at --dev-fee 0. At the end the chain is
|
||||
// read back with `igneum-miner payouts` on the SECOND node (what propagated, not what the miner's node accepted) and
|
||||
// the dev address's block count is set against the miners' own fee= counters and the 1-in-100 expectation.
|
||||
// Never touches the live devnet (26610/26611), the fast-time simnet (29500+) or the proving-v0 network (29800+).
|
||||
//
|
||||
// node tools/dev-fee/run.mjs [--secs 600] [--hold-ms 500]
|
||||
// IGNEUMD=<igneumd of the devnet-v4 line> IGNEUM_MINER=<igneum-miner with --dev-fee> (defaults below)
|
||||
|
||||
import { spawn, spawnSync } from 'node:child_process';
|
||||
import { mkdirSync, rmSync, writeFileSync, readFileSync, openSync, existsSync } from 'node:fs';
|
||||
|
||||
const ROOT = new URL('../../', import.meta.url).pathname;
|
||||
const FILE = `${ROOT}infra/fast-time/override-60x.json`;
|
||||
const IGNEUMD = process.env.IGNEUMD || `${ROOT}vendor/igneum-node-v4/target/release/igneumd`;
|
||||
const MINER = process.env.IGNEUM_MINER || `${ROOT}vendor/igneum-node-devfee/target/release/igneum-miner`;
|
||||
const TMP = '/tmp/igneum-dev-fee';
|
||||
const BASE = 29900, SUFFIX = 957;
|
||||
const args = process.argv.slice(2);
|
||||
const flag = (name, dflt) => { const i = args.indexOf(name); return i >= 0 && args[i + 1] !== undefined ? +args[i + 1] : dflt; };
|
||||
const SECS = flag('--secs', 600);
|
||||
const HOLD = flag('--hold-ms', 500);
|
||||
const started = [];
|
||||
const log = (...a) => console.log(new Date().toISOString().slice(11, 23), ...a);
|
||||
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
||||
for (const b of [IGNEUMD, MINER]) if (!existsSync(b)) { console.error(`missing ${b}`); process.exit(2); }
|
||||
|
||||
rmSync(TMP, { recursive: true, force: true }); mkdirSync(TMP, { recursive: true });
|
||||
const override = `${TMP}/override.json`;
|
||||
const params = JSON.parse(readFileSync(FILE, 'utf8'));
|
||||
params.skip_proof_of_work = true; // the node accepts any nonce; the stub miner still checks its own kHeavyHash
|
||||
params.genesis_bits = 0x207fffff; // target at the floor: the first nonce of every template passes the miner's check
|
||||
writeFileSync(override, JSON.stringify(params));
|
||||
|
||||
// the user addresses: fixed test values, no keys behind them (the fee address is the miner's DEV_FEE_ADDRESS_DEVNET)
|
||||
const USER_A = '0x' + 'a1'.repeat(20), USER_B = '0x' + 'b2'.repeat(20), USER_C = '0x' + 'c3'.repeat(20);
|
||||
|
||||
class Node {
|
||||
constructor(i, connect = []) {
|
||||
this.i = i; this.grpcPort = BASE + i * 10; this.p2pPort = BASE + i * 10 + 1; this.evmPort = BASE + i * 10 + 2;
|
||||
this.connect = connect; this.dir = `${TMP}/n${i}`; this.logFile = `${this.dir}/node.log`;
|
||||
}
|
||||
get grpc() { return `grpc://127.0.0.1:${this.grpcPort}`; }
|
||||
async start() {
|
||||
mkdirSync(this.dir, { recursive: true });
|
||||
const a = ['--devnet', `--devnet-suffix=${SUFFIX}`, '--nodnsseed', '--disable-upnp', '--nologfiles', '--enable-unsynced-mining', '--utxoindex', '--unsaferpc',
|
||||
`--appdir=${this.dir}`, `--rpclisten=127.0.0.1:${this.grpcPort}`, `--evm-rpclisten=127.0.0.1:${this.evmPort}`,
|
||||
`--listen=127.0.0.1:${this.p2pPort}`, `--override-params-file=${override}`, '--loglevel=info', '--yes'];
|
||||
if (this.connect.length) a.push(`--connect=${this.connect.join(',')}`); else a.push('--outpeers=0');
|
||||
const out = openSync(this.logFile, 'a');
|
||||
this.proc = spawn(IGNEUMD, a, { stdio: ['ignore', out, out] });
|
||||
started.push(this.proc);
|
||||
await sleep(1500);
|
||||
log(`n${this.i} up pid ${this.proc.pid} grpc ${this.grpcPort} p2p ${this.p2pPort}`);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
function miner(argv, name) {
|
||||
const out = openSync(`${TMP}/${name}.log`, 'a');
|
||||
const p = spawn(MINER, argv, { stdio: ['ignore', out, out] });
|
||||
started.push(p);
|
||||
return p;
|
||||
}
|
||||
async function stopAll() {
|
||||
for (const p of started.slice().reverse()) { try { p.kill('SIGINT'); } catch { } }
|
||||
await sleep(1500);
|
||||
for (const p of started) { try { p.kill('SIGKILL'); } catch { } }
|
||||
}
|
||||
process.on('SIGINT', async () => { await stopAll(); process.exit(130); });
|
||||
const tailOf = (f, re) => { try { return readFileSync(f, 'utf8').split('\n').filter(l => re.test(l)); } catch { return []; } };
|
||||
|
||||
const t0 = Date.now();
|
||||
const n0 = await new Node(0).start();
|
||||
const n1 = await new Node(1, [`127.0.0.1:${n0.p2pPort}`]).start();
|
||||
await sleep(2000);
|
||||
log(`miners: a and b at --dev-fee 1 (default), c at --dev-fee 0, --engine stub 1 thread --hold-ms ${HOLD}, ${SECS} s`);
|
||||
const common = (label, evm) => ['mine', n0.grpc, '1', String(SECS), label, '--engine', 'stub', '--hold-ms', String(HOLD), '--no-vote', '--status-secs', '30', '--payout-label', label, '--evm-address', evm];
|
||||
const pa = miner([...common('devfee-a', USER_A)], 'miner-a');
|
||||
const pb = miner([...common('devfee-b', USER_B), '--dev-fee', '1'], 'miner-b');
|
||||
const pc = miner([...common('devfee-c', USER_C), '--dev-fee', '0'], 'miner-c');
|
||||
await sleep(3000);
|
||||
for (const n of ['a', 'b', 'c']) log(`miner-${n}: ${tailOf(`${TMP}/miner-${n}.log`, / dev fee /).map(l => l.replace(/^\S+ /, ''))[0] || '(no dev fee line yet)'}`);
|
||||
await new Promise(r => { let left = 3; for (const p of [pa, pb, pc]) p.on('exit', () => { if (--left === 0) r(); }); });
|
||||
log(`miners done after ${((Date.now() - t0) / 1000).toFixed(0)} s`);
|
||||
await sleep(3000);
|
||||
|
||||
const summary = {};
|
||||
for (const n of ['a', 'b', 'c']) {
|
||||
const s = tailOf(`${TMP}/miner-${n}.log`, /^MINER SUMMARY/)[0] || '';
|
||||
const kv = (k) => +(s.match(new RegExp(` ${k}=(\\d+)`)) || [0, 0])[1];
|
||||
const feeLines = tailOf(`${TMP}/miner-${n}.log`, / dev-fee block /).length;
|
||||
summary[n] = { found: kv('found'), rejected: kv('rejected'), fee: kv('fee'), fee_lines: feeLines };
|
||||
log(`miner-${n}: found=${summary[n].found} rejected=${summary[n].rejected} fee=${summary[n].fee} (dev-fee block lines: ${feeLines})`);
|
||||
}
|
||||
for (const [name, n] of [['node 0 (the miners\' node)', n0], ['node 1 (peer)', n1]]) {
|
||||
const r = spawnSync(MINER, ['payouts', n.grpc], { encoding: 'utf8', timeout: 120000 });
|
||||
log(`payouts on ${name}:\n${(r.stdout || '').trim()}${r.stderr ? '\n' + r.stderr.trim() : ''}`);
|
||||
writeFileSync(`${TMP}/payouts-n${n.i}.txt`, r.stdout || '');
|
||||
}
|
||||
const payouts = readFileSync(`${TMP}/payouts-n1.txt`, 'utf8');
|
||||
const total = +(payouts.match(/blocks=(\d+)/) || [0, 0])[1];
|
||||
const dev = payouts.split('\n').find(l => /\(dev fee\)/.test(l)) || '';
|
||||
const devBlocks = +(dev.match(/blocks=(\d+)/) || [0, 0])[1];
|
||||
const feePaying = summary.a.found + summary.b.found;
|
||||
log(`RESULT total_blocks_on_node1=${total} dev_fee_blocks=${devBlocks} miners_fee_counters=${summary.a.fee + summary.b.fee} (control c: ${summary.c.fee}) share_of_fee_paying_miners_blocks=${feePaying ? (100 * devBlocks / feePaying).toFixed(3) : 'n/a'}% expected=1.000%`);
|
||||
await stopAll();
|
||||
Loading…
Reference in a new issue