Repository: valkey-io/valkey
File: src/cluster_legacy.c (C)

Code before:
    node->announce_client_ipv6 = sdsempty();
    node->hostname = sdsempty();
    node->human_nodename = sdsempty();
    node->tcp_port = 0;
    node->cport = 0;
    node->tls_port = 0;
    node->fail_reports = raxNew();
    node->orphaned_time = 0;
    node->repl_offset = 0;
    node->is_node_healthy = 0;
    return node;
}

/* 8 bytes mstime + 8 bytes cluster node */
#define FAILURE_REPORT_ST_KEYLEN 16

/* The expiry time is rounded up (ceiled) to the nearest second in order to bucket
 * timestamps and keep the radix‐tree keys compact.  The resulting timestamp is stored
 * in big‑endian format, followed by the pointer to the clusterNode. */
static void encodeFailureReportKey(unsigned char *buf, mstime_t expiry, clusterNode *node) {
    mstime_t bt = ((expiry + 999) / 1000) * 1000;
    uint64_t be = htonu64((uint64_t)bt);
    memcpy(buf, &be, 8);
    memcpy(buf + 8, &node, sizeof(node));
    if (sizeof(node) == 4) memset(buf + 12, 0, 4);
}

/* Reverses the operation of encodeFailureReportKey, reading back a timestamp
 * and the pointer to the associated clusterNode. */
static void decodeFailureReportKey(unsigned char *buf, mstime_t *expiry_ptr, clusterNode **node_ptr) {
    uint64_t be;
    memcpy(&be, buf, 8);
    *expiry_ptr = (mstime_t)ntohu64(be);
    memcpy(node_ptr, buf + 8, sizeof(*node_ptr));
}

/* This function is called every time we get a failure report from a node.
 * We maintain fail_reports as a radix tree keyed by bucketed expiry timestamp
 * (ceiled to the next second) and the sender pointer, which naturally orders
 * entries by expiry time.
 *
 * 'failing' is the node that is in failure state according to the
 * 'sender' node.
 *
 * The function returns 0 if it just updates a timestamp of an existing
 * failure report from the same sender. 1 is returned if a new failure
 * report is created. */
int clusterNodeAddFailureReport(clusterNode *failing, clusterNode *sender) {
    unsigned char buf[FAILURE_REPORT_ST_KEYLEN];
    mstime_t now = mstime();
    int is_new = 1;

Review comment (bug):
Let's not store the pointer address. It's error prone, what if the node is already freed (via CLUSTER FORGET) while trying to access it back, this will cause a use-after-free crash. Let's use sds api to form the key  `ts + _ + nodename`.

Code after:
    node->announce_client_ipv6 = sdsempty();
    node->hostname = sdsempty();
    node->human_nodename = sdsempty();
    node->tcp_port = 0;
    node->cport = 0;
    node->tls_port = 0;
    node->fail_reports = raxNew();
    node->orphaned_time = 0;
    node->repl_offset = 0;
    node->is_node_healthy = 0;
    return node;
}

/* 8 bytes mstime + 8 bytes cluster node pointer (padded with zeros on 32-bit systems). */
#define FAILURE_REPORT_KEYLEN 16
#define SEC_IN_MS 1000ULL
#define CEIL_OFFSET_MS (SEC_IN_MS - 1)

/* The report time is rounded up (ceiled) to the nearest second in order to bucket
 * timestamps and keep the radix‐tree keys compact.  The resulting timestamp is stored
 * in big‑endian format, followed by the pointer to the clusterNode. */
static void encodeFailureReportKey(clusterNode *node, mstime_t report_time, unsigned char *buf_out) {
    const size_t node_ptr_pad_bytes = (sizeof(clusterNode *) == 4) ? 4 : 0; // pad on 32-bit

    /* Round up to the next second for fewer key splits and quorum grace */
    mstime_t bucketed_time = (report_time / SEC_IN_MS) * SEC_IN_MS + SEC_IN_MS;

    /* Big endian timestamp for numeric time order in rax */
    uint64_t big_endian_time = htonu64((uint64_t)bucketed_time);
    memcpy(buf_out, &big_endian_time, sizeof(uint64_t));

    /* Append the node pointer, plus padding if necessary */
    memcpy(buf_out + sizeof(uint64_t), &node, sizeof(node));
    if (node_ptr_pad_bytes) memset(buf_out + sizeof(uint64_t) + sizeof(node), 0, node_ptr_pad_bytes);
}

/* Reverses the operation of encodeFailureReportKey, reading back a timestamp
 * and the pointer to the associated clusterNode. */
static void decodeFailureReportKey(unsigned char *buf, mstime_t *report_time, clusterNode **node_ptr) {
    uint64_t big_endian_time;
    memcpy(&big_endian_time, buf, sizeof(uint64_t));
    *report_time = (mstime_t)ntohu64(big_endian_time);
    memcpy(node_ptr, buf + sizeof(uint64_t), sizeof(*node_ptr));
}

/* This function is called every time we get a failure report from a node.
 * We maintain fail_reports as a radix tree keyed by bucketed timestamp
 * (ceiled to the next second) and the sender pointer, which naturally orders
 * entries by time.
 *
 * 'failing' is the node that is in failure state according to the