Consensus and Block Production
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,
PIX1111111111111111111111111111111114T1Anmin the API, is disabled and never scheduled (witness_objects.hpp:88). - Approval, since hardfork 30. A witness's approval is its
votesfield: 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
- 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).
- 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).
- 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_aslotcounts 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_missedadded 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 withp2p-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):
- When a witness node accepts a block less than 60 seconds old, it signs a
witness_block_approveoperation (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). - 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.
- 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:
- 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).
- 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).
- 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_scheduleshows the figure for the scheduled count ashardfork_required_witnesses. Upstream Hive keeps a fixed 17, which a chain with fewer than 17 witnesses can never reach. - 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). - 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.
katka7anduykodusanare 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 tokatka7,uykodusanandkatka7. - One key for two witnesses.
katka7andlukas123654registered 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, andparticipation_countwas 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
- Code, at tag
v1.30.1(commit7191f18, 2026-10-15). Paths are relative tolibraries/:- schedule:
chain/witness_schedule.cpp,chain/include/hive/chain/witness_schedule.hpp:15-39,witness_objects.hpp,witness_objects_multiindex.hpp:32-37 - slots, validation and irreversibility:
chain/database.cpp,chain/database_witness.cpp,chain/fork_database.cpp,chain/sync_block_writer.cpp,chain/blockchain_worker_thread_pool.cpp - hardforks:
chain/database_hardfork.cpp,protocol/hardfork.d/ - production:
plugins/witness/witness_plugin.cpp,plugins/witness/block_producer.cpp; skips:plugins/p2p/p2p_plugin.cpp:164,plugins/chain/chain_plugin.cpp - constants:
protocol/include/hive/protocol/config.hpp
- schedule:
- Witness configuration:
witnessrepository at commit704a68f,pixagram/config.ini. - Live chain, 2026-11-03,
api.pixagram.com:condenser_api.get_dynamic_global_properties,get_witness_schedule,get_witnesses_by_vote,database_api.list_witnessesanddatabase_api.get_hardfork_propertiesat block 1,722,917;condenser_api.get_ops_in_blockfor blocks 1,722,872 to 1,722,934, for one block a day from block 949,430, and by bisection to block 1,536,297;block_api.get_block_headerfor blocks 1 and 864,000;database_api.get_config. - Steem whitepaper, March 2016 edition, "Consensus in Steem", pp. 21-22.