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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/contract-verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,6 @@ jobs:
echo "oi-filepaths=$files2" >> "$GITHUB_OUTPUT"
- name: Contract verify action step
id: verify
uses: Franziska-Mueller/qubic-contract-verify@main
uses: qubic/contract-verify@970ce102d56df53b68f1b8fa65b2dd445d5c9d81 # main
with:
filepaths: '${{ steps.filepaths.outputs.contract-filepaths }},${{ steps.filepaths.outputs.oi-filepaths }}'
Binary file modified data/bpp9000.task
Binary file not shown.
55 changes: 30 additions & 25 deletions doc/ant_colony_mining.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
|
+----+----+
| |
Expand All @@ -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.
Expand All @@ -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.

Expand All @@ -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
Expand All @@ -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).
Expand Down Expand Up @@ -153,17 +154,20 @@ 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[3..31] || anchorTickDigest)` (`nonce[0..2]` zeroed) -
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
Expand Down Expand Up @@ -201,14 +205,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
Expand Down Expand Up @@ -254,7 +259,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
Expand Down Expand Up @@ -301,7 +306,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
Expand Down Expand Up @@ -364,7 +369,7 @@ 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];
```

Expand Down
2 changes: 1 addition & 1 deletion doc/contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ In order to develop a contract, follow these steps:
## Review and tests

Each contract must be validated with the following steps:
1. The contract is verified with the [Qubic Contract Verification Tool](https://github.com/Franziska-Mueller/qubic-contract-verify), ensuring that it complies with the formal requirements mentioned above, such as no use of [forbidden C++ features](#restrictions-of-c-language-features).
1. The contract is verified with the [Qubic Contract Verification Tool](https://github.com/qubic/contract-verify), ensuring that it complies with the formal requirements mentioned above, such as no use of [forbidden C++ features](#restrictions-of-c-language-features).
In the `qubic/core` repository, the tool is run automatically as GitHub workflow for PRs to the `develop` and `main` branches (as well as for commits in these branches).
However, since workflow runs on PRs require maintainer approval, we highly recommend to either build the tool from source or use the GitHub action provided in the tool's repository to analyze your contract header file before opening your PR.
2. The features of the contract have to be extensively tested with automated tests implemented within the Qubic Core's GoogleTest framework.
Expand Down
2 changes: 1 addition & 1 deletion doc/contracts_oracles.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ static sint64 getSubscriptionFee(const OracleQuery& query, uint32 notifyPeriodIn
Additionally, the interface struct may contain other structs or convenience features for contracts using the oracle interface.

All code in the interface header file must respect the same [C++ language feature restrictions as contracts](#restrictions-of-c-language-features) (except for a few differences listed below).
These are checked with the [Qubic Contract Verification Tool](https://github.com/Franziska-Mueller/qubic-contract-verify).
These are checked with the [Qubic Contract Verification Tool](https://github.com/qubic/contract-verify).

The C++ language feature restrictions of oracle interfaces differ in the following points from those of contracts:

Expand Down
2 changes: 0 additions & 2 deletions src/Qubic.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
<ClInclude Include="contracts\Qdraw.h" />
<ClInclude Include="contracts\qRWA.h" />
<ClInclude Include="contracts\Qswap.h" />
<ClInclude Include="contracts\Qswap_old.h" />
<ClInclude Include="contracts\RandomLottery.h" />
<ClInclude Include="contracts\QDuel.h" />
<ClInclude Include="contracts\SupplyWatcher.h" />
Expand All @@ -44,7 +43,6 @@
<ClInclude Include="contracts\QBAY.h" />
<ClInclude Include="contracts\Nostromo.h" />
<ClInclude Include="contracts\QRaffle.h" />
<ClInclude Include="contracts\QRaffle_old.h" />
<ClInclude Include="contracts\QBond.h" />
<ClInclude Include="contracts\QIP.h" />
<ClInclude Include="contracts\Qusino.h" />
Expand Down
6 changes: 0 additions & 6 deletions src/Qubic.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,6 @@
<ClInclude Include="contracts\QRaffle.h">
<Filter>contracts</Filter>
</ClInclude>
<ClInclude Include="contracts\QRaffle_old.h">
<Filter>contracts</Filter>
</ClInclude>
<ClInclude Include="contracts\QBond.h">
<Filter>contracts</Filter>
</ClInclude>
Expand Down Expand Up @@ -245,9 +242,6 @@
<ClInclude Include="contracts\Qswap.h">
<Filter>contracts</Filter>
</ClInclude>
<ClInclude Include="contracts\Qswap_old.h">
<Filter>contracts</Filter>
</ClInclude>
<ClInclude Include="platform\uint128.h">
<Filter>platform</Filter>
</ClInclude>
Expand Down
10 changes: 1 addition & 9 deletions src/contract_core/contract_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -209,11 +205,7 @@
#define CONTRACT_INDEX QRAFFLE_CONTRACT_INDEX
#define CONTRACT_STATE_TYPE QRAFFLE
#define CONTRACT_STATE2_TYPE QRAFFLE2
#ifdef OLD_QRAFFLE
#include "contracts/QRaffle_old.h"
#else
#include "contracts/QRaffle.h"
#endif

#undef CONTRACT_INDEX
#undef CONTRACT_STATE_TYPE
Expand Down Expand Up @@ -584,7 +576,7 @@ struct ContractStateChangeInfo
// When enabling, replace both lines below, e.g.:
//constexpr ContractStateChangeInfo contractStateChangeInfos[] = { { DUMMY_CONTRACT_INDEX, MIGRATE, 219 } };
//constexpr unsigned int contractStateChangeCount = sizeof(contractStateChangeInfos) / sizeof(contractStateChangeInfos[0]);
constexpr ContractStateChangeInfo contractStateChangeInfos[] = { { QIP_CONTRACT_INDEX, RESET, 224 }, { RANDOM_CONTRACT_INDEX, PADDING, 224 } };
constexpr ContractStateChangeInfo contractStateChangeInfos[] = { { QIP_CONTRACT_INDEX, RESET, 224 }, { RANDOM_CONTRACT_INDEX, PADDING, 224 }, {NOST_CONTRACT_INDEX, MIGRATE, 229}};
constexpr unsigned int contractStateChangeCount = sizeof(contractStateChangeInfos) / sizeof(contractStateChangeInfos[0]);


Expand Down
Loading
Loading