def conditions_for_solution( puzzle_reveal: Program, solution: Program, ) -> Tuple[Optional[Err], Optional[List[ConditionVarPair]], uint64]: # get the standard script for a puzzle hash and feed in the solution try: cost, r = puzzle_reveal.run_with_cost(solution) error, result = parse_sexp_to_conditions(r) return error, result, uint64(cost) except Program.EvalError: return Err.SEXP_ERROR, None, uint64(0)
def conditions_dict_for_solution( puzzle_reveal: Program, solution: Program, ) -> Tuple[Optional[Err], Optional[Dict[ConditionOpcode, List[ConditionVarPair]]], uint64]: error, result, cost = conditions_for_solution(puzzle_reveal, solution) if error or result is None: return error, None, uint64(0) return None, conditions_by_opcode(result), cost
def calculate_cost_of_program(program: SerializedProgram, clvm_cost_ratio_constant: int, strict_mode: bool = False) -> CostResult: """ This function calculates the total cost of either a block or a spendbundle """ total_clvm_cost = 0 error, npc_list, cost = get_name_puzzle_conditions(program, strict_mode) if error: raise Exception("get_name_puzzle_conditions raised error:" + str(error)) total_clvm_cost += cost # Add cost of conditions npc: NPC total_vbyte_cost = 0 for npc in npc_list: for condition, cvp_list in npc.condition_dict.items(): if condition is ConditionOpcode.AGG_SIG or condition is ConditionOpcode.AGG_SIG_ME: total_vbyte_cost += len(cvp_list) * ConditionCost.AGG_SIG.value elif condition is ConditionOpcode.CREATE_COIN: total_vbyte_cost += len( cvp_list) * ConditionCost.CREATE_COIN.value elif condition is ConditionOpcode.ASSERT_SECONDS_NOW_EXCEEDS: total_vbyte_cost += len( cvp_list) * ConditionCost.ASSERT_SECONDS_NOW_EXCEEDS.value elif condition is ConditionOpcode.ASSERT_HEIGHT_AGE_EXCEEDS: total_vbyte_cost += len( cvp_list) * ConditionCost.ASSERT_HEIGHT_AGE_EXCEEDS.value elif condition is ConditionOpcode.ASSERT_HEIGHT_NOW_EXCEEDS: total_vbyte_cost += len( cvp_list) * ConditionCost.ASSERT_HEIGHT_NOW_EXCEEDS.value elif condition is ConditionOpcode.ASSERT_MY_COIN_ID: total_vbyte_cost += len( cvp_list) * ConditionCost.ASSERT_MY_COIN_ID.value elif condition is ConditionOpcode.RESERVE_FEE: total_vbyte_cost += len( cvp_list) * ConditionCost.RESERVE_FEE.value elif condition is ConditionOpcode.CREATE_ANNOUNCEMENT: total_vbyte_cost += len( cvp_list) * ConditionCost.CREATE_ANNOUNCEMENT.value elif condition is ConditionOpcode.ASSERT_ANNOUNCEMENT: total_vbyte_cost += len( cvp_list) * ConditionCost.ASSERT_ANNOUNCEMENT.value else: # We ignore unknown conditions in order to allow for future soft forks pass # Add raw size of the program total_vbyte_cost += len(bytes(program)) total_clvm_cost += total_vbyte_cost * clvm_cost_ratio_constant return CostResult(error, npc_list, uint64(total_clvm_cost))
def mempool_assert_relative_time_exceeds(condition: ConditionVarPair, unspent: CoinRecord): """ Check if the current time in millis exceeds the time specified by condition """ try: expected_mili_time = int_from_bytes(condition.vars[0]) except ValueError: return Err.INVALID_CONDITION current_time = uint64(int(time.time() * 1000)) if current_time <= expected_mili_time + unspent.timestamp: return Err.ASSERT_SECONDS_NOW_EXCEEDS_FAILED return None
def get_name_puzzle_conditions(block_program: SerializedProgram, safe_mode: bool): # TODO: allow generator mod to take something (future) # TODO: write more tests block_program_args = SerializedProgram.from_bytes(b"\x80") try: if safe_mode: cost, result = GENERATOR_MOD.run_safe_with_cost(block_program, block_program_args) else: cost, result = GENERATOR_MOD.run_with_cost(block_program, block_program_args) npc_list = [] opcodes = set(item.value for item in ConditionOpcode) for res in result.as_iter(): conditions_list = [] name = std_hash( bytes( res.first().first().as_atom() + res.first().rest().first().as_atom() + res.first().rest().rest().first().as_atom() ) ) puzzle_hash = bytes32(res.first().rest().first().as_atom()) for cond in res.rest().first().as_iter(): if cond.first().as_atom() in opcodes: opcode = ConditionOpcode(cond.first().as_atom()) elif not safe_mode: opcode = ConditionOpcode.UNKNOWN else: return "Unknown operator in safe mode.", None, None if len(list(cond.as_iter())) > 1: cond_var_list = [] for cond_1 in cond.rest().as_iter(): cond_var_list.append(cond_1.as_atom()) cvl = ConditionVarPair(opcode, cond_var_list) else: cvl = ConditionVarPair(opcode, []) conditions_list.append(cvl) conditions_dict = conditions_by_opcode(conditions_list) if conditions_dict is None: conditions_dict = {} npc_list.append(NPC(name, puzzle_hash, [(a, b) for a, b in conditions_dict.items()])) return None, npc_list, uint64(cost) except Exception: tb = traceback.format_exc() return tb, None, None
def calculate_pool_reward(height: uint32) -> uint64: """ Returns the pool reward at a certain block height. The pool earns 7/8 of the reward in each block. If the farmer is solo farming, they act as the pool, and therefore earn the entire block reward. These halving events will not be hit at the exact times (3 years, etc), due to fluctuations in difficulty. They will likely come early, if the network space and VDF rates increase continuously. """ if height == 0: return uint64(int((7 / 8) * 21000000 * _mojo_per_chia)) elif height < 3 * _blocks_per_year: return uint64(int((7 / 8) * 2 * _mojo_per_chia)) elif height < 6 * _blocks_per_year: return uint64(int((7 / 8) * 1 * _mojo_per_chia)) elif height < 9 * _blocks_per_year: return uint64(int((7 / 8) * 0.5 * _mojo_per_chia)) elif height < 12 * _blocks_per_year: return uint64(int((7 / 8) * 0.25 * _mojo_per_chia)) else: return uint64(int((7 / 8) * 0.125 * _mojo_per_chia))
def calculate_base_farmer_reward(height: uint32) -> uint64: """ Returns the base farmer reward at a certain block height. The base fee reward is 1/8 of total block reward Returns the coinbase reward at a certain block height. These halving events will not be hit at the exact times (3 years, etc), due to fluctuations in difficulty. They will likely come early, if the network space and VDF rates increase continuously. """ if height == 0: return uint64(int((1 / 8) * 21000000 * _mojo_per_chia)) elif height < 3 * _blocks_per_year: return uint64(int((1 / 8) * 2 * _mojo_per_chia)) elif height < 6 * _blocks_per_year: return uint64(int((1 / 8) * 1 * _mojo_per_chia)) elif height < 9 * _blocks_per_year: return uint64(int((1 / 8) * 0.5 * _mojo_per_chia)) elif height < 12 * _blocks_per_year: return uint64(int((1 / 8) * 0.25 * _mojo_per_chia)) else: return uint64(int((1 / 8) * 0.125 * _mojo_per_chia))
def float_to_timestamp(time: float) -> uint64: return uint64(int(time))
# Used as the initial cc rc challenges, as well as first block back pointers, and first SES back pointer # We override this value based on the chain being run (testnet0, testnet1, mainnet, etc) "GENESIS_CHALLENGE": bytes32([0x00] * 32), "GENESIS_PRE_FARM_POOL_PUZZLE_HASH": bytes.fromhex( "bc4fd6c394fe90c6097afbfa6ab5927743e2210ecf689dad477be8eb408745d5" ), "GENESIS_PRE_FARM_FARMER_PUZZLE_HASH": bytes.fromhex( "4ad98e66a019f11b60a8279514d13105b65588865e2a8e794b5b73d5646174f6" ), "MAX_VDF_WITNESS_SIZE": 64, # Target tx count per sec "TX_PER_SEC": 20, # Size of mempool = 10x the size of block "MEMPOOL_BLOCK_BUFFER": 10, # Max coin amount, fits into 64 bits "MAX_COIN_AMOUNT": uint64((1 << 64) - 1), # Targeting twice bitcoin's block size of 1.3MB per block # Raw size per block target = 1,300,000 * 600 / 47 = approx 100 KB # Rax TX (single in, single out) = 219 bytes (not compressed) # TX = 457 vBytes # floor(100 * 1024 / 219) * 457 = 213684 (size in vBytes) # Max block cost in virtual bytes "MAX_BLOCK_COST": 213684, # MAX block cost in clvm cost units = MAX_BLOCK_COST * CLVM_COST_RATIO_CONSTANT # 1 vByte = 108 clvm cost units "CLVM_COST_RATIO_CONSTANT": 108, # Max block cost in clvm cost units (MAX_BLOCK_COST * CLVM_COST_RATIO_CONSTANT) # "MAX_BLOCK_COST_CLVM": 23077872, "MAX_BLOCK_COST_CLVM": 40000000, # Based on arvid analysis "WEIGHT_PROOF_THRESHOLD": 2, "BLOCKS_CACHE_SIZE": 4608 + (128 * 4),
def from_bytes(cls, blob): parent_coin_info = blob[:32] puzzle_hash = blob[32:64] amount = int_from_bytes(blob[64:]) return Coin(parent_coin_info, puzzle_hash, uint64(amount))
def validate_spendbundle( new_spend: SpendBundle, mempool_removals: List[Coin], current_coin_records: List[CoinRecord], block_height: uint32, validate_signature=True ) -> Tuple[Optional[uint64], MempoolInclusionStatus, Optional[Err]]: spend_name = new_spend.name() cost_result = CostResult.from_bytes(validate_transaction(bytes(new_spend))) npc_list = cost_result.npc_list cost = cost_result.cost if cost > DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM: return None, MempoolInclusionStatus.FAILED, Err.BLOCK_COST_EXCEEDS_MAX if cost_result.error is not None: return None, MempoolInclusionStatus.FAILED, Err(cost_result.error) removal_names: List[bytes32] = new_spend.removal_names() additions = additions_for_npc(npc_list) additions_dict: Dict[bytes32, Coin] = {} for add in additions: additions_dict[add.name()] = add addition_amount = uint64(0) # Check additions for max coin amount for coin in additions: if coin.amount > DEFAULT_CONSTANTS.MAX_COIN_AMOUNT: return ( None, MempoolInclusionStatus.FAILED, Err.COIN_AMOUNT_EXCEEDS_MAXIMUM, ) addition_amount = uint64(addition_amount + coin.amount) # Check for duplicate outputs addition_counter = collections.Counter(_.name() for _ in additions) for k, v in addition_counter.items(): if v > 1: return None, MempoolInclusionStatus.FAILED, Err.DUPLICATE_OUTPUT # Check for duplicate inputs removal_counter = collections.Counter(name for name in removal_names) for k, v in removal_counter.items(): if v > 1: return None, MempoolInclusionStatus.FAILED, Err.DOUBLE_SPEND removal_record_dict: Dict[bytes32, CoinRecord] = {} removal_coin_dict: Dict[bytes32, Coin] = {} unknown_unspent_error: bool = False removal_amount = uint64(0) for name in removal_names: removal_record = list( filter(lambda e: e.coin.name() == name, current_coin_records)) if len(removal_record) == 0: removal_record = None else: removal_record = removal_record[0] if removal_record is None and name not in additions_dict: unknown_unspent_error = True break elif name in additions_dict: removal_coin = additions_dict[name] # TODO(straya): what timestamp to use here? removal_record = CoinRecord( removal_coin, uint32(self.peak.height + 1), # In mempool, so will be included in next height uint32(0), False, False, uint64(int(time.time())), ) assert removal_record is not None removal_amount = uint64(removal_amount + removal_record.coin.amount) removal_record_dict[name] = removal_record removal_coin_dict[name] = removal_record.coin if unknown_unspent_error: return None, MempoolInclusionStatus.FAILED, Err.UNKNOWN_UNSPENT if addition_amount > removal_amount: return None, MempoolInclusionStatus.FAILED, Err.MINTING_COIN fees = removal_amount - addition_amount assert_fee_sum: uint64 = uint64(0) for npc in npc_list: if ConditionOpcode.RESERVE_FEE in npc.condition_dict: fee_list: List[ConditionVarPair] = npc.condition_dict[ ConditionOpcode.RESERVE_FEE] for cvp in fee_list: fee = int_from_bytes(cvp.vars[0]) assert_fee_sum = assert_fee_sum + fee if fees < assert_fee_sum: return ( None, MempoolInclusionStatus.FAILED, Err.RESERVE_FEE_CONDITION_FAILED, ) if cost == 0: return None, MempoolInclusionStatus.FAILED, Err.UNKNOWN # Use this information later when constructing a block fail_reason, conflicts = check_removals(mempool_removals, removal_record_dict) # If there is a mempool conflict check if this spendbundle has a higher fee per cost than all others tmp_error: Optional[Err] = None conflicting_pool_items: Dict[bytes32, Coin] = {} if fail_reason is Err.MEMPOOL_CONFLICT: for conflicting in conflicts: # sb: Coin = mempool_removals[conflicting.name()] conflicting_pool_items[conflicting.name()] = conflicting # for item in conflicting_pool_items.values(): # if item.fee_per_cost >= fees_per_cost: # self.add_to_potential_tx_set(new_spend, spend_name, cost_result) print("The following items conflict with current mempool items: " + conflicting_pool_items) print( "This fails in the simulation, but the bigger fee_per_cost likely wins on the network" ) return ( uint64(cost), MempoolInclusionStatus.FAILED, Err.MEMPOOL_CONFLICT, ) elif fail_reason: return None, MempoolInclusionStatus.FAILED, fail_reason if tmp_error: return None, MempoolInclusionStatus.FAILED, tmp_error # Verify conditions, create hash_key list for aggsig check pks: List[G1Element] = [] msgs: List[bytes32] = [] error: Optional[Err] = None for npc in npc_list: coin_record: CoinRecord = removal_record_dict[npc.coin_name] # Check that the revealed removal puzzles actually match the puzzle hash if npc.puzzle_hash != coin_record.coin.puzzle_hash: return None, MempoolInclusionStatus.FAILED, Err.WRONG_PUZZLE_HASH chialisp_height = block_height - 1 error = mempool_check_conditions_dict(coin_record, new_spend, npc.condition_dict, uint32(chialisp_height)) if error: if error is Err.ASSERT_HEIGHT_NOW_EXCEEDS_FAILED or error is Err.ASSERT_HEIGHT_AGE_EXCEEDS_FAILED: return uint64(cost), MempoolInclusionStatus.PENDING, error break if validate_signature: for pk, message in pkm_pairs_for_conditions_dict( npc.condition_dict, npc.coin_name): pks.append(pk) msgs.append(message) if error: return None, MempoolInclusionStatus.FAILED, error if validate_signature: # Verify aggregated signature if not AugSchemeMPL.aggregate_verify(pks, msgs, new_spend.aggregated_signature): return None, MempoolInclusionStatus.FAILED, Err.BAD_AGGREGATE_SIGNATURE removals: List[Coin] = [coin for coin in removal_coin_dict.values()] # new_item = MempoolItem(new_spend, uint64(fees), cost_result, spend_name, additions, removals) # self.mempool.add_to_pool(new_item, additions, removal_coin_dict) # log.info(f"add_spendbundle took {time.time() - start_time} seconds") return uint64(cost), MempoolInclusionStatus.SUCCESS, None