Skip to content

Commit 7bb3f19

Browse files
committed
add rust upow solver
1 parent a084648 commit 7bb3f19

6 files changed

Lines changed: 431 additions & 11 deletions

File tree

ex/config/runtime.exs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ config :ama, :pruner_enabled, not archival_node and history_keep_epochs > 0
8787
config :ama, :statepeerdownload, System.get_env("STATEPEERDOWNLOAD") in ["true", "y", "yes"]
8888
config :ama, :autoupdate, System.get_env("AUTOUPDATE") in ["true", "y", "yes"]
8989
config :ama, :computor_type, (case System.get_env("COMPUTOR") do nil -> nil; "trainer" -> :trainer; _ -> :default end)
90+
computor_upow_threads_default = max(1, div(:erlang.system_info(:schedulers_online), 2) - 2)
91+
config :ama, :computor_upow_threads, (case System.get_env("COMPUTOR_UPOW_THREADS") do nil -> computor_upow_threads_default; v -> :erlang.binary_to_integer(v) end)
9092

9193
config :ama, :max_peers, (System.get_env("MAX_PEERS") || "300") |> :erlang.binary_to_integer()
9294
config :ama, :buy_peer_sol, System.get_env("BUY_PEER_SOL") in ["true", "y", "yes"]

ex/lib/native/rdb.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ defmodule RDB do
5353

5454
def freivalds(_tensor, _vr), do: :erlang.nif_error(:nif_not_loaded)
5555

56+
def compute_upow(_epoch, _segment_vr_hash, _trainer, _pop, _computor, _diff_bits, _iterations, _threads), do: :erlang.nif_error(:nif_not_loaded)
57+
5658
def bintree_root(_propslist), do: :erlang.nif_error(:nif_not_loaded)
5759
def bintree_root_prove(_propslist, _ns, _key), do: :erlang.nif_error(:nif_not_loaded)
5860
def bintree_root_verify(_expected_root, _proof, _ns, _key, _value), do: :erlang.nif_error(:nif_not_loaded)

ex/lib/node/computor_gen.ex

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
defmodule ComputorGen do
22
use GenServer
33

4+
@batch_iterations 2000
5+
46
def start(type \\ nil) do
57
send(__MODULE__, {:start, type})
68
end
@@ -24,22 +26,22 @@ defmodule ComputorGen do
2426
end
2527

2628
def handle_info(:tick, state) do
27-
state = cond do
28-
!state[:enabled] -> state
29-
!Application.fetch_env!(:ama, :testnet) ->
30-
IO.puts "Computor currently cannot find sols on mainnet due to difficulty. Do not waste CPU running it."
31-
state
29+
next_ms = cond do
30+
!state[:enabled] -> 1000
3231
!FabricSyncAttestGen.isQuorumIsInEpoch() ->
3332
IO.puts "🔴 cannot compute: out_of_sync"
34-
state
33+
1000
3534
true ->
3635
tick(state)
36+
0
3737
end
38-
:erlang.send_after(1000, self(), :tick)
38+
:erlang.send_after(next_ms, self(), :tick)
3939
{:noreply, state}
4040
end
4141

4242
def handle_info({:start, type}, state) do
43+
threads = Application.get_env(:ama, :computor_upow_threads, 0)
44+
IO.puts "🔢 computor enabled (type=#{inspect type}, upow_threads=#{if threads == 0, do: "auto", else: threads})"
4345
state = Map.put(state, :enabled, true)
4446
state = Map.put(state, :type, type)
4547
{:noreply, state}
@@ -53,23 +55,28 @@ defmodule ComputorGen do
5355
def handle_info(_msg, state), do: {:noreply, state}
5456

5557
def tick(state) do
56-
IO.puts "computor running #{DateTime.utc_now()}"
5758
pk = Application.fetch_env!(:ama, :trainer_pk)
5859
pop = Application.fetch_env!(:ama, :trainer_pop)
60+
threads = Application.get_env(:ama, :computor_upow_threads, 0)
5961

6062
coins = DB.Chain.balance(pk)
6163
epoch = DB.Chain.epoch()
64+
segment_vr_hash = DB.Chain.segment_vr_hash()
65+
diff_bits = DB.Chain.diff_bits()
6266
hasExecCoins = coins >= BIC.Coin.to_cents(100)
6367
cond do
68+
is_nil(segment_vr_hash) -> :ok # epoch segment_vr not set yet; nothing to compute against
69+
6470
(state.type == :trainer and !hasExecCoins) or state.type == nil ->
65-
sol = UPOW.compute_for(epoch, EntryGenesis.signer(), EntryGenesis.pop(), pk, :crypto.strong_rand_bytes(96), 100)
71+
# Compute to ourselves as a node: own pk is the computor (reward recipient).
72+
sol = UPOW.compute(epoch, EntryGenesis.signer(), EntryGenesis.pop(), pk, segment_vr_hash, diff_bits, @batch_iterations, threads)
6673
if sol do
6774
IO.puts "🔢 tensor matmul complete! broadcasting sol.."
6875
NodeGen.broadcast(%{op: :sol, sol: sol})
6976
end
7077

7178
true ->
72-
sol = UPOW.compute_for(epoch, pk, pop, pk, :crypto.strong_rand_bytes(96), 100)
79+
sol = UPOW.compute(epoch, pk, pop, pk, segment_vr_hash, diff_bits, @batch_iterations, threads)
7380
if sol do
7481
sk = Application.fetch_env!(:ama, :trainer_sk)
7582
packed_tx = TX.build(sk, "Epoch", "submit_sol", [sol])

ex/lib/node/upow.ex

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
defmodule UPOW do
22
def compute_for(epoch, trainer, pop, computor, segment_vr, itrs \\ 30)
3-
def compute_for(epoch, trainer, pop, computor, segment_vr, 0), do: nil
3+
def compute_for(epoch, trainer, pop, computor, segment_vr, itrs) when epoch >= 156 do
4+
compute(epoch, trainer, pop, computor, Blake3.hash(segment_vr), DB.Chain.diff_bits(), itrs, 0)
5+
end
6+
def compute_for(_epoch, _trainer, _pop, _computor, _segment_vr, 0), do: nil
47
def compute_for(epoch, trainer, pop, computor, segment_vr, itrs) do
58
{hash, sol} = branch_sol(epoch, trainer, pop, computor, segment_vr)
69
valid = BIC.Sol.verify_hash(epoch, hash)
@@ -11,6 +14,13 @@ defmodule UPOW do
1114
end
1215
end
1316

17+
def compute(epoch, trainer, pop, computor, segment_vr_hash, diff_bits, iterations \\ 100, threads \\ 0) do
18+
case RDB.compute_upow(epoch, segment_vr_hash, trainer, pop, computor, diff_bits, iterations, threads) do
19+
{:ok, sol} -> sol
20+
nil -> nil
21+
end
22+
end
23+
1424
def branch_sol(epoch, trainer, pop, computor, segment_vr) do
1525
cond do
1626
epoch >= 156 -> UPOW2.tensormath(epoch, Blake3.hash(segment_vr), trainer, pop, computor)

ex/native/rdb/src/lib.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod atoms;
22
pub mod consensus;
33
pub mod model;
44
pub mod tx_filter;
5+
pub mod upow;
56

67
use rustler::types::{Binary, OwnedBinary};
78
use rustler::{Atom, Encoder, Env, Error, NifResult, NifTaggedEnum, ResourceArc, Term};
@@ -863,6 +864,50 @@ fn freivalds(tensor: Binary, vr_b3: Binary) -> bool {
863864
crate::consensus::bic::sol_freivalds::freivalds(tensor.as_slice(), vr_b3.as_slice())
864865
}
865866

867+
/// UPOW2 computer. Computes up to `iterations` nonce attempts (each a real 16x50240
868+
/// * 50240x16 matmul), split across `threads` workers (min 1), and returns the first
869+
/// 1264-byte sol whose Blake3 hash has at least `diff_bits` leading zero bits (the
870+
/// network difficulty), or nil. DirtyCpu.
871+
#[rustler::nif(schedule = "DirtyCpu")]
872+
fn compute_upow<'a>(
873+
env: Env<'a>,
874+
epoch: u64,
875+
segment_vr_hash: Binary,
876+
trainer: Binary,
877+
pop: Binary,
878+
computor: Binary,
879+
diff_bits: u64,
880+
iterations: u64,
881+
threads: u64,
882+
) -> NifResult<Term<'a>> {
883+
use crate::upow;
884+
if segment_vr_hash.len() != 32 || trainer.len() != 48 || pop.len() != 96 || computor.len() != 48 {
885+
return Err(Error::BadArg);
886+
}
887+
888+
// Build the 240-byte seed template; nonce bytes [228..240] are filled per attempt.
889+
let mut seed = [0u8; upow::PREAMBLE];
890+
seed[0..4].copy_from_slice(&(epoch as u32).to_le_bytes());
891+
seed[4..36].copy_from_slice(segment_vr_hash.as_slice());
892+
seed[36..84].copy_from_slice(trainer.as_slice());
893+
seed[84..180].copy_from_slice(pop.as_slice());
894+
seed[180..228].copy_from_slice(computor.as_slice());
895+
896+
let nthreads = (threads as usize).max(1);
897+
let rng_seeds: Vec<u64> = (0..nthreads).map(|_| rand::random::<u64>()).collect();
898+
899+
let found = std::panic::catch_unwind(|| upow::compute(&seed, diff_bits as u32, iterations, nthreads, &rng_seeds)).unwrap_or(None);
900+
901+
match found {
902+
Some(sol) => {
903+
let mut ob = OwnedBinary::new(sol.len()).ok_or_else(|| Error::Term(Box::new("alloc failed")))?;
904+
ob.as_mut_slice().copy_from_slice(&sol);
905+
Ok((atoms::ok(), Binary::from_owned(ob, env)).encode(env))
906+
}
907+
None => Ok(atoms::nil().encode(env)),
908+
}
909+
}
910+
866911
#[rustler::nif]
867912
fn bintree_root<'a>(env: Env<'a>, proplist: Vec<(Option<Binary<'a>>, Binary<'a>, Binary<'a>)>) -> Term<'a> {
868913
let mut ops = Vec::with_capacity(100);

0 commit comments

Comments
 (0)