From 2eab959e581f7cd3b26bf4be467fa041295c133d Mon Sep 17 00:00:00 2001 From: coopops Date: Tue, 28 Apr 2026 05:51:16 +0000 Subject: [PATCH 1/5] =?UTF-8?q?docs(readme):=20=D0=B7=D0=B0=D0=B4=D0=BE?= =?UTF-8?q?=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D1=82=D1=8C=20=D0=B8=D0=B7=D0=B2=D0=B5=D1=81=D1=82=D0=BD?= =?UTF-8?q?=D0=BE=D0=B5=20=D0=BE=D0=B3=D1=80=D0=B0=D0=BD=D0=B8=D1=87=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20SHiP=20=E2=80=94=20=D1=81=D1=85=D0=BB?= =?UTF-8?q?=D0=BE=D0=BF=D1=8B=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20create+remov?= =?UTF-8?q?e=20=D0=B2=D0=BD=D1=83=D1=82=D1=80=D0=B8=20=D0=B1=D0=BB=D0=BE?= =?UTF-8?q?=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit При emplace+erase строки в одном блоке chainbase::undo_index::on_remove уничтожает узел без записи в _removed_values — это by-design оптимизация для отката блока, но SHiP физически не видит такую транзиентную пару и парсер её не индексирует. Описаны симптомы, когда возникает на практике, workaround на стороне приложения (sleep ≥ block_time между транзакциями или modify вместо erase) и план системного фикса в нашем форке coopos. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index bf2f466..d54bcb6 100644 --- a/README.md +++ b/README.md @@ -240,6 +240,28 @@ docker run --rm --network host \ - [Redis key taxonomy](docs/redis-key-taxonomy.md) - [Disaster recovery / fork scenarios](docs/disaster-recovery.md) +## Известные ограничения + +### Схлопывание дельт create+remove внутри одного блока + +Парсер **не увидит** жизненный цикл строки таблицы, у которой `emplace` и `erase` произошли **внутри одного блока** (даже в разных транзакциях). Это поведение унаследовано от upstream-плагина `state_history_plugin` (SHiP) ноджеса: на уровне `chainbase::undo_index::on_remove` строка, созданная в текущей undo session и тут же удалённая, физически уничтожается без записи в `_removed_values` — это by-design оптимизация для отката блока (нечего откатывать), но она же делает event невидимым для SHiP-снимка состояния. SHiP пакует только то, что есть в undo session, и не имеет шанса увидеть транзиентную пару. + +**Симптомы:** +- На цепочке таблица пустая (получена и тут же удалена). +- ACTION-логи и `getclearance`/`apprvappndx`-подобные действия видны нормально — экшены идут параллельным потоком и не зависят от undo. +- В Redis stream / БД-индексе нет ни `present:true`, ни `present:false` события для конкретного `primary_key`. + +**Когда это всплывает на практике:** +- Контракт делает `emplace + erase` одной сущности в одной транзакции (anti-pattern, но встречается). +- Несколько коротких транзакций над одной сущностью пакуются производителем блоков в один блок (массовый импорт, auto-approve, bulk-операции, миграции, batch-сценарии). + +**Workaround на стороне приложения:** +- Разнести `emplace` и `erase` по разным блокам — в seed/bulk-сценариях добавить sleep ≥ 1 × `block_time` (≈500 мс для EOSIO) между транзакциями. В production-flow ручного approve'а это не нужно — между нажатием кнопки и второй транзакцией всегда проходит более одного блока. +- Если транзиентные пары — часть нормального дизайна контракта, рассмотреть переписывание на `modify(status="approved")` вместо `erase`: SHiP корректно эмиттит дельту изменения статуса. + +**Системный фикс (планируется):** +Drop-in патч `chainbase` + `state_history_plugin` в нашем форке `~/coopos` (uENOSIO) — отдельный список `_transient_removed_values` в undo_index + паковка пар `[present:true, present:false]` в `table_delta_v0` без изменения wire-формата. Wire-совместимо со всеми существующими SHiP-клиентами; после деплоя форка парсер автоматически начнёт видеть транзиентные пары без изменений в коде. Технические детали и план работ — в задаче `f3-shipchainbase-patch-vidimost-tranzientnykh-delt-createremove-vnutri-bloka.md` проекта parser2 в `_blago`. + ## Разработка ### Структура монорепы -- 2.52.0 From 9e37cb585d4ed9fc00e5d7054404e2a31179461c Mon Sep 17 00:00:00 2001 From: coopops Date: Mon, 25 May 2026 16:29:53 +0000 Subject: [PATCH 2/5] =?UTF-8?q?feat(events):=20=D0=BF=D1=80=D0=BE=D0=BA?= =?UTF-8?q?=D0=B8=D0=BD=D1=83=D1=82=D1=8C=20=D0=BF=D0=BE=D0=BB=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9=20SHiP=20action=5Ftrace=20=D0=B2=20ActionEvent=20?= =?UTF-8?q?=E2=80=94=20transaction=5Fid,=20creator=5Faction=5Fordinal,=20r?= =?UTF-8?q?eceipt.auth=5Fsequence,=20console/elapsed/context=5Ffree/accoun?= =?UTF-8?q?t=5Fram=5Fdeltas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ActionEvent отдавал только подмножество трассировки. Потребители (контроллер ЦК: ledger2 связывает операцию с родительским apply по transaction_id + action_ordinal/ creator_action_ordinal; blockchain-explorer показывает receipt/ram-deltas/console) требуют полный паритет с прежним транспортом. Данные уже декодируются wharfkit'ом — ship-reader их просто не извлекал. ship-reader: ShipTrace += creatorActionOrdinal/contextFree/elapsed/console/ accountRamDeltas; ActionReceipt += authSequence; извлечение в ShipProtocol из action_trace (auth_sequence поддержан и как объект, и как кортеж). parser2: BlockProcessor копирует поля в ActionEvent; типы и тесты обновлены. ship-reader 0.1.0→0.2.0, parser2 1.0.3→1.1.0. Тесты: ship-reader 51, parser2 205 — зелёные. Co-Authored-By: Claude Opus 4.7 --- packages/parser2/package.json | 2 +- packages/parser2/src/core/BlockProcessor.ts | 6 ++++ packages/parser2/src/types.ts | 14 +++++++++- .../parser2/test/unit/blockProcessor.test.ts | 21 ++++++++++++-- packages/parser2/test/unit/eventId.test.ts | 6 ++++ packages/parser2/test/unit/filters.test.ts | 6 ++++ packages/ship-reader/package.json | 2 +- packages/ship-reader/src/ShipProtocol.ts | 28 +++++++++++++++++-- packages/ship-reader/src/index.ts | 2 ++ packages/ship-reader/src/types/ship.ts | 16 +++++++++++ 10 files changed, 95 insertions(+), 8 deletions(-) diff --git a/packages/parser2/package.json b/packages/parser2/package.json index 452610d..5bbc8ea 100644 --- a/packages/parser2/package.json +++ b/packages/parser2/package.json @@ -1,6 +1,6 @@ { "name": "@coopenomics/parser2", - "version": "1.0.3", + "version": "1.1.0", "description": "Universal EOSIO/Antelope SHiP-to-Redis blockchain indexer (parser)", "license": "MIT", "author": "Coopenomics contributors", diff --git a/packages/parser2/src/core/BlockProcessor.ts b/packages/parser2/src/core/BlockProcessor.ts index 96a9be4..569b696 100644 --- a/packages/parser2/src/core/BlockProcessor.ts +++ b/packages/parser2/src/core/BlockProcessor.ts @@ -136,13 +136,19 @@ export class BlockProcessor { block_num: blockNum, block_time: blockTime, block_id: blockId, + transaction_id: trace.transactionId, account: trace.account, name: trace.name, authorization: [...trace.authorization], data, action_ordinal: trace.actionOrdinal, + creator_action_ordinal: trace.creatorActionOrdinal, global_sequence: trace.globalSequence, receipt: trace.receipt, + context_free: trace.contextFree, + elapsed: trace.elapsed, + console: trace.console, + account_ram_deltas: [...trace.accountRamDeltas], } actionEvents.push({ ...partial, event_id: computeEventId(partial) }) diff --git a/packages/parser2/src/types.ts b/packages/parser2/src/types.ts index 35168ec..52fdf31 100644 --- a/packages/parser2/src/types.ts +++ b/packages/parser2/src/types.ts @@ -8,7 +8,7 @@ * повторные доставки. */ -import type { ActionAuthorization, ActionReceipt } from '@coopenomics/coopos-ship-reader' +import type { ActionAuthorization, ActionReceipt, AccountRamDelta } from '@coopenomics/coopos-ship-reader' /** Событие вызова смарт-контракта (inline action). */ export interface ActionEvent { @@ -20,6 +20,8 @@ export interface ActionEvent { /** ISO-8601 время блока из трассировки транзакции. */ block_time: string block_id: string + /** Хеш транзакции, в которой исполнено действие. */ + transaction_id: string /** Аккаунт-владелец контракта (account). */ account: string /** Имя действия (action name). */ @@ -29,10 +31,20 @@ export interface ActionEvent { data: Record /** Порядковый номер действия внутри транзакции (1-based). */ action_ordinal: number + /** Порядковый номер родительского (создавшего) действия; 0 — если действие верхнего уровня. */ + creator_action_ordinal: number /** Глобальная уникальная последовательность — монотонный счётчик действий в цепи. */ global_sequence: bigint /** Квитанция об исполнении; null если нет трассировки. */ receipt: ActionReceipt | null + /** Является ли действие context-free. */ + context_free: boolean + /** Время исполнения действия в микросекундах. */ + elapsed: number + /** Консольный вывод действия (eosio::print и т.п.). */ + console: string + /** Изменения RAM по аккаунтам в результате действия. */ + account_ram_deltas: AccountRamDelta[] } /** Изменение строки в пользовательской таблице смарт-контракта (contract_row delta). */ diff --git a/packages/parser2/test/unit/blockProcessor.test.ts b/packages/parser2/test/unit/blockProcessor.test.ts index 5114dfe..6f98c38 100644 --- a/packages/parser2/test/unit/blockProcessor.test.ts +++ b/packages/parser2/test/unit/blockProcessor.test.ts @@ -57,8 +57,13 @@ function makeBlock(blockNum = 1, numTraces = 0, numDeltas = 0): ShipBlock { authorization: [{ actor: 'alice', permission: 'active' }], actRaw: new Uint8Array([1, 2, 3]), actionOrdinal: i + 1, + creatorActionOrdinal: 0, globalSequence: BigInt(100 + i), receipt: null, + contextFree: false, + elapsed: 0, + console: '', + accountRamDeltas: [], blockNum, blockId: 'a'.repeat(64), blockTime: '2024-01-01T00:00:00.000', @@ -176,8 +181,13 @@ describe('BlockProcessor — ABI updates (Story 4.3)', () => { authorization: [], actRaw: new Uint8Array([1]), actionOrdinal: 1, + creatorActionOrdinal: 0, globalSequence: BigInt(1), receipt: null, + contextFree: false, + elapsed: 0, + console: '', + accountRamDeltas: [], blockNum: 500, blockId: 'c'.repeat(64), blockTime: '2024-01-01T00:00:00.000', @@ -210,8 +220,13 @@ describe('BlockProcessor — ABI updates (Story 4.3)', () => { authorization: [], actRaw: new Uint8Array([1]), actionOrdinal: 1, + creatorActionOrdinal: 0, globalSequence: BigInt(2), receipt: null, + contextFree: false, + elapsed: 0, + console: '', + accountRamDeltas: [], blockNum: 501, blockId: 'c'.repeat(64), blockTime: '2024-01-01T00:00:00.000', @@ -324,8 +339,8 @@ describe('BlockProcessor — native deltas (Epic 6)', () => { lastIrreversible: { blockNum: 20, blockId: 'b'.repeat(64) }, prevBlock: null, traces: [ - { account: 'eosio.token', name: 'transfer', authorization: [], actRaw: new Uint8Array([1]), actionOrdinal: 1, globalSequence: BigInt(1), receipt: null, blockNum: 20, blockId: 'b'.repeat(64), blockTime: '2024-01-01T00:00:00.000', transactionId: 'c'.repeat(64) }, - { account: 'eosio.token', name: 'transfer', authorization: [], actRaw: new Uint8Array([1]), actionOrdinal: 2, globalSequence: BigInt(2), receipt: null, blockNum: 20, blockId: 'b'.repeat(64), blockTime: '2024-01-01T00:00:00.000', transactionId: 'c'.repeat(64) }, + { account: 'eosio.token', name: 'transfer', authorization: [], actRaw: new Uint8Array([1]), actionOrdinal: 1, creatorActionOrdinal: 0, globalSequence: BigInt(1), receipt: null, contextFree: false, elapsed: 0, console: '', accountRamDeltas: [], blockNum: 20, blockId: 'b'.repeat(64), blockTime: '2024-01-01T00:00:00.000', transactionId: 'c'.repeat(64) }, + { account: 'eosio.token', name: 'transfer', authorization: [], actRaw: new Uint8Array([1]), actionOrdinal: 2, creatorActionOrdinal: 0, globalSequence: BigInt(2), receipt: null, contextFree: false, elapsed: 0, console: '', accountRamDeltas: [], blockNum: 20, blockId: 'b'.repeat(64), blockTime: '2024-01-01T00:00:00.000', transactionId: 'c'.repeat(64) }, ], deltas: [ { name: 'contract_row', present: true, rowRaw: new Uint8Array([1]), code: 'eosio.token', scope: 'alice', table: 'accounts', primaryKey: '1' }, @@ -355,7 +370,7 @@ describe('BlockProcessor — native deltas (Epic 6)', () => { head: { blockNum: 5, blockId: 'd'.repeat(64) }, lastIrreversible: { blockNum: 5, blockId: 'd'.repeat(64) }, prevBlock: null, - traces: [{ account: 'eosio', name: 'newaccount', authorization: [], actRaw: new Uint8Array([1]), actionOrdinal: 1, globalSequence: BigInt(10), receipt: null, blockNum: 5, blockId: 'd'.repeat(64), blockTime: '2024-01-01T00:00:00.000', transactionId: 'e'.repeat(64) }], + traces: [{ account: 'eosio', name: 'newaccount', authorization: [], actRaw: new Uint8Array([1]), actionOrdinal: 1, creatorActionOrdinal: 0, globalSequence: BigInt(10), receipt: null, contextFree: false, elapsed: 0, console: '', accountRamDeltas: [], blockNum: 5, blockId: 'd'.repeat(64), blockTime: '2024-01-01T00:00:00.000', transactionId: 'e'.repeat(64) }], deltas: [ { name: 'contract_row', present: true, rowRaw: new Uint8Array([1]), code: 'eosio', scope: 'eosio', table: 'global', primaryKey: '0' }, { name: 'permission' as never, present: true, rowRaw: new Uint8Array([1]) }, diff --git a/packages/parser2/test/unit/eventId.test.ts b/packages/parser2/test/unit/eventId.test.ts index 0744038..925ebe0 100644 --- a/packages/parser2/test/unit/eventId.test.ts +++ b/packages/parser2/test/unit/eventId.test.ts @@ -15,13 +15,19 @@ function makeAction(override: Partial } interface RawActionReceipt { @@ -69,6 +74,9 @@ interface RawActionReceipt { act_digest: string global_sequence: string recv_sequence: string + // auth_sequence — массив пар (account, sequence). wharfkit может отдать как + // объект {account, sequence} или как кортеж [account, sequence]. + auth_sequence?: Array<{ account: string; sequence: string | number } | [string, string | number]> code_sequence: number abi_sequence: number } @@ -142,17 +150,28 @@ export function decodeBlocksResult(raw: unknown, abi: ShipAbi, blockNum: number, const receiptData = Array.isArray(at.receipt) ? (at.receipt[1] as RawActionTrace['receipt']) : at.receipt + const authSequence: AuthSequence[] = (receiptData?.auth_sequence ?? []).map(pair => + Array.isArray(pair) + ? { account: String(pair[0]), sequence: String(pair[1]) } + : { account: String(pair.account), sequence: String(pair.sequence) }, + ) const receipt: ActionReceipt | null = receiptData ? ({ receiver: String(receiptData.receiver), actDigest: String(receiptData.act_digest), globalSequence: BigInt(receiptData.global_sequence), recvSequence: BigInt(receiptData.recv_sequence), + authSequence, codeSequence: receiptData.code_sequence, abiSequence: receiptData.abi_sequence, } as ActionReceipt) : null + const accountRamDeltas: AccountRamDelta[] = (at.account_ram_deltas ?? []).map(d => ({ + account: String(d.account), + delta: Number(d.delta), + })) + // action_trace_v1 не имеет top-level global_sequence (только в receipt). // Fallback: если at.global_sequence undefined — берём из receipt. const topGlobalSeq = at.global_sequence ?? receiptData?.global_sequence @@ -162,8 +181,13 @@ export function decodeBlocksResult(raw: unknown, abi: ShipAbi, blockNum: number, authorization, actRaw: at.act.data instanceof Bytes ? at.act.data.array : Uint8Array.from([]), actionOrdinal: at.action_ordinal, + creatorActionOrdinal: at.creator_action_ordinal ?? 0, globalSequence: topGlobalSeq !== undefined ? BigInt(topGlobalSeq) : 0n, receipt, + contextFree: at.context_free ?? false, + elapsed: at.elapsed !== undefined ? Number(at.elapsed) : 0, + console: at.console ?? '', + accountRamDeltas, blockNum: thisBlock.blockNum, blockId: thisBlock.blockId, blockTime, diff --git a/packages/ship-reader/src/index.ts b/packages/ship-reader/src/index.ts index b1b8ec4..cf207a0 100644 --- a/packages/ship-reader/src/index.ts +++ b/packages/ship-reader/src/index.ts @@ -23,6 +23,8 @@ export type { BlockPosition, ActionReceipt, ActionAuthorization, + AuthSequence, + AccountRamDelta, } from './types/ship.js' export type { diff --git a/packages/ship-reader/src/types/ship.ts b/packages/ship-reader/src/types/ship.ts index ed08be2..b7371d1 100644 --- a/packages/ship-reader/src/types/ship.ts +++ b/packages/ship-reader/src/types/ship.ts @@ -8,11 +8,22 @@ export interface ActionAuthorization { readonly permission: string } +export interface AuthSequence { + readonly account: string + readonly sequence: string +} + +export interface AccountRamDelta { + readonly account: string + readonly delta: number +} + export interface ActionReceipt { readonly receiver: string readonly actDigest: string readonly globalSequence: bigint readonly recvSequence: bigint + readonly authSequence: readonly AuthSequence[] readonly codeSequence: number readonly abiSequence: number } @@ -23,8 +34,13 @@ export interface ShipTrace { readonly authorization: readonly ActionAuthorization[] readonly actRaw: Uint8Array readonly actionOrdinal: number + readonly creatorActionOrdinal: number readonly globalSequence: bigint readonly receipt: ActionReceipt | null + readonly contextFree: boolean + readonly elapsed: number + readonly console: string + readonly accountRamDeltas: readonly AccountRamDelta[] readonly blockNum: number readonly blockId: string readonly blockTime: string -- 2.52.0 From 1ee77e3427745ac293e12dd56e344f041c3cf3f5 Mon Sep 17 00:00:00 2001 From: coopops Date: Mon, 25 May 2026 17:16:40 +0000 Subject: [PATCH 3/5] =?UTF-8?q?ci:=20=D1=83=D0=B2=D0=B5=D0=BB=D0=B8=D1=87?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=B1=D1=8E=D0=B4=D0=B6=D0=B5=D1=82=20?= =?UTF-8?q?=D0=BE=D0=B6=D0=B8=D0=B4=D0=B0=D0=BD=D0=B8=D1=8F=20Chain=20API?= =?UTF-8?q?=20=D0=B4=D0=BE=20120=20=D0=BF=D0=BE=D0=BF=D1=8B=D1=82=D0=BE?= =?UTF-8?q?=D0=BA=20+=20curl=20--max-time?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit На нагруженном раннере nodeos поднимался >6 мин (cache restore тоже ловил ETIMEDOUT), а curl без --max-time подвисал на ещё-не-отвечающем порту и съедал 60-попыточный бюджет — integration падал на 'Chain API did not become ready' при здоровой ноде. Узел не крашился (лог сплошь info), нужен лишь запас времени. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6a10355..13f40dd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,12 +93,15 @@ jobs: sleep 3 - name: Wait for Chain API run: | - for i in $(seq 1 60); do - if curl -sf http://localhost:8888/v1/chain/get_info > /dev/null 2>&1; then + # Бюджет 120 попыток × (≤2с curl + 2с sleep) ≈ до 8 мин: на нагруженном + # раннере nodeos поднимается медленно (наблюдали >6 мин до get_info), а + # curl без --max-time подвисал на ещё-не-отвечающем порту и съедал бюджет. + for i in $(seq 1 120); do + if curl -sf --max-time 2 http://localhost:8888/v1/chain/get_info > /dev/null 2>&1; then echo "Chain API ready after $i attempts" exit 0 fi - echo " attempt $i/60..." + echo " attempt $i/120..." sleep 2 done echo "ERROR: Chain API did not become ready. Container logs:" -- 2.52.0 From 82d3ed26431ddb01e3f44b60cee3cb976fe44fe0 Mon Sep 17 00:00:00 2001 From: coopops Date: Mon, 25 May 2026 17:51:44 +0000 Subject: [PATCH 4/5] =?UTF-8?q?ci:=20=D1=81=D0=BD=D0=BE=D1=81=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20=D0=BE=D1=81=D1=82=D0=B0=D1=82=D0=BE=D1=87=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9=20=D0=BA=D0=BE=D0=BD=D1=82=D0=B5=D0=B9=D0=BD=D0=B5=D1=80?= =?UTF-8?q?=20blockchain=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=20docker=20run?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Self-hosted раннер персистентный: упавший integration оставляет контейнер с фиксированным именем blockchain, следующий docker run падает exit 125 (Conflict, name already in use). docker rm -f перед стартом — фикс самовосстанавливающийся, чистит остаток от предыдущего прогона. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13f40dd..7a6c276 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,6 +85,10 @@ jobs: # передачу команды контейнеру. Монтируем config.ini (SHiP на 8080) и genesis.json. - name: Start blockchain node run: | + # Раннер self-hosted и персистентный: упавший прогон оставляет контейнер + # с фиксированным именем blockchain, и следующий docker run падает с + # exit 125 (Conflict, name already in use). Сносим остаток перед стартом. + docker rm -f blockchain 2>/dev/null || true docker run -d --name blockchain \ -p 8888:8888 -p 8080:8080 -p 9876:9876 \ -v "${GITHUB_WORKSPACE}/packages/parser2/test/integration/node-config:/mnt/dev/config" \ -- 2.52.0 From 1c3cf56dd06ecdb5c1b742ead9fa3953240b0007 Mon Sep 17 00:00:00 2001 From: coopops Date: Tue, 26 May 2026 04:25:54 +0000 Subject: [PATCH 5/5] =?UTF-8?q?ci:=20=D0=BC=D0=BE=D0=BD=D1=82=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20pristine-=D0=BA=D0=BE=D0=BF?= =?UTF-8?q?=D0=B8=D1=8E=20node-config=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20m?= =?UTF-8?q?ktemp=20+=20=D0=B7=D0=B0=D0=BF=D0=B0=D1=81=20=D0=BF=D0=BE=20?= =?UTF-8?q?=D1=82=D0=B0=D0=B9=D0=BC=D0=B0=D1=83=D1=82=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Настоящая причина падения integration — не медленный старт, а краш узла: 'plugin_config_exception: Incorrect plugin configuration'. nodeos генерирует protocol_features/*.json в смонтированный config-каталог; при монтировании самого workspace эти untracked root-файлы остаются между прогонами на персистентном раннере и ломают конфиг. Монтируем свежую копию в mktemp-каталог — узел стартует на чистом конфиге. Заодно sleep 4 (бюджет ожидания ~8 мин). Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a6c276..7d58910 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,9 +89,17 @@ jobs: # с фиксированным именем blockchain, и следующий docker run падает с # exit 125 (Conflict, name already in use). Сносим остаток перед стартом. docker rm -f blockchain 2>/dev/null || true + # nodeos на старте генерирует protocol_features/*.json В смонтированный + # config-каталог. Если монтировать сам workspace, эти файлы остаются в нём + # (untracked, root-owned — checkout --force их не чистит) и на следующем + # прогоне ломают узел: 'plugin_config_exception: Incorrect plugin + # configuration'. Монтируем pristine-копию во временном каталоге — узел + # пишет сгенерированное туда, workspace остаётся чистым. + CONFIG_DIR=$(mktemp -d) + cp "${GITHUB_WORKSPACE}/packages/parser2/test/integration/node-config/"* "$CONFIG_DIR/" docker run -d --name blockchain \ -p 8888:8888 -p 8080:8080 -p 9876:9876 \ - -v "${GITHUB_WORKSPACE}/packages/parser2/test/integration/node-config:/mnt/dev/config" \ + -v "$CONFIG_DIR:/mnt/dev/config" \ dicoop/blockchain:v5.1.0-dev \ /bin/bash -c '/usr/local/bin/nodeos -d /mnt/dev/data -p eosio --config-dir /mnt/dev/config --genesis-json /mnt/dev/config/genesis.json --disable-replay-opts' sleep 3 @@ -106,7 +114,7 @@ jobs: exit 0 fi echo " attempt $i/120..." - sleep 2 + sleep 4 done echo "ERROR: Chain API did not become ready. Container logs:" docker logs blockchain | tail -100 -- 2.52.0