Archive of the libbitcoin Slack #general channel.
arraymap primitive has an expanding head, but was never previously read past it's limit due to the nature of the confirmation prevout query that consumed it. I changed two more tables to arraymap (validated_bk and txs) and in these cases the tables are read for items that may not be found just due to lack of ordering. When the element searched is greater than the head allocation there is a read protection fault. When writing there is no problem because a write would just expand the head. The low level file does not guard against such overflows, because at such a low level that's a big performance expense for all tables, most of which wouldn't benefit from it. The arrayhead test cases didn't cover it, because they were derived from the hashmap tests, and hashhead doesn't resize. So test added, simple fix within the arrayhead. https://github.com/libbitcoin/libbitcoin-database/pull/629std::mutex with std::unique_lock to synchronize between the candidate chain and confirmed chain. Basically we cannot allow popping of blocks from the candidate chain during the organization of blocks into the confirmed chain, since the candidate chain provides order for the confirmed chain.
It would be possible to push this into a store atomic read of the full candidate branch, which hash the slight drawback of holding this set in a redundant temporary structure during organization. This would have to be captured before determining if the branch is strong. There would be a very small performance hit, but that would allow the implementation to not expose any synchronization primitives at the application layer.With this in place the bottlenecks have to move to things like network, block serialization, validation parallelism overheads… Development time would be better spent on optimizing those...
I find skipping amount validation more concerning (potential inflation bugs)
It’s basically free so it should do that for sure. That said, there are so many lost/abandoned coins that if miners (with developer collusion) create a chain that unjustly rewards them, they’ll just steal coins rather than inflate.
x add Tagged Hash
x add libsecp2561 schnorr functions
x add Signature Parse
x add coverage::bip341_rule
x add coverage::bip342_rule
x upd script tests for coverage::all_rules
x add coverage::hash_default
x add transaction::is_sighash_valid(uint8_t)
x add operation::is_success(opcode)
x op_if (conditionally "minimal if")
x op_notif (conditionally "minimal if")
x op_checksigadd (conditionally invoke)
x op_checkmultisig (conditionally disable)
x op_checkmultisigverify (conditionally disable)
x op_checksigadd (conditionally enable)
- op_checksigadd (implement)
- op_checksig (conditionally modify)
- op_checksigverify (conditionally modify)
op_checksigadd (conditionally serialize)
Signature Hashing
Signature Hashing Cache
Signature Validation Invocation
Taproot Witness Program Invocation
Script Path Spending Parse and Validation
Script Run Loop
Tapscript Indication (unset BIP342 for non-tapscript program flags)
Stacks element count limit (max_unified_stack_size also applied to initial stack)
Sigops per-script budget (sigops in tapscripts do not count towards block limit)
Script size limit (max_script_size does not apply)
Non-push opcodes limit (max_counted_ops does not apply)
Tagged Hash Midstate (optimization)
Activation (simplified) (edited)x add tagged hash
x add libsecp2561 schnorr functions
x add signature parse
x add flags::bip341_rule
x add flags::bip342_rule
x add coverage::hash_default
x upd coverage::all_rules script tests
x ren script_version::zero to segwit
x add script_version::taproot
x add operation::is_success(opcode)
x add transaction::is_sighash_valid(uint8_t)
x op_if (conditionally "minimal if")
x op_notif (conditionally "minimal if")
x op_checksigadd (conditionally invoke)
x op_checkmultisig (conditionally disable)
x op_checkmultisigverify (conditionally disable)
x op_checksigadd (conditionally enable)
x op_checksigadd (conditionally serialize)
x op_checksigadd (implement)
x op_checksig (conditionally modify)
x op_checksigverify (conditionally modify)
x Interpreter run loop changes
x Tapscript indication (unset BIP342 in program when not tapscript)
x Stacks element count limit (max_unified_stack_size also applied to initial stack)
x Script size limit (max_script_size does not apply)
x Non-push opcodes limit (max_counted_ops does not apply)
x Sigops per-script budget (sigops in tapscripts do not count towards block limit)
x Add success, failure (and roller) detection to script parser
x Cache success, failure (and roller) on script
x Modify script(operations) construction to set success, failure (and roller)
x Early success and fail on script run (from cache)
x Differentiate cached is_prefail and is_prevalid for tapscript execution.
- Signature Validation Invocation
Signature Hashing Cache
Signature Hashing
- Taproot Witness Program Invocation
Taproot Witness Program Extraction
Script Path Spending Parse and Validation
Tagged Hash Midstate (optimization)
Activation (simplified)success opcode, (2) invalid opcode, and (3) op_roll existence in all scripts during parse. This includes reading off the wire and from the store, though it's very efficient since the iteration is already necessary. Previously we were only testing for invalid during parse, and op_roll was computed on the fly prior to script execution. This saved one bit (bool) per script, which wasn't really worth it, since the two flags would just be packed into one byte by the compiler. And since we otherwise need another iteration for the success codes prior to tapscript execution, it was an easy choice to just put these all into the script parser, which basically looks like this (emplace invokes chain::op(reader) to perform an in-place construction on the operation stack).
while (!source.is_exhausted())
{
ops_.emplace_back(source);
const auto& op = ops_.back();
easier_ |= op.is_success();
failer_ |= op.is_invalid();
roller_ |= op.is_roller();
}invalid opcode has changed (reduced to just op_verif and op_vernotif in tapscript) and the unconditional success codes apply only in tapscript, it seemed that this would present some complexity and require additional parse results.
But it turns out that above is all that is required. The last set of comments here explains how it works out, which was quite elegant.
TEMPLATE
INLINE error::script_error_t CLASS::
initialize() const NOEXCEPT
{
const auto bip141 = is_enabled(flags::bip141_rule);
const auto bip342 = is_enabled(flags::bip342_rule);
// Apply stack element limit (520) to initial witness [bip141][bip342].
if (bip141 && witness_ && !chain::witness::is_push_size(*witness_))
return error::invalid_witness_stack;
// Script size limit (10,000) [0.3.7+], removed [bip342].
if (!bip342 && script_->is_oversized())
return error::invalid_script_size;
// Stacks element limit (1,000) applied to initial stack [bip342].
if (bip342 && is_stack_overflow())
return error::invalid_stack_size;
// Succeed if any success code, one overrides all codes [bip342].
if (bip342 && script_->is_prevalid())
return error::prevalid_script;
// Fail if last op underflow, lower priority than success codes [bip342].
if (script_->is_underflow())
return error::invalid_script;
// Fail if any op invalid (invalid codes reduced in tapscript).
// Should be after underflow check since underflow is also an invalid op.
// Promoted success codes are not reachable here due to is_prevalid above.
// So only op_verif/op_vernotif (unpromoted invalids) are caught here for
// tapscript, otherwise is_prevalid bypassed and all invalids caught here.
if (script_->is_prefail())
return error::prefail_script;
return error::script_success;
} (edited)interpreter::run() method, which runs the script:
TEMPLATE
code CLASS::
run() NOEXCEPT
{
// Enforce initial limits, determine early success or failure.
if (const auto ec = state::initialize())
return (ec == error::prevalid_script) ? error::script_success : ec;
for (auto it = state::begin(); it != state::end(); ++it)
{
const auto& op = *it;
if (op.is_oversized())
return error::invalid_push_data_size;
if (!state::ops_increment(op))
return error::invalid_operation_count;
if (state::if_(op))
{
if (const auto ec = run_op(it))
return ec;
if (state::is_stack_overflow())
return error::invalid_stack_size;
}
}
return state::is_balanced() ? error::script_success :
error::invalid_stack_scope;
}op_success codes. None are renamed, it's just another method to test whether a code is in the unconditional success category, and one invocation of that test in the above initialize() method. Otherwise nothing changes, and the codes retain their previous state (invalid or reserved) in non-tapscript executions. So just as there are no actual op_invalid codes, and no op_conditional codes, and some reserved codes that are not named op_reserved, unconditional success is just a new category - confused by the BIP presentation. /// Sets initial state to Hash initialization vector.
constexpr accumulator() NOEXCEPT;
/// Accepts an initial state and count of blocks accumulated by it.
constexpr accumulator(const state_t& state, size_t blocks) NOEXCEPT;constexpr, we can simply construct the midstate under consteval and initialize the accumulator with it for each streaming tagged hash.tapscript bool and tapleaf hash values):
bool transaction::version1_sighash(hash_digest& out,
const input_iterator& input, const script& script, uint64_t value,
uint8_t sighash_flags) const NOEXCEPT
{
constexpr uint8_t epoch{};
// TODO: obtain tapscript indicator and access to tapleaf hash.
constexpr bool tapscript{};
constexpr hash_digest tapleaf{};
const auto& in = **input;
const auto& annex = in.witness().annex();
// Mask anyone_can_pay, and set hash_all by default.
// sighash_flags previously verified (see schnorr_split).
const auto flag = mask_sighash(sighash_flags);
const auto anyone = is_anyone_can_pay(sighash_flags);
const auto single = (flag == coverage::hash_single);
const auto all = (flag == coverage::hash_all);
// Create tagged hash writer.
stream::out::fast stream{ out };
hash::sha256t::fast<"TapSighash"> sink{ stream };
sink.write_byte(epoch);
sink.write_byte(sighash_flags);
sink.write_4_bytes_little_endian(version_);
sink.write_4_bytes_little_endian(locktime_);
if (!anyone)
{
sink.write_bytes(single_hash_points());
sink.write_bytes(single_hash_amounts());
sink.write_bytes(single_hash_scripts());
sink.write_bytes(single_hash_sequences());
}
if (all)
{
sink.write_bytes(single_hash_outputs());
}
sink.write_byte(spend_type_v1(annex, tapscript));
if (anyone)
{
in.point().to_data(sink);
sink.write_8_bytes_little_endian(value);
script.to_data(sink, true);
sink.write_4_bytes_little_endian(in.sequence());
}
else
{
sink.write_4_bytes_little_endian(input_index(input));
}
if (annex)
{
sink.write_variable(annex.size());
sink.write_bytes(annex.hash());
}
if (single)
{
const auto index = input_index(input);
if (output_overflow(index))
return false;
sink.write_bytes(outputs_->at(index)->hash());
}
// Additional for tapscript [bip342].
if (tapscript)
{
sink.write_bytes(tapleaf);
sink.write_byte(to_value(key_version::tapscript));
sink.write_4_bytes_little_endian(subscript_v1(script));
}
sink.flush();
return true;
}annex object to provide the above clean abstraction. On order to make that fully general and safe it required a shared_ptr per input, and computationally it's almost neutral. Really cleans up the above code though. The tagged hash writer [hash::sha256t::fast<"TapSighash">] was also a nice simplification, with no performance compromises.
Still have to complete the hash caching for this. Also some related refactoring allowed me to share code with the original and segwit signature hashing functions. I split up a few files also, which helped with organization.static script checksig_script() NOEXCEPT
{
static const script cached{ { { opcode::checksig } } };
return cached;
}
static script::cptr success_script_ptr() NOEXCEPT
{
constexpr auto op_success = opcode::reserved_80;
static_assert(operation::is_success(op_success));
static const auto cached = to_shared<script>({ { op_success } });
return cached;
}
static script::cptr checksig_script_ptr() NOEXCEPT
{
static const auto cached = to_shared<script>(checksig_script());
return cached;
}
...
case script_version::taproot:
{
// witness stack : [annex]...
if (program->size() == hash_size)
{
auto stack_size = out_stack->size();
// If at least two elements, discard annex if present.
if (drop_annex(*out_stack))
--stack_size;
// p2ts (tapscript, script path spend)
// witness stack : <control><script>[stack-elements]
// input script : (empty)
// output script : <1> <32-byte-tweaked-public-key>
if (stack_size > one)
{
// The last stack element is the control block.
const auto& control = pop(*out_stack);
// Control length must be 33 + 32m, for integer m [0..128].
if (!is_valid_control_block(*control))
return error::invalid_witness;
// The second-to-last stack element is the script.
out_script = to_shared<script>(*pop(*out_stack), false);
// TODO: DO SOME NASTY SHIT WITH CONTROL AND SCRIPT.
// TODO: MUST OBTAIN tapleaf_hash for signature hash.
// q is referred to as `taproot output key`.
// p is referred to as `taproot internal key`.
// Execute script with remaining stack.
return error::script_success;
}
// p2tr (taproot, key path spend)
// witness stack : <signature>
// input script : (empty)
// output script : <1> <32-byte-tweaked-public-key>
if (is_one(stack_size))
{
// Stack element is a signature that must be valid for q.
// Program is q, a 32 byte bip340 public key, so push it.
out_stack->push_back(program);
out_script = checksig_script_ptr();
// out_stack : <public-key><signature>
// out_script : <op_checksig>
return error::script_success;
}
// Fail if witness stack was empty.
return error::invalid_witness;
}
// This op_success script succeeds immediately with bip342 active.
out_script = success_script_ptr();
out_stack->clear();
// Version 1 other than 32 bytes remain unencumbered (success).
return error::script_success;
} (edited)verify_commitment(...). That can't be pushed into a script execution as with the other couple scenarios (assured success and simple checksig). But at least verify_commitment is static, so it can be moved out of this witness class parse method.
case script_version::taproot:
{
if (program->size() == ec_xonly_size)
{
auto stack_size = out_stack->size();
if (drop_annex(*out_stack))
--stack_size;
if (stack_size > one)
{
const auto control = pop(*out_stack);
if (!is_valid_control_block(*control))
return error::invalid_witness;
out_script = to_shared<script>(*pop(*out_stack), false);
if (const auto tap = taproot::parse(*control))
{
using namespace taproot;
out_leaf = to_shared(leaf_hash(tap.version, *out_script));
if (!verify_commitment(*control, *program, *out_leaf,
tap.parity)) return error::invalid_commitment;
return error::script_success;
}
out_script = success_script_ptr();
out_stack->clear();
return error::script_success;
}
if (is_one(stack_size))
{
out_stack->push_back(program);
out_script = checksig_script_ptr();
return error::script_success;
}
return error::invalid_witness;
}
out_script = success_script_ptr();
out_stack->clear();
return error::script_success;
}namespace taproot {
static hash_digest leaf_hash(uint8_t version,
const script& script) NOEXCEPT
{
hash_digest out{};
stream::out::fast stream{ out };
hash::sha256t::fast<"TapLeaf"> sink{ stream };
sink.write_byte(version);
script.to_data(sink, true);
sink.flush();
return out;
}
static hash_digest tweak_hash(const ec_xonly& key,
const hash_digest& merkle) NOEXCEPT
{
hash_digest out{};
stream::out::fast stream{ out };
hash::sha256t::fast<"TapTweak"> sink{ stream };
sink.write_bytes(key);
sink.write_bytes(merkle);
sink.flush();
return out;
}
static hash_digest branch_hash(const hash_digest& left,
const hash_digest& right) NOEXCEPT
{
hash_digest out{};
stream::out::fast stream{ out };
hash::sha256t::fast<"TapBranch"> sink{ stream };
if (std::lexicographical_compare(left.begin(), left.end(),
right.begin(), right.end()))
{
sink.write_bytes(left);
sink.write_bytes(right);
}
else
{
sink.write_bytes(right);
sink.write_bytes(left);
}
sink.flush();
return out;
}
static hash_digest merkle_root(const data_chunk& control,
const hash_digest& tapleaf_hash) NOEXCEPT
{
BC_ASSERT(is_valid_control_block(control));
constexpr auto start = add1(ec_xonly_size);
const auto bytes = floored_subtract(control.size(), start);
const auto count = floored_divide(bytes, ec_xonly_size);
const auto begin = std::next(control.data(), start);
const auto nodes = unsafe_array_cast<ec_xonly, taproot_max_keys>(begin);
hash_digest hash{ tapleaf_hash };
for (size_t node{}; node < count; ++node)
hash = branch_hash(hash, nodes.at(node));
return hash;
}
static bool verify_commitment(const data_chunk& control,
const data_chunk& program, const hash_digest& hash,
bool parity) NOEXCEPT
{
BC_ASSERT(is_valid_control_block(control));
const auto out = program.data();
const auto& out_key = unsafe_array_cast<uint8_t, ec_xonly_size>(out);
const auto in = std::next(control.data());
const auto& in_key = unsafe_array_cast<uint8_t, ec_xonly_size>(in);
const auto root = merkle_root(control, hash);
const auto tweak = tweak_hash(out_key, root);
return schnorr::verify_commitment(in_key, tweak, out_key, parity);
}
struct tap
{
operator bool() const NOEXCEPT { return version == tapscript_version; }
uint8_t version;
bool parity;
};
static tap parse(const data_chunk& control) NOEXCEPT
{
BC_ASSERT(!control.empty());
const auto byte = control.front();
return
{
bit_and(byte, tapscript_mask),
to_bool(bit_and(byte, bit_not(tapscript_mask)))
};
}
} // namespace taprootx add tagged hash x add libsecp2561 schnorr functions x add signature parse x add flags::bip341_rule x add flags::bip342_rule x add coverage::hash_default x upd coverage::all_rules script tests x ren script_version::zero to segwit x add script_version::taproot x add operation::is_success(opcode) x add transaction::is_sighash_valid(uint8_t) x add codeseparator index default constant x op_if (conditionally "minimal if") x op_notif (conditionally "minimal if") x op_checksigadd (conditionally invoke) x op_checkmultisig (conditionally disable) x op_checkmultisigverify (conditionally disable) x op_checksigadd (conditionally enable) x op_checksigadd (conditionally serialize) x op_checksigadd (implement) x op_checksig (conditionally modify) x op_checksigverify (conditionally modify) x Interpreter run loop changes x Tapscript indication (unset BIP342 in program when not tapscript) x Stacks element count limit (max_unified_stack_size also applied to initial stack) x Script size limit (max_script_size does not apply) x Non-push opcodes limit (max_counted_ops does not apply) x Sigops per-script budget (sigops in tapscripts do not count towards block limit) x Add success, failure (and roller) detection to script parser x Cache success, failure (and roller) on script x Modify script(operations) construction to set success, failure (and roller) x Early success and fail on script run (from cache) x Differentiate cached is_prefail and is_prevalid for tapscript execution. x Signature validation invocation x Expose codeseparator index with default x Signature operation legacy count review x Multisig hash cache by sighash byte x Signature hashing x Taproot Witness Program Invocation x Taproot Witness Program Extraction x Script Path Spending Parse and Validation x Key Path Spending Parse and Validation x Tagged Hash Midstate (optimization) x Tagged hash stream writers with midstate optimizations Incorporate script test vectors Activation (simplified)
static keyword inexplicably out of place, only manifests under highly-concurrent signature hashing.52 mins.4.11 hrs, a new record. Previous best was 4.42 hrs. Validation completed to 850,000 in 4.08 hrs and at that point confirmation was at 838,485. In the previous record run confirmation was at 680,286 when validation completed at 4.17 hrs. So validation got a little faster and confirmation got a lot faster. Or it could be that validation didn't change but the reduce cost of confirmation slowed it down less. Would have to run validation without confirmation to isolate that question.void chaser_confirm::do_bumped() NOEXCEPT
{
size_t fork_point{};
const auto& query = archive();
const auto top = query.get_top_confirmed();
auto fork = query.get_validated_fork(fork_point, checkpoint());
if (fork_point > top)
{
fault(error::confirm1);
return;
}
// ADDED THIS CONDITION...
if (fork_point < top)
{
uint256_t work{};
if (!query.get_work(work, fork))
{
fault(error::confirm2);
return;
}
bool strong{};
if (!query.get_strong_fork(strong, work, fork_point))
{
fault(error::confirm3);
return;
}
if (!strong)
return;
}
reorganize(fork, top, fork_point);
} (edited)5.6 hrs (May 13). This completed in 4.6 hrs (4.592) a full hour faster! The previous run, without optional indexes, was 4.11 hrs, so just 30 minutes for the combined 120GiB of optional indexation on the maxi.chrono, thread (except on non-win32 and with ICU), optional, utility, lexical-cast, algorithm, and will soon have regex removed. json has been moved to header only (required an include and a couple defines), along with its dependency container. And the remaining system was being NuGet included, along with others above, in vc++ but are header only. So those are now implicitly included (by the base "targets" directive).#ifndef LIBBITCOIN_SYSTEM_INTRINSICS_NEON_128_HPP
#define LIBBITCOIN_SYSTEM_INTRINSICS_NEON_128_HPP
#include <bitcoin/system/define.hpp>
#include <bitcoin/system/intrinsics/types.hpp>
#include <bitcoin/system/intrinsics/platforms/neon.hpp>
// All operations use NEON intrinsics.
namespace libbitcoin {
namespace system {
#if defined(HAVE_NEON)
namespace f {
/// bitwise primitives
/// ---------------------------------------------------------------------------
/// 32 bit functions are applied because xint128_t is typed as uint32x4_t.
INLINE xint128_t and_(xint128_t a, xint128_t b) NOEXCEPT
{
return vandq_u32(a, b);
}
INLINE xint128_t or_(xint128_t a, xint128_t b) NOEXCEPT
{
return vorrq_u32(a, b);
}
INLINE xint128_t xor_(xint128_t a, xint128_t b) NOEXCEPT
{
return veorq_u32(a, b);
}
INLINE xint128_t not_(xint128_t a) NOEXCEPT
{
return xor_(a, vdupq_n_u32(-1));
}
/// vector primitives
/// ---------------------------------------------------------------------------
template <auto B, auto S>
INLINE xint128_t shr(xint128_t a) NOEXCEPT
{
if constexpr (S == bits<uint8_t>)
return (xint128_t)vshrq_n_u8((uint8x16_t)a, B);
if constexpr (S == bits<uint16_t>)
return (xint128_t)vshrq_n_u16((uint16x8_t)a, B);
if constexpr (S == bits<uint32_t>)
return (xint128_t)vshrq_n_u32((uint32x4_t)a, B);
if constexpr (S == bits<uint64_t>)
return (xint128_t)vshrq_n_u64((uint64x2_t)a, B);
}
template <auto B, auto S>
INLINE xint128_t shl(xint128_t a) NOEXCEPT
{
if constexpr (S == bits<uint8_t>)
return (xint128_t)vshlq_n_u8((uint8x16_t)a, B);
if constexpr (S == bits<uint16_t>)
return (xint128_t)vshlq_n_u16((uint16x8_t)a, B);
if constexpr (S == bits<uint32_t>)
return (xint128_t)vshlq_n_u32((uint32x4_t)a, B);
if constexpr (S == bits<uint64_t>)
return (xint128_t)vshlq_n_u64((uint64x2_t)a, B);
}
template <auto B, auto S>
INLINE xint128_t ror(xint128_t a) NOEXCEPT
{
return or_(shr<B, S>(a), shl<S - B, S>(a));
}
template <auto B, auto S>
INLINE xint128_t rol(xint128_t a) NOEXCEPT
{
return or_(shl<B, S>(a), shr<S - B, S>(a));
}
template <auto S>
INLINE xint128_t add(xint128_t a, xint128_t b) NOEXCEPT
{
if constexpr (S == bits<uint8_t>)
return (xint128_t)vaddq_u8((uint8x16_t)a, (uint8x16_t)b);
if constexpr (S == bits<uint16_t>)
return (xint128_t)vaddq_u16((uint16x8_t)a, (uint16x8_t)b);
if constexpr (S == bits<uint32_t>)
return (xint128_t)vaddq_u32((uint32x4_t)a, (uint32x4_t)b);
if constexpr (S == bits<uint64_t>)
return (xint128_t)vaddq_u64((uint64x2_t)a, (uint64x2_t)b);
}
template <auto K, auto S>
INLINE xint128_t addc(xint128_t a) NOEXCEPT
{
if constexpr (S == bits<uint8_t>)
return add<S>(a, (xint128_t)vdupq_n_u8((uint8_t)K));
if constexpr (S == bits<uint16_t>)
return add<S>(a, (xint128_t)vdupq_n_u16((uint16_t)K));
if constexpr (S == bits<uint32_t>)
return add<S>(a, (xint128_t)vdupq_n_u32((uint32_t)K));
if constexpr (S == bits<uint64_t>)
return add<S>(a, (xint128_t)vdupq_n_u64((uint64_t)K));
}
} // namespace f
/// add/broadcast/get/set
/// ---------------------------------------------------------------------------
template <typename Word, if_integral_integer<Word> = true>
INLINE xint128_t add(xint128_t a, xint128_t b) NOEXCEPT
{
if constexpr (is_same_type<Word, uint8_t>)
return (xint128_t)vaddq_u8((uint8x16_t)a, (uint8x16_t)b);
if constexpr (is_same_type<Word, uint16_t>)
return (xint128_t)vaddq_u16((uint16x8_t)a, (uint16x8_t)b);
if constexpr (is_same_type<Word, uint32_t>)
return (xint128_t)vaddq_u32((uint32x4_t)a, (uint32x4_t)b);
if constexpr (is_same_type<Word, uint64_t>)
return (xint128_t)vaddq_u64((uint64x2_t)a, (uint64x2_t)b);
}
template <typename xWord, typename Word, if_integral_integer<Word> = true,
if_same<xWord, xint128_t> = true>
INLINE xint128_t broadcast(Word a) NOEXCEPT
{
if constexpr (is_same_type<Word, uint8_t>)
return (xint128_t)vdupq_n_u8((uint8_t)a);
if constexpr (is_same_type<Word, uint16_t>)
return (xint128_t)vdupq_n_u16((uint16_t)a);
if constexpr (is_same_type<Word, uint32_t>)
return (xint128_t)vdupq_n_u32((uint32_t)a);
if constexpr (is_same_type<Word, uint64_t>)
return (xint128_t)vdupq_n_u64((uint64_t)a);
}
// Lane zero is lowest order word.
template <typename Word, auto Lane, if_integral_integer<Word> = true>
INLINE Word get(xint128_t a) NOEXCEPT
{
if constexpr (is_same_type<Word, uint8_t>)
return (Word)vgetq_lane_u8((uint8x16_t)a, Lane);
if constexpr (is_same_type<Word, uint16_t>)
return (Word)vgetq_lane_u16((uint16x8_t)a, Lane);
if constexpr (is_same_type<Word, uint32_t>)
return (Word)vgetq_lane_u32((uint32x4_t)a, Lane);
if constexpr (is_same_type<Word, uint64_t>)
return (Word)vgetq_lane_u64((uint64x2_t)a, Lane);
}
// Low order word to the left.
template <typename xWord, if_same<xWord, xint128_t> = true>
INLINE xint128_t set(uint64_t x01, uint64_t x02) NOEXCEPT
{
return (xint128_t)vcombine_u64(vcreate_u64(x01), vcreate_u64(x02));
}
template <typename xWord, if_same<xWord, xint128_t> = true>
INLINE xint128_t set(
uint32_t x01, uint32_t x02, uint32_t x03, uint32_t x04) NOEXCEPT
{
const uint32_t a[]{ x01, x02, x03, x04 };
return (xint128_t)vld1q_u32(a);
}
template <typename xWord, if_same<xWord, xint128_t> = true>
INLINE xint128_t set(
uint16_t x01, uint16_t x02, uint16_t x03, uint16_t x04,
uint16_t x05, uint16_t x06, uint16_t x07, uint16_t x08) NOEXCEPT
{
auto low = vcreate_u16(
(uint64_t)x02 << 16 | (uint64_t)x01 << 0);
low = vset_lane_u16((uint16_t)x03, low, 2);
low = vset_lane_u16((uint16_t)x04, low, 3);
auto high = vcreate_u16(
(uint64_t)x06 << 16 | (uint64_t)x05 << 0);
high = vset_lane_u16((uint16_t)x07, high, 2);
high = vset_lane_u16((uint16_t)x08, high, 3);
return (xint128_t)vcombine_u16(low, high);
}
template <typename xWord, if_same<xWord, xint128_t> = true>
INLINE xint128_t set(
uint8_t x01, uint8_t x02, uint8_t x03, uint8_t x04,
uint8_t x05, uint8_t x06, uint8_t x07, uint8_t x08,
uint8_t x09, uint8_t x10, uint8_t x11, uint8_t x12,
uint8_t x13, uint8_t x14, uint8_t x15, uint8_t x16) NOEXCEPT
{
auto low = vcreate_u8(
(uint64_t)x04 << 24 | (uint64_t)x03 << 16 |
(uint64_t)x02 << 8 | (uint64_t)x01 << 0);
low = vset_lane_u8((uint8_t)x05, low, 4);
low = vset_lane_u8((uint8_t)x06, low, 5);
low = vset_lane_u8((uint8_t)x07, low, 6);
low = vset_lane_u8((uint8_t)x08, low, 7);
auto high = vcreate_u8(
(uint64_t)x12 << 24 | (uint64_t)x11 << 16 |
(uint64_t)x10 << 8 | (uint64_t)x09 << 0);
high = vset_lane_u8((uint8_t)x13, high, 4);
high = vset_lane_u8((uint8_t)x14, high, 5);
high = vset_lane_u8((uint8_t)x15, high, 6);
high = vset_lane_u8((uint8_t)x16, high, 7);
return (xint128_t)vcombine_u8(low, high);
}
/// endianness
/// ---------------------------------------------------------------------------
template <typename Word, if_same<Word, uint8_t> = true>
INLINE xint128_t byteswap(xint128_t a) NOEXCEPT
{
return (xint128_t)a;
}
template <typename Word, if_same<Word, uint16_t> = true>
INLINE xint128_t byteswap(xint128_t a) NOEXCEPT
{
return (xint128_t)vrev16q_u8((uint8x16_t)a);
}
template <typename Word, if_same<Word, uint32_t> = true>
INLINE xint128_t byteswap(xint128_t a) NOEXCEPT
{
return (xint128_t)vrev32q_u8((uint8x16_t)a);
}
template <typename Word, if_same<Word, uint64_t> = true>
INLINE xint128_t byteswap(xint128_t a) NOEXCEPT
{
return (xint128_t)vrev64q_u8((uint8x16_t)a);
}
/// load/store
/// ---------------------------------------------------------------------------
INLINE xint128_t load(const xint128_t& bytes) NOEXCEPT
{
return vld1q_u32((uint32_t*)&bytes);
}
INLINE void store(xint128_t& bytes, xint128_t a) NOEXCEPT
{
vst1q_u32((uint32_t*)&bytes, a);
}
INLINE xint128_t load_aligned(const xint128_t& bytes) NOEXCEPT
{
return vld1q_u32((uint32_t*)&bytes);
}
INLINE void store_aligned(xint128_t& bytes, xint128_t a) NOEXCEPT
{
vst1q_u32((uint32_t*)&bytes, a);
}
#endif // HAVE_NEON
} // namespace system
} // namespace libbitcoin
#endif (edited)constexpr tests for the same vectors are passing.899,101 is 65 mins vs. 53 mins to 850,000. The load factors are higher given the additional elements, and this can pull down the point table duplicate search. We can prob get this back under 60 mins with config.758GiB), up from 715,734,115,162 bytes (667GiB), without adjustments to reduce load factor - though that won't register at the GiB level.899,101 is 662,821,642,114 bytes (617GiB). Total to 850,000 is 582,391,632,423 bytes (542GiB).59 mins. It increases the total headers size by over 1GB, which is a tradeoff, but obviously worth it. Under 1 hours on the mini, at 900,000 blocks!ARM Intrinsic SHA implementation, with all tests passing on the macOS CI platforms.6 minutes between block 850,000 and 900,000 (11 months of growth).850k, of 46 minutes (previous record with necessary duplicate detection was 53 minutes). This was the result of me bumping the process (vs. thread) priority to high, which can result in better memory performance.