Pixa
GitHub

Consensus and Block Production

Status: Live

hived 1.30.1, under hardfork 30. On 2026-11-03 at block 1,722,917, 21 witnesses were scheduled out of 22 eligible, all running 1.30.1; every one of the last 128 slots had a block, and the last irreversible block was the head block. Last checked 2026-11-03.

This page specifies how the Pixa chain agrees on a single history: which witness may sign each 3-second slot, what a node checks before it accepts a block, how it chooses between competing blocks, when a block becomes irreversible, and how a hardfork is switched on. It is written for node operators and developers who need the rules as the code applies them. Elections, voters and pay are on Witnesses and DPoS, the constants on Chain Parameters, and the path of a single transaction on Transaction Lifecycle.

At a glance

Rule Value Source
Slot 3 seconds, at most one block config.hpp:62
Round 21 slots: up to 20 elected witnesses and 1 timeshare witness; no mining slot config.hpp:222-228
Who can be scheduled a witness with a signing key and, since HF30, approval from at least 1% of all VESTS witness_schedule.cpp:155-176
Schedule rebuilt every 21 blocks, one round ahead, in shuffled order witness_schedule.cpp:497-530
Irreversible once all but a quarter (rounded down) of the next round's witnesses have approved the block or a block built on it: 16 of 21 database.cpp:3157-3178
Hardfork quorum 17 of 21 producing witnesses, scaled down in proportion when fewer produce witness_schedule.hpp:15-39

The round

The chain rebuilds its schedule every 21 blocks. When it applies a block whose number is a multiple of 21, it puts into force the schedule it built at the previous such block, then builds the next one (witness_schedule.cpp:497-530). The schedule that governs a round is therefore fixed 21 blocks before the round begins, a rule of Hive's hardfork 26 that Pixa has applied since block 1 (1_26.hf:23-25).

Who can be scheduled

  • A signing key. A witness whose registered signing key is the null key, PIX1111111111111111111111111111111114T1Anm in the API, is disabled and never scheduled (witness_objects.hpp:88).
  • Approval, since hardfork 30. A witness's approval is its votes field: the effective Pixa Power of the accounts voting for it (how votes count). Since hardfork 30 a witness is eligible only with approval of at least 1% of all VESTS, rounded up: 936,081.295484 VESTS at block 1,722,917 (witness_schedule.cpp:155-176, 1_30.hf:13).
  • A fallback. If no witness with a signing key reaches 1%, the floor is ignored and every witness with a key is eligible, so a chain without votes still has producers (witness_schedule.cpp:155-176).
  • Before hardfork 30 every witness with a signing key was eligible. While 21 or fewer had one, all of them were scheduled, whatever their approval.

Filling the slots

no

yes

yes, below the top 20

Registered witnesses

Signing key, and approval
of at least 1% of VESTS?

Not scheduled

Top 20 by approval:
elected

Earliest virtual time:
timeshare slot

Shuffled

3-second slots, in turn

Scroll sideways to see the whole diagram.Open full size
  1. Elected. The eligible witnesses with the most approval, ties broken by name, up to 20 (witness_schedule.cpp:177-189, witness_objects_multiindex.hpp:32-37).
  2. Mining. No slot. Steem launched with one slot for a proof-of-work miner. Its hardfork 17 (2017-03-30) gave that slot to a 20th elected witness, and Pixa applied hardfork 17 at block 1 (database_hardfork.cpp:282-296, config.hpp:222-228).
  3. Timeshare. The slots still open, one when 21 or more witnesses are eligible, go to eligible witnesses not yet chosen, in the order of their virtual scheduled time (witness_schedule.cpp:226-251). Each witness the queue passes has its virtual time moved forward by a fixed lap divided by its approval plus one (253-274). Among the runners-up, one with twice the approval of another therefore comes round about twice as often.

When fewer than 21 are eligible

All of them are scheduled. The chain checks that the schedule holds exactly the smaller of 21 and the number eligible, and at least one witness (witness_schedule.cpp:276-286). Slots go round the shorter list: the witness of absolute slot s is entry s mod n of the shuffled list of the n scheduled witnesses (database.cpp:944-950), so each signs about 21 ÷ n of every 21 blocks. Pixa ran this way from genesis, with 4 to 9 witnesses in daily samples up to hardfork 30, until block 1,536,297 (2026-10-27 21:21 UTC), when the schedule first held 21 (How a round is paid shows how to tell).

The shuffle

At each rebuild the scheduled witnesses are shuffled with a xorshift generator seeded from the timestamp of the block being applied (witness_schedule.cpp:461-480). Every node computes the same order. Steem's whitepaper gives the reason for shuffling: in a fixed order, a witness could keep ignoring the blocks of the witness placed before it (Steem whitepaper, March 2016 edition, "Consensus in Steem").

Censorship and its limits

  • One witness can delay, not exclude. A witness can leave a transaction out of its own blocks; the next slot belongs to another witness, which can include it (what witnesses can and cannot do).
  • Fewer ways around the elected witnesses. Steem's whitepaper offered three ways around censorship by the elected witnesses: wait for the timeshared slot, win the proof-of-work slot, or acquire more stake. It held that censoring is "a good way for elected witnesses to lose their job" (Steem whitepaper, March 2016 edition, "Consensus in Steem"). On Pixa the mining slot does not exist, and the timeshared slot is open only to witnesses above the 1% floor. What remains is stake: votes that replace the witnesses.
  • A majority can do more. Witnesses acting together can refuse whatever they choose, and no rule of the protocol prevents it. The remedies are votes and, in the last resort, a fork (Protocol Upgrades).

Slots

  • Length. A slot lasts 3 seconds (config.hpp:62). Slot times are multiples of 3 seconds counted from the Unix epoch, and a block belongs to the slot its timestamp falls in (database.cpp:952-983).
  • The absolute slot. current_aslot counts slots since genesis, 2026-09-04 12:00:00 UTC. Each block advances it by one, plus one for every empty slot before it (database.cpp:2984-2994). At block 1,722,917 (2026-11-03 08:53:12 UTC) it stood at 1,724,264, the time since genesis divided by 3 seconds, so 1,347 slots since genesis had no block.
  • The owner of a slot is the scheduled witness at that position in the shuffled list (database.cpp:944-950).

Producing a block

A node running the witness plugin signs a block only when all of these hold (witness_plugin.cpp:310-355, 469-525):

Condition Default Source
Caught up. Production starts once the next slot is not in the past, that is, once the node has synced. enable-stale-production = true skips this check. off witness_plugin.cpp:325-331, 593-607
Its turn. The slot belongs to a witness named in the node's witness setting. — 333-336
The right key. The node holds the private key of that witness's registered signing key (private-key). — 338-344
Participation. At least required-participation of the last 128 slots had a block, so that a node cut off on a minority fork stops producing. 33% 347-353, database.cpp:554-558
Timing. The plugin checks every 200 ms and produces when the clock, rounded to the second, reaches the slot time: from half a second before the slot. Half a second after it, it gives the slot up. — witness_plugin.cpp:31-33, 469-506

The witness repository's config.ini leaves enable-stale-production and required-participation at their defaults; Become a Witness explains why, and why the clock must be right to the millisecond.

What goes into the block (block_producer.cpp:31-85, 111-216):

  • Transactions. The producer re-applies its pending transactions in the order it received them. It leaves out any that has expired or now fails, and any that would bring the block to the voted maximum size; those still valid stay pending for a later block.
  • The header. It names the previous block, the slot time, the witness and the merkle root of the transactions (Data Model). Header extensions report the version the node runs and the hardfork it votes for, whenever these differ from what the chain has recorded for the witness (block_producer.cpp:87-109).
  • The signature. The header is signed with the block-signing key, which must be the one registered on chain (block_producer.cpp:31-85).

Empty slots

When a block arrives after empty slots, the chain charges each empty slot to the witness scheduled for it: its total_missed rises by one and a producer_missed virtual operation is recorded (database.cpp:2955-2975, database_witness.cpp:284-306). A slot is not charged to the witness that produces the arriving block, even when that witness was the one scheduled for it. The 128-bit window recent_slots_filled keeps the last 128 slots, 1 for a block, and participation_count counts the ones (database.cpp:2984-2994).

  • No automatic removal. From Steem's hardfork 14 (2016) to its hardfork 20 (2018), a witness that had produced nothing for a day lost its signing key. Pixa applied both hardforks at block 1, so that rule has never run: a witness that stops producing stays scheduled until its voters move or it changes its own key (database_witness.cpp:294-301).
  • Empty slots are not paid (How a round is paid).
  • The counters lag the slots. On 2026-11-03 the 23 registered witnesses' total_missed added up to 1,210, against 1,347 empty slots since genesis. Under the rule above, the difference is the empty slots that belonged to the producer of the next block.

Validating a block

Every node checks every block before it applies it. Work that needs no chain state runs first, on worker threads, as the block arrives; the rest runs in order against the state, in _apply_block (database.cpp:2271-2453).

Checks that need no state

  • The block. The node decompresses and decodes it, computes its id, recovers the public key that signed it from witness_signature, and computes the merkle root of its transactions (blockchain_worker_thread_pool.cpp:183-202).
  • Its transactions. The node runs each operation's validate(). A node that produces blocks, or one started with p2p-force-validate, also recovers each transaction's signing keys and the authorities it needs (283-320).

The results are cached; an error found here is raised when the block is applied.

Checks against the state

# Check Failure Source
1 The transactions' merkle root equals the header's transaction_merkle_root Merkle check failed database.cpp:2325-2350
2 previous is the id of the node's head block assertion database.cpp:2923-2945
3 The timestamp is later than the head block's assertion same
4 The key recovered from the signature is the named witness's registered signing key assertion same
5 The named witness is the one scheduled for the timestamp's slot Witness produced block at wrong time same
6 The uncompressed block is no larger than the voted maximum block size Block size is larger than voted on block size database.cpp:2355-2360
7 The witness runs at least the current hardfork version, as its blocks report Block produced by witness that is not running current hardfork database.cpp:2368-2378
8 Every transaction passes the node's checks, except the Resource Credit check the transaction's own error Transaction Lifecycle

Then the chain does the work of every block: the slot and participation counters, the producer's last block, irreversibility, the schedule (every 21 blocks), the price feed median, issuance, payouts and any hardfork due (database.cpp:2402-2444).

What a node may skip

When What is skipped Source
A block arrives over P2P at a node that does not produce blocks and was not started with p2p-force-validate The signatures and authorities of the transactions in it. The witness's signature on the block is still checked. p2p_plugin.cpp:164
The node replays its own block log, unless started with validate-during-replay Block and transaction signatures, authorities, TaPoS, the merkle root, the schedule, and operation validation chain_plugin.cpp:1253-1268
A block at or below a checkpoint the operator configured Most checks, but not the merkle root chain_plugin.cpp:1071-1085

A node that does not produce blocks therefore relies on the witnesses, which check every signature, for the authority of the transactions in the blocks it follows.

Forks and irreversibility

Choosing between forks

  • The fork database holds the blocks that are not yet irreversible. A new block is attached to its parent there, and the head is the highest block on any branch (fork_database.cpp:73-107).
  • Longest chain. A block that does not raise the head's number is stored and not applied. A block that makes another branch longer than the node's makes the node pop its blocks back to the common ancestor and apply the other branch (sync_block_writer.cpp:96-123); the transactions it pops go back to pending (Transaction Lifecycle).
  • The window. The fork database keeps only the blocks above the last irreversible block (sync_block_writer.cpp:59). A block below that window is refused as unlinkable, not as invalid, so the node fetches what it lacks from its peers instead of disconnecting them (fork_database.cpp:73-107).

The last irreversible block

The witnesses of the next round's schedule decide (database.cpp:3055-3061). For each of them the chain takes the highest block it has approved, by producing it or by a fast confirmation. It then walks each branch down from its tip and adds up the approvals; the first block reached with enough approvals becomes the last irreversible block (database.cpp:3120-3228, sync_block_writer.cpp:267-367).

  • How many. All but a quarter of the n scheduled witnesses, the quarter rounded down: n − ⌊n × 25%⌋ (database.cpp:3157-3178, config.hpp:438). With 21 scheduled, 16; with 8, 6.
  • Final means final. An irreversible block is written to the block log and its state committed; no fork can replace it (database.cpp:3230).
  • Until block 864,000 (2026-10-04 12:51:27 UTC), irreversibility trailed the head by a fixed 21 blocks, about 63 seconds. HIVE_START_MINER_VOTING_BLOCK, 30 days of blocks, is a rule from Steem's mining period that Pixa's code keeps (database.cpp:3126-3135, config.hpp:179).
  • A stall stops the chain. If six of 21 scheduled witnesses stop producing and confirming, blocks continue but none becomes irreversible. A node then refuses any block that would put the head 10,000 blocks, about 8 hours 20 minutes, past the last irreversible block (database.cpp:2997-3004, config.hpp:426). The absent witnesses' return, or votes that replace them in the schedule, restores progress.

Fast confirmation

Since Hive's hardfork 26, which Pixa has applied since block 1, a witness does not wait for its next slot to approve a block (1_26.hf:23-25):

  1. When a witness node accepts a block less than 60 seconds old, it signs a witness_block_approve operation (operation 16) for each witness it runs on the next round's schedule, except the block's own producer, whose block already counts. It signs with the block-signing key and broadcasts the transaction to its peers (witness_plugin.cpp:219-307, config.hpp:491).
  2. Every node counts the approval, ignoring witnesses not on that schedule and approvals older than the witness's last one, and recomputes the last irreversible block (database.cpp:3063-3118). An approval can make a node switch to the branch it confirms.
  3. The operation never enters a block: a block that contains one is invalid (hive_evaluator.cpp:693-697, Operations Reference).

On 2026-11-03 last_irreversible_block_num equalled head_block_number each time it was read, at blocks 1,722,718 and 1,722,917 (condenser_api.get_dynamic_global_properties): the head block was already irreversible when read.

Hardfork activation

The governance side is on Protocol Upgrades. The mechanics:

  1. Signalling. A block's header extensions carry the producer's running version and its hardfork vote, a version with a time. The chain records both on the witness (database_witness.cpp:219-269). The producer adds them automatically when its software knows a hardfork the chain has not applied (block_producer.cpp:87-109).
  2. Tally. At each rebuild the chain counts the scheduled witnesses that produced a block in the last 42 blocks, and the votes among them (witness_schedule.cpp:295-330). Votes for a hardfork already applied are ignored (396-438).
  3. Quorum. Pixa scales Hive's 17 of 21 to the witnesses producing: ⌈n × 17 ÷ 21⌉, at least 1 and at most n (witness_schedule.hpp:15-39, witness_schedule.cpp:361-364). With 21 producing, 17. get_witness_schedule shows the figure for the scheduled count as hardfork_required_witnesses. Upstream Hive keeps a fixed 17, which a chain with fewer than 17 witnesses can never reach.
  4. Activation. Once a quorum votes for the same version and time, the chain records it as next_hardfork. The hardfork applies in the first block whose timestamp is at or after that time (database_hardfork.cpp:126-149). If support falls below the quorum first, the next hardfork reverts to the current one (witness_schedule.cpp:396-438).
  5. Refusal. From then on, a block from a witness that reports an older version is invalid (database.cpp:2368-2378).

Numbering. Pixa's own hardforks are 29 and 30. Upstream Hive 1.28.8, merged into 1.30.1, brings a different hardfork 29; its features are compiled as hardfork 31, which the mainnet build cannot activate (HIVE_NUM_HARDFORKS is 30) (0-preamble.hf:15-22, 1_31.hf:1-34). A guard written for hardfork 30 stopped witness votes from bringing it forward of its compiled time (database_hardfork.cpp:126-149).

On 2026-11-03 no hardfork was pending: next_hardfork was 1.30.0, the current version (database_api.get_hardfork_properties). Of the 21 scheduled witnesses, 8 still carried their vote for 1.30.0 at 2026-10-07 12:00; the 13 registered after hardfork 30 carried the default vote, 0.0.0, which the tally ignores.

Parameters the witnesses set

Each witness publishes values for the account creation fee, the maximum block size and the two parameters of free account tickets, account_subsidy_budget and account_subsidy_decay. When it builds a schedule, the chain sorts the scheduled witnesses by each value and takes the middle one, entry ⌊n ÷ 2⌋ counted from zero, the 11th of 21 (witness_schedule.cpp:58-143). Since Hive's hardfork 28, the maximum block size takes effect when that schedule comes into force (witness_schedule.cpp:42-56, 497-530). Interest on PXS is set to 0 whatever a witness publishes (witness_schedule.cpp:58-143).

On 2026-11-03 all 21 scheduled witnesses published the same four values: 20.000 PIXA, 2,097,152 bytes, 797 and 347,321 (median_props in get_witness_schedule). What each value does is on Chain Parameters and Accounts and keys.

How a round is paid

Each block issues the witness share of new PIXA, as Pixa Power, to its producer (Issuance). How much a single block pays depends on the schedule (database.cpp:1823-1848):

Schedule One block pays Source
21 witnesses the share × 21 × the slot's weight ÷ 25. The weight is 1 for an elected witness and 5 for the timeshare witness, and 25 = 20 × 1 + 1 × 5. An elected block pays 0.84 times the share, the timeshare block 4.2 times; a round of 21 blocks pays exactly 21 shares. witness_schedule.cpp:455-459, witness_objects.hpp:191-193
Fewer than 21, since HF30 exactly the share 1_30.hf:18
Fewer than 21, before HF30 the share × 21 ÷ n, so the chain issued more than its nominal rate database.cpp:1823-1848

On chain. In blocks 1,722,872 to 1,722,934 (2026-11-03), each elected producer received 0.118999 VESTS and the timeshare producer 0.595999 VESTS (producer_reward in condenser_api.get_ops_in_block). Under the partial schedule after hardfork 30, the daily samples paid 0.140999 or 0.141999 VESTS a block. That difference dates the change: block 1,536,296 (2026-10-27 21:21:18 UTC) paid 0.141999 VESTS, block 1,536,297 paid 0.118999, and a daily sample has found the full rate every day since.

The schedule on 2026-11-03

Read at block 1,722,917 (get_witnesses_by_vote, get_witness_schedule), with approval as a share of all 93,608,129.548369 VESTS:

Rank Witness Approval Slot total_missed
1 rex 12.96% elected 27
2 blockforge 12.90% elected 252
3 boitata 12.90% elected 118
4 merlion 12.90% elected 172
5 nodeprime 12.90% elected 93
6 top.witness 12.89% elected 13
7 matus 4.90% elected 245
8 anti 4.86% elected 34
9 chainguard 4.86% elected 10
10 aliento 3.17% elected 4
11 initminer 3.04% none: null signing key 29
12 elmerlin 1.84% elected 0
13 whale 1.84% elected 11
14 juraj04 1.82% elected 56
15 cleesan 1.81% elected 0
16 arm.witness 1.80% elected 0
17 larry 1.80% elected 1
18 majso 1.80% elected 0
19 waynebockts 1.73% elected 2
20 stormwind 1.72% elected 17
21 lukas123654 1.72% elected 1
22 katka7 1.72% timeshare, in turn 1
23 uykodusan 1.71% timeshare, in turn 124
  • Two candidates for one timeshare slot. katka7 and uykodusan are the only eligible witnesses below the top 20, with almost equal approval, so they take the timeshare slot in turn. In the rounds starting at blocks 1,722,883, 1,722,904 and 1,722,925, it went to katka7, uykodusan and katka7.
  • One key for two witnesses. katka7 and lukas123654 registered the same block-signing key, PIX5ZjNEYoDqzErJBWhNsA9DW7NxLb3G3FBZymQagdASVkJaxJA5Y. The protocol allows it: whoever holds that key signs for both.
  • initminer, the genesis witness, has 3.04% approval but the null signing key, so it is skipped. Its last block was 970,717.
  • Versions. All 21 scheduled witnesses run 1.30.1, the majority_version. Every slot in the 63 blocks read had its block, and participation_count was 128 of 128.
  • Missed slots are counted since each witness registered, under the rule in Empty slots; they include periods when fewer witnesses shared the slots.

Read it yourself

Shellrpc() { curl -s -X POST https://api.pixagram.com -H 'Content-Type: application/json' \
  -d "{\"jsonrpc\":\"2.0\",\"method\":\"$1\",\"params\":$2,\"id\":1}"; }

rpc condenser_api.get_dynamic_global_properties '[]'   # head, last irreversible block, current_aslot, participation_count
rpc condenser_api.get_witness_schedule '[]'            # the round: current_shuffled_witnesses, weights, median_props, quorum
rpc condenser_api.get_witnesses_by_vote '["",30]'      # approval (votes), signing key, total_missed, versions and votes
rpc database_api.get_hardfork_properties '{}'          # last hardfork, next_hardfork and its time
rpc block_api.get_block_header '{"block_num":1722917}' # producer, timestamp, header extensions
rpc condenser_api.get_ops_in_block '[1722910,true]'    # producer_reward and any producer_missed

The share of empty slots since genesis is current_aslot minus head_block_number; the round's weights are elected_weight, timeshare_weight and witness_pay_normalization_factor.

Inherited → changed

Aspect Steem / Hive Pixa
Slots per round Steem at launch: 19 elected, 1 proof-of-work miner, 1 timeshared; since Steem's HF17 (2017): 20 elected, 1 timeshared 20 elected, 1 timeshared, from block 1
Who can be scheduled any witness with a signing key also approval of at least 1% of all VESTS, since HF30
Slot 3 s same
Schedule fixed one round ahead Hive, since HF26 (2022) same, from block 1
Irreversibility Steem: the block 75% of the scheduled witnesses have built on; Hive since HF26: fast confirmations as well same as Hive; a fixed 21-block lag until block 864,000
Hardfork quorum 17 of 21 ⌈17/21⌉ of the witnesses producing; 17 with 21
Witness that stops producing Steem HF14 to HF20: signing key removed after a day never removed
Pay per block 21 blocks' worth per round; the timeshare slot weighs 5 same with 21 scheduled; with fewer, the nominal share since HF30
Transaction signatures in P2P blocks, on nodes that do not produce not checked unless p2p-force-validate same
Upstream Hive's HF29 Hive 1.28.8 compiled as HF31, not activatable on mainnet

Sources