Rust GBT proof of concept

This commit is contained in:
Mononaut
2023-06-23 16:42:58 -04:00
parent cd181a0fbb
commit 63713ca4ed
14 changed files with 943 additions and 9 deletions

5
backend/rust-gbt/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
target
index.node
**/node_modules
**/.DS_Store
npm-debug.log*

156
backend/rust-gbt/Cargo.lock generated Normal file
View File

@@ -0,0 +1,156 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "gbt"
version = "0.1.0"
dependencies = [
"neon",
]
[[package]]
name = "libloading"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "neon"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28e15415261d880aed48122e917a45e87bb82cf0260bb6db48bbab44b7464373"
dependencies = [
"neon-build",
"neon-macros",
"neon-runtime",
"semver",
"smallvec",
]
[[package]]
name = "neon-build"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bac98a702e71804af3dacfde41edde4a16076a7bbe889ae61e56e18c5b1c811"
[[package]]
name = "neon-macros"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7288eac8b54af7913c60e0eb0e2a7683020dffa342ab3fd15e28f035ba897cf"
dependencies = [
"quote",
"syn",
"syn-mid",
]
[[package]]
name = "neon-runtime"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4676720fa8bb32c64c3d9f49c47a47289239ec46b4bdb66d0913cc512cb0daca"
dependencies = [
"cfg-if",
"libloading",
"smallvec",
]
[[package]]
name = "proc-macro2"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
dependencies = [
"proc-macro2",
]
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "smallvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn-mid"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baa8e7560a164edb1621a55d18a0c59abf49d360f47aa7b821061dd7eea7fac9"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@@ -0,0 +1,21 @@
[package]
name = "gbt"
version = "0.1.0"
description = "An inefficient re-implementation of the getBlockTemplate algorithm in Rust"
authors = ["mononaut"]
edition = "2018"
exclude = ["index.node"]
[lib]
crate-type = ["cdylib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
priority-queue = "1.3.2"
bytes = "1.4.0"
[dependencies.neon]
version = "0.10"
default-features = false
features = ["napi-6","channel-api"]

121
backend/rust-gbt/README.md Normal file
View File

@@ -0,0 +1,121 @@
# gbt
**gbt:** rust implementation of the getBlockTemplate algorithm
This project was bootstrapped by [create-neon](https://www.npmjs.com/package/create-neon).
## Installing gbt
Installing gbt requires a [supported version of Node and Rust](https://github.com/neon-bindings/neon#platform-support).
You can install the project with npm. In the project directory, run:
```sh
$ npm install
```
This fully installs the project, including installing any dependencies and running the build.
## Building gbt
If you have already installed the project and only want to run the build, run:
```sh
$ npm run build
```
This command uses the [cargo-cp-artifact](https://github.com/neon-bindings/cargo-cp-artifact) utility to run the Rust build and copy the built library into `./index.node`.
## Exploring gbt
After building gbt, you can explore its exports at the Node REPL:
```sh
$ npm install
$ node
> require('.').hello()
"hello node"
```
## Available Scripts
In the project directory, you can run:
### `npm install`
Installs the project, including running `npm run build`.
### `npm build`
Builds the Node addon (`index.node`) from source.
Additional [`cargo build`](https://doc.rust-lang.org/cargo/commands/cargo-build.html) arguments may be passed to `npm build` and `npm build-*` commands. For example, to enable a [cargo feature](https://doc.rust-lang.org/cargo/reference/features.html):
```
npm run build -- --feature=beetle
```
#### `npm build-debug`
Alias for `npm build`.
#### `npm build-release`
Same as [`npm build`](#npm-build) but, builds the module with the [`release`](https://doc.rust-lang.org/cargo/reference/profiles.html#release) profile. Release builds will compile slower, but run faster.
### `npm test`
Runs the unit tests by calling `cargo test`. You can learn more about [adding tests to your Rust code](https://doc.rust-lang.org/book/ch11-01-writing-tests.html) from the [Rust book](https://doc.rust-lang.org/book/).
## Project Layout
The directory structure of this project is:
```
gbt/
├── Cargo.toml
├── README.md
├── index.node
├── package.json
├── src/
| └── lib.rs
└── target/
```
### Cargo.toml
The Cargo [manifest file](https://doc.rust-lang.org/cargo/reference/manifest.html), which informs the `cargo` command.
### README.md
This file.
### index.node
The Node addon—i.e., a binary Node module—generated by building the project. This is the main module for this package, as dictated by the `"main"` key in `package.json`.
Under the hood, a [Node addon](https://nodejs.org/api/addons.html) is a [dynamically-linked shared object](https://en.wikipedia.org/wiki/Library_(computing)#Shared_libraries). The `"build"` script produces this file by copying it from within the `target/` directory, which is where the Rust build produces the shared object.
### package.json
The npm [manifest file](https://docs.npmjs.com/cli/v7/configuring-npm/package-json), which informs the `npm` command.
### src/
The directory tree containing the Rust source code for the project.
### src/lib.rs
The Rust library's main module.
### target/
Binary artifacts generated by the Rust build.
## Learn More
To learn more about Neon, see the [Neon documentation](https://neon-bindings.com).
To learn more about Rust, see the [Rust documentation](https://www.rust-lang.org).
To learn more about Node, see the [Node documentation](https://nodejs.org).

25
backend/rust-gbt/package-lock.json generated Normal file
View File

@@ -0,0 +1,25 @@
{
"name": "gbt",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "gbt",
"version": "0.1.0",
"hasInstallScript": true,
"devDependencies": {
"cargo-cp-artifact": "^0.1"
}
},
"node_modules/cargo-cp-artifact": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/cargo-cp-artifact/-/cargo-cp-artifact-0.1.8.tgz",
"integrity": "sha512-3j4DaoTrsCD1MRkTF2Soacii0Nx7UHCce0EwUf4fHnggwiE4fbmF2AbnfzayR36DF8KGadfh7M/Yfy625kgPlA==",
"dev": true,
"bin": {
"cargo-cp-artifact": "bin/cargo-cp-artifact.js"
}
}
}
}

View File

@@ -0,0 +1,17 @@
{
"name": "gbt",
"version": "0.1.0",
"description": "An inefficient re-implementation of the getBlockTemplate algorithm in Rust",
"main": "index.node",
"scripts": {
"build": "cargo-cp-artifact -nc index.node -- cargo build --message-format=json-render-diagnostics",
"build-debug": "npm run build --",
"build-release": "npm run build -- --release",
"install": "npm run build-release",
"test": "cargo test"
},
"author": "mononaut",
"devDependencies": {
"cargo-cp-artifact": "^0.1"
}
}

View File

@@ -0,0 +1,53 @@
use std::{collections::{HashSet}, hash::{Hash, Hasher}, cmp::Ordering};
#[derive(Clone)]
pub struct AuditTransaction {
pub uid: u32,
pub fee: u64,
pub weight: u32,
pub sigops: u32,
pub fee_per_vsize: f64,
pub effective_fee_per_vsize: f64,
pub dependency_rate: f64,
pub inputs: Vec<u32>,
pub is_relatives_set: bool,
pub ancestors: HashSet<u32>,
pub children: HashSet<u32>,
pub ancestor_fee: u64,
pub ancestor_weight: u32,
pub ancestor_sigops: u32,
pub score: f64,
pub used: bool,
pub modified: bool,
pub dirty: bool,
}
impl Hash for AuditTransaction {
fn hash<H: Hasher>(&self, state: &mut H) {
self.uid.hash(state);
}
}
impl PartialEq for AuditTransaction {
fn eq(&self, other: &Self) -> bool {
self.uid == other.uid
}
}
impl Eq for AuditTransaction {}
impl PartialOrd for AuditTransaction {
fn partial_cmp(&self, other: &AuditTransaction) -> Option<Ordering> {
if self.score == other.score {
return Some(self.uid.cmp(&other.uid));
} else {
return self.score.partial_cmp(&other.score);
}
}
}
impl Ord for AuditTransaction {
fn cmp(&self, other: &AuditTransaction) -> Ordering {
self.partial_cmp(other).unwrap()
}
}

312
backend/rust-gbt/src/gbt.rs Normal file
View File

@@ -0,0 +1,312 @@
use std::cmp::Ordering;
use std::collections::{HashMap, HashSet, VecDeque};
use std::f64::INFINITY;
use priority_queue::PriorityQueue;
use crate::thread_transaction::{ThreadTransaction};
use crate::audit_transaction::{AuditTransaction};
const BLOCK_WEIGHT_UNITS: u32 = 4_000_000;
const BLOCK_SIGOPS: u32 = 80_000;
struct TxPriority {
uid: u32,
score: f64,
}
impl PartialEq for TxPriority {
fn eq(&self, other: &Self) -> bool {
self.uid == other.uid
}
}
impl Eq for TxPriority {}
impl PartialOrd for TxPriority {
fn partial_cmp(&self, other: &TxPriority) -> Option<Ordering> {
if self.score == other.score {
return Some(self.uid.cmp(&other.uid));
} else {
return other.score.partial_cmp(&self.score);
}
}
}
impl Ord for TxPriority {
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap()
}
}
pub fn gbt(mempool_array: Vec<ThreadTransaction>) -> (Vec<Vec<u32>>, Vec<(u32, f64)>, Vec<Vec<u32>>) {
let mut mempool: HashMap<u32,ThreadTransaction> = HashMap::new();
for transaction in mempool_array {
mempool.insert(transaction.uid, transaction);
}
return make_block_templates(mempool);
}
/*
* Build projected mempool blocks using an approximation of the transaction selection algorithm from Bitcoin Core
* (see BlockAssembler in https://github.com/bitcoin/bitcoin/blob/master/src/node/miner.cpp)
* Ported from https://github.com/mempool/mempool/blob/master/backend/src/api/tx-selection-worker.ts
*/
fn make_block_templates(mempool: HashMap<u32,ThreadTransaction>) -> (Vec<Vec<u32>>, Vec<(u32, f64)>, Vec<Vec<u32>>) {
let mut audit_pool: HashMap<u32, AuditTransaction> = HashMap::new();
let mut mempool_array: VecDeque<u32> = VecDeque::new();
let mut cluster_array: Vec<Vec<u32>> = Vec::new();
// Initialize working structs
for (uid, tx) in &mempool {
let audit_tx = AuditTransaction {
uid: tx.uid,
fee: tx.fee,
weight: tx.weight,
sigops: tx.sigops,
fee_per_vsize: tx.fee_per_vsize,
effective_fee_per_vsize: tx.effective_fee_per_vsize,
dependency_rate: INFINITY,
inputs: tx.inputs.clone(),
is_relatives_set: false,
ancestors: HashSet::new(),
children: HashSet::new(),
ancestor_fee: tx.fee,
ancestor_weight: tx.weight,
ancestor_sigops: tx.sigops,
score: 0.0,
used: false,
modified: false,
dirty: false,
};
audit_pool.insert(audit_tx.uid, audit_tx);
mempool_array.push_back(*uid);
}
// Build relatives graph & calculate ancestor scores
for txid in &mempool_array {
set_relatives(*txid, &mut audit_pool);
}
// Sort by descending ancestor score
mempool_array.make_contiguous().sort_unstable_by(|a, b| {
let a_tx = audit_pool.get(a).unwrap();
let b_tx = audit_pool.get(b).unwrap();
b_tx.cmp(a_tx)
});
// Build blocks by greedily choosing the highest feerate package
// (i.e. the package rooted in the transaction with the best ancestor score)
let mut blocks: Vec<Vec<u32>> = Vec::new();
let mut block_weight: u32 = 4000;
let mut block_sigops: u32 = 0;
let mut transactions: Vec<u32> = Vec::new();
let mut modified: PriorityQueue<u32, TxPriority> = PriorityQueue::new();
let mut overflow: Vec<u32> = Vec::new();
let mut failures = 0;
while mempool_array.len() > 0 || !modified.is_empty() {
let next_txid: u32;
if modified.is_empty() {
next_txid = mempool_array.pop_front().unwrap();
} else if mempool_array.len() == 0 {
next_txid = modified.pop().unwrap().0;
} else {
let next_array_txid = mempool_array.front().unwrap();
let next_modified_txid = modified.peek().unwrap().0;
let array_tx: &AuditTransaction = audit_pool.get(next_array_txid).unwrap();
let modified_tx: &AuditTransaction = audit_pool.get(next_modified_txid).unwrap();
match array_tx.cmp(&modified_tx) {
std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => {
next_txid = mempool_array.pop_front().unwrap();
}
std::cmp::Ordering::Less => {
next_txid = modified.pop().unwrap().0;
}
}
}
let next_tx: AuditTransaction = audit_pool.get(&next_txid).unwrap().clone();
if next_tx.used {
continue;
}
if blocks.len() < 7 && ((block_weight + next_tx.ancestor_weight >= BLOCK_WEIGHT_UNITS) || (block_sigops + next_tx.ancestor_sigops > BLOCK_SIGOPS)) {
// hold this package in an overflow list while we check for smaller options
overflow.push(next_txid);
failures += 1;
} else {
let mut package: Vec<(u32, usize, u32)> = Vec::new();
let mut cluster: Vec<u32> = Vec::new();
let is_cluster: bool = next_tx.ancestors.len() > 0;
package.push((next_tx.uid, next_tx.ancestors.len(), next_tx.weight));
cluster.push(next_tx.uid);
for ancestor_id in &next_tx.ancestors {
if let Some(ancestor) = audit_pool.get(ancestor_id) {
package.push((*ancestor_id, ancestor.ancestors.len(), ancestor.weight));
cluster.push(*ancestor_id);
}
}
package.sort_unstable_by_key(|a| 0 - a.1);
if is_cluster {
cluster_array.push(cluster);
}
let cluster_rate = next_tx.dependency_rate.min(next_tx.ancestor_fee as f64 / (next_tx.ancestor_weight as f64 / 4.0));
for package_entry in &package {
if let Some(tx) = audit_pool.get_mut(&package_entry.0) {
tx.used = true;
if tx.effective_fee_per_vsize != cluster_rate {
tx.effective_fee_per_vsize = cluster_rate;
tx.dirty = true;
}
transactions.push(tx.uid);
block_weight += tx.weight;
block_sigops += tx.sigops;
}
update_descendants(package_entry.0, &mut audit_pool, &mut modified, cluster_rate);
}
failures = 0;
}
// this block is full
let exceeded_package_tries = failures > 1000 && block_weight > (BLOCK_WEIGHT_UNITS - 4000);
let queue_is_empty = mempool_array.len() == 0 && modified.is_empty();
if (exceeded_package_tries || queue_is_empty) && blocks.len() < 7 {
// finalize this block
if transactions.len() > 0 {
blocks.push(transactions);
}
// reset for the next block
transactions = Vec::new();
block_weight = 4000;
// 'overflow' packages didn't fit in this block, but are valid candidates for the next
overflow.reverse();
for overflowed in &overflow {
if let Some(overflowed_tx) = audit_pool.get(overflowed) {
if overflowed_tx.modified {
modified.push(*overflowed, TxPriority{ uid: *overflowed, score: overflowed_tx.score});
} else {
mempool_array.push_front(*overflowed);
}
}
}
overflow = Vec::new();
}
}
// add the final unbounded block if it contains any transactions
if transactions.len() > 0 {
blocks.push(transactions);
}
// make a list of dirty transactions and their new rates
let mut rates: Vec<(u32, f64)> = Vec::new();
for (txid, tx) in audit_pool {
if tx.dirty {
rates.push((txid, tx.effective_fee_per_vsize));
}
}
return (blocks, rates, cluster_array);
}
fn set_relatives(txid: u32, audit_pool: &mut HashMap<u32, AuditTransaction>) {
let mut parents: HashSet<u32> = HashSet::new();
if let Some(tx) = audit_pool.get(&txid) {
if tx.is_relatives_set {
return;
}
for input in &tx.inputs {
parents.insert(*input);
}
} else {
return;
}
let mut ancestors: HashSet<u32> = HashSet::new();
for parent_id in &parents {
set_relatives(*parent_id, audit_pool);
let parent_entry: Option<&mut AuditTransaction> = audit_pool.get_mut(&parent_id);
match parent_entry {
Some(parent) => {
ancestors.insert(*parent_id);
parent.children.insert(txid);
for ancestor in &parent.ancestors {
ancestors.insert(*ancestor);
}
}
None => {}
}
}
let mut total_fee: u64 = 0;
let mut total_weight: u32 = 0;
let mut total_sigops: u32 = 0;
for ancestor_id in &ancestors {
let ancestor = audit_pool.get(&ancestor_id).unwrap();
total_fee += ancestor.fee;
total_weight += ancestor.weight;
total_sigops += ancestor.sigops;
}
if let Some(tx) = audit_pool.get_mut(&txid) {
tx.ancestors = ancestors;
tx.ancestor_fee = tx.fee + total_fee;
tx.ancestor_weight = tx.weight + total_weight;
tx.ancestor_sigops = tx.sigops + total_sigops;
tx.score = (tx.ancestor_fee as f64) / (if tx.ancestor_weight != 0 {tx.ancestor_weight as f64 / 4.0} else { 1.0 });
tx.is_relatives_set = true;
}
}
// iterate over remaining descendants, removing the root as a valid ancestor & updating the ancestor score
fn update_descendants(root_txid: u32, audit_pool: &mut HashMap<u32, AuditTransaction>, modified: &mut PriorityQueue<u32, TxPriority>, cluster_rate: f64) {
let mut visited: HashSet<u32> = HashSet::new();
let mut descendant_stack: Vec<u32> = Vec::new();
let root_fee: u64;
let root_weight: u32;
let root_sigops: u32;
if let Some(root_tx) = audit_pool.get(&root_txid) {
for descendant_id in &root_tx.children {
if !visited.contains(descendant_id) {
descendant_stack.push(*descendant_id);
visited.insert(*descendant_id);
}
}
root_fee = root_tx.fee;
root_weight = root_tx.weight;
root_sigops = root_tx.sigops;
} else {
return;
}
while descendant_stack.len() > 0 {
let next_txid: u32 = descendant_stack.pop().unwrap();
if let Some(descendant) = audit_pool.get_mut(&next_txid) {
// remove root tx as ancestor
descendant.ancestors.remove(&root_txid);
descendant.ancestor_fee -= root_fee;
descendant.ancestor_weight -= root_weight;
descendant.ancestor_sigops -= root_sigops;
let current_score = descendant.score;
descendant.score = (descendant.ancestor_fee as f64) / (if descendant.ancestor_weight != 0 {descendant.ancestor_weight as f64 / 4.0} else { 1.0 });
descendant.dependency_rate = descendant.dependency_rate.min(cluster_rate);
descendant.modified = true;
// update modified priority if score has changed
if !descendant.modified || descendant.score < current_score {
modified.push_decrease(descendant.uid, TxPriority { uid: descendant.uid, score: descendant.score});
} else if descendant.score > current_score {
modified.push_increase(descendant.uid, TxPriority { uid: descendant.uid, score: descendant.score});
}
// add this node's children to the stack
for child_id in &descendant.children {
if !visited.contains(child_id) {
descendant_stack.push(*child_id);
visited.insert(*child_id);
}
}
}
}
}

View File

@@ -0,0 +1,75 @@
use neon::{prelude::*, types::buffer::TypedArray};
mod gbt;
mod thread_transaction;
mod audit_transaction;
use thread_transaction::{ThreadTransaction};
fn go(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let mempool_arg = cx.argument::<JsArrayBuffer>(0)?.root(&mut cx).into_inner(&mut cx);
let callback = cx.argument::<JsFunction>(1)?.root(&mut cx);
let channel = cx.channel();
let buffer = mempool_arg.as_slice(&mut cx);
let thread_transactions = ThreadTransaction::batch_from_buffer(buffer);
std::thread::spawn(move || {
let (blocks, rates, clusters) = gbt::gbt(thread_transactions);
channel.send(move |mut cx| {
let result = JsObject::new(&mut cx);
let js_blocks = JsArray::new(&mut cx, blocks.len() as u32);
for (i, block) in blocks.iter().enumerate() {
let inner = JsArray::new(&mut cx, block.len() as u32);
for (j, uid) in block.iter().enumerate() {
let v = cx.number(*uid);
inner.set(&mut cx, j as u32, v)?;
}
js_blocks.set(&mut cx, i as u32, inner)?;
}
let js_clusters = JsArray::new(&mut cx, clusters.len() as u32);
for (i, cluster) in clusters.iter().enumerate() {
let inner = JsArray::new(&mut cx, cluster.len() as u32);
for (j, uid) in cluster.iter().enumerate() {
let v = cx.number(*uid);
inner.set(&mut cx, j as u32, v)?;
}
js_clusters.set(&mut cx, i as u32, inner)?;
}
let js_rates = JsArray::new(&mut cx, rates.len() as u32);
for (i, (uid, rate)) in rates.iter().enumerate() {
let inner = JsArray::new(&mut cx, 2);
let js_uid = cx.number(*uid);
let js_rate = cx.number(*rate);
inner.set(&mut cx, 0, js_uid)?;
inner.set(&mut cx, 1, js_rate)?;
js_rates.set(&mut cx, i as u32, inner)?;
}
result.set(&mut cx, "blocks", js_blocks)?;
result.set(&mut cx, "clusters", js_clusters)?;
result.set(&mut cx, "rates", js_rates)?;
let callback = callback.into_inner(&mut cx);
let this = cx.undefined();
let args = vec![
result.upcast()
];
callback.call(&mut cx, this, args)?;
Ok(())
});
});
Ok(cx.undefined())
}
#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
cx.export_function("go", go)?;
Ok(())
}

View File

@@ -0,0 +1,45 @@
// use neon::{types::{JsObject, JsNumber, JsArray, JsValue, JsBoolean, JsArrayBuffer, buffer::TypedArray}, prelude::{Object, FunctionContext, Handle}};
extern crate bytes;
use std::io::Cursor;
use bytes::buf::Buf;
pub struct ThreadTransaction {
pub uid: u32,
pub fee: u64,
pub weight: u32,
pub sigops: u32,
pub fee_per_vsize: f64,
pub effective_fee_per_vsize: f64,
pub inputs: Vec<u32>,
}
impl ThreadTransaction {
pub fn batch_from_buffer(buffer: &[u8]) -> Vec<ThreadTransaction> {
let mut transactions: Vec<ThreadTransaction> = Vec::new();
let mut cursor = Cursor::new(buffer);
let size = cursor.get_u32();
for _ in 0..size {
let uid = cursor.get_u32();
let fee = cursor.get_f64() as u64;
let weight = cursor.get_u32();
let sigops = cursor.get_u32();
let fee_per_vsize = cursor.get_f64();
let effective_fee_per_vsize = cursor.get_f64();
let input_count = cursor.get_u32();
let mut inputs: Vec<u32> = Vec::new();
for _ in 0..input_count {
inputs.push(cursor.get_u32());
}
transactions.push(ThreadTransaction {
uid,
fee,
weight,
sigops,
fee_per_vsize,
effective_fee_per_vsize,
inputs,
})
}
return transactions;
}
}