def parse_fee(args: SExp, safe_mode: bool) -> List[bytes]: fee_int = sanitize_int(args.first(), safe_mode) if fee_int >= 2**64 or fee_int < 0: raise ValidationError(Err.RESERVE_FEE_CONDITION_FAILED) # note that this may change the representation of the fee. If the original # buffer had redundant leading zeroes, they will be stripped return [int_to_bytes(fee_int)]
async def test_assert_time_relative_exceeds(self, two_nodes): full_node_1, full_node_2, server_1, server_2 = two_nodes time_relative = 3 cvp = ConditionWithArgs(ConditionOpcode.ASSERT_SECONDS_RELATIVE, [int_to_bytes(time_relative)]) dic = {cvp.opcode: [cvp]} blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic) sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name()) assert sb1 is None assert status == MempoolInclusionStatus.FAILED assert err == Err.ASSERT_SECONDS_RELATIVE_FAILED for i in range(0, 4): await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"0")) tx2: full_node_protocol.RespondTransaction = full_node_protocol.RespondTransaction(spend_bundle1) status, err = await respond_transaction(full_node_1, tx2, peer) sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name()) assert sb1 is spend_bundle1 assert status == MempoolInclusionStatus.SUCCESS assert err is None
def test_get_name_puzzle_conditions(self): # this tests that extra block or coin data doesn't confuse `get_name_puzzle_conditions` gen = block_generator() cost, r = run_generator(gen, max_cost=MAX_COST) print(r) npc_result = get_name_puzzle_conditions(gen, max_cost=MAX_COST, cost_per_byte=COST_PER_BYTE, safe_mode=False) assert npc_result.error is None assert npc_result.clvm_cost == EXPECTED_COST cond_1 = ConditionWithArgs( ConditionOpcode.CREATE_COIN, [bytes([0] * 31 + [1]), int_to_bytes(500)]) CONDITIONS = [ (ConditionOpcode.CREATE_COIN, [cond_1]), ] npc = NPC( coin_name=bytes32.fromhex( "e8538c2d14f2a7defae65c5c97f5d4fae7ee64acef7fec9d28ad847a0880fd03" ), puzzle_hash=bytes32.fromhex( "9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2" ), conditions=CONDITIONS, ) assert npc_result.npc_list == [npc]
def generate_unsigned_transaction( self, amount: uint64, new_puzzle_hash: bytes32, coin: Coin, condition_dic: Dict[ConditionOpcode, List[ConditionWithArgs]], fee: int = 0, secret_key: Optional[PrivateKey] = None, ) -> List[CoinSolution]: spends = [] spend_value = coin.amount puzzle_hash = coin.puzzle_hash if secret_key is None: secret_key = self.get_private_key_for_puzzle_hash(puzzle_hash) pubkey = secret_key.get_g1() puzzle = puzzle_for_pk(bytes(pubkey)) if ConditionOpcode.CREATE_COIN not in condition_dic: condition_dic[ConditionOpcode.CREATE_COIN] = [] output = ConditionWithArgs(ConditionOpcode.CREATE_COIN, [new_puzzle_hash, int_to_bytes(amount)]) condition_dic[output.opcode].append(output) amount_total = sum(int_from_bytes(cvp.vars[1]) for cvp in condition_dic[ConditionOpcode.CREATE_COIN]) change = spend_value - amount_total - fee if change > 0: change_puzzle_hash = self.get_new_puzzlehash() change_output = ConditionWithArgs(ConditionOpcode.CREATE_COIN, [change_puzzle_hash, int_to_bytes(change)]) condition_dic[output.opcode].append(change_output) solution = self.make_solution(condition_dic) else: solution = self.make_solution(condition_dic) spends.append(CoinSolution(coin, puzzle, solution)) return spends
async def test_stealing_fee(self, two_nodes): reward_ph = WALLET_A.get_new_puzzlehash() full_node_1, full_node_2, server_1, server_2 = two_nodes blocks = await full_node_1.get_all_full_blocks() start_height = blocks[-1].height blocks = bt.get_consecutive_blocks( 5, block_list_input=blocks, guarantee_transaction_block=True, farmer_reward_puzzle_hash=reward_ph, pool_reward_puzzle_hash=reward_ph, ) full_node_1, full_node_2, server_1, server_2 = two_nodes peer = await connect_and_get_peer(server_1, server_2) for block in blocks: await full_node_1.full_node.respond_block( full_node_protocol.RespondBlock(block)) await time_out_assert(60, node_height_at_least, True, full_node_1, start_height + 5) receiver_puzzlehash = BURN_PUZZLE_HASH cvp = ConditionWithArgs(ConditionOpcode.RESERVE_FEE, [int_to_bytes(10)]) dic = {cvp.opcode: [cvp]} fee = 9 coin_1 = list(blocks[-2].get_included_reward_coins())[0] coin_2 = None for coin in list(blocks[-1].get_included_reward_coins()): if coin.amount == coin_1.amount: coin_2 = coin spend_bundle1 = generate_test_spend_bundle(coin_1, dic, uint64(fee)) steal_fee_spendbundle = WALLET_A.generate_signed_transaction( coin_1.amount + fee - 4, receiver_puzzlehash, coin_2) assert spend_bundle1 is not None assert steal_fee_spendbundle is not None combined = SpendBundle.aggregate( [spend_bundle1, steal_fee_spendbundle]) assert combined.fees() == 4 tx1: full_node_protocol.RespondTransaction = full_node_protocol.RespondTransaction( spend_bundle1) await full_node_1.respond_transaction(tx1, peer) mempool_bundle = full_node_1.full_node.mempool_manager.get_spendbundle( spend_bundle1.name()) assert mempool_bundle is None
def parse_amount(args: SExp, safe_mode: bool) -> List[bytes]: amount_int = sanitize_int(args.first(), safe_mode) if amount_int < 0: raise ValidationError(Err.ASSERT_MY_AMOUNT_FAILED) if amount_int >= 2**64: raise ValidationError(Err.ASSERT_MY_AMOUNT_FAILED) # note that this may change the representation of amount. If the original # buffer had redundant leading zeroes, they will be stripped return [int_to_bytes(amount_int)]
def get_hash(self) -> bytes32: # This does not use streamable format for hashing, the amount is # serialized using CLVM integer format. # Note that int_to_bytes() will prepend a 0 to integers where the most # significant bit is set, to encode it as a positive number. This # despite "amount" being unsigned. This way, a CLVM program can generate # these hashes easily. return std_hash(self.parent_coin_info + self.puzzle_hash + int_to_bytes(self.amount))
def parse_height(args: SExp, safe_mode: bool, error_code: Err) -> Optional[List[bytes]]: height_int = sanitize_int(args.first(), safe_mode) # this condition is inherently satisified, there is no need to keep it if height_int < 0: return None if height_int >= 2 ** 32: raise ValidationError(error_code) # note that this may change the representation of the height. If the original # buffer had redundant leading zeroes, they will be stripped return [int_to_bytes(height_int)]
async def test_negative_block_index(self, two_nodes): full_node_1, full_node_2, server_1, server_2 = two_nodes cvp = ConditionWithArgs(ConditionOpcode.ASSERT_HEIGHT_ABSOLUTE, [int_to_bytes(-1)]) dic = {ConditionOpcode.ASSERT_HEIGHT_ABSOLUTE: [cvp]} blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic) sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name()) assert sb1 is spend_bundle1 assert status == MempoolInclusionStatus.SUCCESS assert err is None
async def test_assert_fee_condition(self, two_nodes): full_node_1, full_node_2, server_1, server_2 = two_nodes cvp = ConditionWithArgs(ConditionOpcode.RESERVE_FEE, [int_to_bytes(10)]) dic = {cvp.opcode: [cvp]} blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic, fee=10) mempool_bundle = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name()) assert mempool_bundle is not None assert status == MempoolInclusionStatus.SUCCESS assert err is None
async def test_correct_block_age(self, two_nodes): full_node_1, full_node_2, server_1, server_2 = two_nodes cvp = ConditionWithArgs(ConditionOpcode.ASSERT_HEIGHT_RELATIVE, [int_to_bytes(1)]) dic = {cvp.opcode: [cvp]} blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic, num_blocks=4) sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name()) assert sb1 is spend_bundle1 assert status == MempoolInclusionStatus.SUCCESS assert err is None
async def test_invalid_block_age(self, two_nodes): full_node_1, full_node_2, server_1, server_2 = two_nodes cvp = ConditionWithArgs(ConditionOpcode.ASSERT_HEIGHT_RELATIVE, [int_to_bytes(5)]) dic = {cvp.opcode: [cvp]} blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic) sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name()) assert sb1 is None # the transaction may become valid later assert status == MempoolInclusionStatus.PENDING assert err == Err.ASSERT_HEIGHT_RELATIVE_FAILED
async def test_assert_fee_condition_fee_too_large(self, two_nodes): full_node_1, full_node_2, server_1, server_2 = two_nodes cvp = ConditionWithArgs(ConditionOpcode.RESERVE_FEE, [int_to_bytes(2 ** 64)]) dic = {cvp.opcode: [cvp]} blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic, fee=10) assert status == MempoolInclusionStatus.FAILED assert err == Err.RESERVE_FEE_CONDITION_FAILED blocks = bt.get_consecutive_blocks( 1, block_list_input=blocks, guarantee_transaction_block=True, transaction_data=spend_bundle1 ) assert full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name()) is None assert (await full_node_1.full_node.blockchain.receive_block(blocks[-1]))[1] == Err.RESERVE_FEE_CONDITION_FAILED
def parse_create_coin(args: SExp, safe_mode: bool) -> List[bytes]: puzzle_hash = args.first().atom args = args.rest() if len(puzzle_hash) != 32: raise ValidationError(Err.INVALID_CONDITION) amount_int = sanitize_int(args.first(), safe_mode) if amount_int >= 2**64: raise ValidationError(Err.COIN_AMOUNT_EXCEEDS_MAXIMUM) if amount_int < 0: raise ValidationError(Err.COIN_AMOUNT_NEGATIVE) # note that this may change the representation of amount. If the original # buffer had redundant leading zeroes, they will be stripped return [puzzle_hash, int_to_bytes(amount_int)]
async def test_my_amount_too_large(self, two_nodes): full_node_1, full_node_2, server_1, server_2 = two_nodes blocks = await full_node_1.get_all_full_blocks() cvp = ConditionWithArgs(ConditionOpcode.ASSERT_MY_AMOUNT, [int_to_bytes(2 ** 64)]) dic = {cvp.opcode: [cvp]} blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic) sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name()) assert sb1 is None assert status == MempoolInclusionStatus.FAILED assert err == Err.ASSERT_MY_AMOUNT_FAILED
async def test_assert_time_exceeds(self, two_nodes): full_node_1, full_node_2, server_1, server_2 = two_nodes # 5 seconds should be before the next block time_now = full_node_1.full_node.blockchain.get_peak().timestamp + 5 cvp = ConditionWithArgs(ConditionOpcode.ASSERT_SECONDS_ABSOLUTE, [int_to_bytes(time_now)]) dic = {cvp.opcode: [cvp]} blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic) sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name()) assert sb1 is spend_bundle1 assert status == MempoolInclusionStatus.SUCCESS assert err is None
async def test_assert_time_relative_negative(self, two_nodes): full_node_1, full_node_2, server_1, server_2 = two_nodes time_relative = -3 cvp = ConditionWithArgs(ConditionOpcode.ASSERT_SECONDS_RELATIVE, [int_to_bytes(time_relative)]) dic = {cvp.opcode: [cvp]} blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic) sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name()) assert sb1 is spend_bundle1 assert status == MempoolInclusionStatus.SUCCESS assert err is None
async def test_correct_my_amount(self, two_nodes): full_node_1, full_node_2, server_1, server_2 = two_nodes blocks = await full_node_1.get_all_full_blocks() coin = list(blocks[-1].get_included_reward_coins())[0] cvp = ConditionWithArgs(ConditionOpcode.ASSERT_MY_AMOUNT, [int_to_bytes(coin.amount)]) dic = {cvp.opcode: [cvp]} blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic, coin=coin) sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name()) assert sb1 is spend_bundle1 assert status == MempoolInclusionStatus.SUCCESS assert err is None
async def test_invalid_block_index(self, two_nodes): full_node_1, full_node_2, server_1, server_2 = two_nodes blocks = await full_node_1.get_all_full_blocks() start_height = blocks[-1].height cvp = ConditionWithArgs( ConditionOpcode.ASSERT_HEIGHT_ABSOLUTE, [int_to_bytes(start_height + 5)], ) dic = {ConditionOpcode.ASSERT_HEIGHT_ABSOLUTE: [cvp]} blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic) sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name()) assert sb1 is None # the transaction may become valid later assert status == MempoolInclusionStatus.PENDING assert err == Err.ASSERT_HEIGHT_ABSOLUTE_FAILED
async def test_assert_fee_condition_wrong_fee(self, two_nodes): reward_ph = WALLET_A.get_new_puzzlehash() full_node_1, full_node_2, server_1, server_2 = two_nodes blocks = await full_node_1.get_all_full_blocks() start_height = blocks[-1].height blocks = bt.get_consecutive_blocks( 3, block_list_input=blocks, guarantee_transaction_block=True, farmer_reward_puzzle_hash=reward_ph, pool_reward_puzzle_hash=reward_ph, ) peer = await connect_and_get_peer(server_1, server_2) for block in blocks: await full_node_1.full_node.respond_block( full_node_protocol.RespondBlock(block)) await time_out_assert(60, node_height_at_least, True, full_node_1, start_height + 3) cvp = ConditionWithArgs(ConditionOpcode.RESERVE_FEE, [int_to_bytes(10)]) dic = {cvp.opcode: [cvp]} spend_bundle1 = generate_test_spend_bundle( list(blocks[-1].get_included_reward_coins())[0], dic, uint64(9)) assert spend_bundle1 is not None tx1: full_node_protocol.RespondTransaction = full_node_protocol.RespondTransaction( spend_bundle1) await full_node_1.respond_transaction(tx1, peer) mempool_bundle = full_node_1.full_node.mempool_manager.get_spendbundle( spend_bundle1.name()) assert mempool_bundle is None
def __bytes__(self): return self.parent_coin_info + self.puzzle_hash + int_to_bytes(self.amount)
async def test_full_block_performance(self, wallet_nodes): full_node_1, server_1, wallet_a, wallet_receiver = wallet_nodes blocks = await full_node_1.get_all_full_blocks() full_node_1.full_node.mempool_manager.limit_factor = 1 wallet_ph = wallet_a.get_new_puzzlehash() blocks = bt.get_consecutive_blocks( 10, block_list_input=blocks, guarantee_transaction_block=True, farmer_reward_puzzle_hash=wallet_ph, pool_reward_puzzle_hash=wallet_ph, ) for block in blocks: await full_node_1.full_node.respond_block(fnp.RespondBlock(block)) start_height = (full_node_1.full_node.blockchain.get_peak().height if full_node_1.full_node.blockchain.get_peak() is not None else -1) incoming_queue, node_id = await add_dummy_connection(server_1, 12312) fake_peer = server_1.all_connections[node_id] # Mempool has capacity of 100, make 110 unspents that we can use puzzle_hashes = [] # Makes a bunch of coins for i in range(20): conditions_dict: Dict = {ConditionOpcode.CREATE_COIN: []} # This should fit in one transaction for _ in range(100): receiver_puzzlehash = wallet_receiver.get_new_puzzlehash() puzzle_hashes.append(receiver_puzzlehash) output = ConditionWithArgs( ConditionOpcode.CREATE_COIN, [receiver_puzzlehash, int_to_bytes(100000000)]) conditions_dict[ConditionOpcode.CREATE_COIN].append(output) spend_bundle = wallet_a.generate_signed_transaction( 100, puzzle_hashes[0], get_future_reward_coins(blocks[1 + i])[0], condition_dic=conditions_dict, ) assert spend_bundle is not None respond_transaction_2 = fnp.RespondTransaction(spend_bundle) await full_node_1.respond_transaction(respond_transaction_2, fake_peer) blocks = bt.get_consecutive_blocks( 1, block_list_input=blocks, guarantee_transaction_block=True, transaction_data=spend_bundle, ) await full_node_1.full_node.respond_block( fnp.RespondBlock(blocks[-1]), fake_peer) await time_out_assert(10, node_height_at_least, True, full_node_1, start_height + 20) spend_bundles = [] spend_bundle_ids = [] # Fill mempool for puzzle_hash in puzzle_hashes[1:]: coin_record = (await full_node_1.full_node.coin_store. get_coin_records_by_puzzle_hash(True, puzzle_hash))[0] receiver_puzzlehash = wallet_receiver.get_new_puzzlehash() if puzzle_hash == puzzle_hashes[-1]: fee = 100000000 # 100 million (20 fee per cost) else: fee = random.randint(1, 100000000) spend_bundle = wallet_receiver.generate_signed_transaction( uint64(500), receiver_puzzlehash, coin_record.coin, fee=fee) spend_bundles.append(spend_bundle) spend_bundle_ids.append(spend_bundle.get_hash()) pr = cProfile.Profile() pr.enable() start = time.time() num_tx: int = 0 for spend_bundle, spend_bundle_id in zip(spend_bundles, spend_bundle_ids): num_tx += 1 respond_transaction = fnp.RespondTransaction(spend_bundle) await full_node_1.respond_transaction(respond_transaction, fake_peer) request = fnp.RequestTransaction(spend_bundle_id) req = await full_node_1.request_transaction(request) if req is None: break log.warning(f"Num Tx: {num_tx}") log.warning(f"Time for mempool: {time.time() - start}") pr.create_stats() pr.dump_stats("./mempool-benchmark.pstats") # Create an unfinished block peak = full_node_1.full_node.blockchain.get_peak() assert peak is not None curr: BlockRecord = peak while not curr.is_transaction_block: curr = full_node_1.full_node.blockchain.block_record( curr.prev_hash) mempool_bundle = await full_node_1.full_node.mempool_manager.create_bundle_from_mempool( curr.header_hash) if mempool_bundle is None: spend_bundle = None else: spend_bundle = mempool_bundle[0] current_blocks = await full_node_1.get_all_full_blocks() blocks = bt.get_consecutive_blocks( 1, transaction_data=spend_bundle, block_list_input=current_blocks, guarantee_transaction_block=True, ) block = blocks[-1] unfinished = UnfinishedBlock( block.finished_sub_slots, block.reward_chain_block.get_unfinished(), block.challenge_chain_sp_proof, block.reward_chain_sp_proof, block.foliage, block.foliage_transaction_block, block.transactions_info, block.transactions_generator, [], ) pr = cProfile.Profile() pr.enable() start = time.time() res = await full_node_1.respond_unfinished_block( fnp.RespondUnfinishedBlock(unfinished), fake_peer) log.warning(f"Res: {res}") log.warning(f"Time for unfinished: {time.time() - start}") pr.create_stats() pr.dump_stats("./unfinished-benchmark.pstats") pr = cProfile.Profile() pr.enable() start = time.time() # No transactions generator, the full node already cached it from the unfinished block block_small = dataclasses.replace(block, transactions_generator=None) res = await full_node_1.full_node.respond_block( fnp.RespondBlock(block_small)) log.warning(f"Res: {res}") log.warning(f"Time for full block: {time.time() - start}") pr.create_stats() pr.dump_stats("./full-block-benchmark.pstats")
def generate_unsigned_transaction( self, amount: uint64, new_puzzle_hash: bytes32, coins: List[Coin], condition_dic: Dict[ConditionOpcode, List[ConditionWithArgs]], fee: int = 0, secret_key: Optional[PrivateKey] = None, additional_outputs: Optional[List[Tuple[bytes32, int]]] = None, ) -> List[CoinSpend]: spends = [] spend_value = sum([c.amount for c in coins]) if ConditionOpcode.CREATE_COIN not in condition_dic: condition_dic[ConditionOpcode.CREATE_COIN] = [] if ConditionOpcode.CREATE_COIN_ANNOUNCEMENT not in condition_dic: condition_dic[ConditionOpcode.CREATE_COIN_ANNOUNCEMENT] = [] output = ConditionWithArgs( ConditionOpcode.CREATE_COIN, [new_puzzle_hash, int_to_bytes(amount)]) condition_dic[output.opcode].append(output) if additional_outputs is not None: for o in additional_outputs: out = ConditionWithArgs(ConditionOpcode.CREATE_COIN, [o[0], int_to_bytes(o[1])]) condition_dic[out.opcode].append(out) amount_total = sum( int_from_bytes(cvp.vars[1]) for cvp in condition_dic[ConditionOpcode.CREATE_COIN]) change = spend_value - amount_total - fee if change > 0: change_puzzle_hash = self.get_new_puzzlehash() change_output = ConditionWithArgs( ConditionOpcode.CREATE_COIN, [change_puzzle_hash, int_to_bytes(change)]) condition_dic[output.opcode].append(change_output) secondary_coins_cond_dic: Dict[ConditionOpcode, List[ConditionWithArgs]] = dict() secondary_coins_cond_dic[ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT] = [] for n, coin in enumerate(coins): puzzle_hash = coin.puzzle_hash if secret_key is None: secret_key = self.get_private_key_for_puzzle_hash(puzzle_hash) pubkey = secret_key.get_g1() puzzle = puzzle_for_pk(bytes(pubkey)) if n == 0: message_list = [c.name() for c in coins] for outputs in condition_dic[ConditionOpcode.CREATE_COIN]: message_list.append( Coin(coin.name(), outputs.vars[0], int_from_bytes(outputs.vars[1])).name()) message = std_hash(b"".join(message_list)) condition_dic[ConditionOpcode.CREATE_COIN_ANNOUNCEMENT].append( ConditionWithArgs(ConditionOpcode.CREATE_COIN_ANNOUNCEMENT, [message])) primary_announcement_hash = Announcement(coin.name(), message).name() secondary_coins_cond_dic[ ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT].append( ConditionWithArgs( ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT, [primary_announcement_hash])) main_solution = self.make_solution(condition_dic) spends.append(CoinSpend(coin, puzzle, main_solution)) else: spends.append( CoinSpend(coin, puzzle, self.make_solution(secondary_coins_cond_dic))) return spends