Skip to content

Commit e244d46

Browse files
riptlripatel-fd
authored andcommitted
Don't hardcode repair tile links
1 parent 4461dad commit e244d46

File tree

1 file changed

+114
-120
lines changed

1 file changed

+114
-120
lines changed

src/discof/repair/fd_repair_tile.c

Lines changed: 114 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,14 @@
1616
#include "../../disco/topo/fd_pod_format.h"
1717
#include "../../util/net/fd_net_headers.h"
1818

19-
#include <unistd.h>
20-
#include <arpa/inet.h>
21-
#include <linux/unistd.h>
22-
#include <sys/random.h>
23-
#include <netdb.h>
2419
#include <errno.h>
25-
#include <netinet/in.h>
2620

27-
#define NET_IN_IDX 0
28-
#define CONTACT_IN_IDX 1
29-
#define STAKE_IN_IDX 2
30-
#define STORE_IN_IDX 3
31-
#define SIGN_IN_IDX 4
21+
#define IN_KIND_NET (0)
22+
#define IN_KIND_CONTACT (1)
23+
#define IN_KIND_STAKE (2)
24+
#define IN_KIND_STORE (3)
25+
#define IN_KIND_SIGN (4)
26+
#define MAX_IN_LINKS (8)
3227

3328
#define STORE_OUT_IDX 0
3429
#define NET_OUT_IDX 1
@@ -37,6 +32,16 @@
3732
#define MAX_REPAIR_PEERS 40200UL
3833
#define MAX_BUFFER_SIZE ( MAX_REPAIR_PEERS * sizeof(fd_shred_dest_wire_t))
3934

35+
typedef union {
36+
struct {
37+
fd_wksp_t * mem;
38+
ulong chunk0;
39+
ulong wmark;
40+
ulong mtu;
41+
};
42+
fd_net_rx_bounds_t net_rx;
43+
} fd_repair_in_ctx_t;
44+
4045
struct fd_repair_tile_ctx {
4146
fd_repair_t * repair;
4247
fd_repair_config_t repair_config;
@@ -54,19 +59,8 @@ struct fd_repair_tile_ctx {
5459

5560
fd_wksp_t * wksp;
5661

57-
fd_wksp_t * contact_in_mem;
58-
ulong contact_in_chunk0;
59-
ulong contact_in_wmark;
60-
61-
fd_wksp_t * stake_weights_in_mem;
62-
ulong stake_weights_in_chunk0;
63-
ulong stake_weights_in_wmark;
64-
65-
fd_wksp_t * repair_req_in_mem;
66-
ulong repair_req_in_chunk0;
67-
ulong repair_req_in_wmark;
68-
69-
fd_net_rx_bounds_t net_in_bounds;
62+
uchar in_kind[ MAX_IN_LINKS ];
63+
fd_repair_in_ctx_t in_links[ MAX_IN_LINKS ];
7064

7165
fd_frag_meta_t * net_out_mcache;
7266
ulong * net_out_sync;
@@ -290,12 +284,10 @@ repair_shred_deliver_fail( fd_pubkey_t const * id FD_PARAM_UNUSED,
290284
static inline int
291285
before_frag( fd_repair_tile_ctx_t * ctx,
292286
ulong in_idx,
293-
ulong seq,
287+
ulong seq FD_PARAM_UNUSED,
294288
ulong sig ) {
295-
(void)ctx;
296-
(void)seq;
297-
298-
if( FD_LIKELY( in_idx==NET_IN_IDX ) ) return fd_disco_netmux_sig_proto( sig )!=DST_PROTO_REPAIR;
289+
uint in_kind = ctx->in_kind[ in_idx ];
290+
if( FD_LIKELY( in_kind==IN_KIND_NET ) ) return fd_disco_netmux_sig_proto( sig )!=DST_PROTO_REPAIR;
299291
return 0;
300292
}
301293

@@ -312,37 +304,34 @@ during_frag( fd_repair_tile_ctx_t * ctx,
312304
ulong dcache_entry_sz;
313305

314306
// TODO: check for sz>MTU for failure once MTUs are decided
315-
if( FD_UNLIKELY( in_idx==CONTACT_IN_IDX ) ) {
316-
if( FD_UNLIKELY( chunk<ctx->contact_in_chunk0 || chunk>ctx->contact_in_wmark ) ) {
317-
FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz,
318-
ctx->contact_in_chunk0, ctx->contact_in_wmark ));
307+
uint in_kind = ctx->in_kind[ in_idx ];
308+
fd_repair_in_ctx_t const * in_ctx = &ctx->in_links[ in_idx ];
309+
if( FD_UNLIKELY( in_kind==IN_KIND_CONTACT ) ) {
310+
if( FD_UNLIKELY( chunk<in_ctx->chunk0 || chunk>in_ctx->wmark ) ) {
311+
FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz, in_ctx->chunk0, in_ctx->wmark ));
319312
}
320-
dcache_entry = fd_chunk_to_laddr_const( ctx->contact_in_mem, chunk );
313+
dcache_entry = fd_chunk_to_laddr_const( in_ctx->mem, chunk );
321314
dcache_entry_sz = sz * sizeof(fd_shred_dest_wire_t);
322315

323-
} else if( FD_UNLIKELY( in_idx==STAKE_IN_IDX ) ) {
324-
if( FD_UNLIKELY( chunk<ctx->stake_weights_in_chunk0 || chunk>ctx->stake_weights_in_wmark ) ) {
325-
FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz,
326-
ctx->stake_weights_in_chunk0, ctx->stake_weights_in_wmark ));
316+
} else if( FD_UNLIKELY( in_kind==IN_KIND_STAKE ) ) {
317+
if( FD_UNLIKELY( chunk<in_ctx->chunk0 || chunk>in_ctx->wmark ) ) {
318+
FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz, in_ctx->chunk0, in_ctx->wmark ));
327319
}
328-
329-
dcache_entry = fd_chunk_to_laddr_const( ctx->stake_weights_in_mem, chunk );
320+
dcache_entry = fd_chunk_to_laddr_const( in_ctx->mem, chunk );
330321
fd_stake_ci_stake_msg_init( ctx->stake_ci, dcache_entry );
331322
return;
332323

333-
} else if( FD_UNLIKELY( in_idx==STORE_IN_IDX ) ) {
334-
if( FD_UNLIKELY( chunk<ctx->repair_req_in_chunk0 || chunk>ctx->repair_req_in_wmark ) ) {
335-
FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz,
336-
ctx->repair_req_in_chunk0, ctx->repair_req_in_wmark ));
324+
} else if( FD_UNLIKELY( in_kind==IN_KIND_STORE ) ) {
325+
if( FD_UNLIKELY( chunk<in_ctx->chunk0 || chunk>in_ctx->wmark ) ) {
326+
FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz, in_ctx->chunk0, in_ctx->wmark ));
337327
}
338-
339-
dcache_entry = fd_chunk_to_laddr_const( ctx->repair_req_in_mem, chunk );
328+
dcache_entry = fd_chunk_to_laddr_const( in_ctx->mem, chunk );
340329
dcache_entry_sz = sz;
341-
} else if ( FD_LIKELY( in_idx == NET_IN_IDX ) ) {
342-
dcache_entry = fd_net_rx_translate_frag( &ctx->net_in_bounds, chunk, ctl, sz );
330+
} else if( FD_LIKELY( in_kind==IN_KIND_NET ) ) {
331+
dcache_entry = fd_net_rx_translate_frag( &in_ctx->net_rx, chunk, ctl, sz );
343332
dcache_entry_sz = sz;
344333
} else {
345-
FD_LOG_ERR(("Unknown in_idx %lu for repair", in_idx));
334+
FD_LOG_ERR(( "Frag from unknown link (kind=%u in_idx=%lu)", in_kind, in_idx ));
346335
}
347336

348337
fd_memcpy( ctx->buffer, dcache_entry, dcache_entry_sz );
@@ -358,18 +347,19 @@ after_frag( fd_repair_tile_ctx_t * ctx,
358347
ulong tspub FD_PARAM_UNUSED,
359348
fd_stem_context_t * stem ) {
360349

361-
if( FD_UNLIKELY( in_idx==CONTACT_IN_IDX ) ) {
350+
uint in_kind = ctx->in_kind[ in_idx ];
351+
if( FD_UNLIKELY( in_kind==IN_KIND_CONTACT ) ) {
362352
handle_new_cluster_contact_info( ctx, ctx->buffer, sz );
363353
return;
364354
}
365355

366-
if( FD_UNLIKELY( in_idx==STAKE_IN_IDX ) ) {
356+
if( FD_UNLIKELY( in_kind==IN_KIND_STAKE ) ) {
367357
fd_stake_ci_stake_msg_fini( ctx->stake_ci );
368358
handle_new_stake_weights( ctx );
369359
return;
370360
}
371361

372-
if( FD_UNLIKELY( in_idx==STORE_IN_IDX ) ) {
362+
if( FD_UNLIKELY( in_kind==IN_KIND_STORE ) ) {
373363
handle_new_repair_requests( ctx, ctx->buffer, sz );
374364
return;
375365
}
@@ -398,12 +388,9 @@ after_frag( fd_repair_tile_ctx_t * ctx,
398388

399389
static inline void
400390
after_credit( fd_repair_tile_ctx_t * ctx,
401-
fd_stem_context_t * stem,
402-
int * opt_poll_in,
391+
fd_stem_context_t * stem FD_PARAM_UNUSED,
392+
int * opt_poll_in FD_PARAM_UNUSED,
403393
int * charge_busy ) {
404-
(void)stem;
405-
(void)opt_poll_in;
406-
407394
/* TODO: Don't charge the tile as busy if after_credit isn't actually
408395
doing any work. */
409396
*charge_busy = 1;
@@ -464,6 +451,7 @@ privileged_init( fd_topo_t * topo,
464451

465452
FD_SCRATCH_ALLOC_INIT( l, scratch );
466453
fd_repair_tile_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_repair_tile_ctx_t), sizeof(fd_repair_tile_ctx_t) );
454+
fd_memset( ctx, 0, sizeof(fd_repair_tile_ctx_t) );
467455

468456
uchar const * identity_key = fd_keyload_load( tile->repair.identity_key_path, /* pubkey only: */ 0 );
469457
fd_memcpy( ctx->identity_private_key, identity_key, sizeof(fd_pubkey_t) );
@@ -478,38 +466,86 @@ privileged_init( fd_topo_t * topo,
478466
}
479467
ctx->repair_config.good_peer_cache_file_fd = tile->repair.good_peer_cache_file_fd;
480468

481-
FD_TEST( sizeof(ulong) == getrandom( &ctx->repair_seed, sizeof(ulong), 0 ) );
469+
FD_TEST( fd_rng_secure( &ctx->repair_seed, sizeof(ulong) ) );
482470
}
483471

484472
static void
485473
unprivileged_init( fd_topo_t * topo,
486474
fd_topo_tile_t * tile ) {
487475
void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
476+
FD_SCRATCH_ALLOC_INIT( l, scratch );
477+
fd_repair_tile_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_repair_tile_ctx_t), sizeof(fd_repair_tile_ctx_t) );
488478

489-
if( FD_UNLIKELY( tile->in_cnt != 5 ||
490-
strcmp( topo->links[ tile->in_link_id[ NET_IN_IDX ] ].name, "net_repair") ||
491-
strcmp( topo->links[ tile->in_link_id[ CONTACT_IN_IDX ] ].name, "gossip_repai" ) ||
492-
strcmp( topo->links[ tile->in_link_id[ STAKE_IN_IDX ] ].name, "stake_out" ) ||
493-
strcmp( topo->links[ tile->in_link_id[ STORE_IN_IDX ] ].name, "store_repair" ) ||
494-
strcmp( topo->links[ tile->in_link_id[ SIGN_IN_IDX ] ].name, "sign_repair" ) ) ) {
495-
FD_LOG_ERR(( "repair tile has none or unexpected input links %lu %s %s",
496-
tile->in_cnt, topo->links[ tile->in_link_id[ 0 ] ].name, topo->links[ tile->in_link_id[ 1 ] ].name ));
497-
}
479+
if( FD_UNLIKELY( tile->in_cnt > MAX_IN_LINKS ) ) FD_LOG_ERR(( "repair tile has too many input links" ));
480+
481+
uint sign_link_in_idx = UINT_MAX;
482+
for( uint in_idx=0U; in_idx<(tile->in_cnt); in_idx++ ) {
483+
fd_topo_link_t * link = &topo->links[ tile->in_link_id[ in_idx ] ];
484+
if( 0==strcmp( link->name, "net_repair" ) ) {
485+
ctx->in_kind[ in_idx ] = IN_KIND_NET;
486+
fd_net_rx_bounds_init( &ctx->in_links[ in_idx ].net_rx, link->dcache );
487+
continue;
488+
} else if( 0==strcmp( link->name, "gossip_repai" ) ) {
489+
ctx->in_kind[ in_idx ] = IN_KIND_CONTACT;
490+
} else if( 0==strcmp( link->name, "stake_out" ) ) {
491+
ctx->in_kind[ in_idx ] = IN_KIND_STAKE;
492+
} else if( 0==strcmp( link->name, "store_repair" ) ) {
493+
ctx->in_kind[ in_idx ] = IN_KIND_STORE;
494+
} else if( 0==strcmp( link->name, "sign_repair" ) ) {
495+
ctx->in_kind[ in_idx ] = IN_KIND_SIGN;
496+
sign_link_in_idx = in_idx;
497+
} else {
498+
FD_LOG_ERR(( "repair tile has unexpected input link %s", link->name ));
499+
}
498500

499-
if( FD_UNLIKELY( tile->out_cnt != 3 ||
500-
strcmp( topo->links[ tile->out_link_id[ STORE_OUT_IDX ] ].name, "repair_store" ) ||
501-
strcmp( topo->links[ tile->out_link_id[ NET_OUT_IDX ] ].name, "repair_net" ) ||
502-
strcmp( topo->links[ tile->out_link_id[ SIGN_OUT_IDX ] ].name, "repair_sign" ) ) ) {
503-
FD_LOG_ERR(( "repair tile has none or unexpected output links %lu %s %s",
504-
tile->out_cnt, topo->links[ tile->out_link_id[ 0 ] ].name, topo->links[ tile->out_link_id[ 1 ] ].name ));
501+
ctx->in_links[ in_idx ].mem = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
502+
ctx->in_links[ in_idx ].chunk0 = fd_dcache_compact_chunk0( ctx->in_links[ in_idx ].mem, link->dcache );
503+
ctx->in_links[ in_idx ].wmark = fd_dcache_compact_wmark( ctx->in_links[ in_idx ].mem, link->dcache, link->mtu );
504+
ctx->in_links[ in_idx ].mtu = link->mtu;
505505
}
506+
if( FD_UNLIKELY( sign_link_in_idx==UINT_MAX ) ) FD_LOG_ERR(( "Missing sign_repair link" ));
507+
508+
uint sign_link_out_idx = UINT_MAX;
509+
for( uint out_idx=0U; out_idx<(tile->out_cnt); out_idx++ ) {
510+
fd_topo_link_t * link = &topo->links[ tile->out_link_id[ out_idx ] ];
511+
512+
if( 0==strcmp( link->name, "repair_store" ) ) {
513+
514+
if( FD_UNLIKELY( ctx->store_out_mcache ) ) FD_LOG_ERR(( "repair tile has multiple repair_store out links" ));
515+
ctx->store_out_mcache = link->mcache;
516+
ctx->store_out_sync = fd_mcache_seq_laddr( ctx->store_out_mcache );
517+
ctx->store_out_depth = fd_mcache_depth( ctx->store_out_mcache );
518+
ctx->store_out_seq = fd_mcache_seq_query( ctx->store_out_sync );
519+
ctx->store_out_mem = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
520+
ctx->store_out_chunk0 = fd_dcache_compact_chunk0( ctx->store_out_mem, link->dcache );
521+
ctx->store_out_wmark = fd_dcache_compact_wmark( ctx->store_out_mem, link->dcache, link->mtu );
522+
ctx->store_out_chunk = ctx->store_out_chunk0;
523+
524+
} else if( 0==strcmp( link->name, "repair_net" ) ) {
525+
526+
if( FD_UNLIKELY( ctx->net_out_mcache ) ) FD_LOG_ERR(( "repair tile has multiple repair_net out links" ));
527+
ctx->net_out_mcache = link->mcache;
528+
ctx->net_out_sync = fd_mcache_seq_laddr( ctx->net_out_mcache );
529+
ctx->net_out_depth = fd_mcache_depth( ctx->net_out_mcache );
530+
ctx->net_out_seq = fd_mcache_seq_query( ctx->net_out_sync );
531+
ctx->net_out_mem = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
532+
ctx->net_out_chunk0 = fd_dcache_compact_chunk0( ctx->net_out_mem, link->dcache );
533+
ctx->net_out_wmark = fd_dcache_compact_wmark( ctx->net_out_mem, link->dcache, link->mtu );
534+
ctx->net_out_chunk = ctx->net_out_chunk0;
535+
536+
} else if( 0==strcmp( link->name, "repair_sign" ) ) {
537+
538+
sign_link_out_idx = out_idx;
539+
540+
} else {
541+
FD_LOG_ERR(( "gossip tile has unexpected output link %s", link->name ));
542+
}
506543

507-
if( FD_UNLIKELY( !tile->out_cnt ) ) FD_LOG_ERR(( "repair tile has no primary output link" ));
544+
}
545+
if( FD_UNLIKELY( sign_link_out_idx==UINT_MAX ) ) FD_LOG_ERR(( "Missing gossip_sign link" ));
508546

509547
/* Scratch mem setup */
510548

511-
FD_SCRATCH_ALLOC_INIT( l, scratch );
512-
fd_repair_tile_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_repair_tile_ctx_t), sizeof(fd_repair_tile_ctx_t) );
513549
ctx->blockstore = &ctx->blockstore_ljoin;
514550
ctx->repair = FD_SCRATCH_ALLOC_APPEND( l, fd_repair_align(), fd_repair_footprint() );
515551

@@ -536,8 +572,8 @@ unprivileged_init( fd_topo_t * topo,
536572
fd_ip4_udp_hdr_init( ctx->serve_hdr, FD_REPAIR_MAX_PACKET_SIZE, 0, ctx->repair_serve_listen_port );
537573

538574
/* Keyguard setup */
539-
fd_topo_link_t * sign_in = &topo->links[ tile->in_link_id[ SIGN_IN_IDX ] ];
540-
fd_topo_link_t * sign_out = &topo->links[ tile->out_link_id[ SIGN_OUT_IDX ] ];
575+
fd_topo_link_t * sign_in = &topo->links[ tile->in_link_id[ sign_link_in_idx ] ];
576+
fd_topo_link_t * sign_out = &topo->links[ tile->out_link_id[ sign_link_out_idx ] ];
541577
if( fd_keyguard_client_join( fd_keyguard_client_new( ctx->keyguard_client,
542578
sign_out->mcache,
543579
sign_out->dcache,
@@ -558,50 +594,8 @@ unprivileged_init( fd_topo_t * topo,
558594
ctx->blockstore = fd_blockstore_join( &ctx->blockstore_ljoin, fd_topo_obj_laddr( topo, blockstore_obj_id ) );
559595
FD_TEST( ctx->blockstore!=NULL );
560596

561-
fd_topo_link_t * netmux_link = &topo->links[ tile->in_link_id[ 0 ] ];
562-
fd_net_rx_bounds_init( &ctx->net_in_bounds, netmux_link->dcache );
563-
564597
FD_LOG_NOTICE(( "repair starting" ));
565598

566-
fd_topo_link_t * net_out = &topo->links[ tile->out_link_id[ NET_OUT_IDX ] ];
567-
ctx->net_out_mcache = net_out->mcache;
568-
ctx->net_out_sync = fd_mcache_seq_laddr( ctx->net_out_mcache );
569-
ctx->net_out_depth = fd_mcache_depth( ctx->net_out_mcache );
570-
ctx->net_out_seq = fd_mcache_seq_query( ctx->net_out_sync );
571-
ctx->net_out_chunk0 = fd_dcache_compact_chunk0( fd_wksp_containing( net_out->dcache ), net_out->dcache );
572-
ctx->net_out_mem = topo->workspaces[ topo->objs[ net_out->dcache_obj_id ].wksp_id ].wksp;
573-
ctx->net_out_wmark = fd_dcache_compact_wmark( ctx->net_out_mem, net_out->dcache, net_out->mtu );
574-
ctx->net_out_chunk = ctx->net_out_chunk0;
575-
576-
577-
fd_topo_link_t * store_out = &topo->links[ tile->out_link_id[ 0 ] ];
578-
ctx->store_out_mcache = store_out->mcache;
579-
ctx->store_out_sync = fd_mcache_seq_laddr( ctx->store_out_mcache );
580-
ctx->store_out_depth = fd_mcache_depth( ctx->store_out_mcache );
581-
ctx->store_out_seq = fd_mcache_seq_query( ctx->store_out_sync );
582-
ctx->store_out_chunk0 = fd_dcache_compact_chunk0( fd_wksp_containing( store_out->dcache ), store_out->dcache );
583-
ctx->store_out_mem = topo->workspaces[ topo->objs[ store_out->dcache_obj_id ].wksp_id ].wksp;
584-
ctx->store_out_wmark = fd_dcache_compact_wmark( ctx->store_out_mem, store_out->dcache, store_out->mtu );
585-
ctx->store_out_chunk = ctx->store_out_chunk0;
586-
587-
/* Set up contact info tile input */
588-
fd_topo_link_t * contact_in_link = &topo->links[ tile->in_link_id[ CONTACT_IN_IDX ] ];
589-
ctx->contact_in_mem = topo->workspaces[ topo->objs[ contact_in_link->dcache_obj_id ].wksp_id ].wksp;
590-
ctx->contact_in_chunk0 = fd_dcache_compact_chunk0( ctx->contact_in_mem, contact_in_link->dcache );
591-
ctx->contact_in_wmark = fd_dcache_compact_wmark ( ctx->contact_in_mem, contact_in_link->dcache, contact_in_link->mtu );
592-
593-
/* Set up tile stake weight tile input */
594-
fd_topo_link_t * stake_weights_in_link = &topo->links[ tile->in_link_id[ STAKE_IN_IDX ] ];
595-
ctx->stake_weights_in_mem = topo->workspaces[ topo->objs[ stake_weights_in_link->dcache_obj_id ].wksp_id ].wksp;
596-
ctx->stake_weights_in_chunk0 = fd_dcache_compact_chunk0( ctx->stake_weights_in_mem, stake_weights_in_link->dcache );
597-
ctx->stake_weights_in_wmark = fd_dcache_compact_wmark ( ctx->stake_weights_in_mem, stake_weights_in_link->dcache, stake_weights_in_link->mtu );
598-
599-
/* Set up tile repair request input */
600-
fd_topo_link_t * repair_req_in_link = &topo->links[ tile->in_link_id[ STORE_IN_IDX ] ];
601-
ctx->repair_req_in_mem = topo->workspaces[ topo->objs[ repair_req_in_link->dcache_obj_id ].wksp_id ].wksp;
602-
ctx->repair_req_in_chunk0 = fd_dcache_compact_chunk0( ctx->repair_req_in_mem, repair_req_in_link->dcache );
603-
ctx->repair_req_in_wmark = fd_dcache_compact_wmark ( ctx->repair_req_in_mem, repair_req_in_link->dcache, repair_req_in_link->mtu );
604-
605599
/* Repair set up */
606600

607601
ctx->repair = fd_repair_join( fd_repair_new( ctx->repair, ctx->repair_seed ) );

0 commit comments

Comments
 (0)