Skip to main content

VM Cycle Limits

Cycle limits are used to regulate VM Scripts. ckb-vm

is a flexible VM that is free to implement many control flow constructs, such as loops or branches. As a result, we will need to enforce certain rules in CKB VM to prevent malicious scripts, such as a script with infinite loops.

We introduce a concept called cycles, each VM instruction or syscall will consume some amount of cycles. At consensus level, a scalar max_block_cycles field is defined so that the sum of cycles consumed by all scripts in a block cannot exceed this value. Otherwise, the block will be rejected. This way we can guarantee all scripts running in CKB VM will halt, or result in error state.

max_block_cycles

As mentioned above, a new scalar max_block_cycles field is added to chain spec as a consensus rule, it puts a hard limit on how many cycles a block's scripts can consume. No block can consume cycles larger than max_block_cycles.

The current max_block_cycles in CKB Mainnet MIRANA is 3_500_000_000.

Note there's no limit on the cycles for an individual transaction or a script. As long as the whole block consumes cycles less than max_block_cycles, a transaction or a script in that block are free to consume how many cycles they want.

Cycle Measures

Here we will specify the cycles needed by each CKB VM instructions or syscalls. Note right now in the RFC, we define hard rules for each instruction or syscall here, in future this might be moved into consensus rules so we can change them more easily.

The cycles consumed for each operation are determined based on the following rules:

  1. Cycles for RISC-V instructions are determined based on real hardware that implement RISC-V ISA.
  2. Cycles for syscalls are measured based on real runtime performance metrics obtained while benchmarking current CKB implementation.

Initial Loading Cycles

For each byte loaded into CKB VM in the initial ELF loading phase, 0.25 cycles will be charged. This is to encourage dapp developers to ship smaller smart contracts as well as preventing DDoS attacks using large binaries. Notice fractions will be rounded up here, so 30.25 cycles will become 31 cycles.

Instruction Cycles

All CKB VM instructions consume 1 cycle except the following ones:

InstructionCycles
JALR3
JAL3
J3
JR3
BEQ3
BNE3
BLT3
BGE3
BLTU3
BGEU3
BEQZ3
BNEZ3
LD2
SD2
LDSP2
SDSP2
LW3
LH3
LB3
LWU3
LHU3
LBU3
SW3
SH3
SB3
LWSP3
SWSP3
MUL5
MULW5
MULH5
MULHU5
MULHSU5
DIV32
DIVW32
DIVU32
DIVUW32
REM32
REMW32
REMU32
REMUW32
ECALL500 (see note below)
EBREAK500 (see note below)

Syscall Cycles

As shown in the above chart, each syscall will have 500 initial cycle consumptions. This is based on real performance metrics gathered benchmarking CKB implementation, certain bookkeeping logics are required for each syscall here.

In addition, for each byte loaded into CKB VM in the syscalls, 0.25 cycles will be charged. Notice fractions will also be rounded up here, so 30.25 cycles will become 31 cycles.

Guidelines

In general, the cycle consumption rules above follow certain guidelines:

  • Branches are more expensive than normal instructions.
  • Memory accesses are more expensive than normal instructions. Since CKB VM is a 64-bit system, loading 64-bit value directly will cost less cycle than loading smaller values.
  • Multiplication and divisions are much more expensive than normal instructions.
  • Syscalls include 2 parts: the bookkeeping part at first, and a plain memcpy phase. The first bookkeeping part includes quite complex logic, which should consume much more cycles. The memcpy part is quite cheap on modern hardware, hence less cycles will be charged.

Looking into the literature, the cycle consumption rules here resemble a lot like the performance metrics one can find in modern computer archtecture.

Other Cycles

B extension in CKB-VM version 1

We have added the RISC-V B extension (v1.0.0). This extension aims at covering the four major categories of bit manipulation: counting, extracting, inserting and swapping. For all B instructions, 1 cycle will be consumed.

MOP Fusion in CKB-VM version 1

Macro-Operation Fusion (also Macro-Op Fusion, MOP Fusion, or Macrofusion) is a hardware optimization technique found in many modern microarchitectures whereby a series of adjacent macro-operations are merged into a single macro-operation prior or during decoding. Those instructions are later decoded into fused-µOPs.

The cycle consumption of the merged instructions is the maximum cycle value of the two instructions before the merge. We have verified that the use of MOPs can lead to significant improvements in some encryption algorithms.

OpcodeOriginCycles
ADC [2]add + sltu + add + sltu + or1 + 0 + 0 + 0 + 0
SBBsub + sltu + sub + sltu + or1 + 0 + 0 + 0 + 0
WIDE_MULmulh + mul5 + 0
WIDE_MULUmulhu + mul5 + 0
WIDE_MULSUmulhsu + mul5 + 0
WIDE_DIVdiv + rem32 + 0
WIDE_DIVUdivu + remu32 + 0
FAR_JUMP_RELauipc + jalr0 + 3
FAR_JUMP_ABSlui + jalr0 + 3
LD_SIGN_EXTENDED_32_CONSTANTlui + addiw1 + 0

MOP Fusion in CKB-VM version 2

There are 5 MOPs added in VM version 2, there are:

OpcodeOriginCyclesDescription
ADCSadd + sltu1 + 0Overflowing addition
SBBSsub + sltu1 + 0Borrowing subtraction
ADD3Aadd + sltu + add1 + 0 + 0Overflowing addition and add the overflow flag to the third number
ADD3Badd + sltu + add1 + 0 + 0Similar to ADD3A but the registers order is different
ADD3Cadd + sltu + add1 + 0 + 0Similar to ADD3A but the registers order is different

Spawn Syscall in CKB-VM version 2

Two new Spawn-related constants for cycles consumption are introduced VM version 2:

pub const SPAWN_EXTRA_CYCLES_BASE: u64 = 100_000;
pub const SPAWN_YIELD_CYCLES_BASE: u64 = 800;

The Cycles consumption of each Spawn-related Syscall is as follows. Among them, the constant 500 and BYTES_TRANSFERRED_CYCLES can be referred to RFC-0014.

Syscall NameCycles Charge
spawn500 + SPAWN_YIELD_CYCLES_BASE + BYTES_TRANSFERRED_CYCLES + SPAWN_EXTRA_CYCLES_BASE
pipe500 + SPAWN_YIELD_CYCLES_BASE
inherited_fd500 + SPAWN_YIELD_CYCLES_BASE
read500 + SPAWN_YIELD_CYCLES_BASE + BYTES_TRANSFERRED_CYCLES
write500 + SPAWN_YIELD_CYCLES_BASE + BYTES_TRANSFERRED_CYCLES
close500 + SPAWN_YIELD_CYCLES_BASE
wait500 + SPAWN_YIELD_CYCLES_BASE
process_id500
load block extension500 + BYTES_TRANSFERRED_CYCLES

In addition, when a VM switches from instantiated to uninstantiated, or from uninstantiated to instantiated, each VM needs SPAWN_EXTRA_CYCLES_BASE cycles.