diff --git a/docs/SECOR.md b/docs/SECOR.md new file mode 100644 index 00000000..d7a1af04 --- /dev/null +++ b/docs/SECOR.md @@ -0,0 +1,196 @@ +# SECOR + +https://github.com/masari-project/research-corner/blob/master/secor/secor.pdf + +## Proposal Summary + +I would like to increase the speed of Nerva transactions by integrating the "Simple Extendend Consensus Resolution" (SECOR) protocol into Nerva. + +The goal is to better define how the network should respond to temporary chain forks when an orphan block is found and reduce deep chain reorganizations. + +### Consensus + +When two blocks are found for the top block height, all network participants should reorganize so that the block with Proof-of-Work which clears the highest difficulty should be on top. + +Miners should include the hash of the losing block in their next block via the `uncle_hash` block field. + +A miner will be rewarded an additional bonus for including that uncle block hash in their block, and the difficulty for the uncle block will be included in the chain cumulative difficulty, giving a miner who includes a valid uncle hash an advantage over miners who may choose not to. + +### Miner Transaction Format + +Miner transactions should include only 1 reward output if the uncle_hash is null. + +If an uncle block is included in the block, then the miner transaction must include a secondary transaction output. + +In order to generate a transaction on behalf of another miner in a non-interactive way without compromising privacy, we re-use the keys associated with the original uncle block miner transaction from the alt chain into the nephew block. + +Since output index is a part of the stealth address generation algorithm, `Hs(aR|i)*G + B = Hs(rA|i)G + B` (see cryptonote whitepaper section 4.3), the ordering of the output destinations in the transaction is important. + +If a block doesn't reference an uncle block, then there should be only one output in the reward transaction, which should be used to reward the person who mined the block. If that block is then included into a nephew block as an uncle block, then the nephew block must place the reward for the uncle miner, using the output key from the uncle block miner tx, into the nephew block miner transaction at index 0. The reward for the person who found the nephew block should be placed at index 1 in the reward transaction. + +If an uncle block references an uncle block itself, then the reward to the true uncle miner should be at index 1 in the uncle block. To accomodate this, the reward to the nephew miner in the main chain should be addressed to output index 0 so that the uncle reward from index 1 may be recycled. + +### Block Reward Bonuses + +I use the reward constants proposed in the original SECOR paper: +5% of the base reward amount to the nephew miner and 50% of the base reward amount to the uncle miner. The impact of this is detailed in the "Block Reward Rationale" section. + +The optimal reward for Nerva's implementation is still an active research question. + +### Block Time Target and Transaction Maturity + +Nerva currently has a target block time of 1 minute and requires a maturity of 20 blocks to spend non-miner transaction outputs. I propose to use a 15 second block time as proposed in the original SECOR paper. The first (and only known?) project to integrate SECOR with CryptoNote was Masari, which decreased from a 2 minute to a 1 minute block time. Therefore, real-world behavior with a 15 second block time is currently unknown. Further research should be done to select/confirm the optimal block time for Nerva. + +With a 20 block transaction maturity threshold and a 15 seconds block target, it should take about 5 minutes for a transaction to mature. If we succeed in preventing blockchain reorganizations > depth 1, then we may be able to decrease the threshold to only 5 blocks, or 75 seconds. + +## Block Reward Rationale +Maximum block reward emission per minute, R, given base reward X: +``` +R = X + (X/2) + (X/20) +R = X + (10X/20) + (X/20) +R = X + (11X/20) +``` + +If we keep the base emission reward the same, X=0.3, then... +``` +R = 0.3 + ((11*0.3)/20) = 0.465 +``` + +If we preserve the current emission target of R=0.3 then... +``` +0.3 = (20X/20) + (11X/20) = 31X/20 +0.3 * 20 = 31X +X = (0.3 * 20) / 31 = 0.19354838709 +``` + +If you have an estimate of how frequently uncle blocks are included in the blockchain, Z, then you can scale the reward to produce the same estimated average emission. +``` +R = X + (((X/2) + (X/20)) * Z) +R = X + ((11X/20) * Z) +``` + +If we think that uncle blocks will be included in half of the main chain blocks then... +``` +0.3 = X + ((11X/20) * 0.5) +0.3 = X + (11X/40) +0.3 = 51X / 40 +X = (0.3 * 40) / 51 = 0.23529411764 +``` + +## Modeling Orphan/Uncle Block Frequencies + +Mining is essentially an [IID process](https://en.wikipedia.org/wiki/Independent_and_identically_distributed_random_variables) as the output of the hashing function used for mining should be an IID random variable. Each time the miner chooses a nonce and produces a hash, the miner has either found a valid block for the given difficulty, or they have not and must attempt again with a successive independent trial. The probabilities for this kind of binary problem follow a [bernoulli distribution](https://en.wikipedia.org/wiki/Bernoulli_distribution). Therefore, a [binomial distribution](https://en.wikipedia.org/wiki/Binomial_distribution) where k=1, aka a [geometric distribution](https://en.wikipedia.org/wiki/Geometric_distribution), can be used to model the cumulative probability of finding a block after mining `n` hashes. + +A block difficulty target is used to control the rate at which blockchain can be written to. Nerva currently has a block time target of 1 block per minute. If there are 10 miners on the network, each mining at a rate of 5 hashes/second, then the target difficulty should eventualy fit to about `10*5*60`. + +#### Scenario A - Few miners, low difficulty. +``` +Suppose: +There is a target block time of 60 seconds. +There are 2 miners, miner A and miner B, on the network, each mining at a rate of 1 H/s. + +Then: +The network difficulty should be 1*2*60=120 +The probability that miner A finds a block after 1 second of hashing is 1/120. +The probability that miner B has found a block after 1 second of hashing is also 1/120. + +The probability that miner A finds a block after 2 seconds of hashing is 2/120. +The probability that miner B has found a block after 2 seconds of hashing is also 1/120. + +The probability that miner A and miner B both find a block after 1 second of mining (at the same time) is (1/120)*(1/120) per IID joint probability. + +The cumulative probability that miner A finds a block after 2 seconds of mining is 1 - (1-(1/120))^2 per geometric CDF +The cumulative probability that miner B finds a block after 2 seconds of mining is 1 - (1-(1/120))^2 per geometric CDF + +The probability that miner A and miner B have both found a block after 2 seconds of mining is (1 - (1-(1/120))^2)^2 + +The probability that miner A and miner B have both found a block after 10 seconds of mining is (1 - (1-(1/120))^10)^2 + +The probability that a given miner finds a block after mining for 1 minute is 1 - ((1-(1/120))^60), roughly 40%. + +The probability that both miners have each found a block after 1 minute of mining is (1 - ((1-(1/120))^60))^2, which is roughly 15%. +``` + +#### Scenario B - Few miners, higher difficulty. +``` +Suppose: +There is a target block time of 60 seconds. +There are 2 miners, miner A and miner B, on the network, each mining at a rate of 1,000 H/s. + +Then: +The network difficulty should be 1000*2*60=120000 +The probability that miner A finds a block after mining a single hash is 1/120000. +The probability that miner B has found a block after mining a single is also 1/120000. + +The probability that miner A and miner B both find a block at the same time after each mining a single hash is (1/120000)^2 per IID joint probability. + +The probability that a given miner finds a block after mining for 1 second is 1 - ((1-(1/120000))^1000) per geometric CDF. + +The probability that miner A and miner B have both found a block after mining 10 hashes is (1 - (1-(1/120000))^10)^2 + +The probability that miner A and miner B have both found after 1 second of mining is (1 - (1-(1/120000))^1000)^2 + +The probability that a given miner finds a block after mining for 1 minute is 1 - ((1-(1/120000))^60000), which is roughly 40%. This is the same as when the network hashrate is only 2 H/s. + +The probability that both miners have each found a block after mining for 1 minute is (1 - ((1-(1/120000))^60000))^2, which is roughly 15%. +``` + +#### Scenario C - Many miners +``` +Suppose: +There is a target block time of 60 seconds. +There are 100 miners on the network, each mining at a rate of 1,000 H/s. + +Then: +The network difficulty should be 1000*100*60=6000000 +The probability that a given miner finds a block after mining a single hash is 1/6000000. + +The probability that 2 miners both find a block at the same time after each mining a single hash is (1/6000000)^2 per IID joint probability. + +The probability that a given miner finds a block after mining for 1 second is 1 - ((1-(1/6000000))^1000) per geometric CDF. + +The probability that two miners have both found a block after mining 10 hashes is (1 - (1-(1/6000000))^10)^2 + +The probability that two miners have both found after 1 second of mining is (1 - (1-(1/6000000))^1000)^2 + +The cumulative probability that a given miner finds a block after mining for 1 minute is 1 - ((1-(1/6000000))^60000), which is just less than 1%. + +The probability that any two miners have each found a block after mining for 1 minute is (100*99/2)*((1-(1-(1/6000000))^(60000))^2)*(((1-(1/6000000))^(60000))^98), per binomial distribution (100, 2) which is about 18% +``` + +Overall, the network difficulty adjusts in a way that adding miners to the network doesnt affect the probability of chain splits significantly assuming that mining power is distributed evenly among miners. This model does not account for the fact that adding miners to the network increases the amount of work required for block propogation across all miners on the network. + +### Mining Difficulty Considerations + +The current difficulty algorithm adjusts based on the observed difference between block timestamps of recently found blocks compared against the target solve time. We will be lowering that target solve time from 60 seconds to 15 seconds. The algorithm should adjust quickly, but it will be a bit wonky for the first ~60 blocks after hard forking. + +Currently the LWMA algorithm uses the parameter N=60 so we are using the most recent 60 blocks when calculating the weighted average. Since we are lowering the target block time, that means we will only be using 15 minutes worth of block histories instead of the last 1 hour worth of blocks. Is this OK? + +The "future time limit" used in the difficulty algorithm is intended to be 500 seconds according to the comments in the code. Currently this is, in fact, hard coded to 300 seconds (`CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT_V6`). Since we are lowering the target solve time, the (FTL / T) ratio increases. Should this be adjusted? + +## Status + +This proposal is a work in progress. I am seeking community feedback to see if I should continue working on this idea, and to get an idea of what people want to do about the block reward. + +### TODOs + +#### Coding + +* Address the TODO comments + +* graceful database migration - currently a sync from scratch is required after upgrading + +#### Research + +* Confirm that the difficulty algorithm doesnt require further adjustment + +* How long does it currently take for a block to propogate throughout the network ? + +* Quantify frequency of orphans / chain re-organizations + +* Decide transaction maturity blocks # + +#### Testing + +* Unit test the whole thing + +* Deploy to testnet diff --git a/scripts/analyze_alt_chains.py b/scripts/analyze_alt_chains.py new file mode 100644 index 00000000..380403a0 --- /dev/null +++ b/scripts/analyze_alt_chains.py @@ -0,0 +1,30 @@ +import json +import datetime +import requests + +DAEMON_HOST = 'http://127.0.0.1:17566/json_rpc' + +def get_block(block_hash:str) -> dict: + r = requests.post(DAEMON_HOST, json={'method': 'get_block', 'params': {'hash': block_hash}}) + return json.loads(r.json()['result']['json']) + +def get_block_by_height(height:int) -> dict: + r = requests.post(DAEMON_HOST, json={'method': 'get_block', 'params': {'height': height}}) + return json.loads(r.json()['result']['json']) + +def get_alt_chains(): + r = requests.post(DAEMON_HOST, json={'method': 'get_alternate_chains'}) + chains = r.json()['result']['chains'] + sorted_chains = sorted(chains, key=lambda alt_chain: alt_chain['height']) + return sorted_chains + +if __name__ == '__main__': + prev_alt_chain_height = None + for alt_chain in get_alt_chains(): + alt_block_timestamp = get_block(alt_chain['block_hashes'][-1])['timestamp'] + main_chain_sibling_timestamp = get_block_by_height(alt_chain['height'])['timestamp'] + print(f'Alt chain found at height {alt_chain["height"]} with head {alt_chain["block_hashes"][-1]}') + print(f' Alt chain length: {alt_chain["length"]}') + print(f' Timestamp: {datetime.datetime.fromtimestamp(alt_block_timestamp)}; Difference from mainchain: {abs(alt_block_timestamp - main_chain_sibling_timestamp)}s') + print(f' Height difference from previous alt chain head: {None if not prev_alt_chain_height else alt_chain["height"] - prev_alt_chain_height}') + prev_alt_chain_height = alt_chain["height"] \ No newline at end of file diff --git a/scripts/analyze_tx_frequency.py b/scripts/analyze_tx_frequency.py new file mode 100644 index 00000000..e9518ee3 --- /dev/null +++ b/scripts/analyze_tx_frequency.py @@ -0,0 +1,55 @@ +import json +import requests + +DAEMON_HOST = 'http://127.0.0.1:17566/json_rpc' + +class BlockchainRPCIterator: + ''' + Iterate from the top of the chain to the origin block via RPC + ''' + def __init__(self): + self.session = requests.Session() + self.next_block_hash, self.height = self.get_top_block() + + def __iter__(self): + return self + + def __next__(self): + block = self.get_block(self.next_block_hash) + if not block: + raise StopIteration + self.next_block_hash = block['prev_id'] + self.height = block['miner_tx']['vin'][0]['gen']['height'] + return block + + def get_top_block(self) -> (str, int): + r = self.session.post(DAEMON_HOST, json={'method': 'get_info'}) + return r.json()['result']['top_block_hash'], r.json()['result']['height'] + + def get_block(self, block_hash:str) -> dict: + try: + r = self.session.post(DAEMON_HOST, json={'method': 'get_block', 'params': {'hash': block_hash}}) + return json.loads(r.json()['result']['json']) + except KeyError: + return + +if __name__ == '__main__': + + empty_block_counter = 0 + blocks_found = 0 + total_blocks_scanned = 0 + + for block in BlockchainRPCIterator(): + non_miner_txs = block['tx_hashes'] + total_blocks_scanned += 1 + + if block['tx_hashes']: + blocks_found += 1 + # print(block) + print(f'{empty_block_counter} block searched since last block') + print(f'{blocks_found} / {total_blocks_scanned} are not empty ({(float(blocks_found)/float(total_blocks_scanned))*100.0}%)') + empty_block_counter = 0 # reset counter to get gap until next non-empty block + else: + empty_block_counter += 1 + + print(f'{100.0 - ((float(blocks_found)/float(total_blocks_scanned))*100.0)}% blocks are empty') \ No newline at end of file diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index 3eae66e9..d7a4b944 100644 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -167,6 +167,7 @@ uint64_t BlockchainDB::add_block( const std::pair& blck , const difficulty_type_128& cumulative_difficulty , const uint64_t& coins_generated , const std::vector>& txs + , const crypto::hash& uncle_blk_hash ) { const block &blk = blck.first; @@ -206,7 +207,7 @@ uint64_t BlockchainDB::add_block( const std::pair& blck // call out to subclass implementation to add the block & metadata time1 = epee::misc_utils::get_tick_count(); - add_block(blk, block_weight, long_term_block_weight, cumulative_difficulty, coins_generated, num_rct_outs, blk_hash); + add_block(blk, block_weight, long_term_block_weight, cumulative_difficulty, coins_generated, num_rct_outs, blk_hash, uncle_blk_hash); TIME_MEASURE_FINISH(time1); time_add_block1 += time1; diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index b34d703f..3d3d9c57 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -384,6 +384,7 @@ class BlockchainDB , const uint64_t& coins_generated , uint64_t num_rct_outs , const crypto::hash& blk_hash + , const crypto::hash& uncle_blk_hash ) = 0; /** @@ -837,6 +838,7 @@ class BlockchainDB , const difficulty_type_128& cumulative_difficulty , const uint64_t& coins_generated , const std::vector>& txs + , const crypto::hash& uncle_blk_hash ); /** diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 5c95b186..a1aac83f 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -679,7 +679,7 @@ uint64_t BlockchainLMDB::get_estimated_batch_size(uint64_t batch_num_blocks, uin } void BlockchainLMDB::add_block(const block& blk, size_t block_weight, uint64_t long_term_block_weight, const difficulty_type_128& cumulative_difficulty, const uint64_t& coins_generated, - uint64_t num_rct_outs, const crypto::hash& blk_hash) + uint64_t num_rct_outs, const crypto::hash& blk_hash, const crypto::hash& uncle_blk_hash) { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); @@ -743,6 +743,8 @@ void BlockchainLMDB::add_block(const block& blk, size_t block_weight, uint64_t l } bi.bi_long_term_block_weight = long_term_block_weight; + bi.uncle_blk_hash = uncle_blk_hash; + MDB_val_set(val, bi); result = mdb_cursor_put(m_cur_block_info, (MDB_val *)&zerokval, &val, MDB_APPENDDUP); if (result) @@ -4079,7 +4081,7 @@ void BlockchainLMDB::block_rtxn_abort() const } uint64_t BlockchainLMDB::add_block(const std::pair& blk, size_t block_weight, uint64_t long_term_block_weight, const difficulty_type_128& cumulative_difficulty, const uint64_t& coins_generated, - const std::vector>& txs) + const std::vector>& txs, const crypto::hash& uncle_blk_hash) { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); @@ -4097,7 +4099,7 @@ uint64_t BlockchainLMDB::add_block(const std::pair& blk, size_t try { - BlockchainDB::add_block(blk, block_weight, long_term_block_weight, cumulative_difficulty, coins_generated, txs); + BlockchainDB::add_block(blk, block_weight, long_term_block_weight, cumulative_difficulty, coins_generated, txs, uncle_blk_hash); } catch (const DB_ERROR_TXN_START& e) { diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index 82cdce40..9a0b17e2 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -66,7 +66,21 @@ typedef struct mdb_block_info_4 uint64_t bi_long_term_block_weight; } mdb_block_info_4; -typedef mdb_block_info_4 mdb_block_info; +typedef struct mdb_block_info_5 +{ + uint64_t bi_height; + uint64_t bi_timestamp; + uint64_t bi_coins; + uint64_t bi_weight; + uint64_t bi_diff_hi; + uint64_t bi_diff_lo; + crypto::hash bi_hash; + uint64_t bi_cum_rct; + uint64_t bi_long_term_block_weight; + crypto::hash uncle_blk_hash; +} mdb_block_info_5; + +typedef mdb_block_info_5 mdb_block_info; typedef struct txindex { crypto::hash key; @@ -348,6 +362,7 @@ class BlockchainLMDB : public BlockchainDB , const difficulty_type_128& cumulative_difficulty , const uint64_t& coins_generated , const std::vector>& txs + , const crypto::hash& uncle_blk_hash ); virtual void set_batch_transactions(bool batch_transactions); @@ -402,6 +417,7 @@ class BlockchainLMDB : public BlockchainDB , const uint64_t& coins_generated , uint64_t num_rct_outs , const crypto::hash& block_hash + , const crypto::hash& uncle_blk_hash ); virtual void remove_block(); diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index a7f0e91e..84f2189a 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -414,6 +414,7 @@ namespace cryptonote uint64_t timestamp; crypto::hash prev_id; uint32_t nonce; + crypto::hash uncle_hash; BEGIN_SERIALIZE() VARINT_FIELD(major_version) @@ -421,6 +422,7 @@ namespace cryptonote VARINT_FIELD(timestamp) FIELD(prev_id) FIELD(nonce) + if (major_version >= HF_VERSION_SECOR) FIELD(uncle_hash) END_SERIALIZE() }; diff --git a/src/cryptonote_basic/cryptonote_basic_impl.cpp b/src/cryptonote_basic/cryptonote_basic_impl.cpp index 98b0d1c1..e55ba09d 100644 --- a/src/cryptonote_basic/cryptonote_basic_impl.cpp +++ b/src/cryptonote_basic/cryptonote_basic_impl.cpp @@ -79,20 +79,29 @@ namespace cryptonote { } //----------------------------------------------------------------------------------------------- bool get_block_reward(size_t median_weight, size_t current_block_weight, uint64_t already_generated_coins, uint64_t &reward, uint8_t version) { - static_assert(DIFFICULTY_TARGET % 60 == 0,"difficulty targets must be a multiple of 60"); - const int target_minutes = DIFFICULTY_TARGET / 60; - const int emission_speed_factor = EMISSION_SPEED_FACTOR_PER_MINUTE - (target_minutes-1); - - const uint64_t premine = 180000000000000000U; - if (median_weight > 0 && already_generated_coins < premine) { - reward = premine; - return true; - } - - uint64_t base_reward = (MONEY_SUPPLY - already_generated_coins) >> emission_speed_factor; - if (base_reward < FINAL_SUBSIDY_PER_MINUTE*target_minutes) + uint64_t base_reward = 0; + if (version < HF_VERSION_SECOR) { - base_reward = FINAL_SUBSIDY_PER_MINUTE*target_minutes; + static_assert(DIFFICULTY_TARGET % 60 == 0,"difficulty targets must be a multiple of 60"); + const int target_minutes = DIFFICULTY_TARGET / 60; + const int emission_speed_factor = EMISSION_SPEED_FACTOR_PER_MINUTE - (target_minutes-1); + + const uint64_t premine = 180000000000000000U; + if (median_weight > 0 && already_generated_coins < premine) { + reward = premine; + return true; + } + + base_reward = (MONEY_SUPPLY - already_generated_coins) >> emission_speed_factor; + if (base_reward < FINAL_SUBSIDY_PER_MINUTE*target_minutes) + { + base_reward = FINAL_SUBSIDY_PER_MINUTE*target_minutes; + } + } + else + { + // assumes that tail emission was already reached before/by HF_VERSION_SECOR + base_reward = (FINAL_SUBSIDY_PER_MINUTE / 60) * DIFFICULTY_TARGET_SECOR; } uint64_t full_reward_zone = get_min_block_weight(version); diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h index 44c3ebb5..5f1cbb3f 100644 --- a/src/cryptonote_basic/cryptonote_boost_serialization.h +++ b/src/cryptonote_basic/cryptonote_boost_serialization.h @@ -182,6 +182,7 @@ namespace boost a & b.prev_id; a & b.nonce; //------------------ + if (b.major_version >= HF_VERSION_SECOR) a & b.uncle_hash; a & b.miner_tx; a & b.tx_hashes; } diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index e487fd1d..a2e269e2 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -909,11 +909,13 @@ namespace cryptonote // try additional tx pubkeys if available if (!additional_derivations.empty()) { - CHECK_AND_ASSERT_MES(output_index < additional_derivations.size(), boost::none, "wrong number of additional derivations"); - hwdev.derive_subaddress_public_key(out_key, additional_derivations[output_index], output_index, subaddress_spendkey); - found = subaddresses.find(subaddress_spendkey); - if (found != subaddresses.end()) - return subaddress_receive_info{ found->second, additional_derivations[output_index] }; + for (auto additional_derivation : additional_derivations) + { + hwdev.derive_subaddress_public_key(out_key, additional_derivation, output_index, subaddress_spendkey); + found = subaddresses.find(subaddress_spendkey); + if (found != subaddresses.end()) + return subaddress_receive_info{ found->second, additional_derivation }; + } } return boost::none; } diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index effeb3bd..13593955 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -81,12 +81,12 @@ #define DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD ((uint64_t)10000000000000) #define DIFFICULTY_TARGET 60 +#define DIFFICULTY_TARGET_SECOR 15 #define DIFFICULTY_WINDOW 720 #define DIFFICULTY_LAG 15 #define DIFFICULTY_CUT 60 #define DIFFICULTY_BLOCKS_COUNT DIFFICULTY_WINDOW + DIFFICULTY_LAG -#define DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN DIFFICULTY_TARGET #define DIFFICULTY_WINDOW_V2 17 #define DIFFICULTY_CUT_V2 6 #define DIFFICULTY_BLOCKS_COUNT_V2 DIFFICULTY_WINDOW_V2 + DIFFICULTY_CUT_V2 * 2 @@ -98,6 +98,7 @@ #define DIFFICULTY_BLOCKS_COUNT_V6 DIFFICULTY_WINDOW_V6 + 1 #define CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V1 DIFFICULTY_TARGET *CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS +#define CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_SECOR DIFFICULTY_TARGET_SECOR *CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS #define CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS 1 #define BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT 10000 @@ -171,6 +172,10 @@ #define CRYPTONOTE_MAX_FRAGMENTS 20 +#define HF_VERSION_SECOR 13 +#define SECOR_UNCLE_REWARD_RATIO 2 +#define SECOR_NEPHEW_REWARD_RATIO 20 + #define DONATION_ADDR "NV1aMtARDQjK8j7XeoQ66S7XQe5ZS8CX92XqXmJxSZMpSDf2i11NQyqgHzghmRsDHR1LwYv3bEnE3VoqqbmyRdrR2MMBfdXvY" struct hard_fork @@ -258,7 +263,8 @@ namespace config { 9, 570}, {10, 580}, {11, 590}, - {12, 700} + {12, 700}, + {13, 800} }; } } diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 6c412ecb..c7939525 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -790,7 +790,7 @@ crypto::hash Blockchain::get_pending_block_id_by_height(uint64_t height) const return get_block_id_by_height(height); } //------------------------------------------------------------------ -bool Blockchain::get_block_by_hash(const crypto::hash &h, block &blk, bool *orphan) const +bool Blockchain::get_block_by_hash(const crypto::hash &h, block &blk, bool *orphan, bool *uncle) const { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); @@ -801,6 +801,8 @@ bool Blockchain::get_block_by_hash(const crypto::hash &h, block &blk, bool *orph blk = m_db->get_block(h); if (orphan) *orphan = false; + if (uncle) + *uncle = false; return true; } // try to find block in alternative chain @@ -815,8 +817,22 @@ bool Blockchain::get_block_by_hash(const crypto::hash &h, block &blk, bool *orph MERROR("Found block " << h << " in alt chain, but failed to parse it"); throw std::runtime_error("Found block in alt chain, but failed to parse it"); } + + if (data.height >= m_db->height()-1) // if the block has a height equal to that of the top block, then it can not be an uncle + { + if (orphan) + *orphan = true; + if (uncle) + *uncle = false; + return true; + } + cryptonote::block nephew_candidate = m_db->get_block(m_db->get_block_hash_from_height(data.height+1)); + if (orphan) - *orphan = true; + *orphan = nephew_candidate.uncle_hash != h; + if (uncle) + *uncle = nephew_candidate.uncle_hash == h; + return true; } } @@ -918,7 +934,7 @@ uint64_t Blockchain::get_difficulty_for_next_block() m_timestamps = timestamps; m_difficulties = difficulties; } - size_t target = DIFFICULTY_TARGET; + size_t target = version >= HF_VERSION_SECOR ? DIFFICULTY_TARGET_SECOR : DIFFICULTY_TARGET; uint64_t diff; if (version == 1) { diff = next_difficulty(timestamps, difficulties, target); @@ -1172,7 +1188,7 @@ uint64_t Blockchain::get_next_difficulty_for_alternative_chain(const std::list= HF_VERSION_SECOR ? DIFFICULTY_TARGET_SECOR : DIFFICULTY_TARGET; // calculate the difficulty target for the block and return it if (version == 1) { @@ -1242,7 +1258,59 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl return false; } - const uint64_t total_reward = base_reward + fee; + uint64_t total_reward = base_reward + fee; + if (version >= HF_VERSION_SECOR && b.uncle_hash != crypto::null_hash) + { + MDEBUG("beginning validation of miner tx for a block which references an uncle block"); + if (b.miner_tx.vout.size() != 2) + { + MERROR("miner tx from a block which includes an uncle hash must contain two separate outputs with separate recipients"); + return false; + } + + total_reward += base_reward / SECOR_NEPHEW_REWARD_RATIO; + total_reward += base_reward / SECOR_UNCLE_REWARD_RATIO; + // make sure that the uncle reward uses the output key from the uncle block miner tx and is for the correct amount - dont let the nephew miner steal the uncle reward + cryptonote::block uncle_block; + if (!get_block_by_hash(b.uncle_hash, uncle_block)) + { + MERROR("couldn't find uncle block " << b.uncle_hash << " in the database"); + return false; + } + // nephew reward at index 1 and uncle reward at index 0 is enforced + crypto::public_key uncle_out_key; + crypto::public_key uncle_tx_key; + get_uncle_block_original_miner_details(uncle_block, uncle_out_key, uncle_tx_key); + crypto::public_key mainchain_out_key; + crypto::public_key mainchain_tx_key; + int mainchain_miner_idx = get_mainchain_block_original_miner_details(b, mainchain_out_key, mainchain_tx_key); + int mainchain_uncle_idx = mainchain_miner_idx ? 0 : 1; + if (boost::get(b.miner_tx.vout[mainchain_uncle_idx].target).key != uncle_out_key) + { + MERROR_VER("nephew block miner tx does not include proper uncle reward output key"); + return false; + } + if (get_tx_pub_key_from_extra(b.miner_tx, mainchain_uncle_idx) != uncle_tx_key) + { + MERROR_VER("nephew block miner tx does not include proper uncle reward tx public key in tx extra"); + return false; + } + + if (b.miner_tx.vout[mainchain_uncle_idx].amount != base_reward / SECOR_UNCLE_REWARD_RATIO) + { + MERROR_VER("miner tx contains uncle reward but the uncle reward amount is incorrect. Amount found " << b.miner_tx.vout[1].amount << " vs amount expected " << base_reward / SECOR_UNCLE_REWARD_RATIO); + return false; + } + } + else if (version >= HF_VERSION_SECOR && b.uncle_hash == crypto::null_hash) + { + if (b.miner_tx.vout.size() > 1) + { + MERROR("miner tx after v" << HF_VERSION_SECOR << " cannot contain more than 1 output without an uncle hash in the mined block"); + return false; + } + } + if(total_reward != money_in_use && already_generated_coins > 0) { MERROR_VER("coinbase transaction doesn't use full amount of block reward: spent " << money_in_use @@ -1438,6 +1506,7 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, already_generated_coins = m_db->get_block_already_generated_coins(height - 1); } b.timestamp = time(NULL); + b.uncle_hash = crypto::null_hash; uint64_t median_ts; if (!check_block_timestamp(b, median_ts)) @@ -1454,6 +1523,21 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, return false; } pool_cookie = m_tx_pool.cookie(); + + const uint8_t hf_version = m_hardfork->get_current_version(); + if (hf_version >= HF_VERSION_SECOR) + { + // check alt chains for a block with the same height as the top block + for (auto it = m_uncle_candidates.end(); it != m_uncle_candidates.begin(); it--) + { + if (it->second == height - 1) + { + LOG_PRINT_L0("uncle block candidate found: " << it->first << " " << it->second); + b.uncle_hash = it->first; + } + } + } + #if defined(DEBUG_CREATE_BLOCK_TEMPLATE) size_t real_txs_weight = 0; uint64_t real_fee = 0; @@ -1500,8 +1584,20 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, block weight, so first miner transaction generated with fake amount of money, and with phase we know think we know expected block weight */ //make blocks coin-base tx looks close to real coinbase tx to get truthful blob size - uint8_t hf_version = m_hardfork->get_current_version(); - bool r = construct_miner_tx(height, median_weight, already_generated_coins, txs_weight, fee, miner_address, b.miner_tx, ex_nonce, 0, hf_version); + bool r; + int uncle_out_idx; + crypto::public_key uncle_out; + crypto::public_key uncle_tx_pubkey; + if (b.uncle_hash != crypto::null_hash) + { + cryptonote::block uncle_block; + get_block_by_hash(b.uncle_hash, uncle_block); + uncle_out_idx = get_uncle_block_original_miner_details(uncle_block, uncle_out, uncle_tx_pubkey); + r = construct_miner_tx(height, median_weight, already_generated_coins, txs_weight, fee, miner_address, b.miner_tx, ex_nonce, 0, hf_version, &uncle_out, &uncle_tx_pubkey, &uncle_out_idx); + } + else + r = construct_miner_tx(height, median_weight, already_generated_coins, txs_weight, fee, miner_address, b.miner_tx, ex_nonce, 0, hf_version); + CHECK_AND_ASSERT_MES(r, false, "Failed to construct miner tx, first chance"); size_t cumulative_weight = txs_weight + get_transaction_weight(b.miner_tx); #if defined(DEBUG_CREATE_BLOCK_TEMPLATE) @@ -1510,7 +1606,10 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, #endif for (size_t try_count = 0; try_count != 10; ++try_count) { - r = construct_miner_tx(height, median_weight, already_generated_coins, cumulative_weight, fee, miner_address, b.miner_tx, ex_nonce, 0, hf_version); + if (b.uncle_hash != crypto::null_hash) + r = construct_miner_tx(height, median_weight, already_generated_coins, cumulative_weight, fee, miner_address, b.miner_tx, ex_nonce, 0, hf_version, &uncle_out, &uncle_tx_pubkey, &uncle_out_idx); + else + r = construct_miner_tx(height, median_weight, already_generated_coins, cumulative_weight, fee, miner_address, b.miner_tx, ex_nonce, 0, hf_version); CHECK_AND_ASSERT_MES(r, false, "Failed to construct miner tx, second chance"); size_t coinbase_weight = get_transaction_weight(b.miner_tx); @@ -1805,6 +1904,14 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id data.already_generated_coins = bei.already_generated_coins; m_db->add_alt_block(id, data, cryptonote::block_to_blob(bei.bl)); alt_chain.push_back(bei); + LOG_PRINT_L0("pushing uncle block candidate: " << id << " " << data.height); + m_uncle_candidates.push_back(std::make_pair(id, data.height)); // TODO: free old data at some point + + uint64_t top_block_height = m_db->height()-1; + cryptonote::block top_block = m_db->get_top_block(); + crypto::hash pow_top_block; + memset(pow_top_block.data, 0xff, sizeof(pow_top_block.data)); + get_block_longhash(m_hash_context, this, top_block, pow_top_block, top_block_height); // FIXME: is it even possible for a checkpoint to show up not on the main chain? if(is_a_checkpoint) @@ -1831,6 +1938,17 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id bvc.m_verifivation_failed = true; return r; } + else if (block_height == top_block_height && reinterpret_cast(proof_of_work) < reinterpret_cast(pow_top_block)) + { + MGINFO_GREEN("###### REORGANIZE DUE TO UNCLE BLOCK on height: " << alt_chain.front().height << " of " << m_db->height() - 1 << " with cum_difficulty " << m_db->get_block_cumulative_difficulty(m_db->height() - 1) << std::endl << " alternative blockchain size: " << alt_chain.size() << " with cum_difficulty " << bei.cumulative_difficulty); + + bool r = switch_to_alternative_blockchain(alt_chain, false); + if (r) + bvc.m_added_to_main_chain = true; + else + bvc.m_verifivation_failed = true; + return r; + } else { MGINFO_BLUE("----- BLOCK ADDED AS ALTERNATIVE ON HEIGHT " << bei.height << std::endl << "id:\t" << id << std::endl << "PoW:\t" << proof_of_work << std::endl << "difficulty:\t" << current_diff); @@ -3238,7 +3356,7 @@ bool Blockchain::is_tx_spendtime_unlocked(uint64_t unlock_time) const { //interpret as time uint64_t current_time = static_cast(time(NULL)); - if(current_time + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V1 >= unlock_time) + if(current_time + (get_current_hard_fork_version() >= HF_VERSION_SECOR ? CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_SECOR : CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V1) >= unlock_time) return true; else return false; @@ -3693,6 +3811,71 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& if(blockchain_height) cumulative_difficulty += m_db->get_block_cumulative_difficulty(blockchain_height - 1); + if (bl.uncle_hash != crypto::null_hash) + { + MINFO("beginning validation of uncle block " << bl.uncle_hash); + CHECK_AND_ASSERT_MES(blockchain_height, 0, "blockchain_height is NULL"); + + cryptonote::block uncle_block; + if (!get_block_by_hash(bl.uncle_hash, uncle_block)) + { + MERROR_VER("uncle block with hash " << bl.uncle_hash << " could not be found in the blockchain database"); + bvc.m_verifivation_failed = true; + return_tx_to_pool(txs); + goto leave; + } + + // validate uncle block PoW + difficulty_type_128 uncle_difficulty = m_db->get_block_cumulative_difficulty(blockchain_height-2) - m_db->get_block_cumulative_difficulty(blockchain_height-3); + crypto::hash uncle_pow; + memset(uncle_pow.data, 0xff, sizeof(uncle_pow.data)); + if (!get_block_longhash(m_hash_context, this, uncle_block, uncle_pow, m_db->height()-1)) + { + MERROR_VER("could not generate PoW hash for uncle block " << bl.uncle_hash); + bvc.m_verifivation_failed = true; + return_tx_to_pool(txs); + goto leave; + } + if(!check_hash(uncle_pow, uncle_difficulty.convert_to())) + { + MERROR_VER("proof-of-work for uncle block failed verification"); + bvc.m_verifivation_failed = true; + return_tx_to_pool(txs); + goto leave; + } + + // the main chain block at the uncle block height should have a smaller PoW hash than the uncle block + cryptonote::block uncle_sibling_block; + crypto::hash uncle_sibling_hash = get_block_id_by_height(m_db->height()-1); + MDEBUG("verifying that uncle block " << bl.uncle_hash << " PoW is less than its sibling block " << uncle_sibling_hash); + if (!get_block_by_hash(uncle_sibling_hash, uncle_sibling_block)) + { + MERROR_VER("could not find uncle block sibling " << uncle_sibling_hash << " by hash in the database"); + bvc.m_verifivation_failed = true; + return_tx_to_pool(txs); + goto leave; + } + crypto::hash pow_uncle_sibling; + memset(pow_uncle_sibling.data, 0xff, sizeof(pow_uncle_sibling.data)); + if(!get_block_longhash(m_hash_context, this, uncle_sibling_block, pow_uncle_sibling, m_db->height()-1)) + { + MERROR_VER("failed to calculate block long hash for uncle block sibling"); + bvc.m_verifivation_failed = true; + return_tx_to_pool(txs); + goto leave; + } + if (reinterpret_cast(uncle_pow) >= reinterpret_cast(pow_uncle_sibling)) + { + MERROR_VER("the proof-of-work hash for an uncle block must be smaller than that of its main chain sibling"); + bvc.m_verifivation_failed = true; + return_tx_to_pool(txs); + goto leave; + } + + // add block difficulty for uncle block(s) to chain cumulative difficulty + cumulative_difficulty += uncle_difficulty; + } + TIME_MEASURE_FINISH(block_processing_time); rtxn_guard.stop(); @@ -3704,7 +3887,7 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& { uint64_t long_term_block_weight = get_next_long_term_block_weight(block_weight); cryptonote::blobdata bd = cryptonote::block_to_blob(bl); - new_height = m_db->add_block(std::make_pair(std::move(bl), std::move(bd)), block_weight, long_term_block_weight, cumulative_difficulty, already_generated_coins, txs); + new_height = m_db->add_block(std::make_pair(std::move(bl), std::move(bd)), block_weight, long_term_block_weight, cumulative_difficulty, already_generated_coins, txs, bl.uncle_hash); } catch (const KEY_IMAGE_EXISTS& e) { @@ -4631,7 +4814,7 @@ bool Blockchain::get_hard_fork_voting_info(uint8_t version, uint32_t &window, ui uint64_t Blockchain::get_difficulty_target() const { - return DIFFICULTY_TARGET; + return get_current_hard_fork_version() >= HF_VERSION_SECOR ? DIFFICULTY_TARGET_SECOR : DIFFICULTY_TARGET; } std::map> Blockchain:: get_output_histogram(const std::vector &amounts, bool unlocked, uint64_t recent_cutoff, uint64_t min_count) const diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index eace9477..f7026069 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -236,7 +236,7 @@ namespace cryptonote * * @return true if the block was found, else false */ - bool get_block_by_hash(const crypto::hash &h, block &blk, bool *orphan = NULL) const; + bool get_block_by_hash(const crypto::hash &h, block &blk, bool *orphan = NULL, bool *uncle = NULL) const; /** * @brief performs some preprocessing on a group of incoming blocks to speed up verification @@ -1095,6 +1095,8 @@ namespace cryptonote uint64_t m_prepare_nblocks; std::vector *m_prepare_blocks; + std::list> m_uncle_candidates; // id, height + /** * @brief collects the keys for all outputs being "spent" as an input * diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 172de0fe..5fe798e9 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -709,8 +709,8 @@ namespace cryptonote r = m_miner.init(vm, m_nettype); CHECK_AND_ASSERT_MES(r, false, "Failed to initialize miner instance"); - if (!keep_alt_blocks && !m_blockchain_storage.get_db().is_read_only()) - m_blockchain_storage.get_db().drop_alt_blocks(); + // if (!keep_alt_blocks && !m_blockchain_storage.get_db().is_read_only()) + // m_blockchain_storage.get_db().drop_alt_blocks(); if (prune_blockchain) { @@ -1450,6 +1450,7 @@ namespace cryptonote CHECK_AND_ASSERT_MES(!bvc.m_verifivation_failed, false, "mined block failed verification"); if(bvc.m_added_to_main_chain) { + relay_uncle_blocks(b, m_blockchain_storage.get_current_blockchain_height()); cryptonote_connection_context exclude_context = {}; NOTIFY_NEW_BLOCK::request arg = AUTO_VAL_INIT(arg); arg.current_blockchain_height = m_blockchain_storage.get_current_blockchain_height(); @@ -1846,6 +1847,22 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- + void core::relay_uncle_blocks(const cryptonote::block &b, uint64_t height) + { + // first relay uncle blocks if an uncle block hash is included in the block + if (b.uncle_hash != crypto::null_hash) + { + cryptonote::block uncle_block; + get_block_by_hash(b.uncle_hash, uncle_block); + relay_uncle_blocks(uncle_block, height-2); // -2 for uncle of an uncle + cryptonote_connection_context exclude_context = {}; + NOTIFY_NEW_BLOCK::request arg = AUTO_VAL_INIT(arg); + arg.current_blockchain_height = height-1; // -1 per uncle block definition + block_to_blob(uncle_block, arg.b.block); + m_pprotocol->relay_block(arg, exclude_context); + } + } + //----------------------------------------------------------------------------------------------- bool core::contact_server() { if (analytics::is_enabled() && (m_nettype == MAINNET || m_nettype == TESTNET)) diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 73d9d457..45a47d90 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -852,6 +852,8 @@ namespace cryptonote */ void flush_bad_txs_cache(); + void relay_uncle_blocks(const cryptonote::block &b, uint64_t height); + private: /** diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 60723c33..57d6f05c 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -81,13 +81,28 @@ namespace cryptonote } //--------------------------------------------------------------- bool construct_miner_tx(size_t height, size_t median_weight, uint64_t already_generated_coins, size_t current_block_weight, uint64_t fee, const account_public_address &miner_address, - transaction& tx, const blobdata& extra_nonce, size_t max_outs, uint8_t hard_fork_version) { + transaction& tx, const blobdata& extra_nonce, size_t max_outs, uint8_t hard_fork_version, crypto::public_key* uncle_reward_out_key, crypto::public_key* uncle_tx_pubkey, int* uncle_reward_idx) { tx.vin.clear(); tx.vout.clear(); tx.extra.clear(); keypair txkey = keypair::generate(hw::get_device("default")); - add_tx_pub_key_to_extra(tx, txkey.pub); + + size_t miner_index; + if (uncle_reward_out_key && uncle_tx_pubkey && uncle_reward_idx && *uncle_reward_idx == 0) { + miner_index = 1; + add_tx_pub_key_to_extra(tx, *uncle_tx_pubkey); + add_additional_tx_pub_keys_to_extra(tx.extra, { txkey.pub }); + } + else if (uncle_reward_out_key && uncle_tx_pubkey && uncle_reward_idx && *uncle_reward_idx == 1) { + miner_index = 0; + add_tx_pub_key_to_extra(tx, txkey.pub); + add_additional_tx_pub_keys_to_extra(tx.extra, { *uncle_tx_pubkey }); + } else { + miner_index = 0; + add_tx_pub_key_to_extra(tx, txkey.pub); + } + if(!extra_nonce.empty()) if(!add_extra_nonce_to_tx_extra(tx.extra, extra_nonce)) return false; @@ -108,6 +123,19 @@ namespace cryptonote LOG_PRINT_L1("Creating block template: reward " << block_reward << ", fee " << fee); #endif + if (uncle_reward_out_key && uncle_tx_pubkey) + { + txout_to_key tk; + tk.key = *uncle_reward_out_key; + + tx_out out; + out.amount = block_reward / SECOR_UNCLE_REWARD_RATIO; + out.target = tk; + tx.vout.push_back(out); + + block_reward += block_reward / SECOR_NEPHEW_REWARD_RATIO; + } + block_reward += fee; crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); @@ -115,7 +143,6 @@ namespace cryptonote bool r = crypto::generate_key_derivation(miner_address.m_view_public_key, txkey.sec, derivation); CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to generate_key_derivation(" << miner_address.m_view_public_key << ", " << rct::sk2rct(txkey.sec) << ")"); - size_t miner_index = 0; r = crypto::derive_public_key(derivation, miner_index, miner_address.m_spend_public_key, out_eph_public_key); CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to derive_public_key(" << derivation << ", " << miner_address.m_spend_public_key << ")"); @@ -801,4 +828,35 @@ namespace cryptonote return true; } + int get_uncle_block_original_miner_details(const cryptonote::block &block, crypto::public_key &output_key, crypto::public_key &tx_pkey) + { + if (block.uncle_hash == crypto::null_hash) + { + output_key = boost::get(block.miner_tx.vout[0].target).key; + tx_pkey = get_tx_pub_key_from_extra(block.miner_tx, 0); + return 0; + } + else + { + output_key = boost::get(block.miner_tx.vout[1].target).key; + tx_pkey = get_additional_tx_pub_keys_from_extra(block.miner_tx).at(0); + return 1; + } + } + + int get_mainchain_block_original_miner_details(const cryptonote::block &block, crypto::public_key &output_key, crypto::public_key &tx_pkey) + { + if (block.uncle_hash == crypto::null_hash) + { + output_key = boost::get(block.miner_tx.vout[1].target).key; + tx_pkey = get_tx_pub_key_from_extra(block.miner_tx, 1); + return 1; + } + else + { + output_key = boost::get(block.miner_tx.vout[0].target).key; + tx_pkey = get_additional_tx_pub_keys_from_extra(block.miner_tx).at(0); + return 0; + } + } } diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index ee2d5661..1c63bd0e 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -46,7 +46,7 @@ namespace cryptonote { //--------------------------------------------------------------- - bool construct_miner_tx(size_t height, size_t median_weight, uint64_t already_generated_coins, size_t current_block_weight, uint64_t fee, const account_public_address &miner_address, transaction& tx, const blobdata& extra_nonce = blobdata(), size_t max_outs = 999, uint8_t hard_fork_version = 1); + bool construct_miner_tx(size_t height, size_t median_weight, uint64_t already_generated_coins, size_t current_block_weight, uint64_t fee, const account_public_address &miner_address, transaction& tx, const blobdata& extra_nonce = blobdata(), size_t max_outs = 999, uint8_t hard_fork_version = 1, crypto::public_key* uncle_reward_out_key = nullptr, crypto::public_key* uncle_tx_pubkey = nullptr, int* uncle_reward_idx = nullptr); bool construct_genesis_tx(transaction& tx, uint64_t amount); @@ -154,6 +154,9 @@ namespace cryptonote bool get_block_longhash_v10(crypto::cn_hash_context_t *context, cryptonote::BlockchainDB &db, const blobdata &blob, crypto::hash &res, uint64_t height); bool get_block_longhash_v9(crypto::cn_hash_context_t *context, cryptonote::BlockchainDB &db, const blobdata &blob, crypto::hash &res, uint64_t height); bool get_block_longhash_v7_8(crypto::cn_hash_context_t *context, cryptonote::BlockchainDB &db, const blobdata &blob, crypto::hash &res, uint64_t height, uint64_t data_offset); + + int get_mainchain_block_original_miner_details(const cryptonote::block &block, crypto::public_key &output_key, crypto::public_key &tx_pkey); + int get_uncle_block_original_miner_details(const cryptonote::block &block, crypto::public_key &output_key, crypto::public_key &tx_pkey); } namespace boost diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index f75c80eb..d800b1af 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -495,6 +495,7 @@ namespace cryptonote if(bvc.m_added_to_main_chain) { //TODO: Add here announce protocol usage + m_core.relay_uncle_blocks(b, arg.current_blockchain_height); // relay uncle blocks BEFORE main block relay_block(arg, context); }else if(bvc.m_marked_as_orphaned) { @@ -781,6 +782,7 @@ namespace cryptonote if( bvc.m_added_to_main_chain ) { //TODO: Add here announce protocol usage + m_core.relay_uncle_blocks(new_block, arg.current_blockchain_height); // relay uncle blocks BEFORE main block NOTIFY_NEW_BLOCK::request reg_arg = AUTO_VAL_INIT(reg_arg); reg_arg.current_blockchain_height = arg.current_blockchain_height; reg_arg.b = b; diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 14a2ff5b..4c319f00 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -7805,7 +7805,7 @@ bool simple_wallet::get_transfers(std::vector& local_args, std::vec else { uint64_t current_time = static_cast(time(NULL)); - uint64_t threshold = current_time + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V1; + uint64_t threshold = current_time + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V1; // TODO: the correct threshold depends on hard fork version if (threshold < pd.m_unlock_time) locked_msg = get_human_readable_timespan(std::chrono::seconds(pd.m_unlock_time - threshold)); } @@ -9405,7 +9405,7 @@ bool simple_wallet::show_transfer(const std::vector &args) else { uint64_t current_time = static_cast(time(NULL)); - uint64_t threshold = current_time + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V1; + uint64_t threshold = current_time + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V1; // TODO: the correct threshold depends on hard fork version if (threshold >= pd.m_unlock_time) success_msg_writer() << "unlocked for " << get_human_readable_timespan(std::chrono::seconds(threshold - pd.m_unlock_time)); else diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index b8b7ba26..fa9734c3 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -1839,19 +1839,16 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote memcpy(&derivation, rct::identity().bytes, sizeof(derivation)); } - // additional tx pubkeys and derivations for multi-destination transfers involving one or more subaddresses - if (pk_index == 1) + // additional tx pubkeys and derivations for multi-destination transfers involving one or more subaddresses + if (find_tx_extra_field_by_type(tx_extra_fields, additional_tx_pub_keys)) { - if (find_tx_extra_field_by_type(tx_extra_fields, additional_tx_pub_keys)) + for (size_t i = 0; i < additional_tx_pub_keys.data.size(); ++i) { - for (size_t i = 0; i < additional_tx_pub_keys.data.size(); ++i) + additional_derivations.push_back({}); + if (!hwdev.generate_key_derivation(additional_tx_pub_keys.data[i], keys.m_view_secret_key, additional_derivations.back())) { - additional_derivations.push_back({}); - if (!hwdev.generate_key_derivation(additional_tx_pub_keys.data[i], keys.m_view_secret_key, additional_derivations.back())) - { - MWARNING("Failed to generate key derivation from additional tx pubkey in " << txid << ", skipping"); - memcpy(&additional_derivations.back(), rct::identity().bytes, sizeof(crypto::key_derivation)); - } + MWARNING("Failed to generate key derivation from additional tx pubkey in " << txid << ", skipping"); + memcpy(&additional_derivations.back(), rct::identity().bytes, sizeof(crypto::key_derivation)); } } } @@ -1862,13 +1859,10 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote error::wallet_internal_error, "pk_index out of range of tx_cache_data"); is_out_data_ptr = &tx_cache_data.primary[pk_index - 1]; derivation = tx_cache_data.primary[pk_index - 1].derivation; - if (pk_index == 1) + for (size_t n = 0; n < tx_cache_data.additional.size(); ++n) { - for (size_t n = 0; n < tx_cache_data.additional.size(); ++n) - { - additional_tx_pub_keys.data.push_back(tx_cache_data.additional[n].pkey); - additional_derivations.push_back(tx_cache_data.additional[n].derivation); - } + additional_tx_pub_keys.data.push_back(tx_cache_data.additional[n].pkey); + additional_derivations.push_back(tx_cache_data.additional[n].derivation); } } @@ -5863,7 +5857,7 @@ bool wallet2::is_tx_spendtime_unlocked(uint64_t unlock_time, uint64_t block_heig uint64_t current_time = static_cast(time(NULL)); // XXX: this needs to be fast, so we'd need to get the starting heights // from the daemon to be correct once voting kicks in - uint64_t leeway = CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V1; + uint64_t leeway = CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V1; // TODO: the correct threshold depends on hard fork version if(current_time + leeway >= unlock_time) return true; else @@ -9467,7 +9461,10 @@ bool wallet2::sanity_check(const std::vector &ptx_vector, s std::string proof = get_tx_proof(ptx.tx, ptx.tx_key, ptx.additional_tx_keys, address, r.second.second, "automatic-sanity-check"); check_tx_proof(ptx.tx, address, r.second.second, "automatic-sanity-check", proof, received); } - catch (const std::exception &e) { received = 0; } + catch (const std::exception &e) { + MDEBUG("tx proof failed sanity check"); + received = 0; + } total_received += received; }