Перейти к содержанию

Жизненный цикл Валидатора

Этапы жизни валидатора

1. Создание

Триггер: Транзакция STAKE с достаточной суммой

Условия:

  • tx.amount >= min_validator_stake
  • pub_key присутствует в payload
  • Баланс отправителя достаточен

Что происходит:

# В State.apply_transaction
val_addr = address_from_pubkey(pub_key_bytes, prefix="cpcvalcons")
val = Validator(
    address=val_addr,
    pq_pub_key=pub_key_hex,
    power=tx.amount,
    is_active=False,  # Еще не активен!
    reward_address=tx.from_address
)

Статус: is_active = False

2. Ожидание (Pending)

Период: До следующей эпохи

Что происходит:

  • Валидатор создан, но не участвует в консенсусе
  • Не может производить блоки
  • Не получает награды

Проверка:

./cpc-cli query validators --node http://localhost:8000

Вывод:

{
  "epoch": 1,
  "validators": [
    {
      "address": "cpcvalcons1...",
      "power": "1500000000000000000000",
      "is_active": false,  // ← Еще не активен
      "reward_address": "cpc1..."
    }
  ]
}

3. Активация

Триггер: Переход эпохи (каждые N блоков)

Процесс:

  1. Собрать всех валидаторов:

    all_validators = state.get_all_validators()
    

  2. Отфильтровать по минимальному стейку:

    active_candidates = [v for v in all_validators if v.power >= min_validator_stake]
    

  3. Сортировать по стейку (по убыванию):

    sorted_validators = sorted(active_candidates, key=lambda v: v.power, reverse=True)
    

  4. Выбрать топ-N:

    top_validators = sorted_validators[:max_validators]
    

  5. Активировать:

    for val in top_validators:
        val.is_active = True
    

  6. Деактивировать остальных:

    for val in all_validators:
        if val not in top_validators:
            val.is_active = False
    

Статус: is_active = True

4. Активная валидация

Период: Пока валидатор в топ-N

Что происходит:

  • Участвует в консенсусе (Round-Robin)
  • Может производить блоки в свой слот
  • Получает награды за блоки и комиссии

Round-Robin:

def get_proposer(height: int) -> Validator:
    active_validators = [v for v in validators if v.is_active]
    index = height % len(active_validators)
    return active_validators[index]

Пример:

Блок 10: Валидатор A (index 0)
Блок 11: Валидатор B (index 1)
Блок 12: Валидатор C (index 2)
Блок 13: Валидатор A (index 0)  # Цикл повторяется

5. Деактивация

Причины:

  1. Недостаточный стейк:
  2. Валидатор упал ниже min_validator_stake
  3. Или другие валидаторы обогнали по стейку

  4. Вытеснение из топ-N:

  5. Появились валидаторы с большим стейком
  6. Валидатор выпал из топ-N

Что происходит:

val.is_active = False

Последствия:

  • Перестает производить блоки
  • Не получает награды
  • Может быть реактивирован в следующей эпохе (если вернется в топ-N)

6. Ре-стейкинг (Добавление стейка)

Триггер: Новая транзакция STAKE с тем же pub_key

Что происходит:

val = get_validator(val_addr)
if val:
    val.power += tx.amount  # Добавляется к существующему стейку

Результат:

  • Повышение позиции в рейтинге
  • Больше шансов остаться в топ-N
  • Больше наград (если активен)

Эпоха

Определение

Эпоха — период между пересчетами набора валидаторов.

Длительность:

Сеть Блоки Время (примерно)
Devnet 10 ~100 сек
Testnet 100 ~50 мин
Mainnet 72 ~72 мин

Переход эпохи

Триггер:

if (block.header.height + 1) % config.epoch_length_blocks == 0:
    _update_consensus_from_state()

Что происходит:

  1. Пересчет набора валидаторов
  2. Активация/деактивация
  3. Обновление ConsensusEngine.validator_set

Примеры сценариев

Сценарий 1: Становление валидатором

Блок 0:  Alice отправляет STAKE(1500 CPC)
Блок 1:  Транзакция включена, валидатор создан (is_active=False)
Блок 2-9: Ожидание эпохи
Блок 10: Эпоха! Alice активируется (is_active=True)
Блок 11: Alice производит первый блок (Round-Robin)

Сценарий 2: Вытеснение из Топ-N

Эпоха 1: Топ-5: [A:2000, B:1800, C:1500, D:1200, E:1000]
         Alice (1500) — активна

Эпоха 2: Топ-5: [A:2000, B:1800, C:1500, F:1400, D:1200]
         Alice (1500) — все еще активна

Эпоха 3: Топ-5: [A:2000, B:1800, G:1600, C:1500, F:1400]
         Alice (1500) — деактивирована (вытеснена G)

Сценарий 3: Возвращение в Топ-N

Эпоха 1: Alice деактивирована (6-я по стейку)
Эпоха 1: Alice отправляет STAKE(+500 CPC)
Эпоха 2: Alice возвращается в топ-5, активируется

Мониторинг

Проверка статуса валидатора

./cpc-cli query validators --node http://localhost:8000

Вывод:

{
  "epoch": 2,
  "validators": [
    {
      "address": "cpcvalcons1alice...",
      "pq_pub_key": "02a1b2c3...",
      "power": "1500000000000000000000",
      "is_active": true,
      "reward_address": "cpc1alice..."
    },
    {
      "address": "cpcvalcons1bob...",
      "pq_pub_key": "02b2c3d4...",
      "power": "1200000000000000000000",
      "is_active": true,
      "reward_address": "cpc1bob..."
    }
  ]
}

Проверка текущего Proposer

# Через статус ноды
curl http://localhost:8000/status

Вывод:

{
  "height": 15,
  "last_hash": "0x...",
  "network": "devnet",
  "mempool_size": 2,
  "epoch": 1
}

Расчет Proposer:

height = 15
epoch = height // 10  # = 1
active_validators = get_active_validators()
proposer_index = height % len(active_validators)
proposer = active_validators[proposer_index]

Следующие шаги