oura 2.2.0

The tail of Cardano
Documentation
use aws_config::BehaviorVersion;
use aws_sdk_s3::{primitives::ByteStream, Client};
use aws_types::region::Region;
use gasket::framework::*;
use pallas::network::miniprotocols::Point;
use serde::Deserialize;

use crate::framework::*;

pub struct Worker {
    client: Client,
}

#[async_trait::async_trait(?Send)]
impl gasket::framework::Worker<Stage> for Worker {
    async fn bootstrap(stage: &Stage) -> Result<Self, WorkerError> {
        let aws_config = aws_config::defaults(BehaviorVersion::v2025_08_07())
            .region(Region::new(stage.config.region.clone()))
            .load()
            .await;

        let client = Client::new(&aws_config);

        Ok(Self { client })
    }

    async fn schedule(
        &mut self,
        stage: &mut Stage,
    ) -> Result<WorkSchedule<ChainEvent>, WorkerError> {
        let msg = stage.input.recv().await.or_panic()?;
        Ok(WorkSchedule::Unit(msg.payload))
    }

    async fn execute(&mut self, unit: &ChainEvent, stage: &mut Stage) -> Result<(), WorkerError> {
        let point = unit.point().clone();
        let record = unit.record().cloned();

        if record.is_none() {
            return Ok(());
        }

        let cbor = match record.unwrap() {
            Record::CborBlock(cbor) => Ok(cbor),
            _ => Err(Error::config(String::from("Invalid configuration daemon"))),
        }
        .or_panic()?;

        let key = match &point {
            Point::Specific(slot, hash) => Ok(format!(
                "{}{}.{}",
                stage.config.prefix,
                slot,
                hex::encode(hash)
            )),
            Point::Origin => Err(Error::Config(String::from("Invalid chain point"))),
        }
        .or_panic()?;

        self.client
            .put_object()
            .bucket(&stage.config.bucket)
            .key(key)
            .body(ByteStream::from(cbor))
            .metadata("slot", point.slot_or_default().to_string())
            .content_type("application/cbor")
            .send()
            .await
            .or_retry()?;

        stage.ops_count.inc(1);
        stage.latest_block.set(point.slot_or_default() as i64);
        stage.cursor.send(point.clone().into()).await.or_panic()?;

        Ok(())
    }
}

#[derive(Stage)]
#[stage(name = "sink-aws-s3", unit = "ChainEvent", worker = "Worker")]
pub struct Stage {
    config: Config,

    pub input: MapperInputPort,
    pub cursor: SinkCursorPort,

    #[metric]
    ops_count: gasket::metrics::Counter,

    #[metric]
    latest_block: gasket::metrics::Gauge,
}

#[derive(Default, Debug, Deserialize)]
pub struct Config {
    pub region: String,
    pub bucket: String,
    #[serde(default)]
    pub prefix: String,
}

impl Config {
    pub fn bootstrapper(self, _ctx: &Context) -> Result<Stage, Error> {
        let stage = Stage {
            config: self,
            ops_count: Default::default(),
            latest_block: Default::default(),
            input: Default::default(),
            cursor: Default::default(),
        };

        Ok(stage)
    }
}