Add Monero and Peya block adapters to shared util

This commit is contained in:
Codex Bot
2026-03-21 15:08:23 +01:00
parent e8af2531ce
commit 6959014a0e
4 changed files with 275 additions and 23 deletions

View File

@@ -1,15 +1,46 @@
module.exports = require('bindings')('cryptoforknote.node');
const SHA3 = require('sha3');
const bignum = require('bignum');
const bitcoin = require('bitcoinjs-lib');
const varuint = require('varuint-bitcoin');
const crypto = require('crypto');
const fastMerkleRoot = require('merkle-lib/fastRoot');
const rtm = require('cryptoforknote-util/rtm');
let bitcoin;
let bignum;
let SHA3;
let varuint;
let fastMerkleRoot;
let rtm;
function getBitcoin() {
if (!bitcoin) bitcoin = require('bitcoinjs-lib');
return bitcoin;
}
function getBignum() {
if (!bignum) bignum = require('bignum');
return bignum;
}
function getSHA3() {
if (!SHA3) SHA3 = require('sha3');
return SHA3;
}
function getVaruint() {
if (!varuint) varuint = require('varuint-bitcoin');
return varuint;
}
function getFastMerkleRoot() {
if (!fastMerkleRoot) fastMerkleRoot = require('merkle-lib/fastRoot');
return fastMerkleRoot;
}
function getRtm() {
if (!rtm) rtm = require('cryptoforknote-util/rtm');
return rtm;
}
function scriptCompile(addrHash) {
const bitcoin = getBitcoin();
return bitcoin.script.compile([
bitcoin.opcodes.OP_DUP,
bitcoin.opcodes.OP_HASH160,
@@ -65,6 +96,7 @@ function transaction_hash3(transaction, forWitness) {
}
function getMerkleRoot(transactions, transaction_hash_func, detectWitness) {
const fastMerkleRoot = getFastMerkleRoot();
if (transactions.length === 0) return Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex')
const forWitness = detectWitness ? txesHaveWitnessCommit(transactions) : false;
const hashes = transactions.map(transaction => transaction_hash_func(transaction, forWitness));
@@ -76,6 +108,7 @@ let last_epoch_number;
let last_seed_hash;
module.exports.baseDiff = function() {
const bignum = getBignum();
return bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16);
};
@@ -84,6 +117,9 @@ module.exports.baseRavenDiff = function() {
};
module.exports.RavenBlockTemplate = function(rpcData, poolAddress) {
const bignum = getBignum();
const bitcoin = getBitcoin();
const varuint = getVaruint();
const poolAddrHash = bitcoin.address.fromBase58Check(poolAddress).hash;
let txCoinbase = new bitcoin.Transaction();
@@ -147,6 +183,7 @@ module.exports.RavenBlockTemplate = function(rpcData, poolAddress) {
const EPOCH_LENGTH = 7500;
const epoch_number = Math.floor(rpcData.height / EPOCH_LENGTH);
if (last_epoch_number !== epoch_number) {
const SHA3 = getSHA3();
let sha3 = new SHA3.SHA3Hash(256);
if (last_epoch_number && last_epoch_number + 1 === epoch_number) {
last_seed_hash = sha3.update(last_seed_hash).digest();
@@ -176,6 +213,8 @@ module.exports.RavenBlockTemplate = function(rpcData, poolAddress) {
};
function update_merkle_root_hash(offset, payload, blob_in, blob_out, transaction_hash_func, detectWitness) {
const bitcoin = getBitcoin();
const varuint = getVaruint();
const nTransactions = varuint.decode(blob_in, offset);
offset += varuint.decode.bytes;
let transactions = [];
@@ -214,6 +253,7 @@ module.exports.constructNewDeroBlob = function(blockTemplate, nonceBuff) {
};
module.exports.EthBlockTemplate = function(rpcData) {
const bignum = getBignum();
const difficulty = module.exports.baseDiff().div(bignum(rpcData[2].substr(2), 16)).toNumber();
return {
hash: rpcData[0].substr(2),
@@ -224,6 +264,7 @@ module.exports.EthBlockTemplate = function(rpcData) {
};
module.exports.ErgBlockTemplate = function(rpcData) {
const bignum = getBignum();
const difficulty = module.exports.baseDiff().div(bignum(rpcData.b)).toNumber();
return {
hash: rpcData.msg,
@@ -234,7 +275,7 @@ module.exports.ErgBlockTemplate = function(rpcData) {
};
module.exports.RtmBlockTemplate = function(rpcData, poolAddress) {
return rtm.RtmBlockTemplate(rpcData, poolAddress);
return getRtm().RtmBlockTemplate(rpcData, poolAddress);
};
module.exports.convertRtmBlob = function(blobBuffer) {

View File

@@ -5,6 +5,7 @@
#pragma once
#include <boost/variant.hpp>
#include <boost/optional/optional.hpp>
#include <boost/functional/hash/hash.hpp>
#include <iostream>
#include <vector>
@@ -30,6 +31,16 @@
namespace cryptonote
{
inline bool is_salvium_family_blob_type(const BLOB_TYPE blob_type)
{
return blob_type == BLOB_TYPE_CRYPTONOTE_SALVIUM;
}
inline bool is_monero_family_blob_type(const BLOB_TYPE blob_type)
{
return blob_type == BLOB_TYPE_CRYPTONOTE;
}
struct block;
class transaction;
class transaction_prefix;
@@ -174,6 +185,7 @@ namespace cryptonote
};
typedef boost::variant<txin_gen, txin_to_script, txin_to_scripthash, txin_to_key> txin_v;
typedef boost::variant<txin_gen, txin_to_script, txin_to_scripthash, txin_to_key> monero_txin_v;
typedef boost::variant<txout_to_script, txout_to_scripthash, txout_to_key, txout_to_tagged_key, txout_to_carrot_v1> txout_target_v;
@@ -188,6 +200,43 @@ namespace cryptonote
END_SERIALIZE()
};
struct monero_txout_to_key
{
monero_txout_to_key() { }
explicit monero_txout_to_key(const crypto::public_key &_key) : key(_key) { }
crypto::public_key key;
BEGIN_SERIALIZE_OBJECT()
FIELD(key)
END_SERIALIZE()
};
struct monero_txout_to_tagged_key
{
monero_txout_to_tagged_key() { }
monero_txout_to_tagged_key(const crypto::public_key &_key, const crypto::view_tag &_view_tag) : key(_key), view_tag(_view_tag) { }
crypto::public_key key;
crypto::view_tag view_tag;
BEGIN_SERIALIZE_OBJECT()
FIELD(key)
FIELD(view_tag)
END_SERIALIZE()
};
typedef boost::variant<txout_to_script, txout_to_scripthash, monero_txout_to_key, monero_txout_to_tagged_key> monero_txout_target_v;
struct monero_tx_out
{
uint64_t amount;
monero_txout_target_v target;
BEGIN_SERIALIZE_OBJECT()
VARINT_FIELD(amount)
FIELD(target)
END_SERIALIZE()
};
class protocol_tx_data_t {
public:
uint8_t version;
@@ -205,6 +254,71 @@ namespace cryptonote
END_SERIALIZE()
};
class monero_coinbase_transaction_t
{
public:
size_t version;
uint64_t unlock_time;
std::vector<monero_txin_v> vin;
std::vector<monero_tx_out> vout;
std::vector<uint8_t> extra;
BEGIN_SERIALIZE_OBJECT()
VARINT_FIELD(version)
if (version != 2) return false;
VARINT_FIELD(unlock_time)
FIELD(vin)
FIELD(vout)
FIELD(extra)
uint8_t rct_signatures_type = 0;
FIELD(rct_signatures_type)
if (rct_signatures_type != 0) return false;
END_SERIALIZE()
public:
monero_coinbase_transaction_t() { set_null(); }
void set_null()
{
version = 2;
unlock_time = 0;
vin.clear();
vout.clear();
extra.clear();
}
};
struct monero_block_header_t
{
uint8_t major_version;
uint8_t minor_version;
uint64_t timestamp;
crypto::hash prev_id;
uint32_t nonce;
monero_block_header_t(): major_version(0), minor_version(0), timestamp(0), prev_id(cryptonote::null_hash), nonce(0) {}
BEGIN_SERIALIZE_OBJECT()
VARINT_FIELD(major_version)
VARINT_FIELD(minor_version)
VARINT_FIELD(timestamp)
FIELD(prev_id)
FIELD(nonce)
END_SERIALIZE()
};
struct monero_block_t: public monero_block_header_t
{
monero_coinbase_transaction_t miner_tx;
std::vector<crypto::hash> tx_hashes;
BEGIN_SERIALIZE_OBJECT()
FIELDS(*static_cast<monero_block_header_t *>(this))
FIELD(miner_tx)
FIELD(tx_hashes)
END_SERIALIZE()
};
struct erc_token_t
{
uint8_t version;
@@ -537,19 +651,46 @@ namespace cryptonote
END_SERIALIZE()
};
struct aux_header_t
{
BEGIN_SERIALIZE_OBJECT()
END_SERIALIZE()
};
struct block: public block_header
{
transaction miner_tx;
transaction protocol_tx;
std::vector<crypto::hash> tx_hashes;
boost::optional<aux_header_t> aux_header;
void set_blob_type(enum BLOB_TYPE bt) { miner_tx.blob_type = protocol_tx.blob_type = blob_type = bt; }
BEGIN_SERIALIZE_OBJECT()
FIELDS(*static_cast<block_header *>(this))
FIELD(miner_tx)
FIELD(protocol_tx)
if (is_salvium_family_blob_type(blob_type))
FIELD(protocol_tx)
FIELD(tx_hashes)
if (is_salvium_family_blob_type(blob_type) && major_version >= HF_VERSION_MERGE_MINING)
{
uint8_t has_aux_header = !!aux_header;
FIELD(has_aux_header)
if (has_aux_header)
{
if (!typename Archive<W>::is_saving())
aux_header = aux_header_t();
FIELD_N("aux_header", *aux_header)
}
else if (!typename Archive<W>::is_saving())
{
aux_header = boost::none;
}
}
else if (!typename Archive<W>::is_saving())
{
aux_header = boost::none;
}
END_SERIALIZE()
};
@@ -615,6 +756,8 @@ VARIANT_TAG(binary_archive, cryptonote::txout_to_scripthash, 0x1);
VARIANT_TAG(binary_archive, cryptonote::txout_to_key, 0x2);
VARIANT_TAG(binary_archive, cryptonote::txout_to_tagged_key, 0x3);
VARIANT_TAG(binary_archive, cryptonote::txout_to_carrot_v1, 0x4);
VARIANT_TAG(binary_archive, cryptonote::monero_txout_to_key, 0x2);
VARIANT_TAG(binary_archive, cryptonote::monero_txout_to_tagged_key, 0x3);
VARIANT_TAG(binary_archive, cryptonote::protocol_tx_data_t, 0x0);
VARIANT_TAG(binary_archive, cryptonote::token_metadata_t, 0x0);
VARIANT_TAG(binary_archive, cryptonote::erc_token_t, 0x0);

View File

@@ -2,6 +2,7 @@
#define CURRENT_TRANSACTION_VERSION 5
#define HF_VERSION_ENABLE_N_OUTS 2
#define HF_VERSION_MERGE_MINING 13
#define TRANSACTION_VERSION_N_OUTS 3
#define TRANSACTION_VERSION_CARROT 4
#define TRANSACTION_VERSION_ENABLE_TOKENS 5

View File

@@ -20,6 +20,56 @@ using namespace cryptonote;
// cryptonote::append_mm_tag_to_extra writes byte with TX_EXTRA_MERGE_MINING_TAG (1 here) and VARINT DEPTH (2 here)
const size_t MM_NONCE_SIZE = 1 + 2 + sizeof(crypto::hash);
static bool parse_monero_block_from_blob(const blobdata &input, cryptonote::monero_block_t &b) {
std::stringstream ss;
ss << input;
binary_archive<false> ba(ss);
return ::serialization::serialize(ba, b);
}
static bool monero_block_to_blob(const cryptonote::monero_block_t &b, blobdata &blob) {
return cryptonote::t_serializable_object_to_blob(b, blob);
}
static crypto::hash get_monero_coinbase_hash(const cryptonote::monero_coinbase_transaction_t &t) {
crypto::hash hashes[3];
std::ostringstream s;
binary_archive<true> a(s);
::serialization::serialize(a, const_cast<cryptonote::monero_coinbase_transaction_t&>(t));
if (s.str().empty()) {
return cryptonote::null_hash;
}
crypto::cn_fast_hash(s.str().data(), s.str().size() - 1, hashes[0]);
const blobdata blob = blobdata(1, '\0');
cryptonote::get_blob_hash(blob, hashes[1]);
hashes[2] = cryptonote::null_hash;
return crypto::cn_fast_hash(hashes, sizeof(hashes));
}
static bool get_monero_block_hashing_blob(const cryptonote::monero_block_t &b, blobdata &blob) {
blob = cryptonote::t_serializable_object_to_blob(static_cast<const cryptonote::monero_block_header_t&>(b));
std::vector<crypto::hash> tx_ids;
tx_ids.reserve(b.tx_hashes.size() + 1);
tx_ids.push_back(get_monero_coinbase_hash(b.miner_tx));
tx_ids.insert(tx_ids.end(), b.tx_hashes.begin(), b.tx_hashes.end());
crypto::hash tree_root_hash = cryptonote::null_hash;
cryptonote::get_tx_tree_hash(tx_ids, tree_root_hash);
blob.append(reinterpret_cast<const char*>(&tree_root_hash), sizeof(tree_root_hash));
blob.append(tools::get_varint_data(b.tx_hashes.size() + 1));
return true;
}
static bool get_monero_block_hash(const cryptonote::monero_block_t &b, crypto::hash &res) {
blobdata blob;
if (!monero_block_to_blob(b, blob)) {
return false;
}
cryptonote::get_blob_hash(blob, res);
return true;
}
blobdata uint64be_to_blob(uint64_t num) {
blobdata res = " ";
res[0] = num >> 56 & 0xff;
@@ -149,10 +199,16 @@ NAN_METHOD(convert_blob) { // (parentBlockBuffer, cnBlobType)
blob_type = static_cast<enum BLOB_TYPE>(Nan::To<int>(info[1]).FromMaybe(0));
}
block b = AUTO_VAL_INIT(b);
b.set_blob_type(blob_type);
if (!parse_and_validate_block_from_blob(input, b)) return THROW_ERROR_EXCEPTION("Failed to parse block 2");
if (!get_block_hashing_blob(b, output)) return THROW_ERROR_EXCEPTION("convert_blob: Failed to create mining block");
if (blob_type == BLOB_TYPE_CRYPTONOTE) {
monero_block_t b = AUTO_VAL_INIT(b);
if (!parse_monero_block_from_blob(input, b)) return THROW_ERROR_EXCEPTION("Failed to parse block 2");
if (!get_monero_block_hashing_blob(b, output)) return THROW_ERROR_EXCEPTION("convert_blob: Failed to create mining block");
} else {
block b = AUTO_VAL_INIT(b);
b.set_blob_type(blob_type);
if (!parse_and_validate_block_from_blob(input, b)) return THROW_ERROR_EXCEPTION("Failed to parse block 2");
if (!get_block_hashing_blob(b, output)) return THROW_ERROR_EXCEPTION("convert_blob: Failed to create mining block");
}
v8::Local<v8::Value> returnValue = Nan::CopyBuffer((char*)output.data(), output.size()).ToLocalChecked();
info.GetReturnValue().Set(returnValue);
@@ -173,12 +229,17 @@ NAN_METHOD(get_block_id) {
blob_type = static_cast<enum BLOB_TYPE>(Nan::To<int>(info[1]).FromMaybe(0));
}
block b = AUTO_VAL_INIT(b);
b.set_blob_type(blob_type);
if (!parse_and_validate_block_from_blob(input, b)) return THROW_ERROR_EXCEPTION("Failed to parse block");
crypto::hash block_id;
if (!get_block_hash(b, block_id)) return THROW_ERROR_EXCEPTION("Failed to calculate hash for block");
if (blob_type == BLOB_TYPE_CRYPTONOTE) {
monero_block_t b = AUTO_VAL_INIT(b);
if (!parse_monero_block_from_blob(input, b)) return THROW_ERROR_EXCEPTION("Failed to parse block");
if (!get_monero_block_hash(b, block_id)) return THROW_ERROR_EXCEPTION("Failed to calculate hash for block");
} else {
block b = AUTO_VAL_INIT(b);
b.set_blob_type(blob_type);
if (!parse_and_validate_block_from_blob(input, b)) return THROW_ERROR_EXCEPTION("Failed to parse block");
if (!get_block_hash(b, block_id)) return THROW_ERROR_EXCEPTION("Failed to calculate hash for block");
}
char *cstr = reinterpret_cast<char*>(&block_id);
v8::Local<v8::Value> returnValue = Nan::CopyBuffer(cstr, 32).ToLocalChecked();
@@ -206,12 +267,18 @@ NAN_METHOD(construct_block_blob) { // (parentBlockTemplateBuffer, nonceBuffer, c
blobdata block_template_blob = std::string(Buffer::Data(block_template_buf), Buffer::Length(block_template_buf));
blobdata output = "";
block b = AUTO_VAL_INIT(b);
b.set_blob_type(blob_type);
if (!parse_and_validate_block_from_blob(block_template_blob, b)) return THROW_ERROR_EXCEPTION("Failed to parse block");
b.nonce = nonce;
if (!block_to_blob(b, output)) return THROW_ERROR_EXCEPTION("Failed to convert block to blob");
if (blob_type == BLOB_TYPE_CRYPTONOTE) {
monero_block_t b = AUTO_VAL_INIT(b);
if (!parse_monero_block_from_blob(block_template_blob, b)) return THROW_ERROR_EXCEPTION("Failed to parse block");
b.nonce = nonce;
if (!monero_block_to_blob(b, output)) return THROW_ERROR_EXCEPTION("Failed to convert block to blob");
} else {
block b = AUTO_VAL_INIT(b);
b.set_blob_type(blob_type);
if (!parse_and_validate_block_from_blob(block_template_blob, b)) return THROW_ERROR_EXCEPTION("Failed to parse block");
b.nonce = nonce;
if (!block_to_blob(b, output)) return THROW_ERROR_EXCEPTION("Failed to convert block to blob");
}
v8::Local<v8::Value> returnValue = Nan::CopyBuffer((char*)output.data(), output.size()).ToLocalChecked();
info.GetReturnValue().Set(returnValue);