Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
book
5 changes: 5 additions & 0 deletions docs/book.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[book]
authors = ["Nyksdevs"]
language = "en"
src = "src"
title = "Nyks Docs"
7 changes: 7 additions & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Summary

- [ZK](zk.md)
- [Block](block.md)
- [Transaction](transaction.md)
- [Mining](mining.md)
- [RPC](rpc.md)
29 changes: 29 additions & 0 deletions docs/src/block.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Block

On Nyks, a block consists of a kernel and a proof. The kernel is made up of a header, body, and appendix.

## Header

The header includes:

* Version
* Height
* Previous block digest, linking it to its parent
* Timestamp
* Proof-of-work data, consisting of a root, two authentication paths (`path_a`, `path_b`), and a nonce
* Cumulative proof-of-work, the running total of work done across the chain
* Difficulty
* Guesser receiver data, describing where the guesser's portion of the block reward goes

## Body

The body includes:

* The transaction kernel, aka the block's inputs and outputs. At this level there's no notion of individual transactions, just the merged set of inputs and outputs that make up the block (its scheme is similar to a single transaction kernel, as explained in [transactions.md](transactions.md))
* The mutator set accumulator, reflecting the mutator set state after this block without guesser UTXOs
* The lock-free MMR accumulator (will be removed)
* The block MMR accumulator, a Merkle mountain range over all block digests in the chain up to and including this block. It lets anyone produce a compact membership proof that a given block is part of the chain's history, without needing the full chain

## Appendix

Contains a block's claims, which are recursively proven on the block's proof.
28 changes: 28 additions & 0 deletions docs/src/mining.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Mining

On Nyks, mining is a 2-step process (plus transaction upgrading, but that's not a "requirement" for consensus, so it doesn't count as one of the steps).

## Composing

The composer's role is to select transactions for inclusion, merge them if needed, or create an empty transaction if none are found. Two transactions are always required for a block transaction, so an empty one gets used as a filler when there's nothing else to include. The composer also generates a coinbase transaction for themselves and finishes computing the zk block proof. This requires beefy machinery, like 128+ GB RAM, 96 cores.

A composer always has to merge two transactions for generating a block transaction, to discourage skipping transactions just to get faster proof generation.

Distribution of the block reward is determined by the composer.

## Mining

Typical PoW mining: a memory-heavy variant of Tip5 is used, and you search for a nonce that satisfies the block's target.

Tip5's research paper can be found here: https://eprint.iacr.org/2023/107.pdf.
Tip5 is also a crucial part of computing ZK proofs [zk.md](zk.md)), so giving miners an incentive to make it faster will probably be worth it in the long term.

Miners choose whichever block template from a composer favors them most.

## Upgraders

There's one extra role for upgrading transactions, called upgraders.

In parallel to composers, upgraders can raise transaction proofs to a quality suitable for on-chain inclusion, merge them for easier inclusion (turning them into a single transaction so the composer doesn't waste time merging), and collect fees from transactions through a process called "gobbling."

Also requires beefy machinery for competition.
46 changes: 46 additions & 0 deletions docs/src/rpc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
## RPC

The Nyks node has a built-in RPC module for whatever purposes you need it for.

It's designed to support multiple transports, but only HTTP is supported for now.

### Enabling

RPC can be enabled with `--rpc-listen <ip>:<port>`.

Methods are grouped into namespaces, which can be individually exposed with `rpc-modules` using the same flag style. This lets you isolate which parts of the node are reachable over RPC, so you can expose only what you need instead of opening up everything.

Available namespaces:

* `Node` - endpoints for general node info
* `Network` - endpoints for peer and networking info
* `Chain` - endpoints for querying blockchain tip state
* `Mining` - endpoints for mining/composing processes
* `Archival` - endpoints for historical data which won't be needed for consensus itself in future
* `Mempool` - endpoints for inspecting mempool status
* `Wallet` - endpoints for serving external wallets

### Request Format

Requests are sent as a JSON POST body:

```json
{
"jsonrpc": "2.0",
"id": 0,
"method": "node_network",
"params": []
}
```

### Response Format

```json
{
"jsonrpc": "2.0",
"id": 0,
"result": "testnet-0"
}
```

For the full list of RPC methods, see [`rpc/core/src/api/ops.rs`](https://github.com/Nyksnet/node/blob/master/rpc/core/src/api/ops.rs).
38 changes: 38 additions & 0 deletions docs/src/transaction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Transaction

On Nyks, transactions consist of 2 parts: the kernel and the proof.

The kernel includes:

* Inputs, which represent removal records from the mutator set
* Outputs, which represent addition records to the mutator set. Each output is made up of a UTXO, a sender randomness, and a receiver digest, which are hashed together with Tip5 to produce the commitment
* Announcements, which allow users to put data on-chain (primarily used for announcing data a receiver needs in order to receive UTXOs today *note*)
* The fee the transaction is paying
* If it's a coinbase transaction, a field declaring the "asked" coinbase reward amount
* A timestamp for when the transaction was created
* The mutator set hash the transaction is synced to
* A merge bit, which can only be true when the transaction has been merged with another transaction

## Proof

Nyks consensus and P2P support 2 proof types.

`ProofCollection` is a collection of proofs for the transaction's validity. This proof kind cannot be included on-chain (it's considered low quality, but fast and easy to compute).

`SingleProof` is generated by recursively proving `ProofCollection` proofs. It's high quality and can be included on-chain, but takes a lot of computation power to produce.

`ProofCollection`s can be upgraded to `SingleProof` by off-P2P services, or by on-P2P upgrader services for a fee (or a different cost, depending on the service). This proving process is why transactions can be expensive.

### Wallet SDK

The Wallet SDK provides a **primitive witness**, a general witness that makes generating these proofs easy.

Transactions are put together with a `TransactionBuilder`, which takes inputs, outputs, and other kernel data, and builds a transaction from them.

Each input is made up of:

* a UTXO
* its mutator set membership proof - proving the UTXO is actually part of the current mutator set, and thus spendable
* the lock script and witness needed to unlock it - proving the caller has the right to spend it

If an output has notification enabled, the `TransactionBuilder` includes its *note* announcement in the kernel's announcements field.
9 changes: 9 additions & 0 deletions docs/src/zk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# ZK

Nyks uses Triton VM as the underlying VM for its STARK proofs. It is a stack-based virtual machine with efficient recursive verification, making it suitable for an indefinitely growing blockchain.

Because each block can verify the proof from the previous block, the latest block can represent the validity of the entire chain and its current state. This keeps verification succinct without requiring the whole blockchain to be verified from scratch.

Succinctness is not implemented yet.

Triton VM's repository can be found here: https://github.com/TritonVM/triton-vm.
3 changes: 1 addition & 2 deletions miner/src/core/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ impl Args {
.await
.expect("Failed to connect to RPC node")
.network
.parse::<Network>()
.unwrap();
.into();

assert_eq!(
self.network, remote_network,
Expand Down
2 changes: 1 addition & 1 deletion node/src/application/rpc/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ async fn find_block_digest(selector: BlockSelector, state: &GlobalState) -> Opti
impl RpcApi for RpcServer {
async fn network_call(&self, _: NetworkRequest) -> RpcResult<NetworkResponse> {
Ok(NetworkResponse {
network: self.state.cli().network.to_string(),
network: self.state.cli().network.into(),
})
}

Expand Down
2 changes: 1 addition & 1 deletion rpc/core/src/api/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::model::message::*;
/// Must be bumped every time a breaking change to the RPC API is made. Adding
/// a new endpoint is not a breaking change. Neither is adding a new field to
/// a type that is returned by this API.
pub const RPC_API_VERSION: u16 = 2;
pub const RPC_API_VERSION: u16 = 1;

#[derive(
Clone,
Expand Down
30 changes: 9 additions & 21 deletions rpc/core/src/api/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::model::block::header::RpcBlockPow;
use crate::model::block::transaction_kernel::RpcAbsoluteIndexSet;
use crate::model::block::transaction_kernel::RpcAdditionRecord;
use crate::model::block::transaction_kernel::RpcTransactionKernelId;
use crate::model::common::RpcBlockSelector;
use crate::model::common::BlockSelector;
use crate::model::json::JsonError;
use crate::model::message::*;
use crate::model::wallet::transaction::RpcTransaction;
Expand Down Expand Up @@ -159,10 +159,7 @@ pub trait RpcApi: Sync + Send {
request: GetBlockDigestsRequest,
) -> RpcResult<GetBlockDigestsResponse>;

async fn get_block_digest(
&self,
selector: RpcBlockSelector,
) -> RpcResult<GetBlockDigestResponse> {
async fn get_block_digest(&self, selector: BlockSelector) -> RpcResult<GetBlockDigestResponse> {
self.get_block_digest_call(GetBlockDigestRequest { selector })
.await
}
Expand All @@ -171,15 +168,12 @@ pub trait RpcApi: Sync + Send {
request: GetBlockDigestRequest,
) -> RpcResult<GetBlockDigestResponse>;

async fn get_block(&self, selector: RpcBlockSelector) -> RpcResult<GetBlockResponse> {
async fn get_block(&self, selector: BlockSelector) -> RpcResult<GetBlockResponse> {
self.get_block_call(GetBlockRequest { selector }).await
}
async fn get_block_call(&self, request: GetBlockRequest) -> RpcResult<GetBlockResponse>;

async fn get_block_proof(
&self,
selector: RpcBlockSelector,
) -> RpcResult<GetBlockProofResponse> {
async fn get_block_proof(&self, selector: BlockSelector) -> RpcResult<GetBlockProofResponse> {
self.get_block_proof_call(GetBlockProofRequest { selector })
.await
}
Expand All @@ -188,10 +182,7 @@ pub trait RpcApi: Sync + Send {
request: GetBlockProofRequest,
) -> RpcResult<GetBlockProofResponse>;

async fn get_block_kernel(
&self,
selector: RpcBlockSelector,
) -> RpcResult<GetBlockKernelResponse> {
async fn get_block_kernel(&self, selector: BlockSelector) -> RpcResult<GetBlockKernelResponse> {
self.get_block_kernel_call(GetBlockKernelRequest { selector })
.await
}
Expand All @@ -200,10 +191,7 @@ pub trait RpcApi: Sync + Send {
request: GetBlockKernelRequest,
) -> RpcResult<GetBlockKernelResponse>;

async fn get_block_header(
&self,
selector: RpcBlockSelector,
) -> RpcResult<GetBlockHeaderResponse> {
async fn get_block_header(&self, selector: BlockSelector) -> RpcResult<GetBlockHeaderResponse> {
self.get_block_header_call(GetBlockHeaderRequest { selector })
.await
}
Expand All @@ -212,7 +200,7 @@ pub trait RpcApi: Sync + Send {
request: GetBlockHeaderRequest,
) -> RpcResult<GetBlockHeaderResponse>;

async fn get_block_body(&self, selector: RpcBlockSelector) -> RpcResult<GetBlockBodyResponse> {
async fn get_block_body(&self, selector: BlockSelector) -> RpcResult<GetBlockBodyResponse> {
self.get_block_body_call(GetBlockBodyRequest { selector })
.await
}
Expand All @@ -223,7 +211,7 @@ pub trait RpcApi: Sync + Send {

async fn get_block_transaction_kernel(
&self,
selector: RpcBlockSelector,
selector: BlockSelector,
) -> RpcResult<GetBlockTransactionKernelResponse> {
self.get_block_transaction_kernel_call(GetBlockTransactionKernelRequest { selector })
.await
Expand All @@ -235,7 +223,7 @@ pub trait RpcApi: Sync + Send {

async fn get_block_announcements(
&self,
selector: RpcBlockSelector,
selector: BlockSelector,
) -> RpcResult<GetBlockAnnouncementsResponse> {
self.get_block_announcements_call(GetBlockAnnouncementsRequest { selector })
.await
Expand Down
32 changes: 30 additions & 2 deletions rpc/core/src/model/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::fmt::LowerHex;
use std::str::FromStr;

use nyks_consensus::block::block_height::BlockHeight;
use nyks_consensus::network::Network;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
Expand Down Expand Up @@ -207,8 +208,35 @@ impl FromStr for BlockSelector {
}
}

// TODO: cleanup...
pub type RpcBlockSelector = BlockSelector;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RpcNetwork(pub Network);

impl From<Network> for RpcNetwork {
fn from(network: Network) -> Self {
RpcNetwork(network)
}
}

impl From<RpcNetwork> for Network {
fn from(network: RpcNetwork) -> Self {
network.0
}
}

impl Serialize for RpcNetwork {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.collect_str(&self.0)
}
}

impl<'de> Deserialize<'de> for RpcNetwork {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let s = String::deserialize(d)?;
Network::from_str(&s)
.map(RpcNetwork)
.map_err(serde::de::Error::custom)
}
}

#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
Expand Down
Loading
Loading