mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2024-09-21 00:24:37 +03:00
Merge branch 'integration' of github.com:Chia-Network/chia-blockchain into integration
This commit is contained in:
commit
e372d362b4
@ -5,8 +5,8 @@ class ConditionCost(Enum):
|
||||
# Condition Costs
|
||||
AGG_SIG = 20
|
||||
CREATE_COIN = 200
|
||||
ASSERT_COIN_CONSUMED = 5
|
||||
ASSERT_MY_COIN_ID = 5
|
||||
ASSERT_TIME_EXCEEDS = 5
|
||||
ASSERT_BLOCK_INDEX_EXCEEDS = 5
|
||||
ASSERT_BLOCK_AGE_EXCEEDS = 5
|
||||
ASSERT_COIN_CONSUMED = 0
|
||||
ASSERT_MY_COIN_ID = 0
|
||||
ASSERT_TIME_EXCEEDS = 0
|
||||
ASSERT_BLOCK_INDEX_EXCEEDS = 0
|
||||
ASSERT_BLOCK_AGE_EXCEEDS = 0
|
||||
|
@ -827,20 +827,25 @@ class Blockchain:
|
||||
fork_h = self._find_fork_for_lca(old_lca, self.lca_block)
|
||||
# Rollback to fork
|
||||
await self.unspent_store.rollback_lca_to_block(fork_h)
|
||||
# Nuke DiffStore
|
||||
self.unspent_store.nuke_diffs()
|
||||
|
||||
# Add blocks between fork point and new lca
|
||||
fork_hash = self.height_to_hash[fork_h]
|
||||
fork_head = self.headers[fork_hash]
|
||||
await self._from_fork_to_lca(fork_head, self.lca_block)
|
||||
# Create DiffStore
|
||||
await self._create_diffs_for_tips(self.lca_block)
|
||||
if not self.store.get_sync_mode():
|
||||
await self.recreate_diff_stores()
|
||||
else:
|
||||
# If LCA has not changed just update the difference
|
||||
self.unspent_store.nuke_diffs()
|
||||
# Create DiffStore
|
||||
await self._create_diffs_for_tips(self.lca_block)
|
||||
|
||||
async def recreate_diff_stores(self):
|
||||
# Nuke DiffStore
|
||||
self.unspent_store.nuke_diffs()
|
||||
# Create DiffStore
|
||||
await self._create_diffs_for_tips(self.lca_block)
|
||||
|
||||
def _reconsider_heights(self, old_lca: Optional[Header], new_lca: Header):
|
||||
"""
|
||||
Update the mapping from height to block hash, when the lca changes.
|
||||
|
@ -31,8 +31,6 @@ class CoinStore:
|
||||
"""
|
||||
|
||||
coin_record_db: aiosqlite.Connection
|
||||
# Whether or not we are syncing
|
||||
sync_mode: bool = False
|
||||
lock: asyncio.Lock
|
||||
lca_coin_records: Dict[str, CoinRecord]
|
||||
head_diffs: Dict[bytes32, DiffStore]
|
||||
|
@ -543,6 +543,8 @@ class FullNode:
|
||||
potential_fut_blocks = (self.store.get_potential_future_blocks()).copy()
|
||||
self.store.set_sync_mode(False)
|
||||
|
||||
await self.blockchain.recreate_diff_stores()
|
||||
|
||||
async with self.store.lock:
|
||||
await self.store.clear_sync_info()
|
||||
|
||||
@ -596,8 +598,14 @@ class FullNode:
|
||||
A peer notifies us of a new transaction.
|
||||
Requests a full transaction if we haven't seen it previously, and if the fees are enough.
|
||||
"""
|
||||
# Ignore if syncing
|
||||
if self.store.get_sync_mode():
|
||||
breakpoint()
|
||||
return
|
||||
# Ignore if already seen
|
||||
if self.mempool_manager.seen(transaction.transaction_id):
|
||||
return
|
||||
|
||||
elif self.mempool_manager.is_fee_enough(transaction.fees, transaction.cost):
|
||||
requestTX = full_node_protocol.RequestTransaction(
|
||||
transaction.transaction_id
|
||||
@ -613,6 +621,10 @@ class FullNode:
|
||||
self, request: full_node_protocol.RequestTransaction
|
||||
) -> OutboundMessageGenerator:
|
||||
""" Peer has requested a full transaction from us. """
|
||||
# Ignore if syncing
|
||||
if self.store.get_sync_mode():
|
||||
breakpoint()
|
||||
return
|
||||
spend_bundle = self.mempool_manager.get_spendbundle(request.transaction_id)
|
||||
if spend_bundle is None:
|
||||
reject = full_node_protocol.RejectTransactionRequest(request.transaction_id)
|
||||
@ -640,6 +652,10 @@ class FullNode:
|
||||
Receives a full transaction from peer.
|
||||
If tx is added to mempool, send tx_id to others. (new_transaction)
|
||||
"""
|
||||
# Ignore if syncing
|
||||
if self.store.get_sync_mode():
|
||||
breakpoint()
|
||||
return
|
||||
async with self.unspent_store.lock:
|
||||
cost, error = await self.mempool_manager.add_spendbundle(tx.transaction)
|
||||
if cost is not None:
|
||||
@ -1727,3 +1743,20 @@ class FullNode:
|
||||
Message("response_reject_additions", request),
|
||||
Delivery.BROADCAST,
|
||||
)
|
||||
|
||||
@api_request
|
||||
async def request_transaction_with_filter(
|
||||
self, request: src.protocols.full_node_protocol.ReceivedMempoolFilter
|
||||
):
|
||||
mempool_filter = PyBIP158(request.filter)
|
||||
transactions = await self.mempool_manager.get_items_not_in_filter(
|
||||
mempool_filter
|
||||
)
|
||||
|
||||
for tx in transactions:
|
||||
transaction = full_node_protocol.RespondTransaction(tx.spend_bundle)
|
||||
yield OutboundMessage(
|
||||
NodeType.FULL_NODE,
|
||||
Message("respond_transaction", transaction),
|
||||
Delivery.RESPOND,
|
||||
)
|
||||
|
@ -2,6 +2,8 @@ import collections
|
||||
from typing import Dict, Optional, Tuple, List, Set
|
||||
import logging
|
||||
|
||||
from chiabip158 import PyBIP158
|
||||
|
||||
from src.consensus.constants import constants as consensus_constants
|
||||
from src.util.bundle_tools import best_solution_program
|
||||
from src.types.full_block import FullBlock
|
||||
@ -35,8 +37,8 @@ class MempoolManager:
|
||||
|
||||
# Transactions that were unable to enter mempool, used for retry. (they were invalid)
|
||||
self.potential_txs: Dict[bytes32, SpendBundle] = {}
|
||||
# TODO limit the size of seen_bundle_hashes
|
||||
self.seen_bundle_hashes: Set[bytes32] = set()
|
||||
# Keep track of seen spend_bundles
|
||||
self.seen_bundle_hashes: Dict[bytes32, bytes32] = {}
|
||||
# Mempool for each tip
|
||||
self.mempools: Dict[bytes32, Mempool] = {}
|
||||
|
||||
@ -51,6 +53,7 @@ class MempoolManager:
|
||||
# MEMPOOL_SIZE = 60000
|
||||
self.mempool_size = tx_per_sec * sec_per_block * block_buffer_count
|
||||
self.potential_cache_size = 300
|
||||
self.seen_cache_size = 10000
|
||||
self.coinbase_freeze = self.constants["COINBASE_FREEZE_PERIOD"]
|
||||
|
||||
# TODO This is hack, it should use proper cost, const. Minimize work, double check/verify solution.
|
||||
@ -89,6 +92,11 @@ class MempoolManager:
|
||||
return True
|
||||
return False
|
||||
|
||||
def maybe_pop_seen(self):
|
||||
while len(self.seen_bundle_hashes) > self.seen_cache_size:
|
||||
first_in = list(self.seen_bundle_hashes.keys())[0]
|
||||
self.seen_bundle_hashes.pop(first_in)
|
||||
|
||||
async def add_spendbundle(
|
||||
self, new_spend: SpendBundle, to_pool: Mempool = None
|
||||
) -> Tuple[Optional[uint64], Optional[Err]]:
|
||||
@ -96,7 +104,8 @@ class MempoolManager:
|
||||
Tries to add spendbundle to either self.mempools or to_pool if it's specified.
|
||||
Returns true if it's added in any of pools, Returns error if it fails.
|
||||
"""
|
||||
self.seen_bundle_hashes.add(new_spend.name())
|
||||
self.seen_bundle_hashes[new_spend.name()] = new_spend.name()
|
||||
self.maybe_pop_seen()
|
||||
|
||||
# Calculate the cost and fees
|
||||
program = best_solution_program(new_spend)
|
||||
@ -282,7 +291,7 @@ class MempoolManager:
|
||||
|
||||
while len(self.potential_txs) > self.potential_cache_size:
|
||||
first_in = list(self.potential_txs.keys())[0]
|
||||
del self.potential_txs[first_in]
|
||||
self.potential_txs.pop(first_in)
|
||||
|
||||
def seen(self, bundle_hash: bytes32) -> bool:
|
||||
""" Return true if we saw this spendbundle before """
|
||||
@ -336,6 +345,39 @@ class MempoolManager:
|
||||
|
||||
self.mempools = new_pools
|
||||
|
||||
async def create_filter_for_pools(self) -> bytes:
|
||||
# Create filter for items in mempools
|
||||
byte_array_tx: List[bytes32] = []
|
||||
added_items: Set[bytes32] = set()
|
||||
for mempool in self.mempools:
|
||||
for key, item in mempool.spends.items():
|
||||
if key in added_items:
|
||||
continue
|
||||
added_items.add(key)
|
||||
byte_array_tx.append(bytearray(item.name()))
|
||||
|
||||
bip158: PyBIP158 = PyBIP158(byte_array_tx)
|
||||
encoded_filter = bytes(bip158.GetEncoded())
|
||||
|
||||
return encoded_filter
|
||||
|
||||
async def get_items_not_in_filter(
|
||||
self, mempool_filter: PyBIP158
|
||||
) -> List[MempoolItem]:
|
||||
items: List[MempoolItem] = []
|
||||
added_items: Set[bytes32] = set()
|
||||
|
||||
for mempool in self.mempools:
|
||||
for key, item in mempool.spends.items():
|
||||
if key in added_items:
|
||||
continue
|
||||
if mempool_filter.Match(key):
|
||||
continue
|
||||
added_items.add(key)
|
||||
items.append(item)
|
||||
|
||||
return items
|
||||
|
||||
async def update_pool(self, pool: Mempool, new_tip: FullBlock):
|
||||
"""
|
||||
Called when new tip extends the tip we had mempool for.
|
||||
|
@ -204,3 +204,8 @@ class RespondHeaderBlock:
|
||||
class RejectHeaderBlockRequest:
|
||||
height: uint32
|
||||
header_hash: bytes32
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ReceivedMempoolFilter:
|
||||
filter: bytes
|
||||
|
@ -49,7 +49,7 @@ class RpcWalletApiHandler:
|
||||
"""
|
||||
Returns a new puzzlehash
|
||||
"""
|
||||
puzzlehash = self.wallet_node.wallet.get_new_puzzlehash().hex()
|
||||
puzzlehash = (await self.wallet_node.wallet.get_new_puzzlehash()).hex()
|
||||
response = {
|
||||
"puzzlehash": puzzlehash,
|
||||
}
|
||||
|
@ -15,11 +15,17 @@ puzzle_prog_template = load_clvm("make_puzzle_m_of_n_direct.clvm")
|
||||
|
||||
|
||||
def puzzle_for_m_of_public_key_list(m, public_key_list):
|
||||
format_tuple = tuple([
|
||||
binutils.disassemble(Program.to(_))
|
||||
for _ in (puzzle_prog_template, m, public_key_list)
|
||||
])
|
||||
puzzle_src = "((c (q %s) (c (q %s) (c (q %s) (a)))))" % (format_tuple[0], format_tuple[1], format_tuple[2])
|
||||
format_tuple = tuple(
|
||||
[
|
||||
binutils.disassemble(Program.to(_))
|
||||
for _ in (puzzle_prog_template, m, public_key_list)
|
||||
]
|
||||
)
|
||||
puzzle_src = "((c (q %s) (c (q %s) (c (q %s) (a)))))" % (
|
||||
format_tuple[0],
|
||||
format_tuple[1],
|
||||
format_tuple[2],
|
||||
)
|
||||
puzzle_prog = binutils.assemble(puzzle_src)
|
||||
return Program.to(puzzle_prog)
|
||||
|
||||
|
13
src/wallet/util/wallet_types.py
Normal file
13
src/wallet/util/wallet_types.py
Normal file
@ -0,0 +1,13 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class WalletType(Enum):
|
||||
# Condition Costs
|
||||
STANDARD_WALLET = 0
|
||||
RATE_LIMITED = 1
|
||||
ATOMIC_SWAP = 2
|
||||
AUTHORIZED_PAYEE = 3
|
||||
MULTI_SIG = 4
|
||||
CUSTODY = 5
|
||||
COLORED_COIN = 6
|
||||
RECOVERABLE = 7
|
@ -25,6 +25,7 @@ from src.wallet.puzzles.puzzle_utils import (
|
||||
make_assert_coin_consumed_condition,
|
||||
make_create_coin_condition,
|
||||
)
|
||||
from src.wallet.util.wallet_types import WalletType
|
||||
|
||||
from src.wallet.wallet_state_manager import WalletStateManager
|
||||
|
||||
@ -34,8 +35,6 @@ class Wallet:
|
||||
key_config: Dict
|
||||
config: Dict
|
||||
server: Optional[ChiaServer]
|
||||
next_address: int = 0
|
||||
pubkey_num_lookup: Dict[bytes, int]
|
||||
wallet_state_manager: WalletStateManager
|
||||
|
||||
log: logging.Logger
|
||||
@ -64,17 +63,13 @@ class Wallet:
|
||||
self.log = logging.getLogger(__name__)
|
||||
|
||||
self.wallet_state_manager = wallet_state_manager
|
||||
self.pubkey_num_lookup = {}
|
||||
|
||||
self.server = None
|
||||
|
||||
return self
|
||||
|
||||
def get_next_public_key(self) -> PublicKey:
|
||||
pubkey = self.private_key.public_child(self.next_address).get_public_key()
|
||||
self.pubkey_num_lookup[pubkey.serialize()] = self.next_address
|
||||
self.next_address = self.next_address + 1
|
||||
self.wallet_state_manager.next_address = self.next_address
|
||||
def get_public_key(self, index) -> PublicKey:
|
||||
pubkey = self.private_key.public_child(index).get_public_key()
|
||||
return pubkey
|
||||
|
||||
async def get_confirmed_balance(self) -> uint64:
|
||||
@ -83,41 +78,28 @@ class Wallet:
|
||||
async def get_unconfirmed_balance(self) -> uint64:
|
||||
return await self.wallet_state_manager.get_unconfirmed_balance()
|
||||
|
||||
def can_generate_puzzle_hash(self, hash: bytes32) -> bool:
|
||||
return any(
|
||||
map(
|
||||
lambda child: hash
|
||||
== puzzle_for_pk(
|
||||
self.private_key.public_child(child).get_public_key().serialize()
|
||||
).get_hash(),
|
||||
reversed(range(self.next_address)),
|
||||
)
|
||||
)
|
||||
async def can_generate_puzzle_hash(self, hash: bytes32) -> bool:
|
||||
return await self.wallet_state_manager.tx_store.puzzle_hash_exists(hash)
|
||||
|
||||
def puzzle_for_pk(self, pubkey) -> Program:
|
||||
return puzzle_for_pk(pubkey)
|
||||
|
||||
def get_new_puzzle(self) -> Program:
|
||||
pubkey: bytes = self.get_next_public_key().serialize()
|
||||
puzzle: Program = puzzle_for_pk(pubkey)
|
||||
return puzzle
|
||||
|
||||
def get_new_puzzlehash(self) -> bytes32:
|
||||
puzzle: Program = self.get_new_puzzle()
|
||||
async def get_new_puzzlehash(self) -> bytes32:
|
||||
index = await self.wallet_state_manager.tx_store.get_max_derivation_path()
|
||||
index += 1
|
||||
pubkey: bytes = self.get_public_key(index).serialize()
|
||||
puzzle: Program = self.puzzle_for_pk(pubkey)
|
||||
puzzlehash: bytes32 = puzzle.get_hash()
|
||||
self.wallet_state_manager.puzzlehash_set.add(puzzlehash)
|
||||
|
||||
await self.wallet_state_manager.tx_store.add_derivation_path_of_interest(
|
||||
index, puzzlehash, pubkey, WalletType.STANDARD_WALLET
|
||||
)
|
||||
|
||||
return puzzlehash
|
||||
|
||||
def set_server(self, server: ChiaServer):
|
||||
self.server = server
|
||||
|
||||
def sign(self, value: bytes32, pubkey: PublicKey):
|
||||
private_key = self.private_key.private_child(
|
||||
self.pubkey_num_lookup[pubkey]
|
||||
).get_private_key()
|
||||
bls_key = BLSPrivateKey(private_key)
|
||||
return bls_key.sign(value)
|
||||
|
||||
def make_solution(self, primaries=None, min_time=0, me=None, consumed=None):
|
||||
ret = []
|
||||
if primaries:
|
||||
@ -134,12 +116,17 @@ class Wallet:
|
||||
ret.append(make_assert_my_coin_id_condition(me["id"]))
|
||||
return clvm.to_sexp_f([puzzle_for_conditions(ret), []])
|
||||
|
||||
def get_keys(self, hash: bytes32) -> Optional[Tuple[PublicKey, ExtendedPrivateKey]]:
|
||||
for child in range(self.next_address):
|
||||
pubkey = self.private_key.public_child(child).get_public_key()
|
||||
if hash == puzzle_for_pk(pubkey.serialize()).get_hash():
|
||||
return pubkey, self.private_key.private_child(child).get_private_key()
|
||||
return None
|
||||
async def get_keys(
|
||||
self, hash: bytes32
|
||||
) -> Optional[Tuple[PublicKey, ExtendedPrivateKey]]:
|
||||
index_for_puzzlehash = await self.wallet_state_manager.tx_store.index_for_puzzle_hash(
|
||||
hash
|
||||
)
|
||||
if index_for_puzzlehash == -1:
|
||||
raise
|
||||
pubkey = self.private_key.public_child(index_for_puzzlehash).get_public_key()
|
||||
private = self.private_key.private_child(index_for_puzzlehash).get_private_key()
|
||||
return pubkey, private
|
||||
|
||||
async def generate_unsigned_transaction(
|
||||
self, amount: int, newpuzzlehash: bytes32, fee: int = 0
|
||||
@ -153,7 +140,7 @@ class Wallet:
|
||||
change = spend_value - amount - fee
|
||||
for coin in utxos:
|
||||
puzzle_hash = coin.puzzle_hash
|
||||
maybe = self.get_keys(puzzle_hash)
|
||||
maybe = await self.get_keys(puzzle_hash)
|
||||
if not maybe:
|
||||
return []
|
||||
pubkey, secretkey = maybe
|
||||
@ -161,7 +148,7 @@ class Wallet:
|
||||
if output_created is False:
|
||||
primaries = [{"puzzlehash": newpuzzlehash, "amount": amount}]
|
||||
if change > 0:
|
||||
changepuzzlehash = self.get_new_puzzlehash()
|
||||
changepuzzlehash = await self.get_new_puzzlehash()
|
||||
primaries.append({"puzzlehash": changepuzzlehash, "amount": change})
|
||||
|
||||
solution = self.make_solution(primaries=primaries)
|
||||
@ -171,10 +158,10 @@ class Wallet:
|
||||
spends.append((puzzle, CoinSolution(coin, solution)))
|
||||
return spends
|
||||
|
||||
def sign_transaction(self, spends: List[Tuple[Program, CoinSolution]]):
|
||||
async def sign_transaction(self, spends: List[Tuple[Program, CoinSolution]]):
|
||||
sigs = []
|
||||
for puzzle, solution in spends:
|
||||
keys = self.get_keys(solution.coin.puzzle_hash)
|
||||
keys = await self.get_keys(solution.coin.puzzle_hash)
|
||||
if not keys:
|
||||
return None
|
||||
pubkey, secretkey = keys
|
||||
@ -208,7 +195,7 @@ class Wallet:
|
||||
)
|
||||
if len(transaction) == 0:
|
||||
return None
|
||||
return self.sign_transaction(transaction)
|
||||
return await self.sign_transaction(transaction)
|
||||
|
||||
async def push_transaction(self, spend_bundle: SpendBundle):
|
||||
""" Use this API to send transactions. """
|
||||
@ -219,7 +206,10 @@ class Wallet:
|
||||
if self.server:
|
||||
msg = OutboundMessage(
|
||||
NodeType.FULL_NODE,
|
||||
Message("respond_transaction", full_node_protocol.RespondTransaction(spend_bundle)),
|
||||
Message(
|
||||
"respond_transaction",
|
||||
full_node_protocol.RespondTransaction(spend_bundle),
|
||||
),
|
||||
Delivery.BROADCAST,
|
||||
)
|
||||
async for reply in self.server.push_message(msg):
|
||||
|
@ -114,11 +114,15 @@ class WalletNode:
|
||||
|
||||
additions: List[Coin] = []
|
||||
|
||||
if self.wallet.can_generate_puzzle_hash(response.header.data.coinbase.puzzle_hash):
|
||||
if await self.wallet.can_generate_puzzle_hash(
|
||||
response.header.data.coinbase.puzzle_hash
|
||||
):
|
||||
await self.wallet_state_manager.coin_added(
|
||||
response.header.data.coinbase, response.height, True
|
||||
)
|
||||
if self.wallet.can_generate_puzzle_hash(response.header.data.fees_coin.puzzle_hash):
|
||||
if await self.wallet.can_generate_puzzle_hash(
|
||||
response.header.data.fees_coin.puzzle_hash
|
||||
):
|
||||
await self.wallet_state_manager.coin_added(
|
||||
response.header.data.fees_coin, response.height, True
|
||||
)
|
||||
@ -132,13 +136,13 @@ class WalletNode:
|
||||
additions.extend(additions_for_npc(npc_list))
|
||||
|
||||
for added_coin in additions:
|
||||
if self.wallet.can_generate_puzzle_hash(added_coin.puzzle_hash):
|
||||
if await self.wallet.can_generate_puzzle_hash(added_coin.puzzle_hash):
|
||||
await self.wallet_state_manager.coin_added(
|
||||
added_coin, response.height, False
|
||||
)
|
||||
|
||||
for npc in npc_list:
|
||||
if self.wallet.can_generate_puzzle_hash(npc.puzzle_hash):
|
||||
if await self.wallet.can_generate_puzzle_hash(npc.puzzle_hash):
|
||||
await self.wallet_state_manager.coin_removed(
|
||||
npc.coin_name, response.height
|
||||
)
|
||||
|
@ -18,13 +18,11 @@ class WalletStateManager:
|
||||
tx_store: WalletTransactionStore
|
||||
header_hash: List[bytes32]
|
||||
start_index: int
|
||||
next_address: int
|
||||
|
||||
log: logging.Logger
|
||||
|
||||
# TODO Don't allow user to send tx until wallet is synced
|
||||
synced: bool
|
||||
puzzlehash_set: set
|
||||
|
||||
@staticmethod
|
||||
async def create(
|
||||
@ -46,9 +44,7 @@ class WalletStateManager:
|
||||
self.wallet_store = wallet_store
|
||||
self.tx_store = tx_store
|
||||
self.synced = False
|
||||
self.next_address = 0
|
||||
|
||||
self.puzzlehash_set = set()
|
||||
return self
|
||||
|
||||
async def get_confirmed_balance(self) -> uint64:
|
||||
@ -70,7 +66,7 @@ class WalletStateManager:
|
||||
|
||||
for record in unconfirmed_tx:
|
||||
for coin in record.additions:
|
||||
if coin.puzzle_hash in self.puzzlehash_set:
|
||||
if await self.tx_store.puzzle_hash_exists(coin.puzzle_hash):
|
||||
addition_amount += coin.amount
|
||||
for coin in record.removals:
|
||||
removal_amount += coin.amount
|
||||
|
@ -5,6 +5,7 @@ import aiosqlite
|
||||
from src.types.sized_bytes import bytes32
|
||||
from src.util.ints import uint32
|
||||
from src.wallet.transaction_record import TransactionRecord
|
||||
from src.wallet.util.wallet_types import WalletType
|
||||
|
||||
|
||||
class WalletTransactionStore:
|
||||
@ -60,6 +61,33 @@ class WalletTransactionStore:
|
||||
"CREATE INDEX IF NOT EXISTS tx_created_time on transaction_record(created_at_time)"
|
||||
)
|
||||
|
||||
await self.transaction_db.execute(
|
||||
(
|
||||
f"CREATE TABLE IF NOT EXISTS derivation_paths("
|
||||
f"id int PRIMARY KEY,"
|
||||
f" pubkey text,"
|
||||
f" puzzle_hash text,"
|
||||
f" wallet_type int,"
|
||||
f" used int)"
|
||||
)
|
||||
)
|
||||
|
||||
await self.transaction_db.execute(
|
||||
"CREATE INDEX IF NOT EXISTS ph on derivation_paths(puzzle_hash)"
|
||||
)
|
||||
|
||||
await self.transaction_db.execute(
|
||||
"CREATE INDEX IF NOT EXISTS pubkey on derivation_paths(pubkey)"
|
||||
)
|
||||
|
||||
await self.transaction_db.execute(
|
||||
"CREATE INDEX IF NOT EXISTS wallet_type on derivation_paths(wallet_type)"
|
||||
)
|
||||
|
||||
await self.transaction_db.execute(
|
||||
"CREATE INDEX IF NOT EXISTS used on derivation_paths(wallet_type)"
|
||||
)
|
||||
|
||||
await self.transaction_db.commit()
|
||||
# Lock
|
||||
self.lock = asyncio.Lock() # external
|
||||
@ -183,3 +211,62 @@ class WalletTransactionStore:
|
||||
records.append(record)
|
||||
|
||||
return records
|
||||
|
||||
async def add_derivation_path_of_interest(
|
||||
self, index: int, puzzlehash: bytes32, pubkey: bytes, wallet_type: WalletType
|
||||
):
|
||||
cursor = await self.transaction_db.execute(
|
||||
"INSERT OR REPLACE INTO derivation_paths VALUES(?, ?, ?, ?, ?)",
|
||||
(index, pubkey.hex(), puzzlehash.hex(), wallet_type.value, 0),
|
||||
)
|
||||
|
||||
await cursor.close()
|
||||
await self.transaction_db.commit()
|
||||
|
||||
async def puzzle_hash_exists(self, puzzle_hash: bytes32) -> bool:
|
||||
cursor = await self.transaction_db.execute(
|
||||
"SELECT * from derivation_paths WHERE puzzle_hash=?", (puzzle_hash.hex(),)
|
||||
)
|
||||
rows = await cursor.fetchall()
|
||||
await cursor.close()
|
||||
|
||||
if len(list(rows)) > 0:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
async def index_for_pubkey(self, pubkey: str) -> int:
|
||||
cursor = await self.transaction_db.execute(
|
||||
"SELECT * from derivation_paths WHERE pubkey=?", (pubkey,)
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
await cursor.close()
|
||||
|
||||
if row:
|
||||
return row[0]
|
||||
|
||||
return -1
|
||||
|
||||
async def index_for_puzzle_hash(self, pubkey: bytes32) -> int:
|
||||
cursor = await self.transaction_db.execute(
|
||||
"SELECT * from derivation_paths WHERE puzzle_hash=?", (pubkey.hex(),)
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
await cursor.close()
|
||||
|
||||
if row:
|
||||
return row[0]
|
||||
|
||||
return -1
|
||||
|
||||
async def get_max_derivation_path(self):
|
||||
cursor = await self.transaction_db.execute(
|
||||
"SELECT MAX(id) FROM derivation_paths;"
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
await cursor.close()
|
||||
|
||||
if row[0]:
|
||||
return row[0]
|
||||
|
||||
return 0
|
||||
|
@ -52,7 +52,9 @@ class TestFullSync:
|
||||
return
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
raise Exception("Took too long to process blocks")
|
||||
raise Exception(
|
||||
f"Took too long to process blocks, stopped at: {time.time() - start_unf}"
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_short_sync(self, two_nodes):
|
||||
|
@ -7,6 +7,7 @@ from src.server.outbound_message import OutboundMessage
|
||||
from src.protocols import full_node_protocol
|
||||
from src.types.condition_var_pair import ConditionVarPair
|
||||
from src.types.condition_opcodes import ConditionOpcode
|
||||
from src.types.hashable.spend_bundle import SpendBundle
|
||||
from src.util.ints import uint64
|
||||
from tests.setup_nodes import setup_two_nodes, test_constants, bt
|
||||
from tests.wallet_tools import WalletTool
|
||||
@ -31,7 +32,7 @@ class TestMempool:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_basic_mempool(self, two_nodes):
|
||||
num_blocks = 3
|
||||
num_blocks = 2
|
||||
wallet_a = WalletTool()
|
||||
coinbase_puzzlehash = wallet_a.get_new_puzzlehash()
|
||||
wallet_receiver = WalletTool()
|
||||
@ -51,7 +52,7 @@ class TestMempool:
|
||||
spend_bundle = wallet_a.generate_signed_transaction(
|
||||
1000, receiver_puzzlehash, block.header.data.coinbase
|
||||
)
|
||||
assert spend_bundle is not None
|
||||
|
||||
tx: full_node_protocol.RespondTransaction = full_node_protocol.RespondTransaction(
|
||||
spend_bundle
|
||||
)
|
||||
@ -65,7 +66,7 @@ class TestMempool:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_coinbase_freeze(self, two_nodes_standard_freeze):
|
||||
num_blocks = 3
|
||||
num_blocks = 2
|
||||
wallet_a = WalletTool()
|
||||
coinbase_puzzlehash = wallet_a.get_new_puzzlehash()
|
||||
wallet_receiver = WalletTool()
|
||||
@ -117,7 +118,7 @@ class TestMempool:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_double_spend(self, two_nodes):
|
||||
num_blocks = 3
|
||||
num_blocks = 2
|
||||
wallet_a = WalletTool()
|
||||
coinbase_puzzlehash = wallet_a.get_new_puzzlehash()
|
||||
wallet_receiver = WalletTool()
|
||||
@ -166,7 +167,7 @@ class TestMempool:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_double_spend_with_higher_fee(self, two_nodes):
|
||||
num_blocks = 3
|
||||
num_blocks = 2
|
||||
wallet_a = WalletTool()
|
||||
coinbase_puzzlehash = wallet_a.get_new_puzzlehash()
|
||||
wallet_receiver = WalletTool()
|
||||
@ -214,7 +215,7 @@ class TestMempool:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invalid_block_index(self, two_nodes):
|
||||
num_blocks = 3
|
||||
num_blocks = 2
|
||||
wallet_a = WalletTool()
|
||||
coinbase_puzzlehash = wallet_a.get_new_puzzlehash()
|
||||
wallet_receiver = WalletTool()
|
||||
@ -257,7 +258,7 @@ class TestMempool:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_correct_block_index(self, two_nodes):
|
||||
num_blocks = 3
|
||||
num_blocks = 2
|
||||
wallet_a = WalletTool()
|
||||
coinbase_puzzlehash = wallet_a.get_new_puzzlehash()
|
||||
wallet_receiver = WalletTool()
|
||||
@ -300,7 +301,7 @@ class TestMempool:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invalid_block_age(self, two_nodes):
|
||||
num_blocks = 3
|
||||
num_blocks = 2
|
||||
wallet_a = WalletTool()
|
||||
coinbase_puzzlehash = wallet_a.get_new_puzzlehash()
|
||||
wallet_receiver = WalletTool()
|
||||
@ -384,7 +385,7 @@ class TestMempool:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_correct_my_id(self, two_nodes):
|
||||
num_blocks = 4
|
||||
num_blocks = 2
|
||||
wallet_a = WalletTool()
|
||||
coinbase_puzzlehash = wallet_a.get_new_puzzlehash()
|
||||
wallet_receiver = WalletTool()
|
||||
@ -427,7 +428,7 @@ class TestMempool:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invalid_my_id(self, two_nodes):
|
||||
num_blocks = 4
|
||||
num_blocks = 2
|
||||
wallet_a = WalletTool()
|
||||
coinbase_puzzlehash = wallet_a.get_new_puzzlehash()
|
||||
wallet_receiver = WalletTool()
|
||||
@ -457,7 +458,6 @@ class TestMempool:
|
||||
1000, receiver_puzzlehash, block.header.data.coinbase, dic
|
||||
)
|
||||
|
||||
assert spend_bundle1 is not None
|
||||
tx1: full_node_protocol.RespondTransaction = full_node_protocol.RespondTransaction(
|
||||
spend_bundle1
|
||||
)
|
||||
@ -472,7 +472,7 @@ class TestMempool:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_assert_time_exceeds(self, two_nodes):
|
||||
num_blocks = 4
|
||||
num_blocks = 2
|
||||
wallet_a = WalletTool()
|
||||
coinbase_puzzlehash = wallet_a.get_new_puzzlehash()
|
||||
wallet_receiver = WalletTool()
|
||||
@ -502,7 +502,6 @@ class TestMempool:
|
||||
1000, receiver_puzzlehash, block.header.data.coinbase, dic
|
||||
)
|
||||
|
||||
assert spend_bundle1 is not None
|
||||
tx1: full_node_protocol.RespondTransaction = full_node_protocol.RespondTransaction(
|
||||
spend_bundle1
|
||||
)
|
||||
@ -517,7 +516,7 @@ class TestMempool:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_assert_time_exceeds_both_cases(self, two_nodes):
|
||||
num_blocks = 4
|
||||
num_blocks = 2
|
||||
wallet_a = WalletTool()
|
||||
coinbase_puzzlehash = wallet_a.get_new_puzzlehash()
|
||||
wallet_receiver = WalletTool()
|
||||
@ -558,9 +557,6 @@ class TestMempool:
|
||||
outbound: OutboundMessage = _
|
||||
assert outbound.message.function != "new_transaction"
|
||||
|
||||
sb1 = full_node_1.mempool_manager.get_spendbundle(spend_bundle1.name())
|
||||
|
||||
assert sb1 is None
|
||||
# Sleep so that 3 sec passes
|
||||
await asyncio.sleep(3)
|
||||
|
||||
@ -575,3 +571,101 @@ class TestMempool:
|
||||
sb1 = full_node_1.mempool_manager.get_spendbundle(spend_bundle1.name())
|
||||
|
||||
assert sb1 is spend_bundle1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_correct_coin_consumed(self, two_nodes):
|
||||
num_blocks = 2
|
||||
wallet_a = WalletTool()
|
||||
coinbase_puzzlehash = wallet_a.get_new_puzzlehash()
|
||||
wallet_receiver = WalletTool()
|
||||
receiver_puzzlehash = wallet_receiver.get_new_puzzlehash()
|
||||
|
||||
blocks = bt.get_consecutive_blocks(
|
||||
test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash
|
||||
)
|
||||
full_node_1, full_node_2, server_1, server_2 = two_nodes
|
||||
|
||||
block = blocks[1]
|
||||
block2 = blocks[2]
|
||||
|
||||
for b in blocks:
|
||||
async for _ in full_node_1.respond_block(
|
||||
full_node_protocol.RespondBlock(b)
|
||||
):
|
||||
pass
|
||||
|
||||
cvp = ConditionVarPair(
|
||||
ConditionOpcode.ASSERT_COIN_CONSUMED,
|
||||
block2.header.data.coinbase.name(),
|
||||
None,
|
||||
)
|
||||
dic = {cvp.opcode: [cvp]}
|
||||
|
||||
spend_bundle1 = wallet_a.generate_signed_transaction(
|
||||
1000, receiver_puzzlehash, block.header.data.coinbase, dic
|
||||
)
|
||||
|
||||
spend_bundle2 = wallet_a.generate_signed_transaction(
|
||||
1000, receiver_puzzlehash, block2.header.data.coinbase
|
||||
)
|
||||
|
||||
bundle = SpendBundle.aggregate([spend_bundle1, spend_bundle2])
|
||||
|
||||
tx1: full_node_protocol.RespondTransaction = full_node_protocol.RespondTransaction(
|
||||
bundle
|
||||
)
|
||||
async for _ in full_node_1.respond_transaction(tx1):
|
||||
outbound: OutboundMessage = _
|
||||
# Maybe transaction means that it's accepted in mempool
|
||||
assert outbound.message.function == "new_transaction"
|
||||
|
||||
mempool_bundle = full_node_1.mempool_manager.get_spendbundle(bundle.name())
|
||||
|
||||
assert mempool_bundle is bundle
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invalid_coin_consumed(self, two_nodes):
|
||||
num_blocks = 2
|
||||
wallet_a = WalletTool()
|
||||
coinbase_puzzlehash = wallet_a.get_new_puzzlehash()
|
||||
wallet_receiver = WalletTool()
|
||||
receiver_puzzlehash = wallet_receiver.get_new_puzzlehash()
|
||||
|
||||
blocks = bt.get_consecutive_blocks(
|
||||
test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash
|
||||
)
|
||||
full_node_1, full_node_2, server_1, server_2 = two_nodes
|
||||
|
||||
block = blocks[1]
|
||||
block2 = blocks[2]
|
||||
|
||||
for b in blocks:
|
||||
async for _ in full_node_1.respond_block(
|
||||
full_node_protocol.RespondBlock(b)
|
||||
):
|
||||
pass
|
||||
|
||||
cvp = ConditionVarPair(
|
||||
ConditionOpcode.ASSERT_COIN_CONSUMED,
|
||||
block2.header.data.coinbase.name(),
|
||||
None,
|
||||
)
|
||||
dic = {cvp.opcode: [cvp]}
|
||||
|
||||
spend_bundle1 = wallet_a.generate_signed_transaction(
|
||||
1000, receiver_puzzlehash, block.header.data.coinbase, dic
|
||||
)
|
||||
|
||||
tx1: full_node_protocol.RespondTransaction = full_node_protocol.RespondTransaction(
|
||||
spend_bundle1
|
||||
)
|
||||
async for _ in full_node_1.respond_transaction(tx1):
|
||||
outbound: OutboundMessage = _
|
||||
# Maybe transaction means that it's accepted in mempool
|
||||
assert outbound.message.function == "new_transaction"
|
||||
|
||||
mempool_bundle = full_node_1.mempool_manager.get_spendbundle(
|
||||
spend_bundle1.name()
|
||||
)
|
||||
|
||||
assert mempool_bundle is None
|
||||
|
@ -30,12 +30,9 @@ class TestFilter:
|
||||
await wallet_node.wallet_store._clear_database()
|
||||
|
||||
num_blocks = 2
|
||||
ph = await wallet.get_new_puzzlehash()
|
||||
blocks = bt.get_consecutive_blocks(
|
||||
test_constants,
|
||||
num_blocks,
|
||||
[],
|
||||
10,
|
||||
reward_puzzlehash=wallet.get_new_puzzlehash(),
|
||||
test_constants, num_blocks, [], 10, reward_puzzlehash=ph,
|
||||
)
|
||||
|
||||
for i in range(1, num_blocks):
|
||||
|
@ -31,12 +31,9 @@ class TestWallet:
|
||||
await wallet_node.tx_store._clear_database()
|
||||
|
||||
num_blocks = 10
|
||||
ph = await wallet.get_new_puzzlehash()
|
||||
blocks = bt.get_consecutive_blocks(
|
||||
test_constants,
|
||||
num_blocks,
|
||||
[],
|
||||
10,
|
||||
reward_puzzlehash=wallet.get_new_puzzlehash(),
|
||||
test_constants, num_blocks, [], 10, reward_puzzlehash=ph,
|
||||
)
|
||||
|
||||
for i in range(1, num_blocks):
|
||||
@ -68,12 +65,9 @@ class TestWallet:
|
||||
await wallet_node_b.tx_store._clear_database()
|
||||
|
||||
num_blocks = 10
|
||||
ph = await wallet.get_new_puzzlehash()
|
||||
blocks = bt.get_consecutive_blocks(
|
||||
test_constants,
|
||||
num_blocks,
|
||||
[],
|
||||
10,
|
||||
reward_puzzlehash=wallet.get_new_puzzlehash(),
|
||||
test_constants, num_blocks, [], 10, reward_puzzlehash=ph,
|
||||
)
|
||||
|
||||
for i in range(1, num_blocks):
|
||||
@ -85,7 +79,7 @@ class TestWallet:
|
||||
assert await wallet.get_confirmed_balance() == 144000000000000
|
||||
|
||||
spend_bundle = await wallet.generate_signed_transaction(
|
||||
10, wallet_b.get_new_puzzlehash(), 0
|
||||
10, await wallet_b.get_new_puzzlehash(), 0
|
||||
)
|
||||
await wallet.push_transaction(spend_bundle)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user