From 61d5a5e951ab2b5aeb89b0ae150d37e455070089 Mon Sep 17 00:00:00 2001 From: Pawel Wankiewicz Date: Sat, 14 Feb 2026 21:41:14 +0100 Subject: [PATCH 01/21] PLA-2317: Fix block re-processing crashes in EventProcessor, ExtrinsicProcessor, and TokenCreated ExtrinsicProcessor/EventProcessor: - Guard against string values from DB longText columns for block.events and block.extrinsics (is_array check instead of null-coalescing) - Filter null events before accessing extrinsicIndex property to prevent "Attempt to read property on null" errors in saveExtrinsicEvents and updateTransaction TokenCreated: - Use firstOrCreate instead of create to prevent UniqueConstraintViolationException when re-processing blocks where the token already exists from a later block's ingestion Co-Authored-By: Claude Opus 4.6 --- .../Processor/Substrate/EventProcessor.php | 2 +- .../MultiTokens/TokenCreated.php | 52 ++++++++++--------- .../Substrate/ExtrinsicProcessor.php | 8 +-- 3 files changed, 33 insertions(+), 29 deletions(-) diff --git a/src/Services/Processor/Substrate/EventProcessor.php b/src/Services/Processor/Substrate/EventProcessor.php index 84d170a7..fc417ebc 100644 --- a/src/Services/Processor/Substrate/EventProcessor.php +++ b/src/Services/Processor/Substrate/EventProcessor.php @@ -15,7 +15,7 @@ public function __construct(protected Block $block, protected Codec $codec) {} public function run(): array { Log::info("Processing Events from block #{$this->block->number}"); - $events = array_filter($this->block->events ?? []); + $events = array_filter(is_array($this->block->events) ? $this->block->events : []); $errors = []; foreach ($events as $event) { diff --git a/src/Services/Processor/Substrate/Events/Implementations/MultiTokens/TokenCreated.php b/src/Services/Processor/Substrate/Events/Implementations/MultiTokens/TokenCreated.php index fc31dbc7..4fd5ee96 100644 --- a/src/Services/Processor/Substrate/Events/Implementations/MultiTokens/TokenCreated.php +++ b/src/Services/Processor/Substrate/Events/Implementations/MultiTokens/TokenCreated.php @@ -97,30 +97,34 @@ public function parseToken(TokenCreatedPolkadart $event, int $count = 0): mixed $symbol = is_array($symbol = $this->getValue($createToken, 'metadata.symbol')) ? HexConverter::bytesToHexPrefixed($symbol) : $symbol; $privilegedParams = $this->getValue($createToken, 'privileged_params'); - return Token::create([ - 'collection_id' => $collection->id, - 'token_chain_id' => $event->tokenId, - 'supply' => 0, // Initial supply is set to 0 and increased by the mint event - 'cap' => $cap?->name, - 'cap_supply' => $capSupply ?? $collapsingSupply, - 'is_frozen' => $isFrozen, - 'royalty_wallet_id' => $beneficiary?->id, - 'royalty_percentage' => $percentage ? $percentage / 10 ** 7 : null, - 'is_currency' => $isCurrency, - 'listing_forbidden' => Arr::get($createToken, 'listing_forbidden') ?? false, - 'requires_deposit' => $privilegedParams === null ? true : Arr::get($privilegedParams, 'requires_deposit', true), - 'creation_depositor' => null, - 'creation_deposit_amount' => 0, // TODO: Implement this - 'owner_deposit' => 0, // TODO: Implement this - 'total_token_account_deposit' => 0, // TODO: Implement this - 'attribute_count' => 0, // This will be increased in the AttributeSet event - 'account_count' => $this->getValue($createToken, 'account_deposit_count') ?? 1, - 'infusion' => $this->getValue($createToken, 'infusion') ?? 0, - 'anyone_can_infuse' => Arr::get($createToken, 'anyone_can_infuse') ?? false, - 'decimal_count' => $this->getValue($createToken, 'metadata.decimal_count') ?? 0, - 'name' => $name === '0x' ? null : $name, - 'symbol' => $symbol === '0x' ? null : $symbol, - ]); + return Token::firstOrCreate( + [ + 'collection_id' => $collection->id, + 'token_chain_id' => $event->tokenId, + ], + [ + 'supply' => 0, // Initial supply is set to 0 and increased by the mint event + 'cap' => $cap?->name, + 'cap_supply' => $capSupply ?? $collapsingSupply, + 'is_frozen' => $isFrozen, + 'royalty_wallet_id' => $beneficiary?->id, + 'royalty_percentage' => $percentage ? $percentage / 10 ** 7 : null, + 'is_currency' => $isCurrency, + 'listing_forbidden' => Arr::get($createToken, 'listing_forbidden') ?? false, + 'requires_deposit' => $privilegedParams === null ? true : Arr::get($privilegedParams, 'requires_deposit', true), + 'creation_depositor' => null, + 'creation_deposit_amount' => 0, // TODO: Implement this + 'owner_deposit' => 0, // TODO: Implement this + 'total_token_account_deposit' => 0, // TODO: Implement this + 'attribute_count' => 0, // This will be increased in the AttributeSet event + 'account_count' => $this->getValue($createToken, 'account_deposit_count') ?? 1, + 'infusion' => $this->getValue($createToken, 'infusion') ?? 0, + 'anyone_can_infuse' => Arr::get($createToken, 'anyone_can_infuse') ?? false, + 'decimal_count' => $this->getValue($createToken, 'metadata.decimal_count') ?? 0, + 'name' => $name === '0x' ? null : $name, + 'symbol' => $symbol === '0x' ? null : $symbol, + ] + ); } public function log(): void diff --git a/src/Services/Processor/Substrate/ExtrinsicProcessor.php b/src/Services/Processor/Substrate/ExtrinsicProcessor.php index b4220a71..6e1f47e3 100644 --- a/src/Services/Processor/Substrate/ExtrinsicProcessor.php +++ b/src/Services/Processor/Substrate/ExtrinsicProcessor.php @@ -25,7 +25,7 @@ public function __construct(protected Block $block, protected Codec $codec) {} public function run(): array { Log::info("Processing Extrinsics from block #{$this->block->number}"); - $extrinsics = $this->block->extrinsics ?? []; + $extrinsics = is_array($this->block->extrinsics) ? $this->block->extrinsics : []; $errors = []; foreach ($extrinsics as $index => $extrinsic) { @@ -50,7 +50,7 @@ protected function processExtrinsic(PolkadartExtrinsic $extrinsic, int $index): ])->orderBy('created_at', 'desc')->first(); if ($transaction) { - if ($this->block->events === null) { + if (!is_array($this->block->events)) { Log::info('Fetching events for block #' . $this->block->number); $rpc = new SubstrateSocketClient(); $blockHash = $this->block->hash; @@ -79,7 +79,7 @@ protected function processExtrinsic(PolkadartExtrinsic $extrinsic, int $index): protected function updateTransaction($transaction, int $index): void { $extrinsicId = "{$this->block->number}-{$index}"; - $resultEvent = collect($this->block->events)->firstWhere( + $resultEvent = collect($this->block->events)->filter()->firstWhere( fn ($event) => (($event instanceof ExtrinsicSuccess) || ($event instanceof ExtrinsicFailed)) && $event->extrinsicIndex == $index ); @@ -95,7 +95,7 @@ protected function saveExtrinsicEvents($transaction, int $index): void { Event::where('transaction_id', $transaction->id)->delete(); - $eventsWithTransaction = collect($this->block->events)->filter(fn ($event) => $event->extrinsicIndex == $index) + $eventsWithTransaction = collect($this->block->events)->filter(fn ($event) => $event !== null && $event->extrinsicIndex == $index) ->map(fn ($event) => [ 'transaction_id' => $transaction->id, 'phase' => '2', From b69d7970059c38c702be4d827d4cd45efc08926f Mon Sep 17 00:00:00 2001 From: Pawel Wankiewicz Date: Sun, 22 Feb 2026 21:40:42 +0100 Subject: [PATCH 02/21] PLA-2317 --- composer.json | 26 +-- composer.lock | 274 ++++++++++++----------- src/Providers/GraphQlServiceProvider.php | 8 +- 3 files changed, 159 insertions(+), 149 deletions(-) diff --git a/composer.json b/composer.json index c5f44d24..4e7476bf 100644 --- a/composer.json +++ b/composer.json @@ -26,9 +26,9 @@ "ext-redis": "*", "ext-sodium": "*", "ext-imagick": "*", - "amphp/amp": "^3.0", + "amphp/amp": "^3.1", "amphp/http": "^2.1", - "amphp/http-client": "^5.2", + "amphp/http-client": "^5.3", "amphp/parallel": "^2.3", "amphp/socket": "^2.3", "amphp/websocket": "^2.0", @@ -37,33 +37,33 @@ "enjin/php-blockchain-tools": "^2.0.1", "gmajor/sr25519-bindings": "^1.0", "gmajor/substrate-codec-php": "^1.1", - "guzzlehttp/guzzle": "^7.9", + "guzzlehttp/guzzle": "^7.10", "illuminate/contracts": "^11.0", "kevinrob/guzzle-cache-middleware": "^6.0", "laravel/pail": "^1.2", "mll-lab/laravel-graphiql": "^4.0.1", - "phrity/websocket": "^3.5", - "rebing/graphql-laravel": "^9.9", + "phrity/websocket": "^3.6", + "rebing/graphql-laravel": "^9.15", "revolt/event-loop": "^1.0", "simplesoftwareio/simple-qrcode": "^4.2", - "spatie/laravel-package-tools": "^1.18", - "stechstudio/backoff": "^1.4", - "tuupola/base58": "^2.1" + "spatie/laravel-package-tools": "^1.93", + "stechstudio/backoff": "^1.6", + "tuupola/base58": "^2.2" }, "require-dev": { "fakerphp/faker": "^1.24", - "larastan/larastan": "^3.0", + "larastan/larastan": "^3.9", "laravel/pint": "^1.27", - "nunomaduro/collision": "^8.5", - "orchestra/testbench": "^9.9", + "nunomaduro/collision": "^8.9", + "orchestra/testbench": "^9.16", "phpstan/extension-installer": "^1.4", "phpstan/phpstan-deprecation-rules": "^2.0", "phpstan/phpstan-phpunit": "^2.0", "phpunit/php-code-coverage": "^11.0", "phpunit/phpunit": "^11.5", - "rector/rector": "^2.0", + "rector/rector": "^2.3", "roave/security-advisories": "dev-latest", - "spatie/ray": "^1.41" + "spatie/ray": "^1.47" }, "repositories": [ { diff --git a/composer.lock b/composer.lock index 5171f050..00425492 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0f72b444f7a99df5c46b936d2aa2f43f", + "content-hash": "e70ae935f4295f9b01c003bf08be3bb7", "packages": [ { "name": "amphp/amp", @@ -2861,21 +2861,21 @@ }, { "name": "laragraph/utils", - "version": "v2.2.0", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/laragraph/utils.git", - "reference": "035c92b37f40c6b51027e76f90ef25d9a9458c56" + "reference": "48218658a7b39557061fc9275ef872e479900113" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laragraph/utils/zipball/035c92b37f40c6b51027e76f90ef25d9a9458c56", - "reference": "035c92b37f40c6b51027e76f90ef25d9a9458c56", + "url": "https://api.github.com/repos/laragraph/utils/zipball/48218658a7b39557061fc9275ef872e479900113", + "reference": "48218658a7b39557061fc9275ef872e479900113", "shasum": "" }, "require": { - "illuminate/contracts": "~5.6.0 || ~5.7.0 || ~5.8.0 || ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12", - "illuminate/http": "~5.6.0 || ~5.7.0 || ~5.8.0 || ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12", + "illuminate/contracts": "~5.6.0 || ~5.7.0 || ~5.8.0 || ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || ^13", + "illuminate/http": "~5.6.0 || ~5.7.0 || ~5.8.0 || ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || ^13", "php": "^7.2 || ^8", "thecodingmachine/safe": "^1.1 || ^2 || ^3", "webonyx/graphql-php": "^0.13.2 || ^14 || ^15" @@ -2884,13 +2884,13 @@ "ergebnis/composer-normalize": "^2.11", "jangregor/phpstan-prophecy": "^1", "mll-lab/php-cs-fixer-config": "^4", - "orchestra/testbench": "~3.6.0 || ~3.7.0 || ~3.8.0 || ~3.9.0 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 || ^10 || ^10.x-dev", + "orchestra/testbench": "~3.6.0 || ~3.7.0 || ~3.8.0 || ~3.9.0 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 || ^10 || ^11", "phpstan/extension-installer": "^1", "phpstan/phpstan": "^1", "phpstan/phpstan-deprecation-rules": "^1", "phpstan/phpstan-phpunit": "^1", "phpstan/phpstan-strict-rules": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9 || ^10.5 || ^11", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9 || ^10.5 || ^11 || ^12", "thecodingmachine/phpstan-safe-rule": "^1.1" }, "type": "library", @@ -2915,7 +2915,7 @@ "issues": "https://github.com/laragraph/utils/issues", "source": "https://github.com/laragraph/utils" }, - "time": "2025-02-12T13:19:02+00:00" + "time": "2026-02-19T16:47:22+00:00" }, { "name": "laravel/framework", @@ -4316,16 +4316,16 @@ }, { "name": "nette/utils", - "version": "v4.1.2", + "version": "v4.1.3", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "f76b5dc3d6c6d3043c8d937df2698515b99cbaf5" + "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/f76b5dc3d6c6d3043c8d937df2698515b99cbaf5", - "reference": "f76b5dc3d6c6d3043c8d937df2698515b99cbaf5", + "url": "https://api.github.com/repos/nette/utils/zipball/bb3ea637e3d131d72acc033cfc2746ee893349fe", + "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe", "shasum": "" }, "require": { @@ -4337,8 +4337,10 @@ }, "require-dev": { "jetbrains/phpstorm-attributes": "^1.2", + "nette/phpstan-rules": "^1.0", "nette/tester": "^2.5", - "phpstan/phpstan": "^2.0@stable", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1@stable", "tracy/tracy": "^2.9" }, "suggest": { @@ -4399,37 +4401,37 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.1.2" + "source": "https://github.com/nette/utils/tree/v4.1.3" }, - "time": "2026-02-03T17:21:09+00:00" + "time": "2026-02-13T03:05:33+00:00" }, { "name": "nunomaduro/termwind", - "version": "v2.3.3", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017" + "reference": "712a31b768f5daea284c2169a7d227031001b9a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/6fb2a640ff502caace8e05fd7be3b503a7e1c017", - "reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/712a31b768f5daea284c2169a7d227031001b9a8", + "reference": "712a31b768f5daea284c2169a7d227031001b9a8", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^8.2", - "symfony/console": "^7.3.6" + "symfony/console": "^7.4.4 || ^8.0.4" }, "require-dev": { - "illuminate/console": "^11.46.1", - "laravel/pint": "^1.25.1", + "illuminate/console": "^11.47.0", + "laravel/pint": "^1.27.1", "mockery/mockery": "^1.6.12", - "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.1.3", + "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.3.2", "phpstan/phpstan": "^1.12.32", "phpstan/phpstan-strict-rules": "^1.6.2", - "symfony/var-dumper": "^7.3.5", + "symfony/var-dumper": "^7.3.5 || ^8.0.4", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -4461,7 +4463,7 @@ "email": "enunomaduro@gmail.com" } ], - "description": "Its like Tailwind CSS, but for the console.", + "description": "It's like Tailwind CSS, but for the console.", "keywords": [ "cli", "console", @@ -4472,7 +4474,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.3.3" + "source": "https://github.com/nunomaduro/termwind/tree/v2.4.0" }, "funding": [ { @@ -4488,7 +4490,7 @@ "type": "github" } ], - "time": "2025-11-20T02:34:59+00:00" + "time": "2026-02-16T23:10:27+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -5761,22 +5763,22 @@ }, { "name": "rebing/graphql-laravel", - "version": "9.14.0", + "version": "9.15.0", "source": { "type": "git", "url": "https://github.com/rebing/graphql-laravel.git", - "reference": "918a57797b38dcd86dcd1a14138bcf8613fe8eef" + "reference": "035b35172120932a0db6208f19d0d732e7c47cb4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rebing/graphql-laravel/zipball/918a57797b38dcd86dcd1a14138bcf8613fe8eef", - "reference": "918a57797b38dcd86dcd1a14138bcf8613fe8eef", + "url": "https://api.github.com/repos/rebing/graphql-laravel/zipball/035b35172120932a0db6208f19d0d732e7c47cb4", + "reference": "035b35172120932a0db6208f19d0d732e7c47cb4", "shasum": "" }, "require": { "ext-json": "*", - "illuminate/contracts": "^11.0 || ^12.0", - "illuminate/support": "^11.0 || ^12.0", + "illuminate/contracts": "^11.0 || ^12.0 || ^13.0", + "illuminate/support": "^11.0 || ^12.0 || ^13.0", "laragraph/utils": "^2.0.1", "php": "^8.2", "thecodingmachine/safe": "^3.0", @@ -5787,10 +5789,10 @@ "fakerphp/faker": "^1.6", "friendsofphp/php-cs-fixer": "^3", "larastan/larastan": "^3", - "laravel/framework": "^11.0 || ^12.0", + "laravel/framework": "^11.0 || ^12.0 || ^13.0", "mfn/php-cs-fixer-config": "^2", "mockery/mockery": "^1.5", - "orchestra/testbench": "^9.0 || ^10.0", + "orchestra/testbench": "^9.0 || ^10.0 || ^11.0", "phpstan/phpstan": "^2", "phpunit/phpunit": "^10.5.32 || ^11.0", "thecodingmachine/phpstan-safe-rule": "^1" @@ -5859,7 +5861,7 @@ ], "support": { "issues": "https://github.com/rebing/graphql-laravel/issues", - "source": "https://github.com/rebing/graphql-laravel/tree/9.14.0" + "source": "https://github.com/rebing/graphql-laravel/tree/9.15.0" }, "funding": [ { @@ -5867,7 +5869,7 @@ "type": "github" } ], - "time": "2025-12-08T09:53:36+00:00" + "time": "2026-02-19T19:47:22+00:00" }, { "name": "revolt/event-loop", @@ -6011,29 +6013,29 @@ }, { "name": "spatie/laravel-package-tools", - "version": "1.92.7", + "version": "1.93.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "f09a799850b1ed765103a4f0b4355006360c49a5" + "reference": "0d097bce95b2bf6802fb1d83e1e753b0f5a948e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/f09a799850b1ed765103a4f0b4355006360c49a5", - "reference": "f09a799850b1ed765103a4f0b4355006360c49a5", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/0d097bce95b2bf6802fb1d83e1e753b0f5a948e7", + "reference": "0d097bce95b2bf6802fb1d83e1e753b0f5a948e7", "shasum": "" }, "require": { - "illuminate/contracts": "^9.28|^10.0|^11.0|^12.0", - "php": "^8.0" + "illuminate/contracts": "^10.0|^11.0|^12.0|^13.0", + "php": "^8.1" }, "require-dev": { "mockery/mockery": "^1.5", - "orchestra/testbench": "^7.7|^8.0|^9.0|^10.0", - "pestphp/pest": "^1.23|^2.1|^3.1", - "phpunit/php-code-coverage": "^9.0|^10.0|^11.0", - "phpunit/phpunit": "^9.5.24|^10.5|^11.5", - "spatie/pest-plugin-test-time": "^1.1|^2.2" + "orchestra/testbench": "^8.0|^9.2|^10.0|^11.0", + "pestphp/pest": "^2.1|^3.1|^4.0", + "phpunit/php-code-coverage": "^10.0|^11.0|^12.0", + "phpunit/phpunit": "^10.5|^11.5|^12.5", + "spatie/pest-plugin-test-time": "^2.2|^3.0" }, "type": "library", "autoload": { @@ -6060,7 +6062,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.92.7" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.93.0" }, "funding": [ { @@ -6068,7 +6070,7 @@ "type": "github" } ], - "time": "2025-07-17T15:46:43+00:00" + "time": "2026-02-21T12:49:54+00:00" }, { "name": "stechstudio/backoff", @@ -8536,16 +8538,16 @@ }, { "name": "thecodingmachine/safe", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/thecodingmachine/safe.git", - "reference": "2cdd579eeaa2e78e51c7509b50cc9fb89a956236" + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/2cdd579eeaa2e78e51c7509b50cc9fb89a956236", - "reference": "2cdd579eeaa2e78e51c7509b50cc9fb89a956236", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19", + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19", "shasum": "" }, "require": { @@ -8655,7 +8657,7 @@ "description": "PHP core functions that throw exceptions instead of returning FALSE on error", "support": { "issues": "https://github.com/thecodingmachine/safe/issues", - "source": "https://github.com/thecodingmachine/safe/tree/v3.3.0" + "source": "https://github.com/thecodingmachine/safe/tree/v3.4.0" }, "funding": [ { @@ -8666,12 +8668,16 @@ "url": "https://github.com/shish", "type": "github" }, + { + "url": "https://github.com/silasjoisten", + "type": "github" + }, { "url": "https://github.com/staabm", "type": "github" } ], - "time": "2025-05-14T06:15:44+00:00" + "time": "2026-02-04T18:08:13+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -9707,39 +9713,36 @@ }, { "name": "nunomaduro/collision", - "version": "v8.8.3", + "version": "v8.9.1", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "1dc9e88d105699d0fee8bb18890f41b274f6b4c4" + "reference": "a1ed3fa530fd60bc515f9303e8520fcb7d4bd935" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/1dc9e88d105699d0fee8bb18890f41b274f6b4c4", - "reference": "1dc9e88d105699d0fee8bb18890f41b274f6b4c4", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/a1ed3fa530fd60bc515f9303e8520fcb7d4bd935", + "reference": "a1ed3fa530fd60bc515f9303e8520fcb7d4bd935", "shasum": "" }, "require": { - "filp/whoops": "^2.18.1", - "nunomaduro/termwind": "^2.3.1", + "filp/whoops": "^2.18.4", + "nunomaduro/termwind": "^2.4.0", "php": "^8.2.0", - "symfony/console": "^7.3.0" + "symfony/console": "^7.4.4 || ^8.0.4" }, "conflict": { - "laravel/framework": "<11.44.2 || >=13.0.0", - "phpunit/phpunit": "<11.5.15 || >=13.0.0" + "laravel/framework": "<11.48.0 || >=14.0.0", + "phpunit/phpunit": "<11.5.50 || >=14.0.0" }, "require-dev": { - "brianium/paratest": "^7.8.3", - "larastan/larastan": "^3.4.2", - "laravel/framework": "^11.44.2 || ^12.18", - "laravel/pint": "^1.22.1", - "laravel/sail": "^1.43.1", - "laravel/sanctum": "^4.1.1", - "laravel/tinker": "^2.10.1", - "orchestra/testbench-core": "^9.12.0 || ^10.4", - "pestphp/pest": "^3.8.2 || ^4.0.0", - "sebastian/environment": "^7.2.1 || ^8.0" + "brianium/paratest": "^7.8.5", + "larastan/larastan": "^3.9.2", + "laravel/framework": "^11.48.0 || ^12.52.0", + "laravel/pint": "^1.27.1", + "orchestra/testbench-core": "^9.12.0 || ^10.9.0", + "pestphp/pest": "^3.8.5 || ^4.4.1 || ^5.0.0", + "sebastian/environment": "^7.2.1 || ^8.0.3 || ^9.0.0" }, "type": "library", "extra": { @@ -9802,7 +9805,7 @@ "type": "patreon" } ], - "time": "2025-11-20T02:55:25+00:00" + "time": "2026-02-17T17:33:08+00:00" }, { "name": "orchestra/canvas", @@ -10387,11 +10390,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.38", + "version": "2.1.39", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dfaf1f530e1663aa167bc3e52197adb221582629", - "reference": "dfaf1f530e1663aa167bc3e52197adb221582629", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c6f73a2af4cbcd99c931d0fb8f08548cc0fa8224", + "reference": "c6f73a2af4cbcd99c931d0fb8f08548cc0fa8224", "shasum": "" }, "require": { @@ -10436,25 +10439,25 @@ "type": "github" } ], - "time": "2026-01-30T17:12:46+00:00" + "time": "2026-02-11T14:48:56+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", - "reference": "468e02c9176891cc901143da118f09dc9505fc2f" + "reference": "6b5571001a7f04fa0422254c30a0017ec2f2cacc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/468e02c9176891cc901143da118f09dc9505fc2f", - "reference": "468e02c9176891cc901143da118f09dc9505fc2f", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/6b5571001a7f04fa0422254c30a0017ec2f2cacc", + "reference": "6b5571001a7f04fa0422254c30a0017ec2f2cacc", "shasum": "" }, "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.1.15" + "phpstan/phpstan": "^2.1.39" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", @@ -10479,24 +10482,27 @@ "MIT" ], "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", + "keywords": [ + "static analysis" + ], "support": { "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", - "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.3" + "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.4" }, - "time": "2025-05-14T10:56:57+00:00" + "time": "2026-02-09T13:21:14+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "2.0.14", + "version": "2.0.16", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "f7553d6c613878d04f7e7ef129d4607118cd7cd4" + "reference": "6ab598e1bc106e6827fd346ae4a12b4a5d634c32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/f7553d6c613878d04f7e7ef129d4607118cd7cd4", - "reference": "f7553d6c613878d04f7e7ef129d4607118cd7cd4", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/6ab598e1bc106e6827fd346ae4a12b4a5d634c32", + "reference": "6ab598e1bc106e6827fd346ae4a12b4a5d634c32", "shasum": "" }, "require": { @@ -10537,9 +10543,9 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.14" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.16" }, - "time": "2026-02-10T11:57:48+00:00" + "time": "2026-02-14T09:05:21+00:00" }, { "name": "phpunit/php-code-coverage", @@ -10890,16 +10896,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.53", + "version": "11.5.55", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a997a653a82845f1240d73ee73a8a4e97e4b0607" + "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a997a653a82845f1240d73ee73a8a4e97e4b0607", - "reference": "a997a653a82845f1240d73ee73a8a4e97e4b0607", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/adc7262fccc12de2b30f12a8aa0b33775d814f00", + "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00", "shasum": "" }, "require": { @@ -10972,7 +10978,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.53" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.55" }, "funding": [ { @@ -10996,20 +11002,20 @@ "type": "tidelift" } ], - "time": "2026-02-10T12:28:25+00:00" + "time": "2026-02-18T12:37:06+00:00" }, { "name": "psy/psysh", - "version": "v0.12.19", + "version": "v0.12.20", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "a4f766e5c5b6773d8399711019bb7d90875a50ee" + "reference": "19678eb6b952a03b8a1d96ecee9edba518bb0373" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/a4f766e5c5b6773d8399711019bb7d90875a50ee", - "reference": "a4f766e5c5b6773d8399711019bb7d90875a50ee", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/19678eb6b952a03b8a1d96ecee9edba518bb0373", + "reference": "19678eb6b952a03b8a1d96ecee9edba518bb0373", "shasum": "" }, "require": { @@ -11073,22 +11079,22 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.19" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.20" }, - "time": "2026-01-30T17:33:13+00:00" + "time": "2026-02-11T15:05:28+00:00" }, { "name": "rector/rector", - "version": "2.3.6", + "version": "2.3.8", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "ca9ebb81d280cd362ea39474dabd42679e32ca6b" + "reference": "bbd37aedd8df749916cffa2a947cfc4714d1ba2c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/ca9ebb81d280cd362ea39474dabd42679e32ca6b", - "reference": "ca9ebb81d280cd362ea39474dabd42679e32ca6b", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/bbd37aedd8df749916cffa2a947cfc4714d1ba2c", + "reference": "bbd37aedd8df749916cffa2a947cfc4714d1ba2c", "shasum": "" }, "require": { @@ -11127,7 +11133,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.3.6" + "source": "https://github.com/rectorphp/rector/tree/2.3.8" }, "funding": [ { @@ -11135,7 +11141,7 @@ "type": "github" } ], - "time": "2026-02-06T14:25:06+00:00" + "time": "2026-02-22T09:45:50+00:00" }, { "name": "roave/security-advisories", @@ -11143,12 +11149,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "0c751a6ae38247a99675c8210d6a9d570314c87f" + "reference": "92c5ec5685cfbcd7ef721a502e6622516728011c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/0c751a6ae38247a99675c8210d6a9d570314c87f", - "reference": "0c751a6ae38247a99675c8210d6a9d570314c87f", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/92c5ec5685cfbcd7ef721a502e6622516728011c", + "reference": "92c5ec5685cfbcd7ef721a502e6622516728011c", "shasum": "" }, "conflict": { @@ -11256,6 +11262,7 @@ "causal/oidc": "<4", "cecil/cecil": "<7.47.1", "centreon/centreon": "<22.10.15", + "cesargb/laravel-magiclink": ">=2,<2.25.1", "cesnet/simplesamlphp-module-proxystatistics": "<3.1", "chriskacerguis/codeigniter-restserver": "<=2.7.1", "chrome-php/chrome": "<1.14", @@ -11315,6 +11322,7 @@ "devgroup/dotplant": "<2020.09.14-dev", "digimix/wp-svg-upload": "<=1", "directmailteam/direct-mail": "<6.0.3|>=7,<7.0.3|>=8,<9.5.2", + "directorytree/imapengine": "<1.22.3", "dl/yag": "<3.0.1", "dmk/webkitpdf": "<1.1.4", "dnadesign/silverstripe-elemental": "<5.3.12", @@ -11417,7 +11425,7 @@ "filegator/filegator": "<7.8", "filp/whoops": "<2.1.13", "fineuploader/php-traditional-server": "<=1.2.2", - "firebase/php-jwt": "<6", + "firebase/php-jwt": "<7", "fisharebest/webtrees": "<=2.1.18", "fixpunkt/fp-masterquiz": "<2.2.1|>=3,<3.5.2", "fixpunkt/fp-newsletter": "<1.1.1|>=1.2,<2.1.2|>=2.2,<3.2.6", @@ -11455,7 +11463,7 @@ "genix/cms": "<=1.1.11", "georgringer/news": "<1.3.3", "geshi/geshi": "<=1.0.9.1", - "getformwork/formwork": "<2.2", + "getformwork/formwork": "<=2.3.3", "getgrav/grav": "<1.11.0.0-beta1", "getkirby/cms": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1|>=5,<=5.2.1", "getkirby/kirby": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1", @@ -11497,7 +11505,7 @@ "ibexa/solr": ">=4.5,<4.5.4", "ibexa/user": ">=4,<4.4.3|>=5,<5.0.4", "icecoder/icecoder": "<=8.1", - "idno/known": "<=1.3.1", + "idno/known": "<=1.6.2", "ilicmiljan/secure-props": ">=1.2,<1.2.2", "illuminate/auth": "<5.5.10", "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<6.18.31|>=7,<7.22.4", @@ -11578,7 +11586,7 @@ "leantime/leantime": "<3.3", "lexik/jwt-authentication-bundle": "<2.10.7|>=2.11,<2.11.3", "libreform/libreform": ">=2,<=2.0.8", - "librenms/librenms": "<25.12", + "librenms/librenms": "<26.2", "liftkit/database": "<2.13.2", "lightsaml/lightsaml": "<1.3.5", "limesurvey/limesurvey": "<6.5.12", @@ -11751,6 +11759,7 @@ "phpwhois/phpwhois": "<=4.2.5", "phpxmlrpc/extras": "<0.6.1", "phpxmlrpc/phpxmlrpc": "<4.9.2", + "phraseanet/phraseanet": "==4.0.3", "pi/pi": "<=2.5", "pimcore/admin-ui-classic-bundle": "<=1.7.15|>=2.0.0.0-RC1-dev,<=2.2.2", "pimcore/customer-management-framework-bundle": "<4.2.1", @@ -11785,7 +11794,7 @@ "propel/propel": ">=2.0.0.0-alpha1,<=2.0.0.0-alpha7", "propel/propel1": ">=1,<=1.7.1", "psy/psysh": "<=0.11.22|>=0.12,<=0.12.18", - "pterodactyl/panel": "<1.12", + "pterodactyl/panel": "<1.12.1", "ptheofan/yii2-statemachine": ">=2.0.0.0-RC1-dev,<=2", "ptrofimov/beanstalk_console": "<1.7.14", "pubnub/pubnub": "<6.1", @@ -11888,7 +11897,7 @@ "starcitizentools/short-description": ">=4,<4.0.1", "starcitizentools/tabber-neue": ">=1.9.1,<2.7.2|>=3,<3.1.1", "starcitizenwiki/embedvideo": "<=4", - "statamic/cms": "<=5.22", + "statamic/cms": "<5.73.9|>=6,<6.3.2", "stormpath/sdk": "<9.9.99", "studio-42/elfinder": "<=2.1.64", "studiomitte/friendlycaptcha": "<0.1.4", @@ -12060,7 +12069,7 @@ "wpanel/wpanel4-cms": "<=4.3.1", "wpcloud/wp-stateless": "<3.2", "wpglobus/wpglobus": "<=1.9.6", - "wwbn/avideo": "<14.3", + "wwbn/avideo": "<21", "xataface/xataface": "<3", "xpressengine/xpressengine": "<3.0.15", "yab/quarx": "<2.4.5", @@ -12119,7 +12128,8 @@ "zf-commons/zfc-user": "<1.2.2", "zfcampus/zf-apigility-doctrine": ">=1,<1.0.3", "zfr/zfr-oauth2-server-module": "<0.1.2", - "zoujingli/thinkadmin": "<=6.1.53" + "zoujingli/thinkadmin": "<=6.1.53", + "zumba/json-serializer": "<3.2.3" }, "default-branch": true, "type": "metapackage", @@ -12157,7 +12167,7 @@ "type": "tidelift" } ], - "time": "2026-02-10T00:36:12+00:00" + "time": "2026-02-20T22:06:39+00:00" }, { "name": "sebastian/cli-parser", @@ -13262,16 +13272,16 @@ }, { "name": "spatie/ray", - "version": "1.46.0", + "version": "1.47.0", "source": { "type": "git", "url": "https://github.com/spatie/ray.git", - "reference": "a227afd1899581d81af1fd5ebec03d34157ed2d2" + "reference": "3112acb6a7fbcefe35f6e47b1dc13341ff5bc5ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/ray/zipball/a227afd1899581d81af1fd5ebec03d34157ed2d2", - "reference": "a227afd1899581d81af1fd5ebec03d34157ed2d2", + "url": "https://api.github.com/repos/spatie/ray/zipball/3112acb6a7fbcefe35f6e47b1dc13341ff5bc5ce", + "reference": "3112acb6a7fbcefe35f6e47b1dc13341ff5bc5ce", "shasum": "" }, "require": { @@ -13285,7 +13295,7 @@ "symfony/var-dumper": "^4.2|^5.1|^6.0|^7.0.3|^8.0" }, "require-dev": { - "illuminate/support": "^7.20|^8.18|^9.0|^10.0|^11.0|^12.0", + "illuminate/support": "^7.20|^8.18|^9.0|^10.0|^11.0|^12.0|^13.0", "nesbot/carbon": "^2.63|^3.8.4", "pestphp/pest": "^1.22", "phpstan/phpstan": "^1.10.57|^2.0.3", @@ -13331,7 +13341,7 @@ ], "support": { "issues": "https://github.com/spatie/ray/issues", - "source": "https://github.com/spatie/ray/tree/1.46.0" + "source": "https://github.com/spatie/ray/tree/1.47.0" }, "funding": [ { @@ -13343,7 +13353,7 @@ "type": "other" } ], - "time": "2026-02-06T07:38:10+00:00" + "time": "2026-02-20T20:42:26+00:00" }, { "name": "staabm/side-effects-detector", diff --git a/src/Providers/GraphQlServiceProvider.php b/src/Providers/GraphQlServiceProvider.php index 565383b4..0c5d090f 100644 --- a/src/Providers/GraphQlServiceProvider.php +++ b/src/Providers/GraphQlServiceProvider.php @@ -165,7 +165,7 @@ protected function graphQlSchemas() GraphQL::addType(UploadType::class); } - protected function registerGraphQlHttpMiddleware() + protected function registerGraphQlHttpMiddleware(): void { $httpMiddlewares = Package::getClassesThatImplementInterface(PlatformGraphQlHttpMiddleware::class); @@ -190,7 +190,7 @@ protected function registerGraphQlHttpMiddleware() }); } - protected function registerGraphQlExecutionMiddleware() + protected function registerGraphQlExecutionMiddleware(): void { $executionMiddlewares = Package::getClassesThatImplementInterface(PlatformGraphQlExecutionMiddleware::class); @@ -215,7 +215,7 @@ protected function registerGraphQlExecutionMiddleware() }); } - protected function registerExternalResolverMiddleware() + protected function registerExternalResolverMiddleware(): void { $resolverMiddlewares = Package::getClassesThatImplementInterface(PlatformGraphQlResolverMiddleware::class) ->map(function ($resolverMiddleware) { @@ -265,7 +265,7 @@ protected function registerGraphiqlEndpoints(): void /** * Set the network for the graphql. */ - private function setNetwork() + private function setNetwork(): void { $segments = request()->segments(); $network = array_values(array_intersect($segments, array_keys(config('enjin-platform.chains.supported')))); From df20e1e43d87c5ff97a5dc23e05827f48d1c9d2a Mon Sep 17 00:00:00 2001 From: Pawel Wankiewicz Date: Sun, 22 Feb 2026 21:52:22 +0100 Subject: [PATCH 03/21] PLA-2317 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4e7476bf..13dedfae 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "gmajor/sr25519-bindings": "^1.0", "gmajor/substrate-codec-php": "^1.1", "guzzlehttp/guzzle": "^7.10", - "illuminate/contracts": "^11.0", + "illuminate/contracts": "^11.0|^12.0", "kevinrob/guzzle-cache-middleware": "^6.0", "laravel/pail": "^1.2", "mll-lab/laravel-graphiql": "^4.0.1", From 9092054599e952c78f79f4ba83d781630a720c11 Mon Sep 17 00:00:00 2001 From: Pawel Wankiewicz Date: Sun, 22 Feb 2026 21:52:27 +0100 Subject: [PATCH 04/21] PLA-2317 --- composer.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index 00425492..40fdf4b9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e70ae935f4295f9b01c003bf08be3bb7", + "content-hash": "bd6f25fb6dda6a28be863660b7ec8f5b", "packages": [ { "name": "amphp/amp", From 45b92da6b88f7fbe1819726898880c443d7c274e Mon Sep 17 00:00:00 2001 From: Pawel Wankiewicz Date: Mon, 23 Feb 2026 12:12:53 +0100 Subject: [PATCH 05/21] PLA-2317 --- config/enjin-platform.php | 11 +++++++++++ src/Clients/Abstracts/WebsocketAbstract.php | 7 ++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/config/enjin-platform.php b/config/enjin-platform.php index 4777cfa7..24bba775 100644 --- a/config/enjin-platform.php +++ b/config/enjin-platform.php @@ -223,6 +223,17 @@ 'image_size' => env('QR_CODE_IMAGE_SIZE', .20), ], + /* + |-------------------------------------------------------------------------- + | The websocket client timeout + |-------------------------------------------------------------------------- + | + | Here you may configure the timeout for internal websocket clients + | connecting to the Substrate nodes (value in seconds). + | + */ + 'websocket_client_timeout' => env('ENJIN_WEBSOCKET_CLIENT_TIMEOUT', 30), + /* |-------------------------------------------------------------------------- | The ingest sync wait timeout diff --git a/src/Clients/Abstracts/WebsocketAbstract.php b/src/Clients/Abstracts/WebsocketAbstract.php index 91d24e07..d902df28 100644 --- a/src/Clients/Abstracts/WebsocketAbstract.php +++ b/src/Clients/Abstracts/WebsocketAbstract.php @@ -89,8 +89,9 @@ public function receive(): mixed $start = now(); while (true) { - if (now()->diffInSeconds($start) >= 30) { - throw new PlatformException(__('enjin-platform::error.websocket.receive_timeout', ['seconds' => 30])); + $timeout = config('enjin-platform.websocket_client_timeout', 30); + if (now()->diffInSeconds($start) >= $timeout) { + throw new PlatformException(__('enjin-platform::error.websocket.receive_timeout', ['seconds' => $timeout])); } $message = $this->client->receive(); @@ -136,7 +137,7 @@ protected function client(): Client $this->client ->addMiddleware(new CloseHandler()) ->addMiddleware(new PingResponder()) - ->setTimeout(30); + ->setTimeout(config('enjin-platform.websocket_client_timeout', 30)); Log::info('Websocket client created.', ['host' => $this->host]); } From 0fedb1c3845e99840df244f94eafbe8237d75224 Mon Sep 17 00:00:00 2001 From: Pawel Wankiewicz Date: Mon, 23 Feb 2026 15:19:58 +0100 Subject: [PATCH 06/21] PLA-2317 --- ...3_000000_add_timestamp_to_blocks_table.php | 29 +++++++++++++++++++ src/Commands/TransactionChecker.php | 10 +++++++ .../Mutations/TokenHolderSnapshotMutation.php | 9 ++++-- src/Models/Laravel/Block.php | 25 +++++++++++++++- .../Processor/Substrate/BlockProcessor.php | 9 ++++++ 5 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 database/migrations/2026_02_23_000000_add_timestamp_to_blocks_table.php diff --git a/database/migrations/2026_02_23_000000_add_timestamp_to_blocks_table.php b/database/migrations/2026_02_23_000000_add_timestamp_to_blocks_table.php new file mode 100644 index 00000000..9bec41d4 --- /dev/null +++ b/database/migrations/2026_02_23_000000_add_timestamp_to_blocks_table.php @@ -0,0 +1,29 @@ +timestamp('timestamp')->nullable()->after('hash'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('blocks', function (Blueprint $table) { + $table->dropColumn('timestamp'); + }); + } +}; diff --git a/src/Commands/TransactionChecker.php b/src/Commands/TransactionChecker.php index 64f596d0..38a196a7 100644 --- a/src/Commands/TransactionChecker.php +++ b/src/Commands/TransactionChecker.php @@ -128,6 +128,16 @@ public function handle(Substrate $client, Codec $codec): void $block->events = $this->fetchEvents($block, $client); $block->extrinsics = $extrinsics; + foreach ($block->extrinsics ?? [] as $extrinsic) { + if ($extrinsic->module === 'Timestamp' && $extrinsic->call === 'set') { + $block->timestamp = Carbon::createFromTimestampMs(Arr::get($extrinsic->params, 'now')); + + break; + } + } + + $block->save(); + $hasExtrinsicErrors = (new ExtrinsicProcessor($block, $this->codec))->run(); if (!empty($hasExtrinsicErrors)) { $this->error(json_encode($hasExtrinsicErrors)); diff --git a/src/GraphQL/Schemas/Primary/Mutations/TokenHolderSnapshotMutation.php b/src/GraphQL/Schemas/Primary/Mutations/TokenHolderSnapshotMutation.php index 4aa448b6..0bbca65d 100644 --- a/src/GraphQL/Schemas/Primary/Mutations/TokenHolderSnapshotMutation.php +++ b/src/GraphQL/Schemas/Primary/Mutations/TokenHolderSnapshotMutation.php @@ -88,9 +88,12 @@ public function resolve($root, array $args, $context, ResolveInfo $resolveInfo, $timestamp = ''; if ($blockOrTimestamp = Arr::get($args, 'blockOrTimestamp')) { - $timestamp = strlen((string) $blockOrTimestamp) === 26 - ? Carbon::createFromTimestamp($timestamp)->toDateTimeString() - : Block::where('number', $blockOrTimestamp)->value('created_at')?->toDateTimeString(); + if (strlen((string) $blockOrTimestamp) === 26) { + $timestamp = Carbon::createFromTimestamp($blockOrTimestamp)->toDateTimeString(); + } else { + $block = Block::where('number', $blockOrTimestamp)->first(['timestamp', 'created_at']); + $timestamp = ($block?->timestamp ?? $block?->created_at)?->toDateTimeString(); + } } $tokens = Token::with(['accounts', 'accounts.wallet']) diff --git a/src/Models/Laravel/Block.php b/src/Models/Laravel/Block.php index 2faf5327..c6ab0a70 100644 --- a/src/Models/Laravel/Block.php +++ b/src/Models/Laravel/Block.php @@ -30,6 +30,7 @@ class Block extends BaseModel public $fillable = [ 'number', 'hash', + 'timestamp', 'synced', 'failed', 'exception', @@ -40,13 +41,35 @@ class Block extends BaseModel 'updated_at', ]; + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'timestamp' => 'datetime', + 'synced' => 'boolean', + 'failed' => 'boolean', + 'retried' => 'boolean', + ]; + /** * Get the prunable model query. */ public function prunable(): Builder { if (!empty($days = config('enjin-platform.prune_blocks'))) { - return static::where('created_at', '<=', now()->subDays($days)); + $cutoff = now()->subDays($days); + + return static::where(function (Builder $query) use ($cutoff): void { + $query->where(function (Builder $query) use ($cutoff): void { + $query->whereNotNull('timestamp') + ->where('timestamp', '<=', $cutoff); + })->orWhere(function (Builder $query) use ($cutoff): void { + $query->whereNull('timestamp') + ->where('created_at', '<=', $cutoff); + }); + }); } return static::where('id', 0); diff --git a/src/Services/Processor/Substrate/BlockProcessor.php b/src/Services/Processor/Substrate/BlockProcessor.php index 5b9c8774..ada403cf 100644 --- a/src/Services/Processor/Substrate/BlockProcessor.php +++ b/src/Services/Processor/Substrate/BlockProcessor.php @@ -2,6 +2,7 @@ namespace Enjin\Platform\Services\Processor\Substrate; +use Carbon\Carbon; use Enjin\BlockchainTools\HexConverter; use Enjin\Platform\Clients\Implementations\SubstrateHttpClient; use Enjin\Platform\Clients\Implementations\SubstrateSocketClient; @@ -349,6 +350,14 @@ protected function fetchExtrinsics(Block $block): Block $block->number ); + foreach ($block->extrinsics ?? [] as $extrinsic) { + if ($extrinsic->module === 'Timestamp' && $extrinsic->call === 'set') { + $block->timestamp = Carbon::createFromTimestampMs(Arr::get($extrinsic->params, 'now')); + + break; + } + } + $this->info(sprintf('Ingested extrinsics for block #%s in %s seconds', $block->number, $syncTime->diffInMilliseconds(now()) / 1000)); return $block; From ec458f80391c3f6aec4709ebb61e5d28d5c4ef16 Mon Sep 17 00:00:00 2001 From: Pawel Wankiewicz Date: Mon, 23 Feb 2026 15:40:01 +0100 Subject: [PATCH 07/21] PLA-2317 --- .../2026_02_23_000000_add_timestamp_to_blocks_table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/2026_02_23_000000_add_timestamp_to_blocks_table.php b/database/migrations/2026_02_23_000000_add_timestamp_to_blocks_table.php index 9bec41d4..3b4d0bba 100644 --- a/database/migrations/2026_02_23_000000_add_timestamp_to_blocks_table.php +++ b/database/migrations/2026_02_23_000000_add_timestamp_to_blocks_table.php @@ -13,7 +13,7 @@ public function up(): void { Schema::table('blocks', function (Blueprint $table) { - $table->timestamp('timestamp')->nullable()->after('hash'); + $table->timestamp('timestamp')->nullable()->after('extrinsics'); }); } From 8fa73a7c22b9ab8c8b10a561202f73a4d4ddb00c Mon Sep 17 00:00:00 2001 From: Pawel Wankiewicz Date: Tue, 24 Feb 2026 10:43:07 +0100 Subject: [PATCH 08/21] PLA-2317 --- .../Processor/Substrate/BlockProcessor.php | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Services/Processor/Substrate/BlockProcessor.php b/src/Services/Processor/Substrate/BlockProcessor.php index ada403cf..13d47e68 100644 --- a/src/Services/Processor/Substrate/BlockProcessor.php +++ b/src/Services/Processor/Substrate/BlockProcessor.php @@ -208,10 +208,21 @@ public function process(Block $block): ?Block $hasEventErrors = (new EventProcessor($block, $this->codec))->run(); $hasExtrinsicErrors = (new ExtrinsicProcessor($block, $this->codec))->run(); - if ($hasEventErrors || $hasExtrinsicErrors) { - $errors = implode(';', [...$hasEventErrors, ...$hasExtrinsicErrors]); - throw new Exception($errors); + $errors = []; + if ($hasEventErrors) { + $errors = array_merge($errors, $hasEventErrors); + } + if ($hasExtrinsicErrors) { + $errors = array_merge($errors, $hasExtrinsicErrors); + } + + if ($block->number > 0 && empty($block->timestamp)) { + $errors[] = 'Missing Extrinsic Timestamp::set'; + } + + if (!empty($errors)) { + throw new Exception(implode(';', $errors)); } $block->fill(['synced' => true, 'failed' => false, 'exception' => null])->save(); From 9ed6c3b2d1d596cecf0e0c5c24c32de27b4bc524 Mon Sep 17 00:00:00 2001 From: Pawel Wankiewicz Date: Tue, 24 Feb 2026 11:26:55 +0100 Subject: [PATCH 09/21] PLA-2317 --- ...00_add_timestamp_index_to_blocks_table.php | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 database/migrations/2026_02_24_000000_add_timestamp_index_to_blocks_table.php diff --git a/database/migrations/2026_02_24_000000_add_timestamp_index_to_blocks_table.php b/database/migrations/2026_02_24_000000_add_timestamp_index_to_blocks_table.php new file mode 100644 index 00000000..233796ba --- /dev/null +++ b/database/migrations/2026_02_24_000000_add_timestamp_index_to_blocks_table.php @@ -0,0 +1,29 @@ +index('timestamp'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('blocks', function (Blueprint $table) { + $table->dropIndex(['timestamp']); + }); + } +}; From e933dac57fdadb2a31d930290039264b9fca3938 Mon Sep 17 00:00:00 2001 From: Pawel Wankiewicz Date: Wed, 25 Feb 2026 23:33:58 +0100 Subject: [PATCH 10/21] PLA-2317: verify block number from chain_getBlock header before processing When chain_getBlock returns data, extract block.header.number and compare with the stored block number. If there's a mismatch (stale hash from chain reorg), re-fetch the correct hash and retry. This prevents false 'Missing Extrinsic Timestamp::set' errors caused by hash/number mismatches. Co-Authored-By: Claude Opus 4.6 --- .../Processor/Substrate/BlockProcessor.php | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/Services/Processor/Substrate/BlockProcessor.php b/src/Services/Processor/Substrate/BlockProcessor.php index 13d47e68..6e0f86de 100644 --- a/src/Services/Processor/Substrate/BlockProcessor.php +++ b/src/Services/Processor/Substrate/BlockProcessor.php @@ -351,6 +351,27 @@ protected function fetchExtrinsics(Block $block): Block $block->number ); + $chainBlockNumber = Arr::get($data, 'block.header.number'); + if ($chainBlockNumber !== null) { + $chainBlockNumber = (int) HexConverter::hexToUInt($chainBlockNumber); + + if ($chainBlockNumber !== $block->number) { + $this->warn("Block #{$block->number}: hash {$block->hash} points to block #{$chainBlockNumber} on chain. Re-fetching correct hash."); + + $newHash = $this->httpClient->jsonRpc('chain_getBlockHash', [$block->number]); + if (is_string($newHash) && str_starts_with($newHash, '0x')) { + $block->hash = $newHash; + $block->save(); + + $data = $this->runOrWaitIfEmpty( + fn () => $this->httpClient->jsonRpc('chain_getBlock', [$block->hash]), + 'extrinsics', + $block->number + ); + } + } + } + if (empty($extrinsics = Arr::get($data, 'block.extrinsics'))) { return $block; } From c6cbb1266152f043e1e9b576d21f3c9fe81ed8b3 Mon Sep 17 00:00:00 2001 From: Pawel Wankiewicz Date: Fri, 27 Feb 2026 09:08:39 +0100 Subject: [PATCH 11/21] PLA-2317 --- src/Models/ModelResolver.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Models/ModelResolver.php b/src/Models/ModelResolver.php index ff4e67d6..41eb6370 100644 --- a/src/Models/ModelResolver.php +++ b/src/Models/ModelResolver.php @@ -5,6 +5,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; +use Override; use ReflectionClass; abstract class ModelResolver extends Model @@ -33,7 +34,7 @@ public function __construct(array $attributes = []) /** * Dynamically pass methods to the model. */ - #[\Override] + #[Override] public function __call($method, $parameters) { return $this->model->{$method}(...$parameters); @@ -42,12 +43,12 @@ public function __call($method, $parameters) /** * Dynamically pass static methods to the model. */ - #[\Override] + #[Override] public static function __callStatic($method, $parameters) { $class = static::resolveClassFqn(static::class); - return $class::$method(...$parameters); + return (new $class)->$method(...$parameters); } /** From f207de41ec38cce0e953ff54c6ba4644f51ca6cd Mon Sep 17 00:00:00 2001 From: Pawel Wankiewicz Date: Fri, 27 Feb 2026 09:18:11 +0100 Subject: [PATCH 12/21] PLA-2317 --- src/Models/ModelResolver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Models/ModelResolver.php b/src/Models/ModelResolver.php index 41eb6370..7c4067a9 100644 --- a/src/Models/ModelResolver.php +++ b/src/Models/ModelResolver.php @@ -48,7 +48,7 @@ public static function __callStatic($method, $parameters) { $class = static::resolveClassFqn(static::class); - return (new $class)->$method(...$parameters); + return (new $class())->{$method}(...$parameters); } /** From ef60d061abe6b0c941c1a83d647b7fb28dffe852 Mon Sep 17 00:00:00 2001 From: Pawel Wankiewicz Date: Tue, 3 Mar 2026 16:36:29 +0100 Subject: [PATCH 13/21] PLA-2317 --- src/Commands/TransactionChecker.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/TransactionChecker.php b/src/Commands/TransactionChecker.php index 38a196a7..67df5742 100644 --- a/src/Commands/TransactionChecker.php +++ b/src/Commands/TransactionChecker.php @@ -57,7 +57,7 @@ public function handle(Substrate $client, Codec $codec): void ->take(100) ->get(); - $maxBlockToCheck = $syncedBlocks->last()->number; + $maxBlockToCheck = $syncedBlocks?->last()?->number ?? 0; $transactions = collect(Transaction::whereIn('state', [TransactionState::BROADCAST, TransactionState::EXECUTED]) ->where('network', currentMatrix()->name) From a6632cec0603346152582161f4cc8a82f69633b7 Mon Sep 17 00:00:00 2001 From: Pawel Wankiewicz Date: Tue, 3 Mar 2026 18:55:28 +0100 Subject: [PATCH 14/21] PLA-2317: fix getCallName to fallback to runtime metadata call indexes Co-Authored-By: Claude Opus 4.6 --- src/Services/Processor/Substrate/Codec/Encoder.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Services/Processor/Substrate/Codec/Encoder.php b/src/Services/Processor/Substrate/Codec/Encoder.php index 22dc7dd7..36bfb46b 100644 --- a/src/Services/Processor/Substrate/Codec/Encoder.php +++ b/src/Services/Processor/Substrate/Codec/Encoder.php @@ -139,6 +139,11 @@ public static function getCallName(int $palletIndex, int $functionIndex): string { $result = array_search([$palletIndex, $functionIndex], static::$overrideCallIndex); + if ($result === false) { + $hexKey = sprintf('%02x%02x', $palletIndex, $functionIndex); + $result = array_search($hexKey, static::$callIndexes); + } + if ($result === false) { throw new PlatformException('Unsupported call index: ' . $palletIndex . '.' . $functionIndex); } From c6a1241b9ed5d5823c79af5eee6190ee8f4bb631 Mon Sep 17 00:00:00 2001 From: Pawel Wankiewicz Date: Tue, 3 Mar 2026 19:40:37 +0100 Subject: [PATCH 15/21] PLA-2317 --- composer.lock | 255 +++++++++++++------------- src/Commands/Sync.php | 5 +- src/Commands/contexts/get_storage.php | 5 +- src/Jobs/HotSync.php | 5 +- 4 files changed, 141 insertions(+), 129 deletions(-) diff --git a/composer.lock b/composer.lock index 40fdf4b9..fb31e763 100644 --- a/composer.lock +++ b/composer.lock @@ -3273,16 +3273,16 @@ }, { "name": "laravel/serializable-closure", - "version": "v2.0.9", + "version": "v2.0.10", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "8f631589ab07b7b52fead814965f5a800459cb3e" + "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/8f631589ab07b7b52fead814965f5a800459cb3e", - "reference": "8f631589ab07b7b52fead814965f5a800459cb3e", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/870fc81d2f879903dfc5b60bf8a0f94a1609e669", + "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669", "shasum": "" }, "require": { @@ -3330,7 +3330,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2026-02-03T06:55:34+00:00" + "time": "2026-02-20T19:59:49+00:00" }, { "name": "league/commonmark", @@ -3523,16 +3523,16 @@ }, { "name": "league/flysystem", - "version": "3.31.0", + "version": "3.32.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "1717e0b3642b0df65ecb0cc89cdd99fa840672ff" + "reference": "254b1595b16b22dbddaaef9ed6ca9fdac4956725" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/1717e0b3642b0df65ecb0cc89cdd99fa840672ff", - "reference": "1717e0b3642b0df65ecb0cc89cdd99fa840672ff", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/254b1595b16b22dbddaaef9ed6ca9fdac4956725", + "reference": "254b1595b16b22dbddaaef9ed6ca9fdac4956725", "shasum": "" }, "require": { @@ -3600,9 +3600,9 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.31.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.32.0" }, - "time": "2026-01-23T15:38:47+00:00" + "time": "2026-02-25T17:01:41+00:00" }, { "name": "league/flysystem-local", @@ -4251,16 +4251,16 @@ }, { "name": "nette/schema", - "version": "v1.3.4", + "version": "v1.3.5", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "086497a2f34b82fede9b5a41cc8e131d087cd8f7" + "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/086497a2f34b82fede9b5a41cc8e131d087cd8f7", - "reference": "086497a2f34b82fede9b5a41cc8e131d087cd8f7", + "url": "https://api.github.com/repos/nette/schema/zipball/f0ab1a3cda782dbc5da270d28545236aa80c4002", + "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002", "shasum": "" }, "require": { @@ -4268,8 +4268,10 @@ "php": "8.1 - 8.5" }, "require-dev": { + "nette/phpstan-rules": "^1.0", "nette/tester": "^2.6", - "phpstan/phpstan": "^2.0@stable", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1.39@stable", "tracy/tracy": "^2.8" }, "type": "library", @@ -4310,9 +4312,9 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.3.4" + "source": "https://github.com/nette/schema/tree/v1.3.5" }, - "time": "2026-02-08T02:54:00+00:00" + "time": "2026-02-23T03:47:12+00:00" }, { "name": "nette/utils", @@ -6195,16 +6197,16 @@ }, { "name": "symfony/console", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894" + "reference": "6d643a93b47398599124022eb24d97c153c12f27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/41e38717ac1dd7a46b6bda7d6a82af2d98a78894", - "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894", + "url": "https://api.github.com/repos/symfony/console/zipball/6d643a93b47398599124022eb24d97c153c12f27", + "reference": "6d643a93b47398599124022eb24d97c153c12f27", "shasum": "" }, "require": { @@ -6269,7 +6271,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.4" + "source": "https://github.com/symfony/console/tree/v7.4.6" }, "funding": [ { @@ -6289,20 +6291,20 @@ "type": "tidelift" } ], - "time": "2026-01-13T11:36:38+00:00" + "time": "2026-02-25T17:02:47+00:00" }, { "name": "symfony/css-selector", - "version": "v7.4.0", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135" + "reference": "2e7c52c647b406e2107dd867db424a4dbac91864" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/ab862f478513e7ca2fe9ec117a6f01a8da6e1135", - "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/2e7c52c647b406e2107dd867db424a4dbac91864", + "reference": "2e7c52c647b406e2107dd867db424a4dbac91864", "shasum": "" }, "require": { @@ -6338,7 +6340,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.4.0" + "source": "https://github.com/symfony/css-selector/tree/v7.4.6" }, "funding": [ { @@ -6358,7 +6360,7 @@ "type": "tidelift" } ], - "time": "2025-10-30T13:39:42+00:00" + "time": "2026-02-17T07:53:42+00:00" }, { "name": "symfony/deprecation-contracts", @@ -6672,16 +6674,16 @@ }, { "name": "symfony/finder", - "version": "v7.4.5", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb" + "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", - "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", + "url": "https://api.github.com/repos/symfony/finder/zipball/8655bf1076b7a3a346cb11413ffdabff50c7ffcf", + "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf", "shasum": "" }, "require": { @@ -6716,7 +6718,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.5" + "source": "https://github.com/symfony/finder/tree/v7.4.6" }, "funding": [ { @@ -6736,20 +6738,20 @@ "type": "tidelift" } ], - "time": "2026-01-26T15:07:59+00:00" + "time": "2026-01-29T09:40:50+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.4.5", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "446d0db2b1f21575f1284b74533e425096abdfb6" + "reference": "fd97d5e926e988a363cef56fbbf88c5c528e9065" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/446d0db2b1f21575f1284b74533e425096abdfb6", - "reference": "446d0db2b1f21575f1284b74533e425096abdfb6", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/fd97d5e926e988a363cef56fbbf88c5c528e9065", + "reference": "fd97d5e926e988a363cef56fbbf88c5c528e9065", "shasum": "" }, "require": { @@ -6798,7 +6800,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.4.5" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.6" }, "funding": [ { @@ -6818,20 +6820,20 @@ "type": "tidelift" } ], - "time": "2026-01-27T16:16:02+00:00" + "time": "2026-02-21T16:25:55+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.4.5", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "229eda477017f92bd2ce7615d06222ec0c19e82a" + "reference": "002ac0cf4cd972a7fd0912dcd513a95e8a81ce83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/229eda477017f92bd2ce7615d06222ec0c19e82a", - "reference": "229eda477017f92bd2ce7615d06222ec0c19e82a", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/002ac0cf4cd972a7fd0912dcd513a95e8a81ce83", + "reference": "002ac0cf4cd972a7fd0912dcd513a95e8a81ce83", "shasum": "" }, "require": { @@ -6873,7 +6875,7 @@ "symfony/config": "^6.4|^7.0|^8.0", "symfony/console": "^6.4|^7.0|^8.0", "symfony/css-selector": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4.1|^7.0.1|^8.0", "symfony/dom-crawler": "^6.4|^7.0|^8.0", "symfony/expression-language": "^6.4|^7.0|^8.0", "symfony/finder": "^6.4|^7.0|^8.0", @@ -6917,7 +6919,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.5" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.6" }, "funding": [ { @@ -6937,20 +6939,20 @@ "type": "tidelift" } ], - "time": "2026-01-28T10:33:42+00:00" + "time": "2026-02-26T08:30:57+00:00" }, { "name": "symfony/mailer", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "7b750074c40c694ceb34cb926d6dffee231c5cd6" + "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/7b750074c40c694ceb34cb926d6dffee231c5cd6", - "reference": "7b750074c40c694ceb34cb926d6dffee231c5cd6", + "url": "https://api.github.com/repos/symfony/mailer/zipball/b02726f39a20bc65e30364f5c750c4ddbf1f58e9", + "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9", "shasum": "" }, "require": { @@ -7001,7 +7003,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.4.4" + "source": "https://github.com/symfony/mailer/tree/v7.4.6" }, "funding": [ { @@ -7021,20 +7023,20 @@ "type": "tidelift" } ], - "time": "2026-01-08T08:25:11+00:00" + "time": "2026-02-25T16:50:00+00:00" }, { "name": "symfony/mime", - "version": "v7.4.5", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148" + "reference": "9fc881d95feae4c6c48678cb6372bd8a7ba04f5f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/b18c7e6e9eee1e19958138df10412f3c4c316148", - "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148", + "url": "https://api.github.com/repos/symfony/mime/zipball/9fc881d95feae4c6c48678cb6372bd8a7ba04f5f", + "reference": "9fc881d95feae4c6c48678cb6372bd8a7ba04f5f", "shasum": "" }, "require": { @@ -7045,7 +7047,7 @@ }, "conflict": { "egulias/email-validator": "~3.0.0", - "phpdocumentor/reflection-docblock": "<5.2|>=6", + "phpdocumentor/reflection-docblock": "<5.2|>=7", "phpdocumentor/type-resolver": "<1.5.1", "symfony/mailer": "<6.4", "symfony/serializer": "<6.4.3|>7.0,<7.0.3" @@ -7053,7 +7055,7 @@ "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", "league/html-to-markdown": "^5.0", - "phpdocumentor/reflection-docblock": "^5.2", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", "symfony/dependency-injection": "^6.4|^7.0|^8.0", "symfony/process": "^6.4|^7.0|^8.0", "symfony/property-access": "^6.4|^7.0|^8.0", @@ -7090,7 +7092,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.5" + "source": "https://github.com/symfony/mime/tree/v7.4.6" }, "funding": [ { @@ -7110,7 +7112,7 @@ "type": "tidelift" } ], - "time": "2026-01-27T08:59:58+00:00" + "time": "2026-02-05T15:57:06+00:00" }, { "name": "symfony/polyfill-ctype", @@ -7928,16 +7930,16 @@ }, { "name": "symfony/routing", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "0798827fe2c79caeed41d70b680c2c3507d10147" + "reference": "238d749c56b804b31a9bf3e26519d93b65a60938" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/0798827fe2c79caeed41d70b680c2c3507d10147", - "reference": "0798827fe2c79caeed41d70b680c2c3507d10147", + "url": "https://api.github.com/repos/symfony/routing/zipball/238d749c56b804b31a9bf3e26519d93b65a60938", + "reference": "238d749c56b804b31a9bf3e26519d93b65a60938", "shasum": "" }, "require": { @@ -7989,7 +7991,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.4.4" + "source": "https://github.com/symfony/routing/tree/v7.4.6" }, "funding": [ { @@ -8009,7 +8011,7 @@ "type": "tidelift" } ], - "time": "2026-01-12T12:19:02+00:00" + "time": "2026-02-25T16:50:00+00:00" }, { "name": "symfony/service-contracts", @@ -8100,16 +8102,16 @@ }, { "name": "symfony/string", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f" + "reference": "9f209231affa85aa930a5e46e6eb03381424b30b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/1c4b10461bf2ec27537b5f36105337262f5f5d6f", - "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f", + "url": "https://api.github.com/repos/symfony/string/zipball/9f209231affa85aa930a5e46e6eb03381424b30b", + "reference": "9f209231affa85aa930a5e46e6eb03381424b30b", "shasum": "" }, "require": { @@ -8167,7 +8169,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.4" + "source": "https://github.com/symfony/string/tree/v7.4.6" }, "funding": [ { @@ -8187,20 +8189,20 @@ "type": "tidelift" } ], - "time": "2026-01-12T10:54:30+00:00" + "time": "2026-02-09T09:33:46+00:00" }, { "name": "symfony/translation", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "bfde13711f53f549e73b06d27b35a55207528877" + "reference": "1888cf064399868af3784b9e043240f1d89d25ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/bfde13711f53f549e73b06d27b35a55207528877", - "reference": "bfde13711f53f549e73b06d27b35a55207528877", + "url": "https://api.github.com/repos/symfony/translation/zipball/1888cf064399868af3784b9e043240f1d89d25ce", + "reference": "1888cf064399868af3784b9e043240f1d89d25ce", "shasum": "" }, "require": { @@ -8267,7 +8269,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.4.4" + "source": "https://github.com/symfony/translation/tree/v7.4.6" }, "funding": [ { @@ -8287,7 +8289,7 @@ "type": "tidelift" } ], - "time": "2026-01-13T10:40:19+00:00" + "time": "2026-02-17T07:53:42+00:00" }, { "name": "symfony/translation-contracts", @@ -8451,16 +8453,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "0e4769b46a0c3c62390d124635ce59f66874b282" + "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0e4769b46a0c3c62390d124635ce59f66874b282", - "reference": "0e4769b46a0c3c62390d124635ce59f66874b282", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/045321c440ac18347b136c63d2e9bf28a2dc0291", + "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291", "shasum": "" }, "require": { @@ -8514,7 +8516,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.4.4" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.6" }, "funding": [ { @@ -8534,7 +8536,7 @@ "type": "tidelift" } ], - "time": "2026-01-01T22:13:48+00:00" + "time": "2026-02-15T10:53:20+00:00" }, { "name": "thecodingmachine/safe", @@ -9289,40 +9291,40 @@ }, { "name": "larastan/larastan", - "version": "v3.9.2", + "version": "v3.9.3", "source": { "type": "git", "url": "https://github.com/larastan/larastan.git", - "reference": "2e9ed291bdc1969e7f270fb33c9cdf3c912daeb2" + "reference": "64a52bcc5347c89fdf131cb59f96ebfbc8d1ad65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/larastan/larastan/zipball/2e9ed291bdc1969e7f270fb33c9cdf3c912daeb2", - "reference": "2e9ed291bdc1969e7f270fb33c9cdf3c912daeb2", + "url": "https://api.github.com/repos/larastan/larastan/zipball/64a52bcc5347c89fdf131cb59f96ebfbc8d1ad65", + "reference": "64a52bcc5347c89fdf131cb59f96ebfbc8d1ad65", "shasum": "" }, "require": { "ext-json": "*", "iamcal/sql-parser": "^0.7.0", - "illuminate/console": "^11.44.2 || ^12.4.1", - "illuminate/container": "^11.44.2 || ^12.4.1", - "illuminate/contracts": "^11.44.2 || ^12.4.1", - "illuminate/database": "^11.44.2 || ^12.4.1", - "illuminate/http": "^11.44.2 || ^12.4.1", - "illuminate/pipeline": "^11.44.2 || ^12.4.1", - "illuminate/support": "^11.44.2 || ^12.4.1", + "illuminate/console": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/container": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/contracts": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/database": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/http": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/pipeline": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/support": "^11.44.2 || ^12.4.1 || ^13", "php": "^8.2", "phpstan/phpstan": "^2.1.32" }, "require-dev": { "doctrine/coding-standard": "^13", - "laravel/framework": "^11.44.2 || ^12.7.2", + "laravel/framework": "^11.44.2 || ^12.7.2 || ^13", "mockery/mockery": "^1.6.12", "nikic/php-parser": "^5.4", - "orchestra/canvas": "^v9.2.2 || ^10.0.1", - "orchestra/testbench-core": "^9.12.0 || ^10.1", + "orchestra/canvas": "^v9.2.2 || ^10.0.1 || ^11", + "orchestra/testbench-core": "^9.12.0 || ^10.1 || ^11", "phpstan/phpstan-deprecation-rules": "^2.0.1", - "phpunit/phpunit": "^10.5.35 || ^11.5.15" + "phpunit/phpunit": "^10.5.35 || ^11.5.15 || ^12.5.8" }, "suggest": { "orchestra/testbench": "Using Larastan for analysing a package needs Testbench", @@ -9367,7 +9369,7 @@ ], "support": { "issues": "https://github.com/larastan/larastan/issues", - "source": "https://github.com/larastan/larastan/tree/v3.9.2" + "source": "https://github.com/larastan/larastan/tree/v3.9.3" }, "funding": [ { @@ -9375,7 +9377,7 @@ "type": "github" } ], - "time": "2026-01-30T15:16:32+00:00" + "time": "2026-02-20T12:07:12+00:00" }, { "name": "laravel/pint", @@ -10390,11 +10392,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.39", + "version": "2.1.40", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c6f73a2af4cbcd99c931d0fb8f08548cc0fa8224", - "reference": "c6f73a2af4cbcd99c931d0fb8f08548cc0fa8224", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b", + "reference": "9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b", "shasum": "" }, "require": { @@ -10439,7 +10441,7 @@ "type": "github" } ], - "time": "2026-02-11T14:48:56+00:00" + "time": "2026-02-23T15:04:35+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -11149,12 +11151,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "92c5ec5685cfbcd7ef721a502e6622516728011c" + "reference": "c1109f3f28a27aa19c894df25d682b5046dc1098" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/92c5ec5685cfbcd7ef721a502e6622516728011c", - "reference": "92c5ec5685cfbcd7ef721a502e6622516728011c", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/c1109f3f28a27aa19c894df25d682b5046dc1098", + "reference": "c1109f3f28a27aa19c894df25d682b5046dc1098", "shasum": "" }, "conflict": { @@ -11458,7 +11460,7 @@ "froxlor/froxlor": "<=2.2.5", "frozennode/administrator": "<=5.0.12", "fuel/core": "<1.8.1", - "funadmin/funadmin": "<=5.0.2", + "funadmin/funadmin": "<=7.1.0.0-RC4", "gaoming13/wechat-php-sdk": "<=1.10.2", "genix/cms": "<=1.1.11", "georgringer/news": "<1.3.3", @@ -11505,7 +11507,7 @@ "ibexa/solr": ">=4.5,<4.5.4", "ibexa/user": ">=4,<4.4.3|>=5,<5.0.4", "icecoder/icecoder": "<=8.1", - "idno/known": "<=1.6.2", + "idno/known": "<1.6.4", "ilicmiljan/secure-props": ">=1.2,<1.2.2", "illuminate/auth": "<5.5.10", "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<6.18.31|>=7,<7.22.4", @@ -11617,7 +11619,7 @@ "marshmallow/nova-tiptap": "<5.7", "matomo/matomo": "<1.11", "matyhtf/framework": "<3.0.6", - "mautic/core": "<5.2.9|>=6,<6.0.7", + "mautic/core": "<5.2.10|>=6,<6.0.8|>=7.0.0.0-alpha,<7.0.1", "mautic/core-lib": ">=1.0.0.0-beta,<4.4.13|>=5.0.0.0-alpha,<5.1.1", "mautic/grapes-js-builder-bundle": ">=4,<4.4.18|>=5,<5.2.9|>=6,<6.0.7", "maximebf/debugbar": "<1.19", @@ -11650,7 +11652,7 @@ "mongodb/mongodb": ">=1,<1.9.2", "mongodb/mongodb-extension": "<1.21.2", "monolog/monolog": ">=1.8,<1.12", - "moodle/moodle": "<4.4.12|>=4.5.0.0-beta,<4.5.8|>=5.0.0.0-beta,<5.0.4|>=5.1.0.0-beta,<5.1.1", + "moodle/moodle": "<4.5.9|>=5.0.0.0-beta,<5.0.5|>=5.1.0.0-beta,<5.1.2", "moonshine/moonshine": "<=3.12.5", "mos/cimage": "<0.7.19", "movim/moxl": ">=0.8,<=0.10", @@ -11768,7 +11770,7 @@ "pimcore/demo": "<10.3", "pimcore/ecommerce-framework-bundle": "<1.0.10", "pimcore/perspective-editor": "<1.5.1", - "pimcore/pimcore": "<=11.5.13|>=12.0.0.0-RC1-dev,<12.3.1", + "pimcore/pimcore": "<=11.5.14.1|>=12,<12.3.3", "pimcore/web2print-tools-bundle": "<=5.2.1|>=6.0.0.0-RC1-dev,<=6.1", "piwik/piwik": "<1.11", "pixelfed/pixelfed": "<0.12.5", @@ -11897,7 +11899,7 @@ "starcitizentools/short-description": ">=4,<4.0.1", "starcitizentools/tabber-neue": ">=1.9.1,<2.7.2|>=3,<3.1.1", "starcitizenwiki/embedvideo": "<=4", - "statamic/cms": "<5.73.9|>=6,<6.3.2", + "statamic/cms": "<5.73.11|>=6,<6.4", "stormpath/sdk": "<9.9.99", "studio-42/elfinder": "<=2.1.64", "studiomitte/friendlycaptcha": "<0.1.4", @@ -11971,7 +11973,7 @@ "thelia/thelia": ">=2.1,<2.1.3", "theonedemon/phpwhois": "<=4.2.5", "thinkcmf/thinkcmf": "<6.0.8", - "thorsten/phpmyfaq": "<=4.0.16|>=4.1.0.0-alpha,<=4.1.0.0-beta2", + "thorsten/phpmyfaq": "<4.0.18|>=4.1.0.0-alpha,<=4.1.0.0-beta2", "tikiwiki/tiki-manager": "<=17.1", "timber/timber": ">=0.16.6,<1.23.1|>=1.24,<1.24.1|>=2,<2.1", "tinymce/tinymce": "<7.2", @@ -11989,6 +11991,7 @@ "ttskch/pagination-service-provider": "<1", "twbs/bootstrap": "<3.4.1|>=4,<4.3.1", "twig/twig": "<3.11.2|>=3.12,<3.14.1|>=3.16,<3.19", + "typicms/core": "<16.1.7", "typo3/cms": "<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2", "typo3/cms-backend": "<4.1.14|>=4.2,<4.2.15|>=4.3,<4.3.7|>=4.4,<4.4.4|>=7,<=7.6.50|>=8,<=8.7.39|>=9,<9.5.55|>=10,<=10.4.54|>=11,<=11.5.48|>=12,<=12.4.40|>=13,<=13.4.22|>=14,<=14.0.1", "typo3/cms-belog": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", @@ -12069,7 +12072,7 @@ "wpanel/wpanel4-cms": "<=4.3.1", "wpcloud/wp-stateless": "<3.2", "wpglobus/wpglobus": "<=1.9.6", - "wwbn/avideo": "<21", + "wwbn/avideo": "<=21", "xataface/xataface": "<3", "xpressengine/xpressengine": "<3.0.15", "yab/quarx": "<2.4.5", @@ -12167,7 +12170,7 @@ "type": "tidelift" } ], - "time": "2026-02-20T22:06:39+00:00" + "time": "2026-03-02T22:09:25+00:00" }, { "name": "sebastian/cli-parser", @@ -13475,16 +13478,16 @@ }, { "name": "symfony/yaml", - "version": "v7.4.1", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "24dd4de28d2e3988b311751ac49e684d783e2345" + "reference": "58751048de17bae71c5aa0d13cb19d79bca26391" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/24dd4de28d2e3988b311751ac49e684d783e2345", - "reference": "24dd4de28d2e3988b311751ac49e684d783e2345", + "url": "https://api.github.com/repos/symfony/yaml/zipball/58751048de17bae71c5aa0d13cb19d79bca26391", + "reference": "58751048de17bae71c5aa0d13cb19d79bca26391", "shasum": "" }, "require": { @@ -13527,7 +13530,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.4.1" + "source": "https://github.com/symfony/yaml/tree/v7.4.6" }, "funding": [ { @@ -13547,7 +13550,7 @@ "type": "tidelift" } ], - "time": "2025-12-04T18:11:45+00:00" + "time": "2026-02-09T09:33:46+00:00" }, { "name": "theseer/tokenizer", diff --git a/src/Commands/Sync.php b/src/Commands/Sync.php index 63497619..c13e459f 100644 --- a/src/Commands/Sync.php +++ b/src/Commands/Sync.php @@ -219,7 +219,10 @@ function ($keyAndHash) use ($rpc, $progress) { $blockHash, ] ); - $storageValues[] = Arr::get($storage, '0.changes'); + $changes = Arr::get($storage, '0.changes'); + if ($changes !== null) { + $storageValues[] = $changes; + } $total += count($keys); $startKey = Arr::last($keys); } diff --git a/src/Commands/contexts/get_storage.php b/src/Commands/contexts/get_storage.php index 1d50fed6..2faa4b4b 100644 --- a/src/Commands/contexts/get_storage.php +++ b/src/Commands/contexts/get_storage.php @@ -48,7 +48,10 @@ $blockHash, ] ); - $storageValues[] = Arr::get($storage, '0.changes'); + $changes = Arr::get($storage, '0.changes'); + if ($changes !== null) { + $storageValues[] = $changes; + } }); $startKey = Arr::last($keys); diff --git a/src/Jobs/HotSync.php b/src/Jobs/HotSync.php index d366bc0f..68521c88 100644 --- a/src/Jobs/HotSync.php +++ b/src/Jobs/HotSync.php @@ -49,7 +49,10 @@ public function handle(WebsocketAbstract $websocket): void } $storage = $websocket->send('state_queryStorageAt', [$keys]); - $storageValues[] = Arr::get($storage, '0.changes'); + $changes = Arr::get($storage, '0.changes'); + if ($changes !== null) { + $storageValues[] = $changes; + } $startKey = Arr::last($keys); } From 79802a3ea78072bacf0ea5c19493f844e16aae09 Mon Sep 17 00:00:00 2001 From: Pawel Wankiewicz Date: Thu, 5 Mar 2026 08:43:33 +0100 Subject: [PATCH 16/21] PLA-2317 --- .../Schemas/FuelTanks/Mutations/DispatchMutation.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/GraphQL/Schemas/FuelTanks/Mutations/DispatchMutation.php b/src/GraphQL/Schemas/FuelTanks/Mutations/DispatchMutation.php index 364e9558..b23371fb 100644 --- a/src/GraphQL/Schemas/FuelTanks/Mutations/DispatchMutation.php +++ b/src/GraphQL/Schemas/FuelTanks/Mutations/DispatchMutation.php @@ -30,6 +30,8 @@ use GraphQL\Type\Definition\Type; use Illuminate\Support\Arr; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Log; +use Override; use Rebing\GraphQL\Support\Facades\GraphQL; class DispatchMutation extends FuelTanksMutation implements PlatformBlockchainTransaction @@ -44,7 +46,7 @@ class DispatchMutation extends FuelTanksMutation implements PlatformBlockchainTr /** * Get the mutation's attributes. */ - #[\Override] + #[Override] public function attributes(): array { return [ @@ -64,7 +66,7 @@ public function type(): Type /** * Get the mutation's arguments definition. */ - #[\Override] + #[Override] public function args(): array { return [ @@ -110,7 +112,7 @@ public function resolve( return Transaction::lazyLoadSelectFields($transaction, $resolveInfo); } - public static function getEncodedCall($args) + public static function getEncodedCall($args): string { $result = GraphQL::queryAndReturnResult( Arr::get($args, 'dispatch.query'), @@ -119,6 +121,8 @@ public static function getEncodedCall($args) )->toArray(); if (Arr::get($result, 'errors.0.message')) { + Log::error('Dispatch query failed: ' . Arr::get($result, 'errors.0.message')); + throw new FuelTanksException(__('enjin-platform::exception.dispatch_query_error')); } @@ -158,7 +162,7 @@ public static function getFuelTankCall($method, $args, ?string $rawCall = null): ); } - #[\Override] + #[Override] public static function getEncodableParams(...$params): array { $tankId = Arr::get($params, 'tankId', Account::daemonPublicKey()); From 846ebf97c433d0b8636b487ec3d65b39da97616a Mon Sep 17 00:00:00 2001 From: Pawel Wankiewicz Date: Thu, 5 Mar 2026 16:37:14 +0100 Subject: [PATCH 17/21] Limit AddToTracked to 1 chainId per request and rate limit to 1 call per minute - Changed chainIds validation from max:10/max:1000 to max:1 regardless of hotSync mode - Added per-IP rate limiting (1 request per 60 seconds) using Laravel RateLimiter, matching the pattern used by RefreshMetadataMutation Co-Authored-By: Claude Opus 4.6 --- .../Primary/Mutations/AddToTrackedMutation.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/GraphQL/Schemas/Primary/Mutations/AddToTrackedMutation.php b/src/GraphQL/Schemas/Primary/Mutations/AddToTrackedMutation.php index 0c2ef93c..fc835e98 100644 --- a/src/GraphQL/Schemas/Primary/Mutations/AddToTrackedMutation.php +++ b/src/GraphQL/Schemas/Primary/Mutations/AddToTrackedMutation.php @@ -4,6 +4,7 @@ use Closure; use Enjin\Platform\Enums\Global\ModelType; +use Enjin\Platform\Exceptions\PlatformException; use Enjin\Platform\GraphQL\Schemas\Primary\Substrate\Traits\HasContextSensitiveRules; use Enjin\Platform\GraphQL\Schemas\Primary\Traits\InPrimarySchema; use Enjin\Platform\Interfaces\PlatformGraphQlMutation; @@ -14,6 +15,7 @@ use Enjin\Platform\Support\Hex; use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\Type; +use Illuminate\Support\Facades\RateLimiter; use Rebing\GraphQL\Support\Facades\GraphQL; use Rebing\GraphQL\Support\Mutation; @@ -77,6 +79,14 @@ public function args(): array */ public function resolve($root, array $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields, CollectionService $collectionService, SyncableService $syncableService): mixed { + $key = 'AddToTrackedRequest:' . request()->ip(); + if (RateLimiter::tooManyAttempts($key, 1)) { + throw new PlatformException( + __('enjin-platform::error.too_many_requests', ['num' => RateLimiter::availableIn($key)]) + ); + } + RateLimiter::hit($key, 60); + collect($args['chainIds'])->each(function ($id) use ($args, $syncableService): void { $syncable = $syncableService->getWithTrashed($id, ModelType::getEnumCase($args['type'])); @@ -101,7 +111,7 @@ public function resolve($root, array $args, $context, ResolveInfo $resolveInfo, protected function rules(array $args = []): array { return [ - 'chainIds' => ['required', 'array', $args['hotSync'] ? 'max:10' : 'max:1000'], + 'chainIds' => ['required', 'array', 'max:1'], ...$this->getContextSensitiveRules(ModelType::getEnumCase($args['type'])->name), ]; } From 4e91b3c368a5f46bfc33e5c990888747682f5cea Mon Sep 17 00:00:00 2001 From: Pawel Wankiewicz Date: Thu, 5 Mar 2026 16:40:07 +0100 Subject: [PATCH 18/21] Keep max:1000 chainIds for non-hotSync requests Only limit to 1 chainId when hotSync is enabled; keep the original max:1000 for non-hotSync. Co-Authored-By: Claude Opus 4.6 --- src/GraphQL/Schemas/Primary/Mutations/AddToTrackedMutation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GraphQL/Schemas/Primary/Mutations/AddToTrackedMutation.php b/src/GraphQL/Schemas/Primary/Mutations/AddToTrackedMutation.php index fc835e98..7f1e4885 100644 --- a/src/GraphQL/Schemas/Primary/Mutations/AddToTrackedMutation.php +++ b/src/GraphQL/Schemas/Primary/Mutations/AddToTrackedMutation.php @@ -111,7 +111,7 @@ public function resolve($root, array $args, $context, ResolveInfo $resolveInfo, protected function rules(array $args = []): array { return [ - 'chainIds' => ['required', 'array', 'max:1'], + 'chainIds' => ['required', 'array', $args['hotSync'] ? 'max:1' : 'max:1000'], ...$this->getContextSensitiveRules(ModelType::getEnumCase($args['type'])->name), ]; } From 0a4166203a48587a33c516c225277306d7b03e9d Mon Sep 17 00:00:00 2001 From: Pawel Wankiewicz Date: Tue, 10 Mar 2026 10:03:44 +0100 Subject: [PATCH 19/21] fix: resolve GraphQL SerializationError for TransactionState Enum Abandoned value --- ...10_000000_uppercase_transaction_states.php | 26 +++++++++++++++++++ src/Commands/TransactionChecker.php | 6 ++--- .../GraphQL/Mutations/AddToTrackedTest.php | 18 ++++++++----- 3 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 database/migrations/2026_03_10_000000_uppercase_transaction_states.php diff --git a/database/migrations/2026_03_10_000000_uppercase_transaction_states.php b/database/migrations/2026_03_10_000000_uppercase_transaction_states.php new file mode 100644 index 00000000..79147416 --- /dev/null +++ b/database/migrations/2026_03_10_000000_uppercase_transaction_states.php @@ -0,0 +1,26 @@ +last()?->number ?? 0; - $transactions = collect(Transaction::whereIn('state', [TransactionState::BROADCAST, TransactionState::EXECUTED]) + $transactions = collect(Transaction::whereIn('state', [TransactionState::BROADCAST->name, TransactionState::EXECUTED->name]) ->where('network', currentMatrix()->name) ->whereNotNull(['signed_at_block', 'transaction_chain_hash']) // We only check transactions older than 100 blocks because ingest @@ -166,10 +166,10 @@ public function handle(Substrate $client, Codec $codec): void protected function setAbandonedState($hashes): void { Transaction::whereIn('transaction_chain_hash', $hashes) - ->whereIn('state', [TransactionState::BROADCAST, TransactionState::EXECUTED]) + ->whereIn('state', [TransactionState::BROADCAST->name, TransactionState::EXECUTED->name]) ->where('network', currentMatrix()->name) ->update([ - 'state' => TransactionState::ABANDONED, + 'state' => TransactionState::ABANDONED->name, ]); } diff --git a/tests/Feature/GraphQL/Mutations/AddToTrackedTest.php b/tests/Feature/GraphQL/Mutations/AddToTrackedTest.php index 12b249cc..b6a65384 100644 --- a/tests/Feature/GraphQL/Mutations/AddToTrackedTest.php +++ b/tests/Feature/GraphQL/Mutations/AddToTrackedTest.php @@ -21,6 +21,7 @@ class AddToTrackedTest extends TestCaseGraphQL protected function setUp(): void { parent::setUp(); + \Illuminate\Support\Facades\RateLimiter::clear('AddToTrackedRequest:127.0.0.1'); } // Data Providers @@ -47,10 +48,13 @@ public static function validDataProvider(): array fn () => Queue::assertPushed(HotSync::class), ], 'track multiple collections' => [ - self::getInputData(chainIds: [ - (string) fake()->unique()->numberBetween(2000), - (string) fake()->unique()->numberBetween(2000), - ]), + self::getInputData( + chainIds: [ + (string) fake()->unique()->numberBetween(2000), + (string) fake()->unique()->numberBetween(2000), + ], + hotSync: false + ), fn () => Queue::assertPushed(HotSync::class), ], 'track multiple collections without hot sync' => [ @@ -87,10 +91,10 @@ public static function invalidDataProvider(): array 'too many chain ids supplied with hot sync' => [ [ 'type' => ModelType::COLLECTION->name, - 'chainIds' => array_map(fn () => (string) fake()->unique()->numberBetween(2000), array_fill(0, 11, null)), + 'chainIds' => array_map(fn () => (string) fake()->unique()->numberBetween(2000), array_fill(0, 2, null)), ], 'chainIds', - 'The chain ids field must not have more than 10 items.', + 'The chain ids field must not have more than 1 items.', ], 'too many chain ids supplied without hot sync' => [ [ @@ -142,6 +146,8 @@ public function test_it_can_restore_tracked_data() $deletedSyncable->delete(); $this->assertTrue($deletedSyncable->trashed()); + \Illuminate\Support\Facades\RateLimiter::clear('AddToTrackedRequest:127.0.0.1'); + $response = $this->graphql($this->method, $data); $restoredSyncable = Syncable::withTrashed()->where('syncable_id', $data['chainIds'][0])->first(); $this->assertTrue($response); From 12af2db62e29902e0b477a251283d5b25e8b604d Mon Sep 17 00:00:00 2001 From: Pawel Wankiewicz Date: Wed, 11 Mar 2026 14:00:58 +0100 Subject: [PATCH 20/21] fix: cast RequiredIf condition to bool in HasTokenIdFieldArrayRules Resolves InvalidArgumentException thrown when Arr::get() returns a string value (e.g. a token integer value) from nested input args. RequiredIf only accepts callable|bool, so the Arr::get() result is now explicitly cast to bool, which matches the original intent of checking parent object presence. Fixes PLA-2298 Co-Authored-By: Claude Sonnet 4.6 --- .../Schemas/Primary/Traits/HasTokenIdFieldArrayRules.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/GraphQL/Schemas/Primary/Traits/HasTokenIdFieldArrayRules.php b/src/GraphQL/Schemas/Primary/Traits/HasTokenIdFieldArrayRules.php index 6ca6a2eb..165ba9e9 100644 --- a/src/GraphQL/Schemas/Primary/Traits/HasTokenIdFieldArrayRules.php +++ b/src/GraphQL/Schemas/Primary/Traits/HasTokenIdFieldArrayRules.php @@ -69,7 +69,7 @@ public function getEncodableTokenIdRules(array $args = [], array $extraRules = [ return Rule::forEach(fn ($value, $attribute) => [ 'bail', 'filled', - new RequiredIf(Arr::get($args, str_replace('.tokenId', '', $attribute))), + new RequiredIf((bool) Arr::get($args, str_replace('.tokenId', '', $attribute))), ...$extraRules, ]); } @@ -82,7 +82,7 @@ public function getEncodableTokenIdRulesExist(array $args = [], array $extraRule return Rule::forEach(fn ($value, $attribute) => [ 'bail', 'filled', - new RequiredIf(Arr::get($args, str_replace('.tokenId', '', $attribute))), + new RequiredIf((bool) Arr::get($args, str_replace('.tokenId', '', $attribute))), new TokenEncodeExistInCollection(), ...$extraRules, ]); @@ -96,7 +96,7 @@ public function getEncodableTokenIdRulesDoesntExist(array $args = [], array $ext return Rule::forEach(fn ($value, $attribute) => [ 'bail', 'filled', - new RequiredIf(Arr::get($args, str_replace('.tokenId', '', $attribute))), + new RequiredIf((bool) Arr::get($args, str_replace('.tokenId', '', $attribute))), new TokenEncodeDoesNotExistInCollection(), ...$extraRules, ]); From 9100deb51a7e7369fcb3decf5e4d9bf9133b961d Mon Sep 17 00:00:00 2001 From: Pawel Wankiewicz Date: Wed, 11 Mar 2026 14:11:34 +0100 Subject: [PATCH 21/21] fix: remove duplicate test case with contradictory queue assertion The 'track multiple collections' entry asserted HotSync was pushed despite hotSync: false being set, making it impossible to pass. Removed in favour of the correct 'track multiple collections without hot sync' entry which asserts assertNotPushed. Co-Authored-By: Claude Sonnet 4.6 --- tests/Feature/GraphQL/Mutations/AddToTrackedTest.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/Feature/GraphQL/Mutations/AddToTrackedTest.php b/tests/Feature/GraphQL/Mutations/AddToTrackedTest.php index b6a65384..f74a69e6 100644 --- a/tests/Feature/GraphQL/Mutations/AddToTrackedTest.php +++ b/tests/Feature/GraphQL/Mutations/AddToTrackedTest.php @@ -47,16 +47,6 @@ public static function validDataProvider(): array self::getInputData(), fn () => Queue::assertPushed(HotSync::class), ], - 'track multiple collections' => [ - self::getInputData( - chainIds: [ - (string) fake()->unique()->numberBetween(2000), - (string) fake()->unique()->numberBetween(2000), - ], - hotSync: false - ), - fn () => Queue::assertPushed(HotSync::class), - ], 'track multiple collections without hot sync' => [ self::getInputData( chainIds: [