Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions src/Event/ErrorEventDetector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?php

declare(strict_types=1);

namespace Substrate\ScaleCodec\Event;

/**
* Helper for detecting and handling error events.
*/
class ErrorEventDetector
{
/**
* Common error event patterns.
*/
private const ERROR_EVENTS = [
['pallet' => 'System', 'name' => 'ExtrinsicFailed'],
['pallet' => 'System', 'name' => 'CodeNotFound'],
['pallet' => 'System', 'name' => 'InvalidSpecName'],
['pallet' => 'System', 'name' => 'SpecVersionNeeded'],
];

/**
* @var array<EventRecord> Events
*/
private array $events;

/**
* Create an error detector from event records.
*
* @param array<EventRecord> $events Event records
*/
public function __construct(array $events)
{
$this->events = $events;
}

/**
* Check if there are any error events.
*/
public function hasErrors(): bool
{
foreach (self::ERROR_EVENTS as $pattern) {
foreach ($this->events as $event) {
if ($event->event->pallet === $pattern['pallet']
&& $event->event->name === $pattern['name']) {
return true;
}
}
}
return false;
}

/**
* Get all error events.
*
* @return array<EventRecord> Error events
*/
public function getErrors(): array
{
$errors = [];
foreach (self::ERROR_EVENTS as $pattern) {
foreach ($this->events as $event) {
if ($event->event->pallet === $pattern['pallet']
&& $event->event->name === $pattern['name']) {
$errors[] = $event;
}
}
}
return $errors;
}

/**
* Check if a specific extrinsic failed.
*
* @param int $extrinsicIndex Extrinsic index
*/
public function extrinsicFailed(int $extrinsicIndex): bool
{
foreach ($this->events as $event) {
if ($event->event->pallet === 'System'
&& $event->event->name === 'ExtrinsicFailed'
&& $event->getExtrinsicIndex() === $extrinsicIndex) {
return true;
}
}
return false;
}

/**
* Get the first ExtrinsicFailed event for an extrinsic.
*
* @param int $extrinsicIndex Extrinsic index
* @return EventRecord|null The error event or null
*/
public function getExtrinsicError(int $extrinsicIndex): ?EventRecord
{
foreach ($this->events as $event) {
if ($event->event->pallet === 'System'
&& $event->event->name === 'ExtrinsicFailed'
&& $event->getExtrinsicIndex() === $extrinsicIndex) {
return $event;
}
}
return null;
}

/**
* Get error summary for a block.
*
* @return array<string, int> Map of error identifiers to counts
*/
public function getErrorSummary(): array
{
$summary = [];
foreach ($this->events as $event) {
$id = $event->event->getIdentifier();
foreach (self::ERROR_EVENTS as $pattern) {
if ($event->event->pallet === $pattern['pallet']
&& $event->event->name === $pattern['name']) {
if (!isset($summary[$id])) {
$summary[$id] = 0;
}
$summary[$id]++;
}
}
}
return $summary;
}
}
64 changes: 64 additions & 0 deletions src/Event/Event.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

namespace Substrate\ScaleCodec\Event;

/**
* Represents a Substrate event.
*/
class Event
{
/**
* @param string $pallet Pallet name
* @param string $name Event name
* @param int $palletIndex Pallet index
* @param int $eventIndex Event index within pallet
* @param array $data Event data fields
*/
public function __construct(
public readonly string $pallet,
public readonly string $name,
public readonly int $palletIndex,
public readonly int $eventIndex,
public readonly array $data = [],
) {}

/**
* Get the event identifier (pallet_index, event_index).
*/
public function getIdentifier(): string
{
return "{$this->pallet}.{$this->name}";
}

/**
* Get the event data by field name.
*/
public function getField(string $name): mixed
{
return $this->data[$name] ?? null;
}

/**
* Check if the event has a specific field.
*/
public function hasField(string $name): bool
{
return array_key_exists($name, $this->data);
}

/**
* Convert to array representation.
*/
public function toArray(): array
{
return [
'pallet' => $this->pallet,
'name' => $this->name,
'palletIndex' => $this->palletIndex,
'eventIndex' => $this->eventIndex,
'data' => $this->data,
];
}
}
170 changes: 170 additions & 0 deletions src/Event/EventIndex.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
<?php

declare(strict_types=1);

namespace Substrate\ScaleCodec\Event;

/**
* Utility for indexing and searching events.
*/
class EventIndex
{
/**
* @var array<EventRecord> Indexed events
*/
private array $events = [];

/**
* @var array<string, array<int>> Index: pallet.name => [event indices]
*/
private array $nameIndex = [];

/**
* @var array<int, array<int>> Index: pallet index => [event indices]
*/
private array $palletIndex = [];

/**
* Create an event index from event records.
*
* @param array<EventRecord> $events Event records to index
*/
public function __construct(array $events = [])
{
foreach ($events as $index => $event) {
$this->addEvent($event, $index);
}
}

/**
* Add an event to the index.
*/
public function addEvent(EventRecord $event, int $index): void
{
$this->events[$index] = $event;

// Index by name
$key = $event->event->getIdentifier();
if (!isset($this->nameIndex[$key])) {
$this->nameIndex[$key] = [];
}
$this->nameIndex[$key][] = $index;

// Index by pallet
$palletIndex = $event->event->palletIndex;
if (!isset($this->palletIndex[$palletIndex])) {
$this->palletIndex[$palletIndex] = [];
}
$this->palletIndex[$palletIndex][] = $index;
}

/**
* Find events by pallet and event name.
*
* @param string $pallet Pallet name
* @param string $eventName Event name
* @return array<EventRecord> Matching events
*/
public function findByName(string $pallet, string $eventName): array
{
$key = "$pallet.$eventName";
$indices = $this->nameIndex[$key] ?? [];
return array_map(fn($i) => $this->events[$i], $indices);
}

/**
* Find events by pallet index.
*
* @param int $palletIndex Pallet index
* @return array<EventRecord> Matching events
*/
public function findByPallet(int $palletIndex): array
{
$indices = $this->palletIndex[$palletIndex] ?? [];
return array_map(fn($i) => $this->events[$i], $indices);
}

/**
* Find events in ApplyExtrinsic phase.
*
* @return array<EventRecord> Events in ApplyExtrinsic phase
*/
public function findApplyExtrinsic(): array
{
return array_filter($this->events, fn($e) => $e->isApplyExtrinsic());
}

/**
* Find events in Finalization phase.
*
* @return array<EventRecord> Events in Finalization phase
*/
public function findFinalization(): array
{
return array_filter($this->events, fn($e) => $e->isFinalization());
}

/**
* Find events by extrinsic index.
*
* @param int $extrinsicIndex Extrinsic index
* @return array<EventRecord> Events from the specified extrinsic
*/
public function findByExtrinsic(int $extrinsicIndex): array
{
return array_filter($this->events, fn($e) => $e->getExtrinsicIndex() === $extrinsicIndex);
}

/**
* Get the first event matching criteria.
*
* @param string $pallet Pallet name
* @param string $eventName Event name
* @return EventRecord|null The first matching event or null
*/
public function findFirst(string $pallet, string $eventName): ?EventRecord
{
$events = $this->findByName($pallet, $eventName);
return $events[0] ?? null;
}

/**
* Get all events.
*
* @return array<EventRecord> All events
*/
public function all(): array
{
return $this->events;
}

/**
* Get event count.
*/
public function count(): int
{
return count($this->events);
}

/**
* Check if an event exists.
*
* @param string $pallet Pallet name
* @param string $eventName Event name
*/
public function has(string $pallet, string $eventName): bool
{
$key = "$pallet.$eventName";
return !empty($this->nameIndex[$key]);
}

/**
* Get unique event identifiers.
*
* @return array<string> Unique event identifiers
*/
public function getUniqueEventIds(): array
{
return array_keys($this->nameIndex);
}
}
Loading
Loading