-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
beaconserver: simulated beacon api server for op-stack (#2678)
only some necessary apis are implemented.
- Loading branch information
Showing
8 changed files
with
470 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package fakebeacon | ||
|
||
import ( | ||
"context" | ||
"sort" | ||
|
||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/crypto/kzg4844" | ||
"github.com/ethereum/go-ethereum/internal/ethapi" | ||
"github.com/ethereum/go-ethereum/log" | ||
"github.com/ethereum/go-ethereum/rpc" | ||
) | ||
|
||
type BlobSidecar struct { | ||
Blob kzg4844.Blob `json:"blob"` | ||
Index int `json:"index"` | ||
KZGCommitment kzg4844.Commitment `json:"kzg_commitment"` | ||
KZGProof kzg4844.Proof `json:"kzg_proof"` | ||
} | ||
|
||
type APIGetBlobSidecarsResponse struct { | ||
Data []*BlobSidecar `json:"data"` | ||
} | ||
|
||
type ReducedGenesisData struct { | ||
GenesisTime string `json:"genesis_time"` | ||
} | ||
|
||
type APIGenesisResponse struct { | ||
Data ReducedGenesisData `json:"data"` | ||
} | ||
|
||
type ReducedConfigData struct { | ||
SecondsPerSlot string `json:"SECONDS_PER_SLOT"` | ||
} | ||
|
||
type IndexedBlobHash struct { | ||
Index int // absolute index in the block, a.k.a. position in sidecar blobs array | ||
Hash common.Hash // hash of the blob, used for consistency checks | ||
} | ||
|
||
func configSpec() ReducedConfigData { | ||
return ReducedConfigData{SecondsPerSlot: "1"} | ||
} | ||
|
||
func beaconGenesis() APIGenesisResponse { | ||
return APIGenesisResponse{Data: ReducedGenesisData{GenesisTime: "0"}} | ||
} | ||
|
||
func beaconBlobSidecars(ctx context.Context, backend ethapi.Backend, slot uint64, indices []int) (APIGetBlobSidecarsResponse, error) { | ||
var blockNrOrHash rpc.BlockNumberOrHash | ||
header, err := fetchBlockNumberByTime(ctx, int64(slot), backend) | ||
if err != nil { | ||
log.Error("Error fetching block number", "slot", slot, "indices", indices) | ||
return APIGetBlobSidecarsResponse{}, err | ||
} | ||
sideCars, err := backend.GetBlobSidecars(ctx, header.Hash()) | ||
if err != nil { | ||
log.Error("Error fetching Sidecars", "blockNrOrHash", blockNrOrHash, "err", err) | ||
return APIGetBlobSidecarsResponse{}, err | ||
} | ||
sort.Ints(indices) | ||
fullBlob := len(indices) == 0 | ||
res := APIGetBlobSidecarsResponse{} | ||
idx := 0 | ||
curIdx := 0 | ||
for _, sideCar := range sideCars { | ||
for i := 0; i < len(sideCar.Blobs); i++ { | ||
//hash := kZGToVersionedHash(sideCar.Commitments[i]) | ||
if !fullBlob && curIdx >= len(indices) { | ||
break | ||
} | ||
if fullBlob || idx == indices[curIdx] { | ||
res.Data = append(res.Data, &BlobSidecar{ | ||
Index: idx, | ||
Blob: sideCar.Blobs[i], | ||
KZGCommitment: sideCar.Commitments[i], | ||
KZGProof: sideCar.Proofs[i], | ||
}) | ||
curIdx++ | ||
} | ||
idx++ | ||
} | ||
} | ||
|
||
return res, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package fakebeacon | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
"strconv" | ||
"strings" | ||
|
||
"github.com/prysmaticlabs/prysm/v5/api/server/structs" | ||
field_params "github.com/prysmaticlabs/prysm/v5/config/fieldparams" | ||
"github.com/prysmaticlabs/prysm/v5/network/httputil" | ||
) | ||
|
||
var ( | ||
versionMethod = "/eth/v1/node/version" | ||
specMethod = "/eth/v1/config/spec" | ||
genesisMethod = "/eth/v1/beacon/genesis" | ||
sidecarsMethodPrefix = "/eth/v1/beacon/blob_sidecars/{slot}" | ||
) | ||
|
||
func VersionMethod(w http.ResponseWriter, r *http.Request) { | ||
resp := &structs.GetVersionResponse{ | ||
Data: &structs.Version{ | ||
Version: "", | ||
}, | ||
} | ||
httputil.WriteJson(w, resp) | ||
} | ||
|
||
func SpecMethod(w http.ResponseWriter, r *http.Request) { | ||
httputil.WriteJson(w, &structs.GetSpecResponse{Data: configSpec()}) | ||
} | ||
|
||
func GenesisMethod(w http.ResponseWriter, r *http.Request) { | ||
httputil.WriteJson(w, beaconGenesis()) | ||
} | ||
|
||
func (s *Service) SidecarsMethod(w http.ResponseWriter, r *http.Request) { | ||
indices, err := parseIndices(r.URL) | ||
if err != nil { | ||
httputil.HandleError(w, err.Error(), http.StatusBadRequest) | ||
return | ||
} | ||
segments := strings.Split(r.URL.Path, "/") | ||
slot, err := strconv.ParseUint(segments[len(segments)-1], 10, 64) | ||
if err != nil { | ||
httputil.HandleError(w, "not a valid slot(timestamp)", http.StatusBadRequest) | ||
return | ||
} | ||
|
||
resp, err := beaconBlobSidecars(r.Context(), s.backend, slot, indices) | ||
if err != nil { | ||
httputil.HandleError(w, err.Error(), http.StatusBadRequest) | ||
return | ||
} | ||
httputil.WriteJson(w, resp) | ||
} | ||
|
||
// parseIndices filters out invalid and duplicate blob indices | ||
func parseIndices(url *url.URL) ([]int, error) { | ||
rawIndices := url.Query()["indices"] | ||
indices := make([]int, 0, field_params.MaxBlobsPerBlock) | ||
invalidIndices := make([]string, 0) | ||
loop: | ||
for _, raw := range rawIndices { | ||
ix, err := strconv.Atoi(raw) | ||
if err != nil { | ||
invalidIndices = append(invalidIndices, raw) | ||
continue | ||
} | ||
if ix >= field_params.MaxBlobsPerBlock { | ||
invalidIndices = append(invalidIndices, raw) | ||
continue | ||
} | ||
for i := range indices { | ||
if ix == indices[i] { | ||
continue loop | ||
} | ||
} | ||
indices = append(indices, ix) | ||
} | ||
|
||
if len(invalidIndices) > 0 { | ||
return nil, fmt.Errorf("requested blob indices %v are invalid", invalidIndices) | ||
} | ||
return indices, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package fakebeacon | ||
|
||
import ( | ||
"net/http" | ||
"strconv" | ||
|
||
"github.com/ethereum/go-ethereum/internal/ethapi" | ||
"github.com/gorilla/mux" | ||
"github.com/prysmaticlabs/prysm/v5/api/server" | ||
) | ||
|
||
const ( | ||
DefaultAddr = "localhost" | ||
DefaultPort = 8686 | ||
) | ||
|
||
type Config struct { | ||
Enable bool | ||
Addr string | ||
Port int | ||
} | ||
|
||
func defaultConfig() *Config { | ||
return &Config{ | ||
Enable: false, | ||
Addr: DefaultAddr, | ||
Port: DefaultPort, | ||
} | ||
} | ||
|
||
type Service struct { | ||
cfg *Config | ||
router *mux.Router | ||
backend ethapi.Backend | ||
} | ||
|
||
func NewService(cfg *Config, backend ethapi.Backend) *Service { | ||
cfgs := defaultConfig() | ||
if cfg.Addr != "" { | ||
cfgs.Addr = cfg.Addr | ||
} | ||
if cfg.Port > 0 { | ||
cfgs.Port = cfg.Port | ||
} | ||
|
||
s := &Service{ | ||
cfg: cfgs, | ||
backend: backend, | ||
} | ||
router := s.newRouter() | ||
s.router = router | ||
return s | ||
} | ||
|
||
func (s *Service) Run() { | ||
_ = http.ListenAndServe(s.cfg.Addr+":"+strconv.Itoa(s.cfg.Port), s.router) | ||
This comment has been minimized.
Sorry, something went wrong. |
||
} | ||
|
||
func (s *Service) newRouter() *mux.Router { | ||
r := mux.NewRouter() | ||
r.Use(server.NormalizeQueryValuesHandler) | ||
for _, e := range s.endpoints() { | ||
r.HandleFunc(e.path, e.handler).Methods(e.methods...) | ||
} | ||
return r | ||
} | ||
|
||
type endpoint struct { | ||
path string | ||
handler http.HandlerFunc | ||
methods []string | ||
} | ||
|
||
func (s *Service) endpoints() []endpoint { | ||
return []endpoint{ | ||
{ | ||
path: versionMethod, | ||
handler: VersionMethod, | ||
methods: []string{http.MethodGet}, | ||
}, | ||
{ | ||
path: specMethod, | ||
handler: SpecMethod, | ||
methods: []string{http.MethodGet}, | ||
}, | ||
{ | ||
path: genesisMethod, | ||
handler: GenesisMethod, | ||
methods: []string{http.MethodGet}, | ||
}, | ||
{ | ||
path: sidecarsMethodPrefix, | ||
handler: s.SidecarsMethod, | ||
methods: []string{http.MethodGet}, | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package fakebeacon | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"strconv" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
// | ||
//func TestFetchBlockNumberByTime(t *testing.T) { | ||
// blockNum, err := fetchBlockNumberByTime(context.Background(), 1724052941, client) | ||
// assert.Nil(t, err) | ||
// assert.Equal(t, uint64(41493946), blockNum) | ||
// | ||
// blockNum, err = fetchBlockNumberByTime(context.Background(), 1734052941, client) | ||
// assert.Equal(t, err, errors.New("time too large")) | ||
// | ||
// blockNum, err = fetchBlockNumberByTime(context.Background(), 1600153618, client) | ||
// assert.Nil(t, err) | ||
// assert.Equal(t, uint64(493946), blockNum) | ||
//} | ||
// | ||
//func TestBeaconBlobSidecars(t *testing.T) { | ||
// indexBlobHash := []IndexedBlobHash{ | ||
// {Hash: common.HexToHash("0x01231952ecbaede62f8d0398b656072c072db36982c9ef106fbbc39ce14f983c"), Index: 0}, | ||
// {Hash: common.HexToHash("0x012c21a8284d2d707bb5318e874d2e1b97a53d028e96abb702b284a2cbb0f79c"), Index: 1}, | ||
// {Hash: common.HexToHash("0x011196c8d02536ede0382aa6e9fdba6c460169c0711b5f97fcd701bd8997aee3"), Index: 2}, | ||
// {Hash: common.HexToHash("0x019c86b46b27401fb978fd175d1eb7dadf4976d6919501b0c5280d13a5bab57b"), Index: 3}, | ||
// {Hash: common.HexToHash("0x01e00db7ee99176b3fd50aab45b4fae953292334bbf013707aac58c455d98596"), Index: 4}, | ||
// {Hash: common.HexToHash("0x0117d23b68123d578a98b3e1aa029661e0abda821a98444c21992eb1e5b7208f"), Index: 5}, | ||
// //{Hash: common.HexToHash("0x01e00db7ee99176b3fd50aab45b4fae953292334bbf013707aac58c455d98596"), Index: 1}, | ||
// } | ||
// | ||
// resp, err := beaconBlobSidecars(context.Background(), 1724055046, []int{0, 1, 2, 3, 4, 5}) // block: 41494647 | ||
// assert.Nil(t, err) | ||
// assert.NotNil(t, resp) | ||
// assert.NotEmpty(t, resp.Data) | ||
// for i, sideCar := range resp.Data { | ||
// assert.Equal(t, indexBlobHash[i].Index, sideCar.Index) | ||
// assert.Equal(t, indexBlobHash[i].Hash, kZGToVersionedHash(sideCar.KZGCommitment)) | ||
// } | ||
// | ||
// apiscs := make([]*BlobSidecar, 0, len(indexBlobHash)) | ||
// // filter and order by hashes | ||
// for _, h := range indexBlobHash { | ||
// for _, apisc := range resp.Data { | ||
// if h.Index == int(apisc.Index) { | ||
// apiscs = append(apiscs, apisc) | ||
// break | ||
// } | ||
// } | ||
// } | ||
// | ||
// assert.Equal(t, len(apiscs), len(resp.Data)) | ||
// assert.Equal(t, len(apiscs), len(indexBlobHash)) | ||
//} | ||
|
||
type TimeToSlotFn func(timestamp uint64) (uint64, error) | ||
|
||
// GetTimeToSlotFn returns a function that converts a timestamp to a slot number. | ||
func GetTimeToSlotFn(ctx context.Context) (TimeToSlotFn, error) { | ||
genesis := beaconGenesis() | ||
config := configSpec() | ||
|
||
genesisTime, _ := strconv.ParseUint(genesis.Data.GenesisTime, 10, 64) | ||
secondsPerSlot, _ := strconv.ParseUint(config.SecondsPerSlot, 10, 64) | ||
if secondsPerSlot == 0 { | ||
return nil, fmt.Errorf("got bad value for seconds per slot: %v", config.SecondsPerSlot) | ||
} | ||
timeToSlotFn := func(timestamp uint64) (uint64, error) { | ||
if timestamp < genesisTime { | ||
return 0, fmt.Errorf("provided timestamp (%v) precedes genesis time (%v)", timestamp, genesisTime) | ||
} | ||
return (timestamp - genesisTime) / secondsPerSlot, nil | ||
} | ||
return timeToSlotFn, nil | ||
} | ||
|
||
func TestAPI(t *testing.T) { | ||
slotFn, err := GetTimeToSlotFn(context.Background()) | ||
assert.Nil(t, err) | ||
|
||
expTx := uint64(123151345) | ||
gotTx, err := slotFn(expTx) | ||
assert.Nil(t, err) | ||
assert.Equal(t, expTx, gotTx) | ||
} |
Oops, something went wrong.
check this line