Andrius: I am trying to understand how kQuai gets updated.
Updating kQuai
func CalculateBetaFromMiningChoiceAndConversions (in core/exchange_controller.go) calls misc.CalculateKQuai (in consensus/misc/rewards.go) to get the exchange rate, which it returns.
- The exchange rate is changed only after there have been enough miner token choices.
- params.ControllerKickInBlock = 262,000
- params.TokenChoiceSetSize = 4,000
- params.KQuaiChangeTable is an array with four entries consisting of a block number and a percentage of KQuai value left after reduction
- 756,000 → 50% Note that 756,000 is also the value of params.KQuaiChangeBlock
- 1,015,200 → 75%
- 1,274,400 → 75%
- 1,533,600 → 75%
- params.KQuaiChangeHoldInterval = 20,000 prime blocks, about 6 to 7 days worth
- params.ExchangeRate is a large integer which, in decimal form, signifies 0.22, the number of Qi per Quai. This is the initial exchange rate.
- Subsequently, at the block numbers given by KQuaiChangeTable, the exchange rate is multiplied by 75%, yielding 0.165. This value is held for the subsequent 4,000 blocks.
func CalculateTokenChoicesSet in core > exchange_controller.go
- returns NewTokenChoiceSet
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 {
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
- Current value of blockNumber is 4,484,863. A value greater than 756,000 = params.KQuaiChangeBlock allows the kQuai increase to be slowed down, dividing it by 3.
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.
- func CalculateTokenChoicesSet
- Difficulty is adjusted based on relative preference of miners/speculators between Quai and Qi. In the case of if Quai is chosen more than Qi, the difficulty (miner difficulty) is shifted downwards and in the case of Qi being greater than Quai the difficulty is is shifted upwards.
rewards.go
CalculateKQuai
if kQuai is increasing, slow it down by dividing by 3.
Andrius: @quAI please calculate the percentage deducted (for conversions from Quai to Qi) for the following blocks: 1000000, 2000000, 3000000, 3500000, 4000000.
I fetched each block header and read header.kQuaiDiscount (stored as an integer where 100000 = 100%). The kQuaiDiscount is the per‑header % subtracted (after any flow/cubic discount). Results:
- Block 1,000,000 — kQuaiDiscount = 0x64 = 100 → discount = 100/100000 = 0.100% → retained = 99.900%
- Block 2,000,000 — kQuaiDiscount = 0xf51 = 3,921 → discount = 3.921% → retained = 96.079%
- Block 3,000,000 — kQuaiDiscount = 0x178d = 6,029 → discount = 6.029% → retained = 93.971%
- Block 3,500,000 — kQuaiDiscount = 0x526f = 21,103 → discount = 21.103% → retained = 78.897%
- Block 4,000,000 — kQuaiDiscount = 0x7f0 = 2,032 → discount = 2.032% → retained = 97.968%
Note: these percentages are only the kQuaiDiscount component from the header. Total % deducted for a Quai→Qi conversion = (flow/cubic conversion discount for that ETX) + (kQuaiDiscount percent applied to the post‑flow amount). To get total realized deduction for a specific conversion you must supply the conversion amount (or the ETX) so I can apply the flow math or call the node’s conversion calculator against that historic header.