Skip to content

Commit

Permalink
Merge pull request #537 from evoskuil/master
Browse files Browse the repository at this point in the history
Implement set_prevouts and change from link to height.
  • Loading branch information
evoskuil authored Jan 12, 2025
2 parents 0bfe2ea + 318a462 commit 295884d
Show file tree
Hide file tree
Showing 6 changed files with 466 additions and 43 deletions.
2 changes: 1 addition & 1 deletion include/bitcoin/database/impl/primitives/arraymap.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ bool CLASS::read(const memory_ptr& ptr, const Link& link,
iostream stream{ offset, size - position };
reader source{ stream };

if constexpr (!is_slab) { BC_DEBUG_ONLY(source.set_limit(Size);) }
if constexpr (!is_slab) { BC_DEBUG_ONLY(source.set_limit(Size * element.count());) }
return element.from_data(source);
}

Expand Down
12 changes: 8 additions & 4 deletions include/bitcoin/database/impl/query/confirm.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,10 @@ error::error_t CLASS::spent_prevout(const point_link& link, index index,
// But if we hold the spend.pk/prevout.tx we can just read the
// spend.hash/index, so we don't need to store the index, and we need to
// read the spend.hash anyway, so index is free (no paging). So that's now
// just spend[4] + tx[4], back to 8 bytes (19GB).
// just spend[4] + tx[4], back to 8 bytes (19GB). But getting spends is
// relatively cheap, just search txs[hash] and navigate puts. The downside
// is the extra search and puts is > 2x prevouts and can't be pruned.
// The upside is half the prevout size (read/write/page) and store increase.

// Iterate points by point hash (of output tx) because may be conflicts.
auto point = store_.point.it(get_point_key(link));
Expand Down Expand Up @@ -705,13 +708,14 @@ bool CLASS::set_unstrong(const header_link& link) NOEXCEPT
}

TEMPLATE
bool CLASS::set_prevouts(const header_link&, const block&) NOEXCEPT
bool CLASS::set_prevouts(size_t height, const block& block) NOEXCEPT
{
// ========================================================================
const auto scope = store_.get_transactor();

// TODO: implement.
return {};
// Clean single allocation failure (e.g. disk full).
const table::prevout::record_put_ref prevouts{ {}, block };
return store_.prevout.put(height, prevouts);
// ========================================================================
}

Expand Down
2 changes: 1 addition & 1 deletion include/bitcoin/database/query.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ class query
/// Block association relies on strong (confirmed or pending).
bool set_strong(const header_link& link) NOEXCEPT;
bool set_unstrong(const header_link& link) NOEXCEPT;
bool set_prevouts(const header_link& link, const block& block) NOEXCEPT;
bool set_prevouts(size_t height, const block& block) NOEXCEPT;
code block_confirmable(const header_link& link) const NOEXCEPT;
////code tx_confirmable(const tx_link& link, const context& ctx) const NOEXCEPT;
code unspent_duplicates(const header_link& coinbase,
Expand Down
143 changes: 130 additions & 13 deletions include/bitcoin/database/tables/caches/prevout.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,44 +29,161 @@ namespace database {
namespace table {

/// prevout is an array map index of previous outputs by block.
/// The coinbase flag is merged into the tx field, reducing it's domain.
/// Masking is from the right in order to accomodate non-integral domain.
struct prevout
: public array_map<schema::prevout>
{
using tx = linkage<schema::tx>;
using spend = linkage<schema::spend_>;
using array_map<schema::prevout>::arraymap;
static constexpr size_t offset = sub1(to_bits(tx::size));

struct record
: public schema::prevout
{
inline bool coinbase() const NOEXCEPT
{
return system::get_right(value, offset);
}

inline tx::integer output_tx_fk() const NOEXCEPT
{
return system::set_right(value, offset, false);
}

inline void set(bool coinbase, tx::integer output_tx_fk) NOEXCEPT
{
using namespace system;
BC_ASSERT_MSG(!get_right(output_tx_fk, offset), "overflow");
value = set_right(output_tx_fk, offset, coinbase);
}

inline bool from_data(reader& source) NOEXCEPT
{
coinbase = source.read_byte();
spend_fk = source.read_little_endian<spend::integer, spend::size>();
output_tx_fk = source.read_little_endian<tx::integer, tx::size>();
value = source.read_little_endian<tx::integer, tx::size>();
BC_ASSERT(!source || source.get_read_position() == minrow);
return source;
}

inline bool to_data(finalizer& sink) const NOEXCEPT
{
sink.write_byte(coinbase);
sink.write_little_endian<spend::integer, spend::size>(spend_fk);
sink.write_little_endian<tx::integer, tx::size>(output_tx_fk);
sink.write_little_endian<tx::integer, tx::size>(value);
BC_ASSERT(!sink || sink.get_write_position() == minrow);
return sink;
}

inline bool operator==(const record& other) const NOEXCEPT
{
return coinbase == other.coinbase
&& spend_fk == other.spend_fk
&& output_tx_fk == other.output_tx_fk;
return coinbase() == other.coinbase()
&& output_tx_fk() == other.output_tx_fk();
}

tx::integer value{};
};

struct record_put_ref
: public schema::prevout
{
static constexpr tx::integer merge(bool coinbase,
tx::integer output_tx_fk) NOEXCEPT
{
using namespace system;
BC_ASSERT_MSG(!get_right(output_tx_fk, offset), "overflow");
return system::set_right(output_tx_fk, offset, coinbase);
}

// This is called once by put(), and hides base count().
inline link count() const NOEXCEPT
{
const auto spends = block.spends();
BC_ASSERT(spends < link::terminal);
return system::possible_narrow_cast<link::integer>(spends);
}

inline bool to_data(finalizer& sink) const NOEXCEPT
{
const auto txs = *block.transactions_ptr();
if (txs.size() <= one)
{
// Empty or coinbase only implies no spends.
sink.invalidate();
}
else
{
const auto write_spend = [&](const auto& in) NOEXCEPT
{
// Sets terminal sentinel for block-internal spends.
const auto value = in->metadata.inside ? tx::terminal :
merge(in->metadata.coinbase, in->metadata.parent);

sink.write_little_endian<tx::integer, tx::size>(value);
};

const auto write_tx = [&](const auto& tx) NOEXCEPT
{
const auto& ins = tx->inputs_ptr();
return std::for_each(ins->begin(), ins->end(), write_spend);
};

std::for_each(std::next(txs.begin()), txs.end(), write_tx);
}

BC_ASSERT(!sink || (sink.get_write_position() == count() * minrow));
return sink;
}

const system::chain::block& block{};
};

struct record_get
: public schema::prevout
{
// This is called once by assert, and hides base class count().
inline link count() const NOEXCEPT
{
BC_ASSERT(values.size() < link::terminal);
return system::possible_narrow_cast<link::integer>(values.size());
}

inline bool from_data(reader& source) NOEXCEPT
{
// Values must be set to read size (i.e. using knowledge of spends).
std::for_each(values.begin(), values.end(), [&](auto& value) NOEXCEPT
{
value = source.read_little_endian<tx::integer, tx::size>();
});

BC_ASSERT(!source || source.get_read_position() == count() * minrow);
return source;
}

inline bool inside(size_t index) const NOEXCEPT
{
BC_ASSERT(index < count());

// Identifies terminal sentinel as block-internal spend.
return values.at(index) == tx::terminal;
}

inline bool coinbase(size_t index) const NOEXCEPT
{
BC_ASSERT(index < count());

// Inside are always reflected as coinbase.
return system::get_right(values.at(index), offset);
}

inline tx::integer output_tx_fk(size_t index) const NOEXCEPT
{
BC_ASSERT(index < count());

// Inside are always mapped to terminal.
return inside(index) ? tx::terminal :
system::set_right(values.at(index), offset, false);
}

bool coinbase{};
spend::integer spend_fk{};
tx::integer output_tx_fk{};
// Spend count is derived in confirmation by summing block.txs.puts.
std::vector<tx::integer> values{};
};
};

Expand Down
32 changes: 16 additions & 16 deletions include/bitcoin/database/tables/schema.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,22 @@ namespace schema
/// Cache tables.
/// -----------------------------------------------------------------------

// record arraymap
struct prevout
{
static constexpr size_t pk = schema::spend_;
static constexpr size_t minsize =
////schema::bit + // merged bit into tx.
schema::tx;
static constexpr size_t minrow = minsize;
static constexpr size_t size = minsize;

// This is hidden by derivatives, to avoid virtual methods.
inline linkage<pk> count() const NOEXCEPT { return one; }
static_assert(minsize == 4u);
static_assert(minrow == 4u);
};

// slab hashmap
struct validated_bk
{
Expand Down Expand Up @@ -355,22 +371,6 @@ namespace schema
static_assert(minrow == 23u);
};

// record arraymap
struct prevout
{
static constexpr size_t pk = schema::spend_;
////static constexpr size_t sk = zero;
static constexpr size_t minsize =
schema::bit + // TODO: merge bit.
schema::spend_ +
schema::tx;
static constexpr size_t minrow = minsize;
static constexpr size_t size = minsize;
static constexpr linkage<pk> count() NOEXCEPT { return 1; }
static_assert(minsize == 9u);
static_assert(minrow == 9u);
};

// slab hashmap
struct neutrino
{
Expand Down
Loading

0 comments on commit 295884d

Please sign in to comment.