forked from bnb-chain/bsc
-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
443 additions
and
0 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,100 @@ | ||
package server_for_op_stack | ||
|
||
import ( | ||
"context" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/crypto/kzg4844" | ||
"github.com/ethereum/go-ethereum/ethclient" | ||
"github.com/ethereum/go-ethereum/log" | ||
"github.com/ethereum/go-ethereum/rpc" | ||
"sort" | ||
) | ||
|
||
var ( | ||
client *ethclient.Client | ||
) | ||
|
||
func Init(nodeURL string) { | ||
var err error | ||
client, err = ethclient.Dial(nodeURL) | ||
if err != nil { | ||
log.Crit("Error connecting to client", "nodeURL", nodeURL, "error", err) | ||
} | ||
} | ||
|
||
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, slot uint64, indices []int) (APIGetBlobSidecarsResponse, error) { | ||
var blockNrOrHash rpc.BlockNumberOrHash | ||
blockNum, err := fetchBlockNumberByTime(ctx, int64(slot), client) | ||
if err != nil { | ||
log.Error("Error fetching block number", "slot", slot, "indices", indices) | ||
return APIGetBlobSidecarsResponse{}, err | ||
} | ||
rpcBlockNum := rpc.BlockNumber(blockNum) | ||
blockNrOrHash.BlockNumber = &rpcBlockNum | ||
sideCars, err := client.BlobSidecars(ctx, blockNrOrHash) | ||
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,87 @@ | ||
package server_for_op_stack | ||
|
||
import ( | ||
"fmt" | ||
"github.com/prysmaticlabs/prysm/v5/api/server/structs" | ||
field_params "github.com/prysmaticlabs/prysm/v5/config/fieldparams" | ||
"github.com/prysmaticlabs/prysm/v5/network/httputil" | ||
"net/http" | ||
"net/url" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
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 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(), 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,73 @@ | ||
package server_for_op_stack | ||
|
||
import ( | ||
"context" | ||
"net/http" | ||
|
||
"github.com/gorilla/mux" | ||
"github.com/prysmaticlabs/prysm/v5/api/server" | ||
) | ||
|
||
type Config struct { | ||
BSCNodeURL string | ||
HostPort string | ||
} | ||
|
||
type Service struct { | ||
cfg Config | ||
router *mux.Router | ||
} | ||
|
||
func NewService(ctx context.Context, cfg *Config) *Service { | ||
Init(cfg.BSCNodeURL) | ||
router := newRouter() | ||
|
||
return &Service{ | ||
cfg: *cfg, | ||
router: router, | ||
} | ||
} | ||
|
||
func (s *Service) Run() { | ||
_ = http.ListenAndServe(s.cfg.HostPort, s.router) | ||
} | ||
|
||
func newRouter() *mux.Router { | ||
r := mux.NewRouter() | ||
r.Use(server.NormalizeQueryValuesHandler) | ||
for _, e := range endpoints() { | ||
r.HandleFunc(e.path, e.handler).Methods(e.methods...) | ||
} | ||
return r | ||
} | ||
|
||
type endpoint struct { | ||
path string | ||
handler http.HandlerFunc | ||
methods []string | ||
} | ||
|
||
func 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: 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,95 @@ | ||
package server_for_op_stack | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/stretchr/testify/assert" | ||
"strconv" | ||
"testing" | ||
) | ||
|
||
func init() { | ||
Init("https://bsc-mainnet.nodereal.io/v1/{api-key}") | ||
} | ||
|
||
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.