Skip to content

Commit

Permalink
Merge pull request #1 from base-org/initial-archiver
Browse files Browse the repository at this point in the history
Initial Archiver Service Implementation
  • Loading branch information
danyalprout authored Feb 15, 2024
2 parents 9253741 + 3714928 commit 0fa146d
Show file tree
Hide file tree
Showing 37 changed files with 3,286 additions and 0 deletions.
22 changes: 22 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# To get started, copy this file to .env and set your beacon http endpoint

BLOB_ARCHIVER_L1_BEACON_HTTP=<unset>
BLOB_ARCHIVER_DATA_STORE=s3
BLOB_ARCHIVER_S3_ENDPOINT=172.17.0.1:9000
BLOB_ARCHIVER_S3_ACCESS_KEY=admin
BLOB_ARCHIVER_S3_SECRET_ACCESS_KEY=password
BLOB_ARCHIVER_S3_ENDPOINT_HTTPS=false
BLOB_ARCHIVER_S3_BUCKET=blobs
BLOB_ARCHIVER_METRICS_ENABLED=true
BLOB_ARCHIVER_METRICS_PORT=7300
BLOB_ARCHIVER_ORIGIN_BLOCK=0x0

BLOB_API_L1_BEACON_HTTP=<unset>
BLOB_API_DATA_STORE=s3
BLOB_API_S3_ENDPOINT=172.17.0.1:9000
BLOB_API_S3_ACCESS_KEY=admin
BLOB_API_S3_SECRET_ACCESS_KEY=password
BLOB_API_S3_ENDPOINT_HTTPS=false
BLOB_API_S3_BUCKET=blobs
BLOB_API_METRICS_ENABLED=true
BLOB_API_METRICS_PORT=7301
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.idea/
.DS_Store
.swp
.env
api/bin
archiver/bin
18 changes: 18 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM golang:1.21.6-alpine3.19 as builder

RUN apk add --no-cache make gcc musl-dev linux-headers jq bash

WORKDIR /app

COPY ./go.mod ./go.sum /app/

RUN go mod download

COPY . /app

RUN make build

FROM alpine:3.19

COPY --from=builder /app/archiver/bin/blob-archiver /usr/local/bin/blob-archiver
COPY --from=builder /app/api/bin/blob-api /usr/local/bin/blob-api
35 changes: 35 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
build:
make -C ./archiver blob-archiver
make -C ./api blob-api
.PHONY: build

build-docker:
docker-compose build
.PHONY: build-docker

clean:
make -C ./archiver clean
make -C ./api clean
.PHONY: clean

test:
make -C ./archiver test
make -C ./api test
.PHONY: test

integration:
docker-compose down
docker-compose up -d minio create-buckets
RUN_INTEGRATION_TESTS=true go test -v ./...
.PHONY: integration

fmt:
gofmt -s -w .
.PHONY: fmt

check: fmt clean build build-docker lint test integration
.PHONY: check

lint:
golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint --timeout 5m -e "errors.As" -e "errors.Is" ./...
.PHONY: lint
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,58 @@
# Blob Archiver
The Blob Archiver is a service to archive and allow querying of all historical blobs from the beacon chain. It consists
of two components:

* **Archiver** - Tracks the beacon chain and writes blobs to a storage backend
* **API** - Implements the blob sidecars [API](https://ethereum.github.io/beacon-APIs/#/Beacon/getBlobSidecars), which
allows clients to retrieve blobs from the storage backend

### Storage
There are currently two supported storage options:

* On-disk storage - Blobs are written to disk in a directory
* S3 storage - Blobs are written to an S3 bucket

You can control which storage backend is used by setting the `BLOB_API_DATA_STORE` and `BLOB_ARCHIVER_DATA_STORE` to
either `disk` or `s3`.

### Data Validity
Currently, the archiver and api do not validate the beacon node's data. Therefore, it's important to either trust the
Beacon node, or validate the data in the client. There is an open [issue](https://github.com/base-org/blob-archiver/issues/4)
to add data validation to the archiver and api.

### Development
The `Makefile` contains a number of commands for development:

```sh
# Run the tests
make test
# Run the integration tests (will start a local S3 bucket)
make integration

# Lint the project
make lint

# Build the project
make build

# Check all tests, formatting, building
make check
```

#### Run Locally
To run the project locally, you should first copy `.env.template` to `.env` and then modify the environment variables
to your beacon client and storage backend of choice. Then you can run the project with:

```sh
docker-compose up
```

You can see a full list of configuration options by running:
```sh
# API
go run api/cmd/main.go

# Archiver
go run archiver/cmd/main.go

```
13 changes: 13 additions & 0 deletions api/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
blob-api:
env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build -v $(LDFLAGS) -o ./bin/blob-api ./cmd/main.go

clean:
rm -f bin/blob-api

test:
go test -v -race ./...

.PHONY: \
blob-api \
clean \
test
72 changes: 72 additions & 0 deletions api/cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package main

import (
"context"
"fmt"
"os"

"github.com/base-org/blob-archiver/api/flags"
"github.com/base-org/blob-archiver/api/metrics"
"github.com/base-org/blob-archiver/api/service"
"github.com/base-org/blob-archiver/common/beacon"
"github.com/base-org/blob-archiver/common/storage"
opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/ethereum-optimism/optimism/op-service/cliapp"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2"
)

var (
Version = "v0.0.1"
GitCommit = ""
GitDate = ""
)

func main() {
oplog.SetupDefaults()

app := cli.NewApp()
app.Flags = cliapp.ProtectFlags(flags.Flags)
app.Version = opservice.FormatVersion(Version, GitCommit, GitDate, "")
app.Name = "blob-api"
app.Usage = "API service for Ethereum blobs"
app.Description = "Service for fetching blob sidecars from a datastore"
app.Action = cliapp.LifecycleCmd(Main())

err := app.Run(os.Args)
if err != nil {
log.Crit("Application failed", "message", err)
}
}

// Main is the entrypoint into the API.
// This method returns a cliapp.LifecycleAction, to create an op-service CLI-lifecycle-managed API Server.
func Main() cliapp.LifecycleAction {
return func(cliCtx *cli.Context, closeApp context.CancelCauseFunc) (cliapp.Lifecycle, error) {
cfg := flags.ReadConfig(cliCtx)
if err := cfg.Check(); err != nil {
return nil, fmt.Errorf("config check failed: %w", err)
}

l := oplog.NewLogger(oplog.AppOut(cliCtx), cfg.LogConfig)
oplog.SetGlobalLogHandler(l.GetHandler())
opservice.ValidateEnvVars(flags.EnvVarPrefix, flags.Flags, l)

m := metrics.NewMetrics()

storageClient, err := storage.NewStorage(cfg.StorageConfig, l)
if err != nil {
return nil, fmt.Errorf("failed to initialize storage: %w", err)
}

beaconClient, err := beacon.NewBeaconClient(context.Background(), cfg.BeaconConfig)
if err != nil {
return nil, fmt.Errorf("failed to initialize beacon client: %w", err)
}

l.Info("Initializing API Service")
api := service.NewAPI(storageClient, beaconClient, m, l)
return service.NewService(l, api, cfg, m.Registry()), nil
}
}
45 changes: 45 additions & 0 deletions api/flags/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package flags

import (
"fmt"

common "github.com/base-org/blob-archiver/common/flags"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/urfave/cli/v2"
)

type APIConfig struct {
LogConfig oplog.CLIConfig
MetricsConfig opmetrics.CLIConfig
BeaconConfig common.BeaconConfig
StorageConfig common.StorageConfig

ListenAddr string
}

func (c APIConfig) Check() error {
if err := c.StorageConfig.Check(); err != nil {
return fmt.Errorf("storage config check failed: %w", err)
}

if err := c.BeaconConfig.Check(); err != nil {
return fmt.Errorf("beacon config check failed: %w", err)
}

if c.ListenAddr == "" {
return fmt.Errorf("listen address must be set")
}

return nil
}

func ReadConfig(cliCtx *cli.Context) APIConfig {
return APIConfig{
LogConfig: oplog.ReadCLIConfig(cliCtx),
MetricsConfig: opmetrics.ReadCLIConfig(cliCtx),
BeaconConfig: common.NewBeaconConfig(cliCtx),
StorageConfig: common.NewStorageConfig(cliCtx),
ListenAddr: cliCtx.String(ListenAddressFlag.Name),
}
}
30 changes: 30 additions & 0 deletions api/flags/flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package flags

import (
common "github.com/base-org/blob-archiver/common/flags"
opservice "github.com/ethereum-optimism/optimism/op-service"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/urfave/cli/v2"
)

const EnvVarPrefix = "BLOB_API"

var (
ListenAddressFlag = &cli.StringFlag{
Name: "api-listen-address",
Usage: "The address to list for new requests on",
EnvVars: opservice.PrefixEnvVar(EnvVarPrefix, "LISTEN_ADDRESS"),
Value: "0.0.0.0:8000",
}
)

func init() {
Flags = append(Flags, common.CLIFlags(EnvVarPrefix)...)
Flags = append(Flags, opmetrics.CLIFlags(EnvVarPrefix)...)
Flags = append(Flags, oplog.CLIFlags(EnvVarPrefix)...)
Flags = append(Flags, ListenAddressFlag)
}

// Flags contains the list of configuration options available to the binary.
var Flags []cli.Flag
50 changes: 50 additions & 0 deletions api/metrics/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package metrics

import (
"github.com/ethereum-optimism/optimism/op-service/metrics"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/prometheus/client_golang/prometheus"
)

type BlockIdType string

var (
MetricsNamespace = "blob_api"

BlockIdTypeHash BlockIdType = "hash"
BlockIdTypeBeacon BlockIdType = "beacon"
BlockIdTypeInvalid BlockIdType = "invalid"
)

type Metricer interface {
Registry() *prometheus.Registry
RecordBlockIdType(t BlockIdType)
}

type metricsRecorder struct {
// blockIdType records the type of block id used to request a block. This could be a hash (BlockIdTypeHash), or a
// beacon block identifier (BlockIdTypeBeacon).
blockIdType *prometheus.CounterVec
registry *prometheus.Registry
}

func NewMetrics() Metricer {
registry := opmetrics.NewRegistry()
factory := metrics.With(registry)
return &metricsRecorder{
registry: registry,
blockIdType: factory.NewCounterVec(prometheus.CounterOpts{
Namespace: MetricsNamespace,
Name: "block_id_type",
Help: "The type of block id used to request a block",
}, []string{"type"}),
}
}

func (m *metricsRecorder) RecordBlockIdType(t BlockIdType) {
m.blockIdType.WithLabelValues(string(t)).Inc()
}

func (m *metricsRecorder) Registry() *prometheus.Registry {
return m.registry
}
Loading

0 comments on commit 0fa146d

Please sign in to comment.