Pulse: Prepare difficulty disabling for Pulse

This commit is contained in:
Doyle 2020-08-27 17:43:08 +10:00 committed by Jason Rhinelander
parent fc951c57ff
commit ffb29989c7
9 changed files with 156 additions and 159 deletions

View File

@ -1812,29 +1812,18 @@ public:
*/
virtual uint64_t get_database_size() const = 0;
// TODO: this should perhaps be (or call) a series of functions which
// progressively update through version updates
/**
* @brief fix up anything that may be wrong due to past bugs
*/
enum struct fixup_type
{
standard,
calculate_difficulty,
};
struct fixup_context
{
fixup_type type;
union
struct
{
struct
{
uint64_t start_height;
} calculate_difficulty_params;
};
uint64_t hf12_height;
uint64_t start_height;
} recalc_diff;
};
virtual void fixup(fixup_context const context = {});
virtual void fixup(fixup_context const context);
virtual void get_output_blacklist(std::vector<uint64_t> &blacklist) const = 0;
virtual void add_output_blacklist(std::vector<uint64_t> const &blacklist) = 0;

View File

@ -4729,121 +4729,99 @@ void BlockchainLMDB::fixup(fixup_context const context)
if (is_read_only())
return;
if (context.type == fixup_type::calculate_difficulty)
uint64_t start_height = (context.recalc_diff.start_height == 0) ? 1 : context.recalc_diff.start_height;
if (start_height >= height())
return;
std::vector<uint64_t> timestamps;
std::vector<difficulty_type> difficulties;
{
uint64_t start_height = (context.calculate_difficulty_params.start_height == 0) ?
1 : context.calculate_difficulty_params.start_height;
uint64_t offset = start_height - std::min<size_t>(start_height, static_cast<size_t>(DIFFICULTY_BLOCKS_COUNT));
if (offset == 0)
offset = 1;
if (start_height >= height())
return;
// The first blocks of v12 get an overridden difficulty, so if the start block is v12 we need to
// make sure it isn't in that initial window; if it *is*, check H-60 to see if that is v11; if
// it is, recalculate from there instead (so that we detect the v12 barrier).
uint8_t v12_initial_blocks_remaining = 0;
uint8_t start_version = get_hard_fork_version(start_height);
if (start_version < cryptonote::network_version_12_checkpointing) {
v12_initial_blocks_remaining = DIFFICULTY_WINDOW;
} else if (start_version == cryptonote::network_version_12_checkpointing && start_height > DIFFICULTY_WINDOW) {
uint8_t earlier_version = get_hard_fork_version(start_height - DIFFICULTY_WINDOW);
if (earlier_version < cryptonote::network_version_12_checkpointing) {
start_height -= DIFFICULTY_WINDOW;
v12_initial_blocks_remaining = DIFFICULTY_WINDOW;
LOG_PRINT_L2("Using earlier recalculation start height " << start_height << " to include v12 fork height");
}
if (start_height > offset)
{
timestamps.reserve (start_height - offset);
difficulties.reserve(start_height - offset);
}
std::vector<uint64_t> timestamps;
std::vector<difficulty_type> difficulties;
for (; offset < start_height; offset++)
{
uint64_t offset = start_height - std::min<size_t>(start_height, static_cast<size_t>(DIFFICULTY_BLOCKS_COUNT));
if (offset == 0)
offset = 1;
if (start_height > offset)
{
timestamps.reserve (start_height - offset);
difficulties.reserve(start_height - offset);
}
for (; offset < start_height; offset++)
{
timestamps.push_back (get_block_timestamp(offset));
difficulties.push_back(get_block_cumulative_difficulty(offset));
}
timestamps.push_back (get_block_timestamp(offset));
difficulties.push_back(get_block_cumulative_difficulty(offset));
}
}
try
try
{
uint64_t const num_blocks = height() - start_height;
uint64_t prev_cumulative_diff = get_block_cumulative_difficulty(start_height - 1);
uint64_t const BLOCKS_PER_BATCH = 10000;
uint64_t const blocks_in_left_over_batch = num_blocks % BLOCKS_PER_BATCH;
uint64_t const num_batches = (num_blocks + (BLOCKS_PER_BATCH - 1)) / BLOCKS_PER_BATCH;
size_t const left_over_batch_index = num_batches - 1;
for (size_t batch_index = 0; batch_index < num_batches; batch_index++)
{
uint64_t const num_blocks = height() - start_height;
uint64_t prev_cumulative_diff = get_block_cumulative_difficulty(start_height - 1);
uint64_t const BLOCKS_PER_BATCH = 10000;
uint64_t const blocks_in_left_over_batch = num_blocks % BLOCKS_PER_BATCH;
uint64_t const num_batches = (num_blocks + (BLOCKS_PER_BATCH - 1)) / BLOCKS_PER_BATCH;
size_t const left_over_batch_index = num_batches - 1;
for (size_t batch_index = 0; batch_index < num_batches; batch_index++)
{
block_wtxn_start();
mdb_txn_cursors *m_cursors = &m_wcursors; // Necessary for macro
CURSOR(block_info);
block_wtxn_start();
mdb_txn_cursors *m_cursors = &m_wcursors; // Necessary for macro
CURSOR(block_info);
uint64_t blocks_in_batch = (batch_index == left_over_batch_index ? blocks_in_left_over_batch : BLOCKS_PER_BATCH);
for (uint64_t block_index = 0; block_index < blocks_in_batch; block_index++)
uint64_t blocks_in_batch = (batch_index == left_over_batch_index ? blocks_in_left_over_batch : BLOCKS_PER_BATCH);
for (uint64_t block_index = 0; block_index < blocks_in_batch; block_index++)
{
uint64_t const curr_height = (start_height + (batch_index * BLOCKS_PER_BATCH) + block_index);
uint8_t version = get_hard_fork_version(curr_height);
difficulty_type diff =
next_difficulty_v2(timestamps,
difficulties,
tools::to_seconds(TARGET_BLOCK_TIME),
difficulty_mode(version, curr_height, context.recalc_diff.hf12_height));
MDB_val_set(key, curr_height);
try
{
uint64_t const curr_height = (start_height + (batch_index * BLOCKS_PER_BATCH) + block_index);
uint8_t version = get_hard_fork_version(curr_height);
bool v12_initial_override = false;
if (version == cryptonote::network_version_12_checkpointing && v12_initial_blocks_remaining > 0) {
v12_initial_override = true;
v12_initial_blocks_remaining--;
}
difficulty_type diff = next_difficulty_v2(timestamps, difficulties, tools::to_seconds(TARGET_BLOCK_TIME),
version <= cryptonote::network_version_9_service_nodes, v12_initial_override);
if (int result = mdb_cursor_get(m_cur_block_info, (MDB_val *)&zerokval, &key, MDB_GET_BOTH))
throw1(BLOCK_DNE(lmdb_error("Failed to get block info in recalculate difficulty: ", result).c_str()));
MDB_val_set(key, curr_height);
mdb_block_info block_info = *(mdb_block_info *)key.mv_data;
uint64_t old_cumulative_diff = block_info.bi_diff;
block_info.bi_diff = prev_cumulative_diff + diff;
prev_cumulative_diff = block_info.bi_diff;
try
{
if (int result = mdb_cursor_get(m_cur_block_info, (MDB_val *)&zerokval, &key, MDB_GET_BOTH))
throw1(BLOCK_DNE(lmdb_error("Failed to get block info in recalculate difficulty: ", result).c_str()));
if (old_cumulative_diff != block_info.bi_diff)
LOG_PRINT_L0("Height: " << curr_height << " prev difficulty: " << old_cumulative_diff << ", new difficulty: " << block_info.bi_diff);
else
LOG_PRINT_L2("Height: " << curr_height << " difficulty unchanged (" << old_cumulative_diff << ")");
mdb_block_info block_info = *(mdb_block_info *)key.mv_data;
uint64_t old_cumulative_diff = block_info.bi_diff;
block_info.bi_diff = prev_cumulative_diff + diff;
prev_cumulative_diff = block_info.bi_diff;
MDB_val_set(val, block_info);
if (int result = mdb_cursor_put(m_cur_block_info, (MDB_val *)&zerokval, &val, MDB_CURRENT))
throw1(BLOCK_DNE(lmdb_error("Failed to put block info: ", result).c_str()));
if (old_cumulative_diff != block_info.bi_diff)
LOG_PRINT_L0("Height: " << curr_height << " prev difficulty: " << old_cumulative_diff << ", new difficulty: " << block_info.bi_diff);
else
LOG_PRINT_L2("Height: " << curr_height << " difficulty unchanged (" << old_cumulative_diff << ")");
MDB_val_set(val, block_info);
if (int result = mdb_cursor_put(m_cur_block_info, (MDB_val *)&zerokval, &val, MDB_CURRENT))
throw1(BLOCK_DNE(lmdb_error("Failed to put block info: ", result).c_str()));
timestamps.push_back(block_info.bi_timestamp);
difficulties.push_back(block_info.bi_diff);
}
catch (DB_ERROR const &e)
{
block_wtxn_abort();
LOG_PRINT_L0("Something went wrong recalculating difficulty for block " << curr_height << e.what());
return;
}
while (timestamps.size() > DIFFICULTY_BLOCKS_COUNT) timestamps.erase(timestamps.begin());
while (difficulties.size() > DIFFICULTY_BLOCKS_COUNT) difficulties.erase(difficulties.begin());
timestamps.push_back(block_info.bi_timestamp);
difficulties.push_back(block_info.bi_diff);
}
catch (DB_ERROR const &e)
{
block_wtxn_abort();
LOG_PRINT_L0("Something went wrong recalculating difficulty for block " << curr_height << e.what());
return;
}
block_wtxn_stop();
while (timestamps.size() > DIFFICULTY_BLOCKS_COUNT) timestamps.erase(timestamps.begin());
while (difficulties.size() > DIFFICULTY_BLOCKS_COUNT) difficulties.erase(difficulties.begin());
}
block_wtxn_stop();
}
catch (DB_ERROR const &e)
{
LOG_PRINT_L0("Something went wrong in the pre-amble of recalculating difficulty for block: " << e.what());
return;
}
}
catch (DB_ERROR const &e)
{
LOG_PRINT_L0("Something went wrong in the pre-amble of recalculating difficulty for block: " << e.what());
return;
}
}
@ -5976,24 +5954,13 @@ void BlockchainLMDB::migrate_4_5(cryptonote::network_type nettype)
txn.commit();
// NOTE: Rescan chain difficulty to mitigate difficulty problem pre hardfork v12
uint64_t hf12_height = 0;
for (const auto &record : cryptonote::HardFork::get_hardcoded_hard_forks(nettype))
{
if (record.version == cryptonote::network_version_12_checkpointing)
{
hf12_height = record.height;
break;
}
}
fixup_context context = {};
context.type = fixup_type::calculate_difficulty;
context.calculate_difficulty_params.start_height = hf12_height;
uint64_t hf12_height = HardFork::get_hardcoded_hard_fork_height(nettype, cryptonote::network_version_12_checkpointing);
fixup_context context = {};
context.recalc_diff.start_height = hf12_height;
context.recalc_diff.hf12_height = hf12_height;
uint64_t constexpr FUDGE = BLOCKS_EXPECTED_IN_DAYS(1);
context.calculate_difficulty_params.start_height = (context.calculate_difficulty_params.start_height < FUDGE)
? 0
: context.calculate_difficulty_params.start_height - FUDGE;
context.recalc_diff.start_height = (context.recalc_diff.start_height < FUDGE) ? 0 : context.recalc_diff.start_height - FUDGE;
fixup(context);
if (int result = write_db_version(m_env, m_properties, (uint32_t)lmdb_version::v5))

View File

@ -720,8 +720,8 @@ int main(int argc, char* argv[])
{
uint64_t recalc_diff_from_block = command_line::get_arg(vm, arg_recalculate_difficulty);
cryptonote::BlockchainDB::fixup_context context = {};
context.type = cryptonote::BlockchainDB::fixup_type::calculate_difficulty;
context.calculate_difficulty_params.start_height = recalc_diff_from_block;
context.recalc_diff.hf12_height = HardFork::get_hardcoded_hard_fork_height(core.get_nettype(), cryptonote::network_version_12_checkpointing);
context.recalc_diff.start_height = recalc_diff_from_block;
core.get_blockchain_storage().get_db().fixup(context);
return 0;
}

View File

@ -140,8 +140,33 @@ namespace cryptonote {
// be reduced from 60*60*2 to 500 seconds to prevent timestamp manipulation from miner's with
// > 50% hash power. If this is too small, it can be increased to 1000 at a cost in protection.
difficulty_type next_difficulty_v2(std::vector<std::uint64_t> timestamps, std::vector<difficulty_type> cumulative_difficulties, size_t target_seconds,
bool use_old_lwma, bool v12_initial_override) {
difficulty_calc_mode difficulty_mode(uint8_t hf_version, uint64_t height, uint64_t hf12_height)
{
auto result = difficulty_calc_mode::post_pulse;
if (hf_version <= cryptonote::network_version_9_service_nodes)
{
result = difficulty_calc_mode::use_old_lwma;
}
// HF12 switches to RandomX with a likely drastically reduced hashrate versus Turtle, so override
// difficulty for the first difficulty window blocks:
else if (hf_version >= cryptonote::network_version_12_checkpointing &&
height < hf12_height + DIFFICULTY_WINDOW)
{
result = difficulty_calc_mode::hf12_override;
}
else if (hf_version <= cryptonote::network_version_15_lns)
{
result = difficulty_calc_mode::pre_pulse;
}
return result;
}
difficulty_type next_difficulty_v2(std::vector<std::uint64_t> timestamps,
std::vector<difficulty_type> cumulative_difficulties,
size_t target_seconds,
difficulty_calc_mode mode)
{
const int64_t T = static_cast<int64_t>(target_seconds);
@ -176,7 +201,7 @@ namespace cryptonote {
for (int64_t i = 1; i <= (int64_t)N; i++) {
solveTime = static_cast<int64_t>(timestamps[i]) - static_cast<int64_t>(timestamps[i - 1]);
if (use_old_lwma) solveTime = std::max<int64_t>(solveTime, (-7 * T));
if (mode == difficulty_calc_mode::use_old_lwma) solveTime = std::max<int64_t>(solveTime, (-7 * T));
solveTime = std::min<int64_t>(solveTime, (T * 7));
difficulty = cumulative_difficulties[i] - cumulative_difficulties[i - 1];
@ -202,7 +227,7 @@ namespace cryptonote {
// Rough estimate based on comparable coins, pre-merge-mining hashrate, and hashrate changes is
// that 30MH/s seems more or less right, so we cap it there for the first WINDOW blocks to
// prevent too-long blocks right after the fork.
if (v12_initial_override)
if (mode == difficulty_calc_mode::hf12_override)
return std::min(next_difficulty, 30000000 * uint64_t(target_seconds));
return next_difficulty;

View File

@ -54,6 +54,18 @@ namespace cryptonote
*/
bool check_hash(const crypto::hash &hash, difficulty_type difficulty);
difficulty_type next_difficulty_v2(std::vector<std::uint64_t> timestamps, std::vector<difficulty_type> cumulative_difficulties, size_t target_second,
bool use_old_lwma, bool v12_initial_override);
enum struct difficulty_calc_mode
{
use_old_lwma,
hf12_override,
pre_pulse,
post_pulse,
};
difficulty_calc_mode difficulty_mode(uint8_t hf_version, uint64_t height, uint64_t hf12_height);
difficulty_type next_difficulty_v2(std::vector<std::uint64_t> timestamps,
std::vector<difficulty_type> cumulative_difficulties,
size_t target_second,
difficulty_calc_mode mode);
}

View File

@ -493,8 +493,9 @@ bool Blockchain::init(BlockchainDB* db, sqlite3 *lns_db, const network_type nett
if (m_nettype != FAKECHAIN)
{
// ensure we fixup anything we found and fix in the future
m_db->fixup();
cryptonote::BlockchainDB::fixup_context context = {};
context.recalc_diff.hf12_height = HardFork::get_hardcoded_hard_fork_height(m_nettype, cryptonote::network_version_12_checkpointing);
m_db->fixup(context);
}
db_rtxn_guard rtxn_guard(m_db);
@ -1020,12 +1021,12 @@ difficulty_type Blockchain::get_difficulty_for_next_block()
m_timestamps = timestamps;
m_difficulties = difficulties;
}
// HF12 switches to RandomX with a likely drastically reduced hashrate versus Turtle, so override
// difficulty for the first difficulty window blocks:
uint64_t hf12_height = m_hardfork->get_earliest_ideal_height_for_version(network_version_12_checkpointing);
difficulty_type diff = next_difficulty_v2(timestamps, difficulties, tools::to_seconds(TARGET_BLOCK_TIME), version <= cryptonote::network_version_9_service_nodes,
height >= hf12_height && height < hf12_height + DIFFICULTY_WINDOW);
uint64_t hf12_height = m_hardfork->get_earliest_ideal_height_for_version(network_version_12_checkpointing);
difficulty_type diff = next_difficulty_v2(timestamps,
difficulties,
tools::to_seconds(TARGET_BLOCK_TIME),
difficulty_mode(version, height, hf12_height));
std::unique_lock diff_lock{m_difficulty_lock};
m_difficulty_for_next_block_top_hash = top_hash;
@ -1274,15 +1275,14 @@ difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std:
}
}
// FIXME: This will fail if fork activation heights are subject to voting
// HF12 switches to RandomX with a likely drastically reduced hashrate versus Turtle, so override
// difficulty for the first difficulty window blocks:
uint64_t hf12_height = m_hardfork->get_earliest_ideal_height_for_version(network_version_12_checkpointing);
uint64_t height = (alt_chain.size() ? alt_chain.front().height : alt_block_height) + alt_chain.size() + 1;
// calculate the difficulty target for the block and return it
return next_difficulty_v2(timestamps, cumulative_difficulties, tools::to_seconds(TARGET_BLOCK_TIME), get_current_hard_fork_version() <= cryptonote::network_version_9_service_nodes,
height >= hf12_height && height < hf12_height + DIFFICULTY_WINDOW);
return next_difficulty_v2(timestamps,
cumulative_difficulties,
tools::to_seconds(TARGET_BLOCK_TIME),
difficulty_mode(get_current_hard_fork_version(), height, hf12_height));
}
//------------------------------------------------------------------
// This function does a sanity check on basic things that all miner
@ -4041,7 +4041,8 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash&
block_pow_verified blk_pow = {};
} miner = {};
if (cryptonote::block_has_pulse_components(bl))
bool const pulse_block = cryptonote::block_has_pulse_components(bl);
if (pulse_block)
{
// NOTE: Pulse blocks don't use PoW. They use Service Node signatures.
// Delay signature verification until Service Node List adds the block in
@ -4302,8 +4303,8 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash&
if (nettype() == MAINNET && (block_height % BLOCKS_EXPECTED_IN_DAYS(1) == 0))
{
cryptonote::BlockchainDB::fixup_context context = {};
context.type = cryptonote::BlockchainDB::fixup_type::calculate_difficulty;
context.calculate_difficulty_params.start_height = block_height - BLOCKS_EXPECTED_IN_DAYS(1);
context.recalc_diff.hf12_height = HardFork::get_hardcoded_hard_fork_height(m_nettype, cryptonote::network_version_12_checkpointing);
context.recalc_diff.start_height = block_height - BLOCKS_EXPECTED_IN_DAYS(1);
m_db->fixup(context);
}
}
@ -4352,7 +4353,8 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash&
++m_sync_counter;
m_tx_pool.on_blockchain_inc(bl);
get_difficulty_for_next_block(); // just to cache it
if (!pulse_block)
get_difficulty_for_next_block(); // just to cache it
invalidate_block_template_cache();
if (notify)

View File

@ -837,8 +837,8 @@ namespace cryptonote
if (recalc_diff_from_block > 0)
{
cryptonote::BlockchainDB::fixup_context context = {};
context.type = cryptonote::BlockchainDB::fixup_type::calculate_difficulty;
context.calculate_difficulty_params.start_height = recalc_diff_from_block;
context.recalc_diff.hf12_height = HardFork::get_hardcoded_hard_fork_height(m_nettype, cryptonote::network_version_12_checkpointing);
context.recalc_diff.start_height = recalc_diff_from_block;
m_blockchain_storage.get_db().fixup(context);
}

View File

@ -45,7 +45,7 @@ namespace
for (size_t i = 0; i < new_block_count; ++i)
{
block blk_next;
difficulty_type diffic = next_difficulty_v2(timestamps, cummulative_difficulties,tools::to_seconds(TARGET_BLOCK_TIME), false /*use_old_lwma*/, false);
difficulty_type diffic = next_difficulty_v2(timestamps, cummulative_difficulties,tools::to_seconds(TARGET_BLOCK_TIME), cryptonote::difficulty_calc_mode::pre_pulse);
if (!generator.construct_block_manually(blk_next, blk_prev, miner_account,
test_generator::bf_timestamp | test_generator::bf_diffic, 0, 0, blk_prev.timestamp, crypto::hash(), diffic))
return false;
@ -175,7 +175,7 @@ bool gen_block_invalid_nonce::generate(std::vector<test_event_entry>& events) co
return false;
// Create invalid nonce
difficulty_type diffic = next_difficulty_v2(timestamps, cummulative_difficulties,tools::to_seconds(TARGET_BLOCK_TIME), false /*use_old_lwma*/, false);
difficulty_type diffic = next_difficulty_v2(timestamps, cummulative_difficulties,tools::to_seconds(TARGET_BLOCK_TIME), cryptonote::difficulty_calc_mode::pre_pulse);
assert(1 < diffic);
const block& blk_last = std::get<block>(events.back());
uint64_t timestamp = blk_last.timestamp;
@ -639,7 +639,7 @@ bool gen_block_invalid_binary_format::generate(std::vector<test_event_entry>& ev
do
{
blk_last = std::get<block>(events.back());
diffic = next_difficulty_v2(timestamps, cummulative_difficulties,tools::to_seconds(TARGET_BLOCK_TIME), false /*use_old_lwma*/, false);
diffic = next_difficulty_v2(timestamps, cummulative_difficulties,tools::to_seconds(TARGET_BLOCK_TIME), cryptonote::difficulty_calc_mode::pre_pulse);
if (!lift_up_difficulty(events, timestamps, cummulative_difficulties, generator, 1, blk_last, miner_account))
return false;
std::cout << "Block #" << events.size() << ", difficulty: " << diffic << std::endl;
@ -654,7 +654,7 @@ bool gen_block_invalid_binary_format::generate(std::vector<test_event_entry>& ev
std::vector<crypto::hash> tx_hashes;
tx_hashes.push_back(get_transaction_hash(tx_0));
size_t txs_weight = get_transaction_weight(tx_0);
diffic = next_difficulty_v2(timestamps, cummulative_difficulties,tools::to_seconds(TARGET_BLOCK_TIME), false /*use_old_lwma*/, false);
diffic = next_difficulty_v2(timestamps, cummulative_difficulties,tools::to_seconds(TARGET_BLOCK_TIME), cryptonote::difficulty_calc_mode::pre_pulse);
if (!generator.construct_block_manually(blk_test, blk_last, miner_account,
test_generator::bf_diffic | test_generator::bf_timestamp | test_generator::bf_tx_hashes, 0, 0, blk_last.timestamp,
crypto::hash(), diffic, transaction(), tx_hashes, txs_weight))

View File

@ -67,7 +67,9 @@ int main(int argc, char *argv[]) {
}
uint64_t res = cryptonote::next_difficulty_v2(
std::vector<uint64_t>(timestamps.begin() + begin, timestamps.begin() + end),
std::vector<uint64_t>(cumulative_difficulties.begin() + begin, cumulative_difficulties.begin() + end), tools::to_seconds(TARGET_BLOCK_TIME), false/*use_old_lwma2*/, false);
std::vector<uint64_t>(cumulative_difficulties.begin() + begin, cumulative_difficulties.begin() + end),
tools::to_seconds(TARGET_BLOCK_TIME),
cryptonote::difficulty_calc_mode::pre_pulse);
if (res != difficulty) {
std::cerr << "Wrong difficulty for block " << n
<< "\nExpected: " << difficulty