diff --git a/.gitmodules b/.gitmodules index e851f69e2..c698a9846 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "secp256k1"] path = 3rdparty/secp256k1 - url = https://github.com/bitcoin-core/secp256k1.git - ignore = dirty + url = https://github.com/mit-dci/secp256k1-zkp.git + branch = feat/aggregated-batch-verification diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index a102f1350..2a3648d96 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -3,28 +3,14 @@ set(secp256k1_CPPFLAGS -DUSE_NUM_NONE=1 -DUSE_FIELD_INV_BUILTIN=1 -DUSE_FIELD_5X52=1 -DUSE_SCALAR_4X64=1 -DECMULT_WINDOW_SIZE=15 -DECMULT_GEN_PREC_BITS=4 -DHAVE___INT128 -DENABLE_MODULE_RECOVERY=1 - -DENABLE_MODULE_SCHNORRSIG=1 -DENABLE_MODULE_EXTRAKEYS=1) + -DENABLE_MODULE_SCHNORRSIG=1 -DENABLE_MODULE_EXTRAKEYS=1 + -DENABLE_MODULE_GENERATOR=1 -DENABLE_MODULE_BPPP=1) -set(secp256k1_genctx_SOURCES secp256k1/src/gen_context.c) - -add_executable(secp256k1_genctx ${secp256k1_genctx_SOURCES}) -target_include_directories(secp256k1_genctx PRIVATE secp256k1) -target_compile_options(secp256k1_genctx PRIVATE ${secp256k1_CPPFLAGS}) - -add_custom_command(OUTPUT src/ecmult_static_context.h - COMMAND mkdir -p src - COMMAND secp256k1_genctx - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) -add_custom_target(secp256k1_genctx_run - DEPENDS src/ecmult_static_context.h) -add_dependencies(secp256k1_genctx_run secp256k1_genctx) - -add_library(secp256k1 secp256k1/src/secp256k1.c) +add_library(secp256k1 secp256k1/src/precomputed_ecmult.c secp256k1/src/precomputed_ecmult_gen.c secp256k1/src/secp256k1.c) target_include_directories(secp256k1 PRIVATE secp256k1 secp256k1/src ${CMAKE_CURRENT_BINARY_DIR}/src) target_compile_options(secp256k1 PRIVATE ${secp256k1_CPPFLAGS}) -add_dependencies(secp256k1 secp256k1_genctx_run) add_subdirectory(crypto) add_subdirectory(bech32) diff --git a/3rdparty/secp256k1 b/3rdparty/secp256k1 index ac05f61fc..a40a6314a 160000 --- a/3rdparty/secp256k1 +++ b/3rdparty/secp256k1 @@ -1 +1 @@ -Subproject commit ac05f61fcf639a15b5101131561620303e4bd808 +Subproject commit a40a6314a476cbc2ad74387685c5daa0673bf542 diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e23aa462..8ebcb984a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,7 @@ find_library(NURAFT_LIBRARY nuraft REQUIRED) find_library(GTEST_LIBRARY gtest REQUIRED) find_library(GTEST_MAIN_LIBRARY gtest_main REQUIRED) find_package(benchmark REQUIRED) + find_library(LUA_LIBRARY lua REQUIRED) find_library(KECCAK_LIBRARY keccak REQUIRED) find_library(EVMC_INSTRUCTIONS_LIBRARY evmc-instructions REQUIRED) @@ -59,6 +60,7 @@ endif() if(CMAKE_BUILD_TYPE STREQUAL "Debug") add_link_options(--coverage) + add_compile_options(-Og -g -ggdb) endif() if(CMAKE_BUILD_TYPE STREQUAL "Profiling") @@ -91,5 +93,4 @@ endif() add_subdirectory(src) add_subdirectory(tests) add_subdirectory(benchmarks) -add_subdirectory(tools/bench) -add_subdirectory(tools/shard-seeder) +add_subdirectory(tools) diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index d616364f0..7ebd56407 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -3,7 +3,8 @@ project(benchmarks) include_directories(. ../src ../tools/watchtower ../3rdparty ../3rdparty/secp256k1/include) set(SECP256K1_LIBRARY $) -add_executable(run_benchmarks low_level.cpp +add_executable(run_benchmarks audits.cpp + low_level.cpp transactions.cpp uhs_leveldb.cpp uhs_set.cpp diff --git a/benchmarks/audits.cpp b/benchmarks/audits.cpp new file mode 100644 index 000000000..8ba51a04f --- /dev/null +++ b/benchmarks/audits.cpp @@ -0,0 +1,210 @@ +// Copyright (c) 2021 MIT Digital Currency Initiative, +// Federal Reserve Bank of Boston +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "uhs/transaction/transaction.hpp" +#include "uhs/twophase/locking_shard/locking_shard.hpp" +#include "util/common/hash.hpp" +#include "util/common/hashmap.hpp" +#include "util/common/keys.hpp" +#include "util/common/config.hpp" +#include "util/common/random_source.hpp" +#include "util/common/snapshot_map.hpp" + +#include +#include +#include +#include +#include +#include + +#define SWEEP_MAX 100000 +#define EPOCH 1000 + +using namespace cbdc; +using secp256k1_context_destroy_type = void (*)(secp256k1_context*); +using uhs_element = locking_shard::locking_shard::uhs_element; + +struct GensDeleter { + explicit GensDeleter(secp256k1_context* ctx) : m_ctx(ctx) {} + + void operator()(secp256k1_bppp_generators* gens) const { + secp256k1_bppp_generators_destroy(m_ctx, gens); + } + + secp256k1_context* m_ctx; +}; + +static std::default_random_engine m_shuffle; + +static const inline auto rnd + = std::make_unique(config::random_source); + +static std::unique_ptr + secp{secp256k1_context_create(SECP256K1_CONTEXT_NONE), + &secp256k1_context_destroy}; + +/// should be set to exactly `floor(log_base(value)) + 1` +/// +/// We use n_bits = 64, base = 16, so this should always be 24. +static const inline auto generator_count = 16 + 8; + +static std::unique_ptr + generators{ + secp256k1_bppp_generators_create(secp.get(), + generator_count), + GensDeleter(secp.get())}; + +static auto gen_map(uint64_t map_size, bool deleted = false) -> snapshot_map { + std::uniform_int_distribution dist(EPOCH - 100, EPOCH + 100); + auto uhs = snapshot_map(); + + auto comm = commit(secp.get(), 10, hash_t{}).value(); + auto rng + = transaction::prove(secp.get(), + generators.get(), + *rnd, + {hash_t{}, 10}, + &comm); + auto commitment = serialize_commitment(secp.get(), comm); + + for(uint64_t i = 1; i <= map_size; i++) { + transaction::compact_output out{commitment, rng, rnd->random_hash()}; + auto del = deleted ? std::optional{dist(m_shuffle)} : std::nullopt; + uhs_element el0{out, 0, del}; + auto key = transaction::calculate_uhs_id(out); + uhs.emplace(key, el0); + } + return uhs; +} + +static auto audit(snapshot_map& uhs, + snapshot_map& locked, + snapshot_map& spent) + -> std::optional { + + { + uhs.snapshot(); + locked.snapshot(); + spent.snapshot(); + } + + bool failed = false; + uint64_t epoch = EPOCH; + + static constexpr auto scratch_size = 8192UL * 1024UL; + [[maybe_unused]] secp256k1_scratch_space* scratch + = secp256k1_scratch_space_create(secp.get(), scratch_size); + + static constexpr size_t threshold = 100000; + size_t cursor = 0; + std::vector comms{}; + auto* range_batch = secp256k1_bppp_rangeproof_batch_create(secp.get(), 34 * (threshold + 1)); + auto summarize + = [&](const snapshot_map& m) { + for(const auto& [id, elem] : m) { + if(failed) { + break; + } + if(elem.m_creation_epoch <= epoch + && (!elem.m_deletion_epoch.has_value() + || (elem.m_deletion_epoch.value() > epoch))) { + + auto uhs_id + = transaction::calculate_uhs_id(elem.m_out); + if(uhs_id != id) { + failed = true; + } + auto comm = elem.m_out.m_value_commitment; + auto c = deserialize_commitment(secp.get(), comm).value(); + auto r = transaction::validation::range_batch_add( + *range_batch, + scratch, + elem.m_out.m_range, + c + ); + if(!r.has_value()) { + ++cursor; + } + comms.push_back(comm); + } + if(cursor >= threshold) { + failed = transaction::validation::check_range_batch(*range_batch).has_value(); + [[maybe_unused]] auto res = secp256k1_bppp_rangeproof_batch_clear(secp.get(), range_batch); + cursor = 0; + } + } + if(cursor > 0) { + failed = transaction::validation::check_range_batch(*range_batch).has_value(); + [[maybe_unused]] auto res = secp256k1_bppp_rangeproof_batch_clear(secp.get(), range_batch); + cursor = 0; + } + }; + + summarize(uhs); + summarize(locked); + summarize(spent); + [[maybe_unused]] auto res = secp256k1_bppp_rangeproof_batch_destroy(secp.get(), range_batch); + free(range_batch); + + secp256k1_scratch_space_destroy(secp.get(), scratch); + +// std::vector comms{}; +// comms.reserve(pool.size()); +// +// for(auto& f : pool) { +// auto c = f.get(); +// failed = !c.has_value(); +// if(failed) { +// break; +// } +// comms.emplace_back(std::move(c.value())); +//k } + + { + uhs.release_snapshot(); + locked.release_snapshot(); + spent.release_snapshot(); + } + + if(failed) { + return std::nullopt; + } + + return sum_commitments(secp.get(), comms); +} + +static void audit_routine(benchmark::State& state) { + auto key_count = state.range(0); + + auto seed = std::chrono::high_resolution_clock::now() + .time_since_epoch() + .count(); + seed %= std::numeric_limits::max(); + m_shuffle.seed(static_cast(seed)); + + uint32_t locked_sz{}; + uint32_t spent_sz{}; + { + std::uniform_int_distribution locked(0, key_count); + locked_sz = locked(m_shuffle); + std::uniform_int_distribution spent(0, key_count - locked_sz); + spent_sz = spent(m_shuffle); + } + + snapshot_map uhs = gen_map(key_count - (locked_sz + spent_sz)); + snapshot_map locked = gen_map(locked_sz); + snapshot_map spent = gen_map(spent_sz, true); + for(auto _ : state) { + auto res = audit(uhs, locked, spent); + ASSERT_NE(res, std::nullopt); + } +} + +BENCHMARK(audit_routine) + ->RangeMultiplier(10) + ->Range(10, SWEEP_MAX) + ->Complexity(benchmark::oAuto); + +BENCHMARK_MAIN(); \ No newline at end of file diff --git a/benchmarks/low_level.cpp b/benchmarks/low_level.cpp index ee0390119..7d13262c8 100644 --- a/benchmarks/low_level.cpp +++ b/benchmarks/low_level.cpp @@ -166,15 +166,7 @@ BENCHMARK_F(low_level, no_inputs)(benchmark::State& state) { BENCHMARK_F(low_level, calculate_uhs_id)(benchmark::State& state) { m_valid_tx = wallet1.send_to(2, wallet2.generate_key(), true).value(); auto cp_tx = cbdc::transaction::compact_tx(m_valid_tx); - auto engine = std::default_random_engine(); for(auto _ : state) { - state.PauseTiming(); - uint64_t i = (uint64_t)engine(); - state.ResumeTiming(); - cbdc::transaction::uhs_id_from_output(cp_tx.m_id, - i, - m_valid_tx.m_outputs[0]); + cbdc::transaction::calculate_uhs_id(cp_tx.m_outputs[0]); } } - -BENCHMARK_MAIN(); diff --git a/benchmarks/uhs_leveldb.cpp b/benchmarks/uhs_leveldb.cpp index f111bb00b..34a91e1d8 100644 --- a/benchmarks/uhs_leveldb.cpp +++ b/benchmarks/uhs_leveldb.cpp @@ -100,12 +100,12 @@ static void uhs_leveldb_put_new(benchmark::State& state) { db.wallet2.confirm_transaction(db.m_valid_tx); db.m_cp_tx = cbdc::transaction::compact_tx(db.m_valid_tx); - std::array out_arr{}; + std::array out_arr{}; std::memcpy(out_arr.data(), - db.m_cp_tx.m_uhs_outputs.data(), - db.m_cp_tx.m_uhs_outputs.size()); + db.m_cp_tx.m_outputs.data(), + db.m_cp_tx.m_outputs.size()); leveldb::Slice OutPointKey(out_arr.data(), - db.m_cp_tx.m_uhs_outputs.size()); + db.m_cp_tx.m_outputs.size()); // actual storage state.ResumeTiming(); @@ -121,12 +121,12 @@ static void uhs_leveldb_item_delete(benchmark::State& state) { auto db = db_container(); db.m_cp_tx = cbdc::transaction::compact_tx(db.m_valid_tx); - std::array out_arr{}; + std::array out_arr{}; std::memcpy(out_arr.data(), - db.m_cp_tx.m_uhs_outputs.data(), - db.m_cp_tx.m_uhs_outputs.size()); + db.m_cp_tx.m_outputs.data(), + db.m_cp_tx.m_outputs.size()); leveldb::Slice OutPointKey(out_arr.data(), - db.m_cp_tx.m_uhs_outputs.size()); + db.m_cp_tx.m_outputs.size()); for(auto _ : state) { state.PauseTiming(); @@ -148,10 +148,11 @@ static void uhs_leveldb_shard_sim(benchmark::State& state) { leveldb::WriteBatch batch; state.ResumeTiming(); for(const auto& tx : db.block) { - for(const auto& out : tx.m_uhs_outputs) { - std::array out_arr{}; - std::memcpy(out_arr.data(), out.data(), out.size()); - leveldb::Slice OutPointKey(out_arr.data(), out.size()); + for(const auto& out : tx.m_outputs) { + auto id = calculate_uhs_id(out); + std::array out_arr{}; + std::memcpy(out_arr.data(), id.data(), id.size()); + leveldb::Slice OutPointKey(out_arr.data(), id.size()); batch.Put(OutPointKey, leveldb::Slice()); } for(const auto& inp : tx.m_inputs) { @@ -176,10 +177,11 @@ static void uhs_leveldb_shard_sim_brief(benchmark::State& state) { leveldb::WriteBatch batch; state.ResumeTiming(); for(const auto& tx : db.block_abridged) { - for(const auto& out : tx.m_uhs_outputs) { - std::array out_arr{}; - std::memcpy(out_arr.data(), out.data(), out.size()); - leveldb::Slice OutPointKey(out_arr.data(), out.size()); + for(const auto& out : tx.m_outputs) { + auto id = calculate_uhs_id(out); + std::array out_arr{}; + std::memcpy(out_arr.data(), id.data(), id.size()); + leveldb::Slice OutPointKey(out_arr.data(), id.size()); batch.Put(OutPointKey, leveldb::Slice()); } for(const auto& inp : tx.m_inputs) { diff --git a/benchmarks/uhs_set.cpp b/benchmarks/uhs_set.cpp index f69369ba4..bdf27a47c 100644 --- a/benchmarks/uhs_set.cpp +++ b/benchmarks/uhs_set.cpp @@ -6,8 +6,10 @@ #include "uhs/transaction/transaction.hpp" #include "uhs/transaction/validation.hpp" #include "uhs/transaction/wallet.hpp" +#include "uhs/twophase/locking_shard/locking_shard.hpp" #include "util/common/hash.hpp" #include "util/common/hashmap.hpp" +#include "util/common/snapshot_map.hpp" #include #include @@ -15,50 +17,58 @@ #include class uhs_set : public ::benchmark::Fixture { + public: + uhs_set() { + Iterations(10000); + } + protected: void SetUp(const ::benchmark::State&) override { + m_uhs.snapshot(); + auto mint_tx1 = wallet1.mint_new_coins(1, 100); wallet1.confirm_transaction(mint_tx1); + auto cp_1 = cbdc::transaction::compact_tx(mint_tx1); + auto id_1 = cbdc::transaction::calculate_uhs_id(cp_1.m_outputs[0]); + uhs_element el_1 {cp_1.m_outputs[0], epoch++, std::nullopt}; + m_uhs.emplace(id_1, el_1); + auto mint_tx2 = wallet2.mint_new_coins(1, 100); wallet2.confirm_transaction(mint_tx2); - m_cp_tx = cbdc::transaction::compact_tx(m_valid_tx); + auto cp_2 = cbdc::transaction::compact_tx(mint_tx2); + auto id_2 = cbdc::transaction::calculate_uhs_id(cp_2.m_outputs[0]); + uhs_element el_2 {cp_2.m_outputs[0], epoch++, std::nullopt}; + m_uhs.emplace(id_2, el_2); } cbdc::transaction::wallet wallet1; cbdc::transaction::wallet wallet2; cbdc::transaction::full_tx m_valid_tx{}; - cbdc::transaction::compact_tx m_cp_tx; + cbdc::transaction::compact_tx m_cp_tx{}; - std::unordered_set set; + using uhs_element = cbdc::locking_shard::locking_shard::uhs_element; + cbdc::snapshot_map m_uhs{}; + size_t epoch{0}; }; -// benchmark how long it takes to emplace new values into an unordered set -BENCHMARK_F(uhs_set, emplace_new)(benchmark::State& state) { +// minimal tx execution +BENCHMARK_F(uhs_set, swap)(benchmark::State& state) { for(auto _ : state) { m_valid_tx = wallet1.send_to(2, wallet1.generate_key(), true).value(); wallet1.confirm_transaction(m_valid_tx); m_cp_tx = cbdc::transaction::compact_tx(m_valid_tx); state.ResumeTiming(); - set.emplace(m_cp_tx.m_id); - state.PauseTiming(); - - m_cp_tx = cbdc::transaction::compact_tx(m_valid_tx); - } -} - -// benchmark how long it takes to remove values from an unordered set -BENCHMARK_F(uhs_set, erase_item)(benchmark::State& state) { - for(auto _ : state) { - m_valid_tx = wallet1.send_to(2, wallet1.generate_key(), true).value(); - wallet1.confirm_transaction(m_valid_tx); - m_cp_tx = cbdc::transaction::compact_tx(m_valid_tx); - set.emplace(m_cp_tx.m_id); - state.ResumeTiming(); - set.erase(m_cp_tx.m_id); + for(const auto& inp : m_cp_tx.m_inputs) { + m_uhs.erase(inp); + } + for(const auto& outp : m_cp_tx.m_outputs) { + const auto& uhs_id = cbdc::transaction::calculate_uhs_id(outp); + uhs_element el{outp, epoch, std::nullopt}; + m_uhs.emplace(uhs_id, el); + } state.PauseTiming(); - - m_cp_tx = cbdc::transaction::compact_tx(m_valid_tx); + epoch++; } } diff --git a/scripts/native-system-benchmark.sh b/scripts/native-system-benchmark.sh new file mode 100755 index 000000000..3c1ff23f0 --- /dev/null +++ b/scripts/native-system-benchmark.sh @@ -0,0 +1,363 @@ +#!/usr/bin/env bash + +RR='rr record --' +GDB='gdb -ex run --args' +VALGRIND='valgrind --leak-check=full' +DBG="${DBG:-$GDB}" + +# runs for DURATION seconds +# if DURATION is set to inf/infinity, run indefinitely +DURATION=30 +CWD=$(pwd) +COMMIT=$(git rev-parse --short HEAD) +TL=$(git rev-parse --show-toplevel) +RT="${TL:-$CWD}" +BLD="$RT"/build +SEEDDIR="$BLD"/preseeds +TESTDIR="$BLD"/test-$(date +"%s") + +IFS='' read -r -d '' usage <<'EOF' +Usage: %s [options] + +Options: + -h, --help print this help and exit + -c, --config=PATH use PATH as the test configuration + -s, --samples=TIME run test for TIME seconds (defaults to 30) + (or indefinitely if set to inf, or infinity) + +Cleanup: + --clean delete all previous test and preseed artifacts + --clean-tests delete all previous test artifacts + --clean-preseeds delete all previous preseed artifacts + +Debugging Options: + --debug run each component under a debugger + (likely requires a Debug build to be useful) + --profile run each component under perf + (likely requires a Profiling build to be useful) + --leak-check run each component under valgrind + + -d, --debugger=CMD specify the debugger CMD + ("gdb" and "rr" are short-cuts for sensible defaults + for those debuggers) + +Note: --debug, --profile, and --leak-check are mutually-exclusive +EOF + +_help= +if [[ $# -eq 0 ]]; then + _help=1 +fi + +_err=0 +while [[ $# -gt 0 ]]; do + optarg= + shft_cnt=1 + if [[ "$1" =~ [=] ]]; then + optarg="${1#*=}" + elif [[ "$1" =~ ^-- && $# -gt 1 && ! "$2" =~ ^- ]]; then + optarg="$2" + shft_cnt=2 + elif [[ "$1" =~ ^-[^-] && $# -gt 1 && ! "$2" =~ ^- ]]; then + optarg="$2" + shft_cnt=2 + elif [[ "$1" =~ ^-[^-] ]]; then + optarg="${1/??/}" + fi + + case "$1" in + -s*|--samples*) DURATION="${optarg:-$DURATION}"; shift "$shft_cnt";; + --leak-check) RECORD=debug; DBG="$VALGRIND"; shift "$shft_cnt";; + --debug) RECORD=debug; shift "$shft_cnt";; + --profile) RECORD=perf; shift "$shft_cnt";; + --clean-tests) + printf '%s\n' 'Deleting all test directories' + rm -rf -- "$BLD"/test-*; shift "$shft_cnt";; + --clean-seeds) + printf '%s\n' 'Deleting all cached preseeds' + rm -rf -- "$BLD"/preseeds; shift "$shft_cnt";; + --clean) + printf '%s\n' 'Deleting all tests and preseeds' + rm -rf -- "$BLD"/test-* "$BLD"/preseeds; shift "$shft_cnt";; + -d*|--debugger*) + case "$optarg" in + gdb) DBG="$GDB";; + rr) DBG="$RR";; + *) DBG="$optarg";; + esac + shift "$shft_cnt";; + -c*|--config*) + if [[ "$optarg" = /* ]]; then + ORIG_CFG="${optarg}" + else + ORIG_CFG="$CWD/$optarg" + fi + shift "$shft_cnt";; + -h|--help) _help=1; shift "$shft_cnt";; + *) + printf 'Unrecognized option: %s\n' "$1" + _help=1; _err=1; + break;; + esac +done + +case "$DURATION" in + inf|infinity) DURATION=infinity;; + '') DURATION=30;; +esac + +if [[ -n "$_help" ]]; then + printf "$usage" "$(basename $0)" + exit "$_err" +fi + +if [[ -z "$ORIG_CFG" ]]; then + printf '%s\n' 'No config specified; exiting' + exit 0 +fi + +# locate and move to test directory +mkdir -p "$TESTDIR" +printf 'Running test from %s\n' "$TESTDIR" +cd "$TESTDIR" || exit + +# normalizes ports for local execution +IFS='' read -r -d '' normalize <<'EOF' +BEGIN { + i = 29800 +} + +/".*:...."/ { + gsub(/".*:...."/, "\"""0.0.0.0:" i "\""); + ++i +} + +{ print } +EOF + +CFG="$TESTDIR"/config +awk "$normalize" "$ORIG_CFG" > "$CFG" + +twophase=$(grep -q '2pc=1' "$CFG" && printf '1\n' || printf '0\n') +arch= +if test "$twophase" -eq 0; then + arch='atomizer' +else + arch='2pc' +fi + +PERFS= +on_int() { + printf 'Interrupting all components\n' + trap '' SIGINT # avoid interrupting ourself + for i in $PIDS; do # intentionally unquoted + if [[ -n "RECORD" ]]; then + kill -SIGINT -- "-$i" + else + kill -SIGINT -- "$i" + fi + done + wait + sleep 5 + + _failed= + for i in "$TESTDIR"/tx_samples_*.txt; do + if ! test -s "$i"; then + printf 'Could not generate plots: %s is not a non-empty, regular file\n' "$i" + _failed=1 + break + fi + done + + if [[ "$RECORD" = 'perf' ]]; then + for i in $PERFS; do + kill -SIGTERM -- "$i" + done + fi + + if [[ -x "$(which flamegraph.pl)" && -x "$(which stackcollapse-perf.pl)" && -n "$(find "$TESTDIR" -maxdepth 1 -name '*.perf' -print -quit)" ]]; then + printf 'Generating Flamegraphs\n' + for i in "$TESTDIR"/*.perf; do + waitpid -t 5 -e $(lsof -Qt "$i") &>/dev/null + perf script -i "$i" | stackcollapse-perf.pl > "${i/.perf/.folded}" + flamegraph.pl "${i/.perf/.folded}" > "${i/.perf/.svg}" + rm -- "${i/.perf/.folded}" + done + fi + + if [[ -z "$_failed" ]]; then + printf 'Generating plots\n' + python "$RT"/scripts/plot.py "$TESTDIR" + fi + + printf 'Terminating any remaining processes\n' + for i in $PIDS; do # intentionally unquoted + if [[ -n "RECORD" ]]; then + kill -SIGTERM -- "-$i" + else + kill -SIGTERM -- "$i" + fi + done +} + +trap on_int SIGINT + +getcount() { + count=$(grep -E "$1_count" "$CFG") + if test "$count"; then + printf '%s\n' "$count" | cut -d'=' -f2 + else + printf '0\n' + fi +} + +getpath() { + case "$1" in + # uniquely-named + archiver) printf '%s/src/uhs/atomizer/archiver/archiverd\n' "$BLD";; + atomizer) printf '%s/src/uhs/atomizer/atomizer/atomizer-raftd\n' "$BLD";; + watchtower) printf '%s/src/uhs/atomizer/watchtower/watchtowerd\n' "$BLD";; + coordinator) printf '%s/src/uhs/twophase/coordinator/coordinatord\n' "$BLD";; + + # special-case + seeder) printf '%s/tools/shard-seeder/shard-seeder\n' "$BLD";; + + # architecture-dependent + loadgen) + if test "$twophase" -eq 1; then + printf '%s/tools/bench/twophase-gen\n' "$BLD" + else + printf '%s/tools/bench/atomizer-cli-watchtower\n' "$BLD" + fi;; + shard) + if test "$twophase" -eq 1; then + printf '%s/src/uhs/twophase/locking_shard/locking-shardd\n' "$BLD" + else + printf '%s/src/uhs/atomizer/shard/shardd\n' "$BLD" + fi;; + sentinel) + if test "$twophase" -eq 1; then + printf '%s/src/uhs/twophase/sentinel_2pc/sentineld-2pc\n' "$BLD" + else + printf '%s/src/uhs/atomizer/sentinel/sentineld\n' "$BLD" + fi;; + *) printf 'Unrecognized component: %s\n' "$1";; + esac +} + +run() { + PROC_LOG="$TESTDIR"/"$PNAME.log" + PERF_LOG="$TESTDIR"/"$PNAME-perf.log" + COMP= + case "$RECORD" in + perf) + $@ &> "$PROC_LOG" & + COMP="$!" + perf record -F 99 -a -g -o "$PNAME".perf -p "$COMP" &> "$PERF_LOG" & + PERFS="$PERFS $!";; + debug) + ${DBG} "$@" &> "$PROC_LOG" & + COMP="$!";; + *) + $@ &> "$PROC_LOG" & + COMP="$!";; + esac + + if test -n "$BLOCK"; then + wait "$COMP" + fi + + echo "$COMP" +} + +seed() { + seed_from=$(grep -E 'seed_from=.*' "$CFG" | cut -d'=' -f2) + seed_from="${seed_from:-0}" + seed_to=$(grep -E 'seed_to=.*' "$CFG" | cut -d'=' -f2) + seed_to="${seed_to:-0}" + seed_count=$(( "$seed_to" - "$seed_from" )) + if test ! "$seed_to" -gt "$seed_from"; then + printf 'Running without seeding\n' + return + fi + + preseed_id="$arch"_"$COMMIT"_"$seed_count" + if test ! -e "$SEEDDIR"/"$preseed_id"; then + printf 'Creating %s\n' "$preseed_id" + mkdir -p -- "$SEEDDIR"/"$preseed_id" + pushd "$SEEDDIR"/"$preseed_id" &> /dev/null + PID=$(PNAME=seeder BLOCK=1 run "$(getpath seeder)" "$CFG") + popd &> /dev/null + fi + + printf 'Using %s as seed\n' "$preseed_id" + for i in "$SEEDDIR"/"$preseed_id"/*; do + ln -sf -- "$i" "$TESTDIR"/"$(basename "$i")" + done +} + +getpgid() { + ps -o pgid= "$1" +} + +PIDS= +launch() { + last=$(getcount "$1") + if test "$last" -le 0; then + if test "$1" = 'loadgen'; then + printf 'Running without a loadgen\n' + else + printf 'Invalid count for %s\n' "$1" + exit 1 + fi + else + for id in $(seq 0 $(( "$last" - 1 )) ); do + raft=$(getcount "$1$id") + PNAME= + if test "$raft" -gt 0; then + for node in $(seq 0 $(( "$raft" - 1 )) ); do + export PNAME="$1${id}_$node" + PID=$(run "$(getpath "$1")" "$CFG" "$id" "$node") + for ep in $(awk -F'[":]' "/$PNAME.*endpoint/ { print \$3 }" "$CFG"); do + "$RT"/scripts/wait-for-it.sh -q -t 5 -h localhost -p "$ep" + done + printf 'Launched logical %s %d, replica %d [PID: %d]\n' "$1" "$id" "$node" "$PID" + if [[ -n "RECORD" ]]; then + PIDS="$PIDS $(getpgid $PID)" + else + PIDS="$PIDS $PID" + fi + done + else + export PNAME="$1${id}" + PID=$(run "$(getpath "$1")" "$CFG" "$id") + for ep in $(awk -F'[":]' "/$PNAME.*endpoint/ { print \$3 }" "$CFG"); do + "$RT"/scripts/wait-for-it.sh -q -t 5 -h localhost -p "$ep" + done + printf 'Launched %s %d [PID: %d]\n' "$1" "$id" "$PID" + if [[ -n "RECORD" ]]; then + PIDS="$PIDS $(getpgid $PID)" + else + PIDS="$PIDS $PID" + fi + fi + done + fi +} + +seed + +if test "$twophase" -eq 0; then # atomizer + for comp in watchtower atomizer archiver shard sentinel loadgen; do + launch "$comp" + done +else # twophase + for comp in shard coordinator sentinel loadgen; do + launch "$comp" + done +fi + +printf 'Awaiting manual termination or timeout (%ds)\n' "$DURATION" +sleep "$DURATION" + +on_int diff --git a/src/parsec/agent/runners/lua/impl.cpp b/src/parsec/agent/runners/lua/impl.cpp index 4c79dc147..81eef0410 100644 --- a/src/parsec/agent/runners/lua/impl.cpp +++ b/src/parsec/agent/runners/lua/impl.cpp @@ -14,11 +14,12 @@ #include namespace cbdc::parsec::agent::runner { - static const auto secp_context - = std::unique_ptr( - secp256k1_context_create(SECP256K1_CONTEXT_VERIFY), - &secp256k1_context_destroy); + using secp256k1_context_destroy_type = void (*)(secp256k1_context*); + + std::unique_ptr + secp_context{secp256k1_context_create(SECP256K1_CONTEXT_NONE), + &secp256k1_context_destroy}; lua_runner::lua_runner(std::shared_ptr logger, const cbdc::parsec::config& cfg, @@ -260,6 +261,7 @@ namespace cbdc::parsec::agent::runner { if(secp256k1_schnorrsig_verify(secp_context.get(), sig.data(), sighash.data(), + sighash.size(), &pubkey) != 1) { lua_pushliteral(L, "invalid signature"); diff --git a/src/uhs/atomizer/sentinel/controller.cpp b/src/uhs/atomizer/sentinel/controller.cpp index ac2af005e..dc1ed181d 100644 --- a/src/uhs/atomizer/sentinel/controller.cpp +++ b/src/uhs/atomizer/sentinel/controller.cpp @@ -95,13 +95,50 @@ namespace cbdc::sentinel { if(!res.has_value()) { m_logger->debug("Accepted tx:", cbdc::to_string(tx_id)); + // Only forward transactions that are valid + send_transaction(tx); } else { - m_logger->debug("Rejected tx:", cbdc::to_string(tx_id)); - } + m_logger->debug( + "Rejected tx:", + cbdc::to_string(tx_id), + "(", + cbdc::transaction::validation::to_string(res.value()), + ")"); + + m_logger->debug("TX Inputs:", std::to_string(tx.m_inputs.size())); + for(size_t i = 0; i < tx.m_inputs.size(); i++) { + m_logger->debug( + "Input [", + std::to_string(i), + "]: prevout [", + cbdc::to_string(tx.m_inputs[i].m_prevout.m_tx_id), + "/", + std::to_string(tx.m_inputs[i].m_prevout.m_index), + "]"); + if(tx.m_inputs[i].m_spend_data.has_value()) { + m_logger->debug( + "Input [", + std::to_string(i), + "]: m_spend_data [", + cbdc::to_string(tx.m_inputs[i].m_spend_data->m_blind), + "/", + std::to_string(tx.m_inputs[i].m_spend_data->m_value), + "]"); + } else { + m_logger->debug("Input [", + std::to_string(i), + "]: no m_spend_data"); + } + } - // Only forward transactions that are valid - if(!res.has_value()) { - send_transaction(tx); + m_logger->debug("TX Outputs:", + std::to_string(tx.m_outputs.size())); + m_logger->debug("TX Witnesses:", + std::to_string(tx.m_witness.size())); + for(size_t i = 0; i < tx.m_witness.size(); i++) { + auto buf = cbdc::make_buffer(tx.m_witness[i]); + m_logger->debug("TX Witness [", i, "]: ", buf.to_hex()); + } } return execute_response{status, res}; diff --git a/src/uhs/atomizer/sentinel/controller.hpp b/src/uhs/atomizer/sentinel/controller.hpp index 483433cfa..667f7b51e 100644 --- a/src/uhs/atomizer/sentinel/controller.hpp +++ b/src/uhs/atomizer/sentinel/controller.hpp @@ -10,11 +10,14 @@ #include "uhs/sentinel/async_interface.hpp" #include "uhs/sentinel/client.hpp" #include "uhs/sentinel/interface.hpp" +#include "uhs/transaction/transaction.hpp" #include "util/common/config.hpp" #include "util/network/connection_manager.hpp" #include #include +#include +#include namespace cbdc::sentinel { /// Sentinel implementation. @@ -67,9 +70,11 @@ namespace cbdc::sentinel { std::unique_ptr m_rpc_server; + using secp256k1_context_destroy_type = void (*)(secp256k1_context*); + std::unique_ptr - m_secp{secp256k1_context_create(SECP256K1_CONTEXT_SIGN), + secp256k1_context_destroy_type> + m_secp{secp256k1_context_create(SECP256K1_CONTEXT_NONE), &secp256k1_context_destroy}; std::vector> @@ -83,8 +88,6 @@ namespace cbdc::sentinel { privkey_t m_privkey{}; - void send_transaction(const transaction::full_tx& tx); - void validate_result_handler(async_interface::validate_result v_res, const transaction::full_tx& tx, transaction::compact_tx ctx, @@ -95,6 +98,8 @@ namespace cbdc::sentinel { std::unordered_set requested); void send_compact_tx(const transaction::compact_tx& ctx); + + void send_transaction(const transaction::full_tx& tx); }; } diff --git a/src/uhs/atomizer/shard/controller.cpp b/src/uhs/atomizer/shard/controller.cpp index 12ab56929..f6bdb148d 100644 --- a/src/uhs/atomizer/shard/controller.cpp +++ b/src/uhs/atomizer/shard/controller.cpp @@ -37,6 +37,10 @@ namespace cbdc::shard { t.join(); } } + + if(m_audit_thread.joinable()) { + m_audit_thread.join(); + } } auto controller::init() -> bool { @@ -49,6 +53,13 @@ namespace cbdc::shard { return false; } + m_audit_log.open(m_opts.m_shard_audit_logs[m_shard_id], + std::ios::app | std::ios::out); + if(!m_audit_log.good()) { + m_logger->error("Failed to open audit log"); + return false; + } + if(!m_archiver_client.init()) { m_logger->warn("Failed to connect to archiver"); } @@ -139,6 +150,7 @@ namespace cbdc::shard { const auto past_blk = m_archiver_client.get_block(i); if(past_blk) { m_shard.digest_block(past_blk.value()); + audit(); } else { m_logger->info("Waiting for archiver sync"); const auto wait_time = std::chrono::milliseconds(10); @@ -148,6 +160,7 @@ namespace cbdc::shard { } } } + audit(); m_logger->info("Digested block", blk.m_height); return std::nullopt; @@ -206,4 +219,29 @@ namespace cbdc::shard { std::visit(res_handler, res); } } + + void controller::audit() { + auto height = m_shard.best_block_height(); + if(m_opts.m_shard_audit_interval > 0 + && height % m_opts.m_shard_audit_interval != 0) { + return; + } + + auto snp = m_shard.get_snapshot(); + if(m_audit_thread.joinable()) { + m_audit_thread.join(); + } + m_audit_thread = std::thread([this, s = std::move(snp), height]() { + auto range_summaries = m_shard.audit(s); + auto buf = cbdc::buffer(); + buf.extend(sizeof(commitment_t)); + for(const auto& [bucket, summary] : range_summaries) { + buf.clear(); + buf.append(summary.data(), summary.size()); + m_audit_log << height << " " << static_cast(bucket) << " " + << buf.to_hex() << std::endl; + } + m_logger->info("Audit completed for", height); + }); + } } diff --git a/src/uhs/atomizer/shard/controller.hpp b/src/uhs/atomizer/shard/controller.hpp index fde38706e..d90715367 100644 --- a/src/uhs/atomizer/shard/controller.hpp +++ b/src/uhs/atomizer/shard/controller.hpp @@ -60,11 +60,15 @@ namespace cbdc::shard { blocking_queue m_request_queue; std::vector m_handler_threads; + std::ofstream m_audit_log; + std::thread m_audit_thread; + auto server_handler(cbdc::network::message_t&& pkt) -> std::optional; auto atomizer_handler(cbdc::network::message_t&& pkt) -> std::optional; void request_consumer(); + void audit(); }; } diff --git a/src/uhs/atomizer/shard/shard.cpp b/src/uhs/atomizer/shard/shard.cpp index c328acfa0..cbe59e49b 100644 --- a/src/uhs/atomizer/shard/shard.cpp +++ b/src/uhs/atomizer/shard/shard.cpp @@ -5,6 +5,8 @@ #include "shard.hpp" +#include "uhs/transaction/messages.hpp" + #include namespace cbdc::shard { @@ -49,7 +51,8 @@ namespace cbdc::shard { sizeof(this->m_best_block_height)); } - update_snapshot(); + auto snp = get_snapshot(); + update_snapshot(std::move(snp)); return std::nullopt; } @@ -64,12 +67,39 @@ namespace cbdc::shard { // Iterate over all confirmed transactions for(const auto& tx : blk.m_transactions) { // Add new outputs - for(const auto& out : tx.m_uhs_outputs) { - if(is_output_on_shard(out)) { - std::array out_arr{}; - std::memcpy(out_arr.data(), out.data(), out.size()); - leveldb::Slice OutPointKey(out_arr.data(), out.size()); - batch.Put(OutPointKey, leveldb::Slice()); + for(const auto& out : tx.m_outputs) { + auto id = transaction::calculate_uhs_id(out); + if(is_output_on_shard(id)) { + std::array out_arr{}; + std::memcpy(out_arr.data(), id.data(), id.size()); + leveldb::Slice OutPointKey(out_arr.data(), id.size()); + + static constexpr auto aux_size = sizeof(out.m_value_commitment); + static constexpr auto sz_size = sizeof(size_t); + const auto rng_size = out.m_range.size(); + static constexpr auto preimg_size = sizeof(out.m_provenance); + auto total_size = + aux_size + sz_size + rng_size + preimg_size; + + std::vector proofs_arr{}; + proofs_arr.reserve(total_size); + proofs_arr.assign(total_size, 0); + std::memcpy(proofs_arr.data(), + out.m_value_commitment.data(), + aux_size); + std::memcpy(proofs_arr.data() + aux_size, + &rng_size, + sz_size); + std::memcpy(proofs_arr.data() + aux_size + sz_size, + out.m_range.data(), + rng_size); + std::memcpy(proofs_arr.data() + aux_size + sz_size + rng_size, + out.m_provenance.data(), + preimg_size); + + leveldb::Slice ProofVal(proofs_arr.data(), + proofs_arr.size()); + batch.Put(OutPointKey, ProofVal); } } @@ -97,7 +127,8 @@ namespace cbdc::shard { // Commit the changes atomically this->m_db->Write(this->m_write_options, &batch); - update_snapshot(); + auto snp = get_snapshot(); + update_snapshot(std::move(snp)); return true; } @@ -174,13 +205,81 @@ namespace cbdc::shard { return config::hash_in_shard_range(m_prefix_range, uhs_hash); } - void shard::update_snapshot() { + auto + shard::is_output_on_shard(const transaction::compact_output& put) const + -> bool { + auto id = transaction::calculate_uhs_id(put); + return config::hash_in_shard_range(m_prefix_range, id); + } + + void shard::update_snapshot(std::shared_ptr snp) { std::unique_lock l(m_snp_mut); m_snp_height = m_best_block_height; - m_snp = std::shared_ptr( + m_snp = std::move(snp); + } + + auto shard::audit(const std::shared_ptr& snp) + -> std::unordered_map { + std::unordered_map> comms{}; + auto opts = leveldb::ReadOptions(); + opts.snapshot = snp.get(); + auto it = std::shared_ptr(m_db->NewIterator(opts)); + it->SeekToFirst(); + // Skip best block height key + it->Next(); + for(; it->Valid(); it->Next()) { + auto key = it->key(); + auto val = it->value(); + + static constexpr auto comm_size + = sizeof(transaction::compact_output::m_value_commitment); + size_t rng_size{}; + static constexpr auto sz_size = sizeof(size_t); + + transaction::compact_output outp{}; + hash_t id{}; + std::memcpy(id.data(), key.data(), key.size()); + std::memcpy(outp.m_value_commitment.data(), val.data(), comm_size); + val.remove_prefix(comm_size); + std::memcpy(&rng_size, val.data(), sz_size); + val.remove_prefix(sz_size); + outp.m_range.reserve(rng_size); + outp.m_range.assign(rng_size, 0); + std::memcpy(outp.m_range.data(), val.data(), rng_size); + val.remove_prefix(rng_size); + std::memcpy(outp.m_provenance.data(), + val.data(), + outp.m_provenance.size()); + + if(id != transaction::calculate_uhs_id(outp)) { + continue; + } + auto bucket = id[0]; + if(comms.find(bucket) == comms.end()) { + std::vector commits{}; + commits.reserve(1); + comms.emplace(bucket, std::move(commits)); + } + comms[bucket].emplace_back(outp.m_value_commitment); + } + + std::unordered_map summaries{}; + for(auto& [k, v] : comms) { + auto summary = sum_commitments(m_secp.get(), v); + if(summary.has_value()) { + summaries[k] = summary.value(); + } + } + + return summaries; + } + + auto shard::get_snapshot() -> std::shared_ptr { + auto snp = std::shared_ptr( m_db->GetSnapshot(), [&](const leveldb::Snapshot* p) { m_db->ReleaseSnapshot(p); }); + return snp; } } diff --git a/src/uhs/atomizer/shard/shard.hpp b/src/uhs/atomizer/shard/shard.hpp index 19ad733e4..9e3f514bd 100644 --- a/src/uhs/atomizer/shard/shard.hpp +++ b/src/uhs/atomizer/shard/shard.hpp @@ -14,7 +14,6 @@ #include "uhs/atomizer/atomizer/block.hpp" #include "uhs/atomizer/atomizer/format.hpp" #include "uhs/atomizer/watchtower/tx_error_messages.hpp" -#include "uhs/transaction/transaction.hpp" #include "util/common/config.hpp" #include "util/common/logging.hpp" #include "util/network/connection_manager.hpp" @@ -65,11 +64,27 @@ namespace cbdc::shard { /// \return the best block height. [[nodiscard]] auto best_block_height() const -> uint64_t; + /// Returns a LevelDB snapshot of the current state of the shard's + /// database. + /// \return LevelDB snapshot. + auto get_snapshot() -> std::shared_ptr; + + /// Audit the supply of coins in this shard's UHS and check UHS IDs + /// match the nested data and value stored in the UHS. + /// \param snp LevelDB snapshot upon which to calculate the audit. + /// \return per-range summary commitments to value + auto audit(const std::shared_ptr& snp) + -> std::unordered_map; + private: [[nodiscard]] auto is_output_on_shard(const hash_t& uhs_hash) const -> bool; - void update_snapshot(); + [[nodiscard]] auto + is_output_on_shard(const transaction::compact_output& put) const + -> bool; + + void update_snapshot(std::shared_ptr snp); std::unique_ptr m_db; leveldb::ReadOptions m_read_options; @@ -81,7 +96,14 @@ namespace cbdc::shard { uint64_t m_snp_height{}; std::shared_mutex m_snp_mut; - const std::string m_best_block_height_key = "bestBlockHeight"; + const std::string m_best_block_height_key; + + using secp256k1_context_destroy_type = void (*)(secp256k1_context*); + + std::unique_ptr + m_secp{secp256k1_context_create(SECP256K1_CONTEXT_NONE), + &secp256k1_context_destroy}; std::pair m_prefix_range; }; diff --git a/src/uhs/atomizer/watchtower/block_cache.cpp b/src/uhs/atomizer/watchtower/block_cache.cpp index 1c0d018e0..9fead55d2 100644 --- a/src/uhs/atomizer/watchtower/block_cache.cpp +++ b/src/uhs/atomizer/watchtower/block_cache.cpp @@ -20,8 +20,9 @@ namespace cbdc::watchtower { for(auto& in : tx.m_inputs) { m_spent_ids.erase(in); } - for(auto& out : tx.m_uhs_outputs) { - m_unspent_ids.erase(out); + for(auto& out : tx.m_outputs) { + auto id = transaction::calculate_uhs_id(out); + m_unspent_ids.erase(id); } } m_blks.pop(); @@ -36,9 +37,10 @@ namespace cbdc::watchtower { m_spent_ids.insert( {{in, std::make_pair(blk_height, tx.m_id)}}); } - for(auto& out : tx.m_uhs_outputs) { + for(auto& out : tx.m_outputs) { + auto id = transaction::calculate_uhs_id(out); m_unspent_ids.insert( - {{out, std::make_pair(blk_height, tx.m_id)}}); + {{id, std::make_pair(blk_height, tx.m_id)}}); } } m_best_blk_height = std::max(m_best_blk_height, blk_height); diff --git a/src/uhs/client/atomizer_client.cpp b/src/uhs/client/atomizer_client.cpp index 04274e852..d55a05f3e 100644 --- a/src/uhs/client/atomizer_client.cpp +++ b/src/uhs/client/atomizer_client.cpp @@ -45,13 +45,20 @@ namespace cbdc { it->second.insert(it->second.end(), ctx.m_inputs.begin(), ctx.m_inputs.end()); + std::vector uhs_ids{}; + std::transform(ctx.m_outputs.begin(), + ctx.m_outputs.end(), + std::back_inserter(uhs_ids), + [](transaction::compact_output p) -> hash_t { + return transaction::calculate_uhs_id(p); + }); it->second.insert(it->second.end(), - ctx.m_uhs_outputs.begin(), - ctx.m_uhs_outputs.end()); + uhs_ids.begin(), + uhs_ids.end()); } for(const auto& [tx_id, in] : pending_inputs()) { - tus.insert({tx_id, {in.hash()}}); + tus.insert({tx_id, {in.m_prevout_data.m_id}}); } cbdc::watchtower::status_update_request req{tus}; diff --git a/src/uhs/client/atomizer_client.hpp b/src/uhs/client/atomizer_client.hpp index e85adaf73..5d2a1674c 100644 --- a/src/uhs/client/atomizer_client.hpp +++ b/src/uhs/client/atomizer_client.hpp @@ -64,9 +64,11 @@ namespace cbdc { std::shared_ptr m_logger; cbdc::config::options m_opts; + using secp256k1_context_destroy_type = void (*)(secp256k1_context*); + std::unique_ptr - m_secp{secp256k1_context_create(SECP256K1_CONTEXT_SIGN), + secp256k1_context_destroy_type> + m_secp{secp256k1_context_create(SECP256K1_CONTEXT_NONE), &secp256k1_context_destroy}; }; } diff --git a/src/uhs/client/client.hpp b/src/uhs/client/client.hpp index ae3cb6d2a..a1366cb2c 100644 --- a/src/uhs/client/client.hpp +++ b/src/uhs/client/client.hpp @@ -10,6 +10,8 @@ #include "uhs/transaction/validation.hpp" #include "uhs/transaction/wallet.hpp" +#include + namespace cbdc { namespace address { static constexpr auto bits_per_byte = 8; @@ -308,6 +310,34 @@ namespace cbdc { void save(); void register_pending_tx(const transaction::full_tx& tx); + + using secp256k1_context_destroy_type = void (*)(secp256k1_context*); + + std::unique_ptr + m_secp{secp256k1_context_create(SECP256K1_CONTEXT_NONE), + &secp256k1_context_destroy}; + + struct GensDeleter { + explicit GensDeleter(secp256k1_context* ctx) : m_ctx(ctx) {} + + void operator()(secp256k1_bppp_generators* gens) const { + secp256k1_bppp_generators_destroy(m_ctx, gens); + } + + secp256k1_context* m_ctx; + }; + + /// should be set to exactly `max(n_bits/log2(base), base) + 7` + /// + /// We use n_bits = 64, base = 16, so this should always be 24. + static const inline auto generator_count = 16 + 8; + + std::unique_ptr + m_generators{ + secp256k1_bppp_generators_create(m_secp.get(), + generator_count), + GensDeleter(m_secp.get())}; }; } diff --git a/src/uhs/client/twophase_client.hpp b/src/uhs/client/twophase_client.hpp index 967062ee7..2782a918d 100644 --- a/src/uhs/client/twophase_client.hpp +++ b/src/uhs/client/twophase_client.hpp @@ -82,9 +82,11 @@ namespace cbdc { std::shared_ptr m_logger; cbdc::config::options m_opts; + using secp256k1_context_destroy_type = void (*)(secp256k1_context*); + std::unique_ptr - m_secp{secp256k1_context_create(SECP256K1_CONTEXT_SIGN), + secp256k1_context_destroy_type> + m_secp{secp256k1_context_create(SECP256K1_CONTEXT_NONE), &secp256k1_context_destroy}; static constexpr auto m_client_timeout diff --git a/src/uhs/transaction/messages.cpp b/src/uhs/transaction/messages.cpp index db35ce7dc..816aad294 100644 --- a/src/uhs/transaction/messages.cpp +++ b/src/uhs/transaction/messages.cpp @@ -22,46 +22,83 @@ namespace cbdc { auto operator<<(serializer& packet, const transaction::output& out) -> serializer& { - return packet << out.m_witness_program_commitment << out.m_value; + return packet << out.m_witness_program_commitment << out.m_id + << out.m_value_commitment << out.m_range; } auto operator>>(serializer& packet, transaction::output& out) -> serializer& { - return packet >> out.m_witness_program_commitment >> out.m_value; + return packet >> out.m_witness_program_commitment >> out.m_id + >> out.m_value_commitment >> out.m_range; + } + + auto operator<<(serializer& packet, const transaction::compact_output& out) + -> serializer& { + return packet << out.m_value_commitment << out.m_range << out.m_provenance; + } + + auto operator>>(serializer& packet, transaction::compact_output& out) + -> serializer& { + return packet >> out.m_value_commitment >> out.m_range >> out.m_provenance; + } + + auto operator<<(serializer& packet, const transaction::spend_data& spnd) + -> serializer& { + return packet << spnd.m_blind << spnd.m_value; + } + + auto operator>>(serializer& packet, transaction::spend_data& spnd) + -> serializer& { + return packet >> spnd.m_blind >> spnd.m_value; } auto operator<<(serializer& packet, const transaction::input& inp) -> serializer& { - return packet << inp.m_prevout << inp.m_prevout_data; + return packet << inp.m_prevout << inp.m_prevout_data + << inp.m_spend_data; } auto operator>>(serializer& packet, transaction::input& inp) -> serializer& { - return packet >> inp.m_prevout >> inp.m_prevout_data; + return packet >> inp.m_prevout >> inp.m_prevout_data + >> inp.m_spend_data; } auto operator<<(serializer& packet, const transaction::full_tx& tx) -> serializer& { - return packet << tx.m_inputs << tx.m_outputs << tx.m_witness; + return packet << tx.m_inputs << tx.m_outputs << tx.m_witness + << tx.m_out_spend_data; } auto operator>>(serializer& packet, transaction::full_tx& tx) -> serializer& { - return packet >> tx.m_inputs >> tx.m_outputs >> tx.m_witness; + return packet >> tx.m_inputs >> tx.m_outputs >> tx.m_witness + >> tx.m_out_spend_data; } auto operator<<(serializer& packet, const transaction::compact_tx& tx) -> serializer& { - return packet << tx.m_id << tx.m_inputs << tx.m_uhs_outputs + return packet << tx.m_id << tx.m_inputs << tx.m_outputs << tx.m_attestations; } auto operator>>(serializer& packet, transaction::compact_tx& tx) -> serializer& { - return packet >> tx.m_id >> tx.m_inputs >> tx.m_uhs_outputs + return packet >> tx.m_id >> tx.m_inputs >> tx.m_outputs >> tx.m_attestations; } + auto operator>>(serializer& packet, + transaction::validation::proof_error& e) -> serializer& { + return packet >> e.m_code; + } + + auto operator<<(serializer& packet, + const transaction::validation::proof_error& e) + -> serializer& { + return packet << e.m_code; + } + auto operator>>(serializer& packet, transaction::validation::input_error& e) -> serializer& { return packet >> e.m_code >> e.m_data_err >> e.m_idx; diff --git a/src/uhs/transaction/messages.hpp b/src/uhs/transaction/messages.hpp index 0002cd11b..49dd9cf16 100644 --- a/src/uhs/transaction/messages.hpp +++ b/src/uhs/transaction/messages.hpp @@ -10,6 +10,8 @@ #include "util/serialization/serializer.hpp" #include "validation.hpp" +// todo: update all doc-comments for serialization of proofs (if-needed) + namespace cbdc { /// \brief Serializes an out_point. /// @@ -27,7 +29,9 @@ namespace cbdc { /// \brief Serializes an output. /// - /// Serializes the witness program commitment, and then the value. + /// Serializes the witness program commitment, then the UHS ID, then + /// the nonce, then the auxiliary commitment, then the range proof, and + /// then the consistency signature. /// \see \ref cbdc::operator<<(serializer&, const std::array&) /// \see \ref cbdc::operator<<(serializer&, T) auto operator<<(serializer& packet, const transaction::output& out) @@ -38,6 +42,32 @@ namespace cbdc { auto operator>>(serializer& packet, transaction::output& out) -> serializer&; + /// \brief Serializes a compact_output. + /// + /// Serializes the UHS ID, then the auxiliary commitment, then the range + /// proof, and then the consistency signature. + /// \see \ref cbdc::operator<<(serializer&, const std::array&) + auto operator<<(serializer& packet, const transaction::compact_output& out) + -> serializer&; + + /// Deserializes a compact_output. + /// \see \ref cbdc::operator<<(serializer&, const transaction::output&) + auto operator>>(serializer& packet, transaction::compact_output& out) + -> serializer&; + + /// \brief Serializes additional spend-required data for an output + /// + /// Serializes the blinding factor and then the value. + /// \see \ref cbdc::operator<<(serializer&, const std::array&) + /// \see \ref cbdc::operator<<(serializer&, T) + auto operator<<(serializer& packet, const transaction::spend_data& spnd) + -> serializer&; + + /// Deserializes additional spend-required data for an output + /// \see \ref cbdc::operator<<(serializer&, const transaction::spend_data&) + auto operator>>(serializer& packet, transaction::spend_data& spnd) + -> serializer&; + /// \brief Serializes an input. /// /// Serializes the out_point and then the output. @@ -80,6 +110,20 @@ namespace cbdc { auto operator>>(serializer& packet, transaction::compact_tx& tx) -> serializer&; + /// Deserializes a proof error. + /// \see \ref cbdc::operator<<(serializer&, + /// const transaction::validation::proof_error&) + auto operator>>(serializer& packet, + transaction::validation::proof_error& e) -> serializer&; + + /// \brief Serializes a proof error. + /// + /// Serializes the error code. + /// \see \ref cbdc::operator<<(serializer&, T) + auto operator<<(serializer& packet, + const transaction::validation::proof_error& e) + -> serializer&; + /// Deserializes an input error. /// \see \ref cbdc::operator<<(serializer&, /// const transaction::validation::input_error&) diff --git a/src/uhs/transaction/transaction.cpp b/src/uhs/transaction/transaction.cpp index 99e16dc92..aa589c0e5 100644 --- a/src/uhs/transaction/transaction.cpp +++ b/src/uhs/transaction/transaction.cpp @@ -25,16 +25,70 @@ namespace cbdc::transaction { auto output::operator==(const output& rhs) const -> bool { return m_witness_program_commitment == rhs.m_witness_program_commitment - && m_value == rhs.m_value; + && m_id == rhs.m_id && m_value_commitment == rhs.m_value_commitment + && m_range == rhs.m_range; } auto output::operator!=(const output& rhs) const -> bool { return !(*this == rhs); } - output::output(hash_t witness_program_commitment, uint64_t value) - : m_witness_program_commitment(witness_program_commitment), - m_value(value) {} + auto output_preimage(const out_point& point, const output& put) + -> std::array { + std::array + buf{}; + + static constexpr auto tx_id_size = sizeof(point.m_tx_id); + static constexpr auto idx_size = sizeof(point.m_index); + static constexpr auto wcom_size + = sizeof(put.m_witness_program_commitment); + + std::memcpy(buf.data(), point.m_tx_id.data(), tx_id_size); + std::memcpy(buf.data() + tx_id_size, &point.m_index, idx_size); + std::memcpy(buf.data() + tx_id_size + idx_size, + put.m_witness_program_commitment.data(), + wcom_size); + + return buf; + } + + auto output_nested_hash(const out_point& point, const output& put) + -> hash_t { + auto buf = output_preimage(point, put); + CSHA256 sha; + sha.Write(buf.data(), buf.size()); + + hash_t res{}; + sha.Finalize(res.data()); + + return res; + } + + compact_output::compact_output(const output& put, const out_point& point) + : m_value_commitment(put.m_value_commitment), + m_range(put.m_range.value()), + m_provenance(output_nested_hash(point, put)) {} + + compact_output::compact_output(const commitment_t& aux, + const rangeproof_t& range, + const hash_t& provenance) + : m_value_commitment(aux), + m_range(range), + m_provenance(provenance) {} + + auto compact_output::operator==(const compact_output& rhs) const -> bool { + return m_value_commitment == rhs.m_value_commitment + && m_range == rhs.m_range + && m_provenance == rhs.m_provenance; + } + + auto compact_output::operator!=(const compact_output& rhs) const -> bool { + return !(*this == rhs); + } auto input::operator==(const input& rhs) const -> bool { return m_prevout == rhs.m_prevout @@ -46,12 +100,13 @@ namespace cbdc::transaction { } auto input::hash() const -> hash_t { - auto buf = cbdc::make_buffer(*this); - CSHA256 sha; - hash_t result; + auto opt_buf = cbdc::make_buffer(this->m_prevout); + sha.Write(opt_buf.c_ptr(), opt_buf.size()); + sha.Write(this->m_prevout_data.m_witness_program_commitment.data(), + sizeof(hash_t)); - sha.Write(buf.c_ptr(), buf.size()); + hash_t result; sha.Finalize(result.data()); return result; @@ -72,11 +127,13 @@ namespace cbdc::transaction { auto tx_id(const full_tx& tx) noexcept -> hash_t { CSHA256 sha; - auto inp_buf = cbdc::make_buffer(tx.m_inputs); - sha.Write(inp_buf.c_ptr(), inp_buf.size()); + for(const auto& inp : tx.m_inputs) { + sha.Write(inp.hash().data(), sizeof(hash_t)); + } - auto out_buf = cbdc::make_buffer(tx.m_outputs); - sha.Write(out_buf.c_ptr(), out_buf.size()); + for(const auto& out : tx.m_outputs) { + sha.Write(out.m_witness_program_commitment.data(), sizeof(hash_t)); + } hash_t ret; sha.Finalize(ret.data()); @@ -91,8 +148,15 @@ namespace cbdc::transaction { return std::nullopt; } ret.m_prevout_data = tx.m_outputs[i]; + // The range proof is not required for the inputs & explicitly removed: + ret.m_prevout_data.m_range.reset(); + if(tx.m_out_spend_data.has_value() && tx.m_out_spend_data.value().size() > i) { + ret.m_spend_data = tx.m_out_spend_data.value()[i]; + } + ret.m_prevout.m_index = i; ret.m_prevout.m_tx_id = txid; + return ret; } @@ -102,21 +166,207 @@ namespace cbdc::transaction { return input_from_output(tx, i, id); } - auto uhs_id_from_output(const hash_t& entropy, - uint64_t i, - const output& output) -> hash_t { + auto calculate_uhs_id(const out_point& point, + const output& put, + const commitment_t& value) -> hash_t { + auto buf = output_nested_hash(point, put); + CSHA256 sha; - hash_t ret; - sha.Write(entropy.data(), entropy.size()); - std::array index_arr{}; - std::memcpy(index_arr.data(), &i, sizeof(i)); - sha.Write(index_arr.data(), sizeof(i)); + sha.Write(buf.data(), buf.size()); + sha.Write(value.data(), value.size()); - auto buf = cbdc::make_buffer(output); + hash_t id{}; + sha.Finalize(id.data()); - sha.Write(buf.c_ptr(), buf.size()); - sha.Finalize(ret.data()); - return ret; + return id; + } + + auto calculate_uhs_id(const compact_output& put) -> hash_t { + CSHA256 sha; + sha.Write(put.m_provenance.data(), put.m_provenance.size()); + sha.Write(put.m_value_commitment.data(), put.m_value_commitment.size()); + // sha.Write(put.m_range.data(), put.m_range.size()); + + hash_t id{}; + sha.Finalize(id.data()); + + return id; + } + + auto roll_auxiliaries(secp256k1_context* ctx, + random_source& rng, + const std::vector& blinds, + std::vector& out_spend_data) + -> std::vector { + const auto make_public = blinds.empty(); + const hash_t empty{}; + + std::vector auxiliaries{}; + + std::vector new_blinds{}; + for(uint64_t i = 0; i < out_spend_data.size() - 1; ++i) { + while(true) { + auto rprime = make_public ? empty : rng.random_hash(); + auto commitment + = commit(ctx, out_spend_data[i].m_value, rprime); + if(commitment.has_value()) { + auxiliaries.push_back(commitment.value()); + new_blinds.push_back(rprime); + out_spend_data[i].m_blind = rprime; + break; + } + } + } + + if(!make_public) { + std::vector allblinds{blinds}; + std::copy(new_blinds.begin(), + new_blinds.end(), + std::back_inserter(allblinds)); + + std::vector blind_ptrs; + blind_ptrs.reserve(allblinds.size()); + for(const auto& b : allblinds) { + blind_ptrs.push_back(b.data()); + } + + hash_t last_blind{}; + [[maybe_unused]] auto ret + = secp256k1_pedersen_blind_sum(ctx, + last_blind.data(), + blind_ptrs.data(), + allblinds.size(), + blinds.size()); + assert(ret == 1); + auxiliaries.push_back( + commit(ctx, out_spend_data.back().m_value, last_blind) + .value()); + out_spend_data.back().m_blind = last_blind; + } else { + auxiliaries.push_back( + commit(ctx, out_spend_data.back().m_value, empty).value()); + new_blinds.push_back(empty); + out_spend_data.back().m_blind = empty; + } + + return auxiliaries; + } + + auto prove(secp256k1_context* ctx, + secp256k1_bppp_generators* gens, + random_source& rng, + const spend_data& out_spend_data, + const secp256k1_pedersen_commitment* comm) -> rangeproof_t { + + static constexpr auto scratch_size = 100UL * 1024UL; + secp256k1_scratch_space* scratch + = secp256k1_scratch_space_create(ctx, scratch_size); + + static constexpr auto upper_bound = 64; // 2^64 - 1 + static constexpr auto base = 16; + + rangeproof_t range{}; + size_t rangelen + = secp256k1_bppp_rangeproof_proof_length(ctx, upper_bound, base); + range.assign(rangelen, 0); + + [[maybe_unused]] auto ret + = secp256k1_bppp_rangeproof_prove( + ctx, + scratch, + gens, + secp256k1_generator_h, + range.data(), + &rangelen, + upper_bound, + base, + out_spend_data.m_value, + 1, + comm, // the commitment for this output + out_spend_data.m_blind.data(), + rng.random_hash().data(), + nullptr, // extra_commit + 0 // extra_commit length + ); + + secp256k1_scratch_space_destroy(ctx, scratch); + + assert(ret == 1); + + return range; + } + + auto prove_output(secp256k1_context* ctx, + secp256k1_bppp_generators* gens, + random_source& rng, + output& put, + const out_point& point, + const spend_data& out_spend_data, + const secp256k1_pedersen_commitment* auxiliary) -> bool { + auto range = prove(ctx, gens, rng, out_spend_data, auxiliary); + + put.m_range = range; + put.m_value_commitment = serialize_commitment(ctx, *auxiliary); + + auto uhs = calculate_uhs_id(point, put, put.m_value_commitment); + put.m_id = uhs; + + return true; + } + + auto add_proof(secp256k1_context* ctx, + secp256k1_bppp_generators* gens, + random_source& rng, + full_tx& tx) -> bool { + std::vector blinds{}; + for(const auto& inp : tx.m_inputs) { + auto spend_data = inp.m_spend_data; + if(!spend_data.has_value() || spend_data.value().m_value == 0) { + // No input spend data or there was a zero-valued input + return false; + } + blinds.push_back(spend_data.value().m_blind); + } + + if(!tx.m_out_spend_data.has_value()) { + // No output spend data + return false; + } + const auto& out_spend_data = tx.m_out_spend_data.value(); + for(const auto& spend_data : out_spend_data) { + if(spend_data.m_value == 0) { + // There was a zero-valued output + return false; + } + } + + auto auxiliaries + = roll_auxiliaries(ctx, rng, blinds, tx.m_out_spend_data.value()); + + const auto txid = tx_id(tx); + + for(uint64_t i = 0; i < tx.m_outputs.size(); ++i) { + [[maybe_unused]] auto res = prove_output( + ctx, + gens, + rng, + tx.m_outputs[i], + input_from_output(tx, i, txid).value().m_prevout, + tx.m_out_spend_data.value()[i], + &auxiliaries[i]); + if(!res) { + return false; + } + } + + // todo: blind spend-required data after adding proof + // (unclear quite when this should happen) + // todo: same for m_out_spend_data? + // for(auto& inp : tx.m_inputs) { + // inp.m_spend_data = std::nullopt; + //} + + return true; } auto compact_tx::operator==(const compact_tx& tx) const noexcept -> bool { @@ -126,11 +376,12 @@ namespace cbdc::transaction { compact_tx::compact_tx(const full_tx& tx) { m_id = tx_id(tx); for(const auto& inp : tx.m_inputs) { - m_inputs.push_back(inp.hash()); + m_inputs.push_back(inp.m_prevout_data.m_id); } - for(uint64_t i = 0; i < tx.m_outputs.size(); i++) { - m_uhs_outputs.push_back( - uhs_id_from_output(m_id, i, tx.m_outputs[i])); + for(size_t i{0}; i < tx.m_outputs.size(); ++i) { + auto put = tx.m_outputs[i]; + out_point point{m_id, i}; + m_outputs.emplace_back(put, point); } } @@ -145,12 +396,11 @@ namespace cbdc::transaction { auto sig = signature_t(); [[maybe_unused]] const auto sign_ret - = secp256k1_schnorrsig_sign(ctx, - sig.data(), - payload.data(), - &keypair, - nullptr, - nullptr); + = secp256k1_schnorrsig_sign32(ctx, + sig.data(), + payload.data(), + &keypair, + nullptr); assert(sign_ret == 1); return {pubkey, sig}; } @@ -178,6 +428,7 @@ namespace cbdc::transaction { if(secp256k1_schnorrsig_verify(ctx, att.second.data(), payload.data(), + payload.size(), &pubkey) != 1) { return false; diff --git a/src/uhs/transaction/transaction.hpp b/src/uhs/transaction/transaction.hpp index 0d39b6aed..a834c3653 100644 --- a/src/uhs/transaction/transaction.hpp +++ b/src/uhs/transaction/transaction.hpp @@ -7,13 +7,17 @@ #define OPENCBDC_TX_SRC_TRANSACTION_TRANSACTION_H_ #include "crypto/sha256.h" +#include "util/common/commitment.hpp" #include "util/common/hash.hpp" #include "util/common/keys.hpp" +#include "util/common/random_source.hpp" #include "util/serialization/format.hpp" #include "util/serialization/util.hpp" #include #include +#include +#include namespace cbdc::transaction { /// \brief The unique identifier of a specific \ref output from @@ -49,16 +53,39 @@ namespace cbdc::transaction { struct output { /// Hash of the witness program hash_t m_witness_program_commitment{}; - - /// The integral value of the output, in atomic units of currency - uint64_t m_value{0}; + /// The UHS ID for the output + hash_t m_id{}; + /// An auxiliary value used to prove preservation of balance + commitment_t m_value_commitment{}; + /// The rangeproof guaranteeing that the output is greater than 0 + /// This rangeproof is only required when functioning as a transaction + /// output, and is removed when converted into a transaction input. + std::optional m_range{}; auto operator==(const output& rhs) const -> bool; auto operator!=(const output& rhs) const -> bool; + }; - output(hash_t witness_program_commitment, uint64_t value); - - output() = default; + /// \brief Calculate the UHS ID from an outpoint and output + /// + /// The commitment is serialized into 32 bytes (via compression similar to + /// x-only public keys), and commits to the value, outpoint, encumbrance, + /// and a random nonce. + /// + /// \param point the \ref out_point disambiguating the origin of the + /// output to-be-spent + /// \param put the \ref output to-be-spent + /// \returns the hash serving as the UHS ID + auto calculate_uhs_id(const out_point& point, + const output& put, + const commitment_t& value) -> hash_t; + + /// \brief Additional information a spender needs to spend an input + struct spend_data { + /// The blinding factor for the auxiliary commitment + hash_t m_blind{}; + /// The value of the associated output + uint64_t m_value{0}; }; /// \brief An input for a new transaction @@ -74,12 +101,13 @@ namespace cbdc::transaction { /// The output's data output m_prevout_data; + /// Additional data to make the input spendable + std::optional m_spend_data{}; + auto operator==(const input& rhs) const -> bool; auto operator!=(const input& rhs) const -> bool; [[nodiscard]] auto hash() const -> hash_t; - - input() = default; }; /// \brief A complete transaction @@ -101,6 +129,8 @@ namespace cbdc::transaction { /// The set of witnesses std::vector m_witness{}; + std::optional> m_out_spend_data{}; + auto operator==(const full_tx& rhs) const -> bool; full_tx() = default; @@ -110,10 +140,46 @@ namespace cbdc::transaction { /// a compact transaction hash. using sentinel_attestation = std::pair; + /// \brief A compacted output of a transaction + /// + /// Contains all (and only) the information necessary for the UHS + /// to be updated and for the system to perform audits. + /// + /// \see \ref cbdc::operator<<(serializer&, const transaction::compact_output&) + struct compact_output { + /// The nonce used to compress the Pedersen Commitment to 32 bytes + commitment_t m_value_commitment{}; + /// The rangeproof guaranteeing that the output is greater than 0 + rangeproof_t m_range{}; + /// The nested hash of the outpoint and encumbrance + hash_t m_provenance{}; + + explicit compact_output(const output& put, const out_point& point); + + compact_output(const commitment_t& aux, + const rangeproof_t& range, + const hash_t& provenance); + compact_output() = default; + + auto operator==(const compact_output& rhs) const -> bool; + auto operator!=(const compact_output& rhs) const -> bool; + }; + + /// \brief Calculate the UHS ID from an compact_output + /// + /// A \ref compact_output includes all the information necessary to + /// calculate the UHS ID (by-design), so we can get the UHS ID from it + /// alone. + /// + /// \param put the \ref compact_output to-be-spent + /// \returns the hash serving as the UHS ID + auto calculate_uhs_id(const compact_output& put) -> hash_t; + /// \brief A condensed, hash-only transaction representation /// /// The minimum amount of data necessary for the transaction processor to - /// update the UHS with the changes from a \ref full_tx. + /// update the UHS with the changes from a \ref full_tx and still support + /// auditing. /// /// \see \ref cbdc::operator<<(serializer&, const transaction::compact_tx&) struct compact_tx { @@ -121,14 +187,14 @@ namespace cbdc::transaction { hash_t m_id{}; /// The set of hashes of the transaction's inputs - std::vector m_inputs; + std::vector m_inputs{}; - /// The set of hashes of the new outputs created in the transaction - std::vector m_uhs_outputs; + /// The output ids and associated proofs + std::vector m_outputs{}; /// Signatures from sentinels attesting the compact TX is valid. std::unordered_map - m_attestations; + m_attestations{}; /// Equality of two compact transactions. Only compares the transaction /// IDs. @@ -169,6 +235,68 @@ namespace cbdc::transaction { auto operator()(compact_tx const& tx) const noexcept -> size_t; }; + /// \brief Roll auxiliary cryptographic commitments + /// + /// \warning Mostly, direct use should be avoided (instead leveraging the + /// higher-level `add_proof` method). + /// + /// \param ctx a secp256k1_context initialized for signing and commitment + /// \param rng a random_source for generating nonces + /// \param blinds the blinding factors, one per-input (in order) + /// \param out_spend_data the additional spend data (in output order) + /// \return the created commitments (in output order) + auto roll_auxiliaries(secp256k1_context* ctx, + random_source& rng, + const std::vector& blinds, + std::vector& out_spend_data) + -> std::vector; + + /// \brief todo: add description + auto prove(secp256k1_context* ctx, + secp256k1_bppp_generators* gens, + random_source& rng, + const spend_data& out_spend_data, + const secp256k1_pedersen_commitment* comm) -> rangeproof_t; + + /// \brief Add cryptographic proof to a single output + /// + /// \warning Mostly, direct use should be avoided (instead leveraging the + /// higher-level `add_proof` method). + /// + /// \param ctx a secp256k1_context initialized for signing and commitment + /// \param gens bulletproof generators + /// \param rng a random_source for generating nonces + /// \param put the output to be proven + /// \param point the out_point uniquely identifying the output + /// \param out_spend_data the additional spending data for the output + /// \param auxiliary the auxiliary commitment + /// \return true if all proofs were correctly added to the output, + /// false otherwise + auto prove_output(secp256k1_context* ctx, + secp256k1_bppp_generators* gens, + random_source& rng, + output& put, + const out_point& point, + const spend_data& out_spend_data, + const secp256k1_pedersen_commitment* auxiliary) -> bool; + + /// \brief Update a transaction with cryptographic proofs + /// + /// Adds a set of cryptographic proofs which enable the auditing and + /// verification of the transaction (and, by extension, the system) without + /// requiring exposure of the transaction details to the core of the + /// system. + /// + /// \param ctx a secp256k1_context initialized for signing and commitment + /// \param gens bulletproof generators + /// \param rng a random_source for generating nonces + /// \param tx the new transaction for which proofs will be created + /// \return true if proving was successful; false otherwise + auto add_proof(secp256k1_context* ctx, + secp256k1_bppp_generators* gens, + random_source& rng, + full_tx& tx) -> bool; + /// \brief Calculates the unique hash of a full transaction /// /// Returns a cryptographic hash of the inputs concatenated with the @@ -194,10 +322,6 @@ namespace cbdc::transaction { /// \return result of input_from_output(tx, i, tx_id(tx)) auto input_from_output(const full_tx& tx, size_t i) -> std::optional; - - auto uhs_id_from_output(const hash_t& entropy, - uint64_t i, - const output& output) -> hash_t; } #endif // OPENCBDC_TX_SRC_TRANSACTION_TRANSACTION_H_ diff --git a/src/uhs/transaction/validation.cpp b/src/uhs/transaction/validation.cpp index ce6aa18c6..28d719c67 100644 --- a/src/uhs/transaction/validation.cpp +++ b/src/uhs/transaction/validation.cpp @@ -10,15 +10,38 @@ #include #include #include +#include #include #include namespace cbdc::transaction::validation { - static const auto secp_context - = std::unique_ptr( - secp256k1_context_create(SECP256K1_CONTEXT_VERIFY), - &secp256k1_context_destroy); + using secp256k1_context_destroy_type = void (*)(secp256k1_context*); + + std::unique_ptr + secp_context{secp256k1_context_create(SECP256K1_CONTEXT_NONE), + &secp256k1_context_destroy}; + + struct GensDeleter { + explicit GensDeleter(secp256k1_context* ctx) : m_ctx(ctx) {} + + void operator()(secp256k1_bppp_generators* gens) const { + secp256k1_bppp_generators_destroy(m_ctx, gens); + } + + secp256k1_context* m_ctx; + }; + + /// should be set to exactly `floor(log_base(value)) + 1`. + /// + /// We use n_bits = 64, base = 16, so this should always be 24. + static const inline auto generator_count = 16 + 8; + + std::unique_ptr + generators{ + secp256k1_bppp_generators_create(secp_context.get(), + generator_count), + GensDeleter(secp_context.get())}; auto input_error::operator==(const input_error& rhs) const -> bool { return std::tie(m_code, m_data_err, m_idx) @@ -33,6 +56,10 @@ namespace cbdc::transaction::validation { return std::tie(m_code, m_idx) == std::tie(rhs.m_code, rhs.m_idx); } + auto proof_error::operator==(const proof_error& rhs) const -> bool { + return m_code == rhs.m_code; + } + auto check_tx(const cbdc::transaction::full_tx& tx) -> std::optional { const auto structure_err = check_tx_structure(tx); @@ -40,33 +67,28 @@ namespace cbdc::transaction::validation { return structure_err; } - for(size_t idx = 0; idx < tx.m_inputs.size(); idx++) { - const auto& inp = tx.m_inputs[idx]; - const auto input_err = check_input_structure(inp); - if(input_err) { - auto&& [code, data] = input_err.value(); - return tx_error{input_error{code, data, idx}}; + for(size_t idx = 0; idx < tx.m_witness.size(); idx++) { + const auto witness_err = check_witness(tx, idx); + if(witness_err) { + return tx_error{witness_error{witness_err.value(), idx}}; } } - for(size_t idx = 0; idx < tx.m_outputs.size(); idx++) { - const auto& out = tx.m_outputs[idx]; - const auto output_err = check_output_value(out); - if(output_err) { - return tx_error{output_error{output_err.value(), idx}}; - } + std::vector inputs{}; + inputs.reserve(tx.m_inputs.size()); + for(const auto& inp : tx.m_inputs) { + inputs.push_back(inp.m_prevout_data.m_value_commitment); } - const auto in_out_set_error = check_in_out_set(tx); - if(in_out_set_error) { - return in_out_set_error; + const auto outproof_exists_err = check_output_rangeproofs_exist(tx); + if(outproof_exists_err) { + return tx_error{outproof_exists_err.value()}; } - for(size_t idx = 0; idx < tx.m_witness.size(); idx++) { - const auto witness_err = check_witness(tx, idx); - if(witness_err) { - return tx_error{witness_error{witness_err.value(), idx}}; - } + const cbdc::transaction::compact_tx ctx(tx); + const auto proof_error = check_proof(ctx, inputs); + if(proof_error) { + return proof_error; } return std::nullopt; @@ -97,42 +119,6 @@ namespace cbdc::transaction::validation { return std::nullopt; } - auto check_input_structure(const cbdc::transaction::input& inp) - -> std::optional< - std::pair>> { - const auto data_err = check_output_value(inp.m_prevout_data); - if(data_err) { - return {{input_error_code::data_error, data_err}}; - } - - return std::nullopt; - } - - auto check_in_out_set(const cbdc::transaction::full_tx& tx) - -> std::optional { - uint64_t input_total{0}; - for(const auto& inp : tx.m_inputs) { - if(input_total + inp.m_prevout_data.m_value <= input_total) { - return tx_error(tx_error_code::value_overflow); - } - input_total += inp.m_prevout_data.m_value; - } - - uint64_t output_total{0}; - for(const auto& out : tx.m_outputs) { - if(output_total + out.m_value <= output_total) { - return tx_error(tx_error_code::value_overflow); - } - output_total += out.m_value; - } - - if(input_total != output_total) { - return tx_error(tx_error_code::asymmetric_values); - } - - return std::nullopt; - } - // TODO: check input assumptions with flags for whether preconditions have // already been checked. auto check_witness(const cbdc::transaction::full_tx& tx, size_t idx) @@ -232,6 +218,7 @@ namespace cbdc::transaction::validation { if(secp256k1_schnorrsig_verify(secp_context.get(), sig_arr.data(), sighash.data(), + sighash.size(), &pubkey) != 1) { return witness_error_code::invalid_signature; @@ -258,6 +245,16 @@ namespace cbdc::transaction::validation { return std::nullopt; } + auto check_output_rangeproofs_exist(const cbdc::transaction::full_tx& tx) + -> std::optional { + for(const auto& outp : tx.m_outputs) { + if(!outp.m_range.has_value()) { + return proof_error{proof_error_code::missing_rangeproof}; + } + } + return std::nullopt; + } + auto check_witness_count(const cbdc::transaction::full_tx& tx) -> std::optional { if(tx.m_inputs.size() != tx.m_witness.size()) { @@ -285,15 +282,159 @@ namespace cbdc::transaction::validation { return std::nullopt; } - auto check_output_value(const cbdc::transaction::output& out) - -> std::optional { - if(out.m_value < 1) { - return output_error_code::zero_value; + auto check_range(const commitment_t& comm, const rangeproof_t& rng) + -> std::optional { + + auto* ctx = secp_context.get(); + auto maybe_c = deserialize_commitment(ctx, comm); + assert(maybe_c.has_value()); + auto c = maybe_c.value(); + + static constexpr auto scratch_size = 100UL * 1024UL; + secp256k1_scratch_space* scratch + = secp256k1_scratch_space_create(ctx, scratch_size); + + [[maybe_unused]] auto ret + = secp256k1_bppp_rangeproof_verify( + ctx, + scratch, + generators.get(), + secp256k1_generator_h, + rng.data(), + rng.size(), + 64, + 16, + 1, // minimum + &c, + nullptr, // extra commit + 0 // extra commit length + ); + + secp256k1_scratch_space_destroy(ctx, scratch); + + if(ret != 1) { + return proof_error{proof_error_code::out_of_range}; + } + + return std::nullopt; + } + + auto range_batch_add(secp256k1_ecmult_multi_batch& batch, + secp256k1_scratch_space* scratch, + const rangeproof_t& rng, + secp256k1_pedersen_commitment& comm) + -> std::optional { + + [[maybe_unused]] auto ret + = secp256k1_bppp_rangeproof_batch_add( + secp_context.get(), + scratch, + generators.get(), + secp256k1_generator_h, + rng.data(), + rng.size(), + 64, + 16, + 1, + &comm, + &batch + ); + + if(ret != 1) { + return proof_error{proof_error_code::out_of_range}; + } + + return std::nullopt; + } + + auto check_range_batch(secp256k1_ecmult_multi_batch& batch) + -> std::optional { + + auto* ctx = secp_context.get(); + static constexpr auto scratch_size = 1024UL * 1024UL; + secp256k1_scratch_space* scratch + = secp256k1_scratch_space_create(ctx, scratch_size); + + [[maybe_unused]] auto ret = secp256k1_bppp_rangeproof_batch_verify( + ctx, + scratch, + &batch + ); + + if(ret != 1) { + return proof_error{proof_error_code::out_of_range}; + } + + return std::nullopt; + } + + auto check_proof(const compact_tx& tx, + const std::vector& inps) + -> std::optional { + auto* ctx = secp_context.get(); + std::vector in_comms{}; + for(const auto& comm : inps) { + auto maybe_aux = deserialize_commitment(ctx, comm); + if(!maybe_aux.has_value()) { + return proof_error{proof_error_code::invalid_commitment}; + } + auto aux = maybe_aux.value(); + in_comms.push_back(aux); + } + std::vector out_comms{}; + for(const auto& proof : tx.m_outputs) { + auto maybe_aux = deserialize_commitment(ctx, proof.m_value_commitment); + if(!maybe_aux.has_value()) { + return proof_error{proof_error_code::invalid_commitment}; + } + auto aux = maybe_aux.value(); + out_comms.push_back(aux); + + //auto rng = check_range(proof.m_value_commitment, proof.m_range.value()); + //if(rng.has_value()) { + // return rng; + //} + } + + if(!check_commitment_sum(in_comms, out_comms, 0)) { + return proof_error{proof_error_code::wrong_sum}; } return std::nullopt; } + auto check_commitment_sum( + const std::vector& inputs, + const std::vector& outputs, + uint64_t minted) -> bool { + std::vector in_ptrs{}; + in_ptrs.reserve(inputs.size()); + for(const auto& c : inputs) { + in_ptrs.push_back(&c); + } + + std::vector out_ptrs{}; + out_ptrs.reserve(outputs.size()); + for(const auto& c : outputs) { + out_ptrs.push_back(&c); + } + + // if this is a minting transaction, we need to balance the minted + // amount + secp256k1_pedersen_commitment minting_com{}; + if(minted != 0) { + minting_com = commit(secp_context.get(), minted, hash_t{}).value(); + out_ptrs.push_back(&minting_com); + } + + return secp256k1_pedersen_verify_tally(secp_context.get(), + in_ptrs.data(), + in_ptrs.size(), + out_ptrs.data(), + out_ptrs.size()) + == 1; + } + auto get_p2pk_witness_commitment(const pubkey_t& payee) -> hash_t { auto witness_program = to_vector(payee); witness_program.insert(witness_program.begin(), @@ -357,6 +498,29 @@ namespace cbdc::transaction::validation { return ret; } + auto to_string(const cbdc::transaction::validation::proof_error& err) + -> std::string { + switch(err.m_code) { + case cbdc::transaction::validation::proof_error_code :: + invalid_commitment: + return "One or more auxiliary commitments were malformed"; + case cbdc::transaction::validation::proof_error_code :: + invalid_uhs_id: + return "One or more UHS ID commitments were malformed"; + case cbdc::transaction::validation::proof_error_code :: + out_of_range: + return "One or more output values lay outside their proven " + "range"; + case cbdc::transaction::validation::proof_error_code ::wrong_sum: + return "Input values do not equal output values"; + case cbdc::transaction::validation::proof_error_code :: + missing_rangeproof: + return "Output missing required rangeproof"; + default: + return "Unknown error"; + } + } + auto to_string(cbdc::transaction::validation::witness_error_code err) -> std::string { switch(err) { diff --git a/src/uhs/transaction/validation.hpp b/src/uhs/transaction/validation.hpp index 03acf20e2..785ea5fed 100644 --- a/src/uhs/transaction/validation.hpp +++ b/src/uhs/transaction/validation.hpp @@ -55,6 +55,23 @@ namespace cbdc::transaction::validation { uint64_t m_idx{}; }; + /// A proof verification error + enum class proof_error_code : uint8_t { + invalid_commitment, ///< deserializing the auxiliary commitment failed + invalid_uhs_id, ///< deserializing the UHS ID failed + out_of_range, ///< range proof did not verify + wrong_sum, ///< auxiliaries did not sum as-required + missing_rangeproof, ///< rangeproof missing in output + }; + + /// An error that may occur when verifying transaction proof + struct proof_error { + auto operator==(const proof_error& rhs) const -> bool; + + /// The type of proof error + proof_error_code m_code{}; + }; + /// Types of errors that may occur when sentinels validate witness /// commitments enum class witness_error_code : uint8_t { @@ -110,8 +127,11 @@ namespace cbdc::transaction::validation { /// A transaction can fail validation because of an error in the inputs, /// outputs, witnesses, or because the transaction-local invariants /// do not hold. - using tx_error = std:: - variant; + using tx_error = std::variant; /// \brief Runs static validation checks on the given transaction /// @@ -122,10 +142,6 @@ namespace cbdc::transaction::validation { auto check_tx(const transaction::full_tx& tx) -> std::optional; auto check_tx_structure(const transaction::full_tx& tx) -> std::optional; - auto check_input_structure(const transaction::input& inp) -> std::optional< - std::pair>>; - auto check_in_out_set(const transaction::full_tx& tx) - -> std::optional; // TODO: check input assumptions with flags for whether preconditions have // already been checked. auto check_witness(const transaction::full_tx& tx, size_t idx) @@ -144,12 +160,28 @@ namespace cbdc::transaction::validation { -> std::optional; auto check_output_count(const transaction::full_tx& tx) -> std::optional; + auto check_output_rangeproofs_exist(const transaction::full_tx& tx) + -> std::optional; auto check_witness_count(const transaction::full_tx& tx) -> std::optional; auto check_input_set(const transaction::full_tx& tx) -> std::optional; - auto check_output_value(const transaction::output& out) - -> std::optional; + auto check_range(const commitment_t& comm, const rangeproof_t& rng) + -> std::optional; + auto range_batch_add(secp256k1_ecmult_multi_batch& batch, + secp256k1_scratch_space* scratch, + const rangeproof_t& rng, + secp256k1_pedersen_commitment& comm) + -> std::optional; + auto check_range_batch(secp256k1_ecmult_multi_batch& batch) + -> std::optional; + auto check_proof(const compact_tx& tx, + const std::vector& inps) + -> std::optional; + auto check_commitment_sum( + const std::vector& inputs, + const std::vector& outputs, + uint64_t minted) -> bool; auto get_p2pk_witness_commitment(const pubkey_t& payee) -> hash_t; auto to_string(const tx_error& err) -> std::string; diff --git a/src/uhs/transaction/wallet.cpp b/src/uhs/transaction/wallet.cpp index 059bd686f..853565a61 100644 --- a/src/uhs/transaction/wallet.cpp +++ b/src/uhs/transaction/wallet.cpp @@ -14,7 +14,16 @@ #include namespace cbdc { - transaction::wallet::wallet() { + transaction::wallet::wallet() : m_log(nullptr) { + init(); + } + + transaction::wallet::wallet(std::shared_ptr log) + : m_log(std::move(log)) { + init(); + } + + void transaction::wallet::init() { auto seed = std::chrono::high_resolution_clock::now() .time_since_epoch() .count(); @@ -35,11 +44,36 @@ namespace cbdc { out.m_witness_program_commitment = transaction::validation::get_p2pk_witness_commitment(pubkey); - out.m_value = output_val; - ret.m_outputs.push_back(out); } + ret.m_out_spend_data + = std::vector(n_outputs, {{}, output_val}); + + [[maybe_unused]] auto res = transaction::add_proof(m_secp.get(), + m_generators.get(), + *m_random_source, + ret); + + assert(res); + + { + auto id = transaction::tx_id(ret); + std::unique_lock ul(m_utxos_mut); + for(size_t i = 0; i < ret.m_outputs.size(); ++i) { + transaction::output put = ret.m_outputs[i]; + put.m_range.reset(); // remove range proofs for inputs + transaction::out_point point{id, i}; + transaction::input inp{point, + put, + ret.m_out_spend_data.value()[i]}; + const auto [_, inserted] = m_utxos_set.insert({point, inp}); + if(inserted) { + m_spend_queue.push_back(inp); + } + } + } + return ret; } @@ -55,8 +89,10 @@ namespace cbdc { auto& ret = maybe_tx.value().first; auto total_amount = maybe_tx.value().second; + std::vector out_spend_data{}; + transaction::output destination_out; - destination_out.m_value = amount; + out_spend_data.push_back(transaction::spend_data{{}, amount}); destination_out.m_witness_program_commitment = transaction::validation::get_p2pk_witness_commitment(payee); @@ -65,11 +101,23 @@ namespace cbdc { if(total_amount > amount) { // Add the change output if we need to transaction::output change_out; - change_out.m_value = total_amount - amount; const auto pubkey = generate_key(); change_out.m_witness_program_commitment = transaction::validation::get_p2pk_witness_commitment(pubkey); ret.m_outputs.push_back(change_out); + transaction::spend_data sp{{}, total_amount - amount}; + out_spend_data.push_back(sp); + } + + ret.m_out_spend_data = out_spend_data; + + auto res = transaction::add_proof(m_secp.get(), + m_generators.get(), + *m_random_source, + ret); + + if(!res) { + return std::nullopt; } if(sign_tx) { @@ -79,31 +127,143 @@ namespace cbdc { return ret; } + auto + transaction::wallet::create_seeded_transaction(size_t seed_idx, + const commitment_t& comm, + const rangeproof_t& range) + -> std::optional { + if(m_seed_from == m_seed_to) { + return std::nullopt; + } + + transaction::full_tx tx; + tx.m_inputs.resize(1); + tx.m_outputs.resize(1); + + transaction::input inp{}; + inp.m_prevout.m_tx_id = {0}; + inp.m_prevout.m_index = seed_idx; + + auto spend = transaction::spend_data{{}, m_seed_value}; + + inp.m_prevout_data.m_witness_program_commitment = {0}; + inp.m_prevout_data.m_value_commitment = comm; + inp.m_prevout_data.m_range.reset(); + inp.m_prevout_data.m_id + = calculate_uhs_id(inp.m_prevout, inp.m_prevout_data, comm); + inp.m_spend_data = {spend}; + + tx.m_inputs[0] = inp; + + transaction::output outp{}; + outp.m_witness_program_commitment = m_seed_witness_commitment; + outp.m_value_commitment = comm; + outp.m_range = range; + tx.m_outputs[0] = outp; + + // Note the delicate ordering: + // 1) the incomplete output object (missing member .m_id) must first be + // set into tx + // 2) tx_id is calculated (which now has the required output) + // 3) the output object can now become complete: uhs_id is calculated + // and is set as member .m_id + const auto outpoint = transaction::out_point(transaction::tx_id(tx), 0); + tx.m_outputs[0].m_id = calculate_uhs_id(outpoint, tx.m_outputs[0], comm); + + std::vector out_spend_data{}; + out_spend_data.push_back(spend); + tx.m_out_spend_data = out_spend_data; + + return tx; + } + auto transaction::wallet::create_seeded_transaction(size_t seed_idx) -> std::optional { if(m_seed_from == m_seed_to) { return std::nullopt; } + + if(m_seed_range_proof.has_value() && m_seed_value_commitment.has_value()) { + return create_seeded_transaction(seed_idx, m_seed_value_commitment.value(), m_seed_range_proof.value()); + } + transaction::full_tx tx; tx.m_inputs.resize(1); tx.m_outputs.resize(1); - tx.m_inputs[0].m_prevout.m_tx_id = {0}; - tx.m_inputs[0].m_prevout_data.m_value = m_seed_value; - tx.m_inputs[0].m_prevout_data.m_witness_program_commitment = {0}; + + transaction::input inp{}; + inp.m_prevout.m_tx_id = {0}; + inp.m_prevout.m_index = seed_idx; + + inp.m_prevout_data.m_witness_program_commitment = {0}; + + std::vector in_spend_data{}; + in_spend_data.push_back(transaction::spend_data{{}, m_seed_value}); + + auto aux = transaction::roll_auxiliaries(m_secp.get(), + *m_random_source, + {}, + in_spend_data); + + inp.m_prevout_data.m_value_commitment + = serialize_commitment(m_secp.get(), aux.front()); + inp.m_prevout_data.m_id + = transaction::calculate_uhs_id(inp.m_prevout, + inp.m_prevout_data, + inp.m_prevout_data.m_value_commitment); + + inp.m_spend_data = in_spend_data.front(); + tx.m_inputs[0] = inp; + tx.m_outputs[0].m_witness_program_commitment = m_seed_witness_commitment; - tx.m_outputs[0].m_value = m_seed_value; - tx.m_inputs[0].m_prevout.m_index = seed_idx; + + std::vector out_spend_data{}; + out_spend_data.push_back(transaction::spend_data{{}, m_seed_value}); + tx.m_out_spend_data = out_spend_data; + auto res = transaction::add_proof(m_secp.get(), + m_generators.get(), + *m_random_source, + tx); + + if(!res) { + return std::nullopt; + } + + if(!m_seed_range_proof.has_value() || !m_seed_value_commitment.has_value()) { + m_seed_range_proof = tx.m_outputs[0].m_range; + m_seed_value_commitment = tx.m_outputs[0].m_value_commitment; + } + return tx; } auto transaction::wallet::create_seeded_input(size_t seed_idx) -> std::optional { - if(auto tx = create_seeded_transaction(seed_idx)) { - const auto& tx_id = transaction::tx_id(tx.value()); - return transaction::input_from_output(tx.value(), 0, tx_id); + if(m_seed_from == m_seed_to) { + return std::nullopt; + } + + auto maybe_tx = create_seeded_transaction(seed_idx); + if(!maybe_tx.has_value()) { + return std::nullopt; + } + auto tx = maybe_tx.value(); + + if(!tx.m_out_spend_data.has_value()) { + return std::nullopt; + } + + auto maybe_inp = transaction::input_from_output(tx, 0); + if(!maybe_inp.has_value()) { + return std::nullopt; } - return std::nullopt; + + auto inp = maybe_inp.value(); + + inp.m_spend_data = tx.m_out_spend_data.value().front(); + + return inp; } auto transaction::wallet::export_send_inputs( @@ -114,8 +274,9 @@ namespace cbdc { auto ret = std::vector(); for(uint32_t i = 0; i < send_tx.m_outputs.size(); i++) { if(send_tx.m_outputs[i].m_witness_program_commitment == wit_comm) { - ret.push_back( - transaction::input_from_output(send_tx, i).value()); + auto inp = transaction::input_from_output(send_tx, i).value(); + inp.m_spend_data = send_tx.m_out_spend_data.value()[i]; + ret.push_back(inp); } } return ret; @@ -155,12 +316,37 @@ namespace cbdc { return ret; } + auto + transaction::wallet::spending_keys(const transaction::full_tx& tx) const + -> std::optional>> { + std::vector> keys{}; + keys.reserve(tx.m_inputs.size()); + for(const auto& inp : tx.m_inputs) { + const auto& wit_commit + = inp.m_prevout_data.m_witness_program_commitment; + + { + std::shared_lock sl(m_keys_mut); + const auto wit_prog = m_witness_programs.find(wit_commit); + if(wit_prog != m_witness_programs.end()) { + keys.emplace_back(m_keys.at(wit_prog->second), + wit_prog->second); + } else { + return std::nullopt; + } + } + } + return keys; + } + void transaction::wallet::sign(transaction::full_tx& tx) const { // TODO: other sighash types besides SIGHASH_ALL? const auto sighash = transaction::tx_id(tx); tx.m_witness.resize(tx.m_inputs.size()); - for(size_t i = 0; i < tx.m_inputs.size(); i++) { + if(m_log) { + m_log->info("Attempting to sign input", i); + } const auto& wit_commit = tx.m_inputs[i].m_prevout_data.m_witness_program_commitment; @@ -178,6 +364,10 @@ namespace cbdc { } if(key_ours) { + if(m_log) { + m_log->info("Input", i, " is ours - signing"); + } + auto& sig = tx.m_witness[i]; sig.resize(transaction::validation::p2pk_witness_len); sig[0] = std::byte( @@ -197,17 +387,20 @@ namespace cbdc { std::array sig_arr{}; [[maybe_unused]] const auto sign_ret - = secp256k1_schnorrsig_sign(m_secp.get(), + = secp256k1_schnorrsig_sign32(m_secp.get(), sig_arr.data(), sighash.data(), &keypair, - nullptr, nullptr); std::memcpy( &sig[transaction::validation::p2pk_witness_prog_len], sig_arr.data(), sizeof(sig_arr)); assert(sign_ret == 1); + } else { + if(m_log) { + m_log->info("Input", i, " is not ours - not signing"); + } } } } @@ -217,19 +410,17 @@ namespace cbdc { const std::vector& debits) { std::unique_lock lu(m_utxos_mut); for(const auto& inp : credits) { - const auto added = m_utxos_set.insert(inp); - if(added.second) { - m_balance += inp.m_prevout_data.m_value; + const auto [_, inserted] + = m_utxos_set.insert({inp.m_prevout, inp}); + if(inserted) { m_spend_queue.push_back(inp); } } for(const auto& inp : debits) { - const auto erased = m_utxos_set.erase(inp) > 0; - if(erased) { - m_balance -= inp.m_prevout_data.m_value; - } + m_utxos_set.erase(inp.m_prevout); } + assert(m_spend_queue.size() == m_utxos_set.size()); } @@ -277,8 +468,10 @@ namespace cbdc { const auto& out = tx.m_outputs[i]; if(m_witness_programs.find(out.m_witness_program_commitment) != m_witness_programs.end()) { - new_utxos.push_back( - transaction::input_from_output(tx, i, tx_id).value()); + auto inp + = transaction::input_from_output(tx, i, tx_id).value(); + inp.m_spend_data = tx.m_out_spend_data.value()[i]; + new_utxos.push_back(inp); } } } @@ -288,7 +481,14 @@ namespace cbdc { auto transaction::wallet::balance() const -> uint64_t { std::shared_lock lg(m_utxos_mut); // TODO: handle overflow - auto balance = m_balance; + uint64_t balance{0}; + for(const auto& [k, v] : m_utxos_set) { + if(m_witness_programs.find( + v.m_prevout_data.m_witness_program_commitment) + != m_witness_programs.end()) { + balance += v.m_spend_data.value().m_value; + } + } if(m_seed_from != m_seed_to) { balance += (m_seed_to - m_seed_from) * m_seed_value; } @@ -352,11 +552,9 @@ namespace cbdc { m_utxos_set.clear(); m_spend_queue.clear(); - m_balance = 0; deser >> m_utxos_set; - for(const auto& utxo : m_utxos_set) { - m_balance += utxo.m_prevout_data.m_value; + for(const auto& [prevout, utxo] : m_utxos_set) { m_spend_queue.push_back(utxo); } } @@ -387,11 +585,12 @@ namespace cbdc { size_t seeded_inputs = 0; while(m_seed_from != m_seed_to && ret.m_inputs.size() < input_count) { - auto seed_utxo = create_seeded_input(m_seed_from); - if(!seed_utxo) { + std::optional seed_inp{}; + seed_inp = create_seeded_input(m_seed_from); + if(!seed_inp) { break; } - ret.m_inputs.push_back(seed_utxo.value()); + ret.m_inputs.push_back(seed_inp.value()); ret.m_witness.emplace_back(sig_len, std::byte(0)); total_amount += m_seed_value; m_seed_from++; @@ -403,7 +602,7 @@ namespace cbdc { && (ret.m_inputs.size() < input_count); utxo++) { ret.m_inputs.push_back(*utxo); - total_amount += utxo->m_prevout_data.m_value; + total_amount += utxo->m_spend_data.value().m_value; } output_val = total_amount / output_count; @@ -416,29 +615,61 @@ namespace cbdc { for(size_t i = seeded_inputs; i < ret.m_inputs.size(); i++) { auto& inp = ret.m_inputs[i]; - m_balance -= inp.m_prevout_data.m_value; - m_utxos_set.erase(inp); + m_utxos_set.erase(inp.m_prevout); m_spend_queue.pop_front(); } } + std::vector out_spend_data{}; + auto wit_comm = transaction::validation::get_p2pk_witness_commitment(payee); ret.m_outputs.reserve(output_count); for(size_t i{0}; i < output_count; i++) { transaction::output send_out; + uint64_t val{}; if(i == output_count - 1) { - send_out.m_value = total_amount; + val = total_amount; } else { - send_out.m_value = output_val; + val = output_val; } - total_amount -= send_out.m_value; + total_amount -= val; send_out.m_witness_program_commitment = wit_comm; + if(m_seed_range_proof.has_value() && m_seed_value_commitment.has_value()) { + send_out.m_range = m_seed_range_proof.value(); + send_out.m_value_commitment = m_seed_value_commitment.value(); + } ret.m_outputs.push_back(send_out); + out_spend_data.push_back(transaction::spend_data{{}, val}); } assert(total_amount == 0); + ret.m_out_spend_data = out_spend_data; + + if(!m_seed_range_proof.has_value() || !m_seed_value_commitment.has_value()) { + auto res = transaction::add_proof(m_secp.get(), + m_generators.get(), + *m_random_source, + ret); + + if(!res) { + return std::nullopt; + } + } + else { + // Note: uhs_id must be set explicitly (in normal course of + // transaction creation, it is set via transaction::add_proof() --> + // transaction::prove_output()) + const auto txid = transaction::tx_id(ret); + for(size_t i{0}; i < output_count; i++) { + ret.m_outputs[i].m_id + = calculate_uhs_id(transaction::out_point(txid, i), + ret.m_outputs[i], + ret.m_outputs[i].m_value_commitment); + } + } + if(sign_tx) { sign(ret); } @@ -465,23 +696,37 @@ namespace cbdc { auto& ret = maybe_tx.value().first; auto total_amount = maybe_tx.value().second; + std::vector out_spend_data{}; + if(total_amount > amount) { // Add the change output if we need to transaction::output change_out; - change_out.m_value = total_amount - amount; const auto pubkey = generate_key(); change_out.m_witness_program_commitment = transaction::validation::get_p2pk_witness_commitment(pubkey); ret.m_outputs.push_back(change_out); + transaction::spend_data sp{{}, total_amount - amount}; + out_spend_data.push_back(sp); } transaction::output destination_out; - destination_out.m_value = value; destination_out.m_witness_program_commitment = transaction::validation::get_p2pk_witness_commitment(payee); for(size_t i{0}; i < output_count; i++) { ret.m_outputs.push_back(destination_out); + out_spend_data.push_back(transaction::spend_data{{}, value}); + } + + ret.m_out_spend_data = out_spend_data; + + auto res = transaction::add_proof(m_secp.get(), + m_generators.get(), + *m_random_source, + ret); + + if(!res) { + return std::nullopt; } if(sign_tx) { @@ -499,11 +744,11 @@ namespace cbdc { std::unique_lock ul(m_utxos_mut); size_t seeded_inputs = 0; while(m_seed_from != m_seed_to && total_amount < amount) { - auto seed_utxo = create_seeded_input(m_seed_from); - if(!seed_utxo) { + auto seed_inp = create_seeded_input(m_seed_from); + if(!seed_inp) { break; } - ret.m_inputs.push_back(seed_utxo.value()); + ret.m_inputs.push_back(seed_inp.value()); ret.m_witness.emplace_back(sig_len, std::byte(0)); total_amount += m_seed_value; m_seed_from++; @@ -514,7 +759,7 @@ namespace cbdc { while((total_amount < amount) && (utxo != m_spend_queue.end())) { ret.m_inputs.push_back(*utxo); ret.m_witness.emplace_back(sig_len, std::byte(0)); - total_amount += utxo->m_prevout_data.m_value; + total_amount += utxo->m_spend_data.value().m_value; std::advance(utxo, 1); } @@ -525,11 +770,11 @@ namespace cbdc { for(size_t i = seeded_inputs; i < ret.m_inputs.size(); i++) { const auto del_utxo = m_spend_queue.begin(); - m_balance -= del_utxo->m_prevout_data.m_value; - m_utxos_set.erase(*del_utxo); + m_utxos_set.erase(del_utxo->m_prevout); m_spend_queue.pop_front(); } } + return {{ret, total_amount}}; } diff --git a/src/uhs/transaction/wallet.hpp b/src/uhs/transaction/wallet.hpp index 4414f5848..ecb7cc94d 100644 --- a/src/uhs/transaction/wallet.hpp +++ b/src/uhs/transaction/wallet.hpp @@ -36,6 +36,14 @@ namespace cbdc::transaction { /// Initializes the randomization engine for key shuffling. wallet(); + /// \brief Constructor. + /// + /// \param log (optional) logger to write debugging to. + explicit wallet(std::shared_ptr log); + + /// Initializes the randomization engine for key shuffling. + void init(); + /// \brief Mints new spendable outputs. /// /// Generates the specified number spendable outputs, each with the @@ -168,6 +176,17 @@ namespace cbdc::transaction { /// \param tx the transaction to confirm. void confirm_transaction(const full_tx& tx); + /// \brief Retrieves the spending-keypairs for a transaction + /// + /// Returns the keypairs (one per input, in-order) needed to authorize + /// spending the transaction's inputs. + /// + /// \param tx the transaction to fetch spending keys for + /// \return the list of keypairs (or std::nullopt if any output is + /// unspendable) + auto spending_keys(const full_tx& tx) const + -> std::optional>>; + /// Signs each of the transaction's inputs using Schnorr signatures. /// \param tx the transaction whose inputs to sign. void sign(full_tx& tx) const; @@ -206,30 +225,31 @@ namespace cbdc::transaction { auto create_seeded_transaction(size_t seed_idx) -> std::optional; + // todo: document overload + auto create_seeded_transaction(size_t seed_idx, + const commitment_t& comm, + const rangeproof_t& range) + -> std::optional; + /// Given a set of credit inputs, add the UTXOs and update the wallet's /// balance. /// \param credits the inputs to add to the wallet's set of UTXOs. void confirm_inputs(const std::vector& credits); private: - struct cmp_input { - auto operator()(const input& lhs, const input& rhs) const -> bool { - // First sort by tx hash then output index - return std::tie(lhs.m_prevout.m_tx_id, lhs.m_prevout.m_index) - < std::tie(rhs.m_prevout.m_tx_id, rhs.m_prevout.m_index); - } - }; - /// Locks access to m_utxos and m_balance (the sum of the UTXOs). /// \warning Do not lock simultaneously with m_keys_mut. mutable std::shared_mutex m_utxos_mut; - uint64_t m_balance{0}; + /// Stores the current set of spendable inputs. - std::set m_utxos_set; + std::map m_utxos_set; + /// Stores the blinds associated with a spendable input size_t m_seed_from{0}; size_t m_seed_to{0}; uint32_t m_seed_value{0}; hash_t m_seed_witness_commitment{0}; + std::optional m_seed_range_proof{}; + std::optional m_seed_value_commitment{}; /// Queue of spendable inputs, oldest first. std::list m_spend_queue; @@ -243,6 +263,7 @@ namespace cbdc::transaction { m_keys; std::vector m_pubkeys; std::default_random_engine m_shuffle; + std::shared_ptr m_log; // TODO: currently this map grows unbounded, we need to garbage // collect it @@ -254,12 +275,36 @@ namespace cbdc::transaction { /// \param seed_idx the index in the seed set to generate the input /// for. /// \returns the generated input to use in a transaction. - auto create_seeded_input(size_t seed_idx) -> std::optional; + auto create_seeded_input(size_t seed_idx) + -> std::optional; + + struct GensDeleter { + explicit GensDeleter(secp256k1_context* ctx) : m_ctx(ctx) {} + + void operator()(secp256k1_bppp_generators* gens) const { + secp256k1_bppp_generators_destroy(m_ctx, gens); + } + + secp256k1_context* m_ctx; + }; + + /// should be set to exactly `max(n_bits/log2(base), base) + 7` + /// + /// We use n_bits = 64, base = 16, so this should always be 24. + static const inline auto generator_count = 16 + 8; + + std::unique_ptr + m_generators{ + secp256k1_bppp_generators_create(m_secp.get(), + generator_count), + GensDeleter(m_secp.get())}; + + using secp256k1_context_destroy_type = void (*)(secp256k1_context*); static const inline auto m_secp = std::unique_ptr( - secp256k1_context_create(SECP256K1_CONTEXT_SIGN), + secp256k1_context_destroy_type>( + secp256k1_context_create(SECP256K1_CONTEXT_NONE), &secp256k1_context_destroy); static const inline auto m_random_source diff --git a/src/uhs/twophase/coordinator/distributed_tx.cpp b/src/uhs/twophase/coordinator/distributed_tx.cpp index 4aabaa6bf..4dc6973e3 100644 --- a/src/uhs/twophase/coordinator/distributed_tx.cpp +++ b/src/uhs/twophase/coordinator/distributed_tx.cpp @@ -149,6 +149,11 @@ namespace cbdc::coordinator { const auto& shard = m_shards[i]; auto stx = locking_shard::tx(); stx.m_tx = tx; + auto duration + = std::chrono::system_clock::now().time_since_epoch(); + auto secs + = std::chrono::duration_cast(duration); + stx.m_epoch = static_cast(secs.count()); bool active{false}; if(shard->hash_in_shard_range(tx.m_id)) { active = true; @@ -160,8 +165,9 @@ namespace cbdc::coordinator { } } if(!active) { - for(const auto& out : tx.m_uhs_outputs) { - if(shard->hash_in_shard_range(out)) { + for(const auto& out : tx.m_outputs) { + auto id = transaction::calculate_uhs_id(out); + if(shard->hash_in_shard_range(id)) { active = true; break; } diff --git a/src/uhs/twophase/locking_shard/controller.cpp b/src/uhs/twophase/locking_shard/controller.cpp index 8b961f78d..28a9c94f5 100644 --- a/src/uhs/twophase/locking_shard/controller.cpp +++ b/src/uhs/twophase/locking_shard/controller.cpp @@ -10,6 +10,7 @@ #include "status_client.hpp" #include "util/rpc/tcp_server.hpp" #include "util/serialization/format.hpp" +#include "util/common/commitment.hpp" #include @@ -29,6 +30,13 @@ namespace cbdc::locking_shard { + "_" + std::to_string(m_shard_id) : "") {} + controller::~controller() { + m_running = false; + if(m_audit_thread.joinable()) { + m_audit_thread.join(); + } + } + auto controller::init() -> bool { if(!m_logger) { std::cerr @@ -37,6 +45,13 @@ namespace cbdc::locking_shard { return false; } + m_audit_log.open(m_opts.m_shard_audit_logs[m_shard_id], + std::ios::app | std::ios::out); + if(!m_audit_log.good()) { + m_logger->error("Failed to open audit log"); + return false; + } + auto params = nuraft::raft_params(); params.election_timeout_lower_bound_ = static_cast(m_opts.m_election_timeout_lower); @@ -61,6 +76,10 @@ namespace cbdc::locking_shard { m_shard = m_state_machine->get_shard_instance(); + m_audit_thread = std::thread([this]() { + audit(); + }); + if(m_shard_id > (m_opts.m_locking_shard_raft_endpoints.size() - 1)) { m_logger->error("The shard ID is out of range " "of the m_locking_shard_raft_endpoints vector."); @@ -130,4 +149,43 @@ namespace cbdc::locking_shard { } return nuraft::cb_func::ReturnCode::Ok; } + + void controller::audit() { + while(m_running) { + constexpr auto audit_wait_interval = std::chrono::seconds(1); + std::this_thread::sleep_for(audit_wait_interval); + if(!m_running) { + break; + } + auto highest_epoch = m_shard->highest_epoch(); + if(highest_epoch - m_last_audit_epoch + > m_opts.m_shard_audit_interval) { + auto audit_epoch = highest_epoch; + if(m_opts.m_shard_audit_interval > 0) { + audit_epoch + = (highest_epoch - m_opts.m_shard_audit_interval) + - (highest_epoch % m_opts.m_shard_audit_interval); + } + if(audit_epoch > highest_epoch + || audit_epoch <= m_last_audit_epoch) { + continue; + } + + m_logger->info("Running Audit for", audit_epoch); + auto maybe_commit = m_shard->get_summary(audit_epoch); + if(!maybe_commit.has_value()) { + m_logger->error("Error running audit at epoch", + audit_epoch); + } else { + m_audit_log << audit_epoch << " " << to_string(maybe_commit.value()) + << std::endl; + m_logger->info("Audit completed for", audit_epoch); + + m_last_audit_epoch = audit_epoch; + + m_shard->prune(audit_epoch); + } + } + } + } } diff --git a/src/uhs/twophase/locking_shard/controller.hpp b/src/uhs/twophase/locking_shard/controller.hpp index c23850a30..d22e7191c 100644 --- a/src/uhs/twophase/locking_shard/controller.hpp +++ b/src/uhs/twophase/locking_shard/controller.hpp @@ -13,6 +13,7 @@ #include "util/raft/node.hpp" #include "util/raft/rpc_server.hpp" #include "util/rpc/tcp_server.hpp" +#include "util/serialization/format.hpp" namespace cbdc::locking_shard { /// Manages a replicated locking shard using Raft. @@ -27,7 +28,7 @@ namespace cbdc::locking_shard { size_t node_id, config::options opts, std::shared_ptr logger); - ~controller() = default; + ~controller(); controller() = delete; controller(const controller&) = delete; @@ -47,6 +48,8 @@ namespace cbdc::locking_shard { nuraft::cb_func::Param* param) -> nuraft::cb_func::ReturnCode; + void audit(); + config::options m_opts; std::shared_ptr m_logger; size_t m_shard_id; @@ -58,6 +61,11 @@ namespace cbdc::locking_shard { std::shared_ptr m_raft_serv; std::unique_ptr m_status_server; std::unique_ptr> m_server; + + std::atomic_bool m_running{true}; + uint64_t m_last_audit_epoch{}; + std::ofstream m_audit_log; + std::thread m_audit_thread; }; } diff --git a/src/uhs/twophase/locking_shard/format.cpp b/src/uhs/twophase/locking_shard/format.cpp index 1f2646c7d..fecc6f0fd 100644 --- a/src/uhs/twophase/locking_shard/format.cpp +++ b/src/uhs/twophase/locking_shard/format.cpp @@ -11,11 +11,11 @@ namespace cbdc { auto operator<<(serializer& packet, const locking_shard::tx& tx) -> serializer& { - return packet << tx.m_tx; + return packet << tx.m_tx << tx.m_epoch; } auto operator>>(serializer& packet, locking_shard::tx& tx) -> serializer& { - return packet >> tx.m_tx; + return packet >> tx.m_tx >> tx.m_epoch; } auto operator<<(serializer& packet, const locking_shard::rpc::request& p) @@ -49,4 +49,18 @@ namespace cbdc { locking_shard::rpc::uhs_status_request& p) -> serializer& { return packet >> p.m_uhs_id; } + + auto operator<<(serializer& ser, + const locking_shard::locking_shard::uhs_element& p) + -> serializer& { + return ser << p.m_out << p.m_creation_epoch + << p.m_deletion_epoch; + } + + auto operator>>(serializer& deser, + locking_shard::locking_shard::uhs_element& p) + -> serializer& { + return deser >> p.m_out >> p.m_creation_epoch + >> p.m_deletion_epoch; + } } diff --git a/src/uhs/twophase/locking_shard/format.hpp b/src/uhs/twophase/locking_shard/format.hpp index 303581209..ba5eaf296 100644 --- a/src/uhs/twophase/locking_shard/format.hpp +++ b/src/uhs/twophase/locking_shard/format.hpp @@ -30,6 +30,14 @@ namespace cbdc { -> serializer&; auto operator>>(serializer& packet, locking_shard::rpc::uhs_status_request& p) -> serializer&; + + auto operator<<(serializer& ser, + const locking_shard::locking_shard::uhs_element& p) + -> serializer&; + + auto operator>>(serializer& deser, + locking_shard::locking_shard::uhs_element& p) + -> serializer&; } #endif // OPENCBDC_TX_SRC_LOCKING_SHARD_MESSAGES_H_ diff --git a/src/uhs/twophase/locking_shard/interface.hpp b/src/uhs/twophase/locking_shard/interface.hpp index 095d25421..ddf3fd08a 100644 --- a/src/uhs/twophase/locking_shard/interface.hpp +++ b/src/uhs/twophase/locking_shard/interface.hpp @@ -19,6 +19,8 @@ namespace cbdc::locking_shard { /// Compact TX. transaction::compact_tx m_tx; + uint64_t m_epoch{}; + auto operator==(const tx& rhs) const -> bool; }; diff --git a/src/uhs/twophase/locking_shard/locking_shard.cpp b/src/uhs/twophase/locking_shard/locking_shard.cpp index 92a325f9c..5bab5ad49 100644 --- a/src/uhs/twophase/locking_shard/locking_shard.cpp +++ b/src/uhs/twophase/locking_shard/locking_shard.cpp @@ -6,7 +6,7 @@ #include "locking_shard.hpp" #include "messages.hpp" -#include "uhs/transaction/validation.hpp" +#include "format.hpp" #include "util/common/config.hpp" #include "util/serialization/format.hpp" #include "util/serialization/istream_serializer.hpp" @@ -31,18 +31,13 @@ namespace cbdc::locking_shard { m_logger(std::move(logger)), m_completed_txs(completed_txs_cache_size), m_opts(std::move(opts)) { - m_uhs.max_load_factor(std::numeric_limits::max()); m_applied_dtxs.max_load_factor(std::numeric_limits::max()); m_prepared_dtxs.max_load_factor(std::numeric_limits::max()); - m_locked.max_load_factor(std::numeric_limits::max()); static constexpr auto dtx_buckets = 100000; m_applied_dtxs.rehash(dtx_buckets); m_prepared_dtxs.rehash(dtx_buckets); - static constexpr auto locked_buckets = 10000000; - m_locked.rehash(locked_buckets); - if(!preseed_file.empty()) { m_logger->info("Reading preseed file into memory"); if(!read_preseed_file(preseed_file)) { @@ -64,12 +59,29 @@ namespace cbdc::locking_shard { } in.seekg(0, std::ios::beg); auto deser = istream_serializer(in); - m_uhs.clear(); - static constexpr auto uhs_size_factor = 2; - auto bucket_count = static_cast(sz / cbdc::hash_size - * uhs_size_factor); - m_uhs.rehash(bucket_count); - deser >> m_uhs; + auto uhs = std::map(); + deser >> uhs; + m_uhs = std::move(uhs); + + std::vector tmp {}; + tmp.push_back({{}, m_opts.m_seed_value}); + + auto comm = transaction::roll_auxiliaries(m_secp.get(), + *m_random_source, + {}, + tmp); + if ( comm.empty() ) { + return false; + } + + m_seed_rangeproof = transaction::prove( + m_secp.get(), + m_generators.get(), + *m_random_source, + tmp[0], + &comm[0] + ); + return true; } return false; @@ -93,6 +105,7 @@ namespace cbdc::locking_shard { for(auto&& tx : txs) { auto success = check_and_lock_tx(tx); ret.push_back(success); + m_highest_epoch = std::max(m_highest_epoch, tx.m_epoch); } auto p = prepared_dtx(); p.m_results = ret; @@ -123,9 +136,10 @@ namespace cbdc::locking_shard { if(success) { for(const auto& uhs_id : t.m_tx.m_inputs) { if(hash_in_shard_range(uhs_id)) { - auto n = m_uhs.extract(uhs_id); - assert(!n.empty()); - m_locked.emplace(uhs_id); + auto it = m_uhs.find(uhs_id); + assert(it != m_locked.end()); + m_locked.emplace(uhs_id, it->second); + m_uhs.erase(uhs_id); } } } @@ -157,23 +171,7 @@ namespace cbdc::locking_shard { } for(size_t i{0}; i < dtx.size(); i++) { auto&& tx = dtx[i]; - if(hash_in_shard_range(tx.m_tx.m_id)) { - m_completed_txs.add(tx.m_tx.m_id); - } - - for(auto&& uhs_id : tx.m_tx.m_uhs_outputs) { - if(hash_in_shard_range(uhs_id) && complete_txs[i]) { - m_uhs.emplace(uhs_id); - } - } - for(auto&& uhs_id : tx.m_tx.m_inputs) { - if(hash_in_shard_range(uhs_id)) { - auto was_locked = m_locked.erase(uhs_id); - if(!complete_txs[i] && (was_locked != 0U)) { - m_uhs.emplace(uhs_id); - } - } - } + apply_tx(tx, complete_txs[i]); } m_prepared_dtxs.erase(dtx_id); @@ -196,4 +194,178 @@ namespace cbdc::locking_shard { -> std::optional { return m_completed_txs.contains(tx_id); } + + void locking_shard::apply_tx(const tx& t, bool complete) { + if(hash_in_shard_range(t.m_tx.m_id)) { + m_completed_txs.add(t.m_tx.m_id); + } + + auto epoch = t.m_epoch; + + for(auto&& proof : t.m_tx.m_outputs) { + auto uhs_id = transaction::calculate_uhs_id(proof); + if(!(hash_in_shard_range(uhs_id) && complete)) { + continue; + } + + uhs_element el{proof, epoch, std::nullopt}; + m_uhs.emplace(uhs_id, el); + } + + for(auto&& uhs_id : t.m_tx.m_inputs) { + if(hash_in_shard_range(uhs_id)) { + auto it = m_locked.find(uhs_id); + if(it == m_locked.end()) { + continue; + } + auto elem = it->second; + if(!complete) { + m_uhs.emplace(uhs_id, elem); + } else { + elem.m_deletion_epoch = t.m_epoch; + m_spent.emplace(uhs_id, elem); + } + m_locked.erase(uhs_id); + } + } + } + + auto locking_shard::get_summary(uint64_t epoch) + -> std::optional { + + { + std::unique_lock l(m_mut); + m_uhs.snapshot(); + m_locked.snapshot(); + m_spent.snapshot(); + } + + std::atomic_bool failed = false; + + static constexpr auto scratch_size = 4096UL * 1024UL; + [[maybe_unused]] secp256k1_scratch_space* scratch + = secp256k1_scratch_space_create(m_secp.get(), scratch_size); + + // SAM START HERE: replace threading with batching + static constexpr size_t threshold = 100000; + size_t cursor = 0; + //std::vector>> pool{}; + std::vector comms{}; + auto* range_batch = secp256k1_bppp_rangeproof_batch_create(m_secp.get(), 34 * (threshold + 1)); + auto summarize + = [&](const snapshot_map& m) { + for(const auto& [id, elem] : m) { + if(failed) { + break; + } + if(elem.m_creation_epoch <= epoch + && (!elem.m_deletion_epoch.has_value() + || (elem.m_deletion_epoch.value() > epoch))) { + + auto uhs_id + = transaction::calculate_uhs_id(elem.m_out); + if(uhs_id != id) { + failed = true; + } + auto comm = elem.m_out.m_value_commitment; + auto c = deserialize_commitment(m_secp.get(), comm).value(); + auto r = transaction::validation::range_batch_add( + *range_batch, + scratch, + elem.m_out.m_range, + c + ); + if(!r.has_value()) { + ++cursor; + } + comms.push_back(comm); + } + if(cursor >= threshold) { + failed = transaction::validation::check_range_batch(*range_batch).has_value(); + [[maybe_unused]] auto res = secp256k1_bppp_rangeproof_batch_clear(m_secp.get(), range_batch); + cursor = 0; + } +// return comm; +// auto f = std::async(std::launch::async, +// [&]() -> std::optional { +// auto uhs_id +// = transaction::calculate_uhs_id(elem.m_out); +// if(uhs_id != id) { +// failed = true; +// return std::nullopt; +// } +// +// auto comm = elem.m_out.m_value_commitment; +// auto res = transaction::validation::check_range( +// comm, +// elem.m_out.m_range); +// if(res.has_value()) { +// failed = true; +// return std::nullopt; +// } +// +// return comm; +// } +// ); +// +// pool.emplace_back(std::move(f)); + } + if(cursor > 0) { + failed = transaction::validation::check_range_batch(*range_batch).has_value(); + [[maybe_unused]] auto res = secp256k1_bppp_rangeproof_batch_clear(m_secp.get(), range_batch); + cursor = 0; + } + }; + + summarize(m_uhs); + summarize(m_locked); + summarize(m_spent); + [[maybe_unused]] auto res = secp256k1_bppp_rangeproof_batch_destroy(m_secp.get(), range_batch); + free(range_batch); + +// if(!failed) { +// comms.reserve(pool.size()); +// +// for(auto& f : pool) { +// auto c = f.get(); +// failed = !c.has_value(); +// if(failed) { +// break; +// } +// comms.emplace_back(std::move(c.value())); +// } +// } + + { + std::unique_lock l(m_mut); + m_uhs.release_snapshot(); + m_locked.release_snapshot(); + m_spent.release_snapshot(); + } + + if(failed) { + return std::nullopt; + } + + return sum_commitments(m_secp.get(), comms); + } + + auto locking_shard::highest_epoch() const -> uint64_t { + std::shared_lock l(m_mut); + return m_highest_epoch; + } + + void locking_shard::prune(uint64_t epoch) { + m_logger->info("Running prune through", epoch); + std::unique_lock l(m_mut); + for(auto it = m_spent.begin(); it != m_spent.end();) { + auto& elem = it->second; + if(elem.m_deletion_epoch.value() < epoch) { + it = m_spent.erase(it); + } else { + it++; + } + } + m_logger->info("Prune complete through", epoch); + } } diff --git a/src/uhs/twophase/locking_shard/locking_shard.hpp b/src/uhs/twophase/locking_shard/locking_shard.hpp index 242a84a27..e0abeb2f2 100644 --- a/src/uhs/twophase/locking_shard/locking_shard.hpp +++ b/src/uhs/twophase/locking_shard/locking_shard.hpp @@ -9,10 +9,13 @@ #include "client.hpp" #include "interface.hpp" #include "status_interface.hpp" +#include "uhs/transaction/messages.hpp" +#include "uhs/transaction/validation.hpp" #include "uhs/transaction/transaction.hpp" #include "util/common/cache_set.hpp" #include "util/common/hash.hpp" #include "util/common/hashmap.hpp" +#include "util/common/snapshot_map.hpp" #include "util/common/logging.hpp" #include @@ -124,9 +127,36 @@ namespace cbdc::locking_shard { [[nodiscard]] auto check_tx_id(const hash_t& tx_id) -> std::optional final; + /// Takes a snapshot of the UHS and calculates the supply of coins at + /// the given epoch. Checks the UHS IDs match the value and nested data + /// included in the UHS element. + /// \param epoch the epoch to audit the supply at. + /// \return commitment to total value in this shard's UHS. std::nullopt + /// if any of the UTXOs do not match their UHS ID. + auto get_summary(uint64_t epoch) -> std::optional; + + /// Prunes any spent UHS elements spent prior to the given epoch. + /// \param epoch epoch to prune prior to. + void prune(uint64_t epoch); + + /// Returns the highest epoch seen by the shard so far. + /// \return highest epoch. + auto highest_epoch() const -> uint64_t; + + struct uhs_element { + /// UTXO + transaction::compact_output m_out{}; + /// Epoch in which the UTXO was created. + uint64_t m_creation_epoch{}; + /// Epoch in which the UTXO was spent, or std::nullopt if + /// it is unspent. + std::optional m_deletion_epoch{}; + }; + private: auto read_preseed_file(const std::string& preseed_file) -> bool; auto check_and_lock_tx(const tx& t) -> bool; + void apply_tx(const tx& t, bool complete); struct prepared_dtx { std::vector m_txs; @@ -136,13 +166,47 @@ namespace cbdc::locking_shard { std::shared_ptr m_logger; mutable std::shared_mutex m_mut; - std::unordered_set m_uhs; - std::unordered_set m_locked; + snapshot_map m_uhs; + snapshot_map m_locked; + snapshot_map m_spent; + std::optional m_seed_rangeproof{}; std::unordered_map m_prepared_dtxs; std::unordered_set m_applied_dtxs; cbdc::cache_set m_completed_txs; config::options m_opts; + uint64_t m_highest_epoch{}; + + using secp256k1_context_destroy_type = void (*)(secp256k1_context*); + + std::unique_ptr + m_secp{secp256k1_context_create(SECP256K1_CONTEXT_NONE), + &secp256k1_context_destroy}; + + static const inline auto m_random_source + = std::make_unique(config::random_source); + + struct GensDeleter { + explicit GensDeleter(secp256k1_context* ctx) : m_ctx(ctx) {} + + void operator()(secp256k1_bppp_generators* gens) const { + secp256k1_bppp_generators_destroy(m_ctx, gens); + } + + secp256k1_context* m_ctx; + }; + + /// should be set to exactly `floor(log_base(value)) + 1` + /// + /// We use n_bits = 64, base = 16, so this should always be 24. + static const inline auto generator_count = 16 + 8; + + std::unique_ptr + m_generators{ + secp256k1_bppp_generators_create(m_secp.get(), + generator_count), + GensDeleter(m_secp.get())}; }; } diff --git a/src/uhs/twophase/sentinel_2pc/controller.cpp b/src/uhs/twophase/sentinel_2pc/controller.cpp index 4c6fc797f..71c5694fe 100644 --- a/src/uhs/twophase/sentinel_2pc/controller.cpp +++ b/src/uhs/twophase/sentinel_2pc/controller.cpp @@ -98,7 +98,37 @@ namespace cbdc::sentinel_2pc { auto controller::execute_transaction( transaction::full_tx tx, execute_result_callback_type result_callback) -> bool { + if(m_opts.m_seed_value > 0 && !m_seed_commitment.has_value()) { + std::vector tmp {}; + tmp.push_back({{}, m_opts.m_seed_value}); + + auto comm = transaction::roll_auxiliaries(m_secp.get(), + *m_random_source, + {}, + tmp); + if(comm.empty()) { return false; } + + m_seed_commitment = serialize_commitment(m_secp.get(), comm[0]); + + m_seed_rangeproof = cbdc::transaction::prove( + m_secp.get(), + m_generators.get(), + *m_random_source, + tmp[0], + &comm[0] + ); + } + + // modify tx to include the seed range proof + //if(m_opts.m_fixed_tx_mode && m_opts.m_fixed_tx_rate > 0.0 && m_seed_commitment.has_value()) { + // for(auto& outp : tx.m_outputs) { + // outp.m_auxiliary = m_seed_commitment.value(); + // outp.m_range = m_seed_rangeproof; + // } + //} + const auto validation_err = transaction::validation::check_tx(tx); + if(validation_err.has_value()) { auto tx_id = transaction::tx_id(tx); m_logger->debug( diff --git a/src/uhs/twophase/sentinel_2pc/controller.hpp b/src/uhs/twophase/sentinel_2pc/controller.hpp index b5485ebf2..2807bdd5c 100644 --- a/src/uhs/twophase/sentinel_2pc/controller.hpp +++ b/src/uhs/twophase/sentinel_2pc/controller.hpp @@ -12,12 +12,15 @@ #include "uhs/sentinel/client.hpp" #include "uhs/sentinel/format.hpp" #include "uhs/transaction/messages.hpp" +#include "uhs/transaction/transaction.hpp" #include "uhs/twophase/coordinator/client.hpp" #include "util/common/config.hpp" #include "util/common/hashmap.hpp" #include "util/network/connection_manager.hpp" #include +#include +#include namespace cbdc::sentinel_2pc { /// Manages a sentinel server for the two-phase commit architecture. @@ -91,11 +94,37 @@ namespace cbdc::sentinel_2pc { std::unique_ptr m_rpc_server; + using secp256k1_context_destroy_type = void (*)(secp256k1_context*); + std::unique_ptr - m_secp{secp256k1_context_create(SECP256K1_CONTEXT_SIGN), + secp256k1_context_destroy_type> + m_secp{secp256k1_context_create(SECP256K1_CONTEXT_NONE), &secp256k1_context_destroy}; + struct GensDeleter { + explicit GensDeleter(secp256k1_context* ctx) : m_ctx(ctx) {} + + void operator()(secp256k1_bppp_generators* gens) const { + secp256k1_bppp_generators_destroy(m_ctx, gens); + } + + secp256k1_context* m_ctx; + }; + + /// should be set to exactly `floor(log_base(value)) + 1`. + /// + /// We use n_bits = 64, base = 16, so this should always be 24. + static const inline auto generator_count = 16 + 8; + + std::unique_ptr + m_generators{ + secp256k1_bppp_generators_create(m_secp.get(), + generator_count), + GensDeleter(m_secp.get())}; + + std::optional m_seed_commitment {}; + cbdc::rangeproof_t m_seed_rangeproof {}; + coordinator::rpc::client m_coordinator_client; std::vector> @@ -105,6 +134,9 @@ namespace cbdc::sentinel_2pc { std::default_random_engine m_rand{m_r()}; std::uniform_int_distribution m_dist{}; + static const inline auto m_random_source + = std::make_unique(config::random_source); + privkey_t m_privkey{}; }; } diff --git a/src/util/common/CMakeLists.txt b/src/util/common/CMakeLists.txt index f9b4d26ae..84042d259 100644 --- a/src/util/common/CMakeLists.txt +++ b/src/util/common/CMakeLists.txt @@ -2,6 +2,7 @@ project(common) add_library(common buffer.cpp hash.cpp + commitment.cpp hashmap.cpp keys.cpp config.cpp diff --git a/src/util/common/commitment.cpp b/src/util/common/commitment.cpp new file mode 100644 index 000000000..b67b17352 --- /dev/null +++ b/src/util/common/commitment.cpp @@ -0,0 +1,133 @@ +// Copyright (c) 2021 MIT Digital Currency Initiative, +// Federal Reserve Bank of Boston +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "commitment.hpp" + +#include "crypto/sha256.h" +#include "keys.hpp" + +#include +#include +#include +#include +#include + +namespace cbdc { + auto + commit(const secp256k1_context* ctx, uint64_t value, const hash_t& blind) + -> std::optional { + secp256k1_pedersen_commitment commit{}; + auto res = secp256k1_pedersen_commit(ctx, + &commit, + blind.data(), + value, + secp256k1_generator_h); + if(res != 1) { + return std::nullopt; + } + + return commit; + } + + auto serialize_commitment(const secp256k1_context* ctx, + secp256k1_pedersen_commitment comm) + -> commitment_t { + commitment_t c{}; + secp256k1_pedersen_commitment_serialize(ctx, c.data(), &comm); + return c; + } + + auto make_commitment(const secp256k1_context* ctx, + uint64_t value, + const hash_t& blind) -> std::optional { + auto comm = commit(ctx, value, blind); + if(!comm.has_value()) { + return std::nullopt; + } + + return serialize_commitment(ctx, comm.value()); + } + + auto deserialize_commitment(const secp256k1_context* ctx, + commitment_t comm) + -> std::optional { + secp256k1_pedersen_commitment commitment{}; + if(secp256k1_pedersen_commitment_parse(ctx, &commitment, comm.data()) + != 1) { + return std::nullopt; + } + + return commitment; + } + + auto sum_commitments(const secp256k1_context* ctx, + std::vector commitments) + -> std::optional { + if(commitments.empty()) { + return std::nullopt; + } + if(commitments.size() == 1) { + return {commitments[0]}; + } + + std::vector as_keys{}; + for(auto& c : commitments) { + auto maybe_pc = deserialize_commitment(ctx, c); + if(!maybe_pc.has_value()) { + return std::nullopt; + } + + auto pc = maybe_pc.value(); + secp256k1_pubkey k{}; + secp256k1_pedersen_commitment_as_key(&pc, &k); + as_keys.emplace_back(k); + } + + std::vector key_pointers{}; + key_pointers.reserve(as_keys.size()); + for(auto& k : as_keys) { + key_pointers.push_back(&k); + } + + secp256k1_pubkey k{}; + auto res = secp256k1_ec_pubkey_combine(ctx, + &k, + key_pointers.data(), + as_keys.size()); + if(res != 1) { + return std::nullopt; + } + + secp256k1_pedersen_commitment summary{}; + secp256k1_pubkey_as_pedersen_commitment(ctx, &k, &summary); + + return serialize_commitment(ctx, summary); + } + + auto to_string(const commitment_t& comm) -> std::string { + std::stringstream ret; + ret << std::hex << std::setfill('0'); + + for(const auto& byte : comm) { + ret << std::setw(2) << static_cast(byte); + } + + return ret.str(); + } + + auto commitment_from_hex(const std::string& hex) -> commitment_t { + commitment_t ret; + + for(size_t i = 0; i < hex.size(); i += 2) { + unsigned int v{}; + std::stringstream s; + s << std::hex << hex.substr(i, 2); + s >> v; + ret[i / 2] = static_cast(v); + } + + return ret; + } +} diff --git a/src/util/common/commitment.hpp b/src/util/common/commitment.hpp new file mode 100644 index 000000000..58bcc291c --- /dev/null +++ b/src/util/common/commitment.hpp @@ -0,0 +1,75 @@ +// Copyright (c) 2021 MIT Digital Currency Initiative, +// Federal Reserve Bank of Boston +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef OPENCBDC_TX_SRC_COMMON_COMMITMENT_H_ +#define OPENCBDC_TX_SRC_COMMON_COMMITMENT_H_ + +#include "hash.hpp" +#include "keys.hpp" + +#include + +namespace cbdc { + /// Creates a Pedersen commitment + /// + /// \param ctx secp256k1 context initialized for signing and commitment + /// \param value the value to commit to + /// \param blind a 32-byte blinding factor + /// \return a pedersen commitment, or nullopt if making the commitment + /// failed + auto + commit(const secp256k1_context* ctx, uint64_t value, const hash_t& blind) + -> std::optional; + + /// Serializes a Pedersen commitment + /// + /// \param ctx secp256k1 context initialized for signing and commitment + /// \param comm the commitment to serialize + /// \return a serialized pedersen commitment + auto serialize_commitment(const secp256k1_context* ctx, + secp256k1_pedersen_commitment comm) + -> commitment_t; + + /// Creates and serializes a Pedersen commitment + /// + /// A shortcut to to \ref commit and \ref serialize_commitment + /// + /// \param ctx secp256k1 context initialized for signing and commitment + /// \param value the value to commit to + /// \param blinder a 32-byte blinding factor + /// \return a serialized pedersen commitment, or nullopt if making the + /// commitment failed + auto make_commitment(const secp256k1_context* ctx, + uint64_t value, + const hash_t& blinder) -> std::optional; + + /// Attempts to deserialize a Pedersen commitment + /// + /// \param ctx secp256k1 context initialized for signing and commitment + /// \param comm a 33-byte serialized Pedersen commitment + /// \return the deserialized commitment, or std::nullopt if the input + /// was invalid + auto deserialize_commitment(const secp256k1_context* ctx, + commitment_t comm) + -> std::optional; + + /// Attempts to sum a list of Pedersen commitments + /// + /// \param ctx secp256k1 context initialized for signing and commitment + /// \param commitments the vector of commitments to sum + /// \return std::nullopt if conversion or summing failed; the summed + /// commitment otherwise + auto sum_commitments(const secp256k1_context* ctx, + std::vector commitments) + -> std::optional; + + /// serialize a commitment to hexadecimal + auto to_string(const commitment_t& comm) -> std::string; + + /// deserialize a commitment from hexadecimal + auto commitment_from_hex(const std::string& hex) -> commitment_t; +} + +#endif // OPENCBDC_TX_SRC_COMMON_COMMITMENT_H_ diff --git a/src/util/common/config.cpp b/src/util/common/config.cpp index 02811f4d2..0888c8315 100644 --- a/src/util/common/config.cpp +++ b/src/util/common/config.cpp @@ -241,6 +241,13 @@ namespace cbdc::config { return ss.str(); } + auto get_shard_audit_log_key(size_t shard_id) -> std::string { + std::stringstream ss; + get_shard_key_prefix(ss, shard_id); + ss << audit_log_postfix; + return ss.str(); + } + auto read_shard_endpoints(options& opts, const parser& cfg) -> std::optional { const auto shard_count = cfg.get_ulong(shard_count_key).value_or(0); @@ -335,6 +342,14 @@ namespace cbdc::config { = std::make_pair(static_cast(*range_start), static_cast(*range_end)); opts.m_shard_ranges.push_back(shard_range); + + const auto audit_log_key = get_shard_audit_log_key(i); + const auto audit_log = cfg.get_string(audit_log_key); + if(!audit_log.has_value()) { + return "No audit log file specified for shard " + + std::to_string(i) + " (" + audit_log_key + ")"; + } + opts.m_shard_audit_logs.push_back(audit_log.value()); } opts.m_shard_completed_txs_cache_size @@ -356,6 +371,10 @@ namespace cbdc::config { = cfg.get_ulong(seed_value).value_or(opts.m_seed_value); } + opts.m_shard_audit_interval + = cfg.get_ulong(shard_audit_interval_key) + .value_or(opts.m_shard_audit_interval); + return std::nullopt; } diff --git a/src/util/common/config.hpp b/src/util/common/config.hpp index 9ed00fedc..c6206dc9c 100644 --- a/src/util/common/config.hpp +++ b/src/util/common/config.hpp @@ -63,6 +63,7 @@ namespace cbdc::config { static constexpr size_t output_count{2}; static constexpr double fixed_tx_rate{1.0}; static constexpr size_t attestation_threshold{1}; + static constexpr uint64_t shard_audit_interval{100}; static constexpr auto log_level = logging::log_level::warn; } @@ -124,6 +125,8 @@ namespace cbdc::config { static constexpr auto private_key_postfix = "private_key"; static constexpr auto public_key_postfix = "public_key"; static constexpr auto attestation_threshold_key = "attestation_threshold"; + static constexpr auto audit_log_postfix = "audit_log"; + static constexpr auto shard_audit_interval_key = "audit_interval"; /// [start, end] inclusive. using shard_range_t = std::pair; @@ -188,6 +191,10 @@ namespace cbdc::config { /// List of shard UHS ID ranges by shard ID. Each shard range is /// inclusive of the start and end of the range. std::vector m_shard_ranges; + /// Number of blocks between which to perform an audit. + uint64_t m_shard_audit_interval{defaults::shard_audit_interval}; + /// List of shard audit log files. + std::vector m_shard_audit_logs; /// private key used for initial seed. std::optional m_seed_privkey; diff --git a/src/util/common/keys.hpp b/src/util/common/keys.hpp index fd7a1813a..e48a3f8be 100644 --- a/src/util/common/keys.hpp +++ b/src/util/common/keys.hpp @@ -8,6 +8,7 @@ #include #include +#include #include struct secp256k1_context_struct; @@ -18,6 +19,8 @@ namespace cbdc { static constexpr size_t pubkey_len = 32; /// Size of signatures used throughout the system, in bytes. static constexpr size_t sig_len = 64; + /// Size of a standard, compressed EC Point + static constexpr size_t point_len = 33; /// A private key of a public/private keypair. using privkey_t = std::array; @@ -27,6 +30,13 @@ namespace cbdc { using witness_t = std::vector; /// A signature. using signature_t = std::array; + /// A Pedersen Commitment. + using commitment_t = std::array; + + /// A range-proof + /// \tparam N the size (in bytes) of the proof (dependent on the range + /// being proven. + using rangeproof_t = std::vector; /// Generates a public key from the specified private key. /// \param privkey private key for which to generate the public key. diff --git a/src/util/common/snapshot_map.hpp b/src/util/common/snapshot_map.hpp new file mode 100644 index 000000000..5cf275d06 --- /dev/null +++ b/src/util/common/snapshot_map.hpp @@ -0,0 +1,130 @@ +// Copyright (c) 2022 MIT Digital Currency Initiative, +// Federal Reserve Bank of Boston +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef CBDC_UNIVERSE0_SRC_COMMON_SNAPSHOT_MAP_H_ +#define CBDC_UNIVERSE0_SRC_COMMON_SNAPSHOT_MAP_H_ + +#include +#include +#include + +namespace cbdc { + template + class snapshot_map { + public: + using map_type = std::map; + using set_type = std::set; + + auto operator=(map_type&& m) -> snapshot_map& { + m_map = std::move(m); + return *this; + } + + void release_snapshot() { + m_snapshot = false; + } + + void snapshot() { + m_snapshot = true; + } + + [[nodiscard]] auto find(const K& key) const -> + typename map_type::const_iterator { + auto added_it = m_added.find(key); + if(added_it != m_added.end()) { + return added_it; + } + + auto removed_it = m_removed.find(key); + if(removed_it != m_removed.end()) { + return m_map.end(); + } + + return m_map.find(key); + } + + [[nodiscard]] auto size() const -> size_t { + return m_added.size() + m_removed.size() + m_map.size(); + } + + [[nodiscard]] auto end() const -> typename map_type::const_iterator { + return m_map.end(); + } + + [[nodiscard]] auto begin() const -> typename map_type::const_iterator { + return m_map.begin(); + } + + template + auto emplace(Args&&... args) + -> std::pair { + gc(); + auto ret = [&]() { + if(m_snapshot) { + return m_added.emplace(std::forward(args)...); + } + auto it = m_map.emplace(std::forward(args)...); + m_added.erase(it.first->first); + return it; + }(); + m_removed.erase(ret.first->first); + return ret; + } + + auto erase(typename map_type::const_iterator it) -> + typename map_type::iterator { + assert(!m_snapshot); + m_added.erase(it->first); + m_removed.erase(it->first); + return m_map.erase(it); + } + + void erase(const K& key) { + gc(); + m_added.erase(key); + if(m_snapshot) { + m_removed.emplace(key); + } else { + m_removed.erase(key); + m_map.erase(key); + } + } + + private: + map_type m_map; + map_type m_added; + set_type m_removed; + bool m_snapshot{false}; + + void gc() { + if(m_snapshot) { + return; + } + constexpr size_t factor = 1000000; + constexpr size_t one = 1; + auto added_elems = std::min(std::max(m_added.size() / factor, one), + m_added.size()); + size_t count = 0; + for(auto it = m_added.begin(); + it != m_added.end() && count < added_elems; + count++) { + auto n = m_added.extract(it++); + m_map.insert(std::move(n)); + } + count = 0; + auto removed_elems + = std::min(std::max(m_removed.size() / factor, one), + m_removed.size()); + for(auto it = m_removed.begin(); + it != m_removed.end() && count < removed_elems; + count++) { + auto n = m_removed.extract(it++); + m_map.erase(n.value()); + } + } + }; +} + +#endif diff --git a/src/util/raft/index_comparator.cpp b/src/util/raft/index_comparator.cpp index 37ba36125..6f6c5f9bd 100644 --- a/src/util/raft/index_comparator.cpp +++ b/src/util/raft/index_comparator.cpp @@ -7,6 +7,7 @@ #include #include +#include #include namespace cbdc::raft { diff --git a/src/util/serialization/format.hpp b/src/util/serialization/format.hpp index 683db3719..cb31184cc 100644 --- a/src/util/serialization/format.hpp +++ b/src/util/serialization/format.hpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -232,6 +233,47 @@ namespace cbdc { return packet; } + /// Serializes the count of key-value pairs, and then each key and value, + /// statically-casted. + /// \see \ref cbdc::operator<<(serializer&, T) + template + auto operator<<(serializer& ser, const std::map& map) + -> serializer& { + auto len = static_cast(map.size()); + ser << len; + for(const auto& it : map) { + ser << static_cast(it.first); + ser << static_cast(it.second); + } + return ser; + } + + /// Deserializes a map of key-value pairs. + /// \see \ref cbdc::operator<<(serializer&, const std::map&) + template + auto operator>>(serializer& deser, std::map& map) -> serializer& { + auto len = uint64_t(); + if(!(deser >> len)) { + return deser; + } + + while(len-- > 0) { + auto key = K(); + if(!(deser >> key)) { + return deser; + } + + auto val = V(); + if(!(deser >> val)) { + return deser; + } + + map.emplace(std::move(key), std::move(val)); + } + + return deser; + } + /// Serializes the count of key-value pairs, and then each key and value, /// statically-casted. /// \see \ref cbdc::operator<<(serializer&, T) diff --git a/tests/integration/atomizer_end_to_end_test.cpp b/tests/integration/atomizer_end_to_end_test.cpp index 37337c296..b8d12eb9c 100644 --- a/tests/integration/atomizer_end_to_end_test.cpp +++ b/tests/integration/atomizer_end_to_end_test.cpp @@ -77,6 +77,20 @@ class atomizer_end_to_end_test : public ::testing::Test { m_sender = nullptr; m_receiver = nullptr; + // Wait for an audit to complete + std::this_thread::sleep_for(std::chrono::seconds(1)); + auto audit_file = std::ifstream("shard0_audit.txt"); + ASSERT_TRUE(audit_file.good()); + auto audit_entries = std::vector>(); + uint64_t epoch{}; + uint64_t value{}; + while(audit_file >> epoch >> value) { + audit_entries.emplace_back(epoch, value); + } + ASSERT_FALSE(audit_entries.empty()); + // ASSERT_EQ(audit_entries.back().second, 100ul); + // todo: sum_commitments with circulation-commitment + std::filesystem::remove_all("archiver0_db"); std::filesystem::remove_all("atomizer_raft_log_0"); std::filesystem::remove_all("atomizer_raft_config_0.dat"); @@ -88,6 +102,7 @@ class atomizer_end_to_end_test : public ::testing::Test { std::filesystem::remove(m_receiver_wallet_store_file); std::filesystem::remove(m_receiver_client_store_file); std::filesystem::remove("tp_samples.txt"); + std::filesystem::remove("shard0_audit.txt"); } void reload_sender() { @@ -142,7 +157,7 @@ TEST_F(atomizer_end_to_end_test, complete_transaction) { ASSERT_TRUE(res.has_value()); ASSERT_FALSE(res->m_tx_error.has_value()); ASSERT_EQ(res->m_tx_status, cbdc::sentinel::tx_status::pending); - ASSERT_EQ(tx->m_outputs[0].m_value, 33UL); + ASSERT_EQ(tx->m_out_spend_data.value()[0].m_value, 33UL); ASSERT_EQ(m_sender->balance(), 60UL); auto in = m_sender->export_send_inputs(tx.value(), addr); ASSERT_EQ(in.size(), 1UL); @@ -184,8 +199,7 @@ TEST_F(atomizer_end_to_end_test, double_spend) { ASSERT_EQ(tx->m_outputs.size(), 2UL); ASSERT_FALSE(res->m_tx_error.has_value()); ASSERT_EQ(res->m_tx_status, cbdc::sentinel::tx_status::pending); - ASSERT_EQ(tx->m_outputs[0].m_value, 33UL); - ASSERT_EQ(tx->m_outputs[1].m_value, 7UL); // amount back to sender + ASSERT_EQ(tx->m_out_spend_data.value()[0].m_value, 33UL); ASSERT_EQ(m_sender->balance(), 60UL); std::this_thread::sleep_for(m_block_wait_interval); @@ -209,13 +223,15 @@ TEST_F(atomizer_end_to_end_test, double_spend) { std::this_thread::sleep_for(m_block_wait_interval); const auto ctx = cbdc::transaction::compact_tx(tx.value()); + const auto out0_id = cbdc::transaction::calculate_uhs_id(ctx.m_outputs[0]); + const auto out1_id = cbdc::transaction::calculate_uhs_id(ctx.m_outputs[1]); const auto wc_res = wc.request_status_update( cbdc::watchtower::status_update_request{{{ctx.m_id, { ctx.m_inputs[0], ctx.m_inputs[1], - ctx.m_uhs_outputs[0], - ctx.m_uhs_outputs[1], + out0_id, + out1_id, }}}}); // Final check - ensure attempted double spends are marked as spent: @@ -236,36 +252,39 @@ TEST_F(atomizer_end_to_end_test, invalid_transaction) { auto wc = cbdc::watchtower::blocking_client( m_opts.m_watchtower_client_endpoints[0]); ASSERT_TRUE(wc.init()); - auto tx = m_sender->create_transaction(33, addr); - ASSERT_TRUE(tx.has_value()); + auto maybe_tx = m_sender->create_transaction(33, addr); + ASSERT_TRUE(maybe_tx.has_value()); + auto tx = maybe_tx.value(); - tx.value().m_outputs[0].m_value = 1; // Unbalanced + tx.m_outputs.erase(tx.m_outputs.end() - 1); - m_sender->sign_transaction(tx.value()); - auto res = m_sender->send_transaction(tx.value()); + m_sender->sign_transaction(tx); + auto res = m_sender->send_transaction(tx); ASSERT_TRUE(res.has_value()); ASSERT_TRUE(res->m_tx_error.has_value()); ASSERT_EQ(res->m_tx_status, cbdc::sentinel::tx_status::static_invalid); ASSERT_TRUE( - std::holds_alternative( + std::holds_alternative( res->m_tx_error.value())); - auto tx_err = std::get( + auto tx_err = std::get( res->m_tx_error.value()); - ASSERT_EQ(tx_err, - cbdc::transaction::validation::tx_error_code::asymmetric_values); + ASSERT_EQ(tx_err.m_code, + cbdc::transaction::validation::proof_error_code::wrong_sum); std::this_thread::sleep_for(m_block_wait_interval); - const auto ctx = cbdc::transaction::compact_tx(tx.value()); + const auto ctx = cbdc::transaction::compact_tx(tx); + const auto out0_id = cbdc::transaction::calculate_uhs_id(ctx.m_outputs[0]); + const auto out1_id = cbdc::transaction::calculate_uhs_id(ctx.m_outputs[1]); const auto wc_res = wc.request_status_update( cbdc::watchtower::status_update_request{{{ctx.m_id, { ctx.m_inputs[0], ctx.m_inputs[1], - ctx.m_uhs_outputs[0], - ctx.m_uhs_outputs[1], + out0_id, + out1_id, }}}}); const auto res_uhs_states = wc_res->states().at(ctx.m_id); @@ -279,3 +298,75 @@ TEST_F(atomizer_end_to_end_test, invalid_transaction) { ASSERT_EQ(res_uhs_states[3].status(), cbdc::watchtower::search_status::no_history); } + +TEST_F(atomizer_end_to_end_test, complete_transaction_loop) { + for(int i = 0; i < 1; ++i) { + ASSERT_EQ(m_sender->balance(), 100UL); + ASSERT_EQ(m_receiver->balance(), 0UL); + + auto addr = m_receiver->new_address(); + + auto [tx, res] = m_sender->send(33, addr); + ASSERT_TRUE(tx.has_value()); + ASSERT_TRUE(res.has_value()); + ASSERT_FALSE(res->m_tx_error.has_value()); + ASSERT_EQ(res->m_tx_status, cbdc::sentinel::tx_status::pending); + ASSERT_EQ(tx->m_out_spend_data.value()[0].m_value, 33UL); + // ASSERT_EQ(m_sender->balance(), 60UL); + auto in = m_sender->export_send_inputs(tx.value(), addr); + ASSERT_EQ(in.size(), 1UL); + + std::this_thread::sleep_for(m_block_wait_interval); + reload_sender(); + // ASSERT_EQ(m_sender->balance(), 60UL); + ASSERT_EQ(m_sender->pending_tx_count(), 1UL); + ASSERT_EQ(m_sender->pending_input_count(), 0UL); + m_sender->sync(); + ASSERT_EQ(m_sender->balance(), 67UL); + ASSERT_EQ(m_sender->pending_tx_count(), 0UL); + + ASSERT_EQ(m_receiver->pending_input_count(), 0UL); + m_receiver->import_send_input(in[0]); + reload_receiver(); + ASSERT_EQ(m_receiver->balance(), 0UL); + ASSERT_EQ(m_sender->pending_tx_count(), 0UL); + ASSERT_EQ(m_receiver->pending_input_count(), 1UL); + m_receiver->sync(); + ASSERT_EQ(m_receiver->balance(), 33UL); + ASSERT_EQ(m_sender->pending_tx_count(), 0UL); + ASSERT_EQ(m_receiver->pending_input_count(), 0UL); + + // SEND BACK: + addr = m_sender->new_address(); + + auto [tx2, res2] = m_receiver->send(33, addr); + ASSERT_TRUE(tx2.has_value()); + ASSERT_TRUE(res2.has_value()); + ASSERT_FALSE(res2->m_tx_error.has_value()); + ASSERT_EQ(res2->m_tx_status, cbdc::sentinel::tx_status::pending); + ASSERT_EQ(tx2->m_out_spend_data.value()[0].m_value, 33UL); + ASSERT_EQ(m_receiver->balance(), 0UL); + in = m_receiver->export_send_inputs(tx2.value(), addr); + ASSERT_EQ(in.size(), 1UL); + + std::this_thread::sleep_for(m_block_wait_interval); + reload_receiver(); + ASSERT_EQ(m_receiver->balance(), 0UL); + ASSERT_EQ(m_receiver->pending_tx_count(), 1UL); + ASSERT_EQ(m_receiver->pending_input_count(), 0UL); + m_receiver->sync(); + ASSERT_EQ(m_receiver->balance(), 0UL); + ASSERT_EQ(m_receiver->pending_tx_count(), 0UL); + + ASSERT_EQ(m_sender->pending_input_count(), 0UL); + m_sender->import_send_input(in[0]); + reload_sender(); + ASSERT_EQ(m_sender->balance(), 67UL); + ASSERT_EQ(m_receiver->pending_tx_count(), 0UL); + ASSERT_EQ(m_sender->pending_input_count(), 1UL); + m_sender->sync(); + ASSERT_EQ(m_sender->balance(), 100UL); + ASSERT_EQ(m_receiver->pending_tx_count(), 0UL); + ASSERT_EQ(m_sender->pending_input_count(), 0UL); + } +} diff --git a/tests/integration/atomizer_raft_integration_test.cpp b/tests/integration/atomizer_raft_integration_test.cpp index f0fcbbbbc..9a9c0ef3b 100644 --- a/tests/integration/atomizer_raft_integration_test.cpp +++ b/tests/integration/atomizer_raft_integration_test.cpp @@ -109,22 +109,26 @@ class atomizer_raft_integration_test : public ::testing::Test { TEST_F(atomizer_raft_integration_test, basic) { ASSERT_TRUE( m_conn.send(cbdc::atomizer::request{cbdc::atomizer::tx_notify_request{ - cbdc::test::simple_tx({'a'}, {{{'b'}}, {{'c'}}}, {{{'d'}}}), + cbdc::test::simple_tx({'a'}, + {{'b'}, {'c'}}, + {{{'d'}, {'e'}, {'f'}}}), {0, 1}, 0}})); ASSERT_TRUE( m_conn.send(cbdc::atomizer::request{cbdc::atomizer::tx_notify_request{ - cbdc::test::simple_tx({'e'}, {{{'f'}}, {{'g'}}}, {{{'h'}}}), + cbdc::test::simple_tx({'e'}, + {{'f'}, {'g'}}, + {{{'h'}, {'i'}, {'j'}}}), {0, 1}, 0}})); cbdc::test::block want_block; want_block.m_height = 1; want_block.m_transactions.push_back( - cbdc::test::simple_tx({'a'}, {{'b'}, {'c'}}, {{'d'}})); + cbdc::test::simple_tx({'a'}, {{'b'}, {'c'}}, {{{'d'}, {'e'}, {'f'}}})); want_block.m_transactions.push_back( - cbdc::test::simple_tx({'e'}, {{'f'}, {'g'}}, {{'h'}})); + cbdc::test::simple_tx({'e'}, {{'f'}, {'g'}}, {{{'h'}, {'i'}, {'j'}}})); expect_block(want_block); } @@ -134,13 +138,17 @@ TEST_F(atomizer_raft_integration_test, error_inputs_spent) { ASSERT_TRUE( m_conn.send(cbdc::atomizer::request{cbdc::atomizer::tx_notify_request{ - cbdc::test::simple_tx({'a'}, {{{'B'}}, {{'c'}}}, {{{'d'}}}), + cbdc::test::simple_tx({'a'}, + {{'B'}, {'c'}}, + {{{'d'}, {'e'}, {'f'}}}), {0, 1}, 0}})); ASSERT_TRUE( m_conn.send(cbdc::atomizer::request{cbdc::atomizer::tx_notify_request{ - cbdc::test::simple_tx({'E'}, {{{'B'}}, {{'f'}}}, {{{'g'}}}), + cbdc::test::simple_tx({'E'}, + {{'B'}, {'f'}}, + {{{'g'}, {'h'}, {'i'}}}), {0, 1}, 0}})); @@ -154,6 +162,6 @@ TEST_F(atomizer_raft_integration_test, error_inputs_spent) { cbdc::test::block want_block; want_block.m_height = 1; want_block.m_transactions.push_back( - cbdc::test::simple_tx({'a'}, {{'B'}, {'c'}}, {{'d'}})); + cbdc::test::simple_tx({'a'}, {{'B'}, {'c'}}, {{{'d'}, {'e'}, {'f'}}})); expect_block(want_block); } diff --git a/tests/integration/integration_tests.cfg b/tests/integration/integration_tests.cfg index 16c26c5c4..72e4fe408 100644 --- a/tests/integration/integration_tests.cfg +++ b/tests/integration/integration_tests.cfg @@ -7,6 +7,7 @@ atomizer_count=1 window_size=40000 shard0_endpoint="127.0.0.1:5556" shard0_db="shard0_db" +shard0_audit_log="shard0_audit.txt" shard0_start=0 shard0_end=255 shard0_loglevel="WARN" @@ -28,3 +29,4 @@ watchtower0_internal_endpoint="127.0.0.1:5560" watchtower0_loglevel="TRACE" watchtower_count=1 loadgen_invalid_tx_rate=0.00 +audit_interval=0 diff --git a/tests/integration/integration_tests_2pc.cfg b/tests/integration/integration_tests_2pc.cfg index 6087c9d47..b1f3ec47d 100644 --- a/tests/integration/integration_tests_2pc.cfg +++ b/tests/integration/integration_tests_2pc.cfg @@ -9,6 +9,7 @@ shard0_count=1 shard0_start=0 shard0_end=255 shard0_loglevel="DEBUG" +shard0_audit_log="shard0_audit.txt" shard0_0_endpoint="127.0.0.1:8987" shard0_0_raft_endpoint="127.0.0.1:8988" shard0_0_readonly_endpoint="127.0.0.1:8989" diff --git a/tests/integration/replicated_atomizer.cfg b/tests/integration/replicated_atomizer.cfg index 3983521e7..f708578e5 100644 --- a/tests/integration/replicated_atomizer.cfg +++ b/tests/integration/replicated_atomizer.cfg @@ -12,6 +12,7 @@ shard0_endpoint="127.0.0.1:5556" shard0_db="shard0_db" shard0_start=0 shard0_end=255 +shard0_audit_log="shard0_audit.txt" shard_count=1 sentinel0_endpoint="127.0.0.1:5557" sentinel_count=1 diff --git a/tests/integration/replicated_atomizer_integration_tests.cpp b/tests/integration/replicated_atomizer_integration_tests.cpp index c66b09ac1..1507ec686 100644 --- a/tests/integration/replicated_atomizer_integration_tests.cpp +++ b/tests/integration/replicated_atomizer_integration_tests.cpp @@ -137,7 +137,7 @@ class replicated_atomizer_integration_tests : public ::testing::Test { TEST_F(replicated_atomizer_integration_tests, can_send_message_from_clustered_atomizer) { - auto tx = cbdc::test::simple_tx({'a'}, {}, {{{'c'}}}); + auto tx = cbdc::test::simple_tx({'a'}, {}, {{{'c'}, {'d'}, {'e'}}}); ASSERT_TRUE(m_cluster.send_to_one(cbdc::atomizer::request{ cbdc::atomizer::tx_notify_request{tx, {}, 0}})); @@ -169,17 +169,17 @@ TEST_F(replicated_atomizer_integration_tests, raftnode_crash_recover) { // Sending a number of transactions (>=snapshot_distance) to ensure // snapshot is being taken: - const auto tx = cbdc::test::simple_tx({'a'}, {}, {{{'c'}}}); + const auto tx = cbdc::test::simple_tx({'a'}, {}, {{{'c'}, {'d'}, {'e'}}}); ASSERT_TRUE(m_cluster.send_to_one(cbdc::atomizer::request{ cbdc::atomizer::tx_notify_request{tx, {}, 0}})); expect_tx(tx, std::chrono::seconds(5)); - const auto tx2 = cbdc::test::simple_tx({'b'}, {}, {{{'d'}}}); + const auto tx2 = cbdc::test::simple_tx({'b'}, {}, {{{'d'}, {'e'}, {'f'}}}); ASSERT_TRUE(m_cluster.send_to_one(cbdc::atomizer::request{ cbdc::atomizer::tx_notify_request{tx2, {}, 0}})); expect_tx(tx2, std::chrono::seconds(5)); - const auto tx3 = cbdc::test::simple_tx({'c'}, {}, {{{'e'}}}); + const auto tx3 = cbdc::test::simple_tx({'c'}, {}, {{{'e'}, {'f'}, {'g'}}}); ASSERT_TRUE(m_cluster.send_to_one(cbdc::atomizer::request{ cbdc::atomizer::tx_notify_request{tx3, {}, 0}})); expect_tx(tx3, std::chrono::seconds(5)); @@ -191,7 +191,7 @@ TEST_F(replicated_atomizer_integration_tests, raftnode_crash_recover) { ASSERT_TRUE(m_ctls[killidx]->init()); // Send/confirm another transaction - const auto tx4 = cbdc::test::simple_tx({'d'}, {}, {{{'f'}}}); + const auto tx4 = cbdc::test::simple_tx({'d'}, {}, {{{'f'}, {'g'}, {'h'}}}); ASSERT_TRUE(m_cluster.send_to_one(cbdc::atomizer::request{ cbdc::atomizer::tx_notify_request{tx4, {}, 0}})); expect_tx(tx4, std::chrono::seconds(5)); diff --git a/tests/integration/replicated_shard.cfg b/tests/integration/replicated_shard.cfg index 7354bec1f..a63ea8154 100644 --- a/tests/integration/replicated_shard.cfg +++ b/tests/integration/replicated_shard.cfg @@ -8,22 +8,27 @@ shard0_endpoint="127.0.0.1:5556" shard0_db="shard0_db" shard0_start=0 shard0_end=8 +shard0_audit_log="shard0_audit.txt" shard1_endpoint="127.0.0.1:5561" shard1_db="shard1_db" shard1_start=4 shard1_end=32 +shard1_audit_log="shard1_audit.txt" shard2_endpoint="127.0.0.1:5562" shard2_db="shard2_db" shard2_start=16 shard2_end=64 +shard2_audit_log="shard2_audit.txt" shard3_endpoint="127.0.0.1:5563" shard3_db="shard3_db" shard3_start=32 shard3_end=128 +shard3_audit_log="shard3_audit.txt" shard4_endpoint="127.0.0.1:5564" shard4_db="shard4_db" shard4_start=64 shard4_end=255 +shard4_audit_log="shard4_audit.txt" shard_count=5 sentinel0_endpoint="127.0.0.1:5557" sentinel0_private_key="0000000000000001000000000000000000000000000000000000000000000000" diff --git a/tests/integration/replicated_shard_integration_tests.cpp b/tests/integration/replicated_shard_integration_tests.cpp index 858377707..093c57373 100644 --- a/tests/integration/replicated_shard_integration_tests.cpp +++ b/tests/integration/replicated_shard_integration_tests.cpp @@ -59,7 +59,9 @@ class replicated_shard_integration_tests : public ::testing::Test { TEST_F(replicated_shard_integration_tests, can_send_messages_from_multiple_shards) { - auto mint_tx = cbdc::test::simple_tx({'a'}, {}, {{'c'}}); + cbdc::transaction::compact_output outp{{'c'}, {'d'}, {'e'}}; + auto o_id = cbdc::transaction::calculate_uhs_id(outp); + auto mint_tx = cbdc::test::simple_tx({'a'}, {}, {outp}); auto init_blk = cbdc::atomizer::block{1, {mint_tx}}; auto br = m_sys->broadcast_from( @@ -69,7 +71,8 @@ TEST_F(replicated_shard_integration_tests, std::this_thread::sleep_for(std::chrono::seconds(5)); - auto spend_tx = cbdc::test::simple_tx({'d'}, {{'c'}}, {{'e'}}); + auto spend_tx + = cbdc::test::simple_tx({'d'}, {o_id}, {{{'e'}, {'f'}, {'g'}}}); cbdc::test::sign_tx(spend_tx, m_opts.m_sentinel_private_keys[0]); std::vector> res; diff --git a/tests/integration/shard_integration_test.cpp b/tests/integration/shard_integration_test.cpp index 724a7fd23..7a5cf56c8 100644 --- a/tests/integration/shard_integration_test.cpp +++ b/tests/integration/shard_integration_test.cpp @@ -54,7 +54,7 @@ TEST_F(shard_integration_test, error_non_existant_input) { init_blk)); std::this_thread::sleep_for(std::chrono::seconds(1)); - auto tx = cbdc::test::simple_tx({'a'}, {{'b'}}, {{'c'}}); + auto tx = cbdc::test::simple_tx({'a'}, {{'b'}}, {{{'c'}, {'d'}, {'e'}}}); cbdc::test::sign_tx(tx, m_opts.m_sentinel_private_keys[0]); m_client.broadcast(tx); @@ -71,7 +71,7 @@ TEST_F(shard_integration_test, error_initial_sync) { auto got_err = m_sys->expect>( cbdc::test::mock_system_module::watchtower); - auto tx = cbdc::test::simple_tx({'a'}, {{'b'}}, {{'c'}}); + auto tx = cbdc::test::simple_tx({'a'}, {{'b'}}, {{{'c'}, {'d'}, {'e'}}}); cbdc::test::sign_tx(tx, m_opts.m_sentinel_private_keys[0]); m_client.broadcast(tx); diff --git a/tests/integration/two_phase_end_to_end_test.cpp b/tests/integration/two_phase_end_to_end_test.cpp index ae82015fd..9788ea323 100644 --- a/tests/integration/two_phase_end_to_end_test.cpp +++ b/tests/integration/two_phase_end_to_end_test.cpp @@ -125,7 +125,7 @@ TEST_F(two_phase_end_to_end_test, complete_transaction) { ASSERT_TRUE(res.has_value()); ASSERT_FALSE(res->m_tx_error.has_value()); ASSERT_EQ(res->m_tx_status, cbdc::sentinel::tx_status::confirmed); - ASSERT_EQ(tx->m_outputs[0].m_value, 33UL); + ASSERT_EQ(tx->m_out_spend_data.value()[0].m_value, 33UL); ASSERT_EQ(m_sender->balance(), 67UL); auto in = m_sender->export_send_inputs(tx.value(), addr); ASSERT_EQ(in.size(), 1UL); @@ -157,7 +157,7 @@ TEST_F(two_phase_end_to_end_test, duplicate_transaction) { ASSERT_FALSE(res2->m_tx_error.has_value()); ASSERT_EQ(res->m_tx_status, cbdc::sentinel::tx_status::confirmed); ASSERT_EQ(res2->m_tx_status, cbdc::sentinel::tx_status::state_invalid); - ASSERT_EQ(tx->m_outputs[0].m_value, 33UL); + ASSERT_EQ(tx->m_out_spend_data.value()[0].m_value, 33UL); ASSERT_EQ(m_sender->balance(), 67UL); auto in = m_sender->export_send_inputs(tx.value(), addr); ASSERT_EQ(in.size(), 1UL); @@ -189,7 +189,7 @@ TEST_F(two_phase_end_to_end_test, double_spend_transaction) { ASSERT_TRUE(res.has_value()); ASSERT_FALSE(res->m_tx_error.has_value()); ASSERT_EQ(res->m_tx_status, cbdc::sentinel::tx_status::confirmed); - ASSERT_EQ(tx->m_outputs[0].m_value, 33UL); + ASSERT_EQ(tx->m_out_spend_data.value()[0].m_value, 33UL); ASSERT_EQ(m_sender->balance(), 67UL); auto in = m_sender->export_send_inputs(tx.value(), addr); ASSERT_EQ(in.size(), 1UL); @@ -205,13 +205,9 @@ TEST_F(two_phase_end_to_end_test, double_spend_transaction) { // Create a second transaction auto tx2 = m_sender->create_transaction(33, addr); - // Append the first input of the first transaction to this second - // transaction, creating the double-spend. Also increase the output value - // to balance the inputs and outputs - tx2.value().m_inputs.push_back(tx.value().m_inputs[0]); - tx2.value().m_outputs[0].m_value - = tx2.value().m_outputs[0].m_value - + tx.value().m_inputs[0].m_prevout_data.m_value; + // Replace first input with the one from the previous transaction + // to create a double-spend. + tx2.value().m_inputs[0] = tx.value().m_inputs[0]; m_sender->sign_transaction(tx2.value()); @@ -226,11 +222,11 @@ TEST_F(two_phase_end_to_end_test, double_spend_transaction) { ASSERT_TRUE(res3.has_value()); ASSERT_TRUE(res3.value()); - // Check if the outputs (excluding the appended double spend one) are + // Check if the outputs (excluding the replaced double spend one) are // still marked as unspent on the shard - for(size_t i = 0; i < tx2.value().m_inputs.size() - 1; i++) { + for(size_t i = 1; i < tx2.value().m_inputs.size() - 1; i++) { auto inp = tx2.value().m_inputs[i]; - auto res4 = m_sender->check_unspent(inp.hash()); + auto res4 = m_sender->check_unspent(inp.m_prevout_data.m_id); ASSERT_TRUE(res4.has_value()); ASSERT_TRUE(res4.value()); } @@ -240,34 +236,37 @@ TEST_F(two_phase_end_to_end_test, double_spend_transaction) { = m_sender->abandon_transaction(cbdc::transaction::tx_id(tx2.value())); ASSERT_TRUE(abandoned); - // Confirm to see if our balance is restored after abandoning - ASSERT_EQ(m_sender->balance(), 67UL); + // Confirm our balance is restored after abandoning. + // Note: this is 57 and not 67 because we /replaced/ an input in the + // transaction; the output which got overwritten was thus not + // returned to the spendable pool by abandoning the transaction. + ASSERT_EQ(m_sender->balance(), 57UL); } TEST_F(two_phase_end_to_end_test, invalid_transaction) { auto addr = m_receiver->new_address(); // Create the transaction normally - auto tx = m_sender->create_transaction(33, addr); + auto maybe_tx = m_sender->create_transaction(33, addr); + ASSERT_TRUE(maybe_tx.has_value()); - ASSERT_TRUE(tx.has_value()); + auto tx = maybe_tx.value(); + + tx.m_outputs.erase(tx.m_outputs.end() - 1); - // Make transaction unbalanced - tx.value().m_outputs[0].m_value = 1; - // Sign it again - m_sender->sign_transaction(tx.value()); + m_sender->sign_transaction(tx); - auto res = m_sender->send_transaction(tx.value()); + auto res = m_sender->send_transaction(tx); ASSERT_TRUE(res.has_value()); ASSERT_TRUE(res->m_tx_error.has_value()); ASSERT_EQ(res->m_tx_status, cbdc::sentinel::tx_status::static_invalid); ASSERT_TRUE( - std::holds_alternative( + std::holds_alternative( res->m_tx_error.value())); - auto tx_err = std::get( + auto tx_err = std::get( res->m_tx_error.value()); - ASSERT_EQ(tx_err, - cbdc::transaction::validation::tx_error_code::asymmetric_values); + ASSERT_EQ(tx_err.m_code, + cbdc::transaction::validation::proof_error_code::wrong_sum); } diff --git a/tests/integration/watchtower_integration_test.cpp b/tests/integration/watchtower_integration_test.cpp index b0a33b439..b41d57d8e 100644 --- a/tests/integration/watchtower_integration_test.cpp +++ b/tests/integration/watchtower_integration_test.cpp @@ -43,7 +43,7 @@ class watchtower_integration_test : public ::testing::Test { b0.m_transactions.push_back( cbdc::test::simple_tx({'t', 'x', 'a'}, {{'s', 'b'}, {'s', 'c'}}, - {{'u', 'd'}})); + {{{'u'}, {'d'}, {'y'}}})); auto pkt = std::make_shared(); auto ser = cbdc::buffer_serializer(*pkt); @@ -77,9 +77,10 @@ class watchtower_integration_test : public ::testing::Test { }; TEST_F(watchtower_integration_test, check_spent_unspent) { + auto id = cbdc::transaction::calculate_uhs_id({{'u'}, {'d'}, {'y'}}); auto got = m_wc->request_status_update(cbdc::watchtower::status_update_request{ - {{{'t', 'x', 'a'}, {{'s', 'b'}, {'u', 'd'}}}}}); + {{{'t', 'x', 'a'}, {{'s', 'b'}, id}}}}); auto want = cbdc::watchtower::status_request_check_success{ {{{'t', 'x', 'a'}, @@ -90,7 +91,7 @@ TEST_F(watchtower_integration_test, check_spent_unspent) { cbdc::watchtower::status_update_state{ cbdc::watchtower::search_status::unspent, m_best_height, - {'u', 'd'}}}}}}; + id}}}}}; ASSERT_EQ(*got, want); } diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index da38a1d86..cc06e98e9 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -24,6 +24,7 @@ add_executable(run_unit_tests archiver_test.cpp twophase_test.cpp validation_test.cpp wallet_test.cpp + uhs_test.cpp watchtower/block_cache_test.cpp watchtower/error_cache_test.cpp watchtower/tx_error_messages_test.cpp diff --git a/tests/unit/archiver_test.cpp b/tests/unit/archiver_test.cpp index 86c10eae5..2dac8c2aa 100644 --- a/tests/unit/archiver_test.cpp +++ b/tests/unit/archiver_test.cpp @@ -50,11 +50,11 @@ class ArchiverTest : public ::testing::Test { inp.m_prevout.m_tx_id = {val++}; inp.m_prevout.m_index = val++; inp.m_prevout_data.m_witness_program_commitment = {val++}; - inp.m_prevout_data.m_value = val++; + inp.m_spend_data = {{}, val++}; cbdc::transaction::output out; out.m_witness_program_commitment = {val++}; - out.m_value = val++; + out.m_range = cbdc::rangeproof_t{'\0'}; tx.m_inputs.push_back(inp); tx.m_outputs.push_back(out); diff --git a/tests/unit/atomizer_test.cpp b/tests/unit/atomizer_test.cpp index 5eaf95976..04466e8a6 100644 --- a/tests/unit/atomizer_test.cpp +++ b/tests/unit/atomizer_test.cpp @@ -46,11 +46,12 @@ TEST_F(atomizer_test, test_with_transactions) { inp.m_prevout.m_tx_id = {val++}; inp.m_prevout.m_index = val++; inp.m_prevout_data.m_witness_program_commitment = {val++}; - inp.m_prevout_data.m_value = val++; + inp.m_prevout_data.m_id = {val++}; + inp.m_spend_data = {{}, val++}; cbdc::transaction::output out; out.m_witness_program_commitment = {val++}; - out.m_value = val++; + out.m_range = cbdc::rangeproof_t{'\0'}; tx.m_inputs.push_back(inp); tx.m_outputs.push_back(out); @@ -72,19 +73,20 @@ TEST_F(atomizer_test, err_stxo_cache_depth_exceeded) { auto errs = m_atomizer->make_block().second; ASSERT_TRUE(errs.empty()); - auto tx0 = cbdc::test::simple_tx({'a'}, {{'b'}}, {{'c'}}); + auto tx0 = cbdc::test::simple_tx({'a'}, {{'b'}}, {{{'c'}, {'d'}, {'e'}}}); auto err = m_atomizer->insert(1, tx0, {0}); ASSERT_FALSE(err.has_value()); errs = m_atomizer->make_block().second; ASSERT_TRUE(errs.empty()); - auto tx1 = cbdc::test::simple_tx({'d'}, {{'e'}}, {{'f'}}); + auto tx1 = cbdc::test::simple_tx({'d'}, {{'e'}}, {{{'f'}, {'g'}, {'h'}}}); err = m_atomizer->insert(2, tx1, {0}); ASSERT_FALSE(err.has_value()); errs = m_atomizer->make_block().second; ASSERT_TRUE(errs.empty()); - auto tx_beyond_stxo_range = cbdc::test::simple_tx({'G'}, {{'h'}}, {{'i'}}); + auto tx_beyond_stxo_range + = cbdc::test::simple_tx({'G'}, {{'h'}}, {{{'i'}, {'j'}, {'k'}}}); err = m_atomizer->insert(0, tx_beyond_stxo_range, {0}); auto want = cbdc::watchtower::tx_error{{'G'}, @@ -99,20 +101,21 @@ TEST_F(atomizer_test, err_inputs_spent) { auto errs = m_atomizer->make_block().second; ASSERT_TRUE(errs.empty()); - auto tx0 = cbdc::test::simple_tx({'a'}, {{'B'}}, {{'c'}}); + auto tx0 = cbdc::test::simple_tx({'a'}, {{'B'}}, {{{'c'}, {'d'}, {'e'}}}); auto err = m_atomizer->insert(1, tx0, {0}); ASSERT_FALSE(err.has_value()); errs = m_atomizer->make_block().second; ASSERT_TRUE(errs.empty()); - auto tx1 = cbdc::test::simple_tx({'d'}, {{'E'}}, {{'f'}}); + auto tx1 = cbdc::test::simple_tx({'d'}, {{'E'}}, {{{'f'}, {'g'}, {'h'}}}); err = m_atomizer->insert(2, tx1, {0}); ASSERT_FALSE(err.has_value()); errs = m_atomizer->make_block().second; ASSERT_TRUE(errs.empty()); - auto tx_inputs_spent - = cbdc::test::simple_tx({'G'}, {{'E'}, {'h'}}, {{'i'}}); + auto tx_inputs_spent = cbdc::test::simple_tx({'G'}, + {{'E'}, {'h'}}, + {{{'i'}, {'j'}, {'k'}}}); err = m_atomizer->insert(2, tx_inputs_spent, {0, 1}); auto want = cbdc::watchtower::tx_error{ @@ -128,7 +131,9 @@ TEST_F(atomizer_test, err_incomplete) { auto [blk, errs] = m_atomizer->make_block(); ASSERT_TRUE(errs.empty()); - auto tx_incomplete = cbdc::test::simple_tx({'A'}, {{'b'}, {'c'}}, {{'d'}}); + auto tx_incomplete = cbdc::test::simple_tx({'A'}, + {{'b'}, {'c'}}, + {{{'d'}, {'e'}, {'f'}}}); auto err = m_atomizer->insert(1, tx_incomplete, {1}); ASSERT_FALSE(err.has_value()); @@ -137,7 +142,9 @@ TEST_F(atomizer_test, err_incomplete) { errs = m_atomizer->make_block().second; ASSERT_TRUE(errs.empty()); - auto tx1 = cbdc::test::simple_tx({'e'}, {{'f'}, {'g'}}, {{'h'}}); + auto tx1 = cbdc::test::simple_tx({'e'}, + {{'f'}, {'g'}}, + {{{'h'}, {'i'}, {'j'}}}); err = m_atomizer->insert(3, tx1, {0, 1}); ASSERT_FALSE(err.has_value()); diff --git a/tests/unit/coordinator/coordinator.cfg b/tests/unit/coordinator/coordinator.cfg index 6087c9d47..b1f3ec47d 100644 --- a/tests/unit/coordinator/coordinator.cfg +++ b/tests/unit/coordinator/coordinator.cfg @@ -9,6 +9,7 @@ shard0_count=1 shard0_start=0 shard0_end=255 shard0_loglevel="DEBUG" +shard0_audit_log="shard0_audit.txt" shard0_0_endpoint="127.0.0.1:8987" shard0_0_raft_endpoint="127.0.0.1:8988" shard0_0_readonly_endpoint="127.0.0.1:8989" diff --git a/tests/unit/coordinator/messages_test.cpp b/tests/unit/coordinator/messages_test.cpp index de7897f6a..0d85b0235 100644 --- a/tests/unit/coordinator/messages_test.cpp +++ b/tests/unit/coordinator/messages_test.cpp @@ -20,7 +20,7 @@ class coordinator_messages_test : public ::testing::Test { cbdc::test::compact_transaction m_tx{ cbdc::test::simple_tx({'a', 'b', 'c'}, {{'d', 'e', 'f'}, {'g', 'h', 'i'}}, - {{'x', 'y', 'z'}, {'z', 'z', 'z'}})}; + {{{'x'}, {'y'}, {'z'}}, {{'z'}, {'z'}, {'z'}}})}; }; TEST_F(coordinator_messages_test, command_header) { diff --git a/tests/unit/locking_shard/format_test.cpp b/tests/unit/locking_shard/format_test.cpp index b24fb5425..c5a34af8b 100644 --- a/tests/unit/locking_shard/format_test.cpp +++ b/tests/unit/locking_shard/format_test.cpp @@ -14,10 +14,14 @@ class locking_shard_format_test : public ::testing::Test { cbdc::buffer_serializer m_ser{m_target_packet}; cbdc::buffer_serializer m_deser{m_target_packet}; - cbdc::locking_shard::tx m_tx{ - cbdc::test::simple_tx({'a', 'b', 'c'}, - {{{'d', 'e', 'f'}}, {{'g', 'h', 'i'}}}, - {{{'x', 'y', 'z'}}, {{'z', 'z', 'z'}}})}; + cbdc::locking_shard::tx m_tx{}; + + void SetUp() override { + m_tx.m_tx.m_id = {'a', 'b', 'c'}; + m_tx.m_tx.m_inputs = {{'d', 'e', 'f'}, {'g', 'h', 'i'}}; + m_tx.m_tx.m_outputs = {{{'x'}, {'y'}, {'z'}}, {{'z'}, {'z'}, {'z'}}}; + m_tx.m_tx.m_attestations = {{{'a'}, {'b'}}}; + } }; TEST_F(locking_shard_format_test, tx) { diff --git a/tests/unit/locking_shard/locking_shard.cfg b/tests/unit/locking_shard/locking_shard.cfg index 6087c9d47..b1f3ec47d 100644 --- a/tests/unit/locking_shard/locking_shard.cfg +++ b/tests/unit/locking_shard/locking_shard.cfg @@ -9,6 +9,7 @@ shard0_count=1 shard0_start=0 shard0_end=255 shard0_loglevel="DEBUG" +shard0_audit_log="shard0_audit.txt" shard0_0_endpoint="127.0.0.1:8987" shard0_0_raft_endpoint="127.0.0.1:8988" shard0_0_readonly_endpoint="127.0.0.1:8989" diff --git a/tests/unit/message_test.cpp b/tests/unit/message_test.cpp index fb349161d..aae07ae71 100644 --- a/tests/unit/message_test.cpp +++ b/tests/unit/message_test.cpp @@ -38,8 +38,10 @@ TEST_F(PacketIOTest, outpoint) { TEST_F(PacketIOTest, output) { cbdc::transaction::output out; - out.m_value = 67; out.m_witness_program_commitment = {'t', 'a', 'f', 'm'}; + out.m_id = {'q', 'w', 'e', 'r'}; + out.m_value_commitment = {'o', 'p', '[', ']'}; + out.m_range = {'}', '{', 'P', 'O'}; m_ser << out; @@ -50,11 +52,17 @@ TEST_F(PacketIOTest, output) { } TEST_F(PacketIOTest, input) { + cbdc::transaction::output out; + out.m_witness_program_commitment = {'t', 'a', 'f', 'm'}; + out.m_id = {'q', 'w', 'e', 'r'}; + out.m_value_commitment = {'o', 'p', '[', ']'}; + out.m_range = {'}', '{', 'P', 'O'}; + cbdc::transaction::input in; - in.m_prevout_data.m_witness_program_commitment = {'a', 'x', 'o', 'p'}; - in.m_prevout_data.m_value = 83; + in.m_prevout_data = out; in.m_prevout.m_index = 1; in.m_prevout.m_tx_id = {'h', 'q', 'l', 'd'}; + in.m_spend_data = std::nullopt; m_ser << in; @@ -65,18 +73,21 @@ TEST_F(PacketIOTest, input) { } TEST_F(PacketIOTest, transaction) { + cbdc::transaction::output inout; + inout.m_witness_program_commitment = {'t', 'a', 'f', 'm'}; + inout.m_id = {'q', 'w', 'e', 'r'}; + inout.m_value_commitment = {'o', 'p', '[', ']'}; + inout.m_range = {'}', '{', 'P', 'O'}; + cbdc::transaction::input in; - in.m_prevout_data.m_witness_program_commitment = {'a', 'x', 'o', 'p'}; - in.m_prevout_data.m_value = 100; + in.m_prevout_data = inout; in.m_prevout.m_index = 1; in.m_prevout.m_tx_id = {'h', 'q', 'l', 'd'}; cbdc::transaction::output out0; - out0.m_value = 70; out0.m_witness_program_commitment = {'t', 'a', 'f', 'm'}; cbdc::transaction::output out1; - out1.m_value = 30; out1.m_witness_program_commitment = {'q', 'e', 'n', 'r'}; cbdc::transaction::full_tx tx; @@ -97,8 +108,8 @@ TEST_F(PacketIOTest, ctx_notify_request) { tx_notify.m_attestations.insert({'e', 'o', 'm', 'e'}); tx_notify.m_block_height = 33; tx_notify.m_tx.m_inputs.push_back({'a', 'x', 'o', 'p'}); - tx_notify.m_tx.m_uhs_outputs.push_back({'t', 'a', 'f', 'm'}); - tx_notify.m_tx.m_uhs_outputs.push_back({'q', 'e', 'n', 'r'}); + tx_notify.m_tx.m_outputs.push_back({{'t'}, {'a'}, {'h'}}); + tx_notify.m_tx.m_outputs.push_back({{'q'}, {'e'}, {'d'}}); tx_notify.m_tx.m_id = {'p', 'l', 'k', 'e'}; m_ser << tx_notify; @@ -112,14 +123,14 @@ TEST_F(PacketIOTest, ctx_notify_request) { TEST_F(PacketIOTest, block) { cbdc::transaction::compact_tx tx0; tx0.m_inputs.push_back({'a', 'x', 'o', 'p'}); - tx0.m_uhs_outputs.push_back({'t', 'a', 'f', 'm'}); - tx0.m_uhs_outputs.push_back({'q', 'e', 'n', 'r'}); + tx0.m_outputs.push_back({{'t'}, {'a'}, {'h'}}); + tx0.m_outputs.push_back({{'q'}, {'e'}, {'d'}}); tx0.m_id = {'p', 'l', 'k', 'e'}; cbdc::transaction::compact_tx tx1; tx1.m_inputs.push_back({'h', 'o', 'o', 'e'}); - tx1.m_uhs_outputs.push_back({'q', 'b', 'g', 'y'}); - tx1.m_uhs_outputs.push_back({'m', 'e', 'o', 'b'}); + tx1.m_outputs.push_back({{'t'}, {'a'}, {'h'}}); + tx1.m_outputs.push_back({{'q'}, {'e'}, {'d'}}); tx1.m_id = {'o', 'g', 'l', 'j'}; cbdc::atomizer::block block; @@ -137,8 +148,8 @@ TEST_F(PacketIOTest, block) { TEST_F(PacketIOTest, compact_transaction) { cbdc::transaction::compact_tx tx; tx.m_inputs.push_back({'h', 'o', 'o', 'e'}); - tx.m_uhs_outputs.push_back({'q', 'b', 'g', 'y'}); - tx.m_uhs_outputs.push_back({'m', 'e', 'o', 'b'}); + tx.m_outputs.push_back({{'q'}, {'b'}, {'p'}}); + tx.m_outputs.push_back({{'m'}, {'e'}, {'z'}}); tx.m_id = {'o', 'g', 'l', 'j'}; m_ser << tx; @@ -426,7 +437,7 @@ TEST_F(PacketIOTest, aggregate_tx_notification) { atn.m_oldest_attestation = 77; atn.m_tx = cbdc::test::simple_tx({'t', 'x', 'a'}, {{'a'}, {'b'}}, - {{'c'}, {'d'}}); + {{{'c'}, {'d'}, {'e'}}}); m_ser << atn; auto atn_deser = cbdc::atomizer::aggregate_tx_notification(); @@ -441,7 +452,7 @@ TEST_F(PacketIOTest, aggregate_tx_notify_request) { atn.m_oldest_attestation = 77; atn.m_tx = cbdc::test::simple_tx({'t', 'x', 'a'}, {{'a'}, {'b'}}, - {{'c'}, {'d'}}); + {{{'c'}, {'d'}, {'e'}}}); atns.m_agg_txs.push_back(atn); @@ -454,7 +465,10 @@ TEST_F(PacketIOTest, aggregate_tx_notify_request) { TEST_F(PacketIOTest, variant) { auto outpoint = cbdc::transaction::out_point{{'a', 'b', 'c', 'd'}, 1}; - auto output = cbdc::transaction::output{{'b', 'c'}, 100}; + auto output = cbdc::transaction::output{{'b'}, + {'c'}, + {'d'}, + cbdc::rangeproof_t{'e'}}; auto var = std::variant(outpoint); m_ser << var; diff --git a/tests/unit/parsec/agent/runners/lua/account_test.cpp b/tests/unit/parsec/agent/runners/lua/account_test.cpp index 13a151ead..753ab91f8 100644 --- a/tests/unit/parsec/agent/runners/lua/account_test.cpp +++ b/tests/unit/parsec/agent/runners/lua/account_test.cpp @@ -78,9 +78,13 @@ class account_test : public ::testing::Test { uint64_t m_init_sequence{0}; cbdc::privkey_t m_init_account_skey{1}; - std::unique_ptr - m_secp_context{secp256k1_context_create(SECP256K1_CONTEXT_SIGN), - &secp256k1_context_destroy}; + using secp256k1_context_destroy_type = void (*)(secp256k1_context*); + + std::unique_ptr + m_secp_context{secp256k1_context_create(SECP256K1_CONTEXT_NONE), + &secp256k1_context_destroy}; + cbdc::pubkey_t m_init_account_pkey{ cbdc::pubkey_from_privkey(m_init_account_skey, m_secp_context.get())}; }; @@ -115,12 +119,11 @@ TEST_F(account_test, pay_test) { m_init_account_skey.data()); cbdc::signature_t sig{}; - ret = secp256k1_schnorrsig_sign(m_secp_context.get(), - sig.data(), - sighash.data(), - &keypair, - nullptr, - nullptr); + ret = secp256k1_schnorrsig_sign32(m_secp_context.get(), + sig.data(), + sighash.data(), + &keypair, + nullptr); params.append(sig.data(), sig.size()); auto exp_from_acc_key = cbdc::buffer(); diff --git a/tests/unit/sentinel_2pc/controller_test.cpp b/tests/unit/sentinel_2pc/controller_test.cpp index 4a35b6bf5..4cade04c9 100644 --- a/tests/unit/sentinel_2pc/controller_test.cpp +++ b/tests/unit/sentinel_2pc/controller_test.cpp @@ -170,11 +170,14 @@ TEST_F(sentinel_2pc_test, digest_valid_transaction_network) { TEST_F(sentinel_2pc_test, tx_validation_test) { ASSERT_TRUE(m_ctl->init()); auto ctx = cbdc::transaction::compact_tx(m_valid_tx); + + using secp256k1_context_destroy_type = void (*)(secp256k1_context*); + auto secp = std::unique_ptr{ - secp256k1_context_create(SECP256K1_CONTEXT_SIGN - | SECP256K1_CONTEXT_VERIFY), + secp256k1_context_destroy_type>{ + secp256k1_context_create(SECP256K1_CONTEXT_NONE), &secp256k1_context_destroy}; + auto res = m_ctl->validate_transaction(m_valid_tx, [&](auto validation_res) { ASSERT_TRUE(validation_res.has_value()); diff --git a/tests/unit/serialization/stream_serializer_test.cpp b/tests/unit/serialization/stream_serializer_test.cpp index 25bedbeba..a13fc2254 100644 --- a/tests/unit/serialization/stream_serializer_test.cpp +++ b/tests/unit/serialization/stream_serializer_test.cpp @@ -32,7 +32,7 @@ class stream_serializer_test : public ::testing::Test { cbdc::test::compact_transaction m_tx{ cbdc::test::simple_tx({'a', 'b', 'c'}, {{'d', 'e', 'f'}, {'g', 'h', 'i'}}, - {{'x', 'y', 'z'}, {'z', 'z', 'z'}})}; + {{{'x'}, {'y'}, {'z'}}, {{'z'}, {'z'}, {'z'}}})}; static constexpr auto m_test_file = "stream_test_file.dat"; }; diff --git a/tests/unit/shard_test.cpp b/tests/unit/shard_test.cpp index e52fbac92..ea0758493 100644 --- a/tests/unit/shard_test.cpp +++ b/tests/unit/shard_test.cpp @@ -37,9 +37,9 @@ class shard_test : public ::testing::Test { cbdc::atomizer::block b1; b1.m_height = 1; b1.m_transactions.push_back( - cbdc::test::simple_tx({'a'}, {}, {{3}, {4}})); + cbdc::test::simple_tx({'a'}, {}, {{{3}, {4}, {5}}})); b1.m_transactions.push_back( - cbdc::test::simple_tx({'b'}, {}, {{5}, {6}})); + cbdc::test::simple_tx({'b'}, {}, {{{5}, {6}, {7}}})); m_shard.digest_block(b1); } @@ -47,7 +47,7 @@ class shard_test : public ::testing::Test { std::filesystem::remove_all(g_shard_test_dir); } - cbdc::shard::shard m_shard{{3, 8}}; + cbdc::shard::shard m_shard{{3, 'n'}}; }; TEST_F(shard_test, digest_block_non_contiguous) { @@ -59,8 +59,9 @@ TEST_F(shard_test, digest_block_non_contiguous) { TEST_F(shard_test, digest_tx_valid) { cbdc::transaction::compact_tx ctx{}; ctx.m_id = {'a'}; - ctx.m_inputs = {{0}, {3}, {6}, {100}}; - ctx.m_uhs_outputs = {{'x'}, {'y'}}; + ctx.m_inputs.push_back( + cbdc::transaction::calculate_uhs_id({{3}, {4}, {5}})); + ctx.m_outputs = {{{'c'}, {'d'}, {'e'}}, {{'i'}, {'j'}, {'k'}}}; auto res = m_shard.digest_transaction(ctx); ASSERT_TRUE( @@ -69,7 +70,7 @@ TEST_F(shard_test, digest_tx_valid) { cbdc::atomizer::tx_notify_request want{}; want.m_tx = ctx; - want.m_attestations = {1, 2}; + want.m_attestations = {0}; want.m_block_height = 1; ASSERT_EQ(got, want); @@ -79,7 +80,7 @@ TEST_F(shard_test, digest_tx_empty_inputs) { cbdc::transaction::compact_tx ctx{}; ctx.m_id = {'a'}; ctx.m_inputs = {}; - ctx.m_uhs_outputs = {{'x'}, {'y'}}; + ctx.m_outputs = {{{'c'}, {'d'}, {'e'}}, {{'i'}, {'j'}, {'k'}}}; auto res = m_shard.digest_transaction(ctx); ASSERT_TRUE(std::holds_alternative(res)); @@ -95,7 +96,7 @@ TEST_F(shard_test, digest_tx_inputs_dne) { cbdc::transaction::compact_tx ctx{}; ctx.m_id = {'a'}; ctx.m_inputs = {{0}, {7}, {8}, {100}}; - ctx.m_uhs_outputs = {{'x'}, {'y'}}; + ctx.m_outputs = {{{'c'}, {'d'}, {'e'}}, {{'i'}, {'j'}, {'k'}}}; auto res = m_shard.digest_transaction(ctx); ASSERT_TRUE(std::holds_alternative(res)); @@ -103,7 +104,7 @@ TEST_F(shard_test, digest_tx_inputs_dne) { cbdc::watchtower::tx_error want{ {'a'}, - cbdc::watchtower::tx_error_inputs_dne{{{7}, {8}}}}; + cbdc::watchtower::tx_error_inputs_dne{{{7}, {8}, {100}}}}; ASSERT_EQ(got, want); } @@ -111,16 +112,20 @@ TEST_F(shard_test, digest_tx_inputs_dne) { TEST_F(shard_test, digest_block_valid) { cbdc::atomizer::block b2; b2.m_height = 2; - b2.m_transactions.push_back( - cbdc::test::simple_tx({'c'}, {{1}, {3}, {4}, {11}}, {{7}})); - b2.m_transactions.push_back( - cbdc::test::simple_tx({'d'}, {{2}, {5}, {6}, {22}}, {{8}})); + b2.m_transactions.push_back(cbdc::test::simple_tx({'c'}, + {{1}, {3}, {4}, {11}}, + {{{7}, {8}, {9}}})); + b2.m_transactions.push_back(cbdc::test::simple_tx({'d'}, + {{2}, {5}, {6}, {22}}, + {{{8}, {9}, {10}}})); m_shard.digest_block(b2); cbdc::transaction::compact_tx valid_ctx{}; valid_ctx.m_id = {'a'}; - valid_ctx.m_inputs = {{0}, {7}, {100}, {8}}; - valid_ctx.m_uhs_outputs = {{'x'}, {'y'}}; + valid_ctx.m_inputs.push_back({0}); + valid_ctx.m_inputs.push_back( + cbdc::transaction::calculate_uhs_id({{3}, {4}, {5}})); + valid_ctx.m_outputs = {{{'c'}, {'d'}, {'e'}}, {{'i'}, {'j'}, {'k'}}}; auto valid_res = m_shard.digest_transaction(valid_ctx); ASSERT_TRUE( @@ -129,7 +134,7 @@ TEST_F(shard_test, digest_block_valid) { cbdc::atomizer::tx_notify_request valid_want{}; valid_want.m_tx = valid_ctx; - valid_want.m_attestations = {1, 3}; + valid_want.m_attestations = {1}; valid_want.m_block_height = 2; ASSERT_EQ(valid_got, valid_want); @@ -137,7 +142,7 @@ TEST_F(shard_test, digest_block_valid) { cbdc::transaction::compact_tx invalid_ctx{}; invalid_ctx.m_id = {'a'}; invalid_ctx.m_inputs = {{0}, {3}, {4}, {5}, {6}, {100}}; - invalid_ctx.m_uhs_outputs = {{'x'}, {'y'}}; + invalid_ctx.m_outputs = {{{'c'}, {'d'}, {'e'}}, {{'i'}, {'j'}, {'k'}}}; auto invalid_res = m_shard.digest_transaction(invalid_ctx); ASSERT_TRUE( std::holds_alternative(invalid_res)); @@ -145,7 +150,7 @@ TEST_F(shard_test, digest_block_valid) { cbdc::watchtower::tx_error invalid_want{ {'a'}, - cbdc::watchtower::tx_error_inputs_dne{{{3}, {4}, {5}, {6}}}}; + cbdc::watchtower::tx_error_inputs_dne{{{3}, {4}, {5}, {6}, {100}}}}; ASSERT_EQ(invalid_got, invalid_want); } diff --git a/tests/unit/transaction_test.cpp b/tests/unit/transaction_test.cpp index 8574ade18..c15891d67 100644 --- a/tests/unit/transaction_test.cpp +++ b/tests/unit/transaction_test.cpp @@ -12,11 +12,9 @@ TEST(CTransaction, input_from_output_basic) { cbdc::transaction::output send_out; cbdc::transaction::output receive_out; - send_out.m_value = 40; send_out.m_witness_program_commitment = {'a', 'b', 'c', 'd'}; tx.m_outputs.push_back(send_out); - receive_out.m_value = 60; receive_out.m_witness_program_commitment = {'e', 'f', 'g', 'h'}; tx.m_outputs.push_back(receive_out); @@ -24,13 +22,11 @@ TEST(CTransaction, input_from_output_basic) { ASSERT_TRUE(send_result); ASSERT_EQ(send_result->m_prevout.m_tx_id, cbdc::transaction::tx_id(tx)); ASSERT_EQ(send_result->m_prevout.m_index, uint32_t{0}); - ASSERT_EQ(send_result->m_prevout_data.m_value, uint32_t{40}); auto receive_result = cbdc::transaction::input_from_output(tx, 1); ASSERT_TRUE(receive_result); ASSERT_EQ(receive_result->m_prevout.m_tx_id, cbdc::transaction::tx_id(tx)); ASSERT_EQ(receive_result->m_prevout.m_index, uint32_t{1}); - ASSERT_EQ(receive_result->m_prevout_data.m_value, uint32_t{60}); } TEST(CTransaction, input_from_output_out_of_bounds) { diff --git a/tests/unit/twophase_test.cpp b/tests/unit/twophase_test.cpp index 600948572..75d495fca 100644 --- a/tests/unit/twophase_test.cpp +++ b/tests/unit/twophase_test.cpp @@ -32,9 +32,9 @@ TEST_F(TwoPhaseTest, test_one_shard) { auto txs = std::vector(); for(size_t i{0}; i < 1000; i++) { auto tx = cbdc::locking_shard::tx(); - auto uhs_id = cbdc::hash_t(); - std::memcpy(uhs_id.data(), &i, sizeof(i)); - tx.m_tx.m_uhs_outputs.push_back(uhs_id); + auto provenance = cbdc::hash_t(); + std::memcpy(provenance.data(), &i, sizeof(i)); + tx.m_tx.m_outputs.push_back({{}, {}, provenance}); txs.push_back(tx); } @@ -70,9 +70,9 @@ TEST_F(TwoPhaseTest, test_two_shards) { for(size_t i{0}; i < 1000; i++) { auto tx = cbdc::transaction::compact_tx(); std::memcpy(tx.m_id.data(), &i, sizeof(i)); - auto uhs_id = cbdc::hash_t(); - std::memcpy(uhs_id.data(), &i, sizeof(i)); - tx.m_uhs_outputs.push_back(uhs_id); + auto provenance = cbdc::hash_t(); + std::memcpy(provenance.data(), &i, sizeof(i)); + tx.m_outputs.push_back({{}, {}, provenance}); txs.push_back(tx); } @@ -115,10 +115,12 @@ TEST_F(TwoPhaseTest, test_one_shard_random) { const auto val = rnd(e); std::memcpy(&output1[j * 8], &val, sizeof(val)); } - tx.m_tx.m_uhs_outputs.push_back(output0); - tx.m_tx.m_uhs_outputs.push_back(output1); - outputs.push(output0); - outputs.push(output1); + auto uhs0 = cbdc::transaction::calculate_uhs_id({{}, {}, output0}); + auto uhs1 = cbdc::transaction::calculate_uhs_id({{}, {}, output1}); + tx.m_tx.m_outputs.push_back({{}, {}, output0}); + tx.m_tx.m_outputs.push_back({{}, {}, output1}); + outputs.push(uhs0); + outputs.push(uhs1); txs.push_back(tx); } @@ -144,8 +146,8 @@ TEST_F(TwoPhaseTest, test_one_shard_random) { const auto val = rnd(e); std::memcpy(&output1[j * 8], &val, sizeof(val)); } - tx.m_tx.m_uhs_outputs.push_back(output0); - tx.m_tx.m_uhs_outputs.push_back(output1); + tx.m_tx.m_outputs.push_back({{}, {}, output0}); + tx.m_tx.m_outputs.push_back({{}, {}, output1}); tx.m_tx.m_inputs.push_back(outputs.front()); outputs.pop(); tx.m_tx.m_inputs.push_back(outputs.front()); @@ -203,10 +205,12 @@ TEST_F(TwoPhaseTest, test_two_shards_random) { const auto val = rnd(e); std::memcpy(&output1[j * 8], &val, sizeof(val)); } - tx.m_uhs_outputs.push_back(output0); - tx.m_uhs_outputs.push_back(output1); - outputs.push(output0); - outputs.push(output1); + auto uhs0 = cbdc::transaction::calculate_uhs_id({{}, {}, output0}); + auto uhs1 = cbdc::transaction::calculate_uhs_id({{}, {}, output1}); + tx.m_outputs.push_back({{}, {}, output0}); + tx.m_outputs.push_back({{}, {}, output1}); + outputs.push(uhs0); + outputs.push(uhs1); txs.push_back(tx); } @@ -238,8 +242,8 @@ TEST_F(TwoPhaseTest, test_two_shards_random) { const auto val = rnd(e); std::memcpy(&output1[j * 8], &val, sizeof(val)); } - tx.m_uhs_outputs.push_back(output0); - tx.m_uhs_outputs.push_back(output1); + tx.m_outputs.push_back({{}, {}, output0}); + tx.m_outputs.push_back({{}, {}, output1}); tx.m_inputs.push_back(outputs.front()); outputs.pop(); tx.m_inputs.push_back(outputs.front()); @@ -299,10 +303,12 @@ TEST_F(TwoPhaseTest, test_two_shards_conflicting) { const auto val = rnd(e); std::memcpy(&output1[j * 8], &val, sizeof(val)); } - tx.m_uhs_outputs.push_back(output0); - tx.m_uhs_outputs.push_back(output1); - outputs.push(output0); - outputs.push(output1); + auto uhs0 = cbdc::transaction::calculate_uhs_id({{}, {}, output0}); + auto uhs1 = cbdc::transaction::calculate_uhs_id({{}, {}, output1}); + tx.m_outputs.push_back({{}, {}, output0}); + tx.m_outputs.push_back({{}, {}, output1}); + outputs.push(uhs0); + outputs.push(uhs1); txs.push_back(tx); } @@ -339,8 +345,8 @@ TEST_F(TwoPhaseTest, test_two_shards_conflicting) { const auto val = rnd(e); std::memcpy(&output1[j * 8], &val, sizeof(val)); } - tx.m_uhs_outputs.push_back(output0); - tx.m_uhs_outputs.push_back(output1); + tx.m_outputs.push_back({{}, {}, output0}); + tx.m_outputs.push_back({{}, {}, output1}); tx.m_inputs.push_back(outputs.front()); outputs.pop(); tx.m_inputs.push_back(outputs.front()); @@ -361,9 +367,10 @@ TEST_F(TwoPhaseTest, test_two_shards_conflicting) { for(size_t i{0}; i < txs.size(); i++) { ASSERT_TRUE((*res)[i]); auto& tx = txs[i]; - for(const auto& out : tx.m_uhs_outputs) { - auto res0 = *shard0->check_unspent(out); - auto res1 = *shard1->check_unspent(out); + for(const auto& out : tx.m_outputs) { + auto id = cbdc::transaction::calculate_uhs_id(out); + auto res0 = *shard0->check_unspent(id); + auto res1 = *shard1->check_unspent(id); ASSERT_TRUE((res0 || res1) && (res0 ^ res1)); } for(const auto& inp : tx.m_inputs) { diff --git a/tests/unit/uhs_test.cpp b/tests/unit/uhs_test.cpp new file mode 100644 index 000000000..238473ef6 --- /dev/null +++ b/tests/unit/uhs_test.cpp @@ -0,0 +1,80 @@ +// Copyright (c) 2021 MIT Digital Currency Initiative, +// Federal Reserve Bank of Boston +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "uhs/transaction/transaction.hpp" +#include "util/common/hash.hpp" +#include "util/common/hashmap.hpp" + +#include +#include +#include +#include + +class uhs_test : public ::testing::Test { + protected: + void SetUp() override { + std::filesystem::remove_all(m_db_dir); + leveldb::Options opt; + opt.create_if_missing = true; + + leveldb::DB* db_ptr{}; + const auto res = leveldb::DB::Open(opt, m_db_dir, &db_ptr); + ASSERT_TRUE(res.ok()); + this->m_db.reset(db_ptr); + } + + void TearDown() override { + std::filesystem::remove_all(m_db_dir); + } + + std::unique_ptr m_db; + leveldb::ReadOptions m_read_options; + leveldb::WriteOptions m_write_options; + + std::unordered_map + m_proofs{}; + + static constexpr const auto m_db_dir = "test_db"; +}; + +TEST_F(uhs_test, leveldb_roundtrip) { + cbdc::hash_t id{{'a', 'b', 'c', 'd'}}; + cbdc::transaction::compact_output o{{'e', 'f', 'g', 'h'}, + {'i', 'j', 'k', 'l'}, + {'m', 'n', 'o', 'p'}}; + + std::array k; + std::memcpy(k.data(), id.data(), k.size()); + leveldb::Slice Key(k.data(), k.size()); + + std::array v; + auto* vptr = v.data(); + std::memcpy(vptr, o.m_value_commitment.data(), o.m_value_commitment.size()); + vptr += o.m_value_commitment.size(); + std::memcpy(vptr, o.m_range.data(), o.m_range.size()); + vptr += o.m_range.size(); + std::memcpy(vptr, o.m_provenance.data(), o.m_provenance.size()); + + leveldb::Slice Val(v.data(), v.size()); + m_db->Put(this->m_write_options, Key, Val); + + std::string outval; + const auto& res = m_db->Get(this->m_read_options, Key, &outval); + EXPECT_FALSE(res.IsNotFound()); + EXPECT_EQ(std::memcmp(v.data(), outval.data(), v.size()), 0); +} + +TEST_F(uhs_test, map_roundtrip) { + cbdc::transaction::compact_output o{{'e', 'f', 'g', 'h'}, + {'i', 'j', 'k', 'l'}, + {'m', 'n', 'o', 'p'}}; + + cbdc::hash_t id{{'a', 'b', 'c', 'd'}}; + m_proofs.emplace(id, o); + const auto& p = m_proofs[id]; + EXPECT_EQ(p, o); +} diff --git a/tests/unit/validation_test.cpp b/tests/unit/validation_test.cpp index df5655726..8297903c2 100644 --- a/tests/unit/validation_test.cpp +++ b/tests/unit/validation_test.cpp @@ -13,11 +13,8 @@ class WalletTxValidationTest : public ::testing::Test { protected: void SetUp() override { - cbdc::transaction::wallet wallet1; - cbdc::transaction::wallet wallet2; - - auto mint_tx1 = wallet1.mint_new_coins(3, 100); - wallet1.confirm_transaction(mint_tx1); + m_mint_tx1 = wallet1.mint_new_coins(3, 100); + wallet1.confirm_transaction(m_mint_tx1); auto mint_tx2 = wallet2.mint_new_coins(1, 100); wallet2.confirm_transaction(mint_tx2); @@ -26,13 +23,20 @@ class WalletTxValidationTest : public ::testing::Test { = wallet1.send_to(200, wallet2.generate_key(), true).value(); } + cbdc::transaction::wallet wallet1; + cbdc::transaction::wallet wallet2; + + cbdc::transaction::full_tx m_mint_tx1{}; cbdc::transaction::full_tx m_valid_tx{}; cbdc::transaction::full_tx m_valid_tx_multi_inp{}; - std::unique_ptr - m_secp{secp256k1_context_create(SECP256K1_CONTEXT_SIGN - | SECP256K1_CONTEXT_VERIFY), + using secp256k1_context_destroy_type = void (*)(secp256k1_context*); + + std::unique_ptr + m_secp{secp256k1_context_create(SECP256K1_CONTEXT_NONE), &secp256k1_context_destroy}; + cbdc::privkey_t m_priv0{cbdc::hash_from_hex( "0000000000000001000000000000000000000000000000000000000000000000")}; cbdc::pubkey_t m_pub0{cbdc::pubkey_from_privkey(m_priv0, m_secp.get())}; @@ -43,6 +47,42 @@ class WalletTxValidationTest : public ::testing::Test { m_pub1}; }; +TEST_F(WalletTxValidationTest, ins_and_outs) { + for(size_t i = 1; i <= 3; ++i) { + cbdc::transaction::wallet a{}; + cbdc::transaction::wallet b{}; + + auto mint_tx = a.mint_new_coins(4, 5 * i); + a.confirm_transaction(mint_tx); + + auto one_to_one = a.send_to(5 * i, b.generate_key(), true); + EXPECT_TRUE(one_to_one.has_value()); + EXPECT_EQ(one_to_one.value().m_inputs.size(), 1UL); + EXPECT_EQ(one_to_one.value().m_outputs.size(), 1UL); + auto err = cbdc::transaction::validation::check_tx(one_to_one.value()); + EXPECT_FALSE(err.has_value()); + + auto one_to_two = a.send_to(5 * i - 1, b.generate_key(), true); + EXPECT_TRUE(one_to_two.has_value()); + EXPECT_EQ(one_to_two.value().m_inputs.size(), 1UL); + EXPECT_EQ(one_to_two.value().m_outputs.size(), 2UL); + err = cbdc::transaction::validation::check_tx(one_to_two.value()); + EXPECT_FALSE(err.has_value()); + + auto two_to_two = a.send_to(5 * i + 1, b.generate_key(), true); + EXPECT_TRUE(two_to_two.has_value()); + EXPECT_EQ(two_to_two.value().m_inputs.size(), 2UL); + EXPECT_EQ(two_to_two.value().m_outputs.size(), 2UL); + err = cbdc::transaction::validation::check_tx(two_to_two.value()); + EXPECT_FALSE(err.has_value()); + + // todo: should we disallow transactions which send only minted + // outputs, and have only one output? + // + // reasoning: such a tx guarantees an output blinding factor of 0 + } +} + TEST_F(WalletTxValidationTest, valid) { auto err = cbdc::transaction::validation::check_tx(m_valid_tx); ASSERT_FALSE(err.has_value()); @@ -96,27 +136,14 @@ TEST_F(WalletTxValidationTest, missing_witness) { } TEST_F(WalletTxValidationTest, zero_output) { - m_valid_tx.m_outputs[0].m_value = 0; - - auto err = cbdc::transaction::validation::check_tx(m_valid_tx); - - ASSERT_TRUE(err.has_value()); - ASSERT_TRUE( - std::holds_alternative( - err.value())); - - auto output_err - = std::get(err.value()); - - ASSERT_EQ(output_err.m_idx, uint64_t{0}); - ASSERT_EQ(output_err.m_code, - cbdc::transaction::validation::output_error_code::zero_value); + auto inval = wallet1.send_to(0, wallet2.generate_key(), true); + ASSERT_FALSE(inval.has_value()); } TEST_F(WalletTxValidationTest, duplicate_input) { m_valid_tx.m_inputs.emplace_back(m_valid_tx.m_inputs[0]); m_valid_tx.m_witness.emplace_back(m_valid_tx.m_witness[0]); - m_valid_tx.m_outputs[0].m_value *= 2; + m_valid_tx.m_out_spend_data.value()[0].m_value *= 2; auto err = cbdc::transaction::validation::check_tx(m_valid_tx); @@ -133,37 +160,12 @@ TEST_F(WalletTxValidationTest, duplicate_input) { cbdc::transaction::validation::input_error_code::duplicate); } -TEST_F(WalletTxValidationTest, invalid_input_prevout) { - m_valid_tx.m_inputs[0].m_prevout_data.m_value = 0; - auto err = cbdc::transaction::validation::check_tx(m_valid_tx); - - ASSERT_TRUE(err.has_value()); - ASSERT_TRUE( - std::holds_alternative( - err.value())); - - auto input_err - = std::get(err.value()); - - ASSERT_EQ(input_err.m_idx, uint64_t{0}); - ASSERT_EQ(input_err.m_code, - cbdc::transaction::validation::input_error_code::data_error); -} - TEST_F(WalletTxValidationTest, asymmetric_inout_set) { - m_valid_tx.m_outputs[0].m_value--; - auto err = cbdc::transaction::validation::check_tx(m_valid_tx); - + cbdc::transaction::compact_tx ctx(m_mint_tx1); + auto err = cbdc::transaction::validation::check_proof(ctx, {}); ASSERT_TRUE(err.has_value()); - ASSERT_TRUE( - std::holds_alternative( - err.value())); - - auto tx_err - = std::get(err.value()); - - ASSERT_EQ(tx_err, - cbdc::transaction::validation::tx_error_code::asymmetric_values); + ASSERT_EQ(err.value().m_code, + cbdc::transaction::validation::proof_error_code::wrong_sum); } TEST_F(WalletTxValidationTest, witness_missing_witness_program_type) { @@ -293,27 +295,6 @@ TEST_F(WalletTxValidationTest, check_to_string) { "TX error: No inputs"); } -TEST_F(WalletTxValidationTest, summation_overflow) { - auto inp_tx = m_valid_tx_multi_inp; - inp_tx.m_inputs[0].m_prevout_data.m_value - = std::numeric_limits::max(); - auto res = cbdc::transaction::validation::check_in_out_set(inp_tx); - ASSERT_TRUE(res.has_value()); - ASSERT_EQ( - res.value(), - cbdc::transaction::validation::tx_error( - cbdc::transaction::validation::tx_error_code::value_overflow)); - - auto out_tx = m_valid_tx_multi_inp; - out_tx.m_outputs[0].m_value = std::numeric_limits::max(); - res = cbdc::transaction::validation::check_in_out_set(inp_tx); - ASSERT_TRUE(res.has_value()); - ASSERT_EQ( - res.value(), - cbdc::transaction::validation::tx_error( - cbdc::transaction::validation::tx_error_code::value_overflow)); -} - TEST_F(WalletTxValidationTest, sign_verify_compact) { auto ctx = cbdc::transaction::compact_tx(m_valid_tx); auto att0 = ctx.sign(m_secp.get(), m_priv0); diff --git a/tests/unit/wallet_test.cpp b/tests/unit/wallet_test.cpp index a1ff2c7b6..b6eb1d999 100644 --- a/tests/unit/wallet_test.cpp +++ b/tests/unit/wallet_test.cpp @@ -25,31 +25,13 @@ class WalletTest : public ::testing::Test { }; TEST_F(WalletTest, update_balance_basic) { - cbdc::transaction::input in0; - in0.m_prevout.m_tx_id = {'e'}; - in0.m_prevout.m_index = 1; - in0.m_prevout_data.m_value = 14; - in0.m_prevout_data.m_witness_program_commitment = {'a'}; - - cbdc::transaction::input in1; - in1.m_prevout.m_tx_id = {'p'}; - in1.m_prevout.m_index = 0; - in1.m_prevout_data.m_value = 22; - in1.m_prevout_data.m_witness_program_commitment = {'j'}; - - m_wallet.confirm_inputs({in0, in1}); + m_wallet.confirm_transaction(m_wallet.mint_new_coins(1, 14)); + m_wallet.confirm_transaction(m_wallet.mint_new_coins(1, 22)); ASSERT_EQ(m_wallet.balance(), uint32_t{136}); } TEST_F(WalletTest, update_balance_double_credit) { - cbdc::transaction::input c0; - c0.m_prevout.m_tx_id = {'e'}; - c0.m_prevout.m_index = 1; - c0.m_prevout_data.m_value = 14; - c0.m_prevout_data.m_witness_program_commitment = {'a'}; - - m_wallet.confirm_inputs({c0}); - m_wallet.confirm_inputs({c0}); + m_wallet.confirm_transaction(m_wallet.mint_new_coins(1, 14)); ASSERT_EQ(m_wallet.balance(), uint32_t{114}); } @@ -62,7 +44,8 @@ TEST_F(WalletTest, export_send_input_basic) { ASSERT_EQ(receiver_inputs[0].m_prevout.m_tx_id, cbdc::transaction::tx_id(send_tx)); - ASSERT_EQ(receiver_inputs[0].m_prevout_data.m_value, 25U); + ASSERT_TRUE(receiver_inputs[0].m_spend_data.has_value()); + ASSERT_EQ(receiver_inputs[0].m_spend_data.value().m_value, 25U); } TEST_F(WalletTest, fan_out) { @@ -71,8 +54,10 @@ TEST_F(WalletTest, fan_out) { ASSERT_EQ(send_tx.m_outputs.size(), 20UL); auto witcom = cbdc::transaction::validation::get_p2pk_witness_commitment( target_addr); - for(const auto& out : send_tx.m_outputs) { - ASSERT_EQ(out.m_value, 5UL); + for(size_t i = 0; i < send_tx.m_outputs.size(); ++i) { + auto out = send_tx.m_outputs[i]; + auto spend = send_tx.m_out_spend_data.value()[i]; + ASSERT_EQ(spend.m_value, 5UL); ASSERT_EQ(out.m_witness_program_commitment, witcom); } auto receiver_inputs @@ -87,10 +72,10 @@ TEST_F(WalletTest, fan_out_change) { auto witcom = cbdc::transaction::validation::get_p2pk_witness_commitment( target_addr); for(size_t i = 1; i < send_tx.m_outputs.size(); i++) { - ASSERT_EQ(send_tx.m_outputs[i].m_value, 5UL); + ASSERT_EQ(send_tx.m_out_spend_data.value()[i].m_value, 5UL); ASSERT_EQ(send_tx.m_outputs[i].m_witness_program_commitment, witcom); } - ASSERT_EQ(send_tx.m_outputs[0].m_value, 5UL); + ASSERT_EQ(send_tx.m_out_spend_data.value()[0].m_value, 5UL); ASSERT_NE(send_tx.m_outputs[0].m_witness_program_commitment, witcom); auto receiver_inputs = cbdc::transaction::wallet::export_send_inputs(send_tx, target_addr); @@ -162,36 +147,21 @@ TEST_F(WalletMultiTxTest, too_many_outputs) { } TEST_F(WalletTest, spend_order) { - cbdc::transaction::input in0; - in0.m_prevout.m_tx_id = {'e'}; - in0.m_prevout.m_index = 1; - in0.m_prevout_data.m_value = 14; - in0.m_prevout_data.m_witness_program_commitment = {'a'}; - - cbdc::transaction::input in1; - in1.m_prevout.m_tx_id = {'p'}; - in1.m_prevout.m_index = 0; - in1.m_prevout_data.m_value = 22; - in1.m_prevout_data.m_witness_program_commitment = {'j'}; - - cbdc::transaction::input in2; - in2.m_prevout.m_tx_id = {'j'}; - in2.m_prevout.m_index = 0; - in2.m_prevout_data.m_value = 33; - in2.m_prevout_data.m_witness_program_commitment = {'j'}; - - m_wallet.confirm_inputs({in0}); - m_wallet.confirm_inputs({in1}); - m_wallet.confirm_inputs({in2}); + m_wallet.confirm_transaction(m_wallet.mint_new_coins(1, 14)); + m_wallet.confirm_transaction(m_wallet.mint_new_coins(1, 22)); + m_wallet.confirm_transaction(m_wallet.mint_new_coins(1, 33)); auto pubkey = m_wallet.generate_key(); m_wallet.send_to(1, 1, pubkey, false); auto tx1 = m_wallet.send_to(1, 1, pubkey, false); - ASSERT_EQ(tx1->m_inputs[0], in0); + ASSERT_TRUE(tx1.has_value()); + ASSERT_EQ(tx1->m_inputs[0].m_spend_data.value().m_value, 14UL); auto tx2 = m_wallet.send_to(1, pubkey, false); - ASSERT_EQ(tx2->m_inputs[0], in1); + ASSERT_TRUE(tx2.has_value()); + ASSERT_EQ(tx2->m_inputs[0].m_spend_data.value().m_value, 22UL); auto tx3 = m_wallet.send_to(1, 1, pubkey, false); - ASSERT_EQ(tx3->m_inputs[0], in2); + ASSERT_TRUE(tx3.has_value()); + ASSERT_EQ(tx3->m_inputs[0].m_spend_data.value().m_value, 33UL); } TEST_F(WalletTest, load_save) { diff --git a/tests/unit/watchtower/block_cache_test.cpp b/tests/unit/watchtower/block_cache_test.cpp index 3103f2ce5..f70dff619 100644 --- a/tests/unit/watchtower/block_cache_test.cpp +++ b/tests/unit/watchtower/block_cache_test.cpp @@ -14,11 +14,17 @@ class BlockCacheTest : public ::testing::Test { cbdc::atomizer::block b0; b0.m_height = 44; b0.m_transactions.push_back( - cbdc::test::simple_tx({'a'}, {{'b'}, {'c'}}, {{'d'}})); + cbdc::test::simple_tx({'a'}, + {{'b'}, {'c'}}, + {{{'d'}, {'e'}, {'f'}}})); b0.m_transactions.push_back( - cbdc::test::simple_tx({'E'}, {{'d'}, {'f'}}, {{'G'}})); + cbdc::test::simple_tx({'E'}, + {{'d'}, {'f'}}, + {{{'G'}, {'H'}, {'I'}}})); b0.m_transactions.push_back( - cbdc::test::simple_tx({'h'}, {{'i'}, {'j'}}, {{'k'}})); + cbdc::test::simple_tx({'h'}, + {{'i'}, {'j'}}, + {{{'k'}, {'l'}, {'m'}}})); m_bc.push_block(std::move(b0)); } @@ -33,52 +39,54 @@ TEST_F(BlockCacheTest, no_history) { } TEST_F(BlockCacheTest, spend_g) { - ASSERT_FALSE(m_bc.check_spent({'G'}).has_value()); - ASSERT_EQ(m_bc.check_unspent({'G'}).value().first, 44UL); - ASSERT_EQ(m_bc.check_unspent({'G'}).value().second, cbdc::hash_t{'E'}); + auto id = cbdc::transaction::calculate_uhs_id({{'G'}, {'H'}, {'I'}}); + ASSERT_FALSE(m_bc.check_spent(id).has_value()); + ASSERT_EQ(m_bc.check_unspent(id).value().first, 44UL); + ASSERT_EQ(m_bc.check_unspent(id).value().second, cbdc::hash_t{'E'}); cbdc::atomizer::block b1; b1.m_height = 45; b1.m_transactions.push_back( - cbdc::test::simple_tx({'L'}, {{'m'}, {'G'}}, {{'o'}})); + cbdc::test::simple_tx({'L'}, {{'m'}, id}, {{{'o'}, {'p'}, {'q'}}})); m_bc.push_block(std::move(b1)); - ASSERT_FALSE(m_bc.check_unspent({'G'}).has_value()); - ASSERT_EQ(m_bc.check_spent({'G'}).value().first, 45UL); - ASSERT_EQ(m_bc.check_spent({'G'}).value().second, cbdc::hash_t{'L'}); + ASSERT_FALSE(m_bc.check_unspent(id).has_value()); + ASSERT_EQ(m_bc.check_spent(id).value().first, 45UL); + ASSERT_EQ(m_bc.check_spent(id).value().second, cbdc::hash_t{'L'}); } TEST_F(BlockCacheTest, add_k_plus_1) { - ASSERT_FALSE(m_bc.check_spent({'G'}).has_value()); - ASSERT_EQ(m_bc.check_unspent({'G'}).value().first, 44UL); - ASSERT_EQ(m_bc.check_unspent({'G'}).value().second, cbdc::hash_t{'E'}); + auto id = cbdc::transaction::calculate_uhs_id({{'G'}, {'H'}, {'I'}}); + ASSERT_FALSE(m_bc.check_spent(id).has_value()); + ASSERT_EQ(m_bc.check_unspent(id).value().first, 44UL); + ASSERT_EQ(m_bc.check_unspent(id).value().second, cbdc::hash_t{'E'}); cbdc::atomizer::block b1; b1.m_height = 45; b1.m_transactions.push_back( - cbdc::test::simple_tx({'l'}, {{'m'}, {'n'}}, {{'o'}})); + cbdc::test::simple_tx({'l'}, {{'m'}, {'n'}}, {{{'o'}, {'p'}, {'q'}}})); b1.m_transactions.push_back( - cbdc::test::simple_tx({'p'}, {{'q'}, {'r'}}, {{'s'}})); + cbdc::test::simple_tx({'p'}, {{'q'}, {'r'}}, {{{'s'}, {'t'}, {'u'}}})); m_bc.push_block(std::move(b1)); cbdc::atomizer::block b2; b2.m_height = 46; b2.m_transactions.push_back( - cbdc::test::simple_tx({'t'}, {{'u'}, {'v'}}, {{'w'}})); + cbdc::test::simple_tx({'t'}, {{'u'}, {'v'}}, {{{'w'}, {'x'}, {'y'}}})); m_bc.push_block(std::move(b2)); - ASSERT_FALSE(m_bc.check_spent({'G'}).has_value()); - ASSERT_FALSE(m_bc.check_unspent({'G'}).has_value()); + ASSERT_FALSE(m_bc.check_spent(id).has_value()); + ASSERT_FALSE(m_bc.check_unspent(id).has_value()); cbdc::atomizer::block b3; b3.m_height = 47; b3.m_transactions.push_back( - cbdc::test::simple_tx({'X'}, {{'y'}, {'G'}}, {{'z'}})); + cbdc::test::simple_tx({'X'}, {{'y'}, id}, {{{'z'}, {'a'}, {'b'}}})); m_bc.push_block(std::move(b3)); - ASSERT_FALSE(m_bc.check_unspent({'G'}).has_value()); - ASSERT_EQ(m_bc.check_spent({'G'}).value().first, 47UL); - ASSERT_EQ(m_bc.check_spent({'G'}).value().second, cbdc::hash_t{'X'}); + ASSERT_FALSE(m_bc.check_unspent(id).has_value()); + ASSERT_EQ(m_bc.check_spent(id).value().first, 47UL); + ASSERT_EQ(m_bc.check_spent(id).value().second, cbdc::hash_t{'X'}); ASSERT_EQ(m_bc.best_block_height(), 47UL); } diff --git a/tests/unit/watchtower/watchtower_test.cpp b/tests/unit/watchtower/watchtower_test.cpp index 595ba5bd7..d4190c698 100644 --- a/tests/unit/watchtower/watchtower_test.cpp +++ b/tests/unit/watchtower/watchtower_test.cpp @@ -14,11 +14,17 @@ class WatchtowerTest : public ::testing::Test { cbdc::atomizer::block b0; b0.m_height = m_best_height; b0.m_transactions.push_back( - cbdc::test::simple_tx({'A'}, {{'b'}, {'C'}}, {{'d'}})); + cbdc::test::simple_tx({'A'}, + {{'b'}, {'C'}}, + {{{'d'}, {'e'}, {'f'}}})); b0.m_transactions.push_back( - cbdc::test::simple_tx({'E'}, {{'d'}, {'f'}}, {{'G'}})); + cbdc::test::simple_tx({'E'}, + {{'d'}, {'f'}}, + {{{'G'}, {'H'}, {'I'}}})); b0.m_transactions.push_back( - cbdc::test::simple_tx({'h'}, {{'i'}, {'j'}}, {{'k'}})); + cbdc::test::simple_tx({'h'}, + {{'i'}, {'j'}}, + {{{'k'}, {'l'}, {'m'}}})); m_watchtower.add_block(std::move(b0)); } @@ -41,8 +47,9 @@ TEST_F(WatchtowerTest, check_spent) { } TEST_F(WatchtowerTest, check_unspent) { + auto id = cbdc::transaction::calculate_uhs_id({{'G'}, {'H'}, {'I'}}); auto res = m_watchtower.handle_status_update_request( - cbdc::watchtower::status_update_request{{{{'E'}, {{'G'}}}}}); + cbdc::watchtower::status_update_request{{{{'E'}, {id}}}}); ASSERT_EQ(*res, (cbdc::watchtower::response{ @@ -51,7 +58,7 @@ TEST_F(WatchtowerTest, check_unspent) { {cbdc::watchtower::status_update_state{ cbdc::watchtower::search_status::unspent, 44, - {'G'}}}}}}})); + id}}}}}})); } TEST_F(WatchtowerTest, internal_error_tx) { diff --git a/tests/util.cpp b/tests/util.cpp index c5f6a9df2..44cfc7646 100644 --- a/tests/util.cpp +++ b/tests/util.cpp @@ -13,13 +13,13 @@ namespace cbdc::test { auto compact_transaction::operator==( const compact_transaction& tx) const noexcept -> bool { return m_id == tx.m_id && (m_inputs == tx.m_inputs) - && (m_uhs_outputs == tx.m_uhs_outputs); + && (m_outputs == tx.m_outputs); } compact_transaction::compact_transaction( const transaction::compact_tx& tx) { m_id = tx.m_id; - m_uhs_outputs = tx.m_uhs_outputs; + m_outputs = tx.m_outputs; m_inputs = tx.m_inputs; } @@ -40,11 +40,12 @@ namespace cbdc::test { auto simple_tx(const hash_t& id, const std::vector& ins, - const std::vector& outs) -> compact_transaction { + const std::vector& outs) + -> compact_transaction { compact_transaction tx{}; tx.m_id = id; tx.m_inputs = ins; - tx.m_uhs_outputs = outs; + tx.m_outputs = outs; return tx; } @@ -65,10 +66,12 @@ namespace cbdc::test { } void sign_tx(compact_transaction& tx, const privkey_t& key) { - auto secp = std::unique_ptr{ - secp256k1_context_create(SECP256K1_CONTEXT_SIGN), - &secp256k1_context_destroy}; + using secp256k1_context_destroy_type = void (*)(secp256k1_context*); + + std::unique_ptr + secp{secp256k1_context_create(SECP256K1_CONTEXT_NONE), + &secp256k1_context_destroy}; auto att = tx.sign(secp.get(), key); tx.m_attestations.insert(att); } diff --git a/tests/util.hpp b/tests/util.hpp index 55bcc9311..ff97a06a4 100644 --- a/tests/util.hpp +++ b/tests/util.hpp @@ -122,7 +122,8 @@ namespace cbdc::test { auto simple_tx(const hash_t& id, const std::vector& ins, - const std::vector& outs) -> compact_transaction; + const std::vector& outs) + -> compact_transaction; void print_sentinel_error( const std::optional& err); diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 000000000..ec65c7a60 --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,5 @@ +project(tools) + +add_subdirectory(audit) +add_subdirectory(bench) +add_subdirectory(shard-seeder) diff --git a/tools/audit/CMakeLists.txt b/tools/audit/CMakeLists.txt new file mode 100644 index 000000000..b030f07d0 --- /dev/null +++ b/tools/audit/CMakeLists.txt @@ -0,0 +1,11 @@ +project(audit) + +include_directories(../../src) + +add_executable(audit audit.cpp) +target_link_libraries(audit transaction + common + util + serialization + crypto + secp256k1) diff --git a/tools/audit/audit.cpp b/tools/audit/audit.cpp new file mode 100644 index 000000000..906ab35a0 --- /dev/null +++ b/tools/audit/audit.cpp @@ -0,0 +1,90 @@ +// Copyright (c) 2021 MIT Digital Currency Initiative, +// Federal Reserve Bank of Boston +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "util/common/commitment.hpp" +#include "util/common/config.hpp" +#include "util/serialization/format.hpp" +#include "util/serialization/util.hpp" +#include "uhs/transaction/validation.hpp" + +#include +#include + +using secp256k1_context_destroy_type = void (*)(secp256k1_context*); + +auto main(int argc, char** argv) -> int { + auto args = cbdc::config::get_args(argc, argv); + if(args.size() < 2) { + std::cout << "Usage: " << args[0] << " [config file]" << std::endl; + return -1; + } + + auto log = cbdc::logging::log(cbdc::logging::log_level::trace); + + auto cfg_or_err = cbdc::config::load_options(args[1]); + if(std::holds_alternative(cfg_or_err)) { + log.error("Error loading config file:", + std::get(cfg_or_err)); + return -1; + } + auto cfg = std::get(cfg_or_err); + + std::unique_ptr + secp{secp256k1_context_create(SECP256K1_CONTEXT_NONE), + &secp256k1_context_destroy}; + + auto expected = (cfg.m_seed_to - cfg.m_seed_from) * cfg.m_seed_value; + + auto audits = std::unordered_map< + uint64_t, + std::vector>(); + + for(auto& audit_file : cfg.m_shard_audit_logs) { + auto f = std::ifstream(audit_file); + if(!f.good()) { + log.error("Unable to open audit log"); + return -1; + } + + uint64_t epoch{}; + std::string commit_hex{}; + while(f >> epoch >> commit_hex) { + auto commitbuf = cbdc::buffer::from_hex(commit_hex); + auto comm + = cbdc::from_buffer(commitbuf.value()) + .value(); + auto maybe_summary = cbdc::deserialize_commitment(secp.get(), comm); + assert(maybe_summary.has_value()); + auto summary = maybe_summary.value(); + + auto it = audits.find(epoch); + if(it != audits.end()) { + auto& summaries = it->second; + summaries.emplace_back(std::move(summary)); + } else { + auto summaries + = std::vector(); + summaries.emplace_back(summary); + audits[epoch] = std::move(summaries); + } + } + } + + // todo: per-epoch: get a vector of all commitments, push_back + // circulation_commitment, check sum = 1 + for(auto& [epoch, summaries] : audits) { + auto success = + cbdc::transaction::validation::check_commitment_sum(summaries, {}, expected); + std::cout << "epoch " << epoch << ": " + << (success ? "PASS" : "FAIL") + << " with " + << summaries.size() << "/" << cfg.m_shard_audit_logs.size() + << " reporting" + << std::endl; + } + + return 0; +} diff --git a/tools/bench/atomizer-cli-watchtower.cpp b/tools/bench/atomizer-cli-watchtower.cpp index 38bdc6536..7c9aaaa1f 100644 --- a/tools/bench/atomizer-cli-watchtower.cpp +++ b/tools/bench/atomizer-cli-watchtower.cpp @@ -122,7 +122,7 @@ auto main(int argc, char** argv) -> int { log->warn("Failed to connect to watchtower."); } - cbdc::transaction::wallet wal; + cbdc::transaction::wallet wal(log); // Optionally Pre-seed wallet with deterministic UTXOs if(cfg.m_seed_from != cfg.m_seed_to) { @@ -342,9 +342,11 @@ auto main(int argc, char** argv) -> int { std::chrono::milliseconds(cfg.m_target_block_interval) * cfg.m_stxo_cache_depth); cbdc::transaction::compact_tx ctx{mint_tx}; + auto out_id + = cbdc::transaction::calculate_uhs_id(ctx.m_outputs[0]); watchtower_client->request_status_update( cbdc::watchtower::status_update_request{ - {{ctx.m_id, {ctx.m_uhs_outputs[0]}}}}); + {{ctx.m_id, {out_id}}}}); static constexpr auto mint_retry_delay = std::chrono::milliseconds(1000); std::this_thread::sleep_for(mint_retry_delay); @@ -399,8 +401,16 @@ auto main(int argc, char** argv) -> int { key_uhs_ids.reserve(pending_txs.size()); for(const auto& it : pending_txs) { cbdc::transaction::compact_tx ctx{it.second}; - key_uhs_ids.emplace( - std::make_pair(ctx.m_id, ctx.m_uhs_outputs)); + std::vector uhs_ids{}; + std::transform( + ctx.m_outputs.begin(), + ctx.m_outputs.end(), + std::back_inserter(uhs_ids), + [](const cbdc::transaction::compact_output& p) { + return cbdc::transaction::calculate_uhs_id(p); + }); + + key_uhs_ids.emplace(std::make_pair(ctx.m_id, uhs_ids)); } } watchtower_client->request_status_update( @@ -532,8 +542,15 @@ auto main(int argc, char** argv) -> int { key_uhs_ids.reserve(pending_txs.size()); for(const auto& it : pending_txs) { cbdc::transaction::compact_tx ctx{it.second}; - key_uhs_ids.emplace( - std::make_pair(ctx.m_id, ctx.m_uhs_outputs)); + std::vector uhs_ids{}; + std::transform( + ctx.m_outputs.begin(), + ctx.m_outputs.end(), + std::back_inserter(uhs_ids), + [](const cbdc::transaction::compact_output& p) { + return cbdc::transaction::calculate_uhs_id(p); + }); + key_uhs_ids.emplace(std::make_pair(ctx.m_id, uhs_ids)); } } diff --git a/tools/bench/parsec/lua/wallet.cpp b/tools/bench/parsec/lua/wallet.cpp index 55c8349fd..7b2cd3918 100644 --- a/tools/bench/parsec/lua/wallet.cpp +++ b/tools/bench/parsec/lua/wallet.cpp @@ -93,12 +93,11 @@ namespace cbdc::parsec { m_privkey.data()); cbdc::signature_t sig{}; - ret = secp256k1_schnorrsig_sign(m_secp.get(), - sig.data(), - sighash.data(), - &keypair, - nullptr, - nullptr); + ret = secp256k1_schnorrsig_sign32(m_secp.get(), + sig.data(), + sighash.data(), + &keypair, + nullptr); params.append(sig.data(), sig.size()); return params; } diff --git a/tools/bench/parsec/lua/wallet.hpp b/tools/bench/parsec/lua/wallet.hpp index f02b612b6..0e582e54f 100644 --- a/tools/bench/parsec/lua/wallet.hpp +++ b/tools/bench/parsec/lua/wallet.hpp @@ -75,9 +75,11 @@ namespace cbdc::parsec { cbdc::buffer m_pay_contract_key; cbdc::buffer m_account_key; + using secp256k1_context_destroy_type = void (*)(secp256k1_context*); + std::unique_ptr - m_secp{secp256k1_context_create(SECP256K1_CONTEXT_SIGN), + secp256k1_context_destroy_type> + m_secp{secp256k1_context_create(SECP256K1_CONTEXT_NONE), &secp256k1_context_destroy}; [[nodiscard]] auto make_pay_params(pubkey_t to, uint64_t amount) const diff --git a/tools/bench/twophase_gen.cpp b/tools/bench/twophase_gen.cpp index 0b86eb518..4cef3f747 100644 --- a/tools/bench/twophase_gen.cpp +++ b/tools/bench/twophase_gen.cpp @@ -77,10 +77,13 @@ auto main(int argc, char** argv) -> int { cfg.m_initial_mint_value); auto compact_mint_tx = cbdc::transaction::compact_tx(mint_tx); - auto secp = std::unique_ptr{ - secp256k1_context_create(SECP256K1_CONTEXT_SIGN), - &secp256k1_context_destroy}; + using secp256k1_context_destroy_type = void (*)(secp256k1_context*); + + + std::unique_ptr + secp{secp256k1_context_create(SECP256K1_CONTEXT_NONE), + &secp256k1_context_destroy}; for(size_t i = 0; i < cfg.m_attestation_threshold; i++) { auto att = compact_mint_tx.sign(secp.get(), cfg.m_sentinel_private_keys[i]); diff --git a/tools/shard-seeder/CMakeLists.txt b/tools/shard-seeder/CMakeLists.txt index 3bac1adb3..98ab867ad 100644 --- a/tools/shard-seeder/CMakeLists.txt +++ b/tools/shard-seeder/CMakeLists.txt @@ -5,7 +5,8 @@ find_package(Threads) include_directories(../../src ../../3rdparty ../../3rdparty/secp256k1/include) add_executable(shard-seeder shard-seeder.cpp) -target_link_libraries(shard-seeder transaction +target_link_libraries(shard-seeder locking_shard + transaction network common serialization diff --git a/tools/shard-seeder/shard-seeder.cpp b/tools/shard-seeder/shard-seeder.cpp index c2fba0c26..c5068e7cf 100644 --- a/tools/shard-seeder/shard-seeder.cpp +++ b/tools/shard-seeder/shard-seeder.cpp @@ -3,13 +3,17 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include "uhs/transaction/messages.hpp" #include "uhs/transaction/transaction.hpp" #include "uhs/transaction/validation.hpp" #include "uhs/transaction/wallet.hpp" +#include "util/common/commitment.hpp" #include "util/common/config.hpp" #include "util/serialization/buffer_serializer.hpp" #include "util/serialization/format.hpp" #include "util/serialization/ostream_serializer.hpp" +#include "uhs/twophase/locking_shard/locking_shard.hpp" +#include "uhs/twophase/locking_shard/format.hpp" #include #include @@ -23,6 +27,11 @@ static constexpr int leveldb_buffer_size static constexpr int write_batch_size = 450000; // well within the write buffer size +/// should be twice the bitcount of the range-proof's upper bound +/// +/// e.g., if proving things in the range [0, 2^64-1], it should be 128. +static const inline auto generator_count = 128; + auto get_2pc_uhs_key(const cbdc::hash_t& uhs_id) -> std::string { auto ret = std::string(); ret.resize(uhs_id.size() + 1); @@ -62,10 +71,31 @@ auto main(int argc, char** argv) -> int { logger.error("Seed private key not specified"); return -1; } - auto secp_context = std::unique_ptr( - secp256k1_context_create(SECP256K1_CONTEXT_SIGN), - &secp256k1_context_destroy); + using secp256k1_context_destroy_type = void (*)(secp256k1_context*); + + std::unique_ptr + secp_context{secp256k1_context_create(SECP256K1_CONTEXT_NONE), + &secp256k1_context_destroy}; + + struct GensDeleter { + explicit GensDeleter(secp256k1_context* ctx) : m_ctx(ctx) {} + + void operator()(secp256k1_bppp_generators* gens) const { + secp256k1_bppp_generators_destroy(m_ctx, gens); + } + + secp256k1_context* m_ctx; + }; + + static std::unique_ptr + bulletproof_gens{ + secp256k1_bppp_generators_create(secp_context.get(), + generator_count), + GensDeleter(secp_context.get())}; + + cbdc::random_source rng(cbdc::config::random_source); + auto pubkey = cbdc::pubkey_from_privkey(cfg.m_seed_privkey.value(), secp_context.get()); auto witness_commitment @@ -122,23 +152,38 @@ auto main(int argc, char** argv) -> int { res.ToString()); return; } - - auto tx = wal.create_seeded_transaction(0).value(); auto batch_size = 0; leveldb::WriteBatch batch; for(size_t tx_idx = 0; tx_idx != num_utxos; tx_idx++) { - tx.m_inputs[0].m_prevout.m_index = tx_idx; + auto tx = wal.create_seeded_transaction(tx_idx).value(); cbdc::transaction::compact_tx ctx(tx); - const cbdc::hash_t& output_hash = ctx.m_uhs_outputs[0]; + const cbdc::hash_t& output_hash + = cbdc::transaction::calculate_uhs_id( + ctx.m_outputs[0]); if(output_hash[0] >= shard_start && output_hash[0] <= shard_end) { std::array hash_arr{}; std::memcpy(hash_arr.data(), output_hash.data(), sizeof(output_hash)); + static constexpr auto aux_size + = sizeof(ctx.m_outputs[0].m_value_commitment); + static constexpr auto rng_size + = sizeof(ctx.m_outputs[0].m_range); + std::array proofs_arr{}; + + std::memcpy(proofs_arr.data(), + ctx.m_outputs[0].m_value_commitment.data(), + aux_size); + std::memcpy(proofs_arr.data() + aux_size, + ctx.m_outputs[0].m_range.data(), + rng_size); leveldb::Slice hash_key(hash_arr.data(), output_hash.size()); - batch.Put(hash_key, leveldb::Slice()); + leveldb::Slice ProofVal(proofs_arr.data(), + proofs_arr.size()); + + batch.Put(hash_key, ProofVal); batch_size++; if(batch_size >= write_batch_size) { db->Write(wopt, &batch); @@ -158,14 +203,17 @@ auto main(int argc, char** argv) -> int { // write dummy size auto ser = cbdc::ostream_serializer(out); ser << count; - auto tx = wal.create_seeded_transaction(0).value(); for(size_t tx_idx = 0; tx_idx != num_utxos; tx_idx++) { - tx.m_inputs[0].m_prevout.m_index = tx_idx; + auto tx = wal.create_seeded_transaction(tx_idx).value(); cbdc::transaction::compact_tx ctx(tx); - const cbdc::hash_t& output_hash = ctx.m_uhs_outputs[0]; - if(output_hash[0] >= shard_start - && output_hash[0] <= shard_end) { - ser << output_hash; + const auto& compact_out = ctx.m_outputs[0]; + const auto& id + = cbdc::transaction::calculate_uhs_id(compact_out); + const auto& elem = cbdc::locking_shard::locking_shard:: + uhs_element{compact_out, 0}; + if(id[0] >= shard_start && id[0] <= shard_end) { + ser << id; + ser << elem; count++; } }