Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First pass API for introspection of edges and assertions #371

Merged
merged 15 commits into from
Jul 24, 2023
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
43 changes: 43 additions & 0 deletions api/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "api",
srcs = [
"data.go",
"edges.go",
"log.go",
"method_assertions.go",
"method_edges.go",
"method_healthz.go",
"server.go",
],
importpath = "github.com/OffchainLabs/challenge-protocol-v2/api",
visibility = ["//visibility:public"],
deps = [
"//chain-abstraction:protocol",
"@com_github_ethereum_go_ethereum//common",
"@com_github_ethereum_go_ethereum//log",
"@com_github_gorilla_mux//:mux",
"@org_golang_x_sync//errgroup",
],
)

go_test(
name = "api_test",
srcs = [
"data_test.go",
"edges_test.go",
"method_assertions_test.go",
"method_edges_test.go",
"method_healthz_test.go",
"server_helper_test.go",
"server_test.go",
],
embed = [":api"],
deps = [
"//chain-abstraction:protocol",
"//challenge-manager/challenge-tree/mock",
"@com_github_gorilla_mux//:mux",
"@in_gopkg_d4l3k_messagediff_v1//:messagediff_v1",
],
)
9 changes: 9 additions & 0 deletions api/data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package api

import (
protocol "github.com/OffchainLabs/challenge-protocol-v2/chain-abstraction"
)

type DataAccessor interface {
GetEdges() []protocol.SpecEdge
rauljordan marked this conversation as resolved.
Show resolved Hide resolved
}
16 changes: 16 additions & 0 deletions api/data_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package api_test

import (
"github.com/OffchainLabs/challenge-protocol-v2/api"
protocol "github.com/OffchainLabs/challenge-protocol-v2/chain-abstraction"
)

var _ = api.DataAccessor(&FakeDataAccessor{})

type FakeDataAccessor struct {
Edges []protocol.SpecEdge
}

func (f *FakeDataAccessor) GetEdges() []protocol.SpecEdge {
return f.Edges
}
186 changes: 186 additions & 0 deletions api/edges.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package api

import (
"context"
"fmt"

protocol "github.com/OffchainLabs/challenge-protocol-v2/chain-abstraction"
"github.com/ethereum/go-ethereum/common"
"golang.org/x/sync/errgroup"
)

type Edge struct {
ID common.Hash `json:"id"`
Type string `json:"type"`
StartCommitment *Commitment `json:"startCommitment"`
EndCommitment *Commitment `json:"endCommitment"`
CreatedAtBlock uint64 `json:"createdAtBlock"`
MutualID common.Hash `json:"mutualId"`
OriginID common.Hash `json:"originId"`
ClaimID common.Hash `json:"claimId"`
HasChildren bool `json:"hasChildren"`
LowerChildID common.Hash `json:"lowerChildId"`
UpperChildID common.Hash `json:"upperChildId"`
MiniStaker common.Address `json:"miniStaker"`
AssertionHash common.Hash `json:"assertionHash"`
TimeUnrivaled uint64 `json:"timeUnrivaled"`
HasRival bool `json:"hasRival"`
Status string `json:"status"`
HasLengthOneRival bool `json:"hasLengthOneRival"`
TopLevelClaimHeight protocol.OriginHeights `json:"topLevelClaimHeight"`

// Validator's point of view
// IsHonest bool `json:"isHonest"`
// AgreesWithStartCommitment `json:"agreesWithStartCommitment"`
Comment on lines +31 to +34
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Validator's point of view
// IsHonest bool `json:"isHonest"`
// AgreesWithStartCommitment `json:"agreesWithStartCommitment"`

}

type Commitment struct {
Height uint64 `json:"height"`
Hash common.Hash `json:"hash"`
}

func convertSpecEdgeEdgesToEdges(ctx context.Context, e []protocol.SpecEdge) ([]*Edge, error) {
// Convert concurrently as some of the underlying methods are API calls.
eg, ctx := errgroup.WithContext(ctx)

edges := make([]*Edge, len(e))
for i, edge := range e {
index := i
ee := edge

eg.Go(func() (err error) {
edges[index], err = convertSpecEdgeEdgeToEdge(ctx, ee)
return
})
}
return edges, eg.Wait()
}

func convertSpecEdgeEdgeToEdge(ctx context.Context, e protocol.SpecEdge) (*Edge, error) {
edge := &Edge{
ID: common.Hash(e.Id()),
Type: e.GetType().String(),
StartCommitment: toCommitment(e.StartCommitment),
EndCommitment: toCommitment(e.EndCommitment),
MutualID: common.Hash(e.MutualId()),
OriginID: common.Hash(e.OriginId()),
ClaimID: func() common.Hash {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern is so neat

if !e.ClaimId().IsNone() {
return common.Hash(e.ClaimId().Unwrap())
}
return common.Hash{}
}(),
MiniStaker: func() common.Address {
if !e.MiniStaker().IsNone() {
return common.Address(e.MiniStaker().Unwrap())
}
return common.Address{}
}(),
CreatedAtBlock: func() uint64 {
cab, err := e.CreatedAtBlock()
if err != nil {
return 0
}
return cab
}(),
}

// The following methods include calls to the backend, so we run them concurrently.
// Note: No rate limiting currently in place.
eg, ctx := errgroup.WithContext(ctx)

eg.Go(func() error {
hasChildren, err := e.HasChildren(ctx)
if err != nil {
return fmt.Errorf("failed to get edge children: %w", err)
}
edge.HasChildren = hasChildren
return nil
})

eg.Go(func() error {
lowerChild, err := e.LowerChild(ctx)
if err != nil {
return fmt.Errorf("failed to get edge lower child: %w", err)
}
if !lowerChild.IsNone() {
edge.LowerChildID = common.Hash(lowerChild.Unwrap())
}
return nil
})

eg.Go(func() error {
upperChild, err := e.UpperChild(ctx)
if err != nil {
return fmt.Errorf("failed to get edge upper child: %w", err)
}
if !upperChild.IsNone() {
edge.UpperChildID = common.Hash(upperChild.Unwrap())
}
return nil
})

eg.Go(func() error {
ah, err := e.AssertionHash(ctx)
if err != nil {
return fmt.Errorf("failed to get edge assertion hash: %w", err)
}
edge.AssertionHash = common.Hash(ah)
return nil
})

eg.Go(func() error {
timeUnrivaled, err := e.TimeUnrivaled(ctx)
if err != nil {
return fmt.Errorf("failed to get edge time unrivaled: %w", err)
}
edge.TimeUnrivaled = timeUnrivaled
return nil
})

eg.Go(func() error {
hasRival, err := e.HasRival(ctx)
if err != nil {
return fmt.Errorf("failed to get edge has rival: %w", err)
}
edge.HasRival = hasRival
return nil
})

eg.Go(func() error {
status, err := e.Status(ctx)
if err != nil {
return fmt.Errorf("failed to get edge status: %w", err)
}
edge.Status = status.String()
return nil
})

eg.Go(func() error {
hasLengthOneRival, err := e.HasLengthOneRival(ctx)
if err != nil {
return fmt.Errorf("failed to get edge has length one rival: %w", err)
}
edge.HasLengthOneRival = hasLengthOneRival
return nil
})

eg.Go(func() error {
topLevelClaimHeight, err := e.TopLevelClaimHeight(ctx)
if err != nil {
return fmt.Errorf("failed to get edge top level claim height: %w", err)
}
edge.TopLevelClaimHeight = topLevelClaimHeight
return nil
})

return edge, eg.Wait()
}

func toCommitment(fn func() (protocol.Height, common.Hash)) *Commitment {
h, hs := fn()
return &Commitment{
Height: uint64(h),
Hash: hs,
}
}
37 changes: 37 additions & 0 deletions api/edges_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package api_test

import (
"github.com/OffchainLabs/challenge-protocol-v2/api"
protocol "github.com/OffchainLabs/challenge-protocol-v2/chain-abstraction"
"github.com/OffchainLabs/challenge-protocol-v2/challenge-manager/challenge-tree/mock"
)

func edgesToMockEdges(e []*api.Edge) []*mock.Edge {
me := make([]*mock.Edge, len(e))
for i, ee := range e {
me[i] = edgeToMockEdge(ee)
}
return me
}

func edgeToMockEdge(e *api.Edge) *mock.Edge {
return &mock.Edge{
ID: mock.EdgeId(e.ID.Bytes()),
EdgeType: func() protocol.EdgeType {
et, err := protocol.EdgeTypeFromString(e.Type)
if err != nil {
panic(err)
}
return et
}(),
StartHeight: e.StartCommitment.Height,
StartCommit: mock.Commit(e.StartCommitment.Hash.Bytes()),
EndHeight: e.EndCommitment.Height,
EndCommit: mock.Commit(e.EndCommitment.Hash.Bytes()),
OriginID: mock.OriginId(e.OriginID.Bytes()),
ClaimID: string(e.ClaimID.Bytes()),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a .String() method on hashes too

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but that returns it in the hex string form where we need it to be back in the original form

LowerChildID: mock.EdgeId(e.LowerChildID.Bytes()),
UpperChildID: mock.EdgeId(e.UpperChildID.Bytes()),
CreationBlock: e.CreatedAtBlock,
}
}
7 changes: 7 additions & 0 deletions api/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package api

import gethLog "github.com/ethereum/go-ethereum/log"

var (
log = gethLog.New("service", "api")
)
17 changes: 17 additions & 0 deletions api/method_assertions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package api

import "net/http"

func listAssertionsHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
if _, err := w.Write([]byte("not implemented")); err != nil {
log.Error("failed to write response body", "err", err)
}
}

func getAssertionHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
if _, err := w.Write([]byte("not implemented")); err != nil {
log.Error("failed to write response body", "err", err)
}
}
49 changes: 49 additions & 0 deletions api/method_assertions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package api_test

import (
"net/http"
"net/http/httptest"
"testing"
)

func TestListAssertions(t *testing.T) {
s, _ := NewTestServer(t)

req, err := http.NewRequest("GET", "/assertions", nil)
if err != nil {
t.Fatal(err)
}

// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
rr := httptest.NewRecorder()

// Serve the request with the http recorder.
s.Router().ServeHTTP(rr, req)

// Check the status code is what we expect.
if status := rr.Code; status != http.StatusNotImplemented {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusNotImplemented)
}
}

func TestGetAssertion(t *testing.T) {
s, _ := NewTestServer(t)

req, err := http.NewRequest("GET", "/assertions/foo", nil)
if err != nil {
t.Fatal(err)
}

// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
rr := httptest.NewRecorder()

// Serve the request with the http recorder.
s.Router().ServeHTTP(rr, req)

// Check the status code is what we expect.
if status := rr.Code; status != http.StatusNotImplemented {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusNotImplemented)
}
}
Loading
Loading