update trade status when farmed

This commit is contained in:
Yostra
2020-07-16 13:05:43 -07:00
committed by Gene Hoffman
parent e4775fca14
commit 3f3e384f71
6 changed files with 264 additions and 126 deletions
+11 -2
View File
@@ -18,7 +18,6 @@ from src.cmds.init import check_keys
from src.server.outbound_message import NodeType, OutboundMessage, Message, Delivery
from src.simulator.simulator_protocol import FarmNewBlockProtocol
from src.util.ints import uint64, uint32
from src.wallet.trading.trade_status import TradeStatus
from src.wallet.util.wallet_types import WalletType
from src.wallet.rl_wallet.rl_wallet import RLWallet
from src.wallet.cc_wallet.cc_wallet import CCWallet
@@ -70,6 +69,11 @@ class WalletRpcApi:
}
async def get_trades(self, request: Dict):
if self.service is None:
return {"success": False}
if self.service.wallet_state_manager is None:
return {"success": False}
all = request["all"]
trade_mgr = self.service.wallet_state_manager.trade_manager
if all:
@@ -85,7 +89,12 @@ class WalletRpcApi:
response = {"success": True, "trades": result}
return response
async def cancel_trade(self, request:Dict):
async def cancel_trade(self, request: Dict):
if self.service is None:
return {"success": False}
if self.service.wallet_state_manager is None:
return {"success": False}
wsm = self.service.wallet_state_manager
secure = request["secure"]
trade_id = request["trade_id"]
+89 -7
View File
@@ -53,11 +53,12 @@ class TradeManager:
records = await self.trade_store.get_trade_record_with_status(status)
return records
async def get_coins_of_interest(self):
async def get_coins_of_interest(
self,
) -> Tuple[Dict[bytes32, Coin], Dict[bytes32, Coin]]:
"""
Returns list of coins we want to monitor blockchain for,
Those will be both coins belonging to us, and coins belonging to someone else that we tried to use in trade
This is important as we might not be the only one who tries to execute that trade.
Returns list of coins we want to check if they are included in filter,
These will include coins that belong to us and coins that that on other side of treade
"""
all_pending = []
pending_accept = await self.get_offers_with_status(TradeStatus.PENDING_ACCEPT)
@@ -66,13 +67,94 @@ class TradeManager:
all_pending.extend(pending_accept)
all_pending.extend(pending_confirm)
all_pending.extend(pending_cancel)
result = {}
removals = {}
additions = {}
for trade in all_pending:
for coin in trade.spend_bundle.removals():
result[coin.name()] = coin
removals[coin.name()] = coin
for coin in trade.spend_bundle.additions():
additions[coin.name()] = coin
return result
return removals, additions
async def get_trade_by_coin(self, coin: Coin) -> Optional[TradeRecord]:
all_trades = await self.get_all_trades()
for trade in all_trades:
if coin in trade.removals:
return trade
if coin in trade.additions:
return trade
return None
async def coins_of_interest_farmed(
self, removals: List[Coin], additions: List[Coin], index: uint32
):
"""
If both our coins and other coins in trade got removed that means that trade was successfully executed
If coins from other side of trade got farmed without ours, that means that trade failed because either someone
else completed trade or other side of trade canceled the trade by doing a spend.
If our coins got farmed but coins from other side didn't, we successfully canceled trade by spending inputs.
"""
removal_dict = {}
addition_dict = {}
checked: Dict[bytes32, Coin] = {}
for coin in removals:
removal_dict[coin.name()] = coin
for coin in additions:
addition_dict[coin.name()] = coin
all_coins = []
all_coins.extend(removals)
all_coins.extend(additions)
for coin in all_coins:
if coin.name() in checked:
continue
trade = await self.get_trade_by_coin(coin)
if trade is None:
self.log.error(f"Coin: {Coin}, not in any trade")
continue
# Check if all coins that are part of the trade got farmed
# If coin is missing, trade failed
failed = False
for coin in trade.removals:
if coin.name() not in removal_dict:
self.log.error(f"{coin} from trade not removed")
failed = True
checked[coin.name()] = coin
for coin in trade.additions:
if coin.name() not in addition_dict:
self.log.error(f"{coin} from trade not added")
failed = True
checked[coin.name()] = coin
if failed is False:
# Mark this trade as succesfull
await self.trade_store.set_status(
trade.trade_id, TradeStatus.CONFIRMED, index
)
self.log.info(
f"Trade with id: {trade.trade_id} confirmed at height: {index}"
)
else:
# Either we canceled this trade or this trade failed
status = TradeStatus(trade.status)
if status is TradeStatus.PENDING_CANCEL:
await self.trade_store.set_status(
trade.trade_id, TradeStatus.CANCELED
)
self.log.info(
f"Trade with id: {trade.trade_id} canceled at height: {index}"
)
else:
await self.trade_store.set_status(
trade.trade_id, TradeStatus.FAILED
)
self.log.warning(
f"Trade with id: {trade.trade_id} failed at height: {index}"
)
async def get_locked_coins(
self, wallet_id: int = None
+1 -1
View File
@@ -37,5 +37,5 @@ class TradeRecord(Streamable):
result["created_at_time"] = self.created_at_time
result["accepted_at_time"] = self.accepted_at_time
result["confirmed_at_index"] = self.confirmed_at_index
result["status"] = TradeStatus(self.status).name
result["status"] = TradeStatus(self.status).name
return result
+1
View File
@@ -7,3 +7,4 @@ class TradeStatus(Enum):
PENDING_CANCEL = 2
CANCELED = 3
CONFIRMED = 4
FAILED = 5
+88 -36
View File
@@ -446,14 +446,69 @@ class WalletStateManager:
removals[coin.name()] = coin
return removals
async def coin_removed(self, coin: Coin, index: uint32):
async def coins_of_interest_received(
self, removals: List[Coin], additions: List[Coin], height: uint32
):
trade_removals = await self.coins_of_interest_removed(removals, height)
trade_additions = await self.coins_of_interest_added(additions, height)
if len(trade_additions) > 0 or len(trade_removals) > 0:
await self.trade_manager.coins_of_interest_farmed(
trade_removals, trade_additions, height
)
async def coins_of_interest_added(
self, coins: List[Coin], height: uint32
) -> List[Coin]:
(
trade_removals,
trade_additions,
) = await self.trade_manager.get_coins_of_interest()
trade_adds: List[Coin] = []
for coin in coins:
if coin.name() in trade_additions:
trade_adds.append(coin)
is_coinbase = False
if (
bytes32((height).to_bytes(32, "big")) == coin.parent_coin_info
or std_hash(std_hash(height)) == coin.parent_coin_info
):
is_coinbase = True
info = await self.puzzle_store.wallet_info_for_puzzle_hash(coin.puzzle_hash)
if info is not None:
wallet_id, wallet_type = info
await self.coin_added(coin, height, is_coinbase, uint32(wallet_id), wallet_type)
return trade_adds
async def coins_of_interest_removed(
self, coins: List[Coin], height: uint32
) -> List[Coin]:
"This get's called when coins of our interest are spent on chain"
(
trade_removals,
trade_additions,
) = await self.trade_manager.get_coins_of_interest()
# Keep track of trade coins that are removed
trade_coin_removed: List[Coin] = []
for coin in coins:
record = await self.wallet_store.get_coin_record_by_coin_id(coin.name())
if coin.name() in trade_removals:
trade_coin_removed.append(coin)
if record is None:
continue
await self.coin_removed(coin, height, record.wallet_id)
return trade_coin_removed
async def coin_removed(self, coin: Coin, index: uint32, wallet_id: int):
"""
Called when coin gets spent
"""
# Only remove our coins
record = await self.wallet_store.get_coin_record_by_coin_id(coin.name())
if record is None:
return
await self.wallet_store.set_spent(coin.name(), index)
@@ -463,15 +518,19 @@ class WalletStateManager:
for unconfirmed in unconfirmed_record:
await self.tx_store.set_confirmed(unconfirmed.name(), index)
self.state_changed("coin_removed", record.wallet_id)
self.state_changed("coin_removed", wallet_id)
async def coin_added(self, coin: Coin, index: uint32, coinbase: bool):
async def coin_added(
self,
coin: Coin,
index: uint32,
coinbase: bool,
wallet_id: uint32,
wallet_type: WalletType,
):
"""
Adding coin to the db
Adding coin to DB
"""
info = await self.puzzle_store.wallet_info_for_puzzle_hash(coin.puzzle_hash)
assert info is not None
wallet_id, wallet_type = info
if coinbase:
now = uint64(int(time.time()))
tx_record = TransactionRecord(
@@ -671,17 +730,9 @@ class WalletStateManager:
assert block.height == 0
await self.wallet_store.add_block_to_path(block.header_hash)
self.lca = block.header_hash
for coin in block.additions:
is_coinbase = False
if (
bytes32((block.height).to_bytes(32, "big"))
== coin.parent_coin_info
or std_hash(std_hash(block.height)) == coin.parent_coin_info
):
is_coinbase = True
await self.coin_added(coin, block.height, is_coinbase)
for coin in block.removals:
await self.coin_removed(coin, block.height)
await self.coins_of_interest_received(
block.removals, block.additions, block.height
)
self.height_to_hash[uint32(0)] = block.header_hash
return ReceiveBlockResult.ADDED_TO_HEAD
@@ -712,20 +763,9 @@ class WalletStateManager:
path_block.additions is not None
and path_block.removals is not None
)
for coin in path_block.additions:
is_coinbase = False
if (
bytes32((path_block.height).to_bytes(32, "big"))
== coin.parent_coin_info
or std_hash(std_hash(path_block.height.to_bytes(4, "big")))
== coin.parent_coin_info
):
is_coinbase = True
await self.coin_added(coin, path_block.height, is_coinbase)
for coin in path_block.removals:
await self.coin_removed(coin, path_block.height)
await self.coins_of_interest_received(
path_block.removals, path_block.additions, path_block.height
)
self.lca = block.header_hash
self.state_changed("new_block")
return ReceiveBlockResult.ADDED_TO_HEAD
@@ -1162,6 +1202,18 @@ class WalletStateManager:
removals_of_interest: bytes32 = []
additions_of_interest: bytes32 = []
(
trade_removals,
trade_additions,
) = await self.trade_manager.get_coins_of_interest()
for name, trade_coin in trade_removals.items():
if tx_filter.Match(bytearray(trade_coin.name())):
removals_of_interest.append(trade_coin.name())
for name, trade_coin in trade_additions.items():
if tx_filter.Match(bytearray(trade_coin.puzzle_hash)):
additions_of_interest.append(trade_coin.puzzle_hash)
for coin_name in unspent_coin_names:
if tx_filter.Match(bytearray(coin_name)):
removals_of_interest.append(coin_name)