Remove the hand-rolled ad-hoc DynArr and DynSet, replace with dlibc vector_t and set_t
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
#define BALANCE_SHEET_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <dynarr.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <dynarr.h>
|
||||
#include <dlibc/vector.h>
|
||||
#include <block/transaction.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
@@ -27,7 +27,7 @@ typedef struct {
|
||||
|
||||
typedef struct {
|
||||
block_header_t header;
|
||||
DynArr* transactions; // Array of signed_transaction_t, NOTE: Potentially move to a hashmap at some point for quick lookups.
|
||||
vector_t* transactions; // Vector of signed_transaction_t, NOTE: Potentially move to a hashmap at some point for quick lookups.
|
||||
} block_t;
|
||||
|
||||
// PoW validity is chain-relative: it needs the epoch DAG size and seed. chain.h includes this
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#define CHAIN_H
|
||||
|
||||
#include <block/block.h>
|
||||
#include <dynarr.h>
|
||||
#include <dlibc/vector.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
@@ -21,7 +21,9 @@ typedef struct {
|
||||
// Tagged so block.h can forward-declare it: PoW validity depends on the chain (it needs the epoch
|
||||
// seed), but chain.h includes block.h, so the tag is what breaks the cycle.
|
||||
typedef struct blockchain {
|
||||
DynArr* blocks;
|
||||
// Vector of block_t, stored by value. Each element owns its `transactions` vector, which the
|
||||
// element destructor frees whenever a block leaves the chain (rollback, clear, destroy).
|
||||
vector_t* blocks;
|
||||
size_t size;
|
||||
|
||||
/**
|
||||
|
||||
+1141
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,887 @@
|
||||
/*
|
||||
vector.h - Vectors for C
|
||||
Version: 1.0.0
|
||||
|
||||
Description:
|
||||
Header-only library for vectors in C. Memory safe against the mistakes it can
|
||||
detect, but not thread-safe (you must implement your own synchronization
|
||||
mechanisms).
|
||||
Designed for C23 onwards, but should be compatible with older standards (tested
|
||||
from C99 onwards).
|
||||
|
||||
Guarantees:
|
||||
- A vector_t must never be duplicated by copying the struct. Two vector_t values
|
||||
sharing one data pointer both believe they own it, and whichever is destroyed
|
||||
or moved from first leaves the other holding freed memory. Copying the pointer
|
||||
(vector_t* b = a;) is fine - that is one vector with two names, and it must be
|
||||
destroyed exactly once.
|
||||
- To hand a vector's data to another vector, use vector_move(). The source is
|
||||
emptied, freed and its pointer set to NULL, so there is only ever one owner.
|
||||
- To get a second, independent vector with the same contents, use
|
||||
vector_deep_copy(), which copies the data as well as the vector itself. Deep
|
||||
copies are only available for vectors without an element destructor, as a
|
||||
byte-wise copy of owning elements would result in a double free.
|
||||
- No gaps in the raw data array.
|
||||
- When a vector is resized, the data is reallocated to a new memory location,
|
||||
so any pointers into the old data are invalidated. There is no way to
|
||||
preserve them; re-fetch with vector_get() after the resize.
|
||||
- Order of elements is preserved.
|
||||
- It is safe to pass a pointer into a vector's own data as the source element
|
||||
of vector_push_back(), vector_insert() and vector_set(). Each of them handles
|
||||
the aliasing in whichever way suits it, so the value survives the reallocation
|
||||
or the shifting of elements. On a vector with an element destructor this is
|
||||
rejected with -1 instead, as a byte-wise copy would leave two slots owning the
|
||||
same memory. To duplicate an owning element, copy what it owns yourself and
|
||||
push that.
|
||||
- vector_take_at() and vector_take_back() transfer ownership of the element to the
|
||||
caller, and do not call the destructor on it. It is the caller's responsibility
|
||||
to free whatever the element owns.
|
||||
|
||||
Element destructors:
|
||||
A vector may be given an element destructor with vector_set_destructor(). It is
|
||||
called for every element that leaves the vector, i.e. by vector_pop_back(),
|
||||
vector_pop_at(), vector_set() (on the element being overwritten), vector_clear()
|
||||
and vector_destroy().
|
||||
|
||||
The destructor is NOT called by vector_take_back() or vector_take_at(), as these
|
||||
functions transfer ownership of the element to the caller. It is the caller's
|
||||
responsibility to free whatever the element owns.
|
||||
|
||||
The destructor receives a pointer to the element's slot inside the vector's data
|
||||
array - NOT a pointer that was returned by malloc(). It must free whatever the
|
||||
element owns, and must never free the pointer it was handed, as that memory
|
||||
belongs to the vector. For a vector of char* this means:
|
||||
|
||||
void free_str(void* element) { free(*(char**)element); }
|
||||
|
||||
vector_t* vec = vector_create(sizeof(char*));
|
||||
vector_set_destructor(vec, free_str);
|
||||
// ... use the vector ...
|
||||
vector_destroy(&vec); // Takes the address of your pointer, and NULLs it
|
||||
|
||||
A warning regarding misuse:
|
||||
The vector manages its own buffer correctly: no leaks, no double frees of the data
|
||||
array, no use of stale pointers internally. It cannot reason about what your
|
||||
elements own — that is what the destructor is for, and it is your responsibility
|
||||
to set one and to avoid duplicating owned pointers between slots.
|
||||
|
||||
License:
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 DcruBro
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef VECTOR_H
|
||||
#define VECTOR_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef void (*vector_destructor_t)(void* element); // Function pointer type for element destructor
|
||||
|
||||
/*
|
||||
A vector. Do not construct, copy or modify this struct directly - the fields are
|
||||
visible only because the functions below are inline. Create vectors with
|
||||
vector_create() and use the accessors. A vector_t that did not come from
|
||||
vector_create() is not a valid vector, and the functions will reject it where
|
||||
they can and misbehave where they cannot.
|
||||
*/
|
||||
typedef struct {
|
||||
size_t size; // Number of elements in the vector
|
||||
size_t capacity; // Allocated capacity of the vector
|
||||
size_t element_size; // Size of each element in the vector
|
||||
void *data; // Pointer to the raw data array
|
||||
vector_destructor_t destructor; // Function pointer to the element destructor
|
||||
} vector_t;
|
||||
|
||||
/* Forward declarations */
|
||||
static inline int vector_is_aliased(const vector_t* vec, const void* ptr);
|
||||
static inline vector_t* vector_create(size_t element_size);
|
||||
static inline int vector_set_destructor(vector_t* vec, vector_destructor_t destructor);
|
||||
static inline vector_destructor_t vector_get_destructor(const vector_t* vec);
|
||||
static inline int vector_reserve(vector_t* vec, size_t new_capacity);
|
||||
static inline int vector_grow(vector_t* vec);
|
||||
static inline int vector_prune(vector_t* vec);
|
||||
static inline int vector_push_back(vector_t* vec, const void* element);
|
||||
static inline int vector_pop_back(vector_t* vec);
|
||||
static inline int vector_pop_at(vector_t* vec, size_t index);
|
||||
static inline int vector_take_at(vector_t* vec, size_t index, void* out);
|
||||
static inline int vector_take_back(vector_t* vec, void* out);
|
||||
static inline int vector_insert(vector_t* vec, size_t index, const void* element);
|
||||
static inline int vector_clear(vector_t* vec);
|
||||
static inline int vector_set(vector_t* vec, size_t index, const void* element);
|
||||
static inline int vector_is_empty(const vector_t* vec);
|
||||
static inline void* vector_front(vector_t* vec);
|
||||
static inline const void* vector_front_const(const vector_t* vec);
|
||||
static inline void* vector_back(vector_t* vec);
|
||||
static inline const void* vector_back_const(const vector_t* vec);
|
||||
static inline void* vector_get(vector_t* vec, size_t index);
|
||||
static inline const void* vector_get_const(const vector_t* vec, size_t index);
|
||||
static inline size_t vector_size(const vector_t* vec);
|
||||
static inline size_t vector_capacity(const vector_t* vec);
|
||||
static inline size_t vector_element_size(const vector_t* vec);
|
||||
static inline const void* vector_as_c_array(const vector_t* vec);
|
||||
static inline void* vector_as_c_array_mutable(vector_t* vec);
|
||||
static inline int vector_move(vector_t* dest, vector_t** src);
|
||||
static inline vector_t* vector_deep_copy(const vector_t* vec);
|
||||
static inline int vector_destroy(vector_t** vec);
|
||||
|
||||
/*
|
||||
@brief Checks whether a pointer points inside the vector's own data array. Used internally to make the write functions safe against self-referential input.
|
||||
@param vec A pointer to the vector to check against.
|
||||
@param ptr The pointer to check.
|
||||
@return 1 if the pointer lies within the vector's allocated buffer, 0 otherwise.
|
||||
@attention This is an internal helper. You are not expected to call it directly, but it is harmless if you do.
|
||||
*/
|
||||
static inline int vector_is_aliased(const vector_t* vec, const void* ptr) {
|
||||
if (!vec || !ptr || !vec->data) {
|
||||
return 0; // Nothing to alias
|
||||
}
|
||||
|
||||
// Compared as integers rather than pointers, as comparing pointers into
|
||||
// different objects is not well defined.
|
||||
uintptr_t base = (uintptr_t)vec->data;
|
||||
uintptr_t end = base + (vec->capacity * vec->element_size);
|
||||
uintptr_t target = (uintptr_t)ptr;
|
||||
|
||||
return target >= base && target < end;
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Creates a new vector with the specified element size.
|
||||
@param element_size The size of each element in the vector. Call with sizeof(type)
|
||||
@return A pointer to the newly created vector, or NULL if allocation fails. The vector lives on the heap.
|
||||
@attention The vector must be destroyed with vector_destroy() to free its memory. Failing to do so will result in a memory leak.
|
||||
@attention The vector's initial capacity is set to 10. If you want to change the initial capacity globally, set the DLIBC_VECTOR_INITIAL_CAPACITY macro before including this header file. The initial capacity must be greater than 0.
|
||||
@attention The vector's element size must be greater than 0. If you pass 0, the function will return NULL.
|
||||
@attention The vector is created without an element destructor. If your elements own memory of their own, set one with vector_set_destructor().
|
||||
*/
|
||||
static inline vector_t* vector_create(size_t element_size) {
|
||||
if (element_size == 0) {
|
||||
return NULL; // Invalid element size
|
||||
}
|
||||
|
||||
#ifndef DLIBC_VECTOR_INITIAL_CAPACITY
|
||||
size_t initial_capacity = 10; // Default initial capacity
|
||||
#else
|
||||
#if DLIBC_VECTOR_INITIAL_CAPACITY <= 0
|
||||
#error "DLIBC_VECTOR_INITIAL_CAPACITY must be greater than 0"
|
||||
#endif
|
||||
size_t initial_capacity = DLIBC_VECTOR_INITIAL_CAPACITY; // Use the macro if defined
|
||||
#endif
|
||||
|
||||
if (SIZE_MAX / element_size < initial_capacity) {
|
||||
return NULL; // Prevent overflow
|
||||
}
|
||||
|
||||
vector_t* vec = (vector_t*)malloc(sizeof(vector_t));
|
||||
if (!vec) {
|
||||
return NULL; // Allocation failed
|
||||
}
|
||||
|
||||
vec->size = 0;
|
||||
vec->capacity = initial_capacity;
|
||||
vec->element_size = element_size;
|
||||
vec->destructor = NULL; // Initialize destructor to NULL
|
||||
vec->data = malloc(vec->capacity * vec->element_size);
|
||||
if (!vec->data) {
|
||||
free(vec);
|
||||
return NULL; // Allocation failed
|
||||
}
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Sets the destructor function for the vector's elements. This function will be called on each element as it leaves the vector, allowing for custom cleanup of dynamically allocated memory within the elements.
|
||||
@param vec A pointer to the vector for which to set the destructor.
|
||||
@param destructor A function pointer to the destructor function, or NULL to remove the current one. The function should take a single void* parameter, which will be a pointer to the element to be destroyed.
|
||||
@return 0 on success, -1 if the vector is NULL.
|
||||
@attention If you do not set a destructor function, the vector will not automatically free any dynamically allocated memory within its elements when it is destroyed. You must ensure that you free any such memory manually before destroying the vector to avoid memory leaks.
|
||||
@attention The destructor is passed a pointer to the element's slot inside the vector's data array, not a pointer returned by malloc(). It must free what the element owns and must never free the pointer it is given. For a vector of char*, the destructor body is free(*(char**)element);
|
||||
@attention This assumes that your destructor function is valid and safe to call. If it isn't, bad things will happen and it will not be nice to watch.
|
||||
@attention Set the destructor before adding any elements. Setting it on a vector that already holds elements is allowed, but elements that were removed beforehand will not have been destroyed.
|
||||
*/
|
||||
static inline int vector_set_destructor(vector_t* vec, vector_destructor_t destructor) {
|
||||
if (!vec) {
|
||||
return -1; // Invalid vector
|
||||
}
|
||||
|
||||
vec->destructor = destructor;
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Gets the destructor function currently set on the vector.
|
||||
@param vec A pointer to the vector whose destructor is to be retrieved.
|
||||
@return The vector's destructor function pointer, or NULL if the vector is NULL or has no destructor set.
|
||||
*/
|
||||
static inline vector_destructor_t vector_get_destructor(const vector_t* vec) {
|
||||
if (!vec) {
|
||||
return NULL; // Invalid vector
|
||||
}
|
||||
|
||||
return vec->destructor;
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Ensures the vector has room for at least the specified number of elements.
|
||||
@param vec A pointer to the vector for which to reserve space.
|
||||
@param new_capacity The minimum capacity the vector should have.
|
||||
@return 0 on success, -1 if the vector is NULL, its element size is zero, or allocation fails.
|
||||
@attention Capacity never decreases. If the vector already has room, this is a no-op and reports success.
|
||||
@attention If the buffer does grow, it is reallocated, so any pointers into the vector's data are invalidated. Use vector_prune() to release unused memory.
|
||||
*/
|
||||
#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ >= 10
|
||||
// GCC's -fanalyzer loses track of new_data once it is stored through a vector that the caller
|
||||
// reached via a struct member (holder->vec), and reports the grown buffer as leaked when this
|
||||
// returns. The buffer is owned by vec->data and released by vector_destroy(), so the report is a
|
||||
// false positive. Scoped to this function only, so genuine leaks elsewhere are still reported.
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak"
|
||||
#endif
|
||||
static inline int vector_reserve(vector_t* vec, size_t new_capacity) {
|
||||
if (!vec) {
|
||||
return -1; // Invalid vector
|
||||
}
|
||||
|
||||
if (vec->element_size == 0) {
|
||||
return -1; // Not a usable vector; also guards the division below
|
||||
}
|
||||
|
||||
if (new_capacity <= vec->capacity) {
|
||||
return 0; // Already have the room. Never shrink
|
||||
}
|
||||
|
||||
if (new_capacity > SIZE_MAX / vec->element_size) {
|
||||
return -1; // Prevent overflow
|
||||
}
|
||||
|
||||
void* new_data = realloc(vec->data, new_capacity * vec->element_size);
|
||||
if (!new_data) {
|
||||
return -1; // Allocation failed
|
||||
}
|
||||
|
||||
vec->data = new_data;
|
||||
vec->capacity = new_capacity;
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ >= 10
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
/*
|
||||
@brief Grows the vector's capacity to make room for at least one more element. Used internally by the functions that add elements.
|
||||
@param vec A pointer to the vector to grow.
|
||||
@return 0 on success, -1 if the vector is NULL or allocation fails.
|
||||
@attention This is an internal helper. You are not expected to call it directly.
|
||||
*/
|
||||
static inline int vector_grow(vector_t* vec) {
|
||||
if (!vec) {
|
||||
return -1; // Invalid vector
|
||||
}
|
||||
|
||||
if (vec->capacity > SIZE_MAX / 2) {
|
||||
return -1; // Prevent overflow
|
||||
}
|
||||
|
||||
size_t new_capacity = vec->capacity > 0 ? vec->capacity * 2 : 1; // Double the capacity, or set to 1 if it was 0
|
||||
return vector_reserve(vec, new_capacity);
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Prunes the vector to free unused memory. If the vector's size is less than its capacity, this function will reallocate the vector's data array to match its size, freeing any unused memory.
|
||||
@param vec A pointer to the vector to be pruned.
|
||||
@return 0 on success, -1 if the vector is NULL or allocation fails.
|
||||
@attention After calling this function, the vector's capacity will be equal to its size. Any pointers to the old data will be invalidated.
|
||||
@attention Capacity may never drop below 1, even if the vector is empty.
|
||||
*/
|
||||
static inline int vector_prune(vector_t* vec) {
|
||||
if (!vec) {
|
||||
return -1; // Invalid vector
|
||||
}
|
||||
|
||||
if (vec->element_size == 0) {
|
||||
return -1; // Not a usable vector; also guards the division below
|
||||
}
|
||||
|
||||
if (vec->size < vec->capacity) {
|
||||
size_t new_capacity = vec->size > 0 ? vec->size : 1; // Ensure capacity is at least 1
|
||||
|
||||
if (new_capacity > SIZE_MAX / vec->element_size) {
|
||||
return -1; // Prevent overflow
|
||||
}
|
||||
|
||||
void* new_data = realloc(vec->data, new_capacity * vec->element_size);
|
||||
if (!new_data) {
|
||||
return -1; // Allocation failed
|
||||
}
|
||||
|
||||
vec->data = new_data;
|
||||
vec->capacity = new_capacity;
|
||||
}
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Adds an element to the end of the vector.
|
||||
@param vec A pointer to the vector to which the element will be added.
|
||||
@param element A pointer to the element to be added. The element will be copied into the vector's data array.
|
||||
@return 0 on success, -1 if the vector is NULL, the element is NULL, if the element is an owning element that is aliased in the vector and a destructor is set, or if reservation fails.
|
||||
@attention The element must be a pointer to a valid memory location containing data of the same type as the vector's element type. The vector will make a copy of the data, so the original element can be modified or freed after this function returns.
|
||||
@attention The element may point into the vector's own data array. If growing the vector moves the data, the pointer is followed to its new location automatically.
|
||||
*/
|
||||
static inline int vector_push_back(vector_t* vec, const void* element) {
|
||||
if (!vec || !element) {
|
||||
return -1; // Invalid vector or element
|
||||
}
|
||||
|
||||
if (vec->destructor && vector_is_aliased(vec, element)) {
|
||||
return -1; // Refuse to push an owning element that lives inside the vector
|
||||
}
|
||||
|
||||
if (vec->size >= vec->capacity) {
|
||||
// If the element lives inside our own data array, remember where it sits
|
||||
// so that we can find it again after the data has been reallocated.
|
||||
// This must happen before the growth, while the old buffer is still valid
|
||||
size_t offset = 0;
|
||||
int aliased = vector_is_aliased(vec, element);
|
||||
if (aliased) {
|
||||
// Subtracted as integers, for the same reason vector_is_aliased() compares them that
|
||||
// way. Pointer subtraction is only defined within one object, which the check above
|
||||
// guarantees, but a static analyzer cannot see through the integer comparison and
|
||||
// reports the subtraction as undefined behaviour
|
||||
offset = (size_t)((uintptr_t)element - (uintptr_t)vec->data);
|
||||
}
|
||||
|
||||
if (vector_grow(vec) != 0) {
|
||||
return -1; // Failed to grow the vector
|
||||
}
|
||||
|
||||
if (aliased) {
|
||||
element = (const char*)vec->data + offset;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the new element into the vector's data array
|
||||
memmove((char*)vec->data + (vec->size * vec->element_size), element, vec->element_size);
|
||||
vec->size++;
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Removes the last element from the vector.
|
||||
@param vec A pointer to the vector from which the element will be removed.
|
||||
@return 0 on success, -1 if the vector is NULL or empty.
|
||||
@attention After calling this function, the vector's size will be reduced by one. The memory occupied by the removed element will not be freed automatically unless a destructor is set (in which case, the destructor will be called on the element being removed if it is set). Use vector_take_back() to retrieve the last element, remove it from the vector, and NOT call the destructor on it (hands ownership to the caller).
|
||||
*/
|
||||
static inline int vector_pop_back(vector_t* vec) {
|
||||
if (!vec || vec->size == 0) {
|
||||
return -1; // Invalid vector or empty vector
|
||||
}
|
||||
|
||||
if (vec->destructor) {
|
||||
void* element = (char*)vec->data + ((vec->size - 1) * vec->element_size);
|
||||
vec->destructor(element); // Call the destructor for the last element
|
||||
}
|
||||
|
||||
vec->size--;
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Removes the element at the specified index from the vector.
|
||||
@param vec A pointer to the vector from which the element will be removed.
|
||||
@param index The index of the element to be removed.
|
||||
@return 0 on success, -1 if the vector is NULL or the index is out of bounds.
|
||||
@attention After calling this function, the vector's size will be reduced by one. The memory occupied by the removed element will not be freed automatically unless a destructor is set (in which case, the destructor will be called on the element being removed if it is set). Use vector_take_at() to retrieve the element at the specified index, remove it from the vector, and NOT call the destructor on it (hands ownership to the caller).
|
||||
*/
|
||||
static inline int vector_pop_at(vector_t* vec, size_t index) {
|
||||
if (!vec || index >= vec->size) {
|
||||
return -1; // Invalid vector or index out of bounds
|
||||
}
|
||||
|
||||
if (vec->destructor) {
|
||||
void* element = (char*)vec->data + (index * vec->element_size);
|
||||
vec->destructor(element); // Call the destructor for the element to be removed
|
||||
}
|
||||
|
||||
// Move elements after the index one position to the left
|
||||
if (index + 1 < vec->size) {
|
||||
memmove((char*)vec->data + (index * vec->element_size),
|
||||
(char*)vec->data + ((index + 1) * vec->element_size),
|
||||
(vec->size - index - 1) * vec->element_size);
|
||||
}
|
||||
|
||||
vec->size--;
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Removes the element at the specified index and transfers ownership of it to the caller.
|
||||
@param vec A pointer to the vector from which the element will be taken.
|
||||
@param index The index of the element to take.
|
||||
@param out A pointer to a buffer of at least vector_element_size(vec) bytes, which the element is copied into.
|
||||
@return 0 on success, -1 if the vector is NULL, out is NULL, out points into the vector's own data, or the index is out of bounds.
|
||||
@attention The element destructor is deliberately NOT called. Whatever the element owns becomes the caller's responsibility to free.
|
||||
@attention out must not point into the vector's own data array. Doing so would leave two slots owning the same memory and is rejected with -1.
|
||||
*/
|
||||
static inline int vector_take_at(vector_t* vec, size_t index, void* out) {
|
||||
if (!vec || index >= vec->size || !out) {
|
||||
return -1; // Invalid vector, index out of bounds, or output pointer is NULL
|
||||
}
|
||||
|
||||
if (vector_is_aliased(vec, out)) {
|
||||
return -1; // Refuse to take an element into a pointer that lives inside the vector
|
||||
}
|
||||
|
||||
void* slot = (char*)vec->data + (index * vec->element_size);
|
||||
memcpy(out, slot, vec->element_size); // Copy the element to the output pointer
|
||||
|
||||
// Move elements after the index one position to the left
|
||||
if (index + 1 < vec->size) {
|
||||
memmove(slot,
|
||||
(char*)vec->data + ((index + 1) * vec->element_size),
|
||||
(vec->size - index - 1) * vec->element_size);
|
||||
}
|
||||
|
||||
vec->size--;
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Removes the last element from the vector and transfers ownership of it to the caller.
|
||||
@param vec A pointer to the vector from which the element will be taken.
|
||||
@param out A pointer to a buffer of at least vector_element_size(vec) bytes, which the element is copied into.
|
||||
@return 0 on success, -1 if the vector is NULL, empty, if out aliases the vector's data, or out is NULL.
|
||||
@attention The element destructor is deliberately NOT called. Whatever the element owns becomes the caller's responsibility to free.
|
||||
*/
|
||||
static inline int vector_take_back(vector_t* vec, void* out) {
|
||||
if (!vec || vec->size == 0 || !out) {
|
||||
return -1; // Invalid vector, empty vector, or output pointer is NULL
|
||||
}
|
||||
|
||||
return vector_take_at(vec, vec->size - 1, out); // Take the last element
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Inserts an element at the specified index in the vector.
|
||||
@param vec A pointer to the vector into which the element will be inserted.
|
||||
@param index The index at which to insert the element.
|
||||
@param element A pointer to the element to be inserted. The element will be copied into the vector's data array.
|
||||
@return 0 on success, -1 if the vector is NULL, the element is NULL, the index is out of bounds, if the element is an owning element that is aliased in the vector and a destructor is set, or if reservation fails.
|
||||
@attention After calling this function, the vector's size will be increased by one and every element from the index onwards will have moved one position to the right. Any pointers into the vector are invalidated.
|
||||
@attention The element may point into the vector's own data array. Its value is copied aside first, so that neither the reallocation nor the shifting of elements can corrupt it.
|
||||
*/
|
||||
static inline int vector_insert(vector_t* vec, size_t index, const void* element) {
|
||||
if (!vec || !element || index > vec->size) {
|
||||
return -1; // Invalid vector, element, or index out of bounds
|
||||
}
|
||||
|
||||
if (vec->destructor && vector_is_aliased(vec, element)) {
|
||||
return -1; // Refuse to insert an owning element that lives inside the vector
|
||||
}
|
||||
|
||||
// If the element lives inside our own data array, take a copy of it now. Both
|
||||
// the reservation below and the shifting of elements would otherwise destroy
|
||||
// the value before it can be read
|
||||
void* temp = NULL;
|
||||
if (vector_is_aliased(vec, element)) {
|
||||
temp = malloc(vec->element_size);
|
||||
if (!temp) {
|
||||
return -1; // Allocation failed
|
||||
}
|
||||
|
||||
memcpy(temp, element, vec->element_size);
|
||||
element = temp;
|
||||
}
|
||||
|
||||
if (vec->size >= vec->capacity) {
|
||||
if (vector_grow(vec) != 0) {
|
||||
free(temp);
|
||||
return -1; // Failed to grow the vector
|
||||
}
|
||||
}
|
||||
|
||||
// Move elements after the index one position to the right
|
||||
memmove((char*)vec->data + ((index + 1) * vec->element_size),
|
||||
(char*)vec->data + (index * vec->element_size),
|
||||
(vec->size - index) * vec->element_size);
|
||||
|
||||
// Copy the new element into the vector's data array at the specified index
|
||||
memcpy((char*)vec->data + (index * vec->element_size), element, vec->element_size);
|
||||
vec->size++;
|
||||
|
||||
free(temp); // Harmless if no copy was needed, as temp is NULL in that case
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Clears all elements from the vector.
|
||||
@param vec A pointer to the vector to be cleared.
|
||||
@return 0 on success, -1 if the vector is NULL.
|
||||
@attention After calling this function, the vector's size will be zero. The memory occupied by the elements will not be freed automatically unless a destructor is set.
|
||||
@attention The vector's capacity is left untouched, so the vector may be refilled without reallocating. Call vector_prune() afterwards if you want the memory back.
|
||||
*/
|
||||
static inline int vector_clear(vector_t* vec) {
|
||||
if (!vec) {
|
||||
return -1; // Invalid vector
|
||||
}
|
||||
|
||||
if (vec->destructor) {
|
||||
for (size_t i = 0; i < vec->size; ++i) {
|
||||
void* element = (char*)vec->data + (i * vec->element_size);
|
||||
vec->destructor(element); // Call the destructor for each element
|
||||
}
|
||||
}
|
||||
|
||||
vec->size = 0;
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Sets the element at the specified index in the vector, overwriting whatever was there before.
|
||||
@param vec A pointer to the vector in which to set the element.
|
||||
@param index The index of the element to set.
|
||||
@param element A pointer to the element to set. The element will be copied into the vector's data array.
|
||||
@return 0 on success, -1 if the vector is NULL, the element is NULL, if the element is an owning element that is aliased in the vector and a destructor is set, or if the index is out of bounds.
|
||||
@attention This never grows the vector. The index must be below the current size.
|
||||
@attention If a destructor is set, it is called on the element being overwritten before the new value is copied in.
|
||||
@attention Setting an element to itself is a no-op and reports success.
|
||||
@attention On a vector without a destructor, the element may point into the
|
||||
vector's own data array; the copy handles any overlap. On a vector with a
|
||||
destructor this is refused with -1, as it would leave two slots owning the
|
||||
same memory.
|
||||
*/
|
||||
static inline int vector_set(vector_t* vec, size_t index, const void* element) {
|
||||
if (!vec || !element || index >= vec->size) {
|
||||
return -1; // Invalid vector, element, or index out of bounds
|
||||
}
|
||||
|
||||
void* slot = (char*)vec->data + (index * vec->element_size);
|
||||
if (slot == element) {
|
||||
return 0; // Setting the element to itself, no action needed
|
||||
}
|
||||
|
||||
if (vec->destructor && vector_is_aliased(vec, element)) {
|
||||
return -1; // Refuse to set an owning element that lives inside the vector
|
||||
}
|
||||
|
||||
if (vec->destructor) {
|
||||
vec->destructor(slot); // Call the destructor for the element being overwritten
|
||||
}
|
||||
|
||||
memmove(slot, element, vec->element_size); // memmove, as the element may overlap the slot
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Checks if the vector is empty.
|
||||
@param vec A pointer to the vector to check.
|
||||
@return 1 if the vector is empty, 0 if it is not empty.
|
||||
@attention If the vector is NULL, this function will return 1 (is empty) to indicate that the vector is invalid.
|
||||
*/
|
||||
static inline int vector_is_empty(const vector_t* vec) {
|
||||
if (!vec) {
|
||||
return 1; // Invalid vector
|
||||
}
|
||||
|
||||
return vec->size == 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Gets a pointer to the first element in the vector.
|
||||
@param vec A pointer to the vector from which to get the element.
|
||||
@return A pointer to the first element in the vector, or NULL if the vector is NULL or empty.
|
||||
@attention The returned pointer is a generic. You should cast it to the appropriate type before using it. The pointer will become invalid if the vector is resized or destroyed.
|
||||
*/
|
||||
static inline void* vector_front(vector_t* vec) {
|
||||
if (!vec || vec->size == 0) {
|
||||
return NULL; // Invalid vector or empty vector
|
||||
}
|
||||
|
||||
return vec->data;
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Gets a const pointer to the first element in the vector. Cannot be used to modify the element.
|
||||
@param vec A pointer to the vector from which to get the element.
|
||||
@return A constant pointer to the first element in the vector, or NULL if the vector is NULL or empty.
|
||||
@attention The returned pointer is a generic. You should cast it to the appropriate type before using it. The pointer will become invalid if the vector is resized or destroyed.
|
||||
*/
|
||||
static inline const void* vector_front_const(const vector_t* vec) {
|
||||
if (!vec || vec->size == 0) {
|
||||
return NULL; // Invalid vector or empty vector
|
||||
}
|
||||
|
||||
return vec->data;
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Gets a pointer to the last element in the vector.
|
||||
@param vec A pointer to the vector from which to get the element.
|
||||
@return A pointer to the last element in the vector, or NULL if the vector is NULL or empty.
|
||||
@attention The returned pointer is a generic. You should cast it to the appropriate type before using it. The pointer will become invalid if the vector is resized or destroyed.
|
||||
*/
|
||||
static inline void* vector_back(vector_t* vec) {
|
||||
if (!vec || vec->size == 0) {
|
||||
return NULL; // Invalid vector or empty vector
|
||||
}
|
||||
|
||||
return (char*)vec->data + ((vec->size - 1) * vec->element_size);
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Gets a const pointer to the last element in the vector. Cannot be used to modify the element.
|
||||
@param vec A pointer to the vector from which to get the element.
|
||||
@return A constant pointer to the last element in the vector, or NULL if the vector is NULL or empty.
|
||||
@attention The returned pointer is a generic. You should cast it to the appropriate type before using it. The pointer will become invalid if the vector is resized or destroyed.
|
||||
*/
|
||||
static inline const void* vector_back_const(const vector_t* vec) {
|
||||
if (!vec || vec->size == 0) {
|
||||
return NULL; // Invalid vector or empty vector
|
||||
}
|
||||
|
||||
return (const char*)vec->data + ((vec->size - 1) * vec->element_size);
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Gets a pointer to the element at the specified index in the vector.
|
||||
@param vec A pointer to the vector from which to get the element.
|
||||
@param index The index of the element to get.
|
||||
@return A pointer to the element at the specified index, or NULL if the vector is NULL or the index is out of bounds.
|
||||
@attention The returned pointer is a generic. You should cast it to the appropriate type before using it. The pointer will become invalid if the vector is resized or destroyed.
|
||||
@attention For most read-only operations, consider using vector_get_const() instead, which returns a const pointer to the element.
|
||||
*/
|
||||
static inline void* vector_get(vector_t* vec, size_t index) {
|
||||
if (!vec || index >= vec->size) {
|
||||
return NULL; // Invalid vector or index out of bounds
|
||||
}
|
||||
|
||||
return (char*)vec->data + (index * vec->element_size);
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Gets a constant pointer to the element at the specified index in the vector. Cannot be used to modify the element.
|
||||
@param vec A pointer to the vector from which to get the element.
|
||||
@param index The index of the element to get.
|
||||
@return A constant pointer to the element at the specified index, or NULL if the vector is NULL or the index is out of bounds.
|
||||
@attention The returned pointer is a generic. You should cast it to the appropriate type before using it. The pointer will become invalid if the vector is resized or destroyed.
|
||||
*/
|
||||
static inline const void* vector_get_const(const vector_t* vec, size_t index) {
|
||||
if (!vec || index >= vec->size) {
|
||||
return NULL; // Invalid vector or index out of bounds
|
||||
}
|
||||
|
||||
return (const char*)vec->data + (index * vec->element_size);
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Gets the size of the vector.
|
||||
@param vec A pointer to the vector whose size is to be retrieved.
|
||||
@return The size of the vector, or 0 if the vector is NULL.
|
||||
*/
|
||||
static inline size_t vector_size(const vector_t* vec) {
|
||||
if (!vec) {
|
||||
return 0; // Invalid vector
|
||||
}
|
||||
|
||||
return vec->size;
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Gets the capacity of the vector.
|
||||
@param vec A pointer to the vector whose capacity is to be retrieved.
|
||||
@return The capacity of the vector, or 0 if the vector is NULL.
|
||||
*/
|
||||
static inline size_t vector_capacity(const vector_t* vec) {
|
||||
if (!vec) {
|
||||
return 0; // Invalid vector
|
||||
}
|
||||
|
||||
return vec->capacity;
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Gets the size of each element in the vector.
|
||||
@param vec A pointer to the vector whose element size is to be retrieved.
|
||||
@return The size of each element in the vector, or 0 if the vector is NULL.
|
||||
*/
|
||||
static inline size_t vector_element_size(const vector_t* vec) {
|
||||
if (!vec) {
|
||||
return 0; // Invalid vector
|
||||
}
|
||||
|
||||
return vec->element_size;
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Gets a pointer to the underlying C array of the vector. Cannot modify the vector through this pointer.
|
||||
@param vec A pointer to the vector whose underlying array is to be retrieved.
|
||||
@return A pointer to the underlying C array, or NULL if the vector is NULL.
|
||||
@attention The returned pointer is a generic. You should cast it to the appropriate type before using it. The pointer will become invalid if the vector is resized or destroyed.
|
||||
*/
|
||||
static inline const void* vector_as_c_array(const vector_t* vec) {
|
||||
if (!vec) {
|
||||
return NULL; // Invalid vector
|
||||
}
|
||||
|
||||
return vec->data;
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Gets a pointer to the underlying C array of the vector. Can be used to modify the vector's elements.
|
||||
@param vec A pointer to the vector whose underlying array is to be retrieved.
|
||||
@return A pointer to the underlying C array, or NULL if the vector is NULL.
|
||||
@attention The returned pointer is a generic. You should cast it to the appropriate type before using it. The pointer will become invalid if the vector is resized or destroyed.
|
||||
*/
|
||||
static inline void* vector_as_c_array_mutable(vector_t* vec) {
|
||||
if (!vec) {
|
||||
return NULL; // Invalid vector
|
||||
}
|
||||
|
||||
return vec->data;
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Moves a vector to another vector, transferring ownership of the data. The destination vector will take ownership of the source vector's data, its element size and its destructor.
|
||||
@param dest A pointer to the destination vector.
|
||||
@param src A pointer to the pointer holding the source vector. It will be set to NULL.
|
||||
@return 0 on success, -1 if the destination vector is NULL, the source pointer is NULL, the source vector is NULL, or both vectors share the same data pointer.
|
||||
@attention After calling this function, the source vector will be freed (excluding data) and the source pointer will be set to NULL, so it cannot be used again.
|
||||
@attention The destination vector's existing elements will be destroyed (using the destination's own destructor, if it has one) and its data freed. Ensure that you do not need the existing data before calling this function.
|
||||
@attention Moving a vector onto itself is a no-op and reports success, leaving the vector untouched.
|
||||
@attention Two vectors can only share a data pointer if a vector_t was duplicated by copying the struct, which is never valid. The move is refused rather than freeing a buffer the source still points at.
|
||||
*/
|
||||
static inline int vector_move(vector_t* dest, vector_t** src) {
|
||||
if (!dest || !src || !*src) {
|
||||
return -1; // Invalid vectors
|
||||
}
|
||||
|
||||
if (dest == *src) {
|
||||
return 0; // Moving to itself, no action needed
|
||||
}
|
||||
|
||||
if (dest->data == (*src)->data) {
|
||||
return -1; // Refuse to move a vector onto another vector that shares its data
|
||||
// Realistically, this is unreachable via legal use, but... safety.
|
||||
// Or something. You WILL trigger this if you do shallow copies. DO NOT!
|
||||
}
|
||||
|
||||
// Destroy the destination vector's existing elements with its own destructor,
|
||||
// then free its data, as it is about to be replaced
|
||||
vector_clear(dest);
|
||||
free(dest->data);
|
||||
|
||||
// Transfer ownership of the source vector's data to the destination vector
|
||||
dest->size = (*src)->size;
|
||||
dest->capacity = (*src)->capacity;
|
||||
dest->element_size = (*src)->element_size;
|
||||
dest->data = (*src)->data;
|
||||
dest->destructor = (*src)->destructor; // The elements keep the cleanup they came with
|
||||
|
||||
// Reset the source vector to an empty state
|
||||
(*src)->size = 0;
|
||||
(*src)->capacity = 0;
|
||||
(*src)->element_size = 0;
|
||||
(*src)->data = NULL;
|
||||
(*src)->destructor = NULL;
|
||||
|
||||
free(*src); // Free the source vector structure, but not its data (ownership transferred)
|
||||
*src = NULL;
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Creates a deep copy of the vector, including its data.
|
||||
@param vec A pointer to the vector to be copied.
|
||||
@return A pointer to the newly created deep copy of the vector, or NULL if allocation fails, if the input vector is NULL, or if the input vector has a destructor set.
|
||||
@attention The returned vector must be destroyed with vector_destroy() to free its memory. Failing to do so will result in a memory leak.
|
||||
@attention Vectors with a destructor cannot be deep copied and this function will return NULL for them. The elements are copied byte for byte, so any memory they own would end up owned by both vectors and freed twice. If you need to copy such a vector, do it by hand: create a new vector and push copies of the elements into it yourself.
|
||||
*/
|
||||
static inline vector_t* vector_deep_copy(const vector_t* vec) {
|
||||
if (!vec) {
|
||||
return NULL; // Invalid vector
|
||||
}
|
||||
|
||||
if (vec->destructor) {
|
||||
return NULL; // Refuse to byte-copy elements that own memory
|
||||
}
|
||||
|
||||
vector_t* new_vec = vector_create(vec->element_size);
|
||||
if (!new_vec) {
|
||||
return NULL; // Allocation failed
|
||||
}
|
||||
|
||||
int r = vector_reserve(new_vec, vec->capacity);
|
||||
if (r != 0) {
|
||||
vector_destroy(&new_vec);
|
||||
return NULL; // Allocation failed
|
||||
}
|
||||
|
||||
if (vec->size > 0) {
|
||||
memcpy(new_vec->data, vec->data, vec->size * vec->element_size);
|
||||
new_vec->size = vec->size;
|
||||
} // vector_create() already sets size to 0 by default
|
||||
|
||||
return new_vec;
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Destroys the vector and frees its memory.
|
||||
@param vec A pointer to the pointer holding the vector to be destroyed. It will be set to NULL.
|
||||
@return 0 on success, or if the vector was already NULL. -1 only if the pointer itself is NULL.
|
||||
@attention After calling this function, the vector pointer should not be used again (It will be set to NULL). Accessing it after destruction will lead to undefined behavior.
|
||||
@attention Only the pointer you pass in is set to NULL. Copies of that pointer held elsewhere are left dangling and must not be used.
|
||||
@attention If stored elements own memory of their own, set a destructor with vector_set_destructor() to have it cleaned up here. Otherwise, the vector will only free the memory allocated for the data array and the vector structure itself, but not any dynamically allocated memory within the elements.
|
||||
*/
|
||||
static inline int vector_destroy(vector_t** vec) {
|
||||
if (!vec) {
|
||||
return -1; // Invalid pointer
|
||||
}
|
||||
|
||||
if (!(*vec)) {
|
||||
return 0; // Already NULL, nothing to destroy
|
||||
}
|
||||
|
||||
vector_t* target = *vec;
|
||||
|
||||
if (target->destructor) {
|
||||
for (size_t i = 0; i < target->size; ++i) {
|
||||
void* element = (char*)target->data + (i * target->element_size);
|
||||
target->destructor(element); // Call the destructor for each element
|
||||
}
|
||||
}
|
||||
|
||||
free(target->data);
|
||||
free(target);
|
||||
*vec = NULL; // Set the pointer to NULL to avoid dangling references
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
#endif // VECTOR_H
|
||||
@@ -1,63 +0,0 @@
|
||||
#ifndef DYNARR_H
|
||||
#define DYNARR_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define DYNARR_MAX_CAPACITY ((size_t)0x7FFFFFFF)
|
||||
|
||||
typedef struct {
|
||||
size_t size;
|
||||
size_t elemSize;
|
||||
size_t capacity;
|
||||
void* data;
|
||||
} DynArr;
|
||||
|
||||
// Do not use; Use DYNARR_CREATE macro instead.
|
||||
DynArr* DynArr_create(size_t elemSize, size_t capacity);
|
||||
|
||||
// Reserve n blocks in arary; New size will be n, NOT size + n; Reserving less memory that current will fail, use prune instead.
|
||||
void DynArr_reserve(DynArr* p, size_t n);
|
||||
|
||||
// Push data into a new block at the end of the array
|
||||
void* DynArr_push_back(DynArr* p, void* value);
|
||||
|
||||
// Remove the last block in the array.
|
||||
void DynArr_pop_back(DynArr* p);
|
||||
|
||||
// Remove first block from array.
|
||||
void DynArr_pop_front(DynArr* p);
|
||||
|
||||
// Remove index from array. This moves all blocks after the index block.
|
||||
void DynArr_remove(DynArr* p, size_t index);
|
||||
|
||||
// Erase the array. This will not free unused blocks.
|
||||
void DynArr_erase(DynArr* p);
|
||||
|
||||
// Prune and free unused blocks. If pruning to zero, ensure to reserve after.
|
||||
void DynArr_prune(DynArr* p);
|
||||
|
||||
// Get a pointer to a block by index
|
||||
void* DynArr_at(DynArr* p, size_t index);
|
||||
|
||||
// Get the index by block pointer
|
||||
size_t DynArr_at_ptr(DynArr* p, void* ptr);
|
||||
|
||||
// Get size
|
||||
size_t DynArr_size(DynArr* p);
|
||||
|
||||
// Get element size
|
||||
size_t DynArr_elemSize(DynArr* p);
|
||||
|
||||
// Get capacity
|
||||
size_t DynArr_capacity(DynArr* p);
|
||||
|
||||
void DynArr_destroy(DynArr* p);
|
||||
|
||||
// Note: Make sure to not overread or overwrite
|
||||
void* DynArr_c_arr(DynArr* p);
|
||||
|
||||
#define DYNARR_CREATE(T, initialCapacity) DynArr_create(sizeof(T), initialCapacity)
|
||||
|
||||
#endif
|
||||
@@ -1,20 +0,0 @@
|
||||
#ifndef DYNSET_H
|
||||
#define DYNSET_H
|
||||
|
||||
#include <dynarr.h>
|
||||
|
||||
// Dynamic Set structure - basically DynArr with uniqueness enforced
|
||||
typedef struct {
|
||||
DynArr* arr;
|
||||
} DynSet;
|
||||
|
||||
// Function prototypes
|
||||
DynSet* DynSet_Create(size_t elemSize);
|
||||
void DynSet_Destroy(DynSet* set);
|
||||
int DynSet_Insert(DynSet* set, const void* element);
|
||||
int DynSet_Contains(DynSet* set, const void* element);
|
||||
size_t DynSet_Size(DynSet* set);
|
||||
void* DynSet_Get(DynSet* set, size_t index);
|
||||
void DynSet_Remove(DynSet* set, const void* element);
|
||||
|
||||
#endif
|
||||
@@ -17,8 +17,8 @@ typedef struct node_discovery node_discovery_t;
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <dynarr.h>
|
||||
#include <dynset.h>
|
||||
#include <dlibc/vector.h>
|
||||
#include <dlibc/set.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
@@ -32,7 +32,7 @@ typedef struct {
|
||||
tcp_client_t outboundClients[MAX_CONS];
|
||||
size_t outboundCount;
|
||||
// Dedup cache for recently seen block hashes (canonical 32-byte hash)
|
||||
DynSet* seenBlocks;
|
||||
set_t* seenBlocks;
|
||||
// Protects seenBlocks
|
||||
pthread_mutex_t seenLock;
|
||||
// Protects outboundClients snapshots and peerBlockHeight writes
|
||||
|
||||
+22
-34
@@ -133,7 +133,7 @@ block_t* Block_Create() {
|
||||
return NULL;
|
||||
}
|
||||
memset(&block->header, 0, sizeof(block_header_t));
|
||||
block->transactions = DYNARR_CREATE(signed_transaction_t, 1);
|
||||
block->transactions = vector_create(sizeof(signed_transaction_t));
|
||||
if (!block->transactions) {
|
||||
free(block);
|
||||
return NULL;
|
||||
@@ -160,13 +160,13 @@ void Block_CalculateMerkleRoot(const block_t* block, uint8_t* outHash) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t txCount = DynArr_size(block->transactions);
|
||||
const size_t txCount = vector_size(block->transactions);
|
||||
if (txCount == 0) {
|
||||
memset(outHash, 0, 32);
|
||||
return;
|
||||
}
|
||||
if (txCount == 1) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, 0);
|
||||
signed_transaction_t* tx = (signed_transaction_t*)vector_get(block->transactions, 0);
|
||||
Transaction_CalculateHash(tx, outHash);
|
||||
return;
|
||||
}
|
||||
@@ -181,7 +181,7 @@ void Block_CalculateMerkleRoot(const block_t* block, uint8_t* outHash) {
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < txCount; ++i) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i);
|
||||
signed_transaction_t* tx = (signed_transaction_t*)vector_get(block->transactions, i);
|
||||
if (!tx) {
|
||||
free(current);
|
||||
free(next);
|
||||
@@ -223,7 +223,7 @@ void Block_AddTransaction(block_t* block, signed_transaction_t* tx) {
|
||||
return;
|
||||
}
|
||||
|
||||
DynArr_push_back(block->transactions, tx);
|
||||
vector_push_back(block->transactions, tx);
|
||||
}
|
||||
|
||||
void Block_RemoveTransaction(block_t* block, uint8_t* txHash) {
|
||||
@@ -231,12 +231,12 @@ void Block_RemoveTransaction(block_t* block, uint8_t* txHash) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < DynArr_size(block->transactions); i++) {
|
||||
signed_transaction_t* currentTx = (signed_transaction_t*)DynArr_at(block->transactions, i);
|
||||
for (size_t i = 0; i < vector_size(block->transactions); i++) {
|
||||
signed_transaction_t* currentTx = (signed_transaction_t*)vector_get(block->transactions, i);
|
||||
uint8_t currentTxHash[32];
|
||||
Transaction_CalculateHash(currentTx, currentTxHash);
|
||||
if (memcmp(currentTxHash, txHash, 32) == 0) {
|
||||
DynArr_remove(block->transactions, i);
|
||||
vector_pop_at(block->transactions, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -311,8 +311,8 @@ bool Block_AllTransactionsValid(const block_t* block) {
|
||||
|
||||
bool hasCoinbase = false;
|
||||
|
||||
for (size_t i = 0; i < DynArr_size(block->transactions); i++) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i);
|
||||
for (size_t i = 0; i < vector_size(block->transactions); i++) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)vector_get(block->transactions, i);
|
||||
if (!Transaction_Verify(tx)) {
|
||||
return false;
|
||||
}
|
||||
@@ -326,7 +326,7 @@ bool Block_AllTransactionsValid(const block_t* block) {
|
||||
}
|
||||
}
|
||||
|
||||
return true && hasCoinbase && DynArr_size(block->transactions) > 0; // Every block must have at least one transaction (the coinbase)
|
||||
return true && hasCoinbase && vector_size(block->transactions) > 0; // Every block must have at least one transaction (the coinbase)
|
||||
}
|
||||
|
||||
bool Block_ValidateCoinbaseAndFees(const block_t* block, uint64_t expectedCoinbaseAmount, uint64_t* outTotalFees) {
|
||||
@@ -338,8 +338,8 @@ bool Block_ValidateCoinbaseAndFees(const block_t* block, uint64_t expectedCoinba
|
||||
uint64_t totalFees = 0;
|
||||
uint8_t zeroAddress[32] = {0};
|
||||
|
||||
for (size_t i = 0; i < DynArr_size(block->transactions); ++i) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i);
|
||||
for (size_t i = 0; i < vector_size(block->transactions); ++i) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)vector_get(block->transactions, i);
|
||||
if (!tx) {
|
||||
return false;
|
||||
}
|
||||
@@ -408,7 +408,7 @@ bool Block_HasValidStructure(const block_t* block) {
|
||||
|
||||
return Block_HasValidVote(block) &&
|
||||
Block_AllTransactionsValid(block) &&
|
||||
DynArr_size(block->transactions) > 0;
|
||||
vector_size(block->transactions) > 0;
|
||||
}
|
||||
|
||||
bool Block_IsFullyValid(const block_t* block, blockchain_t* chain) {
|
||||
@@ -417,7 +417,7 @@ bool Block_IsFullyValid(const block_t* block, blockchain_t* chain) {
|
||||
|
||||
void Block_Destroy(block_t* block) {
|
||||
if (!block) return;
|
||||
DynArr_destroy(block->transactions);
|
||||
vector_destroy(&block->transactions);
|
||||
free(block);
|
||||
}
|
||||
|
||||
@@ -440,9 +440,9 @@ void Block_Print(const block_t* block) {
|
||||
}
|
||||
printf("\n");
|
||||
if (block->transactions) {
|
||||
printf("Transactions (%zu):\n", DynArr_size(block->transactions));
|
||||
for (size_t i = 0; i < DynArr_size(block->transactions); i++) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i);
|
||||
printf("Transactions (%zu):\n", vector_size(block->transactions));
|
||||
for (size_t i = 0; i < vector_size(block->transactions); i++) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)vector_get(block->transactions, i);
|
||||
if (tx) {
|
||||
printf(" Tx #%zu: 1: %llu -> %02x%02x...%02x%02x, fee %llu\n 2: %llu -> %02x%02x...%02x%02x, fee %llu\n",
|
||||
i,
|
||||
@@ -470,7 +470,7 @@ void Block_ShortPrint(const block_t* block) {
|
||||
block->header.version,
|
||||
block->header.prevHash[0], block->header.prevHash[1], block->header.prevHash[30], block->header.prevHash[31],
|
||||
block->header.merkleRoot[0], block->header.merkleRoot[1], block->header.merkleRoot[30], block->header.merkleRoot[31],
|
||||
block->transactions ? DynArr_size(block->transactions) : 0);
|
||||
vector_size(block->transactions));
|
||||
}
|
||||
|
||||
block_t* Block_Copy(const block_t* src) {
|
||||
@@ -479,25 +479,13 @@ block_t* Block_Copy(const block_t* src) {
|
||||
if (!dst) return NULL;
|
||||
dst->header = src->header;
|
||||
if (src->transactions) {
|
||||
size_t txCount = DynArr_size(src->transactions);
|
||||
dst->transactions = DYNARR_CREATE(signed_transaction_t, txCount == 0 ? 1 : txCount);
|
||||
// Transactions own no memory of their own, so the vector has no destructor and a byte-wise
|
||||
// deep copy is exactly an independent copy of the list.
|
||||
dst->transactions = vector_deep_copy(src->transactions);
|
||||
if (!dst->transactions) {
|
||||
free(dst);
|
||||
return NULL;
|
||||
}
|
||||
for (size_t i = 0; i < txCount; ++i) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(src->transactions, i);
|
||||
if (!tx) {
|
||||
DynArr_destroy(dst->transactions);
|
||||
free(dst);
|
||||
return NULL;
|
||||
}
|
||||
if (!DynArr_push_back(dst->transactions, tx)) {
|
||||
DynArr_destroy(dst->transactions);
|
||||
free(dst);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dst->transactions = NULL;
|
||||
}
|
||||
|
||||
+109
-101
@@ -133,7 +133,7 @@ static bool Chain_BorrowBlockTransactions(blockchain_t* chain, size_t index, blo
|
||||
*outBlock = NULL;
|
||||
*outLoadedFromDisk = false;
|
||||
|
||||
block_t* blk = (block_t*)DynArr_at(chain->blocks, index);
|
||||
block_t* blk = (block_t*)vector_get(chain->blocks, index);
|
||||
if (blk && blk->transactions) {
|
||||
*outBlock = blk;
|
||||
return true;
|
||||
@@ -155,9 +155,7 @@ static void Chain_ReturnBlockTransactions(block_t* blk, bool loadedFromDisk) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (blk->transactions) {
|
||||
DynArr_destroy(blk->transactions);
|
||||
}
|
||||
vector_destroy(&blk->transactions);
|
||||
free(blk);
|
||||
}
|
||||
|
||||
@@ -174,8 +172,8 @@ bool Chain_RecomputeRuntimeState(blockchain_t* chain) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < DynArr_size(blk->transactions); ++j) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(blk->transactions, j);
|
||||
for (size_t j = 0; j < vector_size(blk->transactions); ++j) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)vector_get(blk->transactions, j);
|
||||
if (!tx) {
|
||||
Chain_ReturnBlockTransactions(blk, loadedFromDisk);
|
||||
return false;
|
||||
@@ -219,20 +217,24 @@ static void Chain_InvalidateDagEpochs(blockchain_t* chain) {
|
||||
pthread_mutex_unlock(&chain->dagCacheLock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Element destructor for `chain->blocks`.
|
||||
*
|
||||
* A block stored in the chain owns its transaction vector, so the vector is freed whenever the
|
||||
* block leaves the chain -- rollback, clear or destroy -- without every one of those paths having
|
||||
* to remember to do it. Header-only blocks carry NULL here, which vector_destroy() accepts.
|
||||
**/
|
||||
static void Chain_BlockElementDestructor(void* element) {
|
||||
block_t* blk = (block_t*)element;
|
||||
vector_destroy(&blk->transactions);
|
||||
}
|
||||
|
||||
static void Chain_ClearBlocks(blockchain_t* chain) {
|
||||
if (!chain || !chain->blocks) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < DynArr_size(chain->blocks); i++) {
|
||||
block_t* blk = (block_t*)DynArr_at(chain->blocks, i);
|
||||
if (blk && blk->transactions) {
|
||||
DynArr_destroy(blk->transactions);
|
||||
blk->transactions = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
DynArr_erase(chain->blocks);
|
||||
vector_clear(chain->blocks); // The element destructor frees each block's transactions
|
||||
chain->size = 0;
|
||||
Chain_InvalidateDagEpochs(chain);
|
||||
}
|
||||
@@ -243,14 +245,19 @@ blockchain_t* Chain_Create() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ptr->blocks = DYNARR_CREATE(block_t, 1);
|
||||
ptr->blocks = vector_create(sizeof(block_t));
|
||||
if (!ptr->blocks) {
|
||||
free(ptr);
|
||||
return NULL;
|
||||
}
|
||||
vector_set_destructor(ptr->blocks, Chain_BlockElementDestructor);
|
||||
ptr->size = 0;
|
||||
|
||||
ptr->dagEpochs = NULL;
|
||||
ptr->dagEpochsComputed = 0;
|
||||
ptr->dagEpochsCapacity = 0;
|
||||
if (pthread_mutex_init(&ptr->dagCacheLock, NULL) != 0) {
|
||||
DynArr_destroy(ptr->blocks);
|
||||
vector_destroy(&ptr->blocks);
|
||||
free(ptr);
|
||||
return NULL;
|
||||
}
|
||||
@@ -262,7 +269,7 @@ void Chain_Destroy(blockchain_t* chain) {
|
||||
if (chain) {
|
||||
if (chain->blocks) {
|
||||
Chain_ClearBlocks(chain);
|
||||
DynArr_destroy(chain->blocks);
|
||||
vector_destroy(&chain->blocks);
|
||||
}
|
||||
free(chain->dagEpochs);
|
||||
pthread_mutex_destroy(&chain->dagCacheLock);
|
||||
@@ -287,7 +294,7 @@ static bool Chain_AddBlockLocked(blockchain_t* chain, block_t* block) {
|
||||
}
|
||||
|
||||
// Ensure the incoming block's header.blockNumber matches the index it will be appended at.
|
||||
size_t expectedIndex = DynArr_size(chain->blocks);
|
||||
size_t expectedIndex = vector_size(chain->blocks);
|
||||
if (block->header.blockNumber != expectedIndex) {
|
||||
// Mismatched block number; reject to avoid duplicate indices or inconsistent headers.
|
||||
return false;
|
||||
@@ -296,7 +303,7 @@ static bool Chain_AddBlockLocked(blockchain_t* chain, block_t* block) {
|
||||
// Ensure the block actually builds on our tip. Without this, a rollback-then-reapply path can
|
||||
// splice blocks from two different forks into a chain that no longer links up.
|
||||
if (expectedIndex > 0) {
|
||||
block_t* parent = (block_t*)DynArr_at(chain->blocks, expectedIndex - 1);
|
||||
block_t* parent = (block_t*)vector_get(chain->blocks, expectedIndex - 1);
|
||||
if (!parent) {
|
||||
return false;
|
||||
}
|
||||
@@ -351,7 +358,13 @@ static bool Chain_AddBlockLocked(blockchain_t* chain, block_t* block) {
|
||||
}
|
||||
|
||||
do {
|
||||
size_t txCount = DynArr_size(block->transactions);
|
||||
size_t txCount = vector_size(block->transactions);
|
||||
if (txCount == 0) {
|
||||
// Every block carries at least its coinbase, so an empty one fails the coinbase check
|
||||
// below regardless. Rejecting it here also keeps calloc() from being asked for zero bytes.
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
signed_transaction_t* candidateTxs = (signed_transaction_t*)calloc(txCount, sizeof(signed_transaction_t));
|
||||
if (!candidateTxs) {
|
||||
ok = false;
|
||||
@@ -360,7 +373,7 @@ static bool Chain_AddBlockLocked(blockchain_t* chain, block_t* block) {
|
||||
|
||||
size_t nonCoinbaseCount = 0;
|
||||
for (size_t i = 0; i < txCount; ++i) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i);
|
||||
signed_transaction_t* tx = (signed_transaction_t*)vector_get(block->transactions, i);
|
||||
if (!tx) {
|
||||
ok = false;
|
||||
break;
|
||||
@@ -405,8 +418,8 @@ static bool Chain_AddBlockLocked(blockchain_t* chain, block_t* block) {
|
||||
// Debug: log expected coinbase and fees to aid diagnosis when nodes disagree
|
||||
{
|
||||
uint64_t cbAmount = 0;
|
||||
if (block->transactions && DynArr_size(block->transactions) > 0) {
|
||||
signed_transaction_t* firstTx = (signed_transaction_t*)DynArr_at(block->transactions, 0);
|
||||
if (block->transactions && vector_size(block->transactions) > 0) {
|
||||
signed_transaction_t* firstTx = (signed_transaction_t*)vector_get(block->transactions, 0);
|
||||
if (firstTx && Address_IsCoinbase(firstTx->transaction.senderAddress)) {
|
||||
cbAmount = firstTx->transaction.amount1;
|
||||
}
|
||||
@@ -462,37 +475,37 @@ static bool Chain_AddBlockLocked(blockchain_t* chain, block_t* block) {
|
||||
}
|
||||
|
||||
// Push the block only after validation succeeds.
|
||||
block_t* blk = (block_t*)DynArr_push_back(chain->blocks, block);
|
||||
if (!blk) { ok = false; break; }
|
||||
if (vector_push_back(chain->blocks, block) != 0) { ok = false; break; }
|
||||
block_t* blk = (block_t*)vector_back(chain->blocks);
|
||||
stored = blk;
|
||||
chain->size++;
|
||||
currentBlockHeight = (uint64_t)(chain->size - 1);
|
||||
|
||||
/**
|
||||
* The chain now owns the transaction array, so clear the CALLER's pointer to it.
|
||||
* The chain now owns the transaction vector, so clear the CALLER's pointer to it.
|
||||
*
|
||||
* DynArr_push_back stores the struct by value, which leaves the chain's element and the
|
||||
* vector_push_back stores the struct by value, which leaves the chain's element and the
|
||||
* caller's block_t sharing one `transactions` pointer. Three separate places later free
|
||||
* that array through the chain's copy -- Chain_ClearBlocks, Chain_RollbackToHeightLocked
|
||||
* and Chain_SaveToFile -- and each of them NULLs only the chain's side, leaving the
|
||||
* caller holding a dangling pointer. Whether a caller must then use free() or
|
||||
* Block_Destroy() was a convention carried in comments at every call site plus a
|
||||
* consumed-count passed around; getting it wrong aborted in the allocator.
|
||||
* that vector through the chain's copy -- Chain_ClearBlocks and Chain_RollbackToHeightLocked
|
||||
* via the element destructor, and Chain_SaveToFile -- and each of them NULLs only the
|
||||
* chain's side, leaving the caller holding a dangling pointer. Whether a caller must then
|
||||
* use free() or Block_Destroy() was a convention carried in comments at every call site
|
||||
* plus a consumed-count passed around; getting it wrong aborted in the allocator.
|
||||
*
|
||||
* Clearing it here makes the rule structural: free(wrapper) and Block_Destroy(wrapper)
|
||||
* are now equivalent and both safe, because DynArr_destroy(NULL) is a no-op.
|
||||
* are now equivalent and both safe, because vector_destroy() on a NULL vector is a no-op.
|
||||
*
|
||||
* This runs right after the push and NOT at the end on success, deliberately. If the
|
||||
* ledger pass below fails we return false with the block still in the chain, so a caller
|
||||
* that destroys its wrapper on failure would otherwise free the chain's array.
|
||||
* that destroys its wrapper on failure would otherwise free the chain's vector.
|
||||
**/
|
||||
block->transactions = NULL;
|
||||
|
||||
// Second pass: apply the ledger changes.
|
||||
if (blk->transactions) {
|
||||
txCount = DynArr_size(blk->transactions);
|
||||
if (blk && blk->transactions) {
|
||||
txCount = vector_size(blk->transactions);
|
||||
for (size_t i = 0; i < txCount; ++i) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(blk->transactions, i);
|
||||
signed_transaction_t* tx = (signed_transaction_t*)vector_get(blk->transactions, i);
|
||||
if (!tx) {
|
||||
continue;
|
||||
}
|
||||
@@ -527,9 +540,9 @@ static bool Chain_AddBlockLocked(blockchain_t* chain, block_t* block) {
|
||||
}
|
||||
|
||||
// Remove mined non-coinbase transactions from the mempool so they are not re-mined or re-broadcast.
|
||||
if (blk->transactions) {
|
||||
for (size_t i = 0; i < DynArr_size(blk->transactions); ++i) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(blk->transactions, i);
|
||||
if (blk && blk->transactions) {
|
||||
for (size_t i = 0; i < vector_size(blk->transactions); ++i) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)vector_get(blk->transactions, i);
|
||||
if (!tx) continue;
|
||||
if (Address_IsCoinbase(tx->transaction.senderAddress)) continue;
|
||||
uint8_t txHash[32];
|
||||
@@ -589,7 +602,7 @@ block_t* Chain_GetBlock(blockchain_t* chain, size_t index) {
|
||||
if (!chain) return NULL;
|
||||
block_t* blk = NULL;
|
||||
pthread_rwlock_rdlock(&chainLock);
|
||||
blk = (block_t*)DynArr_at(chain->blocks, index);
|
||||
blk = (block_t*)vector_get(chain->blocks, index);
|
||||
pthread_rwlock_unlock(&chainLock);
|
||||
return blk;
|
||||
}
|
||||
@@ -598,7 +611,7 @@ bool Chain_GetBlockCopy(blockchain_t* chain, size_t index, block_t** outCopy) {
|
||||
if (!chain || !outCopy) return false;
|
||||
*outCopy = NULL;
|
||||
pthread_rwlock_rdlock(&chainLock);
|
||||
block_t* src = (block_t*)DynArr_at(chain->blocks, index);
|
||||
block_t* src = (block_t*)vector_get(chain->blocks, index);
|
||||
if (!src) {
|
||||
pthread_rwlock_unlock(&chainLock);
|
||||
return false;
|
||||
@@ -614,7 +627,7 @@ size_t Chain_Size(blockchain_t* chain) {
|
||||
if (!chain) return 0;
|
||||
size_t sz = 0;
|
||||
pthread_rwlock_rdlock(&chainLock);
|
||||
sz = DynArr_size(chain->blocks);
|
||||
sz = vector_size(chain->blocks);
|
||||
pthread_rwlock_unlock(&chainLock);
|
||||
return sz;
|
||||
}
|
||||
@@ -624,14 +637,14 @@ bool Chain_IsValid(blockchain_t* chain) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t chainSize = DynArr_size(chain->blocks);
|
||||
const size_t chainSize = vector_size(chain->blocks);
|
||||
if (chainSize == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (size_t i = 1; i < chainSize; i++) {
|
||||
block_t* blk = (block_t*)DynArr_at(chain->blocks, i);
|
||||
block_t* prevBlk = (block_t*)DynArr_at(chain->blocks, i - 1);
|
||||
block_t* blk = (block_t*)vector_get(chain->blocks, i);
|
||||
block_t* prevBlk = (block_t*)vector_get(chain->blocks, i - 1);
|
||||
if (!blk || !prevBlk || blk->header.blockNumber != i) { return false; } // NULL blocks or blockNumber != order in chain
|
||||
|
||||
// Verify prevHash is valid
|
||||
@@ -649,7 +662,7 @@ bool Chain_IsValid(blockchain_t* chain) {
|
||||
}
|
||||
|
||||
// Genesis needs special handling because the prevHash is always invalid (no previous block)
|
||||
block_t* genesis = (block_t*)DynArr_at(chain->blocks, 0);
|
||||
block_t* genesis = (block_t*)vector_get(chain->blocks, 0);
|
||||
if (!genesis || genesis->header.blockNumber != 0) { return false; }
|
||||
|
||||
return true;
|
||||
@@ -664,7 +677,7 @@ bool Chain_IsValid(blockchain_t* chain) {
|
||||
static bool Chain_RollbackToHeightLocked(blockchain_t* chain, size_t height) {
|
||||
if (!chain || !chain->blocks) return false;
|
||||
|
||||
size_t cur = DynArr_size(chain->blocks);
|
||||
size_t cur = vector_size(chain->blocks);
|
||||
if (height >= cur) {
|
||||
return true; // nothing to do
|
||||
}
|
||||
@@ -702,9 +715,9 @@ static bool Chain_RollbackToHeightLocked(blockchain_t* chain, size_t height) {
|
||||
}
|
||||
|
||||
if (source && source->transactions) {
|
||||
const size_t txCount = DynArr_size(source->transactions);
|
||||
const size_t txCount = vector_size(source->transactions);
|
||||
for (size_t t = 0; t < txCount; ++t) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(source->transactions, t);
|
||||
signed_transaction_t* tx = (signed_transaction_t*)vector_get(source->transactions, t);
|
||||
if (!tx || Address_IsCoinbase(tx->transaction.senderAddress)) {
|
||||
continue;
|
||||
}
|
||||
@@ -715,18 +728,12 @@ static bool Chain_RollbackToHeightLocked(blockchain_t* chain, size_t height) {
|
||||
Chain_ReturnBlockTransactions(source, loadedFromDisk);
|
||||
}
|
||||
|
||||
// Remove blocks above height
|
||||
// Remove blocks above height. The element destructor frees each one's transactions.
|
||||
for (size_t i = cur; i > height; --i) {
|
||||
size_t idx = i - 1;
|
||||
block_t* blk = (block_t*)DynArr_at(chain->blocks, idx);
|
||||
if (blk && blk->transactions) {
|
||||
DynArr_destroy(blk->transactions);
|
||||
blk->transactions = NULL;
|
||||
}
|
||||
DynArr_remove(chain->blocks, idx);
|
||||
vector_pop_at(chain->blocks, i - 1);
|
||||
}
|
||||
|
||||
chain->size = DynArr_size(chain->blocks);
|
||||
chain->size = vector_size(chain->blocks);
|
||||
currentBlockHeight = chain->size ? (uint64_t)(chain->size - 1) : 0ULL;
|
||||
|
||||
// Blocks below the old tip are gone, so the DAG recurrence folded from their votes is stale.
|
||||
@@ -752,9 +759,9 @@ static bool Chain_RollbackToHeightLocked(blockchain_t* chain, size_t height) {
|
||||
|
||||
// Apply transactions
|
||||
if (toProcess && toProcess->transactions) {
|
||||
size_t txCount = DynArr_size(toProcess->transactions);
|
||||
size_t txCount = vector_size(toProcess->transactions);
|
||||
for (size_t ti = 0; ti < txCount; ++ti) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(toProcess->transactions, ti);
|
||||
signed_transaction_t* tx = (signed_transaction_t*)vector_get(toProcess->transactions, ti);
|
||||
if (!tx) continue;
|
||||
|
||||
// Coinbase credit
|
||||
@@ -863,7 +870,7 @@ static uint64_t Chain_MedianTimePastLocked(blockchain_t* chain) {
|
||||
return 0ULL;
|
||||
}
|
||||
|
||||
const size_t size = DynArr_size(chain->blocks);
|
||||
const size_t size = vector_size(chain->blocks);
|
||||
if (size == 0) {
|
||||
return 0ULL;
|
||||
}
|
||||
@@ -872,7 +879,7 @@ static uint64_t Chain_MedianTimePastLocked(blockchain_t* chain) {
|
||||
uint64_t samples[MEDIAN_TIME_SPAN];
|
||||
size_t taken = 0;
|
||||
for (size_t i = 0; i < span; ++i) {
|
||||
block_t* blk = (block_t*)DynArr_at(chain->blocks, size - 1 - i);
|
||||
block_t* blk = (block_t*)vector_get(chain->blocks, size - 1 - i);
|
||||
if (!blk) {
|
||||
continue;
|
||||
}
|
||||
@@ -892,7 +899,7 @@ bool Chain_IsInitialBlockDownload(blockchain_t* chain) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (DynArr_size(chain->blocks) == 0) {
|
||||
if (vector_size(chain->blocks) == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -916,7 +923,7 @@ static bool Chain_BranchIsLinkedLocked(blockchain_t* chain, size_t forkHeight, b
|
||||
return false;
|
||||
}
|
||||
|
||||
block_t* parent = (block_t*)DynArr_at(chain->blocks, forkHeight - 1);
|
||||
block_t* parent = (block_t*)vector_get(chain->blocks, forkHeight - 1);
|
||||
if (!parent) {
|
||||
return false;
|
||||
}
|
||||
@@ -967,10 +974,10 @@ bool Chain_BlockRespectsSenderOrdering(const block_t* block) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t txCount = DynArr_size(block->transactions);
|
||||
const size_t txCount = vector_size(block->transactions);
|
||||
|
||||
for (size_t i = 0; i < txCount; ++i) {
|
||||
const signed_transaction_t* tx = (const signed_transaction_t*)DynArr_at(block->transactions, i);
|
||||
const signed_transaction_t* tx = (const signed_transaction_t*)vector_get_const(block->transactions, i);
|
||||
if (!tx || Address_IsCoinbase(tx->transaction.senderAddress)) {
|
||||
continue; // coinbase is exempt -- see balance_sheet.h
|
||||
}
|
||||
@@ -994,7 +1001,7 @@ bool Chain_BlockRespectsSenderOrdering(const block_t* block) {
|
||||
* worth a per-sender map if they grow.
|
||||
**/
|
||||
for (size_t j = 0; j < i; ++j) {
|
||||
const signed_transaction_t* prev = (const signed_transaction_t*)DynArr_at(block->transactions, j);
|
||||
const signed_transaction_t* prev = (const signed_transaction_t*)vector_get_const(block->transactions, j);
|
||||
if (!prev || Address_IsCoinbase(prev->transaction.senderAddress)) {
|
||||
continue;
|
||||
}
|
||||
@@ -1038,7 +1045,7 @@ bool Chain_ReplaceBranch(blockchain_t* chain,
|
||||
size_t candidateApplied = 0; // how far the apply got, for the log line below
|
||||
|
||||
do {
|
||||
const size_t tipCount = DynArr_size(chain->blocks);
|
||||
const size_t tipCount = vector_size(chain->blocks);
|
||||
if (forkHeight > tipCount) {
|
||||
printf("Chain_ReplaceBranch: fork point %zu is beyond our tip %zu; nothing to replace\n",
|
||||
forkHeight, tipCount);
|
||||
@@ -1302,10 +1309,10 @@ bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t curren
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t chainSize = DynArr_size(chain->blocks);
|
||||
const size_t chainSize = vector_size(chain->blocks);
|
||||
uint64_t byteCount = 0;
|
||||
for (size_t i = 0; i < chainSize; ++i) {
|
||||
block_t* blk = (block_t*)DynArr_at(chain->blocks, i);
|
||||
block_t* blk = (block_t*)vector_get(chain->blocks, i);
|
||||
if (!blk) {
|
||||
fclose(metaFile);
|
||||
fclose(chainFile);
|
||||
@@ -1349,7 +1356,7 @@ bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t curren
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t txSize = DynArr_size(diskCopy->transactions);
|
||||
const size_t txSize = vector_size(diskCopy->transactions);
|
||||
if (fwrite(&txSize, sizeof(size_t), 1, chainFile) != 1) {
|
||||
if (loadedTemp) Block_Destroy(diskCopy);
|
||||
fclose(metaFile);
|
||||
@@ -1364,7 +1371,7 @@ bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t curren
|
||||
byteCount += sizeof(block_header_t) + sizeof(size_t);
|
||||
|
||||
for (size_t j = 0; j < txSize; ++j) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(diskCopy->transactions, j);
|
||||
signed_transaction_t* tx = (signed_transaction_t*)vector_get(diskCopy->transactions, j);
|
||||
if (!tx || fwrite(tx, sizeof(signed_transaction_t), 1, chainFile) != 1) {
|
||||
if (loadedTemp) Block_Destroy(diskCopy);
|
||||
fclose(metaFile);
|
||||
@@ -1397,9 +1404,8 @@ bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t curren
|
||||
|
||||
if (loadedTemp) {
|
||||
Block_Destroy(diskCopy);
|
||||
} else if (blk->transactions) {
|
||||
DynArr_destroy(blk->transactions);
|
||||
blk->transactions = NULL;
|
||||
} else {
|
||||
vector_destroy(&blk->transactions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1407,8 +1413,8 @@ bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t curren
|
||||
fseek(metaFile, 0, SEEK_SET);
|
||||
fwrite(&newSize, sizeof(size_t), 1, metaFile);
|
||||
uint32_t difficultyTarget = INITIAL_DIFFICULTY;
|
||||
if (newSize > 0) {
|
||||
block_t* lastBlock = (block_t*)DynArr_at(chain->blocks, newSize - 1);
|
||||
block_t* lastBlock = (block_t*)vector_back(chain->blocks); // NULL exactly when the chain is empty
|
||||
if (lastBlock) {
|
||||
uint8_t lastHash[32];
|
||||
Block_CalculateHash(lastBlock, lastHash);
|
||||
fwrite(lastHash, sizeof(uint8_t), 32, metaFile);
|
||||
@@ -1560,10 +1566,11 @@ bool Chain_LoadFromFile(blockchain_t* chain, const char* dirpath, uint256_t* out
|
||||
return false;
|
||||
}
|
||||
if (loadTransactions) {
|
||||
blk->transactions = DYNARR_CREATE(signed_transaction_t, txSize == 0 ? 1 : txSize);
|
||||
if (!blk->transactions) {
|
||||
blk->transactions = vector_create(sizeof(signed_transaction_t));
|
||||
if (!blk->transactions || vector_reserve(blk->transactions, txSize) != 0) {
|
||||
fclose(chainFile);
|
||||
fclose(tableFile);
|
||||
vector_destroy(&blk->transactions);
|
||||
free(blk);
|
||||
return false;
|
||||
}
|
||||
@@ -1573,15 +1580,15 @@ bool Chain_LoadFromFile(blockchain_t* chain, const char* dirpath, uint256_t* out
|
||||
if (fread(&tx, sizeof(signed_transaction_t), 1, chainFile) != 1) {
|
||||
fclose(chainFile);
|
||||
fclose(tableFile);
|
||||
DynArr_destroy(blk->transactions);
|
||||
vector_destroy(&blk->transactions);
|
||||
free(blk);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!DynArr_push_back(blk->transactions, &tx)) {
|
||||
if (vector_push_back(blk->transactions, &tx) != 0) {
|
||||
fclose(chainFile);
|
||||
fclose(tableFile);
|
||||
DynArr_destroy(blk->transactions);
|
||||
vector_destroy(&blk->transactions);
|
||||
free(blk);
|
||||
return false;
|
||||
}
|
||||
@@ -1598,15 +1605,15 @@ bool Chain_LoadFromFile(blockchain_t* chain, const char* dirpath, uint256_t* out
|
||||
|
||||
// Loading from disk currently restores headers only. Do not run Chain_AddBlock,
|
||||
// because it enforces transaction presence and mutates balances.
|
||||
if (!DynArr_push_back(chain->blocks, blk)) {
|
||||
if (vector_push_back(chain->blocks, blk) != 0) {
|
||||
fclose(chainFile);
|
||||
fclose(tableFile);
|
||||
free(blk);
|
||||
Block_Destroy(blk); // never stored, so its transactions are still ours to free
|
||||
return false;
|
||||
}
|
||||
chain->size++;
|
||||
|
||||
// DynArr_push_back stores blocks by value, so the copied block now owns
|
||||
// vector_push_back stores blocks by value, so the chain's element now owns
|
||||
// blk->transactions. Free wrapper only.
|
||||
free(blk);
|
||||
}
|
||||
@@ -1733,8 +1740,9 @@ bool Chain_LoadBlockFromFile(const char* dirpath, uint64_t blockNumber, bool loa
|
||||
}
|
||||
|
||||
if (loadTransactions) {
|
||||
blk->transactions = DYNARR_CREATE(signed_transaction_t, txSize == 0 ? 1 : txSize);
|
||||
if (!blk->transactions) {
|
||||
blk->transactions = vector_create(sizeof(signed_transaction_t));
|
||||
if (!blk->transactions || vector_reserve(blk->transactions, txSize) != 0) {
|
||||
vector_destroy(&blk->transactions);
|
||||
free(blk);
|
||||
fclose(chainFile);
|
||||
fclose(tableFile);
|
||||
@@ -1744,15 +1752,15 @@ bool Chain_LoadBlockFromFile(const char* dirpath, uint64_t blockNumber, bool loa
|
||||
for (size_t i = 0; i < txSize; ++i) {
|
||||
signed_transaction_t tx;
|
||||
if (fread(&tx, sizeof(signed_transaction_t), 1, chainFile) != 1) {
|
||||
DynArr_destroy(blk->transactions);
|
||||
vector_destroy(&blk->transactions);
|
||||
free(blk);
|
||||
fclose(chainFile);
|
||||
fclose(tableFile);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!DynArr_push_back(blk->transactions, &tx)) {
|
||||
DynArr_destroy(blk->transactions);
|
||||
if (vector_push_back(blk->transactions, &tx) != 0) {
|
||||
vector_destroy(&blk->transactions);
|
||||
free(blk);
|
||||
fclose(chainFile);
|
||||
fclose(tableFile);
|
||||
@@ -1801,13 +1809,13 @@ uint32_t Chain_ComputeTargetAtHeight(blockchain_t* chain, uint64_t height, uint3
|
||||
return INITIAL_DIFFICULTY;
|
||||
}
|
||||
|
||||
if (height > (uint64_t)DynArr_size(chain->blocks)) {
|
||||
if (height > (uint64_t)vector_size(chain->blocks)) {
|
||||
return 0x00; // Retarget window is not fully present in this chain
|
||||
}
|
||||
|
||||
// Assuming block validation validates timestamps, we can assume they're valid and can just read them
|
||||
block_t* lastBlock = (block_t*)DynArr_at(chain->blocks, (size_t)(height - 1));
|
||||
block_t* adjustmentBlock = (block_t*)DynArr_at(chain->blocks, (size_t)(height - DIFFICULTY_ADJUSTMENT_INTERVAL));
|
||||
block_t* lastBlock = (block_t*)vector_get(chain->blocks, (size_t)(height - 1));
|
||||
block_t* adjustmentBlock = (block_t*)vector_get(chain->blocks, (size_t)(height - DIFFICULTY_ADJUSTMENT_INTERVAL));
|
||||
if (!lastBlock || !adjustmentBlock) {
|
||||
return 0x00; // Impossible difficulty, only valid hash is all zeros (practically impossible)
|
||||
}
|
||||
@@ -1932,13 +1940,13 @@ bool Chain_ComputeWorkRange(blockchain_t* chain, size_t from, size_t to, uint256
|
||||
return false;
|
||||
}
|
||||
|
||||
if (to > DynArr_size(chain->blocks)) {
|
||||
if (to > vector_size(chain->blocks)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint256_t total = uint256_from_u64(0);
|
||||
for (size_t i = from; i < to; ++i) {
|
||||
block_t* blk = (block_t*)DynArr_at(chain->blocks, i);
|
||||
block_t* blk = (block_t*)vector_get(chain->blocks, i);
|
||||
if (!blk) {
|
||||
return false;
|
||||
}
|
||||
@@ -2032,7 +2040,7 @@ static bool Chain_ExtendDagEpochsLocked(blockchain_t* chain, size_t epochIndex)
|
||||
chain->dagEpochsComputed = 1;
|
||||
}
|
||||
|
||||
const size_t chainSize = DynArr_size(chain->blocks);
|
||||
const size_t chainSize = vector_size(chain->blocks);
|
||||
const size_t epochLength = (size_t)EPOCH_LENGTH;
|
||||
|
||||
for (size_t k = chain->dagEpochsComputed; k <= epochIndex; ++k) {
|
||||
@@ -2048,7 +2056,7 @@ static bool Chain_ExtendDagEpochsLocked(blockchain_t* chain, size_t epochIndex)
|
||||
uint64_t holdVotes = 0;
|
||||
uint64_t downVotes = 0;
|
||||
for (size_t i = from; i < to; ++i) {
|
||||
const block_t* blk = (const block_t*)DynArr_at(chain->blocks, i);
|
||||
const block_t* blk = (const block_t*)vector_get_const(chain->blocks, i);
|
||||
if (!blk) {
|
||||
return false;
|
||||
}
|
||||
@@ -2114,11 +2122,11 @@ static bool Chain_EpochDagSeedForHeightLocked(blockchain_t* chain, uint64_t bloc
|
||||
}
|
||||
|
||||
const uint64_t seedBlockNumber = (epochIndex * (uint64_t)EPOCH_LENGTH) - 1ULL;
|
||||
if (seedBlockNumber >= (uint64_t)DynArr_size(chain->blocks)) {
|
||||
if (seedBlockNumber >= (uint64_t)vector_size(chain->blocks)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const block_t* seedBlock = (const block_t*)DynArr_at(chain->blocks, (size_t)seedBlockNumber);
|
||||
const block_t* seedBlock = (const block_t*)vector_get_const(chain->blocks, (size_t)seedBlockNumber);
|
||||
if (!seedBlock) {
|
||||
return false;
|
||||
}
|
||||
|
||||
-179
@@ -1,179 +0,0 @@
|
||||
#include <dynarr.h>
|
||||
|
||||
DynArr* DynArr_create(size_t elemSize, size_t capacity) {
|
||||
DynArr* p = (DynArr*)malloc(sizeof(DynArr));
|
||||
if (!p) return NULL;
|
||||
|
||||
p->elemSize = elemSize;
|
||||
p->capacity = capacity;
|
||||
p->data = malloc(elemSize * capacity);
|
||||
if (!p->data) {
|
||||
free(p);
|
||||
return NULL;
|
||||
}
|
||||
p->size = 0;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
// Reserve n blocks in arary; New size will be n, NOT size + n; Reserving less memory that current will fail, use prune instead.
|
||||
void DynArr_reserve(DynArr* p, size_t n) {
|
||||
if (n <= p->capacity) {
|
||||
printf("reserve ignored; attempted to reserve less or equal to current capacity\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (n > DYNARR_MAX_CAPACITY) {
|
||||
printf("reserve ignored; attempted to reserve over 32 bits\n");
|
||||
return;
|
||||
}
|
||||
|
||||
void* new_data = realloc(p->data, n * p->elemSize);
|
||||
if (!new_data) {
|
||||
printf("reserve failed\n");
|
||||
exit(1);
|
||||
}
|
||||
p->data = new_data;
|
||||
p->capacity = n;
|
||||
}
|
||||
|
||||
// Push data into a new block at the end of the array; If value is NULL, the new block will be zeroed.
|
||||
void* DynArr_push_back(DynArr* p, void* value) {
|
||||
//if (value == NULL) {
|
||||
// printf("push_back ignored; value is null");
|
||||
// return NULL;
|
||||
//}
|
||||
|
||||
if (p->size >= p->capacity) {
|
||||
size_t new_cap = (p->capacity == 0) ? 1 : p->capacity * 2;
|
||||
|
||||
if (new_cap < p->capacity || new_cap > DYNARR_MAX_CAPACITY) {
|
||||
printf("push_back ignored; capacity overflow\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void* new_data = realloc(p->data, new_cap * p->elemSize);
|
||||
if (!new_data) {
|
||||
printf("push failed\n");
|
||||
exit(1);
|
||||
}
|
||||
p->capacity = new_cap;
|
||||
p->data = new_data;
|
||||
}
|
||||
|
||||
void* dst = (void*)((char*)p->data + (p->size * p->elemSize));
|
||||
|
||||
if (value == NULL) {
|
||||
memset(dst, 0, p->elemSize); // Handle NULL value.
|
||||
} else {
|
||||
memcpy((char*)dst, value, p->elemSize);
|
||||
}
|
||||
|
||||
p->size++;
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
// Remove the last block in the array.
|
||||
void DynArr_pop_back(DynArr* p) {
|
||||
if (p->size == 0) {
|
||||
printf("pop_back ignored; size is 0\n");
|
||||
return;
|
||||
}
|
||||
|
||||
p->size--; // Will automatically overwrite that memory naturally
|
||||
}
|
||||
|
||||
// Remove first block from array.
|
||||
void DynArr_pop_front(DynArr* p) {
|
||||
if (p->size == 0) {
|
||||
printf("pop_front ignored; size is 0\n");
|
||||
return;
|
||||
}
|
||||
|
||||
memmove(
|
||||
(char*)p->data,
|
||||
(char*)p->data + p->elemSize,
|
||||
(p->size - 1) * p->elemSize
|
||||
);
|
||||
|
||||
p->size--;
|
||||
}
|
||||
|
||||
// Remove index from array. This moves all blocks after the index block.
|
||||
void DynArr_remove(DynArr* p, size_t index) {
|
||||
if (index >= p->size) return;
|
||||
|
||||
memmove(
|
||||
(char*)p->data + (index * p->elemSize),
|
||||
(char*)p->data + (index + 1) * p->elemSize,
|
||||
(p->size - index - 1) * p->elemSize
|
||||
);
|
||||
|
||||
p->size--;
|
||||
}
|
||||
|
||||
// Erase the array. This will not free unused blocks.
|
||||
void DynArr_erase(DynArr* p) {
|
||||
p->size = 0;
|
||||
}
|
||||
|
||||
// Prune and free unused blocks. If pruning to zero, ensure to reserve after.
|
||||
void DynArr_prune(DynArr* p) {
|
||||
void* new_data = realloc(p->data, (p->size == 0 ? 1 : p->size) * p->elemSize);
|
||||
if (!new_data) {
|
||||
printf("pruning failed\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
p->data = new_data;
|
||||
p->capacity = p->size;
|
||||
}
|
||||
|
||||
// Get a pointer to a block by index
|
||||
void* DynArr_at(DynArr* p, size_t index) {
|
||||
if (index >= p->size) return NULL;
|
||||
return (char*)p->data + (index * p->elemSize);
|
||||
}
|
||||
|
||||
// Get the index by block pointer
|
||||
size_t DynArr_at_ptr(DynArr* p, void* ptr) {
|
||||
if (!p || !ptr) {
|
||||
printf("invalid pointer\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < p->size; i++) {
|
||||
if ((void*)(((char*)p->data) + (i * p->elemSize)) == ptr) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// If for some reason the array has 2^64 elements in it, then fuck it, I guess we'll just crash, I don't care.
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get size
|
||||
size_t DynArr_size(DynArr* p) {
|
||||
return p->size;
|
||||
}
|
||||
|
||||
// Get element size
|
||||
size_t DynArr_elemSize(DynArr* p) {
|
||||
return p->elemSize;
|
||||
}
|
||||
|
||||
// Get capacity
|
||||
size_t DynArr_capacity(DynArr* p) {
|
||||
return p->capacity;
|
||||
}
|
||||
|
||||
void DynArr_destroy(DynArr* p) {
|
||||
if (!p) return;
|
||||
free(p->data);
|
||||
free(p);
|
||||
}
|
||||
|
||||
void* DynArr_c_arr(DynArr* p) {
|
||||
return p->data;
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
#include <dynset.h>
|
||||
|
||||
DynSet* DynSet_Create(size_t elemSize) {
|
||||
DynSet* set = (DynSet*)malloc(sizeof(DynSet));
|
||||
if (!set) {
|
||||
return NULL;
|
||||
}
|
||||
set->arr = DynArr_create(elemSize, 1);
|
||||
if (!set->arr) {
|
||||
free(set);
|
||||
return NULL;
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
void DynSet_Destroy(DynSet* set) {
|
||||
if (set) {
|
||||
DynArr_destroy(set->arr);
|
||||
free(set);
|
||||
}
|
||||
}
|
||||
|
||||
int DynSet_Insert(DynSet* set, const void* element) {
|
||||
if (DynSet_Contains(set, element)) {
|
||||
return 0; // Element already exists
|
||||
}
|
||||
return DynArr_push_back(set->arr, element) != NULL;
|
||||
}
|
||||
|
||||
int DynSet_Contains(DynSet* set, const void* element) {
|
||||
size_t size = DynArr_size(set->arr);
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
void* current = DynArr_at(set->arr, i);
|
||||
if (memcmp(current, element, set->arr->elemSize) == 0) {
|
||||
return 1; // Found
|
||||
}
|
||||
}
|
||||
return 0; // Not found
|
||||
}
|
||||
|
||||
size_t DynSet_Size(DynSet* set) {
|
||||
return DynArr_size(set->arr);
|
||||
}
|
||||
|
||||
void* DynSet_Get(DynSet* set, size_t index) {
|
||||
return DynArr_at(set->arr, index);
|
||||
}
|
||||
|
||||
void DynSet_Remove(DynSet* set, const void* element) {
|
||||
size_t size = DynArr_size(set->arr);
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
void* current = DynArr_at(set->arr, i);
|
||||
if (memcmp(current, element, set->arr->elemSize) == 0) {
|
||||
DynArr_remove(set->arr, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-9
@@ -390,8 +390,8 @@ static bool Block_GetCoinbaseAndFeeTotals(const block_t* block, uint64_t* outCoi
|
||||
uint64_t coinbaseAmount = 0;
|
||||
uint64_t totalFees = 0;
|
||||
|
||||
for (size_t i = 0; i < DynArr_size(block->transactions); ++i) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i);
|
||||
for (size_t i = 0; i < vector_size(block->transactions); ++i) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)vector_get(block->transactions, i);
|
||||
if (!tx) {
|
||||
return false;
|
||||
}
|
||||
@@ -549,8 +549,8 @@ static bool MineAndAppendBlock(blockchain_t* chain,
|
||||
// Read the coinbase BEFORE handing the block to the chain. Chain_AddBlock takes ownership of
|
||||
// the transaction array and clears our pointer to it, so this has to happen first.
|
||||
uint64_t coinbaseAmount = 0;
|
||||
if (block->transactions && DynArr_size(block->transactions) > 0) {
|
||||
signed_transaction_t* firstTx = (signed_transaction_t*)DynArr_at(block->transactions, 0);
|
||||
if (block->transactions && vector_size(block->transactions) > 0) {
|
||||
signed_transaction_t* firstTx = (signed_transaction_t*)vector_get(block->transactions, 0);
|
||||
if (firstTx && Address_IsCoinbase(firstTx->transaction.senderAddress)) {
|
||||
coinbaseAmount = firstTx->transaction.amount1;
|
||||
}
|
||||
@@ -745,17 +745,20 @@ static bool VerifyChainFully(blockchain_t* chain) {
|
||||
|
||||
// Transactions are persisted on disk. Once this block is fully verified,
|
||||
// release its in-memory transaction list to reduce peak memory usage.
|
||||
if (blk->transactions) {
|
||||
DynArr_destroy(blk->transactions);
|
||||
blk->transactions = NULL;
|
||||
}
|
||||
vector_destroy(&blk->transactions);
|
||||
|
||||
// Push a header-only copy of this block into prevChain for future difficulty calculations.
|
||||
// A missing header would shift every later reward calculation, so a failed push fails the
|
||||
// verification rather than being skipped.
|
||||
block_t headerOnly;
|
||||
memset(&headerOnly, 0, sizeof(headerOnly));
|
||||
headerOnly.header = blk->header;
|
||||
headerOnly.transactions = NULL;
|
||||
(void)DynArr_push_back(prevChain->blocks, &headerOnly);
|
||||
if (vector_push_back(prevChain->blocks, &headerOnly) != 0) {
|
||||
Block_Destroy(blk);
|
||||
Chain_Destroy(prevChain);
|
||||
return false;
|
||||
}
|
||||
|
||||
(void)uint256_add_u64(&replaySupply, coinbaseAmount);
|
||||
|
||||
|
||||
+33
-20
@@ -513,20 +513,32 @@ static node_block_accept_result_t Node_ParseAndAcceptBlock(const unsigned char*
|
||||
memcpy(&txCount, payload + offset, sizeof(txCount));
|
||||
offset += sizeof(txCount);
|
||||
|
||||
blk->transactions = DYNARR_CREATE(signed_transaction_t, txCount == 0 ? 1 : (size_t)txCount);
|
||||
if (!blk->transactions) { free(blk); return NODE_BLOCK_REJECTED; }
|
||||
// The count is peer-supplied, so never size an allocation from more transactions than the
|
||||
// payload can actually carry. Such a block fails the length check in the loop below anyway;
|
||||
// rejecting it here just stops a peer from making us reserve memory for it first.
|
||||
if (txCount > (payloadLen - offset) / sizeof(signed_transaction_t)) {
|
||||
free(blk);
|
||||
return NODE_BLOCK_REJECTED;
|
||||
}
|
||||
|
||||
blk->transactions = vector_create(sizeof(signed_transaction_t));
|
||||
if (!blk->transactions || vector_reserve(blk->transactions, (size_t)txCount) != 0) {
|
||||
vector_destroy(&blk->transactions);
|
||||
free(blk);
|
||||
return NODE_BLOCK_REJECTED;
|
||||
}
|
||||
|
||||
for (uint64_t i = 0; i < txCount; ++i) {
|
||||
if (offset + sizeof(signed_transaction_t) > payloadLen) {
|
||||
DynArr_destroy(blk->transactions);
|
||||
vector_destroy(&blk->transactions);
|
||||
free(blk);
|
||||
return NODE_BLOCK_REJECTED;
|
||||
}
|
||||
signed_transaction_t tx;
|
||||
memcpy(&tx, payload + offset, sizeof(tx));
|
||||
offset += sizeof(tx);
|
||||
if (!DynArr_push_back(blk->transactions, &tx)) {
|
||||
DynArr_destroy(blk->transactions);
|
||||
if (vector_push_back(blk->transactions, &tx) != 0) {
|
||||
vector_destroy(&blk->transactions);
|
||||
free(blk);
|
||||
return NODE_BLOCK_REJECTED;
|
||||
}
|
||||
@@ -536,7 +548,7 @@ static node_block_accept_result_t Node_ParseAndAcceptBlock(const unsigned char*
|
||||
// seed are derived from it), so there is nothing to validate against without a chain.
|
||||
if (!currentChain) {
|
||||
printf("Rejected BLOCK_DATA at height %" PRIu64 ": no active chain\n", blockHeight);
|
||||
DynArr_destroy(blk->transactions);
|
||||
vector_destroy(&blk->transactions);
|
||||
free(blk);
|
||||
return NODE_BLOCK_REJECTED;
|
||||
}
|
||||
@@ -551,7 +563,7 @@ static node_block_accept_result_t Node_ParseAndAcceptBlock(const unsigned char*
|
||||
// impossible to assemble. Deferring costs at most a slot in a pool that is already capped.
|
||||
if (!Block_HasValidStructure(blk)) {
|
||||
printf("Rejected BLOCK_DATA at height %" PRIu64 " during validation\n", blockHeight);
|
||||
DynArr_destroy(blk->transactions);
|
||||
vector_destroy(&blk->transactions);
|
||||
free(blk);
|
||||
return NODE_BLOCK_REJECTED;
|
||||
}
|
||||
@@ -588,7 +600,7 @@ static node_block_accept_result_t Node_ParseAndAcceptBlock(const unsigned char*
|
||||
|
||||
if (memcmp(localHash, incomingHash, 32) == 0) {
|
||||
// Exactly the block we already have.
|
||||
DynArr_destroy(blk->transactions);
|
||||
vector_destroy(&blk->transactions);
|
||||
free(blk);
|
||||
return NODE_BLOCK_DUPLICATE;
|
||||
}
|
||||
@@ -695,7 +707,7 @@ net_node_t* Node_Create() {
|
||||
// Initialize outbound lock and seen-block cache
|
||||
pthread_mutex_init(&node->seenLock, NULL);
|
||||
pthread_mutex_init(&node->outboundLock, NULL);
|
||||
node->seenBlocks = DynSet_Create(32); // 32-byte canonical hashes
|
||||
node->seenBlocks = set_create(32); // 32-byte canonical hashes; raw bytes, so memcmp equality is right
|
||||
TxMempool_Init();
|
||||
|
||||
TcpServer_Init(node->server, listenPort, "::");
|
||||
@@ -812,10 +824,7 @@ void Node_Destroy(net_node_t* node) {
|
||||
OrphanPool_Destroy();
|
||||
TxMempool_Destroy();
|
||||
|
||||
if (node->seenBlocks) {
|
||||
DynSet_Destroy(node->seenBlocks);
|
||||
node->seenBlocks = NULL;
|
||||
}
|
||||
set_destroy(&node->seenBlocks);
|
||||
pthread_mutex_destroy(&node->seenLock);
|
||||
pthread_mutex_destroy(&node->outboundLock);
|
||||
|
||||
@@ -1153,11 +1162,11 @@ void Node_Server_OnData(tcp_connection_t* client) {
|
||||
if (loadedFromDisk) {
|
||||
printf("Serving block %" PRIu64 " from disk with %zu transaction(s)\n",
|
||||
requestedHeight,
|
||||
DynArr_size(block->transactions));
|
||||
vector_size(block->transactions));
|
||||
}
|
||||
|
||||
// Serialize into a BLOCK_DATA packet [block header][tx count - 8 bytes][transactions...]
|
||||
size_t txCount = block->transactions ? DynArr_size(block->transactions) : 0;
|
||||
size_t txCount = vector_size(block->transactions);
|
||||
size_t blockDataSize = sizeof(uint64_t) + sizeof(block_header_t) + sizeof(uint64_t) + (txCount * sizeof(signed_transaction_t));
|
||||
unsigned char* blockData = (unsigned char*)malloc(blockDataSize);
|
||||
if (!blockData) {
|
||||
@@ -1181,7 +1190,10 @@ void Node_Server_OnData(tcp_connection_t* client) {
|
||||
offset += sizeof(txCount64);
|
||||
if (block->transactions && txCount > 0) {
|
||||
for (size_t ti = 0; ti < txCount; ++ti) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, ti);
|
||||
signed_transaction_t* tx = (signed_transaction_t*)vector_get(block->transactions, ti);
|
||||
if (!tx) {
|
||||
break;
|
||||
}
|
||||
memcpy(blockData + offset, tx, sizeof(signed_transaction_t));
|
||||
offset += sizeof(signed_transaction_t);
|
||||
}
|
||||
@@ -1645,7 +1657,7 @@ void Node_BroadcastChainRange(net_node_t* node, size_t startHeightInclusive, tcp
|
||||
// peer was connected (or while every peer was filtered out below) was never offered again.
|
||||
int seen = 0;
|
||||
pthread_mutex_lock(&node->seenLock);
|
||||
if (DynSet_Contains(node->seenBlocks, hash)) {
|
||||
if (set_contains(node->seenBlocks, hash)) {
|
||||
seen = 1;
|
||||
}
|
||||
pthread_mutex_unlock(&node->seenLock);
|
||||
@@ -1656,7 +1668,7 @@ void Node_BroadcastChainRange(net_node_t* node, size_t startHeightInclusive, tcp
|
||||
}
|
||||
|
||||
// Serialize payload: [uint64_t height][block_header_t][uint64_t txCount][transactions...]
|
||||
size_t txCount = DynArr_size(blk->transactions);
|
||||
size_t txCount = vector_size(blk->transactions);
|
||||
size_t payloadLen = sizeof(uint64_t) + sizeof(block_header_t) + sizeof(uint64_t) + (txCount * sizeof(signed_transaction_t));
|
||||
unsigned char* payload = (unsigned char*)malloc(payloadLen);
|
||||
if (!payload) {
|
||||
@@ -1670,7 +1682,8 @@ void Node_BroadcastChainRange(net_node_t* node, size_t startHeightInclusive, tcp
|
||||
uint64_t txCount64 = (uint64_t)txCount;
|
||||
memcpy(payload + off, &txCount64, sizeof(txCount64)); off += sizeof(txCount64);
|
||||
for (size_t ti = 0; ti < txCount; ++ti) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(blk->transactions, ti);
|
||||
signed_transaction_t* tx = (signed_transaction_t*)vector_get(blk->transactions, ti);
|
||||
if (!tx) break;
|
||||
memcpy(payload + off, tx, sizeof(signed_transaction_t)); off += sizeof(signed_transaction_t);
|
||||
}
|
||||
|
||||
@@ -1751,7 +1764,7 @@ void Node_BroadcastChainRange(net_node_t* node, size_t startHeightInclusive, tcp
|
||||
|
||||
if (delivered > 0) {
|
||||
pthread_mutex_lock(&node->seenLock);
|
||||
DynSet_Insert(node->seenBlocks, hash);
|
||||
(void)set_insert(node->seenBlocks, hash);
|
||||
pthread_mutex_unlock(&node->seenLock);
|
||||
}
|
||||
|
||||
|
||||
+62
-54
@@ -11,7 +11,7 @@
|
||||
#include <ifaddrs.h>
|
||||
|
||||
#include <constants.h>
|
||||
#include <dynarr.h>
|
||||
#include <dlibc/vector.h>
|
||||
#include <numgen.h>
|
||||
#include <runtime_state.h>
|
||||
#include <utils.h>
|
||||
@@ -52,9 +52,9 @@ typedef struct {
|
||||
struct node_discovery {
|
||||
net_node_t* node;
|
||||
udp_node_t* udpNode;
|
||||
DynArr* peers; // of discovered_peer_t
|
||||
DynArr* selfEndpoints; // of struct sockaddr_storage - our own listen endpoints
|
||||
DynArr* connectAttempts; // of discovery_attempt_t
|
||||
vector_t* peers; // of discovered_peer_t
|
||||
vector_t* selfEndpoints; // of struct sockaddr_storage - our own listen endpoints
|
||||
vector_t* connectAttempts; // of discovery_attempt_t
|
||||
pthread_mutex_t lock;
|
||||
};
|
||||
|
||||
@@ -123,10 +123,10 @@ static void Discovery_NormaliseAddr(struct sockaddr_storage* addr) {
|
||||
|
||||
// Returns non-zero if addr is one of our own listen endpoints. Caller holds disc->lock.
|
||||
static int Discovery_IsSelfUnlocked(node_discovery_t* disc, const struct sockaddr_storage* addr) {
|
||||
size_t n = DynArr_size(disc->selfEndpoints);
|
||||
size_t n = vector_size(disc->selfEndpoints);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
const struct sockaddr_storage* self = (const struct sockaddr_storage*)DynArr_at(disc->selfEndpoints, i);
|
||||
if (Discovery_AddrEqual(self, addr)) return 1;
|
||||
const struct sockaddr_storage* self = (const struct sockaddr_storage*)vector_get_const(disc->selfEndpoints, i);
|
||||
if (self && Discovery_AddrEqual(self, addr)) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -134,7 +134,7 @@ static int Discovery_IsSelfUnlocked(node_discovery_t* disc, const struct sockadd
|
||||
// Adds addr to the self set if not already there. Caller holds disc->lock.
|
||||
static void Discovery_AddSelfUnlocked(node_discovery_t* disc, const struct sockaddr_storage* addr) {
|
||||
if (Discovery_IsSelfUnlocked(disc, addr)) return;
|
||||
DynArr_push_back(disc->selfEndpoints, (void*)addr);
|
||||
vector_push_back(disc->selfEndpoints, addr);
|
||||
}
|
||||
|
||||
// Seeds the self set with (local interface address, our listen port) for every address this host
|
||||
@@ -171,10 +171,10 @@ static void Discovery_SeedSelfEndpoints(node_discovery_t* disc) {
|
||||
}
|
||||
|
||||
static discovered_peer_t* Discovery_FindPtr(node_discovery_t* disc, const struct sockaddr_storage* addr) {
|
||||
size_t n = DynArr_size(disc->peers);
|
||||
size_t n = vector_size(disc->peers);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
discovered_peer_t* p = (discovered_peer_t*)DynArr_at(disc->peers, i);
|
||||
if (Discovery_AddrEqual(&p->addr, addr)) return p;
|
||||
discovered_peer_t* p = (discovered_peer_t*)vector_get(disc->peers, i);
|
||||
if (p && Discovery_AddrEqual(&p->addr, addr)) return p;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -191,7 +191,7 @@ static discovered_peer_t* Discovery_Upsert(node_discovery_t* disc, const struct
|
||||
if (hop < existing->hop) existing->hop = hop; // keep the shortest known distance
|
||||
return existing;
|
||||
}
|
||||
if (DynArr_size(disc->peers) >= DISCOVERY_MAX_KNOWN_PEERS) return NULL;
|
||||
if (vector_size(disc->peers) >= DISCOVERY_MAX_KNOWN_PEERS) return NULL;
|
||||
|
||||
discovered_peer_t np;
|
||||
memset(&np, 0, sizeof(np));
|
||||
@@ -200,17 +200,18 @@ static discovered_peer_t* Discovery_Upsert(node_discovery_t* disc, const struct
|
||||
np.nodeId = 0;
|
||||
np.hop = hop;
|
||||
np.state = DISCOVERY_STATE_NEW;
|
||||
DynArr_push_back(disc->peers, &np);
|
||||
return (discovered_peer_t*)DynArr_at(disc->peers, DynArr_size(disc->peers) - 1);
|
||||
// Checked: on a failed push, back() would hand the caller the previous peer instead.
|
||||
if (vector_push_back(disc->peers, &np) != 0) return NULL;
|
||||
return (discovered_peer_t*)vector_back(disc->peers);
|
||||
}
|
||||
|
||||
// Returns non-zero if addr may be dialed again, i.e. we have not tried it within the retry window.
|
||||
// Caller holds disc->lock.
|
||||
static int Discovery_ConnectCooledDown(node_discovery_t* disc, const struct sockaddr_storage* addr, uint64_t now) {
|
||||
size_t n = DynArr_size(disc->connectAttempts);
|
||||
size_t n = vector_size(disc->connectAttempts);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
const discovery_attempt_t* a = (const discovery_attempt_t*)DynArr_at(disc->connectAttempts, i);
|
||||
if (Discovery_AddrEqual(&a->addr, addr)) {
|
||||
const discovery_attempt_t* a = (const discovery_attempt_t*)vector_get_const(disc->connectAttempts, i);
|
||||
if (a && Discovery_AddrEqual(&a->addr, addr)) {
|
||||
return (now - a->lastMs) >= DISCOVERY_CONNECT_RETRY_MS;
|
||||
}
|
||||
}
|
||||
@@ -220,12 +221,13 @@ static int Discovery_ConnectCooledDown(node_discovery_t* disc, const struct sock
|
||||
// Stamps a dial attempt against addr, evicting the stalest record once the table is full.
|
||||
// Caller holds disc->lock.
|
||||
static void Discovery_NoteConnectAttempt(node_discovery_t* disc, const struct sockaddr_storage* addr, uint64_t now) {
|
||||
size_t n = DynArr_size(disc->connectAttempts);
|
||||
size_t n = vector_size(disc->connectAttempts);
|
||||
size_t oldestIdx = 0;
|
||||
uint64_t oldestMs = UINT64_MAX;
|
||||
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
discovery_attempt_t* a = (discovery_attempt_t*)DynArr_at(disc->connectAttempts, i);
|
||||
discovery_attempt_t* a = (discovery_attempt_t*)vector_get(disc->connectAttempts, i);
|
||||
if (!a) continue;
|
||||
if (Discovery_AddrEqual(&a->addr, addr)) {
|
||||
a->lastMs = now;
|
||||
return;
|
||||
@@ -237,9 +239,11 @@ static void Discovery_NoteConnectAttempt(node_discovery_t* disc, const struct so
|
||||
}
|
||||
|
||||
if (n >= DISCOVERY_MAX_KNOWN_PEERS) {
|
||||
discovery_attempt_t* victim = (discovery_attempt_t*)DynArr_at(disc->connectAttempts, oldestIdx);
|
||||
victim->addr = *addr;
|
||||
victim->lastMs = now;
|
||||
discovery_attempt_t* victim = (discovery_attempt_t*)vector_get(disc->connectAttempts, oldestIdx);
|
||||
if (victim) {
|
||||
victim->addr = *addr;
|
||||
victim->lastMs = now;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -247,16 +251,16 @@ static void Discovery_NoteConnectAttempt(node_discovery_t* disc, const struct so
|
||||
memset(&na, 0, sizeof(na));
|
||||
na.addr = *addr;
|
||||
na.lastMs = now;
|
||||
DynArr_push_back(disc->connectAttempts, &na);
|
||||
vector_push_back(disc->connectAttempts, &na);
|
||||
}
|
||||
|
||||
// Drops the entry for addr, if any. Caller holds disc->lock.
|
||||
static void Discovery_RemoveUnlocked(node_discovery_t* disc, const struct sockaddr_storage* addr) {
|
||||
size_t n = DynArr_size(disc->peers);
|
||||
size_t n = vector_size(disc->peers);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
discovered_peer_t* p = (discovered_peer_t*)DynArr_at(disc->peers, i);
|
||||
if (Discovery_AddrEqual(&p->addr, addr)) {
|
||||
DynArr_remove(disc->peers, i);
|
||||
discovered_peer_t* p = (discovered_peer_t*)vector_get(disc->peers, i);
|
||||
if (p && Discovery_AddrEqual(&p->addr, addr)) {
|
||||
vector_pop_at(disc->peers, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -331,21 +335,21 @@ node_discovery_t* NodeDiscovery_Create(net_node_t* node, udp_node_t* udpNode) {
|
||||
memset(disc, 0, sizeof(*disc));
|
||||
disc->node = node;
|
||||
disc->udpNode = udpNode;
|
||||
disc->peers = DYNARR_CREATE(discovered_peer_t, 16);
|
||||
disc->peers = vector_create(sizeof(discovered_peer_t));
|
||||
if (!disc->peers) {
|
||||
free(disc);
|
||||
return NULL;
|
||||
}
|
||||
disc->selfEndpoints = DYNARR_CREATE(struct sockaddr_storage, 8);
|
||||
disc->selfEndpoints = vector_create(sizeof(struct sockaddr_storage));
|
||||
if (!disc->selfEndpoints) {
|
||||
DynArr_destroy(disc->peers);
|
||||
vector_destroy(&disc->peers);
|
||||
free(disc);
|
||||
return NULL;
|
||||
}
|
||||
disc->connectAttempts = DYNARR_CREATE(discovery_attempt_t, 16);
|
||||
disc->connectAttempts = vector_create(sizeof(discovery_attempt_t));
|
||||
if (!disc->connectAttempts) {
|
||||
DynArr_destroy(disc->selfEndpoints);
|
||||
DynArr_destroy(disc->peers);
|
||||
vector_destroy(&disc->selfEndpoints);
|
||||
vector_destroy(&disc->peers);
|
||||
free(disc);
|
||||
return NULL;
|
||||
}
|
||||
@@ -358,9 +362,9 @@ node_discovery_t* NodeDiscovery_Create(net_node_t* node, udp_node_t* udpNode) {
|
||||
|
||||
void NodeDiscovery_Destroy(node_discovery_t* disc) {
|
||||
if (!disc) return;
|
||||
if (disc->peers) DynArr_destroy(disc->peers);
|
||||
if (disc->selfEndpoints) DynArr_destroy(disc->selfEndpoints);
|
||||
if (disc->connectAttempts) DynArr_destroy(disc->connectAttempts);
|
||||
vector_destroy(&disc->peers);
|
||||
vector_destroy(&disc->selfEndpoints);
|
||||
vector_destroy(&disc->connectAttempts);
|
||||
pthread_mutex_destroy(&disc->lock);
|
||||
free(disc);
|
||||
}
|
||||
@@ -471,15 +475,15 @@ void NodeDiscovery_OnPeersReceived(node_discovery_t* disc, tcp_connection_t* fro
|
||||
void NodeDiscovery_RemovePeer(node_discovery_t* disc, const struct sockaddr_storage* endpoint) {
|
||||
if (!disc || !endpoint) return;
|
||||
pthread_mutex_lock(&disc->lock);
|
||||
size_t n = DynArr_size(disc->peers);
|
||||
size_t n = vector_size(disc->peers);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
discovered_peer_t* p = (discovered_peer_t*)DynArr_at(disc->peers, i);
|
||||
if (Discovery_AddrEqual(&p->addr, endpoint)) {
|
||||
discovered_peer_t* p = (discovered_peer_t*)vector_get(disc->peers, i);
|
||||
if (p && Discovery_AddrEqual(&p->addr, endpoint)) {
|
||||
char ip[INET6_ADDRSTRLEN] = {0};
|
||||
unsigned short port = 0;
|
||||
Discovery_AddrToIpPort(&p->addr, ip, sizeof(ip), &port);
|
||||
printf("NodeDiscovery: struck disconnected peer %s:%u from peer list\n", ip, port);
|
||||
DynArr_remove(disc->peers, i);
|
||||
vector_pop_at(disc->peers, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -563,10 +567,10 @@ void NodeDiscovery_Iterate(node_discovery_t* disc) {
|
||||
}
|
||||
// Demote entries still marked CONNECTED that are no longer in the outbound set.
|
||||
{
|
||||
size_t n = DynArr_size(disc->peers);
|
||||
size_t n = vector_size(disc->peers);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
discovered_peer_t* p = (discovered_peer_t*)DynArr_at(disc->peers, i);
|
||||
if (p->state != DISCOVERY_STATE_CONNECTED) continue;
|
||||
discovered_peer_t* p = (discovered_peer_t*)vector_get(disc->peers, i);
|
||||
if (!p || p->state != DISCOVERY_STATE_CONNECTED) continue;
|
||||
int stillConnected = 0;
|
||||
for (size_t j = 0; j < outEpCount; ++j) {
|
||||
if (Discovery_AddrEqual(&p->addr, &outEndpoints[j])) { stillConnected = 1; break; }
|
||||
@@ -580,9 +584,10 @@ void NodeDiscovery_Iterate(node_discovery_t* disc) {
|
||||
// 2. Ping NEW peers (and refresh stale REACHABLE ones), capped per tick.
|
||||
{
|
||||
int pings = 0;
|
||||
size_t n = DynArr_size(disc->peers);
|
||||
size_t n = vector_size(disc->peers);
|
||||
for (size_t i = 0; i < n && pings < DISCOVERY_MAX_PINGS_PER_TICK; ++i) {
|
||||
discovered_peer_t* p = (discovered_peer_t*)DynArr_at(disc->peers, i);
|
||||
discovered_peer_t* p = (discovered_peer_t*)vector_get(disc->peers, i);
|
||||
if (!p) continue;
|
||||
int shouldPing = 0;
|
||||
if (!p->pingPending) {
|
||||
if (p->state == DISCOVERY_STATE_NEW) {
|
||||
@@ -604,9 +609,10 @@ void NodeDiscovery_Iterate(node_discovery_t* disc) {
|
||||
|
||||
// 3. Timeout backstop (in case the UDP layer's own timeout callback is missed).
|
||||
{
|
||||
size_t n = DynArr_size(disc->peers);
|
||||
size_t n = vector_size(disc->peers);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
discovered_peer_t* p = (discovered_peer_t*)DynArr_at(disc->peers, i);
|
||||
discovered_peer_t* p = (discovered_peer_t*)vector_get(disc->peers, i);
|
||||
if (!p) continue;
|
||||
if (p->state == DISCOVERY_STATE_PINGED && p->pingPending &&
|
||||
(now - p->lastPingMs) > DISCOVERY_PING_TIMEOUT_MS) {
|
||||
p->pingPending = 0;
|
||||
@@ -653,10 +659,10 @@ void NodeDiscovery_Iterate(node_discovery_t* disc) {
|
||||
size_t slots = (size_t)DISCOVERY_TARGET_CONNECTIONS - outCount;
|
||||
for (size_t s = 0; s < slots && toConnectCount < MAX_CONS; ++s) {
|
||||
discovered_peer_t* best = NULL;
|
||||
size_t n = DynArr_size(disc->peers);
|
||||
size_t n = vector_size(disc->peers);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
discovered_peer_t* p = (discovered_peer_t*)DynArr_at(disc->peers, i);
|
||||
if (p->state != DISCOVERY_STATE_REACHABLE) continue;
|
||||
discovered_peer_t* p = (discovered_peer_t*)vector_get(disc->peers, i);
|
||||
if (!p || p->state != DISCOVERY_STATE_REACHABLE) continue;
|
||||
if (!Discovery_ConnectCooledDown(disc, &p->addr, now)) continue;
|
||||
int already = 0;
|
||||
for (size_t j = 0; j < outEpCount; ++j) {
|
||||
@@ -710,10 +716,11 @@ void NodeDiscovery_PrintPeers(node_discovery_t* disc) {
|
||||
static const char* stateNames[] = { "NEW", "PINGED", "REACHABLE", "CONNECTED", "UNREACHABLE" };
|
||||
|
||||
pthread_mutex_lock(&disc->lock);
|
||||
size_t n = DynArr_size(disc->peers);
|
||||
size_t n = vector_size(disc->peers);
|
||||
printf("Known peers (%zu):\n", n);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
discovered_peer_t* p = (discovered_peer_t*)DynArr_at(disc->peers, i);
|
||||
discovered_peer_t* p = (discovered_peer_t*)vector_get(disc->peers, i);
|
||||
if (!p) continue;
|
||||
char ip[INET6_ADDRSTRLEN] = {0};
|
||||
unsigned short port = 0;
|
||||
Discovery_AddrToIpPort(&p->addr, ip, sizeof(ip), &port);
|
||||
@@ -732,10 +739,11 @@ void NodeDiscovery_PrintPeers(node_discovery_t* disc) {
|
||||
(void)port; // port is part of ip endpoint identity; shown via connect logs
|
||||
}
|
||||
|
||||
size_t selfCount = DynArr_size(disc->selfEndpoints);
|
||||
size_t selfCount = vector_size(disc->selfEndpoints);
|
||||
printf("Own endpoints (%zu):\n", selfCount);
|
||||
for (size_t i = 0; i < selfCount; ++i) {
|
||||
const struct sockaddr_storage* self = (const struct sockaddr_storage*)DynArr_at(disc->selfEndpoints, i);
|
||||
const struct sockaddr_storage* self = (const struct sockaddr_storage*)vector_get_const(disc->selfEndpoints, i);
|
||||
if (!self) continue;
|
||||
char ip[INET6_ADDRSTRLEN] = {0};
|
||||
unsigned short port = 0;
|
||||
Discovery_AddrToIpPort(self, ip, sizeof(ip), &port);
|
||||
|
||||
+80
-82
@@ -1,6 +1,6 @@
|
||||
#include <nets/orphan_pool.h>
|
||||
#include <constants.h>
|
||||
#include <dynarr.h>
|
||||
#include <dlibc/vector.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -14,7 +14,7 @@ typedef struct {
|
||||
uint8_t hash[32];
|
||||
} orphan_entry_t;
|
||||
|
||||
static DynArr* g_orphans = NULL;
|
||||
static vector_t* g_orphans = NULL;
|
||||
static uint64_t g_nextSequence = 0;
|
||||
|
||||
// The pool is touched by the maintenance thread, by every per-peer TCP thread and by the REPL
|
||||
@@ -26,9 +26,18 @@ static uint64_t g_nextSequence = 0;
|
||||
// Chain_ReplaceBranch/Chain_AddBlock called.
|
||||
static pthread_mutex_t g_orphanLock = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
// Element destructor for `g_orphans`: an entry owns its block, so dropping or evicting an entry
|
||||
// frees the block along with it. OrphanPool_TakeByHashLocked hands a block out through
|
||||
// vector_take_at(), which deliberately skips this. Block_Destroy accepts NULL.
|
||||
static void OrphanPool_EntryDestructor(void* element) {
|
||||
orphan_entry_t* e = (orphan_entry_t*)element;
|
||||
Block_Destroy(e->block);
|
||||
}
|
||||
|
||||
static void OrphanPool_InitLocked(void) {
|
||||
if (!g_orphans) {
|
||||
g_orphans = DYNARR_CREATE(orphan_entry_t, 16);
|
||||
g_orphans = vector_create(sizeof(orphan_entry_t));
|
||||
vector_set_destructor(g_orphans, OrphanPool_EntryDestructor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,17 +49,7 @@ void OrphanPool_Init(void) {
|
||||
|
||||
void OrphanPool_Destroy(void) {
|
||||
pthread_mutex_lock(&g_orphanLock);
|
||||
if (g_orphans) {
|
||||
size_t n = DynArr_size(g_orphans);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
orphan_entry_t* e = (orphan_entry_t*)DynArr_at(g_orphans, i);
|
||||
if (e && e->block) {
|
||||
Block_Destroy(e->block);
|
||||
}
|
||||
}
|
||||
DynArr_destroy(g_orphans);
|
||||
g_orphans = NULL;
|
||||
}
|
||||
vector_destroy(&g_orphans); // The element destructor frees every pooled block
|
||||
pthread_mutex_unlock(&g_orphanLock);
|
||||
}
|
||||
|
||||
@@ -59,9 +58,9 @@ static ssize_t OrphanPool_FindByHashLocked(const uint8_t blockHash[32]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t n = DynArr_size(g_orphans);
|
||||
size_t n = vector_size(g_orphans);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
orphan_entry_t* e = (orphan_entry_t*)DynArr_at(g_orphans, i);
|
||||
orphan_entry_t* e = (orphan_entry_t*)vector_get(g_orphans, i);
|
||||
if (e && memcmp(e->hash, blockHash, 32) == 0) {
|
||||
return (ssize_t)i;
|
||||
}
|
||||
@@ -77,7 +76,7 @@ static bool OrphanPool_EvictOldestLocked(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t n = DynArr_size(g_orphans);
|
||||
size_t n = vector_size(g_orphans);
|
||||
if (n == 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -85,18 +84,14 @@ static bool OrphanPool_EvictOldestLocked(void) {
|
||||
size_t oldestIndex = 0;
|
||||
uint64_t oldestSequence = UINT64_MAX;
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
orphan_entry_t* e = (orphan_entry_t*)DynArr_at(g_orphans, i);
|
||||
orphan_entry_t* e = (orphan_entry_t*)vector_get(g_orphans, i);
|
||||
if (e && e->sequence < oldestSequence) {
|
||||
oldestSequence = e->sequence;
|
||||
oldestIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
orphan_entry_t* victim = (orphan_entry_t*)DynArr_at(g_orphans, oldestIndex);
|
||||
if (victim && victim->block) {
|
||||
Block_Destroy(victim->block);
|
||||
}
|
||||
DynArr_remove(g_orphans, oldestIndex);
|
||||
vector_pop_at(g_orphans, oldestIndex); // The element destructor frees the victim's block
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -124,7 +119,7 @@ void OrphanPool_Insert(block_t* block, uint64_t height, uint64_t observedAtTipHe
|
||||
return;
|
||||
}
|
||||
|
||||
while (DynArr_size(g_orphans) >= MAX_ORPHAN_BLOCKS) {
|
||||
while (vector_size(g_orphans) >= MAX_ORPHAN_BLOCKS) {
|
||||
if (!OrphanPool_EvictOldestLocked()) {
|
||||
break;
|
||||
}
|
||||
@@ -138,9 +133,9 @@ void OrphanPool_Insert(block_t* block, uint64_t height, uint64_t observedAtTipHe
|
||||
e.sequence = g_nextSequence++;
|
||||
memcpy(e.hash, blockHash, 32);
|
||||
|
||||
if (!DynArr_push_back(g_orphans, &e)) {
|
||||
if (vector_push_back(g_orphans, &e) != 0) {
|
||||
pthread_mutex_unlock(&g_orphanLock);
|
||||
Block_Destroy(block);
|
||||
Block_Destroy(block); // never pooled, so the pool does not own it
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -156,23 +151,25 @@ bool OrphanPool_Contains(const uint8_t blockHash[32]) {
|
||||
|
||||
size_t OrphanPool_Size(void) {
|
||||
pthread_mutex_lock(&g_orphanLock);
|
||||
size_t n = g_orphans ? DynArr_size(g_orphans) : 0;
|
||||
size_t n = vector_size(g_orphans);
|
||||
pthread_mutex_unlock(&g_orphanLock);
|
||||
return n;
|
||||
}
|
||||
|
||||
// Remove the entry with this hash without freeing the block, and hand the block back. Used once a
|
||||
// block has been given to the chain, which then owns its transaction array.
|
||||
// block has been given to the chain, which then owns its transaction array. vector_take_at() skips
|
||||
// the element destructor, which is what moves ownership of the block to the caller.
|
||||
static block_t* OrphanPool_TakeByHashLocked(const uint8_t blockHash[32]) {
|
||||
ssize_t index = OrphanPool_FindByHashLocked(blockHash);
|
||||
if (index < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
orphan_entry_t* e = (orphan_entry_t*)DynArr_at(g_orphans, (size_t)index);
|
||||
block_t* blk = e ? e->block : NULL;
|
||||
DynArr_remove(g_orphans, (size_t)index);
|
||||
return blk;
|
||||
orphan_entry_t entry;
|
||||
if (vector_take_at(g_orphans, (size_t)index, &entry) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
return entry.block;
|
||||
}
|
||||
|
||||
static void OrphanPool_DropByHashLocked(const uint8_t blockHash[32]) {
|
||||
@@ -181,11 +178,7 @@ static void OrphanPool_DropByHashLocked(const uint8_t blockHash[32]) {
|
||||
return;
|
||||
}
|
||||
|
||||
orphan_entry_t* e = (orphan_entry_t*)DynArr_at(g_orphans, (size_t)index);
|
||||
if (e && e->block) {
|
||||
Block_Destroy(e->block);
|
||||
}
|
||||
DynArr_remove(g_orphans, (size_t)index);
|
||||
vector_pop_at(g_orphans, (size_t)index); // The element destructor frees the block
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,9 +194,9 @@ static bool OrphanPool_FindChildLocked(uint64_t height,
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t n = DynArr_size(g_orphans);
|
||||
size_t n = vector_size(g_orphans);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
orphan_entry_t* e = (orphan_entry_t*)DynArr_at(g_orphans, i);
|
||||
orphan_entry_t* e = (orphan_entry_t*)vector_get(g_orphans, i);
|
||||
if (!e || !e->block) {
|
||||
continue;
|
||||
}
|
||||
@@ -240,11 +233,11 @@ static size_t OrphanPool_CollectBranchLocked(uint64_t forkHeight,
|
||||
*outHashes = NULL;
|
||||
*outObservedAtTipHeight = 0;
|
||||
|
||||
DynArr* collected = DYNARR_CREATE(block_t*, 8);
|
||||
DynArr* hashes = DYNARR_CREATE(uint8_t, 8 * 32);
|
||||
vector_t* collected = vector_create(sizeof(block_t*));
|
||||
vector_t* hashes = vector_create(32); // one whole 32-byte block hash per element, index-aligned with collected
|
||||
if (!collected || !hashes) {
|
||||
if (collected) DynArr_destroy(collected);
|
||||
if (hashes) DynArr_destroy(hashes);
|
||||
vector_destroy(&collected);
|
||||
vector_destroy(&hashes);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -263,13 +256,13 @@ static size_t OrphanPool_CollectBranchLocked(uint64_t forkHeight,
|
||||
break;
|
||||
}
|
||||
|
||||
if (!DynArr_push_back(collected, &child)) {
|
||||
// A block only counts once both pushes have landed, so the two vectors never drift out of
|
||||
// step: the copy-out below reads index i from both.
|
||||
if (vector_push_back(collected, &child) != 0) {
|
||||
break;
|
||||
}
|
||||
for (size_t b = 0; b < 32; ++b) {
|
||||
if (!DynArr_push_back(hashes, &childHash[b])) {
|
||||
break;
|
||||
}
|
||||
if (vector_push_back(hashes, childHash) != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (observed < earliestObserved) {
|
||||
@@ -282,8 +275,8 @@ static size_t OrphanPool_CollectBranchLocked(uint64_t forkHeight,
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
DynArr_destroy(collected);
|
||||
DynArr_destroy(hashes);
|
||||
vector_destroy(&collected);
|
||||
vector_destroy(&hashes);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -292,20 +285,24 @@ static size_t OrphanPool_CollectBranchLocked(uint64_t forkHeight,
|
||||
if (!blocks || !hashOut) {
|
||||
free(blocks);
|
||||
free(hashOut);
|
||||
DynArr_destroy(collected);
|
||||
DynArr_destroy(hashes);
|
||||
vector_destroy(&collected);
|
||||
vector_destroy(&hashes);
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
blocks[i] = *(block_t**)DynArr_at(collected, i);
|
||||
for (size_t b = 0; b < 32; ++b) {
|
||||
hashOut[i * 32 + b] = *(uint8_t*)DynArr_at(hashes, i * 32 + b);
|
||||
block_t** blockSlot = (block_t**)vector_get(collected, i);
|
||||
const uint8_t* hashSlot = (const uint8_t*)vector_get_const(hashes, i);
|
||||
if (!blockSlot || !hashSlot) {
|
||||
count = i; // never hand back a branch with a hole in it
|
||||
break;
|
||||
}
|
||||
blocks[i] = *blockSlot;
|
||||
memcpy(&hashOut[i * 32], hashSlot, 32);
|
||||
}
|
||||
|
||||
DynArr_destroy(collected);
|
||||
DynArr_destroy(hashes);
|
||||
vector_destroy(&collected);
|
||||
vector_destroy(&hashes);
|
||||
|
||||
*outBlocks = blocks;
|
||||
*outHashes = hashOut;
|
||||
@@ -324,39 +321,43 @@ static void OrphanPool_PruneStale(blockchain_t* chain) {
|
||||
const size_t chainSize = Chain_Size(chain);
|
||||
|
||||
// Collect the hashes to drop first, so we never call into chain.c while holding the pool lock.
|
||||
DynArr* doomed = DYNARR_CREATE(uint8_t, 32);
|
||||
vector_t* doomed = vector_create(32); // one whole 32-byte block hash per element
|
||||
if (!doomed) {
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_orphanLock);
|
||||
size_t n = g_orphans ? DynArr_size(g_orphans) : 0;
|
||||
DynArr* candidates = DYNARR_CREATE(uint8_t, 32);
|
||||
DynArr* candidateHeights = DYNARR_CREATE(uint64_t, 8);
|
||||
size_t n = vector_size(g_orphans);
|
||||
vector_t* candidates = vector_create(32); // index-aligned with candidateHeights
|
||||
vector_t* candidateHeights = vector_create(sizeof(uint64_t));
|
||||
if (candidates && candidateHeights) {
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
orphan_entry_t* e = (orphan_entry_t*)DynArr_at(g_orphans, i);
|
||||
orphan_entry_t* e = (orphan_entry_t*)vector_get(g_orphans, i);
|
||||
if (!e || !e->block) {
|
||||
continue;
|
||||
}
|
||||
if (e->height >= (uint64_t)chainSize) {
|
||||
continue; // still ahead of us; may attach later
|
||||
}
|
||||
for (size_t b = 0; b < 32; ++b) {
|
||||
(void)DynArr_push_back(candidates, &e->hash[b]);
|
||||
if (vector_push_back(candidates, e->hash) != 0) {
|
||||
break;
|
||||
}
|
||||
if (vector_push_back(candidateHeights, &e->height) != 0) {
|
||||
vector_pop_back(candidates); // keep the two index-aligned
|
||||
break;
|
||||
}
|
||||
(void)DynArr_push_back(candidateHeights, &e->height);
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&g_orphanLock);
|
||||
|
||||
size_t candidateCount = candidateHeights ? DynArr_size(candidateHeights) : 0;
|
||||
size_t candidateCount = vector_size(candidateHeights);
|
||||
for (size_t i = 0; i < candidateCount; ++i) {
|
||||
uint64_t height = *(uint64_t*)DynArr_at(candidateHeights, i);
|
||||
uint8_t orphanHash[32];
|
||||
for (size_t b = 0; b < 32; ++b) {
|
||||
orphanHash[b] = *(uint8_t*)DynArr_at(candidates, i * 32 + b);
|
||||
const uint64_t* heightSlot = (const uint64_t*)vector_get_const(candidateHeights, i);
|
||||
const uint8_t* orphanHash = (const uint8_t*)vector_get_const(candidates, i);
|
||||
if (!heightSlot || !orphanHash) {
|
||||
continue;
|
||||
}
|
||||
uint64_t height = *heightSlot;
|
||||
|
||||
block_t* local = NULL;
|
||||
if (!Chain_GetBlockCopy(chain, (size_t)height, &local) || !local) {
|
||||
@@ -370,28 +371,25 @@ static void OrphanPool_PruneStale(blockchain_t* chain) {
|
||||
// Same block we already have: pure duplicate, drop it. A different block at a height we
|
||||
// have already passed is kept, because it may yet be the base of a heavier branch.
|
||||
if (memcmp(localHash, orphanHash, 32) == 0) {
|
||||
for (size_t b = 0; b < 32; ++b) {
|
||||
(void)DynArr_push_back(doomed, &orphanHash[b]);
|
||||
}
|
||||
(void)vector_push_back(doomed, orphanHash);
|
||||
}
|
||||
}
|
||||
|
||||
size_t doomedCount = DynArr_size(doomed) / 32;
|
||||
size_t doomedCount = vector_size(doomed);
|
||||
if (doomedCount > 0) {
|
||||
pthread_mutex_lock(&g_orphanLock);
|
||||
for (size_t i = 0; i < doomedCount; ++i) {
|
||||
uint8_t h[32];
|
||||
for (size_t b = 0; b < 32; ++b) {
|
||||
h[b] = *(uint8_t*)DynArr_at(doomed, i * 32 + b);
|
||||
const uint8_t* h = (const uint8_t*)vector_get_const(doomed, i);
|
||||
if (h) {
|
||||
OrphanPool_DropByHashLocked(h);
|
||||
}
|
||||
OrphanPool_DropByHashLocked(h);
|
||||
}
|
||||
pthread_mutex_unlock(&g_orphanLock);
|
||||
}
|
||||
|
||||
if (candidates) DynArr_destroy(candidates);
|
||||
if (candidateHeights) DynArr_destroy(candidateHeights);
|
||||
DynArr_destroy(doomed);
|
||||
vector_destroy(&candidates);
|
||||
vector_destroy(&candidateHeights);
|
||||
vector_destroy(&doomed);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -558,7 +556,7 @@ size_t OrphanPool_AttemptAttachForced(blockchain_t* chain, bool bypassPenalty) {
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_orphanLock);
|
||||
bool empty = (g_orphans == NULL) || (DynArr_size(g_orphans) == 0);
|
||||
bool empty = vector_is_empty(g_orphans); // also true while the pool is not initialised
|
||||
pthread_mutex_unlock(&g_orphanLock);
|
||||
if (empty) {
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user