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
7 changes: 3 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ permissions:
# If there's a prerelease-style suffix to the version, then the release(s)
# will be marked as a prerelease.
on:
pull_request:
push:
tags:
- '**[0-9]+.[0-9]+.[0-9]+*'
Expand All @@ -66,7 +65,7 @@ jobs:
# we specify bash to get pipefail; it guards against the `curl` command
# failing. otherwise `sh` won't catch that `curl` returned non-0
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.29.0/cargo-dist-installer.sh | sh"
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.30.3/cargo-dist-installer.sh | sh"
- name: Cache dist
uses: actions/upload-artifact@v4
with:
Expand Down Expand Up @@ -223,8 +222,8 @@ jobs:
- plan
- build-local-artifacts
- build-global-artifacts
# Only run if we're "publishing", and only if local and global didn't fail (skipped is fine)
if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }}
# Only run if we're "publishing", and only if plan, local and global didn't fail (skipped is fine)
if: ${{ always() && needs.plan.result == 'success' && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
runs-on: "ubuntu-22.04"
Expand Down
7 changes: 5 additions & 2 deletions dist-workspace.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ members = ["cargo:."]
# Config for 'dist'
[dist]
# The preferred dist version to use in CI (Cargo.toml SemVer syntax)
cargo-dist-version = "0.29.0"
cargo-dist-version = "0.30.3"
# CI backends to support
ci = "github"
# The installers to generate for each app
Expand All @@ -16,7 +16,10 @@ install-path = "CARGO_HOME"
# The preferred Rust toolchain to use in CI (rustup toolchain syntax)
rust-toolchain-version = "1.89"
# Whether to install an updater program
install-updater = true
install-updater = false
# Don't run the release dry-run on pull requests; only fire on version tags.
# (PR validation lives in ci.yml.)
pr-run-mode = "skip"
# A GitHub repo to push Homebrew formulas to
tap = "txpipe/homebrew-tap"
# Publish jobs to run in CI
Expand Down
34 changes: 29 additions & 5 deletions examples/aws_lambda/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,44 @@ flowchart LR
- **Filters**
- `SplitBlock`: breaks each block into individual transactions.
- `ParseCbor`: decodes the raw transaction CBOR into structured records.
- **Sink** — `AwsLambda`: invokes `function_name` in `region` with each event as the payload.
- **Sink** — `AwsLambda`: invokes `function_name` in `region` with each event as the
JSON payload.

## Prerequisites

- Built with the `aws` feature.
- AWS credentials available to the process (env vars, profile, or instance role) with
permission to invoke the function.
- Edit `region` and `function_name` in `daemon.toml` to match your function.
- A running Docker engine — LocalStack runs each Lambda invocation in its own container
(the compose file mounts the Docker socket for this).

## Run
## Run standalone (LocalStack)

The included `docker-compose.yml` starts [LocalStack](https://www.localstack.cloud/) and
deploys `my-lambda` (a tiny function in `localstack/handler.py` that logs each event), so
the example runs without a real AWS account:

```sh
cd examples/aws_lambda
docker compose up -d
```

Point the AWS SDK at LocalStack with dummy credentials, then run Oura:

```sh
export AWS_ENDPOINT_URL=http://localhost.localstack.cloud:4566
export AWS_ACCESS_KEY_ID=test AWS_SECRET_ACCESS_KEY=test AWS_REGION=us-east-1
cargo run --features aws --bin oura -- daemon --config daemon.toml
```

(or `oura daemon --config daemon.toml` with a binary built with the `aws` feature.)

Watch the function get invoked — one `lambda.Invoke => 200` per event:

```sh
docker compose logs -f localstack | grep "lambda.Invoke"
```

## Run against real AWS

Skip the compose step and the `AWS_ENDPOINT_URL` export. Provide real credentials (env
vars, profile, or instance role) with permission to invoke the function, and set `region`
and `function_name` in `daemon.toml` to match it.
16 changes: 16 additions & 0 deletions examples/aws_lambda/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: "3"
services:
# LocalStack emulates AWS locally, so the example runs without a real AWS account.
# The init script under ./localstack deploys a tiny function named `my-lambda`
# that logs each event it receives.
localstack:
image: localstack/localstack:3
container_name: localstack-lambda
ports:
- "4566:4566"
environment:
- SERVICES=lambda
volumes:
- ./localstack:/etc/localstack/init/ready.d
# LocalStack runs each Lambda invocation in its own container.
- /var/run/docker.sock:/var/run/docker.sock
4 changes: 4 additions & 0 deletions examples/aws_lambda/localstack/handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
def handler(event, context):
# Oura invokes the function once per chain event; the payload is the JSON record.
print("oura event:", event)
return {"statusCode": 200}
11 changes: 11 additions & 0 deletions examples/aws_lambda/localstack/init.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/sh
# Runs inside the LocalStack container once it is ready.
# Packages handler.py and deploys it as the function daemon.toml invokes.
cd /etc/localstack/init/ready.d
python3 -c "import zipfile; zipfile.ZipFile('/tmp/fn.zip','w').write('handler.py')"
awslocal lambda create-function \
--function-name my-lambda \
--runtime python3.12 \
--handler handler.handler \
--role arn:aws:iam::000000000000:role/lambda-role \
--zip-file fileb:///tmp/fn.zip
38 changes: 30 additions & 8 deletions examples/aws_s3/README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,52 @@
# AWS S3 sink

Decode transactions and write each one as an object into an S3 bucket.
Write each block's raw CBOR as an object into an S3 bucket.

## Pipeline

```mermaid
flowchart LR
src[N2N source] --> f1[ParseCbor] --> sink[AwsS3 sink]
src[N2N source] --> sink[AwsS3 sink]
```

- **Source** — `N2N`: mainnet relay, starting from the chain tip.
- **Filters** — `ParseCbor`: decodes the raw transaction CBOR into structured records.
- **Sink** — `AwsS3`: writes objects under `prefix` in `bucket` (`region`).
- **Sink** — `AwsS3`: writes one object per block under `prefix` in `bucket` (`region`),
keyed by `slot.hash`. The `AwsS3` sink stores the block CBOR as-is, so the example runs
no filters — a `ParseCbor` / `SplitBlock` filter would change the record type and the
sink would reject it.

## Prerequisites

- Built with the `aws` feature.
- AWS credentials available to the process (env vars, profile, or instance role) with
permission to write to the bucket.
- Edit `region`, `bucket`, and `prefix` in `daemon.toml` to match your bucket.

## Run
## Run standalone (LocalStack)

The included `docker-compose.yml` starts [LocalStack](https://www.localstack.cloud/) and
provisions the `my-bucket` bucket, so the example runs without a real AWS account:

```sh
cd examples/aws_s3
docker compose up -d
```

Point the AWS SDK at LocalStack with dummy credentials, then run Oura:

```sh
export AWS_ENDPOINT_URL=http://s3.localhost.localstack.cloud:4566
export AWS_ACCESS_KEY_ID=test AWS_SECRET_ACCESS_KEY=test AWS_REGION=us-east-1
cargo run --features aws --bin oura -- daemon --config daemon.toml
```

(or `oura daemon --config daemon.toml` with a binary built with the `aws` feature.)

Inspect the objects Oura wrote:

```sh
docker exec localstack-s3 awslocal s3 ls s3://my-bucket/mainnet/
```

## Run against real AWS

Skip the compose step and the `AWS_ENDPOINT_URL` export. Provide real credentials (env
vars, profile, or instance role) with permission to write to the bucket, and edit
`region`, `bucket`, and `prefix` in `daemon.toml` to match it.
4 changes: 2 additions & 2 deletions examples/aws_s3/daemon.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ type = "mainnet"
[intersect]
type = "Tip"

[[filters]]
type = "ParseCbor"
# The AwsS3 sink stores the raw block CBOR, so the block must reach it unparsed
# (no ParseCbor / SplitBlock filter).

[sink]
type = "AwsS3"
Expand Down
13 changes: 13 additions & 0 deletions examples/aws_s3/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: "3"
services:
# LocalStack emulates AWS locally, so the example runs without a real AWS account.
# The init script under ./localstack provisions the bucket Oura writes to.
localstack:
image: localstack/localstack:3
container_name: localstack-s3
ports:
- "4566:4566"
environment:
- SERVICES=s3
volumes:
- ./localstack:/etc/localstack/init/ready.d
4 changes: 4 additions & 0 deletions examples/aws_s3/localstack/init.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
# Runs inside the LocalStack container once it is ready.
# Creates the bucket that daemon.toml writes to.
awslocal s3 mb s3://my-bucket
34 changes: 28 additions & 6 deletions examples/aws_sqs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,43 @@ flowchart LR
- **Filters**
- `SplitBlock`: breaks each block into individual transactions.
- `ParseCbor`: decodes the raw transaction CBOR into structured records.
- **Sink** — `AwsSqs`: sends messages to `queue_url` (`region`) with the configured
`group_id` (FIFO queues).
- **Sink** — `AwsSqs`: sends each event as a JSON message to `queue_url` (`region`). For a
FIFO queue (`queue_url` ending in `.fifo`) the configured `group_id` is used.

## Prerequisites

- Built with the `aws` feature.
- AWS credentials available to the process (env vars, profile, or instance role) with
permission to send to the queue.
- Edit `region`, `queue_url`, and `group_id` in `daemon.toml` to match your queue.

## Run
## Run standalone (LocalStack)

The included `docker-compose.yml` starts [LocalStack](https://www.localstack.cloud/) and
provisions the `my-queue` queue that `daemon.toml` points at, so the example runs without a
real AWS account:

```sh
cd examples/aws_sqs
docker compose up -d
```

Point the AWS SDK at LocalStack with dummy credentials, then run Oura:

```sh
export AWS_ENDPOINT_URL=http://localhost.localstack.cloud:4566
export AWS_ACCESS_KEY_ID=test AWS_SECRET_ACCESS_KEY=test AWS_REGION=us-east-1
cargo run --features aws --bin oura -- daemon --config daemon.toml
```

(or `oura daemon --config daemon.toml` with a binary built with the `aws` feature.)

Read the messages Oura enqueued:

```sh
docker exec localstack-sqs awslocal sqs receive-message \
--queue-url http://localhost:4566/000000000000/my-queue
```

## Run against real AWS

Skip the compose step and the `AWS_ENDPOINT_URL` export. Provide real credentials (env
vars, profile, or instance role) with permission to send to the queue, and set `region`,
`queue_url`, and `group_id` in `daemon.toml` to match your queue.
4 changes: 3 additions & 1 deletion examples/aws_sqs/daemon.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,7 @@ type = "ParseCbor"
[sink]
type = "AwsSqs"
region = "us-east-1"
queue_url = "https://sqs.us-east-1.amazonaws.com/*****/my-queue"
# Points at the queue created by the bundled LocalStack (see README). For a real
# AWS queue, use its URL: https://sqs.<region>.amazonaws.com/<account-id>/my-queue
queue_url = "http://localhost.localstack.cloud:4566/queue/us-east-1/000000000000/my-queue"
group_id = "my_group"
15 changes: 15 additions & 0 deletions examples/aws_sqs/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
version: "3"
services:
# LocalStack emulates AWS locally, so the example runs without a real AWS account.
# The init script under ./localstack provisions the queue Oura writes to.
localstack:
image: localstack/localstack:3
container_name: localstack-sqs
ports:
- "4566:4566"
environment:
- SERVICES=sqs
# Path-style queue URLs, so the queue_url in daemon.toml is deterministic.
- SQS_ENDPOINT_STRATEGY=path
volumes:
- ./localstack:/etc/localstack/init/ready.d
4 changes: 4 additions & 0 deletions examples/aws_sqs/localstack/init.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
# Runs inside the LocalStack container once it is ready.
# Creates the queue that daemon.toml writes to.
awslocal sqs create-queue --queue-name my-queue
29 changes: 22 additions & 7 deletions examples/gcp_cloudfunction/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# GCP Cloud Function sink

Decode transactions and call a Google Cloud Function once per event.
Decode transactions and call a Google Cloud Function (or any HTTP endpoint) once per event.

## Pipeline

Expand All @@ -13,21 +13,36 @@ flowchart LR
- **Filters**
- `SplitBlock`: breaks each block into individual transactions.
- `ParseCbor`: decodes the raw transaction CBOR into structured records.
- **Sink** — `GcpCloudFunction`: invokes the function at `url`
(`authentication = true` attaches a Google-issued identity token).
- **Sink** — `GcpCloudFunction`: POSTs each event as JSON to `url`. With
`authentication = true` it attaches a Google-issued identity token (requires real GCP
credentials); with `authentication = false` it is a plain HTTP POST.

## Prerequisites

- Built with the `gcp` feature.
- GCP credentials available to the process (e.g. `GOOGLE_APPLICATION_CREDENTIALS`) with
permission to invoke the function.
- Edit `url` in `daemon.toml` to point at your deployed function.

## Run
## Run standalone (local endpoint)

A Cloud Function is just an HTTP endpoint, so the included `docker-compose.yml` starts an
echo server on `:8080` that logs every request. `daemon.toml` posts to it with
`authentication = false`, so the example runs without a GCP project:

```sh
cd examples/gcp_cloudfunction
docker compose up -d
cargo run --features gcp --bin oura -- daemon --config daemon.toml
```

(or `oura daemon --config daemon.toml` with a binary built with the `gcp` feature.)

Watch the events arrive at the endpoint:

```sh
docker compose logs -f function
```

## Run against a real Cloud Function

Set `url` in `daemon.toml` to the function's trigger URL and `authentication = true`, then
provide credentials via `GOOGLE_APPLICATION_CREDENTIALS` with permission to invoke it. The
identity-token path can't be exercised against the local echo server.
7 changes: 5 additions & 2 deletions examples/gcp_cloudfunction/daemon.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,8 @@ type = "ParseCbor"

[sink]
type = "GcpCloudFunction"
url = "https://REGION-PROJECT_ID.cloudfunctions.net/FUNCTION_NAME"
authentication = true
# Posts events to the bundled echo server (see README). For a real Cloud Function, use its
# trigger URL (https://REGION-PROJECT_ID.cloudfunctions.net/FUNCTION_NAME) and set
# authentication = true so Oura attaches a Google-issued identity token.
url = "http://localhost:8080"
authentication = false
11 changes: 11 additions & 0 deletions examples/gcp_cloudfunction/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: "3"
services:
# A Cloud Function is just an HTTP endpoint, so this echo server stands in for one,
# logging every request Oura posts. It lets the example run without a GCP project.
function:
image: mendhak/http-https-echo:31
container_name: cloudfunction-echo
environment:
- HTTP_PORT=8080
ports:
- "8080:8080"
Loading
Loading