Andrius: I am trying to understand how kQuai gets updated.
Updating kQuai
Normalized difficulty {$d=\frac{minerDifficulty_i}{\log_2 (minerDifficulty_i)}$}
Target difficulty {$d^*$}: Simple average of adjusted difficulty over last 4000 blocks.
Exchange rate calculation {$kQuai_i = kQuai_{i-1}(1 + \frac{1}{1,000}(\frac{d^*}{d} - 1))$}
- #L67: func CalculateTokenChoicesSet(hc *HeaderChain, block, parent *types.WorkObject, exchangeRate *big.Int, etxs types.Transactions, actualConversionAmountInHash, realizedConversionAmountInHash *big.Int, minerDifficulty *big.Int) (types.TokenChoiceSet, error) {
- #L85: tokenChoices := types.TokenChoices{Quai: 0, Qi: 0, Diff: minerDifficulty}
- #L88: if types.IsCoinBaseTx(tx) {
- if tx.To().IsInQiLedgerScope() {
- tokenChoices.Qi++
- } else if tx.To().IsInQuaiLedgerScope() {
- tokenChoices.Quai++
- }
- if tx.To().IsInQiLedgerScope() {
- #L108: if realizedConversionAmountInHash.Cmp(common.Big0) != 0 && actualConversionAmountInHash.Cmp(common.Big0) != 0 {
- #L115 // diff (+-)= diff * alpha * (actualAmount - realizedAmount)/actualAmount
- diffErr := new(big.Int).Sub(actualConversionAmountInHash, realizedConversionAmountInHash)
- diffErr = new(big.Int).Mul(diffErr, tokenChoices.Diff)
- diffErr = new(big.Int).Div(diffErr, actualConversionAmountInHash)
- diffErr = new(big.Int).Div(diffErr, params.TokenDiffAlpha)
- if tokenChoices.Qi > tokenChoices.Quai {
- tokenChoices.Diff = new(big.Int).Add(tokenChoices.Diff, diffErr)
- } else { // If Quai choices are more than the Qi choices, then shift the difficulty to the left
- tokenChoices.Diff = new(big.Int).Sub(tokenChoices.Diff, diffErr)
- }
- Line 105 describes the target difficulty d*: x_b_star = -spaces[1]["Beta"][0] / spaces[1]["Beta"][1]
- The comment in CalculateQuaiReward documents the symbol: x_b_star = -spaces[1]["Beta"][0] / spaces[1]["Beta"][1] — i.e. take the negative of the first Beta coefficient divided by the second Beta coefficient (intercept and slope).
- Algebraically, if Beta[0] is β0 and Beta[1] is β1, then x_b_star = −β0/β1 solves β0 + β1·x = 0, so x_b_star is the x at which that linear function crosses zero.
- In the controller math this x_b_star is compared with x_d (d1/d2) and used to adjust k_quai via k_quai += α (x_b_star / x_d − 1) * k_quai, so x_b_star is the target/reference point that drives whether k_quai should increase or decrease.
- In practice the code computes and passes xbStar (newBeta0OverBeta1) from the exchange controller as diff/log(diff) (scaled by 2^64 fixed point) into CalculateKQuai, so xbStar represents the controller’s current beta-derived target used in the k_quai update.
- Example: if β0 = −10 and β1 = 2 then x_b_star = −(−10)/2 = 5 — the linear predictor crosses zero at x=5, which then informs the k_quai adjustment.
- params.KQuaiChangeBlock is the prime‑block height at which the protocol applies a scheduled kQuai change (a reset/reduction of the exchange rate). Its value is 756,000 and it’s used as the anchor in the KQuaiChangeTable to trigger the exchange‑rate adjustment; when currentBlock == KQuaiChangeBlock the code resets the exchange rate to the starting ExchangeRate (and subsequent entries in the table apply later reductions) 【】【】. The related KQuaiChangeHoldInterval defines a window after each change during which the exchange rate is held constant.
If Qi is preferred to Quai, then diffErr is added, which means difficulty is increased.
If Quai is preferred to Qi, then diffErr is subtracted, which means difficulty is decreased.
- func CalculateKQuai(parentExchangeRate *big.Int, minerDifficulty *big.Int, blockNumber uint64, xbStar *big.Int) *big.Int {
- func CalculateReward(header *types.WorkObjectHeader, difficulty *big.Int, exchangeRate *big.Int) *big.Int {
- func CalculateQuaiReward(difficulty *big.Int, exchangeRate *big.Int) *big.Int {
- func CalculateQiReward(header *types.WorkObjectHeader, difficulty *big.Int) *big.Int {
- func FindMinDenominations(reward *big.Int) map[uint8]uint64 {
- func ApplyCubicDiscount(valueInt, meanInt *big.Int) *big.Float {
- func QiToQuai(block *types.WorkObject, exchangeRate *big.Int, difficulty *big.Int, qiAmt *big.Int) *big.Int {
- func QuaiToQi(block *types.WorkObject, exchangeRate *big.Int, difficulty *big.Int, quaiAmt *big.Int) *big.Int {
- func ComputeConversionAmountInQuai(header *types.WorkObject, newInboundEtxs types.Transactions) *big.Int {
- func LogBig(diff *big.Int) *big.Int {
- func (sl *Slice) Append(
- Append takes a proposed header and constructs a local block and attempts to hierarchically append it to the block graph.
internal > quaiapi > quai_api.go
- func (s *PublicBlockChainQuaiAPI) CalculateConversionAmount(ctx context.Context, tx TransactionArgs) (*hexutil.Big, error) {
- CalculateConversionAmount returns the converted amount after applying the prime terminus exchange rate, and the conversion flow discount and k quai discount
- https://docs.qu.ai/learn/advanced-introduction/hierarchical-structure/hierarchical-structure
- zone block every 5 seconds, region block every 10 seconds, prime block every 20 seconds
How often is kQuai calculated?
- Quai is recomputed for each prime block during header processing — i.e., effectively every block (the exchange rate is recalculated by calling CalculateBetaFromMiningChoiceAndConversions, which in turn calls CalculateKQuai) when the node verifies/generates the parent header 【】【】.
- Important exceptions/notes:
- Before the controller is active the exchange rate is left unchanged: the controller only starts after ControllerKickInBlock + TokenChoiceSetSize, so early blocks don’t trigger updates (ControllerKickInBlock and TokenChoiceSetSize are protocol params) 【】.
- The code can use a stored (zone) exchange rate instead of recalculating when an update bit is unset (e.g., early/year‑one behavior) — see the storedExchangeRate/updateBit check in slice.go 【】.
- Scheduled kQuai table changes (KQuaiChangeTable) and the associated hold interval force resets/holds at specific block heights instead of ordinary recalculation 【】.
- So: normally recalculated every prime block, with controlled pauses/overrides per the params and change table.