Skip to content

Commit

Permalink
Migrate neurons to new repo
Browse files Browse the repository at this point in the history
Co-authored-by: [email protected]
  • Loading branch information
cptartur committed May 8, 2024
1 parent 1090c26 commit 17777b8
Show file tree
Hide file tree
Showing 12 changed files with 632 additions and 0 deletions.
2 changes: 2 additions & 0 deletions neurons/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[alias]
lint = "clippy --all-targets --all-features -- -W clippy::pedantic -A clippy::missing_errors_doc -A clippy::module_name_repetitions"
3 changes: 3 additions & 0 deletions neurons/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
data
target
result
115 changes: 115 additions & 0 deletions neurons/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions neurons/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "neurons"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1.0.80"
camino = "1.1.6"
serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.114"
serde_repr = "0.1.18"
37 changes: 37 additions & 0 deletions neurons/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Neurons

This code is an implementation of `neurons`
from the [Neural Quorum Governance](https://stellarcommunityfund.gitbook.io/module-library).
The data computed by this package is uploaded to the voting contract.

This is the source code used to calculate voting powers for each neuron used in NQG mechanism.
It also normalized the votes of voters (converts delegations to final votes) for them to be uploaded to the contract.

## Inputs

Neurons expect the inputs to be provided in `json` format. The inputs are loaded from `data/` directory.

## Outputs

Computed voting powers and normalized votes are written to `result/` directory.

## Running

```shell
cargo run
```

## Neurons

### Assigned Reputation Neuron

Assigns voting power based on voter discord rank.

### Prior Voting History Neuron

Assigns voting power based on rounds voter previously participated in.

### Trust Graph Neuron

Assigns voting power based on trust assigned to voter by other voters.
It uses min-max normalized PageRank algorithm to compute the score.
12 changes: 12 additions & 0 deletions neurons/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use serde::{Deserialize, Serialize};

pub mod neurons;
pub mod quorum;

#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
pub enum Vote {
Yes,
No,
Delegate,
Abstain,
}
79 changes: 79 additions & 0 deletions neurons/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use camino::Utf8Path;
use neurons::neurons::assigned_reputation::AssignedReputationNeuron;
use neurons::neurons::prior_voting_history::PriorVotingHistoryNeuron;
use neurons::neurons::trust_graph::TrustGraphNeuron;
use neurons::neurons::Neuron;
use neurons::quorum::normalize_votes;
use neurons::Vote;
use serde::Serialize;
use std::collections::{BTreeMap, HashMap};
use std::fs;

pub const DECIMALS: i64 = 1_000_000_000_000_000_000;

fn write_result<T>(file_name: &str, data: &T)
where
T: Serialize,
{
let serialized = serde_json::to_string(&data).unwrap();
fs::write(file_name, serialized).unwrap();
}

fn to_sorted_map<K, L>(data: HashMap<K, L>) -> BTreeMap<K, L>
where
K: Ord,
{
data.into_iter().collect()
}

#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
fn to_fixed_point_decimal(val: f64) -> i128 {
(val * DECIMALS as f64) as i128
}

fn calculate_neuron_results(users: &[String], neurons: Vec<Box<dyn Neuron>>) {
for neuron in neurons {
let result = neuron.calculate_result(users);
let result: HashMap<String, String> = result
.into_iter()
.map(|(key, value)| (key, to_fixed_point_decimal(value).to_string()))
.collect();
let result = to_sorted_map(result);
write_result(&format!("result/{}.json", neuron.name()), &result);
}
}

fn main() {
let path = Utf8Path::new("data/previous_rounds_for_users.json");
let prior_voting_history_neuron = PriorVotingHistoryNeuron::try_from_file(path).unwrap();

let path = Utf8Path::new("data/users_reputation.json");
let assigned_reputation_neuron = AssignedReputationNeuron::try_from_file(path).unwrap();

let path = Utf8Path::new("data/trusted_for_user.json");
let trust_graph_neuron = TrustGraphNeuron::try_from_file(path).unwrap();

let users_raw = fs::read_to_string("data/voters.json").unwrap();
let users: Vec<String> = serde_json::from_str(users_raw.as_str()).unwrap();

let votes_raw = fs::read_to_string("data/votes.json").unwrap();
let votes: HashMap<String, HashMap<String, Vote>> =
serde_json::from_str(votes_raw.as_str()).unwrap();
let delegatees_for_user_raw = fs::read_to_string("data/delegatees_for_user.json").unwrap();
let delegatees_for_user: HashMap<String, Vec<String>> =
serde_json::from_str(delegatees_for_user_raw.as_str()).unwrap();
let normalized_votes = normalize_votes(votes, &delegatees_for_user).unwrap();
write_result(
"result/normalized_votes.json",
&to_sorted_map(normalized_votes),
);

calculate_neuron_results(
&users,
vec![
Box::new(prior_voting_history_neuron),
Box::new(assigned_reputation_neuron),
Box::new(trust_graph_neuron),
],
);
}
11 changes: 11 additions & 0 deletions neurons/src/neurons.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use std::collections::HashMap;

pub mod assigned_reputation;
pub mod prior_voting_history;
pub mod trust_graph;

pub trait Neuron {
fn name(&self) -> String;

fn calculate_result(&self, users: &[String]) -> HashMap<String, f64>;
}
58 changes: 58 additions & 0 deletions neurons/src/neurons/assigned_reputation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use crate::neurons::Neuron;
use anyhow::Result;
use camino::Utf8Path;
use serde_repr::Deserialize_repr;
use std::collections::HashMap;
use std::fs::File;
use std::io::BufReader;

#[derive(Deserialize_repr, Clone, Debug)]
#[repr(i32)]
pub enum ReputationTier {
Unknown = -1,
Verified = 0,
Pathfinder = 1,
Navigator = 2,
Pilot = 3,
}

#[derive(Clone, Debug)]
pub struct AssignedReputationNeuron {
users_reputation: HashMap<String, ReputationTier>,
}

impl AssignedReputationNeuron {
pub fn try_from_file(path: &Utf8Path) -> Result<Self> {
let file = File::open(path)?;
let reader = BufReader::new(file);

let users_reputation = serde_json::from_reader(reader)?;
Ok(Self { users_reputation })
}
}

fn reputation_bonus(reputation_tier: &ReputationTier) -> f64 {
match reputation_tier {
ReputationTier::Unknown | ReputationTier::Verified => 0.0,
ReputationTier::Pathfinder => 0.1,
ReputationTier::Navigator => 0.2,
ReputationTier::Pilot => 0.3,
}
}

impl Neuron for AssignedReputationNeuron {
fn name(&self) -> String {
"assigned_reputation_neuron".to_string()
}

fn calculate_result(&self, users: &[String]) -> HashMap<String, f64> {
let mut result = HashMap::new();

for user in users {
let bonus = reputation_bonus(self.users_reputation.get(user).unwrap());
result.insert(user.into(), bonus);
}

result
}
}
Loading

0 comments on commit 17777b8

Please sign in to comment.