1
0
Fork 0
forked from lthn/blockchain
blockchain/tests/core_tests/pos_block_builder.cpp

321 lines
13 KiB
C++
Raw Normal View History

2018-12-27 18:50:45 +03:00
// Copyright (c) 2014-2018 Zano Project
// Copyright (c) 2014-2018 The Louisdor Project
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "chaingen.h"
#include "pos_block_builder.h"
2022-05-11 21:23:44 +02:00
using namespace epee;
2018-12-27 18:50:45 +03:00
using namespace currency;
pos_block_builder::pos_block_builder()
{
clear();
}
void pos_block_builder::clear()
{
m_block = AUTO_VAL_INIT(m_block);
m_stake_kernel = AUTO_VAL_INIT(m_stake_kernel);
m_step = 0;
}
void pos_block_builder::step1_init_header(size_t block_height, crypto::hash& prev_block_hash)
{
CHECK_AND_ASSERT_THROW_MES(m_step == 0, "pos_block_builder: incorrect step sequence");
m_block.minor_version = CURRENT_BLOCK_MINOR_VERSION;
2021-04-28 21:26:23 +03:00
m_block.major_version = BLOCK_MAJOR_VERSION_INITIAL;
2018-12-27 18:50:45 +03:00
m_block.timestamp = 0; // to be set at step 3
m_block.prev_id = prev_block_hash;
m_block.flags = CURRENCY_BLOCK_FLAG_POS_BLOCK;
m_block.nonce = 0;
m_height = block_height;
m_step = 1;
}
void pos_block_builder::step2_set_txs(const std::vector<currency::transaction>& txs)
{
CHECK_AND_ASSERT_THROW_MES(m_step == 1, "pos_block_builder: incorrect step sequence");
m_total_fee = 0;
m_txs_total_size = 0;
m_block.tx_hashes.reserve(txs.size());
for (auto& tx : txs)
{
uint64_t fee = 0;
bool r = get_tx_fee(tx, fee);
CHECK_AND_ASSERT_THROW_MES(r, "wrong transaction passed to step2_set_txs");
m_total_fee += fee;
m_txs_total_size += get_object_blobsize(tx);
m_block.tx_hashes.push_back(get_transaction_hash(tx));
}
m_step = 2;
}
void pos_block_builder::step3_build_stake_kernel(
uint64_t stake_output_amount,
size_t stake_output_gindex,
const crypto::key_image& stake_output_key_image,
currency::wide_difficulty_type difficulty,
const crypto::hash& last_pow_block_hash,
const crypto::hash& last_pos_block_kernel_hash,
uint64_t timestamp_lower_bound,
uint64_t timestamp_window,
uint64_t timestamp_step)
{
CHECK_AND_ASSERT_THROW_MES(m_step == 2, "pos_block_builder: incorrect step sequence");
m_pos_stake_amount = stake_output_amount;
m_pos_stake_output_gindex = stake_output_gindex;
m_stake_kernel.kimage = stake_output_key_image;
m_stake_kernel.block_timestamp = m_block.timestamp;
m_stake_kernel.stake_modifier.last_pow_id = last_pow_block_hash;
m_stake_kernel.stake_modifier.last_pos_kernel_id = last_pos_block_kernel_hash;
if (last_pos_block_kernel_hash == null_hash)
{
bool r = string_tools::parse_tpod_from_hex_string(POS_STARTER_KERNEL_HASH, m_stake_kernel.stake_modifier.last_pos_kernel_id);
2019-09-07 12:46:25 +03:00
CHECK_AND_ASSERT_THROW_MES(r, "Failed to parse POS_STARTER_KERNEL_HASH");
2018-12-27 18:50:45 +03:00
}
wide_difficulty_type stake_difficulty = difficulty / stake_output_amount;
// align timestamp_lower_bound up to timestamp_step boundary if needed
if (timestamp_lower_bound % timestamp_step != 0)
timestamp_lower_bound = timestamp_lower_bound - (timestamp_lower_bound % timestamp_step) + timestamp_step;
bool sk_found = false;
for (uint64_t ts = timestamp_lower_bound; !sk_found && ts < timestamp_lower_bound + timestamp_window; ts += timestamp_step)
{
m_stake_kernel.block_timestamp = ts;
crypto::hash sk_hash = crypto::cn_fast_hash(&m_stake_kernel, sizeof(m_stake_kernel));
if (check_hash(sk_hash, stake_difficulty))
{
sk_found = true;
}
}
if (!sk_found)
ASSERT_MES_AND_THROW("Could't build stake kernel");
// update block header with found timestamp
m_block.timestamp = m_stake_kernel.block_timestamp;
m_step = 3;
}
2019-09-06 18:59:02 +03:00
void pos_block_builder::step4_generate_coinbase_tx(size_t median_size,
const boost::multiprecision::uint128_t& already_generated_coins,
const account_public_address &reward_and_stake_receiver_address,
const blobdata& extra_nonce,
size_t max_outs,
const extra_alias_entry& alias,
keypair tx_one_time_key)
{
step4_generate_coinbase_tx(median_size, already_generated_coins, reward_and_stake_receiver_address, reward_and_stake_receiver_address, extra_nonce, max_outs, alias, tx_one_time_key);
}
2018-12-27 18:50:45 +03:00
void pos_block_builder::step4_generate_coinbase_tx(size_t median_size,
2019-04-10 03:00:29 +02:00
const boost::multiprecision::uint128_t& already_generated_coins,
2018-12-27 18:50:45 +03:00
const account_public_address &reward_receiver_address,
2019-09-06 18:59:02 +03:00
const account_public_address &stakeholder_address,
2018-12-27 18:50:45 +03:00
const blobdata& extra_nonce,
size_t max_outs,
const extra_alias_entry& alias,
keypair tx_one_time_key)
{
CHECK_AND_ASSERT_THROW_MES(m_step == 3, "pos_block_builder: incorrect step sequence");
// generate miner tx using incorrect current_block_size only for size estimation
size_t estimated_block_size = m_txs_total_size;
bool r = construct_homemade_pos_miner_tx(m_height, median_size, already_generated_coins, estimated_block_size, m_total_fee, m_pos_stake_amount, m_stake_kernel.kimage,
2019-09-06 18:59:02 +03:00
m_pos_stake_output_gindex, reward_receiver_address, stakeholder_address, m_block.miner_tx, extra_nonce, max_outs, tx_one_time_key);
2018-12-27 18:50:45 +03:00
CHECK_AND_ASSERT_THROW_MES(r, "construct_homemade_pos_miner_tx failed");
estimated_block_size = m_txs_total_size + get_object_blobsize(m_block.miner_tx);
size_t cumulative_size = 0;
for (size_t try_count = 0; try_count != 10; ++try_count)
{
r = construct_homemade_pos_miner_tx(m_height, median_size, already_generated_coins, estimated_block_size, m_total_fee, m_pos_stake_amount, m_stake_kernel.kimage,
2019-09-06 18:59:02 +03:00
m_pos_stake_output_gindex, reward_receiver_address, stakeholder_address, m_block.miner_tx, extra_nonce, max_outs, tx_one_time_key);
2018-12-27 18:50:45 +03:00
CHECK_AND_ASSERT_THROW_MES(r, "construct_homemade_pos_miner_tx failed");
cumulative_size = m_txs_total_size + get_object_blobsize(m_block.miner_tx);
if (cumulative_size == estimated_block_size)
break; // nice, got what we want
if (cumulative_size > estimated_block_size)
{
estimated_block_size = cumulative_size;
continue; // one more attempt
}
// TODO: implement this rare case
ASSERT_MES_AND_THROW("step4_generate_coinbase_tx implement todo");
}
CHECK_AND_ASSERT_THROW_MES(cumulative_size == estimated_block_size, "step4_generate_coinbase_tx failed to match tx and block size");
m_step = 4;
}
void pos_block_builder::step5_sign(const crypto::public_key& stake_tx_pub_key, size_t stake_tx_out_index, const crypto::public_key& stake_tx_out_pub_key, const currency::account_base& stakeholder_account)
{
CHECK_AND_ASSERT_THROW_MES(m_step == 4, "pos_block_builder: incorrect step sequence");
crypto::key_derivation pos_coin_derivation = AUTO_VAL_INIT(pos_coin_derivation);
2020-04-23 15:41:40 +03:00
bool r = crypto::generate_key_derivation(stake_tx_pub_key, stakeholder_account.get_keys().view_secret_key, pos_coin_derivation); // derivation(tx_pub; view_sec)
2018-12-27 18:50:45 +03:00
CHECK_AND_ASSERT_THROW_MES(r, "generate_key_derivation failed");
crypto::secret_key derived_secret_ephemeral_key = AUTO_VAL_INIT(derived_secret_ephemeral_key);
2020-04-23 15:41:40 +03:00
crypto::derive_secret_key(pos_coin_derivation, stake_tx_out_index, stakeholder_account.get_keys().spend_secret_key, derived_secret_ephemeral_key); // derivation.derive(spend_sec, out_idx) => input ephemeral secret key
2018-12-27 18:50:45 +03:00
// sign block actually in coinbase transaction
crypto::hash block_hash = currency::get_block_hash(m_block);
std::vector<const crypto::public_key*> keys_ptrs(1, &stake_tx_out_pub_key);
crypto::generate_ring_signature(block_hash, m_stake_kernel.kimage, keys_ptrs, derived_secret_ephemeral_key, 0, &m_block.miner_tx.signatures[0][0]);
m_step = 5;
}
2019-04-10 03:00:29 +02:00
bool construct_homemade_pos_miner_tx(size_t height, size_t median_size, const boost::multiprecision::uint128_t& already_generated_coins,
2018-12-27 18:50:45 +03:00
size_t current_block_size,
uint64_t fee,
uint64_t pos_stake_amount,
crypto::key_image pos_stake_keyimage,
size_t pos_stake_gindex,
2019-09-06 18:59:02 +03:00
const account_public_address &reward_receiving_address,
const account_public_address &stakeholder_address,
2018-12-27 18:50:45 +03:00
transaction& tx,
const blobdata& extra_nonce /*= blobdata()*/,
size_t max_outs /*= CURRENCY_MINER_TX_MAX_OUTS*/,
keypair tx_one_time_key /*= keypair::generate()*/)
{
boost::value_initialized<transaction> new_tx;
tx = new_tx;
2022-05-13 23:35:56 +02:00
tx.version = TRANSACTION_VERSION_PRE_HF4;
2018-12-27 18:50:45 +03:00
set_tx_unlock_time(tx, height + CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
// calculate block reward
uint64_t block_reward;
bool r = get_block_reward(true, median_size, current_block_size, already_generated_coins, block_reward, height);
CHECK_AND_ASSERT_MES(r, false, "Block is too big");
block_reward += fee;
// decompose reward into outputs and populate tx.vout
std::vector<size_t> out_amounts;
decompose_amount_into_digits(block_reward, DEFAULT_DUST_THRESHOLD,
[&out_amounts](uint64_t a_chunk) { out_amounts.push_back(a_chunk); },
[&out_amounts](uint64_t a_dust) { out_amounts.push_back(a_dust); });
2019-09-06 18:59:02 +03:00
CHECK_AND_ASSERT_MES(2 <= max_outs, false, "max_out must be greather than 1");
while (out_amounts.size() + 1 > max_outs)
2018-12-27 18:50:45 +03:00
{
out_amounts[out_amounts.size() - 2] += out_amounts.back();
out_amounts.resize(out_amounts.size() - 1);
}
2019-09-06 18:59:02 +03:00
// reward
2020-04-23 15:41:40 +03:00
bool burn_money = reward_receiving_address.spend_public_key == null_pkey && reward_receiving_address.view_public_key == null_pkey; // if true, burn reward, so no one on Earth can spend them
2018-12-27 18:50:45 +03:00
for (size_t output_index = 0; output_index < out_amounts.size(); ++output_index)
{
txout_to_key tk;
tk.key = null_pkey; // null means burn money
tk.mix_attr = 0;
if (!burn_money)
{
2019-09-06 18:59:02 +03:00
r = currency::derive_public_key_from_target_address(reward_receiving_address, tx_one_time_key.sec, output_index, tk.key); // derivation(view_pub; tx_sec).derive(output_index, spend_pub) => output pub key
2018-12-27 18:50:45 +03:00
CHECK_AND_ASSERT_MES(r, false, "failed to derive_public_key_from_target_address");
}
tx_out_old out;
2018-12-27 18:50:45 +03:00
out.amount = out_amounts[output_index];
out.target = tk;
tx.vout.push_back(out);
}
2019-09-06 18:59:02 +03:00
// stake
2020-04-23 15:41:40 +03:00
burn_money = stakeholder_address.spend_public_key == null_pkey && stakeholder_address.view_public_key == null_pkey; // if true, burn stake
2018-12-27 18:50:45 +03:00
{
2019-09-06 18:59:02 +03:00
txout_to_key tk;
tk.key = null_pkey; // null means burn money
tk.mix_attr = 0;
if (!burn_money)
2018-12-27 18:50:45 +03:00
{
2019-09-06 18:59:02 +03:00
r = currency::derive_public_key_from_target_address(stakeholder_address, tx_one_time_key.sec, tx.vout.size(), tk.key);
CHECK_AND_ASSERT_MES(r, false, "failed to derive_public_key_from_target_address");
2018-12-27 18:50:45 +03:00
}
2019-09-06 18:59:02 +03:00
tx_out_old out;
2019-09-06 18:59:02 +03:00
out.amount = pos_stake_amount;
out.target = tk;
tx.vout.push_back(out);
2018-12-27 18:50:45 +03:00
}
2019-09-06 18:59:02 +03:00
// take care about extra
add_tx_pub_key_to_extra(tx, tx_one_time_key.pub);
if (extra_nonce.size())
if (!add_tx_extra_userdata(tx, extra_nonce))
return false;
2018-12-27 18:50:45 +03:00
// populate ins with 1) money-generating and 2) PoS
txin_gen in;
in.height = height;
tx.vin.push_back(in);
txin_to_key posin;
posin.amount = pos_stake_amount;
posin.key_offsets.push_back(pos_stake_gindex);
posin.k_image = pos_stake_keyimage;
tx.vin.push_back(posin);
//reserve place for ring signature
tx.signatures.resize(1);
tx.signatures[0].resize(posin.key_offsets.size());
return true;
}
bool mine_next_pos_block_in_playtime_sign_cb(currency::core& c, const currency::block& prev_block, const currency::block& coinstake_scr_block, const currency::account_base& acc,
std::function<bool(currency::block&)> before_sign_cb, currency::block& output)
{
// these values (median and diff) are correct only for the next main chain block, it's incorrect for altblocks, especially for old altblocks
// but for now we assume they will work fine
uint64_t block_size_median = c.get_blockchain_storage().get_current_comulative_blocksize_limit() / 2;
currency::wide_difficulty_type difficulty = c.get_blockchain_storage().get_next_diff_conditional(true);
crypto::hash prev_id = get_block_hash(prev_block);
size_t height = get_block_height(prev_block) + 1;
block_extended_info bei = AUTO_VAL_INIT(bei);
bool r = c.get_blockchain_storage().get_block_extended_info_by_hash(prev_id, bei);
CHECK_AND_ASSERT_MES(r, false, "get_block_extended_info_by_hash failed for hash = " << prev_id);
const transaction& stake = coinstake_scr_block.miner_tx;
crypto::public_key stake_tx_pub_key = get_tx_pub_key_from_extra(stake);
size_t stake_output_idx = 0;
size_t stake_output_gidx = 0;
uint64_t stake_output_amount = stake.vout[stake_output_idx].amount;
crypto::key_image stake_output_key_image;
keypair kp;
generate_key_image_helper(acc.get_keys(), stake_tx_pub_key, stake_output_idx, kp, stake_output_key_image);
crypto::public_key stake_output_pubkey = boost::get<txout_to_key>(stake.vout[stake_output_idx].target).key;
pos_block_builder pb;
pb.step1_init_header(height, prev_id);
pb.step2_set_txs(std::vector<transaction>());
pb.step3_build_stake_kernel(stake_output_amount, stake_output_gidx, stake_output_key_image, difficulty, prev_id, null_hash, prev_block.timestamp);
pb.step4_generate_coinbase_tx(block_size_median, bei.already_generated_coins, acc.get_public_address());
if (!before_sign_cb(pb.m_block))
return false;
pb.step5_sign(stake_tx_pub_key, stake_output_idx, stake_output_pubkey, acc);
output = pb.m_block;
return true;
}