diff --git a/CMakeLists.txt b/CMakeLists.txt
index f640e996..b89b96ec 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -61,6 +61,7 @@ option(BUILD_BENCHMARK "Build the EFI benchmark application" OFF)
option(BUILD_BINARY "Build the EFI application" ON)
option(USE_SANITIZER "Build test with sanitizer support (clang only)" ON)
option(LITE_WASM_SC "Enable testnet-lite-RAM Wasm smart contracts" OFF)
+option(ANT_WALKER "Build the ant-colony walker sidecar and its node client" ON)
set(QUBIC_STANDALONE_RELEASE ON CACHE INTERNAL "Standalone builds are the only supported configuration" FORCE)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build static dependencies" FORCE)
set(OPENSSL_USE_STATIC_LIBS TRUE CACHE BOOL "Use static OpenSSL" FORCE)
diff --git a/data/bpp9000.task b/data/bpp9000.task
index 8ffa5e17..38ce2351 100644
Binary files a/data/bpp9000.task and b/data/bpp9000.task differ
diff --git a/doc/ant_colony_mining.md b/doc/ant_colony_mining.md
index 881d6501..5e694af4 100644
--- a/doc/ant_colony_mining.md
+++ b/doc/ant_colony_mining.md
@@ -45,9 +45,10 @@ Standalone mining searches alone: every attempt starts from scratch. Ant-colony
- Every **mining identity** (a computor or candidate public key) owns its **own tree** - the colony is
a per-identity forest. A pool's workers extend the tree of the computor they mine for.
-- Each tree starts from a **virtual root**: a starting solution derived from that identity's public key
- and the epoch's spectrum digest. It is fixed for the epoch, identical every time you derive it, and
- is never stored or submitted.
+- Every tree starts from the same **virtual root**: one starting solution per epoch, derived from the
+ epoch's spectrum digest alone - identical for all identities, identical every time you derive it,
+ and never stored or submitted. All identities search from one shared origin; the trees branching
+ from it stay per-identity.
- To mine, you pick a **parent** (the root, or any node already in your tree), **inherit** it, vary it
under your nonce, and score the result.
- If the result **strictly beats the parent** and clears the epoch **threshold**, you **submit** it. On
@@ -58,7 +59,7 @@ miner starts from there instead of from scratch. The goal of the epoch is the si
found anywhere in the forest.
```
- virtual root (per identity, not stored)
+ virtual root (shared per epoch, not stored)
|
+----+----+
| |
@@ -73,20 +74,20 @@ Concretely, error gates every attachment: it only falls down a branch (a child m
and a *start* - a depth-1 child of the root - must clear the threshold.
```
- error = error count, lower is better threshold = 3838
+ error = error count, lower is better threshold = 4000
- root ~4044 raw a fresh root sits above 3838; a start must mutate below it
+ root ~4200 raw the epoch root (same for everyone) sits above 4000; a start must mutate below it
|
- +-- A 3790 <= threshold ACCEPT (depth-1 start)
+ +-- A 3900 <= threshold ACCEPT (depth-1 start)
| |
- | +-- B 3540 < 3790, beats A ACCEPT
+ | +-- B 3540 < 3900, beats A ACCEPT
| | |
| | +-- D 3120 < 3540, beats B ACCEPT
| | +-- E 3560 not < 3540 REJECT (must beat parent)
| |
- | +-- C 3700 < 3790, beats A ACCEPT
+ | +-- C 3700 < 3900, beats A ACCEPT
|
- +-- X 3900 > threshold REJECT (over threshold)
+ +-- X 4100 > threshold REJECT (over threshold)
Error only falls as you go deeper. The epoch winner is the single lowest-error node
found in any identity's forest.
@@ -96,7 +97,7 @@ At epoch end the node ranks every identity by its **single best** score and **ha
(the number of computors).
**Anti-spam deposit.** Each solution a computor publishes on-chain carries a **refundable
-1,000,000 QU deposit**, funded by the computor - not the worker. It is returned when the solution is
+1000000 QU deposit**, funded by the computor - not the worker. It is returned when the solution is
accepted **and** its claimed score matches the node's recompute; otherwise it is kept. So a computor
only publishes solutions it has already validated, and an honest, correct one costs nothing.
@@ -105,7 +106,7 @@ only publishes solutions it has already validated, and an honest, correct one co
## Part 2 - Miner / pool integration guide
**In short.** A miner works one identity's tree. It reads the epoch context, takes a **parent** (the
-identity's virtual root, or a node already in the tree), picks a canonical **nonce**, inherits the
+epoch's shared virtual root, or a node already in the tree), picks a canonical **nonce**, inherits the
parent's network, and **mutates and scores** it - reproducing the node's score exactly. If the result
**beats its parent** and **clears the threshold**, it hands the solution to the **computor**, which
re-checks it and **publishes it on-chain**; every node then recomputes the score, folds it into
@@ -117,8 +118,8 @@ scorer** - the tree, gates, deposit, and queries are the wrapper around it.
1. **Epoch context** - `REQUEST_ANT_EPOCH_CONTEXT` (public). Read the threshold, freshness window,
epoch spectrum digest, and child cap for this epoch, and **verify your task file** against the
returned `topologyHash` / `dataHash` (section 2.7a) before doing any work.
-2. **Get a starting point** - derive your identity's virtual root, or fetch an existing node you want
- to extend (`REQUEST_ANT_PARENT_ANN`).
+2. **Get a starting point** - derive the epoch's shared virtual root (from the spectrum digest), or
+ fetch an existing node you want to extend (`REQUEST_ANT_PARENT_ANN`).
3. **Pick a parent** - the root, or any node in your own tree.
4. **Search** - choose a nonce (section 2.2), inherit the parent LUT, run the mutation walk, score
(section 2.3).
@@ -153,17 +154,21 @@ knobs are not two solutions.
### 2.3 Scoring - bpp9000 (must be bit-exact)
Throughout, `publicKey` is the **mining identity you are extending** - the computor you mine for, which
-becomes the transaction's `sourcePublicKey`. Derive the root and the mutation seed from **that** key,
-not your worker key, or the node's recompute will not match yours.
+becomes the transaction's `sourcePublicKey`. The **mutation seed** derives from **that** key, not your
+worker key, or the node's recompute will not match yours. The **root** derives from no key at all -
+see below.
-**Root.** `deriveRootANN(publicKey, epochPool)`: `K12(publicKey)` seeds a per-neuron LUT from the
-epoch's random pool (the pool comes from the epoch-start spectrum digest). No mutation walk. Never
-stored. The same every time for the epoch.
+**Root.** `deriveRootANN(spectrumDigest, epochPool)`: `K12(spectrumDigest)` - the epoch-start
+spectrum digest from the epoch context - seeds a per-neuron LUT from the epoch's random pool (the
+pool itself also comes from that digest). No mutation walk. Never stored. **One root per epoch,
+identical for every identity**; per-identity variation enters only through the mutation seeds.
**Child.** `computeScoreFromParent(parentLUT, publicKey, nonce, anchorTickDigest)`:
1. Inherit `parentLUT`.
-2. `mutationSeed = K12(publicKey || nonce[3..31] || anchorTickDigest)` (`nonce[0..2]` zeroed).
+2. `mutationSeed = K12(publicKey || nonce || anchorTickDigest)` with `nonce[0..2]` zeroed in place
+ (the full 32-byte nonce is hashed, its first 3 bytes set to 0, not dropped) - still keyed by the
+ mining identity, so different identities walk differently from the shared root.
3. Walk `numberOfMutations = 100` steps. Each step rewrites `L` LUT entries. For the first `K` steps
accept a worse-or-equal result (**explore**); after that accept only better-or-equal (**exploit**);
one-step rollback on reject. Keep and return the **best** score seen. The best is seeded with the
@@ -181,8 +186,9 @@ Score is an error count in `[0, 8088]`; lower is better.
### 2.4 Accept rules
-A submission is accepted (`Valid` or `ValidNotStored`) only if **all** of these hold. The node checks
-in this order; the first failure is the reject reason:
+A submission is accepted (`Valid` or `ValidNotStored`) only if **all** of these hold. On failure the
+reject reason names the rule the node found violated (the exact evaluation order is an internal
+detail and can change):
| Check | Reject reason if it fails |
|-------|---------------------------|
@@ -201,14 +207,15 @@ recorded but the **deposit is kept** and the miner is **not ranked**.
**Starting a tree.** The root's record score is the worst possible value, so a first (depth-1) child
passes the "beats parent" check trivially - the **threshold is the only score gate** for starting a
-tree. A random root scores far above the threshold, so a start still requires real mutation.
+tree. The shared epoch root scores far above the threshold, so a start still requires real mutation -
+and every identity starts from the same score, so ranking differences reflect search effort only.
**`ValidNotStored`.** Accepted, refunded, and ranked exactly like `Valid`, but the per-epoch store was
full so the node was not persisted for others to extend. Ranking and refund are unaffected.
### 2.5 The deposit
-Every on-chain `AntColonyMiningSolutionTransaction` carries a **1,000,000 QU** deposit
+Every on-chain `AntColonyMiningSolutionTransaction` carries a **1000000 QU** deposit
(`SOLUTION_SECURITY_DEPOSIT`), funded by the **computor** that publishes it - not the miner (see 2.6).
It is refunded **iff** the solution is accepted (`Valid` / `ValidNotStored`) **and** the claimed score
equals the node's recompute; otherwise it is kept. So a computor risks its own deposit and therefore
@@ -232,8 +239,8 @@ BroadcastMessage { // 96-byte envelope
}
// then the payload:
AntSolutionBroadcastPayload { // 48 bytes
- unsigned int parentTick; // ABSOLUTE tick of the parent node (0 with the index below = root)
- unsigned int parentSolutionIndexInTick;
+ unsigned int parentTick; // ABSOLUTE tick of the parent node; the virtual root is (0, 0xFFFFFFFF)
+ unsigned int parentSolutionIndexInTick; // parent's index within its tick; 0xFFFFFFFF with parentTick 0 = root
unsigned int anchorTick; // ABSOLUTE tick number
unsigned int claimedScore;
m256i nonce; // the 32-byte nonce from 2.2
@@ -254,7 +261,7 @@ AntColonyMiningSolutionTransaction : Transaction { // 80-byte header + 48-byte
// --- Transaction header ---
m256i sourcePublicKey; // the COMPUTOR (tree owner); signs the tx and funds the deposit
m256i destinationPublicKey; // zero (NULL_ID)
- long long amount; // SOLUTION_SECURITY_DEPOSIT = 1,000,000 QU
+ long long amount; // SOLUTION_SECURITY_DEPOSIT = 1000000 QU
unsigned int tick; // publish tick
unsigned short inputType; // ANT_COLONY_MINING_SOLUTION_INPUT_TYPE = 12
unsigned short inputSize; // 48
@@ -281,6 +288,37 @@ becomes `sourcePublicKey` of the transaction. Workers hold no tree and post no o
hand solutions to the pool's computor, which pre-validates them and risks its own deposit only on
solutions it expects to be accepted and refunded. Fund the computor identity, not the workers.
+### 2.6a Submitting a root (depth-1) solution
+
+A root solution starts a tree: its parent is the epoch's shared virtual root, which is derived rather
+than stored, so you never fetch it. This is the first solution every identity submits, and it differs
+from extending an existing node only in how the parent is named and scored.
+
+1. **Parent is the root.** Set `parentRef = (parentTick = 0, parentSolutionIndexInTick = 0xFFFFFFFF)`.
+ Both fields are load-bearing: `(0, 0xFFFFFFFF)` is the only value the node reads as root; a `0`
+ tick with any other index is treated as a normal parent, found nowhere, and rejected with
+ `RejectParentNotRegistered`.
+2. **Derive the parent LUT yourself.** `deriveRootANN(spectrumDigest, epochPool)` from the epoch
+ context (section 2.3) - do **not** call `REQUEST_ANT_PARENT_ANN` for the root; it answers
+ `status = IS_ROOT` with no ANN payload precisely so you derive it locally. The root is identical
+ for every identity.
+3. **Search.** Pick a canonical nonce (section 2.2), inherit the derived root LUT, run the walk, and
+ take the best score - exactly as for any parent.
+4. **The only score gate is the threshold.** The root's record score is the worst possible value, so
+ the "strictly beats the parent" rule passes trivially; a root child is accepted on score iff its
+ score is `<=` the epoch threshold. The shared root scores far above the threshold, so a valid
+ start still requires real mutation - and since every identity starts from the same root score,
+ ranking reflects search effort alone.
+5. **Anchor and submit.** Choose a non-empty anchor tick within `freshnessWindow` of the publish tick
+ (section 2.3), fill the payload with the root `parentRef` above, and hand it to your computor
+ (section 2.6, stage 1). The computor publishes it as the usual `AntColonyMiningSolutionTransaction`.
+
+On the node, a root submission is recognized by `parentRef.isRoot()`: the parent lookup returns a
+null record (root is not a stored solution), the node derives the shared root itself to recompute
+your `claimedScore`, and root children are de-duplicated per miner because the root is shared by all
+identities. Once accepted, the node becomes a normal parent - extend it by copying its `selfTick` /
+`selfSolutionIndexInTick` from the identity-tree query (section 2.7b) into a child's `parentRef`.
+
### 2.7 Read queries
Three request/response pairs. **Identity tree** and **parent ANN** are **operator-signed**; **epoch
@@ -301,7 +339,7 @@ signature = sign(operatorSubseed, operatorPublicKey, digest) // 64 bytes, appe
Request: empty. Response `RespondAntEpochContext` (120 bytes, packed):
```
-m256i spectrumDigest; // epoch-start spectrum digest (seeds every root)
+m256i spectrumDigest; // epoch-start spectrum digest; IS the root seed (and seeds the pool)
m256i topologyHash; // canonical task topology-block hash (BPP9000_TOPOLOGY_HASH)
m256i dataHash; // canonical task data-block hash (BPP9000_DATA_HASH)
unsigned int threshold; // per-epoch accept bound
@@ -364,11 +402,11 @@ byte, the exact form the scorer consumes - no unpacking needed):
unsigned int parentRefTick;
unsigned int parentRefSolutionIndexInTick;
unsigned int annSizeBytes; // ANN LUT size when status is OK, else 0
-unsigned char status; // 0 = OK, 1 = NOT_FOUND, 2 = IS_ROOT (derive your own root instead)
+unsigned char status; // 0 = OK, 1 = NOT_FOUND, 2 = IS_ROOT (derive the epoch root instead)
unsigned char padding[3];
```
-**Canonical ANN layout.** The same byte form is used everywhere ANN bytes leave the node: this response, the snapshot pool, and the epoch export. Under the current bpp9000 parameters it is 1'728 bytes = 64 rows of 27:
+**Canonical ANN layout.** The same byte form is used everywhere ANN bytes leave the node: this response, the snapshot pool, and the epoch export. Under the current bpp9000 parameters it is 1728 bytes = 64 rows of 27:
```
row k, k = 0..45 LUT of neuron updatedNeuronIndices[k]: the k-th NON-INPUT neuron in
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index eb4cb27c..7cf11416 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -395,6 +395,10 @@ if(IS_CLANG)
endif()
target_link_libraries(qubic-cli PRIVATE m)
+if(ANT_WALKER)
+ target_compile_definitions(Qubic PRIVATE ANT_WALKER)
+endif()
+
# Configure linker settings based on compiler
if(IS_MSVC)
if(NOT LITE_WASM_SC)
diff --git a/src/Qubic.vcxproj b/src/Qubic.vcxproj
index 911cae3e..3db1b943 100644
--- a/src/Qubic.vcxproj
+++ b/src/Qubic.vcxproj
@@ -26,7 +26,6 @@
-
diff --git a/src/Qubic.vcxproj.filters b/src/Qubic.vcxproj.filters
index b222cf7d..6bfbe322 100644
--- a/src/Qubic.vcxproj.filters
+++ b/src/Qubic.vcxproj.filters
@@ -242,9 +242,6 @@
contracts
-
- contracts
-
platform
diff --git a/src/contract_core/contract_def.h b/src/contract_core/contract_def.h
index 074a6dd6..91e2f122 100644
--- a/src/contract_core/contract_def.h
+++ b/src/contract_core/contract_def.h
@@ -145,11 +145,7 @@
#define CONTRACT_INDEX QSWAP_CONTRACT_INDEX
#define CONTRACT_STATE_TYPE QSWAP
#define CONTRACT_STATE2_TYPE QSWAP2
-#ifdef OLD_QSWAP
-#include "contracts/Qswap_old.h"
-#else
#include "contracts/Qswap.h"
-#endif
#undef CONTRACT_INDEX
#undef CONTRACT_STATE_TYPE
diff --git a/src/contracts/QIP.h b/src/contracts/QIP.h
index bec11e1e..de4483d2 100644
--- a/src/contracts/QIP.h
+++ b/src/contracts/QIP.h
@@ -29,6 +29,7 @@ enum QIPLogInfo {
QIP_invalidIssuer = 15,
QIP_qxAskOrderFound = 16,
QIP_icoAlreadyExists = 17,
+ QIP_returnFundsError = 18,
};
struct QIPLogger
@@ -715,9 +716,16 @@ struct QIP : public ContractBase
}
}
- if (locals.log._type == QIP_success && locals.buyerInfo.isReturned)
+ if (locals.log._type == QIP_success)
{
- locals.log._type = QIPLogInfo::QIP_fundsAlreadyReturned;
+ if (qpi.epoch() <= locals.ico.startEpoch + 2)
+ {
+ locals.log._type = QIPLogInfo::QIP_returnFundsError;
+ }
+ else if (locals.buyerInfo.isReturned)
+ {
+ locals.log._type = QIPLogInfo::QIP_fundsAlreadyReturned;
+ }
}
if (locals.log._type != QIP_success)
diff --git a/src/contracts/Qswap_old.h b/src/contracts/Qswap_old.h
deleted file mode 100644
index ecf63457..00000000
--- a/src/contracts/Qswap_old.h
+++ /dev/null
@@ -1,2436 +0,0 @@
-using namespace QPI;
-
-// Log types enum for QSWAP contract
-enum QSWAPLogInfo {
- QSWAPAddLiquidity = 4,
- QSWAPRemoveLiquidity = 5,
- QSWAPSwapExactQuForAsset = 6,
- QSWAPSwapQuForExactAsset = 7,
- QSWAPSwapExactAssetForQu = 8,
- QSWAPSwapAssetForExactQu = 9,
- QSWAPFailedDistribution = 10,
-};
-
-// FIXED CONSTANTS
-constexpr uint64 QSWAP_INITIAL_MAX_POOL = 16384;
-constexpr uint64 QSWAP_MAX_POOL = QSWAP_INITIAL_MAX_POOL * X_MULTIPLIER;
-constexpr uint64 QSWAP_MAX_USER_PER_POOL = 256;
-constexpr sint64 QSWAP_MIN_LIQUIDITY = 1000;
-constexpr uint32 QSWAP_SWAP_FEE_BASE = 10000;
-constexpr uint32 QSWAP_FEE_BASE_100 = 100;
-
-struct QSWAP2
-{
-};
-
-struct QSWAP : public ContractBase
-{
-public:
- // Logging message structures
- struct AddLiquidityMessage
- {
- uint32 _contractIndex;
- uint32 _type;
- id assetIssuer;
- uint64 assetName;
- sint64 userIncreaseLiquidity;
- sint64 quAmount;
- sint64 assetAmount;
- sint8 _terminator;
- };
-
- struct RemoveLiquidityMessage
- {
- uint32 _contractIndex;
- uint32 _type;
- sint64 quAmount;
- sint64 assetAmount;
- sint8 _terminator;
- };
-
- struct SwapMessage
- {
- uint32 _contractIndex;
- uint32 _type;
- id assetIssuer;
- uint64 assetName;
- sint64 assetAmountIn;
- sint64 assetAmountOut;
- sint8 _terminator;
- };
-
- struct FailedDistributionMessage
- {
- uint32 _contractIndex;
- uint32 _type;
- id dst;
- uint64 amount;
- sint8 _terminator;
- };
-
- // Types used by state fields
- struct PoolBasicState
- {
- id poolID;
- sint64 reservedQuAmount;
- sint64 reservedAssetAmount;
- sint64 totalLiquidity;
- };
-
- struct LiquidityInfo
- {
- id entity;
- sint64 liquidity;
- };
-
- struct StateData
- {
- uint32 swapFeeRate; // e.g. 30: 0.3% (base: 10_000)
- uint32 investRewardsFeeRate;// 3: 3% of swap fees to Invest & Rewards (base: 100)
- uint32 shareholderFeeRate; // 27: 27% of swap fees to SC shareholders (base: 100)
- uint32 poolCreationFeeRate; // e.g. 10: 10% (base: 100)
-
- id investRewardsId;
- uint64 investRewardsEarnedFee;
- uint64 investRewardsDistributedAmount;
-
- uint64 shareholderEarnedFee;
- uint64 shareholderDistributedAmount;
-
- Array mPoolBasicStates;
- Collection mLiquidities;
-
- uint32 qxFeeRate; // 5: 5% of swap fees to QX (base: 100)
- uint32 burnFeeRate; // 1: 1% of swap fees burned (base: 100)
-
- uint64 qxEarnedFee;
- uint64 qxDistributedAmount;
-
- uint64 burnEarnedFee; // Total burn fees collected (to be burned in END_TICK)
- uint64 burnedAmount; // Total amount actually burned
-
- uint32 cachedIssuanceFee;
- uint32 cachedTransferFee;
- };
-
- struct Fees_input
- {
- };
- struct Fees_output
- {
- uint32 assetIssuanceFee; // Amount of qus
- uint32 poolCreationFee; // Amount of qus
- uint32 transferFee; // Amount of qus
-
- uint32 swapFee; // 30 -> 0.3%
- uint32 shareholderFee; // 27 -> 27% of swap fee, for SC shareholders
- uint32 investRewardsFee; // 3 -> 3% of swap fee, for Invest & Rewards
- uint32 qxFee; // 5 -> 5% of swap fee, for QX
- uint32 burnFee; // 1 -> 1% of swap fee, burned
- };
-
- struct InvestRewardsInfo_input
- {
- };
- struct InvestRewardsInfo_output
- {
- uint32 investRewardsFee; // 3 -> 3% of swap fee
- id investRewardsId;
- };
-
- struct SetInvestRewardsInfo_input
- {
- id newInvestRewardsId;
- };
- struct SetInvestRewardsInfo_output
- {
- bit success;
- };
-
- struct GetPoolBasicState_input
- {
- id assetIssuer;
- uint64 assetName;
- };
- struct GetPoolBasicState_output
- {
- sint64 poolExists;
- sint64 reservedQuAmount;
- sint64 reservedAssetAmount;
- sint64 totalLiquidity;
- };
-
- struct GetLiquidityOf_input
- {
- id assetIssuer;
- uint64 assetName;
- id account;
- };
- struct GetLiquidityOf_output
- {
- sint64 liquidity;
- };
-
- struct QuoteExactQuInput_input
- {
- id assetIssuer;
- uint64 assetName;
- sint64 quAmountIn;
- };
- struct QuoteExactQuInput_output
- {
- sint64 assetAmountOut;
- };
-
- struct QuoteExactQuOutput_input{
- id assetIssuer;
- uint64 assetName;
- sint64 quAmountOut;
- };
- struct QuoteExactQuOutput_output
- {
- sint64 assetAmountIn;
- };
-
- struct QuoteExactAssetInput_input
- {
- id assetIssuer;
- uint64 assetName;
- sint64 assetAmountIn;
- };
- struct QuoteExactAssetInput_output
- {
- sint64 quAmountOut;
- };
-
- struct QuoteExactAssetOutput_input
- {
- id assetIssuer;
- uint64 assetName;
- sint64 assetAmountOut;
- };
- struct QuoteExactAssetOutput_output
- {
- sint64 quAmountIn;
- };
-
- struct IssueAsset_input
- {
- uint64 assetName;
- sint64 numberOfShares;
- uint64 unitOfMeasurement;
- sint8 numberOfDecimalPlaces;
- };
- struct IssueAsset_output
- {
- sint64 issuedNumberOfShares;
- };
-
- struct CreatePool_input
- {
- id assetIssuer;
- uint64 assetName;
- };
- struct CreatePool_output
- {
- bit success;
- };
-
- struct TransferShareOwnershipAndPossession_input
- {
- id assetIssuer;
- uint64 assetName;
- id newOwnerAndPossessor;
- sint64 amount;
- };
- struct TransferShareOwnershipAndPossession_output
- {
- sint64 transferredAmount;
- };
-
- /**
- * @param quAmountADesired The amount of tokenA to add as liquidity if the B/A price is <= amountBDesired/amountADesired (A depreciates).
- * @param assetAmountBDesired The amount of tokenB to add as liquidity if the A/B price is <= amountADesired/amountBDesired (B depreciates).
- * @param quAmountMin Bounds the extent to which the B/A price can go up before the transaction reverts. Must be <= amountADesired.
- * @param assetAmountMin Bounds the extent to which the A/B price can go up before the transaction reverts. Must be <= amountBDesired.
- */
- struct AddLiquidity_input
- {
- id assetIssuer;
- uint64 assetName;
- sint64 assetAmountDesired;
- sint64 quAmountMin;
- sint64 assetAmountMin;
- };
- struct AddLiquidity_output
- {
- sint64 userIncreaseLiquidity;
- sint64 quAmount;
- sint64 assetAmount;
- };
-
- struct RemoveLiquidity_input
- {
- id assetIssuer;
- uint64 assetName;
- sint64 burnLiquidity;
- sint64 quAmountMin;
- sint64 assetAmountMin;
- };
-
- struct RemoveLiquidity_output
- {
- sint64 quAmount;
- sint64 assetAmount;
- };
-
- struct SwapExactQuForAsset_input
- {
- id assetIssuer;
- uint64 assetName;
- sint64 assetAmountOutMin;
- };
- struct SwapExactQuForAsset_output
- {
- sint64 assetAmountOut;
- };
-
- struct SwapQuForExactAsset_input
- {
- id assetIssuer;
- uint64 assetName;
- sint64 assetAmountOut;
- };
- struct SwapQuForExactAsset_output
- {
- sint64 quAmountIn;
- };
-
- struct SwapExactAssetForQu_input
- {
- id assetIssuer;
- uint64 assetName;
- sint64 assetAmountIn;
- sint64 quAmountOutMin;
- };
- struct SwapExactAssetForQu_output
- {
- sint64 quAmountOut;
- };
-
- struct SwapAssetForExactQu_input
- {
- id assetIssuer;
- uint64 assetName;
- sint64 assetAmountInMax;
- sint64 quAmountOut;
- };
- struct SwapAssetForExactQu_output
- {
- sint64 assetAmountIn;
- };
-
- struct TransferShareManagementRights_input
- {
- Asset asset;
- sint64 numberOfShares;
- uint32 newManagingContractIndex;
- };
- struct TransferShareManagementRights_output
- {
- sint64 transferredNumberOfShares;
- };
-
-protected:
-
-
- inline static sint64 min(sint64 a, sint64 b)
- {
- return (a < b) ? a : b;
- }
-
- // find the sqrt of a*b
- inline static sint64 sqrt(sint64& a, sint64& b, uint128& prod, uint128& y, uint128& z)
- {
- if (a == b)
- {
- return a;
- }
-
- prod = uint128(a) * uint128(b);
-
- y = uint128(0);
-
- z = uint128(1) << uint128(0, 126);
- while (z > prod)
- {
- z >>= uint128(0, 2);
- }
-
- while (z)
- {
- if (prod >= y + z)
- {
- prod -= y + z;
- y = (y >> uint128(0, 1)) + z;
- }
- else
- {
- y >>= uint128(0, 1);
- }
- z >>= uint128(0, 2);
- }
-
- return sint64(y.low);
- }
-
- inline static sint64 quoteEquivalentAmountB(sint64& amountADesired, sint64& reserveA, sint64& reserveB, uint128& tmpRes)
- {
- // amountDesired * reserveB / reserveA
- tmpRes = div(uint128(amountADesired) * uint128(reserveB), uint128(reserveA));
-
- if ((tmpRes.high != 0)|| (tmpRes.low > 0x7FFFFFFFFFFFFFFF))
- {
- return -1;
- }
- else
- {
- return sint64(tmpRes.low);
- }
- }
-
- // reserveIn * reserveOut = (reserveIn + amountIn * (1-fee)) * (reserveOut - x)
- // x = reserveOut * amountIn * (1-fee) / (reserveIn + amountIn * (1-fee))
- inline static sint64 getAmountOutTakeFeeFromInToken(
- sint64& amountIn,
- sint64& reserveIn,
- sint64& reserveOut,
- uint32 fee,
- uint128& amountInWithFee,
- uint128& numerator,
- uint128& denominator,
- uint128& tmpRes
- )
- {
- if (amountIn >= MAX_AMOUNT) return -1;
-
- amountInWithFee = uint128(amountIn) * uint128(QSWAP_SWAP_FEE_BASE - fee);
- numerator = uint128(reserveOut) * amountInWithFee;
- denominator = uint128(reserveIn) * uint128(QSWAP_SWAP_FEE_BASE) + amountInWithFee;
-
- // numerator / denominator
- tmpRes = div(numerator, denominator);
- if ((tmpRes.high != 0) || (tmpRes.low > 0x7FFFFFFFFFFFFFFF))
- {
- return -1;
- }
- else
- {
- return sint64(tmpRes.low);
- }
- }
-
- // reserveIn * reserveOut = (reserveIn + x * (1-fee)) * (reserveOut - amountOut)
- // x = (reserveIn * amountOut * 10000) / ((reserveOut - amountOut) * (10000 - fee))
- inline static sint64 getAmountInTakeFeeFromInToken(sint64& amountOut, sint64& reserveIn, sint64& reserveOut, uint32 fee, uint128& numerator, uint128& denominator, uint128& tmpRes)
- {
- if (amountOut >= MAX_AMOUNT) return -1;
-
- // Calculate full numerator first to avoid premature truncation
- numerator = uint128(reserveIn) * uint128(amountOut) * uint128(QSWAP_SWAP_FEE_BASE);
- denominator = uint128(reserveOut - amountOut) * uint128(QSWAP_SWAP_FEE_BASE - fee);
-
- // Perform single division at the end
- // Use floor + 1 to ensure user pays at least enough (protects LPs)
- tmpRes = div(numerator, denominator) + uint128(1);
-
- if ((tmpRes.high != 0) || (tmpRes.low > 0x7FFFFFFFFFFFFFFF))
- {
- return -1;
- }
- else
- {
- return sint64(tmpRes.low);
- }
- }
-
- // (reserveIn + amountIn) * (reserveOut - x) = reserveIn * reserveOut
- // x = reserveOut * amountIn / (reserveIn + amountIn)
- // NOTE: Despite the name, this returns the GROSS output (before fee deduction).
- // The fee parameter is unused here because fee is applied separately by the caller.
- // This is intentional: the caller needs the gross value for fee distribution calculation.
- inline static sint64 getAmountOutTakeFeeFromOutToken(sint64& amountIn, sint64& reserveIn, sint64& reserveOut, uint32 fee, uint128& numerator, uint128& denominator, uint128& tmpRes)
- {
- if (amountIn >= MAX_AMOUNT) return -1;
-
- numerator = uint128(reserveOut) * uint128(amountIn);
- denominator = uint128(reserveIn + amountIn);
-
- tmpRes = div(numerator, denominator);
- if ((tmpRes.high != 0)|| (tmpRes.low > 0x7FFFFFFFFFFFFFFF))
- {
- return -1;
- }
- else
- {
- return sint64(tmpRes.low);
- }
- }
-
- // (reserveIn + x) * (reserveOut - amountOut/(1 - fee)) = reserveIn * reserveOut
- // x = (reserveIn * amountOut * 10000) / (reserveOut * (10000-fee) - amountOut * 10000)
- inline static sint64 getAmountInTakeFeeFromOutToken(sint64& amountOut, sint64& reserveIn, sint64& reserveOut, uint32 fee, uint128& numerator, uint128& denominator, uint128& tmpRes)
- {
- if (amountOut >= MAX_AMOUNT) return -1;
-
- // Calculate full numerator to avoid premature truncation
- numerator = uint128(reserveIn) * uint128(amountOut) * uint128(QSWAP_SWAP_FEE_BASE);
-
- // Check: reserveOut * (1-fee) must be greater than amountOut
- // Scale reserveOut by (10000-fee) and amountOut by 10000 for comparison
- // Use tmpRes and denominator temporarily for the comparison
- tmpRes = uint128(reserveOut) * uint128(QSWAP_SWAP_FEE_BASE - fee);
- denominator = uint128(amountOut) * uint128(QSWAP_SWAP_FEE_BASE);
-
- if (tmpRes <= denominator)
- {
- return -1;
- }
-
- denominator = tmpRes - denominator;
-
- // Use floor + 1 to ensure user pays at least enough (protects LPs)
- tmpRes = div(numerator, denominator) + uint128(1);
-
- if ((tmpRes.high != 0) || (tmpRes.low > 0x7FFFFFFFFFFFFFFF))
- {
- return -1;
- }
- else
- {
- return sint64(tmpRes.low);
- }
- }
-
- PUBLIC_FUNCTION(Fees)
- {
- output.assetIssuanceFee = state.get().cachedIssuanceFee;
- output.poolCreationFee = uint32(div(uint64(state.get().cachedIssuanceFee) * uint64(state.get().poolCreationFeeRate), uint64(QSWAP_FEE_BASE_100)));
- output.transferFee = state.get().cachedTransferFee;
- output.swapFee = state.get().swapFeeRate;
- output.shareholderFee = state.get().shareholderFeeRate;
- output.investRewardsFee = state.get().investRewardsFeeRate;
- output.qxFee = state.get().qxFeeRate;
- output.burnFee = state.get().burnFeeRate;
- }
-
- struct GetPoolBasicState_locals
- {
- id poolID;
- sint64 poolSlot;
- PoolBasicState poolBasicState;
- uint32 i0;
- };
-
- PUBLIC_FUNCTION_WITH_LOCALS(GetPoolBasicState)
- {
- output.poolExists = 0;
- output.totalLiquidity = -1;
- output.reservedAssetAmount = -1;
- output.reservedQuAmount = -1;
-
- // asset not issued
- if (!qpi.isAssetIssued(input.assetIssuer, input.assetName))
- {
- return;
- }
-
- locals.poolID = input.assetIssuer;
- locals.poolID.u64._3 = input.assetName;
-
- locals.poolSlot = NULL_INDEX;
- for (locals.i0 = 0; locals.i0 < QSWAP_MAX_POOL; locals.i0++)
- {
- if (state.get().mPoolBasicStates.get(locals.i0).poolID == locals.poolID)
- {
- locals.poolSlot = locals.i0;
- break;
- }
- }
-
- if (locals.poolSlot == NULL_INDEX)
- {
- return;
- }
-
- output.poolExists = 1;
-
- locals.poolBasicState = state.get().mPoolBasicStates.get(locals.poolSlot);
-
- output.reservedQuAmount = locals.poolBasicState.reservedQuAmount;
- output.reservedAssetAmount = locals.poolBasicState.reservedAssetAmount;
- output.totalLiquidity = locals.poolBasicState.totalLiquidity;
- }
-
- struct GetLiquidityOf_locals
- {
- id poolID;
- sint64 liqElementIndex;
- };
-
- PUBLIC_FUNCTION_WITH_LOCALS(GetLiquidityOf)
- {
- output.liquidity = 0;
-
- locals.poolID = input.assetIssuer;
- locals.poolID.u64._3 = input.assetName;
-
- locals.liqElementIndex = state.get().mLiquidities.headIndex(locals.poolID, 0);
-
- while (locals.liqElementIndex != NULL_INDEX)
- {
- if (state.get().mLiquidities.element(locals.liqElementIndex).entity == input.account)
- {
- output.liquidity = state.get().mLiquidities.element(locals.liqElementIndex).liquidity;
- return;
- }
- locals.liqElementIndex = state.get().mLiquidities.nextElementIndex(locals.liqElementIndex);
- }
- }
-
- struct QuoteExactQuInput_locals
- {
- id poolID;
- sint64 poolSlot;
- PoolBasicState poolBasicState;
-
- uint32 i0;
- uint128 i1, i2, i3, i4;
- };
-
- PUBLIC_FUNCTION_WITH_LOCALS(QuoteExactQuInput)
- {
- output.assetAmountOut = -1;
-
- if (input.quAmountIn <= 0)
- {
- return;
- }
-
- locals.poolID = input.assetIssuer;
- locals.poolID.u64._3 = input.assetName;
-
- locals.poolSlot = -1;
- for (locals.i0 = 0; locals.i0 < QSWAP_MAX_POOL; locals.i0 ++)
- {
- if (state.get().mPoolBasicStates.get(locals.i0).poolID == locals.poolID)
- {
- locals.poolSlot = locals.i0;
- break;
- }
- }
-
- // no available slot for new pool
- if (locals.poolSlot == -1)
- {
- return;
- }
-
- locals.poolBasicState = state.get().mPoolBasicStates.get(locals.poolSlot);
-
- // no liquidity in the pool
- if (locals.poolBasicState.totalLiquidity == 0)
- {
- return;
- }
-
- output.assetAmountOut = getAmountOutTakeFeeFromInToken(
- input.quAmountIn,
- locals.poolBasicState.reservedQuAmount,
- locals.poolBasicState.reservedAssetAmount,
- state.get().swapFeeRate,
- locals.i1,
- locals.i2,
- locals.i3,
- locals.i4
- );
- }
-
- struct QuoteExactQuOutput_locals
- {
- id poolID;
- sint64 poolSlot;
- PoolBasicState poolBasicState;
-
- uint32 i0;
- uint128 i1, i2, i3;
- };
-
- PUBLIC_FUNCTION_WITH_LOCALS(QuoteExactQuOutput)
- {
- output.assetAmountIn = -1;
-
- if (input.quAmountOut <= 0)
- {
- return;
- }
-
- locals.poolID = input.assetIssuer;
- locals.poolID.u64._3 = input.assetName;
-
- locals.poolSlot = -1;
- for (locals.i0 = 0; locals.i0 < QSWAP_MAX_POOL; locals.i0 ++)
- {
- if (state.get().mPoolBasicStates.get(locals.i0).poolID == locals.poolID)
- {
- locals.poolSlot = locals.i0;
- break;
- }
- }
-
- // no available slot for new pool
- if (locals.poolSlot == -1)
- {
- return;
- }
-
- locals.poolBasicState = state.get().mPoolBasicStates.get(locals.poolSlot);
-
- // no liquidity in the pool
- if (locals.poolBasicState.totalLiquidity == 0)
- {
- return;
- }
-
- if (input.quAmountOut >= locals.poolBasicState.reservedQuAmount)
- {
- return;
- }
-
- output.assetAmountIn = getAmountInTakeFeeFromOutToken(
- input.quAmountOut,
- locals.poolBasicState.reservedAssetAmount,
- locals.poolBasicState.reservedQuAmount,
- state.get().swapFeeRate,
- locals.i1,
- locals.i2,
- locals.i3
- );
- }
-
- struct QuoteExactAssetInput_locals
- {
- id poolID;
- sint64 poolSlot;
- PoolBasicState poolBasicState;
- sint64 quAmountOutWithFee;
-
- uint32 i0;
- uint128 i1, i2, i3;
- };
-
- PUBLIC_FUNCTION_WITH_LOCALS(QuoteExactAssetInput)
- {
- output.quAmountOut = -1;
-
- if (input.assetAmountIn <= 0)
- {
- return;
- }
-
- locals.poolID = input.assetIssuer;
- locals.poolID.u64._3 = input.assetName;
-
- locals.poolSlot = -1;
- for (locals.i0 = 0; locals.i0 < QSWAP_MAX_POOL; locals.i0 ++)
- {
- if (state.get().mPoolBasicStates.get(locals.i0).poolID == locals.poolID)
- {
- locals.poolSlot = locals.i0;
- break;
- }
- }
-
- // no available slot for new pool
- if (locals.poolSlot == -1)
- {
- return;
- }
-
- locals.poolBasicState = state.get().mPoolBasicStates.get(locals.poolSlot);
-
- // no liquidity in the pool
- if (locals.poolBasicState.totalLiquidity == 0)
- {
- return;
- }
-
- locals.quAmountOutWithFee = getAmountOutTakeFeeFromOutToken(
- input.assetAmountIn,
- locals.poolBasicState.reservedAssetAmount,
- locals.poolBasicState.reservedQuAmount,
- state.get().swapFeeRate,
- locals.i1,
- locals.i2,
- locals.i3
- );
-
- // above call overflow
- if (locals.quAmountOutWithFee == -1)
- {
- return;
- }
-
- // amount * (1-fee), no overflow risk
- output.quAmountOut = sint64(div(
- uint128(locals.quAmountOutWithFee) * uint128(QSWAP_SWAP_FEE_BASE - state.get().swapFeeRate),
- uint128(QSWAP_SWAP_FEE_BASE)
- ).low);
- }
-
- struct QuoteExactAssetOutput_locals
- {
- id poolID;
- sint64 poolSlot;
- PoolBasicState poolBasicState;
-
- uint32 i0;
- uint128 i1, i2, i3;
- };
-
- PUBLIC_FUNCTION_WITH_LOCALS(QuoteExactAssetOutput)
- {
- output.quAmountIn = -1;
-
- if (input.assetAmountOut <= 0)
- {
- return;
- }
-
- locals.poolID = input.assetIssuer;
- locals.poolID.u64._3 = input.assetName;
-
- locals.poolSlot = -1;
- for (locals.i0 = 0; locals.i0 < QSWAP_MAX_POOL; locals.i0 ++)
- {
- if (state.get().mPoolBasicStates.get(locals.i0).poolID == locals.poolID)
- {
- locals.poolSlot = locals.i0;
- break;
- }
- }
-
- // no available slot for new pool
- if (locals.poolSlot == -1)
- {
- return;
- }
-
- locals.poolBasicState = state.get().mPoolBasicStates.get(locals.poolSlot);
-
- // no liquidity in the pool
- if (locals.poolBasicState.totalLiquidity == 0)
- {
- return;
- }
-
- if (input.assetAmountOut >= locals.poolBasicState.reservedAssetAmount)
- {
- return;
- }
-
- output.quAmountIn = getAmountInTakeFeeFromInToken(
- input.assetAmountOut,
- locals.poolBasicState.reservedQuAmount,
- locals.poolBasicState.reservedAssetAmount,
- state.get().swapFeeRate,
- locals.i1,
- locals.i2,
- locals.i3
- );
- }
-
- PUBLIC_FUNCTION(InvestRewardsInfo)
- {
- output.investRewardsFee = state.get().investRewardsFeeRate;
- output.investRewardsId = state.get().investRewardsId;
- }
-
-//
-// procedure
-//
- PUBLIC_PROCEDURE(IssueAsset)
- {
- output.issuedNumberOfShares = 0;
- if ((qpi.invocationReward() < state.get().cachedIssuanceFee))
- {
- if (qpi.invocationReward() > 0)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- }
- return;
- }
-
- // check the validity of input
- if ((input.numberOfShares <= 0) || (input.numberOfDecimalPlaces < 0))
- {
- if (qpi.invocationReward() > 0)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- }
- return;
- }
-
- // asset already issued
- if (qpi.isAssetIssued(qpi.invocator(), input.assetName))
- {
- if (qpi.invocationReward() > 0 )
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- }
- return;
- }
-
- output.issuedNumberOfShares = qpi.issueAsset(
- input.assetName,
- qpi.invocator(),
- input.numberOfDecimalPlaces,
- input.numberOfShares,
- input.unitOfMeasurement
- );
-
- if (output.issuedNumberOfShares == 0)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
- else
- {
- if (qpi.invocationReward() > state.get().cachedIssuanceFee)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward() - state.get().cachedIssuanceFee);
- }
- state.mut().shareholderEarnedFee += state.get().cachedIssuanceFee;
- }
- }
-
- struct CreatePool_locals
- {
- id poolID;
- sint64 poolSlot;
- PoolBasicState poolBasicState;
- uint32 poolCreationFee;
-
- uint32 i0, i1;
- };
-
- // create uniswap like pool
- // TODO: reject if there is no shares avaliabe shares in current contract, e.g. asset is issue in contract qx
- PUBLIC_PROCEDURE_WITH_LOCALS(CreatePool)
- {
- output.success = false;
-
- locals.poolCreationFee = uint32(div(uint64(state.get().cachedIssuanceFee) * uint64(state.get().poolCreationFeeRate), uint64(QSWAP_FEE_BASE_100)));
-
- // fee check
- if (qpi.invocationReward() < locals.poolCreationFee)
- {
- if (qpi.invocationReward() > 0)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- }
- return;
- }
-
- // asset no exist
- if (!qpi.isAssetIssued(input.assetIssuer, input.assetName))
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- locals.poolID = input.assetIssuer;
- locals.poolID.u64._3 = input.assetName;
-
- // check if pool already exist
- for (locals.i0 = 0; locals.i0 < QSWAP_MAX_POOL ; locals.i0 ++ )
- {
- if (state.get().mPoolBasicStates.get(locals.i0).poolID == locals.poolID)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
- }
-
- // find an vacant pool slot
- locals.poolSlot = -1;
- for (locals.i1 = 0; locals.i1 < QSWAP_MAX_POOL; locals.i1 ++)
- {
- if (state.get().mPoolBasicStates.get(locals.i1).poolID == id(0,0,0,0))
- {
- locals.poolSlot = locals.i1;
- break;
- }
- }
-
- // no available slot for new pool
- if (locals.poolSlot == -1)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- locals.poolBasicState.poolID = locals.poolID;
- locals.poolBasicState.reservedAssetAmount = 0;
- locals.poolBasicState.reservedQuAmount = 0;
- locals.poolBasicState.totalLiquidity = 0;
-
- state.mut().mPoolBasicStates.set(locals.poolSlot, locals.poolBasicState);
-
- if(qpi.invocationReward() > locals.poolCreationFee)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward() - locals.poolCreationFee );
- }
- state.mut().shareholderEarnedFee += locals.poolCreationFee;
-
- output.success = true;
- }
-
-
- struct AddLiquidity_locals
- {
- AddLiquidityMessage addLiquidityMessage;
- id poolID;
- sint64 poolSlot;
- PoolBasicState poolBasicState;
- LiquidityInfo tmpLiquidity;
-
- sint64 userLiquidityElementIndex;
- sint64 quAmountDesired;
-
- sint64 quTransferAmount;
- sint64 assetTransferAmount;
- sint64 quOptimalAmount;
- sint64 assetOptimalAmount;
- sint64 increaseLiquidity;
- sint64 reservedAssetAmountBefore;
- sint64 reservedAssetAmountAfter;
-
- uint128 tmpIncLiq0;
- uint128 tmpIncLiq1;
-
- uint32 i0;
- uint128 i1, i2, i3;
- };
-
- PUBLIC_PROCEDURE_WITH_LOCALS(AddLiquidity)
- {
- output.userIncreaseLiquidity = 0;
- output.assetAmount = 0;
- output.quAmount = 0;
-
- // add liquidity must stake both qu and asset
- if (qpi.invocationReward() <= 0)
- {
- return;
- }
-
- locals.quAmountDesired = qpi.invocationReward();
-
- // check the vadility of input params
- if ((input.assetAmountDesired <= 0) ||
- (input.quAmountMin < 0) ||
- (input.assetAmountMin < 0))
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- locals.poolID = input.assetIssuer;
- locals.poolID.u64._3 = input.assetName;
-
- // check the pool existance
- locals.poolSlot = -1;
- for (locals.i0 = 0; locals.i0 < QSWAP_MAX_POOL; locals.i0 ++)
- {
- if (state.get().mPoolBasicStates.get(locals.i0).poolID == locals.poolID)
- {
- locals.poolSlot = locals.i0;
- break;
- }
- }
-
- if (locals.poolSlot == -1)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- locals.poolBasicState = state.get().mPoolBasicStates.get(locals.poolSlot);
-
- // check if pool state meet the input condition before desposit
- // and confirm the final qu and asset amount to stake
- if (locals.poolBasicState.totalLiquidity == 0)
- {
- locals.quTransferAmount = locals.quAmountDesired;
- locals.assetTransferAmount = input.assetAmountDesired;
- }
- else
- {
- locals.assetOptimalAmount = quoteEquivalentAmountB(
- locals.quAmountDesired,
- locals.poolBasicState.reservedQuAmount,
- locals.poolBasicState.reservedAssetAmount,
- locals.i1
- );
- // overflow
- if (locals.assetOptimalAmount == -1)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return ;
- }
-
- if (locals.assetOptimalAmount <= input.assetAmountDesired )
- {
- if (locals.assetOptimalAmount < input.assetAmountMin)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return ;
- }
- locals.quTransferAmount = locals.quAmountDesired;
- locals.assetTransferAmount = locals.assetOptimalAmount;
- }
- else
- {
- locals.quOptimalAmount = quoteEquivalentAmountB(
- input.assetAmountDesired,
- locals.poolBasicState.reservedAssetAmount,
- locals.poolBasicState.reservedQuAmount,
- locals.i1
- );
- // overflow
- if (locals.quOptimalAmount == -1)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return ;
- }
- if (locals.quOptimalAmount > locals.quAmountDesired)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return ;
- }
- if (locals.quOptimalAmount < input.quAmountMin)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return ;
- }
- locals.quTransferAmount = locals.quOptimalAmount;
- locals.assetTransferAmount = input.assetAmountDesired;
- }
- }
-
- // check if the qu is enough
- if (qpi.invocationReward() < locals.quTransferAmount)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- // check if the asset is enough
- if (qpi.numberOfPossessedShares(
- input.assetName,
- input.assetIssuer,
- qpi.invocator(),
- qpi.invocator(),
- SELF_INDEX,
- SELF_INDEX
- ) < locals.assetTransferAmount)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- // for pool's initial mint
- if (locals.poolBasicState.totalLiquidity == 0)
- {
- locals.increaseLiquidity = sqrt(locals.quTransferAmount, locals.assetTransferAmount, locals.i1, locals.i2, locals.i3);
-
- if (locals.increaseLiquidity < QSWAP_MIN_LIQUIDITY )
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- locals.reservedAssetAmountBefore = qpi.numberOfPossessedShares(
- input.assetName,
- input.assetIssuer,
- SELF,
- SELF,
- SELF_INDEX,
- SELF_INDEX
- );
- qpi.transferShareOwnershipAndPossession(
- input.assetName,
- input.assetIssuer,
- qpi.invocator(),
- qpi.invocator(),
- locals.assetTransferAmount,
- SELF
- );
- locals.reservedAssetAmountAfter = qpi.numberOfPossessedShares(
- input.assetName,
- input.assetIssuer,
- SELF,
- SELF,
- SELF_INDEX,
- SELF_INDEX
- );
-
- if (locals.reservedAssetAmountAfter - locals.reservedAssetAmountBefore < locals.assetTransferAmount)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- // permanently lock the first MINIMUM_LIQUIDITY tokens
- locals.tmpLiquidity.entity = SELF;
- locals.tmpLiquidity.liquidity = QSWAP_MIN_LIQUIDITY;
- state.mut().mLiquidities.add(locals.poolID, locals.tmpLiquidity, 0);
-
- locals.tmpLiquidity.entity = qpi.invocator();
- locals.tmpLiquidity.liquidity = locals.increaseLiquidity - QSWAP_MIN_LIQUIDITY;
- state.mut().mLiquidities.add(locals.poolID, locals.tmpLiquidity, 0);
-
- output.quAmount = locals.quTransferAmount;
- output.assetAmount = locals.assetTransferAmount;
- output.userIncreaseLiquidity = locals.increaseLiquidity - QSWAP_MIN_LIQUIDITY;
- }
- else
- {
- locals.tmpIncLiq0 = div(
- uint128(locals.quTransferAmount) * uint128(locals.poolBasicState.totalLiquidity),
- uint128(locals.poolBasicState.reservedQuAmount)
- );
- if (locals.tmpIncLiq0.high != 0 || locals.tmpIncLiq0.low > 0x7FFFFFFFFFFFFFFF)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
- locals.tmpIncLiq1 = div(
- uint128(locals.assetTransferAmount) * uint128(locals.poolBasicState.totalLiquidity),
- uint128(locals.poolBasicState.reservedAssetAmount)
- );
- if (locals.tmpIncLiq1.high != 0 || locals.tmpIncLiq1.low > 0x7FFFFFFFFFFFFFFF)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- // increaseLiquity = min(
- // quTransferAmount * totalLiquity / reserveQuAmount,
- // assetTransferAmount * totalLiquity / reserveAssetAmount
- // );
- locals.increaseLiquidity = min(sint64(locals.tmpIncLiq0.low), sint64(locals.tmpIncLiq1.low));
-
- // maybe too little input
- if (locals.increaseLiquidity == 0)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- // find user liquidity index
- locals.userLiquidityElementIndex = state.get().mLiquidities.headIndex(locals.poolID, 0);
- while (locals.userLiquidityElementIndex != NULL_INDEX)
- {
- if(state.get().mLiquidities.element(locals.userLiquidityElementIndex).entity == qpi.invocator())
- {
- break;
- }
-
- locals.userLiquidityElementIndex = state.get().mLiquidities.nextElementIndex(locals.userLiquidityElementIndex);
- }
-
- // no more space for new liquidity item
- if ((locals.userLiquidityElementIndex == NULL_INDEX) && ( state.get().mLiquidities.population() == state.get().mLiquidities.capacity()))
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- // transfer the asset from invocator to contract
- locals.reservedAssetAmountBefore = qpi.numberOfPossessedShares(
- input.assetName,
- input.assetIssuer,
- SELF,
- SELF,
- SELF_INDEX,
- SELF_INDEX
- );
- qpi.transferShareOwnershipAndPossession(
- input.assetName,
- input.assetIssuer,
- qpi.invocator(),
- qpi.invocator(),
- locals.assetTransferAmount,
- SELF
- );
- locals.reservedAssetAmountAfter = qpi.numberOfPossessedShares(
- input.assetName,
- input.assetIssuer,
- SELF,
- SELF,
- SELF_INDEX,
- SELF_INDEX
- );
-
- // only trust the amount in the contract
- if (locals.reservedAssetAmountAfter - locals.reservedAssetAmountBefore < locals.assetTransferAmount)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- if (locals.userLiquidityElementIndex == NULL_INDEX)
- {
- locals.tmpLiquidity.entity = qpi.invocator();
- locals.tmpLiquidity.liquidity = locals.increaseLiquidity;
- state.mut().mLiquidities.add(locals.poolID, locals.tmpLiquidity, 0);
- }
- else
- {
- locals.tmpLiquidity = state.get().mLiquidities.element(locals.userLiquidityElementIndex);
- locals.tmpLiquidity.liquidity += locals.increaseLiquidity;
- state.mut().mLiquidities.replace(locals.userLiquidityElementIndex, locals.tmpLiquidity);
- }
-
- output.quAmount = locals.quTransferAmount;
- output.assetAmount = locals.assetTransferAmount;
- output.userIncreaseLiquidity = locals.increaseLiquidity;
- }
-
- locals.poolBasicState.reservedQuAmount += locals.quTransferAmount;
- locals.poolBasicState.reservedAssetAmount += locals.assetTransferAmount;
- locals.poolBasicState.totalLiquidity += locals.increaseLiquidity;
-
- state.mut().mPoolBasicStates.set(locals.poolSlot, locals.poolBasicState);
-
- // Log AddLiquidity procedure
- locals.addLiquidityMessage._contractIndex = SELF_INDEX;
- locals.addLiquidityMessage._type = QSWAPAddLiquidity;
- locals.addLiquidityMessage.assetIssuer = input.assetIssuer;
- locals.addLiquidityMessage.assetName = input.assetName;
- locals.addLiquidityMessage.userIncreaseLiquidity = output.userIncreaseLiquidity;
- locals.addLiquidityMessage.quAmount = output.quAmount;
- locals.addLiquidityMessage.assetAmount = output.assetAmount;
- LOG_INFO(locals.addLiquidityMessage);
-
- if (qpi.invocationReward() > locals.quTransferAmount)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward() - locals.quTransferAmount);
- }
- }
-
- struct RemoveLiquidity_locals
- {
- RemoveLiquidityMessage removeLiquidityMessage;
- id poolID;
- PoolBasicState poolBasicState;
- sint64 userLiquidityElementIndex;
- sint64 poolSlot;
- LiquidityInfo userLiquidity;
- sint64 burnQuAmount;
- sint64 burnAssetAmount;
-
- uint32 i0;
- };
-
- PUBLIC_PROCEDURE_WITH_LOCALS(RemoveLiquidity)
- {
- output.quAmount = 0;
- output.assetAmount = 0;
-
- if (qpi.invocationReward() > 0 )
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- }
-
- // check the vadility of input params
- if (input.quAmountMin < 0 || input.assetAmountMin < 0 || input.burnLiquidity <= 0)
- {
- return;
- }
-
- locals.poolID = input.assetIssuer;
- locals.poolID.u64._3 = input.assetName;
-
- // get the pool's basic state
- locals.poolSlot = -1;
-
- for (locals.i0 = 0; locals.i0 < QSWAP_MAX_POOL; locals.i0 ++)
- {
- if (state.get().mPoolBasicStates.get(locals.i0).poolID == locals.poolID)
- {
- locals.poolSlot = locals.i0;
- break;
- }
- }
-
- // the pool does not exsit
- if (locals.poolSlot == -1)
- {
- return;
- }
-
- locals.poolBasicState = state.get().mPoolBasicStates.get(locals.poolSlot);
-
- locals.userLiquidityElementIndex = state.get().mLiquidities.headIndex(locals.poolID, 0);
- while (locals.userLiquidityElementIndex != NULL_INDEX)
- {
- if(state.get().mLiquidities.element(locals.userLiquidityElementIndex).entity == qpi.invocator())
- {
- break;
- }
-
- locals.userLiquidityElementIndex = state.get().mLiquidities.nextElementIndex(locals.userLiquidityElementIndex);
- }
-
- if (locals.userLiquidityElementIndex == NULL_INDEX)
- {
- return;
- }
-
- locals.userLiquidity = state.get().mLiquidities.element(locals.userLiquidityElementIndex);
-
- // not enough liquidity for burning
- if (locals.userLiquidity.liquidity < input.burnLiquidity )
- {
- return;
- }
-
- if (locals.poolBasicState.totalLiquidity < input.burnLiquidity )
- {
- return;
- }
-
- // since burnLiquidity < totalLiquidity, so there will be no overflow risk
- locals.burnQuAmount = sint64(div(
- uint128(input.burnLiquidity) * uint128(locals.poolBasicState.reservedQuAmount),
- uint128(locals.poolBasicState.totalLiquidity)
- ).low);
-
- // since burnLiquidity < totalLiquidity, so there will be no overflow risk
- locals.burnAssetAmount = sint64(div(
- uint128(input.burnLiquidity) * uint128(locals.poolBasicState.reservedAssetAmount),
- uint128(locals.poolBasicState.totalLiquidity)
- ).low);
-
-
- if ((locals.burnQuAmount < input.quAmountMin) || (locals.burnAssetAmount < input.assetAmountMin))
- {
- return;
- }
-
- // return qu and asset to invocator
- qpi.transfer(qpi.invocator(), locals.burnQuAmount);
- qpi.transferShareOwnershipAndPossession(
- input.assetName,
- input.assetIssuer,
- SELF,
- SELF,
- locals.burnAssetAmount,
- qpi.invocator()
- );
-
- output.quAmount = locals.burnQuAmount;
- output.assetAmount = locals.burnAssetAmount;
-
- // modify invocator's liquidity info
- locals.userLiquidity.liquidity -= input.burnLiquidity;
- if (locals.userLiquidity.liquidity == 0)
- {
- state.mut().mLiquidities.remove(locals.userLiquidityElementIndex);
- }
- else
- {
- state.mut().mLiquidities.replace(locals.userLiquidityElementIndex, locals.userLiquidity);
- }
-
- // modify the pool's liquidity info
- locals.poolBasicState.totalLiquidity -= input.burnLiquidity;
- locals.poolBasicState.reservedQuAmount -= locals.burnQuAmount;
- locals.poolBasicState.reservedAssetAmount -= locals.burnAssetAmount;
-
- state.mut().mPoolBasicStates.set(locals.poolSlot, locals.poolBasicState);
-
- // Log RemoveLiquidity procedure
- locals.removeLiquidityMessage._contractIndex = SELF_INDEX;
- locals.removeLiquidityMessage._type = QSWAPRemoveLiquidity;
- locals.removeLiquidityMessage.quAmount = output.quAmount;
- locals.removeLiquidityMessage.assetAmount = output.assetAmount;
- LOG_INFO(locals.removeLiquidityMessage);
- }
-
- struct SwapExactQuForAsset_locals
- {
- SwapMessage swapMessage;
- id poolID;
- sint64 poolSlot;
- sint64 quAmountIn;
- PoolBasicState poolBasicState;
- sint64 assetAmountOut;
-
- uint32 i0;
- uint128 i1, i2, i3, i4;
- uint128 swapFee;
- uint128 feeToInvestRewards;
- uint128 feeToShareholders;
-
- uint128 feeToQx;
- uint128 feeToBurn;
-
- sint64 totalFee;
- };
-
- // given an input qu amountIn, only execute swap in case (amountOut >= amountOutMin)
- // https://docs.uniswap.org/contracts/v2/reference/smart-contracts/router-02#swapexacttokensfortokens
- PUBLIC_PROCEDURE_WITH_LOCALS(SwapExactQuForAsset)
- {
- output.assetAmountOut = 0;
-
- // require input qu > 0
- if (qpi.invocationReward() <= 0)
- {
- return;
- }
-
- if (input.assetAmountOutMin < 0)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- locals.quAmountIn = qpi.invocationReward();
-
- locals.poolID = input.assetIssuer;
- locals.poolID.u64._3 = input.assetName;
-
- locals.poolSlot = -1;
- for (locals.i0 = 0; locals.i0 < QSWAP_MAX_POOL; locals.i0 ++)
- {
- if (state.get().mPoolBasicStates.get(locals.i0).poolID == locals.poolID)
- {
- locals.poolSlot = locals.i0;
- break;
- }
- }
-
- if (locals.poolSlot == -1)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- locals.poolBasicState = state.get().mPoolBasicStates.get(locals.poolSlot);
-
- // check the liquidity validity
- if (locals.poolBasicState.totalLiquidity == 0)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- locals.assetAmountOut = getAmountOutTakeFeeFromInToken(
- locals.quAmountIn,
- locals.poolBasicState.reservedQuAmount,
- locals.poolBasicState.reservedAssetAmount,
- state.get().swapFeeRate,
- locals.i1,
- locals.i2,
- locals.i3,
- locals.i4
- );
-
- // overflow
- if (locals.assetAmountOut == -1)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- // not meet user's amountOut requirement
- if (locals.assetAmountOut < input.assetAmountOutMin)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- // swapFee = quAmountIn * 0.3% (swapFeeRate/10000)
- // swapFee distribution: 27% shareholders, 5% QX, 3% invest&rewards, 1% burn, 64% LP
- locals.swapFee = div(uint128(locals.quAmountIn) * uint128(state.get().swapFeeRate), uint128(QSWAP_SWAP_FEE_BASE));
- if (locals.swapFee == uint128_t(0))
- {
- locals.swapFee = QSWAP_FEE_BASE_100;
- }
- locals.feeToShareholders = div(locals.swapFee * uint128(state.get().shareholderFeeRate), uint128(QSWAP_FEE_BASE_100));
- locals.feeToQx = div(locals.swapFee * uint128(state.get().qxFeeRate), uint128(QSWAP_FEE_BASE_100));
- locals.feeToInvestRewards = div(locals.swapFee * uint128(state.get().investRewardsFeeRate), uint128(QSWAP_FEE_BASE_100));
- locals.feeToBurn = div(locals.swapFee * uint128(state.get().burnFeeRate), uint128(QSWAP_FEE_BASE_100));
-
- locals.totalFee = sint64(locals.feeToShareholders.low) + sint64(locals.feeToQx.low) + sint64(locals.feeToInvestRewards.low) + sint64(locals.feeToBurn.low);
-
- // Overflow protection: ensure all fees fit in uint64
- if (locals.feeToShareholders.high != 0 || locals.feeToQx.high != 0
- || locals.feeToInvestRewards.high != 0 || locals.feeToBurn.high != 0
- || locals.quAmountIn < locals.totalFee)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- // transfer the asset from pool to qpi.invocator()
- output.assetAmountOut = qpi.transferShareOwnershipAndPossession(
- input.assetName,
- input.assetIssuer,
- SELF,
- SELF,
- locals.assetAmountOut,
- qpi.invocator()
- ) < 0 ? 0: locals.assetAmountOut;
-
- // in case asset transfer failed
- if (output.assetAmountOut == 0)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- // update fee state after successful transfer
- state.mut().shareholderEarnedFee += locals.feeToShareholders.low;
- state.mut().qxEarnedFee += locals.feeToQx.low;
- state.mut().investRewardsEarnedFee += locals.feeToInvestRewards.low;
- state.mut().burnEarnedFee += locals.feeToBurn.low;
-
- locals.poolBasicState.reservedQuAmount += locals.quAmountIn - locals.totalFee;
- locals.poolBasicState.reservedAssetAmount -= locals.assetAmountOut;
- state.mut().mPoolBasicStates.set(locals.poolSlot, locals.poolBasicState);
-
- // Log SwapExactQuForAsset procedure
- locals.swapMessage._contractIndex = SELF_INDEX;
- locals.swapMessage._type = QSWAPSwapExactQuForAsset;
- locals.swapMessage.assetIssuer = input.assetIssuer;
- locals.swapMessage.assetName = input.assetName;
- locals.swapMessage.assetAmountIn = locals.quAmountIn;
- locals.swapMessage.assetAmountOut = output.assetAmountOut;
- LOG_INFO(locals.swapMessage);
- }
-
- struct SwapQuForExactAsset_locals
- {
- SwapMessage swapMessage;
- id poolID;
- sint64 poolSlot;
- PoolBasicState poolBasicState;
- sint64 quAmountIn;
- sint64 transferredAssetAmount;
-
- uint32 i0;
- uint128 i1, i2, i3;
- uint128 swapFee;
- uint128 feeToInvestRewards;
- uint128 feeToShareholders;
- uint128 feeToQx;
- uint128 feeToBurn;
-
- sint64 totalFee;
- };
-
- // https://docs.uniswap.org/contracts/v2/reference/smart-contracts/router-02#swaptokensforexacttokens
- PUBLIC_PROCEDURE_WITH_LOCALS(SwapQuForExactAsset)
- {
- output.quAmountIn = 0;
-
- // require input qu amount > 0
- if (qpi.invocationReward() <= 0)
- {
- return;
- }
-
- // check input param validity
- if (input.assetAmountOut <= 0)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- locals.poolID = input.assetIssuer;
- locals.poolID.u64._3 = input.assetName;
-
- locals.poolSlot = -1;
- for (locals.i0 = 0; locals.i0 < QSWAP_MAX_POOL; locals.i0 ++)
- {
- if (state.get().mPoolBasicStates.get(locals.i0).poolID == locals.poolID)
- {
- locals.poolSlot = locals.i0;
- break;
- }
- }
-
- if (locals.poolSlot == -1)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- locals.poolBasicState = state.get().mPoolBasicStates.get(locals.poolSlot);
-
- // check if there is liquidity in the poool
- if (locals.poolBasicState.totalLiquidity == 0)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- // check if reserved asset is enough
- if (input.assetAmountOut >= locals.poolBasicState.reservedAssetAmount)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- locals.quAmountIn = getAmountInTakeFeeFromInToken(
- input.assetAmountOut,
- locals.poolBasicState.reservedQuAmount,
- locals.poolBasicState.reservedAssetAmount,
- state.get().swapFeeRate,
- locals.i1,
- locals.i2,
- locals.i3
- );
-
- // above call overflow
- if (locals.quAmountIn == -1 || locals.quAmountIn > qpi.invocationReward())
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- // swapFee = quAmountIn * 0.3% (swapFeeRate/10000)
- // swapFee distribution: 27% shareholders, 5% QX, 3% invest&rewards, 1% burn, 64% LP
- locals.swapFee = div(uint128(locals.quAmountIn) * uint128(state.get().swapFeeRate), uint128(QSWAP_SWAP_FEE_BASE));
- if (locals.swapFee == uint128_t(0))
- {
- locals.swapFee = QSWAP_FEE_BASE_100;
- }
- locals.feeToShareholders = div(locals.swapFee * uint128(state.get().shareholderFeeRate), uint128(QSWAP_FEE_BASE_100));
- locals.feeToQx = div(locals.swapFee * uint128(state.get().qxFeeRate), uint128(QSWAP_FEE_BASE_100));
- locals.feeToInvestRewards = div(locals.swapFee * uint128(state.get().investRewardsFeeRate), uint128(QSWAP_FEE_BASE_100));
- locals.feeToBurn = div(locals.swapFee * uint128(state.get().burnFeeRate), uint128(QSWAP_FEE_BASE_100));
-
- locals.totalFee = sint64(locals.feeToShareholders.low) + sint64(locals.feeToQx.low) + sint64(locals.feeToInvestRewards.low) + sint64(locals.feeToBurn.low);
- if (locals.quAmountIn < locals.totalFee)
- {
- qpi.transfer(qpi.invocator(), locals.quAmountIn);
- return;
- }
-
- // Overflow protection: ensure all fees fit in uint64
- if (locals.feeToShareholders.high != 0 || locals.feeToQx.high != 0 ||
- locals.feeToInvestRewards.high != 0 || locals.feeToBurn.high != 0)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- // transfer the asset from pool to qpi.invocator()
- locals.transferredAssetAmount = qpi.transferShareOwnershipAndPossession(
- input.assetName,
- input.assetIssuer,
- SELF,
- SELF,
- input.assetAmountOut,
- qpi.invocator()
- ) < 0 ? 0: input.assetAmountOut;
-
- // asset transfer failed
- if (locals.transferredAssetAmount == 0)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- output.quAmountIn = locals.quAmountIn;
- if (qpi.invocationReward() > locals.quAmountIn)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward() - locals.quAmountIn);
- }
-
- // update fee state after successful transfer
- state.mut().shareholderEarnedFee += locals.feeToShareholders.low;
- state.mut().qxEarnedFee += locals.feeToQx.low;
- state.mut().investRewardsEarnedFee += locals.feeToInvestRewards.low;
- state.mut().burnEarnedFee += locals.feeToBurn.low;
-
- locals.poolBasicState.reservedQuAmount += locals.quAmountIn - locals.totalFee;
- locals.poolBasicState.reservedAssetAmount -= input.assetAmountOut;
- state.mut().mPoolBasicStates.set(locals.poolSlot, locals.poolBasicState);
-
- // Log SwapQuForExactAsset procedure
- locals.swapMessage._contractIndex = SELF_INDEX;
- locals.swapMessage._type = QSWAPSwapQuForExactAsset;
- locals.swapMessage.assetIssuer = input.assetIssuer;
- locals.swapMessage.assetName = input.assetName;
- locals.swapMessage.assetAmountIn = output.quAmountIn;
- locals.swapMessage.assetAmountOut = input.assetAmountOut;
- LOG_INFO(locals.swapMessage);
- }
-
- struct SwapExactAssetForQu_locals
- {
- SwapMessage swapMessage;
- id poolID;
- sint64 poolSlot;
- PoolBasicState poolBasicState;
- sint64 quAmountOut;
- sint64 quAmountOutWithFee;
- sint64 transferredAssetAmountBefore;
- sint64 transferredAssetAmountAfter;
-
- uint32 i0;
- uint128 i1, i2, i3;
- uint128 swapFee;
- uint128 feeToInvestRewards;
- uint128 feeToShareholders;
- uint128 feeToQx;
- uint128 feeToBurn;
-
- sint64 totalFee;
- };
-
- // given an amount of asset swap in, only execute swaping if quAmountOut >= input.amountOutMin
- PUBLIC_PROCEDURE_WITH_LOCALS(SwapExactAssetForQu)
- {
- output.quAmountOut = 0;
- if (qpi.invocationReward() > 0)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- }
-
- // check input param validity
- if ((input.assetAmountIn <= 0 )||(input.quAmountOutMin < 0))
- {
- return;
- }
-
- locals.poolID = input.assetIssuer;
- locals.poolID.u64._3 = input.assetName;
-
- locals.poolSlot = -1;
- for (locals.i0 = 0; locals.i0 < QSWAP_MAX_POOL; locals.i0++)
- {
- if (state.get().mPoolBasicStates.get(locals.i0).poolID == locals.poolID)
- {
- locals.poolSlot = locals.i0;
- break;
- }
- }
-
- if (locals.poolSlot == -1)
- {
- return;
- }
-
- locals.poolBasicState = state.get().mPoolBasicStates.get(locals.poolSlot);
-
- // check the liquidity validity
- if (locals.poolBasicState.totalLiquidity == 0)
- {
- return;
- }
-
- // invocator's asset not enough
- if (qpi.numberOfPossessedShares(
- input.assetName,
- input.assetIssuer,
- qpi.invocator(),
- qpi.invocator(),
- SELF_INDEX,
- SELF_INDEX
- ) < input.assetAmountIn )
- {
- return;
- }
-
- locals.quAmountOutWithFee = getAmountOutTakeFeeFromOutToken(
- input.assetAmountIn,
- locals.poolBasicState.reservedAssetAmount,
- locals.poolBasicState.reservedQuAmount,
- state.get().swapFeeRate,
- locals.i1,
- locals.i2,
- locals.i3
- );
-
- // above call overflow
- if (locals.quAmountOutWithFee == -1)
- {
- return;
- }
-
- // no overflow risk
- // locals.quAmountOutWithFee * (QSWAP_SWAP_FEE_BASE - state.swapFeeRate) / QSWAP_SWAP_FEE_BASE
- locals.quAmountOut = sint64(div(
- uint128(locals.quAmountOutWithFee) * uint128(QSWAP_SWAP_FEE_BASE - state.get().swapFeeRate),
- uint128(QSWAP_SWAP_FEE_BASE)
- ).low);
-
- // not meet user min amountOut requirement
- if (locals.quAmountOut < input.quAmountOutMin)
- {
- return;
- }
-
- // swapFee = quAmountOutWithFee * 0.3% (swapFeeRate/10000)
- // swapFee distribution: 27% shareholders, 5% QX, 3% invest&rewards, 1% burn, 64% LP
- locals.swapFee = div(uint128(locals.quAmountOutWithFee) * uint128(state.get().swapFeeRate), uint128(QSWAP_SWAP_FEE_BASE));
- if (locals.swapFee == uint128_t(0))
- {
- locals.swapFee = QSWAP_FEE_BASE_100;
- }
- locals.feeToShareholders = div(locals.swapFee * uint128(state.get().shareholderFeeRate), uint128(QSWAP_FEE_BASE_100));
- locals.feeToQx = div(locals.swapFee * uint128(state.get().qxFeeRate), uint128(QSWAP_FEE_BASE_100));
- locals.feeToInvestRewards = div(locals.swapFee * uint128(state.get().investRewardsFeeRate), uint128(QSWAP_FEE_BASE_100));
- locals.feeToBurn = div(locals.swapFee * uint128(state.get().burnFeeRate), uint128(QSWAP_FEE_BASE_100));
-
- // Overflow protection: ensure all fees fit in uint64
- if (locals.feeToShareholders.high != 0 || locals.feeToQx.high != 0 ||
- locals.feeToInvestRewards.high != 0 || locals.feeToBurn.high != 0)
- {
- return;
- }
-
- // transfer assets from user to pool
- locals.transferredAssetAmountBefore = qpi.numberOfPossessedShares(
- input.assetName,
- input.assetIssuer,
- SELF,
- SELF,
- SELF_INDEX,
- SELF_INDEX
- );
- qpi.transferShareOwnershipAndPossession(
- input.assetName,
- input.assetIssuer,
- qpi.invocator(),
- qpi.invocator(),
- input.assetAmountIn,
- SELF
- );
- locals.transferredAssetAmountAfter = qpi.numberOfPossessedShares(
- input.assetName,
- input.assetIssuer,
- SELF,
- SELF,
- SELF_INDEX,
- SELF_INDEX
- );
-
- // pool does not receive enough asset, rollback any received shares
- if (locals.transferredAssetAmountAfter - locals.transferredAssetAmountBefore < input.assetAmountIn)
- {
- // return any shares that were transferred
- if (locals.transferredAssetAmountAfter > locals.transferredAssetAmountBefore)
- {
- qpi.transferShareOwnershipAndPossession(
- input.assetName,
- input.assetIssuer,
- SELF,
- SELF,
- locals.transferredAssetAmountAfter - locals.transferredAssetAmountBefore,
- qpi.invocator()
- );
- }
- return;
- }
-
- qpi.transfer(qpi.invocator(), locals.quAmountOut);
- output.quAmountOut = locals.quAmountOut;
-
- // update fee state after successful transfers
- state.mut().shareholderEarnedFee += locals.feeToShareholders.low;
- state.mut().qxEarnedFee += locals.feeToQx.low;
- state.mut().investRewardsEarnedFee += locals.feeToInvestRewards.low;
- state.mut().burnEarnedFee += locals.feeToBurn.low;
-
- // update pool states
- locals.poolBasicState.reservedAssetAmount += input.assetAmountIn;
- locals.totalFee = locals.quAmountOut + sint64(locals.feeToShareholders.low) + sint64(locals.feeToQx.low) + sint64(locals.feeToInvestRewards.low) + sint64(locals.feeToBurn.low);
- if (locals.poolBasicState.reservedQuAmount < locals.totalFee)
- {
- locals.poolBasicState.reservedQuAmount = 0;
- }
- else
- {
- locals.poolBasicState.reservedQuAmount -= locals.totalFee;
- }
- state.mut().mPoolBasicStates.set(locals.poolSlot, locals.poolBasicState);
-
- // Log SwapExactAssetForQu procedure
- locals.swapMessage._contractIndex = SELF_INDEX;
- locals.swapMessage._type = QSWAPSwapExactAssetForQu;
- locals.swapMessage.assetIssuer = input.assetIssuer;
- locals.swapMessage.assetName = input.assetName;
- locals.swapMessage.assetAmountIn = input.assetAmountIn;
- locals.swapMessage.assetAmountOut = output.quAmountOut;
- LOG_INFO(locals.swapMessage);
- }
-
- struct SwapAssetForExactQu_locals
- {
- SwapMessage swapMessage;
- id poolID;
- sint64 poolSlot;
- PoolBasicState poolBasicState;
- sint64 assetAmountIn;
- sint64 transferredAssetAmountBefore;
- sint64 transferredAssetAmountAfter;
-
- uint32 i0;
- uint128 i1, i2, i3;
- uint128 swapFee;
- uint128 feeToInvestRewards;
- uint128 feeToShareholders;
- uint128 feeToQx;
- uint128 feeToBurn;
-
- sint64 totalFee;
- };
-
- PUBLIC_PROCEDURE_WITH_LOCALS(SwapAssetForExactQu)
- {
- output.assetAmountIn = 0;
- if (qpi.invocationReward() > 0)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- }
-
- if ((input.assetAmountInMax <= 0 )||(input.quAmountOut <= 0))
- {
- return;
- }
-
- locals.poolID = input.assetIssuer;
- locals.poolID.u64._3 = input.assetName;
-
- locals.poolSlot = -1;
- for (locals.i0 = 0; locals.i0 < QSWAP_MAX_POOL; locals.i0 ++)
- {
- if (state.get().mPoolBasicStates.get(locals.i0).poolID == locals.poolID)
- {
- locals.poolSlot = locals.i0;
- break;
- }
- }
-
- if (locals.poolSlot == -1)
- {
- return;
- }
-
- locals.poolBasicState = state.get().mPoolBasicStates.get(locals.poolSlot);
-
- // check the liquidity validity
- if (locals.poolBasicState.totalLiquidity == 0)
- {
- return;
- }
-
- // pool does not hold enough asset
- if (input.quAmountOut >= locals.poolBasicState.reservedQuAmount)
- {
- return;
- }
-
- locals.assetAmountIn = getAmountInTakeFeeFromOutToken(
- input.quAmountOut,
- locals.poolBasicState.reservedAssetAmount,
- locals.poolBasicState.reservedQuAmount,
- state.get().swapFeeRate,
- locals.i1,
- locals.i2,
- locals.i3
- );
-
- // invalid input, assetAmountIn overflow
- if (locals.assetAmountIn == -1)
- {
- return;
- }
-
- // user does not hold enough asset
- if (qpi.numberOfPossessedShares(
- input.assetName,
- input.assetIssuer,
- qpi.invocator(),
- qpi.invocator(),
- SELF_INDEX,
- SELF_INDEX
- ) < locals.assetAmountIn )
- {
- return;
- }
-
- // not meet user amountIn reqiurement
- if (locals.assetAmountIn > input.assetAmountInMax)
- {
- return;
- }
-
- // swapFee = quAmountOut * 30/(10_000 - 30)
- // swapFee distribution: 27% shareholders, 5% QX, 3% invest&rewards, 1% burn, 64% LP
- locals.swapFee = div(uint128(input.quAmountOut) * uint128(state.get().swapFeeRate), uint128(QSWAP_SWAP_FEE_BASE - state.get().swapFeeRate));
- if (locals.swapFee == uint128_t(0))
- {
- locals.swapFee = QSWAP_FEE_BASE_100;
- }
- locals.feeToShareholders = div(locals.swapFee * uint128(state.get().shareholderFeeRate), uint128(QSWAP_FEE_BASE_100));
- locals.feeToQx = div(locals.swapFee * uint128(state.get().qxFeeRate), uint128(QSWAP_FEE_BASE_100));
- locals.feeToInvestRewards = div(locals.swapFee * uint128(state.get().investRewardsFeeRate), uint128(QSWAP_FEE_BASE_100));
- locals.feeToBurn = div(locals.swapFee * uint128(state.get().burnFeeRate), uint128(QSWAP_FEE_BASE_100));
-
- // Overflow protection: ensure all fees fit in uint64
- if (locals.feeToShareholders.high != 0 || locals.feeToQx.high != 0 ||
- locals.feeToInvestRewards.high != 0 || locals.feeToBurn.high != 0)
- {
- return;
- }
-
- locals.transferredAssetAmountBefore = qpi.numberOfPossessedShares(
- input.assetName,
- input.assetIssuer,
- SELF,
- SELF,
- SELF_INDEX,
- SELF_INDEX
- );
- qpi.transferShareOwnershipAndPossession(
- input.assetName,
- input.assetIssuer,
- qpi.invocator(),
- qpi.invocator(),
- locals.assetAmountIn,
- SELF
- );
- locals.transferredAssetAmountAfter = qpi.numberOfPossessedShares(
- input.assetName,
- input.assetIssuer,
- SELF,
- SELF,
- SELF_INDEX,
- SELF_INDEX
- );
-
- // pool does not receive enough asset, rollback any received shares
- if (locals.transferredAssetAmountAfter - locals.transferredAssetAmountBefore < locals.assetAmountIn)
- {
- // return any shares that were transferred
- if (locals.transferredAssetAmountAfter > locals.transferredAssetAmountBefore)
- {
- qpi.transferShareOwnershipAndPossession(
- input.assetName,
- input.assetIssuer,
- SELF,
- SELF,
- locals.transferredAssetAmountAfter - locals.transferredAssetAmountBefore,
- qpi.invocator()
- );
- }
- return;
- }
-
- qpi.transfer(qpi.invocator(), input.quAmountOut);
- output.assetAmountIn = locals.assetAmountIn;
-
- // update fee state after successful transfers
- state.mut().shareholderEarnedFee += locals.feeToShareholders.low;
- state.mut().qxEarnedFee += locals.feeToQx.low;
- state.mut().investRewardsEarnedFee += locals.feeToInvestRewards.low;
- state.mut().burnEarnedFee += locals.feeToBurn.low;
-
- // update pool states
- locals.poolBasicState.reservedAssetAmount += locals.assetAmountIn;
- locals.totalFee = input.quAmountOut + sint64(locals.feeToShareholders.low) + sint64(locals.feeToQx.low) + sint64(locals.feeToInvestRewards.low) + sint64(locals.feeToBurn.low);
- if (locals.poolBasicState.reservedQuAmount < locals.totalFee)
- {
- locals.poolBasicState.reservedQuAmount = 0;
- }
- else
- {
- locals.poolBasicState.reservedQuAmount -= locals.totalFee;
- }
- state.mut().mPoolBasicStates.set(locals.poolSlot, locals.poolBasicState);
-
- // Log SwapAssetForExactQu procedure
- locals.swapMessage._contractIndex = SELF_INDEX;
- locals.swapMessage._type = QSWAPSwapAssetForExactQu;
- locals.swapMessage.assetIssuer = input.assetIssuer;
- locals.swapMessage.assetName = input.assetName;
- locals.swapMessage.assetAmountIn = output.assetAmountIn;
- locals.swapMessage.assetAmountOut = input.quAmountOut;
- LOG_INFO(locals.swapMessage);
- }
-
- PUBLIC_PROCEDURE(TransferShareOwnershipAndPossession)
- {
- output.transferredAmount = 0;
-
- if (qpi.invocationReward() < state.get().cachedTransferFee)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- return;
- }
-
- if (input.amount <= 0)
- {
- if (qpi.invocationReward() > 0 )
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- }
- return;
- }
-
- if (qpi.numberOfPossessedShares(
- input.assetName,
- input.assetIssuer,
- qpi.invocator(),
- qpi.invocator(),
- SELF_INDEX,
- SELF_INDEX
- ) < input.amount)
- {
-
- if (qpi.invocationReward() > 0 )
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- }
- return;
- }
-
- output.transferredAmount = qpi.transferShareOwnershipAndPossession(
- input.assetName,
- input.assetIssuer,
- qpi.invocator(),
- qpi.invocator(),
- input.amount,
- input.newOwnerAndPossessor
- ) < 0 ? 0 : input.amount;
-
- if (output.transferredAmount == 0)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward());
- }
- else
- {
- if (qpi.invocationReward() > state.get().cachedTransferFee)
- {
- qpi.transfer(qpi.invocator(), qpi.invocationReward() - state.get().cachedTransferFee);
- }
- state.mut().shareholderEarnedFee += state.get().cachedTransferFee;
- }
- }
-
- PUBLIC_PROCEDURE(SetInvestRewardsInfo)
- {
- output.success = false;
- if (qpi.invocator() != state.get().investRewardsId)
- {
- return;
- }
-
- state.mut().investRewardsId = input.newInvestRewardsId;
- output.success = true;
- }
-
- struct TransferShareManagementRights_locals
- {
- sint64 result;
- sint64 reward;
- sint64 refundAmount;
- sint64 requiredFee;
- bit success;
- };
-
- PUBLIC_PROCEDURE_WITH_LOCALS(TransferShareManagementRights)
- {
- locals.reward = qpi.invocationReward();
- locals.refundAmount = locals.reward;
-
- output.transferredNumberOfShares = 0;
-
- locals.success = false;
-
- if (qpi.numberOfPossessedShares(
- input.asset.assetName,
- input.asset.issuer,
- qpi.invocator(),
- qpi.invocator(),
- SELF_INDEX,
- SELF_INDEX) >= input.numberOfShares)
- {
- locals.result = qpi.releaseShares(
- input.asset,
- qpi.invocator(),
- qpi.invocator(),
- input.numberOfShares,
- input.newManagingContractIndex,
- input.newManagingContractIndex,
- locals.reward
- );
-
- if (locals.result != INVALID_AMOUNT && locals.result >= 0)
- {
- locals.success = true;
- locals.refundAmount = locals.reward - locals.result;
- }
- }
-
- if (locals.success)
- {
- output.transferredNumberOfShares = input.numberOfShares;
- }
-
- if (locals.refundAmount > 0)
- {
- qpi.transfer(qpi.invocator(), locals.refundAmount);
- }
- }
-
- REGISTER_USER_FUNCTIONS_AND_PROCEDURES()
- {
- // functions
- REGISTER_USER_FUNCTION(Fees, 1);
- REGISTER_USER_FUNCTION(GetPoolBasicState, 2);
- REGISTER_USER_FUNCTION(GetLiquidityOf, 3);
- REGISTER_USER_FUNCTION(QuoteExactQuInput, 4);
- REGISTER_USER_FUNCTION(QuoteExactQuOutput, 5);
- REGISTER_USER_FUNCTION(QuoteExactAssetInput, 6);
- REGISTER_USER_FUNCTION(QuoteExactAssetOutput, 7);
- REGISTER_USER_FUNCTION(InvestRewardsInfo, 8);
-
- // procedure
- REGISTER_USER_PROCEDURE(IssueAsset, 1);
- REGISTER_USER_PROCEDURE(TransferShareOwnershipAndPossession, 2);
- REGISTER_USER_PROCEDURE(CreatePool, 3);
- REGISTER_USER_PROCEDURE(AddLiquidity, 4);
- REGISTER_USER_PROCEDURE(RemoveLiquidity, 5);
- REGISTER_USER_PROCEDURE(SwapExactQuForAsset, 6);
- REGISTER_USER_PROCEDURE(SwapQuForExactAsset, 7);
- REGISTER_USER_PROCEDURE(SwapExactAssetForQu, 8);
- REGISTER_USER_PROCEDURE(SwapAssetForExactQu, 9);
- REGISTER_USER_PROCEDURE(SetInvestRewardsInfo, 10);
- REGISTER_USER_PROCEDURE(TransferShareManagementRights, 11);
- }
-
- INITIALIZE()
- {
- state.mut().swapFeeRate = 30; // 0.3%, must be less than 10000
- state.mut().poolCreationFeeRate = 20; // 20%, must be less than 100
-
- // swapFee distribution: 27% shareholders, 5% QX, 3% invest&rewards, 1% burn, 64% LP providers
- state.mut().shareholderFeeRate = 27; // 27% of swap fees to SC shareholders
- state.mut().investRewardsFeeRate = 3; // 3% of swap fees to Invest & Rewards
- state.mut().qxFeeRate = 5; // 5% of swap fees to QX
- state.mut().burnFeeRate = 1; // 1% of swap fees burned
-
- ASSERT(state.get().swapFeeRate < QSWAP_SWAP_FEE_BASE);
- ASSERT(state.get().shareholderFeeRate + state.get().investRewardsFeeRate + state.get().qxFeeRate + state.get().burnFeeRate <= 100);
- //
- state.mut().investRewardsId = ID(_V, _J, _G, _R, _U, _F, _W, _J, _C, _U, _S, _N, _H, _C, _Q, _J, _R, _W, _R, _R, _Y, _X, _A, _U, _E, _J, _F, _C, _V, _H, _Y, _P, _X, _W, _K, _T, _D, _L, _Y, _K, _U, _A, _C, _P, _V, _V, _Y, _B, _G, _O, _L, _V, _C, _J, _S, _F);
- }
-
- struct BEGIN_EPOCH_locals
- {
- QX::Fees_input feesInput;
- QX::Fees_output feesOutput;
- };
-
- BEGIN_EPOCH_WITH_LOCALS()
- {
- CALL_OTHER_CONTRACT_FUNCTION(QX, Fees, locals.feesInput, locals.feesOutput);
-
- if (interContractCallError == NoCallError)
- {
- state.mut().cachedIssuanceFee = locals.feesOutput.assetIssuanceFee;
- state.mut().cachedTransferFee = locals.feesOutput.transferFee;
- }
- }
-
- struct END_TICK_locals
- {
- uint64 toDistribute;
- uint64 toBurn;
- uint64 dividendPerComputor;
- sint64 transferredAmount;
- FailedDistributionMessage logMsg;
- };
-
- END_TICK_WITH_LOCALS()
- {
- // Distribute Invest & Rewards fees
- if (state.get().investRewardsEarnedFee > state.get().investRewardsDistributedAmount)
- {
- locals.toDistribute = state.get().investRewardsEarnedFee - state.get().investRewardsDistributedAmount;
- locals.transferredAmount = qpi.transfer(state.get().investRewardsId, locals.toDistribute);
- if (locals.transferredAmount < 0)
- {
- locals.logMsg._contractIndex = SELF_INDEX;
- locals.logMsg._type = QSWAPFailedDistribution;
- locals.logMsg.dst = state.get().investRewardsId;
- locals.logMsg.amount = locals.toDistribute;
- LOG_INFO(locals.logMsg);
- }
- else
- state.mut().investRewardsDistributedAmount += locals.toDistribute;
- }
-
- // Distribute QX fees as donation
- if (state.get().qxEarnedFee > state.get().qxDistributedAmount)
- {
- locals.toDistribute = state.get().qxEarnedFee - state.get().qxDistributedAmount;
- locals.transferredAmount = qpi.transfer(id(QX_CONTRACT_INDEX, 0, 0, 0), locals.toDistribute);
- if (locals.transferredAmount < 0)
- {
- locals.logMsg._contractIndex = SELF_INDEX;
- locals.logMsg._type = QSWAPFailedDistribution;
- locals.logMsg.dst = id(QX_CONTRACT_INDEX, 0, 0, 0);
- locals.logMsg.amount = locals.toDistribute;
- LOG_INFO(locals.logMsg);
- }
- else
- state.mut().qxDistributedAmount += locals.toDistribute;
- }
-
- // Distribute shareholder fees (to IPO shareholders via dividends)
- if (state.get().shareholderEarnedFee > state.get().shareholderDistributedAmount)
- {
- locals.dividendPerComputor = div((state.get().shareholderEarnedFee - state.get().shareholderDistributedAmount), 676ULL);
- if (locals.dividendPerComputor > 0 && qpi.distributeDividends(locals.dividendPerComputor))
- {
- state.mut().shareholderDistributedAmount += locals.dividendPerComputor * NUMBER_OF_COMPUTORS;
- }
- }
-
- // Burn fees (adds to contract execution fee reserve)
- if (state.get().burnEarnedFee > state.get().burnedAmount)
- {
- locals.toBurn = state.get().burnEarnedFee - state.get().burnedAmount;
- qpi.burn(locals.toBurn);
- state.mut().burnedAmount += locals.toBurn;
- }
- }
-
- PRE_ACQUIRE_SHARES()
- {
- output.allowTransfer = true;
- }
-};
diff --git a/src/extensions/ant_colony_maintenance.h b/src/extensions/ant_colony_maintenance.h
new file mode 100644
index 00000000..e2073bfb
--- /dev/null
+++ b/src/extensions/ant_colony_maintenance.h
@@ -0,0 +1,43 @@
+#pragma once
+
+// Colony upkeep outside consensus. Pure functions of a colony, so they are testable without a node.
+
+namespace AntColonyMaintenance
+{
+// fork() clones only the calling thread, so a promoted child can inherit a claim with no owner and
+// ensureAntRecordAnn's waiter would spin on it forever.
+inline unsigned int releaseInheritedClaims(AntColonyBpp9000T& colony)
+{
+ unsigned int released = 0;
+ const unsigned int recordCount = colony.solutionCount();
+ for (unsigned int index = 0; index < recordCount; index++)
+ {
+ if (colony.isAnnClaimHeld(index))
+ {
+ colony.releaseAnnClaim(index);
+ released++;
+ }
+ }
+ return released;
+}
+
+// A rebuild starts from the parent's network, so a record whose parent has none cannot be taken.
+inline bool isRebuildableNow(AntColonyBpp9000T& colony, unsigned int index)
+{
+ if (colony.isAnnMaterialised(index) || colony.isAnnClaimHeld(index))
+ {
+ return false;
+ }
+ const AntSolutionRecord* record = colony.recordAt(index);
+ if (record == nullptr)
+ {
+ return false;
+ }
+ if (record->parentRef.isRoot())
+ {
+ return true;
+ }
+ const long long parentIndex = colony.findIndexBySolutionRef(record->parentRef);
+ return parentIndex != ANT_INVALID_INDEX && colony.isAnnMaterialised((unsigned int)parentIndex);
+}
+}
diff --git a/src/extensions/ant_walker_client.h b/src/extensions/ant_walker_client.h
new file mode 100644
index 00000000..b4dd2248
--- /dev/null
+++ b/src/extensions/ant_walker_client.h
@@ -0,0 +1,1000 @@
+#pragma once
+
+// Node side of the ant walker: picks records committed without a network, sends the walk to a
+// separate process, publishes the result after re-verifying it against the record.
+
+#if defined(ANT_WALKER) && !defined(_WIN32)
+
+#include "ant_walker_proto.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace AntWalker
+{
+enum class LinkState
+{
+ Disabled,
+ Disconnected,
+ Connecting,
+ Handshaking,
+ Ready,
+};
+
+// A wrong walker fails every job, and marking each would poison good records permanently, so a
+// streak is read as a broken walker instead.
+static constexpr unsigned int DISAGREEMENT_STREAK_LIMIT = 3;
+static constexpr unsigned int DEADLINE_STREAK_LIMIT = 3;
+// "Gone", not "slow": a walk runs for minutes and the walker answers PING throughout.
+static constexpr long long MIN_JOB_DEADLINE_MS = 600'000;
+static constexpr long long PING_INTERVAL_MS = 10'000;
+static constexpr long long BACKOFF_MIN_MS = 100;
+static constexpr long long BACKOFF_MAX_MS = 30'000;
+static constexpr int POLL_SLICE_MS = 100;
+static constexpr long long HEARTBEAT_INTERVAL_MS = 60'000;
+// Covers a legitimate 512 MB pool derive without hiding a wedged walker.
+static constexpr long long HANDSHAKE_DEADLINE_MS = 120'000;
+// A record the tick is blocked on is handed back, but only once the job is old enough to be stuck:
+// yanking a healthy walk throws its progress away and the node pays the whole walk again.
+static constexpr long long PREEMPT_MIN_AGE_MS = 180'000;
+static constexpr long long PREEMPT_ACK_WAIT_MS = 500;
+static constexpr unsigned int PREEMPT_NONE = 0xFFFFFFFFu;
+
+struct InFlight
+{
+ unsigned long long jobId;
+ unsigned int recordIndex;
+ long long sentAtMs;
+ // Kept for the replay key: re-resolving the anchor later could pick a different digest.
+ m256i anchorDigest;
+};
+
+struct State
+{
+ std::string socketPath;
+ unsigned int threadCount = 0;
+ bool debug = false;
+
+ std::atomic link{ (int)LinkState::Disabled };
+ std::atomic fd{ -1 };
+ std::atomic stopping{ false };
+ std::atomic quiesceRequested{ false };
+ std::atomic quiesceAcknowledged{ true };
+ std::atomic seedGeneration{ 0 };
+ std::atomic walkerPid{ -1 };
+ std::atomic suspectWalkerPid{ -1 };
+ std::atomic preemptRequest{ PREEMPT_NONE };
+ std::atomic handedBack{ PREEMPT_NONE };
+
+ std::atomic jobsSent{ 0 };
+ std::atomic materialised{ 0 };
+ std::atomic memoHits{ 0 };
+ std::atomic preempted{ 0 };
+ std::atomic disagreements{ 0 };
+ std::atomic deadlineExpiries{ 0 };
+ std::atomic staleDropped{ 0 };
+ std::atomic reconnects{ 0 };
+ std::atomic failedCount{ 0 };
+ std::atomic walkMsEma{ 0 };
+ std::atomic lastResultAtMs{ 0 };
+ std::atomic inFlightCount{ 0 };
+ std::atomic backlog{ 0 };
+
+ std::vector failedBits;
+ std::vector rolledBackCandidates;
+ std::vector inFlight;
+ unsigned long long nextJobId = 1;
+ unsigned int cursor = 0;
+ unsigned int disagreementStreak = 0;
+ unsigned int deadlineStreak = 0;
+ unsigned int helloGeneration = 0;
+ long long handshakeStartedAtMs = 0;
+
+ std::thread* dispatcher = nullptr;
+};
+
+inline State gState;
+
+inline const char* linkName(LinkState state)
+{
+ switch (state)
+ {
+ case LinkState::Disabled: return "disabled";
+ case LinkState::Disconnected: return "disconnected";
+ case LinkState::Connecting: return "connecting";
+ case LinkState::Handshaking: return "handshaking";
+ case LinkState::Ready: return "ready";
+ }
+ return "?";
+}
+
+inline long long nowMs()
+{
+ return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count();
+}
+
+inline void logLine(const char* format, ...)
+{
+ va_list arguments;
+ va_start(arguments, format);
+ fprintf(stderr, "[ant-walk] ");
+ vfprintf(stderr, format, arguments);
+ fprintf(stderr, "\n");
+ va_end(arguments);
+ fflush(stderr);
+}
+
+inline bool isEnabled()
+{
+ return gState.threadCount > 0 && !gState.socketPath.empty();
+}
+
+// ── failed-record bitmap ────────────────────────────────────────────────────────────────────────
+// Scheduling only: the on-demand rebuild path ignores this and still walks.
+
+inline void ensureFailedBits()
+{
+ const size_t needed = (size_t)ANT_MAX_NODES_PER_EPOCH / 8;
+ if (gState.failedBits.size() != needed)
+ {
+ gState.failedBits.assign(needed, 0);
+ }
+}
+
+inline bool isFailed(unsigned int index)
+{
+ ensureFailedBits();
+ return (gState.failedBits[index >> 3] >> (index & 7)) & 1;
+}
+
+inline void markFailed(unsigned int index)
+{
+ ensureFailedBits();
+ if (!isFailed(index))
+ {
+ gState.failedCount.fetch_add(1, std::memory_order_relaxed);
+ }
+ gState.failedBits[index >> 3] |= (unsigned char)(1u << (index & 7));
+}
+
+inline void clearFailed(unsigned int index)
+{
+ ensureFailedBits();
+ if (isFailed(index))
+ {
+ gState.failedCount.fetch_sub(1, std::memory_order_relaxed);
+ }
+ gState.failedBits[index >> 3] &= (unsigned char)~(1u << (index & 7));
+}
+
+// ── framing ─────────────────────────────────────────────────────────────────────────────────────
+
+inline bool writeFully(int fd, const void* buffer, size_t size)
+{
+ const unsigned char* in = (const unsigned char*)buffer;
+ size_t done = 0;
+ while (done < size)
+ {
+ const ssize_t put = write(fd, in + done, size - done);
+ if (put > 0)
+ {
+ done += (size_t)put;
+ continue;
+ }
+ if (put < 0 && errno == EINTR)
+ {
+ continue;
+ }
+ return false;
+ }
+ return true;
+}
+
+inline bool readFully(int fd, void* buffer, size_t size)
+{
+ unsigned char* out = (unsigned char*)buffer;
+ size_t done = 0;
+ while (done < size)
+ {
+ const ssize_t got = read(fd, out + done, size - done);
+ if (got > 0)
+ {
+ done += (size_t)got;
+ continue;
+ }
+ if (got < 0 && errno == EINTR)
+ {
+ continue;
+ }
+ return false;
+ }
+ return true;
+}
+
+inline bool sendFrame(int fd, unsigned int type, const void* payload, unsigned int payloadSize)
+{
+ AntWalkProto::FrameHeader header;
+ header.magic = AntWalkProto::MAGIC;
+ header.type = type;
+ header.payloadSize = payloadSize;
+ header.reserved = 0;
+ if (!writeFully(fd, &header, sizeof(header)))
+ {
+ return false;
+ }
+ return payloadSize == 0 || writeFully(fd, payload, payloadSize);
+}
+
+// ── link ────────────────────────────────────────────────────────────────────────────────────────
+
+// Releases every in-flight claim: the on-demand path would otherwise wait on a dead connection.
+inline void dropLink(const char* reason)
+{
+ const int fd = gState.fd.exchange(-1);
+ if (fd >= 0)
+ {
+ close(fd);
+ }
+ unsigned int released = 0;
+ for (const InFlight& job : gState.inFlight)
+ {
+ gAntColony.releaseAnnClaim(job.recordIndex);
+ released++;
+ }
+ gState.inFlight.clear();
+ gState.inFlightCount.store(0, std::memory_order_release);
+ if (gState.link.load(std::memory_order_acquire) != (int)LinkState::Disconnected)
+ {
+ logLine("sidecar lost (%s), %u claims released", reason, released);
+ }
+ gState.link.store((int)LinkState::Disconnected, std::memory_order_release);
+}
+
+inline bool sendHello(int fd)
+{
+ AntWalkProto::HelloPayload hello;
+ memset(&hello, 0, sizeof(hello));
+ hello.version = AntWalkProto::VERSION;
+ hello.epochId = gState.seedGeneration.load(std::memory_order_acquire);
+ hello.annBytes = AntWalkProto::ANN_BYTES;
+ hello.configHash = AntWalkProto::CONFIG_HASH;
+ gState.helloGeneration = hello.epochId;
+ copyMem(hello.miningSeed, score->currentRandomSeed.m256i_u8, 32);
+ copyMem(hello.topologyHash, BPP9000_TOPOLOGY_HASH, 32);
+ copyMem(hello.dataHash, BPP9000_DATA_HASH, 32);
+ return sendFrame(fd, AntWalkProto::MsgHello, &hello, (unsigned int)sizeof(hello));
+}
+
+inline bool tryConnect()
+{
+ gState.link.store((int)LinkState::Connecting, std::memory_order_release);
+ const int fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+ {
+ return false;
+ }
+ struct sockaddr_un address;
+ memset(&address, 0, sizeof(address));
+ address.sun_family = AF_UNIX;
+ snprintf(address.sun_path, sizeof(address.sun_path), "%s", gState.socketPath.c_str());
+ if (connect(fd, (struct sockaddr*)&address, sizeof(address)) != 0)
+ {
+ close(fd);
+ gState.link.store((int)LinkState::Disconnected, std::memory_order_release);
+ return false;
+ }
+
+ gState.fd.store(fd, std::memory_order_release);
+ gState.link.store((int)LinkState::Handshaking, std::memory_order_release);
+ gState.handshakeStartedAtMs = nowMs();
+ if (!sendHello(fd))
+ {
+ dropLink("hello write failed");
+ return false;
+ }
+ return true;
+}
+
+// ── selection ───────────────────────────────────────────────────────────────────────────────────
+
+// Commit order is topological, so this walks each lineage bottom-up without repeating a level.
+inline bool selectNextRecord(unsigned int& outIndex)
+{
+ const unsigned int recordCount = gAntColony.solutionCount();
+ if (recordCount == 0)
+ {
+ return false;
+ }
+
+ for (unsigned int scanned = 0; scanned < recordCount; scanned++)
+ {
+ const unsigned int index = (gState.cursor + scanned) % recordCount;
+ if (index == gState.handedBack.load(std::memory_order_acquire))
+ {
+ // Held until the tick has actually taken it, so the walker cannot re-take what it gave up.
+ if (gAntColony.isAnnClaimHeld(index) || gAntColony.isAnnMaterialised(index))
+ {
+ gState.handedBack.store(PREEMPT_NONE, std::memory_order_release);
+ }
+ continue;
+ }
+ if (isFailed(index) || !AntColonyMaintenance::isRebuildableNow(gAntColony, index))
+ {
+ continue;
+ }
+ gState.cursor = (index + 1) % recordCount;
+ outIndex = index;
+ return true;
+ }
+ return false;
+}
+
+// A full record scan (2^23 on mainnet), so it runs on the heartbeat rather than per dispatch.
+inline void refreshBacklog()
+{
+ const unsigned int recordCount = gAntColony.solutionCount();
+ unsigned int backlogCount = 0;
+ for (unsigned int index = 0; index < recordCount; index++)
+ {
+ if (!gAntColony.isAnnMaterialised(index))
+ {
+ backlogCount++;
+ }
+ }
+ gState.backlog.store(backlogCount, std::memory_order_release);
+}
+
+inline bool dispatchOne()
+{
+ unsigned int index;
+ if (!selectNextRecord(index))
+ {
+ return false;
+ }
+ if (gAntColony.tryClaimAnn(index) != AntColonyBpp9000T::AnnClaimOwned)
+ {
+ return false;
+ }
+ const AntSolutionRecord* record = gAntColony.recordAt(index);
+ if (record == nullptr)
+ {
+ gAntColony.releaseAnnClaim(index);
+ return false;
+ }
+
+ AntWalkProto::JobPayload job;
+ memset(&job, 0, sizeof(job));
+ job.jobId = gState.nextJobId++;
+ job.epochId = gState.seedGeneration.load(std::memory_order_acquire);
+ job.isRoot = record->parentRef.isRoot() ? 1u : 0u;
+ copyMem(job.pubkey, record->pubkey.m256i_u8, 32);
+ copyMem(job.nonce, record->nonce.m256i_u8, 32);
+
+ m256i anchorDigest;
+ if (!getAntAnchorDigestForRebuild(record->anchorTick, anchorDigest))
+ {
+ gAntColony.releaseAnnClaim(index);
+ markFailed(index);
+ return false;
+ }
+ copyMem(job.anchorDigest, anchorDigest.m256i_u8, 32);
+
+ // Every other scoring path memoises its walk, so a score already held here costs no job at all.
+ const AntColonyBpp9000T::ReplayKey replayKey = makeAntReplayKey(record->pubkey, record->nonce, record->parentRef, anchorDigest);
+ AntColonyBpp9000T::Ann memoAnn;
+ unsigned int memoScore;
+ if (gAntColony.tryGetReplayScore(replayKey, memoScore, memoAnn))
+ {
+ if (memoScore != record->score)
+ {
+ // The node's own cache disagreeing is the record's fault, not the walker's, so no streak.
+ gAntColony.releaseAnnClaim(index);
+ markFailed(index);
+ logLine("record %u memo %u != accepted %u, marked failed", index, memoScore, record->score);
+ return false;
+ }
+ unsigned int memoHash;
+ KangarooTwelve(&memoAnn, sizeof(memoAnn), &memoHash, sizeof(memoHash));
+ gAntColony.publishAnn(index, memoAnn, memoHash);
+ gState.memoHits.fetch_add(1, std::memory_order_relaxed);
+ gState.materialised.fetch_add(1, std::memory_order_relaxed);
+ if (gState.debug)
+ {
+ logLine("record %u from the replay cache, no job", index);
+ }
+ return true;
+ }
+
+ if (!job.isRoot)
+ {
+ const long long parentIndex = gAntColony.findIndexBySolutionRef(record->parentRef);
+ const AntSolutionRecord* parentRecord = (parentIndex == ANT_INVALID_INDEX) ? nullptr : gAntColony.recordAt(parentIndex);
+ AntColonyBpp9000T::Ann parentAnn;
+ if (parentRecord == nullptr || !gAntColony.annOfNonRoot(*parentRecord, parentAnn))
+ {
+ gAntColony.releaseAnnClaim(index);
+ return false;
+ }
+ copyMem(job.parentAnn, &parentAnn, sizeof(parentAnn));
+ }
+
+ const int fd = gState.fd.load(std::memory_order_acquire);
+ if (fd < 0 || !sendFrame(fd, AntWalkProto::MsgJob, &job, (unsigned int)sizeof(job)))
+ {
+ gAntColony.releaseAnnClaim(index);
+ dropLink("job write failed");
+ return false;
+ }
+
+ gState.inFlight.push_back(InFlight{ job.jobId, index, nowMs(), anchorDigest });
+ gState.inFlightCount.store((unsigned int)gState.inFlight.size(), std::memory_order_release);
+ gState.jobsSent.fetch_add(1, std::memory_order_relaxed);
+ if (gState.debug)
+ {
+ logLine("job %llu record %u depth %u sent", (unsigned long long)job.jobId, index, (unsigned)record->depth);
+ }
+ return true;
+}
+
+// ── results ─────────────────────────────────────────────────────────────────────────────────────
+
+inline bool takeInFlight(unsigned long long jobId, InFlight& out)
+{
+ for (size_t i = 0; i < gState.inFlight.size(); i++)
+ {
+ if (gState.inFlight[i].jobId == jobId)
+ {
+ out = gState.inFlight[i];
+ gState.inFlight.erase(gState.inFlight.begin() + (long)i);
+ gState.inFlightCount.store((unsigned int)gState.inFlight.size(), std::memory_order_release);
+ return true;
+ }
+ }
+ return false;
+}
+
+inline void noteDisagreement(unsigned int index)
+{
+ markFailed(index);
+ gState.rolledBackCandidates.push_back(index);
+ gState.disagreements.fetch_add(1, std::memory_order_relaxed);
+ gState.disagreementStreak++;
+ if (gState.disagreementStreak < DISAGREEMENT_STREAK_LIMIT)
+ {
+ return;
+ }
+ for (unsigned int candidate : gState.rolledBackCandidates)
+ {
+ clearFailed(candidate);
+ }
+ logLine("%u consecutive disagreements - walker suspect, %u marks rolled back, disconnecting",
+ gState.disagreementStreak, (unsigned int)gState.rolledBackCandidates.size());
+ gState.rolledBackCandidates.clear();
+ gState.disagreementStreak = 0;
+ gState.suspectWalkerPid.store(gState.walkerPid.load(std::memory_order_acquire), std::memory_order_release);
+ dropLink("walker disagrees on every job");
+}
+
+inline void noteSuccess()
+{
+ gState.disagreementStreak = 0;
+ gState.deadlineStreak = 0;
+ gState.rolledBackCandidates.clear();
+}
+
+inline void applyResult(const AntWalkProto::ResultPayload& result)
+{
+ InFlight job;
+ if (!takeInFlight(result.jobId, job))
+ {
+ return;
+ }
+
+ const long long walkMs = nowMs() - job.sentAtMs;
+ const unsigned long long previousEma = gState.walkMsEma.load(std::memory_order_acquire);
+ gState.walkMsEma.store(previousEma ? (previousEma * 3 + (unsigned long long)walkMs) / 4 : (unsigned long long)walkMs, std::memory_order_release);
+ gState.lastResultAtMs.store((unsigned long long)nowMs(), std::memory_order_release);
+
+ if (result.epochId != gState.seedGeneration.load(std::memory_order_acquire) || result.status == AntWalkProto::ResultStaleEpoch)
+ {
+ gAntColony.releaseAnnClaim(job.recordIndex);
+ gState.staleDropped.fetch_add(1, std::memory_order_relaxed);
+ logLine("job %llu dropped, epoch %u is not %u", (unsigned long long)result.jobId,
+ result.epochId, gState.seedGeneration.load(std::memory_order_acquire));
+ return;
+ }
+
+ const AntSolutionRecord* record = gAntColony.recordAt(job.recordIndex);
+ if (record == nullptr)
+ {
+ gAntColony.releaseAnnClaim(job.recordIndex);
+ return;
+ }
+
+ if (result.status != AntWalkProto::ResultOk || result.score != record->score)
+ {
+ gAntColony.releaseAnnClaim(job.recordIndex);
+ logLine("record %u walked %u != accepted %u, marked failed", job.recordIndex, result.score, record->score);
+ noteDisagreement(job.recordIndex);
+ return;
+ }
+
+ AntColonyBpp9000T::Ann childAnn;
+ copyMem(&childAnn, result.childAnn, sizeof(childAnn));
+ unsigned int annHash;
+ KangarooTwelve(&childAnn, sizeof(childAnn), &annHash, sizeof(annHash));
+
+ gAntColony.publishAnn(job.recordIndex, childAnn, annHash);
+ // The cache every scoring path consults, so a strict replay of this solution is a lookup.
+ const AntColonyBpp9000T::ReplayKey replayKey = makeAntReplayKey(record->pubkey, record->nonce, record->parentRef, job.anchorDigest);
+ gAntColony.putReplayScore(replayKey, result.score, childAnn);
+ gState.materialised.fetch_add(1, std::memory_order_relaxed);
+ noteSuccess();
+ if (gState.debug)
+ {
+ logLine("job %llu record %u score %u in %lld ms", (unsigned long long)result.jobId, job.recordIndex, result.score, walkMs);
+ }
+}
+
+inline void checkDeadlines()
+{
+ const unsigned long long ema = gState.walkMsEma.load(std::memory_order_acquire);
+ const long long deadlineMs = (long long)(ema * 3) > MIN_JOB_DEADLINE_MS
+ ? (long long)(ema * 3) : MIN_JOB_DEADLINE_MS;
+ const long long now = nowMs();
+
+ for (size_t i = 0; i < gState.inFlight.size();)
+ {
+ if (now - gState.inFlight[i].sentAtMs < deadlineMs)
+ {
+ i++;
+ continue;
+ }
+ const unsigned int index = gState.inFlight[i].recordIndex;
+ const unsigned long long jobId = gState.inFlight[i].jobId;
+ gState.inFlight.erase(gState.inFlight.begin() + (long)i);
+ gAntColony.releaseAnnClaim(index);
+ gState.deadlineExpiries.fetch_add(1, std::memory_order_relaxed);
+ // A missing result says nothing about the record, so the bitmap is left alone.
+ logLine("job %llu record %u no result in %lld ms, claim released, not marked", jobId, index, deadlineMs);
+ gState.deadlineStreak++;
+ }
+ gState.inFlightCount.store((unsigned int)gState.inFlight.size(), std::memory_order_release);
+
+ if (gState.deadlineStreak >= DEADLINE_STREAK_LIMIT)
+ {
+ gState.deadlineStreak = 0;
+ dropLink("walker alive but not answering");
+ }
+}
+
+// ── dispatcher ──────────────────────────────────────────────────────────────────────────────────
+
+// Answers the on-demand path waiting on a record this link claimed. Only a job past the stale age is
+// handed back: a healthy walk is worth waiting out, since taking it back discards all of its progress.
+inline void servePreempt()
+{
+ const unsigned int index = gState.preemptRequest.load(std::memory_order_acquire);
+ if (index == PREEMPT_NONE)
+ {
+ return;
+ }
+
+ const unsigned long long ema = gState.walkMsEma.load(std::memory_order_acquire);
+ const long long staleMs = (long long)(ema * 2) > PREEMPT_MIN_AGE_MS ? (long long)(ema * 2) : PREEMPT_MIN_AGE_MS;
+ for (size_t i = 0; i < gState.inFlight.size(); i++)
+ {
+ if (gState.inFlight[i].recordIndex != index || nowMs() - gState.inFlight[i].sentAtMs < staleMs)
+ {
+ continue;
+ }
+ const unsigned long long jobId = gState.inFlight[i].jobId;
+ const long long ageMs = nowMs() - gState.inFlight[i].sentAtMs;
+ gState.inFlight.erase(gState.inFlight.begin() + (long)i);
+ gState.inFlightCount.store((unsigned int)gState.inFlight.size(), std::memory_order_release);
+ gState.handedBack.store(index, std::memory_order_release);
+ gAntColony.releaseAnnClaim(index);
+ gState.preempted.fetch_add(1, std::memory_order_relaxed);
+ logLine("job %llu record %u handed back to the tick after %lld ms, its result will be dropped", jobId, index, ageMs);
+ break;
+ }
+ gState.preemptRequest.store(PREEMPT_NONE, std::memory_order_release);
+}
+
+// Reseeding invalidates every index in flight, so claims and scan state go before it proceeds.
+inline void serveQuiesce()
+{
+ for (const InFlight& job : gState.inFlight)
+ {
+ gAntColony.releaseAnnClaim(job.recordIndex);
+ }
+ gState.inFlight.clear();
+ gState.inFlightCount.store(0, std::memory_order_release);
+ gState.preemptRequest.store(PREEMPT_NONE, std::memory_order_release);
+ gState.handedBack.store(PREEMPT_NONE, std::memory_order_release);
+ gState.cursor = 0;
+ gState.disagreementStreak = 0;
+ gState.deadlineStreak = 0;
+ gState.rolledBackCandidates.clear();
+ gState.failedBits.assign((size_t)ANT_MAX_NODES_PER_EPOCH / 8, 0);
+ gState.failedCount.store(0, std::memory_order_release);
+ gState.quiesceAcknowledged.store(true, std::memory_order_release);
+
+ while (gState.quiesceRequested.load(std::memory_order_acquire) && !gState.stopping.load(std::memory_order_acquire))
+ {
+ std::this_thread::sleep_for(std::chrono::milliseconds(5));
+ }
+ const int fd = gState.fd.load(std::memory_order_acquire);
+ if (fd >= 0 && !sendHello(fd))
+ {
+ dropLink("hello write failed after epoch reset");
+ }
+}
+
+inline bool readOneFrame(int fd, unsigned char* payload)
+{
+ AntWalkProto::FrameHeader header;
+ if (!readFully(fd, &header, sizeof(header)))
+ {
+ dropLink("connection closed");
+ return false;
+ }
+ if (header.magic != AntWalkProto::MAGIC || header.payloadSize > AntWalkProto::MAX_PAYLOAD_BYTES)
+ {
+ dropLink("bad frame");
+ return false;
+ }
+ if (header.payloadSize && !readFully(fd, payload, header.payloadSize))
+ {
+ dropLink("short frame");
+ return false;
+ }
+
+ if (header.type == AntWalkProto::MsgReady)
+ {
+ if (header.payloadSize != sizeof(AntWalkProto::ReadyPayload))
+ {
+ dropLink("bad ready frame");
+ return false;
+ }
+ AntWalkProto::ReadyPayload ready;
+ memcpy(&ready, payload, sizeof(ready));
+ if ((int)ready.walkerPid == gState.suspectWalkerPid.load(std::memory_order_acquire))
+ {
+ logLine("walker pid %u already disagreed on every job, not using it", ready.walkerPid);
+ dropLink("suspect walker");
+ return false;
+ }
+ if (ready.status != AntWalkProto::ReadyOk)
+ {
+ logLine("handshake refused by the walker (status %u) - not retrying until it changes", ready.status);
+ dropLink("handshake refused");
+ return false;
+ }
+ // Also answers the hello resent on a seed rotation, which is a re-seed, not a new link.
+ const bool wasReady = (LinkState)gState.link.load(std::memory_order_acquire) == LinkState::Ready;
+ gState.threadCount = ready.threadCount;
+ gState.walkerPid.store((int)ready.walkerPid, std::memory_order_release);
+ gState.link.store((int)LinkState::Ready, std::memory_order_release);
+ if (wasReady)
+ {
+ logLine("sidecar re-seeded for epoch %u", ready.epochId);
+ }
+ else
+ {
+ gState.reconnects.fetch_add(1, std::memory_order_relaxed);
+ logLine("sidecar connected, pid %u, %u threads, epoch %u", ready.walkerPid, ready.threadCount, ready.epochId);
+ }
+ }
+ else if (header.type == AntWalkProto::MsgResult)
+ {
+ if (header.payloadSize != sizeof(AntWalkProto::ResultPayload))
+ {
+ dropLink("bad result frame");
+ return false;
+ }
+ AntWalkProto::ResultPayload result;
+ memcpy(&result, payload, sizeof(result));
+ applyResult(result);
+ }
+ return true;
+}
+
+inline void heartbeat()
+{
+ logLine("backlog %llu, done %llu (memo %llu), failed %llu, inflight %u/%u, walk avg %llu ms, link %s",
+ (unsigned long long)gState.backlog.load(std::memory_order_acquire), (unsigned long long)gState.materialised.load(std::memory_order_acquire),
+ (unsigned long long)gState.memoHits.load(std::memory_order_acquire), (unsigned long long)gState.failedCount.load(std::memory_order_acquire),
+ gState.inFlightCount.load(std::memory_order_acquire), gState.threadCount, (unsigned long long)gState.walkMsEma.load(std::memory_order_acquire),
+ linkName((LinkState)gState.link.load(std::memory_order_acquire)));
+}
+
+inline void dispatcherLoop()
+{
+ long long backoffMs = BACKOFF_MIN_MS;
+ long long reconnectAtMs = 0;
+ long long nextPingAtMs = nowMs() + PING_INTERVAL_MS;
+ long long nextHeartbeatAtMs = nowMs() + HEARTBEAT_INTERVAL_MS;
+ std::vector payload(AntWalkProto::MAX_PAYLOAD_BYTES);
+
+ while (!gState.stopping.load(std::memory_order_acquire))
+ {
+ servePreempt();
+
+ if (nowMs() >= nextHeartbeatAtMs)
+ {
+ nextHeartbeatAtMs = nowMs() + HEARTBEAT_INTERVAL_MS;
+ refreshBacklog();
+ heartbeat();
+ }
+
+ if (gState.quiesceRequested.load(std::memory_order_acquire))
+ {
+ serveQuiesce();
+ continue;
+ }
+
+ // A walker seeded before the first mining seed exists would disagree on every record.
+ if (score == nullptr || isZero(score->currentRandomSeed))
+ {
+ std::this_thread::sleep_for(std::chrono::milliseconds(POLL_SLICE_MS));
+ continue;
+ }
+
+ const LinkState link = (LinkState)gState.link.load(std::memory_order_acquire);
+ if (link != LinkState::Ready && link != LinkState::Handshaking)
+ {
+ const long long now = nowMs();
+ if (now < reconnectAtMs)
+ {
+ std::this_thread::sleep_for(std::chrono::milliseconds(POLL_SLICE_MS));
+ continue;
+ }
+ if (!tryConnect())
+ {
+ backoffMs = (backoffMs * 2 < BACKOFF_MAX_MS) ? backoffMs * 2 : BACKOFF_MAX_MS;
+ reconnectAtMs = nowMs() + backoffMs;
+ continue;
+ }
+ backoffMs = BACKOFF_MIN_MS;
+ continue;
+ }
+
+ if (link == LinkState::Handshaking && nowMs() - gState.handshakeStartedAtMs > HANDSHAKE_DEADLINE_MS)
+ {
+ dropLink("no ready frame within the handshake deadline");
+ continue;
+ }
+
+ const int fd = gState.fd.load(std::memory_order_acquire);
+ if (fd < 0)
+ {
+ continue;
+ }
+
+ struct pollfd entry;
+ entry.fd = fd;
+ entry.events = POLLIN;
+ entry.revents = 0;
+ const int ready = poll(&entry, 1, POLL_SLICE_MS);
+ if (ready > 0 && !readOneFrame(fd, payload.data()))
+ {
+ continue;
+ }
+ if (ready < 0 && errno != EINTR)
+ {
+ dropLink("poll failed");
+ continue;
+ }
+
+ checkDeadlines();
+
+ // Deeper only lets a wedged walker hold claims the on-demand path may need.
+ if ((LinkState)gState.link.load(std::memory_order_acquire) == LinkState::Ready)
+ {
+ while (gState.inFlight.size() < gState.threadCount && dispatchOne())
+ {
+ }
+ }
+
+ const unsigned int generation = gState.seedGeneration.load(std::memory_order_acquire);
+ if (generation != gState.helloGeneration && (LinkState)gState.link.load(std::memory_order_acquire) == LinkState::Ready)
+ {
+ const int helloFd = gState.fd.load(std::memory_order_acquire);
+ if (helloFd < 0 || !sendHello(helloFd))
+ {
+ dropLink("hello write failed on seed change");
+ continue;
+ }
+ }
+
+ const long long now = nowMs();
+ if (now >= nextPingAtMs)
+ {
+ nextPingAtMs = now + PING_INTERVAL_MS;
+ const int liveFd = gState.fd.load(std::memory_order_acquire);
+ if (liveFd >= 0 && !sendFrame(liveFd, AntWalkProto::MsgPing, nullptr, 0))
+ {
+ dropLink("ping write failed");
+ }
+ }
+ }
+
+ dropLink("shutting down");
+}
+
+// ── node-facing api ─────────────────────────────────────────────────────────────────────────────
+
+inline void configure(const std::string& socketPath, unsigned int threadCount, bool debug)
+{
+ gState.socketPath = socketPath;
+ gState.threadCount = threadCount;
+ gState.debug = debug;
+}
+
+inline void start()
+{
+ if (!isEnabled() || gState.dispatcher != nullptr)
+ {
+ return;
+ }
+ gState.stopping.store(false, std::memory_order_release);
+ gState.link.store((int)LinkState::Disconnected, std::memory_order_release);
+ gState.dispatcher = new std::thread(dispatcherLoop);
+}
+
+inline void stop()
+{
+ if (gState.dispatcher == nullptr)
+ {
+ return;
+ }
+ gState.stopping.store(true, std::memory_order_release);
+ gState.dispatcher->join();
+ delete gState.dispatcher;
+ gState.dispatcher = nullptr;
+}
+
+// The pool seed changed, so results still in flight were computed against the old one.
+inline void onEpochBegin()
+{
+ gState.seedGeneration.fetch_add(1, std::memory_order_acq_rel);
+}
+
+inline void quiesceBegin()
+{
+ if (gState.dispatcher == nullptr)
+ {
+ return;
+ }
+ gState.quiesceAcknowledged.store(false, std::memory_order_release);
+ gState.quiesceRequested.store(true, std::memory_order_release);
+
+ const long long startedAtMs = nowMs();
+ while (!gState.quiesceAcknowledged.load(std::memory_order_acquire))
+ {
+ if (nowMs() - startedAtMs > 5'000)
+ {
+ logLine("quiesce not acknowledged in 5000 ms, continuing");
+ break;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+ logLine("quiesce for epoch reset, waited %lld ms", nowMs() - startedAtMs);
+}
+
+inline void quiesceEnd()
+{
+ gState.quiesceRequested.store(false, std::memory_order_release);
+}
+
+// Asks the dispatcher to hand back a record the on-demand path is stuck behind. The caller re-checks
+// the claim afterwards: a claim held by another node processor is not the walker's to release.
+inline void preemptClaim(unsigned int index)
+{
+ if (!isEnabled() || gState.inFlightCount.load(std::memory_order_acquire) == 0)
+ {
+ return;
+ }
+ unsigned int slot = PREEMPT_NONE;
+ if (!gState.preemptRequest.compare_exchange_strong(slot, index, std::memory_order_acq_rel))
+ {
+ return;
+ }
+
+ const long long deadlineMs = nowMs() + PREEMPT_ACK_WAIT_MS;
+ while (gState.preemptRequest.load(std::memory_order_acquire) == index && nowMs() < deadlineMs)
+ {
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ }
+ // A dispatcher that never answered must not leave the slot taken for every later request.
+ slot = index;
+ gState.preemptRequest.compare_exchange_strong(slot, PREEMPT_NONE, std::memory_order_acq_rel);
+}
+
+// The fork child inherits the descriptor; two readers on one socket interleave results.
+inline void closeInheritedSocket()
+{
+ const int fd = gState.fd.exchange(-1);
+ if (fd >= 0)
+ {
+ close(fd);
+ }
+ gState.link.store((int)LinkState::Disconnected, std::memory_order_release);
+}
+
+// The promote path swept the claims, so only the client's own view is rebuilt here.
+inline void restartAfterPromote()
+{
+ closeInheritedSocket();
+ // Only the handle came through fork(), so it is abandoned rather than joined or destroyed.
+ gState.dispatcher = nullptr;
+ gState.inFlight.clear();
+ gState.inFlightCount.store(0, std::memory_order_release);
+ gState.preemptRequest.store(PREEMPT_NONE, std::memory_order_release);
+ gState.handedBack.store(PREEMPT_NONE, std::memory_order_release);
+ gState.quiesceRequested.store(false, std::memory_order_release);
+ gState.quiesceAcknowledged.store(true, std::memory_order_release);
+ gState.cursor = 0;
+ gState.disagreementStreak = 0;
+ gState.deadlineStreak = 0;
+ gState.rolledBackCandidates.clear();
+ start();
+ if (isEnabled())
+ {
+ logLine("dispatcher restarted after promote");
+ }
+}
+
+inline std::string statsJson()
+{
+ char buffer[896];
+ snprintf(buffer, sizeof(buffer),
+ "{\"enabled\":%s,\"state\":\"%s\",\"socket\":\"%s\",\"threads\":%u,"
+ "\"inflight\":%u,\"backlog\":%llu,\"materialised\":%llu,\"failed\":%llu,"
+ "\"jobsSent\":%llu,\"disagreements\":%llu,\"deadlineExpiries\":%llu,"
+ "\"staleDropped\":%llu,\"reconnects\":%llu,\"walkAvgMs\":%llu,"
+ "\"memoHits\":%llu,\"preempted\":%llu,\"epochId\":%u}",
+ isEnabled() ? "true" : "false", linkName((LinkState)gState.link.load(std::memory_order_acquire)), gState.socketPath.c_str(), gState.threadCount,
+ gState.inFlightCount.load(std::memory_order_acquire), (unsigned long long)gState.backlog.load(std::memory_order_acquire),
+ (unsigned long long)gState.materialised.load(std::memory_order_acquire), (unsigned long long)gState.failedCount.load(std::memory_order_acquire),
+ (unsigned long long)gState.jobsSent.load(std::memory_order_acquire), (unsigned long long)gState.disagreements.load(std::memory_order_acquire),
+ (unsigned long long)gState.deadlineExpiries.load(std::memory_order_acquire), (unsigned long long)gState.staleDropped.load(std::memory_order_acquire),
+ (unsigned long long)gState.reconnects.load(std::memory_order_acquire), (unsigned long long)gState.walkMsEma.load(std::memory_order_acquire),
+ (unsigned long long)gState.memoHits.load(std::memory_order_acquire), (unsigned long long)gState.preempted.load(std::memory_order_acquire),
+ gState.seedGeneration.load(std::memory_order_acquire));
+ return std::string(buffer);
+}
+}
+
+#else
+
+#include
+
+namespace AntWalker
+{
+inline void configure(const std::string&, unsigned int, bool) {}
+inline void start() {}
+inline void stop() {}
+inline void onEpochBegin() {}
+inline void quiesceBegin() {}
+inline void quiesceEnd() {}
+inline void preemptClaim(unsigned int) {}
+inline void closeInheritedSocket() {}
+inline void restartAfterPromote() {}
+inline std::string statsJson() { return std::string("{\"enabled\":false}"); }
+}
+
+#endif // ANT_WALKER && !_WIN32
diff --git a/src/extensions/ant_walker_proto.h b/src/extensions/ant_walker_proto.h
new file mode 100644
index 00000000..7f8f5e80
--- /dev/null
+++ b/src/extensions/ant_walker_proto.h
@@ -0,0 +1,128 @@
+#pragma once
+
+// Wire format shared by the walker client and the walker process. Fixed-size payloads only, so a
+// frame is one read of a known length.
+
+#include "score.h"
+#include "public_settings.h"
+
+namespace AntWalkProto
+{
+static constexpr unsigned int MAGIC = 0x57544E41u; // "ANTW"
+static constexpr unsigned int VERSION = 1;
+
+static constexpr unsigned int ANN_BYTES = (unsigned int)sizeof(score_engine::ScoreBpp9000T::ANN);
+
+// Both sides compile the same scorer, so only a build disagreeing on these params scores
+// differently. Compared at the handshake, before a job runs.
+constexpr unsigned int mixConfig(unsigned int accumulated, unsigned long long value)
+{
+ for (unsigned int byteIndex = 0; byteIndex < 8; byteIndex++)
+ {
+ accumulated = (accumulated ^ (unsigned int)((value >> (byteIndex * 8)) & 0xFF)) * 16777619u;
+ }
+ return accumulated;
+}
+
+constexpr unsigned int CONFIG_HASH =
+ mixConfig(mixConfig(mixConfig(mixConfig(mixConfig(mixConfig(mixConfig(mixConfig(mixConfig(mixConfig(
+ 2166136261u,
+ BPP9000_NUMBER_OF_INPUT_NEURONS),
+ BPP9000_NUMBER_OF_OUTPUT_NEURONS),
+ BPP9000_SEQUENCE_LENGTH),
+ BPP9000_WINDOW_WIDTH),
+ BPP9000_MAX_NUMBER_OF_TICKS),
+ BPP9000_NUMBER_OF_NEIGHBORS),
+ BPP9000_POPULATION_THRESHOLD),
+ BPP9000_NUMBER_OF_MUTATIONS),
+ BPP9000_SOLUTION_THRESHOLD_DEFAULT),
+ (unsigned long long)ANN_BYTES);
+
+enum MessageType : unsigned int
+{
+ MsgHello = 1, // node -> walker
+ MsgReady = 2, // walker -> node
+ MsgJob = 3, // node -> walker
+ MsgResult = 4, // walker -> node
+ MsgPing = 5, // node -> walker
+ MsgPong = 6, // walker -> node
+};
+
+enum ResultStatus : unsigned int
+{
+ ResultOk = 0,
+ ResultUnscorable = 1, // the engine returned INVALID_SCORE_VALUE, childAnn is not written
+ ResultStaleEpoch = 2, // the job named an epoch the walker is no longer seeded for
+};
+
+enum ReadyStatus : unsigned int
+{
+ ReadyOk = 0,
+ ReadyVersionMismatch = 1,
+ ReadyTaskMismatch = 2,
+ ReadySeedFailed = 3,
+ ReadyConfigMismatch = 4,
+};
+
+struct FrameHeader
+{
+ unsigned int magic;
+ unsigned int type;
+ unsigned int payloadSize;
+ unsigned int reserved;
+};
+
+struct HelloPayload
+{
+ unsigned int version;
+ unsigned int epochId;
+ unsigned int annBytes;
+ unsigned int configHash;
+ unsigned char miningSeed[32];
+ // The pinned task identity both binaries were compiled against; a mismatch means the two would
+ // score the same nonce differently, which is worth refusing before a single job runs.
+ unsigned char topologyHash[32];
+ unsigned char dataHash[32];
+};
+
+struct ReadyPayload
+{
+ unsigned int version;
+ unsigned int status;
+ unsigned int threadCount;
+ unsigned int annBytes;
+ unsigned int epochId;
+ unsigned int walkerPid; // lets the node refuse a walker it has already found to be wrong
+};
+
+struct JobPayload
+{
+ unsigned long long jobId;
+ unsigned int epochId;
+ unsigned int isRoot; // parentAnn is unused when set; the walker derives the miner's root
+ unsigned char pubkey[32];
+ unsigned char nonce[32];
+ unsigned char anchorDigest[32];
+ unsigned char parentAnn[ANN_BYTES];
+};
+
+struct ResultPayload
+{
+ unsigned long long jobId;
+ unsigned int epochId;
+ unsigned int status;
+ unsigned int score;
+ unsigned int reserved;
+ unsigned char childAnn[ANN_BYTES];
+};
+
+static_assert(sizeof(FrameHeader) == 16, "FrameHeader must stay 16 bytes on the wire");
+static_assert(sizeof(HelloPayload) == 112, "HelloPayload layout changed - bump VERSION");
+static_assert(sizeof(ReadyPayload) == 24, "ReadyPayload layout changed - bump VERSION");
+static_assert(sizeof(JobPayload) == 16 + 96 + ANN_BYTES, "JobPayload layout changed - bump VERSION");
+static_assert(sizeof(ResultPayload) == 24 + ANN_BYTES, "ResultPayload layout changed - bump VERSION");
+
+// The largest frame either side ever reads, so both can size one static buffer.
+static constexpr unsigned int MAX_PAYLOAD_BYTES =
+ (sizeof(JobPayload) > sizeof(ResultPayload)) ? (unsigned int)sizeof(JobPayload) : (unsigned int)sizeof(ResultPayload);
+}
diff --git a/src/extensions/ant_walker_worker.h b/src/extensions/ant_walker_worker.h
new file mode 100644
index 00000000..a191ef4d
--- /dev/null
+++ b/src/extensions/ant_walker_worker.h
@@ -0,0 +1,581 @@
+#pragma once
+
+// The ant score walk, in a process re-exec'd from this binary (--ant-walk-worker): on a node thread
+// it would hold the score-engine lock across nearly every fork point. Stateless, holds no node state.
+
+#if defined(ANT_WALKER) && !defined(_WIN32)
+
+#include "ant_walker_proto.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#if defined(__linux__)
+#include
+#endif
+
+namespace AntWalkerWorker
+{
+using Ann = score_engine::ScoreBpp9000T::ANN;
+
+constexpr int NO_TRAFFIC_TIMEOUT_MS = 60'000;
+constexpr int POLL_SLICE_MS = 1'000;
+
+struct Options
+{
+ std::string socketPath;
+ unsigned int threadCount = 4;
+};
+
+// ── socket helpers ──────────────────────────────────────────────────────────────────────────────
+
+bool readFully(int fd, void* buffer, size_t size)
+{
+ unsigned char* out = (unsigned char*)buffer;
+ size_t done = 0;
+ while (done < size)
+ {
+ const ssize_t got = read(fd, out + done, size - done);
+ if (got > 0)
+ {
+ done += (size_t)got;
+ continue;
+ }
+ if (got < 0 && errno == EINTR)
+ {
+ continue;
+ }
+ return false;
+ }
+ return true;
+}
+
+bool writeFully(int fd, const void* buffer, size_t size)
+{
+ const unsigned char* in = (const unsigned char*)buffer;
+ size_t done = 0;
+ while (done < size)
+ {
+ const ssize_t put = write(fd, in + done, size - done);
+ if (put > 0)
+ {
+ done += (size_t)put;
+ continue;
+ }
+ if (put < 0 && errno == EINTR)
+ {
+ continue;
+ }
+ return false;
+ }
+ return true;
+}
+
+// Bounded, so a peer that vanished without an EOF cannot park this process forever.
+bool waitReadable(int fd, int timeoutMs, const std::atomic& stop)
+{
+ int waitedMs = 0;
+ while (waitedMs < timeoutMs)
+ {
+ if (stop.load(std::memory_order_acquire))
+ {
+ return false;
+ }
+ struct pollfd entry;
+ entry.fd = fd;
+ entry.events = POLLIN;
+ entry.revents = 0;
+ const int slice = (timeoutMs - waitedMs < POLL_SLICE_MS) ? (timeoutMs - waitedMs) : POLL_SLICE_MS;
+ const int ready = poll(&entry, 1, slice);
+ if (ready > 0)
+ {
+ return true;
+ }
+ if (ready < 0 && errno != EINTR)
+ {
+ return false;
+ }
+ waitedMs += slice;
+ }
+ return false;
+}
+
+// ── engine ──────────────────────────────────────────────────────────────────────────────────────
+
+unsigned char* gPool = nullptr;
+unsigned char gPoolSeed[32] = {};
+bool gPoolSeeded = false;
+
+bool ensurePool(const unsigned char* miningSeed)
+{
+ if (gPool == nullptr)
+ {
+ gPool = (unsigned char*)malloc((size_t)score_engine::POOL_VEC_PADDING_SIZE);
+ if (gPool == nullptr)
+ {
+ return false;
+ }
+ }
+ if (gPoolSeeded && memcmp(gPoolSeed, miningSeed, 32) == 0)
+ {
+ return true;
+ }
+ unsigned char state[score_engine::STATE_SIZE];
+ score_engine::generateRandom2Pool(miningSeed, state, gPool);
+ memcpy(gPoolSeed, miningSeed, 32);
+ gPoolSeeded = true;
+ return true;
+}
+
+bool loadEmbeddedTask(score_engine::ScoreBpp9000T& engine)
+{
+ const unsigned int inputTrits = (unsigned int)BPP9000_NUMBER_OF_INPUT_NEURONS;
+ const unsigned int outputTrits = (unsigned int)BPP9000_NUMBER_OF_OUTPUT_NEURONS;
+ const unsigned int population = (unsigned int)BPP9000_POPULATION_THRESHOLD;
+ const unsigned int neighbors = (unsigned int)BPP9000_NUMBER_OF_NEIGHBORS;
+
+ const unsigned long long topologyBytes = score_task_file::topologyBytes(inputTrits, outputTrits, population, neighbors);
+ const unsigned char* topologyBlock = BPP9000_TASK_BYTES + sizeof(score_task_file::TaskFileHeader);
+ const unsigned char* dataBlock = topologyBlock + topologyBytes;
+
+ engine.initMemory();
+ return engine.loadTaskFromMemory(topologyBlock, dataBlock);
+}
+
+// ── worker pool ─────────────────────────────────────────────────────────────────────────────────
+
+struct WorkerPool
+{
+ std::mutex queueMutex; // SMARTMUTEX-EXEMPT: separate process, holds no node state and never forks
+ std::condition_variable queueSignal;
+ std::deque queue;
+
+ std::mutex writeMutex; // SMARTMUTEX-EXEMPT: separate process, holds no node state and never forks
+ std::atomic stopping{ false };
+ std::atomic connected{ false };
+ std::atomic epochId{ 0 };
+ std::atomic connectionFd{ -1 };
+ std::atomic busy{ 0 };
+
+ std::vector threads;
+};
+
+WorkerPool gPoolOfWorkers;
+
+void runWorker(unsigned int workerIndex)
+{
+ score_engine::ScoreBpp9000T* engine = (score_engine::ScoreBpp9000T*)aligned_alloc(64, (sizeof(score_engine::ScoreBpp9000T) + 63) / 64 * 64);
+ if (engine == nullptr || !loadEmbeddedTask(*engine))
+ {
+ fprintf(stderr, "[ant-walker] worker %u could not load the embedded task\n", workerIndex);
+ fflush(stderr);
+ return;
+ }
+ Ann rootAnn;
+ Ann childAnn;
+
+ for (;;)
+ {
+ AntWalkProto::JobPayload job;
+ {
+ std::unique_lock lock(gPoolOfWorkers.queueMutex);
+ gPoolOfWorkers.queueSignal.wait(lock, [] {
+ return gPoolOfWorkers.stopping.load(std::memory_order_acquire)
+ || !gPoolOfWorkers.queue.empty();
+ });
+ if (gPoolOfWorkers.stopping.load(std::memory_order_acquire) && gPoolOfWorkers.queue.empty())
+ {
+ break;
+ }
+ job = gPoolOfWorkers.queue.front();
+ gPoolOfWorkers.queue.pop_front();
+ gPoolOfWorkers.busy.fetch_add(1, std::memory_order_acq_rel);
+ }
+
+ struct BusyGuard
+ {
+ ~BusyGuard() { gPoolOfWorkers.busy.fetch_sub(1, std::memory_order_acq_rel); }
+ } busyGuard;
+
+ AntWalkProto::ResultPayload result;
+ memset(&result, 0, sizeof(result));
+ result.jobId = job.jobId;
+ result.epochId = job.epochId;
+
+ if (job.epochId != gPoolOfWorkers.epochId.load(std::memory_order_acquire))
+ {
+ result.status = AntWalkProto::ResultStaleEpoch;
+ }
+ else
+ {
+ const Ann* parentAnn;
+ if (job.isRoot)
+ {
+ engine->deriveRootANN(job.pubkey, gPool, rootAnn);
+ parentAnn = &rootAnn;
+ }
+ else
+ {
+ parentAnn = (const Ann*)job.parentAnn;
+ }
+
+ const unsigned int score = engine->computeScoreFromParent(*parentAnn, job.pubkey, job.nonce, job.anchorDigest, gPool);
+ if (score == score_engine::INVALID_SCORE_VALUE)
+ {
+ result.status = AntWalkProto::ResultUnscorable;
+ }
+ else
+ {
+ engine->getBestANN(childAnn);
+ result.status = AntWalkProto::ResultOk;
+ result.score = score;
+ memcpy(result.childAnn, &childAnn, sizeof(childAnn));
+ }
+ }
+
+ // A walk cannot be aborted, so one that outlived its connection finishes and is dropped.
+ if (!gPoolOfWorkers.connected.load(std::memory_order_acquire))
+ {
+ continue;
+ }
+ std::lock_guard writeGuard(gPoolOfWorkers.writeMutex);
+ const int fd = gPoolOfWorkers.connectionFd.load(std::memory_order_acquire);
+ if (fd < 0)
+ {
+ continue;
+ }
+ AntWalkProto::FrameHeader header;
+ header.magic = AntWalkProto::MAGIC;
+ header.type = AntWalkProto::MsgResult;
+ header.payloadSize = (unsigned int)sizeof(result);
+ header.reserved = 0;
+ if (!writeFully(fd, &header, sizeof(header)) || !writeFully(fd, &result, sizeof(result)))
+ {
+ gPoolOfWorkers.connected.store(false, std::memory_order_release);
+ }
+ }
+
+ free(engine);
+}
+
+// ── connection ──────────────────────────────────────────────────────────────────────────────────
+
+bool sendReady(int fd, unsigned int status, unsigned int threadCount, unsigned int epochId)
+{
+ AntWalkProto::ReadyPayload ready;
+ memset(&ready, 0, sizeof(ready));
+ ready.version = AntWalkProto::VERSION;
+ ready.status = status;
+ ready.threadCount = threadCount;
+ ready.annBytes = AntWalkProto::ANN_BYTES;
+ ready.epochId = epochId;
+ ready.walkerPid = (unsigned int)getpid();
+
+ AntWalkProto::FrameHeader header;
+ header.magic = AntWalkProto::MAGIC;
+ header.type = AntWalkProto::MsgReady;
+ header.payloadSize = (unsigned int)sizeof(ready);
+ header.reserved = 0;
+
+ std::lock_guard writeGuard(gPoolOfWorkers.writeMutex);
+ return writeFully(fd, &header, sizeof(header)) && writeFully(fd, &ready, sizeof(ready));
+}
+
+unsigned int validateHello(const AntWalkProto::HelloPayload& hello)
+{
+ if (hello.version != AntWalkProto::VERSION || hello.annBytes != AntWalkProto::ANN_BYTES)
+ {
+ return AntWalkProto::ReadyVersionMismatch;
+ }
+ if (memcmp(hello.topologyHash, BPP9000_TOPOLOGY_HASH, 32) != 0 || memcmp(hello.dataHash, BPP9000_DATA_HASH, 32) != 0)
+ {
+ return AntWalkProto::ReadyTaskMismatch;
+ }
+ if (hello.configHash != AntWalkProto::CONFIG_HASH)
+ {
+ fprintf(stderr, "[ant-walker] scorer config %08x does not match the node's %08x\n", AntWalkProto::CONFIG_HASH, hello.configHash);
+ fflush(stderr);
+ return AntWalkProto::ReadyConfigMismatch;
+ }
+ return AntWalkProto::ReadyOk;
+}
+
+bool hasWorkOutstanding()
+{
+ if (gPoolOfWorkers.busy.load(std::memory_order_acquire) > 0)
+ {
+ return true;
+ }
+ std::lock_guard queueGuard(gPoolOfWorkers.queueMutex);
+ return !gPoolOfWorkers.queue.empty();
+}
+
+void serveConnection(int fd, unsigned int threadCount)
+{
+ gPoolOfWorkers.connectionFd.store(fd, std::memory_order_release);
+ gPoolOfWorkers.connected.store(true, std::memory_order_release);
+
+ unsigned char payload[AntWalkProto::MAX_PAYLOAD_BYTES];
+ bool handshaken = false;
+
+ while (gPoolOfWorkers.connected.load(std::memory_order_acquire))
+ {
+ if (!waitReadable(fd, NO_TRAFFIC_TIMEOUT_MS, gPoolOfWorkers.stopping))
+ {
+ // A walk outlasts this timeout, so silence only means a dead peer when nothing is queued.
+ if (hasWorkOutstanding())
+ {
+ continue;
+ }
+ fprintf(stderr, "[ant-walker] no traffic for %d ms and nothing in flight, dropping the connection\n", NO_TRAFFIC_TIMEOUT_MS);
+ fflush(stderr);
+ break;
+ }
+
+ AntWalkProto::FrameHeader header;
+ if (!readFully(fd, &header, sizeof(header)))
+ {
+ break;
+ }
+ if (header.magic != AntWalkProto::MAGIC || header.payloadSize > sizeof(payload))
+ {
+ fprintf(stderr, "[ant-walker] bad frame (magic %08x size %u), dropping the connection\n", header.magic, header.payloadSize);
+ fflush(stderr);
+ break;
+ }
+ if (header.payloadSize && !readFully(fd, payload, header.payloadSize))
+ {
+ break;
+ }
+
+ if (header.type == AntWalkProto::MsgHello)
+ {
+ if (header.payloadSize != sizeof(AntWalkProto::HelloPayload))
+ {
+ break;
+ }
+ AntWalkProto::HelloPayload hello;
+ memcpy(&hello, payload, sizeof(hello));
+
+ unsigned int status = validateHello(hello);
+ if (status == AntWalkProto::ReadyOk && !ensurePool(hello.miningSeed))
+ {
+ status = AntWalkProto::ReadySeedFailed;
+ }
+ if (status == AntWalkProto::ReadyOk)
+ {
+ gPoolOfWorkers.epochId.store(hello.epochId, std::memory_order_release);
+ handshaken = true;
+ }
+ if (!sendReady(fd, status, threadCount, hello.epochId) || status != AntWalkProto::ReadyOk)
+ {
+ fprintf(stderr, "[ant-walker] handshake refused (status %u)\n", status);
+ fflush(stderr);
+ break;
+ }
+ fprintf(stderr, "[ant-walker] ready, %u threads, epoch %u\n", threadCount, hello.epochId);
+ fflush(stderr);
+ }
+ else if (header.type == AntWalkProto::MsgJob)
+ {
+ if (!handshaken || header.payloadSize != sizeof(AntWalkProto::JobPayload))
+ {
+ break;
+ }
+ std::lock_guard queueGuard(gPoolOfWorkers.queueMutex);
+ gPoolOfWorkers.queue.emplace_back();
+ memcpy(&gPoolOfWorkers.queue.back(), payload, sizeof(AntWalkProto::JobPayload));
+ gPoolOfWorkers.queueSignal.notify_one();
+ }
+ else if (header.type == AntWalkProto::MsgPing)
+ {
+ AntWalkProto::FrameHeader pong;
+ pong.magic = AntWalkProto::MAGIC;
+ pong.type = AntWalkProto::MsgPong;
+ pong.payloadSize = 0;
+ pong.reserved = 0;
+ std::lock_guard writeGuard(gPoolOfWorkers.writeMutex);
+ if (!writeFully(fd, &pong, sizeof(pong)))
+ {
+ break;
+ }
+ }
+ }
+
+ gPoolOfWorkers.connected.store(false, std::memory_order_release);
+ {
+ // Queued jobs belong to a connection that is gone; only the walks already running are paid for.
+ std::lock_guard queueGuard(gPoolOfWorkers.queueMutex);
+ gPoolOfWorkers.queue.clear();
+ }
+ {
+ // Held until no worker can still be inside writeFully on this descriptor.
+ std::lock_guard writeGuard(gPoolOfWorkers.writeMutex);
+ gPoolOfWorkers.connectionFd.store(-1, std::memory_order_release);
+ }
+ close(fd);
+}
+
+// ── listener ────────────────────────────────────────────────────────────────────────────────────
+
+// Competing with a live server is how an orphaned sidecar ends up answering for a running one.
+bool socketPathIsServed(const char* path)
+{
+ const int probeFd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (probeFd < 0)
+ {
+ return false;
+ }
+ struct sockaddr_un address;
+ memset(&address, 0, sizeof(address));
+ address.sun_family = AF_UNIX;
+ snprintf(address.sun_path, sizeof(address.sun_path), "%s", path);
+ const bool served = connect(probeFd, (struct sockaddr*)&address, sizeof(address)) == 0;
+ close(probeFd);
+ return served;
+}
+
+int openListener(const char* path)
+{
+ if (socketPathIsServed(path))
+ {
+ fprintf(stderr, "[ant-walker] %s is already served, refusing to compete\n", path);
+ fflush(stderr);
+ return -1;
+ }
+ unlink(path);
+
+ const int listenFd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (listenFd < 0)
+ {
+ perror("[ant-walker] socket");
+ return -1;
+ }
+ struct sockaddr_un address;
+ memset(&address, 0, sizeof(address));
+ address.sun_family = AF_UNIX;
+ snprintf(address.sun_path, sizeof(address.sun_path), "%s", path);
+ if (bind(listenFd, (struct sockaddr*)&address, sizeof(address)) != 0 || listen(listenFd, 4) != 0)
+ {
+ perror("[ant-walker] bind/listen");
+ close(listenFd);
+ return -1;
+ }
+ return listenFd;
+}
+
+bool parseOptions(int argc, const char* argv[], Options& options)
+{
+ for (int i = 1; i + 1 < argc; i++)
+ {
+ const std::string argument = argv[i];
+ if (argument == "--socket")
+ {
+ options.socketPath = argv[i + 1];
+ }
+ else if (argument == "--threads")
+ {
+ options.threadCount = (unsigned int)strtoul(argv[i + 1], nullptr, 10);
+ }
+ }
+ return !options.socketPath.empty() && options.threadCount > 0;
+}
+
+// Checked before any node setup: the node half of main() must not run in the walker.
+inline bool requested(int argc, const char* argv[])
+{
+ for (int i = 1; i < argc; i++)
+ {
+ if (std::string(argv[i]) == "--ant-walk-worker")
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+inline int run(int argc, const char* argv[])
+{
+ Options options;
+ if (!parseOptions(argc, argv, options))
+ {
+ fprintf(stderr, "[ant-walker] --ant-walk-worker needs --socket --threads \n");
+ return 2;
+ }
+
+ signal(SIGPIPE, SIG_IGN);
+#if defined(__linux__)
+ // The node is the only reason this process exists; outliving it would just squat the socket.
+ prctl(PR_SET_PDEATHSIG, SIGTERM);
+ if (getppid() == 1)
+ {
+ return 0;
+ }
+#endif
+
+ const int listenFd = openListener(options.socketPath.c_str());
+ if (listenFd < 0)
+ {
+ return 3;
+ }
+
+ for (unsigned int i = 0; i < options.threadCount; i++)
+ {
+ gPoolOfWorkers.threads.emplace_back(runWorker, i);
+ }
+ fprintf(stderr, "[ant-walker] listening on %s, %u threads, pid %d\n", options.socketPath.c_str(), options.threadCount, (int)getpid());
+ fflush(stderr);
+
+ while (!gPoolOfWorkers.stopping.load(std::memory_order_acquire))
+ {
+ struct pollfd entry;
+ entry.fd = listenFd;
+ entry.events = POLLIN;
+ entry.revents = 0;
+ const int ready = poll(&entry, 1, POLL_SLICE_MS);
+#if defined(__linux__)
+ if (getppid() == 1)
+ {
+ break;
+ }
+#endif
+ if (ready <= 0)
+ {
+ continue;
+ }
+ const int connectionFd = accept(listenFd, nullptr, nullptr);
+ if (connectionFd < 0)
+ {
+ continue;
+ }
+ serveConnection(connectionFd, options.threadCount);
+ }
+
+ gPoolOfWorkers.stopping.store(true, std::memory_order_release);
+ gPoolOfWorkers.queueSignal.notify_all();
+ for (std::thread& worker : gPoolOfWorkers.threads)
+ {
+ worker.join();
+ }
+ close(listenFd);
+ unlink(options.socketPath.c_str());
+ return 0;
+}
+}
+
+#else
+
+namespace AntWalkerWorker
+{
+inline bool requested(int, const char*[]) { return false; }
+inline int run(int, const char*[]) { return 0; }
+}
+
+#endif // ANT_WALKER && !_WIN32
diff --git a/src/extensions/http/controller/rpc_live_controller.h b/src/extensions/http/controller/rpc_live_controller.h
index 4f8be75b..d1133db5 100644
--- a/src/extensions/http/controller/rpc_live_controller.h
+++ b/src/extensions/http/controller/rpc_live_controller.h
@@ -704,6 +704,19 @@ RPC_ROUTE("GET", "/live/v1/debug-trace")
logs.append(logEntry);
}
entry["logs"] = logs;
+
+ Json::Value cheats(Json::arrayValue);
+ for (const auto& cheat : trace.cheats)
+ {
+ Json::Value cheatEntry;
+ cheatEntry["id"] = cheat.id;
+ cheatEntry["part"] = (unsigned int)cheat.part;
+ cheatEntry["size"] = cheat.size;
+ cheatEntry["value"] = (Json::UInt64)cheat.value;
+ cheatEntry["hex"] = cheat.hex;
+ cheats.append(cheatEntry);
+ }
+ entry["cheats"] = cheats;
entries.append(entry);
}
json["entries"] = entries;
diff --git a/src/extensions/http/controller/rpc_stats_controller.h b/src/extensions/http/controller/rpc_stats_controller.h
index fe1927ae..d73dffbe 100644
--- a/src/extensions/http/controller/rpc_stats_controller.h
+++ b/src/extensions/http/controller/rpc_stats_controller.h
@@ -140,6 +140,15 @@ RPC_ROUTE("GET", "/v1/fork-stats")
return r;
}
+// Ant walker health: separates a walker chewing through work from one up but delivering nothing.
+RPC_ROUTE("GET", "/v1/ant-walker")
+{
+ (void)req;
+ RpcResp r;
+ r.body = AntWalker::statsJson();
+ return r;
+}
+
// The full durable record of every unforkable tick (not a recent ring) — one line per skipped fork.
RPC_ROUTE("GET", "/v1/unforkable-ticks")
{
diff --git a/src/extensions/supervisor_shim.h b/src/extensions/supervisor_shim.h
index e8015433..b22c27a2 100644
--- a/src/extensions/supervisor_shim.h
+++ b/src/extensions/supervisor_shim.h
@@ -16,6 +16,7 @@
#include
inline char gSidecarPort[16] = "41841"; // node http port -> sidecar listen + unix-socket key
+inline char gAntWalkerThreads[16] = "0"; // matches the node default; 0 keeps the walker unspawned
// Forward a stop signal to the children so the container/service stops promptly.
static void shimForwardSignal(int sig)
@@ -49,8 +50,38 @@ static pid_t shimForkSidecar()
#endif
}
+// Re-exec self as the ant walker, a sibling of the node. Spawned here, not by the node, so it
+// outlives a rollback promotion.
+static pid_t shimForkAntWalker()
+{
+ if (std::atoi(gAntWalkerThreads) <= 0)
+ return -1;
+
+ const pid_t supervisorPid = getpid();
+ pid_t walkerPid = fork();
+ if (walkerPid != 0)
+ return walkerPid;
+ prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
+ if (getppid() != supervisorPid)
+ _exit(0);
+
+ char self[512];
+ ssize_t pathLength = readlink("/proc/self/exe", self, sizeof(self) - 1);
+ if (pathLength <= 0)
+ _exit(127);
+ self[pathLength] = 0;
+
+ char socketPath[128];
+ snprintf(socketPath, sizeof(socketPath), "/tmp/qubic-antwalk-%s.sock", gSidecarPort);
+ execl(self, "qubic-ant-walker", "--ant-walk-worker", "--socket", socketPath, "--threads", gAntWalkerThreads, (char*)nullptr);
+ // Not fatal for the node: without a walker the backlog is simply paid on demand as before.
+ fprintf(stderr, "[shim] could not exec the ant walker (%s), running without it\n", strerror(errno));
+ fflush(stderr);
+ _exit(127);
+}
+
// True while any child other than the sidecar exists (i.e. the node lineage is still alive).
-static bool shimHasNodeChild(pid_t sidecar)
+static bool shimHasNodeChild(pid_t sidecar, pid_t antWalker)
{
char path[64];
snprintf(path, sizeof(path), "/proc/self/task/%d/children", (int)getpid());
@@ -61,7 +92,7 @@ static bool shimHasNodeChild(pid_t sidecar)
bool hasNodeChild = false;
while (fscanf(childrenFile, "%d", &childPid) == 1)
{
- if (childPid != (int)sidecar)
+ if (childPid != (int)sidecar && childPid != (int)antWalker)
{
hasNodeChild = true;
break;
@@ -85,12 +116,19 @@ static inline void runUnderSupervisor(int argc, const char** argv)
std::strncpy(gSidecarPort, argv[i + 1], sizeof(gSidecarPort) - 1);
gSidecarPort[sizeof(gSidecarPort) - 1] = 0;
}
+ if (std::string(argv[i]) == "--ant-walker-threads" && i + 1 < argc)
+ {
+ std::strncpy(gAntWalkerThreads, argv[i + 1], sizeof(gAntWalkerThreads) - 1);
+ gAntWalkerThreads[sizeof(gAntWalkerThreads) - 1] = 0;
+ }
}
if (prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) != 0) // can't subreap: run node inline
return;
pid_t sidecar = shimForkSidecar();
+ pid_t antWalker = shimForkAntWalker();
+ int antWalkerRestarts = 0;
pid_t node = fork();
if (node < 0)
@@ -121,14 +159,30 @@ static inline void runUnderSupervisor(int argc, const char** argv)
sidecar = shimForkSidecar(); // RPC must not stay down: restart it
continue;
}
+ if (antWalker > 0 && reapedPid == antWalker)
+ {
+ sleep(1); // a squatted socket would hot-loop the respawn
+ // An unrunnable walker would otherwise respawn once a second forever.
+ if (++antWalkerRestarts > 5)
+ {
+ fprintf(stderr, "[shim] ant walker died %d times, leaving it down\n", antWalkerRestarts);
+ fflush(stderr);
+ antWalker = -1;
+ continue;
+ }
+ antWalker = shimForkAntWalker();
+ continue;
+ }
lastStatus = status;
if (WIFSIGNALED(status))
sawSignal = true;
- if (!shimHasNodeChild(sidecar))
+ if (!shimHasNodeChild(sidecar, antWalker))
break; // node lineage drained -> shim exits
}
if (sidecar > 0)
kill(sidecar, SIGTERM);
+ if (antWalker > 0)
+ kill(antWalker, SIGTERM);
_exit(sawSignal ? 1 : (WIFEXITED(lastStatus) ? WEXITSTATUS(lastStatus) : 1));
}
diff --git a/src/extensions/wasm/runtime/dispatch.h b/src/extensions/wasm/runtime/dispatch.h
index d55e0674..db745947 100644
--- a/src/extensions/wasm/runtime/dispatch.h
+++ b/src/extensions/wasm/runtime/dispatch.h
@@ -79,6 +79,7 @@ static CallContext createCallContext(const void* context, uint32_t arenaStart, u
callContext.arenaStart = arenaStart;
callContext.arenaTop = arenaStart;
callContext.arenaLimit = arenaLimit;
+ clearCheatWarp();
return callContext;
}
@@ -88,6 +89,7 @@ static void bindJournal(CallContext& callContext, const EngineSlot& slot)
callContext.journalBaseOffset = slot.journalBaseOffset;
callContext.stateOffset = slot.stateOffset;
callContext.journalHeader = slot.journalBaseOffset ? &slot.journalHeader : nullptr;
+ callContext.guestContextOffset = slot.contextOffset;
}
static void bindEnvironment(wasm_exec_env_t execEnv, CallContext& callContext)
diff --git a/src/extensions/wasm/runtime/host_services.h b/src/extensions/wasm/runtime/host_services.h
index 083daf21..a0c2daef 100644
--- a/src/extensions/wasm/runtime/host_services.h
+++ b/src/extensions/wasm/runtime/host_services.h
@@ -28,6 +28,7 @@ static HostServices hostServices =
.burn = &burn,
.epoch = &epoch,
.tick = &tick,
+ .initialTick = &initialTick,
.numberOfTickTransactions = &numberOfTickTransactions,
.getEntity = &getEntity,
.queryFeeReserve = &queryFeeReserve,
@@ -75,6 +76,7 @@ static HostServices hostServices =
.liteInvokeProcedure = &invokeContractProcedure,
.setShareholderProposal = &setShareholderProposal,
.setShareholderVotes = &setShareholderVotes,
+ .cheat = &cheat,
};
} // namespace Wasm::Runtime
diff --git a/src/extensions/wasm/runtime/lhost_registry.h b/src/extensions/wasm/runtime/lhost_registry.h
index 67d37fab..ee4e83dd 100644
--- a/src/extensions/wasm/runtime/lhost_registry.h
+++ b/src/extensions/wasm/runtime/lhost_registry.h
@@ -25,6 +25,8 @@ struct CallContext
uint32_t journalBaseOffset = 0;
uint32_t stateOffset = 0;
const JournalHeader* journalHeader = nullptr;
+ // Where the guest's copy of QpiContext lives, so a prank can rewrite the caller it observes.
+ uint32_t guestContextOffset = 0;
};
static inline CallContext* activeCallContext(wasm_exec_env_t execEnv)
@@ -248,12 +250,54 @@ static void w_logBytes(wasm_exec_env_t execEnv, uint32_t contractIndex, uint32_t
if (callContext && callContext->trace)
{
+ // hostServices.logBytes stamps this word for the log store and zeroes it after; the trace must capture the stamped bytes.
+ *((unsigned int*)message) = contractIndex;
recordLog((TraceEntry*)callContext->trace, (unsigned char)type, message, size);
}
hostServices.logBytes(contractIndex, (unsigned char)type, message, size);
}
+// CC_PRINT lands on the trace and nowhere else: no log id, no qLogger, no tick log range. Everything
+// that touches node state goes on to hostServices.cheat, which is testnet-only and checks the context.
+static int64_t w_cheat(wasm_exec_env_t execEnv, uint32_t op, uint64_t a, uint64_t b, uint32_t ptrOffset, uint32_t len)
+{
+ CallContext* callContext = activeCallContext(execEnv);
+ // The length says whether there is a payload, not the offset: offset 0 is an ordinary address, and
+ // contract state lives there, so testing the offset drops exactly the reads worth printing.
+ void* payload = len ? nativeAddress(execEnv, ptrOffset) : nullptr;
+
+ if (op == CHEAT_OP_PRINT)
+ {
+ if (callContext && callContext->trace)
+ {
+ recordCheat((TraceEntry*)callContext->trace, (uint32_t)(a >> 8), (unsigned char)(a & 0xff), b, payload, len);
+ }
+
+ return 0;
+ }
+
+ if (!callContext)
+ {
+ return CHEAT_ERR_WRONG_CONTEXT;
+ }
+
+ if (op == CHEAT_OP_PRANK || op == CHEAT_OP_UNPRANK)
+ {
+ void* guestContext = callContext->guestContextOffset ? nativeAddress(execEnv, callContext->guestContextOffset) : nullptr;
+ const bool prank = op == CHEAT_OP_PRANK;
+
+ if (prank && (!payload || len != 32))
+ {
+ return CHEAT_ERR_UNKNOWN_OP;
+ }
+
+ return prankCheatCaller(callContext->ctx, guestContext, prank ? (const m256i*)payload : nullptr, (int64_t)a);
+ }
+
+ return hostServices.cheat(callContext->ctx, op, a, b, payload, len);
+}
+
static uint32_t w_getEntity(wasm_exec_env_t execEnv, uint32_t idOffset, uint32_t entityOffset)
{
CallContext* callContext = activeCallContext(execEnv);
diff --git a/src/extensions/wasm/runtime/qpi_services.h b/src/extensions/wasm/runtime/qpi_services.h
index 9838a933..60c6de1a 100644
--- a/src/extensions/wasm/runtime/qpi_services.h
+++ b/src/extensions/wasm/runtime/qpi_services.h
@@ -203,14 +203,29 @@ static long long burn(const void* context, long long amount, unsigned int contra
return procedureContext(context)->burn(amount, contractIndex);
}
+// CC_WARP shifts only what the contract observes; the node still commits the real tick and epoch, so a
+// warping contract cannot move consensus. Reset per dispatch, and constant-folded away off testnet.
+#if defined(TESTNET)
+static unsigned int cheatTickOffset = 0;
+static unsigned short cheatEpochOffset = 0;
+#else
+static constexpr unsigned int cheatTickOffset = 0;
+static constexpr unsigned short cheatEpochOffset = 0;
+#endif
+
static unsigned short epoch(const void* context)
{
- return functionContext(context)->epoch();
+ return (unsigned short)(functionContext(context)->epoch() + cheatEpochOffset);
}
static unsigned int tick(const void* context)
{
- return functionContext(context)->tick();
+ return functionContext(context)->tick() + cheatTickOffset;
+}
+
+static unsigned int initialTick(const void* context)
+{
+ return functionContext(context)->initialTick();
}
static int numberOfTickTransactions(const void* context)
@@ -424,6 +439,133 @@ static unsigned char distributeDividends(const void* context, long long amountPe
return (unsigned char)procedureContext(context)->distributeDividends(amountPerShare);
}
+// Mirrors QpiContext's protected layout so a prank can rewrite the caller a contract observes. The
+// size assert turns any drift in QpiContext into a compile error rather than a silent misread.
+struct CheatContextImage
+{
+ unsigned int currentContractIndex;
+ int stackIndex;
+ m256i currentContractId;
+ m256i originator;
+ m256i invocator;
+ long long invocationReward;
+ unsigned char entryPoint;
+};
+static_assert(sizeof(CheatContextImage) == sizeof(QPI::QpiContext), "CheatContextImage out of sync with QPI::QpiContext");
+
+static void clearCheatWarp()
+{
+#if defined(TESTNET)
+ cheatTickOffset = 0;
+ cheatEpochOffset = 0;
+#endif
+}
+
+#if defined(TESTNET)
+// Sets a balance outright rather than transferring, which is the whole point of a deal.
+static long long dealCheatBalance(const m256i& publicKey, long long amount)
+{
+ // The amount arrives as an unsigned word, so a value past the signed range lands here negative.
+ // A negative balance is meaningless, and letting it through would decrease against index -1.
+ if (amount < 0)
+ {
+ return CHEAT_ERR_UNKNOWN_OP;
+ }
+
+ const int index = spectrumIndex(publicKey);
+ const long long current = index < 0 ? 0 : energy(index);
+
+ if (current > amount)
+ {
+ return decreaseEnergy(index, current - amount) ? amount : CHEAT_ERR_WRONG_CONTEXT;
+ }
+
+ if (current < amount)
+ {
+ increaseEnergy(publicKey, amount - current);
+ }
+
+ return amount;
+}
+
+#endif
+
+// Rewrites the guest's copy of the context. The host's own QpiContext is untouched, so the node still
+// bills and attributes the real caller; only what the contract reads changes. Lives here rather than in
+// the vtable because the guest address comes from the adapter, and the vtable signature must mirror the
+// guest's exactly.
+static long long prankCheatCaller(const void* context, void* guestContext, const m256i* caller, long long invocationReward)
+{
+#if defined(TESTNET)
+ const CheatContextImage* hostImage = (const CheatContextImage*)context;
+
+ if (!hostImage || hostImage->entryPoint == (unsigned char)DispatchKind::UserFunction)
+ {
+ return CHEAT_ERR_WRONG_CONTEXT;
+ }
+
+ if (!guestContext)
+ {
+ return CHEAT_ERR_WRONG_CONTEXT;
+ }
+
+ CheatContextImage* image = (CheatContextImage*)guestContext;
+
+ // Unprank restores what the host handed the guest at dispatch, which is the host's own context.
+ image->originator = caller ? *caller : hostImage->originator;
+ image->invocator = caller ? *caller : hostImage->invocator;
+ image->invocationReward = caller ? invocationReward : hostImage->invocationReward;
+ return image->invocationReward;
+#else
+ (void)context;
+ (void)guestContext;
+ (void)caller;
+ (void)invocationReward;
+ return CHEAT_ERR_DISABLED;
+#endif
+}
+
+// Development cheatcodes, opcode-dispatched behind one ABI row so a new one costs no import. Every
+// state-touching opcode is testnet-only, and refusal is always a negative return, never a trap.
+// CHEAT_OP_PRINT and the prank opcodes never reach here: the adapter owns the trace and guest memory.
+static long long cheat(const void* context, unsigned int op, unsigned long long a, unsigned long long b, void* ptr, unsigned int len)
+{
+#if defined(TESTNET)
+ const CheatContextImage* image = (const CheatContextImage*)context;
+
+ // Every opcode below mutates something a read-only call must not touch.
+ if (!image || image->entryPoint == (unsigned char)DispatchKind::UserFunction)
+ {
+ return CHEAT_ERR_WRONG_CONTEXT;
+ }
+
+ switch (op)
+ {
+ case CHEAT_OP_DEAL:
+ return (ptr && len == 32) ? dealCheatBalance(*(const m256i*)ptr, (long long)a) : CHEAT_ERR_UNKNOWN_OP;
+
+ case CHEAT_OP_WARP_TICK:
+ cheatTickOffset += (unsigned int)a;
+ return (long long)cheatTickOffset;
+
+ case CHEAT_OP_WARP_EPOCH:
+ cheatEpochOffset = (unsigned short)(cheatEpochOffset + a);
+ return (long long)cheatEpochOffset;
+
+ default:
+ return CHEAT_ERR_UNKNOWN_OP;
+ }
+#else
+ (void)context;
+ (void)op;
+ (void)a;
+ (void)b;
+ (void)ptr;
+ (void)len;
+ return CHEAT_ERR_DISABLED;
+#endif
+}
+
} // namespace Wasm::Runtime
#endif // LITE_WASM_SC
diff --git a/src/extensions/wasm/runtime/trace.h b/src/extensions/wasm/runtime/trace.h
index e27f4ad0..4049bd5b 100644
--- a/src/extensions/wasm/runtime/trace.h
+++ b/src/extensions/wasm/runtime/trace.h
@@ -45,6 +45,16 @@ struct LogTrace
std::string hex;
};
+// One CC_PRINT argument. size == 0 means the value came through by register instead of by pointer.
+struct CheatEntry
+{
+ unsigned int id = 0;
+ unsigned char part = 0;
+ unsigned int size = 0;
+ unsigned long long value = 0;
+ std::string hex;
+};
+
struct TraceEntry
{
unsigned long long sequence = 0;
@@ -67,6 +77,7 @@ struct TraceEntry
std::vector stateDiff;
std::vector hostCalls;
std::vector logs;
+ std::vector cheats;
};
// On by default so a debugger attached after the fact still finds the calls that mattered. This header
@@ -219,6 +230,23 @@ static inline void recordLog(TraceEntry* entry, unsigned char type, const void*
});
}
+// The development print channel. Deliberately separate from logs: it consumes no log id, reaches no
+// qLogger, and is stripped from the contract before submission.
+static inline void recordCheat(TraceEntry* entry, unsigned int id, unsigned char part, unsigned long long value, const void* bytes, unsigned int size)
+{
+ if (!entry)
+ {
+ return;
+ }
+
+ // A guest offset outside linear memory resolves to null; record the size but never read from it.
+ const bool readable = bytes && size;
+
+ entry->cheats.push_back(CheatEntry{
+ id, part, size, value, readable ? hex(bytes, size) : std::string(),
+ });
+}
+
} // namespace Wasm::Runtime
#endif // LITE_WASM_SC
diff --git a/src/extensions/wasm/sdk/intercontract_calls.h b/src/extensions/wasm/sdk/intercontract_calls.h
index fab80bb6..6d15ec00 100644
--- a/src/extensions/wasm/sdk/intercontract_calls.h
+++ b/src/extensions/wasm/sdk/intercontract_calls.h
@@ -13,10 +13,13 @@ int invokeProcedure(const void* callerContext, unsigned int calleeIndex, unsigne
} // namespace Wasm::Sdk
-// Calls remain restricted to lower-index contracts.
+// Calls remain restricted to lower-index contracts. The entry-kind and locals-size checks match
+// qpi_macros.h, so a mistake fails to compile here instead of returning a call error at run time.
#undef CALL_OTHER_CONTRACT_FUNCTION_E
#define CALL_OTHER_CONTRACT_FUNCTION_E(contractStateType, function, input, output, errorVar) \
static_assert(contractStateType::__contract_index < CONTRACT_INDEX, "lite: can only call a lower-index contract"); \
+ static_assert(sizeof(contractStateType::function##_locals) <= MAX_SIZE_OF_CONTRACT_LOCALS, #function "_locals size too large"); \
+ static_assert(contractStateType::__is_function_##function, "CALL_OTHER_CONTRACT_FUNCTION_E() cannot be used to invoke procedures."); \
QPI::InterContractCallError errorVar = (QPI::InterContractCallError)Wasm::Sdk::callFunction( \
&qpi, contractStateType::__contract_index, contractStateType##_##function##_inputType, \
&(input), sizeof(input), &(output), sizeof(output))
@@ -28,6 +31,8 @@ int invokeProcedure(const void* callerContext, unsigned int calleeIndex, unsigne
#undef INVOKE_OTHER_CONTRACT_PROCEDURE_E
#define INVOKE_OTHER_CONTRACT_PROCEDURE_E(contractStateType, procedure, input, output, invocationReward, errorVar) \
static_assert(contractStateType::__contract_index < CONTRACT_INDEX, "lite: can only call a lower-index contract"); \
+ static_assert(sizeof(contractStateType::procedure##_locals) <= MAX_SIZE_OF_CONTRACT_LOCALS, #procedure "_locals size too large"); \
+ static_assert(!contractStateType::__is_function_##procedure, "INVOKE_OTHER_CONTRACT_PROCEDURE_E() cannot be used to call functions."); \
QPI::InterContractCallError errorVar = (QPI::InterContractCallError)Wasm::Sdk::invokeProcedure( \
&qpi, contractStateType::__contract_index, contractStateType##_##procedure##_inputType, \
&(input), sizeof(input), &(output), sizeof(output), (invocationReward))
diff --git a/src/extensions/wasm/sdk/lhost_imports.h b/src/extensions/wasm/sdk/lhost_imports.h
index 7f3881b0..ef6cb5bc 100644
--- a/src/extensions/wasm/sdk/lhost_imports.h
+++ b/src/extensions/wasm/sdk/lhost_imports.h
@@ -44,6 +44,7 @@ LH_IMPORT(abort) void lh_abort(unsigned int errorCode);
LH_IMPORT(burn) long long lh_burn(long long amount, unsigned int contractIndexBurnedFor);
LH_IMPORT(epoch) unsigned int lh_epoch();
LH_IMPORT(tick) unsigned int lh_tick();
+LH_IMPORT(initialTick) unsigned int lh_initialTick();
LH_IMPORT(numberOfTickTransactions) int lh_numberOfTickTransactions();
LH_IMPORT(getEntity) unsigned int lh_getEntity(const void* id32, void* entityOut);
LH_IMPORT(queryFeeReserve) long long lh_queryFeeReserve(unsigned int contractIndex);
@@ -91,6 +92,7 @@ LH_IMPORT(liteCallFunction) int lh_liteCallFunction(unsigned int calleeIdx, unsi
LH_IMPORT(liteInvokeProcedure) int lh_liteInvokeProcedure(unsigned int calleeIdx, unsigned int inputType, const void* in, unsigned int inSize, void* out, unsigned int outSize, long long invocationReward);
LH_IMPORT(liteSetShareholderProposal) unsigned int lh_liteSetShareholderProposal(unsigned int calleeIdx, const void* proposal1024, long long invocationReward);
LH_IMPORT(liteSetShareholderVotes) unsigned int lh_liteSetShareholderVotes(unsigned int calleeIdx, const void* voteData, unsigned int voteSize, long long invocationReward);
+LH_IMPORT(cheat) long long lh_cheat(unsigned int op, unsigned long long a, unsigned long long b, void* ptr, unsigned int len);
} // extern "C"
namespace Wasm::Sdk
diff --git a/src/extensions/wasm/sdk/qpi_forwarders.h b/src/extensions/wasm/sdk/qpi_forwarders.h
index 06eae067..3f780c0f 100644
--- a/src/extensions/wasm/sdk/qpi_forwarders.h
+++ b/src/extensions/wasm/sdk/qpi_forwarders.h
@@ -45,6 +45,11 @@ unsigned int QPI::QpiContextFunctionCall::tick() const
return lh_tick();
}
+unsigned int QPI::QpiContextFunctionCall::initialTick() const
+{
+ return lh_initialTick();
+}
+
int QPI::QpiContextFunctionCall::numberOfTickTransactions() const
{
return lh_numberOfTickTransactions();
diff --git a/src/extensions/wasm/shared/abi_metadata.h b/src/extensions/wasm/shared/abi_metadata.h
index d47e8089..7197151d 100644
--- a/src/extensions/wasm/shared/abi_metadata.h
+++ b/src/extensions/wasm/shared/abi_metadata.h
@@ -1,7 +1,7 @@
#pragma once
// Canonical rows shared by WAMR registration and SDK metadata generation.
-#define WASM_ABI_VERSION 5u
+#define WASM_ABI_VERSION 6u
// G/H selects generated or handwritten adapters; Q/I selects QPI-bound or infrastructure calls.
@@ -35,6 +35,7 @@
HQ("burn", burn, w_burn, "(Ii)I") \
GQ("epoch", epoch, "()i") \
GQ("tick", tick, "()i") \
+ GQ("initialTick", initialTick, "()i") \
GQ("numberOfTickTransactions", numberOfTickTransactions, "()i") \
HQ("getEntity", getEntity, w_getEntity, "(ii)i") \
GQ("queryFeeReserve", queryFeeReserve, "(i)I") \
@@ -81,4 +82,5 @@
HQ("liteCallFunction", liteCallFunction, w_liteCallFunction, "(iiiiii)i") \
HQ("liteInvokeProcedure", liteInvokeProcedure, w_liteInvokeProcedure, "(iiiiiiI)i") \
HQ("liteSetShareholderProposal", setShareholderProposal, w_liteSetShareholderProposal, "(iiI)i") \
- HQ("liteSetShareholderVotes", setShareholderVotes, w_liteSetShareholderVotes, "(iiiI)i")
+ HQ("liteSetShareholderVotes", setShareholderVotes, w_liteSetShareholderVotes, "(iiiI)i") \
+ HQ("cheat", cheat, w_cheat, "(iIIii)I")
diff --git a/src/extensions/wasm/shared/abi_types.h b/src/extensions/wasm/shared/abi_types.h
index 14d3bea9..5bb6e458 100644
--- a/src/extensions/wasm/shared/abi_types.h
+++ b/src/extensions/wasm/shared/abi_types.h
@@ -64,6 +64,7 @@ struct HostServices
long long (*burn)(const void* ctx, long long amount, unsigned int contractIndexBurnedFor);
unsigned short (*epoch)(const void* ctx);
unsigned int (*tick)(const void* ctx);
+ unsigned int (*initialTick)(const void* ctx);
int (*numberOfTickTransactions)(const void* ctx);
unsigned char (*getEntity)(const void* ctx, const void* id32, void* entityOut);
long long (*queryFeeReserve)(const void* ctx, unsigned int contractIndex);
@@ -125,8 +126,26 @@ struct HostServices
unsigned short (*setShareholderProposal)(const void* callerCtx, unsigned int calleeIdx, const void* proposal1024, long long invocationReward);
unsigned char (*setShareholderVotes)(const void* callerCtx, unsigned int calleeIdx, const void* voteData, unsigned int voteSize,
long long invocationReward);
+ // Testnet development aid: opcode-dispatched, negative return on refusal. See CHEAT_OP_* below.
+ long long (*cheat)(const void* ctx, unsigned int op, unsigned long long a, unsigned long long b, void* ptr, unsigned int len);
};
+// Opcodes for HostServices::cheat. Reserved numbers stay listed so an older node answers CHEAT_ERR_UNKNOWN_OP
+// rather than silently accepting a newer client's call.
+#define CHEAT_OP_PRINT 1u
+#define CHEAT_OP_DEAL 2u
+#define CHEAT_OP_WARP_TICK 3u
+#define CHEAT_OP_WARP_EPOCH 4u
+#define CHEAT_OP_PRANK 5u
+#define CHEAT_OP_UNPRANK 6u
+#define CHEAT_OP_WARP_TIME 7u // reserved: needs a calendar inverse the node does not have
+#define CHEAT_OP_SNAPSHOT 8u // reserved: no world-snapshot primitive exists in either runtime
+#define CHEAT_OP_REVERT 9u // reserved, pairs with CHEAT_OP_SNAPSHOT
+
+#define CHEAT_ERR_UNKNOWN_OP (-1LL)
+#define CHEAT_ERR_DISABLED (-2LL)
+#define CHEAT_ERR_WRONG_CONTEXT (-3LL)
+
#define WASM_MAX_USER_ENTRIES 1024
struct ContractDescriptor
diff --git a/src/mining/ant_colony/ant_colony.h b/src/mining/ant_colony/ant_colony.h
index 4f8af706..8c1b3f23 100644
--- a/src/mining/ant_colony/ant_colony.h
+++ b/src/mining/ant_colony/ant_colony.h
@@ -521,7 +521,7 @@ class AntColony
// Constraint specific functions
// Resolves a parent for scoring. outParentRec is null for ROOT_REF, the caller derives the
- // per-identity root from the submitter's pubkey instead.
+ // shared epoch root from the root seed instead.
ValidityResult tryGetParent(const SolutionRef& parentRef,
const AntSolutionRecord** outParentRec) const;
diff --git a/src/mining/bpp9000_task.generated.h b/src/mining/bpp9000_task.generated.h
index e5fa0670..28aab47a 100644
--- a/src/mining/bpp9000_task.generated.h
+++ b/src/mining/bpp9000_task.generated.h
@@ -1,67 +1,67 @@
// AUTO-GENERATED from data/bpp9000.task by cmake/EmbedBinary.cmake.
-// SHA-256: 0c5e9e42c6d86c320af62f4125ca85b2446f2b098893fd6521bcf66c22f7f00a
+// SHA-256: aad604c8ba64325ee83dc12e963ff76b393481dd24ff60ef334551470d4b1cb4
#pragma once
alignas(64) inline constexpr unsigned char BPP9000_TASK_BYTES[] = {
0x4c, 0x55, 0x54, 0x54, 0x01, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x38, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x13, 0xe9, 0x9d, 0x5b, 0x2f, 0xca, 0x56, 0xaa, 0x78, 0x9c, 0xb9, 0x59, 0x57, 0x5f, 0x48, 0x39,
- 0x2f, 0x1a, 0x44, 0x90, 0x9a, 0x8e, 0xaf, 0x27, 0xf2, 0xde, 0x8f, 0x8d, 0x74, 0xb0, 0x7a, 0x6b,
+ 0x39, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x76, 0xa3, 0xf5, 0x10, 0x20, 0x05, 0x9b, 0xf5, 0x22, 0x7f, 0x30, 0x20, 0x13, 0x69, 0xcb, 0x0a,
+ 0x32, 0x3b, 0x93, 0xcd, 0xc2, 0x59, 0x8e, 0x1f, 0x1c, 0x59, 0x66, 0xbd, 0x9c, 0x0d, 0xf4, 0xef,
0x97, 0x9c, 0xdc, 0x22, 0x47, 0xd2, 0xca, 0x4e, 0xd3, 0xd6, 0x14, 0xbf, 0x27, 0x89, 0x63, 0x84,
0xcb, 0x1c, 0x9c, 0x3d, 0x80, 0x4a, 0xf6, 0xed, 0xe6, 0xb5, 0x9f, 0xc5, 0x2c, 0x0e, 0x3d, 0xfa,
- 0x0b, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00,
- 0x15, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
- 0x21, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
- 0x3c, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00,
- 0x26, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x3b, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
- 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x1b, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00,
- 0x15, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
- 0x22, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x3f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
- 0x3d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
- 0x3f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x2a, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00,
- 0x29, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x33, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
- 0x15, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
- 0x26, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x1c, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
- 0x13, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00,
- 0x33, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x1c, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
- 0x1f, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
- 0x34, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00,
- 0x1c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00,
- 0x20, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
- 0x18, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00,
- 0x25, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x2b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
- 0x2e, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00,
- 0x2f, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00,
- 0x28, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x36, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
- 0x34, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00,
- 0x17, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00,
- 0x18, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
- 0x17, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x34, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
- 0x11, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x23, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00,
+ 0x36, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
+ 0x3d, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
+ 0x3b, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x0f, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
+ 0x2d, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x35, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x28, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
+ 0x34, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00,
+ 0x37, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
+ 0x1d, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
+ 0x2f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00,
+ 0x35, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00,
+ 0x17, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
+ 0x35, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x39, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
+ 0x2f, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
+ 0x27, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
+ 0x3e, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00,
+ 0x35, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
+ 0x2c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00,
+ 0x25, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x3c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x22, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
+ 0x27, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
+ 0x38, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x36, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x16, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00,
+ 0x1b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00,
+ 0x1b, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
+ 0x3b, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x29, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
+ 0x31, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
+ 0x38, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
0x79, 0x0d, 0x0d, 0x0d, 0x00, 0x00, 0x51, 0x5e, 0x0c, 0x01, 0x79, 0x28, 0x79, 0x0c, 0x00, 0x79,
0x28, 0x0d, 0x01, 0x00, 0x00, 0x00, 0x5e, 0x04, 0x01, 0x79, 0x79, 0x70, 0x04, 0x00, 0x79, 0x79,
0x25, 0x04, 0x00, 0x79, 0x79, 0x00, 0x00, 0x00, 0x79, 0x79, 0x28, 0x09, 0x00, 0x79, 0x79, 0x5b,
@@ -2799,7 +2799,7 @@ alignas(64) inline constexpr unsigned char BPP9000_TASK_BYTES[] = {
0x01, 0x79, 0x28, 0x00, 0x00, 0x00, 0x79, 0x5e, 0x28, 0x03, 0x00, 0x79, 0x28, 0x1f, 0x0d, 0x00,
0x79, 0x79, 0x70, 0x01, 0x00, 0x00, 0x00, 0x75, 0x0d, 0x01, 0x79, 0x79, 0x27, 0x03, 0x00, 0x00,
0x00, 0x78, 0x00, 0x01, 0x00, 0x00, 0x52, 0x03, 0x01, 0x79, 0x79, 0x76, 0x00, 0x00, 0x79, 0x79,
- 0x25, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x09, 0x01,
+ 0x25, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x09, 0x01, 0x00, 0x00, 0x1c, 0x0d, 0x01,
};
inline constexpr unsigned long long BPP9000_TASK_SIZE = sizeof(BPP9000_TASK_BYTES);
-static_assert(BPP9000_TASK_SIZE == 44744ULL);
+static_assert(BPP9000_TASK_SIZE == 44749ULL);
diff --git a/src/mining/score_bpp9000.h b/src/mining/score_bpp9000.h
index 40468c8c..039a98e6 100644
--- a/src/mining/score_bpp9000.h
+++ b/src/mining/score_bpp9000.h
@@ -11,16 +11,6 @@ namespace score_engine
// Largest L (mutations per step) the scorer clamps nonce[1] to
static constexpr unsigned int MAX_LUT_ENTRIES_PER_STEP = 10;
-// A bpp9000 nonce is canonical iff its score-irrelevant knob bytes are canonical:
-// nonce[0] = algo (enforced by routing), nonce[1] = L in [1, MAX_LUT_ENTRIES_PER_STEP], nonce[2] = K = 0
-static bool isCanonicalBpp9000Nonce(const unsigned char* nonce)
-{
- return (getAlgoType(nonce) == AlgoType::Bpp9000)
- && (nonce[1] >= 1)
- && (nonce[1] <= MAX_LUT_ENTRIES_PER_STEP)
- && (nonce[2] == 0);
-}
-
template
struct ScoreBpp9000
{
@@ -45,6 +35,17 @@ struct ScoreBpp9000
static_assert(lutSize <= lutStride, "LUT rows must fit the padded stride");
+ // A nonce is canonical if its score-irrelevant knob bytes are canonical:
+ // nonce[0] = algo (enforced by routing), nonce[1] = L in [1, MAX_LUT_ENTRIES_PER_STEP],
+ // nonce[2] = K. The standalone walk pins K to 0, so only 0 is canonical there.
+ static bool isCanonicalStandaloneNonce(const unsigned char* nonce)
+ {
+ return (getAlgoType(nonce) == AlgoType::Bpp9000)
+ && (nonce[1] >= 1)
+ && (nonce[1] <= MAX_LUT_ENTRIES_PER_STEP)
+ && (nonce[2] == 0);
+ }
+
// K is a real degree of freedom here: the walk restores K = nonce[2] as its explore-step count
static bool isCanonicalAntNonce(const unsigned char* nonce)
{
@@ -1073,8 +1074,9 @@ struct ScoreBpp9000
compact(bestANN, out);
}
- // Seed the ANN: root LUT from the pubkey alone (each computor's fixed root); mutation seeds from
- // pubkey+nonce (nonce[0..2] are the algo/L/K knobs, excluded from the RNG). Returns the start score.
+ // Standalone path only: root LUT from the pubkey alone; the ant path derives its shared epoch
+ // root via deriveRootANN(rootSeed) instead. Mutation seeds from pubkey+nonce (nonce[0..2] are
+ // the algo/L/K knobs, excluded from the RNG). Returns the start score.
unsigned int initializeANN(
const unsigned char* publicKey,
const unsigned char* nonce,
@@ -1168,14 +1170,16 @@ struct ScoreBpp9000
return computeScoreFromCurrent(L, K, cur);
}
- // Ant colony: the network every one of an identity's lineages starts from. Written to a buffer the
- // caller owns, so two roots can be derived on one engine without the first silently becoming the
- // second - a child scored against the wrong root would differ only in resourceTestingDigest.
+ // Ant colony: the shared per-epoch network every identity's tree starts from. rootSeed is the
+ // epoch-start spectrum digest, so all identities derive the identical root; only the mutation
+ // walks stay per-identity. Written to a buffer the caller owns, so two roots can be derived on
+ // one engine without the first silently becoming the second - a child scored against the wrong
+ // root would differ only in resourceTestingDigest.
// Uses currentANN as its working buffer, so it destroys whatever the engine was holding. Callers
// derive a root and then score from it, which overwrites currentANN anyway.
- void deriveRootANN(const unsigned char* publicKey, const unsigned char* pRandom2Pool, ANN& out)
+ void deriveRootANN(const unsigned char* rootSeed, const unsigned char* pRandom2Pool, ANN& out)
{
- deriveRootLut(publicKey, pRandom2Pool);
+ deriveRootLut(rootSeed, pRandom2Pool);
applyRootLut(currentANN);
compact(currentANN, out);
}
diff --git a/src/mining/score_engine.h b/src/mining/score_engine.h
index a95cb048..a512d5ad 100644
--- a/src/mining/score_engine.h
+++ b/src/mining/score_engine.h
@@ -12,6 +12,9 @@ struct ScoreEngine
ScoreBpp9000 _bpp9000Score;
unsigned char lastNonceByte0;
+ // The inheritable per-neuron LUT the ant colony branches on.
+ using AntAnn = typename ScoreBpp9000::ANN;
+
void initMemory()
{
setMem(&_bpp9000Score, sizeof(ScoreBpp9000), 0);
@@ -19,12 +22,6 @@ struct ScoreEngine
_bpp9000Score.initMemory();
}
- // Unused function
- void initMiningData(const unsigned char* randomPool)
- {
-
- }
-
// Load the task blocks into the active bpp9000 leaf; returns false on invalid topology/data.
bool loadTask(const unsigned char* topoBlock, const unsigned char* dataBlock)
{
@@ -39,8 +36,6 @@ struct ScoreEngine
unsigned int computeBpp9000Score(const unsigned char* publicKey, const unsigned char* nonce, const unsigned char* randomPool)
{
- // The score IS the error count - smaller is better. A timeout maps to the worst in-range value
- // rather than INVALID_SCORE_VALUE, which the score cache cannot store (it reads back as a miss).
const unsigned int failures = _bpp9000Score.computeScore(publicKey, nonce, randomPool);
return (failures == ScoreBpp9000::INFINITE_ERROR)
? (unsigned int)ScoreBpp9000::numberOfWindows
@@ -61,9 +56,19 @@ struct ScoreEngine
}
}
- // Each engine owns its canonical ant-nonce rule; this switch is the algorithm seam, so ingress
- // code stays algorithm-agnostic. Neuraxon is reserved and not ant-minable, so no nonce in its
- // slot is canonical.
+ // Each engine owns its canonical standalone-nonce rule
+ static bool isCanonicalStandaloneNonce(const unsigned char* nonce)
+ {
+ switch (getAlgoType(nonce))
+ {
+ case AlgoType::Bpp9000:
+ return ScoreBpp9000::isCanonicalStandaloneNonce(nonce);
+ default:
+ return false;
+ }
+ }
+
+ // Each engine owns its canonical ant-nonce rule
static bool isCanonicalAntNonce(const unsigned char* nonce)
{
switch (getAlgoType(nonce))
@@ -75,6 +80,33 @@ struct ScoreEngine
}
}
+ // Ant colony: the shared per-epoch network every identity's tree starts from; rootSeed is the
+ // epoch-start spectrum digest
+ void deriveAntRootANN(const unsigned char* rootSeed, const unsigned char* randomPool, AntAnn& out)
+ {
+ _bpp9000Score.deriveRootANN(rootSeed, randomPool, out);
+ }
+
+ // Ant colony: score a child by inheriting the parent's network and walking it with the child's
+ // own seeds. Returns INVALID_SCORE_VALUE for a non-canonical nonce or an unsupported algorithm.
+ unsigned int computeAntScoreFromParent(const AntAnn& parent, const unsigned char* publicKey,
+ const unsigned char* nonce, const unsigned char* anchorDigest, const unsigned char* randomPool)
+ {
+ switch (getAlgoType(nonce))
+ {
+ case AlgoType::Bpp9000:
+ return _bpp9000Score.computeScoreFromParent(parent, publicKey, nonce, anchorDigest, randomPool);
+ default:
+ return INVALID_SCORE_VALUE;
+ }
+ }
+
+ // Ant colony: the network that produced the score the walk returned.
+ void getAntBestANN(AntAnn& out)
+ {
+ _bpp9000Score.getBestANN(out);
+ }
+
// returns last computed output neurons of the active bpp9000 slot
m256i getLastOutput()
{
diff --git a/src/network_messages/ant_colony_message.h b/src/network_messages/ant_colony_message.h
index 98b123eb..10c31ed2 100644
--- a/src/network_messages/ant_colony_message.h
+++ b/src/network_messages/ant_colony_message.h
@@ -95,7 +95,7 @@ static_assert(sizeof(AntIdentityTreeResponse)
// RespondAntParentAnnHeader.status values.
constexpr unsigned char ANT_PARENT_ANN_STATUS_OK = 0; // ANN bytes follow the header
constexpr unsigned char ANT_PARENT_ANN_STATUS_NOT_FOUND = 1; // parentRef has no record
-constexpr unsigned char ANT_PARENT_ANN_STATUS_IS_ROOT = 2; // ROOT_REF; no ANN payload - miner derives its own per-identity root
+constexpr unsigned char ANT_PARENT_ANN_STATUS_IS_ROOT = 2; // ROOT_REF; no ANN payload - miner derives the shared epoch root
// ONE tree node's stored network, named by parentRef - the ANN state a miner mutates to extend
// that node. The tree itself is listed by the identity-tree query; this fetches the material for a
diff --git a/src/platform/concurrency.h b/src/platform/concurrency.h
index 41670cbe..8aa653be 100644
--- a/src/platform/concurrency.h
+++ b/src/platform/concurrency.h
@@ -40,6 +40,8 @@ namespace ForkCensus
{
std::atomic depth{ 0 };
std::atomic what{ nullptr };
+ // Every LockGuard lock reports the same `what`, so only the address names the offender.
+ std::atomic where{ nullptr };
std::atomic live{ 0 }; // 0 = free/reusable, 1 = owned by a live thread
};
inline Slot gSlots[MAX_THREADS];
@@ -99,13 +101,14 @@ namespace ForkCensus
}
}
- inline void enter(const char* what)
+ inline void enter(const char* what, const volatile void* where = nullptr)
{
if (tlLockSlot < 0)
claimSlot();
if (tlLockSlot < 0)
return; // registry full (>MAX_THREADS live): best-effort, this thread uncounted
gSlots[tlLockSlot].what.store(what, std::memory_order_relaxed);
+ gSlots[tlLockSlot].where.store(where, std::memory_order_relaxed);
gSlots[tlLockSlot].depth.fetch_add(1, std::memory_order_relaxed);
}
inline void leave()
@@ -131,6 +134,22 @@ namespace ForkCensus
}
return heldDepth < 0 ? 0 : heldDepth;
}
+ inline const volatile void* offenderAddress()
+ {
+ const int selfSlot = tlLockSlot;
+ int slotCount = gCount.load(std::memory_order_acquire);
+ if (slotCount > MAX_THREADS)
+ slotCount = MAX_THREADS;
+ for (int slotIndex = 0; slotIndex < slotCount; slotIndex++)
+ {
+ if (slotIndex != selfSlot && gSlots[slotIndex].depth.load(std::memory_order_relaxed) > 0)
+ {
+ return gSlots[slotIndex].where.load(std::memory_order_relaxed);
+ }
+ }
+ return nullptr;
+ }
+
inline const char* offenderName()
{
if (gOverflow.load(std::memory_order_acquire))
@@ -157,6 +176,7 @@ namespace ForkCensus
{
gSlots[i].depth.store(0, std::memory_order_relaxed);
gSlots[i].what.store(nullptr, std::memory_order_relaxed);
+ gSlots[i].where.store(nullptr, std::memory_order_relaxed);
gSlots[i].live.store(0, std::memory_order_relaxed);
}
gCount.store(0, std::memory_order_release);
@@ -165,9 +185,9 @@ namespace ForkCensus
}
}
-inline void forkCensusEnter(const char* what)
+inline void forkCensusEnter(const char* what, const volatile void* where = nullptr)
{
- ForkCensus::enter(what);
+ ForkCensus::enter(what, where);
}
inline void forkCensusLeave()
@@ -185,6 +205,11 @@ inline const char* forkCensusOffender()
return ForkCensus::offenderName();
}
+inline const volatile void* forkCensusOffenderAddress()
+{
+ return ForkCensus::offenderAddress();
+}
+
inline void forkCensusResetForChildPromote()
{
ForkCensus::resetForChildPromote();
@@ -199,7 +224,7 @@ inline bool gForkCensus = true;
do { \
while (_InterlockedCompareExchange8(&lock, 1, 0)) \
_mm_pause(); \
- forkCensusEnter(#lock " @ " __FILE__); \
+ forkCensusEnter(#lock " @ " __FILE__, &lock); \
} while (0)
#ifdef NDEBUG
@@ -232,7 +257,7 @@ class BusyWaitingTracker
while (_InterlockedCompareExchange8(&lock, 1, 0)) \
bwt.pause(); \
} \
- forkCensusEnter(#lock " @ " __FILE__); \
+ forkCensusEnter(#lock " @ " __FILE__, &lock); \
} while (0)
#endif
@@ -240,7 +265,7 @@ class BusyWaitingTracker
// Try to acquire lock and return if successful (without blocking)
#define TRY_ACQUIRE(lock) \
(_InterlockedCompareExchange8(&lock, 1, 0) == 0 \
- ? (forkCensusEnter(#lock " @ " __FILE__), true) \
+ ? (forkCensusEnter(#lock " @ " __FILE__, &lock), true) \
: false)
// Release lock
diff --git a/src/public_settings.h b/src/public_settings.h
index b15e051f..a46161b9 100644
--- a/src/public_settings.h
+++ b/src/public_settings.h
@@ -115,12 +115,12 @@ static_assert(AUTO_FORCE_NEXT_TICK_THRESHOLD* TARGET_TICK_DURATION >= PEER_REFRE
// Config options that should NOT be changed by operators
#define VERSION_A 1
-#define VERSION_B 302
+#define VERSION_B 303
#define VERSION_C 1
// Epoch and initial tick for node startup
-#define EPOCH 228
-#define TICK 76550000
+#define EPOCH 229
+#define TICK 77700000
#define TICK_IS_FIRST_TICK_OF_EPOCH 1 // Set to 0 if the network is restarted during the EPOCH with a new initial TICK
#define ARBITRATOR "AFZPUAIYVPNUYGJRQVLUKOPPVLHAZQTGLYAAUUNBXFTVTAMSBKQBLEIEPCVJ"
@@ -160,8 +160,8 @@ static constexpr unsigned int NEURAXON_SOLUTION_THRESHOLD_DEFAULT = 1;
// and hash-verified at node init
static wchar_t SCORE_BPP9000_TASK_FILE_NAME[] = L"bpp9000.task";
static constexpr unsigned char BPP9000_TOPOLOGY_HASH[32] =
- { 0x13, 0xe9, 0x9d, 0x5b, 0x2f, 0xca, 0x56, 0xaa, 0x78, 0x9c, 0xb9, 0x59, 0x57, 0x5f, 0x48, 0x39,
- 0x2f, 0x1a, 0x44, 0x90, 0x9a, 0x8e, 0xaf, 0x27, 0xf2, 0xde, 0x8f, 0x8d, 0x74, 0xb0, 0x7a, 0x6b };
+ { 0x76, 0xa3, 0xf5, 0x10, 0x20, 0x05, 0x9b, 0xf5, 0x22, 0x7f, 0x30, 0x20, 0x13, 0x69, 0xcb, 0x0a,
+ 0x32, 0x3b, 0x93, 0xcd, 0xc2, 0x59, 0x8e, 0x1f, 0x1c, 0x59, 0x66, 0xbd, 0x9c, 0x0d, 0xf4, 0xef };
static constexpr unsigned char BPP9000_DATA_HASH[32] =
{ 0x97, 0x9c, 0xdc, 0x22, 0x47, 0xd2, 0xca, 0x4e, 0xd3, 0xd6, 0x14, 0xbf, 0x27, 0x89, 0x63, 0x84,
0xcb, 0x1c, 0x9c, 0x3d, 0x80, 0x4a, 0xf6, 0xed, 0xe6, 0xb5, 0x9f, 0xc5, 0x2c, 0x0e, 0x3d, 0xfa };
diff --git a/src/qpi/impl/qpi_system_impl.h b/src/qpi/impl/qpi_system_impl.h
index 383dacad..ecf85e39 100644
--- a/src/qpi/impl/qpi_system_impl.h
+++ b/src/qpi/impl/qpi_system_impl.h
@@ -12,3 +12,8 @@ unsigned int QPI::QpiContextFunctionCall::tick() const
{
return system.tick;
}
+
+unsigned int QPI::QpiContextFunctionCall::initialTick() const
+{
+ return system.initialTick;
+}
diff --git a/src/qpi/qpi_context.h b/src/qpi/qpi_context.h
index f8a2a56f..005203ff 100644
--- a/src/qpi/qpi_context.h
+++ b/src/qpi/qpi_context.h
@@ -194,6 +194,9 @@ namespace QPI
inline uint32 tick(
) const; // [0..999'999'999]
+ inline uint32 initialTick(
+ ) const;
+
inline uint8 year(
) const; // [0..99] (0 = 2000, 1 = 2001, ..., 99 = 2099)
diff --git a/src/qubic.cpp b/src/qubic.cpp
index ebbf1698..e054837e 100644
--- a/src/qubic.cpp
+++ b/src/qubic.cpp
@@ -713,27 +713,45 @@ static bool getAntAnchorDigestForRebuild(unsigned int tick, m256i& out)
return recomputeAntAnchorDigest(tick, out);
}
+namespace AntWalker
+{
+inline void preemptClaim(unsigned int index);
+}
+
+// A background walker job holds its claim for a whole walk, so a rebuild the tick needs would queue
+// behind it. Asking repeatedly is free: the walker only hands back a job old enough to be stuck.
+static constexpr unsigned int ANT_ANN_CLAIM_PREEMPT_POLLS = 250;
+
+// Waiting costs what the walk costs; walking it here as well would pay that twice.
+static AntColonyBpp9000T::AnnClaim waitForAnnClaim(unsigned int idx)
+{
+ for (unsigned int polls = 1; gAntColony.isAnnClaimHeld(idx); polls++)
+ {
+ sleepMilliseconds(20);
+ if (polls % ANT_ANN_CLAIM_PREEMPT_POLLS == 0)
+ {
+ AntWalker::preemptClaim(idx);
+ }
+ }
+ return gAntColony.tryClaimAnn(idx);
+}
+
// Rebuilds one record's network. Its parent must already have one, or be the root.
static bool materialiseOneAntRecord(unsigned long long processorNumber, unsigned int idx)
{
- const AntColonyBpp9000T::AnnClaim claim = gAntColony.tryClaimAnn(idx);
+ AntColonyBpp9000T::AnnClaim claim = gAntColony.tryClaimAnn(idx);
+ while (claim == AntColonyBpp9000T::AnnClaimBusy)
+ {
+ claim = waitForAnnClaim(idx);
+ }
if (claim == AntColonyBpp9000T::AnnClaimReady)
{
return true;
}
- if (claim == AntColonyBpp9000T::AnnClaimInvalid)
+ if (claim != AntColonyBpp9000T::AnnClaimOwned)
{
return false;
}
- if (claim == AntColonyBpp9000T::AnnClaimBusy)
- {
- // Waiting costs what the walk costs; walking it here as well would pay that twice.
- while (gAntColony.isAnnClaimHeld(idx))
- {
- sleepMilliseconds(20);
- }
- return gAntColony.isAnnMaterialised(idx);
- }
// Owned from here on, so every exit below either publishes or releases the claim.
const unsigned long long rebuildStart = __rdtsc();
@@ -766,9 +784,15 @@ static bool materialiseOneAntRecord(unsigned long long processorNumber, unsigned
return false;
}
+ // The cache every other scoring path consults, so a rebuild never re-walks a score we hold.
AntColonyBpp9000T::Ann& childAnn = gAntRebuildChildScratch[processorNumber];
- const unsigned int rebuiltScore = score->computeAntChildScore(processorNumber, parentAnn,
- rec->pubkey, rec->nonce, anchorDigest, childAnn);
+ const AntColonyBpp9000T::ReplayKey replayKey = makeAntReplayKey(rec->pubkey, rec->nonce, rec->parentRef, anchorDigest);
+ unsigned int rebuiltScore;
+ if (!gAntColony.tryGetReplayScore(replayKey, rebuiltScore, childAnn))
+ {
+ rebuiltScore = score->computeAntChildScore(processorNumber, parentAnn, rec->pubkey, rec->nonce, anchorDigest, childAnn);
+ gAntColony.putReplayScore(replayKey, rebuiltScore, childAnn);
+ }
// This walk is the computation the record was admitted without. A disagreement means the acceptance
// was wrong, so publish nothing: a network built for a score no other node holds is worse than none.
@@ -859,6 +883,10 @@ static bool ensureAntRecordAnn(unsigned long long processorNumber, unsigned int
return (rec != nullptr) && gAntColony.annOfNonRoot(*rec, out);
}
+#include "extensions/ant_colony_maintenance.h"
+#include "extensions/ant_walker_worker.h"
+#include "extensions/ant_walker_client.h"
+
// A pool miner's solution, arriving over BroadcastMessage
static void queueAntSolution(unsigned long long processorNumber, const m256i& computorPublicKey,
const AntSolutionBroadcastPayload& payload)
@@ -968,8 +996,11 @@ static void antColonyBeginEpoch()
#ifndef NDEBUG
gAntDebugPrintBudget = ANT_DEBUG_PRINTS_PER_EPOCH;
#endif
+ AntWalker::quiesceBegin();
gAntPendingSolutions.reset();
gAntColony.beginEpoch(score->currentRandomSeed, system.initialTick);
+ AntWalker::onEpochBegin();
+ AntWalker::quiesceEnd();
gAntColony.setErrorThreshold((unsigned int)getSolutionThreshold(score_engine::AlgoType::Bpp9000));
// Every identity's root derives from this one value, so a node that seeded differently builds a
@@ -4021,7 +4052,7 @@ static void processTickTransactionAntColonySolution(
}
else
{
- // A null parent record means root, the scorer derives the submitter's own root, since roots
+ // A null parent record means root, the scorer derives the shared epoch root, since roots
// are never stored and so cannot be handed in.
const AntColonyBpp9000T::Ann* parentAnn = nullptr;
if (parentRec != nullptr)
@@ -8799,7 +8830,9 @@ static bool loadBpp9000Task()
const unsigned long long headerBytes = sizeof(score_task_file::TaskFileHeader);
const unsigned long long totalBytes = headerBytes + topoBytes + dataBytes;
- if (totalBytes != BPP9000_TASK_SIZE
+ // The canonical task may carry more samples than the configured sequence length consumes, so the
+ // embedded blob only has to cover totalBytes; anything past that is never read.
+ if (totalBytes > BPP9000_TASK_SIZE
|| save(SCORE_BPP9000_TASK_FILE_NAME, BPP9000_TASK_SIZE, BPP9000_TASK_BYTES, NULL)
!= (long long)BPP9000_TASK_SIZE)
{
@@ -9483,6 +9516,7 @@ static void deinitialize()
fastTxWindow.deinit();
gAntPendingSolutions.deinit();
+ AntWalker::stop();
gAntColony.deinit();
if (score)
@@ -10613,6 +10647,13 @@ static void tickForkChildPromote(unsigned int strictUntilTick)
const bool shadowClean = gShadow.purgeOrphans();
ts.resetSwapPinsForChildPromote();
forkCensusResetForChildPromote();
+ const unsigned int releasedAntClaims = AntColonyMaintenance::releaseInheritedClaims(gAntColony);
+ AntWalker::restartAfterPromote();
+ if (releasedAntClaims)
+ {
+ fprintf(stderr, "[FORK] CHILD: released %u inherited ant network claims\n", releasedAntClaims);
+ fflush(stderr);
+ }
if (!shadowClean)
{
forceVerifySolutions = true;
@@ -10804,8 +10845,8 @@ static void bspForkPoint()
{
const char* offendingLock = forkCensusOffender();
ForkStats::onForkSkipped(ForkStats::CENSUS, (unsigned)system.tick, offendingLock ? offendingLock : "?");
- fprintf(stderr, "[FORK] census: non-BSP thread holds '%s' -> skip fork, run tick %u strict\n", offendingLock ? offendingLock : "?",
- (unsigned)system.tick);
+ fprintf(stderr, "[FORK] census: non-BSP thread holds '%s' (%p) -> skip fork, run tick %u strict\n", offendingLock ? offendingLock : "?",
+ (const void*)forkCensusOffenderAddress(), (unsigned)system.tick);
fflush(stderr);
quiescence.release();
tickFork::gChildPid = -1;
@@ -10834,6 +10875,7 @@ static void bspForkPoint()
{
// CHILD BSP: block until parent's verdict, then become the node.
quiescence.abandonInChild();
+ AntWalker::closeInheritedSocket();
close(tickFork::gPipe[1]);
const auto childCommand = tickForkControl::readChildCommand(tickFork::gPipe[0], (unsigned)system.tick + tickFork::gForkWindowK);
if (childCommand.action == tickForkControl::ChildAction::Retire)
@@ -11754,6 +11796,8 @@ void processArgs(int argc, const char* argv[]) {
("fbas-warmup", "TEST: publish this many valid ant solutions before switching to the --fbas mode", cxxopts::value()->default_value("0"))
("fbas-gap", "TEST: minimum ticks between ant publishes; a gap wider than the fork window makes each window retire", cxxopts::value()->default_value("0"))
("ant-debug", "Trace ant-colony accepts, over-accepts and network rebuilds (budgeted per epoch)", cxxopts::value())
+ ("ant-walker-threads", "Ant network walks handed to the walker sidecar (0=off)", cxxopts::value()->default_value("0"))
+ ("ant-walker-debug", "Trace every ant walker job and result", cxxopts::value())
#if defined(__linux__) && !defined(LITE_WASM_SC)
("verify-fork-rollback", "TEST: assert fork re-run reproduces quorum digest", cxxopts::value())
("fork-force-fork", "TEST: fork every tick (exercise MATCH path)", cxxopts::value())
@@ -11975,6 +12019,18 @@ void processArgs(int argc, const char* argv[]) {
+ std::to_string(ANT_DEBUG_PRINTS_PER_EPOCH) + " lines per epoch");
}
+ {
+ const unsigned int antWalkerThreads = result["ant-walker-threads"].as();
+ char antWalkerSocket[128];
+ snprintf(antWalkerSocket, sizeof(antWalkerSocket), "/tmp/qubic-antwalk-%d.sock", httpPort);
+ AntWalker::configure(antWalkerSocket, antWalkerThreads, result.count("ant-walker-debug") > 0);
+ if (antWalkerThreads > 0)
+ {
+ logColorToScreen("INFO", "Ant walker sidecar enabled, " + std::to_string(antWalkerThreads)
+ + " threads, socket " + antWalkerSocket);
+ }
+ }
+
if (result.count("rebuild-tx-hashmap"))
{
rebuildTxHashmap = true;
@@ -12379,6 +12435,11 @@ int main(int argc, const char* argv[])
return tickStorageScan::scan();
}
#endif
+ // The walker, not a node. _exit so static destructors never run against uninitialised globals.
+ if (AntWalkerWorker::requested(argc, argv))
+ {
+ _exit(AntWalkerWorker::run(argc, argv));
+ }
#if defined(__linux__) && !defined(NO_RPC) && !defined(LITE_WASM_SC)
int rpcProxyExitCode = 0;
if (runRpcProxyIfRequested(argc, argv, rpcProxyExitCode))
@@ -12395,6 +12456,7 @@ int main(int argc, const char* argv[])
#if defined(__linux__) && !defined(NO_RPC) && !defined(LITE_WASM_SC)
startRpcServices();
#endif
+ AntWalker::start();
#if defined(LITE_WASM_SC) && !defined(NO_RPC)
// Wasm testnet serves HTTP in-process; the unix-socket/sidecar stack is compiled out.
QubicHttpServer::start(httpPort);
diff --git a/src/score.h b/src/score.h
index 480213e4..c2835447 100644
--- a/src/score.h
+++ b/src/score.h
@@ -208,7 +208,8 @@ struct ScoreFunction
// Ant colony main score function
// score a child by inheriting its parent's network and walking it with the child's own seeds.
- // parentAnn == nullptr means the parent is the submitter's root, which is derived here from the pubkey
+ // parentAnn == nullptr means the parent is the epoch root, which is derived here from the
+ // epoch-start spectrum digest (currentRandomSeed) and is identical for every identity
// Returns INVALID_SCORE_VALUE for a non-canonical nonce, in which case outChildAnn is not written
// bestANN would still hold the previous call's network, and committing that would put one node's
// stale bytes into childAnnHash.
@@ -222,25 +223,25 @@ struct ScoreFunction
{
const int solutionBufIdx = (int)(processor_Number % solutionBufferCount);
LockGuard guard(solutionEngineLock[solutionBufIdx]);
- score_engine::ScoreBpp9000T& engine = _computeBuffer[solutionBufIdx]._bpp9000Score;
+ score_engine::ScoreEngineT& engine = _computeBuffer[solutionBufIdx];
// Derived into this slot's scratch rather than the engine's own buffer: deriveRootANN() uses
// currentANN as working space
const score_engine::ScoreBpp9000T::ANN* parent = parentAnn;
- // Depth 1, the start node of every public key
+ // Depth 1, the shared epoch root every identity starts from
if (parent == nullptr)
{
- engine.deriveRootANN(publicKey.m256i_u8, poolVec, _antRootScratch[solutionBufIdx]);
+ engine.deriveAntRootANN(currentRandomSeed.m256i_u8, poolVec, _antRootScratch[solutionBufIdx]);
parent = &_antRootScratch[solutionBufIdx];
}
- const unsigned int childScore = engine.computeScoreFromParent(
+ const unsigned int childScore = engine.computeAntScoreFromParent(
*parent, publicKey.m256i_u8, nonce.m256i_u8, anchorDigest.m256i_u8, poolVec);
if (childScore == score_engine::INVALID_SCORE_VALUE)
{
return childScore;
}
- engine.getBestANN(outChildAnn);
+ engine.getAntBestANN(outChildAnn);
return childScore;
}
// main score function
@@ -248,17 +249,9 @@ struct ScoreFunction
{
PROFILE_SCOPE();
- switch (score_engine::getAlgoType(nonce.m256i_u8))
+ if (!score_engine::ScoreEngineT::isCanonicalStandaloneNonce(nonce.m256i_u8))
{
- case score_engine::AlgoType::Bpp9000:
- if (!score_engine::isCanonicalBpp9000Nonce(nonce.m256i_u8))
- {
- return score_engine::INVALID_SCORE_VALUE;
- }
- break;
- default:
- // Unsupported algo
- return score_engine::INVALID_SCORE_VALUE;
+ return score_engine::INVALID_SCORE_VALUE;
}
if (isZero(miningSeed) || miningSeed != currentRandomSeed)
diff --git a/test/ant_colony.cpp b/test/ant_colony.cpp
index 6a93959e..0db4fc19 100644
--- a/test/ant_colony.cpp
+++ b/test/ant_colony.cpp
@@ -6,6 +6,7 @@
// The bound colony, not the bare template: these tests check bpp9000's binding as well as the rules.
#include "../src/mining/ant_colony/ant_colony_bpp9000.h"
+#include "../src/extensions/ant_colony_maintenance.h"
#include
@@ -1146,3 +1147,102 @@ TEST(TestAntColonyExport, BeginEpochClearsIt)
EXPECT_EQ(header.entryCount, 0u);
EXPECT_EQ(header.solutionCount, 0u);
}
+
+// A child of an existing node committed on trust: no network, the way an AUX node stores one.
+static long long commitChildWithoutAnn(AntColonyBpp9000T* colony, const m256i& owner, const SolutionRef& parentRef, unsigned int score,
+ unsigned int txIdx, unsigned long long nonceSeed, unsigned int tick = 100000)
+{
+ const AntSolutionRecord* parentRec = nullptr;
+ if (colony->tryGetParent(parentRef, &parentRec) != ValidityResult::Valid)
+ {
+ return ANT_INVALID_INDEX;
+ }
+
+ AntCommitInput in;
+ in.pubkey = owner;
+ in.nonce = makeKey(nonceSeed);
+ in.parentRef = parentRef;
+ in.selfRef.tick = tick;
+ in.selfRef.solutionIndexInTick = txIdx;
+ in.anchorTick = tick;
+ in.publishTick = tick;
+
+ const long long landsAt = (long long)colony->solutionCount();
+ if (colony->commit(in, parentRec, score, nullptr, 0, true) != ValidityResult::Valid)
+ {
+ return ANT_INVALID_INDEX;
+ }
+ return landsAt;
+}
+
+// A claim held at fork time has no owner in the child, and the on-demand waiter would spin on it.
+TEST(TestAntColonyMaintenance, PromoteReleasesAnInheritedClaim)
+{
+ AntColonyBpp9000T* colony = freshColony();
+ ASSERT_NE(colony, nullptr) << "colony init failed; needs ~6.2 GB";
+
+ const m256i me = makeKey(41);
+ const long long idx = commitRootChildWithoutAnn(colony, me, 3800, 0, 901);
+ ASSERT_NE(idx, ANT_INVALID_INDEX);
+ const unsigned int slot = (unsigned int)idx;
+
+ ASSERT_EQ(colony->tryClaimAnn(slot), AntColonyBpp9000T::AnnClaimOwned);
+ ASSERT_TRUE(colony->isAnnClaimHeld(slot));
+
+ EXPECT_EQ(AntColonyMaintenance::releaseInheritedClaims(*colony), 1u);
+ EXPECT_FALSE(colony->isAnnClaimHeld(slot));
+ // Retryable, not merely unclaimed: a Busy here is the hang the sweep exists to prevent.
+ EXPECT_EQ(colony->tryClaimAnn(slot), AntColonyBpp9000T::AnnClaimOwned);
+ colony->releaseAnnClaim(slot);
+
+ EXPECT_EQ(AntColonyMaintenance::releaseInheritedClaims(*colony), 0u);
+}
+
+// A rebuild starts from the parent's network, so a record whose parent has none cannot be taken yet.
+TEST(TestAntColonyMaintenance, RebuildableOnlyOnceTheParentHasItsNetwork)
+{
+ AntColonyBpp9000T* colony = freshColony();
+ ASSERT_NE(colony, nullptr) << "colony init failed; needs ~6.2 GB";
+
+ const m256i me = makeKey(42);
+ const long long parentIdx = commitRootChildWithoutAnn(colony, me, 3900, 0, 902);
+ ASSERT_NE(parentIdx, ANT_INVALID_INDEX);
+ SolutionRef parentRef;
+ parentRef.tick = 100000;
+ parentRef.solutionIndexInTick = 0;
+ const long long childIdx = commitChildWithoutAnn(colony, me, parentRef, 3800, 1, 903);
+ ASSERT_NE(childIdx, ANT_INVALID_INDEX);
+
+ // The root is closed-form, so the parent is takeable immediately and the child is not.
+ EXPECT_TRUE(AntColonyMaintenance::isRebuildableNow(*colony, (unsigned int)parentIdx));
+ EXPECT_FALSE(AntColonyMaintenance::isRebuildableNow(*colony, (unsigned int)childIdx));
+
+ AntColonyBpp9000T::Ann ann;
+ setMem(&ann, sizeof(ann), 0);
+ unsigned int annHash;
+ KangarooTwelve(&ann, sizeof(ann), &annHash, sizeof(annHash));
+ ASSERT_EQ(colony->tryClaimAnn((unsigned int)parentIdx), AntColonyBpp9000T::AnnClaimOwned);
+ colony->publishAnn((unsigned int)parentIdx, ann, annHash);
+
+ // Materialised records are done, and the level below is now unblocked.
+ EXPECT_FALSE(AntColonyMaintenance::isRebuildableNow(*colony, (unsigned int)parentIdx));
+ EXPECT_TRUE(AntColonyMaintenance::isRebuildableNow(*colony, (unsigned int)childIdx));
+}
+
+// Two rebuilders must not walk the same record: the claim is what keeps the second one moving on.
+TEST(TestAntColonyMaintenance, AClaimedRecordIsNotOfferedAgain)
+{
+ AntColonyBpp9000T* colony = freshColony();
+ ASSERT_NE(colony, nullptr) << "colony init failed; needs ~6.2 GB";
+
+ const m256i me = makeKey(43);
+ const long long idx = commitRootChildWithoutAnn(colony, me, 3800, 0, 904);
+ ASSERT_NE(idx, ANT_INVALID_INDEX);
+
+ EXPECT_TRUE(AntColonyMaintenance::isRebuildableNow(*colony, (unsigned int)idx));
+ ASSERT_EQ(colony->tryClaimAnn((unsigned int)idx), AntColonyBpp9000T::AnnClaimOwned);
+ EXPECT_FALSE(AntColonyMaintenance::isRebuildableNow(*colony, (unsigned int)idx));
+
+ colony->releaseAnnClaim((unsigned int)idx);
+ EXPECT_TRUE(AntColonyMaintenance::isRebuildableNow(*colony, (unsigned int)idx));
+}
diff --git a/test/contract_qip.cpp b/test/contract_qip.cpp
index 2ad7905b..02ce5b73 100644
--- a/test/contract_qip.cpp
+++ b/test/contract_qip.cpp
@@ -1141,7 +1141,6 @@ TEST(ContractQIP, returnFunds)
QIP.beginEpoch();
QIP::buyToken_output buyOutput2 = QIP.buyToken(buyer2, 0, buyAmount2, requiredReward2);
EXPECT_EQ(buyOutput2.returnCode, QIPLogInfo::QIP_success);
- //QIP.endEpoch();
EXPECT_EQ(numberOfPossessedShares(assetName, issuer, buyer, buyer, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX), 0);
EXPECT_EQ(numberOfPossessedShares(assetName, issuer, buyer2, buyer2, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX), 0);
@@ -1149,6 +1148,9 @@ TEST(ContractQIP, returnFunds)
EXPECT_EQ(getBalance(QIP_CONTRACT_ID), 24171804);
EXPECT_EQ(getBalance(QIP_testAddress2), 0);
+ QIP::returnFunds_output returnFundsOutput = QIP.returnFunds(buyer, 0);
+ EXPECT_EQ(returnFundsOutput.returnCode, QIPLogInfo::QIP_returnFundsError);
+
QIP.endEpoch(); // 1
EXPECT_EQ(numberOfPossessedShares(assetName, issuer, buyer, buyer, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX), 792); // buyAmount / vestingPeriod
@@ -1177,7 +1179,7 @@ TEST(ContractQIP, returnFunds)
++system.epoch; // 4
QIP.beginEpoch();
EXPECT_EQ(getBalance(buyer), 0);
- QIP::returnFunds_output returnFundsOutput = QIP.returnFunds(buyer, 0);
+ returnFundsOutput = QIP.returnFunds(buyer, 0);
EXPECT_EQ(returnFundsOutput.returnCode, QIPLogInfo::QIP_success);
EXPECT_EQ(getBalance(buyer), 4059429);
QIP.endEpoch();
diff --git a/test/data/bpp9000.task b/test/data/bpp9000.task
index 8ffa5e17..38ce2351 100644
Binary files a/test/data/bpp9000.task and b/test/data/bpp9000.task differ
diff --git a/test/data/gt_ant_production.csv b/test/data/gt_ant_production.csv
index 664c7574..48aa11ed 100644
--- a/test/data/gt_ant_production.csv
+++ b/test/data/gt_ant_production.csv
@@ -1,65 +1,65 @@
chain, depth, pubkey, nonce, anchor, seed, score
-2, 0, 886ef7f14d42287f8a74aaa92a409eaf901e25b6f9cfbedb5f72e1297bcad551, 01084ed07b9200fe2bb401df72b52ba819fd5c09e436f5f185594f8b8afcbc66, 54ba8fded70f55d660977d169d6a2ab6d7711b11f2b49596fbab1d5bf968a961, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4576
-5, 0, 2df29076f8969739636dff083eb1f54bfe7e5d8fcac5b2946d0871d40ba495aa, 0106216c6da947657f74a629a59d5b81da7a4a6ecce8a3abb4b23079b5f1c56e, 21c6ec71261f8027ee7fe4629982e12c50facf8d0acc1f8f297264db6bc2dc2c, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 6400
-11, 0, 83b379182516db53587b3c553468e1af0ca4c21f13b95150024e68ebb2bb9550, 01085ebfe74c8324c42214dde9c3c71f691e327cd8a764e4c396d7431deaae03, 12545454084db67b036475332b9a7ebd7c7ed73b13e64bc17d87ec2e758dcb22, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 5308
-1, 0, 17620b9920ef596dfa46753b355247b1641eaeda1246fa7bc43884630cf61bf2, 010523bbe90fa21b263946830cc53e8744e270cf6e944b1cf255e85ac74a7887, eae546338da0b54a3b4e10f859b6293f1d285a0e51e4881f10d1379ff511f819, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 6295
-0, 0, 4657de22da43ad6402c8cfdd45ba54542213486481988bdd28c82182ec2714af, 01023378b9bf89fae25fdcd165c8739da9dbba04ff92982345a623031d23b0fb, 91a1d4fc06689515127b01f116fd64c4c2ca4c02a4692675c1b0dd4fc1bf3589, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4800
-8, 0, e0b3b0f9b66fe475eaf17c6be38a2759ff254debd5485c4d4a27bc45486fa0ee, 010733ccce2e806a4aea54c0dcc386ebfd4efc6f3027f63c1de301686508f403, a2050b44a20027fb98984b156632263a8bccefd34e4c1a06d516ac807ea19433, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4414
-6, 0, dc40c51265eff259556066d427dbeebb4109a7e79690d61d6349ef650b29dcb9, 010a33855fd20d0f1c910f0f7f7bc4eef7dfde506c7184d12bb6e50dc095b9de, 0509c75356fc65087c969b8eb8602a28c903a34d8f0648394e29dbeaa9cb4892, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4206
-10, 0, eda9ead70a463d6bf5858faf2a8caa3c3d20b3c3e0e994167e685200bc750276, 01080bf6551a06f4826c9dfb047814381042f281b322ab9452623183daab9dd3, a0651819278fad70d8d2e24db8fa595f305cb879b400fb0c270ecad1361cc25c, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4352
-4, 0, e5e05648b1c70c4deea4c7d7aefccd49b24defadceb45dd033caf0f16407808b, 0105337415ffe5aae3f1e4816712ba7ca6e4df3db6b10276868d4e2861cb69e8, a18598ffcfd37339a922658ea450e94f2e4cd083c4e64d5013d002c4fcb17743, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4379
-6, 1, dc40c51265eff259556066d427dbeebb4109a7e79690d61d6349ef650b29dcb9, 010a54a6afb54d321417a986b3e8ce05e51823ea0aceef2681392fcda42010b0, bb849545500e6716bdbbf6fbbdcf6bfe1fd2b4cc20c1024cb419be77dae27df0, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4206
-5, 1, 2df29076f8969739636dff083eb1f54bfe7e5d8fcac5b2946d0871d40ba495aa, 010354eaf1f371ea113434301d5e15b04d24b92fc11dd20f5e03fb6e6f497d30, e9bf8e92a20da221725f0897cf111f326c7754ead360f7aba074459ad99ec7f9, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 5998
-3, 0, 072fef44ac2f7667c366f067b374016bdd39581dbe8acb0f8aadf98502d13646, 010329371769a20be8033c7868d60327ad8b345b190f69f6a6b54f55c7acde8c, a3e0ad1361d551b2ce13025624602a4ebbcb0bc37c93348a88d23d394c155bd9, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4569
-2, 1, 886ef7f14d42287f8a74aaa92a409eaf901e25b6f9cfbedb5f72e1297bcad551, 01081abdbc10a2b70d301fe0bb0afa298f90a5d9bdd0777d24949228abc63106, 02724836ecf8a9bd54e8f78867ea5b4e5c13dc6c41566094a6b41fa36e219f09, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4576
-8, 1, e0b3b0f9b66fe475eaf17c6be38a2759ff254debd5485c4d4a27bc45486fa0ee, 010229cda723c097587f19e30ee090700b7da9bfc9a24e2fb73d83d55b4ba107, c55bea5399c1ca80efe9b9b6c51991ce99b6cc34f804984957cc7a110a1e03cd, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4373
-0, 1, 4657de22da43ad6402c8cfdd45ba54542213486481988bdd28c82182ec2714af, 010615c66cb4c65a74382fce9000de43248a4425ced3bc79929139502e97c705, 4a564c2984b914fda7e68a1c80fd1bfdf6a75ac2443f348e98645d42fbaa052a, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4450
-10, 1, eda9ead70a463d6bf5858faf2a8caa3c3d20b3c3e0e994167e685200bc750276, 010a5e540d6e35170556e51d0a25400b8e7dce3a21872d56e397633411fc7656, eb23ade51e8662688b5e069f59e64c9049c7896058ee4460bab704180f8be6e6, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4352
-1, 1, 17620b9920ef596dfa46753b355247b1641eaeda1246fa7bc43884630cf61bf2, 01050a3c2127a028962e38ab80d9eb6848790106a3809637f8b1eb849c261a1b, 8dc4c241e049f0be5341d03b0d1023ae5340cd8cc506dd6f03a998743e2fed75, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4663
-2, 2, 886ef7f14d42287f8a74aaa92a409eaf901e25b6f9cfbedb5f72e1297bcad551, 010246c5bb2fa79a2bc5f2ed1fbb2877ba2de90011ee439383347c684cfb80a6, ef9bab5a83f63baadbd79d9e3680a36eeb65d8c9c1c52c6dce0e47f807ccb465, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4394
-11, 1, 83b379182516db53587b3c553468e1af0ca4c21f13b95150024e68ebb2bb9550, 01025ded1ea31c0763dd89595fce37c703c886203ef104ccea9979bf6041b511, 967e0547550da817764ba0a7b51e33556c2435e9a910e4b16d9300bd785a06e1, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 5308
-4, 1, e5e05648b1c70c4deea4c7d7aefccd49b24defadceb45dd033caf0f16407808b, 0106282d2480193df99350d4ef0c3b31b9a4314b38cc3d0e86c3c9564b9fc77b, 9ba91514fec4c80a40201e7fbc9bf7d08ee446b8f3efaff16e32ae54c5afdc92, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4310
-6, 2, dc40c51265eff259556066d427dbeebb4109a7e79690d61d6349ef650b29dcb9, 01052e993e876de4beeed6b7e5f674acf08d15f44a41dc59e080b50b5e9ee9ee, a570a04a90af0b23bf9ea5b4be54314a853f517a060c39df1d983e9665f4c45b, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4206
-3, 1, 072fef44ac2f7667c366f067b374016bdd39581dbe8acb0f8aadf98502d13646, 0109595f50ab0a4d9c0c2209dc2e020058a6a3fcaa4ab687f5338d46a4259031, b8a420a950c50a97f8876a589cc1a4f2822eed80efc2e7ef6bd44b2c780061c6, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4569
-5, 2, 2df29076f8969739636dff083eb1f54bfe7e5d8fcac5b2946d0871d40ba495aa, 01030cc6084cbef2f92c8fc481db9b3b36ec91723770bb5c6aaea1dfb2104376, b3310ce38053f82c73d26eed58ad00897a8d0de995d60c4eeb8d4755afc518c9, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4836
-10, 2, eda9ead70a463d6bf5858faf2a8caa3c3d20b3c3e0e994167e685200bc750276, 01040809aeb747d430f914d0ceb1970d3ff7e50b44ac1db1de8ac6a910795423, 5df3e71634f680908f5954ce538b65f470d65779dfcae051f1d42b43f1527abe, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4241
-10, 3, eda9ead70a463d6bf5858faf2a8caa3c3d20b3c3e0e994167e685200bc750276, 01075358c48d3c17682f8c69578d2db1710168ebad922b591b764b0937353be2, 190832364108015065a051e38badc3535be4faff42a319132ebfecea4bf03416, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4241
-8, 2, e0b3b0f9b66fe475eaf17c6be38a2759ff254debd5485c4d4a27bc45486fa0ee, 010307ae588e0d66befcfb11099713189200bfeab97cb37d4f18bfa0d877cd2a, 2032dcba5a6c53d1cb68ad800ae8c7b455aef9aa90e1c14cb5591bbe2c8e1927, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4307
-6, 3, dc40c51265eff259556066d427dbeebb4109a7e79690d61d6349ef650b29dcb9, 01014f1dc6bcc0311403fe50177fe2b69d702b1109db30aab04be2b6cd964375, 7f2fbd2dca697fee9169049bd06a5cadb6932db37cc8190b7f781e2859a13ddf, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4206
-0, 2, 4657de22da43ad6402c8cfdd45ba54542213486481988bdd28c82182ec2714af, 01011280b730b2317044f434b8e56ca881f7efeaf8dd4b29b12e877ea43fb7a1, d260aab327663b1197f602584a74c80dfdc3915d487440e21d59b6d795ba284c, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4450
-2, 3, 886ef7f14d42287f8a74aaa92a409eaf901e25b6f9cfbedb5f72e1297bcad551, 0107159b5267736895605a21efe7bfa20f676707dd2bfdccabc6d67ed211d511, 32fa4cc87d7f7ba35dfcc9fad0256dd64bd1c3a340e1fb1ce92a2e3102641be8, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4291
-11, 2, 83b379182516db53587b3c553468e1af0ca4c21f13b95150024e68ebb2bb9550, 01040f59f2e3d85b897c1997ae4cd6a1aa20f0397795106ec35742efa2f60207, 70efb4b8a77579ec350179a71f89e517c378ec87896a526d5bca985e45e6a2fc, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4581
-1, 2, 17620b9920ef596dfa46753b355247b1641eaeda1246fa7bc43884630cf61bf2, 0103333ea8e0bc20c0f2cfb7cc93e9d3ae302f4b5e933e605ca8d5f2b101f7c8, 39cb97cbe391fff147ecffe6bc7b6cfcf920726974c9ad5394b54e7a86d3e752, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4663
-14, 0, 8c2cce72fa295e618dada27cd9dce2a8f24a4596d676edf11e096bcf6f5c078f, 0103553c86f3dcc0e0a8c63a9004bc5719b09b04348d77a9de594f5b8bdffaae, db7e8851e5e2b45c072bf7cd15040a9bc313b6d4e8e568f7a50a62810a181842, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 5808
-5, 3, 2df29076f8969739636dff083eb1f54bfe7e5d8fcac5b2946d0871d40ba495aa, 01022aa2e9c6bf0d46e240165a772be18804f052b8f76111e0c22af538b332e6, ed0147d60ab884ab994eca1b25f319349437e4106b21068d0f8ae9c357102e14, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4779
-4, 2, e5e05648b1c70c4deea4c7d7aefccd49b24defadceb45dd033caf0f16407808b, 01080ccfc0cce11c4229f4dd023630330c17f3f495c3db73a30ed50107882a5e, eb7729568d616b19004a42dab7f8b237defd6eb3a0a5448037ecf4ec56044bd7, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4137
-3, 2, 072fef44ac2f7667c366f067b374016bdd39581dbe8acb0f8aadf98502d13646, 01013064dacd09cf7f1898ac0b669d25f4c0db3c2bcea9b3c9cd51f3fc8a7414, 5a107488a0caf086ae94acfefca9b36f6ae84691b11a001ef3999a673d7b160b, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4394
-0, 3, 4657de22da43ad6402c8cfdd45ba54542213486481988bdd28c82182ec2714af, 0109151f9d11847474e333460ea73646a31663c84162313db3cea328a74efa82, c10184404849ef460dd734d49d22cad1b25aafa77bbd3f22073dc88885f644ec, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4450
-4, 3, e5e05648b1c70c4deea4c7d7aefccd49b24defadceb45dd033caf0f16407808b, 01044b182869be4ba5670092661abac68606a2f4b06130d101882d236e482358, b7fc5ca1cb869e33b3725a1f3a4c96e8545200d0f263d099351016b6baf46039, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4137
-14, 1, 8c2cce72fa295e618dada27cd9dce2a8f24a4596d676edf11e096bcf6f5c078f, 01076440117d3ff4d99c831ad310105cb1217c9bfe4bbd7a8d98df63fde9dac1, c01ee3f86edc2dbb38b5fcabf8dca2e5f63e80cfc7161b897783b71fb24eb07b, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 5808
-8, 3, e0b3b0f9b66fe475eaf17c6be38a2759ff254debd5485c4d4a27bc45486fa0ee, 01092583572dbd57774dc64402af3254c4e9cfc97a76b686d690e728f4946882, c1003cacbb633a682fa1587cc0a59535501e3d8511b896df7431c320da790724, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4294
-11, 3, 83b379182516db53587b3c553468e1af0ca4c21f13b95150024e68ebb2bb9550, 010522c21f13bb19c26a46e5798d2ce597bfd035261c4a3e9e2d38963910dba2, e692e800bb6e620f5108e70950e6f65c46997a3dd8aef402a30d44870ba94440, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4344
-12, 0, 2814b082c6683e9490338df3e42c8d89ca9e2f889735013dcbb7ddbdf7d846c4, 010618c41d7d80fdeb077f54165537de721e39ee32e5f8d11dd16e5edff70d91, 9c65303cf21df1f289cd5d7e1d431ddd79016d87c6e8ec57b2c23c15c69c119f, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4862
-1, 3, 17620b9920ef596dfa46753b355247b1641eaeda1246fa7bc43884630cf61bf2, 0108598ac27cff46dd5635d43a3ba7b32bf7c0f844c95418fb359f4bb8b14a48, cdb0470fe9f772124a6d158869b4bed2451b4445293ffa2cc56f96917b108ebc, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4663
-12, 1, 2814b082c6683e9490338df3e42c8d89ca9e2f889735013dcbb7ddbdf7d846c4, 010a33039aba1207b1d63d559fbe40220eaa30b56fd25e5d0767eed2538ed35c, 27ee5997236575efdb19c9845b330da3e969ab8b69af32b6cbace636afbca8cc, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4862
-13, 0, e36507d27632e2d6cc70480f6322a4469d20773ff0d047f6f70285e9d44beb5b, 010a602d4d374fe77a551445a50be97aa8ff00a8e6d1bc87eb6a05a7ca9d5bac, 5e65aac82ee69c59e00bd8bc6b688b45c6a30fc14a97c50f631f6255eaaf7327, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4916
-14, 2, 8c2cce72fa295e618dada27cd9dce2a8f24a4596d676edf11e096bcf6f5c078f, 010203df7f6c6ec32fb907ddd1bfe342d0e638248cf885e02534d40ee5b0fdc7, 7f25490bb25c2a15a4ae4b09834af66b32f6c747cd410f205ebb5f26157628b3, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 5475
-3, 3, 072fef44ac2f7667c366f067b374016bdd39581dbe8acb0f8aadf98502d13646, 01011fba8c525ade36b44b91c10f763cc452b9f359138a17b7095defba3de81f, ddbcfaeaeefae22924db28ad1ac6dbab0878009449fa125d1e072edfd29bfe29, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4337
-12, 2, 2814b082c6683e9490338df3e42c8d89ca9e2f889735013dcbb7ddbdf7d846c4, 01081ae266672ca194549f553909f744cc0043134958ecc17e1cb23b8b321b81, eabf4471ea17eeb0e6b8ef2868bdf533e538e9117d89ee7be0506b5b87100c31, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4862
-13, 1, e36507d27632e2d6cc70480f6322a4469d20773ff0d047f6f70285e9d44beb5b, 01061e74c28eab539b083cc334b3d8f49d500d3a0efaf0ddacfeac3c3d7db59c, a1f90ec26c1e922437b5dce2cb7d5aa74a73dd6b2a633e0aa1a87e181ef4e493, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4183
-12, 3, 2814b082c6683e9490338df3e42c8d89ca9e2f889735013dcbb7ddbdf7d846c4, 0107093cee10017f16cccbff4c1bf264ad41d8416654ce13f71d06d40a068d45, 4728ca3016635dbb5fed672ad264689e97f39372d87fdc8751d24d305147e50f, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4808
-15, 0, dac78edf70e526e26a94ac4982c2abded02fe049c5038196738d66d80849ca3d, 010a4622db8bdc6f2d2a53c4d093a7db2eed1511435c0c9d5f3ad99ccc89461f, 782f1d06c5a143065b84a674c8c957ec1f6c335b9b9d1818aa440358490f9d42, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 5165
-15, 1, dac78edf70e526e26a94ac4982c2abded02fe049c5038196738d66d80849ca3d, 010758cd460961f2865307025c35b5c371a152aa887aa79a5ed5db1562554b51, d949da8e7b84d066ea4e0d44064dfc4d8c58b4c4f6580a817de564a74500ce2e, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 5165
-14, 3, 8c2cce72fa295e618dada27cd9dce2a8f24a4596d676edf11e096bcf6f5c078f, 01051655e2f51a514371801ef25660a391477f0d6c492b613c5a83bb30f490f6, 66473bfc883091c7d6b1ddcb8753db715e7a9568aca4b2f870f155edf0b952b3, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4839
-15, 2, dac78edf70e526e26a94ac4982c2abded02fe049c5038196738d66d80849ca3d, 01034a2930d5d739d27f0a4b65bee1bd19ee6dccc2da5612aa3e380d60e263a3, 61cda7fa7dd9cb6b817139a51b7d17f3fef1ddb31db2a506082cd49411d0df57, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4541
-13, 2, e36507d27632e2d6cc70480f6322a4469d20773ff0d047f6f70285e9d44beb5b, 0102482e077b5bbcf0c1f5da9a77803663ade42ccf6667a2d07366ec25c05373, 7cdb704cdc5df918699d7bd399008f249f79ab8b4ca08689f8d3010c5813de38, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4183
-15, 3, dac78edf70e526e26a94ac4982c2abded02fe049c5038196738d66d80849ca3d, 0104501feb2927fd13760f5c39dc277827c125b069804c6ac18e6021b1811868, 61078520e97db6e828ce6465404fe3456dc27deb16516cfdc3c34c8293c52fd0, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4541
-13, 3, e36507d27632e2d6cc70480f6322a4469d20773ff0d047f6f70285e9d44beb5b, 01024f0896557a0084a1d9a2937130ccb2711a8cb810e512ec9e2c97c3c56db6, d4cbdaa2fb4e3c1e8ef809ed628f0abf16d3bf4a93f81ce03bf77b68e07fb516, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4183
-9, 0, a14d543d43592ce38ef38f600a2dba801557fd924638de966d2725dabc5e2472, 0102152a605e81991fe51e4c8964a98462f6a78ab03dfabfd30552d39e9d4eeb, c17c5c2242fd08302a00dddf39bfe32952a319f4816dbc540b991d8ee33180b4, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 5099
-9, 1, a14d543d43592ce38ef38f600a2dba801557fd924638de966d2725dabc5e2472, 01012c56d755fe8e1c3c18ce6c13e4fb74a62f4dbf6b4d88a4d4d5f12a82111f, aec69cdd848a1b7ff0f89c9ea97feb759270ed6719ec8a675ceba9cc0877a1de, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 5035
-9, 2, a14d543d43592ce38ef38f600a2dba801557fd924638de966d2725dabc5e2472, 010918e449d18c7ea382562025280bd33f3dea08e299eb92eaa7413d9b42c367, 1cb9b9ffb834abc7b8bbfeb01e006fd3cc412717f8b9456e5e4d663466417e99, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4281
-9, 3, a14d543d43592ce38ef38f600a2dba801557fd924638de966d2725dabc5e2472, 010a107f47f98b2357d358f56f2a9cb9759714f3bec8ae28dee41e9ce170a5db, c6d51cd991be84979a28aa4b4e51de3ef0f585aadbab7b66a0593805a5085546, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4132
-7, 0, 158b59e2f807c4b05160ad5dae50f96f079b79a7f7467e11d86c37a808515c1a, 010303ab82e286a8adbe84e8b7d72f1c627058d749eb7fee957227ae354736a9, e1b75477f812f034862401a8ecf7c9b7ffed63e904ebff99b4662bb3157d55f7, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4382
-7, 1, 158b59e2f807c4b05160ad5dae50f96f079b79a7f7467e11d86c37a808515c1a, 01061f0f60aa46bb3d3db810b004f0d63a0666f4304c7902c2d0de7116fd4c3a, a6453d96ed2963d2f8073faf29405223c3b9de7b9f0bf925ad5b5807f0df29ec, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4382
-7, 2, 158b59e2f807c4b05160ad5dae50f96f079b79a7f7467e11d86c37a808515c1a, 010a01e51b9910f599cf59ad2c563f0dedb61dbc3c0226bad673b48d1b538419, 1485b5332779be0d64bd08c69344580358b320cf01b9f38ce70badc069e94ef1, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4032
-7, 3, 158b59e2f807c4b05160ad5dae50f96f079b79a7f7467e11d86c37a808515c1a, 01050c4baf9613939235ebdaa17d71ffd8523d368247815fb47fa0fa6334e99b, 182c36a0398009d6b6e58301e13c805cd5acfc37412f55cadee180e90b27afdc, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4032
+5, 0, 39328e676b63cdd3ba4473d0431402f37b8326f6bb071bf52cd760a128f19013, 01063d6e0eeb11f4b8ee105280e1aeb4d5e280d6ec825a971b0815aa1dffb1a1, 3b61511981cbe61b4c5e26863eb5407b06562960bb9c1491039c7d45504f4a7d, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4320
+5, 1, 39328e676b63cdd3ba4473d0431402f37b8326f6bb071bf52cd760a128f19013, 01093cee5c710d8760b3fe828149c8726e2b9526d2ca5ca94e57c9e9e5074803, 5ffc51127e569a1f927fd10c6cc5e1f0eeee7540b922298b2f0bc61e37060008, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4320
+5, 2, 39328e676b63cdd3ba4473d0431402f37b8326f6bb071bf52cd760a128f19013, 010837ec9b30eaf304d315bd3bef0da838c0c9aa1f4c84521a565f79f093e54d, d40339a95e54d68ffc5574a246e7a5158af5c86ca17ff30d4da101819eeef7dc, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4320
+5, 3, 39328e676b63cdd3ba4473d0431402f37b8326f6bb071bf52cd760a128f19013, 010361d6347e3c2b08bc36b5bea2a5c93424c56575934614770fbcf0026adc91, 924b7a9f8736d7550c8cc75dac1d00a91677f67e653d9b37603f5e89873e4e2a, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4320
+10, 0, 538fc8ec58a371c654ae2369acf7ff0d8df7a4e3ff178c8d6960edc4bf7d8506, 01082aa824190e5d3c70448ad8bb4717bf35149597c81e4ef2afe3636ce69208, 71c696a563f40ba722062e49d4a44054f51a16039ce71d36b0dc4d7f2fa21616, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 5863
+10, 1, 538fc8ec58a371c654ae2369acf7ff0d8df7a4e3ff178c8d6960edc4bf7d8506, 01022fab9f5bdccd0b959ed890ece6faba183333a3cc575641024319877396a7, cb4c9af70d7b2e076091c1a36f16933dacd55fd3be92403b5384e1300e3a6bb7, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 5597
+10, 2, 538fc8ec58a371c654ae2369acf7ff0d8df7a4e3ff178c8d6960edc4bf7d8506, 010539217d7c1ec6bf99027cba42a6ad1925a721640dd971a9c8df0c0265788f, c12696a39fef53d899c413ad6241313794b50ffe873f2efcaef9515deb9a2601, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 5055
+10, 3, 538fc8ec58a371c654ae2369acf7ff0d8df7a4e3ff178c8d6960edc4bf7d8506, 01050c5d9d85cea75c933969196e0f01bbcf93cf5efd6fcb19e19b766f92b2a1, 23d37b23fbbbe110e7bf9895c93a1632a69f37b5e24b294b0a23d19632d7571d, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4103
+11, 0, 9b01190712cc2b617960429079e1e99f5fcc066f89ed4d81e83c5ae60142bf11, 0109093319f061ecb6e666823ada8b4d0b2d874602d82ee8ed3e373c6cc56217, 49029a0333b014053c3f1f821e2c51274e03ad031c3f9d762136e10e3d251358, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4110
+11, 1, 9b01190712cc2b617960429079e1e99f5fcc066f89ed4d81e83c5ae60142bf11, 01045c6f75ff26b547da8b0e3e4d81f7789035b6139f2b53dfbf9261e1505e8d, 69e2db4bbbbd538f4b5c6de70837d155f398e876d95b5cd1c6da8341f24a039a, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4110
+11, 2, 9b01190712cc2b617960429079e1e99f5fcc066f89ed4d81e83c5ae60142bf11, 0107083255da35ebdbeecb2038a8fea675742c0b5b016de71b573baa6308bcc2, 5dd0ea87019b456f5d23141b9e5c9422a0618ea102ec97b237efdee486b7cca5, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4015
+11, 3, 9b01190712cc2b617960429079e1e99f5fcc066f89ed4d81e83c5ae60142bf11, 010735110790c4eed4fdb1d41f8bd3a28667d59d7eaeebd62aa738015fb59eed, 58fd1aa50da65a2169b6cf0742ea569dc3c7a3f1de9c43e5f73e9c47a82c0a3c, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4015
+0, 0, 4d6951e72244e6d70bc3b0b6a6550ecb961c97cacd42e6aad2ff90edf065fc6b, 010643f21a4e13e83868b4ac8160fde045a61539b8d43df48f1d3c28b1b10ff3, a346ddbe9c5b2f8f432c4e734904657c1a043a45d2ebbc436409a50f37c7c990, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4024
+0, 1, 4d6951e72244e6d70bc3b0b6a6550ecb961c97cacd42e6aad2ff90edf065fc6b, 010a144bf3109ad87a0627dca4ab50ef1b7b0b6f18515eccb4dc1611dcd81c47, bdc015579ce60294299d6eac7e99a86dfada3e1b4e29e13920acd7a40e63387c, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4024
+0, 2, 4d6951e72244e6d70bc3b0b6a6550ecb961c97cacd42e6aad2ff90edf065fc6b, 01071fe11e193c2fcd0947988b4d125b5397d48c5776def9fcbd56ce6f92f49f, 68ce9b1946965e6ff15e631a3792fece82cc2fcf8deaf7561b02664de2195e51, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3990
+0, 3, 4d6951e72244e6d70bc3b0b6a6550ecb961c97cacd42e6aad2ff90edf065fc6b, 010455fb5fd6d346f2c74b612072ddd502e7a17d433ba537db2da96e00cabeaa, 03cdfd62beb8c9c4fe9805a75677ec4466cf222e73923d6deccadf0174f14edd, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3990
+2, 0, d269d3c9e83f250a5d471d5d7c3f2b8c068728600216cef27524b3d1b6c5b371, 010428f05fa25f9fe8dad343ca0a2c8f64049d8a9f7e38e033130f3e0d847b87, ced7587ab3b4f773e15289b1ff5e656642cd551d431cdc9c847ff378a88cad36, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4366
+2, 1, d269d3c9e83f250a5d471d5d7c3f2b8c068728600216cef27524b3d1b6c5b371, 0107319e04b5dc666cff589612f47418fffa5537fbe829e91a1a47110b57559c, 564c0ef36f9b208c1a954328e7f9a3f54ca423bc73db67a4c0d5f3c90547cdee, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4084
+2, 2, d269d3c9e83f250a5d471d5d7c3f2b8c068728600216cef27524b3d1b6c5b371, 01085ce85cdb2f9a6316f90cbe6b7b79bd8e2cb69f27a48f9eb48a2c454082ce, 0fc40360c900954f92a6ef47ae075ab204ae06d7ac5bef846cb99daa4727065d, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4084
+2, 3, d269d3c9e83f250a5d471d5d7c3f2b8c068728600216cef27524b3d1b6c5b371, 01013f78fa10e1bf931096a2a8df7960b9de2f6b85a8b7e1cca8bad5b4c13d94, 7f04fc765ff3dab3d2148bd9d445c72370ca3f335e2551a742a911d3983a6558, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4084
+3, 0, cf0f607131740aec1059970e2de315626ecbe9dcc9b8e7fd03a744189a969fbb, 01061c061e969aac1afe128561b711fbf2f3462d407a0db322a63dbcd31d47cc, f29c2b98d0c2008b9c1c17bb491eea892c3b36d6913823a562d53f6c8f8190c5, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4151
+3, 1, cf0f607131740aec1059970e2de315626ecbe9dcc9b8e7fd03a744189a969fbb, 010708e6d5efa482aa0eeedbb5195fa33e0cd85966b746cbf4368486523b3d9d, ffcdbf851bc811fefb8ed19e030c45b22210df3de3bbddb0a119db24655f40b2, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4025
+3, 2, cf0f607131740aec1059970e2de315626ecbe9dcc9b8e7fd03a744189a969fbb, 01040424a692fcddb6f3c89680e02136ed8354bde8e75bec98f5944eb90e0b8e, 90e40d22ec747058cadc0d3d78bf37ea55a9efc17f5a9e9fe9331633283b832e, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4003
+3, 3, cf0f607131740aec1059970e2de315626ecbe9dcc9b8e7fd03a744189a969fbb, 010716ba575d16fd951744d958c36c0c7868c413a8145d8021f02d9ac8d18ba3, e8005fe308a6ca1000a38daf4efcc6ba4115c37fbd2413a93a216e2ba67ab94c, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3950
+8, 0, eb1dd1de89c1808e995cf5ec9484ce99a44b6beef8f5696451eb45d1979a1afd, 01071074f94c7b46b48a562ef152b999f9bbd66885f30c862e67a1c058e12ebe, 3c9adc3aa8162877139de8552efd270d501866db15115e472d50d257497aa690, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4485
+8, 1, eb1dd1de89c1808e995cf5ec9484ce99a44b6beef8f5696451eb45d1979a1afd, 010a409e45c1036344945f841fecc19b5baf9b52b9e38ab1a53095749554dbe7, fa885262e5ac6af13bbde9086829e06b79b139516aadec74863f226ca63b2372, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4485
+8, 2, eb1dd1de89c1808e995cf5ec9484ce99a44b6beef8f5696451eb45d1979a1afd, 0105085e801eec72f2c302fd800d6055523c14de552532999b7e7ff148042cc0, 2d807da12bdf39158396cb94db480dcf875f3bacbad8193d6f94451198129584, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4470
+8, 3, eb1dd1de89c1808e995cf5ec9484ce99a44b6beef8f5696451eb45d1979a1afd, 010a0beaef18d340a523a50ed3803cb104c2c17ff19bd130d55908f2985fde64, 6886d2f62b722505da3c028aa52fb0e5b25d080d4babb38dfdd7cc7ffe1feeac, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4037
+4, 0, b7616f473c023eecd7d77b3562cae096fe5ced3e616aea14824a9c608b108f73, 010507d263112a994edbf20868e6d37bd3fee9d39900ca36e40b28b28a1f4722, bdb772d32bbc40e208a0b64780ce4029e024aba2334d3577f1af2741dafcca04, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4296
+4, 1, b7616f473c023eecd7d77b3562cae096fe5ced3e616aea14824a9c608b108f73, 01053be2154acdcf30778947d25c378bcaf91c5db0253c2e5dd488122ffd81d3, cb35fca83b2b502a30e080815b7800889df8485656be917bdcd504ac2d236ffb, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4296
+4, 2, b7616f473c023eecd7d77b3562cae096fe5ced3e616aea14824a9c608b108f73, 010707cd33ecd3a3a06b0de75b57dfb7055a4186c420aa6019946bd8d6729114, d6f0e8c19648292a186ebafe8585f4b5e3041218f16c5c0e78a88d900b83ea62, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4296
+4, 3, b7616f473c023eecd7d77b3562cae096fe5ced3e616aea14824a9c608b108f73, 0101274ec706e0b52f3ea3e663184c20ca41e72ea769051100d24757c922bdcd, c13078cff28f022685405d7460b916b45d880484a138d3a05e8cf9d0b86340f6, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4296
+7, 0, e097286c70975340ceaa8f469be4ea733b00eff7c9fc94baa92d1b6c466f3d12, 010511b7804235c69f7cf9279f433479a9793d83367390698dac95197168d502, 30ef7c37d83e5709ad6724b883129db22f9c7e19ab9ea37e2dad50e46d0f325f, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4093
+7, 1, e097286c70975340ceaa8f469be4ea733b00eff7c9fc94baa92d1b6c466f3d12, 010945a965acca76c03ce5ea9e3a4c0ecb16dbbdc7bbf156dbd4ef0801f34bc4, 86dbfe6a602ee07d7e45aa4227978a0c2dfc18570df92568f07863c1e6912242, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4093
+7, 2, e097286c70975340ceaa8f469be4ea733b00eff7c9fc94baa92d1b6c466f3d12, 010525f4baea1a21cf4e44914e93553e558f24b866186ef7f7f15145cdfe3401, 376b4e8d2b7110d20c0fdab95e491529757eb706032aaa80b7adba7726215f77, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4090
+7, 3, e097286c70975340ceaa8f469be4ea733b00eff7c9fc94baa92d1b6c466f3d12, 010527dd5047bee95ae94b75c75ad22a200aab49c66bab70888c0257b7268d2b, 4e4edc07ff71d34de5d56352bcc4befd03ed5b0703d8b977ff3ecf2998722ed9, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4083
+6, 0, 3ffa69241233915f4e17236bc03b30ad5f49c1b0630647d9548867b23d915ff7, 01061fc31fe4df546a8e9a11045a2f89c0ea0b9a9f7fc4737b6182bf5f32baca, 80ded011a9caa347a6603f224e6152b002fe2bfb7bf12f5b9a72b78010f9fbea, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4990
+6, 1, 3ffa69241233915f4e17236bc03b30ad5f49c1b0630647d9548867b23d915ff7, 01030bad30670ebfbdd4433acaf8e0af5a2a7cb682211761530d17ed3bcc1249, b7cd4af3a75eb78a0626c76b25b5a4b1be566bf4a9f8d351b14d5a3509e710f7, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4007
+6, 2, 3ffa69241233915f4e17236bc03b30ad5f49c1b0630647d9548867b23d915ff7, 010427015a5d4dd334101291097f7a303b05b86181f621179cab664acb041ad7, c5c1aef28e07f52345152f892f50d5f3c3533bdfc7d8525bcbed141513f3b9b8, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4007
+6, 3, 3ffa69241233915f4e17236bc03b30ad5f49c1b0630647d9548867b23d915ff7, 0101419bd618e12b74dfbc0257842ddd87099cfc6b226e6c28eb411f7de83653, a39db8178bef1aff2f85089fb24cecee189c414202297d221cb0fbbf238b882c, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4007
+1, 0, f55edf0abe2efa13b69b4d0b68781f79a1fcc3670c9170ff0c461127da9b4a59, 010431e438eab93c4be3c9da56724076726933bdbdcca30b59fedced0a79e998, 8881e7fe8fd1a2e1e08ce9423699bf0bb6997bea2d8763ab51e09497de74b29b, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4298
+1, 1, f55edf0abe2efa13b69b4d0b68781f79a1fcc3670c9170ff0c461127da9b4a59, 0103101f4398732a849fdd717c7304d205bb8b54f0042fb21a25aa4d6ef3472b, 69b7a6a64c54f6ede93f291feeb6f481e9569ae79c426de617434417d802c0a3, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4225
+1, 2, f55edf0abe2efa13b69b4d0b68781f79a1fcc3670c9170ff0c461127da9b4a59, 01080a2b54018c61e7524856e090cc98b2f7713babb7082a9c125c2f47a84104, 2c666088a7713615e08683bdbef7b17764522eb2ab699d4584858739ff8154f2, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4086
+1, 3, f55edf0abe2efa13b69b4d0b68781f79a1fcc3670c9170ff0c461127da9b4a59, 01032363a6b032f025e22dd96d946c4c77ed741473d19d2f998bca13e80ab21a, afbca6ae4902c5a4d894acd25d57fb91577699132cdd4ab65633f1e07117be4c, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3921
+9, 0, 778a5f9b13e6754fd05684f2eaafb093794ab3e864c4ef5c5a30c44b181bee0f, 01072cc7ed3c8d4511d3df97f1917002650b90dd23293df7b9693c4b32953eb4, 338df6d2204ba7c38dafbc5ada86d165c003162501108156ea530d7b3f4465cf, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 5258
+9, 1, 778a5f9b13e6754fd05684f2eaafb093794ab3e864c4ef5c5a30c44b181bee0f, 010906bc116085964f6e05b17d9ced5d7638dd800f9b9f491bb62cd22ada4b6f, f2a21fcf4c55ec1bcae0716ea793523471424cce625cbc2b1c62194a46b9c763, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4607
+9, 2, 778a5f9b13e6754fd05684f2eaafb093794ab3e864c4ef5c5a30c44b181bee0f, 01030d703cc5730ff14717d864b1e50eccd7a7fd180cff06d233e8acb153f05c, 796a83a0142df9652b530143f33d8cdfce181d8c7513beef9b9d692a1c50d58b, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4252
+9, 3, 778a5f9b13e6754fd05684f2eaafb093794ab3e864c4ef5c5a30c44b181bee0f, 01023d40919862ed174aa0903881d2de7dfc1741a86b834a0d1db36ea5ba0476, 51220ac298d1a70f3496834c6361a82e818c71560f35ded4730ab3bed3b34ac3, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4252
+12, 0, ffa831d1e5798862fee1146cb9e7cbc23452fe633fb16752f99f32d7b8e8b5cb, 01091a674dff91b7c38f43c9fd03a6124bbed9544c301ca78a370fbda290870f, c5dc870536bd345374b2efecb2044ef9f1492d5a563d6aed0b2a8b33d18caa5c, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 5182
+12, 1, ffa831d1e5798862fee1146cb9e7cbc23452fe633fb16752f99f32d7b8e8b5cb, 01033aead48b1256cc6c2db85361c0da448fc99ae278322cd4349336c672e330, 8499f3a88422c7a4469a8c6b12ec73a569665ddf435f0d2f0fcb4b9d123626af, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 5182
+12, 2, ffa831d1e5798862fee1146cb9e7cbc23452fe633fb16752f99f32d7b8e8b5cb, 01034f81a9323a3fe2a4c4586e40e9cfb3a144d7b7f15038dd40426bebf4547c, c3fbe32d39964bc177b563eac4d67f03a3e80b84aa592d1ca8a8371bb622ece6, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 5154
+12, 3, ffa831d1e5798862fee1146cb9e7cbc23452fe633fb16752f99f32d7b8e8b5cb, 01013f9bb45b3c7c67233d185dfadd279d75b79e0f9b40ff5751433ba0cf80f4, fdaf0e3152166a8c0c8ecea4215f15c75d5286d912aca439ec126ab589ddc007, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4634
+14, 0, fdca870684bd40b126f01c964f68b0518b5b5e971bacb418cbc256a5861fc038, 01011f30857cb23af64a5a0deec3c40ef9cf45cb7f6ed8ac3f21a1c892786b2a, f78d2b1033e1f2382a833b3a4c61d9c5d2e5bd1dfa075d2e691dbb25ad3b3c98, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4526
+14, 1, fdca870684bd40b126f01c964f68b0518b5b5e971bacb418cbc256a5861fc038, 01091907cf5e956040192b5db3a5edbc76c1bb088b9a77cc13490a1daeb6e977, a77c9d981f188ec8411a3d0a72c96922167c9fbfbb6665de16c7c57afb6e15f0, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4420
+14, 2, fdca870684bd40b126f01c964f68b0518b5b5e971bacb418cbc256a5861fc038, 010a08b37ab285018c4ad1082e543164c2b2a13e20c4acce029604ca9d2ffb22, 2c1285b6b4a12ac7fd7195fae65371d7da402a7a4f8b10046ff9cdd1be1365d6, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3949
+14, 3, fdca870684bd40b126f01c964f68b0518b5b5e971bacb418cbc256a5861fc038, 01013ba097ecdcafc088477791d2024f48b247bdd0d87d163803af5d22369d06, 9bad023b69cc8ceb4ddec62732c460c967e36265d7fee97bd1f7f6ebff089ee4, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3949
+15, 0, 287b689730e2bc1225674d3640924dcfb5133be02359cc8d14bffe70b1bf159c, 0108406db6989cf01571981674520b7362fe944c4e0483f1052f1b6422dbbf8e, a232474b89fc06f50bffe99663376e99ad545c211c438f84a2248a55894b3278, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4640
+15, 1, 287b689730e2bc1225674d3640924dcfb5133be02359cc8d14bffe70b1bf159c, 010536142e58d24d2a46c6c4108b1ea81eb72e381d2d0a11501747c6e59fe2e4, 5cf67c6acf27e226720170354cacad9f451617fdc9cc2c78155d6880ee42050c, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4131
+15, 2, 287b689730e2bc1225674d3640924dcfb5133be02359cc8d14bffe70b1bf159c, 010822451956749b7695f5ab431a90e7b5e6f12cc4230b8980397cc119b4f3d6, 5124ba73fb9444bfa2158c2070f271aaf63d46b554f42a6ef1014925e031b514, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4005
+15, 3, 287b689730e2bc1225674d3640924dcfb5133be02359cc8d14bffe70b1bf159c, 0106086b217b66240fa9e86343d45e7c2680e1922aef83ed1064045e180d6ec1, 50e38e423d762b4fc02723fdca764b50f51b6a700993b56a1ffd7e43385477f1, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3913
+13, 0, 5f10c44e7cc47758dc8ed15da928d398723165c11ad92e8e065892b0c5b1ba16, 01061296db9d8060d3cec6aa3013d3236e9f524dd07e2a4b465c857ca1095fb5, 22ab32567bca60e8672429b7440d3a5658c1317be299212c07b28cc28dacd0a9, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4173
+13, 1, 5f10c44e7cc47758dc8ed15da928d398723165c11ad92e8e065892b0c5b1ba16, 01073743ac0c235540589c2a8539d8aa1e35dc9cc7135f00bf96a3bfc93b8f4b, 9da7c23a6f60904b97b39f859daaa2cc154c7b60b3752a8fa66a0f976a579220, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4173
+13, 2, 5f10c44e7cc47758dc8ed15da928d398723165c11ad92e8e065892b0c5b1ba16, 010806ebf3a372c7dd00bfa473500ce244254c8e4b91ad48a19c44a73e8002a6, 21b56be5db094d08d47b64cd94ce4031b19b685bec283cb3ace2cd7f1c7a7272, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3974
+13, 3, 5f10c44e7cc47758dc8ed15da928d398723165c11ad92e8e065892b0c5b1ba16, 010430da00fe2903025447bb3862843d6e2f36728b9b9a7a85875473811f77a7, 11eada061151751e0b82093b39d3b35bbf395b22f847da6904f38aaa9e375f6b, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3974
diff --git a/test/data/gt_production.csv b/test/data/gt_production.csv
index 80569d03..5608faa4 100644
--- a/test/data/gt_production.csv
+++ b/test/data/gt_production.csv
@@ -1,97 +1,97 @@
pubkey, nonce, miningseed, score
-039cc1f1560aa96daa994a2b296f22d7f2fc9503ce95321d0b8193079e5f93dc, bee1e55fc2c967601827dd80f09a6b0a47ceab37b920b57efbd23c8fdc49e38f, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4254
-9b3d041660f08f692ae1005118f85c35bec491c33b51c94ef126663f4402fb1e, 1a314eb596f55aa79e7f61334072380042ea4e351ca59dc09f536f8224a39afa, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4787
-5ddde3533e040840b737304dd92b4b48d62ab209419a8bf4506e7b0497e9c614, 419569517e0908f9bae5b07159d322204053afd4564b4079fe160f775e214993, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4329
-66950904cb50245726a50f9039e2d384bb878ce3e39e0575f64eb61bf9963325, 158191c4be8a6382d9eff13a448550c7742568909abc76d8213c958e85bb02f5, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4021
-beb0cbbaab3cc54e87e4b6ed4a5f6c4af2fa4f452a02252a8ec910c43fb71a43, 96725650ea0a2052e3175c0a1047f51fcc7031f1455fe168630f0ecbfce1a180, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4642
-c7f5024050c450389d341eabcea04b16e79b7a51f18bcb59284eaf40000cb7a6, 77b3965cec126453cfeb23b5b44f20d652058f7726f0e43e8c265eafba6e8418, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4138
-a6f49eb0c3fd08eeb56b4ab83bbfb0b415b02233101baff5eec26bb2d1455e34, ac8db67e608af98f13f70b611c71f527dbb626afe41128cc6dbb27da921a6db5, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4323
-5c49beb4e29c921d8fad67d9b1e09fd693254f9275dfd77ded5a66acf721b4fd, 5e80e9f095fadb379b12130c4796f74ec967fc526b1face4af813a13b029fcd3, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4596
-d8470f83e18ebd6bd221423f03b0e17a87872f6ef25ca8909f28078a6c300ff0, 7989ab5f03db1a93b51a8629ac6d569ff0299ee1744ddce813013e9a64ff6497, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4254
-4c83417da5f166accd29107292d5c0b7502811b4e0eef6d252c01474dce157bb, 59245fdd6fb52df651c4b3228596175e327aaaf4f4405cd37bf84ac1a9e51747, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4476
-aee5c8edcf3dfdab7ec84b7c580be8a64c0b902be6b365fe916733f05e238ac2, 56dddea0c9052587a47db7f5deaa16727663725edcbd0c5d78f9b1193bef12da, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4179
-f2112f60356004b9fe52840e06e9047d2020ba228b7e1987663491a7d90e3e85, 4893d1aeb7591ce464e26ed43e34501acf450a6d6ff3a4dbb85a3920d9011308, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4227
-050a8c2acdcc99c71b1f6e04f3785291f2980fcfbc0a2355a200eec5287a18d8, cd2744b32506fc199fb1aaf0b9edaa553d05f0f4c66e9f7d2e47140fbc7973e6, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4112
-5bb6da0fd6e14135dcaee492185a223c5495936b29cabecf415094e1cf0b64a4, 1b169a9f717bd61508b009a9e149c3bf3bf192acbc2a11af6da31214ca0c1be2, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4367
-ce103ac663da7295f0013f33b1eadb5295e32dbf8392f67b8085caca9d8291da, 44fdb9bc59d865bd9d87f049ea4a7a702d1a0fca20cddb7172bb10d7c17dcb06, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4373
-da39c1bc6effaa3edb88cc805e6e7bd7aad1726ade6b0dd38d54b52957f14125, 50ee325b50af29c82a0ac06b3ec85885f9e36e08f1342a938acfd9a002ce524e, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4538
-2f2a57841de709ea79ec678d1848a2dc13cb1d96d1d029278181e925ce6ce281, 29b9f9db6a5001480686637bb3f187868d23a8881b32482000a4e3b217074d54, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 5044
-7bf6c1e4d203b633f0f5f5421d2d16e66db924cccc51092f1e89ededa54ad322, 490f7927f8f91dbeab6434e4f0ca660642d9157267613ded72a8315df821d838, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4272
-a6f0ae22305a9a0336a2c7d97e1568697cdb62e1aedfc9874a1ac1915b4e3c67, 332f47bf272e90de5befc330c0ae65b88a981c6c28819c9df8bbc7fccd7176c8, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 5126
-1f710bfadc49f5a1bed75fb10054979a3b4856a48e3b75184f7b056d3a23ac41, 6d5adaad8e0ea9208390e5649d2e0c6365ee84cc1611ae5f765424d7992b8322, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4227
-36f621af4c726d3e5bc3cce3d26a4418c2272024fd8177110a6a64fcc9071afd, 6fa998c1ee8efba299b8b51e865a13b939df3189d94c0b939f86c8b2d9fb7893, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4002
-8117882a9cbfce4ca0df28f72a818773ba80377417e8c73d2c0a56e8c6e4ae22, c762e691c4918cc826e72b5277fa2c11f4ff9cb2a01efefabe6ab8f6c8c5be3e, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4124
-a7a69484aeb270fda2ce28d9b6d4ef6f1e73edde9adea5404f81b7ae265a24bc, 4b18c6bf59aeaf1f96a11956eba1c2ac7bbddbed8e0f12ec5a94dcc8107a5cb6, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4500
-a84e3c009575dd77299c18d600d0252f9d0480d34c5da189a7625a0274f83cc8, e0c96c11ba7a7137aa7fe213dd34dee838a598acf0d76776e5c3778307c1e631, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4334
-dc100c8d058c0cf6df88dbf4fe01f6023b173be3bedd6b86dee4b8eecc72e058, 179fdf1f611d6c27a08eb533b826d169a0bd3d33e6c329ded1eee76fcefcd2dd, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4259
-087fa2a7ada94299393d118b19c6360699845db46f559eba3ee5b8bc494f2a8f, 0f200d7799483ecf52a4af5c3a69111fd29a14bd7a03a82f9f39f1913b29e5f8, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4347
-8f9069a8eb962dc47afac9ea7d4a77d7ab801c3e932183f9dcda9c8cd7fdaf9b, ab4f54740589f62c4e35c2b7a2508a3f7fb23872afd62771e57deea64bc540d9, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4477
-ecbce54faf01fbf9015cf9b19be9bbf42e9530be81fe09fc92c666eace8bfc15, 86acf64f19dc27151febf20c2549d49b05f4dc87290e108e935ebd6d49950cee, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4513
-36e28fe48780115b126255d893208b16f04505687e9aafd17ba73dbe543a8319, ab908ba2a064b7381f26c7a02cfaa2d8b7689d5f24877aaad99e25e0714ca19b, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4711
-ee4fca45aa951e2a2c0e535fddda71cd1c0722f1b30bf74416d9ac6df8787e52, b53606d34971e204c260e1c5c2bcb88450f429b76340d0860046bf2a67cc359f, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4634
-25b3ace18ee066c5cb561d32bfc89f69f20199741519d148a1d00312191e1f0f, 06ab93f88f0fe2aaa54efe1abf60cceef2b21d15c378d66096398c8b82947c7d, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4032
-a7149ab5f6c9f3ec2dc997564839a050de78dcf54e255d0019b4d81c3675bb56, accab93bf12ddf0a8b2ee43bcb94fe01d265eacd6df6aa8bcf133a5d359a79f6, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4964
-d4aaeaf020007d1349590d77e2f779ad48f9a6cf720d04ca6a65fbbad81dc050, 7a40d7032c899ac35f224bd0a7be2c12244e9b2b4a32852b2ec880d526929ece, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 5357
-5fd98a26b2bcd498daef005d5035336777164fd594690d083ffc07c2750a00bf, f8c4c01377b01fb07d914d37301d2eee2349e015128adac766c676913f7c056e, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4538
-acc5d85d4bdb5d3e63879ee9975db8dd89474eaa143fd3a3b0dd9b406e6fb19a, 2097e387f6f27d6f630532f3407189adfebfae9bfe4308f0a4a718b72756cb64, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4251
-5329b924cde1d5be74ae7b98fd5dcd6c6cfc497abaabcfd13ba6ad76b3e80a73, 409da8d39041e54f20851bc0ae095cb8b80ba393af8531ea6f1977e14e900bca, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4435
-bd50121a1a9982b2e6f1becff5beced13d3aeb3b8292b6d64eaa641a85c6af36, e2c0de9cf3488fb4db674d9b6bdb7261044556a5ff21d66781c975316624ce8b, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4064
-f09fa2dd45236ebd36dfca4443787119dcefdd152723cf5f4346f16c33611ca4, ab4f459cae0455dcee65258b8c8340e3429cb43d826001a52542b0b10b2d3051, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4618
-5df6390b68e67e1cff09fea890e14b204f8b5e9ef847b14a05b7b32f83576c73, d2c779399ff772a44024e4991f32463193e7d21e958b926ead5186fad44d276e, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4658
-0c5c814529d514ed2084f87554a1278e303aa627c720c21502509e6453b46e75, 4d2317b4d7645a12954929dd48e1541ab6c333eeb590fed06bc795876d399b86, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4805
-1ec0460a6d6ecba87d61bde51c2b27c758cefecc568cd807b5d34c51d4d9f427, d0aaeea09c9dee00f7062fabcf47fd61f2c288b80295a820ec4222a18c2f67cd, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 5385
-e91795a4cc4ea7a134da695bd2ca1638e3510d2ed21a6207c34b25c920925556, 2cfdfbd4047379ca02f323115287c8132cbd1ab84ae94bb4142147ccfaabcde9, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4104
-547ec5ed8156d06f369b8642d81f542aeb06d8be0fddfb82d499722def67ab8d, 58315e20f5e05abcbb1c225435e985dc08edb01a51bb472e5a2caa1ebea54636, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4293
-d8e5cac99aa0e4d53d13a2a5efcb44533def7116cd8cab23b3e8ffe2e6e36033, db5d8b11d57d382bb4a174aeaeea1934089ca1ce87f695b02e969aa2d804323b, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4490
-11e6f5455923f425403174d26957f3c4343082676f2786a504b175a37e095257, 3d2b359ee811655af3afa43d4b6bcef022d9b086bf7b90196f8d2fd319cef2e9, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4101
-3c5904c5eef6ae6bd1c5f04b329bf8263f5de55ed27b416caec5d1c7ba8a1563, 4595621029e319dd6a712f40b4694bb41a198b97fcb5e1431e36b43d2a49f70c, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4229
-bd57f4064ecfd309f405a10536844d1ba5ee33ef367100f8a7bb8c0d30564c09, 3e3fe3eb8a8e57fd02bf0aed9362a8fa922792c23466f23910a07f5ecd6a5891, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4399
-8cd870e8a74f72c9293b449aff6f2703eee771a2e804ab3b0e85e90110a70ebe, e4043488f279cd9c732d5074e615a25c2684e84c82d86c742627ea7a5eacca33, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4427
-2aad58dee6a39af963379f65fe80379c9787fa0ef39ac6cfca83883b39d88f80, fe7024fbe4ed4989b6004ecc78ef92db203631b606d3bcc0266406bc2dc2c95d, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4211
-2944a74d098bc8cb2c49c7058fa5c1749b6b0762e7953d2909d081a621322bc9, a4251d12a25da7049ac54781cfcf729548fba470abd9aab5071854aa33651f48, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4392
-59423970ab4e01dcd75b21c9bb28039cc754cbd9504f1baf7690ad67f653d9bc, 8cb0ca3bc6616437eac5c040fcbc82de45a9374a686c68be73e1ece507fbe7a2, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4234
-4111344f7654a20f63f205251e233949ced281978307fe80f5994101ba30fff5, e0cb33733e52c5cfe2f342b834e6be21a4c8ba142184a5a712fbe01d2f321ca0, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4192
-cb2e9d7b14e92a7acd3606295181a85b3a3834812c85a58c5d8e8fb966d52e80, ffe82f2f47efff7e3c7c1f8489126be98246f789a0d5cff4e62e251d7e9637b5, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4246
-6acfa4e805379c4ad2c8fed920eb94bea5c66753f76c9e8903cb9f061b9a8568, 3291da84832c5e9fd2ec14ab4bc3852a9f501635962490595f92fccdb66104aa, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4277
-5950a4ccc1ee66af76e4ddec06caa7a0992cb2f59b7fe0468edd64f7bf7ac3e5, 8f6afeae5b25d78e7a9105fe200525e0012cd20ccf172dd0f610dbf804c1d731, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4140
-971e704c6ef92be3bedbdf77b8829804ee21011c6a2a3e79a68b1d4ad1cf826f, 19be39d709b2b1cd553f4ade9553d982a5ebc2028c7d61fa2693ce7c701ccf2b, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4122
-95ebe78a1bec9a12aa57462a7818c047b9cae0376666d0640905e81ff6e9e356, 7a418455ac94a63d6726f6fdecbf32d9426dd990ba0548e31e710703639980f2, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4418
-8782eea2fe0934ef90467ece2754218f16e02f731277ec7f244217f0ee0bb764, 7904e219e0265db109364c873d81f45e7e7522e881f9646faeba35fcc49985af, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4189
-7097a76021713afceeaa610bb79cffa218a8f6235ea1aefe078e957210170a47, 4068216c2a24e21043646f0f6b411628892c9350517451f528e632dc757ddabd, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4174
-e2c85076c6767b991f71921fa6e3bdd669a3e1e739385b4f0330250c828bf8ec, 017471a30b0129216db7a28a5da7dd2b16c4bc856c85d5135985119e4a99e6d3, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4336
-983e19c755f902f0026754de7fc89dbfba6ccc17f161a6f69c94e27ae5f3b8f4, 63301609c9ae6610af2aa1282c70531380a6d90e2b87d36ced6a84108f64755f, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4686
-66f9328e9c1a79595d52798a25d3459552afa6af9aa52ceb7ec76f38a8f3bd39, 0b71ad39ef27577b06bceab5f264201b2e8087143e0df90c69b0e206042470f1, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4307
-befe30abf1c5e24965d39b7ff3f2fc33fc4698be8a33c87aa4f8536ddb8a1996, ad87664522487ee7986f5ce7c26ff962b5feba171551b15b047d9a7053dedca5, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4235
-0363966c6d6ea832ddb1df165841a3479df9ec8d2ce0cea8a9400360f3cecc34, 391a80f81e72894605743026c249995103834322ce94fd953107a781583c2f23, 25b115eca9f2c1c0b3ada40faa46c248f2919d8e5f6e7fa2866d4d96ca1c0eaf, 4472
-2cfd43630593e07902ff52948ce387aaf0e2bb0e11952f8a0c91a33672203bcc, 157ebdf68c866bef7f93adafa1490ce1c15c3b951f23461f5327b53b3546726a, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4874
-f99a06858d3d9dd741b0d1ce08556aab5c4b383b8199920d776daea35634b1b6, 494feb7026395e0ce15b603a2e424b79cea1d46da11fead0d84c29cae57299a5, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4657
-09bf69a84c357ff346577e5d17748c2b3d5b3ed84e9329759b131c2b94e7f4b5, 108075e0dfc82821119e0f323ab789ef809dc39b203db23ee3242f3a42ec0a80, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4728
-7ba937604ac1bdd1cdda1954b6b6a93eda265f468a49f4941f8de0980caed213, eaf8c0959fdd124b1756144818ba32a60f4fe12454bc342e25a74b13b745241a, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4274
-060f7e1a8c8cb6f938b3116ff25884b6eb87fafe9ec957d07ca28ee58fea68ee, fdead1f49e206d1892dc77b38a8b0fc8a286d01683ee994f7fe591c6f66b2bdd, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4212
-377741f3abd82ac5b90df9edf3d59ca9ab1fea5376dadb40242c87f55a6c287a, 9669b5d4bf3ef9184daac77ab44f0e3ce4e46752e2a7fd68ebecfbcf6109fe3a, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 5152
-7a4fcbb032e872b47f2a33fbb2d27fa06965165a91ce284a8d29a43bb4d118aa, edd6bd4e7bff26c7e441011422fcfe753bded55005a5221046884efb2afafaa9, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4144
-6ceb9db060f76ff7a170037cf3fb0413f3bcb5b2cbdf7f42106d30fe6516be95, 9c83bb2e063e73e7338944f8babdfd18d01ed1d13c9baa43fa7489fb52f176c2, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 3956
-43e55c8788cad0c02cdf142a7f7e4683b332ed6a8efcc831c580aab3e9a1b27e, d9990b01cbe2746aafedc17d14f5a84675938f952eaeee0b3b5764e4bf054074, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4236
-cd0e79b31ed4e699811ca0fcbaf81cb3b0e08188acfc38b6a98e3f6d077e6fe7, 3625fb00a429fd89c4ce1c4f92303cececfbccfd7bc10b1f00d98e8e8540fbd6, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4421
-61217450605f85dfd56bb5c1c046db8018bec5c533f3d5dd92732ffdf8786e60, a165d267e0fa10de03d6149b6853930ba3f9a244f3dd07a66b202f064535dc6a, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4330
-c7f29ca4e434a0283049a26b8dba34072bf7a90e2116a5374171b0137789fa34, cce63e8a939e4dbbcff17212af5f390f7b5d5c7ac7c1c4bdc9db353b324b092c, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 5074
-19c0127c42618672cda53ef8998cf7a79bce5eda48aba533c4c157db73bf1430, 63f09dee35a7ad227266f20295f406311a3563765e51dcab2135f589127c5750, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4916
-93970bbe1875f9bd9766a225f95aa3d3e748ae4ff7a629e8d8067582ae6874be, 4e9fc8003d5384be5dde14ed04c9bb256dfc34882ae7c51873a74aba28e300c5, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4419
-6b17204cc22a12de5171f59978a5598908a3778e9d635e925190e312f27a5104, eb631efad97aa19034488b9b19fd26ac670fb584ed0f7eeb0bff2d4f1e29f0c4, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4692
-d0d2996accf533fac438f273ab65edc50eb5a756a727fa9c51308f8b8608583a, fd0ad8a263a4cae7cc1cba87fa8c5af6b0c74694cf6dcc9c56e4867d1467b53e, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4544
-f075dc821b7f48bb638dd898b9c1336abe8ac4b8ba5c964b5900c8b26fb89223, 88ed66223b41c277d464e33e1eca9e022df91b476a316050c9fe296c2d27a55a, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4127
-c14ff23a8dd058c87e49edb5b6e0433e24238911ec1c7c179cd8338ad3f1eea5, c35e26a31a309a160cd348f3540aef3277f3ca07eb869c59634b6986f44ae8d0, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4488
-7baf3e8717e443d1f3a2459c6e4368b79a6233f75e236aa6367b5eaaa2deb8cc, 853d8bc62f96344aed6f69a683ff7bb15f82839fecadbbad8b2eeda3713b73d6, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 5043
-aaab6229bd572c77a7a89b3b9a7bd17cfd085528ac5471fc358b1a4c6d011c4a, 1276013c419de03614ac9da61dc28c732b5d706529e5492caab405db613d6340, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4216
-f73ab8675d3eef76ffd330ad2ef8c2dbdb2f5fe8e76bb7c5458639f2e2cdb7b4, 5c3dba3a97e7cf0d9b92e5cbb8228d5b6ea3f9dc54fba6c288e6181b03ba369f, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4014
-adede9708eecf496012fb63c6dba5926c896d077ff1bd2e8a911b4eced495d82, 66cbfcfbd434f0a4c066c6e42c54c8a5ff544eb81834c14781850421d3b87d28, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4549
-0c5a86ecee90b2275137aead69f1005ef9f1edc420845fa667ea020f98c8d064, b033d35f89a05b58388d4cfd3f2cb855ae4a3925353926b6f267a06d63fc9420, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4144
-f2ce52d93723e7c386d4604876a3c3ae7a06a295411ea2e3719f0d5b5e2a5e44, f3b1935402df86ee59dd2913a11fb327451542645a62bb8e5217a459e242e820, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4309
-f067e50164c5eab1194c3b77cc119365c01ec9501a7adf5becc7804c46695dff, f342f24d14e52fb7441f409386ab457e353caa06ee395beaa25e27be5e1a7c7b, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4517
-7a33c852aef3009f1a8096124c4e166115fd14c1619bf8a1275b94292787c274, 1f704e695d2b807cd9b3f877ab12fa6d5cabe77127a589fb2ae6df94470e7717, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4352
-34cd3c37f0cbda82af0b62b0b3c75887a1332c5a855218c75fe47e2d6bc6aeec, 35df8cd36c809e50e74990932a316fe5ecefcbb19fe3f4a8d027fff912202998, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4042
-dc679985b8b7d22ca9dcf52c947f826d9cf6f36582b70f7282ac1b67a0c46ae8, 80c2186977f4180ad43db66bb7d5596d7e42410dbaf7e84734b71d589f46724e, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4054
-17f3b29ae0d930361208d16169894ee4ed9b5846bd01147d0c7bcf05ae7d25e4, 5ef965812a3918a2dec24cedfb23a306a0305e40e52d9b38ff00b2a0d2ebe616, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4480
-836eb6226f84ecc4a270ea559f2b3418cdc0bb9eb66a26bcceaa7e45c802cdee, 9a7512dc2a55c148ee1900e85f9bec223bdd73ba25b650873433164d196f2349, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4849
-6e8090bf760bbd8dd5146ebe3420223b8be6b30b65e714220c27d1c2ab62423e, 29ad5c73e1f39167a370020bc2ed3af84e640bfcde167a6981f0114723fd7ac9, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4296
-9c256502b40844cd9f2b11ed9eb5472441731b66797542837dbc68ee2524cb98, 5627705fd251600ddff839d02077e646f85dbb08fc08947e9098d0e2c289022a, 96d417dd9476db171dfdb190e1e2890fcc8ac78965ad0a65d7c2d2705d49d5d5, 4454
+5c6abfcb759cfacf849b67bd300b165b96084ca0e71176cfb7c5a33dc08bbaad, 83117509e50f639121de2895d6ab1be7414f6e4eb47a584b23c868eb02efbcda, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4834
+3e09a895669d7bc0a0178ee5a222efa0aadced0323b1d9979d8c91245bd2d807, e04b504d08bc81e18ffb0c5e26053179a02462db28e087053d0f190774d9ed41, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4299
+69848177b7d38c8dc9e4d19b7105ee6a5cb7fc2336a05775183343566ba2c96f, cf663581e3966026b8eb3cbfcca5123587eb54dc4be7840409b90f6b3bbbcf90, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4111
+43b689a5bf647c1be885a1edcad5b556c82d653af74e5b550259071e41b585a2, 2dd0f2368db7d54246c1eb6b25769cbec11afaa1cfc91dc2a8c8f30aa9e8fdbd, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4408
+7cbe7074fa28b6b729ae68dca950f3771d4d366ebd7143ba8e8c2760acfe86fe, f5797c25536802d6c8090650d59575863c26c838475ecdbc91f8acc86e22113c, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4079
+528018592df920788f63e08e67bb28ec93773d1abaec524cc3e272fed477b256, 475fc1adc179f7d2863f15f0bc581d9028e6bfedd98f294e5bac85c833c0f146, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4332
+8a98ab944c81eb261eb5139fc559d38194ec8cebff331bfd8f6c3ee120be9727, 892ecbb404e49bdce6a635322ca7d0079bf1a10f70c1a7ba5a1ad5750bfab59c, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4191
+252d81094d6463586ea147fc81b8c52f9c2c662b7b46a9ce7c24b40713eb23f6, 65c4101fbad875daf96c2336bc1765949528d7347d6d620530177d631c87f872, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4002
+b5222770b54b868a918dff38148836037e24c57205f69c05af18dffcae566caa, f96f40960247a776db986f1188ea577cfced37a8d3cf9d829c1fb482c694ff03, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4014
+233bd25bb925a17e74b57644d01a8f02aa190d5fc458a3e9146af6a18fbfc4e5, d970c14ae7dde13424696cfe13c2537df036ea1982c1265df7e1791d834f0d9d, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4053
+4abe622ff9ad1579f45bfcbe745fabf5b08feb40c957b3b3b599e3965865000e, d8ab02479da9e51d2e8e98248f8448fc933e586f1ea660e48d361aad4fc95efe, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4656
+1e2049d7f096d7139a9d0953e3de346e29d869e42088acfb0e105b1e5beec400, 50016a62dd87e15506817198a9227e1b4cb51f478c2cff5710d27424cf63d210, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 5361
+b79d9aea466940965878d446c5afc50cca28ce50fcbe69ebada6b38c5cfca999, d37aa7a0ba4fb51628de0747ab5de0a76bfe0ddbdb930e0cc440612229b5faa3, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4017
+3974d191cf4fc9b4c32997ef596817950c7c9a76310ae8a49d1a3340575fcbcd, a558d8dca066bedca197c2c0d5b77e55db6531531e0668434e8a9b7883fb18ec, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4088
+97683b0f82cecfd2fe4cdfdb2e6cfd7c1955ffeefdbe6a43b1d45ae623db38b2, d67466d62add63979c892058f3107b5f950fdc750558e679727e3fbf8c74f443, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4146
+a776ed3457761d8d5c3ccbb99b9bfc4c7c1c13fcc4cc00a885a36beab298565d, 36301c6a59665c4dec2cfaa970bd9f1379236601521c0e284cb859933b5bba02, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4005
+f219619241de2bedf3cc635d3e90a0c085628d0bf90d33a17bc49e1d59932b49, 4b234c876cdadefa0ebd99f6821a042f6973ce06771a31c00ca23d4cd1ad15fc, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4556
+bd094d0209ab1ea3b4d2f5ba0a17639deb046e5252fef7a7f297fe4e2a231bd8, a8915bcde2eed41171cb46b189fb2b5bb5cc55e769162a55c6489729bd6224c8, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3998
+1be2fe6b05634c1f4b2724b1731192edf9da6d82201bd424781581b840a74c97, 7719b80b4343d5538ba4d343cd408aa1327a3ebe824eea3162771c6d7041b644, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3881
+5823092689e26cab35c4548e187b27968db44ee2ebc9462e7825dc629ec3cd5a, 82b10fd72b1eee4e17647cd782d9b65b5e6dc2e42816f95ace4cefb85aa330db, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4266
+1a597194d68fd4472f1e9b0e0246048091a68f276edd7b3d098ec302c5c5dbb0, 722423c42f5c37c3fd546d305f7bac2a7b3b3d502e88f94dc9d777b4b0595ef2, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4196
+69f7f8a0a5785812a99987b520198033099c7711364131330f349adf84043da0, 9650ee05ea97d8dfe539e561a95d13bb0ed3010122cd83d7cb2de12e17117e78, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4051
+96d97311d817be86a1e92669190d61b5f74a6349300cf9866e8947ae2eeb8b56, f9d2d3f44d659b2fcdd1cb790d05e9e07ae8c02a639dc6fac51128d2e647adf2, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4846
+0fb2d3f10a46a8589ce717c9fcf2d0964a7587dad5ed77c5e9fd77c770e6f96c, 0aaf6507ae1b6741d33dcd9df870382e909968a4913ed120ace7e8a8c5db97b0, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4294967295
+ebf175e23874afbfdc78b553e2a18d6d437b1315791038a05fa27cf23eb43834, 7353fb920de39d5a7157248ccd7bed40bcb0bebdd845c178394b3990c62b16e4, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4194
+3b3811ad2d7cae19dc1ae072995144777b38834df1c7afeb42acba448e89bc94, c79c8599b14a928e590bc08267b46687b669e44f8c2f17ec5cd546757898b23f, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4279
+b24eb5baaeaed3c61f518ca9977e0a3ed7f096d6ded8928c9e82796db54c6db0, 6128372fb6122ce0580c8be4ccbcdf8dedda6e715e1346bdc07ce8af2fd64004, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3984
+86af826199eff97b4096cdc0a65c39d3a7999943d5c972b986a4790de3c6834e, ef1d45ca4e7798874551587794b46c2f8a326a65ef2606a816edf640a4e64f35, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4198
+95f4c1fe80bd070241363c54a7cacb2700f676d27ef3be667ea4c4468972faec, 1d7f731c233ce24a8ef8c8554e632e559b86a36077e9ef383aa35555a99ba93a, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4112
+2fa2b7502405b29c91d860624137128d014805c731c995f1e8ea0618375bef71, 487c98844fd5e508b91bb51fe106c29053b2d789ce34f83eb25b9236d044ce54, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4445
+43a22b7e74dbdcb06eaf05af45e973ecd0516618c0cdc5c5f862eba3930ec6ca, d45e68a4f7bc514ddadddd88563cb1149d1a4627176cbc889c646bd993f7d9dc, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4525
+c78d83d544cde30940a7c2e79dacd89b7be67bfc2f675246b0c900c3348be47b, 7f07b4b55f5461eaca22747939121bcdd480eaa3e52fe6eaae533e87b5bab6f0, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4079
+0868b5d914ab9fd4286e8c1288d0ad20279cfecc4fb62f7a0261349593868034, 2b18adeef5443c713cdd1eb669bd522f811e148b51becee2ec7fa076121fef3d, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4068
+8c67119e8866f54e299659373bfcdb50658f132f5838f5f98f2a6cccfa694582, 6f1983a520ad271eb4b3a0feb3d78379df1aa1ca6b1d39270f0800b052220206, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4035
+567a12bb956b64450bdc1873cb916ffb2e5aa647b44a6e7c694b96ac4939f80f, 3f4be93dd2bd342345973a164c36991a2eaa123ab679e2b662cf79f3fadaa1f3, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3967
+cef39307dc5f8b905ffaa40b74dd7a6ae28e9b20ad410aec447fc1e3faeb3109, 6d898904e6a64658c756f2bd12ca465fe79a8e177bcc7c08ad72acb543d455d3, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3995
+b5f70c7915c32b6304048d682a5f56a27a6e6647e03ea626a9ce66a1e5b74882, a07872db7e1665817caa07bfc7c05fcf8a316ce17601aa64a280095ee4a47f3f, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3994
+05bd14c740eccef297fe0c36dbce6dfcf3a573c5c557dd1762d8a91df58db2ad, f181307bc0c696c266e89d214846dcef1f2ae46fa6ab0c755f7021bafcec2458, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4241
+3c635171bf5e2c20d0464af2752f665927946f7da667f26a6169212d0c7b9e19, 71b317f8d47857d19c26ce96f47c246f24bc359bc94aec5fae3617815eaa0627, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3924
+4cf7eced94f73f92f1051c49993c02464a46ed9b4cf992b8a1b32e82c8e7ee53, 81249a9488535d742ac2f0604a56d0e03731fdf883909f81a67140ee2543a7ae, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4830
+b21455f84dd299a4e7eecf59bc16a4dac3693bed9d7dc39205a790f387218891, 091300da9360265d7f95e331d1aa7cc5bd34d20ad29d644c7187614c1c96855e, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4048
+7a916acd1bf9c4ce52457eadb627bc8de362fd77f7ce0362e810dbed21547378, 2050a738dd2fb5a268cd781a4881dddb52066b7d75817bd06f296ef5be6fa856, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4104
+272470c8ca87d42d07870a914c9e980b32104b32de7b9a51b8c7c984f79c2c69, 56f4c134b9cb6dec98c81a8401fb1971cf78211d2ad4f79f3d7dfc8d9b0301a5, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4483
+1a3f8d93141ad5e3ac1a90e9f46d1f32631bf694bed474c7962d651e1fd26881, 4add95fc66f5aa5d9938c8e32dddc7c70ad054f114507cb95e14ae42bb253d96, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3987
+76e7e980a4199b13a0417db3d722a4558c586885bce7e6679467835f6fc253ff, 5e524a4a4c0e39e44a83e2040a2ffc4b536098e1a4659cef228bf326d7130b54, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4072
+e20a1940ddaa1371af7e7ea919f8064fbe50df43830bab732931aa1900b7acbf, 82be747d83026cc2bb90f1f551a5abd16ac9d635c4723004751c26ac15549a1b, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4012
+59b8ff49ba21f829bcb682a9e51acd7c4bd43726f8e8131e7c0399dc15d1e3e0, 18a3e10ca629fa686ba5f4cbd5440dddfe82f874175f6d77b822012a1cf54b0a, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4250
+87a835df7f5e7703fc019bedb0e94a7ff63a715ebdce83d5e0fb178119e4f06e, cecfc2663701707933ef70ad09a70de9c4cb49c324e5a666bf2941b481065ac0, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4029
+ff23e4cea9eef8e9ccbdb7febbe8a696c77398b42ee05b3eb7fce8ffba9f22d0, 647d7cd0fc289c2f923ab330f2fd39d476c284e41bdd98e9e1d517e3014e0511, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3958
+de7688270f4f7f936bb26eea7e35340b5de7ae9ef76ec13e160864dd133d88ba, 229d383c968182630f5216ae7621c8e8d509a2facb150dc0853b14c88ead9698, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3873
+5d6908a6dab0c1a5842f8fbbef4b9056aa937c8391b95b2d821241bad4640ce3, 35c4480634c28602acffa168e4a46f402ae7e006e1a6226d4a17d0abcf377a1a, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3968
+8d47429fff684d4ecfb63e5a71bce0fbc73812897d2306c267065354e14d63bc, 2e05ceae39061864f93d20a95c48a778de47ea4505976e8aa5a4d53eff95f389, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4294967295
+86c34012e9381c9983251165bf62206dfccda9d51e05349b852424b51a5f5791, 60979a1a72c4557f8238411fd06230ad600ad5bc5cd0cd528d27e04d048bb3f9, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4327
+73ac3e8e5d99461848fe285df80ff1207c0c1fe9e474120e44a7da1d6cc680af, e076dc58b469918cc5edb8813eff740951bad4f52e5a532b69271e62dd4604c2, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4171
+2df1baf2e452551a257fa2171e7e9e1f60c729b088dd70293c0e3ce9d1259fed, d3b9433e36f40ae4663ac9cac9e6086129b4b9a6d4c713cd431f3dd4574157aa, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4071
+950a839b99846d1a7a9d3abaa180636d785915bd85fbdad3565dab051f7dd889, dcb4544c304036f9c50a23a2a2677e29f4d401455f5ee714ce70ae2c40d964b8, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4104
+633fbdaa31e766e271591f8e6cf2a5e39087166cd48fdebdc3fd44f57c07fd04, 4f37c954c7a00bff07f402eca1fe37062f3b33342b8891c4d4564d0588239a84, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4013
+dba0248958481896f41ffe6d255636f65f311dfa518fb6ff7fb1dde7d7ba083c, fa909b9c1d0a7f4ef21142ee1b10a6fba3562dc83676c4e34862165650480ab0, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4031
+51493015b513ab69f8f44def0202a6783a6a33ba19c4a43ddd393696e6c6de09, d7946e834695f2e0ef98b7a34b720f70deab58add43e678634c2e6ac6b225f6a, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3952
+7e0923ab0d299c31ffe97ce203e48e816fbce737e7603057bc14698c50e454cb, 7e79180baf44db64e4e65f512b335bc16d8e214709d913455334962812e66ea5, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4119
+836253c93cfc7660d008e3ca8f5dbb4fb970daf0c91ed4fc99468d44e481cf7e, 9e3776397e6b4d06500f5d5f985bf3d6225d884677d0b802b906b8f37a10cea0, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4128
+204a5494b217b0fa69ed9c8580c28ea64055fc8c7f22082f574dc271f8ef83d4, f24f9d48d1ef5dd8b386d10e77010b2c73555e2a8020e82ff6c2680e4637bcd2, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4134
+ddc72b919c2359cd39b7939aa65a4ecd97fd399d66f2a1b78fbcab52c4ead007, 4020b0d9a331befeaf3964ac11435baa56827bf3c82bce6de0c0e6facad7f465, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3948
+49c4411d211cf2e58ef0eee11727922ca997ab48e1fced6afc73e67901f7fe4b, b4f5fc06c493a28645f74f49ad5931c4facef97821e752e27404a0f5a515c808, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4063
+1e9416b24af8a319b2559c37d46b3e8e2dd9fcdd688d9b0c9d87f5bbaedf988c, 6671e7215aa9b2e8bdafdd6c956e6c079469537c2714782992b660cbacb12aa6, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4294967295
+83e18b0f2174692da8efc778104fd8ad559a8877def7a78d19fc3e7703b6f908, 17dab68f4b8027af7edb6dbeb8ee92225398763cae2a06176034c26305b2d50c, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3961
+42a88e5f44f823ce807cc95980312f9a9064a0734064712eedb21d84227f5f8d, 6161140438c6001c2997a4614b4c02e71f45721eea5a0380b9b5a0f0a9369b67, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3926
+4f54d8fdfa317ee4a0132781e6923c850774fbdeffedb5a7cc32b4b3bba723a8, cbdfe8ac2cfd9ec657fbafe379a6fd31b8e62e4b39d8b5fcd325a6c01cf2c212, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3981
+d2dfcd4e82677b03cf1ca02f6ff518cff7b991a9ad139bf10dc3d4dc0a49a720, da69a84301141ca55e791ef6596eb8379f76db494d7186f64f1cd869ee537135, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3975
+2287e5cc8f335e611a972a4b5a2f88dc0973fafde8e43b0ad63418e8f12e7f6c, d9b558052a2435163012591e988cd0838d2e3aa7244c26a1b147c8a3d2998156, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3960
+bee65d4b28d44ea33146dea8b373eec6d5e2d8827d3ea92fff17425e05feec25, 75bc42813109f42075d132dd809867203a728477c80eb20c15a2842da431beee, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3946
+a3b1f532d43d656d7bed4b9d2ff8a44190fe3642d0228bf81a1aaef6c95fd2e2, 305b210d72143091c54d0bf96584aa928c61a75bba3e94d905b860bbeb509190, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4172
+54908b3b71a0f46a23125e35c2852497e42afd57413c8098eb286046676ea102, 261465d24c4e55ca775b4aa5626124de3dd222641d5baf8d95635b1683cf3212, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3994
+f9bb99de949e8f9c70fa8ae4ea0533fe4fd02c8172c97aa47c65f0a86a1a9733, 76550a5bf77ca1bccf693d7a36688a5e753eb459618dd4f80d5389e579204baf, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4038
+347963dadbe084b61bb4001be819e02668519ccd27b15d7743685e391ec30e28, 51a478d941a5629d3459e498d8cb6b0b95ac5bf3822aee799d40755ee1033e81, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4082
+37300bb5fbf3521a7247943ff8bed828c2db93fa837001eb0625883f2b30846e, 2eef924fc10970e83f06fef44bf919a9179f4dce6332b366e64993d6e8b6dc99, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4076
+2c84e6be8d6e60989311f0de9c2176b7172dec9b41717e5d8cdbce17b359e586, 987bcae10727dd07db008123363dcf05b648e3d3be585dbb750c4b4ce7b04a17, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4022
+a044257dba17f3f818ea7867dc8d2210272fdf9a6c49d7293dd2d6c8f74c38e0, e58f5d95d7634aa49c36a3af0bb639dfff50d56cc009fc6ad6966680d8b38432, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4058
+b863c20a85a4f855d09d6459fbbd44ee2675beb82589b88d891119e6ffaf9a22, 846f5192c83d0b83b04946cd656e7d22f5fc9279e738ed67802a1526fc9ada75, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4051
+8842676e4ef79dd65c96c8838490e0f71c5e0587fd472392f1c2568aa4b16d0e, 2350214ac3af8c12542a3f59c2c9ef563dfad32dc129880cb21314cac35801f6, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4002
+88ca5952066524cd9af9c4d452c0d40adedebdcd15be2cca4cfa7c25d8fed68e, 827a81e2f47c4b9d06e88a9c6c4b378505f5da182130e931976f786d9327f2d3, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4022
+e1bc3740297568ba64ce7e65c78e8e3910ec87bbcafb461257d4ba92da25e00f, 94dfb94f48cf22ba93783bd3fe767717a0fecf0be4b6f407764f24a99c906a72, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4054
+5c31de2265febcfa88b17f7cc4ac351bd0d55d1e27f8bb3114cdfb73929dd793, f779b1c36dc62d90f2d134c5f024c2fc9458dad86d2437c4b2adcae4c26ab8e9, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4096
+6d36632caef4b8005dd52f0cb2478c73782a8b769861c6a481617477c845c9c0, 2d4a99d7e6cc7e8b6dbdada85c673168c9ca18933e900860da05b45d84b45f48, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4011
+9765fb5c321cbb53694351e3d6b608ff4e91bccf85867e6f36b8a9b588d99daf, c9c12209c36e8ee50c4cb6271aa34deb8b19fe5947be9775fdca32d1ed11e3f4, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3982
+70ba464f9f4ae5cdbf84645ded1860f9040eb73d8f4217bc37fd5de96efc0e1f, a6b9fcdb4d4ce11c947b23dce9a72bd57cf5e39dd929b49c4353942014ceb689, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4030
+f35ab2abc229b23a84e747b56ba655aa80ec43f8123639de199b339ce0988afd, 763f7bb911922d08c3b21d6346b02ed124319262329774a1e1e9108ed9f36e3c, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4038
+ba3929fa4120a0e76bfb93a3d345169ead7d551ffbc067217b75d0da3d3239e9, c216ed9e232d0fa509a82bba3aa1fb910c77e2829ba403e2adb4388d4d778ef9, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3945
+0c94637c6ef6411516af889a492757b2125031c975fe6ac0e311e0354cf5343d, a7bc7631babeddd33aacac22fccd6eb8f97e1b6280fd5209d07df7ced41b20fa, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4217
+b6eaae827e70dc56049a0cf6b9714eda2a254f23de838a015b7f96b1aab466d4, a2dd155fdcf3480ad4e49e782d3ac61c152191c83bfb19f7e00a9951fe6e16f6, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4554
+c091ca2227954b5334e5485a8a0a4fb3b1cc9dcd3fe3d5b5cbdf5764962766e8, 10b88367348be5e3ff5f070f0cdb035f3edd0c571a18dd45b43bb41f43554ca4, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4419
+773bc69bbc4d0aba482e0c3f9da033d2099694ab1470cf01712db4f4c9c68041, bf02268b41216a96fe90692d60adb3cc3b35516f533e991530495f50ffdf9926, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4436
+2c648cb8f34052477c5b5c668a42ce9ec1d6f93b0a13fdac21a85ad391fb6b4a, 04083fc4261e3b3b6b5df0562394724fe906704d65ac8d8fdbb2231ede1eafcb, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4073
+8039cd218bb0968fc8408bc001cbb5a6fcb5816a0bc661830fc904bde80a1ae7, c59fd8b643fdbf435dd1f114467c9029dccfac89220f66c11d0bab9ac4a14792, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 3924
+d722442174e215ccdba594ac37c83abe81e07590ccc77b6dac24836e9f4ddfe4, ddd764aa3f1e1711ca8ffb23228d29a85a1d7b9c9202b08ee182a33c9c0f5246, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4050
+17d5a1fef4b69dc6a1aa176383a2a64a71a68036c20726b031034058ad430321, 36688dd1635866dc842651320845bf1b25ba17bb57ffe403e20c1342c3c08491, 8b12add89bc264e01038b61b784e0778a03cd3025e1faeedda6598f197ceccc0, 4060
diff --git a/test/score.cpp b/test/score.cpp
index 519210be..af534272 100644
--- a/test/score.cpp
+++ b/test/score.cpp
@@ -548,7 +548,7 @@ TEST(TestQubicScoreFunction, Bpp9000AntColonyRegression)
const unsigned char* pool = pools[chain.poolIndex].data();
score_engine::ScoreBpp9000::ANN parent;
- engine->deriveRootANN(chain.pubkey.m256i_u8, pool, parent); // depth 0's parent = the derived root
+ engine->deriveRootANN(uniqueSeeds[chain.poolIndex].m256i_u8, pool, parent); // depth 0's parent = the shared epoch root
for (size_t d = 0; d < chain.nodes.size(); ++d)
{
const AntNode& node = chain.nodes[d];
@@ -876,42 +876,53 @@ TEST(TestQubicScoreAntColony, AnnSurvivesExpandAndCompact)
EXPECT_EQ(memcmp(&restored, &original, sizeof(original)), 0) << "expand/compact is not lossless";
}
-TEST(TestQubicScoreAntColony, RootAnnIsDeterministicAndPerIdentity)
+// The root is shared per epoch: it is a function of the root seed (the epoch-start spectrum digest)
+// alone, so the same seed must give the identical root every time, and a different seed (a different
+// epoch) must give a different root.
+TEST(TestQubicScoreAntColony, RootAnnIsDeterministicAndShared)
{
AntFixture f;
ASSERT_TRUE(makeAntFixture(f));
- const m256i pkA = makePubkey(4);
- const m256i pkB = makePubkey(5);
+ const m256i seedA = makePubkey(4);
+ const m256i seedB = makePubkey(5);
AntEngine::ANN a1;
AntEngine::ANN a2;
AntEngine::ANN b1;
- f.engine->deriveRootANN(pkA.m256i_u8, f.pool.data(), a1);
- // Deriving another identity's root overwrites initValue.lutInit, which is the state a1 came from.
- f.engine->deriveRootANN(pkB.m256i_u8, f.pool.data(), b1);
- f.engine->deriveRootANN(pkA.m256i_u8, f.pool.data(), a2);
+ f.engine->deriveRootANN(seedA.m256i_u8, f.pool.data(), a1);
+ // Deriving another epoch's root overwrites initValue.lutInit, which is the state a1 came from.
+ f.engine->deriveRootANN(seedB.m256i_u8, f.pool.data(), b1);
+ f.engine->deriveRootANN(seedA.m256i_u8, f.pool.data(), a2);
EXPECT_EQ(memcmp(&a1, &a2, sizeof(a1)), 0) << "root depends on engine state";
- EXPECT_NE(memcmp(&a1, &b1, sizeof(a1)), 0) << "two identities share a root";
+ EXPECT_NE(memcmp(&a1, &b1, sizeof(a1)), 0) << "two different seeds share a root";
}
// A child is a function of (parent, pubkey, nonce, anchor). Same inputs must give the same score AND
-// the same inherited LUT; a different parent must not give the same child.
+// the same inherited LUT; a different parent must not give the same child. The root is shared, so
+// the second parent is a mutated child of the root rather than another identity's root.
TEST(TestQubicScoreAntColony, ChildIsDeterministicAndInheritsParent)
{
AntFixture f;
ASSERT_TRUE(makeAntFixture(f));
+ const m256i seed = makePubkey(4);
const m256i pk = makePubkey(6);
const m256i nonce = makeAntNonce(5, 3, 41);
const m256i anchor = makePubkey(12);
AntEngine::ANN parentA;
+ f.engine->deriveRootANN(seed.m256i_u8, f.pool.data(), parentA);
+
+ m256i improvingNonce;
+ ASSERT_TRUE(findImprovingNonce(*f.engine, parentA, pk, anchor, f.pool.data(), improvingNonce))
+ << "no nonce improved on the root, so no distinct second parent can be built";
+ f.engine->computeScoreFromParent(parentA, pk.m256i_u8, improvingNonce.m256i_u8, anchor.m256i_u8, f.pool.data());
AntEngine::ANN parentB;
- f.engine->deriveRootANN(pk.m256i_u8, f.pool.data(), parentA);
- f.engine->deriveRootANN(makePubkey(7).m256i_u8, f.pool.data(), parentB);
+ f.engine->getBestANN(parentB);
+ ASSERT_NE(memcmp(&parentA, &parentB, sizeof(parentA)), 0) << "the mutated child equals the root";
const unsigned int s1 = f.engine->computeScoreFromParent(
parentA, pk.m256i_u8, nonce.m256i_u8, anchor.m256i_u8, f.pool.data());
@@ -968,18 +979,17 @@ TEST(TestQubicScoreAntColony, NonCanonicalNonceIsRejected)
AntFixture f;
ASSERT_TRUE(makeAntFixture(f));
+ const m256i seed = makePubkey(4);
const m256i pk = makePubkey(10);
const m256i anchor = makePubkey(30);
- AntEngine::ANN parentA;
- AntEngine::ANN parentB;
- f.engine->deriveRootANN(pk.m256i_u8, f.pool.data(), parentA);
- f.engine->deriveRootANN(makePubkey(11).m256i_u8, f.pool.data(), parentB);
+ AntEngine::ANN parent;
+ f.engine->deriveRootANN(seed.m256i_u8, f.pool.data(), parent);
constexpr unsigned char maxK = (unsigned char)AntCfg::numberOfMutations;
const m256i good = makeAntNonce(3, 5, 62);
// A rejected nonce and a timed-out walk
- f.engine->computeScoreFromParent(parentA, pk.m256i_u8, good.m256i_u8, anchor.m256i_u8, f.pool.data());
+ f.engine->computeScoreFromParent(parent, pk.m256i_u8, good.m256i_u8, anchor.m256i_u8, f.pool.data());
AntEngine::ANN afterA;
f.engine->getBestANN(afterA);
@@ -995,7 +1005,7 @@ TEST(TestQubicScoreAntColony, NonCanonicalNonceIsRejected)
for (unsigned int i = 0; i < numberOfBadNonces; i++)
{
// The bad nonce is early rejected in computeScoreFromParent()
- EXPECT_EQ(f.engine->computeScoreFromParent(parentB, pk.m256i_u8, bad[i].m256i_u8, anchor.m256i_u8, f.pool.data()),
+ EXPECT_EQ(f.engine->computeScoreFromParent(parent, pk.m256i_u8, bad[i].m256i_u8, anchor.m256i_u8, f.pool.data()),
score_engine::INVALID_SCORE_VALUE) << "non-canonical nonce " << i << " accepted";
AntEngine::ANN now;
@@ -1003,8 +1013,11 @@ TEST(TestQubicScoreAntColony, NonCanonicalNonceIsRejected)
EXPECT_EQ(memcmp(&now, &afterA, sizeof(now)), 0) << "rejected nonce " << i << " still ran the walk";
}
- // Now after bad nonce, we feed good nonce, we expect this is ok
- f.engine->computeScoreFromParent(parentB, pk.m256i_u8, good.m256i_u8, anchor.m256i_u8, f.pool.data());
+ // Now after bad nonces, an improving canonical nonce on the same parent must move bestANN again
+ m256i good2;
+ ASSERT_TRUE(findImprovingNonce(*f.engine, parent, pk, anchor, f.pool.data(), good2))
+ << "no nonce improved on the parent, so the post-rejection walk check would be vacuous";
+ f.engine->computeScoreFromParent(parent, pk.m256i_u8, good2.m256i_u8, anchor.m256i_u8, f.pool.data());
AntEngine::ANN afterB;
f.engine->getBestANN(afterB);
EXPECT_NE(memcmp(&afterB, &afterA, sizeof(afterB)), 0) << "canonical nonce was not scored";