-added test for generation

-ported relative amount spendbundle creation for offers
-made get_innerpuz more internally consistent
This commit is contained in:
Matthew Howard 2020-04-14 16:02:25 +01:00 committed by Yostra
parent 1b1aa7e6ef
commit 38e61976f6
4 changed files with 477 additions and 3 deletions

View File

@ -36,7 +36,8 @@ class CCInfo(Streamable):
] # {coin.name(): (parent_coin_info, puzzle_hash, coin.amount)}
puzzle_cache: Optional[Dict] # {"innerpuz"+"core": puzzle}
my_colour_name: Optional[str]
derivation_todos: Optional[List]
# TODO: write tests based on wallet tests
# TODO: {Matt} compatibility based on deriving innerpuzzle from derivation record
# TODO: {Matt} convert this into wallet_state_manager.puzzle_store
# TODO: {Matt} add hooks in WebSocketServer for all UI functions
@ -66,7 +67,7 @@ class CCWallet:
self.wallet_state_manager = wallet_state_manager
self.cc_info = CCInfo(None, dict(), dict(), dict(), dict(), None, dict())
self.cc_info = CCInfo(None, dict(), dict(), dict(), dict(), None, [], dict())
info_as_string = json.dumps(self.cc_info.to_json_dict())
self.wallet_info = await wallet_state_manager.user_store.create_wallet(
"CC Wallet", WalletType.COLOURED_COIN, info_as_string
@ -106,6 +107,7 @@ class CCWallet:
async def set_core(self, core: str):
self.cc_info.my_core = core
self.update_derivation_todos()
async def coin_added(self, coin: Coin, height: int, header_hash: bytes32):
""" Notification from wallet state manager that wallet has been received. """
@ -137,7 +139,7 @@ class CCWallet:
}
data_str = dict_to_json_str(data)
await self.wallet_state_manager.create_action(
response = await self.wallet_state_manager.create_action(
self,
name="cc_get_generator",
wallet_id=self.wallet_info.id,
@ -146,12 +148,42 @@ class CCWallet:
done=False,
data=data_str,
)
# TODO: actually fetch parent information
async def generator_received(self, generator: Program, action_id: int):
""" Notification that wallet has received a generator it asked for. """
await self.wallet_state_manager.set_action_done(action_id)
# Note, if you do this before you have a colour assigned you will need to add the colour
async def get_new_innerpuzhash(self):
async with self.wallet_state_manager.puzzle_store.lock:
max = await self.wallet_state_manager.puzzle_store.get_last_derivation_path()
max_pk = self.standard_wallet.get_public_key(max)
innerpuzzlehash = self.standard_wallet.puzzle_for_pk(bytes(max_pk)).get_hash()
if self.cc_info.my_core is not None:
cc_puzzle = cc_wallet_puzzles.cc_make_puzzle(
innerpuzzlehash, self.cc_info.my_core
)
new_inner_puzzle_record = DerivationRecord(max, innerpuzzlehash.get_hash(), max_pk, WalletType.COLORED_COIN, self.wallet_info.id)
new_record = DerivationRecord(max, cc_puzzle.get_hash(), max_pk, WalletType.COLORED_COIN, self.wallet_info.id)
await self.wallet_state_manager.puzzle_store.add_derivation_paths([new_inner_puzzle_record, new_record])
else:
# If we don't have a colour yet, store this record to be updated later
self.cc_info.derivation_todos.append(max)
return innerpuzzlehash
async def update_derivation_todos(self):
for index in self.cc_info.derivation_todos:
pk = self.standard_wallet.get_public_key(index)
innerpuzzlehash = self.standard_wallet.puzzle_for_pk(bytes(pk)).get_hash()
cc_puzzle = cc_wallet_puzzles.cc_make_puzzle(
innerpuzzlehash, self.cc_info.my_core
)
new_inner_puzzle_record = DerivationRecord(index, innerpuzzlehash.get_hash(), pk, WalletType.COLORED_COIN, self.wallet_info.id)
new_record = DerivationRecord(index, cc_puzzle.get_hash(), pk, WalletType.COLORED_COIN, self.wallet_info.id)
await self.wallet_state_manager.puzzle_store.add_derivation_paths([new_inner_puzzle_record, new_record])
async def get_new_ccpuzzlehash(self):
async with self.wallet_state_manager.puzzle_store.lock:
max = await self.wallet_state_manager.puzzle_store.get_last_derivation_path()
@ -311,3 +343,59 @@ class CCWallet:
solution = Program(binutils.assemble("()"))
coinsol = CoinSolution(coin, clvm.to_sexp_f([puzzle, solution]))
return coinsol
# Create the spend bundle given a relative amount change (i.e -400 or 1000) and a colour
def create_spend_bundle_relative_core(self, cc_amount):
# Coloured Coin processing
# If we're losing value then get coloured coins with at least that much value
# If we're gaining value then our amount doesn't matter
if cc_amount < 0:
cc_spends = self.select_coins(abs(cc_amount))
else:
cc_spends = self.select_coins(1)
if cc_spends is None:
return None
# Calculate output amount given relative difference and sum of actual values
spend_value = sum([coin.amount for coin in cc_spends])
cc_amount = spend_value + cc_amount
# Loop through coins and create solution for innerpuzzle
list_of_solutions = []
output_created = None
sigs = []
for coin in cc_spends:
if output_created is None:
newinnerpuzhash = self.get_new_innerpuzhash()
innersol = self.make_solution(
primaries=[{"puzzlehash": newinnerpuzhash, "amount": cc_amount}]
)
output_created = coin
else:
innersol = self.make_solution(consumed=[output_created.name()])
if coin in self.my_coloured_coins:
innerpuz = self.my_coloured_coins[coin][0]
# Use coin info to create solution and add coin and solution to list of CoinSolutions
solution = self.cc_make_solution(
self.my_core,
self.parent_info[coin.parent_coin_info],
coin.amount,
binutils.disassemble(innerpuz),
binutils.disassemble(innersol),
None,
None,
)
list_of_solutions.append(
CoinSolution(
coin,
clvm.to_sexp_f(
[self.cc_make_puzzle(innerpuz.get_hash(), core), solution]
),
)
)
sigs = sigs + self.get_sigs_for_innerpuz_with_innersol(innerpuz, innersol)
aggsig = BLSSignature.aggregate(sigs)
return SpendBundle(list_of_solutions, aggsig)

View File

@ -458,3 +458,45 @@ class Wallet:
aggsig = BLSSignature.aggregate(sigs)
spend_bundle = SpendBundle(list_of_solutions, aggsig)
return spend_bundle
# Create an offer spend bundle for chia given an amount of relative change (i.e -400 or 1000)
# This is to be aggregated together with a coloured coin offer to ensure that the trade happens
async def create_spend_bundle_relative_chia(self, chia_amount: uint64):
list_of_solutions = []
utxos = None
# If we're losing value then get coins with at least that much value
# If we're gaining value then our amount doesn't matter
if chia_amount < 0:
utxos = self.select_coins(abs(chia_amount))
else:
utxos = [self.select_coins(1)]
if utxos is None:
return None
# Calculate output amount given sum of utxos
spend_value = sum([coin.amount for coin in utxos])
chia_amount = spend_value + chia_amount
# Create coin solutions for each utxo
output_created = None
sigs = []
for coin in utxos:
pubkey, secretkey = self.get_keys(coin.puzzle_hash)
puzzle = self.puzzle_for_pk(bytes(pubkey))
if output_created is None:
newpuzhash = self.get_new_puzzlehash()
primaries = [{"puzzlehash": newpuzhash, "amount": chia_amount}]
solution = self.make_solution(primaries=primaries)
output_created = coin
else:
solution = self.make_solution(consumed=[output_created.name()])
list_of_solutions.append(
CoinSolution(coin, clvm.to_sexp_f([puzzle, solution]))
)
sigs = sigs + self.get_sigs_for_innerpuz_with_innersol(puzzle, solution)
aggsig = BLSSignature.aggregate(sigs)
spend_bundle = SpendBundle(list_of_solutions, aggsig)
return spend_bundle

View File

View File

@ -0,0 +1,344 @@
import asyncio
from secrets import token_bytes
import pytest
from src.protocols import full_node_protocol
from src.simulator.simulator_protocol import FarmNewBlockProtocol, ReorgProtocol
from src.types.peer_info import PeerInfo
from src.util.ints import uint16, uint32
from tests.setup_nodes import setup_simulators_and_wallets
from src.consensus.block_rewards import calculate_base_fee, calculate_block_reward
from src.wallet.cc_wallet.cc_wallet import CCWallet
@pytest.fixture(scope="module")
def event_loop():
loop = asyncio.get_event_loop()
yield loop
class TestWalletSimulator:
@pytest.fixture(scope="function")
async def wallet_node(self):
async for _ in setup_simulators_and_wallets(1, 1, {}):
yield _
@pytest.fixture(scope="function")
async def two_wallet_nodes(self):
async for _ in setup_simulators_and_wallets(
1, 2, {"COINBASE_FREEZE_PERIOD": 0}
):
yield _
@pytest.fixture(scope="function")
async def two_wallet_nodes_five_freeze(self):
async for _ in setup_simulators_and_wallets(
1, 2, {"COINBASE_FREEZE_PERIOD": 5}
):
yield _
@pytest.fixture(scope="function")
async def three_sim_two_wallets(self):
async for _ in setup_simulators_and_wallets(
3, 2, {"COINBASE_FREEZE_PERIOD": 0}
):
yield _
@pytest.mark.asyncio
async def test_colour_creation(self, wallet_node):
num_blocks = 10
full_nodes, wallets = wallet_node
full_node_1, server_1 = full_nodes[0]
wallet_node, server_2 = wallets[0]
wallet = wallet_node.wallet_state_manager.main_wallet
cc_wallet: CCWallet = await CCWallet.create(
wallet_node.config, wallet_node.key_config, wallet_node.wallet_state_manager, wallet
)
wallet_node.wallet_state_manager.wallets[
cc_wallet.wallet_info.id
] = cc_wallet
ph = await wallet.get_new_puzzlehash()
await server_2.start_client(PeerInfo("localhost", uint16(server_1._port)), None)
for i in range(1, num_blocks):
await full_node_1.farm_new_block(FarmNewBlockProtocol(ph))
await asyncio.sleep(3)
funds = sum(
[
calculate_base_fee(uint32(i)) + calculate_block_reward(uint32(i))
for i in range(1, num_blocks - 2)
]
)
assert await wallet.get_confirmed_balance() == funds
innerpuzhash = cc_wallet.get_new_innerpuzhash()
spend_bundle, core = wallet.generate_new_coloured_coins(1000, innerpuzhash)
cc_wallet.set_core(core)
await wallet.push_transaction(spend_bundle)
await asyncio.sleep(2)
confirmed_balance = await cc_wallet.get_confirmed_balance()
unconfirmed_balance = await cc_wallet.get_unconfirmed_balance()
for i in range(0, num_blocks):
await full_node_1.farm_new_block(FarmNewBlockProtocol(ph))
await asyncio.sleep(2)
breakpoint()
@pytest.mark.asyncio
async def test_wallet_make_transaction(self, two_wallet_nodes):
num_blocks = 10
full_nodes, wallets = two_wallet_nodes
full_node_1, server_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("localhost", uint16(server_1._port)), None)
for i in range(0, num_blocks):
await full_node_1.farm_new_block(FarmNewBlockProtocol(ph))
funds = sum(
[
calculate_base_fee(uint32(i)) + calculate_block_reward(uint32(i))
for i in range(0, num_blocks - 2)
]
)
await asyncio.sleep(2)
assert await wallet.get_confirmed_balance() == funds
assert await wallet.get_unconfirmed_balance() == funds
spend_bundle = await wallet.generate_signed_transaction(
10,
await wallet_node_2.wallet_state_manager.main_wallet.get_new_puzzlehash(),
0,
)
await wallet.push_transaction(spend_bundle)
await asyncio.sleep(2)
confirmed_balance = await wallet.get_confirmed_balance()
unconfirmed_balance = await wallet.get_unconfirmed_balance()
assert confirmed_balance == funds
assert unconfirmed_balance == funds - 10
for i in range(0, num_blocks):
await full_node_1.farm_new_block(FarmNewBlockProtocol(ph))
await asyncio.sleep(2)
new_funds = sum(
[
calculate_base_fee(uint32(i)) + calculate_block_reward(uint32(i))
for i in range(0, (2 * num_blocks) - 2)
]
)
confirmed_balance = await wallet.get_confirmed_balance()
unconfirmed_balance = await wallet.get_unconfirmed_balance()
assert confirmed_balance == new_funds - 10
assert unconfirmed_balance == new_funds - 10
@pytest.mark.asyncio
async def test_wallet_coinbase_reorg(self, wallet_node):
num_blocks = 10
full_nodes, wallets = wallet_node
full_node_1, server_1 = full_nodes[0]
wallet_node, server_2 = wallets[0]
wallet = wallet_node.wallet_state_manager.main_wallet
ph = await wallet.get_new_puzzlehash()
await server_2.start_client(PeerInfo("localhost", uint16(server_1._port)), None)
for i in range(1, num_blocks):
await full_node_1.farm_new_block(FarmNewBlockProtocol(ph))
await asyncio.sleep(3)
funds = sum(
[
calculate_base_fee(uint32(i)) + calculate_block_reward(uint32(i))
for i in range(1, num_blocks - 2)
]
)
assert await wallet.get_confirmed_balance() == funds
await full_node_1.reorg_from_index_to_new_index(
ReorgProtocol(uint32(5), uint32(num_blocks + 3), token_bytes())
)
await asyncio.sleep(3)
funds = sum(
[
calculate_base_fee(uint32(i)) + calculate_block_reward(uint32(i))
for i in range(1, 5)
]
)
assert await wallet.get_confirmed_balance() == funds
@pytest.mark.asyncio
async def test_wallet_send_to_three_peers(self, three_sim_two_wallets):
num_blocks = 10
full_nodes, wallets = three_sim_two_wallets
wallet_0, wallet_server_0 = wallets[0]
full_node_0, server_0 = full_nodes[0]
full_node_1, server_1 = full_nodes[1]
full_node_2, server_2 = full_nodes[2]
ph = await wallet_0.wallet_state_manager.main_wallet.get_new_puzzlehash()
# wallet0 <-> sever0
await wallet_server_0.start_client(
PeerInfo("localhost", uint16(server_0._port)), None
)
for i in range(1, num_blocks):
await full_node_0.farm_new_block(FarmNewBlockProtocol(ph))
all_blocks = await full_node_0.get_current_blocks(full_node_0.get_tip())
for block in all_blocks:
async for _ in full_node_1.respond_block(
full_node_protocol.RespondBlock(block)
):
pass
async for _ in full_node_2.respond_block(
full_node_protocol.RespondBlock(block)
):
pass
await asyncio.sleep(2)
funds = sum(
[
calculate_base_fee(uint32(i)) + calculate_block_reward(uint32(i))
for i in range(1, num_blocks - 2)
]
)
assert (
await wallet_0.wallet_state_manager.main_wallet.get_confirmed_balance()
== funds
)
spend_bundle = await wallet_0.wallet_state_manager.main_wallet.generate_signed_transaction(
10, token_bytes(), 0
)
await wallet_0.wallet_state_manager.main_wallet.push_transaction(spend_bundle)
await asyncio.sleep(1)
bundle0 = full_node_0.mempool_manager.get_spendbundle(spend_bundle.name())
assert bundle0 is not None
# wallet0 <-> sever1
await wallet_server_0.start_client(
PeerInfo("localhost", uint16(server_1._port)), wallet_0._on_connect
)
await asyncio.sleep(1)
bundle1 = full_node_1.mempool_manager.get_spendbundle(spend_bundle.name())
assert bundle1 is not None
# wallet0 <-> sever2
await wallet_server_0.start_client(
PeerInfo("localhost", uint16(server_2._port)), wallet_0._on_connect
)
await asyncio.sleep(1)
bundle2 = full_node_2.mempool_manager.get_spendbundle(spend_bundle.name())
assert bundle2 is not None
@pytest.mark.asyncio
async def test_wallet_make_transaction_hop(self, two_wallet_nodes_five_freeze):
num_blocks = 10
full_nodes, wallets = two_wallet_nodes_five_freeze
full_node_0, full_node_server = full_nodes[0]
wallet_node_0, wallet_0_server = wallets[0]
wallet_node_1, wallet_1_server = wallets[1]
wallet_0 = wallet_node_0.wallet_state_manager.main_wallet
wallet_1 = wallet_node_1.wallet_state_manager.main_wallet
ph = await wallet_0.get_new_puzzlehash()
await wallet_0_server.start_client(
PeerInfo("localhost", uint16(full_node_server._port)), None
)
await wallet_1_server.start_client(
PeerInfo("localhost", uint16(full_node_server._port)), None
)
for i in range(0, num_blocks):
await full_node_0.farm_new_block(FarmNewBlockProtocol(ph))
funds = sum(
[
calculate_base_fee(uint32(i)) + calculate_block_reward(uint32(i))
for i in range(0, num_blocks - 2)
]
)
await asyncio.sleep(2)
assert await wallet_0.get_confirmed_balance() == funds
assert await wallet_0.get_unconfirmed_balance() == funds
spend_bundle = await wallet_0.generate_signed_transaction(
10,
await wallet_node_1.wallet_state_manager.main_wallet.get_new_puzzlehash(),
0,
)
await wallet_0.push_transaction(spend_bundle)
await asyncio.sleep(1)
# Full node height 11, wallet height 9
confirmed_balance = await wallet_0.get_confirmed_balance()
unconfirmed_balance = await wallet_0.get_unconfirmed_balance()
assert confirmed_balance == funds
assert unconfirmed_balance == funds - 10
for i in range(0, 7):
await full_node_0.farm_new_block(FarmNewBlockProtocol(token_bytes()))
await asyncio.sleep(1)
new_funds = sum(
[
calculate_base_fee(uint32(i)) + calculate_block_reward(uint32(i))
for i in range(0, num_blocks)
]
)
# Full node height 17, wallet height 15
confirmed_balance = await wallet_0.get_confirmed_balance()
unconfirmed_balance = await wallet_0.get_unconfirmed_balance()
wallet_2_confirmed_balance = await wallet_1.get_confirmed_balance()
assert confirmed_balance == new_funds - 10
assert unconfirmed_balance == new_funds - 10
assert wallet_2_confirmed_balance == 10
spend_bundle = await wallet_1.generate_signed_transaction(
5, await wallet_0.get_new_puzzlehash(), 0
)
await wallet_1.push_transaction(spend_bundle)
for i in range(0, 7):
await full_node_0.farm_new_block(FarmNewBlockProtocol(token_bytes()))
await asyncio.sleep(1)
confirmed_balance = await wallet_0.get_confirmed_balance()
unconfirmed_balance = await wallet_0.get_unconfirmed_balance()
wallet_2_confirmed_balance = await wallet_1.get_confirmed_balance()
assert confirmed_balance == new_funds - 5
assert unconfirmed_balance == new_funds - 5
assert wallet_2_confirmed_balance == 5