Merge pull request #6527 from daira/zip317-computations

ZIP 317 preparation: fix bitrotted miner tests and add conventional fee computations
This commit is contained in:
Kris Nuttycombe 2023-04-04 16:30:51 -06:00 committed by GitHub
commit b3dbfb1ddd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 273 additions and 168 deletions

View File

@ -543,6 +543,7 @@ libbitcoin_common_a_SOURCES = \
transaction_builder.cpp \
util/test.cpp \
warnings.cpp \
zip317.cpp \
$(BITCOIN_CORE_H) \
$(LIBZCASH_H)
@ -709,7 +710,8 @@ libzcash_script_la_SOURCES = \
script/interpreter.cpp \
script/script.cpp \
uint256.cpp \
util/strencodings.cpp
util/strencodings.cpp \
zip317.cpp
if GLIBC_BACK_COMPAT
libzcash_script_la_SOURCES += compat/glibc_compat.cpp

View File

@ -127,7 +127,7 @@ void EhIndexToArray(const eh_index i, unsigned char* array)
memcpy(array, &bei, sizeof(eh_index));
}
std::vector<unsigned char> GetMinimalFromIndices(std::vector<eh_index> indices,
std::vector<unsigned char> GetMinimalFromIndices(const std::vector<eh_index>& indices,
size_t cBitLen)
{
assert(((cBitLen+1)+7)/8 <= sizeof(eh_index));

View File

@ -16,7 +16,7 @@ inline constexpr size_t equihash_solution_size(unsigned int N, unsigned int K) {
typedef uint32_t eh_index;
typedef uint8_t eh_trunc;
std::vector<unsigned char> GetMinimalFromIndices(std::vector<eh_index> indices,
std::vector<unsigned char> GetMinimalFromIndices(const std::vector<eh_index>& indices,
size_t cBitLen);
void CompressArray(const unsigned char* in, size_t in_len,
unsigned char* out, size_t out_len,

View File

@ -1053,37 +1053,12 @@ void static BitcoinMiner(const CChainParams& chainparams)
// TODO: factor this out into a function with the same API for each solver.
if (solver == "tromp") {
// Create solver and initialize it.
equi eq(1);
eq.setstate(curr_state.inner);
// Initialization done, start algo driver.
eq.digit0(0);
eq.xfull = eq.bfull = eq.hfull = 0;
eq.showbsizes(0);
for (u32 r = 1; r < WK; r++) {
(r&1) ? eq.digitodd(r, 0) : eq.digiteven(r, 0);
eq.xfull = eq.bfull = eq.hfull = 0;
eq.showbsizes(r);
}
eq.digitK(0);
ehSolverRuns.increment();
// Convert solution indices to byte array (decompress) and pass it to validBlock method.
for (size_t s = 0; s < std::min(MAXSOLS, eq.nsols); s++) {
std::function<void()> incrementRuns = [&]() { ehSolverRuns.increment(); };
std::function<bool(size_t s, const std::vector<uint32_t>&)> checkSolution = [&](size_t s, const std::vector<uint32_t>& index_vector) {
LogPrint("pow", "Checking solution %d\n", s+1);
std::vector<eh_index> index_vector(PROOFSIZE);
for (size_t i = 0; i < PROOFSIZE; i++) {
index_vector[i] = eq.sols[s][i];
}
std::vector<unsigned char> sol_char = GetMinimalFromIndices(index_vector, DIGITBITS);
if (validBlock(sol_char)) {
// If we find a POW solution, do not try other solutions
// because they become invalid as we created a new block in blockchain.
break;
}
}
return validBlock(GetMinimalFromIndices(index_vector, DIGITBITS));
};
equihash_solve(curr_state.inner, incrementRuns, checkSolution);
} else {
try {
// If we find a valid block, we rebuild

View File

@ -1,5 +1,5 @@
// Equihash solver
// Copyright (c) 2016-2016 John Tromp, The Zcash developers
// Copyright (c) 2016-2023 John Tromp, The Zcash developers
#ifndef ZCASH_POW_TROMP_EQUI_H
#define ZCASH_POW_TROMP_EQUI_H
@ -43,64 +43,19 @@ typedef u32 proof[PROOFSIZE];
enum verify_code { POW_OK, POW_DUPLICATE, POW_OUT_OF_ORDER, POW_NONZERO_XOR };
const char *errstr[] = { "OK", "duplicate index", "indices out of order", "nonzero xor" };
extern const char *errstr[4];
void genhash(const rust::Box<blake2b::State>& ctx, u32 idx, uchar *hash) {
auto state = ctx->box_clone();
u32 leb = htole32(idx / HASHESPERBLAKE);
state->update({(const uchar *)&leb, sizeof(u32)});
uchar blakehash[HASHOUT];
state->finalize({blakehash, HASHOUT});
memcpy(hash, blakehash + (idx % HASHESPERBLAKE) * WN/8, WN/8);
}
int verifyrec(const rust::Box<blake2b::State>& ctx, u32 *indices, uchar *hash, int r) {
if (r == 0) {
genhash(ctx, *indices, hash);
return POW_OK;
}
u32 *indices1 = indices + (1 << (r-1));
if (*indices >= *indices1)
return POW_OUT_OF_ORDER;
uchar hash0[WN/8], hash1[WN/8];
int vrf0 = verifyrec(ctx, indices, hash0, r-1);
if (vrf0 != POW_OK)
return vrf0;
int vrf1 = verifyrec(ctx, indices1, hash1, r-1);
if (vrf1 != POW_OK)
return vrf1;
for (int i=0; i < WN/8; i++)
hash[i] = hash0[i] ^ hash1[i];
int i, b = r * DIGITBITS;
for (i = 0; i < b/8; i++)
if (hash[i])
return POW_NONZERO_XOR;
if ((b%8) && hash[i] >> (8-(b%8)))
return POW_NONZERO_XOR;
return POW_OK;
}
int compu32(const void *pa, const void *pb) {
u32 a = *(u32 *)pa, b = *(u32 *)pb;
return a<b ? -1 : a==b ? 0 : +1;
}
bool duped(proof prf) {
proof sortprf;
memcpy(sortprf, prf, sizeof(proof));
qsort(sortprf, PROOFSIZE, sizeof(u32), &compu32);
for (u32 i=1; i<PROOFSIZE; i++)
if (sortprf[i] <= sortprf[i-1])
return true;
return false;
}
void genhash(const rust::Box<blake2b::State>& ctx, u32 idx, uchar *hash);
int verifyrec(const rust::Box<blake2b::State>& ctx, u32 *indices, uchar *hash, int r);
int compu32(const void *pa, const void *pb);
bool duped(proof prf);
// verify Wagner conditions
int verify(u32 indices[PROOFSIZE], const rust::Box<blake2b::State> ctx) {
if (duped(indices))
return POW_DUPLICATE;
uchar hash[WN/8];
return verifyrec(ctx, indices, hash, WK);
}
int verify(u32 indices[PROOFSIZE], const rust::Box<blake2b::State> ctx);
bool equihash_solve(
const rust::Box<blake2b::State>& curr_state,
std::function<void()>& incrementRuns,
std::function<bool(size_t s, const std::vector<uint32_t>&)>& checkSolution);
#endif // ZCASH_POW_TROMP_EQUI_H

View File

@ -1,5 +1,5 @@
// Equihash solver
// Copyright (c) 2016 John Tromp, The Zcash developers
// Copyright (c) 2016-2023 John Tromp, The Zcash developers
// Fix N, K, such that n = N/(k+1) is integer
// Fix M = 2^{n+1} hashes each of length N bits,
@ -75,6 +75,8 @@ static const u32 NBLOCKS = (NHASHES+HASHESPERBLAKE-1)/HASHESPERBLAKE;
// nothing larger found in 100000 runs
static const u32 MAXSOLS = 8;
const char *errstr[] = { "OK", "duplicate index", "indices out of order", "nonzero xor" };
// tree node identifying its children as two different slots in
// a bucket on previous layer with the same rest bits (x-tra hash)
struct tree {
@ -646,4 +648,98 @@ void *worker(void *vp) {
return 0;
}
void genhash(const rust::Box<blake2b::State>& ctx, u32 idx, uchar *hash) {
auto state = ctx->box_clone();
u32 leb = htole32(idx / HASHESPERBLAKE);
state->update({(const uchar *)&leb, sizeof(u32)});
uchar blakehash[HASHOUT];
state->finalize({blakehash, HASHOUT});
memcpy(hash, blakehash + (idx % HASHESPERBLAKE) * WN/8, WN/8);
}
int verifyrec(const rust::Box<blake2b::State>& ctx, u32 *indices, uchar *hash, int r) {
if (r == 0) {
genhash(ctx, *indices, hash);
return POW_OK;
}
u32 *indices1 = indices + (1 << (r-1));
if (*indices >= *indices1)
return POW_OUT_OF_ORDER;
uchar hash0[WN/8], hash1[WN/8];
int vrf0 = verifyrec(ctx, indices, hash0, r-1);
if (vrf0 != POW_OK)
return vrf0;
int vrf1 = verifyrec(ctx, indices1, hash1, r-1);
if (vrf1 != POW_OK)
return vrf1;
for (int i=0; i < WN/8; i++)
hash[i] = hash0[i] ^ hash1[i];
int i, b = r * DIGITBITS;
for (i = 0; i < b/8; i++)
if (hash[i])
return POW_NONZERO_XOR;
if ((b%8) && hash[i] >> (8-(b%8)))
return POW_NONZERO_XOR;
return POW_OK;
}
int compu32(const void *pa, const void *pb) {
u32 a = *(u32 *)pa, b = *(u32 *)pb;
return a<b ? -1 : a==b ? 0 : +1;
}
bool duped(proof prf) {
proof sortprf;
memcpy(sortprf, prf, sizeof(proof));
qsort(sortprf, PROOFSIZE, sizeof(u32), &compu32);
for (u32 i=1; i<PROOFSIZE; i++)
if (sortprf[i] <= sortprf[i-1])
return true;
return false;
}
// verify Wagner conditions
int verify(u32 indices[PROOFSIZE], const rust::Box<blake2b::State> ctx) {
if (duped(indices))
return POW_DUPLICATE;
uchar hash[WN/8];
return verifyrec(ctx, indices, hash, WK);
}
bool equihash_solve(
const rust::Box<blake2b::State>& curr_state,
std::function<void()>& incrementRuns,
std::function<bool(size_t s, const std::vector<uint32_t>&)>& checkSolution)
{
// Create solver and initialize it.
equi eq(1);
eq.setstate(curr_state);
// Initialization done, start algo driver.
eq.digit0(0);
eq.xfull = eq.bfull = eq.hfull = 0;
eq.showbsizes(0);
for (u32 r = 1; r < WK; r++) {
(r&1) ? eq.digitodd(r, 0) : eq.digiteven(r, 0);
eq.xfull = eq.bfull = eq.hfull = 0;
eq.showbsizes(r);
}
eq.digitK(0);
incrementRuns();
// Decompress solution indices and pass to checkSolution.
for (size_t s = 0; s < std::min(MAXSOLS, eq.nsols); s++) {
std::vector<uint32_t> index_vector(PROOFSIZE);
for (size_t i = 0; i < PROOFSIZE; i++) {
index_vector[i] = eq.sols[s][i];
}
if (checkSolution(s, index_vector)) {
// If we find a POW solution, do not try other solutions
// because they become invalid as we created a new block in blockchain.
return true;
}
}
return false;
}
#endif // ZCASH_POW_TROMP_EQUI_MINER_H

View File

@ -9,6 +9,7 @@
#include "hash.h"
#include "tinyformat.h"
#include "util/strencodings.h"
#include "zip317.h"
#include <rust/transaction.h>
@ -385,6 +386,20 @@ unsigned int CTransaction::CalculateModifiedSize(unsigned int nTxSize) const
return nTxSize;
}
CAmount CTransaction::GetConventionalFee() const {
return CalculateConventionalFee(GetLogicalActionCount());
}
size_t CTransaction::GetLogicalActionCount() const {
return CalculateLogicalActionCount(
vin,
vout,
vJoinSplit.size(),
vShieldedSpend.size(),
vShieldedOutput.size(),
orchardBundle.GetNumActions());
}
std::string CTransaction::ToString() const
{
std::string str;

View File

@ -977,6 +977,14 @@ public:
// Compute modified tx size for priority calculation (optionally given tx size)
unsigned int CalculateModifiedSize(unsigned int nTxSize=0) const;
// Return the conventional fee for this transaction calculated according to
// <https://zips.z.cash/zip-0317#fee-calculation>.
CAmount GetConventionalFee() const;
// Return the number of logical actions calculated according to
// <https://zips.z.cash/zip-0317#fee-calculation>.
size_t GetLogicalActionCount() const;
bool IsCoinBase() const
{
return (vin.size() == 1 && vin[0].prevout.IsNull());

View File

@ -12,10 +12,11 @@
#include "uint256.h"
#include "util/system.h"
#include "crypto/equihash.h"
//#include "pow/tromp/equi_miner.h"
#include "pow/tromp/equi.h"
#include "test/test_bitcoin.h"
#include <rust/equihash.h>
#include <boost/test/unit_test.hpp>
BOOST_FIXTURE_TEST_SUITE(miner_tests, TestingSetup)
@ -170,11 +171,18 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
// Simple block creation, nothing special yet:
BOOST_CHECK(pblocktemplate = BlockAssembler(chainparams).CreateNewBlock(scriptPubKey));
// We can't make transactions until we have inputs
// Therefore, load 100 blocks :)
std::vector<CTransaction*>txFirst;
for (unsigned int i = 0; i < sizeof(blockinfo)/sizeof(*blockinfo); ++i)
// We can't make transactions until we have inputs. Therefore, load 110 blocks.
const int nblocks = 110;
assert(nblocks < chainparams.GetConsensus().SubsidySlowStartShift());
auto MinerSubsidy = [](int height) { return height*50000; };
auto FoundersReward = [](int height) { return height*12500; };
std::vector<CTransaction*> txFirst;
for (unsigned int i = 0; i < nblocks; ++i)
{
int height = i+1;
CBlock *pblock = &pblocktemplate->block; // pointer for convenience
pblock->nVersion = 4;
// Fake the blocks taking at least nPowTargetSpacing to be mined.
@ -182,99 +190,70 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
// of the next block must be six spacings ahead of that to be at least
// one spacing ahead of the tip. Within 11 blocks of genesis, the median
// will be closer to the tip, and blocks will appear slower.
pblock->nTime = chainActive.Tip()->GetMedianTimePast()+6*Params().GetConsensus().PoWTargetSpacing(i);
pblock->nTime = chainActive.Tip()->GetMedianTimePast() + 6*chainparams.GetConsensus().PoWTargetSpacing(i);
pblock->nBits = GetNextWorkRequired(chainActive.Tip(), pblock, chainparams.GetConsensus());
CMutableTransaction txCoinbase(pblock->vtx[0]);
txCoinbase.nVersion = 1;
txCoinbase.vin[0].scriptSig = CScript() << (chainActive.Height()+1) << OP_0;
txCoinbase.vout[0].scriptPubKey = CScript();
txCoinbase.vout[0].nValue = 50000 * (i + 1);
txCoinbase.vout[1].nValue = 12500 * (i + 1);
txCoinbase.vout[0].nValue = MinerSubsidy(height);
txCoinbase.vout[1].nValue = FoundersReward(height);
pblock->vtx[0] = CTransaction(txCoinbase);
if (txFirst.size() < 2)
txFirst.push_back(new CTransaction(pblock->vtx[0]));
pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
pblock->nNonce = uint256S(blockinfo[i].nonce_hex);
pblock->nSolution = ParseHex(blockinfo[i].solution_hex);
/*
{
arith_uint256 try_nonce(0);
unsigned int n = Params().GetConsensus().nEquihashN;
unsigned int k = Params().GetConsensus().nEquihashK;
if (i < sizeof(blockinfo)/sizeof(*blockinfo)) {
pblock->nNonce = uint256S(blockinfo[i].nonce_hex);
pblock->nSolution = ParseHex(blockinfo[i].solution_hex);
} else {
// If you need to mine more blocks than are currently in blockinfo, this code will do so.
// We leave it compiling so that it doesn't bitrot.
arith_uint256 try_nonce(0);
unsigned int n = chainparams.GetConsensus().nEquihashN;
unsigned int k = chainparams.GetConsensus().nEquihashK;
// Hash state
eh_HashState eh_state = EhInitialiseState(n, k);
// Hash state
eh_HashState eh_state = EhInitialiseState(n, k);
// I = the block header minus nonce and solution.
CEquihashInput I{*pblock};
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << I;
// I = the block header minus nonce and solution.
CEquihashInput I{*pblock};
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << I;
// H(I||...
eh_state.Update((unsigned char*)&ss[0], ss.size());
// H(I||...
eh_state.Update((unsigned char*)&ss[0], ss.size());
while (true) {
pblock->nNonce = ArithToUint256(try_nonce);
while (true) {
pblock->nNonce = ArithToUint256(try_nonce);
// H(I||V||...
eh_HashState curr_state(eh_state);
curr_state.Update(pblock->nNonce.begin(), pblock->nNonce.size());
// H(I||V||...
eh_HashState curr_state(eh_state);
curr_state.Update(pblock->nNonce.begin(), pblock->nNonce.size());
// Create solver and initialize it.
equi eq(1);
eq.setstate(curr_state.state);
std::function<void()> incrementRuns = [&]() {};
std::function<bool(size_t, const std::vector<uint32_t>&)> checkSolution = [&](size_t s, const std::vector<uint32_t>& index_vector) {
auto soln = GetMinimalFromIndices(index_vector, DIGITBITS);
pblock->nSolution = soln;
if (!equihash::is_valid(
n, k,
{(const unsigned char*)ss.data(), ss.size()},
{pblock->nNonce.begin(), pblock->nNonce.size()},
{soln.data(), soln.size()})) {
return false;
}
CValidationState state;
return ProcessNewBlock(state, chainparams, NULL, pblock, true, NULL) && state.IsValid();
};
if (equihash_solve(curr_state.inner, incrementRuns, checkSolution))
break;
// Initialization done, start algo driver.
eq.digit0(0);
eq.xfull = eq.bfull = eq.hfull = 0;
eq.showbsizes(0);
for (u32 r = 1; r < WK; r++) {
(r&1) ? eq.digitodd(r, 0) : eq.digiteven(r, 0);
eq.xfull = eq.bfull = eq.hfull = 0;
eq.showbsizes(r);
try_nonce += 1;
}
eq.digitK(0);
// Convert solution indices to byte array (decompress) and pass it to validBlock method.
std::set<std::vector<unsigned char>> solns;
for (size_t s = 0; s < std::min(MAXSOLS, eq.nsols); s++) {
LogPrint("pow", "Checking solution %d\n", s+1);
std::vector<eh_index> index_vector(PROOFSIZE);
for (size_t i = 0; i < PROOFSIZE; i++) {
index_vector[i] = eq.sols[s][i];
}
std::vector<unsigned char> sol_char = GetMinimalFromIndices(index_vector, DIGITBITS);
solns.insert(sol_char);
}
for (auto soln : solns) {
if (!equihash::is_valid(
n, k,
{(const unsigned char*)ss.data(), ss.size()},
{pblock->nNonce.begin(), pblock->nNonce.size()},
{soln.data(), soln.size()})) continue;
pblock->nSolution = soln;
CValidationState state;
if (ProcessNewBlock(state, NULL, pblock, true, NULL) && state.IsValid()) {
goto foundit;
}
//std::cout << state.GetRejectReason() << std::endl;
}
try_nonce += 1;
}
foundit:
std::cout << " {\"" << pblock->nNonce.GetHex() << "\", \"";
std::cout << HexStr(pblock->nSolution.begin(), pblock->nSolution.end());
std::cout << "\"}," << std::endl;
}
*/
// These tests assume null hashBlockCommitments (before Sapling)
pblock->hashBlockCommitments = uint256();
@ -285,6 +264,9 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
pblock->hashPrevBlock = pblock->GetHash();
}
// Stop here if we needed to mine more blocks.
assert(nblocks == sizeof(blockinfo)/sizeof(*blockinfo));
// Set the clock to be just ahead of the last "mined" block, to ensure we satisfy the
// future timestamp soft fork rule.
auto curTime = GetTime();

32
src/zip317.cpp Normal file
View File

@ -0,0 +1,32 @@
// Copyright (c) 2023-2023 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
#include "zip317.h"
#include <algorithm>
static size_t ceil_div(size_t num, size_t den) {
return (num + den - 1)/den;
}
CAmount CalculateConventionalFee(size_t logicalActionCount) {
return MARGINAL_FEE * std::max(GRACE_ACTIONS, logicalActionCount);
}
size_t CalculateLogicalActionCount(
const std::vector<CTxIn>& vin,
const std::vector<CTxOut>& vout,
unsigned int joinSplitCount,
unsigned int saplingSpendCount,
unsigned int saplingOutputCount,
unsigned int orchardActionCount) {
const size_t tx_in_total_size = GetSerializeSize(vin, SER_NETWORK, PROTOCOL_VERSION);
const size_t tx_out_total_size = GetSerializeSize(vout, SER_NETWORK, PROTOCOL_VERSION);
return std::max(ceil_div(tx_in_total_size, P2PKH_STANDARD_INPUT_SIZE),
ceil_div(tx_out_total_size, P2PKH_STANDARD_OUTPUT_SIZE)) +
2 * joinSplitCount +
std::max(saplingSpendCount, saplingOutputCount) +
orchardActionCount;
}

40
src/zip317.h Normal file
View File

@ -0,0 +1,40 @@
// Copyright (c) 2023-2023 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
#ifndef ZCASH_ZIP317_H
#define ZCASH_ZIP317_H
#include "amount.h"
#include "primitives/transaction.h"
#include <cstdint>
#include <cstddef>
#include <vector>
// Constants for fee calculation.
static const CAmount MARGINAL_FEE = 5000;
static const size_t GRACE_ACTIONS = 2;
static const size_t P2PKH_STANDARD_INPUT_SIZE = 150;
static const size_t P2PKH_STANDARD_OUTPUT_SIZE = 34;
// Constants for block template construction.
static const int64_t WEIGHT_RATIO_SCALE = INT64_C(10000000000000000);
static const int64_t WEIGHT_RATIO_CAP = 4;
static const size_t BLOCK_UNPAID_ACTION_LIMIT = 50;
/// Return the conventional fee for the given `logicalActionCount` calculated according to
/// <https://zips.z.cash/zip-0317#fee-calculation>.
CAmount CalculateConventionalFee(size_t logicalActionCount);
/// Return the number of logical actions calculated according to
/// <https://zips.z.cash/zip-0317#fee-calculation>.
size_t CalculateLogicalActionCount(
const std::vector<CTxIn>& vin,
const std::vector<CTxOut>& vout,
unsigned int joinSplitCount,
unsigned int saplingSpendCount,
unsigned int saplingOutputCount,
unsigned int orchardActionCount);
#endif // ZCASH_ZIP317_H