mirror of
https://github.com/aljazceru/lightning.git
synced 2026-01-06 23:54:22 +01:00
gossipd: use modern 'sync_complete' field.
We assume if they set this to 0 (which nobody did previously), they're using it as a modern flag and use it to indicate when they're finished. Otherwise, we count how many blocks they've sent and use that to determine whether they've finished. See: https://github.com/lightningnetwork/lightning-rfc/pull/826 Signed-off-by: Rusty Russell <rusty@rustcorp.com.au> Changelog-Changed: Protocol: we use `sync_complete` for gossip range query replies, with detection for older spec nodes.
This commit is contained in:
2
Makefile
2
Makefile
@@ -24,7 +24,7 @@ CCANDIR := ccan
|
||||
|
||||
# Where we keep the BOLT RFCs
|
||||
BOLTDIR := ../lightning-rfc/
|
||||
BOLTVERSION := b80f8a719406b70f67e4cf7d034e8cd331850173
|
||||
BOLTVERSION := edd45ecf22095ce97c1b5e9136a7d79351bd68cb
|
||||
|
||||
-include config.vars
|
||||
|
||||
|
||||
@@ -102,12 +102,11 @@ struct peer {
|
||||
|
||||
/* What we're querying: [range_first_blocknum, range_end_blocknum) */
|
||||
u32 range_first_blocknum, range_end_blocknum;
|
||||
u32 range_prev_end_blocknum;
|
||||
u32 range_blocks_outstanding;
|
||||
struct range_query_reply *range_replies;
|
||||
void (*query_channel_range_cb)(struct peer *peer,
|
||||
u32 first_blocknum, u32 number_of_blocks,
|
||||
const struct range_query_reply *replies,
|
||||
bool complete);
|
||||
const struct range_query_reply *replies);
|
||||
|
||||
/* The daemon_conn used to queue messages to/from the peer. */
|
||||
struct daemon_conn *dc;
|
||||
|
||||
@@ -350,7 +350,8 @@ static void send_reply_channel_range(struct peer *peer,
|
||||
const struct short_channel_id *scids,
|
||||
const struct channel_update_timestamps *tstamps,
|
||||
const struct channel_update_checksums *csums,
|
||||
size_t num_scids)
|
||||
size_t num_scids,
|
||||
bool final)
|
||||
{
|
||||
/* BOLT #7:
|
||||
*
|
||||
@@ -358,11 +359,6 @@ static void send_reply_channel_range(struct peer *peer,
|
||||
* - MUST set with `chain_hash` equal to that of `query_channel_range`,
|
||||
* - MUST limit `number_of_blocks` to the maximum number of blocks
|
||||
* whose results could fit in `encoded_short_ids`
|
||||
* - if does not maintain up-to-date channel information for
|
||||
* `chain_hash`:
|
||||
* - MUST set `full_information` to 0.
|
||||
* - otherwise:
|
||||
* - SHOULD set `full_information` to 1.
|
||||
*/
|
||||
u8 *encoded_scids = encoding_start(tmpctx);
|
||||
u8 *encoded_timestamps = encoding_start(tmpctx);
|
||||
@@ -392,11 +388,16 @@ static void send_reply_channel_range(struct peer *peer,
|
||||
struct channel_update_checksums,
|
||||
csums, num_scids, 0);
|
||||
|
||||
/* BOLT #7:
|
||||
*
|
||||
* - MUST set `sync_complete` to `false` if this is not the final
|
||||
* `reply_channel_range`.
|
||||
*/
|
||||
u8 *msg = towire_reply_channel_range(NULL,
|
||||
&chainparams->genesis_blockhash,
|
||||
first_blocknum,
|
||||
number_of_blocks,
|
||||
1, encoded_scids, tlvs);
|
||||
final, encoded_scids, tlvs);
|
||||
queue_peer_msg(peer, take(msg));
|
||||
}
|
||||
|
||||
@@ -453,7 +454,7 @@ static size_t max_entries(enum query_option_flags query_option_flags)
|
||||
* * [`chain_hash`:`chain_hash`]
|
||||
* * [`u32`:`first_blocknum`]
|
||||
* * [`u32`:`number_of_blocks`]
|
||||
* * [`byte`:`full_information`]
|
||||
* * [`byte`:`sync_complete`]
|
||||
* * [`u16`:`len`]
|
||||
* * [`len*byte`:`encoded_short_ids`]
|
||||
*/
|
||||
@@ -632,7 +633,8 @@ static void queue_channel_ranges(struct peer *peer,
|
||||
? tstamps + off : NULL,
|
||||
query_option_flags & QUERY_ADD_CHECKSUMS
|
||||
? csums + off : NULL,
|
||||
n);
|
||||
n,
|
||||
this_num_blocks == number_of_blocks);
|
||||
first_blocknum += this_num_blocks;
|
||||
number_of_blocks -= this_num_blocks;
|
||||
off += n;
|
||||
@@ -733,7 +735,7 @@ static u8 *append_range_reply(struct peer *peer,
|
||||
const u8 *handle_reply_channel_range(struct peer *peer, const u8 *msg)
|
||||
{
|
||||
struct bitcoin_blkid chain;
|
||||
u8 complete;
|
||||
u8 sync_complete;
|
||||
u32 first_blocknum, number_of_blocks, start, end;
|
||||
u8 *encoded;
|
||||
struct short_channel_id *scids;
|
||||
@@ -741,13 +743,12 @@ const u8 *handle_reply_channel_range(struct peer *peer, const u8 *msg)
|
||||
const u8 *err;
|
||||
void (*cb)(struct peer *peer,
|
||||
u32 first_blocknum, u32 number_of_blocks,
|
||||
const struct range_query_reply *replies,
|
||||
bool complete);
|
||||
const struct range_query_reply *replies);
|
||||
struct tlv_reply_channel_range_tlvs *tlvs
|
||||
= tlv_reply_channel_range_tlvs_new(tmpctx);
|
||||
|
||||
if (!fromwire_reply_channel_range(tmpctx, msg, &chain, &first_blocknum,
|
||||
&number_of_blocks, &complete,
|
||||
&number_of_blocks, &sync_complete,
|
||||
&encoded, tlvs)) {
|
||||
return towire_warningfmt(peer, NULL,
|
||||
"Bad reply_channel_range w/tlvs %s",
|
||||
@@ -788,7 +789,6 @@ const u8 *handle_reply_channel_range(struct peer *peer, const u8 *msg)
|
||||
tal_count(scids));
|
||||
|
||||
/* BOLT #7:
|
||||
*
|
||||
* The receiver of `query_channel_range`:
|
||||
*...
|
||||
* - the first `reply_channel_range` message:
|
||||
@@ -797,12 +797,14 @@ const u8 *handle_reply_channel_range(struct peer *peer, const u8 *msg)
|
||||
* - MUST set `first_blocknum` plus `number_of_blocks` greater than
|
||||
* `first_blocknum` in `query_channel_range`.
|
||||
* - successive `reply_channel_range` message:
|
||||
* - MUST set `first_blocknum` to the previous `first_blocknum`
|
||||
* plus `number_of_blocks`.
|
||||
* - MUST have `first_blocknum` equal or greater than the previous
|
||||
* `first_blocknum`.
|
||||
* - MUST set `sync_complete` to `false` if this is not the final `reply_channel_range`.
|
||||
* - the final `reply_channel_range` message:
|
||||
* - MUST have `first_blocknum` plus `number_of_blocks` equal or
|
||||
* greater than the `query_channel_range` `first_blocknum` plus
|
||||
* `number_of_blocks`.
|
||||
* - MUST set `sync_complete` to `true`.
|
||||
*/
|
||||
/* ie. They can be outside range we asked, but they must overlap! */
|
||||
if (first_blocknum + number_of_blocks <= peer->range_first_blocknum
|
||||
@@ -823,28 +825,58 @@ const u8 *handle_reply_channel_range(struct peer *peer, const u8 *msg)
|
||||
if (end > peer->range_end_blocknum)
|
||||
end = peer->range_end_blocknum;
|
||||
|
||||
/* LND mis-implemented the spec. If they have multiple replies, set
|
||||
* each one to the *whole* range, with complete=0 except the last.
|
||||
* Try to accomodate that (pretend we make no progress until the
|
||||
* end)! */
|
||||
/* Have a seat. It's time for a history lesson in Rusty Screws Up.
|
||||
*
|
||||
* Part 1
|
||||
* ------
|
||||
* The original spec had a field called "complete" which meant
|
||||
* "I believe I have complete knowledge of gossip", with the idea
|
||||
* that lite nodes in future would not set this.
|
||||
*
|
||||
* But I chose a terrible name, and LND mis-implemented the spec,
|
||||
* thinking this was an "end of replies". If they have multiple
|
||||
* replies, set each one to the *whole* range, with complete=0 except
|
||||
* the last.
|
||||
*
|
||||
* Here we try to accomodate that (pretend we make no progress
|
||||
* until the end)! */
|
||||
if (first_blocknum == peer->range_first_blocknum
|
||||
&& first_blocknum + number_of_blocks == peer->range_end_blocknum
|
||||
&& !complete
|
||||
&& !sync_complete
|
||||
&& tal_bytelen(msg) == 64046) {
|
||||
status_unusual("Old LND reply_channel_range detected: result will be truncated!");
|
||||
}
|
||||
|
||||
/* They're supposed to send them in order, but LND actually
|
||||
* can overlap. */
|
||||
if (first_blocknum != peer->range_prev_end_blocknum + 1
|
||||
&& first_blocknum != peer->range_prev_end_blocknum) {
|
||||
return towire_warningfmt(peer, NULL,
|
||||
"reply_channel_range %u+%u previous end was block %u",
|
||||
first_blocknum, number_of_blocks,
|
||||
peer->range_prev_end_blocknum);
|
||||
}
|
||||
peer->range_prev_end_blocknum = end;
|
||||
|
||||
/*
|
||||
* Part 2
|
||||
* ------
|
||||
* You were supposed to use the first_blocknum + number_of_blocks
|
||||
* to tell when gossip was finished, with the rule being no replies
|
||||
* could overlap, so you could say "I asked for blocks 100-199" and if
|
||||
* you got a reply saying it covered blocks 50-150, you knew that you
|
||||
* still had 49 blocks to receive.
|
||||
*
|
||||
* The field was renamed to `full_information`, and since everyone
|
||||
* did it this way anyway, we insisted the replies be in
|
||||
* non-overlapping ascending order.
|
||||
*
|
||||
* But LND didn't do this, and can actually overlap, since they just
|
||||
* chop them up when they reach length, not by block boundary, so
|
||||
* we had to allow that.
|
||||
*
|
||||
* Reading this implementation gave me envy: it was much simpler than
|
||||
* backing out to a block boundary!
|
||||
*
|
||||
* And what if a single block had so many channel openings that you
|
||||
* couldn't fit it in a single reply? (This was originally
|
||||
* inconceivable, but with the addition of timestamps and checksums,
|
||||
* is now possible).
|
||||
*
|
||||
* So we decided to make the lie into a truth. `full_information`
|
||||
* was re-renamed to `sync_complete`, and once everyone has upgraded
|
||||
* we can use that, rather than tallying the block numbers, to
|
||||
* tell if replies are finished.
|
||||
*/
|
||||
err = append_range_reply(peer, scids, tlvs->timestamps_tlv);
|
||||
if (err)
|
||||
return err;
|
||||
@@ -853,9 +885,20 @@ const u8 *handle_reply_channel_range(struct peer *peer, const u8 *msg)
|
||||
* since scids are only 8 bytes, use a discount over normal gossip. */
|
||||
peer_supplied_good_gossip(peer, tal_count(scids) / 20);
|
||||
|
||||
/* Still more to go? */
|
||||
if (peer->range_prev_end_blocknum < peer->range_end_blocknum)
|
||||
/* Old code used to set this to 1 all the time; not setting it implies
|
||||
* we're talking to an upgraded node. */
|
||||
if (!sync_complete) {
|
||||
/* We no longer need old heuristic counter. */
|
||||
peer->range_blocks_outstanding = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* FIXME: This "how many blocks do we have answers for?" heuristic
|
||||
* can go away once everyone uses sync_complete properly. */
|
||||
if (end - start < peer->range_blocks_outstanding) {
|
||||
peer->range_blocks_outstanding -= end - start;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Clear these immediately in case cb want to queue more */
|
||||
replies = tal_steal(tmpctx, peer->range_replies);
|
||||
@@ -864,7 +907,7 @@ const u8 *handle_reply_channel_range(struct peer *peer, const u8 *msg)
|
||||
peer->range_replies = NULL;
|
||||
peer->query_channel_range_cb = NULL;
|
||||
|
||||
cb(peer, first_blocknum, number_of_blocks, replies, complete);
|
||||
cb(peer, first_blocknum, number_of_blocks, replies);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -1087,8 +1130,7 @@ bool query_channel_range(struct daemon *daemon,
|
||||
enum query_option_flags qflags,
|
||||
void (*cb)(struct peer *peer,
|
||||
u32 first_blocknum, u32 number_of_blocks,
|
||||
const struct range_query_reply *replies,
|
||||
bool complete))
|
||||
const struct range_query_reply *replies))
|
||||
{
|
||||
u8 *msg;
|
||||
struct tlv_query_channel_range_tlvs *tlvs;
|
||||
@@ -1114,7 +1156,7 @@ bool query_channel_range(struct daemon *daemon,
|
||||
queue_peer_msg(peer, take(msg));
|
||||
peer->range_first_blocknum = first_blocknum;
|
||||
peer->range_end_blocknum = first_blocknum + number_of_blocks;
|
||||
peer->range_prev_end_blocknum = first_blocknum-1;
|
||||
peer->range_blocks_outstanding = number_of_blocks;
|
||||
peer->range_replies = tal_arr(peer, struct range_query_reply, 0);
|
||||
peer->query_channel_range_cb = cb;
|
||||
|
||||
|
||||
@@ -42,8 +42,7 @@ bool query_channel_range(struct daemon *daemon,
|
||||
enum query_option_flags qflags,
|
||||
void (*cb)(struct peer *peer,
|
||||
u32 first_blocknum, u32 number_of_blocks,
|
||||
const struct range_query_reply *replies,
|
||||
bool complete));
|
||||
const struct range_query_reply *replies));
|
||||
|
||||
/* Ask this peer for info about an array of scids, with optional query_flags */
|
||||
bool query_short_channel_ids(struct daemon *daemon,
|
||||
|
||||
@@ -643,8 +643,7 @@ static void check_timestamps(struct seeker *seeker,
|
||||
|
||||
static void process_scid_probe(struct peer *peer,
|
||||
u32 first_blocknum, u32 number_of_blocks,
|
||||
const struct range_query_reply *replies,
|
||||
bool complete)
|
||||
const struct range_query_reply *replies)
|
||||
{
|
||||
struct seeker *seeker = peer->daemon->seeker;
|
||||
bool new_unknown_scids = false;
|
||||
|
||||
@@ -39,8 +39,7 @@ bool query_channel_range(struct daemon *daemon UNNEEDED,
|
||||
enum query_option_flags qflags UNNEEDED,
|
||||
void (*cb)(struct peer *peer UNNEEDED,
|
||||
u32 first_blocknum UNNEEDED, u32 number_of_blocks UNNEEDED,
|
||||
const struct range_query_reply *replies UNNEEDED,
|
||||
bool complete))
|
||||
const struct range_query_reply *replies))
|
||||
{ fprintf(stderr, "query_channel_range called!\n"); abort(); }
|
||||
/* Generated stub for query_short_channel_ids */
|
||||
bool query_short_channel_ids(struct daemon *daemon UNNEEDED,
|
||||
|
||||
8
wire/peer_printgen.c
generated
8
wire/peer_printgen.c
generated
@@ -2005,10 +2005,10 @@ void printwire_reply_channel_range(const char *fieldname, const u8 *cursor)
|
||||
printf("**TRUNCATED**\n");
|
||||
return;
|
||||
}
|
||||
printf("full_information=");
|
||||
u8 full_information = fromwire_u8(&cursor, &plen);
|
||||
printf("sync_complete=");
|
||||
u8 sync_complete = fromwire_u8(&cursor, &plen);
|
||||
|
||||
printwire_u8(tal_fmt(NULL, "%s.full_information", fieldname), &full_information);
|
||||
printwire_u8(tal_fmt(NULL, "%s.sync_complete", fieldname), &sync_complete);
|
||||
if (!cursor) {
|
||||
printf("**TRUNCATED**\n");
|
||||
return;
|
||||
@@ -2130,4 +2130,4 @@ void printpeer_wire_tlv_message(const char *tlv_name, const u8 *msg) {
|
||||
printwire_tlvs(tlv_name, &msg, &plen, print_tlvs_onion_message_tlvs, ARRAY_SIZE(print_tlvs_onion_message_tlvs));
|
||||
}
|
||||
}
|
||||
// SHA256STAMP:d0f5b313c478153542610f14d7c6b39c1121b6a6b08fb72f3d427a103243b990
|
||||
// SHA256STAMP:b3a92a714208711191936bbdb7ff0f0a2ca412941ea95e67210029e5249f3d60
|
||||
|
||||
2
wire/peer_printgen.h
generated
2
wire/peer_printgen.h
generated
@@ -74,4 +74,4 @@ void printwire_onion_message(const char *fieldname, const u8 *cursor);
|
||||
void printwire_channel_update_checksums(const char *fieldname, const u8 **cursor, size_t *plen);
|
||||
void printwire_channel_update_timestamps(const char *fieldname, const u8 **cursor, size_t *plen);
|
||||
#endif /* LIGHTNING_WIRE_PEER_PRINTGEN_H */
|
||||
// SHA256STAMP:d0f5b313c478153542610f14d7c6b39c1121b6a6b08fb72f3d427a103243b990
|
||||
// SHA256STAMP:b3a92a714208711191936bbdb7ff0f0a2ca412941ea95e67210029e5249f3d60
|
||||
|
||||
@@ -195,7 +195,7 @@ msgtype,reply_channel_range,264,gossip_queries
|
||||
msgdata,reply_channel_range,chain_hash,chain_hash,
|
||||
msgdata,reply_channel_range,first_blocknum,u32,
|
||||
msgdata,reply_channel_range,number_of_blocks,u32,
|
||||
msgdata,reply_channel_range,full_information,byte,
|
||||
msgdata,reply_channel_range,sync_complete,byte,
|
||||
msgdata,reply_channel_range,len,u16,
|
||||
msgdata,reply_channel_range,encoded_short_ids,byte,len
|
||||
msgdata,reply_channel_range,tlvs,reply_channel_range_tlvs,
|
||||
|
||||
|
10
wire/peer_wiregen.c
generated
10
wire/peer_wiregen.c
generated
@@ -1617,7 +1617,7 @@ bool fromwire_query_channel_range(const void *p, struct bitcoin_blkid *chain_has
|
||||
}
|
||||
|
||||
/* WIRE: REPLY_CHANNEL_RANGE */
|
||||
u8 *towire_reply_channel_range(const tal_t *ctx, const struct bitcoin_blkid *chain_hash, u32 first_blocknum, u32 number_of_blocks, u8 full_information, const u8 *encoded_short_ids, const struct tlv_reply_channel_range_tlvs *tlvs)
|
||||
u8 *towire_reply_channel_range(const tal_t *ctx, const struct bitcoin_blkid *chain_hash, u32 first_blocknum, u32 number_of_blocks, u8 sync_complete, const u8 *encoded_short_ids, const struct tlv_reply_channel_range_tlvs *tlvs)
|
||||
{
|
||||
u16 len = tal_count(encoded_short_ids);
|
||||
u8 *p = tal_arr(ctx, u8, 0);
|
||||
@@ -1626,14 +1626,14 @@ u8 *towire_reply_channel_range(const tal_t *ctx, const struct bitcoin_blkid *cha
|
||||
towire_bitcoin_blkid(&p, chain_hash);
|
||||
towire_u32(&p, first_blocknum);
|
||||
towire_u32(&p, number_of_blocks);
|
||||
towire_u8(&p, full_information);
|
||||
towire_u8(&p, sync_complete);
|
||||
towire_u16(&p, len);
|
||||
towire_u8_array(&p, encoded_short_ids, len);
|
||||
towire_reply_channel_range_tlvs(&p, tlvs);
|
||||
|
||||
return memcheck(p, tal_count(p));
|
||||
}
|
||||
bool fromwire_reply_channel_range(const tal_t *ctx, const void *p, struct bitcoin_blkid *chain_hash, u32 *first_blocknum, u32 *number_of_blocks, u8 *full_information, u8 **encoded_short_ids, struct tlv_reply_channel_range_tlvs *tlvs)
|
||||
bool fromwire_reply_channel_range(const tal_t *ctx, const void *p, struct bitcoin_blkid *chain_hash, u32 *first_blocknum, u32 *number_of_blocks, u8 *sync_complete, u8 **encoded_short_ids, struct tlv_reply_channel_range_tlvs *tlvs)
|
||||
{
|
||||
u16 len;
|
||||
|
||||
@@ -1645,7 +1645,7 @@ bool fromwire_reply_channel_range(const tal_t *ctx, const void *p, struct bitcoi
|
||||
fromwire_bitcoin_blkid(&cursor, &plen, chain_hash);
|
||||
*first_blocknum = fromwire_u32(&cursor, &plen);
|
||||
*number_of_blocks = fromwire_u32(&cursor, &plen);
|
||||
*full_information = fromwire_u8(&cursor, &plen);
|
||||
*sync_complete = fromwire_u8(&cursor, &plen);
|
||||
len = fromwire_u16(&cursor, &plen);
|
||||
// 2nd case encoded_short_ids
|
||||
*encoded_short_ids = len ? tal_arr(ctx, u8, len) : NULL;
|
||||
@@ -1749,4 +1749,4 @@ bool fromwire_channel_update_option_channel_htlc_max(const void *p, secp256k1_ec
|
||||
*htlc_maximum_msat = fromwire_amount_msat(&cursor, &plen);
|
||||
return cursor != NULL;
|
||||
}
|
||||
// SHA256STAMP:d0f5b313c478153542610f14d7c6b39c1121b6a6b08fb72f3d427a103243b990
|
||||
// SHA256STAMP:b3a92a714208711191936bbdb7ff0f0a2ca412941ea95e67210029e5249f3d60
|
||||
|
||||
6
wire/peer_wiregen.h
generated
6
wire/peer_wiregen.h
generated
@@ -633,8 +633,8 @@ u8 *towire_query_channel_range(const tal_t *ctx, const struct bitcoin_blkid *cha
|
||||
bool fromwire_query_channel_range(const void *p, struct bitcoin_blkid *chain_hash, u32 *first_blocknum, u32 *number_of_blocks, struct tlv_query_channel_range_tlvs *tlvs);
|
||||
|
||||
/* WIRE: REPLY_CHANNEL_RANGE */
|
||||
u8 *towire_reply_channel_range(const tal_t *ctx, const struct bitcoin_blkid *chain_hash, u32 first_blocknum, u32 number_of_blocks, u8 full_information, const u8 *encoded_short_ids, const struct tlv_reply_channel_range_tlvs *tlvs);
|
||||
bool fromwire_reply_channel_range(const tal_t *ctx, const void *p, struct bitcoin_blkid *chain_hash, u32 *first_blocknum, u32 *number_of_blocks, u8 *full_information, u8 **encoded_short_ids, struct tlv_reply_channel_range_tlvs *tlvs);
|
||||
u8 *towire_reply_channel_range(const tal_t *ctx, const struct bitcoin_blkid *chain_hash, u32 first_blocknum, u32 number_of_blocks, u8 sync_complete, const u8 *encoded_short_ids, const struct tlv_reply_channel_range_tlvs *tlvs);
|
||||
bool fromwire_reply_channel_range(const tal_t *ctx, const void *p, struct bitcoin_blkid *chain_hash, u32 *first_blocknum, u32 *number_of_blocks, u8 *sync_complete, u8 **encoded_short_ids, struct tlv_reply_channel_range_tlvs *tlvs);
|
||||
|
||||
/* WIRE: GOSSIP_TIMESTAMP_FILTER */
|
||||
u8 *towire_gossip_timestamp_filter(const tal_t *ctx, const struct bitcoin_blkid *chain_hash, u32 first_timestamp, u32 timestamp_range);
|
||||
@@ -650,4 +650,4 @@ bool fromwire_channel_update_option_channel_htlc_max(const void *p, secp256k1_ec
|
||||
|
||||
|
||||
#endif /* LIGHTNING_WIRE_PEER_WIREGEN_H */
|
||||
// SHA256STAMP:d0f5b313c478153542610f14d7c6b39c1121b6a6b08fb72f3d427a103243b990
|
||||
// SHA256STAMP:b3a92a714208711191936bbdb7ff0f0a2ca412941ea95e67210029e5249f3d60
|
||||
|
||||
Reference in New Issue
Block a user