Qubic Core

repository·main·Indexed 41 days ago

https://github.com/qubic/core

Source code for a full Qubic node designed to run as an EFI application on bare-metal hardware. Supports Listening and Computor Nodes, featuring documentation on hardware prerequisites, UEFI deployment, and the Qubic Programming Interface (QPI) for smart contract development. Includes details on the Ant Colony mining structure and the bpp9000 algorithm.

Tokens
36.8K
Snippets
63
Records
129
Agent score
52%

What's inside Qubic

  1. How Oracles work in Qubic Smart Contracts

    main

    Oracles allow Smart Contracts to query off-chain data via the QPI (Qubic Programming Interface).

    The Architecture:

    1. Core Nodes: Run the smart contracts and initiate queries.
    2. Oracle Machine (OM) Nodes: Act as a bridge between Core nodes and external Oracles. They process queries asynchronously.
    3. Oracles: Independent external data sources that provide information to OM nodes.
    4. Quorum: Once an OM node receives a reply, Core nodes must reach a quorum of 451 agreeing reply commits before the data is confirmed on-chain.

    Fees: Queries are subject to fees to prevent spam. These fees are destroyed (not added to the execution reserve).

  2. Understand Oracle Interfaces in Qubic

    main

    An Oracle Interface defines the input and output types for the Oracle Machine (OM). It acts as a contract between the Oracle Machine and the Qubic contracts that query it.

    Key components of an interface:

    • OracleQuery: A struct defining the input parameters (e.g., which oracle to ask, what timestamp, what specific data like a currency pair).
    • OracleReply: A struct defining the output data provided by the oracle.
    • OI Namespace: All oracle interfaces are accessed within contracts via the OI namespace. For example, if an interface is named Price, you access its types via OI::Price::OracleQuery.

    To ensure consensus, an OracleQuery must be deterministic: the same query parameters must always result in the exact same OracleReply so that a quorum of computors can agree on the result.

  3. Implement a MIGRATE procedure for state migration

    main

    To perform a state migration, your contract must include the following:

    1. OldStateData struct: A nested struct within the contract struct that has the exact same layout as the previous StateData. If constants used for state variables have changed, keep the old values using a naming convention like <constant_name>_OLD within this struct.
    2. MIGRATE or MIGRATE_WITH_LOCALS procedure: This procedure has access to:
      • const QPI::QpiContextFunctionCall& qpi: The QPI context.
      • QPI::ContractState<StateData>& state: The new state struct.
      • const OldStateData& oldState: The previous state data.
      • MIGRATE_locals& locals: Local variables (if using MIGRATE_WITH_LOCALS).

    Restriction: MIGRATE procedures are prohibited from using any QPI functions that change the universe or spectrum.

  4. Use QPI context in functions and procedures

    main

    The qpi object provides access to the QPI (Qubic Programming Interface) within functions (QpiContextFunctionCall) and procedures (QpiContextProcedureCall).

    Common QPI Methods

    • qpi.epoch(): Returns the current epoch.
    • qpi.tick(): Returns the current tick.
    • qpi.getEntity(): Returns info (like incomingAmount and outgoingAmount) for an entity ID (32-byte public key). Use SELF for the contract's own ID.
    • numberOfShares(): Returns the number of shares for an asset.
    • qpi.invocator(): Returns the ID of the entity that invoked the procedure (source user for transactions, caller contract for inter-contract calls).
    • qpi.originator(): Returns the ID of the user who signed the transaction.
    • qpi.invocationReward(): Returns the QU amount transferred to the contract by the invoker.

    Procedure-Specific QPI Methods

    • qpi.transfer(): Transfers QUs from this contract to another entity.
    • qpi.burn(): Burns QUs owned by this contract.
    • qpi.issueAsset(): Issues a new asset.
    • qpi.transferShareOwnershipAndPossession(): Transfers asset shares.
  5. How bpp9000 mining works

    main

    Under the bpp9000 algorithm, mining is a search for a neural network (an "ANN") that minimizes an error count over the task's data windows.

    • Solution: A neural network where the wiring (the graph of which neuron reads which) is global and defined by the epoch's task file.
    • Search Space: Miners search over each neuron's lookup table (LUT), which computes a ternary function.
    • Scoring: Solutions are scored by an error count in the range [0, 8088].
    • Goal: Lower is better. A flawless network has an error count of zero.

    To mine, a worker picks a parent (either the virtual root or an existing node in the identity's tree), inherits it, varies it using a nonce, and scores the result.

  6. Configure a canonical nonce for bpp9000

    main

    A nonce must be exactly 32 bytes and follow the canonical rule to avoid RejectNonCanonicalNonce. For the bpp9000 algorithm, the first 3 bytes (nonce[0..2]) are used as configuration knobs and are zeroed out before hashing to ensure they do not affect the RNG seed.

    Nonce Structure:

    • nonce[0]: Algorithm selector (must be bpp9000).
    • nonce[1]: L (LUT entries rewritten per mutation step). Valid range: [1, 10].
    • nonce[2]: K (number of explore steps). Valid range: [0, 100].
    • nonce[3..31]: The walk seed (actual search space).

    Canonical Rule: algo == bpp9000 && nonce[1] in [1, 10] && nonce[2] in [0, 100]

    algo == bpp9000  &&  nonce[1] in [1, 10]  &&  nonce[2] in [0, 100]
  7. How state management works in Qubic contracts

    main

    The state is the persistent memory of a contract that remains synchronized across all nodes. To manage state, you must declare all persistent fields inside a nested struct StateData within your contract implementation.

    Accessing state is handled through two specific methods:

    • state.get(): Used for read-only access to the state.
    • state.mut(): Used for read-write access. Calling state.mut() marks the state as 'dirty', which triggers a recomputation of its digest at the end of the tick.

    Note: The maximum contract state size is currently capped at 1 GiB.

    struct StateData {
        uint64 my_persistent_value;
    };
    
    // Example usage:
    // uint64 val = state.get().my_persistent_value;
    // state.mut().my_persistent_value = 100;
  8. How to finalize state variables after a proposal is accepted

    main

    The IMPLEMENT_FinalizeShareholderStateVarProposals macro provides a template for the END_EPOCH procedure. It iterates through all proposals that were open for voting in the current epoch, checks if they were accepted, and then calls a user-defined procedure to apply the changes to your contract's state.

    For yes/no proposals, use the default implementation. For complex multi-option or scalar voting, refer to the TestExampleB contract pattern.

    IMPLEMENT_FinalizeShareholderStateVarProposals()
    {
        // your code for setting state variable, which is called by FinalizeShareholderStateVarProposals()
        // after a proposal has been accepted
    }
  9. How Seamless Epoch Transition works

    main

    Since Epoch 102, Qubic supports Seamless Epoch Transition, allowing nodes to transition between epochs without manual restarts or updates every Wednesday.

    Transition behavior depends on the node's configuration mode:

    Transition ModeDescription
    MAIN/MAINThe same node continues in MAIN mode into the next epoch. This is the most seamless option—no restart or update needed.
    AUX/MAINThe node was in AUX mode during the previous epoch and becomes MAIN in the new one. Allows for smooth upgrading or switching nodes across epochs.
    MAIN/AUXThe node downgrades to AUX mode, usually for backup or updating purposes.
    AUX/AUXPassive mode used for ticking without taking part in consensus.

    ⚠️ Important: Only one node per computor ID should run in MAIN mode at any given time. Running multiple MAIN nodes with the same ID will result in faulty behavior.

    Ticking Lifecycle: Ticking in a new epoch begins only after the arbitrator sends out the new computor list (the "getting index" step). A node in MAIN mode will start ticking automatically once a quorum of 451 votes is reached.

  10. Configure the Qubic Disk File Structure

    main

    Your boot disk must follow a specific directory and file structure. You must download the latest spectrum, universe, and contract files from https://storage.qubic.li/network and place them on the disk. Replace XXX or YYYY with the current epoch (e.g., 114).

    Required Structure:

    • /contract0000.XXX: Current contract #0 file.
    • /contract0001.XXX: Current contract #1 file (Data from Qx).
    • /contractYYYY.XXX: Current contract #YYYY file (State data from all contracts).
    • /spectrum.XXX: Current spectrum file.
    • /universe.XXX: Current universe file.
    • /system: An empty file used to start from scratch (e.g., touch system).
    • /efi/boot/Bootx64.efi: The boot loader.
    • /efi/boot/startup.nsh: The UEFI start script.
    • /efi/boot/Qubic.efi: The compiled Qubic node EFI executable.
  11. Difference between Functions and Procedures in Qubic contracts

    main

    Qubic contracts distinguish between Functions and Procedures based on their ability to modify state and how they are invoked:

    FeatureFunctionsProcedures
    State ModificationCannot change contract stateCan change contract state
    Invocation MethodVia RequestContractFunction network messageVia a transaction (runs during the tick processing)
    CostN/AUsually involves a transfer of an invocation reward from the user to the contract

    Rules for calling other entities:

    • Procedures can call procedures and functions of the same contract, or contracts with a lower contract index.
    • Functions can call functions of the same contract, or contracts with a lower contract ID.
    • Private functions/procedures cannot be called from external contracts.
    • Registration: To make any function or procedure available for invocation, you must register them in the special member function REGISTER_USER_FUNCTIONS_AND_PROCEDURES.
  12. Customizing Hash Functions for HashMap and HashSet

    main

    You can define how keys are hashed for HashMap and HashSet using two methods:

    1. Template Specialization: Define a specialization of the HashFunction template class (found in qpi.h) for your specific KeyT.
      • Note: The default implementation for the 32-byte id type uses the first 8 bytes as the hash. Other types default to a K12 hash.
    2. Custom Class Parameter: Define your own hash function class for the key type and pass it as the final template parameter of the HashMap or HashSet, following the capacity L parameter.