fn speedup, transaction cost limit, max send amount

This commit is contained in:
Yostra 2021-02-15 18:17:22 -05:00 committed by Gene Hoffman
parent 6f5fbfc071
commit f0a317761a
15 changed files with 334 additions and 74 deletions

View File

@ -15,7 +15,6 @@ from src.consensus.coinbase import create_pool_coin, create_farmer_coin
from src.consensus.constants import ConsensusConstants
from src.full_node.bundle_tools import best_solution_program
from src.consensus.cost_calculator import calculate_cost_of_program, CostResult
from src.full_node.mempool_check_conditions import get_name_puzzle_conditions
from src.full_node.signage_point import SignagePoint
from src.consensus.block_record import BlockRecord
from src.types.blockchain_format.coin import Coin, hash_coin_list
@ -27,10 +26,10 @@ from src.types.blockchain_format.foliage import (
FoliageBlockData,
)
from src.types.full_block import additions_for_npc, FullBlock
from src.types.blockchain_format.pool_target import PoolTarget
from src.types.blockchain_format.program import SerializedProgram
from src.types.blockchain_format.proof_of_space import ProofOfSpace
from src.types.blockchain_format.reward_chain_block import (
from src.types.pool_target import PoolTarget
from src.types.program import SerializedProgram
from src.types.proof_of_space import ProofOfSpace
from src.types.reward_chain_block import (
RewardChainBlockUnfinished,
RewardChainBlock,
)
@ -49,6 +48,8 @@ def create_foliage(
constants: ConsensusConstants,
reward_block_unfinished: RewardChainBlockUnfinished,
spend_bundle: Optional[SpendBundle],
additions: List[Coin],
removals: List[Coin],
prev_block: Optional[BlockRecord],
blocks: BlockchainInterface,
total_iters_sp: uint128,
@ -132,13 +133,22 @@ def create_foliage(
if spend_bundle is not None:
solution_program = best_solution_program(spend_bundle)
spend_bundle_fees = spend_bundle.fees()
aggregate_sig = spend_bundle.aggregated_signature
# Calculate the cost of transactions
if solution_program is not None:
result: CostResult = calculate_cost_of_program(solution_program, constants.CLVM_COST_RATIO_CONSTANT)
cost = result.cost
removal_amount = 0
addition_amount = 0
for coin in removals:
removal_amount += coin.amount
for coin in additions:
addition_amount += coin.amount
spend_bundle_fees = removal_amount - addition_amount
else:
spend_bundle_fees = 0
# TODO: prev generators root
reward_claims_incorporated = []
if height > 0:
@ -179,17 +189,13 @@ def create_foliage(
)
reward_claims_incorporated += [pool_coin, farmer_coin]
curr = blocks.block_record(curr.prev_hash)
additions: List[Coin] = reward_claims_incorporated.copy()
npc_list = []
if solution_program is not None:
error, npc_list, _ = get_name_puzzle_conditions(solution_program, False)
additions += additions_for_npc(npc_list)
additions.extend(reward_claims_incorporated.copy())
for coin in additions:
tx_additions.append(coin)
byte_array_tx.append(bytearray(coin.puzzle_hash))
for npc in npc_list:
tx_removals.append(npc.coin_name)
byte_array_tx.append(bytearray(npc.coin_name))
for coin in removals:
tx_removals.append(coin.name())
byte_array_tx.append(bytearray(coin.name()))
bip158: PyBIP158 = PyBIP158(byte_array_tx)
encoded = bytes(bip158.GetEncoded())
@ -287,6 +293,8 @@ def create_unfinished_block(
blocks: BlockchainInterface,
seed: bytes32 = b"",
spend_bundle: Optional[SpendBundle] = None,
additions: Optional[List[Coin]] = None,
removals: Optional[List[Coin]] = None,
prev_block: Optional[BlockRecord] = None,
finished_sub_slots_input: List[EndOfSubSlotBundle] = None,
) -> UnfinishedBlock:
@ -311,6 +319,8 @@ def create_unfinished_block(
timestamp: timestamp to add to the foliage block, if created
seed: seed to randomize chain
spend_bundle: transactions to add to the foliage block, if created
additions: Coins added in spend_bundle
removals: Coins removed in spend_bundle
prev_block: previous block (already in chain) from the signage point
blocks: dictionary from header hash to SBR of all included SBR
finished_sub_slots_input: finished_sub_slots at the signage point
@ -369,11 +379,16 @@ def create_unfinished_block(
signage_point.rc_vdf,
rc_sp_signature,
)
if additions is None:
additions = []
if removals is None:
removals = []
(foliage, foliage_transaction_block, transactions_info, solution_program,) = create_foliage(
constants,
rc_block,
spend_bundle,
additions,
removals,
prev_block,
blocks,
total_iters_sp,

View File

@ -641,8 +641,18 @@ class FullNodeAPI:
peak: Optional[BlockRecord] = self.full_node.blockchain.get_peak()
if peak is None:
spend_bundle: Optional[SpendBundle] = None
additions = None
removals = None
else:
spend_bundle = await self.full_node.mempool_manager.create_bundle_from_mempool(peak.header_hash)
mempool_bundle = await self.full_node.mempool_manager.create_bundle_from_mempool(peak.header_hash)
if mempool_bundle is None:
spend_bundle = None
additions = None
removals = None
else:
spend_bundle = mempool_bundle[0]
additions = mempool_bundle[1]
removals = mempool_bundle[2]
def get_plot_sig(to_sign, _) -> G2Element:
if to_sign == request.challenge_chain_sp:
@ -764,6 +774,8 @@ class FullNodeAPI:
self.full_node.blockchain,
b"",
spend_bundle,
additions,
removals,
prev_b,
finished_sub_slots,
)
@ -1030,6 +1042,7 @@ class FullNodeAPI:
error: Optional[Err] = Err.NO_TRANSACTIONS_WHILE_SYNCING
else:
cost_result = await self.full_node.mempool_manager.pre_validate_spendbundle(request.transaction)
async with self.full_node.blockchain.lock:
cost, status, error = await self.full_node.mempool_manager.add_spendbundle(
request.transaction, cost_result, spend_name
@ -1038,13 +1051,15 @@ class FullNodeAPI:
self.log.debug(f"Added transaction to mempool: {spend_name}")
# Only broadcast successful transactions, not pending ones. Otherwise it's a DOS
# vector.
fees = request.transaction.fees()
mempool_item = self.full_node.mempool_manager.get_mempool_item(spend_name)
assert mempool_item is not None
fees = mempool_item.fee
assert fees >= 0
assert cost is not None
new_tx = full_node_protocol.NewTransaction(
spend_name,
cost,
uint64(request.transaction.fees()),
uint64(fees),
)
msg = make_msg(ProtocolMessageTypes.new_transaction, new_tx)
await self.full_node.server.send_to_all([msg], NodeType.FULL_NODE)

View File

@ -9,7 +9,7 @@ from src.types.blockchain_format.sized_bytes import bytes32
class Mempool:
spends: Dict[bytes32, MempoolItem]
sorted_spends: SortedDict
sorted_spends: SortedDict # Dict[float, Dict[bytes32, MempoolItem]]
additions: Dict[bytes32, MempoolItem]
removals: Dict[bytes32, MempoolItem]
size: int

View File

@ -78,7 +78,9 @@ class MempoolManager:
def shut_down(self):
self.pool.shutdown(wait=True)
async def create_bundle_from_mempool(self, peak_header_hash: bytes32) -> Optional[SpendBundle]:
async def create_bundle_from_mempool(
self, peak_header_hash: bytes32
) -> Optional[Tuple[SpendBundle, List[Coin], List[Coin]]]:
"""
Returns aggregated spendbundle that can be used for creating new block
"""
@ -92,6 +94,8 @@ class MempoolManager:
cost_sum = 0 # Checks that total cost does not exceed block maximum
fee_sum = 0 # Checks that total fees don't exceed 64 bits
spend_bundles: List[SpendBundle] = []
removals = []
additions = []
for dic in self.mempool.sorted_spends.values():
for item in dic.values():
if (
@ -101,10 +105,12 @@ class MempoolManager:
spend_bundles.append(item.spend_bundle)
cost_sum += item.cost_result.cost
fee_sum += item.fee
removals.extend(item.removals)
additions.extend(item.additions)
else:
break
if len(spend_bundles) > 0:
return SpendBundle.aggregate(spend_bundles)
return SpendBundle.aggregate(spend_bundles), additions, removals
else:
return None
@ -150,10 +156,12 @@ class MempoolManager:
Errors are included within the cached_result.
This runs in another process so we don't block the main thread
"""
start_time = time.time()
cached_result_bytes = await asyncio.get_running_loop().run_in_executor(
self.pool, validate_transaction_multiprocess, self.constants_json, bytes(new_spend)
)
end_time = time.time()
log.info(f"It took {end_time - start_time} to pre validate transaction")
return CostResult.from_bytes(cached_result_bytes)
async def add_spendbundle(
@ -342,7 +350,8 @@ class MempoolManager:
for mempool_item in conflicting_pool_items.values():
self.mempool.remove_spend(mempool_item)
new_item = MempoolItem(new_spend, fees_per_cost, uint64(fees), cost_result, spend_name)
removals: List[Coin] = [coin for coin in removal_coin_dict.values()]
new_item = MempoolItem(new_spend, fees_per_cost, 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
@ -388,6 +397,12 @@ class MempoolManager:
return self.mempool.spends[bundle_hash].spend_bundle
return None
def get_mempool_item(self, bundle_hash: bytes32) -> Optional[MempoolItem]:
""" Returns a MempoolItem if it's inside one the mempools"""
if bundle_hash in self.mempool.spends:
return self.mempool.spends[bundle_hash]
return None
async def new_peak(self, new_peak: Optional[BlockRecord]):
"""
Called when a new peak is available, we try to recreate a mempool for the new tip.

View File

@ -372,6 +372,7 @@ class WalletRpcApi:
pending_balance = await wallet.get_unconfirmed_balance(unspent_records)
spendable_balance = await wallet.get_spendable_balance(unspent_records)
pending_change = await wallet.get_pending_change_balance()
max_send_amount = await wallet.get_max_send_amount(unspent_records)
wallet_balance = {
"wallet_id": wallet_id,
@ -379,6 +380,7 @@ class WalletRpcApi:
"unconfirmed_wallet_balance": pending_balance,
"spendable_balance": spendable_balance,
"pending_change": pending_change,
"max_send_amount": max_send_amount,
}
return {"wallet_balance": wallet_balance}

View File

@ -6,7 +6,6 @@ from src.full_node.full_node_api import FullNodeAPI
from src.protocols.full_node_protocol import RespondBlock
from src.simulator.simulator_protocol import FarmNewBlockProtocol, ReorgProtocol
from src.types.full_block import FullBlock
from src.types.spend_bundle import SpendBundle
from src.util.api_decorators import api_request
from src.util.ints import uint8
@ -52,14 +51,22 @@ class FullNodeSimulator(FullNodeAPI):
peak = self.full_node.blockchain.get_peak()
assert peak is not None
bundle: Optional[SpendBundle] = await self.full_node.mempool_manager.create_bundle_from_mempool(
peak.header_hash
)
mempool_bundle = await self.full_node.mempool_manager.create_bundle_from_mempool(peak.header_hash)
if mempool_bundle is None:
spend_bundle = None
additions = None
removals = None
else:
spend_bundle = mempool_bundle[0]
additions = mempool_bundle[1]
removals = mempool_bundle[2]
current_blocks = await self.get_all_full_blocks()
target = request.puzzle_hash
more = self.bt.get_consecutive_blocks(
1,
transaction_data=bundle,
transaction_data=spend_bundle,
additions=additions,
removals=removals,
farmer_reward_puzzle_hash=target,
pool_reward_puzzle_hash=target,
block_list_input=current_blocks,
@ -83,14 +90,22 @@ class FullNodeSimulator(FullNodeAPI):
peak = self.full_node.blockchain.get_peak()
assert peak is not None
bundle: Optional[SpendBundle] = await self.full_node.mempool_manager.create_bundle_from_mempool(
peak.header_hash
)
mempool_bundle = await self.full_node.mempool_manager.create_bundle_from_mempool(peak.header_hash)
if mempool_bundle is None:
spend_bundle = None
additions = None
removals = None
else:
spend_bundle = mempool_bundle[0]
additions = mempool_bundle[1]
removals = mempool_bundle[2]
current_blocks = await self.get_all_full_blocks()
target = request.puzzle_hash
more = self.bt.get_consecutive_blocks(
1,
transaction_data=bundle,
transaction_data=spend_bundle,
additions=additions,
removals=removals,
farmer_reward_puzzle_hash=target,
pool_reward_puzzle_hash=target,
block_list_input=current_blocks,

View File

@ -1,6 +1,8 @@
from dataclasses import dataclass
from typing import List
from src.consensus.cost_calculator import CostResult
from src.types.coin import Coin
from src.types.spend_bundle import SpendBundle
from src.types.blockchain_format.sized_bytes import bytes32
from src.util.ints import uint64
@ -13,6 +15,8 @@ class MempoolItem:
fee: uint64
cost_result: CostResult
spend_bundle_name: bytes32
additions: List[Coin]
removals: List[Coin]
def __lt__(self, other):
# TODO test to see if it's < or >

View File

@ -34,6 +34,7 @@ from src.consensus.block_record import BlockRecord
from src.consensus.vdf_info_computation import get_signage_point_vdf_info
from src.plotting.plot_tools import load_plots, PlotInfo
from src.types.blockchain_format.classgroup import ClassgroupElement
from src.types.coin import Coin
from src.types.end_of_slot_bundle import EndOfSubSlotBundle
from src.types.full_block import FullBlock
from src.types.blockchain_format.pool_target import PoolTarget
@ -247,6 +248,8 @@ class BlockTools:
farmer_reward_puzzle_hash: Optional[bytes32] = None,
pool_reward_puzzle_hash: Optional[bytes32] = None,
transaction_data: Optional[SpendBundle] = None,
additions: Optional[List[Coin]] = None,
removals: Optional[List[Coin]] = None,
seed: bytes = b"",
time_per_block: Optional[float] = None,
force_overflow: bool = False,
@ -402,6 +405,8 @@ class BlockTools:
start_height,
time_per_block,
transaction_data,
additions,
removals,
height_to_hash,
difficulty,
required_iters,
@ -620,6 +625,8 @@ class BlockTools:
start_height,
time_per_block,
transaction_data,
additions,
removals,
height_to_hash,
difficulty,
required_iters,
@ -1162,6 +1169,8 @@ def get_full_block_and_sub_record(
start_height: uint32,
time_per_block: float,
transaction_data: Optional[SpendBundle],
additions: Optional[List[Coin]],
removals: Optional[List[Coin]],
height_to_hash: Dict[uint32, bytes32],
difficulty: uint64,
required_iters: uint64,
@ -1195,6 +1204,8 @@ def get_full_block_and_sub_record(
BlockCache(blocks),
seed,
transaction_data,
additions,
removals,
prev_block,
finished_sub_slots,
)

View File

@ -7,6 +7,8 @@ from secrets import token_bytes
from typing import Dict, Optional, List, Any, Set
from blspy import G2Element, AugSchemeMPL
from src.consensus.cost_calculator import calculate_cost_of_program, CostResult
from src.full_node.bundle_tools import best_solution_program
from src.protocols.wallet_protocol import PuzzleSolutionResponse
from src.types.blockchain_format.coin import Coin
from src.types.coin_solution import CoinSolution
@ -59,6 +61,7 @@ class CCWallet:
standard_wallet: Wallet
base_puzzle_program: Optional[bytes]
base_inner_puzzle_hash: Optional[bytes32]
cost_of_single_tx: Optional[int]
@staticmethod
async def create_new_cc(
@ -67,6 +70,7 @@ class CCWallet:
amount: uint64,
):
self = CCWallet()
self.cost_of_single_tx = None
self.base_puzzle_program = None
self.base_inner_puzzle_hash = None
self.standard_wallet = wallet
@ -150,8 +154,8 @@ class CCWallet:
wallet: Wallet,
genesis_checker_hex: str,
) -> CCWallet:
self = CCWallet()
self.cost_of_single_tx = None
self.base_puzzle_program = None
self.base_inner_puzzle_hash = None
self.standard_wallet = wallet
@ -227,6 +231,39 @@ class CCWallet:
self.log.info(f"Unconfirmed balance for cc wallet {self.id()} is {result}")
return uint128(result)
async def get_max_send_amount(self, records=None):
spendable: List[WalletCoinRecord] = list(
await self.wallet_state_manager.get_spendable_coins_for_wallet(self.id(), records)
)
if len(spendable) == 0:
return 0
spendable.sort(reverse=True, key=lambda record: record.coin.amount)
if self.cost_of_single_tx is None:
coin = spendable[0].coin
tx = await self.generate_signed_transaction(
coin.amount, coin.puzzle_hash, coins={coin}, ignore_max_send=True
)
program = best_solution_program(tx.spend_bundle)
# npc contains names of the coins removed, puzzle_hashes and their spend conditions
cost_result: CostResult = calculate_cost_of_program(
program, self.wallet_state_manager.constants.CLVM_COST_RATIO_CONSTANT, True
)
self.cost_of_single_tx = cost_result.cost
self.log.info(f"Cost of a single tx for standard wallet: {self.cost_of_single_tx}")
max_cost = self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM / 2 # avoid full block TXs
current_cost = 0
total_amount = 0
total_coin_count = 0
for record in spendable:
current_cost += self.cost_of_single_tx
total_amount += record.coin.amount
total_coin_count += 1
if current_cost + self.cost_of_single_tx > max_cost:
break
return total_amount
async def get_name(self):
return self.wallet_info.name
@ -523,19 +560,26 @@ class CCWallet:
fee: uint64 = uint64(0),
origin_id: bytes32 = None,
coins: Set[Coin] = None,
ignore_max_sent_amount: bool = False
) -> TransactionRecord:
sigs: List[G2Element] = []
# Get coins and calculate amount of change required
outgoing_amount = uint64(sum(amounts))
total_outgoing = outgoing_amount + fee
if not ignore_max_sent_amount:
max_send = await self.get_max_send_amount()
if total_outgoing > max_send:
raise ValueError(f"Can't send more than {max_send} in a single transaction")
if coins is None:
selected_coins: Set[Coin] = await self.select_coins(uint64(outgoing_amount + fee))
selected_coins: Set[Coin] = await self.select_coins(uint64(total_outgoing))
else:
selected_coins = coins
total_amount = sum([x.amount for x in selected_coins])
change = total_amount - outgoing_amount - fee
change = total_amount - total_outgoing
primaries = []
for amount, puzzle_hash in zip(amounts, puzzle_hashes):
primaries.append({"puzzlehash": puzzle_hash, "amount": amount})

View File

@ -95,6 +95,6 @@ def solution_with_hidden_puzzle(
return Program.to([puzzle, [hidden_public_key, hidden_puzzle, solution_to_hidden_puzzle]])
def solution_for_conditions(conditions: Program) -> Program:
def solution_for_conditions(conditions) -> Program:
delegated_puzzle = puzzle_for_conditions(conditions)
return solution_for_delegated_puzzle(delegated_puzzle, Program.to(0))

View File

@ -346,6 +346,10 @@ class RLWallet:
spendable_am = await self.wallet_state_manager.get_confirmed_spendable_balance_for_wallet(self.id())
return spendable_am
async def get_max_send_amount(self, records=None):
# Rate limited wallet is a singleton, max send is same as spendable
return await self.get_spendable_balance()
async def get_pending_change_balance(self) -> uint64:
unconfirmed_tx = await self.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(self.id())
addition_amount = 0

View File

@ -1,10 +1,12 @@
import logging
import time
from typing import Dict, List, Set, Any
from typing import Dict, List, Set, Any, Optional
from blspy import G1Element
from src.types.blockchain_format.coin import Coin
from src.consensus.cost_calculator import calculate_cost_of_program, CostResult
from src.full_node.bundle_tools import best_solution_program
from src.types.coin_solution import CoinSolution
from src.types.blockchain_format.program import Program
from src.types.blockchain_format.sized_bytes import bytes32
@ -38,6 +40,7 @@ class Wallet:
log: logging.Logger
wallet_id: uint32
secret_key_store: SecretKeyStore
cost_of_single_tx: Optional[int]
@staticmethod
async def create(
@ -53,9 +56,42 @@ class Wallet:
self.wallet_state_manager = wallet_state_manager
self.wallet_id = info.id
self.secret_key_store = SecretKeyStore()
self.cost_of_single_tx = None
return self
async def get_max_send_amount(self, records=None):
spendable: List[WalletCoinRecord] = list(
await self.wallet_state_manager.get_spendable_coins_for_wallet(self.id(), records)
)
if len(spendable) == 0:
return 0
spendable.sort(reverse=True, key=lambda record: record.coin.amount)
if self.cost_of_single_tx is None:
coin = spendable[0].coin
tx = await self.generate_signed_transaction(
coin.amount, coin.puzzle_hash, coins={coin}, ignore_max_send=True
)
program = best_solution_program(tx.spend_bundle)
# npc contains names of the coins removed, puzzle_hashes and their spend conditions
cost_result: CostResult = calculate_cost_of_program(
program, self.wallet_state_manager.constants.CLVM_COST_RATIO_CONSTANT, True
)
self.cost_of_single_tx = cost_result.cost
self.log.info(f"Cost of a single tx for standard wallet: {self.cost_of_single_tx}")
max_cost = self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM / 2 # avoid full block TXs
current_cost = 0
total_amount = 0
total_coin_count = 0
for record in spendable:
current_cost += self.cost_of_single_tx
total_amount += record.coin.amount
total_coin_count += 1
if current_cost + self.cost_of_single_tx > max_cost:
break
return total_amount
@classmethod
def type(cls) -> uint8:
return uint8(WalletType.STANDARD_WALLET)
@ -135,7 +171,7 @@ class Wallet:
def make_solution(
self,
primaries=None,
primaries: Optional[List[Dict[str, bytes32]]] = None,
min_time=0,
me=None,
announcements=None,
@ -185,7 +221,7 @@ class Wallet:
used_coins: Set = set()
# Use older coins first
unspent.sort(key=lambda r: r.confirmed_block_height)
unspent.sort(reverse=True, key=lambda r: r.coin.amount)
# Try to use coins from the store, if there isn't enough of "unused"
# coins use change coins that are not confirmed yet
@ -220,17 +256,32 @@ class Wallet:
fee: uint64 = uint64(0),
origin_id: bytes32 = None,
coins: Set[Coin] = None,
primaries: Optional[List[Dict[str, bytes32]]] = None,
ignore_max_send: bool = False,
) -> List[CoinSolution]:
"""
Generates a unsigned transaction in form of List(Puzzle, Solutions)
"""
if primaries is None:
total_amount = amount + fee
else:
primaries_amount = 0
for prim in primaries:
primaries_amount += prim["amount"]
total_amount = amount + fee + primaries_amount
if not ignore_max_send:
max_send = await self.get_max_send_amount()
if total_amount > max_send:
raise ValueError(f"Can't send more than {max_send} in a single transaction")
if coins is None:
coins = await self.select_coins(amount + fee)
coins = await self.select_coins(total_amount)
assert len(coins) > 0
self.log.info(f"coins is not None {coins}")
spend_value = sum([coin.amount for coin in coins])
change = spend_value - amount - fee
change = spend_value - total_amount
spends: List[CoinSolution] = []
output_created = False
@ -241,11 +292,13 @@ class Wallet:
# Only one coin creates outputs
if not output_created and origin_id in (None, coin.name()):
primaries = [{"puzzlehash": newpuzzlehash, "amount": amount}]
if primaries is None:
primaries = [{"puzzlehash": newpuzzlehash, "amount": amount}]
else:
primaries.append({"puzzlehash": newpuzzlehash, "amount": amount})
if change > 0:
changepuzzlehash = await self.get_new_puzzlehash()
primaries.append({"puzzlehash": changepuzzlehash, "amount": change})
solution = self.make_solution(primaries=primaries, fee=fee)
output_created = True
else:
@ -267,10 +320,14 @@ class Wallet:
fee: uint64 = uint64(0),
origin_id: bytes32 = None,
coins: Set[Coin] = None,
primaries: Optional[List[Dict[str, bytes32]]] = None,
ignore_max_send: bool = False,
) -> TransactionRecord:
""" Use this to generate transaction. """
transaction = await self.generate_unsigned_transaction(amount, puzzle_hash, fee, origin_id, coins)
transaction = await self.generate_unsigned_transaction(
amount, puzzle_hash, fee, origin_id, coins, primaries, ignore_max_send
)
assert len(transaction) > 0
self.log.info("About to sign a transaction")

View File

@ -1,4 +1,4 @@
from typing import Dict, Optional, List, Set
from typing import Dict, Optional, List, Set, Any
import aiosqlite
from src.types.blockchain_format.sized_bytes import bytes32
from src.util.ints import uint32, uint8
@ -16,7 +16,7 @@ class WalletTransactionStore:
db_connection: aiosqlite.Connection
cache_size: uint32
tx_record_cache: Dict[bytes32, TransactionRecord]
tx_wallet_cache: Dict[int, Set[bytes32]]
tx_wallet_cache: Dict[int, Dict[Any, Set[bytes32]]]
@classmethod
async def create(cls, connection: aiosqlite.Connection, cache_size: uint32 = uint32(600000)):
@ -109,7 +109,10 @@ class WalletTransactionStore:
self.tx_record_cache[record.name] = record
if record.wallet_id in self.tx_wallet_cache:
self.tx_wallet_cache[record.wallet_id].add(record.name)
if None in self.tx_wallet_cache[record.wallet_id]:
self.tx_wallet_cache[record.wallet_id][None].add(record.name)
if record.type in self.tx_wallet_cache[record.wallet_id]:
self.tx_wallet_cache[record.wallet_id][record.type].add(record.name)
if len(self.tx_record_cache) > self.cache_size:
while len(self.tx_record_cache) > self.cache_size:
@ -156,7 +159,8 @@ class WalletTransactionStore:
async def tx_with_addition_coin(self, removal_id: bytes32, wallet_id: int) -> List[TransactionRecord]:
""" Returns a record containing removed coin with id: removal_id"""
result = []
all_records: List[TransactionRecord] = await self.get_all_transactions(wallet_id, TransactionType.OUTGOING_TX)
all_records = await self.get_all_transactions(wallet_id, TransactionType.OUTGOING_TX.value)
for record in all_records:
for coin in record.additions:
if coin.name() == removal_id:
@ -352,15 +356,15 @@ class WalletTransactionStore:
await cursor.close()
return count
async def get_all_transactions(self, wallet_id: int, type=None) -> List[TransactionRecord]:
async def get_all_transactions(self, wallet_id: int, type: int = None) -> List[TransactionRecord]:
"""
Returns all stored transactions.
"""
if type is None and wallet_id in self.tx_wallet_cache:
wallet_txs = self.tx_wallet_cache[wallet_id]
if wallet_id in self.tx_wallet_cache and type in self.tx_wallet_cache[wallet_id]:
wallet_txs = self.tx_wallet_cache[wallet_id][type]
txs = []
for tx_id in wallet_txs:
txs.append(self.tx_record_cache[tx_id])
for name in wallet_txs:
txs.append(self.tx_record_cache[name])
return txs
if type is None:
@ -385,8 +389,9 @@ class WalletTransactionStore:
records.append(record)
cache_set.add(record.name)
if type is None:
self.tx_wallet_cache[wallet_id] = cache_set
if wallet_id not in self.tx_wallet_cache:
self.tx_wallet_cache[wallet_id] = {}
self.tx_wallet_cache[wallet_id][type] = cache_set
return records

View File

@ -22,6 +22,13 @@ def event_loop():
yield loop
async def tx_in_pool(mempool: MempoolManager, tx_id: bytes32):
tx = mempool.get_spendbundle(tx_id)
if tx is None:
return False
return True
class TestCCWallet:
@pytest.fixture(scope="function")
async def wallet_node(self):
@ -38,12 +45,6 @@ class TestCCWallet:
async for _ in setup_simulators_and_wallets(1, 3, {}):
yield _
async def tx_in_pool(self, mempool: MempoolManager, tx_id: bytes32):
tx = mempool.get_spendbundle(tx_id)
if tx is None:
return False
return True
@pytest.mark.asyncio
async def test_colour_creation(self, two_wallet_nodes):
num_blocks = 3
@ -72,7 +73,7 @@ class TestCCWallet:
tx_queue: List[TransactionRecord] = await wallet_node.wallet_state_manager.get_send_queue()
tx_record = tx_queue[0]
await time_out_assert(
15, self.tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record.spend_bundle.name()
15, tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record.spend_bundle.name()
)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"0"))
@ -112,7 +113,7 @@ class TestCCWallet:
tx_queue: List[TransactionRecord] = await wallet_node.wallet_state_manager.get_send_queue()
tx_record = tx_queue[0]
await time_out_assert(
15, self.tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record.spend_bundle.name()
15, tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record.spend_bundle.name()
)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"0"))
@ -132,7 +133,7 @@ class TestCCWallet:
await wallet.wallet_state_manager.add_pending_transaction(tx_record)
await time_out_assert(
15, self.tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record.spend_bundle.name()
15, tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record.spend_bundle.name()
)
for i in range(1, num_blocks):
@ -149,7 +150,7 @@ class TestCCWallet:
await wallet.wallet_state_manager.add_pending_transaction(tx_record)
await time_out_assert(
15, self.tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record.spend_bundle.name()
15, tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record.spend_bundle.name()
)
for i in range(1, num_blocks):
@ -234,7 +235,7 @@ class TestCCWallet:
assert cc_wallet.cc_info.my_genesis_checker == cc_wallet_2.cc_info.my_genesis_checker
spend_bundle = await cc_wallet_2.generate_zero_val_coin()
await time_out_assert(15, self.tx_in_pool, True, full_node_api.full_node.mempool_manager, spend_bundle.name())
await time_out_assert(15, tx_in_pool, True, full_node_api.full_node.mempool_manager, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
@ -282,7 +283,7 @@ class TestCCWallet:
tx_queue: List[TransactionRecord] = await wallet_node.wallet_state_manager.get_send_queue()
tx_record = tx_queue[0]
await time_out_assert(
15, self.tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record.spend_bundle.name()
15, tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record.spend_bundle.name()
)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"0"))
@ -301,7 +302,7 @@ class TestCCWallet:
tx_record = await cc_wallet.generate_signed_transaction([uint64(60)], [cc_2_hash])
await wallet.wallet_state_manager.add_pending_transaction(tx_record)
await time_out_assert(
15, self.tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record.spend_bundle.name()
15, tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record.spend_bundle.name()
)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"0"))
@ -316,7 +317,7 @@ class TestCCWallet:
tx_record = await wallet.wallet_state_manager.main_wallet.generate_signed_transaction(10, cc2_ph, 0)
await wallet.wallet_state_manager.add_pending_transaction(tx_record)
await time_out_assert(
15, self.tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record.spend_bundle.name()
15, tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record.spend_bundle.name()
)
for i in range(0, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"0"))
@ -362,7 +363,7 @@ class TestCCWallet:
tx_queue: List[TransactionRecord] = await wallet_node_0.wallet_state_manager.get_send_queue()
tx_record = tx_queue[0]
await time_out_assert(
15, self.tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record.spend_bundle.name()
15, tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record.spend_bundle.name()
)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"0"))
@ -390,7 +391,7 @@ class TestCCWallet:
tx_record = await cc_wallet_0.generate_signed_transaction([uint64(60), uint64(20)], [cc_1_hash, cc_2_hash])
await wallet_0.wallet_state_manager.add_pending_transaction(tx_record)
await time_out_assert(
15, self.tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record.spend_bundle.name()
15, tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record.spend_bundle.name()
)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"0"))
@ -412,10 +413,10 @@ class TestCCWallet:
tx_record_2 = await cc_wallet_2.generate_signed_transaction([uint64(20)], [cc_hash])
await wallet_2.wallet_state_manager.add_pending_transaction(tx_record_2)
await time_out_assert(
15, self.tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record.spend_bundle.name()
15, tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record.spend_bundle.name()
)
await time_out_assert(
15, self.tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record_2.spend_bundle.name()
15, tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record_2.spend_bundle.name()
)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"0"))

View File

@ -13,6 +13,7 @@ from src.wallet.util.transaction_type import TransactionType
from src.wallet.wallet_state_manager import WalletStateManager
from tests.setup_nodes import setup_simulators_and_wallets, self_hostname
from tests.time_out_assert import time_out_assert, time_out_assert_not_none
from tests.wallet.cc_wallet.test_cc_wallet import tx_in_pool
@pytest.fixture(scope="module")
@ -382,3 +383,74 @@ class TestWalletSimulator:
await time_out_assert(5, wallet.get_confirmed_balance, new_funds - tx_amount - tx_fee)
await time_out_assert(5, wallet.get_unconfirmed_balance, new_funds - tx_amount - tx_fee)
@pytest.mark.asyncio
async def test_wallet_create_hit_max_send_amount(self, two_wallet_nodes):
num_blocks = 5
full_nodes, wallets = two_wallet_nodes
full_node_1 = full_nodes[0]
wallet_node, server_2 = wallets[0]
wallet_node_2, server_3 = wallets[1]
wallet = wallet_node.wallet_state_manager.main_wallet
ph = await wallet.get_new_puzzlehash()
await server_2.start_client(PeerInfo(self_hostname, uint16(full_node_1.full_node.server._port)), None)
for i in range(0, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph))
funds = sum(
[calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks)]
)
await time_out_assert(5, wallet.get_confirmed_balance, funds)
primaries = []
for i in range(0, 600):
primaries.append({"puzzlehash": ph, "amount": 100000000 + i})
tx_split_coins = await wallet.generate_signed_transaction(1, ph, 0, primaries=primaries)
await wallet.push_transaction(tx_split_coins)
await time_out_assert(
15, tx_in_pool, True, full_node_1.full_node.mempool_manager, tx_split_coins.spend_bundle.name()
)
for i in range(0, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"0"))
funds = sum(
[
calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i))
for i in range(1, num_blocks + 1)
]
)
await time_out_assert(25, wallet.get_confirmed_balance, funds)
max_sent_amount = await wallet.get_max_send_amount()
# 1) Generate transaction that is under the limit
under_limit_tx = None
try:
under_limit_tx = await wallet.generate_signed_transaction(max_sent_amount - 1, ph, 0, )
except ValueError:
assert ValueError
assert under_limit_tx is not None
# 2) Generate transaction that is equal to limit
at_limit_tx = None
try:
at_limit_tx = await wallet.generate_signed_transaction(max_sent_amount, ph, 0, )
except ValueError:
assert ValueError
assert at_limit_tx is not None
# 3) Generate transaction that is greater than limit
above_limit_tx = None
try:
above_limit_tx = await wallet.generate_signed_transaction(max_sent_amount + 1, ph, 0, )
except ValueError:
pass
assert above_limit_tx is None