-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
611dda7
commit d5b04fe
Showing
9 changed files
with
334 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
on: | ||
push: | ||
branches: | ||
- main | ||
pull_request: | ||
|
||
name: build | ||
|
||
jobs: | ||
|
||
build: | ||
name: Build | ||
runs-on: ubuntu-latest | ||
strategy: | ||
matrix: | ||
rust: | ||
- stable | ||
- nightly | ||
features: | ||
- default | ||
steps: | ||
- name: checkout | ||
uses: actions/checkout@v3 | ||
- name: Generate cache key | ||
run: echo "${{ matrix.rust }} ${{ matrix.features }}" | tee .cache_key | ||
- name: cache | ||
uses: actions/cache@v2 | ||
with: | ||
path: | | ||
~/.cargo/registry | ||
~/.cargo/git | ||
target | ||
key: ${{ runner.os }}-cargo-${{ hashFiles('.cache_key') }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }} | ||
- name: Set default toolchain | ||
run: rustup default ${{ matrix.rust }} | ||
- name: Set profile | ||
run: rustup set profile minimal | ||
- name: Update toolchain | ||
run: rustup update | ||
- name: Build | ||
run: cargo build --features ${{ matrix.features }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
on: | ||
push: | ||
branches: | ||
- main | ||
pull_request: | ||
|
||
name: lint | ||
|
||
jobs: | ||
|
||
fmt: | ||
name: rust fmt | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v3 | ||
- name: Set default toolchain | ||
run: rustup default nightly | ||
- name: Set profile | ||
run: rustup set profile minimal | ||
- name: Add rustfmt | ||
run: rustup component add rustfmt | ||
- name: Add clippy | ||
run: rustup component add clippy | ||
- name: Update toolchain | ||
run: rustup update | ||
- name: Check fmt | ||
run: cargo fmt --all -- --check | ||
- name: Clippy | ||
run: cargo clippy --all-targets --all-features -- -D warnings |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
on: | ||
push: | ||
branches: | ||
- main | ||
pull_request: | ||
|
||
name: test | ||
|
||
jobs: | ||
test_with_codecov: | ||
name: Run tests with coverage reporting | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v3 | ||
|
||
- name: Set default toolchain | ||
run: rustup default nightly | ||
- name: Set profile | ||
run: rustup set profile minimal | ||
|
||
# Pin grcov to v0.8.2 because of build failure at 0.8.3 | ||
- name: Install grcov | ||
run: cargo install grcov --force --version 0.8.2 | ||
|
||
# Tests are run with code coverage support | ||
- name: Run cargo test | ||
env: | ||
CARGO_INCREMENTAL: '0' | ||
RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off' | ||
RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off' | ||
run: cargo test -- --nocapture | ||
- id: coverage | ||
name: Generate coverage | ||
uses: actions-rs/[email protected] | ||
|
||
# Upload coverage report | ||
- name: Upload coverage to Codecov | ||
uses: codecov/codecov-action@v1 | ||
with: | ||
file: ${{ steps.coverage.outputs.report }} | ||
directory: ./coverage/reports/ |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
[package] | ||
name = "rust-coinselect" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[dependencies] | ||
|
||
|
||
#Empty default feature set, (helpful to generalise in github actions) | ||
[features] | ||
default = [] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,60 @@ | ||
# rust-coinselect | ||
A blockchain-agnostic coinselection library built in rust. | ||
|
||
A blockchain-agnostic coin selection library built in Rust. | ||
|
||
## Problem Statement | ||
|
||
Coin selection is the operation of selecting a subset of UTXOs from the wallet's UTXO set for transaction building. It is a fundamental and generic wallet management operation, and a standalone Rust library would be useful for various downstream wallet projects. | ||
|
||
Coin selection is a variant of the Subset-Sum problem and can be solved via various algorithms. Finding an optimized solution depends on various conflicting goals such as confirmation urgency, privacy, UTXO footprint management, etc. | ||
|
||
## Background | ||
|
||
The following literature describes the current state of coin selection in the Bitcoin ecosystem: | ||
|
||
- Murch's coin selection thesis: [PDF](https://murch.one/erhardt2016coinselection.pdf) | ||
- A Survey on Coin Selection Algorithms in UTXO-based Blockchains: [PDF](./docs/coinselectionpdf) | ||
- Bitcoin Core's Coin selection module: [GitHub](https://github.com/bitcoin/bitcoin/blob/master/src/wallet/coinselection.cpp) | ||
- Bcoin's Coin selector: [GitHub](https://github.com/bcoin-org/bcoin/blob/master/lib/wallet/coinselector.js) | ||
- A rough implementation of the Lowest Larger Algorithm: [GitHub](https://github.com/Bitshala-Incubator/silent-pay/blob/main/src/wallet/coin-selector.ts) | ||
- Waste Metric Calculation: [GitHub](https://github.com/bitcoin/bitcoin/blob/baed5edeb611d949982c849461949c645f8998a7/src/wallet/coinselection.cpp#L795) | ||
|
||
## Technical Scope | ||
|
||
The library will perform coin selection via various algorithms through a well-documented API. The API will be generic in nature and will not assume any Bitcoin structure or methods. It can be used for any UTXO-based blockchain. | ||
|
||
The following algorithms will be implemented from scratch in Rust: | ||
|
||
- Knapsack solving | ||
- Branch and Bound | ||
- Lowest Larger | ||
- First-In-First-Out | ||
- Single-Random-Draw | ||
|
||
The library will have individual APIs for each algorithm and provide a wrapper API `coin_select()` which will perform selection via each algorithm and return the result with the least waste metric. | ||
|
||
Other characteristics of the library: | ||
|
||
- Well-documented code, helpful in understanding coin selection theory. | ||
- Minimal possible dependency footprint. | ||
- Minimum possible MSRV (Minimum Supported Rust Version). | ||
|
||
## Contributing | ||
|
||
The project is under active development by a few motivated Rusty Bitcoin devs. Contributions for features, tests, docs, and other fixes/upgrades are encouraged and welcomed. The maintainers will use the PR thread to provide quick reviews and suggestions and are generally proactive at merging good contributions. | ||
|
||
Directions for new contributors: | ||
|
||
- The list of [issues](https://github.com/Bitshala-Incubator/rust-coinselect/issues) is a good place to look for contributable tasks and open problems. | ||
- Issues marked with [`good first issue`](https://github.com/Bitshala-Incubator/rust-coinselect/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) are good places to get started for newbie Rust/Bitcoin devs. | ||
- The background [docs](#background) are a good place to start reading up on coin selection theory. | ||
- Reviewing [open PRs](https://github.com/Bitshala-Incubator/rust-coinselect/pulls) is a good place to start gathering a contextual understanding of the codebase. | ||
- Search for `TODO`s in the codebase to find in-line marked code todos and smaller improvements. | ||
|
||
## Community | ||
|
||
The dev community gathers in a small corner of Discord [here](https://discord.gg/TSSAB3g4Zf) (say hello if you drop there from this readme). | ||
|
||
Dev discussions predominantly happen via FOSS (Free and Open-Source Software) best practices, and by using GitHub as the Community Forum. | ||
|
||
The Issues, PRs, and Discussions are where all the heavy lifting happens. |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
//! A blockchain-agnostic Rust Coinselection library | ||
|
||
|
||
/// A [`OutputGroup`] represents an input candidate for Coinselection. This can either be a | ||
/// single UTXO, or a group of UTXOs that should be spent together. | ||
/// The library user is responsible for crafting this structure correctly. Incorrect representation of this | ||
/// structure will cause incorrect selection result. | ||
#[derive(Debug, Clone, Copy)] | ||
pub struct OutputGroup { | ||
/// Total value of the UTXO(s) that this [`WeightedValue`] represents. | ||
pub value: u64, | ||
/// Total weight of including this/these UTXO(s). | ||
/// `txin` fields: `prevout`, `nSequence`, `scriptSigLen`, `scriptSig`, `scriptWitnessLen`, | ||
/// `scriptWitness` should all be included. | ||
pub weight: u32, | ||
/// The total number of inputs; so we can calculate extra `varint` weight due to `vin` length changes. | ||
pub input_count: usize, | ||
/// Whether this [`OutputGroup`] contains at least one segwit spend. | ||
pub is_segwit: bool, | ||
/// Relative Creation sequence for this group. Only used for FIFO selection. Specify None, if FIFO | ||
/// selection is not required. | ||
/// Sequqence numbers are arbitrary index only to denote relative age of utxo group among a set of groups. | ||
/// To denote the oldest utxo group, give them a sequence number of Some(0). | ||
pub creation_sequqence: Option<u32> | ||
} | ||
|
||
/// A set of Options that guides the CoinSelection algorithms. These are inputs specified by the | ||
/// user to perform coinselection to achieve a set a target parameters. | ||
#[derive(Debug, Clone, Copy)] | ||
pub struct CoinSelectionOpt { | ||
/// The value we need to select. | ||
pub target_value: u64, | ||
|
||
/// The feerate we should try and achieve in sats per weight unit. | ||
pub target_feerate: f32, | ||
/// The feerate | ||
pub long_term_feerate: Option<f32>, // TODO: Maybe out of scope? (waste) | ||
/// The minimum absolute fee. I.e., needed for RBF. | ||
pub min_absolute_fee: u64, | ||
|
||
/// The weight of the template transaction, including fixed fields and outputs. | ||
pub base_weight: u32, | ||
/// Additional weight if we include the drain (change) output. | ||
pub drain_weight: u32, | ||
/// Weight of spending the drain (change) output in the future. | ||
pub spend_drain_weight: u32, // TODO: Maybe out of scope? (waste) | ||
|
||
/// Minimum value allowed for a drain (change) output. | ||
pub min_drain_value: u64, | ||
} | ||
|
||
/// Strategy to decide what to do with the excess amount. | ||
#[derive(Clone, Copy, Debug)] | ||
pub enum ExcessStrategy { | ||
ToFee, | ||
ToRecipient, | ||
ToDrain, | ||
} | ||
|
||
/// Error Describing failure of a selection attempt. | ||
#[derive(Debug)] | ||
pub enum SelectionError{ | ||
SomethingWentWrong | ||
} | ||
|
||
/// Calculated waste for a specific selection. | ||
/// This is used to compare various selection algorithm and find the most | ||
/// optimizewd solution, represented by least [WasteMetric] value. | ||
#[derive(Debug)] | ||
pub struct WasteMetric(u64); | ||
|
||
|
||
/// Perform Coinselection via Branch And Bound algorithm. | ||
/// Return None, if no solution exists. | ||
pub fn select_coin_bnb(inputs: Vec<OutputGroup>, opitons: CoinSelectionOpt, excess_strategy: ExcessStrategy) -> Result<Option<(Vec<u32>, WasteMetric)>, SelectionError> { | ||
unimplemented!() | ||
} | ||
|
||
/// Perform Coinselection via Knapsack solver. | ||
/// Return None, if no solution exists. | ||
pub fn select_coin_knapsack(inputs: Vec<OutputGroup>, opitons: CoinSelectionOpt, excess_strategy: ExcessStrategy) -> Result<Option<(Vec<u32>, WasteMetric)>, SelectionError> { | ||
unimplemented!() | ||
} | ||
|
||
|
||
/// Perform Coinselection via Lowest Larger algorithm. | ||
/// Return None, if no solution exists. | ||
pub fn select_coin_lowestlarger(inputs: Vec<OutputGroup>, opitons: CoinSelectionOpt, excess_strategy: ExcessStrategy) -> Result<Option<(Vec<u32>, WasteMetric)>, SelectionError> { | ||
unimplemented!() | ||
} | ||
|
||
|
||
/// Perform Coinselection via First-In-First-Out algorithm. | ||
/// Return None, if no solution exists. | ||
pub fn select_coin_fifo(inputs: Vec<OutputGroup>, opitons: CoinSelectionOpt, excess_strategy: ExcessStrategy) -> Result<Option<(Vec<u32>, WasteMetric)>, SelectionError> { | ||
unimplemented!() | ||
} | ||
|
||
/// Perform Coinselection via Single Random Draw. | ||
/// Return None, if no solution exists. | ||
pub fn select_coin_srd(inputs: Vec<OutputGroup>, opitons: CoinSelectionOpt, excess_strategy: ExcessStrategy) -> Result<Option<(Vec<u32>, WasteMetric)>, SelectionError> { | ||
unimplemented!() | ||
} | ||
|
||
|
||
/// The Global Coinselection API that performs all the algorithms and proudeces result with least [WasteMetric]. | ||
/// At least one selection solution should be found. | ||
pub fn select_coin_(inputs: Vec<OutputGroup>, opitons: CoinSelectionOpt, excess_strategy: ExcessStrategy) -> Result<(Vec<u32>, WasteMetric), SelectionError> { | ||
unimplemented!() | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
|
||
use super::*; | ||
|
||
#[test] | ||
fn test_bnb() { | ||
// Perform BNB selection of set of test values. | ||
} | ||
|
||
#[test] | ||
fn test_srd() { | ||
// Perform SRD selection of set of test values. | ||
} | ||
|
||
#[test] | ||
fn test_knapsack() { | ||
// Perform Knapsack selection of set of test values. | ||
} | ||
|
||
#[test] | ||
fn test_fifo() { | ||
// Perform FIFO selection of set of test values. | ||
} | ||
|
||
#[test] | ||
fn test_lowestlarger() { | ||
// Perform LowestLarger selection of set of test values. | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
fn main() { | ||
println!("let there be coin selection"); | ||
} |