Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 233 additions & 0 deletions doc/stable_computor_index_diagram.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/Qubic.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
<ClInclude Include="ticking\tick_storage.h" />
<ClInclude Include="ticking\pending_txs_pool.h" />
<ClInclude Include="ticking\execution_fee_report_collector.h" />
<ClInclude Include="ticking\stable_computor_index.h" />
<ClInclude Include="vote_counter.h" />
</ItemGroup>
<ItemGroup>
Expand Down
3 changes: 3 additions & 0 deletions src/Qubic.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,9 @@
<ClInclude Include="ticking\execution_fee_report_collector.h">
<Filter>ticking</Filter>
</ClInclude>
<ClInclude Include="ticking\stable_computor_index.h">
<Filter>ticking</Filter>
</ClInclude>
<ClInclude Include="contracts\Qdraw.h">
<Filter>contracts</Filter>
</ClInclude>
Expand Down
6 changes: 6 additions & 0 deletions src/qubic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
#include "contract_core/qpi_ticking_impl.h"
#include "vote_counter.h"
#include "ticking/execution_fee_report_collector.h"
#include "ticking/stable_computor_index.h"
#include "network_messages/execution_fees.h"

#include "contract_core/ipo.h"
Expand Down Expand Up @@ -5248,6 +5249,11 @@ static void tickProcessor(void*)
// Save the file of revenue. This blocking save can be called from any thread
saveRevenueComponents(NULL);

// Reorder futureComputors so requalifying computors keep their index
// This is needed for correct execution fee reporting across epoch boundaries
static_assert(reorgBufferSize >= stableComputorIndexBufferSize(), "reorgBuffer too small for stable computor index");
calculateStableComputorIndex(system.futureComputors, broadcastedComputors.computors.publicKeys, reorgBuffer);

// instruct main loop to save system and wait until it is done
systemMustBeSaved = true;
WAIT_WHILE(systemMustBeSaved);
Expand Down
69 changes: 69 additions & 0 deletions src/ticking/stable_computor_index.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#pragma once

#include "platform/m256.h"
#include "platform/memory.h"
#include "public_settings.h"

// Minimum buffer size: NUMBER_OF_COMPUTORS * sizeof(m256i) + 2 * NUMBER_OF_COMPUTORS bytes (~23KB)
constexpr unsigned long long stableComputorIndexBufferSize()
{
return NUMBER_OF_COMPUTORS * sizeof(m256i) + 2 * NUMBER_OF_COMPUTORS;
}

// Reorders futureComputors so requalifying computors keep their current index.
// New computors fill remaining slots. See doc/stable_computor_index_diagram.svg
// Returns false if there aren't enough computors to fill all slots.
static bool calculateStableComputorIndex(
m256i* futureComputors,
const m256i* currentComputors,
void* tempBuffer)
{
m256i* tempComputorList = (m256i*)tempBuffer;
bool* isIndexTaken = (bool*)(tempComputorList + NUMBER_OF_COMPUTORS);
bool* isFutureComputorUsed = isIndexTaken + NUMBER_OF_COMPUTORS;

setMem(tempComputorList, NUMBER_OF_COMPUTORS * sizeof(m256i), 0);
setMem(isIndexTaken, NUMBER_OF_COMPUTORS, 0);
setMem(isFutureComputorUsed, NUMBER_OF_COMPUTORS, 0);

// Step 1: Requalifying computors keep their current index
for (unsigned int futureIdx = 0; futureIdx < NUMBER_OF_COMPUTORS; futureIdx++)
{
for (unsigned int currentIdx = 0; currentIdx < NUMBER_OF_COMPUTORS; currentIdx++)
{
if (futureComputors[futureIdx] == currentComputors[currentIdx])
{
tempComputorList[currentIdx] = futureComputors[futureIdx];
isIndexTaken[currentIdx] = true;
isFutureComputorUsed[futureIdx] = true;
break;
}
}
}

// Step 2: New computors fill remaining slots
unsigned int nextNewComputorIdx = 0;
for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++)
{
if (!isIndexTaken[i])
{
while (nextNewComputorIdx < NUMBER_OF_COMPUTORS && isFutureComputorUsed[nextNewComputorIdx])
{
nextNewComputorIdx++;
}

if (nextNewComputorIdx >= NUMBER_OF_COMPUTORS)
{
return false;
}

tempComputorList[i] = futureComputors[nextNewComputorIdx];
isFutureComputorUsed[nextNewComputorIdx] = true;
nextNewComputorIdx++;
}
}

copyMem(futureComputors, tempComputorList, NUMBER_OF_COMPUTORS * sizeof(m256i));

return true;
}
214 changes: 214 additions & 0 deletions test/stable_computor_index.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
#define NO_UEFI

#include "gtest/gtest.h"
#include "../src/ticking/stable_computor_index.h"

class StableComputorIndexTest : public ::testing::Test
{
protected:
m256i futureComputors[NUMBER_OF_COMPUTORS];
m256i currentComputors[NUMBER_OF_COMPUTORS];
unsigned char tempBuffer[stableComputorIndexBufferSize()];

void SetUp() override
{
memset(futureComputors, 0, sizeof(futureComputors));
memset(currentComputors, 0, sizeof(currentComputors));
memset(tempBuffer, 0, sizeof(tempBuffer));
}

m256i makeId(int n)
{
m256i id = m256i::zero();
id.m256i_u64[1] = n;
return id;
}
};

// Test: All computors requalify - all should keep their indices
TEST_F(StableComputorIndexTest, AllRequalify)
{
// Set up current computors with IDs 1 to NUMBER_OF_COMPUTORS
for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++)
{
currentComputors[i] = makeId(i + 1);
}

// Future: Same IDs but reversed order (simulating score reordering)
for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++)
{
futureComputors[i] = makeId(NUMBER_OF_COMPUTORS - i);
}

bool result = calculateStableComputorIndex(futureComputors, currentComputors, tempBuffer);
ASSERT_TRUE(result);

// All should be back to their original indices
for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++)
{
EXPECT_EQ(futureComputors[i], makeId(i + 1)) << "Index " << i << " mismatch";
}
}

// Test: Half computors replaced - requalifying keep index, new fill gaps
TEST_F(StableComputorIndexTest, PartialRequalify)
{
// Current: ID 1 at idx 0, ID 2 at idx 1, ..., ID 676 at idx 675
for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++)
{
currentComputors[i] = makeId(i + 1);
}

// Future input (scrambled): odd IDs requalify, even IDs replaced by new (1001+)
unsigned int requalifyingId = 1;
unsigned int newId = 1001;
for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++)
{
if (i % 2 == 0 && requalifyingId <= NUMBER_OF_COMPUTORS)
{
futureComputors[i] = makeId(requalifyingId);
requalifyingId += 2;
}
else
{
futureComputors[i] = makeId(newId++);
}
}

bool result = calculateStableComputorIndex(futureComputors, currentComputors, tempBuffer);
ASSERT_TRUE(result);

// Odd IDs (1,3,5,...) should be at original indices (0,2,4,...)
for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i += 2)
{
EXPECT_EQ(futureComputors[i], makeId(i + 1));
}

// New IDs (1001,1002,...) should fill gaps at indices (1,3,5,...)
unsigned int expectedNewId = 1001;
for (unsigned int i = 1; i < NUMBER_OF_COMPUTORS; i += 2)
{
EXPECT_EQ(futureComputors[i], makeId(expectedNewId++));
}
}

// Test: All computors are new - order preserved
TEST_F(StableComputorIndexTest, AllNew)
{
// Current: IDs 1 to 676
for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++)
{
currentComputors[i] = makeId(i + 1);
}

// Future: Completely new set (IDs 1000 to 1675)
for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++)
{
futureComputors[i] = makeId(i + 1000);
}

bool result = calculateStableComputorIndex(futureComputors, currentComputors, tempBuffer);
ASSERT_TRUE(result);

// New computors should fill slots in order
for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++)
{
EXPECT_EQ(futureComputors[i], makeId(i + 1000));
}
}

// Test: Single computor requalifies
TEST_F(StableComputorIndexTest, SingleRequalify)
{
// Current: IDs 1 to 676
for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++)
{
currentComputors[i] = makeId(i + 1);
}

// Future: Only ID 100 requalifies (at position 0), rest are new
futureComputors[0] = makeId(100); // Requalifying, was at idx 99
for (unsigned int i = 1; i < NUMBER_OF_COMPUTORS; i++)
{
futureComputors[i] = makeId(i + 1000); // New IDs
}

bool result = calculateStableComputorIndex(futureComputors, currentComputors, tempBuffer);
ASSERT_TRUE(result);

// ID 100 should be at its original index 99
EXPECT_EQ(futureComputors[99], makeId(100));

// New computors fill remaining slots (0-98, 100-675)
unsigned int newIdx = 0;
for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++)
{
if (i == 99) continue; // Skip the requalifying slot
EXPECT_EQ(futureComputors[i], makeId(newIdx + 1001)) << "New computor at index " << i;
newIdx++;
}
}


// Test: First and last computor swap positions in input
TEST_F(StableComputorIndexTest, FirstLastSwap)
{
// Current: IDs 1 to 676
for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++)
{
currentComputors[i] = makeId(i + 1);
}

// Future: All same IDs, but first and last swapped in input order
for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++)
{
futureComputors[i] = makeId(i + 1);
}
futureComputors[0] = makeId(NUMBER_OF_COMPUTORS); // Last ID at first position
futureComputors[NUMBER_OF_COMPUTORS - 1] = makeId(1); // First ID at last position

bool result = calculateStableComputorIndex(futureComputors, currentComputors, tempBuffer);
ASSERT_TRUE(result);

// All should be at their original indices regardless of input order
for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++)
{
EXPECT_EQ(futureComputors[i], makeId(i + 1)) << "Index " << i << " mismatch";
}
}

// Test: Realistic scenario - 225 computors change (max allowed)
TEST_F(StableComputorIndexTest, MaxChange225)
{
// Current: IDs 1 to 676
for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++)
{
currentComputors[i] = makeId(i + 1);
}

// Future: First 451 (QUORUM) stay, last 225 are replaced with new IDs
for (unsigned int i = 0; i < 451; i++)
{
futureComputors[i] = makeId(i + 1); // Same IDs, possibly different order
}
for (unsigned int i = 451; i < NUMBER_OF_COMPUTORS; i++)
{
futureComputors[i] = makeId(i + 1000); // New IDs
}

bool result = calculateStableComputorIndex(futureComputors, currentComputors, tempBuffer);
ASSERT_TRUE(result);

// First 451 should keep their indices
for (unsigned int i = 0; i < 451; i++)
{
EXPECT_EQ(futureComputors[i], makeId(i + 1));
}

// Last 225 slots should have the new IDs
for (unsigned int i = 451; i < NUMBER_OF_COMPUTORS; i++)
{
EXPECT_EQ(futureComputors[i], makeId(i + 1000));
}
}

1 change: 1 addition & 0 deletions test/test.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@
<ClCompile Include="contract_qip.cpp" />
<ClCompile Include="custom_mining.cpp" />
<ClCompile Include="execution_fees.cpp" />
<ClCompile Include="stable_computor_index.cpp" />
<ClCompile Include="file_io.cpp" />
<ClCompile Include="qpi_collection.cpp" />
<ClCompile Include="qpi_date_time.cpp" />
Expand Down
1 change: 1 addition & 0 deletions test/test.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<ClCompile Include="virtual_memory.cpp" />
<ClCompile Include="custom_mining.cpp" />
<ClCompile Include="execution_fees.cpp" />
<ClCompile Include="stable_computor_index.cpp" />
<ClCompile Include="contract_qutil.cpp" />
<ClCompile Include="revenue.cpp" />
<ClCompile Include="time.cpp" />
Expand Down
Loading