mirror of
https://github.com/basicswap/basicswap.git
synced 2025-11-06 02:38:11 +01:00
Make backwards compatible with smsg payload version 1
This commit is contained in:
@@ -2293,11 +2293,13 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
# coin_from_t, ensure_balance, proof_of_funds_hash
|
# coin_from_t, ensure_balance, proof_of_funds_hash
|
||||||
# )
|
# )
|
||||||
|
|
||||||
offer_bytes = msg_buf.to_bytes()
|
offer_bytes: bytes = msg_buf.to_bytes()
|
||||||
payload_hex = str.format("{:02x}", MessageTypes.OFFER) + offer_bytes.hex()
|
payload_hex: str = (
|
||||||
|
str.format("{:02x}", MessageTypes.OFFER) + offer_bytes.hex()
|
||||||
|
)
|
||||||
msg_valid: int = max(self.SMSG_SECONDS_IN_HOUR, valid_for_seconds)
|
msg_valid: int = max(self.SMSG_SECONDS_IN_HOUR, valid_for_seconds)
|
||||||
# Send offers to active and bridged networks
|
# Send offers to active and bridged networks
|
||||||
offer_id = self.sendMessage(
|
offer_id: bytes = self.sendMessage(
|
||||||
offer_addr, offer_addr_to, payload_hex, msg_valid, cursor
|
offer_addr, offer_addr_to, payload_hex, msg_valid, cursor
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -2337,7 +2339,6 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
auto_accept_type=msg_buf.auto_accept_type,
|
auto_accept_type=msg_buf.auto_accept_type,
|
||||||
message_nets=msg_buf.message_nets,
|
message_nets=msg_buf.message_nets,
|
||||||
)
|
)
|
||||||
|
|
||||||
offer.setState(OfferStates.OFFER_SENT)
|
offer.setState(OfferStates.OFFER_SENT)
|
||||||
|
|
||||||
if swap_type == SwapTypes.XMR_SWAP:
|
if swap_type == SwapTypes.XMR_SWAP:
|
||||||
@@ -2411,7 +2412,12 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
|
|
||||||
msg_valid: int = max(self.SMSG_SECONDS_IN_HOUR, offer.time_valid)
|
msg_valid: int = max(self.SMSG_SECONDS_IN_HOUR, offer.time_valid)
|
||||||
msg_id = self.sendMessage(
|
msg_id = self.sendMessage(
|
||||||
offer.addr_from, self.network_addr, payload_hex, msg_valid, cursor
|
offer.addr_from,
|
||||||
|
self.network_addr,
|
||||||
|
payload_hex,
|
||||||
|
msg_valid,
|
||||||
|
cursor,
|
||||||
|
payload_version=offer.smsg_payload_version,
|
||||||
)
|
)
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
f"Revoked offer {self.log.id(offer_id)} in msg {self.log.id(msg_id)}"
|
f"Revoked offer {self.log.id(offer_id)} in msg {self.log.id(msg_id)}"
|
||||||
@@ -3667,6 +3673,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
msg_valid,
|
msg_valid,
|
||||||
use_cursor,
|
use_cursor,
|
||||||
message_nets=bid.message_nets,
|
message_nets=bid.message_nets,
|
||||||
|
payload_version=bid.smsg_payload_version,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.addMessageLink(
|
self.addMessageLink(
|
||||||
@@ -3698,6 +3705,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
bid_msg_ids,
|
bid_msg_ids,
|
||||||
cursor,
|
cursor,
|
||||||
message_nets,
|
message_nets,
|
||||||
|
payload_version,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
dleag_split_size_init, dleag_split_size = xmr_swap.getMsgSplitInfo()
|
dleag_split_size_init, dleag_split_size = xmr_swap.getMsgSplitInfo()
|
||||||
@@ -3723,6 +3731,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
msg_valid,
|
msg_valid,
|
||||||
cursor,
|
cursor,
|
||||||
message_nets=message_nets,
|
message_nets=message_nets,
|
||||||
|
payload_version=payload_version,
|
||||||
)
|
)
|
||||||
num_sent += 1
|
num_sent += 1
|
||||||
sent_bytes += size_to_send
|
sent_bytes += size_to_send
|
||||||
@@ -3763,6 +3772,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
timestamp=bid.created_at,
|
timestamp=bid.created_at,
|
||||||
deterministic=(False if bid.bid_id is None else True),
|
deterministic=(False if bid.bid_id is None else True),
|
||||||
message_nets=bid.message_nets,
|
message_nets=bid.message_nets,
|
||||||
|
payload_version=offer.smsg_payload_version,
|
||||||
)
|
)
|
||||||
|
|
||||||
def getXmrBidMessage(self, bid, xmr_swap, offer) -> XmrBidMessage:
|
def getXmrBidMessage(self, bid, xmr_swap, offer) -> XmrBidMessage:
|
||||||
@@ -3815,6 +3825,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
timestamp=bid.created_at,
|
timestamp=bid.created_at,
|
||||||
deterministic=(False if bid.bid_id is None else True),
|
deterministic=(False if bid.bid_id is None else True),
|
||||||
message_nets=bid.message_nets,
|
message_nets=bid.message_nets,
|
||||||
|
payload_version=offer.smsg_payload_version,
|
||||||
)
|
)
|
||||||
bid_id = bid_msg_id
|
bid_id = bid_msg_id
|
||||||
if bid.bid_id and bid_msg_id != bid.bid_id:
|
if bid.bid_id and bid_msg_id != bid.bid_id:
|
||||||
@@ -3836,6 +3847,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
bid_msg_ids,
|
bid_msg_ids,
|
||||||
cursor,
|
cursor,
|
||||||
message_nets=bid.message_nets,
|
message_nets=bid.message_nets,
|
||||||
|
payload_version=offer.smsg_payload_version,
|
||||||
)
|
)
|
||||||
for k, msg_id in bid_msg_ids.items():
|
for k, msg_id in bid_msg_ids.items():
|
||||||
self.addMessageLink(
|
self.addMessageLink(
|
||||||
@@ -3895,6 +3907,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
timestamp=bid.created_at,
|
timestamp=bid.created_at,
|
||||||
deterministic=(False if bid.bid_id is None else True),
|
deterministic=(False if bid.bid_id is None else True),
|
||||||
message_nets=bid.message_nets,
|
message_nets=bid.message_nets,
|
||||||
|
payload_version=offer.smsg_payload_version,
|
||||||
)
|
)
|
||||||
if bid.bid_id and bid_msg_id != bid.bid_id:
|
if bid.bid_id and bid_msg_id != bid.bid_id:
|
||||||
self.log.warning(
|
self.log.warning(
|
||||||
@@ -4539,6 +4552,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
msg_valid,
|
msg_valid,
|
||||||
use_cursor,
|
use_cursor,
|
||||||
message_nets=bid.message_nets,
|
message_nets=bid.message_nets,
|
||||||
|
payload_version=offer.smsg_payload_version,
|
||||||
)
|
)
|
||||||
|
|
||||||
if ci_to.curve_type() == Curves.ed25519:
|
if ci_to.curve_type() == Curves.ed25519:
|
||||||
@@ -4552,6 +4566,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
bid_msg_ids,
|
bid_msg_ids,
|
||||||
use_cursor,
|
use_cursor,
|
||||||
bid.message_nets,
|
bid.message_nets,
|
||||||
|
payload_version=offer.smsg_payload_version,
|
||||||
)
|
)
|
||||||
|
|
||||||
bid.setState(BidStates.BID_ACCEPTED) # ADS
|
bid.setState(BidStates.BID_ACCEPTED) # ADS
|
||||||
@@ -4694,6 +4709,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
msg_valid,
|
msg_valid,
|
||||||
use_cursor,
|
use_cursor,
|
||||||
message_nets=bid.message_nets,
|
message_nets=bid.message_nets,
|
||||||
|
payload_version=offer.smsg_payload_version,
|
||||||
)
|
)
|
||||||
|
|
||||||
if ci_to.curve_type() == Curves.ed25519:
|
if ci_to.curve_type() == Curves.ed25519:
|
||||||
@@ -4707,6 +4723,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
bid_msg_ids,
|
bid_msg_ids,
|
||||||
use_cursor,
|
use_cursor,
|
||||||
message_nets=bid.message_nets,
|
message_nets=bid.message_nets,
|
||||||
|
payload_version=offer.smsg_payload_version,
|
||||||
)
|
)
|
||||||
|
|
||||||
bid.setState(BidStates.BID_REQUEST_ACCEPTED)
|
bid.setState(BidStates.BID_REQUEST_ACCEPTED)
|
||||||
@@ -7529,6 +7546,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
def processOffer(self, msg) -> None:
|
def processOffer(self, msg) -> None:
|
||||||
offer_bytes = self.getSmsgMsgBytes(msg)
|
offer_bytes = self.getSmsgMsgBytes(msg)
|
||||||
|
|
||||||
|
msg_payload_version = self.getSmsgMsgPayloadVersion(msg)
|
||||||
offer_data = OfferMessage(init_all=False)
|
offer_data = OfferMessage(init_all=False)
|
||||||
try:
|
try:
|
||||||
offer_data.from_bytes(offer_bytes[:2], init_all=False)
|
offer_data.from_bytes(offer_bytes[:2], init_all=False)
|
||||||
@@ -7679,6 +7697,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
else None
|
else None
|
||||||
),
|
),
|
||||||
message_nets=offer_data.message_nets,
|
message_nets=offer_data.message_nets,
|
||||||
|
smsg_payload_version=msg_payload_version,
|
||||||
)
|
)
|
||||||
offer.setState(OfferStates.OFFER_RECEIVED)
|
offer.setState(OfferStates.OFFER_RECEIVED)
|
||||||
self.add(offer, cursor)
|
self.add(offer, cursor)
|
||||||
@@ -7707,6 +7726,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
self.notify(NT.OFFER_RECEIVED, {"offer_id": offer_id.hex()}, cursor)
|
self.notify(NT.OFFER_RECEIVED, {"offer_id": offer_id.hex()}, cursor)
|
||||||
else:
|
else:
|
||||||
existing_offer.setState(OfferStates.OFFER_RECEIVED)
|
existing_offer.setState(OfferStates.OFFER_RECEIVED)
|
||||||
|
existing_offer.pk_from = pk_from
|
||||||
self.add(existing_offer, cursor, upsert=True)
|
self.add(existing_offer, cursor, upsert=True)
|
||||||
received_on_net: str = networkTypeToID(msg.get("type", "smsg"))
|
received_on_net: str = networkTypeToID(msg.get("type", "smsg"))
|
||||||
self.addMessageNetworkLink(
|
self.addMessageNetworkLink(
|
||||||
@@ -8068,8 +8088,8 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
bid_id = bytes.fromhex(msg["msgid"])
|
bid_id = bytes.fromhex(msg["msgid"])
|
||||||
|
|
||||||
bid = self.getBid(bid_id)
|
bid = self.getBid(bid_id)
|
||||||
|
pk_from: bytes = getMsgPubkey(self, msg)
|
||||||
if bid is None:
|
if bid is None:
|
||||||
pk_from: bytes = getMsgPubkey(self, msg)
|
|
||||||
bid = Bid(
|
bid = Bid(
|
||||||
active_ind=1,
|
active_ind=1,
|
||||||
bid_id=bid_id,
|
bid_id=bid_id,
|
||||||
@@ -8100,6 +8120,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
)
|
)
|
||||||
bid.created_at = msg["sent"]
|
bid.created_at = msg["sent"]
|
||||||
bid.expire_at = msg["sent"] + bid_data.time_valid
|
bid.expire_at = msg["sent"] + bid_data.time_valid
|
||||||
|
bid.pk_bid_addr = pk_from
|
||||||
bid.was_received = True
|
bid.was_received = True
|
||||||
if len(bid_data.proof_address) > 0:
|
if len(bid_data.proof_address) > 0:
|
||||||
bid.proof_address = bid_data.proof_address
|
bid.proof_address = bid_data.proof_address
|
||||||
@@ -8517,8 +8538,8 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
)
|
)
|
||||||
|
|
||||||
bid, xmr_swap = self.getXmrBid(bid_id)
|
bid, xmr_swap = self.getXmrBid(bid_id)
|
||||||
|
pk_from: bytes = getMsgPubkey(self, msg)
|
||||||
if bid is None:
|
if bid is None:
|
||||||
pk_from: bytes = getMsgPubkey(self, msg)
|
|
||||||
bid = Bid(
|
bid = Bid(
|
||||||
active_ind=1,
|
active_ind=1,
|
||||||
bid_id=bid_id,
|
bid_id=bid_id,
|
||||||
@@ -8559,6 +8580,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
)
|
)
|
||||||
# Don't update bid.created_at, it's been used to derive kaf
|
# Don't update bid.created_at, it's been used to derive kaf
|
||||||
bid.expire_at = msg["sent"] + bid_data.time_valid
|
bid.expire_at = msg["sent"] + bid_data.time_valid
|
||||||
|
bid.pk_bid_addr = pk_from
|
||||||
bid.was_received = True
|
bid.was_received = True
|
||||||
|
|
||||||
bid.setState(BidStates.BID_RECEIVING)
|
bid.setState(BidStates.BID_RECEIVING)
|
||||||
@@ -8877,6 +8899,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
msg_valid,
|
msg_valid,
|
||||||
cursor,
|
cursor,
|
||||||
message_nets=bid.message_nets,
|
message_nets=bid.message_nets,
|
||||||
|
payload_version=offer.smsg_payload_version,
|
||||||
)
|
)
|
||||||
self.addMessageLink(
|
self.addMessageLink(
|
||||||
Concepts.BID,
|
Concepts.BID,
|
||||||
@@ -9251,6 +9274,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
msg_valid,
|
msg_valid,
|
||||||
cursor,
|
cursor,
|
||||||
message_nets=bid.message_nets,
|
message_nets=bid.message_nets,
|
||||||
|
payload_version=offer.smsg_payload_version,
|
||||||
)
|
)
|
||||||
self.addMessageLink(
|
self.addMessageLink(
|
||||||
Concepts.BID,
|
Concepts.BID,
|
||||||
@@ -9679,6 +9703,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
msg_valid,
|
msg_valid,
|
||||||
cursor,
|
cursor,
|
||||||
message_nets=bid.message_nets,
|
message_nets=bid.message_nets,
|
||||||
|
payload_version=offer.smsg_payload_version,
|
||||||
)
|
)
|
||||||
|
|
||||||
bid.setState(BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX)
|
bid.setState(BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX)
|
||||||
@@ -10063,8 +10088,8 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
bid_id = bytes.fromhex(msg["msgid"])
|
bid_id = bytes.fromhex(msg["msgid"])
|
||||||
|
|
||||||
bid, xmr_swap = self.getXmrBid(bid_id)
|
bid, xmr_swap = self.getXmrBid(bid_id)
|
||||||
|
pk_from: bytes = getMsgPubkey(self, msg)
|
||||||
if bid is None:
|
if bid is None:
|
||||||
pk_from: bytes = getMsgPubkey(self, msg)
|
|
||||||
bid = Bid(
|
bid = Bid(
|
||||||
active_ind=1,
|
active_ind=1,
|
||||||
bid_id=bid_id,
|
bid_id=bid_id,
|
||||||
@@ -10101,6 +10126,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
)
|
)
|
||||||
# Don't update bid.created_at, it's been used to derive kaf
|
# Don't update bid.created_at, it's been used to derive kaf
|
||||||
bid.expire_at = msg["sent"] + bid_data.time_valid
|
bid.expire_at = msg["sent"] + bid_data.time_valid
|
||||||
|
bid.pk_bid_addr = pk_from
|
||||||
bid.was_received = True
|
bid.was_received = True
|
||||||
|
|
||||||
bid.setState(BidStates.BID_RECEIVED) # BID_REQUEST_RECEIVED
|
bid.setState(BidStates.BID_RECEIVED) # BID_REQUEST_RECEIVED
|
||||||
|
|||||||
@@ -195,6 +195,7 @@ class Offer(Table):
|
|||||||
) # Address to spend lock tx to - address from wallet if empty TODO
|
) # Address to spend lock tx to - address from wallet if empty TODO
|
||||||
security_token = Column("blob")
|
security_token = Column("blob")
|
||||||
bid_reversed = Column("bool")
|
bid_reversed = Column("bool")
|
||||||
|
smsg_payload_version = Column("integer")
|
||||||
|
|
||||||
state = Column("integer")
|
state = Column("integer")
|
||||||
states = Column("blob") # Packed states and times
|
states = Column("blob") # Packed states and times
|
||||||
@@ -259,6 +260,7 @@ class Bid(Table):
|
|||||||
chain_b_height_start = Column("integer")
|
chain_b_height_start = Column("integer")
|
||||||
|
|
||||||
reject_code = Column("integer")
|
reject_code = Column("integer")
|
||||||
|
smsg_payload_version = Column("integer")
|
||||||
|
|
||||||
initiate_tx = None
|
initiate_tx = None
|
||||||
participate_tx = None
|
participate_tx = None
|
||||||
|
|||||||
@@ -135,7 +135,11 @@ class BSXNetwork:
|
|||||||
):
|
):
|
||||||
self._can_use_smsg_payload2 = True
|
self._can_use_smsg_payload2 = True
|
||||||
# Set smsg_payload_version automatically if it's unset
|
# Set smsg_payload_version automatically if it's unset
|
||||||
self._smsg_payload_version = int(self.settings.get("smsg_payload_version", 2 if self._can_use_smsg_payload2 else 1))
|
self._smsg_payload_version = int(
|
||||||
|
self.settings.get(
|
||||||
|
"smsg_payload_version", 2 if self._can_use_smsg_payload2 else 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
have_smsg: bool = False
|
have_smsg: bool = False
|
||||||
for network in network_config_list:
|
for network in network_config_list:
|
||||||
@@ -501,6 +505,7 @@ class BSXNetwork:
|
|||||||
timestamp=None,
|
timestamp=None,
|
||||||
deterministic=False,
|
deterministic=False,
|
||||||
message_nets=None, # None|empty -> all
|
message_nets=None, # None|empty -> all
|
||||||
|
payload_version=None,
|
||||||
) -> bytes:
|
) -> bytes:
|
||||||
message_id: bytes = None
|
message_id: bytes = None
|
||||||
active_networks_list, bridged_networks_list = self.expandMessageNets(
|
active_networks_list, bridged_networks_list = self.expandMessageNets(
|
||||||
@@ -603,6 +608,7 @@ class BSXNetwork:
|
|||||||
msg_valid,
|
msg_valid,
|
||||||
return_msg=False,
|
return_msg=False,
|
||||||
cursor=cursor,
|
cursor=cursor,
|
||||||
|
payload_version=payload_version,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
net_message_id, smsg_msg = self.sendSmsg(
|
net_message_id, smsg_msg = self.sendSmsg(
|
||||||
@@ -612,6 +618,7 @@ class BSXNetwork:
|
|||||||
msg_valid,
|
msg_valid,
|
||||||
return_msg=True,
|
return_msg=True,
|
||||||
cursor=cursor,
|
cursor=cursor,
|
||||||
|
payload_version=payload_version,
|
||||||
)
|
)
|
||||||
elif network_type == MessageNetworks.SIMPLEX:
|
elif network_type == MessageNetworks.SIMPLEX:
|
||||||
if smsg_msg:
|
if smsg_msg:
|
||||||
@@ -704,9 +711,13 @@ class BSXNetwork:
|
|||||||
msg_valid: int,
|
msg_valid: int,
|
||||||
return_msg: bool = False,
|
return_msg: bool = False,
|
||||||
cursor=None,
|
cursor=None,
|
||||||
|
payload_version: int = None,
|
||||||
) -> bytes:
|
) -> bytes:
|
||||||
options = {"decodehex": True, "ttl_is_seconds": True}
|
options = {"decodehex": True, "ttl_is_seconds": True}
|
||||||
if self._smsg_payload_version >= 2:
|
use_payload_version = (
|
||||||
|
self._smsg_payload_version if payload_version is None else payload_version
|
||||||
|
)
|
||||||
|
if use_payload_version >= 2:
|
||||||
options["payload_format_version"] = 2
|
options["payload_format_version"] = 2
|
||||||
options["compression"] = 0
|
options["compression"] = 0
|
||||||
if self._can_use_smsg_payload2:
|
if self._can_use_smsg_payload2:
|
||||||
@@ -780,8 +791,12 @@ class BSXNetwork:
|
|||||||
)
|
)
|
||||||
self.commitDB()
|
self.commitDB()
|
||||||
|
|
||||||
|
def getSmsgMsgPayloadVersion(self, msg) -> int:
|
||||||
|
return msg.get("payloadversion", self._smsg_payload_version)
|
||||||
|
|
||||||
def getSmsgMsgBytes(self, msg) -> bytes:
|
def getSmsgMsgBytes(self, msg) -> bytes:
|
||||||
if int(self._smsg_payload_version) < 2:
|
payload_version = self.getSmsgMsgPayloadVersion(msg)
|
||||||
|
if payload_version < 2:
|
||||||
return bytes.fromhex(msg["hex"][2:-2])
|
return bytes.fromhex(msg["hex"][2:-2])
|
||||||
return bytes.fromhex(msg["hex"][2:])
|
return bytes.fromhex(msg["hex"][2:])
|
||||||
|
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ def smsgEncrypt(
|
|||||||
address_version = 0
|
address_version = 0
|
||||||
plaintext_data: bytes = bytes((address_version,))
|
plaintext_data: bytes = bytes((address_version,))
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unknown plaintext format.")
|
raise ValueError("Unknown payload format.")
|
||||||
plaintext_data += bytes(
|
plaintext_data += bytes(
|
||||||
pkh_from + signature + len_payload.to_bytes(4, byteorder="little") + payload
|
pkh_from + signature + len_payload.to_bytes(4, byteorder="little") + payload
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user