Archive of the libbitcoin Slack #general channel.
std::atomic_ref<T> workaround. Also rewrote the store serialization for tx. This allocates all inputs and all outputs in one chunk each, and writes them contiguously, as opposed to iterating over the allocation. Given that input/output both cache size during wired deserialization there's no cost to computing the variable offsets. I also found that I was using the script and witness size methods previously, to store the size prefixes. But those aren't cached, so that was a bit wasteful - iteration over all opcodes. So allocations are reduced by around 5 billion or so, and those impose an exclusive lock over respective tables, which is the only material source of lock contention, so that's really good.cpu-speed x threads and shani and sufficient RAM/SSD speed it will be faster. shani would save about 5-10% on the maxi (80% of the tx and signature hashing cost).// Expand synchronizes keys with ins_fk, entries dropped into same offset.
// Allocate contiguous points (at sequential keys matching ins_fk).
if (!store_.point.expand(ins_fk + inputs))
return error::tx_point_allocate;
const auto ptr = store_.point.get_memory();
if (tx.is_coinbase())
{
for (const auto& in: *ins)
if (!store_.point.put(ptr, ins_fk++,
table::point::compose(in->point()), table::point::record{}))
return error::tx_point_put;
}
else
{
// Write all table elements (row not linked).
auto fk = ins_fk;
for (const auto& in: *ins)
if (!store_.point.set(ptr, fk++,
table::point::compose(in->point()), table::point::record{}))
return error::tx_point_set;
// Exclusive search and index commit.
std::unique_lock lock{ mutex_ };
// TODO: write a record for every match found.
fk = ins_fk;
for (const auto& in: *ins)
if (store_.point.exists(ptr,
table::point::compose(in->point())))
return error::confirmed_double_spend;
// Link all table elements (row linked).
fk = ins_fk;
for (const auto& in: *ins)
if (!store_.point.commit(ptr, fk++,
table::point::compose(in->point())))
return error::tx_point_commit;
}
aabbccdd.......:0, in the point table the hashmap key is aabbccdd?aabbccdd and the 0 . This requires a mathematical operation called hash combination.namespace std
{
template<>
struct hash<bc::system::chain::point>
{
size_t operator()(const bc::system::chain::point& value) const NOEXCEPT
{
return bc::system::hash_combine(value.index(),
bc::system::unique_hash(value.hash()));
}
};
template<>
struct hash<bc::system::chain::point_cref>
{
std::size_t operator()(
const bc::system::chain::point_cref& value) const NOEXCEPT
{
return std::hash<bc::system::chain::point>{}(value.get());
}
};// Combine hash values, such as a pair of djb2_hash outputs.
INLINE constexpr size_t hash_combine(size_t left, size_t right) NOEXCEPT
{
return left ^ shift_left(right, one);
}template <size_t Size>
INLINE constexpr size_t unique_hash(const data_array<Size>& key) NOEXCEPT
{
constexpr auto size = std::min(Size, sizeof(size_t));
// This optimization breaks data portability (by endianness).
// Could be modified to use an endian conversion vs. simple copy.
size_t value{};
std::copy_n(key.begin(), size, system::byte_cast(value).begin());
return value;
}// Objectives: deterministic, uniform distribution, efficient computation.
INLINE constexpr size_t djb2_hash(const data_slice& data) NOEXCEPT
{
// Nothing special here except that it tested well against collisions.
auto hash = 5381_size;
// Efficient sum of ((hash * 33) + byte) for all bytes.
for (const auto byte: data)
hash = (shift_left(hash, 5_size) + hash) + byte;
return hash;
}
INLINE constexpr size_t hash_combine(size_t left, size_t right,
size_t) NOEXCEPT
{
////constexpr auto prime1 = possible_narrow_cast<size_t>(0x9e3779b97f4a7c15_u64);
////constexpr auto prime2 = possible_narrow_cast<size_t>(0x517cc1b727220a95_u64);
////auto first = left;
////first ^= shift_right(first, 23);
////first *= prime1;
////first ^= shift_right(first, 19);
////auto second = right;
////second ^= shift_right(second, 13);
////second *= prime2;
////second ^= shift_right(second, 31);
////first ^= second;
////first += seed;
////first ^= shift_right(first, 17);
////first *= prime1;
////first ^= shift_right(first, 29);
////return first;
return left ^ shift_left(right, one);
}
hash:index values excluding coinbases). There is no need to maintain an accumulation of utxos or even the unspent points, just to know what are the points that have more than one entry in the table (typically there are none). There is no need to search the table to know this, because in theory it's most efficiently done upon insertion. At that point (download phase) we have the most free CPU available. But the point table is over 100GB and it's totally not feasible to search it for every insert, or at any other point, for every spend, on a low memory machine, due to paging cost.transaction and bk_validate, and is_strong, even to obtain points - as those are navigated from the prevout parent tx and point index.k value (e.g. 7), we can project the false positive rate. The actual hash function values present almost a perfect Poisson distribution (based on the use of sha256 values), but not perfectly (due to mixing in the point index). And in fact the uniformity is better than Poisson:


using screen_t = std_array<uin32_t, 16>;
using matrix_t = std_array<screen_t, 16>;
constexpr matrix_t matrix
{
screen_t
{
// 1
0b0000'1111111111111111111111111111
},
{
// 2
0b0000'1111111111111100000000000000,
0b0000'0000000000000011111111111111
},
{
// 3
0b0000'1111111111000000000000000000,
0b0000'0000000000000011111111100000,
0b0000'0000000000111100000000011111
},
{
// 4
0b0000'1111111000000000000000000000,
0b0000'0000000000000011111110000000,
0b0000'0000000000111100000000011100,
0b0000'0000000111000000000001100011
},
{
// 5
0b0000'1111110000000000000000000000,
0b0000'0000000000000011111100000000,
0b0000'0000000000111100000000011000,
0b0000'0000000111000000000001100000,
0b0000'0000001000000000000010000111
},
{
// 6
0b0000'1111100000000000000000000000,
0b0000'0000000000000011111000000000,
0b0000'0000000000111100000000010000,
0b0000'0000000111000000000001100000,
0b0000'0000001000000000000010000110,
0b0000'0000010000000000000100001001
},
{
// 7
0b0000'1111000000000000000000000000,
0b0000'0000000000000011110000000000,
0b0000'0000000000111100000000000000,
0b0000'0000000111000000000001000000,
0b0000'0000001000000000000010000110,
0b0000'0000010000000000000100001001,
0b0000'0000100000000000001000110000
},
{
// 8
0b0000'1111000000000000000000000000,
0b0000'0000000000000011110000000000,
0b0000'0000000000111100000000000000,
0b0000'0000000111000000000001000000,
0b0000'0000001000000000000010000100,
0b0000'0000010000000000000100001000,
0b0000'0000100000000000001000100000,
0b0000'0000000000000000000000010011
},
{
// 9
0b0000'1111000000000000000000000000,
0b0000'0000000000000011100000000000,
0b0000'0000000000111000000000000000,
0b0000'0000000111000000000000000000,
0b0000'0000001000000000000010000100,
0b0000'0000010000000000000100001000,
0b0000'0000100000000000001000100000,
0b0000'0000000000000000000000010011,
0b0000'0000000000000100010001000000
},
{
// 10
0b0000'1110000000000000000000000000,
0b0000'0000000000000011100000000000,
0b0000'0000000000111000000000000000,
0b0000'0000000111000000000000000000,
0b0000'0000001000000000000010000100,
0b0000'0000010000000000000100001000,
0b0000'0000100000000000001000100000,
0b0000'0000000000000000000000010011,
0b0000'0000000000000100010000000000,
0b0000'0001000000000000000001000000
},
{
// 11
0b0000'1110000000000000000000000000,
0b0000'0000000000000011100000000000,
0b0000'0000000000111000000000000000,
0b0000'0000000111000000000000000000,
0b0000'0000001000000000000010000100,
0b0000'0000010000000000000100001000,
0b0000'0000100000000000001000000000,
0b0000'0000000000000000000000010010,
0b0000'0000000000000100010000000000,
0b0000'0001000000000000000001000000,
0b0000'0000000000000000000000100001
},
{
// 12
0b0000'1110000000000000000000000000,
0b0000'0000000000000011100000000000,
0b0000'0000000000111000000000000000,
0b0000'0000000111000000000000000000,
0b0000'0000001000000000000010000000,
0b0000'0000010000000000000100000000,
0b0000'0000100000000000001000000000,
0b0000'0000000000000000000000010010,
0b0000'0000000000000100010000000000,
0b0000'0001000000000000000001000000,
0b0000'0000000000000000000000100001,
0b0000'0000000000000000000000001100
},
{
// 13
0b0000'1110000000000000000000000000,
0b0000'0000000000000011100000000000,
0b0000'0000000000110000000000000000,
0b0000'0000000110000000000000000000,
0b0000'0000001000000000000010000000,
0b0000'0000010000000000000100000000,
0b0000'0000100000000000001000000000,
0b0000'0000000000000000000000010010,
0b0000'0000000000000100010000000000,
0b0000'0001000000000000000001000000,
0b0000'0000000000000000000000100001,
0b0000'0000000000000000000000001100,
0b0000'0000000001001000000000000000
},
{
// 14
0b0000'1100000000000000000000000000,
0b0000'0000000000000011000000000000,
0b0000'0000000000110000000000000000,
0b0000'0000000110000000000000000000,
0b0000'0000001000000000000010000000,
0b0000'0000010000000000000100000000,
0b0000'0000100000000000001000000000,
0b0000'0000000000000000000000010010,
0b0000'0000000000000100010000000000,
0b0000'0001000000000000000001000000,
0b0000'0000000000000000000000100001,
0b0000'0000000000000000000000001100,
0b0000'0000000001001000000000000000,
0b0000'0010000000000000100000000000
},
{
// 15
0b0000'1100000000000000000000000000,
0b0000'0000000000000011000000000000,
0b0000'0000000000110000000000000000,
0b0000'0000000110000000000000000000,
0b0000'0000001000000000000010000000,
0b0000'0000010000000000000100000000,
0b0000'0000100000000000001000000000,
0b0000'0000000000000000000000010010,
0b0000'0000000000000100010000000000,
0b0000'0001000000000000000001000000,
0b0000'0000000000000000000000100001,
0b0000'0000000000000000000000001100,
0b0000'0000000001001000000000000000,
0b0000'0010000000000000000000000000,
0b0000'0000000000000000100000000000
},
{
// 16
0b0000'1100000000000000000000000000,
0b0000'0000000000000011000000000000,
0b0000'0000000000110000000000000000,
0b0000'0000000110000000000000000000,
0b0000'0000001000000000000010000000,
0b0000'0000010000000000000100000000,
0b0000'0000100000000000001000000000,
0b0000'0000000000000000000000010010,
0b0000'0000000000000100010000000000,
0b0000'0001000000000000000001000000,
0b0000'0000000000000000000000100001,
0b0000'0000000000000000000000001100,
0b0000'0000000001000000000000000000,
0b0000'0010000000000000000000000000,
0b0000'0000000000000000100000000000,
0b0000'0000000000001000000000000000
}
};class CTransaction
{
public:
...
int nLockTime;
...
bool IsFinal() const
{
if (nLockTime == 0 || nLockTime < nBestHeight)
return true;
foreach(const CTxIn& txin, vin)
if (!txin.IsFinal())
return false;
return true;
}
...
};
class CTxIn
{
public:
...
bool IsFinal() const
{
return (nSequence == UINT_MAX);
}
...
};nLockTime type to unsigned.
class CTransaction
{
public:
...
unsigned int nLockTime;
...
bool IsFinal(int64 nBlockTime=0) const
{
// Time based nLockTime implemented in 0.1.6,
// do not use time based until most 0.1.5 nodes have upgraded.
if (nBlockTime == 0)
nBlockTime = GetAdjustedTime();
if (nLockTime == 0)
return true;
if (nLockTime < (nLockTime < 500000000 ? nBestHeight : nBlockTime))
return true;
foreach(const CTxIn& txin, vin)
if (!txin.IsFinal())
return false;
return true;
}
...
};// To help v0.1.5 clients who would see it as negative number. please delete this later.
if (nLockTime > INT_MAX)
return error("AcceptTransaction() : not accepting nLockTime beyond 2038");So absolute block locktime was always active, it was forked in v0.2.0 to include time
cc0b4c3b62367a2aebe5fc1f4d0ed4b97e9c2ac9 . This change was a soft fork which activated on block 31,000. Had it been enforced by consensus before, the change to include time would have been a hard fork.b075bbf9862df41cdf5265026ba787b7d0fecb07. Technically a hard fork, but at this time all new releases were hard forks due to OP_VER anyways.and that was immediately applied to all blocks.
cb420a1dfc23d3c11c5281ed8f7ae003c2f61594 (February 2010). Before that it was only applied from block 36,001, starting with commit b075bbf9862df41cdf5265026ba787b7d0fecb07. Before that it was only applied from block 31,001.class CTransaction
{
public:
...
int nLockTime;
...
bool IsFinal() const
{
if (nLockTime == 0 || nLockTime < nBestHeight)
return true;
foreach(const CTxIn& txin, vin)
if (!txin.IsFinal())
return false;
return true;
}
...
};
class CTxIn
{
public:
...
bool IsFinal() const
{
return (nSequence == UINT_MAX);
}
...
};nLockTime type to unsigned.
class CTransaction
{
public:
...
unsigned int nLockTime;
...
bool IsFinal(int64 nBlockTime=0) const
{
// Time based nLockTime implemented in 0.1.6,
// do not use time based until most 0.1.5 nodes have upgraded.
if (nBlockTime == 0)
nBlockTime = GetAdjustedTime();
if (nLockTime == 0)
return true;
if (nLockTime < (nLockTime < 500000000 ? nBestHeight : nBlockTime))
return true;
foreach(const CTxIn& txin, vin)
if (!txin.IsFinal())
return false;
return true;
}
...
};// To help v0.1.5 clients who would see it as negative number. please delete this later.
if (nLockTime > INT_MAX)
return error("AcceptTransaction() : not accepting nLockTime beyond 2038"); // Both indexes are screen selector, as each new screen adds one mask.
const auto mask = matrix[selector][selector];
// Merge incremented selector, current sieve, and new fingerprint.
sieve = bit_or
(
shift_left(selector, screen_bits),
bit_and
(
selector_mask,
bit_or
(
bit_and(print, mask),
bit_and(sieve, bit_not(mask))
)
)
); (edited) // Compare masked fingerprint to masked sieve, for all masks of the screen.
for (auto mask = zero; mask <= selector; ++mask)
if (bit_and(print, matrix[selector][mask]) ==
bit_and(sieve, matrix[selector][mask]))
return true;So absolute block locktime was always active, it was forked in v0.2.0 to include time
cc0b4c3b62367a2aebe5fc1f4d0ed4b97e9c2ac9 . This change was a soft fork which activated on block 31,000. Had it been enforced by consensus before, the change to include time would have been a hard fork.and that was immediately applied to all blocks.
cb420a1dfc23d3c11c5281ed8f7ae003c2f61594 (February 2010). Before that it was only applied from block 36,001, starting with commit b075bbf9862df41cdf5265026ba787b7d0fecb07. Before that it was only applied from block 31,001.consteval function to generate the mask table for the fancy filter, and compressed the table by ~50%, as it is a sparsely-populated 2 dimensional matrix. The final implementation matches exactly the manually-generated table (below), though it also adapts based on template arguments for the filter size. Extensive text coverage also. The implementation is simple and performant. Hopefully it does what I think it will do, integrating into the hashmap template and the adding to the point table now.
point table. There is some in the validated_bk table, as we check block state for validation/confirmation. However this is minor, and I am planning to replace this and the validated_tx and txs tables with the new arraymap (which was devised for prevout caching, but will advantage any other table indexed search-indexed with another table's surrogate key - since these are sequential). However the filtering will significantly reduce negative search cost for general purpose hashmap queries.sieve class into a static class (basically a templatized namespace) because it holds no dynamic state. I've completed that, which simplified the code and maybe gave tiny perf boost. I also updated tests, refactored, and added better runtime instrumentation to measure the filter perf.
I then got into the runtime debugger and quickly confirmed that the filter was not actually having an effect (which was nice to confirm). I was not returning the right value from the header updating method. I also handled an error condition that I mentioned had been commented out. These required a minor refactor of the hashhead and hashmap templates. This made things pertaining to the filter quite a bit simpler.
It's now all together and checked in, and I'm running for the first time at full milestone speed and concurrency on the maxi and with good instrumentation. Passing block 450k I've got a couple quick data points:
369 / (322040940 + 369) <= this is ratio of point searches to total point writes passing block 400k. That's 1.14 / 1,000,000 = 0.00014% . Passing 450k it's 1757 / (484908338 + 1757) or 0.00036% . And 450k the baseline time is generally around 650 seconds, and with this change (which includes 8 GiB of extra header), we're at 656 seconds. Well within typical deviations.0.0013% search rate. CPU 28% which is also typical.0.0022% search rate.1.223, with 2,627,207,611 points therefore 2,147,471,459 hashmap buckets. The sieve doubles the hashhead cell size from 4 bytes to 8, and therefore uses an extra 8GiB of header. This should be reduceable by up to half without a huge hit, just by increasing the table load factor, though I need to revert to modulo bucket selection to get this. The sieve can then also get twice as efficient by adding one more screen bit, by borrowing it from the bucket link.0.21% and estimates half that for average probability across the build of the table. The above ratios are comparable to that halving, or a target of 0.1%.0.014% search rate.0.024% search rate - about 75% of points written. CPU still 28%.0.037% search rate - about 85% of points written.1,552,144 point searches (duplicate detection) out of 2,626,357,610 non-coinbase points, for an actual average false positive rate of 0.0591%. Not too far off the ballpark estimate of cutting the full table calculation of 0.21% in half. Given that we know the actual buck distributions, and know that the sieve hash function is both independent and even, the full table calculation should be exact, but currently I'm only able to measure the cumulative rate, not the rate when the table is full.0.0083% (vs. 0.0013% at half the LF).0.077% search rate.0.22% search rate, which is about 1 in 500 average (much higher immediate). But still a minute ahead of the 1 in 1000 simulation. Also, this is a copy of the previous analysis on effect of increasing LF. It's pretty moderate, and with 2.446 LF I'd expect to see it end up around 0.8% over the run.
0.34% now, 3 minutes behind baseline.0.53% , which means probably over 1.0% toward the end. Over 1/100 is a bit too high, but will def come down with the LF.0.53% but this better represents the more costly late behavior. It didn't start falling behind the benchmark until around the 0.8% instantaneous false positive rate rate. That many searches leads to thrashing.0.14% vs. 0.53% with the higher load factor, so right on. This also drops the late stage LF by the same amount, so from 1.1% of inserts, to 0.29%. This is significant, and all comes at the cost of increasing the heads by 2.2GiB. We won't see the impact of that until tomorrow morning. The total head size for validation will be 18.2GiB, whereas in yesterday's high LF run it was 16.0GiB. The earlier run that bogged down with a 1.22LF was too big, at 24GiB heads. So I'm hopeful that this will be much better, but in any case we should be able to squeeze enough out of the heads to get to yesterday's 16.0GiB if we have to, and that will certainly be good.doubles table (point duplicates)Link::terminal and schema to allow for bit-aligned link terminals