mirror of
https://github.com/Cuprate/cuprate.git
synced 2025-02-02 03:06:36 +00:00
Merge branch 'main' into hack
This commit is contained in:
commit
8ff899b212
224 changed files with 8229 additions and 2002 deletions
2
.github/ISSUE_TEMPLATE/bug.md
vendored
2
.github/ISSUE_TEMPLATE/bug.md
vendored
|
@ -24,7 +24,7 @@ Example:
|
|||
## Bug
|
||||
What is the bug?
|
||||
|
||||
### Expected behavior
|
||||
## Expected behavior
|
||||
What correct beahvior was expected to happen?
|
||||
|
||||
## Steps to reproduce
|
||||
|
|
6
.github/ISSUE_TEMPLATE/proposal.md
vendored
6
.github/ISSUE_TEMPLATE/proposal.md
vendored
|
@ -14,11 +14,11 @@ Note: Please search to see if an issue already exists for this proposal.
|
|||
## What
|
||||
Describe your proposal.
|
||||
|
||||
## Where
|
||||
Describe where your proposal will cause changes to.
|
||||
|
||||
## Why
|
||||
Describe why the proposal is needed.
|
||||
|
||||
## Where
|
||||
Describe where your proposal will cause changes to.
|
||||
|
||||
## How
|
||||
Describe how the proposal could be implemented.
|
||||
|
|
41
.github/ISSUE_TEMPLATE/tracking_issue.md
vendored
Normal file
41
.github/ISSUE_TEMPLATE/tracking_issue.md
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
name: 🔍 Tracking Issue
|
||||
about: Create an issue that tracks a wider effort
|
||||
title: 'Tracking Issue for ...'
|
||||
labels: ["C-tracking-issue"]
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- Consider keeping the following section in the issue. -->
|
||||
### About tracking issues
|
||||
Tracking issues are used to record the overall progress of implementation.
|
||||
They are also used as hubs connecting to other relevant issues, e.g., bugs or open design questions.
|
||||
A tracking issue is however not meant for large scale discussion, questions, or bug reports about a feature.
|
||||
Instead, open a dedicated issue for the specific matter.
|
||||
|
||||
### What
|
||||
This is a tracking issue for ...
|
||||
|
||||
### Steps
|
||||
<!--
|
||||
Describe the steps required to bring this effort to completion.
|
||||
|
||||
For larger features, more steps might be involved.
|
||||
If the feature is changed later, please add those PRs here as well.
|
||||
-->
|
||||
|
||||
- [ ] Initial implementation: #...
|
||||
- [ ] Other code: #...
|
||||
- [ ] Multi-PR effort
|
||||
- #...
|
||||
- #...
|
||||
- #...
|
||||
- [ ] Finalization PR: #...
|
||||
|
||||
### Related
|
||||
<!-- Link any related issues/PRs here. -->
|
||||
|
||||
- #...
|
||||
- #...
|
||||
- #...
|
35
.github/pull_request_template.md
vendored
Normal file
35
.github/pull_request_template.md
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
<!--
|
||||
PR titles should be:
|
||||
<AREA>: <SHORT_DESCRIPTION>
|
||||
|
||||
For example:
|
||||
books: fix typo
|
||||
-->
|
||||
|
||||
<!--
|
||||
If your pull request is long and/or has sections
|
||||
that need clarifying, consider leaving a review on
|
||||
your own PR with comments explaining the changes.
|
||||
-->
|
||||
|
||||
### What
|
||||
<!--
|
||||
If applicable, close a related issue with:
|
||||
|
||||
Fixes #<BUG_ISSUE_NUMBER>
|
||||
|
||||
...or...
|
||||
|
||||
Closes #<FEATURE_ISSUE_NUMBER>
|
||||
-->
|
||||
|
||||
<!-- Describe the pull request in detail. -->
|
||||
|
||||
### Why
|
||||
<!-- If applicable, describe why this pull request exists. -->
|
||||
|
||||
### Where
|
||||
<!-- If applicable, describe the places this pull request affects. -->
|
||||
|
||||
### How
|
||||
<!-- If applicable, describe how this pull request works. -->
|
168
CONTRIBUTING.md
168
CONTRIBUTING.md
|
@ -4,37 +4,87 @@ Thank you for wanting to help out!
|
|||
Cuprate is in the stage where things are likely to change quickly, so it's recommended
|
||||
you ask questions in our public [Matrix room](https://matrix.to/#/#cuprate:monero.social).
|
||||
|
||||
- [1. Submitting a pull request](#1-submitting-a-pull-request)
|
||||
- [1.1 Rust toolchain](#11-rust-toolchain)
|
||||
- [1.2 Draft PR](#12-draft-pr)
|
||||
- [1.3 Passing CI](#13-passing-ci)
|
||||
- [1.4 Ready for review](#14-ready-for-review)
|
||||
- [2. Crate names](#2-crate-names)
|
||||
- [3. Coding guidelines](#3-coding-guidelines)
|
||||
- [4. Keeping track of issues and PRs](#4-keeping-track-of-issues-and-prs)
|
||||
- [1. Submitting an issue](#1-submitting-an-issue)
|
||||
- [1.1 Discussion](#11-discussion)
|
||||
- [1.2 Proposal](#12-proposal)
|
||||
- [1.3 Tracking issue](#13-tracking-issue)
|
||||
- [2. Submitting a pull request](#2-submitting-a-pull-request)
|
||||
- [2.1 Rust toolchain](#21-rust-toolchain)
|
||||
- [2.2 Draft PR](#22-draft-pr)
|
||||
- [2.3 Passing CI](#23-passing-ci)
|
||||
- [2.4 Ready for review](#24-ready-for-review)
|
||||
- [3. Keeping track of issues and PRs](#3-keeping-track-of-issues-and-prs)
|
||||
- [3.1 Labels](#31-labels)
|
||||
- [3.2 Tracking issues](#32-tracking-issues)
|
||||
- [4. Coding guidelines](#4-coding-guidelines)
|
||||
- [4.1 General guidelines](#41-general-guidelines)
|
||||
- [4.2 Crate names](#42-crate-names)
|
||||
- [4.3 Pull request title and description](#43-pull-request-title-and-description)
|
||||
- [5. Documentation](#5-documentation)
|
||||
- [6. Books](#6-books)
|
||||
- [6.1 Architecture book](#61-architecture-book)
|
||||
- [6.2 Protocol book](#62-protocol-book)
|
||||
- [6.3 User book](#63-user-book)
|
||||
|
||||
## 1. Submitting a pull request
|
||||
Once you have found something you would like to work on by:
|
||||
## 1. Submitting an issue
|
||||
Before starting work, consider opening an issue for discussion.
|
||||
|
||||
If you have a plan already, you can jump straight into [submitting a pull request](#2-submitting-a-pull-request).
|
||||
|
||||
Otherwise, see below for issue types and what they're used for.
|
||||
|
||||
### 1.1 Discussion
|
||||
These are for general discussion on topics that have questions that aren't fully answered yet.
|
||||
|
||||
If you would like to discuss a topic and get some feedback, consider [opening a discussion](https://github.com/Cuprate/cuprate/issues/new/choose).
|
||||
|
||||
Examples:
|
||||
- https://github.com/Cuprate/cuprate/issues/40
|
||||
- https://github.com/Cuprate/cuprate/issues/53
|
||||
- https://github.com/Cuprate/cuprate/issues/163
|
||||
|
||||
### 1.2 Proposal
|
||||
These are formal issues that specify changes that are _almost_ ready for implementation.
|
||||
|
||||
These should answer some basic questions:
|
||||
- **What** is this proposal for?
|
||||
- **Why** is this proposal needed?
|
||||
- **Where** will this proposal make changes to?
|
||||
- **How** will this proposal be implemented?
|
||||
|
||||
If you have a close to fully fleshed out idea, consider [opening a proposal](https://github.com/Cuprate/cuprate/issues/new/choose).
|
||||
|
||||
Opening a PR and writing the proposal in the PR description is also viable.
|
||||
|
||||
Examples:
|
||||
- https://github.com/Cuprate/cuprate/pull/146
|
||||
- https://github.com/Cuprate/cuprate/issues/106
|
||||
- https://github.com/Cuprate/cuprate/issues/153
|
||||
- https://github.com/Cuprate/cuprate/issues/181
|
||||
|
||||
### 1.3 Tracking issue
|
||||
These are meta-issues that track an in-progress implementation.
|
||||
|
||||
See [`Tracking issues`](#32-tracking-issues) for more info.
|
||||
|
||||
## 2. Submitting a pull request
|
||||
Once you have found something you would like to work on after:
|
||||
- Discussing an idea on an [issue](#1-submitting-an-issue)
|
||||
- Looking at the [open issues](https://github.com/Cuprate/cuprate/issues)
|
||||
- Looking at issues with the [`A-help-wanted`](https://github.com/Cuprate/cuprate/issues?q=is%3Aissue+is%3Aopen+label%3AE-help-wanted) label
|
||||
- or joining Cuprate's [Matrix room](https://matrix.to/#/#cuprate:monero.social) and asking
|
||||
- Joining Cuprate's [Matrix room](https://matrix.to/#/#cuprate:monero.social) and asking
|
||||
|
||||
it is recommended to make your interest on working on that thing known so people don't duplicate work.
|
||||
|
||||
Before starting, consider reading/using Cuprate's:
|
||||
- [`Documentation`](#5-documentation) (practical `cargo` docs)
|
||||
- [`Books`](#6-books) (Cuprate's architecture and protocol)
|
||||
- [`Documentation`](#5-documentation)
|
||||
- [`Books`](#6-books)
|
||||
|
||||
These may answer some questions you have, or may confirm an issue you would like to fix.
|
||||
|
||||
_Note: Cuprate is currently a work-in-progress; documentation will be changing/unfinished._
|
||||
|
||||
### 1.1 Rust toolchain
|
||||
### 2.1 Rust toolchain
|
||||
Cuprate is written in [Rust](https://rust-lang.org).
|
||||
|
||||
If you are editing code, you will need Rust's toolchain and package manager,
|
||||
|
@ -42,12 +92,12 @@ If you are editing code, you will need Rust's toolchain and package manager,
|
|||
|
||||
Get started with Rust here: <https://www.rust-lang.org/learn/get-started>.
|
||||
|
||||
### 1.2 Draft PR
|
||||
### 2.2 Draft PR
|
||||
Consider opening a draft PR until you have passed all CI.
|
||||
|
||||
This is also the stage where you can ask for feedback from others. Keep in mind that feedback may take time especially if the change is large.
|
||||
|
||||
### 1.3 Passing CI
|
||||
### 2.3 Passing CI
|
||||
Each commit pushed in a PR will trigger our [lovely, yet pedantic CI](https://github.com/Cuprate/cuprate/blob/main/.github/workflows/ci.yml).
|
||||
|
||||
It currently:
|
||||
|
@ -57,7 +107,7 @@ It currently:
|
|||
- Runs [`clippy`](https://github.com/rust-lang/rust-clippy) (and fails on warnings)
|
||||
- Runs all tests
|
||||
- Builds all targets
|
||||
- Automatically add approriate [labels](#4-keeping-track-of-issues-and-prs) to your PR
|
||||
- Automatically adds approriate [labels](#31-labels) to your PR
|
||||
|
||||
Before pushing your code, please run the following at the root of the repository:
|
||||
|
||||
|
@ -79,45 +129,25 @@ After that, ensure all other CI passes by running:
|
|||
|
||||
**Note: in order for some tests to work, you will need to place a [`monerod`](https://www.getmonero.org/downloads/) binary at the root of the repository.**
|
||||
|
||||
### 1.4 Ready for review
|
||||
### 2.4 Ready for review
|
||||
Once your PR has passed all CI and is ready to go, open it for review. Others will leave their thoughts and may ask for changes to be made.
|
||||
|
||||
Finally, if everything looks good, we will merge your code! Thank you for contributing!
|
||||
|
||||
## 2. Crate names
|
||||
All of Cuprate's crates (libraries) are prefixed with `cuprate-`. All directories containing crates however, are not.
|
||||
## 3. Keeping track of issues and PRs
|
||||
The Cuprate GitHub repository has a lot of issues and PRs to keep track of.
|
||||
|
||||
For example:
|
||||
This section documents tools used to help with this.
|
||||
|
||||
| Crate Directory | Crate Name |
|
||||
|--------------------|--------------------|
|
||||
| `storage/database` | `cuprate-database` |
|
||||
| `net/levin` | `cuprate-levin` |
|
||||
| `net/wire` | `cuprate-wire` |
|
||||
|
||||
## 3. Coding guidelines
|
||||
This is a list of rules that are not mandated by any automation, but contributors generally follow.
|
||||
|
||||
You should keep these in mind when submitting code:
|
||||
|
||||
- Separate and sort imports as core, std, third-party, Cuprate crates, current crate
|
||||
- Follow the [Rust API Guidelines](https://rust-lang.github.io/api-guidelines)
|
||||
- `// Comment like this.` and not `//like this`
|
||||
- Use `TODO` instead of `FIXME`
|
||||
- Avoid `unsafe`
|
||||
|
||||
And the most important rule:
|
||||
- Break any and all of the above rules when it makes sense
|
||||
|
||||
## 4. Keeping track of issues and PRs
|
||||
The Cuprate GitHub repository has a lot of issues and PRs to keep track of. Cuprate makes use of generic labels and labels grouped by a prefixes to help with this.
|
||||
### 3.1 Labels
|
||||
Cuprate makes use of labels grouped by prefixes.
|
||||
|
||||
Some labels will be [automatically added/removed](https://github.com/Cuprate/cuprate/tree/main/.github/labeler.yml) if certain file paths have been changed in a PR.
|
||||
|
||||
The following section explains the meaning of various labels used.
|
||||
This section is primarily targeted at maintainers. Most contributors aren't able to set these labels.
|
||||
|
||||
| Labels | Description | Example |
|
||||
| Prefix | Description | Example |
|
||||
|--------------|-------------|---------|
|
||||
| [A-] | The **area** of the project an issue relates to. | `A-storage`, `A-rpc`, `A-docs`
|
||||
| [C-] | The **category** of an issue. | `C-cleanup`, `C-optimization`
|
||||
|
@ -135,6 +165,56 @@ This section is primarily targeted at maintainers. Most contributors aren't able
|
|||
[O-]: https://github.com/Cuprate/cuprate/labels?q=O
|
||||
[P-]: https://github.com/Cuprate/cuprate/labels?q=P
|
||||
|
||||
### 3.2 Tracking issues
|
||||
If you are working on a larger effort, consider opening a [tracking issue](https://github.com/Cuprate/cuprate/issues/new/choose)!
|
||||
|
||||
The main purpose of these are to track efforts that may contain multiple PRs and/or are generally spread out. These don't usually contain the "why", but if they do, they are brief. These contain no implementation details or the how, as those are for the issues/PRs that are being tracked.
|
||||
|
||||
Examples:
|
||||
- https://github.com/Cuprate/cuprate/issues/187
|
||||
- https://github.com/Cuprate/cuprate/issues/183
|
||||
|
||||
## 4. Coding guidelines
|
||||
These are some rules that are not mandated by any automation, but contributors generally follow.
|
||||
|
||||
### 4.1 General guidelines
|
||||
General guidelines you should keep these in mind when submitting code:
|
||||
|
||||
- Separate and sort imports as `core`, `std`, `third-party`, Cuprate crates, current crate
|
||||
- Follow the [Rust API Guidelines](https://rust-lang.github.io/api-guidelines)
|
||||
- `// Comment like this.` and not `//like this`
|
||||
- Use `TODO` instead of `FIXME`
|
||||
- Avoid `unsafe`
|
||||
|
||||
And the most important rule:
|
||||
- Break any and all of the above rules when it makes sense
|
||||
|
||||
### 4.2 Crate names
|
||||
All of Cuprate's crates (libraries) are prefixed with `cuprate-`. All directories containing crates however, are not.
|
||||
|
||||
For example:
|
||||
|
||||
| Crate Directory | Crate Name |
|
||||
|--------------------|--------------------|
|
||||
| `storage/database` | `cuprate-database` |
|
||||
| `net/levin` | `cuprate-levin` |
|
||||
| `net/wire` | `cuprate-wire` |
|
||||
|
||||
### 4.3 Pull request title and description
|
||||
In general, pull request titles should follow this syntax:
|
||||
```
|
||||
<AREA>: <SHORT_DESCRIPTION>
|
||||
```
|
||||
|
||||
For example:
|
||||
```
|
||||
books: fix typo
|
||||
```
|
||||
|
||||
The description of pull requests should generally follow the template laid out in [`.github/pull_request_template.md`](.github/pull_request_template.md).
|
||||
|
||||
If your pull request is long and/or has sections that need clarifying, consider leaving a review on your own PR with comments explaining the changes.
|
||||
|
||||
## 5. Documentation
|
||||
Cuprate's crates (libraries) have inline documentation.
|
||||
|
||||
|
|
698
Cargo.lock
generated
698
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
19
Cargo.toml
19
Cargo.toml
|
@ -10,21 +10,21 @@ members = [
|
|||
"net/epee-encoding",
|
||||
"net/fixed-bytes",
|
||||
"net/levin",
|
||||
"net/monero-wire",
|
||||
"p2p/cuprate-p2p",
|
||||
"p2p/dandelion",
|
||||
"p2p/monero-p2p",
|
||||
"net/wire",
|
||||
"p2p/p2p",
|
||||
"p2p/p2p-core",
|
||||
"p2p/dandelion-tower",
|
||||
"p2p/async-buffer",
|
||||
"p2p/address-book",
|
||||
"storage/cuprate-blockchain",
|
||||
"storage/cuprate-txpool",
|
||||
"storage/blockchain",
|
||||
"storage/txpool",
|
||||
"storage/database",
|
||||
"pruning",
|
||||
"test-utils",
|
||||
"types",
|
||||
"rpc/json-rpc",
|
||||
"rpc/monero-rpc-types",
|
||||
"rpc/cuprate-rpc-interface",
|
||||
"rpc/types",
|
||||
"rpc/interface",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
|
@ -56,7 +56,7 @@ clap = { version = "4.4.7", default-features = false }
|
|||
chrono = { version = "0.4.31", default-features = false }
|
||||
crypto-bigint = { version = "0.5.5", default-features = false }
|
||||
crossbeam = { version = "0.8.4", default-features = false }
|
||||
curve25519-dalek = { version = "4.1.1", default-features = false }
|
||||
curve25519-dalek = { version = "4.1.3", default-features = false }
|
||||
dalek-ff-group = { git = "https://github.com/Cuprate/serai.git", rev = "d27d934", default-features = false }
|
||||
dashmap = { version = "5.5.3", default-features = false }
|
||||
dirs = { version = "5.0.1", default-features = false }
|
||||
|
@ -89,6 +89,7 @@ tempfile = { version = "3" }
|
|||
pretty_assertions = { version = "1.4.0" }
|
||||
proptest = { version = "1" }
|
||||
proptest-derive = { version = "0.4.0" }
|
||||
tokio-test = { version = "0.4.4" }
|
||||
|
||||
## TODO:
|
||||
## Potential dependencies.
|
||||
|
|
14
README.md
14
README.md
|
@ -10,6 +10,7 @@ _(work-in-progress)_
|
|||
</div>
|
||||
|
||||
## Contents
|
||||
|
||||
- [About](#about)
|
||||
- [Documentation](#documentation)
|
||||
- [Contributing](#contributing)
|
||||
|
@ -27,13 +28,17 @@ TODO: add these sections someday.
|
|||
-->
|
||||
|
||||
## About
|
||||
Cuprate is an effort to create an alternative [Monero](https://getmonero.org) node implementation in [Rust](http://rust-lang.org).
|
||||
|
||||
It will be able to independently validate Monero consensus rules, providing a layer of security and redundancy for the Monero network.
|
||||
Cuprate is an effort to create an alternative [Monero](https://getmonero.org) node implementation
|
||||
in [Rust](http://rust-lang.org).
|
||||
|
||||
It will be able to independently validate Monero consensus rules, providing a layer of security and redundancy for the
|
||||
Monero network.
|
||||
|
||||
<!-- TODO: add some details about what Cuprate is and is not, goals, status -->
|
||||
|
||||
## Documentation
|
||||
|
||||
_Cuprate is currently a work-in-progress; documentation will be changing/unfinished._
|
||||
|
||||
Cuprate maintains various documentation books:
|
||||
|
@ -41,18 +46,21 @@ Cuprate maintains various documentation books:
|
|||
| Book | Description |
|
||||
|-----------------------------------------------------------------|------------------------------------------------------------|
|
||||
| [Cuprate's architecture book](https://architecture.cuprate.org) | Documents Cuprate's internal architecture & implementation |
|
||||
| [Cuprate's protocol book](https://monero-book.cuprate.org) | Documents the Monero protocol |
|
||||
| [Monero's protocol book](https://monero-book.cuprate.org) | Documents the Monero protocol |
|
||||
| [Cuprate's user book](https://user.cuprate.org) | Practical user-guide for using `cuprated` |
|
||||
|
||||
For crate (library) documentation, see the `Documentation` section in [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
See [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
||||
|
||||
## Security
|
||||
|
||||
Cuprate has a responsible vulnerability disclosure policy, see [`SECURITY.md`](SECURITY.md).
|
||||
|
||||
## License
|
||||
|
||||
The `binaries/` directory is licensed under AGPL-3.0, everything else is licensed under MIT.
|
||||
|
||||
See [`LICENSE`](LICENSE) for more details.
|
||||
|
|
|
@ -1,20 +1,26 @@
|
|||
## Books
|
||||
|
||||
This directory contains the source files for Cuprate's various books.
|
||||
|
||||
The source files are edited here, and published in other repositories, see:
|
||||
|
||||
- [Cuprate's architecture book](https://github.com/Cuprate/architecture-book)
|
||||
- [Cuprate's protocol book](https://github.com/Cuprate/monero-book)
|
||||
- [Monero's protocol book](https://github.com/Cuprate/monero-book)
|
||||
- [Cuprate's user book](https://github.com/Cuprate/user-book)
|
||||
|
||||
## Build tools
|
||||
Building the book(s) requires [Rust's cargo tool](https://doc.rust-lang.org/cargo/getting-started/installation.html) and [mdBook](https://github.com/rust-lang/mdBook).
|
||||
|
||||
Building the book(s) requires [Rust's cargo tool](https://doc.rust-lang.org/cargo/getting-started/installation.html)
|
||||
and [mdBook](https://github.com/rust-lang/mdBook).
|
||||
|
||||
After installing `cargo`, install `mdbook` with:
|
||||
|
||||
```bash
|
||||
cargo install mdbook
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To build a book, go into a book's directory and build:
|
||||
|
||||
```bash
|
||||
|
@ -23,7 +29,9 @@ cd user/
|
|||
mdbook build
|
||||
```
|
||||
|
||||
The output will be in the `book` subdirectory (`user/book` for the above example). To open the book, you can open it in your web browser like so:
|
||||
The output will be in the `book` subdirectory (`user/book` for the above example). To open the book, you can open it in
|
||||
your web browser like so:
|
||||
|
||||
```bash
|
||||
mdbook build --open
|
||||
```
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
## Cuprate's protocol book
|
||||
## Monero's protocol book
|
||||
|
||||
This book documents the Monero protocol.
|
||||
|
||||
See:
|
||||
|
||||
- <https://monero-book.cuprate.org>
|
||||
- <https://github.com/Cuprate/monero-book>
|
||||
|
|
22
books/protocol/book.toml
Normal file
22
books/protocol/book.toml
Normal file
|
@ -0,0 +1,22 @@
|
|||
[book]
|
||||
authors = ["Boog900"]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
title = "The Monero Book"
|
||||
|
||||
[build]
|
||||
create-missing = false
|
||||
|
||||
[output.html]
|
||||
mathjax-support = true
|
||||
additional-css = ["svgbob.css"]
|
||||
git-repository-url = "https://github.com/Cuprate/cuprate/tree/main/books/protocol"
|
||||
git-repository-icon = "fa-github"
|
||||
no-section-label = true
|
||||
|
||||
[output.html.fold]
|
||||
enable = true
|
||||
level = 2
|
||||
|
||||
[preprocessor.svgbob]
|
5
books/protocol/src/INTRO.md
Normal file
5
books/protocol/src/INTRO.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# ![Monero logo](https://www.getmonero.org/press-kit/logos/monero-logo-symbol-on-white-480.png)
|
||||
|
||||
This book aims to document the Monero protocol. Currently, work is being done to document Monero's consensus rules.
|
||||
|
||||
This being completed as a part of [Cuprate](https://github.com/Cuprate/cuprate), the Rust Monero node.
|
27
books/protocol/src/SUMMARY.md
Normal file
27
books/protocol/src/SUMMARY.md
Normal file
|
@ -0,0 +1,27 @@
|
|||
# Summary
|
||||
|
||||
[Introduction](./INTRO.md)
|
||||
|
||||
- [Consensus Rules](./consensus_rules.md)
|
||||
- [The Genesis Block](./consensus_rules/genesis_block.md)
|
||||
- [Hard Forks](./consensus_rules/hardforks.md)
|
||||
- [Blocks](./consensus_rules/blocks.md)
|
||||
- [Difficulty](./consensus_rules/blocks/difficulty.md)
|
||||
- [Weights](./consensus_rules/blocks/weights.md)
|
||||
- [Block Reward](./consensus_rules/blocks/reward.md)
|
||||
- [Miner Transactions](./consensus_rules/blocks/miner_tx.md)
|
||||
- [Transactions](./consensus_rules/transactions.md)
|
||||
- [Inputs](./consensus_rules/transactions/inputs.md)
|
||||
- [Outputs](./consensus_rules/transactions/outputs.md)
|
||||
- [Unlock Time](./consensus_rules/transactions/unlock_time.md)
|
||||
- [Ring Signatures](./consensus_rules/transactions/ring_signatures.md)
|
||||
- [Ring CT](./consensus_rules/transactions/ring_ct.md)
|
||||
- [Borromean](./consensus_rules/transactions/ring_ct/borromean.md)
|
||||
- [MLSAG](./consensus_rules/transactions/ring_ct/mlsag.md)
|
||||
- [Bulletproofs](./consensus_rules/transactions/ring_ct/bulletproofs.md)
|
||||
- [CLSAG](./consensus_rules/transactions/ring_ct/clsag.md)
|
||||
- [Bulletproofs+](./consensus_rules/transactions/ring_ct/bulletproofs+.md)
|
||||
- [P2P Network](./p2p_network.md)
|
||||
- [Levin Protocol](./p2p_network/levin.md)
|
||||
- [P2P Messages](./p2p_network/messages.md)
|
||||
- [Pruning](./pruning.md)
|
50
books/protocol/src/consensus_rules.md
Normal file
50
books/protocol/src/consensus_rules.md
Normal file
|
@ -0,0 +1,50 @@
|
|||
# Consensus Rules
|
||||
|
||||
This chapter contains all of Monero's consensus rules, from genesis to now. Some rules
|
||||
are complex so have been split into their own chapter.
|
||||
|
||||
Rules that are not bound to consensus (relay rules) are not included here. Also we have not documented "rules" which are enforced by
|
||||
(de)serialization, for example it's impossible to have a ringCT signature in a version 1 transaction, rules that are unclear if they
|
||||
can be omitted or not should _always_ be included.
|
||||
|
||||
## Index
|
||||
|
||||
1. [The Genesis Block](./consensus_rules/genesis_block.md)
|
||||
2. [Hard Forks](./consensus_rules/hardforks.md)
|
||||
3. [Blocks](./consensus_rules/blocks.md)
|
||||
4. [Transactions](./consensus_rules/transactions.md)
|
||||
|
||||
## Definitions
|
||||
|
||||
Canonically Encoded Scalar:
|
||||
an Ed25519 scalar which is fully reduced mod l, where \\(l = 2^{252} + 27742317777372353535851937790883648493 \\).
|
||||
|
||||
Canonically Encoded Point:
|
||||
an Ed25519 point which is not the negative identity and with y coordinate fully reduced mod p, where \\(p = 2^{255} - 19 \\).
|
||||
|
||||
Prime Order Point:
|
||||
a point in the prime subgroup.
|
||||
PoW Hash:
|
||||
the hash calculated by using the active proof of work function.
|
||||
|
||||
Block Hash:
|
||||
the keccak hash of the block.
|
||||
|
||||
Transaction Blob:
|
||||
the raw bytes of a serialized transaction.
|
||||
|
||||
Block Blob:
|
||||
the raw bytes of a serialized block.
|
||||
|
||||
Chain Height:
|
||||
the amount of blocks in the chain, this is different to the height of the top block as
|
||||
blocks start counting at 0.
|
||||
|
||||
Ring (transactions inputs):
|
||||
the group of potential outputs of which one is the true spend.
|
||||
|
||||
Decoys (transactions inputs):
|
||||
the fake outputs used to hide the true spend, the length of this is equal to one minus the `Rings` length.
|
||||
|
||||
MixIns (transactions inputs):
|
||||
another term for `Decoys`
|
111
books/protocol/src/consensus_rules/blocks.md
Normal file
111
books/protocol/src/consensus_rules/blocks.md
Normal file
|
@ -0,0 +1,111 @@
|
|||
# Blocks
|
||||
|
||||
## Introduction
|
||||
|
||||
This chapter contains all the rules that apply to a block. Miner transactions are included in this section as the rules that apply to them
|
||||
are different to normal transactions.
|
||||
|
||||
## Index
|
||||
|
||||
1. [Block Rules](./blocks.md#block-rules)
|
||||
2. [Difficulty](./blocks/difficulty.md)
|
||||
3. [Weights](./blocks/weights.md)
|
||||
4. [Block Reward](./blocks/reward.md)
|
||||
5. [Miner Transaction](./blocks/miner_tx.md)
|
||||
|
||||
## Block Rules
|
||||
|
||||
### Block Weight And Size
|
||||
|
||||
The `block blob` must not be bigger than (2 * the [effective median weight](./blocks/weights.md#effective-median-weight) + 100)[^block-size-check].
|
||||
|
||||
The [block weight](./blocks/weights.md#block-weights) must not be more than 2 *
|
||||
[the median weight for coinbase checks](./blocks/weights.md#median-weight-for-coinbase-checks)[^block-weight-limit].
|
||||
|
||||
### Amount Of Transactions
|
||||
|
||||
The amount of transactions in a block (including the miner transaction) must be less than `0x10000000`[^max-amount-of-txs].
|
||||
|
||||
### No Duplicate Transactions
|
||||
|
||||
There must be no duplicate transactions in the block or the blockchain[^no-duplicate-txs].
|
||||
|
||||
### Key Images
|
||||
|
||||
There must be no duplicate key images in the block[^no-duplicate-ki], or the whole chain.
|
||||
|
||||
### Previous ID
|
||||
|
||||
The blocks `prev_id` must equal the `block hash` of the last block[^prev_id].
|
||||
|
||||
### PoW Function
|
||||
|
||||
The proof of work function used depends on the hard-fork[^pow-func]:
|
||||
|
||||
| hard-fork | PoW function |
|
||||
|------------|----------------|
|
||||
| 1 to 6 | CryptoNight v0 |
|
||||
| 7 | CryptoNight v1 |
|
||||
| 8 to 9 | CryptoNight v2 |
|
||||
| 10 to 11 | CryptoNight R |
|
||||
| 12 onwards | RandomX |
|
||||
|
||||
> For block 202612 always return the same PoW hash, no matter the network[^202612-pow-hash].
|
||||
>
|
||||
> PoW hash: `84f64766475d51837ac9efbef1926486e58563c95a19fef4aec3254f03000000`
|
||||
|
||||
### Checking PoW Hash
|
||||
|
||||
See [checking PoW in the difficulty chapter](./blocks/difficulty.md#checking-a-blocks-proof-of-work).
|
||||
|
||||
### RandomX Seed
|
||||
|
||||
The RandomX seed, which is used to set up the dataset, is a previous block hash in the blockchain.
|
||||
|
||||
The seed height is 0 if the current height is below or equal to \\( 2048 + 64 \\) otherwise is got by:
|
||||
|
||||
\\( seedHeight = (height - 64 - 1) \land \lnot(2048 - 1) \\)
|
||||
|
||||
with \\( \land \\) being a bit-and and \\( \lnot \\) being a bit-not.
|
||||
|
||||
You then get the block hash at `seedHeight` which is then the RandomX seed.[^rx-seed]
|
||||
|
||||
### Version And Vote
|
||||
|
||||
The block's major version must equal to the current hard-fork and the vote must be greater than or equal to the current
|
||||
hard-fork[^version-vote].
|
||||
|
||||
> Vote is not always the same as the minor version, see [here](./hardforks.md#blocks-version-and-vote).
|
||||
|
||||
### Timestamp
|
||||
|
||||
The block's timestamp must not be more than the current UNIX time + 2 hours[^timestamp-upper-limit] and the timestamp
|
||||
must not be less than
|
||||
the median timestamp over the last 60 blocks[^timestamp-lower-limit], if there are less than 60 blocks in the chain then
|
||||
the timestamp is always valid.
|
||||
|
||||
---
|
||||
|
||||
[^block-size-check]: <https://github.com/monero-project/monero/blob/67d190ce7c33602b6a3b804f633ee1ddb7fbb4a1/src/cryptonote_core/cryptonote_core.cpp#L1684>
|
||||
|
||||
[^block-weight-limit]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L1418-L1428> && <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_basic/cryptonote_basic_impl.cpp#L107>
|
||||
|
||||
[^max-amount-of-txs]: <https://github.com/monero-project/monero/blob/67d190ce7c33602b6a3b804f633ee1ddb7fbb4a1/src/crypto/tree-hash.c#L55>
|
||||
|
||||
[^no-duplicate-txs]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L5267> && <https://github.com/monero-project/monero/blob/67d190ce7c33602b6a3b804f633ee1ddb7fbb4a1/src/cryptonote_core/blockchain.cpp#L4319>
|
||||
|
||||
[^no-duplicate-ki]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L5281>
|
||||
|
||||
[^prev_id]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L4150>
|
||||
|
||||
[^pow-func]: <https://github.com/monero-project/monero/blob/67d190ce7c33602b6a3b804f633ee1ddb7fbb4a1/src/cryptonote_core/cryptonote_tx_utils.cpp#L689-L704>
|
||||
|
||||
[^202612-pow-hash]: <https://github.com/monero-project/monero/blob/67d190ce7c33602b6a3b804f633ee1ddb7fbb4a1/src/cryptonote_core/cryptonote_tx_utils.cpp#L683>
|
||||
|
||||
[^rx-seed]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/crypto/rx-slow-hash.c#L179-L186>
|
||||
|
||||
[^version-vote]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_basic/hardfork.cpp#L109>
|
||||
|
||||
[^timestamp-upper-limit]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L4064>
|
||||
|
||||
[^timestamp-lower-limit]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L4045>
|
67
books/protocol/src/consensus_rules/blocks/difficulty.md
Normal file
67
books/protocol/src/consensus_rules/blocks/difficulty.md
Normal file
|
@ -0,0 +1,67 @@
|
|||
# Difficulty
|
||||
|
||||
Difficulty is a measure used to keep block production at a constant rate, it is the average amount of hashes before a solution
|
||||
is found.
|
||||
|
||||
## Checking A Block's Proof Of Work
|
||||
|
||||
To check a block's `PoW hash` you interpret the hash as a little endian integer and multiply it by the difficulty, if the result
|
||||
does not overflow the hash is valid[^check-pow]:
|
||||
|
||||
\\(Hash * difficulty <= MAXu256 \\)
|
||||
|
||||
## Calculating Difficulty
|
||||
|
||||
To calculate difficulty, Monero keeps a window of the last 735[^diff-blocks-count] timestamps and cumulative difficulties,
|
||||
if there are not enough blocks, then you just use as many as possible.
|
||||
|
||||
> The genesis block is skipped for these calculations[^skip-genesis] so should not be included in the timestamp / CD list but it is
|
||||
> included in the cumulative difficulty of the chain.
|
||||
|
||||
If the amount of blocks is less than or equal to 1 then 1 is returned as the difficulty[^amt-blocks-1].
|
||||
|
||||
The timestamps and cumulative difficulties are then shortened to 720[^diff-window] blocks so that calculations lag 15 blocks behind
|
||||
the chain.
|
||||
|
||||
The timestamps are then sorted, in ascending order. We now need to get a time span value to do this we first remove the outliers:
|
||||
|
||||
If the number of timestamps is less than or equal to the amount of blocks we are accounting for (600: 720 - 2 * 60) then the lower
|
||||
is set to 0 and the upper is set to the length of timestamps. Otherwise, if we have enough timestamps, the lower and upper is calculated
|
||||
by[^calculating-lower-upper]:
|
||||
|
||||
\\(lower = \frac{len(timestamps) - 600+1}{2} \\)
|
||||
|
||||
\\(upper = lower + 600 \\)
|
||||
|
||||
We then get the timestamp at position `lower` and take this away from the timestamp at position `upper -1` to get `timeSpan`.
|
||||
If `timeSpan` is 0 we set it to 1[^timespan0].
|
||||
|
||||
We also get the cumulative difficulty at position `lower` and take this away from the cumulative difficulty at position `upper -1` to get `totalWork`.
|
||||
|
||||
The next difficulty is then calculated by[^final-diff-cal]:
|
||||
|
||||
\\(difficulty = \frac{totalWork * targetSeconds + timeSpan -1}{timeSpan} \\)
|
||||
|
||||
## Target Seconds
|
||||
|
||||
For hard-fork v1 the target seconds is 60, so one block a minute. For hard-fork 2 onwards block time is 120[^target-block-time].
|
||||
|
||||
---
|
||||
|
||||
[^check-pow]: <https://github.com/monero-project/monero/blob/67d190ce7c33602b6a3b804f633ee1ddb7fbb4a1/src/cryptonote_basic/difficulty.cpp#L196>
|
||||
|
||||
[^diff-blocks-count]: <https://github.com/monero-project/monero/blob/67d190ce7c33602b6a3b804f633ee1ddb7fbb4a1/src/cryptonote_config.h#L84>
|
||||
|
||||
[^skip-genesis]: <https://github.com/monero-project/monero/blob/67d190ce7c33602b6a3b804f633ee1ddb7fbb4a1/src/cryptonote_core/blockchain.cpp#L849C40-L849C65>
|
||||
|
||||
[^amt-blocks-1]: <https://github.com/monero-project/monero/blob/67d190ce7c33602b6a3b804f633ee1ddb7fbb4a1/src/cryptonote_basic/difficulty.cpp#L214>
|
||||
|
||||
[^diff-window]: <https://github.com/monero-project/monero/blob/67d190ce7c33602b6a3b804f633ee1ddb7fbb4a1/src/cryptonote_config.h#L81> && <https://github.com/monero-project/monero/blob/67d190ce7c33602b6a3b804f633ee1ddb7fbb4a1/src/cryptonote_basic/difficulty.cpp#L205>
|
||||
|
||||
[^calculating-lower-upper]: <https://github.com/monero-project/monero/blob/67d190ce7c33602b6a3b804f633ee1ddb7fbb4a1/src/cryptonote_basic/difficulty.cpp#L222>
|
||||
|
||||
[^timespan0]: <https://github.com/monero-project/monero/blob/67d190ce7c33602b6a3b804f633ee1ddb7fbb4a1/src/cryptonote_basic/difficulty.cpp#L231>
|
||||
|
||||
[^final-diff-cal]: <https://github.com/monero-project/monero/blob/67d190ce7c33602b6a3b804f633ee1ddb7fbb4a1/src/cryptonote_basic/difficulty.cpp#L236>
|
||||
|
||||
[^target-block-time]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L5512>
|
92
books/protocol/src/consensus_rules/blocks/miner_tx.md
Normal file
92
books/protocol/src/consensus_rules/blocks/miner_tx.md
Normal file
|
@ -0,0 +1,92 @@
|
|||
# Miner Transaction Rules
|
||||
|
||||
## Introduction
|
||||
|
||||
Miner transactions are handled differently to normal transactions, see [here](../transactions.md) for the rules on normal transactions.
|
||||
|
||||
## Rules
|
||||
|
||||
### Version
|
||||
|
||||
The transactions version must be either 1 or 2[^versions-allowed].
|
||||
|
||||
The version can be 1 or 2 up to hard-fork 12 then it must be 2[^weird-version-rules].
|
||||
|
||||
### Input
|
||||
|
||||
The transaction must only have one input and it must be of type `txin_gen`[^input-type].
|
||||
|
||||
The height specified in the input must be the actual block height[^input-height].
|
||||
|
||||
### RingCT Type
|
||||
|
||||
From hard-fork 12 version 2 miner transactions must have a ringCT type of `Null`[^null-ringct].
|
||||
|
||||
### Unlock Time
|
||||
|
||||
The unlock time must be the current height + 60[^miner-unlock-time].
|
||||
|
||||
### Output Amounts
|
||||
|
||||
The output, when summed, must not overflow[^outputs-overflow].
|
||||
|
||||
For _only_ hard-fork 3 the output amount must be a valid decomposed amount[^decomposed-amount], which means the amount must be
|
||||
in [this](https://github.com/monero-project/monero/blob/67d190ce7c33602b6a3b804f633ee1ddb7fbb4a1/src/cryptonote_basic/cryptonote_format_utils.cpp#L52) table.
|
||||
|
||||
### Total Outputs
|
||||
|
||||
The [reward from the block](./reward.md#calculating-block-reward) + the total fees must not be more than the summed output amount[^total-output-amount].
|
||||
|
||||
For hard-fork 1 and from 12 onwards the summed output amount must equal the reward + fees[^exact-output-amount] this means from 2 till 11 miners can collect
|
||||
less if they want less dust.
|
||||
|
||||
### Output Type
|
||||
|
||||
The output type allowed depends on the hard-fork[^output-types]:
|
||||
|
||||
| hard-fork | output type |
|
||||
| ---------- | ------------------------------------ |
|
||||
| 1 to 14 | txout_to_key |
|
||||
| 15 | txout_to_key and txout_to_tagged_key |
|
||||
| 16 onwards | txout_to_tagged_key |
|
||||
|
||||
> For hard-fork 15 both are allowed but the transactions outputs must all be the same type.
|
||||
|
||||
### Zero Amount V1 Output
|
||||
|
||||
Monero does not explicitly ban zero amount V1 outputs on miner transactions but the database throws an error if a 0 amount output doesn't have a commitment
|
||||
[^zero-output] meaning they are banned.
|
||||
|
||||
### V2 Output Pool
|
||||
|
||||
When adding version 2 miner transactions to the blockchain, put the outputs into the 0 amount pool and create dummy commitments of:[^v2-output]
|
||||
|
||||
\\(commitment = G + amount * H \\)
|
||||
|
||||
---
|
||||
|
||||
[^versions-allowed]: <https://github.com/monero-project/monero/blob/67d190ce7c33602b6a3b804f633ee1ddb7fbb4a1/src/cryptonote_basic/cryptonote_basic.h#L185>
|
||||
|
||||
[^weird-version-rules]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L1371>
|
||||
|
||||
[^input-type]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L1369-L1370>
|
||||
|
||||
[^input-height]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L1379>
|
||||
|
||||
[^null-ringct]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L1374>
|
||||
|
||||
[^miner-unlock-time]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L1385>
|
||||
|
||||
[^outputs-overflow]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L1388>
|
||||
|
||||
[^decomposed-amount]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L1409>
|
||||
|
||||
[^total-output-amount]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L1434>
|
||||
|
||||
[^exact-output-amount]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L1440-L1447>
|
||||
|
||||
[^output-types]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_basic/cryptonote_format_utils.cpp#L960>
|
||||
|
||||
[^zero-output]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/blockchain_db/lmdb/db_lmdb.cpp#L1069>
|
||||
|
||||
[^v2-output]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/blockchain_db/blockchain_db.cpp#L234-L241>
|
45
books/protocol/src/consensus_rules/blocks/reward.md
Normal file
45
books/protocol/src/consensus_rules/blocks/reward.md
Normal file
|
@ -0,0 +1,45 @@
|
|||
# Block Reward
|
||||
|
||||
The block reward is the amount paid out to a miner for mining a block.
|
||||
|
||||
## Calculating Base Block Reward
|
||||
|
||||
The base block reward is the reward before factoring in the potential penalty for expanding blocks.
|
||||
|
||||
To calculate the base block reward you first need the total amount of coins already generated, then define:
|
||||
|
||||
[^money-supply] \\(moneySupply = 2^{64} -1 \\)
|
||||
|
||||
[^emission-speed-factor] \\(emissionSpeedFactor = 20 - (targetMinutes - 1) \\)
|
||||
|
||||
where `targetMinutes` is the [target block time](./difficulty.md#target-seconds) in minutes.
|
||||
|
||||
The `baseReward` is then calculated by:
|
||||
|
||||
[^base-reward] \\(baseReward = (moneySupply - alreadyGeneratedCoins) >> emissionSpeedFactor \\)
|
||||
|
||||
If `baseReward` falls below the final subsidy (0.3 XMR / minute) them set the `baseReward` to that instead [^final-base-reward].
|
||||
|
||||
## Calculating Block Reward
|
||||
|
||||
First calculate the [base block reward](#calculating-base-block-reward).
|
||||
|
||||
Now we need to get the [median weight for block rewards](weights.md#median-weight-for-coinbase-checks)
|
||||
|
||||
If the current block weight is not more than the median weight then the block reward is the base reward.
|
||||
|
||||
Otherwise the block reward is:[^block-reward]
|
||||
|
||||
\\(blockReward = baseReward * (1 - (\frac{blockWeight}{effectiveMedianWeight} -1)^2) \\)
|
||||
|
||||
---
|
||||
|
||||
[^money-supply]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_config.h#L53>
|
||||
|
||||
[^emission-speed-factor]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_basic/cryptonote_basic_impl.cpp#L87>
|
||||
|
||||
[^base-reward]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_basic/cryptonote_basic_impl.cpp#L89>
|
||||
|
||||
[^final-base-reward]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_basic/cryptonote_basic_impl.cpp#L90-L93>
|
||||
|
||||
[^block-reward]: <https://web.getmonero.org/library/Zero-to-Monero-2-0-0.pdf#subsection.7.3.3> && <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_basic/cryptonote_basic_impl.cpp#L111-L127>
|
113
books/protocol/src/consensus_rules/blocks/weights.md
Normal file
113
books/protocol/src/consensus_rules/blocks/weights.md
Normal file
|
@ -0,0 +1,113 @@
|
|||
# Block Weights
|
||||
|
||||
Monero's blockchain, unlike other blockchains, has dynamic block sizes which means blocks expand to handle demand.
|
||||
However Monero does not allow unrestricted block growth, miners will face a penalty for expanding blocks and miners
|
||||
are restricted by how much they can expand a block.
|
||||
|
||||
## Index
|
||||
|
||||
1. [Penalty Free Zone](weights.md#penalty-free-zone)
|
||||
2. [Blocks Weight](#blocks-weight)
|
||||
3. [Long Term Block Weight](#long-term-block-weight)
|
||||
4. [Effective Median Weight](#effective-median-weight)
|
||||
5. [Median Weight For Coinbase Checks](#median-weight-for-coinbase-checks)
|
||||
|
||||
## Penalty Free Zone
|
||||
|
||||
Monero sets a minimum max block weight so that miners don't get punished for expanding small blocks.
|
||||
|
||||
For hf 1 this is 20000 bytes, for hf 2-4 this is 60000 and from 5 onwards this is 300000 bytes[^minimum-max-weight].
|
||||
|
||||
## Blocks Weight
|
||||
|
||||
A blocks weight is the sum of all the transactions weights in a block, including the miner transaction. The block header
|
||||
and transaction hashes are not included[^calculating-bw].
|
||||
|
||||
## Long Term Block Weight
|
||||
|
||||
The block's long term weight is the block's weight adjusted with previous block's weights.
|
||||
|
||||
### Calculating A Blocks Long Term Weight
|
||||
|
||||
Up until hard-fork 10, the blocks long term weight is just the block's weight[^pre-hf-10-long-weight].
|
||||
|
||||
From hard-fork 10 onwards we first get the median long term weight over the last 100,000 blocks, if this is less than
|
||||
the [penalty free zone](#penalty-free-zone) then set the median long term weight to this instead[^ltw-median].
|
||||
|
||||
Now we need to set a shot term constraint and adjusted block weight, the way we do this is different depending on the hard-fork.
|
||||
|
||||
From hard-fork 10 to 14[^hf-10-14-stc]:
|
||||
|
||||
\\(adjustedBlockWeight = blockWeight\\)
|
||||
|
||||
\\(shortTermConstraint = medianLongTermWeight * 1.4\\)
|
||||
|
||||
From 15 onwards[^hf-15-adjustments]:
|
||||
|
||||
\\(adjustedBlockWeight = max(blockWeight, \frac{medianLongTermWeight}{1.7})\\)
|
||||
|
||||
\\(shortTermConstraint = medianLongTermWeight * 1.7\\)
|
||||
|
||||
Now the long term weight is defined as `min(adjustedBlockWeight, shortTermConstraint)`[^long-term-weight].
|
||||
|
||||
## Effective Median Weight
|
||||
|
||||
The effective median weight is used to calculate block reward and to limit block size.
|
||||
|
||||
### Calculating Effective Median Weight
|
||||
|
||||
For any hard-fork the minimum this can be is the [penalty free zone](#penalty-free-zone)[^minimum-effective-median].
|
||||
|
||||
Up until hard-fork 10, this is done by just getting the median **block weight** over the last 100 blocks[^pre-hf-10-effective-median], if
|
||||
there are less than 100 blocks just get the median over all the blocks.
|
||||
|
||||
For hf 10 onwards, we first get the median **long term weight** over the last 100,000 blocks[^hf-10+-effective-median-step-1], if this median
|
||||
is less than the hf 5 [penalty free zone](#penalty-free-zone) set the median to that, this is the long term median.
|
||||
|
||||
Now get the median **block weight** over the last 100 blocks, this is the short term median.
|
||||
|
||||
Now we can calculate the effective median, for hard-forks 10 to 14 this is done by[^effective-median]:
|
||||
|
||||
\\(effectiveMedian = min(max(hf5PenaltyFreeZone, shortTermMedian), 50 * longTermMedian) \\)
|
||||
|
||||
From 15 onwards this is done by:
|
||||
|
||||
\\(effectiveMedian = min(max(longTermMedian, shortTermMedian), 50 * longTermMedian) \\)
|
||||
|
||||
## Median Weight For Coinbase Checks
|
||||
|
||||
When checking coinbase transactions and block weight Monero uses yet another median weight :).
|
||||
|
||||
### Calculating Median Weight For Coinbase Checks
|
||||
|
||||
Before hf 12 this is the median block weight over the last 100 blocks[^median-weight-coinbase-before-v12].
|
||||
|
||||
From hf 12 this is the [effective median weight](#effective-median-weight)[^median-weight-coinbase-from-v12]
|
||||
|
||||
---
|
||||
|
||||
[^minimum-max-weight]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_basic/cryptonote_basic_impl.cpp#L69>
|
||||
|
||||
[^calculating-bw]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L4289> and <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L4408>
|
||||
|
||||
[^pre-hf-10-long-weight]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L4577>
|
||||
|
||||
[^ltw-median]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L4581>
|
||||
|
||||
[^hf-10-14-stc]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L4593>
|
||||
|
||||
[^hf-15-adjustments]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L4587>
|
||||
|
||||
[^long-term-weight]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L4595>
|
||||
|
||||
[^minimum-effective-median]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L4676>
|
||||
|
||||
[^pre-hf-10-effective-median]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L4611>
|
||||
|
||||
[^hf-10+-effective-median-step-1]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L4651>
|
||||
|
||||
[^effective-median]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L4659-L4671>
|
||||
|
||||
[^median-weight-coinbase-before-v12]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L1425-L1427>
|
||||
|
||||
[^median-weight-coinbase-from-v12]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L1421>
|
92
books/protocol/src/consensus_rules/genesis_block.md
Normal file
92
books/protocol/src/consensus_rules/genesis_block.md
Normal file
|
@ -0,0 +1,92 @@
|
|||
# Genesis
|
||||
|
||||
Monero has a hardcoded genesis block that gets added to the blockchain on the first run of the daemon[^first-run]. The contents of this block
|
||||
are different depending on the network.
|
||||
|
||||
For all networks the timestamp is set to 0, the major and minor version of the block are set to `CURRENT_BLOCK_MAJOR_VERSION` and
|
||||
`CURRENT_BLOCK_MINOR_VERSION`[^version-set]. These two constants are set to 1 and 0 respectively[^version-defined]. The transaction
|
||||
field is empty, and the previous block hash is not set so that field is zeroed.
|
||||
|
||||
## Mainnet
|
||||
|
||||
The nonce is set to 10,000 and the miner transaction is set to:
|
||||
`013c01ff0001ffffffffffff03029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd08807121017767aafcde9be00dcfd098715ebcf7f410daebc582fda69d24a28e9d0bc890d1`
|
||||
[^mainnet-params]
|
||||
|
||||
The mainnet genesis block will hash to: `418015bb9ae982a1975da7d79277c2705727a56894ba0fb246adaabb1f4632e3`.
|
||||
|
||||
The final block:
|
||||
|
||||
```json
|
||||
{
|
||||
header: {
|
||||
major_version: 1,
|
||||
minor_version: 0,
|
||||
timestamp: 0,
|
||||
previous: [0; 32],
|
||||
nonce: 10000
|
||||
},
|
||||
miner_tx: "013c01ff0001ffffffffffff03029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd08807121017767aafcde9be00dcfd098715ebcf7f410daebc582fda69d24a28e9d0bc890d1",
|
||||
txs: [],
|
||||
}
|
||||
```
|
||||
|
||||
## Testnet
|
||||
|
||||
The nonce is set to 10,001 and the miner transaction is set to the same as mainnet[^testnet-params]
|
||||
|
||||
The testnet genesis block will hash to `48ca7cd3c8de5b6a4d53d2861fbdaedca141553559f9be9520068053cda8430b`.
|
||||
|
||||
The final block:
|
||||
|
||||
```json
|
||||
{
|
||||
header: {
|
||||
major_version: 1,
|
||||
minor_version: 0,
|
||||
timestamp: 0,
|
||||
previous: [0; 32],
|
||||
nonce: 10001
|
||||
},
|
||||
miner_tx: "013c01ff0001ffffffffffff03029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd08807121017767aafcde9be00dcfd098715ebcf7f410daebc582fda69d24a28e9d0bc890d1",
|
||||
txs: [],
|
||||
}
|
||||
```
|
||||
|
||||
## Stagenet
|
||||
|
||||
The nonce is set to 10,002 and the miner transaction is set to:
|
||||
`013c01ff0001ffffffffffff0302df5d56da0c7d643ddd1ce61901c7bdc5fb1738bfe39fbe69c28a3a7032729c0f2101168d0c4ca86fb55a4cf6a36d31431be1c53a3bd7411bb24e8832410289fa6f3b`
|
||||
[^stagenet-params].
|
||||
|
||||
The stagenet genesis block will hash to `76ee3cc98646292206cd3e86f74d88b4dcc1d937088645e9b0cbca84b7ce74eb`.
|
||||
|
||||
The final block:
|
||||
|
||||
```json
|
||||
{
|
||||
header: {
|
||||
major_version: 1,
|
||||
minor_version: 0,
|
||||
timestamp: 0,
|
||||
previous: [0; 32],
|
||||
nonce: 10002
|
||||
},
|
||||
miner_tx: "013c01ff0001ffffffffffff0302df5d56da0c7d643ddd1ce61901c7bdc5fb1738bfe39fbe69c28a3a7032729c0f2101168d0c4ca86fb55a4cf6a36d31431be1c53a3bd7411bb24e8832410289fa6f3b",
|
||||
txs: [],
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
[^first-run]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L340>
|
||||
|
||||
[^version-set]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/cryptonote_tx_utils.cpp#L663-L665>
|
||||
|
||||
[^version-defined]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_config.h#L45-L46>
|
||||
|
||||
[^mainnet-params]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_config.h#L231-L232>
|
||||
|
||||
[^testnet-params]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_config.h#L272-L273>
|
||||
|
||||
[^stagenet-params]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_config.h#L287-L288>
|
114
books/protocol/src/consensus_rules/hardforks.md
Normal file
114
books/protocol/src/consensus_rules/hardforks.md
Normal file
|
@ -0,0 +1,114 @@
|
|||
# Hard Forks
|
||||
|
||||
Monero makes use of hard-forks to update its protocol. Although it has never been used, Monero has a system in it's codebase to
|
||||
allow voting for activation of a hard-fork[^hardfork-class]. It works by using the blocks `minor version` field as a voting field,
|
||||
when enough blocks vote for a hard fork the fork is activated.
|
||||
|
||||
Because Monero has never used hard fork voting, you don't _need_ to implement it but as it's included in the codebase, an explanation
|
||||
is included here.
|
||||
|
||||
## Blocks version and vote
|
||||
|
||||
Monero uses the block's `major version` field as an indicator of hard-fork and the `minor version` field as an indicator of the blocks
|
||||
vote. A minor version of 0 is treated as a vote for 1 as legacy blocks use to just set this field to 0[^minor-v-0].
|
||||
|
||||
The block's vote must be greater than or equal to the version, a vote higher than the maximum known hard-fork is interpreted
|
||||
as a vote for the latest hard-fork[^minor-v-too-large]. So if a block is at V2 then the vote must be V2 or higher.
|
||||
|
||||
## Accepting a fork
|
||||
|
||||
When a hard-fork is added to Monero's protocol it must specify a `threshold`, a number between 0 and 100, this is the proportion of
|
||||
blocks in the window that must vote for this fork (or a later one) for it to activate. For all current forks the threshold is 0 meaning
|
||||
that no votes are needed for the fork to activate.
|
||||
|
||||
Monero keeps track of a week (10,080 blocks) worth of votes[^window-size], when a new block is added Monero works backwards through the
|
||||
list of hard-forks (latest to oldest) tallying the votes and checking if the number of votes is bigger than the amount needed[^accepting-hfs],
|
||||
votes for later hardforks are also votes for previous hard-forks. The amount needed is calculated:
|
||||
|
||||
\\( amountNeeded = \frac{windowSize * threshold + 99}{100} \\)
|
||||
|
||||
If the amount of votes is greater than or equal to the amount needed and the current blockchain height is greater than or equal to the hard-fork
|
||||
height the HF is activated[^accepting-hfs].
|
||||
|
||||
## Mainnet Hard-Forks [^mainnet-hfs] {#Mainnet-Hard-Forks}
|
||||
|
||||
| Version | Height | Threshold | Finalized (timestamp) |
|
||||
| ------- | ----------- | --------- | ------------------------ |
|
||||
| 1 | 0[^v1-at-0] | 0 | Jul 04 2012 (1341378000) |
|
||||
| 2 | 1009827 | 0 | Sep 20 2015 (1442763710) |
|
||||
| 3 | 1141317 | 0 | Mar 21 2016 (1458558528) |
|
||||
| 4 | 1220516 | 0 | Jan 05 2017 (1483574400) |
|
||||
| 5 | 1288616 | 0 | Mar 14 2017 (1489520158) |
|
||||
| 6 | 1400000 | 0 | Aug 18 2017 (1503046577) |
|
||||
| 7 | 1546000 | 0 | Mar 17 2018 (1521303150) |
|
||||
| 8 | 1685555 | 0 | Sep 02 2018 (1535889547) |
|
||||
| 9 | 1686275 | 0 | Sep 02 2018 (1535889548) |
|
||||
| 10 | 1788000 | 0 | Feb 10 2019 (1549792439) |
|
||||
| 11 | 1788720 | 0 | Feb 15 2019 (1550225678) |
|
||||
| 12 | 1978433 | 0 | Oct 18 2019 (1571419280) |
|
||||
| 13 | 2210000 | 0 | Aug 23 2020 (1598180817) |
|
||||
| 14 | 2210720 | 0 | Aug 24 2020 (1598180818) |
|
||||
| 15 | 2688888 | 0 | Jun 30 2022 (1656629117) |
|
||||
| 16 | 2689608 | 0 | Jun 30 2022 (1656629118) |
|
||||
|
||||
## Testnet Hard-Forks [^testnet-hfs] {#Testnet-Hard-Forks}
|
||||
|
||||
| Version | Height | Threshold | Finalized (timestamp) |
|
||||
| ------- | ----------- | --------- | ------------------------------------ |
|
||||
| 1 | 0[^v1-at-0] | 0 | Jul 04 2012 (1341378000) |
|
||||
| 2 | 624634 | 0 | Oct 20 2015 (1445355000) |
|
||||
| 3 | 800500 | 0 | Aug 28 2016 (1472415034) |
|
||||
| 4 | 801219 | 0 | Aug 28 2016 (1472415035) |
|
||||
| 5 | 802660 | 0 | Aug 28 2016 (1472415036 + 86400*180) |
|
||||
| 6 | 971400 | 0 | Aug 02 2017 (1501709789) |
|
||||
| 7 | 1057027 | 0 | Dec 02 2017 (1512211236) |
|
||||
| 8 | 1057058 | 0 | Aug 02 2018 (1533211200) |
|
||||
| 9 | 1057778 | 0 | Aug 03 2018 (1533297600) |
|
||||
| 10 | 1154318 | 0 | Feb 14 2019 (1550153694) |
|
||||
| 11 | 1155038 | 0 | Feb 15 2019 (1550225678) |
|
||||
| 12 | 1308737 | 0 | Sep 27 2019 (1569582000) |
|
||||
| 13 | 1543939 | 0 | Sep 02 2020 (1599069376) |
|
||||
| 14 | 1544659 | 0 | Sep 02 2020 (1599069377) |
|
||||
| 15 | 1982800 | 0 | May 16 2022 (1652727000) |
|
||||
| 16 | 1983520 | 0 | May 17 2022 (1652813400) |
|
||||
|
||||
## Stagenet Hard-Forks [^stagenet-hfs] {#Stagenet-Hard-Forks}
|
||||
|
||||
| Version | Height | Threshold | Finalized (timestamp) |
|
||||
| ------- | ----------- | --------- | ------------------------ |
|
||||
| 1 | 0[^v1-at-0] | 0 | Jul 04 2012 (1341378000) |
|
||||
| 2 | 32000 | 0 | Mar 14 2018 (1521000000) |
|
||||
| 3 | 33000 | 0 | Mar 15 2018 (1521120000) |
|
||||
| 4 | 34000 | 0 | Mar 16 2018 (1521240000) |
|
||||
| 5 | 35000 | 0 | Mar 18 2018 (1521360000) |
|
||||
| 6 | 36000 | 0 | Mar 19 2018 (1521480000) |
|
||||
| 7 | 37000 | 0 | Mar 21 2018 (1521600000) |
|
||||
| 8 | 176456 | 0 | Sep 24 2018 (1537821770) |
|
||||
| 9 | 177176 | 0 | Sep 24 2018 (1537821771) |
|
||||
| 10 | 269000 | 0 | Feb 14 2019 (1550153694) |
|
||||
| 11 | 269720 | 0 | Feb 15 2019 (1550225678) |
|
||||
| 12 | 454721 | 0 | Oct 18 2019 (1571419280) |
|
||||
| 13 | 675405 | 0 | Aug 23 2020 (1598180817) |
|
||||
| 14 | 676125 | 0 | Aug 23 2020 (1598180818) |
|
||||
| 15 | 1151000 | 0 | Jun 30 2022 (1656629117) |
|
||||
| 16 | 1151720 | 0 | Jun 30 2022 (1656629118) |
|
||||
|
||||
---
|
||||
|
||||
[^hardfork-class]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_basic/hardfork.h>
|
||||
|
||||
[^minor-v-0]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_basic/hardfork.cpp#L47>
|
||||
|
||||
[^minor-v-too-large]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_basic/hardfork.cpp#L99>
|
||||
|
||||
[^window-size]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_basic/hardfork.h#L51>
|
||||
|
||||
[^accepting-hfs]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_basic/hardfork.cpp#L311>
|
||||
|
||||
[^mainnet-hfs]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/hardforks/hardforks.cpp#L34>
|
||||
|
||||
[^v1-at-0]: Monero C++ sets this to 1 even though the [genesis block](genesis_block.md) has a major version of 1.
|
||||
|
||||
[^testnet-hfs]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/hardforks/hardforks.cpp#L80>
|
||||
|
||||
[^stagenet-hfs]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/hardforks/hardforks.cpp#L107>
|
89
books/protocol/src/consensus_rules/transactions.md
Normal file
89
books/protocol/src/consensus_rules/transactions.md
Normal file
|
@ -0,0 +1,89 @@
|
|||
# Transaction Rules
|
||||
|
||||
## Introduction
|
||||
|
||||
This chapter does not include miner, coinbase, transactions as they are handled elsewhere, the rules for them are under [blocks](blocks.md)
|
||||
|
||||
## Index
|
||||
|
||||
1. [Miscellaneous Rules](#miscellaneous-rules)
|
||||
2. [Input Rules](./transactions/inputs.md)
|
||||
3. [Output Rules](./transactions/outputs.md)
|
||||
4. [Unlock Time](./transactions/unlock_time.md)
|
||||
5. [Ring Signatures](./transactions/ring_signatures.md)
|
||||
6. [RingCT](./transactions/ring_ct.md)
|
||||
|
||||
## Miscellaneous Rules
|
||||
|
||||
### Version
|
||||
|
||||
Version 0 is never allowed[^tx-v0].
|
||||
|
||||
The max transaction version is 1 up to hard fork 4 then the max is 2[^max-tx-version].
|
||||
|
||||
The minimum tx version is 1 up until version 6 then if the [number of un-mixable inputs](#minimum-decoys)
|
||||
is 0 the minimum is 2 otherwise 1[^min-tx-version] so a version 1 transaction is allowed if the amount
|
||||
it's spending does not have enough outputs with the same amount to mix with.
|
||||
|
||||
### Transaction Size
|
||||
|
||||
The size of the `transaction blob` must not be bigger than 1 million bytes[^tx-size-limit].
|
||||
|
||||
From v8, the transaction's _weight_ must not be bigger than half of the [block penalty free zone](./blocks/weights.md#penalty-free-zone) minus 600[^tx-weight_limit].
|
||||
|
||||
#### Calculating Transaction Weight
|
||||
|
||||
For all transactions that don't use bulletproofs or bulletproofs+ the weight is just the length of the transaction blob.[^weight-pre-bp]
|
||||
|
||||
For bulletproofs(+) transactions we add a "clawback" onto the transaction.
|
||||
|
||||
To calculate the "clawback" we fist define a `bpBase` which is the size of a 2 output proof, normalized to 1 proof by dividing by 2[^bp-base]:
|
||||
|
||||
for bulletproofs: \\(fields = 9\\)
|
||||
|
||||
for bulletproofs+: \\(fields = 6\\)
|
||||
|
||||
\\(bpBase = \frac{(32 * (fields + 7 * 2))}{2}\\)
|
||||
|
||||
Next we calculate the size of the bulletproofs(+) field by first getting the first power of 2 above or equal to the number of outputs: `firstPower2AboveNumbOuts`.
|
||||
|
||||
If `firstPower2AboveNumbOuts` is <= 2 then the \\(clawback = 0\\)[^fp2-less-than-2].
|
||||
|
||||
Next define the number of L and R elements[^lr-elements]: \\(nlr = firstPower2AboveNumbOuts + 6\\)
|
||||
|
||||
now the size of the bulletproofs(+) field is[^bp+-size]:
|
||||
|
||||
\\(bpSize = 32 * (fields + 2 * nlr)\\)
|
||||
|
||||
now the `clawback` is[^clawback]:
|
||||
|
||||
\\( clawback = \frac{(bpBase * firstPower2AboveNumbOuts - bpSize) * 4}{ 5} \\)
|
||||
|
||||
To get the transaction weight now you just get the length of the transaction blob and add this `clawback`[^bp-tx-weight].
|
||||
|
||||
---
|
||||
|
||||
[^tx-v0]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/tx_pool.cpp#L152>
|
||||
|
||||
[^max-tx-version]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L3418>
|
||||
|
||||
[^min-tx-version]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L3425>
|
||||
|
||||
[^tx-size-limit]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/cryptonote_core.cpp#L791>
|
||||
and <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_basic/cryptonote_basic_impl.cpp#L78>
|
||||
|
||||
[^tx-weight_limit]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/tx_pool.cpp#L117> && <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/tx_pool.cpp#L221>
|
||||
|
||||
[^weight-pre-bp]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/cryptonote_basic/cryptonote_format_utils.cpp#L447-L453>
|
||||
|
||||
[^bp-base]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/cryptonote_basic/cryptonote_format_utils.cpp#L110C40-L110C40>
|
||||
|
||||
[^fp2-less-than-2]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/cryptonote_basic/cryptonote_format_utils.cpp#L112>
|
||||
|
||||
[^lr-elements]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/cryptonote_basic/cryptonote_format_utils.cpp#L117>
|
||||
|
||||
[^bp+-size]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/cryptonote_basic/cryptonote_format_utils.cpp#L118>
|
||||
|
||||
[^clawback]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/cryptonote_basic/cryptonote_format_utils.cpp#L122>
|
||||
|
||||
[^bp-tx-weight]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/cryptonote_basic/cryptonote_format_utils.cpp#L457>
|
160
books/protocol/src/consensus_rules/transactions/inputs.md
Normal file
160
books/protocol/src/consensus_rules/transactions/inputs.md
Normal file
|
@ -0,0 +1,160 @@
|
|||
# Transaction Inputs
|
||||
|
||||
## Introduction
|
||||
|
||||
These rules apply to transaction inputs, excluding miner transactions.
|
||||
|
||||
## Index
|
||||
|
||||
1. [Necessary Functions/Definitions](#functionsdefinitions)
|
||||
2. [Rules](#rules)
|
||||
|
||||
## Necessary Functions/Definitions
|
||||
|
||||
### Default Minimum Decoys
|
||||
|
||||
This is the default number of decoys an input must at least have.
|
||||
|
||||
> There are exceptions to this being the minimum decoy size for all transactions. See further down in [Rules](#rules).
|
||||
|
||||
| Hard-Fork | Minimum Decoys[^min-decoys] |
|
||||
| --------- | --------------------------- |
|
||||
| 1 | N/A |
|
||||
| 2 to 5 | 2 |
|
||||
| 6 | 4 |
|
||||
| 7 | 6 |
|
||||
| 8 to 14 | 10 |
|
||||
| 15+ | 15 |
|
||||
|
||||
### Minimum And Maximum Decoys Used
|
||||
|
||||
To check a transaction input's `ring` size we must first get the minimum and maximum number of `decoys`
|
||||
used in the transactions inputs[^min-max-decoys].
|
||||
|
||||
So if this was our transactions:
|
||||
|
||||
| Input | 1 | 2 | 3 |
|
||||
| --------- | -- | - | -- |
|
||||
| Ring size | 12 | 8 | 16 |
|
||||
|
||||
The minimum and maximum amount of decoys would be 7 and 15 respectively.
|
||||
|
||||
### Mixable And Un-Mixable Inputs
|
||||
|
||||
A mixable input is one that has enough outputs on the chain with the same amount to be able to build a ring with the
|
||||
minimum amount of decoys needed.
|
||||
|
||||
A ringCT input, aka an output with 0 amount, is always considered mixable[^0-amt-mixable].
|
||||
|
||||
For other inputs you first get the amount of outputs on chain with that amount and check if that's less than or equal
|
||||
to the [default minimum amount of decoys](#default-minimum-decoys) if it is then the input is un-mixable otherwise it is
|
||||
mixable[^check-mixability].
|
||||
|
||||
## Rules
|
||||
|
||||
### No Empty Inputs
|
||||
|
||||
The transaction must have at least 1 input[^no-empty-ins].
|
||||
|
||||
### No Empty decoys
|
||||
|
||||
All inputs must have decoys[^empty-decoys].
|
||||
|
||||
### Input Type
|
||||
|
||||
All inputs must be of type `txin_to_key`[^input-types].
|
||||
|
||||
### Inputs Must Not Overflow
|
||||
|
||||
The inputs when summed must not overflow a `u64` and the outputs when summed must not either[^amount-overflow].
|
||||
|
||||
### Unique Ring Members
|
||||
|
||||
From hard-fork 6, all ring members in an input must be unique, this is done by checking that
|
||||
no `key_offset` after the first is 0[^unique-ring].
|
||||
|
||||
### Unique Key Image
|
||||
|
||||
The key image must be unique in a transaction[^key-images-in-tx] and the whole chain [^key-images-in-chain].
|
||||
|
||||
### Torsion Free Key Image
|
||||
|
||||
The key image must be a canonical prime order point[^torsion-free-keyimage].
|
||||
|
||||
### Minimum Decoys
|
||||
|
||||
These rules are in effect from hard fork 2.
|
||||
|
||||
First you get the [minimum number of decoys used in the transaction](#minimum-and-maximum-decoys-used).
|
||||
|
||||
Then you get the [amount of mixable and un-mixable inputs](#mixable-and-unmixable-inputs).
|
||||
|
||||
Now get the [default minimum decoys allowed for the current hard-fork](#default-minimum-decoys).
|
||||
|
||||
If the minimum amount of decoys used in the transaction is less than the default minimum decoys allowed then the transaction is only
|
||||
allowed if there is at least one input which is un-mixable[^tx-without-minimum-decoys].
|
||||
|
||||
If there is an un-mixable then the transaction is not allowed to have more than 1 mixable input as well.
|
||||
|
||||
Special rules[^min-decoys-special-rules]:
|
||||
|
||||
- For hard-fork 15, both 10 and 15 decoys are allowed.
|
||||
- From hard-fork 8 upwards, the minimum amount of decoys used in a transaction must be equal to the minimum allowed.
|
||||
|
||||
### Equal Number Of Decoys
|
||||
|
||||
From hard-fork 12, all inputs must have the same number of decoys[^equal-decoys].
|
||||
|
||||
### Sorted Inputs
|
||||
|
||||
From hard-fork 7, the inputs must be sorted by key image, in descending lexicographic order[^sorted-kis].
|
||||
|
||||
### 10 Block Lock
|
||||
|
||||
From hard-fork 12, all ring members must be at least 10 blocks old[^minimum-out-age].
|
||||
|
||||
### The Output Must Exist
|
||||
|
||||
The output a transaction references must exist in the chain[^output-must-exist].
|
||||
|
||||
### The Output Must Not Be Locked
|
||||
|
||||
The outputs, which are referenced in the inputs, unlock time must have passed, see the [chapter on unlock time](./unlock_time.md).
|
||||
|
||||
---
|
||||
|
||||
[^min-decoys]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L3345>
|
||||
|
||||
[^min-max-decoys]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L3369-L3373>
|
||||
|
||||
[^0-amt-mixable]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L3357>
|
||||
|
||||
[^check-mixability]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L3361-L3367>
|
||||
|
||||
[^no-empty-ins]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/cryptonote_core.cpp#L1125>
|
||||
|
||||
[^empty-decoys]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L3473>
|
||||
|
||||
[^input-types]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_basic/cryptonote_format_utils.cpp#L844>
|
||||
|
||||
[^amount-overflow]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_basic/cryptonote_format_utils.cpp#L871>
|
||||
|
||||
[^unique-ring]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/cryptonote_core.cpp#L1309>
|
||||
|
||||
[^key-images-in-tx]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/cryptonote_core.cpp#L1297>
|
||||
|
||||
[^key-images-in-chain]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L3475>
|
||||
|
||||
[^torsion-free-keyimage]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/cryptonote_core.cpp#L1324>
|
||||
|
||||
[^tx-without-minimum-decoys]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L3392>
|
||||
|
||||
[^min-decoys-special-rules]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L3406-L3410>
|
||||
|
||||
[^equal-decoys]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L3378>
|
||||
|
||||
[^sorted-kis]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L3435>
|
||||
|
||||
[^minimum-out-age]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L3533>
|
||||
|
||||
[^output-must-exist]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L3995>
|
64
books/protocol/src/consensus_rules/transactions/outputs.md
Normal file
64
books/protocol/src/consensus_rules/transactions/outputs.md
Normal file
|
@ -0,0 +1,64 @@
|
|||
# Transaction Outputs
|
||||
|
||||
## Introduction
|
||||
|
||||
These rules apply to transaction outputs, excluding miner transaction outputs.
|
||||
|
||||
## Rules
|
||||
|
||||
### Outputs Must Not Overflow
|
||||
|
||||
The outputs when summed must not overflow a u64[^amount-overflow].
|
||||
|
||||
### Output Amount
|
||||
|
||||
For version 1, txs sum of the outputs must be less than the sum of the inputs, the difference between the
|
||||
inputs and the outputs is then the fee.[^more-in-than-out] The amount of each output must also not be zero.[^zero-output]
|
||||
|
||||
From hard-fork 2, version 1 transaction output amounts also must be validly decomposed[^decomposed-amounts].
|
||||
A valid decomposed amount is an amount contained in [this table](https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_basic/cryptonote_format_utils.cpp#L52)
|
||||
|
||||
For version 2, txs all outputs must have a zero amount.[^v2-output-amount]
|
||||
|
||||
### Output Keys Canonical
|
||||
|
||||
All output public keys must be `canonical points`[^output-key-canonical].
|
||||
|
||||
> This was added as a rule in hard-fork 4 but that check is redundant as it was done before that.
|
||||
> So how did invalid keys get on the chain? [miner txs](./blocks/miner_tx.md).
|
||||
|
||||
### Output Type
|
||||
|
||||
The output type allowed depends on the hard-fork[^output-types]:
|
||||
|
||||
| hard-fork | output type |
|
||||
| ---------- | ------------------------------------ |
|
||||
| 1 to 14 | txout_to_key |
|
||||
| 15 | txout_to_key and txout_to_tagged_key |
|
||||
| 16 onwards | txout_to_tagged_key |
|
||||
|
||||
> For hard-fork 15, both are allowed but the transactions outputs must all be the same type[^same-output-type].
|
||||
|
||||
### 2 Outputs
|
||||
|
||||
From hard-fork 12, version 2 transactions (RCT) must have 2 outputs[^minimum-2-outs].
|
||||
|
||||
---
|
||||
|
||||
[^amount-overflow]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_basic/cryptonote_format_utils.cpp#L871>
|
||||
|
||||
[^more-in-than-out]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/cryptonote_core.cpp#L1163> and <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/tx_pool.cpp#L190-L204>
|
||||
|
||||
[^zero-output]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_basic/cryptonote_format_utils.cpp#L862>
|
||||
|
||||
[^decomposed-amounts]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L3048>
|
||||
|
||||
[^v2-output-amount]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/cryptonote_core/blockchain.cpp#L3059>
|
||||
|
||||
[^output-key-canonical]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_basic/cryptonote_format_utils.cpp#L865>
|
||||
|
||||
[^output-types]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_basic/cryptonote_format_utils.cpp#L960>
|
||||
|
||||
[^same-output-type]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_basic/cryptonote_format_utils.cpp#L984>
|
||||
|
||||
[^minimum-2-outs]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/cryptonote_core/blockchain.cpp#L3324>
|
89
books/protocol/src/consensus_rules/transactions/ring_ct.md
Normal file
89
books/protocol/src/consensus_rules/transactions/ring_ct.md
Normal file
|
@ -0,0 +1,89 @@
|
|||
# Ring Confidential Transactions
|
||||
|
||||
## Introduction
|
||||
|
||||
Ring confidential transactions are version 2 Monero transactions which keep amounts hidden. They were activated at hard-fork 4. There are multiple
|
||||
types of RingCT transactions that were activated and deprecated at different hard-forks.
|
||||
|
||||
## Definitions
|
||||
|
||||
OutPK:
|
||||
A pedersen commitment to the output amount.
|
||||
|
||||
Pseudo-outs:
|
||||
A pedersen commitment to the true spends amount with a different mask, such that the sum of the pseudo-outs is the same as the sum of the outPKs + fee * H.
|
||||
|
||||
## Index
|
||||
|
||||
1. [Rules That Apply To All Types](#rules-that-apply-to-all-types)
|
||||
2. [Simple Types Rules](#simple-types-rules)
|
||||
3. [Borromean Rules](./ring_ct/borromean.md)
|
||||
4. [MLSAG Rules](./ring_ct/mlsag.md)
|
||||
5. [Bulletproofs Rules](./ring_ct/bulletproofs.md)
|
||||
6. [CLSAG Rules](./ring_ct/clsag.md)
|
||||
7. [Bulletproofs+ Rules](./ring_ct/bulletproofs+.md)
|
||||
|
||||
## Rules That Apply To All Types
|
||||
|
||||
### Type
|
||||
|
||||
RingCT type define the proofs used in the transaction, the ringCT types allowed depend on the hard-fork:
|
||||
|
||||
| Type (Name) | Short description | Hard Fork allowed | Hard Fork disallowed |
|
||||
| ---------------- | --------------------------------------------------------------------- | ---------------------------------------------------------- | --------------------------------------------------------------------- |
|
||||
| 0 (NULL) | No ringCT signatures, used for coinbase transactions | 4 (only miner transactions) [^first-three-type-activation] | Still allowed |
|
||||
| 1 (Full) | A single aggregate MLSAG signature with borromean range proofs | 4 [^first-three-type-activation] | 9 [^bulletproof-activated-borromean-disallowed] |
|
||||
| 2 (Simple) | MLSAG signatures per input with borromean range proofs | 4 [^first-three-type-activation] | 9 [^bulletproof-activated-borromean-disallowed] |
|
||||
| 3 (Bulletproof) | MLSAG signatures per input with a single bulletproof for all outputs | 8 [^bulletproof-activated-borromean-disallowed] | 11 [^bulletproof2-activated-bulletproof-disallowed] |
|
||||
| 4 (Bulletproof2) | Uses the same signatures as type 3 | 10 [^bulletproof2-activated-bulletproof-disallowed] | 14 (except 2 transactions) [^clsag-activated-bulletproof2-disallowed] |
|
||||
| 5 (CLSAG) | CLSAG signatures per input with a single bulletproof for all outputs | 13 [^clsag-activated-bulletproof2-disallowed] | 16 [^bulletproof+-activated-clsag-disallowed] |
|
||||
| 6 (Bulletproof+) | CLSAG signatures per input with a single bulletproof+ for all outputs | 15 [^bulletproof+-activated-clsag-disallowed] | Still allowed |
|
||||
| 6+ | Future type not currently allowed | Not allowed [^future-rct-types] | Not allowed |
|
||||
|
||||
There are 2 type 4 RCT transactions that are allowed after hard-fork 13, this was due to a bug in which transactions added to the txpool before a fork
|
||||
were not being checked for new fork rules they are:
|
||||
`c5151944f0583097ba0c88cd0f43e7fabb3881278aa2f73b3b0a007c5d34e910` and `6f2f117cde6fbcf8d4a6ef8974fcac744726574ac38cf25d3322c996b21edd4c`[^grandfathered-txs].
|
||||
|
||||
### OutPKs Valid Points
|
||||
|
||||
All outPKs must be canonically encoded points[^outPKs-valid-points].
|
||||
|
||||
## Simple Types Rules
|
||||
|
||||
These rules apply to all RCT "simple" types, which are all except type "FULL".
|
||||
|
||||
### Pseudo-outs Valid Points
|
||||
|
||||
This rule applies to the pseudo-outs, from type 3 (Bulletproof) the pseudo-outs field moved to the prunable RCT section from the non-prunable section.
|
||||
|
||||
The pseudo-outs must all be canonically encoded points[^pseudo-outs-valid-points].
|
||||
|
||||
### Pseudo-outs OutPKs Balance
|
||||
|
||||
The sum of the pseudo-outs must equal the sum of the OutPKs + fee * H:[^simple-amounts-balance]
|
||||
|
||||
\\(\sum PseudoOuts == \sum outPK + fee * H \\)
|
||||
|
||||
---
|
||||
|
||||
[^first-three-type-activation]: There is no direct code allowing these types of RingCT, these are the original types that got activated when version 2 transactions
|
||||
got activated
|
||||
|
||||
[^bulletproof-activated-borromean-disallowed]: <https://github.com/monero-project/monero/blob/master/src/cryptonote_core/blockchain.cpp#L3083-L3107>
|
||||
|
||||
[^bulletproof2-activated-bulletproof-disallowed]: <https://github.com/monero-project/monero/blob/master/src/cryptonote_core/blockchain.cpp#L3108-L3130>
|
||||
|
||||
[^clsag-activated-bulletproof2-disallowed]: <https://github.com/monero-project/monero/blob/master/src/cryptonote_core/blockchain.cpp#L3132-L3166>
|
||||
|
||||
[^bulletproof+-activated-clsag-disallowed]: <https://github.com/monero-project/monero/blob/master/src/cryptonote_core/blockchain.cpp#L3168-L3193>
|
||||
|
||||
[^future-rct-types]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctTypes.h#L335>
|
||||
|
||||
[^grandfathered-txs]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/cryptonote_core/blockchain.cpp#L3150>
|
||||
|
||||
[^outPKs-valid-points]: For simple types: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L1444>,
|
||||
For type FULL: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L829-L829>
|
||||
|
||||
[^pseudo-outs-valid-points]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L1449>
|
||||
|
||||
[^simple-amounts-balance]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L1453>
|
|
@ -0,0 +1,51 @@
|
|||
# Borromean Rules
|
||||
|
||||
## Introduction
|
||||
|
||||
These rules apply to all ringCT types that use Borromean ring signatures to prove an output amount is in the correct range.
|
||||
|
||||
## Rules
|
||||
|
||||
### Number Of Borromean Range Proofs
|
||||
|
||||
The amount of Borromean range proofs must be the same as the number of outputs.[^numb-borro]
|
||||
|
||||
### Ci Valid Points
|
||||
|
||||
Each Ci (bit commitment) must be canonically encoded points.[^ci-valid-points]
|
||||
|
||||
### Sum Ci
|
||||
|
||||
For a range proof at a certain index the sum of each Ci must equal the outPK at that index.[^sum-ci]
|
||||
|
||||
### Borromean Scalar Encoding
|
||||
|
||||
Monero does not check that the scalars `s0` and `s1` are reduced this leads to them, if not reduced, being interpreted as a different scalar by the `slide` function
|
||||
which calculates the 5-NAF of the number. The `slide` function restricts its output to 256 bytes however if the last bit is set on the input this could lead to the
|
||||
5-NAF of the scalar being 257 bytes long. There are scalars on the chain which have this behavior.[^scalar-report]
|
||||
|
||||
The scalar `ee` must be a fully reduced scalar as it is compared against the raw bytes of an output from the `hash_to_scalar` function.[^s0-s1-ee-encoding]
|
||||
|
||||
### The Borromean Ring Must Be Valid
|
||||
|
||||
To verify a Borromean ring signature is valid you must first set up the public keys that the ring will be verified with, one member of the ring will be a Ci the
|
||||
other will be (\\(Ci - H * 2^X \\)), where X is the index of the Ci. By setting up the ring like this the prover will only know the discreet log of a
|
||||
ring member if either the Ci is a commitment to 0 or \\(2^X\\)[^public-key-setup].
|
||||
|
||||
After setting up the public keys the actual borromean rings must be valid.[^ring-valid]
|
||||
|
||||
---
|
||||
|
||||
[^numb-borro]: <https://github.com/monero-project/monero/blame/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctTypes.h#L480>
|
||||
|
||||
[^ci-valid-points]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L581>
|
||||
|
||||
[^sum-ci]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L590>
|
||||
|
||||
[^scalar-report]: <https://www.moneroinflation.com/static/data_py/report_scalars_df.pdf>
|
||||
|
||||
[^s0-s1-ee-encoding]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L213-L222>
|
||||
|
||||
[^public-key-setup]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L574-L577>
|
||||
|
||||
[^ring-valid]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L208>
|
|
@ -0,0 +1,52 @@
|
|||
# Bulletproofs+ Rules
|
||||
|
||||
## Introduction
|
||||
|
||||
These rules apply to all ringCT types that use bulletproofs+.
|
||||
|
||||
## Rules
|
||||
|
||||
### L & R Length
|
||||
|
||||
The Length of the L & R fields must be the same, they must both be equal to \\( 6 + log_2(firstPower2AboveNumbOuts) \\).[^L-R-Size]
|
||||
|
||||
Where `firstPower2AboveNumbOuts` is the first power of 2 above or equal to the amount of outputs in the transaction, so:
|
||||
|
||||
If outputs = 3, firstPower2AboveNumbOuts = 4.
|
||||
|
||||
If outputs = 8, firstPower2AboveNumbOuts = 8.
|
||||
|
||||
### Number Of Bulletproofs
|
||||
|
||||
There must only be one bulletproof in a transaction.[^one-bulletproof+]
|
||||
|
||||
### Max Outputs
|
||||
|
||||
The amount of outputs in the transaction must not be more than 16 [^max-outputs]
|
||||
|
||||
### Canonical Encoding
|
||||
|
||||
`r1`, `s2`, `d1` must all be canonically encoded, reduced, scalars.[^scalars-reduced] All the points of `V`, `L` and `R` must be canonically encoded and `A1`, `B` and
|
||||
`A` must canonically encoded points.[^canonical-points]
|
||||
|
||||
### At Least One Output
|
||||
|
||||
There must be at least one element of V, which is constructed from the outPKs which must have the same number of elements as the outputs.[^one-out]
|
||||
|
||||
### The Bulletproof Must Be Valid
|
||||
|
||||
The bulletproof must pass verification. [^bulletproof+-valid]
|
||||
|
||||
[^L-R-Size]: <https://github.com/monero-project/monero/blob/master/src/ringct/rctTypes.cpp#L300-L304> && <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/bulletproofs_plus.cc#L850>
|
||||
|
||||
[^one-bulletproof+]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/cryptonote_basic/cryptonote_format_utils.cpp#L173>
|
||||
|
||||
[^max-outputs]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/cryptonote_core/cryptonote_core.cpp#L887>
|
||||
|
||||
[^scalars-reduced]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/bulletproofs_plus.cc#L825-L827>
|
||||
|
||||
[^canonical-points]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/bulletproofs_plus.cc#L931-L939> && <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctOps.cpp#L415>
|
||||
|
||||
[^one-out]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/bulletproofs_plus.cc#L829>
|
||||
|
||||
[^bulletproof+-valid]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/bulletproofs_plus.cc#L799>
|
|
@ -0,0 +1,55 @@
|
|||
# Bulletproofs Rules
|
||||
|
||||
## Introduction
|
||||
|
||||
These rules apply to all ringCT types that use bulletproofs.
|
||||
|
||||
## Rules
|
||||
|
||||
### L & R Length
|
||||
|
||||
The Length of the L & R fields must be the same, they must both be equal to \\( 6 + log_2(firstPower2AboveNumbOuts) \\).[^L-R-Size]
|
||||
|
||||
Where `firstPower2AboveNumbOuts` is the first power of 2 above or equal to the amount of outputs in the transaction, so:
|
||||
|
||||
If outputs = 3, firstPower2AboveNumbOuts = 4.
|
||||
|
||||
If outputs = 8, firstPower2AboveNumbOuts = 8.
|
||||
|
||||
### Number Of Bulletproofs
|
||||
|
||||
There must only be one bulletproof in a transaction.[^one-bulletproof]
|
||||
|
||||
### Max Outputs
|
||||
|
||||
The amount of outputs in the transaction must not be more 16 [^max-outputs]
|
||||
|
||||
### At Least One Output
|
||||
|
||||
There must be at least one element of V, which is constructed from the outPKs which must have the same number of elements as the outputs.[^one-out]
|
||||
|
||||
### Canonical Encoding
|
||||
|
||||
`taux`, `mu`, `a`, `b`, `t` must all be fully reduced scalars[^canonical-scalars].
|
||||
|
||||
All the elements of `V`, `L`, `R` and `A`, `T1`, `T2` and `S` must all be valid, canonically encoded points.[^canonical-points]
|
||||
|
||||
### The Bulletproof Must Be Valid
|
||||
|
||||
The bulletproof must pass verification. [^bulletproof-valid]
|
||||
|
||||
---
|
||||
|
||||
[^L-R-Size]: <https://github.com/monero-project/monero/blob/master/src/ringct/rctTypes.cpp#L300-L304> && <https://github.com/monero-project/monero/blob/master/src/ringct/bulletproofs.cc#L862-L863>
|
||||
|
||||
[^one-bulletproof]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/cryptonote_basic/cryptonote_format_utils.cpp#L197>
|
||||
|
||||
[^max-outputs]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/cryptonote_core/cryptonote_core.cpp#L877>
|
||||
|
||||
[^one-out]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/bulletproofs.cc#L839>
|
||||
|
||||
[^canonical-scalars]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/bulletproofs.cc#L833-L837>
|
||||
|
||||
[^canonical-points]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/bulletproofs.cc#L919-L930>
|
||||
|
||||
[^bulletproof-valid]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/bulletproofs.cc#L810>
|
|
@ -0,0 +1,41 @@
|
|||
# CLSAG Rules
|
||||
|
||||
## Introduction
|
||||
|
||||
These rules apply to all ringCT types that use CLSAG signatures.
|
||||
|
||||
## Rules
|
||||
|
||||
### Number Of CLSAGs
|
||||
|
||||
There must be the same number of CLSAG signatures as there are inputs.[^numb-clsags]
|
||||
|
||||
### `s` Size
|
||||
|
||||
The `s` field must have has many elements as the amount of ring members.[^s-size]
|
||||
|
||||
### Canonical Encoding
|
||||
|
||||
All `s` scalars must be fully reduced, the `c1` scalar must be fully reduced[^scalars-reduced] and the `D` point must be canonically encoded.[^D-canonical]
|
||||
|
||||
### Key Images Not Identity
|
||||
|
||||
The key image and 8 * `D`, the commitment key image, must both not be the identity point.[^kis-not-identity]
|
||||
|
||||
### The CLSAG Signature Must Be Correctly Formed
|
||||
|
||||
The signature must be valid.[^clsag-valid]
|
||||
|
||||
---
|
||||
|
||||
[^numb-clsags]: <https://github.com/monero-project/monero/blame/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctTypes.h#L496>
|
||||
|
||||
[^s-size]: <https://github.com/monero-project/monero/blame/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L880>
|
||||
|
||||
[^scalars-reduced]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L881-L883>
|
||||
|
||||
[^D-canonical]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L894>
|
||||
|
||||
[^kis-not-identity]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L895> and <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L884>
|
||||
|
||||
[^clsag-valid]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L872>
|
123
books/protocol/src/consensus_rules/transactions/ring_ct/mlsag.md
Normal file
123
books/protocol/src/consensus_rules/transactions/ring_ct/mlsag.md
Normal file
|
@ -0,0 +1,123 @@
|
|||
# MLSAG Rules
|
||||
|
||||
## Introduction
|
||||
|
||||
These rules are split into 3 sections: Full, Simple and Both. Full is for RCT type Full and Simple are for the other RCT types
|
||||
that use MLSAG signatures.
|
||||
|
||||
> Simple is not just for RCT type Simple!
|
||||
|
||||
## Index
|
||||
|
||||
1. [Full Rules](#full-rules)
|
||||
2. [Simple Rules](#simple-rules)
|
||||
|
||||
## Full Rules
|
||||
|
||||
### Creating The Ring Matrix (Full)
|
||||
|
||||
For RCT type full the ring matrix contains every inputs ring members: [^full-matrix]
|
||||
|
||||
(The signer owns a whole column)
|
||||
|
||||
```bob
|
||||
.-------.-------.-------.- - - -.
|
||||
| I1 R1 | I1 R2 | I1 R3 | ..... |
|
||||
| I2 R1 | I2 R2 | I2 R3 | ..... |
|
||||
| I3 R1 | I3 R2 | I3 R3 | ..... |
|
||||
..... ..... ..... .....
|
||||
| A | A | A | ..... | <-.
|
||||
'-------'-------'-------'-------' |
|
||||
|
|
||||
I = Input |
|
||||
R = Ring member |
|
||||
A = Pedersen Commitment |
|
||||
```
|
||||
|
||||
The last row contains: \\(\sum CommitmentsAtIndex - \sum outPK - fee * H \\) [^full-last-row]
|
||||
|
||||
Where CommitmentsAtIndex are the ring members commitments in that column.
|
||||
|
||||
Which means that for the true spends column the entry in the last row will be commitment to 0.
|
||||
|
||||
By structuring the matrix like this the true spend has to be a the same index in each inputs ring,
|
||||
which is not good for privacy.
|
||||
|
||||
### Number Of Ring Members
|
||||
|
||||
There must be the same amount of ring members in each inputs ring.[^full-numb-ring-members]
|
||||
|
||||
### One MLSAGs
|
||||
|
||||
There must be only one MLSAG signature.[^numb-mlsags]
|
||||
|
||||
## Simple Rules
|
||||
|
||||
### Creating The Ring Matrix (Simple)
|
||||
|
||||
For simple RCT types the ring matrix only contains the ring members of a single input: [^simple-matrix]
|
||||
|
||||
```bob
|
||||
.-------.-------.-------.- - - -.
|
||||
| IX R1 | IX R2 | IX R3 | ..... |
|
||||
| A | A | A | ..... | <-.
|
||||
'-------'-------'-------'- - - -' |
|
||||
|
|
||||
I = Input |
|
||||
R = Ring member |
|
||||
A = Pedersen Commitment |
|
||||
```
|
||||
|
||||
The last row contains the ring members commitment minus the pseudo-out for this input.[^simple-last-row]
|
||||
|
||||
### Simple Number Of MLSAGs
|
||||
|
||||
There must be the same amount of MLSAG signatures as there are inputs.[^numb-mlsags]
|
||||
|
||||
## Rules That Apply To Both
|
||||
|
||||
### More Than One Ring Member
|
||||
|
||||
There must be more than one ring member.[^more-than-one-ring-member]
|
||||
|
||||
### SS Size
|
||||
|
||||
The ss field must be the same length as the key matrix[^ss-size] and each ss member lengths must be the same as the matrix's rows. [^ss-member-size]
|
||||
|
||||
### SS, CC Canonical Encoding
|
||||
|
||||
Every ss element and cc must be fully reduced scalars.[^ss-cc-reduced]
|
||||
|
||||
### Key Images Not Identity
|
||||
|
||||
All the key images must not be equal to the identity point.[^ki-not-identity]
|
||||
|
||||
### The MLSAG Signature Must Be Correct
|
||||
|
||||
The signature must be valid.[^mlsag-valid]
|
||||
|
||||
---
|
||||
|
||||
[^full-matrix]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L802>
|
||||
|
||||
[^full-last-row]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L827-L833>
|
||||
|
||||
[^full-numb-ring-members]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L810>
|
||||
|
||||
[^numb-mlsags]: <https://github.com/monero-project/monero/blame/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctTypes.h#L537-L540C28>s
|
||||
|
||||
[^simple-matrix]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L841>
|
||||
|
||||
[^simple-last-row]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L861-L864>
|
||||
|
||||
[^more-than-one-ring-member]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L462>
|
||||
|
||||
[^ss-size]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L469>
|
||||
|
||||
[^ss-member-size]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L471>
|
||||
|
||||
[^ss-cc-reduced]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L477-L480>
|
||||
|
||||
[^ki-not-identity]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L487>
|
||||
|
||||
[^mlsag-valid]: <https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/src/ringct/rctSigs.cpp#L460>
|
|
@ -0,0 +1,39 @@
|
|||
# Transaction Version 1 Rules
|
||||
|
||||
## Introduction
|
||||
|
||||
These rules apply only to version 1, pre-ringCT, transactions.
|
||||
|
||||
## Rules
|
||||
|
||||
### Amount Of Ring Signatures
|
||||
|
||||
The amount of ring signatures must be the same as the number of inputs[^amt-of-ring-sigs].
|
||||
|
||||
### Amount Of Signatures In A Ring
|
||||
|
||||
For a ring signature at a certain index, the input at that same index must have the same amount of ring members as the ring signature has signatures[^amt-of-sigs].
|
||||
|
||||
### Signatures Must Be Canonical
|
||||
|
||||
Every signatures c and r value must be `canonical scalars`[^canonical-sig].
|
||||
|
||||
### Ring Members Must Be Valid Points
|
||||
|
||||
All outputs used as ring members must be valid canonical points[^valid-members].
|
||||
|
||||
### The Ring Signature Must Be Valid
|
||||
|
||||
The ring signature must be correctly formed[^ring-sig-correct].
|
||||
|
||||
---
|
||||
|
||||
[^amt-of-ring-sigs]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L3485> and <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_basic/cryptonote_basic.h#L266>
|
||||
|
||||
[^amt-of-sigs]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L3999> and <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_basic/cryptonote_basic.h#L271-L282>
|
||||
|
||||
[^canonical-sig]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/crypto/crypto.cpp#L735>
|
||||
|
||||
[^valid-members]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/crypto/crypto.cpp#L738>
|
||||
|
||||
[^ring-sig-correct]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/crypto/crypto.cpp#L711>
|
|
@ -0,0 +1,68 @@
|
|||
# Unlock Time
|
||||
|
||||
To spend an output the output's unlock time must have passed.
|
||||
|
||||
## Interpreting An Unlock Time
|
||||
|
||||
The unlock time is just a 64 bit unsigned number. It is interpreted as a block height if less than 500,000,000 otherwise it's a Unix timestamp[^interpreting-unlock-time].
|
||||
|
||||
## Checking The Output Is Unlocked
|
||||
|
||||
### Block Height
|
||||
|
||||
First you get the top blocks height and add one, we do this because we are checking if
|
||||
the transaction is allowed in the next block not the last.
|
||||
|
||||
We now check if this height is greater than or equal to the unlock time if it is then
|
||||
accept the block[^height-accepting].
|
||||
|
||||
### Timestamp
|
||||
|
||||
#### Getting The Current Time
|
||||
|
||||
Before hard-fork 13, this was done by just getting the computer's time, from hf 13 onwards, we use
|
||||
an average over the last blocks[^getting-time].
|
||||
|
||||
Monero uses the last 60 blocks to get an average, if the `chain height` is less than
|
||||
60, just use the current time[^height-less-60].
|
||||
|
||||
First you get the median timestamp of the last 60 blocks. We then project this
|
||||
timestamp to match approximately when the block being validated will appear, to do
|
||||
this we do[^median-timestamp]:
|
||||
|
||||
\\(adjustedMedian = median + \frac{(TimestampWindow + 1) * DifficultyTarget}{2} \\)
|
||||
|
||||
where:
|
||||
|
||||
\\(TimestampWindow = 60\\)
|
||||
|
||||
\\(DifficultyTarget = 120\\)
|
||||
|
||||
You then get the top block's timestamp and add the target seconds per block[^adjusting-top-block].
|
||||
|
||||
The timestamp we use is then the minimum out of the adjusted median and adjusted most
|
||||
recent timestamp[^minimum-timestamp].
|
||||
|
||||
### Checking Timestamp Has Passed
|
||||
|
||||
Now with our timestamp we add the [target seconds](../blocks/difficulty.md#target-seconds)
|
||||
per block and check if this is more than or equal to the unlock
|
||||
time[^checking-timestamp].
|
||||
|
||||
---
|
||||
|
||||
[^interpreting-unlock-time]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L3921>
|
||||
|
||||
[^height-accepting]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L3925>
|
||||
|
||||
[^getting-time]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L3933>
|
||||
|
||||
[^height-less-60]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L4011>
|
||||
|
||||
[^median-timestamp]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L4024-L4028>
|
||||
|
||||
[^adjusting-top-block]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L4032>
|
||||
|
||||
[^minimum-timestamp]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L4036>
|
||||
|
||||
[^checking-timestamp]: <https://github.com/monero-project/monero/blob/eac1b86bb2818ac552457380c9dd421fb8935e5b/src/cryptonote_core/blockchain.cpp#L3934>
|
3
books/protocol/src/p2p_network.md
Normal file
3
books/protocol/src/p2p_network.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# P2P Network
|
||||
|
||||
This chapter contains descriptions of Monero's peer to peer network, including messages, flows, expected responses, etc.
|
3
books/protocol/src/p2p_network/epee.md
Normal file
3
books/protocol/src/p2p_network/epee.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Epee Binary Format
|
||||
|
||||
The epee binary format is described here: TODO
|
68
books/protocol/src/p2p_network/levin.md
Normal file
68
books/protocol/src/p2p_network/levin.md
Normal file
|
@ -0,0 +1,68 @@
|
|||
# Levin Protocol
|
||||
|
||||
This chapter describes the levin protocol.
|
||||
|
||||
## Buckets
|
||||
|
||||
A Bucket is a single piece of data that the levin protocol parser can decode, it will contain a p2p message or it will be part of a chain
|
||||
of buckets that will be combined into a single message.
|
||||
|
||||
### Bucket Format
|
||||
|
||||
| Field | Type | Size (bytes) |
|
||||
| ------ | ----------------------------- | ------------ |
|
||||
| Header | [BucketHeader](#bucketheader) | 33 |
|
||||
| Body | bytes | dynamic |
|
||||
|
||||
### BucketHeader
|
||||
|
||||
Format:
|
||||
|
||||
| Field | Type | Size (bytes) |
|
||||
| ---------------- | ------ | ------------ |
|
||||
| Signature | LE u64 | 8 |
|
||||
| Size | LE u64 | 8 |
|
||||
| Expect Response | bool | 1 |
|
||||
| Command | LE u32 | 4 |
|
||||
| Return Code | LE i32 | 4 |
|
||||
| Flags | LE u32 | 4 |
|
||||
| Protocol Version | LE u32 | 4 |
|
||||
|
||||
#### Signature
|
||||
|
||||
The signature field is fixed for every bucket and is used to tell apart peers running different protocols.
|
||||
|
||||
Its value should be `0x0101010101012101`
|
||||
|
||||
#### Size
|
||||
|
||||
This field represents the size of the buckets body.
|
||||
|
||||
#### Expect Response
|
||||
|
||||
Messages with the expect response field set must be responded to in order, other messages are still allowed in between responses.
|
||||
|
||||
#### Command
|
||||
|
||||
This field is an identifier for what specific message the bucket's body contains.
|
||||
|
||||
#### Return Code
|
||||
|
||||
This field represents the status of the response from the peer, requests and notifications should set this to `0` and successful
|
||||
responses should be `1`.
|
||||
|
||||
#### Flags
|
||||
|
||||
This is a bit-flag field that determines what type of bucket this is:
|
||||
|
||||
| Type | Bits set |
|
||||
| -------------- | ----------- |
|
||||
| Request | `0000_0001` |
|
||||
| Response | `0000_0010` |
|
||||
| Start Fragment | `0000_0100` |
|
||||
| End Fragment | `0000_1000` |
|
||||
| Dummy | `0000_1100` |
|
||||
|
||||
#### Protocol Version
|
||||
|
||||
This is a fixed value of 1.
|
37
books/protocol/src/p2p_network/messages.md
Normal file
37
books/protocol/src/p2p_network/messages.md
Normal file
|
@ -0,0 +1,37 @@
|
|||
# P2P Messages
|
||||
|
||||
This chapter contains every P2P message.
|
||||
|
||||
## Index
|
||||
|
||||
## Types
|
||||
|
||||
Types used in multiple P2P messages.
|
||||
|
||||
### Support Flags
|
||||
|
||||
Support flags specify any protocol extensions the peer supports, currently only the first bit is used:
|
||||
|
||||
`FLUFFY_BLOCKS = 1` - for if the peer supports receiving fluffy blocks.
|
||||
|
||||
### Basic Node Data
|
||||
|
||||
| Fields | Type (Epee Type) | Description |
|
||||
| ---------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| `network_id` | A UUID (String) | A fixed constant value for a specific network (mainnet,testnet,stagenet) |
|
||||
| `my_port` | u32 (u32) | The peer's inbound port, if the peer does not want inbound connections this should be `0` |
|
||||
| `rpc_port` | u16 (u16) | The peer's RPC port, if the peer does not want inbound connections this should be `0` |
|
||||
| `rpc_credits_per_hash` | u32 (u32) | TODO |
|
||||
| `peer_id` | u64 (u64) | A fixed ID for the node, set to 1 for anonymity networks |
|
||||
| `support_flags` | [support flags](#support-flags) (u32) | Specifies any protocol extensions the peer supports |
|
||||
|
||||
## Messages
|
||||
|
||||
### Handshake Requests
|
||||
|
||||
levin command: 1001
|
||||
|
||||
| Fields | Type (Epee Type) | Description |
|
||||
| ----------- | -------------------------------------------- | ----------- |
|
||||
| `node_data` | [basic node data](#basic-node-data) (Object) | |
|
||||
| | | |
|
373
books/protocol/src/pruning.md
Normal file
373
books/protocol/src/pruning.md
Normal file
|
@ -0,0 +1,373 @@
|
|||
# Pruning
|
||||
|
||||
Monero pruning works by having 8 possible pruning seeds, the seed chosen will decide what part of the blockchain's signing data your node will keep. Each pruned peer generates their pruning seed randomly.
|
||||
|
||||
## Stripes
|
||||
|
||||
This is the amount of different blockchain portions that a pruned peer could keep. For Monero this is currently 8, this means the blockchain's signing data is split into 8 portions.
|
||||
|
||||
## Stripes Size
|
||||
|
||||
Depending on your stripe (and therefore your seed) `monerod` will store, in a cyclic manner, a portion of blocks while discarding the ones that are out of your stripe. The stripe's size is amount of blocks before another stripe will have to store their portion of blocks, it is set at 4096. That means that in terms of a block's height, the first pruning stripe will store blocks 0 to 4095, the second stripes will store blocks 4096 to 8191, the third stripe will store blocks 8192 to 12288... etc. While a specific stripe is storing a portion of the blockchain, nodes with another stripe can just discard them. This is shown in the table below:
|
||||
|
||||
| stripe | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|
||||
| ---------------- | ------------- | ----------- | ------------ | -- | -- | -- | -- | -- |
|
||||
| will have blocks | 0 - 4095 | 4096 - 8191 | 8192 - 12287 | .. | .. | .. | .. | .. |
|
||||
| | 32768 - 36863 | .. | .. | .. | .. | .. | .. | .. |
|
||||
| | .. | .. | .. | .. | .. | .. | .. | .. |
|
||||
|
||||
## Tip Blocks
|
||||
|
||||
Blocks within 5500 of the tip of the chain will not be pruned.
|
||||
|
||||
## Generating Pruning Seeds
|
||||
|
||||
The function in Monero to generate pruning seeds:
|
||||
|
||||
```c++
|
||||
uint32_t make_pruning_seed(uint32_t stripe, uint32_t log_stripes)
|
||||
{
|
||||
CHECK_AND_ASSERT_THROW_MES(log_stripes <= PRUNING_SEED_LOG_STRIPES_MASK, "log_stripes out of range");
|
||||
CHECK_AND_ASSERT_THROW_MES(stripe > 0 && stripe <= (1ul << log_stripes), "stripe out of range");
|
||||
return (log_stripes << PRUNING_SEED_LOG_STRIPES_SHIFT) | ((stripe - 1) << PRUNING_SEED_STRIPE_SHIFT);
|
||||
}
|
||||
```
|
||||
|
||||
This function takes in a stripe which is number 1 to 8 including(1 & 8) and a log_stripes which is log2 of the amount of different stripes (8) which is 3.
|
||||
|
||||
The constants used in this function:
|
||||
|
||||
```c++
|
||||
static constexpr uint32_t PRUNING_SEED_LOG_STRIPES_SHIFT = 7;
|
||||
static constexpr uint32_t PRUNING_SEED_LOG_STRIPES_MASK = 0x7;
|
||||
static constexpr uint32_t PRUNING_SEED_STRIPE_SHIFT = 0;
|
||||
```
|
||||
|
||||
The possible inputs/outputs of this function (`log_stripes` is always 3)
|
||||
|
||||
| input (stripe) | output (seed) |
|
||||
| -------------- | ------------- |
|
||||
| 1 | 384 |
|
||||
| 2 | 385 |
|
||||
| 3 | 386 |
|
||||
| 4 | 387 |
|
||||
| 5 | 388 |
|
||||
| 6 | 389 |
|
||||
| 7 | 390 |
|
||||
| 8 | 391 |
|
||||
|
||||
## Getting A Seed's Log Stripes
|
||||
|
||||
Monero currently only accepts a log stripes value of 3 and will reject any peers that use a different value. The function to calculate a seed's log stripes is:
|
||||
|
||||
```c++
|
||||
constexpr inline uint32_t get_pruning_log_stripes(uint32_t pruning_seed) {
|
||||
return (pruning_seed >> PRUNING_SEED_LOG_STRIPES_SHIFT) & PRUNING_SEED_LOG_STRIPES_MASK;
|
||||
}
|
||||
```
|
||||
|
||||
This will only return 3 for all currently valid Monero seeds.
|
||||
|
||||
## Getting A Seed's Pruning Stripe
|
||||
|
||||
The seed's pruning stripe corresponds, as explain earlier, to the range of blocks we keep. This is the function that gets the stripe from the pruning seed:
|
||||
|
||||
```c++
|
||||
inline uint32_t get_pruning_stripe(uint32_t pruning_seed) {
|
||||
if (pruning_seed == 0) return 0;
|
||||
return 1 + ((pruning_seed >> PRUNING_SEED_STRIPE_SHIFT) & PRUNING_SEED_STRIPE_MASK); }
|
||||
```
|
||||
|
||||
A pruning seed of 0 means no pruning. This function is just the inverse of [Generating Pruning Seeds](#generating-pruning-seeds) so the inputs/outputs of this will just be the other way round.
|
||||
|
||||
## Getting A Block's Pruning Stripe
|
||||
|
||||
A Block's pruning stripe is the stripe that corresponds to keeping that block so for blocks 0 to 4095 this will be 1, for blocks 4096 to 8191 this will be 2.
|
||||
The function in Monero to get the pruning stripe that corresponds to keeping that block is:
|
||||
|
||||
```c++
|
||||
uint32_t get_pruning_stripe(uint64_t block_height, uint64_t blockchain_height, uint32_t log_stripes)
|
||||
{
|
||||
if (block_height + CRYPTONOTE_PRUNING_TIP_BLOCKS >= blockchain_height)
|
||||
return 0;
|
||||
return ((block_height / CRYPTONOTE_PRUNING_STRIPE_SIZE) & (uint64_t)((1ul << log_stripes) - 1)) + 1;
|
||||
}
|
||||
```
|
||||
|
||||
[Pruning Stripe Size](#stripes-size)
|
||||
|
||||
This function takes in a number (`block_height`) and outputs a number 0 to 8. Zero is a special case for if the block_height is within Tip Blocks, this means every seed should keep this block. For 1 to 8 the output will rotate every 4096 so if I input 0 the output is 1 and if I input 4096 the output is 2 and so
|
||||
on...
|
||||
|
||||
#### explaining what the function is doing in depth:
|
||||
|
||||
As you can see, this function first checks if the block_height is within Tip Blocks and returns 0, because every seed will have this block.
|
||||
|
||||
`((1ul << log_stripes) - 1)` This sets the last 3 bits: `0000 0111` so when we bitand we
|
||||
remove every other bit.
|
||||
|
||||
`(block_height / CRYPTONOTE_PRUNING_STRIPE_SIZE)`:
|
||||
|
||||
- for any block 0 to 4095 dividing by 4096 will output 0 (stripe: 1)
|
||||
- for any block 4096 to 8191 dividing by 4096 will output 1 (stripe: 2)
|
||||
- for any blocks 32768 to 36863 dividing by 4096 will output 8 (stripe: 1)
|
||||
|
||||
Here's an issue, we need the strips to be cyclic. A result of 8 should give an output of 1, and a result of 455 should give an output of 5.
|
||||
To do so we just use the modulo operation. (8 mod 8 = 1, 455 mod 8 = 5) In binary operation, if the divisor is a power of two, then this is
|
||||
equivalent to bitand the value with the divisor -1:
|
||||
|
||||
This is why if we bitand this with 7 (0000 0111), this then becomes:
|
||||
|
||||
- 0 to 4095 would be 0
|
||||
- 4096 to 8191 would be 1
|
||||
- 32768 to 36863 would be 0
|
||||
|
||||
now we are close, all we have to do now to get the stripe is add 1
|
||||
|
||||
## Getting A Block's Pruning Seed
|
||||
|
||||
The Block's pruning seed is the seed that will keep that block. This is the function in Monero:
|
||||
|
||||
```c++
|
||||
uint32_t get_pruning_seed(uint64_t block_height, uint64_t blockchain_height, uint32_t log_stripes)
|
||||
{
|
||||
const uint32_t stripe = get_pruning_stripe(block_height, blockchain_height, log_stripes);
|
||||
if (stripe == 0)
|
||||
return 0;
|
||||
return make_pruning_seed(stripe, log_stripes);
|
||||
}
|
||||
```
|
||||
|
||||
This is simple, a call to [`get_pruning_stripe`](#getting-a-blocks-pruning-stripe) and passing that stripe into [`make_pruning_seed`](#generating-pruning-seeds)
|
||||
|
||||
## Getting The Next Un-pruned Block
|
||||
|
||||
For a particular seed and block height we can calculate what the height of the next un-pruned block will
|
||||
be. The function to do this in Monero is:
|
||||
|
||||
```c++
|
||||
uint64_t get_next_unpruned_block_height(uint64_t block_height, uint64_t blockchain_height, uint32_t pruning_seed)
|
||||
{
|
||||
CHECK_AND_ASSERT_MES(block_height <= CRYPTONOTE_MAX_BLOCK_NUMBER+1, block_height, "block_height too large");
|
||||
CHECK_AND_ASSERT_MES(blockchain_height <= CRYPTONOTE_MAX_BLOCK_NUMBER+1, block_height, "blockchain_height too large");
|
||||
const uint32_t stripe = get_pruning_stripe(pruning_seed);
|
||||
if (stripe == 0)
|
||||
return block_height;
|
||||
if (block_height + CRYPTONOTE_PRUNING_TIP_BLOCKS >= blockchain_height)
|
||||
return block_height;
|
||||
const uint32_t seed_log_stripes = get_pruning_log_stripes(pruning_seed);
|
||||
const uint64_t log_stripes = seed_log_stripes ? seed_log_stripes : CRYPTONOTE_PRUNING_LOG_STRIPES;
|
||||
const uint64_t mask = (1ul << log_stripes) - 1;
|
||||
const uint32_t block_pruning_stripe = ((block_height / CRYPTONOTE_PRUNING_STRIPE_SIZE) & mask) + 1;
|
||||
if (block_pruning_stripe == stripe)
|
||||
return block_height;
|
||||
const uint64_t cycles = ((block_height / CRYPTONOTE_PRUNING_STRIPE_SIZE) >> log_stripes);
|
||||
const uint64_t cycle_start = cycles + ((stripe > block_pruning_stripe) ? 0 : 1);
|
||||
const uint64_t h = cycle_start * (CRYPTONOTE_PRUNING_STRIPE_SIZE << log_stripes) + (stripe - 1) * CRYPTONOTE_PRUNING_STRIPE_SIZE;
|
||||
if (h + CRYPTONOTE_PRUNING_TIP_BLOCKS > blockchain_height)
|
||||
return blockchain_height < CRYPTONOTE_PRUNING_TIP_BLOCKS ? 0 : blockchain_height - CRYPTONOTE_PRUNING_TIP_BLOCKS;
|
||||
CHECK_AND_ASSERT_MES(h >= block_height, block_height, "h < block_height, unexpected");
|
||||
return h;
|
||||
}
|
||||
```
|
||||
|
||||
As you can see this is a monstrous function
|
||||
|
||||
#### explaining what the function is doing in depth:
|
||||
|
||||
```c++
|
||||
const uint32_t stripe = get_pruning_stripe(pruning_seed);
|
||||
if (stripe == 0)
|
||||
return block_height;
|
||||
if (block_height + CRYPTONOTE_PRUNING_TIP_BLOCKS >= blockchain_height)
|
||||
return block_height;
|
||||
```
|
||||
|
||||
This is calculating the [stripe](#getting-a-seeds-pruning-stripe) of the inputted pruning seed, remember if the seed/stripe is `0` that means no pruning so we can return the current
|
||||
height as the next un-pruned height and similarly if the block's height is within [Tip Blocks](#tip-blocks) of the blockchain's height that also means the block won't be pruned.
|
||||
|
||||
```c++
|
||||
const uint32_t seed_log_stripes = get_pruning_log_stripes(pruning_seed);
|
||||
const uint64_t log_stripes = seed_log_stripes ? seed_log_stripes : CRYPTONOTE_PRUNING_LOG_STRIPES;
|
||||
const uint64_t mask = (1ul << log_stripes) - 1;
|
||||
```
|
||||
|
||||
This is calculating the [log stripes](#getting-a-seeds-log-stripes) of the seed, although Monero currently only allows a log stripes of 3 in the future a higher number could be allowed so this function accounts for that.
|
||||
|
||||
If the seed's log stripes are zero this will set it to `CRYPTONOTE_PRUNING_LOG_STRIPES` which is currently `3`.
|
||||
|
||||
Then this sets the value of `mask` to one less than the amount of [stripes](#stripes), for Monero the amount of stripes is 8 so `mask` will be 7.
|
||||
|
||||
```c++
|
||||
const uint32_t block_pruning_stripe = ((block_height / CRYPTONOTE_PRUNING_STRIPE_SIZE) & mask) + 1;
|
||||
if (block_pruning_stripe == stripe)
|
||||
return block_height;
|
||||
```
|
||||
|
||||
This calculates the [block's pruning stripe](#getting-a-blocks-pruning-stripe) using the same method that we saw in [this](#getting-a-blocks-pruning-stripe) function.
|
||||
|
||||
This then checks if the block's stripe is the same as the seed stripe, if you remember if a seed and block have the same stripe that means the seed will keep the block, so we can just return the entered `block_height`.
|
||||
|
||||
```c++
|
||||
const uint64_t cycles = ((block_height / CRYPTONOTE_PRUNING_STRIPE_SIZE) >> log_stripes);
|
||||
```
|
||||
|
||||
This calculates how many cycles of this table we have done:
|
||||
|
||||
| stripe | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|
||||
| -------- | ------------- | ------------ | ------------ | -- | -- | -- | -- | -- |
|
||||
| cycle 0: | 0 - 4095 | 4096 - 8,191 | 8192 - 12287 | .. | .. | .. | .. | .. |
|
||||
| cycle 1: | 32768 - 36863 | .. | .. | .. | .. | .. | .. | .. |
|
||||
| cycle 2: | .. | .. | .. | .. | .. | .. | .. | .. |
|
||||
| | .. | | | | | | | |
|
||||
|
||||
If we think about what this is doing, this makes sense:
|
||||
|
||||
## \\(cycles = \frac{block height}{CRYPTONOTE PRUNING STRIPE SIZE} * \frac{1}{2^{log stripes}} \\)
|
||||
|
||||
for normal Monero pruning this is the same as:
|
||||
|
||||
## \\(cycles = \frac{block height}{4096 * 2^{3}} = \frac{block height}{32768}\\)
|
||||
|
||||
```c++
|
||||
const uint64_t cycle_start = cycles + ((stripe > block_pruning_stripe) ? 0 : 1);
|
||||
```
|
||||
|
||||
This checks if we are a past our seeds stripe in a cycle and if we are past it we add
|
||||
one to the number of cycles to get `cycles_start` which is the start of the cycle our
|
||||
stripe will next be storing blocks in.
|
||||
|
||||
```c++
|
||||
const uint64_t h = cycle_start * (CRYPTONOTE_PRUNING_STRIPE_SIZE << log_stripes) + (stripe - 1) * CRYPTONOTE_PRUNING_STRIPE_SIZE;
|
||||
```
|
||||
|
||||
If you remember from the table [here](#stripes-size) each stripe will keep a part of the blockchain in a cyclic manner, which replates every 32,768.
|
||||
|
||||
- so stripe 1 will keep `numb_of_cycles * 32768 + 0 * 4096`
|
||||
- so stripe 2 will keep `numb_of_cycles * 32768 + 1 * 4096`
|
||||
- so stripe 3 will keep `numb_of_cycles * 32768 + 2 * 4096`
|
||||
|
||||
Each stripe will stop keeping blocks at one less than the next stripes start.
|
||||
|
||||
This can be formalized into the equation:
|
||||
|
||||
`numb_of_cycles * blocks_in_a_cycle + (stripe - 1) * stripe_size`
|
||||
|
||||
which also equals:
|
||||
|
||||
`numb_of_cycles * (stripe_size * amt_of_stripes) + (stripe - 1) * stripe_size`
|
||||
|
||||
Knowing this, let's split this into 2 parts:
|
||||
|
||||
#### Part 1:
|
||||
|
||||
```c++
|
||||
cycle_start * (CRYPTONOTE_PRUNING_STRIPE_SIZE << log_stripes)
|
||||
```
|
||||
|
||||
This gets the block height at the start of the `cycle_start` cycle, so if `cycle_start` was:
|
||||
|
||||
- `0` the height would be `0`
|
||||
- `1` the height would be `32768`
|
||||
- `2` the height would be `65536`
|
||||
|
||||
Which is: `numb_of_cycles * blocks_in_a_cycle`.</br>
|
||||
For normal Monero pruning: `numb_of_cycles * (4096 * 8)`
|
||||
|
||||
#### Part 2:
|
||||
|
||||
```c++
|
||||
(stripe - 1) * CRYPTONOTE_PRUNING_STRIPE_SIZE
|
||||
```
|
||||
|
||||
This gets how many blocks from the start of a cycle until the seeds stripe starts.
|
||||
|
||||
For example if the seed's stripe was:
|
||||
|
||||
- `1` the amount of blocks would be `0`
|
||||
- `2` the amount of blocks would be `4096`
|
||||
- `3` the amount of blocks would be `8192`
|
||||
|
||||
which is: `(stripe-1) * stripe_size`
|
||||
|
||||
As you can see if we add the amount of blocks until the start of a cycle (`numb_of_cycles * blocks_in_a_cycle`) to the amount of blocks into a cycle the until the seed's stripe "kicks in" (`(stripe-1) * stripe_size`) we will get the next un-pruned height.
|
||||
|
||||
```c++
|
||||
if (h + CRYPTONOTE_PRUNING_TIP_BLOCKS > blockchain_height)
|
||||
return blockchain_height < CRYPTONOTE_PRUNING_TIP_BLOCKS ? 0 : blockchain_height - CRYPTONOTE_PRUNING_TIP_BLOCKS;
|
||||
CHECK_AND_ASSERT_MES(h >= block_height, block_height, "h < block_height, unexpected");
|
||||
return h;
|
||||
```
|
||||
|
||||
We now have to check if the height we calculated is above the [tip blocks](#tip-blocks), if it is we get the starting height of the tip blocks and return that or if it isn't over
|
||||
the tip blocks we can just return the calculated height. Yay, we are done!
|
||||
|
||||
## Getting The Next Pruned Block
|
||||
|
||||
For a particular seed and block height we can calculate what the height of the next pruned block will
|
||||
be. The function to do this in Monero is:
|
||||
|
||||
```c++
|
||||
uint64_t get_next_pruned_block_height(uint64_t block_height, uint64_t blockchain_height, uint32_t pruning_seed)
|
||||
{
|
||||
const uint32_t stripe = get_pruning_stripe(pruning_seed);
|
||||
if (stripe == 0)
|
||||
return blockchain_height;
|
||||
if (block_height + CRYPTONOTE_PRUNING_TIP_BLOCKS >= blockchain_height)
|
||||
return blockchain_height;
|
||||
const uint32_t seed_log_stripes = get_pruning_log_stripes(pruning_seed);
|
||||
const uint64_t log_stripes = seed_log_stripes ? seed_log_stripes : CRYPTONOTE_PRUNING_LOG_STRIPES;
|
||||
const uint64_t mask = (1ul << log_stripes) - 1;
|
||||
const uint32_t block_pruning_seed = ((block_height / CRYPTONOTE_PRUNING_STRIPE_SIZE) & mask) + 1;
|
||||
if (block_pruning_seed != stripe)
|
||||
return block_height;
|
||||
const uint32_t next_stripe = 1 + (block_pruning_seed & mask);
|
||||
return get_next_unpruned_block_height(block_height, blockchain_height, tools::make_pruning_seed(next_stripe, log_stripes));
|
||||
}
|
||||
```
|
||||
|
||||
#### explaining what the function is doing in depth:
|
||||
|
||||
```c++
|
||||
const uint32_t stripe = get_pruning_stripe(pruning_seed);
|
||||
if (stripe == 0)
|
||||
return blockchain_height;
|
||||
if (block_height + CRYPTONOTE_PRUNING_TIP_BLOCKS >= blockchain_height)
|
||||
return blockchain_height;
|
||||
```
|
||||
|
||||
This is calculating the [stripe](#getting-a-seeds-pruning-stripe) of the inputted pruning seed, remember if the seed/stripe is `0` that means no pruning so we can return the blockchain height as the next un-pruned height and similarly if the block's height is within [Tip Blocks](#tip-blocks) of the blockchain's height that also means the block won't be pruned.
|
||||
|
||||
Returning the blockchain's height means the next pruned block doesn't currently exist, its bigger than or equal to blockchain_height - CRYPTONOTE_PRUNING_TIP_BLOCKS or it means it
|
||||
will never exist in the case of a zero pruning seed.
|
||||
|
||||
```c++
|
||||
const uint32_t seed_log_stripes = get_pruning_log_stripes(pruning_seed);
|
||||
const uint64_t log_stripes = seed_log_stripes ? seed_log_stripes : CRYPTONOTE_PRUNING_LOG_STRIPES;
|
||||
const uint64_t mask = (1ul << log_stripes) - 1;
|
||||
```
|
||||
|
||||
This is calculating the [log stripes](#getting-a-seeds-log-stripes) of the seed, although Monero currently only allows a log stripes of 3 in the future a higher number could be allowed so this function accounts for that.
|
||||
|
||||
If the seed's log stripes are zero this will set it to `CRYPTONOTE_PRUNING_LOG_STRIPES` which is currently `3`.
|
||||
|
||||
Then this sets the value of `mask` to one less than the amount of [stripes](#stripes), for Monero the amount of stripes is 8 so `mask` will be 7.
|
||||
|
||||
```c++
|
||||
const uint32_t block_pruning_seed = ((block_height / CRYPTONOTE_PRUNING_STRIPE_SIZE) & mask) + 1;
|
||||
if (block_pruning_seed != stripe)
|
||||
return block_height;
|
||||
```
|
||||
|
||||
> There is a typo here it should be block_pruning_stripe, think of this as foreshadowing what we are about to do
|
||||
|
||||
This calculates the [blocks pruning ~~seed~~ STRIPE](#getting-a-blocks-pruning-stripe) using the same method that we saw in [this](#getting-a-blocks-pruning-stripe) function.
|
||||
|
||||
This then checks if the block's stripe is NOT the same as the seed stripe, if you remember if a seed and block don't have the same stripe that means the seed will prune that block, so we can just return the entered `block_height`.
|
||||
|
||||
```c++
|
||||
const uint32_t next_stripe = 1 + (block_pruning_seed & mask);
|
||||
return get_next_unpruned_block_height(block_height, blockchain_height, tools::make_pruning_seed(next_stripe, log_stripes));
|
||||
```
|
||||
|
||||
Because the seed's stripe == the block's stripe we need to work out when our stripe ends (when the next stripe starts to get the next pruned block). To do this we can simply calculate the next stripe, make a [new pruning seed](#generating-pruning-seeds) and pass in that seed, which has a stripe one more than ours, into [get next un-pruned block](#getting-the-next-unpruned-block) to get the start of the next stripe's un-pruned set and therefore the start of our next pruned set.
|
4
books/protocol/svgbob.css
Normal file
4
books/protocol/svgbob.css
Normal file
|
@ -0,0 +1,4 @@
|
|||
/* Ensure text is legible in all themes. */
|
||||
svg text {
|
||||
fill: var(--fg);
|
||||
}
|
|
@ -10,7 +10,7 @@ path = "src/create.rs"
|
|||
|
||||
[dependencies]
|
||||
clap = { workspace = true, features = ["derive", "std"] }
|
||||
cuprate-blockchain = { path = "../../storage/cuprate-blockchain" }
|
||||
cuprate-blockchain = { path = "../../storage/blockchain" }
|
||||
cuprate-consensus = { path = ".." }
|
||||
cuprate-consensus-rules = { path = "../rules" }
|
||||
cuprate-types = { path = "../../types" }
|
||||
|
|
|
@ -3,7 +3,9 @@ use std::{fmt::Write, fs::write};
|
|||
use clap::Parser;
|
||||
use tower::{Service, ServiceExt};
|
||||
|
||||
use cuprate_blockchain::{config::ConfigBuilder, service::DatabaseReadHandle, RuntimeError};
|
||||
use cuprate_blockchain::{
|
||||
config::ConfigBuilder, cuprate_database::RuntimeError, service::DatabaseReadHandle,
|
||||
};
|
||||
use cuprate_types::blockchain::{BCReadRequest, BCResponse};
|
||||
|
||||
use cuprate_fast_sync::{hash_of_hashes, BlockId, HashOfHashes};
|
||||
|
|
|
@ -12,7 +12,7 @@ rayon = ["dep:rayon"]
|
|||
|
||||
[dependencies]
|
||||
cuprate-helper = { path = "../../helper", default-features = false, features = ["std"] }
|
||||
cryptonight-cuprate = {path = "../../cryptonight"}
|
||||
cuprate-cryptonight = {path = "../../cryptonight"}
|
||||
|
||||
monero-serai = { workspace = true, features = ["std"] }
|
||||
multiexp = { workspace = true, features = ["std", "batch"] }
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::collections::HashSet;
|
|||
use crypto_bigint::{CheckedMul, U256};
|
||||
use monero_serai::block::Block;
|
||||
|
||||
use cryptonight_cuprate::*;
|
||||
use cuprate_cryptonight::*;
|
||||
|
||||
use crate::{
|
||||
current_unix_timestamp,
|
||||
|
|
|
@ -21,6 +21,7 @@ use cuprate_consensus_rules::{
|
|||
calculate_pow_hash, check_block, check_block_pow, is_randomx_seed_height,
|
||||
randomx_seed_height, BlockError, RandomX,
|
||||
},
|
||||
hard_forks::HardForkError,
|
||||
miner_tx::MinerTxError,
|
||||
ConsensusError, HardFork,
|
||||
};
|
||||
|
@ -327,6 +328,14 @@ where
|
|||
})
|
||||
.await?;
|
||||
|
||||
let Some(last_block) = blocks.last() else {
|
||||
return Err(ExtendedConsensusError::NoBlocksToVerify);
|
||||
};
|
||||
|
||||
// hard-forks cannot be reversed, so the last block will contain the highest hard fork (provided the
|
||||
// batch is valid).
|
||||
let top_hf_in_batch = last_block.hf_version;
|
||||
|
||||
// A Vec of (timestamp, HF) for each block to calculate the expected difficulty for each block.
|
||||
let mut timestamps_hfs = Vec::with_capacity(blocks.len());
|
||||
let mut new_rx_vm = None;
|
||||
|
@ -338,6 +347,13 @@ where
|
|||
let block_0 = &window[0];
|
||||
let block_1 = &window[1];
|
||||
|
||||
// Make sure no blocks in the batch have a higher hard fork than the last block.
|
||||
if block_0.hf_version > top_hf_in_batch {
|
||||
Err(ConsensusError::Block(BlockError::HardForkError(
|
||||
HardForkError::VersionIncorrect,
|
||||
)))?;
|
||||
}
|
||||
|
||||
if block_0.block_hash != block_1.block.header.previous
|
||||
|| block_0.height != block_1.height - 1
|
||||
{
|
||||
|
@ -346,7 +362,7 @@ where
|
|||
}
|
||||
|
||||
// Cache any potential RX VM seeds as we may need them for future blocks in the batch.
|
||||
if is_randomx_seed_height(block_0.height) {
|
||||
if is_randomx_seed_height(block_0.height) && top_hf_in_batch >= HardFork::V12 {
|
||||
new_rx_vm = Some((block_0.height, block_0.block_hash));
|
||||
}
|
||||
|
||||
|
@ -395,7 +411,20 @@ where
|
|||
Err(ConsensusError::Block(BlockError::PreviousIDIncorrect))?;
|
||||
}
|
||||
|
||||
let mut rx_vms = context.rx_vms;
|
||||
let mut rx_vms = if top_hf_in_batch < HardFork::V12 {
|
||||
HashMap::new()
|
||||
} else {
|
||||
let BlockChainContextResponse::RxVms(rx_vms) = context_svc
|
||||
.ready()
|
||||
.await?
|
||||
.call(BlockChainContextRequest::GetCurrentRxVm)
|
||||
.await?
|
||||
else {
|
||||
panic!("Blockchain context service returned wrong response!");
|
||||
};
|
||||
|
||||
rx_vms
|
||||
};
|
||||
|
||||
// If we have a RX seed in the batch calculate it.
|
||||
if let Some((new_vm_height, new_vm_seed)) = new_rx_vm {
|
||||
|
@ -407,9 +436,7 @@ where
|
|||
.await;
|
||||
|
||||
context_svc
|
||||
.ready()
|
||||
.await?
|
||||
.call(BlockChainContextRequest::NewRXVM((
|
||||
.oneshot(BlockChainContextRequest::NewRXVM((
|
||||
new_vm_seed,
|
||||
new_vm.clone(),
|
||||
)))
|
||||
|
@ -501,7 +528,21 @@ where
|
|||
|
||||
// Set up the block and just pass it to [`verify_prepped_main_chain_block`]
|
||||
|
||||
let rx_vms = context.rx_vms.clone();
|
||||
// We just use the raw `major_version` here, no need to turn it into a `HardFork`.
|
||||
let rx_vms = if block.header.major_version < 12 {
|
||||
HashMap::new()
|
||||
} else {
|
||||
let BlockChainContextResponse::RxVms(rx_vms) = context_svc
|
||||
.ready()
|
||||
.await?
|
||||
.call(BlockChainContextRequest::GetCurrentRxVm)
|
||||
.await?
|
||||
else {
|
||||
panic!("Blockchain context service returned wrong response!");
|
||||
};
|
||||
|
||||
rx_vms
|
||||
};
|
||||
|
||||
let height = context.chain_height;
|
||||
let prepped_block = rayon_spawn_async(move || {
|
||||
|
|
|
@ -85,20 +85,7 @@ impl ContextConfig {
|
|||
pub async fn initialize_blockchain_context<D>(
|
||||
cfg: ContextConfig,
|
||||
database: D,
|
||||
) -> Result<
|
||||
impl Service<
|
||||
BlockChainContextRequest,
|
||||
Response = BlockChainContextResponse,
|
||||
Error = tower::BoxError,
|
||||
Future = impl Future<Output = Result<BlockChainContextResponse, tower::BoxError>>
|
||||
+ Send
|
||||
+ 'static,
|
||||
> + Clone
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
ExtendedConsensusError,
|
||||
>
|
||||
) -> Result<BlockChainContextService, ExtendedConsensusError>
|
||||
where
|
||||
D: Database + Clone + Send + Sync + 'static,
|
||||
D::Future: Send + 'static,
|
||||
|
@ -121,9 +108,6 @@ where
|
|||
pub struct RawBlockChainContext {
|
||||
/// The current cumulative difficulty.
|
||||
pub cumulative_difficulty: u128,
|
||||
/// RandomX VMs, this maps seeds height to VM. Will definitely contain the VM required to calculate the current blocks
|
||||
/// POW hash (if a RX VM is required), may contain more.
|
||||
pub rx_vms: HashMap<u64, Arc<RandomXVM>>,
|
||||
/// Context to verify a block, as needed by [`cuprate-consensus-rules`]
|
||||
pub context_to_verify_block: ContextToVerifyBlock,
|
||||
/// The median long term block weight.
|
||||
|
@ -162,7 +146,7 @@ impl RawBlockChainContext {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns the next blocks long term weight from it's block weight.
|
||||
/// Returns the next blocks long term weight from its block weight.
|
||||
pub fn next_block_long_term_weight(&self, block_weight: usize) -> usize {
|
||||
weight::calculate_block_long_term_weight(
|
||||
&self.current_hf,
|
||||
|
@ -232,6 +216,8 @@ pub struct NewBlockData {
|
|||
pub enum BlockChainContextRequest {
|
||||
/// Get the current blockchain context.
|
||||
GetContext,
|
||||
/// Gets the current RandomX VM.
|
||||
GetCurrentRxVm,
|
||||
/// Get the next difficulties for these blocks.
|
||||
///
|
||||
/// Inputs: a list of block timestamps and hfs
|
||||
|
@ -252,6 +238,8 @@ pub enum BlockChainContextRequest {
|
|||
pub enum BlockChainContextResponse {
|
||||
/// Blockchain context response.
|
||||
Context(BlockChainContext),
|
||||
/// A map of seed height to RandomX VMs.
|
||||
RxVms(HashMap<u64, Arc<RandomXVM>>),
|
||||
/// A list of difficulties.
|
||||
BatchDifficulties(Vec<u128>),
|
||||
/// Ok response.
|
||||
|
|
|
@ -125,64 +125,69 @@ impl RandomXVMCache {
|
|||
}
|
||||
|
||||
/// Get the RandomX VMs.
|
||||
pub fn get_vms(&self) -> HashMap<u64, Arc<RandomXVM>> {
|
||||
pub async fn get_vms(&mut self) -> HashMap<u64, Arc<RandomXVM>> {
|
||||
match self.seeds.len().checked_sub(self.vms.len()) {
|
||||
// No difference in the amount of seeds to VMs.
|
||||
Some(0) => (),
|
||||
// One more seed than VM.
|
||||
Some(1) => {
|
||||
let (seed_height, next_seed_hash) = *self.seeds.front().unwrap();
|
||||
|
||||
let new_vm = 'new_vm_block: {
|
||||
tracing::debug!(
|
||||
"Initializing RandomX VM for seed: {}",
|
||||
hex::encode(next_seed_hash)
|
||||
);
|
||||
|
||||
// Check if we have been given the RX VM from another part of Cuprate.
|
||||
if let Some((cached_hash, cached_vm)) = self.cached_vm.take() {
|
||||
if cached_hash == next_seed_hash {
|
||||
tracing::debug!("VM was already created.");
|
||||
break 'new_vm_block cached_vm;
|
||||
}
|
||||
};
|
||||
|
||||
rayon_spawn_async(move || Arc::new(RandomXVM::new(&next_seed_hash).unwrap()))
|
||||
.await
|
||||
};
|
||||
|
||||
self.vms.insert(seed_height, new_vm);
|
||||
}
|
||||
// More than one more seed than VM.
|
||||
_ => {
|
||||
// this will only happen when syncing and rx activates.
|
||||
tracing::debug!("RandomX has activated, initialising VMs");
|
||||
|
||||
let seeds_clone = self.seeds.clone();
|
||||
self.vms = rayon_spawn_async(move || {
|
||||
seeds_clone
|
||||
.par_iter()
|
||||
.map(|(height, seed)| {
|
||||
let vm = RandomXVM::new(seed).expect("Failed to create RandomX VM!");
|
||||
let vm = Arc::new(vm);
|
||||
(*height, vm)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
self.vms.clone()
|
||||
}
|
||||
|
||||
/// Add a new block to the VM cache.
|
||||
///
|
||||
/// hash is the block hash not the blocks PoW hash.
|
||||
pub async fn new_block(&mut self, height: u64, hash: &[u8; 32], hf: &HardFork) {
|
||||
let should_make_vms = hf >= &HardFork::V12;
|
||||
if should_make_vms && self.vms.len() != self.seeds.len() {
|
||||
// this will only happen when syncing and rx activates.
|
||||
tracing::debug!("RandomX has activated, initialising VMs");
|
||||
|
||||
let seeds_clone = self.seeds.clone();
|
||||
self.vms = rayon_spawn_async(move || {
|
||||
seeds_clone
|
||||
.par_iter()
|
||||
.map(|(height, seed)| {
|
||||
(
|
||||
*height,
|
||||
Arc::new(RandomXVM::new(seed).expect("Failed to create RandomX VM!")),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn new_block(&mut self, height: u64, hash: &[u8; 32]) {
|
||||
if is_randomx_seed_height(height) {
|
||||
tracing::debug!("Block {height} is a randomX seed height, adding it to the cache.",);
|
||||
|
||||
self.seeds.push_front((height, *hash));
|
||||
|
||||
if should_make_vms {
|
||||
let new_vm = 'new_vm_block: {
|
||||
tracing::debug!(
|
||||
"Past hard-fork 12 initializing VM for seed: {}",
|
||||
hex::encode(hash)
|
||||
);
|
||||
|
||||
// Check if we have been given the RX VM from another part of Cuprate.
|
||||
if let Some((cached_hash, cached_vm)) = self.cached_vm.take() {
|
||||
if &cached_hash == hash {
|
||||
tracing::debug!("VM was already created.");
|
||||
break 'new_vm_block cached_vm;
|
||||
}
|
||||
};
|
||||
|
||||
let hash_clone = *hash;
|
||||
rayon_spawn_async(move || Arc::new(RandomXVM::new(&hash_clone).unwrap())).await
|
||||
};
|
||||
|
||||
self.vms.insert(height, new_vm);
|
||||
}
|
||||
|
||||
if self.seeds.len() > RX_SEEDS_CACHED {
|
||||
self.seeds.pop_back();
|
||||
// TODO: This is really not efficient but the amount of VMs cached is not a lot.
|
||||
// HACK: This is really inefficient but the amount of VMs cached is not a lot.
|
||||
self.vms.retain(|height, _| {
|
||||
self.seeds
|
||||
.iter()
|
||||
|
|
|
@ -158,13 +158,15 @@ impl ContextTask {
|
|||
next_difficulty: self.difficulty_cache.next_difficulty(¤t_hf),
|
||||
already_generated_coins: self.already_generated_coins,
|
||||
},
|
||||
rx_vms: self.rx_vm_cache.get_vms(),
|
||||
cumulative_difficulty: self.difficulty_cache.cumulative_difficulty(),
|
||||
median_long_term_weight: self.weight_cache.median_long_term_weight(),
|
||||
top_block_timestamp: self.difficulty_cache.top_block_timestamp(),
|
||||
},
|
||||
})
|
||||
}
|
||||
BlockChainContextRequest::GetCurrentRxVm => {
|
||||
BlockChainContextResponse::RxVms(self.rx_vm_cache.get_vms().await)
|
||||
}
|
||||
BlockChainContextRequest::BatchGetDifficulties(blocks) => {
|
||||
tracing::debug!("Getting batch difficulties len: {}", blocks.len() + 1);
|
||||
|
||||
|
@ -199,15 +201,7 @@ impl ContextTask {
|
|||
|
||||
self.hardfork_state.new_block(new.vote, new.height);
|
||||
|
||||
self.rx_vm_cache
|
||||
.new_block(
|
||||
new.height,
|
||||
&new.block_hash,
|
||||
// We use the current hf and not the hf of the top block as when syncing we need to generate VMs
|
||||
// on the switch to RX not after it.
|
||||
&self.hardfork_state.current_hardfork(),
|
||||
)
|
||||
.await;
|
||||
self.rx_vm_cache.new_block(new.height, &new.block_hash);
|
||||
|
||||
self.chain_height = new.height + 1;
|
||||
self.top_block_hash = new.block_hash;
|
||||
|
|
|
@ -44,6 +44,9 @@ pub enum ExtendedConsensusError {
|
|||
/// One or more statements in the batch verifier was invalid.
|
||||
#[error("One or more statements in the batch verifier was invalid.")]
|
||||
OneOrMoreBatchVerificationStatementsInvalid,
|
||||
/// A request to verify a batch of blocks had no blocks in the batch.
|
||||
#[error("A request to verify a batch of blocks had no blocks in the batch.")]
|
||||
NoBlocksToVerify,
|
||||
}
|
||||
|
||||
/// Initialize the 2 verifier [`tower::Service`]s (block and transaction).
|
||||
|
|
|
@ -47,7 +47,9 @@ async fn rx_vm_created_on_hf_12() {
|
|||
.unwrap();
|
||||
|
||||
assert!(cache.vms.is_empty());
|
||||
cache.new_block(11, &[30; 32], &HardFork::V12).await;
|
||||
cache.new_block(11, &[30; 32]);
|
||||
cache.get_vms().await;
|
||||
|
||||
assert!(!cache.vms.is_empty());
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "cryptonight-cuprate"
|
||||
name = "cuprate-cryptonight"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "A wrapper around Monero's CryptoNight hash function."
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "epee-encoding"
|
||||
name = "cuprate-epee-encoding"
|
||||
version = "0.5.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
@ -12,12 +12,11 @@ rust-version = "1.60"
|
|||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = ["dep:thiserror", "bytes/std", "fixed-bytes/std"]
|
||||
std = ["dep:thiserror", "bytes/std", "cuprate-fixed-bytes/std"]
|
||||
|
||||
[dependencies]
|
||||
fixed-bytes = { path = "../fixed-bytes", default-features = false }
|
||||
cuprate-fixed-bytes = { path = "../fixed-bytes", default-features = false }
|
||||
|
||||
sealed = "0.5.0"
|
||||
paste = "1.0.14"
|
||||
ref-cast = "1.0.22"
|
||||
bytes = { workspace = true }
|
||||
|
|
|
@ -2,9 +2,8 @@ use alloc::{string::ToString, vec, vec::Vec};
|
|||
|
||||
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||
use ref_cast::RefCast;
|
||||
use sealed::sealed;
|
||||
|
||||
use crate::{error::*, value::*, EpeeValue, InnerMarker, Marker};
|
||||
use crate::{error::*, EpeeValue, InnerMarker, Marker};
|
||||
|
||||
#[derive(RefCast)]
|
||||
#[repr(transparent)]
|
||||
|
@ -28,7 +27,6 @@ impl<'a, T: Containerable + EpeeValue> From<&'a Vec<T>> for &'a ContainerAsBlob<
|
|||
}
|
||||
}
|
||||
|
||||
#[sealed]
|
||||
impl<T: Containerable + EpeeValue> EpeeValue for ContainerAsBlob<T> {
|
||||
const MARKER: Marker = Marker::new(InnerMarker::String);
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
//!
|
||||
//! example without macro:
|
||||
//! ```rust
|
||||
//! # use epee_encoding::{EpeeObject, EpeeObjectBuilder, read_epee_value, write_field, to_bytes, from_bytes};
|
||||
//! # use cuprate_epee_encoding::{EpeeObject, EpeeObjectBuilder, read_epee_value, write_field, to_bytes, from_bytes};
|
||||
//! # use bytes::{Buf, BufMut};
|
||||
//!
|
||||
//! pub struct Test {
|
||||
|
@ -21,7 +21,7 @@
|
|||
//! }
|
||||
//!
|
||||
//! impl EpeeObjectBuilder<Test> for __TestEpeeBuilder {
|
||||
//! fn add_field<B: Buf>(&mut self, name: &str, r: &mut B) -> epee_encoding::error::Result<bool> {
|
||||
//! fn add_field<B: Buf>(&mut self, name: &str, r: &mut B) -> cuprate_epee_encoding::error::Result<bool> {
|
||||
//! match name {
|
||||
//! "val" => {self.val = Some(read_epee_value(r)?);}
|
||||
//! _ => return Ok(false),
|
||||
|
@ -29,10 +29,10 @@
|
|||
//! Ok(true)
|
||||
//! }
|
||||
//!
|
||||
//! fn finish(self) -> epee_encoding::error::Result<Test> {
|
||||
//! fn finish(self) -> cuprate_epee_encoding::error::Result<Test> {
|
||||
//! Ok(
|
||||
//! Test {
|
||||
//! val: self.val.ok_or_else(|| epee_encoding::error::Error::Format("Required field was not found!"))?
|
||||
//! val: self.val.ok_or_else(|| cuprate_epee_encoding::error::Error::Format("Required field was not found!"))?
|
||||
//! }
|
||||
//! )
|
||||
//! }
|
||||
|
@ -45,7 +45,7 @@
|
|||
//! 1
|
||||
//! }
|
||||
//!
|
||||
//! fn write_fields<B: BufMut>(self, w: &mut B) -> epee_encoding::error::Result<()> {
|
||||
//! fn write_fields<B: BufMut>(self, w: &mut B) -> cuprate_epee_encoding::error::Result<()> {
|
||||
//! // write the fields
|
||||
//! write_field(self.val, "val", w)
|
||||
//! }
|
||||
|
@ -78,7 +78,7 @@ pub use error::*;
|
|||
use io::*;
|
||||
pub use marker::{InnerMarker, Marker};
|
||||
pub use value::EpeeValue;
|
||||
use varint::*;
|
||||
pub use varint::{read_varint, write_varint};
|
||||
|
||||
/// Header that needs to be at the beginning of every binary blob that follows
|
||||
/// this binary serialization format.
|
||||
|
@ -213,6 +213,87 @@ fn write_epee_value<T: EpeeValue, B: BufMut>(val: T, w: &mut B) -> Result<()> {
|
|||
val.write(w)
|
||||
}
|
||||
|
||||
/// Write a byte array to `w` with [`write_varint`].
|
||||
///
|
||||
/// This function:
|
||||
/// - Writes the length of `t`'s bytes into `w` using [`write_varint`]
|
||||
/// - Writes `t`'s bytes into `w`
|
||||
///
|
||||
/// It is used as the internal [`EpeeValue::write`]
|
||||
/// implementation of byte-like containers such as:
|
||||
/// - [`EpeeValue::<Vec<u8>>::write`]
|
||||
/// - [`EpeeValue::<String>::write`]
|
||||
///
|
||||
/// # Errors
|
||||
/// This will error if:
|
||||
/// - [`write_varint`] fails
|
||||
/// - `w` does not have enough capacity
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// let t: [u8; 8] = [3, 0, 0, 0, 1, 0, 0, 0];
|
||||
/// let mut w = vec![];
|
||||
///
|
||||
/// cuprate_epee_encoding::write_bytes(t, &mut w).unwrap();
|
||||
///
|
||||
/// assert_eq!(w.len(), 9); // length of bytes + bytes
|
||||
/// assert_eq!(w[1..], t);
|
||||
/// ```
|
||||
pub fn write_bytes<T: AsRef<[u8]>, B: BufMut>(t: T, w: &mut B) -> Result<()> {
|
||||
let bytes = t.as_ref();
|
||||
let len = bytes.len();
|
||||
|
||||
write_varint(len.try_into()?, w)?;
|
||||
|
||||
if w.remaining_mut() < len {
|
||||
return Err(Error::IO("Not enough capacity to write bytes"));
|
||||
}
|
||||
|
||||
w.put_slice(bytes);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write an [`Iterator`] of [`EpeeValue`]s to `w` with [`write_varint`].
|
||||
///
|
||||
/// This function:
|
||||
/// - Writes the length of the `iterator`, into `w` using [`write_varint`]
|
||||
/// - [`EpeeValue::write`]s each `T` of the iterator into `w`
|
||||
///
|
||||
/// It is used as the internal [`EpeeValue::write`]
|
||||
/// implementation of containers such as [`EpeeValue::<Vec<T>>::write`].
|
||||
///
|
||||
/// # Errors
|
||||
/// This will error if:
|
||||
/// - [`write_varint`] fails
|
||||
/// - [`EpeeValue::<T>::write`] fails
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// let t: u64 = 3;
|
||||
/// let vec: Vec<u64> = vec![t, t];
|
||||
/// let mut w = vec![];
|
||||
///
|
||||
/// let iter: std::vec::IntoIter<u64> = vec.into_iter();
|
||||
/// cuprate_epee_encoding::write_iterator(iter, &mut w).unwrap();
|
||||
///
|
||||
/// assert_eq!(w.len(), 17);
|
||||
/// assert_eq!(w[1..9], [3, 0, 0, 0, 0, 0, 0, 0]);
|
||||
/// assert_eq!(w[9..], [3, 0, 0, 0, 0, 0, 0, 0]);
|
||||
/// ```
|
||||
pub fn write_iterator<T, I, B>(iterator: I, w: &mut B) -> Result<()>
|
||||
where
|
||||
T: EpeeValue,
|
||||
I: Iterator<Item = T> + ExactSizeIterator,
|
||||
B: BufMut,
|
||||
{
|
||||
write_varint(iterator.len().try_into()?, w)?;
|
||||
for item in iterator.into_iter() {
|
||||
item.write(w)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A helper object builder that just skips every field.
|
||||
#[derive(Default)]
|
||||
struct SkipObjectBuilder;
|
||||
|
|
|
@ -10,7 +10,7 @@ pub use paste::paste;
|
|||
/// // see: <https://github.com/rust-lang/rust/issues/64079>
|
||||
/// mod visibility {
|
||||
///
|
||||
/// use epee_encoding::epee_object;
|
||||
/// use cuprate_epee_encoding::epee_object;
|
||||
///
|
||||
/// struct Example {
|
||||
/// a: u8
|
||||
|
@ -30,7 +30,7 @@ pub use paste::paste;
|
|||
/// // see: <https://github.com/rust-lang/rust/issues/64079>
|
||||
/// mod visibility {
|
||||
///
|
||||
/// use epee_encoding::epee_object;
|
||||
/// use cuprate_epee_encoding::epee_object;
|
||||
///
|
||||
/// struct Example {
|
||||
/// a: u8,
|
||||
|
@ -60,7 +60,7 @@ pub use paste::paste;
|
|||
/// c: u8 as u8,
|
||||
/// // `=> read_fn, write_fn, should_write_fn,` allows you to specify alt field encoding functions.
|
||||
/// // for the required args see the default functions, which are used here:
|
||||
/// d: u8 => epee_encoding::read_epee_value, epee_encoding::write_field, <u8 as epee_encoding::EpeeValue>::should_write,
|
||||
/// d: u8 => cuprate_epee_encoding::read_epee_value, cuprate_epee_encoding::write_field, <u8 as cuprate_epee_encoding::EpeeValue>::should_write,
|
||||
/// // `!flatten` can be used on fields which are epee objects, and it flattens the fields of that object into this object.
|
||||
/// // So for this example `e_f` will not appear in the data but e will.
|
||||
/// // You can't use the other options with this.
|
||||
|
@ -126,25 +126,25 @@ macro_rules! epee_object {
|
|||
$(!flatten: $flat_field: ident: $flat_ty:ty ,)*
|
||||
|
||||
) => {
|
||||
epee_encoding::macros::paste!(
|
||||
cuprate_epee_encoding::macros::paste!(
|
||||
#[allow(non_snake_case)]
|
||||
mod [<__epee_builder_ $obj>] {
|
||||
use super::*;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct [<__Builder $obj>] {
|
||||
$($field: Option<epee_encoding::epee_object!(@internal_field_type $ty, $($ty_as)?)>,)*
|
||||
$($flat_field: <$flat_ty as epee_encoding::EpeeObject>::Builder,)*
|
||||
$($field: Option<cuprate_epee_encoding::epee_object!(@internal_field_type $ty, $($ty_as)?)>,)*
|
||||
$($flat_field: <$flat_ty as cuprate_epee_encoding::EpeeObject>::Builder,)*
|
||||
}
|
||||
|
||||
impl epee_encoding::EpeeObjectBuilder<$obj> for [<__Builder $obj>] {
|
||||
fn add_field<B: epee_encoding::macros::bytes::Buf>(&mut self, name: &str, b: &mut B) -> epee_encoding::error::Result<bool> {
|
||||
impl cuprate_epee_encoding::EpeeObjectBuilder<$obj> for [<__Builder $obj>] {
|
||||
fn add_field<B: cuprate_epee_encoding::macros::bytes::Buf>(&mut self, name: &str, b: &mut B) -> cuprate_epee_encoding::error::Result<bool> {
|
||||
match name {
|
||||
$(epee_encoding::epee_object!(@internal_field_name $field, $($alt_name)?) => {
|
||||
$(cuprate_epee_encoding::epee_object!(@internal_field_name $field, $($alt_name)?) => {
|
||||
if core::mem::replace(&mut self.$field, Some(
|
||||
epee_encoding::epee_object!(@internal_try_right_then_left epee_encoding::read_epee_value(b)?, $($read_fn(b)?)?)
|
||||
cuprate_epee_encoding::epee_object!(@internal_try_right_then_left cuprate_epee_encoding::read_epee_value(b)?, $($read_fn(b)?)?)
|
||||
)).is_some() {
|
||||
Err(epee_encoding::error::Error::Value(format!("Duplicate field in data: {}", epee_encoding::epee_object!(@internal_field_name$field, $($alt_name)?))))?;
|
||||
Err(cuprate_epee_encoding::error::Error::Value(format!("Duplicate field in data: {}", cuprate_epee_encoding::epee_object!(@internal_field_name$field, $($alt_name)?))))?;
|
||||
}
|
||||
Ok(true)
|
||||
},)*
|
||||
|
@ -159,12 +159,12 @@ macro_rules! epee_object {
|
|||
}
|
||||
}
|
||||
|
||||
fn finish(self) -> epee_encoding::error::Result<$obj> {
|
||||
fn finish(self) -> cuprate_epee_encoding::error::Result<$obj> {
|
||||
Ok(
|
||||
$obj {
|
||||
$(
|
||||
$field: {
|
||||
let epee_default_value = epee_encoding::epee_object!(@internal_try_right_then_left epee_encoding::EpeeValue::epee_default_value(), $({
|
||||
let epee_default_value = cuprate_epee_encoding::epee_object!(@internal_try_right_then_left cuprate_epee_encoding::EpeeValue::epee_default_value(), $({
|
||||
let _ = $should_write_fn;
|
||||
None
|
||||
})?);
|
||||
|
@ -173,7 +173,7 @@ macro_rules! epee_object {
|
|||
$(.or(Some($default)))?
|
||||
.or(epee_default_value)
|
||||
$(.map(<$ty_as>::into))?
|
||||
.ok_or(epee_encoding::error::Error::Value(format!("Missing field in data: {}", epee_encoding::epee_object!(@internal_field_name$field, $($alt_name)?))))?
|
||||
.ok_or(cuprate_epee_encoding::error::Error::Value(format!("Missing field in data: {}", cuprate_epee_encoding::epee_object!(@internal_field_name$field, $($alt_name)?))))?
|
||||
},
|
||||
)*
|
||||
|
||||
|
@ -187,16 +187,16 @@ macro_rules! epee_object {
|
|||
}
|
||||
}
|
||||
|
||||
impl epee_encoding::EpeeObject for $obj {
|
||||
impl cuprate_epee_encoding::EpeeObject for $obj {
|
||||
type Builder = [<__epee_builder_ $obj>]::[<__Builder $obj>];
|
||||
|
||||
fn number_of_fields(&self) -> u64 {
|
||||
let mut fields = 0;
|
||||
|
||||
$(
|
||||
let field = epee_encoding::epee_object!(@internal_try_right_then_left &self.$field, $(<&$ty_as>::from(&self.$field))? );
|
||||
let field = cuprate_epee_encoding::epee_object!(@internal_try_right_then_left &self.$field, $(<&$ty_as>::from(&self.$field))? );
|
||||
|
||||
if $((field) != &$default &&)? epee_encoding::epee_object!(@internal_try_right_then_left epee_encoding::EpeeValue::should_write, $($should_write_fn)?)(field )
|
||||
if $((field) != &$default &&)? cuprate_epee_encoding::epee_object!(@internal_try_right_then_left cuprate_epee_encoding::EpeeValue::should_write, $($should_write_fn)?)(field )
|
||||
{
|
||||
fields += 1;
|
||||
}
|
||||
|
@ -209,13 +209,13 @@ macro_rules! epee_object {
|
|||
fields
|
||||
}
|
||||
|
||||
fn write_fields<B: epee_encoding::macros::bytes::BufMut>(self, w: &mut B) -> epee_encoding::error::Result<()> {
|
||||
fn write_fields<B: cuprate_epee_encoding::macros::bytes::BufMut>(self, w: &mut B) -> cuprate_epee_encoding::error::Result<()> {
|
||||
$(
|
||||
let field = epee_encoding::epee_object!(@internal_try_right_then_left self.$field, $(<$ty_as>::from(self.$field))? );
|
||||
let field = cuprate_epee_encoding::epee_object!(@internal_try_right_then_left self.$field, $(<$ty_as>::from(self.$field))? );
|
||||
|
||||
if $(field != $default &&)? epee_encoding::epee_object!(@internal_try_right_then_left epee_encoding::EpeeValue::should_write, $($should_write_fn)?)(&field )
|
||||
if $(field != $default &&)? cuprate_epee_encoding::epee_object!(@internal_try_right_then_left cuprate_epee_encoding::EpeeValue::should_write, $($should_write_fn)?)(&field )
|
||||
{
|
||||
epee_encoding::epee_object!(@internal_try_right_then_left epee_encoding::write_field, $($write_fn)?)((field), epee_encoding::epee_object!(@internal_field_name$field, $($alt_name)?), w)?;
|
||||
cuprate_epee_encoding::epee_object!(@internal_try_right_then_left cuprate_epee_encoding::write_field, $($write_fn)?)((field), cuprate_epee_encoding::epee_object!(@internal_field_name$field, $($alt_name)?), w)?;
|
||||
}
|
||||
)*
|
||||
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
//! This module contains a `sealed` [`EpeeValue`] trait and different impls for
|
||||
//! the different possible base epee values.
|
||||
use alloc::{string::String, vec, vec::Vec};
|
||||
//! This module contains a [`EpeeValue`] trait and
|
||||
//! impls for some possible base epee values.
|
||||
|
||||
use alloc::{string::String, vec::Vec};
|
||||
use core::fmt::Debug;
|
||||
|
||||
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||
use sealed::sealed;
|
||||
|
||||
use fixed_bytes::{ByteArray, ByteArrayVec};
|
||||
use cuprate_fixed_bytes::{ByteArray, ByteArrayVec};
|
||||
|
||||
use crate::{
|
||||
io::*, varint::*, EpeeObject, Error, InnerMarker, Marker, Result, MAX_STRING_LEN_POSSIBLE,
|
||||
io::{checked_read_primitive, checked_write_primitive},
|
||||
varint::{read_varint, write_varint},
|
||||
write_bytes, write_iterator, EpeeObject, Error, InnerMarker, Marker, Result,
|
||||
MAX_STRING_LEN_POSSIBLE,
|
||||
};
|
||||
|
||||
/// A trait for epee values, this trait is sealed as all possible epee values are
|
||||
/// defined in the lib, to make an [`EpeeValue`] outside the lib you will need to
|
||||
/// use the trait [`EpeeObject`].
|
||||
#[sealed(pub(crate))]
|
||||
/// A trait for epee values.
|
||||
///
|
||||
/// All [`EpeeObject`] objects automatically implement [`EpeeValue`].
|
||||
pub trait EpeeValue: Sized {
|
||||
const MARKER: Marker;
|
||||
|
||||
|
@ -37,7 +39,6 @@ pub trait EpeeValue: Sized {
|
|||
fn write<B: BufMut>(self, w: &mut B) -> Result<()>;
|
||||
}
|
||||
|
||||
#[sealed]
|
||||
impl<T: EpeeObject> EpeeValue for T {
|
||||
const MARKER: Marker = Marker::new(InnerMarker::Object);
|
||||
|
||||
|
@ -56,7 +57,6 @@ impl<T: EpeeObject> EpeeValue for T {
|
|||
}
|
||||
}
|
||||
|
||||
#[sealed]
|
||||
impl<T: EpeeObject> EpeeValue for Vec<T> {
|
||||
const MARKER: Marker = T::MARKER.into_seq();
|
||||
|
||||
|
@ -86,15 +86,10 @@ impl<T: EpeeObject> EpeeValue for Vec<T> {
|
|||
}
|
||||
|
||||
fn write<B: BufMut>(self, w: &mut B) -> Result<()> {
|
||||
write_varint(self.len().try_into()?, w)?;
|
||||
for item in self.into_iter() {
|
||||
item.write(w)?;
|
||||
}
|
||||
Ok(())
|
||||
write_iterator(self.into_iter(), w)
|
||||
}
|
||||
}
|
||||
|
||||
#[sealed]
|
||||
impl<T: EpeeObject + Debug, const N: usize> EpeeValue for [T; N] {
|
||||
const MARKER: Marker = <T>::MARKER.into_seq();
|
||||
|
||||
|
@ -109,17 +104,12 @@ impl<T: EpeeObject + Debug, const N: usize> EpeeValue for [T; N] {
|
|||
}
|
||||
|
||||
fn write<B: BufMut>(self, w: &mut B) -> Result<()> {
|
||||
write_varint(self.len().try_into()?, w)?;
|
||||
for item in self.into_iter() {
|
||||
item.write(w)?;
|
||||
}
|
||||
Ok(())
|
||||
write_iterator(self.into_iter(), w)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! epee_numb {
|
||||
($numb:ty, $marker:ident, $read_fn:ident, $write_fn:ident) => {
|
||||
#[sealed]
|
||||
impl EpeeValue for $numb {
|
||||
const MARKER: Marker = Marker::new(InnerMarker::$marker);
|
||||
|
||||
|
@ -148,7 +138,6 @@ epee_numb!(u32, U32, get_u32_le, put_u32_le);
|
|||
epee_numb!(u64, U64, get_u64_le, put_u64_le);
|
||||
epee_numb!(f64, F64, get_f64_le, put_f64_le);
|
||||
|
||||
#[sealed]
|
||||
impl EpeeValue for bool {
|
||||
const MARKER: Marker = Marker::new(InnerMarker::Bool);
|
||||
|
||||
|
@ -165,7 +154,6 @@ impl EpeeValue for bool {
|
|||
}
|
||||
}
|
||||
|
||||
#[sealed]
|
||||
impl EpeeValue for Vec<u8> {
|
||||
const MARKER: Marker = Marker::new(InnerMarker::String);
|
||||
|
||||
|
@ -198,18 +186,10 @@ impl EpeeValue for Vec<u8> {
|
|||
}
|
||||
|
||||
fn write<B: BufMut>(self, w: &mut B) -> Result<()> {
|
||||
write_varint(self.len().try_into()?, w)?;
|
||||
|
||||
if w.remaining_mut() < self.len() {
|
||||
return Err(Error::IO("Not enough capacity to write bytes"));
|
||||
}
|
||||
|
||||
w.put_slice(&self);
|
||||
Ok(())
|
||||
write_bytes(self, w)
|
||||
}
|
||||
}
|
||||
|
||||
#[sealed::sealed]
|
||||
impl EpeeValue for Bytes {
|
||||
const MARKER: Marker = Marker::new(InnerMarker::String);
|
||||
|
||||
|
@ -239,18 +219,10 @@ impl EpeeValue for Bytes {
|
|||
}
|
||||
|
||||
fn write<B: BufMut>(self, w: &mut B) -> Result<()> {
|
||||
write_varint(self.len().try_into()?, w)?;
|
||||
|
||||
if w.remaining_mut() < self.len() {
|
||||
return Err(Error::IO("Not enough capacity to write bytes"));
|
||||
}
|
||||
|
||||
w.put(self);
|
||||
Ok(())
|
||||
write_bytes(self, w)
|
||||
}
|
||||
}
|
||||
|
||||
#[sealed::sealed]
|
||||
impl EpeeValue for BytesMut {
|
||||
const MARKER: Marker = Marker::new(InnerMarker::String);
|
||||
|
||||
|
@ -283,18 +255,10 @@ impl EpeeValue for BytesMut {
|
|||
}
|
||||
|
||||
fn write<B: BufMut>(self, w: &mut B) -> Result<()> {
|
||||
write_varint(self.len().try_into()?, w)?;
|
||||
|
||||
if w.remaining_mut() < self.len() {
|
||||
return Err(Error::IO("Not enough capacity to write bytes"));
|
||||
}
|
||||
|
||||
w.put(self);
|
||||
Ok(())
|
||||
write_bytes(self, w)
|
||||
}
|
||||
}
|
||||
|
||||
#[sealed::sealed]
|
||||
impl<const N: usize> EpeeValue for ByteArrayVec<N> {
|
||||
const MARKER: Marker = Marker::new(InnerMarker::String);
|
||||
|
||||
|
@ -326,19 +290,10 @@ impl<const N: usize> EpeeValue for ByteArrayVec<N> {
|
|||
|
||||
fn write<B: BufMut>(self, w: &mut B) -> Result<()> {
|
||||
let bytes = self.take_bytes();
|
||||
|
||||
write_varint(bytes.len().try_into()?, w)?;
|
||||
|
||||
if w.remaining_mut() < bytes.len() {
|
||||
return Err(Error::IO("Not enough capacity to write bytes"));
|
||||
}
|
||||
|
||||
w.put(bytes);
|
||||
Ok(())
|
||||
write_bytes(bytes, w)
|
||||
}
|
||||
}
|
||||
|
||||
#[sealed::sealed]
|
||||
impl<const N: usize> EpeeValue for ByteArray<N> {
|
||||
const MARKER: Marker = Marker::new(InnerMarker::String);
|
||||
|
||||
|
@ -362,19 +317,10 @@ impl<const N: usize> EpeeValue for ByteArray<N> {
|
|||
|
||||
fn write<B: BufMut>(self, w: &mut B) -> Result<()> {
|
||||
let bytes = self.take_bytes();
|
||||
|
||||
write_varint(N.try_into().unwrap(), w)?;
|
||||
|
||||
if w.remaining_mut() < N {
|
||||
return Err(Error::IO("Not enough capacity to write bytes"));
|
||||
}
|
||||
|
||||
w.put(bytes);
|
||||
Ok(())
|
||||
write_bytes(bytes, w)
|
||||
}
|
||||
}
|
||||
|
||||
#[sealed]
|
||||
impl EpeeValue for String {
|
||||
const MARKER: Marker = Marker::new(InnerMarker::String);
|
||||
|
||||
|
@ -392,18 +338,10 @@ impl EpeeValue for String {
|
|||
}
|
||||
|
||||
fn write<B: BufMut>(self, w: &mut B) -> Result<()> {
|
||||
write_varint(self.len().try_into()?, w)?;
|
||||
|
||||
if w.remaining_mut() < self.len() {
|
||||
return Err(Error::IO("Not enough capacity to write bytes"));
|
||||
}
|
||||
|
||||
w.put_slice(self.as_bytes());
|
||||
Ok(())
|
||||
write_bytes(self, w)
|
||||
}
|
||||
}
|
||||
|
||||
#[sealed]
|
||||
impl<const N: usize> EpeeValue for [u8; N] {
|
||||
const MARKER: Marker = Marker::new(InnerMarker::String);
|
||||
|
||||
|
@ -418,18 +356,10 @@ impl<const N: usize> EpeeValue for [u8; N] {
|
|||
}
|
||||
|
||||
fn write<B: BufMut>(self, w: &mut B) -> Result<()> {
|
||||
write_varint(self.len().try_into()?, w)?;
|
||||
|
||||
if w.remaining_mut() < self.len() {
|
||||
return Err(Error::IO("Not enough capacity to write bytes"));
|
||||
}
|
||||
|
||||
w.put_slice(&self);
|
||||
Ok(())
|
||||
write_bytes(self, w)
|
||||
}
|
||||
}
|
||||
|
||||
#[sealed]
|
||||
impl<const N: usize> EpeeValue for Vec<[u8; N]> {
|
||||
const MARKER: Marker = <[u8; N]>::MARKER.into_seq();
|
||||
|
||||
|
@ -460,17 +390,12 @@ impl<const N: usize> EpeeValue for Vec<[u8; N]> {
|
|||
}
|
||||
|
||||
fn write<B: BufMut>(self, w: &mut B) -> Result<()> {
|
||||
write_varint(self.len().try_into()?, w)?;
|
||||
for item in self.into_iter() {
|
||||
item.write(w)?;
|
||||
}
|
||||
Ok(())
|
||||
write_iterator(self.into_iter(), w)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! epee_seq {
|
||||
($val:ty) => {
|
||||
#[sealed]
|
||||
impl EpeeValue for Vec<$val> {
|
||||
const MARKER: Marker = <$val>::MARKER.into_seq();
|
||||
|
||||
|
@ -501,15 +426,10 @@ macro_rules! epee_seq {
|
|||
}
|
||||
|
||||
fn write<B: BufMut>(self, w: &mut B) -> Result<()> {
|
||||
write_varint(self.len().try_into()?, w)?;
|
||||
for item in self.into_iter() {
|
||||
item.write(w)?;
|
||||
}
|
||||
Ok(())
|
||||
write_iterator(self.into_iter(), w)
|
||||
}
|
||||
}
|
||||
|
||||
#[sealed]
|
||||
impl<const N: usize> EpeeValue for [$val; N] {
|
||||
const MARKER: Marker = <$val>::MARKER.into_seq();
|
||||
|
||||
|
@ -524,11 +444,7 @@ macro_rules! epee_seq {
|
|||
}
|
||||
|
||||
fn write<B: BufMut>(self, w: &mut B) -> Result<()> {
|
||||
write_varint(self.len().try_into()?, w)?;
|
||||
for item in self.into_iter() {
|
||||
item.write(w)?;
|
||||
}
|
||||
Ok(())
|
||||
write_iterator(self.into_iter(), w)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -548,7 +464,6 @@ epee_seq!(String);
|
|||
epee_seq!(Bytes);
|
||||
epee_seq!(BytesMut);
|
||||
|
||||
#[sealed]
|
||||
impl<T: EpeeValue> EpeeValue for Option<T> {
|
||||
const MARKER: Marker = T::MARKER;
|
||||
|
||||
|
|
|
@ -7,6 +7,18 @@ const FITS_IN_ONE_BYTE: u64 = 2_u64.pow(8 - SIZE_OF_SIZE_MARKER) - 1;
|
|||
const FITS_IN_TWO_BYTES: u64 = 2_u64.pow(16 - SIZE_OF_SIZE_MARKER) - 1;
|
||||
const FITS_IN_FOUR_BYTES: u64 = 2_u64.pow(32 - SIZE_OF_SIZE_MARKER) - 1;
|
||||
|
||||
/// Read an epee variable sized number from `r`.
|
||||
///
|
||||
/// ```rust
|
||||
/// use cuprate_epee_encoding::read_varint;
|
||||
///
|
||||
/// assert_eq!(read_varint(&mut [252].as_slice()).unwrap(), 63);
|
||||
/// assert_eq!(read_varint(&mut [1, 1].as_slice()).unwrap(), 64);
|
||||
/// assert_eq!(read_varint(&mut [253, 255].as_slice()).unwrap(), 16_383);
|
||||
/// assert_eq!(read_varint(&mut [2, 0, 1, 0].as_slice()).unwrap(), 16_384);
|
||||
/// assert_eq!(read_varint(&mut [254, 255, 255, 255].as_slice()).unwrap(), 1_073_741_823);
|
||||
/// assert_eq!(read_varint(&mut [3, 0, 0, 0, 1, 0, 0, 0].as_slice()).unwrap(), 1_073_741_824);
|
||||
/// ```
|
||||
pub fn read_varint<B: Buf>(r: &mut B) -> Result<u64> {
|
||||
if !r.has_remaining() {
|
||||
Err(Error::IO("Not enough bytes to build VarInt"))?
|
||||
|
@ -26,6 +38,26 @@ pub fn read_varint<B: Buf>(r: &mut B) -> Result<u64> {
|
|||
Ok(vi)
|
||||
}
|
||||
|
||||
/// Write an epee variable sized number into `w`.
|
||||
///
|
||||
/// ```rust
|
||||
/// use cuprate_epee_encoding::write_varint;
|
||||
///
|
||||
/// let mut buf = vec![];
|
||||
///
|
||||
/// for (number, expected_bytes) in [
|
||||
/// (63, [252].as_slice()),
|
||||
/// (64, [1, 1].as_slice()),
|
||||
/// (16_383, [253, 255].as_slice()),
|
||||
/// (16_384, [2, 0, 1, 0].as_slice()),
|
||||
/// (1_073_741_823, [254, 255, 255, 255].as_slice()),
|
||||
/// (1_073_741_824, [3, 0, 0, 0, 1, 0, 0, 0].as_slice()),
|
||||
/// ] {
|
||||
/// buf.clear();
|
||||
/// write_varint(number, &mut buf);
|
||||
/// assert_eq!(buf.as_slice(), expected_bytes);
|
||||
/// }
|
||||
/// ```
|
||||
pub fn write_varint<B: BufMut>(number: u64, w: &mut B) -> Result<()> {
|
||||
let size_marker = match number {
|
||||
0..=FITS_IN_ONE_BYTE => 0,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use epee_encoding::{epee_object, from_bytes, to_bytes};
|
||||
use cuprate_epee_encoding::{epee_object, from_bytes, to_bytes};
|
||||
|
||||
struct AltName {
|
||||
val: u8,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use epee_encoding::{epee_object, from_bytes};
|
||||
use cuprate_epee_encoding::{epee_object, from_bytes};
|
||||
|
||||
struct T {
|
||||
a: u8,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use epee_encoding::{epee_object, from_bytes, to_bytes};
|
||||
use cuprate_epee_encoding::{epee_object, from_bytes, to_bytes};
|
||||
|
||||
pub struct Optional {
|
||||
val: u8,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use epee_encoding::{epee_object, from_bytes, to_bytes};
|
||||
use cuprate_epee_encoding::{epee_object, from_bytes, to_bytes};
|
||||
|
||||
struct Child {
|
||||
val: u64,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use epee_encoding::{epee_object, from_bytes, to_bytes};
|
||||
use cuprate_epee_encoding::{epee_object, from_bytes, to_bytes};
|
||||
use std::ops::Deref;
|
||||
|
||||
#[derive(Clone)]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use epee_encoding::{epee_object, from_bytes, to_bytes};
|
||||
use cuprate_epee_encoding::{epee_object, from_bytes, to_bytes};
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||
pub struct SupportFlags(u32);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use epee_encoding::{epee_object, from_bytes, to_bytes};
|
||||
use cuprate_epee_encoding::{epee_object, from_bytes, to_bytes};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct BaseResponse {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use epee_encoding::{epee_object, from_bytes};
|
||||
use cuprate_epee_encoding::{epee_object, from_bytes};
|
||||
|
||||
struct ObjSeq {
|
||||
seq: Vec<ObjSeq>,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use epee_encoding::{epee_object, from_bytes};
|
||||
use cuprate_epee_encoding::{epee_object, from_bytes};
|
||||
|
||||
struct D {
|
||||
val: u8,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "fixed-bytes"
|
||||
name = "cuprate-fixed-bytes"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "levin-cuprate"
|
||||
name = "cuprate-levin"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "A crate for working with the Levin protocol in Rust."
|
||||
|
|
|
@ -8,7 +8,7 @@ use tokio::{
|
|||
};
|
||||
use tokio_util::codec::{FramedRead, FramedWrite};
|
||||
|
||||
use levin_cuprate::{
|
||||
use cuprate_levin::{
|
||||
message::make_fragmented_messages, BucketBuilder, BucketError, LevinBody, LevinCommand,
|
||||
LevinMessageCodec, MessageType, Protocol,
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "monero-wire"
|
||||
name = "cuprate-wire"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
@ -8,12 +8,12 @@ repository = "https://github.com/SyntheticBird45/cuprate/tree/main/net/monero-wi
|
|||
|
||||
[features]
|
||||
default = []
|
||||
tracing = ["levin-cuprate/tracing"]
|
||||
tracing = ["cuprate-levin/tracing"]
|
||||
|
||||
[dependencies]
|
||||
levin-cuprate = {path="../levin"}
|
||||
epee-encoding = { path = "../epee-encoding" }
|
||||
fixed-bytes = { path = "../fixed-bytes" }
|
||||
cuprate-levin = { path = "../levin" }
|
||||
cuprate-epee-encoding = { path = "../epee-encoding" }
|
||||
cuprate-fixed-bytes = { path = "../fixed-bytes" }
|
||||
|
||||
bitflags = { workspace = true, features = ["std"] }
|
||||
bytes = { workspace = true, features = ["std"] }
|
|
@ -25,11 +25,11 @@
|
|||
pub mod network_address;
|
||||
pub mod p2p;
|
||||
|
||||
pub use levin_cuprate::BucketError;
|
||||
pub use cuprate_levin::BucketError;
|
||||
pub use network_address::{NetZone, NetworkAddress};
|
||||
pub use p2p::*;
|
||||
|
||||
// re-export.
|
||||
pub use levin_cuprate as levin;
|
||||
pub use cuprate_levin as levin;
|
||||
|
||||
pub type MoneroWireCodec = levin_cuprate::codec::LevinMessageCodec<Message>;
|
||||
pub type MoneroWireCodec = cuprate_levin::codec::LevinMessageCodec<Message>;
|
|
@ -18,7 +18,7 @@
|
|||
//! I2p. Currently this module only has IPv(4/6).
|
||||
//!
|
||||
use bytes::BufMut;
|
||||
use epee_encoding::EpeeObject;
|
||||
use cuprate_epee_encoding::EpeeObject;
|
||||
use std::{hash::Hash, net, net::SocketAddr};
|
||||
|
||||
mod epee_builder;
|
||||
|
@ -45,7 +45,7 @@ impl EpeeObject for NetworkAddress {
|
|||
2
|
||||
}
|
||||
|
||||
fn write_fields<B: BufMut>(self, w: &mut B) -> epee_encoding::Result<()> {
|
||||
fn write_fields<B: BufMut>(self, w: &mut B) -> cuprate_epee_encoding::Result<()> {
|
||||
TaggedNetworkAddress::from(self).write_fields(w)
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
use bytes::Buf;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
|
||||
|
||||
use epee_encoding::{epee_object, EpeeObjectBuilder};
|
||||
use cuprate_epee_encoding::{epee_object, EpeeObjectBuilder};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::NetworkAddress;
|
||||
|
@ -19,19 +19,28 @@ epee_object!(
|
|||
);
|
||||
|
||||
impl EpeeObjectBuilder<NetworkAddress> for TaggedNetworkAddress {
|
||||
fn add_field<B: Buf>(&mut self, name: &str, b: &mut B) -> epee_encoding::Result<bool> {
|
||||
fn add_field<B: Buf>(&mut self, name: &str, b: &mut B) -> cuprate_epee_encoding::Result<bool> {
|
||||
match name {
|
||||
"type" => {
|
||||
if std::mem::replace(&mut self.ty, Some(epee_encoding::read_epee_value(b)?))
|
||||
.is_some()
|
||||
if std::mem::replace(
|
||||
&mut self.ty,
|
||||
Some(cuprate_epee_encoding::read_epee_value(b)?),
|
||||
)
|
||||
.is_some()
|
||||
{
|
||||
return Err(epee_encoding::Error::Format("Duplicate field in data."));
|
||||
return Err(cuprate_epee_encoding::Error::Format(
|
||||
"Duplicate field in data.",
|
||||
));
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
"addr" => {
|
||||
if std::mem::replace(&mut self.addr, epee_encoding::read_epee_value(b)?).is_some() {
|
||||
return Err(epee_encoding::Error::Format("Duplicate field in data."));
|
||||
if std::mem::replace(&mut self.addr, cuprate_epee_encoding::read_epee_value(b)?)
|
||||
.is_some()
|
||||
{
|
||||
return Err(cuprate_epee_encoding::Error::Format(
|
||||
"Duplicate field in data.",
|
||||
));
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
@ -39,9 +48,9 @@ impl EpeeObjectBuilder<NetworkAddress> for TaggedNetworkAddress {
|
|||
}
|
||||
}
|
||||
|
||||
fn finish(self) -> epee_encoding::Result<NetworkAddress> {
|
||||
fn finish(self) -> cuprate_epee_encoding::Result<NetworkAddress> {
|
||||
self.try_into()
|
||||
.map_err(|_| epee_encoding::Error::Value("Invalid network address".to_string()))
|
||||
.map_err(|_| cuprate_epee_encoding::Error::Value("Invalid network address".to_string()))
|
||||
}
|
||||
}
|
||||
|
|
@ -20,8 +20,8 @@ use std::fmt::Formatter;
|
|||
|
||||
use bytes::{Buf, BytesMut};
|
||||
|
||||
use epee_encoding::epee_object;
|
||||
use levin_cuprate::{
|
||||
use cuprate_epee_encoding::epee_object;
|
||||
use cuprate_levin::{
|
||||
BucketBuilder, BucketError, LevinBody, LevinCommand as LevinCommandTrait, MessageType,
|
||||
};
|
||||
|
||||
|
@ -154,22 +154,23 @@ impl From<LevinCommand> for u32 {
|
|||
}
|
||||
}
|
||||
|
||||
fn decode_message<B: Buf, T: epee_encoding::EpeeObject, Ret>(
|
||||
fn decode_message<B: Buf, T: cuprate_epee_encoding::EpeeObject, Ret>(
|
||||
ret: impl FnOnce(T) -> Ret,
|
||||
buf: &mut B,
|
||||
) -> Result<Ret, BucketError> {
|
||||
let t = epee_encoding::from_bytes(buf).map_err(|e| BucketError::BodyDecodingError(e.into()))?;
|
||||
let t = cuprate_epee_encoding::from_bytes(buf)
|
||||
.map_err(|e| BucketError::BodyDecodingError(e.into()))?;
|
||||
Ok(ret(t))
|
||||
}
|
||||
|
||||
fn build_message<T: epee_encoding::EpeeObject>(
|
||||
fn build_message<T: cuprate_epee_encoding::EpeeObject>(
|
||||
id: LevinCommand,
|
||||
val: T,
|
||||
builder: &mut BucketBuilder<LevinCommand>,
|
||||
) -> Result<(), BucketError> {
|
||||
builder.set_command(id);
|
||||
builder.set_body(
|
||||
epee_encoding::to_bytes(val)
|
||||
cuprate_epee_encoding::to_bytes(val)
|
||||
.map(BytesMut::freeze)
|
||||
.map_err(|e| BucketError::BodyDecodingError(e.into()))?,
|
||||
);
|
||||
|
@ -280,13 +281,13 @@ impl RequestMessage {
|
|||
C::Handshake => decode_message(RequestMessage::Handshake, buf)?,
|
||||
C::TimedSync => decode_message(RequestMessage::TimedSync, buf)?,
|
||||
C::Ping => {
|
||||
epee_encoding::from_bytes::<EmptyMessage, _>(buf)
|
||||
cuprate_epee_encoding::from_bytes::<EmptyMessage, _>(buf)
|
||||
.map_err(|e| BucketError::BodyDecodingError(e.into()))?;
|
||||
|
||||
RequestMessage::Ping
|
||||
}
|
||||
C::SupportFlags => {
|
||||
epee_encoding::from_bytes::<EmptyMessage, _>(buf)
|
||||
cuprate_epee_encoding::from_bytes::<EmptyMessage, _>(buf)
|
||||
.map_err(|e| BucketError::BodyDecodingError(e.into()))?;
|
||||
|
||||
RequestMessage::SupportFlags
|
|
@ -19,7 +19,7 @@
|
|||
//! protocol messages.
|
||||
|
||||
use bytes::Bytes;
|
||||
use epee_encoding::epee_object;
|
||||
use cuprate_epee_encoding::epee_object;
|
||||
|
||||
use super::common::{BasicNodeData, CoreSyncData, PeerListEntryBase, PeerSupportFlags};
|
||||
|
||||
|
@ -134,7 +134,8 @@ mod tests {
|
|||
186, 15, 178, 70, 173, 170, 187, 31, 70, 50, 227, 11, 116, 111, 112, 95, 118, 101, 114,
|
||||
115, 105, 111, 110, 8, 1,
|
||||
];
|
||||
let handshake: HandshakeRequest = epee_encoding::from_bytes(&mut &bytes[..]).unwrap();
|
||||
let handshake: HandshakeRequest =
|
||||
cuprate_epee_encoding::from_bytes(&mut &bytes[..]).unwrap();
|
||||
let basic_node_data = BasicNodeData {
|
||||
my_port: 0,
|
||||
network_id: [
|
||||
|
@ -161,8 +162,9 @@ mod tests {
|
|||
assert_eq!(basic_node_data, handshake.node_data);
|
||||
assert_eq!(core_sync_data, handshake.payload_data);
|
||||
|
||||
let mut encoded_bytes = epee_encoding::to_bytes(handshake.clone()).unwrap();
|
||||
let handshake_2: HandshakeRequest = epee_encoding::from_bytes(&mut encoded_bytes).unwrap();
|
||||
let mut encoded_bytes = cuprate_epee_encoding::to_bytes(handshake.clone()).unwrap();
|
||||
let handshake_2: HandshakeRequest =
|
||||
cuprate_epee_encoding::from_bytes(&mut encoded_bytes).unwrap();
|
||||
|
||||
assert_eq!(handshake, handshake_2);
|
||||
}
|
||||
|
@ -938,7 +940,8 @@ mod tests {
|
|||
181, 216, 193, 135, 23, 186, 168, 207, 119, 86, 235, 11, 116, 111, 112, 95, 118, 101,
|
||||
114, 115, 105, 111, 110, 8, 16,
|
||||
];
|
||||
let handshake: HandshakeResponse = epee_encoding::from_bytes(&mut &bytes[..]).unwrap();
|
||||
let handshake: HandshakeResponse =
|
||||
cuprate_epee_encoding::from_bytes(&mut &bytes[..]).unwrap();
|
||||
|
||||
let basic_node_data = BasicNodeData {
|
||||
my_port: 18080,
|
||||
|
@ -967,9 +970,10 @@ mod tests {
|
|||
assert_eq!(core_sync_data, handshake.payload_data);
|
||||
assert_eq!(250, handshake.local_peerlist_new.len());
|
||||
|
||||
let mut encoded_bytes = epee_encoding::to_bytes(handshake.clone()).unwrap();
|
||||
let mut encoded_bytes = cuprate_epee_encoding::to_bytes(handshake.clone()).unwrap();
|
||||
|
||||
let handshake_2: HandshakeResponse = epee_encoding::from_bytes(&mut encoded_bytes).unwrap();
|
||||
let handshake_2: HandshakeResponse =
|
||||
cuprate_epee_encoding::from_bytes(&mut encoded_bytes).unwrap();
|
||||
|
||||
assert_eq!(handshake, handshake_2);
|
||||
}
|
|
@ -18,8 +18,8 @@
|
|||
use bitflags::bitflags;
|
||||
use bytes::{Buf, BufMut, Bytes};
|
||||
|
||||
use epee_encoding::{epee_object, EpeeValue, InnerMarker};
|
||||
use fixed_bytes::ByteArray;
|
||||
use cuprate_epee_encoding::{epee_object, EpeeValue, InnerMarker};
|
||||
use cuprate_fixed_bytes::ByteArray;
|
||||
|
||||
use crate::NetworkAddress;
|
||||
|
||||
|
@ -241,12 +241,12 @@ epee_object!(
|
|||
txs: TransactionBlobs = TransactionBlobs::None => tx_blob_read, tx_blob_write, should_write_tx_blobs,
|
||||
);
|
||||
|
||||
fn tx_blob_read<B: Buf>(b: &mut B) -> epee_encoding::Result<TransactionBlobs> {
|
||||
let marker = epee_encoding::read_marker(b)?;
|
||||
fn tx_blob_read<B: Buf>(b: &mut B) -> cuprate_epee_encoding::Result<TransactionBlobs> {
|
||||
let marker = cuprate_epee_encoding::read_marker(b)?;
|
||||
match marker.inner_marker {
|
||||
InnerMarker::Object => Ok(TransactionBlobs::Pruned(Vec::read(b, &marker)?)),
|
||||
InnerMarker::String => Ok(TransactionBlobs::Normal(Vec::read(b, &marker)?)),
|
||||
_ => Err(epee_encoding::Error::Value(
|
||||
_ => Err(cuprate_epee_encoding::Error::Value(
|
||||
"Invalid marker for tx blobs".to_string(),
|
||||
)),
|
||||
}
|
||||
|
@ -256,11 +256,15 @@ fn tx_blob_write<B: BufMut>(
|
|||
val: TransactionBlobs,
|
||||
field_name: &str,
|
||||
w: &mut B,
|
||||
) -> epee_encoding::Result<()> {
|
||||
) -> cuprate_epee_encoding::Result<()> {
|
||||
if should_write_tx_blobs(&val) {
|
||||
match val {
|
||||
TransactionBlobs::Normal(bytes) => epee_encoding::write_field(bytes, field_name, w)?,
|
||||
TransactionBlobs::Pruned(obj) => epee_encoding::write_field(obj, field_name, w)?,
|
||||
TransactionBlobs::Normal(bytes) => {
|
||||
cuprate_epee_encoding::write_field(bytes, field_name, w)?
|
||||
}
|
||||
TransactionBlobs::Pruned(obj) => {
|
||||
cuprate_epee_encoding::write_field(obj, field_name, w)?
|
||||
}
|
||||
TransactionBlobs::None => (),
|
||||
}
|
||||
}
|
|
@ -20,8 +20,8 @@
|
|||
|
||||
use bytes::Bytes;
|
||||
|
||||
use epee_encoding::{container_as_blob::ContainerAsBlob, epee_object};
|
||||
use fixed_bytes::{ByteArray, ByteArrayVec};
|
||||
use cuprate_epee_encoding::{container_as_blob::ContainerAsBlob, epee_object};
|
||||
use cuprate_fixed_bytes::{ByteArray, ByteArrayVec};
|
||||
|
||||
use super::common::BlockCompleteEntry;
|
||||
|
||||
|
@ -705,13 +705,14 @@ mod tests {
|
|||
248, 248, 91, 110, 107, 144, 12, 175, 253, 21, 121, 28,
|
||||
];
|
||||
|
||||
let new_transactions: NewTransactions = epee_encoding::from_bytes(&mut &bytes[..]).unwrap();
|
||||
let new_transactions: NewTransactions =
|
||||
cuprate_epee_encoding::from_bytes(&mut &bytes[..]).unwrap();
|
||||
|
||||
assert_eq!(4, new_transactions.txs.len());
|
||||
|
||||
let mut encoded_bytes = epee_encoding::to_bytes(new_transactions.clone()).unwrap();
|
||||
let mut encoded_bytes = cuprate_epee_encoding::to_bytes(new_transactions.clone()).unwrap();
|
||||
let new_transactions_2: NewTransactions =
|
||||
epee_encoding::from_bytes(&mut encoded_bytes).unwrap();
|
||||
cuprate_epee_encoding::from_bytes(&mut encoded_bytes).unwrap();
|
||||
|
||||
assert_eq!(new_transactions, new_transactions_2);
|
||||
}
|
||||
|
@ -1057,10 +1058,12 @@ mod tests {
|
|||
101, 110, 116, 95, 98, 108, 111, 99, 107, 99, 104, 97, 105, 110, 95, 104, 101, 105,
|
||||
103, 104, 116, 5, 209, 45, 42, 0, 0, 0, 0, 0,
|
||||
];
|
||||
let fluffy_block: NewFluffyBlock = epee_encoding::from_bytes(&mut &bytes[..]).unwrap();
|
||||
let fluffy_block: NewFluffyBlock =
|
||||
cuprate_epee_encoding::from_bytes(&mut &bytes[..]).unwrap();
|
||||
|
||||
let mut encoded_bytes = epee_encoding::to_bytes(fluffy_block.clone()).unwrap();
|
||||
let fluffy_block_2: NewFluffyBlock = epee_encoding::from_bytes(&mut encoded_bytes).unwrap();
|
||||
let mut encoded_bytes = cuprate_epee_encoding::to_bytes(fluffy_block.clone()).unwrap();
|
||||
let fluffy_block_2: NewFluffyBlock =
|
||||
cuprate_epee_encoding::from_bytes(&mut encoded_bytes).unwrap();
|
||||
|
||||
assert_eq!(fluffy_block, fluffy_block_2);
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "monero-address-book"
|
||||
name = "cuprate-address-book"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
@ -7,9 +7,9 @@ authors = ["Boog900"]
|
|||
|
||||
|
||||
[dependencies]
|
||||
monero-pruning = { path = "../../pruning" }
|
||||
monero-wire = { path= "../../net/monero-wire" }
|
||||
monero-p2p = { path = "../monero-p2p" }
|
||||
cuprate-pruning = { path = "../../pruning" }
|
||||
cuprate-wire = { path= "../../net/wire" }
|
||||
cuprate-p2p-core = { path = "../p2p-core" }
|
||||
|
||||
tower = { workspace = true, features = ["util"] }
|
||||
tokio = { workspace = true, features = ["time", "fs", "rt"]}
|
||||
|
|
|
@ -19,13 +19,13 @@ use tokio::{
|
|||
use tokio_util::time::DelayQueue;
|
||||
use tower::Service;
|
||||
|
||||
use monero_p2p::{
|
||||
use cuprate_p2p_core::{
|
||||
client::InternalPeerID,
|
||||
handles::ConnectionHandle,
|
||||
services::{AddressBookRequest, AddressBookResponse, ZoneSpecificPeerListEntryBase},
|
||||
NetZoneAddress, NetworkZone,
|
||||
};
|
||||
use monero_pruning::PruningSeed;
|
||||
use cuprate_pruning::PruningSeed;
|
||||
|
||||
use crate::{peer_list::PeerList, store::save_peers_to_disk, AddressBookConfig, AddressBookError};
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ use std::{path::PathBuf, sync::Arc, time::Duration};
|
|||
use futures::StreamExt;
|
||||
use tokio::{sync::Semaphore, time::interval};
|
||||
|
||||
use monero_p2p::handles::HandleBuilder;
|
||||
use monero_pruning::PruningSeed;
|
||||
use cuprate_p2p_core::handles::HandleBuilder;
|
||||
use cuprate_pruning::PruningSeed;
|
||||
|
||||
use super::{AddressBook, ConnectionPeerEntry, InternalPeerID};
|
||||
use crate::{peer_list::tests::make_fake_peer_list, AddressBookConfig, AddressBookError};
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
//!
|
||||
//! This module holds the logic for persistent peer storage.
|
||||
//! Cuprates address book is modeled as a [`tower::Service`]
|
||||
//! The request is [`AddressBookRequest`](monero_p2p::services::AddressBookRequest) and the response is
|
||||
//! [`AddressBookResponse`](monero_p2p::services::AddressBookResponse).
|
||||
//! The request is [`AddressBookRequest`](cuprate_p2p_core::services::AddressBookRequest) and the response is
|
||||
//! [`AddressBookResponse`](cuprate_p2p_core::services::AddressBookResponse).
|
||||
//!
|
||||
//! Cuprate, like monerod, actually has multiple address books, one
|
||||
//! for each [`NetworkZone`]. This is to reduce the possibility of
|
||||
|
@ -13,7 +13,7 @@
|
|||
//!
|
||||
use std::{io::ErrorKind, path::PathBuf, time::Duration};
|
||||
|
||||
use monero_p2p::NetworkZone;
|
||||
use cuprate_p2p_core::NetworkZone;
|
||||
|
||||
mod book;
|
||||
mod peer_list;
|
||||
|
|
|
@ -3,8 +3,8 @@ use std::collections::{BTreeMap, HashMap, HashSet};
|
|||
use indexmap::IndexMap;
|
||||
use rand::prelude::*;
|
||||
|
||||
use monero_p2p::{services::ZoneSpecificPeerListEntryBase, NetZoneAddress, NetworkZone};
|
||||
use monero_pruning::{PruningSeed, CRYPTONOTE_MAX_BLOCK_HEIGHT};
|
||||
use cuprate_p2p_core::{services::ZoneSpecificPeerListEntryBase, NetZoneAddress, NetworkZone};
|
||||
use cuprate_pruning::{PruningSeed, CRYPTONOTE_MAX_BLOCK_HEIGHT};
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
|
|
|
@ -2,11 +2,9 @@ use std::collections::HashSet;
|
|||
|
||||
use rand::Rng;
|
||||
|
||||
use monero_p2p::services::ZoneSpecificPeerListEntryBase;
|
||||
use monero_pruning::PruningSeed;
|
||||
|
||||
use cuprate_p2p_core::{services::ZoneSpecificPeerListEntryBase, NetZoneAddress};
|
||||
use cuprate_pruning::PruningSeed;
|
||||
use cuprate_test_utils::test_netzone::{TestNetZone, TestNetZoneAddr};
|
||||
use monero_p2p::NetZoneAddress;
|
||||
|
||||
use super::PeerList;
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::fs;
|
|||
use borsh::{from_slice, to_vec, BorshDeserialize, BorshSerialize};
|
||||
use tokio::task::{spawn_blocking, JoinHandle};
|
||||
|
||||
use monero_p2p::{services::ZoneSpecificPeerListEntryBase, NetZoneAddress, NetworkZone};
|
||||
use cuprate_p2p_core::{services::ZoneSpecificPeerListEntryBase, NetZoneAddress, NetworkZone};
|
||||
|
||||
use crate::{peer_list::PeerList, AddressBookConfig};
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "async-buffer"
|
||||
name = "cuprate-async-buffer"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use futures::{FutureExt, StreamExt};
|
||||
|
||||
use async_buffer::new_buffer;
|
||||
use cuprate_async_buffer::new_buffer;
|
||||
|
||||
#[tokio::test]
|
||||
async fn async_buffer_send_rec() {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "dandelion-tower"
|
||||
name = "cuprate-dandelion-tower"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "monero-p2p"
|
||||
name = "cuprate-p2p-core"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
@ -7,12 +7,12 @@ authors = ["Boog900"]
|
|||
|
||||
[features]
|
||||
default = ["borsh"]
|
||||
borsh = ["dep:borsh", "monero-pruning/borsh"]
|
||||
borsh = ["dep:borsh", "cuprate-pruning/borsh"]
|
||||
|
||||
[dependencies]
|
||||
cuprate-helper = { path = "../../helper" }
|
||||
monero-wire = { path = "../../net/monero-wire", features = ["tracing"] }
|
||||
monero-pruning = { path = "../../pruning" }
|
||||
cuprate-helper = { path = "../../helper", features = ["asynch"], default-features = false }
|
||||
cuprate-wire = { path = "../../net/wire", features = ["tracing"] }
|
||||
cuprate-pruning = { path = "../../pruning" }
|
||||
|
||||
tokio = { workspace = true, features = ["net", "sync", "macros", "time", "rt"]}
|
||||
tokio-util = { workspace = true, features = ["codec"] }
|
|
@ -10,13 +10,15 @@ use tokio::{
|
|||
task::JoinHandle,
|
||||
};
|
||||
use tokio_util::sync::PollSemaphore;
|
||||
use tower::Service;
|
||||
use tower::{Service, ServiceExt};
|
||||
use tracing::Instrument;
|
||||
|
||||
use cuprate_helper::asynch::InfallibleOneshotReceiver;
|
||||
use cuprate_pruning::PruningSeed;
|
||||
|
||||
use crate::{
|
||||
handles::ConnectionHandle, ConnectionDirection, NetworkZone, PeerError, PeerRequest,
|
||||
PeerResponse, SharedError,
|
||||
handles::{ConnectionGuard, ConnectionHandle},
|
||||
ConnectionDirection, NetworkZone, PeerError, PeerRequest, PeerResponse, SharedError,
|
||||
};
|
||||
|
||||
mod connection;
|
||||
|
@ -26,7 +28,6 @@ mod timeout_monitor;
|
|||
|
||||
pub use connector::{ConnectRequest, Connector};
|
||||
pub use handshaker::{DoHandshakeRequest, HandShaker, HandshakeError};
|
||||
use monero_pruning::PruningSeed;
|
||||
|
||||
/// An internal identifier for a given peer, will be their address if known
|
||||
/// or a random u128 if not.
|
||||
|
@ -158,11 +159,70 @@ impl<Z: NetworkZone> Service<PeerRequest> for Client<Z> {
|
|||
permit: Some(permit),
|
||||
};
|
||||
|
||||
self.connection_tx
|
||||
.try_send(req)
|
||||
.map_err(|_| ())
|
||||
.expect("poll_ready should have been called");
|
||||
if let Err(e) = self.connection_tx.try_send(req) {
|
||||
// The connection task could have closed between a call to `poll_ready` and the call to
|
||||
// `call`, which means if we don't handle the error here the receiver would panic.
|
||||
use mpsc::error::TrySendError;
|
||||
|
||||
match e {
|
||||
TrySendError::Closed(req) | TrySendError::Full(req) => {
|
||||
self.set_err(PeerError::ClientChannelClosed);
|
||||
|
||||
let _ = req
|
||||
.response_channel
|
||||
.send(Err(PeerError::ClientChannelClosed.into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rx.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a mock [`Client`] for testing purposes.
|
||||
///
|
||||
/// `request_handler` will be used to handle requests sent to the [`Client`]
|
||||
pub fn mock_client<Z: NetworkZone, S>(
|
||||
info: PeerInformation<Z::Addr>,
|
||||
connection_guard: ConnectionGuard,
|
||||
mut request_handler: S,
|
||||
) -> Client<Z>
|
||||
where
|
||||
S: crate::PeerRequestHandler,
|
||||
{
|
||||
let (tx, mut rx) = mpsc::channel(1);
|
||||
|
||||
let task_span = tracing::error_span!("mock_connection", addr = %info.id);
|
||||
|
||||
let task_handle = tokio::spawn(
|
||||
async move {
|
||||
let _guard = connection_guard;
|
||||
loop {
|
||||
let Some(req): Option<connection::ConnectionTaskRequest> = rx.recv().await else {
|
||||
tracing::debug!("Channel closed, closing mock connection");
|
||||
return;
|
||||
};
|
||||
|
||||
tracing::debug!("Received new request: {:?}", req.request.id());
|
||||
let res = request_handler
|
||||
.ready()
|
||||
.await
|
||||
.unwrap()
|
||||
.call(req.request)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
tracing::debug!("Sending back response");
|
||||
|
||||
let _ = req.response_channel.send(Ok(res));
|
||||
}
|
||||
}
|
||||
.instrument(task_span),
|
||||
);
|
||||
|
||||
let timeout_task = tokio::spawn(futures::future::pending());
|
||||
let semaphore = Arc::new(Semaphore::new(1));
|
||||
let error_slot = SharedError::new();
|
||||
|
||||
Client::new(info, tx, task_handle, timeout_task, semaphore, error_slot)
|
||||
}
|
|
@ -17,7 +17,7 @@ use tokio::{
|
|||
use tokio_stream::wrappers::ReceiverStream;
|
||||
use tower::ServiceExt;
|
||||
|
||||
use monero_wire::{LevinCommand, Message, ProtocolMessage};
|
||||
use cuprate_wire::{LevinCommand, Message, ProtocolMessage};
|
||||
|
||||
use crate::{
|
||||
constants::{REQUEST_TIMEOUT, SENDING_TIMEOUT},
|
||||
|
@ -241,7 +241,7 @@ where
|
|||
/// The main-loop for when we are in [`State::WaitingForRequest`].
|
||||
async fn state_waiting_for_request<Str>(&mut self, stream: &mut Str) -> Result<(), PeerError>
|
||||
where
|
||||
Str: FusedStream<Item = Result<Message, monero_wire::BucketError>> + Unpin,
|
||||
Str: FusedStream<Item = Result<Message, cuprate_wire::BucketError>> + Unpin,
|
||||
{
|
||||
tracing::debug!("waiting for peer/client request.");
|
||||
|
||||
|
@ -274,7 +274,7 @@ where
|
|||
/// The main-loop for when we are in [`State::WaitingForResponse`].
|
||||
async fn state_waiting_for_response<Str>(&mut self, stream: &mut Str) -> Result<(), PeerError>
|
||||
where
|
||||
Str: FusedStream<Item = Result<Message, monero_wire::BucketError>> + Unpin,
|
||||
Str: FusedStream<Item = Result<Message, cuprate_wire::BucketError>> + Unpin,
|
||||
{
|
||||
tracing::debug!("waiting for peer response.");
|
||||
|
||||
|
@ -306,7 +306,7 @@ where
|
|||
/// `eager_protocol_messages` are protocol messages that we received during a handshake.
|
||||
pub async fn run<Str>(mut self, mut stream: Str, eager_protocol_messages: Vec<ProtocolMessage>)
|
||||
where
|
||||
Str: FusedStream<Item = Result<Message, monero_wire::BucketError>> + Unpin,
|
||||
Str: FusedStream<Item = Result<Message, cuprate_wire::BucketError>> + Unpin,
|
||||
{
|
||||
tracing::debug!(
|
||||
"Handling eager messages len: {}",
|
|
@ -20,8 +20,8 @@ use tokio::{
|
|||
use tower::{Service, ServiceExt};
|
||||
use tracing::{info_span, Instrument};
|
||||
|
||||
use monero_pruning::{PruningError, PruningSeed};
|
||||
use monero_wire::{
|
||||
use cuprate_pruning::{PruningError, PruningSeed};
|
||||
use cuprate_wire::{
|
||||
admin::{
|
||||
HandshakeRequest, HandshakeResponse, PingResponse, SupportFlagsResponse,
|
||||
PING_OK_RESPONSE_STATUS_TEXT,
|
||||
|
@ -586,7 +586,7 @@ async fn wait_for_message<Z: NetworkZone>(
|
|||
peer_sink: &mut Z::Sink,
|
||||
peer_stream: &mut Z::Stream,
|
||||
|
||||
eager_protocol_messages: &mut Vec<monero_wire::ProtocolMessage>,
|
||||
eager_protocol_messages: &mut Vec<cuprate_wire::ProtocolMessage>,
|
||||
|
||||
our_basic_node_data: &BasicNodeData,
|
||||
) -> Result<Message, HandshakeError> {
|
|
@ -5,7 +5,6 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use futures::channel::oneshot;
|
||||
use monero_wire::admin::TimedSyncRequest;
|
||||
use tokio::{
|
||||
sync::{mpsc, Semaphore},
|
||||
time::{interval, MissedTickBehavior},
|
||||
|
@ -13,6 +12,8 @@ use tokio::{
|
|||
use tower::ServiceExt;
|
||||
use tracing::instrument;
|
||||
|
||||
use cuprate_wire::admin::TimedSyncRequest;
|
||||
|
||||
use crate::{
|
||||
client::{connection::ConnectionTaskRequest, InternalPeerID},
|
||||
constants::{MAX_PEERS_IN_PEER_LIST_MESSAGE, TIMEOUT_INTERVAL},
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue