chia-blockchain/src/wallet/wallet.py

217 lines
8.0 KiB
Python
Raw Normal View History

2020-02-24 20:16:47 +03:00
from typing import Dict, Optional, List, Tuple
2020-02-12 04:01:39 +03:00
import clvm
from blspy import ExtendedPrivateKey, PublicKey
2020-02-12 00:00:41 +03:00
import logging
from src.server.outbound_message import OutboundMessage, NodeType, Message, Delivery
2020-02-12 04:01:39 +03:00
from src.server.server import ChiaServer
from src.protocols import full_node_protocol
2020-02-13 22:57:40 +03:00
from src.types.hashable.BLSSignature import BLSSignature
from src.types.hashable.coin_solution import CoinSolution
from src.types.hashable.program import Program
from src.types.hashable.spend_bundle import SpendBundle
2020-02-12 04:01:39 +03:00
from src.types.sized_bytes import bytes32
2020-02-13 22:57:40 +03:00
from src.util.condition_tools import (
conditions_for_solution,
conditions_by_opcode,
hash_key_pairs_for_conditions_dict,
)
2020-02-24 20:16:47 +03:00
from src.util.ints import uint64
2020-02-12 04:01:39 +03:00
from src.wallet.BLSPrivateKey import BLSPrivateKey
from src.wallet.puzzles.p2_conditions import puzzle_for_conditions
from src.wallet.puzzles.p2_delegated_puzzle import puzzle_for_pk
2020-02-13 22:57:40 +03:00
from src.wallet.puzzles.puzzle_utils import (
make_assert_my_coin_id_condition,
make_assert_time_exceeds_condition,
make_assert_coin_consumed_condition,
make_create_coin_condition,
)
from src.wallet.util.wallet_types import WalletType
2020-02-24 20:16:47 +03:00
from src.wallet.wallet_state_manager import WalletStateManager
2020-02-12 00:00:41 +03:00
class Wallet:
private_key: ExtendedPrivateKey
key_config: Dict
config: Dict
2020-02-14 04:31:21 +03:00
server: Optional[ChiaServer]
2020-02-24 20:16:47 +03:00
wallet_state_manager: WalletStateManager
2020-02-12 00:00:41 +03:00
2020-02-13 22:57:40 +03:00
log: logging.Logger
2020-02-13 23:59:03 +03:00
# TODO Don't allow user to send tx until wallet is synced
synced: bool
2020-02-14 02:35:49 +03:00
# Queue of SpendBundles that FullNode hasn't acked yet.
send_queue: Dict[bytes32, SpendBundle]
2020-02-13 22:57:40 +03:00
@staticmethod
2020-02-24 21:41:54 +03:00
async def create(
config: Dict,
key_config: Dict,
wallet_state_manager: WalletStateManager,
name: str = None,
):
2020-02-13 22:57:40 +03:00
self = Wallet()
2020-02-12 00:00:41 +03:00
self.config = config
self.key_config = key_config
sk_hex = self.key_config["wallet_sk"]
self.private_key = ExtendedPrivateKey.from_bytes(bytes.fromhex(sk_hex))
if name:
self.log = logging.getLogger(name)
else:
self.log = logging.getLogger(__name__)
2020-02-24 20:16:47 +03:00
self.wallet_state_manager = wallet_state_manager
2020-02-13 23:59:03 +03:00
2020-02-14 04:18:11 +03:00
self.server = None
2020-02-14 02:35:49 +03:00
2020-02-13 22:57:40 +03:00
return self
2020-02-12 04:01:39 +03:00
def get_public_key(self, index) -> PublicKey:
pubkey = self.private_key.public_child(index).get_public_key()
2020-02-12 04:01:39 +03:00
return pubkey
2020-02-13 23:59:03 +03:00
async def get_confirmed_balance(self) -> uint64:
2020-02-24 20:16:47 +03:00
return await self.wallet_state_manager.get_confirmed_balance()
2020-02-13 22:57:40 +03:00
2020-02-13 23:59:03 +03:00
async def get_unconfirmed_balance(self) -> uint64:
2020-02-24 20:16:47 +03:00
return await self.wallet_state_manager.get_unconfirmed_balance()
2020-02-13 23:59:03 +03:00
async def can_generate_puzzle_hash(self, hash: bytes32) -> bool:
2020-03-05 02:22:36 +03:00
return await self.wallet_state_manager.puzzle_store.puzzle_hash_exists(hash)
2020-02-12 04:01:39 +03:00
def puzzle_for_pk(self, pubkey) -> Program:
return puzzle_for_pk(pubkey)
async def get_new_puzzlehash(self) -> bytes32:
2020-03-05 02:22:36 +03:00
index = await self.wallet_state_manager.puzzle_store.get_max_derivation_path()
index += 1
pubkey: bytes = self.get_public_key(index).serialize()
puzzle: Program = self.puzzle_for_pk(pubkey)
2020-02-12 04:01:39 +03:00
puzzlehash: bytes32 = puzzle.get_hash()
2020-03-05 02:22:36 +03:00
await self.wallet_state_manager.puzzle_store.add_derivation_path_of_interest(
2020-02-27 23:39:59 +03:00
index, puzzlehash, pubkey, WalletType.STANDARD_WALLET
)
2020-02-12 04:01:39 +03:00
return puzzlehash
def set_server(self, server: ChiaServer):
self.server = server
2020-02-13 22:57:40 +03:00
def make_solution(self, primaries=None, min_time=0, me=None, consumed=None):
2020-02-12 04:01:39 +03:00
ret = []
2020-02-13 22:57:40 +03:00
if primaries:
for primary in primaries:
ret.append(
make_create_coin_condition(primary["puzzlehash"], primary["amount"])
)
if consumed:
for coin in consumed:
ret.append(make_assert_coin_consumed_condition(coin))
2020-02-12 04:01:39 +03:00
if min_time > 0:
ret.append(make_assert_time_exceeds_condition(min_time))
if me:
2020-02-13 22:57:40 +03:00
ret.append(make_assert_my_coin_id_condition(me["id"]))
2020-02-12 04:01:39 +03:00
return clvm.to_sexp_f([puzzle_for_conditions(ret), []])
2020-02-27 23:39:59 +03:00
async def get_keys(
self, hash: bytes32
) -> Optional[Tuple[PublicKey, ExtendedPrivateKey]]:
2020-03-05 02:22:36 +03:00
index_for_puzzlehash = await self.wallet_state_manager.puzzle_store.index_for_puzzle_hash(
2020-02-27 23:39:59 +03:00
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
2020-02-12 04:01:39 +03:00
2020-02-13 22:57:40 +03:00
async def generate_unsigned_transaction(
self, amount: int, newpuzzlehash: bytes32, fee: int = 0
) -> List[Tuple[Program, CoinSolution]]:
2020-02-24 20:16:47 +03:00
utxos = await self.wallet_state_manager.select_coins(amount + fee)
2020-02-13 22:57:40 +03:00
if utxos is None:
return []
2020-02-12 04:01:39 +03:00
spends: List[Tuple[Program, CoinSolution]] = []
output_created = False
spend_value = sum([coin.amount for coin in utxos])
change = spend_value - amount - fee
for coin in utxos:
puzzle_hash = coin.puzzle_hash
maybe = await self.get_keys(puzzle_hash)
2020-02-13 22:57:40 +03:00
if not maybe:
return []
pubkey, secretkey = maybe
2020-02-12 04:01:39 +03:00
puzzle: Program = puzzle_for_pk(pubkey.serialize())
if output_created is False:
2020-02-13 22:57:40 +03:00
primaries = [{"puzzlehash": newpuzzlehash, "amount": amount}]
2020-02-12 04:01:39 +03:00
if change > 0:
changepuzzlehash = await self.get_new_puzzlehash()
2020-02-13 22:57:40 +03:00
primaries.append({"puzzlehash": changepuzzlehash, "amount": change})
2020-02-24 20:16:47 +03:00
2020-02-12 04:01:39 +03:00
solution = self.make_solution(primaries=primaries)
output_created = True
else:
solution = self.make_solution(consumed=[coin.name()])
spends.append((puzzle, CoinSolution(coin, solution)))
return spends
async def sign_transaction(self, spends: List[Tuple[Program, CoinSolution]]):
2020-02-13 22:57:40 +03:00
sigs = []
for puzzle, solution in spends:
keys = await self.get_keys(solution.coin.puzzle_hash)
2020-02-13 22:57:40 +03:00
if not keys:
return None
pubkey, secretkey = keys
secretkey = BLSPrivateKey(secretkey)
code_ = [puzzle, solution.solution]
sexp = clvm.to_sexp_f(code_)
2020-02-27 04:33:25 +03:00
err, con, cost = conditions_for_solution(sexp)
2020-02-13 22:57:40 +03:00
if err or not con:
return None
conditions_dict = conditions_by_opcode(con)
for _ in hash_key_pairs_for_conditions_dict(conditions_dict):
signature = secretkey.sign(_.message_hash)
sigs.append(signature)
aggsig = BLSSignature.aggregate(sigs)
solution_list: List[CoinSolution] = [
CoinSolution(
coin_solution.coin, clvm.to_sexp_f([puzzle, coin_solution.solution])
)
for (puzzle, coin_solution) in spends
]
spend_bundle = SpendBundle(solution_list, aggsig)
return spend_bundle
async def generate_signed_transaction(
self, amount, newpuzzlehash, fee: int = 0
) -> Optional[SpendBundle]:
2020-02-25 04:19:50 +03:00
""" Use this to generate transaction. """
2020-02-13 22:57:40 +03:00
transaction = await self.generate_unsigned_transaction(
amount, newpuzzlehash, fee
)
if len(transaction) == 0:
return None
return await self.sign_transaction(transaction)
2020-02-13 22:57:40 +03:00
2020-02-14 02:35:49 +03:00
async def push_transaction(self, spend_bundle: SpendBundle):
2020-02-25 04:19:50 +03:00
""" Use this API to send transactions. """
2020-02-24 20:16:47 +03:00
await self.wallet_state_manager.add_pending_transaction(spend_bundle)
2020-02-14 02:35:49 +03:00
await self._send_transaction(spend_bundle)
async def _send_transaction(self, spend_bundle: SpendBundle):
2020-02-14 04:18:11 +03:00
if self.server:
2020-02-24 21:41:54 +03:00
msg = OutboundMessage(
NodeType.FULL_NODE,
2020-02-27 23:39:59 +03:00
Message(
"respond_transaction",
full_node_protocol.RespondTransaction(spend_bundle),
),
2020-02-24 21:41:54 +03:00
Delivery.BROADCAST,
)
2020-02-14 04:18:11 +03:00
async for reply in self.server.push_message(msg):
self.log.info(reply)