mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2024-09-11 18:58:59 +03:00
Remove rl wallet (#13429)
* remove all references to RL wallet * Missed one * Some new references from main * Fix is_transaction_confirmed * mypy * check for 'confirmed' * Try again to fix is_transaction_confirmed * remove from mypy.ini
This commit is contained in:
parent
b081c208e3
commit
2de55aff17
@ -110,7 +110,6 @@ extend_skip=
|
||||
chia/wallet/puzzles/puzzle_utils.py
|
||||
chia/wallet/puzzles/singleton_top_layer.py
|
||||
chia/wallet/puzzles/tails.py
|
||||
chia/wallet/rl_wallet/rl_wallet.py
|
||||
chia/wallet/sign_coin_spends.py
|
||||
chia/wallet/trade_manager.py
|
||||
chia/wallet/trade_record.py
|
||||
|
@ -686,7 +686,7 @@ async def cancel_offer(args: dict, wallet_client: WalletRpcClient, fingerprint:
|
||||
def wallet_coin_unit(typ: WalletType, address_prefix: str) -> Tuple[str, int]:
|
||||
if typ == WalletType.CAT:
|
||||
return "", units["cat"]
|
||||
if typ in [WalletType.STANDARD_WALLET, WalletType.POOLING_WALLET, WalletType.MULTI_SIG, WalletType.RATE_LIMITED]:
|
||||
if typ in [WalletType.STANDARD_WALLET, WalletType.POOLING_WALLET, WalletType.MULTI_SIG]:
|
||||
return address_prefix, units["chia"]
|
||||
return "", units["mojo"]
|
||||
|
||||
|
@ -48,7 +48,6 @@ from chia.wallet.nft_wallet.nft_wallet import NFTWallet
|
||||
from chia.wallet.nft_wallet.uncurry_nft import UncurriedNFT
|
||||
from chia.wallet.outer_puzzles import AssetType
|
||||
from chia.wallet.puzzle_drivers import PuzzleInfo, Solver
|
||||
from chia.wallet.rl_wallet.rl_wallet import RLWallet
|
||||
from chia.wallet.trade_record import TradeRecord
|
||||
from chia.wallet.trading.offer import Offer
|
||||
from chia.wallet.transaction_record import TransactionRecord
|
||||
@ -155,10 +154,6 @@ class WalletRpcApi:
|
||||
"/nft_transfer_nft": self.nft_transfer_nft,
|
||||
"/nft_add_uri": self.nft_add_uri,
|
||||
"/nft_calculate_royalties": self.nft_calculate_royalties,
|
||||
# RL wallet
|
||||
"/rl_set_user_info": self.rl_set_user_info,
|
||||
"/send_clawback_transaction:": self.send_clawback_transaction,
|
||||
"/add_rate_limited_funds:": self.add_rate_limited_funds,
|
||||
# Pool Wallet
|
||||
"/pw_join_pool": self.pw_join_pool,
|
||||
"/pw_self_pool": self.pw_self_pool,
|
||||
@ -511,41 +506,6 @@ class WalletRpcApi:
|
||||
else: # undefined mode
|
||||
pass
|
||||
|
||||
elif request["wallet_type"] == "rl_wallet":
|
||||
if request["rl_type"] == "admin":
|
||||
log.info("Create rl admin wallet")
|
||||
async with self.service.wallet_state_manager.lock:
|
||||
rl_admin: RLWallet = await RLWallet.create_rl_admin(wallet_state_manager)
|
||||
success = await rl_admin.admin_create_coin(
|
||||
uint64(int(request["interval"])),
|
||||
uint64(int(request["limit"])),
|
||||
request["pubkey"],
|
||||
uint64(int(request["amount"])),
|
||||
uint64(int(request["fee"])) if "fee" in request else uint64(0),
|
||||
)
|
||||
assert rl_admin.rl_info.admin_pubkey is not None
|
||||
return {
|
||||
"success": success,
|
||||
"id": rl_admin.id(),
|
||||
"type": rl_admin.type(),
|
||||
"origin": rl_admin.rl_info.rl_origin,
|
||||
"pubkey": rl_admin.rl_info.admin_pubkey.hex(),
|
||||
}
|
||||
|
||||
elif request["rl_type"] == "user":
|
||||
log.info("Create rl user wallet")
|
||||
async with self.service.wallet_state_manager.lock:
|
||||
rl_user: RLWallet = await RLWallet.create_rl_user(wallet_state_manager)
|
||||
assert rl_user.rl_info.user_pubkey is not None
|
||||
return {
|
||||
"id": rl_user.id(),
|
||||
"type": rl_user.type(),
|
||||
"pubkey": rl_user.rl_info.user_pubkey.hex(),
|
||||
}
|
||||
|
||||
else: # undefined rl_type
|
||||
pass
|
||||
|
||||
elif request["wallet_type"] == "did_wallet":
|
||||
if request["did_type"] == "new":
|
||||
backup_dids = []
|
||||
@ -1849,48 +1809,6 @@ class WalletRpcApi:
|
||||
{asset["asset"]: uint64(asset["amount"]) for asset in request.get("fungible_assets", [])},
|
||||
)
|
||||
|
||||
##########################################################################################
|
||||
# Rate Limited Wallet
|
||||
##########################################################################################
|
||||
|
||||
async def rl_set_user_info(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(int(request["wallet_id"]))
|
||||
rl_user = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
origin = request["origin"]
|
||||
async with self.service.wallet_state_manager.lock:
|
||||
await rl_user.set_user_info(
|
||||
uint64(request["interval"]),
|
||||
uint64(request["limit"]),
|
||||
origin["parent_coin_info"],
|
||||
origin["puzzle_hash"],
|
||||
origin["amount"],
|
||||
request["admin_pubkey"],
|
||||
)
|
||||
return {}
|
||||
|
||||
async def send_clawback_transaction(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
wallet: RLWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
|
||||
fee = int(request["fee"])
|
||||
async with self.service.wallet_state_manager.lock:
|
||||
tx = await wallet.clawback_rl_coin_transaction(fee)
|
||||
await wallet.push_transaction(tx)
|
||||
|
||||
# Transaction may not have been included in the mempool yet. Use get_transaction to check.
|
||||
return {
|
||||
"transaction": tx,
|
||||
"transaction_id": tx.name,
|
||||
}
|
||||
|
||||
async def add_rate_limited_funds(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
wallet: RLWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
puzzle_hash = wallet.rl_get_aggregation_puzzlehash(wallet.rl_info.rl_puzzle_hash)
|
||||
async with self.service.wallet_state_manager.lock:
|
||||
await wallet.rl_add_funds(request["amount"], puzzle_hash, request["fee"])
|
||||
return {"status": "SUCCESS"}
|
||||
|
||||
async def get_farmed_amount(self, request) -> EndpointResult:
|
||||
tx_records: List[TransactionRecord] = await self.service.wallet_state_manager.tx_store.get_farming_rewards()
|
||||
amount = 0
|
||||
|
@ -1,107 +0,0 @@
|
||||
(mod (pubkey
|
||||
rate_amount
|
||||
interval_time
|
||||
origin_id
|
||||
clawback_pubkey
|
||||
mode
|
||||
. args)
|
||||
|
||||
(defconstant RATE_LIMITED_MODE 1)
|
||||
(defconstant AGGREGATE_MODE 2)
|
||||
(defconstant CLAWBACK_MODE 3)
|
||||
|
||||
(include condition_codes.clvm)
|
||||
|
||||
(defun sha256tree (tree)
|
||||
(if (l tree)
|
||||
(sha256 2 (sha256tree (f tree)) (sha256tree (r tree)))
|
||||
(sha256 1 tree)))
|
||||
|
||||
(defun-inline aggsig-solution-with-key (key)
|
||||
(list AGG_SIG_UNSAFE key (sha256tree (c mode args)))
|
||||
)
|
||||
|
||||
(defmacro assert items
|
||||
(if (r items)
|
||||
(list if (f items) (c assert (r items)) (q (x)))
|
||||
(f items)
|
||||
)
|
||||
)
|
||||
|
||||
(defmacro or args
|
||||
(if args
|
||||
(qq (if (unquote (f args))
|
||||
1
|
||||
(unquote (c or (r args)))))
|
||||
0))
|
||||
|
||||
(defun create-lock (consolidating_primary_input consolidating_coin_puzzle_hash outgoing_amount)
|
||||
(list CREATE_COIN_ANNOUNCEMENT
|
||||
(sha256 consolidating_primary_input
|
||||
consolidating_coin_puzzle_hash
|
||||
outgoing_amount))
|
||||
)
|
||||
|
||||
(defun aggregation (origin_id
|
||||
(my_puzzle_hash
|
||||
consolidating_primary_input
|
||||
consolidating_coin_puzzle_hash
|
||||
outgoing_amount
|
||||
primary_input
|
||||
incoming_amount
|
||||
parent_amount
|
||||
my_parent_parent_id))
|
||||
(assert (or (= (sha256 my_parent_parent_id my_puzzle_hash parent_amount)
|
||||
primary_input)
|
||||
(= origin_id primary_input))
|
||||
(list (generate-assert-id-condition primary_input my_puzzle_hash incoming_amount)
|
||||
(create-lock consolidating_primary_input consolidating_coin_puzzle_hash outgoing_amount)
|
||||
(create-new-coin my_puzzle_hash (+ outgoing_amount incoming_amount)))))
|
||||
|
||||
(defun >= (a b)
|
||||
(or (> a b)
|
||||
(= a b)))
|
||||
|
||||
(defun generate-block-age-condition (min_block_time outgoing_amount rate_amount interval_time)
|
||||
(assert (>= (* min_block_time rate_amount)
|
||||
(* outgoing_amount interval_time))
|
||||
(list ASSERT_HEIGHT_RELATIVE min_block_time)))
|
||||
|
||||
(defun create-change (my_puzzlehash my_amount outgoing_amount fee)
|
||||
(list CREATE_COIN my_puzzlehash (- my_amount (+ outgoing_amount fee))))
|
||||
|
||||
(defun generate-assert-id-condition (my_parent_id my_puzzlehash my_amount)
|
||||
(list ASSERT_MY_COIN_ID (sha256 my_parent_id my_puzzlehash my_amount)))
|
||||
|
||||
(defun create-new-coin (outgoing_puzzle_hash outgoing_amount)
|
||||
(list CREATE_COIN outgoing_puzzle_hash outgoing_amount))
|
||||
|
||||
(defun rate-limited-puzzle
|
||||
(rate_amount
|
||||
interval_time
|
||||
origin_id
|
||||
(my_parent_id
|
||||
my_puzzlehash
|
||||
my_amount
|
||||
outgoing_puzzle_hash
|
||||
outgoing_amount
|
||||
min_block_time
|
||||
parent_parent_id
|
||||
parent_amount
|
||||
fee))
|
||||
(assert (or (= (sha256 parent_parent_id my_puzzlehash parent_amount)
|
||||
my_parent_id)
|
||||
(= origin_id my_parent_id))
|
||||
(list (generate-block-age-condition min_block_time outgoing_amount rate_amount interval_time)
|
||||
(create-change my_puzzlehash my_amount outgoing_amount fee)
|
||||
(generate-assert-id-condition my_parent_id my_puzzlehash my_amount)
|
||||
(create-new-coin outgoing_puzzle_hash outgoing_amount))))
|
||||
|
||||
(if (= mode CLAWBACK_MODE)
|
||||
(c (aggsig-solution-with-key clawback_pubkey)
|
||||
args)
|
||||
(c (aggsig-solution-with-key pubkey)
|
||||
(if (= mode RATE_LIMITED_MODE)
|
||||
(rate-limited-puzzle rate_amount interval_time origin_id args)
|
||||
(aggregation origin_id args))))
|
||||
)
|
@ -1 +0,0 @@
|
||||
ff02ffff01ff02ffff03ffff09ff81bfff2480ffff01ff04ffff04ff30ffff04ff5fffff04ffff02ff3effff04ff02ffff04ffff04ff81bfff81ff80ff80808080ff80808080ff81ff80ffff01ff04ffff04ff30ffff04ff05ffff04ffff02ff3effff04ff02ffff04ffff04ff81bfff81ff80ff80808080ff80808080ffff02ffff03ffff09ff81bfff3c80ffff01ff02ff2effff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff81ffff80808080808080ffff01ff02ff22ffff04ff02ffff04ff2fffff04ff81ffff808080808080ff01808080ff0180ffff04ffff01ffffffffff02ffff03ffff15ff05ff0b80ffff01ff0101ffff01ff02ffff03ffff09ff05ff0b80ffff01ff0101ff8080ff018080ff018031ff5246ffff0333ff3c01ffffffff02ffff03ffff02ffff03ffff09ffff0bff820bfbff13ff8205fb80ff82017b80ffff01ff0101ffff01ff02ffff03ffff09ff05ff82017b80ffff01ff0101ff8080ff018080ff0180ffff01ff04ffff02ff26ffff04ff02ffff04ff82017bffff04ff13ffff04ff8202fbff808080808080ffff04ffff02ff2affff04ff02ffff04ff2bffff04ff5bffff04ff81bbff808080808080ffff04ffff02ff3affff04ff02ffff04ff13ffff04ffff10ff81bbff8202fb80ff8080808080ff80808080ffff01ffff08808080ff0180ff04ff34ffff04ff05ffff04ffff11ff0bffff10ff17ff2f8080ff80808080ffff04ff2cffff04ffff0bff05ff0bff1780ff808080ff04ff34ffff04ff05ffff04ff0bff80808080ffffff04ff38ffff04ffff0bff05ff0bff1780ff808080ff02ffff03ffff02ff20ffff04ff02ffff04ffff12ff05ff1780ffff04ffff12ff0bff2f80ff8080808080ffff01ff04ff28ffff04ff05ff808080ffff01ffff08808080ff0180ffff02ffff03ffff02ffff03ffff09ffff0bff8217efff81afff822fef80ff4f80ffff01ff0101ffff01ff02ffff03ffff09ff17ff4f80ffff01ff0101ff8080ff018080ff0180ffff01ff04ffff02ff36ffff04ff02ffff04ff820befffff04ff8205efffff04ff05ffff04ff0bff80808080808080ffff04ffff02ff32ffff04ff02ffff04ff81afffff04ff82016fffff04ff8205efffff04ff825fefff80808080808080ffff04ffff02ff26ffff04ff02ffff04ff4fffff04ff81afffff04ff82016fff808080808080ffff04ffff02ff3affff04ff02ffff04ff8202efffff04ff8205efff8080808080ff8080808080ffff01ffff08808080ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
|
@ -1 +0,0 @@
|
||||
f663796a8c522b85bd9472cbea2bf7f138e8351e8e4032706fc0539e87f94faf
|
@ -1,26 +0,0 @@
|
||||
(mod (wallet_puzzle
|
||||
my-id
|
||||
wallet-coin-primary-input
|
||||
wallet-coin-amount)
|
||||
|
||||
(include condition_codes.clvm)
|
||||
|
||||
(defun sha256tree (tree)
|
||||
(if (l tree)
|
||||
(sha256 2 (sha256tree (f tree)) (sha256tree (r tree)))
|
||||
(sha256 1 tree)))
|
||||
|
||||
(defun-inline create-my-id-condition ()
|
||||
(list ASSERT_MY_COIN_ID my-id))
|
||||
|
||||
(include create-lock-puzzlehash.clvm)
|
||||
|
||||
(defun-inline parent-coin-id ()
|
||||
(sha256 wallet-coin-primary-input wallet_puzzle wallet-coin-amount))
|
||||
|
||||
(defun-inline input-of-lock ()
|
||||
(list ASSERT_COIN_ANNOUNCEMENT (sha256 (parent-coin-id) my-id)))
|
||||
|
||||
(list (create-my-id-condition)
|
||||
(input-of-lock))
|
||||
)
|
@ -1 +0,0 @@
|
||||
ff02ffff01ff04ffff04ff06ffff04ff0bff808080ffff04ffff04ff04ffff04ffff0bffff0bff17ff05ff2f80ff0b80ff808080ff808080ffff04ffff01ff3d46ff018080
|
@ -1 +0,0 @@
|
||||
007400187f63927ee023a7172bb88f14d49aaa4beb790ecaf7dde7c1a79b6481
|
@ -1,708 +0,0 @@
|
||||
# RLWallet is subclass of Wallet
|
||||
import json
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from secrets import token_bytes
|
||||
from typing import Any, List, Optional, Tuple
|
||||
|
||||
from blspy import AugSchemeMPL, G1Element, PrivateKey
|
||||
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.coin_spend import CoinSpend
|
||||
from chia.types.spend_bundle import SpendBundle
|
||||
from chia.util.byte_types import hexstr_to_bytes
|
||||
from chia.util.ints import uint8, uint32, uint64, uint128
|
||||
from chia.util.streamable import Streamable, streamable
|
||||
from chia.wallet.derivation_record import DerivationRecord
|
||||
from chia.wallet.derive_keys import master_sk_to_wallet_sk
|
||||
from chia.wallet.rl_wallet.rl_wallet_puzzles import (
|
||||
make_clawback_solution,
|
||||
rl_make_aggregation_puzzle,
|
||||
rl_make_aggregation_solution,
|
||||
rl_make_solution_mode_2,
|
||||
rl_puzzle_for_pk,
|
||||
solution_for_rl,
|
||||
)
|
||||
from chia.wallet.transaction_record import TransactionRecord
|
||||
from chia.wallet.util.transaction_type import TransactionType
|
||||
from chia.wallet.util.wallet_types import WalletType
|
||||
from chia.wallet.util.compute_memos import compute_memos
|
||||
from chia.wallet.wallet import Wallet
|
||||
from chia.wallet.wallet_coin_record import WalletCoinRecord
|
||||
from chia.wallet.wallet_info import WalletInfo
|
||||
|
||||
|
||||
@streamable
|
||||
@dataclass(frozen=True)
|
||||
class RLInfo(Streamable):
|
||||
type: str
|
||||
admin_pubkey: Optional[bytes]
|
||||
user_pubkey: Optional[bytes]
|
||||
limit: Optional[uint64]
|
||||
interval: Optional[uint64]
|
||||
rl_origin: Optional[Coin]
|
||||
rl_origin_id: Optional[bytes32]
|
||||
rl_puzzle_hash: Optional[bytes32]
|
||||
initialized: bool
|
||||
|
||||
|
||||
class RLWallet:
|
||||
wallet_state_manager: Any
|
||||
wallet_info: WalletInfo
|
||||
rl_coin_record: Optional[WalletCoinRecord]
|
||||
rl_info: RLInfo
|
||||
main_wallet: Wallet
|
||||
private_key: PrivateKey
|
||||
|
||||
@staticmethod
|
||||
async def create_rl_admin(
|
||||
wallet_state_manager: Any,
|
||||
):
|
||||
unused: Optional[uint32] = await wallet_state_manager.puzzle_store.get_unused_derivation_path()
|
||||
if unused is None:
|
||||
await wallet_state_manager.create_more_puzzle_hashes()
|
||||
unused = await wallet_state_manager.puzzle_store.get_unused_derivation_path()
|
||||
assert unused is not None
|
||||
|
||||
private_key = master_sk_to_wallet_sk(wallet_state_manager.private_key, unused)
|
||||
pubkey: G1Element = private_key.get_g1()
|
||||
|
||||
rl_info = RLInfo("admin", bytes(pubkey), None, None, None, None, None, None, False)
|
||||
info_as_string = json.dumps(rl_info.to_json_dict())
|
||||
wallet_info: WalletInfo = await wallet_state_manager.user_store.create_wallet(
|
||||
"RL Admin", WalletType.RATE_LIMITED, info_as_string
|
||||
)
|
||||
|
||||
await wallet_state_manager.puzzle_store.add_derivation_paths(
|
||||
[
|
||||
DerivationRecord(
|
||||
unused,
|
||||
bytes32(token_bytes(32)),
|
||||
pubkey,
|
||||
WalletType.RATE_LIMITED,
|
||||
wallet_info.id,
|
||||
False,
|
||||
)
|
||||
]
|
||||
)
|
||||
await wallet_state_manager.puzzle_store.set_used_up_to(unused)
|
||||
|
||||
self = await RLWallet.create(wallet_state_manager, wallet_info)
|
||||
await wallet_state_manager.add_new_wallet(self, self.id())
|
||||
return self
|
||||
|
||||
@staticmethod
|
||||
async def create_rl_user(
|
||||
wallet_state_manager: Any,
|
||||
):
|
||||
async with wallet_state_manager.puzzle_store.lock:
|
||||
unused: Optional[uint32] = await wallet_state_manager.puzzle_store.get_unused_derivation_path()
|
||||
if unused is None:
|
||||
await wallet_state_manager.create_more_puzzle_hashes()
|
||||
unused = await wallet_state_manager.puzzle_store.get_unused_derivation_path()
|
||||
assert unused is not None
|
||||
|
||||
private_key = wallet_state_manager.private_key
|
||||
|
||||
pubkey: G1Element = master_sk_to_wallet_sk(private_key, unused).get_g1()
|
||||
|
||||
rl_info = RLInfo("user", None, bytes(pubkey), None, None, None, None, None, False)
|
||||
info_as_string = json.dumps(rl_info.to_json_dict())
|
||||
wallet_info = await wallet_state_manager.user_store.create_wallet(
|
||||
"RL User",
|
||||
WalletType.RATE_LIMITED,
|
||||
info_as_string,
|
||||
)
|
||||
|
||||
self = await RLWallet.create(wallet_state_manager, wallet_info)
|
||||
|
||||
await wallet_state_manager.puzzle_store.add_derivation_paths(
|
||||
[
|
||||
DerivationRecord(
|
||||
unused, bytes32(token_bytes(32)), pubkey, WalletType.RATE_LIMITED, wallet_info.id, False
|
||||
)
|
||||
]
|
||||
)
|
||||
await wallet_state_manager.puzzle_store.set_used_up_to(unused)
|
||||
|
||||
await wallet_state_manager.add_new_wallet(self, self.id())
|
||||
return self
|
||||
|
||||
@staticmethod
|
||||
async def create(wallet_state_manager: Any, info: WalletInfo):
|
||||
self = RLWallet()
|
||||
|
||||
self.private_key = wallet_state_manager.private_key
|
||||
|
||||
self.wallet_state_manager = wallet_state_manager
|
||||
|
||||
self.wallet_info = info
|
||||
self.rl_info = RLInfo.from_json_dict(json.loads(info.data))
|
||||
self.main_wallet = wallet_state_manager.main_wallet
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def type(cls) -> uint8:
|
||||
return uint8(WalletType.RATE_LIMITED)
|
||||
|
||||
def id(self) -> uint32:
|
||||
return self.wallet_info.id
|
||||
|
||||
async def admin_create_coin(
|
||||
self,
|
||||
interval: uint64,
|
||||
limit: uint64,
|
||||
user_pubkey: str,
|
||||
amount: uint64,
|
||||
fee: uint64,
|
||||
) -> bool:
|
||||
coins = await self.wallet_state_manager.main_wallet.select_coins(amount)
|
||||
if coins is None:
|
||||
return False
|
||||
|
||||
origin = coins.copy().pop()
|
||||
origin_id = origin.name()
|
||||
|
||||
user_pubkey_bytes = hexstr_to_bytes(user_pubkey)
|
||||
|
||||
assert self.rl_info.admin_pubkey is not None
|
||||
|
||||
rl_puzzle = rl_puzzle_for_pk(
|
||||
pubkey=user_pubkey_bytes,
|
||||
rate_amount=limit,
|
||||
interval_time=interval,
|
||||
origin_id=origin_id,
|
||||
clawback_pk=self.rl_info.admin_pubkey,
|
||||
)
|
||||
|
||||
rl_puzzle_hash = rl_puzzle.get_tree_hash()
|
||||
index = await self.wallet_state_manager.puzzle_store.index_for_pubkey(
|
||||
G1Element.from_bytes(self.rl_info.admin_pubkey)
|
||||
)
|
||||
|
||||
assert index is not None
|
||||
record = DerivationRecord(
|
||||
index,
|
||||
rl_puzzle_hash,
|
||||
G1Element.from_bytes(self.rl_info.admin_pubkey),
|
||||
WalletType.RATE_LIMITED,
|
||||
self.id(),
|
||||
False,
|
||||
)
|
||||
await self.wallet_state_manager.puzzle_store.add_derivation_paths([record])
|
||||
|
||||
spend_bundle = await self.main_wallet.generate_signed_transaction(amount, rl_puzzle_hash, fee, origin_id, coins)
|
||||
if spend_bundle is None:
|
||||
return False
|
||||
|
||||
await self.main_wallet.push_transaction(spend_bundle)
|
||||
new_rl_info = RLInfo(
|
||||
"admin",
|
||||
self.rl_info.admin_pubkey,
|
||||
user_pubkey_bytes,
|
||||
limit,
|
||||
interval,
|
||||
origin,
|
||||
origin.name(),
|
||||
rl_puzzle_hash,
|
||||
True,
|
||||
)
|
||||
|
||||
data_str = json.dumps(new_rl_info.to_json_dict())
|
||||
new_wallet_info = WalletInfo(self.id(), self.wallet_info.name, self.type(), data_str)
|
||||
await self.wallet_state_manager.user_store.update_wallet(new_wallet_info)
|
||||
await self.wallet_state_manager.add_new_wallet(self, self.id())
|
||||
self.wallet_info = new_wallet_info
|
||||
self.rl_info = new_rl_info
|
||||
|
||||
return True
|
||||
|
||||
async def set_user_info(
|
||||
self,
|
||||
interval: uint64,
|
||||
limit: uint64,
|
||||
origin_parent_id: str,
|
||||
origin_puzzle_hash: str,
|
||||
origin_amount: uint64,
|
||||
admin_pubkey: str,
|
||||
) -> None:
|
||||
admin_pubkey_bytes = hexstr_to_bytes(admin_pubkey)
|
||||
|
||||
assert self.rl_info.user_pubkey is not None
|
||||
origin = Coin(
|
||||
bytes32(hexstr_to_bytes(origin_parent_id)),
|
||||
bytes32(hexstr_to_bytes(origin_puzzle_hash)),
|
||||
origin_amount,
|
||||
)
|
||||
rl_puzzle = rl_puzzle_for_pk(
|
||||
pubkey=self.rl_info.user_pubkey,
|
||||
rate_amount=limit,
|
||||
interval_time=interval,
|
||||
origin_id=origin.name(),
|
||||
clawback_pk=admin_pubkey_bytes,
|
||||
)
|
||||
|
||||
rl_puzzle_hash = rl_puzzle.get_tree_hash()
|
||||
|
||||
new_rl_info = RLInfo(
|
||||
"user",
|
||||
admin_pubkey_bytes,
|
||||
self.rl_info.user_pubkey,
|
||||
limit,
|
||||
interval,
|
||||
origin,
|
||||
origin.name(),
|
||||
rl_puzzle_hash,
|
||||
True,
|
||||
)
|
||||
rl_puzzle_hash = rl_puzzle.get_tree_hash()
|
||||
if await self.wallet_state_manager.puzzle_store.puzzle_hash_exists(rl_puzzle_hash):
|
||||
raise ValueError(
|
||||
"Cannot create multiple Rate Limited wallets under the same keys. This will change in a future release."
|
||||
)
|
||||
user_pubkey: G1Element = G1Element.from_bytes(self.rl_info.user_pubkey)
|
||||
index = await self.wallet_state_manager.puzzle_store.index_for_pubkey(user_pubkey)
|
||||
assert index is not None
|
||||
record = DerivationRecord(
|
||||
uint32(index),
|
||||
rl_puzzle_hash,
|
||||
user_pubkey,
|
||||
WalletType.RATE_LIMITED,
|
||||
self.id(),
|
||||
False,
|
||||
)
|
||||
|
||||
aggregation_puzzlehash = self.rl_get_aggregation_puzzlehash(new_rl_info.rl_puzzle_hash)
|
||||
record2 = DerivationRecord(
|
||||
uint32(index + 1),
|
||||
aggregation_puzzlehash,
|
||||
user_pubkey,
|
||||
WalletType.RATE_LIMITED,
|
||||
self.id(),
|
||||
False,
|
||||
)
|
||||
await self.wallet_state_manager.puzzle_store.add_derivation_paths([record, record2])
|
||||
self.wallet_state_manager.set_coin_with_puzzlehash_created_callback(
|
||||
aggregation_puzzlehash, self.aggregate_this_coin
|
||||
)
|
||||
|
||||
data_str = json.dumps(new_rl_info.to_json_dict())
|
||||
new_wallet_info = WalletInfo(self.id(), self.wallet_info.name, self.type(), data_str)
|
||||
await self.wallet_state_manager.user_store.update_wallet(new_wallet_info)
|
||||
await self.wallet_state_manager.add_new_wallet(self, self.id())
|
||||
self.wallet_info = new_wallet_info
|
||||
self.rl_info = new_rl_info
|
||||
|
||||
async def aggregate_this_coin(self, coin: Coin):
|
||||
spend_bundle = await self.rl_generate_signed_aggregation_transaction(
|
||||
self.rl_info, coin, await self._get_rl_parent(), await self._get_rl_coin()
|
||||
)
|
||||
|
||||
rl_coin = await self._get_rl_coin()
|
||||
puzzle_hash = rl_coin.puzzle_hash if rl_coin is not None else None
|
||||
assert puzzle_hash is not None
|
||||
tx_record = TransactionRecord(
|
||||
confirmed_at_height=uint32(0),
|
||||
created_at_time=uint64(int(time.time())),
|
||||
to_puzzle_hash=puzzle_hash,
|
||||
amount=uint64(0),
|
||||
fee_amount=uint64(0),
|
||||
confirmed=False,
|
||||
sent=uint32(0),
|
||||
spend_bundle=spend_bundle,
|
||||
additions=spend_bundle.additions(),
|
||||
removals=spend_bundle.removals(),
|
||||
wallet_id=self.id(),
|
||||
sent_to=[],
|
||||
trade_id=None,
|
||||
type=uint32(TransactionType.OUTGOING_TX.value),
|
||||
name=spend_bundle.name(),
|
||||
memos=list(compute_memos(spend_bundle).items()),
|
||||
)
|
||||
|
||||
await self.push_transaction(tx_record)
|
||||
|
||||
async def rl_available_balance(self) -> uint64:
|
||||
self.rl_coin_record = await self._get_rl_coin_record()
|
||||
if self.rl_coin_record is None:
|
||||
return uint64(0)
|
||||
peak = self.wallet_state_manager.blockchain.get_peak()
|
||||
height = peak.height if peak else 0
|
||||
assert self.rl_info.limit is not None
|
||||
if self.rl_info.interval is None:
|
||||
raise RuntimeError("rl_available_balance: rl_info.interval is undefined")
|
||||
unlocked = int(
|
||||
((height - self.rl_coin_record.confirmed_block_height) / self.rl_info.interval) * int(self.rl_info.limit)
|
||||
)
|
||||
total_amount = self.rl_coin_record.coin.amount
|
||||
available_amount = min(unlocked, total_amount)
|
||||
return uint64(available_amount)
|
||||
|
||||
async def get_confirmed_balance(self, unspent_records=None) -> uint128:
|
||||
return await self.wallet_state_manager.get_confirmed_balance_for_wallet(self.id(), unspent_records)
|
||||
|
||||
async def get_unconfirmed_balance(self, unspent_records=None) -> uint128:
|
||||
return await self.wallet_state_manager.get_unconfirmed_balance(self.id(), unspent_records)
|
||||
|
||||
async def get_spendable_balance(self, unspent_records=None) -> uint128:
|
||||
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
|
||||
|
||||
for record in unconfirmed_tx:
|
||||
if not record.is_in_mempool():
|
||||
continue
|
||||
our_spend = False
|
||||
for coin in record.removals:
|
||||
if await self.wallet_state_manager.does_coin_belong_to_wallet(coin, self.id()):
|
||||
our_spend = True
|
||||
break
|
||||
|
||||
if our_spend is not True:
|
||||
continue
|
||||
|
||||
for coin in record.additions:
|
||||
if await self.wallet_state_manager.does_coin_belong_to_wallet(coin, self.id()):
|
||||
addition_amount += coin.amount
|
||||
|
||||
return uint64(addition_amount)
|
||||
|
||||
def get_new_puzzle(self) -> Program:
|
||||
if (
|
||||
self.rl_info.limit is None
|
||||
or self.rl_info.interval is None
|
||||
or self.rl_info.user_pubkey is None
|
||||
or self.rl_info.admin_pubkey is None
|
||||
or self.rl_info.rl_origin_id is None
|
||||
):
|
||||
raise ValueError("One or more of the RL info fields is None")
|
||||
return rl_puzzle_for_pk(
|
||||
pubkey=self.rl_info.user_pubkey,
|
||||
rate_amount=self.rl_info.limit,
|
||||
interval_time=self.rl_info.interval,
|
||||
origin_id=self.rl_info.rl_origin_id,
|
||||
clawback_pk=self.rl_info.admin_pubkey,
|
||||
)
|
||||
|
||||
def get_new_puzzlehash(self) -> bytes32:
|
||||
return self.get_new_puzzle().get_tree_hash()
|
||||
|
||||
async def can_generate_rl_puzzle_hash(self, hash) -> bool:
|
||||
return await self.wallet_state_manager.puzzle_store.puzzle_hash_exists(hash)
|
||||
|
||||
def puzzle_for_pk(self, pk) -> Optional[Program]:
|
||||
if self.rl_info.initialized is False:
|
||||
return None
|
||||
if (
|
||||
self.rl_info.limit is None
|
||||
or self.rl_info.interval is None
|
||||
or self.rl_info.user_pubkey is None
|
||||
or self.rl_info.admin_pubkey is None
|
||||
or self.rl_info.rl_origin_id is None
|
||||
):
|
||||
return None
|
||||
return rl_puzzle_for_pk(
|
||||
pubkey=self.rl_info.user_pubkey,
|
||||
rate_amount=self.rl_info.limit,
|
||||
interval_time=self.rl_info.interval,
|
||||
origin_id=self.rl_info.rl_origin_id,
|
||||
clawback_pk=self.rl_info.admin_pubkey,
|
||||
)
|
||||
|
||||
def puzzle_hash_for_pk(self, pubkey: G1Element) -> Optional[bytes32]:
|
||||
# TODO: rewrite using curry_and_treehash
|
||||
puzzle = self.puzzle_for_pk(pubkey)
|
||||
if puzzle is None:
|
||||
return puzzle
|
||||
return puzzle.get_tree_hash()
|
||||
|
||||
async def get_keys(self, puzzle_hash: bytes32) -> Tuple[G1Element, PrivateKey]:
|
||||
"""
|
||||
Returns keys for puzzle_hash.
|
||||
"""
|
||||
index_for_puzzlehash = await self.wallet_state_manager.puzzle_store.index_for_puzzle_hash_and_wallet(
|
||||
puzzle_hash, self.id()
|
||||
)
|
||||
if index_for_puzzlehash is None:
|
||||
raise ValueError(f"index_for_puzzlehash is None ph {puzzle_hash}")
|
||||
private = master_sk_to_wallet_sk(self.private_key, index_for_puzzlehash)
|
||||
pubkey = private.get_g1()
|
||||
return pubkey, private
|
||||
|
||||
async def get_keys_pk(self, clawback_pubkey: bytes):
|
||||
"""
|
||||
Return keys for pubkey
|
||||
"""
|
||||
index_for_pubkey = await self.wallet_state_manager.puzzle_store.index_for_pubkey(
|
||||
G1Element.from_bytes(clawback_pubkey)
|
||||
)
|
||||
if index_for_pubkey is None:
|
||||
raise ValueError(f"index_for_pubkey is None pk {clawback_pubkey.hex()}")
|
||||
private = master_sk_to_wallet_sk(self.private_key, index_for_pubkey)
|
||||
pubkey = private.get_g1()
|
||||
|
||||
return pubkey, private
|
||||
|
||||
async def _get_rl_coin(self) -> Optional[Coin]:
|
||||
if self.rl_info.rl_puzzle_hash is None:
|
||||
return None
|
||||
rl_coins = await self.wallet_state_manager.coin_store.get_coin_records_by_puzzle_hash(
|
||||
self.rl_info.rl_puzzle_hash
|
||||
)
|
||||
for coin_record in rl_coins:
|
||||
if coin_record.spent is False:
|
||||
return coin_record.coin
|
||||
|
||||
return None
|
||||
|
||||
async def _get_rl_coin_record(self) -> Optional[WalletCoinRecord]:
|
||||
if self.rl_info.rl_puzzle_hash is None:
|
||||
return None
|
||||
rl_coins = await self.wallet_state_manager.coin_store.get_coin_records_by_puzzle_hash(
|
||||
self.rl_info.rl_puzzle_hash
|
||||
)
|
||||
for coin_record in rl_coins:
|
||||
if coin_record.spent is False:
|
||||
return coin_record
|
||||
|
||||
return None
|
||||
|
||||
async def _get_rl_parent(self) -> Optional[Coin]:
|
||||
self.rl_coin_record = await self._get_rl_coin_record()
|
||||
if not self.rl_coin_record:
|
||||
return None
|
||||
rl_parent_id = self.rl_coin_record.coin.parent_coin_info
|
||||
if rl_parent_id == self.rl_info.rl_origin_id:
|
||||
return self.rl_info.rl_origin
|
||||
rl_parent = await self.wallet_state_manager.coin_store.get_coin_record(rl_parent_id)
|
||||
if rl_parent is None:
|
||||
return None
|
||||
|
||||
return rl_parent.coin
|
||||
|
||||
async def rl_generate_unsigned_transaction(self, to_puzzlehash, amount, fee) -> List[CoinSpend]:
|
||||
spends = []
|
||||
assert self.rl_coin_record is not None
|
||||
coin = self.rl_coin_record.coin
|
||||
puzzle_hash = coin.puzzle_hash
|
||||
pubkey = self.rl_info.user_pubkey
|
||||
rl_parent: Optional[Coin] = await self._get_rl_parent()
|
||||
if rl_parent is None:
|
||||
raise ValueError("No RL parent coin")
|
||||
|
||||
# these lines make mypy happy
|
||||
|
||||
assert pubkey is not None
|
||||
assert self.rl_info.limit is not None
|
||||
assert self.rl_info.interval is not None
|
||||
assert self.rl_info.rl_origin_id is not None
|
||||
assert self.rl_info.admin_pubkey is not None
|
||||
|
||||
puzzle = rl_puzzle_for_pk(
|
||||
bytes(pubkey),
|
||||
self.rl_info.limit,
|
||||
self.rl_info.interval,
|
||||
self.rl_info.rl_origin_id,
|
||||
self.rl_info.admin_pubkey,
|
||||
)
|
||||
|
||||
solution = solution_for_rl(
|
||||
coin.parent_coin_info,
|
||||
puzzle_hash,
|
||||
uint64(coin.amount),
|
||||
to_puzzlehash,
|
||||
amount,
|
||||
rl_parent.parent_coin_info,
|
||||
uint64(rl_parent.amount),
|
||||
self.rl_info.interval,
|
||||
self.rl_info.limit,
|
||||
fee,
|
||||
)
|
||||
|
||||
spends.append(CoinSpend(coin, puzzle, solution))
|
||||
return spends
|
||||
|
||||
async def generate_signed_transaction(
|
||||
self, amount, to_puzzle_hash, fee: uint64 = uint64(0), memo: Optional[List[bytes]] = None
|
||||
) -> TransactionRecord:
|
||||
self.rl_coin_record = await self._get_rl_coin_record()
|
||||
if not self.rl_coin_record:
|
||||
raise ValueError("No unspent coin (zero balance)")
|
||||
if amount > self.rl_coin_record.coin.amount:
|
||||
raise ValueError(f"Coin value not sufficient: {amount} > {self.rl_coin_record.coin.amount}")
|
||||
transaction = await self.rl_generate_unsigned_transaction(to_puzzle_hash, amount, fee)
|
||||
spend_bundle = await self.rl_sign_transaction(transaction)
|
||||
|
||||
return TransactionRecord(
|
||||
confirmed_at_height=uint32(0),
|
||||
created_at_time=uint64(int(time.time())),
|
||||
to_puzzle_hash=to_puzzle_hash,
|
||||
amount=uint64(amount),
|
||||
fee_amount=uint64(0),
|
||||
confirmed=False,
|
||||
sent=uint32(0),
|
||||
spend_bundle=spend_bundle,
|
||||
additions=spend_bundle.additions(),
|
||||
removals=spend_bundle.removals(),
|
||||
wallet_id=self.id(),
|
||||
sent_to=[],
|
||||
trade_id=None,
|
||||
type=uint32(TransactionType.OUTGOING_TX.value),
|
||||
name=spend_bundle.name(),
|
||||
memos=list(compute_memos(spend_bundle).items()),
|
||||
)
|
||||
|
||||
async def rl_sign_transaction(self, spends: List[CoinSpend]) -> SpendBundle:
|
||||
sigs = []
|
||||
for coin_spend in spends:
|
||||
pubkey, secretkey = await self.get_keys(coin_spend.coin.puzzle_hash)
|
||||
signature = AugSchemeMPL.sign(secretkey, coin_spend.solution.get_tree_hash())
|
||||
sigs.append(signature)
|
||||
|
||||
aggsig = AugSchemeMPL.aggregate(sigs)
|
||||
|
||||
return SpendBundle(spends, aggsig)
|
||||
|
||||
def generate_unsigned_clawback_transaction(self, clawback_coin: Coin, clawback_puzzle_hash: bytes32, fee):
|
||||
if (
|
||||
self.rl_info.limit is None
|
||||
or self.rl_info.interval is None
|
||||
or self.rl_info.user_pubkey is None
|
||||
or self.rl_info.admin_pubkey is None
|
||||
):
|
||||
raise ValueError("One ore more of the elements of rl_info is None")
|
||||
spends = []
|
||||
coin = clawback_coin
|
||||
if self.rl_info.rl_origin is None:
|
||||
raise ValueError("Origin not initialized")
|
||||
puzzle = rl_puzzle_for_pk(
|
||||
self.rl_info.user_pubkey,
|
||||
self.rl_info.limit,
|
||||
self.rl_info.interval,
|
||||
self.rl_info.rl_origin.name(),
|
||||
self.rl_info.admin_pubkey,
|
||||
)
|
||||
solution = make_clawback_solution(clawback_puzzle_hash, clawback_coin.amount, fee)
|
||||
spends.append((puzzle, CoinSpend(coin, puzzle, solution)))
|
||||
return spends
|
||||
|
||||
async def sign_clawback_transaction(self, spends: List[Tuple[Program, CoinSpend]], clawback_pubkey) -> SpendBundle:
|
||||
sigs = []
|
||||
for puzzle, solution in spends:
|
||||
pubkey, secretkey = await self.get_keys_pk(clawback_pubkey)
|
||||
signature = AugSchemeMPL.sign(secretkey, solution.solution.get_tree_hash())
|
||||
sigs.append(signature)
|
||||
aggsig = AugSchemeMPL.aggregate(sigs)
|
||||
solution_list = []
|
||||
for puzzle, coin_spend in spends:
|
||||
solution_list.append(coin_spend)
|
||||
|
||||
return SpendBundle(solution_list, aggsig)
|
||||
|
||||
async def clawback_rl_coin(self, clawback_puzzle_hash: bytes32, fee) -> SpendBundle:
|
||||
rl_coin = await self._get_rl_coin()
|
||||
if rl_coin is None:
|
||||
raise ValueError("rl_coin is None")
|
||||
transaction = self.generate_unsigned_clawback_transaction(rl_coin, clawback_puzzle_hash, fee)
|
||||
return await self.sign_clawback_transaction(transaction, self.rl_info.admin_pubkey)
|
||||
|
||||
async def clawback_rl_coin_transaction(self, fee) -> TransactionRecord:
|
||||
to_puzzle_hash = await self.main_wallet.get_new_puzzlehash()
|
||||
spend_bundle = await self.clawback_rl_coin(to_puzzle_hash, fee)
|
||||
|
||||
return TransactionRecord(
|
||||
confirmed_at_height=uint32(0),
|
||||
created_at_time=uint64(int(time.time())),
|
||||
to_puzzle_hash=to_puzzle_hash,
|
||||
amount=uint64(0),
|
||||
fee_amount=fee,
|
||||
confirmed=False,
|
||||
sent=uint32(0),
|
||||
spend_bundle=spend_bundle,
|
||||
additions=spend_bundle.additions(),
|
||||
removals=spend_bundle.removals(),
|
||||
wallet_id=self.id(),
|
||||
sent_to=[],
|
||||
trade_id=None,
|
||||
type=uint32(TransactionType.OUTGOING_TX.value),
|
||||
name=spend_bundle.name(),
|
||||
memos=list(compute_memos(spend_bundle).items()),
|
||||
)
|
||||
|
||||
# This is for using the AC locked coin and aggregating it into wallet - must happen in same block as RL Mode 2
|
||||
async def rl_generate_signed_aggregation_transaction(self, rl_info, consolidating_coin, rl_parent, rl_coin):
|
||||
if (
|
||||
rl_info.limit is None
|
||||
or rl_info.interval is None
|
||||
or rl_info.user_pubkey is None
|
||||
or rl_info.admin_pubkey is None
|
||||
):
|
||||
raise ValueError("One or more of the elements of rl_info is None")
|
||||
if self.rl_coin_record is None:
|
||||
raise ValueError("Rl coin record is None")
|
||||
|
||||
list_of_coin_spends = []
|
||||
self.rl_coin_record = await self._get_rl_coin_record()
|
||||
pubkey, secretkey = await self.get_keys(self.rl_coin_record.coin.puzzle_hash)
|
||||
# Spend wallet coin
|
||||
puzzle = rl_puzzle_for_pk(
|
||||
rl_info.user_pubkey,
|
||||
rl_info.limit,
|
||||
rl_info.interval,
|
||||
rl_info.rl_origin_id,
|
||||
rl_info.admin_pubkey,
|
||||
)
|
||||
|
||||
solution = rl_make_solution_mode_2(
|
||||
rl_coin.puzzle_hash,
|
||||
consolidating_coin.parent_coin_info,
|
||||
consolidating_coin.puzzle_hash,
|
||||
consolidating_coin.amount,
|
||||
rl_coin.parent_coin_info,
|
||||
rl_coin.amount,
|
||||
rl_parent.amount,
|
||||
rl_parent.parent_coin_info,
|
||||
)
|
||||
signature = AugSchemeMPL.sign(secretkey, solution.get_tree_hash())
|
||||
rl_spend = CoinSpend(self.rl_coin_record.coin, puzzle, solution)
|
||||
|
||||
list_of_coin_spends.append(rl_spend)
|
||||
|
||||
# Spend consolidating coin
|
||||
puzzle = rl_make_aggregation_puzzle(self.rl_coin_record.coin.puzzle_hash)
|
||||
solution = rl_make_aggregation_solution(
|
||||
consolidating_coin.name(),
|
||||
self.rl_coin_record.coin.parent_coin_info,
|
||||
self.rl_coin_record.coin.amount,
|
||||
)
|
||||
agg_spend = CoinSpend(consolidating_coin, puzzle, solution)
|
||||
|
||||
list_of_coin_spends.append(agg_spend)
|
||||
aggsig = AugSchemeMPL.aggregate([signature])
|
||||
|
||||
return SpendBundle(list_of_coin_spends, aggsig)
|
||||
|
||||
def rl_get_aggregation_puzzlehash(self, wallet_puzzle):
|
||||
puzzle_hash = rl_make_aggregation_puzzle(wallet_puzzle).get_tree_hash()
|
||||
|
||||
return puzzle_hash
|
||||
|
||||
async def rl_add_funds(self, amount, puzzle_hash, fee):
|
||||
spend_bundle = await self.main_wallet.generate_signed_transaction(amount, puzzle_hash, fee)
|
||||
if spend_bundle is None:
|
||||
return False
|
||||
|
||||
await self.main_wallet.push_transaction(spend_bundle)
|
||||
|
||||
async def push_transaction(self, tx: TransactionRecord) -> None:
|
||||
"""Use this API to send transactions."""
|
||||
await self.wallet_state_manager.add_pending_transaction(tx)
|
@ -1,124 +0,0 @@
|
||||
import math
|
||||
from binascii import hexlify
|
||||
|
||||
from clvm_tools import binutils
|
||||
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.condition_opcodes import ConditionOpcode
|
||||
from chia.util.ints import uint64
|
||||
from chia.wallet.chialisp import sexp
|
||||
from chia.wallet.puzzles.load_clvm import load_clvm
|
||||
|
||||
RATE_LIMITED_MODE = 1
|
||||
AGGREGATION_MODE = 2
|
||||
CLAWBACK_MODE = 3
|
||||
|
||||
|
||||
def rl_puzzle_for_pk(
|
||||
pubkey: bytes,
|
||||
rate_amount: uint64,
|
||||
interval_time: uint64,
|
||||
origin_id: bytes32,
|
||||
clawback_pk: bytes,
|
||||
):
|
||||
"""
|
||||
Solution to this puzzle must be in format:
|
||||
(1 my_parent_id, my_puzzlehash, my_amount, outgoing_puzzle_hash, outgoing_amount,
|
||||
min_block_time, parent_parent_id, parent_amount, fee)
|
||||
RATE LIMIT LOGIC:
|
||||
M - chia_per_interval
|
||||
N - interval_blocks
|
||||
V - amount being spent
|
||||
MIN_BLOCK_AGE = V / (M / N)
|
||||
if not (min_block_age * M >= V * N) do X (raise)
|
||||
ASSERT_COIN_BLOCK_AGE_EXCEEDS min_block_age
|
||||
"""
|
||||
|
||||
MOD = load_clvm("rl.clvm")
|
||||
return MOD.curry(pubkey, rate_amount, interval_time, origin_id, clawback_pk)
|
||||
|
||||
|
||||
def rl_make_aggregation_solution(myid, wallet_coin_primary_input, wallet_coin_amount):
|
||||
opcode_myid = "0x" + hexlify(myid).decode("ascii")
|
||||
primary_input = "0x" + hexlify(wallet_coin_primary_input).decode("ascii")
|
||||
sol = sexp(opcode_myid, primary_input, wallet_coin_amount)
|
||||
return Program.to(binutils.assemble(sol))
|
||||
|
||||
|
||||
def make_clawback_solution(puzzlehash, amount, fee):
|
||||
opcode_create = hexlify(ConditionOpcode.CREATE_COIN).decode("ascii")
|
||||
solution = sexp(CLAWBACK_MODE, sexp("0x" + opcode_create, "0x" + str(puzzlehash), amount - fee))
|
||||
return Program.to(binutils.assemble(solution))
|
||||
|
||||
|
||||
def rl_make_solution_mode_2(
|
||||
my_puzzle_hash,
|
||||
consolidating_primary_input,
|
||||
consolidating_coin_puzzle_hash,
|
||||
outgoing_amount,
|
||||
my_primary_input,
|
||||
incoming_amount,
|
||||
parent_amount,
|
||||
my_parent_parent_id,
|
||||
):
|
||||
my_puzzle_hash = hexlify(my_puzzle_hash).decode("ascii")
|
||||
consolidating_primary_input = hexlify(consolidating_primary_input).decode("ascii")
|
||||
consolidating_coin_puzzle_hash = hexlify(consolidating_coin_puzzle_hash).decode("ascii")
|
||||
primary_input = hexlify(my_primary_input).decode("ascii")
|
||||
sol = sexp(
|
||||
AGGREGATION_MODE,
|
||||
"0x" + my_puzzle_hash,
|
||||
"0x" + consolidating_primary_input,
|
||||
"0x" + consolidating_coin_puzzle_hash,
|
||||
outgoing_amount,
|
||||
"0x" + primary_input,
|
||||
incoming_amount,
|
||||
parent_amount,
|
||||
"0x" + str(my_parent_parent_id),
|
||||
)
|
||||
return Program.to(binutils.assemble(sol))
|
||||
|
||||
|
||||
def solution_for_rl(
|
||||
my_parent_id: bytes32,
|
||||
my_puzzlehash: bytes32,
|
||||
my_amount: uint64,
|
||||
out_puzzlehash: bytes32,
|
||||
out_amount: uint64,
|
||||
my_parent_parent_id: bytes32,
|
||||
parent_amount: uint64,
|
||||
interval,
|
||||
limit,
|
||||
fee,
|
||||
):
|
||||
"""
|
||||
Solution is (1 my_parent_id, my_puzzlehash, my_amount, outgoing_puzzle_hash, outgoing_amount,
|
||||
min_block_time, parent_parent_id, parent_amount, fee)
|
||||
min block time = Math.ceil((new_amount * self.interval) / self.limit)
|
||||
"""
|
||||
|
||||
min_block_count = math.ceil((out_amount * interval) / limit)
|
||||
solution = sexp(
|
||||
RATE_LIMITED_MODE,
|
||||
"0x" + my_parent_id.hex(),
|
||||
"0x" + my_puzzlehash.hex(),
|
||||
my_amount,
|
||||
"0x" + out_puzzlehash.hex(),
|
||||
out_amount,
|
||||
min_block_count,
|
||||
"0x" + my_parent_parent_id.hex(),
|
||||
parent_amount,
|
||||
fee,
|
||||
)
|
||||
return Program.to(binutils.assemble(solution))
|
||||
|
||||
|
||||
def rl_make_aggregation_puzzle(wallet_puzzle):
|
||||
"""
|
||||
If Wallet A wants to send further funds to Wallet B then they can lock them up using this code
|
||||
Solution will be (my_id wallet_coin_primary_input wallet_coin_amount)
|
||||
"""
|
||||
|
||||
MOD = load_clvm("rl_aggregation.clvm")
|
||||
return MOD.curry(wallet_puzzle)
|
@ -10,7 +10,6 @@ from chia.util.ints import uint64
|
||||
class WalletType(IntEnum):
|
||||
# Wallet Types
|
||||
STANDARD_WALLET = 0
|
||||
RATE_LIMITED = 1
|
||||
ATOMIC_SWAP = 2
|
||||
AUTHORIZED_PAYEE = 3
|
||||
MULTI_SIG = 4
|
||||
|
@ -58,7 +58,6 @@ from chia.wallet.nft_wallet.uncurry_nft import UncurriedNFT
|
||||
from chia.wallet.outer_puzzles import AssetType
|
||||
from chia.wallet.puzzle_drivers import PuzzleInfo
|
||||
from chia.wallet.puzzles.cat_loader import CAT_MOD, CAT_MOD_HASH
|
||||
from chia.wallet.rl_wallet.rl_wallet import RLWallet
|
||||
from chia.wallet.settings.user_settings import UserSettings
|
||||
from chia.wallet.trade_manager import TradeManager
|
||||
from chia.wallet.transaction_record import TransactionRecord
|
||||
@ -227,8 +226,6 @@ class WalletStateManager:
|
||||
self.main_wallet,
|
||||
wallet_info,
|
||||
)
|
||||
elif wallet_info.type == WalletType.RATE_LIMITED:
|
||||
wallet = await RLWallet.create(self, wallet_info)
|
||||
elif wallet_info.type == WalletType.DECENTRALIZED_ID:
|
||||
wallet = await DIDWallet.create(
|
||||
self,
|
||||
|
1
setup.py
1
setup.py
@ -111,7 +111,6 @@ kwargs = dict(
|
||||
"chia.wallet",
|
||||
"chia.wallet.db_wallet",
|
||||
"chia.wallet.puzzles",
|
||||
"chia.wallet.rl_wallet",
|
||||
"chia.wallet.cat_wallet",
|
||||
"chia.wallet.did_wallet",
|
||||
"chia.wallet.nft_wallet",
|
||||
|
@ -27,11 +27,11 @@ from chia.util.byte_types import hexstr_to_bytes
|
||||
from chia.util.config import save_config
|
||||
from chia.util.ints import uint16, uint32
|
||||
from chia.wallet.trading.offer import Offer as TradingOffer
|
||||
from chia.wallet.transaction_record import TransactionRecord
|
||||
from chia.wallet.wallet import Wallet
|
||||
from chia.wallet.wallet_node import WalletNode
|
||||
from tests.setup_nodes import setup_simulators_and_wallets
|
||||
from tests.util.wallet_is_synced import wallet_is_synced
|
||||
from tests.wallet.rl_wallet.test_rl_rpc import is_transaction_confirmed
|
||||
|
||||
pytestmark = pytest.mark.data_layer
|
||||
nodes = Tuple[WalletNode, FullNodeSimulator]
|
||||
@ -148,6 +148,15 @@ async def farm_block_check_singelton(
|
||||
await time_out_assert(10, check_singleton_confirmed, True, data_layer, store_id)
|
||||
|
||||
|
||||
async def is_transaction_confirmed(user_wallet_id: uint32, api: WalletRpcApi, tx_id: bytes32) -> bool:
|
||||
try:
|
||||
val = await api.get_transaction({"wallet_id": user_wallet_id, "transaction_id": tx_id.hex()})
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
return True if TransactionRecord.from_json_dict_convenience(val["transaction"]).confirmed else False # mypy
|
||||
|
||||
|
||||
async def farm_block_with_spend(
|
||||
full_node_api: FullNodeSimulator, ph: bytes32, tx_rec: bytes32, wallet_rpc_api: WalletRpcApi
|
||||
) -> None:
|
||||
|
@ -1,174 +0,0 @@
|
||||
import asyncio
|
||||
|
||||
import pytest
|
||||
|
||||
from chia.rpc.wallet_rpc_api import WalletRpcApi
|
||||
from chia.simulator.simulator_protocol import FarmNewBlockProtocol
|
||||
from chia.simulator.time_out_assert import time_out_assert
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.mempool_inclusion_status import MempoolInclusionStatus
|
||||
from chia.types.peer_info import PeerInfo
|
||||
from chia.util.bech32m import encode_puzzle_hash
|
||||
from chia.util.ints import uint16
|
||||
from chia.wallet.transaction_record import TransactionRecord
|
||||
from chia.wallet.util.wallet_types import WalletType
|
||||
from tests.wallet.sync.test_wallet_sync import wallet_height_at_least
|
||||
|
||||
|
||||
async def is_transaction_in_mempool(user_wallet_id, api, tx_id: bytes32) -> bool:
|
||||
try:
|
||||
val = await api.get_transaction({"wallet_id": user_wallet_id, "transaction_id": tx_id.hex()})
|
||||
except ValueError:
|
||||
return False
|
||||
for _, mis, _ in TransactionRecord.from_json_dict_convenience(val["transaction"]).sent_to:
|
||||
if (
|
||||
MempoolInclusionStatus(mis) == MempoolInclusionStatus.SUCCESS
|
||||
or MempoolInclusionStatus(mis) == MempoolInclusionStatus.PENDING
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
async def is_transaction_confirmed(user_wallet_id, api, tx_id: bytes32) -> bool:
|
||||
try:
|
||||
val = await api.get_transaction({"wallet_id": user_wallet_id, "transaction_id": tx_id.hex()})
|
||||
except ValueError:
|
||||
return False
|
||||
return TransactionRecord.from_json_dict_convenience(val["transaction"]).confirmed
|
||||
|
||||
|
||||
async def check_balance(api, wallet_id):
|
||||
balance_response = await api.get_wallet_balance({"wallet_id": wallet_id})
|
||||
balance = balance_response["wallet_balance"]["confirmed_wallet_balance"]
|
||||
return balance
|
||||
|
||||
|
||||
class TestRLWallet:
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skip
|
||||
async def test_create_rl_coin(self, three_wallet_nodes, self_hostname):
|
||||
num_blocks = 4
|
||||
full_nodes, wallets, _ = three_wallet_nodes
|
||||
full_node_api = full_nodes[0]
|
||||
full_node_server = full_node_api.server
|
||||
wallet_node, server_2 = wallets[0]
|
||||
wallet_node_1, wallet_server_1 = wallets[1]
|
||||
wallet_node_2, wallet_server_2 = wallets[2]
|
||||
|
||||
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_server._port)), None)
|
||||
await wallet_server_1.start_client(PeerInfo(self_hostname, uint16(full_node_server._port)), None)
|
||||
await wallet_server_2.start_client(PeerInfo(self_hostname, uint16(full_node_server._port)), None)
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
|
||||
for i in range(0, num_blocks + 1):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"\0"))
|
||||
await time_out_assert(15, wallet_height_at_least, True, wallet_node, 6)
|
||||
fund_owners_initial_balance = await wallet.get_confirmed_balance()
|
||||
api_user = WalletRpcApi(wallet_node_1)
|
||||
val = await api_user.create_new_wallet(
|
||||
{"wallet_type": "rl_wallet", "rl_type": "user", "host": f"{self_hostname}:5000"}
|
||||
)
|
||||
await asyncio.sleep(2)
|
||||
assert isinstance(val, dict)
|
||||
if "success" in val:
|
||||
assert val["success"]
|
||||
assert val["id"]
|
||||
assert val["type"] == WalletType.RATE_LIMITED.value
|
||||
user_wallet_id = val["id"]
|
||||
pubkey = val["pubkey"]
|
||||
|
||||
api_admin = WalletRpcApi(wallet_node)
|
||||
val = await api_admin.create_new_wallet(
|
||||
{
|
||||
"wallet_type": "rl_wallet",
|
||||
"rl_type": "admin",
|
||||
"interval": 2,
|
||||
"limit": 10,
|
||||
"pubkey": pubkey,
|
||||
"amount": 100,
|
||||
"fee": 1,
|
||||
"host": f"{self_hostname}:5000",
|
||||
}
|
||||
)
|
||||
assert isinstance(val, dict)
|
||||
if "success" in val:
|
||||
assert val["success"]
|
||||
assert val["id"]
|
||||
assert val["type"] == WalletType.RATE_LIMITED.value
|
||||
assert val["origin"]
|
||||
assert val["pubkey"]
|
||||
admin_wallet_id = val["id"]
|
||||
admin_pubkey = val["pubkey"]
|
||||
origin: Coin = val["origin"]
|
||||
await asyncio.sleep(2)
|
||||
|
||||
await api_user.rl_set_user_info(
|
||||
{
|
||||
"wallet_id": user_wallet_id,
|
||||
"interval": 2,
|
||||
"limit": 10,
|
||||
"origin": {
|
||||
"parent_coin_info": origin.parent_coin_info.hex(),
|
||||
"puzzle_hash": origin.puzzle_hash.hex(),
|
||||
"amount": origin.amount,
|
||||
},
|
||||
"admin_pubkey": admin_pubkey,
|
||||
}
|
||||
)
|
||||
await asyncio.sleep(2)
|
||||
|
||||
assert (await api_user.get_wallet_balance({"wallet_id": user_wallet_id}))["wallet_balance"][
|
||||
"confirmed_wallet_balance"
|
||||
] == 0
|
||||
for i in range(0, 2 * num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"\0"))
|
||||
await time_out_assert(15, wallet_height_at_least, True, wallet_node, 14)
|
||||
assert await wallet.get_confirmed_balance() == fund_owners_initial_balance - 101
|
||||
assert await check_balance(api_user, user_wallet_id) == 100
|
||||
receiving_wallet = wallet_node_2.wallet_state_manager.main_wallet
|
||||
address = encode_puzzle_hash(await receiving_wallet.get_new_puzzlehash(), "xch")
|
||||
assert await receiving_wallet.get_spendable_balance() == 0
|
||||
val = await api_user.send_transaction({"wallet_id": user_wallet_id, "amount": 3, "fee": 2, "address": address})
|
||||
await asyncio.sleep(2)
|
||||
assert "transaction_id" in val
|
||||
await time_out_assert(15, is_transaction_in_mempool, True, user_wallet_id, api_user, val["transaction_id"])
|
||||
for i in range(0, num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"\0"))
|
||||
await time_out_assert(15, wallet_height_at_least, True, wallet_node, 18)
|
||||
assert await is_transaction_confirmed(user_wallet_id, api_user, val["transaction_id"])
|
||||
assert await check_balance(api_user, user_wallet_id) == 95
|
||||
assert await receiving_wallet.get_spendable_balance() == 3
|
||||
|
||||
val = await api_admin.add_rate_limited_funds({"wallet_id": admin_wallet_id, "amount": 100, "fee": 7})
|
||||
assert val["status"] == "SUCCESS"
|
||||
await asyncio.sleep(2)
|
||||
for i in range(0, 50):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"\0"))
|
||||
await time_out_assert(15, wallet_height_at_least, True, wallet_node, 68)
|
||||
assert await check_balance(api_user, user_wallet_id) == 195
|
||||
# test spending
|
||||
puzzle_hash = encode_puzzle_hash(await receiving_wallet.get_new_puzzlehash(), "xch")
|
||||
val = await api_user.send_transaction(
|
||||
{"wallet_id": user_wallet_id, "amount": 105, "fee": 0, "address": puzzle_hash}
|
||||
)
|
||||
await asyncio.sleep(2)
|
||||
await time_out_assert(15, is_transaction_in_mempool, True, user_wallet_id, api_user, val["transaction_id"])
|
||||
for i in range(0, num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"\0"))
|
||||
await time_out_assert(15, wallet_height_at_least, True, wallet_node, 72)
|
||||
assert await is_transaction_confirmed(user_wallet_id, api_user, val["transaction_id"])
|
||||
assert await check_balance(api_user, user_wallet_id) == 90
|
||||
assert await receiving_wallet.get_spendable_balance() == 108
|
||||
|
||||
val = await api_admin.send_clawback_transaction({"wallet_id": admin_wallet_id, "fee": 11})
|
||||
await asyncio.sleep(2)
|
||||
await time_out_assert(15, is_transaction_in_mempool, True, user_wallet_id, api_admin, val["transaction_id"])
|
||||
for i in range(0, num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"\0"))
|
||||
await time_out_assert(15, wallet_height_at_least, True, wallet_node, 76)
|
||||
assert await is_transaction_confirmed(user_wallet_id, api_admin, val["transaction_id"])
|
||||
assert await check_balance(api_user, user_wallet_id) == 0
|
||||
final_balance = await wallet.get_confirmed_balance()
|
||||
assert final_balance == fund_owners_initial_balance - 129
|
@ -1,72 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from chia.simulator.simulator_protocol import FarmNewBlockProtocol
|
||||
from chia.simulator.time_out_assert import time_out_assert
|
||||
from chia.types.peer_info import PeerInfo
|
||||
from chia.util.ints import uint16, uint64
|
||||
from chia.wallet.rl_wallet.rl_wallet import RLWallet
|
||||
|
||||
|
||||
class TestCATWallet:
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skip
|
||||
async def test_create_rl_coin(self, two_wallet_nodes, self_hostname):
|
||||
num_blocks = 4
|
||||
full_nodes, wallets, _ = two_wallet_nodes
|
||||
full_node_api = full_nodes[0]
|
||||
full_node_server = full_node_api.server
|
||||
wallet_node, server_2 = wallets[0]
|
||||
wallet_node_1, wallet_server_1 = 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_server._port)), None)
|
||||
await wallet_server_1.start_client(PeerInfo(self_hostname, uint16(full_node_server._port)), None)
|
||||
|
||||
for i in range(0, num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
|
||||
|
||||
rl_admin: RLWallet = await RLWallet.create_rl_admin(wallet_node.wallet_state_manager)
|
||||
|
||||
rl_user: RLWallet = await RLWallet.create_rl_user(wallet_node_1.wallet_state_manager)
|
||||
interval = uint64(2)
|
||||
limit = uint64(1)
|
||||
amount = uint64(100)
|
||||
await rl_admin.admin_create_coin(interval, limit, rl_user.rl_info.user_pubkey.hex(), amount, 0)
|
||||
origin = rl_admin.rl_info.rl_origin
|
||||
admin_pubkey = rl_admin.rl_info.admin_pubkey
|
||||
|
||||
await rl_user.set_user_info(
|
||||
interval,
|
||||
limit,
|
||||
origin.parent_coin_info.hex(),
|
||||
origin.puzzle_hash.hex(),
|
||||
origin.amount,
|
||||
admin_pubkey.hex(),
|
||||
)
|
||||
|
||||
for i in range(0, num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"\0"))
|
||||
|
||||
for i in range(0, num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"\0"))
|
||||
|
||||
await time_out_assert(15, rl_user.get_confirmed_balance, 100)
|
||||
balance = await rl_user.rl_available_balance()
|
||||
|
||||
tx_record = await rl_user.generate_signed_transaction(1, 32 * b"\0")
|
||||
|
||||
await wallet_node_1.wallet_state_manager.main_wallet.push_transaction(tx_record)
|
||||
|
||||
for i in range(0, num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"\0"))
|
||||
|
||||
balance = await rl_user.get_confirmed_balance()
|
||||
print(balance)
|
||||
|
||||
await time_out_assert(15, rl_user.get_confirmed_balance, 99)
|
||||
|
||||
rl_user.rl_get_aggregation_puzzlehash(rl_user.get_new_puzzle())
|
||||
# rl_admin.rl_generate_signed_aggregation_transaction()
|
@ -37,7 +37,7 @@ class TestPuzzleStore:
|
||||
uint32(i),
|
||||
token_bytes(32),
|
||||
AugSchemeMPL.key_gen(token_bytes(32)).get_g1(),
|
||||
WalletType.RATE_LIMITED,
|
||||
WalletType.CAT,
|
||||
uint32(2),
|
||||
False,
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user