Skip to content

Commit

Permalink
initial structure and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
rajarshimaitra committed Feb 28, 2024
1 parent 611dda7 commit d5b04fe
Show file tree
Hide file tree
Showing 9 changed files with 334 additions and 1 deletion.
41 changes: 41 additions & 0 deletions .github/workflows/build.yaml
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 }}
30 changes: 30 additions & 0 deletions .github/workflows/lint.yaml
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
42 changes: 42 additions & 0 deletions .github/workflows/test.yaml
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/
7 changes: 7 additions & 0 deletions Cargo.lock

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

11 changes: 11 additions & 0 deletions Cargo.toml
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 = []
60 changes: 59 additions & 1 deletion README.md
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 added docs/coinselectionpdf
Binary file not shown.
141 changes: 141 additions & 0 deletions src/lib.rs
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.
}
}
3 changes: 3 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
println!("let there be coin selection");
}

0 comments on commit d5b04fe

Please sign in to comment.