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
4 changes: 4 additions & 0 deletions pkg/tasks/tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ import (
runtasks "github.com/ethpandaops/assertoor/pkg/tasks/run_tasks"
runtasksconcurrent "github.com/ethpandaops/assertoor/pkg/tasks/run_tasks_concurrent"
sleep "github.com/ethpandaops/assertoor/pkg/tasks/sleep"
tysmhookactivation "github.com/ethpandaops/assertoor/pkg/tasks/tysm_hook_activation"
tysmhookdeactivation "github.com/ethpandaops/assertoor/pkg/tasks/tysm_hook_deactivation"
)

var AvailableTaskDescriptors = []*types.TaskDescriptor{
Expand Down Expand Up @@ -90,6 +92,8 @@ var AvailableTaskDescriptors = []*types.TaskDescriptor{
runtasks.TaskDescriptor,
runtasksconcurrent.TaskDescriptor,
sleep.TaskDescriptor,
tysmhookactivation.TaskDescriptor,
tysmhookdeactivation.TaskDescriptor,
}

func GetTaskDescriptor(name string) *types.TaskDescriptor {
Expand Down
111 changes: 111 additions & 0 deletions pkg/tasks/tysm_hook_activation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
## `tysm_hook_activation` Task

### Description

Creates a TTL-bound activation against a TYSM beacon node's hook-control API
(`POST /tysm/v1/activations`).

An activation overlays `(enabled, configPatch)` on the hook's baseline state
for a bounded duration. When the TTL expires (or the activation is deleted
via `tysm_hook_deactivation`), the hook reverts to its baseline. Designed
for chaos-style flows where assertoor flips a hook to a non-default state,
exercises the network, and then cleans up.

This task is a paired primitive: pair it with `tysm_hook_deactivation`
placed in the test's top-level `cleanupTasks:` block so the activation is
torn down even if the test fails.

#### Task Behavior

- POSTs the activation request and returns immediately on `201 Created`.
- Records `activation_id` and `expires_at` as task outputs for later use
(typically by a deactivation task in cleanup).
- Fails on any non-`201` response, surfacing the server's error message.
- Does not wait for or assert anything about hook side-effects — that is
the responsibility of subsequent tasks in the playbook.

### Configuration Parameters

- **`endpoint`**:\
Base URL of the TYSM API, e.g. `http://beacon:8080`. Required.

- **`auth_token`**:\
Bearer token sent in the `Authorization` header. Required when the TYSM
API has auth enabled. Recommended to supply via `configVars` so the
secret is not hard-coded into the playbook.

- **`hook`**:\
Name of the hook to activate. Must be a hook implementing
`RuntimeReconfigurable` on the server side (currently `blob-mutator`,
`data-column-mutator`); other names are rejected with `400`.

- **`enabled`**:\
Optional boolean override of the hook's enabled flag while the activation
is in force. Either this or `configPatch` (or both) must be supplied.

- **`configPatch`**:\
Optional shallow patch over the hook's baseline configuration. Top-level
keys present here wholly replace the corresponding baseline keys; absent
keys keep their baseline value.

- **`duration`**:\
Activation TTL as a Go duration string (`10m`, `1h`, ...). Required. The
server enforces a hard cap (`api.max_activation_duration`); requests
exceeding it are rejected.

- **`replace`**:\
If `true`, replace any existing activation against the same hook instead
of returning `409 Conflict`. Default `false`.

### Defaults

```yaml
- name: tysm_hook_activation
config:
endpoint: ""
auth_token: ""
hook: ""
enabled: null
configPatch: {}
duration: "0s"
replace: false
```

### Outputs

| Name | Type | Description |
|------------------|----------|------------------------------------------------------------------------------|
| `activation_id` | `string` | Server-assigned activation ID. Pass to `tysm_hook_deactivation`. |
| `expires_at` | `string` | RFC3339 timestamp at which the server-side TTL expires. |
| `hook` | `string` | Hook the activation targets (echoes the input). |

### Example

```yaml
tests:
- id: kzg_chaos_run
name: "blob-mutator KZG chaos"
cleanupTasks:
- name: tysm_hook_deactivation
config:
endpoint: "http://beacon:8080"
configVars:
auth_token: "tysmApiToken"
activation_id: "tasks.kzg_chaos.outputs.activation_id"
tasks:
- name: tysm_hook_activation
id: kzg_chaos
config:
endpoint: "http://beacon:8080"
hook: "blob-mutator"
enabled: true
configPatch:
mutationProbability: 1.0
enabledStrategies: ["kzg-corruption"]
duration: "10m"
replace: false
configVars:
auth_token: "tysmApiToken"

# ... assertions about network behaviour while activation is in force ...
```
53 changes: 53 additions & 0 deletions pkg/tasks/tysm_hook_activation/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package tysmhookactivation

import (
"errors"
"net/url"
"strings"

"github.com/ethpandaops/assertoor/pkg/helper"
)

// Config drives a single POST to the TYSM hook-control API.
//
// AuthToken is intentionally yaml-tagged with snake_case: it is expected
// to be supplied via the task's configVars (e.g. configVars: { auth_token:
// "tysmApiToken" }) so the secret never lives in the playbook source.
// Inline assignment under config: still works, it is just discouraged.
type Config struct {
Endpoint string `yaml:"endpoint" json:"endpoint" require:"A" desc:"Base URL of the TYSM API, e.g. http://beacon:8080"`
AuthToken string `yaml:"auth_token" json:"auth_token" desc:"Bearer token sent in the Authorization header. Prefer supplying via configVars."`
Hook string `yaml:"hook" json:"hook" require:"A" desc:"Name of the TYSM hook to activate (e.g. blob-mutator)."`
Enabled *bool `yaml:"enabled,omitempty" json:"enabled,omitempty" desc:"Override the hook's enabled flag while the activation is in force."`
ConfigPatch map[string]interface{} `yaml:"configPatch,omitempty" json:"configPatch,omitempty" desc:"Top-level config keys to overlay on top of the hook's baseline configuration."`
Duration helper.Duration `yaml:"duration" json:"duration" require:"A" desc:"Activation TTL (Go duration: 10m, 1h, ...). The server enforces a hard cap; values exceeding it are rejected."`
Replace bool `yaml:"replace" json:"replace" desc:"If true, replace any existing activation for the same hook instead of returning 409 Conflict."`
}

func DefaultConfig() Config {
return Config{}
}

func (c *Config) Validate() error {
if strings.TrimSpace(c.Endpoint) == "" {
return errors.New("endpoint is required")
}

if _, err := url.Parse(c.Endpoint); err != nil {
return errors.New("endpoint must be a valid URL")
}

if strings.TrimSpace(c.Hook) == "" {
return errors.New("hook is required")
}

if c.Duration.Duration <= 0 {
return errors.New("duration must be greater than 0")
}

if c.Enabled == nil && len(c.ConfigPatch) == 0 {
return errors.New("at least one of enabled or configPatch must be set")
}

return nil
}
Loading
Loading