WIP: merged mining.

This commit is contained in:
Jianping Wu 2019-03-11 23:04:57 -07:00
parent b9335132b0
commit fce6d8ea6d
8 changed files with 333 additions and 40 deletions

View File

@ -87,3 +87,7 @@ void hash_extra_jh(const void *data, size_t length, char *hash);
void hash_extra_skein(const void *data, size_t length, char *hash);
void tree_hash(const char (*hashes)[HASH_SIZE], size_t count, char *root_hash);
size_t tree_depth(size_t count);
void tree_branch(const char (*hashes)[HASH_SIZE], size_t count, char (*branch)[HASH_SIZE]);
void tree_hash_from_branch(const char (*branch)[HASH_SIZE], size_t depth, const char* leaf, const void* path, char* root_hash);

View File

@ -1,21 +1,21 @@
// Copyright (c) 2014-2018, The Monero Project
//
//
// All rights reserved.
//
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
@ -25,7 +25,7 @@
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#include <assert.h>
@ -43,7 +43,7 @@
#include <stdlib.h>
#endif
/***
/***
* Round to power of two, for count>=3 and for count being not too large (as reasonable for tree hash calculations)
*/
size_t tree_hash_cnt(size_t count) {
@ -112,3 +112,98 @@ void tree_hash(const char (*hashes)[HASH_SIZE], size_t count, char *root_hash) {
cn_fast_hash(ints[0], 64, root_hash);
}
}
/*******************************************************************************************/
/* Helper functions for merged mining - for merkle tree branch compuation. */
size_t tree_depth(size_t count)
{
size_t i;
size_t depth = 0;
assert(count > 0);
for (i = sizeof(size_t) << 2; i > 0; i >>= 1)
{
if (count >> i > 0)
{
count >>= i;
depth += i;
}
}
return depth;
}
void tree_branch(const char (*hashes)[HASH_SIZE], size_t count, char (*branch)[HASH_SIZE])
{
size_t i, j;
size_t cnt = 1;
size_t depth = 0;
char (*ints)[HASH_SIZE];
assert(count > 0);
for (i = sizeof(size_t) << 2; i > 0; i >>= 1)
{
if (cnt << i <= count)
{
cnt <<= i;
depth += i;
}
}
assert(cnt == 1ULL << depth);
assert(depth == tree_depth(count));
ints = alloca((cnt - 1) * HASH_SIZE);
memcpy(ints, hashes + 1, (2 * cnt - count - 1) * HASH_SIZE);
for (i = 2 * cnt - count, j = 2 * cnt - count - 1; j < cnt - 1; i += 2, ++j)
{
cn_fast_hash(hashes[i], 2 * HASH_SIZE, ints[j]);
}
assert(i == count);
while (depth > 0)
{
assert(cnt == 1ULL << depth);
cnt >>= 1;
--depth;
memcpy(branch[depth], ints[0], HASH_SIZE);
for (i = 1, j = 0; j < cnt - 1; i += 2, ++j)
{
cn_fast_hash(ints[i], 2 * HASH_SIZE, ints[j]);
}
}
}
void tree_hash_from_branch(const char (*branch)[HASH_SIZE], size_t depth, const char* leaf, const void* path, char* root_hash)
{
if (depth == 0)
{
memcpy(root_hash, leaf, HASH_SIZE);
}
else
{
char buffer[2][HASH_SIZE];
int from_leaf = 1;
char *leaf_path, *branch_path;
while (depth > 0)
{
--depth;
if (path && (((const char*) path)[depth >> 3] & (1 << (depth & 7))) != 0)
{
leaf_path = buffer[1];
branch_path = buffer[0];
}
else
{
leaf_path = buffer[0];
branch_path = buffer[1];
}
if (from_leaf)
{
memcpy(leaf_path, leaf, HASH_SIZE);
from_leaf = 0;
}
else
{
cn_fast_hash(buffer, 2 * HASH_SIZE, leaf_path);
}
memcpy(branch_path, branch[depth], HASH_SIZE);
}
cn_fast_hash(buffer, 2 * HASH_SIZE, root_hash);
}
}

View File

@ -618,6 +618,32 @@ namespace cryptonote
return find_tx_extra_field_by_type(tx_extra_fields, keva_block);
}
//---------------------------------------------------------------
bool append_keva_blockhash_to_extra(std::vector<uint8_t>& tx_extra, const tx_extra_keva_blockhash& keva_blockhash)
{
// convert to variant
tx_extra_field field = tx_extra_keva_blockhash{ keva_blockhash };
// serialize
std::ostringstream oss;
binary_archive<true> ar(oss);
bool r = ::do_serialize(ar, field);
CHECK_AND_NO_ASSERT_MES_L1(r, false, "failed to serialize tx extra keva blockhash");
// append
std::string tx_extra_str = oss.str();
size_t pos = tx_extra.size();
tx_extra.resize(tx_extra.size() + tx_extra_str.size());
memcpy(&tx_extra[pos], tx_extra_str.data(), tx_extra_str.size());
return true;
}
//---------------------------------------------------------------
bool get_keva_blockhash_from_extra(const std::vector<uint8_t>& tx_extra, tx_extra_keva_blockhash& keva_blockhash)
{
std::vector<tx_extra_field> tx_extra_fields;
if (!parse_tx_extra(tx_extra, tx_extra_fields))
return false;
return find_tx_extra_field_by_type(tx_extra_fields, keva_blockhash);
}
//---------------------------------------------------------------
bool get_inputs_money_amount(const transaction& tx, uint64_t& money)
{
money = 0;

View File

@ -76,6 +76,8 @@ namespace cryptonote
bool add_extra_nonce_to_tx_extra(std::vector<uint8_t>& tx_extra, const blobdata& extra_nonce);
bool append_keva_block_to_extra(std::vector<uint8_t>& tx_extra, const tx_extra_keva_block& keva_block);
bool get_keva_block_from_extra(const std::vector<uint8_t>& tx_extra, tx_extra_keva_block& keva_block);
bool append_keva_blockhash_to_extra(std::vector<uint8_t>& tx_extra, const tx_extra_keva_blockhash& keva_blockhash);
bool get_keva_blockhash_from_extra(const std::vector<uint8_t>& tx_extra, tx_extra_keva_blockhash& keva_blockhash);
bool remove_field_from_tx_extra(std::vector<uint8_t>& tx_extra, const std::type_info &type);
void set_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash& payment_id);
void set_encrypted_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash8& payment_id);

View File

@ -38,10 +38,17 @@
#define TX_EXTRA_TAG_PUBKEY 0x01
#define TX_EXTRA_NONCE 0x02
#define TX_EXTRA_MERGE_MINING_TAG 0x03
// TX_EXTRA_KEVA_BLOCK_HASH_TAG is exactly the same as
// TX_EXTRA_MERGE_MINING_TAG
#define TX_EXTRA_KEVA_BLOCKHASH_TAG 0x03
#define TX_EXTRA_TAG_ADDITIONAL_PUBKEYS 0x04
#define TX_EXTRA_MYSTERIOUS_MINERGATE_TAG 0xDE
#define TX_EXTRA_KEVA_BLOCK_TAG 0xa1
// TX_EXTRA_KEVA_BLOCK_TAG is exactly the same as
// TX_EXTRA_MYSTERIOUS_MINERGATE_TAG
#define TX_EXTRA_KEVA_BLOCK_TAG 0xDE
#define TX_EXTRA_NONCE_PAYMENT_ID 0x00
#define TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID 0x01
@ -162,6 +169,8 @@ namespace cryptonote
}
};
typedef tx_extra_merge_mining_tag tx_extra_keva_blockhash;
// per-output additional tx pubkey for multi-destination transfers involving at least one subaddress
struct tx_extra_additional_pub_keys
{
@ -181,21 +190,13 @@ namespace cryptonote
END_SERIALIZE()
};
struct tx_extra_keva_block
{
std::string keva_block;
BEGIN_SERIALIZE()
FIELD(keva_block)
END_SERIALIZE()
};
typedef tx_extra_mysterious_minergate tx_extra_keva_block;
// tx_extra_field format, except tx_extra_padding and tx_extra_pub_key:
// varint tag;
// varint size;
// varint data[];
typedef boost::variant<tx_extra_padding, tx_extra_pub_key, tx_extra_nonce, tx_extra_merge_mining_tag, tx_extra_additional_pub_keys, tx_extra_mysterious_minergate, tx_extra_keva_block> tx_extra_field;
typedef boost::variant<tx_extra_padding, tx_extra_pub_key, tx_extra_nonce, tx_extra_merge_mining_tag, tx_extra_additional_pub_keys, tx_extra_mysterious_minergate, tx_extra_keva_block, tx_extra_keva_blockhash> tx_extra_field;
}
VARIANT_TAG(binary_archive, cryptonote::tx_extra_padding, TX_EXTRA_TAG_PADDING);
@ -204,4 +205,3 @@ VARIANT_TAG(binary_archive, cryptonote::tx_extra_nonce, TX_EXTRA_NONCE);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_merge_mining_tag, TX_EXTRA_MERGE_MINING_TAG);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_additional_pub_keys, TX_EXTRA_TAG_ADDITIONAL_PUBKEYS);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_mysterious_minergate, TX_EXTRA_MYSTERIOUS_MINERGATE_TAG);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_keva_block, TX_EXTRA_KEVA_BLOCK_TAG);

View File

@ -14,6 +14,8 @@
extern "C" void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed, uint64_t height);
const uint256 CBlockHeader::DIFFICULTY_1 = uint256S("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
uint256 CBlockHeader::GetHash() const
{
CHashWriter hashWriter(SER_GETHASH, PROTOCOL_VERSION);
@ -30,13 +32,43 @@ uint256 CBlockHeader::GetPoWHash() const
return thash;
}
// prev_id of CN header is used to store the kevacoin block hash.
// The value of prev_id and block hash must be the same to prove
// that PoW has been properly done.
if (GetHash() != cnHeader.prev_id) {
memset(thash.begin(), 0xff, thash.size());
if (!IsAuxpow()) {
// prev_id of CN header is used to store the kevacoin block hash.
// The value of prev_id and block hash must be the same to prove
// that PoW has been properly done.
if (GetHash() != cnHeader.prev_id) {
return DIFFICULTY_1;
}
cryptonote::blobdata blob = cryptonote::t_serializable_object_to_blob(cnHeader);
cn_slow_hash(blob.data(), blob.size(), BEGIN(thash), 2, 0, 0);
return thash;
}
// Merged mining.
cryptonote::tx_extra_keva_blockhash keva_blockhash;
if (!cryptonote::get_keva_blockhash_from_extra(cnHeader.aux_pow->miner_tx.extra, keva_blockhash)) {
return DIFFICULTY_1;
}
uint256 actualHash = GetHash();
if (memcmp(keva_blockhash.merkle_root.data, actualHash.begin(), thash.size()) != 0) {
return DIFFICULTY_1;
}
crypto::hash miner_tx_hash;
if (!cryptonote::get_transaction_hash(cnHeader.aux_pow->miner_tx, miner_tx_hash)) {
return DIFFICULTY_1;
}
crypto::hash aux_blocks_merkle_root;
crypto::tree_hash_from_branch(
reinterpret_cast<const char (*)[32]>(cnHeader.aux_pow->merkle_branch.data()),
cnHeader.aux_pow->merkle_branch.size(),
reinterpret_cast<char*>(&miner_tx_hash), 0,
reinterpret_cast<char*>(&aux_blocks_merkle_root));
if (memcmp(aux_blocks_merkle_root.data, cnHeader.merkle_root.begin(), cnHeader.merkle_root.size()) != 0) {
return DIFFICULTY_1;
}
cryptonote::blobdata blob = cryptonote::t_serializable_object_to_blob(cnHeader);
cn_slow_hash(blob.data(), blob.size(), BEGIN(thash), 2, 0, 0);
return thash;

View File

@ -12,6 +12,56 @@
#include <cryptonote_basic/cryptonote_format_utils.h>
class CAuxPow
{
public:
// Cryptnote coinbase tx, which contains the block hash of kevacoin block.
cryptonote::transaction miner_tx;
// Merkle branch is used to establish that miner_tx is part of the
// merkel tree whose root is merkle_root.
std::vector<crypto::hash> merkle_branch;
uint256 CheckMerkleBranch (uint256 hash,
const std::vector<uint256>& vMerkleBranch,
int nIndex)
{
if (nIndex == -1) {
return uint256();
}
#if 0
for (std::vector<uint256>::const_iterator it(vMerkleBranch.begin());
it != vMerkleBranch.end(); ++it) {
if (nIndex & 1) {
hash = Hash(BEGIN(*it), END(*it), BEGIN(hash), END(hash));
} else {
hash = Hash(BEGIN(hash), END(hash), BEGIN(*it), END(*it));
}
nIndex >>= 1;
}
return hash;
#endif
}
// load
template <template <bool> class Archive>
bool do_serialize(Archive<false>& ar)
{
FIELD(miner_tx)
FIELD(merkle_branch)
return true;
}
// store
template <template <bool> class Archive>
bool do_serialize(Archive<true>& ar)
{
FIELD(miner_tx)
FIELD(merkle_branch)
return true;
}
};
/**
* This header is to store the proof-of-work of cryptonote mining.
* Kevacoin uses Cryptonight PoW and uses its existing infrastructure
@ -28,11 +78,27 @@ public:
uint256 merkle_root;
size_t nTxes; // Number of transactions.
CryptoNoteHeader()
private:
bool is_aux_block;
public:
std::unique_ptr<CAuxPow> aux_pow; // It is used only is_aux_block is true.
CryptoNoteHeader() : is_aux_block(false)
{
SetNull();
}
void SetAuxBlock(bool isAuxBlock)
{
is_aux_block = isAuxBlock;
}
bool IsAuxBlock()
{
return is_aux_block;
}
void SetNull()
{
major_version = 0;
@ -64,6 +130,10 @@ public:
FIELD(merkle_hash)
memcpy(merkle_root.begin(), &merkle_hash, merkle_root.size());
VARINT_FIELD(nTxes)
if (is_aux_block) {
aux_pow_ptr = std::make_unique<CAuxPow>();
FIELD(*aux_pow_ptr)
}
return true;
}
@ -82,6 +152,9 @@ public:
memcpy(&merkle_hash, merkle_root.begin(), merkle_root.size());
FIELD(merkle_hash)
VARINT_FIELD(nTxes)
if (is_aux_block) {
FIELD(*aux_pow)
}
return true;
}
@ -124,6 +197,7 @@ public:
uint256 hashMerkleRoot;
uint32_t nTime;
uint32_t nBits;
// nNonce is used to store chainID in merged mining.
uint32_t nNonce;
// CryptoNote header for emulation or merged mining
@ -146,6 +220,7 @@ public:
READWRITE(nNonce);
// Genesis block does not have cnHeader.
if (!hashPrevBlock.IsNull()) {
cnHeader.SetAuxBlock(IsAuxpow());
READWRITE(cnHeader);
}
}
@ -173,6 +248,30 @@ public:
{
return (int64_t)nTime;
}
static const int32_t DEFAULT_AUXPOW_CHAIN_ID = 0x000000AD;
// ChainID is used for merged mining.
inline void SetChainID(uint32_t chainID = CBlockHeader::DEFAULT_AUXPOW_CHAIN_ID)
{
nNonce = chainID;
}
inline uint32_t GetChainID() const
{
return nNonce;
}
/**
* Check if the auxpow flag is set in the version.
* @return True iff this block version is marked as auxpow.
*/
inline bool IsAuxpow() const
{
return nNonce != 0;
}
static const uint256 DIFFICULTY_1;
};
@ -193,7 +292,13 @@ public:
CBlock(const CBlockHeader &header)
{
SetNull();
*((CBlockHeader*)this) = header;
//*((CBlockHeader*)this) = header;
nVersion = header.nVersion;
hashPrevBlock = header.hashPrevBlock;
hashMerkleRoot = header.hashMerkleRoot;
nTime = header.nTime;
nBits = header.nBits;
nNonce = header.nNonce;
}
ADD_SERIALIZE_METHODS;

View File

@ -302,8 +302,8 @@ std::string gbt_vb_name(const Consensus::DeploymentPos pos) {
UniValue getblocktemplate(const JSONRPCRequest& request)
{
// JSON-RPC2 request
// {reserve_size: 8, wallet_address: poolAddress}
if (request.fHelp || request.params.size() != 2)
// {reserve_size: 8, wallet_address: poolAddress, aux_block: true | false}
if (request.fHelp || request.params.size() < 2)
throw std::runtime_error(
"getblocktemplate ( TemplateRequest )\n"
"\nIf the request parameters include a 'mode' key, that is used to explicitly select between the default 'template' request or a 'proposal'.\n"
@ -405,6 +405,14 @@ UniValue getblocktemplate(const JSONRPCRequest& request)
throw JSONRPCError(RPC_INVALID_PARAMS, "Invalid wallet address");
}
bool is_aux_block = false;
if (request.params.size() == 3) {
if (request.params[2].getType() != UniValue::VBOOL) {
throw JSONRPCError(RPC_INVALID_PARAMS, "Incorrect is_autx_block type, boolean expected");
}
is_aux_block = request.params[2].get_bool();
}
LOCK(cs_main);
std::string strMode = "template";
@ -448,7 +456,12 @@ UniValue getblocktemplate(const JSONRPCRequest& request)
// Update nTime
UpdateTime(pblock, consensusParams, pindexPrev);
pblock->nNonce = 0;
if (is_aux_block) {
// Mark it as auxpow block.
pblock->SetChainID();
} else {
pblock->nNonce = 0;
}
std::set<std::string> setClientRules;
UniValue aRules(UniValue::VARR);
@ -544,7 +557,7 @@ UniValue getblocktemplate(const JSONRPCRequest& request)
stream << *pblock;
std::string kevaBlockData = stream.str();
cryptonote::tx_extra_keva_block extra_keva_block;
extra_keva_block.keva_block = kevaBlockData;
extra_keva_block.data = kevaBlockData;
if (!cryptonote::append_keva_block_to_extra(cn_block.miner_tx.extra, extra_keva_block)) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Internal error: failed to add block");
}
@ -608,7 +621,7 @@ static uint256 CryptoHashToUint256(const crypto::hash& hash)
// Cryptonote RPC call, only one parameter allowed.
UniValue submitblock(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 1) {
if (request.fHelp || request.params.size() < 2) {
throw std::runtime_error(
"submitblock \"hexdata\"\n"
"\nAttempts to submit new block to network.\n"
@ -616,7 +629,7 @@ UniValue submitblock(const JSONRPCRequest& request)
"\nArguments\n"
"1. \"hexdata\" (string, required) the hex-encoded block data to submit\n"
"2. \"dummy\" (optional) dummy value, for compatibility with BIP22. This value is ignored.\n"
"2. \"auxpow\" (string, optional) block auxpow data for merged mining.\n"
"\nResult:\n"
"\nExamples:\n"
+ HelpExampleCli("submitblock", "\"mydata\"")
@ -624,15 +637,26 @@ UniValue submitblock(const JSONRPCRequest& request)
);
}
std::unique_ptr<CAuxPow> auxPow = std::make_unique<CAuxPow>();
bool isAuxPow = false;
if (request.params.size() == 2) {
if (request.params[1].getType() != UniValue::VSTR) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "auxpow must be string type");
}
std::stringstream ss;
ss << request.params[1].get_str();
// load
binary_archive<false> ba(ss);
isAuxPow = ::serialization::serialize(ba, *auxPow);
}
cryptonote::blobdata blockblob;
if(!epee::string_tools::parse_hexstr_to_binbuff(request.params[0].get_str(), blockblob))
{
if(!epee::string_tools::parse_hexstr_to_binbuff(request.params[0].get_str(), blockblob)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Wrong block blob");
}
cryptonote::block cnblock = AUTO_VAL_INIT(cnblock);
if(!cryptonote::parse_and_validate_block_from_blob(blockblob, cnblock))
{
if(!cryptonote::parse_and_validate_block_from_blob(blockblob, cnblock)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Wrong block blob");
}
@ -643,7 +667,7 @@ UniValue submitblock(const JSONRPCRequest& request)
std::shared_ptr<CBlock> blockptr = std::make_shared<CBlock>();
CBlock& block = *blockptr;
const std::vector<char> keva_block(keva_block_blob.keva_block.begin(), keva_block_blob.keva_block.end());
const std::vector<char> keva_block(keva_block_blob.data.begin(), keva_block_blob.data.end());
CDataStream ssBlock(keva_block, SER_NETWORK, PROTOCOL_VERSION);
try {
ssBlock >> block;
@ -672,6 +696,11 @@ UniValue submitblock(const JSONRPCRequest& request)
throw JSONRPCError(RPC_VERIFY_ERROR, "Kevacoin block hash does not match cryptnote hash");
}
block.cnHeader.SetAuxBlock(isAuxPow);
if (isAuxPow) {
block.cnHeader.aux_pow = std::move(auxPow);
}
// TODO: fix the return message.
bool fBlockPresent = false;
{
@ -924,8 +953,8 @@ static const CRPCCommand commands[] =
{ "mining", "getnetworkhashps", &getnetworkhashps, {"nblocks","height"} },
{ "mining", "getmininginfo", &getmininginfo, {} },
{ "mining", "prioritisetransaction", &prioritisetransaction, {"txid","dummy","fee_delta"} },
{ "mining", "getblocktemplate", &getblocktemplate, {"reserve_size", "wallet_address"} },
{ "mining", "submitblock", &submitblock, {"hexdata","dummy"} },
{ "mining", "getblocktemplate", &getblocktemplate, {"reserve_size", "wallet_address", "is_aux_block"} },
{ "mining", "submitblock", &submitblock, {"hexdata","auxpow"} },
{ "generating", "generatetoaddress", &generatetoaddress, {"nblocks","address","maxtries"} },