refac some (#9185)

This commit is contained in:
a 2024-01-14 23:22:34 -06:00 committed by GitHub
parent ac9f9e0a25
commit 8d4d4d802c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
76 changed files with 5933 additions and 2909 deletions

View File

@ -73,7 +73,6 @@ func HandleEndpoint[T any](h EndpointHandler[T]) http.HandlerFunc {
ans, err := h.Handle(w, r)
log.Debug("beacon api request", "endpoint", r.URL.Path, "duration", time.Since(start))
if err != nil {
log.Error("beacon api request error", "err", err)
var endpointError *EndpointError
if e, ok := err.(*EndpointError); ok {
endpointError = e

View File

@ -0,0 +1,176 @@
package beaconhttp
import (
"fmt"
"net/http"
"regexp"
"strconv"
"github.com/go-chi/chi/v5"
"github.com/ledgerwatch/erigon-lib/common"
)
type chainTag int
var (
Head chainTag = 0
Finalized chainTag = 1
Justified chainTag = 2
Genesis chainTag = 3
)
// Represent either state id or block id
type SegmentID struct {
tag chainTag
slot *uint64
root *common.Hash
}
func (c *SegmentID) Head() bool {
return c.tag == Head && c.slot == nil && c.root == nil
}
func (c *SegmentID) Finalized() bool {
return c.tag == Finalized
}
func (c *SegmentID) Justified() bool {
return c.tag == Justified
}
func (c *SegmentID) Genesis() bool {
return c.tag == Genesis
}
func (c *SegmentID) GetSlot() *uint64 {
return c.slot
}
func (c *SegmentID) GetRoot() *common.Hash {
return c.root
}
func EpochFromRequest(r *http.Request) (uint64, error) {
// Must only be a number
regex := regexp.MustCompile(`^\d+$`)
epoch := chi.URLParam(r, "epoch")
if !regex.MatchString(epoch) {
return 0, fmt.Errorf("invalid path variable: {epoch}")
}
epochMaybe, err := strconv.ParseUint(epoch, 10, 64)
if err != nil {
return 0, err
}
return epochMaybe, nil
}
func StringFromRequest(r *http.Request, name string) (string, error) {
str := chi.URLParam(r, name)
if str == "" {
return "", nil
}
return str, nil
}
func BlockIdFromRequest(r *http.Request) (*SegmentID, error) {
regex := regexp.MustCompile(`^(?:0x[0-9a-fA-F]{64}|head|finalized|genesis|\d+)$`)
blockId := chi.URLParam(r, "block_id")
if !regex.MatchString(blockId) {
return nil, fmt.Errorf("invalid path variable: {block_id}")
}
if blockId == "head" {
return &SegmentID{tag: Head}, nil
}
if blockId == "finalized" {
return &SegmentID{tag: Finalized}, nil
}
if blockId == "genesis" {
return &SegmentID{tag: Genesis}, nil
}
slotMaybe, err := strconv.ParseUint(blockId, 10, 64)
if err == nil {
return &SegmentID{slot: &slotMaybe}, nil
}
root := common.HexToHash(blockId)
return &SegmentID{
root: &root,
}, nil
}
func StateIdFromRequest(r *http.Request) (*SegmentID, error) {
regex := regexp.MustCompile(`^(?:0x[0-9a-fA-F]{64}|head|finalized|genesis|justified|\d+)$`)
stateId := chi.URLParam(r, "state_id")
if !regex.MatchString(stateId) {
return nil, fmt.Errorf("invalid path variable: {state_id}")
}
if stateId == "head" {
return &SegmentID{tag: Head}, nil
}
if stateId == "finalized" {
return &SegmentID{tag: Finalized}, nil
}
if stateId == "genesis" {
return &SegmentID{tag: Genesis}, nil
}
if stateId == "justified" {
return &SegmentID{tag: Justified}, nil
}
slotMaybe, err := strconv.ParseUint(stateId, 10, 64)
if err == nil {
return &SegmentID{slot: &slotMaybe}, nil
}
root := common.HexToHash(stateId)
return &SegmentID{
root: &root,
}, nil
}
func HashFromQueryParams(r *http.Request, name string) (*common.Hash, error) {
hashStr := r.URL.Query().Get(name)
if hashStr == "" {
return nil, nil
}
// check if hashstr is an hex string
if len(hashStr) != 2+2*32 {
return nil, fmt.Errorf("invalid hash length")
}
if hashStr[:2] != "0x" {
return nil, fmt.Errorf("invalid hash prefix")
}
notHex, err := regexp.MatchString("[^0-9A-Fa-f]", hashStr[2:])
if err != nil {
return nil, err
}
if notHex {
return nil, fmt.Errorf("invalid hash characters")
}
hash := common.HexToHash(hashStr)
return &hash, nil
}
// uint64FromQueryParams retrieves a number from the query params, in base 10.
func Uint64FromQueryParams(r *http.Request, name string) (*uint64, error) {
str := r.URL.Query().Get(name)
if str == "" {
return nil, nil
}
num, err := strconv.ParseUint(str, 10, 64)
if err != nil {
return nil, err
}
return &num, nil
}
// decode a list of strings from the query params
func StringListFromQueryParams(r *http.Request, name string) ([]string, error) {
str := r.URL.Query().Get(name)
if str == "" {
return nil, nil
}
return regexp.MustCompile(`\s*,\s*`).Split(str, -1), nil
}

View File

@ -0,0 +1,95 @@
package beaconhttp
import (
"encoding/json"
"net/http"
"github.com/ledgerwatch/erigon-lib/types/ssz"
"github.com/ledgerwatch/erigon/cl/clparams"
)
type BeaconResponse struct {
Data any
Finalized *bool
Version *clparams.StateVersion
ExecutionOptimistic *bool
Extra map[string]any
}
func NewBeaconResponse(data any) *BeaconResponse {
return &BeaconResponse{
Data: data,
}
}
func (r *BeaconResponse) With(key string, value any) (out *BeaconResponse) {
out = new(BeaconResponse)
*out = *r
out.Extra[key] = value
return out
}
func (r *BeaconResponse) WithFinalized(finalized bool) (out *BeaconResponse) {
out = new(BeaconResponse)
*out = *r
out.Finalized = new(bool)
out.ExecutionOptimistic = new(bool)
out.Finalized = &finalized
return out
}
func (r *BeaconResponse) WithOptimistic(optimistic bool) (out *BeaconResponse) {
out = new(BeaconResponse)
*out = *r
out.ExecutionOptimistic = new(bool)
out.ExecutionOptimistic = &optimistic
return out
}
func (r *BeaconResponse) WithVersion(version clparams.StateVersion) (out *BeaconResponse) {
out = new(BeaconResponse)
*out = *r
out.Version = new(clparams.StateVersion)
out.Version = &version
return out
}
func (b *BeaconResponse) MarshalJSON() ([]byte, error) {
o := map[string]any{
"data": b.Data,
}
if b.Finalized != nil {
o["finalized"] = *b.Finalized
}
if b.Version != nil {
o["version"] = *b.Version
}
if b.ExecutionOptimistic != nil {
o["execution_optimistic"] = *b.ExecutionOptimistic
}
for k, v := range b.Extra {
o[k] = v
}
return json.Marshal(o)
}
func (b *BeaconResponse) EncodeSSZ(xs []byte) ([]byte, error) {
marshaler, ok := b.Data.(ssz.Marshaler)
if !ok {
return nil, NewEndpointError(http.StatusBadRequest, "This endpoint does not support SSZ response")
}
encoded, err := marshaler.EncodeSSZ(nil)
if err != nil {
return nil, err
}
return encoded, nil
}
func (b *BeaconResponse) EncodingSizeSSZ() int {
marshaler, ok := b.Data.(ssz.Marshaler)
if !ok {
return 9
}
return marshaler.EncodingSizeSSZ()
}

View File

@ -0,0 +1,8 @@
package beacontest
import "errors"
var (
ErrExpressionMustReturnBool = errors.New("cel expression must return bool")
ErrUnknownType = errors.New("unknown type")
)

View File

@ -0,0 +1,436 @@
package beacontest
import (
"bytes"
"context"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"reflect"
"strings"
"testing"
"text/template"
"github.com/Masterminds/sprig/v3"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/yaml"
)
type HarnessOption func(*Harness) error
func WithTesting(t *testing.T) func(*Harness) error {
return func(h *Harness) error {
h.t = t
return nil
}
}
func WithTests(name string, xs []Test) func(*Harness) error {
return func(h *Harness) error {
h.tests[name] = xs
return nil
}
}
func WithHandler(name string, handler http.Handler) func(*Harness) error {
return func(h *Harness) error {
h.handlers[name] = handler
return nil
}
}
func WithFilesystem(name string, handler afero.Fs) func(*Harness) error {
return func(h *Harness) error {
h.fss[name] = handler
return nil
}
}
func WithTestFromFs(fs afero.Fs, name string) func(*Harness) error {
return func(h *Harness) error {
filename := name
for _, fn := range []string{name, name + ".yaml", name + ".yml", name + ".json"} {
// check if file exists
_, err := fs.Stat(fn)
if err == nil {
filename = fn
break
}
}
xs, err := afero.ReadFile(fs, filename)
if err != nil {
return err
}
return WithTestFromBytes(name, xs)(h)
}
}
type Extra struct {
Vars map[string]any `json:"vars"`
RawBodyZZZZ json.RawMessage `json:"tests"`
}
func WithTestFromBytes(name string, xs []byte) func(*Harness) error {
return func(h *Harness) error {
var t struct {
T []Test `json:"tests"`
}
x := &Extra{}
s := md5.New()
s.Write(xs)
hsh := hex.EncodeToString(s.Sum(nil))
// unmarshal just the extra data
err := yaml.Unmarshal(xs, &x, yaml.JSONOpt(func(d *json.Decoder) *json.Decoder {
return d
}))
if err != nil {
return err
}
tmpl := template.Must(template.New(hsh).Funcs(sprig.FuncMap()).Parse(string(xs)))
// execute the template using the extra data as the provided top level object
// we can use the original buffer as the output since the original buffer has already been copied when it was passed into template
buf := bytes.NewBuffer(xs)
buf.Reset()
err = tmpl.Execute(buf, x)
if err != nil {
return err
}
err = yaml.Unmarshal(buf.Bytes(), &t)
if err != nil {
return err
}
if len(t.T) == 0 {
return fmt.Errorf("suite with name %s had no tests", name)
}
h.tests[name] = t.T
return nil
}
}
type Harness struct {
tests map[string][]Test
t *testing.T
handlers map[string]http.Handler
fss map[string]afero.Fs
}
func Execute(options ...HarnessOption) {
h := &Harness{
handlers: map[string]http.Handler{},
tests: map[string][]Test{},
fss: map[string]afero.Fs{
"": afero.NewOsFs(),
},
}
for _, v := range options {
err := v(h)
if err != nil {
h.t.Error(err)
}
}
h.Execute()
}
func (h *Harness) Execute() {
ctx := context.Background()
for suiteName, tests := range h.tests {
for idx, v := range tests {
v.Actual.h = h
v.Expect.h = h
name := v.Name
if name == "" {
name = "test"
}
fullname := fmt.Sprintf("%s_%s_%d", suiteName, name, idx)
h.t.Run(fullname, func(t *testing.T) {
err := v.Execute(ctx, t)
require.NoError(t, err)
})
}
}
}
type Test struct {
Name string `json:"name"`
Expect Source `json:"expect"`
Actual Source `json:"actual"`
Compare Comparison `json:"compare"`
}
func (c *Test) Execute(ctx context.Context, t *testing.T) error {
a, aCode, err := c.Expect.Execute(ctx)
if err != nil {
return fmt.Errorf("get expect data: %w", err)
}
b, bCode, err := c.Actual.Execute(ctx)
if err != nil {
return fmt.Errorf("get actual data: %w", err)
}
err = c.Compare.Compare(t, a, b, aCode, bCode)
if err != nil {
return fmt.Errorf("compare: %w", err)
}
return nil
}
type Comparison struct {
Expr string `json:"expr"`
Exprs []string `json:"exprs"`
Literal bool `json:"literal"`
}
func (c *Comparison) Compare(t *testing.T, aRaw, bRaw json.RawMessage, aCode, bCode int) error {
var err error
var a, b any
var aType, bType *types.Type
if !c.Literal {
var aMap, bMap any
err = yaml.Unmarshal(aRaw, &aMap)
if err != nil {
return err
}
err = yaml.Unmarshal(bRaw, &bMap)
if err != nil {
return err
}
a = aMap
b = bMap
if a != nil {
switch reflect.TypeOf(a).Kind() {
case reflect.Slice:
aType = cel.ListType(cel.MapType(cel.StringType, cel.DynType))
default:
aType = cel.MapType(cel.StringType, cel.DynType)
}
} else {
aType = cel.MapType(cel.StringType, cel.DynType)
}
if b != nil {
switch reflect.TypeOf(b).Kind() {
case reflect.Slice:
bType = cel.ListType(cel.MapType(cel.StringType, cel.DynType))
default:
bType = cel.MapType(cel.StringType, cel.DynType)
}
} else {
bType = cel.MapType(cel.StringType, cel.DynType)
}
} else {
a = string(aRaw)
b = string(bRaw)
aType = cel.StringType
bType = cel.StringType
}
exprs := []string{}
// if no default expr set and no exprs are set, then add the default expr
if len(c.Exprs) == 0 && c.Expr == "" {
exprs = append(exprs, "actual_code == 200", "actual == expect")
}
env, err := cel.NewEnv(
cel.Variable("expect", aType),
cel.Variable("actual", bType),
cel.Variable("expect_code", cel.IntType),
cel.Variable("actual_code", cel.IntType),
)
if err != nil {
return err
}
for _, expr := range append(c.Exprs, exprs...) {
ast, issues := env.Compile(expr)
if issues != nil && issues.Err() != nil {
return issues.Err()
}
prg, err := env.Program(ast)
if err != nil {
return fmt.Errorf("program construction error: %w", err)
}
res, _, err := prg.Eval(map[string]any{
"expect": a,
"actual": b,
"expect_code": aCode,
"actual_code": bCode,
})
if err != nil {
return err
}
if res.Type() != cel.BoolType {
return ErrExpressionMustReturnBool
}
bres, ok := res.Value().(bool)
if !ok {
return ErrExpressionMustReturnBool
}
if !assert.Equal(t, bres, true, `expr: %s`, expr) {
if os.Getenv("HIDE_HARNESS_LOG") != "1" {
t.Logf(`name: %s
expect%d: %v
actual%d: %v
expr: %s
`, t.Name(), aCode, a, bCode, b, expr)
}
t.FailNow()
}
}
return nil
}
type Source struct {
// backref to the harness
h *Harness `json:"-"`
// remote type
Remote *string `json:"remote,omitempty"`
Handler *string `json:"handler,omitempty"`
Method string `json:"method"`
Path string `json:"path"`
Query map[string]string `json:"query"`
Headers map[string]string `json:"headers"`
Body *Source `json:"body,omitempty"`
// data type
Data any `json:"data,omitempty"`
// file type
File *string `json:"file,omitempty"`
Fs string `json:"fs,omitempty"`
// for raw type
Raw *string `json:"raw,omitempty"`
}
func (s *Source) Execute(ctx context.Context) (json.RawMessage, int, error) {
if s.Raw != nil {
return s.executeRaw(ctx)
}
if s.File != nil {
return s.executeFile(ctx)
}
if s.Remote != nil || s.Handler != nil {
return s.executeRemote(ctx)
}
if s.Data != nil {
return s.executeData(ctx)
}
return s.executeEmpty(ctx)
}
func (s *Source) executeRemote(ctx context.Context) (json.RawMessage, int, error) {
method := "GET"
if s.Method != "" {
method = s.Method
}
method = strings.ToUpper(method)
var body io.Reader
// hydrate the harness
if s.Body != nil {
s.Body.h = s.h
msg, _, err := s.Body.Execute(ctx)
if err != nil {
return nil, 0, fmt.Errorf("getting body: %w", err)
}
body = bytes.NewBuffer(msg)
}
var purl *url.URL
if s.Remote != nil {
niceUrl, err := url.Parse(*s.Remote)
if err != nil {
return nil, 0, err
}
purl = niceUrl
} else if s.Handler != nil {
handler, ok := s.h.handlers[*s.Handler]
if !ok {
return nil, 0, fmt.Errorf("handler not registered: %s", *s.Handler)
}
server := httptest.NewServer(handler)
defer server.Close()
niceUrl, err := url.Parse(server.URL)
if err != nil {
return nil, 0, err
}
purl = niceUrl
} else {
panic("impossible code path. bug? source.Execute() should ensure this never happens")
}
purl = purl.JoinPath(s.Path)
q := purl.Query()
for k, v := range s.Query {
q.Add(k, v)
}
purl.RawQuery = q.Encode()
request, err := http.NewRequest(method, purl.String(), body)
if err != nil {
return nil, 0, err
}
for k, v := range s.Headers {
request.Header.Set(k, v)
}
resp, err := http.DefaultClient.Do(request)
if err != nil {
return nil, 0, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, resp.StatusCode, nil
}
out, err := io.ReadAll(resp.Body)
if err != nil {
return nil, 200, err
}
return json.RawMessage(out), 200, nil
}
func (s *Source) executeData(ctx context.Context) (json.RawMessage, int, error) {
ans, err := json.Marshal(s.Data)
if err != nil {
return nil, 400, nil
}
return ans, 200, nil
}
func (s *Source) executeFile(ctx context.Context) (json.RawMessage, int, error) {
afs, ok := s.h.fss[s.Fs]
if !ok {
return nil, 404, fmt.Errorf("filesystem %s not defined", s.Fs)
}
name := *s.File
filename := name
for _, fn := range []string{name, name + ".yaml", name + ".yml", name + ".json"} {
// check if file exists
_, err := afs.Stat(fn)
if err == nil {
filename = fn
break
}
}
fileBytes, err := afero.ReadFile(afs, filename)
if err != nil {
return nil, 404, err
}
return json.RawMessage(fileBytes), 200, nil
}
func (s *Source) executeRaw(ctx context.Context) (json.RawMessage, int, error) {
return json.RawMessage(*s.Raw), 200, nil
}
func (s *Source) executeEmpty(ctx context.Context) (json.RawMessage, int, error) {
return []byte("{}"), 200, nil
}

View File

@ -0,0 +1,19 @@
package beacontest_test
import (
"testing"
_ "embed"
"github.com/ledgerwatch/erigon/cl/beacon/beacontest"
)
//go:embed harness_test_data.yml
var testData []byte
func TestSimpleHarness(t *testing.T) {
beacontest.Execute(
beacontest.WithTesting(t),
beacontest.WithTestFromBytes("test", testData),
)
}

View File

@ -0,0 +1,62 @@
tests:
- name: "equality expression"
expect:
data:
hello: world
actual:
data:
hello: world
compare:
type: "expr"
expr: "actual == expect"
- name: "neg equality expr"
expect:
data:
hello: world
actual:
data:
hello: worlds
compare:
expr: "actual != expect"
- name: "subkey world"
expect:
data:
hi: world
actual:
data:
hello: world
compare:
expr: "actual.hello == expect.hi"
- name: "default compare"
expect:
data:
hello: world
actual:
data:
hello: world
- name: "default neg compare"
expect:
data:
hello: world
actual:
data:
hello: worlds
compare:
expr: "actual != expect"
- name: "key order doesnt matter for non literal"
expect:
data:
a: 1
b: 2
actual:
raw: '{"b":2,"a":1}'
- name: "key order does matter for literal"
expect:
data:
a: 1
b: 2
actual:
raw: '{"b":2,"a":1}'
compare:
literal: true
expr: "actual != expect"

View File

@ -0,0 +1,252 @@
package beacontest
import (
"io/fs"
"os"
"path"
"runtime"
"strings"
"time"
"github.com/spf13/afero"
)
var (
_ afero.Lstater = (*BasePathFs)(nil)
_ fs.ReadDirFile = (*BasePathFile)(nil)
)
// This is a version of the afero basepathfs that uses path instead of filepath.
// this is needed to work with things like zipfs and embedfs on windows
type BasePathFs struct {
source afero.Fs
path string
}
type BasePathFile struct {
afero.File
path string
}
func (f *BasePathFile) Name() string {
sourcename := f.File.Name()
return strings.TrimPrefix(sourcename, path.Clean(f.path))
}
func (f *BasePathFile) ReadDir(n int) ([]fs.DirEntry, error) {
if rdf, ok := f.File.(fs.ReadDirFile); ok {
return rdf.ReadDir(n)
}
return readDirFile{f.File}.ReadDir(n)
}
func NewBasePathFs(source afero.Fs, path string) afero.Fs {
return &BasePathFs{source: source, path: path}
}
// on a file outside the base path it returns the given file name and an error,
// else the given file with the base path prepended
func (b *BasePathFs) RealPath(name string) (p string, err error) {
if err := validateBasePathName(name); err != nil {
return name, err
}
bpath := path.Clean(b.path)
p = path.Clean(path.Join(bpath, name))
if !strings.HasPrefix(p, bpath) {
return name, os.ErrNotExist
}
return p, nil
}
func validateBasePathName(name string) error {
if runtime.GOOS != "windows" {
// Not much to do here;
// the virtual file paths all look absolute on *nix.
return nil
}
// On Windows a common mistake would be to provide an absolute OS path
// We could strip out the base part, but that would not be very portable.
if path.IsAbs(name) {
return os.ErrNotExist
}
return nil
}
func (b *BasePathFs) Chtimes(name string, atime, mtime time.Time) (err error) {
if name, err = b.RealPath(name); err != nil {
return &os.PathError{Op: "chtimes", Path: name, Err: err}
}
return b.source.Chtimes(name, atime, mtime)
}
func (b *BasePathFs) Chmod(name string, mode os.FileMode) (err error) {
if name, err = b.RealPath(name); err != nil {
return &os.PathError{Op: "chmod", Path: name, Err: err}
}
return b.source.Chmod(name, mode)
}
func (b *BasePathFs) Chown(name string, uid, gid int) (err error) {
if name, err = b.RealPath(name); err != nil {
return &os.PathError{Op: "chown", Path: name, Err: err}
}
return b.source.Chown(name, uid, gid)
}
func (b *BasePathFs) Name() string {
return "BasePathFs"
}
func (b *BasePathFs) Stat(name string) (fi os.FileInfo, err error) {
if name, err = b.RealPath(name); err != nil {
return nil, &os.PathError{Op: "stat", Path: name, Err: err}
}
return b.source.Stat(name)
}
func (b *BasePathFs) Rename(oldname, newname string) (err error) {
if oldname, err = b.RealPath(oldname); err != nil {
return &os.PathError{Op: "rename", Path: oldname, Err: err}
}
if newname, err = b.RealPath(newname); err != nil {
return &os.PathError{Op: "rename", Path: newname, Err: err}
}
return b.source.Rename(oldname, newname)
}
func (b *BasePathFs) RemoveAll(name string) (err error) {
if name, err = b.RealPath(name); err != nil {
return &os.PathError{Op: "remove_all", Path: name, Err: err}
}
return b.source.RemoveAll(name)
}
func (b *BasePathFs) Remove(name string) (err error) {
if name, err = b.RealPath(name); err != nil {
return &os.PathError{Op: "remove", Path: name, Err: err}
}
return b.source.Remove(name)
}
func (b *BasePathFs) OpenFile(name string, flag int, mode os.FileMode) (f afero.File, err error) {
if name, err = b.RealPath(name); err != nil {
return nil, &os.PathError{Op: "openfile", Path: name, Err: err}
}
sourcef, err := b.source.OpenFile(name, flag, mode)
if err != nil {
return nil, err
}
return &BasePathFile{sourcef, b.path}, nil
}
func (b *BasePathFs) Open(name string) (f afero.File, err error) {
if name, err = b.RealPath(name); err != nil {
return nil, &os.PathError{Op: "open", Path: name, Err: err}
}
sourcef, err := b.source.Open(name)
if err != nil {
return nil, err
}
return &BasePathFile{File: sourcef, path: b.path}, nil
}
func (b *BasePathFs) Mkdir(name string, mode os.FileMode) (err error) {
if name, err = b.RealPath(name); err != nil {
return &os.PathError{Op: "mkdir", Path: name, Err: err}
}
return b.source.Mkdir(name, mode)
}
func (b *BasePathFs) MkdirAll(name string, mode os.FileMode) (err error) {
if name, err = b.RealPath(name); err != nil {
return &os.PathError{Op: "mkdir", Path: name, Err: err}
}
return b.source.MkdirAll(name, mode)
}
func (b *BasePathFs) Create(name string) (f afero.File, err error) {
if name, err = b.RealPath(name); err != nil {
return nil, &os.PathError{Op: "create", Path: name, Err: err}
}
sourcef, err := b.source.Create(name)
if err != nil {
return nil, err
}
return &BasePathFile{File: sourcef, path: b.path}, nil
}
func (b *BasePathFs) LstatIfPossible(name string) (os.FileInfo, bool, error) {
name, err := b.RealPath(name)
if err != nil {
return nil, false, &os.PathError{Op: "lstat", Path: name, Err: err}
}
if lstater, ok := b.source.(afero.Lstater); ok {
return lstater.LstatIfPossible(name)
}
fi, err := b.source.Stat(name)
return fi, false, err
}
func (b *BasePathFs) SymlinkIfPossible(oldname, newname string) error {
oldname, err := b.RealPath(oldname)
if err != nil {
return &os.LinkError{Op: "symlink", Old: oldname, New: newname, Err: err}
}
newname, err = b.RealPath(newname)
if err != nil {
return &os.LinkError{Op: "symlink", Old: oldname, New: newname, Err: err}
}
if linker, ok := b.source.(afero.Linker); ok {
return linker.SymlinkIfPossible(oldname, newname)
}
return &os.LinkError{Op: "symlink", Old: oldname, New: newname, Err: afero.ErrNoSymlink}
}
func (b *BasePathFs) ReadlinkIfPossible(name string) (string, error) {
name, err := b.RealPath(name)
if err != nil {
return "", &os.PathError{Op: "readlink", Path: name, Err: err}
}
if reader, ok := b.source.(afero.LinkReader); ok {
return reader.ReadlinkIfPossible(name)
}
return "", &os.PathError{Op: "readlink", Path: name, Err: afero.ErrNoReadlink}
}
// the readDirFile helper is requried
// readDirFile provides adapter from afero.File to fs.ReadDirFile needed for correct Open
type readDirFile struct {
afero.File
}
var _ fs.ReadDirFile = readDirFile{}
func (r readDirFile) ReadDir(n int) ([]fs.DirEntry, error) {
items, err := r.File.Readdir(n)
if err != nil {
return nil, err
}
ret := make([]fs.DirEntry, len(items))
for i := range items {
ret[i] = fileInfoDirEntry{FileInfo: items[i]}
}
return ret, nil
}
// FileInfoDirEntry provides an adapter from os.FileInfo to fs.DirEntry
type fileInfoDirEntry struct {
fs.FileInfo
}
var _ fs.DirEntry = fileInfoDirEntry{}
func (d fileInfoDirEntry) Type() fs.FileMode { return d.FileInfo.Mode().Type() }
func (d fileInfoDirEntry) Info() (fs.FileInfo, error) { return d.FileInfo, nil }

View File

@ -40,7 +40,7 @@ type attestationsRewardsResponse struct {
TotalRewards []TotalReward `json:"total_rewards"`
}
func (a *ApiHandler) getAttestationsRewards(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) getAttestationsRewards(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
ctx := r.Context()
tx, err := a.indiciesDB.BeginRo(ctx)
@ -49,7 +49,7 @@ func (a *ApiHandler) getAttestationsRewards(w http.ResponseWriter, r *http.Reque
}
defer tx.Rollback()
epoch, err := epochFromRequest(r)
epoch, err := beaconhttp.EpochFromRequest(r)
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
@ -180,7 +180,7 @@ func (a *ApiHandler) baseReward(version clparams.StateVersion, effectiveBalance,
return effectiveBalance * a.beaconChainCfg.BaseRewardFactor / activeBalanceRoot / a.beaconChainCfg.BaseRewardsPerEpoch
}
func (a *ApiHandler) computeAttestationsRewardsForAltair(validatorSet *solid.ValidatorSet, inactivityScores solid.Uint64ListSSZ, previousParticipation *solid.BitList, inactivityLeak bool, filterIndicies []uint64, epoch uint64) (*beaconResponse, error) {
func (a *ApiHandler) computeAttestationsRewardsForAltair(validatorSet *solid.ValidatorSet, inactivityScores solid.Uint64ListSSZ, previousParticipation *solid.BitList, inactivityLeak bool, filterIndicies []uint64, epoch uint64) (*beaconhttp.BeaconResponse, error) {
totalActiveBalance := uint64(0)
flagsUnslashedIndiciesSet := statechange.GetUnslashedIndiciesSet(a.beaconChainCfg, epoch, validatorSet, previousParticipation)
weights := a.beaconChainCfg.ParticipationWeights()
@ -289,7 +289,7 @@ func (a *ApiHandler) computeAttestationsRewardsForAltair(validatorSet *solid.Val
}
// processRewardsAndPenaltiesPhase0 process rewards and penalties for phase0 state.
func (a *ApiHandler) computeAttestationsRewardsForPhase0(s *state.CachingBeaconState, filterIndicies []uint64, epoch uint64) (*beaconResponse, error) {
func (a *ApiHandler) computeAttestationsRewardsForPhase0(s *state.CachingBeaconState, filterIndicies []uint64, epoch uint64) (*beaconhttp.BeaconResponse, error) {
response := &attestationsRewardsResponse{}
beaconConfig := s.BeaconConfig()
if epoch == beaconConfig.GenesisEpoch {

File diff suppressed because one or more lines are too long

View File

@ -23,18 +23,18 @@ type getHeadersRequest struct {
ParentRoot *libcommon.Hash `json:"root,omitempty"`
}
func (a *ApiHandler) rootFromBlockId(ctx context.Context, tx kv.Tx, blockId *segmentID) (root libcommon.Hash, err error) {
func (a *ApiHandler) rootFromBlockId(ctx context.Context, tx kv.Tx, blockId *beaconhttp.SegmentID) (root libcommon.Hash, err error) {
switch {
case blockId.head():
case blockId.Head():
root, _, err = a.forkchoiceStore.GetHead()
if err != nil {
return libcommon.Hash{}, err
}
case blockId.finalized():
case blockId.Finalized():
root = a.forkchoiceStore.FinalizedCheckpoint().BlockRoot()
case blockId.justified():
case blockId.Justified():
root = a.forkchoiceStore.JustifiedCheckpoint().BlockRoot()
case blockId.genesis():
case blockId.Genesis():
root, err = beacon_indicies.ReadCanonicalBlockRoot(tx, 0)
if err != nil {
return libcommon.Hash{}, err
@ -42,24 +42,24 @@ func (a *ApiHandler) rootFromBlockId(ctx context.Context, tx kv.Tx, blockId *seg
if root == (libcommon.Hash{}) {
return libcommon.Hash{}, beaconhttp.NewEndpointError(http.StatusNotFound, "genesis block not found")
}
case blockId.getSlot() != nil:
root, err = beacon_indicies.ReadCanonicalBlockRoot(tx, *blockId.getSlot())
case blockId.GetSlot() != nil:
root, err = beacon_indicies.ReadCanonicalBlockRoot(tx, *blockId.GetSlot())
if err != nil {
return libcommon.Hash{}, err
}
if root == (libcommon.Hash{}) {
return libcommon.Hash{}, beaconhttp.NewEndpointError(http.StatusNotFound, fmt.Sprintf("block not found %d", *blockId.getSlot()))
return libcommon.Hash{}, beaconhttp.NewEndpointError(http.StatusNotFound, fmt.Sprintf("block not found %d", *blockId.GetSlot()))
}
case blockId.getRoot() != nil:
case blockId.GetRoot() != nil:
// first check if it exists
root = *blockId.getRoot()
root = *blockId.GetRoot()
default:
return libcommon.Hash{}, beaconhttp.NewEndpointError(http.StatusInternalServerError, "cannot parse block id")
}
return
}
func (a *ApiHandler) getBlock(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) getBlock(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
ctx := r.Context()
tx, err := a.indiciesDB.BeginRo(ctx)
if err != nil {
@ -67,7 +67,7 @@ func (a *ApiHandler) getBlock(w http.ResponseWriter, r *http.Request) (*beaconRe
}
defer tx.Rollback()
blockId, err := blockIdFromRequest(r)
blockId, err := beaconhttp.BlockIdFromRequest(r)
if err != nil {
return nil, err
}
@ -90,11 +90,11 @@ func (a *ApiHandler) getBlock(w http.ResponseWriter, r *http.Request) (*beaconRe
return nil, err
}
return newBeaconResponse(blk).
withFinalized(root == canonicalRoot && blk.Block.Slot <= a.forkchoiceStore.FinalizedSlot()).
withVersion(blk.Version()), nil
WithFinalized(root == canonicalRoot && blk.Block.Slot <= a.forkchoiceStore.FinalizedSlot()).
WithVersion(blk.Version()), nil
}
func (a *ApiHandler) getBlindedBlock(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) getBlindedBlock(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
ctx := r.Context()
tx, err := a.indiciesDB.BeginRo(ctx)
if err != nil {
@ -102,7 +102,7 @@ func (a *ApiHandler) getBlindedBlock(w http.ResponseWriter, r *http.Request) (*b
}
defer tx.Rollback()
blockId, err := blockIdFromRequest(r)
blockId, err := beaconhttp.BlockIdFromRequest(r)
if err != nil {
return nil, err
}
@ -129,18 +129,18 @@ func (a *ApiHandler) getBlindedBlock(w http.ResponseWriter, r *http.Request) (*b
return nil, err
}
return newBeaconResponse(blinded).
withFinalized(root == canonicalRoot && blk.Block.Slot <= a.forkchoiceStore.FinalizedSlot()).
withVersion(blk.Version()), nil
WithFinalized(root == canonicalRoot && blk.Block.Slot <= a.forkchoiceStore.FinalizedSlot()).
WithVersion(blk.Version()), nil
}
func (a *ApiHandler) getBlockAttestations(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) getBlockAttestations(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
ctx := r.Context()
tx, err := a.indiciesDB.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
blockId, err := blockIdFromRequest(r)
blockId, err := beaconhttp.BlockIdFromRequest(r)
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
@ -160,18 +160,19 @@ func (a *ApiHandler) getBlockAttestations(w http.ResponseWriter, r *http.Request
if err != nil {
return nil, err
}
return newBeaconResponse(blk.Block.Body.Attestations).withFinalized(root == canonicalRoot && blk.Block.Slot <= a.forkchoiceStore.FinalizedSlot()).
withVersion(blk.Version()), nil
return newBeaconResponse(blk.Block.Body.Attestations).
WithFinalized(root == canonicalRoot && blk.Block.Slot <= a.forkchoiceStore.FinalizedSlot()).
WithVersion(blk.Version()), nil
}
func (a *ApiHandler) getBlockRoot(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) getBlockRoot(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
ctx := r.Context()
tx, err := a.indiciesDB.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
blockId, err := blockIdFromRequest(r)
blockId, err := beaconhttp.BlockIdFromRequest(r)
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
@ -195,5 +196,5 @@ func (a *ApiHandler) getBlockRoot(w http.ResponseWriter, r *http.Request) (*beac
}
return newBeaconResponse(struct {
Root libcommon.Hash `json:"root"`
}{Root: root}).withFinalized(canonicalRoot == root && *slot <= a.forkchoiceStore.FinalizedSlot()), nil
}{Root: root}).WithFinalized(canonicalRoot == root && *slot <= a.forkchoiceStore.FinalizedSlot()), nil
}

File diff suppressed because one or more lines are too long

View File

@ -10,7 +10,7 @@ import (
"github.com/ledgerwatch/erigon/cl/phase1/core/state"
)
func (a *ApiHandler) GetEth1V1BuilderStatesExpectedWit(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) GetEth1V1BuilderStatesExpectedWit(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
ctx := r.Context()
tx, err := a.indiciesDB.BeginRo(ctx)
@ -19,7 +19,7 @@ func (a *ApiHandler) GetEth1V1BuilderStatesExpectedWit(w http.ResponseWriter, r
}
defer tx.Rollback()
blockId, err := stateIdFromRequest(r)
blockId, err := beaconhttp.StateIdFromRequest(r)
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
@ -44,7 +44,7 @@ func (a *ApiHandler) GetEth1V1BuilderStatesExpectedWit(w http.ResponseWriter, r
if root == headRoot {
s, cn := a.syncedData.HeadState()
defer cn()
return newBeaconResponse(state.ExpectedWithdrawals(s)).withFinalized(false), nil
return newBeaconResponse(state.ExpectedWithdrawals(s)).WithFinalized(false), nil
}
lookAhead := 1024
for currSlot := *slot + 1; currSlot < *slot+uint64(lookAhead); currSlot++ {
@ -62,7 +62,7 @@ func (a *ApiHandler) GetEth1V1BuilderStatesExpectedWit(w http.ResponseWriter, r
if err != nil {
return nil, err
}
return newBeaconResponse(blk.Block.Body.ExecutionPayload.Withdrawals).withFinalized(false), nil
return newBeaconResponse(blk.Block.Body.ExecutionPayload.Withdrawals).WithFinalized(false), nil
}
return nil, beaconhttp.NewEndpointError(http.StatusNotFound, "state not found")

View File

@ -1,169 +0,0 @@
//go:build integration
package handler
import (
"io"
"math"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/ledgerwatch/erigon/cl/cltypes/solid"
"github.com/ledgerwatch/erigon/common"
"github.com/stretchr/testify/require"
)
func TestGetCommitteesAntiquated(t *testing.T) {
// setupTestingHandler(t, clparams.Phase0Version)
_, blocks, _, _, postState, handler, _, _, fcu := setupTestingHandler(t, clparams.Phase0Version)
postRoot, err := postState.HashSSZ()
require.NoError(t, err)
fcu.HeadVal, err = blocks[len(blocks)-1].Block.HashSSZ()
require.NoError(t, err)
fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot
fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(fcu.HeadVal, fcu.HeadSlotVal/32)
fcu.FinalizedSlotVal = math.MaxUint64
fcu.StateAtBlockRootVal[fcu.HeadVal] = postState
cases := []struct {
name string
blockID string
code int
query string
expected string
}{
{
name: "slot",
blockID: "0x" + common.Bytes2Hex(postRoot[:]),
code: http.StatusOK,
query: "?slot=" + strconv.FormatUint(fcu.HeadSlotVal, 10),
expected: `{"data":[{"index":"0","slot":"8322","validators":["0","104","491","501","379","318","275","504","75","280","105","399","35","401"]}],"finalized":true,"execution_optimistic":false}` + "\n",
},
{
name: "empty-index",
blockID: "0x" + common.Bytes2Hex(postRoot[:]),
code: http.StatusOK,
query: "?index=1",
expected: `{"data":[],"finalized":true,"execution_optimistic":false}` + "\n",
},
{
name: "all-queries",
blockID: "0x" + common.Bytes2Hex(postRoot[:]),
code: http.StatusOK,
query: "?index=0&slot=" + strconv.FormatUint(fcu.HeadSlotVal-32, 10) + "&epoch=" + strconv.FormatUint((fcu.HeadSlotVal/32)-1, 10),
expected: `{"data":[{"index":"0","slot":"8290","validators":["127","377","274","85","309","420","423","398","153","480","273","429","374","260"]}],"finalized":true,"execution_optimistic":false}` + "\n",
},
{
blockID: "0x" + common.Bytes2Hex(make([]byte, 32)),
code: http.StatusNotFound,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
server := httptest.NewServer(handler.mux)
defer server.Close()
// Query the block in the handler with /eth/v2/beacon/states/{block_id} with content-type octet-stream
req, err := http.NewRequest("GET", server.URL+"/eth/v1/beacon/states/"+c.blockID+"/committees"+c.query, nil)
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, c.code, resp.StatusCode)
if resp.StatusCode != http.StatusOK {
return
}
// read the all of the octect
out, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, c.expected, string(out))
})
}
}
func TestGetCommitteesNonAntiquated(t *testing.T) {
// setupTestingHandler(t, clparams.Phase0Version)
_, blocks, _, _, postState, handler, _, sm, fcu := setupTestingHandler(t, clparams.Phase0Version)
postRoot, err := postState.HashSSZ()
require.NoError(t, err)
fcu.HeadVal, err = blocks[len(blocks)-1].Block.HashSSZ()
require.NoError(t, err)
fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot
fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(fcu.HeadVal, fcu.HeadSlotVal/32)
fcu.FinalizedSlotVal = 0
fcu.StateAtBlockRootVal[fcu.HeadVal] = postState
require.NoError(t, sm.OnHeadState(postState))
cases := []struct {
name string
blockID string
code int
query string
expected string
}{
{
name: "slot",
blockID: "0x" + common.Bytes2Hex(postRoot[:]),
code: http.StatusOK,
query: "?slot=" + strconv.FormatUint(fcu.HeadSlotVal, 10),
expected: `{"data":[{"index":"0","slot":"8322","validators":["0","104","491","501","379","318","275","504","75","280","105","399","35","401"]}],"finalized":false,"execution_optimistic":false}` + "\n",
},
{
name: "empty-index",
blockID: "0x" + common.Bytes2Hex(postRoot[:]),
code: http.StatusOK,
query: "?index=1",
expected: `{"data":[],"finalized":false,"execution_optimistic":false}` + "\n",
},
{
name: "all-queries",
blockID: "0x" + common.Bytes2Hex(postRoot[:]),
code: http.StatusOK,
query: "?index=0&slot=" + strconv.FormatUint(fcu.HeadSlotVal-32, 10) + "&epoch=" + strconv.FormatUint((fcu.HeadSlotVal/32)-1, 10),
expected: `{"data":[{"index":"0","slot":"8290","validators":["127","377","274","85","309","420","423","398","153","480","273","429","374","260"]}],"finalized":false,"execution_optimistic":false}` + "\n",
},
{
blockID: "0x" + common.Bytes2Hex(make([]byte, 32)),
code: http.StatusNotFound,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
server := httptest.NewServer(handler.mux)
defer server.Close()
// Query the block in the handler with /eth/v2/beacon/states/{block_id} with content-type octet-stream
req, err := http.NewRequest("GET", server.URL+"/eth/v1/beacon/states/"+c.blockID+"/committees"+c.query, nil)
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, c.code, resp.StatusCode)
if resp.StatusCode != http.StatusOK {
return
}
// read the all of the octect
out, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, c.expected, string(out))
})
}
}

View File

@ -17,20 +17,20 @@ type committeeResponse struct {
Validators []string `json:"validators"` // do string directly but it is still a base10 number
}
func (a *ApiHandler) getCommittees(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) getCommittees(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
ctx := r.Context()
epochReq, err := uint64FromQueryParams(r, "epoch")
epochReq, err := beaconhttp.Uint64FromQueryParams(r, "epoch")
if err != nil {
return nil, err
}
index, err := uint64FromQueryParams(r, "index")
index, err := beaconhttp.Uint64FromQueryParams(r, "index")
if err != nil {
return nil, err
}
slotFilter, err := uint64FromQueryParams(r, "slot")
slotFilter, err := beaconhttp.Uint64FromQueryParams(r, "slot")
if err != nil {
return nil, err
}
@ -40,7 +40,7 @@ func (a *ApiHandler) getCommittees(w http.ResponseWriter, r *http.Request) (*bea
return nil, err
}
defer tx.Rollback()
blockId, err := stateIdFromRequest(r)
blockId, err := beaconhttp.StateIdFromRequest(r)
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
@ -100,7 +100,7 @@ func (a *ApiHandler) getCommittees(w http.ResponseWriter, r *http.Request) (*bea
resp = append(resp, data)
}
}
return newBeaconResponse(resp).withFinalized(isFinalized), nil
return newBeaconResponse(resp).WithFinalized(isFinalized), nil
}
// finality case
activeIdxs, err := state_accessors.ReadActiveIndicies(tx, epoch*a.beaconChainCfg.SlotsPerEpoch)
@ -143,5 +143,5 @@ func (a *ApiHandler) getCommittees(w http.ResponseWriter, r *http.Request) (*bea
resp = append(resp, data)
}
}
return newBeaconResponse(resp).withFinalized(isFinalized), nil
return newBeaconResponse(resp).WithFinalized(isFinalized), nil
}

View File

@ -6,14 +6,15 @@ import (
"sort"
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon/cl/beacon/beaconhttp"
"github.com/ledgerwatch/erigon/cl/cltypes"
)
func (a *ApiHandler) getSpec(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) getSpec(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
return newBeaconResponse(a.beaconChainCfg), nil
}
func (a *ApiHandler) getDepositContract(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) getDepositContract(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
return newBeaconResponse(struct {
ChainId uint64 `json:"chain_id,string"`
DepositContract string `json:"address"`
@ -21,7 +22,7 @@ func (a *ApiHandler) getDepositContract(w http.ResponseWriter, r *http.Request)
}
func (a *ApiHandler) getForkSchedule(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) getForkSchedule(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
response := []cltypes.Fork{}
// create first response (unordered and incomplete)
for currentVersion, epoch := range a.beaconChainCfg.ForkVersionSchedule {

View File

@ -1,83 +0,0 @@
//go:build integration
package handler
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/stretchr/testify/require"
)
func TestGetSpec(t *testing.T) {
// setupTestingHandler(t, clparams.Phase0Version)
_, _, _, _, _, handler, _, _, _ := setupTestingHandler(t, clparams.Phase0Version)
server := httptest.NewServer(handler.mux)
defer server.Close()
resp, err := http.Get(server.URL + "/eth/v1/config/spec")
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
out := make(map[string]interface{})
err = json.NewDecoder(resp.Body).Decode(&out)
require.NoError(t, err)
data := out["data"].(map[string]interface{})
require.Equal(t, data["SlotsPerEpoch"], float64(32))
require.Equal(t, data["SlotsPerHistoricalRoot"], float64(8192))
}
func TestGetForkSchedule(t *testing.T) {
// setupTestingHandler(t, clparams.Phase0Version)
_, _, _, _, _, handler, _, _, _ := setupTestingHandler(t, clparams.Phase0Version)
server := httptest.NewServer(handler.mux)
defer server.Close()
resp, err := http.Get(server.URL + "/eth/v1/config/fork_schedule")
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
out := make(map[string]interface{})
err = json.NewDecoder(resp.Body).Decode(&out)
require.NoError(t, err)
require.Greater(t, len(out["data"].([]interface{})), 2)
for _, v := range out["data"].([]interface{}) {
data := v.(map[string]interface{})
require.NotNil(t, data["current_version"])
require.NotNil(t, data["epoch"])
require.NotNil(t, data["previous_version"])
}
}
func TestGetDepositContract(t *testing.T) {
// setupTestingHandler(t, clparams.Phase0Version)
_, _, _, _, _, handler, _, _, _ := setupTestingHandler(t, clparams.Phase0Version)
server := httptest.NewServer(handler.mux)
defer server.Close()
resp, err := http.Get(server.URL + "/eth/v1/config/deposit_contract")
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
out := make(map[string]interface{})
err = json.NewDecoder(resp.Body).Decode(&out)
require.NoError(t, err)
data := out["data"].(map[string]interface{})
require.Equal(t, data["address"], "0x00000000219ab540356cBB839Cbe05303d7705Fa")
require.Equal(t, data["chain_id"], "1")
}

View File

@ -0,0 +1,98 @@
package handler_test
import (
"embed"
"math"
"os"
"strings"
"testing"
"github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon/cl/beacon/beacontest"
"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/ledgerwatch/erigon/cl/cltypes/solid"
"github.com/ledgerwatch/erigon/cl/phase1/forkchoice"
"github.com/ledgerwatch/log/v3"
"github.com/spf13/afero"
"github.com/stretchr/testify/require"
)
//go:embed test_data/*
var testData embed.FS
var TestDatae = beacontest.NewBasePathFs(afero.FromIOFS{FS: testData}, "test_data")
//go:embed harness/*
var testHarness embed.FS
var Harnesses = beacontest.NewBasePathFs(afero.FromIOFS{FS: testHarness}, "harness")
type harnessConfig struct {
t *testing.T
v clparams.StateVersion
finalized bool
forkmode int
}
func defaultHarnessOpts(c harnessConfig) []beacontest.HarnessOption {
logger := log.New()
for _, v := range os.Args {
if !strings.Contains(v, "test.v") || strings.Contains(v, "test.v=false") {
logger.SetHandler(log.DiscardHandler())
}
}
_, blocks, _, _, postState, handler, _, sm, fcu := setupTestingHandler(c.t, c.v, logger)
var err error
if c.forkmode == 0 {
fcu.HeadVal, err = blocks[len(blocks)-1].Block.HashSSZ()
require.NoError(c.t, err)
fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot
fcu.JustifiedCheckpointVal = solid.NewCheckpointFromParameters(fcu.HeadVal, fcu.HeadSlotVal/32)
if c.finalized {
fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(fcu.HeadVal, fcu.HeadSlotVal/32)
fcu.FinalizedSlotVal = math.MaxUint64
} else {
fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(fcu.HeadVal, fcu.HeadSlotVal/32)
fcu.FinalizedSlotVal = 0
fcu.StateAtBlockRootVal[fcu.HeadVal] = postState
require.NoError(c.t, sm.OnHeadState(postState))
}
}
if c.forkmode == 1 {
sm.OnHeadState(postState)
s, cancel := sm.HeadState()
s.SetSlot(789274827847783)
cancel()
fcu.HeadSlotVal = 128
fcu.HeadVal = common.Hash{1, 2, 3}
fcu.WeightsMock = []forkchoice.ForkNode{
{
BlockRoot: common.Hash{1, 2, 3},
ParentRoot: common.Hash{1, 2, 3},
Slot: 128,
Weight: 1,
},
{
BlockRoot: common.Hash{1, 2, 2, 4, 5, 3},
ParentRoot: common.Hash{1, 2, 5},
Slot: 128,
Weight: 2,
},
}
fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(common.Hash{1, 2, 3}, 1)
fcu.JustifiedCheckpointVal = solid.NewCheckpointFromParameters(common.Hash{1, 2, 3}, 2)
}
return []beacontest.HarnessOption{
beacontest.WithTesting(c.t),
beacontest.WithFilesystem("td", TestDatae),
beacontest.WithHandler("i", handler),
}
}

View File

@ -22,8 +22,8 @@ type attesterDutyResponse struct {
Slot uint64 `json:"slot,string"`
}
func (a *ApiHandler) getAttesterDuties(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
epoch, err := epochFromRequest(r)
func (a *ApiHandler) getAttesterDuties(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
epoch, err := beaconhttp.EpochFromRequest(r)
if err != nil {
return nil, err
}
@ -33,7 +33,7 @@ func (a *ApiHandler) getAttesterDuties(w http.ResponseWriter, r *http.Request) (
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, fmt.Errorf("could not decode request body: %w. request body is required", err).Error())
}
if len(idxsStr) == 0 {
return newBeaconResponse([]string{}).withOptimistic(false), nil
return newBeaconResponse([]string{}).WithOptimistic(false), nil
}
idxSet := map[int]struct{}{}
// convert the request to uint64
@ -100,7 +100,7 @@ func (a *ApiHandler) getAttesterDuties(w http.ResponseWriter, r *http.Request) (
}
}
}
return newBeaconResponse(resp).withOptimistic(false), nil
return newBeaconResponse(resp).WithOptimistic(false), nil
}
stageStateProgress, err := state_accessors.GetStateProcessingProgress(tx)
@ -159,5 +159,5 @@ func (a *ApiHandler) getAttesterDuties(w http.ResponseWriter, r *http.Request) (
}
}
}
return newBeaconResponse(resp).withOptimistic(false), nil
return newBeaconResponse(resp).WithOptimistic(false), nil
}

View File

@ -1,153 +0,0 @@
//go:build integration
package handler
import (
"bytes"
"io"
"math"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/ledgerwatch/erigon/cl/cltypes/solid"
"github.com/stretchr/testify/require"
)
func TestDutiesAttesterAntiquated(t *testing.T) {
// setupTestingHandler(t, clparams.Phase0Version)
_, blocks, _, _, postState, handler, _, _, fcu := setupTestingHandler(t, clparams.Phase0Version)
fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot
fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(fcu.HeadVal, fcu.HeadSlotVal/32)
fcu.FinalizedSlotVal = math.MaxUint64
fcu.StateAtBlockRootVal[fcu.HeadVal] = postState
cases := []struct {
name string
epoch string
code int
reqBody string
expected string
}{
{
name: "non-empty-indicies",
epoch: strconv.FormatUint(fcu.HeadSlotVal/32, 10),
code: http.StatusOK,
reqBody: `["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]`,
expected: `{"data":[{"pubkey":"0x97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb","validator_index":"0","committee_index":"0","committee_length":"14","validator_committee_index":"0","committees_at_slot":"1","slot":"8322"},{"pubkey":"0xb0e7791fb972fe014159aa33a98622da3cdc98ff707965e536d8636b5fcc5ac7a91a8c46e59a00dca575af0f18fb13dc","validator_index":"4","committee_index":"0","committee_length":"13","validator_committee_index":"5","committees_at_slot":"1","slot":"8327"},{"pubkey":"0xb928f3beb93519eecf0145da903b40a4c97dca00b21f12ac0df3be9116ef2ef27b2ae6bcd4c5bc2d54ef5a70627efcb7","validator_index":"6","committee_index":"0","committee_length":"13","validator_committee_index":"10","committees_at_slot":"1","slot":"8327"},{"pubkey":"0xa6e82f6da4520f85c5d27d8f329eccfa05944fd1096b20734c894966d12a9e2a9a9744529d7212d33883113a0cadb909","validator_index":"5","committee_index":"0","committee_length":"14","validator_committee_index":"10","committees_at_slot":"1","slot":"8329"},{"pubkey":"0x89ece308f9d1f0131765212deca99697b112d61f9be9a5f1f3780a51335b3ff981747a0b2ca2179b96d2c0c9024e5224","validator_index":"2","committee_index":"0","committee_length":"14","validator_committee_index":"11","committees_at_slot":"1","slot":"8331"},{"pubkey":"0xaf81da25ecf1c84b577fefbedd61077a81dc43b00304015b2b596ab67f00e41c86bb00ebd0f90d4b125eb0539891aeed","validator_index":"9","committee_index":"0","committee_length":"14","validator_committee_index":"8","committees_at_slot":"1","slot":"8342"},{"pubkey":"0xac9b60d5afcbd5663a8a44b7c5a02f19e9a77ab0a35bd65809bb5c67ec582c897feb04decc694b13e08587f3ff9b5b60","validator_index":"3","committee_index":"0","committee_length":"13","validator_committee_index":"6","committees_at_slot":"1","slot":"8348"}],"execution_optimistic":false}` + "\n",
},
{
name: "empty-index",
epoch: strconv.FormatUint(fcu.HeadSlotVal/32, 10),
code: http.StatusOK,
reqBody: `[]`,
expected: `{"data":[],"execution_optimistic":false}` + "\n",
},
{
name: "404",
reqBody: `["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]`,
epoch: `999999999`,
code: http.StatusBadRequest,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
server := httptest.NewServer(handler.mux)
defer server.Close()
//
body := bytes.Buffer{}
body.WriteString(c.reqBody)
// Query the block in the handler with /eth/v2/beacon/states/{block_id} with content-type octet-stream
req, err := http.NewRequest("POST", server.URL+"/eth/v1/validator/duties/attester/"+c.epoch, &body)
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, c.code, resp.StatusCode)
if resp.StatusCode != http.StatusOK {
return
}
// read the all of the octect
out, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, c.expected, string(out))
})
}
}
func TestDutiesAttesterNonAntiquated(t *testing.T) {
// setupTestingHandler(t, clparams.Phase0Version)
_, blocks, _, _, postState, handler, _, sm, fcu := setupTestingHandler(t, clparams.Phase0Version)
fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot
fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(fcu.HeadVal, fcu.HeadSlotVal/32)
fcu.FinalizedSlotVal = 0
fcu.StateAtBlockRootVal[fcu.HeadVal] = postState
require.NoError(t, sm.OnHeadState(postState))
cases := []struct {
name string
epoch string
code int
reqBody string
expected string
}{
{
name: "non-empty-indicies",
epoch: strconv.FormatUint(fcu.HeadSlotVal/32, 10),
code: http.StatusOK,
reqBody: `["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]`,
expected: `{"data":[{"pubkey":"0x97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb","validator_index":"0","committee_index":"0","committee_length":"14","validator_committee_index":"0","committees_at_slot":"1","slot":"8322"},{"pubkey":"0xb0e7791fb972fe014159aa33a98622da3cdc98ff707965e536d8636b5fcc5ac7a91a8c46e59a00dca575af0f18fb13dc","validator_index":"4","committee_index":"0","committee_length":"13","validator_committee_index":"5","committees_at_slot":"1","slot":"8327"},{"pubkey":"0xb928f3beb93519eecf0145da903b40a4c97dca00b21f12ac0df3be9116ef2ef27b2ae6bcd4c5bc2d54ef5a70627efcb7","validator_index":"6","committee_index":"0","committee_length":"13","validator_committee_index":"10","committees_at_slot":"1","slot":"8327"},{"pubkey":"0xa6e82f6da4520f85c5d27d8f329eccfa05944fd1096b20734c894966d12a9e2a9a9744529d7212d33883113a0cadb909","validator_index":"5","committee_index":"0","committee_length":"14","validator_committee_index":"10","committees_at_slot":"1","slot":"8329"},{"pubkey":"0x89ece308f9d1f0131765212deca99697b112d61f9be9a5f1f3780a51335b3ff981747a0b2ca2179b96d2c0c9024e5224","validator_index":"2","committee_index":"0","committee_length":"14","validator_committee_index":"11","committees_at_slot":"1","slot":"8331"},{"pubkey":"0xaf81da25ecf1c84b577fefbedd61077a81dc43b00304015b2b596ab67f00e41c86bb00ebd0f90d4b125eb0539891aeed","validator_index":"9","committee_index":"0","committee_length":"14","validator_committee_index":"8","committees_at_slot":"1","slot":"8342"},{"pubkey":"0xac9b60d5afcbd5663a8a44b7c5a02f19e9a77ab0a35bd65809bb5c67ec582c897feb04decc694b13e08587f3ff9b5b60","validator_index":"3","committee_index":"0","committee_length":"13","validator_committee_index":"6","committees_at_slot":"1","slot":"8348"}],"execution_optimistic":false}` + "\n",
},
{
name: "empty-index",
epoch: strconv.FormatUint(fcu.HeadSlotVal/32, 10),
code: http.StatusOK,
reqBody: `[]`,
expected: `{"data":[],"execution_optimistic":false}` + "\n",
},
{
name: "404",
reqBody: `["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]`,
epoch: `999999999`,
code: http.StatusBadRequest,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
server := httptest.NewServer(handler.mux)
defer server.Close()
//
body := bytes.Buffer{}
body.WriteString(c.reqBody)
// Query the block in the handler with /eth/v2/beacon/states/{block_id} with content-type octet-stream
req, err := http.NewRequest("POST", server.URL+"/eth/v1/validator/duties/attester/"+c.epoch, &body)
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, c.code, resp.StatusCode)
if resp.StatusCode != http.StatusOK {
return
}
// read the all of the octect
out, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, c.expected, string(out))
})
}
}

View File

@ -21,8 +21,8 @@ type proposerDuties struct {
Slot uint64 `json:"slot,string"`
}
func (a *ApiHandler) getDutiesProposer(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
epoch, err := epochFromRequest(r)
func (a *ApiHandler) getDutiesProposer(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
epoch, err := beaconhttp.EpochFromRequest(r)
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
@ -55,7 +55,7 @@ func (a *ApiHandler) getDutiesProposer(w http.ResponseWriter, r *http.Request) (
Slot: epoch*a.beaconChainCfg.SlotsPerEpoch + i,
}
}
return newBeaconResponse(duties).withFinalized(true).withVersion(a.beaconChainCfg.GetCurrentStateVersion(epoch)), nil
return newBeaconResponse(duties).WithFinalized(true).WithVersion(a.beaconChainCfg.GetCurrentStateVersion(epoch)), nil
}
// We need to compute our duties
@ -118,5 +118,5 @@ func (a *ApiHandler) getDutiesProposer(w http.ResponseWriter, r *http.Request) (
}
wg.Wait()
return newBeaconResponse(duties).withFinalized(false).withVersion(a.beaconChainCfg.GetCurrentStateVersion(epoch)), nil
return newBeaconResponse(duties).WithFinalized(false).WithVersion(a.beaconChainCfg.GetCurrentStateVersion(epoch)), nil
}

View File

@ -1,114 +0,0 @@
//go:build integration
package handler
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/ledgerwatch/erigon/cl/cltypes/solid"
"github.com/stretchr/testify/require"
)
func TestProposerDutiesProposerFcu(t *testing.T) {
// setupTestingHandler(t, clparams.Phase0Version)
_, blocks, _, postState, _, handler, _, syncedDataManager, fcu := setupTestingHandler(t, clparams.Phase0Version)
epoch := blocks[len(blocks)-1].Block.Slot / 32
require.NoError(t, syncedDataManager.OnHeadState(postState))
fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(common.Hash{}, epoch)
server := httptest.NewServer(handler.mux)
defer server.Close()
resp, err := http.Get(server.URL + "/eth/v1/validator/duties/proposer/" + strconv.FormatUint(epoch, 10))
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
out := make(map[string]interface{})
err = json.NewDecoder(resp.Body).Decode(&out)
require.NoError(t, err)
data := out["data"].([]interface{})
require.Equal(t, len(data), 32)
for _, v := range data {
d := v.(map[string]interface{})
require.NotNil(t, d["pubkey"])
require.NotNil(t, d["validator_index"])
require.NotNil(t, d["slot"])
}
}
func TestProposerDutiesProposerBadEpoch(t *testing.T) {
// setupTestingHandler(t, clparams.Phase0Version)
_, _, _, postState, _, handler, _, syncedDataManager, fcu := setupTestingHandler(t, clparams.Phase0Version)
require.NoError(t, syncedDataManager.OnHeadState(postState))
fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(common.Hash{}, 1)
server := httptest.NewServer(handler.mux)
defer server.Close()
resp, err := http.Get(server.URL + "/eth/v1/validator/duties/proposer/abc")
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
}
func TestProposerDutiesNotSynced(t *testing.T) {
_, _, _, _, _, handler, _, _, fcu := setupTestingHandler(t, clparams.Phase0Version)
fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(common.Hash{}, 1)
server := httptest.NewServer(handler.mux)
defer server.Close()
resp, err := http.Get(server.URL + "/eth/v1/validator/duties/proposer/1")
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
}
func TestProposerDutiesProposerFcuHistorical(t *testing.T) {
// setupTestingHandler(t, clparams.Phase0Version)
_, blocks, _, postState, _, handler, _, syncedDataManager, fcu := setupTestingHandler(t, clparams.Phase0Version)
epoch := blocks[len(blocks)-1].Block.Slot / 32
require.NoError(t, syncedDataManager.OnHeadState(postState))
fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(common.Hash{}, epoch)
server := httptest.NewServer(handler.mux)
defer server.Close()
resp, err := http.Get(server.URL + "/eth/v1/validator/duties/proposer/" + strconv.FormatUint(epoch-1, 10))
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
out := make(map[string]interface{})
err = json.NewDecoder(resp.Body).Decode(&out)
require.NoError(t, err)
data := out["data"].([]interface{})
require.Equal(t, len(data), 32)
for _, v := range data {
d := v.(map[string]interface{})
require.NotNil(t, d["pubkey"])
require.NotNil(t, d["validator_index"])
require.NotNil(t, d["slot"])
}
}

View File

@ -20,8 +20,8 @@ type syncDutyResponse struct {
ValidatorSyncCommitteeIndicies []string `json:"validator_sync_committee_indicies"`
}
func (a *ApiHandler) getSyncDuties(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
epoch, err := epochFromRequest(r)
func (a *ApiHandler) getSyncDuties(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
epoch, err := beaconhttp.EpochFromRequest(r)
if err != nil {
return nil, err
}
@ -34,7 +34,7 @@ func (a *ApiHandler) getSyncDuties(w http.ResponseWriter, r *http.Request) (*bea
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, fmt.Errorf("could not decode request body: %w. request body is required.", err).Error())
}
if len(idxsStr) == 0 {
return newBeaconResponse([]string{}).withOptimistic(false), nil
return newBeaconResponse([]string{}).WithOptimistic(false), nil
}
duplicates := map[int]struct{}{}
// convert the request to uint64
@ -142,5 +142,5 @@ func (a *ApiHandler) getSyncDuties(w http.ResponseWriter, r *http.Request) (*bea
return duties[i].ValidatorIndex < duties[j].ValidatorIndex
})
return newBeaconResponse(duties).withOptimistic(false), nil
return newBeaconResponse(duties).WithOptimistic(false), nil
}

View File

@ -1,87 +0,0 @@
//go:build integration
package handler
import (
"bytes"
"io"
"math"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/ledgerwatch/erigon/cl/cltypes/solid"
"github.com/stretchr/testify/require"
)
func TestDutiesSync(t *testing.T) {
// setupTestingHandler(t, clparams.Phase0Version)
_, blocks, _, _, postState, handler, _, _, fcu := setupTestingHandler(t, clparams.BellatrixVersion)
fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot
fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(fcu.HeadVal, fcu.HeadSlotVal/32)
fcu.FinalizedSlotVal = math.MaxUint64
fcu.StateAtBlockRootVal[fcu.HeadVal] = postState
cases := []struct {
name string
epoch string
code int
reqBody string
expected string
}{
{
name: "non-empty-indicies",
epoch: strconv.FormatUint(fcu.HeadSlotVal/32, 10),
code: http.StatusOK,
reqBody: `["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]`,
expected: `{"data":[{"pubkey":"0x97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb","validator_index":"0","validator_sync_committee_indicies":["30","286"]},{"pubkey":"0xa572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e","validator_index":"1","validator_sync_committee_indicies":["120","376"]},{"pubkey":"0x89ece308f9d1f0131765212deca99697b112d61f9be9a5f1f3780a51335b3ff981747a0b2ca2179b96d2c0c9024e5224","validator_index":"2","validator_sync_committee_indicies":["138","394"]},{"pubkey":"0xac9b60d5afcbd5663a8a44b7c5a02f19e9a77ab0a35bd65809bb5c67ec582c897feb04decc694b13e08587f3ff9b5b60","validator_index":"3","validator_sync_committee_indicies":["10","266"]},{"pubkey":"0xb0e7791fb972fe014159aa33a98622da3cdc98ff707965e536d8636b5fcc5ac7a91a8c46e59a00dca575af0f18fb13dc","validator_index":"4","validator_sync_committee_indicies":["114","370"]},{"pubkey":"0xa6e82f6da4520f85c5d27d8f329eccfa05944fd1096b20734c894966d12a9e2a9a9744529d7212d33883113a0cadb909","validator_index":"5","validator_sync_committee_indicies":["103","359"]},{"pubkey":"0xb928f3beb93519eecf0145da903b40a4c97dca00b21f12ac0df3be9116ef2ef27b2ae6bcd4c5bc2d54ef5a70627efcb7","validator_index":"6","validator_sync_committee_indicies":["163","419"]},{"pubkey":"0xa85ae765588126f5e860d019c0e26235f567a9c0c0b2d8ff30f3e8d436b1082596e5e7462d20f5be3764fd473e57f9cf","validator_index":"7","validator_sync_committee_indicies":["197","453"]},{"pubkey":"0x99cdf3807146e68e041314ca93e1fee0991224ec2a74beb2866816fd0826ce7b6263ee31e953a86d1b72cc2215a57793","validator_index":"8","validator_sync_committee_indicies":["175","431"]},{"pubkey":"0xaf81da25ecf1c84b577fefbedd61077a81dc43b00304015b2b596ab67f00e41c86bb00ebd0f90d4b125eb0539891aeed","validator_index":"9","validator_sync_committee_indicies":["53","309"]}],"execution_optimistic":false}` + "\n",
},
{
name: "empty-index",
epoch: strconv.FormatUint(fcu.HeadSlotVal/32, 10),
code: http.StatusOK,
reqBody: `[]`,
expected: `{"data":[],"execution_optimistic":false}` + "\n",
},
{
name: "404",
reqBody: `["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]`,
epoch: `999999999`,
code: http.StatusNotFound,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
server := httptest.NewServer(handler.mux)
defer server.Close()
//
body := bytes.Buffer{}
body.WriteString(c.reqBody)
// Query the block in the handler with /eth/v2/beacon/states/{block_id} with content-type octet-stream
req, err := http.NewRequest("POST", server.URL+"/eth/v1/validator/duties/sync/"+c.epoch, &body)
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, c.code, resp.StatusCode)
if resp.StatusCode != http.StatusOK {
return
}
// read the all of the octect
out, err := io.ReadAll(resp.Body)
require.NoError(t, err)
if string(out) != c.expected {
panic(string(out))
}
require.Equal(t, c.expected, string(out))
})
}
}

View File

@ -7,7 +7,7 @@ import (
"github.com/ledgerwatch/erigon/cl/beacon/beaconhttp"
)
func (a *ApiHandler) GetEthV2DebugBeaconHeads(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) GetEthV2DebugBeaconHeads(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
if a.syncedData.Syncing() {
return nil, beaconhttp.NewEndpointError(http.StatusServiceUnavailable, "beacon node is syncing")
}

View File

@ -1,80 +0,0 @@
package handler
import (
"io"
"net/http/httptest"
"testing"
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/ledgerwatch/erigon/cl/cltypes/solid"
"github.com/ledgerwatch/erigon/cl/phase1/forkchoice"
"github.com/stretchr/testify/require"
)
func TestGetHeads(t *testing.T) {
// find server
_, _, _, _, p, handler, _, sm, fcu := setupTestingHandler(t, clparams.Phase0Version)
sm.OnHeadState(p)
s, cancel := sm.HeadState()
s.SetSlot(789274827847783)
cancel()
fcu.HeadSlotVal = 128
fcu.HeadVal = libcommon.Hash{1, 2, 3}
server := httptest.NewServer(handler.mux)
defer server.Close()
// get heads
resp, err := server.Client().Get(server.URL + "/eth/v2/debug/beacon/heads")
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, 200, resp.StatusCode)
out, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, `{"data":[{"execution_optimistic":false,"root":"0x0102030000000000000000000000000000000000000000000000000000000000","slot":128}]}`+"\n", string(out))
}
func TestGetForkchoice(t *testing.T) {
// find server
_, _, _, _, p, handler, _, sm, fcu := setupTestingHandler(t, clparams.Phase0Version)
sm.OnHeadState(p)
s, cancel := sm.HeadState()
s.SetSlot(789274827847783)
cancel()
fcu.HeadSlotVal = 128
fcu.HeadVal = libcommon.Hash{1, 2, 3}
server := httptest.NewServer(handler.mux)
defer server.Close()
fcu.WeightsMock = []forkchoice.ForkNode{
{
BlockRoot: libcommon.Hash{1, 2, 3},
ParentRoot: libcommon.Hash{1, 2, 3},
Slot: 128,
Weight: 1,
},
{
BlockRoot: libcommon.Hash{1, 2, 2, 4, 5, 3},
ParentRoot: libcommon.Hash{1, 2, 5},
Slot: 128,
Weight: 2,
},
}
fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(libcommon.Hash{1, 2, 3}, 1)
fcu.JustifiedCheckpointVal = solid.NewCheckpointFromParameters(libcommon.Hash{1, 2, 3}, 2)
// get heads
resp, err := server.Client().Get(server.URL + "/eth/v1/debug/fork_choice")
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, 200, resp.StatusCode)
out, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, `{"finalized_checkpoint":{"epoch":"1","root":"0x0102030000000000000000000000000000000000000000000000000000000000"},"fork_choice_nodes":[{"slot":"128","block_root":"0x0102030000000000000000000000000000000000000000000000000000000000","parent_root":"0x0102030000000000000000000000000000000000000000000000000000000000","justified_epoch":"0","finalized_epoch":"0","weight":"1","validity":"","execution_block_hash":"0x0000000000000000000000000000000000000000000000000000000000000000"},{"slot":"128","block_root":"0x0102020405030000000000000000000000000000000000000000000000000000","parent_root":"0x0102050000000000000000000000000000000000000000000000000000000000","justified_epoch":"0","finalized_epoch":"0","weight":"2","validity":"","execution_block_hash":"0x0000000000000000000000000000000000000000000000000000000000000000"}],"justified_checkpoint":{"epoch":"2","root":"0x0102030000000000000000000000000000000000000000000000000000000000"}}`+"\n", string(out))
}

View File

@ -1,242 +1,9 @@
package handler
import (
"fmt"
"net/http"
"regexp"
"strconv"
"github.com/go-chi/chi/v5"
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon-lib/types/ssz"
"github.com/ledgerwatch/erigon/cl/beacon/beaconhttp"
"github.com/ledgerwatch/erigon/cl/clparams"
)
type apiError struct {
code int
err error
}
type beaconResponse struct {
Data any `json:"data,omitempty"`
Finalized *bool `json:"finalized,omitempty"`
Version *clparams.StateVersion `json:"version,omitempty"`
ExecutionOptimistic *bool `json:"execution_optimistic,omitempty"`
}
func (b *beaconResponse) EncodeSSZ(xs []byte) ([]byte, error) {
marshaler, ok := b.Data.(ssz.Marshaler)
if !ok {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, "This endpoint does not support SSZ response")
}
encoded, err := marshaler.EncodeSSZ(nil)
if err != nil {
return nil, err
}
return encoded, nil
}
func (b *beaconResponse) EncodingSizeSSZ() int {
marshaler, ok := b.Data.(ssz.Marshaler)
if !ok {
return 9
}
return marshaler.EncodingSizeSSZ()
}
func newBeaconResponse(data any) *beaconResponse {
return &beaconResponse{
Data: data,
}
}
func (r *beaconResponse) withFinalized(finalized bool) (out *beaconResponse) {
out = new(beaconResponse)
*out = *r
out.Finalized = new(bool)
out.ExecutionOptimistic = new(bool)
out.Finalized = &finalized
return out
}
func (r *beaconResponse) withOptimistic(optimistic bool) (out *beaconResponse) {
out = new(beaconResponse)
*out = *r
out.ExecutionOptimistic = new(bool)
out.ExecutionOptimistic = &optimistic
return out
}
func (r *beaconResponse) withVersion(version clparams.StateVersion) (out *beaconResponse) {
out = new(beaconResponse)
*out = *r
out.Version = new(clparams.StateVersion)
out.Version = &version
return out
}
type chainTag int
var (
Head chainTag = 0
Finalized chainTag = 1
Justified chainTag = 2
Genesis chainTag = 3
)
// Represent either state id or block id
type segmentID struct {
tag chainTag
slot *uint64
root *libcommon.Hash
}
func (c *segmentID) head() bool {
return c.tag == Head && c.slot == nil && c.root == nil
}
func (c *segmentID) finalized() bool {
return c.tag == Finalized
}
func (c *segmentID) justified() bool {
return c.tag == Justified
}
func (c *segmentID) genesis() bool {
return c.tag == Genesis
}
func (c *segmentID) getSlot() *uint64 {
return c.slot
}
func (c *segmentID) getRoot() *libcommon.Hash {
return c.root
}
func epochFromRequest(r *http.Request) (uint64, error) {
// Must only be a number
regex := regexp.MustCompile(`^\d+$`)
epoch := chi.URLParam(r, "epoch")
if !regex.MatchString(epoch) {
return 0, fmt.Errorf("invalid path variable: {epoch}")
}
epochMaybe, err := strconv.ParseUint(epoch, 10, 64)
if err != nil {
return 0, err
}
return epochMaybe, nil
}
func stringFromRequest(r *http.Request, name string) (string, error) {
str := chi.URLParam(r, name)
if str == "" {
return "", nil
}
return str, nil
}
func blockIdFromRequest(r *http.Request) (*segmentID, error) {
regex := regexp.MustCompile(`^(?:0x[0-9a-fA-F]{64}|head|finalized|genesis|\d+)$`)
blockId := chi.URLParam(r, "block_id")
if !regex.MatchString(blockId) {
return nil, fmt.Errorf("invalid path variable: {block_id}")
}
if blockId == "head" {
return &segmentID{tag: Head}, nil
}
if blockId == "finalized" {
return &segmentID{tag: Finalized}, nil
}
if blockId == "genesis" {
return &segmentID{tag: Genesis}, nil
}
slotMaybe, err := strconv.ParseUint(blockId, 10, 64)
if err == nil {
return &segmentID{slot: &slotMaybe}, nil
}
root := libcommon.HexToHash(blockId)
return &segmentID{
root: &root,
}, nil
}
func stateIdFromRequest(r *http.Request) (*segmentID, error) {
regex := regexp.MustCompile(`^(?:0x[0-9a-fA-F]{64}|head|finalized|genesis|justified|\d+)$`)
stateId := chi.URLParam(r, "state_id")
if !regex.MatchString(stateId) {
return nil, fmt.Errorf("invalid path variable: {state_id}")
}
if stateId == "head" {
return &segmentID{tag: Head}, nil
}
if stateId == "finalized" {
return &segmentID{tag: Finalized}, nil
}
if stateId == "genesis" {
return &segmentID{tag: Genesis}, nil
}
if stateId == "justified" {
return &segmentID{tag: Justified}, nil
}
slotMaybe, err := strconv.ParseUint(stateId, 10, 64)
if err == nil {
return &segmentID{slot: &slotMaybe}, nil
}
root := libcommon.HexToHash(stateId)
return &segmentID{
root: &root,
}, nil
}
func hashFromQueryParams(r *http.Request, name string) (*libcommon.Hash, error) {
hashStr := r.URL.Query().Get(name)
if hashStr == "" {
return nil, nil
}
// check if hashstr is an hex string
if len(hashStr) != 2+2*32 {
return nil, fmt.Errorf("invalid hash length")
}
if hashStr[:2] != "0x" {
return nil, fmt.Errorf("invalid hash prefix")
}
notHex, err := regexp.MatchString("[^0-9A-Fa-f]", hashStr[2:])
if err != nil {
return nil, err
}
if notHex {
return nil, fmt.Errorf("invalid hash characters")
}
hash := libcommon.HexToHash(hashStr)
return &hash, nil
}
// uint64FromQueryParams retrieves a number from the query params, in base 10.
func uint64FromQueryParams(r *http.Request, name string) (*uint64, error) {
str := r.URL.Query().Get(name)
if str == "" {
return nil, nil
}
num, err := strconv.ParseUint(str, 10, 64)
if err != nil {
return nil, err
}
return &num, nil
}
// decode a list of strings from the query params
func stringListFromQueryParams(r *http.Request, name string) ([]string, error) {
str := r.URL.Query().Get(name)
if str == "" {
return nil, nil
}
return regexp.MustCompile(`\s*,\s*`).Split(str, -1), nil
func newBeaconResponse(data any) *beaconhttp.BeaconResponse {
return beaconhttp.NewBeaconResponse(data)
}

View File

@ -15,7 +15,7 @@ type genesisResponse struct {
GenesisForkVersion libcommon.Bytes4 `json:"genesis_fork_version"`
}
func (a *ApiHandler) getGenesis(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) getGenesis(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
if a.genesisCfg == nil {
return nil, beaconhttp.NewEndpointError(http.StatusNotFound, "Genesis Config is missing")
}

View File

@ -1,35 +0,0 @@
package handler
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/stretchr/testify/require"
)
func TestGetGenesis(t *testing.T) {
// setupTestingHandler(t, clparams.Phase0Version)
_, _, _, _, _, handler, _, _, _ := setupTestingHandler(t, clparams.Phase0Version)
server := httptest.NewServer(handler.mux)
defer server.Close()
resp, err := http.Get(server.URL + "/eth/v1/beacon/genesis")
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
out := make(map[string]interface{})
err = json.NewDecoder(resp.Body).Decode(&out)
require.NoError(t, err)
data := out["data"].(map[string]interface{})
genesisTime := data["genesis_time"].(string)
require.Equal(t, genesisTime, "1606824023")
require.Equal(t, data["genesis_fork_version"], "0xbba4da96")
require.Equal(t, data["genesis_validators_root"], "0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95")
}

View File

@ -44,6 +44,11 @@ func NewApiHandler(genesisConfig *clparams.GenesisConfig, beaconChainConfig *clp
}}, sentinel: sentinel, version: version}
}
func (a *ApiHandler) Init() {
a.o.Do(func() {
a.init()
})
}
func (a *ApiHandler) init() {
r := chi.NewRouter()
a.mux = r

View File

@ -0,0 +1,31 @@
vars:
finalized_epoch: "99999999"
justified_slot: "160"
justified_epoch: "4"
tests:
## blocks
- name: all validators
expect:
file: "attestations_1"
fs: td
actual:
handler: i
method: post
path: /eth/v1/beacon/rewards/attestations/{{.Vars.justified_epoch}}
- name: two validators
expect:
file: "attestations_2"
fs: td
actual:
handler: i
method: post
path: /eth/v1/beacon/rewards/attestations/{{.Vars.justified_epoch}}
body:
data: ["1","4"]
- name: not found
actual:
handler: i
method: post
path: /eth/v1/beacon/rewards/attestations/{{.Vars.finalized_epoch}}
compare:
expr: "actual_code == 404"

View File

@ -0,0 +1,33 @@
vars:
head_hash: '0xeffdd8ef40c3c901f0724d48e04ce257967cf1da31929f3b6db614f89ef8d660'
bad_hash: '0xbeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef'
finalized_epoch: "99999999"
justified_slot: "8322"
justified_epoch: "259"
tests:
## blocks
- name: all validators
expect:
file: "attestations_3"
fs: td
actual:
handler: i
method: post
path: /eth/v1/beacon/rewards/attestations/{{.Vars.justified_epoch}}
- name: two validators
expect:
file: "attestations_4"
fs: td
actual:
handler: i
path: /eth/v1/beacon/rewards/attestations/{{.Vars.justified_epoch}}
method: post
body:
data: ["1","4"]
- name: not found
actual:
handler: i
method: post
path: /eth/v1/beacon/rewards/attestations/{{.Vars.finalized_epoch}}
compare:
expr: "actual_code == 404"

View File

@ -0,0 +1,100 @@
vars:
head_hash: '0xeffdd8ef40c3c901f0724d48e04ce257967cf1da31929f3b6db614f89ef8d660'
bad_hash: '0xbeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef'
tests:
## blocks
- name: by hash
expect:
file: "block_1"
fs: td
actual:
handler: i
path: /eth/v2/beacon/blocks/{{.Vars.head_hash}}
- name: by head
expect:
file: "block_1"
fs: td
compare:
exprs:
- actual_code == 200
- actual == expect
actual:
handler: i
path: /eth/v2/beacon/blocks/head
- name: not found
actual:
handler: i
path: /eth/v2/beacon/blocks/{{.Vars.bad_hash}}
compare:
expr: "actual_code == 404"
## blinded blocks
- name: blinded by hash
expect:
file: "blinded_block_1"
fs: td
actual:
handler: i
path: /eth/v1/beacon/blinded_blocks/{{.Vars.head_hash}}
- name: blinded by head
expect:
file: "blinded_block_1"
fs: td
actual:
handler: i
path: /eth/v1/beacon/blinded_blocks/head
- name: blinded not found
actual:
handler: i
path: /eth/v1/beacon/blinded_blocks/{{.Vars.bad_hash}}
compare:
expr: "actual_code == 404"
### attestations
- name: attestations by hash
expect:
file: "block_1"
fs: td
actual:
handler: i
path: /eth/v1/beacon/blocks/{{.Vars.head_hash}}/attestations
compare:
expr: "size(actual.data) == size(expect.data.message.body.attestations)"
- name: attestions by head
expect:
file: "block_1"
fs: td
actual:
handler: i
path: /eth/v1/beacon/blocks/head/attestations
compare:
exprs:
- actual_code == 200
- size(actual.data) == size(expect.data.message.body.attestations)
- name: attestions not found
actual:
handler: i
path: /eth/v1/beacon/blocks/{{.Vars.bad_hash}}/attestations
compare:
expr: "actual_code == 404"
### root
- name: root by hash
actual:
handler: i
path: /eth/v1/beacon/blocks/{{.Vars.head_hash}}/root
compare:
exprs:
- actual_code == 200
- actual.data.root == "{{.Vars.head_hash}}"
- name: root by head
actual:
handler: i
path: /eth/v1/beacon/blocks/head/root
compare:
exprs:
- actual_code == 200
- actual.data.root == "{{.Vars.head_hash}}"
- name: root not found
actual:
handler: i
path: /eth/v1/beacon/blocks/19912929/root
compare:
expr: "actual_code == 404"

View File

@ -0,0 +1,55 @@
vars:
head_hash: '0xeffdd8ef40c3c901f0724d48e04ce257967cf1da31929f3b6db614f89ef8d660'
bad_hash: '0xbeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef'
post_root: '0x933d6650f2999f17012e781f5012981edb549e5935de1c981fce81cdd241d4e1'
head_slot: 8322
head_epoch: "260"
tests:
- name: slot non antiquated
expect:
file: "committees_1"
fs: td
actual:
handler: i
path: /eth/v1/beacon/states/{{.Vars.post_root}}/committees
query:
slot: "8322"
compare:
exprs:
- "actual_code == 200"
- "expect[3] == actual"
- name: empty index non antiquated
expect:
file: "committees_1"
fs: td
actual:
handler: i
path: /eth/v1/beacon/states/{{.Vars.post_root}}/committees
query:
index: "1"
compare:
exprs:
- "actual_code == 200"
- "expect[4] == actual"
- name: all queries non antiquated
expect:
file: "committees_1"
fs: td
actual:
handler: i
path: /eth/v1/beacon/states/{{.Vars.post_root}}/committees
query:
index: "0"
slot: "{{sub .Vars.head_slot 32}}"
epoch: "{{sub .Vars.head_epoch 1}}"
compare:
exprs:
- "actual_code == 200"
- "expect[5] == actual"
- name: 404 non antiquated
actual:
handler: i
path: /eth/v1/beacon/states/{{.Vars.bad_hash}}/committees
compare:
exprs:
- "actual_code == 404"

View File

@ -0,0 +1,55 @@
vars:
head_hash: '0xeffdd8ef40c3c901f0724d48e04ce257967cf1da31929f3b6db614f89ef8d660'
bad_hash: '0xbeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef'
post_root: '0x933d6650f2999f17012e781f5012981edb549e5935de1c981fce81cdd241d4e1'
head_slot: 8322
head_epoch: "260"
tests:
- name: slot
expect:
file: "committees_1"
fs: td
actual:
handler: i
path: /eth/v1/beacon/states/{{.Vars.post_root}}/committees
query:
slot: "8322"
compare:
exprs:
- "actual_code == 200"
- "expect[0] == actual"
- name: empty index
expect:
file: "committees_1"
fs: td
actual:
handler: i
path: /eth/v1/beacon/states/{{.Vars.post_root}}/committees
query:
index: "1"
compare:
exprs:
- "actual_code == 200"
- "expect[1] == actual"
- name: all queries
expect:
file: "committees_1"
fs: td
actual:
handler: i
path: /eth/v1/beacon/states/{{.Vars.post_root}}/committees
query:
index: "0"
slot: "{{sub .Vars.head_slot 32}}"
epoch: "{{sub .Vars.head_epoch 1}}"
compare:
exprs:
- "actual_code == 200"
- "expect[2] == actual"
- name: "404"
actual:
handler: i
path: /eth/v1/beacon/states/{{.Vars.bad_hash}}/committees
compare:
exprs:
- "actual_code == 404"

View File

@ -0,0 +1,32 @@
tests:
- name: spec
actual:
handler: i
path: /eth/v1/config/spec
compare:
exprs:
- actual_code == 200
- actual.data.SlotsPerEpoch == 32
- actual.data.SlotsPerHistoricalRoot == 8192
- name: fork schedule
actual:
handler: i
path: /eth/v1/config/fork_schedule
compare:
exprs:
- actual_code == 200
- has(actual.data[0].current_version)
- has(actual.data[0].previous_version)
- has(actual.data[0].epoch)
- has(actual.data[1].current_version)
- has(actual.data[1].previous_version)
- has(actual.data[1].epoch)
- name: deposit contract
actual:
handler: i
path: /eth/v1/config/deposit_contract
compare:
exprs:
- actual_code == 200
- actual.data.address == "0x00000000219ab540356cBB839Cbe05303d7705Fa"
- actual.data.chain_id == "1"

View File

@ -0,0 +1,45 @@
vars:
head_hash: '0xeffdd8ef40c3c901f0724d48e04ce257967cf1da31929f3b6db614f89ef8d660'
bad_hash: '0xbeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef'
post_root: '0x933d6650f2999f17012e781f5012981edb549e5935de1c981fce81cdd241d4e1'
head_slot: 8322
head_epoch: "260"
tests:
- name: non empty indices
expect:
file: "duties_1"
fs: td
actual:
handler: i
path: /eth/v1/validator/duties/attester/{{.Vars.head_epoch}}
method: post
body:
data: ["0","1","2","3","4","5","6","7","8","9"]
compare:
exprs:
- "actual_code == 200"
- "expect[0] == actual"
- name: empty index
expect:
file: "duties_1"
fs: td
actual:
handler: i
path: /eth/v1/validator/duties/attester/{{.Vars.head_epoch}}
method: post
body:
data: []
compare:
exprs:
- "actual_code == 200"
- "expect[1] == actual"
- name: 400 non antiquated
actual:
handler: i
path: /eth/v1/validator/duties/attester/999999999
method: post
body:
data: ["0","1","2","3","4","5","6","7","8","9"]
compare:
exprs:
- "actual_code == 400"

View File

@ -0,0 +1,45 @@
vars:
head_hash: '0xeffdd8ef40c3c901f0724d48e04ce257967cf1da31929f3b6db614f89ef8d660'
bad_hash: '0xbeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef'
post_root: '0x933d6650f2999f17012e781f5012981edb549e5935de1c981fce81cdd241d4e1'
head_slot: 8322
head_epoch: "260"
tests:
- name: non empty indices
expect:
file: "duties_1"
fs: td
actual:
handler: i
path: /eth/v1/validator/duties/attester/{{.Vars.head_epoch}}
method: post
body:
data: ["0","1","2","3","4","5","6","7","8","9"]
compare:
exprs:
- "actual_code == 200"
- "expect[0] == actual"
- name: empty index
expect:
file: "duties_1"
fs: td
actual:
handler: i
path: /eth/v1/validator/duties/attester/{{.Vars.head_epoch}}
method: post
body:
data: []
compare:
exprs:
- "actual_code == 200"
- "expect[1] == actual"
- name: 400 non antiquated
actual:
handler: i
path: /eth/v1/validator/duties/attester/999999999
method: post
body:
data: ["0","1","2","3","4","5","6","7","8","9"]
compare:
exprs:
- "actual_code == 400"

View File

@ -0,0 +1,42 @@
vars:
head_hash: '0xeffdd8ef40c3c901f0724d48e04ce257967cf1da31929f3b6db614f89ef8d660'
bad_hash: '0xbeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef'
post_root: '0x933d6650f2999f17012e781f5012981edb549e5935de1c981fce81cdd241d4e1'
head_slot: 8322
head_epoch: "260"
tests:
- name: proposer duties
actual:
handler: i
path: /eth/v1/validator/duties/proposer/{{.Vars.head_epoch}}
compare:
exprs:
- actual_code == 200
- size(actual.data) == 32
- has(actual.data[0].pubkey)
- has(actual.data[0].validator_index)
- has(actual.data[0].slot)
- name: proposer bad epoch
actual:
handler: i
path: /eth/v1/validator/duties/proposer/abc
compare:
expr: "actual_code == 400"
- name: proposer duties not synced
actual:
handler: i
path: /eth/v1/validator/duties/proposer/1
compare:
expr: "actual_code == 503"
- name: fcu historical
actual:
handler: i
path: /eth/v1/validator/duties/proposer/{{sub .Vars.head_epoch 1}}
compare:
exprs:
- actual_code == 200
- size(actual.data) == 32
- has(actual.data[0].pubkey)
- has(actual.data[0].validator_index)
- has(actual.data[0].slot)

View File

@ -0,0 +1,43 @@
vars:
finalized_epoch: 99999999
head_slot: 160
head_epoch: 4
tests:
- name: non empty indices
expect:
file: "duties_sync_1"
fs: td
actual:
handler: i
path: /eth/v1/validator/duties/sync/{{.Vars.head_epoch}}
method: post
body:
data: ["0","1","2","3","4","5","6","7","8","9"]
compare:
exprs:
- "actual_code == 200"
- "expect[0] == actual"
- name: empty index
expect:
file: "duties_sync_1"
fs: td
actual:
handler: i
path: /eth/v1/validator/duties/sync/{{.Vars.head_epoch}}
method: post
body:
data: []
compare:
exprs:
- "actual_code == 200"
- "expect[1] == actual"
- name: "404 giant epoch"
actual:
handler: i
path: /eth/v1/validator/duties/sync/999999999
method: post
body:
data: ["0","1","2","3","4","5","6","7","8","9"]
compare:
exprs:
- "actual_code == 404"

View File

@ -0,0 +1,24 @@
vars:
tests:
- name: get fork choice
expect:
file: "forkchoice_1"
fs: td
actual:
handler: i
path: /eth/v2/debug/beacon/heads
compare:
exprs:
- "actual_code == 200"
- "actual == expect[0]"
- name: get heads
expect:
file: "forkchoice_1"
fs: td
actual:
handler: i
path: /eth/v1/debug/fork_choice
compare:
exprs:
- "actual_code == 200"
- "actual == expect[1]"

View File

@ -0,0 +1,38 @@
vars:
head_hash: '0xeffdd8ef40c3c901f0724d48e04ce257967cf1da31929f3b6db614f89ef8d660'
post_root: '0x933d6650f2999f17012e781f5012981edb549e5935de1c981fce81cdd241d4e1'
bad_hash: '0xbeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef'
head_slot: 8322
first_slot: 8288
head_epoch: "260"
body_root_1: "0x8d07005613673b3684b527f9c4dab5191403177e79b0e0bc1d58f15021abab19"
body_root_2: "0xa6957819a5055b6d760c1b2ec034522cc033a7dd94c743ed936d8f8d0eb5ccce"
block_1_hash: "0x86979f6f6dc7626064ef0d38d4dffb89e91d1d4c18492e3fb7d7ee93cedca3ed"
tests:
- name: not head
actual:
handler: i
path: /eth/v1/beacon/headers/{{.Vars.block_1_hash}}
compare:
exprs:
- actual_code == 200
- actual.data.canonical == true
- actual.data.header.message.body_root == "{{.Vars.body_root_1}}"
- actual.data.header.message.slot == "{{.Vars.first_slot}}"
- name: head
actual:
handler: i
path: /eth/v1/beacon/headers/head
compare:
exprs:
- actual_code == 200
- actual.data.canonical == true
- actual.data.header.message.body_root == "{{.Vars.body_root_2}}"
- actual.data.header.message.slot == "{{.Vars.head_slot}}"
- name: not head
actual:
handler: i
path: /eth/v1/beacon/headers/{{.Vars.bad_hash}}
compare:
exprs:
- actual_code == 404

View File

@ -0,0 +1,13 @@
tests:
- name: spec
actual:
handler: i
path: /eth/v1/validator/liveness/260
method: post
body:
data: ["0","1","2","3","4","5","6","7","8","9","10"]
compare:
exprs:
- "actual_code==200"
- "size(actual.data) == 11"
- "actual.data.all(x,has(x.is_live) && has(x.index))"

View File

@ -0,0 +1,53 @@
package handler_test
import (
"testing"
"github.com/ledgerwatch/erigon/cl/beacon/beacontest"
"github.com/ledgerwatch/erigon/cl/clparams"
_ "embed"
)
func TestHarnessPhase0(t *testing.T) {
beacontest.Execute(
append(
defaultHarnessOpts(harnessConfig{t: t, v: clparams.Phase0Version}),
beacontest.WithTestFromFs(Harnesses, "blocks"),
beacontest.WithTestFromFs(Harnesses, "config"),
beacontest.WithTestFromFs(Harnesses, "headers"),
beacontest.WithTestFromFs(Harnesses, "attestation_rewards_phase0"),
beacontest.WithTestFromFs(Harnesses, "committees"),
beacontest.WithTestFromFs(Harnesses, "duties_attester"),
beacontest.WithTestFromFs(Harnesses, "duties_proposer"),
)...,
)
}
func TestHarnessPhase0Finalized(t *testing.T) {
beacontest.Execute(
append(
defaultHarnessOpts(harnessConfig{t: t, v: clparams.Phase0Version, finalized: true}),
beacontest.WithTestFromFs(Harnesses, "liveness"),
beacontest.WithTestFromFs(Harnesses, "duties_attester_f"),
beacontest.WithTestFromFs(Harnesses, "committees_f"),
)...,
)
}
func TestHarnessBellatrix(t *testing.T) {
beacontest.Execute(
append(
defaultHarnessOpts(harnessConfig{t: t, v: clparams.BellatrixVersion, finalized: true}),
beacontest.WithTestFromFs(Harnesses, "attestation_rewards_bellatrix"),
beacontest.WithTestFromFs(Harnesses, "duties_sync_bellatrix"),
)...,
)
}
func TestHarnessForkChoice(t *testing.T) {
beacontest.Execute(
append(
defaultHarnessOpts(harnessConfig{t: t, v: clparams.BellatrixVersion, forkmode: 1}),
beacontest.WithTestFromFs(Harnesses, "fork_choice"),
)...,
)
}

View File

@ -9,14 +9,14 @@ import (
"github.com/ledgerwatch/erigon/cl/persistence/beacon_indicies"
)
func (a *ApiHandler) getHeaders(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) getHeaders(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
ctx := r.Context()
querySlot, err := uint64FromQueryParams(r, "slot")
querySlot, err := beaconhttp.Uint64FromQueryParams(r, "slot")
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
queryParentHash, err := hashFromQueryParams(r, "parent_root")
queryParentHash, err := beaconhttp.HashFromQueryParams(r, "parent_root")
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
@ -89,14 +89,14 @@ func (a *ApiHandler) getHeaders(w http.ResponseWriter, r *http.Request) (*beacon
return newBeaconResponse(headers), nil
}
func (a *ApiHandler) getHeader(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) getHeader(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
ctx := r.Context()
tx, err := a.indiciesDB.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
blockId, err := blockIdFromRequest(r)
blockId, err := beaconhttp.BlockIdFromRequest(r)
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
@ -125,5 +125,5 @@ func (a *ApiHandler) getHeader(w http.ResponseWriter, r *http.Request) (*beaconR
Root: root,
Canonical: canonicalRoot == root,
Header: signedHeader,
}).withFinalized(canonicalRoot == root && signedHeader.Header.Slot <= a.forkchoiceStore.FinalizedSlot()).withVersion(version), nil
}).WithFinalized(canonicalRoot == root && signedHeader.Header.Slot <= a.forkchoiceStore.FinalizedSlot()).WithVersion(version), nil
}

View File

@ -1,180 +0,0 @@
//go:build integration
package handler
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strconv"
"testing"
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/ledgerwatch/erigon/common"
"github.com/stretchr/testify/require"
)
func TestGetHeader(t *testing.T) {
// setupTestingHandler(t, clparams.Phase0Version)
_, blocks, _, _, _, handler, _, _, fcu := setupTestingHandler(t, clparams.Phase0Version)
// Start by testing
rootBlock1, err := blocks[0].Block.HashSSZ()
if err != nil {
t.Fatal(err)
}
fcu.HeadVal, err = blocks[len(blocks)-1].Block.HashSSZ()
require.NoError(t, err)
fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot
bodyRoot1, err := blocks[0].Block.Body.HashSSZ()
require.NoError(t, err)
bodyRoot2, err := blocks[len(blocks)-1].Block.Body.HashSSZ()
require.NoError(t, err)
cases := []struct {
blockID string
code int
slot uint64
bodyRoot string
}{
{
blockID: "0x" + common.Bytes2Hex(rootBlock1[:]),
code: http.StatusOK,
slot: blocks[0].Block.Slot,
bodyRoot: "0x" + common.Bytes2Hex(bodyRoot1[:]),
},
{
blockID: "head",
code: http.StatusOK,
slot: blocks[len(blocks)-1].Block.Slot,
bodyRoot: "0x" + common.Bytes2Hex(bodyRoot2[:]),
},
{
blockID: "0x" + common.Bytes2Hex(make([]byte, 32)),
code: http.StatusNotFound,
},
}
for _, c := range cases {
t.Run(c.blockID, func(t *testing.T) {
server := httptest.NewServer(handler.mux)
defer server.Close()
// Query the block in the handler with /eth/v2/beacon/blocks/{block_id}
resp, err := http.Get(server.URL + "/eth/v1/beacon/headers/" + c.blockID)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, c.code, resp.StatusCode)
if resp.StatusCode != http.StatusOK {
return
}
jsonVal := make(map[string]interface{})
// unmarshal the json
require.NoError(t, json.NewDecoder(resp.Body).Decode(&jsonVal))
data := jsonVal["data"].(map[string]interface{})
header := data["header"].(map[string]interface{})
message := header["message"].(map[string]interface{})
// compare the block
require.Equal(t, message["slot"], strconv.FormatInt(int64(c.slot), 10))
require.Equal(t, message["body_root"], c.bodyRoot)
require.Equal(t, data["canonical"], true)
})
}
}
func TestGetHeaders(t *testing.T) {
// setupTestingHandler(t, clparams.Phase0Version)
_, blocks, _, _, _, handler, _, _, fcu := setupTestingHandler(t, clparams.Phase0Version)
var err error
fcu.HeadVal, err = blocks[len(blocks)-1].Block.HashSSZ()
require.NoError(t, err)
fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot
bodyRoot1, err := blocks[0].Block.Body.HashSSZ()
require.NoError(t, err)
bodyRoot2, err := blocks[len(blocks)-1].Block.Body.HashSSZ()
require.NoError(t, err)
cases := []struct {
name string
code int
slotReq *uint64
parentRoot *libcommon.Hash
slot uint64
bodyRoot string
count int
}{
{
count: 1,
name: "slot",
code: http.StatusOK,
slotReq: &blocks[0].Block.Slot,
slot: blocks[0].Block.Slot,
bodyRoot: "0x" + common.Bytes2Hex(bodyRoot1[:]),
},
{
count: 0,
name: "none",
code: http.StatusOK,
slot: blocks[len(blocks)-1].Block.Slot,
bodyRoot: "0x" + common.Bytes2Hex(bodyRoot2[:]),
},
{
count: 0,
name: "parent",
code: http.StatusOK,
slotReq: &blocks[0].Block.Slot,
slot: blocks[0].Block.Slot,
parentRoot: &blocks[0].Block.ParentRoot,
bodyRoot: "0x" + common.Bytes2Hex(bodyRoot1[:]),
},
{
count: 0,
name: "wtf",
code: http.StatusOK,
slotReq: new(uint64),
slot: blocks[0].Block.Slot,
parentRoot: &blocks[0].Block.ParentRoot,
bodyRoot: "0x" + common.Bytes2Hex(bodyRoot1[:]),
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
server := httptest.NewServer(handler.mux)
defer server.Close()
url := server.URL + "/eth/v1/beacon/headers?lol=0" // lol is a random query param
if c.slotReq != nil {
url += "&slot=" + strconv.FormatInt(int64(*c.slotReq), 10)
}
if c.parentRoot != nil {
url += "&parent_root=" + "0x" + common.Bytes2Hex(c.parentRoot[:])
}
// Query the block in the handler with /eth/v2/beacon/blocks/{block_id}
resp, err := http.Get(url)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, c.code, resp.StatusCode)
if resp.StatusCode != http.StatusOK {
return
}
jsonVal := make(map[string]interface{})
// unmarshal the json
require.NoError(t, json.NewDecoder(resp.Body).Decode(&jsonVal))
data := jsonVal["data"].([]interface{})
require.Equal(t, len(data), c.count)
})
}
}

View File

@ -20,8 +20,8 @@ type live struct {
IsLive bool `json:"is_live"`
}
func (a *ApiHandler) liveness(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
epoch, err := epochFromRequest(r)
func (a *ApiHandler) liveness(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
epoch, err := beaconhttp.EpochFromRequest(r)
if err != nil {
return nil, err
}

View File

@ -1,62 +0,0 @@
package handler
import (
"bytes"
"encoding/json"
"math"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/ledgerwatch/erigon/cl/cltypes/solid"
"github.com/stretchr/testify/require"
)
func TestLiveness(t *testing.T) {
// i just want the correct schema to be generated
_, blocks, _, _, _, handler, _, _, fcu := setupTestingHandler(t, clparams.Phase0Version)
fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot
fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(fcu.HeadVal, fcu.HeadSlotVal/32)
fcu.FinalizedSlotVal = math.MaxUint64
reqBody := `["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]`
server := httptest.NewServer(handler.mux)
defer server.Close()
//
body := bytes.Buffer{}
body.WriteString(reqBody)
// Query the block in the handler with /eth/v2/beacon/states/{block_id} with content-type octet-stream
req, err := http.NewRequest("POST", server.URL+"/eth/v1/validator/liveness/"+strconv.FormatUint(fcu.HeadSlotVal/32, 10), &body)
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
out := map[string]interface{}{}
require.NoError(t, json.NewDecoder(resp.Body).Decode(&out))
data := out["data"].([]interface{})
require.Equal(t, 11, len(data))
// check that is has is_live (bool) and index (stringifed int)
for _, d := range data {
d := d.(map[string]interface{})
require.Equal(t, 2, len(d))
isLive, ok := d["is_live"]
require.True(t, ok)
_, ok = isLive.(bool)
require.True(t, ok)
i1, ok := d["index"]
require.True(t, ok)
strIndex, ok := i1.(string)
require.True(t, ok)
_, err := strconv.ParseUint(strIndex, 10, 64)
require.NoError(t, err)
}
}

View File

@ -5,10 +5,12 @@ import (
"fmt"
"net/http"
"runtime"
"github.com/ledgerwatch/erigon/cl/beacon/beaconhttp"
)
func (a *ApiHandler) GetEthV1NodeHealth(w http.ResponseWriter, r *http.Request) {
syncingStatus, err := uint64FromQueryParams(r, "syncing_status")
syncingStatus, err := beaconhttp.Uint64FromQueryParams(r, "syncing_status")
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return

View File

@ -1,71 +0,0 @@
package handler
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/stretchr/testify/require"
)
func TestNodeHealthSyncing(t *testing.T) {
// i just want the correct schema to be generated
_, _, _, _, _, handler, _, _, _ := setupTestingHandler(t, clparams.Phase0Version)
// Call GET /eth/v1/node/health
server := httptest.NewServer(handler.mux)
defer server.Close()
req, err := http.NewRequest("GET", server.URL+"/eth/v1/node/health?syncing_status=666", nil)
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, 666, resp.StatusCode)
}
func TestNodeHealthSyncingTip(t *testing.T) {
// i just want the correct schema to be generated
_, _, _, _, post, handler, _, sm, _ := setupTestingHandler(t, clparams.Phase0Version)
// Call GET /eth/v1/node/health
server := httptest.NewServer(handler.mux)
defer server.Close()
req, err := http.NewRequest("GET", server.URL+"/eth/v1/node/health?syncing_status=666", nil)
require.NoError(t, err)
require.NoError(t, sm.OnHeadState(post))
s, cancel := sm.HeadState()
s.SetSlot(999999999999999)
cancel()
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, 200, resp.StatusCode)
}
func TestNodeVersion(t *testing.T) {
// i just want the correct schema to be generated
_, _, _, _, _, handler, _, _, _ := setupTestingHandler(t, clparams.Phase0Version)
// Call GET /eth/v1/node/health
server := httptest.NewServer(handler.mux)
defer server.Close()
req, err := http.NewRequest("GET", server.URL+"/eth/v1/node/version", nil)
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
out := map[string]interface{}{}
require.NoError(t, json.NewDecoder(resp.Body).Decode(&out))
v := out["data"].(map[string]interface{})["version"].(string)
require.True(t, strings.Contains(v, "Caplin"))
}

View File

@ -11,29 +11,29 @@ import (
"github.com/ledgerwatch/erigon/cl/gossip"
)
func (a *ApiHandler) GetEthV1BeaconPoolVoluntaryExits(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) GetEthV1BeaconPoolVoluntaryExits(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
return newBeaconResponse(a.operationsPool.VoluntaryExistsPool.Raw()), nil
}
func (a *ApiHandler) GetEthV1BeaconPoolAttesterSlashings(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) GetEthV1BeaconPoolAttesterSlashings(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
fmt.Println("GetEthV1BeaconPoolAttesterSlashings", a.operationsPool.AttesterSlashingsPool.Raw())
return newBeaconResponse(a.operationsPool.AttesterSlashingsPool.Raw()), nil
}
func (a *ApiHandler) GetEthV1BeaconPoolProposerSlashings(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) GetEthV1BeaconPoolProposerSlashings(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
return newBeaconResponse(a.operationsPool.ProposerSlashingsPool.Raw()), nil
}
func (a *ApiHandler) GetEthV1BeaconPoolBLSExecutionChanges(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) GetEthV1BeaconPoolBLSExecutionChanges(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
return newBeaconResponse(a.operationsPool.BLSToExecutionChangesPool.Raw()), nil
}
func (a *ApiHandler) GetEthV1BeaconPoolAttestations(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
slot, err := uint64FromQueryParams(r, "slot")
func (a *ApiHandler) GetEthV1BeaconPoolAttestations(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
slot, err := beaconhttp.Uint64FromQueryParams(r, "slot")
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
committeeIndex, err := uint64FromQueryParams(r, "committee_index")
committeeIndex, err := beaconhttp.Uint64FromQueryParams(r, "committee_index")
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}

View File

@ -1,248 +0,0 @@
//go:build integration
package handler
import (
"bytes"
"encoding/json"
"net/http/httptest"
"testing"
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/ledgerwatch/erigon/cl/cltypes"
"github.com/ledgerwatch/erigon/cl/cltypes/solid"
"github.com/stretchr/testify/require"
)
func TestPoolAttesterSlashings(t *testing.T) {
attesterSlashing := &cltypes.AttesterSlashing{
Attestation_1: &cltypes.IndexedAttestation{
AttestingIndices: solid.NewRawUint64List(2048, []uint64{2, 3, 4, 5, 6}),
Data: solid.NewAttestationData(),
},
Attestation_2: &cltypes.IndexedAttestation{
AttestingIndices: solid.NewRawUint64List(2048, []uint64{2, 3, 4, 1, 6}),
Data: solid.NewAttestationData(),
},
}
// find server
_, _, _, _, _, handler, _, _, _ := setupTestingHandler(t, clparams.Phase0Version)
server := httptest.NewServer(handler.mux)
defer server.Close()
// json
req, err := json.Marshal(attesterSlashing)
require.NoError(t, err)
// post attester slashing
resp, err := server.Client().Post(server.URL+"/eth/v1/beacon/pool/attester_slashings", "application/json", bytes.NewBuffer(req))
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, 200, resp.StatusCode)
// get attester slashings
resp, err = server.Client().Get(server.URL + "/eth/v1/beacon/pool/attester_slashings")
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, 200, resp.StatusCode)
out := struct {
Data []*cltypes.AttesterSlashing `json:"data"`
}{
Data: []*cltypes.AttesterSlashing{
cltypes.NewAttesterSlashing(),
},
}
err = json.NewDecoder(resp.Body).Decode(&out)
require.NoError(t, err)
require.Equal(t, 1, len(out.Data))
require.Equal(t, attesterSlashing, out.Data[0])
}
func TestPoolProposerSlashings(t *testing.T) {
proposerSlashing := &cltypes.ProposerSlashing{
Header1: &cltypes.SignedBeaconBlockHeader{
Header: &cltypes.BeaconBlockHeader{
Slot: 1,
ProposerIndex: 3,
},
},
Header2: &cltypes.SignedBeaconBlockHeader{
Header: &cltypes.BeaconBlockHeader{
Slot: 2,
ProposerIndex: 4,
},
},
}
// find server
_, _, _, _, _, handler, _, _, _ := setupTestingHandler(t, clparams.Phase0Version)
server := httptest.NewServer(handler.mux)
defer server.Close()
// json
req, err := json.Marshal(proposerSlashing)
require.NoError(t, err)
// post attester slashing
resp, err := server.Client().Post(server.URL+"/eth/v1/beacon/pool/proposer_slashings", "application/json", bytes.NewBuffer(req))
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, 200, resp.StatusCode)
// get attester slashings
resp, err = server.Client().Get(server.URL + "/eth/v1/beacon/pool/proposer_slashings")
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, 200, resp.StatusCode)
out := struct {
Data []*cltypes.ProposerSlashing `json:"data"`
}{
Data: []*cltypes.ProposerSlashing{},
}
err = json.NewDecoder(resp.Body).Decode(&out)
require.NoError(t, err)
require.Equal(t, 1, len(out.Data))
require.Equal(t, proposerSlashing, out.Data[0])
}
func TestPoolVoluntaryExits(t *testing.T) {
voluntaryExit := &cltypes.SignedVoluntaryExit{
VoluntaryExit: &cltypes.VoluntaryExit{
Epoch: 1,
ValidatorIndex: 3,
},
}
// find server
_, _, _, _, _, handler, _, _, _ := setupTestingHandler(t, clparams.Phase0Version)
server := httptest.NewServer(handler.mux)
defer server.Close()
// json
req, err := json.Marshal(voluntaryExit)
require.NoError(t, err)
// post attester slashing
resp, err := server.Client().Post(server.URL+"/eth/v1/beacon/pool/voluntary_exits", "application/json", bytes.NewBuffer(req))
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, 200, resp.StatusCode)
// get attester slashings
resp, err = server.Client().Get(server.URL + "/eth/v1/beacon/pool/voluntary_exits")
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, 200, resp.StatusCode)
out := struct {
Data []*cltypes.SignedVoluntaryExit `json:"data"`
}{
Data: []*cltypes.SignedVoluntaryExit{},
}
err = json.NewDecoder(resp.Body).Decode(&out)
require.NoError(t, err)
require.Equal(t, 1, len(out.Data))
require.Equal(t, voluntaryExit, out.Data[0])
}
func TestPoolBlsToExecutionChainges(t *testing.T) {
msg := []*cltypes.SignedBLSToExecutionChange{
{
Message: &cltypes.BLSToExecutionChange{
ValidatorIndex: 45,
},
Signature: libcommon.Bytes96{2},
},
{
Message: &cltypes.BLSToExecutionChange{
ValidatorIndex: 46,
},
},
}
// find server
_, _, _, _, _, handler, _, _, _ := setupTestingHandler(t, clparams.Phase0Version)
server := httptest.NewServer(handler.mux)
defer server.Close()
// json
req, err := json.Marshal(msg)
require.NoError(t, err)
// post attester slashing
resp, err := server.Client().Post(server.URL+"/eth/v1/beacon/pool/bls_to_execution_changes", "application/json", bytes.NewBuffer(req))
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, 200, resp.StatusCode)
// get attester slashings
resp, err = server.Client().Get(server.URL + "/eth/v1/beacon/pool/bls_to_execution_changes")
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, 200, resp.StatusCode)
out := struct {
Data []*cltypes.SignedBLSToExecutionChange `json:"data"`
}{
Data: []*cltypes.SignedBLSToExecutionChange{},
}
err = json.NewDecoder(resp.Body).Decode(&out)
require.NoError(t, err)
require.Equal(t, 2, len(out.Data))
require.Equal(t, msg[0], out.Data[0])
require.Equal(t, msg[1], out.Data[1])
}
func TestPoolAggregatesAndProofs(t *testing.T) {
msg := []*cltypes.SignedAggregateAndProof{
{
Message: &cltypes.AggregateAndProof{
Aggregate: solid.NewAttestionFromParameters([]byte{1, 2}, solid.NewAttestationData(), libcommon.Bytes96{3, 45, 6}),
},
Signature: libcommon.Bytes96{2},
},
{
Message: &cltypes.AggregateAndProof{
Aggregate: solid.NewAttestionFromParameters([]byte{1, 2, 5, 6}, solid.NewAttestationData(), libcommon.Bytes96{3, 0, 6}),
},
Signature: libcommon.Bytes96{2, 3, 5},
},
}
// find server
_, _, _, _, _, handler, _, _, _ := setupTestingHandler(t, clparams.Phase0Version)
server := httptest.NewServer(handler.mux)
defer server.Close()
// json
req, err := json.Marshal(msg)
require.NoError(t, err)
// post attester slashing
resp, err := server.Client().Post(server.URL+"/eth/v1/validator/aggregate_and_proofs", "application/json", bytes.NewBuffer(req))
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, 200, resp.StatusCode)
// get attester slashings
resp, err = server.Client().Get(server.URL + "/eth/v1/beacon/pool/attestations")
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, 200, resp.StatusCode)
out := struct {
Data []*solid.Attestation `json:"data"`
}{
Data: []*solid.Attestation{},
}
err = json.NewDecoder(resp.Body).Decode(&out)
require.NoError(t, err)
require.Equal(t, 2, len(out.Data))
require.Equal(t, msg[0].Message.Aggregate, out.Data[0])
require.Equal(t, msg[1].Message.Aggregate, out.Data[1])
}

View File

@ -23,7 +23,7 @@ type blockRewardsResponse struct {
Total uint64 `json:"total,string"`
}
func (a *ApiHandler) getBlockRewards(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) getBlockRewards(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
ctx := r.Context()
tx, err := a.indiciesDB.BeginRo(ctx)
if err != nil {
@ -31,7 +31,7 @@ func (a *ApiHandler) getBlockRewards(w http.ResponseWriter, r *http.Request) (*b
}
defer tx.Rollback()
blockId, err := blockIdFromRequest(r)
blockId, err := beaconhttp.BlockIdFromRequest(r)
if err != nil {
return nil, err
}
@ -61,7 +61,7 @@ func (a *ApiHandler) getBlockRewards(w http.ResponseWriter, r *http.Request) (*b
AttesterSlashings: blkRewards.AttesterSlashings,
SyncAggregate: blkRewards.SyncAggregate,
Total: blkRewards.Attestations + blkRewards.ProposerSlashings + blkRewards.AttesterSlashings + blkRewards.SyncAggregate,
}).withFinalized(isFinalized), nil
}).WithFinalized(isFinalized), nil
}
slotData, err := state_accessors.ReadSlotData(tx, slot)
if err != nil {
@ -77,7 +77,7 @@ func (a *ApiHandler) getBlockRewards(w http.ResponseWriter, r *http.Request) (*b
AttesterSlashings: slotData.AttesterSlashings,
SyncAggregate: slotData.SyncAggregateRewards,
Total: slotData.AttestationsRewards + slotData.ProposerSlashings + slotData.AttesterSlashings + slotData.SyncAggregateRewards,
}).withFinalized(isFinalized), nil
}).WithFinalized(isFinalized), nil
}
type syncCommitteeReward struct {
@ -85,7 +85,7 @@ type syncCommitteeReward struct {
Reward int64 `json:"reward,string"`
}
func (a *ApiHandler) getSyncCommitteesRewards(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) getSyncCommitteesRewards(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
ctx := r.Context()
tx, err := a.indiciesDB.BeginRo(ctx)
@ -111,7 +111,7 @@ func (a *ApiHandler) getSyncCommitteesRewards(w http.ResponseWriter, r *http.Req
return nil, err
}
blockId, err := blockIdFromRequest(r)
blockId, err := beaconhttp.BlockIdFromRequest(r)
if err != nil {
return nil, err
}
@ -220,7 +220,7 @@ func (a *ApiHandler) getSyncCommitteesRewards(w http.ResponseWriter, r *http.Req
sort.Slice(rewards, func(i, j int) bool {
return rewards[i].ValidatorIndex < rewards[j].ValidatorIndex
})
return newBeaconResponse(rewards).withFinalized(isFinalized), nil
return newBeaconResponse(rewards).WithFinalized(isFinalized), nil
}
func (a *ApiHandler) syncPartecipantReward(activeBalance uint64) uint64 {

File diff suppressed because one or more lines are too long

View File

@ -17,21 +17,21 @@ import (
"github.com/ledgerwatch/erigon/cl/utils"
)
func (a *ApiHandler) blockRootFromStateId(ctx context.Context, tx kv.Tx, stateId *segmentID) (root libcommon.Hash, httpStatusErr int, err error) {
func (a *ApiHandler) blockRootFromStateId(ctx context.Context, tx kv.Tx, stateId *beaconhttp.SegmentID) (root libcommon.Hash, httpStatusErr int, err error) {
switch {
case stateId.head():
case stateId.Head():
root, _, err = a.forkchoiceStore.GetHead()
if err != nil {
return libcommon.Hash{}, http.StatusInternalServerError, err
}
return
case stateId.finalized():
case stateId.Finalized():
root = a.forkchoiceStore.FinalizedCheckpoint().BlockRoot()
return
case stateId.justified():
case stateId.Justified():
root = a.forkchoiceStore.JustifiedCheckpoint().BlockRoot()
return
case stateId.genesis():
case stateId.Genesis():
root, err = beacon_indicies.ReadCanonicalBlockRoot(tx, 0)
if err != nil {
return libcommon.Hash{}, http.StatusInternalServerError, err
@ -40,17 +40,17 @@ func (a *ApiHandler) blockRootFromStateId(ctx context.Context, tx kv.Tx, stateId
return libcommon.Hash{}, http.StatusNotFound, fmt.Errorf("genesis block not found")
}
return
case stateId.getSlot() != nil:
root, err = beacon_indicies.ReadCanonicalBlockRoot(tx, *stateId.getSlot())
case stateId.GetSlot() != nil:
root, err = beacon_indicies.ReadCanonicalBlockRoot(tx, *stateId.GetSlot())
if err != nil {
return libcommon.Hash{}, http.StatusInternalServerError, err
}
if root == (libcommon.Hash{}) {
return libcommon.Hash{}, http.StatusNotFound, fmt.Errorf("block not found %d", *stateId.getSlot())
return libcommon.Hash{}, http.StatusNotFound, fmt.Errorf("block not found %d", *stateId.GetSlot())
}
return
case stateId.getRoot() != nil:
root, err = beacon_indicies.ReadBlockRootByStateRoot(tx, *stateId.getRoot())
case stateId.GetRoot() != nil:
root, err = beacon_indicies.ReadBlockRootByStateRoot(tx, *stateId.GetRoot())
if err != nil {
return libcommon.Hash{}, http.StatusInternalServerError, err
}
@ -71,7 +71,7 @@ func previousVersion(v clparams.StateVersion) clparams.StateVersion {
return v - 1
}
func (a *ApiHandler) getStateFork(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) getStateFork(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
ctx := r.Context()
tx, err := a.indiciesDB.BeginRo(ctx)
@ -80,7 +80,7 @@ func (a *ApiHandler) getStateFork(w http.ResponseWriter, r *http.Request) (*beac
}
defer tx.Rollback()
blockId, err := stateIdFromRequest(r)
blockId, err := beaconhttp.StateIdFromRequest(r)
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
@ -110,7 +110,7 @@ func (a *ApiHandler) getStateFork(w http.ResponseWriter, r *http.Request) (*beac
}), nil
}
func (a *ApiHandler) getStateRoot(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) getStateRoot(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
ctx := r.Context()
tx, err := a.indiciesDB.BeginRo(ctx)
@ -119,7 +119,7 @@ func (a *ApiHandler) getStateRoot(w http.ResponseWriter, r *http.Request) (*beac
}
defer tx.Rollback()
blockId, err := stateIdFromRequest(r)
blockId, err := beaconhttp.StateIdFromRequest(r)
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
@ -149,10 +149,10 @@ func (a *ApiHandler) getStateRoot(w http.ResponseWriter, r *http.Request) (*beac
}
return newBeaconResponse(&rootResponse{Root: stateRoot}).
withFinalized(canonicalRoot == root && *slot <= a.forkchoiceStore.FinalizedSlot()), nil
WithFinalized(canonicalRoot == root && *slot <= a.forkchoiceStore.FinalizedSlot()), nil
}
func (a *ApiHandler) getFullState(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) getFullState(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
ctx := r.Context()
tx, err := a.indiciesDB.BeginRo(ctx)
@ -161,7 +161,7 @@ func (a *ApiHandler) getFullState(w http.ResponseWriter, r *http.Request) (*beac
}
defer tx.Rollback()
blockId, err := stateIdFromRequest(r)
blockId, err := beaconhttp.StateIdFromRequest(r)
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
@ -198,10 +198,10 @@ func (a *ApiHandler) getFullState(w http.ResponseWriter, r *http.Request) (*beac
if state == nil {
return nil, beaconhttp.NewEndpointError(http.StatusNotFound, fmt.Sprintf("could not read state: %x", blockRoot))
}
return newBeaconResponse(state).withFinalized(true).withVersion(state.Version()), nil
return newBeaconResponse(state).WithFinalized(true).WithVersion(state.Version()), nil
}
return newBeaconResponse(state).withFinalized(false).withVersion(state.Version()), nil
return newBeaconResponse(state).WithFinalized(false).WithVersion(state.Version()), nil
}
type finalityCheckpointsResponse struct {
@ -210,7 +210,7 @@ type finalityCheckpointsResponse struct {
PreviousJustifiedCheckpoint solid.Checkpoint `json:"previous_justified_checkpoint"`
}
func (a *ApiHandler) getFinalityCheckpoints(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) getFinalityCheckpoints(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
ctx := r.Context()
tx, err := a.indiciesDB.BeginRo(ctx)
@ -218,7 +218,7 @@ func (a *ApiHandler) getFinalityCheckpoints(w http.ResponseWriter, r *http.Reque
return nil, err
}
defer tx.Rollback()
blockId, err := stateIdFromRequest(r)
blockId, err := beaconhttp.StateIdFromRequest(r)
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
@ -259,7 +259,7 @@ func (a *ApiHandler) getFinalityCheckpoints(w http.ResponseWriter, r *http.Reque
FinalizedCheckpoint: finalizedCheckpoint,
CurrentJustifiedCheckpoint: currentJustifiedCheckpoint,
PreviousJustifiedCheckpoint: previousJustifiedCheckpoint,
}).withFinalized(canonicalRoot == blockRoot && *slot <= a.forkchoiceStore.FinalizedSlot()).withVersion(version), nil
}).WithFinalized(canonicalRoot == blockRoot && *slot <= a.forkchoiceStore.FinalizedSlot()).WithVersion(version), nil
}
type syncCommitteesResponse struct {
@ -267,7 +267,7 @@ type syncCommitteesResponse struct {
ValidatorAggregates [][]string `json:"validator_aggregates"`
}
func (a *ApiHandler) getSyncCommittees(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) getSyncCommittees(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
ctx := r.Context()
tx, err := a.indiciesDB.BeginRo(ctx)
@ -275,7 +275,7 @@ func (a *ApiHandler) getSyncCommittees(w http.ResponseWriter, r *http.Request) (
return nil, err
}
defer tx.Rollback()
blockId, err := stateIdFromRequest(r)
blockId, err := beaconhttp.StateIdFromRequest(r)
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
@ -312,7 +312,7 @@ func (a *ApiHandler) getSyncCommittees(w http.ResponseWriter, r *http.Request) (
}
// Now fetch the data we need
statePeriod := a.beaconChainCfg.SyncCommitteePeriod(*slot)
queryEpoch, err := uint64FromQueryParams(r, "epoch")
queryEpoch, err := beaconhttp.Uint64FromQueryParams(r, "epoch")
if err != nil {
return nil, err
}
@ -355,14 +355,14 @@ func (a *ApiHandler) getSyncCommittees(w http.ResponseWriter, r *http.Request) (
return nil, err
}
return newBeaconResponse(response).withFinalized(canonicalRoot == blockRoot && *slot <= a.forkchoiceStore.FinalizedSlot()), nil
return newBeaconResponse(response).WithFinalized(canonicalRoot == blockRoot && *slot <= a.forkchoiceStore.FinalizedSlot()), nil
}
type randaoResponse struct {
Randao libcommon.Hash `json:"randao"`
}
func (a *ApiHandler) getRandao(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) getRandao(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
ctx := r.Context()
tx, err := a.indiciesDB.BeginRo(ctx)
@ -370,7 +370,7 @@ func (a *ApiHandler) getRandao(w http.ResponseWriter, r *http.Request) (*beaconR
return nil, err
}
defer tx.Rollback()
blockId, err := stateIdFromRequest(r)
blockId, err := beaconhttp.StateIdFromRequest(r)
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
@ -380,7 +380,7 @@ func (a *ApiHandler) getRandao(w http.ResponseWriter, r *http.Request) (*beaconR
return nil, beaconhttp.NewEndpointError(httpStatus, err.Error())
}
epochReq, err := uint64FromQueryParams(r, "epoch")
epochReq, err := beaconhttp.Uint64FromQueryParams(r, "epoch")
if err != nil {
return nil, err
}
@ -401,7 +401,7 @@ func (a *ApiHandler) getRandao(w http.ResponseWriter, r *http.Request) (*beaconR
if a.forkchoiceStore.RandaoMixes(blockRoot, randaoMixes) {
mix := randaoMixes.Get(int(epoch % a.beaconChainCfg.EpochsPerHistoricalVector))
return newBeaconResponse(randaoResponse{Randao: mix}).withFinalized(slot <= a.forkchoiceStore.FinalizedSlot()), nil
return newBeaconResponse(randaoResponse{Randao: mix}).WithFinalized(slot <= a.forkchoiceStore.FinalizedSlot()), nil
}
// check if the block is canonical
canonicalRoot, err := beacon_indicies.ReadCanonicalBlockRoot(tx, slot)
@ -415,5 +415,5 @@ func (a *ApiHandler) getRandao(w http.ResponseWriter, r *http.Request) (*beaconR
if err != nil {
return nil, err
}
return newBeaconResponse(randaoResponse{Randao: mix}).withFinalized(slot <= a.forkchoiceStore.FinalizedSlot()), nil
return newBeaconResponse(randaoResponse{Randao: mix}).WithFinalized(slot <= a.forkchoiceStore.FinalizedSlot()), nil
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"data":{"ideal_rewards":[{"effective_balance":"32000000000","head":"0","target":"290680","source":"0","inclusion_delay":"0","inactivity":"0"},{"effective_balance":"32000000000","head":"0","target":"290680","source":"0","inclusion_delay":"0","inactivity":"0"}],"total_rewards":[{"validator_index":"1","head":"0","target":"290680","source":"-156520","inclusion_delay":"0","inactivity":"0"},{"validator_index":"4","head":"0","target":"290680","source":"-156520","inclusion_delay":"0","inactivity":"0"}]}}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"data":{"ideal_rewards":[{"effective_balance":"20000000000","head":"0","target":"0","source":"0","inclusion_delay":"0","inactivity":"0"},{"effective_balance":"17000000000","head":"57360","target":"57360","source":"57360","inclusion_delay":"14217","inactivity":"0"}],"total_rewards":[{"validator_index":"1","head":"0","target":"0","source":"0","inclusion_delay":"0","inactivity":"0"},{"validator_index":"4","head":"57360","target":"57360","source":"57360","inclusion_delay":"14217","inactivity":"0"}]}}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
- {"data":[{"index":"0","slot":"8322","validators":["0","104","491","501","379","318","275","504","75","280","105","399","35","401"]}],"execution_optimistic":false,"finalized":true}
- {"data":[],"finalized":true,"execution_optimistic":false}
- {"data":[{"index":"0","slot":"8290","validators":["127","377","274","85","309","420","423","398","153","480","273","429","374","260"]}],"execution_optimistic":false,"finalized":true}
- {"data":[{"index":"0","slot":"8322","validators":["0","104","491","501","379","318","275","504","75","280","105","399","35","401"]}],"finalized":false,"execution_optimistic":false}
- {"data":[],"finalized":false,"execution_optimistic":false}
- {"data":[{"index":"0","slot":"8290","validators":["127","377","274","85","309","420","423","398","153","480","273","429","374","260"]}],"finalized":false,"execution_optimistic":false}

View File

@ -0,0 +1,2 @@
- {"data":[{"pubkey":"0x97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb","validator_index":"0","committee_index":"0","committee_length":"14","validator_committee_index":"0","committees_at_slot":"1","slot":"8322"},{"pubkey":"0xb0e7791fb972fe014159aa33a98622da3cdc98ff707965e536d8636b5fcc5ac7a91a8c46e59a00dca575af0f18fb13dc","validator_index":"4","committee_index":"0","committee_length":"13","validator_committee_index":"5","committees_at_slot":"1","slot":"8327"},{"pubkey":"0xb928f3beb93519eecf0145da903b40a4c97dca00b21f12ac0df3be9116ef2ef27b2ae6bcd4c5bc2d54ef5a70627efcb7","validator_index":"6","committee_index":"0","committee_length":"13","validator_committee_index":"10","committees_at_slot":"1","slot":"8327"},{"pubkey":"0xa6e82f6da4520f85c5d27d8f329eccfa05944fd1096b20734c894966d12a9e2a9a9744529d7212d33883113a0cadb909","validator_index":"5","committee_index":"0","committee_length":"14","validator_committee_index":"10","committees_at_slot":"1","slot":"8329"},{"pubkey":"0x89ece308f9d1f0131765212deca99697b112d61f9be9a5f1f3780a51335b3ff981747a0b2ca2179b96d2c0c9024e5224","validator_index":"2","committee_index":"0","committee_length":"14","validator_committee_index":"11","committees_at_slot":"1","slot":"8331"},{"pubkey":"0xaf81da25ecf1c84b577fefbedd61077a81dc43b00304015b2b596ab67f00e41c86bb00ebd0f90d4b125eb0539891aeed","validator_index":"9","committee_index":"0","committee_length":"14","validator_committee_index":"8","committees_at_slot":"1","slot":"8342"},{"pubkey":"0xac9b60d5afcbd5663a8a44b7c5a02f19e9a77ab0a35bd65809bb5c67ec582c897feb04decc694b13e08587f3ff9b5b60","validator_index":"3","committee_index":"0","committee_length":"13","validator_committee_index":"6","committees_at_slot":"1","slot":"8348"}],"execution_optimistic":false}
- {"data":[],"execution_optimistic":false}

View File

@ -0,0 +1,2 @@
- {"data":[{"pubkey":"0x97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb","validator_index":"0","validator_sync_committee_indicies":["30","286"]},{"pubkey":"0xa572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e","validator_index":"1","validator_sync_committee_indicies":["120","376"]},{"pubkey":"0x89ece308f9d1f0131765212deca99697b112d61f9be9a5f1f3780a51335b3ff981747a0b2ca2179b96d2c0c9024e5224","validator_index":"2","validator_sync_committee_indicies":["138","394"]},{"pubkey":"0xac9b60d5afcbd5663a8a44b7c5a02f19e9a77ab0a35bd65809bb5c67ec582c897feb04decc694b13e08587f3ff9b5b60","validator_index":"3","validator_sync_committee_indicies":["10","266"]},{"pubkey":"0xb0e7791fb972fe014159aa33a98622da3cdc98ff707965e536d8636b5fcc5ac7a91a8c46e59a00dca575af0f18fb13dc","validator_index":"4","validator_sync_committee_indicies":["114","370"]},{"pubkey":"0xa6e82f6da4520f85c5d27d8f329eccfa05944fd1096b20734c894966d12a9e2a9a9744529d7212d33883113a0cadb909","validator_index":"5","validator_sync_committee_indicies":["103","359"]},{"pubkey":"0xb928f3beb93519eecf0145da903b40a4c97dca00b21f12ac0df3be9116ef2ef27b2ae6bcd4c5bc2d54ef5a70627efcb7","validator_index":"6","validator_sync_committee_indicies":["163","419"]},{"pubkey":"0xa85ae765588126f5e860d019c0e26235f567a9c0c0b2d8ff30f3e8d436b1082596e5e7462d20f5be3764fd473e57f9cf","validator_index":"7","validator_sync_committee_indicies":["197","453"]},{"pubkey":"0x99cdf3807146e68e041314ca93e1fee0991224ec2a74beb2866816fd0826ce7b6263ee31e953a86d1b72cc2215a57793","validator_index":"8","validator_sync_committee_indicies":["175","431"]},{"pubkey":"0xaf81da25ecf1c84b577fefbedd61077a81dc43b00304015b2b596ab67f00e41c86bb00ebd0f90d4b125eb0539891aeed","validator_index":"9","validator_sync_committee_indicies":["53","309"]}],"execution_optimistic":false}
- {"data":[],"execution_optimistic":false}

View File

@ -0,0 +1,2 @@
- {"data":[{"execution_optimistic":false,"root":"0x0102030000000000000000000000000000000000000000000000000000000000","slot":128}]}
- {"finalized_checkpoint":{"epoch":"1","root":"0x0102030000000000000000000000000000000000000000000000000000000000"},"fork_choice_nodes":[{"slot":"128","block_root":"0x0102030000000000000000000000000000000000000000000000000000000000","parent_root":"0x0102030000000000000000000000000000000000000000000000000000000000","justified_epoch":"0","finalized_epoch":"0","weight":"1","validity":"","execution_block_hash":"0x0000000000000000000000000000000000000000000000000000000000000000"},{"slot":"128","block_root":"0x0102020405030000000000000000000000000000000000000000000000000000","parent_root":"0x0102050000000000000000000000000000000000000000000000000000000000","justified_epoch":"0","finalized_epoch":"0","weight":"2","validity":"","execution_block_hash":"0x0000000000000000000000000000000000000000000000000000000000000000"}],"justified_checkpoint":{"epoch":"2","root":"0x0102030000000000000000000000000000000000000000000000000000000000"}}

View File

@ -0,0 +1,3 @@
- {"data":{"proposer_index":"203","attestations":"332205","proposer_slashings":"0","attester_slashings":"0","sync_aggregate":"0","total":"332205"},"finalized":true,"execution_optimistic":false}
- {"data":{"proposer_index":"98","attestations":"332205","proposer_slashings":"0","attester_slashings":"0","sync_aggregate":"0","total":"332205"},"finalized":true,"execution_optimistic":false}
- {"data":[{"validator_index":"1","reward":"-698"},{"validator_index":"4","reward":"-698"}],"execution_optimistic":false,"finalized":true}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
finality_checkpoint: {"data":{"finalized_checkpoint":{"epoch":"1","root":"0xde46b0f2ed5e72f0cec20246403b14c963ec995d7c2825f3532b0460c09d5693"},"current_justified_checkpoint":{"epoch":"3","root":"0xa6e47f164b1a3ca30ea3b2144bd14711de442f51e5b634750a12a1734e24c987"},"previous_justified_checkpoint":{"epoch":"2","root":"0x4c3ee7969e485696669498a88c17f70e6999c40603e2f4338869004392069063"}},"finalized":false,"version":2,"execution_optimistic":false}
randao: {"data":{"randao":"0xdeec617717272914bfd73e02ca1da113a83cf4cf33cd4939486509e2da4ccf4e"},"finalized":false,"execution_optimistic":false}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,9 @@
- {"data":[{"index":"1","status":"withdrawal_possible","balance":"20125000000","validator":{"pubkey":"0xa572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e","withdrawal_credentials":"0x001f09ed305c0767d56f1b3bdb25f301298027f8e98a8e0cd2dcbcc660723d7b","effective_balance":"20000000000","slashed":false,"activation_eligibility_epoch":"0","activation_epoch":"0","exit_epoch":"253","withdrawable_epoch":"257"}},{"index":"2","status":"active_slashed","balance":"25678253779","validator":{"pubkey":"0x89ece308f9d1f0131765212deca99697b112d61f9be9a5f1f3780a51335b3ff981747a0b2ca2179b96d2c0c9024e5224","withdrawal_credentials":"0x006adc4a1e4caba37c54d56d2411fd0df3a102f8489a4c1be535f4fd5f8810c9","effective_balance":"25000000000","slashed":true,"activation_eligibility_epoch":"0","activation_epoch":"0","exit_epoch":"261","withdrawable_epoch":"8448"}},{"index":"3","status":"active_slashed","balance":"35998164834","validator":{"pubkey":"0xac9b60d5afcbd5663a8a44b7c5a02f19e9a77ab0a35bd65809bb5c67ec582c897feb04decc694b13e08587f3ff9b5b60","withdrawal_credentials":"0x0081c852078a2ad430d438d7eaefc39646f53895292596bbe199e2d7d1884ab8","effective_balance":"32000000000","slashed":true,"activation_eligibility_epoch":"0","activation_epoch":"0","exit_epoch":"261","withdrawable_epoch":"8448"}}],"finalized":true,"execution_optimistic":false}
- {"data":[{"index":"2","status":"active_slashed","balance":"25678253779","validator":{"pubkey":"0x89ece308f9d1f0131765212deca99697b112d61f9be9a5f1f3780a51335b3ff981747a0b2ca2179b96d2c0c9024e5224","withdrawal_credentials":"0x006adc4a1e4caba37c54d56d2411fd0df3a102f8489a4c1be535f4fd5f8810c9","effective_balance":"25000000000","slashed":true,"activation_eligibility_epoch":"0","activation_epoch":"0","exit_epoch":"261","withdrawable_epoch":"8448"}},{"index":"3","status":"active_slashed","balance":"35998164834","validator":{"pubkey":"0xac9b60d5afcbd5663a8a44b7c5a02f19e9a77ab0a35bd65809bb5c67ec582c897feb04decc694b13e08587f3ff9b5b60","withdrawal_credentials":"0x0081c852078a2ad430d438d7eaefc39646f53895292596bbe199e2d7d1884ab8","effective_balance":"32000000000","slashed":true,"activation_eligibility_epoch":"0","activation_epoch":"0","exit_epoch":"261","withdrawable_epoch":"8448"}}],"finalized":true,"execution_optimistic":false}
- {"data":[{"index":"1","status":"withdrawal_possible","balance":"20125000000","validator":{"pubkey":"0xa572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e","withdrawal_credentials":"0x001f09ed305c0767d56f1b3bdb25f301298027f8e98a8e0cd2dcbcc660723d7b","effective_balance":"20000000000","slashed":false,"activation_eligibility_epoch":"0","activation_epoch":"0","exit_epoch":"253","withdrawable_epoch":"257"}}],"finalized":true,"execution_optimistic":false}
- {"data":[{"index":"1","balance":"20125000000"},{"index":"2","balance":"25678253779"},{"index":"3","balance":"35998164834"}],"finalized":true,"execution_optimistic":false}
- {"data":[{"index":"1","balance":"20125000000"}],"finalized":true,"execution_optimistic":false}
- {"data":{"index":"1","status":"withdrawal_possible","balance":"20125000000","validator":{"pubkey":"0xa572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e","withdrawal_credentials":"0x001f09ed305c0767d56f1b3bdb25f301298027f8e98a8e0cd2dcbcc660723d7b","effective_balance":"20000000000","slashed":false,"activation_eligibility_epoch":"0","activation_epoch":"0","exit_epoch":"253","withdrawable_epoch":"257"}},"finalized":true,"execution_optimistic":false}
- {"data":{"index":"1","status":"withdrawal_possible","balance":"20125000000","validator":{"pubkey":"0xa572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e","withdrawal_credentials":"0x001f09ed305c0767d56f1b3bdb25f301298027f8e98a8e0cd2dcbcc660723d7b","effective_balance":"20000000000","slashed":false,"activation_eligibility_epoch":"0","activation_epoch":"0","exit_epoch":"253","withdrawable_epoch":"257"}},"finalized":true,"execution_optimistic":false}

View File

@ -1,4 +1,4 @@
package handler
package handler_test
import (
"context"
@ -9,6 +9,7 @@ import (
"github.com/ledgerwatch/erigon-lib/kv/memdb"
"github.com/ledgerwatch/erigon/cl/antiquary"
"github.com/ledgerwatch/erigon/cl/antiquary/tests"
"github.com/ledgerwatch/erigon/cl/beacon/handler"
"github.com/ledgerwatch/erigon/cl/beacon/synced_data"
"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/ledgerwatch/erigon/cl/cltypes"
@ -23,7 +24,7 @@ import (
"github.com/stretchr/testify/require"
)
func setupTestingHandler(t *testing.T, v clparams.StateVersion) (db kv.RwDB, blocks []*cltypes.SignedBeaconBlock, f afero.Fs, preState, postState *state.CachingBeaconState, handler *ApiHandler, opPool pool.OperationsPool, syncedData *synced_data.SyncedDataManager, fcu *forkchoice.ForkChoiceStorageMock) {
func setupTestingHandler(t *testing.T, v clparams.StateVersion, logger log.Logger) (db kv.RwDB, blocks []*cltypes.SignedBeaconBlock, f afero.Fs, preState, postState *state.CachingBeaconState, h *handler.ApiHandler, opPool pool.OperationsPool, syncedData *synced_data.SyncedDataManager, fcu *forkchoice.ForkChoiceStorageMock) {
bcfg := clparams.MainnetBeaconConfig
if v == clparams.Phase0Version {
blocks, preState, postState = tests.GetPhase0Random()
@ -44,7 +45,7 @@ func setupTestingHandler(t *testing.T, v clparams.StateVersion) (db kv.RwDB, blo
ctx := context.Background()
vt := state_accessors.NewStaticValidatorTable()
a := antiquary.NewAntiquary(ctx, preState, vt, &bcfg, datadir.New("/tmp"), nil, db, nil, reader, nil, log.New(), true, true, f)
a := antiquary.NewAntiquary(ctx, preState, vt, &bcfg, datadir.New("/tmp"), nil, db, nil, reader, nil, logger, true, true, f)
require.NoError(t, a.IncrementBeaconState(ctx, blocks[len(blocks)-1].Block.Slot+33))
// historical states reader below
statesReader := historical_states_reader.NewHistoricalStatesReader(&bcfg, reader, vt, f, preState)
@ -52,7 +53,7 @@ func setupTestingHandler(t *testing.T, v clparams.StateVersion) (db kv.RwDB, blo
fcu.Pool = opPool
syncedData = synced_data.NewSyncedDataManager(true, &bcfg)
gC := clparams.GenesisConfigs[clparams.MainnetNetwork]
handler = NewApiHandler(
h = handler.NewApiHandler(
&gC,
&bcfg,
rawDB,
@ -64,6 +65,6 @@ func setupTestingHandler(t *testing.T, v clparams.StateVersion) (db kv.RwDB, blo
statesReader,
nil,
"test-version")
handler.init()
h.Init()
return
}

View File

@ -184,7 +184,7 @@ func checkValidValidatorId(s string) (bool, error) {
return false, nil
}
func (a *ApiHandler) getAllValidators(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) getAllValidators(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
ctx := r.Context()
tx, err := a.indiciesDB.BeginRo(ctx)
@ -193,7 +193,7 @@ func (a *ApiHandler) getAllValidators(w http.ResponseWriter, r *http.Request) (*
}
defer tx.Rollback()
blockId, err := stateIdFromRequest(r)
blockId, err := beaconhttp.StateIdFromRequest(r)
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
@ -203,12 +203,12 @@ func (a *ApiHandler) getAllValidators(w http.ResponseWriter, r *http.Request) (*
return nil, beaconhttp.NewEndpointError(httpStatus, err.Error())
}
queryFilters, err := stringListFromQueryParams(r, "status")
queryFilters, err := beaconhttp.StringListFromQueryParams(r, "status")
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
validatorIds, err := stringListFromQueryParams(r, "id")
validatorIds, err := beaconhttp.StringListFromQueryParams(r, "id")
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
@ -226,7 +226,7 @@ func (a *ApiHandler) getAllValidators(w http.ResponseWriter, r *http.Request) (*
return nil, err
}
if blockId.head() { // Lets see if we point to head, if yes then we need to look at the head state we always keep.
if blockId.Head() { // Lets see if we point to head, if yes then we need to look at the head state we always keep.
s, cn := a.syncedData.HeadState()
defer cn()
if s == nil {
@ -308,7 +308,7 @@ func parseQueryValidatorIndicies(tx kv.Tx, ids []string) ([]uint64, error) {
return filterIndicies, nil
}
func (a *ApiHandler) getSingleValidator(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) getSingleValidator(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
ctx := r.Context()
tx, err := a.indiciesDB.BeginRo(ctx)
@ -317,7 +317,7 @@ func (a *ApiHandler) getSingleValidator(w http.ResponseWriter, r *http.Request)
}
defer tx.Rollback()
blockId, err := stateIdFromRequest(r)
blockId, err := beaconhttp.StateIdFromRequest(r)
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
@ -327,7 +327,7 @@ func (a *ApiHandler) getSingleValidator(w http.ResponseWriter, r *http.Request)
return nil, beaconhttp.NewEndpointError(httpStatus, err.Error())
}
validatorId, err := stringFromRequest(r, "validator_id")
validatorId, err := beaconhttp.StringFromRequest(r, "validator_id")
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
@ -337,11 +337,11 @@ func (a *ApiHandler) getSingleValidator(w http.ResponseWriter, r *http.Request)
return nil, err
}
if blockId.head() { // Lets see if we point to head, if yes then we need to look at the head state we always keep.
if blockId.Head() { // Lets see if we point to head, if yes then we need to look at the head state we always keep.
s, cn := a.syncedData.HeadState()
defer cn()
if s.ValidatorLength() <= int(validatorIndex) {
return newBeaconResponse([]int{}).withFinalized(false), nil
return newBeaconResponse([]int{}).WithFinalized(false), nil
}
if s == nil {
return nil, beaconhttp.NewEndpointError(http.StatusNotFound, "node is not synced")
@ -375,7 +375,7 @@ func (a *ApiHandler) getSingleValidator(w http.ResponseWriter, r *http.Request)
return responseValidator(validatorIndex, stateEpoch, state.Balances(), state.Validators(), *slot <= a.forkchoiceStore.FinalizedSlot())
}
func (a *ApiHandler) getAllValidatorsBalances(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
func (a *ApiHandler) getAllValidatorsBalances(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
ctx := r.Context()
tx, err := a.indiciesDB.BeginRo(ctx)
@ -384,7 +384,7 @@ func (a *ApiHandler) getAllValidatorsBalances(w http.ResponseWriter, r *http.Req
}
defer tx.Rollback()
blockId, err := stateIdFromRequest(r)
blockId, err := beaconhttp.StateIdFromRequest(r)
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
@ -394,7 +394,7 @@ func (a *ApiHandler) getAllValidatorsBalances(w http.ResponseWriter, r *http.Req
return nil, beaconhttp.NewEndpointError(httpStatus, err.Error())
}
validatorIds, err := stringListFromQueryParams(r, "id")
validatorIds, err := beaconhttp.StringListFromQueryParams(r, "id")
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
@ -407,7 +407,7 @@ func (a *ApiHandler) getAllValidatorsBalances(w http.ResponseWriter, r *http.Req
return nil, err
}
if blockId.head() { // Lets see if we point to head, if yes then we need to look at the head state we always keep.
if blockId.Head() { // Lets see if we point to head, if yes then we need to look at the head state we always keep.
s, cn := a.syncedData.HeadState()
defer cn()
if s == nil {
@ -444,7 +444,7 @@ func (d directString) MarshalJSON() ([]byte, error) {
return []byte(d), nil
}
func responseValidators(filterIndicies []uint64, filterStatuses []validatorStatus, stateEpoch uint64, balances solid.Uint64ListSSZ, validators *solid.ValidatorSet, finalized bool) (*beaconResponse, error) {
func responseValidators(filterIndicies []uint64, filterStatuses []validatorStatus, stateEpoch uint64, balances solid.Uint64ListSSZ, validators *solid.ValidatorSet, finalized bool) (*beaconhttp.BeaconResponse, error) {
var b strings.Builder
b.WriteString("[")
first := true
@ -474,14 +474,14 @@ func responseValidators(filterIndicies []uint64, filterStatuses []validatorStatu
_, err = b.WriteString("]\n")
return newBeaconResponse(directString(b.String())).withFinalized(finalized), err
return newBeaconResponse(directString(b.String())).WithFinalized(finalized), err
}
func responseValidator(idx uint64, stateEpoch uint64, balances solid.Uint64ListSSZ, validators *solid.ValidatorSet, finalized bool) (*beaconResponse, error) {
func responseValidator(idx uint64, stateEpoch uint64, balances solid.Uint64ListSSZ, validators *solid.ValidatorSet, finalized bool) (*beaconhttp.BeaconResponse, error) {
var b strings.Builder
var err error
if validators.Length() <= int(idx) {
return newBeaconResponse([]int{}).withFinalized(finalized), nil
return newBeaconResponse([]int{}).WithFinalized(finalized), nil
}
v := validators.Get(int(idx))
@ -493,10 +493,10 @@ func responseValidator(idx uint64, stateEpoch uint64, balances solid.Uint64ListS
_, err = b.WriteString("\n")
return newBeaconResponse(directString(b.String())).withFinalized(finalized), err
return newBeaconResponse(directString(b.String())).WithFinalized(finalized), err
}
func responseValidatorsBalances(filterIndicies []uint64, stateEpoch uint64, balances solid.Uint64ListSSZ, finalized bool) (*beaconResponse, error) {
func responseValidatorsBalances(filterIndicies []uint64, stateEpoch uint64, balances solid.Uint64ListSSZ, finalized bool) (*beaconhttp.BeaconResponse, error) {
var b strings.Builder
b.WriteString("[")
jsonTemplate := "{\"index\":\"%d\",\"balance\":\"%d\"}"
@ -524,7 +524,7 @@ func responseValidatorsBalances(filterIndicies []uint64, stateEpoch uint64, bala
_, err = b.WriteString("]\n")
return newBeaconResponse(directString(b.String())).withFinalized(finalized), err
return newBeaconResponse(directString(b.String())).WithFinalized(finalized), err
}
func shouldStatusBeFiltered(status validatorStatus, statuses []validatorStatus) bool {

View File

@ -1,201 +0,0 @@
//go:build integration
package handler
import (
"io"
"net/http"
"net/http/httptest"
"testing"
"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/ledgerwatch/erigon/cl/cltypes/solid"
"github.com/ledgerwatch/erigon/common"
"github.com/stretchr/testify/require"
)
func TestGetAllValidators(t *testing.T) {
// setupTestingHandler(t, clparams.Phase0Version)
_, blocks, _, _, postState, handler, _, _, fcu := setupTestingHandler(t, clparams.Phase0Version)
postRoot, err := postState.HashSSZ()
require.NoError(t, err)
fcu.HeadVal, err = blocks[len(blocks)-1].Block.HashSSZ()
require.NoError(t, err)
fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot
fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(fcu.HeadVal, fcu.HeadSlotVal/32)
cases := []struct {
blockID string
code int
queryParams string
expectedResp string
}{
{
blockID: "0x" + common.Bytes2Hex(postRoot[:]),
code: http.StatusOK,
queryParams: "?id=1,2,3",
expectedResp: `{"data":[{"index":"1","status":"withdrawal_possible","balance":"20125000000","validator":{"pubkey":"0xa572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e","withdrawal_credentials":"0x001f09ed305c0767d56f1b3bdb25f301298027f8e98a8e0cd2dcbcc660723d7b","effective_balance":"20000000000","slashed":false,"activation_eligibility_epoch":"0","activation_epoch":"0","exit_epoch":"253","withdrawable_epoch":"257"}},{"index":"2","status":"active_slashed","balance":"25678253779","validator":{"pubkey":"0x89ece308f9d1f0131765212deca99697b112d61f9be9a5f1f3780a51335b3ff981747a0b2ca2179b96d2c0c9024e5224","withdrawal_credentials":"0x006adc4a1e4caba37c54d56d2411fd0df3a102f8489a4c1be535f4fd5f8810c9","effective_balance":"25000000000","slashed":true,"activation_eligibility_epoch":"0","activation_epoch":"0","exit_epoch":"261","withdrawable_epoch":"8448"}},{"index":"3","status":"active_slashed","balance":"35998164834","validator":{"pubkey":"0xac9b60d5afcbd5663a8a44b7c5a02f19e9a77ab0a35bd65809bb5c67ec582c897feb04decc694b13e08587f3ff9b5b60","withdrawal_credentials":"0x0081c852078a2ad430d438d7eaefc39646f53895292596bbe199e2d7d1884ab8","effective_balance":"32000000000","slashed":true,"activation_eligibility_epoch":"0","activation_epoch":"0","exit_epoch":"261","withdrawable_epoch":"8448"}}],"finalized":true,"execution_optimistic":false}` + "\n",
},
{
blockID: "finalized",
code: http.StatusOK,
queryParams: "?status=active&id=1,2,3",
expectedResp: `{"data":[{"index":"2","status":"active_slashed","balance":"25678253779","validator":{"pubkey":"0x89ece308f9d1f0131765212deca99697b112d61f9be9a5f1f3780a51335b3ff981747a0b2ca2179b96d2c0c9024e5224","withdrawal_credentials":"0x006adc4a1e4caba37c54d56d2411fd0df3a102f8489a4c1be535f4fd5f8810c9","effective_balance":"25000000000","slashed":true,"activation_eligibility_epoch":"0","activation_epoch":"0","exit_epoch":"261","withdrawable_epoch":"8448"}},{"index":"3","status":"active_slashed","balance":"35998164834","validator":{"pubkey":"0xac9b60d5afcbd5663a8a44b7c5a02f19e9a77ab0a35bd65809bb5c67ec582c897feb04decc694b13e08587f3ff9b5b60","withdrawal_credentials":"0x0081c852078a2ad430d438d7eaefc39646f53895292596bbe199e2d7d1884ab8","effective_balance":"32000000000","slashed":true,"activation_eligibility_epoch":"0","activation_epoch":"0","exit_epoch":"261","withdrawable_epoch":"8448"}}],"finalized":true,"execution_optimistic":false}` + "\n",
},
{
blockID: "finalized",
code: http.StatusOK,
queryParams: "?id=0xa572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e",
expectedResp: `{"data":[{"index":"1","status":"withdrawal_possible","balance":"20125000000","validator":{"pubkey":"0xa572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e","withdrawal_credentials":"0x001f09ed305c0767d56f1b3bdb25f301298027f8e98a8e0cd2dcbcc660723d7b","effective_balance":"20000000000","slashed":false,"activation_eligibility_epoch":"0","activation_epoch":"0","exit_epoch":"253","withdrawable_epoch":"257"}}],"finalized":true,"execution_optimistic":false}` + "\n",
},
{
blockID: "alabama",
code: http.StatusBadRequest,
},
}
for _, c := range cases {
t.Run(c.blockID, func(t *testing.T) {
server := httptest.NewServer(handler.mux)
defer server.Close()
// Query the block in the handler with /eth/v2/beacon/blocks/{block_id}
resp, err := http.Get(server.URL + "/eth/v1/beacon/states/" + c.blockID + "/validators" + c.queryParams)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, c.code, resp.StatusCode)
if resp.StatusCode != http.StatusOK {
return
}
// unmarshal the json
out, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, c.expectedResp, string(out))
})
}
}
func TestGetValidatorsBalances(t *testing.T) {
t.Skip("FIXME: oom")
// setupTestingHandler(t, clparams.Phase0Version)
_, blocks, _, _, postState, handler, _, _, fcu := setupTestingHandler(t, clparams.Phase0Version)
postRoot, err := postState.HashSSZ()
require.NoError(t, err)
fcu.HeadVal, err = blocks[len(blocks)-1].Block.HashSSZ()
require.NoError(t, err)
fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot
fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(fcu.HeadVal, fcu.HeadSlotVal/32)
cases := []struct {
blockID string
code int
queryParams string
expectedResp string
}{
{
blockID: "0x" + common.Bytes2Hex(postRoot[:]),
code: http.StatusOK,
queryParams: "?id=1,2,3",
expectedResp: `{"data":[{"index":"1","balance":"20125000000"},{"index":"2","balance":"25678253779"},{"index":"3","balance":"35998164834"}],"finalized":true,"execution_optimistic":false}` + "\n",
},
{
blockID: "finalized",
code: http.StatusOK,
queryParams: "?id=0xa572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e",
expectedResp: `{"data":[{"index":"1","balance":"20125000000"}],"finalized":true,"execution_optimistic":false}` + "\n",
},
{
blockID: "alabama",
code: http.StatusBadRequest,
},
}
for _, c := range cases {
t.Run(c.blockID, func(t *testing.T) {
server := httptest.NewServer(handler.mux)
defer server.Close()
// Query the block in the handler with /eth/v2/beacon/blocks/{block_id}
resp, err := http.Get(server.URL + "/eth/v1/beacon/states/" + c.blockID + "/validator_balances" + c.queryParams)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, c.code, resp.StatusCode)
if resp.StatusCode != http.StatusOK {
return
}
// unmarshal the json
out, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, c.expectedResp, string(out))
})
}
}
func TestGetSingleValidator(t *testing.T) {
// setupTestingHandler(t, clparams.Phase0Version)
_, blocks, _, _, postState, handler, _, _, fcu := setupTestingHandler(t, clparams.Phase0Version)
postRoot, err := postState.HashSSZ()
require.NoError(t, err)
fcu.HeadVal, err = blocks[len(blocks)-1].Block.HashSSZ()
require.NoError(t, err)
fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot
fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(fcu.HeadVal, fcu.HeadSlotVal/32)
cases := []struct {
blockID string
code int
validatorIdx string
expectedResp string
}{
{
blockID: "0x" + common.Bytes2Hex(postRoot[:]),
code: http.StatusOK,
validatorIdx: "1",
expectedResp: `{"data":{"index":"1","status":"withdrawal_possible","balance":"20125000000","validator":{"pubkey":"0xa572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e","withdrawal_credentials":"0x001f09ed305c0767d56f1b3bdb25f301298027f8e98a8e0cd2dcbcc660723d7b","effective_balance":"20000000000","slashed":false,"activation_eligibility_epoch":"0","activation_epoch":"0","exit_epoch":"253","withdrawable_epoch":"257"}},"finalized":true,"execution_optimistic":false}` + "\n",
},
{
blockID: "finalized",
code: http.StatusOK,
validatorIdx: "0xa572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e",
expectedResp: `{"data":{"index":"1","status":"withdrawal_possible","balance":"20125000000","validator":{"pubkey":"0xa572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e","withdrawal_credentials":"0x001f09ed305c0767d56f1b3bdb25f301298027f8e98a8e0cd2dcbcc660723d7b","effective_balance":"20000000000","slashed":false,"activation_eligibility_epoch":"0","activation_epoch":"0","exit_epoch":"253","withdrawable_epoch":"257"}},"finalized":true,"execution_optimistic":false}` + "\n",
},
{
blockID: "alabama",
code: http.StatusBadRequest,
validatorIdx: "3",
},
}
for _, c := range cases {
t.Run(c.blockID, func(t *testing.T) {
server := httptest.NewServer(handler.mux)
defer server.Close()
// Query the block in the handler with /eth/v2/beacon/blocks/{block_id}
resp, err := http.Get(server.URL + "/eth/v1/beacon/states/" + c.blockID + "/validators/" + c.validatorIdx)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, c.code, resp.StatusCode)
if resp.StatusCode != http.StatusOK {
return
}
// unmarshal the json
out, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, c.expectedResp, string(out))
})
}
}

13
go.mod
View File

@ -15,6 +15,7 @@ require (
gfx.cafe/util/go/generic v0.0.0-20230721185457-c559e86c829c
github.com/99designs/gqlgen v0.17.40
github.com/Giulio2002/bls v0.0.0-20230906201036-c2330c97dc7d
github.com/Masterminds/sprig/v3 v3.2.3
github.com/RoaringBitmap/roaring v1.2.3
github.com/VictoriaMetrics/fastcache v1.12.2
github.com/alecthomas/kong v0.8.0
@ -45,6 +46,7 @@ require (
github.com/golang/mock v1.6.0
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb
github.com/google/btree v1.1.2
github.com/google/cel-go v0.18.2
github.com/google/gofuzz v1.2.0
github.com/gorilla/websocket v1.5.1
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
@ -103,9 +105,12 @@ require (
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.28.0
pgregory.net/rapid v1.1.0
sigs.k8s.io/yaml v1.4.0
)
require (
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 // indirect
github.com/alecthomas/atomic v0.1.0-alpha2 // indirect
@ -122,6 +127,7 @@ require (
github.com/anacrolix/stm v0.4.1-0.20221221005312-96d17df0e496 // indirect
github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96 // indirect
github.com/anacrolix/utp v0.1.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/aws/aws-sdk-go-v2 v1.21.2 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.14 // indirect
github.com/aws/aws-sdk-go-v2/config v1.19.0 // indirect
@ -178,6 +184,7 @@ require (
github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/ianlancetaylor/cgosymbolizer v0.0.0-20220405231054-a1ae3e4bba26 // indirect
github.com/imdario/mergo v0.3.11 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/ipfs/go-cid v0.4.1 // indirect
github.com/ipfs/go-log/v2 v2.5.1 // indirect
@ -207,7 +214,9 @@ require (
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect
github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mitchellh/copystructure v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.0 // indirect
github.com/mmcloughlin/addchain v0.4.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
@ -256,9 +265,12 @@ require (
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/sosodev/duration v1.1.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/stoewer/go-strcase v1.2.0 // indirect
github.com/supranational/blst v0.3.11 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
@ -270,6 +282,7 @@ require (
golang.org/x/mod v0.14.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.16.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 // indirect
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect

34
go.sum
View File

@ -55,6 +55,12 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Giulio2002/bls v0.0.0-20230906201036-c2330c97dc7d h1:fAztVLpjcVcd2al4GL8xYr9Yp7LmXXSTuLqu83U8hKo=
github.com/Giulio2002/bls v0.0.0-20230906201036-c2330c97dc7d/go.mod h1:nCQrFU6/QsJtLS+SBLWRn9UG2nds1f3hQKfWHCrtUqw=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w=
github.com/RoaringBitmap/roaring v0.4.17/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrXIr8KVNvRsDi1NI=
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
@ -137,6 +143,8 @@ github.com/anacrolix/utp v0.1.0 h1:FOpQOmIwYsnENnz7tAGohA+r6iXpRjrq8ssKSre2Cp4=
github.com/anacrolix/utp v0.1.0/go.mod h1:MDwc+vsGEq7RMw6lr2GKOEqjWny5hO5OZXRVNaBJ2Dk=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
@ -407,6 +415,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/cel-go v0.18.2 h1:L0B6sNBSVmt0OyECi8v6VOS74KOc9W/tLiWKfZABvf4=
github.com/google/cel-go v0.18.2/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@ -419,6 +429,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@ -443,6 +454,7 @@ github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b h1:h9U78+dx9a4BKdQkBB
github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
@ -480,6 +492,7 @@ github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63
github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huin/goupnp v1.2.0 h1:uOKW26NG1hsSSbXIZ1IR7XP9Gjd1U8pnLaCMgntmkmY=
@ -488,6 +501,8 @@ github.com/ianlancetaylor/cgosymbolizer v0.0.0-20220405231054-a1ae3e4bba26 h1:UT
github.com/ianlancetaylor/cgosymbolizer v0.0.0-20220405231054-a1ae3e4bba26/go.mod h1:DvXTE/K/RtHehxU8/GtDs4vFtfw64jJ3PaCnFri8CRg=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
@ -611,8 +626,12 @@ github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8Rv
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY=
github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU=
github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU=
@ -808,6 +827,8 @@ github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5P
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
@ -848,10 +869,14 @@ github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0b
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
@ -955,6 +980,7 @@ golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220516162934-403b01795ae8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
@ -1051,6 +1077,7 @@ golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
@ -1151,6 +1178,7 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -1162,6 +1190,7 @@ golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
@ -1174,6 +1203,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
@ -1321,6 +1351,8 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU=
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 h1:6GQBEOdGkX6MMTLT9V+TjtIRZCw9VPD5Z+yHY9wMgS0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
@ -1429,6 +1461,8 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU=
rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
zombiezen.com/go/sqlite v0.13.1 h1:qDzxyWWmMtSSEH5qxamqBFmqA2BLSSbtODi3ojaE02o=